# Data management

Loading, updating, and managing your data with dcupl's data API.

## Data API overview

All data operations are accessed through `dcupl.data`:

- `set()` - Replace all data for a model
- `update()` - Incremental updates to existing items
- `upsert()` - Insert or update items
- `remove()` - Delete specific items
- `reset()` - Remove all items of a model
- `apply()` - Stage a whole `DataContainer` (`{ model, type, data }`, `type` one of `'set'`, `'update'`, `'upsert'`, `'remove'`), for example one you built from a sync message

Data operations are staged. They take effect when you call `dcupl.init()` to initialize dcupl, or `dcupl.update()` to process changes after initialization.

```typescript filename="data-api.ts"
// Data operations overview: each call is staged
dcupl.data.set([{ key: 'p1', name: 'Laptop', price: 999 }], { model: 'Product' }); // Replace all data
dcupl.data.update([{ key: 'p1', price: 899 }], { model: 'Product' }); // Update existing items
dcupl.data.upsert([{ key: 'p2', name: 'Mouse', price: 29 }], { model: 'Product' }); // Insert or update
dcupl.data.remove([{ key: 'p2' }], { model: 'Product' }); // Delete items
dcupl.data.reset({ model: 'Product' }); // Remove all items of a model
dcupl.data.apply({ model: 'Product', type: 'upsert', data: [{ key: 'p3', name: 'Desk' }] }); // A whole container

// Process the staged changes (dcupl.init() the first time)
await dcupl.update();
```

## set() - replace all data

**Use when:** You want to completely replace all data for a model.

This method clears existing data and replaces it with the provided items. Useful for initial data loading or full refreshes.

```typescript filename="set-data.ts"
dcupl.data.set(
  [
    { key: 'p1', name: 'Laptop', price: 999 },
    { key: 'p2', name: 'Mouse', price: 29 },
  ],
  { model: 'Product' }
);

await dcupl.update();
```

## update() - incremental update

**Use when:** You want to modify existing items. Items whose key does not exist yet are ignored, also in a model that has no data yet: `update()` never inserts rows. Use `upsert()` to add them (see [update()](/sdk/api/data#update)).

Only include changed fields for better performance. Each field in the payload replaces the stored value as a whole: arrays and `json` objects are replaced, not merged. Fields you leave out (or set to `undefined`) keep their value; send `null` to clear a field.

```typescript filename="update-data.ts"
dcupl.data.update(
  [
    { key: 'p1', price: 899 }, // Only changed fields
    { key: 'p2', tags: ['sale'] }, // Replaces the whole tags array
  ],
  { model: 'Product' }
);

await dcupl.update();
```

## upsert() - insert or update

**Use when:** You don't know if items exist.

If an item with the key exists, it's updated. If not, it's created. Combines the behavior of insert and update.

```typescript filename="upsert-data.ts"
dcupl.data.upsert(
  [
    { key: 'p1', price: 899 },    // Updates if exists
    { key: 'p99', name: 'Chair' }, // Creates if doesn't exist
  ],
  { model: 'Product' }
);

await dcupl.update();
```

## remove() - delete items

**Use when:** You want to delete specific items.

Provide items with at least the key property to identify which items to remove.

```typescript filename="remove-data.ts"
dcupl.data.remove(
  [{ key: 'p1' }, { key: 'p2' }],
  { model: 'Product' }
);

await dcupl.update();
```

## Data options

`set()`, `update()`, `upsert()` and `remove()` take a [`DataOptions`](/sdk/api/data#dataoptions) object; `reset()` takes only `{ model }`. An option you leave out falls back to the model's setting:

| Option | Description |
| --- | --- |
| `model` | **Required.** Specify which model the data belongs to |
| `keyProperty` | Override the primary key field (default: the model's, else `'key'`). An array builds a composite key. A row with no value for it (for a composite key: no value for any of its parts) is dropped: `set()` and `upsert()` record it as a `MissingKey` quality error (unless `autoGenerateKey` gives it a key, which happens only with the default `'key'`), `update()` and `remove()` skip it. An `upsert()` after `init()` that `dcupl.update()` applies on the [partial path](/sdk/api/data#partial-updates) currently drops the row without the error; the next full rebuild (`init()`, or an `update()` that rebuilds) records it |
| `keyPropertySeparator` | Separator joining the parts of a composite key (default: the model's, else `'::'`) |
| `autoGenerateKey` | Generate a key for rows without one. Works only with the default `'key'`. When `keyProperty`, in these options or on the model, names another field or is an array, it is ignored and rows without a key are still dropped as `MissingKey` |

```typescript filename="data-options.ts"
// Custom key property
dcupl.data.set(
  [{ id: '1', name: 'Laptop' }],
  {
    model: 'Product',
    keyProperty: 'id',
  }
);

// Auto-generate keys
dcupl.data.set(
  [
    { name: 'Laptop', price: 999 }, // No key provided
  ],
  {
    model: 'Product',
    autoGenerateKey: true,
  }
);
```

## Performance tips

#### Batch updates

Always batch multiple items into a single update call. Avoid calling update inside loops.

#### Use incremental updates

Prefer `update()` over `set()` when possible. Full replacement is slower than incremental updates.

#### Only include changed fields

When updating, only include the fields that actually changed. This reduces processing overhead.

```typescript filename="performance.ts"
// Good: Single update with multiple items
dcupl.data.update(
  [
    { key: 'p1', price: 899 },
    { key: 'p2', price: 25 },
  ],
  { model: 'Product' }
);
await dcupl.update();

// Avoid: Multiple individual updates
const changed = [{ key: 'p1', price: 899 }, { key: 'p2', price: 25 }];
for (const product of changed) {
  dcupl.data.update([product], { model: 'Product' });
  await dcupl.update(); // Triggers processing each time!
}

// Good: only the changed field
dcupl.data.update([{ key: 'p1', price: 899 }], { model: 'Product' });

// Wasteful: all fields when only price changed
dcupl.data.update([{ key: 'p1', name: 'Laptop', price: 899, category: 'Electronics' }], { model: 'Product' });
```

## Best practices

#### Always await dcupl.update()

Data operations are queued and processed when you call `dcupl.update()`. Always await it before querying to ensure you get the latest data.

Missing the await can lead to stale data bugs that are hard to track down.

```typescript filename="best-practices.ts"
const list = dcupl.lists.create({ modelKey: 'Product' });

// Good: Await update before querying
dcupl.data.update([{ key: 'p1', price: 899 }], { model: 'Product' });
await dcupl.update();
const results = list.catalog.query.items();

// Bad: Missing await - data may be stale!
dcupl.data.update([{ key: 'p1', price: 799 }], { model: 'Product' });
dcupl.update(); // Not awaited!
const staleResults = list.catalog.query.items(); // Stale data!
```

## Next steps

- [Lists](/sdk/core-concepts/lists) — Query your updated data
- [Real-Time Updates](/sdk/advanced/realtime-updates) — Live data updates pattern
- [Hybrid Data](/sdk/guides/hybrid-data) — Combine static and dynamic data
