Skip to main content

@Entity & @Property

These two decorators form the foundation of the Oidis model system. Together they map TypeScript classes to MongoDB collections and define how fields are stored, validated, and serialized.

Package: io-oidis-commons

import { Entity, Property } from "@io-oidis-commons/Io/Oidis/Commons/Primitives/Decorators.js";

@Entity​

Maps a model class to a MongoDB collection.

Syntax​

@Entity("collectionName")
export class MyRecord extends BaseModel<MyRecord> { ... }

Parameters​

ParameterTypeDescription
collectionNamestringMongoDB collection name (lowercase, typically plural)

How It Works​

  1. At class load, @Entity registers the class in the entity metadata registry
  2. BaseDbContext / MongoContext reads this metadata at startup
  3. A Mongoose Schema and Model are generated for the collection
  4. All BaseModel<T> static methods (Find, FindById, Save, etc.) use this Model

Rules​

  • One @Entity per class — A class maps to exactly one collection
  • Collection names are lowercase — Convention: products, orders, employees
  • Class name can differ — @Entity("products") on class ProductRecord is fine
  • Required for DB-persisted models — Frontend-only models (extending hub-gui base without own DB) may omit @Entity

@Property​

Declares a field on a model class with its type, validation, and serialization behavior.

Syntax​

@Property({type: String})
public declare FieldName : string;

Options (IPropertyOptions)​

Source: io-oidis-commons Primitives/Decorators.ts (Property, IPropertyOptions) and Interfaces/IBaseObject.ts (ISchemaOptions).

OptionTypeDefaultDescription
typeclass / [class]—Field type. Primitives: String, Number, Boolean, Date, Buffer. Arrays: [String]. Models: ModelClass / [ModelClass]
symbolstringcamelCase of the property nameName of the stored MongoDB field. Use it in queries, sorts and indexes (FullName → fullName). Never put spaces or labels here
requiredbooleantrueMongoose schema-level required — Save() fails when the value is missing. Mark every optional field required: false
defaultany—Value set on new instances (functions are called, arrays/objects/dates are copied)
validatorIValidator instancenew PropertyValidator() (accepts everything)Custom format check run by Validate() and before Save()
converterIConverter instancenew IdentityConverter()DB ↔ model transformation, e.g. new GroupToRefConverter()
aliasstring—Mongoose alias for the stored field
addIdbooleanfalseLet Mongoose generate _id inside object-typed sub-fields
noJSONbooleanfalseExclude from ToJSON() / ToInterface() output (also from JSON.stringify)
noSerializebooleanfalseSerialize only the referenced record's id instead of its full data (unless expanded)
noResolvebooleanfalseFind() does not resolve the reference — only the id is loaded
protectedbooleanfalseWrite-protected: SyncWith() skips the property, so client input cannot set it. Only an explicit setter call writes it — see Protected Properties
noAutoListValidationbooleanfalseSkip validation of individual array items
Options that do not exist

There is no unique or index option — the schema generator only emits type, required, default, alias and the _id flag. Create indexes explicitly through the Mongoose model (MyRecord.Model().collection.createIndex(...)), using the stored (symbol) field names.

Type Reference​

// Primitives
@Property({type: String}) // → string
@Property({type: Number}) // → number
@Property({type: Boolean}) // → boolean
@Property({type: Date}) // → Date

// Arrays of primitives
@Property({type: [String], default: [], converter: new ArrayConverter()}) // → string[]

// Reference to another model — stored as the referenced record's id (string)
@Property({type: OtherModel, converter: new OtherModelToRefConverter()})

// Array of model references
@Property({type: [OtherModel], default: [], converter: new OtherModelListToRefConverter()})

// Embedded subdocument
@Property({type: SubDocument, converter: new InclusionConverter(SubDocument)})

The declare Keyword​

Always use declare for @Property fields:

// ✅ Correct — decorator handles initialization
@Property({type: String})
public declare Name : string;

// ❌ Incorrect — conflicts with decorator initialization
@Property({type: String})
public Name : string;

The declare keyword tells TypeScript the property exists without generating initialization code that would interfere with the decorator's metadata registration.

Validation​

required is enforced by the generated Mongoose schema when the document is saved. The default PropertyValidator accepts any value, so Validate() alone does not detect a missing required field.

Because required defaults to true, a model with an unset optional field cannot be saved (and therefore not soft-deleted either — Delete() calls Save()) until that field is declared required: false.

For format validation, provide a validator instance. Validators live in io-oidis-hub-gui DAO/Validators/:

import { EmailValidator } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/DAO/Validators/EmailValidator.js";
import { RangeValidator } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/DAO/Validators/RangeValidator.js";
import { RegexValidator } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/DAO/Validators/RegexValidator.js";

@Property({type: String, required: false, validator: new EmailValidator()})
public declare Email : string;

@Property({type: Number, required: false, validator: new RangeValidator<number>(0, 100)})
public declare Percentage : number;

@Property({type: String, validator: new RegexValidator(/^[A-Z]{3}-\d{4}$/)})
public declare Code : string;

RangeValidator and RegexValidator accept an optional $allowEmpty last argument.

Converters​

Converters transform values between the database and model instances. Always pass an instance:

// Reference: DB stores the referenced record's id, model holds the resolved object
@Property({type: Group, converter: new GroupToRefConverter()})
public declare Group : Group;

// Embedded: DB stores the full subdocument inline
@Property({type: Address, converter: new InclusionConverter(Address)})
public declare Address : Address;
Saving a parent saves its references

BaseToRefConverter.to() calls Save() on every referenced model object assigned to the property. Assign an id string instead of an object when the referenced record must not be written.

See Converters for details on writing custom converters.

Schema Generation​

When the application starts, MongoContext reads @Entity and @Property metadata to generate Mongoose schemas (io-oidis-commons DAO/BaseDbContext.ts):

@Property optionMongoose schema effect
symbolField name
typeSchema type (String, Number, etc.; model references become String)
requiredSchema-level required
defaultDefault value in schema
aliasField alias
addId_id flag of object-typed fields

converter, validator and the no* modifiers are applied by BaseModel itself, not by the schema.

You rarely interact with Mongoose schemas directly — BaseModel<T> abstracts all database operations.

Complete Example​

import { BaseModel } from "@io-oidis-commons/Io/Oidis/Commons/Primitives/BaseModel.js";
import { Entity, Property } from "@io-oidis-commons/Io/Oidis/Commons/Primitives/Decorators.js";
import { ArrayConverter } from "@io-oidis-commons/Io/Oidis/Commons/DAO/Converters/ArrayConverter.js";
import { InclusionConverter } from "@io-oidis-commons/Io/Oidis/Commons/DAO/Converters/InclusionConverter.js";
import { EmailValidator } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/DAO/Validators/EmailValidator.js";
import { RangeValidator } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/DAO/Validators/RangeValidator.js";

@Entity("employees")
export class Employee extends BaseModel<Employee> {

@Property({type: String})
public declare FullName : string;

@Property({type: String, validator: new EmailValidator()})
public declare Email : string;

@Property({type: Number, required: false, validator: new RangeValidator<number>(18, 99, true)})
public declare Age : number;

@Property({type: Department, required: false, converter: new DepartmentToRefConverter()})
public declare Department : Department;

@Property({type: [String], default: [], converter: new ArrayConverter()})
public declare Skills : string[];

@Property({type: String, required: false, default: EmployeeStatus.ACTIVE})
public declare Status : string;

@Property({type: Address, required: false, converter: new InclusionConverter(Address)})
public declare HomeAddress : Address;

@Property({type: Boolean, required: false, default: false})
public declare IsManager : boolean;
}

This model:

  • Maps to the employees MongoDB collection with fields fullName, email, age, department, skills, status, homeAddress, isManager
  • Has 8 declared properties + 5 auto-managed ones (Id, Deleted, LastModified, Created, ClassName)
  • Requires FullName and Email (default required: true); everything else is optional
  • Validates email format and age range
  • Stores Department as a reference (the department's id string in DB, full object in memory)
  • Embeds Address as a subdocument
  • Has sensible defaults for Status and IsManager

See also: Models Overview | @Extern | @Route | @Resolver