@agentshouse/mdruntime 0.1.0-c2aaa9c0e3ca4 → 0.1.0-c2c52158b7381
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 +285 -276
- package/README.md +9 -8
- package/dist/adapter.d.ts +80 -21
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +4 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +13 -3
- package/dist/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- package/dist/bootstrap.d.ts +3 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +83 -26
- package/dist/change-set.d.ts +7 -2
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +37 -10
- package/dist/change.d.ts +31 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +10 -1
- package/dist/chat/acts.d.ts +3 -4
- package/dist/chat/acts.d.ts.map +1 -1
- package/dist/chat/acts.js +10 -10
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- 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/kit.d.ts +2 -1
- package/dist/chat/kit.d.ts.map +1 -1
- package/dist/chat/kit.js +3 -3
- package/dist/chat/list.d.ts +9 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +53 -9
- package/dist/chat/markdown.d.ts +3 -1
- package/dist/chat/markdown.d.ts.map +1 -1
- package/dist/chat/markdown.js +85 -26
- package/dist/chat/message.d.ts +1 -0
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +57 -12
- package/dist/chat/messages.d.ts +9 -1
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +67 -28
- 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 +1 -0
- package/dist/chat/overlay.d.ts.map +1 -1
- package/dist/chat/overlay.js +11 -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/pieces.d.ts +62 -0
- package/dist/chat/pieces.d.ts.map +1 -0
- package/dist/chat/pieces.js +1110 -0
- package/dist/chat/provider.d.ts +7 -2
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +129 -155
- 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/ready.d.ts +5 -1
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +9 -7
- 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 +72 -0
- package/dist/chat/steady.d.ts +3 -0
- package/dist/chat/steady.d.ts.map +1 -0
- package/dist/chat/steady.js +26 -0
- package/dist/chat/style.d.ts.map +1 -1
- package/dist/chat/style.js +8 -4
- package/dist/chat.d.ts +3 -2
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/client.d.ts +21 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +47 -8
- package/dist/collection.d.ts +21 -6
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +332 -40
- package/dist/connection.d.ts +39 -11
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2294 -492
- package/dist/conversation.d.ts +28 -9
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +500 -165
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/durability.d.ts +8 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +18 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +157 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- 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 +15 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +103 -13
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +6 -0
- package/dist/index.d.ts +15 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +8 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +43 -12
- package/dist/model.d.ts +4 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +7 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +29 -12
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/outcome.d.ts +39 -4
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +29 -0
- package/dist/overlay.d.ts +6 -4
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +229 -161
- package/dist/query.d.ts +57 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +37 -7
- package/dist/reach.d.ts +11 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +192 -41
- package/dist/react.d.ts +8 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +7 -17
- package/dist/read-side.d.ts +18 -9
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +894 -155
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +198 -0
- package/dist/refresh.d.ts +4 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +46 -20
- package/dist/replica.d.ts +12 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +151 -64
- package/dist/room-draft.d.ts +16 -2
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +32 -25
- package/dist/rows.d.ts +6 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +10 -13
- package/dist/save.d.ts +52 -10
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +519 -159
- package/dist/search.d.ts +49 -6
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +242 -46
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +19 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +461 -0
- package/dist/status.d.ts +4 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/store.d.ts +61 -17
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +581 -310
- package/dist/tables.d.ts +242 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +406 -0
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +67 -0
- package/dist/windows.d.ts +21 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +84 -0
- package/dist/wire.d.ts +168 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +56 -31
- package/package.json +10 -2
package/dist/read-side.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { and,
|
|
2
|
-
import { ARRAY_INDEX, pointerSegments } from '@agentshouse/mdmodel';
|
|
3
|
-
import { rowsOf } from './observation.js';
|
|
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
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';
|
|
5
7
|
function every(expressions) {
|
|
6
8
|
const [first, second, ...rest] = expressions;
|
|
7
9
|
return second === undefined ? first : and(first, second, ...rest);
|
|
@@ -54,16 +56,21 @@ function conditioned(condition, row) {
|
|
|
54
56
|
function narrowed(query, where, alias) {
|
|
55
57
|
return where === null ? query : query.fn.where((row) => conditioned(where, row[alias]));
|
|
56
58
|
}
|
|
57
|
-
function reached(reach, room, document) {
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
];
|
|
61
66
|
}
|
|
62
|
-
function scoped(scope, row, rooms, reach) {
|
|
67
|
+
function scoped(scope, row, rooms, reach, locked, paths = null, folder = null) {
|
|
63
68
|
return every([
|
|
64
69
|
eq(row.kind, 'document'),
|
|
65
70
|
...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
|
|
66
|
-
...
|
|
71
|
+
...(paths === null ? [] : [inArray(row.path, [...paths])]),
|
|
72
|
+
...(folder === null ? [] : [eq(row.folder, folder)]),
|
|
73
|
+
...reached(reach, locked, row.room, row.key),
|
|
67
74
|
...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
|
|
68
75
|
...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
|
|
69
76
|
]);
|
|
@@ -71,83 +78,165 @@ function scoped(scope, row, rooms, reach) {
|
|
|
71
78
|
function keyed(key, row) {
|
|
72
79
|
return key.of === 'field' ? row.values[key.field] : row[key.of];
|
|
73
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
|
+
}
|
|
74
89
|
function live(query) {
|
|
75
90
|
return createLiveQueryCollection({
|
|
76
91
|
query: query,
|
|
77
92
|
startSync: true,
|
|
93
|
+
gcTime: 0,
|
|
78
94
|
});
|
|
79
95
|
}
|
|
80
|
-
function related(q, rows, scope, field, kept, reach) {
|
|
96
|
+
function related(q, rows, scope, field, kept, reach, locked) {
|
|
81
97
|
const matched = kept === 'source' ? 'target' : 'source';
|
|
82
98
|
const from = q.from({ r: rows });
|
|
83
99
|
const joined = from
|
|
84
100
|
.where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [inArray(field, r.forms)])]))
|
|
85
|
-
.innerJoin({ m: rows }, ({ r, m }) => eq(r[matched], m.key))
|
|
86
|
-
.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));
|
|
87
103
|
return narrowed(joined, scope.where, 'm')
|
|
88
104
|
.groupBy(({ r }) => r[kept])
|
|
89
105
|
.select(({ r }) => ({ key: r[kept] }));
|
|
90
106
|
}
|
|
91
107
|
function filtered(q, rows, plan) {
|
|
92
108
|
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');
|
|
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');
|
|
94
110
|
if (plan.hop !== null) {
|
|
95
|
-
const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
|
|
96
|
-
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)));
|
|
97
113
|
}
|
|
98
114
|
if (plan.backlinks !== null) {
|
|
99
|
-
const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach);
|
|
100
|
-
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)));
|
|
101
117
|
}
|
|
102
118
|
return query;
|
|
103
119
|
}
|
|
104
|
-
function
|
|
105
|
-
return
|
|
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
|
+
]);
|
|
106
146
|
}
|
|
107
147
|
function documents(q, rows, plan, shape) {
|
|
108
148
|
let query = filtered(q, rows, plan);
|
|
109
|
-
|
|
110
|
-
|
|
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));
|
|
111
153
|
}
|
|
112
154
|
for (const { key, options } of shape.orderBy) {
|
|
113
|
-
query = query.orderBy(({ d }) => keyed(key, d), options);
|
|
155
|
+
query = query.orderBy(({ d }) => streamed(keyed(key, d)), options);
|
|
114
156
|
}
|
|
115
|
-
if (shape.orderBy.length
|
|
116
|
-
query = query.orderBy(({ d }) => d.key, { stringSort: 'lexical' });
|
|
157
|
+
if (shape.orderBy.length > 0 || plan.limit !== null || plan.after !== null) {
|
|
158
|
+
query = query.orderBy(({ d }) => streamed(d.key), { stringSort: 'lexical' });
|
|
117
159
|
}
|
|
118
160
|
if (plan.limit !== null)
|
|
119
|
-
query = query.limit(plan.limit);
|
|
120
|
-
return query.select(({ d
|
|
161
|
+
query = query.limit(plan.limit + 1);
|
|
162
|
+
return query.select(({ d }) => ({
|
|
121
163
|
room: d.room,
|
|
122
164
|
path: d.path,
|
|
123
165
|
type: d.type,
|
|
124
166
|
binary: d.binary,
|
|
167
|
+
...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
|
|
125
168
|
...(shape.fields === null ? {} : { fields: d.fields }),
|
|
126
|
-
...(shape
|
|
127
|
-
...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
|
|
128
|
-
...(shape.preamble ? { preamble: b.preamble } : {}),
|
|
129
|
-
...(shape.markdown ? { markdown: b.markdown } : {}),
|
|
130
|
-
...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
|
|
169
|
+
...(bodied(shape) ? { text: d.text } : {}),
|
|
131
170
|
...(shape.change ? { change: d.change } : {}),
|
|
171
|
+
...(shape.fileId ? { fileId: d.fileId } : {}),
|
|
132
172
|
}));
|
|
133
173
|
}
|
|
134
|
-
function
|
|
174
|
+
function references(q, rows, reach, locked) {
|
|
135
175
|
const from = q.from({ r: rows });
|
|
136
176
|
return from
|
|
137
|
-
.where(({ r }) => every([eq(r.
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
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 });
|
|
141
232
|
}
|
|
142
|
-
function groups(q, stage, shape
|
|
233
|
+
function groups(q, stage, shape) {
|
|
143
234
|
const keys = shape.groupBy.map((_, index) => `key${index}`);
|
|
144
235
|
const from = q.from({ m: stage });
|
|
145
236
|
let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
|
|
146
237
|
for (const { by, direction } of shape.orderBy) {
|
|
147
238
|
query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
|
|
148
239
|
}
|
|
149
|
-
if (limit !== null && shape.orderBy.length > 0)
|
|
150
|
-
query = query.limit(limit);
|
|
151
240
|
return query.select(({ m }) => ({
|
|
152
241
|
...Object.fromEntries(keys.map((key) => [key, m[key]])),
|
|
153
242
|
count: count(m.key),
|
|
@@ -157,28 +246,56 @@ function built(rows, plan) {
|
|
|
157
246
|
const shape = plan.shape;
|
|
158
247
|
if (shape.kind === 'documents') {
|
|
159
248
|
const collection = live((q) => documents(q, rows, plan, shape));
|
|
160
|
-
|
|
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
|
+
};
|
|
161
267
|
}
|
|
162
268
|
const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
|
|
163
269
|
key: d.key,
|
|
164
270
|
...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
|
|
165
271
|
})));
|
|
166
|
-
const collection = live((q) => groups(q, stage, shape
|
|
272
|
+
const collection = live((q) => groups(q, stage, shape));
|
|
167
273
|
return {
|
|
168
274
|
live: collection,
|
|
275
|
+
count: null,
|
|
276
|
+
links: null,
|
|
169
277
|
dispose: () => {
|
|
170
278
|
void collection.cleanup();
|
|
171
279
|
void stage.cleanup();
|
|
172
280
|
},
|
|
173
281
|
};
|
|
174
282
|
}
|
|
175
|
-
function
|
|
176
|
-
return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
|
|
177
|
-
}
|
|
178
|
-
function part(row, prefix, names, whole) {
|
|
283
|
+
function sectionsOf(sections, names) {
|
|
179
284
|
if (names === 'all')
|
|
180
|
-
return
|
|
181
|
-
return
|
|
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;
|
|
182
299
|
}
|
|
183
300
|
function masked(pointers) {
|
|
184
301
|
const root = { whole: false, members: new Map() };
|
|
@@ -229,15 +346,17 @@ function cut(value, mask) {
|
|
|
229
346
|
function selected(fields, pointers) {
|
|
230
347
|
return (cut(fields, masked(pointers)) ?? {});
|
|
231
348
|
}
|
|
232
|
-
function documentOf(row, shape) {
|
|
349
|
+
function documentOf(key, row, shape, bodies, linked) {
|
|
233
350
|
if (row.type === null) {
|
|
234
351
|
const binary = row.binary;
|
|
235
352
|
return {
|
|
236
353
|
room: row.room,
|
|
237
354
|
path: row.path,
|
|
238
355
|
...(binary === null ? {} : { binaryFile: binary }),
|
|
356
|
+
...(shape.fileId ? { fileId: row.fileId } : {}),
|
|
239
357
|
};
|
|
240
358
|
}
|
|
359
|
+
const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
|
|
241
360
|
return {
|
|
242
361
|
room: row.room,
|
|
243
362
|
path: row.path,
|
|
@@ -249,192 +368,812 @@ function documentOf(row, shape) {
|
|
|
249
368
|
? row.fields
|
|
250
369
|
: selected(row.fields, shape.fields),
|
|
251
370
|
}),
|
|
252
|
-
...(shape.sections === null
|
|
371
|
+
...(shape.sections === null || body === null
|
|
253
372
|
? {}
|
|
254
|
-
: {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
...(shape.preamble ? { preamble: row.preamble } : {}),
|
|
258
|
-
...(shape.markdown ? { markdown: row.markdown } : {}),
|
|
373
|
+
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
374
|
+
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
375
|
+
...(shape.markdown && body !== null ? { markdown: body.content } : {}),
|
|
259
376
|
...(shape.backlinks
|
|
260
377
|
? {
|
|
261
|
-
backlinks:
|
|
378
|
+
backlinks: (linked.get(documentKey(row.room, row.path)) ?? [])
|
|
379
|
+
.slice(0, MAXIMUM_WINDOW)
|
|
380
|
+
.map(([, link]) => link),
|
|
262
381
|
}
|
|
263
382
|
: {}),
|
|
264
383
|
...(shape.change ? { change: row.change } : {}),
|
|
384
|
+
...(shape.fileId ? { fileId: row.fileId } : {}),
|
|
265
385
|
};
|
|
266
386
|
}
|
|
267
|
-
function
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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;
|
|
274
406
|
}
|
|
275
|
-
|
|
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 }) {
|
|
276
465
|
return {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
count: row.count,
|
|
281
|
-
})),
|
|
282
|
-
}),
|
|
283
|
-
keys: bounded.map(([key]) => key),
|
|
466
|
+
...rows,
|
|
467
|
+
...(next === undefined ? {} : { next }),
|
|
468
|
+
...(total === undefined ? {} : { total }),
|
|
284
469
|
};
|
|
285
470
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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();
|
|
289
516
|
const changed = [];
|
|
290
|
-
const rows =
|
|
291
|
-
|
|
517
|
+
const rows = [];
|
|
518
|
+
yield* strides(next.keys.entries(), ([index, key]) => {
|
|
519
|
+
const row = later[index];
|
|
292
520
|
const kept = held.get(key);
|
|
521
|
+
present.add(key);
|
|
293
522
|
if (kept !== undefined && deepEquals(kept, row))
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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;
|
|
297
556
|
});
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
survivors.some((key, index) => key !== next.keys[index]);
|
|
303
|
-
if (changed.length === 0 && removed.length === 0 && !moved)
|
|
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) {
|
|
304
561
|
return null;
|
|
562
|
+
}
|
|
305
563
|
return {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
},
|
|
312
|
-
change: { changed, removed, order: moved ? next.keys : null },
|
|
564
|
+
changed,
|
|
565
|
+
removed,
|
|
566
|
+
order: moved ? next.keys : null,
|
|
567
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
568
|
+
...framing,
|
|
313
569
|
};
|
|
314
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
|
+
}
|
|
315
578
|
export function createReadSide() {
|
|
316
579
|
configureIndexDevMode({ enabled: false });
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
gcTime: 0,
|
|
321
|
-
startSync: true,
|
|
322
|
-
autoIndex: 'eager',
|
|
323
|
-
defaultIndexType: BasicIndex,
|
|
324
|
-
sync: {
|
|
325
|
-
rowUpdateMode: 'full',
|
|
326
|
-
sync: (params) => {
|
|
327
|
-
sync = params;
|
|
328
|
-
params.markReady();
|
|
329
|
-
},
|
|
330
|
-
},
|
|
331
|
-
});
|
|
580
|
+
const rows = new Map();
|
|
581
|
+
const roomPaths = new Map();
|
|
582
|
+
const views = new Set();
|
|
332
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
|
+
}
|
|
333
693
|
function searched(row) {
|
|
334
|
-
const
|
|
694
|
+
const read = row.text === null ? null : parse(row.text, row.path);
|
|
695
|
+
if (read === null || !read.ok)
|
|
696
|
+
return null;
|
|
335
697
|
return {
|
|
336
698
|
room: row.room,
|
|
337
699
|
path: row.path,
|
|
338
700
|
type: row.type,
|
|
339
701
|
fields: row.fields,
|
|
340
|
-
preamble:
|
|
341
|
-
sections:
|
|
702
|
+
preamble: read.document.preamble,
|
|
703
|
+
sections: read.document.sections,
|
|
342
704
|
};
|
|
343
705
|
}
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
if (next === null)
|
|
347
|
-
return;
|
|
348
|
-
entry.result = next.keyed;
|
|
349
|
-
for (const listener of entry.listeners)
|
|
350
|
-
listener(next.change);
|
|
706
|
+
function counted(entry) {
|
|
707
|
+
return entry.built.count === null ? undefined : entry.built.count.size;
|
|
351
708
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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;
|
|
360
744
|
}
|
|
361
|
-
|
|
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
|
+
});
|
|
362
851
|
return;
|
|
363
|
-
|
|
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();
|
|
364
911
|
for (const [key, row] of next) {
|
|
365
|
-
if (row
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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);
|
|
371
963
|
}
|
|
372
|
-
|
|
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);
|
|
373
1008
|
},
|
|
374
|
-
|
|
375
|
-
|
|
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;
|
|
376
1016
|
try {
|
|
377
|
-
|
|
378
|
-
|
|
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 };
|
|
379
1037
|
}
|
|
380
1038
|
finally {
|
|
381
|
-
|
|
1039
|
+
following?.unsubscribe();
|
|
1040
|
+
made?.dispose();
|
|
1041
|
+
dropped(view);
|
|
382
1042
|
}
|
|
383
1043
|
},
|
|
384
|
-
watch(plan, identity,
|
|
1044
|
+
watch(plan, identity, cover, deliverTo) {
|
|
385
1045
|
let entry = shared.get(identity);
|
|
386
1046
|
if (entry === undefined) {
|
|
1047
|
+
const view = viewOf(plan);
|
|
1048
|
+
void filled(view);
|
|
387
1049
|
const created = {
|
|
388
1050
|
plan,
|
|
389
|
-
|
|
1051
|
+
view,
|
|
1052
|
+
built: built(view.rows, plan),
|
|
1053
|
+
cover,
|
|
390
1054
|
result: undefined,
|
|
1055
|
+
rows: new Map(),
|
|
1056
|
+
generation: 0,
|
|
1057
|
+
bodies: new Map(),
|
|
391
1058
|
listeners: new Set(),
|
|
1059
|
+
fresh: new Set(),
|
|
1060
|
+
ready: new Set(),
|
|
392
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,
|
|
393
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
|
+
}
|
|
394
1082
|
created.built.live.on('status:change', ({ status }) => {
|
|
395
|
-
if (status
|
|
1083
|
+
if (status !== 'cleaned-up' || created.listeners.size > 0)
|
|
1084
|
+
return;
|
|
1085
|
+
if (shared.get(identity) === created)
|
|
396
1086
|
shared.delete(identity);
|
|
397
1087
|
});
|
|
398
1088
|
shared.set(identity, created);
|
|
399
1089
|
entry = created;
|
|
400
1090
|
}
|
|
401
1091
|
const observed = entry;
|
|
402
|
-
observed.listeners.add(
|
|
403
|
-
observed.built.live.onFirstReady(() => {
|
|
404
|
-
|
|
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))
|
|
405
1107
|
return;
|
|
1108
|
+
observed.fresh.add(deliverTo);
|
|
406
1109
|
if (observed.subscription === null) {
|
|
407
|
-
observed.
|
|
408
|
-
observed.subscription = observed.built.live.subscribeChanges(() => {
|
|
409
|
-
|
|
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();
|
|
410
1118
|
}, { includeInitialState: false });
|
|
1119
|
+
observed.counting =
|
|
1120
|
+
observed.built.count?.subscribeChanges(() => {
|
|
1121
|
+
observed.stale = true;
|
|
1122
|
+
settled();
|
|
1123
|
+
}, { includeInitialState: false }) ?? null;
|
|
411
1124
|
}
|
|
412
|
-
|
|
1125
|
+
wake(observed);
|
|
413
1126
|
});
|
|
414
1127
|
return () => {
|
|
415
|
-
|
|
1128
|
+
observed.fresh.delete(deliverTo);
|
|
1129
|
+
observed.ready.delete(deliverTo);
|
|
1130
|
+
if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
|
|
416
1131
|
return;
|
|
417
1132
|
observed.subscription?.unsubscribe();
|
|
418
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);
|
|
419
1148
|
};
|
|
420
1149
|
},
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
+
}
|
|
425
1156
|
},
|
|
426
1157
|
document(room, path) {
|
|
427
1158
|
const row = rows.get(documentKey(room, path));
|
|
428
|
-
return row
|
|
1159
|
+
return row?.kind !== 'document' || row.type === null ? null : searched(row);
|
|
429
1160
|
},
|
|
430
1161
|
close() {
|
|
1162
|
+
closed = true;
|
|
431
1163
|
for (const entry of shared.values()) {
|
|
432
1164
|
entry.listeners.clear();
|
|
1165
|
+
entry.fresh.clear();
|
|
1166
|
+
entry.ready.clear();
|
|
433
1167
|
entry.subscription?.unsubscribe();
|
|
1168
|
+
entry.counting?.unsubscribe();
|
|
1169
|
+
entry.linking?.unsubscribe();
|
|
434
1170
|
entry.built.dispose();
|
|
435
1171
|
}
|
|
436
1172
|
shared.clear();
|
|
437
|
-
|
|
1173
|
+
for (const view of [...views])
|
|
1174
|
+
dropped(view);
|
|
1175
|
+
rows.clear();
|
|
1176
|
+
roomPaths.clear();
|
|
438
1177
|
},
|
|
439
1178
|
};
|
|
440
1179
|
}
|