WebDyne::Cloudflare::DurableObject

Request-scoped Durable Object namespace and RPC client for WebDyne under ZeroPerl. Requires the finite-invocation tooling in ZeroPerl 1.0.13 or later for Perl-hosted objects, and targets Wrangler 4.131.1. Client-only access uses the normal extension lifecycle. No inheritance framework is required for application handlers.

Synopsis

use Future::AsyncAwait;
use WebDyne::Cloudflare::DurableObject;

my $namespace_or=WebDyne::Cloudflare::DurableObject->new(
    scope => $scope_hr, binding => 'COUNTERS',
);
my $counter_or=await $namespace_or->get_by_name('account:123');
my $result_hr=await $counter_or->call('increment', 1);

new(scope => ..., binding => ...) validates the invocation's binding allowlist. get_by_name($name) and get_by_id($id) return Futures resolving to a stub. call($method, @args) returns a Future resolving to one value. id() returns a stub's canonical ID; binding() returns the configured binding name. Names are non-empty strings. Persist names or IDs, not namespace/stub objects: capabilities expire at invocation completion. These operations do not retry failed calls.

Values and errors

RPC supports strings, finite numbers (integer values within JavaScript's safe integer range), JSON booleans, undef, arrays, hashes, and explicit byte values:

my $bytes_or=WebDyne::Cloudflare::DurableObject::bytes("\0\xff");
my $echo_or=await $counter_or->call('echo', $bytes_or);
my $raw=$echo_or->value();

Incoming bytes are DurableObject::Bytes objects, not implicit text. Tagged wire containers prevent ordinary hashes from being misinterpreted as bytes. Cycles, other blessed objects, streams, RPC targets and coderefs are unsupported. Nesting is limited to 64 and complete messages to 1 MiB. Keep large integers as text; SQL applications can select CAST(value AS TEXT) when exact integer values exceed the safe range. SQL blobs use the same explicit bytes wrapper.

Host and remote failures fail the Future, normally with a WebDyne::Cloudflare::DurableObject::Error exposing name, message, and code. Validation errors may be plain exceptions. A failure after a write does not imply rollback. Use idempotency keys for operations the application may retry.

Configuration

{
  "webdyne": {
    "entry": "app.psp",
    "perlLibrary": ["lib"],
    "extensions": {"@webdyne/webdyne-cloudflare": {}},
    "cloudflare": {
      "durableObjects": [
        {
          "binding": "COUNTERS",
          "className": "Counter",
          "perlPackage": "Example::Counter",
          "methods": ["increment", "read"],
          "initialize": true
        }
      ]
    }
  }
}

The updated ZeroPerl CLI adds namespace capabilities, exports the named JavaScript class, and generates Wrangler durable_objects.bindings and SQLite exports. initialize is optional and defaults to false. Methods must be explicitly listed; constructors, lifecycle handlers, then, AUTOLOAD, DESTROY, and internal dispatch names cannot be RPC methods. JavaScript class names start with an uppercase letter. Class and package names are validated before code is generated. The application package must be in perlLibrary.

For another Worker's Perl-hosted class, specify binding, className, and scriptName, omitting local package, methods, and initialization. For an existing ordinary JavaScript object, also specify native: true. Native calls invoke the named method directly; framework calls use the versioned webdyneInvoke protocol. Native bindings cannot carry our ancestry metadata, so call-cycle detection is only guaranteed along framework-to-framework call paths. Native services must not synchronously call back into a waiting framework object.

Custom Workers can set durableObjectBindings and durableObjectNativeBindings on createWebDyneCloudflareExtension. For hosting, import createWebDyneDurableObject from @webdyne/webdyne-cloudflare/durable-object, and supply createRuntime, runtimeOptions, definition, and a createExtensions factory. Each object receives fresh extension instances. The generated Worker illustrates this composition. Direct JavaScript clients can call the explicitly exposed methods normally; calls made by Perl should use this module so ancestry is preserved.

Existing custom Wrangler configurations remain user-owned. Add the equivalent bindings and SQLite declarations there. Do not combine exports and legacy migrations, or switch an existing deployment without reviewing its namespace history. Application schema initialization and Cloudflare class lifecycle configuration are separate concerns.

Object behavior

A JavaScript class owns one isolated interpreter per active Durable Object. Each RPC invocation has a fresh context and capability lifetime. Complete invocations, including initialization and awaited cleanup, run one at a time; at most 64 calls may be queued/running. Call chains are bounded to 32 and reject cycles, including same-object re-entry through binding aliases. A long or never-settling handler blocks later calls; there is no automatic handler retry or forced timeout.

Object handler signatures and SQLite operations are documented in Context.pm.md. Alarms, hibernating WebSockets, callback transactions, HTTP forwarding and rich RPC objects are deferred. The finite event transport is separate from HTTP/PAGI response transport so future event adapters can share scheduling and cleanup without keeping a request open.

Perl memory, globals, /tmp, and virtual filesystem writes are temporary. Store important state in SQLite before returning. Initialization runs again after an object or interpreter is reconstructed. There is no shutdown-save guarantee. Runtime/cleanup failures retire the hosting runtime reference; the next call initializes a fresh interpreter. Ordinary reported application errors preserve the interpreter, so application caches must remain consistent with any partial work.

One interpreter starts with a 32 MiB WASM memory allocation in the qualified build; this is not a total-memory estimate. JavaScript, filesystems, additional active objects and application allocations add overhead. Avoid one global object for unrelated entities and qualify workloads within Cloudflare's isolate limits.

References