Adding a Data Object
Data objects represent database entities in the frontend. They are schema-driven, type-safe, and registered in a central factory so the sync engine can create them dynamically. This guide walks through adding a new DataObject + registering it in the model.
Architecture Overview
Schema definition → CreateObject() → Base class → Your class (extends base)
↓
ObjectFactory.register()
- Schema: A
constobject describing the table name, fields, and relations. - CreateObject(): A factory function from
libs/upp-data/src/modules/model/item.tsthat dynamically generates a typed base class from the schema. - Your class: Extends the generated base class, adds domain logic.
- ObjectFactory: Singleton that maps table names to constructors for dynamic instantiation.
Step 1 — Define the Schema
Create a new file at libs/upp-data/src/modules/model/objects/<name>.ts:
import { dataService } from '../../data';
import { DataObject, ObjectOptions } from '../base';
import { CreateObject } from '../item';
const _schema = {
name: 'MYTABLE',
fields: [
{ name: 'title', type: 'string' },
{ name: 'amount', type: 'number' },
{ name: 'status', type: 'string', default: 'AC' },
{ name: 'parentId', type: 'objid' },
],
relate: [],
} as const;
Field Types
| Type | TypeScript type | Description |
|---|---|---|
'string' | string | Text field |
'number' | number | Numeric field |
'objid' | string | Foreign key (object ID reference) |
Relations
Relations are defined in the relate array:
relate: [
{
direction: '>', // '>' = parent-to-child, '<' = child-to-parent
target: 'CHILDTABLE', // Target table name
by: 'children', // Accessor name on the parent
name: 'children', // Relation name
reason: 'parentId', // Foreign key field
class: ChildTableType, // Type reference (from model.ts)
},
]
Use as const on the schema object to enable full type inference.
Step 2 — Create the Base Class
const _MyTableClass = CreateObject(_schema);
if (!_MyTableClass) {
throw new Error("Failed to create BaseClass for '" + _schema.name + "'");
}
The CreateObject() function generates a class with typed getters and setters for every field defined in the schema.
Step 3 — Define Your Class
export class MyTable extends _MyTableClass {
constructor(
objid: string | null,
data: dataService,
objoptions: ObjectOptions | null = null,
) {
super(_schema, objid, data, objoptions || {});
}
Copy(store: Array<DataObject> = []): MyTable {
return this._Copy(store) as MyTable;
}
}
Conventions
- The constructor receives
objid(nullable for new objects),data(the data service), andobjoptions. - Implement
Copy()to support object duplication, delegating to_Copy()from the base class. - Add domain-specific methods and computed properties as needed.
Step 4 — Register in the Model
Open libs/upp-data/src/modules/model/model.ts and:
- Import your class:
import { MyTable } from './objects/mytable';
- Export a type reference (breaks circular dependency):
export const MyTableType = null as unknown as abstract new (...args: any[]) => MyTable;
- Register in
ObjectRegistry():
export function ObjectRegistry() {
// ... existing registrations
ObjectFactory.register('MYTABLE', MyTable);
}
The string key ('MYTABLE') must match the name in your schema. This is the key the sync engine uses to instantiate objects.
Complete Example: Address
The Address object is one of the simplest examples in the codebase:
import { dataService } from '../../data';
import { DataObject, ObjectOptions } from '../base';
import { CreateObject } from '../item';
const _schema = {
name: 'ADDRESS',
fields: [
{ name: 'formatted', type: 'string' },
{ name: 'door', type: 'string' },
{ name: 'latitude', type: 'number' },
{ name: 'longitude', type: 'number' },
{ name: 'accuracy', type: 'number' },
{ name: 'timezone', type: 'string', default: 'UTC' },
{ name: 'street', type: 'string' },
{ name: 'number', type: 'string' },
{ name: 'zipcode', type: 'string' },
{ name: 'town', type: 'string' },
{ name: 'province', type: 'string' },
{ name: 'country', type: 'string' },
],
relate: [],
} as const;
const _AddressClass = CreateObject(_schema);
if (!_AddressClass) {
throw new Error("Failed to create BaseClass for '" + _schema.name + "'");
}
export class Address extends _AddressClass {
constructor(
objid: string | null,
data: dataService,
objoptions: ObjectOptions | null = null,
) {
super(_schema, objid, data, objoptions || {});
}
Copy(store: Array<DataObject> = []): Address {
return this._Copy(store) as Address;
}
}
The ObjectFactory
The ObjectFactory is a singleton defined in libs/upp-data/src/modules/model/factory.ts. It provides:
| Method | Description |
|---|---|
register(name, cls) | Register a class constructor under a table name |
getClass(name) | Retrieve the constructor for a table name |
object(table, objid, data, opts?) | Create or retrieve an instance — first checks the data store, then falls back to the constructor |
The sync engine calls ObjectFactory.object(table, objid, data) to instantiate objects received from the server.
Checklist
- Schema defined with
as const - Base class generated via
CreateObject() - Class extends the base, implements
Copy() - Imported and type-exported in
model.ts - Registered in
ObjectRegistry() - Server-side table exists with matching column names