One envelope for everything
There is no per-feature schema at the storage layer. Every record — a product, an employee, a community post, a bank transfer — is a BaseModel<T> where T is the feature-specific payload.
interface BaseModel<T> {
pk: string;
sk: string;
name: string;
data?: T;
datatype?: DataType | string;
subschema?: string;
version: number;
state?: ModelState;
createdate?: Date;
modifydate?: Date;
publishedDate?: Date;
author?: string;
created_by?: string;
modified_by?: string;
owner?: { datatype?: DataType; id?: string; name?: string; email?: string };
requiredRole?: RequiredRoleModel;
workflow?: TaskModel;
rules?: any[];
notes?: { author: string; comment: string; date: Date }[];
stats?: BaseModelStats;
share?: BaseModelShare;
post?: PostSubModel;
style?: StyleSubModel;
search?: string;
create_hash?: string;
client?: string;
isNew?: boolean;
}Keys
| Field | Type | Description |
|---|---|---|
sk required | string | The record id. This is what /repository/get/:datatype/:id takes, what author and owner.id point at, and what the guard compares for ownership. Not _id. |
pk required | string | Partition key. Groups related records. |
name required | string | Human-readable label. Present on every record regardless of datatype. |
datatype | `DataType | string` |
subschema | string | A variant within the datatype. Addressed in URLs as datatype-subschema. |
The system-owned field list is exported as baseModelSystemFields — useful when you need to separate platform fields from a user's own. |
What every record gets for free
Because these live on BaseModel rather than on individual features, they work identically for all 261 datatypes.
Lifecycle
state moves through ModelState:
draft → new → pending → inprogress → reviewed → approved → published → completed
with hold, rejected, cancelled, archived and deleted as off-ramps. Driven by:
/repository/request-approval/:datatype/:idJWT/repository/approve/:datatype/:idJWT/repository/reject/:idJWT/repository/publish/:datatype/:idJWT/repository/unpublish/:datatype/:idJWTVersioning and history
version increments on write; prior revisions are retrievable and restorable.
/repository/history/:datatype/:idJWT/repository/history/:restoreJWTDeletes are recoverable — deleted records land in the trash datatype.
/repository/trash-restoreJWTRow-level permissions
requiredRole names the content permissions needed per operation:
{ read?: string[]; create?: string[]; update?: string[]; delete?: string[]; review?: string[]; approve?: string[] }JwtAuthGuard reads requiredRole.update and requiredRole.delete directly. Set them and a record enforces its own access rules, independent of the handler's decorators.
Engagement
stats is on every record, so any datatype can be liked, viewed or rated without new storage:
{ likes?, dislikes?, views?, shares?, bookmarks?, follows?,
averageRating?, ratingCount?,
reactions?: { author, reaction, reacted_at }[],
reactionSummary?: { [type: string]: number },
last_viewed?, last_activity? }The stats controller (/stats/*, part of AnalyticsModule) is the generic engine that maintains it.
Sharing
share is a tokenized public link with optional passcode, expiry and notification:
{ token: string; status: 'active' | 'revoked'; passcodeHash?: string; expiresAt?: string;
notify?: { channel: 'sms' | 'email' | 'whatsapp'; to: string; sentAt?: string }[];
createdBy?: string; createdAt?: string; openedAt?: string }openedAt records first open — enough to tell whether a shared quote was ever looked at.
Publishing metadata
post carries the content-publishing block used by pages, posts and any shareable record: title, summary, allowShare, allowComment, allowRating, showRelated, categories (tree-selected from the category collection), tags (from tag), and images.
images is a file reference with pre-generated size variants:
{ path, url, contentType, isPublic,
meta: { width, height, size, xs: { path, url }, sm: { path, url }, md: { path, url } } }Uploads produce those variants automatically — see Files.
Styling
style lets a record carry its own presentation: a theme (name, darkMode of auto/dark/light, property/value settings), plus raw classes, css, javascript, and arrays of styleLinks / scriptLinks validated against ^https?://.
Schema definition
Schemas are JSON Schema with x- extensions that drive UI generation — the same document validates the data and renders the form.
| Extension | Purpose |
|---|---|
x-control | Which control to render (ControlType.selectMany, .file, .code…) |
x-control-variant | Variant — textarea, chip, combo, tree, css, javascript |
x-render | How to render the value when displaying |
dataSource | Where options come from: { source: 'collection', collection, value, label, children } or { source: 'function', value } |
hidden, hideIn, readOnly | Visibility, optionally scoped to a context such as generator |
collapsible, group, layout, styleClass, styling | Layout hints |
This is why the Studio form builder needs no separate UI metadata — it renders these documents directly.
Collections
A collection is a user-defined datatype, stored as a collection record holding its schema.
/repository/collections/:name?/:subName?JWT/repository/collections/fixJWTcollections/fix reconciles a collection with its schema — creating the MongoDB collection and its indexes if they are missing. createCollection also has a time-series variant for metric-shaped data.
Once defined, a custom collection is addressed exactly like a built-in datatype.
Query responses
List endpoints return BaseModelDTO<T>:
interface BaseModelDTO<T> {
data?: BaseModel<T>[];
total?: number;
datatype?: DataType | string;
page?: number; pageSize?: number; lastPage?: number; lastItem?: number;
hasNext?: boolean;
sort?: any; sortType?: SortType; // asc = 1, desc = -1
modelState?: ModelState | ModelState[];
fromCache?: boolean;
error?: { message: string; code: number; stalk: any };
}DataOptions — the request-side half — additionally accepts refresh, enrich, random, and field controls includeFields, excludeFields, maskFields.
With enrich, the repository follows references and inlines related records (repository.enrichment.service.ts) rather than returning bare ids.