stitchkit 0.94.0 → 0.95.0
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 +104 -0
- package/dist/agent-runtime/coding-shell-process.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 +3 -2
- package/dist/agent-runtime/deferred-tool-types.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/provider-origin.d.ts +7 -0
- package/dist/agent-runtime/provider-origin.d.ts.map +1 -1
- package/dist/agent-runtime/run-operation-lifecycle.d.ts.map +1 -1
- package/dist/agent-runtime/runtime.d.ts +1 -1
- 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/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.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.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/kernel-resource-context.d.ts.map +1 -1
- package/dist/application/notification-outbox.d.ts.map +1 -1
- package/dist/application/process-lifecycle-transitions.d.ts +175 -0
- package/dist/application/process-lifecycle-transitions.d.ts.map +1 -0
- package/dist/application/process-lifecycle.d.ts +3 -131
- package/dist/application/process-lifecycle.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 +2 -2
- 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.d.ts +3 -2
- package/dist/browser/client.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 +1 -3
- 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/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 +2 -18
- package/dist/browser/socket-io.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 +3 -357
- package/dist/contract/define.d.ts.map +1 -1
- 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 +15 -2
- package/dist/contract/tool-view.d.ts.map +1 -1
- package/dist/durability/contract.d.ts +83 -0
- package/dist/durability/contract.d.ts.map +1 -1
- package/dist/durability/effect.d.ts +24 -0
- package/dist/durability/effect.d.ts.map +1 -0
- package/dist/durability/engine.d.ts +2 -2
- package/dist/durability/engine.d.ts.map +1 -1
- package/dist/durability/events.d.ts +2 -0
- package/dist/durability/events.d.ts.map +1 -1
- package/dist/durability/ledger.d.ts +11 -0
- package/dist/durability/ledger.d.ts.map +1 -1
- package/dist/durability/parks.d.ts +22 -0
- package/dist/durability/parks.d.ts.map +1 -0
- package/dist/durability/scheduler.d.ts +1 -0
- package/dist/durability/scheduler.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/coding-tools.js +4 -4
- package/dist/entrypoints/agent-runtime/harness.js +127 -116
- package/dist/entrypoints/agent-runtime/sandbox.js +4 -4
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +3 -3
- package/dist/entrypoints/agent-runtime/sqlite/node.js +3 -3
- package/dist/entrypoints/agent-runtime/testing.js +246 -214
- package/dist/entrypoints/agent-runtime.js +224 -203
- package/dist/entrypoints/application/diagnostic-journal.js +63 -203
- package/dist/entrypoints/application/opentelemetry.js +1 -1
- package/dist/entrypoints/application.d.ts +2 -1
- package/dist/entrypoints/application.d.ts.map +1 -1
- package/dist/entrypoints/application.js +506 -380
- package/dist/entrypoints/cli.d.ts +4 -1
- package/dist/entrypoints/cli.d.ts.map +1 -1
- package/dist/entrypoints/cli.js +57 -35
- package/dist/entrypoints/contract.d.ts +5 -2
- package/dist/entrypoints/contract.d.ts.map +1 -1
- package/dist/entrypoints/contract.js +3 -2
- package/dist/entrypoints/files.d.ts +2 -0
- package/dist/entrypoints/files.d.ts.map +1 -1
- package/dist/entrypoints/files.js +98 -2
- package/dist/entrypoints/index.d.ts +4 -2
- package/dist/entrypoints/index.d.ts.map +1 -1
- package/dist/entrypoints/index.js +348 -282
- package/dist/entrypoints/live.js +89 -71
- package/dist/entrypoints/node.d.ts +2 -1
- package/dist/entrypoints/node.d.ts.map +1 -1
- package/dist/entrypoints/node.js +13 -12
- package/dist/entrypoints/react.js +1 -1
- package/dist/entrypoints/remote.js +4 -4
- package/dist/entrypoints/server.d.ts +4 -2
- package/dist/entrypoints/server.d.ts.map +1 -1
- package/dist/entrypoints/server.js +293 -79
- package/dist/entrypoints/testing.d.ts +2 -1
- package/dist/entrypoints/testing.d.ts.map +1 -1
- package/dist/entrypoints/testing.js +288 -281
- package/dist/entrypoints/tools/connections.js +2 -2
- package/dist/entrypoints/tools/invoker.js +4 -4
- package/dist/entrypoints/tools.d.ts +6 -3
- package/dist/entrypoints/tools.d.ts.map +1 -1
- package/dist/entrypoints/tools.js +245 -202
- package/dist/entrypoints/tracking.js +325 -274
- 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/index-1j8pm5t0.js +412 -0
- package/dist/{index-kvj7f4hq.js → index-2bxbyab0.js} +10 -2
- package/dist/{index-xnj9bgxe.js → index-2gxzjvyr.js} +2 -162
- package/dist/index-4gcnakhx.js +82 -0
- package/dist/{index-j05z5gn4.js → index-55hkhm67.js} +136 -119
- package/dist/{index-cq4qfycf.js → index-79z7m7dt.js} +227 -59
- package/dist/{index-r7nmqpsg.js → index-7p1f0vp2.js} +1 -1
- package/dist/{index-dcpdvjsc.js → index-863ea9jb.js} +4 -4
- package/dist/index-8p52kd5p.js +242 -0
- package/dist/{index-97ac4gph.js → index-8zja4pkf.js} +26 -4
- package/dist/{index-2qzskm8h.js → index-dce5wxcq.js} +1 -1
- package/dist/index-e01r054k.js +105 -0
- package/dist/{index-sees8yf8.js → index-ensd0r74.js} +118 -111
- package/dist/{index-1nd48tm9.js → index-gxrt5h6v.js} +2 -1
- package/dist/{index-e87kakm4.js → index-ham5sz8m.js} +112 -110
- package/dist/{index-kj2wbeta.js → index-het2he5g.js} +280 -196
- package/dist/{index-vcvkq30w.js → index-hmp4800j.js} +3 -3
- package/dist/{index-gyr48nzg.js → index-k30h66yd.js} +71 -151
- package/dist/{index-2cr1cqhn.js → index-khtgb21r.js} +2 -2
- package/dist/{index-x4799c19.js → index-ndddxtyx.js} +11 -11
- package/dist/{index-dydjcp8z.js → index-np4m8hd3.js} +38 -36
- package/dist/{index-p81267c2.js → index-p3vsv1fb.js} +621 -357
- package/dist/{index-mp5sn43f.js → index-p9e0w8hs.js} +2 -2
- package/dist/{index-rewkc2hj.js → index-p9sh0xx4.js} +3 -3
- package/dist/{index-70yj2ebz.js → index-paz6sgdp.js} +107 -103
- package/dist/{index-bn2cjajt.js → index-px2gm2jp.js} +26 -102
- package/dist/index-s812ycat.js +65 -0
- package/dist/{index-xsqjkha9.js → index-sp1tzwnb.js} +2 -2
- package/dist/{index-n6hxg8as.js → index-t9rhjr9f.js} +615 -566
- package/dist/{index-zeemqhqx.js → index-v260df89.js} +88 -80
- package/dist/{index-fdp6hdxn.js → index-w906953s.js} +5 -1
- package/dist/{index-mdzh02yc.js → index-wdkt63w3.js} +1 -1
- package/dist/{index-58k4fp34.js → index-wsm5w4ee.js} +1 -1
- package/dist/{index-v38qyerb.js → index-xw3s75nj.js} +1 -1
- package/dist/internal/atomic-file.d.ts +23 -2
- package/dist/internal/atomic-file.d.ts.map +1 -1
- package/dist/internal/exclusive-lock.d.ts +51 -0
- package/dist/internal/exclusive-lock.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/with-exclusive-lock.d.ts +67 -0
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -0
- package/dist/live/watch-client.d.ts.map +1 -1
- package/dist/observability/audit.d.ts +2 -2
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/event.d.ts +2 -1
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/primitives/export-operation.d.ts +1 -1
- 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/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 +1 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/endpoint-response.d.ts +2 -1
- package/dist/server/endpoint-response.d.ts.map +1 -1
- package/dist/server/error-dispatch.d.ts +1 -1
- package/dist/server/error-dispatch.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +1 -1
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/handler-state.d.ts +1 -1
- package/dist/server/handler-state.d.ts.map +1 -1
- 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/jwt.d.ts +31 -0
- package/dist/server/middleware/jwt.d.ts.map +1 -0
- 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/provider.d.ts +1 -68
- package/dist/server/oauth/provider.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 +4 -1
- 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 +2 -1
- package/dist/testing/handler-clients.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 +0 -31
- 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.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 +2 -65
- package/dist/tools/cli/args.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/tools/cli/command.d.ts +1 -1
- package/dist/tools/cli/command.d.ts.map +1 -1
- package/dist/tools/cli/config.d.ts +1 -1
- package/dist/tools/cli/config.d.ts.map +1 -1
- package/dist/tools/cli/format.d.ts +2 -1
- package/dist/tools/cli/format.d.ts.map +1 -1
- package/dist/tools/cli/invoke.d.ts +2 -1
- package/dist/tools/cli/invoke.d.ts.map +1 -1
- package/dist/tools/cli/managed-surface.d.ts +1 -1
- package/dist/tools/cli/managed-surface.d.ts.map +1 -1
- package/dist/tools/cli/policy.d.ts +1 -1
- package/dist/tools/cli/policy.d.ts.map +1 -1
- package/dist/tools/cli/prepare.d.ts.map +1 -1
- package/dist/tools/cli/run-managed.d.ts.map +1 -1
- package/dist/tools/cli/session.d.ts +1 -1
- package/dist/tools/cli/session.d.ts.map +1 -1
- package/dist/tools/cli/update.d.ts +19 -0
- package/dist/tools/cli/update.d.ts.map +1 -1
- package/dist/tools/cli/view.d.ts +1 -1
- package/dist/tools/cli/view.d.ts.map +1 -1
- package/dist/tools/cli/wait.d.ts +1 -1
- package/dist/tools/cli/wait.d.ts.map +1 -1
- package/dist/tools/connections/runtime.d.ts +1 -1
- package/dist/tools/connections/runtime.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 +4 -193
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +2 -1
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/invoker.d.ts +2 -1
- 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/mcp/mount.d.ts +1 -1
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/prepare.d.ts +2 -1
- package/dist/tools/mcp/prepare.d.ts.map +1 -1
- package/dist/tools/mcp/progress.d.ts +1 -1
- package/dist/tools/mcp/progress.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts +3 -2
- package/dist/tools/mcp/register.d.ts.map +1 -1
- 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 +1 -1
- package/dist/tools/mcp/round-policy.d.ts.map +1 -1
- package/dist/tools/mcp/round.d.ts +3 -11
- package/dist/tools/mcp/round.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts +2 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +3 -1
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/native-definition.d.ts +2 -1
- package/dist/tools/native-definition.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-runtime-tools.d.ts +2 -1
- package/dist/tools/operations/async-operation-runtime-tools.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-runtime-types.d.ts +2 -1
- package/dist/tools/operations/async-operation-runtime-types.d.ts.map +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +3 -1
- package/dist/tools/runtime-tool.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/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/llms/application.2.txt +271 -7
- package/llms/application.txt +66 -289
- package/llms/cli.2.txt +80 -0
- package/llms/cli.txt +45 -74
- package/llms/contract.txt +3 -3
- package/llms/files.txt +3 -0
- package/llms/stitchkit.txt +3 -3
- package/llms/tools.2.txt +88 -279
- package/llms/tools.3.txt +288 -0
- package/llms/tools.txt +27 -65
- package/llms/{upgrading-0.37.0-0.50.0.txt → upgrading-0.37.0-0.53.0.txt} +25 -3
- package/llms/{upgrading-0.53.0-0.67.0.txt → upgrading-0.55.0-0.68.0.txt} +53 -25
- package/llms/{upgrading-0.68.0-0.94.0.txt → upgrading-0.69.0-0.95.0.txt} +45 -53
- package/llms/upgrading.txt +3 -3
- package/llms-full.txt +216 -43
- package/llms.txt +8 -8
- package/package.json +1 -1
- package/dist/index-e2z3ahw9.js +0 -325
- package/dist/index-mkm15s49.js +0 -394
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
createActivityProjection,
|
|
14
14
|
createApplicationSnapshotSink,
|
|
15
15
|
createManagedSchedule
|
|
16
|
-
} from "../index-
|
|
16
|
+
} from "../index-ensd0r74.js";
|
|
17
17
|
import {
|
|
18
18
|
DiagnosticJournalCloseResultSchema,
|
|
19
19
|
DiagnosticJournalFailurePhaseSchema,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
ApplicationShutdownBudgetSchema,
|
|
37
37
|
ApplicationShutdownOptionsSchema,
|
|
38
38
|
createApplication
|
|
39
|
-
} from "../index-
|
|
39
|
+
} from "../index-w906953s.js";
|
|
40
40
|
import {
|
|
41
41
|
ApplicationAdmissionSnapshotSchema,
|
|
42
42
|
ApplicationHealthSchema,
|
|
@@ -69,8 +69,8 @@ import {
|
|
|
69
69
|
deltaWins,
|
|
70
70
|
diff,
|
|
71
71
|
watchKeyString
|
|
72
|
-
} from "../index-
|
|
73
|
-
import"../index-
|
|
72
|
+
} from "../index-wdkt63w3.js";
|
|
73
|
+
import"../index-1j8pm5t0.js";
|
|
74
74
|
import {
|
|
75
75
|
BoundedChannelPolicySchema,
|
|
76
76
|
BoundedChannelReaderError,
|
|
@@ -94,6 +94,85 @@ import"../index-kzfs85xp.js";
|
|
|
94
94
|
import"../index-hffjpmq9.js";
|
|
95
95
|
// src/application/admission.ts
|
|
96
96
|
import { z } from "zod";
|
|
97
|
+
|
|
98
|
+
// src/application/admission-rate.ts
|
|
99
|
+
function pruneRate(samples, intervalMs, at) {
|
|
100
|
+
let expired = 0;
|
|
101
|
+
while (expired < samples.length && at - (samples[expired]?.at ?? at) >= intervalMs) {
|
|
102
|
+
expired += 1;
|
|
103
|
+
}
|
|
104
|
+
if (expired > 0)
|
|
105
|
+
samples.splice(0, expired);
|
|
106
|
+
}
|
|
107
|
+
function pruneExpired(policy, globalRate, keys, at) {
|
|
108
|
+
if (policy.global.rate)
|
|
109
|
+
pruneRate(globalRate, policy.global.rate.intervalMs, at);
|
|
110
|
+
for (const [key, record] of keys) {
|
|
111
|
+
if (record.limits?.rate)
|
|
112
|
+
pruneRate(record.rate, record.limits.rate.intervalMs, at);
|
|
113
|
+
if (record.active === 0 && record.rate.length === 0)
|
|
114
|
+
keys.delete(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function retryAfter(samples, budget, at) {
|
|
118
|
+
return Math.max(1, Math.ceil(budget.intervalMs - (at - (samples[0]?.at ?? at))));
|
|
119
|
+
}
|
|
120
|
+
function removeSample(samples, sample) {
|
|
121
|
+
if (!sample)
|
|
122
|
+
return;
|
|
123
|
+
const index = samples.indexOf(sample);
|
|
124
|
+
if (index >= 0)
|
|
125
|
+
samples.splice(index, 1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/application/admission-run.ts
|
|
129
|
+
async function runAdmitted(acquire, key, work, options) {
|
|
130
|
+
if (options.timeoutMs !== undefined) {
|
|
131
|
+
if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0) {
|
|
132
|
+
throw new TypeError("run timeoutMs must be a non-negative safe integer");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (options.signal?.aborted)
|
|
136
|
+
throw new BoundedOperationWaitError("cancelled");
|
|
137
|
+
const admission = acquire(key);
|
|
138
|
+
if (admission.outcome === "refused") {
|
|
139
|
+
throw new BoundedAdmissionRefusalError(admission.reason, admission.retryAfterMs);
|
|
140
|
+
}
|
|
141
|
+
const workAbort = new AbortController;
|
|
142
|
+
const abortWork = () => workAbort.abort(options.signal?.reason);
|
|
143
|
+
options.signal?.addEventListener("abort", abortWork, { once: true });
|
|
144
|
+
let timer;
|
|
145
|
+
let timedOut = false;
|
|
146
|
+
if (options.timeoutMs !== undefined) {
|
|
147
|
+
timer = setTimeout(() => {
|
|
148
|
+
timedOut = true;
|
|
149
|
+
workAbort.abort(new DOMException("Operation wait timed out", "TimeoutError"));
|
|
150
|
+
}, options.timeoutMs);
|
|
151
|
+
}
|
|
152
|
+
const outcome = Promise.resolve().then(() => work({ signal: workAbort.signal })).then((value) => ({ kind: "value", value }), (error) => ({ kind: "error", error })).finally(() => admission.lease.release());
|
|
153
|
+
const callerDone = new Promise((resolve) => {
|
|
154
|
+
const settle = () => resolve({ kind: "caller" });
|
|
155
|
+
if (workAbort.signal.aborted)
|
|
156
|
+
settle();
|
|
157
|
+
else
|
|
158
|
+
workAbort.signal.addEventListener("abort", settle, { once: true });
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
const settled = await Promise.race([outcome, callerDone]);
|
|
162
|
+
if (settled.kind === "caller") {
|
|
163
|
+
throw new BoundedOperationWaitError(timedOut ? "timed-out" : "cancelled");
|
|
164
|
+
}
|
|
165
|
+
if (settled.kind === "error")
|
|
166
|
+
throw settled.error;
|
|
167
|
+
return settled.value;
|
|
168
|
+
} finally {
|
|
169
|
+
if (timer !== undefined)
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
options.signal?.removeEventListener("abort", abortWork);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/application/admission.ts
|
|
97
176
|
var PositiveSafeIntegerSchema = z.number().int().positive().safe();
|
|
98
177
|
var BoundedRateBudgetSchema = z.object({
|
|
99
178
|
limit: PositiveSafeIntegerSchema,
|
|
@@ -176,22 +255,59 @@ class BoundedOperationWaitError extends Error {
|
|
|
176
255
|
this.name = "BoundedOperationWaitError";
|
|
177
256
|
}
|
|
178
257
|
}
|
|
258
|
+
var NO_REFUSALS = {
|
|
259
|
+
"not-accepting": 0,
|
|
260
|
+
"global-concurrency": 0,
|
|
261
|
+
"key-required": 0,
|
|
262
|
+
"key-capacity": 0,
|
|
263
|
+
"key-concurrency": 0,
|
|
264
|
+
"global-rate": 0,
|
|
265
|
+
"key-rate": 0,
|
|
266
|
+
upstream: 0
|
|
267
|
+
};
|
|
268
|
+
async function waitForDrain(drainWaiters, options) {
|
|
269
|
+
const controller = new AbortController;
|
|
270
|
+
let timer;
|
|
271
|
+
const abort = () => controller.abort();
|
|
272
|
+
if (options.signal?.aborted)
|
|
273
|
+
abort();
|
|
274
|
+
else
|
|
275
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
276
|
+
if (options.timeoutMs !== undefined) {
|
|
277
|
+
if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0) {
|
|
278
|
+
throw new TypeError("drain timeoutMs must be a non-negative safe integer");
|
|
279
|
+
}
|
|
280
|
+
timer = setTimeout(abort, options.timeoutMs);
|
|
281
|
+
}
|
|
282
|
+
let resolveDrained;
|
|
283
|
+
try {
|
|
284
|
+
await Promise.race([
|
|
285
|
+
new Promise((resolve) => {
|
|
286
|
+
resolveDrained = resolve;
|
|
287
|
+
drainWaiters.add(resolve);
|
|
288
|
+
}),
|
|
289
|
+
new Promise((resolve) => {
|
|
290
|
+
if (controller.signal.aborted)
|
|
291
|
+
resolve();
|
|
292
|
+
else
|
|
293
|
+
controller.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
294
|
+
})
|
|
295
|
+
]);
|
|
296
|
+
} finally {
|
|
297
|
+
if (timer !== undefined)
|
|
298
|
+
clearTimeout(timer);
|
|
299
|
+
options.signal?.removeEventListener("abort", abort);
|
|
300
|
+
if (resolveDrained)
|
|
301
|
+
drainWaiters.delete(resolveDrained);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
179
304
|
function createBoundedAdmission(config) {
|
|
180
305
|
const policy = BoundedAdmissionPolicySchema.parse(config.policy);
|
|
181
306
|
const clock = config.clock ?? { now: () => performance.now() };
|
|
182
307
|
const keys = new Map;
|
|
183
308
|
const globalRate = [];
|
|
184
309
|
const drainWaiters = new Set;
|
|
185
|
-
const refusals = {
|
|
186
|
-
"not-accepting": 0,
|
|
187
|
-
"global-concurrency": 0,
|
|
188
|
-
"key-required": 0,
|
|
189
|
-
"key-capacity": 0,
|
|
190
|
-
"key-concurrency": 0,
|
|
191
|
-
"global-rate": 0,
|
|
192
|
-
"key-rate": 0,
|
|
193
|
-
upstream: 0
|
|
194
|
-
};
|
|
310
|
+
const refusals = { ...NO_REFUSALS };
|
|
195
311
|
let state = "accepting";
|
|
196
312
|
let active = 0;
|
|
197
313
|
let accepted = 0;
|
|
@@ -219,26 +335,7 @@ function createBoundedAdmission(config) {
|
|
|
219
335
|
...perKey.rate && { rate: perKey.rate }
|
|
220
336
|
});
|
|
221
337
|
};
|
|
222
|
-
const
|
|
223
|
-
let expired = 0;
|
|
224
|
-
while (expired < samples.length && at - (samples[expired]?.at ?? at) >= intervalMs) {
|
|
225
|
-
expired += 1;
|
|
226
|
-
}
|
|
227
|
-
if (expired > 0)
|
|
228
|
-
samples.splice(0, expired);
|
|
229
|
-
};
|
|
230
|
-
const prune = (at) => {
|
|
231
|
-
if (policy.global.rate)
|
|
232
|
-
pruneRate(globalRate, policy.global.rate.intervalMs, at);
|
|
233
|
-
for (const [key, record] of keys) {
|
|
234
|
-
if (record.limits?.rate) {
|
|
235
|
-
pruneRate(record.rate, record.limits.rate.intervalMs, at);
|
|
236
|
-
}
|
|
237
|
-
if (record.active === 0 && record.rate.length === 0)
|
|
238
|
-
keys.delete(key);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
const retryAfter = (samples, budget, at) => Math.max(1, Math.ceil(budget.intervalMs - (at - (samples[0]?.at ?? at))));
|
|
338
|
+
const prune = (at) => pruneExpired(policy, globalRate, keys, at);
|
|
242
339
|
const refuse = (reason, retryAfterMs) => {
|
|
243
340
|
refused += 1;
|
|
244
341
|
refusals[reason] += 1;
|
|
@@ -260,16 +357,9 @@ function createBoundedAdmission(config) {
|
|
|
260
357
|
active -= 1;
|
|
261
358
|
if (keyRecord)
|
|
262
359
|
keyRecord.active -= 1;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
globalRate.splice(index, 1);
|
|
267
|
-
}
|
|
268
|
-
if (keySample && keyRecord) {
|
|
269
|
-
const index = keyRecord.rate.indexOf(keySample);
|
|
270
|
-
if (index >= 0)
|
|
271
|
-
keyRecord.rate.splice(index, 1);
|
|
272
|
-
}
|
|
360
|
+
removeSample(globalRate, globalSample);
|
|
361
|
+
if (keyRecord)
|
|
362
|
+
removeSample(keyRecord.rate, keySample);
|
|
273
363
|
if (key !== undefined && keyRecord?.active === 0 && keyRecord.rate.length === 0) {
|
|
274
364
|
keys.delete(key);
|
|
275
365
|
}
|
|
@@ -368,87 +458,10 @@ function createBoundedAdmission(config) {
|
|
|
368
458
|
stopAdmission();
|
|
369
459
|
if (active === 0)
|
|
370
460
|
return { drained: true, remaining: 0 };
|
|
371
|
-
|
|
372
|
-
let timer;
|
|
373
|
-
const abort = () => controller.abort();
|
|
374
|
-
if (options.signal?.aborted)
|
|
375
|
-
abort();
|
|
376
|
-
else
|
|
377
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
378
|
-
if (options.timeoutMs !== undefined) {
|
|
379
|
-
if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0) {
|
|
380
|
-
throw new TypeError("drain timeoutMs must be a non-negative safe integer");
|
|
381
|
-
}
|
|
382
|
-
timer = setTimeout(abort, options.timeoutMs);
|
|
383
|
-
}
|
|
384
|
-
let resolveDrained;
|
|
385
|
-
try {
|
|
386
|
-
await Promise.race([
|
|
387
|
-
new Promise((resolve) => {
|
|
388
|
-
resolveDrained = resolve;
|
|
389
|
-
drainWaiters.add(resolve);
|
|
390
|
-
}),
|
|
391
|
-
new Promise((resolve) => {
|
|
392
|
-
if (controller.signal.aborted)
|
|
393
|
-
resolve();
|
|
394
|
-
else
|
|
395
|
-
controller.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
396
|
-
})
|
|
397
|
-
]);
|
|
398
|
-
} finally {
|
|
399
|
-
if (timer !== undefined)
|
|
400
|
-
clearTimeout(timer);
|
|
401
|
-
options.signal?.removeEventListener("abort", abort);
|
|
402
|
-
if (resolveDrained)
|
|
403
|
-
drainWaiters.delete(resolveDrained);
|
|
404
|
-
}
|
|
461
|
+
await waitForDrain(drainWaiters, options);
|
|
405
462
|
return { drained: active === 0, remaining: active };
|
|
406
463
|
};
|
|
407
|
-
const run =
|
|
408
|
-
if (options.timeoutMs !== undefined) {
|
|
409
|
-
if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0) {
|
|
410
|
-
throw new TypeError("run timeoutMs must be a non-negative safe integer");
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (options.signal?.aborted)
|
|
414
|
-
throw new BoundedOperationWaitError("cancelled");
|
|
415
|
-
const admission = acquire(key);
|
|
416
|
-
if (admission.outcome === "refused") {
|
|
417
|
-
throw new BoundedAdmissionRefusalError(admission.reason, admission.retryAfterMs);
|
|
418
|
-
}
|
|
419
|
-
const workAbort = new AbortController;
|
|
420
|
-
const abortWork = () => workAbort.abort(options.signal?.reason);
|
|
421
|
-
options.signal?.addEventListener("abort", abortWork, { once: true });
|
|
422
|
-
let timer;
|
|
423
|
-
let timedOut = false;
|
|
424
|
-
if (options.timeoutMs !== undefined) {
|
|
425
|
-
timer = setTimeout(() => {
|
|
426
|
-
timedOut = true;
|
|
427
|
-
workAbort.abort(new DOMException("Operation wait timed out", "TimeoutError"));
|
|
428
|
-
}, options.timeoutMs);
|
|
429
|
-
}
|
|
430
|
-
const outcome = Promise.resolve().then(() => work({ signal: workAbort.signal })).then((value) => ({ kind: "value", value }), (error) => ({ kind: "error", error })).finally(() => admission.lease.release());
|
|
431
|
-
const callerDone = new Promise((resolve) => {
|
|
432
|
-
const settle = () => resolve({ kind: "caller" });
|
|
433
|
-
if (workAbort.signal.aborted)
|
|
434
|
-
settle();
|
|
435
|
-
else
|
|
436
|
-
workAbort.signal.addEventListener("abort", settle, { once: true });
|
|
437
|
-
});
|
|
438
|
-
try {
|
|
439
|
-
const settled = await Promise.race([outcome, callerDone]);
|
|
440
|
-
if (settled.kind === "caller") {
|
|
441
|
-
throw new BoundedOperationWaitError(timedOut ? "timed-out" : "cancelled");
|
|
442
|
-
}
|
|
443
|
-
if (settled.kind === "error")
|
|
444
|
-
throw settled.error;
|
|
445
|
-
return settled.value;
|
|
446
|
-
} finally {
|
|
447
|
-
if (timer !== undefined)
|
|
448
|
-
clearTimeout(timer);
|
|
449
|
-
options.signal?.removeEventListener("abort", abortWork);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
464
|
+
const run = (key, work, options = {}) => runAdmitted(acquire, key, work, options);
|
|
452
465
|
return {
|
|
453
466
|
acquire,
|
|
454
467
|
run,
|
|
@@ -815,37 +828,67 @@ var emptyOutboxState = () => ({
|
|
|
815
828
|
});
|
|
816
829
|
var sizeOf = (value) => measureSize(value).responseBytes;
|
|
817
830
|
var DEFAULT_OUTBOX_BACKOFF = { minDelayMs: 1000, maxDelayMs: 60000, jitter: 0 };
|
|
818
|
-
function
|
|
819
|
-
const schema = notificationOutboxStateSchema(config.payloadSchema);
|
|
820
|
-
const clock = config.clock ?? (() => new Date);
|
|
821
|
-
const ownerId = config.ownerId ?? crypto.randomUUID();
|
|
831
|
+
function resolveOutboxLimits(config) {
|
|
822
832
|
const pollIntervalMs = z5.number().int().min(10).parse(config.pollIntervalMs ?? 1000);
|
|
823
833
|
const leaseMs = z5.number().int().min(100).parse(config.leaseMs ?? 30000);
|
|
824
834
|
const maxAttempts = z5.number().int().min(1).max(1e4).parse(config.maxAttempts ?? 100);
|
|
825
835
|
const maxQueue = z5.number().int().min(1).max(1e5).parse(config.maxQueue ?? 1000);
|
|
826
836
|
const maxStateBytes = z5.number().int().min(1024).parse(config.maxStateBytes ?? 1024 * 1024);
|
|
827
837
|
const retainReceipts = z5.number().int().min(0).max(1e5).parse(config.retainReceipts ?? 1000);
|
|
838
|
+
return { pollIntervalMs, leaseMs, maxAttempts, maxQueue, maxStateBytes, retainReceipts };
|
|
839
|
+
}
|
|
840
|
+
function boundedOutboxState(parsed, maxQueue, retainReceipts, maxStateBytes) {
|
|
841
|
+
if (parsed.queue.length > maxQueue) {
|
|
842
|
+
throw new Error(`[stitchkit] notification outbox queue limit (${maxQueue}) exceeded`);
|
|
843
|
+
}
|
|
844
|
+
let receipts = parsed.receipts.slice(0, retainReceipts);
|
|
845
|
+
let next = { ...parsed, receipts };
|
|
846
|
+
while (sizeOf(next) > maxStateBytes && receipts.length > 0) {
|
|
847
|
+
receipts = receipts.slice(0, -1);
|
|
848
|
+
next = { ...parsed, receipts };
|
|
849
|
+
}
|
|
850
|
+
if (sizeOf(next) > maxStateBytes) {
|
|
851
|
+
throw new Error(`[stitchkit] notification outbox state limit (${maxStateBytes} bytes) exceeded`);
|
|
852
|
+
}
|
|
853
|
+
return next;
|
|
854
|
+
}
|
|
855
|
+
function dropReason(classification, attempts, maxAttempts) {
|
|
856
|
+
if (classification.recipientUnreachable === true)
|
|
857
|
+
return "recipient-unreachable";
|
|
858
|
+
if (!classification.retryable)
|
|
859
|
+
return "terminal";
|
|
860
|
+
return attempts >= maxAttempts ? "attempt-limit" : null;
|
|
861
|
+
}
|
|
862
|
+
var RetryDelaySchema = z5.number().nonnegative().max(24 * 60 * 60 * 1000);
|
|
863
|
+
var NO_LEASE = { leaseOwner: null, leaseId: null, leaseUntil: null };
|
|
864
|
+
function claimDue(queue, now, ownerId, leaseMs) {
|
|
865
|
+
const candidate = [...queue].sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt)).find((item) => Date.parse(item.nextAttemptAt) <= now.getTime() && (item.leaseUntil === null || Date.parse(item.leaseUntil) <= now.getTime()));
|
|
866
|
+
if (!candidate)
|
|
867
|
+
return null;
|
|
868
|
+
return {
|
|
869
|
+
...candidate,
|
|
870
|
+
attempts: candidate.attempts + 1,
|
|
871
|
+
leaseOwner: ownerId,
|
|
872
|
+
leaseId: crypto.randomUUID(),
|
|
873
|
+
leaseUntil: new Date(now.getTime() + leaseMs).toISOString()
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
async function reportOutboxError(config, error) {
|
|
877
|
+
try {
|
|
878
|
+
await config.onError?.(error);
|
|
879
|
+
} catch {}
|
|
880
|
+
}
|
|
881
|
+
function createNotificationOutbox(config) {
|
|
882
|
+
const schema = notificationOutboxStateSchema(config.payloadSchema);
|
|
883
|
+
const clock = config.clock ?? (() => new Date);
|
|
884
|
+
const ownerId = config.ownerId ?? crypto.randomUUID();
|
|
885
|
+
const { pollIntervalMs, leaseMs, maxAttempts, maxQueue, maxStateBytes, retainReceipts } = resolveOutboxLimits(config);
|
|
828
886
|
const backoffMs = config.backoffMs ?? ((attempt) => backoffDelay(DEFAULT_OUTBOX_BACKOFF, attempt));
|
|
829
887
|
let timer;
|
|
830
888
|
let running = false;
|
|
831
889
|
let interrupted = false;
|
|
832
890
|
let flushTail = Promise.resolve();
|
|
833
|
-
const bounded = (input) =>
|
|
834
|
-
const parsed = schema.parse(input);
|
|
835
|
-
if (parsed.queue.length > maxQueue) {
|
|
836
|
-
throw new Error(`[stitchkit] notification outbox queue limit (${maxQueue}) exceeded`);
|
|
837
|
-
}
|
|
838
|
-
let receipts = parsed.receipts.slice(0, retainReceipts);
|
|
839
|
-
let next = { ...parsed, receipts };
|
|
840
|
-
while (sizeOf(next) > maxStateBytes && receipts.length > 0) {
|
|
841
|
-
receipts = receipts.slice(0, -1);
|
|
842
|
-
next = { ...parsed, receipts };
|
|
843
|
-
}
|
|
844
|
-
if (sizeOf(next) > maxStateBytes) {
|
|
845
|
-
throw new Error(`[stitchkit] notification outbox state limit (${maxStateBytes} bytes) exceeded`);
|
|
846
|
-
}
|
|
847
|
-
return next;
|
|
848
|
-
};
|
|
891
|
+
const bounded = (input) => boundedOutboxState(schema.parse(input), maxQueue, retainReceipts, maxStateBytes);
|
|
849
892
|
const parse = (state) => schema.parse(state ?? emptyOutboxState());
|
|
850
893
|
const enqueue = async (input) => {
|
|
851
894
|
const key = z5.string().min(1).max(512).parse(input.key);
|
|
@@ -857,18 +900,10 @@ function createNotificationOutbox(config) {
|
|
|
857
900
|
return { state, result: false };
|
|
858
901
|
}
|
|
859
902
|
const now = clock().toISOString();
|
|
903
|
+
const fresh = { key, payload, createdAt: now, attempts: 0, nextAttemptAt: now };
|
|
860
904
|
const queue = [
|
|
861
905
|
...state.queue.filter((item) => !supersedes.has(item.key)),
|
|
862
|
-
{
|
|
863
|
-
key,
|
|
864
|
-
payload,
|
|
865
|
-
createdAt: now,
|
|
866
|
-
attempts: 0,
|
|
867
|
-
nextAttemptAt: now,
|
|
868
|
-
leaseOwner: null,
|
|
869
|
-
leaseId: null,
|
|
870
|
-
leaseUntil: null
|
|
871
|
-
}
|
|
906
|
+
{ ...fresh, ...NO_LEASE }
|
|
872
907
|
];
|
|
873
908
|
const next = bounded({ ...state, queue });
|
|
874
909
|
return { state: next, result: true };
|
|
@@ -878,21 +913,13 @@ function createNotificationOutbox(config) {
|
|
|
878
913
|
const now = clock();
|
|
879
914
|
return config.store.update((current) => {
|
|
880
915
|
const state = parse(current);
|
|
881
|
-
const
|
|
882
|
-
if (!
|
|
916
|
+
const claimed = claimDue(state.queue, now, ownerId, leaseMs);
|
|
917
|
+
if (!claimed)
|
|
883
918
|
return { state, result: null };
|
|
884
|
-
const leaseId = crypto.randomUUID();
|
|
885
|
-
const claimed = {
|
|
886
|
-
...candidate,
|
|
887
|
-
attempts: candidate.attempts + 1,
|
|
888
|
-
leaseOwner: ownerId,
|
|
889
|
-
leaseId,
|
|
890
|
-
leaseUntil: new Date(now.getTime() + leaseMs).toISOString()
|
|
891
|
-
};
|
|
892
919
|
return {
|
|
893
920
|
state: bounded({
|
|
894
921
|
...state,
|
|
895
|
-
queue: state.queue.map((item) => item.key ===
|
|
922
|
+
queue: state.queue.map((item) => item.key === claimed.key ? claimed : item)
|
|
896
923
|
}),
|
|
897
924
|
result: claimed
|
|
898
925
|
};
|
|
@@ -926,7 +953,7 @@ function createNotificationOutbox(config) {
|
|
|
926
953
|
if (!live)
|
|
927
954
|
return { state, result: null };
|
|
928
955
|
const attempts = live.attempts;
|
|
929
|
-
const reason = classification
|
|
956
|
+
const reason = dropReason(classification, attempts, maxAttempts);
|
|
930
957
|
if (reason) {
|
|
931
958
|
return {
|
|
932
959
|
state: bounded({
|
|
@@ -936,15 +963,11 @@ function createNotificationOutbox(config) {
|
|
|
936
963
|
result: { item: { ...live, attempts }, error, reason }
|
|
937
964
|
};
|
|
938
965
|
}
|
|
939
|
-
const
|
|
940
|
-
const wait = z5.number().finite().nonnegative().max(24 * 60 * 60 * 1000).parse(rawDelay);
|
|
966
|
+
const wait = RetryDelaySchema.parse(backoffMs(attempts));
|
|
941
967
|
const retry = {
|
|
942
968
|
...live,
|
|
943
|
-
attempts,
|
|
944
969
|
nextAttemptAt: new Date(clock().getTime() + wait).toISOString(),
|
|
945
|
-
|
|
946
|
-
leaseId: null,
|
|
947
|
-
leaseUntil: null
|
|
970
|
+
...NO_LEASE
|
|
948
971
|
};
|
|
949
972
|
return {
|
|
950
973
|
state: bounded({
|
|
@@ -990,9 +1013,7 @@ function createNotificationOutbox(config) {
|
|
|
990
1013
|
try {
|
|
991
1014
|
await flush();
|
|
992
1015
|
} catch (error) {
|
|
993
|
-
|
|
994
|
-
await config.onError?.(error);
|
|
995
|
-
} catch {}
|
|
1016
|
+
await reportOutboxError(config, error);
|
|
996
1017
|
} finally {
|
|
997
1018
|
schedule();
|
|
998
1019
|
}
|
|
@@ -1047,21 +1068,28 @@ function notificationOutboxResource(outbox, config = {}) {
|
|
|
1047
1068
|
});
|
|
1048
1069
|
}
|
|
1049
1070
|
// src/application/process-lifecycle.ts
|
|
1071
|
+
import { z as z7 } from "zod";
|
|
1072
|
+
|
|
1073
|
+
// src/application/process-lifecycle-transitions.ts
|
|
1050
1074
|
import { z as z6 } from "zod";
|
|
1051
1075
|
var LifecycleTerminationSchema = z6.enum([
|
|
1052
1076
|
"active",
|
|
1077
|
+
"draining",
|
|
1053
1078
|
"clean",
|
|
1054
1079
|
"forced",
|
|
1080
|
+
"startup-failed",
|
|
1055
1081
|
"hot-reload",
|
|
1056
1082
|
"abnormal"
|
|
1057
1083
|
]);
|
|
1084
|
+
var timestamp = z6.string().datetime({ offset: true });
|
|
1058
1085
|
var LifecycleRunSchema = z6.object({
|
|
1059
1086
|
runId: z6.string().min(1).max(128),
|
|
1060
1087
|
pid: z6.number().int().positive(),
|
|
1061
1088
|
version: z6.string().min(1).max(256),
|
|
1062
|
-
startedAt:
|
|
1063
|
-
readyAt:
|
|
1064
|
-
|
|
1089
|
+
startedAt: timestamp,
|
|
1090
|
+
readyAt: timestamp.nullable(),
|
|
1091
|
+
unavailableAt: timestamp.nullable().default(null),
|
|
1092
|
+
stoppedAt: timestamp.nullable(),
|
|
1065
1093
|
termination: LifecycleTerminationSchema
|
|
1066
1094
|
}).strict();
|
|
1067
1095
|
var LifecycleStateSchema = z6.object({
|
|
@@ -1073,17 +1101,20 @@ var PreviousExitSchema = z6.enum([
|
|
|
1073
1101
|
"hot-reload",
|
|
1074
1102
|
"clean",
|
|
1075
1103
|
"forced",
|
|
1104
|
+
"startup-failed",
|
|
1076
1105
|
"handoff",
|
|
1077
1106
|
"abnormal"
|
|
1078
1107
|
]);
|
|
1079
1108
|
var emptyState = () => ({ schemaVersion: 1, runs: [] });
|
|
1080
1109
|
var epoch = (value) => Date.parse(value);
|
|
1081
1110
|
var elapsed = (later, earlier) => Math.max(0, epoch(later) - epoch(earlier));
|
|
1082
|
-
|
|
1111
|
+
var isOpenRun = (run) => run.termination === "active" || run.termination === "draining";
|
|
1112
|
+
function normalizeLifecycleState(state, retain) {
|
|
1083
1113
|
const parsed = LifecycleStateSchema.parse(state ?? emptyState());
|
|
1084
1114
|
const runs = [...parsed.runs];
|
|
1085
1115
|
for (let index = runs.length - 1;runs.length > retain && index >= 0; index -= 1) {
|
|
1086
|
-
|
|
1116
|
+
const run = runs[index];
|
|
1117
|
+
if (run && !isOpenRun(run))
|
|
1087
1118
|
runs.splice(index, 1);
|
|
1088
1119
|
}
|
|
1089
1120
|
return { schemaVersion: 1, runs: runs.slice(0, retain) };
|
|
@@ -1092,15 +1123,45 @@ var UNKNOWN_VERSION = "unknown";
|
|
|
1092
1123
|
function isVersionChange(previous, next) {
|
|
1093
1124
|
return previous !== UNKNOWN_VERSION && next !== UNKNOWN_VERSION && previous !== next;
|
|
1094
1125
|
}
|
|
1126
|
+
var RetainSchema = z6.number().int().min(1).max(1000);
|
|
1127
|
+
function previousExitOf(previous, run, overlap) {
|
|
1128
|
+
if (!previous)
|
|
1129
|
+
return "first-boot";
|
|
1130
|
+
const ended = previous.termination;
|
|
1131
|
+
if (ended !== "active" && ended !== "draining")
|
|
1132
|
+
return ended;
|
|
1133
|
+
if (previous.pid === run.pid)
|
|
1134
|
+
return "hot-reload";
|
|
1135
|
+
if (isVersionChange(previous.version, run.version) || overlap === "handoff")
|
|
1136
|
+
return "handoff";
|
|
1137
|
+
return previous.readyAt === null ? "startup-failed" : "abnormal";
|
|
1138
|
+
}
|
|
1139
|
+
function closeAbandoned(candidate, previous, previousExit, run, overlap) {
|
|
1140
|
+
if (!isOpenRun(candidate))
|
|
1141
|
+
return candidate;
|
|
1142
|
+
const closed = { ...candidate, stoppedAt: run.startedAt };
|
|
1143
|
+
if (candidate.pid === run.pid)
|
|
1144
|
+
return { ...closed, termination: "hot-reload" };
|
|
1145
|
+
if (overlap === "handoff")
|
|
1146
|
+
return candidate;
|
|
1147
|
+
if (candidate.runId === previous?.runId && previousExit === "handoff")
|
|
1148
|
+
return candidate;
|
|
1149
|
+
return {
|
|
1150
|
+
...closed,
|
|
1151
|
+
termination: candidate.readyAt === null ? "startup-failed" : "abnormal"
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1095
1154
|
function transitionProcessStart(source, input) {
|
|
1096
|
-
const retain =
|
|
1097
|
-
const
|
|
1155
|
+
const retain = RetainSchema.parse(input.retain ?? 20);
|
|
1156
|
+
const overlap = input.sameVersionOverlap ?? "abnormal";
|
|
1157
|
+
const state = normalizeLifecycleState(source, 1000);
|
|
1098
1158
|
const run = LifecycleRunSchema.parse({
|
|
1099
1159
|
runId: input.runId,
|
|
1100
1160
|
pid: input.pid,
|
|
1101
1161
|
version: input.version,
|
|
1102
1162
|
startedAt: input.now,
|
|
1103
1163
|
readyAt: null,
|
|
1164
|
+
unavailableAt: null,
|
|
1104
1165
|
stoppedAt: null,
|
|
1105
1166
|
termination: "active"
|
|
1106
1167
|
});
|
|
@@ -1108,31 +1169,10 @@ function transitionProcessStart(source, input) {
|
|
|
1108
1169
|
throw new Error(`[stitchkit] lifecycle run id already exists: ${run.runId}`);
|
|
1109
1170
|
}
|
|
1110
1171
|
const previous = state.runs[0] ?? null;
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
downtimeMs = elapsed(run.startedAt, previous.stoppedAt);
|
|
1116
|
-
} else if (previous) {
|
|
1117
|
-
if (previous.pid === run.pid)
|
|
1118
|
-
previousExit = "hot-reload";
|
|
1119
|
-
else if (isVersionChange(previous.version, run.version))
|
|
1120
|
-
previousExit = "handoff";
|
|
1121
|
-
else
|
|
1122
|
-
previousExit = input.sameVersionOverlap ?? "abnormal";
|
|
1123
|
-
}
|
|
1124
|
-
const runs = state.runs.map((candidate) => {
|
|
1125
|
-
if (candidate.runId !== previous?.runId || candidate.termination !== "active")
|
|
1126
|
-
return candidate;
|
|
1127
|
-
if (previousExit === "hot-reload") {
|
|
1128
|
-
return { ...candidate, stoppedAt: run.startedAt, termination: "hot-reload" };
|
|
1129
|
-
}
|
|
1130
|
-
if (previousExit === "abnormal") {
|
|
1131
|
-
return { ...candidate, stoppedAt: run.startedAt, termination: "abnormal" };
|
|
1132
|
-
}
|
|
1133
|
-
return candidate;
|
|
1134
|
-
});
|
|
1135
|
-
const next = normalize({ schemaVersion: 1, runs: [run, ...runs] }, retain);
|
|
1172
|
+
const previousExit = previousExitOf(previous, run, overlap);
|
|
1173
|
+
const processGapMs = previous && !isOpenRun(previous) && previous.stoppedAt ? elapsed(run.startedAt, previous.stoppedAt) : null;
|
|
1174
|
+
const runs = state.runs.map((candidate) => closeAbandoned(candidate, previous, previousExit, run, overlap));
|
|
1175
|
+
const next = normalizeLifecycleState({ schemaVersion: 1, runs: [run, ...runs] }, retain);
|
|
1136
1176
|
return {
|
|
1137
1177
|
state: next,
|
|
1138
1178
|
fact: {
|
|
@@ -1146,15 +1186,30 @@ function transitionProcessStart(source, input) {
|
|
|
1146
1186
|
previousPid: previous?.pid ?? null,
|
|
1147
1187
|
previousVersion: previous?.version ?? null,
|
|
1148
1188
|
versionChanged: previous !== null && isVersionChange(previous.version, run.version),
|
|
1149
|
-
|
|
1189
|
+
processGapMs
|
|
1150
1190
|
}
|
|
1151
1191
|
};
|
|
1152
1192
|
}
|
|
1193
|
+
function findOpen(state, input) {
|
|
1194
|
+
return state.runs.find((run) => run.runId === input.runId && run.pid === input.pid && isOpenRun(run));
|
|
1195
|
+
}
|
|
1196
|
+
function unavailability(state, target, readyAt) {
|
|
1197
|
+
const older = state.runs.slice(state.runs.indexOf(target) + 1);
|
|
1198
|
+
const served = older.find((run) => run.readyAt !== null);
|
|
1199
|
+
if (!served)
|
|
1200
|
+
return { downtimeMs: null, unavailableSince: null };
|
|
1201
|
+
if (served.unavailableAt !== null) {
|
|
1202
|
+
return {
|
|
1203
|
+
downtimeMs: elapsed(readyAt, served.unavailableAt),
|
|
1204
|
+
unavailableSince: served.unavailableAt
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
return isOpenRun(served) ? { downtimeMs: 0, unavailableSince: null } : { downtimeMs: null, unavailableSince: null };
|
|
1208
|
+
}
|
|
1153
1209
|
function transitionProcessReady(source, input) {
|
|
1154
|
-
const
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
if (!target) {
|
|
1210
|
+
const state = normalizeLifecycleState(source, input.retain ?? 20);
|
|
1211
|
+
const target = findOpen(state, input);
|
|
1212
|
+
if (target?.termination !== "active") {
|
|
1158
1213
|
return {
|
|
1159
1214
|
state,
|
|
1160
1215
|
fact: {
|
|
@@ -1163,6 +1218,8 @@ function transitionProcessReady(source, input) {
|
|
|
1163
1218
|
pid: input.pid,
|
|
1164
1219
|
readyAt: input.now,
|
|
1165
1220
|
startupMs: 0,
|
|
1221
|
+
downtimeMs: null,
|
|
1222
|
+
unavailableSince: null,
|
|
1166
1223
|
recorded: false
|
|
1167
1224
|
}
|
|
1168
1225
|
};
|
|
@@ -1179,14 +1236,45 @@ function transitionProcessReady(source, input) {
|
|
|
1179
1236
|
pid: target.pid,
|
|
1180
1237
|
readyAt,
|
|
1181
1238
|
startupMs: elapsed(readyAt, target.startedAt),
|
|
1239
|
+
...unavailability(state, target, readyAt),
|
|
1182
1240
|
recorded: target.readyAt === null
|
|
1183
1241
|
}
|
|
1184
1242
|
};
|
|
1185
1243
|
}
|
|
1244
|
+
function transitionProcessDraining(source, input) {
|
|
1245
|
+
const state = normalizeLifecycleState(source, input.retain ?? 20);
|
|
1246
|
+
const target = findOpen(state, input);
|
|
1247
|
+
if (target?.termination !== "active") {
|
|
1248
|
+
return {
|
|
1249
|
+
state,
|
|
1250
|
+
fact: {
|
|
1251
|
+
type: "draining",
|
|
1252
|
+
runId: input.runId,
|
|
1253
|
+
pid: input.pid,
|
|
1254
|
+
unavailableAt: target ? target.unavailableAt : null,
|
|
1255
|
+
recorded: false
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
const unavailableAt = target.readyAt === null ? null : input.now;
|
|
1260
|
+
return {
|
|
1261
|
+
state: {
|
|
1262
|
+
schemaVersion: 1,
|
|
1263
|
+
runs: state.runs.map((run) => run.runId === target.runId ? { ...run, termination: "draining", unavailableAt } : run)
|
|
1264
|
+
},
|
|
1265
|
+
fact: {
|
|
1266
|
+
type: "draining",
|
|
1267
|
+
runId: target.runId,
|
|
1268
|
+
pid: target.pid,
|
|
1269
|
+
unavailableAt,
|
|
1270
|
+
recorded: true
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1186
1274
|
function transitionProcessShutdown(source, input) {
|
|
1187
|
-
const state =
|
|
1188
|
-
const target = state
|
|
1189
|
-
const
|
|
1275
|
+
const state = normalizeLifecycleState(source, input.retain ?? 20);
|
|
1276
|
+
const target = findOpen(state, input);
|
|
1277
|
+
const requested = input.forced ? "forced" : "clean";
|
|
1190
1278
|
if (!target) {
|
|
1191
1279
|
return {
|
|
1192
1280
|
state,
|
|
@@ -1196,15 +1284,17 @@ function transitionProcessShutdown(source, input) {
|
|
|
1196
1284
|
pid: input.pid,
|
|
1197
1285
|
stoppedAt: input.now,
|
|
1198
1286
|
uptimeMs: null,
|
|
1199
|
-
termination,
|
|
1287
|
+
termination: requested,
|
|
1200
1288
|
recorded: false
|
|
1201
1289
|
}
|
|
1202
1290
|
};
|
|
1203
1291
|
}
|
|
1292
|
+
const termination = target.readyAt === null ? "startup-failed" : requested;
|
|
1293
|
+
const unavailableAt = target.unavailableAt ?? (target.readyAt === null ? null : input.now);
|
|
1204
1294
|
return {
|
|
1205
1295
|
state: {
|
|
1206
1296
|
schemaVersion: 1,
|
|
1207
|
-
runs: state.runs.map((run) => run.runId === target.runId ? { ...run, stoppedAt: input.now, termination } : run)
|
|
1297
|
+
runs: state.runs.map((run) => run.runId === target.runId ? { ...run, unavailableAt, stoppedAt: input.now, termination } : run)
|
|
1208
1298
|
},
|
|
1209
1299
|
fact: {
|
|
1210
1300
|
type: "stopped",
|
|
@@ -1217,10 +1307,12 @@ function transitionProcessShutdown(source, input) {
|
|
|
1217
1307
|
}
|
|
1218
1308
|
};
|
|
1219
1309
|
}
|
|
1310
|
+
|
|
1311
|
+
// src/application/process-lifecycle.ts
|
|
1220
1312
|
function createProcessLifecycleLedger(config) {
|
|
1221
1313
|
const clock = config.clock ?? (() => new Date);
|
|
1222
|
-
const pid =
|
|
1223
|
-
const retain =
|
|
1314
|
+
const pid = z7.number().int().positive().parse(config.pid ?? process.pid);
|
|
1315
|
+
const retain = z7.number().int().min(1).max(1000).parse(config.retain ?? 20);
|
|
1224
1316
|
const configuredRunId = config.runId;
|
|
1225
1317
|
const nextRunId = typeof configuredRunId === "function" ? configuredRunId : configuredRunId === undefined ? () => crypto.randomUUID() : () => configuredRunId;
|
|
1226
1318
|
let runId = nextRunId();
|
|
@@ -1264,6 +1356,20 @@ function createProcessLifecycleLedger(config) {
|
|
|
1264
1356
|
publish(fact);
|
|
1265
1357
|
return fact;
|
|
1266
1358
|
},
|
|
1359
|
+
async recordDraining() {
|
|
1360
|
+
const fact = await config.store.update((state) => {
|
|
1361
|
+
const transition = transitionProcessDraining(state, {
|
|
1362
|
+
runId,
|
|
1363
|
+
pid,
|
|
1364
|
+
now: clock().toISOString(),
|
|
1365
|
+
retain
|
|
1366
|
+
});
|
|
1367
|
+
return { state: transition.state, result: transition.fact };
|
|
1368
|
+
});
|
|
1369
|
+
if (fact.recorded)
|
|
1370
|
+
publish(fact);
|
|
1371
|
+
return fact;
|
|
1372
|
+
},
|
|
1267
1373
|
async recordShutdown(input) {
|
|
1268
1374
|
const fact = await config.store.update((state) => {
|
|
1269
1375
|
const transition = transitionProcessShutdown(state, {
|
|
@@ -1280,11 +1386,11 @@ function createProcessLifecycleLedger(config) {
|
|
|
1280
1386
|
return fact;
|
|
1281
1387
|
},
|
|
1282
1388
|
async current() {
|
|
1283
|
-
const state =
|
|
1389
|
+
const state = normalizeLifecycleState(await config.store.read(), retain);
|
|
1284
1390
|
return state.runs.find((run) => run.runId === runId && run.pid === pid) ?? null;
|
|
1285
1391
|
},
|
|
1286
1392
|
async runs() {
|
|
1287
|
-
return
|
|
1393
|
+
return normalizeLifecycleState(await config.store.read(), retain).runs;
|
|
1288
1394
|
},
|
|
1289
1395
|
subscribe(listener) {
|
|
1290
1396
|
listeners.add(listener);
|
|
@@ -1319,6 +1425,9 @@ function lifecycleLedgerResource(ledger, config) {
|
|
|
1319
1425
|
async activate() {
|
|
1320
1426
|
await ledger.recordReady();
|
|
1321
1427
|
},
|
|
1428
|
+
async stopAdmission() {
|
|
1429
|
+
await ledger.recordDraining();
|
|
1430
|
+
},
|
|
1322
1431
|
async close() {
|
|
1323
1432
|
await stop(false);
|
|
1324
1433
|
},
|
|
@@ -1328,27 +1437,27 @@ function lifecycleLedgerResource(ledger, config) {
|
|
|
1328
1437
|
});
|
|
1329
1438
|
}
|
|
1330
1439
|
// src/application/revision-signal.ts
|
|
1331
|
-
import { z as
|
|
1332
|
-
var NonNegativeSafeIntegerSchema =
|
|
1333
|
-
var PositiveSafeIntegerSchema2 =
|
|
1334
|
-
var TimerDelaySchema =
|
|
1335
|
-
var RevisionSignalStateSchema =
|
|
1336
|
-
var RevisionSignalWaitOutcomeSchema =
|
|
1440
|
+
import { z as z8 } from "zod";
|
|
1441
|
+
var NonNegativeSafeIntegerSchema = z8.number().int().nonnegative().safe();
|
|
1442
|
+
var PositiveSafeIntegerSchema2 = z8.number().int().positive().safe();
|
|
1443
|
+
var TimerDelaySchema = z8.number().int().positive().max(2147483647);
|
|
1444
|
+
var RevisionSignalStateSchema = z8.enum(["open", "closed"]);
|
|
1445
|
+
var RevisionSignalWaitOutcomeSchema = z8.enum([
|
|
1337
1446
|
"changed",
|
|
1338
1447
|
"timed-out",
|
|
1339
1448
|
"aborted",
|
|
1340
1449
|
"closed",
|
|
1341
1450
|
"capacity"
|
|
1342
1451
|
]);
|
|
1343
|
-
var RevisionSignalWaitResultSchema =
|
|
1452
|
+
var RevisionSignalWaitResultSchema = z8.object({
|
|
1344
1453
|
outcome: RevisionSignalWaitOutcomeSchema,
|
|
1345
1454
|
revision: NonNegativeSafeIntegerSchema
|
|
1346
1455
|
}).strict().readonly();
|
|
1347
|
-
var RevisionSignalAdvanceResultSchema =
|
|
1348
|
-
outcome:
|
|
1456
|
+
var RevisionSignalAdvanceResultSchema = z8.object({
|
|
1457
|
+
outcome: z8.enum(["advanced", "closed"]),
|
|
1349
1458
|
revision: NonNegativeSafeIntegerSchema
|
|
1350
1459
|
}).strict().readonly();
|
|
1351
|
-
var RevisionSignalSnapshotSchema =
|
|
1460
|
+
var RevisionSignalSnapshotSchema = z8.object({
|
|
1352
1461
|
state: RevisionSignalStateSchema,
|
|
1353
1462
|
revision: NonNegativeSafeIntegerSchema,
|
|
1354
1463
|
pending: NonNegativeSafeIntegerSchema,
|
|
@@ -1552,59 +1661,60 @@ function managedServerResource(config) {
|
|
|
1552
1661
|
}
|
|
1553
1662
|
});
|
|
1554
1663
|
}
|
|
1555
|
-
// src/application/watch-hub.ts
|
|
1664
|
+
// src/application/watch-hub-core.ts
|
|
1556
1665
|
var DEFAULT_BACKOFF = { minDelayMs: 250, maxDelayMs: 30000, jitter: 0.2 };
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1666
|
+
|
|
1667
|
+
class WatchHubCore {
|
|
1668
|
+
config;
|
|
1669
|
+
sources = new Map;
|
|
1670
|
+
held = new Map;
|
|
1671
|
+
maxWatches;
|
|
1672
|
+
holdMs;
|
|
1673
|
+
deltaMemoryBytes;
|
|
1674
|
+
same;
|
|
1675
|
+
reads = 0;
|
|
1676
|
+
closed = false;
|
|
1677
|
+
constructor(config) {
|
|
1678
|
+
this.config = config;
|
|
1679
|
+
this.maxWatches = config.maxWatchesPerSubscriber ?? 64;
|
|
1680
|
+
this.holdMs = config.holdMs ?? 0;
|
|
1681
|
+
this.deltaMemoryBytes = config.deltaMemoryBytes ?? 262144;
|
|
1682
|
+
this.same = config.same ?? ((previous, next) => serializeCanonicalJson(previous) === serializeCanonicalJson(next));
|
|
1683
|
+
}
|
|
1684
|
+
tell(key, send) {
|
|
1575
1685
|
try {
|
|
1576
|
-
|
|
1686
|
+
send();
|
|
1577
1687
|
return true;
|
|
1578
1688
|
} catch (error) {
|
|
1579
|
-
config.logger?.warn?.("[stitchkit] watch subscriber threw", {
|
|
1689
|
+
this.config.logger?.warn?.("[stitchkit] watch subscriber threw", {
|
|
1580
1690
|
key: watchKeyString(key),
|
|
1581
1691
|
error: error instanceof Error ? error.message : String(error)
|
|
1582
1692
|
});
|
|
1583
1693
|
return false;
|
|
1584
1694
|
}
|
|
1585
1695
|
}
|
|
1586
|
-
|
|
1696
|
+
announceState(source, state) {
|
|
1587
1697
|
source.state = state;
|
|
1588
1698
|
for (const subscriber of source.subscribers) {
|
|
1589
|
-
tell(source.key, () => subscriber.state(state));
|
|
1699
|
+
this.tell(source.key, () => subscriber.state(state));
|
|
1590
1700
|
}
|
|
1591
1701
|
}
|
|
1592
|
-
|
|
1593
|
-
if (deltaMemoryBytes === 0)
|
|
1702
|
+
remember(source, revision, value, fingerprint) {
|
|
1703
|
+
if (this.deltaMemoryBytes === 0)
|
|
1594
1704
|
return;
|
|
1595
1705
|
const bytes = (JSON.stringify(value) ?? "null").length;
|
|
1596
|
-
if (bytes > deltaMemoryBytes)
|
|
1706
|
+
if (bytes > this.deltaMemoryBytes)
|
|
1597
1707
|
return;
|
|
1598
1708
|
source.history.set(revision, { value, fingerprint, bytes });
|
|
1599
1709
|
source.historyBytes += bytes;
|
|
1600
1710
|
for (const [oldest, entry] of source.history) {
|
|
1601
|
-
if (source.historyBytes <= deltaMemoryBytes)
|
|
1711
|
+
if (source.historyBytes <= this.deltaMemoryBytes)
|
|
1602
1712
|
break;
|
|
1603
1713
|
source.history.delete(oldest);
|
|
1604
1714
|
source.historyBytes -= entry.bytes;
|
|
1605
1715
|
}
|
|
1606
1716
|
}
|
|
1607
|
-
|
|
1717
|
+
frameFor(source, subscriber) {
|
|
1608
1718
|
const value = source.value;
|
|
1609
1719
|
const fingerprint = source.fingerprint ?? valueFingerprint(value);
|
|
1610
1720
|
const full = {
|
|
@@ -1620,10 +1730,10 @@ function createWatchHub(config) {
|
|
|
1620
1730
|
if (base === source.revision) {
|
|
1621
1731
|
return { kind: "unchanged", key: source.key, revision: source.revision, fingerprint };
|
|
1622
1732
|
}
|
|
1623
|
-
const
|
|
1624
|
-
if (!
|
|
1733
|
+
const held = source.history.get(base);
|
|
1734
|
+
if (!held)
|
|
1625
1735
|
return full;
|
|
1626
|
-
const delta = diff(
|
|
1736
|
+
const delta = diff(held.value, value);
|
|
1627
1737
|
if (delta === undefined || !deltaWins(delta, value))
|
|
1628
1738
|
return full;
|
|
1629
1739
|
return {
|
|
@@ -1635,7 +1745,7 @@ function createWatchHub(config) {
|
|
|
1635
1745
|
delta
|
|
1636
1746
|
};
|
|
1637
1747
|
}
|
|
1638
|
-
|
|
1748
|
+
adoptBaseline(source, subscriber, have) {
|
|
1639
1749
|
if (source.fingerprint === have.fingerprint) {
|
|
1640
1750
|
source.baselines.set(subscriber, source.revision);
|
|
1641
1751
|
return;
|
|
@@ -1652,56 +1762,56 @@ function createWatchHub(config) {
|
|
|
1652
1762
|
return;
|
|
1653
1763
|
}
|
|
1654
1764
|
}
|
|
1655
|
-
|
|
1656
|
-
const frame = frameFor(source, subscriber);
|
|
1657
|
-
const delivered = tell(source.key, () => subscriber.value(frame));
|
|
1765
|
+
deliver(source, subscriber) {
|
|
1766
|
+
const frame = this.frameFor(source, subscriber);
|
|
1767
|
+
const delivered = this.tell(source.key, () => subscriber.value(frame));
|
|
1658
1768
|
if (delivered)
|
|
1659
1769
|
source.baselines.set(subscriber, source.revision);
|
|
1660
1770
|
return delivered;
|
|
1661
1771
|
}
|
|
1662
|
-
|
|
1772
|
+
publish(source, value, fingerprint) {
|
|
1663
1773
|
if (source.fingerprint !== undefined) {
|
|
1664
|
-
remember(source, source.revision, source.value, source.fingerprint);
|
|
1774
|
+
this.remember(source, source.revision, source.value, source.fingerprint);
|
|
1665
1775
|
}
|
|
1666
1776
|
source.revision += 1;
|
|
1667
1777
|
source.value = value;
|
|
1668
1778
|
source.fingerprint = fingerprint;
|
|
1669
1779
|
for (const subscriber of source.subscribers)
|
|
1670
|
-
deliver(source, subscriber);
|
|
1780
|
+
this.deliver(source, subscriber);
|
|
1671
1781
|
}
|
|
1672
|
-
async
|
|
1673
|
-
if (source.reading || closed)
|
|
1782
|
+
async pump(source) {
|
|
1783
|
+
if (source.reading || this.closed)
|
|
1674
1784
|
return;
|
|
1675
1785
|
source.reading = true;
|
|
1676
1786
|
try {
|
|
1677
|
-
while (source.dirty && !closed) {
|
|
1787
|
+
while (source.dirty && !this.closed) {
|
|
1678
1788
|
source.dirty = false;
|
|
1679
|
-
reads += 1;
|
|
1789
|
+
this.reads += 1;
|
|
1680
1790
|
try {
|
|
1681
|
-
const value = await config.read(source.operation, source.args);
|
|
1791
|
+
const value = await this.config.read(source.operation, source.args);
|
|
1682
1792
|
source.backoff.reset();
|
|
1683
1793
|
const signature = serializeCanonicalJson(value);
|
|
1684
|
-
const unchanged = source.signature !== undefined && same(source.value, value);
|
|
1794
|
+
const unchanged = source.signature !== undefined && this.same(source.value, value);
|
|
1685
1795
|
source.signature = signature;
|
|
1686
1796
|
if (!unchanged)
|
|
1687
|
-
publish(source, value, valueFingerprint(value));
|
|
1797
|
+
this.publish(source, value, valueFingerprint(value));
|
|
1688
1798
|
if (source.state.phase !== "live") {
|
|
1689
|
-
announceState(source, { key: source.key, phase: "live" });
|
|
1799
|
+
this.announceState(source, { key: source.key, phase: "live" });
|
|
1690
1800
|
}
|
|
1691
1801
|
} catch (error) {
|
|
1692
1802
|
const code = readErrorCode(error);
|
|
1693
|
-
announceState(source, {
|
|
1803
|
+
this.announceState(source, {
|
|
1694
1804
|
key: source.key,
|
|
1695
1805
|
phase: "unavailable",
|
|
1696
1806
|
reason: "source-error",
|
|
1697
1807
|
...code !== undefined && { code },
|
|
1698
1808
|
message: error instanceof Error ? error.message : String(error)
|
|
1699
1809
|
});
|
|
1700
|
-
config.logger?.warn?.("[stitchkit] watched read failed", {
|
|
1810
|
+
this.config.logger?.warn?.("[stitchkit] watched read failed", {
|
|
1701
1811
|
key: watchKeyString(source.key),
|
|
1702
1812
|
error
|
|
1703
1813
|
});
|
|
1704
|
-
scheduleRetry(source);
|
|
1814
|
+
this.scheduleRetry(source);
|
|
1705
1815
|
return;
|
|
1706
1816
|
}
|
|
1707
1817
|
}
|
|
@@ -1709,19 +1819,19 @@ function createWatchHub(config) {
|
|
|
1709
1819
|
source.reading = false;
|
|
1710
1820
|
}
|
|
1711
1821
|
}
|
|
1712
|
-
|
|
1713
|
-
if (closed || source.subscribers.size === 0)
|
|
1822
|
+
scheduleRetry(source) {
|
|
1823
|
+
if (this.closed || source.subscribers.size === 0)
|
|
1714
1824
|
return;
|
|
1715
1825
|
clearTimeout(source.retry);
|
|
1716
1826
|
source.retry = setTimeout(() => {
|
|
1717
1827
|
source.dirty = true;
|
|
1718
|
-
pump(source);
|
|
1828
|
+
this.pump(source);
|
|
1719
1829
|
}, source.backoff.next());
|
|
1720
1830
|
source.retry.unref?.();
|
|
1721
1831
|
}
|
|
1722
|
-
|
|
1832
|
+
acquire(operation, key, args) {
|
|
1723
1833
|
const id = watchKeyString(key);
|
|
1724
|
-
const existing = sources.get(id);
|
|
1834
|
+
const existing = this.sources.get(id);
|
|
1725
1835
|
if (existing) {
|
|
1726
1836
|
clearTimeout(existing.release);
|
|
1727
1837
|
existing.release = undefined;
|
|
@@ -1740,18 +1850,18 @@ function createWatchHub(config) {
|
|
|
1740
1850
|
reading: false,
|
|
1741
1851
|
dirty: true,
|
|
1742
1852
|
state: { key, phase: "opening" },
|
|
1743
|
-
backoff: createBackoff(config.backoff ?? DEFAULT_BACKOFF)
|
|
1853
|
+
backoff: createBackoff(this.config.backoff ?? DEFAULT_BACKOFF)
|
|
1744
1854
|
};
|
|
1745
|
-
for (const topic of config.invalidatedBy(operation, args)) {
|
|
1746
|
-
source.unsubscribes.push(config.subscribe(topic, () => {
|
|
1855
|
+
for (const topic of this.config.invalidatedBy(operation, args)) {
|
|
1856
|
+
source.unsubscribes.push(this.config.subscribe(topic, () => {
|
|
1747
1857
|
source.dirty = true;
|
|
1748
|
-
pump(source);
|
|
1858
|
+
this.pump(source);
|
|
1749
1859
|
}));
|
|
1750
1860
|
}
|
|
1751
|
-
sources.set(id, source);
|
|
1861
|
+
this.sources.set(id, source);
|
|
1752
1862
|
return source;
|
|
1753
1863
|
}
|
|
1754
|
-
|
|
1864
|
+
release(source) {
|
|
1755
1865
|
if (source.subscribers.size > 0)
|
|
1756
1866
|
return;
|
|
1757
1867
|
clearTimeout(source.retry);
|
|
@@ -1764,109 +1874,105 @@ function createWatchHub(config) {
|
|
|
1764
1874
|
}
|
|
1765
1875
|
for (const unsubscribe of source.unsubscribes)
|
|
1766
1876
|
unsubscribe();
|
|
1767
|
-
sources.delete(watchKeyString(source.key));
|
|
1877
|
+
this.sources.delete(watchKeyString(source.key));
|
|
1768
1878
|
};
|
|
1769
|
-
if (holdMs === 0) {
|
|
1879
|
+
if (this.holdMs === 0) {
|
|
1770
1880
|
drop();
|
|
1771
1881
|
return;
|
|
1772
1882
|
}
|
|
1773
|
-
source.release = setTimeout(drop, holdMs);
|
|
1883
|
+
source.release = setTimeout(drop, this.holdMs);
|
|
1774
1884
|
source.release.unref?.();
|
|
1775
1885
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
if (keys.has(id))
|
|
1793
|
-
return { accepted: true };
|
|
1794
|
-
if (keys.size >= maxWatches) {
|
|
1795
|
-
return {
|
|
1796
|
-
accepted: false,
|
|
1797
|
-
reason: `this connection is already watching ${keys.size} reads (limit ${maxWatches})`
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
const source = acquire(operation, key, args);
|
|
1801
|
-
source.subscribers.add(subscriber);
|
|
1802
|
-
keys.add(id);
|
|
1803
|
-
if (have)
|
|
1804
|
-
adoptBaseline(source, subscriber, have);
|
|
1805
|
-
try {
|
|
1806
|
-
if (source.signature !== undefined) {
|
|
1807
|
-
const frame = frameFor(source, subscriber);
|
|
1808
|
-
subscriber.value(frame);
|
|
1809
|
-
source.baselines.set(subscriber, source.revision);
|
|
1810
|
-
}
|
|
1811
|
-
subscriber.state(source.state);
|
|
1812
|
-
} catch (error) {
|
|
1813
|
-
source.subscribers.delete(subscriber);
|
|
1814
|
-
source.baselines.delete(subscriber);
|
|
1815
|
-
keys.delete(id);
|
|
1816
|
-
release(source);
|
|
1817
|
-
return {
|
|
1818
|
-
accepted: false,
|
|
1819
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
1820
|
-
};
|
|
1821
|
-
}
|
|
1822
|
-
pump(source);
|
|
1886
|
+
attach(subscriber) {
|
|
1887
|
+
const keys = new Set;
|
|
1888
|
+
this.held.set(subscriber, keys);
|
|
1889
|
+
return {
|
|
1890
|
+
open: (key, args, have) => {
|
|
1891
|
+
if (this.closed)
|
|
1892
|
+
return { accepted: false, reason: "the watch hub is closed" };
|
|
1893
|
+
const operation = { service: key.service, action: key.action };
|
|
1894
|
+
if (!this.config.watchable(operation)) {
|
|
1895
|
+
return {
|
|
1896
|
+
accepted: false,
|
|
1897
|
+
reason: `${key.service}.${key.action} is not watchable`
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
const id = watchKeyString(key);
|
|
1901
|
+
if (keys.has(id))
|
|
1823
1902
|
return { accepted: true };
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1903
|
+
if (keys.size >= this.maxWatches) {
|
|
1904
|
+
return {
|
|
1905
|
+
accepted: false,
|
|
1906
|
+
reason: `this connection is already watching ${keys.size} reads (limit ${this.maxWatches})`
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
const source = this.acquire(operation, key, args);
|
|
1910
|
+
source.subscribers.add(subscriber);
|
|
1911
|
+
keys.add(id);
|
|
1912
|
+
if (have)
|
|
1913
|
+
this.adoptBaseline(source, subscriber, have);
|
|
1914
|
+
try {
|
|
1915
|
+
if (source.signature !== undefined) {
|
|
1916
|
+
const frame = this.frameFor(source, subscriber);
|
|
1917
|
+
subscriber.value(frame);
|
|
1918
|
+
source.baselines.set(subscriber, source.revision);
|
|
1919
|
+
}
|
|
1920
|
+
subscriber.state(source.state);
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
source.subscribers.delete(subscriber);
|
|
1923
|
+
source.baselines.delete(subscriber);
|
|
1924
|
+
keys.delete(id);
|
|
1925
|
+
this.release(source);
|
|
1926
|
+
return {
|
|
1927
|
+
accepted: false,
|
|
1928
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
this.pump(source);
|
|
1932
|
+
return { accepted: true };
|
|
1933
|
+
},
|
|
1934
|
+
close: (key) => {
|
|
1935
|
+
const id = watchKeyString(key);
|
|
1936
|
+
if (!keys.delete(id))
|
|
1937
|
+
return;
|
|
1938
|
+
const source = this.sources.get(id);
|
|
1939
|
+
if (!source)
|
|
1940
|
+
return;
|
|
1941
|
+
source.subscribers.delete(subscriber);
|
|
1942
|
+
source.baselines.delete(subscriber);
|
|
1943
|
+
this.release(source);
|
|
1944
|
+
},
|
|
1945
|
+
detach: () => {
|
|
1946
|
+
for (const id of keys) {
|
|
1947
|
+
const source = this.sources.get(id);
|
|
1830
1948
|
if (!source)
|
|
1831
|
-
|
|
1949
|
+
continue;
|
|
1832
1950
|
source.subscribers.delete(subscriber);
|
|
1833
1951
|
source.baselines.delete(subscriber);
|
|
1834
|
-
release(source);
|
|
1835
|
-
},
|
|
1836
|
-
detach() {
|
|
1837
|
-
for (const id of keys) {
|
|
1838
|
-
const source = sources.get(id);
|
|
1839
|
-
if (!source)
|
|
1840
|
-
continue;
|
|
1841
|
-
source.subscribers.delete(subscriber);
|
|
1842
|
-
source.baselines.delete(subscriber);
|
|
1843
|
-
release(source);
|
|
1844
|
-
}
|
|
1845
|
-
keys.clear();
|
|
1846
|
-
held.delete(subscriber);
|
|
1952
|
+
this.release(source);
|
|
1847
1953
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
readCount: () => reads,
|
|
1851
|
-
size: () => sources.size,
|
|
1852
|
-
close() {
|
|
1853
|
-
closed = true;
|
|
1854
|
-
const teardown = [...sources.values()];
|
|
1855
|
-
sources.clear();
|
|
1856
|
-
held.clear();
|
|
1857
|
-
for (const source of teardown) {
|
|
1858
|
-
clearTimeout(source.retry);
|
|
1859
|
-
clearTimeout(source.release);
|
|
1860
|
-
announceState(source, {
|
|
1861
|
-
key: source.key,
|
|
1862
|
-
phase: "unavailable",
|
|
1863
|
-
reason: "source-unavailable"
|
|
1864
|
-
});
|
|
1865
|
-
for (const unsubscribe of source.unsubscribes)
|
|
1866
|
-
unsubscribe();
|
|
1954
|
+
keys.clear();
|
|
1955
|
+
this.held.delete(subscriber);
|
|
1867
1956
|
}
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
close() {
|
|
1960
|
+
this.closed = true;
|
|
1961
|
+
const teardown = [...this.sources.values()];
|
|
1962
|
+
this.sources.clear();
|
|
1963
|
+
this.held.clear();
|
|
1964
|
+
for (const source of teardown) {
|
|
1965
|
+
clearTimeout(source.retry);
|
|
1966
|
+
clearTimeout(source.release);
|
|
1967
|
+
this.announceState(source, {
|
|
1968
|
+
key: source.key,
|
|
1969
|
+
phase: "unavailable",
|
|
1970
|
+
reason: "source-unavailable"
|
|
1971
|
+
});
|
|
1972
|
+
for (const unsubscribe of source.unsubscribes)
|
|
1973
|
+
unsubscribe();
|
|
1868
1974
|
}
|
|
1869
|
-
}
|
|
1975
|
+
}
|
|
1870
1976
|
}
|
|
1871
1977
|
function valueFingerprint(value) {
|
|
1872
1978
|
return argumentsDigest({ value });
|
|
@@ -1877,11 +1983,31 @@ function readErrorCode(error) {
|
|
|
1877
1983
|
const code = Reflect.get(error, "code");
|
|
1878
1984
|
return typeof code === "string" ? code : undefined;
|
|
1879
1985
|
}
|
|
1986
|
+
|
|
1987
|
+
// src/application/watch-hub.ts
|
|
1988
|
+
function watchKey(operation, args) {
|
|
1989
|
+
const record = typeof args === "object" && args !== null && !Array.isArray(args) ? args : { value: args };
|
|
1990
|
+
return {
|
|
1991
|
+
service: operation.service,
|
|
1992
|
+
action: operation.action,
|
|
1993
|
+
digest: argumentsDigest(record)
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
function createWatchHub(config) {
|
|
1997
|
+
const hub = new WatchHubCore(config);
|
|
1998
|
+
return {
|
|
1999
|
+
attach: (subscriber) => hub.attach(subscriber),
|
|
2000
|
+
readCount: () => hub.reads,
|
|
2001
|
+
size: () => hub.sources.size,
|
|
2002
|
+
close: () => hub.close()
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
1880
2005
|
export {
|
|
1881
2006
|
watchKey,
|
|
1882
2007
|
transitionProcessStart,
|
|
1883
2008
|
transitionProcessShutdown,
|
|
1884
2009
|
transitionProcessReady,
|
|
2010
|
+
transitionProcessDraining,
|
|
1885
2011
|
sqliteKeyspaceBackend,
|
|
1886
2012
|
readDiagnosticJournalLockDiagnosis,
|
|
1887
2013
|
projectApplicationStatus,
|