// --------------------------------------------------------------------------- // Kv — generic scoped key-value store // // The foundational storage primitive. Everything persists through this: // tools, definitions, secrets, policies, plugin data. Implementations // live in @executor/storage-file or are provided by the host. // --------------------------------------------------------------------------- import { Effect } from "effect"; /** * Global KV — requires a namespace on every call. * Implementations: makeSqliteKv, makeInMemoryKv */ export interface Kv { readonly get: (namespace: string, key: string) => Effect.Effect; readonly set: (namespace: string, key: string, value: string) => Effect.Effect; readonly delete: (namespace: string, key: string) => Effect.Effect; readonly list: (namespace: string) => Effect.Effect; readonly deleteAll: (namespace: string) => Effect.Effect; readonly withTransaction?: ( effect: Effect.Effect, ) => Effect.Effect; } /** * Scoped KV — already bound to a namespace. * This is what stores and adapters receive. */ export interface ScopedKv { readonly get: (key: string) => Effect.Effect; readonly set: (key: string, value: string) => Effect.Effect; readonly delete: (key: string) => Effect.Effect; readonly list: () => Effect.Effect; readonly deleteAll: () => Effect.Effect; readonly withTransaction?: ( effect: Effect.Effect, ) => Effect.Effect; } /** * Scope a Kv to a specific namespace. */ export const scopeKv = (kv: Kv, namespace: string): ScopedKv => ({ get: (key) => kv.get(namespace, key), set: (key, value) => kv.set(namespace, key, value), delete: (key) => kv.delete(namespace, key), list: () => kv.list(namespace), deleteAll: () => kv.deleteAll(namespace), withTransaction: kv.withTransaction, }); /** * In-memory ScopedKv — useful for tests and plugins that don't need persistence. */ export const makeInMemoryScopedKv = (): ScopedKv => { const store = new Map(); return { get: (key) => Effect.succeed(store.get(key) ?? null), set: (key, value) => Effect.sync(() => { store.set(key, value); }), delete: (key) => Effect.sync(() => store.delete(key)), list: () => Effect.sync(() => [...store.entries()].map(([key, value]) => ({ key, value })), ), deleteAll: () => Effect.sync(() => { const n = store.size; store.clear(); return n; }), withTransaction: (effect) => effect, }; };