.md

Common mistakes

This guide helps you identify and fix the most common mistakes beginners make with dcupl. Each section shows the wrong approach, explains why it fails, and provides the correct solution.

Forgetting to await init()

The most common mistake is querying data before dcupl has finished initializing.

The problem

Wrong: Querying before init completes
const dcupl = new Dcupl();
dcupl.models.set({ key: 'Product', properties: [...] });
dcupl.data.set([...], { model: 'Product' });

dcupl.init(); // Missing await!

const list = dcupl.lists.create({ modelKey: 'Product' });
const results = list.catalog.query.items();
// Not guaranteed to see the data: init() may not have finished

Why it fails: init() is async and returns a Promise<boolean>. Without await, your code doesn't wait for initialization to finish, so results can be empty or incomplete — and you never learn whether it worked: init() does not throw. It catches its own errors, logs "Initialization failed" and resolves false.

The fix

Correct: Always await init()
const dcupl = new Dcupl();
dcupl.models.set({ key: 'Product', properties: [...] });
dcupl.data.set([...], { model: 'Product' });

const ready = await dcupl.init(); // Wait for initialization
if (!ready) {
  throw new Error('dcupl initialization failed'); // init() resolves false instead of throwing
}

const list = dcupl.lists.create({ modelKey: 'Product' });
const results = list.catalog.query.items();
// Results: [{ key: 'p1', ... }, { key: 'p2', ... }]

init() does not run or wait for loaders. With a loader, await loader.process() first, then dcupl.init():

Correct: Load, then initialize
await loader.config.fetch();
await loader.process(); // fetches models and data
await dcupl.init(); // processes what the loader added
Note

Tip: The same applies to dcupl.update() after modifying data. Always await it — like init(), it resolves false instead of throwing when it fails.

Error: Model with key X not found

A list or query for a model key that isn't registered throws. It does not return [].

Wrong: Typo in model key
dcupl.models.set({ key: 'Product', properties: [...] });
await dcupl.init();

const list = dcupl.lists.create({ modelKey: 'Products' }); // Typo: 'Products' vs 'Product'
// Throws: Error: Model with key Products not found
Correct: Use a registered model key
console.log('Available models:', dcupl.models.keys()); // ['Product']

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

With a loader, loader.process() only stages the models; dcupl.init() registers them. Create lists after await dcupl.init().

Error: Data must be an Array

The dcupl.data.* calls take an array of items, also for a single item.

Wrong: A single item
dcupl.data.update({ key: 'p1', price: 199 }, { model: 'Product' });
// Throws: Error: Invalid Data - Data must be an Array
Correct: Wrap it in an array
dcupl.data.update([{ key: 'p1', price: 199 }], { model: 'Product' });

Options without model, or a null or undefined payload, throw Invalid Data Container. In TypeScript, a single item and options without model are compile errors, and so is a null or undefined payload with the strict option (strictNullChecks).

Query returns no results

When your query returns an empty array unexpectedly, check these common causes.

Data for an unknown model

Data staged for a model key that doesn't exist loads into no model and raises no error: the rows stay staged under that key and load once a model with that key is registered. init() still resolves true, dcupl.quality.errors.summary() stays empty, and the model you meant has no items. The only sign is a console line during init() or a full rebuild, [ModelParser] Model not found during data processing. Data staged after init() with data.upsert(), update() or remove() and applied as a partial update doesn't even log it; the dcupl_partial_update event lists the unknown key in changedModels.

Wrong: Typo in the data's model key
dcupl.models.set({ key: 'Product', properties: [{ key: 'name', type: 'string' }] });
dcupl.data.set([{ key: 'p1', name: 'Laptop' }], { model: 'Prodcut' }); // Typo
await dcupl.init(); // true

dcupl.lists.create({ modelKey: 'Product' }).catalog.query.count(); // 0
Correct: Stage the data for a registered model
console.log('Available models:', dcupl.models.keys()); // ['Product']

dcupl.data.set([{ key: 'p1', name: 'Laptop' }], { model: 'Product' });

In a loader configuration, a typo in a data resource's model logs nothing by default. Missing-model handling is on unless the configuration's options.missingModelHandling, or the options you pass to loader.process() (they replace the configuration's), turns it off (see ProcessOptions). The loader creates a second model under the misspelled key, infers its properties from the rows and loads them there, so the model you meant stays empty. Check dcupl.models.keys() for a key you didn't expect. With missingModelHandling.autoGenerateProperties: false, or missingModelHandling: {}, which also turns it off, the rows load into no model and the console shows the same Model not found line. A CSV resource's rows are then not parsed at all, so they don't load later either.

Wrong attribute name

Wrong: Attribute name doesn't match model
dcupl.models.set({
  key: 'Product',
  properties: [
    { key: 'productName', type: 'string' }, // Property is 'productName'
  ],
});

list.catalog.query.addCondition({
  attribute: 'name', // Wrong: should be 'productName'
  operator: 'eq',
  value: 'Laptop',
});
const results = list.catalog.query.items();
// Results: [] (no match because attribute doesn't exist)

Debug tip: Inspect the model definition:

const model = dcupl.models.get('Product');
console.log(
  'Properties:',
  model?.properties?.map((p) => p.key),
);

Data type mismatch

Wrong: String vs number comparison
dcupl.data.set(
  [
    { key: 'p1', price: 100 }, // price is a number
  ],
  { model: 'Product' },
);

list.catalog.query.addCondition({
  attribute: 'price',
  operator: 'eq',
  value: '100', // Wrong: string instead of number
});
// Results: [] (no match - types don't match)
Correct: Match the data type
list.catalog.query.addCondition({
  attribute: 'price',
  operator: 'eq',
  value: 100, // Correct: number type
});

Forgetting to call items()

The query builder is fluent - it returns the query API, not results.

The problem

Wrong: Expecting results from addCondition
list.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

const results = list.catalog.query; // Wrong: This is the query API object, not results
console.log(results.length); // undefined

The fix

Correct: Call items() to get results
list.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

const results = list.catalog.query.items(); // Call items() to execute
console.log(results.length); // 2

Related methods that return data:

list.catalog.query.items(); // All matching items
list.catalog.query.first(); // First matching item
list.catalog.query.count(); // Number of matches (the page length once start/count are applied)

Not cleaning up lists

Lists consume memory and event listeners. Forgetting to destroy them causes memory leaks.

The problem

Wrong: Lists accumulate without cleanup
function showProducts(category: string) {
  const list = dcupl.lists.create({ modelKey: 'Product' });
  list.catalog.query.addCondition({ attribute: 'category', operator: 'eq', value: category });
  return list.catalog.query.items();
  // List is never destroyed - memory leak!
}

// Each call creates a new list that's never cleaned up
showProducts('Electronics');
showProducts('Furniture');
showProducts('Clothing');

The fix

Option 1: Destroy after use

Correct: Destroy list when done
function showProducts(category: string) {
  const list = dcupl.lists.create({ modelKey: 'Product' });
  list.catalog.query.addCondition({ attribute: 'category', operator: 'eq', value: category });
  const results = list.catalog.query.items();
  list.destroy(); // Clean up
  return results;
}

Option 2: Reuse a single list

Better: Reuse a single list
const productList = dcupl.lists.create({ modelKey: 'Product' });

function showProducts(category: string) {
  productList.catalog.query.clear(); // Reset filters
  productList.catalog.query.addCondition({
    attribute: 'category',
    operator: 'eq',
    value: category,
  });
  return productList.catalog.query.items();
}

Option 3: Framework cleanup hooks

React: Cleanup in useEffect
useEffect(() => {
  const list = dcupl.lists.create({ modelKey: 'Product' });
  // ... use list
  return () => list.destroy(); // Cleanup on unmount
}, []);

Mutating items directly

Query results are copies by default, so direct mutations don't persist.

The problem

Wrong: Mutating items directly
const products = list.catalog.query.items();
products[0].price = 199; // Direct mutation

// The change is lost - original data unchanged
const refreshed = list.catalog.query.items();
console.log(refreshed[0].price); // Still the original price

Why it fails: items() returns cloned data (deep clones are on by default, performance: { clone: { enabled: true } }). Mutations don't affect the source. With performance: { clone: { enabled: false } } you get dcupl's stored objects instead, and a mutation changes dcupl's data directly, bypassing dcupl.data.update() and dcupl.update(). Don't mutate results in either mode.

The fix

Correct: Use dcupl.data.update()
// Update through the data API (changes existing items; use upsert() to add new ones)
dcupl.data.update([{ key: 'p1', price: 199 }], { model: 'Product' });

await dcupl.update(); // Process changes

const results = list.catalog.query.items();
console.log(results[0].price); // 199 - updated!

Properties disappear after models.set()

In SDK 2.0, dcupl.models.set() registers the whole definition for its key. Called again with only the property you want to add, it replaces the model: the properties you left out are removed, and their values are gone after the next update().

Wrong: set() with only the new property
dcupl.models.set({ key: 'Product', properties: [{ key: 'color', type: 'string' }] });
await dcupl.update();
// Product now has only 'color': 'name' and 'price' are gone
Correct: update() merges into the existing model
dcupl.models.update({ key: 'Product', properties: [{ key: 'color', type: 'string' }] });
await dcupl.update();
// Product has 'name', 'price' and 'color'

To change a property the model already has, get the definition, change it and set() the whole definition (Models API).

Case sensitivity in filters

String comparisons are case-sensitive by default.

The problem

Wrong: Case mismatch
// Data has: { status: 'Active' }

list.catalog.query.addCondition({
  attribute: 'status',
  operator: 'eq',
  value: 'active', // Won't match 'Active'
});
// Results: []

The fix

Option 1: Use transform for case-insensitive matching

Correct: Use lowercase transform
list.catalog.query.addCondition({
  attribute: 'status',
  operator: 'eq',
  value: 'active',
  options: { transform: ['lowercase'] },
});

Option 2: Match the exact case in your data

Alternative: Match exact case
list.catalog.query.addCondition({
  attribute: 'status',
  operator: 'eq',
  value: 'Active', // Matches 'Active' exactly
});
Note

Best practice: Normalize data during import (e.g., lowercase all status values) for consistent filtering.

Search finds only exact matches

The find operator with a plain string matches the whole value, not a part of it.

The problem

Wrong: Expecting a substring search
// Data has: 'Laptop', 'Gaming Laptop', 'laptop stand'

list.catalog.query.addCondition({
  attribute: 'name',
  operator: 'find',
  value: 'Laptop',
});
// Results: only 'Laptop'

The fix

Wrap the search term in slashes: find then treats it as a regular expression and matches it anywhere in the value. Add regexFlags: 'i' (or transform: ['lowercase']) to ignore case:

Correct: Regex for a substring search
list.catalog.query.addCondition({
  attribute: 'name',
  operator: 'find',
  value: '/laptop/',
  options: { regexFlags: 'i' },
});
// Results: 'Laptop', 'Gaming Laptop', 'laptop stand'

Escape regex characters in user input before you put it between the slashes. See find for the options.

Import and installation issues

Missing package

Wrong: Incorrect import
import { Dcupl } from 'dcupl'; // Wrong package name
// Error: Cannot find module 'dcupl'
Correct: Import from @dcupl/core
import { Dcupl } from '@dcupl/core';

Loader not installed

Wrong: Using loader without installing
import { DcuplAppLoader } from '@dcupl/loader';
// Error: Cannot find module '@dcupl/loader'
Fix: Install the loader package
npm install @dcupl/loader

Version mismatch

Ensure all dcupl packages are the same version. @dcupl/core depends on exactly its own version of @dcupl/common, and @dcupl/loader on exactly its own versions of both, so pin the same exact version for every package:

package.json
{
  "dependencies": {
    "@dcupl/common": "2.0.0",
    "@dcupl/core": "2.0.0",
    "@dcupl/loader": "2.0.0"
  }
}

Filters accumulating unexpectedly

Filters persist until explicitly cleared. addCondition() combines conditions on different attributes with AND, and conditions on the same attribute with OR (each attribute gets its own or group). Use setCondition() to replace the conditions of an attribute.

The problem

Wrong: Filters accumulate
// First filter
list.catalog.query.addCondition({ attribute: 'category', operator: 'eq', value: 'Electronics' });
const electronics = list.catalog.query.items(); // Works: 2 items

// Add second filter - accumulates with first!
list.catalog.query.addCondition({ attribute: 'price', operator: 'gte', value: 500 });
const expensive = list.catalog.query.items(); // Only expensive Electronics, not all expensive items

The fix

Correct: Clear before new query
// First query
list.catalog.query.addCondition({ attribute: 'category', operator: 'eq', value: 'Electronics' });
const electronics = list.catalog.query.items();

// Clear and start fresh
list.catalog.query.clear();
list.catalog.query.addCondition({ attribute: 'price', operator: 'gte', value: 500 });
const expensive = list.catalog.query.items(); // All expensive items

Quick debugging checklist

When things don't work as expected:

  1. Check init: Did you await dcupl.init() (after await loader.process() when you use a loader), and did it resolve true?
  2. Check model keys: Is the model name spelled correctly, in the list and in the data you staged?
  3. Check attribute names: Do they match your model properties?
  4. Check data types: Are filter values the correct type (number vs string)?
  5. Check case: Are string comparisons matching case?
  6. Check search: Does a find for part of a value use the /…/ form?
  7. Check filters: Do you need to clear() previous filters?
  8. Check execution: Did you call items() to get results?
Debug helper
// Log current state
console.log('Models:', dcupl.models.keys());
console.log('Query:', JSON.stringify(list.catalog.query.current(), null, 2));
console.log('Results:', list.catalog.query.items().length);