Skip to content

kwhitley/itty-durable

Repository files navigation


durable

Version npm bundle size

Discord


Tiny (~400 bytes) wrapper for Cloudflare Durable Objects, adding the following functionality to the native RPC implementation:

  1. Auto persists properties to storage
  2. Optionally define your own external store (e.g. R2, KV, supabase, any async service/API, etc)
  3. Use with any router/framework... itty, Hono, etc

Bonus - Middleware is included for itty-router and Hono to allow easier accessing of stubs by string identifier (simplifies access from Worker). Documentation to come...

Disclaimer

This is a next tag release, for playtesting/confirming the final public version. While I've tried to cover the bases, there is not yet test coverage (until the API is finalized), and the API may change as discussions/feedback unfolds. Use at your own risk!

Installation

npm install itty-durable@next

Example

import { IttyDurable } from 'itty-durable'

export class Counter extends IttyDurable {
  // anything in this.persisted will be automatically stored
  $persisted = {
    value = 0
  }

  increment(by: number = 1) {
    this.$persisted.value += by

    return this.$props() // optionally return the props
  }
}

How it Works

Under the hood, IttyDurable (which directy extends DurableObject) returns a Proxy to itself. When methods on your Durable Object are called, it intercepts these requests to do the following lifecycle steps:

  1. Sync props from storage if not already done. Thanks to RPC being fully async, we can safely await this, allowing slow requests from any data source.
  2. Execute the original method call.
  3. Register a debounced timer (default delay is 2000ms, subject to change) to persist the props back to storage. This prevents rapid method executions from writing more than necessary to storage. When action on the DO stops, the write executes well before the DO expires.

API

The API of IttyDurable is intentionally minimalist. In fact, we only expose a handful of properties/methods, each prefixed with a $ for clarity/separation:

$persisted -

The $persisted property in your DO is automatically synced via the store (defaults to using DO internal storage using a single key). Anything you put in here will be saved. Anything outside will reside in memory only, resetting when the DO resets/sleeps.

export class Counter extends IttyDurable {
  // this property will not be persisted
  foo = 'bar'

  // but this will be
  $persisted = {
    value = 0
  }

  increment(by: number = 1) {
    this.persisted.value += by
  }
}

$store - swappable external storage

The $store property can be used to swap in your own external storage. The required interface is simple, consisting of a get/put method pair. These functions can be sync/async - as they'll be awaited regardless. To use the original DO as this within your functions (e.g. to access this.ctx or bindings within this.env), you must write these in function syntax (shown below), rather than arrow functions. If you don't need this, then either will work.

const CustomStore = {
  get() {
    // return some data here
  },
  put(value: any) {
    // handle storage of value
  }
}

export class Counter extends IttyDurable {
  $store = CustomStore

  // ...
}

To give you a better idea of a store implementation, here's the actual default store (writes to a single key in default DO storage):

$store: DurableStore = {
  get() {
    return this.ctx.storage.get('v')
  },
  put(value: any) {
    this.ctx.storage.put('v', value)
  }
}

$persist(delay = 0) - manually persist to storage

The $persist() method is automatically called internally when methods are executed, but you can always call it yourself manually if you want an immediate (or delayed) write.

export class Counter extends IttyDurable {
  value = 20

  increment(by: number = 1) {
    this.value += by

    this.$persist() // calls persist with 0 delay
    this.$persist(10000) // calls persist with 10 second delay
  }
}

$props() - return the DO properties

The $props() method is used to simplify the extraction of your own properties, including the contents within the memory-only $ object. This is a helpful return for your functions that are called from a Worker, if they need to see/return the contents of the DO after exection. This function naturally omits the env, ctx, $store` properties on the DO.

export class Counter extends IttyDurable {
  value = 20
  foo = 'bar'

  increment(by: number = 1) {
    this.value += by

    return this.$props() // { $: {}, value: 21, foo: 'bar' }
  }
}

About

Cloudflare Durable Objects + Itty Router = shorter code

Resources

License

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published