statewire 0.14.0 → 0.16.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/README.md +29 -80
- package/dist/client/connection.d.ts +19 -6
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +79 -40
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +1 -0
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +5 -0
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +2 -0
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +98 -19
- package/dist/client/session.js.map +1 -1
- package/dist/host/adapter.d.ts +1 -1
- package/dist/host/adapter.d.ts.map +1 -1
- package/dist/host/adapter.js +7 -1
- package/dist/host/adapter.js.map +1 -1
- package/dist/host/host.d.ts +1 -0
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +45 -5
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +75 -47
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +23 -7
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +275 -74
- package/dist/host/socket.js.map +1 -1
- package/dist/host/work.d.ts +7 -0
- package/dist/host/work.d.ts.map +1 -0
- package/dist/host/work.js +53 -0
- package/dist/host/work.js.map +1 -0
- package/dist/host-internal.d.ts +35 -5
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host-internal.js +2 -1
- package/dist/host.d.ts +16 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/statewire.d.ts +13 -4
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js.map +1 -1
- package/dist/transports/sse.d.ts +2 -5
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +50 -59
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.js +4 -4
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +3 -3
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +57 -44
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util/randomId.js +1 -1
- package/dist/util/randomId.js.map +1 -1
- package/dist/wire/documents.d.ts +20 -0
- package/dist/wire/documents.d.ts.map +1 -0
- package/dist/wire/documents.js +74 -0
- package/dist/wire/documents.js.map +1 -0
- package/dist/wire/limits.d.ts +10 -0
- package/dist/wire/limits.d.ts.map +1 -0
- package/dist/wire/limits.js +11 -0
- package/dist/wire/limits.js.map +1 -0
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +1 -1
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +1 -2
- package/dist/wire/ops.js.map +1 -1
- package/dist/wire/version.d.ts +7 -0
- package/dist/wire/version.d.ts.map +1 -0
- package/dist/wire/version.js +8 -0
- package/dist/wire/version.js.map +1 -0
- package/package.json +1 -1
- package/src/client/connection.ts +159 -52
- package/src/client/core.ts +6 -0
- package/src/client/session.ts +105 -32
- package/src/host/adapter.ts +10 -2
- package/src/host/host.ts +54 -4
- package/src/host/http.ts +132 -66
- package/src/host/socket.ts +410 -81
- package/src/host/work.ts +50 -0
- package/src/host-internal.ts +32 -4
- package/src/host.ts +18 -2
- package/src/index.ts +2 -2
- package/src/statewire.ts +18 -4
- package/src/transports/sse.ts +70 -78
- package/src/transports/testing.ts +4 -4
- package/src/transports/ws.ts +36 -55
- package/src/types.ts +1 -0
- package/src/util/randomId.ts +3 -3
- package/src/wire/documents.ts +109 -0
- package/src/wire/limits.ts +6 -0
- package/src/wire/meta.ts +6 -1
- package/src/wire/ops.ts +1 -2
- package/src/wire/version.ts +3 -0
package/dist/statewire.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StatewireHost } from "./host.js";
|
|
1
2
|
import { TERMINAL } from "./client/connection.js";
|
|
2
3
|
import { Statewire } from "./types.js";
|
|
3
4
|
import { ResourceElement } from "@assistant-ui/tap";
|
|
@@ -37,6 +38,7 @@ declare namespace StatewireClient {
|
|
|
37
38
|
state: NonNullable<TState>;
|
|
38
39
|
}) => void;
|
|
39
40
|
onConnectionChange?: (connection: Connection) => void;
|
|
41
|
+
onError?: (error: ConnectionError) => void;
|
|
40
42
|
/** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */
|
|
41
43
|
failureCovered?: (state: NonNullable<TState>, entry: FailedCommand) => boolean;
|
|
42
44
|
};
|
|
@@ -61,14 +63,15 @@ declare namespace StatewireClient {
|
|
|
61
63
|
/** `cause`/`nextRetryAt`/`lastError` are present only while waiting out the backoff timer; a dial in flight carries `attempt` alone. */
|
|
62
64
|
type ConnectionState = {
|
|
63
65
|
status: "disconnected";
|
|
64
|
-
reason: "deferred" | "idle" | "gone";
|
|
66
|
+
reason: "deferred" | "idle" | "gone" | "forbidden" | "protocol-error" | "unauthorized";
|
|
65
67
|
message?: string;
|
|
66
68
|
payload?: unknown;
|
|
67
69
|
} | {
|
|
68
70
|
status: "connecting";
|
|
69
71
|
/** 0 = the episode's first dial, >0 = retry. */
|
|
70
72
|
attempt: number;
|
|
71
|
-
cause?: "dropped" | "
|
|
73
|
+
cause?: "dropped" | "reconnect" | "error" | "unauthorized";
|
|
74
|
+
code?: string;
|
|
72
75
|
nextRetryAt?: number;
|
|
73
76
|
lastError?: string;
|
|
74
77
|
message?: string;
|
|
@@ -76,6 +79,7 @@ declare namespace StatewireClient {
|
|
|
76
79
|
status: "connected";
|
|
77
80
|
};
|
|
78
81
|
type Connection = ConnectionState & {
|
|
82
|
+
error?: ConnectionError;
|
|
79
83
|
reconnect: () => void;
|
|
80
84
|
/** True while retrying (`connecting` with `attempt > 0`) or parked `gone`. */
|
|
81
85
|
degraded: boolean;
|
|
@@ -187,10 +191,13 @@ declare namespace StatewireClient {
|
|
|
187
191
|
subscribe?: (listener: () => void) => () => void;
|
|
188
192
|
};
|
|
189
193
|
/** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */
|
|
194
|
+
type ConnectionError = Error & {
|
|
195
|
+
fin: StatewireHost.Fin;
|
|
196
|
+
};
|
|
190
197
|
type HeadersOption = HeadersInit | ((ctx: {
|
|
191
198
|
staleAuth: boolean;
|
|
192
199
|
}) => HeadersInit | Promise<HeadersInit>);
|
|
193
|
-
type
|
|
200
|
+
type HttpOptions = {
|
|
194
201
|
sessionStore?: SessionStorage | undefined;
|
|
195
202
|
url: string;
|
|
196
203
|
/** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */
|
|
@@ -199,10 +206,11 @@ declare namespace StatewireClient {
|
|
|
199
206
|
fetch?: typeof globalThis.fetch;
|
|
200
207
|
onSessionLost?: OnSessionLost;
|
|
201
208
|
onChange?: (connection: Connection) => void;
|
|
209
|
+
onError?: (error: ConnectionError) => void;
|
|
202
210
|
/** The broadcast idle flag's rising edge: the instance went quiet. */
|
|
203
211
|
onDone?: () => void;
|
|
204
212
|
};
|
|
205
|
-
type
|
|
213
|
+
type WebsocketOptions = {
|
|
206
214
|
sessionStore?: SessionStorage | undefined;
|
|
207
215
|
url: string;
|
|
208
216
|
/** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */
|
|
@@ -213,6 +221,7 @@ declare namespace StatewireClient {
|
|
|
213
221
|
webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
|
|
214
222
|
onSessionLost?: OnSessionLost;
|
|
215
223
|
onChange?: (connection: Connection) => void;
|
|
224
|
+
onError?: (error: ConnectionError) => void;
|
|
216
225
|
/** The broadcast idle flag's rising edge: the instance went quiet. */
|
|
217
226
|
onDone?: () => void;
|
|
218
227
|
};
|
package/dist/statewire.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;;cAiCa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,QAAQ,QAAQ;cA6DtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU;mBAEhC;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ;MAKjD,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;MAIpC;EAIJ,WAAW,OAAO;MAId,mBAAmB,gBAAgB;EAIvC,YAAY,OAAO,gBAAgB,gBAAgB;EAInD,cAAc,OAAO,gBAAgB;EAIrC,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;IAEjD,WAAW,gBAAgB,UAAU,QAAQ;;IAE7C;MACM,UAAU,gBAAgB;;;IAGhC,eAAe;IACf,aAAa,WAAW,YAAY,SAAS;IAC7C,cAAc,YAAY,YAAY;IACtC,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;IAE5B,sBAAsB,YAAY;IAClC,WAAW,OAAO;;IAElB,kBACE,OAAO,YAAY,SACnB,OAAO;;OAIC,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC;;IAEA,aAAa,OAAO;;IAEpB,iBAAiB;;IAEjB,cAAc,OAAO,kBAAkB;;IAEvC,gBAAgB,OAAO;;;OAIb;IAEN;IACA;IAOA;IACA;;IAGA;;IAEA;IACA;IACA;IACA;IACA;IACA;;IAEA;;OACM,aAAa;IACvB,QAAQ;IACR;;IAEA;;OAEU,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;OAQU,YAAY,WAAW,OAAO;;;;;;;;;;;;;OAc9B;IAEN;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAiB;;IACjC;IAAiB;;IACjB;IAAiB;;IAEjB;IACA;IACA;;IAGA;IACA;;;OAIM,iBACV,SAAS;IACP;IACA;IACA;IACA;IACA;;;OAKQ;aACD;aACA;aACA;aACA,SAAS;aACT;aACA;;;OAIC;IACV;IACA;IACA;IACA;;;OAIU;IACV;IACA;IACA;IACA,mBAAmB;;IAEnB,oBAAoB;;IAEpB,kBAAkB;;;OAIR;IACV;IACA,cAAc;IACd;IACA;IACA;IACA;;;OAIU;IACV,YAAY;IACZ,OAAO,QAAQ;;IAEf,QAAQ,GAAG,UAAU,IAAI,QAAQ,OAAO,QAAQ;;IAEhD,aAAa;;;OAIH,kBAAkB;IAAU,KAAK,cAAc;;OAE/C,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD;IACV,eAAe;IACf;;IAEA;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,gBAAgB;IAChB,YAAY,YAAY;IACxB,WAAW,OAAO;;IAElB;;OAGU;IACV,eAAe;IACf;;IAEA;IACA,UAAU;IACV;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,gBAAgB;IAChB,YAAY,YAAY;IACxB,WAAW,OAAO;;IAElB"}
|
package/dist/statewire.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","CONNECTED_CONNECTION","useStatewireCore","Statewire","TERMINAL","SessionStorageContext","useSessionTransport","transport","session","StatewireClient","SessionStorage","SessionTransport","useStatewire","Commands","options","Options","TState","TCommands","Helpers","latest","current","storage","undefined","value","core","subscribe","listener","state","getState","isBusy","getBusy","failed","getFailed","connection","failures","NonNullable","Error","commands","CommandsProxy","setContext","contextValue","Record","retryFailed","entry","FailedCommand","retry","discardFailed","discard","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Promise","dispose","unmount","initialState","optimistic","Optimistic","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","failureCovered","ConnectionState","status","reason","message","payload","attempt","cause","nextRetryAt","lastError","reconnect","degraded","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","code","predecessorLost","OnSessionLost","pending","ReadonlyArray","method","params","meta","failure","SessionCommand","SessionRecord","nextSeq","ctxNextSeq","verdicts","SessionVerdict","type","load","save","record","lock","fn","T","HeadersOption","HeadersInit","staleAuth","SSEOptions","sessionStore","url","isNew","headers","fetch","globalThis","onSessionLost","onChange","onDone","WSOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useContextProvider,\n useResource,\n useResources,\n withKey,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { CONNECTED_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport { SessionStorageContext } from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const value = useResource(\n SessionTransport({ transport: options.transport, session }),\n );\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);\n const failed = useSyncExternalStore(\n subscribe,\n core.getFailed,\n core.getFailed,\n );\n const connection = value.connection ?? CONNECTED_CONNECTION;\n\n return useMemo(() => {\n const failures = (): NonNullable<typeof value.failures> => {\n if (!value.failures) {\n throw new Error(\"statewire: this transport has no failed-send lane\");\n }\n return value.failures;\n };\n return {\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n value.setContext(contextValue);\n },\n failed,\n retryFailed: (entry: StatewireClient.FailedCommand) =>\n failures().retry(entry),\n discardFailed: (entry: StatewireClient.FailedCommand) =>\n failures().discard(entry),\n };\n }, [state, isBusy, failed, connection, core, value]);\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands>) {\n const ClientRoot = () => useStatewire<TState, TCommands>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n setContext(value: Record<string, unknown>): void {\n this.root.getValue().setContext(value);\n }\n\n get failed(): readonly StatewireClient.FailedCommand[] {\n return this.root.getValue().failed;\n }\n\n retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {\n return this.root.getValue().retryFailed(entry);\n }\n\n discardFailed(entry: StatewireClient.FailedCommand): void {\n this.root.getValue().discardFailed(entry);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | { session?: ResourceElement<SessionStorage> | undefined }\n | undefined;\n /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n onConnectionChange?: (connection: Connection) => void;\n /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */\n failureCovered?: (\n state: NonNullable<TState>,\n entry: FailedCommand,\n ) => boolean;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Negation of the server's broadcast idle level (a transport without idle is always busy). */\n isBusy: boolean;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => void;\n /** Non-accepted sends retained in the session record; reload with it. */\n failed: readonly FailedCommand[];\n /** Re-enqueues the entry's command on fresh clocks and removes the entry. */\n retryFailed: (entry: FailedCommand) => Promise<unknown>;\n /** Removes the entry. */\n discardFailed: (entry: FailedCommand) => void;\n };\n\n /** `cause`/`nextRetryAt`/`lastError` are present only while waiting out the backoff timer; a dial in flight carries `attempt` alone. */\n export type ConnectionState =\n | {\n status: \"disconnected\";\n reason: \"deferred\" | \"idle\" | \"gone\";\n message?: string;\n payload?: unknown;\n }\n | {\n status: \"connecting\";\n /** 0 = the episode's first dial, >0 = retry. */\n attempt: number;\n cause?: \"dropped\" | \"evicted\" | \"error\" | \"unauthorized\";\n nextRetryAt?: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"connected\" };\n export type Connection = ConnectionState & {\n reconnect: () => void;\n /** True while retrying (`connecting` with `attempt > 0`) or parked `gone`. */\n degraded: boolean;\n };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the server refused it\n * (`code` \"unknown-command\" | \"invalid-params\" for the static rejects,\n * code-less for handler rejects), \"lost\" = sent but no verdict will ever\n * arrive — the record is gone (a proven seq gap) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | {\n fate: \"rejected\";\n cause: \"server\";\n code?: string;\n message?: string;\n payload?: unknown;\n }\n | { fate: \"failed\"; cause: \"server\"; message?: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | { fate: \"expired\"; cause: \"server\" }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n };\n\n /** Held commands a proven record gap settled `{fate: \"lost\", cause: \"session-lost\"}`; return \"requeue\" to re-enqueue them on the reset clocks. */\n export type OnSessionLost = (\n pending: ReadonlyArray<{\n method: string;\n params: readonly unknown[];\n ts: number;\n seq: number;\n clientId?: string;\n }>,\n ) => \"requeue\" | \"drop\";\n\n /** A non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */\n export type FailedCommand = {\n readonly command: string;\n readonly params: readonly unknown[];\n readonly meta?: unknown;\n readonly failure: SendFailure;\n readonly ts: number;\n readonly seq: number;\n };\n\n /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n /** Non-accepted sends retained for the user; survive record rebases and resets. */\n failed?: readonly FailedCommand[];\n };\n\n /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */\n export type SessionVerdict = {\n seq: number;\n type: (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n };\n\n /** Session storage seam; `save` must persist synchronously in the caller's flow. */\n export type SessionStorage = {\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Serializes record mutations across sharers; presence marks the storage shared. */\n lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */\n subscribe?: (listener: () => void) => () => void;\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type SSEOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WSOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"statewire.js","names":["StatewireHost","useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","CONNECTED_CONNECTION","useStatewireCore","Statewire","TERMINAL","SessionStorageContext","useSessionTransport","transport","session","StatewireClient","SessionStorage","SessionTransport","useStatewire","Commands","options","Options","TState","TCommands","Helpers","latest","current","storage","undefined","value","core","subscribe","listener","state","getState","isBusy","getBusy","failed","getFailed","connection","failures","NonNullable","Error","commands","CommandsProxy","setContext","contextValue","Record","retryFailed","entry","FailedCommand","retry","discardFailed","discard","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Promise","dispose","unmount","initialState","optimistic","Optimistic","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","onError","error","ConnectionError","failureCovered","ConnectionState","status","reason","message","payload","attempt","cause","code","nextRetryAt","lastError","reconnect","degraded","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","predecessorLost","OnSessionLost","pending","ReadonlyArray","method","params","meta","failure","SessionCommand","SessionRecord","nextSeq","ctxNextSeq","verdicts","SessionVerdict","type","load","save","record","lock","fn","T","fin","Fin","HeadersOption","HeadersInit","staleAuth","HttpOptions","sessionStore","url","isNew","headers","fetch","globalThis","onSessionLost","onChange","onDone","WebsocketOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import type { StatewireHost } from \"./host\";\nimport { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useContextProvider,\n useResource,\n useResources,\n withKey,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { CONNECTED_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport { SessionStorageContext } from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const value = useResource(\n SessionTransport({ transport: options.transport, session }),\n );\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);\n const failed = useSyncExternalStore(\n subscribe,\n core.getFailed,\n core.getFailed,\n );\n const connection = value.connection ?? CONNECTED_CONNECTION;\n\n return useMemo(() => {\n const failures = (): NonNullable<typeof value.failures> => {\n if (!value.failures) {\n throw new Error(\"statewire: this transport has no failed-send lane\");\n }\n return value.failures;\n };\n return {\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n value.setContext(contextValue);\n },\n failed,\n retryFailed: (entry: StatewireClient.FailedCommand) =>\n failures().retry(entry),\n discardFailed: (entry: StatewireClient.FailedCommand) =>\n failures().discard(entry),\n };\n }, [state, isBusy, failed, connection, core, value]);\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands>) {\n const ClientRoot = () => useStatewire<TState, TCommands>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n setContext(value: Record<string, unknown>): void {\n this.root.getValue().setContext(value);\n }\n\n get failed(): readonly StatewireClient.FailedCommand[] {\n return this.root.getValue().failed;\n }\n\n retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {\n return this.root.getValue().retryFailed(entry);\n }\n\n discardFailed(entry: StatewireClient.FailedCommand): void {\n this.root.getValue().discardFailed(entry);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | { session?: ResourceElement<SessionStorage> | undefined }\n | undefined;\n /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n onConnectionChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */\n failureCovered?: (\n state: NonNullable<TState>,\n entry: FailedCommand,\n ) => boolean;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Negation of the server's broadcast idle level (a transport without idle is always busy). */\n isBusy: boolean;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => void;\n /** Non-accepted sends retained in the session record; reload with it. */\n failed: readonly FailedCommand[];\n /** Re-enqueues the entry's command on fresh clocks and removes the entry. */\n retryFailed: (entry: FailedCommand) => Promise<unknown>;\n /** Removes the entry. */\n discardFailed: (entry: FailedCommand) => void;\n };\n\n /** `cause`/`nextRetryAt`/`lastError` are present only while waiting out the backoff timer; a dial in flight carries `attempt` alone. */\n export type ConnectionState =\n | {\n status: \"disconnected\";\n reason:\n | \"deferred\"\n | \"idle\"\n | \"gone\"\n | \"forbidden\"\n | \"protocol-error\"\n | \"unauthorized\";\n message?: string;\n payload?: unknown;\n }\n | {\n status: \"connecting\";\n /** 0 = the episode's first dial, >0 = retry. */\n attempt: number;\n cause?: \"dropped\" | \"reconnect\" | \"error\" | \"unauthorized\";\n code?: string;\n nextRetryAt?: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"connected\" };\n export type Connection = ConnectionState & {\n error?: ConnectionError;\n reconnect: () => void;\n /** True while retrying (`connecting` with `attempt > 0`) or parked `gone`. */\n degraded: boolean;\n };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the server refused it\n * (`code` \"unknown-command\" | \"invalid-params\" for the static rejects,\n * code-less for handler rejects), \"lost\" = sent but no verdict will ever\n * arrive — the record is gone (a proven seq gap) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | {\n fate: \"rejected\";\n cause: \"server\";\n code?: string;\n message?: string;\n payload?: unknown;\n }\n | { fate: \"failed\"; cause: \"server\"; message?: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | { fate: \"expired\"; cause: \"server\" }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n };\n\n /** Held commands a proven record gap settled `{fate: \"lost\", cause: \"session-lost\"}`; return \"requeue\" to re-enqueue them on the reset clocks. */\n export type OnSessionLost = (\n pending: ReadonlyArray<{\n method: string;\n params: readonly unknown[];\n ts: number;\n seq: number;\n clientId?: string;\n }>,\n ) => \"requeue\" | \"drop\";\n\n /** A non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */\n export type FailedCommand = {\n readonly command: string;\n readonly params: readonly unknown[];\n readonly meta?: unknown;\n readonly failure: SendFailure;\n readonly ts: number;\n readonly seq: number;\n };\n\n /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n /** Non-accepted sends retained for the user; survive record rebases and resets. */\n failed?: readonly FailedCommand[];\n };\n\n /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */\n export type SessionVerdict = {\n seq: number;\n type: (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n };\n\n /** Session storage seam; `save` must persist synchronously in the caller's flow. */\n export type SessionStorage = {\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Serializes record mutations across sharers; presence marks the storage shared. */\n lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */\n subscribe?: (listener: () => void) => () => void;\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type ConnectionError = Error & { fin: StatewireHost.Fin };\n\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type HttpOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WebsocketOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":"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"}
|
package/dist/transports/sse.d.ts
CHANGED
|
@@ -53,10 +53,7 @@ type RemoteLink = {
|
|
|
53
53
|
parked: boolean;
|
|
54
54
|
send: (frame: StatewireFrame) => Promise<StatewireSubmitResult>;
|
|
55
55
|
};
|
|
56
|
-
|
|
57
|
-
stale: boolean;
|
|
58
|
-
};
|
|
59
|
-
declare const StatewireSSE: <TState = any, TCommands extends Statewire.Commands = Statewire.Commands>(options: StatewireClient.SSEOptions) => ResourceElement<Statewire<TState | undefined, TCommands>>;
|
|
56
|
+
declare const StatewireHttp: <TState = any, TCommands extends Statewire.Commands = Statewire.Commands>(options: StatewireClient.HttpOptions) => ResourceElement<Statewire<TState | undefined, TCommands>>;
|
|
60
57
|
//#endregion
|
|
61
|
-
export {
|
|
58
|
+
export { ConnectionState, RemoteLink, StatewireFrame, StatewireHttp, StatewireRemoteProps, StatewireSubmitResult };
|
|
62
59
|
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;;KA2BY;EACV;IAAQ;IAAgB;;EACxB;IAAQ;IAAgB;IAAmB;IAAgB;;;KAGjD;EACV;;EAEA,uBAAuB;EACvB,UAAU;IAAQ;IAAgB,KAAK;;EACvC,QAAQ;IAAQ;IAAgB,MAAM;;EACtC,SAAS,MAAM;EACf,YAAY,KAAK;EACjB,SAAS;EACT;EACA,WAAW,MAAM,cAAc;;KAGrB;EACN;;EACA;EAAiB,KAAK;;EACtB;;EACA;;KAEM,kBAAkB,gBAAgB;;KAGlC;EACV,YAAY,gBAAgB;;EAE5B;EACA,OAAO,OAAO,mBAAmB,QAAQ;;cAsO9B,gBACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,gBACtB,gBAAgB,UAAU,oBAAoB"}
|
package/dist/transports/sse.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
+
import { VERSION_HEADER } from "../wire/version.js";
|
|
1
2
|
import { createSseParser } from "./sse-parser.js";
|
|
2
3
|
import { timing } from "../client/timing.js";
|
|
3
|
-
import { classify, handleEnvelope,
|
|
4
|
+
import { classify, handleEnvelope, resolveHeaders, settleFin, unsupportedVersion, useLink, validateRefusalSyn } from "../client/connection.js";
|
|
4
5
|
import { useStatewireRemote } from "./remote.js";
|
|
5
|
-
import { useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
6
6
|
import { flushTapSync, resource } from "@assistant-ui/tap";
|
|
7
7
|
//#region src/transports/sse.ts
|
|
8
8
|
const LEASE_HEADER = "Statewire-Lease";
|
|
9
9
|
const baseUrl = (options) => options.url.replace(/\/$/, "");
|
|
10
|
-
const resolveHeaders = async (options, ctx) => {
|
|
11
|
-
const { headers } = options;
|
|
12
|
-
return new Headers(typeof headers === "function" ? await headers(ctx) : headers);
|
|
13
|
-
};
|
|
14
10
|
const withExtra = (headers, extra) => {
|
|
15
11
|
const merged = new Headers(headers);
|
|
16
12
|
for (const [name, value] of Object.entries(extra)) merged.set(name, value);
|
|
@@ -45,40 +41,37 @@ const consume = async (c, reader, heartbeat, dead) => {
|
|
|
45
41
|
}
|
|
46
42
|
}
|
|
47
43
|
};
|
|
48
|
-
const attemptStream = async (c, options,
|
|
49
|
-
const stale = auth.stale;
|
|
50
|
-
auth.stale = false;
|
|
44
|
+
const attemptStream = async (c, options, abort) => {
|
|
51
45
|
c.lease = null;
|
|
52
|
-
const
|
|
46
|
+
const headers = await resolveHeaders(options.headers, c.authStale);
|
|
47
|
+
c.authStale = false;
|
|
53
48
|
const lanes = flushTapSync(() => c.ctx.takeAttachLanes());
|
|
54
|
-
const
|
|
55
|
-
headers: Object.fromEntries(userHeaders),
|
|
56
|
-
...lanes ?? {}
|
|
57
|
-
};
|
|
49
|
+
const body = lanes === null ? void 0 : JSON.stringify(lanes);
|
|
58
50
|
const heartbeat = new AbortController();
|
|
59
51
|
const headerTimer = setTimeout(() => heartbeat.abort(), timing.heartbeatTimeoutMs);
|
|
60
52
|
let res;
|
|
61
53
|
try {
|
|
62
54
|
res = await timedFetch(options, `${baseUrl(options)}/stream`, {
|
|
63
|
-
method: "POST",
|
|
64
|
-
headers: withExtra(
|
|
55
|
+
method: body === void 0 ? "GET" : "POST",
|
|
56
|
+
headers: withExtra(headers, {
|
|
65
57
|
"content-type": "application/json",
|
|
66
58
|
"Statewire-Client-Id": c.ctx.clientId,
|
|
67
59
|
accept: "text/event-stream"
|
|
68
60
|
}),
|
|
69
|
-
body:
|
|
61
|
+
...body === void 0 ? {} : { body },
|
|
70
62
|
cache: "no-store",
|
|
71
63
|
signal: AbortSignal.any([abort.signal, heartbeat.signal])
|
|
72
64
|
}, heartbeat.signal);
|
|
73
65
|
} finally {
|
|
74
66
|
clearTimeout(headerTimer);
|
|
75
67
|
}
|
|
76
|
-
if (res.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
if (!res.ok) return flushTapSync(() => settleFin(c, httpFin(res)));
|
|
69
|
+
if (!res.body) throw new Error("statewire: protocol error: stream response has no body");
|
|
70
|
+
const version = res.headers.get(VERSION_HEADER)?.trim();
|
|
71
|
+
if (version !== void 0 && version !== "1") {
|
|
72
|
+
res.body.cancel().catch(() => {});
|
|
73
|
+
return unsupportedVersion(c, version);
|
|
80
74
|
}
|
|
81
|
-
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
82
75
|
c.lease = res.headers.get(LEASE_HEADER);
|
|
83
76
|
const reader = res.body.getReader();
|
|
84
77
|
try {
|
|
@@ -87,11 +80,10 @@ const attemptStream = async (c, options, auth, abort) => {
|
|
|
87
80
|
reader.cancel().catch(() => {});
|
|
88
81
|
}
|
|
89
82
|
};
|
|
90
|
-
const postFrame = async (c, options, lease, frame, timeout
|
|
83
|
+
const postFrame = async (c, options, lease, frame, timeout) => timedFetch(options, `${baseUrl(options)}/frames`, {
|
|
91
84
|
method: "POST",
|
|
92
|
-
headers: withExtra(
|
|
85
|
+
headers: withExtra(new Headers(), {
|
|
93
86
|
"content-type": "application/json",
|
|
94
|
-
"Statewire-Client-Id": c.ctx.clientId,
|
|
95
87
|
[LEASE_HEADER]: lease
|
|
96
88
|
}),
|
|
97
89
|
body: JSON.stringify(frame),
|
|
@@ -103,17 +95,23 @@ const retryAfterMs = (res) => {
|
|
|
103
95
|
const retry = Number(header) * 1e3;
|
|
104
96
|
return Number.isSafeInteger(retry) ? retry : null;
|
|
105
97
|
};
|
|
98
|
+
const httpFin = (res) => ({
|
|
99
|
+
reason: res.status === 401 ? "unauthorized" : res.status === 403 ? "forbidden" : res.status === 429 || res.status === 503 ? "reconnect" : res.status >= 400 && res.status < 500 ? "protocol-error" : "error",
|
|
100
|
+
status: res.status,
|
|
101
|
+
message: `HTTP ${res.status}`,
|
|
102
|
+
...res.status === 429 || res.status === 503 ? { code: "overloaded" } : {},
|
|
103
|
+
...retryAfterMs(res) !== null ? { retry: retryAfterMs(res) } : {}
|
|
104
|
+
});
|
|
106
105
|
const refusalSyn = async (res) => {
|
|
107
106
|
const syn = (await res.json().catch(() => null))?.syn;
|
|
108
107
|
if (typeof syn !== "object" || syn === null) throw new Error(`HTTP ${res.status}`);
|
|
109
108
|
return validateRefusalSyn(syn);
|
|
110
109
|
};
|
|
111
|
-
const sendFrame = async (c, options,
|
|
110
|
+
const sendFrame = async (c, options, frame) => {
|
|
112
111
|
const lease = c.lease;
|
|
113
112
|
if (lease === null) return { type: "no-lease" };
|
|
114
113
|
const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);
|
|
115
|
-
|
|
116
|
-
if (res.status === 401 && typeof options.headers === "function") res = await postFrame(c, options, lease, frame, timeout, true);
|
|
114
|
+
const res = await postFrame(c, options, lease, frame, timeout);
|
|
117
115
|
if (res.ok) return { type: "ok" };
|
|
118
116
|
if (res.status === 429 || res.status === 409) {
|
|
119
117
|
const headerRetry = res.status === 429 ? retryAfterMs(res) : null;
|
|
@@ -128,39 +126,32 @@ const sendFrame = async (c, options, auth, frame) => {
|
|
|
128
126
|
};
|
|
129
127
|
}
|
|
130
128
|
if (res.status === 423) return { type: "stale-lease" };
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
c.episode = { cause: "unauthorized" };
|
|
134
|
-
c.connection.reconnect();
|
|
135
|
-
throw new Error("unauthorized");
|
|
136
|
-
}
|
|
129
|
+
flushTapSync(() => settleFin(c, httpFin(res)));
|
|
130
|
+
c.abort?.abort();
|
|
137
131
|
throw new Error(`HTTP ${res.status}`);
|
|
138
132
|
};
|
|
139
|
-
const useSseLink = (options, ctx) => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
161
|
-
const useStatewireSSE = (options) => useStatewireRemote(options, useSseLink);
|
|
162
|
-
const StatewireSSE = resource(useStatewireSSE);
|
|
133
|
+
const useSseLink = (options, ctx) => useLink({
|
|
134
|
+
...options,
|
|
135
|
+
ctx,
|
|
136
|
+
attemptOnce: (c, abort) => attemptStream(c, options, abort),
|
|
137
|
+
send: async (c_0, frame) => {
|
|
138
|
+
try {
|
|
139
|
+
return await sendFrame(c_0, options, frame);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof SyntaxError || error instanceof Error && error.message.startsWith("statewire: protocol error:")) {
|
|
142
|
+
flushTapSync(() => settleFin(c_0, {
|
|
143
|
+
reason: "protocol-error",
|
|
144
|
+
message: error.message
|
|
145
|
+
}));
|
|
146
|
+
c_0.abort?.abort();
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const useStatewireHttp = (options) => useStatewireRemote(options, useSseLink);
|
|
153
|
+
const StatewireHttp = resource(useStatewireHttp);
|
|
163
154
|
//#endregion
|
|
164
|
-
export {
|
|
155
|
+
export { StatewireHttp };
|
|
165
156
|
|
|
166
157
|
//# sourceMappingURL=sse.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.js","names":["useRef","useState","flushTapSync","resource","ResourceElement","createSseParser","timing","classify","handleEnvelope","useStatewireConn","validateRefusalSyn","AttemptOutcome","Conn","StatewireCmd","StatewireEnvelope","StatewireSyn","Statewire","StatewireClient","StatewireHost","useStatewireRemote","StatewireFrame","ctx","ops","seq","cmd","method","params","meta","StatewireRemoteProps","clientId","takeAttachLanes","onHello","args","syn","onOps","cmds","onCmds","onRefusal","onIdle","level","onDetach","onFinish","Fin","StatewireSubmitResult","type","ConnectionState","RemoteLink","connection","Connection","parked","send","frame","Promise","LEASE_HEADER","baseUrl","options","SSEOptions","url","replace","AuthState","stale","resolveHeaders","staleAuth","Headers","headers","withExtra","extra","Record","merged","name","value","Object","entries","set","timedFetch","input","init","RequestInit","signal","AbortSignal","timeout","Response","fetch","globalThis","consume","c","reader","ReadableStreamDefaultReader","Uint8Array","heartbeat","AbortController","dead","decoder","TextDecoder","parse","timer","setTimeout","abort","heartbeatTimeoutMs","result","ReadableStreamReadResult","read","clearTimeout","done","data","decode","stream","env","JSON","attemptStream","auth","lease","userHeaders","lanes","fromEntries","headerTimer","res","accept","body","stringify","cache","any","status","episode","cause","Error","ok","get","getReader","aborted","cancel","catch","postFrame","lifetime","retryAfterMs","header","trim","undefined","test","retry","Number","isSafeInteger","refusalSyn","json","sendFrame","headerRetry","error","reconnect","useSseLink","latest","current","isNew","onChange","attemptOnce","link","useStatewireSSE","StatewireSSE","Commands","TState","TCommands"],"sources":["../../src/transports/sse.ts"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { createSseParser } from \"./sse-parser\";\nimport { timing } from \"../client/timing\";\nimport {\n classify,\n handleEnvelope,\n useStatewireConn,\n validateRefusalSyn,\n} from \"../client/connection\";\nimport type {\n AttemptOutcome,\n Conn,\n StatewireCmd,\n StatewireEnvelope,\n StatewireSyn,\n} from \"../client/connection\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { StatewireHost } from \"../host\";\nimport { useStatewireRemote } from \"./remote\";\n\n/** A client frame's lanes: stamped statements, strictly consecutive per lane. */\nexport type StatewireFrame = {\n ctx?: { ops: unknown[]; seq: number }[];\n cmd?: { method: string; params: unknown[]; meta?: unknown; seq: number }[];\n};\n\nexport type StatewireRemoteProps = {\n clientId: string;\n /** Lanes to ride the attach frame (no-lease supersession only); records the included seqs. */\n takeAttachLanes: () => StatewireFrame | null;\n onHello: (args: { ops: unknown[]; syn: StatewireSyn }) => void;\n onOps: (args: { ops: unknown[]; cmds: StatewireCmd[] }) => void;\n onCmds: (cmds: StatewireCmd[]) => void;\n onRefusal: (syn: StatewireSyn) => void;\n onIdle: (level: boolean) => void;\n onDetach: () => void;\n onFinish: (args: StatewireHost.Fin) => void;\n};\n\nexport type StatewireSubmitResult =\n | { type: \"ok\" }\n | { type: \"refusal\"; syn: StatewireSyn }\n | { type: \"stale-lease\" }\n | { type: \"no-lease\" };\n\nexport type ConnectionState = StatewireClient.ConnectionState;\n\n/** The live wire a remote session drives: connection status plus frame submission. */\nexport type RemoteLink = {\n connection: StatewireClient.Connection;\n /** A deferred first attach still waiting; `reconnect()` releases it. */\n parked: boolean;\n send: (frame: StatewireFrame) => Promise<StatewireSubmitResult>;\n};\n\nconst LEASE_HEADER = \"Statewire-Lease\";\n\nconst baseUrl = (options: StatewireClient.SSEOptions) =>\n options.url.replace(/\\/$/, \"\");\n\nexport type AuthState = { stale: boolean };\n\nconst resolveHeaders = async (\n options: StatewireClient.SSEOptions,\n ctx: { staleAuth: boolean },\n): Promise<Headers> => {\n const { headers } = options;\n return new Headers(\n typeof headers === \"function\" ? await headers(ctx) : headers,\n );\n};\n\nconst withExtra = (headers: Headers, extra: Record<string, string>) => {\n const merged = new Headers(headers);\n for (const [name, value] of Object.entries(extra)) merged.set(name, value);\n return merged;\n};\n\nconst timedFetch = async (\n options: StatewireClient.SSEOptions,\n input: string,\n init: RequestInit & { signal: AbortSignal },\n timeout: AbortSignal,\n): Promise<Response> => {\n try {\n return await (options.fetch ?? globalThis.fetch)(input, init);\n } catch {\n throw classify(timeout);\n }\n};\n\nconst consume = async (\n c: Conn,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n heartbeat: AbortController,\n dead: () => boolean,\n): Promise<AttemptOutcome> => {\n const decoder = new TextDecoder();\n const parse = createSseParser();\n for (;;) {\n const timer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let result: ReadableStreamReadResult<Uint8Array>;\n try {\n result = await reader.read();\n } catch {\n throw classify(heartbeat.signal);\n } finally {\n clearTimeout(timer);\n }\n if (result.done) return \"ended\";\n for (const data of parse(decoder.decode(result.value, { stream: true }))) {\n if (dead()) return \"ended\";\n const env = JSON.parse(data) as StatewireEnvelope;\n const done = flushTapSync(() => handleEnvelope(c, env));\n if (done) return done;\n }\n }\n};\n\nconst attemptStream = async (\n c: Conn,\n options: StatewireClient.SSEOptions,\n auth: AuthState,\n abort: AbortController,\n): Promise<AttemptOutcome> => {\n const stale = auth.stale;\n auth.stale = false;\n c.lease = null;\n const userHeaders = await resolveHeaders(options, { staleAuth: stale });\n const lanes = flushTapSync(() => c.ctx.takeAttachLanes());\n const frame = {\n headers: Object.fromEntries(userHeaders),\n ...(lanes ?? {}),\n };\n const heartbeat = new AbortController();\n const headerTimer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let res: Response;\n try {\n res = await timedFetch(\n options,\n `${baseUrl(options)}/stream`,\n {\n method: \"POST\",\n headers: withExtra(userHeaders, {\n \"content-type\": \"application/json\",\n \"Statewire-Client-Id\": c.ctx.clientId,\n accept: \"text/event-stream\",\n }),\n body: JSON.stringify(frame),\n cache: \"no-store\",\n signal: AbortSignal.any([abort.signal, heartbeat.signal]),\n },\n heartbeat.signal,\n );\n } finally {\n clearTimeout(headerTimer);\n }\n if (res.status === 401) {\n c.episode = { cause: \"unauthorized\" };\n auth.stale = true;\n throw new Error(\"unauthorized\");\n }\n if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);\n c.lease = res.headers.get(LEASE_HEADER);\n const reader = res.body.getReader();\n try {\n return await consume(c, reader, heartbeat, () => abort.signal.aborted);\n } finally {\n reader.cancel().catch(() => {});\n }\n};\n\nconst postFrame = async (\n c: Conn,\n options: StatewireClient.SSEOptions,\n lease: string,\n frame: StatewireFrame,\n timeout: AbortSignal,\n stale: boolean,\n): Promise<Response> =>\n timedFetch(\n options,\n `${baseUrl(options)}/frames`,\n {\n method: \"POST\",\n headers: withExtra(await resolveHeaders(options, { staleAuth: stale }), {\n \"content-type\": \"application/json\",\n \"Statewire-Client-Id\": c.ctx.clientId,\n [LEASE_HEADER]: lease,\n }),\n body: JSON.stringify(frame),\n signal: AbortSignal.any([\n c.lifetime?.signal ?? AbortSignal.abort(),\n timeout,\n ]),\n },\n timeout,\n );\n\n// Delta-seconds only; anything else reads as absent.\nconst retryAfterMs = (res: Response): number | null => {\n const header = res.headers.get(\"retry-after\")?.trim();\n if (header === undefined || !/^\\d+$/.test(header)) return null;\n const retry = Number(header) * 1000;\n return Number.isSafeInteger(retry) ? retry : null;\n};\n\nconst refusalSyn = async (res: Response): Promise<StatewireSyn> => {\n const body = (await res.json().catch(() => null)) as {\n syn?: StatewireSyn;\n } | null;\n const syn = body?.syn;\n if (typeof syn !== \"object\" || syn === null) {\n throw new Error(`HTTP ${res.status}`);\n }\n return validateRefusalSyn(syn);\n};\n\nconst sendFrame = async (\n c: Conn,\n options: StatewireClient.SSEOptions,\n auth: AuthState,\n frame: StatewireFrame,\n): Promise<StatewireSubmitResult> => {\n const lease = c.lease;\n if (lease === null) return { type: \"no-lease\" };\n const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);\n let res = await postFrame(c, options, lease, frame, timeout, false);\n if (res.status === 401 && typeof options.headers === \"function\") {\n res = await postFrame(c, options, lease, frame, timeout, true);\n }\n if (res.ok) return { type: \"ok\" };\n if (res.status === 429 || res.status === 409) {\n // The syn body retry wins; Retry-After covers 429s minted below the host.\n const headerRetry = res.status === 429 ? retryAfterMs(res) : null;\n const syn: StatewireSyn = await refusalSyn(res).catch((error: unknown) => {\n if (headerRetry === null) throw error;\n return { retry: headerRetry };\n });\n if (syn.retry === undefined && headerRetry !== null)\n syn.retry = headerRetry;\n return { type: \"refusal\", syn };\n }\n if (res.status === 423) return { type: \"stale-lease\" };\n if (res.status === 401) {\n // Fresh credentials did not clear the 401: signal auth-failed on the\n // connection and let the send retry as a transport fault.\n auth.stale = true;\n c.episode = { cause: \"unauthorized\" };\n c.connection.reconnect();\n throw new Error(\"unauthorized\");\n }\n throw new Error(`HTTP ${res.status}`);\n};\n\nconst useSseLink = (\n options: StatewireClient.SSEOptions,\n ctx: StatewireRemoteProps,\n): RemoteLink => {\n const latest = useRef(options);\n latest.current = options;\n const [auth] = useState((): AuthState => ({ stale: false }));\n const c = useStatewireConn({\n url: options.url,\n isNew: options.isNew,\n onChange: options.onChange,\n ctx,\n attemptOnce: (c, abort) => attemptStream(c, options, auth, abort),\n });\n const [link] = useState(\n (): RemoteLink => ({\n get connection() {\n return c.connection;\n },\n get parked() {\n return c.parked;\n },\n send: (frame) => sendFrame(c, latest.current, auth, frame),\n }),\n );\n return link;\n};\n\nconst useStatewireSSE = (\n options: StatewireClient.SSEOptions,\n): Statewire<any, any> => useStatewireRemote(options, useSseLink);\n\nexport const StatewireSSE = resource(useStatewireSSE) as <\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.SSEOptions,\n) => ResourceElement<Statewire<TState | undefined, TCommands>>;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sse.js","names":["flushTapSync","resource","ResourceElement","createSseParser","timing","settleFin","classify","handleEnvelope","resolveHeaders","unsupportedVersion","useLink","validateRefusalSyn","AttemptOutcome","Conn","StatewireCmd","StatewireEnvelope","StatewireSyn","Statewire","StatewireClient","StatewireHost","useStatewireRemote","PROTOCOL_VERSION","VERSION_HEADER","StatewireFrame","ctx","ops","seq","cmd","method","params","meta","StatewireRemoteProps","clientId","takeAttachLanes","onHello","args","syn","onOps","cmds","onCmds","onRefusal","onIdle","level","onDetach","onFinish","Fin","StatewireSubmitResult","type","ConnectionState","RemoteLink","connection","Connection","parked","send","frame","Promise","LEASE_HEADER","baseUrl","options","HttpOptions","url","replace","withExtra","headers","Headers","extra","Record","merged","name","value","Object","entries","set","timedFetch","input","init","RequestInit","signal","AbortSignal","timeout","Response","fetch","globalThis","consume","c","reader","ReadableStreamDefaultReader","Uint8Array","heartbeat","AbortController","dead","decoder","TextDecoder","parse","timer","setTimeout","abort","heartbeatTimeoutMs","result","ReadableStreamReadResult","read","clearTimeout","done","data","decode","stream","env","JSON","attemptStream","lease","authStale","lanes","body","undefined","stringify","headerTimer","res","accept","cache","any","ok","httpFin","Error","version","get","trim","cancel","catch","getReader","aborted","postFrame","lifetime","retryAfterMs","header","test","retry","Number","isSafeInteger","reason","status","message","code","refusalSyn","json","sendFrame","headerRetry","error","useSseLink","attemptOnce","SyntaxError","startsWith","useStatewireHttp","StatewireHttp","Commands","TState","TCommands"],"sources":["../../src/transports/sse.ts"],"sourcesContent":["import { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { createSseParser } from \"./sse-parser\";\nimport { timing } from \"../client/timing\";\nimport {\n settleFin,\n classify,\n handleEnvelope,\n resolveHeaders,\n unsupportedVersion,\n useLink,\n validateRefusalSyn,\n} from \"../client/connection\";\nimport type {\n AttemptOutcome,\n Conn,\n StatewireCmd,\n StatewireEnvelope,\n StatewireSyn,\n} from \"../client/connection\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { StatewireHost } from \"../host\";\nimport { useStatewireRemote } from \"./remote\";\nimport { PROTOCOL_VERSION, VERSION_HEADER } from \"../wire/version\";\n\n/** A client frame's lanes: stamped statements, strictly consecutive per lane. */\nexport type StatewireFrame = {\n ctx?: { ops: unknown[]; seq: number }[];\n cmd?: { method: string; params: unknown[]; meta?: unknown; seq: number }[];\n};\n\nexport type StatewireRemoteProps = {\n clientId: string;\n /** Lanes to ride the attach frame (no-lease supersession only); records the included seqs. */\n takeAttachLanes: () => StatewireFrame | null;\n onHello: (args: { ops: unknown[]; syn: StatewireSyn }) => void;\n onOps: (args: { ops: unknown[]; cmds: StatewireCmd[] }) => void;\n onCmds: (cmds: StatewireCmd[]) => void;\n onRefusal: (syn: StatewireSyn) => void;\n onIdle: (level: boolean) => void;\n onDetach: () => void;\n onFinish: (args: StatewireHost.Fin) => void;\n};\n\nexport type StatewireSubmitResult =\n | { type: \"ok\" }\n | { type: \"refusal\"; syn: StatewireSyn }\n | { type: \"stale-lease\" }\n | { type: \"no-lease\" };\n\nexport type ConnectionState = StatewireClient.ConnectionState;\n\n/** The live wire a remote session drives: connection status plus frame submission. */\nexport type RemoteLink = {\n connection: StatewireClient.Connection;\n /** A deferred first attach still waiting; `reconnect()` releases it. */\n parked: boolean;\n send: (frame: StatewireFrame) => Promise<StatewireSubmitResult>;\n};\n\nconst LEASE_HEADER = \"Statewire-Lease\";\n\nconst baseUrl = (options: StatewireClient.HttpOptions) =>\n options.url.replace(/\\/$/, \"\");\n\nconst withExtra = (headers: Headers, extra: Record<string, string>) => {\n const merged = new Headers(headers);\n for (const [name, value] of Object.entries(extra)) merged.set(name, value);\n return merged;\n};\n\nconst timedFetch = async (\n options: StatewireClient.HttpOptions,\n input: string,\n init: RequestInit & { signal: AbortSignal },\n timeout: AbortSignal,\n): Promise<Response> => {\n try {\n return await (options.fetch ?? globalThis.fetch)(input, init);\n } catch {\n throw classify(timeout);\n }\n};\n\nconst consume = async (\n c: Conn,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n heartbeat: AbortController,\n dead: () => boolean,\n): Promise<AttemptOutcome> => {\n const decoder = new TextDecoder();\n const parse = createSseParser();\n for (;;) {\n const timer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let result: ReadableStreamReadResult<Uint8Array>;\n try {\n result = await reader.read();\n } catch {\n throw classify(heartbeat.signal);\n } finally {\n clearTimeout(timer);\n }\n if (result.done) return \"ended\";\n for (const data of parse(decoder.decode(result.value, { stream: true }))) {\n if (dead()) return \"ended\";\n const env = JSON.parse(data) as StatewireEnvelope;\n const done = flushTapSync(() => handleEnvelope(c, env));\n if (done) return done;\n }\n }\n};\n\nconst attemptStream = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n abort: AbortController,\n): Promise<AttemptOutcome> => {\n c.lease = null;\n const headers = await resolveHeaders(options.headers, c.authStale);\n c.authStale = false;\n const lanes = flushTapSync(() => c.ctx.takeAttachLanes());\n const body = lanes === null ? undefined : JSON.stringify(lanes);\n const heartbeat = new AbortController();\n const headerTimer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let res: Response;\n try {\n res = await timedFetch(\n options,\n `${baseUrl(options)}/stream`,\n {\n method: body === undefined ? \"GET\" : \"POST\",\n headers: withExtra(headers, {\n \"content-type\": \"application/json\",\n \"Statewire-Client-Id\": c.ctx.clientId,\n accept: \"text/event-stream\",\n }),\n ...(body === undefined ? {} : { body }),\n cache: \"no-store\",\n signal: AbortSignal.any([abort.signal, heartbeat.signal]),\n },\n heartbeat.signal,\n );\n } finally {\n clearTimeout(headerTimer);\n }\n if (!res.ok) return flushTapSync(() => settleFin(c, httpFin(res)));\n if (!res.body)\n throw new Error(\"statewire: protocol error: stream response has no body\");\n const version = res.headers.get(VERSION_HEADER)?.trim();\n if (version !== undefined && version !== PROTOCOL_VERSION) {\n res.body.cancel().catch(() => {});\n return unsupportedVersion(c, version);\n }\n c.lease = res.headers.get(LEASE_HEADER);\n const reader = res.body.getReader();\n try {\n return await consume(c, reader, heartbeat, () => abort.signal.aborted);\n } finally {\n reader.cancel().catch(() => {});\n }\n};\n\nconst postFrame = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n lease: string,\n frame: StatewireFrame,\n timeout: AbortSignal,\n): Promise<Response> =>\n timedFetch(\n options,\n `${baseUrl(options)}/frames`,\n {\n method: \"POST\",\n headers: withExtra(new Headers(), {\n \"content-type\": \"application/json\",\n [LEASE_HEADER]: lease,\n }),\n body: JSON.stringify(frame),\n signal: AbortSignal.any([\n c.lifetime?.signal ?? AbortSignal.abort(),\n timeout,\n ]),\n },\n timeout,\n );\n\n// Delta-seconds only; anything else reads as absent.\nconst retryAfterMs = (res: Response): number | null => {\n const header = res.headers.get(\"retry-after\")?.trim();\n if (header === undefined || !/^\\d+$/.test(header)) return null;\n const retry = Number(header) * 1000;\n return Number.isSafeInteger(retry) ? retry : null;\n};\n\nconst httpFin = (res: Response): StatewireHost.Fin => ({\n reason:\n res.status === 401\n ? \"unauthorized\"\n : res.status === 403\n ? \"forbidden\"\n : res.status === 429 || res.status === 503\n ? \"reconnect\"\n : res.status >= 400 && res.status < 500\n ? \"protocol-error\"\n : \"error\",\n status: res.status,\n message: `HTTP ${res.status}`,\n ...(res.status === 429 || res.status === 503 ? { code: \"overloaded\" } : {}),\n ...(retryAfterMs(res) !== null ? { retry: retryAfterMs(res)! } : {}),\n});\n\nconst refusalSyn = async (res: Response): Promise<StatewireSyn> => {\n const body = (await res.json().catch(() => null)) as {\n syn?: StatewireSyn;\n } | null;\n const syn = body?.syn;\n if (typeof syn !== \"object\" || syn === null) {\n throw new Error(`HTTP ${res.status}`);\n }\n return validateRefusalSyn(syn);\n};\n\nconst sendFrame = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n frame: StatewireFrame,\n): Promise<StatewireSubmitResult> => {\n const lease = c.lease;\n if (lease === null) return { type: \"no-lease\" };\n const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);\n const res = await postFrame(c, options, lease, frame, timeout);\n if (res.ok) return { type: \"ok\" };\n if (res.status === 429 || res.status === 409) {\n // The syn body retry wins; Retry-After covers 429s minted below the host.\n const headerRetry = res.status === 429 ? retryAfterMs(res) : null;\n const syn: StatewireSyn = await refusalSyn(res).catch((error: unknown) => {\n if (headerRetry === null) throw error;\n return { retry: headerRetry };\n });\n if (syn.retry === undefined && headerRetry !== null)\n syn.retry = headerRetry;\n return { type: \"refusal\", syn };\n }\n if (res.status === 423) return { type: \"stale-lease\" };\n flushTapSync(() => settleFin(c, httpFin(res)));\n c.abort?.abort();\n throw new Error(`HTTP ${res.status}`);\n};\n\nconst useSseLink = (\n options: StatewireClient.HttpOptions,\n ctx: StatewireRemoteProps,\n): RemoteLink =>\n useLink({\n ...options,\n ctx,\n attemptOnce: (c, abort) => attemptStream(c, options, abort),\n send: async (c, frame) => {\n try {\n return await sendFrame(c, options, frame);\n } catch (error) {\n if (\n error instanceof SyntaxError ||\n (error instanceof Error &&\n error.message.startsWith(\"statewire: protocol error:\"))\n ) {\n flushTapSync(() =>\n settleFin(c, { reason: \"protocol-error\", message: error.message }),\n );\n c.abort?.abort();\n }\n throw error;\n }\n },\n });\n\nconst useStatewireHttp = (\n options: StatewireClient.HttpOptions,\n): Statewire<any, any> => useStatewireRemote(options, useSseLink);\n\nexport const StatewireHttp = resource(useStatewireHttp) as <\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.HttpOptions,\n) => ResourceElement<Statewire<TState | undefined, TCommands>>;\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyOptimistic } from "../util/recordMutations.js";
|
|
2
|
-
import {
|
|
2
|
+
import { StatewireHttp } from "./sse.js";
|
|
3
3
|
import { openSSEStream, sseFrame } from "./fake-sse.js";
|
|
4
4
|
import { commandFrame, createCounterElement, createSocketRecorder, settle } from "./fixtures.js";
|
|
5
5
|
//#region src/transports/testing.ts
|
|
@@ -48,7 +48,7 @@ const createMockTransport = (initialState) => {
|
|
|
48
48
|
ctxSeq: 0
|
|
49
49
|
});
|
|
50
50
|
const rec = clients.get(id);
|
|
51
|
-
const frame = JSON.parse(String(init?.body));
|
|
51
|
+
const frame = JSON.parse(String(init?.body ?? "{}"));
|
|
52
52
|
admit(id, frame);
|
|
53
53
|
return openSSEStream(streams, id, (handle) => {
|
|
54
54
|
handle.send(sseFrame({
|
|
@@ -65,13 +65,13 @@ const createMockTransport = (initialState) => {
|
|
|
65
65
|
}, init?.signal ?? void 0, { "Statewire-Lease": "mock-lease" });
|
|
66
66
|
}
|
|
67
67
|
if (url.endsWith("/frames")) {
|
|
68
|
-
admit(
|
|
68
|
+
admit(clientId, JSON.parse(String(init?.body ?? "{}")));
|
|
69
69
|
return Response.json({});
|
|
70
70
|
}
|
|
71
71
|
return new Response(null, { status: 404 });
|
|
72
72
|
});
|
|
73
73
|
return {
|
|
74
|
-
transport:
|
|
74
|
+
transport: StatewireHttp({
|
|
75
75
|
url: `http://mock-${++mockCount}/api`,
|
|
76
76
|
fetch: fakeFetch
|
|
77
77
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":["ResourceElement","StatewireEnvelope","
|
|
1
|
+
{"version":3,"file":"testing.js","names":["ResourceElement","StatewireEnvelope","StatewireHttp","StatewireFrame","Statewire","openSSEStream","sseFrame","FakeStreamHandle","tick","Promise","r","setTimeout","applyOptimistic","MockTransport","transport","TState","commands","method","params","context","setState","state","applied","seq","settle","payload","sendEnvelope","envelope","finish","reason","message","clientId","attached","mockCount","createMockTransport","initialState","finished","clients","Map","ctxSeq","streams","Set","stream","send","admit","id","frame","rec","get","statement","ctx","Math","max","root","ops","value","push","cmd","fakeFetch","input","RequestInfo","URL","init","RequestInit","Response","TypeError","url","String","headers","Headers","endsWith","has","set","JSON","parse","body","handle","op","path","syn","signal","undefined","json","status","fetch","next","type","fin","close","size"],"sources":["../../src/transports/testing.ts"],"sourcesContent":["import type { ResourceElement } from \"@assistant-ui/tap\";\nimport type { StatewireEnvelope } from \"../client/connection\";\nimport { StatewireHttp } from \"./sse\";\nimport type { StatewireFrame } from \"./sse\";\nimport type { Statewire } from \"../types\";\nimport { openSSEStream, sseFrame } from \"./fake-sse\";\nimport type { FakeStreamHandle } from \"./fake-sse\";\n\nexport * from \"./fixtures\";\n\n/** Let queued microtasks and op batches drain through the statewire client. */\nexport const tick = () => new Promise<void>((r) => setTimeout(r, 0));\n\nexport { applyOptimistic } from \"../util/recordMutations\";\n\nexport type MockTransport<TState> = {\n /** Pass as the `transport:` option of useStatewire / Harness. */\n transport: ResourceElement<Statewire<TState | undefined, any>>;\n /** Captured statements in arrival order: cmd as `{method, params}`, ctx as `{context}`. */\n commands: { method?: string; params?: unknown[]; context?: unknown }[];\n /** Replace the replicated state and broadcast it as a root snapshot op. */\n setState: (state: TState) => Promise<void>;\n /** Deliver a bare ack cmd packet on the stream. */\n applied: (seq: number) => Promise<void>;\n /** Deliver a terminal result cmd packet on the stream. */\n settle: (seq: number, payload?: unknown) => Promise<void>;\n /** Deliver an arbitrary envelope on the stream. */\n sendEnvelope: (envelope: StatewireEnvelope) => Promise<void>;\n /** End the stream with a fin; further attaches fail like a dead server. */\n finish: (reason: string, message?: string) => Promise<void>;\n readonly clientId: string;\n readonly attached: boolean;\n};\n\n/**\n * A mock statewire backend: serves `initialState` as the attach snapshot,\n * records submitted frames (receipt only — acks and results are delivered\n * from test code), and exposes the wire as a transport ResourceElement.\n */\nlet mockCount = 0;\n\nexport const createMockTransport = <TState>(\n initialState: TState,\n): MockTransport<TState> => {\n let state: unknown = initialState;\n let clientId = \"\";\n let finished = false;\n const clients = new Map<string, { seq: number; ctxSeq: number }>();\n const streams = new Set<FakeStreamHandle>();\n const commands: MockTransport<unknown>[\"commands\"] = [];\n\n const sendEnvelope = (envelope: StatewireEnvelope) => {\n for (const stream of streams) stream.send(sseFrame(envelope));\n return tick();\n };\n\n const admit = (id: string, frame: StatewireFrame) => {\n const rec = clients.get(id)!;\n for (const statement of frame.ctx ?? []) {\n rec.ctxSeq = Math.max(rec.ctxSeq, statement.seq);\n const root = statement.ops[0] as { value?: unknown } | undefined;\n commands.push({ context: root?.value });\n }\n for (const statement of frame.cmd ?? []) {\n rec.seq = Math.max(rec.seq, statement.seq);\n commands.push({ method: statement.method, params: statement.params });\n }\n };\n\n const fakeFetch = (async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n if (finished) throw new TypeError(\"fetch failed (finished)\");\n const url = String(input);\n const headers = new Headers(init?.headers);\n const id = headers.get(\"Statewire-Client-Id\") ?? \"\";\n if (url.endsWith(\"/stream\")) {\n clientId = id;\n if (!clients.has(id)) clients.set(id, { seq: 0, ctxSeq: 0 });\n const rec = clients.get(id)!;\n const frame = JSON.parse(String(init?.body ?? \"{}\")) as StatewireFrame;\n admit(id, frame);\n return openSSEStream(\n streams,\n id,\n (handle) => {\n handle.send(\n sseFrame({\n ops: [{ op: \"replace\", path: [], value: state }],\n syn: { seq: rec.seq, ctxSeq: rec.ctxSeq },\n }),\n );\n },\n init?.signal ?? undefined,\n { \"Statewire-Lease\": \"mock-lease\" },\n );\n }\n if (url.endsWith(\"/frames\")) {\n admit(clientId, JSON.parse(String(init?.body ?? \"{}\")) as StatewireFrame);\n return Response.json({});\n }\n return new Response(null, { status: 404 });\n }) as typeof fetch;\n\n return {\n transport: StatewireHttp({\n url: `http://mock-${++mockCount}/api`,\n fetch: fakeFetch,\n }),\n commands,\n setState: (next) => {\n state = next;\n return sendEnvelope({ ops: [{ op: \"replace\", path: [], value: next }] });\n },\n applied: (seq) => sendEnvelope({ cmd: [{ seq, type: \"applied\" }] }),\n settle: (seq, payload) =>\n sendEnvelope({\n cmd: [\n {\n seq,\n type: \"result\",\n ...(payload !== undefined && { payload }),\n },\n ],\n }),\n sendEnvelope,\n finish: async (reason, message) => {\n await sendEnvelope({\n fin: { reason, ...(message !== undefined && { message }) },\n });\n finished = true;\n for (const stream of [...streams]) stream.close();\n await tick();\n },\n get clientId() {\n return clientId;\n },\n get attached() {\n return streams.size > 0;\n },\n };\n};\n"],"mappings":"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"}
|
package/dist/transports/ws.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { StatewireClient } from "../statewire.js";
|
|
2
2
|
import { Statewire } from "../types.js";
|
|
3
|
+
import { WS_SUBPROTOCOL } from "../wire/version.js";
|
|
3
4
|
import { ResourceElement } from "@assistant-ui/tap";
|
|
4
5
|
//#region src/transports/ws.d.ts
|
|
5
|
-
declare const
|
|
6
|
-
declare const StatewireWS: <TState = any, TCommands extends Statewire.Commands = Statewire.Commands>(options: StatewireClient.WSOptions) => ResourceElement<Statewire<TState | undefined, TCommands>>;
|
|
6
|
+
declare const StatewireWebsocket: <TState = any, TCommands extends Statewire.Commands = Statewire.Commands>(options: StatewireClient.WebsocketOptions) => ResourceElement<Statewire<TState | undefined, TCommands>>;
|
|
7
7
|
//#endregion
|
|
8
|
-
export {
|
|
8
|
+
export { StatewireWebsocket, WS_SUBPROTOCOL };
|
|
9
9
|
//# sourceMappingURL=ws.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws.d.ts","names":[],"sources":["../../src/transports/ws.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ws.d.ts","names":[],"sources":["../../src/transports/ws.ts"],"mappings":";;;;;cAwLa,qBACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,qBACtB,gBAAgB,UAAU,oBAAoB"}
|