stitchkit 0.74.0 → 0.75.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.
Files changed (77) hide show
  1. package/dist/agent-runtime/sqlite-purge.d.ts +3 -3
  2. package/dist/agent-runtime/sqlite-purge.d.ts.map +1 -1
  3. package/dist/agent-runtime/sqlite.d.ts +5 -17
  4. package/dist/agent-runtime/sqlite.d.ts.map +1 -1
  5. package/dist/agent-runtime-coding-tools.js +2 -1
  6. package/dist/agent-runtime-harness.js +5 -4
  7. package/dist/agent-runtime-sqlite-bun.d.ts +1 -1
  8. package/dist/agent-runtime-sqlite-bun.d.ts.map +1 -1
  9. package/dist/agent-runtime-sqlite-node.d.ts +1 -1
  10. package/dist/agent-runtime-sqlite-node.d.ts.map +1 -1
  11. package/dist/agent-runtime.js +8 -7
  12. package/dist/application/diagnostic-journal-lock.d.ts +14 -0
  13. package/dist/application/diagnostic-journal-lock.d.ts.map +1 -1
  14. package/dist/application/keyspace-sqlite.d.ts +31 -0
  15. package/dist/application/keyspace-sqlite.d.ts.map +1 -0
  16. package/dist/application/keyspace.d.ts +142 -0
  17. package/dist/application/keyspace.d.ts.map +1 -0
  18. package/dist/application/watch-hub.d.ts +113 -0
  19. package/dist/application/watch-hub.d.ts.map +1 -0
  20. package/dist/application.d.ts +4 -0
  21. package/dist/application.d.ts.map +1 -1
  22. package/dist/application.js +475 -3
  23. package/dist/cli.js +7 -6
  24. package/dist/contract/index.js +2 -1
  25. package/dist/{index-aczggrty.js → index-28jsdwt1.js} +1 -1
  26. package/dist/index-35z5h2ty.js +594 -0
  27. package/dist/{index-f6n5n7nz.js → index-4kp4fxvp.js} +4 -18
  28. package/dist/{index-118v0z41.js → index-52400rdd.js} +8 -3
  29. package/dist/{index-da1aqnhb.js → index-65kathdm.js} +6 -4
  30. package/dist/{index-jqtsc9mj.js → index-6wr9f6qm.js} +2 -2
  31. package/dist/{index-7qy2ex0m.js → index-885chjxq.js} +1 -1
  32. package/dist/{index-k2zczx1g.js → index-9c5dbr5q.js} +1 -1
  33. package/dist/{index-s2rchahr.js → index-9my2n66s.js} +4 -4
  34. package/dist/{index-nemjkxjp.js → index-e0a57ymn.js} +1 -1
  35. package/dist/{index-vbf2p6me.js → index-endzd8sj.js} +1 -1
  36. package/dist/{index-vj3vvpaa.js → index-hsz2mmk0.js} +1 -17
  37. package/dist/{index-s4c8wy8m.js → index-k68e5jz0.js} +2 -2
  38. package/dist/index-rxfy4cq7.js +17 -0
  39. package/dist/{index-58jzmnn4.js → index-v5dm4bwq.js} +1 -1
  40. package/dist/{index-m668wzyc.js → index-v7xjfy10.js} +1 -1
  41. package/dist/index-vdvmcy7r.js +22 -0
  42. package/dist/{index-ezmn6ac6.js → index-xgahrwdf.js} +3 -3
  43. package/dist/index.js +26 -611
  44. package/dist/internal/sqlite.d.ts +27 -0
  45. package/dist/internal/sqlite.d.ts.map +1 -0
  46. package/dist/internal/stable-digest.d.ts +24 -0
  47. package/dist/internal/stable-digest.d.ts.map +1 -0
  48. package/dist/live/events.d.ts +155 -0
  49. package/dist/live/events.d.ts.map +1 -0
  50. package/dist/live/watch-client.d.ts +66 -0
  51. package/dist/live/watch-client.d.ts.map +1 -0
  52. package/dist/live/watch-contract.d.ts +157 -0
  53. package/dist/live/watch-contract.d.ts.map +1 -0
  54. package/dist/live.d.ts +18 -0
  55. package/dist/live.d.ts.map +1 -0
  56. package/dist/live.js +284 -0
  57. package/dist/node.js +5 -4
  58. package/dist/observability/index.js +4 -3
  59. package/dist/primitives.js +2 -1
  60. package/dist/remote.js +3 -2
  61. package/dist/server/create.d.ts.map +1 -1
  62. package/dist/server/event-bus.d.ts +49 -1
  63. package/dist/server/event-bus.d.ts.map +1 -1
  64. package/dist/server/index.d.ts +2 -1
  65. package/dist/server/index.d.ts.map +1 -1
  66. package/dist/server/index.js +224 -12
  67. package/dist/server/middleware/trust-fence.d.ts +116 -0
  68. package/dist/server/middleware/trust-fence.d.ts.map +1 -0
  69. package/dist/server/types.d.ts +16 -1
  70. package/dist/server/types.d.ts.map +1 -1
  71. package/dist/testing.js +3 -2
  72. package/dist/tool-invoker.js +6 -5
  73. package/dist/tools/mcp-round.d.ts.map +1 -1
  74. package/dist/tools.js +15 -30
  75. package/llms-full.txt +417 -7
  76. package/llms.txt +1 -0
  77. package/package.json +6 -2
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The minimal synchronous SQLite boundary the framework types against.
3
+ *
4
+ * Deliberately three methods and nothing else: it is satisfied structurally by
5
+ * `bun:sqlite`'s `Database` and by a thin wrapper over `node:sqlite`, so a
6
+ * caller passes the handle it already holds instead of adopting a driver of
7
+ * ours. → ADR 0142.
8
+ *
9
+ * One name for one thing. This was `AgentRuntimeSqliteDatabase` while the agent
10
+ * runtime was its only user; a second user made the name wrong rather than
11
+ * merely long — a keyspace typed against an `AgentRuntime*` boundary reads as
12
+ * a dependency on the agent runtime, which it is not.
13
+ */
14
+ export type SqliteValue = string | number | bigint | null | Uint8Array;
15
+ export interface SqliteStatement {
16
+ get(...parameters: SqliteValue[]): unknown;
17
+ all(...parameters: SqliteValue[]): readonly unknown[];
18
+ run(...parameters: SqliteValue[]): {
19
+ changes: number;
20
+ };
21
+ }
22
+ export interface SqliteDatabase {
23
+ exec(sql: string): void;
24
+ prepare(sql: string): SqliteStatement;
25
+ close(): void;
26
+ }
27
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/internal/sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,UAAU,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAC3C,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,SAAS,OAAO,EAAE,CAAC;IACtD,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,KAAK,IAAI,IAAI,CAAC;CACf"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The value with every object's keys sorted, recursively — arrays keep their
3
+ * order, because in an array order *is* the value.
4
+ *
5
+ * `Object.keys` returns own enumerable keys only, so nothing from a prototype
6
+ * reaches the digest.
7
+ */
8
+ export declare function stableValue(value: unknown): unknown;
9
+ /**
10
+ * A bounded, order-independent digest of a call's arguments.
11
+ *
12
+ * SHA-256 over the key-sorted JSON, base64url without padding. Bounded because
13
+ * the key travels on the wire and arguments do not have a bounded size; a hash
14
+ * rather than the JSON itself so a large argument object does not become a
15
+ * large subscription key repeated in every frame.
16
+ *
17
+ * What it cannot do, stated because the limit is easy to walk into: values
18
+ * `JSON.stringify` drops or transforms — `undefined` members, a `Date`, a `Map`,
19
+ * a `BigInt` (which throws) — are not distinguished, or not survivable, here.
20
+ * Contract arguments are parsed JSON, so this is the argument shape by
21
+ * construction; a caller digesting something else has to say what it means.
22
+ */
23
+ export declare function argumentsDigest(args: Record<string, unknown>): Promise<string>;
24
+ //# sourceMappingURL=stable-digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-digest.d.ts","sourceRoot":"","sources":["../../src/internal/stable-digest.ts"],"names":[],"mappings":"AAkBA;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQnD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpF"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * An event declaration beside the operation contract.
3
+ *
4
+ * `defineContract` says what a caller may *ask*. This says what the server may
5
+ * *announce*: a topic, the schema of its one payload, and how it is delivered
6
+ * to listeners in this process. One declaration, so a topic cannot be published
7
+ * in one shape and parsed in another — the failure `defineContract` exists to
8
+ * make impossible for requests, made impossible for announcements too.
9
+ *
10
+ * **This is a declaration, not a transport.** The wire is the Socket.IO realtime
11
+ * contract stitchkit already ships: {@link toRealtimeContract} projects this
12
+ * declaration onto `RealtimeContract`, and `bindRealtimeServer` /
13
+ * `bindRealtimeClient` carry it with the validation, rejection reporting and
14
+ * room semantics they already have. There is no second socket, no second
15
+ * validator and no second `on()`. That is deliberate and it is the whole point:
16
+ * ADR 0009 records a hand-rolled event stack in this repository that reached
17
+ * about 700 lines, was never adopted by a single consumer, and was deleted —
18
+ * with the lesson written into ADR 0008 as *wrap the transport the consumers
19
+ * already run on*. This declaration does not reopen that decision.
20
+ *
21
+ * → ADR 0150.
22
+ */
23
+ import { z } from 'zod';
24
+ import type { RealtimeContract, RealtimeEventRegistry } from '../realtime/contract.js';
25
+ /**
26
+ * How a topic reaches the listeners registered for it **in this process**.
27
+ *
28
+ * Delivery to remote subscribers is always observation: a browser cannot delay
29
+ * or veto a server's announcement, so the mode says nothing about the wire. It
30
+ * says what the *server's own* listeners may do.
31
+ *
32
+ * - `emit` — announce and continue. Listeners run concurrently, a failing one
33
+ * is isolated from the others, and the caller does not wait.
34
+ * - `serial` — listeners run one at a time, in registration order, and the
35
+ * caller waits for all of them. For work whose order is the point.
36
+ * - `decision` — listeners vote. Each returns `allow`, `deny` with a reason, or
37
+ * `defer`; the first `deny` wins and the rest are not consulted.
38
+ */
39
+ export type EventDeliveryMode = 'emit' | 'serial' | 'decision';
40
+ /**
41
+ * One listener's vote on a `decision` topic.
42
+ *
43
+ * `defer` is a real answer — "not my call" — and it is distinct from `allow` on
44
+ * purpose: an event where every listener defers is one nobody claimed, and what
45
+ * should happen then is a policy the topic has to state rather than a default
46
+ * somebody guesses. See `whenAllDefer`.
47
+ */
48
+ export type EventDecision = {
49
+ readonly outcome: 'allow';
50
+ } | {
51
+ readonly outcome: 'deny';
52
+ readonly reason: string;
53
+ } | {
54
+ readonly outcome: 'defer';
55
+ };
56
+ /** What a `decision` topic concludes when every listener deferred, or there were none. */
57
+ export type EventUndecided = 'allow' | 'deny';
58
+ export interface EventTopicDeclaration<TSchema extends z.ZodType = z.ZodType> {
59
+ /** The payload schema. One payload per topic — a topic is not a function call. */
60
+ readonly schema: TSchema;
61
+ readonly mode: EventDeliveryMode;
62
+ /**
63
+ * Required on a `decision` topic, refused on any other.
64
+ *
65
+ * There is no default, and that is the point: whichever value were chosen as
66
+ * the default would be a standing `allow` or a standing `deny` applied to
67
+ * every topic whose author never thought about it. Both are decisions; a
68
+ * default makes them silently.
69
+ */
70
+ readonly whenAllDefer?: EventUndecided;
71
+ /**
72
+ * How long one listener may take on a `serial` or `decision` topic before the
73
+ * dispatcher stops waiting for it, in milliseconds.
74
+ *
75
+ * Required on those two modes, refused on `emit` (which never waits). A
76
+ * listener that never settles would otherwise hang the caller forever, and a
77
+ * caller hanging forever is indistinguishable from a caller doing work.
78
+ * A `decision` listener that runs out of time votes `deny` — a vote that
79
+ * never arrived cannot be read as consent.
80
+ */
81
+ readonly listenerTimeoutMs?: number;
82
+ }
83
+ export type EventTopicRegistry = Record<string, EventTopicDeclaration>;
84
+ export interface EventsConfig {
85
+ /**
86
+ * Prefixed onto every topic name, separated by a dot.
87
+ *
88
+ * The prefixed form is the **only** name the topic has: it is the key of the
89
+ * declaration's `topics`, the event name on the wire, and the string passed to
90
+ * `on`. The short key in the literal is where the full name is built, not a
91
+ * second name for the same topic — a topic addressable two ways is a topic
92
+ * that will be published one way and subscribed the other.
93
+ */
94
+ readonly prefix?: string;
95
+ }
96
+ /** The wire name of a topic: `prefix.name`, or just `name` when no prefix is declared. */
97
+ export type WireTopic<TPrefix, TName extends string> = TPrefix extends string ? `${TPrefix}.${TName}` : TName;
98
+ export interface EventsDeclaration<TPrefix extends string | undefined, TTopics extends EventTopicRegistry> {
99
+ readonly prefix: TPrefix;
100
+ /** Keyed by wire topic — see {@link EventsConfig.prefix}. */
101
+ readonly topics: {
102
+ readonly [TName in keyof TTopics & string as WireTopic<TPrefix, TName>]: TTopics[TName];
103
+ };
104
+ }
105
+ /** The payload type of each topic, keyed by its wire name. */
106
+ export type EventPayloads<TDeclaration> = TDeclaration extends EventsDeclaration<infer _TPrefix, infer _TTopics> ? {
107
+ [TTopic in keyof TDeclaration['topics']]: TDeclaration['topics'][TTopic] extends {
108
+ schema: infer TSchema extends z.ZodType;
109
+ } ? z.output<TSchema> : never;
110
+ } : never;
111
+ /** Topics of one declaration whose declared mode is `TMode`. */
112
+ export type EventTopicsOfMode<TDeclaration, TMode extends EventDeliveryMode> = TDeclaration extends EventsDeclaration<infer _TPrefix, infer _TTopics> ? {
113
+ [TTopic in keyof TDeclaration['topics']]: TDeclaration['topics'][TTopic] extends {
114
+ mode: TMode;
115
+ } ? TTopic : never;
116
+ }[keyof TDeclaration['topics']] & string : never;
117
+ /**
118
+ * Declare a set of topics.
119
+ *
120
+ * ```ts
121
+ * const events = defineEvents(
122
+ * { prefix: 'notes' },
123
+ * {
124
+ * 'changed': { schema: z.object({ folder: z.string() }), mode: 'emit' },
125
+ * 'archiving': {
126
+ * schema: z.object({ folder: z.string() }),
127
+ * mode: 'decision',
128
+ * whenAllDefer: 'allow',
129
+ * listenerTimeoutMs: 2_000,
130
+ * },
131
+ * },
132
+ * );
133
+ * // events.topics has the keys 'notes.changed' and 'notes.archiving'
134
+ * ```
135
+ */
136
+ export declare function defineEvents<const TConfig extends EventsConfig, const TTopics extends EventTopicRegistry>(config: TConfig, topics: TTopics): EventsDeclaration<TConfig extends {
137
+ prefix: infer TPrefix extends string;
138
+ } ? TPrefix : undefined, TTopics>;
139
+ /**
140
+ * Project a declaration onto the realtime contract, so the existing validated
141
+ * socket carries it.
142
+ *
143
+ * A realtime event's `args` is the **tuple of wire arguments**, not a payload,
144
+ * so a one-payload topic becomes a one-element tuple. That single mapping is the
145
+ * only place the two shapes meet, and it is why a test that binds one
146
+ * declaration to both ends proves less than it looks: both ends would project
147
+ * identically, so a wrong projection stays invisible. The test that measures it
148
+ * injects a raw frame past the validating wrapper.
149
+ *
150
+ * Announcements travel server → client. `clientToServer` is empty by
151
+ * construction: a client that could publish a server's topic would make the
152
+ * declared publisher a suggestion.
153
+ */
154
+ export declare function toRealtimeContract<TPrefix extends string | undefined, TTopics extends EventTopicRegistry>(declaration: EventsDeclaration<TPrefix, TTopics>): RealtimeContract<RealtimeEventRegistry, Record<string, never>>;
155
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/live/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The client half of a watched read.
3
+ *
4
+ * `watch.notes.list({ folder: 'a' })` returns a handle. Subscribe to it and the
5
+ * values arrive; unsubscribe and, once nobody is left, the server is told to
6
+ * stop reading. Two components asking the same question share one subscription
7
+ * and one server-side read, which is the arrangement the hub exists for and
8
+ * which a per-component `useEffect` cannot reach.
9
+ *
10
+ * ## Why it is built from the contract, not from a method
11
+ *
12
+ * `watch(api.notes.list, args)` is the shape one would write first, and it
13
+ * cannot work: a client method is a bare closure with a `withOptions` property
14
+ * and no identity on it, deliberately — it has to survive being handed to a
15
+ * `map`. Passing one here gives an anonymous function, and the server needs to
16
+ * know *what* to re-read. So the watch client is built from the contract, like
17
+ * `createUrlBuilder`, and the identity is the contract's own
18
+ * `(prefix, endpoint key)` — the same pair the server labels every request with.
19
+ *
20
+ * ## Retention
21
+ *
22
+ * The last value of a key is kept while anyone holds it, and for `holdMs` after
23
+ * the last subscriber leaves. A component that unmounts and remounts inside that
24
+ * window paints immediately from memory. The key digest is cached too, so the
25
+ * second subscription resolves synchronously — computing it is a promise, and a
26
+ * value that arrives on a later microtask is not "before the network" in any
27
+ * sense a rendering component can use.
28
+ */
29
+ import type { ContractDef, EndpointDef } from '../contract/define.js';
30
+ import { type WatchKey, type WatchStateFrame } from './watch-contract.js';
31
+ export interface WatchListeners<TValue> {
32
+ value(value: TValue): void;
33
+ /** Phase and, when unhealthy, the read's own code and message. */
34
+ state?(state: WatchStateFrame): void;
35
+ }
36
+ export interface WatchHandle<TValue> {
37
+ /** Returns the unsubscribe. The retained value, if any, arrives before it returns. */
38
+ subscribe(listeners: WatchListeners<TValue>): () => void;
39
+ /** Drops every listener this handle registered and releases the key. */
40
+ close(): void;
41
+ }
42
+ /** The transport half a watch client needs — a bound realtime client for `watchContract`. */
43
+ export interface WatchTransport {
44
+ on(event: string, handler: (payload: never) => void): () => void;
45
+ emit(event: string, payload: unknown): void;
46
+ request(event: string, payload: unknown, options: {
47
+ timeoutMs: number;
48
+ }): Promise<{
49
+ accepted: boolean;
50
+ reason?: string;
51
+ }>;
52
+ }
53
+ export interface WatchClientConfig {
54
+ readonly transport: WatchTransport;
55
+ /** How long a key's value is retained after its last subscriber. Default 0. */
56
+ readonly holdMs?: number;
57
+ /** Deadline for the `open` acknowledgement. Default 10000. */
58
+ readonly openTimeoutMs?: number;
59
+ /** Called when the server refuses to open a watch, in its own words. */
60
+ readonly onRefused?: (key: WatchKey, reason: string) => void;
61
+ }
62
+ export type TypedWatchClient<T extends Record<string, EndpointDef>> = {
63
+ [K in keyof T]: (args?: Record<string, unknown>) => WatchHandle<unknown>;
64
+ };
65
+ export declare function createWatchClient<T extends Record<string, EndpointDef>>(contract: ContractDef<T, string>, config: WatchClientConfig): TypedWatchClient<T>;
66
+ //# sourceMappingURL=watch-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-client.d.ts","sourceRoot":"","sources":["../../src/live/watch-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAKL,KAAK,QAAQ,EACb,KAAK,eAAe,EAGrB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,cAAc,CAAC,MAAM;IACpC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kEAAkE;IAClE,KAAK,CAAC,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,WAAW,CAAC,MAAM;IACjC,sFAAsF;IACtF,SAAS,CAAC,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC;IACzD,wEAAwE;IACxE,KAAK,IAAI,IAAI,CAAC;CACf;AAED,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACjE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,OAAO,CACL,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAC7B,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D;AAcD,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI;KACnE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC;CACzE,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrE,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,EAChC,MAAM,EAAE,iBAAiB,GACxB,gBAAgB,CAAC,CAAC,CAAC,CAqKrB"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * The wire protocol of a watched read — one contract, shared by both ends.
3
+ *
4
+ * A watched read is a GET that the server re-runs when something it depends on
5
+ * changes, pushing the new answer to everyone watching. The protocol is four
6
+ * events on the realtime contract the application already runs, and it is
7
+ * declared once here so the hub and the client cannot drift: a second copy of
8
+ * these shapes on the client is exactly the hand-written frame parsing that
9
+ * `defineContract` exists to end.
10
+ *
11
+ * The state vocabulary is `LiveStatePhase` and `LiveStateStopReason` — the ones
12
+ * the live-state controller already publishes — and not a second, poorer pair.
13
+ * A reader with one component on a controller's phase and another on a watch's
14
+ * state must be able to compare them, and `live | unavailable` beside
15
+ * `idle | opening | live | resync-required | unavailable | closed` cannot be
16
+ * compared at all. It also supplies the third answer a two-word vocabulary
17
+ * loses: `opening` — subscribed, nothing read yet — which is neither healthy nor
18
+ * broken, and rendering it as "unavailable" tells a user something is wrong when
19
+ * the truth is that it is early.
20
+ */
21
+ import { z } from 'zod';
22
+ /** The identity of one watched read: an operation plus the arguments it was asked with. */
23
+ export declare const WatchKeySchema: z.ZodReadonly<z.ZodObject<{
24
+ service: z.ZodString;
25
+ action: z.ZodString;
26
+ digest: z.ZodString;
27
+ }, z.core.$strict>>;
28
+ export type WatchKey = z.infer<typeof WatchKeySchema>;
29
+ export declare const WatchOpenSchema: z.ZodReadonly<z.ZodObject<{
30
+ key: z.ZodReadonly<z.ZodObject<{
31
+ service: z.ZodString;
32
+ action: z.ZodString;
33
+ digest: z.ZodString;
34
+ }, z.core.$strict>>;
35
+ args: z.ZodUnknown;
36
+ }, z.core.$strip>>;
37
+ export declare const WatchAcceptedSchema: z.ZodReadonly<z.ZodObject<{
38
+ accepted: z.ZodBoolean;
39
+ reason: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strict>>;
41
+ export declare const WatchValueSchema: z.ZodReadonly<z.ZodObject<{
42
+ key: z.ZodReadonly<z.ZodObject<{
43
+ service: z.ZodString;
44
+ action: z.ZodString;
45
+ digest: z.ZodString;
46
+ }, z.core.$strict>>;
47
+ revision: z.ZodNumber;
48
+ value: z.ZodUnknown;
49
+ }, z.core.$strip>>;
50
+ export declare const WatchStateSchema: z.ZodReadonly<z.ZodObject<{
51
+ key: z.ZodReadonly<z.ZodObject<{
52
+ service: z.ZodString;
53
+ action: z.ZodString;
54
+ digest: z.ZodString;
55
+ }, z.core.$strict>>;
56
+ phase: z.ZodEnum<{
57
+ closed: "closed";
58
+ idle: "idle";
59
+ live: "live";
60
+ opening: "opening";
61
+ "resync-required": "resync-required";
62
+ unavailable: "unavailable";
63
+ }>;
64
+ reason: z.ZodOptional<z.ZodEnum<{
65
+ "buffer-overflow": "buffer-overflow";
66
+ "controller-capacity": "controller-capacity";
67
+ "controller-error": "controller-error";
68
+ gap: "gap";
69
+ "source-error": "source-error";
70
+ "source-unavailable": "source-unavailable";
71
+ }>>;
72
+ code: z.ZodOptional<z.ZodString>;
73
+ message: z.ZodOptional<z.ZodString>;
74
+ }, z.core.$strip>>;
75
+ export type WatchValueFrame = z.infer<typeof WatchValueSchema>;
76
+ export type WatchStateFrame = z.infer<typeof WatchStateSchema>;
77
+ export declare const WATCH_OPEN = "stitchkit.watch.open";
78
+ export declare const WATCH_CLOSE = "stitchkit.watch.close";
79
+ export declare const WATCH_VALUE = "stitchkit.watch.value";
80
+ export declare const WATCH_STATE = "stitchkit.watch.state";
81
+ /**
82
+ * The realtime contract a watched read travels on.
83
+ *
84
+ * Merge it into the application's own contract — `{ serverToClient: {...app,
85
+ * ...watchContract.serverToClient}, … }` — or bind it on its own. Event names
86
+ * are namespaced so a merge cannot collide with an application's topics.
87
+ */
88
+ export declare const watchContract: {
89
+ readonly serverToClient: {
90
+ readonly "stitchkit.watch.value": {
91
+ readonly args: z.ZodTuple<[z.ZodReadonly<z.ZodObject<{
92
+ key: z.ZodReadonly<z.ZodObject<{
93
+ service: z.ZodString;
94
+ action: z.ZodString;
95
+ digest: z.ZodString;
96
+ }, z.core.$strict>>;
97
+ revision: z.ZodNumber;
98
+ value: z.ZodUnknown;
99
+ }, z.core.$strip>>], null>;
100
+ };
101
+ readonly "stitchkit.watch.state": {
102
+ readonly args: z.ZodTuple<[z.ZodReadonly<z.ZodObject<{
103
+ key: z.ZodReadonly<z.ZodObject<{
104
+ service: z.ZodString;
105
+ action: z.ZodString;
106
+ digest: z.ZodString;
107
+ }, z.core.$strict>>;
108
+ phase: z.ZodEnum<{
109
+ closed: "closed";
110
+ idle: "idle";
111
+ live: "live";
112
+ opening: "opening";
113
+ "resync-required": "resync-required";
114
+ unavailable: "unavailable";
115
+ }>;
116
+ reason: z.ZodOptional<z.ZodEnum<{
117
+ "buffer-overflow": "buffer-overflow";
118
+ "controller-capacity": "controller-capacity";
119
+ "controller-error": "controller-error";
120
+ gap: "gap";
121
+ "source-error": "source-error";
122
+ "source-unavailable": "source-unavailable";
123
+ }>>;
124
+ code: z.ZodOptional<z.ZodString>;
125
+ message: z.ZodOptional<z.ZodString>;
126
+ }, z.core.$strip>>], null>;
127
+ };
128
+ };
129
+ readonly clientToServer: {
130
+ readonly "stitchkit.watch.open": {
131
+ readonly args: z.ZodTuple<[z.ZodReadonly<z.ZodObject<{
132
+ key: z.ZodReadonly<z.ZodObject<{
133
+ service: z.ZodString;
134
+ action: z.ZodString;
135
+ digest: z.ZodString;
136
+ }, z.core.$strict>>;
137
+ args: z.ZodUnknown;
138
+ }, z.core.$strip>>], null>;
139
+ readonly ack: z.ZodReadonly<z.ZodObject<{
140
+ accepted: z.ZodBoolean;
141
+ reason: z.ZodOptional<z.ZodString>;
142
+ }, z.core.$strict>>;
143
+ };
144
+ readonly "stitchkit.watch.close": {
145
+ readonly args: z.ZodTuple<[z.ZodReadonly<z.ZodObject<{
146
+ key: z.ZodReadonly<z.ZodObject<{
147
+ service: z.ZodString;
148
+ action: z.ZodString;
149
+ digest: z.ZodString;
150
+ }, z.core.$strict>>;
151
+ }, z.core.$strip>>], null>;
152
+ };
153
+ };
154
+ };
155
+ /** The wire key as one string — what a map is keyed by on both ends. */
156
+ export declare function watchKeyString(key: WatchKey): string;
157
+ //# sourceMappingURL=watch-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-contract.d.ts","sourceRoot":"","sources":["../../src/live/watch-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,2FAA2F;AAC3F,eAAO,MAAM,cAAc;;;;mBAkBd,CAAC;AACd,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,eAAO,MAAM,eAAe;;;;;;;kBAAkE,CAAC;AAE/F,eAAO,MAAM,mBAAmB;;;mBAOnB,CAAC;AAEd,eAAO,MAAM,gBAAgB;;;;;;;;kBAUhB,CAAC;AAEd,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;kBAShB,CAAC;AAEd,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC/D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE/D,eAAO,MAAM,UAAU,yBAAyB,CAAC;AACjD,eAAO,MAAM,WAAW,0BAA0B,CAAC;AACnD,eAAO,MAAM,WAAW,0BAA0B,CAAC;AACnD,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;qBAEL,IAAI;;;;;;;;;;;qBACJ,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAGL,IAAI;;;;;;;;qBAA8B,GAAG;;;;;;qBACpC,IAAI;;;;;;;;;CAKxB,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAEpD"}
package/dist/live.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `stitchkit/live` — declarations for the things that change while a caller is
3
+ * watching: announcements and watched reads.
4
+ *
5
+ * Browser-safe and evolving. Browser-safe because both halves are declarations
6
+ * plus a client, and a client that cannot run in a browser is not a client.
7
+ * Evolving because the shape here is being found with its first consumers — the
8
+ * entrypoint says so rather than leaving a reader to discover it in a minor.
9
+ *
10
+ * What lives here is deliberately small: this entrypoint owns no transport. The
11
+ * wire is the Socket.IO realtime contract from `stitchkit`, the in-process
12
+ * delivery is `createEventBus` from `stitchkit/server`, and the server halves of
13
+ * a watched read live in `stitchkit/application`. → ADR 0150.
14
+ */
15
+ export { defineEvents, type EventDecision, type EventDeliveryMode, type EventPayloads, type EventsConfig, type EventsDeclaration, type EventTopicDeclaration, type EventTopicRegistry, type EventTopicsOfMode, type EventUndecided, toRealtimeContract, type WireTopic, } from './live/events.js';
16
+ export { createWatchClient, type TypedWatchClient, type WatchClientConfig, type WatchHandle, type WatchListeners, type WatchTransport, } from './live/watch-client.js';
17
+ export { WATCH_CLOSE, WATCH_OPEN, WATCH_STATE, WATCH_VALUE, type WatchKey, WatchKeySchema, type WatchStateFrame, WatchStateSchema, type WatchValueFrame, WatchValueSchema, watchContract, watchKeyString, } from './live/watch-contract.js';
18
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,KAAK,QAAQ,EACb,cAAc,EACd,KAAK,eAAe,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,uBAAuB,CAAC"}