@rayfold/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +50 -0
- package/cache.d.ts +115 -0
- package/cache.js +464 -0
- package/cache.js.map +1 -0
- package/client.d.ts +152 -0
- package/client.js +425 -0
- package/client.js.map +1 -0
- package/index.d.ts +6 -0
- package/index.js +7 -0
- package/index.js.map +1 -0
- package/offline.d.ts +61 -0
- package/offline.js +119 -0
- package/offline.js.map +1 -0
- package/package.json +46 -0
- package/transport.d.ts +38 -0
- package/transport.js +109 -0
- package/transport.js.map +1 -0
- package/types.d.ts +5 -0
- package/types.js +48 -0
- package/types.js.map +1 -0
- package/ws-transport.d.ts +16 -0
- package/ws-transport.js +154 -0
- package/ws-transport.js.map +1 -0
package/ws-transport.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { RbCodec } from "@rayfold/rb";
|
|
2
|
+
export function createWebSocketTransport(o) {
|
|
3
|
+
const WS = o.WebSocket ?? globalThis.WebSocket;
|
|
4
|
+
let socket = null;
|
|
5
|
+
let opening = null;
|
|
6
|
+
let nextId = 1;
|
|
7
|
+
const pending = new Set();
|
|
8
|
+
const codec = o.binary ? new RbCodec(o.binary) : null;
|
|
9
|
+
const encode = (v) => (codec ? codec.encode(v) : JSON.stringify(v));
|
|
10
|
+
const route = (f) => {
|
|
11
|
+
if (!("id" in f)) {
|
|
12
|
+
for (const p of pending)
|
|
13
|
+
p.push(f);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const p of pending) {
|
|
17
|
+
if (p.ids.has(f.id)) {
|
|
18
|
+
p.push(f);
|
|
19
|
+
if ("fin" in f && f.fin) {
|
|
20
|
+
p.ids.delete(f.id);
|
|
21
|
+
if (!p.ids.size)
|
|
22
|
+
p.close();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const connect = async () => {
|
|
28
|
+
if (socket && socket.readyState === WS.OPEN)
|
|
29
|
+
return socket;
|
|
30
|
+
if (opening)
|
|
31
|
+
return opening;
|
|
32
|
+
opening = (async () => {
|
|
33
|
+
const url = o.connectUrl ? await o.connectUrl() : o.url;
|
|
34
|
+
const ws = new WS(url, o.protocols ?? ["rayfold.0.1"]);
|
|
35
|
+
if (codec)
|
|
36
|
+
ws.binaryType = "arraybuffer";
|
|
37
|
+
await new Promise((res, rej) => {
|
|
38
|
+
ws.addEventListener("open", () => res(), { once: true });
|
|
39
|
+
ws.addEventListener("error", () => rej(new Error("WebSocket connection failed")), { once: true });
|
|
40
|
+
});
|
|
41
|
+
ws.addEventListener("message", (ev) => {
|
|
42
|
+
if (typeof ev.data === "string")
|
|
43
|
+
return route(JSON.parse(ev.data));
|
|
44
|
+
// a binary message holds length-prefixed RB frames
|
|
45
|
+
if (codec)
|
|
46
|
+
for (const f of codec.decodeFrames(new Uint8Array(ev.data)))
|
|
47
|
+
route(f);
|
|
48
|
+
});
|
|
49
|
+
ws.addEventListener("close", () => {
|
|
50
|
+
socket = null;
|
|
51
|
+
for (const p of pending) {
|
|
52
|
+
for (const id of p.ids)
|
|
53
|
+
p.push({ id, error: { code: "unavailable", message: "Connection closed" }, fin: true });
|
|
54
|
+
p.close();
|
|
55
|
+
}
|
|
56
|
+
pending.clear();
|
|
57
|
+
});
|
|
58
|
+
socket = ws;
|
|
59
|
+
opening = null;
|
|
60
|
+
return ws;
|
|
61
|
+
})();
|
|
62
|
+
return opening;
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
send(envelope, opts = {}) {
|
|
66
|
+
const queue = [];
|
|
67
|
+
let waiting = null;
|
|
68
|
+
let closed = false;
|
|
69
|
+
const p = {
|
|
70
|
+
ids: new Set(),
|
|
71
|
+
push: (f) => {
|
|
72
|
+
if (waiting) {
|
|
73
|
+
const w = waiting;
|
|
74
|
+
waiting = null;
|
|
75
|
+
w({ value: f, done: false });
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
queue.push(f);
|
|
79
|
+
},
|
|
80
|
+
close: () => {
|
|
81
|
+
closed = true;
|
|
82
|
+
pending.delete(p);
|
|
83
|
+
if (waiting) {
|
|
84
|
+
const w = waiting;
|
|
85
|
+
waiting = null;
|
|
86
|
+
w({ value: undefined, done: true });
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
// Remap ids so several batches can share the socket.
|
|
91
|
+
const map = new Map();
|
|
92
|
+
const remapped = { ...envelope, ops: envelope.ops.map((op) => {
|
|
93
|
+
const id = nextId++;
|
|
94
|
+
map.set(id, op.id);
|
|
95
|
+
p.ids.add(id);
|
|
96
|
+
return { ...op, args: remapRefs(op.args, envelope.ops, map), id };
|
|
97
|
+
}) };
|
|
98
|
+
const backMap = new Map([...map.entries()].map(([k, v]) => [k, v]));
|
|
99
|
+
pending.add(p);
|
|
100
|
+
const start = connect().then((ws) => ws.send(encode(remapped))).catch((e) => {
|
|
101
|
+
p.push({ error: { code: "unavailable", message: e.message }, fin: true });
|
|
102
|
+
p.close();
|
|
103
|
+
});
|
|
104
|
+
opts.signal?.addEventListener("abort", () => {
|
|
105
|
+
void start.then(() => {
|
|
106
|
+
for (const id of p.ids)
|
|
107
|
+
socket?.send(encode({ cancel: id }));
|
|
108
|
+
});
|
|
109
|
+
}, { once: true });
|
|
110
|
+
return {
|
|
111
|
+
[Symbol.asyncIterator]() {
|
|
112
|
+
return {
|
|
113
|
+
next: () => {
|
|
114
|
+
const deliver = (f) => ({ value: "id" in f ? { ...f, id: backMap.get(f.id) ?? f.id } : f, done: false });
|
|
115
|
+
if (queue.length)
|
|
116
|
+
return Promise.resolve(deliver(queue.shift()));
|
|
117
|
+
if (closed)
|
|
118
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
119
|
+
return new Promise((res) => (waiting = (r) => res(r.done ? r : deliver(r.value))));
|
|
120
|
+
},
|
|
121
|
+
return: () => {
|
|
122
|
+
for (const id of p.ids)
|
|
123
|
+
socket?.send(encode({ cancel: id }));
|
|
124
|
+
p.close();
|
|
125
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
close() {
|
|
132
|
+
socket?.close();
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Rewrite `{ $ref: "<oldId>.path" }` to the remapped id. */
|
|
137
|
+
function remapRefs(v, ops, map) {
|
|
138
|
+
if (v === null || typeof v !== "object")
|
|
139
|
+
return v;
|
|
140
|
+
if (Array.isArray(v))
|
|
141
|
+
return v.map((x) => remapRefs(x, ops, map));
|
|
142
|
+
const o = v;
|
|
143
|
+
if (typeof o["$ref"] === "string" && Object.keys(o).length === 1) {
|
|
144
|
+
const [idText, ...path] = o["$ref"].split(".");
|
|
145
|
+
const oldId = Number(idText);
|
|
146
|
+
const newId = [...map.entries()].find(([, old]) => old === oldId)?.[0];
|
|
147
|
+
return { $ref: `${newId ?? oldId}.${path.join(".")}` };
|
|
148
|
+
}
|
|
149
|
+
const out = {};
|
|
150
|
+
for (const [k, x] of Object.entries(o))
|
|
151
|
+
out[k] = remapRefs(x, ops, map);
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=ws-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-transport.js","sourceRoot":"","sources":["../src/ws-transport.ts"],"names":[],"mappings":"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","sourcesContent":["/** WebSocket transport: one socket, many concurrent batches, per-op cancel. Spec 04 §5. */\nimport type { Frame, RequestEnvelope } from \"@rayfold/server/protocol\";\nimport { RbCodec } from \"@rayfold/rb\";\nimport type { RayfoldSchemaIR } from \"@rayfold/schema\";\nimport type { Transport } from \"./transport.ts\";\n\nexport interface WsTransportOptions {\n url: string;\n /** Subprotocols; default [\"rayfold.0.1\"]. */\n protocols?: string[];\n /** WebSocket constructor (defaults to the global one). */\n WebSocket?: typeof WebSocket;\n /** Called to build a fresh socket URL (e.g. to append a token) before each connect. */\n connectUrl?: () => string | Promise<string>;\n /** Rayfold Binary: pass the schema IR (from /rayfold/manifest) to send and receive RB messages instead of JSON text. */\n binary?: RayfoldSchemaIR;\n}\n\ninterface Pending {\n ids: Set<number>;\n push: (f: Frame) => void;\n close: () => void;\n}\n\nexport function createWebSocketTransport(o: WsTransportOptions): Transport & { close(): void } {\n const WS = o.WebSocket ?? globalThis.WebSocket;\n let socket: WebSocket | null = null;\n let opening: Promise<WebSocket> | null = null;\n let nextId = 1;\n const pending = new Set<Pending>();\n const codec = o.binary ? new RbCodec(o.binary) : null;\n const encode = (v: unknown): string | Uint8Array<ArrayBuffer> => (codec ? (codec.encode(v) as Uint8Array<ArrayBuffer>) : JSON.stringify(v));\n\n const route = (f: Frame): void => {\n if (!(\"id\" in f)) {\n for (const p of pending) p.push(f);\n return;\n }\n for (const p of pending) {\n if (p.ids.has(f.id)) {\n p.push(f);\n if (\"fin\" in f && f.fin) {\n p.ids.delete(f.id);\n if (!p.ids.size) p.close();\n }\n }\n }\n };\n\n const connect = async (): Promise<WebSocket> => {\n if (socket && socket.readyState === WS.OPEN) return socket;\n if (opening) return opening;\n opening = (async () => {\n const url = o.connectUrl ? await o.connectUrl() : o.url;\n const ws = new WS(url, o.protocols ?? [\"rayfold.0.1\"]);\n if (codec) ws.binaryType = \"arraybuffer\";\n await new Promise<void>((res, rej) => {\n ws.addEventListener(\"open\", () => res(), { once: true });\n ws.addEventListener(\"error\", () => rej(new Error(\"WebSocket connection failed\")), { once: true });\n });\n ws.addEventListener(\"message\", (ev) => {\n if (typeof ev.data === \"string\") return route(JSON.parse(ev.data) as Frame);\n // a binary message holds length-prefixed RB frames\n if (codec) for (const f of codec.decodeFrames(new Uint8Array(ev.data as ArrayBuffer))) route(f as Frame);\n });\n ws.addEventListener(\"close\", () => {\n socket = null;\n for (const p of pending) {\n for (const id of p.ids) p.push({ id, error: { code: \"unavailable\", message: \"Connection closed\" }, fin: true });\n p.close();\n }\n pending.clear();\n });\n socket = ws;\n opening = null;\n return ws;\n })();\n return opening;\n };\n\n return {\n send(envelope: RequestEnvelope, opts = {}) {\n const queue: Frame[] = [];\n let waiting: ((r: IteratorResult<Frame>) => void) | null = null;\n let closed = false;\n const p: Pending = {\n ids: new Set(),\n push: (f) => {\n if (waiting) {\n const w = waiting;\n waiting = null;\n w({ value: f, done: false });\n } else queue.push(f);\n },\n close: () => {\n closed = true;\n pending.delete(p);\n if (waiting) {\n const w = waiting;\n waiting = null;\n w({ value: undefined as never, done: true });\n }\n },\n };\n // Remap ids so several batches can share the socket.\n const map = new Map<number, number>();\n const remapped: RequestEnvelope = { ...envelope, ops: envelope.ops.map((op) => {\n const id = nextId++;\n map.set(id, op.id);\n p.ids.add(id);\n return { ...op, args: remapRefs(op.args, envelope.ops, map) as Record<string, unknown>, id };\n }) };\n const backMap = new Map([...map.entries()].map(([k, v]) => [k, v]));\n pending.add(p);\n const start = connect().then((ws) => ws.send(encode(remapped))).catch((e: Error) => {\n p.push({ error: { code: \"unavailable\", message: e.message }, fin: true });\n p.close();\n });\n opts.signal?.addEventListener(\"abort\", () => {\n void start.then(() => {\n for (const id of p.ids) socket?.send(encode({ cancel: id }));\n });\n }, { once: true });\n return {\n [Symbol.asyncIterator](): AsyncIterator<Frame> {\n return {\n next: (): Promise<IteratorResult<Frame>> => {\n const deliver = (f: Frame): IteratorResult<Frame> => ({ value: \"id\" in f ? { ...f, id: backMap.get(f.id) ?? f.id } as Frame : f, done: false });\n if (queue.length) return Promise.resolve(deliver(queue.shift()!));\n if (closed) return Promise.resolve({ value: undefined as never, done: true });\n return new Promise((res) => (waiting = (r) => res(r.done ? r : deliver(r.value))));\n },\n return: (): Promise<IteratorResult<Frame>> => {\n for (const id of p.ids) socket?.send(encode({ cancel: id }));\n p.close();\n return Promise.resolve({ value: undefined as never, done: true });\n },\n };\n },\n };\n },\n close() {\n socket?.close();\n },\n };\n}\n\n/** Rewrite `{ $ref: \"<oldId>.path\" }` to the remapped id. */\nfunction remapRefs(v: unknown, ops: RequestEnvelope[\"ops\"], map: Map<number, number>): unknown {\n if (v === null || typeof v !== \"object\") return v;\n if (Array.isArray(v)) return v.map((x) => remapRefs(x, ops, map));\n const o = v as Record<string, unknown>;\n if (typeof o[\"$ref\"] === \"string\" && Object.keys(o).length === 1) {\n const [idText, ...path] = o[\"$ref\"].split(\".\");\n const oldId = Number(idText);\n const newId = [...map.entries()].find(([, old]) => old === oldId)?.[0];\n return { $ref: `${newId ?? oldId}.${path.join(\".\")}` };\n }\n const out: Record<string, unknown> = {};\n for (const [k, x] of Object.entries(o)) out[k] = remapRefs(x, ops, map);\n return out;\n}\n"]}
|