1/**
2 * Node-side seeding path into the local workerd Secrets Store simulator,
3 * built on the runtime's platform proxy (`PlatformProxy.open` — our
4 * `getPlatformProxy`).
5 *
6 * For a `dev:` store there is no cloud API to speak REST to. The local
7 * `Secret` provider instead writes values through the runtime's
8 * `SecretsStore.admin` hook — the raw KV-namespace view of a store (the
9 * equivalent of Miniflare's `getSecretsStoreSecretAPI` admin surface): a
10 * scoped platform proxy hosts the binding and Node drives it natively.
11 *
12 * Node ── proxy.env.STORE.put(name, value) ──▶ secrets-store service
13 *
14 * Data lands in the same `{storage}/secrets-store` directory every local
15 * worker's `secrets_store_secret` binding reads.
16 *
17 * NOT exported from `index.ts` — provider-internal scaffolding.
18 */
19import type * as runtime from "@cloudflare/workers-types";
20import { SecretsStore } from "@alchemy.run/cloudflare-runtime/core/bindings";
21import { open } from "@alchemy.run/cloudflare-runtime/core/platform-proxy";
22import * as Data from "effect/Data";
23import * as Effect from "effect/Effect";
24import { gatewayName } from "../LocalGateway.ts";
25
26export class LocalSecretsStoreError extends Data.TaggedError(
27 "LocalSecretsStoreError",
28)<{
29 message: string;
30 cause?: unknown;
31}> {}
32
33/**
34 * Boot a scoped platform proxy for `storeId`, hand `use` the store's raw
35 * KV-namespace admin surface, and tear the instance down when `use`
36 * completes. One proxy boot per operation — slow but correct; callers are
37 * provider lifecycle operations, not a request path.
38 */
39export const withLocalSecretsStore = <A, E, R>(
40 storeId: string,
41 use: (store: runtime.KVNamespace<string>) => Effect.Effect<A, E, R>,
42) =>
43 Effect.scoped(
44 Effect.gen(function* () {
45 const proxy = yield* open({
46 name: gatewayName("alchemy-secrets-store-gateway", storeId),
47 bindings: [SecretsStore.admin({ binding: "STORE", storeId })],
48 });
49 const store = (proxy.env as Record<string, unknown>)
50 .STORE as runtime.KVNamespace<string>;
51 return yield* use(store);
52 }),
53 );
54
55/** Write a secret value into the local store (idempotent overwrite). */
56export const seedLocalSecret = (
57 storeId: string,
58 secretName: string,
59 value: string,
60) =>
61 withLocalSecretsStore(storeId, (store) =>
62 Effect.tryPromise({
63 try: () => store.put(secretName, value),
64 catch: (cause) =>
65 new LocalSecretsStoreError({
66 message: `Failed to seed secret "${secretName}" into the local Secrets Store`,
67 cause,
68 }),
69 }),
70 );
71
72/** Remove a secret from the local store (idempotent). */
73export const deleteLocalSecret = (storeId: string, secretName: string) =>
74 withLocalSecretsStore(storeId, (store) =>
75 Effect.tryPromise({
76 try: () => store.delete(secretName),
77 catch: (cause) =>
78 new LocalSecretsStoreError({
79 message: `Failed to delete secret "${secretName}" from the local Secrets Store`,
80 cause,
81 }),
82 }),
83 );