@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/client.d.ts ADDED
@@ -0,0 +1,152 @@
1
+ /** RayfoldClient: batches with refs, cache-coherent commands, live-updating watches. */
2
+ import type { Frame, RequestEnvelope, RequestOp, WireError } from "@rayfold/server/protocol";
3
+ import type { RayfoldSchemaIR } from "@rayfold/schema";
4
+ import { RayfoldCache, type OptimisticOp } from "./cache.js";
5
+ import { type QueueEvent, type QueueStorage, type QueuedCommand } from "./offline.js";
6
+ import type { Transport } from "./transport.js";
7
+ export declare class RayfoldClientError extends Error {
8
+ readonly code: string;
9
+ readonly type: string | undefined;
10
+ readonly data: unknown;
11
+ readonly path: string | undefined;
12
+ readonly retryable: boolean;
13
+ constructor(w: WireError);
14
+ /** Narrow on a declared domain error: `if (e.is("OutOfStock")) e.data.available` */
15
+ is(type: string): boolean;
16
+ }
17
+ export interface OpOptions {
18
+ shape?: string;
19
+ vars?: Record<string, unknown>;
20
+ key?: string;
21
+ deadline?: number;
22
+ simulate?: boolean;
23
+ live?: boolean;
24
+ /** Conditional write: the entity version last seen; a stale value fails with VersionConflict carrying the current entity. */
25
+ ifVersion?: string | number;
26
+ }
27
+ export interface CommandOptions extends OpOptions {
28
+ /**
29
+ * The change the command is expected to make (sub-profile `sync`, spec 08 section 5), shown in the cache at once. The
30
+ * server's own patch replaces it when the command succeeds; it is rolled back when the command fails. A function
31
+ * gets the cache, to compute the prediction from current values.
32
+ */
33
+ optimistic?: OptimisticOp[] | ((cache: RayfoldCache) => OptimisticOp[]);
34
+ }
35
+ export interface QueryOptions extends OpOptions {
36
+ /** "network" (default) always fetches; "cache" serves a fresh cached result when present. */
37
+ policy?: "network" | "cache";
38
+ }
39
+ export interface ClientOptions {
40
+ transport: Transport;
41
+ cache?: RayfoldCache;
42
+ /** Sent as meta.client, e.g. "web/1.2.0". */
43
+ client?: string;
44
+ /** Batch deadline in ms. */
45
+ deadline?: number;
46
+ /** Idempotency key generator for commands without an explicit key. */
47
+ keyGen?: () => string;
48
+ now?: () => number;
49
+ /** Schema IR (from /rayfold/manifest). Enables compact frames: the server omits redundant `$type`/`meta`, the client restores types. */
50
+ schema?: RayfoldSchemaIR;
51
+ /**
52
+ * Queue commands made while the server cannot be reached (sub-profile `sync`) and send them later, in order and with
53
+ * their idempotency keys: on `drain()`, and when the browser comes back online. Their predictions stay shown meanwhile.
54
+ */
55
+ offline?: {
56
+ storage?: QueueStorage;
57
+ drainOnReconnect?: boolean;
58
+ };
59
+ }
60
+ /** A handle to an op inside a batch. `ref("id")` produces a `$ref` usable in later ops' args. */
61
+ export declare class OpHandle<T = unknown> {
62
+ readonly id: number;
63
+ readonly req: RequestOp;
64
+ readonly promise: Promise<T>;
65
+ private resolveFn;
66
+ private rejectFn;
67
+ readonly frames: Frame[];
68
+ constructor(id: number, req: RequestOp);
69
+ ref(path: string): {
70
+ $ref: string;
71
+ };
72
+ /** @internal */
73
+ _resolve(v: T): void;
74
+ /** @internal */
75
+ _reject(e: unknown): void;
76
+ }
77
+ export interface BatchResult {
78
+ frames: Frame[];
79
+ }
80
+ export declare class Batch {
81
+ private readonly client;
82
+ private readonly ops;
83
+ private nextId;
84
+ constructor(client: RayfoldClient);
85
+ query<T = unknown>(op: string, args?: Record<string, unknown>, o?: OpOptions): OpHandle<T>;
86
+ command<T = unknown>(op: string, args?: Record<string, unknown>, o?: OpOptions): OpHandle<T>;
87
+ private add;
88
+ /** Send the batch; every handle's promise settles as its frames arrive. */
89
+ run(opts?: {
90
+ signal?: AbortSignal;
91
+ onFrame?: (f: Frame) => void;
92
+ }): Promise<BatchResult>;
93
+ }
94
+ export declare class RayfoldClient {
95
+ private readonly opts;
96
+ readonly cache: RayfoldCache;
97
+ private readonly keyGen;
98
+ private readonly now;
99
+ constructor(opts: ClientOptions);
100
+ private readonly queue;
101
+ /** With a schema, every op is sent compact and types are restored on arrival. */
102
+ /** @internal */
103
+ prepare(req: RequestOp): RequestOp;
104
+ private typed;
105
+ newKey(): string;
106
+ batch(): Batch;
107
+ /** One query; returns the denormalized result. Subsequent reads of the same entities stay coherent via the cache. */
108
+ query<T = unknown>(op: string, args?: Record<string, unknown>, o?: QueryOptions): Promise<T>;
109
+ command<T = unknown>(op: string, args?: Record<string, unknown>, o?: CommandOptions): Promise<T>;
110
+ /** Drops the command's prediction and returns its result as the server left it, not as it was predicted. */
111
+ private settled;
112
+ /** Orders commands by when they were made, across reloads too: it starts from the clock. */
113
+ private nextSeq;
114
+ private sendCommand;
115
+ /** Commands waiting for the server (option `offline`), oldest first. */
116
+ get queued(): readonly QueuedCommand[];
117
+ /** Sends the waiting commands in order; resolves to how many still wait because the server is still unreachable. */
118
+ drain(): Promise<number>;
119
+ /** Follows the queue: a command queued, sent, or refused by the server when it finally went out. */
120
+ onQueue(fn: (e: QueueEvent) => void): () => void;
121
+ /** Stream items; ends when the server sends fin or the signal aborts. */
122
+ stream<T = unknown>(op: string, args?: Record<string, unknown>, o?: OpOptions & {
123
+ signal?: AbortSignal;
124
+ }): AsyncIterable<T>;
125
+ /**
126
+ * Watch a query: `fn` receives the current denormalized data now and again whenever a later command's
127
+ * patch (or another query) changes any entity the result contains. No refetch involved. `onError` gets the
128
+ * initial fetch's failure; without it the failure is dropped.
129
+ */
130
+ watch<T = unknown>(op: string, args: Record<string, unknown>, o: QueryOptions, fn: (data: T) => void, onError?: (e: unknown) => void): () => void;
131
+ /**
132
+ * Live query (extension `live`): the server keeps the query open and pushes patches. `fn` receives the
133
+ * current data now and after every server-side change. `onError` gets an error frame or a failed connection
134
+ * (not the abort from unsubscribing). Returns an unsubscribe function.
135
+ */
136
+ live<T = unknown>(op: string, args: Record<string, unknown>, o: OpOptions, fn: (data: T, meta: {
137
+ initial: boolean;
138
+ }) => void, onError?: (e: unknown) => void): () => void;
139
+ /** @internal */
140
+ envelope(ops: RequestOp[]): RequestEnvelope;
141
+ /** @internal */
142
+ runBatch(handles: OpHandle[], opts: {
143
+ signal?: AbortSignal;
144
+ onFrame?: (f: Frame) => void;
145
+ }): Promise<BatchResult>;
146
+ private readonly opKinds;
147
+ private readonly schema;
148
+ /** Ops are assumed safe when the transport is used with a schema-aware hint; default: name-based heuristic overridden by `markQueries`. */
149
+ private isQuery;
150
+ /** Tell the client which op names are queries so all-query batches go over the safe method. */
151
+ markQueries(names: string[]): void;
152
+ }
package/client.js ADDED
@@ -0,0 +1,425 @@
1
+ import { annotation } from "@rayfold/schema";
2
+ import { RayfoldCache } from "./cache.js";
3
+ import { OfflineQueue, isUnreachable, memoryQueue } from "./offline.js";
4
+ import { restoreTypes, typeAtPath } from "./types.js";
5
+ export class RayfoldClientError extends Error {
6
+ code;
7
+ type;
8
+ data;
9
+ path;
10
+ retryable;
11
+ constructor(w) {
12
+ super(w.message);
13
+ this.name = "RayfoldClientError";
14
+ this.code = w.code;
15
+ this.type = w.type;
16
+ this.data = w.data;
17
+ this.path = w.path;
18
+ this.retryable = w.retryable ?? ["unavailable", "deadline_exceeded", "aborted"].includes(w.code);
19
+ }
20
+ /** Narrow on a declared domain error: `if (e.is("OutOfStock")) e.data.available` */
21
+ is(type) {
22
+ return this.code === "domain" && this.type === type;
23
+ }
24
+ }
25
+ /** A handle to an op inside a batch. `ref("id")` produces a `$ref` usable in later ops' args. */
26
+ export class OpHandle {
27
+ id;
28
+ req;
29
+ promise;
30
+ resolveFn;
31
+ rejectFn;
32
+ frames = [];
33
+ constructor(id, req) {
34
+ this.id = id;
35
+ this.req = req;
36
+ this.promise = new Promise((res, rej) => {
37
+ this.resolveFn = res;
38
+ this.rejectFn = rej;
39
+ });
40
+ this.promise.catch(() => { }); // avoid unhandled rejections when the caller ignores this handle
41
+ }
42
+ ref(path) {
43
+ return { $ref: `${this.id}.${path}` };
44
+ }
45
+ /** @internal */
46
+ _resolve(v) {
47
+ this.resolveFn(v);
48
+ }
49
+ /** @internal */
50
+ _reject(e) {
51
+ this.rejectFn(e);
52
+ }
53
+ }
54
+ export class Batch {
55
+ client;
56
+ ops = [];
57
+ nextId = 1;
58
+ constructor(client) {
59
+ this.client = client;
60
+ }
61
+ query(op, args = {}, o = {}) {
62
+ return this.add({ id: this.nextId++, op, args, ...pick(o) });
63
+ }
64
+ command(op, args = {}, o = {}) {
65
+ const req = { id: this.nextId++, op, args, ...pick(o) };
66
+ if (req.key === undefined)
67
+ req.key = this.client.newKey();
68
+ return this.add(req);
69
+ }
70
+ add(req) {
71
+ const h = new OpHandle(req.id, this.client.prepare(req));
72
+ this.ops.push(h);
73
+ return h;
74
+ }
75
+ /** Send the batch; every handle's promise settles as its frames arrive. */
76
+ async run(opts = {}) {
77
+ return this.client.runBatch(this.ops, opts);
78
+ }
79
+ }
80
+ export class RayfoldClient {
81
+ opts;
82
+ cache;
83
+ keyGen;
84
+ now;
85
+ constructor(opts) {
86
+ this.opts = opts;
87
+ this.now = opts.now ?? Date.now;
88
+ this.cache = opts.cache ?? new RayfoldCache(this.now, mergePolicyOf(opts.schema));
89
+ this.keyGen = opts.keyGen ?? (() => (globalThis.crypto?.randomUUID?.() ?? `${this.now().toString(36)}-${Math.random().toString(36).slice(2)}`).replace(/-/g, ""));
90
+ this.schema = opts.schema;
91
+ if (this.schema)
92
+ for (const op of Object.values(this.schema.ops))
93
+ if (op.kind === "query")
94
+ this.opKinds.set(op.name, "query");
95
+ if (opts.offline) {
96
+ const queue = new OfflineQueue(opts.offline.storage ?? memoryQueue(), (c) => this.sendCommand(c).then((r) => this.settled(c, r)), (c) => this.cache.removeLayer(c.key), (c) => {
97
+ if (c.optimistic?.length)
98
+ this.cache.addLayer(c.key, c.optimistic);
99
+ });
100
+ this.queue = queue;
101
+ const target = globalThis;
102
+ if (opts.offline.drainOnReconnect !== false && typeof target.addEventListener === "function")
103
+ target.addEventListener("online", () => void queue.drain());
104
+ }
105
+ }
106
+ queue;
107
+ /** With a schema, every op is sent compact and types are restored on arrival. */
108
+ /** @internal */
109
+ prepare(req) {
110
+ return this.schema ? { ...req, compact: true } : req;
111
+ }
112
+ typed(op, data, at) {
113
+ if (!this.schema)
114
+ return data;
115
+ const def = this.schema.ops[op];
116
+ if (!def)
117
+ return data;
118
+ const t = at ? typeAtPath(this.schema, def.returns, at) : def.returns;
119
+ return t ? restoreTypes(this.schema, t, data) : data;
120
+ }
121
+ newKey() {
122
+ return this.keyGen();
123
+ }
124
+ batch() {
125
+ return new Batch(this);
126
+ }
127
+ /** One query; returns the denormalized result. Subsequent reads of the same entities stay coherent via the cache. */
128
+ async query(op, args = {}, o = {}) {
129
+ const rk = RayfoldCache.resultKey(op, args, o.shape, o.vars);
130
+ if (o.policy === "cache") {
131
+ const cached = this.cache.getResult(rk);
132
+ if (cached && !cached.stale && ![...cached.keys].some((k) => this.cache.isStale(k)))
133
+ return this.cache.denormalize(cached.data);
134
+ }
135
+ const b = this.batch();
136
+ const h = b.query(op, args, o);
137
+ await b.run();
138
+ return h.promise;
139
+ }
140
+ async command(op, args = {}, o = {}) {
141
+ const { optimistic, ...options } = o;
142
+ const predicted = typeof optimistic === "function" ? optimistic(this.cache) : optimistic;
143
+ const command = { key: o.key ?? this.newKey(), op, args, options, queuedAt: this.now(), seq: this.nextSeq++, ...(predicted?.length ? { optimistic: predicted } : {}) };
144
+ if (predicted?.length)
145
+ this.cache.addLayer(command.key, predicted);
146
+ if (this.queue) {
147
+ await this.queue.restored;
148
+ // behind the commands still waiting, so the server sees them in the order they were made
149
+ if (this.queue.size)
150
+ return this.queue.add(command);
151
+ }
152
+ try {
153
+ return this.settled(command, await this.sendCommand(command));
154
+ }
155
+ catch (e) {
156
+ if (this.queue && isUnreachable(e))
157
+ return this.queue.add(command); // its prediction stays until it is sent
158
+ this.cache.removeLayer(command.key);
159
+ throw e;
160
+ }
161
+ }
162
+ /** Drops the command's prediction and returns its result as the server left it, not as it was predicted. */
163
+ settled(c, result) {
164
+ if (!c.optimistic?.length)
165
+ return result;
166
+ this.cache.removeLayer(c.key);
167
+ const stored = this.cache.getResult(RayfoldCache.resultKey(c.op, c.args, c.options.shape, c.options.vars));
168
+ return stored ? this.cache.denormalize(stored.data) : result;
169
+ }
170
+ /** Orders commands by when they were made, across reloads too: it starts from the clock. */
171
+ nextSeq = Date.now();
172
+ async sendCommand(c) {
173
+ const b = this.batch();
174
+ const h = b.command(c.op, c.args, { ...c.options, key: c.key });
175
+ await b.run();
176
+ return h.promise;
177
+ }
178
+ /** Commands waiting for the server (option `offline`), oldest first. */
179
+ get queued() {
180
+ return this.queue?.commands ?? [];
181
+ }
182
+ /** Sends the waiting commands in order; resolves to how many still wait because the server is still unreachable. */
183
+ drain() {
184
+ return this.queue ? this.queue.drain() : Promise.resolve(0);
185
+ }
186
+ /** Follows the queue: a command queued, sent, or refused by the server when it finally went out. */
187
+ onQueue(fn) {
188
+ return this.queue ? this.queue.subscribe(fn) : () => { };
189
+ }
190
+ /** Stream items; ends when the server sends fin or the signal aborts. */
191
+ stream(op, args = {}, o = {}) {
192
+ const client = this;
193
+ return (async function* () {
194
+ const req = client.prepare({ id: 1, op, args, ...pick(o) });
195
+ const sendOpts = {};
196
+ if (o.signal)
197
+ sendOpts.signal = o.signal;
198
+ for await (const f of client.opts.transport.send(client.envelope([req]), sendOpts)) {
199
+ if ("item" in f)
200
+ yield client.cache.denormalize(client.cache.normalize(client.typed(op, f.item)));
201
+ else if ("error" in f) {
202
+ if (f.error.code === "canceled" && o.signal?.aborted)
203
+ return;
204
+ throw new RayfoldClientError(f.error);
205
+ }
206
+ else if ("fin" in f && f.fin)
207
+ return;
208
+ }
209
+ })();
210
+ }
211
+ /**
212
+ * Watch a query: `fn` receives the current denormalized data now and again whenever a later command's
213
+ * patch (or another query) changes any entity the result contains. No refetch involved. `onError` gets the
214
+ * initial fetch's failure; without it the failure is dropped.
215
+ */
216
+ watch(op, args, o, fn, onError) {
217
+ const rk = RayfoldCache.resultKey(op, args, o.shape, o.vars);
218
+ let active = true;
219
+ let ready = false; // ignore the cache events produced by the initial fetch itself
220
+ let seen; // the stored result `fn` last reported
221
+ // This result changed when it was replaced (a refetch), when an entity it holds changed, or when its op was
222
+ // invalidated. Another result of the same op changing is not a reason: the event's `ops` alone would say so.
223
+ const listener = ({ keys, ops }) => {
224
+ const r = this.cache.getResult(rk);
225
+ if (!r || !active || !ready)
226
+ return;
227
+ const hit = r !== seen || [...keys].some((k) => r.keys.has(k)) || (ops.has(op) && r.stale);
228
+ if (!hit)
229
+ return;
230
+ seen = r;
231
+ fn(this.cache.denormalize(r.data));
232
+ };
233
+ const off = this.cache.subscribe(listener);
234
+ void this.query(op, args, o).then((d) => {
235
+ ready = true;
236
+ seen = this.cache.getResult(rk);
237
+ if (active)
238
+ fn(d);
239
+ }, (e) => {
240
+ if (active)
241
+ onError?.(e);
242
+ });
243
+ return () => {
244
+ active = false;
245
+ off();
246
+ };
247
+ }
248
+ /**
249
+ * Live query (extension `live`): the server keeps the query open and pushes patches. `fn` receives the
250
+ * current data now and after every server-side change. `onError` gets an error frame or a failed connection
251
+ * (not the abort from unsubscribing). Returns an unsubscribe function.
252
+ */
253
+ live(op, args, o, fn, onError) {
254
+ const ac = new AbortController();
255
+ const rk = RayfoldCache.resultKey(op, args, o.shape, o.vars);
256
+ let initial = true;
257
+ const b = this.batch();
258
+ const h = b.query(op, args, { ...o, live: true });
259
+ void b
260
+ .run({
261
+ signal: ac.signal,
262
+ onFrame: (f) => {
263
+ if (!("id" in f) || f.id !== h.id)
264
+ return;
265
+ if ("error" in f) {
266
+ // after unsubscribing, the stream ends with a "canceled" frame: that is the stop, not a failure
267
+ if (!ac.signal.aborted)
268
+ onError?.(new RayfoldClientError(f.error));
269
+ return;
270
+ }
271
+ if (("data" in f && !("at" in f)) || "patch" in f || "fin" in f) {
272
+ const r = this.cache.getResult(rk);
273
+ if (!r)
274
+ return;
275
+ fn(this.cache.denormalize(r.data), { initial });
276
+ initial = false;
277
+ }
278
+ },
279
+ })
280
+ .catch((e) => {
281
+ if (!ac.signal.aborted)
282
+ onError?.(e);
283
+ });
284
+ return () => ac.abort();
285
+ }
286
+ /** @internal */
287
+ envelope(ops) {
288
+ const env = { rayfold: "0.1", ops };
289
+ const meta = {};
290
+ if (this.opts.client)
291
+ meta.client = this.opts.client;
292
+ if (this.opts.deadline !== undefined)
293
+ meta.deadline = this.opts.deadline;
294
+ if (Object.keys(meta).length)
295
+ env.meta = meta;
296
+ return env;
297
+ }
298
+ /** @internal */
299
+ async runBatch(handles, opts) {
300
+ const byId = new Map(handles.map((h) => [h.id, h]));
301
+ const safe = handles.every((h) => this.isQuery(h.req.op));
302
+ const sendOpts = { safe };
303
+ if (opts.signal)
304
+ sendOpts.signal = opts.signal;
305
+ const frames = [];
306
+ const settled = new Set();
307
+ const resultKeys = new Map();
308
+ for (const h of handles)
309
+ resultKeys.set(h.id, RayfoldCache.resultKey(h.req.op, h.req.args, h.req.shape, h.req.vars));
310
+ const resolve = (h, v) => {
311
+ settled.add(h.id);
312
+ h._resolve(v);
313
+ };
314
+ const reject = (h, e) => {
315
+ settled.add(h.id);
316
+ h._reject(e);
317
+ };
318
+ try {
319
+ for await (const f of this.opts.transport.send(this.envelope(handles.map((h) => h.req)), sendOpts)) {
320
+ frames.push(f);
321
+ if (!("id" in f)) {
322
+ const err = new RayfoldClientError(f.error);
323
+ for (const h of handles)
324
+ if (!settled.has(h.id))
325
+ reject(h, err);
326
+ opts.onFrame?.(f);
327
+ continue;
328
+ }
329
+ const h = byId.get(f.id);
330
+ if (!h)
331
+ continue;
332
+ h.frames.push(f);
333
+ if ("error" in f) {
334
+ const current = f.error.type === "VersionConflict" ? f.error.data?.current : undefined;
335
+ if (current)
336
+ this.cache.mergeEntities(current);
337
+ reject(h, new RayfoldClientError(f.error));
338
+ }
339
+ else if ("ok" in f) {
340
+ let r;
341
+ this.cache.transaction(() => {
342
+ r = this.cache.putResult(resultKeys.get(h.id), h.req.op, this.typed(h.req.op, f.ok));
343
+ if (f.patch)
344
+ this.cache.applyPatch(f.patch);
345
+ });
346
+ resolve(h, this.cache.denormalize(r.data));
347
+ }
348
+ else if ("data" in f && !("at" in f)) {
349
+ const r = this.cache.putResult(resultKeys.get(h.id), h.req.op, this.typed(h.req.op, f.data));
350
+ if (f.fin)
351
+ resolve(h, this.cache.denormalize(r.data));
352
+ }
353
+ else if ("at" in f) {
354
+ this.cache.mergeAt(resultKeys.get(h.id), f.at, this.typed(h.req.op, f.data, f.at));
355
+ }
356
+ else if ("patch" in f) {
357
+ // a live update: `at` and `list` ops describe this op's own stored result
358
+ this.cache.applyPatch(f.patch, resultKeys.get(h.id));
359
+ }
360
+ else if ("fin" in f && f.fin && !settled.has(h.id)) {
361
+ const r = this.cache.getResult(resultKeys.get(h.id));
362
+ resolve(h, r ? this.cache.denormalize(r.data) : undefined);
363
+ }
364
+ opts.onFrame?.(f); // after the cache has absorbed the frame
365
+ }
366
+ }
367
+ catch (e) {
368
+ for (const h of handles)
369
+ if (!settled.has(h.id))
370
+ reject(h, e);
371
+ throw e;
372
+ }
373
+ for (const h of handles)
374
+ if (!settled.has(h.id))
375
+ reject(h, new RayfoldClientError({ code: "unavailable", message: "Batch ended without a result for this op" }));
376
+ return { frames };
377
+ }
378
+ opKinds = new Map();
379
+ schema;
380
+ /** Ops are assumed safe when the transport is used with a schema-aware hint; default: name-based heuristic overridden by `markQueries`. */
381
+ isQuery(op) {
382
+ return this.opKinds.get(op) === "query";
383
+ }
384
+ /** Tell the client which op names are queries so all-query batches go over the safe method. */
385
+ markQueries(names) {
386
+ for (const n of names)
387
+ this.opKinds.set(n, "query");
388
+ }
389
+ }
390
+ function pick(o) {
391
+ const out = {};
392
+ if (o.shape !== undefined)
393
+ out.shape = o.shape;
394
+ if (o.vars !== undefined)
395
+ out.vars = o.vars;
396
+ if (o.key !== undefined)
397
+ out.key = o.key;
398
+ if (o.deadline !== undefined)
399
+ out.deadline = o.deadline;
400
+ if (o.simulate !== undefined)
401
+ out.simulate = o.simulate;
402
+ if (o.live !== undefined)
403
+ out.live = o.live;
404
+ if (o.ifVersion !== undefined)
405
+ out.ifVersion = o.ifVersion;
406
+ return out;
407
+ }
408
+ /** A field's `@merge` policy from the schema, memoised; without a schema there is no policy to read. */
409
+ function mergePolicyOf(schema) {
410
+ if (!schema)
411
+ return () => undefined;
412
+ const seen = new Map();
413
+ return (type, field) => {
414
+ const key = `${type}.${field}`;
415
+ if (seen.has(key))
416
+ return seen.get(key);
417
+ const def = schema.types[type];
418
+ const f = def && "fields" in def ? def.fields.find((x) => x.name === field) : undefined;
419
+ const value = f ? annotation(f, "merge")?.args["value"] : undefined;
420
+ const policy = value && typeof value === "object" && "$ident" in value ? String(value.$ident) : undefined;
421
+ seen.set(key, policy);
422
+ return policy;
423
+ };
424
+ }
425
+ //# sourceMappingURL=client.js.map
package/client.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["/** RayfoldClient: batches with refs, cache-coherent commands, live-updating watches. */\nimport type { Frame, PatchOp, RequestEnvelope, RequestOp, WireError } from \"@rayfold/server/protocol\";\nimport type { RayfoldSchemaIR } from \"@rayfold/schema\";\nimport { annotation } from \"@rayfold/schema\";\nimport { RayfoldCache, type CacheListener, type CachedResult, type MergePolicy, type OptimisticOp } from \"./cache.ts\";\nimport { OfflineQueue, isUnreachable, memoryQueue, type QueueEvent, type QueueStorage, type QueuedCommand } from \"./offline.ts\";\nimport type { Transport } from \"./transport.ts\";\nimport { restoreTypes, typeAtPath } from \"./types.ts\";\n\nexport class RayfoldClientError extends Error {\n readonly code: string;\n readonly type: string | undefined;\n readonly data: unknown;\n readonly path: string | undefined;\n readonly retryable: boolean;\n constructor(w: WireError) {\n super(w.message);\n this.name = \"RayfoldClientError\";\n this.code = w.code;\n this.type = w.type;\n this.data = w.data;\n this.path = w.path;\n this.retryable = w.retryable ?? [\"unavailable\", \"deadline_exceeded\", \"aborted\"].includes(w.code);\n }\n /** Narrow on a declared domain error: `if (e.is(\"OutOfStock\")) e.data.available` */\n is(type: string): boolean {\n return this.code === \"domain\" && this.type === type;\n }\n}\n\nexport interface OpOptions {\n shape?: string;\n vars?: Record<string, unknown>;\n key?: string;\n deadline?: number;\n simulate?: boolean;\n live?: boolean;\n /** Conditional write: the entity version last seen; a stale value fails with VersionConflict carrying the current entity. */\n ifVersion?: string | number;\n}\n\nexport interface CommandOptions extends OpOptions {\n /**\n * The change the command is expected to make (sub-profile `sync`, spec 08 section 5), shown in the cache at once. The\n * server's own patch replaces it when the command succeeds; it is rolled back when the command fails. A function\n * gets the cache, to compute the prediction from current values.\n */\n optimistic?: OptimisticOp[] | ((cache: RayfoldCache) => OptimisticOp[]);\n}\n\nexport interface QueryOptions extends OpOptions {\n /** \"network\" (default) always fetches; \"cache\" serves a fresh cached result when present. */\n policy?: \"network\" | \"cache\";\n}\n\nexport interface ClientOptions {\n transport: Transport;\n cache?: RayfoldCache;\n /** Sent as meta.client, e.g. \"web/1.2.0\". */\n client?: string;\n /** Batch deadline in ms. */\n deadline?: number;\n /** Idempotency key generator for commands without an explicit key. */\n keyGen?: () => string;\n now?: () => number;\n /** Schema IR (from /rayfold/manifest). Enables compact frames: the server omits redundant `$type`/`meta`, the client restores types. */\n schema?: RayfoldSchemaIR;\n /**\n * Queue commands made while the server cannot be reached (sub-profile `sync`) and send them later, in order and with\n * their idempotency keys: on `drain()`, and when the browser comes back online. Their predictions stay shown meanwhile.\n */\n offline?: { storage?: QueueStorage; drainOnReconnect?: boolean };\n}\n\n/** A handle to an op inside a batch. `ref(\"id\")` produces a `$ref` usable in later ops' args. */\nexport class OpHandle<T = unknown> {\n readonly promise: Promise<T>;\n private resolveFn!: (v: T) => void;\n private rejectFn!: (e: unknown) => void;\n readonly frames: Frame[] = [];\n constructor(\n readonly id: number,\n readonly req: RequestOp,\n ) {\n this.promise = new Promise<T>((res, rej) => {\n this.resolveFn = res;\n this.rejectFn = rej;\n });\n this.promise.catch(() => {}); // avoid unhandled rejections when the caller ignores this handle\n }\n ref(path: string): { $ref: string } {\n return { $ref: `${this.id}.${path}` };\n }\n /** @internal */\n _resolve(v: T): void {\n this.resolveFn(v);\n }\n /** @internal */\n _reject(e: unknown): void {\n this.rejectFn(e);\n }\n}\n\nexport interface BatchResult {\n frames: Frame[];\n}\n\nexport class Batch {\n private readonly ops: OpHandle[] = [];\n private nextId = 1;\n constructor(private readonly client: RayfoldClient) {}\n\n query<T = unknown>(op: string, args: Record<string, unknown> = {}, o: OpOptions = {}): OpHandle<T> {\n return this.add<T>({ id: this.nextId++, op, args, ...pick(o) });\n }\n command<T = unknown>(op: string, args: Record<string, unknown> = {}, o: OpOptions = {}): OpHandle<T> {\n const req: RequestOp = { id: this.nextId++, op, args, ...pick(o) };\n if (req.key === undefined) req.key = this.client.newKey();\n return this.add<T>(req);\n }\n private add<T>(req: RequestOp): OpHandle<T> {\n const h = new OpHandle<T>(req.id, this.client.prepare(req));\n this.ops.push(h as unknown as OpHandle);\n return h;\n }\n /** Send the batch; every handle's promise settles as its frames arrive. */\n async run(opts: { signal?: AbortSignal; onFrame?: (f: Frame) => void } = {}): Promise<BatchResult> {\n return this.client.runBatch(this.ops, opts);\n }\n}\n\nexport class RayfoldClient {\n readonly cache: RayfoldCache;\n private readonly keyGen: () => string;\n private readonly now: () => number;\n\n constructor(private readonly opts: ClientOptions) {\n this.now = opts.now ?? Date.now;\n this.cache = opts.cache ?? new RayfoldCache(this.now, mergePolicyOf(opts.schema));\n this.keyGen = opts.keyGen ?? (() => (globalThis.crypto?.randomUUID?.() ?? `${this.now().toString(36)}-${Math.random().toString(36).slice(2)}`).replace(/-/g, \"\"));\n this.schema = opts.schema;\n if (this.schema) for (const op of Object.values(this.schema.ops)) if (op.kind === \"query\") this.opKinds.set(op.name, \"query\");\n if (opts.offline) {\n const queue = new OfflineQueue(\n opts.offline.storage ?? memoryQueue(),\n (c) => this.sendCommand(c).then((r) => this.settled(c, r)),\n (c) => this.cache.removeLayer(c.key),\n (c) => {\n if (c.optimistic?.length) this.cache.addLayer(c.key, c.optimistic);\n },\n );\n this.queue = queue;\n const target = globalThis as { addEventListener?: (type: string, fn: () => void) => void };\n if (opts.offline.drainOnReconnect !== false && typeof target.addEventListener === \"function\") target.addEventListener(\"online\", () => void queue.drain());\n }\n }\n\n private readonly queue: OfflineQueue | undefined;\n\n /** With a schema, every op is sent compact and types are restored on arrival. */\n /** @internal */\n prepare(req: RequestOp): RequestOp {\n return this.schema ? { ...req, compact: true } : req;\n }\n private typed(op: string, data: unknown, at?: string): unknown {\n if (!this.schema) return data;\n const def = this.schema.ops[op];\n if (!def) return data;\n const t = at ? typeAtPath(this.schema, def.returns, at) : def.returns;\n return t ? restoreTypes(this.schema, t, data) : data;\n }\n\n newKey(): string {\n return this.keyGen();\n }\n\n batch(): Batch {\n return new Batch(this);\n }\n\n /** One query; returns the denormalized result. Subsequent reads of the same entities stay coherent via the cache. */\n async query<T = unknown>(op: string, args: Record<string, unknown> = {}, o: QueryOptions = {}): Promise<T> {\n const rk = RayfoldCache.resultKey(op, args, o.shape, o.vars);\n if (o.policy === \"cache\") {\n const cached = this.cache.getResult(rk);\n if (cached && !cached.stale && ![...cached.keys].some((k) => this.cache.isStale(k))) return this.cache.denormalize(cached.data) as T;\n }\n const b = this.batch();\n const h = b.query<T>(op, args, o);\n await b.run();\n return h.promise;\n }\n\n async command<T = unknown>(op: string, args: Record<string, unknown> = {}, o: CommandOptions = {}): Promise<T> {\n const { optimistic, ...options } = o;\n const predicted = typeof optimistic === \"function\" ? optimistic(this.cache) : optimistic;\n const command: QueuedCommand = { key: o.key ?? this.newKey(), op, args, options, queuedAt: this.now(), seq: this.nextSeq++, ...(predicted?.length ? { optimistic: predicted } : {}) };\n if (predicted?.length) this.cache.addLayer(command.key, predicted);\n if (this.queue) {\n await this.queue.restored;\n // behind the commands still waiting, so the server sees them in the order they were made\n if (this.queue.size) return this.queue.add<T>(command);\n }\n try {\n return this.settled(command, await this.sendCommand<T>(command));\n } catch (e) {\n if (this.queue && isUnreachable(e)) return this.queue.add<T>(command); // its prediction stays until it is sent\n this.cache.removeLayer(command.key);\n throw e;\n }\n }\n\n /** Drops the command's prediction and returns its result as the server left it, not as it was predicted. */\n private settled<T>(c: QueuedCommand, result: T): T {\n if (!c.optimistic?.length) return result;\n this.cache.removeLayer(c.key);\n const stored = this.cache.getResult(RayfoldCache.resultKey(c.op, c.args, c.options.shape, c.options.vars));\n return stored ? (this.cache.denormalize(stored.data) as T) : result;\n }\n\n /** Orders commands by when they were made, across reloads too: it starts from the clock. */\n private nextSeq = Date.now();\n\n private async sendCommand<T>(c: QueuedCommand): Promise<T> {\n const b = this.batch();\n const h = b.command<T>(c.op, c.args, { ...c.options, key: c.key });\n await b.run();\n return h.promise;\n }\n\n /** Commands waiting for the server (option `offline`), oldest first. */\n get queued(): readonly QueuedCommand[] {\n return this.queue?.commands ?? [];\n }\n\n /** Sends the waiting commands in order; resolves to how many still wait because the server is still unreachable. */\n drain(): Promise<number> {\n return this.queue ? this.queue.drain() : Promise.resolve(0);\n }\n\n /** Follows the queue: a command queued, sent, or refused by the server when it finally went out. */\n onQueue(fn: (e: QueueEvent) => void): () => void {\n return this.queue ? this.queue.subscribe(fn) : () => {};\n }\n\n /** Stream items; ends when the server sends fin or the signal aborts. */\n stream<T = unknown>(op: string, args: Record<string, unknown> = {}, o: OpOptions & { signal?: AbortSignal } = {}): AsyncIterable<T> {\n const client = this;\n return (async function* () {\n const req: RequestOp = client.prepare({ id: 1, op, args, ...pick(o) });\n const sendOpts: { signal?: AbortSignal } = {};\n if (o.signal) sendOpts.signal = o.signal;\n for await (const f of client.opts.transport.send(client.envelope([req]), sendOpts)) {\n if (\"item\" in f) yield client.cache.denormalize(client.cache.normalize(client.typed(op, f.item))) as T;\n else if (\"error\" in f) {\n if (f.error.code === \"canceled\" && o.signal?.aborted) return;\n throw new RayfoldClientError(f.error);\n } else if (\"fin\" in f && f.fin) return;\n }\n })();\n }\n\n /**\n * Watch a query: `fn` receives the current denormalized data now and again whenever a later command's\n * patch (or another query) changes any entity the result contains. No refetch involved. `onError` gets the\n * initial fetch's failure; without it the failure is dropped.\n */\n watch<T = unknown>(op: string, args: Record<string, unknown>, o: QueryOptions, fn: (data: T) => void, onError?: (e: unknown) => void): () => void {\n const rk = RayfoldCache.resultKey(op, args, o.shape, o.vars);\n let active = true;\n let ready = false; // ignore the cache events produced by the initial fetch itself\n let seen: CachedResult | undefined; // the stored result `fn` last reported\n // This result changed when it was replaced (a refetch), when an entity it holds changed, or when its op was\n // invalidated. Another result of the same op changing is not a reason: the event's `ops` alone would say so.\n const listener: CacheListener = ({ keys, ops }) => {\n const r = this.cache.getResult(rk);\n if (!r || !active || !ready) return;\n const hit = r !== seen || [...keys].some((k) => r.keys.has(k)) || (ops.has(op) && r.stale);\n if (!hit) return;\n seen = r;\n fn(this.cache.denormalize(r.data) as T);\n };\n const off = this.cache.subscribe(listener);\n void this.query<T>(op, args, o).then(\n (d) => {\n ready = true;\n seen = this.cache.getResult(rk);\n if (active) fn(d);\n },\n (e: unknown) => {\n if (active) onError?.(e);\n },\n );\n return () => {\n active = false;\n off();\n };\n }\n\n /**\n * Live query (extension `live`): the server keeps the query open and pushes patches. `fn` receives the\n * current data now and after every server-side change. `onError` gets an error frame or a failed connection\n * (not the abort from unsubscribing). Returns an unsubscribe function.\n */\n live<T = unknown>(op: string, args: Record<string, unknown>, o: OpOptions, fn: (data: T, meta: { initial: boolean }) => void, onError?: (e: unknown) => void): () => void {\n const ac = new AbortController();\n const rk = RayfoldCache.resultKey(op, args, o.shape, o.vars);\n let initial = true;\n const b = this.batch();\n const h = b.query<T>(op, args, { ...o, live: true });\n void b\n .run({\n signal: ac.signal,\n onFrame: (f) => {\n if (!(\"id\" in f) || f.id !== h.id) return;\n if (\"error\" in f) {\n // after unsubscribing, the stream ends with a \"canceled\" frame: that is the stop, not a failure\n if (!ac.signal.aborted) onError?.(new RayfoldClientError(f.error));\n return;\n }\n if ((\"data\" in f && !(\"at\" in f)) || \"patch\" in f || \"fin\" in f) {\n const r = this.cache.getResult(rk);\n if (!r) return;\n fn(this.cache.denormalize(r.data) as T, { initial });\n initial = false;\n }\n },\n })\n .catch((e: unknown) => {\n if (!ac.signal.aborted) onError?.(e);\n });\n return () => ac.abort();\n }\n\n /** @internal */\n envelope(ops: RequestOp[]): RequestEnvelope {\n const env: RequestEnvelope = { rayfold: \"0.1\", ops };\n const meta: RequestEnvelope[\"meta\"] = {};\n if (this.opts.client) meta.client = this.opts.client;\n if (this.opts.deadline !== undefined) meta.deadline = this.opts.deadline;\n if (Object.keys(meta).length) env.meta = meta;\n return env;\n }\n\n /** @internal */\n async runBatch(handles: OpHandle[], opts: { signal?: AbortSignal; onFrame?: (f: Frame) => void }): Promise<BatchResult> {\n const byId = new Map(handles.map((h) => [h.id, h]));\n const safe = handles.every((h) => this.isQuery(h.req.op));\n const sendOpts: { signal?: AbortSignal; safe?: boolean } = { safe };\n if (opts.signal) sendOpts.signal = opts.signal;\n const frames: Frame[] = [];\n const settled = new Set<number>();\n const resultKeys = new Map<number, string>();\n for (const h of handles) resultKeys.set(h.id, RayfoldCache.resultKey(h.req.op, h.req.args, h.req.shape, h.req.vars));\n const resolve = (h: OpHandle, v: unknown) => {\n settled.add(h.id);\n h._resolve(v);\n };\n const reject = (h: OpHandle, e: unknown) => {\n settled.add(h.id);\n h._reject(e);\n };\n try {\n for await (const f of this.opts.transport.send(this.envelope(handles.map((h) => h.req)), sendOpts)) {\n frames.push(f);\n if (!(\"id\" in f)) {\n const err = new RayfoldClientError(f.error);\n for (const h of handles) if (!settled.has(h.id)) reject(h, err);\n opts.onFrame?.(f);\n continue;\n }\n const h = byId.get(f.id);\n if (!h) continue;\n h.frames.push(f);\n if (\"error\" in f) {\n const current = f.error.type === \"VersionConflict\" ? (f.error.data as { current?: unknown } | undefined)?.current : undefined;\n if (current) this.cache.mergeEntities(current);\n reject(h, new RayfoldClientError(f.error));\n } else if (\"ok\" in f) {\n let r!: ReturnType<RayfoldCache[\"putResult\"]>;\n this.cache.transaction(() => {\n r = this.cache.putResult(resultKeys.get(h.id)!, h.req.op, this.typed(h.req.op, f.ok));\n if (f.patch) this.cache.applyPatch(f.patch as PatchOp[]);\n });\n resolve(h, this.cache.denormalize(r.data));\n } else if (\"data\" in f && !(\"at\" in f)) {\n const r = this.cache.putResult(resultKeys.get(h.id)!, h.req.op, this.typed(h.req.op, f.data));\n if (f.fin) resolve(h, this.cache.denormalize(r.data));\n } else if (\"at\" in f) {\n this.cache.mergeAt(resultKeys.get(h.id)!, f.at, this.typed(h.req.op, f.data, f.at));\n } else if (\"patch\" in f) {\n // a live update: `at` and `list` ops describe this op's own stored result\n this.cache.applyPatch(f.patch as PatchOp[], resultKeys.get(h.id)!);\n } else if (\"fin\" in f && f.fin && !settled.has(h.id)) {\n const r = this.cache.getResult(resultKeys.get(h.id)!);\n resolve(h, r ? this.cache.denormalize(r.data) : undefined);\n }\n opts.onFrame?.(f); // after the cache has absorbed the frame\n }\n } catch (e) {\n for (const h of handles) if (!settled.has(h.id)) reject(h, e);\n throw e;\n }\n for (const h of handles) if (!settled.has(h.id)) reject(h, new RayfoldClientError({ code: \"unavailable\", message: \"Batch ended without a result for this op\" }));\n return { frames };\n }\n\n private readonly opKinds = new Map<string, \"query\" | \"other\">();\n private readonly schema: RayfoldSchemaIR | undefined;\n /** Ops are assumed safe when the transport is used with a schema-aware hint; default: name-based heuristic overridden by `markQueries`. */\n private isQuery(op: string): boolean {\n return this.opKinds.get(op) === \"query\";\n }\n /** Tell the client which op names are queries so all-query batches go over the safe method. */\n markQueries(names: string[]): void {\n for (const n of names) this.opKinds.set(n, \"query\");\n }\n}\n\nfunction pick(o: OpOptions): Partial<RequestOp> {\n const out: Partial<RequestOp> = {};\n if (o.shape !== undefined) out.shape = o.shape;\n if (o.vars !== undefined) out.vars = o.vars as never;\n if (o.key !== undefined) out.key = o.key;\n if (o.deadline !== undefined) out.deadline = o.deadline;\n if (o.simulate !== undefined) out.simulate = o.simulate;\n if (o.live !== undefined) out.live = o.live;\n if (o.ifVersion !== undefined) out.ifVersion = o.ifVersion;\n return out;\n}\n\n/** A field's `@merge` policy from the schema, memoised; without a schema there is no policy to read. */\nfunction mergePolicyOf(schema: RayfoldSchemaIR | undefined): (type: string, field: string) => MergePolicy | undefined {\n if (!schema) return () => undefined;\n const seen = new Map<string, MergePolicy | undefined>();\n return (type, field) => {\n const key = `${type}.${field}`;\n if (seen.has(key)) return seen.get(key);\n const def = schema.types[type];\n const f = def && \"fields\" in def ? def.fields.find((x) => x.name === field) : undefined;\n const value = f ? annotation(f, \"merge\")?.args[\"value\"] : undefined;\n const policy = value && typeof value === \"object\" && \"$ident\" in value ? (String((value as { $ident: string }).$ident) as MergePolicy) : undefined;\n seen.set(key, policy);\n return policy;\n };\n}\n"]}
package/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { RayfoldCache, entityKey, isRef, type EntityKey, type Ref, type CachedResult, type CacheListener, type OptimisticOp } from "./cache.js";
2
+ export { createFetchTransport, createLocalTransport, type Transport, type FetchTransportOptions } from "./transport.js";
3
+ export { RayfoldClient, RayfoldClientError, Batch, OpHandle, type ClientOptions, type OpOptions, type CommandOptions, type QueryOptions, type BatchResult } from "./client.js";
4
+ export { localStorageQueue, memoryQueue, isUnreachable, type QueueStorage, type QueuedCommand, type QueueEvent } from "./offline.js";
5
+ export { createWebSocketTransport, type WsTransportOptions } from "./ws-transport.js";
6
+ export { restoreTypes, typeAtPath } from "./types.js";