import type * as runtime from "@cloudflare/workers-types"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Binding from "../../Binding.ts"; import type { ResourceLike } from "../../Resource.ts"; import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts"; import type { KVNamespace } from "./KVNamespace.ts"; export class KVNamespaceError extends Data.TaggedError("KVNamespaceError")<{ message: string; cause: Error; }> {} export interface KVNamespaceClient { raw: Effect.Effect; get( key: Key, options?: Partial>, ): Effect.Effect; get( key: Key, type: "text", ): Effect.Effect; get( key: Key, type: "json", ): Effect.Effect; get( key: Key, type: "arrayBuffer", ): Effect.Effect; get( key: Key, type: "stream", ): Effect.Effect; get( key: Key, options?: KVNamespaceGetOptions<"text">, ): Effect.Effect; get( key: Key, options?: KVNamespaceGetOptions<"json">, ): Effect.Effect; get( key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">, ): Effect.Effect; get( key: Key, options?: KVNamespaceGetOptions<"stream">, ): Effect.Effect; get( key: Array, type: "text", ): Effect.Effect< Map, KVNamespaceError, WorkerEnvironment >; get( key: Array, type: "json", ): Effect.Effect< Map, KVNamespaceError, WorkerEnvironment >; get( key: Array, options?: Partial>, ): Effect.Effect< Map, KVNamespaceError, WorkerEnvironment >; get( key: Array, options?: KVNamespaceGetOptions<"text">, ): Effect.Effect< Map, KVNamespaceError, WorkerEnvironment >; get( key: Array, options?: KVNamespaceGetOptions<"json">, ): Effect.Effect< Map, KVNamespaceError, WorkerEnvironment >; list( options?: KVNamespaceListOptions, ): Effect.Effect< KVNamespaceListResult, KVNamespaceError, WorkerEnvironment >; put( key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions, ): Effect.Effect; getWithMetadata( key: Key, options?: Partial>, ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, type: "text", ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, type: "json", ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, type: "arrayBuffer", ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, type: "stream", ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, options: KVNamespaceGetOptions<"text">, ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, options: KVNamespaceGetOptions<"json">, ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, options: KVNamespaceGetOptions<"arrayBuffer">, ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Key, options: KVNamespaceGetOptions<"stream">, ): Effect.Effect< KVNamespaceGetWithMetadataResult, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Array, type: "text", ): Effect.Effect< Map>, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Array, type: "json", ): Effect.Effect< Map>, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Array, options?: Partial>, ): Effect.Effect< Map>, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Array, options?: KVNamespaceGetOptions<"text">, ): Effect.Effect< Map>, KVNamespaceError, WorkerEnvironment >; getWithMetadata( key: Array, options?: KVNamespaceGetOptions<"json">, ): Effect.Effect< Map>, KVNamespaceError, WorkerEnvironment >; delete(key: Key): Effect.Effect; } export class KVNamespaceBinding extends Binding.Service< KVNamespaceBinding, (bucket: KVNamespace) => Effect.Effect >()("Cloudflare.KVNamespace") {} export const KVNamespaceBindingLive = Layer.effect( KVNamespaceBinding, Effect.gen(function* () { const bind = yield* KVNamespaceBindingPolicy; return Effect.fn(function* (bucket: KVNamespace) { yield* bind(bucket); const env = WorkerEnvironment.asEffect(); const raw = env.pipe( Effect.map( (env) => (env as Record)[bucket.LogicalId], ), ); const tryPromise = ( fn: () => Promise, ): Effect.Effect => Effect.tryPromise({ try: fn, catch: (error: any) => new KVNamespaceError({ message: error.message ?? "Unknown error", cause: error, }), }); const use = ( fn: (raw: runtime.KVNamespace) => Promise, ): Effect.Effect => raw.pipe(Effect.flatMap((raw) => tryPromise(() => fn(raw)))); return { raw: raw, // @ts-expect-error get: (...args: Parameters) => use((raw) => raw.get(...args)), // @ts-expect-error getWithMetadata: ( ...args: Parameters ) => use((raw) => raw.getWithMetadata(...args)), // @ts-expect-error put: (...args: Parameters) => use((raw) => raw.put(...args)), list: (...args: Parameters) => use((raw) => raw.list(...args)), delete: (...args: Parameters) => use((raw) => raw.delete(...args)), } satisfies KVNamespaceClient as KVNamespaceClient; }); }), ); export class KVNamespaceBindingPolicy extends Binding.Policy< KVNamespaceBindingPolicy, (bucket: KVNamespace) => Effect.Effect >()("Cloudflare.KVNamespace") {} export const KVNamespaceBindingPolicyLive = KVNamespaceBindingPolicy.layer.succeed( Effect.fn(function* (host: ResourceLike, namespace: KVNamespace) { if (isWorker(host)) { yield* host.bind`${namespace}`({ bindings: [ { type: "kv_namespace", name: namespace.LogicalId, namespaceId: namespace.namespaceId, }, ], }); } else { return yield* Effect.die( new Error(`BucketBinding does not support runtime '${host.Type}'`), ); } }), );