1import type * as runtime from "@cloudflare/workers-types";
2import * as Effect from "effect/Effect";
3import { Worker, WorkerEnvironment } from "../Workers/Worker.ts";
4import type { Namespace } from "./Namespace.ts";
5import { NamespaceError } from "./NamespaceTypes.ts";
6
7/**
8 * Shared scaffolding for the Worker-binding implementations of the KV
9 * services.
10 *
11 * Resolves the {@link WorkerEnvironment} and host {@link Worker}, registers
12 * the `kv_namespace` binding at deploy time, then delegates to `makeClient`
13 * with the shared {@link makeKVNamespaceHelpers} to build the
14 * read/write/read-write client.
15 */
16export const makeKVNamespaceBinding = <Client>(options: {
17 makeClient: (helpers: ReturnType<typeof makeKVNamespaceHelpers>) => Client;
18}) =>
19 Effect.gen(function* () {
20 const env = yield* WorkerEnvironment;
21 const host = yield* Worker;
22
23 return Effect.fn(function* (namespace: Namespace) {
24 if (!globalThis.__ALCHEMY_RUNTIME__) {
25 yield* host.bind`${namespace}`({
26 bindings: [
27 {
28 type: "kv_namespace",
29 name: namespace.LogicalId,
30 namespaceId: namespace.namespaceId,
31 },
32 ],
33 });
34 }
35
36 return options.makeClient(makeKVNamespaceHelpers(env, namespace));
37 });
38 });
39
40/** Primitives shared by the read and write halves of the binding client. */
41export const makeKVNamespaceHelpers = (
42 env: Record<string, any>,
43 namespace: Namespace,
44) => {
45 const raw = Effect.sync(
46 // Lazy — the WorkerEnvironment binding is not populated until runtime.
47 () => (env as Record<string, runtime.KVNamespace>)[namespace.LogicalId]!,
48 );
49
50 const tryPromise = <T>(
51 fn: () => Promise<T>,
52 ): Effect.Effect<T, NamespaceError> =>
53 Effect.tryPromise({
54 try: fn,
55 catch: (error: any) =>
56 new NamespaceError({
57 message: error?.message ?? "Unknown error",
58 cause: error,
59 }),
60 });
61
62 const use = <T>(
63 fn: (raw: runtime.KVNamespace<string>) => Promise<T>,
64 ): Effect.Effect<T, NamespaceError> =>
65 raw.pipe(Effect.flatMap((raw) => tryPromise(() => fn(raw))));
66
67 return { raw, use, tryPromise };
68};