stitchkit 0.93.0 → 0.94.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 +134 -0
- package/README.md +5 -2
- package/dist/agent-runtime/children-control.d.ts +6 -0
- package/dist/agent-runtime/children-control.d.ts.map +1 -0
- package/dist/agent-runtime/children-spawn.d.ts +6 -0
- package/dist/agent-runtime/children-spawn.d.ts.map +1 -0
- package/dist/agent-runtime/children-state.d.ts +46 -0
- package/dist/agent-runtime/children-state.d.ts.map +1 -0
- package/dist/agent-runtime/children-usage.d.ts +8 -0
- package/dist/agent-runtime/children-usage.d.ts.map +1 -0
- package/dist/agent-runtime/children.d.ts +11 -14
- package/dist/agent-runtime/children.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-contract.d.ts +2 -1
- package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
- package/dist/agent-runtime/deferred-tool-types.d.ts +2 -2
- package/dist/agent-runtime/deferred-tool-types.d.ts.map +1 -1
- package/dist/agent-runtime/events.d.ts +1 -1
- package/dist/agent-runtime/events.d.ts.map +1 -1
- package/dist/agent-runtime/managed-tools.d.ts +1 -1
- package/dist/agent-runtime/managed-tools.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts +1 -1
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/{agent-runtime-openrouter.d.ts → agent-runtime/openrouter.d.ts} +3 -3
- package/dist/agent-runtime/openrouter.d.ts.map +1 -0
- package/dist/agent-runtime/projections.d.ts +1 -1
- package/dist/agent-runtime/projections.d.ts.map +1 -1
- package/dist/agent-runtime/run-acquisition.d.ts +17 -0
- package/dist/agent-runtime/run-acquisition.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution-state.d.ts +133 -0
- package/dist/agent-runtime/run-execution-state.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution.d.ts +17 -33
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/run-failure.d.ts +18 -0
- package/dist/agent-runtime/run-failure.d.ts.map +1 -0
- package/dist/agent-runtime/run-model-attempts.d.ts +6 -0
- package/dist/agent-runtime/run-model-attempts.d.ts.map +1 -0
- package/dist/agent-runtime/run-prompt-history.d.ts +58 -0
- package/dist/agent-runtime/run-prompt-history.d.ts.map +1 -0
- package/dist/agent-runtime/run-provider-ledger.d.ts +26 -0
- package/dist/agent-runtime/run-provider-ledger.d.ts.map +1 -0
- package/dist/agent-runtime/run-stream-parts.d.ts +17 -0
- package/dist/agent-runtime/run-stream-parts.d.ts.map +1 -0
- package/dist/agent-runtime/run-terminal.d.ts +11 -0
- package/dist/agent-runtime/run-terminal.d.ts.map +1 -0
- package/dist/agent-runtime/run-turn-preparation.d.ts +22 -0
- package/dist/agent-runtime/run-turn-preparation.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-admission-gate.d.ts +29 -0
- package/dist/agent-runtime/runtime-admission-gate.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-control.d.ts +9 -0
- package/dist/agent-runtime/runtime-control.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-recovery.d.ts +10 -0
- package/dist/agent-runtime/runtime-recovery.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-state.d.ts +36 -0
- package/dist/agent-runtime/runtime-state.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-submit.d.ts +5 -0
- package/dist/agent-runtime/runtime-submit.d.ts.map +1 -0
- package/dist/agent-runtime/runtime.d.ts +9 -4
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/{agent-runtime-sqlite-bun.d.ts → agent-runtime/sqlite-bun.d.ts} +5 -4
- package/dist/agent-runtime/sqlite-bun.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-events.d.ts +5 -0
- package/dist/agent-runtime/sqlite-events.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-history.d.ts +7 -0
- package/dist/agent-runtime/sqlite-history.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-ledger.d.ts +7 -0
- package/dist/agent-runtime/sqlite-ledger.d.ts.map +1 -0
- package/dist/{agent-runtime-sqlite-node.d.ts → agent-runtime/sqlite-node.d.ts} +5 -4
- package/dist/agent-runtime/sqlite-node.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-reads.d.ts +14 -0
- package/dist/agent-runtime/sqlite-reads.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-rows.d.ts +178 -0
- package/dist/agent-runtime/sqlite-rows.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-schema.d.ts +7 -0
- package/dist/agent-runtime/sqlite-schema.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-transactions.d.ts +25 -0
- package/dist/agent-runtime/sqlite-transactions.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite.d.ts +10 -6
- package/dist/agent-runtime/sqlite.d.ts.map +1 -1
- package/dist/agent-runtime/store-archive.d.ts +16 -0
- package/dist/agent-runtime/store-archive.d.ts.map +1 -0
- package/dist/{testing/agent-store-conformance.d.ts → agent-runtime/store-conformance.d.ts} +2 -2
- package/dist/agent-runtime/store-conformance.d.ts.map +1 -0
- package/dist/agent-runtime/store-create.d.ts +11 -0
- package/dist/agent-runtime/store-create.d.ts.map +1 -0
- package/dist/agent-runtime/{store-driver.d.ts → store-driver-contract.d.ts} +7 -7
- package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -0
- package/dist/agent-runtime/store-events.d.ts +7 -89
- package/dist/agent-runtime/store-events.d.ts.map +1 -1
- package/dist/agent-runtime/store-memory.d.ts +4 -0
- package/dist/agent-runtime/store-memory.d.ts.map +1 -0
- package/dist/agent-runtime/store-mutate.d.ts +13 -0
- package/dist/agent-runtime/store-mutate.d.ts.map +1 -0
- package/dist/agent-runtime/store-purge.d.ts +1 -1
- package/dist/agent-runtime/store-purge.d.ts.map +1 -1
- package/dist/agent-runtime/store-reads.d.ts +29 -0
- package/dist/agent-runtime/store-reads.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-history.d.ts +10 -0
- package/dist/agent-runtime/store-reduce-history.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-run.d.ts +14 -0
- package/dist/agent-runtime/store-reduce-run.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-shared.d.ts +76 -0
- package/dist/agent-runtime/store-reduce-shared.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-terminal.d.ts +6 -0
- package/dist/agent-runtime/store-reduce-terminal.d.ts.map +1 -0
- package/dist/agent-runtime/store-reducer.d.ts +24 -0
- package/dist/agent-runtime/store-reducer.d.ts.map +1 -0
- package/dist/agent-runtime/store-snapshot.d.ts +19 -0
- package/dist/agent-runtime/store-snapshot.d.ts.map +1 -0
- package/dist/agent-runtime/store.d.ts +1 -1
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/application/admission.d.ts +1 -1
- package/dist/application/admission.d.ts.map +1 -1
- package/dist/application/events.d.ts +1 -1
- package/dist/application/events.d.ts.map +1 -1
- package/dist/{server → application}/file-state-store.d.ts +1 -1
- package/dist/application/file-state-store.d.ts.map +1 -0
- package/dist/application/health.d.ts +1 -1
- package/dist/application/health.d.ts.map +1 -1
- package/dist/application/kernel-admission.d.ts +8 -0
- package/dist/application/kernel-admission.d.ts.map +1 -0
- package/dist/application/kernel-contract.d.ts +142 -0
- package/dist/application/kernel-contract.d.ts.map +1 -0
- package/dist/application/kernel-resource-context.d.ts +24 -0
- package/dist/application/kernel-resource-context.d.ts.map +1 -0
- package/dist/application/kernel-restart.d.ts +5 -0
- package/dist/application/kernel-restart.d.ts.map +1 -0
- package/dist/application/kernel-rollback.d.ts +4 -0
- package/dist/application/kernel-rollback.d.ts.map +1 -0
- package/dist/application/kernel-shutdown.d.ts +6 -0
- package/dist/application/kernel-shutdown.d.ts.map +1 -0
- package/dist/application/kernel-start.d.ts +20 -0
- package/dist/application/kernel-start.d.ts.map +1 -0
- package/dist/application/kernel-state.d.ts +116 -0
- package/dist/application/kernel-state.d.ts.map +1 -0
- package/dist/application/kernel.d.ts +9 -140
- package/dist/application/kernel.d.ts.map +1 -1
- package/dist/application/latest-sink.d.ts +1 -1
- package/dist/application/latest-sink.d.ts.map +1 -1
- package/dist/application/opentelemetry.d.ts +1 -1
- package/dist/application/opentelemetry.d.ts.map +1 -1
- package/dist/application/watch-hub.d.ts +2 -2
- package/dist/application/watch-hub.d.ts.map +1 -1
- package/dist/browser/client-multipart.d.ts +1 -1
- package/dist/browser/client-multipart.d.ts.map +1 -1
- package/dist/browser/client-url.d.ts +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/browser/client.d.ts +1 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http.d.ts +1 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/resumable.d.ts +1 -28
- package/dist/browser/resumable.d.ts.map +1 -1
- package/dist/browser/retained.d.ts.map +1 -0
- package/dist/browser/socket-io.d.ts +23 -1
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/browser/stream.d.ts +14 -2
- package/dist/browser/stream.d.ts.map +1 -1
- package/dist/contract/define.d.ts +72 -65
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/{internal/errors.d.ts → contract/normalize.d.ts} +3 -39
- package/dist/contract/normalize.d.ts.map +1 -0
- package/dist/contract/tool-view.d.ts +135 -0
- package/dist/contract/tool-view.d.ts.map +1 -0
- package/dist/declaration/declaration.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-contract.d.ts → durability/contract.d.ts} +2 -2
- package/dist/durability/contract.d.ts.map +1 -0
- package/dist/{agent-runtime/durability.d.ts → durability/engine.d.ts} +4 -4
- package/dist/durability/engine.d.ts.map +1 -0
- package/dist/durability/events.d.ts +101 -0
- package/dist/durability/events.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-json.d.ts → durability/json.d.ts} +1 -1
- package/dist/durability/json.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-ledger.d.ts → durability/ledger.d.ts} +2 -2
- package/dist/durability/ledger.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-scheduler.d.ts → durability/scheduler.d.ts} +2 -2
- package/dist/durability/scheduler.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/browser.d.ts +5 -0
- package/dist/entrypoints/agent-runtime/browser.d.ts.map +1 -0
- package/dist/{agent-runtime-browser.js → entrypoints/agent-runtime/browser.js} +3 -3
- package/dist/{agent-runtime-coding-tools.d.ts → entrypoints/agent-runtime/coding-tools.d.ts} +2 -2
- package/dist/entrypoints/agent-runtime/coding-tools.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/coding-tools.js +22 -0
- package/dist/{agent-runtime-harness.d.ts → entrypoints/agent-runtime/harness.d.ts} +2 -2
- package/dist/entrypoints/agent-runtime/harness.d.ts.map +1 -0
- package/dist/{agent-runtime-harness.js → entrypoints/agent-runtime/harness.js} +24 -25
- package/dist/entrypoints/agent-runtime/openrouter.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/openrouter.d.ts.map +1 -0
- package/dist/{agent-runtime-openrouter.js → entrypoints/agent-runtime/openrouter.js} +3 -3
- package/dist/{agent-runtime-sandbox.d.ts → entrypoints/agent-runtime/sandbox.d.ts} +5 -5
- package/dist/entrypoints/agent-runtime/sandbox.d.ts.map +1 -0
- package/dist/{agent-runtime-sandbox.js → entrypoints/agent-runtime/sandbox.js} +11 -12
- package/dist/entrypoints/agent-runtime/sqlite/bun.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/sqlite/bun.d.ts.map +1 -0
- package/dist/{agent-runtime-sqlite-bun.js → entrypoints/agent-runtime/sqlite/bun.js} +8 -6
- package/dist/entrypoints/agent-runtime/sqlite/node.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/sqlite/node.d.ts.map +1 -0
- package/dist/{agent-runtime-sqlite-node.js → entrypoints/agent-runtime/sqlite/node.js} +8 -6
- package/dist/entrypoints/agent-runtime/testing.d.ts +4 -0
- package/dist/entrypoints/agent-runtime/testing.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/testing.js +1084 -0
- package/dist/{agent-runtime.d.ts → entrypoints/agent-runtime.d.ts} +41 -37
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -0
- package/dist/{agent-runtime.js → entrypoints/agent-runtime.js} +408 -366
- package/dist/{application-diagnostic-journal.d.ts → entrypoints/application/diagnostic-journal.d.ts} +2 -2
- package/dist/entrypoints/application/diagnostic-journal.d.ts.map +1 -0
- package/dist/{application-diagnostic-journal.js → entrypoints/application/diagnostic-journal.js} +3 -3
- package/dist/{application-grammy.d.ts → entrypoints/application/grammy.d.ts} +2 -2
- package/dist/entrypoints/application/grammy.d.ts.map +1 -0
- package/dist/{application-grammy.js → entrypoints/application/grammy.js} +2 -2
- package/dist/{application-opentelemetry.d.ts → entrypoints/application/opentelemetry.d.ts} +2 -2
- package/dist/entrypoints/application/opentelemetry.d.ts.map +1 -0
- package/dist/{application-opentelemetry.js → entrypoints/application/opentelemetry.js} +4 -4
- package/dist/{application-schemas.d.ts → entrypoints/application/schemas.d.ts} +2 -2
- package/dist/entrypoints/application/schemas.d.ts.map +1 -0
- package/dist/{application-schemas.js → entrypoints/application/schemas.js} +2 -2
- package/dist/{application.d.ts → entrypoints/application.d.ts} +22 -21
- package/dist/entrypoints/application.d.ts.map +1 -0
- package/dist/{application.js → entrypoints/application.js} +24 -22
- package/dist/entrypoints/bin/upgrade-cli.d.ts.map +1 -0
- package/dist/{upgrade-cli.js → entrypoints/bin/upgrade-cli.js} +3 -3
- package/dist/{cli.d.ts → entrypoints/cli.d.ts} +15 -15
- package/dist/entrypoints/cli.d.ts.map +1 -0
- package/dist/{cli.js → entrypoints/cli.js} +30 -32
- package/dist/entrypoints/contract.d.ts +9 -0
- package/dist/entrypoints/contract.d.ts.map +1 -0
- package/dist/{contract/index.js → entrypoints/contract.js} +14 -6
- package/dist/entrypoints/declaration.d.ts +2 -0
- package/dist/entrypoints/declaration.d.ts.map +1 -0
- package/dist/{declaration.js → entrypoints/declaration.js} +1 -1
- package/dist/{files.d.ts → entrypoints/files.d.ts} +2 -2
- package/dist/entrypoints/files.d.ts.map +1 -0
- package/dist/{files.js → entrypoints/files.js} +3 -3
- package/dist/{geo.d.ts → entrypoints/geo.d.ts} +1 -1
- package/dist/entrypoints/geo.d.ts.map +1 -0
- package/dist/{geo.js → entrypoints/geo.js} +4 -4
- package/dist/entrypoints/google.d.ts +2 -0
- package/dist/entrypoints/google.d.ts.map +1 -0
- package/dist/{google.js → entrypoints/google.js} +1 -1
- package/dist/{index.d.ts → entrypoints/index.d.ts} +14 -13
- package/dist/entrypoints/index.d.ts.map +1 -0
- package/dist/{index.js → entrypoints/index.js} +99 -33
- package/dist/{live.d.ts → entrypoints/live.d.ts} +4 -4
- package/dist/entrypoints/live.d.ts.map +1 -0
- package/dist/{live.js → entrypoints/live.js} +6 -4
- package/dist/{node.d.ts → entrypoints/node.d.ts} +11 -11
- package/dist/entrypoints/node.d.ts.map +1 -0
- package/dist/{node.js → entrypoints/node.js} +20 -16
- package/dist/entrypoints/oauth.d.ts +2 -0
- package/dist/entrypoints/oauth.d.ts.map +1 -0
- package/dist/{oauth.js → entrypoints/oauth.js} +3 -3
- package/dist/{observability/index.d.ts → entrypoints/observability.d.ts} +11 -11
- package/dist/entrypoints/observability.d.ts.map +1 -0
- package/dist/{observability/index.js → entrypoints/observability.js} +9 -11
- package/dist/entrypoints/primitives.d.ts +3 -0
- package/dist/entrypoints/primitives.d.ts.map +1 -0
- package/dist/{primitives.js → entrypoints/primitives.js} +1 -6
- package/dist/{react.d.ts → entrypoints/react.d.ts} +4 -4
- package/dist/entrypoints/react.d.ts.map +1 -0
- package/dist/{react.js → entrypoints/react.js} +3 -3
- package/dist/{release.d.ts → entrypoints/release.d.ts} +4 -4
- package/dist/entrypoints/release.d.ts.map +1 -0
- package/dist/{release.js → entrypoints/release.js} +1 -1
- package/dist/{remote.d.ts → entrypoints/remote.d.ts} +1 -1
- package/dist/entrypoints/remote.d.ts.map +1 -0
- package/dist/entrypoints/remote.js +66 -0
- package/dist/{server/index.d.ts → entrypoints/server.d.ts} +35 -34
- package/dist/entrypoints/server.d.ts.map +1 -0
- package/dist/{server/index.js → entrypoints/server.js} +294 -244
- package/dist/{telegram.d.ts → entrypoints/telegram.d.ts} +2 -2
- package/dist/entrypoints/telegram.d.ts.map +1 -0
- package/dist/{telegram.js → entrypoints/telegram.js} +1 -1
- package/dist/entrypoints/testing.d.ts +9 -0
- package/dist/entrypoints/testing.d.ts.map +1 -0
- package/dist/{testing.js → entrypoints/testing.js} +80 -919
- package/dist/{tools/connections/index.d.ts → entrypoints/tools/connections.d.ts} +6 -7
- package/dist/entrypoints/tools/connections.d.ts.map +1 -0
- package/dist/{tools/connections/index.js → entrypoints/tools/connections.js} +7 -4
- package/dist/{tools-contract.d.ts → entrypoints/tools/contract.d.ts} +5 -5
- package/dist/entrypoints/tools/contract.d.ts.map +1 -0
- package/dist/{tools-contract.js → entrypoints/tools/contract.js} +4 -4
- package/dist/{tool-invoker.d.ts → entrypoints/tools/invoker.d.ts} +2 -2
- package/dist/entrypoints/tools/invoker.d.ts.map +1 -0
- package/dist/entrypoints/tools/invoker.js +20 -0
- package/dist/entrypoints/tools.d.ts +51 -0
- package/dist/entrypoints/tools.d.ts.map +1 -0
- package/dist/{tools.js → entrypoints/tools.js} +1787 -1751
- package/dist/{tracking-server.d.ts → entrypoints/tracking/server.d.ts} +8 -8
- package/dist/entrypoints/tracking/server.d.ts.map +1 -0
- package/dist/{tracking.d.ts → entrypoints/tracking.d.ts} +15 -15
- package/dist/entrypoints/tracking.d.ts.map +1 -0
- package/dist/{tracking.js → entrypoints/tracking.js} +5 -8
- package/dist/google/google.d.ts.map +1 -0
- package/dist/{index-a916km3r.js → index-0b1ess5e.js} +6 -13
- package/dist/{index-zwkgfq1s.js → index-1nd48tm9.js} +113 -118
- package/dist/{index-7zbps32p.js → index-1zbbzgfh.js} +2 -2
- package/dist/{index-mzvgxp0n.js → index-2cr1cqhn.js} +16 -4
- package/dist/{index-f9mb610r.js → index-35mh5bhj.js} +2 -2
- package/dist/index-48ffdxgk.js +6 -0
- package/dist/index-58k4fp34.js +1023 -0
- package/dist/{index-p13mwz16.js → index-83bdg97s.js} +1 -1
- package/dist/index-97ac4gph.js +3618 -0
- package/dist/{index-kw9t5ayn.js → index-cq4qfycf.js} +5 -5
- package/dist/{index-7gnkny4z.js → index-d9pk3td4.js} +1 -1
- package/dist/{index-e42wzzz5.js → index-dcpdvjsc.js} +7 -7
- package/dist/{index-h9etkkyk.js → index-dydjcp8z.js} +102 -25
- package/dist/{index-sr93cwf9.js → index-e2z3ahw9.js} +2 -2
- package/dist/{index-yewwh1w4.js → index-e87kakm4.js} +96 -11
- package/dist/{index-cby4ar3v.js → index-eqqvgdb3.js} +1 -1
- package/dist/index-fdp6hdxn.js +942 -0
- package/dist/{index-0a349c33.js → index-gyr48nzg.js} +125 -198
- package/dist/{index-wnfk50r0.js → index-h6eeys1v.js} +1 -1
- package/dist/{index-77fekveh.js → index-hffjpmq9.js} +1 -15
- package/dist/{index-yn8remb1.js → index-j05z5gn4.js} +50 -14
- package/dist/index-k5hsfc3j.js +31 -0
- package/dist/{index-fq77yfzc.js → index-kj2wbeta.js} +1 -1
- package/dist/index-kvj7f4hq.js +39 -0
- package/dist/{index-4fpa9dy2.js → index-m9zn6cpq.js} +2 -2
- package/dist/{index-2na0qrfn.js → index-mdzh02yc.js} +7 -7
- package/dist/{index-ywd0y6m1.js → index-mkm15s49.js} +1 -1
- package/dist/index-mp5sn43f.js +1084 -0
- package/dist/{index-3bmnp41g.js → index-n6hxg8as.js} +507 -419
- package/dist/index-ngfhvfpd.js +30 -0
- package/dist/{index-e0znv5gn.js → index-p5r7qpdd.js} +2 -93
- package/dist/{index-n0f51dfn.js → index-p81267c2.js} +608 -549
- package/dist/{index-wc4x00bn.js → index-qwhynv6x.js} +1 -1
- package/dist/index-r7nmqpsg.js +154 -0
- package/dist/{index-bx75zwt7.js → index-rewkc2hj.js} +5 -4
- package/dist/{index-9f8ztey6.js → index-sees8yf8.js} +2 -2
- package/dist/{index-vjpvjqpn.js → index-v38qyerb.js} +16 -2
- package/dist/{index-2c2bjw3s.js → index-vcvkq30w.js} +3 -3
- package/dist/index-wqgjfstn.js +18 -0
- package/dist/{index-f477k430.js → index-x4799c19.js} +1 -1
- package/dist/{index-cxqhvq21.js → index-xnj9bgxe.js} +142 -129
- package/dist/index-xsqjkha9.js +281 -0
- package/dist/{index-6kyksz9y.js → index-yw4fzmdz.js} +1 -1
- package/dist/{index-3xwxfj4z.js → index-zeemqhqx.js} +1 -1
- package/dist/{index-tmwcrptx.js → index-zfrhfn82.js} +1 -1
- package/dist/index-znfs20ss.js +98 -0
- package/dist/internal/backoff.d.ts +29 -0
- package/dist/internal/backoff.d.ts.map +1 -0
- package/dist/internal/canonical-json.d.ts +21 -11
- package/dist/internal/canonical-json.d.ts.map +1 -1
- package/dist/internal/channel.d.ts.map +1 -0
- package/dist/internal/logger.d.ts.map +1 -0
- package/dist/internal/request.d.ts.map +1 -0
- package/dist/internal/stable-digest.d.ts +0 -43
- package/dist/internal/stable-digest.d.ts.map +1 -1
- package/dist/internal/typed.d.ts +0 -8
- package/dist/internal/typed.d.ts.map +1 -1
- package/dist/internal/zod-issues.d.ts +38 -0
- package/dist/internal/zod-issues.d.ts.map +1 -0
- package/dist/{tools/json-schema-dialect.d.ts → json-schema/dialect.d.ts} +1 -1
- package/dist/json-schema/dialect.d.ts.map +1 -0
- package/dist/json-schema/json-schema.d.ts.map +1 -0
- package/dist/live/watch-delta.d.ts.map +1 -1
- package/dist/{oauth.d.ts → oauth/authorization-code.d.ts} +1 -1
- package/dist/oauth/authorization-code.d.ts.map +1 -0
- package/dist/observability/audit.d.ts +2 -2
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/bounded-logger.d.ts +1 -1
- package/dist/observability/bounded-logger.d.ts.map +1 -1
- package/dist/{internal/observability-sink.d.ts → observability/bounded-sink.d.ts} +2 -2
- package/dist/observability/bounded-sink.d.ts.map +1 -0
- package/dist/observability/context.d.ts +1 -1
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/event.d.ts +1 -1
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/observability/work.d.ts +1 -1
- package/dist/observability/work.d.ts.map +1 -1
- package/dist/primitives/audit.d.ts +1 -1
- package/dist/primitives/audit.d.ts.map +1 -1
- package/dist/primitives/export-operation.d.ts +4 -2
- package/dist/primitives/export-operation.d.ts.map +1 -1
- package/dist/realtime/socket.d.ts +1 -1
- package/dist/realtime/socket.d.ts.map +1 -1
- package/dist/server/context-contribution.d.ts +1 -1
- package/dist/server/context-contribution.d.ts.map +1 -1
- package/dist/server/context.d.ts +2 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract-method.d.ts +15 -0
- package/dist/server/contract-method.d.ts.map +1 -0
- package/dist/server/contract-stream.d.ts +1 -1
- package/dist/server/contract-stream.d.ts.map +1 -1
- package/dist/server/create.d.ts +8 -0
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/endpoint-dispatch.d.ts +10 -0
- package/dist/server/endpoint-dispatch.d.ts.map +1 -0
- package/dist/server/endpoint-response.d.ts +18 -0
- package/dist/server/endpoint-response.d.ts.map +1 -0
- package/dist/server/error-dispatch.d.ts +17 -1
- package/dist/server/error-dispatch.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +2 -2
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/handler-state.d.ts +44 -0
- package/dist/server/handler-state.d.ts.map +1 -0
- package/dist/server/implement.d.ts +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +1 -1
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts +17 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/middleware/trust-fence.d.ts +1 -1
- package/dist/server/middleware/trust-fence.d.ts.map +1 -1
- package/dist/server/multipart.d.ts +1 -1
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/{tools/oauth-metadata.d.ts → server/oauth/metadata.d.ts} +2 -2
- package/dist/server/oauth/metadata.d.ts.map +1 -0
- package/dist/{tools/oauth-provider.d.ts → server/oauth/provider.d.ts} +2 -2
- package/dist/server/oauth/provider.d.ts.map +1 -0
- package/dist/server/realtime.d.ts +1 -1
- package/dist/server/realtime.d.ts.map +1 -1
- package/dist/server/request-completion.d.ts +16 -0
- package/dist/server/request-completion.d.ts.map +1 -0
- package/dist/server/response-metadata.d.ts +1 -1
- package/dist/server/response-metadata.d.ts.map +1 -1
- package/dist/server/route-table.d.ts +9 -0
- package/dist/server/route-table.d.ts.map +1 -0
- package/dist/server/stream.d.ts +0 -13
- package/dist/server/stream.d.ts.map +1 -1
- package/dist/server/types.d.ts +7 -4
- package/dist/server/types.d.ts.map +1 -1
- package/dist/testing/handler-clients.d.ts +31 -0
- package/dist/testing/handler-clients.d.ts.map +1 -0
- package/dist/testing/managed-resource-conformance-scenarios.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +2 -1
- package/dist/testing/surface-conformance.d.ts.map +1 -1
- package/dist/testing/surface-manifest.d.ts +30 -4
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/tools/{cli-args.d.ts → cli/args.d.ts} +1 -1
- package/dist/tools/cli/args.d.ts.map +1 -0
- package/dist/tools/{cli-checkpoint.d.ts → cli/checkpoint.d.ts} +1 -1
- package/dist/tools/cli/checkpoint.d.ts.map +1 -0
- package/dist/tools/{cli-command.d.ts → cli/command.d.ts} +4 -4
- package/dist/tools/cli/command.d.ts.map +1 -0
- package/dist/tools/{cli.d.ts → cli/config.d.ts} +5 -9
- package/dist/tools/cli/config.d.ts.map +1 -0
- package/dist/tools/cli/create-cli.d.ts +36 -0
- package/dist/tools/cli/create-cli.d.ts.map +1 -0
- package/dist/tools/cli/download.d.ts +8 -0
- package/dist/tools/cli/download.d.ts.map +1 -0
- package/dist/tools/{cli-format.d.ts → cli/format.d.ts} +2 -2
- package/dist/tools/cli/format.d.ts.map +1 -0
- package/dist/tools/cli/help.d.ts +24 -0
- package/dist/tools/cli/help.d.ts.map +1 -0
- package/dist/tools/{cli-installer.d.ts → cli/installer.d.ts} +2 -2
- package/dist/tools/cli/installer.d.ts.map +1 -0
- package/dist/tools/{cli-invoke.d.ts → cli/invoke.d.ts} +8 -8
- package/dist/tools/cli/invoke.d.ts.map +1 -0
- package/dist/tools/cli/io.d.ts +10 -0
- package/dist/tools/cli/io.d.ts.map +1 -0
- package/dist/tools/cli/managed-surface.d.ts +30 -0
- package/dist/tools/cli/managed-surface.d.ts.map +1 -0
- package/dist/tools/{cli-manifest.d.ts → cli/manifest.d.ts} +1 -1
- package/dist/tools/cli/manifest.d.ts.map +1 -0
- package/dist/tools/{cli-policy.d.ts → cli/policy.d.ts} +2 -2
- package/dist/tools/cli/policy.d.ts.map +1 -0
- package/dist/tools/cli/prepare.d.ts +14 -0
- package/dist/tools/cli/prepare.d.ts.map +1 -0
- package/dist/tools/{cli-profile.d.ts → cli/profile.d.ts} +2 -2
- package/dist/tools/cli/profile.d.ts.map +1 -0
- package/dist/tools/cli/run-managed.d.ts +5 -0
- package/dist/tools/cli/run-managed.d.ts.map +1 -0
- package/dist/tools/cli/run-native.d.ts +9 -0
- package/dist/tools/cli/run-native.d.ts.map +1 -0
- package/dist/tools/cli/session.d.ts +40 -0
- package/dist/tools/cli/session.d.ts.map +1 -0
- package/dist/tools/{cli-signature.d.ts → cli/signature.d.ts} +2 -2
- package/dist/tools/cli/signature.d.ts.map +1 -0
- package/dist/tools/{cli-stream.d.ts → cli/stream.d.ts} +4 -4
- package/dist/tools/cli/stream.d.ts.map +1 -0
- package/dist/tools/{cli-update.d.ts → cli/update.d.ts} +3 -3
- package/dist/tools/cli/update.d.ts.map +1 -0
- package/dist/tools/{cli-view.d.ts → cli/view.d.ts} +2 -2
- package/dist/tools/cli/view.d.ts.map +1 -0
- package/dist/tools/{cli-wait.d.ts → cli/wait.d.ts} +2 -2
- package/dist/tools/cli/wait.d.ts.map +1 -0
- package/dist/tools/connections/runtime.d.ts +1 -1
- package/dist/tools/connections/runtime.d.ts.map +1 -1
- package/dist/tools/connections/types.d.ts +3 -3
- package/dist/tools/connections/types.d.ts.map +1 -1
- package/dist/tools/durability-context.d.ts +1 -1
- package/dist/tools/durability-context.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +30 -3
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +8 -6
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/internal/tool-view.d.ts +41 -0
- package/dist/tools/internal/tool-view.d.ts.map +1 -0
- package/dist/tools/invoker.d.ts +11 -3
- package/dist/tools/invoker.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +3 -3
- package/dist/tools/list-names.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts +2 -2
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/{mcp-app.d.ts → mcp/app.d.ts} +1 -1
- package/dist/tools/mcp/app.d.ts.map +1 -0
- package/dist/tools/{mcp-catalog.d.ts → mcp/catalog.d.ts} +2 -2
- package/dist/tools/mcp/catalog.d.ts.map +1 -0
- package/dist/tools/{mcp-handler.d.ts → mcp/handler.d.ts} +5 -5
- package/dist/tools/mcp/handler.d.ts.map +1 -0
- package/dist/tools/{mcp.d.ts → mcp/mount.d.ts} +9 -9
- package/dist/tools/mcp/mount.d.ts.map +1 -0
- package/dist/tools/{mcp-prepare.d.ts → mcp/prepare.d.ts} +9 -9
- package/dist/tools/mcp/prepare.d.ts.map +1 -0
- package/dist/tools/{mcp-progress.d.ts → mcp/progress.d.ts} +2 -2
- package/dist/tools/mcp/progress.d.ts.map +1 -0
- package/dist/tools/mcp/register.d.ts +35 -0
- package/dist/tools/mcp/register.d.ts.map +1 -0
- package/dist/tools/{mcp-round-policy.d.ts → mcp/round-policy.d.ts} +2 -2
- package/dist/tools/mcp/round-policy.d.ts.map +1 -0
- package/dist/tools/{mcp-round.d.ts → mcp/round.d.ts} +4 -4
- package/dist/tools/mcp/round.d.ts.map +1 -0
- package/dist/tools/mcp/runtime-tools.d.ts +24 -0
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -0
- package/dist/tools/{mcp-stdio-signals.d.ts → mcp/stdio-signals.d.ts} +3 -3
- package/dist/tools/mcp/stdio-signals.d.ts.map +1 -0
- package/dist/tools/{mcp-stdio.d.ts → mcp/stdio.d.ts} +4 -4
- package/dist/tools/mcp/stdio.d.ts.map +1 -0
- package/dist/tools/{mcp-trace.d.ts → mcp/trace.d.ts} +1 -1
- package/dist/tools/mcp/trace.d.ts.map +1 -0
- package/dist/tools/mount.d.ts +12 -7
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/native-definition.d.ts +3 -3
- package/dist/tools/native-definition.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-binding-types.d.ts +127 -0
- package/dist/tools/operations/async-operation-binding-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-binding.d.ts +28 -0
- package/dist/tools/operations/async-operation-binding.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-canonical-types.d.ts +110 -0
- package/dist/tools/operations/async-operation-canonical-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-canonical.d.ts +7 -0
- package/dist/tools/operations/async-operation-canonical.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-contract.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-id.d.ts +23 -0
- package/dist/tools/operations/async-operation-id.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime-tools.d.ts +40 -0
- package/dist/tools/operations/async-operation-runtime-tools.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime-types.d.ts +68 -0
- package/dist/tools/operations/async-operation-runtime-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime.d.ts +5 -0
- package/dist/tools/operations/async-operation-runtime.d.ts.map +1 -0
- package/dist/tools/{define-wait-tool.d.ts → operations/define-wait-tool.d.ts} +2 -2
- package/dist/tools/operations/define-wait-tool.d.ts.map +1 -0
- package/dist/tools/operations/mount-wait.d.ts.map +1 -0
- package/dist/tools/operations/wait-core.d.ts.map +1 -0
- package/dist/tools/remote.d.ts +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +11 -4
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/schema/coerce.d.ts.map +1 -0
- package/dist/tools/schema/flatten-join.d.ts.map +1 -0
- package/dist/tools/schema/flatten.d.ts.map +1 -0
- package/dist/tools/schema/portable-formats.d.ts.map +1 -0
- package/dist/tools/{presentation.d.ts → schema/presentation.d.ts} +2 -0
- package/dist/tools/schema/presentation.d.ts.map +1 -0
- package/dist/tools/schema/schema.d.ts.map +1 -0
- package/dist/tools/schema/untyped-properties.d.ts.map +1 -0
- package/dist/tools/surface.d.ts +2 -3
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/toolkit.d.ts +4 -4
- package/dist/tools/toolkit.d.ts.map +1 -1
- package/dist/tools/{define-download-tool.d.ts → transfer/define-download-tool.d.ts} +4 -4
- package/dist/tools/transfer/define-download-tool.d.ts.map +1 -0
- package/dist/tools/{define-upload-tool.d.ts → transfer/define-upload-tool.d.ts} +3 -3
- package/dist/tools/transfer/define-upload-tool.d.ts.map +1 -0
- package/dist/tools/{define-view-file-tool.d.ts → transfer/define-view-file-tool.d.ts} +2 -2
- package/dist/tools/transfer/define-view-file-tool.d.ts.map +1 -0
- package/dist/tools/{download-core.d.ts → transfer/download-core.d.ts} +2 -2
- package/dist/tools/transfer/download-core.d.ts.map +1 -0
- package/dist/tools/{managed-file-error.d.ts → transfer/managed-file-error.d.ts} +1 -1
- package/dist/tools/transfer/managed-file-error.d.ts.map +1 -0
- package/dist/tools/{mount-download.d.ts → transfer/mount-download.d.ts} +1 -1
- package/dist/tools/transfer/mount-download.d.ts.map +1 -0
- package/dist/tools/{mount-upload.d.ts → transfer/mount-upload.d.ts} +1 -1
- package/dist/tools/transfer/mount-upload.d.ts.map +1 -0
- package/dist/tools/transfer/native-result.d.ts.map +1 -0
- package/dist/tools/{upload-core.d.ts → transfer/upload-core.d.ts} +1 -1
- package/dist/tools/transfer/upload-core.d.ts.map +1 -0
- package/dist/tools/transfer/view-file-contract.d.ts.map +1 -0
- package/dist/tools/{view-file.d.ts → transfer/view-file.d.ts} +1 -1
- package/dist/tools/transfer/view-file.d.ts.map +1 -0
- package/dist/tools/transports.d.ts +1 -1
- package/dist/tools/transports.d.ts.map +1 -1
- package/dist/tracking/contract.d.ts +1 -1
- package/dist/tracking/contract.d.ts.map +1 -1
- package/llms/agent-runtime-browser.txt +33 -0
- package/llms/agent-runtime-coding-tools.txt +58 -0
- package/llms/agent-runtime-harness.txt +45 -0
- package/llms/agent-runtime-openrouter.txt +22 -0
- package/llms/agent-runtime-sandbox.txt +159 -0
- package/llms/agent-runtime-sqlite-bun.txt +29 -0
- package/llms/agent-runtime-sqlite-node.txt +26 -0
- package/llms/agent-runtime-testing.txt +30 -0
- package/llms/agent-runtime.2.txt +550 -0
- package/llms/agent-runtime.3.txt +311 -0
- package/llms/agent-runtime.txt +893 -0
- package/llms/application-diagnostic-journal.txt +30 -0
- package/llms/application-grammy.txt +27 -0
- package/llms/application-opentelemetry.txt +27 -0
- package/llms/application-schemas.txt +42 -0
- package/llms/application.2.txt +223 -0
- package/llms/application.txt +1144 -0
- package/llms/cli.txt +977 -0
- package/llms/contract.txt +606 -0
- package/llms/declaration.txt +286 -0
- package/llms/files.txt +30 -0
- package/llms/geo.txt +114 -0
- package/llms/google.txt +27 -0
- package/llms/live.txt +471 -0
- package/llms/node.txt +43 -0
- package/llms/oauth.txt +136 -0
- package/llms/observability.txt +893 -0
- package/llms/primitives.txt +158 -0
- package/llms/react.txt +184 -0
- package/llms/release.txt +176 -0
- package/llms/remote.txt +25 -0
- package/llms/server.2.txt +361 -0
- package/llms/server.3.txt +1031 -0
- package/llms/server.4.txt +905 -0
- package/llms/server.5.txt +250 -0
- package/llms/server.txt +996 -0
- package/llms/stitchkit.2.txt +836 -0
- package/llms/stitchkit.3.txt +147 -0
- package/llms/stitchkit.txt +260 -0
- package/llms/telegram.txt +30 -0
- package/llms/testing.txt +676 -0
- package/llms/tools-connections.txt +45 -0
- package/llms/tools-contract.txt +9 -0
- package/llms/tools-invoker.txt +9 -0
- package/llms/tools.2.txt +838 -0
- package/llms/tools.txt +1004 -0
- package/llms/tracking-server.txt +32 -0
- package/llms/tracking.txt +263 -0
- package/llms/upgrading-0.37.0-0.50.0.txt +947 -0
- package/llms/upgrading-0.53.0-0.67.0.txt +1174 -0
- package/llms/upgrading-0.68.0-0.94.0.txt +1159 -0
- package/llms/upgrading.txt +215 -0
- package/llms-full.txt +214 -75
- package/llms.txt +54 -5
- package/package.json +84 -83
- package/dist/agent-runtime/durability-contract.d.ts.map +0 -1
- package/dist/agent-runtime/durability-json.d.ts.map +0 -1
- package/dist/agent-runtime/durability-ledger.d.ts.map +0 -1
- package/dist/agent-runtime/durability-scheduler.d.ts.map +0 -1
- package/dist/agent-runtime/durability.d.ts.map +0 -1
- package/dist/agent-runtime/store-driver.d.ts.map +0 -1
- package/dist/agent-runtime-browser.d.ts +0 -5
- package/dist/agent-runtime-browser.d.ts.map +0 -1
- package/dist/agent-runtime-coding-tools.d.ts.map +0 -1
- package/dist/agent-runtime-coding-tools.js +0 -23
- package/dist/agent-runtime-harness.d.ts.map +0 -1
- package/dist/agent-runtime-openrouter.d.ts.map +0 -1
- package/dist/agent-runtime-sandbox.d.ts.map +0 -1
- package/dist/agent-runtime-sqlite-bun.d.ts.map +0 -1
- package/dist/agent-runtime-sqlite-node.d.ts.map +0 -1
- package/dist/agent-runtime-testing.d.ts +0 -3
- package/dist/agent-runtime-testing.d.ts.map +0 -1
- package/dist/agent-runtime-testing.js +0 -156
- package/dist/agent-runtime.d.ts.map +0 -1
- package/dist/application/channel.d.ts.map +0 -1
- package/dist/application-diagnostic-journal.d.ts.map +0 -1
- package/dist/application-grammy.d.ts.map +0 -1
- package/dist/application-opentelemetry.d.ts.map +0 -1
- package/dist/application-schemas.d.ts.map +0 -1
- package/dist/application.d.ts.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/contract/index.d.ts +0 -8
- package/dist/contract/index.d.ts.map +0 -1
- package/dist/declaration.d.ts.map +0 -1
- package/dist/files.d.ts.map +0 -1
- package/dist/geo.d.ts.map +0 -1
- package/dist/google.d.ts.map +0 -1
- package/dist/index-0j3vcp5m.js +0 -28
- package/dist/index-a11g2kry.js +0 -866
- package/dist/index-cnyk6te3.js +0 -77
- package/dist/index-fadnxwna.js +0 -1046
- package/dist/index-hrbajay7.js +0 -1196
- package/dist/index-s8cnx02s.js +0 -90
- package/dist/index-v5573g1k.js +0 -142
- package/dist/index-vk3kq9sb.js +0 -3344
- package/dist/index.d.ts.map +0 -1
- package/dist/internal/errors.d.ts.map +0 -1
- package/dist/internal/observability-sink.d.ts.map +0 -1
- package/dist/live.d.ts.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/oauth.d.ts.map +0 -1
- package/dist/observability/index.d.ts.map +0 -1
- package/dist/primitives.d.ts +0 -3
- package/dist/primitives.d.ts.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/release.d.ts.map +0 -1
- package/dist/remote.d.ts.map +0 -1
- package/dist/remote.js +0 -88
- package/dist/retained.d.ts.map +0 -1
- package/dist/server/file-state-store.d.ts.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/request.d.ts.map +0 -1
- package/dist/telegram.d.ts.map +0 -1
- package/dist/testing/agent-store-conformance.d.ts.map +0 -1
- package/dist/testing.d.ts +0 -39
- package/dist/testing.d.ts.map +0 -1
- package/dist/tool-invoker.d.ts.map +0 -1
- package/dist/tool-invoker.js +0 -21
- package/dist/tools/async-operation-contract.d.ts.map +0 -1
- package/dist/tools/async-operation.d.ts +0 -332
- package/dist/tools/async-operation.d.ts.map +0 -1
- package/dist/tools/cli-args.d.ts.map +0 -1
- package/dist/tools/cli-checkpoint.d.ts.map +0 -1
- package/dist/tools/cli-command.d.ts.map +0 -1
- package/dist/tools/cli-format.d.ts.map +0 -1
- package/dist/tools/cli-installer.d.ts.map +0 -1
- package/dist/tools/cli-invoke.d.ts.map +0 -1
- package/dist/tools/cli-manifest.d.ts.map +0 -1
- package/dist/tools/cli-policy.d.ts.map +0 -1
- package/dist/tools/cli-profile.d.ts.map +0 -1
- package/dist/tools/cli-signature.d.ts.map +0 -1
- package/dist/tools/cli-stream.d.ts.map +0 -1
- package/dist/tools/cli-update.d.ts.map +0 -1
- package/dist/tools/cli-view.d.ts.map +0 -1
- package/dist/tools/cli-wait.d.ts.map +0 -1
- package/dist/tools/cli.d.ts.map +0 -1
- package/dist/tools/coerce.d.ts.map +0 -1
- package/dist/tools/connections/index.d.ts.map +0 -1
- package/dist/tools/define-download-tool.d.ts.map +0 -1
- package/dist/tools/define-upload-tool.d.ts.map +0 -1
- package/dist/tools/define-view-file-tool.d.ts.map +0 -1
- package/dist/tools/define-wait-tool.d.ts.map +0 -1
- package/dist/tools/download-core.d.ts.map +0 -1
- package/dist/tools/flatten-join.d.ts.map +0 -1
- package/dist/tools/flatten.d.ts.map +0 -1
- package/dist/tools/json-schema-dialect.d.ts.map +0 -1
- package/dist/tools/json-schema.d.ts.map +0 -1
- package/dist/tools/managed-file-error.d.ts.map +0 -1
- package/dist/tools/mcp-app.d.ts.map +0 -1
- package/dist/tools/mcp-catalog.d.ts.map +0 -1
- package/dist/tools/mcp-handler.d.ts.map +0 -1
- package/dist/tools/mcp-prepare.d.ts.map +0 -1
- package/dist/tools/mcp-progress.d.ts.map +0 -1
- package/dist/tools/mcp-round-policy.d.ts.map +0 -1
- package/dist/tools/mcp-round.d.ts.map +0 -1
- package/dist/tools/mcp-stdio-signals.d.ts.map +0 -1
- package/dist/tools/mcp-stdio.d.ts.map +0 -1
- package/dist/tools/mcp-trace.d.ts.map +0 -1
- package/dist/tools/mcp.d.ts.map +0 -1
- package/dist/tools/mount-download.d.ts.map +0 -1
- package/dist/tools/mount-upload.d.ts.map +0 -1
- package/dist/tools/mount-wait.d.ts.map +0 -1
- package/dist/tools/native-mcp.d.ts +0 -20
- package/dist/tools/native-mcp.d.ts.map +0 -1
- package/dist/tools/native-result.d.ts.map +0 -1
- package/dist/tools/oauth-metadata.d.ts.map +0 -1
- package/dist/tools/oauth-provider.d.ts.map +0 -1
- package/dist/tools/portable-formats.d.ts.map +0 -1
- package/dist/tools/presentation.d.ts.map +0 -1
- package/dist/tools/schema.d.ts.map +0 -1
- package/dist/tools/untyped-properties.d.ts.map +0 -1
- package/dist/tools/upload-core.d.ts.map +0 -1
- package/dist/tools/view-file-contract.d.ts.map +0 -1
- package/dist/tools/view-file.d.ts.map +0 -1
- package/dist/tools/wait-core.d.ts.map +0 -1
- package/dist/tools-contract.d.ts.map +0 -1
- package/dist/tools.d.ts +0 -50
- package/dist/tools.d.ts.map +0 -1
- package/dist/tracking-server.d.ts.map +0 -1
- package/dist/tracking.d.ts.map +0 -1
- package/dist/upgrade-cli.d.ts.map +0 -1
- package/dist/{retained.d.ts → browser/retained.d.ts} +0 -0
- package/dist/{declaration.d.ts → declaration/declaration.d.ts} +0 -0
- package/dist/{upgrade-cli.d.ts → entrypoints/bin/upgrade-cli.d.ts} +0 -0
- package/dist/{tracking-server.js → entrypoints/tracking/server.js} +0 -0
- package/dist/{google.d.ts → google/google.d.ts} +0 -0
- package/dist/{index-pq95qkbf.js → index-5jm36zv1.js} +0 -0
- package/dist/{index-actkayzn.js → index-70yj2ebz.js} +3 -3
- /package/dist/{application → internal}/channel.d.ts +0 -0
- /package/dist/{logger.d.ts → internal/logger.d.ts} +0 -0
- /package/dist/{server → internal}/request.d.ts +0 -0
- /package/dist/{tools → json-schema}/json-schema.d.ts +0 -0
- /package/dist/tools/{async-operation-contract.d.ts → operations/async-operation-contract.d.ts} +0 -0
- /package/dist/tools/{mount-wait.d.ts → operations/mount-wait.d.ts} +0 -0
- /package/dist/tools/{wait-core.d.ts → operations/wait-core.d.ts} +0 -0
- /package/dist/tools/{coerce.d.ts → schema/coerce.d.ts} +0 -0
- /package/dist/tools/{flatten-join.d.ts → schema/flatten-join.d.ts} +0 -0
- /package/dist/tools/{flatten.d.ts → schema/flatten.d.ts} +0 -0
- /package/dist/tools/{portable-formats.d.ts → schema/portable-formats.d.ts} +0 -0
- /package/dist/tools/{schema.d.ts → schema/schema.d.ts} +0 -0
- /package/dist/tools/{untyped-properties.d.ts → schema/untyped-properties.d.ts} +0 -0
- /package/dist/tools/{native-result.d.ts → transfer/native-result.d.ts} +0 -0
- /package/dist/tools/{view-file-contract.d.ts → transfer/view-file-contract.d.ts} +0 -0
|
@@ -0,0 +1,1031 @@
|
|
|
1
|
+
# stitchkit/server — stitchkit docs slice, part 3 of 5
|
|
2
|
+
|
|
3
|
+
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
|
+
> `llms.txt` in this package.
|
|
5
|
+
> Continues in `llms/server.4.txt`.
|
|
6
|
+
|
|
7
|
+
Parts of this slice:
|
|
8
|
+
- `llms/server.txt`: Guide: HTTP server (docs/guide/server.md)
|
|
9
|
+
- `llms/server.2.txt`: Guide: HTTP server (docs/guide/server.md)
|
|
10
|
+
- `llms/server.3.txt`: Guide: Realtime (docs/guide/realtime.md)
|
|
11
|
+
- `llms/server.4.txt`: Guide: Auth & errors (docs/guide/auth-and-errors.md) · Guide: Multi-tenant (docs/guide/multi-tenant.md)
|
|
12
|
+
- `llms/server.5.txt`: API reference: `stitchkit/server` (docs/api/reference.md)
|
|
13
|
+
|
|
14
|
+
Also read:
|
|
15
|
+
- see `llms/testing.txt` for the Testing & deployment guide
|
|
16
|
+
|
|
17
|
+
==============================================================================
|
|
18
|
+
# Guide: Realtime (docs/guide/realtime.md)
|
|
19
|
+
==============================================================================
|
|
20
|
+
|
|
21
|
+
# Realtime
|
|
22
|
+
|
|
23
|
+
stitchkit's realtime layer is [Socket.IO](https://socket.io) — `polling`
|
|
24
|
+
fallback, heartbeats, acks, a mature client. stitchkit does not ship its own
|
|
25
|
+
WebSocket engine; it ships thin, typed wrappers over Socket.IO and a bridge that
|
|
26
|
+
syncs socket events into the TanStack Query cache. See
|
|
27
|
+
[ADR 0008](../decisions/0008-thin-wrappers.md).
|
|
28
|
+
The separate contract shape and rejection ownership are recorded in
|
|
29
|
+
[ADR 0069](../decisions/0069-realtime-contracts-validate-without-owning-delivery.md).
|
|
30
|
+
|
|
31
|
+
## Zod-first event contract
|
|
32
|
+
|
|
33
|
+
Declare wire events once in the shared module. Each `args` schema is a tuple, so
|
|
34
|
+
Socket.IO's no-payload and variadic forms stay intact; `ack` describes the value
|
|
35
|
+
returned through an acknowledgement callback.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// shared/contracts.ts
|
|
39
|
+
import { defineRealtimeContract } from 'stitchkit'
|
|
40
|
+
import { z } from 'zod'
|
|
41
|
+
|
|
42
|
+
export const realtimeContract = defineRealtimeContract({
|
|
43
|
+
serverToClient: {
|
|
44
|
+
'note:created': { args: z.tuple([NoteSchema]) },
|
|
45
|
+
'note:deleted': { args: z.tuple([z.string()]) },
|
|
46
|
+
},
|
|
47
|
+
clientToServer: {
|
|
48
|
+
'room:join': {
|
|
49
|
+
args: z.tuple([z.string()]),
|
|
50
|
+
ack: z.object({ joined: z.boolean() }),
|
|
51
|
+
},
|
|
52
|
+
ready: { args: z.tuple([]) },
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The contract derives every event handler type and validates both directions at
|
|
58
|
+
runtime. A malformed inbound tuple never reaches the application handler;
|
|
59
|
+
invalid outbound data throws before Socket.IO publishes it. Rejections call the
|
|
60
|
+
optional `onRejected` hook with event, direction, phase and the Zod error.
|
|
61
|
+
|
|
62
|
+
### Protocol generations
|
|
63
|
+
|
|
64
|
+
For a distributed producer/consumer pair, put a literal generation first in the
|
|
65
|
+
first payload object. Zod validates object fields in declaration order, so an
|
|
66
|
+
incompatible peer is identified before the rest of its payload is interpreted:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const ReplicationMessage = z.object({
|
|
70
|
+
v: z.literal(2),
|
|
71
|
+
item: ReplicatedItem,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const realtimeContract = defineRealtimeContract({
|
|
75
|
+
serverToClient: {
|
|
76
|
+
replicated: { args: z.tuple([ReplicationMessage]) },
|
|
77
|
+
},
|
|
78
|
+
clientToServer: {},
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A frame that fails this check is **refused, and the sender is told so** — when
|
|
83
|
+
the event has an acknowledgement. `request()` rejects with
|
|
84
|
+
`RealtimeRequestRejectedError`, immediately, carrying the peer's own issues:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { RealtimeRequestRejectedError } from 'stitchkit'
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await socket.request('replicate', message, { timeoutMs: 5_000 })
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof RealtimeRequestRejectedError) {
|
|
93
|
+
// reason: 'invalid-arguments'; issues: [{ path: '0.v', code: 'invalid_value', … }]
|
|
94
|
+
if (error.issues?.some((issue) => issue.path === '0.v')) schedulePeerUpgrade()
|
|
95
|
+
else reportMalformedRealtimePayload(error)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`path` is `'0.v'` and not `'v'` because event arguments are a tuple: index `0`
|
|
101
|
+
is the first payload. The issues are already flattened by Stitchkit's own
|
|
102
|
+
normaliser, so telling "wrong generation" from "malformed payload" is one
|
|
103
|
+
comparison rather than an inspection of a `ZodError`'s internals.
|
|
104
|
+
|
|
105
|
+
The receiving side still reports it locally through `onRejected` — a refusal is
|
|
106
|
+
now visible on **both** ends rather than only where it happened.
|
|
107
|
+
|
|
108
|
+
**Two limits, both real.** A **fire-and-forget** event has no acknowledgement
|
|
109
|
+
channel, so its refusal stays local: the sender learns nothing, and no
|
|
110
|
+
convention in the payload can change that. And an event the receiver's contract
|
|
111
|
+
does not contain has no listener at all, so there is nothing on that side to
|
|
112
|
+
answer with — adding an event is not a change a generation field can announce.
|
|
113
|
+
|
|
114
|
+
### Where protocol identity belongs
|
|
115
|
+
|
|
116
|
+
For a distributed pair whose planes are mostly fire-and-forget, compare
|
|
117
|
+
identity **at the handshake** instead, where a mismatch is refused before the
|
|
118
|
+
first frame is interpreted and both ends see it at once. The typed handshake is
|
|
119
|
+
already the place:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const handshake = {
|
|
123
|
+
schema: z.object({ token: z.string(), protocol: z.string() }),
|
|
124
|
+
verify: (auth) => {
|
|
125
|
+
if (auth.protocol !== PROTOCOL_IDENTITY) return null // refused, with a reason
|
|
126
|
+
return { subject: verifyToken(auth.token) }
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The client sends it as `auth`, and a rejection reaches `onConnectError` with
|
|
132
|
+
`terminal: true` — distinguishable in a log from a bad token, so a half-rolled
|
|
133
|
+
deployment reads as a half-rolled deployment and not as an access problem.
|
|
134
|
+
|
|
135
|
+
What that identity *is* remains the application's decision — a build version, a
|
|
136
|
+
contract hash, a protocol generation. Stitchkit does not compare it for you
|
|
137
|
+
(→ ADR 0002); it gives the place where the comparison happens before any frame
|
|
138
|
+
is interpreted, and it makes the per-frame alternative honest by letting its
|
|
139
|
+
refusals be seen.
|
|
140
|
+
|
|
141
|
+
## Server — `createSocketIOServer`
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import {
|
|
145
|
+
bindRealtimeServer,
|
|
146
|
+
createServer,
|
|
147
|
+
createSocketIOServer,
|
|
148
|
+
} from 'stitchkit/server'
|
|
149
|
+
import { realtimeContract } from '@app/shared'
|
|
150
|
+
|
|
151
|
+
const socket = await createSocketIOServer({
|
|
152
|
+
cors: { origin: 'https://app.example.com' },
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const realtime = bindRealtimeServer(realtimeContract, socket, {
|
|
156
|
+
onRejected: (event) => audit.realtimeRejected(event),
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
realtime.onConnection(({ raw, events, to }) => {
|
|
160
|
+
events.on('room:join', (room, acknowledge) => {
|
|
161
|
+
raw.join(room) // authorization and room membership remain application policy
|
|
162
|
+
acknowledge({ joined: true })
|
|
163
|
+
to(room).emit('note:created', note)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Pass the full handle to `createServer`; it mounts and owns the transport once:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
createServer({
|
|
172
|
+
services,
|
|
173
|
+
socket,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// elsewhere — validated broadcast:
|
|
177
|
+
realtime.emit('note:created', note)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`socket.io` and, on Bun, `@socket.io/bun-engine` are optional peers, resolved
|
|
181
|
+
lazily so a project that never opens a socket does not have to install them. If
|
|
182
|
+
you ship **one self-contained file** to a machine with no `node_modules`, tell
|
|
183
|
+
the framework how to load them so your bundler can put them inside — see
|
|
184
|
+
[shipping one self-contained artifact](./testing-and-deployment.md#shipping-one-self-contained-artifact).
|
|
185
|
+
|
|
186
|
+
The canonical room-broadcast example below is executed by the test suite. Its
|
|
187
|
+
body is kept byte-identical to `packages/core/examples/realtime/realtime-room.ts`.
|
|
188
|
+
|
|
189
|
+
```ts canonical-realtime-room
|
|
190
|
+
export function publishExampleNote(realtime: ExampleRealtimePublisher): void {
|
|
191
|
+
const note = { id: 'note-1', text: 'Ready' };
|
|
192
|
+
realtime.to('general').emit('note:created', note);
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| Handle field | Purpose |
|
|
197
|
+
|--------------|---------|
|
|
198
|
+
| `io` | raw Socket.IO server for middleware, handshake auth and transport ownership |
|
|
199
|
+
| `websocket` | Bun handlers used directly only in explicit raw-lane composition |
|
|
200
|
+
| `route` | `/socket.io/*socketPath`, mounted automatically by `createServer({ socket })` |
|
|
201
|
+
| `close()` | idempotent standalone close for CLI/tools with no HTTP server |
|
|
202
|
+
| `beginShutdown()` / `connections()` | lifecycle surface consumed by the managed server |
|
|
203
|
+
|
|
204
|
+
`SocketIOServerConfig` also takes `path`, `transports`, `pingTimeout`,
|
|
205
|
+
`pingInterval`, a runtime-neutral `allowRequest(Request)` handshake policy and
|
|
206
|
+
the typed **`handshake`** identity gate (schema + verify →
|
|
207
|
+
[typed `socket.data`](#handshake-auth--cookie-or-token)). `allowRequest` is the
|
|
208
|
+
transport gate (composed with managed-shutdown admission on both Bun and Node);
|
|
209
|
+
`handshake` is the identity gate that runs after it.
|
|
210
|
+
`transports` is an admission allowlist: `['websocket']` refuses polling,
|
|
211
|
+
`['polling']` refuses WebSocket handshakes/upgrades, and both explicitly enabled
|
|
212
|
+
transports remain usable. Defaults are both transports on Bun and WebSocket-only
|
|
213
|
+
on Node. Bun enforces this before consumer authorization through the engine's
|
|
214
|
+
request-policy extension (including existing sessions); Node uses native Engine.IO
|
|
215
|
+
enforcement. Bun's native upgrade hints do not override the allowlist. CORS
|
|
216
|
+
preflight is not transport admission and keeps its ordinary behavior.
|
|
217
|
+
For anything else socket.io's `ServerOptions` exposes, use the
|
|
218
|
+
typed **`serverOptions`** passthrough — most often `maxHttpBufferSize` to lift the
|
|
219
|
+
1 MB default for large emits:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
await createSocketIOServer({
|
|
223
|
+
cors: { origin: 'https://app.example.com' },
|
|
224
|
+
serverOptions: { maxHttpBufferSize: 5 * 1024 * 1024 }, // 5 MB
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`cors` is **optional**. Omit it when the browser reaches this server on its
|
|
229
|
+
own origin — Socket.IO then emits no CORS headers, which is same-origin only.
|
|
230
|
+
Supply it only for a genuinely cross-origin browser: naming a foreign origin is
|
|
231
|
+
naming where the code will run, and a repository that does not have to know
|
|
232
|
+
should not say.
|
|
233
|
+
|
|
234
|
+
The wrapper-owned fields (`cors` / `path` / `transports` / `ping*` /
|
|
235
|
+
`allowRequest`) take
|
|
236
|
+
precedence over the same keys in `serverOptions`. On Bun the engine-level options
|
|
237
|
+
(`maxHttpBufferSize`, the ping heartbeat, `upgradeTimeout`) are forwarded to
|
|
238
|
+
`@socket.io/bun-engine` too — so a configured `maxHttpBufferSize` actually applies
|
|
239
|
+
instead of silently truncating at 1 MB.
|
|
240
|
+
|
|
241
|
+
## Client — `createRealtimeClient`
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { createRealtimeClient } from 'stitchkit'
|
|
245
|
+
import { realtimeContract } from '@app/shared'
|
|
246
|
+
|
|
247
|
+
const socket = createRealtimeClient(realtimeContract, {
|
|
248
|
+
url: 'https://api.example.com',
|
|
249
|
+
retain: ['note:created'],
|
|
250
|
+
onRejected: (event) => reportClientError(event),
|
|
251
|
+
onRequestPhase: (event) => metrics.realtimeRequestPhase(event),
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
socket.connect()
|
|
255
|
+
socket.on('note:created', (note) => { /* typed note */ })
|
|
256
|
+
socket.emit('room:join', 'general', ({ joined }) => { /* typed + validated */ })
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
When an application already owns the low-level Stitchkit transport, bind the
|
|
260
|
+
contract without opening a second connection:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { bindRealtimeClient, createSocketIOClient } from 'stitchkit'
|
|
264
|
+
|
|
265
|
+
const transport = createSocketIOClient({ url: 'https://api.example.com' })
|
|
266
|
+
const events = bindRealtimeClient(realtimeContract, transport, { onRejected })
|
|
267
|
+
|
|
268
|
+
transport.connect() // lifecycle stays with the transport owner
|
|
269
|
+
events.on('note:created', handleNote)
|
|
270
|
+
await events.request('room:join', 'general', { timeoutMs: 5_000 })
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The bound handle intentionally has no `connect()` or `disconnect()`. Its
|
|
274
|
+
`on`/`emit`/`request`, rejection and timeout semantics are exactly the path used
|
|
275
|
+
by `createRealtimeClient`; only transport construction/lifecycle differs.
|
|
276
|
+
|
|
277
|
+
## Snapshot + event state synchronization
|
|
278
|
+
|
|
279
|
+
`createLiveStateController` is the optional browser-safe state machine between a
|
|
280
|
+
validated transport binding and any renderer. It solves one problem: install a
|
|
281
|
+
snapshot and every event after that snapshot's consistency point without a race.
|
|
282
|
+
It does not create a socket, choose a cursor, retry a transport, store history or
|
|
283
|
+
invent ordering for the application.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import {
|
|
287
|
+
createLiveStateController,
|
|
288
|
+
type LiveStateEventDecision,
|
|
289
|
+
type LiveStateSource,
|
|
290
|
+
} from 'stitchkit'
|
|
291
|
+
|
|
292
|
+
type View = { revision: number; rows: readonly Row[] }
|
|
293
|
+
type Change = { revision: number; row: Row }
|
|
294
|
+
|
|
295
|
+
const applyChange = (state: View, event: Change): LiveStateEventDecision<View> => {
|
|
296
|
+
if (event.revision <= state.revision) return { outcome: 'duplicate' }
|
|
297
|
+
if (event.revision !== state.revision + 1) return { outcome: 'gap' }
|
|
298
|
+
return {
|
|
299
|
+
outcome: 'applied',
|
|
300
|
+
state: { revision: event.revision, rows: [...state.rows, event.row] },
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const live = createLiveStateController({
|
|
305
|
+
source,
|
|
306
|
+
applyEvent: applyChange,
|
|
307
|
+
maxBufferedEvents: 128,
|
|
308
|
+
maxBufferedBytes: 256 * 1024,
|
|
309
|
+
sizeOfEvent: encodedChangeBytes,
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
const unsubscribe = live.subscribe(render)
|
|
313
|
+
await live.start()
|
|
314
|
+
// On a gap/overflow or source loss, choose when the UI should resync.
|
|
315
|
+
const status = live.getSnapshot()
|
|
316
|
+
if (status.phase === 'resync-required' || status.phase === 'unavailable') {
|
|
317
|
+
await live.resync()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
unsubscribe()
|
|
321
|
+
await live.close()
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The source boundary is the important part:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
interface LiveStateSource<State, Event> {
|
|
328
|
+
open(input: {
|
|
329
|
+
signal: AbortSignal
|
|
330
|
+
onEvent(event: Event): void
|
|
331
|
+
onUnavailable(): void
|
|
332
|
+
}): Promise<{ snapshot: State; close(): void | Promise<void> }>
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
`onEvent` is available before `open()` begins asynchronous work. By the time
|
|
337
|
+
`open()` resolves, the source guarantees that every event after the returned
|
|
338
|
+
snapshot's consistency point has already been or will be passed to that callback.
|
|
339
|
+
The controller buffers early events within both explicit limits, installs the
|
|
340
|
+
snapshot, drains in order, then becomes `live`. A late result from an earlier
|
|
341
|
+
`resync()` generation is fenced. Non-cooperative caller-owned cleanup is asked to
|
|
342
|
+
stop but cannot hold controller settlement.
|
|
343
|
+
|
|
344
|
+
`subscribe()` listeners are synchronous external-store notifications: read the
|
|
345
|
+
published snapshot and schedule rendering elsewhere. A listener that returns a
|
|
346
|
+
Promise is removed after its first call, preventing unresolved UI work from
|
|
347
|
+
accumulating per event. Source `close()` must be idempotent because an abort-aware
|
|
348
|
+
binding may have started cleanup before the controller calls it.
|
|
349
|
+
|
|
350
|
+
At most two physical source `open()` / `close()` operations in total may remain
|
|
351
|
+
unsettled. If a caller-owned source ignores cancellation beyond that operation
|
|
352
|
+
bound, `resync()` returns
|
|
353
|
+
`unavailable/controller-capacity` without opening another generation. When a slot
|
|
354
|
+
settles, the controller publishes `resync-required/controller-capacity`; the host
|
|
355
|
+
may retry explicitly. This bounds controller-retained work without inventing a
|
|
356
|
+
transport retry loop.
|
|
357
|
+
|
|
358
|
+
### Socket.IO binding
|
|
359
|
+
|
|
360
|
+
Use an acknowledged operation whose server handler establishes the subscription
|
|
361
|
+
before it captures/returns the snapshot. For example, the server can join the
|
|
362
|
+
socket to the resource room, capture revision `N`, then acknowledge that snapshot;
|
|
363
|
+
ordered Socket.IO frames after that point reach the already-installed handler:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const source: LiveStateSource<View, Change> = {
|
|
367
|
+
async open({ signal, onEvent, onUnavailable }) {
|
|
368
|
+
const offEvent = socket.on('view:changed', onEvent)
|
|
369
|
+
const offConnection = socket.onConnectionChange((connected) => {
|
|
370
|
+
if (!connected) onUnavailable()
|
|
371
|
+
})
|
|
372
|
+
let closed = false
|
|
373
|
+
const close = () => {
|
|
374
|
+
if (closed) return
|
|
375
|
+
closed = true
|
|
376
|
+
offEvent()
|
|
377
|
+
offConnection()
|
|
378
|
+
}
|
|
379
|
+
signal.addEventListener('abort', close, { once: true })
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const snapshot = await socket.request('view:open', { timeoutMs: 5_000 })
|
|
383
|
+
return { snapshot, close }
|
|
384
|
+
} catch (error) {
|
|
385
|
+
close()
|
|
386
|
+
throw error
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Socket.IO still owns physical reconnect. A reconnected transport only means the
|
|
393
|
+
connection is open; call `resync()` when application state needs a fresh
|
|
394
|
+
generation. If the application has replay, its source may resume from its opaque
|
|
395
|
+
cursor and return an accepted consistency point. If history expired or a cursor is
|
|
396
|
+
incompatible, the source must acquire a fresh authoritative snapshot or reject the
|
|
397
|
+
open; the controller does not classify or compare opaque cursors itself.
|
|
398
|
+
|
|
399
|
+
### One-way HTTP stream binding
|
|
400
|
+
|
|
401
|
+
NDJSON/SSE can use the same receiver semantics when **one response generation**
|
|
402
|
+
starts with a schema-validated snapshot frame and every later frame is a validated
|
|
403
|
+
event. Attach `parseNDJSON` or the typed contract-stream reader, parse the first
|
|
404
|
+
frame before resolving `open()`, and pump remaining frames into `onEvent`. Abort
|
|
405
|
+
that response in `close()`.
|
|
406
|
+
|
|
407
|
+
A separate `GET /snapshot` followed by `GET /events` is not this boundary: a
|
|
408
|
+
change can land between the two requests and disappear unless the application
|
|
409
|
+
supplies a watermark/replay protocol. The controller intentionally cannot make
|
|
410
|
+
that uncoordinated recipe safe.
|
|
411
|
+
|
|
412
|
+
### Rendering, cache and process lifecycle
|
|
413
|
+
|
|
414
|
+
For replaceable progress, let the reducer replace the absolute view at each
|
|
415
|
+
accepted revision. For ordered records, append only the exact next revision and
|
|
416
|
+
return `gap` otherwise. Both use the same controller; their ordering policy stays
|
|
417
|
+
in their reducers. `getSnapshot()` + `subscribe()` works headlessly and with
|
|
418
|
+
`useSyncExternalStore`. A React Query application can update its existing query
|
|
419
|
+
cache from a subscriber after `phase === 'live'`; no second hook or store adapter
|
|
420
|
+
is required. Cache `markFresh` windows suppress local echoes, while revision/cursor
|
|
421
|
+
classification detects duplicates—those are different policies.
|
|
422
|
+
|
|
423
|
+
A server process that owns such a receiver can place `start()` and `close()` in
|
|
424
|
+
an existing `defineManagedResource` and include it in `createApplication`.
|
|
425
|
+
Readiness follows a successful `live` snapshot; shutdown calls `close()`. Stitchkit
|
|
426
|
+
does not add another supervisor, reconnect loop or durable event database.
|
|
427
|
+
|
|
428
|
+
When migrating a hand-written receiver, remove only the superseded attach/snapshot
|
|
429
|
+
race loop, retry timer and listener bookkeeping. Keep the application's schemas,
|
|
430
|
+
authorization, reducer, cursor/replay policy and durable storage. Development
|
|
431
|
+
proxying and Vite HMR remain frontend tooling; they are described in
|
|
432
|
+
[frontend integrations](./frontend-integrations.md) and never travel through live
|
|
433
|
+
application event envelopes.
|
|
434
|
+
|
|
435
|
+
The Agent harness control server follows the same ordering: it installs the
|
|
436
|
+
conversation attachment before awaiting the authoritative snapshot and rolls the
|
|
437
|
+
attachment back if that read fails. A host adapter installs its delivery callback,
|
|
438
|
+
issues `attach`, and supplies the returned snapshot through its live-state source;
|
|
439
|
+
the existing Agent cursor and view reducers still own runtime epochs, durable
|
|
440
|
+
versions and transcript projection.
|
|
441
|
+
|
|
442
|
+
### Request-response over realtime
|
|
443
|
+
|
|
444
|
+
For an event with an `ack` schema, `request()` is the Promise form of the same
|
|
445
|
+
native Socket.IO acknowledgement. Arguments and the resolved value are inferred
|
|
446
|
+
from the contract and validated on both sides:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
const result = await socket.request(
|
|
450
|
+
'room:join',
|
|
451
|
+
'general',
|
|
452
|
+
{
|
|
453
|
+
timeoutMs: 5_000,
|
|
454
|
+
onPhase: (phase) => roomJoinMetrics.record(phase),
|
|
455
|
+
},
|
|
456
|
+
)
|
|
457
|
+
// result: { joined: boolean }
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
An event without `ack` cannot be passed to `request()` by type. A disconnected
|
|
461
|
+
client rejects immediately with `RealtimeRequestDisconnectedError`; an in-flight
|
|
462
|
+
disconnect uses the same error; an elapsed native acknowledgement timeout uses
|
|
463
|
+
`RealtimeRequestTimeoutError`. These are distinct stable classes and codes, so
|
|
464
|
+
never parse Socket.IO error text. An invalid acknowledgement still fires the
|
|
465
|
+
existing `onRejected` hook with phase `acknowledgement`, then rejects with
|
|
466
|
+
`RealtimeRequestInvalidAcknowledgementError`.
|
|
467
|
+
|
|
468
|
+
`timeoutMs` must be finite and greater than zero. Use acknowledgements only for
|
|
469
|
+
bounded request-response work. A job that runs for minutes, progress streaming,
|
|
470
|
+
or resumable delivery should use separate correlated events or the async
|
|
471
|
+
operation protocol; keeping one acknowledgement open is not a durable RPC/job
|
|
472
|
+
transport.
|
|
473
|
+
|
|
474
|
+
### Acknowledged-request phases
|
|
475
|
+
|
|
476
|
+
`createRealtimeClient` can expose the local boundary that a single Promise
|
|
477
|
+
normally hides:
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
const socket = createRealtimeClient(realtimeContract, {
|
|
481
|
+
url,
|
|
482
|
+
onRequestPhase: ({ requestId, event, phase, elapsedMs }) => {
|
|
483
|
+
requestPhaseHistogram.record(elapsedMs, { requestId, event, phase })
|
|
484
|
+
},
|
|
485
|
+
})
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The hook is opt-in and each record has exactly four metadata fields: an opaque,
|
|
489
|
+
Kit-owned `requestId`, the contract event name, monotonic `elapsedMs`, and one
|
|
490
|
+
closed phase:
|
|
491
|
+
|
|
492
|
+
| Phase | Exact local boundary |
|
|
493
|
+
|-------|----------------------|
|
|
494
|
+
| `engine-handoff` | Engine.IO created the outbound message packet and accepted it into its write path |
|
|
495
|
+
| `engine-ack-received` | Engine.IO decoded the inbound acknowledgement message, before Socket.IO invokes the acknowledgement callback |
|
|
496
|
+
| `settled` | the acknowledgement callback ran and Stitchkit finished acknowledgement validation |
|
|
497
|
+
| `timeout` | the existing native acknowledgement timeout won |
|
|
498
|
+
| `disconnected` | the request began disconnected or an in-flight disconnect won |
|
|
499
|
+
|
|
500
|
+
Observation also works on non-localhost HTTP origins: identity generation uses
|
|
501
|
+
browser-compatible `crypto.getRandomValues`, not secure-context-only
|
|
502
|
+
`crypto.randomUUID`. A `requestId` is opaque local diagnostic identity, not a
|
|
503
|
+
promised UUID format, authentication token or transmitted field. Bun and Node
|
|
504
|
+
use the same mechanism; enabling observation adds no HTTPS requirement.
|
|
505
|
+
|
|
506
|
+
When a caller needs to join those phases to its own invocation, put `onPhase`
|
|
507
|
+
on that request's options and keep the caller identity in the hook's closure:
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
await socket.request('room:join', 'general', {
|
|
511
|
+
timeoutMs: 5_000,
|
|
512
|
+
onPhase: (phase) => recordPhase({ operationId, phase }),
|
|
513
|
+
})
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
The client-wide hook receives every observed request; a request hook receives
|
|
517
|
+
only its invocation. If the same function is supplied in both places it runs
|
|
518
|
+
once per phase. The closure is local: Stitchkit does not retain `operationId`,
|
|
519
|
+
add it to the event or transmit it to the peer. A request hook works without a
|
|
520
|
+
client-wide hook, and the no-hook path installs no phase listeners.
|
|
521
|
+
|
|
522
|
+
Engine handoff is not proof of a physical network write. Engine acknowledgement
|
|
523
|
+
receipt is not a remote clock, end-to-end RTT or proof that application
|
|
524
|
+
validation has run. The useful interval is local and monotonic:
|
|
525
|
+
`engine-ack-received → settled` isolates callback scheduling plus validation
|
|
526
|
+
from transport waiting.
|
|
527
|
+
|
|
528
|
+
Records never contain request arguments, acknowledgement values, raw packets,
|
|
529
|
+
credentials, URLs or query data. Concurrent requests are correlated internally
|
|
530
|
+
with Socket.IO acknowledgement ids, but those ids are never exposed. A timeout
|
|
531
|
+
or disconnect is terminal, a late packet cannot reopen the identity, and sync
|
|
532
|
+
or async observer failures are ignored so telemetry cannot change request
|
|
533
|
+
correctness. With no hook, no request identity, Engine.IO listener or correlation
|
|
534
|
+
map entry is created. The low-level `createSocketIOClient` implements the same
|
|
535
|
+
request option, so a non-owning `bindRealtimeClient` over that transport keeps
|
|
536
|
+
request-scoped observation without opening a second connection.
|
|
537
|
+
|
|
538
|
+
## Low-level transport
|
|
539
|
+
|
|
540
|
+
`createSocketIOClient` remains the low-level Socket.IO transport wrapper for
|
|
541
|
+
schema-agnostic infrastructure. Application wire events should use
|
|
542
|
+
`createRealtimeClient`; it adds the shared contract without replacing Socket.IO.
|
|
543
|
+
Use `bindRealtimeClient` when that low-level transport already exists.
|
|
544
|
+
|
|
545
|
+
### Durable subscriptions
|
|
546
|
+
|
|
547
|
+
`socket.on(...)` returns an unsubscribe and is **durable** — the handler is
|
|
548
|
+
re-attached to every socket the client builds, so it survives a reconnect. You
|
|
549
|
+
subscribe once; reconnection is the wrapper's problem, not yours.
|
|
550
|
+
|
|
551
|
+
`SocketIOClientConfig` takes `url`, `path`, `withCredentials` (cookies on the
|
|
552
|
+
handshake — default `true`), `auth`, `query`, `extraHeaders`, `transports`,
|
|
553
|
+
`reconnectionAttempts`, `reconnectionDelay`, `retain` (below), `onConnectError`
|
|
554
|
+
(handshake/connection failures — see the auth section) and `onDroppedEmit`
|
|
555
|
+
(observability for emits dropped while disconnected — see below).
|
|
556
|
+
|
|
557
|
+
### Honest emit — what happens while disconnected
|
|
558
|
+
|
|
559
|
+
`emit` has exactly three outcomes, in order:
|
|
560
|
+
|
|
561
|
+
1. **A local contract violation throws** (validated `RealtimeClient` only) —
|
|
562
|
+
validation runs before the connection guard, even while disconnected.
|
|
563
|
+
2. **Disconnected → the emit is dropped**: `emit` returns `false` and the
|
|
564
|
+
`onDroppedEmit` hook fires with the event name and wire arguments. This
|
|
565
|
+
includes the short window right after `connect()` while the socket.io peer
|
|
566
|
+
is still loading.
|
|
567
|
+
3. **Otherwise `emit` returns `true`** — handed to the transport, which is
|
|
568
|
+
*not* a delivery guarantee.
|
|
569
|
+
|
|
570
|
+
The default is deliberately a drop, not a buffer: after a reconnect, durable
|
|
571
|
+
subscriptions and [sticky events](#sticky-events) replay current state
|
|
572
|
+
deterministically, instead of the server receiving an unordered backlog of
|
|
573
|
+
stale emits. What changed is that the drop is now observable — assert it
|
|
574
|
+
per-call (`if (!client.emit(...)) …`) or centrally:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
const client = createSocketIOClient({
|
|
578
|
+
url,
|
|
579
|
+
onDroppedEmit: ({ event }) => metrics.count('realtime.dropped_emit', { event }),
|
|
580
|
+
})
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Server-side emits (`realtime.emit`, `to(room).emit`, `connection.events.emit`)
|
|
584
|
+
always return `true` — a broadcast into an empty room is not a drop, and the
|
|
585
|
+
server has no disconnected state of this kind. For state that must survive
|
|
586
|
+
gaps, see [durability](#durability--idempotent--replay).
|
|
587
|
+
|
|
588
|
+
### Sticky events
|
|
589
|
+
|
|
590
|
+
A handler that subscribes **after** an event was emitted misses it — the UI stays
|
|
591
|
+
on stale state until the next emission. List those events in **`retain`** and the
|
|
592
|
+
client keeps each one's last payload and replays it to a handler the moment it
|
|
593
|
+
subscribes (and on the next subscribe after a re-render). It is the pub/sub
|
|
594
|
+
analogue of an MQTT *retained* message or an RxJS `BehaviorSubject`.
|
|
595
|
+
|
|
596
|
+
```ts
|
|
597
|
+
const client = createRealtimeClient(realtimeContract, {
|
|
598
|
+
url,
|
|
599
|
+
retain: ['presence:changed', 'job:state'], // events to keep the last value of
|
|
600
|
+
})
|
|
601
|
+
client.connect()
|
|
602
|
+
|
|
603
|
+
// Later — even after the event already fired — this handler fires at once with
|
|
604
|
+
// the last value, then on every future change:
|
|
605
|
+
client.on('job:state', (s) => render(s))
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
The retained value survives a `disconnect()` / `connect()` cycle (the store lives
|
|
609
|
+
outside the socket). Only an event's **first** argument is retained.
|
|
610
|
+
|
|
611
|
+
For a pub/sub channel that is **not** Socket.IO (your own transport — see
|
|
612
|
+
[below](#bring-your-own-transport)), use the same memory directly:
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
import { createRetainedTopics } from 'stitchkit'
|
|
616
|
+
|
|
617
|
+
const topics = createRetainedTopics<{ 'job:state': JobState }>()
|
|
618
|
+
topics.record('job:state', state) // on every publish
|
|
619
|
+
topics.replay('job:state', (s) => render(s)) // for a late subscriber
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Handshake auth — cookie or token
|
|
623
|
+
|
|
624
|
+
By default the handshake carries cookies (`withCredentials: true`) — the right
|
|
625
|
+
fit for a browser app with a session cookie. A client that holds a token
|
|
626
|
+
explicitly (desktop, mobile, CLI, server-to-server) authenticates the
|
|
627
|
+
handshake with **`auth`** instead — the token reaches the server as
|
|
628
|
+
`socket.handshake.auth`:
|
|
629
|
+
|
|
630
|
+
```ts
|
|
631
|
+
// client — a function is re-read on every (re)connect, so a rotated token is
|
|
632
|
+
// picked up automatically; no need to recreate the client (or lose durable
|
|
633
|
+
// subscriptions). It may be async.
|
|
634
|
+
const socket = createRealtimeClient(realtimeContract, {
|
|
635
|
+
url: 'https://api.example.com',
|
|
636
|
+
auth: () => ({ token: getAccessToken() }),
|
|
637
|
+
})
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
// server — the typed identity gate: Zod-validate handshake.auth, verify,
|
|
642
|
+
// and the result lands in socket.data — typed all the way to onConnection.
|
|
643
|
+
import { verifyJwt } from 'stitchkit/server'
|
|
644
|
+
|
|
645
|
+
const socket = await createSocketIOServer({
|
|
646
|
+
cors: { origin: 'https://app.example.com' },
|
|
647
|
+
handshake: {
|
|
648
|
+
schema: z.object({ token: z.string() }),
|
|
649
|
+
verify: async ({ token }) => ({ user: await verifyJwt(token, secret) }),
|
|
650
|
+
},
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
const realtime = bindRealtimeServer(realtimeContract, socket)
|
|
654
|
+
realtime.onConnection(({ raw }) => {
|
|
655
|
+
raw.data.user // typed — no String(...) coercions, no casts
|
|
656
|
+
})
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
`verify` may be async (the wrapper runs it inside a settled promise chain —
|
|
660
|
+
raw async `io.use` middleware would leak an unhandled rejection); throwing or
|
|
661
|
+
returning `null` rejects the handshake **before** the connection handler and
|
|
662
|
+
before any event validation. A thrown error's raw message is logged
|
|
663
|
+
server-side and never reaches the unauthenticated peer — the wire always
|
|
664
|
+
carries the generic `handshake rejected` (the same never-leak policy as the
|
|
665
|
+
HTTP error normalizer). The schema itself must be synchronous (no async
|
|
666
|
+
refine/transform). Omit `verify` and the schema output itself is the
|
|
667
|
+
identity. The gate registers as the **first** middleware, so `socket.io.use(…)`
|
|
668
|
+
middlewares you add afterwards see the typed `socket.data` already in place.
|
|
669
|
+
Raw `io.use` remains available for anything beyond identity.
|
|
670
|
+
|
|
671
|
+
Type inference works on calls without explicit event generics (the
|
|
672
|
+
`bindRealtimeServer` lane above). With explicit generics TypeScript cannot
|
|
673
|
+
partially infer — pass the identity types too:
|
|
674
|
+
`createSocketIOServer<S, C, z.infer<typeof schema>, Identity>`.
|
|
675
|
+
|
|
676
|
+
A static object (`auth: { token }`) works too, but only the function form
|
|
677
|
+
re-reads on reconnect — prefer it for rotating tokens. `query` adds handshake
|
|
678
|
+
URL params (`socket.handshake.query`) — note its values are **strings** on the
|
|
679
|
+
wire, which is why the schema gate reads `auth`; `extraHeaders` adds handshake
|
|
680
|
+
headers, but in a browser those apply to the **polling** transport only (a
|
|
681
|
+
WebSocket upgrade cannot set request headers) — for browser WebSocket auth use
|
|
682
|
+
`auth`.
|
|
683
|
+
|
|
684
|
+
**A gate rejection is terminal for the client.** Unlike a transport-level
|
|
685
|
+
failure (which socket.io retries indefinitely), a middleware/handshake-gate
|
|
686
|
+
rejection stops the client: socket.io destroys its retry path and will not
|
|
687
|
+
reconnect on its own. The stitchkit client surfaces this through
|
|
688
|
+
**`onConnectError`** with `terminal: true` (and `data.code ===
|
|
689
|
+
'handshake_rejected'` for the built-in gate) and resets its connection intent —
|
|
690
|
+
so recovery is explicit: rotate the credential, call `connect()`, and the
|
|
691
|
+
function-form `auth` is re-read:
|
|
692
|
+
|
|
693
|
+
```ts
|
|
694
|
+
const client = createRealtimeClient(realtimeContract, {
|
|
695
|
+
url: 'https://api.example.com',
|
|
696
|
+
auth: () => ({ token: getAccessToken() }),
|
|
697
|
+
onConnectError: ({ terminal }) => {
|
|
698
|
+
if (terminal) refreshTokenThen(() => client.connect())
|
|
699
|
+
},
|
|
700
|
+
})
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
If an auth producer throws or rejects, the wrapper sends an empty auth object —
|
|
704
|
+
the server gate then rejects it visibly (via `onConnectError`) instead of
|
|
705
|
+
leaving the handshake waiting forever.
|
|
706
|
+
|
|
707
|
+
## Cache bridge
|
|
708
|
+
|
|
709
|
+
**Using a validated realtime contract? Use `createRealtimeCacheBridge`.** A
|
|
710
|
+
realtime registry maps an event name to its *definition* (`{ args, ack }`),
|
|
711
|
+
while the plain bridge's event map expects the *handler function* at that
|
|
712
|
+
position. A validated socket satisfies the looser type structurally, so passing
|
|
713
|
+
one to `createCacheBridge` compiled and inferred every payload as `never` —
|
|
714
|
+
the error then landed on your own property access rather than on the seam.
|
|
715
|
+
`createRealtimeCacheBridge` performs the mapping; nothing runs differently.
|
|
716
|
+
|
|
717
|
+
```ts
|
|
718
|
+
import { createRealtimeCacheBridge } from 'stitchkit/react'
|
|
719
|
+
|
|
720
|
+
const bridge = createRealtimeCacheBridge<typeof contract.serverToClient>({
|
|
721
|
+
socket: realtimeClient,
|
|
722
|
+
queryClient,
|
|
723
|
+
handlers: {
|
|
724
|
+
noteUpdated: (note, { queryClient: qc }) => qc.setQueryData(noteKey(note.id), note),
|
|
725
|
+
},
|
|
726
|
+
})
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
`createCacheBridge` syncs socket events into the TanStack Query cache — a server
|
|
730
|
+
push updates the UI with no refetch. It is transport-agnostic: it takes any
|
|
731
|
+
emitter with `on(event, handler) => unsubscribe`, which the
|
|
732
|
+
`createSocketIOClient` result satisfies.
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
import { createCacheBridge } from 'stitchkit/react'
|
|
736
|
+
|
|
737
|
+
const bridge = createCacheBridge({
|
|
738
|
+
socket,
|
|
739
|
+
queryClient,
|
|
740
|
+
handlers: {
|
|
741
|
+
'note:created': (note, ctx) => {
|
|
742
|
+
if (ctx.isFresh(['notes'])) return // skip the echo of our own mutation
|
|
743
|
+
ctx.queryClient.invalidateQueries({ queryKey: ['notes'] })
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
})
|
|
747
|
+
bridge.connect()
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### The echo problem
|
|
751
|
+
|
|
752
|
+
When the client makes a mutation, it updates the cache itself — and the server
|
|
753
|
+
also broadcasts the change back over the socket. Without care the UI updates
|
|
754
|
+
twice. `markFresh` plus a short freshness window solves it: mark a key fresh
|
|
755
|
+
right after a local mutation, and the bridge handler skips the echo.
|
|
756
|
+
|
|
757
|
+
```ts
|
|
758
|
+
// in the mutation:
|
|
759
|
+
onSuccess: () => bridge.markFresh(['notes'])
|
|
760
|
+
// in the handler: if (ctx.isFresh(['notes'])) return
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
`createCacheBridge` is a convenience, not a requirement — any code can subscribe
|
|
764
|
+
to the socket and call `queryClient` directly. The bridge just centralises the
|
|
765
|
+
event-to-cache mapping and the echo guard.
|
|
766
|
+
|
|
767
|
+
### Entity cache handlers
|
|
768
|
+
|
|
769
|
+
The created / updated / deleted events of one entity almost always patch the
|
|
770
|
+
cache the same way: prepend to the list, replace by id, remove by id — plus the
|
|
771
|
+
detail query. `createEntityCacheHandlers` builds those three handlers from a
|
|
772
|
+
small config, so you wire them onto the bridge instead of hand-rolling the
|
|
773
|
+
updater per entity:
|
|
774
|
+
|
|
775
|
+
```ts
|
|
776
|
+
import { createEntityCacheHandlers } from 'stitchkit/react'
|
|
777
|
+
|
|
778
|
+
const widgetCache = createEntityCacheHandlers<Widget>({
|
|
779
|
+
getId: (widget) => widget.id,
|
|
780
|
+
getListItemId: (widget) => widget.id,
|
|
781
|
+
toListItem: (widget) => widget,
|
|
782
|
+
list: {
|
|
783
|
+
key: ['widgets'],
|
|
784
|
+
shape: 'paginated',
|
|
785
|
+
createAt: 'start',
|
|
786
|
+
updateMissing: 'skip',
|
|
787
|
+
},
|
|
788
|
+
detailKey: (event) => ['widgets', event.id],
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
createCacheBridge({ socket, queryClient, handlers: {
|
|
792
|
+
widgetCreated: widgetCache.created,
|
|
793
|
+
widgetUpdated: widgetCache.updated,
|
|
794
|
+
widgetDeleted: widgetCache.deleted,
|
|
795
|
+
}})
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
The `list.shape` discriminant supports `array`, `paginated`, `infinite-array`
|
|
799
|
+
and `infinite-paginated`. Every mutation preserves the surrounding envelope,
|
|
800
|
+
page metadata and `pageParams`; an infinite create changes only the selected
|
|
801
|
+
edge page (`createAt: 'start' | 'end'`). Creates are deduplicated across every
|
|
802
|
+
cached page. `updateMissing` makes an absent update explicitly skip or insert.
|
|
803
|
+
|
|
804
|
+
Filtered query families can additionally declare membership per exact key and
|
|
805
|
+
evidence-aware total reconciliation:
|
|
806
|
+
|
|
807
|
+
```ts
|
|
808
|
+
list: {
|
|
809
|
+
key: ['tickets'],
|
|
810
|
+
shape: 'infinite-paginated',
|
|
811
|
+
createAt: 'start',
|
|
812
|
+
updateMissing: 'skip',
|
|
813
|
+
membership: {
|
|
814
|
+
evaluate: (event, queryKey) => membershipFor(event, queryKey),
|
|
815
|
+
unknown: 'invalidate',
|
|
816
|
+
},
|
|
817
|
+
total: {
|
|
818
|
+
mode: 'reconcile',
|
|
819
|
+
unknown: 'invalidate',
|
|
820
|
+
delta: ({ event, present, membership }) => provenDelta(event, present, membership),
|
|
821
|
+
},
|
|
822
|
+
}
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
Membership is `include | exclude | unknown`. An excluded update removes an
|
|
826
|
+
existing row; an included one follows `updateMissing`. Unknown evidence never
|
|
827
|
+
guesses and either preserves or invalidates the exact query. The conservative
|
|
828
|
+
total policy increments only a non-duplicate create and decrements only a
|
|
829
|
+
delete/update whose membership is observed; unseen IDs are `unknown`. Supply
|
|
830
|
+
`delta` only when the event itself proves a stronger transition. Every page's
|
|
831
|
+
numeric `total` changes together while cursor metadata and `pageParams` remain
|
|
832
|
+
unchanged.
|
|
833
|
+
|
|
834
|
+
The event entity may be richer than a list row. Keep the full value in detail
|
|
835
|
+
cache, project it for lists, and provide the same comparator the backend uses:
|
|
836
|
+
|
|
837
|
+
```ts
|
|
838
|
+
const memberCache = createEntityCacheHandlers<Member, MemberListItem>({
|
|
839
|
+
getId: (member) => member.id,
|
|
840
|
+
getListItemId: (item) => item.id,
|
|
841
|
+
toListItem: (member) => ({
|
|
842
|
+
id: member.id,
|
|
843
|
+
name: member.name,
|
|
844
|
+
joinedAt: member.joinedAt,
|
|
845
|
+
}),
|
|
846
|
+
list: {
|
|
847
|
+
key: (event) => {
|
|
848
|
+
if (event.type !== 'deleted') {
|
|
849
|
+
return ['workspaces', event.entity.workspaceId, 'members']
|
|
850
|
+
}
|
|
851
|
+
if ('workspaceId' in event.payload) {
|
|
852
|
+
return ['workspaces', event.payload.workspaceId, 'members']
|
|
853
|
+
}
|
|
854
|
+
throw new Error('A scoped delete must carry its entity')
|
|
855
|
+
},
|
|
856
|
+
shape: 'array',
|
|
857
|
+
createAt: 'start',
|
|
858
|
+
updateMissing: 'skip',
|
|
859
|
+
compare: (left, right) => left.joinedAt.localeCompare(right.joinedAt),
|
|
860
|
+
},
|
|
861
|
+
detailKey: (event) => ['members', event.id],
|
|
862
|
+
})
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Static `QueryKey` values remain the short path. A key factory receives a typed
|
|
866
|
+
`created | updated | deleted` event, so scoped keys can use the full entity or
|
|
867
|
+
deleted payload without guessing. The same resolved detail key drives the
|
|
868
|
+
`isFresh` echo guard. Shape checks also leave neighbouring detail caches alone
|
|
869
|
+
when a list key is intentionally used as a partial query-key prefix.
|
|
870
|
+
|
|
871
|
+
The helper deliberately does **not** flatten pages, infer filter semantics,
|
|
872
|
+
derive a sort order or replace arbitrary `setQueryData` logic. Those remain
|
|
873
|
+
declared application policies; Stitchkit applies their generic cache mechanics.
|
|
874
|
+
|
|
875
|
+
## Raw binary lane (Bun)
|
|
876
|
+
|
|
877
|
+
For a high-throughput raw binary channel beside Socket.IO, use the orthogonal
|
|
878
|
+
composition boundary from
|
|
879
|
+
[ADR 0020](../decisions/0020-raw-websocket-lane.md).
|
|
880
|
+
|
|
881
|
+
`composeWebSocketHandlers` composes that one handler from several lanes. A raw
|
|
882
|
+
lane stamps its own marker onto `ws.data` at upgrade and is matched positively;
|
|
883
|
+
Socket.IO is the catch-all (`socketIoLane`, placed last) — so the engine's
|
|
884
|
+
opaque `ws.data` is never inspected, and the whole thing stays cast-free.
|
|
885
|
+
|
|
886
|
+
```ts
|
|
887
|
+
import {
|
|
888
|
+
composeWebSocketHandlers,
|
|
889
|
+
createServer,
|
|
890
|
+
createSocketIOServer,
|
|
891
|
+
socketIoLane,
|
|
892
|
+
webSocketLane,
|
|
893
|
+
} from 'stitchkit/server'
|
|
894
|
+
import type { RawRoute } from 'stitchkit/server'
|
|
895
|
+
import type { ServerWebSocket, WebSocketHandler } from 'bun'
|
|
896
|
+
|
|
897
|
+
const socket = await createSocketIOServer<ServerToClientEvents, ClientToServerEvents>({
|
|
898
|
+
cors: { origin: 'https://app.example.com' },
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
// 1. Discriminate the raw lane by a marker on ws.data (a type guard, cast-free
|
|
902
|
+
// — the `in` operator narrows, no `as`).
|
|
903
|
+
interface PcmData { lane: 'pcm'; roomId: string }
|
|
904
|
+
function isPcm(ws: ServerWebSocket<unknown>): ws is ServerWebSocket<PcmData> {
|
|
905
|
+
const data = ws.data
|
|
906
|
+
return typeof data === 'object' && data !== null && 'lane' in data && data.lane === 'pcm'
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// 2. Raw handlers — ws.data is typed PcmData, no casts.
|
|
910
|
+
const pcmHandlers: WebSocketHandler<PcmData> = {
|
|
911
|
+
message(ws, frame) { ws.publish(ws.data.roomId, frame) },
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// 3. An upgrade route stamps the marker (your auth + data live here).
|
|
915
|
+
const pcmRoute: RawRoute = {
|
|
916
|
+
method: 'GET',
|
|
917
|
+
path: '/ws/pcm',
|
|
918
|
+
handler: (req, ctx) => {
|
|
919
|
+
if (!ctx.server) throw new Error('needs a running Bun server')
|
|
920
|
+
const ok = ctx.server.upgrade(req, { data: { lane: 'pcm', roomId: '…' } })
|
|
921
|
+
return ok ? new Response(null) : new Response(null, { status: 400 })
|
|
922
|
+
},
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 4. Compose — raw lane first, Socket.IO last. The tuning is server-wide, so
|
|
926
|
+
// set maxPayloadLength to the most permissive lane (Socket.IO's default is
|
|
927
|
+
// 1 MB, its maxHttpBufferSize).
|
|
928
|
+
const websocket = composeWebSocketHandlers(
|
|
929
|
+
[webSocketLane({ match: isPcm, handlers: pcmHandlers }), socketIoLane(socket.websocket)],
|
|
930
|
+
{ maxPayloadLength: 16 * 1024 * 1024 },
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
createServer({
|
|
934
|
+
services,
|
|
935
|
+
socket,
|
|
936
|
+
websocket,
|
|
937
|
+
rawRoutes: [pcmRoute],
|
|
938
|
+
})
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
Notes:
|
|
942
|
+
|
|
943
|
+
- **Bun-only.** On Node, Socket.IO attaches to the `node:http.Server` `upgrade`
|
|
944
|
+
event (`serveNode({ socket })`); a raw lane there is a separate upgrade
|
|
945
|
+
handler, not this composition. See [ADR 0020](../decisions/0020-raw-websocket-lane.md).
|
|
946
|
+
- The upgrade path must not collide with `/socket.io/*socketPath`.
|
|
947
|
+
- The tuning (`maxPayloadLength`, `idleTimeout`, `backpressureLimit`, …) is
|
|
948
|
+
global — keep `idleTimeout` ≥ Socket.IO needs (> 2 × `pingInterval`).
|
|
949
|
+
- For high throughput, handle backpressure in the raw lane: `ws.send()` returns
|
|
950
|
+
`-1` under pressure; resume on the `drain` callback.
|
|
951
|
+
|
|
952
|
+
## Bring-your-own transport
|
|
953
|
+
|
|
954
|
+
Sometimes the transport is neither HTTP nor Socket.IO — a desktop app whose UI
|
|
955
|
+
webview talks to its own local Bun sidecar over a raw WebSocket, an IPC channel,
|
|
956
|
+
a queue worker. You can still drive it from one `defineContract`: share the
|
|
957
|
+
contract's Zod schemas to validate each inbound frame, run your handlers, and
|
|
958
|
+
reuse the contract metadata below. You own **both** the wire (framing,
|
|
959
|
+
handshake, reconnect) *and* the per-call validate-run loop — stitchkit ships the
|
|
960
|
+
contract and its metadata, not a transport engine. A reliable-RPC-over-raw-
|
|
961
|
+
WebSocket engine would be a competing WebSocket transport
|
|
962
|
+
([ADR 0008](../decisions/0008-thin-wrappers.md)); the wire stays yours.
|
|
963
|
+
|
|
964
|
+
Two pieces of the contract carry straight over to a bring-your-own lane:
|
|
965
|
+
|
|
966
|
+
- **`source` is an open tag.** `TransportSource` is `'http' | 'mcp' | 'agent' |
|
|
967
|
+
'cli' | (string & {})`, so a handler or hook can tell your transport's calls
|
|
968
|
+
apart — tag them `source: 'local-ws'` and read `ctx.source`.
|
|
969
|
+
- **`idempotent` drives replay-on-reconnect** — see below.
|
|
970
|
+
|
|
971
|
+
### Durability — `idempotent` + replay
|
|
972
|
+
|
|
973
|
+
If the sidecar can restart mid-call, the client decides what to do with an
|
|
974
|
+
in-flight request on reconnect from the operation's **idempotency**, declared on
|
|
975
|
+
the contract:
|
|
976
|
+
|
|
977
|
+
```ts
|
|
978
|
+
export const runtimeContract = defineContract({ prefix: 'runtime' }, {
|
|
979
|
+
'tasks.setDone': { method: 'POST', path: '/done', desc: '…',
|
|
980
|
+
idempotent: true, input: taskDone, output: ok }, // re-send after reconnect — same result
|
|
981
|
+
'capture.start': { method: 'POST', path: '/start', desc: '…',
|
|
982
|
+
output: snapshot }, // unset → one-shot, do not re-send
|
|
983
|
+
})
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
`idempotent` rides through to `MethodDef.idempotent`; the core attaches no
|
|
987
|
+
behaviour. Your reconnect logic reads it: replay an idempotent call (the
|
|
988
|
+
durability guarantee — the user's action is not lost), reject a non-idempotent
|
|
989
|
+
one rather than fire a second side effect. Pair it with
|
|
990
|
+
[sticky events](#sticky-events) (`createRetainedTopics`) so a reconnected client
|
|
991
|
+
also catches up on the latest pushed state.
|
|
992
|
+
|
|
993
|
+
This is deliberately *not* a reliable-RPC engine — that would be a competing
|
|
994
|
+
WebSocket transport ([ADR 0008](../decisions/0008-thin-wrappers.md)). stitchkit
|
|
995
|
+
gives you the contract and the metadata (`idempotent`, the open `source` tag,
|
|
996
|
+
`createRetainedTopics`); the wire and the per-call execution stay yours. See
|
|
997
|
+
[ADR 0028](../decisions/0028-revert-contract-dispatcher.md).
|
|
998
|
+
|
|
999
|
+
## Authenticated room registry and replay
|
|
1000
|
+
|
|
1001
|
+
`bindSocketRegistry` composes over `bindRealtimeServer`; it never authenticates
|
|
1002
|
+
a socket a second time and never creates another outbound validator. The
|
|
1003
|
+
identity in `connection.raw.data` has already passed the Socket.IO handshake.
|
|
1004
|
+
|
|
1005
|
+
`rooms(identity)` names the rooms a connecting identity may be in; the registry
|
|
1006
|
+
joins them and refuses a later `join` to any other. The disconnect listener is
|
|
1007
|
+
attached before that lookup is awaited, so a socket that drops while its
|
|
1008
|
+
permissions are still being resolved never becomes a member. `registry.room(name)` mints
|
|
1009
|
+
the opaque `AuthorizedSocketRoom` for a name — the token proves the name came
|
|
1010
|
+
through this registry, not that anyone is in the room, so a room with no open
|
|
1011
|
+
tab is an ordinary `emitTo` target that reports zero recipients. The registry
|
|
1012
|
+
owns join/leave, multiple sockets per identity, immutable snapshots and
|
|
1013
|
+
listener cleanup.
|
|
1014
|
+
|
|
1015
|
+
Replay is revisioned: while a socket's snapshot is open, frames sent through
|
|
1016
|
+
`emitTo` to its rooms are held back and delivered after the snapshot, so the
|
|
1017
|
+
socket never sees a delta for state its snapshot already contains, nor a
|
|
1018
|
+
snapshot that predates a delta it already received. A snapshot taken across a
|
|
1019
|
+
`revision` change is retried up to `replayAttempts` (default 3). The hold-back buffer is
|
|
1020
|
+
bounded by `maxBufferedFrames` (default 1000): past it the attempt is abandoned
|
|
1021
|
+
and retried, and when the attempts are spent `onResyncRequired(socketId,
|
|
1022
|
+
identity)` fires with nothing half-delivered — the application decides what a
|
|
1023
|
+
resync means for that socket. A retry relies on the fresh snapshot carrying
|
|
1024
|
+
what the abandoned frames carried, which holds when every emitted change also
|
|
1025
|
+
moves `revision`; without a `revision` the registry has no way to tell, so
|
|
1026
|
+
declare one wherever replay matters. Room tokens are recognised, not
|
|
1027
|
+
remembered: a token the application keeps stays valid for the life of the
|
|
1028
|
+
registry, and a name seen once does not live on in a map. This ordering exists only for frames that go
|
|
1029
|
+
through `emitTo`; a broadcast through `realtime.to(room).emit` reaches the
|
|
1030
|
+
socket immediately, replay or not, so keep room traffic whose order matters
|
|
1031
|
+
relative to the snapshot on the registry.
|