.md

Models

Define the structure and schema of your data with properties, types, and references.

What is a model?

A ModelDefinition is a configuration object that describes the structure and schema of your data.

  • Properties: the fields in your data structure
  • Data types: the type of each property (string, int, boolean…)
  • References: relationships to other models
  • Data: optional inline data

Think of models as blueprints or table schemas for your data.

product.model.ts
import type { ModelDefinition } from '@dcupl/common';

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'sku', type: 'string', index: true },
    { key: 'price', type: 'float' },
    { key: 'tags', type: 'Array<string>' },
    { key: 'inStock', type: 'boolean' },
  ],
  references: [
    { key: 'category', type: 'singleValued', model: 'Category' },
  ],
};
Product5 properties · 1 referenceDemo data
sku, stringname, stringprice, floattags, Array<string>inStock, booleancategory, → Category
SKU-1001 NYM-J cable 3x1.5, 50 m 42.9 indoor3-coretruecable
SKU-2001 Rocker switch, white 6.9 flushtrueswitch
SKU-2002 LED dimmer switch, white 48 ledflushfalseswitch
SKU-3001 LED panel 620×620, 36 W 89.5 ledceilingtruelighting

Point at a property in the code to find its column. category holds the key of a Category item.

Property types

dcupl supports these data types:

Type Description Example
string Text values "Laptop"
int Whole numbers 42
float Decimal numbers 99.99
boolean True/false true
date Date/time values "2025-01-15"
json Complex nested objects { color: 'red' }
any Any value, stored as is 42, 'a'
Array<string>, Array<int>, Array<float>, Array<date> Typed arrays ['red', 'blue']

A boolean property also reads the strings 'true'/'false'/'1'/'0' (any case) and the numbers 1/0. Any other string or number, and any object or array, is a WrongDataType quality error, and the property stays undefined. With quality.forceStrictDataType, only true/false pass.

There is no plain array type. A property with any other type is rejected with an InvalidModelDefinition quality error ("Property type is invalid"), and its values are not loaded.

property-types.ts
const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
    { key: 'rating', type: 'float' },
    { key: 'inStock', type: 'boolean' },
    { key: 'createdAt', type: 'date' },
    { key: 'metadata', type: 'json' },
    { key: 'tags', type: 'Array<string>' },
  ],
};

Property options

Properties support additional options for indexing, computed values, and validation.

index

Queries and facets work without it: dcupl builds a property's lookup index on the first query that needs it. index: true builds it at load time instead, so use it for attributes you filter on often.

derive

Create computed properties from references. Pull values from referenced models automatically.

expression

Template-based computed properties using ${property} placeholders. The result is always a string.

quality

Data validation rules: required fields and built-in validators (email, pattern, min/max, enum, unique, …).

More options (filter, aggregate, origin, separator, inputFormat and UTC for dates, fractionDigits) are in the Models API reference.

property-options.ts
const orderModel: ModelDefinition = {
  key: 'Order',
  properties: [
    // Index built at load time instead of on first query
    { key: 'orderId', type: 'string', index: true },

    // Derived from reference
    {
      key: 'customerName',
      type: 'string',
      derive: {
        localReference: 'customer',
        remoteProperty: 'name',
      },
    },

    // Template expression
    {
      key: 'displayText',
      type: 'string',
      expression: 'Order #${orderId} - ${customerName}',
    },

    // Validation rules
    {
      key: 'email',
      type: 'string',
      quality: {
        required: true,
        validators: { email: {} },
      },
    },
  ],
  references: [
    { key: 'customer', type: 'singleValued', model: 'Customer' },
  ],
};

References

References link one model to another, enabling:

  • Navigate between related data
  • Query across relationships (deep queries)
  • Build complex data structures
  • Maintain data normalization

Single-valued (many-to-one)

Each item references one other item. Use for: Order → Customer, Product → Category.

Multi-valued (to many)

Each item holds a list of keys; the same target can be referenced by many items. Use for: Order → Products, User → Roles.

References can also be computed (resolve, derive, groupBy, query) or check their keys (validity): see Reference in the Models API.

references.ts
const orderModel: ModelDefinition = {
  key: 'Order',
  properties: [
    { key: 'total', type: 'int' },
  ],
  references: [
    // Single-valued: one customer per order
    {
      key: 'customer',
      type: 'singleValued',
      model: 'Customer',
    },
    // Multi-valued: many products per order
    {
      key: 'products',
      type: 'multiValued',
      model: 'Product',
    },
  ],
};

// Data format:
// { key: 'o1', total: 150, customer: 'c1', products: ['p1', 'p2'] }

Resolved references

Create the inverse of another reference automatically using resolve.

Instead of manually maintaining both directions of a relationship, dcupl can compute the inverse for you.

Example: If Order → Customer exists, you can automatically get Customer → Orders.

The resolved reference is computed at initialization time and stays in sync with your data.

resolved-refs.ts
// Customer model with inverse reference
const customerModel: ModelDefinition = {
  key: 'Customer',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'email', type: 'string' },
  ],
  references: [
    {
      key: 'orders',
      type: 'multiValued',
      model: 'Order',
      // Automatically resolve from Order.customer
      resolve: { reference: 'customer', model: 'Order' },
    },
  ],
};

// Now customer.orders contains all orders
// for that customer automatically

Model options

Models support additional configuration options:

Option Description
keyProperty Use a custom property as item identifier (default: 'key'). An array builds a composite key: ['ID', 'Language'] gives items keys like 'A1::en'
keyPropertySeparator Separator joining the parts of a composite key (default: '::')
autoGenerateKey Generate unique keys for items without one
autoGenerateProperties Infer properties from the data: true reads the first row and checks number columns against every row, { deep: true } all rows, { sampleSize: n } up to n rows. String values like "42" or "true" are typed as numbers, booleans, or dates; zero-padded values like "007" stay strings.
supportsAutoCreation Auto-create an item of this model when another model references a key it doesn't have
valueMappings Transform values during data loading
meta Attach metadata to the model
quality Configure validation rules
model-options.ts
const productModel: ModelDefinition = {
  key: 'Product',

  // Use 'sku' as identifier instead of 'key'
  keyProperty: 'sku',

  // Require explicit keys
  autoGenerateKey: false,

  // Don't auto-create a Product when another model
  // references a Product key that doesn't exist
  supportsAutoCreation: false,

  // Attach metadata
  meta: {
    name: 'Products',
    icon: 'package',
  },

  properties: [
    { key: 'sku', type: 'string' },
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
  ],
};

Quality

Define quality criteria to validate your data at both model and attribute level.

Model-level quality

Set defaults for the quality flags of all attributes of a model in quality.attributes. Validators can only be set per attribute. quality.enabled: false turns the checks off for the model.

Attribute-level quality

Override or extend quality rules for specific properties:

  • required - Property must have a value. Defaults to true, so a missing value is reported unless you set required: false
  • nullable - Allow null (default false)
  • forceStrictDataType - Report and drop wrong-typed values instead of converting them (default false)
  • validators - Validation rules: email, pattern, startsWith, endsWith, includes, enum, min/max, minLength/maxLength, unique. They run on string, int and float properties
  • validatorHandling - 'loose' (default) reports a failed validator and keeps the value, 'strict' also drops it

Quality issues are reported during data loading. Read the totals with dcupl.quality.errors.summary() and the individual errors by querying the $DcuplErrorTrackingErrors model.

quality.ts
const userModel: ModelDefinition = {
  key: 'User',

  // Model-level quality: default rules for all attributes
  quality: {
    attributes: { required: false },
  },

  properties: [
    // Override: make this property required
    {
      key: 'email',
      type: 'string',
      quality: {
        required: true,
        validators: { email: {} },
      },
    },

    // Regex validation
    {
      key: 'phone',
      type: 'string',
      quality: {
        validators: {
          pattern: { value: '^\\+?[0-9]{10,14}$' },
        },
      },
    },

    // Prefix validation
    {
      key: 'website',
      type: 'string',
      quality: {
        validators: { startsWith: { value: 'https://' } },
      },
    },

    // No quality override - uses model defaults
    { key: 'nickname', type: 'string' },
  ],
};

Meta

Attach custom metadata to models and attributes for documentation, tooling, or frontend logic.

Model meta

Describe the model itself:

  • Display names and descriptions
  • Data source information
  • Icons or UI hints

Attribute meta

Describe individual properties:

  • Labels for forms or tables
  • Tooltips and help text
  • Formatting hints (currency, percentage)
  • Source system field mappings

Meta is a free-form object with a required name and an optional description - structure the rest however fits your needs.

meta.ts
const productModel: ModelDefinition = {
  key: 'Product',

  // Model-level metadata
  meta: {
    name: 'Products',
    description: 'Product catalog from ERP system',
    source: {
      system: 'SAP',
      table: 'MARA',
      refreshInterval: '15min',
    },
    ui: {
      icon: 'package',
      color: '#3b82f6',
    },
  },

  properties: [
    {
      key: 'name',
      type: 'string',
      // Attribute-level metadata
      meta: {
        name: 'Product Name',
        description: 'Official product name from catalog',
        searchable: true,
      },
    },
    {
      key: 'price',
      type: 'float',
      meta: {
        name: 'Price',
        format: 'currency',
        currency: 'EUR',
        source: { field: 'NETPR' },
      },
    },
    {
      key: 'rating',
      type: 'float',
      meta: {
        name: 'Customer Rating',
        format: 'stars',
        min: 0,
        max: 5,
      },
    },
  ],
};

Best practices

  • Use clear, descriptive keys - Product, CustomerOrder instead of Item, Data
  • Keep models focused - Each model should represent a single entity
  • Use specific types - float for prices, int for counts, boolean for flags, not string
  • Use typed arrays - Array<string>, Array<int>, … (there is no untyped array type)
  • Index hot filter attributes - index: true moves the index build from the first query to load time; every index costs memory
  • Choose the right reference type - Single-valued for one key per item, multi-valued for a list of keys
  • Limit reference depth - Deep queries (3+ levels) add overhead
best-practices.ts
// Good: Clear types, an index for a hot filter attribute
const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'sku', type: 'string', index: true },
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
    { key: 'rating', type: 'float' },
    { key: 'inStock', type: 'boolean' },
    { key: 'tags', type: 'Array<string>' },
  ],
  references: [
    { key: 'category', type: 'singleValued', model: 'Category' },
  ],
};

// Avoid: String types for everything
// { key: 'price', type: 'string' }  // Bad
// { key: 'count', type: 'string' }  // Bad