stitchkit 0.94.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 +64 -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/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/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.js +361 -324
- 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-97ac4gph.js → index-7f68ghdn.js} +3 -3
- 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-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 +266 -2
- package/llms/application.txt +8 -266
- 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/tools.2.txt +88 -279
- package/llms/tools.3.txt +288 -0
- package/llms/tools.txt +27 -65
- package/llms-full.txt +108 -12
- package/llms.txt +5 -5
- 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
|
}
|
|
@@ -1552,59 +1573,60 @@ function managedServerResource(config) {
|
|
|
1552
1573
|
}
|
|
1553
1574
|
});
|
|
1554
1575
|
}
|
|
1555
|
-
// src/application/watch-hub.ts
|
|
1576
|
+
// src/application/watch-hub-core.ts
|
|
1556
1577
|
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
|
-
|
|
1578
|
+
|
|
1579
|
+
class WatchHubCore {
|
|
1580
|
+
config;
|
|
1581
|
+
sources = new Map;
|
|
1582
|
+
held = new Map;
|
|
1583
|
+
maxWatches;
|
|
1584
|
+
holdMs;
|
|
1585
|
+
deltaMemoryBytes;
|
|
1586
|
+
same;
|
|
1587
|
+
reads = 0;
|
|
1588
|
+
closed = false;
|
|
1589
|
+
constructor(config) {
|
|
1590
|
+
this.config = config;
|
|
1591
|
+
this.maxWatches = config.maxWatchesPerSubscriber ?? 64;
|
|
1592
|
+
this.holdMs = config.holdMs ?? 0;
|
|
1593
|
+
this.deltaMemoryBytes = config.deltaMemoryBytes ?? 262144;
|
|
1594
|
+
this.same = config.same ?? ((previous, next) => serializeCanonicalJson(previous) === serializeCanonicalJson(next));
|
|
1595
|
+
}
|
|
1596
|
+
tell(key, send) {
|
|
1575
1597
|
try {
|
|
1576
|
-
|
|
1598
|
+
send();
|
|
1577
1599
|
return true;
|
|
1578
1600
|
} catch (error) {
|
|
1579
|
-
config.logger?.warn?.("[stitchkit] watch subscriber threw", {
|
|
1601
|
+
this.config.logger?.warn?.("[stitchkit] watch subscriber threw", {
|
|
1580
1602
|
key: watchKeyString(key),
|
|
1581
1603
|
error: error instanceof Error ? error.message : String(error)
|
|
1582
1604
|
});
|
|
1583
1605
|
return false;
|
|
1584
1606
|
}
|
|
1585
1607
|
}
|
|
1586
|
-
|
|
1608
|
+
announceState(source, state) {
|
|
1587
1609
|
source.state = state;
|
|
1588
1610
|
for (const subscriber of source.subscribers) {
|
|
1589
|
-
tell(source.key, () => subscriber.state(state));
|
|
1611
|
+
this.tell(source.key, () => subscriber.state(state));
|
|
1590
1612
|
}
|
|
1591
1613
|
}
|
|
1592
|
-
|
|
1593
|
-
if (deltaMemoryBytes === 0)
|
|
1614
|
+
remember(source, revision, value, fingerprint) {
|
|
1615
|
+
if (this.deltaMemoryBytes === 0)
|
|
1594
1616
|
return;
|
|
1595
1617
|
const bytes = (JSON.stringify(value) ?? "null").length;
|
|
1596
|
-
if (bytes > deltaMemoryBytes)
|
|
1618
|
+
if (bytes > this.deltaMemoryBytes)
|
|
1597
1619
|
return;
|
|
1598
1620
|
source.history.set(revision, { value, fingerprint, bytes });
|
|
1599
1621
|
source.historyBytes += bytes;
|
|
1600
1622
|
for (const [oldest, entry] of source.history) {
|
|
1601
|
-
if (source.historyBytes <= deltaMemoryBytes)
|
|
1623
|
+
if (source.historyBytes <= this.deltaMemoryBytes)
|
|
1602
1624
|
break;
|
|
1603
1625
|
source.history.delete(oldest);
|
|
1604
1626
|
source.historyBytes -= entry.bytes;
|
|
1605
1627
|
}
|
|
1606
1628
|
}
|
|
1607
|
-
|
|
1629
|
+
frameFor(source, subscriber) {
|
|
1608
1630
|
const value = source.value;
|
|
1609
1631
|
const fingerprint = source.fingerprint ?? valueFingerprint(value);
|
|
1610
1632
|
const full = {
|
|
@@ -1620,10 +1642,10 @@ function createWatchHub(config) {
|
|
|
1620
1642
|
if (base === source.revision) {
|
|
1621
1643
|
return { kind: "unchanged", key: source.key, revision: source.revision, fingerprint };
|
|
1622
1644
|
}
|
|
1623
|
-
const
|
|
1624
|
-
if (!
|
|
1645
|
+
const held = source.history.get(base);
|
|
1646
|
+
if (!held)
|
|
1625
1647
|
return full;
|
|
1626
|
-
const delta = diff(
|
|
1648
|
+
const delta = diff(held.value, value);
|
|
1627
1649
|
if (delta === undefined || !deltaWins(delta, value))
|
|
1628
1650
|
return full;
|
|
1629
1651
|
return {
|
|
@@ -1635,7 +1657,7 @@ function createWatchHub(config) {
|
|
|
1635
1657
|
delta
|
|
1636
1658
|
};
|
|
1637
1659
|
}
|
|
1638
|
-
|
|
1660
|
+
adoptBaseline(source, subscriber, have) {
|
|
1639
1661
|
if (source.fingerprint === have.fingerprint) {
|
|
1640
1662
|
source.baselines.set(subscriber, source.revision);
|
|
1641
1663
|
return;
|
|
@@ -1652,56 +1674,56 @@ function createWatchHub(config) {
|
|
|
1652
1674
|
return;
|
|
1653
1675
|
}
|
|
1654
1676
|
}
|
|
1655
|
-
|
|
1656
|
-
const frame = frameFor(source, subscriber);
|
|
1657
|
-
const delivered = tell(source.key, () => subscriber.value(frame));
|
|
1677
|
+
deliver(source, subscriber) {
|
|
1678
|
+
const frame = this.frameFor(source, subscriber);
|
|
1679
|
+
const delivered = this.tell(source.key, () => subscriber.value(frame));
|
|
1658
1680
|
if (delivered)
|
|
1659
1681
|
source.baselines.set(subscriber, source.revision);
|
|
1660
1682
|
return delivered;
|
|
1661
1683
|
}
|
|
1662
|
-
|
|
1684
|
+
publish(source, value, fingerprint) {
|
|
1663
1685
|
if (source.fingerprint !== undefined) {
|
|
1664
|
-
remember(source, source.revision, source.value, source.fingerprint);
|
|
1686
|
+
this.remember(source, source.revision, source.value, source.fingerprint);
|
|
1665
1687
|
}
|
|
1666
1688
|
source.revision += 1;
|
|
1667
1689
|
source.value = value;
|
|
1668
1690
|
source.fingerprint = fingerprint;
|
|
1669
1691
|
for (const subscriber of source.subscribers)
|
|
1670
|
-
deliver(source, subscriber);
|
|
1692
|
+
this.deliver(source, subscriber);
|
|
1671
1693
|
}
|
|
1672
|
-
async
|
|
1673
|
-
if (source.reading || closed)
|
|
1694
|
+
async pump(source) {
|
|
1695
|
+
if (source.reading || this.closed)
|
|
1674
1696
|
return;
|
|
1675
1697
|
source.reading = true;
|
|
1676
1698
|
try {
|
|
1677
|
-
while (source.dirty && !closed) {
|
|
1699
|
+
while (source.dirty && !this.closed) {
|
|
1678
1700
|
source.dirty = false;
|
|
1679
|
-
reads += 1;
|
|
1701
|
+
this.reads += 1;
|
|
1680
1702
|
try {
|
|
1681
|
-
const value = await config.read(source.operation, source.args);
|
|
1703
|
+
const value = await this.config.read(source.operation, source.args);
|
|
1682
1704
|
source.backoff.reset();
|
|
1683
1705
|
const signature = serializeCanonicalJson(value);
|
|
1684
|
-
const unchanged = source.signature !== undefined && same(source.value, value);
|
|
1706
|
+
const unchanged = source.signature !== undefined && this.same(source.value, value);
|
|
1685
1707
|
source.signature = signature;
|
|
1686
1708
|
if (!unchanged)
|
|
1687
|
-
publish(source, value, valueFingerprint(value));
|
|
1709
|
+
this.publish(source, value, valueFingerprint(value));
|
|
1688
1710
|
if (source.state.phase !== "live") {
|
|
1689
|
-
announceState(source, { key: source.key, phase: "live" });
|
|
1711
|
+
this.announceState(source, { key: source.key, phase: "live" });
|
|
1690
1712
|
}
|
|
1691
1713
|
} catch (error) {
|
|
1692
1714
|
const code = readErrorCode(error);
|
|
1693
|
-
announceState(source, {
|
|
1715
|
+
this.announceState(source, {
|
|
1694
1716
|
key: source.key,
|
|
1695
1717
|
phase: "unavailable",
|
|
1696
1718
|
reason: "source-error",
|
|
1697
1719
|
...code !== undefined && { code },
|
|
1698
1720
|
message: error instanceof Error ? error.message : String(error)
|
|
1699
1721
|
});
|
|
1700
|
-
config.logger?.warn?.("[stitchkit] watched read failed", {
|
|
1722
|
+
this.config.logger?.warn?.("[stitchkit] watched read failed", {
|
|
1701
1723
|
key: watchKeyString(source.key),
|
|
1702
1724
|
error
|
|
1703
1725
|
});
|
|
1704
|
-
scheduleRetry(source);
|
|
1726
|
+
this.scheduleRetry(source);
|
|
1705
1727
|
return;
|
|
1706
1728
|
}
|
|
1707
1729
|
}
|
|
@@ -1709,19 +1731,19 @@ function createWatchHub(config) {
|
|
|
1709
1731
|
source.reading = false;
|
|
1710
1732
|
}
|
|
1711
1733
|
}
|
|
1712
|
-
|
|
1713
|
-
if (closed || source.subscribers.size === 0)
|
|
1734
|
+
scheduleRetry(source) {
|
|
1735
|
+
if (this.closed || source.subscribers.size === 0)
|
|
1714
1736
|
return;
|
|
1715
1737
|
clearTimeout(source.retry);
|
|
1716
1738
|
source.retry = setTimeout(() => {
|
|
1717
1739
|
source.dirty = true;
|
|
1718
|
-
pump(source);
|
|
1740
|
+
this.pump(source);
|
|
1719
1741
|
}, source.backoff.next());
|
|
1720
1742
|
source.retry.unref?.();
|
|
1721
1743
|
}
|
|
1722
|
-
|
|
1744
|
+
acquire(operation, key, args) {
|
|
1723
1745
|
const id = watchKeyString(key);
|
|
1724
|
-
const existing = sources.get(id);
|
|
1746
|
+
const existing = this.sources.get(id);
|
|
1725
1747
|
if (existing) {
|
|
1726
1748
|
clearTimeout(existing.release);
|
|
1727
1749
|
existing.release = undefined;
|
|
@@ -1740,18 +1762,18 @@ function createWatchHub(config) {
|
|
|
1740
1762
|
reading: false,
|
|
1741
1763
|
dirty: true,
|
|
1742
1764
|
state: { key, phase: "opening" },
|
|
1743
|
-
backoff: createBackoff(config.backoff ?? DEFAULT_BACKOFF)
|
|
1765
|
+
backoff: createBackoff(this.config.backoff ?? DEFAULT_BACKOFF)
|
|
1744
1766
|
};
|
|
1745
|
-
for (const topic of config.invalidatedBy(operation, args)) {
|
|
1746
|
-
source.unsubscribes.push(config.subscribe(topic, () => {
|
|
1767
|
+
for (const topic of this.config.invalidatedBy(operation, args)) {
|
|
1768
|
+
source.unsubscribes.push(this.config.subscribe(topic, () => {
|
|
1747
1769
|
source.dirty = true;
|
|
1748
|
-
pump(source);
|
|
1770
|
+
this.pump(source);
|
|
1749
1771
|
}));
|
|
1750
1772
|
}
|
|
1751
|
-
sources.set(id, source);
|
|
1773
|
+
this.sources.set(id, source);
|
|
1752
1774
|
return source;
|
|
1753
1775
|
}
|
|
1754
|
-
|
|
1776
|
+
release(source) {
|
|
1755
1777
|
if (source.subscribers.size > 0)
|
|
1756
1778
|
return;
|
|
1757
1779
|
clearTimeout(source.retry);
|
|
@@ -1764,109 +1786,105 @@ function createWatchHub(config) {
|
|
|
1764
1786
|
}
|
|
1765
1787
|
for (const unsubscribe of source.unsubscribes)
|
|
1766
1788
|
unsubscribe();
|
|
1767
|
-
sources.delete(watchKeyString(source.key));
|
|
1789
|
+
this.sources.delete(watchKeyString(source.key));
|
|
1768
1790
|
};
|
|
1769
|
-
if (holdMs === 0) {
|
|
1791
|
+
if (this.holdMs === 0) {
|
|
1770
1792
|
drop();
|
|
1771
1793
|
return;
|
|
1772
1794
|
}
|
|
1773
|
-
source.release = setTimeout(drop, holdMs);
|
|
1795
|
+
source.release = setTimeout(drop, this.holdMs);
|
|
1774
1796
|
source.release.unref?.();
|
|
1775
1797
|
}
|
|
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);
|
|
1798
|
+
attach(subscriber) {
|
|
1799
|
+
const keys = new Set;
|
|
1800
|
+
this.held.set(subscriber, keys);
|
|
1801
|
+
return {
|
|
1802
|
+
open: (key, args, have) => {
|
|
1803
|
+
if (this.closed)
|
|
1804
|
+
return { accepted: false, reason: "the watch hub is closed" };
|
|
1805
|
+
const operation = { service: key.service, action: key.action };
|
|
1806
|
+
if (!this.config.watchable(operation)) {
|
|
1807
|
+
return {
|
|
1808
|
+
accepted: false,
|
|
1809
|
+
reason: `${key.service}.${key.action} is not watchable`
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
const id = watchKeyString(key);
|
|
1813
|
+
if (keys.has(id))
|
|
1823
1814
|
return { accepted: true };
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1815
|
+
if (keys.size >= this.maxWatches) {
|
|
1816
|
+
return {
|
|
1817
|
+
accepted: false,
|
|
1818
|
+
reason: `this connection is already watching ${keys.size} reads (limit ${this.maxWatches})`
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
const source = this.acquire(operation, key, args);
|
|
1822
|
+
source.subscribers.add(subscriber);
|
|
1823
|
+
keys.add(id);
|
|
1824
|
+
if (have)
|
|
1825
|
+
this.adoptBaseline(source, subscriber, have);
|
|
1826
|
+
try {
|
|
1827
|
+
if (source.signature !== undefined) {
|
|
1828
|
+
const frame = this.frameFor(source, subscriber);
|
|
1829
|
+
subscriber.value(frame);
|
|
1830
|
+
source.baselines.set(subscriber, source.revision);
|
|
1831
|
+
}
|
|
1832
|
+
subscriber.state(source.state);
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
source.subscribers.delete(subscriber);
|
|
1835
|
+
source.baselines.delete(subscriber);
|
|
1836
|
+
keys.delete(id);
|
|
1837
|
+
this.release(source);
|
|
1838
|
+
return {
|
|
1839
|
+
accepted: false,
|
|
1840
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
this.pump(source);
|
|
1844
|
+
return { accepted: true };
|
|
1845
|
+
},
|
|
1846
|
+
close: (key) => {
|
|
1847
|
+
const id = watchKeyString(key);
|
|
1848
|
+
if (!keys.delete(id))
|
|
1849
|
+
return;
|
|
1850
|
+
const source = this.sources.get(id);
|
|
1851
|
+
if (!source)
|
|
1852
|
+
return;
|
|
1853
|
+
source.subscribers.delete(subscriber);
|
|
1854
|
+
source.baselines.delete(subscriber);
|
|
1855
|
+
this.release(source);
|
|
1856
|
+
},
|
|
1857
|
+
detach: () => {
|
|
1858
|
+
for (const id of keys) {
|
|
1859
|
+
const source = this.sources.get(id);
|
|
1830
1860
|
if (!source)
|
|
1831
|
-
|
|
1861
|
+
continue;
|
|
1832
1862
|
source.subscribers.delete(subscriber);
|
|
1833
1863
|
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);
|
|
1864
|
+
this.release(source);
|
|
1847
1865
|
}
|
|
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();
|
|
1866
|
+
keys.clear();
|
|
1867
|
+
this.held.delete(subscriber);
|
|
1867
1868
|
}
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
close() {
|
|
1872
|
+
this.closed = true;
|
|
1873
|
+
const teardown = [...this.sources.values()];
|
|
1874
|
+
this.sources.clear();
|
|
1875
|
+
this.held.clear();
|
|
1876
|
+
for (const source of teardown) {
|
|
1877
|
+
clearTimeout(source.retry);
|
|
1878
|
+
clearTimeout(source.release);
|
|
1879
|
+
this.announceState(source, {
|
|
1880
|
+
key: source.key,
|
|
1881
|
+
phase: "unavailable",
|
|
1882
|
+
reason: "source-unavailable"
|
|
1883
|
+
});
|
|
1884
|
+
for (const unsubscribe of source.unsubscribes)
|
|
1885
|
+
unsubscribe();
|
|
1868
1886
|
}
|
|
1869
|
-
}
|
|
1887
|
+
}
|
|
1870
1888
|
}
|
|
1871
1889
|
function valueFingerprint(value) {
|
|
1872
1890
|
return argumentsDigest({ value });
|
|
@@ -1877,6 +1895,25 @@ function readErrorCode(error) {
|
|
|
1877
1895
|
const code = Reflect.get(error, "code");
|
|
1878
1896
|
return typeof code === "string" ? code : undefined;
|
|
1879
1897
|
}
|
|
1898
|
+
|
|
1899
|
+
// src/application/watch-hub.ts
|
|
1900
|
+
function watchKey(operation, args) {
|
|
1901
|
+
const record = typeof args === "object" && args !== null && !Array.isArray(args) ? args : { value: args };
|
|
1902
|
+
return {
|
|
1903
|
+
service: operation.service,
|
|
1904
|
+
action: operation.action,
|
|
1905
|
+
digest: argumentsDigest(record)
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
function createWatchHub(config) {
|
|
1909
|
+
const hub = new WatchHubCore(config);
|
|
1910
|
+
return {
|
|
1911
|
+
attach: (subscriber) => hub.attach(subscriber),
|
|
1912
|
+
readCount: () => hub.reads,
|
|
1913
|
+
size: () => hub.sources.size,
|
|
1914
|
+
close: () => hub.close()
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1880
1917
|
export {
|
|
1881
1918
|
watchKey,
|
|
1882
1919
|
transitionProcessStart,
|