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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentbaseAI Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,85 @@
|
|
|
1
1
|
# statewire
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React/TypeScript client for the **Statewire** protocol: a server owns a JSON state object and streams it to clients over SSE (`GET /stream`, snapshot first); clients change it by POSTing method calls (`POST /commands`, body `{"method", "params"}`). The client keeps your UI on the canonical state with optimistic updates rebased on top, survives disconnects, evictions, and reloads of the server, and tells you exactly what happened to every command you sent.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { useStatewire, StatewireSSE } from "statewire";
|
|
7
|
+
|
|
8
|
+
type Commands = {
|
|
9
|
+
increment(): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const { state, transport, commands } = useStatewire<State, Commands, Event>({
|
|
13
|
+
transport: StatewireSSE({ url: "/api/threads/demo" }),
|
|
14
|
+
optimistic: (draft, command) => {
|
|
15
|
+
if (command.method === "increment") draft.count += 1;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const onClick = async () => {
|
|
20
|
+
await commands.increment(); // resolves when the server has provably applied it
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What you get
|
|
25
|
+
|
|
26
|
+
- **`state`** — `undefined` until the first snapshot, then canonical server state with your pending commands' optimistic effects rebased on top. When the server's ack arrives, the optimistic overlay drops in the same tick: no flicker.
|
|
27
|
+
- **`transport`** — connection health as a tagged union: `transport.status` is `"connecting" | "live" | "reconnecting" | "gone"`, and each variant carries exactly the data that exists in that situation: `reconnecting` has `cause` (`"dropped"`, `"ended"`, or `"error"`), `attempt`, `nextRetryAt`, `lastError?`, `message?`; `gone` carries the server's `message?`. `transport.reconnect()` re-attaches manually from `gone`, or "retry now" during backoff.
|
|
28
|
+
- **`commands`** — a typed proxy over the `Commands` method map: `commands.increment()` submits `{ method: "increment", params: [] }` and resolves with the handler's result only once the stream ack proves the effect is in canonical state. Rejects with `StatewireSendError`, whose `failure.stage` carries the verdict; the boundary is definitiveness:
|
|
29
|
+
- `"aborted"` — definitely not applied (never POSTed, or every attempt accounted for); safe to resend
|
|
30
|
+
- `"lost"` — a POST left the machine and never got a definitive answer; whether the server applied it is unknown — check the snapshot for your payload ids
|
|
31
|
+
- `"rejected"` — the server said no; not applied (`message`, optional `payload` from the backend's `Reject`)
|
|
32
|
+
- `"crashed"` — acked, but the processing died before producing a result
|
|
33
|
+
- `"unavailable"` — acked, but the result can no longer be delivered
|
|
34
|
+
|
|
35
|
+
Every failure also carries `reason` — the trigger: `"response"` = the server's response settled it, `"stream-gone"` = terminal `finish(gone)`, `"session-lost"` = the server forgot the client id (eviction), `"superseded"` = another attach took the writer lease, `"unmount"` / `"cascade"` are client-side. `Statewire.SendFailure` is the taxonomy's single exported type; the `stage` and `reason` unions are reachable as `SendFailure["stage"]` / `SendFailure["reason"]`.
|
|
36
|
+
|
|
37
|
+
Reconnects are not errors: transport drops and server evictions self-heal with exponential backoff while `state` stays renderable. Commands carry a monotonic seq, so retries are deduplicated server-side and a reconnect splices already-applied commands instead of re-running them.
|
|
38
|
+
|
|
39
|
+
Sessions are single-writer: every attach takes a writer lease, and any older stream for the same client id ends with `finish(superseded)` — that client goes `gone` (no reconnect) and its pending sends fail with reason `"superseded"`.
|
|
40
|
+
|
|
41
|
+
## Three ways to mount
|
|
42
|
+
|
|
43
|
+
One implementation, three entry points:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
useStatewire(options) // React hook
|
|
47
|
+
StatewireResource(options) // @assistant-ui/tap resource, for composition
|
|
48
|
+
new Statewire(options) // framework-free: .state/.transport/.commands,
|
|
49
|
+
// .subscribe(cb), .dispose()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`Statewire#subscribe` follows the external-store contract (`useSyncExternalStore`-compatible). `dispose()` settles pending commands like an unmount.
|
|
53
|
+
|
|
54
|
+
Every public type merges onto the class as a namespace — `Statewire.Transport`, `Statewire.TransportStatus`, `Statewire.Options`, `Statewire.SendFailure`, … — so one import covers the whole surface.
|
|
55
|
+
|
|
56
|
+
There is no session key: a transport `url` change reattaches in place and the attach snapshot arbitrates identity (an unknown client evicts — pending settles, the client id rotates). For a forced fresh session, key the resource — `withKey(threadId, StatewireResource(options))`; pending commands settle with reason `"unmount"`.
|
|
57
|
+
|
|
58
|
+
## Transport
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
StatewireSSE({
|
|
62
|
+
url, // endpoint root — the transport appends /stream and /commands
|
|
63
|
+
headers, // HeadersInit or (async) () => HeadersInit, re-evaluated per request
|
|
64
|
+
fetch, // custom fetch — the escape hatch for everything else
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`StatewireWS({ url, protocols, query, webSocket })` is the full-duplex sibling over `/ws`. Transports implement the `Statewire.TransportProps` seam; `createMockTransport` from `statewire/testing` is a complete in-memory driver for tests.
|
|
69
|
+
|
|
70
|
+
## Backend
|
|
71
|
+
|
|
72
|
+
Any server speaking the Statewire convention works. The Python implementation lives in this repo (`python/statewire`):
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from statewire import Statewire, Reject, command
|
|
76
|
+
|
|
77
|
+
class Counter(Statewire):
|
|
78
|
+
@command
|
|
79
|
+
async def increment(self):
|
|
80
|
+
if self.state["count"] >= 10:
|
|
81
|
+
raise Reject("counter is full", payload={"max": 10})
|
|
82
|
+
self.state["count"] += 1
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
See `examples/vite-statewire-example` (frontend) and `python/statewire/examples/demo_app.py` (backend) for a complete runnable pair, and the repo's protocol spec for the wire format.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { StatewirePath } from "../wire/ops.js";
|
|
2
|
+
import { StatewireChanges } from "../types.js";
|
|
3
|
+
//#region src/client/changes.d.ts
|
|
4
|
+
declare const createChanges: (prev: unknown, next: unknown, changedPaths: readonly StatewirePath[]) => StatewireChanges;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { createChanges };
|
|
7
|
+
//# sourceMappingURL=changes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.d.ts","names":[],"sources":["../../src/client/changes.ts"],"mappings":";;;cAqBa,gBACX,eACA,eACA,uBAAuB,oBACtB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/client/changes.ts
|
|
2
|
+
const lookupValue = (state, path) => {
|
|
3
|
+
let node = state;
|
|
4
|
+
for (const key of path) {
|
|
5
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
6
|
+
node = Object.hasOwn(node, key) ? node[key] : void 0;
|
|
7
|
+
}
|
|
8
|
+
return node;
|
|
9
|
+
};
|
|
10
|
+
const keysAt = (node) => typeof node === "object" && node !== null ? Object.keys(node) : [];
|
|
11
|
+
const isPrefix = (prefix, path) => prefix.length <= path.length && prefix.every((key, i) => String(key) === String(path[i]));
|
|
12
|
+
const createChanges = (prev, next, changedPaths) => ({
|
|
13
|
+
isChangedAt: (path) => changedPaths.some((p) => isPrefix(p, path) || isPrefix(path, p)),
|
|
14
|
+
getChangedKeys: (path) => {
|
|
15
|
+
if (changedPaths.some((p) => p.length <= path.length && isPrefix(p, path))) {
|
|
16
|
+
const keys = keysAt(lookupValue(next, path));
|
|
17
|
+
for (const key of keysAt(lookupValue(prev, path))) if (!keys.includes(key)) keys.push(key);
|
|
18
|
+
return keys;
|
|
19
|
+
}
|
|
20
|
+
const keys = [];
|
|
21
|
+
for (const p of changedPaths) {
|
|
22
|
+
if (p.length <= path.length || !isPrefix(path, p)) continue;
|
|
23
|
+
const key = String(p[path.length]);
|
|
24
|
+
if (!keys.includes(key)) keys.push(key);
|
|
25
|
+
}
|
|
26
|
+
return keys;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { createChanges };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.js","names":["StatewirePath","StatewireChanges","lookupValue","state","path","node","key","undefined","Object","hasOwn","Record","keysAt","keys","isPrefix","prefix","length","every","i","String","createChanges","prev","next","changedPaths","isChangedAt","some","p","getChangedKeys","wiped","includes","push"],"sources":["../../src/client/changes.ts"],"sourcesContent":["import type { StatewirePath } from \"../wire/ops\";\nimport type { StatewireChanges } from \"../types\";\n\nconst lookupValue = (state: unknown, path: StatewirePath): unknown => {\n let node = state;\n for (const key of path) {\n if (typeof node !== \"object\" || node === null) return undefined;\n node = Object.hasOwn(node, key)\n ? (node as Record<string, unknown>)[key]\n : undefined;\n }\n return node;\n};\n\nconst keysAt = (node: unknown): string[] =>\n typeof node === \"object\" && node !== null ? Object.keys(node) : [];\n\nconst isPrefix = (prefix: StatewirePath, path: StatewirePath): boolean =>\n prefix.length <= path.length &&\n prefix.every((key, i) => String(key) === String(path[i]));\n\nexport const createChanges = (\n prev: unknown,\n next: unknown,\n changedPaths: readonly StatewirePath[],\n): StatewireChanges => ({\n isChangedAt: (path) =>\n changedPaths.some((p) => isPrefix(p, path) || isPrefix(path, p)),\n getChangedKeys: (path) => {\n const wiped = changedPaths.some(\n (p) => p.length <= path.length && isPrefix(p, path),\n );\n if (wiped) {\n const keys = keysAt(lookupValue(next, path));\n for (const key of keysAt(lookupValue(prev, path))) {\n if (!keys.includes(key)) keys.push(key);\n }\n return keys;\n }\n const keys: string[] = [];\n for (const p of changedPaths) {\n if (p.length <= path.length || !isPrefix(path, p)) continue;\n const key = String(p[path.length]);\n if (!keys.includes(key)) keys.push(key);\n }\n return keys;\n },\n});\n"],"mappings":";AAGA,MAAME,eAAeC,OAAgBC,SAAiC;CACpE,IAAIC,OAAOF;CACX,KAAK,MAAMG,OAAOF,MAAM;EACtB,IAAI,OAAOC,SAAS,YAAYA,SAAS,MAAM,OAAOE,KAAAA;EACtDF,OAAOG,OAAOC,OAAOJ,MAAMC,GAAG,IACzBD,KAAiCC,OAClCC,KAAAA;CACN;CACA,OAAOF;AACT;AAEA,MAAMM,UAAUN,SACd,OAAOA,SAAS,YAAYA,SAAS,OAAOG,OAAOI,KAAKP,IAAI,IAAI,CAAA;AAElE,MAAMQ,YAAYC,QAAuBV,SACvCU,OAAOC,UAAUX,KAAKW,UACtBD,OAAOE,OAAOV,KAAKW,MAAMC,OAAOZ,GAAG,MAAMY,OAAOd,KAAKa,EAAE,CAAC;AAE1D,MAAaE,iBACXC,MACAC,MACAC,kBACsB;CACtBC,cAAcnB,SACZkB,aAAaE,MAAMC,MAAMZ,SAASY,GAAGrB,IAAI,KAAKS,SAAST,MAAMqB,CAAC,CAAC;CACjEC,iBAAiBtB,SAAS;EAIxB,IAHckB,aAAaE,MACxBC,MAAMA,EAAEV,UAAUX,KAAKW,UAAUF,SAASY,GAAGrB,IAAI,CAEhDuB,GAAO;GACT,MAAMf,OAAOD,OAAOT,YAAYmB,MAAMjB,IAAI,CAAC;GAC3C,KAAK,MAAME,OAAOK,OAAOT,YAAYkB,MAAMhB,IAAI,CAAC,GAC9C,IAAI,CAACQ,KAAKgB,SAAStB,GAAG,GAAGM,KAAKiB,KAAKvB,GAAG;GAExC,OAAOM;EACT;EACA,MAAMA,OAAiB,CAAA;EACvB,KAAK,MAAMa,KAAKH,cAAc;GAC5B,IAAIG,EAAEV,UAAUX,KAAKW,UAAU,CAACF,SAAST,MAAMqB,CAAC,GAAG;GACnD,MAAMnB,MAAMY,OAAOO,EAAErB,KAAKW,OAAO;GACjC,IAAI,CAACH,KAAKgB,SAAStB,GAAG,GAAGM,KAAKiB,KAAKvB,GAAG;EACxC;EACA,OAAOM;CACT;AACF"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ConnectionState, RemoteLink, StatewireConnection, StatewireTransportProps } from "../transports/sse.js";
|
|
2
|
+
//#region src/client/connection.d.ts
|
|
3
|
+
type Episode = {
|
|
4
|
+
cause: "dropped" | "ended" | "error";
|
|
5
|
+
message?: string;
|
|
6
|
+
};
|
|
7
|
+
type StatewireFinish = {
|
|
8
|
+
reason: "ended" | "error" | "gone" | "superseded" | (string & {});
|
|
9
|
+
message?: string;
|
|
10
|
+
payload?: unknown;
|
|
11
|
+
meta?: unknown;
|
|
12
|
+
};
|
|
13
|
+
type StatewireResponse = {
|
|
14
|
+
seq: number;
|
|
15
|
+
type: "accepted" | "rejected" | "pending" | "crashed" | "result-unavailable";
|
|
16
|
+
code?: string;
|
|
17
|
+
message?: string;
|
|
18
|
+
payload?: unknown;
|
|
19
|
+
meta?: unknown;
|
|
20
|
+
};
|
|
21
|
+
type StatewireSyn = {
|
|
22
|
+
lastSeq: number;
|
|
23
|
+
lease?: string;
|
|
24
|
+
};
|
|
25
|
+
type StatewireEnvelope = {
|
|
26
|
+
ops?: unknown[];
|
|
27
|
+
res?: StatewireResponse[];
|
|
28
|
+
ack?: number;
|
|
29
|
+
syn?: StatewireSyn;
|
|
30
|
+
fin?: StatewireFinish;
|
|
31
|
+
};
|
|
32
|
+
declare const createGate: () => {
|
|
33
|
+
take: () => boolean;
|
|
34
|
+
wait: (timeoutMs?: number) => Promise<void>;
|
|
35
|
+
open: () => void;
|
|
36
|
+
};
|
|
37
|
+
type Gate = ReturnType<typeof createGate>;
|
|
38
|
+
type ConnBase = {
|
|
39
|
+
connection: StatewireConnection;
|
|
40
|
+
hasSnapshot: boolean;
|
|
41
|
+
lease: string | null;
|
|
42
|
+
attempt: number;
|
|
43
|
+
episode: Episode | null;
|
|
44
|
+
gate: Gate;
|
|
45
|
+
abort: AbortController | null;
|
|
46
|
+
lifetime: AbortController | null;
|
|
47
|
+
latest: {
|
|
48
|
+
readonly current: {
|
|
49
|
+
options: {
|
|
50
|
+
onChange?: (connection: StatewireConnection) => void;
|
|
51
|
+
};
|
|
52
|
+
ctx: StatewireTransportProps;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
notify: (connection: StatewireConnection) => void;
|
|
56
|
+
reconnect: () => void;
|
|
57
|
+
attemptOnce: (abort: AbortController) => Promise<"stop" | "reattach" | "ended">;
|
|
58
|
+
};
|
|
59
|
+
declare const setStatus: (c: ConnBase, next: ConnectionState) => void;
|
|
60
|
+
declare const deliverEnvelope: (c: ConnBase, env: Pick<StatewireEnvelope, "ops" | "res" | "ack" | "syn">, attached: boolean) => void;
|
|
61
|
+
declare const isTransientFinish: (reason: StatewireFinish["reason"]) => reason is "ended" | "error";
|
|
62
|
+
declare const settleEnvelope: (c: ConnBase, env: StatewireEnvelope) => "stop" | "reattach" | null;
|
|
63
|
+
declare const handleEnvelope: (c: ConnBase, env: StatewireEnvelope, attached: boolean) => "stop" | "reattach" | null;
|
|
64
|
+
declare const scheduleRetry: (c: ConnBase, failure: string | undefined, immediate: boolean, retryAfterMs?: number) => number;
|
|
65
|
+
declare const reconnectConn: (c: ConnBase) => void;
|
|
66
|
+
declare const useStatewireConn: <TOptions extends {
|
|
67
|
+
url: string;
|
|
68
|
+
onChange?: (connection: StatewireConnection) => void;
|
|
69
|
+
}>(options: TOptions, ctx: StatewireTransportProps, create: (latest: {
|
|
70
|
+
readonly current: {
|
|
71
|
+
options: TOptions;
|
|
72
|
+
ctx: StatewireTransportProps;
|
|
73
|
+
};
|
|
74
|
+
}, notify: (connection: StatewireConnection) => void) => ConnBase & {
|
|
75
|
+
send: RemoteLink["send"];
|
|
76
|
+
}) => RemoteLink;
|
|
77
|
+
declare const classify: (timeout: AbortSignal) => Error;
|
|
78
|
+
declare const parseRetryAfter: (header: string | null) => number | undefined;
|
|
79
|
+
declare const overloadedError: (retryAfterMs?: number) => Error & {
|
|
80
|
+
retryAfterMs: number | undefined;
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { ConnBase, Episode, Gate, StatewireEnvelope, StatewireFinish, StatewireResponse, StatewireSyn, classify, createGate, deliverEnvelope, handleEnvelope, isTransientFinish, overloadedError, parseRetryAfter, reconnectConn, scheduleRetry, setStatus, settleEnvelope, useStatewireConn };
|
|
84
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","names":[],"sources":["../../src/client/connection.ts"],"mappings":";;KAWY;EACV;EACA;;KAGU;EACV;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;;KAGU;EACV;EACA,MAAM;EACN;EACA,MAAM;EACN,MAAM;;cAGK;;EASU,OAAA,uBAAM;;;KA2BjB,OAAO,kBAAkB;KAEzB;EACV,YAAY;EACZ;EACA;EACA;EACA,SAAS;EACT,MAAM;EACN,OAAO;EACP,UAAU;EACV;aACW;MACP;QAAW,YAAY,YAAY;;MACnC,KAAK;;;EAGT,SAAS,YAAY;EACrB;EACA,cACE,OAAO,oBACJ;;cAGM,YAAa,GAAG,UAAU,MAAM;cAOhC,kBACX,GAAG,UACH,KAAK,KAAK,mDACV;cAsBW,oBACX,QAAQ,8BACP;cAEU,iBACX,GAAG,UACH,KAAK;cA+BM,iBACX,GAAG,UACH,KAAK,mBACL;cAMW,gBACX,GAAG,UACH,6BACA,oBACA;cAiBW,gBAAiB,GAAG;cAwEpB,mBACX;EACE;EACA,YAAY,YAAY;GAG1B,SAAS,UACT,KAAK,yBACL,SACE;WACW;IACP,SAAS;IACT,KAAK;;GAGT,SAAS,YAAY,iCAClB;EAAa,MAAM;MACvB;cAqBU,WAAY,SAAS,gBAAW;cAGhC,kBAAmB;cAQnB,kBAAmB,0BAAqB"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { reviveOps, reviveResponse, reviveValue } from "../wire/meta.js";
|
|
2
|
+
import { backoff } from "./timing.js";
|
|
3
|
+
import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
4
|
+
import { flushTapSync } from "@assistant-ui/tap";
|
|
5
|
+
//#region src/client/connection.ts
|
|
6
|
+
const createGate = () => {
|
|
7
|
+
let latched = false;
|
|
8
|
+
let wake;
|
|
9
|
+
return {
|
|
10
|
+
take: () => {
|
|
11
|
+
const was = latched;
|
|
12
|
+
latched = false;
|
|
13
|
+
return was;
|
|
14
|
+
},
|
|
15
|
+
wait: (timeoutMs) => {
|
|
16
|
+
if (latched) {
|
|
17
|
+
latched = false;
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
22
|
+
wake = void 0;
|
|
23
|
+
resolve();
|
|
24
|
+
}, timeoutMs);
|
|
25
|
+
wake = () => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
wake = void 0;
|
|
28
|
+
resolve();
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
open: () => {
|
|
33
|
+
if (wake) wake();
|
|
34
|
+
else latched = true;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const setStatus = (c, next) => {
|
|
39
|
+
const changed = next.status !== c.connection.status;
|
|
40
|
+
c.connection = {
|
|
41
|
+
...next,
|
|
42
|
+
reconnect: c.reconnect
|
|
43
|
+
};
|
|
44
|
+
c.notify(c.connection);
|
|
45
|
+
if (changed) c.latest.current.options.onChange?.(c.connection);
|
|
46
|
+
};
|
|
47
|
+
const deliverEnvelope = (c, env, attached) => {
|
|
48
|
+
if (env.syn?.lease !== void 0) c.lease = env.syn.lease;
|
|
49
|
+
const ctx = c.latest.current.ctx;
|
|
50
|
+
const ops = env.ops === void 0 ? [] : reviveOps(env.ops);
|
|
51
|
+
const res = env.res?.map(reviveResponse);
|
|
52
|
+
if (!attached) ctx.onSnapshot({
|
|
53
|
+
ops,
|
|
54
|
+
lastSeq: env.syn?.lastSeq ?? -1
|
|
55
|
+
});
|
|
56
|
+
else if (env.ops !== void 0 || env.res !== void 0 || env.ack !== void 0) ctx.onOps({
|
|
57
|
+
ops,
|
|
58
|
+
...res !== void 0 && { res },
|
|
59
|
+
...env.ack !== void 0 && { ack: env.ack }
|
|
60
|
+
});
|
|
61
|
+
c.hasSnapshot = true;
|
|
62
|
+
};
|
|
63
|
+
const isTransientFinish = (reason) => reason === "ended" || reason === "error";
|
|
64
|
+
const settleEnvelope = (c, env) => {
|
|
65
|
+
let finish = env.fin;
|
|
66
|
+
if (finish) {
|
|
67
|
+
if (finish.meta !== void 0) {
|
|
68
|
+
const { meta, ...rest } = finish;
|
|
69
|
+
finish = {
|
|
70
|
+
...rest,
|
|
71
|
+
payload: reviveValue(rest.payload, meta)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
c.latest.current.ctx.onFinish(finish);
|
|
75
|
+
if (isTransientFinish(finish.reason)) {
|
|
76
|
+
c.episode = {
|
|
77
|
+
cause: finish.reason,
|
|
78
|
+
...finish.message !== void 0 && { message: finish.message }
|
|
79
|
+
};
|
|
80
|
+
return "reattach";
|
|
81
|
+
}
|
|
82
|
+
setStatus(c, {
|
|
83
|
+
status: "gone",
|
|
84
|
+
...finish.message !== void 0 && { message: finish.message },
|
|
85
|
+
...finish.payload !== void 0 && { payload: finish.payload }
|
|
86
|
+
});
|
|
87
|
+
return "stop";
|
|
88
|
+
}
|
|
89
|
+
if (c.connection.status !== "live") {
|
|
90
|
+
c.attempt = 0;
|
|
91
|
+
c.episode = null;
|
|
92
|
+
setStatus(c, { status: "live" });
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
const handleEnvelope = (c, env, attached) => {
|
|
97
|
+
deliverEnvelope(c, env, attached);
|
|
98
|
+
return settleEnvelope(c, env);
|
|
99
|
+
};
|
|
100
|
+
const scheduleRetry = (c, failure, immediate, retryAfterMs) => {
|
|
101
|
+
c.attempt += 1;
|
|
102
|
+
c.episode ??= { cause: "dropped" };
|
|
103
|
+
const delay = immediate ? 0 : retryAfterMs ?? backoff(c.attempt);
|
|
104
|
+
if (c.hasSnapshot) setStatus(c, {
|
|
105
|
+
status: "reconnecting",
|
|
106
|
+
...c.episode,
|
|
107
|
+
attempt: c.attempt,
|
|
108
|
+
nextRetryAt: Date.now() + delay,
|
|
109
|
+
...failure !== void 0 && { lastError: failure }
|
|
110
|
+
});
|
|
111
|
+
return delay;
|
|
112
|
+
};
|
|
113
|
+
const reconnectConn = (c) => {
|
|
114
|
+
if (!c.lifetime) return;
|
|
115
|
+
if (c.connection.status === "gone") {
|
|
116
|
+
c.attempt = 0;
|
|
117
|
+
c.episode = { cause: "dropped" };
|
|
118
|
+
flushTapSync(() => {
|
|
119
|
+
if (c.hasSnapshot) scheduleRetry(c, void 0, true);
|
|
120
|
+
else setStatus(c, { status: "connecting" });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
c.gate.open();
|
|
124
|
+
c.abort?.abort();
|
|
125
|
+
};
|
|
126
|
+
const run = async (c, lifetime, gate) => {
|
|
127
|
+
const alive = () => c.lifetime === lifetime;
|
|
128
|
+
while (alive()) {
|
|
129
|
+
const abort = new AbortController();
|
|
130
|
+
c.abort = abort;
|
|
131
|
+
let failure;
|
|
132
|
+
let retryAfterMs;
|
|
133
|
+
let gone = false;
|
|
134
|
+
try {
|
|
135
|
+
gone = await c.attemptOnce(abort) === "stop";
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (!abort.signal.aborted) {
|
|
138
|
+
failure = error instanceof Error && error.message ? error.message : "network";
|
|
139
|
+
if (error instanceof Error && "retryAfterMs" in error && typeof error.retryAfterMs === "number") retryAfterMs = error.retryAfterMs;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
abort.abort();
|
|
143
|
+
if (!alive()) return;
|
|
144
|
+
if (gone) {
|
|
145
|
+
await gate.wait();
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const immediate = gate.take();
|
|
149
|
+
const delay = flushTapSync(() => scheduleRetry(c, failure, immediate, retryAfterMs));
|
|
150
|
+
if (delay === 0) continue;
|
|
151
|
+
await gate.wait(delay);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const startConn = (c) => {
|
|
155
|
+
c.hasSnapshot = false;
|
|
156
|
+
c.lease = null;
|
|
157
|
+
c.attempt = 0;
|
|
158
|
+
c.episode = null;
|
|
159
|
+
if (c.connection.status !== "connecting") setStatus(c, { status: "connecting" });
|
|
160
|
+
c.lifetime = new AbortController();
|
|
161
|
+
c.gate = createGate();
|
|
162
|
+
run(c, c.lifetime, c.gate);
|
|
163
|
+
};
|
|
164
|
+
const stopConn = (c) => {
|
|
165
|
+
c.abort?.abort();
|
|
166
|
+
c.lifetime?.abort();
|
|
167
|
+
c.lifetime = null;
|
|
168
|
+
c.gate.open();
|
|
169
|
+
};
|
|
170
|
+
const useStatewireConn = (options, ctx, create) => {
|
|
171
|
+
const latest = useRef({
|
|
172
|
+
options,
|
|
173
|
+
ctx
|
|
174
|
+
});
|
|
175
|
+
const [connection, setConnection] = useState(null);
|
|
176
|
+
const [c] = useState(() => create(latest, setConnection));
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
latest.current = {
|
|
179
|
+
options,
|
|
180
|
+
ctx
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
startConn(c);
|
|
185
|
+
return () => stopConn(c);
|
|
186
|
+
}, [c, options.url]);
|
|
187
|
+
return {
|
|
188
|
+
connection: connection ?? c.connection,
|
|
189
|
+
send: c.send
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
const classify = (timeout) => /* @__PURE__ */ new Error(timeout.aborted ? "timeout" : "network");
|
|
193
|
+
const parseRetryAfter = (header) => {
|
|
194
|
+
if (!header) return void 0;
|
|
195
|
+
const value = header.trim();
|
|
196
|
+
if (/^\d+$/.test(value)) return Number(value) * 1e3;
|
|
197
|
+
const date = Date.parse(value);
|
|
198
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
|
|
199
|
+
};
|
|
200
|
+
const overloadedError = (retryAfterMs) => Object.assign(/* @__PURE__ */ new Error("overloaded"), { retryAfterMs });
|
|
201
|
+
//#endregion
|
|
202
|
+
export { classify, createGate, deliverEnvelope, handleEnvelope, isTransientFinish, overloadedError, parseRetryAfter, reconnectConn, scheduleRetry, setStatus, settleEnvelope, useStatewireConn };
|
|
203
|
+
|
|
204
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","names":["useEffect","useRef","useState","flushTapSync","reviveOps","reviveResponse","reviveValue","backoff","ConnectionState","RemoteLink","StatewireConnection","StatewireTransportProps","Episode","cause","message","StatewireFinish","reason","payload","meta","StatewireResponse","seq","type","code","StatewireSyn","lastSeq","lease","StatewireEnvelope","ops","res","ack","syn","fin","createGate","latched","wake","take","was","wait","timeoutMs","Promise","resolve","timer","undefined","setTimeout","clearTimeout","open","Gate","ReturnType","ConnBase","connection","hasSnapshot","attempt","episode","gate","abort","AbortController","lifetime","latest","current","options","onChange","ctx","notify","reconnect","attemptOnce","setStatus","c","next","changed","status","deliverEnvelope","env","Pick","attached","map","onSnapshot","onOps","isTransientFinish","settleEnvelope","finish","rest","onFinish","handleEnvelope","scheduleRetry","failure","immediate","retryAfterMs","delay","nextRetryAt","Date","now","lastError","reconnectConn","run","alive","gone","error","signal","aborted","Error","startConn","stopConn","useStatewireConn","url","TOptions","create","send","setConnection","classify","timeout","AbortSignal","parseRetryAfter","header","value","trim","test","Number","date","parse","isNaN","Math","max","overloadedError","Object","assign"],"sources":["../../src/client/connection.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { reviveOps, reviveResponse, reviveValue } from \"../wire/meta\";\nimport { backoff } from \"./timing\";\nimport type {\n ConnectionState,\n RemoteLink,\n StatewireConnection,\n StatewireTransportProps,\n} from \"../transports/sse\";\n\nexport type Episode = {\n cause: \"dropped\" | \"ended\" | \"error\";\n message?: string;\n};\n\nexport type StatewireFinish = {\n reason: \"ended\" | \"error\" | \"gone\" | \"superseded\" | (string & {});\n message?: string;\n payload?: unknown;\n meta?: unknown;\n};\n\nexport type StatewireResponse = {\n seq: number;\n type: \"accepted\" | \"rejected\" | \"pending\" | \"crashed\" | \"result-unavailable\";\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n};\n\nexport type StatewireSyn = {\n lastSeq: number;\n lease?: string;\n};\n\nexport type StatewireEnvelope = {\n ops?: unknown[];\n res?: StatewireResponse[];\n ack?: number;\n syn?: StatewireSyn;\n fin?: StatewireFinish;\n};\n\nexport const createGate = () => {\n let latched = false;\n let wake: (() => void) | undefined;\n return {\n take: () => {\n const was = latched;\n latched = false;\n return was;\n },\n wait: (timeoutMs?: number) => {\n if (latched) {\n latched = false;\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer =\n timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n wake = undefined;\n resolve();\n }, timeoutMs);\n wake = () => {\n clearTimeout(timer);\n wake = undefined;\n resolve();\n };\n });\n },\n open: () => {\n if (wake) wake();\n else latched = true;\n },\n };\n};\n\nexport type Gate = ReturnType<typeof createGate>;\n\nexport type ConnBase = {\n connection: StatewireConnection;\n hasSnapshot: boolean;\n lease: string | null;\n attempt: number;\n episode: Episode | null;\n gate: Gate;\n abort: AbortController | null;\n lifetime: AbortController | null;\n latest: {\n readonly current: {\n options: { onChange?: (connection: StatewireConnection) => void };\n ctx: StatewireTransportProps;\n };\n };\n notify: (connection: StatewireConnection) => void;\n reconnect: () => void;\n attemptOnce: (\n abort: AbortController,\n ) => Promise<\"stop\" | \"reattach\" | \"ended\">;\n};\n\nexport const setStatus = (c: ConnBase, next: ConnectionState) => {\n const changed = next.status !== c.connection.status;\n c.connection = { ...next, reconnect: c.reconnect };\n c.notify(c.connection);\n if (changed) c.latest.current.options.onChange?.(c.connection);\n};\n\nexport const deliverEnvelope = (\n c: ConnBase,\n env: Pick<StatewireEnvelope, \"ops\" | \"res\" | \"ack\" | \"syn\">,\n attached: boolean,\n) => {\n if (env.syn?.lease !== undefined) c.lease = env.syn.lease;\n const ctx = c.latest.current.ctx;\n const ops = env.ops === undefined ? [] : reviveOps(env.ops);\n const res = env.res?.map(reviveResponse);\n if (!attached) {\n ctx.onSnapshot({ ops, lastSeq: env.syn?.lastSeq ?? -1 });\n } else if (\n env.ops !== undefined ||\n env.res !== undefined ||\n env.ack !== undefined\n ) {\n ctx.onOps({\n ops,\n ...(res !== undefined && { res }),\n ...(env.ack !== undefined && { ack: env.ack }),\n });\n }\n c.hasSnapshot = true;\n};\n\nexport const isTransientFinish = (\n reason: StatewireFinish[\"reason\"],\n): reason is \"ended\" | \"error\" => reason === \"ended\" || reason === \"error\";\n\nexport const settleEnvelope = (\n c: ConnBase,\n env: StatewireEnvelope,\n): \"stop\" | \"reattach\" | null => {\n let finish = env.fin;\n if (finish) {\n if (finish.meta !== undefined) {\n const { meta, ...rest } = finish;\n finish = { ...rest, payload: reviveValue(rest.payload, meta) };\n }\n c.latest.current.ctx.onFinish(finish);\n if (isTransientFinish(finish.reason)) {\n c.episode = {\n cause: finish.reason,\n ...(finish.message !== undefined && { message: finish.message }),\n };\n return \"reattach\";\n }\n setStatus(c, {\n status: \"gone\",\n ...(finish.message !== undefined && { message: finish.message }),\n ...(finish.payload !== undefined && { payload: finish.payload }),\n });\n return \"stop\";\n }\n if (c.connection.status !== \"live\") {\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"live\" });\n }\n return null;\n};\n\nexport const handleEnvelope = (\n c: ConnBase,\n env: StatewireEnvelope,\n attached: boolean,\n): \"stop\" | \"reattach\" | null => {\n deliverEnvelope(c, env, attached);\n return settleEnvelope(c, env);\n};\n\nexport const scheduleRetry = (\n c: ConnBase,\n failure: string | undefined,\n immediate: boolean,\n retryAfterMs?: number,\n) => {\n c.attempt += 1;\n c.episode ??= { cause: \"dropped\" };\n const delay = immediate ? 0 : (retryAfterMs ?? backoff(c.attempt));\n if (c.hasSnapshot) {\n setStatus(c, {\n status: \"reconnecting\",\n ...c.episode,\n attempt: c.attempt,\n nextRetryAt: Date.now() + delay,\n ...(failure !== undefined && { lastError: failure }),\n });\n }\n return delay;\n};\n\nexport const reconnectConn = (c: ConnBase) => {\n if (!c.lifetime) return;\n if (c.connection.status === \"gone\") {\n c.attempt = 0;\n c.episode = { cause: \"dropped\" };\n flushTapSync(() => {\n if (c.hasSnapshot) scheduleRetry(c, undefined, true);\n else setStatus(c, { status: \"connecting\" });\n });\n }\n c.gate.open();\n c.abort?.abort();\n};\n\nconst run = async (c: ConnBase, lifetime: AbortController, gate: Gate) => {\n const alive = () => c.lifetime === lifetime;\n while (alive()) {\n const abort = new AbortController();\n c.abort = abort;\n let failure: string | undefined;\n let retryAfterMs: number | undefined;\n let gone = false;\n try {\n gone = (await c.attemptOnce(abort)) === \"stop\";\n } catch (error) {\n if (!abort.signal.aborted) {\n failure =\n error instanceof Error && error.message ? error.message : \"network\";\n if (\n error instanceof Error &&\n \"retryAfterMs\" in error &&\n typeof error.retryAfterMs === \"number\"\n ) {\n retryAfterMs = error.retryAfterMs;\n }\n }\n }\n abort.abort();\n if (!alive()) return;\n if (gone) {\n await gate.wait();\n continue;\n }\n const immediate = gate.take();\n const delay = flushTapSync(() =>\n scheduleRetry(c, failure, immediate, retryAfterMs),\n );\n if (delay === 0) continue;\n await gate.wait(delay);\n }\n};\n\nconst startConn = (c: ConnBase) => {\n c.hasSnapshot = false;\n c.lease = null;\n c.attempt = 0;\n c.episode = null;\n if (c.connection.status !== \"connecting\") {\n setStatus(c, { status: \"connecting\" });\n }\n c.lifetime = new AbortController();\n c.gate = createGate();\n void run(c, c.lifetime, c.gate);\n};\n\nconst stopConn = (c: ConnBase) => {\n c.abort?.abort();\n c.lifetime?.abort();\n c.lifetime = null;\n c.gate.open();\n};\n\nexport const useStatewireConn = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireConnection) => void;\n },\n>(\n options: TOptions,\n ctx: StatewireTransportProps,\n create: (\n latest: {\n readonly current: {\n options: TOptions;\n ctx: StatewireTransportProps;\n };\n },\n notify: (connection: StatewireConnection) => void,\n ) => ConnBase & { send: RemoteLink[\"send\"] },\n): RemoteLink => {\n const latest = useRef({ options, ctx });\n const [connection, setConnection] = useState<StatewireConnection | null>(\n null,\n );\n const [c] = useState(() => create(latest, setConnection));\n\n useEffect(() => {\n latest.current = { options, ctx };\n });\n useEffect(() => {\n startConn(c);\n return () => stopConn(c);\n }, [c, options.url]);\n\n return {\n connection: connection ?? c.connection,\n send: c.send,\n };\n};\n\nexport const classify = (timeout: AbortSignal) =>\n new Error(timeout.aborted ? \"timeout\" : \"network\");\n\nexport const parseRetryAfter = (header: string | null): number | undefined => {\n if (!header) return undefined;\n const value = header.trim();\n if (/^\\d+$/.test(value)) return Number(value) * 1000;\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());\n};\n\nexport const overloadedError = (retryAfterMs?: number) =>\n Object.assign(new Error(\"overloaded\"), { retryAfterMs });\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StatewireConnection } from "../transports/sse.js";
|
|
2
|
+
import { StatewireCommandsProxy, StatewireOptions, StatewireTransport } from "../types.js";
|
|
3
|
+
//#region src/client/core.d.ts
|
|
4
|
+
declare const LIVE_CONNECTION: StatewireConnection;
|
|
5
|
+
type StatewireCore = ReturnType<typeof createStatewireCore>;
|
|
6
|
+
declare const createStatewireCore: (getOptions: () => StatewireOptions<any, any, any>) => {
|
|
7
|
+
attach: (next: StatewireTransport<unknown>) => void;
|
|
8
|
+
detach: () => void;
|
|
9
|
+
dispose: () => void;
|
|
10
|
+
getState: () => unknown;
|
|
11
|
+
getConnection: () => StatewireConnection;
|
|
12
|
+
commands: StatewireCommandsProxy<any>;
|
|
13
|
+
flush: () => void;
|
|
14
|
+
subscribe: (listener: () => void) => () => void;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { LIVE_CONNECTION, StatewireCore, createStatewireCore };
|
|
18
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","names":[],"sources":["../../src/client/core.ts"],"mappings":";;;cAkBa,iBAAiB;KA0ClB,gBAAgB,kBAAkB;cAEjC,sBACX,kBAAkB;EA8LI,SAAA,MAAA;;;;EAtGI,qBAAA;;;EA2LF,YAAA"}
|