1import * as Effect from "effect/Effect";
2import { defaultDurableObjectUniqueKey } from "../internal/constants.ts";
3import type { BindingHook } from "../PluginContext.ts";
4import { PluginContext } from "../PluginContext.ts";
5import { RegistryProxy } from "../registry/RegistryProxy.ts";
6import { ConfigError } from "../RuntimeError.shared.ts";
7import type * as WorkerdConfig from "../workerd/Config.ts";
8
9export interface LocalDurableObjectNamespaceProps {
10 /**
11 * Name of the binding.
12 */
13 readonly binding: string;
14 /**
15 * Class name of the Durable Object namespace to bind to.
16 */
17 readonly className: string;
18 /**
19 * If set and the referenced worker isn't the current one, the binding is
20 * routed through the dev-registry proxy. This enables service-binding
21 * style cross-process Durable Object access during local development.
22 */
23 readonly scriptName?: string;
24 /**
25 * If set and the referenced worker isn't the current one, the binding is
26 * routed through the dev-registry proxy. This enables service-binding
27 * style cross-process Durable Object access during local development.
28 */
29 readonly uniqueKey?: string;
30}
31
32/**
33 * Bind a Durable Object namespace.
34 *
35 * - Without `scriptName`: bind a DO defined on the current worker.
36 * - With `scriptName === <current worker name>`: same as above.
37 * - With `scriptName !== <current worker name>`: route through the dev
38 * registry proxy so the DO can live in a different `cloudflare-runtime`
39 * or `wrangler dev` process.
40 */
41export const local = ({
42 binding,
43 className,
44 scriptName,
45 uniqueKey,
46}: LocalDurableObjectNamespaceProps): BindingHook<RegistryProxy> =>
47 PluginContext.use((context) => {
48 if (scriptName === undefined || scriptName === context.worker.name) {
49 const namespace = context.worker.durableObjectNamespaces?.find(
50 (namespace) => namespace.className === className,
51 );
52 if (!namespace) {
53 return Effect.fail(
54 new ConfigError({
55 subtag: "DurableObjectNamespaceNotFound",
56 message: `Durable object namespace ${className} not found`,
57 hint: `Make sure the durable object namespace ${className} is defined in the worker config`,
58 detail: {
59 className,
60 registeredClasses: context.worker.durableObjectNamespaces?.map(
61 (namespace) => namespace.className,
62 ),
63 },
64 }),
65 );
66 }
67 if (uniqueKey && namespace.uniqueKey !== uniqueKey) {
68 return Effect.fail(
69 new ConfigError({
70 subtag: "DurableObjectNamespaceUniqueKeyMismatch",
71 message: `Durable object namespace ${className} has unique key "${namespace.uniqueKey}" but "${uniqueKey}" was provided`,
72 }),
73 );
74 }
75 return Effect.succeed<WorkerdConfig.Worker_Binding>({
76 name: binding,
77 durableObjectNamespace: { className },
78 });
79 }
80 return context.get(RegistryProxy).pipe(
81 Effect.flatMap((proxy) =>
82 proxy.api.subscribe({
83 kind: "durable-object",
84 scriptName,
85 className,
86 uniqueKey:
87 uniqueKey ?? defaultDurableObjectUniqueKey(scriptName, className),
88 }),
89 ),
90 Effect.map((durableObjectNamespace): WorkerdConfig.Worker_Binding => ({
91 name: binding,
92 durableObjectNamespace,
93 })),
94 );
95 });