@agentshouse/mdruntime 0.1.0-cc294e382554d → 0.1.0-cc56db96f86ae
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 +28 -23
- package/README.md +9 -8
- package/dist/adapter.d.ts +11 -10
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +60 -29
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +2 -6
- package/dist/change.d.ts +31 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +10 -1
- package/dist/chat/house.d.ts +25 -0
- package/dist/chat/house.d.ts.map +1 -0
- package/dist/chat/house.js +38 -0
- package/dist/chat/input.d.ts +3 -1
- package/dist/chat/input.d.ts.map +1 -1
- package/dist/chat/input.js +11 -18
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +45 -10
- package/dist/chat/messages.d.ts +6 -1
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +14 -11
- package/dist/chat/name.d.ts +1 -0
- package/dist/chat/name.d.ts.map +1 -1
- package/dist/chat/name.js +1 -1
- package/dist/chat/opened.d.ts +1 -0
- package/dist/chat/opened.d.ts.map +1 -1
- package/dist/chat/opened.js +2 -0
- package/dist/chat/overlay.d.ts.map +1 -1
- package/dist/chat/overlay.js +1 -5
- package/dist/chat/parts.d.ts +1 -0
- package/dist/chat/parts.d.ts.map +1 -1
- package/dist/chat/parts.js +3 -2
- package/dist/chat/provider.d.ts +5 -1
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +58 -77
- package/dist/chat/ready.d.ts +4 -1
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +7 -5
- package/dist/chat/room-file.d.ts +8 -0
- package/dist/chat/room-file.d.ts.map +1 -0
- package/dist/chat/room-file.js +67 -0
- package/dist/chat/style.d.ts.map +1 -1
- package/dist/chat/style.js +4 -0
- package/dist/chat.d.ts +1 -0
- package/dist/chat.d.ts.map +1 -1
- package/dist/client.d.ts +4 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/collection.d.ts +2 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +56 -40
- package/dist/connection.d.ts +5 -1
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +723 -158
- package/dist/conversation.d.ts +2 -2
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +18 -29
- package/dist/digest.d.ts +2 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +100 -0
- package/dist/durability.d.ts +1 -1
- package/dist/file.d.ts +2 -10
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +3 -120
- package/dist/host.d.ts +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +7 -3
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +9 -5
- package/dist/outcome.d.ts +20 -1
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +22 -0
- package/dist/overlay.d.ts +1 -1
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +27 -3
- package/dist/query.d.ts +2 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +1 -0
- package/dist/reach.d.ts +5 -1
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +144 -39
- package/dist/read-side.d.ts +4 -4
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +585 -232
- package/dist/readiness.d.ts +1 -1
- package/dist/readiness.d.ts.map +1 -1
- package/dist/recovery.d.ts +2 -2
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +8 -7
- package/dist/refresh.js +8 -8
- package/dist/replica.d.ts +1 -2
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +69 -48
- package/dist/room-draft.d.ts +3 -1
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +21 -13
- package/dist/save.d.ts +43 -7
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +329 -77
- package/dist/search.d.ts +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +61 -10
- package/dist/shared.d.ts +5 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +72 -9
- package/dist/store.d.ts +8 -13
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +189 -230
- package/dist/tables.d.ts +181 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +309 -0
- package/dist/turn.d.ts +1 -1
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +9 -6
- package/dist/windows.d.ts +0 -5
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +0 -9
- package/dist/wire.d.ts +8 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +2 -0
- package/package.json +6 -2
package/dist/read-side.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { and,
|
|
1
|
+
import { and, coalesce, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, inArray, not, or, } from '@tanstack/db';
|
|
2
2
|
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
3
|
+
import { rowsOf, } from './observation.js';
|
|
3
4
|
import { documentKey } from './rows.js';
|
|
4
|
-
import { after, inUnits, ROW_WEIGHT } from './turn.js';
|
|
5
|
-
import { beyond, cursorOf,
|
|
5
|
+
import { after, inUnits, ROW_WEIGHT, stepped } from './turn.js';
|
|
6
|
+
import { beyond, cursorOf, FROM_START, MAXIMUM_WINDOW } from './windows.js';
|
|
6
7
|
function every(expressions) {
|
|
7
8
|
const [first, second, ...rest] = expressions;
|
|
8
9
|
return second === undefined ? first : and(first, second, ...rest);
|
|
@@ -55,18 +56,21 @@ function conditioned(condition, row) {
|
|
|
55
56
|
function narrowed(query, where, alias) {
|
|
56
57
|
return where === null ? query : query.fn.where((row) => conditioned(where, row[alias]));
|
|
57
58
|
}
|
|
58
|
-
function reached(reach, room, document) {
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
];
|
|
62
66
|
}
|
|
63
|
-
function scoped(scope, row, rooms, reach, paths = null, folder = null) {
|
|
67
|
+
function scoped(scope, row, rooms, reach, locked, paths = null, folder = null) {
|
|
64
68
|
return every([
|
|
65
69
|
eq(row.kind, 'document'),
|
|
66
70
|
...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
|
|
67
71
|
...(paths === null ? [] : [inArray(row.path, [...paths])]),
|
|
68
72
|
...(folder === null ? [] : [eq(row.folder, folder)]),
|
|
69
|
-
...reached(reach, row.room, row.key),
|
|
73
|
+
...reached(reach, locked, row.room, row.key),
|
|
70
74
|
...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
|
|
71
75
|
...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
|
|
72
76
|
]);
|
|
@@ -74,33 +78,42 @@ function scoped(scope, row, rooms, reach, paths = null, folder = null) {
|
|
|
74
78
|
function keyed(key, row) {
|
|
75
79
|
return key.of === 'field' ? row.values[key.field] : row[key.of];
|
|
76
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
|
+
}
|
|
77
89
|
function live(query) {
|
|
78
90
|
return createLiveQueryCollection({
|
|
79
91
|
query: query,
|
|
80
92
|
startSync: true,
|
|
93
|
+
gcTime: 0,
|
|
81
94
|
});
|
|
82
95
|
}
|
|
83
|
-
function related(q, rows, scope, field, kept, reach) {
|
|
96
|
+
function related(q, rows, scope, field, kept, reach, locked) {
|
|
84
97
|
const matched = kept === 'source' ? 'target' : 'source';
|
|
85
98
|
const from = q.from({ r: rows });
|
|
86
99
|
const joined = from
|
|
87
100
|
.where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [inArray(field, r.forms)])]))
|
|
88
|
-
.innerJoin({ m: rows }, ({ r, m }) => eq(r[matched], m.key))
|
|
89
|
-
.where(({ m }) => scoped(scope, m, null, reach));
|
|
101
|
+
.innerJoin({ m: rows }, ({ r, m }) => eq(streamed(r[matched]), streamed(m.key)))
|
|
102
|
+
.where(({ m }) => scoped(scope, m, null, reach, locked));
|
|
90
103
|
return narrowed(joined, scope.where, 'm')
|
|
91
104
|
.groupBy(({ r }) => r[kept])
|
|
92
105
|
.select(({ r }) => ({ key: r[kept] }));
|
|
93
106
|
}
|
|
94
107
|
function filtered(q, rows, plan) {
|
|
95
108
|
const from = q.from({ d: rows });
|
|
96
|
-
let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach, plan.paths, plan.folder)), plan.root.where, 'd');
|
|
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');
|
|
97
110
|
if (plan.hop !== null) {
|
|
98
|
-
const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
|
|
99
|
-
query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
|
|
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)));
|
|
100
113
|
}
|
|
101
114
|
if (plan.backlinks !== null) {
|
|
102
|
-
const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach);
|
|
103
|
-
query = query.innerJoin({ k: backlinks }, ({ d, k }) => eq(d.key, k.key));
|
|
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)));
|
|
104
117
|
}
|
|
105
118
|
return query;
|
|
106
119
|
}
|
|
@@ -139,10 +152,10 @@ function documents(q, rows, plan, shape) {
|
|
|
139
152
|
query = query.fn.where(({ d }) => beyond([...shape.orderBy.map(({ key }) => storedValue(key, d)), d.key], cursor, orderings));
|
|
140
153
|
}
|
|
141
154
|
for (const { key, options } of shape.orderBy) {
|
|
142
|
-
query = query.orderBy(({ d }) => keyed(key, d), options);
|
|
155
|
+
query = query.orderBy(({ d }) => streamed(keyed(key, d)), options);
|
|
143
156
|
}
|
|
144
157
|
if (shape.orderBy.length > 0 || plan.limit !== null || plan.after !== null) {
|
|
145
|
-
query = query.orderBy(({ d }) => d.key, { stringSort: 'lexical' });
|
|
158
|
+
query = query.orderBy(({ d }) => streamed(d.key), { stringSort: 'lexical' });
|
|
146
159
|
}
|
|
147
160
|
if (plan.limit !== null)
|
|
148
161
|
query = query.limit(plan.limit + 1);
|
|
@@ -154,18 +167,68 @@ function documents(q, rows, plan, shape) {
|
|
|
154
167
|
...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
|
|
155
168
|
...(shape.fields === null ? {} : { fields: d.fields }),
|
|
156
169
|
...(bodied(shape) ? { text: d.text } : {}),
|
|
157
|
-
...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
|
|
158
170
|
...(shape.change ? { change: d.change } : {}),
|
|
159
171
|
...(shape.fileId ? { fileId: d.fileId } : {}),
|
|
160
172
|
}));
|
|
161
173
|
}
|
|
162
|
-
function
|
|
174
|
+
function references(q, rows, reach, locked) {
|
|
163
175
|
const from = q.from({ r: rows });
|
|
164
176
|
return from
|
|
165
|
-
.where(({ r }) => every([eq(r.
|
|
166
|
-
.
|
|
167
|
-
|
|
168
|
-
|
|
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 });
|
|
169
232
|
}
|
|
170
233
|
function groups(q, stage, shape) {
|
|
171
234
|
const keys = shape.groupBy.map((_, index) => `key${index}`);
|
|
@@ -186,13 +249,19 @@ function built(rows, plan) {
|
|
|
186
249
|
const count = plan.total
|
|
187
250
|
? live((q) => filtered(q, rows, plan).select(({ d }) => ({ key: d.key })))
|
|
188
251
|
: null;
|
|
252
|
+
const links = shape.backlinks
|
|
253
|
+
? live((q) => references(q, rows, plan.reach, plan.locked))
|
|
254
|
+
: null;
|
|
189
255
|
return {
|
|
190
256
|
live: collection,
|
|
191
257
|
count,
|
|
258
|
+
links,
|
|
192
259
|
dispose: () => {
|
|
193
260
|
void collection.cleanup();
|
|
194
261
|
if (count !== null)
|
|
195
262
|
void count.cleanup();
|
|
263
|
+
if (links !== null)
|
|
264
|
+
void links.cleanup();
|
|
196
265
|
},
|
|
197
266
|
};
|
|
198
267
|
}
|
|
@@ -204,6 +273,7 @@ function built(rows, plan) {
|
|
|
204
273
|
return {
|
|
205
274
|
live: collection,
|
|
206
275
|
count: null,
|
|
276
|
+
links: null,
|
|
207
277
|
dispose: () => {
|
|
208
278
|
void collection.cleanup();
|
|
209
279
|
void stage.cleanup();
|
|
@@ -276,7 +346,7 @@ function cut(value, mask) {
|
|
|
276
346
|
function selected(fields, pointers) {
|
|
277
347
|
return (cut(fields, masked(pointers)) ?? {});
|
|
278
348
|
}
|
|
279
|
-
function documentOf(key, row, shape, bodies) {
|
|
349
|
+
function documentOf(key, row, shape, bodies, linked) {
|
|
280
350
|
if (row.type === null) {
|
|
281
351
|
const binary = row.binary;
|
|
282
352
|
return {
|
|
@@ -305,19 +375,52 @@ function documentOf(key, row, shape, bodies) {
|
|
|
305
375
|
...(shape.markdown && body !== null ? { markdown: body.content } : {}),
|
|
306
376
|
...(shape.backlinks
|
|
307
377
|
? {
|
|
308
|
-
backlinks: row.
|
|
378
|
+
backlinks: (linked.get(documentKey(row.room, row.path)) ?? [])
|
|
309
379
|
.slice(0, MAXIMUM_WINDOW)
|
|
310
|
-
.map((
|
|
380
|
+
.map(([, link]) => link),
|
|
311
381
|
}
|
|
312
382
|
: {}),
|
|
313
383
|
...(shape.change ? { change: row.change } : {}),
|
|
314
384
|
...(shape.fileId ? { fileId: row.fileId } : {}),
|
|
315
385
|
};
|
|
316
386
|
}
|
|
387
|
+
function valueAt(row, path) {
|
|
388
|
+
let value = row;
|
|
389
|
+
for (const part of path)
|
|
390
|
+
value = value?.[part];
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
317
393
|
function weight(row) {
|
|
318
394
|
return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
|
|
319
395
|
}
|
|
320
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
|
+
}
|
|
321
424
|
function groupValues(shape, [key, row]) {
|
|
322
425
|
return [...shape.orderBy.map(({ by }) => (by === 'count' ? row.count : row[`key${by}`])), key];
|
|
323
426
|
}
|
|
@@ -330,23 +433,33 @@ function groupOrder(shape) {
|
|
|
330
433
|
localeOptions: undefined,
|
|
331
434
|
}));
|
|
332
435
|
}
|
|
333
|
-
function windowOf(plan,
|
|
436
|
+
function* windowOf(plan, keys, valueOf, total, each) {
|
|
334
437
|
const shape = plan.shape;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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 };
|
|
350
463
|
}
|
|
351
464
|
function framed(rows, { next, total }) {
|
|
352
465
|
return {
|
|
@@ -355,27 +468,36 @@ function framed(rows, { next, total }) {
|
|
|
355
468
|
...(total === undefined ? {} : { total }),
|
|
356
469
|
};
|
|
357
470
|
}
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (shape.kind === 'documents') {
|
|
363
|
-
const documents = [];
|
|
364
|
-
return after(inUnits(kept, ([key, row]) => {
|
|
365
|
-
documents.push(each(documentOf(key, row, shape, bodies)));
|
|
366
|
-
return weight(row);
|
|
367
|
-
}), () => ({ rows: framed({ documents }, window), keys, ...window }));
|
|
368
|
-
}
|
|
369
|
-
const groups = [];
|
|
370
|
-
return after(inUnits(kept, ([, row]) => {
|
|
371
|
-
groups.push(each({
|
|
372
|
-
key: shape.groupBy.map((_, index) => row[`key${index}`]),
|
|
373
|
-
count: row.count,
|
|
374
|
-
}));
|
|
375
|
-
return ROW_WEIGHT;
|
|
376
|
-
}), () => ({ rows: framed({ groups }, window), keys, ...window }));
|
|
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 };
|
|
377
475
|
}
|
|
378
|
-
|
|
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) {
|
|
379
501
|
const before = previous.result;
|
|
380
502
|
const after = next.result;
|
|
381
503
|
if ('pending' in before || 'pending' in after) {
|
|
@@ -383,40 +505,67 @@ export function reconciled(previous, next) {
|
|
|
383
505
|
return null;
|
|
384
506
|
return { keyed: next, message: next };
|
|
385
507
|
}
|
|
386
|
-
const earlier =
|
|
387
|
-
const held = new Map(
|
|
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();
|
|
388
516
|
const changed = [];
|
|
389
|
-
const rows =
|
|
390
|
-
|
|
517
|
+
const rows = [];
|
|
518
|
+
yield* strides(next.keys.entries(), ([index, key]) => {
|
|
519
|
+
const row = later[index];
|
|
391
520
|
const kept = held.get(key);
|
|
521
|
+
present.add(key);
|
|
392
522
|
if (kept !== undefined && deepEquals(kept, row))
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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;
|
|
396
556
|
});
|
|
397
|
-
|
|
398
|
-
const removed = previous.keys.filter((key) => !present.has(key));
|
|
399
|
-
const moved = reordered(previous.keys, next.keys, present);
|
|
557
|
+
moved ||= at !== next.keys.length;
|
|
400
558
|
const covered = !deepEquals(before.coverage, after.coverage);
|
|
401
559
|
const framing = reframed(before, after);
|
|
402
560
|
if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
|
|
403
561
|
return null;
|
|
404
562
|
}
|
|
405
|
-
const window = { next: after.next, total: after.total };
|
|
406
563
|
return {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
},
|
|
413
|
-
message: {
|
|
414
|
-
changed,
|
|
415
|
-
removed,
|
|
416
|
-
order: moved ? next.keys : null,
|
|
417
|
-
...(covered ? { coverage: after.coverage } : {}),
|
|
418
|
-
...framing,
|
|
419
|
-
},
|
|
564
|
+
changed,
|
|
565
|
+
removed,
|
|
566
|
+
order: moved ? next.keys : null,
|
|
567
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
568
|
+
...framing,
|
|
420
569
|
};
|
|
421
570
|
}
|
|
422
571
|
function reframed(before, after) {
|
|
@@ -426,38 +575,121 @@ function reframed(before, after) {
|
|
|
426
575
|
? null
|
|
427
576
|
: { ...next, ...total };
|
|
428
577
|
}
|
|
429
|
-
function reordered(previous, next, present) {
|
|
430
|
-
let at = 0;
|
|
431
|
-
for (const key of previous) {
|
|
432
|
-
if (!present.has(key))
|
|
433
|
-
continue;
|
|
434
|
-
if (next[at] !== key)
|
|
435
|
-
return true;
|
|
436
|
-
at += 1;
|
|
437
|
-
}
|
|
438
|
-
return at !== next.length;
|
|
439
|
-
}
|
|
440
578
|
export function createReadSide() {
|
|
441
579
|
configureIndexDevMode({ enabled: false });
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
gcTime: 0,
|
|
446
|
-
startSync: true,
|
|
447
|
-
autoIndex: 'eager',
|
|
448
|
-
defaultIndexType: BasicIndex,
|
|
449
|
-
sync: {
|
|
450
|
-
rowUpdateMode: 'full',
|
|
451
|
-
sync: (params) => {
|
|
452
|
-
sync = params;
|
|
453
|
-
params.markReady();
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
});
|
|
580
|
+
const rows = new Map();
|
|
581
|
+
const roomPaths = new Map();
|
|
582
|
+
const views = new Set();
|
|
457
583
|
const shared = new Map();
|
|
458
584
|
let writing = false;
|
|
459
585
|
let publishing = null;
|
|
460
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
|
+
}
|
|
461
693
|
function searched(row) {
|
|
462
694
|
const read = row.text === null ? null : parse(row.text, row.path);
|
|
463
695
|
if (read === null || !read.ok)
|
|
@@ -471,80 +703,116 @@ export function createReadSide() {
|
|
|
471
703
|
sections: read.document.sections,
|
|
472
704
|
};
|
|
473
705
|
}
|
|
474
|
-
function kept(entry, key, row) {
|
|
475
|
-
const held = entry.rows.get(key);
|
|
476
|
-
if (held !== undefined && deepEquals(held, row))
|
|
477
|
-
return;
|
|
478
|
-
entry.rows.set(key, row);
|
|
479
|
-
}
|
|
480
706
|
function counted(entry) {
|
|
481
707
|
return entry.built.count === null ? undefined : entry.built.count.size;
|
|
482
708
|
}
|
|
483
|
-
function
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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;
|
|
492
744
|
}
|
|
493
|
-
|
|
494
|
-
|
|
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;
|
|
495
771
|
});
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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) => {
|
|
505
781
|
entry.rows.delete(key);
|
|
506
782
|
entry.bodies.delete(key);
|
|
507
|
-
}
|
|
508
|
-
const values = inWindow.filter(([key]) => dirty.has(key) || !entry.rows.has(key));
|
|
509
|
-
const done = inUnits(values, ([key, value]) => {
|
|
510
|
-
kept(entry, key, documentOf(key, value, shape, entry.bodies));
|
|
511
|
-
return weight(value);
|
|
512
783
|
});
|
|
513
|
-
return
|
|
784
|
+
return { result, message };
|
|
514
785
|
}
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
return {
|
|
518
|
-
result: entry.plan.shape.kind === 'groups'
|
|
519
|
-
? framed({ groups: rows, coverage }, found)
|
|
520
|
-
: framed({ documents: rows, coverage }, found),
|
|
521
|
-
keys: found.keys,
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
function delivered(entry, next) {
|
|
525
|
-
const previous = entry.result;
|
|
526
|
-
entry.result = next;
|
|
527
|
-
const message = previous === undefined ? null : changedFrom(previous, next);
|
|
786
|
+
function delivered(entry, { result, message }) {
|
|
787
|
+
entry.result = result;
|
|
528
788
|
if (message !== null)
|
|
529
789
|
for (const listener of entry.ready)
|
|
530
790
|
listener(message);
|
|
531
791
|
for (const listener of entry.fresh) {
|
|
532
792
|
entry.ready.add(listener);
|
|
533
|
-
listener(
|
|
793
|
+
listener(result);
|
|
534
794
|
}
|
|
535
795
|
entry.fresh.clear();
|
|
536
796
|
}
|
|
537
797
|
function run(entry) {
|
|
538
798
|
while (entry.listeners.size > 0 &&
|
|
539
|
-
(entry.whole ||
|
|
799
|
+
(entry.whole ||
|
|
800
|
+
entry.dirty.size > 0 ||
|
|
801
|
+
entry.touched.size > 0 ||
|
|
802
|
+
entry.stale ||
|
|
803
|
+
entry.fresh.size > 0)) {
|
|
540
804
|
entry.stale = false;
|
|
541
805
|
const cover = entry.cover();
|
|
542
806
|
if ('pending' in cover) {
|
|
543
807
|
entry.whole = true;
|
|
544
808
|
entry.dirty.clear();
|
|
809
|
+
entry.touched.clear();
|
|
545
810
|
entry.rows.clear();
|
|
546
811
|
entry.bodies.clear();
|
|
547
|
-
|
|
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 });
|
|
548
816
|
break;
|
|
549
817
|
}
|
|
550
818
|
const whole = entry.whole ||
|
|
@@ -552,23 +820,38 @@ export function createReadSide() {
|
|
|
552
820
|
entry.result === undefined ||
|
|
553
821
|
'pending' in entry.result.result;
|
|
554
822
|
const dirty = entry.dirty;
|
|
823
|
+
const touched = entry.touched;
|
|
824
|
+
const relinking = entry.relinking;
|
|
555
825
|
entry.dirty = new Set();
|
|
826
|
+
entry.touched = new Set();
|
|
827
|
+
entry.relinking = [];
|
|
556
828
|
entry.whole = false;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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) => {
|
|
561
841
|
if (entry.listeners.size > 0)
|
|
562
|
-
delivered(entry,
|
|
842
|
+
delivered(entry, delivering);
|
|
843
|
+
})
|
|
844
|
+
.finally(() => thaw(entry.view))
|
|
845
|
+
.then(() => {
|
|
563
846
|
entry.running = false;
|
|
564
847
|
wake(entry);
|
|
565
|
-
})
|
|
566
|
-
.catch(() => {
|
|
848
|
+
}, () => {
|
|
567
849
|
entry.running = false;
|
|
568
850
|
});
|
|
569
851
|
return;
|
|
570
852
|
}
|
|
571
|
-
|
|
853
|
+
void thaw(entry.view);
|
|
854
|
+
delivered(entry, made);
|
|
572
855
|
}
|
|
573
856
|
entry.running = false;
|
|
574
857
|
}
|
|
@@ -599,34 +882,84 @@ export function createReadSide() {
|
|
|
599
882
|
wake(entry);
|
|
600
883
|
}
|
|
601
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
|
+
}
|
|
602
899
|
function committed(next) {
|
|
603
900
|
if (closed || next.size === 0)
|
|
604
901
|
return;
|
|
605
|
-
sync.begin();
|
|
606
902
|
for (const [key, row] of next) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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);
|
|
611
916
|
}
|
|
612
|
-
|
|
917
|
+
written(view, writes);
|
|
613
918
|
}
|
|
614
|
-
|
|
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;
|
|
615
946
|
}
|
|
616
947
|
function published(set) {
|
|
617
948
|
let unit = new Map();
|
|
618
949
|
return inUnits(set.documents, (change) => {
|
|
619
950
|
if (closed)
|
|
620
951
|
return 0;
|
|
621
|
-
const
|
|
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]));
|
|
622
955
|
let spent = 0;
|
|
623
|
-
for (const row of
|
|
956
|
+
for (const row of before) {
|
|
624
957
|
unit.set(row.key, null);
|
|
625
|
-
spent += ROW_WEIGHT +
|
|
958
|
+
spent += ROW_WEIGHT + (kept.has(row.key) ? 0 : charges(row, undefined));
|
|
626
959
|
}
|
|
627
|
-
for (const row of
|
|
960
|
+
for (const row of after) {
|
|
628
961
|
unit.set(row.key, row);
|
|
629
|
-
spent += weight(row) +
|
|
962
|
+
spent += weight(row) + charges(held.get(row.key), row);
|
|
630
963
|
}
|
|
631
964
|
return spent;
|
|
632
965
|
}, () => {
|
|
@@ -657,8 +990,8 @@ export function createReadSide() {
|
|
|
657
990
|
return {
|
|
658
991
|
apply(set, next = () => { }) {
|
|
659
992
|
const pending = publishing;
|
|
660
|
-
if (pending !== null) {
|
|
661
|
-
return publishedLater(pending.then(() => after(published(
|
|
993
|
+
if (pending !== null || set instanceof Promise) {
|
|
994
|
+
return publishedLater(Promise.all([set, pending]).then(([held]) => after(published(held), () => next(held))));
|
|
662
995
|
}
|
|
663
996
|
writing = true;
|
|
664
997
|
let applied;
|
|
@@ -669,48 +1002,83 @@ export function createReadSide() {
|
|
|
669
1002
|
writing = false;
|
|
670
1003
|
}
|
|
671
1004
|
if (applied instanceof Promise)
|
|
672
|
-
return publishedLater(applied.then(next));
|
|
1005
|
+
return publishedLater(applied.then(() => next(set)));
|
|
673
1006
|
released();
|
|
674
|
-
next();
|
|
1007
|
+
next(set);
|
|
675
1008
|
},
|
|
676
1009
|
recover,
|
|
677
1010
|
settled,
|
|
678
1011
|
async query(plan, at) {
|
|
679
|
-
const
|
|
1012
|
+
const view = viewOf(plan);
|
|
1013
|
+
const linked = new Map();
|
|
1014
|
+
let made = null;
|
|
1015
|
+
let following = null;
|
|
680
1016
|
try {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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);
|
|
684
1030
|
await settled();
|
|
685
|
-
|
|
1031
|
+
freeze(view);
|
|
1032
|
+
following?.unsubscribe();
|
|
1033
|
+
following = null;
|
|
686
1034
|
const held = at();
|
|
687
|
-
const
|
|
688
|
-
return { rows
|
|
1035
|
+
const rows = await stepped(rowsOfPlan(plan, collection.keys(), (key) => collection.get(key), count?.size, linked));
|
|
1036
|
+
return { rows, at: held };
|
|
689
1037
|
}
|
|
690
1038
|
finally {
|
|
691
|
-
|
|
1039
|
+
following?.unsubscribe();
|
|
1040
|
+
made?.dispose();
|
|
1041
|
+
dropped(view);
|
|
692
1042
|
}
|
|
693
1043
|
},
|
|
694
1044
|
watch(plan, identity, cover, deliverTo) {
|
|
695
1045
|
let entry = shared.get(identity);
|
|
696
1046
|
if (entry === undefined) {
|
|
1047
|
+
const view = viewOf(plan);
|
|
1048
|
+
void filled(view);
|
|
697
1049
|
const created = {
|
|
698
1050
|
plan,
|
|
699
|
-
|
|
1051
|
+
view,
|
|
1052
|
+
built: built(view.rows, plan),
|
|
700
1053
|
cover,
|
|
701
1054
|
result: undefined,
|
|
702
1055
|
rows: new Map(),
|
|
1056
|
+
generation: 0,
|
|
703
1057
|
bodies: new Map(),
|
|
704
1058
|
listeners: new Set(),
|
|
705
1059
|
fresh: new Set(),
|
|
706
1060
|
ready: new Set(),
|
|
707
1061
|
subscription: null,
|
|
708
1062
|
counting: null,
|
|
1063
|
+
linking: null,
|
|
1064
|
+
linked: new Map(),
|
|
1065
|
+
relinking: [],
|
|
1066
|
+
touched: new Set(),
|
|
709
1067
|
whole: true,
|
|
710
1068
|
dirty: new Set(),
|
|
711
1069
|
stale: false,
|
|
712
1070
|
running: false,
|
|
713
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
|
+
}
|
|
714
1082
|
created.built.live.on('status:change', ({ status }) => {
|
|
715
1083
|
if (status !== 'cleaned-up' || created.listeners.size > 0)
|
|
716
1084
|
return;
|
|
@@ -722,7 +1090,12 @@ export function createReadSide() {
|
|
|
722
1090
|
}
|
|
723
1091
|
const observed = entry;
|
|
724
1092
|
observed.listeners.add(deliverTo);
|
|
725
|
-
const ready = (then) => observed.built.live.onFirstReady(() =>
|
|
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
|
+
});
|
|
726
1099
|
const settled = () => {
|
|
727
1100
|
if (observed.built.count === null)
|
|
728
1101
|
wake(observed);
|
|
@@ -760,22 +1133,30 @@ export function createReadSide() {
|
|
|
760
1133
|
observed.subscription = null;
|
|
761
1134
|
observed.counting?.unsubscribe();
|
|
762
1135
|
observed.counting = null;
|
|
1136
|
+
observed.linking?.unsubscribe();
|
|
1137
|
+
observed.linking = null;
|
|
763
1138
|
observed.result = undefined;
|
|
764
1139
|
observed.rows.clear();
|
|
765
1140
|
observed.bodies.clear();
|
|
766
1141
|
observed.dirty.clear();
|
|
1142
|
+
observed.touched.clear();
|
|
767
1143
|
observed.whole = true;
|
|
768
1144
|
if (shared.get(identity) === observed)
|
|
769
1145
|
shared.delete(identity);
|
|
770
1146
|
observed.built.dispose();
|
|
1147
|
+
dropped(observed.view);
|
|
771
1148
|
};
|
|
772
1149
|
},
|
|
773
|
-
room(room) {
|
|
774
|
-
|
|
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
|
+
}
|
|
775
1156
|
},
|
|
776
1157
|
document(room, path) {
|
|
777
1158
|
const row = rows.get(documentKey(room, path));
|
|
778
|
-
return row
|
|
1159
|
+
return row?.kind !== 'document' || row.type === null ? null : searched(row);
|
|
779
1160
|
},
|
|
780
1161
|
close() {
|
|
781
1162
|
closed = true;
|
|
@@ -785,42 +1166,14 @@ export function createReadSide() {
|
|
|
785
1166
|
entry.ready.clear();
|
|
786
1167
|
entry.subscription?.unsubscribe();
|
|
787
1168
|
entry.counting?.unsubscribe();
|
|
1169
|
+
entry.linking?.unsubscribe();
|
|
788
1170
|
entry.built.dispose();
|
|
789
1171
|
}
|
|
790
1172
|
shared.clear();
|
|
791
|
-
|
|
1173
|
+
for (const view of [...views])
|
|
1174
|
+
dropped(view);
|
|
1175
|
+
rows.clear();
|
|
1176
|
+
roomPaths.clear();
|
|
792
1177
|
},
|
|
793
1178
|
};
|
|
794
1179
|
}
|
|
795
|
-
function changedFrom(previous, next) {
|
|
796
|
-
const before = previous.result;
|
|
797
|
-
const after = next.result;
|
|
798
|
-
if ('pending' in before || 'pending' in after)
|
|
799
|
-
return deepEquals(before, after) ? null : next;
|
|
800
|
-
const earlier = 'groups' in before ? before.groups : before.documents;
|
|
801
|
-
const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
|
|
802
|
-
const later = 'groups' in after ? after.groups : after.documents;
|
|
803
|
-
const changed = [];
|
|
804
|
-
next.keys.forEach((key, index) => {
|
|
805
|
-
if (held.get(key) !== later[index])
|
|
806
|
-
changed.push([key, later[index]]);
|
|
807
|
-
});
|
|
808
|
-
const present = new Set(next.keys);
|
|
809
|
-
const removed = previous.keys.filter((key) => !present.has(key));
|
|
810
|
-
const moved = reordered(previous.keys, next.keys, present);
|
|
811
|
-
const covered = !deepEquals(before.coverage, after.coverage);
|
|
812
|
-
const framing = reframed(before, after);
|
|
813
|
-
if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
|
|
814
|
-
return null;
|
|
815
|
-
}
|
|
816
|
-
return {
|
|
817
|
-
changed,
|
|
818
|
-
removed,
|
|
819
|
-
order: moved ? next.keys : null,
|
|
820
|
-
...(covered ? { coverage: after.coverage } : {}),
|
|
821
|
-
...framing,
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
function rowAt(rows, index) {
|
|
825
|
-
return 'groups' in rows ? rows.groups[index] : rows.documents[index];
|
|
826
|
-
}
|