{"version":3,"file":"image-preview.d.ts","sourceRoot":"","sources":["../../src/server/image-preview.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAE5C,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,WAAW,GAAG,YAAY,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAmBD,2EAA2E;AAC3E,qBAAa,kBAAmB,YAAW,qBAAqB;IAU9D,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,SAAS;IAV3B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAGpB;IAEJ;IACC,iFAAiF;IAChE,SAAS,oBAAwD,EAC/E;IAEE,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAUlF;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ3B;IAED,OAAO,CAAC,SAAS;CA8BjB","sourcesContent":["import { Worker } from \"node:worker_threads\";\n\nexport const MAX_PREVIEW_WIDTH = 1024;\nexport const MAX_PREVIEW_HEIGHT = 1024;\nexport const MAX_PREVIEW_BYTES = 256 * 1024;\n\nexport interface GeneratedImagePreview {\n\tbytes: Uint8Array;\n\tmimeType: \"image/png\" | \"image/jpeg\";\n\twidth: number;\n\theight: number;\n}\n\nexport interface ImagePreviewGenerator {\n\tgenerate(bytes: Uint8Array, mimeType: string): Promise<GeneratedImagePreview>;\n\tclose(): Promise<void>;\n}\n\ninterface WorkerSuccess {\n\tid: number;\n\tok: true;\n\tbytes: ArrayBuffer;\n\tmimeType: \"image/png\" | \"image/jpeg\";\n\twidth: number;\n\theight: number;\n}\n\ninterface WorkerFailure {\n\tid: number;\n\tok: false;\n\terror: string;\n}\n\ntype WorkerReply = WorkerSuccess | WorkerFailure;\n\n/** One worker keeps Photon decode/resize work off the ordered SSE path. */\nexport class ImagePreviewWorker implements ImagePreviewGenerator {\n\tprivate worker: Worker | undefined;\n\tprivate nextId = 1;\n\tprivate closed = false;\n\tprivate readonly pending = new Map<\n\t\tnumber,\n\t\t{ resolve: (preview: GeneratedImagePreview) => void; reject: (error: Error) => void }\n\t>();\n\n\tconstructor(\n\t\t/** Injectable worker URL keeps abnormal-exit handling deterministic in tests. */\n\t\tprivate readonly workerUrl = new URL(\"./image-preview-worker.js\", import.meta.url),\n\t) {}\n\n\tasync generate(bytes: Uint8Array, mimeType: string): Promise<GeneratedImagePreview> {\n\t\tif (this.closed) throw new Error(\"Image preview worker is closed\");\n\t\tconst worker = this.getWorker();\n\t\tconst id = this.nextId++;\n\t\t// Do not detach repository-owned storage when transferring to the worker.\n\t\tconst copy = Uint8Array.from(bytes);\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.pending.set(id, { resolve, reject });\n\t\t\tworker.postMessage({ id, bytes: copy.buffer, mimeType }, [copy.buffer]);\n\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tfor (const pending of this.pending.values()) pending.reject(new Error(\"Image preview worker closed\"));\n\t\tthis.pending.clear();\n\t\tconst worker = this.worker;\n\t\tthis.worker = undefined;\n\t\tif (worker) await worker.terminate();\n\t}\n\n\tprivate getWorker(): Worker {\n\t\tif (this.worker) return this.worker;\n\t\tconst worker = new Worker(this.workerUrl);\n\t\tworker.on(\"message\", (reply: WorkerReply) => {\n\t\t\tconst pending = this.pending.get(reply.id);\n\t\t\tif (!pending) return;\n\t\t\tthis.pending.delete(reply.id);\n\t\t\tif (!reply.ok) {\n\t\t\t\tpending.reject(new Error(reply.error));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpending.resolve({\n\t\t\t\tbytes: new Uint8Array(reply.bytes),\n\t\t\t\tmimeType: reply.mimeType,\n\t\t\t\twidth: reply.width,\n\t\t\t\theight: reply.height,\n\t\t\t});\n\t\t});\n\t\tconst fail = (error: Error) => {\n\t\t\tfor (const pending of this.pending.values()) pending.reject(error);\n\t\t\tthis.pending.clear();\n\t\t\tthis.worker = undefined;\n\t\t};\n\t\tworker.on(\"error\", fail);\n\t\tworker.on(\"exit\", (code) => {\n\t\t\tif (!this.closed && code !== 0) fail(new Error(`Image preview worker exited with code ${code}`));\n\t\t});\n\t\tthis.worker = worker;\n\t\treturn worker;\n\t}\n}\n"]}