# Data API

The Data API (`dcupl.data`) provides methods for managing data in dcupl. It supports various operations including setting, updating, upserting, and removing data.

## Overview

The Data API offers different strategies for data management:

| Method     | Existing key                 | New key  | Rows not in the payload | Update path                |
| ---------- | ---------------------------- | -------- | ----------------------- | -------------------------- |
| `set()`    | replaced                     | inserted | removed                 | always a full rebuild      |
| `update()` | attributes in payload change | ignored  | kept                    | partial (exceptions below) |
| `upsert()` | attributes in payload change | inserted | kept                    | partial (exceptions below) |
| `remove()` | removed                      | ignored  | kept                    | partial (exceptions below) |
| `reset()`  | removed                      | -        | removed                 | always a full rebuild      |
| `apply()`  | as its container's `type`    |          |                         |                            |

The thresholds are in `performance.partialUpdates` ([DcuplPartialUpdateConfig](/sdk/api/dcupl#dcuplpartialupdateconfig)): `dcupl.update()` rebuilds everything when both the share of changed models and the share of changed items exceed their thresholds (default `0.5` each). It also always rebuilds after a model definition change after `init()` or a staged change that can alter a [query reference](/sdk/api/models#reference)'s result; the full list is under [DcuplPartialUpdateConfig](/sdk/api/dcupl#dcuplpartialupdateconfig).

All data operations are queued and processed when you call `dcupl.init()` or `dcupl.update()`.

## Methods

### set()

Replaces all existing data for a model with the provided data.

Rows with the same key in one `set()` payload are deduped: the last row wins as a whole (no attribute
of an earlier row is kept), and each replaced row is recorded as a `NonUniqueKey` quality error.
Keys are compared as strings, so `1` and `'1'` collide.

**Signature:**

```typescript
set(data: RawItem[], options: DataOptions): void
```

**Parameters:**

| Parameter | Type          | Required | Description            |
| --------- | ------------- | -------- | ---------------------- |
| `data`    | `RawItem[]`   | Yes      | Array of data items    |
| `options` | `DataOptions` | Yes      | Data operation options |

**Returns:** `void`

**Examples:**

```typescript title="Basic set operation"
dcupl.data.set(
  [
    { key: 'p1', name: 'Product 1', price: 100 },
    { key: 'p2', name: 'Product 2', price: 200 },
  ],
  { model: 'Product' },
);
```

```typescript title="With custom key property"
dcupl.data.set(
  [
    { id: 'p1', name: 'Product 1' },
    { id: 'p2', name: 'Product 2' },
  ],
  {
    model: 'Product',
    keyProperty: 'id',
  },
);
```

```typescript title="With auto-generated keys"
dcupl.data.set([{ name: 'Product 1' }, { name: 'Product 2' }], {
  model: 'Product',
  autoGenerateKey: true,
});
```

```typescript title="Replace all products"
dcupl.data.set(newProducts, { model: 'Product' });
await dcupl.update();
```

### update()

Updates existing items by their keys. An item whose key does not exist yet is ignored — use
[`upsert()`](#upsert) to add items.

This also holds for a model that has no data yet: `update()` and `remove()` ignore unknown keys
there too and never insert rows. Keys are compared as strings, so `{ key: '1' }` patches a row
keyed `1`.

**Signature:**

```typescript
update(data: RawItem[], options: DataOptions): void
```

**Parameters:**

| Parameter | Type          | Required | Description              |
| --------- | ------------- | -------- | ------------------------ |
| `data`    | `RawItem[]`   | Yes      | Array of items to update |
| `options` | `DataOptions` | Yes      | Data operation options   |

**Returns:** `void`

**Examples:**

```typescript title="Update single item"
dcupl.data.update([{ key: 'p1', price: 150 }], { model: 'Product' });
```

```typescript title="Update multiple items"
dcupl.data.update(
  [
    { key: 'p1', price: 150, stock: 20 },
    { key: 'p2', price: 250, stock: 15 },
  ],
  { model: 'Product' },
);
```

```typescript title="Partial update - only specified fields"
dcupl.data.update(
  [
    { key: 'p1', price: 150 }, // Only price is updated
  ],
  { model: 'Product' },
);
await dcupl.update();
```

```typescript title="Update with custom key property"
dcupl.data.update([{ id: 'p1', price: 150 }], {
  model: 'Product',
  keyProperty: 'id',
});
```

**Attribute replace rule:** every attribute present in an `update()` or `upsert()` payload replaces
the stored value — arrays, multi-valued references and `json` values are replaced as a whole, not
merged. Attributes you leave out (or set to `undefined`) keep their value; send `null` to clear one.
A value that cannot be converted to the property's type is dropped and recorded as a `WrongDataType`
quality error: the attribute ends up without a value, the previous one is not kept.

```typescript title="Replace an array, clear a value"
// Stored: { key: 'p1', name: 'Laptop', price: 100, tags: ['a', 'b'] }
dcupl.data.update([{ key: 'p1', tags: ['c'], name: null }], { model: 'Product' });
await dcupl.update();
// Now:    { key: 'p1', name: null, price: 100, tags: ['c'] }
```

### upsert()

Updates existing items or inserts new ones. If an item with the key exists, it's updated (same
[attribute replace rule](#update) as `update()`); otherwise, it's inserted.

**Signature:**

```typescript
upsert(data: RawItem[], options: DataOptions): void
```

**Parameters:**

| Parameter | Type          | Required | Description              |
| --------- | ------------- | -------- | ------------------------ |
| `data`    | `RawItem[]`   | Yes      | Array of items to upsert |
| `options` | `DataOptions` | Yes      | Data operation options   |

**Returns:** `void`

**Examples:**

```typescript title="Upsert items (update if exists, insert if not)"
dcupl.data.upsert(
  [
    { key: 'p1', name: 'Updated Product 1', price: 150 }, // Updates if exists
    { key: 'p4', name: 'New Product 4', price: 400 }, // Inserts if not exists
  ],
  { model: 'Product' },
);
```

```typescript title="Upsert with auto-key generation for new items"
dcupl.data.upsert(
  [
    { key: 'p1', price: 150 },
    { name: 'New Product' }, // Gets auto-generated key
  ],
  {
    model: 'Product',
    autoGenerateKey: true,
  },
);
```

```typescript title="Safe data synchronization"
dcupl.data.upsert(fetchedData, { model: 'Product' });
await dcupl.update();
```

### remove()

Removes specific items from the model.

**Signature:**

```typescript
remove(data: RawItem[], options: DataOptions): void
```

**Parameters:**

| Parameter | Type          | Required | Description                                |
| --------- | ------------- | -------- | ------------------------------------------ |
| `data`    | `RawItem[]`   | Yes      | Array of items to remove (only key needed) |
| `options` | `DataOptions` | Yes      | Data operation options                     |

**Returns:** `void`

**Examples:**

```typescript title="Remove single item"
dcupl.data.remove([{ key: 'p1' }], { model: 'Product' });
```

```typescript title="Remove multiple items"
dcupl.data.remove([{ key: 'p1' }, { key: 'p2' }, { key: 'p3' }], { model: 'Product' });
```

```typescript title="Remove with custom key property"
dcupl.data.remove([{ id: 'p1' }], {
  model: 'Product',
  keyProperty: 'id',
});
```

```typescript title="Remove items and update"
dcupl.data.remove(itemsToDelete, { model: 'Product' });
await dcupl.update();
```

### reset()

Removes all data for a specific model.

Like the other data methods, `reset()` is **staged**: it is applied by the next
`dcupl.update()` or `dcupl.init()`, it is folded in call order with mutations staged after it
(`reset()` then `upsert()` leaves only the upserted items), and it invalidates the lists that
use the model. It is equivalent to `dcupl.data.set([], { model })`.

**Signature:**

```typescript
reset(options: { model: string }): void
```

**Parameters:**

| Parameter | Type                | Required | Description            |
| --------- | ------------------- | -------- | ---------------------- |
| `options` | `{ model: string }` | Yes      | Options with model key |

**Returns:** `void`

**Examples:**

```typescript title="Clear all products"
dcupl.data.reset({ model: 'Product' });
await dcupl.update();
```

```typescript title="Reset, then stage new items"
// Later mutations win: only the two upserted products survive the update.
dcupl.data.reset({ model: 'Product' });
dcupl.data.upsert([{ key: 'p1' }, { key: 'p2' }], { model: 'Product' });
await dcupl.update();
```

```typescript title="Reset and reload"
dcupl.data.reset({ model: 'Product' });
dcupl.data.set(newData, { model: 'Product' });
await dcupl.update();
```

```typescript title="Clear multiple models"
dcupl.data.reset({ model: 'Product' });
dcupl.data.reset({ model: 'Category' });
await dcupl.update();
```

### apply()

Stages a data container directly (advanced usage). The container's `type` picks the operation;
without a `type` it is `'set'` and replaces the model's data.

**Signature:**

```typescript
apply(container: DataContainer): void
```

**Parameters:**

| Parameter   | Type            | Required | Description             |
| ----------- | --------------- | -------- | ----------------------- |
| `container` | `DataContainer` | Yes      | Data container to apply |

**Returns:** `void`

**Examples:**

```typescript title="Apply custom container"
const container: DataContainer = {
  model: 'Product',
  type: 'upsert',
  data: products,
  keyProperty: 'id',
};
dcupl.data.apply(container);
```

```typescript title="Advanced container with options"
dcupl.data.apply({
  model: 'Product',
  type: 'set',
  data: [{ productId: 'p1', name: 'Laptop', color: 'silver' }],
  keyProperty: 'productId',
  autoGenerateKey: false,
  autoGenerateProperties: true, // adds the undeclared 'color' property
});
await dcupl.update();
```

`autoGenerateProperties` also covers an attribute that an `update` or `upsert` container brings in
after `init()`: when `dcupl.update()` applies the container, the property is added to the model and
filled for every row that carries the attribute (SDK 2.0).

## Configuration types

### DataOptions

Configuration options for data operations.

**Type Definition:**

```typescript
type DataOptions = {
  model: string;
  keyProperty?: string | readonly string[];
  keyPropertySeparator?: string;
  autoGenerateKey?: boolean;
};
```

**Properties:**

| Property               | Type                          | Default                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ---------------------- | ----------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`                | `string`                      | -                         | Model key (required)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `keyProperty`          | `string \| readonly string[]` | the model's, else `'key'` | Property name to use as item key; an array builds a composite key                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `keyPropertySeparator` | `string`                      | the model's, else `'::'`  | Separator joining the parts of a composite key                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `autoGenerateKey`      | `boolean`                     | the model's, else `false` | Auto-generate keys for items without one. Without it, a row with no `key` is dropped: `set()` and `upsert()` record a `MissingKey` quality error (an `upsert()` applied on the [partial path](#partial-updates) after `init()` currently records it only at the next full rebuild), `update()` and `remove()` skip it without one. The same holds for a custom `keyProperty` (`undefined` and `null` count as missing), also with `autoGenerateKey`: it only fills the default `key`. With a composite `keyProperty` the row is dropped only when every part is missing; a missing part is kept as `'undefined'` (or `'null'`) in the key, e.g. `x::undefined`, and several rows with the same partial key collapse into one, reported as `NonUniqueKey` |

**Examples:**

```typescript title="Basic options"
const options: DataOptions = { model: 'Product' };
```

```typescript title="Custom key property"
const options: DataOptions = { model: 'Product', keyProperty: 'productId' };
```

```typescript title="Auto-generate keys"
// rows without a key get a generated one, readable as item.key once applied
dcupl.data.set(
  [
    { name: 'Product 1', price: 100 },
    { name: 'Product 2', price: 200 },
  ],
  { model: 'Product', autoGenerateKey: true },
);
await dcupl.init();
```

```typescript title="Composite key"
// keys like 'A1-en'
const options: DataOptions = { model: 'Product', keyProperty: ['sku', 'locale'], keyPropertySeparator: '-' };
```

### DataContainer

Advanced data container for direct application.

**Type Definition:**

```typescript
type DataContainer = {
  model: string;
  type?: DataContainerType;
  data: RawItem[];
  keyProperty?: string | readonly string[];
  keyPropertySeparator?: string;
  autoGenerateKey?: boolean;
  autoGenerateProperties?: boolean | { deep?: boolean; sampleSize?: number };
  placeholderUid?: string;
};

type DataContainerType = 'upsert' | 'update' | 'set' | 'remove';
```

**Properties:**

| Property                 | Type                          | Description                                                                                                      |
| ------------------------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `model`                  | `string`                      | Model key                                                                                                        |
| `type`                   | `DataContainerType`           | Operation type (default `'set'`)                                                                                 |
| `data`                   | `RawItem[]`                   | Data items                                                                                                       |
| `keyProperty`            | `string \| readonly string[]` | Key property name (array = composite key)                                                                        |
| `keyPropertySeparator`   | `string`                      | Separator for composite keys (default `'::'`)                                                                    |
| `autoGenerateKey`        | `boolean`                     | Auto-generate keys                                                                                               |
| `autoGenerateProperties` | `boolean \| object`           | Auto-generate properties from data (`true`, `{ deep: true }` or `{ sampleSize: N }`)                             |
| `placeholderUid`         | `string`                      | Reserved for the loader; don't set it. A container with a `placeholderUid` is skipped, so its data is not loaded |

## Performance considerations

### Partial updates

`update()`, `upsert()` and `remove()` re-process only the changed attributes and their dependents;
when `dcupl.update()` rebuilds everything instead is stated in the [Overview](#overview). Staged
operations are applied together, so stage all changes and call `dcupl.update()` once rather than
after each call. Importing a large dataset in batches makes the first rows queryable early, but every
batch is a separate `update()`; if you only need the end result, one `set()` and one `init()` is
faster. Validation of the applied data is covered in
[Quality validation](/sdk/advanced/quality-validation); loading, syncing and migrating data step by
step in [Data management](/sdk/core-concepts/data-management) and
[Real-time updates](/sdk/advanced/realtime-updates).

### Auto-update control

Every `dcupl.update()` drops the caches of all affected lists, so unscoped lists always see the new
data. Auto-update only decides whether a `query`-scoped list re-runs its seed query; it is off by
default (`performance.autoUpdateLists.enabled: false`). See
[Lists API → Auto-update pattern](/sdk/api/lists#auto-update-pattern).

```typescript title="Perform bulk updates without re-running seed queries"
dcupl.data.update(batch1, { model: 'Product' });
dcupl.data.update(batch2, { model: 'Product' });
await dcupl.update({ autoUpdateLists: false });
```

```typescript title="Re-run a query-scoped list's seed query when ready"
myList.update();
```

## See also

- [Dcupl Class API Reference](/sdk/api/dcupl)
- [Models API Reference](/sdk/api/models)
- [Lists API Reference](/sdk/api/lists)
- [Data management](/sdk/core-concepts/data-management)
