{"version":3,"file":"redis.mjs","names":[],"sources":["../../../../../../../ai/src/snapshot/redis.ts"],"sourcesContent":["import type {\n  RedisClientLike,\n  SnapshotStore,\n} from \"../contracts/orchestrator/snapshot-store.contract\";\nimport type { SupervisorSnapshot } from \"../contracts/supervisor/supervisor-snapshot.type\";\n\n/**\n * Default key prefix the redis store prepends to each `runId`. Namespaces\n * the snapshot keys so they coexist with other data in the same Redis\n * database without collision.\n */\nconst DEFAULT_PREFIX = \"warlock:supervisor:snapshot:\";\n\n/**\n * Options for {@link redis}. The `client` is an already-connected redis\n * client (anything satisfying {@link RedisClientLike}); the store never\n * connects or quits it — connection lifecycle stays with the caller.\n */\nexport type RedisSnapshotStoreOptions = {\n  /** Pre-connected redis client. The store only calls `get`/`set`/`del`. */\n  client: RedisClientLike;\n  /**\n   * Key prefix prepended to each `runId`. Defaults to\n   * `warlock:supervisor:snapshot:`.\n   */\n  prefix?: string;\n};\n\n/**\n * Redis {@link SnapshotStore} — supervisor run snapshots persisted as one\n * JSON string value per `runId`, under a namespaced key (orchestrator.md\n * §8).\n *\n * Owns: durable round-tripping of the {@link SupervisorSnapshot} envelope\n * keyed by `runId`, so a crashed mid-turn `iterate: true` iteration can\n * resume after a restart. Does NOT own: the connection (the caller passes\n * a live client and keeps owning its lifecycle) or enumeration — the\n * structural {@link RedisClientLike} surface exposes only `get`/`set`/\n * `del`, with no `SCAN`/`KEYS`, so `list()` is intentionally not\n * implemented (the contract allows stores that can't enumerate to omit\n * it). Pair it with a {@link import(\"../contracts/orchestrator/checkpoint-store.contract\").CheckpointStore}\n * for the boot-drain loop, which is where enumeration is actually needed.\n *\n * `save()` overwrites the key — a run has exactly one live snapshot.\n * Redis needs no schema, so {@link RedisSnapshotStore.schema} returns an\n * empty string for uniformity with the other drivers.\n *\n * Front it with the {@link redis} factory — callers never `new` it.\n */\nclass RedisSnapshotStore implements SnapshotStore {\n  /** The user-supplied redis client. Only `get`/`set`/`del` are called. */\n  private readonly client: RedisClientLike;\n\n  /** Key prefix prepended to each `runId`. */\n  private readonly prefix: string;\n\n  public constructor(options: RedisSnapshotStoreOptions) {\n    if (\n      !options ||\n      !options.client ||\n      typeof options.client.get !== \"function\" ||\n      typeof options.client.set !== \"function\" ||\n      typeof options.client.del !== \"function\"\n    ) {\n      throw new Error(\n        \"Redis snapshot store requires a 'client' option implementing { get, set, del } — pass a connected redis client.\",\n      );\n    }\n\n    this.client = options.client;\n    this.prefix = options.prefix ?? DEFAULT_PREFIX;\n  }\n\n  /**\n   * Build the namespaced Redis key for a `runId`.\n   */\n  private key(runId: string): string {\n    return `${this.prefix}${runId}`;\n  }\n\n  /**\n   * Load the snapshot for a `runId`, or `undefined` when the key is\n   * missing. Redis returns `null` for an absent key — converted to\n   * `undefined` at the boundary.\n   */\n  public async load(runId: string): Promise<SupervisorSnapshot | undefined> {\n    const value = await this.client.get(this.key(runId));\n\n    if (value === null) {\n      return undefined;\n    }\n\n    return JSON.parse(value) as SupervisorSnapshot;\n  }\n\n  /**\n   * Persist a snapshot, keyed by its own `runId`. Overwrites any prior\n   * snapshot for the same run — a run has exactly one live snapshot.\n   */\n  public async save(snapshot: SupervisorSnapshot): Promise<void> {\n    await this.client.set(this.key(snapshot.runId), JSON.stringify(snapshot));\n  }\n\n  /**\n   * Drop the snapshot for a `runId`.\n   */\n  public async delete(runId: string): Promise<void> {\n    await this.client.del(this.key(runId));\n  }\n\n  /**\n   * Redis needs no backing table — there is nothing to migrate. Returns\n   * an empty string so callers can treat `schema()` uniformly across\n   * drivers.\n   */\n  public schema(): string {\n    return \"\";\n  }\n}\n\n/**\n * Create a Redis-backed {@link SnapshotStore}. Pass a connected redis\n * client — the store never connects or quits it.\n *\n * Note: this store does not implement the optional `list()` — the\n * structural client surface has no `SCAN`/`KEYS`. Use a checkpoint store\n * for the production boot-drain loop where enumeration is needed.\n *\n * @example\n * import { createClient } from \"redis\";\n * import { ai } from \"@warlock.js/ai\";\n *\n * const client = createClient({ url: process.env.REDIS_URL });\n * await client.connect();\n *\n * const orchestrator = ai.orchestrator({\n *   name: \"support\",\n *   intents: { ... },\n *   iterate: true,\n *   snapshotStore: ai.snapshot.redis({ client }),\n * });\n */\nexport function redis(options: RedisSnapshotStoreOptions): SnapshotStore {\n  return new RedisSnapshotStore(options);\n}\n"],"mappings":";;;;;;AAWA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;AAsCvB,IAAM,qBAAN,MAAkD;CAOhD,AAAO,YAAY,SAAoC;EACrD,IACE,CAAC,WACD,CAAC,QAAQ,UACT,OAAO,QAAQ,OAAO,QAAQ,cAC9B,OAAO,QAAQ,OAAO,QAAQ,cAC9B,OAAO,QAAQ,OAAO,QAAQ,YAE9B,MAAM,IAAI,MACR,iHACF;EAGF,KAAK,SAAS,QAAQ;EACtB,KAAK,SAAS,QAAQ,UAAU;CAClC;;;;CAKA,AAAQ,IAAI,OAAuB;EACjC,OAAO,GAAG,KAAK,SAAS;CAC1B;;;;;;CAOA,MAAa,KAAK,OAAwD;EACxE,MAAM,QAAQ,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC;EAEnD,IAAI,UAAU,MACZ;EAGF,OAAO,KAAK,MAAM,KAAK;CACzB;;;;;CAMA,MAAa,KAAK,UAA6C;EAC7D,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI,SAAS,KAAK,GAAG,KAAK,UAAU,QAAQ,CAAC;CAC1E;;;;CAKA,MAAa,OAAO,OAA8B;EAChD,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC;CACvC;;;;;;CAOA,AAAO,SAAiB;EACtB,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,MAAM,SAAmD;CACvE,OAAO,IAAI,mBAAmB,OAAO;AACvC"}