stitchkit 0.93.0 → 0.94.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/CHANGELOG.md +198 -0
- package/README.md +5 -2
- package/dist/agent-runtime/children-control.d.ts +6 -0
- package/dist/agent-runtime/children-control.d.ts.map +1 -0
- package/dist/agent-runtime/children-spawn.d.ts +6 -0
- package/dist/agent-runtime/children-spawn.d.ts.map +1 -0
- package/dist/agent-runtime/children-state.d.ts +46 -0
- package/dist/agent-runtime/children-state.d.ts.map +1 -0
- package/dist/agent-runtime/children-usage.d.ts +8 -0
- package/dist/agent-runtime/children-usage.d.ts.map +1 -0
- package/dist/agent-runtime/children.d.ts +11 -14
- package/dist/agent-runtime/children.d.ts.map +1 -1
- package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-contract.d.ts +2 -1
- package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
- package/dist/agent-runtime/contained-darwin.d.ts +38 -0
- package/dist/agent-runtime/contained-darwin.d.ts.map +1 -0
- package/dist/agent-runtime/contained-files.d.ts +11 -26
- package/dist/agent-runtime/contained-files.d.ts.map +1 -1
- package/dist/agent-runtime/contained-scan.d.ts +21 -0
- package/dist/agent-runtime/contained-scan.d.ts.map +1 -0
- package/dist/agent-runtime/contained-write.d.ts +9 -0
- package/dist/agent-runtime/contained-write.d.ts.map +1 -0
- package/dist/agent-runtime/deferred-tool-types.d.ts +4 -3
- package/dist/agent-runtime/deferred-tool-types.d.ts.map +1 -1
- package/dist/agent-runtime/events.d.ts +1 -1
- package/dist/agent-runtime/events.d.ts.map +1 -1
- package/dist/agent-runtime/harness-control.d.ts +0 -1
- package/dist/agent-runtime/harness-control.d.ts.map +1 -1
- package/dist/agent-runtime/harness.d.ts +0 -5
- package/dist/agent-runtime/harness.d.ts.map +1 -1
- package/dist/agent-runtime/managed-tools.d.ts +3 -2
- package/dist/agent-runtime/managed-tools.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts +1 -1
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/agent-runtime/openrouter.d.ts +29 -0
- package/dist/agent-runtime/openrouter.d.ts.map +1 -0
- package/dist/agent-runtime/projections.d.ts +1 -1
- package/dist/agent-runtime/projections.d.ts.map +1 -1
- package/dist/agent-runtime/run-acquisition.d.ts +17 -0
- package/dist/agent-runtime/run-acquisition.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution-state.d.ts +133 -0
- package/dist/agent-runtime/run-execution-state.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution.d.ts +17 -33
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/run-failure.d.ts +18 -0
- package/dist/agent-runtime/run-failure.d.ts.map +1 -0
- package/dist/agent-runtime/run-model-attempts.d.ts +6 -0
- package/dist/agent-runtime/run-model-attempts.d.ts.map +1 -0
- package/dist/agent-runtime/run-prompt-history.d.ts +58 -0
- package/dist/agent-runtime/run-prompt-history.d.ts.map +1 -0
- package/dist/agent-runtime/run-provider-ledger.d.ts +26 -0
- package/dist/agent-runtime/run-provider-ledger.d.ts.map +1 -0
- package/dist/agent-runtime/run-stream-parts.d.ts +17 -0
- package/dist/agent-runtime/run-stream-parts.d.ts.map +1 -0
- package/dist/agent-runtime/run-terminal.d.ts +11 -0
- package/dist/agent-runtime/run-terminal.d.ts.map +1 -0
- package/dist/agent-runtime/run-turn-preparation.d.ts +22 -0
- package/dist/agent-runtime/run-turn-preparation.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-admission-gate.d.ts +29 -0
- package/dist/agent-runtime/runtime-admission-gate.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-control.d.ts +9 -0
- package/dist/agent-runtime/runtime-control.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-recovery.d.ts +10 -0
- package/dist/agent-runtime/runtime-recovery.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-state.d.ts +36 -0
- package/dist/agent-runtime/runtime-state.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-submit.d.ts +5 -0
- package/dist/agent-runtime/runtime-submit.d.ts.map +1 -0
- package/dist/agent-runtime/runtime.d.ts +10 -5
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/schedule-records.d.ts +34 -0
- package/dist/agent-runtime/schedule-records.d.ts.map +1 -0
- package/dist/agent-runtime/schedules.d.ts +34 -0
- package/dist/agent-runtime/schedules.d.ts.map +1 -1
- package/dist/agent-runtime/sqlite-bun.d.ts +12 -0
- package/dist/agent-runtime/sqlite-bun.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-events.d.ts +5 -0
- package/dist/agent-runtime/sqlite-events.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-history.d.ts +7 -0
- package/dist/agent-runtime/sqlite-history.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-ledger.d.ts +7 -0
- package/dist/agent-runtime/sqlite-ledger.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-node.d.ts +12 -0
- package/dist/agent-runtime/sqlite-node.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-reads.d.ts +14 -0
- package/dist/agent-runtime/sqlite-reads.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-rows.d.ts +178 -0
- package/dist/agent-runtime/sqlite-rows.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-schema.d.ts +7 -0
- package/dist/agent-runtime/sqlite-schema.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-transactions.d.ts +25 -0
- package/dist/agent-runtime/sqlite-transactions.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite.d.ts +10 -6
- package/dist/agent-runtime/sqlite.d.ts.map +1 -1
- package/dist/agent-runtime/store-archive.d.ts +16 -0
- package/dist/agent-runtime/store-archive.d.ts.map +1 -0
- package/dist/agent-runtime/store-conformance-lifecycle.d.ts +434 -0
- package/dist/agent-runtime/store-conformance-lifecycle.d.ts.map +1 -0
- package/dist/agent-runtime/store-conformance-ordering.d.ts +18 -0
- package/dist/agent-runtime/store-conformance-ordering.d.ts.map +1 -0
- package/dist/agent-runtime/store-conformance-support.d.ts +130 -0
- package/dist/agent-runtime/store-conformance-support.d.ts.map +1 -0
- package/dist/agent-runtime/store-conformance-terminal.d.ts +12 -0
- package/dist/agent-runtime/store-conformance-terminal.d.ts.map +1 -0
- package/dist/agent-runtime/store-conformance.d.ts +40 -0
- package/dist/agent-runtime/store-conformance.d.ts.map +1 -0
- package/dist/agent-runtime/store-create.d.ts +11 -0
- package/dist/agent-runtime/store-create.d.ts.map +1 -0
- package/dist/agent-runtime/store-driver-contract.d.ts +814 -0
- package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -0
- package/dist/agent-runtime/store-events.d.ts +7 -89
- package/dist/agent-runtime/store-events.d.ts.map +1 -1
- package/dist/agent-runtime/store-memory.d.ts +4 -0
- package/dist/agent-runtime/store-memory.d.ts.map +1 -0
- package/dist/agent-runtime/store-mutate.d.ts +13 -0
- package/dist/agent-runtime/store-mutate.d.ts.map +1 -0
- package/dist/agent-runtime/store-purge.d.ts +1 -1
- package/dist/agent-runtime/store-purge.d.ts.map +1 -1
- package/dist/agent-runtime/store-reads.d.ts +29 -0
- package/dist/agent-runtime/store-reads.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-history.d.ts +10 -0
- package/dist/agent-runtime/store-reduce-history.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-run.d.ts +14 -0
- package/dist/agent-runtime/store-reduce-run.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-shared.d.ts +76 -0
- package/dist/agent-runtime/store-reduce-shared.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-terminal.d.ts +6 -0
- package/dist/agent-runtime/store-reduce-terminal.d.ts.map +1 -0
- package/dist/agent-runtime/store-reducer.d.ts +24 -0
- package/dist/agent-runtime/store-reducer.d.ts.map +1 -0
- package/dist/agent-runtime/store-snapshot.d.ts +19 -0
- package/dist/agent-runtime/store-snapshot.d.ts.map +1 -0
- package/dist/agent-runtime/store.d.ts +1 -1
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/application/admission-rate.d.ts +21 -0
- package/dist/application/admission-rate.d.ts.map +1 -0
- package/dist/application/admission-run.d.ts +8 -0
- package/dist/application/admission-run.d.ts.map +1 -0
- package/dist/application/admission.d.ts +1 -1
- package/dist/application/admission.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-lock.d.ts +9 -34
- package/dist/application/diagnostic-journal-lock.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-manager.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-storage.d.ts.map +1 -1
- package/dist/application/events.d.ts +1 -1
- package/dist/application/events.d.ts.map +1 -1
- package/dist/application/file-state-store.d.ts +23 -0
- package/dist/application/file-state-store.d.ts.map +1 -0
- package/dist/application/health.d.ts +1 -1
- package/dist/application/health.d.ts.map +1 -1
- package/dist/application/kernel-admission.d.ts +8 -0
- package/dist/application/kernel-admission.d.ts.map +1 -0
- package/dist/application/kernel-contract.d.ts +142 -0
- package/dist/application/kernel-contract.d.ts.map +1 -0
- package/dist/application/kernel-resource-context.d.ts +24 -0
- package/dist/application/kernel-resource-context.d.ts.map +1 -0
- package/dist/application/kernel-restart.d.ts +5 -0
- package/dist/application/kernel-restart.d.ts.map +1 -0
- package/dist/application/kernel-rollback.d.ts +4 -0
- package/dist/application/kernel-rollback.d.ts.map +1 -0
- package/dist/application/kernel-shutdown.d.ts +6 -0
- package/dist/application/kernel-shutdown.d.ts.map +1 -0
- package/dist/application/kernel-start.d.ts +20 -0
- package/dist/application/kernel-start.d.ts.map +1 -0
- package/dist/application/kernel-state.d.ts +116 -0
- package/dist/application/kernel-state.d.ts.map +1 -0
- package/dist/application/kernel.d.ts +9 -140
- package/dist/application/kernel.d.ts.map +1 -1
- package/dist/application/latest-sink.d.ts +1 -1
- package/dist/application/latest-sink.d.ts.map +1 -1
- package/dist/application/notification-outbox.d.ts.map +1 -1
- package/dist/application/opentelemetry.d.ts +1 -1
- package/dist/application/opentelemetry.d.ts.map +1 -1
- package/dist/application/schedule.d.ts +0 -1
- package/dist/application/schedule.d.ts.map +1 -1
- package/dist/application/watch-hub-core.d.ts +122 -0
- package/dist/application/watch-hub-core.d.ts.map +1 -0
- package/dist/application/watch-hub.d.ts +3 -3
- package/dist/application/watch-hub.d.ts.map +1 -1
- package/dist/browser/client-executors.d.ts +14 -0
- package/dist/browser/client-executors.d.ts.map +1 -0
- package/dist/browser/client-multipart.d.ts +1 -1
- package/dist/browser/client-multipart.d.ts.map +1 -1
- package/dist/browser/client-url.d.ts +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/browser/client.d.ts +3 -2
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http-fetch.d.ts +22 -0
- package/dist/browser/http-fetch.d.ts.map +1 -0
- package/dist/browser/http.d.ts +2 -4
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/live-state-core.d.ts +63 -0
- package/dist/browser/live-state-core.d.ts.map +1 -0
- package/dist/browser/live-state.d.ts.map +1 -1
- package/dist/browser/realtime-client.d.ts +26 -0
- package/dist/browser/realtime-client.d.ts.map +1 -0
- package/dist/browser/resumable.d.ts +1 -28
- package/dist/browser/resumable.d.ts.map +1 -1
- package/dist/browser/retained.d.ts.map +1 -0
- package/dist/browser/socket-io-connection.d.ts +82 -0
- package/dist/browser/socket-io-connection.d.ts.map +1 -0
- package/dist/browser/socket-io-peer.d.ts +20 -0
- package/dist/browser/socket-io-peer.d.ts.map +1 -0
- package/dist/browser/socket-io-trace.d.ts +31 -0
- package/dist/browser/socket-io-trace.d.ts.map +1 -0
- package/dist/browser/socket-io.d.ts +24 -18
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/browser/stream.d.ts +14 -2
- package/dist/browser/stream.d.ts.map +1 -1
- package/dist/contract/client-types.d.ts +134 -0
- package/dist/contract/client-types.d.ts.map +1 -0
- package/dist/contract/define-validation.d.ts +30 -0
- package/dist/contract/define-validation.d.ts.map +1 -0
- package/dist/contract/define.d.ts +47 -394
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/normalize.d.ts +56 -0
- package/dist/contract/normalize.d.ts.map +1 -0
- package/dist/contract/runtime-context.d.ts +131 -0
- package/dist/contract/runtime-context.d.ts.map +1 -0
- package/dist/contract/tool-options.d.ts +105 -0
- package/dist/contract/tool-options.d.ts.map +1 -0
- package/dist/contract/tool-view.d.ts +148 -0
- package/dist/contract/tool-view.d.ts.map +1 -0
- package/dist/declaration/declaration.d.ts.map +1 -0
- package/dist/durability/contract.d.ts +259 -0
- package/dist/durability/contract.d.ts.map +1 -0
- package/dist/durability/effect.d.ts +24 -0
- package/dist/durability/effect.d.ts.map +1 -0
- package/dist/durability/engine.d.ts +14 -0
- package/dist/durability/engine.d.ts.map +1 -0
- package/dist/durability/events.d.ts +103 -0
- package/dist/durability/events.d.ts.map +1 -0
- package/dist/durability/json.d.ts +3 -0
- package/dist/durability/json.d.ts.map +1 -0
- package/dist/durability/ledger.d.ts +27 -0
- package/dist/durability/ledger.d.ts.map +1 -0
- package/dist/durability/parks.d.ts +22 -0
- package/dist/durability/parks.d.ts.map +1 -0
- package/dist/durability/scheduler.d.ts +30 -0
- package/dist/durability/scheduler.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/browser.d.ts +5 -0
- package/dist/entrypoints/agent-runtime/browser.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/browser.js +124 -0
- package/dist/entrypoints/agent-runtime/coding-tools.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/coding-tools.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/coding-tools.js +22 -0
- package/dist/entrypoints/agent-runtime/harness.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/harness.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/harness.js +739 -0
- package/dist/entrypoints/agent-runtime/openrouter.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/openrouter.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/openrouter.js +270 -0
- package/dist/entrypoints/agent-runtime/sandbox.d.ts +5 -0
- package/dist/entrypoints/agent-runtime/sandbox.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/sandbox.js +797 -0
- package/dist/entrypoints/agent-runtime/sqlite/bun.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/sqlite/bun.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +44 -0
- package/dist/entrypoints/agent-runtime/sqlite/node.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/sqlite/node.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/sqlite/node.js +46 -0
- package/dist/entrypoints/agent-runtime/testing.d.ts +4 -0
- package/dist/entrypoints/agent-runtime/testing.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/testing.js +1116 -0
- package/dist/entrypoints/agent-runtime.d.ts +42 -0
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime.js +2670 -0
- package/dist/entrypoints/application/diagnostic-journal.d.ts +21 -0
- package/dist/entrypoints/application/diagnostic-journal.d.ts.map +1 -0
- package/dist/entrypoints/application/diagnostic-journal.js +492 -0
- package/dist/entrypoints/application/grammy.d.ts +2 -0
- package/dist/entrypoints/application/grammy.d.ts.map +1 -0
- package/dist/entrypoints/application/grammy.js +169 -0
- package/dist/entrypoints/application/opentelemetry.d.ts +2 -0
- package/dist/entrypoints/application/opentelemetry.d.ts.map +1 -0
- package/dist/entrypoints/application/opentelemetry.js +250 -0
- package/dist/entrypoints/application/schemas.d.ts +15 -0
- package/dist/entrypoints/application/schemas.d.ts.map +1 -0
- package/dist/entrypoints/application/schemas.js +27 -0
- package/dist/entrypoints/application.d.ts +23 -0
- package/dist/entrypoints/application.d.ts.map +1 -0
- package/dist/entrypoints/application.js +2016 -0
- package/dist/entrypoints/bin/upgrade-cli.d.ts.map +1 -0
- package/dist/entrypoints/bin/upgrade-cli.js +177 -0
- package/dist/entrypoints/cli.d.ts +44 -0
- package/dist/entrypoints/cli.d.ts.map +1 -0
- package/dist/entrypoints/cli.js +773 -0
- package/dist/entrypoints/contract.d.ts +12 -0
- package/dist/entrypoints/contract.d.ts.map +1 -0
- package/dist/entrypoints/contract.js +64 -0
- package/dist/entrypoints/declaration.d.ts +2 -0
- package/dist/entrypoints/declaration.d.ts.map +1 -0
- package/dist/entrypoints/declaration.js +183 -0
- package/dist/entrypoints/files.d.ts +5 -0
- package/dist/entrypoints/files.d.ts.map +1 -0
- package/dist/entrypoints/files.js +111 -0
- package/dist/entrypoints/geo.d.ts +3 -0
- package/dist/entrypoints/geo.d.ts.map +1 -0
- package/dist/entrypoints/geo.js +245 -0
- package/dist/entrypoints/google.d.ts +2 -0
- package/dist/entrypoints/google.d.ts.map +1 -0
- package/dist/entrypoints/google.js +175 -0
- package/dist/entrypoints/index.d.ts +17 -0
- package/dist/entrypoints/index.d.ts.map +1 -0
- package/dist/entrypoints/index.js +764 -0
- package/dist/entrypoints/live.d.ts +19 -0
- package/dist/entrypoints/live.d.ts.map +1 -0
- package/dist/entrypoints/live.js +302 -0
- package/dist/entrypoints/node.d.ts +19 -0
- package/dist/entrypoints/node.d.ts.map +1 -0
- package/dist/entrypoints/node.js +308 -0
- package/dist/entrypoints/oauth.d.ts +2 -0
- package/dist/entrypoints/oauth.d.ts.map +1 -0
- package/dist/entrypoints/oauth.js +170 -0
- package/dist/entrypoints/observability.d.ts +19 -0
- package/dist/entrypoints/observability.d.ts.map +1 -0
- package/dist/entrypoints/observability.js +567 -0
- package/dist/entrypoints/primitives.d.ts +3 -0
- package/dist/entrypoints/primitives.d.ts.map +1 -0
- package/dist/entrypoints/primitives.js +749 -0
- package/dist/entrypoints/react.d.ts +5 -0
- package/dist/entrypoints/react.d.ts.map +1 -0
- package/dist/entrypoints/react.js +403 -0
- package/dist/entrypoints/release.d.ts +15 -0
- package/dist/entrypoints/release.d.ts.map +1 -0
- package/dist/entrypoints/release.js +196 -0
- package/dist/entrypoints/remote.d.ts +9 -0
- package/dist/entrypoints/remote.d.ts.map +1 -0
- package/dist/entrypoints/remote.js +66 -0
- package/dist/entrypoints/server.d.ts +39 -0
- package/dist/entrypoints/server.d.ts.map +1 -0
- package/dist/entrypoints/server.js +2009 -0
- package/dist/entrypoints/telegram.d.ts +3 -0
- package/dist/entrypoints/telegram.d.ts.map +1 -0
- package/dist/entrypoints/telegram.js +224 -0
- package/dist/entrypoints/testing.d.ts +10 -0
- package/dist/entrypoints/testing.d.ts.map +1 -0
- package/dist/entrypoints/testing.js +1377 -0
- package/dist/entrypoints/tools/connections.d.ts +19 -0
- package/dist/entrypoints/tools/connections.d.ts.map +1 -0
- package/dist/entrypoints/tools/connections.js +1110 -0
- package/dist/entrypoints/tools/contract.d.ts +29 -0
- package/dist/entrypoints/tools/contract.d.ts.map +1 -0
- package/dist/entrypoints/tools/contract.js +24 -0
- package/dist/entrypoints/tools/invoker.d.ts +9 -0
- package/dist/entrypoints/tools/invoker.d.ts.map +1 -0
- package/dist/entrypoints/tools/invoker.js +20 -0
- package/dist/entrypoints/tools.d.ts +54 -0
- package/dist/entrypoints/tools.d.ts.map +1 -0
- package/dist/entrypoints/tools.js +3009 -0
- package/dist/entrypoints/tracking/server.d.ts +15 -0
- package/dist/entrypoints/tracking/server.d.ts.map +1 -0
- package/dist/entrypoints/tracking.d.ts +24 -0
- package/dist/entrypoints/tracking.d.ts.map +1 -0
- package/dist/entrypoints/tracking.js +1140 -0
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files/file-io.d.ts +14 -0
- package/dist/files/file-io.d.ts.map +1 -0
- package/dist/google/google.d.ts.map +1 -0
- package/dist/index-0b1ess5e.js +38 -0
- package/dist/index-1j8pm5t0.js +412 -0
- package/dist/index-1zbbzgfh.js +89 -0
- package/dist/index-2bxbyab0.js +47 -0
- package/dist/index-2gxzjvyr.js +266 -0
- package/dist/index-35mh5bhj.js +177 -0
- package/dist/index-48ffdxgk.js +6 -0
- package/dist/index-4gcnakhx.js +82 -0
- package/dist/index-55hkhm67.js +343 -0
- package/dist/index-79z7m7dt.js +675 -0
- package/dist/index-7f68ghdn.js +3618 -0
- package/dist/index-7p1f0vp2.js +154 -0
- package/dist/index-83bdg97s.js +16 -0
- package/dist/index-863ea9jb.js +154 -0
- package/dist/index-8p52kd5p.js +242 -0
- package/dist/index-d9pk3td4.js +326 -0
- package/dist/index-dce5wxcq.js +63 -0
- package/dist/index-e01r054k.js +105 -0
- package/dist/index-ensd0r74.js +595 -0
- package/dist/index-eqqvgdb3.js +46 -0
- package/dist/index-gxrt5h6v.js +341 -0
- package/dist/index-h6eeys1v.js +211 -0
- package/dist/index-ham5sz8m.js +734 -0
- package/dist/index-het2he5g.js +1387 -0
- package/dist/index-hffjpmq9.js +30 -0
- package/dist/index-hmp4800j.js +51 -0
- package/dist/index-k30h66yd.js +317 -0
- package/dist/index-k5hsfc3j.js +31 -0
- package/dist/index-khtgb21r.js +92 -0
- package/dist/index-m9zn6cpq.js +51 -0
- package/dist/index-ndddxtyx.js +324 -0
- package/dist/index-ngfhvfpd.js +30 -0
- package/dist/index-np4m8hd3.js +1205 -0
- package/dist/index-p3vsv1fb.js +3493 -0
- package/dist/index-p5r7qpdd.js +85 -0
- package/dist/index-p9e0w8hs.js +1084 -0
- package/dist/index-p9sh0xx4.js +64 -0
- package/dist/index-paz6sgdp.js +371 -0
- package/dist/index-px2gm2jp.js +448 -0
- package/dist/index-qwhynv6x.js +103 -0
- package/dist/index-s812ycat.js +65 -0
- package/dist/index-sp1tzwnb.js +281 -0
- package/dist/index-t9rhjr9f.js +2306 -0
- package/dist/index-v260df89.js +342 -0
- package/dist/index-w906953s.js +946 -0
- package/dist/index-wdkt63w3.js +214 -0
- package/dist/index-wqgjfstn.js +18 -0
- package/dist/index-wsm5w4ee.js +1023 -0
- package/dist/index-xw3s75nj.js +100 -0
- package/dist/index-yw4fzmdz.js +268 -0
- package/dist/index-zfrhfn82.js +85 -0
- package/dist/index-znfs20ss.js +98 -0
- package/dist/internal/atomic-file.d.ts +23 -2
- package/dist/internal/atomic-file.d.ts.map +1 -1
- package/dist/internal/backoff.d.ts +29 -0
- package/dist/internal/backoff.d.ts.map +1 -0
- package/dist/internal/canonical-json.d.ts +21 -11
- package/dist/internal/canonical-json.d.ts.map +1 -1
- package/dist/internal/channel.d.ts.map +1 -0
- package/dist/internal/exclusive-lock.d.ts +51 -0
- package/dist/internal/exclusive-lock.d.ts.map +1 -0
- package/dist/internal/logger.d.ts.map +1 -0
- package/dist/internal/process-identity.d.ts +47 -0
- package/dist/internal/process-identity.d.ts.map +1 -0
- package/dist/internal/request.d.ts.map +1 -0
- package/dist/internal/stable-digest.d.ts +0 -43
- package/dist/internal/stable-digest.d.ts.map +1 -1
- package/dist/internal/typed.d.ts +0 -8
- package/dist/internal/typed.d.ts.map +1 -1
- package/dist/internal/with-exclusive-lock.d.ts +67 -0
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -0
- package/dist/internal/zod-issues.d.ts +38 -0
- package/dist/internal/zod-issues.d.ts.map +1 -0
- package/dist/json-schema/dialect.d.ts +17 -0
- package/dist/json-schema/dialect.d.ts.map +1 -0
- package/dist/json-schema/json-schema.d.ts.map +1 -0
- package/dist/live/watch-client.d.ts.map +1 -1
- package/dist/live/watch-delta.d.ts.map +1 -1
- package/dist/oauth/authorization-code.d.ts +57 -0
- package/dist/oauth/authorization-code.d.ts.map +1 -0
- package/dist/observability/audit.d.ts +3 -3
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/bounded-logger.d.ts +1 -1
- package/dist/observability/bounded-logger.d.ts.map +1 -1
- package/dist/observability/bounded-sink.d.ts +81 -0
- package/dist/observability/bounded-sink.d.ts.map +1 -0
- package/dist/observability/context.d.ts +1 -1
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/event.d.ts +2 -1
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/observability/work.d.ts +1 -1
- package/dist/observability/work.d.ts.map +1 -1
- package/dist/primitives/audit.d.ts +1 -1
- package/dist/primitives/audit.d.ts.map +1 -1
- package/dist/primitives/export-operation.d.ts +4 -2
- package/dist/primitives/export-operation.d.ts.map +1 -1
- package/dist/realtime/registry.d.ts +0 -5
- package/dist/realtime/registry.d.ts.map +1 -1
- package/dist/realtime/socket.d.ts +1 -1
- package/dist/realtime/socket.d.ts.map +1 -1
- package/dist/server/bun.d.ts +0 -1
- package/dist/server/bun.d.ts.map +1 -1
- package/dist/server/context-contribution.d.ts +1 -1
- package/dist/server/context-contribution.d.ts.map +1 -1
- package/dist/server/context.d.ts +2 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract-method.d.ts +15 -0
- package/dist/server/contract-method.d.ts.map +1 -0
- package/dist/server/contract-stream.d.ts +1 -1
- package/dist/server/contract-stream.d.ts.map +1 -1
- package/dist/server/create.d.ts +8 -0
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/endpoint-dispatch.d.ts +10 -0
- package/dist/server/endpoint-dispatch.d.ts.map +1 -0
- package/dist/server/endpoint-response.d.ts +19 -0
- package/dist/server/endpoint-response.d.ts.map +1 -0
- package/dist/server/error-dispatch.d.ts +17 -1
- package/dist/server/error-dispatch.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +2 -2
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/handler-state.d.ts +44 -0
- package/dist/server/handler-state.d.ts.map +1 -0
- package/dist/server/implement-registry.d.ts +54 -0
- package/dist/server/implement-registry.d.ts.map +1 -0
- package/dist/server/implement.d.ts +9 -55
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +1 -31
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts +17 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/middleware/jwt.d.ts +31 -0
- package/dist/server/middleware/jwt.d.ts.map +1 -0
- package/dist/server/middleware/trust-fence.d.ts +1 -1
- package/dist/server/middleware/trust-fence.d.ts.map +1 -1
- package/dist/server/multipart-reader.d.ts +22 -0
- package/dist/server/multipart-reader.d.ts.map +1 -0
- package/dist/server/multipart.d.ts +1 -1
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/oauth/cimd.d.ts +88 -0
- package/dist/server/oauth/cimd.d.ts.map +1 -0
- package/dist/server/oauth/endpoints.d.ts +39 -0
- package/dist/server/oauth/endpoints.d.ts.map +1 -0
- package/dist/server/oauth/metadata.d.ts +28 -0
- package/dist/server/oauth/metadata.d.ts.map +1 -0
- package/dist/server/oauth/provider.d.ts +117 -0
- package/dist/server/oauth/provider.d.ts.map +1 -0
- package/dist/server/realtime.d.ts +1 -1
- package/dist/server/realtime.d.ts.map +1 -1
- package/dist/server/request-completion.d.ts +16 -0
- package/dist/server/request-completion.d.ts.map +1 -0
- package/dist/server/response-metadata.d.ts +1 -1
- package/dist/server/response-metadata.d.ts.map +1 -1
- package/dist/server/route-table.d.ts +9 -0
- package/dist/server/route-table.d.ts.map +1 -0
- package/dist/server/stream.d.ts +0 -13
- package/dist/server/stream.d.ts.map +1 -1
- package/dist/server/streaming-pump.d.ts +66 -0
- package/dist/server/streaming-pump.d.ts.map +1 -0
- package/dist/server/streaming-route.d.ts +12 -5
- package/dist/server/streaming-route.d.ts.map +1 -1
- package/dist/server/types.d.ts +10 -4
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/unix-client-bun.d.ts.map +1 -1
- package/dist/server/unix-client-http1.d.ts +76 -0
- package/dist/server/unix-client-http1.d.ts.map +1 -0
- package/dist/server/unix-client.d.ts.map +1 -1
- package/dist/testing/handler-clients.d.ts +32 -0
- package/dist/testing/handler-clients.d.ts.map +1 -0
- package/dist/testing/managed-resource-conformance-scenarios.d.ts.map +1 -1
- package/dist/testing/realtime-probe.d.ts +33 -0
- package/dist/testing/realtime-probe.d.ts.map +1 -0
- package/dist/testing/surface-conformance.d.ts +2 -32
- package/dist/testing/surface-conformance.d.ts.map +1 -1
- package/dist/testing/surface-manifest-digest.d.ts +44 -0
- package/dist/testing/surface-manifest-digest.d.ts.map +1 -0
- package/dist/testing/surface-manifest.d.ts +30 -4
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli/args-fields.d.ts +31 -0
- package/dist/tools/cli/args-fields.d.ts.map +1 -0
- package/dist/tools/cli/args-route.d.ts +42 -0
- package/dist/tools/cli/args-route.d.ts.map +1 -0
- package/dist/tools/cli/args-view.d.ts +32 -0
- package/dist/tools/cli/args-view.d.ts.map +1 -0
- package/dist/tools/cli/args.d.ts +80 -0
- package/dist/tools/cli/args.d.ts.map +1 -0
- package/dist/tools/cli/argument-error.d.ts +5 -0
- package/dist/tools/cli/argument-error.d.ts.map +1 -0
- package/dist/tools/cli/checkpoint.d.ts +24 -0
- package/dist/tools/cli/checkpoint.d.ts.map +1 -0
- package/dist/tools/cli/command.d.ts +70 -0
- package/dist/tools/cli/command.d.ts.map +1 -0
- package/dist/tools/cli/config.d.ts +90 -0
- package/dist/tools/cli/config.d.ts.map +1 -0
- package/dist/tools/cli/create-cli.d.ts +36 -0
- package/dist/tools/cli/create-cli.d.ts.map +1 -0
- package/dist/tools/cli/download.d.ts +8 -0
- package/dist/tools/cli/download.d.ts.map +1 -0
- package/dist/tools/cli/format.d.ts +50 -0
- package/dist/tools/cli/format.d.ts.map +1 -0
- package/dist/tools/cli/help.d.ts +24 -0
- package/dist/tools/cli/help.d.ts.map +1 -0
- package/dist/tools/cli/installer.d.ts +34 -0
- package/dist/tools/cli/installer.d.ts.map +1 -0
- package/dist/tools/cli/invoke.d.ts +144 -0
- package/dist/tools/cli/invoke.d.ts.map +1 -0
- package/dist/tools/cli/io.d.ts +10 -0
- package/dist/tools/cli/io.d.ts.map +1 -0
- package/dist/tools/cli/managed-surface.d.ts +30 -0
- package/dist/tools/cli/managed-surface.d.ts.map +1 -0
- package/dist/tools/cli/manifest.d.ts +80 -0
- package/dist/tools/cli/manifest.d.ts.map +1 -0
- package/dist/tools/cli/policy.d.ts +27 -0
- package/dist/tools/cli/policy.d.ts.map +1 -0
- package/dist/tools/cli/prepare.d.ts +14 -0
- package/dist/tools/cli/prepare.d.ts.map +1 -0
- package/dist/tools/cli/profile.d.ts +40 -0
- package/dist/tools/cli/profile.d.ts.map +1 -0
- package/dist/tools/cli/run-managed.d.ts +5 -0
- package/dist/tools/cli/run-managed.d.ts.map +1 -0
- package/dist/tools/cli/run-native.d.ts +9 -0
- package/dist/tools/cli/run-native.d.ts.map +1 -0
- package/dist/tools/cli/session.d.ts +40 -0
- package/dist/tools/cli/session.d.ts.map +1 -0
- package/dist/tools/cli/signature.d.ts +81 -0
- package/dist/tools/cli/signature.d.ts.map +1 -0
- package/dist/tools/cli/stream.d.ts +93 -0
- package/dist/tools/cli/stream.d.ts.map +1 -0
- package/dist/tools/cli/update.d.ts +145 -0
- package/dist/tools/cli/update.d.ts.map +1 -0
- package/dist/tools/cli/view.d.ts +12 -0
- package/dist/tools/cli/view.d.ts.map +1 -0
- package/dist/tools/cli/wait.d.ts +54 -0
- package/dist/tools/cli/wait.d.ts.map +1 -0
- package/dist/tools/connections/runtime.d.ts +1 -1
- package/dist/tools/connections/runtime.d.ts.map +1 -1
- package/dist/tools/connections/types.d.ts +3 -3
- package/dist/tools/connections/types.d.ts.map +1 -1
- package/dist/tools/durability-context.d.ts +1 -1
- package/dist/tools/durability-context.d.ts.map +1 -1
- package/dist/tools/execute-args.d.ts +23 -0
- package/dist/tools/execute-args.d.ts.map +1 -0
- package/dist/tools/execute-hooks.d.ts +146 -0
- package/dist/tools/execute-hooks.d.ts.map +1 -0
- package/dist/tools/execute-result.d.ts +40 -0
- package/dist/tools/execute-result.d.ts.map +1 -0
- package/dist/tools/execute.d.ts +31 -193
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +9 -6
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/internal/tool-view.d.ts +41 -0
- package/dist/tools/internal/tool-view.d.ts.map +1 -0
- package/dist/tools/invoker.d.ts +13 -4
- package/dist/tools/invoker.d.ts.map +1 -1
- package/dist/tools/lifecycle.d.ts +1 -1
- package/dist/tools/lifecycle.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +3 -3
- package/dist/tools/list-names.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts +2 -2
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/mcp/app.d.ts +62 -0
- package/dist/tools/mcp/app.d.ts.map +1 -0
- package/dist/tools/mcp/catalog.d.ts +68 -0
- package/dist/tools/mcp/catalog.d.ts.map +1 -0
- package/dist/tools/mcp/handler.d.ts +46 -0
- package/dist/tools/mcp/handler.d.ts.map +1 -0
- package/dist/tools/mcp/mount.d.ts +152 -0
- package/dist/tools/mcp/mount.d.ts.map +1 -0
- package/dist/tools/mcp/prepare.d.ts +76 -0
- package/dist/tools/mcp/prepare.d.ts.map +1 -0
- package/dist/tools/mcp/progress.d.ts +46 -0
- package/dist/tools/mcp/progress.d.ts.map +1 -0
- package/dist/tools/mcp/register.d.ts +36 -0
- package/dist/tools/mcp/register.d.ts.map +1 -0
- package/dist/tools/mcp/round-context.d.ts +38 -0
- package/dist/tools/mcp/round-context.d.ts.map +1 -0
- package/dist/tools/mcp/round-policy.d.ts +33 -0
- package/dist/tools/mcp/round-policy.d.ts.map +1 -0
- package/dist/tools/mcp/round.d.ts +53 -0
- package/dist/tools/mcp/round.d.ts.map +1 -0
- package/dist/tools/mcp/runtime-tools.d.ts +25 -0
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -0
- package/dist/tools/mcp/stdio-signals.d.ts +33 -0
- package/dist/tools/mcp/stdio-signals.d.ts.map +1 -0
- package/dist/tools/mcp/stdio.d.ts +20 -0
- package/dist/tools/mcp/stdio.d.ts.map +1 -0
- package/dist/tools/mcp/trace.d.ts +12 -0
- package/dist/tools/mcp/trace.d.ts.map +1 -0
- package/dist/tools/mount.d.ts +14 -7
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/native-definition.d.ts +4 -3
- package/dist/tools/native-definition.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-binding-types.d.ts +127 -0
- package/dist/tools/operations/async-operation-binding-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-binding.d.ts +28 -0
- package/dist/tools/operations/async-operation-binding.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-canonical-types.d.ts +110 -0
- package/dist/tools/operations/async-operation-canonical-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-canonical.d.ts +7 -0
- package/dist/tools/operations/async-operation-canonical.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-contract.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-id.d.ts +23 -0
- package/dist/tools/operations/async-operation-id.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime-tools.d.ts +41 -0
- package/dist/tools/operations/async-operation-runtime-tools.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime-types.d.ts +69 -0
- package/dist/tools/operations/async-operation-runtime-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime.d.ts +5 -0
- package/dist/tools/operations/async-operation-runtime.d.ts.map +1 -0
- package/dist/tools/operations/define-wait-tool.d.ts +22 -0
- package/dist/tools/operations/define-wait-tool.d.ts.map +1 -0
- package/dist/tools/operations/mount-wait.d.ts.map +1 -0
- package/dist/tools/operations/wait-core.d.ts.map +1 -0
- package/dist/tools/remote.d.ts +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +13 -4
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/schema/coerce.d.ts.map +1 -0
- package/dist/tools/schema/flatten-join.d.ts.map +1 -0
- package/dist/tools/schema/flatten.d.ts.map +1 -0
- package/dist/tools/schema/portable-formats.d.ts.map +1 -0
- package/dist/tools/schema/presentation.d.ts +24 -0
- package/dist/tools/schema/presentation.d.ts.map +1 -0
- package/dist/tools/schema/schema.d.ts.map +1 -0
- package/dist/tools/schema/untyped-properties.d.ts.map +1 -0
- package/dist/tools/surface.d.ts +2 -3
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/tool-logger.d.ts +1 -1
- package/dist/tools/tool-logger.d.ts.map +1 -1
- package/dist/tools/toolkit.d.ts +4 -4
- package/dist/tools/toolkit.d.ts.map +1 -1
- package/dist/tools/transfer/define-download-tool.d.ts +18 -0
- package/dist/tools/transfer/define-download-tool.d.ts.map +1 -0
- package/dist/tools/transfer/define-upload-tool.d.ts +16 -0
- package/dist/tools/transfer/define-upload-tool.d.ts.map +1 -0
- package/dist/tools/transfer/define-view-file-tool.d.ts +10 -0
- package/dist/tools/transfer/define-view-file-tool.d.ts.map +1 -0
- package/dist/tools/transfer/download-core.d.ts +20 -0
- package/dist/tools/transfer/download-core.d.ts.map +1 -0
- package/dist/tools/transfer/managed-file-error.d.ts +6 -0
- package/dist/tools/transfer/managed-file-error.d.ts.map +1 -0
- package/dist/tools/transfer/mount-download.d.ts +41 -0
- package/dist/tools/transfer/mount-download.d.ts.map +1 -0
- package/dist/tools/transfer/mount-upload.d.ts +21 -0
- package/dist/tools/transfer/mount-upload.d.ts.map +1 -0
- package/dist/tools/transfer/native-result.d.ts.map +1 -0
- package/dist/tools/transfer/upload-core.d.ts +4 -0
- package/dist/tools/transfer/upload-core.d.ts.map +1 -0
- package/dist/tools/transfer/view-file-contract.d.ts.map +1 -0
- package/dist/tools/transfer/view-file.d.ts +62 -0
- package/dist/tools/transfer/view-file.d.ts.map +1 -0
- package/dist/tools/transports.d.ts +1 -1
- package/dist/tools/transports.d.ts.map +1 -1
- package/dist/tracking/client-listeners.d.ts +30 -0
- package/dist/tracking/client-listeners.d.ts.map +1 -0
- package/dist/tracking/client-visit.d.ts +61 -0
- package/dist/tracking/client-visit.d.ts.map +1 -0
- package/dist/tracking/client.d.ts +3 -2
- package/dist/tracking/client.d.ts.map +1 -1
- package/dist/tracking/contract.d.ts +1 -1
- package/dist/tracking/contract.d.ts.map +1 -1
- package/llms/agent-runtime-browser.txt +33 -0
- package/llms/agent-runtime-coding-tools.txt +58 -0
- package/llms/agent-runtime-harness.txt +45 -0
- package/llms/agent-runtime-openrouter.txt +22 -0
- package/llms/agent-runtime-sandbox.txt +159 -0
- package/llms/agent-runtime-sqlite-bun.txt +29 -0
- package/llms/agent-runtime-sqlite-node.txt +26 -0
- package/llms/agent-runtime-testing.txt +30 -0
- package/llms/agent-runtime.2.txt +550 -0
- package/llms/agent-runtime.3.txt +311 -0
- package/llms/agent-runtime.txt +893 -0
- package/llms/application-diagnostic-journal.txt +30 -0
- package/llms/application-grammy.txt +27 -0
- package/llms/application-opentelemetry.txt +27 -0
- package/llms/application-schemas.txt +42 -0
- package/llms/application.2.txt +487 -0
- package/llms/application.txt +886 -0
- package/llms/cli.2.txt +80 -0
- package/llms/cli.txt +948 -0
- package/llms/contract.txt +606 -0
- package/llms/declaration.txt +286 -0
- package/llms/files.txt +33 -0
- package/llms/geo.txt +114 -0
- package/llms/google.txt +27 -0
- package/llms/live.txt +471 -0
- package/llms/node.txt +43 -0
- package/llms/oauth.txt +136 -0
- package/llms/observability.txt +893 -0
- package/llms/primitives.txt +158 -0
- package/llms/react.txt +184 -0
- package/llms/release.txt +176 -0
- package/llms/remote.txt +25 -0
- package/llms/server.2.txt +361 -0
- package/llms/server.3.txt +1031 -0
- package/llms/server.4.txt +905 -0
- package/llms/server.5.txt +250 -0
- package/llms/server.txt +996 -0
- package/llms/stitchkit.2.txt +836 -0
- package/llms/stitchkit.3.txt +147 -0
- package/llms/stitchkit.txt +260 -0
- package/llms/telegram.txt +30 -0
- package/llms/testing.txt +676 -0
- package/llms/tools-connections.txt +45 -0
- package/llms/tools-contract.txt +9 -0
- package/llms/tools-invoker.txt +9 -0
- package/llms/tools.2.txt +647 -0
- package/llms/tools.3.txt +288 -0
- package/llms/tools.txt +966 -0
- package/llms/tracking-server.txt +32 -0
- package/llms/tracking.txt +263 -0
- package/llms/upgrading-0.37.0-0.50.0.txt +947 -0
- package/llms/upgrading-0.53.0-0.67.0.txt +1174 -0
- package/llms/upgrading-0.68.0-0.94.0.txt +1159 -0
- package/llms/upgrading.txt +215 -0
- package/llms-full.txt +312 -77
- package/llms.txt +54 -5
- package/package.json +84 -83
- package/dist/agent-runtime/durability-contract.d.ts +0 -176
- package/dist/agent-runtime/durability-contract.d.ts.map +0 -1
- package/dist/agent-runtime/durability-json.d.ts +0 -3
- package/dist/agent-runtime/durability-json.d.ts.map +0 -1
- package/dist/agent-runtime/durability-ledger.d.ts +0 -16
- package/dist/agent-runtime/durability-ledger.d.ts.map +0 -1
- package/dist/agent-runtime/durability-scheduler.d.ts +0 -29
- package/dist/agent-runtime/durability-scheduler.d.ts.map +0 -1
- package/dist/agent-runtime/durability.d.ts +0 -14
- package/dist/agent-runtime/durability.d.ts.map +0 -1
- package/dist/agent-runtime/store-driver.d.ts +0 -814
- package/dist/agent-runtime/store-driver.d.ts.map +0 -1
- package/dist/agent-runtime-browser.d.ts +0 -5
- package/dist/agent-runtime-browser.d.ts.map +0 -1
- package/dist/agent-runtime-browser.js +0 -124
- package/dist/agent-runtime-coding-tools.d.ts +0 -2
- package/dist/agent-runtime-coding-tools.d.ts.map +0 -1
- package/dist/agent-runtime-coding-tools.js +0 -23
- package/dist/agent-runtime-harness.d.ts +0 -2
- package/dist/agent-runtime-harness.d.ts.map +0 -1
- package/dist/agent-runtime-harness.js +0 -729
- package/dist/agent-runtime-openrouter.d.ts +0 -29
- package/dist/agent-runtime-openrouter.d.ts.map +0 -1
- package/dist/agent-runtime-openrouter.js +0 -270
- package/dist/agent-runtime-sandbox.d.ts +0 -5
- package/dist/agent-runtime-sandbox.d.ts.map +0 -1
- package/dist/agent-runtime-sandbox.js +0 -798
- package/dist/agent-runtime-sqlite-bun.d.ts +0 -11
- package/dist/agent-runtime-sqlite-bun.d.ts.map +0 -1
- package/dist/agent-runtime-sqlite-bun.js +0 -42
- package/dist/agent-runtime-sqlite-node.d.ts +0 -11
- package/dist/agent-runtime-sqlite-node.d.ts.map +0 -1
- package/dist/agent-runtime-sqlite-node.js +0 -44
- package/dist/agent-runtime-testing.d.ts +0 -3
- package/dist/agent-runtime-testing.d.ts.map +0 -1
- package/dist/agent-runtime-testing.js +0 -156
- package/dist/agent-runtime.d.ts +0 -38
- package/dist/agent-runtime.d.ts.map +0 -1
- package/dist/agent-runtime.js +0 -2607
- package/dist/application/channel.d.ts.map +0 -1
- package/dist/application-diagnostic-journal.d.ts +0 -21
- package/dist/application-diagnostic-journal.d.ts.map +0 -1
- package/dist/application-diagnostic-journal.js +0 -632
- package/dist/application-grammy.d.ts +0 -2
- package/dist/application-grammy.d.ts.map +0 -1
- package/dist/application-grammy.js +0 -169
- package/dist/application-opentelemetry.d.ts +0 -2
- package/dist/application-opentelemetry.d.ts.map +0 -1
- package/dist/application-opentelemetry.js +0 -250
- package/dist/application-schemas.d.ts +0 -15
- package/dist/application-schemas.d.ts.map +0 -1
- package/dist/application-schemas.js +0 -27
- package/dist/application.d.ts +0 -22
- package/dist/application.d.ts.map +0 -1
- package/dist/application.js +0 -1977
- package/dist/cli.d.ts +0 -41
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -753
- package/dist/contract/index.d.ts +0 -8
- package/dist/contract/index.d.ts.map +0 -1
- package/dist/contract/index.js +0 -55
- package/dist/declaration.d.ts.map +0 -1
- package/dist/declaration.js +0 -183
- package/dist/files.d.ts +0 -3
- package/dist/files.d.ts.map +0 -1
- package/dist/files.js +0 -15
- package/dist/geo.d.ts +0 -3
- package/dist/geo.d.ts.map +0 -1
- package/dist/geo.js +0 -245
- package/dist/google.d.ts.map +0 -1
- package/dist/google.js +0 -175
- package/dist/index-0a349c33.js +0 -470
- package/dist/index-0j3vcp5m.js +0 -28
- package/dist/index-2c2bjw3s.js +0 -51
- package/dist/index-2na0qrfn.js +0 -214
- package/dist/index-2qzskm8h.js +0 -63
- package/dist/index-3bmnp41g.js +0 -2169
- package/dist/index-3xwxfj4z.js +0 -334
- package/dist/index-4fpa9dy2.js +0 -51
- package/dist/index-6kyksz9y.js +0 -268
- package/dist/index-77fekveh.js +0 -44
- package/dist/index-7gnkny4z.js +0 -326
- package/dist/index-7zbps32p.js +0 -89
- package/dist/index-9f8ztey6.js +0 -588
- package/dist/index-a11g2kry.js +0 -866
- package/dist/index-a916km3r.js +0 -45
- package/dist/index-actkayzn.js +0 -367
- package/dist/index-bn2cjajt.js +0 -524
- package/dist/index-bx75zwt7.js +0 -63
- package/dist/index-cby4ar3v.js +0 -46
- package/dist/index-cnyk6te3.js +0 -77
- package/dist/index-cxqhvq21.js +0 -413
- package/dist/index-e0znv5gn.js +0 -176
- package/dist/index-e42wzzz5.js +0 -154
- package/dist/index-f477k430.js +0 -324
- package/dist/index-f9mb610r.js +0 -177
- package/dist/index-fadnxwna.js +0 -1046
- package/dist/index-fq77yfzc.js +0 -1303
- package/dist/index-h9etkkyk.js +0 -1126
- package/dist/index-hrbajay7.js +0 -1196
- package/dist/index-kw9t5ayn.js +0 -507
- package/dist/index-mzvgxp0n.js +0 -80
- package/dist/index-n0f51dfn.js +0 -3170
- package/dist/index-p13mwz16.js +0 -16
- package/dist/index-s8cnx02s.js +0 -90
- package/dist/index-sr93cwf9.js +0 -325
- package/dist/index-tmwcrptx.js +0 -85
- package/dist/index-v5573g1k.js +0 -142
- package/dist/index-vjpvjqpn.js +0 -86
- package/dist/index-vk3kq9sb.js +0 -3344
- package/dist/index-wc4x00bn.js +0 -103
- package/dist/index-wnfk50r0.js +0 -211
- package/dist/index-yewwh1w4.js +0 -647
- package/dist/index-yn8remb1.js +0 -290
- package/dist/index-ywd0y6m1.js +0 -394
- package/dist/index-zwkgfq1s.js +0 -345
- package/dist/index.d.ts +0 -14
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -632
- package/dist/internal/errors.d.ts +0 -92
- package/dist/internal/errors.d.ts.map +0 -1
- package/dist/internal/observability-sink.d.ts +0 -81
- package/dist/internal/observability-sink.d.ts.map +0 -1
- package/dist/live.d.ts +0 -19
- package/dist/live.d.ts.map +0 -1
- package/dist/live.js +0 -282
- package/dist/logger.d.ts.map +0 -1
- package/dist/node.d.ts +0 -18
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js +0 -303
- package/dist/oauth.d.ts +0 -57
- package/dist/oauth.d.ts.map +0 -1
- package/dist/oauth.js +0 -170
- package/dist/observability/index.d.ts +0 -19
- package/dist/observability/index.d.ts.map +0 -1
- package/dist/observability/index.js +0 -569
- package/dist/primitives.d.ts +0 -3
- package/dist/primitives.d.ts.map +0 -1
- package/dist/primitives.js +0 -754
- package/dist/react.d.ts +0 -5
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js +0 -403
- package/dist/release.d.ts +0 -15
- package/dist/release.d.ts.map +0 -1
- package/dist/release.js +0 -196
- package/dist/remote.d.ts +0 -9
- package/dist/remote.d.ts.map +0 -1
- package/dist/remote.js +0 -88
- package/dist/retained.d.ts.map +0 -1
- package/dist/server/file-state-store.d.ts +0 -23
- package/dist/server/file-state-store.d.ts.map +0 -1
- package/dist/server/index.d.ts +0 -36
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -1745
- package/dist/server/request.d.ts.map +0 -1
- package/dist/telegram.d.ts +0 -3
- package/dist/telegram.d.ts.map +0 -1
- package/dist/telegram.js +0 -224
- package/dist/testing/agent-store-conformance.d.ts +0 -40
- package/dist/testing/agent-store-conformance.d.ts.map +0 -1
- package/dist/testing.d.ts +0 -39
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -2209
- package/dist/tool-invoker.d.ts +0 -9
- package/dist/tool-invoker.d.ts.map +0 -1
- package/dist/tool-invoker.js +0 -21
- package/dist/tools/async-operation-contract.d.ts.map +0 -1
- package/dist/tools/async-operation.d.ts +0 -332
- package/dist/tools/async-operation.d.ts.map +0 -1
- package/dist/tools/cli-args.d.ts +0 -143
- package/dist/tools/cli-args.d.ts.map +0 -1
- package/dist/tools/cli-checkpoint.d.ts +0 -24
- package/dist/tools/cli-checkpoint.d.ts.map +0 -1
- package/dist/tools/cli-command.d.ts +0 -70
- package/dist/tools/cli-command.d.ts.map +0 -1
- package/dist/tools/cli-format.d.ts +0 -49
- package/dist/tools/cli-format.d.ts.map +0 -1
- package/dist/tools/cli-installer.d.ts +0 -34
- package/dist/tools/cli-installer.d.ts.map +0 -1
- package/dist/tools/cli-invoke.d.ts +0 -143
- package/dist/tools/cli-invoke.d.ts.map +0 -1
- package/dist/tools/cli-manifest.d.ts +0 -80
- package/dist/tools/cli-manifest.d.ts.map +0 -1
- package/dist/tools/cli-policy.d.ts +0 -27
- package/dist/tools/cli-policy.d.ts.map +0 -1
- package/dist/tools/cli-profile.d.ts +0 -40
- package/dist/tools/cli-profile.d.ts.map +0 -1
- package/dist/tools/cli-signature.d.ts +0 -81
- package/dist/tools/cli-signature.d.ts.map +0 -1
- package/dist/tools/cli-stream.d.ts +0 -93
- package/dist/tools/cli-stream.d.ts.map +0 -1
- package/dist/tools/cli-update.d.ts +0 -126
- package/dist/tools/cli-update.d.ts.map +0 -1
- package/dist/tools/cli-view.d.ts +0 -12
- package/dist/tools/cli-view.d.ts.map +0 -1
- package/dist/tools/cli-wait.d.ts +0 -54
- package/dist/tools/cli-wait.d.ts.map +0 -1
- package/dist/tools/cli.d.ts +0 -94
- package/dist/tools/cli.d.ts.map +0 -1
- package/dist/tools/coerce.d.ts.map +0 -1
- package/dist/tools/connections/index.d.ts +0 -20
- package/dist/tools/connections/index.d.ts.map +0 -1
- package/dist/tools/connections/index.js +0 -1107
- package/dist/tools/define-download-tool.d.ts +0 -18
- package/dist/tools/define-download-tool.d.ts.map +0 -1
- package/dist/tools/define-upload-tool.d.ts +0 -16
- package/dist/tools/define-upload-tool.d.ts.map +0 -1
- package/dist/tools/define-view-file-tool.d.ts +0 -10
- package/dist/tools/define-view-file-tool.d.ts.map +0 -1
- package/dist/tools/define-wait-tool.d.ts +0 -22
- package/dist/tools/define-wait-tool.d.ts.map +0 -1
- package/dist/tools/download-core.d.ts +0 -20
- package/dist/tools/download-core.d.ts.map +0 -1
- package/dist/tools/flatten-join.d.ts.map +0 -1
- package/dist/tools/flatten.d.ts.map +0 -1
- package/dist/tools/json-schema-dialect.d.ts +0 -17
- package/dist/tools/json-schema-dialect.d.ts.map +0 -1
- package/dist/tools/json-schema.d.ts.map +0 -1
- package/dist/tools/managed-file-error.d.ts +0 -6
- package/dist/tools/managed-file-error.d.ts.map +0 -1
- package/dist/tools/mcp-app.d.ts +0 -62
- package/dist/tools/mcp-app.d.ts.map +0 -1
- package/dist/tools/mcp-catalog.d.ts +0 -68
- package/dist/tools/mcp-catalog.d.ts.map +0 -1
- package/dist/tools/mcp-handler.d.ts +0 -46
- package/dist/tools/mcp-handler.d.ts.map +0 -1
- package/dist/tools/mcp-prepare.d.ts +0 -75
- package/dist/tools/mcp-prepare.d.ts.map +0 -1
- package/dist/tools/mcp-progress.d.ts +0 -46
- package/dist/tools/mcp-progress.d.ts.map +0 -1
- package/dist/tools/mcp-round-policy.d.ts +0 -33
- package/dist/tools/mcp-round-policy.d.ts.map +0 -1
- package/dist/tools/mcp-round.d.ts +0 -61
- package/dist/tools/mcp-round.d.ts.map +0 -1
- package/dist/tools/mcp-stdio-signals.d.ts +0 -33
- package/dist/tools/mcp-stdio-signals.d.ts.map +0 -1
- package/dist/tools/mcp-stdio.d.ts +0 -20
- package/dist/tools/mcp-stdio.d.ts.map +0 -1
- package/dist/tools/mcp-trace.d.ts +0 -12
- package/dist/tools/mcp-trace.d.ts.map +0 -1
- package/dist/tools/mcp.d.ts +0 -152
- package/dist/tools/mcp.d.ts.map +0 -1
- package/dist/tools/mount-download.d.ts +0 -41
- package/dist/tools/mount-download.d.ts.map +0 -1
- package/dist/tools/mount-upload.d.ts +0 -21
- package/dist/tools/mount-upload.d.ts.map +0 -1
- package/dist/tools/mount-wait.d.ts.map +0 -1
- package/dist/tools/native-mcp.d.ts +0 -20
- package/dist/tools/native-mcp.d.ts.map +0 -1
- package/dist/tools/native-result.d.ts.map +0 -1
- package/dist/tools/oauth-metadata.d.ts +0 -28
- package/dist/tools/oauth-metadata.d.ts.map +0 -1
- package/dist/tools/oauth-provider.d.ts +0 -184
- package/dist/tools/oauth-provider.d.ts.map +0 -1
- package/dist/tools/portable-formats.d.ts.map +0 -1
- package/dist/tools/presentation.d.ts +0 -22
- package/dist/tools/presentation.d.ts.map +0 -1
- package/dist/tools/schema.d.ts.map +0 -1
- package/dist/tools/untyped-properties.d.ts.map +0 -1
- package/dist/tools/upload-core.d.ts +0 -4
- package/dist/tools/upload-core.d.ts.map +0 -1
- package/dist/tools/view-file-contract.d.ts.map +0 -1
- package/dist/tools/view-file.d.ts +0 -62
- package/dist/tools/view-file.d.ts.map +0 -1
- package/dist/tools/wait-core.d.ts.map +0 -1
- package/dist/tools-contract.d.ts +0 -29
- package/dist/tools-contract.d.ts.map +0 -1
- package/dist/tools-contract.js +0 -24
- package/dist/tools.d.ts +0 -50
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -2930
- package/dist/tracking-server.d.ts +0 -15
- package/dist/tracking-server.d.ts.map +0 -1
- package/dist/tracking.d.ts +0 -24
- package/dist/tracking.d.ts.map +0 -1
- package/dist/tracking.js +0 -1092
- package/dist/upgrade-cli.d.ts.map +0 -1
- package/dist/upgrade-cli.js +0 -177
- /package/dist/{retained.d.ts → browser/retained.d.ts} +0 -0
- /package/dist/{declaration.d.ts → declaration/declaration.d.ts} +0 -0
- /package/dist/{upgrade-cli.d.ts → entrypoints/bin/upgrade-cli.d.ts} +0 -0
- /package/dist/{tracking-server.js → entrypoints/tracking/server.js} +0 -0
- /package/dist/{google.d.ts → google/google.d.ts} +0 -0
- /package/dist/{index-pq95qkbf.js → index-5jm36zv1.js} +0 -0
- /package/dist/{application → internal}/channel.d.ts +0 -0
- /package/dist/{logger.d.ts → internal/logger.d.ts} +0 -0
- /package/dist/{server → internal}/request.d.ts +0 -0
- /package/dist/{tools → json-schema}/json-schema.d.ts +0 -0
- /package/dist/tools/{async-operation-contract.d.ts → operations/async-operation-contract.d.ts} +0 -0
- /package/dist/tools/{mount-wait.d.ts → operations/mount-wait.d.ts} +0 -0
- /package/dist/tools/{wait-core.d.ts → operations/wait-core.d.ts} +0 -0
- /package/dist/tools/{coerce.d.ts → schema/coerce.d.ts} +0 -0
- /package/dist/tools/{flatten-join.d.ts → schema/flatten-join.d.ts} +0 -0
- /package/dist/tools/{flatten.d.ts → schema/flatten.d.ts} +0 -0
- /package/dist/tools/{portable-formats.d.ts → schema/portable-formats.d.ts} +0 -0
- /package/dist/tools/{schema.d.ts → schema/schema.d.ts} +0 -0
- /package/dist/tools/{untyped-properties.d.ts → schema/untyped-properties.d.ts} +0 -0
- /package/dist/tools/{native-result.d.ts → transfer/native-result.d.ts} +0 -0
- /package/dist/tools/{view-file-contract.d.ts → transfer/view-file-contract.d.ts} +0 -0
package/dist/index-n0f51dfn.js
DELETED
|
@@ -1,3170 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RELEASE_HEADER
|
|
3
|
-
} from "./index-y91ry9h2.js";
|
|
4
|
-
import {
|
|
5
|
-
ShutdownOptionsSchema,
|
|
6
|
-
isStreamCancellation,
|
|
7
|
-
ownHttpStream,
|
|
8
|
-
settleStreamCleanup
|
|
9
|
-
} from "./index-3z73fh2c.js";
|
|
10
|
-
import {
|
|
11
|
-
createValidatedRealtimeSocket,
|
|
12
|
-
isModuleNotFound
|
|
13
|
-
} from "./index-f477k430.js";
|
|
14
|
-
import {
|
|
15
|
-
DEFAULT_PROCESS_SIGNALS,
|
|
16
|
-
RUNTIME_CONTEXT_RESERVED_KEYS,
|
|
17
|
-
assertCorsConfig,
|
|
18
|
-
corsHeaders,
|
|
19
|
-
corsPreflightResponse,
|
|
20
|
-
defaultSignalSource,
|
|
21
|
-
guardSignalCallback,
|
|
22
|
-
isOriginAllowed,
|
|
23
|
-
mediaTypeEssence,
|
|
24
|
-
reportSignalError
|
|
25
|
-
} from "./index-cxqhvq21.js";
|
|
26
|
-
import {
|
|
27
|
-
getRequestContext,
|
|
28
|
-
runWithRequestContext,
|
|
29
|
-
setRequestEndpoint,
|
|
30
|
-
setRequestError
|
|
31
|
-
} from "./index-tmwcrptx.js";
|
|
32
|
-
import {
|
|
33
|
-
extractIp,
|
|
34
|
-
getClientInfo,
|
|
35
|
-
parseQueryParams,
|
|
36
|
-
resolveSocketIp,
|
|
37
|
-
resolveTraceId
|
|
38
|
-
} from "./index-f9mb610r.js";
|
|
39
|
-
import {
|
|
40
|
-
errorCode,
|
|
41
|
-
normalizeError,
|
|
42
|
-
recordedErrorMessage,
|
|
43
|
-
validateDeclaredOutput
|
|
44
|
-
} from "./index-e0znv5gn.js";
|
|
45
|
-
import {
|
|
46
|
-
DEFAULT_CONTRACT_STREAM_FRAME_BYTES,
|
|
47
|
-
joinRoutePath,
|
|
48
|
-
parseTrailingWildcard
|
|
49
|
-
} from "./index-0a349c33.js";
|
|
50
|
-
import {
|
|
51
|
-
resolveTraceContext
|
|
52
|
-
} from "./index-zcgf3gqf.js";
|
|
53
|
-
import {
|
|
54
|
-
AppError,
|
|
55
|
-
badRequest,
|
|
56
|
-
forbidden
|
|
57
|
-
} from "./index-2vtcmrj9.js";
|
|
58
|
-
import {
|
|
59
|
-
isUnsafeKey,
|
|
60
|
-
safeJsonParse
|
|
61
|
-
} from "./index-kzfs85xp.js";
|
|
62
|
-
import {
|
|
63
|
-
isRecord,
|
|
64
|
-
transportResult
|
|
65
|
-
} from "./index-77fekveh.js";
|
|
66
|
-
|
|
67
|
-
// src/server/multipart.ts
|
|
68
|
-
var DEFAULT_MAX_REQUEST_BYTES = 25 * 1024 * 1024;
|
|
69
|
-
var DEFAULT_MAX_FIELD_BYTES = 1024 * 1024;
|
|
70
|
-
var DEFAULT_MAX_HEADER_BYTES = 64 * 1024;
|
|
71
|
-
var DEFAULT_MAX_PARTS = 1000;
|
|
72
|
-
var CRLF = new Uint8Array([13, 10]);
|
|
73
|
-
var HEADER_END = new Uint8Array([13, 10, 13, 10]);
|
|
74
|
-
var decoder = new TextDecoder;
|
|
75
|
-
var headerDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
76
|
-
var encoder = new TextEncoder;
|
|
77
|
-
var TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
78
|
-
function hasControl(value, allowTab = false) {
|
|
79
|
-
for (let index = 0;index < value.length; index++) {
|
|
80
|
-
const code = value.charCodeAt(index);
|
|
81
|
-
if (code < 32 && !(allowTab && code === 9) || code === 127)
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
function trimWhitespace(value) {
|
|
87
|
-
return value.replace(/^[ \t]+|[ \t]+$/g, "");
|
|
88
|
-
}
|
|
89
|
-
function indexOfBytes(haystack, needle) {
|
|
90
|
-
outer:
|
|
91
|
-
for (let index = 0;index <= haystack.length - needle.length; index += 1) {
|
|
92
|
-
for (let offset = 0;offset < needle.length; offset += 1) {
|
|
93
|
-
if (haystack[index + offset] !== needle[offset])
|
|
94
|
-
continue outer;
|
|
95
|
-
}
|
|
96
|
-
return index;
|
|
97
|
-
}
|
|
98
|
-
return -1;
|
|
99
|
-
}
|
|
100
|
-
function startsWithBytes(value, prefix) {
|
|
101
|
-
if (value.length < prefix.length)
|
|
102
|
-
return false;
|
|
103
|
-
for (let index = 0;index < prefix.length; index += 1) {
|
|
104
|
-
if (value[index] !== prefix[index])
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
function concatBytes(left, right) {
|
|
110
|
-
if (left.length === 0)
|
|
111
|
-
return right.slice();
|
|
112
|
-
if (right.length === 0)
|
|
113
|
-
return left.slice();
|
|
114
|
-
const joined = new Uint8Array(left.length + right.length);
|
|
115
|
-
joined.set(left);
|
|
116
|
-
joined.set(right, left.length);
|
|
117
|
-
return joined;
|
|
118
|
-
}
|
|
119
|
-
function parseBoundary(req) {
|
|
120
|
-
const contentType = req.headers.get("content-type") ?? "";
|
|
121
|
-
if (!contentType.toLowerCase().startsWith("multipart/form-data")) {
|
|
122
|
-
badRequest("Request body must be multipart/form-data");
|
|
123
|
-
}
|
|
124
|
-
const match = /(?:^|;)\s*boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(contentType);
|
|
125
|
-
const boundary = match?.[1] ?? match?.[2];
|
|
126
|
-
if (!boundary || boundary.length > 200)
|
|
127
|
-
badRequest("Invalid multipart boundary");
|
|
128
|
-
return boundary;
|
|
129
|
-
}
|
|
130
|
-
function parseDisposition(value) {
|
|
131
|
-
if (!value)
|
|
132
|
-
badRequest("Invalid multipart disposition");
|
|
133
|
-
const separator = value.indexOf(";");
|
|
134
|
-
const kind = separator < 0 ? value : value.slice(0, separator);
|
|
135
|
-
if (trimWhitespace(kind).toLowerCase() !== "form-data") {
|
|
136
|
-
badRequest("Invalid multipart disposition");
|
|
137
|
-
}
|
|
138
|
-
const parameters = new Map;
|
|
139
|
-
let rest = separator < 0 ? "" : value.slice(separator);
|
|
140
|
-
while (rest.length > 0) {
|
|
141
|
-
const match = /^;[ \t]*([^=; \t]+)[ \t]*=[ \t]*(?:"((?:[^"\\]|\\.)*)"|([^; \t]+))[ \t]*/.exec(rest);
|
|
142
|
-
if (!match?.[1])
|
|
143
|
-
badRequest("Invalid multipart disposition parameter");
|
|
144
|
-
const key = match[1].toLowerCase();
|
|
145
|
-
if (!TOKEN.test(key) || parameters.has(key)) {
|
|
146
|
-
badRequest("Invalid or duplicate multipart disposition parameter");
|
|
147
|
-
}
|
|
148
|
-
const quoted = match[2];
|
|
149
|
-
const raw = match[3] ?? "";
|
|
150
|
-
if (quoted === undefined && !TOKEN.test(raw)) {
|
|
151
|
-
badRequest("Invalid multipart disposition parameter");
|
|
152
|
-
}
|
|
153
|
-
const decoded = quoted === undefined ? raw : quoted.replace(/\\(.)/g, "$1");
|
|
154
|
-
if (hasControl(decoded))
|
|
155
|
-
badRequest("Invalid multipart disposition parameter");
|
|
156
|
-
parameters.set(key, decoded);
|
|
157
|
-
rest = rest.slice(match[0].length);
|
|
158
|
-
}
|
|
159
|
-
const name = parameters.get("name");
|
|
160
|
-
let filename = parameters.get("filename");
|
|
161
|
-
const extended = parameters.get("filename*");
|
|
162
|
-
if (extended !== undefined) {
|
|
163
|
-
const match = /^utf-8'[A-Za-z0-9-]*'((?:[!#$&+.^_`|~0-9A-Za-z-]|%[0-9A-Fa-f]{2})*)$/i.exec(extended);
|
|
164
|
-
if (!match)
|
|
165
|
-
badRequest("Invalid multipart extended filename");
|
|
166
|
-
try {
|
|
167
|
-
filename = decodeURIComponent(match[1] ?? "");
|
|
168
|
-
} catch {
|
|
169
|
-
badRequest("Invalid multipart extended filename");
|
|
170
|
-
}
|
|
171
|
-
if (hasControl(filename))
|
|
172
|
-
badRequest("Invalid multipart extended filename");
|
|
173
|
-
}
|
|
174
|
-
if (!name || isUnsafeKey(name))
|
|
175
|
-
badRequest("Invalid multipart field name");
|
|
176
|
-
return filename === undefined ? { name } : { name, filename };
|
|
177
|
-
}
|
|
178
|
-
function parsePartHeaders(bytes) {
|
|
179
|
-
let text;
|
|
180
|
-
try {
|
|
181
|
-
text = headerDecoder.decode(bytes);
|
|
182
|
-
} catch {
|
|
183
|
-
badRequest("Invalid multipart headers");
|
|
184
|
-
}
|
|
185
|
-
const headers = new Map;
|
|
186
|
-
for (const line of text.split(`\r
|
|
187
|
-
`)) {
|
|
188
|
-
const separator = line.indexOf(":");
|
|
189
|
-
if (separator <= 0)
|
|
190
|
-
badRequest("Invalid multipart header");
|
|
191
|
-
const name = line.slice(0, separator).toLowerCase();
|
|
192
|
-
const value = trimWhitespace(line.slice(separator + 1));
|
|
193
|
-
if (!TOKEN.test(name) || hasControl(value, true)) {
|
|
194
|
-
badRequest("Invalid multipart header");
|
|
195
|
-
}
|
|
196
|
-
if (headers.has(name))
|
|
197
|
-
badRequest("Duplicate multipart header");
|
|
198
|
-
headers.set(name, value);
|
|
199
|
-
}
|
|
200
|
-
const disposition = parseDisposition(headers.get("content-disposition") ?? undefined);
|
|
201
|
-
const rawContentType = headers.get("content-type");
|
|
202
|
-
const contentType = rawContentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
203
|
-
const rawSize = headers.get("content-length");
|
|
204
|
-
if (rawSize !== undefined && !/^[0-9]+$/.test(rawSize)) {
|
|
205
|
-
badRequest("Invalid multipart part content-length");
|
|
206
|
-
}
|
|
207
|
-
const declaredSize = rawSize === undefined ? undefined : Number(rawSize);
|
|
208
|
-
if (declaredSize !== undefined && (!Number.isSafeInteger(declaredSize) || declaredSize < 0)) {
|
|
209
|
-
badRequest("Invalid multipart part content-length");
|
|
210
|
-
}
|
|
211
|
-
return { ...disposition, contentType, declaredSize };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
class MultipartStreamReader {
|
|
215
|
-
#reader;
|
|
216
|
-
#delimiter;
|
|
217
|
-
#initialBoundary;
|
|
218
|
-
#maxRequestBytes;
|
|
219
|
-
#buffer = new Uint8Array;
|
|
220
|
-
#readBytes = 0;
|
|
221
|
-
#sourceDone = false;
|
|
222
|
-
#terminal = false;
|
|
223
|
-
#activePart = false;
|
|
224
|
-
constructor(req, boundary, maxRequestBytes) {
|
|
225
|
-
if (!req.body)
|
|
226
|
-
badRequest("Multipart request body is empty");
|
|
227
|
-
this.#reader = req.body.getReader();
|
|
228
|
-
this.#delimiter = encoder.encode(`\r
|
|
229
|
-
--${boundary}`);
|
|
230
|
-
this.#initialBoundary = encoder.encode(`--${boundary}`);
|
|
231
|
-
this.#maxRequestBytes = maxRequestBytes;
|
|
232
|
-
}
|
|
233
|
-
async start() {
|
|
234
|
-
await this.#ensure(this.#initialBoundary.length + 2);
|
|
235
|
-
if (!startsWithBytes(this.#buffer, this.#initialBoundary)) {
|
|
236
|
-
badRequest("Malformed multipart body");
|
|
237
|
-
}
|
|
238
|
-
this.#consume(this.#initialBoundary.length);
|
|
239
|
-
await this.#consumeBoundarySuffix();
|
|
240
|
-
}
|
|
241
|
-
async nextPart() {
|
|
242
|
-
if (this.#activePart)
|
|
243
|
-
badRequest("Multipart part stream was not fully consumed");
|
|
244
|
-
if (this.#terminal)
|
|
245
|
-
return null;
|
|
246
|
-
const header = await this.#readUntil(HEADER_END, DEFAULT_MAX_HEADER_BYTES);
|
|
247
|
-
const parsed = parsePartHeaders(header);
|
|
248
|
-
this.#activePart = true;
|
|
249
|
-
let ended = false;
|
|
250
|
-
const stream = new ReadableStream({
|
|
251
|
-
pull: async (controller) => {
|
|
252
|
-
if (ended) {
|
|
253
|
-
controller.close();
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
try {
|
|
257
|
-
const chunk = await this.#readPartChunk();
|
|
258
|
-
if (chunk.value.length > 0)
|
|
259
|
-
controller.enqueue(chunk.value);
|
|
260
|
-
if (chunk.end) {
|
|
261
|
-
ended = true;
|
|
262
|
-
controller.close();
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
controller.error(error);
|
|
266
|
-
this.cancel(error);
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
cancel: (reason) => {
|
|
270
|
-
this.cancel(reason);
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
return { ...parsed, stream, consumed: () => ended };
|
|
274
|
-
}
|
|
275
|
-
cancel(reason) {
|
|
276
|
-
this.#reader.cancel(reason).catch(() => {});
|
|
277
|
-
}
|
|
278
|
-
release() {
|
|
279
|
-
this.#reader.releaseLock();
|
|
280
|
-
}
|
|
281
|
-
async#readPartChunk() {
|
|
282
|
-
while (true) {
|
|
283
|
-
const boundaryIndex = indexOfBytes(this.#buffer, this.#delimiter);
|
|
284
|
-
if (boundaryIndex >= 0) {
|
|
285
|
-
const value = this.#buffer.slice(0, boundaryIndex);
|
|
286
|
-
this.#consume(boundaryIndex + this.#delimiter.length);
|
|
287
|
-
await this.#consumeBoundarySuffix();
|
|
288
|
-
this.#activePart = false;
|
|
289
|
-
return { value, end: true };
|
|
290
|
-
}
|
|
291
|
-
const retained = this.#delimiter.length - 1;
|
|
292
|
-
if (this.#buffer.length > retained) {
|
|
293
|
-
const emitLength = this.#buffer.length - retained;
|
|
294
|
-
const value = this.#buffer.slice(0, emitLength);
|
|
295
|
-
this.#consume(emitLength);
|
|
296
|
-
return { value, end: false };
|
|
297
|
-
}
|
|
298
|
-
if (this.#sourceDone)
|
|
299
|
-
badRequest("Incomplete multipart body");
|
|
300
|
-
await this.#readMore();
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
async#consumeBoundarySuffix() {
|
|
304
|
-
await this.#ensure(2);
|
|
305
|
-
if (this.#buffer[0] === 45 && this.#buffer[1] === 45) {
|
|
306
|
-
this.#consume(2);
|
|
307
|
-
this.#terminal = true;
|
|
308
|
-
if (startsWithBytes(this.#buffer, CRLF))
|
|
309
|
-
this.#consume(2);
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
if (!startsWithBytes(this.#buffer, CRLF))
|
|
313
|
-
badRequest("Malformed multipart boundary");
|
|
314
|
-
this.#consume(2);
|
|
315
|
-
}
|
|
316
|
-
async#readUntil(marker, maxBytes) {
|
|
317
|
-
while (true) {
|
|
318
|
-
const index = indexOfBytes(this.#buffer, marker);
|
|
319
|
-
if (index >= 0) {
|
|
320
|
-
if (index > maxBytes)
|
|
321
|
-
badRequest("Multipart part headers are too large");
|
|
322
|
-
const value = this.#buffer.slice(0, index);
|
|
323
|
-
this.#consume(index + marker.length);
|
|
324
|
-
return value;
|
|
325
|
-
}
|
|
326
|
-
if (this.#buffer.length > maxBytes)
|
|
327
|
-
badRequest("Multipart part headers are too large");
|
|
328
|
-
if (this.#sourceDone)
|
|
329
|
-
badRequest("Incomplete multipart headers");
|
|
330
|
-
await this.#readMore();
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
async#ensure(length) {
|
|
334
|
-
while (this.#buffer.length < length && !this.#sourceDone)
|
|
335
|
-
await this.#readMore();
|
|
336
|
-
if (this.#buffer.length < length)
|
|
337
|
-
badRequest("Incomplete multipart body");
|
|
338
|
-
}
|
|
339
|
-
async#readMore() {
|
|
340
|
-
const result = await this.#reader.read();
|
|
341
|
-
if (result.done) {
|
|
342
|
-
this.#sourceDone = true;
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
this.#readBytes += result.value.length;
|
|
346
|
-
if (this.#readBytes > this.#maxRequestBytes) {
|
|
347
|
-
this.cancel();
|
|
348
|
-
badRequest(`Multipart request exceeds ${this.#maxRequestBytes} bytes`);
|
|
349
|
-
}
|
|
350
|
-
this.#buffer = concatBytes(this.#buffer, result.value);
|
|
351
|
-
}
|
|
352
|
-
#consume(length) {
|
|
353
|
-
this.#buffer = this.#buffer.slice(length);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function matchesContentType(contentType, accepted) {
|
|
357
|
-
const normalized = contentType.toLowerCase();
|
|
358
|
-
return accepted.some((candidate) => {
|
|
359
|
-
const policy = candidate.toLowerCase();
|
|
360
|
-
return policy.endsWith("/*") ? normalized.startsWith(policy.slice(0, -1)) : normalized === policy;
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
function limitedStream(stream, maxBytes, label, signal) {
|
|
364
|
-
const reader = stream.getReader();
|
|
365
|
-
let total = 0;
|
|
366
|
-
const cancelReader = (reason) => {
|
|
367
|
-
reader.cancel(reason).catch(() => {});
|
|
368
|
-
};
|
|
369
|
-
return new ReadableStream({
|
|
370
|
-
async pull(controller) {
|
|
371
|
-
if (signal.aborted) {
|
|
372
|
-
cancelReader(signal.reason);
|
|
373
|
-
controller.error(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
try {
|
|
377
|
-
const result = await reader.read();
|
|
378
|
-
if (result.done) {
|
|
379
|
-
controller.close();
|
|
380
|
-
reader.releaseLock();
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
total += result.value.length;
|
|
384
|
-
if (total > maxBytes) {
|
|
385
|
-
cancelReader();
|
|
386
|
-
badRequest(`${label} exceeds ${maxBytes} bytes`);
|
|
387
|
-
}
|
|
388
|
-
controller.enqueue(result.value);
|
|
389
|
-
} catch (error) {
|
|
390
|
-
controller.error(error);
|
|
391
|
-
}
|
|
392
|
-
},
|
|
393
|
-
cancel(reason) {
|
|
394
|
-
cancelReader(reason);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
async function collectFile(part, stream) {
|
|
399
|
-
const chunks = [];
|
|
400
|
-
const reader = stream.getReader();
|
|
401
|
-
while (true) {
|
|
402
|
-
const result = await reader.read();
|
|
403
|
-
if (result.done)
|
|
404
|
-
break;
|
|
405
|
-
chunks.push(Uint8Array.from(result.value).buffer);
|
|
406
|
-
}
|
|
407
|
-
return new File(chunks, part.filename ?? "upload", {
|
|
408
|
-
type: part.contentType ?? "application/octet-stream"
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
async function readText(stream, maxBytes) {
|
|
412
|
-
const chunks = [];
|
|
413
|
-
const reader = stream.getReader();
|
|
414
|
-
let total = 0;
|
|
415
|
-
while (true) {
|
|
416
|
-
const result = await reader.read();
|
|
417
|
-
if (result.done)
|
|
418
|
-
break;
|
|
419
|
-
total += result.value.length;
|
|
420
|
-
if (total > maxBytes) {
|
|
421
|
-
await reader.cancel();
|
|
422
|
-
badRequest(`Multipart text field exceeds ${maxBytes} bytes`);
|
|
423
|
-
}
|
|
424
|
-
chunks.push(result.value);
|
|
425
|
-
}
|
|
426
|
-
const value = new Uint8Array(total);
|
|
427
|
-
let offset = 0;
|
|
428
|
-
for (const chunk of chunks) {
|
|
429
|
-
value.set(chunk, offset);
|
|
430
|
-
offset += chunk.length;
|
|
431
|
-
}
|
|
432
|
-
return decoder.decode(value);
|
|
433
|
-
}
|
|
434
|
-
function required(policy) {
|
|
435
|
-
return policy.required !== false;
|
|
436
|
-
}
|
|
437
|
-
async function parseMultipart(req, descriptor, fieldsSchema, receivers) {
|
|
438
|
-
const maxRequestBytes = descriptor.maxRequestBytes ?? DEFAULT_MAX_REQUEST_BYTES;
|
|
439
|
-
const contentLength = Number(req.headers.get("content-length"));
|
|
440
|
-
if (Number.isFinite(contentLength) && contentLength > maxRequestBytes) {
|
|
441
|
-
badRequest(`Multipart request exceeds ${maxRequestBytes} bytes`);
|
|
442
|
-
}
|
|
443
|
-
const boundary = parseBoundary(req);
|
|
444
|
-
const parser = new MultipartStreamReader(req, boundary, maxRequestBytes);
|
|
445
|
-
const fields = {};
|
|
446
|
-
const files = {};
|
|
447
|
-
const counts = new Map;
|
|
448
|
-
const cleanups = [];
|
|
449
|
-
let rolledBack = false;
|
|
450
|
-
const rollback = async () => {
|
|
451
|
-
if (rolledBack)
|
|
452
|
-
return;
|
|
453
|
-
rolledBack = true;
|
|
454
|
-
for (let index = cleanups.length - 1;index >= 0; index -= 1) {
|
|
455
|
-
try {
|
|
456
|
-
await cleanups[index]?.();
|
|
457
|
-
} catch (error) {
|
|
458
|
-
console.error("[stitchkit] multipart receiver cleanup failed", error);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
try {
|
|
463
|
-
await parser.start();
|
|
464
|
-
let partCount = 0;
|
|
465
|
-
while (true) {
|
|
466
|
-
const part = await parser.nextPart();
|
|
467
|
-
if (!part)
|
|
468
|
-
break;
|
|
469
|
-
partCount += 1;
|
|
470
|
-
if (partCount > DEFAULT_MAX_PARTS)
|
|
471
|
-
badRequest("Too many multipart parts");
|
|
472
|
-
const policy = descriptor.files[part.name];
|
|
473
|
-
const isFile = part.filename !== undefined || part.contentType !== undefined;
|
|
474
|
-
if (!isFile) {
|
|
475
|
-
if (policy)
|
|
476
|
-
badRequest(`Multipart file field "${part.name}" must contain a file`);
|
|
477
|
-
if (Object.hasOwn(fields, part.name)) {
|
|
478
|
-
badRequest(`Duplicate multipart text field: ${part.name}`);
|
|
479
|
-
}
|
|
480
|
-
fields[part.name] = await readText(part.stream, descriptor.maxFieldBytes ?? DEFAULT_MAX_FIELD_BYTES);
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
if (!policy)
|
|
484
|
-
badRequest(`Unexpected multipart file field: ${part.name}`);
|
|
485
|
-
const count = (counts.get(part.name) ?? 0) + 1;
|
|
486
|
-
counts.set(part.name, count);
|
|
487
|
-
const maxFiles = policy.multiple === true ? policy.maxFiles ?? DEFAULT_MAX_PARTS : 1;
|
|
488
|
-
if (count > maxFiles)
|
|
489
|
-
badRequest(`Too many files for multipart field: ${part.name}`);
|
|
490
|
-
const contentType = part.contentType ?? "";
|
|
491
|
-
if (policy.contentTypes && !matchesContentType(contentType, policy.contentTypes)) {
|
|
492
|
-
badRequest(`Unsupported content type for multipart field "${part.name}"`);
|
|
493
|
-
}
|
|
494
|
-
const maxFileBytes = policy.maxBytes ?? maxRequestBytes;
|
|
495
|
-
if (part.declaredSize !== undefined && part.declaredSize > maxFileBytes) {
|
|
496
|
-
badRequest(`Multipart field "${part.name}" exceeds ${maxFileBytes} bytes`);
|
|
497
|
-
}
|
|
498
|
-
const stream = limitedStream(part.stream, maxFileBytes, `Multipart field "${part.name}"`, req.signal);
|
|
499
|
-
let value;
|
|
500
|
-
if (descriptor.delivery === "stream") {
|
|
501
|
-
const receiver = receivers?.[part.name];
|
|
502
|
-
if (!receiver)
|
|
503
|
-
badRequest(`Missing multipart receiver for field: ${part.name}`);
|
|
504
|
-
const result = await receiver({
|
|
505
|
-
metadata: {
|
|
506
|
-
field: part.name,
|
|
507
|
-
filename: part.filename ?? "upload",
|
|
508
|
-
contentType: contentType || "application/octet-stream",
|
|
509
|
-
size: part.declaredSize
|
|
510
|
-
},
|
|
511
|
-
stream,
|
|
512
|
-
signal: req.signal
|
|
513
|
-
});
|
|
514
|
-
cleanups.push(result.cleanup);
|
|
515
|
-
if (!part.consumed()) {
|
|
516
|
-
await stream.cancel();
|
|
517
|
-
badRequest(`Multipart receiver for "${part.name}" did not consume its stream`);
|
|
518
|
-
}
|
|
519
|
-
value = result.value;
|
|
520
|
-
} else {
|
|
521
|
-
value = await collectFile(part, stream);
|
|
522
|
-
}
|
|
523
|
-
if (policy.multiple === true) {
|
|
524
|
-
const existing = files[part.name];
|
|
525
|
-
if (Array.isArray(existing))
|
|
526
|
-
existing.push(value);
|
|
527
|
-
else
|
|
528
|
-
files[part.name] = [value];
|
|
529
|
-
} else {
|
|
530
|
-
files[part.name] = value;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
for (const [field, policy] of Object.entries(descriptor.files)) {
|
|
534
|
-
const count = counts.get(field) ?? 0;
|
|
535
|
-
if (required(policy) && count === 0)
|
|
536
|
-
badRequest(`Missing multipart file field: ${field}`);
|
|
537
|
-
if (policy.multiple === true && count === 0 && !required(policy))
|
|
538
|
-
files[field] = [];
|
|
539
|
-
}
|
|
540
|
-
return {
|
|
541
|
-
files,
|
|
542
|
-
fields: fieldsSchema ? fieldsSchema.parse(fields) : fields,
|
|
543
|
-
rollback
|
|
544
|
-
};
|
|
545
|
-
} catch (error) {
|
|
546
|
-
parser.cancel(error);
|
|
547
|
-
await rollback();
|
|
548
|
-
throw error;
|
|
549
|
-
} finally {
|
|
550
|
-
parser.release();
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// src/server/streaming-route.ts
|
|
555
|
-
var DEFAULT_STREAM_HEARTBEAT_MS = 5000;
|
|
556
|
-
var FRAMINGS = {
|
|
557
|
-
ndjson: {
|
|
558
|
-
contentType: "application/x-ndjson",
|
|
559
|
-
keepAlive: `
|
|
560
|
-
`,
|
|
561
|
-
frame: (value) => `${JSON.stringify(value)}
|
|
562
|
-
`
|
|
563
|
-
},
|
|
564
|
-
sse: {
|
|
565
|
-
contentType: "text/event-stream",
|
|
566
|
-
keepAlive: `: keep-alive
|
|
567
|
-
|
|
568
|
-
`,
|
|
569
|
-
frame: (value) => `data: ${JSON.stringify(value)}
|
|
570
|
-
|
|
571
|
-
`,
|
|
572
|
-
done: `data: [DONE]
|
|
573
|
-
|
|
574
|
-
`
|
|
575
|
-
}
|
|
576
|
-
};
|
|
577
|
-
function applyIdleTimeout(server, request, seconds) {
|
|
578
|
-
if (typeof server !== "object" || server === null)
|
|
579
|
-
return;
|
|
580
|
-
const timeout = Reflect.get(server, "timeout");
|
|
581
|
-
if (typeof timeout !== "function")
|
|
582
|
-
return;
|
|
583
|
-
try {
|
|
584
|
-
Reflect.apply(timeout, server, [request, seconds]);
|
|
585
|
-
} catch {}
|
|
586
|
-
}
|
|
587
|
-
function unrefTimer(timer) {
|
|
588
|
-
if (typeof timer !== "object" || timer === null)
|
|
589
|
-
return;
|
|
590
|
-
const unref = Reflect.get(timer, "unref");
|
|
591
|
-
if (typeof unref === "function")
|
|
592
|
-
Reflect.apply(unref, timer, []);
|
|
593
|
-
}
|
|
594
|
-
var MAX_BUFFERED_FRAMES = 16;
|
|
595
|
-
function responseHeaders(framing, extra) {
|
|
596
|
-
const headers = new Headers(extra);
|
|
597
|
-
headers.set("Content-Type", framing.contentType);
|
|
598
|
-
headers.set("Cache-Control", "no-cache");
|
|
599
|
-
headers.set("Connection", "keep-alive");
|
|
600
|
-
headers.set("X-Accel-Buffering", "no");
|
|
601
|
-
return headers;
|
|
602
|
-
}
|
|
603
|
-
function streamingRoute(options) {
|
|
604
|
-
const heartbeatMs = options.heartbeatMs ?? DEFAULT_STREAM_HEARTBEAT_MS;
|
|
605
|
-
if (!Number.isFinite(heartbeatMs) || heartbeatMs <= 0) {
|
|
606
|
-
throw new TypeError("heartbeatMs must be a finite positive number of milliseconds");
|
|
607
|
-
}
|
|
608
|
-
const idleTimeoutSeconds = options.idleTimeoutSeconds ?? 0;
|
|
609
|
-
if (!Number.isInteger(idleTimeoutSeconds) || idleTimeoutSeconds < 0) {
|
|
610
|
-
throw new TypeError("idleTimeoutSeconds must be a non-negative integer");
|
|
611
|
-
}
|
|
612
|
-
const framing = FRAMINGS[options.format ?? "ndjson"];
|
|
613
|
-
return {
|
|
614
|
-
method: options.method ?? "GET",
|
|
615
|
-
path: options.path,
|
|
616
|
-
handler: async (request, context) => {
|
|
617
|
-
applyIdleTimeout(context.server, request, idleTimeoutSeconds);
|
|
618
|
-
const departed = new AbortController;
|
|
619
|
-
const settled = Promise.withResolvers();
|
|
620
|
-
settled.promise.catch(() => {
|
|
621
|
-
return;
|
|
622
|
-
});
|
|
623
|
-
const pumped = Promise.withResolvers();
|
|
624
|
-
pumped.promise.catch(() => {
|
|
625
|
-
return;
|
|
626
|
-
});
|
|
627
|
-
let cancel = () => departed.abort();
|
|
628
|
-
ownHttpStream(request, { cancel: () => cancel(), settled: settled.promise });
|
|
629
|
-
let iterator;
|
|
630
|
-
try {
|
|
631
|
-
const iterable = await options.source(request, {
|
|
632
|
-
...context,
|
|
633
|
-
signal: departed.signal
|
|
634
|
-
});
|
|
635
|
-
iterator = iterable[Symbol.asyncIterator]();
|
|
636
|
-
} catch (error) {
|
|
637
|
-
departed.abort();
|
|
638
|
-
settled.reject(error);
|
|
639
|
-
throw error;
|
|
640
|
-
}
|
|
641
|
-
const encoder2 = new TextEncoder;
|
|
642
|
-
let heartbeat = null;
|
|
643
|
-
let closed = false;
|
|
644
|
-
const stopHeartbeat = () => {
|
|
645
|
-
if (heartbeat === null)
|
|
646
|
-
return;
|
|
647
|
-
clearInterval(heartbeat);
|
|
648
|
-
heartbeat = null;
|
|
649
|
-
};
|
|
650
|
-
const release = () => {
|
|
651
|
-
if (closed)
|
|
652
|
-
return;
|
|
653
|
-
closed = true;
|
|
654
|
-
stopHeartbeat();
|
|
655
|
-
signalDemand();
|
|
656
|
-
request.signal.removeEventListener("abort", onRequestAbort);
|
|
657
|
-
departed.abort();
|
|
658
|
-
const hook = Promise.withResolvers();
|
|
659
|
-
try {
|
|
660
|
-
options.onClose?.();
|
|
661
|
-
hook.resolve();
|
|
662
|
-
} catch (error) {
|
|
663
|
-
hook.reject(error);
|
|
664
|
-
}
|
|
665
|
-
settleStreamCleanup([
|
|
666
|
-
hook.promise,
|
|
667
|
-
pumped.promise,
|
|
668
|
-
Promise.resolve().then(() => iterator.return?.(undefined))
|
|
669
|
-
]).then(() => settled.resolve(), (error) => settled.reject(error));
|
|
670
|
-
};
|
|
671
|
-
let demand = null;
|
|
672
|
-
const signalDemand = () => {
|
|
673
|
-
const resolve = demand;
|
|
674
|
-
demand = null;
|
|
675
|
-
resolve?.();
|
|
676
|
-
};
|
|
677
|
-
const onRequestAbort = () => cancel();
|
|
678
|
-
const stream = new ReadableStream({
|
|
679
|
-
start(controller) {
|
|
680
|
-
const send = (text) => {
|
|
681
|
-
if (closed)
|
|
682
|
-
return false;
|
|
683
|
-
try {
|
|
684
|
-
controller.enqueue(encoder2.encode(text));
|
|
685
|
-
return true;
|
|
686
|
-
} catch {
|
|
687
|
-
release();
|
|
688
|
-
return false;
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
const finish = () => {
|
|
692
|
-
try {
|
|
693
|
-
controller.close();
|
|
694
|
-
} catch {}
|
|
695
|
-
};
|
|
696
|
-
cancel = () => {
|
|
697
|
-
release();
|
|
698
|
-
finish();
|
|
699
|
-
};
|
|
700
|
-
if (request.signal.aborted || departed.signal.aborted) {
|
|
701
|
-
pumped.resolve();
|
|
702
|
-
cancel();
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
request.signal.addEventListener("abort", onRequestAbort, { once: true });
|
|
706
|
-
send(framing.keepAlive);
|
|
707
|
-
heartbeat = setInterval(() => {
|
|
708
|
-
if ((controller.desiredSize ?? 1) > 0)
|
|
709
|
-
send(framing.keepAlive);
|
|
710
|
-
}, heartbeatMs);
|
|
711
|
-
unrefTimer(heartbeat);
|
|
712
|
-
const awaitDemand = async () => {
|
|
713
|
-
while (!closed && (controller.desiredSize ?? 1) <= 0) {
|
|
714
|
-
await new Promise((resolve) => {
|
|
715
|
-
demand = resolve;
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
let sinceYield = 0;
|
|
720
|
-
(async () => {
|
|
721
|
-
try {
|
|
722
|
-
for (;; ) {
|
|
723
|
-
await awaitDemand();
|
|
724
|
-
if (closed)
|
|
725
|
-
return;
|
|
726
|
-
const next = await iterator.next();
|
|
727
|
-
if (closed)
|
|
728
|
-
return;
|
|
729
|
-
if (next.done)
|
|
730
|
-
break;
|
|
731
|
-
if (!send(framing.frame(next.value)))
|
|
732
|
-
return;
|
|
733
|
-
sinceYield += 1;
|
|
734
|
-
if (sinceYield >= MAX_BUFFERED_FRAMES) {
|
|
735
|
-
sinceYield = 0;
|
|
736
|
-
await new Promise((resolve) => {
|
|
737
|
-
setTimeout(resolve, 0);
|
|
738
|
-
});
|
|
739
|
-
if (closed)
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
if (framing.done)
|
|
744
|
-
send(framing.done);
|
|
745
|
-
} catch (error) {
|
|
746
|
-
if (closed) {
|
|
747
|
-
if (!isStreamCancellation(error, departed.signal))
|
|
748
|
-
pumped.reject(error);
|
|
749
|
-
} else
|
|
750
|
-
send(framing.frame(normalizeError(error).toJSON()));
|
|
751
|
-
} finally {
|
|
752
|
-
pumped.resolve();
|
|
753
|
-
release();
|
|
754
|
-
finish();
|
|
755
|
-
}
|
|
756
|
-
})();
|
|
757
|
-
},
|
|
758
|
-
pull() {
|
|
759
|
-
signalDemand();
|
|
760
|
-
},
|
|
761
|
-
cancel() {
|
|
762
|
-
release();
|
|
763
|
-
}
|
|
764
|
-
}, { highWaterMark: MAX_BUFFERED_FRAMES });
|
|
765
|
-
return new Response(stream, { headers: responseHeaders(framing, options.headers) });
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
function ndjsonRoute(options) {
|
|
770
|
-
return streamingRoute({ ...options, format: "ndjson" });
|
|
771
|
-
}
|
|
772
|
-
function sseRoute(options) {
|
|
773
|
-
return streamingRoute({ ...options, format: "sse" });
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// src/server/request-body.ts
|
|
777
|
-
function requestAbortReason(req) {
|
|
778
|
-
return req.signal.reason ?? new DOMException("The connection was closed", "AbortError");
|
|
779
|
-
}
|
|
780
|
-
function throwIfRequestAborted(req) {
|
|
781
|
-
if (req.signal.aborted)
|
|
782
|
-
throw requestAbortReason(req);
|
|
783
|
-
}
|
|
784
|
-
function assertJsonBodyLimit(maxBytes, owner) {
|
|
785
|
-
if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {
|
|
786
|
-
throw new Error(`${owner} must be a positive safe integer, received ${maxBytes}`);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
async function readRequestText(req, maxBytes) {
|
|
790
|
-
throwIfRequestAborted(req);
|
|
791
|
-
if (maxBytes === undefined)
|
|
792
|
-
return req.text();
|
|
793
|
-
assertJsonBodyLimit(maxBytes, "maxJsonBodyBytes");
|
|
794
|
-
const reader = req.body?.getReader();
|
|
795
|
-
if (!reader)
|
|
796
|
-
return "";
|
|
797
|
-
const chunks = [];
|
|
798
|
-
let total = 0;
|
|
799
|
-
let rejectAbortedRead = (_reason) => {
|
|
800
|
-
return;
|
|
801
|
-
};
|
|
802
|
-
const abortedRead = new Promise((_resolve, reject) => {
|
|
803
|
-
rejectAbortedRead = reject;
|
|
804
|
-
});
|
|
805
|
-
const onAbort = () => {
|
|
806
|
-
const reason = requestAbortReason(req);
|
|
807
|
-
rejectAbortedRead(reason);
|
|
808
|
-
reader.cancel(reason).catch(() => {
|
|
809
|
-
return;
|
|
810
|
-
});
|
|
811
|
-
};
|
|
812
|
-
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
813
|
-
if (req.signal.aborted)
|
|
814
|
-
onAbort();
|
|
815
|
-
try {
|
|
816
|
-
while (true) {
|
|
817
|
-
const { done, value } = await Promise.race([reader.read(), abortedRead]);
|
|
818
|
-
if (done)
|
|
819
|
-
break;
|
|
820
|
-
total += value.byteLength;
|
|
821
|
-
if (total > maxBytes) {
|
|
822
|
-
await reader.cancel();
|
|
823
|
-
badRequest(`JSON body exceeds the ${maxBytes}-byte limit`);
|
|
824
|
-
}
|
|
825
|
-
chunks.push(value);
|
|
826
|
-
}
|
|
827
|
-
} finally {
|
|
828
|
-
req.signal.removeEventListener("abort", onAbort);
|
|
829
|
-
if (!req.signal.aborted)
|
|
830
|
-
reader.releaseLock();
|
|
831
|
-
}
|
|
832
|
-
const bytes = new Uint8Array(total);
|
|
833
|
-
let offset = 0;
|
|
834
|
-
for (const chunk of chunks) {
|
|
835
|
-
bytes.set(chunk, offset);
|
|
836
|
-
offset += chunk.byteLength;
|
|
837
|
-
}
|
|
838
|
-
return new TextDecoder().decode(bytes);
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// src/server/context.ts
|
|
842
|
-
function parseJsonBody(req, text, method, cors) {
|
|
843
|
-
if (text.trim() === "")
|
|
844
|
-
return {};
|
|
845
|
-
const essence = mediaTypeEssence(req.headers.get("content-type"));
|
|
846
|
-
if (essence !== "application/json") {
|
|
847
|
-
if (!method.safelistedBody || essence !== "text/plain") {
|
|
848
|
-
badRequest("Request body must be application/json");
|
|
849
|
-
}
|
|
850
|
-
const origin = req.headers.get("origin");
|
|
851
|
-
if (!cors || cors.origin === undefined || cors.origin === "*") {
|
|
852
|
-
forbidden("A text/plain body requires an explicit cors.origin allow-list on the server");
|
|
853
|
-
}
|
|
854
|
-
if (!origin)
|
|
855
|
-
forbidden("A text/plain body requires an Origin header");
|
|
856
|
-
if (!isOriginAllowed(cors, origin)) {
|
|
857
|
-
forbidden("A text/plain body is accepted only from an allowed origin");
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
try {
|
|
861
|
-
return safeJsonParse(text);
|
|
862
|
-
} catch {
|
|
863
|
-
badRequest("Invalid JSON body");
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
function buildBaseContext(req, url, pathParams, traceId, clientIp) {
|
|
867
|
-
const safePathParams = {};
|
|
868
|
-
for (const [k, v] of Object.entries(pathParams)) {
|
|
869
|
-
if (!RUNTIME_CONTEXT_RESERVED_KEYS.has(k) && !isUnsafeKey(k))
|
|
870
|
-
safePathParams[k] = v;
|
|
871
|
-
}
|
|
872
|
-
return {
|
|
873
|
-
params: pathParams,
|
|
874
|
-
input: undefined,
|
|
875
|
-
source: "http",
|
|
876
|
-
req,
|
|
877
|
-
url,
|
|
878
|
-
headers: req.headers,
|
|
879
|
-
signal: req.signal,
|
|
880
|
-
...safePathParams,
|
|
881
|
-
traceId,
|
|
882
|
-
...getClientInfo(req, clientIp)
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
function parsePathParamsInto(ctx, method) {
|
|
886
|
-
if (method.paramsSchema) {
|
|
887
|
-
ctx.params = method.paramsSchema.parse(ctx.params);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
async function parseRequestPayloadInto(ctx, req, url, method, maxJsonBodyBytes, cors) {
|
|
891
|
-
if (method.multipart) {
|
|
892
|
-
const multipart = await parseMultipart(req, method.multipart, method.inputSchema, method.multipartReceivers);
|
|
893
|
-
ctx.input = multipart.fields;
|
|
894
|
-
ctx.files = multipart.files;
|
|
895
|
-
return multipart;
|
|
896
|
-
}
|
|
897
|
-
if (method.inputSchema) {
|
|
898
|
-
if (req.method === "GET") {
|
|
899
|
-
ctx.input = method.inputSchema.parse(parseQueryParams(url));
|
|
900
|
-
} else if (req.method === "DELETE") {
|
|
901
|
-
if (mediaTypeEssence(req.headers.get("content-type")) === "application/json") {
|
|
902
|
-
const text = await readRequestText(req, method.maxJsonBodyBytes ?? maxJsonBodyBytes);
|
|
903
|
-
if (method.rawBody)
|
|
904
|
-
ctx.rawBody = text;
|
|
905
|
-
ctx.input = method.inputSchema.parse(parseJsonBody(req, text, method, cors));
|
|
906
|
-
} else {
|
|
907
|
-
ctx.input = method.inputSchema.parse(parseQueryParams(url));
|
|
908
|
-
}
|
|
909
|
-
} else {
|
|
910
|
-
const text = await readRequestText(req, method.maxJsonBodyBytes ?? maxJsonBodyBytes);
|
|
911
|
-
if (method.rawBody)
|
|
912
|
-
ctx.rawBody = text;
|
|
913
|
-
ctx.input = method.inputSchema.parse(parseJsonBody(req, text, method, cors));
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
function buildErrorContext(req, url, traceId, clientIp) {
|
|
919
|
-
return {
|
|
920
|
-
params: undefined,
|
|
921
|
-
input: undefined,
|
|
922
|
-
source: "http",
|
|
923
|
-
req,
|
|
924
|
-
url,
|
|
925
|
-
headers: req.headers,
|
|
926
|
-
signal: req.signal,
|
|
927
|
-
traceId,
|
|
928
|
-
...getClientInfo(req, clientIp)
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// src/server/contract-stream.ts
|
|
933
|
-
function encodedBytes(value, format) {
|
|
934
|
-
const json = JSON.stringify(value);
|
|
935
|
-
if (json === undefined)
|
|
936
|
-
throw new TypeError("Streaming item is not JSON serializable");
|
|
937
|
-
const framing = format === "sse" ? `data: ${json}
|
|
938
|
-
|
|
939
|
-
` : `${json}
|
|
940
|
-
`;
|
|
941
|
-
return new TextEncoder().encode(framing).byteLength;
|
|
942
|
-
}
|
|
943
|
-
function waitForAbort(signal) {
|
|
944
|
-
return new Promise((_resolve, reject) => {
|
|
945
|
-
const rejectAborted = () => reject(signal.reason ?? new Error("Stream aborted"));
|
|
946
|
-
if (signal.aborted)
|
|
947
|
-
rejectAborted();
|
|
948
|
-
else
|
|
949
|
-
signal.addEventListener("abort", rejectAborted, { once: true });
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
function contractStreamResponse(request, context, source, descriptor, operationAbort) {
|
|
953
|
-
const format = descriptor.format ?? "ndjson";
|
|
954
|
-
const maxFrameBytes = descriptor.maxFrameBytes ?? DEFAULT_CONTRACT_STREAM_FRAME_BYTES;
|
|
955
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
956
|
-
let pendingNext;
|
|
957
|
-
const settled = Promise.withResolvers();
|
|
958
|
-
settled.promise.catch(() => {
|
|
959
|
-
return;
|
|
960
|
-
});
|
|
961
|
-
let lifetime;
|
|
962
|
-
let lifetimeExpired = false;
|
|
963
|
-
let closing = false;
|
|
964
|
-
const closeSource = (cancelled = operationAbort.signal.aborted) => {
|
|
965
|
-
if (closing)
|
|
966
|
-
return;
|
|
967
|
-
closing = true;
|
|
968
|
-
if (lifetime !== undefined)
|
|
969
|
-
clearTimeout(lifetime);
|
|
970
|
-
operationAbort.abort();
|
|
971
|
-
settleStreamCleanup([
|
|
972
|
-
Promise.resolve(pendingNext).catch((error) => {
|
|
973
|
-
if (cancelled && !isStreamCancellation(error, operationAbort.signal))
|
|
974
|
-
throw error;
|
|
975
|
-
}),
|
|
976
|
-
Promise.resolve().then(() => iterator.return?.(undefined))
|
|
977
|
-
]).then(() => settled.resolve(), (error) => settled.reject(error));
|
|
978
|
-
};
|
|
979
|
-
ownHttpStream(request, { cancel: () => closeSource(true), settled: settled.promise });
|
|
980
|
-
if (descriptor.lifetimeMs !== undefined && !closing) {
|
|
981
|
-
lifetime = setTimeout(() => {
|
|
982
|
-
lifetimeExpired = true;
|
|
983
|
-
operationAbort.abort(new Error("Stream lifetime expired"));
|
|
984
|
-
}, descriptor.lifetimeMs);
|
|
985
|
-
lifetime.unref?.();
|
|
986
|
-
}
|
|
987
|
-
const frames = async function* () {
|
|
988
|
-
if (closing)
|
|
989
|
-
return;
|
|
990
|
-
const aborted = waitForAbort(operationAbort.signal);
|
|
991
|
-
let terminalSeen = descriptor.terminal === undefined;
|
|
992
|
-
try {
|
|
993
|
-
for (;; ) {
|
|
994
|
-
pendingNext = Promise.resolve(iterator.next());
|
|
995
|
-
const next = await Promise.race([pendingNext, aborted]);
|
|
996
|
-
if (next.done)
|
|
997
|
-
break;
|
|
998
|
-
const parsed = descriptor.item.safeParse(next.value);
|
|
999
|
-
if (!parsed.success) {
|
|
1000
|
-
console.error("[stitchkit] contract stream item failed validation:", parsed.error);
|
|
1001
|
-
throw new AppError("STREAM_ITEM_INVALID", "Stream item did not match its contract", 500);
|
|
1002
|
-
}
|
|
1003
|
-
const item = parsed.data;
|
|
1004
|
-
const terminal = descriptor.terminal?.safeParse(item).success ?? false;
|
|
1005
|
-
if (terminal)
|
|
1006
|
-
terminalSeen = true;
|
|
1007
|
-
const frame = descriptor.framing === "item" ? item : { type: "data", data: item };
|
|
1008
|
-
if (encodedBytes(frame, format) > maxFrameBytes) {
|
|
1009
|
-
console.error(`[stitchkit] contract stream frame exceeded its ${maxFrameBytes} byte limit`);
|
|
1010
|
-
throw new AppError("STREAM_FRAME_TOO_LARGE", "Stream item exceeded its declared frame limit", 500);
|
|
1011
|
-
}
|
|
1012
|
-
yield frame;
|
|
1013
|
-
if (terminal && descriptor.completion === "terminal")
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
if (!terminalSeen) {
|
|
1017
|
-
throw new AppError("STREAM_TERMINAL_MISSING", "Stream completed before its declared terminal item", 500);
|
|
1018
|
-
}
|
|
1019
|
-
if (descriptor.framing !== "item")
|
|
1020
|
-
yield { type: "end" };
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
if (operationAbort.signal.aborted && !lifetimeExpired)
|
|
1023
|
-
return;
|
|
1024
|
-
if (lifetimeExpired) {
|
|
1025
|
-
if (descriptor.framing === "item")
|
|
1026
|
-
return;
|
|
1027
|
-
yield {
|
|
1028
|
-
type: "error",
|
|
1029
|
-
error: new AppError("STREAM_LIFETIME_EXCEEDED", "Stream exceeded its declared lifetime", 408).toJSON().error
|
|
1030
|
-
};
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
if (descriptor.framing === "item") {
|
|
1034
|
-
console.error("[stitchkit] item-framed contract stream failed:", normalizeError(error));
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
yield { type: "error", error: normalizeError(error).toJSON().error };
|
|
1038
|
-
} finally {
|
|
1039
|
-
if (lifetime !== undefined)
|
|
1040
|
-
clearTimeout(lifetime);
|
|
1041
|
-
closeSource();
|
|
1042
|
-
}
|
|
1043
|
-
};
|
|
1044
|
-
const route = streamingRoute({
|
|
1045
|
-
path: "/",
|
|
1046
|
-
format,
|
|
1047
|
-
heartbeatMs: descriptor.heartbeatMs,
|
|
1048
|
-
idleTimeoutSeconds: descriptor.idleTimeoutSeconds,
|
|
1049
|
-
source: () => frames(),
|
|
1050
|
-
onClose: () => operationAbort.abort()
|
|
1051
|
-
});
|
|
1052
|
-
return Promise.resolve(route.handler(request, context));
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
// src/server/error-dispatch.ts
|
|
1056
|
-
async function dispatchErrorHooks(config) {
|
|
1057
|
-
for (const [scope, hooks] of [
|
|
1058
|
-
["group", config.group],
|
|
1059
|
-
["global", config.global]
|
|
1060
|
-
]) {
|
|
1061
|
-
if (!hooks?.onError)
|
|
1062
|
-
continue;
|
|
1063
|
-
try {
|
|
1064
|
-
const response = await hooks.onError(config.context, config.error, config.endpoint);
|
|
1065
|
-
if (response instanceof Response)
|
|
1066
|
-
return config.respond(response);
|
|
1067
|
-
} catch (error) {
|
|
1068
|
-
const message = `[stitchkit] ${scope} onError failed`;
|
|
1069
|
-
try {
|
|
1070
|
-
if (config.logger) {
|
|
1071
|
-
config.logger.error(message, { error, traceId: config.context.traceId });
|
|
1072
|
-
} else
|
|
1073
|
-
console.error(message, error);
|
|
1074
|
-
} catch {}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// src/server/logger.ts
|
|
1081
|
-
var c = {
|
|
1082
|
-
reset: "\x1B[0m",
|
|
1083
|
-
dim: "\x1B[2m",
|
|
1084
|
-
red: "\x1B[31m",
|
|
1085
|
-
green: "\x1B[32m",
|
|
1086
|
-
yellow: "\x1B[33m",
|
|
1087
|
-
blue: "\x1B[34m",
|
|
1088
|
-
magenta: "\x1B[35m",
|
|
1089
|
-
cyan: "\x1B[36m",
|
|
1090
|
-
gray: "\x1B[90m"
|
|
1091
|
-
};
|
|
1092
|
-
var METHOD_COLOR = {
|
|
1093
|
-
GET: c.green,
|
|
1094
|
-
POST: c.yellow,
|
|
1095
|
-
PUT: c.blue,
|
|
1096
|
-
PATCH: c.cyan,
|
|
1097
|
-
DELETE: c.red,
|
|
1098
|
-
OPTIONS: c.gray
|
|
1099
|
-
};
|
|
1100
|
-
var runtimeEnv = process.env;
|
|
1101
|
-
function resolveLogFormat(preference) {
|
|
1102
|
-
if (preference)
|
|
1103
|
-
return preference;
|
|
1104
|
-
return runtimeEnv.NODE_ENV === "production" ? "json" : "pretty";
|
|
1105
|
-
}
|
|
1106
|
-
function timestamp() {
|
|
1107
|
-
const now = new Date;
|
|
1108
|
-
const t = now.toLocaleTimeString("en-US", {
|
|
1109
|
-
hour12: false,
|
|
1110
|
-
hour: "2-digit",
|
|
1111
|
-
minute: "2-digit",
|
|
1112
|
-
second: "2-digit"
|
|
1113
|
-
});
|
|
1114
|
-
return `${t}.${now.getMilliseconds().toString().padStart(3, "0")}`;
|
|
1115
|
-
}
|
|
1116
|
-
function safePath(pathname) {
|
|
1117
|
-
let out = "";
|
|
1118
|
-
for (let i = 0;i < pathname.length; i++) {
|
|
1119
|
-
const code = pathname.charCodeAt(i);
|
|
1120
|
-
if (code > 31 && code !== 127)
|
|
1121
|
-
out += pathname.charAt(i);
|
|
1122
|
-
}
|
|
1123
|
-
return out;
|
|
1124
|
-
}
|
|
1125
|
-
function levelForStatus(status) {
|
|
1126
|
-
if (status === 499)
|
|
1127
|
-
return "info";
|
|
1128
|
-
if (status >= 500)
|
|
1129
|
-
return "error";
|
|
1130
|
-
if (status >= 400)
|
|
1131
|
-
return "warn";
|
|
1132
|
-
return "info";
|
|
1133
|
-
}
|
|
1134
|
-
function buildLogFields(method, path, status, durationMs, traceId, errorCode2) {
|
|
1135
|
-
const fields = { traceId, method, path, status, durationMs };
|
|
1136
|
-
if (errorCode2 === undefined && status >= 400)
|
|
1137
|
-
return fields;
|
|
1138
|
-
return { ...fields, errorCode: errorCode2 };
|
|
1139
|
-
}
|
|
1140
|
-
function formatMs(ms) {
|
|
1141
|
-
if (ms >= 1000)
|
|
1142
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
1143
|
-
if (ms >= 1)
|
|
1144
|
-
return `${Math.round(ms)}ms`;
|
|
1145
|
-
return `${Math.round(ms * 1000)}µs`;
|
|
1146
|
-
}
|
|
1147
|
-
function durationColor(ms) {
|
|
1148
|
-
if (ms >= 1000)
|
|
1149
|
-
return c.red;
|
|
1150
|
-
if (ms > 300)
|
|
1151
|
-
return c.yellow;
|
|
1152
|
-
return c.green;
|
|
1153
|
-
}
|
|
1154
|
-
function statusColor(status) {
|
|
1155
|
-
if (status >= 500)
|
|
1156
|
-
return c.red;
|
|
1157
|
-
if (status >= 400)
|
|
1158
|
-
return c.yellow;
|
|
1159
|
-
if (status >= 300)
|
|
1160
|
-
return c.cyan;
|
|
1161
|
-
return c.green;
|
|
1162
|
-
}
|
|
1163
|
-
function ipLabel(ip) {
|
|
1164
|
-
if (!ip || ip === "::1" || ip === "127.0.0.1")
|
|
1165
|
-
return `${c.magenta}local${c.reset}`;
|
|
1166
|
-
return `${c.dim}${ip}${c.reset}`;
|
|
1167
|
-
}
|
|
1168
|
-
var SKIP_PREFIXES = ["/_bun/", "/_bundle", "/favicon"];
|
|
1169
|
-
function shouldLog(pathname, method) {
|
|
1170
|
-
if (method === "OPTIONS")
|
|
1171
|
-
return false;
|
|
1172
|
-
return !SKIP_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
|
1173
|
-
}
|
|
1174
|
-
function logIncoming(req, pathname, traceId, format, ipAddress) {
|
|
1175
|
-
const log = { traceId };
|
|
1176
|
-
if (format === "pretty") {
|
|
1177
|
-
const mc = METHOD_COLOR[req.method] ?? c.dim;
|
|
1178
|
-
console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${traceId}${c.reset} ${c.cyan}→${c.reset} ${safePath(pathname)} ${ipLabel(ipAddress ?? "")}`);
|
|
1179
|
-
}
|
|
1180
|
-
return log;
|
|
1181
|
-
}
|
|
1182
|
-
function structuredLine(own, extra) {
|
|
1183
|
-
try {
|
|
1184
|
-
return JSON.stringify({ ...extra, ...own });
|
|
1185
|
-
} catch {
|
|
1186
|
-
return JSON.stringify(own);
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
function logOutgoing(entry) {
|
|
1190
|
-
const { req, pathname, status, log, ipAddress, errorCode: errorCode2, durationMs, format, extra } = entry;
|
|
1191
|
-
const ms = durationMs;
|
|
1192
|
-
if (format === "json") {
|
|
1193
|
-
const own = {
|
|
1194
|
-
ts: new Date().toISOString(),
|
|
1195
|
-
level: levelForStatus(status),
|
|
1196
|
-
msg: `${req.method} ${pathname} ${status}`,
|
|
1197
|
-
...buildLogFields(req.method, pathname, status, durationMs, log.traceId, errorCode2),
|
|
1198
|
-
ip: ipAddress
|
|
1199
|
-
};
|
|
1200
|
-
console.log(structuredLine(own, extra));
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
const mc = METHOD_COLOR[req.method] ?? c.dim;
|
|
1204
|
-
const code = errorCode2 ? ` ${c.red}${errorCode2}${c.reset}` : "";
|
|
1205
|
-
console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${log.traceId}${c.reset} ${c.cyan}←${c.reset} ${safePath(pathname)} ${statusColor(status)}${status}${c.reset}${code} ${durationColor(ms)}${formatMs(ms)}${c.reset} ${ipLabel(ipAddress ?? "")}`);
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
// src/server/logging.ts
|
|
1209
|
-
var LOGGER_METHODS = ["info", "warn", "error", "debug"];
|
|
1210
|
-
function isBareLogger(value) {
|
|
1211
|
-
if (value.logger !== undefined || value.skip !== undefined || value.enrich !== undefined) {
|
|
1212
|
-
return false;
|
|
1213
|
-
}
|
|
1214
|
-
return LOGGER_METHODS.every((method) => typeof value[method] === "function");
|
|
1215
|
-
}
|
|
1216
|
-
function resolveLoggingConfig(logging) {
|
|
1217
|
-
if (logging === false)
|
|
1218
|
-
return null;
|
|
1219
|
-
if (logging === true)
|
|
1220
|
-
return {};
|
|
1221
|
-
if (isRecord(logging) && isBareLogger(logging)) {
|
|
1222
|
-
throw new Error("[stitchkit] `logging` no longer takes a StitchLogger directly — nest it: " + "`logging: { logger: myLogger }`.");
|
|
1223
|
-
}
|
|
1224
|
-
return logging;
|
|
1225
|
-
}
|
|
1226
|
-
function shouldSkipLog(config, req, url) {
|
|
1227
|
-
if (!config.skip)
|
|
1228
|
-
return false;
|
|
1229
|
-
try {
|
|
1230
|
-
return config.skip(req, url);
|
|
1231
|
-
} catch {
|
|
1232
|
-
return false;
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
function collectExtraLogFields(config, req, url, outcome) {
|
|
1236
|
-
const fields = {};
|
|
1237
|
-
const enrichKeys = [];
|
|
1238
|
-
const ctx = getRequestContext();
|
|
1239
|
-
if (ctx) {
|
|
1240
|
-
if (ctx.userId)
|
|
1241
|
-
fields.userId = ctx.userId;
|
|
1242
|
-
if (ctx.serviceName)
|
|
1243
|
-
fields.serviceName = ctx.serviceName;
|
|
1244
|
-
if (ctx.action)
|
|
1245
|
-
fields.action = ctx.action;
|
|
1246
|
-
if (ctx.dimensions)
|
|
1247
|
-
fields.dimensions = ctx.dimensions;
|
|
1248
|
-
}
|
|
1249
|
-
if (config.enrich) {
|
|
1250
|
-
try {
|
|
1251
|
-
const extra = config.enrich(req, url, outcome);
|
|
1252
|
-
if (extra) {
|
|
1253
|
-
enrichKeys.push(...Object.keys(extra));
|
|
1254
|
-
Object.assign(fields, extra);
|
|
1255
|
-
}
|
|
1256
|
-
} catch {}
|
|
1257
|
-
}
|
|
1258
|
-
return { fields, enrichKeys };
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
// src/server/response-metadata.ts
|
|
1262
|
-
var RESERVED_HEADERS = new Set(["content-type", "content-length", "x-request-id"]);
|
|
1263
|
-
function hasGetSetCookie(headers) {
|
|
1264
|
-
return "getSetCookie" in headers && typeof headers.getSetCookie === "function";
|
|
1265
|
-
}
|
|
1266
|
-
function isReservedHeader(name) {
|
|
1267
|
-
const normalized = name.toLowerCase();
|
|
1268
|
-
return RESERVED_HEADERS.has(normalized) || normalized.startsWith("access-control-");
|
|
1269
|
-
}
|
|
1270
|
-
function createResponseMetadata() {
|
|
1271
|
-
return { headers: new Headers };
|
|
1272
|
-
}
|
|
1273
|
-
function applyResponseMetadata(target, metadata, endpointIdentity) {
|
|
1274
|
-
if (!metadata)
|
|
1275
|
-
return;
|
|
1276
|
-
for (const [name] of metadata.headers) {
|
|
1277
|
-
if (isReservedHeader(name)) {
|
|
1278
|
-
throw new Error(`${endpointIdentity} cannot set framework-owned response header "${name}"`);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
const preservesSetCookie = hasGetSetCookie(metadata.headers);
|
|
1282
|
-
for (const [name, value] of metadata.headers) {
|
|
1283
|
-
if (name.toLowerCase() === "set-cookie" && preservesSetCookie)
|
|
1284
|
-
continue;
|
|
1285
|
-
target.append(name, value);
|
|
1286
|
-
}
|
|
1287
|
-
if (preservesSetCookie) {
|
|
1288
|
-
for (const cookie of metadata.headers.getSetCookie()) {
|
|
1289
|
-
target.append("Set-Cookie", cookie);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
// src/server/router.ts
|
|
1295
|
-
function routeSegments(path) {
|
|
1296
|
-
return path.split("/").filter(Boolean);
|
|
1297
|
-
}
|
|
1298
|
-
function rawRouteShape(path) {
|
|
1299
|
-
parseTrailingWildcard(path);
|
|
1300
|
-
const segments = routeSegments(path).map((segment) => {
|
|
1301
|
-
if (segment.startsWith(":"))
|
|
1302
|
-
return { kind: "param" };
|
|
1303
|
-
if (segment.startsWith("*"))
|
|
1304
|
-
return { kind: "wildcard" };
|
|
1305
|
-
return { kind: "static", value: segment };
|
|
1306
|
-
});
|
|
1307
|
-
return {
|
|
1308
|
-
segments,
|
|
1309
|
-
signature: segments.map((segment) => segment.kind === "static" ? segment.value : `:${segment.kind}`).join("/"),
|
|
1310
|
-
wildcard: segments.at(-1)?.kind === "wildcard"
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
function segmentCovers(earlier, later) {
|
|
1314
|
-
if (earlier.kind === "param")
|
|
1315
|
-
return later.kind !== "wildcard";
|
|
1316
|
-
if (earlier.kind === "wildcard")
|
|
1317
|
-
return true;
|
|
1318
|
-
return later.kind === "static" && earlier.value === later.value;
|
|
1319
|
-
}
|
|
1320
|
-
function routeShapeCovers(earlier, later) {
|
|
1321
|
-
const earlierPrefixLength = earlier.wildcard ? earlier.segments.length - 1 : earlier.segments.length;
|
|
1322
|
-
const laterPrefixLength = later.wildcard ? later.segments.length - 1 : later.segments.length;
|
|
1323
|
-
if (earlier.wildcard) {
|
|
1324
|
-
if (earlierPrefixLength > laterPrefixLength)
|
|
1325
|
-
return false;
|
|
1326
|
-
} else {
|
|
1327
|
-
if (later.wildcard || earlierPrefixLength !== laterPrefixLength)
|
|
1328
|
-
return false;
|
|
1329
|
-
}
|
|
1330
|
-
for (let index = 0;index < earlierPrefixLength; index++) {
|
|
1331
|
-
const earlierSegment = earlier.segments[index];
|
|
1332
|
-
const laterSegment = later.segments[index];
|
|
1333
|
-
if (!earlierSegment || !laterSegment || !segmentCovers(earlierSegment, laterSegment)) {
|
|
1334
|
-
return false;
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
return true;
|
|
1338
|
-
}
|
|
1339
|
-
function matchSegments(patternSegments, requestSegments) {
|
|
1340
|
-
const wildcardSegment = patternSegments.at(-1);
|
|
1341
|
-
const wildcardName = wildcardSegment?.startsWith("*") ? wildcardSegment.slice(1) : null;
|
|
1342
|
-
const prefixLength = wildcardName ? patternSegments.length - 1 : patternSegments.length;
|
|
1343
|
-
if (wildcardName) {
|
|
1344
|
-
if (requestSegments.length < prefixLength)
|
|
1345
|
-
return null;
|
|
1346
|
-
} else if (patternSegments.length !== requestSegments.length) {
|
|
1347
|
-
return null;
|
|
1348
|
-
}
|
|
1349
|
-
const params = {};
|
|
1350
|
-
for (const [i, pattern] of patternSegments.slice(0, prefixLength).entries()) {
|
|
1351
|
-
const actual = requestSegments[i];
|
|
1352
|
-
if (actual === undefined)
|
|
1353
|
-
return null;
|
|
1354
|
-
if (pattern.startsWith(":")) {
|
|
1355
|
-
const decoded = decodeSegment(actual);
|
|
1356
|
-
if (decoded === null)
|
|
1357
|
-
return null;
|
|
1358
|
-
params[pattern.slice(1)] = decoded;
|
|
1359
|
-
} else if (pattern !== actual) {
|
|
1360
|
-
return null;
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
if (wildcardName) {
|
|
1364
|
-
const decoded = requestSegments.slice(prefixLength).map(decodeSegment);
|
|
1365
|
-
if (decoded.some((segment) => segment === null))
|
|
1366
|
-
return null;
|
|
1367
|
-
params[wildcardName] = decoded.join("/");
|
|
1368
|
-
}
|
|
1369
|
-
return params;
|
|
1370
|
-
}
|
|
1371
|
-
function decodeSegment(segment) {
|
|
1372
|
-
try {
|
|
1373
|
-
return decodeURIComponent(segment);
|
|
1374
|
-
} catch {
|
|
1375
|
-
return null;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
function segmentRank(segment) {
|
|
1379
|
-
if (segment?.startsWith("*"))
|
|
1380
|
-
return 2;
|
|
1381
|
-
if (segment?.startsWith(":"))
|
|
1382
|
-
return 1;
|
|
1383
|
-
return 0;
|
|
1384
|
-
}
|
|
1385
|
-
function buildRouteMap(groups) {
|
|
1386
|
-
const map = new Map;
|
|
1387
|
-
for (const { prefix, service, hooks } of groups) {
|
|
1388
|
-
for (const [, method] of Object.entries(service.methods)) {
|
|
1389
|
-
if (method.expose && !method.expose.includes("HTTP"))
|
|
1390
|
-
continue;
|
|
1391
|
-
const servicePath = joinRoutePath("/", service.prefix, method.path === "/" ? "" : method.path);
|
|
1392
|
-
const fullPath = prefix ? joinRoutePath(prefix, servicePath) : servicePath;
|
|
1393
|
-
const segments = routeSegments(fullPath);
|
|
1394
|
-
const entries = map.get(method.method) ?? [];
|
|
1395
|
-
entries.push({ method, pattern: fullPath, segments, groupHooks: hooks });
|
|
1396
|
-
map.set(method.method, entries);
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
for (const [, entries] of map) {
|
|
1400
|
-
entries.sort((a, b) => {
|
|
1401
|
-
const len = Math.min(a.segments.length, b.segments.length);
|
|
1402
|
-
for (let i = 0;i < len; i++) {
|
|
1403
|
-
const rankDifference = segmentRank(a.segments[i]) - segmentRank(b.segments[i]);
|
|
1404
|
-
if (rankDifference !== 0)
|
|
1405
|
-
return rankDifference;
|
|
1406
|
-
}
|
|
1407
|
-
return a.segments.length - b.segments.length;
|
|
1408
|
-
});
|
|
1409
|
-
}
|
|
1410
|
-
return map;
|
|
1411
|
-
}
|
|
1412
|
-
function matchRoute(routeMap, httpMethod, pathname) {
|
|
1413
|
-
const entries = routeMap.get(httpMethod);
|
|
1414
|
-
if (!entries)
|
|
1415
|
-
return null;
|
|
1416
|
-
const requestSegments = routeSegments(pathname);
|
|
1417
|
-
for (const entry of entries) {
|
|
1418
|
-
const pathParams = matchSegments(entry.segments, requestSegments);
|
|
1419
|
-
if (pathParams) {
|
|
1420
|
-
return {
|
|
1421
|
-
method: entry.method,
|
|
1422
|
-
pathParams,
|
|
1423
|
-
groupHooks: entry.groupHooks
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
return null;
|
|
1428
|
-
}
|
|
1429
|
-
function allowedMethods(routeMap, pathname) {
|
|
1430
|
-
const requestSegments = routeSegments(pathname);
|
|
1431
|
-
const methods = [];
|
|
1432
|
-
for (const [method, entries] of routeMap) {
|
|
1433
|
-
for (const entry of entries) {
|
|
1434
|
-
if (matchSegments(entry.segments, requestSegments)) {
|
|
1435
|
-
methods.push(method);
|
|
1436
|
-
break;
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
return methods;
|
|
1441
|
-
}
|
|
1442
|
-
function validateRoutes(routeMap) {
|
|
1443
|
-
for (const [method, entries] of routeMap) {
|
|
1444
|
-
const seen = new Map;
|
|
1445
|
-
for (const entry of entries) {
|
|
1446
|
-
parseTrailingWildcard(entry.pattern);
|
|
1447
|
-
const normalized = entry.segments.map((s) => s.startsWith(":") ? ":param" : s.startsWith("*") ? "*wildcard" : s).join("/");
|
|
1448
|
-
const key = `${method} /${normalized}`;
|
|
1449
|
-
const existing = seen.get(key);
|
|
1450
|
-
if (existing) {
|
|
1451
|
-
throw new Error(`Duplicate route: ${method} ${entry.pattern} conflicts with ${existing}`);
|
|
1452
|
-
}
|
|
1453
|
-
seen.set(key, entry.pattern);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
function findShadowedRoutes(routeMap, rawRoutes) {
|
|
1458
|
-
if (!rawRoutes || rawRoutes.length === 0)
|
|
1459
|
-
return [];
|
|
1460
|
-
const shadowed = [];
|
|
1461
|
-
for (const [httpMethod, entries] of routeMap) {
|
|
1462
|
-
for (const entry of entries) {
|
|
1463
|
-
const probe = `/${entry.segments.map((segment) => {
|
|
1464
|
-
if (segment.startsWith(":"))
|
|
1465
|
-
return "__param__";
|
|
1466
|
-
if (segment.startsWith("*"))
|
|
1467
|
-
return "__wildcard__";
|
|
1468
|
-
return segment;
|
|
1469
|
-
}).join("/")}`;
|
|
1470
|
-
const match = matchRawRoute(rawRoutes, httpMethod, probe);
|
|
1471
|
-
if (!match)
|
|
1472
|
-
continue;
|
|
1473
|
-
shadowed.push({
|
|
1474
|
-
pattern: `${httpMethod} ${entry.pattern}`,
|
|
1475
|
-
endpoint: `${entry.method.serviceName}.${entry.method.key}`,
|
|
1476
|
-
rawRoute: `${match.route.method} ${match.route.path}`,
|
|
1477
|
-
scope: entry.method.scope
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
return shadowed;
|
|
1482
|
-
}
|
|
1483
|
-
function matchRawRoute(rawRoutes, httpMethod, pathname) {
|
|
1484
|
-
for (const route of rawRoutes) {
|
|
1485
|
-
if (route.method !== "ALL" && route.method !== httpMethod)
|
|
1486
|
-
continue;
|
|
1487
|
-
if (parseTrailingWildcard(route.path)) {
|
|
1488
|
-
const routeSegs = routeSegments(route.path);
|
|
1489
|
-
const pathSegs = routeSegments(pathname);
|
|
1490
|
-
const params = matchSegments(routeSegs, pathSegs);
|
|
1491
|
-
if (params)
|
|
1492
|
-
return { route, params };
|
|
1493
|
-
continue;
|
|
1494
|
-
}
|
|
1495
|
-
if (route.path.includes("/:")) {
|
|
1496
|
-
const routeSegs = routeSegments(route.path);
|
|
1497
|
-
const pathSegs = routeSegments(pathname);
|
|
1498
|
-
const params = matchSegments(routeSegs, pathSegs);
|
|
1499
|
-
if (params)
|
|
1500
|
-
return { route, params };
|
|
1501
|
-
continue;
|
|
1502
|
-
}
|
|
1503
|
-
if (route.path === pathname)
|
|
1504
|
-
return { route, params: {} };
|
|
1505
|
-
}
|
|
1506
|
-
return null;
|
|
1507
|
-
}
|
|
1508
|
-
function validateRawRoutes(rawRoutes) {
|
|
1509
|
-
const routes = rawRoutes ?? [];
|
|
1510
|
-
for (const route of routes) {
|
|
1511
|
-
if (route.serviceName !== undefined && route.serviceName.trim().length === 0) {
|
|
1512
|
-
throw new Error(`Raw route ${route.method} ${route.path} has an empty serviceName`);
|
|
1513
|
-
}
|
|
1514
|
-
if (route.action !== undefined && route.action.trim().length === 0) {
|
|
1515
|
-
throw new Error(`Raw route ${route.method} ${route.path} has an empty action`);
|
|
1516
|
-
}
|
|
1517
|
-
if (route.action !== undefined && route.serviceName === undefined) {
|
|
1518
|
-
throw new Error(`Raw route ${route.method} ${route.path} declares action without serviceName`);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
const shapes = routes.map((route) => rawRouteShape(route.path));
|
|
1522
|
-
const conflicts = [];
|
|
1523
|
-
for (const [laterIndex, later] of routes.entries()) {
|
|
1524
|
-
const laterShape = shapes[laterIndex];
|
|
1525
|
-
if (!laterShape)
|
|
1526
|
-
continue;
|
|
1527
|
-
for (let earlierIndex = 0;earlierIndex < laterIndex; earlierIndex++) {
|
|
1528
|
-
const earlier = routes[earlierIndex];
|
|
1529
|
-
const earlierShape = shapes[earlierIndex];
|
|
1530
|
-
if (!earlier || !earlierShape)
|
|
1531
|
-
continue;
|
|
1532
|
-
if (earlier.method === later.method && earlier.path === later.path) {
|
|
1533
|
-
conflicts.push(`${later.method} ${later.path} duplicates earlier ${earlier.method} ${earlier.path}`);
|
|
1534
|
-
continue;
|
|
1535
|
-
}
|
|
1536
|
-
if (earlier.method === later.method && earlierShape.signature === laterShape.signature) {
|
|
1537
|
-
conflicts.push(`${later.method} ${later.path} has the same parameter shape as earlier ${earlier.method} ${earlier.path}`);
|
|
1538
|
-
continue;
|
|
1539
|
-
}
|
|
1540
|
-
const methodCovered = earlier.method === "ALL" || earlier.method === later.method;
|
|
1541
|
-
if (methodCovered && routeShapeCovers(earlierShape, laterShape)) {
|
|
1542
|
-
conflicts.push(`${later.method} ${later.path} is unreachable because earlier ${earlier.method} ${earlier.path} matches every request it could receive`);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
if (conflicts.length > 0) {
|
|
1547
|
-
throw new Error(`[stitchkit] conflicting raw routes:
|
|
1548
|
-
- ${conflicts.join(`
|
|
1549
|
-
- `)}`);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
// src/server/create.ts
|
|
1554
|
-
var BODY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
1555
|
-
var MAX_REQUEST_ABORT_CAUSE_DEPTH = 8;
|
|
1556
|
-
function containsRequestAbortReason(error, reason) {
|
|
1557
|
-
let current = error;
|
|
1558
|
-
const visited = new Set;
|
|
1559
|
-
for (let depth = 0;depth <= MAX_REQUEST_ABORT_CAUSE_DEPTH; depth += 1) {
|
|
1560
|
-
if (current === reason)
|
|
1561
|
-
return true;
|
|
1562
|
-
if (depth === MAX_REQUEST_ABORT_CAUSE_DEPTH || !isRecord(current))
|
|
1563
|
-
return false;
|
|
1564
|
-
if (visited.has(current))
|
|
1565
|
-
return false;
|
|
1566
|
-
visited.add(current);
|
|
1567
|
-
current = current.cause;
|
|
1568
|
-
}
|
|
1569
|
-
return false;
|
|
1570
|
-
}
|
|
1571
|
-
function isClientClosedRequest(req, error) {
|
|
1572
|
-
if (!req.signal.aborted)
|
|
1573
|
-
return false;
|
|
1574
|
-
if (isRecord(error) && error.name === "AbortError")
|
|
1575
|
-
return true;
|
|
1576
|
-
return req.signal.reason !== undefined && containsRequestAbortReason(error, req.signal.reason);
|
|
1577
|
-
}
|
|
1578
|
-
function isAsyncIterable(value) {
|
|
1579
|
-
if (typeof value !== "object" && typeof value !== "function" || value === null) {
|
|
1580
|
-
return false;
|
|
1581
|
-
}
|
|
1582
|
-
return typeof Reflect.get(value, Symbol.asyncIterator) === "function";
|
|
1583
|
-
}
|
|
1584
|
-
function createHandler(config) {
|
|
1585
|
-
const { cors, hooks, logging = false, observability, trustProxy = false } = config;
|
|
1586
|
-
if (cors)
|
|
1587
|
-
assertCorsConfig(cors);
|
|
1588
|
-
assertJsonBodyLimit(config.maxJsonBodyBytes, "HandlerConfig.maxJsonBodyBytes");
|
|
1589
|
-
const logConfig = resolveLoggingConfig(logging);
|
|
1590
|
-
const customLogger = logConfig?.logger ?? null;
|
|
1591
|
-
const useDefaultLog = logConfig !== null && !logConfig.logger;
|
|
1592
|
-
const customTraceId = config.traceId;
|
|
1593
|
-
const warn = (line) => {
|
|
1594
|
-
try {
|
|
1595
|
-
if (customLogger)
|
|
1596
|
-
customLogger.warn(line);
|
|
1597
|
-
else
|
|
1598
|
-
console.warn(line);
|
|
1599
|
-
} catch {}
|
|
1600
|
-
};
|
|
1601
|
-
let traceResolverBroken = false;
|
|
1602
|
-
const warnedEnrichKeys = new Set;
|
|
1603
|
-
const resolveId = (req, fallback) => {
|
|
1604
|
-
if (!customTraceId)
|
|
1605
|
-
return fallback ?? resolveTraceId(req);
|
|
1606
|
-
try {
|
|
1607
|
-
return customTraceId(req) ?? fallback ?? resolveTraceId(req);
|
|
1608
|
-
} catch (err) {
|
|
1609
|
-
if (!traceResolverBroken) {
|
|
1610
|
-
traceResolverBroken = true;
|
|
1611
|
-
warn("[stitchkit] `traceId` resolver threw — falling back to the framework resolver. " + "Ids will not match your observability context until it is fixed: " + `${err instanceof Error ? err.message : String(err)}`);
|
|
1612
|
-
}
|
|
1613
|
-
return fallback ?? resolveTraceId(req);
|
|
1614
|
-
}
|
|
1615
|
-
};
|
|
1616
|
-
for (const group of config.groups ?? []) {
|
|
1617
|
-
if (group.hooks && "onRequest" in group.hooks) {
|
|
1618
|
-
throw new Error("[stitchkit] a route group cannot declare `onRequest` — it runs before routing, so the group it belongs to is not known yet and the hook would never be dispatched. Refuse before dispatch with the server-level `hooks.onRequest`, or gate the group with its `authorize`, which runs once the endpoint is known.");
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
const routeMap = buildRouteMap(normalizeGroups(config));
|
|
1622
|
-
validateRoutes(routeMap);
|
|
1623
|
-
validateRawRoutes(config.rawRoutes);
|
|
1624
|
-
for (const shadow of findShadowedRoutes(routeMap, config.rawRoutes)) {
|
|
1625
|
-
const gate = shadow.scope && shadow.scope !== "public" ? ` (scope "${shadow.scope}")` : "";
|
|
1626
|
-
const line = `[stitchkit] raw route ${shadow.rawRoute} shadows contract route ${shadow.pattern}` + ` → ${shadow.endpoint}${gate} will never run, and its hooks never apply`;
|
|
1627
|
-
warn(line);
|
|
1628
|
-
}
|
|
1629
|
-
async function dispatch(req, url, traceId, server, clientIp, startedAt) {
|
|
1630
|
-
const shouldLogRequest = logConfig !== null && shouldLog(url.pathname, req.method) && !shouldSkipLog(logConfig, req, url);
|
|
1631
|
-
const ipAddress = extractIp(req, clientIp) || undefined;
|
|
1632
|
-
const payload = observability?.includePayload && BODY_METHODS.has(req.method) ? req.clone().json().catch(() => {
|
|
1633
|
-
return;
|
|
1634
|
-
}) : undefined;
|
|
1635
|
-
const logFormat = resolveLogFormat(logConfig?.format);
|
|
1636
|
-
let reqLog;
|
|
1637
|
-
if (shouldLogRequest && useDefaultLog) {
|
|
1638
|
-
reqLog = logIncoming(req, url.pathname, traceId, logFormat, ipAddress);
|
|
1639
|
-
}
|
|
1640
|
-
if (shouldLogRequest && customLogger) {
|
|
1641
|
-
reqLog = { traceId };
|
|
1642
|
-
try {
|
|
1643
|
-
customLogger.debug(`${req.method} ${url.pathname}`, {
|
|
1644
|
-
traceId,
|
|
1645
|
-
method: req.method,
|
|
1646
|
-
path: url.pathname,
|
|
1647
|
-
ip: ipAddress
|
|
1648
|
-
});
|
|
1649
|
-
} catch {}
|
|
1650
|
-
}
|
|
1651
|
-
let completed = false;
|
|
1652
|
-
const complete = (status, errorCode2, outcome) => {
|
|
1653
|
-
if (completed)
|
|
1654
|
-
return;
|
|
1655
|
-
completed = true;
|
|
1656
|
-
const durationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
|
|
1657
|
-
if (reqLog && logConfig) {
|
|
1658
|
-
try {
|
|
1659
|
-
const collected = collectExtraLogFields(logConfig, req, url, {
|
|
1660
|
-
status,
|
|
1661
|
-
durationMs,
|
|
1662
|
-
errorCode: errorCode2
|
|
1663
|
-
});
|
|
1664
|
-
const frameworkFields = buildLogFields(req.method, url.pathname, status, durationMs, reqLog.traceId, errorCode2);
|
|
1665
|
-
const ownedFields = { ...frameworkFields, ip: ipAddress };
|
|
1666
|
-
for (const key of collected.enrichKeys) {
|
|
1667
|
-
const ownedByActiveSink = Object.hasOwn(ownedFields, key) || useDefaultLog && (key === "ts" || key === "level" || key === "msg");
|
|
1668
|
-
if (ownedByActiveSink && !warnedEnrichKeys.has(key)) {
|
|
1669
|
-
warnedEnrichKeys.add(key);
|
|
1670
|
-
warn(`[stitchkit] logging.enrich field "${key}" was discarded because the framework owns it`);
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
const extra = collected.fields;
|
|
1674
|
-
if (useDefaultLog) {
|
|
1675
|
-
logOutgoing({
|
|
1676
|
-
req,
|
|
1677
|
-
pathname: url.pathname,
|
|
1678
|
-
status,
|
|
1679
|
-
log: reqLog,
|
|
1680
|
-
ipAddress,
|
|
1681
|
-
errorCode: errorCode2,
|
|
1682
|
-
durationMs,
|
|
1683
|
-
format: logFormat,
|
|
1684
|
-
extra
|
|
1685
|
-
});
|
|
1686
|
-
}
|
|
1687
|
-
if (customLogger) {
|
|
1688
|
-
const level = levelForStatus(status);
|
|
1689
|
-
customLogger[level](`${req.method} ${url.pathname} ${status}${errorCode2 ? ` ${errorCode2}` : ""} ${durationMs}ms`, {
|
|
1690
|
-
...extra,
|
|
1691
|
-
...frameworkFields,
|
|
1692
|
-
ip: ipAddress
|
|
1693
|
-
});
|
|
1694
|
-
}
|
|
1695
|
-
} catch {}
|
|
1696
|
-
}
|
|
1697
|
-
const context = getRequestContext();
|
|
1698
|
-
if (observability && context) {
|
|
1699
|
-
try {
|
|
1700
|
-
observability.complete({
|
|
1701
|
-
context,
|
|
1702
|
-
statusCode: status,
|
|
1703
|
-
durationMs,
|
|
1704
|
-
payload,
|
|
1705
|
-
...outcome !== undefined && { outcome }
|
|
1706
|
-
});
|
|
1707
|
-
} catch {}
|
|
1708
|
-
}
|
|
1709
|
-
};
|
|
1710
|
-
const respondError = async (err, errCtx, endpoint, group) => {
|
|
1711
|
-
if (isClientClosedRequest(req, err)) {
|
|
1712
|
-
const response = applyCors(new Response(null, { status: 499, statusText: "Client Closed Request" }), cors, req);
|
|
1713
|
-
complete(response.status, undefined, "cancelled");
|
|
1714
|
-
return response;
|
|
1715
|
-
}
|
|
1716
|
-
const recordFailure = (normalized) => {
|
|
1717
|
-
if (getRequestContext()?.error !== undefined)
|
|
1718
|
-
return;
|
|
1719
|
-
const known = AppError.is(err) ? err : normalized;
|
|
1720
|
-
const code = known?.code ?? errorCode(err) ?? "INTERNAL_SERVER_ERROR";
|
|
1721
|
-
setRequestError({
|
|
1722
|
-
code,
|
|
1723
|
-
message: recordedErrorMessage(code, known?.message, err),
|
|
1724
|
-
details: known?.details
|
|
1725
|
-
});
|
|
1726
|
-
};
|
|
1727
|
-
const customResponse = await dispatchErrorHooks({
|
|
1728
|
-
context: errCtx ?? buildErrorContext(req, url, traceId, clientIp),
|
|
1729
|
-
error: err,
|
|
1730
|
-
endpoint,
|
|
1731
|
-
group,
|
|
1732
|
-
global: hooks,
|
|
1733
|
-
logger: customLogger,
|
|
1734
|
-
respond: (response) => {
|
|
1735
|
-
recordFailure();
|
|
1736
|
-
const withCors = applyCors(response, cors, req);
|
|
1737
|
-
complete(withCors.status, errorCode(err));
|
|
1738
|
-
return withCors;
|
|
1739
|
-
}
|
|
1740
|
-
});
|
|
1741
|
-
if (customResponse)
|
|
1742
|
-
return customResponse;
|
|
1743
|
-
const appErr = normalizeError(err);
|
|
1744
|
-
recordFailure(appErr);
|
|
1745
|
-
complete(appErr.status, appErr.code);
|
|
1746
|
-
return json(appErr.toJSON(), appErr.status, cors, req);
|
|
1747
|
-
};
|
|
1748
|
-
if (cors && req.method === "OPTIONS") {
|
|
1749
|
-
const response = corsPreflightResponse(cors, req);
|
|
1750
|
-
complete(response.status);
|
|
1751
|
-
return response;
|
|
1752
|
-
}
|
|
1753
|
-
try {
|
|
1754
|
-
if (hooks?.onRequest) {
|
|
1755
|
-
const earlyResponse = await hooks.onRequest(req);
|
|
1756
|
-
if (earlyResponse instanceof Response) {
|
|
1757
|
-
const withCors = applyCors(earlyResponse, cors, req);
|
|
1758
|
-
complete(withCors.status);
|
|
1759
|
-
return withCors;
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
} catch (err) {
|
|
1763
|
-
return respondError(err);
|
|
1764
|
-
}
|
|
1765
|
-
if (config.rawRoutes) {
|
|
1766
|
-
const rawMatch = matchRawRoute(config.rawRoutes, req.method, url.pathname);
|
|
1767
|
-
if (rawMatch) {
|
|
1768
|
-
if (rawMatch.route.serviceName !== undefined) {
|
|
1769
|
-
setRequestEndpoint(rawMatch.route.serviceName, rawMatch.route.action);
|
|
1770
|
-
}
|
|
1771
|
-
try {
|
|
1772
|
-
const res = await rawMatch.route.handler(req, {
|
|
1773
|
-
params: rawMatch.params,
|
|
1774
|
-
server,
|
|
1775
|
-
ipAddress
|
|
1776
|
-
});
|
|
1777
|
-
const withCors = applyCors(res, cors, req);
|
|
1778
|
-
complete(withCors.status);
|
|
1779
|
-
return withCors;
|
|
1780
|
-
} catch (err) {
|
|
1781
|
-
return respondError(err);
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
const match = matchRoute(routeMap, req.method, url.pathname);
|
|
1786
|
-
if (!match) {
|
|
1787
|
-
const allow = allowedMethods(routeMap, url.pathname);
|
|
1788
|
-
if (allow.length > 0) {
|
|
1789
|
-
const res = await respondError(new AppError("METHOD_NOT_ALLOWED", `Method ${req.method} not allowed`, 405));
|
|
1790
|
-
res.headers.set("Allow", allow.join(", "));
|
|
1791
|
-
return res;
|
|
1792
|
-
}
|
|
1793
|
-
return respondError(new AppError("NOT_FOUND", "Not found", 404));
|
|
1794
|
-
}
|
|
1795
|
-
const { method, pathParams, groupHooks } = match;
|
|
1796
|
-
const ctx = buildBaseContext(req, url, pathParams, traceId, clientIp);
|
|
1797
|
-
setRequestEndpoint(method.serviceName, method.key);
|
|
1798
|
-
let multipartLifecycle;
|
|
1799
|
-
try {
|
|
1800
|
-
parsePathParamsInto(ctx, method);
|
|
1801
|
-
if (hooks?.authorize) {
|
|
1802
|
-
await hooks.authorize(ctx, method);
|
|
1803
|
-
}
|
|
1804
|
-
if (groupHooks?.authorize) {
|
|
1805
|
-
await groupHooks.authorize(ctx, method);
|
|
1806
|
-
}
|
|
1807
|
-
multipartLifecycle = await parseRequestPayloadInto(ctx, req, url, method, config.maxJsonBodyBytes, cors);
|
|
1808
|
-
if (hooks?.beforeHandle) {
|
|
1809
|
-
await hooks.beforeHandle(ctx, method);
|
|
1810
|
-
}
|
|
1811
|
-
if (groupHooks?.beforeHandle) {
|
|
1812
|
-
await groupHooks.beforeHandle(ctx, method);
|
|
1813
|
-
}
|
|
1814
|
-
const responseMetadata = method.responseMeta ? createResponseMetadata() : undefined;
|
|
1815
|
-
if (responseMetadata)
|
|
1816
|
-
ctx.response = responseMetadata;
|
|
1817
|
-
const streamAbort = method.stream ? new AbortController : undefined;
|
|
1818
|
-
if (streamAbort) {
|
|
1819
|
-
if (req.signal.aborted)
|
|
1820
|
-
streamAbort.abort(req.signal.reason);
|
|
1821
|
-
else {
|
|
1822
|
-
req.signal.addEventListener("abort", () => streamAbort.abort(req.signal.reason), {
|
|
1823
|
-
once: true
|
|
1824
|
-
});
|
|
1825
|
-
}
|
|
1826
|
-
ctx.signal = streamAbort.signal;
|
|
1827
|
-
}
|
|
1828
|
-
let result = await method.handler(ctx);
|
|
1829
|
-
if (method.rawResponse) {
|
|
1830
|
-
if (!(result instanceof Response)) {
|
|
1831
|
-
throw new AppError("INTERNAL_SERVER_ERROR", `Raw endpoint ${method.serviceName}.${method.key} must return a Response`, 500);
|
|
1832
|
-
}
|
|
1833
|
-
const response = method.method === "HEAD" ? new Response(null, {
|
|
1834
|
-
status: result.status,
|
|
1835
|
-
statusText: result.statusText,
|
|
1836
|
-
headers: result.headers
|
|
1837
|
-
}) : result;
|
|
1838
|
-
const rawRes = applyCors(response, cors, req);
|
|
1839
|
-
complete(rawRes.status);
|
|
1840
|
-
return rawRes;
|
|
1841
|
-
}
|
|
1842
|
-
if (method.stream) {
|
|
1843
|
-
if (!streamAbort || !isAsyncIterable(result)) {
|
|
1844
|
-
streamAbort?.abort();
|
|
1845
|
-
throw new AppError("INTERNAL_SERVER_ERROR", `Streaming endpoint ${method.serviceName}.${method.key} must return an AsyncIterable`, 500);
|
|
1846
|
-
}
|
|
1847
|
-
const response = await contractStreamResponse(req, { params: pathParams, server, ipAddress: clientIp.socketIp }, result, method.stream, streamAbort);
|
|
1848
|
-
const withCors = applyCors(response, cors, req);
|
|
1849
|
-
complete(withCors.status);
|
|
1850
|
-
return withCors;
|
|
1851
|
-
}
|
|
1852
|
-
if (groupHooks?.afterHandle) {
|
|
1853
|
-
const transformed = await groupHooks.afterHandle(ctx, result, method);
|
|
1854
|
-
if (transformed !== undefined)
|
|
1855
|
-
result = transformed;
|
|
1856
|
-
}
|
|
1857
|
-
if (hooks?.afterHandle) {
|
|
1858
|
-
const transformed = await hooks.afterHandle(ctx, result, method);
|
|
1859
|
-
if (transformed !== undefined)
|
|
1860
|
-
result = transformed;
|
|
1861
|
-
}
|
|
1862
|
-
if (result instanceof Response) {
|
|
1863
|
-
throw new AppError("INTERNAL_SERVER_ERROR", `${method.serviceName}.${method.key} produced a Response on the data path — only a \`rawResponse: true\` endpoint may return one (an afterHandle hook must return data)`, 500);
|
|
1864
|
-
}
|
|
1865
|
-
const checked = validateDeclaredOutput(method.outputSchema, result, config.warnOnOutputStrip && method.outputSchema ? (paths) => {
|
|
1866
|
-
warn(`[stitchkit] output strip ${method.serviceName}.${method.key}: ${paths.join(", ")}`);
|
|
1867
|
-
} : undefined);
|
|
1868
|
-
if (!checked.ok) {
|
|
1869
|
-
throw new AppError("INTERNAL_SERVER_ERROR", checked.message, 500);
|
|
1870
|
-
}
|
|
1871
|
-
result = checked.data;
|
|
1872
|
-
const responseStatus = method.responseMeta?.status ?? (method.outputSchema ? 200 : 204);
|
|
1873
|
-
if (method.outputSchema && (responseStatus === 204 || responseStatus === 205)) {
|
|
1874
|
-
throw new AppError("INTERNAL_SERVER_ERROR", `${method.serviceName}.${method.key} cannot combine output with bodyless status ${responseStatus}`, 500);
|
|
1875
|
-
}
|
|
1876
|
-
const responseHeaders2 = new Headers(corsHeaders2(cors, req));
|
|
1877
|
-
applyResponseMetadata(responseHeaders2, responseMetadata, `${method.serviceName}.${method.key}`);
|
|
1878
|
-
if (!method.outputSchema) {
|
|
1879
|
-
const empty = new Response(null, { status: responseStatus, headers: responseHeaders2 });
|
|
1880
|
-
complete(responseStatus);
|
|
1881
|
-
return empty;
|
|
1882
|
-
}
|
|
1883
|
-
const body = Response.json(result, { status: responseStatus, headers: responseHeaders2 });
|
|
1884
|
-
complete(responseStatus);
|
|
1885
|
-
return body;
|
|
1886
|
-
} catch (err) {
|
|
1887
|
-
await multipartLifecycle?.rollback();
|
|
1888
|
-
return respondError(err, ctx, method, groupHooks);
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
return async (req, server) => {
|
|
1892
|
-
const url = new URL(req.url, "http://localhost");
|
|
1893
|
-
const requestStartedAt = process.hrtime.bigint();
|
|
1894
|
-
const clientIp = {
|
|
1895
|
-
trustProxy,
|
|
1896
|
-
socketIp: resolveSocketIp(req, server)
|
|
1897
|
-
};
|
|
1898
|
-
const run = async (traceId2, startedAt) => {
|
|
1899
|
-
const response = await dispatch(req, url, traceId2, server, clientIp, startedAt);
|
|
1900
|
-
try {
|
|
1901
|
-
response.headers.set("x-request-id", traceId2);
|
|
1902
|
-
const buildId = config.release?.current();
|
|
1903
|
-
if (buildId)
|
|
1904
|
-
response.headers.set(RELEASE_HEADER, buildId);
|
|
1905
|
-
} catch {}
|
|
1906
|
-
return response;
|
|
1907
|
-
};
|
|
1908
|
-
const activeContext = getRequestContext();
|
|
1909
|
-
if (activeContext) {
|
|
1910
|
-
return run(activeContext.trace.traceId, activeContext.startedAt);
|
|
1911
|
-
}
|
|
1912
|
-
if (!observability)
|
|
1913
|
-
return run(resolveId(req), requestStartedAt);
|
|
1914
|
-
const trace = resolveTraceContext(req);
|
|
1915
|
-
const traceId = resolveId(req, trace.traceId);
|
|
1916
|
-
const ipAddress = extractIp(req, clientIp) || undefined;
|
|
1917
|
-
const userAgent = req.headers.get("user-agent") ?? undefined;
|
|
1918
|
-
return runWithRequestContext({
|
|
1919
|
-
source: "http",
|
|
1920
|
-
method: req.method,
|
|
1921
|
-
path: url.pathname,
|
|
1922
|
-
startedAt: requestStartedAt,
|
|
1923
|
-
trace: { ...trace, traceId },
|
|
1924
|
-
...ipAddress !== undefined && { ipAddress },
|
|
1925
|
-
...userAgent !== undefined && { userAgent }
|
|
1926
|
-
}, () => run(traceId, requestStartedAt));
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
function normalizeGroups(config) {
|
|
1930
|
-
const result = [];
|
|
1931
|
-
if (config.services) {
|
|
1932
|
-
for (const service of config.services) {
|
|
1933
|
-
const prefix = config.scopePrefixes?.[service.scope] ?? "";
|
|
1934
|
-
result.push({ prefix, service });
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
if (config.groups) {
|
|
1938
|
-
for (const group of config.groups) {
|
|
1939
|
-
for (const service of group.services) {
|
|
1940
|
-
result.push({ prefix: group.pathPrefix ?? "", service, hooks: group.hooks });
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
return result;
|
|
1945
|
-
}
|
|
1946
|
-
function corsHeaders2(cors, req) {
|
|
1947
|
-
if (!cors)
|
|
1948
|
-
return {};
|
|
1949
|
-
return corsHeaders(cors, req.headers.get("origin"));
|
|
1950
|
-
}
|
|
1951
|
-
function applyCors(res, cors, req) {
|
|
1952
|
-
const extra = corsHeaders2(cors, req);
|
|
1953
|
-
if (Object.keys(extra).length === 0)
|
|
1954
|
-
return res;
|
|
1955
|
-
try {
|
|
1956
|
-
for (const [key, value] of Object.entries(extra))
|
|
1957
|
-
setCorsHeader(res.headers, key, value);
|
|
1958
|
-
return res;
|
|
1959
|
-
} catch {
|
|
1960
|
-
const headers = new Headers(res.headers);
|
|
1961
|
-
for (const [key, value] of Object.entries(extra))
|
|
1962
|
-
setCorsHeader(headers, key, value);
|
|
1963
|
-
return new Response(res.body, {
|
|
1964
|
-
status: res.status,
|
|
1965
|
-
statusText: res.statusText,
|
|
1966
|
-
headers
|
|
1967
|
-
});
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
function setCorsHeader(headers, key, value) {
|
|
1971
|
-
if (key !== "Vary") {
|
|
1972
|
-
headers.set(key, value);
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
const existing = headers.get("Vary");
|
|
1976
|
-
if (existing === null || existing.trim() === "") {
|
|
1977
|
-
headers.set("Vary", value);
|
|
1978
|
-
return;
|
|
1979
|
-
}
|
|
1980
|
-
const present = existing.split(",").some((field) => field.trim().toLowerCase() === "origin");
|
|
1981
|
-
if (!present)
|
|
1982
|
-
headers.set("Vary", `${existing}, ${value}`);
|
|
1983
|
-
}
|
|
1984
|
-
function json(data, status, cors, req) {
|
|
1985
|
-
return Response.json(data, { status, headers: corsHeaders2(cors, req) });
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
// src/server/process-signals.ts
|
|
1989
|
-
var bound = new WeakSet;
|
|
1990
|
-
function bindProcessSignals(handle, options = {}) {
|
|
1991
|
-
if (bound.has(handle)) {
|
|
1992
|
-
throw new Error("[stitchkit] bindProcessSignals: this server is already bound; close the first binding before creating another");
|
|
1993
|
-
}
|
|
1994
|
-
bound.add(handle);
|
|
1995
|
-
const signals = [...new Set(options.signals ?? DEFAULT_PROCESS_SIGNALS)];
|
|
1996
|
-
const source = options.signalSource ?? defaultSignalSource;
|
|
1997
|
-
const requested = options.shutdown ?? {};
|
|
1998
|
-
const validated = ShutdownOptionsSchema.omit({ signal: true }).parse(requested);
|
|
1999
|
-
const budgets = {
|
|
2000
|
-
...requested.gracePeriodMs !== undefined && { gracePeriodMs: validated.gracePeriodMs },
|
|
2001
|
-
...requested.forceTimeoutMs !== undefined && {
|
|
2002
|
-
forceTimeoutMs: validated.forceTimeoutMs
|
|
2003
|
-
},
|
|
2004
|
-
...requested.realtimeCloseTimeoutMs !== undefined && {
|
|
2005
|
-
realtimeCloseTimeoutMs: validated.realtimeCloseTimeoutMs
|
|
2006
|
-
},
|
|
2007
|
-
...requested.retryAfterSeconds !== undefined && {
|
|
2008
|
-
retryAfterSeconds: validated.retryAfterSeconds
|
|
2009
|
-
}
|
|
2010
|
-
};
|
|
2011
|
-
const controller = new AbortController;
|
|
2012
|
-
const handlers = [];
|
|
2013
|
-
let started = false;
|
|
2014
|
-
let sameTurnAsStart = false;
|
|
2015
|
-
let settled = false;
|
|
2016
|
-
let closed = false;
|
|
2017
|
-
let resolveChain = () => {
|
|
2018
|
-
return;
|
|
2019
|
-
};
|
|
2020
|
-
let rejectChain = () => {
|
|
2021
|
-
return;
|
|
2022
|
-
};
|
|
2023
|
-
const promise = new Promise((resolve, reject) => {
|
|
2024
|
-
resolveChain = resolve;
|
|
2025
|
-
rejectChain = reject;
|
|
2026
|
-
});
|
|
2027
|
-
promise.catch(() => {
|
|
2028
|
-
return;
|
|
2029
|
-
});
|
|
2030
|
-
const removeListeners = () => {
|
|
2031
|
-
if (closed)
|
|
2032
|
-
return;
|
|
2033
|
-
closed = true;
|
|
2034
|
-
for (const [signal, handler] of handlers)
|
|
2035
|
-
source.off(signal, handler);
|
|
2036
|
-
if (!started)
|
|
2037
|
-
bound.delete(handle);
|
|
2038
|
-
};
|
|
2039
|
-
const close = () => {
|
|
2040
|
-
const hadStarted = started;
|
|
2041
|
-
removeListeners();
|
|
2042
|
-
if (!hadStarted)
|
|
2043
|
-
resolveChain(undefined);
|
|
2044
|
-
};
|
|
2045
|
-
const run = async (signal) => {
|
|
2046
|
-
try {
|
|
2047
|
-
await options.onShutdown?.(signal);
|
|
2048
|
-
} catch (error) {
|
|
2049
|
-
reportSignalError("prepare", error, options.onError);
|
|
2050
|
-
}
|
|
2051
|
-
let result;
|
|
2052
|
-
try {
|
|
2053
|
-
result = await handle.shutdown({ ...budgets, signal: controller.signal });
|
|
2054
|
-
} catch (error) {
|
|
2055
|
-
settled = true;
|
|
2056
|
-
rejectChain(error);
|
|
2057
|
-
reportSignalError("shutdown", error, options.onError);
|
|
2058
|
-
removeListeners();
|
|
2059
|
-
return;
|
|
2060
|
-
}
|
|
2061
|
-
settled = true;
|
|
2062
|
-
resolveChain(result);
|
|
2063
|
-
try {
|
|
2064
|
-
await options.onComplete?.(result);
|
|
2065
|
-
} catch (error) {
|
|
2066
|
-
reportSignalError("complete", error, options.onError);
|
|
2067
|
-
} finally {
|
|
2068
|
-
removeListeners();
|
|
2069
|
-
}
|
|
2070
|
-
};
|
|
2071
|
-
const onSignal = (signal) => {
|
|
2072
|
-
if (!started) {
|
|
2073
|
-
started = true;
|
|
2074
|
-
sameTurnAsStart = true;
|
|
2075
|
-
queueMicrotask(() => {
|
|
2076
|
-
sameTurnAsStart = false;
|
|
2077
|
-
});
|
|
2078
|
-
run(signal).catch((error) => reportSignalError("shutdown", error, options.onError));
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2081
|
-
if (sameTurnAsStart)
|
|
2082
|
-
return;
|
|
2083
|
-
if (!settled && !controller.signal.aborted) {
|
|
2084
|
-
controller.abort();
|
|
2085
|
-
guardSignalCallback(() => options.onRepeatedSignal?.(signal, "force"), "shutdown", options.onError);
|
|
2086
|
-
return;
|
|
2087
|
-
}
|
|
2088
|
-
removeListeners();
|
|
2089
|
-
guardSignalCallback(() => options.onRepeatedSignal?.(signal, "escalate"), "shutdown", options.onError);
|
|
2090
|
-
let restored = false;
|
|
2091
|
-
try {
|
|
2092
|
-
restored = source.raiseDefault(signal);
|
|
2093
|
-
} catch (error) {
|
|
2094
|
-
reportSignalError("shutdown", error, options.onError);
|
|
2095
|
-
}
|
|
2096
|
-
if (!restored) {
|
|
2097
|
-
guardSignalCallback(() => options.onEscalationBlocked?.(signal), "shutdown", options.onError);
|
|
2098
|
-
}
|
|
2099
|
-
};
|
|
2100
|
-
for (const signal of signals) {
|
|
2101
|
-
const handler = () => onSignal(signal);
|
|
2102
|
-
handlers.push([signal, handler]);
|
|
2103
|
-
source.on(signal, handler);
|
|
2104
|
-
}
|
|
2105
|
-
return { promise, close };
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
// src/server/realtime.ts
|
|
2109
|
-
function bindRealtimeServer(contract, handle, options = {}) {
|
|
2110
|
-
const outbound = (target) => createValidatedRealtimeSocket({
|
|
2111
|
-
target,
|
|
2112
|
-
inbound: {},
|
|
2113
|
-
outbound: contract.serverToClient,
|
|
2114
|
-
inboundDirection: "server-inbound",
|
|
2115
|
-
outboundDirection: "server-outbound",
|
|
2116
|
-
onRejected: options.onRejected,
|
|
2117
|
-
logger: options.logger
|
|
2118
|
-
});
|
|
2119
|
-
const broadcast = outbound(handle.io);
|
|
2120
|
-
return {
|
|
2121
|
-
onConnection: (handler) => {
|
|
2122
|
-
const listener = (raw) => {
|
|
2123
|
-
const events = createValidatedRealtimeSocket({
|
|
2124
|
-
target: raw,
|
|
2125
|
-
inbound: contract.clientToServer,
|
|
2126
|
-
outbound: contract.serverToClient,
|
|
2127
|
-
inboundDirection: "server-inbound",
|
|
2128
|
-
outboundDirection: "server-outbound",
|
|
2129
|
-
onRejected: options.onRejected,
|
|
2130
|
-
logger: options.logger
|
|
2131
|
-
});
|
|
2132
|
-
handler({
|
|
2133
|
-
raw,
|
|
2134
|
-
events,
|
|
2135
|
-
to: (room) => outbound(raw.to(room))
|
|
2136
|
-
});
|
|
2137
|
-
};
|
|
2138
|
-
handle.io.on("connection", listener);
|
|
2139
|
-
return () => {
|
|
2140
|
-
handle.io.off("connection", listener);
|
|
2141
|
-
};
|
|
2142
|
-
},
|
|
2143
|
-
emit: broadcast.emit,
|
|
2144
|
-
to: (room) => outbound(handle.io.to(room))
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
// src/server/websocket.ts
|
|
2149
|
-
function webSocketLane(lane) {
|
|
2150
|
-
const { match, handlers } = lane;
|
|
2151
|
-
return {
|
|
2152
|
-
match,
|
|
2153
|
-
handlers: {
|
|
2154
|
-
message(ws, message) {
|
|
2155
|
-
if (match(ws))
|
|
2156
|
-
handlers.message(ws, message);
|
|
2157
|
-
},
|
|
2158
|
-
open(ws) {
|
|
2159
|
-
if (match(ws))
|
|
2160
|
-
handlers.open?.(ws);
|
|
2161
|
-
},
|
|
2162
|
-
close(ws, code, reason) {
|
|
2163
|
-
if (match(ws))
|
|
2164
|
-
handlers.close?.(ws, code, reason);
|
|
2165
|
-
},
|
|
2166
|
-
drain(ws) {
|
|
2167
|
-
if (match(ws))
|
|
2168
|
-
handlers.drain?.(ws);
|
|
2169
|
-
},
|
|
2170
|
-
ping(ws, data) {
|
|
2171
|
-
if (match(ws))
|
|
2172
|
-
handlers.ping?.(ws, data);
|
|
2173
|
-
},
|
|
2174
|
-
pong(ws, data) {
|
|
2175
|
-
if (match(ws))
|
|
2176
|
-
handlers.pong?.(ws, data);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
};
|
|
2180
|
-
}
|
|
2181
|
-
function composeWebSocketHandlers(lanes, config) {
|
|
2182
|
-
const pick = (ws) => lanes.find((lane) => lane.match(ws));
|
|
2183
|
-
return {
|
|
2184
|
-
...config,
|
|
2185
|
-
message(ws, message) {
|
|
2186
|
-
pick(ws)?.handlers.message(ws, message);
|
|
2187
|
-
},
|
|
2188
|
-
open(ws) {
|
|
2189
|
-
pick(ws)?.handlers.open?.(ws);
|
|
2190
|
-
},
|
|
2191
|
-
close(ws, code, reason) {
|
|
2192
|
-
pick(ws)?.handlers.close?.(ws, code, reason);
|
|
2193
|
-
},
|
|
2194
|
-
drain(ws) {
|
|
2195
|
-
pick(ws)?.handlers.drain?.(ws);
|
|
2196
|
-
},
|
|
2197
|
-
ping(ws, data) {
|
|
2198
|
-
pick(ws)?.handlers.ping?.(ws, data);
|
|
2199
|
-
},
|
|
2200
|
-
pong(ws, data) {
|
|
2201
|
-
pick(ws)?.handlers.pong?.(ws, data);
|
|
2202
|
-
}
|
|
2203
|
-
};
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
// src/server/socket-io.ts
|
|
2207
|
-
var SOCKET_IO_SERVER = "socket.io";
|
|
2208
|
-
var SOCKET_IO_BUN_ENGINE = "@socket.io/bun-engine";
|
|
2209
|
-
var noop = () => {};
|
|
2210
|
-
async function importPeer(load, pkg, injected) {
|
|
2211
|
-
try {
|
|
2212
|
-
return await load();
|
|
2213
|
-
} catch (err) {
|
|
2214
|
-
if (isModuleNotFound(err)) {
|
|
2215
|
-
throw new Error(injected ? `[stitchkit] createSocketIOServer could not load the optional peer "${pkg}" through the loader passed in \`peers\` — the artifact does not contain it. Check that the loader is a literal \`import('${pkg}')\` your bundler can follow, and that "${pkg}" is a dependency of the package being bundled.` : `[stitchkit] createSocketIOServer needs the optional peer "${pkg}" — install it: bun add ${pkg}. Shipping one self-contained artifact instead? Pass \`peers: { … }\` so your bundler puts it inside.`, { cause: err });
|
|
2216
|
-
}
|
|
2217
|
-
throw err;
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
function isBunEngineModule(value) {
|
|
2221
|
-
return typeof value === "object" && value !== null && typeof Reflect.get(value, "Server") === "function";
|
|
2222
|
-
}
|
|
2223
|
-
function handshakeRejection(message) {
|
|
2224
|
-
const error = new Error(message);
|
|
2225
|
-
error.data = { code: "handshake_rejected" };
|
|
2226
|
-
return error;
|
|
2227
|
-
}
|
|
2228
|
-
function handshakeMiddleware(handshake) {
|
|
2229
|
-
return (socket, next) => {
|
|
2230
|
-
let parsed;
|
|
2231
|
-
try {
|
|
2232
|
-
parsed = handshake.schema.safeParse(socket.handshake.auth);
|
|
2233
|
-
} catch (error) {
|
|
2234
|
-
console.error("[stitchkit] handshake schema threw — use a synchronous schema", error);
|
|
2235
|
-
next(handshakeRejection("handshake auth failed validation"));
|
|
2236
|
-
return;
|
|
2237
|
-
}
|
|
2238
|
-
if (!parsed.success) {
|
|
2239
|
-
next(handshakeRejection("handshake auth failed validation"));
|
|
2240
|
-
return;
|
|
2241
|
-
}
|
|
2242
|
-
const verify = handshake.verify;
|
|
2243
|
-
Promise.resolve().then(() => {
|
|
2244
|
-
if (verify)
|
|
2245
|
-
return verify(parsed.data, { handshake: socket.handshake });
|
|
2246
|
-
return transportResult(parsed.data);
|
|
2247
|
-
}).then((identity) => {
|
|
2248
|
-
if (identity === null || identity === undefined) {
|
|
2249
|
-
next(handshakeRejection("handshake rejected"));
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
socket.data = identity;
|
|
2253
|
-
next();
|
|
2254
|
-
}, (error) => {
|
|
2255
|
-
console.error("[stitchkit] handshake verify rejected", error);
|
|
2256
|
-
next(handshakeRejection("handshake rejected"));
|
|
2257
|
-
});
|
|
2258
|
-
};
|
|
2259
|
-
}
|
|
2260
|
-
async function createSocketIOServer(config) {
|
|
2261
|
-
const path = config.path ?? "/socket.io/";
|
|
2262
|
-
const onBun = "Bun" in globalThis;
|
|
2263
|
-
const transports = config.transports ?? (onBun ? ["websocket", "polling"] : ["websocket"]);
|
|
2264
|
-
const pingTimeout = config.pingTimeout ?? 20000;
|
|
2265
|
-
const pingInterval = config.pingInterval ?? 1e4;
|
|
2266
|
-
const cors = config.cors ? {
|
|
2267
|
-
origin: config.cors.origin,
|
|
2268
|
-
credentials: config.cors.credentials ?? true,
|
|
2269
|
-
methods: ["GET", "POST"]
|
|
2270
|
-
} : undefined;
|
|
2271
|
-
let accepting = true;
|
|
2272
|
-
let attached = false;
|
|
2273
|
-
let closePromise;
|
|
2274
|
-
const consumerAllowRequest = config.allowRequest;
|
|
2275
|
-
const checkRequest = async (request) => {
|
|
2276
|
-
if (onBun) {
|
|
2277
|
-
const requested = new URL(request.url).searchParams.get("transport");
|
|
2278
|
-
if (!transports.some((transport) => transport === requested)) {
|
|
2279
|
-
throw new Error("Transport rejected by the configured Socket.IO policy");
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
if (consumerAllowRequest && !await consumerAllowRequest(request)) {
|
|
2283
|
-
throw new Error("Request rejected by the configured Socket.IO policy");
|
|
2284
|
-
}
|
|
2285
|
-
if (!accepting)
|
|
2286
|
-
throw new Error("Server is shutting down");
|
|
2287
|
-
};
|
|
2288
|
-
const allowRequest = (request, done) => {
|
|
2289
|
-
const headers = new Headers;
|
|
2290
|
-
for (const [name, value] of Object.entries(request.headers)) {
|
|
2291
|
-
if (Array.isArray(value)) {
|
|
2292
|
-
for (const item of value)
|
|
2293
|
-
headers.append(name, item);
|
|
2294
|
-
} else if (value !== undefined) {
|
|
2295
|
-
headers.set(name, value);
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
const host = headers.get("host") ?? "localhost";
|
|
2299
|
-
const webRequest = new Request(new URL(request.url ?? "/", `http://${host}`), {
|
|
2300
|
-
method: request.method,
|
|
2301
|
-
headers
|
|
2302
|
-
});
|
|
2303
|
-
checkRequest(webRequest).then(() => done(null, true), (error) => done(error instanceof Error ? error.message : "Request rejected", false));
|
|
2304
|
-
};
|
|
2305
|
-
const injectedServer = config.peers?.server;
|
|
2306
|
-
const { Server } = await importPeer(injectedServer ?? (() => import(SOCKET_IO_SERVER).then((module) => module)), SOCKET_IO_SERVER, injectedServer !== undefined);
|
|
2307
|
-
const io = new Server({
|
|
2308
|
-
...config.serverOptions,
|
|
2309
|
-
path,
|
|
2310
|
-
cors,
|
|
2311
|
-
transports,
|
|
2312
|
-
pingTimeout,
|
|
2313
|
-
pingInterval,
|
|
2314
|
-
allowRequest
|
|
2315
|
-
});
|
|
2316
|
-
if (config.handshake)
|
|
2317
|
-
io.use(handshakeMiddleware(config.handshake));
|
|
2318
|
-
const lifecycle = {
|
|
2319
|
-
beginShutdown() {
|
|
2320
|
-
accepting = false;
|
|
2321
|
-
},
|
|
2322
|
-
close() {
|
|
2323
|
-
if (closePromise)
|
|
2324
|
-
return closePromise;
|
|
2325
|
-
closePromise = onBun || attached ? io.close() : Promise.resolve();
|
|
2326
|
-
return closePromise;
|
|
2327
|
-
},
|
|
2328
|
-
connections() {
|
|
2329
|
-
return io.engine?.clientsCount ?? 0;
|
|
2330
|
-
}
|
|
2331
|
-
};
|
|
2332
|
-
if (onBun) {
|
|
2333
|
-
const injectedEngine = config.peers?.bunEngine;
|
|
2334
|
-
const engineModule = await importPeer(injectedEngine ?? (() => import(SOCKET_IO_BUN_ENGINE).then((module) => module)), SOCKET_IO_BUN_ENGINE, injectedEngine !== undefined);
|
|
2335
|
-
if (!isBunEngineModule(engineModule)) {
|
|
2336
|
-
throw new Error(`[stitchkit] the loader passed in \`peers.bunEngine\` did not return the "${SOCKET_IO_BUN_ENGINE}" module — it must resolve to the module itself, as \`() => import('${SOCKET_IO_BUN_ENGINE}')\`.`);
|
|
2337
|
-
}
|
|
2338
|
-
const { Server: Engine } = engineModule;
|
|
2339
|
-
const engineOpts = {
|
|
2340
|
-
path,
|
|
2341
|
-
cors,
|
|
2342
|
-
pingTimeout,
|
|
2343
|
-
pingInterval,
|
|
2344
|
-
allowRequest: checkRequest
|
|
2345
|
-
};
|
|
2346
|
-
if (config.serverOptions?.maxHttpBufferSize !== undefined) {
|
|
2347
|
-
engineOpts.maxHttpBufferSize = config.serverOptions.maxHttpBufferSize;
|
|
2348
|
-
}
|
|
2349
|
-
if (config.serverOptions?.upgradeTimeout !== undefined) {
|
|
2350
|
-
engineOpts.upgradeTimeout = config.serverOptions.upgradeTimeout;
|
|
2351
|
-
}
|
|
2352
|
-
const engine = new Engine(engineOpts);
|
|
2353
|
-
io.bind(engine);
|
|
2354
|
-
const { websocket } = engine.handler();
|
|
2355
|
-
const route = {
|
|
2356
|
-
method: "ALL",
|
|
2357
|
-
path: `${path.replace(/\/+$/, "")}/*socketPath`,
|
|
2358
|
-
handler: (req, ctx) => {
|
|
2359
|
-
if (!ctx.server) {
|
|
2360
|
-
throw new Error("[stitchkit] createSocketIOServer route needs a running Bun server — mount it via createServer.");
|
|
2361
|
-
}
|
|
2362
|
-
return engine.handleRequest(req, ctx.server);
|
|
2363
|
-
}
|
|
2364
|
-
};
|
|
2365
|
-
return { io, websocket, route, attach: noop, ...lifecycle };
|
|
2366
|
-
}
|
|
2367
|
-
return {
|
|
2368
|
-
io,
|
|
2369
|
-
attach: (server) => {
|
|
2370
|
-
io.attach(server);
|
|
2371
|
-
attached = true;
|
|
2372
|
-
},
|
|
2373
|
-
websocket: { open: noop, message: noop, close: noop, maxPayloadLength: 0 },
|
|
2374
|
-
route: {
|
|
2375
|
-
method: "ALL",
|
|
2376
|
-
path: `${path.replace(/\/+$/, "")}/*socketPath`,
|
|
2377
|
-
handler: () => {
|
|
2378
|
-
throw new Error("[stitchkit] On Node, Socket.IO attaches via serveNode({ socket }) — this route is not mounted.");
|
|
2379
|
-
}
|
|
2380
|
-
},
|
|
2381
|
-
...lifecycle
|
|
2382
|
-
};
|
|
2383
|
-
}
|
|
2384
|
-
function socketIoLane(websocket) {
|
|
2385
|
-
return webSocketLane({
|
|
2386
|
-
match: (_ws) => true,
|
|
2387
|
-
handlers: websocket
|
|
2388
|
-
});
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
// src/server/unix-client-error.ts
|
|
2392
|
-
class UnixClientTransportError extends Error {
|
|
2393
|
-
code;
|
|
2394
|
-
delivery;
|
|
2395
|
-
constructor(code, message, delivery = "possibly-dispatched", options) {
|
|
2396
|
-
super(message, options);
|
|
2397
|
-
this.code = code;
|
|
2398
|
-
this.delivery = delivery;
|
|
2399
|
-
this.name = "UnixClientTransportError";
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
// src/server/unix-client.ts
|
|
2404
|
-
import {
|
|
2405
|
-
Agent,
|
|
2406
|
-
request as httpRequest
|
|
2407
|
-
} from "node:http";
|
|
2408
|
-
|
|
2409
|
-
// src/server/unix-client-body.ts
|
|
2410
|
-
async function readBoundedRequestBody(request, maxBytes) {
|
|
2411
|
-
const body = request.body;
|
|
2412
|
-
if (!body)
|
|
2413
|
-
return;
|
|
2414
|
-
const declared = request.headers.get("content-length");
|
|
2415
|
-
if (declared !== null && Number(declared) > maxBytes) {
|
|
2416
|
-
throw new UnixClientTransportError("UNIX_REQUEST_TOO_LARGE", `Unix request body exceeds the ${maxBytes} byte limit`, "not-dispatched");
|
|
2417
|
-
}
|
|
2418
|
-
const reader = body.getReader();
|
|
2419
|
-
const chunks = [];
|
|
2420
|
-
let total = 0;
|
|
2421
|
-
try {
|
|
2422
|
-
for (;; ) {
|
|
2423
|
-
if (request.signal.aborted)
|
|
2424
|
-
throw request.signal.reason;
|
|
2425
|
-
const chunk = await reader.read();
|
|
2426
|
-
if (chunk.done)
|
|
2427
|
-
break;
|
|
2428
|
-
total += chunk.value.byteLength;
|
|
2429
|
-
if (total > maxBytes) {
|
|
2430
|
-
throw new UnixClientTransportError("UNIX_REQUEST_TOO_LARGE", `Unix request body exceeds the ${maxBytes} byte limit`, "not-dispatched");
|
|
2431
|
-
}
|
|
2432
|
-
chunks.push(chunk.value);
|
|
2433
|
-
}
|
|
2434
|
-
} finally {
|
|
2435
|
-
reader.releaseLock();
|
|
2436
|
-
}
|
|
2437
|
-
const result = new Uint8Array(total);
|
|
2438
|
-
let offset = 0;
|
|
2439
|
-
for (const chunk of chunks) {
|
|
2440
|
-
result.set(chunk, offset);
|
|
2441
|
-
offset += chunk.byteLength;
|
|
2442
|
-
}
|
|
2443
|
-
return result;
|
|
2444
|
-
}
|
|
2445
|
-
function incomingResponseBody(response, maxBytes, onDone) {
|
|
2446
|
-
let finished = false;
|
|
2447
|
-
let received = 0;
|
|
2448
|
-
const finish = () => {
|
|
2449
|
-
if (finished)
|
|
2450
|
-
return;
|
|
2451
|
-
finished = true;
|
|
2452
|
-
onDone();
|
|
2453
|
-
};
|
|
2454
|
-
return new ReadableStream({
|
|
2455
|
-
start(controller) {
|
|
2456
|
-
response.pause();
|
|
2457
|
-
response.on("data", (chunk) => {
|
|
2458
|
-
if (finished)
|
|
2459
|
-
return;
|
|
2460
|
-
if (maxBytes !== undefined)
|
|
2461
|
-
received += chunk.byteLength;
|
|
2462
|
-
if (maxBytes !== undefined && received > maxBytes) {
|
|
2463
|
-
controller.error(new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${maxBytes} byte limit`, "response-received"));
|
|
2464
|
-
response.destroy();
|
|
2465
|
-
finish();
|
|
2466
|
-
return;
|
|
2467
|
-
}
|
|
2468
|
-
controller.enqueue(new Uint8Array(chunk));
|
|
2469
|
-
response.pause();
|
|
2470
|
-
});
|
|
2471
|
-
response.once("end", () => {
|
|
2472
|
-
if (finished)
|
|
2473
|
-
return;
|
|
2474
|
-
controller.close();
|
|
2475
|
-
finish();
|
|
2476
|
-
});
|
|
2477
|
-
response.once("aborted", () => {
|
|
2478
|
-
if (finished)
|
|
2479
|
-
return;
|
|
2480
|
-
controller.error(new UnixClientTransportError("UNIX_RESPONSE_ABORTED", "Unix response was aborted before completion", "response-received"));
|
|
2481
|
-
finish();
|
|
2482
|
-
});
|
|
2483
|
-
response.once("error", (error) => {
|
|
2484
|
-
if (finished)
|
|
2485
|
-
return;
|
|
2486
|
-
controller.error(new UnixClientTransportError("UNIX_RESPONSE_ABORTED", "Unix response failed before completion", "response-received", { cause: error }));
|
|
2487
|
-
finish();
|
|
2488
|
-
});
|
|
2489
|
-
},
|
|
2490
|
-
pull() {
|
|
2491
|
-
response.resume();
|
|
2492
|
-
},
|
|
2493
|
-
cancel() {
|
|
2494
|
-
response.destroy();
|
|
2495
|
-
finish();
|
|
2496
|
-
}
|
|
2497
|
-
}, { highWaterMark: 1 });
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
// src/server/unix-client-bun.ts
|
|
2501
|
-
var MAX_BODY_CHUNK_BYTES = 64 * 1024;
|
|
2502
|
-
function isBunUnixRuntime(candidate) {
|
|
2503
|
-
if (typeof candidate !== "object" || candidate === null)
|
|
2504
|
-
return false;
|
|
2505
|
-
return typeof Reflect.get(candidate, "connect") === "function";
|
|
2506
|
-
}
|
|
2507
|
-
function runtimeBun() {
|
|
2508
|
-
const candidate = Reflect.get(globalThis, "Bun");
|
|
2509
|
-
return isBunUnixRuntime(candidate) ? candidate : undefined;
|
|
2510
|
-
}
|
|
2511
|
-
function hasBunUnixRuntime() {
|
|
2512
|
-
return runtimeBun() !== undefined;
|
|
2513
|
-
}
|
|
2514
|
-
function concatBytes2(left, right) {
|
|
2515
|
-
if (left.byteLength === 0)
|
|
2516
|
-
return right.slice();
|
|
2517
|
-
const combined = new Uint8Array(left.byteLength + right.byteLength);
|
|
2518
|
-
combined.set(left);
|
|
2519
|
-
combined.set(right, left.byteLength);
|
|
2520
|
-
return combined;
|
|
2521
|
-
}
|
|
2522
|
-
function indexOfSequence(bytes, sequence) {
|
|
2523
|
-
outer:
|
|
2524
|
-
for (let index = 0;index <= bytes.byteLength - sequence.length; index += 1) {
|
|
2525
|
-
for (let offset = 0;offset < sequence.length; offset += 1) {
|
|
2526
|
-
if (bytes[index + offset] !== sequence[offset])
|
|
2527
|
-
continue outer;
|
|
2528
|
-
}
|
|
2529
|
-
return index;
|
|
2530
|
-
}
|
|
2531
|
-
return -1;
|
|
2532
|
-
}
|
|
2533
|
-
function requestBytes(request, body) {
|
|
2534
|
-
const url = new URL(request.url);
|
|
2535
|
-
const headers = new Headers(request.headers);
|
|
2536
|
-
headers.set("host", url.host);
|
|
2537
|
-
headers.set("connection", "close");
|
|
2538
|
-
headers.set("accept-encoding", "identity");
|
|
2539
|
-
headers.delete("transfer-encoding");
|
|
2540
|
-
headers.delete("content-length");
|
|
2541
|
-
if (body !== undefined)
|
|
2542
|
-
headers.set("content-length", String(body.byteLength));
|
|
2543
|
-
const lines = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`];
|
|
2544
|
-
for (const [name, value] of headers)
|
|
2545
|
-
lines.push(`${name}: ${value}`);
|
|
2546
|
-
const head = new TextEncoder().encode(`${lines.join(`\r
|
|
2547
|
-
`)}\r
|
|
2548
|
-
\r
|
|
2549
|
-
`);
|
|
2550
|
-
return body === undefined ? head : concatBytes2(head, body);
|
|
2551
|
-
}
|
|
2552
|
-
function parseContentLength(headers) {
|
|
2553
|
-
const raw = headers.get("content-length");
|
|
2554
|
-
if (raw === null)
|
|
2555
|
-
return;
|
|
2556
|
-
if (!/^\d+$/.test(raw))
|
|
2557
|
-
throw new Error("Unix response has an invalid Content-Length");
|
|
2558
|
-
const value = Number(raw);
|
|
2559
|
-
if (!Number.isSafeInteger(value)) {
|
|
2560
|
-
throw new Error("Unix response Content-Length is not a safe integer");
|
|
2561
|
-
}
|
|
2562
|
-
return value;
|
|
2563
|
-
}
|
|
2564
|
-
function bunUnixRequest(options) {
|
|
2565
|
-
const outgoing = requestBytes(options.request, options.body);
|
|
2566
|
-
const bun = runtimeBun();
|
|
2567
|
-
if (!bun)
|
|
2568
|
-
throw new Error("Bun Unix runtime is unavailable");
|
|
2569
|
-
let socket;
|
|
2570
|
-
let controller;
|
|
2571
|
-
let wire = new Uint8Array;
|
|
2572
|
-
let responseResolved = false;
|
|
2573
|
-
let headersReceived = false;
|
|
2574
|
-
let settled = false;
|
|
2575
|
-
let bodyComplete = false;
|
|
2576
|
-
let peerEnded = false;
|
|
2577
|
-
let chunked = false;
|
|
2578
|
-
let chunkRemaining;
|
|
2579
|
-
let expectChunkCrlf = false;
|
|
2580
|
-
let trailers = false;
|
|
2581
|
-
let remainingLength;
|
|
2582
|
-
let received = 0;
|
|
2583
|
-
let writeOffset = 0;
|
|
2584
|
-
let resolveResponse = () => {
|
|
2585
|
-
return;
|
|
2586
|
-
};
|
|
2587
|
-
let rejectResponse = () => {
|
|
2588
|
-
return;
|
|
2589
|
-
};
|
|
2590
|
-
const response = new Promise((resolve, reject) => {
|
|
2591
|
-
resolveResponse = resolve;
|
|
2592
|
-
rejectResponse = reject;
|
|
2593
|
-
});
|
|
2594
|
-
const transportFailure = (error) => {
|
|
2595
|
-
if (error instanceof UnixClientTransportError)
|
|
2596
|
-
return error;
|
|
2597
|
-
if (headersReceived) {
|
|
2598
|
-
return new UnixClientTransportError("UNIX_RESPONSE_ABORTED", "Unix response failed before completion", "response-received", { cause: error });
|
|
2599
|
-
}
|
|
2600
|
-
const dispatched = writeOffset > 0;
|
|
2601
|
-
return new UnixClientTransportError(dispatched ? "UNIX_DELIVERY_UNCERTAIN" : "UNIX_CONNECT_FAILED", dispatched ? "Unix request transport failed after dispatch may have begun" : "Unix socket connection failed before request dispatch", dispatched ? "possibly-dispatched" : "not-dispatched", { cause: error });
|
|
2602
|
-
};
|
|
2603
|
-
const clearRegistration = options.registerAbort(() => {
|
|
2604
|
-
fail(new UnixClientTransportError("UNIX_CLIENT_CLOSED", "Unix client transport is closed"));
|
|
2605
|
-
});
|
|
2606
|
-
const timer = setTimeout(() => {
|
|
2607
|
-
fail(new UnixClientTransportError("UNIX_HEADERS_TIMEOUT", `Unix response headers did not arrive within ${options.headersTimeoutMs}ms`));
|
|
2608
|
-
}, options.headersTimeoutMs);
|
|
2609
|
-
timer.unref();
|
|
2610
|
-
const removeRequestAbort = () => {
|
|
2611
|
-
options.request.signal.removeEventListener("abort", abortRequest);
|
|
2612
|
-
};
|
|
2613
|
-
const finish = (terminate = true) => {
|
|
2614
|
-
if (settled)
|
|
2615
|
-
return;
|
|
2616
|
-
settled = true;
|
|
2617
|
-
clearTimeout(timer);
|
|
2618
|
-
clearRegistration();
|
|
2619
|
-
removeRequestAbort();
|
|
2620
|
-
if (terminate)
|
|
2621
|
-
socket?.terminate();
|
|
2622
|
-
};
|
|
2623
|
-
function fail(error) {
|
|
2624
|
-
if (settled)
|
|
2625
|
-
return;
|
|
2626
|
-
if (responseResolved)
|
|
2627
|
-
controller?.error(error);
|
|
2628
|
-
else
|
|
2629
|
-
rejectResponse(error);
|
|
2630
|
-
finish();
|
|
2631
|
-
}
|
|
2632
|
-
function abortRequest() {
|
|
2633
|
-
fail(options.request.signal.reason ?? new DOMException("Request aborted", "AbortError"));
|
|
2634
|
-
}
|
|
2635
|
-
const takeWire = (bytes) => {
|
|
2636
|
-
const value = wire.slice(0, bytes);
|
|
2637
|
-
wire = wire.slice(bytes);
|
|
2638
|
-
return value;
|
|
2639
|
-
};
|
|
2640
|
-
const enqueue = (value) => {
|
|
2641
|
-
if (options.maxResponseBytes !== undefined)
|
|
2642
|
-
received += value.byteLength;
|
|
2643
|
-
if (options.maxResponseBytes !== undefined && received > options.maxResponseBytes) {
|
|
2644
|
-
fail(new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${options.maxResponseBytes} byte limit`, "response-received"));
|
|
2645
|
-
return;
|
|
2646
|
-
}
|
|
2647
|
-
controller?.enqueue(value);
|
|
2648
|
-
};
|
|
2649
|
-
const completeBody = () => {
|
|
2650
|
-
if (bodyComplete || settled)
|
|
2651
|
-
return;
|
|
2652
|
-
bodyComplete = true;
|
|
2653
|
-
controller?.close();
|
|
2654
|
-
finish();
|
|
2655
|
-
};
|
|
2656
|
-
const resumeForData = () => {
|
|
2657
|
-
if (settled || bodyComplete || peerEnded)
|
|
2658
|
-
return;
|
|
2659
|
-
socket?.resume();
|
|
2660
|
-
};
|
|
2661
|
-
const pumpChunked = () => {
|
|
2662
|
-
if (!controller || settled || bodyComplete)
|
|
2663
|
-
return;
|
|
2664
|
-
for (;; ) {
|
|
2665
|
-
if ((controller.desiredSize ?? 1) <= 0)
|
|
2666
|
-
return;
|
|
2667
|
-
if (trailers) {
|
|
2668
|
-
if (wire.byteLength >= 2 && wire[0] === 13 && wire[1] === 10) {
|
|
2669
|
-
takeWire(2);
|
|
2670
|
-
completeBody();
|
|
2671
|
-
return;
|
|
2672
|
-
}
|
|
2673
|
-
const trailerEnd = indexOfSequence(wire, [13, 10, 13, 10]);
|
|
2674
|
-
if (trailerEnd < 0) {
|
|
2675
|
-
if (wire.byteLength > options.maxHeaderBytes) {
|
|
2676
|
-
fail(new UnixClientTransportError("UNIX_HEADERS_TOO_LARGE", `Unix response trailers exceed the ${options.maxHeaderBytes} byte limit`, "response-received"));
|
|
2677
|
-
} else if (peerEnded) {
|
|
2678
|
-
fail(transportFailure(new Error("Unix chunked response ended inside trailers")));
|
|
2679
|
-
} else {
|
|
2680
|
-
resumeForData();
|
|
2681
|
-
}
|
|
2682
|
-
return;
|
|
2683
|
-
}
|
|
2684
|
-
takeWire(trailerEnd + 4);
|
|
2685
|
-
completeBody();
|
|
2686
|
-
return;
|
|
2687
|
-
}
|
|
2688
|
-
if (expectChunkCrlf) {
|
|
2689
|
-
if (wire.byteLength < 2) {
|
|
2690
|
-
if (peerEnded) {
|
|
2691
|
-
fail(transportFailure(new Error("Unix chunked response ended before chunk delimiter")));
|
|
2692
|
-
} else
|
|
2693
|
-
resumeForData();
|
|
2694
|
-
return;
|
|
2695
|
-
}
|
|
2696
|
-
if (wire[0] !== 13 || wire[1] !== 10) {
|
|
2697
|
-
fail(transportFailure(new Error("Unix chunked response has an invalid chunk delimiter")));
|
|
2698
|
-
return;
|
|
2699
|
-
}
|
|
2700
|
-
takeWire(2);
|
|
2701
|
-
expectChunkCrlf = false;
|
|
2702
|
-
}
|
|
2703
|
-
if (chunkRemaining === undefined) {
|
|
2704
|
-
const lineEnd = indexOfSequence(wire, [13, 10]);
|
|
2705
|
-
if (lineEnd < 0) {
|
|
2706
|
-
if (wire.byteLength > 1024) {
|
|
2707
|
-
fail(transportFailure(new Error("Unix chunk header is too large")));
|
|
2708
|
-
} else if (peerEnded) {
|
|
2709
|
-
fail(transportFailure(new Error("Unix chunked response ended inside chunk header")));
|
|
2710
|
-
} else
|
|
2711
|
-
resumeForData();
|
|
2712
|
-
return;
|
|
2713
|
-
}
|
|
2714
|
-
const line = new TextDecoder("ascii", { fatal: true }).decode(takeWire(lineEnd));
|
|
2715
|
-
takeWire(2);
|
|
2716
|
-
const sizeText = line.split(";", 1)[0]?.trim() ?? "";
|
|
2717
|
-
if (!/^[0-9a-f]+$/i.test(sizeText)) {
|
|
2718
|
-
fail(transportFailure(new Error("Unix chunked response has an invalid chunk size")));
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
chunkRemaining = Number.parseInt(sizeText, 16);
|
|
2722
|
-
if (!Number.isSafeInteger(chunkRemaining)) {
|
|
2723
|
-
fail(transportFailure(new Error("Unix chunk size is not a safe integer")));
|
|
2724
|
-
return;
|
|
2725
|
-
}
|
|
2726
|
-
if (chunkRemaining === 0) {
|
|
2727
|
-
chunkRemaining = undefined;
|
|
2728
|
-
trailers = true;
|
|
2729
|
-
continue;
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
if (wire.byteLength === 0) {
|
|
2733
|
-
if (peerEnded) {
|
|
2734
|
-
fail(transportFailure(new Error("Unix chunked response ended inside a chunk")));
|
|
2735
|
-
} else
|
|
2736
|
-
resumeForData();
|
|
2737
|
-
return;
|
|
2738
|
-
}
|
|
2739
|
-
const bytes = Math.min(chunkRemaining, wire.byteLength, MAX_BODY_CHUNK_BYTES);
|
|
2740
|
-
const value = takeWire(bytes);
|
|
2741
|
-
chunkRemaining -= bytes;
|
|
2742
|
-
if (chunkRemaining === 0) {
|
|
2743
|
-
chunkRemaining = undefined;
|
|
2744
|
-
expectChunkCrlf = true;
|
|
2745
|
-
}
|
|
2746
|
-
enqueue(value);
|
|
2747
|
-
return;
|
|
2748
|
-
}
|
|
2749
|
-
};
|
|
2750
|
-
const pumpPlain = () => {
|
|
2751
|
-
if (!controller || settled || bodyComplete)
|
|
2752
|
-
return;
|
|
2753
|
-
if ((controller.desiredSize ?? 1) <= 0)
|
|
2754
|
-
return;
|
|
2755
|
-
if (wire.byteLength > 0) {
|
|
2756
|
-
const permitted = remainingLength ?? wire.byteLength;
|
|
2757
|
-
const bytes = Math.min(permitted, wire.byteLength, MAX_BODY_CHUNK_BYTES);
|
|
2758
|
-
enqueue(takeWire(bytes));
|
|
2759
|
-
if (settled)
|
|
2760
|
-
return;
|
|
2761
|
-
if (remainingLength !== undefined) {
|
|
2762
|
-
remainingLength -= bytes;
|
|
2763
|
-
if (remainingLength === 0) {
|
|
2764
|
-
completeBody();
|
|
2765
|
-
return;
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
return;
|
|
2769
|
-
}
|
|
2770
|
-
if (peerEnded) {
|
|
2771
|
-
if (remainingLength !== undefined && remainingLength > 0) {
|
|
2772
|
-
fail(transportFailure(new Error("Unix response ended before Content-Length bytes arrived")));
|
|
2773
|
-
} else {
|
|
2774
|
-
completeBody();
|
|
2775
|
-
}
|
|
2776
|
-
return;
|
|
2777
|
-
}
|
|
2778
|
-
resumeForData();
|
|
2779
|
-
};
|
|
2780
|
-
const pump = () => {
|
|
2781
|
-
if (chunked)
|
|
2782
|
-
pumpChunked();
|
|
2783
|
-
else
|
|
2784
|
-
pumpPlain();
|
|
2785
|
-
};
|
|
2786
|
-
const parseHeaders = () => {
|
|
2787
|
-
const headerEnd = indexOfSequence(wire, [13, 10, 13, 10]);
|
|
2788
|
-
if (headerEnd < 0) {
|
|
2789
|
-
if (wire.byteLength > options.maxHeaderBytes) {
|
|
2790
|
-
fail(new UnixClientTransportError("UNIX_HEADERS_TOO_LARGE", `Unix response headers exceed the ${options.maxHeaderBytes} byte limit`, "response-received"));
|
|
2791
|
-
} else {
|
|
2792
|
-
socket?.resume();
|
|
2793
|
-
}
|
|
2794
|
-
return;
|
|
2795
|
-
}
|
|
2796
|
-
if (headerEnd + 4 > options.maxHeaderBytes) {
|
|
2797
|
-
fail(new UnixClientTransportError("UNIX_HEADERS_TOO_LARGE", `Unix response headers exceed the ${options.maxHeaderBytes} byte limit`, "response-received"));
|
|
2798
|
-
return;
|
|
2799
|
-
}
|
|
2800
|
-
headersReceived = true;
|
|
2801
|
-
let text;
|
|
2802
|
-
try {
|
|
2803
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(takeWire(headerEnd));
|
|
2804
|
-
} catch (error) {
|
|
2805
|
-
fail(transportFailure(error));
|
|
2806
|
-
return;
|
|
2807
|
-
}
|
|
2808
|
-
takeWire(4);
|
|
2809
|
-
const lines = text.split(`\r
|
|
2810
|
-
`);
|
|
2811
|
-
const statusLine = lines.shift() ?? "";
|
|
2812
|
-
const matched = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
|
|
2813
|
-
if (!matched) {
|
|
2814
|
-
fail(transportFailure(new Error("Unix response has an invalid HTTP status line")));
|
|
2815
|
-
return;
|
|
2816
|
-
}
|
|
2817
|
-
const status = Number(matched[1]);
|
|
2818
|
-
const statusText = matched[2] ?? "";
|
|
2819
|
-
const headers = new Headers;
|
|
2820
|
-
try {
|
|
2821
|
-
for (const line of lines) {
|
|
2822
|
-
const separator = line.indexOf(":");
|
|
2823
|
-
if (separator <= 0)
|
|
2824
|
-
throw new Error("Unix response has an invalid header line");
|
|
2825
|
-
headers.append(line.slice(0, separator).trim(), line.slice(separator + 1).trim());
|
|
2826
|
-
}
|
|
2827
|
-
remainingLength = parseContentLength(headers);
|
|
2828
|
-
} catch (error) {
|
|
2829
|
-
fail(transportFailure(error));
|
|
2830
|
-
return;
|
|
2831
|
-
}
|
|
2832
|
-
if (options.maxResponseBytes !== undefined && remainingLength !== undefined && remainingLength > options.maxResponseBytes) {
|
|
2833
|
-
fail(new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${options.maxResponseBytes} byte limit`, "response-received"));
|
|
2834
|
-
return;
|
|
2835
|
-
}
|
|
2836
|
-
chunked = headers.get("transfer-encoding")?.toLowerCase().includes("chunked") ?? false;
|
|
2837
|
-
const bodyless = options.request.method === "HEAD" || status === 204 || status === 205 || status === 304;
|
|
2838
|
-
clearTimeout(timer);
|
|
2839
|
-
responseResolved = true;
|
|
2840
|
-
if (bodyless || remainingLength === 0) {
|
|
2841
|
-
resolveResponse(new Response(null, { status, statusText, headers }));
|
|
2842
|
-
finish();
|
|
2843
|
-
return;
|
|
2844
|
-
}
|
|
2845
|
-
const body = new ReadableStream({
|
|
2846
|
-
start(value) {
|
|
2847
|
-
controller = value;
|
|
2848
|
-
pump();
|
|
2849
|
-
},
|
|
2850
|
-
pull() {
|
|
2851
|
-
pump();
|
|
2852
|
-
},
|
|
2853
|
-
cancel() {
|
|
2854
|
-
finish();
|
|
2855
|
-
}
|
|
2856
|
-
}, { highWaterMark: 1 });
|
|
2857
|
-
resolveResponse(new Response(body, { status, statusText, headers }));
|
|
2858
|
-
};
|
|
2859
|
-
const flushRequest = (active) => {
|
|
2860
|
-
while (writeOffset < outgoing.byteLength) {
|
|
2861
|
-
const written = active.write(outgoing, writeOffset, outgoing.byteLength - writeOffset);
|
|
2862
|
-
if (written < 0) {
|
|
2863
|
-
fail(transportFailure(new Error("Unix request socket closed while writing")));
|
|
2864
|
-
return;
|
|
2865
|
-
}
|
|
2866
|
-
if (written === 0)
|
|
2867
|
-
return;
|
|
2868
|
-
writeOffset += written;
|
|
2869
|
-
}
|
|
2870
|
-
};
|
|
2871
|
-
if (options.request.signal.aborted) {
|
|
2872
|
-
fail(options.request.signal.reason ?? new DOMException("Request aborted", "AbortError"));
|
|
2873
|
-
return response;
|
|
2874
|
-
}
|
|
2875
|
-
options.request.signal.addEventListener("abort", abortRequest, { once: true });
|
|
2876
|
-
bun.connect({
|
|
2877
|
-
unix: options.socketPath,
|
|
2878
|
-
socket: {
|
|
2879
|
-
binaryType: "uint8array",
|
|
2880
|
-
open(active) {
|
|
2881
|
-
socket = active;
|
|
2882
|
-
if (settled) {
|
|
2883
|
-
active.terminate();
|
|
2884
|
-
return;
|
|
2885
|
-
}
|
|
2886
|
-
flushRequest(active);
|
|
2887
|
-
},
|
|
2888
|
-
drain(active) {
|
|
2889
|
-
flushRequest(active);
|
|
2890
|
-
},
|
|
2891
|
-
data(active, data) {
|
|
2892
|
-
active.pause();
|
|
2893
|
-
wire = concatBytes2(wire, data);
|
|
2894
|
-
if (!responseResolved)
|
|
2895
|
-
parseHeaders();
|
|
2896
|
-
else
|
|
2897
|
-
pump();
|
|
2898
|
-
},
|
|
2899
|
-
end() {
|
|
2900
|
-
peerEnded = true;
|
|
2901
|
-
if (!responseResolved) {
|
|
2902
|
-
fail(transportFailure(new Error("Unix connection ended before response headers")));
|
|
2903
|
-
} else
|
|
2904
|
-
pump();
|
|
2905
|
-
},
|
|
2906
|
-
close(_active, error) {
|
|
2907
|
-
peerEnded = true;
|
|
2908
|
-
if (settled)
|
|
2909
|
-
return;
|
|
2910
|
-
if (error)
|
|
2911
|
-
fail(transportFailure(error));
|
|
2912
|
-
else if (!responseResolved) {
|
|
2913
|
-
fail(transportFailure(new Error("Unix connection closed before response headers")));
|
|
2914
|
-
} else
|
|
2915
|
-
pump();
|
|
2916
|
-
},
|
|
2917
|
-
error(_active, error) {
|
|
2918
|
-
fail(transportFailure(error));
|
|
2919
|
-
},
|
|
2920
|
-
connectError(_active, error) {
|
|
2921
|
-
fail(transportFailure(error));
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
}).catch((error) => fail(transportFailure(error)));
|
|
2925
|
-
return response;
|
|
2926
|
-
}
|
|
2927
|
-
|
|
2928
|
-
// src/server/unix-client.ts
|
|
2929
|
-
var DEFAULT_MAX_BODY_BYTES = 16 * 1024 * 1024;
|
|
2930
|
-
var DEFAULT_HEADERS_TIMEOUT_MS = 30000;
|
|
2931
|
-
var DEFAULT_MAX_CONNECTIONS = 8;
|
|
2932
|
-
var DEFAULT_MAX_REDIRECTS = 5;
|
|
2933
|
-
var DEFAULT_MAX_HEADER_BYTES2 = 64 * 1024;
|
|
2934
|
-
function positiveInteger(value, fallback, name) {
|
|
2935
|
-
const resolved = value ?? fallback;
|
|
2936
|
-
if (!Number.isSafeInteger(resolved) || resolved <= 0) {
|
|
2937
|
-
throw new TypeError(`${name} must be a positive safe integer`);
|
|
2938
|
-
}
|
|
2939
|
-
return resolved;
|
|
2940
|
-
}
|
|
2941
|
-
function nonNegativeInteger(value, fallback, name) {
|
|
2942
|
-
const resolved = value ?? fallback;
|
|
2943
|
-
if (!Number.isSafeInteger(resolved) || resolved < 0) {
|
|
2944
|
-
throw new TypeError(`${name} must be a non-negative safe integer`);
|
|
2945
|
-
}
|
|
2946
|
-
return resolved;
|
|
2947
|
-
}
|
|
2948
|
-
function isRedirect(status) {
|
|
2949
|
-
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
2950
|
-
}
|
|
2951
|
-
function requestBody(body) {
|
|
2952
|
-
if (body === undefined)
|
|
2953
|
-
return;
|
|
2954
|
-
const copy = new ArrayBuffer(body.byteLength);
|
|
2955
|
-
new Uint8Array(copy).set(body);
|
|
2956
|
-
return copy;
|
|
2957
|
-
}
|
|
2958
|
-
function responseHeaders2(response) {
|
|
2959
|
-
const headers = new Headers;
|
|
2960
|
-
for (let index = 0;index < response.rawHeaders.length; index += 2) {
|
|
2961
|
-
const name = response.rawHeaders[index];
|
|
2962
|
-
const value = response.rawHeaders[index + 1];
|
|
2963
|
-
if (name !== undefined && value !== undefined)
|
|
2964
|
-
headers.append(name, value);
|
|
2965
|
-
}
|
|
2966
|
-
return headers;
|
|
2967
|
-
}
|
|
2968
|
-
function isNodeHeaderOverflow(error) {
|
|
2969
|
-
return typeof error === "object" && error !== null && Reflect.get(error, "code") === "HPE_HEADER_OVERFLOW";
|
|
2970
|
-
}
|
|
2971
|
-
function redirectedRequest(request, location, status, body) {
|
|
2972
|
-
const url = new URL(location, request.url);
|
|
2973
|
-
const changesToGet = status === 303 || (status === 301 || status === 302) && request.method === "POST";
|
|
2974
|
-
const headers = new Headers(request.headers);
|
|
2975
|
-
if (changesToGet) {
|
|
2976
|
-
headers.delete("content-length");
|
|
2977
|
-
headers.delete("content-type");
|
|
2978
|
-
}
|
|
2979
|
-
return new Request(url, {
|
|
2980
|
-
method: changesToGet ? "GET" : request.method,
|
|
2981
|
-
headers,
|
|
2982
|
-
...!changesToGet && body !== undefined && { body: requestBody(body) },
|
|
2983
|
-
redirect: request.redirect,
|
|
2984
|
-
signal: request.signal
|
|
2985
|
-
});
|
|
2986
|
-
}
|
|
2987
|
-
function createUnixClientTransport(config) {
|
|
2988
|
-
if (!config.socketPath.startsWith("/") || config.socketPath.includes("\x00")) {
|
|
2989
|
-
throw new TypeError("socketPath must be an absolute Unix socket path");
|
|
2990
|
-
}
|
|
2991
|
-
const maxRequestBytes = positiveInteger(config.maxRequestBytes, DEFAULT_MAX_BODY_BYTES, "maxRequestBytes");
|
|
2992
|
-
if (config.responseBodyMode === "streaming" && config.maxResponseBytes !== undefined) {
|
|
2993
|
-
throw new TypeError("maxResponseBytes cannot be combined with responseBodyMode streaming");
|
|
2994
|
-
}
|
|
2995
|
-
const maxResponseBytes = config.responseBodyMode === "streaming" ? undefined : positiveInteger(config.maxResponseBytes, DEFAULT_MAX_BODY_BYTES, "maxResponseBytes");
|
|
2996
|
-
const headersTimeoutMs = positiveInteger(config.headersTimeoutMs, DEFAULT_HEADERS_TIMEOUT_MS, "headersTimeoutMs");
|
|
2997
|
-
const maxConnections = positiveInteger(config.maxConnections, DEFAULT_MAX_CONNECTIONS, "maxConnections");
|
|
2998
|
-
const maxHeaderBytes = positiveInteger(config.maxHeaderBytes, DEFAULT_MAX_HEADER_BYTES2, "maxHeaderBytes");
|
|
2999
|
-
const maxRedirects = nonNegativeInteger(config.maxRedirects, DEFAULT_MAX_REDIRECTS, "maxRedirects");
|
|
3000
|
-
const agent = new Agent({ keepAlive: true, maxSockets: maxConnections });
|
|
3001
|
-
const activeRequests = new Set;
|
|
3002
|
-
const activeResponses = new Set;
|
|
3003
|
-
const bunAborters = new Set;
|
|
3004
|
-
let closed = false;
|
|
3005
|
-
const dispatch = async (request, redirectCount) => {
|
|
3006
|
-
if (closed) {
|
|
3007
|
-
throw new UnixClientTransportError("UNIX_CLIENT_CLOSED", "Unix client transport is closed", "not-dispatched");
|
|
3008
|
-
}
|
|
3009
|
-
if (request.signal.aborted)
|
|
3010
|
-
throw request.signal.reason;
|
|
3011
|
-
const url = new URL(request.url);
|
|
3012
|
-
if (url.protocol !== "http:") {
|
|
3013
|
-
throw new TypeError("Unix client transport accepts only http: request URLs");
|
|
3014
|
-
}
|
|
3015
|
-
const body = await readBoundedRequestBody(request, maxRequestBytes);
|
|
3016
|
-
if (hasBunUnixRuntime()) {
|
|
3017
|
-
if (bunAborters.size >= maxConnections) {
|
|
3018
|
-
throw new UnixClientTransportError("UNIX_CONNECTION_LIMIT", `Unix client connection limit ${maxConnections} reached`, "not-dispatched");
|
|
3019
|
-
}
|
|
3020
|
-
const response = await bunUnixRequest({
|
|
3021
|
-
socketPath: config.socketPath,
|
|
3022
|
-
request,
|
|
3023
|
-
body,
|
|
3024
|
-
maxResponseBytes,
|
|
3025
|
-
maxHeaderBytes,
|
|
3026
|
-
headersTimeoutMs,
|
|
3027
|
-
registerAbort(abort) {
|
|
3028
|
-
bunAborters.add(abort);
|
|
3029
|
-
return () => void bunAborters.delete(abort);
|
|
3030
|
-
}
|
|
3031
|
-
});
|
|
3032
|
-
const location2 = response.headers.get("location");
|
|
3033
|
-
if (location2 && isRedirect(response.status)) {
|
|
3034
|
-
if (request.redirect === "error" || redirectCount >= maxRedirects) {
|
|
3035
|
-
await response.body?.cancel();
|
|
3036
|
-
throw new UnixClientTransportError("UNIX_REDIRECT_REFUSED", redirectCount >= maxRedirects ? `Unix redirect limit ${maxRedirects} exceeded` : "Unix request redirect mode is error", "response-received");
|
|
3037
|
-
}
|
|
3038
|
-
if (request.redirect === "follow") {
|
|
3039
|
-
await response.body?.cancel();
|
|
3040
|
-
return dispatch(redirectedRequest(request, location2, response.status, body), redirectCount + 1);
|
|
3041
|
-
}
|
|
3042
|
-
}
|
|
3043
|
-
return response;
|
|
3044
|
-
}
|
|
3045
|
-
const headers = Object.fromEntries(request.headers.entries());
|
|
3046
|
-
if (!Object.keys(headers).some((name) => name.toLowerCase() === "host")) {
|
|
3047
|
-
headers.host = url.host;
|
|
3048
|
-
}
|
|
3049
|
-
const incoming = await new Promise((resolve, reject) => {
|
|
3050
|
-
let settled = false;
|
|
3051
|
-
const resolveOnce = (response) => {
|
|
3052
|
-
if (settled)
|
|
3053
|
-
return;
|
|
3054
|
-
settled = true;
|
|
3055
|
-
resolve(response);
|
|
3056
|
-
};
|
|
3057
|
-
const rejectOnce = (error) => {
|
|
3058
|
-
if (settled)
|
|
3059
|
-
return;
|
|
3060
|
-
settled = true;
|
|
3061
|
-
reject(error);
|
|
3062
|
-
};
|
|
3063
|
-
const outgoing = httpRequest({
|
|
3064
|
-
socketPath: config.socketPath,
|
|
3065
|
-
path: `${url.pathname}${url.search}`,
|
|
3066
|
-
method: request.method,
|
|
3067
|
-
headers,
|
|
3068
|
-
agent,
|
|
3069
|
-
signal: request.signal,
|
|
3070
|
-
maxHeaderSize: maxHeaderBytes
|
|
3071
|
-
}, resolveOnce);
|
|
3072
|
-
let connected = false;
|
|
3073
|
-
let requestStartBytes = 0;
|
|
3074
|
-
outgoing.once("socket", (socket) => {
|
|
3075
|
-
requestStartBytes = socket.bytesWritten;
|
|
3076
|
-
if (!socket.connecting && !socket.destroyed)
|
|
3077
|
-
connected = true;
|
|
3078
|
-
else {
|
|
3079
|
-
socket.once("connect", () => {
|
|
3080
|
-
connected = true;
|
|
3081
|
-
});
|
|
3082
|
-
}
|
|
3083
|
-
});
|
|
3084
|
-
activeRequests.add(outgoing);
|
|
3085
|
-
const timer = setTimeout(() => {
|
|
3086
|
-
const error = new UnixClientTransportError("UNIX_HEADERS_TIMEOUT", `Unix response headers did not arrive within ${headersTimeoutMs}ms`);
|
|
3087
|
-
rejectOnce(error);
|
|
3088
|
-
outgoing.destroy(error);
|
|
3089
|
-
}, headersTimeoutMs);
|
|
3090
|
-
timer.unref();
|
|
3091
|
-
outgoing.once("response", () => clearTimeout(timer));
|
|
3092
|
-
outgoing.once("error", (error) => {
|
|
3093
|
-
if (isNodeHeaderOverflow(error)) {
|
|
3094
|
-
rejectOnce(new UnixClientTransportError("UNIX_HEADERS_TOO_LARGE", `Unix response headers exceed the ${maxHeaderBytes} byte limit`, "response-received", { cause: error }));
|
|
3095
|
-
return;
|
|
3096
|
-
}
|
|
3097
|
-
const dispatched = connected && (outgoing.socket?.bytesWritten ?? requestStartBytes) > requestStartBytes;
|
|
3098
|
-
rejectOnce(new UnixClientTransportError(dispatched ? "UNIX_DELIVERY_UNCERTAIN" : "UNIX_CONNECT_FAILED", dispatched ? "Unix request transport failed after dispatch may have begun" : "Unix socket connection failed before request dispatch", dispatched ? "possibly-dispatched" : "not-dispatched", { cause: error }));
|
|
3099
|
-
});
|
|
3100
|
-
outgoing.once("close", () => {
|
|
3101
|
-
clearTimeout(timer);
|
|
3102
|
-
activeRequests.delete(outgoing);
|
|
3103
|
-
});
|
|
3104
|
-
outgoing.end(body);
|
|
3105
|
-
});
|
|
3106
|
-
activeResponses.add(incoming);
|
|
3107
|
-
const releaseResponse = () => void activeResponses.delete(incoming);
|
|
3108
|
-
const status = incoming.statusCode ?? 0;
|
|
3109
|
-
const responseHeadersValue = responseHeaders2(incoming);
|
|
3110
|
-
const declaredResponseBytes = responseHeadersValue.get("content-length");
|
|
3111
|
-
if (declaredResponseBytes !== null && /^\d+$/.test(declaredResponseBytes) && maxResponseBytes !== undefined && Number(declaredResponseBytes) > maxResponseBytes) {
|
|
3112
|
-
incoming.destroy();
|
|
3113
|
-
releaseResponse();
|
|
3114
|
-
throw new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${maxResponseBytes} byte limit`, "response-received");
|
|
3115
|
-
}
|
|
3116
|
-
const location = responseHeadersValue.get("location");
|
|
3117
|
-
if (location && isRedirect(status)) {
|
|
3118
|
-
if (request.redirect === "error" || redirectCount >= maxRedirects) {
|
|
3119
|
-
incoming.destroy();
|
|
3120
|
-
releaseResponse();
|
|
3121
|
-
throw new UnixClientTransportError("UNIX_REDIRECT_REFUSED", redirectCount >= maxRedirects ? `Unix redirect limit ${maxRedirects} exceeded` : "Unix request redirect mode is error", "response-received");
|
|
3122
|
-
}
|
|
3123
|
-
if (request.redirect === "follow") {
|
|
3124
|
-
incoming.destroy();
|
|
3125
|
-
releaseResponse();
|
|
3126
|
-
return dispatch(redirectedRequest(request, location, status, body), redirectCount + 1);
|
|
3127
|
-
}
|
|
3128
|
-
}
|
|
3129
|
-
const bodyless = request.method === "HEAD" || status === 204 || status === 205 || status === 304;
|
|
3130
|
-
if (bodyless) {
|
|
3131
|
-
incoming.resume();
|
|
3132
|
-
incoming.once("end", releaseResponse);
|
|
3133
|
-
return new Response(null, {
|
|
3134
|
-
status,
|
|
3135
|
-
statusText: incoming.statusMessage,
|
|
3136
|
-
headers: responseHeadersValue
|
|
3137
|
-
});
|
|
3138
|
-
}
|
|
3139
|
-
return new Response(incomingResponseBody(incoming, maxResponseBytes, releaseResponse), {
|
|
3140
|
-
status,
|
|
3141
|
-
statusText: incoming.statusMessage,
|
|
3142
|
-
headers: responseHeadersValue
|
|
3143
|
-
});
|
|
3144
|
-
};
|
|
3145
|
-
return {
|
|
3146
|
-
fetch(input, init) {
|
|
3147
|
-
return dispatch(new Request(input, init), 0);
|
|
3148
|
-
},
|
|
3149
|
-
get closed() {
|
|
3150
|
-
return closed;
|
|
3151
|
-
},
|
|
3152
|
-
async close() {
|
|
3153
|
-
if (closed)
|
|
3154
|
-
return;
|
|
3155
|
-
closed = true;
|
|
3156
|
-
for (const request of activeRequests)
|
|
3157
|
-
request.destroy();
|
|
3158
|
-
for (const response of activeResponses)
|
|
3159
|
-
response.destroy();
|
|
3160
|
-
for (const abort of [...bunAborters])
|
|
3161
|
-
abort();
|
|
3162
|
-
activeRequests.clear();
|
|
3163
|
-
activeResponses.clear();
|
|
3164
|
-
bunAborters.clear();
|
|
3165
|
-
agent.destroy();
|
|
3166
|
-
}
|
|
3167
|
-
};
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
|
-
export { parseMultipart, DEFAULT_STREAM_HEARTBEAT_MS, streamingRoute, ndjsonRoute, sseRoute, createHandler, bindProcessSignals, bindRealtimeServer, webSocketLane, composeWebSocketHandlers, createSocketIOServer, socketIoLane, UnixClientTransportError, createUnixClientTransport };
|