.md

Dcupl class API

The Dcupl class is the main entry point for the dcupl SDK. It provides access to models, lists, data operations, and query functionality.

Constructor

new Dcupl(options?)

Creates a new dcupl instance.

Signature:

constructor(options?: DcuplInitOptions): Dcupl

Parameters:

Parameter Type Required Description
options DcuplInitOptions No Configuration options for initialization

Returns: Dcupl instance

Dcupl takes an optional type parameter for your model keys (Dcupl<ModelKeys extends string = string>). It narrows the model-key parameters of models.get/set/update/remove, data.reset and data.apply, so a typo in a model key is a compile error.

The option and data types (DcuplInitOptions, DataOptions, DataContainer, …) are exported from @dcupl/common; @dcupl/core exports only the Dcupl and DcuplList classes. The namespaces on the instance (dcupl.models, dcupl.data, …) are plain objects without an exported type.

Examples:

Create a basic instance
const dcupl = new Dcupl();
Type the model keys
const dcupl = new Dcupl<'Product' | 'Category'>();
dcupl.models.get('Product'); // ok
dcupl.data.reset({ model: 'Prodcut' }); // compile error: not a model key
Create with configuration
const dcupl = new Dcupl({
  config: {
    projectId: 'your-project-id',
    apiKey: 'your-api-key',
    version: 'draft',
  },
});
Configure quality checks
const dcupl = new Dcupl({
  quality: {
    enabled: true, // default
    maxErrorsPerGroup: 1000, // default
  },
});
Configure performance caching
const dcupl = new Dcupl({
  performance: {
    cache: {
      enabled: true, // default
    },
  },
});
Auto-update query-scoped lists
const dcupl = new Dcupl({
  performance: {
    autoUpdateLists: {
      enabled: true,
    },
  },
});

Properties

models

Access to the models API for managing data models.

Type: object on the instance (no exported type)

See: Models API Reference

Example:

const model = dcupl.models.get('Product');

lists

Access to the lists API for managing data lists.

Type: object on the instance (no exported type)

See: Lists API Reference

Example:

const list = dcupl.lists.create({ modelKey: 'Product' });

data

Access to the data API for managing data operations.

Type: object on the instance (no exported type)

See: Data API Reference

Example:

dcupl.data.set(products, { model: 'Product' });

query

Access to query operations.

Type: object on the instance (no exported type)

Methods:

Method Signature Description
one (options: { modelKey: string; itemKey: string; projection?: Projection }) => T | null Get one item by key (null if the key is unknown)
many (options: { modelKey: string; itemKeys: string[]; projection?: Projection }) => T[] Get items by key (unknown keys are skipped)
execute (options: DcuplGlobalQueryOptions) => T[] Run a query: modelKey, queries, plus item options (sort, start, count, projection)
generate (modelKey: string, query: DcuplQuery | DcuplQueryGroup | DcuplQuery[] | DcuplQueryGroup[]) => DcuplGlobalQueryOptions Build the options object execute takes, without running it
registerCustomOperator (operator: string, fn: (query: DcuplQuery, item: ListItem) => boolean) => void Deprecated: register a custom query operator. Use an operator script instead, which takes precedence

one and many throw an Error with an empty message when modelKey is not a known model.

Example:

const items = dcupl.query.execute({
  modelKey: 'Product',
  queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
});

fn

Access to analytical functions.

Type: object on the instance (no exported type)

pivot, aggregate, suggest, groupBy, facets and metadata. Each takes { modelKey, options, query? } (metadata takes { modelKey, query? }) and works on all items of that model, or on those that match query; see the Functions API.

Example:

const facets = dcupl.fn.facets({
  modelKey: 'Product',
  options: { attribute: 'category' },
});

views

Access to view definitions. Views are saved report shapes (section views for groupBy, pivot views for cross-tabulation). The SDK only stores them: nothing computes a view for you. Read one with views.get() (or from dcupl.fn.metadata({ modelKey }).views) and pass its settings to fn.groupBy / fn.pivot yourself.

Type: object on the instance (no exported type)

Methods:

Method Signature Description
get (viewKey: string) => ViewDefinition | undefined Get a view definition by key
keys (options?: { modelKey: string }) => string[] Get all view keys, optionally filtered by model
set (view: ViewDefinition) => void Register a new view definition
remove (viewKey: string) => void Unregister a view at once; no update() needed (SDK 2.0)

View Types:

// Section View - for groupBy operations
type ViewDefinitionSection = {
  key: string;
  model: string;
  type: 'section';
  primaryAttribute: string;
  aggregates: AggregationOptions[];
};

// Pivot View - for pivot table operations
type ViewDefinitionPivot = {
  key: string;
  model: string;
  type: 'pivot';
  options: PivotOptions;
};

Examples:

// Register a section view
dcupl.views.set({
  key: 'productsByCategory',
  model: 'Product',
  type: 'section',
  primaryAttribute: 'category',
  aggregates: [{ attribute: 'price', types: ['sum', 'avg'] }],
});

// Register a pivot view
dcupl.views.set({
  key: 'salesPivot',
  model: 'Order',
  type: 'pivot',
  options: {
    rows: [{ attribute: 'region' }],
    columns: [{ attribute: 'quarter' }],
    values: [{ attribute: 'revenue', types: ['sum'] }],
  },
});

// Get a view
const view = dcupl.views.get('productsByCategory');

// Get all view keys for a model
const productViews = dcupl.views.keys({ modelKey: 'Product' });

scripts

Access to custom scripts. Scripts allow you to execute custom JavaScript code with access to the dcupl instance.

Type: object on the instance (no exported type)

Methods:

Method Signature Description
set (key: string, script: string) => void Register a script by key
get (key: string) => string | undefined Get a script by key
remove (key: string) => void Remove a script
keys () => string[] Get all registered script keys
execute (key: string, options?: any) => any Execute a script synchronously
executeAsync (key: string, options?: any) => Promise<any> Execute a script asynchronously
executeEndpoint (key: string, request: EndpointScriptRequest, options?: Record<string, unknown>) => Promise<unknown> Run a script in endpoint mode against a read-only facade of the instance

Script Context:

A script is a function body. Its first parameter is script_type, the mode it runs in; the others depend on the mode (context is always present as an alias of options):

Mode Called by Parameters
script execute dcupl, options
async_script executeAsync dcupl, options, resolve, reject
endpoint executeEndpoint dcupl (a ReadOnlyDcupl), request, options
sort a query with sort.script options, items
operator a query condition with a non-built-in operator query, item

An unknown key throws (or rejects with) a ScriptNotFoundError (name: 'ScriptNotFound'). See Scripts and script modes for the full contract and the script types.

With new Dcupl({ scripting: { enabled: false } }) no script runs: execute, executeAsync and executeEndpoint throw (or reject with) Error('Scripting is disabled for this dcupl instance'), and the loader skips script, operator and transformer resources, recording a ScriptingDisabled quality error for each. scripting.compile replaces the default new Function compiler (see DcuplInitScriptingConfig).

Examples:

// Register a synchronous script
dcupl.scripts.set(
  'calculateTotal',
  `
  const list = dcupl.lists.get('cart');
  const items = list.catalog.query.items();
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
`,
);

// Execute synchronously
const total = dcupl.scripts.execute('calculateTotal');

// Register an async script
dcupl.scripts.set(
  'fetchAndUpdate',
  `
  fetch('/api/products')
    .then(res => res.json())
    .then(async data => {
      dcupl.data.set(data, { model: 'Product' });
      await dcupl.update(); // apply the staged data
      resolve({ updated: data.length });
    })
    .catch(reject);
`,
);

// Execute asynchronously
const result = await dcupl.scripts.executeAsync('fetchAndUpdate');

// Pass options to script
dcupl.scripts.set(
  'filterByCategory',
  `
  const list = dcupl.lists.get('products');
  list.catalog.query.addCondition({
    attribute: 'category',
    operator: 'eq',
    value: options.category,
  });
  return list.catalog.query.items();
`,
);

const electronics = dcupl.scripts.execute('filterByCategory', { category: 'Electronics' });

// Run a script as an endpoint: `dcupl` is read-only, the return value is the response body
dcupl.scripts.set(
  'product-count',
  `
  if (script_type !== 'endpoint') throw new Error('unsupported script type: ' + script_type);
  const items = dcupl.query.execute({
    modelKey: 'Product',
    queries: [{ attribute: 'category', operator: 'eq', value: request.query.category }],
  });
  return { count: items.length };
`,
);

const body = await dcupl.scripts.executeEndpoint('product-count', {
  method: 'GET',
  path: 'product-count',
  params: {},
  query: { category: 'Electronics' },
  body: undefined,
  headers: {},
});

// List all scripts
const scriptKeys = dcupl.scripts.keys();

// Remove a script
dcupl.scripts.remove('filterByCategory');

loaders

Access to data loaders. Loaders handle fetching and processing configuration and data from external sources.

Type: object on the instance (no exported type)

Methods:

Method Signature Description
add (loader: DcuplAppLoader, key?: string) => void Register a loader (key auto-generated if omitted)
remove (key: string) => void Remove a loader and what it loaded: its models, views and rows go on the next update(); models and views defined in code keep their code definition (without the rows the loader loaded). Another loader can then load those keys
get (key: string) => DcuplAppLoader | undefined Get a loader by key

Example:

import { Dcupl } from '@dcupl/core';
import { DcuplAppLoader } from '@dcupl/loader';

const dcupl = new Dcupl({
  config: { projectId: 'my-project', apiKey: 'my-api-key' },
});

// Register the loader first: it takes projectId/apiKey from dcupl
const loader = new DcuplAppLoader();
dcupl.loaders.add(loader, 'main');

// Fetch the configuration, load the resources, then initialize
await loader.config.fetch();
await loader.process(); // loads every resource; see /sdk/api/loader#applications
await dcupl.init();

// Access loader later
const mainLoader = dcupl.loaders.get('main');

dcupl.init() does not process loaders: call loader.process() before it.

quality

Access to the data quality results. Quality checks run when data is applied (init() / update()) and are enabled by default.

Type: { errors: { summary(): ErrorGroupSummary[] } }

Methods:

Method Signature Description
errors.summary() () => ErrorGroupSummary[] Error totals per (model, attribute, error type): key, model, attribute, errorType, totalCount, storedCount, truncated, …

Individual error records are stored in the built-in model $DcuplErrorTrackingErrors (at most quality.maxErrorsPerGroup per group). Query it like any other model.

Example:

// Totals per (model, attribute, errorType)
for (const group of dcupl.quality.errors.summary()) {
  console.log(group.key, group.totalCount); // key: 'Model::attribute::ErrorType'
}

// Individual error records
const userErrors = dcupl.query.execute({
  modelKey: '$DcuplErrorTrackingErrors',
  queries: [{ attribute: 'model', operator: 'eq', value: 'User' }],
});

initOptions

The effective options: the options passed to the constructor, merged over the defaults.

Type: DcuplInitOptions

Example:

console.log(dcupl.initOptions.performance?.cache?.enabled);

version

The installed @dcupl/core version.

Type: string

sealed

true once the instance is sealed: after seal(), or after an init() with performance.retainStagedData: false built the models.

Type: boolean

Methods

init()

Initializes the dcupl instance and processes all registered models and data.

Signature:

async init(): Promise<boolean>

Returns: Promise<boolean> - true if initialization was successful, false otherwise

Examples:

Basic initialization
await dcupl.init();
With error handling
const success = await dcupl.init();
if (!success) {
  console.error('Initialization failed');
}
Complete workflow
const dcupl = new Dcupl();
dcupl.models.set({
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
  ],
});
dcupl.data.set(products, { model: 'Product' });
await dcupl.init();

update()

Applies all staged data changes (data.set / update / upsert / remove / reset / apply). Supports partial updates for better performance. Every affected list drops its caches and emits result_updated once; a query-scoped list also re-runs its seed query when it auto-updates (see Lists API → Auto-Update Pattern). With nothing staged, update() re-runs the full build.

Signature:

async update(options?: DcuplUpdateOptions): Promise<boolean>

Parameters:

Parameter Type Required Description
options DcuplUpdateOptions No Update configuration options

Returns: Promise<boolean> - true if update was successful, false otherwise

Examples:

Basic update
await dcupl.update();
Re-run every query-scoped list in this update
await dcupl.update({ autoUpdateLists: true });
Skip re-running query-scoped lists in this update
await dcupl.update({ autoUpdateLists: false });
Update workflow
dcupl.data.update([{ key: 'p1', name: 'Updated Product' }], { model: 'Product' });
await dcupl.update();
Batch updates
dcupl.data.update(batch1, { model: 'Product' });
dcupl.data.update(batch2, { model: 'Category' });
await dcupl.update(); // Processes both batches

destroy()

Destroys every list (dcupl.lists.destroy()) and every registered loader, then emits dcupl_destroyed. Models, data, scripts and on() subscriptions are kept, and the instance stays usable; drop your references to it to free its memory.

Signature:

destroy(): void

Examples:

Clean up resources
dcupl.destroy();
Complete lifecycle
const dcupl = new Dcupl();
await dcupl.init();
// ... use dcupl
dcupl.destroy();

seal()

Drops the staged raw rows that update() re-applies and a full rebuild starts from. After init() an instance holds its rows twice (the staged rows and the built models), so sealing frees the share of the heap the raw rows take: measured 15.6–21.8 % less retained heap after init() on a 135,880-row fixture (2026-10-15) and 2–24 % on customer catalogs of 1,000–44,000 rows (2026-10-16; most on wide JSON rows, nothing on a 1,000-row catalog). Use it for instances that load once and never update, such as a server-side worker; performance.retainStagedData: false does the same inside init() after a successful build.

After sealing, every read works as before: query, fn, lists, models.get, scripts and quality.errors. Every write throws DcuplSealedError (exported from @dcupl/common) that names the call, for example data.update is not available on a sealed dcupl instance, and changes nothing: data.set/update/upsert/remove/reset/apply, models.set/update/remove, loaders.remove, and a loader's process() (it rejects before fetching anything). init() and update() resolve false and log the error. destroy() keeps working.

seal() refuses, throwing DcuplSealedError('seal()', reason) and changing nothing, before init() has completed, while an init() or update() is in flight, while a loader resource is still pending, and while changes are staged but not applied (call update() first). With retainStagedData: false, init() resolves false and does not seal when a loader resource is still pending before the build (nothing is built) or when a write or resource arrives during the build (the build stands; call update(), then seal()). Await loader.process() before init().

DcuplSealedError carries what (the rejected call), reason (why a precondition refused) and message as readonly fields, toJSON() returns them with name, and instanceof DcuplSealedError holds across bundled copies of @dcupl/common (err.name === 'DcuplSealedError' works too).

Signature:

seal(): void

Examples:

Seal after init()
const dcupl = new Dcupl({ performance: { retainStagedData: false } });
await loader.process();
await dcupl.init(); // builds the models, then drops the staged rows
dcupl.sealed; // true
Seal on demand
import { DcuplSealedError } from '@dcupl/common';

await dcupl.init();
dcupl.seal();
try {
  dcupl.data.update([{ key: 'p1', price: 9 }], { model: 'Product' });
} catch (err) {
  if (err instanceof DcuplSealedError) console.log(err.what, err.message);
}

on()

Subscribes to change events from the dcupl instance.

Signature:

on(callback: CallbackFunction): () => void

Parameters:

Parameter Type Required Description
callback CallbackFunction Yes Function to call on changes

Returns: () => void - Unsubscribe function

Examples:

Subscribe to all changes
const unsubscribe = dcupl.on((message) => {
  console.log('Change detected:', message);
});
Unsubscribe when done
unsubscribe();
React to specific change types
dcupl.on((message) => {
  if (message.type === 'dcupl_updated_manually') {
    console.log('Data was updated');
  }
  if (message.type === 'dcupl_partial_update') {
    console.log('Changed models:', message.args[0].changedModels);
  }
});

dcupl_partial_update fires after every update() that applied staged changes or a model definition change (models.set/update/remove after init()), also when the SDK fell back to a full rebuild. args[0] is { isPartialUpdate, changedModels, changedAttributes, affectedModels }; isPartialUpdate is true for a partial update and false for a full rebuild. dcupl_updated_manually fires after every update(), with or without changes.

Multiple subscribers
const unsub1 = dcupl.on(callback1);
const unsub2 = dcupl.on(callback2);

// Cleanup all
unsub1();
unsub2();

Configuration types

DcuplInitOptions

Configuration options for initializing a dcupl instance.

Type Definition:

type DcuplInitOptions = {
  config?: DcuplInitConfig;
  quality?: DcuplInitQualityConfig;
  performance?: DcuplInitPerformanceConfig;
  analytics?: DcuplInitErrorAnalyticsConfig;
  referenceMetadata?: DcuplInitReferenceMetadataConfig;
  logging?: DcuplInitLoggingConfig;
  scripting?: DcuplInitScriptingConfig;
  /** @deprecated use `quality` */
  errorTracking?: { enabled: boolean };
};

Properties:

Property Type Description
config DcuplInitConfig Project configuration (projectId, apiKey, version)
quality DcuplInitQualityConfig Data quality validation settings
performance DcuplInitPerformanceConfig Performance optimization settings
analytics DcuplInitErrorAnalyticsConfig { enabled } (default false). Leave it off: it gives you no output you can read. For timings, read the duration of events (Timings from Events)
referenceMetadata DcuplInitReferenceMetadataConfig { enabled } (default false): reference values in results carry _dcupl_ref_: 'hit' or 'miss', and unresolved references stay in projected results
logging DcuplInitLoggingConfig Logging configuration (typed, but not applied to the SDK core's logs, see below)
scripting DcuplInitScriptingConfig enabled (default true) and compile — see Types
errorTracking { enabled: boolean } Deprecated: enabled: true turns quality checks on even when quality.enabled is false

DcuplInitConfig

Project configuration settings.

Type Definition:

type DcuplInitConfig = {
  projectId?: string;
  apiKey?: string;
  version?: string; // default 'draft'
};

The core SDK makes no requests itself: dcupl.loaders.add() passes projectId, apiKey and version to each loader as default variables.

Example:

const dcupl = new Dcupl({
  config: {
    projectId: 'my-project',
    apiKey: 'readonly-key',
    version: 'v1.0.0',
  },
});

DcuplInitQualityConfig

Data quality validation configuration.

Type Definition:

type DcuplInitQualityConfig = {
  enabled: boolean; // default true
  maxErrorsPerGroup?: number; // default 1000
};

maxErrorsPerGroup caps the error records stored per (model, attribute, error type) group; dcupl.quality.errors.summary() keeps counting past the cap (truncated: true). Failed type coercions are the exception: they are consolidated into one WrongDataType record per attribute, and when more values of one attribute fail than maxErrorsPerGroup, those counts aren't exact (see Limiting Stored Errors).

Example:

const dcupl = new Dcupl({
  quality: {
    enabled: true,
  },
});

DcuplInitPerformanceConfig

Performance optimization settings.

Type Definition:

type DcuplInitPerformanceConfig = {
  cache?: {
    enabled: boolean;
    maxSize?: number; // estimated bytes, default 64 MiB; Infinity = no limit
    maxProjectionSize?: number; // estimated bytes per list, default 256 MiB; Infinity = no limit
  };
  clone?: {
    enabled: boolean;
  };
  partialUpdates?: DcuplPartialUpdateConfig;
  containerBatchSize?: number;
  autoUpdateLists?: {
    enabled: boolean;
  };
  retainStagedData?: boolean; // default true; false seals the instance after init()
};

Properties:

Property Type Description
cache.enabled boolean Per-list caches for query results, facets and aggregations (default true; see Performance)
cache.maxSize number Bound, in estimated bytes (default 64 MiB, Infinity for no limit), for each list's result cache and for the indices dcupl builds per model (or per scoped list). Least recently used entries are dropped and rebuilt on their next use; results are unchanged. The index bound also applies with the cache disabled
cache.maxProjectionSize number Bound, in estimated bytes per list (default 256 MiB, Infinity for no limit), for the projected copies that deep facets and deep filters build (a dotted path in a facet attribute, a list's query or an fn.* query), one per distinct set of dotted attributes. Least recently used copies are dropped and projected again on their next use; a copy larger than the bound is served but not kept. Results are unchanged.
clone.enabled boolean Return deep clones from query and fn calls (default true); false returns live items, which you must not mutate
partialUpdates DcuplPartialUpdateConfig Partial update optimization settings
containerBatchSize number Batch processing size (undefined = async, 0 = sync, N = batch)
autoUpdateLists.enabled boolean Re-run the seed query of query-scoped lists on dcupl.update() (default false); a listItemKeys list is the same either way. Caches of affected lists are dropped either way
retainStagedData boolean false makes init() drop the staged raw rows after a successful build, as seal() does (default true). Frees 16–22 % of the retained heap on a 135,880-row fixture (2–24 % on customer catalogs) for instances that load once and never update; afterwards every write throws DcuplSealedError and init()/update() resolve false, reads are unchanged

Examples:

Enable caching
const dcupl = new Dcupl({
  performance: {
    cache: { enabled: true },
  },
});
Optimize partial updates
const dcupl = new Dcupl({
  performance: {
    partialUpdates: {
      modelRatioThreshold: 0.5,
      itemRatioThreshold: 0.5,
      enableDetailedLogging: true,
    },
  },
});
Auto-update query-scoped lists
const dcupl = new Dcupl({
  performance: {
    autoUpdateLists: { enabled: true },
  },
});

DcuplPartialUpdateConfig

Partial update optimization configuration.

Type Definition:

type DcuplPartialUpdateConfig = {
  modelRatioThreshold?: number;
  itemRatioThreshold?: number;
  enableDetailedLogging?: boolean;
};

Properties:

Property Type Default Description
modelRatioThreshold number 0.5 Share of models changed (0-1); see below
itemRatioThreshold number 0.5 Share of items changed in those models (0-1); see below
enableDetailedLogging boolean false Detailed update logging; prints nothing (below)

update() takes the partial path unless both ratios exceed their thresholds; then it rebuilds everything. Whatever the ratios, three things always trigger a full rebuild: a staged data.set() or data.reset(), a definition change after init() (models.set(), models.update() or models.remove()), and a staged change that can alter a query reference's result: a change to the model that defines it, to its target model, or to a model the target reaches through its references. With nothing staged, update() also re-runs the full build (see update()), without a dcupl_partial_update event. The dcupl_partial_update event reports the path taken in isPartialUpdate; after a definition change it fires even when no rows changed, and names the redefined model in changedModels.

enableDetailedLogging currently prints nothing: its messages are debug-level, and the core SDK's log level can't be changed, not even with logging: { level: 'debug' } (see DcuplInitLoggingConfig).

cacheInvalidationThreshold was removed in SDK 2.0: a typed config that still passes it is a TypeScript error, and at runtime the key is ignored. To bound cache memory, use performance.cache.maxSize, which bounds each list's result cache and the indices dcupl builds per model.

DcuplUpdateOptions

Options for the update() method. The type is not exported; use Parameters<Dcupl['update']>[0] if you need it.

Type Definition:

type DcuplUpdateOptions = {
  autoUpdateLists?: boolean;
};

Properties:

Property Type Description
autoUpdateLists boolean Override auto-update for this update: whether query-scoped lists re-run their seed query. Overrides list and global settings

Affected lists drop their caches either way, so false does not hide the new data from unscoped lists.

Example:

Re-run every query-scoped list
await dcupl.update({ autoUpdateLists: true });
Keep query-scoped lists' item sets
await dcupl.update({ autoUpdateLists: false });

DcuplInitLoggingConfig

Logging configuration settings.

Type Definition:

type DcuplInitLoggingConfig = {
  level?: LogLevel;
  format?: 'json' | 'pretty';
  onLog?: (entry: LogEntry) => void;
  modules?: Record<string, LogLevel>;
};

Properties:

Property Type Description
level LogLevel Minimum log level ('silent', 'error', 'warn', 'info', 'debug')
format 'json' | 'pretty' Output format
onLog function Custom log handler
modules Record<string, LogLevel> Module-specific log levels
Watch out

The logging option is typed but not wired: it doesn't change the level, format or output of the SDK's logs, and onLog is never called (only level: 'debug' adds a [Filter] Caches invalidated console message after updates). The core SDK's logging can't be configured at all: it writes errors and warnings to the console, and logger.configure() from @dcupl/common doesn't change that. logger.configure() only configures the logs of @dcupl/loader; see Error Handling → Logging Configuration.

Complete examples

Basic setup

import { Dcupl } from '@dcupl/core';

// Create instance
const dcupl = new Dcupl();

// Define model
dcupl.models.set({
  key: 'Product',
  properties: [
    { key: 'name', type: 'string' },
    { key: 'price', type: 'float' },
    { key: 'status', type: 'string' },
  ],
});

// Add data
dcupl.data.set(
  [
    { key: 'p1', name: 'Product 1', price: 100, status: 'active' },
    { key: 'p2', name: 'Product 2', price: 200, status: 'active' },
  ],
  { model: 'Product' },
);

// Initialize
await dcupl.init();

// Query data
const products = dcupl.query.execute({
  modelKey: 'Product',
  queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
});

Advanced configuration

const dcupl = new Dcupl({
  config: {
    projectId: 'my-project',
    apiKey: 'my-api-key',
    version: 'v1.0.0',
  },
  quality: {
    enabled: true,
  },
  performance: {
    cache: {
      enabled: true,
    },
    clone: {
      enabled: true,
    },
    partialUpdates: {
      modelRatioThreshold: 0.5,
      itemRatioThreshold: 0.5,
      enableDetailedLogging: false,
    },
    autoUpdateLists: {
      enabled: true,
    },
  },
});

Update lifecycle

// Initial setup
await dcupl.init();

// Update single item
dcupl.data.update([{ key: 'p1', price: 150 }], { model: 'Product' });
await dcupl.update();

// Batch updates
dcupl.data.update(batch1, { model: 'Product' });
dcupl.data.update(batch2, { model: 'Category' });
await dcupl.update();

// Re-run query-scoped lists in this update
await dcupl.update({ autoUpdateLists: true });

Event handling

Subscribe to changes
const unsubscribe = dcupl.on((message) => {
  console.log('Event type:', message.type);
  console.log('Scope:', message.scope);

  if (message.type === 'dcupl_partial_update') {
    const changeInfo = message.args[0];
    console.log('Changed models:', changeInfo.changedModels);
    console.log('Affected models:', changeInfo.affectedModels);
  }
});
Cleanup
unsubscribe();

Multiple instances

Each instance has its own models, data, lists and queries.

Instance for products
const productsCore = new Dcupl();
productsCore.models.set(productModel);
await productsCore.init();
Instance for orders
const ordersCore = new Dcupl();
ordersCore.models.set(orderModel);
await ordersCore.init();

See also