statewire 0.18.2 → 0.19.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/dist/client/connection.d.ts +8 -5
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +24 -13
- package/dist/client/connection.js.map +1 -1
- package/dist/client/session.d.ts +13 -15
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +291 -256
- package/dist/client/session.js.map +1 -1
- package/dist/client/timing.d.ts +2 -1
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +2 -1
- package/dist/client/timing.js.map +1 -1
- package/dist/host/engine.d.ts +2 -0
- package/dist/host/engine.d.ts.map +1 -1
- package/dist/host/engine.js +2 -0
- package/dist/host/engine.js.map +1 -1
- package/dist/host/host.d.ts +3 -1
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +38 -7
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +8 -17
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +47 -23
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +389 -245
- package/dist/host/socket.js.map +1 -1
- package/dist/host/synced.d.ts +9 -0
- package/dist/host/synced.d.ts.map +1 -0
- package/dist/host/synced.js +45 -0
- package/dist/host/synced.js.map +1 -0
- package/dist/host-internal.d.ts +20 -8
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host.d.ts +33 -9
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/statewire.d.ts +8 -10
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +0 -14
- package/dist/statewire.js.map +1 -1
- package/dist/storage.d.ts +2 -5
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +55 -41
- package/dist/storage.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +1 -6
- package/dist/transports/remote.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.map +1 -1
- package/dist/transports/testing.d.ts +7 -5
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +19 -19
- package/dist/transports/testing.js.map +1 -1
- package/dist/types.d.ts +3 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wire/limits.d.ts +2 -2
- package/dist/wire/limits.js +2 -2
- package/dist/wire/limits.js.map +1 -1
- package/dist/wire/protocol.d.ts +6 -1
- package/dist/wire/protocol.d.ts.map +1 -1
- package/dist/wire/protocol.js +33 -12
- package/dist/wire/protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +46 -33
- package/src/client/session.ts +434 -365
- package/src/client/timing.ts +2 -0
- package/src/host/engine.ts +3 -1
- package/src/host/host.ts +50 -3
- package/src/host/http.ts +22 -23
- package/src/host/socket.ts +550 -321
- package/src/host/synced.ts +60 -0
- package/src/host-internal.ts +21 -3
- package/src/host.ts +40 -8
- package/src/index.ts +1 -0
- package/src/statewire.ts +8 -30
- package/src/storage.ts +96 -79
- package/src/transports/remote.ts +0 -7
- package/src/transports/sse.ts +8 -3
- package/src/transports/testing.ts +34 -19
- package/src/types.ts +3 -5
- package/src/wire/limits.ts +1 -1
- package/src/wire/protocol.ts +40 -11
package/dist/wire/limits.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
const MAX_COMMANDS_PER_FRAME = 256;
|
|
3
3
|
const MAX_INFLIGHT_COMMANDS_PER_CLIENT = 256;
|
|
4
4
|
const MAX_FRAME_BODY_BYTES = 8388608;
|
|
5
|
-
const
|
|
5
|
+
const MAX_EPHEMERAL_DOCUMENT_BYTES = 65536;
|
|
6
6
|
const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;
|
|
7
7
|
const COMMAND_CAPACITY_RETRY_MS = 1e3;
|
|
8
8
|
//#endregion
|
|
9
|
-
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME,
|
|
9
|
+
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_EPHEMERAL_DOCUMENT_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
|
|
10
10
|
|
|
11
11
|
//# sourceMappingURL=limits.js.map
|
package/dist/wire/limits.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","names":["MAX_COMMANDS_PER_FRAME","MAX_INFLIGHT_COMMANDS_PER_CLIENT","MAX_FRAME_BODY_BYTES","
|
|
1
|
+
{"version":3,"file":"limits.js","names":["MAX_COMMANDS_PER_FRAME","MAX_INFLIGHT_COMMANDS_PER_CLIENT","MAX_FRAME_BODY_BYTES","MAX_EPHEMERAL_DOCUMENT_BYTES","MAX_INFLIGHT_COMMANDS_PER_HOST","COMMAND_CAPACITY_RETRY_MS"],"sources":["../../src/wire/limits.ts"],"sourcesContent":["export const MAX_COMMANDS_PER_FRAME = 256;\nexport const MAX_INFLIGHT_COMMANDS_PER_CLIENT = 256;\nexport const MAX_FRAME_BODY_BYTES = 8 * 1024 * 1024;\nexport const MAX_EPHEMERAL_DOCUMENT_BYTES = 64 * 1024;\nexport const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;\nexport const COMMAND_CAPACITY_RETRY_MS = 1_000;\n"],"mappings":";AAAA,MAAaA,yBAAyB;AACtC,MAAaC,mCAAmC;AAChD,MAAaC,uBAAuB;AACpC,MAAaC,+BAA+B;AAC5C,MAAaC,iCAAiC;AAC9C,MAAaC,4BAA4B"}
|
package/dist/wire/protocol.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ declare namespace StatewireProtocol {
|
|
|
13
13
|
protocols: {
|
|
14
14
|
name: string;
|
|
15
15
|
version: string;
|
|
16
|
+
record?: "ephemeral";
|
|
16
17
|
}[];
|
|
17
18
|
};
|
|
18
19
|
type Options = {
|
|
@@ -24,9 +25,13 @@ declare namespace StatewireProtocol {
|
|
|
24
25
|
type Definition = Range & {
|
|
25
26
|
name: string;
|
|
26
27
|
requires?: Readonly<Record<string, Range>>;
|
|
28
|
+
subprotocols?: readonly {
|
|
29
|
+
name: string;
|
|
30
|
+
record?: "ephemeral";
|
|
31
|
+
}[];
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
|
-
declare const WIRE_VERSION = "2026-09-
|
|
34
|
+
declare const WIRE_VERSION = "2026-09-13";
|
|
30
35
|
declare const DEFAULT_PROTOCOL = "default";
|
|
31
36
|
declare const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
32
37
|
declare const VERSION_HEADER = "Statewire-Version";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","names":[],"sources":["../../src/wire/protocol.ts"],"mappings":";kBAAiB;OACH;IAAU;IAAiB;;OAC3B,QAAQ;IAAU;IAAc;;OAChC;IACV;IACA;MAAa;MAAc;;;
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","names":[],"sources":["../../src/wire/protocol.ts"],"mappings":";kBAAiB;OACH;IAAU;IAAiB;;OAC3B,QAAQ;IAAU;IAAc;;OAChC;IACV;IACA;MAAa;MAAc;MAAiB;;;OAElC;IACV,cAAc;IACd,gCAAgC;IAChC;IACA,cAAc,SAAS;;OAEb,aAAa;IACvB;IACA,WAAW,SAAS,eAAe;IACnC;MAA0B;MAAc;;;;cAI/B;cACA;cACA;cACA;cACA,eAAa;cAMb,kBAAmB;cAanB,gBAAiB,OAAO,kBAAkB;;;;cAU1C,UAAW,eAAe,OAAO,kBAAkB;cAKnD,iBAAkB,SAAS,kBAAkB,qBAAO,kBAAA;cAKpD,qBACX,SAAS,kBAAkB,YAC1B;cA6DU,mBACX,SAAS,YACR,SACD,KAAK,kBAAkB;cA0DZ,oBACX,gBACA,SAAS,kBAAkB,YAC1B,kBAAkB;cA6CR,qBACX,SAAS,SACT,MAAM,kBAAkB,OACxB,sBAAsB,kBAAkB,iBAEtC,kBAAkB;EAEhB;IACE;IACA;IACA;IACA;IACA"}
|
package/dist/wire/protocol.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/wire/protocol.ts
|
|
2
|
-
const WIRE_VERSION = "2026-09-
|
|
2
|
+
const WIRE_VERSION = "2026-09-13";
|
|
3
3
|
const DEFAULT_PROTOCOL = "default";
|
|
4
4
|
const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
5
5
|
const VERSION_HEADER = "Statewire-Version";
|
|
@@ -28,7 +28,7 @@ const inRange = (value, range) => {
|
|
|
28
28
|
};
|
|
29
29
|
const protocolOffers = (options) => options.applicationProtocols ?? [{
|
|
30
30
|
name: options.protocol ?? "default",
|
|
31
|
-
version: "2026-09-
|
|
31
|
+
version: "2026-09-13"
|
|
32
32
|
}];
|
|
33
33
|
const negotiationHeaders = (options) => {
|
|
34
34
|
const suffix = (range) => {
|
|
@@ -39,7 +39,7 @@ const negotiationHeaders = (options) => {
|
|
|
39
39
|
const seen = /* @__PURE__ */ new Set();
|
|
40
40
|
if (offers !== "*" && !offers.length) invalid("protocol list must not be empty");
|
|
41
41
|
return {
|
|
42
|
-
[VERSION_HEADER]: suffix(options.wireVersion ?? { version: "2026-09-
|
|
42
|
+
[VERSION_HEADER]: suffix(options.wireVersion ?? { version: "2026-09-13" }),
|
|
43
43
|
[PROTOCOL_HEADER]: offers === "*" ? "*" : offers.map((offer) => {
|
|
44
44
|
if (!protocolToken.test(offer.name) || offer.name === "*" || seen.has(offer.name)) invalid("invalid or duplicate protocol name");
|
|
45
45
|
if (offer.optional !== void 0 && typeof offer.optional !== "boolean") invalid("optional must be boolean");
|
|
@@ -123,24 +123,30 @@ const validateSelection = (value, options) => {
|
|
|
123
123
|
if (typeof value !== "object" || value === null) return invalid("missing negotiation selection");
|
|
124
124
|
const syn = value;
|
|
125
125
|
const version = validateVersion(syn.version);
|
|
126
|
-
if (!inRange(version, options.wireVersion ?? { version: "2026-09-
|
|
126
|
+
if (!inRange(version, options.wireVersion ?? { version: "2026-09-13" })) invalid("selected wire version is outside offer");
|
|
127
127
|
if (!Array.isArray(syn.protocols)) return invalid("syn.protocols must be an array");
|
|
128
128
|
const offers = protocolOffers(options);
|
|
129
|
-
const seen = /* @__PURE__ */ new
|
|
129
|
+
const seen = /* @__PURE__ */ new Map();
|
|
130
130
|
const protocols = syn.protocols.map((entry) => {
|
|
131
131
|
if (typeof entry !== "object" || entry === null) return invalid("invalid selected protocol");
|
|
132
132
|
const item = entry;
|
|
133
133
|
const name = item.name;
|
|
134
134
|
if (typeof name !== "string" || !protocolToken.test(name) || name === "*" || seen.has(name)) return invalid("invalid selected protocol name");
|
|
135
135
|
const version = validateVersion(item.version);
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
if (item.record !== void 0 && item.record !== "ephemeral") invalid("invalid selected protocol record");
|
|
137
|
+
const record = item.record;
|
|
138
|
+
const parent = name.slice(0, name.lastIndexOf("/"));
|
|
139
|
+
if (parent !== "" && seen.has(parent)) {
|
|
140
|
+
if (seen.get(parent) !== version) invalid("subprotocol version differs from its parent");
|
|
141
|
+
} else if (offers !== "*") {
|
|
138
142
|
const offer = offers.find((o) => o.name === name);
|
|
139
143
|
if (!offer || !inRange(version, offer)) invalid("selected protocol is outside offer");
|
|
140
144
|
}
|
|
145
|
+
seen.set(name, version);
|
|
141
146
|
return {
|
|
142
147
|
name,
|
|
143
|
-
version
|
|
148
|
+
version,
|
|
149
|
+
...record !== void 0 && { record }
|
|
144
150
|
};
|
|
145
151
|
});
|
|
146
152
|
if (offers !== "*" && offers.some((o) => !o.optional && !seen.has(o.name))) invalid("required protocol missing from selection");
|
|
@@ -179,6 +185,13 @@ const negotiateProtocols = (headers, wire, definitions) => {
|
|
|
179
185
|
})) : request.applicationProtocols;
|
|
180
186
|
const registry = new Map(definitions.map((d) => [d.name, d]));
|
|
181
187
|
if (registry.size !== definitions.length) throw new Error("statewire: duplicate registered protocol");
|
|
188
|
+
for (const definition of definitions) {
|
|
189
|
+
const children = /* @__PURE__ */ new Set();
|
|
190
|
+
for (const child of definition.subprotocols ?? []) {
|
|
191
|
+
if (!protocolToken.test(child.name) || child.name.includes("/") || children.has(child.name) || child.record !== void 0 && child.record !== "ephemeral") throw new Error("statewire: invalid subprotocol registration");
|
|
192
|
+
children.add(child.name);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
182
195
|
const offered = new Map(offers.map((o) => [o.name, o]));
|
|
183
196
|
const solve = (names) => {
|
|
184
197
|
const active = new Set(names);
|
|
@@ -209,10 +222,18 @@ const negotiateProtocols = (headers, wire, definitions) => {
|
|
|
209
222
|
for (const offer of offers) if (offer.optional) selected = solve(/* @__PURE__ */ new Set([...selected.keys(), offer.name])) ?? selected;
|
|
210
223
|
return {
|
|
211
224
|
version,
|
|
212
|
-
protocols: offers.flatMap((o) =>
|
|
213
|
-
|
|
214
|
-
version
|
|
215
|
-
|
|
225
|
+
protocols: offers.flatMap((o) => {
|
|
226
|
+
const version = selected.get(o.name);
|
|
227
|
+
if (version === void 0) return [];
|
|
228
|
+
return [{
|
|
229
|
+
name: o.name,
|
|
230
|
+
version
|
|
231
|
+
}, ...(registry.get(o.name).subprotocols ?? []).map((child) => ({
|
|
232
|
+
name: `${o.name}/${child.name}`,
|
|
233
|
+
version,
|
|
234
|
+
...child.record !== void 0 && { record: child.record }
|
|
235
|
+
}))];
|
|
236
|
+
})
|
|
216
237
|
};
|
|
217
238
|
};
|
|
218
239
|
//#endregion
|
|
@@ -1 +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":";AAmBA,MAAakB,eAAe;AAC5B,MAAaC,mBAAmB;AAChC,MAAaC,kBAAkB;AAC/B,MAAaC,iBAAiB;AAC9B,MAAaC,gBAAgB;AAE7B,MAAMC,WAAWC,YAA2B;CAC1C,MAAM,IAAIC,MAAM,8BAA8BD,SAAS;AACzD;AAEA,MAAaE,mBAAmBC,UAA2B;CACzD,IAAI,OAAOA,UAAU,YAAY,CAAC,sBAAsBC,KAAKD,KAAK,GAChE,OAAOJ,QAAQ,mCAAmC;CACpD,MAAMM,yBAAS,IAAIC,KAAK,GAAGH,MAAK,eAAgB;CAChD,IACE,CAACI,OAAOC,SAASH,OAAOI,QAAQ,CAAC,KACjCJ,OAAOK,YAAY,CAAC,CAACC,MAAM,GAAG,EAAE,MAAMR,SACtCA,MAAMS,WAAW,MAAM,GAEvB,OAAOb,QAAQ,sBAAsB;CACvC,OAAOI;AACT;AAEA,MAAaU,iBAAiBC,UAAmC;CAC/D,MAAMpC,UAAUwB,gBAAgBY,MAAMpC,OAAO;CAC7C,MAAMC,aACJmC,MAAMnC,eAAeoC,KAAAA,IACjBrC,UACAwB,gBAAgBY,MAAMnC,UAAU;CACtC,IAAIA,aAAaD,SAASqB,QAAQ,6BAA6B;CAC/D,OAAO;EAAErB;EAASC;CAAW;AAC/B;AAEA,MAAaqC,WAAWb,OAAeW,UAAmC;CACxE,MAAMG,UAAUJ,cAAcC,KAAK;CACnC,OAAOX,SAASc,QAAQtC,cAAcwB,SAASc,QAAQvC;AACzD;AAEA,MAAawC,kBAAkBC,YAC7BA,QAAQhC,wBAAwB,CAC9B;CAAEN,MAAMsC,QAAQ/B,YAAAA;CAA8BV,SAAAA;AAAsB,CAAC;AAGzE,MAAa0C,sBACXD,YAC2B;CAC3B,MAAME,UAAUP,UAAmC;EACjD,MAAM,EAAEpC,SAASC,eAAekC,cAAcC,KAAK;EACnD,OAAO,IAAIpC,QAAO,GAAIC,eAAeD,UAAU,KAAK,kBAAkBC,WAAU;CAClF;CACA,MAAM2C,SAASJ,eAAeC,OAAO;CACrC,MAAMI,uBAAO,IAAIC,IAAY;CAC7B,IAAIF,WAAW,OAAO,CAACA,OAAOG,QAC5B1B,QAAQ,iCAAiC;CAC3C,OAAO;GACJF,iBAAiBwB,OAAOF,QAAQjC,eAAe,EAAER,SAAAA,aAAsB,CAAC;GACxEkB,kBACC0B,WAAW,MACP,MACAA,OACGI,KAAKC,UAAU;GACd,IACE,CAAC7B,cAAcM,KAAKuB,MAAM9C,IAAI,KAC9B8C,MAAM9C,SAAS,OACf0C,KAAKK,IAAID,MAAM9C,IAAI,GAEnBkB,QAAQ,oCAAoC;GAC9C,IACE4B,MAAM7C,aAAaiC,KAAAA,KACnB,OAAOY,MAAM7C,aAAa,WAE1BiB,QAAQ,0BAA0B;GACpCwB,KAAKM,IAAIF,MAAM9C,IAAI;GACnB,OAAO,GAAG8C,MAAM9C,KAAI,YAAawC,OAAOM,KAAK,IAAIA,MAAM7C,WAAW,eAAe;EACnF,CAAC,CAAC,CACDgD,KAAK,IAAI;CACpB;AACF;AAEA,MAAMC,aAAaC,UAAkB;CACnC,MAAMC,OAAOC,OAAOC,GAAG;CACvB,MAAMC,OAAOJ,MAAMK,KAAK;CACxB,MAAMC,QAAQ,IAAIC,OAAO,OAAON,KAAI,EAAG,CAAC,CAACO,KAAKJ,IAAI;CAClD,IAAI,CAACE,OAAO,OAAOvC,QAAQ,yBAAyB;CACpD,MAAM0C,SAASH,MAAM,EAAE,CAAC1B,WAAW,IAAG;CACtC,MAAM8B,UAAUP,QAAgBA,IAAIxB,MAAM,GAAG,EAAE,CAAC,CAACgC,QAAQ,cAAc,IAAI;CAC3E,MAAMxC,QAAQsC,SAASC,OAAOJ,MAAM,EAAE,IAAIA,MAAM;CAChD,MAAMM,yBAAS,IAAIC,IAA8B;CACjD,IAAIC,OAAOV,KAAKzB,MAAM2B,MAAM,EAAE,CAACb,MAAM;CACrC,OAAOqB,MAAM;EACX,MAAMC,YAAY,IAAIR,OACpB,iCAAiCN,KAAI,IACvC,CAAC,CAACO,KAAKM,IAAI;EACX,IAAI,CAACC,WAAW,OAAOhD,QAAQ,+BAA+B;EAC9D,MAAMiD,MAAMD,UAAU;EACtB,MAAMZ,MAAMY,UAAU;EACtB,IAAIH,OAAOhB,IAAIoB,GAAG,GAAGjD,QAAQ,qBAAqB;EAClD,IAAIoC,QAAQpB,KAAAA,KAAaoB,QAAQ,MAAMS,OAAOK,IAAID,KAAK,IAAI;OACtD,IAAIb,QAAQ,MAAMS,OAAOK,IAAID,KAAK,KAAK;OACvC,IAAIb,IAAIvB,WAAW,IAAG,GAAGgC,OAAOK,IAAID,KAAKN,OAAOP,GAAG,CAAC;OACpDpC,QAAQ,qCAAqC;EAClD+C,OAAOA,KAAKnC,MAAMoC,UAAU,EAAE,CAACtB,MAAM;CACvC;CACA,OAAO;EAAEtB;EAAOsC;EAAQG;CAAO;AACjC;AAEA,MAAaM,oBACXC,YAGG;CACH,MAAMI,OAAOJ,QAAQK,IAAI3D,cAAc;CACvC,MAAM4D,cAAcN,QAAQK,IAAI5D,eAAe;CAC/C,IAAI2D,SAAS,QAAQE,gBAAgB,MACnC,OAAO1D,QAAQ,iCAAiC;CAClD,MAAM2D,OAAO3B,UAAUwB,IAAI;CAC3B,IAAI,CAACG,KAAKjB,UAAU,CAAC,GAAGiB,KAAKd,OAAOe,KAAK,CAAC,CAAC,CAACC,MAAMC,MAAMA,MAAM,aAAa,GACzE9D,QAAQ,2BAA2B;CACrC,MAAMpB,aAAa+E,KAAKd,OAAOY,IAAI,aAAa;CAChD,IAAI7E,eAAeoC,KAAAA,KAAa,OAAOpC,eAAe,UACpD,OAAOoB,QAAQ,8BAA8B;CAC/C,MAAMb,cAAc2B,cAAc;EAChCnC,SAASgF,KAAKvD;EACd,GAAIxB,eAAeoC,KAAAA,IAAY,CAAC,IAAI,EAAEpC,WAAW;CACnD,CAAC;CACD,IAAI8E,YAAYpB,KAAK,MAAM,KACzB,OAAO;EAAEnD;EAAaC,sBAAsB;CAAI;CAClD,MAAM2E,wBAAQ,IAAItC,IAAY;CAqC9B,OAAO;EAAEtC;EAAaC,sBApCOsE,YAC1BM,MAAM,GAAG,CAAC,CACVrC,KAAKU,SAAkC;GACtC,MAAM,EAAEjC,OAAOtB,MAAM4D,QAAQG,WAAWb,UAAUK,IAAI;GACtD,IACEK,UACA,CAAC3C,cAAcM,KAAKvB,IAAI,KACxBA,SAAS,OACTiF,MAAMlC,IAAI/C,IAAI,GAEdkB,QAAQ,oCAAoC;GAC9C+D,MAAMjC,IAAIhD,IAAI;GACd,IACE,CAAC,GAAG+D,OAAOe,KAAK,CAAC,CAAC,CAACC,MAChBC,MAAM,CAAC;IAAC;IAAW;IAAe;GAAU,CAAC,CAACG,SAASH,CAAC,CAC3D,GAEA9D,QAAQ,uBAAuB;GACjC,MAAMrB,UAAUkE,OAAOY,IAAI,SAAS;GACpC,MAAM7E,aAAaiE,OAAOY,IAAI,aAAa;GAC3C,MAAM1E,WAAW8D,OAAOY,IAAI,UAAU,KAAK;GAC3C,IACE,OAAO9E,YAAY,YAClBC,eAAeoC,KAAAA,KAAa,OAAOpC,eAAe,YACnD,OAAOG,aAAa,WAEpB,OAAOiB,QAAQ,6BAA6B;GAC9C,OAAO;IACLlB;IACA,GAAGgC,cAAc;KACfnC;KACA,GAAIC,eAAeoC,KAAAA,IAAY,CAAC,IAAI,EAAEpC,WAAW;IACnD,CAAC;IACDG;GACF;EACF,CACoBK;CAAqB;AAC7C;AAEA,MAAa8E,qBACX9D,OACAgB,YACgC;CAChC,IAAI,OAAOhB,UAAU,YAAYA,UAAU,MACzC,OAAOJ,QAAQ,+BAA+B;CAChD,MAAMmE,MAAM/D;CACZ,MAAMzB,UAAUwB,gBAAgBgE,IAAIxF,OAAO;CAC3C,IAAI,CAACsC,QAAQtC,SAASyC,QAAQjC,eAAe,EAAER,SAAAA,aAAsB,CAAC,GACpEqB,QAAQ,wCAAwC;CAClD,IAAI,CAACoE,MAAMC,QAAQF,IAAIlF,SAAS,GAC9B,OAAOe,QAAQ,gCAAgC;CACjD,MAAMuB,SAASJ,eAAeC,OAAO;CACrC,MAAMI,uBAAO,IAAIC,IAAY;CAC7B,MAAMxC,YAAYkF,IAAIlF,UAAU0C,KAAK2C,UAAmB;EACtD,IAAI,OAAOA,UAAU,YAAYA,UAAU,MACzC,OAAOtE,QAAQ,2BAA2B;EAC5C,MAAM2D,OAAOW;EACb,MAAMxF,OAAO6E,KAAK7E;EAClB,IACE,OAAOA,SAAS,YAChB,CAACiB,cAAcM,KAAKvB,IAAI,KACxBA,SAAS,OACT0C,KAAKK,IAAI/C,IAAI,GAEb,OAAOkB,QAAQ,gCAAgC;EACjD,MAAMrB,UAAUwB,gBAAgBwD,KAAKhF,OAAO;EAC5C6C,KAAKM,IAAIhD,IAAI;EACb,IAAIyC,WAAW,KAAK;GAClB,MAAMK,QAAQL,OAAOgD,MAAMC,MAAMA,EAAE1F,SAASA,IAAI;GAChD,IAAI,CAAC8C,SAAS,CAACX,QAAQtC,SAASiD,KAAK,GACnC5B,QAAQ,oCAAoC;EAChD;EACA,OAAO;GAAElB;GAAMH;EAAQ;CACzB,CAAC;CACD,IAAI4C,WAAW,OAAOA,OAAOsC,MAAMW,MAAM,CAACA,EAAEzF,YAAY,CAACyC,KAAKK,IAAI2C,EAAE1F,IAAI,CAAC,GACvEkB,QAAQ,0CAA0C;CACpD,OAAO;EAAErB;EAASM;CAAU;AAC9B;AAEA,MAAawF,sBACXrB,SACAI,MACAkB,gBAWO;CACP,MAAMM,YAAY;EAChB,GAAGlE,cAAc0C,IAAI;EACrBvE,WAAWyF,YAAY/C,KAAKsD,OAAO;GAAEnG,MAAMmG,EAAEnG;GAAM,GAAGgC,cAAcmE,CAAC;EAAE,EAAE;CAC3E;CACA,MAAMC,WAAWL,MAAc5E,aAAqB,EAClD0E,KAAK;EACHC,QAAQ;EACRC;EACA5E;EACA6E,QAAQD,SAAS,wBAAwB,MAAM;EAC/C,GAAIA,SAAS,wBAAwB,CAAC,IAAI,EAAEE,SAASC,UAAU;CACjE,EACF;CACA,IAAII;CACJ,IAAI;EACFA,UAAUjC,iBAAiBC,OAAO;CACpC,SAASkC,OAAO;EACd,OAAOJ,QACL,uBACAI,iBAAiBpF,QAAQoF,MAAMrF,UAAU,qBAC3C;CACF;CACA,MAAMtB,UAAU,CAAC6E,KAAK7E,SAASyG,QAAQjG,YAAYR,OAAO,CAAC,CAAC4G,KAAK,CAAC,CAAC;CACnE,IAAI,CAACtE,QAAQtC,SAAS6E,IAAI,KAAK,CAACvC,QAAQtC,SAASyG,QAAQjG,WAAW,GAClE,OAAO+F,QAAQ,uBAAuB,mCAAmC;CAC3E,MAAM3D,SACJ6D,QAAQhG,yBAAyB,MAC7BsF,YAAY/C,KAAKsD,OAAO;EACtBnG,MAAMmG,EAAEnG;EACRH,SAASsG,EAAEtG;EACXI,UAAU;CACZ,EAAE,IACFqG,QAAQhG;CACd,MAAMoG,WAAW,IAAI1C,IAAI4B,YAAY/C,KAAKsD,MAAM,CAACA,EAAEnG,MAAMmG,CAAC,CAAC,CAAC;CAC5D,IAAIO,SAASC,SAASf,YAAYhD,QAChC,MAAM,IAAIxB,MAAM,0CAA0C;CAC5D,MAAMwF,UAAU,IAAI5C,IAAIvB,OAAOI,KAAK6C,MAAM,CAACA,EAAE1F,MAAM0F,CAAC,CAAC,CAAC;CACtD,MAAMmB,SAAS5B,UAAuB;EACpC,MAAM6B,SAAS,IAAInE,IAAIsC,KAAK;EAC5B,KAAK,MAAMjF,QAAQ8G,QAAQ;GACzB,MAAMC,aAAaL,SAAS/B,IAAI3E,IAAI;GACpC,IAAI,CAAC+G,cAAc,CAACH,QAAQ7D,IAAI/C,IAAI,GAAG,OAAOkC,KAAAA;GAC9C,KAAK,MAAM8E,cAAcC,OAAOnC,KAAKiC,WAAWnG,YAAY,CAAC,CAAC,GAC5DkG,OAAO9D,IAAIgE,UAAU;EACzB;EACA,MAAME,yBAAS,IAAIlD,IAAoB;EACvC,KAAK,MAAMhE,QAAQ8G,QAAQ;GACzB,MAAMK,SAAS;IACbT,SAAS/B,IAAI3E,IAAI;IACjB4G,QAAQjC,IAAI3E,IAAI;IAChB,GAAG,CAAC,GAAG8G,MAAM,CAAC,CAACM,SAASC,WAAW;KACjC,MAAML,aAAaN,SAAS/B,IAAI0C,MAAM,CAAC,CAAEzG,WAAWZ;KACpD,OAAOgH,aAAa,CAACA,UAAU,IAAI,CAAA;IACrC,CAAC;GAAC,CACH,CAACnE,IAAIb,aAAa;GACnB,MAAMsF,QAAQH,OACXtE,KAAK0E,MAAMA,EAAEzH,UAAU,CAAC,CACxB2G,KAAK,CAAC,CACNe,GAAG,EAAE;GACR,MAAMC,QAAQN,OAAOtE,KAAK0E,MAAMA,EAAE1H,OAAO,CAAC,CAAC4G,KAAK,CAAC,CAAC;GAClD,IAAIa,QAAQG,OAAO,OAAOvF,KAAAA;GAC1BgF,OAAO9C,IAAIpE,MAAMyH,KAAK;EACxB;EACA,OAAOP;CACT;CACA,IAAIQ,WAAWb,MACb,IAAIlE,IAAIF,OAAOkF,QAAQjC,MAAM,CAACA,EAAEzF,QAAQ,CAAC,CAAC4C,KAAK6C,MAAMA,EAAE1F,IAAI,CAAC,CAC9D;CACA,IAAI,CAAC0H,UACH,OAAOtB,QACL,wBACA,8CACF;CACF,KAAK,MAAMtD,SAASL,QAClB,IAAIK,MAAM7C,UACRyH,WAAWb,sBAAM,IAAIlE,IAAI,CAAC,GAAG+E,SAAS5C,KAAK,GAAGhC,MAAM9C,IAAI,CAAC,CAAC,KAAK0H;CAEnE,OAAO;EACL7H;EACAM,WAAWsC,OAAO2E,SAAS1B,MACzBgC,SAAU3E,IAAI2C,EAAE1F,IAAI,IAChB,CAAC;GAAEA,MAAM0F,EAAE1F;GAAMH,SAAS6H,SAAU/C,IAAIe,EAAE1F,IAAI;EAAG,CAAC,IAClD,CAAA,CACN;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"protocol.js","names":["StatewireProtocol","Range","version","minVersion","Offer","name","optional","Selection","protocols","record","Options","wireVersion","applicationProtocols","protocol","keyPrefixes","Readonly","Record","Definition","requires","subprotocols","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","parent","lastIndexOf","child","find","o","negotiateProtocols","definitions","fin","reason","code","status","payload","supported","d","failure","const","request","ReturnType","error","sort","registry","size","definition","children","offered","solve","active","dependency","Object","result","ranges","flatMap","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; record?: \"ephemeral\" }[];\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 subprotocols?: readonly { name: string; record?: \"ephemeral\" }[];\n };\n}\n\nexport const WIRE_VERSION = \"2026-09-13\";\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 Map<string, 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 if (item.record !== undefined && item.record !== \"ephemeral\")\n invalid(\"invalid selected protocol record\");\n const record = item.record as \"ephemeral\" | undefined;\n const parent = name.slice(0, name.lastIndexOf(\"/\"));\n const child = parent !== \"\" && seen.has(parent);\n if (child) {\n if (seen.get(parent) !== version)\n invalid(\"subprotocol version differs from its parent\");\n } else 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 seen.set(name, version);\n return { name, version, ...(record !== undefined && { record }) };\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 for (const definition of definitions) {\n const children = new Set<string>();\n for (const child of definition.subprotocols ?? []) {\n if (\n !protocolToken.test(child.name) ||\n child.name.includes(\"/\") ||\n children.has(child.name) ||\n (child.record !== undefined && child.record !== \"ephemeral\")\n )\n throw new Error(\"statewire: invalid subprotocol registration\");\n children.add(child.name);\n }\n }\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 const version = selected!.get(o.name);\n if (version === undefined) return [];\n return [\n { name: o.name, version },\n ...(registry.get(o.name)!.subprotocols ?? []).map((child) => ({\n name: `${o.name}/${child.name}`,\n version,\n ...(child.record !== undefined && { record: child.record }),\n })),\n ];\n }),\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/client/connection.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { StatewireProtocol } from "../wire/protocol";
|
|
|
7
7
|
import { useEffect, useRef, useState } from "react";
|
|
8
8
|
import { flushTapSync } from "@assistant-ui/tap";
|
|
9
9
|
import { reviveOps, reviveValue } from "../wire/meta";
|
|
10
|
-
import { backoff, timing } from "./timing";
|
|
10
|
+
import { backoff, retryHint, timing } from "./timing";
|
|
11
11
|
import { isStorageError } from "./storageError";
|
|
12
12
|
import type {
|
|
13
13
|
ConnectionState,
|
|
@@ -35,6 +35,7 @@ export const TERMINAL = [
|
|
|
35
35
|
] as const;
|
|
36
36
|
|
|
37
37
|
export type StatewireCmd = {
|
|
38
|
+
protocol: string;
|
|
38
39
|
seq: number;
|
|
39
40
|
type?: "applied" | (typeof TERMINAL)[number];
|
|
40
41
|
code?: string;
|
|
@@ -47,10 +48,12 @@ export type StatewireCmd = {
|
|
|
47
48
|
|
|
48
49
|
export type StatewireSyn = {
|
|
49
50
|
version?: string;
|
|
50
|
-
protocols?:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
protocols?: {
|
|
52
|
+
name: string;
|
|
53
|
+
version?: string;
|
|
54
|
+
record?: "ephemeral";
|
|
55
|
+
seq?: number;
|
|
56
|
+
}[];
|
|
54
57
|
id?: string;
|
|
55
58
|
idle?: boolean;
|
|
56
59
|
retry?: number;
|
|
@@ -197,13 +200,33 @@ const validateOptionalString = (value: unknown, field: string): void => {
|
|
|
197
200
|
}
|
|
198
201
|
};
|
|
199
202
|
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
const validateSynProtocols = (value: unknown, hello: boolean): void => {
|
|
204
|
+
if (value === undefined) return;
|
|
205
|
+
if (!Array.isArray(value))
|
|
206
|
+
return protocolError("syn.protocols must be an array");
|
|
207
|
+
for (const entry of value) {
|
|
208
|
+
if (!isObject(entry))
|
|
209
|
+
return protocolError("syn.protocols entry must be an object");
|
|
210
|
+
if (typeof entry["name"] !== "string")
|
|
211
|
+
protocolError("syn.protocols[].name must be a string");
|
|
212
|
+
if (
|
|
213
|
+
!hello &&
|
|
214
|
+
(entry["version"] !== undefined || entry["record"] !== undefined)
|
|
215
|
+
)
|
|
216
|
+
protocolError("invalid mid-stream syn");
|
|
217
|
+
if (
|
|
218
|
+
hello
|
|
219
|
+
? !isNonnegativeSafeInteger(entry["seq"])
|
|
220
|
+
: entry["seq"] !== undefined && !isNonnegativeSafeInteger(entry["seq"])
|
|
221
|
+
)
|
|
222
|
+
protocolError("syn.protocols[].seq must be a nonnegative safe integer");
|
|
223
|
+
if (hello && entry["record"] === "ephemeral" && entry["seq"] !== 0)
|
|
224
|
+
protocolError("ephemeral lane hello seq must be 0");
|
|
206
225
|
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const validateSynScalars = (syn: Record<string, unknown>, hello: boolean) => {
|
|
229
|
+
validateSynProtocols(syn["protocols"], hello);
|
|
207
230
|
if (syn["retry"] !== undefined && !isNonnegativeSafeInteger(syn["retry"])) {
|
|
208
231
|
protocolError("syn.retry must be a nonnegative safe integer");
|
|
209
232
|
}
|
|
@@ -211,24 +234,19 @@ const validateSynScalars = (syn: Record<string, unknown>) => {
|
|
|
211
234
|
if (syn["idle"] !== undefined && typeof syn["idle"] !== "boolean") {
|
|
212
235
|
protocolError("syn.idle must be a boolean");
|
|
213
236
|
}
|
|
214
|
-
validateOptionalString(syn["record"], "syn.record");
|
|
215
237
|
};
|
|
216
238
|
|
|
217
239
|
export const validateRefusalSyn = (value: unknown): StatewireSyn => {
|
|
218
240
|
if (!isObject(value)) return protocolError("syn must be an object");
|
|
219
|
-
validateSynScalars(value);
|
|
241
|
+
validateSynScalars(value, false);
|
|
220
242
|
if (
|
|
221
243
|
value["id"] !== undefined ||
|
|
222
244
|
value["idle"] !== undefined ||
|
|
223
|
-
value["
|
|
245
|
+
value["version"] !== undefined
|
|
224
246
|
) {
|
|
225
247
|
protocolError("invalid refusal syn");
|
|
226
248
|
}
|
|
227
|
-
if (
|
|
228
|
-
value["seq"] === undefined &&
|
|
229
|
-
value["ctxSeq"] === undefined &&
|
|
230
|
-
value["retry"] === undefined
|
|
231
|
-
) {
|
|
249
|
+
if (value["protocols"] === undefined && value["retry"] === undefined) {
|
|
232
250
|
protocolError("refusal syn has no watermarks or retry");
|
|
233
251
|
}
|
|
234
252
|
return value as StatewireSyn;
|
|
@@ -236,6 +254,9 @@ export const validateRefusalSyn = (value: unknown): StatewireSyn => {
|
|
|
236
254
|
|
|
237
255
|
const validateCmd = (value: unknown): value is StatewireCmd => {
|
|
238
256
|
if (!isObject(value)) return protocolError("cmd entry must be an object");
|
|
257
|
+
if (typeof value["protocol"] !== "string") {
|
|
258
|
+
protocolError("cmd.protocol must be a string");
|
|
259
|
+
}
|
|
239
260
|
if (!isPositiveSafeInteger(value["seq"])) {
|
|
240
261
|
protocolError("cmd.seq must be a positive safe integer");
|
|
241
262
|
}
|
|
@@ -313,7 +334,7 @@ const snapshotOf = (env: StatewireEnvelope): unknown[] => {
|
|
|
313
334
|
|
|
314
335
|
const helloSynOf = (env: StatewireEnvelope): StatewireSyn => {
|
|
315
336
|
if (!isObject(env.syn)) return protocolError("expected hello");
|
|
316
|
-
validateSynScalars(env.syn);
|
|
337
|
+
validateSynScalars(env.syn, true);
|
|
317
338
|
if (env.syn["retry"] !== undefined) {
|
|
318
339
|
protocolError("hello syn cannot carry retry");
|
|
319
340
|
}
|
|
@@ -328,6 +349,7 @@ const handleHello = (c: Conn, env: StatewireEnvelope) => {
|
|
|
328
349
|
ops,
|
|
329
350
|
syn,
|
|
330
351
|
});
|
|
352
|
+
if (env.cmd !== undefined) c.ctx.onCmds(env.cmd);
|
|
331
353
|
c.ctx.onIdle(syn.idle ?? false);
|
|
332
354
|
c.awaitingHello = false;
|
|
333
355
|
c.error = undefined;
|
|
@@ -347,20 +369,11 @@ const warnUnknownSynFact = () => {
|
|
|
347
369
|
|
|
348
370
|
const deliverSyn = (c: Conn, value: unknown) => {
|
|
349
371
|
if (!isObject(value)) return protocolError("syn must be an object");
|
|
350
|
-
validateSynScalars(value);
|
|
351
|
-
if (
|
|
352
|
-
value["version"] !== undefined ||
|
|
353
|
-
value["protocols"] !== undefined ||
|
|
354
|
-
value["record"] !== undefined ||
|
|
355
|
-
value["idle"] !== undefined
|
|
356
|
-
) {
|
|
372
|
+
validateSynScalars(value, false);
|
|
373
|
+
if (value["version"] !== undefined || value["idle"] !== undefined) {
|
|
357
374
|
protocolError("invalid mid-stream syn");
|
|
358
375
|
}
|
|
359
|
-
if (
|
|
360
|
-
value["seq"] !== undefined ||
|
|
361
|
-
value["ctxSeq"] !== undefined ||
|
|
362
|
-
value["retry"] !== undefined
|
|
363
|
-
) {
|
|
376
|
+
if (value["protocols"] !== undefined || value["retry"] !== undefined) {
|
|
364
377
|
c.ctx.onRefusal(validateRefusalSyn(value));
|
|
365
378
|
return;
|
|
366
379
|
}
|
|
@@ -394,7 +407,7 @@ export const settleFin = (c: Conn, fin: StatewireHost.Fin): AttemptOutcome => {
|
|
|
394
407
|
? fin
|
|
395
408
|
: { ...fin, payload: reviveValue(fin.payload, fin.meta) };
|
|
396
409
|
releaseHeld(c);
|
|
397
|
-
c.retryAt = Date.now() + (finish.retry ?? 0);
|
|
410
|
+
c.retryAt = Date.now() + retryHint(finish.retry ?? 0);
|
|
398
411
|
if (finish.reason === "idle") {
|
|
399
412
|
c.ctx.onIdle(true);
|
|
400
413
|
c.attempt = 0;
|