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
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 finishedWhy 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
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():
await loader.config.fetch();
await loader.process(); // fetches models and data
await dcupl.init(); // processes what the loader addedTip: 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 [].
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 foundconsole.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.
dcupl.data.update({ key: 'p1', price: 199 }, { model: 'Product' });
// Throws: Error: Invalid Data - Data must be an Arraydcupl.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.
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(); // 0console.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
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
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)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
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); // undefinedThe fix
list.catalog.query.addCondition({
attribute: 'category',
operator: 'eq',
value: 'Electronics',
});
const results = list.catalog.query.items(); // Call items() to execute
console.log(results.length); // 2Related 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
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
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
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
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
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 priceWhy 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
// 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().
dcupl.models.set({ key: 'Product', properties: [{ key: 'color', type: 'string' }] });
await dcupl.update();
// Product now has only 'color': 'name' and 'price' are gonedcupl.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
// 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
list.catalog.query.addCondition({
attribute: 'status',
operator: 'eq',
value: 'active',
options: { transform: ['lowercase'] },
});Option 2: Match the exact case in your data
list.catalog.query.addCondition({
attribute: 'status',
operator: 'eq',
value: 'Active', // Matches 'Active' exactly
});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
// 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:
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
import { Dcupl } from 'dcupl'; // Wrong package name
// Error: Cannot find module 'dcupl'import { Dcupl } from '@dcupl/core';Loader not installed
import { DcuplAppLoader } from '@dcupl/loader';
// Error: Cannot find module '@dcupl/loader'npm install @dcupl/loaderVersion 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:
{
"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
// 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 itemsThe fix
// 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 itemsQuick debugging checklist
When things don't work as expected:
- Check init: Did you
await dcupl.init()(afterawait loader.process()when you use a loader), and did it resolvetrue? - Check model keys: Is the model name spelled correctly, in the list and in the data you staged?
- Check attribute names: Do they match your model properties?
- Check data types: Are filter values the correct type (number vs string)?
- Check case: Are string comparisons matching case?
- Check search: Does a
findfor part of a value use the/…/form? - Check filters: Do you need to
clear()previous filters? - Check execution: Did you call
items()to get results?
// 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);