.md

Lists API

The Lists API (dcupl.lists) provides methods for creating and managing isolated data collections with their own filters, queries, and state management.

Overview

Lists are isolated views of your data that maintain their own query state and provide access to the catalog API for querying, filtering, and analytics. Each list operates independently and can have different filters applied to the same underlying data model.

Methods

create()

Creates a new list instance.

Signature:

create<T extends RawItem = RawItem>(options: {
  modelKey: string;
  listKey?: string;
  listItemKeys?: string[];
  query?: DcuplGlobalQueryOptions<ListItem>;
}): DcuplList<T>

Parameters:

Parameter Type Required Description
options DcuplListOptions Yes List configuration options

Returns: DcuplList<T> - The created list instance

create<Product>() returns a DcuplList<Product> (SDK 2.0): items(), first(), execute(), any() and count() keep that item type whatever options you pass (items({ sort }) no longer narrows it to the sorted fields). A type argument on a read must extend the list's item type: items<ProductWithStock>() adds fields, it cannot drop them (items<Pick<Product, 'key' | 'name'>>() is a type error). For a projected shape, create the list without a type argument (or with create<Partial<Product>>()) and type the read, items<{ key: string; name: string }>({ projection: { name: true } }), or cast the result. one() and many() take no type argument and return the list's item type. The published list query types use NoInfer, so consumers need TypeScript 5.4 or later.

Throws: Model with key <modelKey> not found when the model does not exist, which includes calling create() before dcupl.init() has run. An existing listKey is not reused: create() adds a second list with the same key.

A list created with listItemKeys or query is scoped: it keeps a set of keys (the listItemKeys you pass, or the keys the query matches when the list is created), and its own catalog query starts empty (clear() does not remove the scope). It is not an initial filter. The list always serves the model's current items for those keys: after dcupl.update() changed values show, removed items drop out, and an item added under one of the keys is served. For a listItemKeys list that is all there is, so auto-update changes only the events, not the items. Items that start matching a seed query join only when you call update() or enable auto-update.

Examples:

Create list for all products
const productList = dcupl.lists.create({
  modelKey: 'Product',
});
Create list with custom key
const activeProducts = dcupl.lists.create({
  modelKey: 'Product',
  listKey: 'activeProducts',
});
Create a list scoped to specific items
const selectedProducts = dcupl.lists.create({
  modelKey: 'Product',
  listItemKeys: ['p1', 'p2', 'p3'],
});
Create a list scoped to a query
const expensiveProducts = dcupl.lists.create({
  modelKey: 'Product',
  query: {
    modelKey: 'Product',
    queries: [{ operator: 'gte', attribute: 'price', value: 100 }],
  },
});
Create a scoped list with auto-update enabled
import type { DcuplListOptions } from '@dcupl/common';

// autoUpdate is missing from the create() parameter type,
// so pass the options as a DcuplListOptions variable
const options: DcuplListOptions = {
  modelKey: 'Product',
  query: {
    modelKey: 'Product',
    queries: [{ operator: 'gte', attribute: 'price', value: 100 }],
  },
  autoUpdate: true,
};
const list = dcupl.lists.create(options);

get()

Retrieves a list by its key.

Signature:

get(listKey: string): DcuplList | undefined

Parameters:

Parameter Type Required Description
listKey string Yes The key of the list to retrieve

Returns: DcuplList | undefined - The list instance or undefined if not found

Examples:

Get an existing list
const list = dcupl.lists.get('activeProducts');
if (list) {
  const items = list.catalog.query.items();
}
Check if list exists
const exists = dcupl.lists.get('myList') !== undefined;
Safe access
const list = dcupl.lists.get('myList');
if (list) {
  // Use list
} else {
  // Create list
  const newList = dcupl.lists.create({
    modelKey: 'Product',
    listKey: 'myList',
  });
}

getAll()

Retrieves all lists or filters by model.

Signature:

getAll(filter?: { modelKey: string }): DcuplList[]

Parameters:

Parameter Type Required Description
filter { modelKey: string } No Optional filter by model key

Returns: DcuplList[] - Array of list instances

The result also contains the internal __global_list__<modelKey> lists that dcupl.fn.*, dcupl.query.execute() and create() with a query set up. Skip keys starting with __global_list__ when you only want your own lists.

Examples:

Get all lists
const allLists = dcupl.lists.getAll();
console.log(allLists.length);
Get lists for specific model
const productLists = dcupl.lists.getAll({ modelKey: 'Product' });
Update all lists
dcupl.lists.getAll().forEach((list) => {
  list.update();
});
Count lists per model
const listsByModel: Record<string, number> = {};
dcupl.lists.getAll().forEach((list) => {
  listsByModel[list.modelKey] = (listsByModel[list.modelKey] || 0) + 1;
});

destroy()

Destroys lists and cleans up resources.

Signature:

destroy(options?: { listKey?: string; modelKey?: string }): void

Parameters:

Parameter Type Required Description
options object No Destroy options (all lists if omitted)
options.listKey string No Specific list key to destroy
options.modelKey string No Destroy all lists for a model

Examples:

Destroy specific list
dcupl.lists.destroy({ listKey: 'activeProducts' });
Destroy all lists for a model
dcupl.lists.destroy({ modelKey: 'Product' });
Destroy all lists
dcupl.lists.destroy();
Safe destroy with check
const list = dcupl.lists.get('myList');
if (list) {
  dcupl.lists.destroy({ listKey: 'myList' });
}

DcuplList class

The DcuplList class represents an individual list instance with its own state and catalog API.

Properties

key

The unique identifier for this list.

Type: string

Example:

const list = dcupl.lists.create({
  modelKey: 'Product',
  listKey: 'myList',
});
console.log(list.key); // 'myList'

modelKey

The model this list is based on.

Type: string

Example:

console.log(list.modelKey); // 'Product'

catalog

Access to the catalog API for querying and analytics.

Type: DcuplList<T>['catalog'], an instance of the Catalog class typed with the list's item type. Catalog is not exported from the @dcupl/core main entry; import it from @dcupl/core/catalog if you need the class itself.

See: Catalog API Reference

Example:

const items = list.catalog.query.items();
const facets = list.catalog.fn.facets({ attribute: 'category' });

Methods

update()

Re-derives the list from the current data: a query-scoped list re-runs its seed query (so new matches join and items that stop matching leave), and the list's caches are rebuilt. A listItemKeys list re-applies its keys, which gives the items it already serves after dcupl.update(). Emits list_updated (not result_updated).

Signature:

update(options?: { reprocessData?: boolean }): void

Parameters:

Parameter Type Required Description
options object No Update options
options.reprocessData boolean No update() and update({ reprocessData: true }) re-derive the list; an options object without it skips that (list_updated is still emitted)

Examples:

Basic update
list.update();
Re-derive a query-scoped list after a data change
dcupl.data.update([{ key: 'p1', price: 150 }], { model: 'Product' });
await dcupl.update();
list.update(); // Only needed to re-run the seed query of a query-scoped list
Note

await dcupl.update() already drops the query caches of every affected list, so an unscoped list returns the new data without any extra call, and so does a listItemKeys list. Use update() when a list was created with a query scope and you want that query re-run on the current data.

Note

A scoped list that does not auto-update keeps its key set but serves current values: after a partial update as well as after a full rebuild (data.set(), data.reset(), or an update above the partial-update thresholds) its queries and functions run on the model's current items, and removed items are gone. Only new items that would match the seed query need list.update() or autoUpdate: true to join; a listItemKeys list also serves an item added later under one of its keys.

Auto-update workflow
const list = dcupl.lists.create(options); // a query-scoped list with autoUpdate: true (see create())
dcupl.data.upsert([{ key: 'p9', name: 'New Product', price: 120 }], { model: 'Product' });
await dcupl.update(); // re-derives the list: p9 joins it without a list.update() call

invalidate()

Drops the list's query and result caches. The filtered data is recomputed lazily on the next read, from the list's current data — without re-deriving a scoped list's item set (that is what update() does).

dcupl.update() calls this automatically for every affected list that does not auto-update, so a query executed before the update can never keep serving its pre-update result. Call it yourself only when you mutate list data outside the normal dcupl.update() flow. It keeps a scoped list's key set, so it does not replace update() when new matches of a seed query should join the list.

Signature:

invalidate(): void

Examples:

Drop cached query results
list.invalidate();
list.catalog.query.execute(); // recomputed from current data

destroy()

Destroys the list and cleans up resources.

Signature:

destroy(): void

Examples:

Destroy a list
const list = dcupl.lists.create({ modelKey: 'Product' });
list.destroy();
Cleanup in component
import type { DcuplList } from '@dcupl/core';

class MyComponent {
  private list: DcuplList;

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

  destroy() {
    this.list.destroy();
  }
}

on()

Subscribes to every event of the list, including reads (query_execute, fn_facets, …), plus the instance's dcupl_updated_manually after each dcupl.update(). To re-render when the list's result changes, use onResultChange(). See the Events reference for all event types.

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 list changes
const unsubscribe = list.on((message) => {
  console.log('List changed:', message);
});
Unsubscribe
unsubscribe();
React to specific events
list.on((message) => {
  if (message.type === 'result_updated') {
    console.log('Result changed');
  }
  if (message.type === 'query_apply') {
    console.log('Query was applied');
  }
});
Multiple subscribers
const unsub1 = list.on(callback1);
const unsub2 = list.on(callback2);

// Cleanup
unsub1();
unsub2();

onResultChange()

Subscribes to changes of the list's result: list.on() filtered to result_updated. The callback fires when a query change recomputes the result (addCondition, removeGroup, clear, applyOptions, …; not with { skipProcessing: true }) and once per dcupl.update() that can have changed the list. It never fires for reads (items(), execute(), fn.facets(), …) or for a manual list.update(). This is the recommended hook for re-rendering.

Signature:

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

Returns: () => void - Unsubscribe function

Examples:

Re-render when the result changes
const unsubscribe = list.onResultChange(() => {
  renderProducts(list.catalog.query.items());
});

list.catalog.query.addCondition({ operator: 'eq', attribute: 'status', value: 'active' }); // fires once

dcupl.data.upsert([{ key: 'p5', name: 'Chair', price: 80, status: 'active' }], { model: 'Product' });
await dcupl.update(); // fires once

// Later: cleanup
unsubscribe();

Configuration types

DcuplListOptions

Configuration options for creating a list.

Type Definition:

type DcuplListOptions = {
  modelKey: string;
  listKey?: string;
  listItemKeys?: string[];
  query?: DcuplGlobalQueryOptions<ListItem>;
  autoUpdate?: boolean;
};

Properties:

Property Type Required Description
modelKey string Yes Model this list is based on
listKey string No Unique list identifier (auto-generated if omitted)
listItemKeys string[] No Scopes the list to these item keys (the key set is kept across updates; current values are served, and an item added later under one of the keys joins)
query DcuplGlobalQueryOptions<ListItem> No Seed query: scopes the list to the items matching it at creation (key set kept across updates, not an initial filter)
autoUpdate boolean No Re-run a seed query on every dcupl.update() (new matches join, items that stop matching leave); a listItemKeys list serves the same items either way, with it the list also emits list_updated on every update that affects it. Default: performance.autoUpdateLists.enabled (false)
Watch out

autoUpdate works at runtime but is missing from the dcupl.lists.create() parameter type, so TypeScript rejects it in an object literal passed to create(). Declare the options as DcuplListOptions (from @dcupl/common) and pass the variable, as in the examples below.

Examples:

Minimal configuration
const options: DcuplListOptions = { modelKey: 'Product' };
With custom key
const options: DcuplListOptions = {
  modelKey: 'Product',
  listKey: 'featuredProducts',
};
Scoped to specific items
const options: DcuplListOptions = {
  modelKey: 'Product',
  listItemKeys: ['p1', 'p2', 'p3'],
};
Scoped to a query
const options: DcuplListOptions = {
  modelKey: 'Product',
  query: {
    modelKey: 'Product',
    queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
  },
};
Scoped to a query, with auto-update
const options: DcuplListOptions = {
  modelKey: 'Product',
  listKey: 'activeProducts',
  query: {
    modelKey: 'Product',
    queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
  },
  autoUpdate: true,
};

Auto-update pattern

Auto-update matters only for a query-scoped list: with it, every dcupl.update() that affects the list re-runs the seed query, so a new item that matches joins the list and one that stops matching leaves; without it, the list keeps the keys the query matched at creation (or at the last list.update()). A listItemKeys list is the same with or without it: it always holds the current items for exactly its keys. Unscoped lists are not affected either: every dcupl.update() drops the caches of every affected list, so their queries always run against the current data.

Priority: dcupl.update({ autoUpdateLists }) > the list's autoUpdate > performance.autoUpdateLists.enabled (default false). Pass autoUpdate through a DcuplListOptions variable, as in the last example above; Real-time updates walks through the update flow end to end.

More patterns

A list for a fixed subset takes the query or listItemKeys option (see DcuplListOptions); for a filter that always follows the current data, call catalog.query.addCondition() on an unscoped list instead (Catalog API). Several filter groups on one list are covered in Query logic. These patterns have their own guides:

See also