statewire 0.0.1 → 0.0.3
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 +14 -13
- package/dist/client/changes.d.ts +2 -2
- package/dist/client/changes.d.ts.map +1 -1
- package/dist/client/changes.js +3 -10
- package/dist/client/changes.js.map +1 -1
- package/dist/client/connection.d.ts +18 -39
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +61 -54
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +7 -11
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +58 -55
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +12 -26
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +70 -43
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -1
- package/dist/client/sessionStore.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 +5 -1
- package/dist/client/timing.js.map +1 -1
- package/dist/host/adapter.d.ts +8 -0
- package/dist/host/adapter.d.ts.map +1 -0
- package/dist/host/adapter.js +23 -0
- package/dist/host/adapter.js.map +1 -0
- package/dist/host/draft.d.ts +21 -7
- package/dist/host/draft.d.ts.map +1 -1
- package/dist/host/draft.js +190 -103
- package/dist/host/draft.js.map +1 -1
- package/dist/host/host.d.ts +19 -35
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +86 -54
- package/dist/host/host.js.map +1 -1
- package/dist/host/socket.d.ts +14 -0
- package/dist/host/socket.d.ts.map +1 -0
- package/dist/host/socket.js +511 -0
- package/dist/host/socket.js.map +1 -0
- package/dist/host-internal.d.ts +27 -0
- package/dist/host-internal.d.ts.map +1 -0
- package/dist/host-internal.js +3 -0
- package/dist/host.d.ts +54 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +5 -28
- package/dist/index.js +2 -3
- package/dist/statewire.d.ts +97 -10
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +32 -36
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fake-sse.d.ts +12 -0
- package/dist/transports/fake-sse.d.ts.map +1 -0
- package/dist/transports/fake-sse.js +39 -0
- package/dist/transports/fake-sse.js.map +1 -0
- package/dist/transports/fixtures.d.ts +33 -0
- package/dist/transports/fixtures.d.ts.map +1 -0
- package/dist/transports/fixtures.js +50 -0
- package/dist/transports/fixtures.js.map +1 -0
- package/dist/transports/remote.d.ts +6 -13
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +7 -25
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +32 -20
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +57 -46
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +8 -6
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +19 -46
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +4 -13
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +59 -63
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +55 -100
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util/draftTraps.d.ts +6 -0
- package/dist/util/draftTraps.d.ts.map +1 -0
- package/dist/util/draftTraps.js +25 -0
- package/dist/util/draftTraps.js.map +1 -0
- package/dist/util/macrotask.d.ts +5 -0
- package/dist/util/macrotask.d.ts.map +1 -0
- package/dist/util/macrotask.js +25 -0
- package/dist/util/macrotask.js.map +1 -0
- package/dist/util/methodProxy.d.ts +5 -0
- package/dist/util/methodProxy.d.ts.map +1 -0
- package/dist/util/methodProxy.js +17 -0
- package/dist/util/methodProxy.js.map +1 -0
- package/dist/util/randomId.d.ts +5 -0
- package/dist/util/randomId.d.ts.map +1 -0
- package/dist/util/randomId.js +6 -0
- package/dist/util/randomId.js.map +1 -0
- package/dist/{client/optimistic.d.ts → util/recordMutations.d.ts} +2 -2
- package/dist/util/recordMutations.d.ts.map +1 -0
- package/dist/{client/optimistic.js → util/recordMutations.js} +5 -24
- package/dist/util/recordMutations.js.map +1 -0
- package/dist/util/statePath.d.ts +14 -0
- package/dist/util/statePath.d.ts.map +1 -0
- package/dist/util/statePath.js +31 -0
- package/dist/util/statePath.js.map +1 -0
- package/dist/wire/meta.d.ts +6 -1
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +30 -28
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.d.ts +5 -26
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +67 -80
- package/dist/wire/ops.js.map +1 -1
- package/package.json +15 -11
- package/src/client/changes.ts +5 -15
- package/src/client/connection.ts +87 -113
- package/src/client/core.ts +57 -91
- package/src/client/session.ts +129 -90
- package/src/client/sessionStore.ts +1 -1
- package/src/client/timing.ts +6 -0
- package/src/host/adapter.ts +33 -0
- package/src/host/draft.ts +293 -125
- package/src/host/host.ts +122 -110
- package/src/host/socket.ts +618 -0
- package/src/host-internal.ts +28 -0
- package/src/host.ts +72 -0
- package/src/index.ts +2 -62
- package/src/statewire.ts +168 -61
- package/src/transports/fake-sse.ts +51 -0
- package/src/transports/fixtures.ts +60 -0
- package/src/transports/remote.ts +14 -51
- package/src/transports/sse.ts +87 -89
- package/src/transports/testing.ts +33 -63
- package/src/transports/ws.ts +78 -100
- package/src/types.ts +52 -131
- package/src/util/draftTraps.ts +32 -0
- package/src/util/macrotask.ts +25 -0
- package/src/util/methodProxy.ts +16 -0
- package/src/util/randomId.ts +4 -0
- package/src/{client/optimistic.ts → util/recordMutations.ts} +0 -0
- package/src/util/statePath.ts +61 -0
- package/src/wire/meta.ts +42 -47
- package/src/wire/ops.ts +107 -106
- package/LICENSE +0 -21
- package/dist/client/optimistic.d.ts.map +0 -1
- package/dist/client/optimistic.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/server.d.ts +0 -15
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -3
- package/src/server.ts +0 -32
package/dist/host.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { StatewireReject } from "./host/engine.js";
|
|
2
|
+
import { append } from "./host/draft.js";
|
|
3
|
+
import { CommandExecution, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState } from "./host/host.js";
|
|
4
|
+
export { CommandExecution, StatewireReject, append, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,28 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { StatewireWS
|
|
5
|
-
|
|
6
|
-
//#region src/index.d.ts
|
|
7
|
-
declare namespace Statewire {
|
|
8
|
-
type Connection = StatewireConnection;
|
|
9
|
-
type ConnectionStatus = StatewireConnection["status"];
|
|
10
|
-
type Transport<TState = any, TCommands extends StatewireCommands = StatewireCommands, TEvent = any> = StatewireTransport<TState, TCommands, TEvent>;
|
|
11
|
-
type StateSource = StatewireStateSource;
|
|
12
|
-
type SSEOptions = StatewireSSEOptions;
|
|
13
|
-
type WSOptions = StatewireWSOptions;
|
|
14
|
-
type Options<TState, TCommands extends StatewireCommands = StatewireCommands, TEvent = any> = StatewireOptions<TState, TCommands, TEvent>;
|
|
15
|
-
type Helpers<TState, TCommands extends StatewireCommands = StatewireCommands> = StatewireHelpers<TState, TCommands>;
|
|
16
|
-
type Commands = StatewireCommands;
|
|
17
|
-
type Command<TCommands extends StatewireCommands = StatewireCommands> = StatewireCommand<TCommands>;
|
|
18
|
-
type CommandsProxy<TCommands extends StatewireCommands> = StatewireCommandsProxy<TCommands>;
|
|
19
|
-
type CommandUpdate<TCommands extends StatewireCommands = StatewireCommands> = StatewireCommandUpdate<TCommands>;
|
|
20
|
-
type Optimistic<TState, TCommands extends StatewireCommands = StatewireCommands> = StatewireOptimistic<TState, TCommands>;
|
|
21
|
-
type OptimisticContext = StatewireOptimisticContext;
|
|
22
|
-
type SendFailure = SendFailure;
|
|
23
|
-
type Changes = StatewireChanges;
|
|
24
|
-
type Op = StatewireOp;
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
export { Statewire, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, applyOps, useStatewire };
|
|
28
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
import { StatewireSSE } from "./transports/sse.js";
|
|
2
|
+
import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js";
|
|
3
|
+
import { Statewire, StatewireSendError } from "./types.js";
|
|
4
|
+
import { StatewireWS } from "./transports/ws.js";
|
|
5
|
+
export { type Statewire, StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { applyOps } from "./wire/ops.js";
|
|
2
1
|
import { StatewireSendError } from "./types.js";
|
|
3
|
-
import {
|
|
2
|
+
import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js";
|
|
4
3
|
import { StatewireSSE } from "./transports/sse.js";
|
|
5
4
|
import { StatewireWS } from "./transports/ws.js";
|
|
6
|
-
export {
|
|
5
|
+
export { StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
|
package/dist/statewire.d.ts
CHANGED
|
@@ -1,20 +1,107 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ConnectionState } from "./transports/sse.js";
|
|
2
|
+
import { StatewireSessionStoreOptions } from "./client/sessionStore.js";
|
|
3
|
+
import { Statewire } from "./types.js";
|
|
3
4
|
import { ResourceElement } from "@assistant-ui/tap";
|
|
4
5
|
//#region src/statewire.d.ts
|
|
5
|
-
declare const useStatewire: <TState, TCommands extends
|
|
6
|
-
declare const StatewireResource: <TState, TCommands extends
|
|
7
|
-
declare class
|
|
8
|
-
private readonly core;
|
|
6
|
+
declare const useStatewire: <TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.Options<TState, TCommands, TEvent>) => StatewireClient.Helpers<TState, TCommands>;
|
|
7
|
+
declare const StatewireResource: <TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.Options<TState, TCommands, TEvent>) => ResourceElement<StatewireClient.Helpers<TState, TCommands>>;
|
|
8
|
+
declare class StatewireClient<TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any> {
|
|
9
9
|
private readonly root;
|
|
10
10
|
private disposed;
|
|
11
|
-
constructor(options:
|
|
11
|
+
constructor(options: StatewireClient.Options<TState, TCommands, TEvent>);
|
|
12
12
|
get state(): TState;
|
|
13
|
-
get connection():
|
|
14
|
-
get commands():
|
|
13
|
+
get connection(): StatewireClient.Connection;
|
|
14
|
+
get commands(): Statewire.CommandsProxy<TCommands>;
|
|
15
|
+
setContext(value: Record<string, unknown>): Promise<unknown>;
|
|
15
16
|
subscribe(listener: () => void): () => void;
|
|
16
17
|
dispose(): void;
|
|
17
18
|
}
|
|
19
|
+
declare namespace StatewireClient {
|
|
20
|
+
type Options<TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any> = {
|
|
21
|
+
transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;
|
|
22
|
+
optimistic?: Optimistic<NonNullable<TState>, TCommands>;
|
|
23
|
+
onEvent?: (event: TEvent, ctx: {
|
|
24
|
+
state: NonNullable<TState>;
|
|
25
|
+
}) => void;
|
|
26
|
+
onStateChange?: (state: NonNullable<TState>, ctx: {
|
|
27
|
+
changes: Statewire.Changes;
|
|
28
|
+
}) => void;
|
|
29
|
+
onCommandChange?: (update: Statewire.CommandUpdate<TCommands>, ctx: {
|
|
30
|
+
state: NonNullable<TState>;
|
|
31
|
+
}) => void;
|
|
32
|
+
};
|
|
33
|
+
type Helpers<TState, TCommands extends Statewire.Commands = Statewire.Commands> = {
|
|
34
|
+
/** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */
|
|
35
|
+
state: TState;
|
|
36
|
+
/** Identity-stable across renders; reads the current canonical state without subscribing the caller. */
|
|
37
|
+
getState: () => TState;
|
|
38
|
+
connection: Connection;
|
|
39
|
+
commands: Statewire.CommandsProxy<TCommands>;
|
|
40
|
+
/** Replace this client's ambient context on the server; throws on transports without a client context channel. */
|
|
41
|
+
setContext: (value: Record<string, unknown>) => Promise<unknown>;
|
|
42
|
+
};
|
|
43
|
+
type Connection = ConnectionState & {
|
|
44
|
+
reconnect: () => void;
|
|
45
|
+
};
|
|
46
|
+
type ConnectionStatus = Connection["status"];
|
|
47
|
+
/**
|
|
48
|
+
* Mutate the draft in place; the client records the next state plus the exact
|
|
49
|
+
* changed paths. Re-run against the new base on every rebase.
|
|
50
|
+
*/
|
|
51
|
+
type Optimistic<TState, TCommands extends Statewire.Commands = Statewire.Commands> = (draft: TState, command: Statewire.Command<TCommands>, ctx: OptimisticContext) => void;
|
|
52
|
+
type OptimisticContext = {
|
|
53
|
+
ts: number;
|
|
54
|
+
seq: number;
|
|
55
|
+
clientId?: string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* fate: "aborted" = never sent, "rejected" = the handler refused,
|
|
59
|
+
* "unknown-command" = no such command, "invalid-params" = the args did not
|
|
60
|
+
* match the command's schema, "lost" = sent but never covered by an ack,
|
|
61
|
+
* "crashed" = acked but the processing died, "result-unavailable" = acked
|
|
62
|
+
* but the result is gone.
|
|
63
|
+
* cause is which channel settled it: "server" = the server's answer;
|
|
64
|
+
* the rest are channel deaths the client settled locally.
|
|
65
|
+
*/
|
|
66
|
+
type SendFailure = {
|
|
67
|
+
fate: "rejected";
|
|
68
|
+
cause: "server";
|
|
69
|
+
message: string;
|
|
70
|
+
payload?: unknown;
|
|
71
|
+
} | {
|
|
72
|
+
fate: "unknown-command";
|
|
73
|
+
cause: "server";
|
|
74
|
+
} | {
|
|
75
|
+
fate: "invalid-params";
|
|
76
|
+
cause: "server";
|
|
77
|
+
message: string;
|
|
78
|
+
} | {
|
|
79
|
+
fate: "crashed";
|
|
80
|
+
cause: "server";
|
|
81
|
+
} | {
|
|
82
|
+
fate: "result-unavailable";
|
|
83
|
+
cause: "server" | "unmount" | "stream-gone" | "session-lost" | "superseded";
|
|
84
|
+
} | {
|
|
85
|
+
fate: "aborted";
|
|
86
|
+
cause: "cascade" | "unmount" | "stream-gone" | "session-lost" | "superseded";
|
|
87
|
+
} | {
|
|
88
|
+
fate: "lost";
|
|
89
|
+
cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
|
|
90
|
+
};
|
|
91
|
+
type SSEOptions = StatewireSessionStoreOptions & {
|
|
92
|
+
url: string;
|
|
93
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
94
|
+
fetch?: typeof globalThis.fetch;
|
|
95
|
+
onChange?: (connection: Connection) => void;
|
|
96
|
+
};
|
|
97
|
+
type WSOptions = StatewireSessionStoreOptions & {
|
|
98
|
+
url: string;
|
|
99
|
+
protocols?: string | string[];
|
|
100
|
+
query?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
101
|
+
webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
|
|
102
|
+
onChange?: (connection: Connection) => void;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
18
105
|
//#endregion
|
|
19
|
-
export {
|
|
106
|
+
export { StatewireClient, StatewireResource, useStatewire };
|
|
20
107
|
//# sourceMappingURL=statewire.d.ts.map
|
package/dist/statewire.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;;cAaa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,QAAQ,QAAQ;cAqCtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;mBAEiB;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW;MAK5D,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;EAIxC,WAAW,OAAO,0BAA0B;EAI5C,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;IAEA,WAAW,gBAAgB,UAAU,QAAQ,WAAW;IACxD,aAAa,WAAW,YAAY,SAAS;IAC7C,WAAW,OAAO,QAAQ;MAAO,OAAO,YAAY;;IACpD,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;;OAIlB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC,aAAa,OAAO,4BAA4B;;OAGtC,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;;;;;OAYU;IACN;IAAkB;IAAiB;IAAiB;;IACpD;IAAyB;;IACzB;IAAwB;IAAiB;;IACzC;IAAiB;;IAEjB;IACA;;IAQA;IACA;;IAQA;IACA;;OAGM,aAAa;IACvB;IACA,UAAU,qBAAqB,cAAc,QAAQ;IACrD,eAAe,WAAW;IAC1B,YAAY,YAAY;;OAGd,YAAY;IACtB;IACA;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY"}
|
package/dist/statewire.js
CHANGED
|
@@ -1,65 +1,61 @@
|
|
|
1
|
-
import { LIVE_CONNECTION,
|
|
2
|
-
import {
|
|
1
|
+
import { LIVE_CONNECTION, useStatewireCore } from "./client/core.js";
|
|
2
|
+
import { useMemo, useRef, useState, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
|
|
3
3
|
import { createTapRoot, flushTapSync, resource, useResource } from "@assistant-ui/tap";
|
|
4
4
|
//#region src/statewire.ts
|
|
5
5
|
const useStatewire = (options) => {
|
|
6
6
|
const latest = useRef(options);
|
|
7
7
|
latest.current = options;
|
|
8
|
-
const [, setVersion] = useState(0);
|
|
9
|
-
const [core] = useState(() => createStatewireCore(() => latest.current));
|
|
10
8
|
const value = useResource(options.transport);
|
|
11
|
-
core.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
9
|
+
const core = useStatewireCore(() => latest.current, value);
|
|
10
|
+
const [subscribe] = useState(() => (listener) => core.subscribe(() => flushTapSync(listener)));
|
|
11
|
+
const state = useSyncExternalStore(subscribe, core.getState, core.getState);
|
|
12
|
+
const connection = value.connection ?? LIVE_CONNECTION;
|
|
13
|
+
return useMemo(() => ({
|
|
14
|
+
state,
|
|
15
|
+
getState: core.getState,
|
|
16
|
+
connection,
|
|
17
|
+
commands: core.commands,
|
|
18
|
+
setContext: (contextValue) => {
|
|
19
|
+
if (!value.setContext) throw new Error("statewire: this transport has no client context channel");
|
|
20
|
+
return value.setContext(contextValue);
|
|
21
|
+
}
|
|
22
|
+
}), [
|
|
23
|
+
state,
|
|
24
|
+
connection,
|
|
25
|
+
core,
|
|
26
|
+
value
|
|
27
|
+
]);
|
|
27
28
|
};
|
|
28
29
|
const StatewireResource = (options) => resource(useStatewire)(options);
|
|
29
|
-
var
|
|
30
|
-
core;
|
|
30
|
+
var StatewireClient = class {
|
|
31
31
|
root;
|
|
32
32
|
disposed = false;
|
|
33
33
|
constructor(options) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.root = createTapRoot(() => {
|
|
37
|
-
const value = useResource(options.transport);
|
|
38
|
-
core.attach(value);
|
|
39
|
-
return value;
|
|
40
|
-
});
|
|
41
|
-
flushTapSync(() => {});
|
|
34
|
+
const ClientRoot = () => useStatewire(options);
|
|
35
|
+
this.root = createTapRoot(ClientRoot);
|
|
42
36
|
}
|
|
43
37
|
get state() {
|
|
44
|
-
return this.
|
|
38
|
+
return this.root.getValue().getState();
|
|
45
39
|
}
|
|
46
40
|
get connection() {
|
|
47
|
-
return this.root.getValue().connection
|
|
41
|
+
return this.root.getValue().connection;
|
|
48
42
|
}
|
|
49
43
|
get commands() {
|
|
50
|
-
return this.
|
|
44
|
+
return this.root.getValue().commands;
|
|
45
|
+
}
|
|
46
|
+
setContext(value) {
|
|
47
|
+
return this.root.getValue().setContext(value);
|
|
51
48
|
}
|
|
52
49
|
subscribe(listener) {
|
|
53
|
-
return this.
|
|
50
|
+
return this.root.subscribe(listener);
|
|
54
51
|
}
|
|
55
52
|
dispose() {
|
|
56
53
|
if (this.disposed) return;
|
|
57
54
|
this.disposed = true;
|
|
58
55
|
this.root.unmount();
|
|
59
|
-
this.core.dispose();
|
|
60
56
|
}
|
|
61
57
|
};
|
|
62
58
|
//#endregion
|
|
63
|
-
export {
|
|
59
|
+
export { StatewireClient, StatewireResource, useStatewire };
|
|
64
60
|
|
|
65
61
|
//# sourceMappingURL=statewire.js.map
|
package/dist/statewire.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire.js","names":["
|
|
1
|
+
{"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useResource","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","ConnectionState","StatewireSessionStoreOptions","useStatewire","Commands","options","StatewireClient","Options","TState","TCommands","TEvent","Helpers","latest","current","value","transport","core","subscribe","listener","state","getState","connection","commands","CommandsProxy","setContext","contextValue","Record","Error","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Promise","dispose","unmount","optimistic","Optimistic","NonNullable","onEvent","event","ctx","onStateChange","changes","Changes","onCommandChange","update","CommandUpdate","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","cause","message","payload","SSEOptions","url","headers","HeadersInit","fetch","globalThis","onChange","WSOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useResource,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { ConnectionState } from \"./transports/sse\";\nimport type { StatewireSessionStoreOptions } from \"./client/sessionStore\";\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const value = useResource(options.transport) as Statewire<unknown>;\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(\n () => ({\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n return value.setContext(contextValue);\n },\n }),\n [state, connection, core, value],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands, TEvent>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands, TEvent>) {\n const ClientRoot = () => useStatewire<TState, TCommands, TEvent>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n setContext(value: Record<string, unknown>): Promise<unknown> {\n return this.root.getValue().setContext(value);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n onEvent?: (event: TEvent, ctx: { state: NonNullable<TState> }) => void;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => Promise<unknown>;\n };\n\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the handler refused,\n * \"unknown-command\" = no such command, \"invalid-params\" = the args did not\n * match the command's schema, \"lost\" = sent but never covered by an ack,\n * \"crashed\" = acked but the processing died, \"result-unavailable\" = acked\n * but the result is gone.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | { fate: \"rejected\"; cause: \"server\"; message: string; payload?: unknown }\n | { fate: \"unknown-command\"; cause: \"server\" }\n | { fate: \"invalid-params\"; cause: \"server\"; message: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | {\n fate: \"result-unavailable\";\n cause:\n | \"server\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"aborted\";\n cause:\n | \"cascade\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n };\n\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\n url: string;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onChange?: (connection: Connection) => void;\n };\n}\n"],"mappings":";;;;AAaA,MAAac,gBAKXE,YAC+C;CAC/C,MAAMO,SAAStB,OAAOe,OAAO;CAC7BO,OAAOC,UAAUR;CAEjB,MAAMS,QAAQlB,YAAYS,QAAQU,SAAS;CAC3C,MAAMC,OAAOjB,uBAAuBa,OAAOC,SAASC,KAAK;CACzD,MAAM,CAACG,aAAa1B,gBACX2B,aACLF,KAAKC,gBAAgBvB,aAAawB,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQ3B,qBACZyB,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,aAAaP,MAAMO,cAAcvB;CAEvC,OAAOT,eACE;EACL8B;EACAC,UAAUJ,KAAKI;EACfC;EACAC,UAAUN,KAAKM;EACfE,aAAaC,iBAA0C;GACrD,IAAI,CAACX,MAAMU,YACT,MAAM,IAAIG,MACR,yDACF;GAEF,OAAOb,MAAMU,WAAWC,YAAY;EACtC;CACF,IACA;EAACN;EAAOE;EAAYL;EAAMF;CAAK,CACjC;AACF;AAEA,MAAac,qBAKXvB,YAEAV,SAASQ,YAAuC,CAAC,CAACE,OAAO;AAE3D,IAAaC,kBAAb,MAIE;CACA;CACA,WAAmB;CAEnByB,YAAY1B,SAA6D;EACvE,MAAM2B,mBAAmB7B,aAAwCE,OAAO;EACxE,KAAKwB,OAAOpC,cAAcuC,UAAU;CACtC;CAEA,IAAIb,QAAgB;EAClB,OAAO,KAAKU,KAAKI,SAAS,CAAC,CAACb,SAAS;CACvC;CAEA,IAAIC,aAAyC;EAC3C,OAAO,KAAKQ,KAAKI,SAAS,CAAC,CAACZ;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKO,KAAKI,SAAS,CAAC,CAACX;CAC9B;CAEAE,WAAWV,OAAkD;EAC3D,OAAO,KAAKe,KAAKI,SAAS,CAAC,CAACT,WAAWV,KAAK;CAC9C;CAEAG,UAAUC,UAAkC;EAC1C,OAAO,KAAKW,KAAKZ,UAAUC,QAAQ;CACrC;CAEAkB,UAAgB;EACd,IAAI,KAAKN,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKQ,QAAQ;CACpB;AACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StatewireEnvelope } from "../client/connection.js";
|
|
2
|
+
//#region src/transports/fake-sse.d.ts
|
|
3
|
+
type FakeStreamHandle = {
|
|
4
|
+
clientId: string;
|
|
5
|
+
send: (text: string) => void;
|
|
6
|
+
close: () => void;
|
|
7
|
+
};
|
|
8
|
+
declare const sseFrame: (envelope: StatewireEnvelope) => string;
|
|
9
|
+
declare const openSSEStream: (streams: Set<FakeStreamHandle>, clientId: string, onOpen: (handle: FakeStreamHandle) => void, signal?: AbortSignal) => Response;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { FakeStreamHandle, openSSEStream, sseFrame };
|
|
12
|
+
//# sourceMappingURL=fake-sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-sse.d.ts","names":[],"sources":["../../src/transports/fake-sse.ts"],"mappings":";;KAEY;EACV;EACA,OAAO;EACP;;cAGW,WAAY,UAAU;cAGtB,gBACX,SAAS,IAAI,mBACb,kBACA,SAAS,QAAQ,2BACjB,SAAS,gBACR"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/transports/fake-sse.ts
|
|
2
|
+
const sseFrame = (envelope) => `data: ${JSON.stringify(envelope)}\n\n`;
|
|
3
|
+
const openSSEStream = (streams, clientId, onOpen, signal) => {
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
let handle;
|
|
6
|
+
const body = new ReadableStream({
|
|
7
|
+
start: (controller) => {
|
|
8
|
+
let open = true;
|
|
9
|
+
handle = {
|
|
10
|
+
clientId,
|
|
11
|
+
send: (text) => {
|
|
12
|
+
if (open) controller.enqueue(encoder.encode(text));
|
|
13
|
+
},
|
|
14
|
+
close: () => {
|
|
15
|
+
if (!open) return;
|
|
16
|
+
open = false;
|
|
17
|
+
streams.delete(handle);
|
|
18
|
+
controller.close();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
streams.add(handle);
|
|
22
|
+
onOpen(handle);
|
|
23
|
+
},
|
|
24
|
+
cancel: () => {
|
|
25
|
+
streams.delete(handle);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
signal?.addEventListener("abort", () => {
|
|
29
|
+
for (const stream of [...streams]) if (stream.clientId === clientId) stream.close();
|
|
30
|
+
});
|
|
31
|
+
return new Response(body, {
|
|
32
|
+
status: 200,
|
|
33
|
+
headers: { "content-type": "text/event-stream" }
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { openSSEStream, sseFrame };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=fake-sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-sse.js","names":["StatewireEnvelope","FakeStreamHandle","clientId","send","text","close","sseFrame","envelope","JSON","stringify","openSSEStream","streams","Set","onOpen","handle","signal","AbortSignal","Response","encoder","TextEncoder","body","ReadableStream","Uint8Array","start","controller","open","enqueue","encode","delete","add","cancel","addEventListener","stream","status","headers"],"sources":["../../src/transports/fake-sse.ts"],"sourcesContent":["import type { StatewireEnvelope } from \"../client/connection\";\n\nexport type FakeStreamHandle = {\n clientId: string;\n send: (text: string) => void;\n close: () => void;\n};\n\nexport const sseFrame = (envelope: StatewireEnvelope) =>\n `data: ${JSON.stringify(envelope)}\\n\\n`;\n\nexport const openSSEStream = (\n streams: Set<FakeStreamHandle>,\n clientId: string,\n onOpen: (handle: FakeStreamHandle) => void,\n signal?: AbortSignal,\n): Response => {\n const encoder = new TextEncoder();\n let handle: FakeStreamHandle;\n const body = new ReadableStream<Uint8Array>({\n start: (controller) => {\n let open = true;\n handle = {\n clientId,\n send: (text) => {\n if (open) controller.enqueue(encoder.encode(text));\n },\n close: () => {\n if (!open) return;\n open = false;\n streams.delete(handle);\n controller.close();\n },\n };\n streams.add(handle);\n onOpen(handle);\n },\n cancel: () => {\n streams.delete(handle);\n },\n });\n signal?.addEventListener(\"abort\", () => {\n for (const stream of [...streams]) {\n if (stream.clientId === clientId) stream.close();\n }\n });\n return new Response(body, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n });\n};\n"],"mappings":";AAQA,MAAaM,YAAYC,aACvB,SAASC,KAAKC,UAAUF,QAAQ,EAAC;AAEnC,MAAaG,iBACXC,SACAT,UACAW,QACAE,WACa;CACb,MAAMG,UAAU,IAAIC,YAAY;CAChC,IAAIL;CACJ,MAAMM,OAAO,IAAIC,eAA2B;EAC1CE,QAAQC,eAAe;GACrB,IAAIC,OAAO;GACXX,SAAS;IACPZ;IACAC,OAAOC,SAAS;KACd,IAAIqB,MAAMD,WAAWE,QAAQR,QAAQS,OAAOvB,IAAI,CAAC;IACnD;IACAC,aAAa;KACX,IAAI,CAACoB,MAAM;KACXA,OAAO;KACPd,QAAQiB,OAAOd,MAAM;KACrBU,WAAWnB,MAAM;IACnB;GACF;GACAM,QAAQkB,IAAIf,MAAM;GAClBD,OAAOC,MAAM;EACf;EACAgB,cAAc;GACZnB,QAAQiB,OAAOd,MAAM;EACvB;CACF,CAAC;CACDC,QAAQgB,iBAAiB,eAAe;EACtC,KAAK,MAAMC,UAAU,CAAC,GAAGrB,OAAO,GAC9B,IAAIqB,OAAO9B,aAAaA,UAAU8B,OAAO3B,MAAM;CAEnD,CAAC;CACD,OAAO,IAAIY,SAASG,MAAM;EACxBa,QAAQ;EACRC,SAAS,EAAE,gBAAgB,oBAAoB;CACjD,CAAC;AACH"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { StatewireHost } from "../host.js";
|
|
2
|
+
import { Statewire } from "../types.js";
|
|
3
|
+
import "../index.js";
|
|
4
|
+
//#region src/transports/fixtures.d.ts
|
|
5
|
+
/** Let several rounds of queued microtasks and op batches drain. */
|
|
6
|
+
declare const settle: () => Promise<void>;
|
|
7
|
+
/** A minimal statewire host element: `{ count }` state with an `increment` command. */
|
|
8
|
+
declare const createCounterElement: () => import("@assistant-ui/tap").ResourceElement<{
|
|
9
|
+
state: {
|
|
10
|
+
count: number;
|
|
11
|
+
};
|
|
12
|
+
commands: Statewire.CommandsProxy<StatewireHost.HandledCommands<{
|
|
13
|
+
increment: () => void;
|
|
14
|
+
}>>;
|
|
15
|
+
emit: (event: unknown) => void;
|
|
16
|
+
}>;
|
|
17
|
+
/** Serialize a command frame as a client would send it over a socket. */
|
|
18
|
+
declare const commandFrame: (method: string, seq: number, params?: unknown[]) => string;
|
|
19
|
+
type SocketRecorder = {
|
|
20
|
+
sent: string[];
|
|
21
|
+
closes: {
|
|
22
|
+
code?: number;
|
|
23
|
+
reason?: string;
|
|
24
|
+
}[];
|
|
25
|
+
last: () => any;
|
|
26
|
+
send: (data: string) => void;
|
|
27
|
+
close: (code?: number, reason?: string) => void;
|
|
28
|
+
};
|
|
29
|
+
/** Record sent frames and close calls; `last()` parses the latest frame. */
|
|
30
|
+
declare const createSocketRecorder: () => SocketRecorder;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { SocketRecorder, commandFrame, createCounterElement, createSocketRecorder, settle };
|
|
33
|
+
//# sourceMappingURL=fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","names":[],"sources":["../../src/transports/fixtures.ts"],"mappings":";;;;;cASa,cAAM;;cAKN,wDAAoB;;;;YAApB,UAAA,cAAA,cAAA;;;;;;cAaA,eACX,gBACA,aACA;KAGU;EACV;EACA;IAAU;IAAe;;EACzB;EACA,OAAO;EACP,QAAQ,eAAe;;;cAIZ,4BAA2B"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useStatewireCommands, useStatewireEmit, useStatewireState } from "../host/host.js";
|
|
2
|
+
import "../host.js";
|
|
3
|
+
import { tick } from "./testing.js";
|
|
4
|
+
import { resource } from "@assistant-ui/tap";
|
|
5
|
+
//#region src/transports/fixtures.ts
|
|
6
|
+
/** Let several rounds of queued microtasks and op batches drain. */
|
|
7
|
+
const settle = async () => {
|
|
8
|
+
for (let i = 0; i < 3; i++) await tick();
|
|
9
|
+
};
|
|
10
|
+
/** A minimal statewire host element: `{ count }` state with an `increment` command. */
|
|
11
|
+
const createCounterElement = () => resource(() => {
|
|
12
|
+
const [state] = useStatewireState(() => ({ count: 0 }));
|
|
13
|
+
const emit = useStatewireEmit();
|
|
14
|
+
return {
|
|
15
|
+
state,
|
|
16
|
+
commands: useStatewireCommands({ increment: () => {
|
|
17
|
+
state.count += 1;
|
|
18
|
+
} }),
|
|
19
|
+
emit
|
|
20
|
+
};
|
|
21
|
+
})();
|
|
22
|
+
/** Serialize a command frame as a client would send it over a socket. */
|
|
23
|
+
const commandFrame = (method, seq, params = []) => JSON.stringify({
|
|
24
|
+
method,
|
|
25
|
+
params,
|
|
26
|
+
seq
|
|
27
|
+
});
|
|
28
|
+
/** Record sent frames and close calls; `last()` parses the latest frame. */
|
|
29
|
+
const createSocketRecorder = () => {
|
|
30
|
+
const sent = [];
|
|
31
|
+
const closes = [];
|
|
32
|
+
return {
|
|
33
|
+
sent,
|
|
34
|
+
closes,
|
|
35
|
+
last: () => JSON.parse(sent.at(-1)),
|
|
36
|
+
send: (data) => {
|
|
37
|
+
sent.push(data);
|
|
38
|
+
},
|
|
39
|
+
close: (code, reason) => {
|
|
40
|
+
closes.push({
|
|
41
|
+
...code !== void 0 && { code },
|
|
42
|
+
...reason !== void 0 && { reason }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { commandFrame, createCounterElement, createSocketRecorder, settle };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=fixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.js","names":["resource","useStatewireCommands","useStatewireEmit","useStatewireState","tick","settle","i","createCounterElement","state","count","emit","commands","increment","commandFrame","method","seq","params","JSON","stringify","SocketRecorder","sent","closes","code","reason","last","send","data","close","createSocketRecorder","parse","at","push","undefined"],"sources":["../../src/transports/fixtures.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport {\n useStatewireCommands,\n useStatewireEmit,\n useStatewireState,\n} from \"../host\";\nimport { tick } from \"./testing\";\n\n/** Let several rounds of queued microtasks and op batches drain. */\nexport const settle = async () => {\n for (let i = 0; i < 3; i++) await tick();\n};\n\n/** A minimal statewire host element: `{ count }` state with an `increment` command. */\nexport const createCounterElement = () =>\n resource(() => {\n const [state] = useStatewireState(() => ({ count: 0 }));\n const emit = useStatewireEmit();\n const commands = useStatewireCommands({\n increment: () => {\n state.count += 1;\n },\n });\n return { state, commands, emit };\n })();\n\n/** Serialize a command frame as a client would send it over a socket. */\nexport const commandFrame = (\n method: string,\n seq: number,\n params: unknown[] = [],\n) => JSON.stringify({ method, params, seq });\n\nexport type SocketRecorder = {\n sent: string[];\n closes: { code?: number; reason?: string }[];\n last: () => any;\n send: (data: string) => void;\n close: (code?: number, reason?: string) => void;\n};\n\n/** Record sent frames and close calls; `last()` parses the latest frame. */\nexport const createSocketRecorder = (): SocketRecorder => {\n const sent: string[] = [];\n const closes: { code?: number; reason?: string }[] = [];\n return {\n sent,\n closes,\n last: () => JSON.parse(sent.at(-1)!),\n send: (data) => {\n sent.push(data);\n },\n close: (code, reason) => {\n closes.push({\n ...(code !== undefined && { code }),\n ...(reason !== undefined && { reason }),\n });\n },\n };\n};\n"],"mappings":";;;;;;AASA,MAAaK,SAAS,YAAY;CAChC,KAAK,IAAIC,IAAI,GAAGA,IAAI,GAAGA,KAAK,MAAMF,KAAK;AACzC;;AAGA,MAAaG,6BACXP,eAAe;CACb,MAAM,CAACQ,SAASL,yBAAyB,EAAEM,OAAO,EAAE,EAAE;CACtD,MAAMC,OAAOR,iBAAiB;CAM9B,OAAO;EAAEM;EAAOG,UALCV,qBAAqB,EACpCW,iBAAiB;GACfJ,MAAMC,SAAS;EACjB,EACF,CACgBE;EAAUD;CAAK;AACjC,CAAC,CAAC,CAAC;;AAGL,MAAaG,gBACXC,QACAC,KACAC,SAAoB,CAAA,MACjBC,KAAKC,UAAU;CAAEJ;CAAQE;CAAQD;AAAI,CAAC;;AAW3C,MAAaa,6BAA6C;CACxD,MAAMR,OAAiB,CAAA;CACvB,MAAMC,SAA+C,CAAA;CACrD,OAAO;EACLD;EACAC;EACAG,YAAYP,KAAKY,MAAMT,KAAKU,GAAG,EAAE,CAAE;EACnCL,OAAOC,SAAS;GACdN,KAAKW,KAAKL,IAAI;EAChB;EACAC,QAAQL,MAAMC,WAAW;GACvBF,OAAOU,KAAK;IACV,GAAIT,SAASU,KAAAA,KAAa,EAAEV,KAAK;IACjC,GAAIC,WAAWS,KAAAA,KAAa,EAAET,OAAO;GACvC,CAAC;EACH;CACF;AACF"}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { RemoteLink, StatewireRemoteProps } from "./sse.js";
|
|
2
|
+
import { StatewireSessionStoreOptions } from "../client/sessionStore.js";
|
|
3
|
+
import { StatewireClient } from "../statewire.js";
|
|
4
|
+
import { Statewire } from "../types.js";
|
|
5
5
|
//#region src/transports/remote.d.ts
|
|
6
6
|
declare const useStatewireRemote: <TOptions extends {
|
|
7
7
|
url: string;
|
|
8
|
-
onChange?: (connection:
|
|
9
|
-
} &
|
|
10
|
-
readonly current: {
|
|
11
|
-
options: TOptions;
|
|
12
|
-
ctx: StatewireTransportProps;
|
|
13
|
-
};
|
|
14
|
-
}, notify: (connection: StatewireConnection) => void) => ConnBase & {
|
|
15
|
-
send: RemoteLink["send"];
|
|
16
|
-
}) => StatewireTransport<any, any, any>;
|
|
8
|
+
onChange?: (connection: StatewireClient.Connection) => void;
|
|
9
|
+
} & StatewireSessionStoreOptions>(options: TOptions, useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink) => Statewire<any, any, any>;
|
|
17
10
|
//#endregion
|
|
18
11
|
export { useStatewireRemote };
|
|
19
12
|
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAYa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;IACtC,8BAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
|
|
@@ -1,37 +1,19 @@
|
|
|
1
1
|
import { useStatewireEmit } from "../host/host.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
2
|
+
import { setContext, setTransport, useStatewireSession } from "../client/session.js";
|
|
3
|
+
import { useEffect } from "@assistant-ui/tap/react-shim";
|
|
5
4
|
//#region src/transports/remote.ts
|
|
6
|
-
const useStatewireRemote = (options,
|
|
5
|
+
const useStatewireRemote = (options, useLink) => {
|
|
7
6
|
const emit = useStatewireEmit();
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
s.emitEvent = emit;
|
|
11
|
-
return () => {
|
|
12
|
-
s.emitEvent = () => {};
|
|
13
|
-
};
|
|
14
|
-
}, [s, emit]);
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
start(s);
|
|
17
|
-
return () => stop(s);
|
|
18
|
-
}, [s]);
|
|
19
|
-
const [ctx] = useState(() => ({
|
|
20
|
-
get clientId() {
|
|
21
|
-
return s.record.clientId;
|
|
22
|
-
},
|
|
23
|
-
onSnapshot: (args) => handleSnapshot(s, args),
|
|
24
|
-
onOps: (args_0) => handleOps(s, args_0),
|
|
25
|
-
onFinish: (args_1) => handleFinish(s, args_1)
|
|
26
|
-
}));
|
|
27
|
-
const conn = useStatewireConn(options, ctx, create);
|
|
7
|
+
const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);
|
|
8
|
+
const conn = useLink(options, s.ctx);
|
|
28
9
|
useEffect(() => setTransport(s, conn), [s, conn]);
|
|
29
10
|
return {
|
|
30
11
|
state: s.canonical,
|
|
31
12
|
commands: s.commands,
|
|
32
13
|
emit,
|
|
33
14
|
connection: conn.connection,
|
|
34
|
-
source: s.source
|
|
15
|
+
source: s.source,
|
|
16
|
+
setContext: (value) => setContext(s, value)
|
|
35
17
|
};
|
|
36
18
|
};
|
|
37
19
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.js","names":["useEffect","
|
|
1
|
+
{"version":3,"file":"remote.js","names":["useEffect","useStatewireEmit","Statewire","setContext","setTransport","useStatewireSession","StatewireSessionStoreOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","options","TOptions","useLink","ctx","emit","s","sessionStore","conn","state","canonical","commands","source","value"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useStatewireEmit } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport {\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionStoreOptions } from \"../client/sessionStore\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n } & StatewireSessionStoreOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any, any> => {\n const emit = useStatewireEmit<any>();\n const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n emit,\n connection: conn.connection,\n source: s.source,\n setContext: (value) => setContext(s, value),\n };\n};\n"],"mappings":";;;;AAYA,MAAaU,sBAMXK,SACAE,YAC6B;CAC7B,MAAME,OAAOlB,iBAAsB;CACnC,MAAMmB,IAAIf,oBAAoB,EAAEgB,cAAcN,QAAQM,aAAa,GAAGF,IAAI;CAC1E,MAAMG,OAAOL,QAAQF,SAASK,EAAEF,GAAG;CACnClB,gBAAgBI,aAAagB,GAAGE,IAAI,GAAG,CAACF,GAAGE,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOH,EAAEI;EACTC,UAAUL,EAAEK;EACZN;EACAN,YAAYS,KAAKT;EACjBa,QAAQN,EAAEM;EACVvB,aAAawB,UAAUxB,WAAWiB,GAAGO,KAAK;CAC5C;AACF"}
|