# Models API

The Models API (`dcupl.models`) provides methods for managing data model definitions in dcupl. Models define the structure, types, and relationships of your data.

## Methods

### set()

Adds a model definition. dcupl builds the model on the next `init()` or `update()`.

If a model with the same key already exists, `set()` replaces its definition (SDK 2.0): a changed attribute takes effect and an attribute left out of the new definition is removed. To add to a definition instead, use [`update()`](#update). dcupl stores a copy, so later changes to the object you passed do not reach the instance. After `init()`, the change takes effect on the next `dcupl.update()`, which rebuilds all data like `init()` (a full update), and lists created earlier serve the new definition. Define each property only once within a definition: a property key given twice adds a second definition and reports a `DuplicatePropertyKey` quality error. Model files loaded by the app loader are the exception: files with the same key are still merged (first declaration wins).

`set()` does not throw on an invalid definition. A model with an empty `key` or `keyProperty` (or a malformed `autoGenerateProperties`, `properties` or `references`) is not registered, and a property with an unknown `type` is left out of the model; each case records an `InvalidModelDefinition` quality error, which stays until a valid `set()` for the same key replaces the definition.

**Signature:**

```typescript
set(model: ModelDefinition): void
```

**Parameters:**

| Parameter | Type              | Required | Description                 |
| --------- | ----------------- | -------- | --------------------------- |
| `model`   | `ModelDefinition` | Yes      | The model definition to set |

**Examples:**

```typescript title="Basic model"
dcupl.models.set({
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
  ],
});
```

```typescript title="Model with references"
dcupl.models.set({
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
  ],
  references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
});
```

```typescript title="Model with common options"
dcupl.models.set({
  key: 'Product',
  keyProperty: 'id',
  autoGenerateKey: false,
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
    { key: 'tags', type: 'Array<string>', separator: ',' },
  ],
  references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
  quality: {
    enabled: true,
    attributes: {
      required: true,
    },
  },
});
```

### update()

Merges a definition into an existing model: `properties` and `references` are appended, scalar fields of the later definition win, and an attribute keeps its first declaration. The change is applied on the next `dcupl.update()`, which rebuilds all data like `init()`; `get()` then returns the merged definition. Use [`set()`](#set) to replace a definition.

**Signature:**

```typescript
update(model: ModelDefinition): void
```

**Parameters:**

| Parameter | Type              | Required | Description                   |
| --------- | ----------------- | -------- | ----------------------------- |
| `model`   | `ModelDefinition` | Yes      | The model definition to merge |

**Examples:**

```typescript title="Update existing model"
dcupl.models.update({
  key: 'Product',
  properties: [{ key: 'description', type: 'string' }],
});
```

```typescript title="Add reference to existing model"
dcupl.models.update({
  key: 'Product',
  references: [{ key: 'manufacturer', type: 'singleValued', model: 'Manufacturer' }],
});
```

### get()

Retrieves the definition of a built model by key. It returns `undefined` for a model that is not built yet: before `init()`, or for a key first set after `init()` until the next `dcupl.update()`. For a built model it returns the definition the model was built from; a `set()` or `update()` on it shows in `get()` only after the next `dcupl.update()`. A `set()` with an invalid definition unregisters the model at once, so `get()` returns `undefined`. For a model with `autoGenerateProperties`, the inferred properties are included. The result is a copy: changing it has no effect on the instance (edit a model with get → change → `set()` → `dcupl.update()`). Inline `data` given in the definition is not included.

**Signature:**

```typescript
get(modelKey: string): ModelDefinition | undefined
```

**Parameters:**

| Parameter  | Type     | Required | Description                      |
| ---------- | -------- | -------- | -------------------------------- |
| `modelKey` | `string` | Yes      | The key of the model to retrieve |

**Returns:** `ModelDefinition | undefined` - The model definition or undefined if the model is not built

**Examples:**

```typescript title="Get a model"
const model = dcupl.models.get('Product');
if (model) {
  console.log(model.properties);
  console.log(model.references);
}
```

```typescript title="Check if model exists"
const exists = dcupl.models.get('Product') !== undefined;
```

```typescript title="Access model properties"
const model = dcupl.models.get('Product');
if (model) {
  const properties = model.properties || [];
  const references = model.references || [];
}
```

### remove()

Unregisters a model at once (SDK 2.0): its definition, its data (rows from `data.*` or inline `data`) and its lists. Queries on it throw, its errors leave `quality.errors.summary()`, and the next `init()` or `update()` rebuilds all data like `init()` without bringing it back; references from other models then resolve as to a missing model (`MissingModel`). A later `set()` registers the model again; pass its data again.

**Signature:**

```typescript
remove(modelKey: string): void
```

**Parameters:**

| Parameter  | Type     | Required | Description                    |
| ---------- | -------- | -------- | ------------------------------ |
| `modelKey` | `string` | Yes      | The key of the model to remove |

**Examples:**

```typescript title="Remove a model"
dcupl.models.remove('Product');
```

```typescript title="Remove multiple models"
['Product', 'Category', 'Tag'].forEach((key) => {
  dcupl.models.remove(key);
});
```

### keys()

Returns the keys of all built models. dcupl's internal `$Dcupl…` models are left out.

**Signature:**

```typescript
keys(): string[]
```

**Returns:** `string[]` - Array of model keys

**Examples:**

```typescript title="Get all model keys"
const modelKeys = dcupl.models.keys();
console.log(modelKeys); // ['Product', 'Category', 'Tag']
```

```typescript title="Check if a model exists"
const hasProduct = dcupl.models.keys().includes('Product');
```

```typescript title="Count models"
const modelCount = dcupl.models.keys().length;
```

## Type definitions

### ModelDefinition

Defines the structure of a data model.

**Type Definition:**

```typescript
type ModelDefinition<ModelNames extends string = string> = {
  key: ModelNames;
  properties?: Property[];
  references?: Reference[];
  data?: RawItem[];
  keyProperty?: string | string[];
  keyPropertySeparator?: string;
  autoGenerateKey?: boolean;
  autoGenerateProperties?: boolean | { deep?: boolean; sampleSize?: number };
  supportsAutoCreation?: boolean;
  valueMappings?: ValueMappingConfig[];
  meta?: ModelMetadata;
  quality?: ModelQualityConfig;
};
```

**Properties:**

| Property                 | Type                   | Description                                                                      |
| ------------------------ | ---------------------- | -------------------------------------------------------------------------------- |
| `key`                    | `string`               | Unique model identifier                                                          |
| `properties`             | `Property[]`           | Array of property definitions                                                    |
| `references`             | `Reference[]`          | Array of reference definitions                                                   |
| `data`                   | `RawItem[]`            | Optional inline data for the model                                               |
| `keyProperty`            | `string \| string[]`   | Property to use as item key (default: `'key'`); an array builds a composite key  |
| `keyPropertySeparator`   | `string`               | Separator joining the parts of a composite key (default: `'::'`)                 |
| `autoGenerateKey`        | `boolean`              | Auto-generate unique keys for items without one (default: `false`)               |
| `autoGenerateProperties` | `boolean \| object`    | Infer property definitions from the data (default: `false`), see below           |
| `supportsAutoCreation`   | `boolean`              | Allow automatic item creation when referenced by other models (default: `false`) |
| `valueMappings`          | `ValueMappingConfig[]` | Transform values during data loading                                             |
| `meta`                   | `ModelMetadata`        | Metadata attached to the model (`name` is required, other keys are free)         |
| `quality`                | `ModelQualityConfig`   | Quality validation configuration                                                 |

**Examples:**

```typescript title="Minimal model"
const model: ModelDefinition = {
  key: 'Product',
  properties: [{ key: 'name', type: 'string' }],
};
```

```typescript title="Complete model"
const model: ModelDefinition = {
  key: 'Product',
  keyProperty: 'productId',
  autoGenerateKey: false,
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
    { key: 'createdAt', type: 'date', inputFormat: 'yyyy-MM-dd' },
  ],
  references: [
    { key: 'category', type: 'singleValued', model: 'Category' },
    { key: 'tags', type: 'multiValued', model: 'Tag' },
  ],
  quality: {
    enabled: true,
    attributes: {
      required: true,
    },
  },
};
```

### Model options

#### keyProperty

Specifies which property in your data should be used as the unique item identifier. By default, dcupl looks for a property named `key`.

```typescript title="Using a custom key property"
const model: ModelDefinition = {
  key: 'Product',
  keyProperty: 'productId', // Use 'productId' instead of 'key'
  properties: [
    { key: 'productId', type: 'string' },
    { key: 'name', type: 'string' },
  ],
};

// Data can now use 'productId' as the identifier
dcupl.data.set(
  [
    { productId: 'SKU-001', name: 'Widget' },
    { productId: 'SKU-002', name: 'Gadget' },
  ],
  { model: 'Product' },
);
```

An array of property names builds a composite key: dcupl joins the values in array order with `keyPropertySeparator` (default `'::'`) and writes the result to the item's `key`. Each value is converted with `String()`, so a missing part becomes `'undefined'` (or `'null'`) in the key.

```typescript title="Composite key"
dcupl.models.set({
  key: 'Translation',
  keyProperty: ['sku', 'lang'],
  properties: [
    { key: 'sku', type: 'string' },
    { key: 'lang', type: 'string' },
    { key: 'title', type: 'string' },
  ],
});

dcupl.data.set([{ sku: 'A1', lang: 'en', title: 'Widget' }], { model: 'Translation' });
await dcupl.init();

dcupl.query.one({ modelKey: 'Translation', itemKey: 'A1::en' }); // { key: 'A1::en', sku: 'A1', lang: 'en', title: 'Widget' }
```

A row with no value for the key property (`undefined` or `null`; for a composite key: every part) is dropped and reported as a `MissingKey` [quality error](/sdk/advanced/quality-validation#error-types). On a model with derived, grouped, resolved or query references, or derived or expression properties, such a row currently also makes `init()`, or an `update()` that rebuilds (for example after `data.set()`, or after `upsert()` on a model with query references), log "Initialization failed" or "Update failed" and resolve `false`. Processing stops at that row, so remove such rows before loading. After `init()`, a keyless row in an `upsert()` that `dcupl.update()` applies on the [partial path](/sdk/api/data#partial-updates) is currently dropped without the error; the next full rebuild reports it, and on those models resolves `false`. A composite key with only some parts missing is kept, with `'undefined'` or `'null'` for those parts. An empty string counts as a value: a blank key cell in a CSV file gives the key `''` and the row is kept; further rows with a blank key replace it and are reported as `NonUniqueKey`.

#### autoGenerateKey

When `true`, dcupl generates a unique 10-character key for items that don't have one. Useful when your data doesn't have natural identifiers.

```typescript title="Auto-generating keys"
const model: ModelDefinition = {
  key: 'LogEntry',
  autoGenerateKey: true,
  properties: [
    { key: 'message', type: 'string' },
    { key: 'timestamp', type: 'date' },
  ],
};

// Data without keys - dcupl generates them automatically
dcupl.data.set(
  [
    { message: 'User logged in', timestamp: '2025-01-15' },
    { message: 'User logged out', timestamp: '2025-01-15' },
  ],
  { model: 'LogEntry' },
);

// Items will have auto-generated keys like 'a8f3k2m9x1'
```

#### autoGenerateProperties

When `true`, dcupl infers property definitions from the data structure. Useful for quick prototyping or when working with dynamic data.

```typescript title="Auto-generating properties"
const model: ModelDefinition = {
  key: 'DynamicData',
  autoGenerateProperties: true,
  // No properties defined - they will be inferred from data
};

dcupl.data.set([{ key: 'item1', name: 'Test', count: 42, active: true }], { model: 'DynamicData' });

// Properties 'name', 'count', 'active' are automatically created
```

`autoGenerateProperties` accepts `true`, `{ deep: true }` (scan every row), `{ sampleSize: N }` (scan up to N rows) or `{ deep: false }` (the first row and nothing else). With `true` the first row names the properties and their types, and a column it types as a number is checked against every row: a later decimal makes it `float`, a later zero-padded value makes it `string`. In SDK 2.0, string values are inferred too: `'19.99'` becomes `float`, `'3'` becomes `int`, `'true'` / `'false'` (in any case) become `boolean`, `'1'` / `'0'` stay `int`, and a strict ISO-8601 date becomes `date`, so CSV columns are not all `string`. The key column stays `string`. A digit string is a number only when the number keeps every character: zero-padded values (`'007'`, a zip code `'01234'`) and integers beyond `Number.MAX_SAFE_INTEGER` are inferred as `string`, and so is a column that mixes them with numbers. Properties you declare in `properties` keep their declared type, so declare one where you need a specific type (for example `int` for months `'01'` to `'12'`, which reads `'07'` as 7).

```typescript title="Inferring types from string values"
dcupl.models.set({ key: 'Row', autoGenerateProperties: { deep: true } });

dcupl.data.set(
  [
    { key: 'r1', price: '5' },
    { key: 'r2', price: '19.99' },
  ],
  { model: 'Row' },
);
await dcupl.init();

// price is inferred as 'float', with true as well (number columns are checked against every row).
// With { deep: false } only the first row counts: price becomes 'int' and '19.99' is stored as 19
```

> **Note:** A `boolean` property, declared or inferred, converts the strings `'true'`, `'false'`, `'1'` and `'0'` (case-insensitive, no trimming) and the numbers `1` and `0` to booleans. Any other string, number, object or array is reported as `WrongDataType` and the value stays undefined, so `'yes'`, `'no'`, `''` or `2` do not load as a boolean. Inline data, JSON and CSV follow the same rule; an empty CSV cell counts as no value, not as `''`. Map other spellings with [valueMappings](#valuemappings), or declare the column as `string`:
>
> ```typescript
> dcupl.models.set({
>   key: 'Row',
>   properties: [{ key: 'ok', type: 'boolean' }],
>   valueMappings: [
>     {
>       attributes: ['ok'],
>       values: [
>         { from: ['yes', 'Y'], to: true },
>         { from: ['no', 'N'], to: false },
>       ],
>     },
>   ],
> });
> ```

#### supportsAutoCreation

When `true`, items are automatically created in this model when referenced by other models. This is useful for lookup tables or reference data that might be incomplete.

```typescript title="Auto-creation of referenced items"
const productModel: ModelDefinition = {
  key: 'Product',
  references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
  data: [
    { key: 'p1', category: 'electronics' },
    { key: 'p2', category: 'books' },
  ],
};

const categoryModel: ModelDefinition = {
  key: 'Category',
  supportsAutoCreation: true, // Categories created automatically
  properties: [{ key: 'name', type: 'string' }],
};

// After init, 'electronics' and 'books' exist in Category model
// even though no Category data was explicitly provided
```

#### valueMappings

Transform values during data loading. Useful for normalizing data, handling special values, or converting between formats.

**Type Definition:**

```typescript
type ValueMappingConfig = {
  attributes: string[]; // Properties/references to apply mapping to
  values: {
    from: (string | '$dcupl_falsy')[]; // Values to match
    to: any; // Replacement value
  }[];
};
```

Mappings run on the raw value, before type conversion. CSV files loaded by `DcuplAppLoader` are the exception: the CSV parser converts each cell to the declared type first, and a `boolean` column accepts only `true` / `false` / `1` / `0` (in any case), so `Yes` / `No` are dropped before a mapping sees them (convert them in a [loader transformer](/sdk/api/loader#transformers) instead). A `from` value matches only with strict equality (`'0'` does not match the number `0`). A `from` list that contains `'$dcupl_falsy'` matches only `undefined`, `null` and `''`; other entries in that list are ignored, so put them in a separate `values` entry.

```typescript title="Mapping falsy values to a default"
const model: ModelDefinition = {
  key: 'Product',
  valueMappings: [
    {
      attributes: ['category', 'brand'],
      values: [
        {
          from: ['$dcupl_falsy'], // Matches undefined, null, empty string
          to: 'uncategorized',
        },
      ],
    },
  ],
  references: [
    { key: 'category', type: 'singleValued', model: 'Category' },
    { key: 'brand', type: 'singleValued', model: 'Brand' },
  ],
};
```

```typescript title="Converting string values to boolean"
const model: ModelDefinition = {
  key: 'Product',
  valueMappings: [
    {
      attributes: ['inStock'],
      values: [
        { from: ['Yes', 'yes', 'Y', '1'], to: true },
        { from: ['No', 'no', 'N', '0'], to: false },
      ],
    },
  ],
  properties: [{ key: 'inStock', type: 'boolean' }],
};
```

#### meta

Attach arbitrary metadata to the model for documentation or application-specific purposes.

```typescript title="Model with metadata"
const model: ModelDefinition = {
  key: 'Product',
  meta: {
    name: 'Product Catalog',
    description: 'All products available for sale',
    version: '2.0',
    maintainer: 'catalog-team@example.com',
  },
  properties: [{ key: 'name', type: 'string' }],
};

// Access metadata
const productModel = dcupl.models.get('Product');
console.log(productModel?.meta?.description);
```

### Property

Defines a property (field) in a model.

**Type Definition:**

```typescript
type Property =
  | PropertyString
  | PropertyStringArray
  | PropertyInt
  | PropertyIntArray
  | PropertyFloat
  | PropertyFloatArray
  | PropertyDate
  | PropertyDateArray
  | PropertyJson
  | PropertyBoolean
  | PropertyAny;
```

**Common Properties:**

| Property     | Type                     | Description                                                                                                                     |
| ------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `key`        | `string`                 | Unique property identifier                                                                                                      |
| `type`       | `PropertyType`           | Data type of the property                                                                                                       |
| `index`      | `boolean`                | Build the property's lookup index at load time instead of on the first query that needs it (ignored on `expression` properties) |
| `expression` | `string`                 | Computed property expression                                                                                                    |
| `derive`     | `DeriveConfig`           | Derive value from reference                                                                                                     |
| `quality`    | `AttributeQualityConfig` | Validation rules                                                                                                                |
| `filter`     | `boolean \| object`      | List the property in [`catalog.filters`](/sdk/api/catalog#catalogfilters) (filter UI items)                                     |
| `origin`     | `string`                 | Read the value from a differently named source field, e.g. `origin: 'Product Title'` for a CSV column header                    |
| `meta`       | `ModelMetadata`          | Metadata attached to the property                                                                                               |
| `aggregate`  | `boolean \| object`      | Accepted and returned by `models.get()`; no effect on queries or `fn.aggregate()`                                               |

**Type-specific options:**

| Option           | Types                   | Description                                                                                                     |
| ---------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------- |
| `separator`      | `Array<…>`              | Splits a string value into array elements (default: `','`)                                                      |
| `fractionDigits` | `float`, `Array<float>` | Rounds the value to this many decimals                                                                          |
| `inputFormat`    | `date`, `Array<date>`   | Format of the raw value, e.g. `'yyyy-MM-dd'` or `'dd.MM.yyyy'`; `'t'` = Unix seconds, `'T'` = Unix milliseconds |
| `UTC`            | `date`, `Array<date>`   | Treat the parsed date and time as UTC instead of local time                                                     |

**Property Types:**

| Type              | Description                                                                                                                       |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `'string'`        | Text values                                                                                                                       |
| `'Array<string>'` | Array of text values                                                                                                              |
| `'int'`           | Integer numbers                                                                                                                   |
| `'Array<int>'`    | Array of integers                                                                                                                 |
| `'float'`         | Floating point numbers                                                                                                            |
| `'Array<float>'`  | Array of floats                                                                                                                   |
| `'date'`          | Date/time values                                                                                                                  |
| `'Array<date>'`   | Array of dates                                                                                                                    |
| `'boolean'`       | Boolean values: the strings `true`/`false`/`1`/`0` (any case) and the numbers `1`/`0` convert, anything else is a `WrongDataType` |
| `'json'`          | JSON objects                                                                                                                      |
| `'any'`           | Any type                                                                                                                          |

**Examples:**

```typescript title="String property"
{
  key: 'name',
  type: 'string'
}
```

```typescript title="Number with fractionDigits"
{
  key: 'price',
  type: 'float',
  fractionDigits: 2
}
```

```typescript title="Array property"
{
  key: 'tags',
  type: 'Array<string>',
  separator: ','
}
```

```typescript title="Date property"
{
  key: 'createdAt',
  type: 'date',
  inputFormat: 'yyyy-MM-dd',
  UTC: true
}
```

```typescript title="Derived property"
{
  key: 'categoryName',
  type: 'string',
  derive: {
    localReference: 'category',
    remoteProperty: 'name'
  }
}
```

```typescript title="Expression property"
{
  key: 'displayName',
  type: 'string',
  expression: '${brand} ${name}'
}
```

```typescript title="Property with quality checks"
{
  key: 'email',
  type: 'string',
  quality: {
    required: true,
    validators: {
      email: { value: true }
    }
  }
}
```

### Reference

Defines a relationship to another model.

**Type Definition:**

```typescript
// GroupedRefeference is spelled this way in @dcupl/common
type Reference = ReferenceBase | ResolvedReference | DerivedReference | GroupedRefeference | QueryReference;
```

**Common Properties:**

| Property    | Type                              | Description                                                                                                                                                                                                               |
| ----------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`       | `string`                          | Unique reference identifier                                                                                                                                                                                               |
| `type`      | `'singleValued' \| 'multiValued'` | Single or multiple items                                                                                                                                                                                                  |
| `model`     | `string`                          | Target model key                                                                                                                                                                                                          |
| `separator` | `string`                          | Separator for multi-valued references                                                                                                                                                                                     |
| `validity`  | `'loose' \| 'strict'`             | `'loose'` (default) keeps a reference to a key missing in the target model; `'strict'` drops it                                                                                                                           |
| `filter`    | `boolean \| object`               | List the reference in [`catalog.filters`](/sdk/api/catalog#catalogfilters) (filter UI items)                                                                                                                              |
| `origin`    | `string`                          | Read the key(s) from a differently named source field                                                                                                                                                                     |
| `quality`   | `AttributeQualityConfig`          | Only `required` and `nullable` apply ([AttributeQualityConfig](#attributequalityconfig)): a `null` is kept and reported as `NullValue` unless `nullable`; a missing value is an `UndefinedValue` unless `required: false` |
| `meta`      | `ModelMetadata`                   | Metadata attached to the reference (returned by `models.get()`)                                                                                                                                                           |
| `index`     | `boolean`                         | Accepted, no effect: a reference's lookup index is always built at load time                                                                                                                                              |
| `aggregate` | `boolean \| object`               | Accepted and returned by `models.get()`; no effect on queries or `fn.aggregate()`                                                                                                                                         |

**Reference Types:**

**1. Basic Reference**

```typescript
{
  key: 'category',
  type: 'singleValued',
  model: 'Category'
}

{
  key: 'tags',
  type: 'multiValued',
  model: 'Tag',
  separator: ','
}
```

**2. Resolved Reference**

The reverse of a reference: on `Category`, collect every `Product` whose `category` reference points to this category. `resolve.model` is the model that holds the reference, `resolve.reference` the reference on it.

```typescript
{
  key: 'products',
  type: 'multiValued',
  model: 'Product',
  resolve: {
    reference: 'category',
    model: 'Product'
  }
}
```

**3. Derived Reference**

Follows a local reference and reads a reference of the target. Here: the products of this product's category (uses the `products` reference on `Category` from above; the product itself is included).

```typescript
{
  key: 'similarProducts',
  type: 'multiValued',
  model: 'Product',
  derive: {
    localReference: 'category',
    remoteReference: 'products'
  }
}
```

**4. Grouped Reference**

Collects the other items of the same model that share a value of `groupBy.reference`. Here: the other products in this product's category. `includeSelfInGroup: true` adds the item itself (default `false`).

```typescript
{
  key: 'categoryGroup',
  type: 'multiValued',
  model: 'Product',
  groupBy: {
    reference: 'category',
    includeSelfInGroup: false
  }
}
```

**5. Query Reference**

Runs a query for every item. Values in the query can contain `${attribute}` placeholders that are filled from the item's raw data, for example `value: '${category}'`. The query always runs against the reference's `model`; a `modelKey` inside `query` is ignored. `query` also accepts a single condition or an array of conditions, and a `singleValued` query reference takes the first match.

> **Note:** When the rows of the model that defines a query reference change, or its target model (or a model the target reaches through its references), `dcupl.update()` falls back to a full rebuild so the reference is recomputed; other changes stay on the partial path. See [Partial Updates](/sdk/advanced/performance#partial-updates).

```typescript
{
  key: 'activeProducts',
  type: 'multiValued',
  model: 'Product',
  query: {
    modelKey: 'Product',
    queries: [
      { operator: 'eq', attribute: 'status', value: 'active' }
    ]
  }
}
```

### Property options

#### derive

Derives a property value from a referenced model.

**Type:** `{ localReference: string; remoteProperty: string; separator?: string }`

**Examples:**

```typescript title="Derive single value"
{
  key: 'categoryName',
  type: 'string',
  derive: {
    localReference: 'category',
    remoteProperty: 'name'
  }
}
```

```typescript title="Derive multiple values"
{
  key: 'tagNames',
  type: 'string',
  derive: {
    localReference: 'tags',
    remoteProperty: 'name',
    separator: ', '
  }
}
```

Over a multi-valued reference, an `Array<…>` property gets the list of values (`separator` is ignored); a `string`, `int`, `float` or `date` property gets the values joined with `separator` (default `', '`), e.g. `'sale, new'`.

#### expression

Defines a computed property from a template: every `${attribute}` placeholder is replaced with the item's value. Expressions are text templates, not formulas: there is no arithmetic, comparison or condition, and the result is always a string (the declared `type` is not applied). An unknown placeholder becomes an empty string and records an `UnknownExpressionVariable` quality error. Compute numbers before loading the data, for example in a [loader transformer](/sdk/api/loader).

**Type:** `string`

**Examples:**

```typescript title="Combine values"
{
  key: 'displayName',
  type: 'string',
  expression: '${brand} ${name}'
}
```

```typescript title="Text around a value"
{
  key: 'priceLabel',
  type: 'string',
  expression: '${price} EUR'
}
```

```typescript title="Use a derived property"
{
  key: 'label',
  type: 'string',
  expression: '${name} (${categoryName})' // categoryName derived from the category reference
}
```

A placeholder can also follow a single-valued reference (`'${category.name}'`). It resolves, but SDK 2.0 reports it as an `UnknownExpressionVariable` quality error; a derived property as above avoids that. More examples: [Expression properties](/sdk/advanced/expression-properties).

#### quality

Defines validation rules for a property.

**Type:** `AttributeQualityConfig`

**Examples:**

```typescript title="Required field"
{
  key: 'name',
  type: 'string',
  quality: {
    required: true
  }
}
```

```typescript title="Email validation"
{
  key: 'email',
  type: 'string',
  quality: {
    required: true,
    validators: {
      email: { value: true }
    }
  }
}
```

```typescript title="Number range"
{
  key: 'age',
  type: 'int',
  quality: {
    required: true,
    validators: {
      min: { value: 0 },
      max: { value: 120 }
    }
  }
}
```

```typescript title="String pattern"
{
  key: 'zipCode',
  type: 'string',
  quality: {
    validators: {
      pattern: { value: '^[0-9]{5}$' }
    }
  }
}
```

```typescript title="Unique constraint"
{
  key: 'username',
  type: 'string',
  quality: {
    required: true,
    validators: {
      unique: { value: true }
    }
  }
}
```

### ModelQualityConfig

Quality validation configuration for a model.

**Type Definition:**

```typescript
type ModelQualityConfig = {
  enabled?: boolean; // default: true
  attributes?: Omit<AttributeQualityConfig, 'validators'>; // model-wide defaults for every attribute
};
```

`attributes` sets defaults for the quality flags of all properties and references; each attribute's own `quality` overrides them. Validators can only be set per attribute.

**Example:**

```typescript
{
  key: 'Product',
  properties: [...],
  quality: {
    enabled: true,
    attributes: {
      required: true,
      nullable: false
    }
  }
}
```

### AttributeQualityConfig

Quality validation configuration for properties and references (on a reference only `required` and `nullable` apply).

**Type Definition:**

```typescript
type AttributeQualityConfig = {
  required?: boolean;
  nullable?: boolean;
  forceStrictDataType?: boolean;
  validators?: AttributeValidatorConfig;
  validatorHandling?: 'loose' | 'strict';
};
```

**Properties:**

| Property              | Type                       | Description                                                                                                |
| --------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `required`            | `boolean`                  | A missing value is reported as `UndefinedValue` (default: `true`)                                          |
| `nullable`            | `boolean`                  | `null` is allowed; with `false` it is reported as `NullValue` (default: `false`)                           |
| `forceStrictDataType` | `boolean`                  | Turns off type coercion: wrong-typed values are reported as `WrongDataType` and dropped (default: `false`) |
| `validators`          | `AttributeValidatorConfig` | Validation rules                                                                                           |
| `validatorHandling`   | `'loose' \| 'strict'`      | `'loose'` (default) reports a failed validator and keeps the value; `'strict'` also drops the value        |

### AttributeValidatorConfig

Validator configuration for properties.

**Type Definition:**

```typescript
type AttributeValidatorConfig = {
  email?: ValidatorConfig;
  endsWith?: ValidatorConfig;
  enum?: ValidatorConfig;
  includes?: ValidatorConfig;
  max?: ValidatorConfig;
  maxLength?: ValidatorConfig;
  min?: ValidatorConfig;
  minLength?: ValidatorConfig;
  pattern?: ValidatorConfig;
  unique?: ValidatorConfig;
  startsWith?: ValidatorConfig;
};
```

Validators check `string`, `int` and `float` values (for `Array<…>` types, each element). Other property types and references are not validated, except by `unique`, which only detects repeated primitive values (not dates, arrays or objects). A validator runs whenever its key is present, so `email: { value: false }` still checks the format. A failed validator is reported as an `InvalidValidator` quality error.

**Available Validators:**

| Validator    | Description              | Value Type |
| ------------ | ------------------------ | ---------- |
| `email`      | Email format validation  | `boolean`  |
| `endsWith`   | String ends with value   | `string`   |
| `enum`       | Value must be in list    | `any[]`    |
| `includes`   | String contains value    | `string`   |
| `max`        | Maximum value            | `number`   |
| `maxLength`  | Maximum string length    | `number`   |
| `min`        | Minimum value            | `number`   |
| `minLength`  | Minimum string length    | `number`   |
| `pattern`    | Regex pattern match      | `string`   |
| `unique`     | Unique across all items  | `boolean`  |
| `startsWith` | String starts with value | `string`   |

**Examples:**

```typescript
validators: {
  email: { value: true },
  minLength: { value: 5 },
  maxLength: { value: 100 },
  pattern: { value: '^[a-zA-Z0-9]+$' }
}

validators: {
  min: { value: 0 },
  max: { value: 1000 },
  unique: { value: true }
}

validators: {
  enum: { value: ['active', 'inactive', 'pending'] },
  startsWith: { value: 'PRD-' }
}
```

## Complete examples

### Product model

```typescript
dcupl.models.set({
  key: 'Product',
  keyProperty: 'productId',
  properties: [
    {
      key: 'name',
      type: 'string',
      quality: {
        required: true,
        validators: {
          minLength: { value: 3 },
          maxLength: { value: 100 },
        },
      },
    },
    {
      key: 'price',
      type: 'float',
      fractionDigits: 2,
      quality: {
        required: true,
        validators: {
          min: { value: 0 },
        },
      },
    },
    {
      key: 'description',
      type: 'string',
    },
    {
      key: 'tags',
      type: 'Array<string>',
      separator: ',',
    },
    {
      key: 'createdAt',
      type: 'date',
      inputFormat: 'yyyy-MM-dd',
      UTC: true,
    },
  ],
  references: [
    {
      key: 'category',
      type: 'singleValued',
      model: 'Category',
    },
    {
      key: 'manufacturer',
      type: 'singleValued',
      model: 'Manufacturer',
    },
  ],
  quality: {
    enabled: true,
  },
});
```

### Model with derived properties

```typescript
dcupl.models.set({
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
    {
      key: 'categoryName',
      type: 'string',
      derive: {
        localReference: 'category',
        remoteProperty: 'name',
      },
    },
    {
      key: 'label',
      type: 'string',
      expression: '${name} (${categoryName})',
    },
  ],
  references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
});
```

### Model with quality checks

```typescript
dcupl.models.set({
  key: 'User',
  properties: [
    {
      key: 'username',
      type: 'string',
      quality: {
        required: true,
        validators: {
          minLength: { value: 3 },
          maxLength: { value: 20 },
          pattern: { value: '^[a-zA-Z0-9_]+$' },
          unique: { value: true },
        },
      },
    },
    {
      key: 'email',
      type: 'string',
      quality: {
        required: true,
        validators: {
          email: { value: true },
          unique: { value: true },
        },
      },
    },
    {
      key: 'age',
      type: 'int',
      quality: {
        validators: {
          min: { value: 18 },
          max: { value: 120 },
        },
      },
    },
    {
      key: 'status',
      type: 'string',
      quality: {
        required: true,
        validators: {
          enum: { value: ['active', 'inactive', 'pending'] },
        },
      },
    },
  ],
  quality: {
    enabled: true,
    attributes: {
      required: true,
      nullable: false,
    },
  },
});
```

### Complex references

```typescript
// Product model with various reference types
dcupl.models.set({
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
  ],
  references: [
    // Basic reference
    {
      key: 'category',
      type: 'singleValued',
      model: 'Category',
    },
    // Multi-valued reference
    {
      key: 'tags',
      type: 'multiValued',
      model: 'Tag',
      separator: ',',
    },
    // Query reference: products in the same category (${category} is this item's category key)
    {
      key: 'similarProducts',
      type: 'multiValued',
      model: 'Product',
      query: {
        modelKey: 'Product',
        queries: [{ operator: 'eq', attribute: 'category', value: '${category}' }],
      },
    },
  ],
});

// Category model with a resolved reference back to its products
dcupl.models.set({
  key: 'Category',
  properties: [{ key: 'name', type: 'string' }],
  references: [
    {
      key: 'products',
      type: 'multiValued',
      model: 'Product',
      resolve: {
        reference: 'category',
        model: 'Product',
      },
    },
  ],
});
```

## See also

- [Dcupl Class API Reference](/sdk/api/dcupl)
- [Views](/sdk/api/dcupl#views): a model file can also hold a view definition, which the loader passes to `dcupl.views.set()`
- [Data API Reference](/sdk/api/data)
- [Models](/sdk/core-concepts/models)
- [Data management](/sdk/core-concepts/data-management)
