# 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

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

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

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

> **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 `[]`.

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

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

```typescript title="Wrong: A single item"
dcupl.data.update({ key: 'p1', price: 199 }, { model: 'Product' });
// Throws: Error: Invalid Data - Data must be an Array
```

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

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

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

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

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

### Data type mismatch

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

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

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

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

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

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

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

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

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

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

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

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

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

## Case sensitivity in filters

String comparisons are case-sensitive by default.

### The problem

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

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

```typescript title="Alternative: Match exact case"
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

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

```typescript title="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](/sdk/api/query-operators#find) for the options.

## Import and installation issues

### Missing package

```typescript title="Wrong: Incorrect import"
import { Dcupl } from 'dcupl'; // Wrong package name
// Error: Cannot find module 'dcupl'
```

```typescript title="Correct: Import from @dcupl/core"
import { Dcupl } from '@dcupl/core';
```

### Loader not installed

```typescript title="Wrong: Using loader without installing"
import { DcuplAppLoader } from '@dcupl/loader';
// Error: Cannot find module '@dcupl/loader'
```

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

```json filename="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

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

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

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

## Next steps

- [Error handling](/sdk/guides/error-handling) — What fails, what throws, and where the errors go
- [Debugging with Console](/sdk/guides/debugging) — Inspect your running app in dcupl Console
- [Quick start](/sdk/quick-start) — Build your first query correctly
