@agentshouse/mdruntime 0.1.0-c93103f5357ff → 0.1.0-c94cfdbdb6f68
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 +194 -194
- package/README.md +8 -8
- package/dist/adapter.d.ts +20 -6
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +2 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +39 -3
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +11 -12
- package/dist/chat/question.d.ts +1 -1
- package/dist/chat/question.d.ts.map +1 -1
- package/dist/chat/question.js +7 -18
- package/dist/chat/style.js +1 -1
- package/dist/client.d.ts +6 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +64 -34
- package/dist/connection.d.ts +9 -4
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +312 -154
- package/dist/conversation.d.ts +6 -0
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +161 -41
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +2 -1
- package/dist/host.d.ts +0 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +8 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/memory.d.ts +6 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +17 -11
- package/dist/observation.d.ts +2 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +9 -3
- package/dist/outcome.d.ts +8 -2
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +3 -0
- package/dist/query.d.ts +21 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +28 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +13 -0
- package/dist/react.d.ts +1 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +2 -14
- package/dist/read-side.d.ts +3 -1
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +251 -69
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/refresh.d.ts +4 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/replica.d.ts +5 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +19 -3
- package/dist/rows.d.ts +1 -0
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +5 -0
- package/dist/save.d.ts +1 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +22 -8
- package/dist/search.d.ts +15 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +76 -17
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +18 -4
- package/dist/store.d.ts +16 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +48 -14
- package/dist/turn.d.ts +3 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +11 -5
- package/dist/windows.d.ts +26 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +93 -0
- package/dist/wire.d.ts +16 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +4 -0
- package/package.json +2 -2
- package/dist/acquisition.d.ts +0 -14
- package/dist/acquisition.d.ts.map +0 -1
- package/dist/acquisition.js +0 -24
package/dist/read-side.js
CHANGED
|
@@ -2,6 +2,7 @@ import { and, BasicIndex, configureIndexDevMode, count, createCollection, create
|
|
|
2
2
|
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
3
3
|
import { documentKey } from './rows.js';
|
|
4
4
|
import { after, inUnits, ROW_WEIGHT } from './turn.js';
|
|
5
|
+
import { beyond, cursorOf, MAXIMUM_WINDOW, windowed } from './windows.js';
|
|
5
6
|
function every(expressions) {
|
|
6
7
|
const [first, second, ...rest] = expressions;
|
|
7
8
|
return second === undefined ? first : and(first, second, ...rest);
|
|
@@ -59,10 +60,12 @@ function reached(reach, room, document) {
|
|
|
59
60
|
? []
|
|
60
61
|
: [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))];
|
|
61
62
|
}
|
|
62
|
-
function scoped(scope, row, rooms, reach) {
|
|
63
|
+
function scoped(scope, row, rooms, reach, paths = null, folder = null) {
|
|
63
64
|
return every([
|
|
64
65
|
eq(row.kind, 'document'),
|
|
65
66
|
...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
|
|
67
|
+
...(paths === null ? [] : [inArray(row.path, [...paths])]),
|
|
68
|
+
...(folder === null ? [] : [eq(row.folder, folder)]),
|
|
66
69
|
...reached(reach, row.room, row.key),
|
|
67
70
|
...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
|
|
68
71
|
...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
|
|
@@ -90,7 +93,7 @@ function related(q, rows, scope, field, kept, reach) {
|
|
|
90
93
|
}
|
|
91
94
|
function filtered(q, rows, plan) {
|
|
92
95
|
const from = q.from({ d: rows });
|
|
93
|
-
let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach)), plan.root.where, 'd');
|
|
96
|
+
let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach, plan.paths, plan.folder)), plan.root.where, 'd');
|
|
94
97
|
if (plan.hop !== null) {
|
|
95
98
|
const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
|
|
96
99
|
query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
|
|
@@ -104,21 +107,51 @@ function filtered(q, rows, plan) {
|
|
|
104
107
|
function bodied(shape) {
|
|
105
108
|
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
106
109
|
}
|
|
110
|
+
function orderingOf(options) {
|
|
111
|
+
const collated = options.stringSort === 'locale';
|
|
112
|
+
return {
|
|
113
|
+
direction: options.direction,
|
|
114
|
+
nulls: options.nulls ?? 'first',
|
|
115
|
+
stringSort: options.stringSort ?? 'locale',
|
|
116
|
+
locale: collated ? options.locale : undefined,
|
|
117
|
+
localeOptions: collated
|
|
118
|
+
? options.localeOptions
|
|
119
|
+
: undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function storedValue(key, row) {
|
|
123
|
+
return key.of === 'field' ? row.values[key.field] : row[key.of];
|
|
124
|
+
}
|
|
125
|
+
function documentOrder(shape) {
|
|
126
|
+
return shape.orderBy.map(({ options }) => orderingOf(options));
|
|
127
|
+
}
|
|
128
|
+
function documentCursor(shape, row) {
|
|
129
|
+
return cursorOf([
|
|
130
|
+
...shape.orderBy.map((_, index) => row[`order${index}`]),
|
|
131
|
+
documentKey(row.room, row.path),
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
107
134
|
function documents(q, rows, plan, shape) {
|
|
108
135
|
let query = filtered(q, rows, plan);
|
|
136
|
+
const cursor = plan.after?.values ?? null;
|
|
137
|
+
if (cursor !== null) {
|
|
138
|
+
const orderings = documentOrder(shape);
|
|
139
|
+
query = query.fn.where(({ d }) => beyond([...shape.orderBy.map(({ key }) => storedValue(key, d)), d.key], cursor, orderings));
|
|
140
|
+
}
|
|
109
141
|
for (const { key, options } of shape.orderBy) {
|
|
110
142
|
query = query.orderBy(({ d }) => keyed(key, d), options);
|
|
111
143
|
}
|
|
112
|
-
if (shape.orderBy.length
|
|
144
|
+
if (shape.orderBy.length > 0 || plan.limit !== null || plan.after !== null) {
|
|
113
145
|
query = query.orderBy(({ d }) => d.key, { stringSort: 'lexical' });
|
|
114
146
|
}
|
|
115
147
|
if (plan.limit !== null)
|
|
116
|
-
query = query.limit(plan.limit);
|
|
148
|
+
query = query.limit(plan.limit + 1);
|
|
117
149
|
return query.select(({ d }) => ({
|
|
118
150
|
room: d.room,
|
|
119
151
|
path: d.path,
|
|
120
152
|
type: d.type,
|
|
121
153
|
binary: d.binary,
|
|
154
|
+
...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
|
|
122
155
|
...(shape.fields === null ? {} : { fields: d.fields }),
|
|
123
156
|
...(bodied(shape) ? { text: d.text } : {}),
|
|
124
157
|
...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
|
|
@@ -134,15 +167,13 @@ function backlinked(q, rows, document, reach) {
|
|
|
134
167
|
.orderBy(({ r }) => r.field, { stringSort: 'lexical' })
|
|
135
168
|
.select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
|
|
136
169
|
}
|
|
137
|
-
function groups(q, stage, shape
|
|
170
|
+
function groups(q, stage, shape) {
|
|
138
171
|
const keys = shape.groupBy.map((_, index) => `key${index}`);
|
|
139
172
|
const from = q.from({ m: stage });
|
|
140
173
|
let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
|
|
141
174
|
for (const { by, direction } of shape.orderBy) {
|
|
142
175
|
query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
|
|
143
176
|
}
|
|
144
|
-
if (limit !== null && shape.orderBy.length > 0)
|
|
145
|
-
query = query.limit(limit);
|
|
146
177
|
return query.select(({ m }) => ({
|
|
147
178
|
...Object.fromEntries(keys.map((key) => [key, m[key]])),
|
|
148
179
|
count: count(m.key),
|
|
@@ -152,15 +183,27 @@ function built(rows, plan) {
|
|
|
152
183
|
const shape = plan.shape;
|
|
153
184
|
if (shape.kind === 'documents') {
|
|
154
185
|
const collection = live((q) => documents(q, rows, plan, shape));
|
|
155
|
-
|
|
186
|
+
const count = plan.total
|
|
187
|
+
? live((q) => filtered(q, rows, plan).select(({ d }) => ({ key: d.key })))
|
|
188
|
+
: null;
|
|
189
|
+
return {
|
|
190
|
+
live: collection,
|
|
191
|
+
count,
|
|
192
|
+
dispose: () => {
|
|
193
|
+
void collection.cleanup();
|
|
194
|
+
if (count !== null)
|
|
195
|
+
void count.cleanup();
|
|
196
|
+
},
|
|
197
|
+
};
|
|
156
198
|
}
|
|
157
199
|
const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
|
|
158
200
|
key: d.key,
|
|
159
201
|
...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
|
|
160
202
|
})));
|
|
161
|
-
const collection = live((q) => groups(q, stage, shape
|
|
203
|
+
const collection = live((q) => groups(q, stage, shape));
|
|
162
204
|
return {
|
|
163
205
|
live: collection,
|
|
206
|
+
count: null,
|
|
164
207
|
dispose: () => {
|
|
165
208
|
void collection.cleanup();
|
|
166
209
|
void stage.cleanup();
|
|
@@ -262,7 +305,9 @@ function documentOf(key, row, shape, bodies) {
|
|
|
262
305
|
...(shape.markdown && body !== null ? { markdown: body.content } : {}),
|
|
263
306
|
...(shape.backlinks
|
|
264
307
|
? {
|
|
265
|
-
backlinks: row.backlinks
|
|
308
|
+
backlinks: row.backlinks
|
|
309
|
+
.slice(0, MAXIMUM_WINDOW)
|
|
310
|
+
.map(({ path, type, field }) => ({ path, type, field })),
|
|
266
311
|
}
|
|
267
312
|
: {}),
|
|
268
313
|
...(shape.change ? { change: row.change } : {}),
|
|
@@ -272,24 +317,62 @@ function documentOf(key, row, shape, bodies) {
|
|
|
272
317
|
function weight(row) {
|
|
273
318
|
return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
|
|
274
319
|
}
|
|
275
|
-
function
|
|
320
|
+
function groupValues(shape, [key, row]) {
|
|
321
|
+
return [...shape.orderBy.map(({ by }) => (by === 'count' ? row.count : row[`key${by}`])), key];
|
|
322
|
+
}
|
|
323
|
+
function groupOrder(shape) {
|
|
324
|
+
return shape.orderBy.map(({ direction }) => ({
|
|
325
|
+
direction,
|
|
326
|
+
nulls: 'first',
|
|
327
|
+
stringSort: 'locale',
|
|
328
|
+
locale: undefined,
|
|
329
|
+
localeOptions: undefined,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
function windowOf(plan, entries, total) {
|
|
276
333
|
const shape = plan.shape;
|
|
334
|
+
if (shape.kind === 'documents') {
|
|
335
|
+
return {
|
|
336
|
+
...windowed(entries, plan.limit, plan.after?.text, ([, row]) => documentCursor(shape, row)),
|
|
337
|
+
total,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const cursor = plan.after?.values ?? null;
|
|
341
|
+
const orderings = groupOrder(shape);
|
|
342
|
+
const following = cursor === null
|
|
343
|
+
? entries
|
|
344
|
+
: entries.filter((entry) => beyond(groupValues(shape, entry), cursor, orderings));
|
|
345
|
+
return {
|
|
346
|
+
...windowed(following, plan.limit, plan.after?.text, (entry) => cursorOf(groupValues(shape, entry))),
|
|
347
|
+
total: plan.total ? entries.length : undefined,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function framed(rows, { next, total }) {
|
|
351
|
+
return {
|
|
352
|
+
...rows,
|
|
353
|
+
...(next === undefined ? {} : { next }),
|
|
354
|
+
...(total === undefined ? {} : { total }),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function rowsOfPlan(plan, entries, total, bodies, each = (row) => row) {
|
|
358
|
+
const shape = plan.shape;
|
|
359
|
+
const { kept, ...window } = windowOf(plan, entries, total);
|
|
360
|
+
const keys = kept.map(([key]) => key);
|
|
277
361
|
if (shape.kind === 'documents') {
|
|
278
362
|
const documents = [];
|
|
279
|
-
return after(inUnits(
|
|
363
|
+
return after(inUnits(kept, ([key, row]) => {
|
|
280
364
|
documents.push(each(documentOf(key, row, shape, bodies)));
|
|
281
365
|
return weight(row);
|
|
282
|
-
}), () => ({ rows: { documents },
|
|
366
|
+
}), () => ({ rows: framed({ documents }, window), keys, ...window }));
|
|
283
367
|
}
|
|
284
|
-
const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
|
|
285
368
|
const groups = [];
|
|
286
|
-
return after(inUnits(
|
|
369
|
+
return after(inUnits(kept, ([, row]) => {
|
|
287
370
|
groups.push(each({
|
|
288
371
|
key: shape.groupBy.map((_, index) => row[`key${index}`]),
|
|
289
372
|
count: row.count,
|
|
290
373
|
}));
|
|
291
374
|
return ROW_WEIGHT;
|
|
292
|
-
}), () => ({ rows: { groups },
|
|
375
|
+
}), () => ({ rows: framed({ groups }, window), keys, ...window }));
|
|
293
376
|
}
|
|
294
377
|
export function reconciled(previous, next) {
|
|
295
378
|
const before = previous.result;
|
|
@@ -314,13 +397,16 @@ export function reconciled(previous, next) {
|
|
|
314
397
|
const removed = previous.keys.filter((key) => !present.has(key));
|
|
315
398
|
const moved = reordered(previous.keys, next.keys, present);
|
|
316
399
|
const covered = !deepEquals(before.coverage, after.coverage);
|
|
317
|
-
|
|
400
|
+
const framing = reframed(before, after);
|
|
401
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
|
|
318
402
|
return null;
|
|
403
|
+
}
|
|
404
|
+
const window = { next: after.next, total: after.total };
|
|
319
405
|
return {
|
|
320
406
|
keyed: {
|
|
321
407
|
result: 'groups' in after
|
|
322
|
-
? { groups: rows, coverage: after.coverage }
|
|
323
|
-
: { documents: rows, coverage: after.coverage },
|
|
408
|
+
? framed({ groups: rows, coverage: after.coverage }, window)
|
|
409
|
+
: framed({ documents: rows, coverage: after.coverage }, window),
|
|
324
410
|
keys: next.keys,
|
|
325
411
|
},
|
|
326
412
|
message: {
|
|
@@ -328,9 +414,17 @@ export function reconciled(previous, next) {
|
|
|
328
414
|
removed,
|
|
329
415
|
order: moved ? next.keys : null,
|
|
330
416
|
...(covered ? { coverage: after.coverage } : {}),
|
|
417
|
+
...framing,
|
|
331
418
|
},
|
|
332
419
|
};
|
|
333
420
|
}
|
|
421
|
+
function reframed(before, after) {
|
|
422
|
+
const next = before.next === after.next ? {} : { next: after.next ?? null };
|
|
423
|
+
const total = before.total === after.total || after.total === undefined ? {} : { total: after.total };
|
|
424
|
+
return Object.keys(next).length === 0 && Object.keys(total).length === 0
|
|
425
|
+
? null
|
|
426
|
+
: { ...next, ...total };
|
|
427
|
+
}
|
|
334
428
|
function reordered(previous, next, present) {
|
|
335
429
|
let at = 0;
|
|
336
430
|
for (const key of previous) {
|
|
@@ -360,6 +454,9 @@ export function createReadSide() {
|
|
|
360
454
|
},
|
|
361
455
|
});
|
|
362
456
|
const shared = new Map();
|
|
457
|
+
let writing = false;
|
|
458
|
+
let publishing = null;
|
|
459
|
+
let closed = false;
|
|
363
460
|
function searched(row) {
|
|
364
461
|
const read = row.text === null ? null : parse(row.text, row.path);
|
|
365
462
|
if (read === null || !read.ok)
|
|
@@ -379,8 +476,11 @@ export function createReadSide() {
|
|
|
379
476
|
return;
|
|
380
477
|
entry.rows.set(key, row);
|
|
381
478
|
}
|
|
479
|
+
function counted(entry) {
|
|
480
|
+
return entry.built.count === null ? undefined : entry.built.count.size;
|
|
481
|
+
}
|
|
382
482
|
function rebuilt(entry) {
|
|
383
|
-
return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
|
|
483
|
+
return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], counted(entry), entry.bodies), ({ rows: found, keys, next, total }) => {
|
|
384
484
|
const present = new Set(keys);
|
|
385
485
|
for (const key of [...entry.rows.keys()])
|
|
386
486
|
if (!present.has(key))
|
|
@@ -390,32 +490,34 @@ export function createReadSide() {
|
|
|
390
490
|
entry.bodies.delete(key);
|
|
391
491
|
}
|
|
392
492
|
keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
|
|
393
|
-
return keys;
|
|
493
|
+
return { keys, next, total };
|
|
394
494
|
});
|
|
395
495
|
}
|
|
396
496
|
function refreshed(entry, dirty) {
|
|
397
497
|
const shape = entry.plan.shape;
|
|
398
|
-
const
|
|
399
|
-
const
|
|
400
|
-
const
|
|
498
|
+
const { kept: inWindow, next, total, } = windowOf(entry.plan, [...entry.built.live.entries()], counted(entry));
|
|
499
|
+
const keys = inWindow.map(([key]) => key);
|
|
500
|
+
const present = new Set(keys);
|
|
501
|
+
for (const key of [...entry.rows.keys()]) {
|
|
502
|
+
if (present.has(key))
|
|
503
|
+
continue;
|
|
504
|
+
entry.rows.delete(key);
|
|
505
|
+
entry.bodies.delete(key);
|
|
506
|
+
}
|
|
507
|
+
const values = inWindow.filter(([key]) => dirty.has(key) || !entry.rows.has(key));
|
|
401
508
|
const done = inUnits(values, ([key, value]) => {
|
|
402
|
-
|
|
403
|
-
kept(entry, key, documentOf(key, value, shape, entry.bodies));
|
|
404
|
-
else {
|
|
405
|
-
entry.rows.delete(key);
|
|
406
|
-
entry.bodies.delete(key);
|
|
407
|
-
}
|
|
509
|
+
kept(entry, key, documentOf(key, value, shape, entry.bodies));
|
|
408
510
|
return weight(value);
|
|
409
511
|
});
|
|
410
|
-
return after(done, () => keys);
|
|
512
|
+
return after(done, () => ({ keys, next, total }));
|
|
411
513
|
}
|
|
412
|
-
function current(entry,
|
|
413
|
-
const
|
|
514
|
+
function current(entry, found, coverage) {
|
|
515
|
+
const rows = found.keys.map((key) => entry.rows.get(key));
|
|
414
516
|
return {
|
|
415
517
|
result: entry.plan.shape.kind === 'groups'
|
|
416
|
-
? { groups:
|
|
417
|
-
: { documents:
|
|
418
|
-
keys,
|
|
518
|
+
? framed({ groups: rows, coverage }, found)
|
|
519
|
+
: framed({ documents: rows, coverage }, found),
|
|
520
|
+
keys: found.keys,
|
|
419
521
|
};
|
|
420
522
|
}
|
|
421
523
|
function delivered(entry, next) {
|
|
@@ -451,25 +553,26 @@ export function createReadSide() {
|
|
|
451
553
|
const dirty = entry.dirty;
|
|
452
554
|
entry.dirty = new Set();
|
|
453
555
|
entry.whole = false;
|
|
454
|
-
const
|
|
455
|
-
if (
|
|
456
|
-
void
|
|
457
|
-
.then((
|
|
556
|
+
const found = whole ? rebuilt(entry) : refreshed(entry, dirty);
|
|
557
|
+
if (found instanceof Promise) {
|
|
558
|
+
void found
|
|
559
|
+
.then((window) => {
|
|
458
560
|
if (entry.listeners.size > 0)
|
|
459
|
-
delivered(entry, current(entry,
|
|
460
|
-
|
|
561
|
+
delivered(entry, current(entry, window, cover.coverage));
|
|
562
|
+
entry.running = false;
|
|
563
|
+
wake(entry);
|
|
461
564
|
})
|
|
462
565
|
.catch(() => {
|
|
463
566
|
entry.running = false;
|
|
464
567
|
});
|
|
465
568
|
return;
|
|
466
569
|
}
|
|
467
|
-
delivered(entry, current(entry,
|
|
570
|
+
delivered(entry, current(entry, found, cover.coverage));
|
|
468
571
|
}
|
|
469
572
|
entry.running = false;
|
|
470
573
|
}
|
|
471
574
|
function wake(entry) {
|
|
472
|
-
if (entry.subscription === null || entry.running)
|
|
575
|
+
if (entry.subscription === null || entry.running || writing || publishing !== null)
|
|
473
576
|
return;
|
|
474
577
|
entry.running = true;
|
|
475
578
|
try {
|
|
@@ -495,37 +598,91 @@ export function createReadSide() {
|
|
|
495
598
|
wake(entry);
|
|
496
599
|
}
|
|
497
600
|
}
|
|
601
|
+
function committed(next) {
|
|
602
|
+
if (closed || next.size === 0)
|
|
603
|
+
return;
|
|
604
|
+
sync.begin();
|
|
605
|
+
for (const [key, row] of next) {
|
|
606
|
+
if (row === null) {
|
|
607
|
+
if (rows.has(key))
|
|
608
|
+
sync.write({ type: 'delete', key });
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
|
|
612
|
+
}
|
|
613
|
+
sync.commit();
|
|
614
|
+
}
|
|
615
|
+
function published(set) {
|
|
616
|
+
let unit = new Map();
|
|
617
|
+
return inUnits(set.documents, (change) => {
|
|
618
|
+
if (closed)
|
|
619
|
+
return 0;
|
|
620
|
+
let spent = 0;
|
|
621
|
+
for (const row of change.before) {
|
|
622
|
+
unit.set(row.key, null);
|
|
623
|
+
spent += ROW_WEIGHT;
|
|
624
|
+
}
|
|
625
|
+
for (const row of change.after) {
|
|
626
|
+
unit.set(row.key, row);
|
|
627
|
+
spent += weight(row);
|
|
628
|
+
}
|
|
629
|
+
return spent;
|
|
630
|
+
}, () => {
|
|
631
|
+
committed(unit);
|
|
632
|
+
unit = new Map();
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
function released() {
|
|
636
|
+
recover();
|
|
637
|
+
for (const entry of shared.values())
|
|
638
|
+
wake(entry);
|
|
639
|
+
}
|
|
640
|
+
function publishedLater(applied) {
|
|
641
|
+
const fence = applied.then(() => { }, () => { });
|
|
642
|
+
publishing = fence;
|
|
643
|
+
void fence.then(() => {
|
|
644
|
+
if (publishing !== fence)
|
|
645
|
+
return;
|
|
646
|
+
publishing = null;
|
|
647
|
+
released();
|
|
648
|
+
});
|
|
649
|
+
return applied;
|
|
650
|
+
}
|
|
651
|
+
async function settled() {
|
|
652
|
+
while (publishing !== null)
|
|
653
|
+
await publishing;
|
|
654
|
+
}
|
|
498
655
|
return {
|
|
499
|
-
apply(set) {
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
next.set(row.key, null);
|
|
504
|
-
for (const row of change.after)
|
|
505
|
-
next.set(row.key, row);
|
|
656
|
+
apply(set, next = () => { }) {
|
|
657
|
+
const pending = publishing;
|
|
658
|
+
if (pending !== null) {
|
|
659
|
+
return publishedLater(pending.then(() => after(published(set), next)));
|
|
506
660
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
|
|
516
|
-
}
|
|
517
|
-
sync.commit();
|
|
661
|
+
writing = true;
|
|
662
|
+
let applied;
|
|
663
|
+
try {
|
|
664
|
+
applied = published(set);
|
|
665
|
+
}
|
|
666
|
+
finally {
|
|
667
|
+
writing = false;
|
|
518
668
|
}
|
|
519
|
-
|
|
669
|
+
if (applied instanceof Promise)
|
|
670
|
+
return publishedLater(applied.then(next));
|
|
671
|
+
released();
|
|
672
|
+
next();
|
|
520
673
|
},
|
|
521
674
|
recover,
|
|
675
|
+
settled,
|
|
522
676
|
async query(plan, at) {
|
|
523
|
-
const { live: collection, dispose } = built(rows, plan);
|
|
677
|
+
const { live: collection, count, dispose } = built(rows, plan);
|
|
524
678
|
try {
|
|
525
679
|
await collection.toArrayWhenReady();
|
|
680
|
+
if (count !== null)
|
|
681
|
+
await count.toArrayWhenReady();
|
|
682
|
+
await settled();
|
|
526
683
|
const entries = [...collection.entries()];
|
|
527
684
|
const held = at();
|
|
528
|
-
const found = await rowsOfPlan(plan, entries, null, structuredClone);
|
|
685
|
+
const found = await rowsOfPlan(plan, entries, count?.size, null, structuredClone);
|
|
529
686
|
return { rows: found.rows, at: held };
|
|
530
687
|
}
|
|
531
688
|
finally {
|
|
@@ -546,13 +703,16 @@ export function createReadSide() {
|
|
|
546
703
|
fresh: new Set(),
|
|
547
704
|
ready: new Set(),
|
|
548
705
|
subscription: null,
|
|
706
|
+
counting: null,
|
|
549
707
|
whole: true,
|
|
550
708
|
dirty: new Set(),
|
|
551
709
|
stale: false,
|
|
552
710
|
running: false,
|
|
553
711
|
};
|
|
554
712
|
created.built.live.on('status:change', ({ status }) => {
|
|
555
|
-
if (status
|
|
713
|
+
if (status !== 'cleaned-up' || created.listeners.size > 0)
|
|
714
|
+
return;
|
|
715
|
+
if (shared.get(identity) === created)
|
|
556
716
|
shared.delete(identity);
|
|
557
717
|
});
|
|
558
718
|
shared.set(identity, created);
|
|
@@ -560,7 +720,14 @@ export function createReadSide() {
|
|
|
560
720
|
}
|
|
561
721
|
const observed = entry;
|
|
562
722
|
observed.listeners.add(deliverTo);
|
|
563
|
-
observed.built.live.onFirstReady(() =>
|
|
723
|
+
const ready = (then) => observed.built.live.onFirstReady(() => observed.built.count === null ? then() : observed.built.count.onFirstReady(then));
|
|
724
|
+
const settled = () => {
|
|
725
|
+
if (observed.built.count === null)
|
|
726
|
+
wake(observed);
|
|
727
|
+
else
|
|
728
|
+
queueMicrotask(() => wake(observed));
|
|
729
|
+
};
|
|
730
|
+
ready(() => {
|
|
564
731
|
if (!observed.listeners.has(deliverTo))
|
|
565
732
|
return;
|
|
566
733
|
observed.fresh.add(deliverTo);
|
|
@@ -572,8 +739,13 @@ export function createReadSide() {
|
|
|
572
739
|
else
|
|
573
740
|
for (const { key } of changes)
|
|
574
741
|
observed.dirty.add(key);
|
|
575
|
-
|
|
742
|
+
settled();
|
|
576
743
|
}, { includeInitialState: false });
|
|
744
|
+
observed.counting =
|
|
745
|
+
observed.built.count?.subscribeChanges(() => {
|
|
746
|
+
observed.stale = true;
|
|
747
|
+
settled();
|
|
748
|
+
}, { includeInitialState: false }) ?? null;
|
|
577
749
|
}
|
|
578
750
|
wake(observed);
|
|
579
751
|
});
|
|
@@ -584,11 +756,16 @@ export function createReadSide() {
|
|
|
584
756
|
return;
|
|
585
757
|
observed.subscription?.unsubscribe();
|
|
586
758
|
observed.subscription = null;
|
|
759
|
+
observed.counting?.unsubscribe();
|
|
760
|
+
observed.counting = null;
|
|
587
761
|
observed.result = undefined;
|
|
588
762
|
observed.rows.clear();
|
|
589
763
|
observed.bodies.clear();
|
|
590
764
|
observed.dirty.clear();
|
|
591
765
|
observed.whole = true;
|
|
766
|
+
if (shared.get(identity) === observed)
|
|
767
|
+
shared.delete(identity);
|
|
768
|
+
observed.built.dispose();
|
|
592
769
|
};
|
|
593
770
|
},
|
|
594
771
|
room(room) {
|
|
@@ -599,11 +776,13 @@ export function createReadSide() {
|
|
|
599
776
|
return row === undefined || row.type === null ? null : searched(row);
|
|
600
777
|
},
|
|
601
778
|
close() {
|
|
779
|
+
closed = true;
|
|
602
780
|
for (const entry of shared.values()) {
|
|
603
781
|
entry.listeners.clear();
|
|
604
782
|
entry.fresh.clear();
|
|
605
783
|
entry.ready.clear();
|
|
606
784
|
entry.subscription?.unsubscribe();
|
|
785
|
+
entry.counting?.unsubscribe();
|
|
607
786
|
entry.built.dispose();
|
|
608
787
|
}
|
|
609
788
|
shared.clear();
|
|
@@ -628,13 +807,16 @@ function changedFrom(previous, next) {
|
|
|
628
807
|
const removed = previous.keys.filter((key) => !present.has(key));
|
|
629
808
|
const moved = reordered(previous.keys, next.keys, present);
|
|
630
809
|
const covered = !deepEquals(before.coverage, after.coverage);
|
|
631
|
-
|
|
810
|
+
const framing = reframed(before, after);
|
|
811
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
|
|
632
812
|
return null;
|
|
813
|
+
}
|
|
633
814
|
return {
|
|
634
815
|
changed,
|
|
635
816
|
removed,
|
|
636
817
|
order: moved ? next.keys : null,
|
|
637
818
|
...(covered ? { coverage: after.coverage } : {}),
|
|
819
|
+
...framing,
|
|
638
820
|
};
|
|
639
821
|
}
|
|
640
822
|
function rowAt(rows, index) {
|
package/dist/reads.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Adapter } from './adapter.js';
|
|
2
|
+
import type { Reads } from './bootstrap.js';
|
|
3
|
+
export type DoorReads = Reads & {
|
|
4
|
+
carried(locator: string): number;
|
|
5
|
+
finished(locator: string, turn: number): void;
|
|
6
|
+
opened(): void;
|
|
7
|
+
applied(): void;
|
|
8
|
+
lost(): void;
|
|
9
|
+
};
|
|
10
|
+
export declare function doorReads(adapter: Adapter, declaration: (locator: string) => unknown, generation: () => number): DoorReads;
|
|
11
|
+
//# sourceMappingURL=reads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reads.d.ts","sourceRoot":"","sources":["../src/reads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAOR,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG;IAC9B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;IAChB,IAAI,IAAI,IAAI,CAAC;CACd,CAAC;AAiCF,wBAAgB,SAAS,CACvB,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,EACzC,UAAU,EAAE,MAAM,MAAM,GACvB,SAAS,CA0FX"}
|
package/dist/reads.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
function declaredAs(declaration) {
|
|
2
|
+
if (declaration === null || typeof declaration !== 'object')
|
|
3
|
+
return declaration;
|
|
4
|
+
const { roomVersion: _version, ...declared } = declaration;
|
|
5
|
+
return declared;
|
|
6
|
+
}
|
|
7
|
+
function sameHeld(one, other) {
|
|
8
|
+
return one?.documents === other?.documents && one?.revisions === other?.revisions;
|
|
9
|
+
}
|
|
10
|
+
function failed(answer) {
|
|
11
|
+
return answer?.kind === 'transport-uncertainty';
|
|
12
|
+
}
|
|
13
|
+
function continues(answer) {
|
|
14
|
+
const page = answer;
|
|
15
|
+
return page?.kind === 'net-advancement' && page.pageToken !== undefined;
|
|
16
|
+
}
|
|
17
|
+
export function doorReads(adapter, declaration, generation) {
|
|
18
|
+
const reading = new Map();
|
|
19
|
+
const taken = new Map();
|
|
20
|
+
let carrying = false;
|
|
21
|
+
function joined(read, authorization, request, ask) {
|
|
22
|
+
const key = `${read} ${request.locator} ${request.pageToken ?? ''}`;
|
|
23
|
+
const { held, ...named } = request;
|
|
24
|
+
const asked = JSON.stringify([declaredAs(declaration(request.locator)), named]);
|
|
25
|
+
const asking = generation();
|
|
26
|
+
const current = reading.get(key);
|
|
27
|
+
if (current !== undefined &&
|
|
28
|
+
current.authorization === authorization &&
|
|
29
|
+
current.asked === asked &&
|
|
30
|
+
sameHeld(current.held, held) &&
|
|
31
|
+
(!current.settled || current.holder < asking)) {
|
|
32
|
+
if (current.settled && !current.continued)
|
|
33
|
+
reading.delete(key);
|
|
34
|
+
else
|
|
35
|
+
current.holder = Math.max(current.holder, asking);
|
|
36
|
+
if (current.from === asking)
|
|
37
|
+
return current.answer;
|
|
38
|
+
const again = () => joined(read, authorization, request, ask);
|
|
39
|
+
return current.answer.then((answer) => {
|
|
40
|
+
if (failed(answer))
|
|
41
|
+
return again();
|
|
42
|
+
taken.set(request.locator, (taken.get(request.locator) ?? 0) + 1);
|
|
43
|
+
return answer;
|
|
44
|
+
}, again);
|
|
45
|
+
}
|
|
46
|
+
const answer = ask();
|
|
47
|
+
const sent = {
|
|
48
|
+
locator: request.locator,
|
|
49
|
+
authorization,
|
|
50
|
+
asked,
|
|
51
|
+
held,
|
|
52
|
+
answer,
|
|
53
|
+
from: asking,
|
|
54
|
+
holder: asking,
|
|
55
|
+
settled: false,
|
|
56
|
+
continued: false,
|
|
57
|
+
};
|
|
58
|
+
reading.set(key, sent);
|
|
59
|
+
const settled = (result, kept) => {
|
|
60
|
+
sent.settled = true;
|
|
61
|
+
sent.continued = kept && read === 'refresh' && continues(result);
|
|
62
|
+
if (reading.get(key) !== sent)
|
|
63
|
+
return;
|
|
64
|
+
const live = sent.holder === generation();
|
|
65
|
+
if (!kept || (live ? !sent.continued : !carrying))
|
|
66
|
+
reading.delete(key);
|
|
67
|
+
};
|
|
68
|
+
answer.then((result) => settled(result, !failed(result)), () => settled(null, false));
|
|
69
|
+
return answer;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
bootstrap: (authorization, request) => joined('bootstrap', authorization, request, () => adapter.bootstrap(authorization, request)),
|
|
73
|
+
refresh: (authorization, request) => joined('refresh', authorization, request, () => adapter.refresh(authorization, request)),
|
|
74
|
+
reached: (locator, roomVersion, target) => adapter.reached(locator, roomVersion, target),
|
|
75
|
+
carried: (locator) => taken.get(locator) ?? 0,
|
|
76
|
+
finished(locator, turn) {
|
|
77
|
+
for (const [key, held] of reading) {
|
|
78
|
+
if (held.locator !== locator || !held.settled || held.holder !== turn)
|
|
79
|
+
continue;
|
|
80
|
+
if (turn === generation() || !carrying)
|
|
81
|
+
reading.delete(key);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
opened() {
|
|
85
|
+
carrying = true;
|
|
86
|
+
},
|
|
87
|
+
applied() {
|
|
88
|
+
carrying = false;
|
|
89
|
+
for (const [key, held] of reading) {
|
|
90
|
+
if (held.settled && held.holder !== generation())
|
|
91
|
+
reading.delete(key);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
lost() {
|
|
95
|
+
reading.clear();
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|