Skip to main content

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()
  1. Schema: A const object describing the table name, fields, and relations.
  2. CreateObject(): A factory function from libs/upp-data/src/modules/model/item.ts that dynamically generates a typed base class from the schema.
  3. Your class: Extends the generated base class, adds domain logic.
  4. 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

TypeTypeScript typeDescription
'string'stringText field
'number'numberNumeric field
'objid'stringForeign 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), and objoptions.
  • 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:

  1. Import your class:
import { MyTable } from './objects/mytable';
  1. Export a type reference (breaks circular dependency):
export const MyTableType = null as unknown as abstract new (...args: any[]) => MyTable;
  1. 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:

MethodDescription
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