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

```typescript
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:**

```typescript title="Create a basic instance"
const dcupl = new Dcupl();
```

```typescript title="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
```

```typescript title="Create with configuration"
const dcupl = new Dcupl({
  config: {
    projectId: 'your-project-id',
    apiKey: 'your-api-key',
    version: 'draft',
  },
});
```

```typescript title="Configure quality checks"
const dcupl = new Dcupl({
  quality: {
    enabled: true, // default
    maxErrorsPerGroup: 1000, // default
  },
});
```

```typescript title="Configure performance caching"
const dcupl = new Dcupl({
  performance: {
    cache: {
      enabled: true, // default
    },
  },
});
```

```typescript title="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](/sdk/api/models)

**Example:**

```typescript
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](/sdk/api/lists)

**Example:**

```typescript
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](/sdk/api/data)

**Example:**

```typescript
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](/sdk/guides/scripts) instead, which takes precedence |

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

**Example:**

```typescript
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](/sdk/api/functions).

**Example:**

```typescript
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:**

```typescript
// 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:**

```typescript
// 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](/sdk/guides/scripts) for the full contract and the [script types](/sdk/api/types#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](/sdk/api/types#dcuplinitscriptingconfig)).

**Examples:**

```typescript
// 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:**

```typescript
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:**

```typescript
// 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:**

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

### version

The installed `@dcupl/core` version.

**Type:** `string`

### sealed

`true` once the instance is sealed: after [`seal()`](#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:**

```typescript
async init(): Promise<boolean>
```

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

**Examples:**

```typescript title="Basic initialization"
await dcupl.init();
```

```typescript title="With error handling"
const success = await dcupl.init();
if (!success) {
  console.error('Initialization failed');
}
```

```typescript title="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](/sdk/api/lists#auto-update-pattern)). With nothing staged,
`update()` re-runs the full build.

**Signature:**

```typescript
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:**

```typescript title="Basic update"
await dcupl.update();
```

```typescript title="Re-run every query-scoped list in this update"
await dcupl.update({ autoUpdateLists: true });
```

```typescript title="Skip re-running query-scoped lists in this update"
await dcupl.update({ autoUpdateLists: false });
```

```typescript title="Update workflow"
dcupl.data.update([{ key: 'p1', name: 'Updated Product' }], { model: 'Product' });
await dcupl.update();
```

```typescript title="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:**

```typescript
destroy(): void
```

**Examples:**

```typescript title="Clean up resources"
dcupl.destroy();
```

```typescript title="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:**

```typescript
seal(): void
```

**Examples:**

```typescript title="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
```

```typescript title="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:**

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

**Parameters:**

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

**Returns:** `() => void` - Unsubscribe function

**Examples:**

```typescript title="Subscribe to all changes"
const unsubscribe = dcupl.on((message) => {
  console.log('Change detected:', message);
});
```

```typescript title="Unsubscribe when done"
unsubscribe();
```

```typescript title="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.

```typescript title="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:**

```typescript
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](/sdk/advanced/performance#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](/sdk/api/types#dcuplinitscriptingconfig)                                                                                                       |
| `errorTracking`     | `{ enabled: boolean }`             | Deprecated: `enabled: true` turns quality checks on even when `quality.enabled` is `false`                                                                                                            |

### DcuplInitConfig

Project configuration settings.

**Type Definition:**

```typescript
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:**

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

### DcuplInitQualityConfig

Data quality validation configuration.

**Type Definition:**

```typescript
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](/sdk/advanced/quality-validation#limiting-stored-errors)).

**Example:**

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

### DcuplInitPerformanceConfig

Performance optimization settings.

**Type Definition:**

```typescript
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](/sdk/advanced/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()`](#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:**

```typescript title="Enable caching"
const dcupl = new Dcupl({
  performance: {
    cache: { enabled: true },
  },
});
```

```typescript title="Optimize partial updates"
const dcupl = new Dcupl({
  performance: {
    partialUpdates: {
      modelRatioThreshold: 0.5,
      itemRatioThreshold: 0.5,
      enableDetailedLogging: true,
    },
  },
});
```

```typescript title="Auto-update query-scoped lists"
const dcupl = new Dcupl({
  performance: {
    autoUpdateLists: { enabled: true },
  },
});
```

### DcuplPartialUpdateConfig

Partial update optimization configuration.

**Type Definition:**

```typescript
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](/sdk/api/models#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()`](#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](#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`](#dcuplinitperformanceconfig), 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:**

```typescript
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:**

```typescript title="Re-run every query-scoped list"
await dcupl.update({ autoUpdateLists: true });
```

```typescript title="Keep query-scoped lists' item sets"
await dcupl.update({ autoUpdateLists: false });
```

### DcuplInitLoggingConfig

Logging configuration settings.

**Type Definition:**

```typescript
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](/sdk/guides/error-handling#logging-configuration).

## Complete examples

### Basic setup

```typescript
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

```typescript
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

```typescript
// 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

```typescript title="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);
  }
});
```

```typescript title="Cleanup"
unsubscribe();
```

### Multiple instances

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

```typescript title="Instance for products"
const productsCore = new Dcupl();
productsCore.models.set(productModel);
await productsCore.init();
```

```typescript title="Instance for orders"
const ordersCore = new Dcupl();
ordersCore.models.set(orderModel);
await ordersCore.init();
```

## See also

- [Models API Reference](/sdk/api/models)
- [Lists API Reference](/sdk/api/lists)
- [Data API Reference](/sdk/api/data)
- [Catalog API Reference](/sdk/api/catalog)
- [Quick start](/sdk/quick-start)
- [Setup options](/sdk/setup-options)
