@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.
- package/AGENTS.md +14 -10
- package/dist/adapter.d.ts +14 -5
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +13 -6
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +86 -88
- package/dist/chain.d.ts +11 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +5 -9
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +10 -86
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +1 -1
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +8 -1
- package/dist/chat/messages.d.ts +1 -0
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +9 -1
- package/dist/chat/provider.d.ts +2 -0
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +17 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/collection.d.ts +5 -19
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +22 -360
- package/dist/compile.d.ts +11 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +712 -0
- package/dist/connection.d.ts +2 -2
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1396 -624
- package/dist/conversation.d.ts +1 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +26 -17
- package/dist/entry.d.ts +2 -1
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +17 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/live.d.ts +22 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +123 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +12 -0
- package/dist/observation.d.ts +12 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +84 -0
- package/dist/overlay.d.ts +0 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +1 -151
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +15 -35
- package/dist/reads.js +1 -1
- package/dist/refresh.d.ts +10 -13
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +74 -70
- package/dist/replica.d.ts +55 -38
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +273 -214
- package/dist/rows.d.ts +29 -31
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +113 -69
- package/dist/save.d.ts +9 -10
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +59 -57
- package/dist/search.d.ts +2 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +60 -28
- package/dist/shared.d.ts +0 -3
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +1 -31
- package/dist/store.d.ts +79 -62
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +164 -330
- package/dist/stored.d.ts +143 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +561 -0
- package/dist/tables.d.ts +246 -168
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +156 -25
- package/dist/turn.d.ts +1 -1
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +28 -20
- package/dist/validation.d.ts +30 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +162 -0
- package/dist/windows.d.ts +2 -1
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +30 -25
- package/package.json +2 -3
- package/dist/read-side.d.ts +0 -27
- package/dist/read-side.d.ts.map +0 -1
- 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
|
-
}
|