{"version":3,"file":"fs_workspace.cjs","names":[],"sources":["../../../../src/batteries/media/engines/fs_workspace.ts"],"sourcesContent":["/**\n * A {@link @nhtio/adk/batteries/media/contracts!ScratchWorkspace} implementation backed by the\n * local filesystem via `node:fs/promises`.\n *\n * @module @nhtio/adk/batteries/media/engines/fs_workspace\n *\n * @remarks\n * The bundled filesystem workspace. `node:fs/promises` is acquired through an async resolver\n * (default: a lazy dynamic import) so this module carries no static `node:*` import and the\n * builtin loads only when a workspace is actually minted.\n *\n * The factory requires an explicit `root` directory — there is no `os.tmpdir()` silent\n * default. Each minted workspace is a fresh unique subdirectory of `root`, removed on\n * `dispose()`. Any BYO implementation whose paths the paired `BinaryExecutor` can open\n * satisfies the same contract (tmpfs mounts, container volumes, sandbox dirs…).\n */\n\nimport { isError, isInstanceOf } from '@nhtio/adk/guards'\nimport { E_INVALID_MEDIA_PIPELINE_CONFIG } from '../exceptions'\nimport type { ScratchWorkspace, ScratchWorkspaceFactory } from '../contracts'\n\n/** The slice of `node:fs/promises` the workspace uses. */\nexport interface FsLike {\n  /** Create a directory (recursively). */\n  mkdir(path: string, options: { recursive: true }): Promise<unknown>\n  /** Write bytes to a path. */\n  writeFile(path: string, data: Uint8Array): Promise<void>\n  /** Read a file's bytes. */\n  readFile(path: string): Promise<Uint8Array>\n  /** List directory entries (basenames). */\n  readdir(path: string): Promise<string[]>\n  /** Remove a path recursively, ignoring absence. */\n  rm(path: string, options: { recursive: true; force: true }): Promise<void>\n}\n\n/** Resolver forms accepted for the fs module. */\nexport type FsResolver = FsLike | (() => FsLike | Promise<FsLike>)\n\n/** Options for {@link fsScratchWorkspace}. */\nexport interface FsScratchWorkspaceOptions {\n  /** The directory all workspaces are minted under. Required — no platform default. */\n  root: string\n  /** The fs module or a resolver for it. Defaults to a lazy `import('node:fs/promises')`. */\n  fs?: FsResolver\n}\n\nconst resolveFs = async (supplied: FsResolver | undefined): Promise<FsLike> => {\n  let value: unknown = supplied ?? ((): Promise<FsLike> => import('node:fs/promises'))\n  if (typeof value === 'function') {\n    try {\n      value = await (value as () => unknown)()\n    } catch (err) {\n      const detail = isError(err) ? err.message : String(err)\n      throw new E_INVALID_MEDIA_PIPELINE_CONFIG([`fs resolver failed: ${detail}`])\n    }\n  }\n  if (\n    value === null ||\n    typeof value !== 'object' ||\n    typeof (value as FsLike).writeFile !== 'function'\n  ) {\n    throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n      'fs resolver did not resolve to a node:fs/promises-compatible module',\n    ])\n  }\n  return value as FsLike\n}\n\nlet counter = 0\nconst uniqueName = (): string => {\n  counter = (counter + 1) % Number.MAX_SAFE_INTEGER\n  const rand = Array.from(crypto.getRandomValues(new Uint8Array(6)))\n    .map((b) => b.toString(16).padStart(2, '0'))\n    .join('')\n  return `media-${rand}-${counter}`\n}\n\nconst joinPath = (...parts: string[]): string => parts.join('/').replace(/\\/{2,}/g, '/')\n\n/**\n * Construct a {@link ScratchWorkspaceFactory} minting per-invocation directories under `root`.\n *\n * @param options - The root directory and fs resolver.\n * @returns A factory binary engines call once per invocation.\n */\nexport const fsScratchWorkspace = (options: FsScratchWorkspaceOptions): ScratchWorkspaceFactory => {\n  if (typeof options?.root !== 'string' || options.root.length === 0) {\n    throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n      'fsScratchWorkspace requires an explicit root directory (no platform default)',\n    ])\n  }\n  let fsPromise: Promise<FsLike> | undefined\n  const getFs = (): Promise<FsLike> => {\n    fsPromise ??= resolveFs(options.fs)\n    return fsPromise\n  }\n\n  return async (): Promise<ScratchWorkspace> => {\n    const fs = await getFs()\n    const dir = joinPath(options.root, uniqueName())\n    await fs.mkdir(dir, { recursive: true })\n    return {\n      async materialize(bytes: Uint8Array, filename: string): Promise<string> {\n        const safe = filename.replace(/[/\\\\]/g, '_')\n        const path = joinPath(dir, safe)\n        await fs.writeFile(path, bytes)\n        return path\n      },\n      async read(path: string): Promise<Uint8Array> {\n        const bytes = await fs.readFile(path)\n        return isInstanceOf(bytes, 'Uint8Array', Uint8Array) ? bytes : new Uint8Array(bytes)\n      },\n      dir(): string {\n        return dir\n      },\n      async list(): Promise<string[]> {\n        return fs.readdir(dir)\n      },\n      async dispose(): Promise<void> {\n        await fs.rm(dir, { recursive: true, force: true })\n      },\n    }\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA8CA,IAAM,YAAY,OAAO,aAAsD;CAC7E,IAAI,QAAiB,mBAAoC,OAAO;CAChE,IAAI,OAAO,UAAU,YACnB,IAAI;EACF,QAAQ,MAAO,MAAwB;CACzC,SAAS,KAAK;EAEZ,MAAM,IAAI,mBAAA,gCAAgC,CAAC,uBAD5B,eAAA,QAAQ,GAAG,IAAI,IAAI,UAAU,OAAO,GAAG,GACoB,CAAC;CAC7E;CAEF,IACE,UAAU,QACV,OAAO,UAAU,YACjB,OAAQ,MAAiB,cAAc,YAEvC,MAAM,IAAI,mBAAA,gCAAgC,CACxC,qEACF,CAAC;CAEH,OAAO;AACT;AAEA,IAAI,UAAU;AACd,IAAM,mBAA2B;CAC/B,WAAW,UAAU,KAAK,OAAO;CAIjC,OAAO,SAHM,MAAM,KAAK,OAAO,gBAAgB,IAAI,WAAW,CAAC,CAAC,CAAC,EAC9D,KAAK,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EACQ,EAAK,GAAG;AAC1B;AAEA,IAAM,YAAY,GAAG,UAA4B,MAAM,KAAK,GAAG,EAAE,QAAQ,WAAW,GAAG;;;;;;;AAQvF,IAAa,sBAAsB,YAAgE;CACjG,IAAI,OAAO,SAAS,SAAS,YAAY,QAAQ,KAAK,WAAW,GAC/D,MAAM,IAAI,mBAAA,gCAAgC,CACxC,8EACF,CAAC;CAEH,IAAI;CACJ,MAAM,cAA+B;EACnC,cAAc,UAAU,QAAQ,EAAE;EAClC,OAAO;CACT;CAEA,OAAO,YAAuC;EAC5C,MAAM,KAAK,MAAM,MAAM;EACvB,MAAM,MAAM,SAAS,QAAQ,MAAM,WAAW,CAAC;EAC/C,MAAM,GAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;EACvC,OAAO;GACL,MAAM,YAAY,OAAmB,UAAmC;IAEtE,MAAM,OAAO,SAAS,KADT,SAAS,QAAQ,UAAU,GACb,CAAI;IAC/B,MAAM,GAAG,UAAU,MAAM,KAAK;IAC9B,OAAO;GACT;GACA,MAAM,KAAK,MAAmC;IAC5C,MAAM,QAAQ,MAAM,GAAG,SAAS,IAAI;IACpC,OAAO,eAAA,aAAa,OAAO,cAAc,UAAU,IAAI,QAAQ,IAAI,WAAW,KAAK;GACrF;GACA,MAAc;IACZ,OAAO;GACT;GACA,MAAM,OAA0B;IAC9B,OAAO,GAAG,QAAQ,GAAG;GACvB;GACA,MAAM,UAAyB;IAC7B,MAAM,GAAG,GAAG,KAAK;KAAE,WAAW;KAAM,OAAO;IAAK,CAAC;GACnD;EACF;CACF;AACF"}