.md

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): 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's result; the full list is under 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:

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:

Basic set operation
dcupl.data.set(
  [
    { key: 'p1', name: 'Product 1', price: 100 },
    { key: 'p2', name: 'Product 2', price: 200 },
  ],
  { model: 'Product' },
);
With custom key property
dcupl.data.set(
  [
    { id: 'p1', name: 'Product 1' },
    { id: 'p2', name: 'Product 2' },
  ],
  {
    model: 'Product',
    keyProperty: 'id',
  },
);
With auto-generated keys
dcupl.data.set([{ name: 'Product 1' }, { name: 'Product 2' }], {
  model: 'Product',
  autoGenerateKey: true,
});
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() 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:

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:

Update single item
dcupl.data.update([{ key: 'p1', price: 150 }], { model: 'Product' });
Update multiple items
dcupl.data.update(
  [
    { key: 'p1', price: 150, stock: 20 },
    { key: 'p2', price: 250, stock: 15 },
  ],
  { model: 'Product' },
);
Partial update - only specified fields
dcupl.data.update(
  [
    { key: 'p1', price: 150 }, // Only price is updated
  ],
  { model: 'Product' },
);
await dcupl.update();
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.

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 as update()); otherwise, it's inserted.

Signature:

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:

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' },
);
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,
  },
);
Safe data synchronization
dcupl.data.upsert(fetchedData, { model: 'Product' });
await dcupl.update();

remove()

Removes specific items from the model.

Signature:

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:

Remove single item
dcupl.data.remove([{ key: 'p1' }], { model: 'Product' });
Remove multiple items
dcupl.data.remove([{ key: 'p1' }, { key: 'p2' }, { key: 'p3' }], { model: 'Product' });
Remove with custom key property
dcupl.data.remove([{ id: 'p1' }], {
  model: 'Product',
  keyProperty: 'id',
});
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:

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

Parameters:

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

Returns: void

Examples:

Clear all products
dcupl.data.reset({ model: 'Product' });
await dcupl.update();
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();
Reset and reload
dcupl.data.reset({ model: 'Product' });
dcupl.data.set(newData, { model: 'Product' });
await dcupl.update();
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:

apply(container: DataContainer): void

Parameters:

Parameter Type Required Description
container DataContainer Yes Data container to apply

Returns: void

Examples:

Apply custom container
const container: DataContainer = {
  model: 'Product',
  type: 'upsert',
  data: products,
  keyProperty: 'id',
};
dcupl.data.apply(container);
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:

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

Basic options
const options: DataOptions = { model: 'Product' };
Custom key property
const options: DataOptions = { model: 'Product', keyProperty: 'productId' };
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();
Composite key
// keys like 'A1-en'
const options: DataOptions = { model: 'Product', keyProperty: ['sku', 'locale'], keyPropertySeparator: '-' };

DataContainer

Advanced data container for direct application.

Type Definition:

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. 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; loading, syncing and migrating data step by step in Data management and Real-time 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.

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 });
Re-run a query-scoped list's seed query when ready
myList.update();

See also