statewire 0.19.4 → 0.21.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 +15 -1
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +18 -4
- package/dist/client/connection.js.map +1 -1
- package/dist/client/session.d.ts +10 -1
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +92 -27
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +2 -0
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/host/host.d.ts +2 -1
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +20 -12
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +14 -9
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +14 -3
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +162 -20
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +5 -2
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host.d.ts +6 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/statewire.d.ts +3 -0
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +23 -5
- package/dist/statewire.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +3 -2
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +5 -0
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +12 -0
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +2 -0
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +11 -4
- package/dist/transports/testing.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wire/context.d.ts +14 -0
- package/dist/wire/context.d.ts.map +1 -0
- package/dist/wire/context.js +31 -0
- package/dist/wire/context.js.map +1 -0
- package/dist/wire/limits.d.ts +2 -1
- package/dist/wire/limits.d.ts.map +1 -1
- package/dist/wire/limits.js +2 -1
- package/dist/wire/limits.js.map +1 -1
- package/dist/wire/protocol.d.ts +1 -1
- package/dist/wire/protocol.js +4 -4
- package/dist/wire/protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +16 -4
- package/src/client/session.ts +128 -19
- package/src/client/sessionStore.ts +5 -1
- package/src/host/host.ts +18 -3
- package/src/host/http.ts +37 -5
- package/src/host/socket.ts +232 -13
- package/src/host-internal.ts +4 -0
- package/src/host.ts +5 -0
- package/src/statewire.ts +34 -3
- package/src/transports/remote.ts +3 -0
- package/src/transports/sse.ts +19 -0
- package/src/transports/testing.ts +14 -3
- package/src/types.ts +12 -0
- package/src/wire/context.ts +40 -0
- package/src/wire/limits.ts +2 -0
- package/src/wire/protocol.ts +1 -1
|
@@ -5,7 +5,7 @@ import type { StatewireFrame } from "./sse";
|
|
|
5
5
|
import type { Statewire } from "../types";
|
|
6
6
|
import { openSSEStream, sseFrame } from "./fake-sse";
|
|
7
7
|
import type { FakeStreamHandle } from "./fake-sse";
|
|
8
|
-
import { WIRE_VERSION } from "../wire/protocol";
|
|
8
|
+
import { WIRE_VERSION, parseNegotiation } from "../wire/protocol";
|
|
9
9
|
|
|
10
10
|
export * from "./fixtures";
|
|
11
11
|
|
|
@@ -19,6 +19,7 @@ export type MockTransport<TState> = {
|
|
|
19
19
|
transport: ResourceElement<Statewire<TState | undefined, any>>;
|
|
20
20
|
/** Captured statements in arrival order. */
|
|
21
21
|
commands: { protocol: string; method: string; params: unknown[] }[];
|
|
22
|
+
contexts: NonNullable<StatewireFrame["ctx"]>;
|
|
22
23
|
/** Replace the replicated state and broadcast it as a root snapshot op. */
|
|
23
24
|
setState: (state: TState) => Promise<void>;
|
|
24
25
|
/** Deliver a bare ack cmd packet on the stream. */
|
|
@@ -54,6 +55,7 @@ export const createMockTransport = <TState>(
|
|
|
54
55
|
const clients = new Map<string, Record<string, number>>();
|
|
55
56
|
const streams = new Set<FakeStreamHandle>();
|
|
56
57
|
const commands: MockTransport<unknown>["commands"] = [];
|
|
58
|
+
const contexts: MockTransport<unknown>["contexts"] = [];
|
|
57
59
|
|
|
58
60
|
const sendEnvelope = (envelope: StatewireEnvelope) => {
|
|
59
61
|
for (const stream of streams) stream.send(sseFrame(envelope));
|
|
@@ -62,6 +64,7 @@ export const createMockTransport = <TState>(
|
|
|
62
64
|
|
|
63
65
|
const admit = (id: string, frame: StatewireFrame) => {
|
|
64
66
|
const rec = clients.get(id)!;
|
|
67
|
+
contexts.push(...(frame.ctx ?? []));
|
|
65
68
|
for (const statement of frame.cmd ?? []) {
|
|
66
69
|
if (statement.seq <= (rec[statement.protocol] ?? 0)) continue;
|
|
67
70
|
rec[statement.protocol] = statement.seq;
|
|
@@ -86,6 +89,13 @@ export const createMockTransport = <TState>(
|
|
|
86
89
|
if (!clients.has(id)) clients.set(id, {});
|
|
87
90
|
const rec = clients.get(id)!;
|
|
88
91
|
const ephemeral = ephemeralLanes();
|
|
92
|
+
const offers = parseNegotiation(headers).applicationProtocols;
|
|
93
|
+
const applicationVersion =
|
|
94
|
+
offers === "*"
|
|
95
|
+
? WIRE_VERSION
|
|
96
|
+
: offers.find((offer) => offer.name === protocol)?.version;
|
|
97
|
+
if (applicationVersion === undefined)
|
|
98
|
+
throw new Error("mock protocol was not offered");
|
|
89
99
|
for (const lane of ephemeral) delete rec[lane];
|
|
90
100
|
const frame = JSON.parse(String(init?.body ?? "{}")) as StatewireFrame;
|
|
91
101
|
admit(id, frame);
|
|
@@ -101,12 +111,12 @@ export const createMockTransport = <TState>(
|
|
|
101
111
|
protocols: [
|
|
102
112
|
{
|
|
103
113
|
name: protocol,
|
|
104
|
-
version:
|
|
114
|
+
version: applicationVersion,
|
|
105
115
|
seq: rec[protocol] ?? 0,
|
|
106
116
|
},
|
|
107
117
|
...ephemeral.map((name) => ({
|
|
108
118
|
name,
|
|
109
|
-
version:
|
|
119
|
+
version: applicationVersion,
|
|
110
120
|
record: "ephemeral" as const,
|
|
111
121
|
seq: 0,
|
|
112
122
|
})),
|
|
@@ -132,6 +142,7 @@ export const createMockTransport = <TState>(
|
|
|
132
142
|
fetch: fakeFetch,
|
|
133
143
|
}),
|
|
134
144
|
commands,
|
|
145
|
+
contexts,
|
|
135
146
|
setState: (next) => {
|
|
136
147
|
state = next;
|
|
137
148
|
return sendEnvelope({ ops: [{ op: "replace", path: [], value: next }] });
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { StatewireClient } from "./statewire";
|
|
|
5
5
|
import type { StatewirePath } from "./wire/ops";
|
|
6
6
|
|
|
7
7
|
export namespace Statewire {
|
|
8
|
+
export type Context = Readonly<Record<string, unknown>>;
|
|
8
9
|
export type Commands = Record<string, (...args: never[]) => unknown>;
|
|
9
10
|
|
|
10
11
|
export type Command<TCommands extends Commands = Commands> = {
|
|
@@ -56,6 +57,17 @@ export interface Statewire<
|
|
|
56
57
|
state: TState;
|
|
57
58
|
documents?: StatewireDocuments.Source;
|
|
58
59
|
selection?: StatewireProtocol.Selection | undefined;
|
|
60
|
+
setContext?: (protocol: string, value: Statewire.Context) => void;
|
|
61
|
+
validateContext?: (
|
|
62
|
+
protocol: string,
|
|
63
|
+
value: Statewire.Context,
|
|
64
|
+
attach: StatewireHost.Attach,
|
|
65
|
+
) => void;
|
|
66
|
+
contextChanged?: (
|
|
67
|
+
protocol: string,
|
|
68
|
+
value: Statewire.Context,
|
|
69
|
+
attach: StatewireHost.Attach,
|
|
70
|
+
) => void;
|
|
59
71
|
invoke?: (
|
|
60
72
|
protocol: string,
|
|
61
73
|
method: string,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { decodeDocument, encodeDocument } from "../document";
|
|
2
|
+
import type { Statewire } from "../types";
|
|
3
|
+
import { MAX_CONTEXT_BYTES } from "./limits";
|
|
4
|
+
import { protocolToken } from "./protocol";
|
|
5
|
+
|
|
6
|
+
const freeze = (value: unknown): void => {
|
|
7
|
+
if (typeof value !== "object" || value === null) return;
|
|
8
|
+
for (const child of Object.values(value)) freeze(child);
|
|
9
|
+
Object.freeze(value);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const contextValue = (encoded: unknown): Statewire.Context => {
|
|
13
|
+
const value = decodeDocument(encoded);
|
|
14
|
+
if (
|
|
15
|
+
typeof value !== "object" ||
|
|
16
|
+
value === null ||
|
|
17
|
+
Array.isArray(value) ||
|
|
18
|
+
value instanceof Date
|
|
19
|
+
)
|
|
20
|
+
throw new TypeError("statewire: context root must be an object");
|
|
21
|
+
freeze(value);
|
|
22
|
+
return value as Statewire.Context;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const encodeContext = (protocol: string, value: Statewire.Context) => {
|
|
26
|
+
if (!protocolToken.test(protocol))
|
|
27
|
+
throw new TypeError("statewire: invalid context protocol");
|
|
28
|
+
const encoded = encodeDocument(value);
|
|
29
|
+
contextValue(encoded);
|
|
30
|
+
const entry = { protocol, ...encoded };
|
|
31
|
+
if (
|
|
32
|
+
new TextEncoder().encode(JSON.stringify(entry)).byteLength >
|
|
33
|
+
MAX_CONTEXT_BYTES
|
|
34
|
+
)
|
|
35
|
+
throw new TypeError("statewire: context entry too large");
|
|
36
|
+
return entry;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const captureContext = (value: Statewire.Context): Statewire.Context =>
|
|
40
|
+
contextValue(encodeDocument(value));
|
package/src/wire/limits.ts
CHANGED
package/src/wire/protocol.ts
CHANGED
|
@@ -18,7 +18,7 @@ export namespace StatewireProtocol {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export const WIRE_VERSION = "2026-09-
|
|
21
|
+
export const WIRE_VERSION = "2026-09-30";
|
|
22
22
|
export const DEFAULT_PROTOCOL = "default";
|
|
23
23
|
export const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
24
24
|
export const VERSION_HEADER = "Statewire-Version";
|