Skip to content

Commit

Permalink
feat(cloudflare): support resolveDurableStub (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Feb 10, 2025
1 parent abe67f8 commit c340e31
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 14 deletions.
10 changes: 10 additions & 0 deletions docs/2.adapters/cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,13 @@ new_classes = ["$DurableObject"]
::read-more
See [`test/fixture/cloudflare-durable.ts`](https://github.com/unjs/crossws/blob/main/test/fixture/cloudflare-durable.ts) for demo and [`src/adapters/cloudflare-durable.ts`](https://github.com/unjs/crossws/blob/main/src/adapters/cloudflare-durable.ts) for implementation.
::

### Adapter options

> [!NOTE]
> By default, crossws uses the durable object class `$DurableObject` from `env` with an instance named `crossws`.
> You can customize this behavior by providing `resolveDurableStub` option.
- `bindingName`: Durable Object binding name from environment (default: `$DurableObject`).
- `instanceName`: Durable Object instance name (default: `crossws`).
- `resolveDurableStub`: Custom function that resolves Durable Object binding to handle the WebSocket upgrade. This option will override `bindingName` and `instanceName`.
66 changes: 52 additions & 14 deletions src/adapters/cloudflare-durable.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type * as CF from "@cloudflare/workers-types";
import type { DurableObject } from "cloudflare:workers";
import type { AdapterOptions, AdapterInstance, Adapter } from "../adapter.ts";
import type * as web from "../../types/web.ts";
import { toBufferLike } from "../utils.ts";
Expand All @@ -6,25 +8,66 @@ import { AdapterHookable } from "../hooks.ts";
import { Message } from "../message.ts";
import { Peer } from "../peer.ts";

import type * as CF from "@cloudflare/workers-types";
import type { DurableObject } from "cloudflare:workers";
type ResolveDurableStub = (
req: CF.Request,
env: unknown,
context: CF.ExecutionContext,
) => CF.DurableObjectStub | Promise<CF.DurableObjectStub>;

export interface CloudflareOptions extends AdapterOptions {
/**
* Durable Object binding name from environment.
*
* **Note:** This option will be ignored if `resolveDurableStub` is provided.
*
* @default "$DurableObject"
*/
bindingName?: string;

/**
* Durable Object instance name.
*
* **Note:** This option will be ignored if `resolveDurableStub` is provided.
*
* @default "crossws"
*/
instanceName?: string;

/**
* Custom function that resolves Durable Object binding to handle the WebSocket upgrade.
*
* **Note:** This option will override `bindingName` and `instanceName`.
*/
resolveDurableStub?: ResolveDurableStub;
}

// https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server/

const cloudflareDurableAdapter: Adapter<
CloudflareDurableAdapter,
CloudflareOptions
> = (opts) => {
> = (opts = {}) => {
const hooks = new AdapterHookable(opts);
const peers = new Set<CloudflareDurablePeer>();

const resolveDurableStub: ResolveDurableStub =
opts.resolveDurableStub ||
((_req, env: any, _context): CF.DurableObjectStub => {
const bindingName = opts.bindingName || "$DurableObject";
const binding = env[bindingName] as CF.DurableObjectNamespace;
if (!binding) {
throw new Error(
`Durable Object binding "${bindingName}" not available`,
);
}
const instanceId = binding.idFromName(opts.instanceName || "crossws");
return binding.get(instanceId);
});

return {
...adapterUtils(peers),
handleUpgrade: async (req, env, _context) => {
const bindingName = opts?.bindingName ?? "$DurableObject";
const instanceName = opts?.instanceName ?? "crossws";
const binding = (env as any)[bindingName] as CF.DurableObjectNamespace;
const id = binding.idFromName(instanceName);
const stub = binding.get(id);
handleUpgrade: async (req, env, context) => {
const stub = await resolveDurableStub(req as CF.Request, env, context);
return stub.fetch(req as CF.Request) as unknown as Response;
},
handleDurableInit: async (obj, state, env) => {
Expand Down Expand Up @@ -225,8 +268,3 @@ export interface CloudflareDurableAdapter extends AdapterInstance {
wasClean: boolean,
): Promise<void>;
}

export interface CloudflareOptions extends AdapterOptions {
bindingName?: string;
instanceName?: string;
}

0 comments on commit c340e31

Please sign in to comment.