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): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of data items |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
dcupl.data.set(
[
{ key: 'p1', name: 'Product 1', price: 100 },
{ key: 'p2', name: 'Product 2', price: 200 },
],
{ model: 'Product' },
);dcupl.data.set(
[
{ id: 'p1', name: 'Product 1' },
{ id: 'p2', name: 'Product 2' },
],
{
model: 'Product',
keyProperty: 'id',
},
);dcupl.data.set([{ name: 'Product 1' }, { name: 'Product 2' }], {
model: 'Product',
autoGenerateKey: true,
});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): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of items to update |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
dcupl.data.update([{ key: 'p1', price: 150 }], { model: 'Product' });dcupl.data.update(
[
{ key: 'p1', price: 150, stock: 20 },
{ key: 'p2', price: 250, stock: 15 },
],
{ model: 'Product' },
);dcupl.data.update(
[
{ key: 'p1', price: 150 }, // Only price is updated
],
{ model: 'Product' },
);
await dcupl.update();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.
// 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): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of items to upsert |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
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' },
);dcupl.data.upsert(
[
{ key: 'p1', price: 150 },
{ name: 'New Product' }, // Gets auto-generated key
],
{
model: 'Product',
autoGenerateKey: true,
},
);dcupl.data.upsert(fetchedData, { model: 'Product' });
await dcupl.update();remove()
Removes specific items from the model.
Signature:
remove(data: RawItem[], options: DataOptions): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of items to remove (only key needed) |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
dcupl.data.remove([{ key: 'p1' }], { model: 'Product' });dcupl.data.remove([{ key: 'p1' }, { key: 'p2' }, { key: 'p3' }], { model: 'Product' });dcupl.data.remove([{ id: 'p1' }], {
model: 'Product',
keyProperty: 'id',
});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 }): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
{ model: string } |
Yes | Options with model key |
Returns: void
Examples:
dcupl.data.reset({ model: 'Product' });
await dcupl.update();// 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();dcupl.data.reset({ model: 'Product' });
dcupl.data.set(newData, { model: 'Product' });
await dcupl.update();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): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
container |
DataContainer |
Yes | Data container to apply |
Returns: void
Examples:
const container: DataContainer = {
model: 'Product',
type: 'upsert',
data: products,
keyProperty: 'id',
};
dcupl.data.apply(container);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:
const options: DataOptions = { model: 'Product' };const options: DataOptions = { model: 'Product', keyProperty: 'productId' };// 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();// 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.
dcupl.data.update(batch1, { model: 'Product' });
dcupl.data.update(batch2, { model: 'Product' });
await dcupl.update({ autoUpdateLists: false });myList.update();