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
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { StatewireDocuments } from "./wire/documents.js";
|
|
2
|
+
import { StatewireProtocol } from "./wire/protocol.js";
|
|
1
3
|
import { StatewireClient } from "./statewire.js";
|
|
2
4
|
import { StatewirePath } from "./wire/ops.js";
|
|
3
5
|
//#region src/types.d.ts
|
|
@@ -9,6 +11,7 @@ declare namespace Statewire {
|
|
|
9
11
|
}; }[keyof TCommands];
|
|
10
12
|
type CommandsProxy<TCommands extends Commands> = { [K in keyof TCommands]: (...args: Parameters<TCommands[K]>) => Promise<Awaited<ReturnType<TCommands[K]>>>; };
|
|
11
13
|
type CommandUpdate<TCommands extends Commands = Commands> = { [K in keyof TCommands]: {
|
|
14
|
+
readonly protocol?: string;
|
|
12
15
|
method: K;
|
|
13
16
|
params: Parameters<TCommands[K]>;
|
|
14
17
|
seq: number;
|
|
@@ -43,6 +46,10 @@ declare namespace Statewire {
|
|
|
43
46
|
}
|
|
44
47
|
interface Statewire<TState = any, TCommands extends Statewire.Commands = Statewire.Commands> {
|
|
45
48
|
state: TState;
|
|
49
|
+
documents?: StatewireDocuments.Source;
|
|
50
|
+
selection?: StatewireProtocol.Selection | undefined;
|
|
51
|
+
invoke?: (protocol: string, method: string, params: readonly unknown[]) => Promise<unknown>;
|
|
52
|
+
updateContext?: (ops: readonly unknown[]) => void;
|
|
46
53
|
commands: Statewire.CommandsProxy<TCommands>;
|
|
47
54
|
connection?: StatewireClient.Connection;
|
|
48
55
|
source?: Statewire.StateSource;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;kBAKiB;OACH,WAAW,mBAAmB;OAE9B,QAAQ,kBAAkB,WAAW,eAC9C,WAAW;IAAc,QAAQ;IAAG,QAAQ,WAAW,UAAU;aAC5D;OAEI,cAAc,kBAAkB,eACzC,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;OAGhC,cAAc,kBAAkB,WAAW,eACpD,WAAW;aACD;IACT,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACA;IACA;IACA,UAAU,gBAAgB;aAEtB;;OAGI;IACV;;IAEA,mBAAmB;;IAEnB,cAAc;;OAGJ;IACN;IAAe,MAAM;IAAe;;IACpC;IAAW,MAAM;IAAe;;IAChC;IAAc,MAAM;;OAEd;IACV,YAAY;IACZ,eAAe;;;UAIF,UACf,cACA,kBAAkB,UAAU,WAAW,UAAU;EAEjD,OAAO;EACP,YAAY,mBAAmB;EAC/B,YAAY,kBAAkB;EAC9B,UACE,kBACA,gBACA,+BACG;EACL,iBAAiB;EACjB,UAAU,UAAU,cAAc;EAClC,aAAa,gBAAgB;EAC7B,SAAS,UAAU;;EAEnB;;EAEA;EACA;aAAkB;;;EAElB,cAAc,OAAO;;EAErB;IACE,qBAAqB,gBAAgB;IACrC,QAAQ,OAAO,gBAAgB,kBAAkB;IACjD,UAAU,OAAO,gBAAgB;;;cAuBxB,2BAA2B;WAC7B,SAAS,gBAAgB;EAEtB,YAAA,SAAS,gBAAgB"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","commands","connection","Connection","source","durableSeq","isBusy","work","isIdle","setContext","failures","list","FailedCommand","retry","entry","discard","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Highest command seq the server's dur flag states durably admitted — never lost, never run again — and released from the resend queue. */\n durableSeq?: number;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n work?: { readonly isIdle: boolean };\n /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */\n setContext?: (value: Record<string, unknown>) => void;\n /** The retained failed-send lane; present on transports with a durable session. */\n failures?: {\n list: () => readonly StatewireClient.FailedCommand[];\n retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;\n discard: (entry: StatewireClient.FailedCommand) => void;\n };\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"types.js","names":["StatewireDocuments","StatewireProtocol","StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","protocol","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","documents","Source","selection","Selection","invoke","updateContext","ops","commands","connection","Connection","source","durableSeq","isBusy","work","isIdle","setContext","failures","list","FailedCommand","retry","entry","discard","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n readonly protocol?: string;\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n documents?: StatewireDocuments.Source;\n selection?: StatewireProtocol.Selection | undefined;\n invoke?: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\n updateContext?: (ops: readonly unknown[]) => void;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Highest command seq the server's dur flag states durably admitted — never lost, never run again — and released from the resend queue. */\n durableSeq?: number;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n work?: { readonly isIdle: boolean };\n /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */\n setContext?: (value: Record<string, unknown>) => void;\n /** The retained failed-send lane; present on transports with a durable session. */\n failures?: {\n list: () => readonly StatewireClient.FailedCommand[];\n retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;\n discard: (entry: StatewireClient.FailedCommand) => void;\n };\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";AAiFA,MAAM6D,YAAYtC,YAAiD;CACjE,QAAQA,QAAQuC,MAAhB;EACE,KAAK,YACH,OAAO,8BAA8BvC,QAAQwC,OAAO,KAAKxC,QAAQwC,KAAI,KAAM,KAAKxC,QAAQyC,UAAU,KAAKzC,QAAQyC,YAAY;EAC7H,KAAK,WACH,OAAO,+BAA+BzC,QAAQ0C,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4B1C,QAAQ0C,MAAK;EAClD,KAAK,UACH,OAAO1C,QAAQyC,UACX,8BAA8BzC,QAAQyC,YACtC;EACN,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO,sCAAsCzC,QAAQ0C,MAAK;CAC9D;AACF;AAEA,IAAaC,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAY7C,SAAsC;EAChD,MAAMsC,SAAStC,OAAO,CAAC;EACvB,KAAK8C,OAAO;EACZ,KAAK9C,UAAUA;CACjB;AACF"}
|
package/dist/wire/documents.d.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { StatewirePath } from "./ops.js";
|
|
2
2
|
//#region src/wire/documents.d.ts
|
|
3
3
|
declare namespace StatewireDocuments {
|
|
4
|
+
type CacheEntry = {
|
|
5
|
+
version: string;
|
|
6
|
+
value: unknown;
|
|
7
|
+
};
|
|
8
|
+
type Storage = {
|
|
9
|
+
namespace?: string;
|
|
10
|
+
load: (identity: readonly string[]) => CacheEntry | undefined;
|
|
11
|
+
save: (identity: readonly string[], entry: CacheEntry) => void;
|
|
12
|
+
subscribe?: (listener: () => void) => () => void;
|
|
13
|
+
};
|
|
14
|
+
type Source = {
|
|
15
|
+
get: (protocol: string, key?: readonly string[], prefix?: readonly string[]) => unknown;
|
|
16
|
+
list: () => ReadonlyMap<number, Document>;
|
|
17
|
+
subscribe: (listener: () => void) => () => void;
|
|
18
|
+
};
|
|
4
19
|
type Document = {
|
|
5
20
|
key: readonly string[] | null;
|
|
21
|
+
protocol?: string;
|
|
22
|
+
main?: boolean;
|
|
6
23
|
value: unknown;
|
|
7
24
|
};
|
|
8
25
|
type State = {
|
|
@@ -10,11 +27,17 @@ declare namespace StatewireDocuments {
|
|
|
10
27
|
documents: ReadonlyMap<number, Document>;
|
|
11
28
|
};
|
|
12
29
|
}
|
|
13
|
-
declare const applyDocumentOps: (previous: StatewireDocuments.State, ops: readonly unknown[]
|
|
30
|
+
declare const applyDocumentOps: (previous: StatewireDocuments.State, ops: readonly unknown[], protocols?: readonly {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
}[], strict?: boolean) => {
|
|
14
34
|
mode: "mounted" | "keyless";
|
|
15
35
|
documents: Map<number, StatewireDocuments.Document>;
|
|
16
36
|
changedPaths: StatewirePath[];
|
|
17
37
|
};
|
|
38
|
+
declare const mainDocument: (state: StatewireDocuments.State, protocol: string) => StatewireDocuments.Document | undefined;
|
|
39
|
+
declare const resolveDocumentKey: (key: readonly string[], prefix?: readonly string[]) => readonly string[] | undefined;
|
|
40
|
+
declare const findDocument: (state: StatewireDocuments.State, protocol: string, key?: readonly string[], prefix?: readonly string[]) => StatewireDocuments.Document | undefined;
|
|
18
41
|
//#endregion
|
|
19
|
-
export { StatewireDocuments, applyDocumentOps };
|
|
42
|
+
export { StatewireDocuments, applyDocumentOps, findDocument, mainDocument, resolveDocumentKey };
|
|
20
43
|
//# sourceMappingURL=documents.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","names":[],"sources":["../../src/wire/documents.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"documents.d.ts","names":[],"sources":["../../src/wire/documents.ts"],"mappings":";;kBAKiB;OACH;IAAe;IAAiB;;OAChC;IACV;IACA,OAAO,gCAAgC;IACvC,OAAO,6BAA6B,OAAO;IAC3C,aAAa;;OAEH;IACV,MACE,kBACA,yBACA;IAEF,YAAY,oBAAoB;IAChC,YAAY;;OAEF;IACV;IACA;IACA;IACA;;OAEU;IACV;IACA,WAAW,oBAAoB;;;cAQtB,mBACX,UAAU,mBAAmB,OAC7B,yBACA;EAAuB;EAAc;KACrC;;;;;cAqJW,eACX,OAAO,mBAAmB,OAC1B,qBAAgB,mBAAA;cAML,qBACX,wBACA;cAQW,eACX,OAAO,mBAAmB,OAC1B,kBACA,yBACA,+BAA0B,mBAAA"}
|
package/dist/wire/documents.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { reviveDocumentValue } from "../document.js";
|
|
2
|
+
import { protocolToken } from "./protocol.js";
|
|
1
3
|
import { applyOps } from "./ops.js";
|
|
2
|
-
import { reviveValue } from "./meta.js";
|
|
3
4
|
//#region src/wire/documents.ts
|
|
4
5
|
const invalid = (message) => {
|
|
5
6
|
throw new Error(`statewire: protocol error: ${message}`);
|
|
6
7
|
};
|
|
7
|
-
const applyDocumentOps = (previous, ops) => {
|
|
8
|
-
let mode = previous.mode;
|
|
8
|
+
const applyDocumentOps = (previous, ops, protocols, strict = false) => {
|
|
9
|
+
let mode = previous.mode ?? (protocols !== void 0 && ops.length === 0 ? "mounted" : null);
|
|
9
10
|
const documents = new Map(previous.documents);
|
|
10
11
|
const changedPaths = [];
|
|
11
12
|
for (const raw of ops) {
|
|
@@ -20,10 +21,12 @@ const applyDocumentOps = (previous, ops) => {
|
|
|
20
21
|
"replace",
|
|
21
22
|
"remove"
|
|
22
23
|
].includes(op.op)) {
|
|
24
|
+
if (strict) invalid("unknown context operation");
|
|
23
25
|
const { doc: _doc, ...unknown } = op;
|
|
24
26
|
applyOps(void 0, [unknown]);
|
|
25
27
|
continue;
|
|
26
28
|
}
|
|
29
|
+
if (protocols !== void 0 && mode === null && op.op !== "mount") invalid("negotiated state must start with mounts");
|
|
27
30
|
if (mode === null) {
|
|
28
31
|
if (op.op === "mount") mode = "mounted";
|
|
29
32
|
else if (op.op === "replace" && doc === 0 && Array.isArray(op.path) && op.path.length === 0 && "value" in op) {
|
|
@@ -37,22 +40,32 @@ const applyDocumentOps = (previous, ops) => {
|
|
|
37
40
|
if (mode === "keyless" && (doc !== 0 || op.op === "mount" || op.op === "unmount")) invalid("keyless mode only supports mutations of document 0");
|
|
38
41
|
if (op.op === "mount") {
|
|
39
42
|
if (documents.has(doc)) invalid("document is already mounted");
|
|
40
|
-
if (!Array.isArray(op.key) || !op.key.every((key) => typeof key === "string") || !("value" in op)) invalid("mount requires
|
|
43
|
+
if (op.key !== void 0 && (!Array.isArray(op.key) || !op.key.every((key) => typeof key === "string")) || !("value" in op)) invalid("mount requires value and an optional key array");
|
|
44
|
+
if (op.protocol !== void 0 && (typeof op.protocol !== "string" || !protocolToken.test(op.protocol) || op.protocol === "*")) invalid("invalid mount protocol");
|
|
45
|
+
if (protocols !== void 0 && !protocols.some((p) => p.name === op.protocol)) invalid("mount protocol was not negotiated");
|
|
46
|
+
if (op.main !== void 0 && typeof op.main !== "boolean") invalid("main must be boolean");
|
|
47
|
+
if (op.main === true && [...documents.values()].some((d) => d.protocol === op.protocol && d.main)) invalid("protocol already has a main document");
|
|
48
|
+
if (op.key !== void 0 && [...documents.values()].some((d) => d.protocol === op.protocol && JSON.stringify(d.key) === JSON.stringify(op.key))) invalid("protocol document key is already mounted");
|
|
41
49
|
documents.set(doc, {
|
|
42
|
-
key: op.key,
|
|
43
|
-
|
|
50
|
+
key: op.key === void 0 ? null : op.key,
|
|
51
|
+
...op.protocol === void 0 ? {} : { protocol: op.protocol },
|
|
52
|
+
...op.main === void 0 ? {} : { main: op.main },
|
|
53
|
+
value: op.meta === void 0 ? op.value : reviveDocumentValue(op.value, op.meta, { rejectUnknown: strict })
|
|
44
54
|
});
|
|
45
55
|
if (doc === 0) changedPaths.push([]);
|
|
46
56
|
} else if (op.op === "unmount") {
|
|
57
|
+
const document = documents.get(doc);
|
|
58
|
+
if (protocols !== void 0 && document !== void 0 && !protocols.some((p) => p.name === document.protocol)) invalid("document protocol was not negotiated");
|
|
47
59
|
if (!documents.delete(doc)) invalid("document is not mounted");
|
|
48
60
|
if (doc === 0) changedPaths.push([]);
|
|
49
61
|
} else if (op.op === "add" || op.op === "replace" || op.op === "remove") {
|
|
50
62
|
const document = documents.get(doc);
|
|
63
|
+
if (protocols !== void 0 && document !== void 0 && !protocols.some((p) => p.name === document.protocol)) invalid("document protocol was not negotiated");
|
|
51
64
|
if (!document) invalid("document is not mounted");
|
|
52
65
|
if (!Array.isArray(op.path) || !op.path.every((part) => typeof part === "string" || typeof part === "number" && Number.isSafeInteger(part) && part >= 0)) invalid("operation requires a valid path");
|
|
53
66
|
if (op.op !== "remove" && !("value" in op)) invalid("operation requires a value");
|
|
54
67
|
const { doc: _doc, meta, ...mutation } = op;
|
|
55
|
-
if (meta !== void 0) mutation.value =
|
|
68
|
+
if (meta !== void 0) mutation.value = reviveDocumentValue(mutation.value, meta, { rejectUnknown: strict });
|
|
56
69
|
const applied = applyOps(document.value, [mutation]);
|
|
57
70
|
documents.set(doc, {
|
|
58
71
|
...document,
|
|
@@ -68,7 +81,14 @@ const applyDocumentOps = (previous, ops) => {
|
|
|
68
81
|
changedPaths
|
|
69
82
|
};
|
|
70
83
|
};
|
|
84
|
+
const mainDocument = (state, protocol) => [...state.documents.values()].find((document) => document.protocol === protocol && document.main);
|
|
85
|
+
const resolveDocumentKey = (key, prefix) => key[0] === "." ? prefix === void 0 ? void 0 : [...prefix, ...key.slice(1)] : key;
|
|
86
|
+
const findDocument = (state, protocol, key, prefix) => {
|
|
87
|
+
if (key === void 0) return mainDocument(state, protocol);
|
|
88
|
+
const resolved = resolveDocumentKey(key, prefix);
|
|
89
|
+
return [...state.documents.values()].find((document) => document.protocol === protocol && document.key !== null && (JSON.stringify(document.key) === JSON.stringify(key) || resolved !== void 0 && JSON.stringify(resolveDocumentKey(document.key, prefix)) === JSON.stringify(resolved)));
|
|
90
|
+
};
|
|
71
91
|
//#endregion
|
|
72
|
-
export { applyDocumentOps };
|
|
92
|
+
export { applyDocumentOps, findDocument, mainDocument, resolveDocumentKey };
|
|
73
93
|
|
|
74
94
|
//# sourceMappingURL=documents.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.js","names":["applyOps","reviveValue","StatewirePath","StatewireDocuments","Document","key","value","State","mode","documents","ReadonlyMap","invalid","message","Error","applyDocumentOps","previous","ops","Map","changedPaths","raw","Array","isArray","op","Record","doc","undefined","Number","isSafeInteger","includes","_doc","unknown","path","length","set","has","every","meta","push","delete","document","get","part","mutation","applied","state"],"sources":["../../src/wire/documents.ts"],"sourcesContent":["import { applyOps } from \"./ops\";\nimport { reviveValue } from \"./meta\";\nimport type { StatewirePath } from \"./ops\";\n\nexport namespace StatewireDocuments {\n export type Document = { key: readonly string[] | null; value: unknown };\n export type State = {\n mode: \"mounted\" | \"keyless\" | null;\n documents: ReadonlyMap<number, Document>;\n };\n}\n\nconst invalid: (message: string) => never = (message) => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nexport const applyDocumentOps = (\n previous: StatewireDocuments.State,\n ops: readonly unknown[],\n) => {\n let mode = previous.mode;\n const documents = new Map(previous.documents);\n const changedPaths: StatewirePath[] = [];\n for (const raw of ops) {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n invalid(\"operation must be an object\");\n }\n const op = raw as Record<string, unknown>;\n const doc = op.doc === undefined ? 0 : op.doc;\n if (typeof doc !== \"number\" || !Number.isSafeInteger(doc) || doc < 0) {\n invalid(\"doc must be a nonnegative safe integer\");\n }\n if (\n ![\"mount\", \"unmount\", \"add\", \"replace\", \"remove\"].includes(\n op.op as string,\n )\n ) {\n const { doc: _doc, ...unknown } = op;\n applyOps(undefined, [unknown]);\n continue;\n }\n if (mode === null) {\n if (op.op === \"mount\") mode = \"mounted\";\n else if (\n op.op === \"replace\" &&\n doc === 0 &&\n Array.isArray(op.path) &&\n op.path.length === 0 &&\n \"value\" in op\n ) {\n mode = \"keyless\";\n documents.set(0, { key: null, value: undefined });\n } else\n invalid(\n \"first operation must mount a document or replace document 0 at the root\",\n );\n }\n if (\n mode === \"keyless\" &&\n (doc !== 0 || op.op === \"mount\" || op.op === \"unmount\")\n ) {\n invalid(\"keyless mode only supports mutations of document 0\");\n }\n if (op.op === \"mount\") {\n if (documents.has(doc)) invalid(\"document is already mounted\");\n if (\n !Array.isArray(op.key) ||\n !op.key.every((key) => typeof key === \"string\") ||\n !(\"value\" in op)\n ) {\n invalid(\"mount requires a key array and value\");\n }\n documents.set(doc, {\n key: op.key,\n value:\n op.meta === undefined ? op.value : reviveValue(op.value, op.meta),\n });\n if (doc === 0) changedPaths.push([]);\n } else if (op.op === \"unmount\") {\n if (!documents.delete(doc)) invalid(\"document is not mounted\");\n if (doc === 0) changedPaths.push([]);\n } else if (op.op === \"add\" || op.op === \"replace\" || op.op === \"remove\") {\n const document = documents.get(doc);\n if (!document) invalid(\"document is not mounted\");\n if (\n !Array.isArray(op.path) ||\n !op.path.every(\n (part) =>\n typeof part === \"string\" ||\n (typeof part === \"number\" &&\n Number.isSafeInteger(part) &&\n part >= 0),\n )\n ) {\n invalid(\"operation requires a valid path\");\n }\n if (op.op !== \"remove\" && !(\"value\" in op))\n invalid(\"operation requires a value\");\n const { doc: _doc, meta, ...mutation } = op;\n if (meta !== undefined)\n mutation.value = reviveValue(mutation.value, meta);\n const applied = applyOps(document.value, [mutation]);\n documents.set(doc, { ...document, value: applied.state });\n if (doc === 0) changedPaths.push(...applied.changedPaths);\n }\n }\n if (mode === null) invalid(\"hello missing snapshot\");\n return { mode, documents, changedPaths };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"documents.js","names":["applyOps","reviveValue","protocolToken","StatewirePath","StatewireDocuments","CacheEntry","version","value","Storage","namespace","load","identity","save","entry","subscribe","listener","Source","get","protocol","key","prefix","list","ReadonlyMap","Document","main","State","mode","documents","invalid","message","Error","applyDocumentOps","previous","ops","protocols","name","strict","undefined","length","Map","changedPaths","raw","Array","isArray","op","Record","doc","Number","isSafeInteger","includes","_doc","unknown","path","set","has","every","test","some","p","values","d","JSON","stringify","meta","rejectUnknown","push","document","delete","part","mutation","applied","state","mainDocument","find","resolveDocumentKey","slice","findDocument","resolved"],"sources":["../../src/wire/documents.ts"],"sourcesContent":["import { applyOps } from \"./ops\";\nimport { reviveValue } from \"./meta\";\nimport { protocolToken } from \"./protocol\";\nimport type { StatewirePath } from \"./ops\";\n\nexport namespace StatewireDocuments {\n export type CacheEntry = { version: string; value: unknown };\n export type Storage = {\n namespace?: string;\n load: (identity: readonly string[]) => CacheEntry | undefined;\n save: (identity: readonly string[], entry: CacheEntry) => void;\n subscribe?: (listener: () => void) => () => void;\n };\n export type Source = {\n get: (\n protocol: string,\n key?: readonly string[],\n prefix?: readonly string[],\n ) => unknown;\n list: () => ReadonlyMap<number, Document>;\n subscribe: (listener: () => void) => () => void;\n };\n export type Document = {\n key: readonly string[] | null;\n protocol?: string;\n main?: boolean;\n value: unknown;\n };\n export type State = {\n mode: \"mounted\" | \"keyless\" | null;\n documents: ReadonlyMap<number, Document>;\n };\n}\n\nconst invalid: (message: string) => never = (message) => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nexport const applyDocumentOps = (\n previous: StatewireDocuments.State,\n ops: readonly unknown[],\n protocols?: readonly { name: string; version: string }[],\n strict = false,\n) => {\n let mode =\n previous.mode ??\n (protocols !== undefined && ops.length === 0 ? \"mounted\" : null);\n const documents = new Map(previous.documents);\n const changedPaths: StatewirePath[] = [];\n for (const raw of ops) {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n invalid(\"operation must be an object\");\n }\n const op = raw as Record<string, unknown>;\n const doc = op.doc === undefined ? 0 : op.doc;\n if (typeof doc !== \"number\" || !Number.isSafeInteger(doc) || doc < 0) {\n invalid(\"doc must be a nonnegative safe integer\");\n }\n if (\n ![\"mount\", \"unmount\", \"add\", \"replace\", \"remove\"].includes(\n op.op as string,\n )\n ) {\n if (strict) invalid(\"unknown context operation\");\n const { doc: _doc, ...unknown } = op;\n applyOps(undefined, [unknown]);\n continue;\n }\n if (protocols !== undefined && mode === null && op.op !== \"mount\")\n invalid(\"negotiated state must start with mounts\");\n if (mode === null) {\n if (op.op === \"mount\") mode = \"mounted\";\n else if (\n op.op === \"replace\" &&\n doc === 0 &&\n Array.isArray(op.path) &&\n op.path.length === 0 &&\n \"value\" in op\n ) {\n mode = \"keyless\";\n documents.set(0, { key: null, value: undefined });\n } else\n invalid(\n \"first operation must mount a document or replace document 0 at the root\",\n );\n }\n if (\n mode === \"keyless\" &&\n (doc !== 0 || op.op === \"mount\" || op.op === \"unmount\")\n ) {\n invalid(\"keyless mode only supports mutations of document 0\");\n }\n if (op.op === \"mount\") {\n if (documents.has(doc)) invalid(\"document is already mounted\");\n if (\n (op.key !== undefined &&\n (!Array.isArray(op.key) ||\n !op.key.every((key) => typeof key === \"string\"))) ||\n !(\"value\" in op)\n ) {\n invalid(\"mount requires value and an optional key array\");\n }\n if (\n op.protocol !== undefined &&\n (typeof op.protocol !== \"string\" ||\n !protocolToken.test(op.protocol) ||\n op.protocol === \"*\")\n )\n invalid(\"invalid mount protocol\");\n if (\n protocols !== undefined &&\n !protocols.some((p) => p.name === op.protocol)\n )\n invalid(\"mount protocol was not negotiated\");\n if (op.main !== undefined && typeof op.main !== \"boolean\")\n invalid(\"main must be boolean\");\n if (\n op.main === true &&\n [...documents.values()].some(\n (d) => d.protocol === op.protocol && d.main,\n )\n )\n invalid(\"protocol already has a main document\");\n if (\n op.key !== undefined &&\n [...documents.values()].some(\n (d) =>\n d.protocol === op.protocol &&\n JSON.stringify(d.key) === JSON.stringify(op.key),\n )\n )\n invalid(\"protocol document key is already mounted\");\n documents.set(doc, {\n key: op.key === undefined ? null : (op.key as string[]),\n ...(op.protocol === undefined\n ? {}\n : { protocol: op.protocol as string }),\n ...(op.main === undefined ? {} : { main: op.main as boolean }),\n value:\n op.meta === undefined\n ? op.value\n : reviveValue(op.value, op.meta, { rejectUnknown: strict }),\n });\n if (doc === 0) changedPaths.push([]);\n } else if (op.op === \"unmount\") {\n const document = documents.get(doc);\n if (\n protocols !== undefined &&\n document !== undefined &&\n !protocols.some((p) => p.name === document.protocol)\n )\n invalid(\"document protocol was not negotiated\");\n if (!documents.delete(doc)) invalid(\"document is not mounted\");\n if (doc === 0) changedPaths.push([]);\n } else if (op.op === \"add\" || op.op === \"replace\" || op.op === \"remove\") {\n const document = documents.get(doc);\n if (\n protocols !== undefined &&\n document !== undefined &&\n !protocols.some((p) => p.name === document.protocol)\n )\n invalid(\"document protocol was not negotiated\");\n if (!document) invalid(\"document is not mounted\");\n if (\n !Array.isArray(op.path) ||\n !op.path.every(\n (part) =>\n typeof part === \"string\" ||\n (typeof part === \"number\" &&\n Number.isSafeInteger(part) &&\n part >= 0),\n )\n ) {\n invalid(\"operation requires a valid path\");\n }\n if (op.op !== \"remove\" && !(\"value\" in op))\n invalid(\"operation requires a value\");\n const { doc: _doc, meta, ...mutation } = op;\n if (meta !== undefined)\n mutation.value = reviveValue(mutation.value, meta, {\n rejectUnknown: strict,\n });\n const applied = applyOps(document.value, [mutation]);\n documents.set(doc, { ...document, value: applied.state });\n if (doc === 0) changedPaths.push(...applied.changedPaths);\n }\n }\n if (mode === null) invalid(\"hello missing snapshot\");\n return { mode, documents, changedPaths };\n};\n\nexport const mainDocument = (\n state: StatewireDocuments.State,\n protocol: string,\n) =>\n [...state.documents.values()].find(\n (document) => document.protocol === protocol && document.main,\n );\n\nexport const resolveDocumentKey = (\n key: readonly string[],\n prefix?: readonly string[],\n) =>\n key[0] === \".\"\n ? prefix === undefined\n ? undefined\n : [...prefix, ...key.slice(1)]\n : key;\n\nexport const findDocument = (\n state: StatewireDocuments.State,\n protocol: string,\n key?: readonly string[],\n prefix?: readonly string[],\n) => {\n if (key === undefined) return mainDocument(state, protocol);\n const resolved = resolveDocumentKey(key, prefix);\n return [...state.documents.values()].find(\n (document) =>\n document.protocol === protocol &&\n document.key !== null &&\n (JSON.stringify(document.key) === JSON.stringify(key) ||\n (resolved !== undefined &&\n JSON.stringify(resolveDocumentKey(document.key, prefix)) ===\n JSON.stringify(resolved))),\n );\n};\n"],"mappings":"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"}
|
package/dist/wire/meta.d.ts
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
|
+
import { StatewireDocument, reviveDocumentValue } from "../document.js";
|
|
1
2
|
//#region src/wire/meta.d.ts
|
|
2
|
-
type StatewireMetaEntry = {
|
|
3
|
-
path: (string | number)[];
|
|
4
|
-
type: string;
|
|
5
|
-
};
|
|
6
|
-
/** Revive meta-typed locations inside a wire value (in place). Clients skip
|
|
7
|
-
* unknown types; hosts pass `rejectUnknown` to fault on them instead. */
|
|
8
|
-
declare const reviveValue: (value: unknown, meta: unknown, { rejectUnknown }?: {
|
|
9
|
-
rejectUnknown?: boolean | undefined;
|
|
10
|
-
}) => unknown;
|
|
11
3
|
declare const reviveOps: (ops: unknown[]) => unknown[];
|
|
12
4
|
declare const reviveResponse: <T extends {
|
|
13
5
|
payload?: unknown;
|
|
14
6
|
meta?: unknown;
|
|
15
7
|
}>(response: T) => T;
|
|
16
|
-
/** Encode Date instances inside command params as canonical ISO strings,
|
|
17
|
-
* recording their paths (starting at the param index) in meta. */
|
|
18
8
|
declare const encodeParams: (params: readonly unknown[]) => {
|
|
19
9
|
params: unknown[];
|
|
20
|
-
meta?:
|
|
21
|
-
};
|
|
22
|
-
/** Encode Date instances inside a wire value, recording their paths in meta. */
|
|
23
|
-
declare const encodeValue: (value: unknown) => {
|
|
24
|
-
value: unknown;
|
|
25
|
-
meta?: StatewireMetaEntry[];
|
|
10
|
+
meta?: StatewireDocument.MetaEntry[];
|
|
26
11
|
};
|
|
27
12
|
//#endregion
|
|
28
|
-
export {
|
|
13
|
+
export { encodeParams, reviveOps, reviveResponse, reviveDocumentValue as reviveValue };
|
|
29
14
|
//# sourceMappingURL=meta.d.ts.map
|
package/dist/wire/meta.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.d.ts","names":[],"sources":["../../src/wire/meta.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"meta.d.ts","names":[],"sources":["../../src/wire/meta.ts"],"mappings":";;cAQa,YAAa;cAOb,iBAAkB;EAAY;EAAmB;GAC5D,UAAU,MACT;cAMU,eACX;EACG;EAAmB,OAAO,kBAAkB"}
|
package/dist/wire/meta.js
CHANGED
|
@@ -1,36 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encodeDocument, reviveDocumentValue } from "../document.js";
|
|
2
2
|
//#region src/wire/meta.ts
|
|
3
|
-
const fault = (message) => {
|
|
4
|
-
throw new Error(`statewire: protocol error: ${message}`);
|
|
5
|
-
};
|
|
6
|
-
const isPath = (path) => Array.isArray(path) && path.every((seg) => typeof seg === "string" || typeof seg === "number" && Number.isInteger(seg));
|
|
7
|
-
/** Revive meta-typed locations inside a wire value (in place). Clients skip
|
|
8
|
-
* unknown types; hosts pass `rejectUnknown` to fault on them instead. */
|
|
9
|
-
const reviveValue = (value, meta, { rejectUnknown = false } = {}) => {
|
|
10
|
-
if (!Array.isArray(meta)) return fault("meta must be an array");
|
|
11
|
-
for (const entry of meta) {
|
|
12
|
-
const path = typeof entry === "object" && entry !== null ? entry.path : null;
|
|
13
|
-
if (!isPath(path)) return fault("meta entry must carry a path array");
|
|
14
|
-
if (entry.type !== "date") {
|
|
15
|
-
if (rejectUnknown) return fault(`unknown meta type: ${String(entry.type)}`);
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
const walked = walkStatePath(value, path, "resolve");
|
|
19
|
-
if (walked === void 0) return fault(`meta path ${path.join(".")} does not resolve`);
|
|
20
|
-
if (typeof walked.leaf !== "string") return fault(`meta path ${path.join(".")} must name a string`);
|
|
21
|
-
const date = new Date(walked.leaf);
|
|
22
|
-
if (!/(?:T| )\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})$/i.test(walked.leaf) || Number.isNaN(date.getTime())) return fault(`invalid date at meta path ${path.join(".")}`);
|
|
23
|
-
if (walked.parent === void 0) return date;
|
|
24
|
-
walked.parent[walked.key] = date;
|
|
25
|
-
}
|
|
26
|
-
return value;
|
|
27
|
-
};
|
|
28
3
|
const reviveOps = (ops) => ops.map((op) => {
|
|
29
4
|
if (typeof op !== "object" || op === null || !("meta" in op)) return op;
|
|
30
5
|
const { meta, ...rest } = op;
|
|
31
6
|
return {
|
|
32
7
|
...rest,
|
|
33
|
-
value:
|
|
8
|
+
value: reviveDocumentValue(rest.value, meta)
|
|
34
9
|
};
|
|
35
10
|
});
|
|
36
11
|
const reviveResponse = (response) => {
|
|
@@ -38,41 +13,17 @@ const reviveResponse = (response) => {
|
|
|
38
13
|
const { meta, ...rest } = response;
|
|
39
14
|
return {
|
|
40
15
|
...rest,
|
|
41
|
-
payload:
|
|
16
|
+
payload: reviveDocumentValue(rest.payload, meta)
|
|
42
17
|
};
|
|
43
18
|
};
|
|
44
|
-
const walkDates = (node, path, meta) => {
|
|
45
|
-
if (node instanceof Date) {
|
|
46
|
-
meta.push({
|
|
47
|
-
path,
|
|
48
|
-
type: "date"
|
|
49
|
-
});
|
|
50
|
-
return node.toISOString();
|
|
51
|
-
}
|
|
52
|
-
if (Array.isArray(node)) return node.map((item, i) => walkDates(item, [...path, i], meta));
|
|
53
|
-
if (typeof node === "object" && node !== null) return Object.fromEntries(Object.keys(node).map((key) => [key, walkDates(node[key], [...path, key], meta)]));
|
|
54
|
-
return node;
|
|
55
|
-
};
|
|
56
|
-
/** Encode Date instances inside command params as canonical ISO strings,
|
|
57
|
-
* recording their paths (starting at the param index) in meta. */
|
|
58
19
|
const encodeParams = (params) => {
|
|
59
|
-
const meta =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
params: encoded,
|
|
63
|
-
meta
|
|
64
|
-
} : { params: encoded };
|
|
65
|
-
};
|
|
66
|
-
/** Encode Date instances inside a wire value, recording their paths in meta. */
|
|
67
|
-
const encodeValue = (value) => {
|
|
68
|
-
const meta = [];
|
|
69
|
-
const encoded = walkDates(value, [], meta);
|
|
70
|
-
return meta.length > 0 ? {
|
|
71
|
-
value: encoded,
|
|
20
|
+
const { value, meta } = encodeDocument(params);
|
|
21
|
+
return meta ? {
|
|
22
|
+
params: value,
|
|
72
23
|
meta
|
|
73
|
-
} : {
|
|
24
|
+
} : { params: value };
|
|
74
25
|
};
|
|
75
26
|
//#endregion
|
|
76
|
-
export { encodeParams,
|
|
27
|
+
export { encodeParams, reviveOps, reviveResponse, reviveDocumentValue as reviveValue };
|
|
77
28
|
|
|
78
29
|
//# sourceMappingURL=meta.js.map
|
package/dist/wire/meta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.js","names":["
|
|
1
|
+
{"version":3,"file":"meta.js","names":["encodeDocument","reviveDocumentValue","reviveValue","StatewireDocument","reviveOps","ops","map","op","meta","rest","value","reviveResponse","payload","response","T","undefined","encodeParams","params","MetaEntry"],"sources":["../../src/wire/meta.ts"],"sourcesContent":["import {\n encodeDocument,\n reviveDocumentValue as reviveValue,\n} from \"../document\";\nimport type { StatewireDocument } from \"../document\";\n\nexport { reviveDocumentValue as reviveValue } from \"../document\";\n\nexport const reviveOps = (ops: unknown[]): unknown[] =>\n ops.map((op) => {\n if (typeof op !== \"object\" || op === null || !(\"meta\" in op)) return op;\n const { meta, ...rest } = op as { meta: unknown; value?: unknown };\n return { ...rest, value: reviveValue(rest.value, meta) };\n });\n\nexport const reviveResponse = <T extends { payload?: unknown; meta?: unknown }>(\n response: T,\n): T => {\n if (response.meta === undefined) return response;\n const { meta, ...rest } = response;\n return { ...rest, payload: reviveValue(rest.payload, meta) } as T;\n};\n\nexport const encodeParams = (\n params: readonly unknown[],\n): { params: unknown[]; meta?: StatewireDocument.MetaEntry[] } => {\n const { value, meta } = encodeDocument(params);\n return meta\n ? { params: value as unknown[], meta }\n : { params: value as unknown[] };\n};\n"],"mappings":";;AAQA,MAAaI,aAAaC,QACxBA,IAAIC,KAAKC,OAAO;CACd,IAAI,OAAOA,OAAO,YAAYA,OAAO,QAAQ,EAAE,UAAUA,KAAK,OAAOA;CACrE,MAAM,EAAEC,MAAM,GAAGC,SAASF;CAC1B,OAAO;EAAE,GAAGE;EAAMC,OAAOR,oBAAYO,KAAKC,OAAOF,IAAI;CAAE;AACzD,CAAC;AAEH,MAAaG,kBACXE,aACM;CACN,IAAIA,SAASL,SAASO,KAAAA,GAAW,OAAOF;CACxC,MAAM,EAAEL,MAAM,GAAGC,SAASI;CAC1B,OAAO;EAAE,GAAGJ;EAAMG,SAASV,oBAAYO,KAAKG,SAASJ,IAAI;CAAE;AAC7D;AAEA,MAAaQ,gBACXC,WACgE;CAChE,MAAM,EAAEP,OAAOF,SAASR,eAAeiB,MAAM;CAC7C,OAAOT,OACH;EAAES,QAAQP;EAAoBF;CAAK,IACnC,EAAES,QAAQP,MAAmB;AACnC"}
|
package/dist/wire/ops.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { recordMutations } from "../util/recordMutations.js";
|
|
2
1
|
import { isUnsafeKey, walkStatePath } from "../util/statePath.js";
|
|
2
|
+
import { recordMutations } from "../util/recordMutations.js";
|
|
3
3
|
//#region src/wire/ops.ts
|
|
4
4
|
const warnStripped = (key, parent) => {
|
|
5
5
|
if (typeof process === "undefined" || process.env["NODE_ENV"] !== "production") console.warn(`statewire: stripped unsafe key "${key}"` + (parent === void 0 ? "" : ` under "${parent}"`));
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/wire/protocol.d.ts
|
|
2
|
+
declare namespace StatewireProtocol {
|
|
3
|
+
type Range = {
|
|
4
|
+
version: string;
|
|
5
|
+
minVersion?: string;
|
|
6
|
+
};
|
|
7
|
+
type Offer = Range & {
|
|
8
|
+
name: string;
|
|
9
|
+
optional?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type Selection = {
|
|
12
|
+
version: string;
|
|
13
|
+
protocols: {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
type Options = {
|
|
19
|
+
wireVersion?: Range;
|
|
20
|
+
applicationProtocols?: readonly Offer[] | "*";
|
|
21
|
+
protocol?: string;
|
|
22
|
+
keyPrefixes?: Readonly<Record<string, readonly string[]>>;
|
|
23
|
+
};
|
|
24
|
+
type Definition = Range & {
|
|
25
|
+
name: string;
|
|
26
|
+
requires?: Readonly<Record<string, Range>>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
declare const WIRE_VERSION = "2026-09-07";
|
|
30
|
+
declare const DEFAULT_PROTOCOL = "default";
|
|
31
|
+
declare const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
32
|
+
declare const VERSION_HEADER = "Statewire-Version";
|
|
33
|
+
declare const protocolToken: RegExp;
|
|
34
|
+
declare const validateVersion: (value: unknown) => string;
|
|
35
|
+
declare const validateRange: (range: StatewireProtocol.Range) => {
|
|
36
|
+
version: string;
|
|
37
|
+
minVersion: string;
|
|
38
|
+
};
|
|
39
|
+
declare const inRange: (value: string, range: StatewireProtocol.Range) => boolean;
|
|
40
|
+
declare const protocolOffers: (options: StatewireProtocol.Options) => readonly StatewireProtocol.Offer[] | "*";
|
|
41
|
+
declare const negotiationHeaders: (options: StatewireProtocol.Options) => Record<string, string>;
|
|
42
|
+
declare const parseNegotiation: (headers: Headers) => Required<Pick<StatewireProtocol.Options, "wireVersion" | "applicationProtocols">>;
|
|
43
|
+
declare const validateSelection: (value: unknown, options: StatewireProtocol.Options) => StatewireProtocol.Selection;
|
|
44
|
+
declare const negotiateProtocols: (headers: Headers, wire: StatewireProtocol.Range, definitions: readonly StatewireProtocol.Definition[]) => StatewireProtocol.Selection | {
|
|
45
|
+
fin: {
|
|
46
|
+
reason: "protocol-error";
|
|
47
|
+
code: string;
|
|
48
|
+
status: number;
|
|
49
|
+
message: string;
|
|
50
|
+
payload?: unknown;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { DEFAULT_PROTOCOL, PROTOCOL_HEADER, StatewireProtocol, VERSION_HEADER, WIRE_VERSION, inRange, negotiateProtocols, negotiationHeaders, parseNegotiation, protocolOffers, protocolToken, validateRange, validateSelection, validateVersion };
|
|
55
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +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;;;OAEjB;IACV,cAAc;IACd,gCAAgC;IAChC;IACA,cAAc,SAAS;;OAEb,aAAa;IACvB;IACA,WAAW,SAAS,eAAe;;;cAI1B;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;cAqCR,qBACX,SAAS,SACT,MAAM,kBAAkB,OACxB,sBAAsB,kBAAkB,iBAEtC,kBAAkB;EAEhB;IACE;IACA;IACA;IACA;IACA"}
|