| 1 | import * as Effect from "effect/Effect"; |
| 2 | import { defaultDurableObjectUniqueKey } from "../internal/constants.ts"; |
| 3 | import type { BindingHook } from "../PluginContext.ts"; |
| 4 | import { PluginContext } from "../PluginContext.ts"; |
| 5 | import { RegistryProxy } from "../registry/RegistryProxy.ts"; |
| 6 | import { ConfigError } from "../RuntimeError.shared.ts"; |
| 7 | import type * as WorkerdConfig from "../workerd/Config.ts"; |
| 8 | |
| 9 | export 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 | */ |
| 41 | export 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 | }); |