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.
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' },
],
};| sku, string | name, string | price, float | tags, Array<string> | inStock, boolean | category, → Category |
|---|---|---|---|---|---|
| SKU-1001 | NYM-J cable 3x1.5, 50 m | 42.9 | indoor3-core | true | cable |
| SKU-2001 | Rocker switch, white | 6.9 | flush | true | switch |
| SKU-2002 | LED dimmer switch, white | 48 | ledflush | false | switch |
| SKU-3001 | LED panel 620×620, 36 W | 89.5 | ledceiling | true | lighting |
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.
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.
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.
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.
// 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 automaticallyModel 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 |
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 totrue, so a missing value is reported unless you setrequired: falsenullable- Allownull(defaultfalse)forceStrictDataType- Report and drop wrong-typed values instead of converting them (defaultfalse)validators- Validation rules:email,pattern,startsWith,endsWith,includes,enum,min/max,minLength/maxLength,unique. They run onstring,intandfloatpropertiesvalidatorHandling-'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.
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.
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,CustomerOrderinstead ofItem,Data - Keep models focused - Each model should represent a single entity
- Use specific types -
floatfor prices,intfor counts,booleanfor flags, notstring - Use typed arrays -
Array<string>,Array<int>, … (there is no untypedarraytype) - Index hot filter attributes -
index: truemoves 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
// 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