Skip to content

Cloudflare Durable Objects

This guide focuses on using workers-qb with Cloudflare Durable Objects, specifically leveraging Durable Objects' built-in storage for structured data via SQLite.

Cloudflare Durable Objects Storage

Cloudflare Durable Objects provide a way to maintain state and coordinate operations across a globally distributed network. Each Durable Object instance has its own persistent storage, which is based on SQLite. workers-qb's DOQB class is designed to interact with this SQLite storage within your Durable Objects.

Important Considerations for Durable Objects Storage:

  • Synchronous Operations: Durable Objects storage operations are synchronous and block the Durable Object's event loop while they are executing. Keep operations reasonably fast.
  • SQLite Limitations: Durable Objects storage uses SQLite, which has certain limitations compared to more full-featured database systems like PostgreSQL. Be aware of SQLite-specific syntax and features.
  • Local Persistence: Storage is local to each Durable Object instance. Data is not shared directly between different Durable Object instances unless you implement explicit coordination mechanisms.

DOQB Class

To work with Durable Objects storage using workers-qb, you will use the DOQB class. This class extends QueryBuilder and is adapted for the synchronous nature of Durable Objects storage operations.

Note: DOQB is designed for synchronous operations (IsAsync extends boolean = false in its definition).

Connecting to Durable Objects Storage

Within your Durable Object class, you can access the DurableObjectStorage instance through this.storage.sql. Pass this instance to the DOQB constructor.

Example: Connecting to Durable Objects Storage in a Durable Object Class

import { DOQB } from 'workers-qb';

export class MyDurableObject extends DurableObject {
  constructor(state: DurableObjectState, env: Env) {
    super(state, env);
    // ... other initialization ...
  }

  async fetch(request: Request): Promise<Response> {
    const qb = new DOQB(this.storage.sql); // Initialize DOQB with DurableObjectStorage

    // ... your queries using qb ...

    return new Response("Durable Object queries executed");
  }
}

DO Specific Examples

Here are examples showing how to use DOQB within your Durable Object.

Basic Queries in Durable Objects

All basic and advanced query operations described in Basic Queries and Advanced Queries are generally applicable to DOQB, keeping in mind the synchronous nature and SQLite limitations.

Example: Inserting and Fetching Data in Durable Objects

import { DOQB } from 'workers-qb';

export class MyDurableObject extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    const qb = new DOQB(this.storage.sql);

    // Create table (if not exists) - typically in Durable Object's constructor or first fetch
    qb.createTable({
        tableName: 'items',
        ifNotExists: true,
        schema: `
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            value TEXT
        `
    }).execute();

    type Item = {
      id: number;
      name: string;
      value: string;
    };

    // Insert an item
    const insertedItem = qb.insert<Item>({
      tableName: 'items',
      data: {
        name: 'Example Item',
        value: 'Some value',
      },
      returning: ['id', 'name', 'value'],
    }).execute();

    console.log('Inserted item:', insertedItem.results);

    // Fetch all items
    const allItems = qb.fetchAll<Item>({
      tableName: 'items',
    }).execute();

    console.log('All items:', allItems.results?.length);

    return Response.json({
      insertedItem: insertedItem.results,
      allItemsCount: allItems.results?.length,
    });
  }
}

Lazy Queries in Durable Objects

DOQB supports lazy query execution using the lazyExecute method (and the lazy: true option in fetchAll). This can be beneficial when dealing with potentially large datasets in Durable Objects, as it allows you to process results iteratively without loading the entire dataset into memory at once.

Example: Lazy Fetching and Iterating Through Items

import { DOQB } from 'workers-qb';

export class MyDurableObject extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    const qb = new DOQB(this.storage.sql);

    type Item = {
      id: number;
      name: string;
      value: string;
    };

    // Lazy fetch all items
    const lazyItemsResult = await qb.fetchAll<Item, true>({ // Note: <Item, true> for lazy fetch
      tableName: 'items',
      lazy: true, // Explicitly set lazy: true
    }).execute();

    let itemCount = 0;
    if (lazyItemsResult.results) {
      for await (const item of lazyItemsResult.results) { // Async iteration over lazy results
        itemCount++;
        // Process each item here, e.g., console.log(item.name);
      }
    }

    console.log('Total items processed lazily:', itemCount);

    return Response.json({
      lazyItemsCount: itemCount,
    });
  }
}

In this example, fetchAll is called with lazy: true and the generic type specified as <Item, true>. The execute() method returns a result object where results is an AsyncIterable<Item>. You can then use an for await...of loop to iterate through the results asynchronously, processing items one by one as they are fetched from the database.

Note: Lazy queries are particularly useful in Durable Objects to avoid blocking the event loop for extended periods when dealing with large datasets. However, keep in mind that each iteration still involves synchronous storage operations. Optimize your processing logic within the loop to maintain responsiveness.

This guide provides an overview of using workers-qb with Cloudflare Durable Objects storage. Remember to consider the synchronous nature and SQLite limitations of Durable Objects storage when designing your applications. Next, explore the guide for PostgreSQL integration.