@ny4n/durable 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +711 -2
- package/dist/documents.d.ts +66 -0
- package/dist/documents.d.ts.map +1 -0
- package/dist/documents.js +98 -0
- package/dist/documents.js.map +1 -0
- package/dist/entries.d.ts +31 -0
- package/dist/entries.d.ts.map +1 -0
- package/dist/entries.js +31 -0
- package/dist/entries.js.map +1 -0
- package/dist/env/decode.d.ts +16 -0
- package/dist/env/decode.d.ts.map +1 -0
- package/dist/env/decode.js +28 -0
- package/dist/env/decode.js.map +1 -0
- package/dist/env/index.d.ts +263 -0
- package/dist/env/index.d.ts.map +1 -0
- package/dist/env/index.js +49 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/line-scan.d.ts +13 -0
- package/dist/env/line-scan.d.ts.map +1 -0
- package/dist/env/line-scan.js +147 -0
- package/dist/env/line-scan.js.map +1 -0
- package/dist/env/node-watch.d.ts +29 -0
- package/dist/env/node-watch.d.ts.map +1 -0
- package/dist/env/node-watch.js +485 -0
- package/dist/env/node-watch.js.map +1 -0
- package/dist/env/node.d.ts +56 -0
- package/dist/env/node.d.ts.map +1 -0
- package/dist/env/node.js +1166 -0
- package/dist/env/node.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/errors.js.map +1 -0
- package/dist/harness/agent.d.ts +33 -0
- package/dist/harness/agent.d.ts.map +1 -0
- package/dist/harness/agent.js +235 -0
- package/dist/harness/agent.js.map +1 -0
- package/dist/harness/assistant.d.ts +15 -0
- package/dist/harness/assistant.d.ts.map +1 -0
- package/dist/harness/assistant.js +23 -0
- package/dist/harness/assistant.js.map +1 -0
- package/dist/harness/compaction.d.ts +56 -0
- package/dist/harness/compaction.d.ts.map +1 -0
- package/dist/harness/compaction.js +384 -0
- package/dist/harness/compaction.js.map +1 -0
- package/dist/harness/context.d.ts +63 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +221 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/define.d.ts +18 -0
- package/dist/harness/define.d.ts.map +1 -0
- package/dist/harness/define.js +25 -0
- package/dist/harness/define.js.map +1 -0
- package/dist/harness/events.d.ts +180 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +370 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/generation.d.ts +54 -0
- package/dist/harness/generation.d.ts.map +1 -0
- package/dist/harness/generation.js +514 -0
- package/dist/harness/generation.js.map +1 -0
- package/dist/harness/harness.d.ts +17 -0
- package/dist/harness/harness.d.ts.map +1 -0
- package/dist/harness/harness.js +332 -0
- package/dist/harness/harness.js.map +1 -0
- package/dist/harness/inbox.d.ts +59 -0
- package/dist/harness/inbox.d.ts.map +1 -0
- package/dist/harness/inbox.js +97 -0
- package/dist/harness/inbox.js.map +1 -0
- package/dist/harness/json.d.ts +10 -0
- package/dist/harness/json.d.ts.map +1 -0
- package/dist/harness/json.js +33 -0
- package/dist/harness/json.js.map +1 -0
- package/dist/harness/live.d.ts +142 -0
- package/dist/harness/live.d.ts.map +1 -0
- package/dist/harness/live.js +149 -0
- package/dist/harness/live.js.map +1 -0
- package/dist/harness/output.d.ts +69 -0
- package/dist/harness/output.d.ts.map +1 -0
- package/dist/harness/output.js +318 -0
- package/dist/harness/output.js.map +1 -0
- package/dist/harness/prompt.d.ts +27 -0
- package/dist/harness/prompt.d.ts.map +1 -0
- package/dist/harness/prompt.js +131 -0
- package/dist/harness/prompt.js.map +1 -0
- package/dist/harness/provider.d.ts +14 -0
- package/dist/harness/provider.d.ts.map +1 -0
- package/dist/harness/provider.js +30 -0
- package/dist/harness/provider.js.map +1 -0
- package/dist/harness/registry.d.ts +6 -0
- package/dist/harness/registry.d.ts.map +1 -0
- package/dist/harness/registry.js +101 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/run.d.ts +17 -0
- package/dist/harness/run.d.ts.map +1 -0
- package/dist/harness/run.js +26 -0
- package/dist/harness/run.js.map +1 -0
- package/dist/harness/scheduler.d.ts +106 -0
- package/dist/harness/scheduler.d.ts.map +1 -0
- package/dist/harness/scheduler.js +1743 -0
- package/dist/harness/scheduler.js.map +1 -0
- package/dist/harness/submissions.d.ts +29 -0
- package/dist/harness/submissions.d.ts.map +1 -0
- package/dist/harness/submissions.js +175 -0
- package/dist/harness/submissions.js.map +1 -0
- package/dist/harness/task-graph.d.ts +49 -0
- package/dist/harness/task-graph.d.ts.map +1 -0
- package/dist/harness/task-graph.js +168 -0
- package/dist/harness/task-graph.js.map +1 -0
- package/dist/harness/tool.d.ts +76 -0
- package/dist/harness/tool.d.ts.map +1 -0
- package/dist/harness/tool.js +736 -0
- package/dist/harness/tool.js.map +1 -0
- package/dist/harness/types.d.ts +713 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/usage.d.ts +18 -0
- package/dist/harness/usage.d.ts.map +1 -0
- package/dist/harness/usage.js +57 -0
- package/dist/harness/usage.js.map +1 -0
- package/dist/harness/util.d.ts +19 -0
- package/dist/harness/util.d.ts.map +1 -0
- package/dist/harness/util.js +58 -0
- package/dist/harness/util.js.map +1 -0
- package/dist/harness/view.d.ts +47 -0
- package/dist/harness/view.d.ts.map +1 -0
- package/dist/harness/view.js +206 -0
- package/dist/harness/view.js.map +1 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +9 -0
- package/dist/ids.js.map +1 -0
- package/dist/images/cloudflare.d.ts +4 -0
- package/dist/images/cloudflare.d.ts.map +1 -0
- package/dist/images/cloudflare.js +9 -0
- package/dist/images/cloudflare.js.map +1 -0
- package/dist/images/exif.d.ts +3 -0
- package/dist/images/exif.d.ts.map +1 -0
- package/dist/images/exif.js +71 -0
- package/dist/images/exif.js.map +1 -0
- package/dist/images/index.d.ts +18 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +125 -0
- package/dist/images/index.js.map +1 -0
- package/dist/images/node-worker.d.ts +17 -0
- package/dist/images/node-worker.d.ts.map +1 -0
- package/dist/images/node-worker.js +16 -0
- package/dist/images/node-worker.js.map +1 -0
- package/dist/images/node.d.ts +21 -0
- package/dist/images/node.d.ts.map +1 -0
- package/dist/images/node.js +121 -0
- package/dist/images/node.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/session/forks.d.ts +10 -0
- package/dist/session/forks.d.ts.map +1 -0
- package/dist/session/forks.js +42 -0
- package/dist/session/forks.js.map +1 -0
- package/dist/session/observation.d.ts +42 -0
- package/dist/session/observation.d.ts.map +1 -0
- package/dist/session/observation.js +281 -0
- package/dist/session/observation.js.map +1 -0
- package/dist/session/session.d.ts +133 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +811 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/transaction.d.ts +119 -0
- package/dist/session/transaction.d.ts.map +1 -0
- package/dist/session/transaction.js +795 -0
- package/dist/session/transaction.js.map +1 -0
- package/dist/storage/jsonl/index.d.ts +2 -0
- package/dist/storage/jsonl/index.d.ts.map +1 -0
- package/dist/storage/jsonl/index.js +2 -0
- package/dist/storage/jsonl/index.js.map +1 -0
- package/dist/storage/jsonl/node.d.ts +6 -0
- package/dist/storage/jsonl/node.d.ts.map +1 -0
- package/dist/storage/jsonl/node.js +8 -0
- package/dist/storage/jsonl/node.js.map +1 -0
- package/dist/storage/jsonl/storage.d.ts +143 -0
- package/dist/storage/jsonl/storage.d.ts.map +1 -0
- package/dist/storage/jsonl/storage.js +682 -0
- package/dist/storage/jsonl/storage.js.map +1 -0
- package/dist/storage/memory.d.ts +62 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +733 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/scan.d.ts +18 -0
- package/dist/storage/scan.d.ts.map +1 -0
- package/dist/storage/scan.js +29 -0
- package/dist/storage/scan.js.map +1 -0
- package/dist/storage/sqlite/cloudflare.d.ts +46 -0
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
- package/dist/storage/sqlite/cloudflare.js +106 -0
- package/dist/storage/sqlite/cloudflare.js.map +1 -0
- package/dist/storage/sqlite/database.d.ts +37 -0
- package/dist/storage/sqlite/database.d.ts.map +1 -0
- package/dist/storage/sqlite/database.js +2 -0
- package/dist/storage/sqlite/database.js.map +1 -0
- package/dist/storage/sqlite/index.d.ts +4 -0
- package/dist/storage/sqlite/index.d.ts.map +1 -0
- package/dist/storage/sqlite/index.js +3 -0
- package/dist/storage/sqlite/index.js.map +1 -0
- package/dist/storage/sqlite/migrations.d.ts +11 -0
- package/dist/storage/sqlite/migrations.d.ts.map +1 -0
- package/dist/storage/sqlite/migrations.js +111 -0
- package/dist/storage/sqlite/migrations.js.map +1 -0
- package/dist/storage/sqlite/node.d.ts +41 -0
- package/dist/storage/sqlite/node.d.ts.map +1 -0
- package/dist/storage/sqlite/node.js +179 -0
- package/dist/storage/sqlite/node.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +65 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -0
- package/dist/storage/sqlite/storage.js +670 -0
- package/dist/storage/sqlite/storage.js.map +1 -0
- package/dist/tasks.d.ts +6 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +5 -0
- package/dist/tasks.js.map +1 -0
- package/dist/testing/assertions.d.ts +5 -0
- package/dist/testing/assertions.d.ts.map +1 -0
- package/dist/testing/assertions.js +25 -0
- package/dist/testing/assertions.js.map +1 -0
- package/dist/testing/env-conformance.d.ts +7 -0
- package/dist/testing/env-conformance.d.ts.map +1 -0
- package/dist/testing/env-conformance.js +431 -0
- package/dist/testing/env-conformance.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/runner.d.ts +12 -0
- package/dist/testing/runner.d.ts.map +1 -0
- package/dist/testing/runner.js +23 -0
- package/dist/testing/runner.js.map +1 -0
- package/dist/testing/storage-benchmark.d.ts +41 -0
- package/dist/testing/storage-benchmark.d.ts.map +1 -0
- package/dist/testing/storage-benchmark.js +350 -0
- package/dist/testing/storage-benchmark.js.map +1 -0
- package/dist/testing/storage-conformance.d.ts +4 -0
- package/dist/testing/storage-conformance.d.ts.map +1 -0
- package/dist/testing/storage-conformance.js +1213 -0
- package/dist/testing/storage-conformance.js.map +1 -0
- package/dist/testing/types.d.ts +37 -0
- package/dist/testing/types.d.ts.map +1 -0
- package/dist/testing/types.js +2 -0
- package/dist/testing/types.js.map +1 -0
- package/dist/tools/bash.d.ts +55 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +138 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit-diff.d.ts +89 -0
- package/dist/tools/edit-diff.d.ts.map +1 -0
- package/dist/tools/edit-diff.js +386 -0
- package/dist/tools/edit-diff.js.map +1 -0
- package/dist/tools/edit.d.ts +18 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +117 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/env.d.ts +5 -0
- package/dist/tools/env.d.ts.map +1 -0
- package/dist/tools/env.js +7 -0
- package/dist/tools/env.js.map +1 -0
- package/dist/tools/file-mutation-queue.d.ts +9 -0
- package/dist/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/tools/file-mutation-queue.js +52 -0
- package/dist/tools/file-mutation-queue.js.map +1 -0
- package/dist/tools/image-processor.d.ts +47 -0
- package/dist/tools/image-processor.d.ts.map +1 -0
- package/dist/tools/image-processor.js +30 -0
- package/dist/tools/image-processor.js.map +1 -0
- package/dist/tools/image.d.ts +20 -0
- package/dist/tools/image.d.ts.map +1 -0
- package/dist/tools/image.js +178 -0
- package/dist/tools/image.js.map +1 -0
- package/dist/tools/index.d.ts +46 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/path-utils.d.ts +5 -0
- package/dist/tools/path-utils.d.ts.map +1 -0
- package/dist/tools/path-utils.js +26 -0
- package/dist/tools/path-utils.js.map +1 -0
- package/dist/tools/read.d.ts +25 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +235 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +31 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/truncate.d.ts +64 -0
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +154 -0
- package/dist/truncate.js.map +1 -0
- package/dist/types.d.ts +932 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +140 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/harness/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8D,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAyC,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EAAE,SAAS,EAAoB,MAAM,uBAAuB,CAAC;AAYpE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAyBxC,MAAM,OAAO,GAAG;IACf,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,WAAW;IACX,QAAQ;CACkD,CAAC;AAC5D,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;AAUlG,MAAM,KAAK,GAAG,IAAI,OAAO,EAA6B,CAAC;AAEvD,gEAAgE;AAChE,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IAC1D,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,iBAAiB;IACpB,QAAQ,CAAc;IACtB,QAAQ,CAAU;IAClB,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACpD,OAAO,GAAG,KAAK,CAAC;IAEhB,YAAY,OAAoB,EAAE,OAAgB;QACjD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE;YAC/C,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO;gBACrC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE;YAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;gBACxC,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC7C,SAAS,CACR,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAClD,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAC7B,CAAC;gBACH,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,+DAA+D;IAC/D,MAAM,CAAC,KAAc;QACpB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,sDAAsD;IACtD,KAAK,CAAC,KAAK,CAAC,EAAkB,EAAE,OAAgB;QAC/C,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAC7C,EAAE,EACF,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,oBAAoB,CAAmB,KAAK,EAAE,OAAO,CAAC,EAC9E,OAAO,CACP,CAAC;QACF,IAAI,CAAC;YACJ,OAAO,eAAe,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,EAAE,CAAC;YACT,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,KAAK,CAAC,EAAkB,EAAE,OAAgB;QAC/C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CACrC,EAAE,EACF,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAClB,IAAI,cAAc,CAAmB,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAC7F,OAAO,CACP,CAAC;QACF,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;QACnC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACrB,QAAQ,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,MAAM,CAAC,MAAM,CAAC;QACrB,CAAC;QACD,IAAI,MAAM,KAAK,SAAS;YAAE,QAAQ,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACH,MAAM,CACL,EAAkB,EAClB,MAA0F,EAC1F,OAAgB;QAEhB,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;YACvE,MAAM,MAAM,GAAG,GAAS,EAAE;gBACzB,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACjC,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,KAAK;oBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3F,CAAC,CAAC;YACF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAClE,mFAAmF;YACnF,IAAI,IAAI,CAAC,OAAO;gBAAE,MAAM,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnD,OAAO,CAAC,WAAW,EAAE,cAAc,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC5B,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAkB,EAAE,OAAgB;QAChD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACnE,IAAI,YAAY,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAC;QACrF,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACxE,MAAM,IAAI,GAA+B,EAAE,CAAC;QAC5C,MAAM,YAAY,GAAG,IAAI,GAAG,EAA+C,CAAC;QAC5E,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,0BAA0B,CAAC,KAAK,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;YAClF,IAAI,MAAM,KAAK,SAAS;gBAAE,SAAS;YACnC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;YAC3C,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IAC7F,CAAC;CACD;AAED;;;GAGG;AACH,SAAS,OAAO,CACf,EAAkB,EAClB,KAAY,EACZ,WAA8B,EAC9B,OAAgB,EAChB,MAAgC;IAEhC,MAAM,MAAM,GAAS,EAAE,CAAC;IACxB,MAAM,QAAQ,GAAS,EAAE,CAAC;IAC1B,IAAI,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;IAClC,0CAA0C;IAC1C,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,cAAc,KAAK,EAAE,EAAE,CAAC;YACnE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;YAC3B,MAAM,KAAK,GAAG,KAA6B,CAAC;YAC5C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC9D,OAAO,GAAG,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,CAAC;gBAC9B,SAAS;YACV,CAAC;YACD,wGAAwG;YACxG,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC;YAC1B,IAAI,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,SAAS,IAAI,SAAS,CAAC,EAAE,IAAI,MAAM,CAAC,CAAC;YACpG,IAAI,IAAI,GAAG,CAAC;gBAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;YACpC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACpD,OAAO,GAAG,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1C,SAAS;QACV,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,IAAI,MAAM,CAAC,cAAc,KAAK,EAAE;YAAE,SAAS;QACzE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QAChC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS;YAAE,SAAS;QAC1E,MAAM,IAAI,GAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YAC3B,IAAI,OAAO,EAAE,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,EAAE;gBAAE,SAAS;YAC/C,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,OAAO,EAAE,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,OAAO,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC;YACnF,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,GAAG;gBAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9D,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAQ,EAAE,CAAC,CAAC;YACzE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACxC,CAAC;IACF,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;IAC3B,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC;IACrC,MAAM,YAAY,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACtF,KAAK,CAAC,KAAK,GAAG,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,CAAC;QACxE,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7C,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7E,CAAC;IACF,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7C,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;IACtG,CAAC;AACF,CAAC;AAED,iFAAiF;AACjF,SAAS,QAAQ,CAAC,EAAM,EAAE,MAAoB;IAC7C,MAAM,EAAE,GAAG,CAAC,IAAU,EAAgB,EAAE,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,CAA4B,CAAC;IACzF,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACf,KAAK,GAAG;YACP,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,KAAK,GAAG;YACP,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,KAAK,GAAG;YACP,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,KAAK,GAAG;YACP,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,KAAK,GAAG;YACP,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzB,KAAK,GAAG;YACP,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,KAAK,GAAG;YACP,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;AACF,CAAC","sourcesContent":["import { type AttachedReplicatedState, type Context, type JsonValue, replicatedState } from \"@ny4n/chord\";\nimport { withoutAbortSignal } from \"@ny4n/chord/context\";\nimport { applyImmutable, type NonEmptyPath, type Op, type Path } from \"@ny4n/chord/delta\";\nimport { CommittedStateSource, CommittedWatch } from \"../session/observation.ts\";\nimport { contained, type SessionImpl } from \"../session/session.ts\";\nimport type {\n\tCommitPublication,\n\tConversationDocToken,\n\tConversationId,\n\tConversationRecord,\n\tDocumentId,\n\tEntryRecord,\n\tJsonObject,\n\tStorage,\n\tWatchHandle,\n} from \"../types.ts\";\nimport { AgentDoc } from \"./agent.ts\";\nimport { activeEntries, captureContextBounds } from \"./context.ts\";\nimport { InboxDoc } from \"./inbox.ts\";\nimport { LiveDoc } from \"./live.ts\";\nimport { ProviderDoc } from \"./provider.ts\";\nimport { UsageDoc } from \"./usage.ts\";\nimport { closedError } from \"./util.ts\";\n\n/** Structural mount of one conversation's active transcript and built-in documents (spec §9.3). */\nexport type ConversationView = {\n\treadonly conversation: ConversationRecord;\n\t/** Raw active entries, as `ContextView.entries`: the head marker, then the non-head entries from its head. */\n\treadonly entries: readonly EntryRecord[];\n\t/** Built-in conversation documents keyed by kind; absent documents are absent. */\n\treadonly docs: Readonly<Record<string, JsonObject>>;\n};\n\n/** Receives each next revision of a mount, and the Session's close. */\nexport type ViewObserver = {\n\tadvance?(value: ConversationView, ops: readonly Op[], context: Context): void;\n\t/** Every publication, after the mount took it; `ops` are the mount's, possibly none. */\n\tpublication?(\n\t\tbefore: ConversationView,\n\t\tafter: ConversationView,\n\t\tops: readonly Op[],\n\t\tpublication: CommitPublication,\n\t\tcontext: Context,\n\t): void;\n\tcloseSession(failure: { readonly error: unknown } | undefined): void;\n};\n\nconst MOUNTED = [\n\tAgentDoc,\n\tLiveDoc,\n\tInboxDoc,\n\tProviderDoc,\n\tUsageDoc,\n] as unknown as readonly ConversationDocToken<JsonObject>[];\nconst MOUNTED_KINDS: ReadonlySet<string> = new Set(MOUNTED.map((token) => token.definition.kind));\n\n/** One conversation's mount: its current revision, the document incarnations it shows, and its observers. */\ntype Mount = {\n\tvalue: ConversationView;\n\t/** Mounted incarnation and definition version per kind; another incarnation or version is set whole. */\n\treadonly docs: Map<string, { id: DocumentId; version: number }>;\n\treadonly observers: Set<ViewObserver>;\n};\n\nconst VIEWS = new WeakMap<object, ConversationViews>();\n\n/** The view mounts of a Harness, for adapters built on them. */\nexport function conversationViews(harness: object): ConversationViews {\n\tconst views = VIEWS.get(harness);\n\tif (views === undefined) throw new Error(\"Not a Harness\");\n\treturn views;\n}\n\n/**\n * The Harness's conversation view mounts: at most one per conversation, built on the Session line by its first\n * observer and dropped with its last. Each mount advances from the Session's commit publications, which are durable.\n */\nexport class ConversationViews {\n\treadonly #session: SessionImpl;\n\treadonly #storage: Storage;\n\treadonly #mounts = new Map<ConversationId, Mount>();\n\t#closed = false;\n\n\tconstructor(session: SessionImpl, storage: Storage) {\n\t\tthis.#session = session;\n\t\tthis.#storage = storage;\n\t\tVIEWS.set(session, this);\n\t\tsession.observeCommits((publication, context) => {\n\t\t\tfor (const [id, mount] of this.#mounts)\n\t\t\t\tadvance(id, mount, publication, context, (error) => this.report(error));\n\t\t});\n\t\tsession.subscribeClose(() => {\n\t\t\tthis.#closed = true;\n\t\t\tfor (const mount of this.#mounts.values())\n\t\t\t\tfor (const observer of [...mount.observers]) {\n\t\t\t\t\tcontained(\n\t\t\t\t\t\t() => observer.closeSession(this.#session.failure),\n\t\t\t\t\t\t(error) => this.report(error),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\tthis.#mounts.clear();\n\t\t});\n\t}\n\n\t/** Report an observer's listener error through the Harness. */\n\treport(error: unknown): void {\n\t\tthis.#session.report(error);\n\t}\n\n\t/** A disposable read-only Chord state of the view. */\n\tasync state(id: ConversationId, context: Context): Promise<AttachedReplicatedState<ConversationView>> {\n\t\tconst { observer, detach } = await this.attach(\n\t\t\tid,\n\t\t\t(value, release) => new CommittedStateSource<ConversationView>(value, release),\n\t\t\tcontext,\n\t\t);\n\t\ttry {\n\t\t\treturn replicatedState(observer, { onError: (error) => this.#session.report(error) });\n\t\t} catch (error) {\n\t\t\tdetach();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/** A serialized exact-frame watch of the view; cancelling `context` stops it. */\n\tasync watch(id: ConversationId, context: Context): Promise<WatchHandle<ConversationView>> {\n\t\tconst { observer } = await this.attach(\n\t\t\tid,\n\t\t\t(value, release) =>\n\t\t\t\tnew CommittedWatch<ConversationView>(value, release, (error) => this.#session.report(error)),\n\t\t\tcontext,\n\t\t);\n\t\tconst signal = context.abortSignal;\n\t\tif (signal?.aborted) {\n\t\t\tobserver.cancel();\n\t\t\tthrow signal.reason;\n\t\t}\n\t\tif (signal !== undefined) observer.observeCancellation(signal);\n\t\treturn observer;\n\t}\n\n\t/**\n\t * Register an observer created from the current revision, atomically on the Session line: it sees every later\n\t * publication and nothing earlier. `create` may read committed Storage, still on the line. `release` drops it, and\n\t * the mount with its last observer.\n\t */\n\tattach<O extends ViewObserver>(\n\t\tid: ConversationId,\n\t\tcreate: (value: ConversationView, release: () => void, storage: Storage) => O | Promise<O>,\n\t\tcontext: Context,\n\t): Promise<{ observer: O; detach: () => void }> {\n\t\treturn this.#session.readOnLine(async () => {\n\t\t\tconst mount = this.#mounts.get(id) ?? (await this.#build(id, context));\n\t\t\tconst detach = (): void => {\n\t\t\t\tmount.observers.delete(observer);\n\t\t\t\tif (mount.observers.size === 0 && this.#mounts.get(id) === mount) this.#mounts.delete(id);\n\t\t\t};\n\t\t\tconst observer = await create(mount.value, detach, this.#storage);\n\t\t\t// Close or cancellation may begin while the mount hydrates; register nothing then.\n\t\t\tif (this.#closed) throw closedError(this.#session);\n\t\t\tcontext.abortSignal?.throwIfAborted();\n\t\t\tthis.#mounts.set(id, mount);\n\t\t\tmount.observers.add(observer);\n\t\t\treturn { observer, detach };\n\t\t});\n\t}\n\n\tasync #build(id: ConversationId, context: Context): Promise<Mount> {\n\t\tconst conversation = await this.#storage.conversation(id, context);\n\t\tif (conversation === undefined) throw new Error(`Conversation ${id} does not exist`);\n\t\tconst bounds = await captureContextBounds(this.#storage, id, context);\n\t\tconst entries = await activeEntries(this.#storage, id, bounds, context);\n\t\tconst docs: Record<string, JsonObject> = {};\n\t\tconst incarnations = new Map<string, { id: DocumentId; version: number }>();\n\t\tfor (const token of MOUNTED) {\n\t\t\tconst loaded = await this.#session.conversationDocumentOnLine(token, id, context);\n\t\t\tif (loaded === undefined) continue;\n\t\t\tdocs[token.definition.kind] = loaded.value;\n\t\t\tincarnations.set(token.definition.kind, { id: loaded.record.id, version: loaded.version });\n\t\t}\n\t\treturn { value: { conversation, entries, docs }, docs: incarnations, observers: new Set() };\n\t}\n}\n\n/**\n * Derive the mount's operations from one publication, apply them, and hand the revision to every observer; one that\n * throws is reported through `report`, and the others still get it.\n */\nfunction advance(\n\tid: ConversationId,\n\tmount: Mount,\n\tpublication: CommitPublication,\n\tcontext: Context,\n\treport: (error: unknown) => void,\n): void {\n\tconst docOps: Op[] = [];\n\tconst entryOps: Op[] = [];\n\tlet entries = mount.value.entries;\n\t// Entry writes are published in ID order.\n\tfor (const change of publication.changes) {\n\t\tif (change.type === \"entry\" && change.value.conversationId === id) {\n\t\t\tconst entry = change.value;\n\t\t\tconst value = entry as unknown as JsonValue;\n\t\t\tif (entry.head === undefined) {\n\t\t\t\tentryOps.push([\"p\", [\"entries\"], entries.length, 0, [value]]);\n\t\t\t\tentries = [...entries, entry];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// A head marker keeps the non-head entries from its head, which are always a suffix, and goes in front.\n\t\t\tconst target = entry.head;\n\t\t\tlet kept = entries.findIndex((candidate) => candidate.head === undefined && candidate.id >= target);\n\t\t\tif (kept < 0) kept = entries.length;\n\t\t\tentryOps.push([\"p\", [\"entries\"], 0, kept, [value]]);\n\t\t\tentries = [entry, ...entries.slice(kept)];\n\t\t\tcontinue;\n\t\t}\n\t\tif (change.type !== \"document\" || change.conversationId !== id) continue;\n\t\tconst kind = change.record.kind;\n\t\tif (!MOUNTED_KINDS.has(kind) || change.record.key !== undefined) continue;\n\t\tconst path: NonEmptyPath = [\"docs\", kind];\n\t\tconst mounted = mount.docs.get(kind);\n\t\tif (change.value === null) {\n\t\t\tif (mounted?.id !== change.record.id) continue;\n\t\t\tmount.docs.delete(kind);\n\t\t\tdocOps.push([\"d\", path]);\n\t\t} else if (mounted?.id === change.record.id && mounted.version === change.version) {\n\t\t\tfor (const op of change.ops) docOps.push(prefixed(op, path));\n\t\t} else {\n\t\t\tmount.docs.set(kind, { id: change.record.id, version: change.version! });\n\t\t\tdocOps.push([\"s\", path, change.value]);\n\t\t}\n\t}\n\tconst before = mount.value;\n\tconst ops = [...docOps, ...entryOps];\n\tconst frameContext = withoutAbortSignal(context);\n\tif (ops.length > 0) {\n\t\tconst value = docOps.length === 0 ? mount.value : applyImmutable(mount.value, docOps);\n\t\tmount.value = entries === value.entries ? value : { ...value, entries };\n\t\tfor (const observer of [...mount.observers]) {\n\t\t\tcontained(() => observer.advance?.(mount.value, ops, frameContext), report);\n\t\t}\n\t}\n\tfor (const observer of [...mount.observers]) {\n\t\tcontained(() => observer.publication?.(before, mount.value, ops, publication, frameContext), report);\n\t}\n}\n\n/** `op` moved under `prefix`; a root replacement becomes a set of the prefix. */\nfunction prefixed(op: Op, prefix: NonEmptyPath): Op {\n\tconst at = (path: Path): NonEmptyPath => [...prefix, ...path] as unknown as NonEmptyPath;\n\tswitch (op[0]) {\n\t\tcase \"r\":\n\t\t\treturn [\"s\", prefix, op[1]];\n\t\tcase \"p\":\n\t\t\treturn [\"p\", at(op[1]), op[2], op[3], op[4]];\n\t\tcase \"m\":\n\t\t\treturn [\"m\", at(op[1]), op[2]];\n\t\tcase \"s\":\n\t\t\treturn [\"s\", at(op[1]), op[2]];\n\t\tcase \"d\":\n\t\t\treturn [\"d\", at(op[1])];\n\t\tcase \"a\":\n\t\t\treturn [\"a\", at(op[1]), op[2]];\n\t\tcase \"t\":\n\t\t\treturn [\"t\", at(op[1]), op[2]];\n\t}\n}\n"]}
|
package/dist/ids.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Id, Seq } from "./types.ts";
|
|
2
|
+
/** Apply an erased ID brand at a trusted numeric allocation or decoding boundary. */
|
|
3
|
+
export declare function idFromNumber<I extends Id<string>>(value: number): I;
|
|
4
|
+
/** Apply the erased commit-sequence brand at a trusted storage boundary. */
|
|
5
|
+
export declare function seqFromNumber(value: number): Seq;
|
|
6
|
+
//# sourceMappingURL=ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAE1C,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAEnE;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAEhD"}
|
package/dist/ids.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Apply an erased ID brand at a trusted numeric allocation or decoding boundary. */
|
|
2
|
+
export function idFromNumber(value) {
|
|
3
|
+
return value;
|
|
4
|
+
}
|
|
5
|
+
/** Apply the erased commit-sequence brand at a trusted storage boundary. */
|
|
6
|
+
export function seqFromNumber(value) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=ids.js.map
|
package/dist/ids.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.js","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAuB,KAAa;IAC/D,OAAO,KAAU,CAAC;AACnB,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,KAAa;IAC1C,OAAO,KAAY,CAAC;AACrB,CAAC","sourcesContent":["import type { Id, Seq } from \"./types.ts\";\n\n/** Apply an erased ID brand at a trusted numeric allocation or decoding boundary. */\nexport function idFromNumber<I extends Id<string>>(value: number): I {\n\treturn value as I;\n}\n\n/** Apply the erased commit-sequence brand at a trusted storage boundary. */\nexport function seqFromNumber(value: number): Seq {\n\treturn value as Seq;\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ImageProcessor } from "../tools/image-processor.ts";
|
|
2
|
+
/** `createPhotonImages()` for Cloudflare Workers and Durable Objects, built with Wrangler. */
|
|
3
|
+
export declare function createCloudflarePhotonImages(): ImageProcessor;
|
|
4
|
+
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/images/cloudflare.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAGlE,8FAA8F;AAC9F,wBAAgB,4BAA4B,IAAI,cAAc,CAE7D"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Wrangler's default `CompiledWasm` rule turns this import into a `WebAssembly.Module`; Workers cannot compile
|
|
2
|
+
// WebAssembly from bytes at runtime. Other bundlers do not support it: use `createPhotonImages()` there.
|
|
3
|
+
import photonWasm from "@silvia-odwyer/photon/photon_rs_bg.wasm";
|
|
4
|
+
import { createPhotonImages } from "./index.js";
|
|
5
|
+
/** `createPhotonImages()` for Cloudflare Workers and Durable Objects, built with Wrangler. */
|
|
6
|
+
export function createCloudflarePhotonImages() {
|
|
7
|
+
return createPhotonImages(photonWasm);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/images/cloudflare.ts"],"names":[],"mappings":"AAAA,+GAA+G;AAC/G,yGAAyG;AACzG,OAAO,UAAU,MAAM,yCAAyC,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,8FAA8F;AAC9F,MAAM,UAAU,4BAA4B;IAC3C,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC;AACvC,CAAC","sourcesContent":["// Wrangler's default `CompiledWasm` rule turns this import into a `WebAssembly.Module`; Workers cannot compile\n// WebAssembly from bytes at runtime. Other bundlers do not support it: use `createPhotonImages()` there.\nimport photonWasm from \"@silvia-odwyer/photon/photon_rs_bg.wasm\";\nimport type { ImageProcessor } from \"../tools/image-processor.ts\";\nimport { createPhotonImages } from \"./index.ts\";\n\n/** `createPhotonImages()` for Cloudflare Workers and Durable Objects, built with Wrangler. */\nexport function createCloudflarePhotonImages(): ImageProcessor {\n\treturn createPhotonImages(photonWasm);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exif.d.ts","sourceRoot":"","sources":["../../src/images/exif.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAuBzD"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// EXIF orientation of JPEG and WebP files, read from the bytes without decoding the image.
|
|
2
|
+
/** The EXIF orientation of a JPEG or WebP, 1 to 8; 1 when absent or unreadable. */
|
|
3
|
+
export function exifOrientation(bytes) {
|
|
4
|
+
const tiff = tiffStart(bytes);
|
|
5
|
+
if (tiff < 0 || tiff + 8 > bytes.length)
|
|
6
|
+
return 1;
|
|
7
|
+
const little = bytes[tiff] === 0x49 && bytes[tiff + 1] === 0x49;
|
|
8
|
+
const u16 = (at) => little ? bytes[at] | (bytes[at + 1] << 8) : (bytes[at] << 8) | bytes[at + 1];
|
|
9
|
+
const u32 = (at) => little
|
|
10
|
+
? (bytes[at] | (bytes[at + 1] << 8) | (bytes[at + 2] << 16) | (bytes[at + 3] << 24)) >>> 0
|
|
11
|
+
: ((bytes[at] << 24) | (bytes[at + 1] << 16) | (bytes[at + 2] << 8) | bytes[at + 3]) >>> 0;
|
|
12
|
+
const directory = tiff + u32(tiff + 4);
|
|
13
|
+
if (directory + 2 > bytes.length)
|
|
14
|
+
return 1;
|
|
15
|
+
const entries = u16(directory);
|
|
16
|
+
for (let index = 0; index < entries; index++) {
|
|
17
|
+
const entry = directory + 2 + index * 12;
|
|
18
|
+
if (entry + 12 > bytes.length)
|
|
19
|
+
return 1;
|
|
20
|
+
// Tag 0x0112 is Orientation, a SHORT whose value sits in the entry.
|
|
21
|
+
if (u16(entry) === 0x0112) {
|
|
22
|
+
const value = u16(entry + 8);
|
|
23
|
+
return value >= 1 && value <= 8 ? value : 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
/** Where the TIFF header of a JPEG's APP1 or a WebP's EXIF chunk starts; -1 when there is none. */
|
|
29
|
+
function tiffStart(bytes) {
|
|
30
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
31
|
+
for (let offset = 2; offset + 4 <= bytes.length;) {
|
|
32
|
+
if (bytes[offset] !== 0xff)
|
|
33
|
+
return -1;
|
|
34
|
+
const marker = bytes[offset + 1];
|
|
35
|
+
if (marker === 0xff) {
|
|
36
|
+
offset++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (marker === 0xe1 && isExifHeader(bytes, offset + 4))
|
|
40
|
+
return offset + 10;
|
|
41
|
+
offset += 2 + ((bytes[offset + 2] << 8) | bytes[offset + 3]);
|
|
42
|
+
}
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
45
|
+
if (ascii(bytes, 0, "RIFF") && ascii(bytes, 8, "WEBP")) {
|
|
46
|
+
for (let offset = 12; offset + 8 <= bytes.length;) {
|
|
47
|
+
const size = (bytes[offset + 4] |
|
|
48
|
+
(bytes[offset + 5] << 8) |
|
|
49
|
+
(bytes[offset + 6] << 16) |
|
|
50
|
+
(bytes[offset + 7] << 24)) >>>
|
|
51
|
+
0;
|
|
52
|
+
// Some WebP files prefix the TIFF header with "Exif\0\0".
|
|
53
|
+
if (ascii(bytes, offset, "EXIF"))
|
|
54
|
+
return isExifHeader(bytes, offset + 8) ? offset + 14 : offset + 8;
|
|
55
|
+
// RIFF chunks are padded to an even size.
|
|
56
|
+
offset += 8 + size + (size % 2);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return -1;
|
|
60
|
+
}
|
|
61
|
+
function isExifHeader(bytes, offset) {
|
|
62
|
+
return ascii(bytes, offset, "Exif") && bytes[offset + 4] === 0 && bytes[offset + 5] === 0;
|
|
63
|
+
}
|
|
64
|
+
function ascii(bytes, offset, text) {
|
|
65
|
+
for (let index = 0; index < text.length; index++) {
|
|
66
|
+
if (bytes[offset + index] !== text.charCodeAt(index))
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=exif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exif.js","sourceRoot":"","sources":["../../src/images/exif.ts"],"names":[],"mappings":"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","sourcesContent":["// EXIF orientation of JPEG and WebP files, read from the bytes without decoding the image.\n\n/** The EXIF orientation of a JPEG or WebP, 1 to 8; 1 when absent or unreadable. */\nexport function exifOrientation(bytes: Uint8Array): number {\n\tconst tiff = tiffStart(bytes);\n\tif (tiff < 0 || tiff + 8 > bytes.length) return 1;\n\tconst little = bytes[tiff] === 0x49 && bytes[tiff + 1] === 0x49;\n\tconst u16 = (at: number): number =>\n\t\tlittle ? bytes[at]! | (bytes[at + 1]! << 8) : (bytes[at]! << 8) | bytes[at + 1]!;\n\tconst u32 = (at: number): number =>\n\t\tlittle\n\t\t\t? (bytes[at]! | (bytes[at + 1]! << 8) | (bytes[at + 2]! << 16) | (bytes[at + 3]! << 24)) >>> 0\n\t\t\t: ((bytes[at]! << 24) | (bytes[at + 1]! << 16) | (bytes[at + 2]! << 8) | bytes[at + 3]!) >>> 0;\n\tconst directory = tiff + u32(tiff + 4);\n\tif (directory + 2 > bytes.length) return 1;\n\tconst entries = u16(directory);\n\tfor (let index = 0; index < entries; index++) {\n\t\tconst entry = directory + 2 + index * 12;\n\t\tif (entry + 12 > bytes.length) return 1;\n\t\t// Tag 0x0112 is Orientation, a SHORT whose value sits in the entry.\n\t\tif (u16(entry) === 0x0112) {\n\t\t\tconst value = u16(entry + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\treturn 1;\n}\n\n/** Where the TIFF header of a JPEG's APP1 or a WebP's EXIF chunk starts; -1 when there is none. */\nfunction tiffStart(bytes: Uint8Array): number {\n\tif (bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\tfor (let offset = 2; offset + 4 <= bytes.length; ) {\n\t\t\tif (bytes[offset] !== 0xff) return -1;\n\t\t\tconst marker = bytes[offset + 1]!;\n\t\t\tif (marker === 0xff) {\n\t\t\t\toffset++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (marker === 0xe1 && isExifHeader(bytes, offset + 4)) return offset + 10;\n\t\t\toffset += 2 + ((bytes[offset + 2]! << 8) | bytes[offset + 3]!);\n\t\t}\n\t\treturn -1;\n\t}\n\tif (ascii(bytes, 0, \"RIFF\") && ascii(bytes, 8, \"WEBP\")) {\n\t\tfor (let offset = 12; offset + 8 <= bytes.length; ) {\n\t\t\tconst size =\n\t\t\t\t(bytes[offset + 4]! |\n\t\t\t\t\t(bytes[offset + 5]! << 8) |\n\t\t\t\t\t(bytes[offset + 6]! << 16) |\n\t\t\t\t\t(bytes[offset + 7]! << 24)) >>>\n\t\t\t\t0;\n\t\t\t// Some WebP files prefix the TIFF header with \"Exif\\0\\0\".\n\t\t\tif (ascii(bytes, offset, \"EXIF\")) return isExifHeader(bytes, offset + 8) ? offset + 14 : offset + 8;\n\t\t\t// RIFF chunks are padded to an even size.\n\t\t\toffset += 8 + size + (size % 2);\n\t\t}\n\t}\n\treturn -1;\n}\n\nfunction isExifHeader(bytes: Uint8Array, offset: number): boolean {\n\treturn ascii(bytes, offset, \"Exif\") && bytes[offset + 4] === 0 && bytes[offset + 5] === 0;\n}\n\nfunction ascii(bytes: Uint8Array, offset: number, text: string): boolean {\n\tfor (let index = 0; index < text.length; index++) {\n\t\tif (bytes[offset + index] !== text.charCodeAt(index)) return false;\n\t}\n\treturn true;\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type SyncInitInput } from "@silvia-odwyer/photon/photon_rs.js";
|
|
2
|
+
import type { ImageProcessor } from "../tools/image-processor.ts";
|
|
3
|
+
/**
|
|
4
|
+
* An `ImageProcessor` on Photon (Rust compiled to WebAssembly), for `createCodingTools({ images })`. `wasm` is
|
|
5
|
+
* Photon's `photon_rs_bg.wasm` as bytes or as a compiled module: `@ny4n/durable/images/node` reads it from
|
|
6
|
+
* the installed package, `@ny4n/durable/images/cloudflare` imports it as a Workers module. Elsewhere, such
|
|
7
|
+
* as a browser, fetch the file and pass its bytes. Photon loads once per process; later calls reuse the first `wasm`.
|
|
8
|
+
*
|
|
9
|
+
* An image already upright, in a format providers take inline, and within the limits is returned as it is, once it
|
|
10
|
+
* decodes. Otherwise it is turned upright by its EXIF orientation, scaled to the
|
|
11
|
+
* maximum width and height, and encoded as PNG or JPEG at falling qualities (JPEG first for JPEG sources), then at
|
|
12
|
+
* smaller dimensions, 3/4 at a time, until the base64 fits. Formats providers do not take inline (BMP) become PNG.
|
|
13
|
+
*
|
|
14
|
+
* The work runs on the calling thread: about a second and a half for a 12-megapixel photo. Decoding takes the image's
|
|
15
|
+
* RGBA size in WebAssembly memory, which does not shrink again; leave the processor out on memory-constrained hosts.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createPhotonImages(wasm: SyncInitInput): ImageProcessor;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/images/index.ts"],"names":[],"mappings":"AACA,OAAO,EAON,KAAK,aAAa,EAClB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAe,cAAc,EAAiB,MAAM,6BAA6B,CAAC;AAO9F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,CAGtE"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// The package declares only `module`, which bundlers use but Node does not: import the file itself.
|
|
2
|
+
import { fliph, flipv, initSync, PhotonImage, resize, SamplingFilter, } from "@silvia-odwyer/photon/photon_rs.js";
|
|
3
|
+
import { base64Length, INLINE_IMAGE_TYPES, toBase64 } from "../tools/image-processor.js";
|
|
4
|
+
import { exifOrientation } from "./exif.js";
|
|
5
|
+
/** JPEG qualities tried in order. */
|
|
6
|
+
const JPEG_QUALITIES = [80, 70, 55, 40];
|
|
7
|
+
/**
|
|
8
|
+
* An `ImageProcessor` on Photon (Rust compiled to WebAssembly), for `createCodingTools({ images })`. `wasm` is
|
|
9
|
+
* Photon's `photon_rs_bg.wasm` as bytes or as a compiled module: `@ny4n/durable/images/node` reads it from
|
|
10
|
+
* the installed package, `@ny4n/durable/images/cloudflare` imports it as a Workers module. Elsewhere, such
|
|
11
|
+
* as a browser, fetch the file and pass its bytes. Photon loads once per process; later calls reuse the first `wasm`.
|
|
12
|
+
*
|
|
13
|
+
* An image already upright, in a format providers take inline, and within the limits is returned as it is, once it
|
|
14
|
+
* decodes. Otherwise it is turned upright by its EXIF orientation, scaled to the
|
|
15
|
+
* maximum width and height, and encoded as PNG or JPEG at falling qualities (JPEG first for JPEG sources), then at
|
|
16
|
+
* smaller dimensions, 3/4 at a time, until the base64 fits. Formats providers do not take inline (BMP) become PNG.
|
|
17
|
+
*
|
|
18
|
+
* The work runs on the calling thread: about a second and a half for a 12-megapixel photo. Decoding takes the image's
|
|
19
|
+
* RGBA size in WebAssembly memory, which does not shrink again; leave the processor out on memory-constrained hosts.
|
|
20
|
+
*/
|
|
21
|
+
export function createPhotonImages(wasm) {
|
|
22
|
+
initSync({ module: wasm });
|
|
23
|
+
return { prepare: async (bytes, mimeType, limits) => prepare(bytes, mimeType, limits) };
|
|
24
|
+
}
|
|
25
|
+
function prepare(bytes, mimeType, limits) {
|
|
26
|
+
const orientation = exifOrientation(bytes);
|
|
27
|
+
const inline = INLINE_IMAGE_TYPES.has(mimeType);
|
|
28
|
+
let image;
|
|
29
|
+
try {
|
|
30
|
+
// Decoded even when it is returned as it is: a damaged file is an error, not an image the provider rejects.
|
|
31
|
+
image = PhotonImage.new_from_byteslice(bytes);
|
|
32
|
+
if (inline &&
|
|
33
|
+
orientation === 1 &&
|
|
34
|
+
image.get_width() <= limits.maxWidth &&
|
|
35
|
+
image.get_height() <= limits.maxHeight &&
|
|
36
|
+
base64Length(bytes.byteLength) <= limits.maxBytes) {
|
|
37
|
+
return { data: toBase64(bytes), mimeType };
|
|
38
|
+
}
|
|
39
|
+
if (orientation >= 5) {
|
|
40
|
+
const upright = transposed(image, orientation);
|
|
41
|
+
// Freed first, so the upright copy reuses its WebAssembly memory.
|
|
42
|
+
image.free();
|
|
43
|
+
image = undefined;
|
|
44
|
+
image = new PhotonImage(upright.pixels, upright.width, upright.height);
|
|
45
|
+
}
|
|
46
|
+
else if (orientation !== 1) {
|
|
47
|
+
if (orientation !== 4)
|
|
48
|
+
fliph(image);
|
|
49
|
+
if (orientation !== 2)
|
|
50
|
+
flipv(image);
|
|
51
|
+
}
|
|
52
|
+
const width = image.get_width();
|
|
53
|
+
const height = image.get_height();
|
|
54
|
+
const scale = Math.min(1, limits.maxWidth / width, limits.maxHeight / height);
|
|
55
|
+
let target = { width: Math.max(1, Math.round(width * scale)), height: Math.max(1, Math.round(height * scale)) };
|
|
56
|
+
for (;;) {
|
|
57
|
+
const encoded = encodeWithin(image, target, limits.maxBytes, mimeType === "image/jpeg");
|
|
58
|
+
if (encoded !== undefined) {
|
|
59
|
+
const resized = target.width === width && target.height === height
|
|
60
|
+
? {}
|
|
61
|
+
: { resized: { from: { width, height }, to: target } };
|
|
62
|
+
const converted = encoded.mimeType === mimeType ? {} : { convertedFrom: mimeType };
|
|
63
|
+
return { ...encoded, ...converted, ...resized };
|
|
64
|
+
}
|
|
65
|
+
if (target.width === 1 && target.height === 1)
|
|
66
|
+
return undefined;
|
|
67
|
+
target = {
|
|
68
|
+
width: Math.max(1, Math.floor(target.width * 0.75)),
|
|
69
|
+
height: Math.max(1, Math.floor(target.height * 0.75)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Photon throws for bytes it cannot decode.
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
image?.free();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* `image` at `size` in the first encoding whose base64 fits `maxBytes`: PNG, then JPEG at falling qualities, or the
|
|
83
|
+
* JPEG qualities first when `jpegFirst`; `undefined` if none fits.
|
|
84
|
+
*/
|
|
85
|
+
function encodeWithin(image, size, maxBytes, jpegFirst) {
|
|
86
|
+
const same = size.width === image.get_width() && size.height === image.get_height();
|
|
87
|
+
const scaled = same ? image : resize(image, size.width, size.height, SamplingFilter.Lanczos3);
|
|
88
|
+
try {
|
|
89
|
+
const png = { mimeType: "image/png", encode: () => scaled.get_bytes() };
|
|
90
|
+
const jpegs = JPEG_QUALITIES.map((quality) => ({
|
|
91
|
+
mimeType: "image/jpeg",
|
|
92
|
+
encode: () => scaled.get_bytes_jpeg(quality),
|
|
93
|
+
}));
|
|
94
|
+
for (const candidate of jpegFirst ? [...jpegs, png] : [png, ...jpegs]) {
|
|
95
|
+
const bytes = candidate.encode();
|
|
96
|
+
if (base64Length(bytes.byteLength) <= maxBytes)
|
|
97
|
+
return { data: toBase64(bytes), mimeType: candidate.mimeType };
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (!same)
|
|
103
|
+
scaled.free();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The RGBA pixels of `image` turned upright for EXIF `orientation` 5 to 8, the orientations that swap width and
|
|
108
|
+
* height. Photon's own `rotate` drops pixels at right angles, so the pixels are moved here.
|
|
109
|
+
*/
|
|
110
|
+
function transposed(image, orientation) {
|
|
111
|
+
const width = image.get_width();
|
|
112
|
+
const height = image.get_height();
|
|
113
|
+
const source = image.get_raw_pixels();
|
|
114
|
+
const pixels = new Uint8Array(source.length);
|
|
115
|
+
for (let y = 0; y < height; y++) {
|
|
116
|
+
for (let x = 0; x < width; x++) {
|
|
117
|
+
// Where (x, y) lands in the upright image, which is `height` wide.
|
|
118
|
+
const outX = orientation === 5 || orientation === 8 ? y : height - 1 - y;
|
|
119
|
+
const outY = orientation === 5 || orientation === 6 ? x : width - 1 - x;
|
|
120
|
+
pixels.set(source.subarray((y * width + x) * 4, (y * width + x) * 4 + 4), (outY * height + outX) * 4);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { pixels, width: height, height: width };
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/images/index.ts"],"names":[],"mappings":"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","sourcesContent":["// The package declares only `module`, which bundlers use but Node does not: import the file itself.\nimport {\n\tfliph,\n\tflipv,\n\tinitSync,\n\tPhotonImage,\n\tresize,\n\tSamplingFilter,\n\ttype SyncInitInput,\n} from \"@silvia-odwyer/photon/photon_rs.js\";\nimport type { ImageLimits, ImageProcessor, PreparedImage } from \"../tools/image-processor.ts\";\nimport { base64Length, INLINE_IMAGE_TYPES, toBase64 } from \"../tools/image-processor.ts\";\nimport { exifOrientation } from \"./exif.ts\";\n\n/** JPEG qualities tried in order. */\nconst JPEG_QUALITIES = [80, 70, 55, 40];\n\n/**\n * An `ImageProcessor` on Photon (Rust compiled to WebAssembly), for `createCodingTools({ images })`. `wasm` is\n * Photon's `photon_rs_bg.wasm` as bytes or as a compiled module: `@ny4n/durable/images/node` reads it from\n * the installed package, `@ny4n/durable/images/cloudflare` imports it as a Workers module. Elsewhere, such\n * as a browser, fetch the file and pass its bytes. Photon loads once per process; later calls reuse the first `wasm`.\n *\n * An image already upright, in a format providers take inline, and within the limits is returned as it is, once it\n * decodes. Otherwise it is turned upright by its EXIF orientation, scaled to the\n * maximum width and height, and encoded as PNG or JPEG at falling qualities (JPEG first for JPEG sources), then at\n * smaller dimensions, 3/4 at a time, until the base64 fits. Formats providers do not take inline (BMP) become PNG.\n *\n * The work runs on the calling thread: about a second and a half for a 12-megapixel photo. Decoding takes the image's\n * RGBA size in WebAssembly memory, which does not shrink again; leave the processor out on memory-constrained hosts.\n */\nexport function createPhotonImages(wasm: SyncInitInput): ImageProcessor {\n\tinitSync({ module: wasm });\n\treturn { prepare: async (bytes, mimeType, limits) => prepare(bytes, mimeType, limits) };\n}\n\nfunction prepare(bytes: Uint8Array, mimeType: string, limits: ImageLimits): PreparedImage | undefined {\n\tconst orientation = exifOrientation(bytes);\n\tconst inline = INLINE_IMAGE_TYPES.has(mimeType);\n\tlet image: PhotonImage | undefined;\n\ttry {\n\t\t// Decoded even when it is returned as it is: a damaged file is an error, not an image the provider rejects.\n\t\timage = PhotonImage.new_from_byteslice(bytes);\n\t\tif (\n\t\t\tinline &&\n\t\t\torientation === 1 &&\n\t\t\timage.get_width() <= limits.maxWidth &&\n\t\t\timage.get_height() <= limits.maxHeight &&\n\t\t\tbase64Length(bytes.byteLength) <= limits.maxBytes\n\t\t) {\n\t\t\treturn { data: toBase64(bytes), mimeType };\n\t\t}\n\t\tif (orientation >= 5) {\n\t\t\tconst upright = transposed(image, orientation);\n\t\t\t// Freed first, so the upright copy reuses its WebAssembly memory.\n\t\t\timage.free();\n\t\t\timage = undefined;\n\t\t\timage = new PhotonImage(upright.pixels, upright.width, upright.height);\n\t\t} else if (orientation !== 1) {\n\t\t\tif (orientation !== 4) fliph(image);\n\t\t\tif (orientation !== 2) flipv(image);\n\t\t}\n\t\tconst width = image.get_width();\n\t\tconst height = image.get_height();\n\t\tconst scale = Math.min(1, limits.maxWidth / width, limits.maxHeight / height);\n\t\tlet target = { width: Math.max(1, Math.round(width * scale)), height: Math.max(1, Math.round(height * scale)) };\n\t\tfor (;;) {\n\t\t\tconst encoded = encodeWithin(image, target, limits.maxBytes, mimeType === \"image/jpeg\");\n\t\t\tif (encoded !== undefined) {\n\t\t\t\tconst resized =\n\t\t\t\t\ttarget.width === width && target.height === height\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { resized: { from: { width, height }, to: target } };\n\t\t\t\tconst converted = encoded.mimeType === mimeType ? {} : { convertedFrom: mimeType };\n\t\t\t\treturn { ...encoded, ...converted, ...resized };\n\t\t\t}\n\t\t\tif (target.width === 1 && target.height === 1) return undefined;\n\t\t\ttarget = {\n\t\t\t\twidth: Math.max(1, Math.floor(target.width * 0.75)),\n\t\t\t\theight: Math.max(1, Math.floor(target.height * 0.75)),\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// Photon throws for bytes it cannot decode.\n\t\treturn undefined;\n\t} finally {\n\t\timage?.free();\n\t}\n}\n\n/**\n * `image` at `size` in the first encoding whose base64 fits `maxBytes`: PNG, then JPEG at falling qualities, or the\n * JPEG qualities first when `jpegFirst`; `undefined` if none fits.\n */\nfunction encodeWithin(\n\timage: PhotonImage,\n\tsize: { readonly width: number; readonly height: number },\n\tmaxBytes: number,\n\tjpegFirst: boolean,\n): { readonly data: string; readonly mimeType: string } | undefined {\n\tconst same = size.width === image.get_width() && size.height === image.get_height();\n\tconst scaled = same ? image : resize(image, size.width, size.height, SamplingFilter.Lanczos3);\n\ttry {\n\t\tconst png = { mimeType: \"image/png\", encode: () => scaled.get_bytes() };\n\t\tconst jpegs = JPEG_QUALITIES.map((quality) => ({\n\t\t\tmimeType: \"image/jpeg\",\n\t\t\tencode: () => scaled.get_bytes_jpeg(quality),\n\t\t}));\n\t\tfor (const candidate of jpegFirst ? [...jpegs, png] : [png, ...jpegs]) {\n\t\t\tconst bytes = candidate.encode();\n\t\t\tif (base64Length(bytes.byteLength) <= maxBytes) return { data: toBase64(bytes), mimeType: candidate.mimeType };\n\t\t}\n\t\treturn undefined;\n\t} finally {\n\t\tif (!same) scaled.free();\n\t}\n}\n\n/**\n * The RGBA pixels of `image` turned upright for EXIF `orientation` 5 to 8, the orientations that swap width and\n * height. Photon's own `rotate` drops pixels at right angles, so the pixels are moved here.\n */\nfunction transposed(\n\timage: PhotonImage,\n\torientation: number,\n): { readonly pixels: Uint8Array; readonly width: number; readonly height: number } {\n\tconst width = image.get_width();\n\tconst height = image.get_height();\n\tconst source = image.get_raw_pixels();\n\tconst pixels = new Uint8Array(source.length);\n\tfor (let y = 0; y < height; y++) {\n\t\tfor (let x = 0; x < width; x++) {\n\t\t\t// Where (x, y) lands in the upright image, which is `height` wide.\n\t\t\tconst outX = orientation === 5 || orientation === 8 ? y : height - 1 - y;\n\t\t\tconst outY = orientation === 5 || orientation === 6 ? x : width - 1 - x;\n\t\t\tpixels.set(source.subarray((y * width + x) * 4, (y * width + x) * 4 + 4), (outY * height + outX) * 4);\n\t\t}\n\t}\n\treturn { pixels, width: height, height: width };\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ImageLimits, PreparedImage } from "../tools/image-processor.ts";
|
|
2
|
+
/** One request: the image to prepare, and its ID for the reply. */
|
|
3
|
+
export type PrepareRequest = {
|
|
4
|
+
readonly id: number;
|
|
5
|
+
readonly bytes: Uint8Array;
|
|
6
|
+
readonly mimeType: string;
|
|
7
|
+
readonly limits: ImageLimits;
|
|
8
|
+
};
|
|
9
|
+
/** The reply to a request: its result, or why it failed. */
|
|
10
|
+
export type PrepareReply = {
|
|
11
|
+
readonly id: number;
|
|
12
|
+
readonly result: PreparedImage | undefined;
|
|
13
|
+
} | {
|
|
14
|
+
readonly id: number;
|
|
15
|
+
readonly error: string;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=node-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker.d.ts","sourceRoot":"","sources":["../../src/images/node-worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG9E,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC7B,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The worker thread of `createNodePhotonImages()`: Photon prepares images here, off the caller's event loop.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { parentPort } from "node:worker_threads";
|
|
5
|
+
import { createPhotonImages } from "./index.js";
|
|
6
|
+
const port = parentPort;
|
|
7
|
+
if (port === null)
|
|
8
|
+
throw new Error("The Photon image worker needs a parent thread");
|
|
9
|
+
const images = createPhotonImages(readFileSync(createRequire(import.meta.url).resolve("@silvia-odwyer/photon/photon_rs_bg.wasm")));
|
|
10
|
+
port.on("message", (request) => {
|
|
11
|
+
void images.prepare(request.bytes, request.mimeType, request.limits).then((result) => port.postMessage({ id: request.id, result }), (error) => port.postMessage({
|
|
12
|
+
id: request.id,
|
|
13
|
+
error: error instanceof Error ? error.message : String(error),
|
|
14
|
+
}));
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=node-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker.js","sourceRoot":"","sources":["../../src/images/node-worker.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAehD,MAAM,IAAI,GAAG,UAAU,CAAC;AACxB,IAAI,IAAI,KAAK,IAAI;IAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;AACpF,MAAM,MAAM,GAAG,kBAAkB,CAChC,YAAY,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,CAC/F,CAAC;AACF,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAuB,EAAE,EAAE;IAC9C,KAAK,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CACxE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAyB,CAAC,EAC/E,CAAC,KAAc,EAAE,EAAE,CAClB,IAAI,CAAC,WAAW,CAAC;QAChB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KACtC,CAAC,CAC1B,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["// The worker thread of `createNodePhotonImages()`: Photon prepares images here, off the caller's event loop.\nimport { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { parentPort } from \"node:worker_threads\";\nimport type { ImageLimits, PreparedImage } from \"../tools/image-processor.ts\";\nimport { createPhotonImages } from \"./index.ts\";\n\n/** One request: the image to prepare, and its ID for the reply. */\nexport type PrepareRequest = {\n\treadonly id: number;\n\treadonly bytes: Uint8Array;\n\treadonly mimeType: string;\n\treadonly limits: ImageLimits;\n};\n\n/** The reply to a request: its result, or why it failed. */\nexport type PrepareReply =\n\t| { readonly id: number; readonly result: PreparedImage | undefined }\n\t| { readonly id: number; readonly error: string };\n\nconst port = parentPort;\nif (port === null) throw new Error(\"The Photon image worker needs a parent thread\");\nconst images = createPhotonImages(\n\treadFileSync(createRequire(import.meta.url).resolve(\"@silvia-odwyer/photon/photon_rs_bg.wasm\")),\n);\nport.on(\"message\", (request: PrepareRequest) => {\n\tvoid images.prepare(request.bytes, request.mimeType, request.limits).then(\n\t\t(result) => port.postMessage({ id: request.id, result } satisfies PrepareReply),\n\t\t(error: unknown) =>\n\t\t\tport.postMessage({\n\t\t\t\tid: request.id,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t} satisfies PrepareReply),\n\t);\n});\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ImageProcessor } from "../tools/image-processor.ts";
|
|
2
|
+
export type NodePhotonImagesOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Prepare images in a worker thread, so a large photo does not block the event loop for the second or two Photon
|
|
5
|
+
* takes. Default `true`; `false` prepares them on the calling thread.
|
|
6
|
+
*/
|
|
7
|
+
readonly worker?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* How long the worker stays after its last image before it exits, freeing Photon's WebAssembly memory, which never
|
|
10
|
+
* shrinks while it lives. The next image starts a new one. Default 30 s.
|
|
11
|
+
*/
|
|
12
|
+
readonly idleMs?: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* `createPhotonImages()` for Node and Bun, with Photon's WebAssembly read from the installed `@silvia-odwyer/photon`,
|
|
16
|
+
* by default in a worker thread that keeps the process alive only while it prepares an image. A worker that cannot
|
|
17
|
+
* start falls back to the calling thread. A single-file binary cannot resolve the package: read its own copy of
|
|
18
|
+
* `photon_rs_bg.wasm` and call `createPhotonImages()`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createNodePhotonImages(options?: NodePhotonImagesOptions): Promise<ImageProcessor>;
|
|
21
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/images/node.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAe,cAAc,EAAiB,MAAM,6BAA6B,CAAC;AAI9F,MAAM,MAAM,uBAAuB,GAAG;IACrC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,cAAc,CAAC,CAqB3G"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { Worker } from "node:worker_threads";
|
|
4
|
+
import { createPhotonImages } from "./index.js";
|
|
5
|
+
/**
|
|
6
|
+
* `createPhotonImages()` for Node and Bun, with Photon's WebAssembly read from the installed `@silvia-odwyer/photon`,
|
|
7
|
+
* by default in a worker thread that keeps the process alive only while it prepares an image. A worker that cannot
|
|
8
|
+
* start falls back to the calling thread. A single-file binary cannot resolve the package: read its own copy of
|
|
9
|
+
* `photon_rs_bg.wasm` and call `createPhotonImages()`.
|
|
10
|
+
*/
|
|
11
|
+
export async function createNodePhotonImages(options = {}) {
|
|
12
|
+
if (options.worker === false)
|
|
13
|
+
return inProcess();
|
|
14
|
+
const idleMs = options.idleMs ?? 30_000;
|
|
15
|
+
let thread;
|
|
16
|
+
let fallback;
|
|
17
|
+
return {
|
|
18
|
+
async prepare(bytes, mimeType, limits) {
|
|
19
|
+
if (fallback !== undefined)
|
|
20
|
+
return (await fallback).prepare(bytes, mimeType, limits);
|
|
21
|
+
try {
|
|
22
|
+
thread ??= new PhotonThread(idleMs, () => {
|
|
23
|
+
thread = undefined;
|
|
24
|
+
});
|
|
25
|
+
return await thread.prepare(bytes, mimeType, limits);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (!(error instanceof WorkerUnavailable))
|
|
29
|
+
throw error;
|
|
30
|
+
// No worker on this platform or build: prepare here from now on.
|
|
31
|
+
fallback = inProcess();
|
|
32
|
+
return (await fallback).prepare(bytes, mimeType, limits);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function inProcess() {
|
|
38
|
+
const path = createRequire(import.meta.url).resolve("@silvia-odwyer/photon/photon_rs_bg.wasm");
|
|
39
|
+
return createPhotonImages(await readFile(path));
|
|
40
|
+
}
|
|
41
|
+
/** The worker could not start, as opposed to failing on one image. */
|
|
42
|
+
class WorkerUnavailable extends Error {
|
|
43
|
+
}
|
|
44
|
+
/** One worker thread and the requests it has not answered; it exits after `idleMs` without requests. */
|
|
45
|
+
class PhotonThread {
|
|
46
|
+
#worker;
|
|
47
|
+
#pending = new Map();
|
|
48
|
+
#idleMs;
|
|
49
|
+
#onExit;
|
|
50
|
+
#nextId = 0;
|
|
51
|
+
#idle;
|
|
52
|
+
/** Whether the worker answered once: a failure before that means it is unavailable. */
|
|
53
|
+
#started = false;
|
|
54
|
+
constructor(idleMs, onExit) {
|
|
55
|
+
this.#idleMs = idleMs;
|
|
56
|
+
this.#onExit = onExit;
|
|
57
|
+
// Run from source under a TypeScript runtime, as in tests; built, the worker sits next to this file as JS.
|
|
58
|
+
const file = import.meta.url.endsWith(".ts") ? "./node-worker.ts" : "./node-worker.js";
|
|
59
|
+
try {
|
|
60
|
+
this.#worker = new Worker(new URL(file, import.meta.url));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new WorkerUnavailable("The Photon image worker cannot start", { cause: error });
|
|
64
|
+
}
|
|
65
|
+
this.#worker.unref();
|
|
66
|
+
this.#worker.on("message", (reply) => {
|
|
67
|
+
this.#started = true;
|
|
68
|
+
const request = this.#pending.get(reply.id);
|
|
69
|
+
if (request === undefined)
|
|
70
|
+
return;
|
|
71
|
+
this.#pending.delete(reply.id);
|
|
72
|
+
if ("error" in reply)
|
|
73
|
+
request.reject(new Error(reply.error));
|
|
74
|
+
else
|
|
75
|
+
request.resolve(reply.result);
|
|
76
|
+
this.#settled();
|
|
77
|
+
});
|
|
78
|
+
const fail = (error) => {
|
|
79
|
+
const failure = this.#started
|
|
80
|
+
? error
|
|
81
|
+
: new WorkerUnavailable("The Photon image worker cannot start", { cause: error });
|
|
82
|
+
for (const request of this.#pending.values())
|
|
83
|
+
request.reject(failure);
|
|
84
|
+
this.#pending.clear();
|
|
85
|
+
this.#exit();
|
|
86
|
+
};
|
|
87
|
+
this.#worker.on("error", fail);
|
|
88
|
+
this.#worker.on("exit", (code) => fail(new Error(`The Photon image worker exited with code ${code}`)));
|
|
89
|
+
}
|
|
90
|
+
prepare(bytes, mimeType, limits) {
|
|
91
|
+
const id = this.#nextId++;
|
|
92
|
+
const request = Promise.withResolvers();
|
|
93
|
+
this.#pending.set(id, request);
|
|
94
|
+
if (this.#idle !== undefined)
|
|
95
|
+
clearTimeout(this.#idle);
|
|
96
|
+
this.#idle = undefined;
|
|
97
|
+
// A pending image keeps the process alive, as one prepared on the calling thread would.
|
|
98
|
+
this.#worker.ref();
|
|
99
|
+
// A copy, transferred: the caller keeps its bytes.
|
|
100
|
+
const copy = bytes.slice();
|
|
101
|
+
this.#worker.postMessage({ id, bytes: copy, mimeType, limits }, [copy.buffer]);
|
|
102
|
+
return request.promise;
|
|
103
|
+
}
|
|
104
|
+
#settled() {
|
|
105
|
+
if (this.#pending.size > 0)
|
|
106
|
+
return;
|
|
107
|
+
this.#worker.unref();
|
|
108
|
+
this.#idle = setTimeout(() => {
|
|
109
|
+
this.#exit();
|
|
110
|
+
void this.#worker.terminate();
|
|
111
|
+
}, this.#idleMs);
|
|
112
|
+
this.#idle.unref();
|
|
113
|
+
}
|
|
114
|
+
#exit() {
|
|
115
|
+
if (this.#idle !== undefined)
|
|
116
|
+
clearTimeout(this.#idle);
|
|
117
|
+
this.#idle = undefined;
|
|
118
|
+
this.#onExit();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/images/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { Worker } from \"node:worker_threads\";\nimport type { ImageLimits, ImageProcessor, PreparedImage } from \"../tools/image-processor.ts\";\nimport { createPhotonImages } from \"./index.ts\";\nimport type { PrepareReply, PrepareRequest } from \"./node-worker.ts\";\n\nexport type NodePhotonImagesOptions = {\n\t/**\n\t * Prepare images in a worker thread, so a large photo does not block the event loop for the second or two Photon\n\t * takes. Default `true`; `false` prepares them on the calling thread.\n\t */\n\treadonly worker?: boolean;\n\t/**\n\t * How long the worker stays after its last image before it exits, freeing Photon's WebAssembly memory, which never\n\t * shrinks while it lives. The next image starts a new one. Default 30 s.\n\t */\n\treadonly idleMs?: number;\n};\n\n/**\n * `createPhotonImages()` for Node and Bun, with Photon's WebAssembly read from the installed `@silvia-odwyer/photon`,\n * by default in a worker thread that keeps the process alive only while it prepares an image. A worker that cannot\n * start falls back to the calling thread. A single-file binary cannot resolve the package: read its own copy of\n * `photon_rs_bg.wasm` and call `createPhotonImages()`.\n */\nexport async function createNodePhotonImages(options: NodePhotonImagesOptions = {}): Promise<ImageProcessor> {\n\tif (options.worker === false) return inProcess();\n\tconst idleMs = options.idleMs ?? 30_000;\n\tlet thread: PhotonThread | undefined;\n\tlet fallback: Promise<ImageProcessor> | undefined;\n\treturn {\n\t\tasync prepare(bytes, mimeType, limits) {\n\t\t\tif (fallback !== undefined) return (await fallback).prepare(bytes, mimeType, limits);\n\t\t\ttry {\n\t\t\t\tthread ??= new PhotonThread(idleMs, () => {\n\t\t\t\t\tthread = undefined;\n\t\t\t\t});\n\t\t\t\treturn await thread.prepare(bytes, mimeType, limits);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof WorkerUnavailable)) throw error;\n\t\t\t\t// No worker on this platform or build: prepare here from now on.\n\t\t\t\tfallback = inProcess();\n\t\t\t\treturn (await fallback).prepare(bytes, mimeType, limits);\n\t\t\t}\n\t\t},\n\t};\n}\n\nasync function inProcess(): Promise<ImageProcessor> {\n\tconst path = createRequire(import.meta.url).resolve(\"@silvia-odwyer/photon/photon_rs_bg.wasm\");\n\treturn createPhotonImages(await readFile(path));\n}\n\n/** The worker could not start, as opposed to failing on one image. */\nclass WorkerUnavailable extends Error {}\n\n/** One worker thread and the requests it has not answered; it exits after `idleMs` without requests. */\nclass PhotonThread {\n\treadonly #worker: Worker;\n\treadonly #pending = new Map<number, PromiseWithResolvers<PreparedImage | undefined>>();\n\treadonly #idleMs: number;\n\treadonly #onExit: () => void;\n\t#nextId = 0;\n\t#idle: ReturnType<typeof setTimeout> | undefined;\n\t/** Whether the worker answered once: a failure before that means it is unavailable. */\n\t#started = false;\n\n\tconstructor(idleMs: number, onExit: () => void) {\n\t\tthis.#idleMs = idleMs;\n\t\tthis.#onExit = onExit;\n\t\t// Run from source under a TypeScript runtime, as in tests; built, the worker sits next to this file as JS.\n\t\tconst file = import.meta.url.endsWith(\".ts\") ? \"./node-worker.ts\" : \"./node-worker.js\";\n\t\ttry {\n\t\t\tthis.#worker = new Worker(new URL(file, import.meta.url));\n\t\t} catch (error) {\n\t\t\tthrow new WorkerUnavailable(\"The Photon image worker cannot start\", { cause: error });\n\t\t}\n\t\tthis.#worker.unref();\n\t\tthis.#worker.on(\"message\", (reply: PrepareReply) => {\n\t\t\tthis.#started = true;\n\t\t\tconst request = this.#pending.get(reply.id);\n\t\t\tif (request === undefined) return;\n\t\t\tthis.#pending.delete(reply.id);\n\t\t\tif (\"error\" in reply) request.reject(new Error(reply.error));\n\t\t\telse request.resolve(reply.result);\n\t\t\tthis.#settled();\n\t\t});\n\t\tconst fail = (error: unknown): void => {\n\t\t\tconst failure = this.#started\n\t\t\t\t? error\n\t\t\t\t: new WorkerUnavailable(\"The Photon image worker cannot start\", { cause: error });\n\t\t\tfor (const request of this.#pending.values()) request.reject(failure);\n\t\t\tthis.#pending.clear();\n\t\t\tthis.#exit();\n\t\t};\n\t\tthis.#worker.on(\"error\", fail);\n\t\tthis.#worker.on(\"exit\", (code) => fail(new Error(`The Photon image worker exited with code ${code}`)));\n\t}\n\n\tprepare(bytes: Uint8Array, mimeType: string, limits: ImageLimits): Promise<PreparedImage | undefined> {\n\t\tconst id = this.#nextId++;\n\t\tconst request = Promise.withResolvers<PreparedImage | undefined>();\n\t\tthis.#pending.set(id, request);\n\t\tif (this.#idle !== undefined) clearTimeout(this.#idle);\n\t\tthis.#idle = undefined;\n\t\t// A pending image keeps the process alive, as one prepared on the calling thread would.\n\t\tthis.#worker.ref();\n\t\t// A copy, transferred: the caller keeps its bytes.\n\t\tconst copy = bytes.slice();\n\t\tthis.#worker.postMessage({ id, bytes: copy, mimeType, limits } satisfies PrepareRequest, [copy.buffer]);\n\t\treturn request.promise;\n\t}\n\n\t#settled(): void {\n\t\tif (this.#pending.size > 0) return;\n\t\tthis.#worker.unref();\n\t\tthis.#idle = setTimeout(() => {\n\t\t\tthis.#exit();\n\t\t\tvoid this.#worker.terminate();\n\t\t}, this.#idleMs);\n\t\tthis.#idle.unref();\n\t}\n\n\t#exit(): void {\n\t\tif (this.#idle !== undefined) clearTimeout(this.#idle);\n\t\tthis.#idle = undefined;\n\t\tthis.#onExit();\n\t}\n}\n"]}
|