Derived Properties

Derived properties automatically populate values from referenced models, enabling denormalization and computed fields without manual data duplication. They're perfect for pulling data across relationships to improve query performance and simplify data access.

What are Derived Properties?

A derived property is a computed field that automatically extracts values from a referenced model and stores them locally. Think of it as automatic denormalization.

Benefits:

  • Query local properties instead of deep queries
  • Faster filtering and searching
  • Simplified data access
  • Automatic updates when references change
{
  key: 'customerName',
  type: 'string',
  derive: {
    localReference: 'customer',    // Navigate through this reference
    remoteProperty: 'name',        // Get this property value
  },
}

How it works:

Order (key: 'o1')
  ├─ customer: 'c1'               // Reference to Customer
  └─ customerName: 'Alice'        // Derived from customer.name

Basic Derived Property

Single-Valued Reference Derivation

Pull a property from a single referenced item:

import { Dcupl } from '@dcupl/core';
import type { ModelDefinition } from '@dcupl/common';

const dcupl = new Dcupl();

// Customer model
const customerModel: ModelDefinition = {
  key: 'Customer',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'email', type: 'string' },
    { key: 'country', type: 'string' },
  ],
  data: [
    { key: 'c1', name: 'Alice Johnson', email: 'alice@example.com', country: 'USA' },
    { key: 'c2', name: 'Bob Smith', email: 'bob@example.com', country: 'Canada' },
  ],
};

// Order model with derived property
const orderModel: ModelDefinition = {
  key: 'Order',
  properties: [
    { key: 'orderId', type: 'string' },
    { key: 'total', type: 'int' },

    // Derived property - automatically populated from customer.name
    {
      key: 'customerName',
      type: 'string',
      derive: {
        localReference: 'customer',
        remoteProperty: 'name',
      },
    },
  ],
  references: [{ key: 'customer', type: 'singleValued', model: 'Customer' }],
  data: [
    { key: 'o1', orderId: 'order1', total: 150, customer: 'c1' },
    { key: 'o2', orderId: 'order2', total: 200, customer: 'c1' },
    { key: 'o3', orderId: 'order3', total: 100, customer: 'c2' },
  ],
};

dcupl.models.set(customerModel);
dcupl.models.set(orderModel);
await dcupl.init();

const orderList = dcupl.lists.create({ modelKey: 'Order' });
const orders = orderList.catalog.query.items();

console.log(orders[0].customerName); // 'Alice Johnson' (derived!)

// Filter on derived property (much faster than deep query!)
orderList.catalog.query.addCondition({
  attribute: 'customerName',
  operator: 'eq',
  value: 'Alice Johnson',
});

Multi-Valued Reference Derivation

Derive properties from multi-valued references (arrays):

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'int' },
  ],
  data: [
    { key: 'p1', name: 'Laptop', price: 999 },
    { key: 'p2', name: 'Mouse', price: 29 },
    { key: 'p3', name: 'Keyboard', price: 79 },
  ],
};

const orderModel: ModelDefinition = {
  key: 'Order',
  properties: [
    { key: 'orderId', type: 'string' },

    // Derive product names from multi-valued reference
    {
      key: 'productNames',
      type: 'string',
      derive: {
        localReference: 'products',
        remoteProperty: 'name',
        separator: ', ', // Join multiple values
      },
    },
  ],
  references: [{ key: 'products', type: 'multiValued', model: 'Product' }],
  data: [
    { key: 'o1', orderId: 'order1', products: ['p1', 'p2'] },
    { key: 'o2', orderId: 'order2', products: ['p3'] },
  ],
};

dcupl.models.set(productModel);
dcupl.models.set(orderModel);
await dcupl.init();

const orderList = dcupl.lists.create({ modelKey: 'Order' });
const orders = orderList.catalog.query.items();

console.log(orders[0].productNames); // 'Laptop, Mouse'
console.log(orders[1].productNames); // 'Keyboard'

Chained Derivations

Navigate through multiple levels of references:

const countryModel: ModelDefinition = {
  key: 'Country',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'code', type: 'string' },
  ],
  data: [
    { key: 'us', name: 'United States', code: 'US' },
    { key: 'ca', name: 'Canada', code: 'CA' },
  ],
};

const customerModel: ModelDefinition = {
  key: 'Customer',
  properties: [
    { key: 'name', type: 'string' },

    // Derive country name
    {
      key: 'countryName',
      type: 'string',
      derive: {
        localReference: 'country',
        remoteProperty: 'name',
      },
    },
  ],
  references: [{ key: 'country', type: 'singleValued', model: 'Country' }],
  data: [
    { key: 'c1', name: 'Alice', country: 'us' },
    { key: 'c2', name: 'Bob', country: 'ca' },
  ],
};

const orderModel: ModelDefinition = {
  key: 'Order',
  properties: [
    { key: 'orderId', type: 'string' },

    // Derive customer's country name (2-level derivation)
    {
      key: 'customerCountryName',
      type: 'string',
      derive: {
        localReference: 'customer',
        remoteProperty: 'countryName', // This is already derived!
      },
    },
  ],
  references: [{ key: 'customer', type: 'singleValued', model: 'Customer' }],
  data: [
    { key: 'o1', orderId: 'order1', customer: 'c1' },
    { key: 'o2', orderId: 'order2', customer: 'c2' },
  ],
};

dcupl.models.set(countryModel);
dcupl.models.set(customerModel);
dcupl.models.set(orderModel);
await dcupl.init();

const orderList = dcupl.lists.create({ modelKey: 'Order' });
const orders = orderList.catalog.query.items();

console.log(orders[0].customerCountryName); // 'United States'
console.log(orders[1].customerCountryName); // 'Canada'

// Fast filtering on derived property
orderList.catalog.query.addCondition({
  attribute: 'customerCountryName',
  operator: 'eq',
  value: 'United States',
});

Use Cases

Use Case 1: E-commerce - Product Category Name

Display category name without deep queries:

const categoryModel: ModelDefinition = {
  key: 'Category',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'slug', type: 'string' },
  ],
  data: [
    { key: 'c1', name: 'Electronics', slug: 'electronics' },
    { key: 'c2', name: 'Clothing', slug: 'clothing' },
  ],
};

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'int' },

    // Derive category name for easy filtering
    {
      key: 'categoryName',
      type: 'string',
      derive: {
        localReference: 'category',
        remoteProperty: 'name',
      },
    },
  ],
  references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
  data: [
    { key: 'p1', name: 'Laptop', price: 999, category: 'c1' },
    { key: 'p2', name: 'Mouse', price: 29, category: 'c1' },
    { key: 'p3', name: 'T-Shirt', price: 19, category: 'c2' },
  ],
};

dcupl.models.set(categoryModel);
dcupl.models.set(productModel);
await dcupl.init();

const productList = dcupl.lists.create({ modelKey: 'Product' });

// Fast filter on derived property
productList.catalog.query.addCondition({
  attribute: 'categoryName',
  operator: 'eq',
  value: 'Electronics',
});

const electronics = productList.catalog.query.items();
// Returns: Laptop, Mouse

Use Case 2: Order Management - Customer Info

const customerModel: ModelDefinition = {
  key: 'Customer',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'email', type: 'string' },
    { key: 'tier', type: 'string' },
  ],
  data: [
    { key: 'c1', name: 'Alice', email: 'alice@example.com', tier: 'Gold' },
    { key: 'c2', name: 'Bob', email: 'bob@example.com', tier: 'Silver' },
  ],
};

const orderModel: ModelDefinition = {
  key: 'Order',
  properties: [
    { key: 'orderId', type: 'string' },
    { key: 'total', type: 'int' },

    // Derive multiple customer properties
    {
      key: 'customerName',
      type: 'string',
      derive: {
        localReference: 'customer',
        remoteProperty: 'name',
      },
    },
    {
      key: 'customerEmail',
      type: 'string',
      derive: {
        localReference: 'customer',
        remoteProperty: 'email',
      },
    },
    {
      key: 'customerTier',
      type: 'string',
      derive: {
        localReference: 'customer',
        remoteProperty: 'tier',
      },
    },
  ],
  references: [{ key: 'customer', type: 'singleValued', model: 'Customer' }],
  data: [
    { key: 'o1', orderId: 'order1', total: 500, customer: 'c1' },
    { key: 'o2', orderId: 'order2', total: 200, customer: 'c2' },
  ],
};

dcupl.models.set(customerModel);
dcupl.models.set(orderModel);
await dcupl.init();

const orderList = dcupl.lists.create({ modelKey: 'Order' });

// Filter by customer tier (derived property)
orderList.catalog.query.addCondition({
  attribute: 'customerTier',
  operator: 'eq',
  value: 'Gold',
});

const goldOrders = orderList.catalog.query.items();
console.log(goldOrders[0].customerName); // 'Alice'
console.log(goldOrders[0].customerEmail); // 'alice@example.com'

Use Case 3: Blog - Author Name

const authorModel: ModelDefinition = {
  key: 'Author',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'bio', type: 'string' },
    { key: 'avatar', type: 'string' },
  ],
  data: [
    { key: 'a1', name: 'John Doe', bio: 'Tech writer', avatar: '/avatars/john.jpg' },
    { key: 'a2', name: 'Jane Smith', bio: 'Science blogger', avatar: '/avatars/jane.jpg' },
  ],
};

const articleModel: ModelDefinition = {
  key: 'Article',
  properties: [
    { key: 'title', type: 'string' },
    { key: 'content', type: 'string' },

    // Derive author info
    {
      key: 'authorName',
      type: 'string',
      derive: {
        localReference: 'author',
        remoteProperty: 'name',
      },
    },
  ],
  references: [{ key: 'author', type: 'singleValued', model: 'Author' }],
  data: [
    { key: 'art1', title: 'Tech Trends 2025', content: '...', author: 'a1' },
    { key: 'art2', title: 'Science News', content: '...', author: 'a2' },
  ],
};

dcupl.models.set(authorModel);
dcupl.models.set(articleModel);
await dcupl.init();

const articleList = dcupl.lists.create({ modelKey: 'Article' });

// Filter by author name
articleList.catalog.query.addCondition({
  attribute: 'authorName',
  operator: 'eq',
  value: 'John Doe',
});

Use Case 4: Inventory - Supplier Info

const supplierModel: ModelDefinition = {
  key: 'Supplier',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'country', type: 'string' },
    { key: 'rating', type: 'float' },
  ],
  data: [
    { key: 's1', name: 'TechSupply Co', country: 'USA', rating: 4.8 },
    { key: 's2', name: 'Global Parts', country: 'Germany', rating: 4.5 },
  ],
};

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'sku', type: 'string' },
    { key: 'name', type: 'string' },

    // Derive supplier info
    {
      key: 'supplierName',
      type: 'string',
      derive: {
        localReference: 'supplier',
        remoteProperty: 'name',
      },
    },
    {
      key: 'supplierCountry',
      type: 'string',
      derive: {
        localReference: 'supplier',
        remoteProperty: 'country',
      },
    },
    {
      key: 'supplierRating',
      type: 'float',
      derive: {
        localReference: 'supplier',
        remoteProperty: 'rating',
      },
    },
  ],
  references: [{ key: 'supplier', type: 'singleValued', model: 'Supplier' }],
  data: [
    { key: 'p1', sku: 'SKU001', name: 'Widget', supplier: 's1' },
    { key: 'p2', sku: 'SKU002', name: 'Gadget', supplier: 's2' },
  ],
};

dcupl.models.set(supplierModel);
dcupl.models.set(productModel);
await dcupl.init();

const productList = dcupl.lists.create({ modelKey: 'Product' });

// Filter by supplier rating
productList.catalog.query.addCondition({
  attribute: 'supplierRating',
  operator: 'gte',
  value: 4.7,
});

Use Case 5: Multi-Tag Derivation

const tagModel: ModelDefinition = {
  key: 'Tag',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'color', type: 'string' },
  ],
  data: [
    { key: 't1', name: 'JavaScript', color: '#f7df1e' },
    { key: 't2', name: 'TypeScript', color: '#3178c6' },
    { key: 't3', name: 'React', color: '#61dafb' },
  ],
};

const articleModel: ModelDefinition = {
  key: 'Article',
  properties: [
    { key: 'title', type: 'string' },

    // Derive tag names (multi-valued)
    {
      key: 'tagNames',
      type: 'string',
      derive: {
        localReference: 'tags',
        remoteProperty: 'name',
        separator: ', ',
      },
    },
  ],
  references: [{ key: 'tags', type: 'multiValued', model: 'Tag' }],
  data: [
    { key: 'art1', title: 'Getting Started with TS', tags: ['t1', 't2'] },
    { key: 'art2', title: 'React Hooks Guide', tags: ['t1', 't3'] },
  ],
};

dcupl.models.set(tagModel);
dcupl.models.set(articleModel);
await dcupl.init();

const articleList = dcupl.lists.create({ modelKey: 'Article' });
const articles = articleList.catalog.query.items();

console.log(articles[0].tagNames); // 'JavaScript, TypeScript'
console.log(articles[1].tagNames); // 'JavaScript, React'

// Search in derived tag names
articleList.catalog.query.addCondition({
  attribute: 'tagNames',
  operator: 'find',
  value: 'TypeScript',
});

Use Case 6: Location Hierarchy

const countryModel: ModelDefinition = {
  key: 'Country',
  properties: [{ key: 'name', type: 'string' }],
  data: [{ key: 'us', name: 'United States' }],
};

const stateModel: ModelDefinition = {
  key: 'State',
  properties: [
    { key: 'name', type: 'string' },

    // Derive country name
    {
      key: 'countryName',
      type: 'string',
      derive: {
        localReference: 'country',
        remoteProperty: 'name',
      },
    },
  ],
  references: [{ key: 'country', type: 'singleValued', model: 'Country' }],
  data: [{ key: 'ca', name: 'California', country: 'us' }],
};

const cityModel: ModelDefinition = {
  key: 'City',
  properties: [
    { key: 'name', type: 'string' },

    // Derive state name
    {
      key: 'stateName',
      type: 'string',
      derive: {
        localReference: 'state',
        remoteProperty: 'name',
      },
    },

    // Derive country name (2 levels)
    {
      key: 'countryName',
      type: 'string',
      derive: {
        localReference: 'state',
        remoteProperty: 'countryName',
      },
    },
  ],
  references: [{ key: 'state', type: 'singleValued', model: 'State' }],
  data: [{ key: 'sf', name: 'San Francisco', state: 'ca' }],
};

dcupl.models.set(countryModel);
dcupl.models.set(stateModel);
dcupl.models.set(cityModel);
await dcupl.init();

const cityList = dcupl.lists.create({ modelKey: 'City' });
const cities = cityList.catalog.query.items();

console.log(cities[0].name); // 'San Francisco'
console.log(cities[0].stateName); // 'California'
console.log(cities[0].countryName); // 'United States'

Use Case 7: Product Brand Info

const brandModel: ModelDefinition = {
  key: 'Brand',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'country', type: 'string' },
    { key: 'established', type: 'int' },
  ],
  data: [
    { key: 'b1', name: 'Apple', country: 'USA', established: 1976 },
    { key: 'b2', name: 'Samsung', country: 'South Korea', established: 1969 },
  ],
};

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },

    // Derive brand properties
    {
      key: 'brandName',
      type: 'string',
      derive: {
        localReference: 'brand',
        remoteProperty: 'name',
      },
    },
    {
      key: 'brandCountry',
      type: 'string',
      derive: {
        localReference: 'brand',
        remoteProperty: 'country',
      },
    },
  ],
  references: [{ key: 'brand', type: 'singleValued', model: 'Brand' }],
  data: [
    { key: 'p1', name: 'iPhone 14', brand: 'b1' },
    { key: 'p2', name: 'Galaxy S23', brand: 'b2' },
  ],
};

dcupl.models.set(brandModel);
dcupl.models.set(productModel);
await dcupl.init();

const productList = dcupl.lists.create({ modelKey: 'Product' });

// Filter by brand country
productList.catalog.query.addCondition({
  attribute: 'brandCountry',
  operator: 'eq',
  value: 'USA',
});

Use Case 8: Employee Department Info

const departmentModel: ModelDefinition = {
  key: 'Department',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'building', type: 'string' },
  ],
  data: [
    { key: 'd1', name: 'Engineering', building: 'Building A' },
    { key: 'd2', name: 'Sales', building: 'Building B' },
  ],
};

const employeeModel: ModelDefinition = {
  key: 'Employee',
  properties: [
    { key: 'name', type: 'string' },

    // Derive department info
    {
      key: 'departmentName',
      type: 'string',
      derive: {
        localReference: 'department',
        remoteProperty: 'name',
      },
    },
    {
      key: 'departmentBuilding',
      type: 'string',
      derive: {
        localReference: 'department',
        remoteProperty: 'building',
      },
    },
  ],
  references: [{ key: 'department', type: 'singleValued', model: 'Department' }],
  data: [
    { key: 'e1', name: 'Alice', department: 'd1' },
    { key: 'e2', name: 'Bob', department: 'd2' },
  ],
};

dcupl.models.set(departmentModel);
dcupl.models.set(employeeModel);
await dcupl.init();

const employeeList = dcupl.lists.create({ modelKey: 'Employee' });

// Filter by department name
employeeList.catalog.query.addCondition({
  attribute: 'departmentName',
  operator: 'eq',
  value: 'Engineering',
});

Advanced Examples

Example 9: Numeric Derived Properties

const categoryModel: ModelDefinition = {
  key: 'Category',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'taxRate', type: 'float' },
  ],
  data: [
    { key: 'c1', name: 'Electronics', taxRate: 0.08 },
    { key: 'c2', name: 'Food', taxRate: 0.05 },
  ],
};

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },

    // Derive numeric property
    {
      key: 'categoryTaxRate',
      type: 'float',
      derive: {
        localReference: 'category',
        remoteProperty: 'taxRate',
      },
    },
  ],
  references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
  data: [
    { key: 'p1', name: 'Laptop', price: 999, category: 'c1' },
    { key: 'p2', name: 'Apple', price: 1.99, category: 'c2' },
  ],
};

dcupl.models.set(categoryModel);
dcupl.models.set(productModel);
await dcupl.init();

const productList = dcupl.lists.create({ modelKey: 'Product' });

// Filter by tax rate
productList.catalog.query.addCondition({
  attribute: 'categoryTaxRate',
  operator: 'gte',
  value: 0.07,
});

Example 10: Date Derived Properties

const projectModel: ModelDefinition = {
  key: 'Project',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'deadline', type: 'date' },
  ],
  data: [{ key: 'proj1', name: 'Website Redesign', deadline: '2025-12-31' }],
};

const taskModel: ModelDefinition = {
  key: 'Task',
  properties: [
    { key: 'title', type: 'string' },

    // Derive project deadline
    {
      key: 'projectDeadline',
      type: 'date',
      derive: {
        localReference: 'project',
        remoteProperty: 'deadline',
      },
    },
  ],
  references: [{ key: 'project', type: 'singleValued', model: 'Project' }],
  data: [{ key: 't1', title: 'Design mockups', project: 'proj1' }],
};

dcupl.models.set(projectModel);
dcupl.models.set(taskModel);
await dcupl.init();

const taskList = dcupl.lists.create({ modelKey: 'Task' });

// Filter by project deadline
taskList.catalog.query.addCondition({
  attribute: 'projectDeadline',
  operator: 'lte',
  value: '2025-12-31',
});

Example 11: Boolean Derived Properties

const categoryModel: ModelDefinition = {
  key: 'Category',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'taxable', type: 'boolean' },
  ],
  data: [
    { key: 'c1', name: 'Electronics', taxable: true },
    { key: 'c2', name: 'Books', taxable: false },
  ],
};

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },

    // Derive boolean property
    {
      key: 'isTaxable',
      type: 'boolean',
      derive: {
        localReference: 'category',
        remoteProperty: 'taxable',
      },
    },
  ],
  references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
  data: [
    { key: 'p1', name: 'Laptop', category: 'c1' },
    { key: 'p2', name: 'Novel', category: 'c2' },
  ],
};

dcupl.models.set(categoryModel);
dcupl.models.set(productModel);
await dcupl.init();

const productList = dcupl.lists.create({ modelKey: 'Product' });

// Filter by taxable status
productList.catalog.query.addCondition({
  attribute: 'isTaxable',
  operator: 'eq',
  value: true,
});

Example 12: Array Derived Properties

const vendorModel: ModelDefinition = {
  key: 'Vendor',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'certifications', type: 'Array<string>' },
  ],
  data: [{ key: 'v1', name: 'TechCorp', certifications: ['ISO9001', 'ISO27001'] }],
};

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },

    // Derive array property
    {
      key: 'vendorCertifications',
      type: 'string',
      derive: {
        localReference: 'vendor',
        remoteProperty: 'certifications',
        separator: ', ',
      },
    },
  ],
  references: [{ key: 'vendor', type: 'singleValued', model: 'Vendor' }],
  data: [{ key: 'p1', name: 'Widget', vendor: 'v1' }],
};

dcupl.models.set(vendorModel);
dcupl.models.set(productModel);
await dcupl.init();

const productList = dcupl.lists.create({ modelKey: 'Product' });
const products = productList.catalog.query.items();

console.log(products[0].vendorCertifications); // 'ISO9001, ISO27001'

Example 13: Three-Level Derivation

const regionModel: ModelDefinition = {
  key: 'Region',
  properties: [{ key: 'name', type: 'string' }],
  data: [{ key: 'r1', name: 'North America' }],
};

const countryModel: ModelDefinition = {
  key: 'Country',
  properties: [
    { key: 'name', type: 'string' },

    {
      key: 'regionName',
      type: 'string',
      derive: {
        localReference: 'region',
        remoteProperty: 'name',
      },
    },
  ],
  references: [{ key: 'region', type: 'singleValued', model: 'Region' }],
  data: [{ key: 'us', name: 'USA', region: 'r1' }],
};

const customerModel: ModelDefinition = {
  key: 'Customer',
  properties: [
    { key: 'name', type: 'string' },

    {
      key: 'countryName',
      type: 'string',
      derive: {
        localReference: 'country',
        remoteProperty: 'name',
      },
    },

    // 3-level derivation
    {
      key: 'regionName',
      type: 'string',
      derive: {
        localReference: 'country',
        remoteProperty: 'regionName',
      },
    },
  ],
  references: [{ key: 'country', type: 'singleValued', model: 'Country' }],
  data: [{ key: 'c1', name: 'Alice', country: 'us' }],
};

dcupl.models.set(regionModel);
dcupl.models.set(countryModel);
dcupl.models.set(customerModel);
await dcupl.init();

const customerList = dcupl.lists.create({ modelKey: 'Customer' });
const customers = customerList.catalog.query.items();

console.log(customers[0].regionName); // 'North America' (3 levels!)

Example 14: Multi-Value with Custom Separator

const authorModel: ModelDefinition = {
  key: 'Author',
  properties: [{ key: 'name', type: 'string' }],
  data: [
    { key: 'a1', name: 'John Doe' },
    { key: 'a2', name: 'Jane Smith' },
    { key: 'a3', name: 'Bob Johnson' },
  ],
};

const bookModel: ModelDefinition = {
  key: 'Book',
  properties: [
    { key: 'title', type: 'string' },

    // Derive author names with custom separator
    {
      key: 'authorNames',
      type: 'string',
      derive: {
        localReference: 'authors',
        remoteProperty: 'name',
        separator: ' & ', // Custom separator
      },
    },
  ],
  references: [{ key: 'authors', type: 'multiValued', model: 'Author' }],
  data: [{ key: 'b1', title: 'Design Patterns', authors: ['a1', 'a2', 'a3'] }],
};

dcupl.models.set(authorModel);
dcupl.models.set(bookModel);
await dcupl.init();

const bookList = dcupl.lists.create({ modelKey: 'Book' });
const books = bookList.catalog.query.items();

console.log(books[0].authorNames); // 'John Doe & Jane Smith & Bob Johnson'
const manufacturerModel: ModelDefinition = {
  key: 'Manufacturer',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'code', type: 'string' },
  ],
  data: [{ key: 'm1', name: 'Acme Corp', code: 'ACME' }],
};

const productModel: ModelDefinition = {
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },

    // Derive for text search
    {
      key: 'manufacturerName',
      type: 'string',
      derive: {
        localReference: 'manufacturer',
        remoteProperty: 'name',
      },
    },

    {
      key: 'manufacturerCode',
      type: 'string',
      derive: {
        localReference: 'manufacturer',
        remoteProperty: 'code',
      },
    },
  ],
  references: [{ key: 'manufacturer', type: 'singleValued', model: 'Manufacturer' }],
  data: [{ key: 'p1', name: 'Widget Pro', manufacturer: 'm1' }],
};

dcupl.models.set(manufacturerModel);
dcupl.models.set(productModel);
await dcupl.init();

const productList = dcupl.lists.create({ modelKey: 'Product' });

// Search by manufacturer name
productList.catalog.query.addCondition({
  attribute: 'manufacturerName',
  operator: 'find',
  value: 'Acme',
});

Performance Considerations

Query Performance

Before (Deep Query):

Slow - navigates through reference at query time
orderList.catalog.query.addCondition({
  attribute: 'customer.name',
  operator: 'eq',
  value: 'Alice',
});

After (Derived Property):

Fast - uses indexed derived property
orderList.catalog.query.addCondition({
  attribute: 'customerName', // Derived property
  operator: 'eq',
  value: 'Alice',
});

Performance gain: 5-20x faster for single-level references, more for deeper paths.

Memory vs Speed Trade-off

Derived properties use more memory but provide faster queries:

Without derived property
// Memory: Low
// Query speed: Slow (deep query)
With derived property
// Memory: +10-20% (depends on data size)
// Query speed: 5-20x faster

Best practice: Derive properties you frequently query on.

When to Use Derived Properties

Use derived properties when:

  • You frequently query on referenced properties
  • Query performance is critical
  • The referenced data changes infrequently
  • You need fast facets/aggregations

Avoid derived properties when:

  • The referenced data changes constantly
  • Memory is very constrained
  • You rarely query the property
  • Deep queries are fast enough for your use case

Indexing Derived Properties

Always enable filtering on derived properties for maximum performance:

{
  key: 'customerName',
  type: 'string',
  derive: {
    localReference: 'customer',
    remoteProperty: 'name',
  },
}

Best Practices

1. Enable Filtering on Derived Properties

// ✅ Good: Make derived properties filterable
{
  key: 'categoryName',
  type: 'string',
  derive: {
    localReference: 'category',
    remoteProperty: 'name',
  },
}

// ❌ Bad: Derived property without filter
{
  key: 'categoryName',
  type: 'string',
  derive: {
    localReference: 'category',
    remoteProperty: 'name',
  },
}

2. Use Appropriate Data Types

// ✅ Good: Match remote property type
// Remote: { key: 'price', type: 'float' }
{
  key: 'categoryPrice',
  type: 'float',  // Same type
  derive: {
    localReference: 'category',
    remoteProperty: 'price',
  },
}

// ❌ Bad: Mismatched types
{
  key: 'categoryPrice',
  type: 'string',  // Wrong type
  derive: {
    localReference: 'category',
    remoteProperty: 'price',
  },
}

3. Use Descriptive Names

// ✅ Good: Clear naming
{
  key: 'customerName',
  key: 'categoryTitle',
  key: 'authorFullName',
}

// ❌ Bad: Vague naming
{
  key: 'name',
  key: 'title',
  key: 'value',
}

4. Consider Using Separators for Multi-Valued

// ✅ Good: Use appropriate separator
{
  key: 'tagNames',
  type: 'string',
  derive: {
    localReference: 'tags',
    remoteProperty: 'name',
    separator: ', ',  // Clear separation
  },
}

5. Derive Only What You Need

// ✅ Good: Only derive properties you'll use
properties: [
  { key: 'customerName', type: 'string', derive: {...} },
  { key: 'customerEmail', type: 'string', derive: {...} },
]

// ❌ Bad: Deriving everything
properties: [
  { key: 'customerName', type: 'string', derive: {...} },
  { key: 'customerEmail', type: 'string', derive: {...} },
  { key: 'customerPhone', type: 'string', derive: {...} },
  { key: 'customerAddress', type: 'string', derive: {...} },
  { key: 'customerCity', type: 'string', derive: {...} },
  { key: 'customerState', type: 'string', derive: {...} },
  // ... too many derived properties
]

Troubleshooting

Derived Property is Undefined

Problem: Derived property shows as undefined

console.log(order.customerName); // undefined

Solutions:

  1. Check reference exists:
console.log(order.customer); // Should be an object, not ID
  1. Verify reference is resolved:
await dcupl.init(); // Must call init()
  1. Check remote property exists:
console.log(order.customer.name); // Verify source property

Type Mismatch Errors

Problem: Type coercion issues

Solution: Match types exactly:

// Remote property
{ key: 'price', type: 'float' }

// Derived property (must match)
{ key: 'categoryPrice', type: 'float', derive: {...} }

Multi-Valued Derivation Not Working

Problem: Multi-valued derivation returns incorrect results

Solution: Ensure reference is multi-valued:

references: [
  { key: 'tags', type: 'multiValued', model: 'Tag' }, // Must be multiValued
];

properties: [
  {
    key: 'tagNames',
    derive: {
      localReference: 'tags',
      remoteProperty: 'name',
      separator: ', ',
    },
  },
];

Performance Issues

Problem: Derived properties not improving performance

Solution: Enable filtering:

{
  key: 'customerName',
  type: 'string',
  derive: {
    localReference: 'customer',
    remoteProperty: 'name',
  },
}

What's Next?