# 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

```typescript
{
  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
```

## Reference

| `derive` key     | Meaning                                                                                                  |
| ---------------- | -------------------------------------------------------------------------------------------------------- |
| `localReference` | A reference of this model (single- or multi-valued). If it doesn't exist: `MissingReference` error       |
| `remoteProperty` | A property of the referenced model, not a dotted path. If it doesn't exist: `MissingProperty` error      |
| `separator`      | Joins the values over a multi-valued reference into a string (default `', '`); ignored for single-valued |

| Situation                                                              | Derived value                                                                     |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Single-valued reference                                                | The remote value as it is; the declared `type` doesn't convert it                 |
| Multi-valued reference, `string`, `int`, `float` or `date` type        | The values joined with `separator`, as a string                                   |
| Multi-valued reference, `Array<…>` (or any other) type                 | The list of values                                                                |
| Falsy remote values (`0`, `false`, `''`) over a multi-valued reference | Left out                                                                          |
| Single-valued reference is empty, or its key has no item               | The property is left out (a missing item is a `RemoteReferenceKeyNotFound` error) |
| Multi-valued reference is empty, or none of its keys has an item       | `''` for `string`, `int`, `float` and `date` types, `[]` for `Array<…>` types     |
| Some keys of a multi-valued reference have no item                     | Those keys are skipped; the others are joined or listed                           |
| A referenced item changes on `dcupl.update()`                          | The derived value is recomputed                                                   |

`index: true` builds an inverse index for derived properties over single-valued references (see [Indexing Derived Properties](#indexing-derived-properties)). To derive a reference instead of a value, use a derived reference (`derive: { localReference, remoteReference }`, see [Reference types](/sdk/api/models#reference)); the `derive` option itself is listed in the [Models API](/sdk/api/models#derive).

## Basic derived property

### Single-valued reference derivation

Pull a property from a single referenced item:

```typescript
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 the derived property instead of a deep query on customer.name
orderList.catalog.query.addCondition({
  attribute: 'customerName',
  operator: 'eq',
  value: 'Alice Johnson',
});
```

The examples below reuse these imports and start from a fresh `const dcupl = new Dcupl()` each.

## Multi-valued reference derivation

Derive properties from multi-valued references (arrays):

```typescript
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'
```

Over a multi-valued reference, a `string`, `int`, `float` or `date` property gets the values joined with `separator` (default `', '`; `separator: ' & '` gives `'Laptop & Mouse'`); an `Array<…>` property (or any other type) gets the list of values. Falsy values (`0`, `false`, `''`) are left out. Over a single-valued reference, the remote value is copied as it is and `separator` is ignored.

## Chained derivations

`derive` follows exactly one reference, and `remoteProperty` is a property of the referenced model (not a dotted path). To reach further, derive the value on the intermediate model first and derive that property:

```typescript
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',
});
```

## Examples

### Filter by a referenced name

Filter products by their category's name without a deep query:

```typescript
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',
      index: true, // eq/in on categoryName are index lookups
      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' });

productList.catalog.query.addCondition({
  attribute: 'categoryName',
  operator: 'eq',
  value: 'Electronics',
});

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

### Search a multi-valued derivation

```typescript
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 ('/TypeScript/' matches anywhere in the value)
articleList.catalog.query.addCondition({
  attribute: 'tagNames',
  operator: 'find',
  value: '/TypeScript/',
});
// → Getting Started with TS
```

### Date derived properties

```typescript
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 (compare dates with a Date; an ISO string matches nothing)
taskList.catalog.query.addCondition({
  attribute: 'projectDeadline',
  operator: 'lte',
  value: new Date('2025-12-31'),
});
```

### Array derived properties

```typescript
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 (copied as it is over a single-valued reference)
    {
      key: 'vendorCertifications',
      type: 'Array<string>',
      derive: {
        localReference: 'vendor',
        remoteProperty: 'certifications',
      },
    },
  ],
  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']
```

## Performance considerations

### Query performance

**Before (Deep Query):**

```typescript title="Resolves the reference for every evaluated item"
orderList.catalog.query.addCondition({
  attribute: 'customer.name',
  operator: 'eq',
  value: 'Alice',
});
```

**After (Derived Property):**

```typescript title="Reads the value stored on the item"
orderList.catalog.query.addCondition({
  attribute: 'customerName', // Derived property
  operator: 'eq',
  value: 'Alice',
});
```

A deep query resolves the reference on every evaluated item; a derived property is stored on the item when the data is processed, and with `index: true` an `eq` condition becomes an index lookup. The gap grows with the number of items and the depth of the path. Measure with your own data (see [Profiling](/sdk/advanced/performance#profiling-and-debugging)).

### Memory vs speed trade-off

A derived property stores one more value on every item and is recomputed when the referenced items change, so it costs memory and some processing time in `init()` and `update()`. Derive the values you query often, not every field of the referenced model.

### 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

Set `index: true` on a derived property over a single-valued reference to build its inverse index while the data is processed, so `eq` and `in` conditions on it are index lookups. Derived properties over multi-valued references are not indexed. (`filter: true` doesn't index; it only lists the property in `catalog.filters` for filter UIs.)

```typescript
{
  key: 'customerName',
  type: 'string',
  index: true,
  derive: {
    localReference: 'customer',
    remoteProperty: 'name',
  },
}
```

## Best practices

### 1. Index derived properties you filter by exact value

```typescript
// ✅ Good: indexed, so eq/in conditions are index lookups
{
  key: 'categoryName',
  type: 'string',
  index: true,
  derive: {
    localReference: 'category',
    remoteProperty: 'name',
  },
}

// ❌ Bad: no index, so every eq/in condition scans the items
{
  key: 'categoryName',
  type: 'string',
  derive: {
    localReference: 'category',
    remoteProperty: 'name',
  },
}
```

### 2. Declare the remote property's type

The derived value is copied as it is (see [Type Mismatch Errors](#type-mismatch-errors)), so give the derived property the type of the remote property: a `float` remote property gets a `float` derived property.

### 3. Use descriptive names

Prefix the derived property with the reference it comes from, such as `customerName`, `categoryTitle` or `authorFullName`, rather than `name`, `title` or `value`.

### 4. Derive only what you need

Each derived property adds a value to every item. Derive the fields you filter, sort, facet or display (for example `customerName` and `customerEmail`); read the rest with a [projection](/sdk/api/types#projection) when you need it.

## Troubleshooting

### Derived property is undefined

**Problem:** Derived property shows as undefined

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

**Solutions:**

1. Check reference exists. Over a single-valued reference, dcupl leaves the derived property out when the reference is empty or its key has no item (over a multi-valued reference you get `''` or `[]` instead):

```typescript
console.log(order.customer); // { key: 'c1' }, not undefined
```

2. Verify reference is resolved:

```typescript
await dcupl.init(); // Must call init()
```

3. Check remote property exists. Items list a reference as `{ key }` only, so project it to see the remote values:

```typescript
const [order] = orderList.catalog.query.items({ projection: { $: true, customer: { $: true } } });
console.log(order.customer.name); // Verify source property
```

`dcupl.quality.errors.summary()` also lists the cause: `RemoteReferenceKeyNotFound` (the referenced key has no item), `MissingProperty` (`remoteProperty` isn't a property of the referenced model) or `MissingReference` (`localReference` isn't a reference of this model).

### Type mismatch errors

**Problem:** The derived value doesn't have the type the property declares

**Solution:** dcupl copies the remote value as it is: the derived property's `type` doesn't convert it (over a multi-valued reference it only decides between a joined string and a list). Declare the remote property's type:

```typescript
// 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:

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

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

### Performance issues

**Problem:** Derived properties not improving performance

**Solution:** Set `index: true` (derived properties over single-valued references) for properties you filter with `eq` or `in`:

```typescript
{
  key: 'customerName',
  type: 'string',
  index: true,
  derive: {
    localReference: 'customer',
    remoteProperty: 'name',
  },
}
```

## Next steps

- [Expression properties](/sdk/advanced/expression-properties) — Template-based computed fields
- [Models](/sdk/core-concepts/models) — Properties and reference relationships
- [Deep queries](/sdk/queries/query-deep) — Querying across references
- [Performance](/sdk/advanced/performance) — Optimization strategies
- [Models API](/sdk/api/models#reference) — Derived, resolved and query references
