@agentshouse/mdruntime 0.1.0-cdf82f77fae68 → 0.1.0-ceb97106cc2fd
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/AGENTS.md +259 -272
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +37 -9
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- package/dist/bootstrap.d.ts +2 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +32 -6
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +36 -10
- package/dist/chat/acts.d.ts +3 -4
- package/dist/chat/acts.d.ts.map +1 -1
- package/dist/chat/acts.js +10 -10
- package/dist/chat/kit.d.ts +2 -1
- package/dist/chat/kit.d.ts.map +1 -1
- package/dist/chat/kit.js +3 -3
- package/dist/chat/list.d.ts +8 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +10 -6
- package/dist/chat/markdown.d.ts +2 -1
- package/dist/chat/markdown.d.ts.map +1 -1
- package/dist/chat/markdown.js +7 -6
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +2 -2
- package/dist/chat/messages.d.ts +1 -0
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +4 -0
- package/dist/chat/provider.d.ts +1 -1
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +51 -67
- package/dist/chat/ready.d.ts +1 -0
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +4 -4
- package/dist/chat/style.js +3 -3
- package/dist/client.d.ts +12 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/collection.d.ts +17 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +230 -19
- package/dist/connection.d.ts +24 -7
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1340 -318
- package/dist/conversation.d.ts +14 -7
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +328 -137
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +18 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +89 -12
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +6 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/memory.d.ts +4 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +23 -2
- package/dist/model.d.ts +4 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/observation.d.ts +5 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +22 -11
- package/dist/outcome.d.ts +4 -0
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +1 -0
- package/dist/overlay.d.ts +2 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +58 -55
- package/dist/query.d.ts +34 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +7 -1
- package/dist/reach.d.ts +6 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +17 -5
- package/dist/react.d.ts +7 -4
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +6 -4
- package/dist/read-side.d.ts +13 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +285 -83
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +17 -10
- package/dist/replica.d.ts +1 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +57 -20
- package/dist/room-draft.d.ts +13 -1
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +2 -2
- package/dist/rows.d.ts +5 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +5 -13
- package/dist/save.d.ts +6 -2
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +42 -18
- package/dist/search.d.ts +34 -6
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +116 -45
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +61 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -0
- package/dist/status.d.ts +4 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/store.d.ts +45 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +315 -85
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/wire.d.ts +136 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +48 -31
- package/package.json +12 -8
package/dist/read-side.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { and, BasicIndex, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, inArray, or, toArray, } from '@tanstack/db';
|
|
2
|
-
import { ARRAY_INDEX, pointerSegments } from '@agentshouse/mdmodel';
|
|
3
|
-
import { rowsOf } from './observation.js';
|
|
2
|
+
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
4
3
|
import { documentKey } from './rows.js';
|
|
4
|
+
import { after, inUnits, ROW_WEIGHT } from './turn.js';
|
|
5
5
|
function every(expressions) {
|
|
6
6
|
const [first, second, ...rest] = expressions;
|
|
7
7
|
return second === undefined ? first : and(first, second, ...rest);
|
|
@@ -101,14 +101,11 @@ function filtered(q, rows, plan) {
|
|
|
101
101
|
}
|
|
102
102
|
return query;
|
|
103
103
|
}
|
|
104
|
-
function
|
|
105
|
-
return
|
|
104
|
+
function bodied(shape) {
|
|
105
|
+
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
106
106
|
}
|
|
107
107
|
function documents(q, rows, plan, shape) {
|
|
108
108
|
let query = filtered(q, rows, plan);
|
|
109
|
-
if (shape.sections !== null || shape.preamble || shape.markdown) {
|
|
110
|
-
query = query.leftJoin({ b: rows }, ({ d, b }) => eq(d.body, b.key));
|
|
111
|
-
}
|
|
112
109
|
for (const { key, options } of shape.orderBy) {
|
|
113
110
|
query = query.orderBy(({ d }) => keyed(key, d), options);
|
|
114
111
|
}
|
|
@@ -117,18 +114,16 @@ function documents(q, rows, plan, shape) {
|
|
|
117
114
|
}
|
|
118
115
|
if (plan.limit !== null)
|
|
119
116
|
query = query.limit(plan.limit);
|
|
120
|
-
return query.select(({ d
|
|
117
|
+
return query.select(({ d }) => ({
|
|
121
118
|
room: d.room,
|
|
122
119
|
path: d.path,
|
|
123
120
|
type: d.type,
|
|
124
121
|
binary: d.binary,
|
|
125
122
|
...(shape.fields === null ? {} : { fields: d.fields }),
|
|
126
|
-
...(shape
|
|
127
|
-
...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
|
|
128
|
-
...(shape.preamble ? { preamble: b.preamble } : {}),
|
|
129
|
-
...(shape.markdown ? { markdown: b.markdown } : {}),
|
|
123
|
+
...(bodied(shape) ? { text: d.text } : {}),
|
|
130
124
|
...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
|
|
131
125
|
...(shape.change ? { change: d.change } : {}),
|
|
126
|
+
...(shape.fileId ? { fileId: d.fileId } : {}),
|
|
132
127
|
}));
|
|
133
128
|
}
|
|
134
129
|
function backlinked(q, rows, document, reach) {
|
|
@@ -172,13 +167,22 @@ function built(rows, plan) {
|
|
|
172
167
|
},
|
|
173
168
|
};
|
|
174
169
|
}
|
|
175
|
-
function
|
|
176
|
-
return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
|
|
177
|
-
}
|
|
178
|
-
function part(row, prefix, names, whole) {
|
|
170
|
+
function sectionsOf(sections, names) {
|
|
179
171
|
if (names === 'all')
|
|
180
|
-
return
|
|
181
|
-
return
|
|
172
|
+
return sections;
|
|
173
|
+
return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
|
|
174
|
+
}
|
|
175
|
+
function bodyOf(row, key, bodies) {
|
|
176
|
+
const text = row.text;
|
|
177
|
+
if (text === null)
|
|
178
|
+
return null;
|
|
179
|
+
const held = bodies?.get(key);
|
|
180
|
+
if (held !== undefined && held.text === text)
|
|
181
|
+
return held.document;
|
|
182
|
+
const read = parse(text, row.path);
|
|
183
|
+
const document = read.ok ? read.document : null;
|
|
184
|
+
bodies?.set(key, { text, document });
|
|
185
|
+
return document;
|
|
182
186
|
}
|
|
183
187
|
function masked(pointers) {
|
|
184
188
|
const root = { whole: false, members: new Map() };
|
|
@@ -229,15 +233,17 @@ function cut(value, mask) {
|
|
|
229
233
|
function selected(fields, pointers) {
|
|
230
234
|
return (cut(fields, masked(pointers)) ?? {});
|
|
231
235
|
}
|
|
232
|
-
function documentOf(row, shape) {
|
|
236
|
+
function documentOf(key, row, shape, bodies) {
|
|
233
237
|
if (row.type === null) {
|
|
234
238
|
const binary = row.binary;
|
|
235
239
|
return {
|
|
236
240
|
room: row.room,
|
|
237
241
|
path: row.path,
|
|
238
242
|
...(binary === null ? {} : { binaryFile: binary }),
|
|
243
|
+
...(shape.fileId ? { fileId: row.fileId } : {}),
|
|
239
244
|
};
|
|
240
245
|
}
|
|
246
|
+
const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
|
|
241
247
|
return {
|
|
242
248
|
room: row.room,
|
|
243
249
|
path: row.path,
|
|
@@ -249,45 +255,54 @@ function documentOf(row, shape) {
|
|
|
249
255
|
? row.fields
|
|
250
256
|
: selected(row.fields, shape.fields),
|
|
251
257
|
}),
|
|
252
|
-
...(shape.sections === null
|
|
258
|
+
...(shape.sections === null || body === null
|
|
253
259
|
? {}
|
|
254
|
-
: {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
...(shape.preamble ? { preamble: row.preamble } : {}),
|
|
258
|
-
...(shape.markdown ? { markdown: row.markdown } : {}),
|
|
260
|
+
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
261
|
+
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
262
|
+
...(shape.markdown && body !== null ? { markdown: body.content } : {}),
|
|
259
263
|
...(shape.backlinks
|
|
260
264
|
? {
|
|
261
265
|
backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
|
|
262
266
|
}
|
|
263
267
|
: {}),
|
|
264
268
|
...(shape.change ? { change: row.change } : {}),
|
|
269
|
+
...(shape.fileId ? { fileId: row.fileId } : {}),
|
|
265
270
|
};
|
|
266
271
|
}
|
|
267
|
-
function
|
|
272
|
+
function weight(row) {
|
|
273
|
+
return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
|
|
274
|
+
}
|
|
275
|
+
function rowsOfPlan(plan, entries, bodies, each = (row) => row) {
|
|
268
276
|
const shape = plan.shape;
|
|
269
277
|
if (shape.kind === 'documents') {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
278
|
+
const documents = [];
|
|
279
|
+
return after(inUnits(entries, ([key, row]) => {
|
|
280
|
+
documents.push(each(documentOf(key, row, shape, bodies)));
|
|
281
|
+
return weight(row);
|
|
282
|
+
}), () => ({ rows: { documents }, keys: entries.map(([key]) => key) }));
|
|
274
283
|
}
|
|
275
284
|
const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
};
|
|
285
|
+
const groups = [];
|
|
286
|
+
return after(inUnits(bounded, ([, row]) => {
|
|
287
|
+
groups.push(each({
|
|
288
|
+
key: shape.groupBy.map((_, index) => row[`key${index}`]),
|
|
289
|
+
count: row.count,
|
|
290
|
+
}));
|
|
291
|
+
return ROW_WEIGHT;
|
|
292
|
+
}), () => ({ rows: { groups }, keys: bounded.map(([key]) => key) }));
|
|
285
293
|
}
|
|
286
294
|
export function reconciled(previous, next) {
|
|
287
|
-
const before =
|
|
288
|
-
const
|
|
295
|
+
const before = previous.result;
|
|
296
|
+
const after = next.result;
|
|
297
|
+
if ('pending' in before || 'pending' in after) {
|
|
298
|
+
if (deepEquals(before, after))
|
|
299
|
+
return null;
|
|
300
|
+
return { keyed: next, message: next };
|
|
301
|
+
}
|
|
302
|
+
const earlier = 'groups' in before ? before.groups : before.documents;
|
|
303
|
+
const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
|
|
289
304
|
const changed = [];
|
|
290
|
-
const rows =
|
|
305
|
+
const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
|
|
291
306
|
const key = next.keys[index];
|
|
292
307
|
const kept = held.get(key);
|
|
293
308
|
if (kept !== undefined && deepEquals(kept, row))
|
|
@@ -296,22 +311,37 @@ export function reconciled(previous, next) {
|
|
|
296
311
|
return row;
|
|
297
312
|
});
|
|
298
313
|
const present = new Set(next.keys);
|
|
299
|
-
const survivors = previous.keys.filter((key) => present.has(key));
|
|
300
314
|
const removed = previous.keys.filter((key) => !present.has(key));
|
|
301
|
-
const moved =
|
|
302
|
-
|
|
303
|
-
if (changed.length === 0 && removed.length === 0 && !moved)
|
|
315
|
+
const moved = reordered(previous.keys, next.keys, present);
|
|
316
|
+
const covered = !deepEquals(before.coverage, after.coverage);
|
|
317
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered)
|
|
304
318
|
return null;
|
|
305
319
|
return {
|
|
306
320
|
keyed: {
|
|
307
|
-
result: 'groups' in
|
|
308
|
-
? { groups: rows }
|
|
309
|
-
: { documents: rows },
|
|
321
|
+
result: 'groups' in after
|
|
322
|
+
? { groups: rows, coverage: after.coverage }
|
|
323
|
+
: { documents: rows, coverage: after.coverage },
|
|
310
324
|
keys: next.keys,
|
|
311
325
|
},
|
|
312
|
-
|
|
326
|
+
message: {
|
|
327
|
+
changed,
|
|
328
|
+
removed,
|
|
329
|
+
order: moved ? next.keys : null,
|
|
330
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
331
|
+
},
|
|
313
332
|
};
|
|
314
333
|
}
|
|
334
|
+
function reordered(previous, next, present) {
|
|
335
|
+
let at = 0;
|
|
336
|
+
for (const key of previous) {
|
|
337
|
+
if (!present.has(key))
|
|
338
|
+
continue;
|
|
339
|
+
if (next[at] !== key)
|
|
340
|
+
return true;
|
|
341
|
+
at += 1;
|
|
342
|
+
}
|
|
343
|
+
return at !== next.length;
|
|
344
|
+
}
|
|
315
345
|
export function createReadSide() {
|
|
316
346
|
configureIndexDevMode({ enabled: false });
|
|
317
347
|
let sync;
|
|
@@ -331,23 +361,139 @@ export function createReadSide() {
|
|
|
331
361
|
});
|
|
332
362
|
const shared = new Map();
|
|
333
363
|
function searched(row) {
|
|
334
|
-
const
|
|
364
|
+
const read = row.text === null ? null : parse(row.text, row.path);
|
|
365
|
+
if (read === null || !read.ok)
|
|
366
|
+
return null;
|
|
335
367
|
return {
|
|
336
368
|
room: row.room,
|
|
337
369
|
path: row.path,
|
|
338
370
|
type: row.type,
|
|
339
371
|
fields: row.fields,
|
|
340
|
-
preamble:
|
|
341
|
-
sections:
|
|
372
|
+
preamble: read.document.preamble,
|
|
373
|
+
sections: read.document.sections,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function kept(entry, key, row) {
|
|
377
|
+
const held = entry.rows.get(key);
|
|
378
|
+
if (held !== undefined && deepEquals(held, row))
|
|
379
|
+
return;
|
|
380
|
+
entry.rows.set(key, row);
|
|
381
|
+
}
|
|
382
|
+
function rebuilt(entry) {
|
|
383
|
+
return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
|
|
384
|
+
const present = new Set(keys);
|
|
385
|
+
for (const key of [...entry.rows.keys()])
|
|
386
|
+
if (!present.has(key))
|
|
387
|
+
entry.rows.delete(key);
|
|
388
|
+
for (const key of [...entry.bodies.keys()]) {
|
|
389
|
+
if (!present.has(key))
|
|
390
|
+
entry.bodies.delete(key);
|
|
391
|
+
}
|
|
392
|
+
keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
|
|
393
|
+
return keys;
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function refreshed(entry, dirty) {
|
|
397
|
+
const shape = entry.plan.shape;
|
|
398
|
+
const keys = [...entry.built.live.keys()];
|
|
399
|
+
const named = new Set([...dirty, ...keys.filter((key) => !entry.rows.has(key))]);
|
|
400
|
+
const values = [...named].map((key) => [key, entry.built.live.get(key)]);
|
|
401
|
+
const done = inUnits(values, ([key, value]) => {
|
|
402
|
+
if (value !== undefined)
|
|
403
|
+
kept(entry, key, documentOf(key, value, shape, entry.bodies));
|
|
404
|
+
else {
|
|
405
|
+
entry.rows.delete(key);
|
|
406
|
+
entry.bodies.delete(key);
|
|
407
|
+
}
|
|
408
|
+
return weight(value);
|
|
409
|
+
});
|
|
410
|
+
return after(done, () => keys);
|
|
411
|
+
}
|
|
412
|
+
function current(entry, keys, coverage) {
|
|
413
|
+
const found = keys.map((key) => entry.rows.get(key));
|
|
414
|
+
return {
|
|
415
|
+
result: entry.plan.shape.kind === 'groups'
|
|
416
|
+
? { groups: found, coverage }
|
|
417
|
+
: { documents: found, coverage },
|
|
418
|
+
keys,
|
|
342
419
|
};
|
|
343
420
|
}
|
|
344
|
-
function
|
|
345
|
-
const
|
|
346
|
-
|
|
421
|
+
function delivered(entry, next) {
|
|
422
|
+
const previous = entry.result;
|
|
423
|
+
entry.result = next;
|
|
424
|
+
const message = previous === undefined ? null : changedFrom(previous, next);
|
|
425
|
+
if (message !== null)
|
|
426
|
+
for (const listener of entry.ready)
|
|
427
|
+
listener(message);
|
|
428
|
+
for (const listener of entry.fresh) {
|
|
429
|
+
entry.ready.add(listener);
|
|
430
|
+
listener(next);
|
|
431
|
+
}
|
|
432
|
+
entry.fresh.clear();
|
|
433
|
+
}
|
|
434
|
+
function run(entry) {
|
|
435
|
+
while (entry.listeners.size > 0 &&
|
|
436
|
+
(entry.whole || entry.dirty.size > 0 || entry.stale || entry.fresh.size > 0)) {
|
|
437
|
+
entry.stale = false;
|
|
438
|
+
const cover = entry.cover();
|
|
439
|
+
if ('pending' in cover) {
|
|
440
|
+
entry.whole = true;
|
|
441
|
+
entry.dirty.clear();
|
|
442
|
+
entry.rows.clear();
|
|
443
|
+
entry.bodies.clear();
|
|
444
|
+
delivered(entry, { result: cover, keys: [] });
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
const whole = entry.whole ||
|
|
448
|
+
entry.plan.shape.kind === 'groups' ||
|
|
449
|
+
entry.result === undefined ||
|
|
450
|
+
'pending' in entry.result.result;
|
|
451
|
+
const dirty = entry.dirty;
|
|
452
|
+
entry.dirty = new Set();
|
|
453
|
+
entry.whole = false;
|
|
454
|
+
const keys = whole ? rebuilt(entry) : refreshed(entry, dirty);
|
|
455
|
+
if (keys instanceof Promise) {
|
|
456
|
+
void keys
|
|
457
|
+
.then((found) => {
|
|
458
|
+
if (entry.listeners.size > 0)
|
|
459
|
+
delivered(entry, current(entry, found, cover.coverage));
|
|
460
|
+
run(entry);
|
|
461
|
+
})
|
|
462
|
+
.catch(() => {
|
|
463
|
+
entry.running = false;
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
delivered(entry, current(entry, keys, cover.coverage));
|
|
468
|
+
}
|
|
469
|
+
entry.running = false;
|
|
470
|
+
}
|
|
471
|
+
function wake(entry) {
|
|
472
|
+
if (entry.subscription === null || entry.running)
|
|
347
473
|
return;
|
|
348
|
-
entry.
|
|
349
|
-
|
|
350
|
-
|
|
474
|
+
entry.running = true;
|
|
475
|
+
try {
|
|
476
|
+
run(entry);
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
entry.running = false;
|
|
480
|
+
throw error;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function recover() {
|
|
484
|
+
for (const entry of shared.values()) {
|
|
485
|
+
if (entry.result === undefined || entry.subscription === null)
|
|
486
|
+
continue;
|
|
487
|
+
const cover = entry.cover();
|
|
488
|
+
const before = entry.result.result;
|
|
489
|
+
const held = 'pending' in cover
|
|
490
|
+
? deepEquals(before, cover)
|
|
491
|
+
: 'coverage' in before && deepEquals(before.coverage, cover.coverage);
|
|
492
|
+
if (held)
|
|
493
|
+
continue;
|
|
494
|
+
entry.stale = true;
|
|
495
|
+
wake(entry);
|
|
496
|
+
}
|
|
351
497
|
}
|
|
352
498
|
return {
|
|
353
499
|
apply(set) {
|
|
@@ -358,38 +504,52 @@ export function createReadSide() {
|
|
|
358
504
|
for (const row of change.after)
|
|
359
505
|
next.set(row.key, row);
|
|
360
506
|
}
|
|
361
|
-
if (next.size
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
507
|
+
if (next.size > 0) {
|
|
508
|
+
sync.begin();
|
|
509
|
+
for (const [key, row] of next) {
|
|
510
|
+
if (row === null) {
|
|
511
|
+
if (rows.has(key))
|
|
512
|
+
sync.write({ type: 'delete', key });
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
|
|
369
516
|
}
|
|
370
|
-
sync.
|
|
517
|
+
sync.commit();
|
|
371
518
|
}
|
|
372
|
-
|
|
519
|
+
recover();
|
|
373
520
|
},
|
|
374
|
-
|
|
521
|
+
recover,
|
|
522
|
+
async query(plan, at) {
|
|
375
523
|
const { live: collection, dispose } = built(rows, plan);
|
|
376
524
|
try {
|
|
377
525
|
await collection.toArrayWhenReady();
|
|
378
|
-
|
|
526
|
+
const entries = [...collection.entries()];
|
|
527
|
+
const held = at();
|
|
528
|
+
const found = await rowsOfPlan(plan, entries, null, structuredClone);
|
|
529
|
+
return { rows: found.rows, at: held };
|
|
379
530
|
}
|
|
380
531
|
finally {
|
|
381
532
|
dispose();
|
|
382
533
|
}
|
|
383
534
|
},
|
|
384
|
-
watch(plan, identity,
|
|
535
|
+
watch(plan, identity, cover, deliverTo) {
|
|
385
536
|
let entry = shared.get(identity);
|
|
386
537
|
if (entry === undefined) {
|
|
387
538
|
const created = {
|
|
388
539
|
plan,
|
|
389
540
|
built: built(rows, plan),
|
|
541
|
+
cover,
|
|
390
542
|
result: undefined,
|
|
543
|
+
rows: new Map(),
|
|
544
|
+
bodies: new Map(),
|
|
391
545
|
listeners: new Set(),
|
|
546
|
+
fresh: new Set(),
|
|
547
|
+
ready: new Set(),
|
|
392
548
|
subscription: null,
|
|
549
|
+
whole: true,
|
|
550
|
+
dirty: new Set(),
|
|
551
|
+
stale: false,
|
|
552
|
+
running: false,
|
|
393
553
|
};
|
|
394
554
|
created.built.live.on('status:change', ({ status }) => {
|
|
395
555
|
if (status === 'cleaned-up' && created.listeners.size === 0)
|
|
@@ -399,29 +559,40 @@ export function createReadSide() {
|
|
|
399
559
|
entry = created;
|
|
400
560
|
}
|
|
401
561
|
const observed = entry;
|
|
402
|
-
observed.listeners.add(
|
|
562
|
+
observed.listeners.add(deliverTo);
|
|
403
563
|
observed.built.live.onFirstReady(() => {
|
|
404
|
-
if (!observed.listeners.has(
|
|
564
|
+
if (!observed.listeners.has(deliverTo))
|
|
405
565
|
return;
|
|
566
|
+
observed.fresh.add(deliverTo);
|
|
406
567
|
if (observed.subscription === null) {
|
|
407
|
-
observed.
|
|
408
|
-
observed.subscription = observed.built.live.subscribeChanges(() => {
|
|
409
|
-
|
|
568
|
+
observed.whole = true;
|
|
569
|
+
observed.subscription = observed.built.live.subscribeChanges((changes) => {
|
|
570
|
+
if (observed.plan.shape.kind === 'groups')
|
|
571
|
+
observed.whole = true;
|
|
572
|
+
else
|
|
573
|
+
for (const { key } of changes)
|
|
574
|
+
observed.dirty.add(key);
|
|
575
|
+
wake(observed);
|
|
410
576
|
}, { includeInitialState: false });
|
|
411
577
|
}
|
|
412
|
-
|
|
578
|
+
wake(observed);
|
|
413
579
|
});
|
|
414
580
|
return () => {
|
|
415
|
-
|
|
581
|
+
observed.fresh.delete(deliverTo);
|
|
582
|
+
observed.ready.delete(deliverTo);
|
|
583
|
+
if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
|
|
416
584
|
return;
|
|
417
585
|
observed.subscription?.unsubscribe();
|
|
418
586
|
observed.subscription = null;
|
|
587
|
+
observed.result = undefined;
|
|
588
|
+
observed.rows.clear();
|
|
589
|
+
observed.bodies.clear();
|
|
590
|
+
observed.dirty.clear();
|
|
591
|
+
observed.whole = true;
|
|
419
592
|
};
|
|
420
593
|
},
|
|
421
|
-
|
|
422
|
-
return rows.toArray
|
|
423
|
-
.filter((row) => row.kind === 'document' && row.type !== null)
|
|
424
|
-
.map(searched);
|
|
594
|
+
room(room) {
|
|
595
|
+
return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
|
|
425
596
|
},
|
|
426
597
|
document(room, path) {
|
|
427
598
|
const row = rows.get(documentKey(room, path));
|
|
@@ -430,6 +601,8 @@ export function createReadSide() {
|
|
|
430
601
|
close() {
|
|
431
602
|
for (const entry of shared.values()) {
|
|
432
603
|
entry.listeners.clear();
|
|
604
|
+
entry.fresh.clear();
|
|
605
|
+
entry.ready.clear();
|
|
433
606
|
entry.subscription?.unsubscribe();
|
|
434
607
|
entry.built.dispose();
|
|
435
608
|
}
|
|
@@ -438,3 +611,32 @@ export function createReadSide() {
|
|
|
438
611
|
},
|
|
439
612
|
};
|
|
440
613
|
}
|
|
614
|
+
function changedFrom(previous, next) {
|
|
615
|
+
const before = previous.result;
|
|
616
|
+
const after = next.result;
|
|
617
|
+
if ('pending' in before || 'pending' in after)
|
|
618
|
+
return deepEquals(before, after) ? null : next;
|
|
619
|
+
const earlier = 'groups' in before ? before.groups : before.documents;
|
|
620
|
+
const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
|
|
621
|
+
const later = 'groups' in after ? after.groups : after.documents;
|
|
622
|
+
const changed = [];
|
|
623
|
+
next.keys.forEach((key, index) => {
|
|
624
|
+
if (held.get(key) !== later[index])
|
|
625
|
+
changed.push([key, later[index]]);
|
|
626
|
+
});
|
|
627
|
+
const present = new Set(next.keys);
|
|
628
|
+
const removed = previous.keys.filter((key) => !present.has(key));
|
|
629
|
+
const moved = reordered(previous.keys, next.keys, present);
|
|
630
|
+
const covered = !deepEquals(before.coverage, after.coverage);
|
|
631
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered)
|
|
632
|
+
return null;
|
|
633
|
+
return {
|
|
634
|
+
changed,
|
|
635
|
+
removed,
|
|
636
|
+
order: moved ? next.keys : null,
|
|
637
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function rowAt(rows, index) {
|
|
641
|
+
return 'groups' in rows ? rows.groups[index] : rows.documents[index];
|
|
642
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Open } from './open.js';
|
|
2
|
+
export type CodeAvailability = Open<'stored' | 'absent'>;
|
|
3
|
+
export type RoomPreparation = Open<'complete' | 'acquiring' | 'preparing' | 'indexing' | 'refused' | 'unadmitted'>;
|
|
4
|
+
export type RoomCoverage = {
|
|
5
|
+
readonly room: string;
|
|
6
|
+
readonly state: RoomPreparation;
|
|
7
|
+
};
|
|
8
|
+
export type ConversationCoverage = Open<'complete' | 'acquiring' | 'indexing' | 'undeclared'>;
|
|
9
|
+
export type SearchObligation = Open<'required' | 'unused'>;
|
|
10
|
+
export type TargetCoverage = {
|
|
11
|
+
readonly complete: boolean;
|
|
12
|
+
readonly listed: boolean;
|
|
13
|
+
readonly rooms: readonly RoomCoverage[];
|
|
14
|
+
readonly conversations: ConversationCoverage;
|
|
15
|
+
readonly search: SearchObligation;
|
|
16
|
+
};
|
|
17
|
+
export type Persistence = Open<'granted' | 'denied' | 'unavailable'>;
|
|
18
|
+
export type Durability = Open<'persistent' | 'best-effort'>;
|
|
19
|
+
export type ConnectionAvailability = Open<'connected' | 'connecting' | 'unreachable'>;
|
|
20
|
+
export type HouseOutcome = Open<'open' | 'write-halted' | 'refused'>;
|
|
21
|
+
export type CapacityCause = {
|
|
22
|
+
readonly cause: Open<'insufficient-space' | 'quota-exceeded'>;
|
|
23
|
+
readonly needed: number | null;
|
|
24
|
+
readonly available: number | null;
|
|
25
|
+
};
|
|
26
|
+
export type RefusedRoom = {
|
|
27
|
+
readonly room: string;
|
|
28
|
+
readonly file: string;
|
|
29
|
+
readonly rule: string;
|
|
30
|
+
readonly location: string | null;
|
|
31
|
+
};
|
|
32
|
+
export type ReadinessStatus = Open<'preparing' | 'paused' | 'refused' | 'ready' | 'lost'>;
|
|
33
|
+
export type Readiness = {
|
|
34
|
+
readonly status: ReadinessStatus;
|
|
35
|
+
readonly ready: boolean;
|
|
36
|
+
readonly usable: boolean;
|
|
37
|
+
readonly code: CodeAvailability;
|
|
38
|
+
readonly coverage: TargetCoverage;
|
|
39
|
+
readonly durability: Durability;
|
|
40
|
+
readonly persistence: Persistence;
|
|
41
|
+
readonly connection: ConnectionAvailability;
|
|
42
|
+
readonly house: HouseOutcome;
|
|
43
|
+
readonly capacity: CapacityCause | null;
|
|
44
|
+
readonly refused: readonly RefusedRoom[];
|
|
45
|
+
readonly lost: boolean;
|
|
46
|
+
};
|
|
47
|
+
export type ReadinessFacts = Omit<Readiness, 'status' | 'ready' | 'usable' | 'durability' | 'coverage'> & {
|
|
48
|
+
readonly coverage: Omit<TargetCoverage, 'complete'>;
|
|
49
|
+
readonly established: boolean;
|
|
50
|
+
};
|
|
51
|
+
export declare const SETTLED_ROOMS: ReadonlySet<string>;
|
|
52
|
+
export declare function readinessOf(facts: ReadinessFacts): Readiness;
|
|
53
|
+
//# sourceMappingURL=readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const COMPLETE_ROOMS = new Set(['complete', 'unadmitted']);
|
|
2
|
+
export const SETTLED_ROOMS = new Set([...COMPLETE_ROOMS, 'refused']);
|
|
3
|
+
export function readinessOf(facts) {
|
|
4
|
+
const { established, coverage: held, ...reported } = facts;
|
|
5
|
+
const settledConversations = held.conversations === 'complete' || held.conversations === 'undeclared';
|
|
6
|
+
const complete = held.listed &&
|
|
7
|
+
settledConversations &&
|
|
8
|
+
held.rooms.every((room) => COMPLETE_ROOMS.has(room.state));
|
|
9
|
+
const settled = held.listed &&
|
|
10
|
+
settledConversations &&
|
|
11
|
+
held.rooms.every((room) => SETTLED_ROOMS.has(room.state));
|
|
12
|
+
const coverage = { ...held, complete };
|
|
13
|
+
const stored = facts.code === 'stored';
|
|
14
|
+
const ready = stored && complete;
|
|
15
|
+
const usable = stored && (ready || established);
|
|
16
|
+
const status = ready
|
|
17
|
+
? 'ready'
|
|
18
|
+
: facts.capacity !== null
|
|
19
|
+
? 'paused'
|
|
20
|
+
: facts.lost && !established
|
|
21
|
+
? 'lost'
|
|
22
|
+
: settled && !complete
|
|
23
|
+
? 'refused'
|
|
24
|
+
: 'preparing';
|
|
25
|
+
return {
|
|
26
|
+
...reported,
|
|
27
|
+
coverage,
|
|
28
|
+
status,
|
|
29
|
+
ready,
|
|
30
|
+
usable,
|
|
31
|
+
durability: facts.persistence === 'granted' ? 'persistent' : 'best-effort',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type Attempted = 'done' | 'again' | {
|
|
2
|
+
readonly at: number;
|
|
3
|
+
};
|
|
4
|
+
export type Attempt = (signal: AbortSignal) => Promise<Attempted>;
|
|
5
|
+
export type AttemptOptions = {
|
|
6
|
+
readonly at?: number;
|
|
7
|
+
readonly again?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type RecoveryOptions = {
|
|
10
|
+
readonly firstMs?: number;
|
|
11
|
+
readonly ceilingMs?: number;
|
|
12
|
+
readonly attempts?: number;
|
|
13
|
+
readonly inFlight?: number;
|
|
14
|
+
};
|
|
15
|
+
export type Recovery = {
|
|
16
|
+
attempt(intent: string, run: Attempt, options?: AttemptOptions): Promise<boolean>;
|
|
17
|
+
pending(intent: string): boolean;
|
|
18
|
+
resume(): void;
|
|
19
|
+
cancel(intent: string): void;
|
|
20
|
+
scope(inFlight?: number): Recovery;
|
|
21
|
+
retire(): Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
export declare function retryDelay(tries: number, firstMs?: number, ceilingMs?: number): number;
|
|
24
|
+
export declare function createRecovery(options?: RecoveryOptions): Recovery;
|
|
25
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,MAAM,IAAI,IAAI,CAAC;IACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAsBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAW,EAAE,SAAS,SAAa,GAAG,MAAM,CAG5F;AAuKD,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAStE"}
|