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.nameBasic 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, MouseUse 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'Example 15: Derived Property for Search
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):
orderList.catalog.query.addCondition({
attribute: 'customer.name',
operator: 'eq',
value: 'Alice',
});After (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:
// Memory: Low
// Query speed: Slow (deep query)// Memory: +10-20% (depends on data size)
// Query speed: 5-20x fasterBest 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); // undefinedSolutions:
- Check reference exists:
console.log(order.customer); // Should be an object, not ID- Verify reference is resolved:
await dcupl.init(); // Must call init()- Check remote property exists:
console.log(order.customer.name); // Verify source propertyType 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?
- Expression Properties - Template-based computed fields
- Models - Properties and reference relationships
- Deep Queries - Querying across references
- Performance - Optimization strategies