> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript Web Legacy→DQL Migration Guide

> Guide for migrating JavaScript Web apps from legacy query builder APIs to DQL (Ditto Query Language) syntax.

## Overview

This guide will help you successfully migrate your Ditto JavaScript application from the legacy query builder APIs to the modern DQL (Ditto Query Language). After reviewing this documentation, you'll understand how to convert method chaining patterns to DQL syntax and systematically update your data operations.

***

## AI Agent Prompt

Use this prompt when working with an AI coding assistant to migrate your Ditto JavaScript app from legacy query builder to DQL.

<Accordion title="Copy AI Migration Prompt (Click to Expand)">
  ````text theme={null}
  I need help migrating a Ditto JavaScript application from the legacy query builder APIs to modern DQL (Ditto Query Language). This migration involves converting method chaining patterns to SQL-like DQL syntax.

  CRITICAL RULES:
  1. All query builder method chains (.collection().find()) must be replaced with ditto.store.execute() using DQL
  2. Use parameterized queries with :paramName syntax - NEVER string interpolation
  3. Counter operations must use PN_INCREMENT BY in APPLY clause - do NOT initialize counter fields
  4. Sync subscriptions must use ditto.sync.registerSubscription() instead of .find().subscribe()
  5. observeLocal must be replaced with registerObserver and for await iteration

  ---

  CORE MIGRATION AREAS:

  1. QUERY SYNTAX MIGRATION

  BEFORE (Legacy Query Builder):
  ```typescript
  ditto.store.collection("cars")
      .find("color == $args.color", { color: "red" })
      .exec();
  ```

  AFTER (DQL):
  ```typescript
  await ditto.store.execute(
      "SELECT * FROM cars WHERE color = :color",
      { color: "red" }
  );
  ```

  2. INSERT OPERATIONS

  BEFORE (Legacy Query Builder):
  ```typescript
  ditto.store.collection("cars")
      .upsert({ _id: id, color: "blue" });
  ```

  AFTER (DQL):
  ```typescript
  await ditto.store.execute(
      "INSERT INTO cars DOCUMENTS (:car)",
      { car: carData }
  );
  ```

  3. UPDATE OPERATIONS

  BEFORE (Legacy Query Builder):
  ```typescript
  ditto.store.collection("cars")
      .findByID(id)
      .update((doc) => {
          doc.color = "green";
      });
  ```

  AFTER (DQL):
  ```typescript
  await ditto.store.execute(
      "UPDATE cars SET color = :color WHERE _id = :id",
      { color: "green", id: id }
  );
  ```

  4. DELETE OPERATIONS

  BEFORE (Legacy Query Builder):
  ```typescript
  ditto.store.collection("cars").findByID(id).remove();
  ```

  AFTER (DQL):
  ```typescript
  await ditto.store.execute(
      "DELETE FROM cars WHERE _id = :id",
      { id: id }
  );
  ```

  5. EVICTION OPERATIONS

  BEFORE (Legacy Query Builder):
  ```typescript
  ditto.store.collection("cars").findByID(id).evict();
  ```

  AFTER (DQL):
  ```typescript
  await ditto.store.execute(
      "EVICT FROM cars WHERE _id = :id",
      { id: id }
  );
  ```

  6. COUNTER OPERATIONS (PN_COUNTER)

  BEFORE (Legacy Query Builder):
  ```typescript
  ditto.store.collection("cars")
      .findByID(id)
      .update((doc) => {
          doc.numUpdates.counter?.increment(1);
      });
  ```

  AFTER (DQL with PN_INCREMENT):
  ```typescript
  await ditto.store.execute(
      "UPDATE cars APPLY numUpdates PN_INCREMENT BY :increment WHERE _id = :id",
      { increment: 1, id: id }
  );
  ```

  IMPORTANT: Do NOT initialize counter fields in documents:
  ```typescript
  // WRONG - Creates a register, not a counter
  { counter: 0 }

  // CORRECT - Omit counter field, it's created on first PN_INCREMENT
  { _id: id, color: "blue" }
  ```

  7. DOCUMENT FIELD ACCESS MIGRATION

  BEFORE (Legacy Query Builder):
  ```typescript
  const document = ditto.store.collection("cars").findByID(id).exec();
  const color = document?.value.color;
  ```

  AFTER (DQL):
  ```typescript
  const result = await ditto.store.execute(dqlString);
  const item = result.items[0];
  const color = item.value.color;
  ```

  8. OBSERVER MIGRATION (observeLocal → registerObserver)

  BEFORE (Legacy observeLocal):
  ```typescript
  const liveQuery = ditto.store.collection("cars")
      .find(`_id.locationId == '${Constants.locationId}'`)
      .observeLocal((docs, event) => {
          if (event.type === 'update') {
              // Handle changes
          } else if (event.type === 'initial') {
              // Handle initial data
          }
      });
  ```

  AFTER (DQL with registerObserver):
  ```typescript
  const observer = ditto.store.registerObserver(
      "SELECT * FROM cars WHERE _id.locationId = :locationId",
      { locationId: Constants.locationId }
  );

  for await (const result of observer) {
      // Extract values within loop scope — do not hold QueryResultItems outside this scope
      const items = result.items.map((item) => item.value);

      // Update UI
      updateUI(items);
  }
  ```

  9. SYNC SUBSCRIPTIONS MIGRATION

  BEFORE (Legacy Query Builder):
  ```typescript
  const subscription = ditto.store.collection("cars")
      .find("color == $args.color", { color: "red" })
      .subscribe();
  ```

  AFTER (DQL):
  ```typescript
  const subscription = ditto.sync.registerSubscription(
      "SELECT * FROM cars WHERE color = :color",
      { color: "red" }
  );
  ```

  ---

  COMMON PITFALLS TO AVOID:

  1. DQL Syntax Errors
     - Use :paramName for parameters, not $args.paramName or string interpolation

  2. Missing Parameter Binding
     - NEVER use string interpolation in queries
     - Always use parameterized queries with arguments object

  3. Counter Type Errors
     - Do NOT initialize counter fields with new DittoCounter() or numbers
     - Use PN_INCREMENT BY in APPLY clause
     - Pass negative values for decrements

  4. Memory Management with Observers
     - Use for await loop for automatic cleanup
     - Break from loop to stop observation

  5. Attachment Handling
     - Use ATTACHMENT annotation: "(image ATTACHMENT)"
     - Create attachments with ditto.store.newAttachment()

  ---

  MIGRATION CHECKLIST:

  Search for these legacy patterns and replace:
  - [ ] .collection( → await ditto.store.execute("SELECT * FROM
  - [ ] .find( → Convert to DQL WHERE clause with arguments
  - [ ] .findByID( → Convert to DQL WHERE _id = :id
  - [ ] .upsert( → Convert to DQL INSERT INTO
  - [ ] .update( → Convert to DQL UPDATE SET
  - [ ] .remove( → Convert to DQL DELETE FROM
  - [ ] .evict( → Convert to DQL EVICT FROM
  - [ ] .counter?.increment( → Convert to PN_INCREMENT BY in APPLY clause
  - [ ] new DittoCounter() → Remove initialization, use PN_INCREMENT
  - [ ] .observeLocal( → Convert to registerObserver with for await
  - [ ] .subscribe() → Convert to ditto.sync.registerSubscription()

  ---

  Please help me convert all legacy query builder patterns in my codebase to DQL syntax. Focus on:
  1. Maintaining the same functionality
  2. Using proper parameterized queries with arguments object
  3. Handling counter operations correctly with PN_INCREMENT
  4. Implementing proper observer cleanup with for await
  5. Converting all sync subscriptions to DQL

  Start by identifying all uses of .collection() in my codebase and systematically converting each one to the appropriate DQL pattern.
  ````
</Accordion>

***

## Syntax Change Reference

### Document Query Syntax

**Document Query All**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "SELECT * FROM cars"
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars").exec();
  ```
</CodeGroup>

**Document Query by ID**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "SELECT * FROM cars WHERE _id = '123'"
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars")
      .findByID("123")
      .exec();
  ```
</CodeGroup>

**Document Query with Predicate**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "SELECT * FROM cars WHERE color = 'blue'"
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars")
      .find("color == 'blue'")
      .exec();
  ```
</CodeGroup>

**Document Query with Arguments**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "SELECT * FROM cars WHERE color = :color",
      { color: "red" }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars")
      .find("color == $args.color", { color: "red" })
      .exec();
  ```
</CodeGroup>

**Document Insert**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "INSERT INTO cars DOCUMENTS (:car)",
      { car: carData }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars")
      .upsert({ _id: id, color: "blue" });
  ```
</CodeGroup>

**Document Update**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "UPDATE cars SET color = :color WHERE _id = :id",
      { color: "green", id: id }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  // Upsert document
  ditto.store.collection("cars")
      .upsert({ _id: id, color: "green" });

  // Update with function
  ditto.store.collection("cars")
      .findByID(id)
      .update((doc) => {
          doc.color = "green";
      });
  ```
</CodeGroup>

**Document Delete**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "DELETE FROM cars WHERE _id = :id",
      { id: id }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars").findByID(id).remove();
  ```
</CodeGroup>

**Document Local Eviction**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // Evict by ID
  await ditto.store.execute(
      "EVICT FROM cars WHERE _id = :id",
      { id: id }
  );

  // Evict all matching documents
  await ditto.store.execute(
      "EVICT FROM cars WHERE color = :color",
      { color: "red" }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  // Evict by ID
  ditto.store.collection("cars").findByID(id).evict();

  // Evict all matching documents
  ditto.store.collection("cars").findAll().evict();
  ```
</CodeGroup>

### Query Response Handling

**Legacy Query Builder → DQL Response**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  const result = await ditto.store.execute(dqlString);
  const item = result.items[0];
  const color = item.value.color;
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  const document = ditto.store.collection("cars").findByID(id).exec();
  const color = document?.value.color;
  ```
</CodeGroup>

**Legacy Query Builder → Modern Document Conversion**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // Pass item.value (not the QueryResultItem) to avoid holding references outside callback scope
  function documentToCar(value) {
      return {
          id: value._id,
          color: value.color
      };
  }

  // Usage: extract value within callback scope
  const cars = result.items.map((item) => documentToCar(item.value));
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  function documentToCar(doc) {
      return {
          id: doc.value._id,
          color: doc.value.color
      };
  }
  ```
</CodeGroup>

### Observer Migration

**Legacy Query Builder → DQL Store Observer Migration**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  const observer = ditto.store.registerObserver(
      "SELECT * FROM cars WHERE _id.locationId = :locationId",
      { locationId: Constants.locationId }
  );

  for await (const result of observer) {
      // Extract values within loop scope — do not hold QueryResultItems outside this scope
      const items = result.items.map((item) => item.value);

      // Update UI
      updateUI(items);
  }
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  const liveQuery = ditto.store.collection("cars")
      .find(`_id.locationId == '${Constants.locationId}'`)
      .observeLocal((docs, event) => {
          console.log(`Live query handler called. Event: ${event} Docs count: ${docs.length}`);
          if (event.type === 'update') {
              // Handle deletions, insertions, updates
          } else if (event.type === 'initial') {
              // Handle initial data
          }
      });
  ```
</CodeGroup>

<Warning>
  **Performance Consideration**: DQL observers provide more advanced return results including aggregates and projections. This requires more database full scans to ensure consistent results compared to the legacy query builder.

  **Use indexes on query fields** to maintain and improve observer performance. Indexes ensure your observers remain functional with optimal query performance.
</Warning>

**Best Practice: Create Indexes for Observer Queries**

```typescript theme={null}
// Create index on frequently queried fields
await ditto.store.execute(`
  CREATE INDEX idx_cars_locationId
  ON cars (_id.locationId)
`);

// Then register observer - queries will use the index
const observer = ditto.store.registerObserver(
    "SELECT * FROM cars WHERE _id.locationId = :locationId",
    { locationId: Constants.locationId }
);

for await (const result of observer) {
    // Process results
}
```

For more information on creating and managing indexes, see the [DQL Indexing documentation](/dql/indexing).

### Sync Subscriptions Migration

**Legacy Query Builder → DQL Sync Subscriptions**

**Subscribe with Query**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  const subscription = ditto.sync.registerSubscription(
      "SELECT * FROM cars WHERE color = :color",
      { color: "red" }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  const subscription = ditto.store.collection("cars")
      .find("color == $args.color", { color: "red" })
      .subscribe();
  ```
</CodeGroup>

**Subscribe with Parameters**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  const subscription = ditto.sync.registerSubscription(
      "SELECT * FROM cars WHERE _id.locationId = :locationId",
      { locationId: Constants.locationId }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  const subscription = ditto.store.collection("cars")
      .find(`_id.locationId == '${Constants.locationId}'`)
      .subscribe();
  ```
</CodeGroup>

**Multiple Subscriptions**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  const subscriptions = [];
  subscriptions.push(
      ditto.sync.registerSubscription(
          "SELECT * FROM cars WHERE color = :color",
          { color: "red" }
      )
  );
  subscriptions.push(
      ditto.sync.registerSubscription(
          "SELECT * FROM cars WHERE year > :year",
          { year: 2020 }
      )
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  const subscriptions = [];
  subscriptions.push(
      ditto.store.collection("cars")
          .find("color == 'red'")
          .subscribe()
  );
  subscriptions.push(
      ditto.store.collection("cars")
          .find("year > 2020")
          .subscribe()
  );
  ```
</CodeGroup>

**Cancel Subscription**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  subscription.cancel();
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  subscription.cancel();
  ```
</CodeGroup>

**Subscribe to All Documents**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  const subscription = ditto.sync.registerSubscription(
      "SELECT * FROM cars"
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  const subscription = ditto.store.collection("cars")
      .findAll()
      .subscribe();
  ```
</CodeGroup>

### Counter Type Migration

<Note>
  **PN\_COUNTER is the DQL equivalent of the legacy `DittoCounter` type.** When migrating counter operations from the legacy query builder's counter methods, use `PN_INCREMENT BY` in the `APPLY` clause. This maintains full compatibility with existing counter data created by `DittoCounter`.
</Note>

**Counter Increment**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "UPDATE cars APPLY numUpdates PN_INCREMENT BY :increment WHERE _id = :id",
      { increment: 1, id: id }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars")
      .findByID(id)
      .update((doc) => {
          doc.numUpdates.counter?.increment(1);
      });
  ```
</CodeGroup>

**Counter Decrement**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      "UPDATE cars APPLY viewCount PN_INCREMENT BY :decrement WHERE _id = :id",
      { decrement: -1, id: id }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars")
      .findByID(id)
      .update((doc) => {
          doc.viewCount.counter?.increment(-1);
      });
  ```
</CodeGroup>

**Initialize Counter in Document**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // Counter fields are automatically created on first PN_INCREMENT use
  await ditto.store.execute(
      "INSERT INTO cars DOCUMENTS (:car)",
      { car: {
          _id: id,
          color: "blue"
          // Do NOT initialize counter fields - they are created on first PN_INCREMENT
      }}
  );

  // Then use PN_INCREMENT with APPLY clause to create and increment the counter
  await ditto.store.execute(
      "UPDATE cars APPLY numUpdates PN_INCREMENT BY 1 WHERE _id = :id",
      { id: id }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  const carData = {
      _id: id,
      color: "blue",
      numUpdates: new DittoCounter()
  };
  ditto.store.collection("cars").upsert(carData);
  ```
</CodeGroup>

**Multiple Counter Operations**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  await ditto.store.execute(
      `UPDATE cars
       APPLY likes PN_INCREMENT BY :likeIncrement,
             dislikes PN_INCREMENT BY :dislikeDecrement,
             views PN_INCREMENT BY :viewIncrement
       WHERE _id = :id`,
      {
          likeIncrement: 1,
          dislikeDecrement: -1,
          viewIncrement: 1,
          id: id
      }
  );
  ```

  ```typescript JAVASCRIPT LEGACY (v4) theme={null}
  ditto.store.collection("cars")
      .findByID(id)
      .update((doc) => {
          doc.likes.counter?.increment(1);
          doc.dislikes.counter?.increment(-1);
          doc.views.counter?.increment(1);
      });
  ```
</CodeGroup>

### Attachment Operations with DQL

**Attachment Creation and Storage**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // Create attachment using store
  const attachment = await ditto.store.newAttachment(
      filePath,
      metadata
  );

  // Store attachment with DQL
  await ditto.store.execute(
      "INSERT INTO COLLECTION cars (image ATTACHMENT) DOCUMENTS (:doc)",
      { doc: docWithAttachment }
  );
  ```
</CodeGroup>

**Attachment Fetching**

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // Fetch attachment with progress callback
  const fetchResult = await ditto.store.fetchAttachment(
      attachmentToken,
      (event) => {
          if (event.type === 'progress') {
              updateProgress(event.downloadedBytes, event.totalBytes);
          }
      }
  );
  ```
</CodeGroup>

***

## Performance Enhancements

### Indexes for Improved Query Performance

DQL observers and queries benefit significantly from proper indexing. When migrating from the legacy query builder to DQL, creating indexes on frequently queried fields is essential for maintaining optimal performance.

**Why Indexes Matter for DQL:**

* DQL observers support advanced features like aggregates and projections
* These advanced features require full database scans to ensure consistent results
* Indexes dramatically reduce query execution time by avoiding full scans
* Combining indexes with observers provides better performance than legacy query builder

**Creating Indexes:**

```typescript theme={null}
// Create index on single field
await ditto.store.execute(`
  CREATE INDEX idx_cars_color
  ON cars (color)
`);

// Create compound index on multiple fields
await ditto.store.execute(`
  CREATE INDEX idx_cars_color_year
  ON cars (color, year)
`);

// Create index on nested field
await ditto.store.execute(`
  CREATE INDEX idx_cars_location
  ON cars (_id.locationId)
`);
```

**Best Practices:**

1. Create indexes on fields used in `WHERE` clauses
2. Create indexes before registering observers for those queries
3. Use compound indexes for queries with multiple filter conditions
4. Monitor query performance and add indexes as needed

For comprehensive information on indexing strategies, syntax, and best practices, see the [DQL Indexing documentation](/dql/indexing).

***

## Common Pitfalls to Avoid

### 1. DQL Syntax Errors

Use `:paramName` for parameters, not `$args.paramName` or template literals.

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // ❌ Wrong: Template literal interpolation
  const color = "red";
  await ditto.store.execute(`SELECT * FROM cars WHERE color = '${color}'`);

  // ✅ Correct: Using :paramName with object notation
  await ditto.store.execute(
      "SELECT * FROM cars WHERE color = :color",
      { color: "red" }
  );
  ```
</CodeGroup>

### 2. Missing Parameter Binding

**NEVER** use template literals in queries. Always use parameterized queries with object notation.

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // ❌ Wrong: Template literal interpolation
  const locationId = "loc_123";
  await ditto.store.execute(
      `SELECT * FROM cars WHERE _id.locationId = '${locationId}'`
  );

  // ✅ Correct: Parameterized query
  await ditto.store.execute(
      "SELECT * FROM cars WHERE _id.locationId = :locationId",
      { locationId }
  );
  ```
</CodeGroup>

### 3. Counter Type Errors

Use `COUNTER` annotation in collection definitions. Do **NOT** use `SET` with `COUNTER` fields. Use `APPLY` with `PN_INCREMENT BY`. Pass negative values for decrements.

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // ❌ Wrong: Initializing counter with a number (creates REGISTER, not COUNTER)
  await ditto.store.execute(
      "INSERT INTO items DOCUMENTS (:doc)",
      { doc: { counter: 0, _id: id }}
  );

  // ❌ Wrong: Using SET on counter field
  await ditto.store.execute(
      "UPDATE items SET counter = 5 WHERE _id = :id",
      { id }
  );

  // ✅ Correct: Use PN_INCREMENT BY with APPLY clause (creates counter on first use)
  await ditto.store.execute(
      "UPDATE COLLECTION items (counter COUNTER) APPLY counter PN_INCREMENT BY :value WHERE _id = :id",
      { value: 1, id }
  );

  // ✅ Correct: Decrement by passing negative value
  await ditto.store.execute(
      "UPDATE items APPLY counter PN_INCREMENT BY :value WHERE _id = :id",
      { value: -1, id }
  );
  ```
</CodeGroup>

### 4. Memory Management with Observers

Extract `item.value` immediately within the callback scope. Break from `for await` loop to stop observation. Use indexes for improved memory and performance.

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // ❌ Wrong: Storing QueryResultItems outside callback scope
  let items: QueryResultItem[] = [];
  const observer = ditto.store.registerObserver(
      "SELECT * FROM cars",
      {}
  );
  for await (const result of observer) {
      items = result.items;  // Holds FFI native memory
  }

  // ✅ Correct: Extract values immediately and break when done
  const observer = ditto.store.registerObserver(
      "SELECT * FROM cars",
      {}
  );
  for await (const result of observer) {
      // Extract plain values immediately - no FFI overhead in memory
      const cars = result.items.map((item) => item.value);

      updateUI(cars);

      // Break from loop to stop observation
      if (shouldStop) {
          break;  // Automatically cleans up
      }
  }
  ```
</CodeGroup>

<Warning>
  Do **not** store or pass `result.items` (or individual `QueryResultItem` objects) outside the scope where they were created. Each item holds a reference into the FFI layer (Rust native memory), increasing memory pressure.
</Warning>

### 5. Attachment Handling

Use `ATTACHMENT` annotation in collection definitions. Create attachments with `ditto.store.newAttachment()`.

<CodeGroup>
  ```typescript JAVASCRIPT DQL (v5) theme={null}
  // ❌ Wrong: Missing ATTACHMENT annotation
  await ditto.store.execute(
      "INSERT INTO cars DOCUMENTS (:doc)",
      { doc: docWithAttachment }
  );

  // ✅ Correct: Use ATTACHMENT annotation in COLLECTION definition
  const attachment = await ditto.store.newAttachment(
      filePath,
      metadata
  );

  await ditto.store.execute(
      "INSERT INTO COLLECTION cars (image ATTACHMENT) DOCUMENTS (:doc)",
      { doc: {
          _id: id,
          image: attachment
      }}
  );
  ```
</CodeGroup>
