statewire 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/dist/client/connection.d.ts +5 -1
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +22 -6
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +2 -0
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +15 -4
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +235 -52
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +5 -1
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js +3 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/document.d.ts +25 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +88 -0
- package/dist/document.js.map +1 -0
- package/dist/host/draft.js +1 -1
- package/dist/host/host.d.ts +1 -0
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +27 -9
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +12 -5
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +107 -43
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +9 -1
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host-internal.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/statewire.d.ts +33 -2
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +34 -3
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fixtures.js +1 -0
- package/dist/transports/fixtures.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +15 -8
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +8 -8
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +1 -1
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +12 -4
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.js +2 -2
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wire/documents.d.ts +25 -2
- package/dist/wire/documents.d.ts.map +1 -1
- package/dist/wire/documents.js +28 -8
- package/dist/wire/documents.js.map +1 -1
- package/dist/wire/meta.d.ts +3 -18
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +8 -57
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.js +1 -1
- package/dist/wire/protocol.d.ts +55 -0
- package/dist/wire/protocol.d.ts.map +1 -0
- package/dist/wire/protocol.js +221 -0
- package/dist/wire/protocol.js.map +1 -0
- package/dist/wire/version.d.ts +1 -3
- package/dist/wire/version.d.ts.map +1 -1
- package/dist/wire/version.js +1 -3
- package/dist/wire/version.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +43 -13
- package/src/client/core.ts +6 -0
- package/src/client/session.ts +340 -45
- package/src/client/sessionStore.ts +8 -0
- package/src/document.ts +185 -0
- package/src/host/host.ts +1 -0
- package/src/host/http.ts +36 -7
- package/src/host/socket.ts +149 -57
- package/src/host-internal.ts +12 -1
- package/src/index.ts +5 -0
- package/src/statewire.ts +109 -6
- package/src/transports/fixtures.ts +1 -1
- package/src/transports/remote.ts +16 -7
- package/src/transports/sse.ts +18 -8
- package/src/transports/testing.ts +10 -2
- package/src/transports/ws.ts +1 -1
- package/src/types.ts +11 -0
- package/src/wire/documents.ts +126 -8
- package/src/wire/meta.ts +11 -94
- package/src/wire/protocol.ts +327 -0
- package/src/wire/version.ts +0 -2
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
//#region src/wire/protocol.ts
|
|
2
|
+
const WIRE_VERSION = "2026-09-07";
|
|
3
|
+
const DEFAULT_PROTOCOL = "default";
|
|
4
|
+
const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
5
|
+
const VERSION_HEADER = "Statewire-Version";
|
|
6
|
+
const protocolToken = /^[A-Za-z*][A-Za-z0-9!#$%&'*+.^_`|~:/-]*$/;
|
|
7
|
+
const invalid = (message) => {
|
|
8
|
+
throw new Error(`statewire: protocol error: ${message}`);
|
|
9
|
+
};
|
|
10
|
+
const validateVersion = (value) => {
|
|
11
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return invalid("version must be a YYYY-MM-DD date");
|
|
12
|
+
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
13
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value || value.startsWith("0000")) return invalid("invalid version date");
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
const validateRange = (range) => {
|
|
17
|
+
const version = validateVersion(range.version);
|
|
18
|
+
const minVersion = range.minVersion === void 0 ? version : validateVersion(range.minVersion);
|
|
19
|
+
if (minVersion > version) invalid("min-version exceeds version");
|
|
20
|
+
return {
|
|
21
|
+
version,
|
|
22
|
+
minVersion
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const inRange = (value, range) => {
|
|
26
|
+
const checked = validateRange(range);
|
|
27
|
+
return value >= checked.minVersion && value <= checked.version;
|
|
28
|
+
};
|
|
29
|
+
const protocolOffers = (options) => options.applicationProtocols ?? [{
|
|
30
|
+
name: options.protocol ?? "default",
|
|
31
|
+
version: "2026-09-07"
|
|
32
|
+
}];
|
|
33
|
+
const negotiationHeaders = (options) => {
|
|
34
|
+
const suffix = (range) => {
|
|
35
|
+
const { version, minVersion } = validateRange(range);
|
|
36
|
+
return `"${version}"${minVersion === version ? "" : `; min-version="${minVersion}"`}`;
|
|
37
|
+
};
|
|
38
|
+
const offers = protocolOffers(options);
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
if (offers !== "*" && !offers.length) invalid("protocol list must not be empty");
|
|
41
|
+
return {
|
|
42
|
+
[VERSION_HEADER]: suffix(options.wireVersion ?? { version: "2026-09-07" }),
|
|
43
|
+
[PROTOCOL_HEADER]: offers === "*" ? "*" : offers.map((offer) => {
|
|
44
|
+
if (!protocolToken.test(offer.name) || offer.name === "*" || seen.has(offer.name)) invalid("invalid or duplicate protocol name");
|
|
45
|
+
if (offer.optional !== void 0 && typeof offer.optional !== "boolean") invalid("optional must be boolean");
|
|
46
|
+
seen.add(offer.name);
|
|
47
|
+
return `${offer.name}; version=${suffix(offer)}${offer.optional ? "; optional" : ""}`;
|
|
48
|
+
}).join(", ")
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const parseItem = (input) => {
|
|
52
|
+
const atom = String.raw`"(?:[\x20-\x21\x23-\x5b\x5d-\x7e]|\\["\\])*"|[A-Za-z*][A-Za-z0-9!#$%&'*+.^_\x60|~:/-]*|\?[01]`;
|
|
53
|
+
const text = input.trim();
|
|
54
|
+
const match = new RegExp(`^(?:${atom})`).exec(text);
|
|
55
|
+
if (!match) return invalid("invalid structured item");
|
|
56
|
+
const quoted = match[0].startsWith("\"");
|
|
57
|
+
const decode = (raw) => raw.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
58
|
+
const value = quoted ? decode(match[0]) : match[0];
|
|
59
|
+
const params = /* @__PURE__ */ new Map();
|
|
60
|
+
let rest = text.slice(match[0].length);
|
|
61
|
+
while (rest) {
|
|
62
|
+
const parameter = new RegExp(`^; *([a-z*][a-z0-9_.*-]*)(?:=(${atom}))?`).exec(rest);
|
|
63
|
+
if (!parameter) return invalid("invalid structured parameters");
|
|
64
|
+
const key = parameter[1];
|
|
65
|
+
const raw = parameter[2];
|
|
66
|
+
if (params.has(key)) invalid("duplicate parameter");
|
|
67
|
+
if (raw === void 0 || raw === "?1") params.set(key, true);
|
|
68
|
+
else if (raw === "?0") params.set(key, false);
|
|
69
|
+
else if (raw.startsWith("\"")) params.set(key, decode(raw));
|
|
70
|
+
else invalid("parameter must be string or boolean");
|
|
71
|
+
rest = rest.slice(parameter[0].length);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
value,
|
|
75
|
+
quoted,
|
|
76
|
+
params
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const parseNegotiation = (headers) => {
|
|
80
|
+
const wire = headers.get(VERSION_HEADER);
|
|
81
|
+
const application = headers.get(PROTOCOL_HEADER);
|
|
82
|
+
if (wire === null || application === null) return invalid("negotiation fields are required");
|
|
83
|
+
const item = parseItem(wire);
|
|
84
|
+
if (!item.quoted || [...item.params.keys()].some((k) => k !== "min-version")) invalid("invalid Statewire-Version");
|
|
85
|
+
const minVersion = item.params.get("min-version");
|
|
86
|
+
if (minVersion !== void 0 && typeof minVersion !== "string") return invalid("min-version must be a string");
|
|
87
|
+
const wireVersion = validateRange({
|
|
88
|
+
version: item.value,
|
|
89
|
+
...minVersion === void 0 ? {} : { minVersion }
|
|
90
|
+
});
|
|
91
|
+
if (application.trim() === "*") return {
|
|
92
|
+
wireVersion,
|
|
93
|
+
applicationProtocols: "*"
|
|
94
|
+
};
|
|
95
|
+
const names = /* @__PURE__ */ new Set();
|
|
96
|
+
return {
|
|
97
|
+
wireVersion,
|
|
98
|
+
applicationProtocols: application.split(",").map((text) => {
|
|
99
|
+
const { value: name, quoted, params } = parseItem(text);
|
|
100
|
+
if (quoted || !protocolToken.test(name) || name === "*" || names.has(name)) invalid("invalid or duplicate protocol name");
|
|
101
|
+
names.add(name);
|
|
102
|
+
if ([...params.keys()].some((k) => ![
|
|
103
|
+
"version",
|
|
104
|
+
"min-version",
|
|
105
|
+
"optional"
|
|
106
|
+
].includes(k))) invalid("unsupported parameter");
|
|
107
|
+
const version = params.get("version");
|
|
108
|
+
const minVersion = params.get("min-version");
|
|
109
|
+
const optional = params.get("optional") ?? false;
|
|
110
|
+
if (typeof version !== "string" || minVersion !== void 0 && typeof minVersion !== "string" || typeof optional !== "boolean") return invalid("invalid protocol parameters");
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
...validateRange({
|
|
114
|
+
version,
|
|
115
|
+
...minVersion === void 0 ? {} : { minVersion }
|
|
116
|
+
}),
|
|
117
|
+
optional
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const validateSelection = (value, options) => {
|
|
123
|
+
if (typeof value !== "object" || value === null) return invalid("missing negotiation selection");
|
|
124
|
+
const syn = value;
|
|
125
|
+
const version = validateVersion(syn.version);
|
|
126
|
+
if (!inRange(version, options.wireVersion ?? { version: "2026-09-07" })) invalid("selected wire version is outside offer");
|
|
127
|
+
if (!Array.isArray(syn.protocols)) return invalid("syn.protocols must be an array");
|
|
128
|
+
const offers = protocolOffers(options);
|
|
129
|
+
const seen = /* @__PURE__ */ new Set();
|
|
130
|
+
const protocols = syn.protocols.map((entry) => {
|
|
131
|
+
if (typeof entry !== "object" || entry === null) return invalid("invalid selected protocol");
|
|
132
|
+
const item = entry;
|
|
133
|
+
const name = item.name;
|
|
134
|
+
if (typeof name !== "string" || !protocolToken.test(name) || name === "*" || seen.has(name)) return invalid("invalid selected protocol name");
|
|
135
|
+
const version = validateVersion(item.version);
|
|
136
|
+
seen.add(name);
|
|
137
|
+
if (offers !== "*") {
|
|
138
|
+
const offer = offers.find((o) => o.name === name);
|
|
139
|
+
if (!offer || !inRange(version, offer)) invalid("selected protocol is outside offer");
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
name,
|
|
143
|
+
version
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
if (offers !== "*" && offers.some((o) => !o.optional && !seen.has(o.name))) invalid("required protocol missing from selection");
|
|
147
|
+
return {
|
|
148
|
+
version,
|
|
149
|
+
protocols
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const negotiateProtocols = (headers, wire, definitions) => {
|
|
153
|
+
const supported = {
|
|
154
|
+
...validateRange(wire),
|
|
155
|
+
protocols: definitions.map((d) => ({
|
|
156
|
+
name: d.name,
|
|
157
|
+
...validateRange(d)
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
const failure = (code, message) => ({ fin: {
|
|
161
|
+
reason: "protocol-error",
|
|
162
|
+
code,
|
|
163
|
+
message,
|
|
164
|
+
status: code === "invalid-negotiation" ? 400 : 406,
|
|
165
|
+
...code === "invalid-negotiation" ? {} : { payload: supported }
|
|
166
|
+
} });
|
|
167
|
+
let request;
|
|
168
|
+
try {
|
|
169
|
+
request = parseNegotiation(headers);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return failure("invalid-negotiation", error instanceof Error ? error.message : "invalid negotiation");
|
|
172
|
+
}
|
|
173
|
+
const version = [wire.version, request.wireVersion.version].sort()[0];
|
|
174
|
+
if (!inRange(version, wire) || !inRange(version, request.wireVersion)) return failure("unsupported-version", "Statewire versions do not overlap");
|
|
175
|
+
const offers = request.applicationProtocols === "*" ? definitions.map((d) => ({
|
|
176
|
+
name: d.name,
|
|
177
|
+
version: d.version,
|
|
178
|
+
optional: false
|
|
179
|
+
})) : request.applicationProtocols;
|
|
180
|
+
const registry = new Map(definitions.map((d) => [d.name, d]));
|
|
181
|
+
if (registry.size !== definitions.length) throw new Error("statewire: duplicate registered protocol");
|
|
182
|
+
const offered = new Map(offers.map((o) => [o.name, o]));
|
|
183
|
+
const solve = (names) => {
|
|
184
|
+
const active = new Set(names);
|
|
185
|
+
for (const name of active) {
|
|
186
|
+
const definition = registry.get(name);
|
|
187
|
+
if (!definition || !offered.has(name)) return void 0;
|
|
188
|
+
for (const dependency of Object.keys(definition.requires ?? {})) active.add(dependency);
|
|
189
|
+
}
|
|
190
|
+
const result = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const name of active) {
|
|
192
|
+
const ranges = [
|
|
193
|
+
registry.get(name),
|
|
194
|
+
offered.get(name),
|
|
195
|
+
...[...active].flatMap((parent) => {
|
|
196
|
+
const dependency = registry.get(parent).requires?.[name];
|
|
197
|
+
return dependency ? [dependency] : [];
|
|
198
|
+
})
|
|
199
|
+
].map(validateRange);
|
|
200
|
+
const lower = ranges.map((r) => r.minVersion).sort().at(-1);
|
|
201
|
+
const upper = ranges.map((r) => r.version).sort()[0];
|
|
202
|
+
if (lower > upper) return void 0;
|
|
203
|
+
result.set(name, upper);
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
};
|
|
207
|
+
let selected = solve(new Set(offers.filter((o) => !o.optional).map((o) => o.name)));
|
|
208
|
+
if (!selected) return failure("unsupported-protocol", "required protocol combination is unsupported");
|
|
209
|
+
for (const offer of offers) if (offer.optional) selected = solve(/* @__PURE__ */ new Set([...selected.keys(), offer.name])) ?? selected;
|
|
210
|
+
return {
|
|
211
|
+
version,
|
|
212
|
+
protocols: offers.flatMap((o) => selected.has(o.name) ? [{
|
|
213
|
+
name: o.name,
|
|
214
|
+
version: selected.get(o.name)
|
|
215
|
+
}] : [])
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
//#endregion
|
|
219
|
+
export { DEFAULT_PROTOCOL, PROTOCOL_HEADER, VERSION_HEADER, WIRE_VERSION, inRange, negotiateProtocols, negotiationHeaders, parseNegotiation, protocolOffers, protocolToken, validateRange, validateSelection, validateVersion };
|
|
220
|
+
|
|
221
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","names":["StatewireProtocol","Range","version","minVersion","Offer","name","optional","Selection","protocols","Options","wireVersion","applicationProtocols","protocol","keyPrefixes","Readonly","Record","Definition","requires","WIRE_VERSION","DEFAULT_PROTOCOL","PROTOCOL_HEADER","VERSION_HEADER","protocolToken","invalid","message","Error","validateVersion","value","test","parsed","Date","Number","isFinite","getTime","toISOString","slice","startsWith","validateRange","range","undefined","inRange","checked","protocolOffers","options","negotiationHeaders","suffix","offers","seen","Set","length","map","offer","has","add","join","parseItem","input","atom","String","raw","text","trim","match","RegExp","exec","quoted","decode","replace","params","Map","rest","parameter","key","set","parseNegotiation","headers","Headers","Required","Pick","wire","get","application","item","keys","some","k","names","split","includes","validateSelection","syn","Array","isArray","entry","find","o","negotiateProtocols","definitions","fin","reason","code","status","payload","supported","d","failure","const","request","ReturnType","error","sort","registry","size","offered","solve","active","definition","dependency","Object","result","ranges","flatMap","parent","lower","r","at","upper","selected","filter"],"sources":["../../src/wire/protocol.ts"],"sourcesContent":["export namespace StatewireProtocol {\n export type Range = { version: string; minVersion?: string };\n export type Offer = Range & { name: string; optional?: boolean };\n export type Selection = {\n version: string;\n protocols: { name: string; version: string }[];\n };\n export type Options = {\n wireVersion?: Range;\n applicationProtocols?: readonly Offer[] | \"*\";\n protocol?: string;\n keyPrefixes?: Readonly<Record<string, readonly string[]>>;\n };\n export type Definition = Range & {\n name: string;\n requires?: Readonly<Record<string, Range>>;\n };\n}\n\nexport const WIRE_VERSION = \"2026-09-07\";\nexport const DEFAULT_PROTOCOL = \"default\";\nexport const PROTOCOL_HEADER = \"Statewire-Protocol\";\nexport const VERSION_HEADER = \"Statewire-Version\";\nexport const protocolToken = /^[A-Za-z*][A-Za-z0-9!#$%&'*+.^_`|~:/-]*$/;\n\nconst invalid = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nexport const validateVersion = (value: unknown): string => {\n if (typeof value !== \"string\" || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value))\n return invalid(\"version must be a YYYY-MM-DD date\");\n const parsed = new Date(`${value}T00:00:00.000Z`);\n if (\n !Number.isFinite(parsed.getTime()) ||\n parsed.toISOString().slice(0, 10) !== value ||\n value.startsWith(\"0000\")\n )\n return invalid(\"invalid version date\");\n return value;\n};\n\nexport const validateRange = (range: StatewireProtocol.Range) => {\n const version = validateVersion(range.version);\n const minVersion =\n range.minVersion === undefined\n ? version\n : validateVersion(range.minVersion);\n if (minVersion > version) invalid(\"min-version exceeds version\");\n return { version, minVersion };\n};\n\nexport const inRange = (value: string, range: StatewireProtocol.Range) => {\n const checked = validateRange(range);\n return value >= checked.minVersion && value <= checked.version;\n};\n\nexport const protocolOffers = (options: StatewireProtocol.Options) =>\n options.applicationProtocols ?? [\n { name: options.protocol ?? DEFAULT_PROTOCOL, version: WIRE_VERSION },\n ];\n\nexport const negotiationHeaders = (\n options: StatewireProtocol.Options,\n): Record<string, string> => {\n const suffix = (range: StatewireProtocol.Range) => {\n const { version, minVersion } = validateRange(range);\n return `\"${version}\"${minVersion === version ? \"\" : `; min-version=\"${minVersion}\"`}`;\n };\n const offers = protocolOffers(options);\n const seen = new Set<string>();\n if (offers !== \"*\" && !offers.length)\n invalid(\"protocol list must not be empty\");\n return {\n [VERSION_HEADER]: suffix(options.wireVersion ?? { version: WIRE_VERSION }),\n [PROTOCOL_HEADER]:\n offers === \"*\"\n ? \"*\"\n : offers\n .map((offer) => {\n if (\n !protocolToken.test(offer.name) ||\n offer.name === \"*\" ||\n seen.has(offer.name)\n )\n invalid(\"invalid or duplicate protocol name\");\n if (\n offer.optional !== undefined &&\n typeof offer.optional !== \"boolean\"\n )\n invalid(\"optional must be boolean\");\n seen.add(offer.name);\n return `${offer.name}; version=${suffix(offer)}${offer.optional ? \"; optional\" : \"\"}`;\n })\n .join(\", \"),\n };\n};\n\nconst parseItem = (input: string) => {\n const atom = String.raw`\"(?:[\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]|\\\\[\"\\\\])*\"|[A-Za-z*][A-Za-z0-9!#$%&'*+.^_\\x60|~:/-]*|\\?[01]`;\n const text = input.trim();\n const match = new RegExp(`^(?:${atom})`).exec(text);\n if (!match) return invalid(\"invalid structured item\");\n const quoted = match[0].startsWith('\"');\n const decode = (raw: string) => raw.slice(1, -1).replace(/\\\\([\"\\\\])/g, \"$1\");\n const value = quoted ? decode(match[0]) : match[0];\n const params = new Map<string, string | boolean>();\n let rest = text.slice(match[0].length);\n while (rest) {\n const parameter = new RegExp(\n `^; *([a-z*][a-z0-9_.*-]*)(?:=(${atom}))?`,\n ).exec(rest);\n if (!parameter) return invalid(\"invalid structured parameters\");\n const key = parameter[1]!;\n const raw = parameter[2];\n if (params.has(key)) invalid(\"duplicate parameter\");\n if (raw === undefined || raw === \"?1\") params.set(key, true);\n else if (raw === \"?0\") params.set(key, false);\n else if (raw.startsWith('\"')) params.set(key, decode(raw));\n else invalid(\"parameter must be string or boolean\");\n rest = rest.slice(parameter[0].length);\n }\n return { value, quoted, params };\n};\n\nexport const parseNegotiation = (\n headers: Headers,\n): Required<\n Pick<StatewireProtocol.Options, \"wireVersion\" | \"applicationProtocols\">\n> => {\n const wire = headers.get(VERSION_HEADER);\n const application = headers.get(PROTOCOL_HEADER);\n if (wire === null || application === null)\n return invalid(\"negotiation fields are required\");\n const item = parseItem(wire);\n if (!item.quoted || [...item.params.keys()].some((k) => k !== \"min-version\"))\n invalid(\"invalid Statewire-Version\");\n const minVersion = item.params.get(\"min-version\");\n if (minVersion !== undefined && typeof minVersion !== \"string\")\n return invalid(\"min-version must be a string\");\n const wireVersion = validateRange({\n version: item.value,\n ...(minVersion === undefined ? {} : { minVersion }),\n });\n if (application.trim() === \"*\")\n return { wireVersion, applicationProtocols: \"*\" };\n const names = new Set<string>();\n const applicationProtocols = application\n .split(\",\")\n .map((text): StatewireProtocol.Offer => {\n const { value: name, quoted, params } = parseItem(text);\n if (\n quoted ||\n !protocolToken.test(name) ||\n name === \"*\" ||\n names.has(name)\n )\n invalid(\"invalid or duplicate protocol name\");\n names.add(name);\n if (\n [...params.keys()].some(\n (k) => ![\"version\", \"min-version\", \"optional\"].includes(k),\n )\n )\n invalid(\"unsupported parameter\");\n const version = params.get(\"version\");\n const minVersion = params.get(\"min-version\");\n const optional = params.get(\"optional\") ?? false;\n if (\n typeof version !== \"string\" ||\n (minVersion !== undefined && typeof minVersion !== \"string\") ||\n typeof optional !== \"boolean\"\n )\n return invalid(\"invalid protocol parameters\");\n return {\n name,\n ...validateRange({\n version,\n ...(minVersion === undefined ? {} : { minVersion }),\n }),\n optional,\n };\n });\n return { wireVersion, applicationProtocols };\n};\n\nexport const validateSelection = (\n value: unknown,\n options: StatewireProtocol.Options,\n): StatewireProtocol.Selection => {\n if (typeof value !== \"object\" || value === null)\n return invalid(\"missing negotiation selection\");\n const syn = value as Record<string, unknown>;\n const version = validateVersion(syn.version);\n if (!inRange(version, options.wireVersion ?? { version: WIRE_VERSION }))\n invalid(\"selected wire version is outside offer\");\n if (!Array.isArray(syn.protocols))\n return invalid(\"syn.protocols must be an array\");\n const offers = protocolOffers(options);\n const seen = new Set<string>();\n const protocols = syn.protocols.map((entry: unknown) => {\n if (typeof entry !== \"object\" || entry === null)\n return invalid(\"invalid selected protocol\");\n const item = entry as Record<string, unknown>;\n const name = item.name;\n if (\n typeof name !== \"string\" ||\n !protocolToken.test(name) ||\n name === \"*\" ||\n seen.has(name)\n )\n return invalid(\"invalid selected protocol name\");\n const version = validateVersion(item.version);\n seen.add(name);\n if (offers !== \"*\") {\n const offer = offers.find((o) => o.name === name);\n if (!offer || !inRange(version, offer))\n invalid(\"selected protocol is outside offer\");\n }\n return { name, version };\n });\n if (offers !== \"*\" && offers.some((o) => !o.optional && !seen.has(o.name)))\n invalid(\"required protocol missing from selection\");\n return { version, protocols };\n};\n\nexport const negotiateProtocols = (\n headers: Headers,\n wire: StatewireProtocol.Range,\n definitions: readonly StatewireProtocol.Definition[],\n):\n | StatewireProtocol.Selection\n | {\n fin: {\n reason: \"protocol-error\";\n code: string;\n status: number;\n message: string;\n payload?: unknown;\n };\n } => {\n const supported = {\n ...validateRange(wire),\n protocols: definitions.map((d) => ({ name: d.name, ...validateRange(d) })),\n };\n const failure = (code: string, message: string) => ({\n fin: {\n reason: \"protocol-error\" as const,\n code,\n message,\n status: code === \"invalid-negotiation\" ? 400 : 406,\n ...(code === \"invalid-negotiation\" ? {} : { payload: supported }),\n },\n });\n let request: ReturnType<typeof parseNegotiation>;\n try {\n request = parseNegotiation(headers);\n } catch (error) {\n return failure(\n \"invalid-negotiation\",\n error instanceof Error ? error.message : \"invalid negotiation\",\n );\n }\n const version = [wire.version, request.wireVersion.version].sort()[0]!;\n if (!inRange(version, wire) || !inRange(version, request.wireVersion))\n return failure(\"unsupported-version\", \"Statewire versions do not overlap\");\n const offers =\n request.applicationProtocols === \"*\"\n ? definitions.map((d) => ({\n name: d.name,\n version: d.version,\n optional: false,\n }))\n : request.applicationProtocols;\n const registry = new Map(definitions.map((d) => [d.name, d]));\n if (registry.size !== definitions.length)\n throw new Error(\"statewire: duplicate registered protocol\");\n const offered = new Map(offers.map((o) => [o.name, o]));\n const solve = (names: Set<string>) => {\n const active = new Set(names);\n for (const name of active) {\n const definition = registry.get(name);\n if (!definition || !offered.has(name)) return undefined;\n for (const dependency of Object.keys(definition.requires ?? {}))\n active.add(dependency);\n }\n const result = new Map<string, string>();\n for (const name of active) {\n const ranges = [\n registry.get(name)!,\n offered.get(name)!,\n ...[...active].flatMap((parent) => {\n const dependency = registry.get(parent)!.requires?.[name];\n return dependency ? [dependency] : [];\n }),\n ].map(validateRange);\n const lower = ranges\n .map((r) => r.minVersion)\n .sort()\n .at(-1)!;\n const upper = ranges.map((r) => r.version).sort()[0]!;\n if (lower > upper) return undefined;\n result.set(name, upper);\n }\n return result;\n };\n let selected = solve(\n new Set(offers.filter((o) => !o.optional).map((o) => o.name)),\n );\n if (!selected)\n return failure(\n \"unsupported-protocol\",\n \"required protocol combination is unsupported\",\n );\n for (const offer of offers) {\n if (offer.optional)\n selected = solve(new Set([...selected.keys(), offer.name])) ?? selected;\n }\n return {\n version,\n protocols: offers.flatMap((o) =>\n selected!.has(o.name)\n ? [{ name: o.name, version: selected!.get(o.name)! }]\n : [],\n ),\n };\n};\n"],"mappings":"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"}
|
package/dist/wire/version.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
//#region src/wire/version.d.ts
|
|
2
|
-
declare const PROTOCOL_VERSION = "1";
|
|
3
|
-
declare const VERSION_HEADER = "Statewire-Version";
|
|
4
2
|
declare const WS_SUBPROTOCOL = "statewire.v1";
|
|
5
3
|
//#endregion
|
|
6
|
-
export {
|
|
4
|
+
export { WS_SUBPROTOCOL };
|
|
7
5
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","names":[],"sources":["../../src/wire/version.ts"],"mappings":";cAAa
|
|
1
|
+
{"version":3,"file":"version.d.ts","names":[],"sources":["../../src/wire/version.ts"],"mappings":";cAAa"}
|
package/dist/wire/version.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
//#region src/wire/version.ts
|
|
2
|
-
const PROTOCOL_VERSION = "1";
|
|
3
|
-
const VERSION_HEADER = "Statewire-Version";
|
|
4
2
|
const WS_SUBPROTOCOL = "statewire.v1";
|
|
5
3
|
//#endregion
|
|
6
|
-
export {
|
|
4
|
+
export { WS_SUBPROTOCOL };
|
|
7
5
|
|
|
8
6
|
//# sourceMappingURL=version.js.map
|
package/dist/wire/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","names":["
|
|
1
|
+
{"version":3,"file":"version.js","names":["WS_SUBPROTOCOL"],"sources":["../../src/wire/version.ts"],"sourcesContent":["export const WS_SUBPROTOCOL = \"statewire.v1\";\n"],"mappings":";AAAA,MAAaA,iBAAiB"}
|
package/package.json
CHANGED
package/src/client/connection.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
negotiationHeaders,
|
|
3
|
+
validateSelection,
|
|
4
|
+
parseNegotiation,
|
|
5
|
+
} from "../wire/protocol";
|
|
6
|
+
import type { StatewireProtocol } from "../wire/protocol";
|
|
1
7
|
import { useEffect, useRef, useState } from "react";
|
|
2
8
|
import { flushTapSync } from "@assistant-ui/tap";
|
|
3
9
|
import { reviveOps, reviveValue } from "../wire/meta";
|
|
@@ -39,6 +45,8 @@ export type StatewireCmd = {
|
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
export type StatewireSyn = {
|
|
48
|
+
version?: string;
|
|
49
|
+
protocols?: StatewireProtocol.Selection["protocols"];
|
|
42
50
|
seq?: number;
|
|
43
51
|
ctxSeq?: number;
|
|
44
52
|
record?: "ephemeral";
|
|
@@ -95,6 +103,7 @@ const openGate = (g: Gate) => {
|
|
|
95
103
|
export type AttemptOutcome = "stop" | "reattach" | "ended" | "idle";
|
|
96
104
|
|
|
97
105
|
export type Conn = {
|
|
106
|
+
negotiation?: StatewireProtocol.Options;
|
|
98
107
|
connection: StatewireClient.Connection;
|
|
99
108
|
parked: boolean;
|
|
100
109
|
lease: string | null;
|
|
@@ -283,7 +292,6 @@ const cmdsOf = (env: StatewireEnvelope): StatewireCmd[] => {
|
|
|
283
292
|
const snapshotOf = (env: StatewireEnvelope): unknown[] => {
|
|
284
293
|
const ops = env.ops;
|
|
285
294
|
if (ops === undefined) return protocolError("hello missing snapshot");
|
|
286
|
-
if (ops.length === 0) return protocolError("hello missing snapshot");
|
|
287
295
|
return reviveOps(ops);
|
|
288
296
|
};
|
|
289
297
|
|
|
@@ -298,6 +306,7 @@ const helloSynOf = (env: StatewireEnvelope): StatewireSyn => {
|
|
|
298
306
|
|
|
299
307
|
const handleHello = (c: Conn, env: StatewireEnvelope) => {
|
|
300
308
|
const syn = helloSynOf(env);
|
|
309
|
+
if (c.negotiation !== undefined) validateSelection(syn, c.negotiation);
|
|
301
310
|
const ops = snapshotOf(env);
|
|
302
311
|
c.ctx.onHello({
|
|
303
312
|
ops,
|
|
@@ -323,7 +332,12 @@ const warnUnknownSynFact = () => {
|
|
|
323
332
|
const deliverSyn = (c: Conn, value: unknown) => {
|
|
324
333
|
if (!isObject(value)) return protocolError("syn must be an object");
|
|
325
334
|
validateSynScalars(value);
|
|
326
|
-
if (
|
|
335
|
+
if (
|
|
336
|
+
value["version"] !== undefined ||
|
|
337
|
+
value["protocols"] !== undefined ||
|
|
338
|
+
value["record"] !== undefined ||
|
|
339
|
+
value["idle"] !== undefined
|
|
340
|
+
) {
|
|
327
341
|
protocolError("invalid mid-stream syn");
|
|
328
342
|
}
|
|
329
343
|
if (
|
|
@@ -578,6 +592,12 @@ export const attachFrame = async (
|
|
|
578
592
|
headers: StatewireClient.HeadersOption | undefined,
|
|
579
593
|
) => {
|
|
580
594
|
const resolved = await resolveHeaders(headers, c.authStale);
|
|
595
|
+
for (const [name, value] of Object.entries(
|
|
596
|
+
negotiationHeaders(c.negotiation ?? {}),
|
|
597
|
+
)) {
|
|
598
|
+
if (!resolved.has(name)) resolved.set(name, value);
|
|
599
|
+
}
|
|
600
|
+
c.negotiation = parseNegotiation(resolved);
|
|
581
601
|
c.authStale = false;
|
|
582
602
|
const lanes = flushTapSync(() => c.ctx.takeAttachLanes());
|
|
583
603
|
return {
|
|
@@ -589,15 +609,17 @@ export const attachFrame = async (
|
|
|
589
609
|
};
|
|
590
610
|
};
|
|
591
611
|
|
|
592
|
-
export const useLink = (
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
612
|
+
export const useLink = (
|
|
613
|
+
props: StatewireProtocol.Options & {
|
|
614
|
+
url: string;
|
|
615
|
+
isNew?: boolean | undefined;
|
|
616
|
+
onChange?: ((connection: StatewireClient.Connection) => void) | undefined;
|
|
617
|
+
onError?: ((error: StatewireClient.ConnectionError) => void) | undefined;
|
|
618
|
+
ctx: StatewireRemoteProps;
|
|
619
|
+
attemptOnce: (c: Conn, abort: AbortController) => Promise<AttemptOutcome>;
|
|
620
|
+
send: (c: Conn, frame: StatewireFrame) => Promise<StatewireSubmitResult>;
|
|
621
|
+
},
|
|
622
|
+
): RemoteLink => {
|
|
601
623
|
const latest = useRef(props);
|
|
602
624
|
latest.current = props;
|
|
603
625
|
const [, setConnection] = useState<StatewireClient.Connection | null>(null);
|
|
@@ -622,7 +644,10 @@ export const useLink = (props: {
|
|
|
622
644
|
},
|
|
623
645
|
onError: (error) => latest.current.onError?.(error),
|
|
624
646
|
onChange: (connection) => latest.current.onChange?.(connection),
|
|
625
|
-
attemptOnce: (abort) =>
|
|
647
|
+
attemptOnce: (abort) => {
|
|
648
|
+
conn.negotiation = latest.current;
|
|
649
|
+
return latest.current.attemptOnce(conn, abort);
|
|
650
|
+
},
|
|
626
651
|
notify: setConnection,
|
|
627
652
|
reconnect: () => reconnectConn(conn),
|
|
628
653
|
connection: {
|
|
@@ -647,6 +672,11 @@ export const useLink = (props: {
|
|
|
647
672
|
}),
|
|
648
673
|
);
|
|
649
674
|
|
|
675
|
+
const negotiation = JSON.stringify([
|
|
676
|
+
props.protocol,
|
|
677
|
+
props.wireVersion,
|
|
678
|
+
props.applicationProtocols,
|
|
679
|
+
]);
|
|
650
680
|
useEffect(() => {
|
|
651
681
|
c.lease = null;
|
|
652
682
|
c.attempt = 0;
|
|
@@ -671,7 +701,7 @@ export const useLink = (props: {
|
|
|
671
701
|
if (c.lifetime === lifetime) c.lifetime = null;
|
|
672
702
|
openGate(c.gate);
|
|
673
703
|
};
|
|
674
|
-
}, [c, props.url]);
|
|
704
|
+
}, [c, props.url, negotiation]);
|
|
675
705
|
|
|
676
706
|
return link;
|
|
677
707
|
};
|
package/src/client/core.ts
CHANGED
|
@@ -119,6 +119,11 @@ const recompute = (c: Core) => {
|
|
|
119
119
|
for (const overlay of c.overlays) {
|
|
120
120
|
if (overlay.dropped) continue;
|
|
121
121
|
const { inv } = overlay;
|
|
122
|
+
if (
|
|
123
|
+
inv.protocol !== undefined &&
|
|
124
|
+
inv.protocol !== (c.getOptions().protocol ?? "default")
|
|
125
|
+
)
|
|
126
|
+
continue;
|
|
122
127
|
const ctx: StatewireClient.OptimisticContext = {
|
|
123
128
|
ts: inv.ts,
|
|
124
129
|
seq: inv.seq,
|
|
@@ -235,6 +240,7 @@ const note = (
|
|
|
235
240
|
) => {
|
|
236
241
|
if (!c.getOptions().onCommandChange) return;
|
|
237
242
|
c.updates.push({
|
|
243
|
+
...(inv.protocol !== undefined && { protocol: inv.protocol }),
|
|
238
244
|
method: inv.method,
|
|
239
245
|
params: inv.params as never[],
|
|
240
246
|
seq: inv.seq,
|