@agentshouse/mdruntime 0.1.0-c2c52158b7381 → 0.1.0-c2e8a6f99866d

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.
Files changed (97) hide show
  1. package/AGENTS.md +14 -10
  2. package/dist/adapter.d.ts +14 -5
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/bootstrap.d.ts +13 -6
  5. package/dist/bootstrap.d.ts.map +1 -1
  6. package/dist/bootstrap.js +86 -88
  7. package/dist/chain.d.ts +11 -0
  8. package/dist/chain.d.ts.map +1 -0
  9. package/dist/chain.js +64 -0
  10. package/dist/change-set.d.ts +5 -9
  11. package/dist/change-set.d.ts.map +1 -1
  12. package/dist/change-set.js +10 -86
  13. package/dist/chat/list.d.ts.map +1 -1
  14. package/dist/chat/list.js +1 -1
  15. package/dist/chat/message.d.ts.map +1 -1
  16. package/dist/chat/message.js +8 -1
  17. package/dist/chat/messages.d.ts +1 -0
  18. package/dist/chat/messages.d.ts.map +1 -1
  19. package/dist/chat/messages.js +9 -1
  20. package/dist/chat/provider.d.ts +2 -0
  21. package/dist/chat/provider.d.ts.map +1 -1
  22. package/dist/chat/provider.js +17 -1
  23. package/dist/client.d.ts +1 -0
  24. package/dist/client.d.ts.map +1 -1
  25. package/dist/client.js +1 -0
  26. package/dist/collection.d.ts +5 -19
  27. package/dist/collection.d.ts.map +1 -1
  28. package/dist/collection.js +22 -360
  29. package/dist/compile.d.ts +11 -0
  30. package/dist/compile.d.ts.map +1 -0
  31. package/dist/compile.js +712 -0
  32. package/dist/connection.d.ts +2 -2
  33. package/dist/connection.d.ts.map +1 -1
  34. package/dist/connection.js +1396 -624
  35. package/dist/conversation.d.ts +1 -1
  36. package/dist/conversation.d.ts.map +1 -1
  37. package/dist/conversation.js +26 -17
  38. package/dist/entry.d.ts +2 -1
  39. package/dist/entry.d.ts.map +1 -1
  40. package/dist/entry.js +17 -23
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/live.d.ts +22 -0
  44. package/dist/live.d.ts.map +1 -0
  45. package/dist/live.js +123 -0
  46. package/dist/memory.d.ts +1 -0
  47. package/dist/memory.d.ts.map +1 -1
  48. package/dist/memory.js +12 -0
  49. package/dist/observation.d.ts +12 -0
  50. package/dist/observation.d.ts.map +1 -1
  51. package/dist/observation.js +84 -0
  52. package/dist/overlay.d.ts +0 -3
  53. package/dist/overlay.d.ts.map +1 -1
  54. package/dist/overlay.js +1 -151
  55. package/dist/reach.d.ts.map +1 -1
  56. package/dist/reach.js +15 -35
  57. package/dist/reads.js +1 -1
  58. package/dist/refresh.d.ts +10 -13
  59. package/dist/refresh.d.ts.map +1 -1
  60. package/dist/refresh.js +74 -70
  61. package/dist/replica.d.ts +55 -38
  62. package/dist/replica.d.ts.map +1 -1
  63. package/dist/replica.js +273 -214
  64. package/dist/rows.d.ts +29 -31
  65. package/dist/rows.d.ts.map +1 -1
  66. package/dist/rows.js +113 -69
  67. package/dist/save.d.ts +9 -10
  68. package/dist/save.d.ts.map +1 -1
  69. package/dist/save.js +59 -57
  70. package/dist/search.d.ts +2 -2
  71. package/dist/search.d.ts.map +1 -1
  72. package/dist/search.js +60 -28
  73. package/dist/shared.d.ts +0 -3
  74. package/dist/shared.d.ts.map +1 -1
  75. package/dist/shared.js +1 -31
  76. package/dist/store.d.ts +79 -62
  77. package/dist/store.d.ts.map +1 -1
  78. package/dist/store.js +164 -330
  79. package/dist/stored.d.ts +143 -0
  80. package/dist/stored.d.ts.map +1 -0
  81. package/dist/stored.js +561 -0
  82. package/dist/tables.d.ts +246 -168
  83. package/dist/tables.d.ts.map +1 -1
  84. package/dist/tables.js +156 -25
  85. package/dist/turn.d.ts +1 -1
  86. package/dist/turn.d.ts.map +1 -1
  87. package/dist/turn.js +28 -20
  88. package/dist/validation.d.ts +30 -0
  89. package/dist/validation.d.ts.map +1 -0
  90. package/dist/validation.js +162 -0
  91. package/dist/windows.d.ts +2 -1
  92. package/dist/windows.d.ts.map +1 -1
  93. package/dist/windows.js +30 -25
  94. package/package.json +2 -3
  95. package/dist/read-side.d.ts +0 -27
  96. package/dist/read-side.d.ts.map +0 -1
  97. package/dist/read-side.js +0 -1179
package/dist/read-side.js DELETED
@@ -1,1179 +0,0 @@
1
- import { and, coalesce, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, inArray, not, or, } from '@tanstack/db';
2
- import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
3
- import { rowsOf, } from './observation.js';
4
- import { documentKey } from './rows.js';
5
- import { after, inUnits, ROW_WEIGHT, stepped } from './turn.js';
6
- import { beyond, cursorOf, FROM_START, MAXIMUM_WINDOW } from './windows.js';
7
- function every(expressions) {
8
- const [first, second, ...rest] = expressions;
9
- return second === undefined ? first : and(first, second, ...rest);
10
- }
11
- function same(held, value) {
12
- return Array.isArray(held) ? held.includes(value) : held === value;
13
- }
14
- function ordered(held, [operator, bound]) {
15
- if (typeof held !== typeof bound || held === null || typeof held === 'object')
16
- return false;
17
- const left = held;
18
- const right = bound;
19
- if (operator === 'gt')
20
- return left > right;
21
- if (operator === 'gte')
22
- return left >= right;
23
- if (operator === 'lt')
24
- return left < right;
25
- return left <= right;
26
- }
27
- function satisfied(test, held) {
28
- if (test.op === 'eq')
29
- return same(held, test.value);
30
- if (test.op === 'ne')
31
- return !same(held, test.value);
32
- if (test.op === 'in')
33
- return test.values.some((value) => same(held, value));
34
- if (test.op === 'nin')
35
- return !test.values.some((value) => same(held, value));
36
- if (test.op === 'range')
37
- return test.bounds.every((bound) => ordered(held, bound));
38
- if (test.op === 'all')
39
- return Array.isArray(held) && test.values.every((value) => same(held, value));
40
- return Array.isArray(held) && held.length === test.size;
41
- }
42
- function tested(test, reached) {
43
- if (test.op === 'exists')
44
- return test.exists === (reached !== undefined);
45
- if (reached === undefined)
46
- return test.op === 'ne' || test.op === 'nin';
47
- return reached.some((held) => satisfied(test, held));
48
- }
49
- function conditioned(condition, row) {
50
- if ('every' in condition)
51
- return condition.every.every((item) => conditioned(item, row));
52
- if ('some' in condition)
53
- return condition.some.some((item) => conditioned(item, row));
54
- return tested(condition.test, row.reached[condition.field]);
55
- }
56
- function narrowed(query, where, alias) {
57
- return where === null ? query : query.fn.where((row) => conditioned(where, row[alias]));
58
- }
59
- function reached(reach, locked, room, document) {
60
- return [
61
- ...(reach === null
62
- ? []
63
- : [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))]),
64
- ...(locked.length === 0 ? [] : [not(inArray(room, [...locked]))]),
65
- ];
66
- }
67
- function scoped(scope, row, rooms, reach, locked, paths = null, folder = null) {
68
- return every([
69
- eq(row.kind, 'document'),
70
- ...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
71
- ...(paths === null ? [] : [inArray(row.path, [...paths])]),
72
- ...(folder === null ? [] : [eq(row.folder, folder)]),
73
- ...reached(reach, locked, row.room, row.key),
74
- ...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
75
- ...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
76
- ]);
77
- }
78
- function keyed(key, row) {
79
- return key.of === 'field' ? row.values[key.field] : row[key.of];
80
- }
81
- function readied(live) {
82
- return new Promise((resolve) => {
83
- live.onFirstReady(resolve);
84
- });
85
- }
86
- function streamed(value) {
87
- return coalesce(value);
88
- }
89
- function live(query) {
90
- return createLiveQueryCollection({
91
- query: query,
92
- startSync: true,
93
- gcTime: 0,
94
- });
95
- }
96
- function related(q, rows, scope, field, kept, reach, locked) {
97
- const matched = kept === 'source' ? 'target' : 'source';
98
- const from = q.from({ r: rows });
99
- const joined = from
100
- .where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [inArray(field, r.forms)])]))
101
- .innerJoin({ m: rows }, ({ r, m }) => eq(streamed(r[matched]), streamed(m.key)))
102
- .where(({ m }) => scoped(scope, m, null, reach, locked));
103
- return narrowed(joined, scope.where, 'm')
104
- .groupBy(({ r }) => r[kept])
105
- .select(({ r }) => ({ key: r[kept] }));
106
- }
107
- function filtered(q, rows, plan) {
108
- const from = q.from({ d: rows });
109
- let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach, plan.locked, plan.paths, plan.folder)), plan.root.where, 'd');
110
- if (plan.hop !== null) {
111
- const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach, plan.locked);
112
- query = query.innerJoin({ h: hop }, ({ d, h }) => eq(streamed(d.key), streamed(h.key)));
113
- }
114
- if (plan.backlinks !== null) {
115
- const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach, plan.locked);
116
- query = query.innerJoin({ k: backlinks }, ({ d, k }) => eq(streamed(d.key), streamed(k.key)));
117
- }
118
- return query;
119
- }
120
- function bodied(shape) {
121
- return shape.sections !== null || shape.preamble || shape.markdown;
122
- }
123
- function orderingOf(options) {
124
- const collated = options.stringSort === 'locale';
125
- return {
126
- direction: options.direction,
127
- nulls: options.nulls ?? 'first',
128
- stringSort: options.stringSort ?? 'locale',
129
- locale: collated ? options.locale : undefined,
130
- localeOptions: collated
131
- ? options.localeOptions
132
- : undefined,
133
- };
134
- }
135
- function storedValue(key, row) {
136
- return key.of === 'field' ? row.values[key.field] : row[key.of];
137
- }
138
- function documentOrder(shape) {
139
- return shape.orderBy.map(({ options }) => orderingOf(options));
140
- }
141
- function documentCursor(shape, row) {
142
- return cursorOf([
143
- ...shape.orderBy.map((_, index) => row[`order${index}`]),
144
- documentKey(row.room, row.path),
145
- ]);
146
- }
147
- function documents(q, rows, plan, shape) {
148
- let query = filtered(q, rows, plan);
149
- const cursor = plan.after?.values ?? null;
150
- if (cursor !== null) {
151
- const orderings = documentOrder(shape);
152
- query = query.fn.where(({ d }) => beyond([...shape.orderBy.map(({ key }) => storedValue(key, d)), d.key], cursor, orderings));
153
- }
154
- for (const { key, options } of shape.orderBy) {
155
- query = query.orderBy(({ d }) => streamed(keyed(key, d)), options);
156
- }
157
- if (shape.orderBy.length > 0 || plan.limit !== null || plan.after !== null) {
158
- query = query.orderBy(({ d }) => streamed(d.key), { stringSort: 'lexical' });
159
- }
160
- if (plan.limit !== null)
161
- query = query.limit(plan.limit + 1);
162
- return query.select(({ d }) => ({
163
- room: d.room,
164
- path: d.path,
165
- type: d.type,
166
- binary: d.binary,
167
- ...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
168
- ...(shape.fields === null ? {} : { fields: d.fields }),
169
- ...(bodied(shape) ? { text: d.text } : {}),
170
- ...(shape.change ? { change: d.change } : {}),
171
- ...(shape.fileId ? { fileId: d.fileId } : {}),
172
- }));
173
- }
174
- function references(q, rows, reach, locked) {
175
- const from = q.from({ r: rows });
176
- return from
177
- .where(({ r }) => every([eq(r.kind, 'reference'), ...reached(reach, locked, r.room, r.source)]))
178
- .select(({ r }) => ({
179
- key: r.key,
180
- target: r.target,
181
- path: r.path,
182
- type: r.type,
183
- field: r.field,
184
- }));
185
- }
186
- function lexical(left, right) {
187
- return left < right ? -1 : left > right ? 1 : 0;
188
- }
189
- function linkOrder([leftKey, left], [rightKey, right]) {
190
- return (lexical(left.path, right.path) || lexical(left.field, right.field) || lexical(leftKey, rightKey));
191
- }
192
- function linkAt(held, entry) {
193
- let low = 0;
194
- for (let high = held.length; low < high;) {
195
- const middle = (low + high) >>> 1;
196
- if (linkOrder(held[middle], entry) < 0)
197
- low = middle + 1;
198
- else
199
- high = middle;
200
- }
201
- return low;
202
- }
203
- function targetOf(change) {
204
- return change.value.target;
205
- }
206
- function relink(linked, { type, key, value }) {
207
- const row = value;
208
- const entry = [String(key), { path: row.path, type: row.type, field: row.field }];
209
- const held = linked.get(row.target) ?? [];
210
- const at = linkAt(held, entry);
211
- const present = held[at]?.[0] === entry[0];
212
- const shifted = (type === 'delete') === present ? held.length - at : 0;
213
- if (type === 'delete') {
214
- if (present)
215
- held.splice(at, 1);
216
- }
217
- else if (present)
218
- held[at] = entry;
219
- else
220
- held.splice(at, 0, entry);
221
- if (held.length === 0)
222
- linked.delete(row.target);
223
- else
224
- linked.set(row.target, held);
225
- return shifted;
226
- }
227
- function relinked(linked, changes) {
228
- return strides(changes, (change) => ROW_WEIGHT + relink(linked, change) / HELD_PER_WEIGHT);
229
- }
230
- function linking(links, received) {
231
- return links.subscribeChanges(received, { includeInitialState: true });
232
- }
233
- function groups(q, stage, shape) {
234
- const keys = shape.groupBy.map((_, index) => `key${index}`);
235
- const from = q.from({ m: stage });
236
- let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
237
- for (const { by, direction } of shape.orderBy) {
238
- query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
239
- }
240
- return query.select(({ m }) => ({
241
- ...Object.fromEntries(keys.map((key) => [key, m[key]])),
242
- count: count(m.key),
243
- }));
244
- }
245
- function built(rows, plan) {
246
- const shape = plan.shape;
247
- if (shape.kind === 'documents') {
248
- const collection = live((q) => documents(q, rows, plan, shape));
249
- const count = plan.total
250
- ? live((q) => filtered(q, rows, plan).select(({ d }) => ({ key: d.key })))
251
- : null;
252
- const links = shape.backlinks
253
- ? live((q) => references(q, rows, plan.reach, plan.locked))
254
- : null;
255
- return {
256
- live: collection,
257
- count,
258
- links,
259
- dispose: () => {
260
- void collection.cleanup();
261
- if (count !== null)
262
- void count.cleanup();
263
- if (links !== null)
264
- void links.cleanup();
265
- },
266
- };
267
- }
268
- const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
269
- key: d.key,
270
- ...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
271
- })));
272
- const collection = live((q) => groups(q, stage, shape));
273
- return {
274
- live: collection,
275
- count: null,
276
- links: null,
277
- dispose: () => {
278
- void collection.cleanup();
279
- void stage.cleanup();
280
- },
281
- };
282
- }
283
- function sectionsOf(sections, names) {
284
- if (names === 'all')
285
- return sections;
286
- return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
287
- }
288
- function bodyOf(row, key, bodies) {
289
- const text = row.text;
290
- if (text === null)
291
- return null;
292
- const held = bodies?.get(key);
293
- if (held !== undefined && held.text === text)
294
- return held.document;
295
- const read = parse(text, row.path);
296
- const document = read.ok ? read.document : null;
297
- bodies?.set(key, { text, document });
298
- return document;
299
- }
300
- function masked(pointers) {
301
- const root = { whole: false, members: new Map() };
302
- for (const pointer of pointers) {
303
- let mask = root;
304
- for (const token of pointerSegments(pointer)) {
305
- const next = mask.members.get(token) ?? { whole: false, members: new Map() };
306
- mask.members.set(token, next);
307
- mask = next;
308
- }
309
- mask.whole = true;
310
- }
311
- return root;
312
- }
313
- function merged(left, right) {
314
- if (left === undefined)
315
- return right;
316
- const members = new Map(left.members);
317
- for (const [token, mask] of right.members)
318
- members.set(token, merged(members.get(token), mask));
319
- return { whole: left.whole || right.whole, members };
320
- }
321
- function cut(value, mask) {
322
- if (mask.whole)
323
- return value;
324
- if (Array.isArray(value)) {
325
- const each = {
326
- whole: false,
327
- members: new Map([...mask.members].filter(([token]) => !ARRAY_INDEX.test(token))),
328
- };
329
- const kept = [];
330
- value.forEach((item, index) => {
331
- const part = cut(item, merged(mask.members.get(String(index)), each));
332
- if (part !== undefined)
333
- kept[index] = part;
334
- });
335
- return kept.length === 0 ? undefined : kept;
336
- }
337
- if (typeof value !== 'object' || value === null)
338
- return undefined;
339
- const kept = Object.entries(value).flatMap(([key, held]) => {
340
- const member = mask.members.get(key);
341
- const part = member === undefined ? undefined : cut(held, member);
342
- return part === undefined ? [] : [[key, part]];
343
- });
344
- return kept.length === 0 ? undefined : Object.fromEntries(kept);
345
- }
346
- function selected(fields, pointers) {
347
- return (cut(fields, masked(pointers)) ?? {});
348
- }
349
- function documentOf(key, row, shape, bodies, linked) {
350
- if (row.type === null) {
351
- const binary = row.binary;
352
- return {
353
- room: row.room,
354
- path: row.path,
355
- ...(binary === null ? {} : { binaryFile: binary }),
356
- ...(shape.fileId ? { fileId: row.fileId } : {}),
357
- };
358
- }
359
- const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
360
- return {
361
- room: row.room,
362
- path: row.path,
363
- type: row.type,
364
- ...(shape.fields === null
365
- ? {}
366
- : {
367
- fields: shape.fields === 'all'
368
- ? row.fields
369
- : selected(row.fields, shape.fields),
370
- }),
371
- ...(shape.sections === null || body === null
372
- ? {}
373
- : { sections: sectionsOf(body.sections, shape.sections) }),
374
- ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
375
- ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
376
- ...(shape.backlinks
377
- ? {
378
- backlinks: (linked.get(documentKey(row.room, row.path)) ?? [])
379
- .slice(0, MAXIMUM_WINDOW)
380
- .map(([, link]) => link),
381
- }
382
- : {}),
383
- ...(shape.change ? { change: row.change } : {}),
384
- ...(shape.fileId ? { fileId: row.fileId } : {}),
385
- };
386
- }
387
- function valueAt(row, path) {
388
- let value = row;
389
- for (const part of path)
390
- value = value?.[part];
391
- return value;
392
- }
393
- function weight(row) {
394
- return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
395
- }
396
- const HELD_PER_WEIGHT = 16;
397
- const STRIDE = ROW_WEIGHT * 16;
398
- function* strides(items, each) {
399
- let spent = 0;
400
- for (const item of items) {
401
- spent += each(item);
402
- if (spent < STRIDE)
403
- continue;
404
- yield spent;
405
- spent = 0;
406
- }
407
- if (spent > 0)
408
- yield spent;
409
- }
410
- function joined(plan) {
411
- return (plan.hop !== null ||
412
- plan.backlinks !== null ||
413
- (plan.shape.kind === 'documents' && plan.shape.backlinks));
414
- }
415
- function admission(plan) {
416
- if (joined(plan))
417
- return () => true;
418
- const rooms = plan.rooms === null ? null : new Set(plan.rooms);
419
- const paths = plan.paths === null ? null : new Set(plan.paths);
420
- return (row) => row.kind === 'document' &&
421
- (rooms === null || rooms.has(row.room)) &&
422
- (paths === null || paths.has(row.path));
423
- }
424
- function groupValues(shape, [key, row]) {
425
- return [...shape.orderBy.map(({ by }) => (by === 'count' ? row.count : row[`key${by}`])), key];
426
- }
427
- function groupOrder(shape) {
428
- return shape.orderBy.map(({ direction }) => ({
429
- direction,
430
- nulls: 'first',
431
- stringSort: 'locale',
432
- locale: undefined,
433
- localeOptions: undefined,
434
- }));
435
- }
436
- function* windowOf(plan, keys, valueOf, total, each) {
437
- const shape = plan.shape;
438
- const cursor = shape.kind === 'groups' ? (plan.after?.values ?? null) : null;
439
- const orderings = shape.kind === 'groups' ? groupOrder(shape) : [];
440
- let all = 0;
441
- let following = 0;
442
- let last;
443
- yield* strides(keys, (key) => {
444
- all += 1;
445
- const admitted = shape.kind === 'documents' ||
446
- cursor === null ||
447
- beyond(groupValues(shape, [key, valueOf(key)]), cursor, orderings);
448
- if (admitted)
449
- following += 1;
450
- if (!admitted || (plan.limit !== null && following > plan.limit))
451
- return ROW_WEIGHT;
452
- last = key;
453
- return each(key);
454
- });
455
- const next = plan.limit === null || following <= plan.limit
456
- ? undefined
457
- : last === undefined
458
- ? (plan.after?.text ?? FROM_START)
459
- : shape.kind === 'documents'
460
- ? documentCursor(shape, valueOf(last))
461
- : cursorOf(groupValues(shape, [last, valueOf(last)]));
462
- return { next, total: shape.kind === 'documents' ? total : plan.total ? all : undefined };
463
- }
464
- function framed(rows, { next, total }) {
465
- return {
466
- ...rows,
467
- ...(next === undefined ? {} : { next }),
468
- ...(total === undefined ? {} : { total }),
469
- };
470
- }
471
- function rowOf(shape, [key, row], bodies, linked) {
472
- if (shape.kind === 'documents')
473
- return documentOf(key, row, shape, bodies, linked);
474
- return { key: shape.groupBy.map((_, index) => row[`key${index}`]), count: row.count };
475
- }
476
- function resultOf(grouped, rows, coverage, window) {
477
- return grouped
478
- ? framed({ groups: rows, coverage }, window)
479
- : framed({ documents: rows, coverage }, window);
480
- }
481
- function* rowsOfPlan(plan, keys, valueOf, total, linked) {
482
- const rows = [];
483
- const window = yield* windowOf(plan, keys, valueOf, total, (key) => {
484
- const value = valueOf(key);
485
- rows.push(structuredClone(rowOf(plan.shape, [key, value], null, linked)));
486
- return weight(value);
487
- });
488
- return plan.shape.kind === 'groups'
489
- ? framed({ groups: rows }, window)
490
- : framed({ documents: rows }, window);
491
- }
492
- function textOf(row) {
493
- if (!('path' in row))
494
- return 0;
495
- let length = (row.markdown?.length ?? 0) + (row.preamble?.length ?? 0);
496
- for (const section of Object.values(row.sections ?? {}))
497
- length += section.length;
498
- return length;
499
- }
500
- export function* reconciled(previous, next) {
501
- const before = previous.result;
502
- const after = next.result;
503
- if ('pending' in before || 'pending' in after) {
504
- if (deepEquals(before, after))
505
- return null;
506
- return { keyed: next, message: next };
507
- }
508
- const earlier = rowsOf(before);
509
- const held = new Map();
510
- yield* strides(previous.keys.entries(), ([index, key]) => {
511
- held.set(key, earlier[index]);
512
- return ROW_WEIGHT;
513
- });
514
- const later = rowsOf(after);
515
- const present = new Set();
516
- const changed = [];
517
- const rows = [];
518
- yield* strides(next.keys.entries(), ([index, key]) => {
519
- const row = later[index];
520
- const kept = held.get(key);
521
- present.add(key);
522
- if (kept !== undefined && deepEquals(kept, row))
523
- rows.push(kept);
524
- else {
525
- changed.push([key, row]);
526
- rows.push(row);
527
- }
528
- return ROW_WEIGHT + textOf(row);
529
- });
530
- const keyed = {
531
- result: resultOf('groups' in after, rows, after.coverage, {
532
- next: after.next,
533
- total: after.total,
534
- }),
535
- keys: next.keys,
536
- };
537
- const message = yield* changeOf(previous, keyed, changed, (key) => present.has(key));
538
- return message === null ? null : { keyed, message };
539
- }
540
- function* changeOf(previous, next, changed, present, dropped = () => { }) {
541
- const before = previous.result;
542
- const after = next.result;
543
- const removed = [];
544
- let at = 0;
545
- let moved = false;
546
- yield* strides(previous.keys.entries(), ([index, key]) => {
547
- if (present(key, index)) {
548
- moved ||= next.keys[at] !== key;
549
- at += 1;
550
- }
551
- else {
552
- removed.push(key);
553
- dropped(key);
554
- }
555
- return ROW_WEIGHT;
556
- });
557
- moved ||= at !== next.keys.length;
558
- const covered = !deepEquals(before.coverage, after.coverage);
559
- const framing = reframed(before, after);
560
- if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
561
- return null;
562
- }
563
- return {
564
- changed,
565
- removed,
566
- order: moved ? next.keys : null,
567
- ...(covered ? { coverage: after.coverage } : {}),
568
- ...framing,
569
- };
570
- }
571
- function reframed(before, after) {
572
- const next = before.next === after.next ? {} : { next: after.next ?? null };
573
- const total = before.total === after.total || after.total === undefined ? {} : { total: after.total };
574
- return Object.keys(next).length === 0 && Object.keys(total).length === 0
575
- ? null
576
- : { ...next, ...total };
577
- }
578
- export function createReadSide() {
579
- configureIndexDevMode({ enabled: false });
580
- const rows = new Map();
581
- const roomPaths = new Map();
582
- const views = new Set();
583
- const shared = new Map();
584
- let writing = false;
585
- let publishing = null;
586
- let closed = false;
587
- function viewOf(plan) {
588
- let sync;
589
- const view = {
590
- plan,
591
- rows: createCollection({
592
- getKey: (row) => row.key,
593
- gcTime: 0,
594
- startSync: true,
595
- autoIndex: 'off',
596
- sync: {
597
- rowUpdateMode: 'full',
598
- sync: (params) => {
599
- sync = params;
600
- },
601
- },
602
- }),
603
- sync,
604
- admits: admission(plan),
605
- stopped: false,
606
- waiting: null,
607
- };
608
- views.add(view);
609
- return view;
610
- }
611
- function freeze(view) {
612
- view.waiting ??= new Map();
613
- }
614
- function thaw(view) {
615
- const waiting = view.waiting;
616
- if (waiting === null)
617
- return;
618
- let unit = new Map();
619
- return after(inUnits(waiting, ([key, row]) => {
620
- waiting.delete(key);
621
- unit.set(key, row);
622
- const was = view.rows.get(key);
623
- return weight(row ?? undefined) + charged(view, was, row ?? undefined);
624
- }, () => {
625
- synced(view, unit);
626
- unit = new Map();
627
- }), () => {
628
- if (waiting.size > 0)
629
- return thaw(view);
630
- view.waiting = null;
631
- });
632
- }
633
- function dropped(view) {
634
- view.stopped = true;
635
- views.delete(view);
636
- void view.rows.cleanup();
637
- }
638
- function* offered(view) {
639
- const { plan } = view;
640
- const { rooms, paths } = plan;
641
- if (joined(plan) || rooms === null || paths === null) {
642
- for (const key of rows.keys()) {
643
- if (view.stopped)
644
- return;
645
- yield key;
646
- }
647
- return;
648
- }
649
- for (const room of rooms) {
650
- for (const path of paths) {
651
- if (view.stopped)
652
- return;
653
- yield documentKey(room, path);
654
- }
655
- }
656
- }
657
- function written(view, writes) {
658
- if (view.waiting === null)
659
- synced(view, writes);
660
- else
661
- for (const [key, row] of writes)
662
- view.waiting.set(key, row);
663
- }
664
- function synced(view, writes) {
665
- if (view.stopped || writes.size === 0)
666
- return;
667
- view.sync.begin();
668
- for (const [key, row] of writes) {
669
- if (row !== null) {
670
- view.sync.write({ type: view.rows.has(key) ? 'update' : 'insert', value: row });
671
- }
672
- else if (view.rows.has(key))
673
- view.sync.write({ type: 'delete', key });
674
- }
675
- view.sync.commit();
676
- }
677
- function filled(view) {
678
- let unit = new Map();
679
- return after(inUnits(offered(view), (key) => {
680
- const row = rows.get(key);
681
- if (row === undefined || !view.admits(row) || view.rows.has(key))
682
- return ROW_WEIGHT;
683
- unit.set(key, row);
684
- return weight(row) + charged(view, undefined, row);
685
- }, () => {
686
- written(view, unit);
687
- unit = new Map();
688
- }), () => {
689
- if (!view.stopped)
690
- view.sync.markReady();
691
- });
692
- }
693
- function searched(row) {
694
- const read = row.text === null ? null : parse(row.text, row.path);
695
- if (read === null || !read.ok)
696
- return null;
697
- return {
698
- room: row.room,
699
- path: row.path,
700
- type: row.type,
701
- fields: row.fields,
702
- preamble: read.document.preamble,
703
- sections: read.document.sections,
704
- };
705
- }
706
- function counted(entry) {
707
- return entry.built.count === null ? undefined : entry.built.count.size;
708
- }
709
- function* delivery(entry, whole, dirty, touched, relinking, coverage) {
710
- const { plan } = entry;
711
- const live = entry.built.live;
712
- const valueOf = (key) => live.get(key);
713
- const previous = entry.result;
714
- const answered = previous === undefined || 'pending' in previous.result ? null : previous;
715
- const answeredKeys = answered?.keys ?? [];
716
- const answeredRows = answered === null ? [] : rowsOf(answered.result);
717
- const matched = new Uint8Array(answeredKeys.length);
718
- const generation = (entry.generation += 1);
719
- const keys = [];
720
- const changed = [];
721
- const rows = [];
722
- let at = 0;
723
- const touches = (key) => {
724
- const { room, path } = valueOf(key);
725
- return touched.has(documentKey(room, path));
726
- };
727
- const behind = (key) => !live.has(key) || entry.rows.get(key)?.at === generation;
728
- yield* relinked(entry.linked, relinking);
729
- const window = yield* windowOf(plan, live.keys(), valueOf, counted(entry), (key) => {
730
- let skipped = 0;
731
- while (skipped < STRIDE &&
732
- at < answeredKeys.length &&
733
- answeredKeys[at] !== key &&
734
- behind(answeredKeys[at])) {
735
- at += 1;
736
- skipped += ROW_WEIGHT;
737
- }
738
- let holder;
739
- let prior;
740
- if (at < answeredKeys.length && answeredKeys[at] === key) {
741
- prior = answeredRows[at];
742
- matched[at] = 1;
743
- at += 1;
744
- }
745
- else {
746
- holder = entry.rows.get(key);
747
- prior = holder?.row;
748
- if (holder !== undefined)
749
- holder.at = generation;
750
- }
751
- let row = prior;
752
- let spent = ROW_WEIGHT;
753
- if (row === undefined || whole || dirty.has(key) || (touched.size > 0 && touches(key))) {
754
- const value = valueOf(key);
755
- const renewed = rowOf(plan.shape, [key, value], entry.bodies, entry.linked);
756
- if (row === undefined || !deepEquals(row, renewed))
757
- row = renewed;
758
- spent = weight(value);
759
- }
760
- if (row !== prior) {
761
- holder ??= entry.rows.get(key);
762
- if (holder === undefined)
763
- entry.rows.set(key, { row, at: generation });
764
- else
765
- holder.row = row;
766
- changed.push([key, row]);
767
- }
768
- keys.push(key);
769
- rows.push(row);
770
- return skipped + spent;
771
- });
772
- const result = {
773
- result: resultOf(plan.shape.kind === 'groups', rows, coverage, window),
774
- keys,
775
- };
776
- if (previous === undefined)
777
- return { result, message: null };
778
- if (answered === null)
779
- return { result, message: result };
780
- const message = yield* changeOf(answered, result, changed, (key, index) => matched[index] === 1 || entry.rows.get(key)?.at === generation, (key) => {
781
- entry.rows.delete(key);
782
- entry.bodies.delete(key);
783
- });
784
- return { result, message };
785
- }
786
- function delivered(entry, { result, message }) {
787
- entry.result = result;
788
- if (message !== null)
789
- for (const listener of entry.ready)
790
- listener(message);
791
- for (const listener of entry.fresh) {
792
- entry.ready.add(listener);
793
- listener(result);
794
- }
795
- entry.fresh.clear();
796
- }
797
- function run(entry) {
798
- while (entry.listeners.size > 0 &&
799
- (entry.whole ||
800
- entry.dirty.size > 0 ||
801
- entry.touched.size > 0 ||
802
- entry.stale ||
803
- entry.fresh.size > 0)) {
804
- entry.stale = false;
805
- const cover = entry.cover();
806
- if ('pending' in cover) {
807
- entry.whole = true;
808
- entry.dirty.clear();
809
- entry.touched.clear();
810
- entry.rows.clear();
811
- entry.bodies.clear();
812
- const result = { result: cover, keys: [] };
813
- const previous = entry.result;
814
- const same = previous === undefined || deepEquals(previous.result, cover);
815
- delivered(entry, { result, message: same ? null : result });
816
- break;
817
- }
818
- const whole = entry.whole ||
819
- entry.plan.shape.kind === 'groups' ||
820
- entry.result === undefined ||
821
- 'pending' in entry.result.result;
822
- const dirty = entry.dirty;
823
- const touched = entry.touched;
824
- const relinking = entry.relinking;
825
- entry.dirty = new Set();
826
- entry.touched = new Set();
827
- entry.relinking = [];
828
- entry.whole = false;
829
- freeze(entry.view);
830
- let made;
831
- try {
832
- made = stepped(delivery(entry, whole, dirty, touched, relinking, cover.coverage));
833
- }
834
- catch (error) {
835
- void thaw(entry.view);
836
- throw error;
837
- }
838
- if (made instanceof Promise) {
839
- void made
840
- .then((delivering) => {
841
- if (entry.listeners.size > 0)
842
- delivered(entry, delivering);
843
- })
844
- .finally(() => thaw(entry.view))
845
- .then(() => {
846
- entry.running = false;
847
- wake(entry);
848
- }, () => {
849
- entry.running = false;
850
- });
851
- return;
852
- }
853
- void thaw(entry.view);
854
- delivered(entry, made);
855
- }
856
- entry.running = false;
857
- }
858
- function wake(entry) {
859
- if (entry.subscription === null || entry.running || writing || publishing !== null)
860
- return;
861
- entry.running = true;
862
- try {
863
- run(entry);
864
- }
865
- catch (error) {
866
- entry.running = false;
867
- throw error;
868
- }
869
- }
870
- function recover() {
871
- for (const entry of shared.values()) {
872
- if (entry.result === undefined || entry.subscription === null)
873
- continue;
874
- const cover = entry.cover();
875
- const before = entry.result.result;
876
- const held = 'pending' in cover
877
- ? deepEquals(before, cover)
878
- : 'coverage' in before && deepEquals(before.coverage, cover.coverage);
879
- if (held)
880
- continue;
881
- entry.stale = true;
882
- wake(entry);
883
- }
884
- }
885
- function listed(key, row) {
886
- const held = rows.get(key) ?? row;
887
- if (held?.kind !== 'document')
888
- return;
889
- const paths = roomPaths.get(held.room) ?? new Set();
890
- if (row?.kind === 'document' && row.type !== null)
891
- paths.add(key);
892
- else
893
- paths.delete(key);
894
- if (paths.size === 0)
895
- roomPaths.delete(held.room);
896
- else
897
- roomPaths.set(held.room, paths);
898
- }
899
- function committed(next) {
900
- if (closed || next.size === 0)
901
- return;
902
- for (const [key, row] of next) {
903
- listed(key, row);
904
- if (row === null)
905
- rows.delete(key);
906
- else
907
- rows.set(key, row);
908
- }
909
- for (const view of views) {
910
- const writes = new Map();
911
- for (const [key, row] of next) {
912
- if (row !== null && view.admits(row))
913
- writes.set(key, row);
914
- else if (view.rows.has(key) || view.waiting?.has(key))
915
- writes.set(key, null);
916
- }
917
- written(view, writes);
918
- }
919
- }
920
- function moved({ plan }, before, after) {
921
- const shape = plan.shape;
922
- if (shape.kind === 'documents' && plan.limit === null && shape.orderBy.length > 0) {
923
- return true;
924
- }
925
- const keys = shape.kind === 'documents' ? shape.orderBy.map(({ key }) => key) : shape.groupBy;
926
- for (const key of keys) {
927
- const path = key.of === 'field' ? ['values', key.field] : [key.of];
928
- if (!deepEquals(valueAt(before, path), valueAt(after, path)))
929
- return true;
930
- }
931
- return false;
932
- }
933
- function charged(view, was, row) {
934
- const kept = was !== undefined && view.admits(was);
935
- const admitted = row !== undefined && view.admits(row);
936
- if (!kept && !admitted)
937
- return 0;
938
- const shifted = view.rows.size / HELD_PER_WEIGHT;
939
- return shifted + (kept && admitted && !moved(view, was, row) ? 0 : shifted);
940
- }
941
- function charges(was, row) {
942
- let spent = 0;
943
- for (const view of views)
944
- spent += charged(view, was, row);
945
- return spent;
946
- }
947
- function published(set) {
948
- let unit = new Map();
949
- return inUnits(set.documents, (change) => {
950
- if (closed)
951
- return 0;
952
- const { before, after } = change;
953
- const kept = new Set(after.map((row) => row.key));
954
- const held = new Map(before.map((row) => [row.key, row]));
955
- let spent = 0;
956
- for (const row of before) {
957
- unit.set(row.key, null);
958
- spent += ROW_WEIGHT + (kept.has(row.key) ? 0 : charges(row, undefined));
959
- }
960
- for (const row of after) {
961
- unit.set(row.key, row);
962
- spent += weight(row) + charges(held.get(row.key), row);
963
- }
964
- return spent;
965
- }, () => {
966
- committed(unit);
967
- unit = new Map();
968
- });
969
- }
970
- function released() {
971
- recover();
972
- for (const entry of shared.values())
973
- wake(entry);
974
- }
975
- function publishedLater(applied) {
976
- const fence = applied.then(() => { }, () => { });
977
- publishing = fence;
978
- void fence.then(() => {
979
- if (publishing !== fence)
980
- return;
981
- publishing = null;
982
- released();
983
- });
984
- return applied;
985
- }
986
- async function settled() {
987
- while (publishing !== null)
988
- await publishing;
989
- }
990
- return {
991
- apply(set, next = () => { }) {
992
- const pending = publishing;
993
- if (pending !== null || set instanceof Promise) {
994
- return publishedLater(Promise.all([set, pending]).then(([held]) => after(published(held), () => next(held))));
995
- }
996
- writing = true;
997
- let applied;
998
- try {
999
- applied = published(set);
1000
- }
1001
- finally {
1002
- writing = false;
1003
- }
1004
- if (applied instanceof Promise)
1005
- return publishedLater(applied.then(() => next(set)));
1006
- released();
1007
- next(set);
1008
- },
1009
- recover,
1010
- settled,
1011
- async query(plan, at) {
1012
- const view = viewOf(plan);
1013
- const linked = new Map();
1014
- let made = null;
1015
- let following = null;
1016
- try {
1017
- const filling = filled(view);
1018
- made = built(view.rows, plan);
1019
- const { live: collection, count, links } = made;
1020
- if (links !== null) {
1021
- following = linking(links, (changes) => {
1022
- for (const change of changes)
1023
- relink(linked, change);
1024
- });
1025
- }
1026
- await filling;
1027
- for (const one of [collection, count, links])
1028
- if (one !== null)
1029
- await readied(one);
1030
- await settled();
1031
- freeze(view);
1032
- following?.unsubscribe();
1033
- following = null;
1034
- const held = at();
1035
- const rows = await stepped(rowsOfPlan(plan, collection.keys(), (key) => collection.get(key), count?.size, linked));
1036
- return { rows, at: held };
1037
- }
1038
- finally {
1039
- following?.unsubscribe();
1040
- made?.dispose();
1041
- dropped(view);
1042
- }
1043
- },
1044
- watch(plan, identity, cover, deliverTo) {
1045
- let entry = shared.get(identity);
1046
- if (entry === undefined) {
1047
- const view = viewOf(plan);
1048
- void filled(view);
1049
- const created = {
1050
- plan,
1051
- view,
1052
- built: built(view.rows, plan),
1053
- cover,
1054
- result: undefined,
1055
- rows: new Map(),
1056
- generation: 0,
1057
- bodies: new Map(),
1058
- listeners: new Set(),
1059
- fresh: new Set(),
1060
- ready: new Set(),
1061
- subscription: null,
1062
- counting: null,
1063
- linking: null,
1064
- linked: new Map(),
1065
- relinking: [],
1066
- touched: new Set(),
1067
- whole: true,
1068
- dirty: new Set(),
1069
- stale: false,
1070
- running: false,
1071
- };
1072
- if (created.built.links !== null) {
1073
- created.linking = linking(created.built.links, (changes) => {
1074
- for (const change of changes) {
1075
- created.relinking.push(change);
1076
- created.touched.add(targetOf(change));
1077
- }
1078
- if (created.subscription !== null)
1079
- queueMicrotask(() => wake(created));
1080
- });
1081
- }
1082
- created.built.live.on('status:change', ({ status }) => {
1083
- if (status !== 'cleaned-up' || created.listeners.size > 0)
1084
- return;
1085
- if (shared.get(identity) === created)
1086
- shared.delete(identity);
1087
- });
1088
- shared.set(identity, created);
1089
- entry = created;
1090
- }
1091
- const observed = entry;
1092
- observed.listeners.add(deliverTo);
1093
- const ready = (then) => observed.built.live.onFirstReady(() => {
1094
- const linked = () => observed.built.links === null ? then() : observed.built.links.onFirstReady(then);
1095
- return observed.built.count === null
1096
- ? linked()
1097
- : observed.built.count.onFirstReady(linked);
1098
- });
1099
- const settled = () => {
1100
- if (observed.built.count === null)
1101
- wake(observed);
1102
- else
1103
- queueMicrotask(() => wake(observed));
1104
- };
1105
- ready(() => {
1106
- if (!observed.listeners.has(deliverTo))
1107
- return;
1108
- observed.fresh.add(deliverTo);
1109
- if (observed.subscription === null) {
1110
- observed.whole = true;
1111
- observed.subscription = observed.built.live.subscribeChanges((changes) => {
1112
- if (observed.plan.shape.kind === 'groups')
1113
- observed.whole = true;
1114
- else
1115
- for (const { key } of changes)
1116
- observed.dirty.add(key);
1117
- settled();
1118
- }, { includeInitialState: false });
1119
- observed.counting =
1120
- observed.built.count?.subscribeChanges(() => {
1121
- observed.stale = true;
1122
- settled();
1123
- }, { includeInitialState: false }) ?? null;
1124
- }
1125
- wake(observed);
1126
- });
1127
- return () => {
1128
- observed.fresh.delete(deliverTo);
1129
- observed.ready.delete(deliverTo);
1130
- if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
1131
- return;
1132
- observed.subscription?.unsubscribe();
1133
- observed.subscription = null;
1134
- observed.counting?.unsubscribe();
1135
- observed.counting = null;
1136
- observed.linking?.unsubscribe();
1137
- observed.linking = null;
1138
- observed.result = undefined;
1139
- observed.rows.clear();
1140
- observed.bodies.clear();
1141
- observed.dirty.clear();
1142
- observed.touched.clear();
1143
- observed.whole = true;
1144
- if (shared.get(identity) === observed)
1145
- shared.delete(identity);
1146
- observed.built.dispose();
1147
- dropped(observed.view);
1148
- };
1149
- },
1150
- *room(room) {
1151
- for (const key of roomPaths.get(room) ?? []) {
1152
- const row = rows.get(key);
1153
- if (row?.kind === 'document')
1154
- yield row.path;
1155
- }
1156
- },
1157
- document(room, path) {
1158
- const row = rows.get(documentKey(room, path));
1159
- return row?.kind !== 'document' || row.type === null ? null : searched(row);
1160
- },
1161
- close() {
1162
- closed = true;
1163
- for (const entry of shared.values()) {
1164
- entry.listeners.clear();
1165
- entry.fresh.clear();
1166
- entry.ready.clear();
1167
- entry.subscription?.unsubscribe();
1168
- entry.counting?.unsubscribe();
1169
- entry.linking?.unsubscribe();
1170
- entry.built.dispose();
1171
- }
1172
- shared.clear();
1173
- for (const view of [...views])
1174
- dropped(view);
1175
- rows.clear();
1176
- roomPaths.clear();
1177
- },
1178
- };
1179
- }