little-durable-objects 0.1.3

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; actor state is stored as NDJSON in regional GCS buckets and coordination lives 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. Its body is { "codeRevision", "imageRef", "workingDirectory", "actorEntrypoint" }. The workflow-token body is { "executionId", "deadlineUnixMs", "storageRegion" }.

    storageRegion selects the home region only when an actor is first created. Later invocations route over gRPC to 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 sends an authenticated JSON POST to the control plane:

    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