little-durable-objects 0.1.7

Standalone regional durable-object control plane, host, and durability runtime
Documentation

little-durable-objects

A small, provider-neutral durable-object runtime. Modal is the first sandbox provider; immutable actor-state snapshots live in regional GCS buckets, while placement and each actor's current state head live in Postgres.

Install

Install the TypeScript API in actor and workflow projects:

pnpm add little-durable-objects

Install the Rust runtime from crates.io:

cargo install little-durable-objects --locked

Container builds can copy the binary from us-central1-docker.pkg.dev/fluid-analogy-473415-c2/public/little-durable-objects:latest. The image is a runtime base, not a complete actor sandbox: actor images also need Node.js, the project code, and little-durable-objects in the project's dependencies.

Quickstart

  1. Create a Postgres database and one GCS STANDARD bucket. Give the service account in GOOGLE_APPLICATION_CREDENTIALS object access to the bucket.

  2. Build the Rust runtime and TypeScript package:

    pnpm install
    pnpm build
    chmod +x npm/dist/providers/modalCli.js
    
  3. Start the control plane. Its HTTP origin serves the public REST API and the internal host gRPC API, so it must be reachable from Modal with HTTP/2 enabled.

    export DURABLE_OBJECT_PROCESS_ROLE=control_plane
    export DURABLE_OBJECT_POSTGRES_URL='postgresql://localhost/durable_objects?sslmode=disable'
    export DURABLE_OBJECT_STANDARD_BUCKETS='{"north-america-east":"my-actor-state-bucket"}'
    export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
    export DURABLE_OBJECT_CONTROL_PLANE_BIND=0.0.0.0:7100
    export DURABLE_OBJECT_CONTROL_PLANE_URL=https://objects.example.com
    export DURABLE_OBJECT_JWT_SIGNING_KEY="$(openssl genpkey -algorithm Ed25519 -outform DER | base64 | tr -d '\n')"
    export DURABLE_OBJECT_ADMIN_TOKEN="$(openssl rand -hex 32)"
    export DURABLE_OBJECT_SANDBOX_PROVIDER=modal
    export DURABLE_OBJECT_SANDBOX_COMMAND="$PWD/npm/dist/providers/modalCli.js"
    export MODAL_TOKEN_ID=...
    export MODAL_TOKEN_SECRET=...
    
    ./target/release/little-durable-objects
    
  4. Export actors from src/durable-objects.ts in your project:

    import { Actor } from "little-durable-objects"
    
    export class Counter extends Actor {
      count = 0
      async increment(): Promise<number> {
        return ++this.count
      }
    }
    
  5. From your trusted backend, call the JSON API using Authorization: Bearer $DURABLE_OBJECT_ADMIN_TOKEN:

    PUT  /v1/namespaces/{namespaceId}/deployment
    POST /v1/namespaces/{namespaceId}/workflow-tokens
    

    The deployment call atomically ensures the namespace and registers its active deployment. When it replaces an existing deployment, the control plane terminates that revision's cached actor hosts in every configured region before returning; actor state and placement remain durable for reactivation on the new revision. Its body is { "codeRevision", "imageRef", "workingDirectory", "actorEntrypoint", "warmRegion" }; warmRegion is optional and starts a disposable background sandbox to warm the image cache without delaying or failing registration. The workflow-token body is { "executionId", "deadlineUnixMs", "storageRegion" }.

    storageRegion selects the home region only when an actor is first created. Later invocations resolve the actor's existing host region.

    image_ref is a Modal image containing Node.js, your built project, its dependencies, and little-durable-objects at /usr/local/bin/little-durable-objects.

  6. Give the issued project token to the workflow and call the actor. The package resolves a short-lived actor target through the control plane, caches it, and invokes the regional host directly over gRPC:

    import { configureDurableObjects } from "little-durable-objects"
    import { Counter } from "./durable-objects.js"
    
    configureDurableObjects({
      token: process.env.DURABLE_OBJECT_TOKEN!,
      namespaceId: "my-project",
      controlPlaneUrl: "https://objects.example.com",
    })
    
    await Counter.get("account-1").increment()
    

Actors leave memory after 60 seconds idle; empty host sandboxes stop after 5 minutes. Override those defaults with DURABLE_OBJECT_ACTOR_IDLE_TIMEOUT_MS and DURABLE_OBJECT_HOST_IDLE_TIMEOUT_MS on the control plane.

See the architecture diagram for the request, credential, placement, and state flow.

Release maintainers should follow the release guide.

License

MIT © 2026 Terse