{"version":3,"file":"in_memory.mjs","names":["#content","#lines","#bytes","#entries"],"sources":["../../../src/batteries/storage/in_memory/index.ts"],"sourcesContent":["/**\n * In-memory spool readers and stores for tests, scripts, and non-durable prototypes.\n *\n * @module @nhtio/adk/batteries/storage/in_memory\n *\n * @remarks\n * Opt-in in-memory persistence battery. Provides {@link InMemorySpoolReader} (a sync\n * {@link @nhtio/adk!SpoolReader} over a string) plus {@link InMemorySpoolStore} (a `Map<callId, bytes>`\n * with a `write()` method that returns a fresh reader bound to the stored bytes).\n *\n * Use this when:\n *\n * - Writing unit or functional tests that need a real `SpoolReader` over known bytes.\n * - Running a REPL or one-shot script where persistence beyond the process lifetime is not\n *   needed.\n * - Prototyping an agent before deciding on a real disk/object-store-backed persistence layer.\n *\n * Do **not** use this for production agents that need durability across process restarts —\n * everything lives in process memory and is lost on exit.\n */\n\nimport { isInstanceOf } from '@nhtio/adk/guards'\nimport type { ReaderDescriptor, SpoolReader, SpoolStore } from '@nhtio/adk/common'\n\n/**\n * Resolver tag for the in-memory spool reader handle. The locator inlines the decoded content because an\n * in-memory reader owns its bytes outright — there is no external store to point at.\n */\nexport const SPOOL_READER_TAG_IN_MEMORY = 'spool:in-memory'\n\n/**\n * Sync in-memory {@link @nhtio/adk!SpoolReader} over a byte-faithful `Uint8Array` body.\n *\n * @remarks\n * Stores the raw bytes and decodes them as UTF-8 once at construction, then splits the decoded\n * string on `\\n` and caches the resulting line array. All four `SpoolReader` methods resolve\n * synchronously from the cache — no I/O happens after construction. `byteLength()` reports the\n * true stored byte count (not the decoded character count), so it stays correct for multi-byte\n * content; `line()`/`readAll()` operate on the decoded text.\n *\n * The reader accepts a `string` or a `Uint8Array`. A `string` is encoded as UTF-8 for the byte\n * count; a `Uint8Array` is held byte-faithfully (no lossy re-encode) and decoded for text reads.\n *\n * Empty input yields a reader with `lineCount() === 0` and `byteLength() === 0`. A trailing\n * newline produces a final empty line: `\"a\\nb\\n\".split('\\n') === ['a', 'b', '']`. This matches\n * the JavaScript `String.prototype.split` contract and lets a `lineCount()` consumer\n * distinguish \"two lines, no trailing newline\" from \"two lines, trailing newline\".\n */\nexport class InMemorySpoolReader implements SpoolReader {\n  readonly #content: string\n  readonly #lines: string[]\n  readonly #bytes: number\n\n  constructor(content: string | Uint8Array) {\n    if (typeof content === 'string') {\n      this.#content = content\n      this.#bytes = new TextEncoder().encode(content).length\n    } else {\n      this.#content = new TextDecoder().decode(content)\n      this.#bytes = content.byteLength\n    }\n    this.#lines = this.#content === '' ? [] : this.#content.split('\\n')\n  }\n\n  line(index: number): string | undefined {\n    return this.#lines[index]\n  }\n\n  byteLength(): number {\n    return this.#bytes\n  }\n\n  lineCount(): number {\n    return this.#lines.length\n  }\n\n  readAll(): string {\n    return this.#content\n  }\n\n  describe(): ReaderDescriptor {\n    // The decoded content IS the backing store — inline it, no external locator exists.\n    return { tag: SPOOL_READER_TAG_IN_MEMORY, locator: { content: this.#content } }\n  }\n}\n\n/**\n * Drains a `ReadableStream<Uint8Array>` into a single concatenated `Uint8Array`.\n *\n * @remarks\n * In-memory storage cannot stream-to-disk, so a stream input is buffered fully — the documented\n * trade-off for {@link InMemorySpoolStore}. Use {@link @nhtio/adk/batteries/storage/opfs!OpfsSpoolStore}\n * or a Flydrive-backed store when true streaming persistence is required.\n */\nconst drainStream = async (stream: ReadableStream<Uint8Array>): Promise<Uint8Array> => {\n  const chunks: Uint8Array[] = []\n  let total = 0\n  const reader = stream.getReader()\n  try {\n    for (;;) {\n      const { done, value } = await reader.read()\n      if (done) break\n      if (value) {\n        chunks.push(value)\n        total += value.byteLength\n      }\n    }\n  } finally {\n    reader.releaseLock()\n  }\n  const out = new Uint8Array(total)\n  let offset = 0\n  for (const chunk of chunks) {\n    out.set(chunk, offset)\n    offset += chunk.byteLength\n  }\n  return out\n}\n\n/**\n * In-memory \"give bytes, get a reader\" persistence layer keyed by `callId`.\n *\n * @remarks\n * Stores each value byte-faithfully as a `Uint8Array`. `string` inputs are encoded as UTF-8;\n * `Uint8Array` inputs are held verbatim (no lossy text round-trip, so binary payloads survive\n * intact); `ReadableStream<Uint8Array>` inputs are drained fully into a buffer — in-memory storage\n * cannot stream to disk, so the stream form resolves asynchronously and is the documented\n * trade-off for this battery.\n *\n * Each `write()` and each `read()` returns a *fresh* {@link InMemorySpoolReader} — the store\n * owns the bytes, the reader is a view. Mutating the store after handing out a reader does not\n * invalidate the reader.\n *\n * Implements {@link @nhtio/adk/common!SpoolStore} (i.e. `ByteStore<SpoolReader>`).\n *\n * @example\n * ```ts\n * const store = new InMemorySpoolStore()\n * const bytes = await tool.executor(ctx)(args)\n * const reader = await store.write(callId, bytes)\n * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact\n * const artifact = new Ctor(reader)\n * ```\n */\nexport class InMemorySpoolStore implements SpoolStore {\n  readonly #entries = new Map<string, Uint8Array>()\n\n  /**\n   * Persists `bytes` under `callId` and returns a reader over them.\n   *\n   * @remarks\n   * `string` input is encoded as UTF-8; `Uint8Array` is stored byte-faithfully;\n   * `ReadableStream<Uint8Array>` is drained fully (and `write` returns a `Promise`). Re-writing the\n   * same `callId` replaces the prior entry; readers handed out before the rewrite continue to view\n   * the old bytes (they hold their own snapshot via the `InMemorySpoolReader` constructor).\n   *\n   * @param callId - Identifier used to retrieve the bytes via {@link InMemorySpoolStore.read}.\n   * @param bytes - The bytes to store, as a `string`, `Uint8Array`, or `ReadableStream<Uint8Array>`.\n   * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes — a `Promise` for stream\n   *   input, synchronous otherwise.\n   */\n  write(callId: string, bytes: string): InMemorySpoolReader\n  write(callId: string, bytes: Uint8Array): InMemorySpoolReader\n  write(callId: string, bytes: ReadableStream<Uint8Array>): Promise<InMemorySpoolReader>\n  write(\n    callId: string,\n    bytes: string | Uint8Array | ReadableStream<Uint8Array>\n  ): InMemorySpoolReader | Promise<InMemorySpoolReader>\n  write(\n    callId: string,\n    bytes: string | Uint8Array | ReadableStream<Uint8Array>\n  ): InMemorySpoolReader | Promise<InMemorySpoolReader> {\n    if (isInstanceOf(bytes, 'ReadableStream', ReadableStream)) {\n      return drainStream(bytes).then((buffer) => {\n        this.#entries.set(callId, buffer)\n        return new InMemorySpoolReader(buffer)\n      })\n    }\n    const buffer = typeof bytes === 'string' ? new TextEncoder().encode(bytes) : bytes\n    this.#entries.set(callId, buffer)\n    return new InMemorySpoolReader(buffer)\n  }\n\n  /**\n   * Returns a reader over the bytes previously written under `callId`, or `undefined` if the\n   * entry has not been written or has been deleted.\n   *\n   * @param callId - Identifier supplied to a prior {@link InMemorySpoolStore.write} call.\n   * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes, or `undefined`.\n   */\n  read(callId: string): InMemorySpoolReader | undefined {\n    const buffer = this.#entries.get(callId)\n    if (buffer === undefined) return undefined\n    return new InMemorySpoolReader(buffer)\n  }\n\n  /**\n   * Removes the entry under `callId`.\n   *\n   * @param callId - Identifier whose entry should be removed.\n   * @returns `true` if an entry existed and was removed; `false` otherwise.\n   */\n  delete(callId: string): boolean {\n    return this.#entries.delete(callId)\n  }\n\n  /**\n   * Removes every entry from the store.\n   *\n   * @remarks\n   * Existing readers handed out by prior `write()` / `read()` calls remain valid — they hold\n   * their own snapshot.\n   */\n  clear(): void {\n    this.#entries.clear()\n  }\n\n  /**\n   * Returns the number of entries currently in the store.\n   */\n  get size(): number {\n    return this.#entries.size\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,6BAA6B;;;;;;;;;;;;;;;;;;;AAoB1C,IAAa,sBAAb,MAAwD;CACtD;CACA;CACA;CAEA,YAAY,SAA8B;EACxC,IAAI,OAAO,YAAY,UAAU;GAC/B,KAAKA,WAAW;GAChB,KAAKE,SAAS,IAAI,YAAY,EAAE,OAAO,OAAO,EAAE;EAClD,OAAO;GACL,KAAKF,WAAW,IAAI,YAAY,EAAE,OAAO,OAAO;GAChD,KAAKE,SAAS,QAAQ;EACxB;EACA,KAAKD,SAAS,KAAKD,aAAa,KAAK,CAAC,IAAI,KAAKA,SAAS,MAAM,IAAI;CACpE;CAEA,KAAK,OAAmC;EACtC,OAAO,KAAKC,OAAO;CACrB;CAEA,aAAqB;EACnB,OAAO,KAAKC;CACd;CAEA,YAAoB;EAClB,OAAO,KAAKD,OAAO;CACrB;CAEA,UAAkB;EAChB,OAAO,KAAKD;CACd;CAEA,WAA6B;EAE3B,OAAO;GAAE,KAAK;GAA4B,SAAS,EAAE,SAAS,KAAKA,SAAS;EAAE;CAChF;AACF;;;;;;;;;AAUA,IAAM,cAAc,OAAO,WAA4D;CACrF,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,MAAM,SAAS,OAAO,UAAU;CAChC,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;GACV,IAAI,OAAO;IACT,OAAO,KAAK,KAAK;IACjB,SAAS,MAAM;GACjB;EACF;CACF,UAAU;EACR,OAAO,YAAY;CACrB;CACA,MAAM,MAAM,IAAI,WAAW,KAAK;CAChC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,IAAI,OAAO,MAAM;EACrB,UAAU,MAAM;CAClB;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,IAAa,qBAAb,MAAsD;CACpD,2BAAoB,IAAI,IAAwB;CAuBhD,MACE,QACA,OACoD;EACpD,IAAI,aAAa,OAAO,kBAAkB,cAAc,GACtD,OAAO,YAAY,KAAK,EAAE,MAAM,WAAW;GACzC,KAAKG,SAAS,IAAI,QAAQ,MAAM;GAChC,OAAO,IAAI,oBAAoB,MAAM;EACvC,CAAC;EAEH,MAAM,SAAS,OAAO,UAAU,WAAW,IAAI,YAAY,EAAE,OAAO,KAAK,IAAI;EAC7E,KAAKA,SAAS,IAAI,QAAQ,MAAM;EAChC,OAAO,IAAI,oBAAoB,MAAM;CACvC;;;;;;;;CASA,KAAK,QAAiD;EACpD,MAAM,SAAS,KAAKA,SAAS,IAAI,MAAM;EACvC,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;EACjC,OAAO,IAAI,oBAAoB,MAAM;CACvC;;;;;;;CAQA,OAAO,QAAyB;EAC9B,OAAO,KAAKA,SAAS,OAAO,MAAM;CACpC;;;;;;;;CASA,QAAc;EACZ,KAAKA,SAAS,MAAM;CACtB;;;;CAKA,IAAI,OAAe;EACjB,OAAO,KAAKA,SAAS;CACvB;AACF"}