statewire 0.0.0-0 → 0.0.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/LICENSE +21 -0
- package/README.md +83 -1
- package/dist/client/changes.d.ts +7 -0
- package/dist/client/changes.d.ts.map +1 -0
- package/dist/client/changes.js +32 -0
- package/dist/client/changes.js.map +1 -0
- package/dist/client/connection.d.ts +84 -0
- package/dist/client/connection.d.ts.map +1 -0
- package/dist/client/connection.js +204 -0
- package/dist/client/connection.js.map +1 -0
- package/dist/client/core.d.ts +18 -0
- package/dist/client/core.d.ts.map +1 -0
- package/dist/client/core.js +269 -0
- package/dist/client/core.js.map +1 -0
- package/dist/client/optimistic.d.ts +10 -0
- package/dist/client/optimistic.d.ts.map +1 -0
- package/dist/client/optimistic.js +128 -0
- package/dist/client/optimistic.js.map +1 -0
- package/dist/client/session.d.ts +88 -0
- package/dist/client/session.d.ts.map +1 -0
- package/dist/client/session.js +509 -0
- package/dist/client/session.js.map +1 -0
- package/dist/client/sessionStore.d.ts +16 -0
- package/dist/client/sessionStore.d.ts.map +1 -0
- package/dist/client/sessionStore.js +14 -0
- package/dist/client/sessionStore.js.map +1 -0
- package/dist/client/timing.d.ts +10 -0
- package/dist/client/timing.d.ts.map +1 -0
- package/dist/client/timing.js +11 -0
- package/dist/client/timing.js.map +1 -0
- package/dist/host/draft.d.ts +14 -0
- package/dist/host/draft.d.ts.map +1 -0
- package/dist/host/draft.js +124 -0
- package/dist/host/draft.js.map +1 -0
- package/dist/host/engine.d.ts +14 -0
- package/dist/host/engine.d.ts.map +1 -0
- package/dist/host/engine.js +17 -0
- package/dist/host/engine.js.map +1 -0
- package/dist/host/host.d.ts +79 -0
- package/dist/host/host.d.ts.map +1 -0
- package/dist/host/host.js +303 -0
- package/dist/host/host.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +3 -0
- package/dist/statewire.d.ts +20 -0
- package/dist/statewire.d.ts.map +1 -0
- package/dist/statewire.js +65 -0
- package/dist/statewire.js.map +1 -0
- package/dist/transports/remote.d.ts +19 -0
- package/dist/transports/remote.d.ts.map +1 -0
- package/dist/transports/remote.js +40 -0
- package/dist/transports/remote.js.map +1 -0
- package/dist/transports/sse-parser.d.ts +5 -0
- package/dist/transports/sse-parser.d.ts.map +1 -0
- package/dist/transports/sse-parser.js +26 -0
- package/dist/transports/sse-parser.js.map +1 -0
- package/dist/transports/sse.d.ts +74 -0
- package/dist/transports/sse.d.ts.map +1 -0
- package/dist/transports/sse.js +136 -0
- package/dist/transports/sse.js.map +1 -0
- package/dist/transports/testing.d.ts +31 -0
- package/dist/transports/testing.d.ts.map +1 -0
- package/dist/transports/testing.js +121 -0
- package/dist/transports/testing.js.map +1 -0
- package/dist/transports/ws.d.ts +18 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +172 -0
- package/dist/transports/ws.js.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/wire/meta.d.ts +22 -0
- package/dist/wire/meta.d.ts.map +1 -0
- package/dist/wire/meta.js +73 -0
- package/dist/wire/meta.js.map +1 -0
- package/dist/wire/ops.d.ts +40 -0
- package/dist/wire/ops.d.ts.map +1 -0
- package/dist/wire/ops.js +125 -0
- package/dist/wire/ops.js.map +1 -0
- package/package.json +49 -6
- package/src/client/changes.ts +48 -0
- package/src/client/connection.ts +327 -0
- package/src/client/core.ts +346 -0
- package/src/client/optimistic.ts +0 -0
- package/src/client/session.ts +679 -0
- package/src/client/sessionStore.ts +23 -0
- package/src/client/timing.ts +8 -0
- package/src/host/draft.ts +155 -0
- package/src/host/engine.ts +13 -0
- package/src/host/host.ts +481 -0
- package/src/index.ts +65 -0
- package/src/server.ts +32 -0
- package/src/statewire.ts +107 -0
- package/src/transports/remote.ts +72 -0
- package/src/transports/sse-parser.ts +24 -0
- package/src/transports/sse.ts +280 -0
- package/src/transports/testing.ts +155 -0
- package/src/transports/ws.ts +272 -0
- package/src/types.ts +169 -0
- package/src/wire/meta.ts +106 -0
- package/src/wire/ops.ts +201 -0
- package/index.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { applyOps } from "./wire/ops.js";
|
|
2
|
+
import { StatewireSendError } from "./types.js";
|
|
3
|
+
import { Statewire, StatewireResource, useStatewire } from "./statewire.js";
|
|
4
|
+
import { StatewireSSE } from "./transports/sse.js";
|
|
5
|
+
import { StatewireWS } from "./transports/ws.js";
|
|
6
|
+
export { Statewire, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, applyOps, useStatewire };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StatewireCommands, StatewireStateSource, StatewireTransport } from "./types.js";
|
|
2
|
+
import { CommandExecution, CommandExecutionResolvers, StatewireHandledCommands, StatewireStateCtl, useStatewireCommands, useStatewireEmit, useStatewireState } from "./host/host.js";
|
|
3
|
+
import { StatewireReject } from "./host/engine.js";
|
|
4
|
+
//#region src/server.d.ts
|
|
5
|
+
declare namespace StatewireServer {
|
|
6
|
+
type StateCtl = StatewireStateCtl;
|
|
7
|
+
type StateSource = StatewireStateSource;
|
|
8
|
+
type Execution<T = void> = CommandExecution<T>;
|
|
9
|
+
type ExecutionResolvers<T = void> = CommandExecutionResolvers<T>;
|
|
10
|
+
type HandledCommands<THandlers extends StatewireCommands> = StatewireHandledCommands<THandlers>;
|
|
11
|
+
type Transport<TState = any, TCommands extends StatewireCommands = StatewireCommands, TEvent = any> = StatewireTransport<TState, TCommands, TEvent>;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { CommandExecution, StatewireReject, StatewireServer, useStatewireCommands, useStatewireEmit, useStatewireState };
|
|
15
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;kBAmBiB;OACH,WAAW;OACX,cAAc;OACd,UAAU,YAAY,iBAAiB;OACvC,mBAAmB,YAAY,0BAA0B;OACzD,gBAAgB,kBAAkB,qBAC5C,yBAAyB;OACf,UACV,cACA,kBAAkB,oBAAoB,mBACtC,gBACE,mBAAmB,QAAQ,WAAW"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StatewireConnection } from "./transports/sse.js";
|
|
2
|
+
import { StatewireCommands, StatewireCommandsProxy, StatewireHelpers, StatewireOptions } from "./types.js";
|
|
3
|
+
import { ResourceElement } from "@assistant-ui/tap";
|
|
4
|
+
//#region src/statewire.d.ts
|
|
5
|
+
declare const useStatewire: <TState, TCommands extends StatewireCommands = StatewireCommands, TEvent = any>(options: StatewireOptions<TState, TCommands, TEvent>) => StatewireHelpers<TState, TCommands>;
|
|
6
|
+
declare const StatewireResource: <TState, TCommands extends StatewireCommands = StatewireCommands, TEvent = any>(options: StatewireOptions<TState, TCommands, TEvent>) => ResourceElement<StatewireHelpers<TState, TCommands>>;
|
|
7
|
+
declare class Statewire<TState, TCommands extends StatewireCommands = StatewireCommands, TEvent = any> {
|
|
8
|
+
private readonly core;
|
|
9
|
+
private readonly root;
|
|
10
|
+
private disposed;
|
|
11
|
+
constructor(options: StatewireOptions<TState, TCommands, TEvent>);
|
|
12
|
+
get state(): TState;
|
|
13
|
+
get connection(): StatewireConnection;
|
|
14
|
+
get commands(): StatewireCommandsProxy<TCommands>;
|
|
15
|
+
subscribe(listener: () => void): () => void;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { Statewire, StatewireResource, useStatewire };
|
|
20
|
+
//# sourceMappingURL=statewire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;cAkBa,eACX,QACA,kBAAkB,oBAAoB,mBACtC,cAEA,SAAS,iBAAiB,QAAQ,WAAW,YAC5C,iBAAiB,QAAQ;cA6Bf,oBACX,QACA,kBAAkB,oBAAoB,mBACtC,cAEA,SAAS,iBAAiB,QAAQ,WAAW,YAC5C,gBAAgB,iBAAiB,QAAQ;cAG/B,UACX,QACA,kBAAkB,oBAAoB,mBACtC;mBAEiB;mBACA;UACT;EAEI,YAAA,SAAS,iBAAiB,QAAQ,WAAW;MAarD,SAAS;MAIT,cAAc;MAId,YAAY,uBAAuB;EAIvC,UAAU;EAIV"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { LIVE_CONNECTION, createStatewireCore } from "./client/core.js";
|
|
2
|
+
import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
3
|
+
import { createTapRoot, flushTapSync, resource, useResource } from "@assistant-ui/tap";
|
|
4
|
+
//#region src/statewire.ts
|
|
5
|
+
const useStatewire = (options) => {
|
|
6
|
+
const latest = useRef(options);
|
|
7
|
+
latest.current = options;
|
|
8
|
+
const [, setVersion] = useState(0);
|
|
9
|
+
const [core] = useState(() => createStatewireCore(() => latest.current));
|
|
10
|
+
const value = useResource(options.transport);
|
|
11
|
+
core.attach(value);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
core.attach(value);
|
|
14
|
+
});
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const unsubscribe = core.subscribe(() => flushTapSync(() => setVersion((v) => v + 1)));
|
|
17
|
+
return () => {
|
|
18
|
+
unsubscribe();
|
|
19
|
+
core.detach();
|
|
20
|
+
};
|
|
21
|
+
}, [core]);
|
|
22
|
+
return {
|
|
23
|
+
state: core.getState(),
|
|
24
|
+
connection: value.connection ?? LIVE_CONNECTION,
|
|
25
|
+
commands: core.commands
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const StatewireResource = (options) => resource(useStatewire)(options);
|
|
29
|
+
var Statewire = class {
|
|
30
|
+
core;
|
|
31
|
+
root;
|
|
32
|
+
disposed = false;
|
|
33
|
+
constructor(options) {
|
|
34
|
+
this.core = createStatewireCore(() => options);
|
|
35
|
+
const core = this.core;
|
|
36
|
+
this.root = createTapRoot(() => {
|
|
37
|
+
const value = useResource(options.transport);
|
|
38
|
+
core.attach(value);
|
|
39
|
+
return value;
|
|
40
|
+
});
|
|
41
|
+
flushTapSync(() => {});
|
|
42
|
+
}
|
|
43
|
+
get state() {
|
|
44
|
+
return this.core.getState();
|
|
45
|
+
}
|
|
46
|
+
get connection() {
|
|
47
|
+
return this.root.getValue().connection ?? LIVE_CONNECTION;
|
|
48
|
+
}
|
|
49
|
+
get commands() {
|
|
50
|
+
return this.core.commands;
|
|
51
|
+
}
|
|
52
|
+
subscribe(listener) {
|
|
53
|
+
return this.core.subscribe(listener);
|
|
54
|
+
}
|
|
55
|
+
dispose() {
|
|
56
|
+
if (this.disposed) return;
|
|
57
|
+
this.disposed = true;
|
|
58
|
+
this.root.unmount();
|
|
59
|
+
this.core.dispose();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { Statewire, StatewireResource, useStatewire };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=statewire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statewire.js","names":["useEffect","useRef","useState","createTapRoot","flushTapSync","resource","useResource","ResourceElement","createStatewireCore","LIVE_CONNECTION","StatewireTransport","StatewireCommands","StatewireCommandsProxy","StatewireHelpers","StatewireOptions","StatewireConnection","useStatewire","options","TState","TCommands","TEvent","latest","current","setVersion","core","value","transport","attach","unsubscribe","subscribe","v","detach","state","getState","connection","commands","StatewireResource","Statewire","root","disposed","constructor","getValue","listener","dispose","unmount"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useResource,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { createStatewireCore, LIVE_CONNECTION } from \"./client/core\";\nimport type { StatewireTransport } from \"./types\";\nimport type {\n StatewireCommands,\n StatewireCommandsProxy,\n StatewireHelpers,\n StatewireOptions,\n} from \"./types\";\nimport type { StatewireConnection } from \"./transports/sse\";\n\nexport const useStatewire = <\n TState,\n TCommands extends StatewireCommands = StatewireCommands,\n TEvent = any,\n>(\n options: StatewireOptions<TState, TCommands, TEvent>,\n): StatewireHelpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n const [, setVersion] = useState(0);\n const [core] = useState(() => createStatewireCore(() => latest.current));\n\n const value = useResource(options.transport) as StatewireTransport<unknown>;\n core.attach(value);\n\n useEffect(() => {\n core.attach(value);\n });\n useEffect(() => {\n const unsubscribe = core.subscribe(() =>\n flushTapSync(() => setVersion((v) => v + 1)),\n );\n return () => {\n unsubscribe();\n core.detach();\n };\n }, [core]);\n\n return {\n state: core.getState() as TState,\n connection: value.connection ?? LIVE_CONNECTION,\n commands: core.commands as StatewireCommandsProxy<TCommands>,\n };\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends StatewireCommands = StatewireCommands,\n TEvent = any,\n>(\n options: StatewireOptions<TState, TCommands, TEvent>,\n): ResourceElement<StatewireHelpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands, TEvent>)(options);\n\nexport class Statewire<\n TState,\n TCommands extends StatewireCommands = StatewireCommands,\n TEvent = any,\n> {\n private readonly core;\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireOptions<TState, TCommands, TEvent>) {\n this.core = createStatewireCore(() => options);\n const core = this.core;\n this.root = createTapRoot(() => {\n const value = useResource(\n options.transport,\n ) as StatewireTransport<unknown>;\n core.attach(value);\n return value;\n });\n flushTapSync(() => {});\n }\n\n get state(): TState {\n return this.core.getState() as TState;\n }\n\n get connection(): StatewireConnection {\n return this.root.getValue().connection ?? LIVE_CONNECTION;\n }\n\n get commands(): StatewireCommandsProxy<TCommands> {\n return this.core.commands as StatewireCommandsProxy<TCommands>;\n }\n\n subscribe(listener: () => void): () => void {\n return this.core.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n this.core.dispose();\n }\n}\n"],"mappings":";;;;AAkBA,MAAagB,gBAKXC,YACwC;CACxC,MAAMI,SAASpB,OAAOgB,OAAO;CAC7BI,OAAOC,UAAUL;CACjB,MAAM,GAAGM,cAAcrB,SAAS,CAAC;CACjC,MAAM,CAACsB,QAAQtB,eAAeM,0BAA0Ba,OAAOC,OAAO,CAAC;CAEvE,MAAMG,QAAQnB,YAAYW,QAAQS,SAAS;CAC3CF,KAAKG,OAAOF,KAAK;CAEjBzB,gBAAgB;EACdwB,KAAKG,OAAOF,KAAK;CACnB,CAAC;CACDzB,gBAAgB;EACd,MAAM4B,cAAcJ,KAAKK,gBACvBzB,mBAAmBmB,YAAYO,MAAMA,IAAI,CAAC,CAAC,CAC7C;EACA,aAAa;GACXF,YAAY;GACZJ,KAAKO,OAAO;EACd;CACF,GAAG,CAACP,IAAI,CAAC;CAET,OAAO;EACLQ,OAAOR,KAAKS,SAAS;EACrBC,YAAYT,MAAMS,cAAczB;EAChC0B,UAAUX,KAAKW;CACjB;AACF;AAEA,MAAaC,qBAKXnB,YAEAZ,SAASW,YAAuC,CAAC,CAACC,OAAO;AAE3D,IAAaoB,YAAb,MAIE;CACA;CACA;CACA,WAAmB;CAEnBG,YAAYvB,SAAsD;EAChE,KAAKO,OAAOhB,0BAA0BS,OAAO;EAC7C,MAAMO,OAAO,KAAKA;EAClB,KAAKc,OAAOnC,oBAAoB;GAC9B,MAAMsB,QAAQnB,YACZW,QAAQS,SACV;GACAF,KAAKG,OAAOF,KAAK;GACjB,OAAOA;EACT,CAAC;EACDrB,mBAAmB,CAAC,CAAC;CACvB;CAEA,IAAI4B,QAAgB;EAClB,OAAO,KAAKR,KAAKS,SAAS;CAC5B;CAEA,IAAIC,aAAkC;EACpC,OAAO,KAAKI,KAAKG,SAAS,CAAC,CAACP,cAAczB;CAC5C;CAEA,IAAI0B,WAA8C;EAChD,OAAO,KAAKX,KAAKW;CACnB;CAEAN,UAAUa,UAAkC;EAC1C,OAAO,KAAKlB,KAAKK,UAAUa,QAAQ;CACrC;CAEAC,UAAgB;EACd,IAAI,KAAKJ,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKM,QAAQ;EAClB,KAAKpB,KAAKmB,QAAQ;CACpB;AACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ConnBase } from "../client/connection.js";
|
|
2
|
+
import { RemoteLink, StatewireConnection, StatewireTransportProps } from "./sse.js";
|
|
3
|
+
import { StatewireTransport } from "../types.js";
|
|
4
|
+
import { SessionOptions } from "../client/session.js";
|
|
5
|
+
//#region src/transports/remote.d.ts
|
|
6
|
+
declare const useStatewireRemote: <TOptions extends {
|
|
7
|
+
url: string;
|
|
8
|
+
onChange?: (connection: StatewireConnection) => void;
|
|
9
|
+
} & SessionOptions>(options: TOptions, create: (latest: {
|
|
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>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { useStatewireRemote };
|
|
19
|
+
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAqBa,qBACX;EACE;EACA,YAAY,YAAY;IACtB,gBAEJ,SAAS,UACT,SACE;WACW;IAAW,SAAS;IAAU,KAAK;;GAE9C,SAAS,YAAY,iCAClB;EAAa,MAAM;MACvB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useStatewireEmit } from "../host/host.js";
|
|
2
|
+
import { useStatewireConn } from "../client/connection.js";
|
|
3
|
+
import { createSession, handleFinish, handleOps, handleSnapshot, setTransport, start, stop } from "../client/session.js";
|
|
4
|
+
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
5
|
+
//#region src/transports/remote.ts
|
|
6
|
+
const useStatewireRemote = (options, create) => {
|
|
7
|
+
const emit = useStatewireEmit();
|
|
8
|
+
const [s] = useState(() => createSession({ sessionStore: options.sessionStore }));
|
|
9
|
+
useEffect(() => {
|
|
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);
|
|
28
|
+
useEffect(() => setTransport(s, conn), [s, conn]);
|
|
29
|
+
return {
|
|
30
|
+
state: s.canonical,
|
|
31
|
+
commands: s.commands,
|
|
32
|
+
emit,
|
|
33
|
+
connection: conn.connection,
|
|
34
|
+
source: s.source
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { useStatewireRemote };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.js","names":["useEffect","useState","useStatewireConn","ConnBase","useStatewireEmit","StatewireTransport","createSession","handleFinish","handleOps","handleSnapshot","setTransport","start","stop","SessionOptions","RemoteLink","StatewireConnection","StatewireTransportProps","useStatewireRemote","url","onChange","connection","options","TOptions","create","latest","current","ctx","notify","send","emit","s","sessionStore","emitEvent","clientId","record","onSnapshot","args","onOps","onFinish","conn","state","canonical","commands","source"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useStatewireConn } from \"../client/connection\";\nimport type { ConnBase } from \"../client/connection\";\nimport { useStatewireEmit } from \"../host/host\";\nimport type { StatewireTransport } from \"../types\";\nimport {\n createSession,\n handleFinish,\n handleOps,\n handleSnapshot,\n setTransport,\n start,\n stop,\n} from \"../client/session\";\nimport type { SessionOptions } from \"../client/session\";\nimport type {\n RemoteLink,\n StatewireConnection,\n StatewireTransportProps,\n} from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireConnection) => void;\n } & SessionOptions,\n>(\n options: TOptions,\n create: (\n latest: {\n readonly current: { options: TOptions; ctx: StatewireTransportProps };\n },\n notify: (connection: StatewireConnection) => void,\n ) => ConnBase & { send: RemoteLink[\"send\"] },\n): StatewireTransport<any, any, any> => {\n const emit = useStatewireEmit<any>();\n const [s] = useState(() =>\n createSession({ sessionStore: options.sessionStore }),\n );\n\n useEffect(() => {\n s.emitEvent = emit;\n return () => {\n s.emitEvent = () => {};\n };\n }, [s, emit]);\n useEffect(() => {\n start(s);\n return () => stop(s);\n }, [s]);\n\n const [ctx] = useState(\n (): StatewireTransportProps => ({\n get clientId() {\n return s.record.clientId;\n },\n onSnapshot: (args) => handleSnapshot(s, args),\n onOps: (args) => handleOps(s, args),\n onFinish: (args) => handleFinish(s, args),\n }),\n );\n const conn = useStatewireConn(options, ctx, create);\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 };\n};\n"],"mappings":";;;;;AAqBA,MAAaiB,sBAMXI,SACAE,WAMsC;CACtC,MAAMM,OAAOzB,iBAAsB;CACnC,MAAM,CAAC0B,KAAK7B,eACVK,cAAc,EAAEyB,cAAcV,QAAQU,aAAa,CAAC,CACtD;CAEA/B,gBAAgB;EACd8B,EAAEE,YAAYH;EACd,aAAa;GACXC,EAAEE,kBAAkB,CAAC;EACvB;CACF,GAAG,CAACF,GAAGD,IAAI,CAAC;CACZ7B,gBAAgB;EACdW,MAAMmB,CAAC;EACP,aAAalB,KAAKkB,CAAC;CACrB,GAAG,CAACA,CAAC,CAAC;CAEN,MAAM,CAACJ,OAAOzB,gBACoB;EAC9B,IAAIgC,WAAW;GACb,OAAOH,EAAEI,OAAOD;EAClB;EACAE,aAAaC,SAAS3B,eAAeqB,GAAGM,IAAI;EAC5CC,QAAQD,WAAS5B,UAAUsB,GAAGM,MAAI;EAClCE,WAAWF,WAAS7B,aAAauB,GAAGM,MAAI;CAC1C,EACF;CACA,MAAMG,OAAOrC,iBAAiBmB,SAASK,KAAKH,MAAM;CAClDvB,gBAAgBU,aAAaoB,GAAGS,IAAI,GAAG,CAACT,GAAGS,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOV,EAAEW;EACTC,UAAUZ,EAAEY;EACZb;EACAT,YAAYmB,KAAKnB;EACjBuB,QAAQb,EAAEa;CACZ;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-parser.d.ts","names":[],"sources":["../../src/transports/sse-parser.ts"],"mappings":";cAAa,yBAAwB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/transports/sse-parser.ts
|
|
2
|
+
const createSseParser = () => {
|
|
3
|
+
let buffer = "";
|
|
4
|
+
let dataLines = [];
|
|
5
|
+
return (chunk) => {
|
|
6
|
+
const events = [];
|
|
7
|
+
buffer += chunk;
|
|
8
|
+
const held = buffer.endsWith("\r") ? "\r" : "";
|
|
9
|
+
const lines = buffer.slice(0, buffer.length - held.length).split(/\r\n|\r|\n/);
|
|
10
|
+
buffer = lines.pop() + held;
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
if (line === "") {
|
|
13
|
+
if (dataLines.length > 0) events.push(dataLines.join("\n"));
|
|
14
|
+
dataLines = [];
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const match = /^data(?:: ?(.*))?$/.exec(line);
|
|
18
|
+
if (match) dataLines.push(match[1] ?? "");
|
|
19
|
+
}
|
|
20
|
+
return events;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { createSseParser };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=sse-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-parser.js","names":["createSseParser","chunk","buffer","dataLines","events","held","endsWith","lines","slice","length","split","pop","line","push","join","match","exec"],"sources":["../../src/transports/sse-parser.ts"],"sourcesContent":["export const createSseParser = (): ((chunk: string) => string[]) => {\n let buffer = \"\";\n let dataLines: string[] = [];\n\n return (chunk: string) => {\n const events: string[] = [];\n buffer += chunk;\n const held = buffer.endsWith(\"\\r\") ? \"\\r\" : \"\";\n const lines = buffer\n .slice(0, buffer.length - held.length)\n .split(/\\r\\n|\\r|\\n/);\n buffer = lines.pop()! + held;\n for (const line of lines) {\n if (line === \"\") {\n if (dataLines.length > 0) events.push(dataLines.join(\"\\n\"));\n dataLines = [];\n continue;\n }\n const match = /^data(?:: ?(.*))?$/.exec(line);\n if (match) dataLines.push(match[1] ?? \"\");\n }\n return events;\n };\n};\n"],"mappings":";AAAA,MAAaA,wBAAuD;CAClE,IAAIE,SAAS;CACb,IAAIC,YAAsB,CAAA;CAE1B,QAAQF,UAAkB;EACxB,MAAMG,SAAmB,CAAA;EACzBF,UAAUD;EACV,MAAMI,OAAOH,OAAOI,SAAS,IAAI,IAAI,OAAO;EAC5C,MAAMC,QAAQL,OACXM,MAAM,GAAGN,OAAOO,SAASJ,KAAKI,MAAM,CAAC,CACrCC,MAAM,YAAY;EACrBR,SAASK,MAAMI,IAAI,IAAKN;EACxB,KAAK,MAAMO,QAAQL,OAAO;GACxB,IAAIK,SAAS,IAAI;IACf,IAAIT,UAAUM,SAAS,GAAGL,OAAOS,KAAKV,UAAUW,KAAK,IAAI,CAAC;IAC1DX,YAAY,CAAA;IACZ;GACF;GACA,MAAMY,QAAQ,qBAAqBC,KAAKJ,IAAI;GAC5C,IAAIG,OAAOZ,UAAUU,KAAKE,MAAM,MAAM,EAAE;EAC1C;EACA,OAAOX;CACT;AACF"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { StatewireFinish, StatewireResponse } from "../client/connection.js";
|
|
2
|
+
import { StatewireSessionStore } from "../client/sessionStore.js";
|
|
3
|
+
import { StatewireCommands, StatewireTransport } from "../types.js";
|
|
4
|
+
import { ResourceElement } from "@assistant-ui/tap";
|
|
5
|
+
//#region src/transports/sse.d.ts
|
|
6
|
+
type StatewireTransportProps = {
|
|
7
|
+
clientId: string;
|
|
8
|
+
onSnapshot: (args: {
|
|
9
|
+
ops: unknown[];
|
|
10
|
+
lastSeq: number;
|
|
11
|
+
}) => void;
|
|
12
|
+
onOps: (args: {
|
|
13
|
+
ops: unknown[];
|
|
14
|
+
res?: StatewireResponse[];
|
|
15
|
+
ack?: number;
|
|
16
|
+
}) => void;
|
|
17
|
+
onFinish: (args: StatewireFinish) => void;
|
|
18
|
+
};
|
|
19
|
+
type StatewireWireCommand = {
|
|
20
|
+
method: string;
|
|
21
|
+
params: readonly unknown[];
|
|
22
|
+
};
|
|
23
|
+
type StatewireSubmitResult = {
|
|
24
|
+
type: "receipt";
|
|
25
|
+
response?: StatewireResponse;
|
|
26
|
+
} | {
|
|
27
|
+
type: "rejected";
|
|
28
|
+
message: string;
|
|
29
|
+
payload?: unknown;
|
|
30
|
+
} | {
|
|
31
|
+
type: "seq-gap";
|
|
32
|
+
} | {
|
|
33
|
+
type: "unknown-client";
|
|
34
|
+
} | {
|
|
35
|
+
type: "stale-lease";
|
|
36
|
+
};
|
|
37
|
+
type ConnectionState = {
|
|
38
|
+
status: "connecting";
|
|
39
|
+
} | {
|
|
40
|
+
status: "live";
|
|
41
|
+
} | {
|
|
42
|
+
status: "reconnecting";
|
|
43
|
+
cause: "dropped" | "ended" | "error";
|
|
44
|
+
attempt: number;
|
|
45
|
+
nextRetryAt: number;
|
|
46
|
+
lastError?: string;
|
|
47
|
+
message?: string;
|
|
48
|
+
} | {
|
|
49
|
+
status: "gone";
|
|
50
|
+
message?: string;
|
|
51
|
+
payload?: unknown;
|
|
52
|
+
};
|
|
53
|
+
type StatewireConnection = ConnectionState & {
|
|
54
|
+
reconnect: () => void;
|
|
55
|
+
};
|
|
56
|
+
/** The live wire a remote session drives: connection status plus command POST. */
|
|
57
|
+
type RemoteLink = {
|
|
58
|
+
connection: StatewireConnection;
|
|
59
|
+
send: (args: {
|
|
60
|
+
seq: number;
|
|
61
|
+
command: StatewireWireCommand;
|
|
62
|
+
}) => Promise<StatewireSubmitResult>;
|
|
63
|
+
};
|
|
64
|
+
type StatewireSSEOptions = {
|
|
65
|
+
url: string;
|
|
66
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
67
|
+
fetch?: typeof globalThis.fetch;
|
|
68
|
+
onChange?: (connection: StatewireConnection) => void;
|
|
69
|
+
sessionStore?: StatewireSessionStore;
|
|
70
|
+
};
|
|
71
|
+
declare const StatewireSSE: <TState = any, TCommands extends StatewireCommands = StatewireCommands, TEvent = any>(options: StatewireSSEOptions) => ResourceElement<StatewireTransport<TState | undefined, TCommands, TEvent>>;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { ConnectionState, RemoteLink, StatewireConnection, StatewireSSE, StatewireSSEOptions, StatewireSubmitResult, StatewireTransportProps, StatewireWireCommand };
|
|
74
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;KAwBY;EACV;EACA,aAAa;IAAQ;IAAgB;;EACrC,QAAQ;IACN;IACA,MAAM;IACN;;EAEF,WAAW,MAAM;;KAGP;EACV;EACA;;KAGU;EACN;EAAiB,WAAW;;EAC5B;EAAkB;EAAiB;;EACnC;;EACA;;EACA;;KAEM;EACN;;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;EAEA;EAAgB;EAAkB;;KAE5B,sBAAsB;EAAoB;;;KAG1C;EACV,YAAY;EACZ,OAAO;IACL;IACA,SAAS;QACL,QAAQ;;KAGJ;EACV;EACA,UAAU,qBAAqB,cAAc,QAAQ;EACrD,eAAe,WAAW;EAC1B,YAAY,YAAY;EACxB,eAAe;;cAqMJ,eACX,cACA,kBAAkB,oBAAoB,mBACtC,cAEA,SAAS,wBACN,gBAAgB,mBAAmB,oBAAoB,WAAW"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { encodeParams, reviveResponse } from "../wire/meta.js";
|
|
2
|
+
import { createSseParser } from "./sse-parser.js";
|
|
3
|
+
import { timing } from "../client/timing.js";
|
|
4
|
+
import { classify, createGate, handleEnvelope, overloadedError, parseRetryAfter, reconnectConn } from "../client/connection.js";
|
|
5
|
+
import { useStatewireRemote } from "./remote.js";
|
|
6
|
+
import { flushTapSync, resource } from "@assistant-ui/tap";
|
|
7
|
+
//#region src/transports/sse.ts
|
|
8
|
+
const createConn = (latest, notify) => {
|
|
9
|
+
const c = {
|
|
10
|
+
hasSnapshot: false,
|
|
11
|
+
lease: null,
|
|
12
|
+
attempt: 0,
|
|
13
|
+
episode: null,
|
|
14
|
+
abort: null,
|
|
15
|
+
gate: createGate(),
|
|
16
|
+
lifetime: null,
|
|
17
|
+
latest,
|
|
18
|
+
notify,
|
|
19
|
+
reconnect: () => reconnectConn(c),
|
|
20
|
+
attemptOnce: (abort) => attemptStream(c, abort),
|
|
21
|
+
send: (args) => sendCommand(c, args),
|
|
22
|
+
connection: {
|
|
23
|
+
status: "connecting",
|
|
24
|
+
reconnect: () => c.reconnect()
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return c;
|
|
28
|
+
};
|
|
29
|
+
const baseUrl = (c) => c.latest.current.options.url.replace(/\/$/, "");
|
|
30
|
+
const requestHeaders = async (c, extra) => {
|
|
31
|
+
const { headers } = c.latest.current.options;
|
|
32
|
+
const merged = new Headers(typeof headers === "function" ? await headers() : headers);
|
|
33
|
+
for (const [name, value] of Object.entries(extra)) merged.set(name, value);
|
|
34
|
+
return merged;
|
|
35
|
+
};
|
|
36
|
+
const timedFetch = async (c, input, init, timeout) => {
|
|
37
|
+
try {
|
|
38
|
+
return await (c.latest.current.options.fetch ?? globalThis.fetch)(input, init);
|
|
39
|
+
} catch {
|
|
40
|
+
throw classify(timeout);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const consume = async (c, reader, heartbeat, dead) => {
|
|
44
|
+
const decoder = new TextDecoder();
|
|
45
|
+
const parse = createSseParser();
|
|
46
|
+
let attached = false;
|
|
47
|
+
for (;;) {
|
|
48
|
+
const timer = setTimeout(() => heartbeat.abort(), timing.heartbeatTimeoutMs);
|
|
49
|
+
let result;
|
|
50
|
+
try {
|
|
51
|
+
result = await reader.read();
|
|
52
|
+
} catch {
|
|
53
|
+
throw classify(heartbeat.signal);
|
|
54
|
+
} finally {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
}
|
|
57
|
+
if (result.done) return "ended";
|
|
58
|
+
for (const data of parse(decoder.decode(result.value, { stream: true }))) {
|
|
59
|
+
if (dead()) return "ended";
|
|
60
|
+
const env = JSON.parse(data);
|
|
61
|
+
const done = flushTapSync(() => handleEnvelope(c, env, attached));
|
|
62
|
+
attached = true;
|
|
63
|
+
if (done) return done;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const attemptStream = async (c, abort) => {
|
|
68
|
+
const headers = await requestHeaders(c, {
|
|
69
|
+
"Statewire-Client-Id": c.latest.current.ctx.clientId,
|
|
70
|
+
accept: "text/event-stream"
|
|
71
|
+
});
|
|
72
|
+
const heartbeat = new AbortController();
|
|
73
|
+
const headerTimer = setTimeout(() => heartbeat.abort(), timing.heartbeatTimeoutMs);
|
|
74
|
+
let res;
|
|
75
|
+
try {
|
|
76
|
+
res = await timedFetch(c, `${baseUrl(c)}/stream`, {
|
|
77
|
+
headers,
|
|
78
|
+
cache: "no-store",
|
|
79
|
+
signal: AbortSignal.any([abort.signal, heartbeat.signal])
|
|
80
|
+
}, heartbeat.signal);
|
|
81
|
+
} finally {
|
|
82
|
+
clearTimeout(headerTimer);
|
|
83
|
+
}
|
|
84
|
+
if (res.status === 429 || res.status === 503) throw overloaded(res);
|
|
85
|
+
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
86
|
+
const reader = res.body.getReader();
|
|
87
|
+
try {
|
|
88
|
+
return await consume(c, reader, heartbeat, () => abort.signal.aborted);
|
|
89
|
+
} finally {
|
|
90
|
+
reader.cancel().catch(() => {});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const overloaded = (res) => overloadedError(parseRetryAfter(res.headers.get("Retry-After")));
|
|
94
|
+
const sendCommand = async (c, { seq, command }) => {
|
|
95
|
+
const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);
|
|
96
|
+
const res = await timedFetch(c, `${baseUrl(c)}/commands`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: await requestHeaders(c, {
|
|
99
|
+
"content-type": "application/json",
|
|
100
|
+
"Statewire-Client-Id": c.latest.current.ctx.clientId,
|
|
101
|
+
"Statewire-Command-Seq": String(seq),
|
|
102
|
+
...c.lease !== null && { "Statewire-Lease": c.lease }
|
|
103
|
+
}),
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
method: command.method,
|
|
106
|
+
...encodeParams(command.params)
|
|
107
|
+
}),
|
|
108
|
+
signal: AbortSignal.any([c.lifetime?.signal ?? AbortSignal.abort(), timeout])
|
|
109
|
+
}, timeout);
|
|
110
|
+
if (res.ok) {
|
|
111
|
+
const body = await res.json().catch(() => null);
|
|
112
|
+
return body?.res !== void 0 ? {
|
|
113
|
+
type: "receipt",
|
|
114
|
+
response: reviveResponse(body.res)
|
|
115
|
+
} : { type: "receipt" };
|
|
116
|
+
}
|
|
117
|
+
if (res.status === 409 || res.status === 412 || res.status === 423) {
|
|
118
|
+
const verdict = res.status === 409 ? "seq-gap" : res.status === 412 ? "unknown-client" : "stale-lease";
|
|
119
|
+
if ((await res.json().catch(() => null))?.error === verdict) return { type: verdict };
|
|
120
|
+
throw new Error(`HTTP ${res.status}`);
|
|
121
|
+
}
|
|
122
|
+
if (res.status === 429 || res.status === 503) throw overloaded(res);
|
|
123
|
+
if (res.status >= 400 && res.status < 500) return {
|
|
124
|
+
type: "rejected",
|
|
125
|
+
message: `HTTP ${res.status}`
|
|
126
|
+
};
|
|
127
|
+
throw new Error(`HTTP ${res.status}`);
|
|
128
|
+
};
|
|
129
|
+
const useStatewireSSE = (options) => {
|
|
130
|
+
return useStatewireRemote(options, createConn);
|
|
131
|
+
};
|
|
132
|
+
const StatewireSSE = resource(useStatewireSSE);
|
|
133
|
+
//#endregion
|
|
134
|
+
export { StatewireSSE };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","names":["flushTapSync","resource","ResourceElement","encodeParams","reviveResponse","createSseParser","timing","classify","createGate","handleEnvelope","overloadedError","parseRetryAfter","reconnectConn","ConnBase","StatewireEnvelope","StatewireFinish","StatewireResponse","StatewireTransport","useStatewireRemote","StatewireSessionStore","StatewireCommands","StatewireTransportProps","clientId","onSnapshot","args","ops","lastSeq","onOps","res","ack","onFinish","StatewireWireCommand","method","params","StatewireSubmitResult","type","response","message","payload","ConnectionState","status","cause","attempt","nextRetryAt","lastError","StatewireConnection","reconnect","RemoteLink","connection","send","seq","command","Promise","StatewireSSEOptions","url","headers","HeadersInit","fetch","globalThis","onChange","sessionStore","Latest","options","ctx","Conn","latest","current","createConn","notify","c","hasSnapshot","lease","episode","abort","gate","lifetime","attemptOnce","attemptStream","sendCommand","baseUrl","replace","requestHeaders","extra","Record","merged","Headers","name","value","Object","entries","set","timedFetch","input","init","RequestInit","signal","AbortSignal","timeout","Response","consume","reader","ReadableStreamDefaultReader","Uint8Array","heartbeat","AbortController","dead","decoder","TextDecoder","parse","attached","timer","setTimeout","heartbeatTimeoutMs","result","ReadableStreamReadResult","read","clearTimeout","done","data","decode","stream","env","JSON","accept","headerTimer","cache","any","overloaded","ok","body","Error","getReader","aborted","cancel","catch","get","String","stringify","json","undefined","verdict","const","error","useStatewireSSE","StatewireSSE","TState","TCommands","TEvent"],"sources":["../../src/transports/sse.ts"],"sourcesContent":["import { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { encodeParams, reviveResponse } from \"../wire/meta\";\nimport { createSseParser } from \"./sse-parser\";\nimport { timing } from \"../client/timing\";\nimport {\n classify,\n createGate,\n handleEnvelope,\n overloadedError,\n parseRetryAfter,\n reconnectConn,\n} from \"../client/connection\";\nimport type {\n ConnBase,\n StatewireEnvelope,\n StatewireFinish,\n StatewireResponse,\n} from \"../client/connection\";\nimport type { StatewireTransport } from \"../types\";\nimport { useStatewireRemote } from \"./remote\";\nimport type { StatewireSessionStore } from \"../client/sessionStore\";\nimport type { StatewireCommands } from \"../types\";\n\nexport type StatewireTransportProps = {\n clientId: string;\n onSnapshot: (args: { ops: unknown[]; lastSeq: number }) => void;\n onOps: (args: {\n ops: unknown[];\n res?: StatewireResponse[];\n ack?: number;\n }) => void;\n onFinish: (args: StatewireFinish) => void;\n};\n\nexport type StatewireWireCommand = {\n method: string;\n params: readonly unknown[];\n};\n\nexport type StatewireSubmitResult =\n | { type: \"receipt\"; response?: StatewireResponse }\n | { type: \"rejected\"; message: string; payload?: unknown }\n | { type: \"seq-gap\" }\n | { type: \"unknown-client\" }\n | { type: \"stale-lease\" };\n\nexport type ConnectionState =\n | { status: \"connecting\" }\n | { status: \"live\" }\n | {\n status: \"reconnecting\";\n cause: \"dropped\" | \"ended\" | \"error\";\n attempt: number;\n nextRetryAt: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"gone\"; message?: string; payload?: unknown };\n\nexport type StatewireConnection = ConnectionState & { reconnect: () => void };\n\n/** The live wire a remote session drives: connection status plus command POST. */\nexport type RemoteLink = {\n connection: StatewireConnection;\n send: (args: {\n seq: number;\n command: StatewireWireCommand;\n }) => Promise<StatewireSubmitResult>;\n};\n\nexport type StatewireSSEOptions = {\n url: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: StatewireConnection) => void;\n sessionStore?: StatewireSessionStore;\n};\n\ntype Latest = {\n options: StatewireSSEOptions;\n ctx: StatewireTransportProps;\n};\n\ntype Conn = ConnBase & {\n latest: { readonly current: Latest };\n send: RemoteLink[\"send\"];\n};\n\nconst createConn = (\n latest: { readonly current: Latest },\n notify: (connection: StatewireConnection) => void,\n): Conn => {\n const c: Conn = {\n hasSnapshot: false,\n lease: null,\n attempt: 0,\n episode: null,\n abort: null,\n gate: createGate(),\n lifetime: null,\n latest,\n notify,\n reconnect: () => reconnectConn(c),\n attemptOnce: (abort) => attemptStream(c, abort),\n send: (args) => sendCommand(c, args),\n connection: { status: \"connecting\", reconnect: () => c.reconnect() },\n };\n return c;\n};\n\nconst baseUrl = (c: Conn) => c.latest.current.options.url.replace(/\\/$/, \"\");\n\nconst requestHeaders = async (c: Conn, extra: Record<string, string>) => {\n const { headers } = c.latest.current.options;\n const merged = new Headers(\n typeof headers === \"function\" ? await headers() : headers,\n );\n for (const [name, value] of Object.entries(extra)) merged.set(name, value);\n return merged;\n};\n\nconst timedFetch = async (\n c: Conn,\n input: string,\n init: RequestInit & { signal: AbortSignal },\n timeout: AbortSignal,\n): Promise<Response> => {\n try {\n return await (c.latest.current.options.fetch ?? globalThis.fetch)(\n input,\n init,\n );\n } catch {\n throw classify(timeout);\n }\n};\n\nconst consume = async (\n c: Conn,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n heartbeat: AbortController,\n dead: () => boolean,\n): Promise<\"stop\" | \"reattach\" | \"ended\"> => {\n const decoder = new TextDecoder();\n const parse = createSseParser();\n let attached = false;\n for (;;) {\n const timer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let result: ReadableStreamReadResult<Uint8Array>;\n try {\n result = await reader.read();\n } catch {\n throw classify(heartbeat.signal);\n } finally {\n clearTimeout(timer);\n }\n if (result.done) return \"ended\";\n for (const data of parse(decoder.decode(result.value, { stream: true }))) {\n if (dead()) return \"ended\";\n const env = JSON.parse(data) as StatewireEnvelope;\n const done = flushTapSync(() => handleEnvelope(c, env, attached));\n attached = true;\n if (done) return done;\n }\n }\n};\n\nconst attemptStream = async (\n c: Conn,\n abort: AbortController,\n): Promise<\"stop\" | \"reattach\" | \"ended\"> => {\n const headers = await requestHeaders(c, {\n \"Statewire-Client-Id\": c.latest.current.ctx.clientId,\n accept: \"text/event-stream\",\n });\n const heartbeat = new AbortController();\n const headerTimer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let res: Response;\n try {\n res = await timedFetch(\n c,\n `${baseUrl(c)}/stream`,\n {\n headers,\n cache: \"no-store\",\n signal: AbortSignal.any([abort.signal, heartbeat.signal]),\n },\n heartbeat.signal,\n );\n } finally {\n clearTimeout(headerTimer);\n }\n if (res.status === 429 || res.status === 503) throw overloaded(res);\n if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);\n const reader = res.body.getReader();\n try {\n return await consume(c, reader, heartbeat, () => abort.signal.aborted);\n } finally {\n reader.cancel().catch(() => {});\n }\n};\n\nconst overloaded = (res: Response) =>\n overloadedError(parseRetryAfter(res.headers.get(\"Retry-After\")));\n\nconst sendCommand = async (\n c: Conn,\n { seq, command }: { seq: number; command: StatewireWireCommand },\n): Promise<StatewireSubmitResult> => {\n const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);\n const res = await timedFetch(\n c,\n `${baseUrl(c)}/commands`,\n {\n method: \"POST\",\n headers: await requestHeaders(c, {\n \"content-type\": \"application/json\",\n \"Statewire-Client-Id\": c.latest.current.ctx.clientId,\n \"Statewire-Command-Seq\": String(seq),\n ...(c.lease !== null && { \"Statewire-Lease\": c.lease }),\n }),\n body: JSON.stringify({\n method: command.method,\n ...encodeParams(command.params),\n }),\n signal: AbortSignal.any([\n c.lifetime?.signal ?? AbortSignal.abort(),\n timeout,\n ]),\n },\n timeout,\n );\n if (res.ok) {\n const body = (await res.json().catch(() => null)) as {\n res?: StatewireResponse;\n } | null;\n return body?.res !== undefined\n ? { type: \"receipt\", response: reviveResponse(body.res) }\n : { type: \"receipt\" };\n }\n if (res.status === 409 || res.status === 412 || res.status === 423) {\n // Only a body naming the same verdict as the status is statewire's own;\n // a bare status is middleware noise — a transport fault, retried.\n const verdict =\n res.status === 409\n ? (\"seq-gap\" as const)\n : res.status === 412\n ? (\"unknown-client\" as const)\n : (\"stale-lease\" as const);\n const body = (await res.json().catch(() => null)) as {\n error?: string;\n } | null;\n if (body?.error === verdict) return { type: verdict };\n throw new Error(`HTTP ${res.status}`);\n }\n if (res.status === 429 || res.status === 503) throw overloaded(res);\n if (res.status >= 400 && res.status < 500) {\n return { type: \"rejected\", message: `HTTP ${res.status}` };\n }\n throw new Error(`HTTP ${res.status}`);\n};\n\nconst useStatewireSSE = (\n options: StatewireSSEOptions,\n): StatewireTransport<any, any, any> => useStatewireRemote(options, createConn);\n\nexport const StatewireSSE = resource(useStatewireSSE) as <\n TState = any,\n TCommands extends StatewireCommands = StatewireCommands,\n TEvent = any,\n>(\n options: StatewireSSEOptions,\n) => ResourceElement<StatewireTransport<TState | undefined, TCommands, TEvent>>;\n"],"mappings":";;;;;;;AAyFA,MAAMmE,cACJF,QACAG,WACS;CACT,MAAMC,IAAU;EACdC,aAAa;EACbC,OAAO;EACP7B,SAAS;EACT8B,SAAS;EACTC,OAAO;EACPC,MAAMlE,WAAW;EACjBmE,UAAU;EACVV;EACAG;EACAtB,iBAAiBlC,cAAcyD,CAAC;EAChCO,cAAcH,UAAUI,cAAcR,GAAGI,KAAK;EAC9CxB,OAAOzB,SAASsD,YAAYT,GAAG7C,IAAI;EACnCwB,YAAY;GAAER,QAAQ;GAAcM,iBAAiBuB,EAAEvB,UAAU;EAAE;CACrE;CACA,OAAOuB;AACT;AAEA,MAAMU,WAAWV,MAAYA,EAAEJ,OAAOC,QAAQJ,QAAQR,IAAI0B,QAAQ,OAAO,EAAE;AAE3E,MAAMC,iBAAiB,OAAOZ,GAASa,UAAkC;CACvE,MAAM,EAAE3B,YAAYc,EAAEJ,OAAOC,QAAQJ;CACrC,MAAMsB,SAAS,IAAIC,QACjB,OAAO9B,YAAY,aAAa,MAAMA,QAAQ,IAAIA,OACpD;CACA,KAAK,MAAM,CAAC+B,MAAMC,UAAUC,OAAOC,QAAQP,KAAK,GAAGE,OAAOM,IAAIJ,MAAMC,KAAK;CACzE,OAAOH;AACT;AAEA,MAAMO,aAAa,OACjBtB,GACAuB,OACAC,MACAI,YACsB;CACtB,IAAI;EACF,OAAO,OAAO5B,EAAEJ,OAAOC,QAAQJ,QAAQL,SAASC,WAAWD,MAAAA,CACzDmC,OACAC,IACF;CACF,QAAQ;EACN,MAAMtF,SAAS0F,OAAO;CACxB;AACF;AAEA,MAAME,UAAU,OACd9B,GACA+B,QACAG,WACAE,SAC2C;CAC3C,MAAMC,UAAU,IAAIC,YAAY;CAChC,MAAMC,QAAQvG,gBAAgB;CAC9B,IAAIwG,WAAW;CACf,SAAS;EACP,MAAMC,QAAQC,iBACNR,UAAU9B,MAAM,GACtBnE,OAAO0G,kBACT;EACA,IAAIC;EACJ,IAAI;GACFA,SAAS,MAAMb,OAAOe,KAAK;EAC7B,QAAQ;GACN,MAAM5G,SAASgG,UAAUR,MAAM;EACjC,UAAU;GACRqB,aAAaN,KAAK;EACpB;EACA,IAAIG,OAAOI,MAAM,OAAO;EACxB,KAAK,MAAMC,QAAQV,MAAMF,QAAQa,OAAON,OAAO1B,OAAO,EAAEiC,QAAQ,KAAK,CAAC,CAAC,GAAG;GACxE,IAAIf,KAAK,GAAG,OAAO;GACnB,MAAMgB,MAAMC,KAAKd,MAAMU,IAAI;GAC3B,MAAMD,OAAOrH,mBAAmBS,eAAe4D,GAAGoD,KAAKZ,QAAQ,CAAC;GAChEA,WAAW;GACX,IAAIQ,MAAM,OAAOA;EACnB;CACF;AACF;AAEA,MAAMxC,gBAAgB,OACpBR,GACAI,UAC2C;CAC3C,MAAMlB,UAAU,MAAM0B,eAAeZ,GAAG;EACtC,uBAAuBA,EAAEJ,OAAOC,QAAQH,IAAIzC;EAC5CqG,QAAQ;CACV,CAAC;CACD,MAAMpB,YAAY,IAAIC,gBAAgB;CACtC,MAAMoB,cAAcb,iBACZR,UAAU9B,MAAM,GACtBnE,OAAO0G,kBACT;CACA,IAAIpF;CACJ,IAAI;EACFA,MAAM,MAAM+D,WACVtB,GACA,GAAGU,QAAQV,CAAC,EAAC,UACb;GACEd;GACAsE,OAAO;GACP9B,QAAQC,YAAY8B,IAAI,CAACrD,MAAMsB,QAAQQ,UAAUR,MAAM,CAAC;EAC1D,GACAQ,UAAUR,MACZ;CACF,UAAU;EACRqB,aAAaQ,WAAW;CAC1B;CACA,IAAIhG,IAAIY,WAAW,OAAOZ,IAAIY,WAAW,KAAK,MAAMuF,WAAWnG,GAAG;CAClE,IAAI,CAACA,IAAIoG,MAAM,CAACpG,IAAIqG,MAAM,MAAM,IAAIC,MAAM,QAAQtG,IAAIY,QAAQ;CAC9D,MAAM4D,SAASxE,IAAIqG,KAAKE,UAAU;CAClC,IAAI;EACF,OAAO,MAAMhC,QAAQ9B,GAAG+B,QAAQG,iBAAiB9B,MAAMsB,OAAOqC,OAAO;CACvE,UAAU;EACRhC,OAAOiC,OAAO,CAAC,CAACC,YAAY,CAAC,CAAC;CAChC;AACF;AAEA,MAAMP,cAAcnG,QAClBlB,gBAAgBC,gBAAgBiB,IAAI2B,QAAQgF,IAAI,aAAa,CAAC,CAAC;AAEjE,MAAMzD,cAAc,OAClBT,GACA,EAAEnB,KAAKC,cAC4B;CACnC,MAAM8C,UAAUD,YAAYC,QAAQ3F,OAAO0G,kBAAkB;CAC7D,MAAMpF,MAAM,MAAM+D,WAChBtB,GACA,GAAGU,QAAQV,CAAC,EAAC,YACb;EACErC,QAAQ;EACRuB,SAAS,MAAM0B,eAAeZ,GAAG;GAC/B,gBAAgB;GAChB,uBAAuBA,EAAEJ,OAAOC,QAAQH,IAAIzC;GAC5C,yBAAyBkH,OAAOtF,GAAG;GACnC,GAAImB,EAAEE,UAAU,QAAQ,EAAE,mBAAmBF,EAAEE,MAAM;EACvD,CAAC;EACD0D,MAAMP,KAAKe,UAAU;GACnBzG,QAAQmB,QAAQnB;GAChB,GAAG7B,aAAagD,QAAQlB,MAAM;EAChC,CAAC;EACD8D,QAAQC,YAAY8B,IAAI,CACtBzD,EAAEM,UAAUoB,UAAUC,YAAYvB,MAAM,GACxCwB,OAAO,CACR;CACH,GACAA,OACF;CACA,IAAIrE,IAAIoG,IAAI;EACV,MAAMC,OAAQ,MAAMrG,IAAI8G,KAAK,CAAC,CAACJ,YAAY,IAAI;EAG/C,OAAOL,MAAMrG,QAAQ+G,KAAAA,IACjB;GAAExG,MAAM;GAAWC,UAAUhC,eAAe6H,KAAKrG,GAAG;EAAE,IACtD,EAAEO,MAAM,UAAU;CACxB;CACA,IAAIP,IAAIY,WAAW,OAAOZ,IAAIY,WAAW,OAAOZ,IAAIY,WAAW,KAAK;EAGlE,MAAMoG,UACJhH,IAAIY,WAAW,MACV,YACDZ,IAAIY,WAAW,MACZ,mBACA;EAIT,KAAIyF,MAHgBrG,IAAI8G,KAAK,CAAC,CAACJ,YAAY,IAAI,EAAA,EAGrCQ,UAAUF,SAAS,OAAO,EAAEzG,MAAMyG,QAAQ;EACpD,MAAM,IAAIV,MAAM,QAAQtG,IAAIY,QAAQ;CACtC;CACA,IAAIZ,IAAIY,WAAW,OAAOZ,IAAIY,WAAW,KAAK,MAAMuF,WAAWnG,GAAG;CAClE,IAAIA,IAAIY,UAAU,OAAOZ,IAAIY,SAAS,KACpC,OAAO;EAAEL,MAAM;EAAYE,SAAS,QAAQT,IAAIY;CAAS;CAE3D,MAAM,IAAI0F,MAAM,QAAQtG,IAAIY,QAAQ;AACtC;AAEA,MAAMuG,mBAAkBjF,YAAA;CAAA,OAEgB5C,mBAAmB4C,SAASK,UAAU;AAAC;AAE/E,MAAa6E,eAAe/I,SAAS8I,eAAe"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { StatewireEnvelope } from "../client/connection.js";
|
|
2
|
+
import { StatewireTransport } from "../types.js";
|
|
3
|
+
import { ResourceElement } from "@assistant-ui/tap";
|
|
4
|
+
//#region src/transports/testing.d.ts
|
|
5
|
+
/** Let queued microtasks and op batches drain through the statewire client. */
|
|
6
|
+
declare const tick: () => Promise<void>;
|
|
7
|
+
/** Run a mutator against a recording draft, as the client does for `optimistic`. */
|
|
8
|
+
declare const applyOptimistic: <TState>(state: TState, mutate: (draft: TState) => void) => TState;
|
|
9
|
+
type MockTransport<TState> = {
|
|
10
|
+
/** Pass as the `transport:` option of useStatewire / Harness. */
|
|
11
|
+
transport: ResourceElement<StatewireTransport<TState | undefined, any, any>>;
|
|
12
|
+
/** Captured `{ method, params }` command bodies, in POST order. */
|
|
13
|
+
commands: {
|
|
14
|
+
method: string;
|
|
15
|
+
params: unknown[];
|
|
16
|
+
}[];
|
|
17
|
+
/** Replace the replicated state and broadcast it as a root snapshot op. */
|
|
18
|
+
setState: (state: TState) => Promise<void>;
|
|
19
|
+
/** Deliver an ack watermark on the stream. */
|
|
20
|
+
ack: (seq: number) => Promise<void>;
|
|
21
|
+
/** Deliver an arbitrary envelope on the stream. */
|
|
22
|
+
sendEnvelope: (envelope: StatewireEnvelope) => Promise<void>;
|
|
23
|
+
/** End the stream with a fin; further attaches fail like a dead server. */
|
|
24
|
+
finish: (reason: string, message?: string) => Promise<void>;
|
|
25
|
+
readonly clientId: string;
|
|
26
|
+
readonly attached: boolean;
|
|
27
|
+
};
|
|
28
|
+
declare const createMockTransport: <TState>(initialState: TState) => MockTransport<TState>;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { MockTransport, applyOptimistic, createMockTransport, tick };
|
|
31
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","names":[],"sources":["../../src/transports/testing.ts"],"mappings":";;;;;cAOa,YAAI;;cAGJ,kBAAmB,QAC9B,OAAO,QACP,SAAS,OAAO,oBACf;KAQS,cAAc;;EAExB,WAAW,gBAAgB,mBAAmB;;EAE9C;IAAY;IAAgB;;;EAE5B,WAAW,OAAO,WAAW;;EAE7B,MAAM,gBAAgB;;EAEtB,eAAe,UAAU,sBAAsB;;EAE/C,SAAS,gBAAgB,qBAAqB;WACrC;WACA;;cAUE,sBAAuB,QAClC,cAAc,WACb,cAAc"}
|