@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
| Parameter | Type | Description |
|---|---|---|
collectionName | string | MongoDB collection name (lowercase, typically plural) |
How It Works
- At class load,
@Entityregisters the class in the entity metadata registry BaseDbContext/MongoContextreads this metadata at startup- A Mongoose Schema and Model are generated for the collection
- All
BaseModel<T>static methods (Find,FindById,Save, etc.) use this Model
Rules
- One
@Entityper class — A class maps to exactly one collection - Collection names are lowercase — Convention:
products,orders,employees - Class name can differ —
@Entity("products")on classProductRecordis 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).
| Option | Type | Default | Description |
|---|---|---|---|
type | class / [class] | — | Field type. Primitives: String, Number, Boolean, Date, Buffer. Arrays: [String]. Models: ModelClass / [ModelClass] |
symbol | string | camelCase of the property name | Name of the stored MongoDB field. Use it in queries, sorts and indexes (FullName → fullName). Never put spaces or labels here |
required | boolean | true | Mongoose schema-level required — Save() fails when the value is missing. Mark every optional field required: false |
default | any | — | Value set on new instances (functions are called, arrays/objects/dates are copied) |
validator | IValidator instance | new PropertyValidator() (accepts everything) | Custom format check run by Validate() and before Save() |
converter | IConverter instance | new IdentityConverter() | DB ↔ model transformation, e.g. new GroupToRefConverter() |
alias | string | — | Mongoose alias for the stored field |
addId | boolean | false | Let Mongoose generate _id inside object-typed sub-fields |
noJSON | boolean | false | Exclude from ToJSON() / ToInterface() output (also from JSON.stringify) |
noSerialize | boolean | false | Serialize only the referenced record's id instead of its full data (unless expanded) |
noResolve | boolean | false | Find() does not resolve the reference — only the id is loaded |
protected | boolean | false | Write-protected: SyncWith() skips the property, so client input cannot set it. Only an explicit setter call writes it — see Protected Properties |
noAutoListValidation | boolean | false | Skip validation of individual array items |
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;
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 option | Mongoose schema effect |
|---|---|
symbol | Field name |
type | Schema type (String, Number, etc.; model references become String) |
required | Schema-level required |
default | Default value in schema |
alias | Field 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
employeesMongoDB collection with fieldsfullName,email,age,department,skills,status,homeAddress,isManager - Has 8 declared properties + 5 auto-managed ones (Id, Deleted, LastModified, Created, ClassName)
- Requires
FullNameandEmail(defaultrequired: 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