@agentshouse/mdruntime 0.1.0-c3903a68dcbbf → 0.1.0-c4478f1a5ac2a
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 +196 -97
- package/README.md +22 -12
- package/dist/adapter.d.ts +149 -64
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +47 -0
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +20 -6
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +110 -38
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +49 -21
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +33 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +60 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +26 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +62 -0
- package/dist/chat/markdown.d.ts +20 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +188 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +21 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +151 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +620 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +33 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +23 -19
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +69 -32
- package/dist/collection.d.ts +21 -14
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +270 -32
- package/dist/connection.d.ts +36 -38
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1605 -841
- package/dist/conversation.d.ts +43 -19
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +523 -210
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +11 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +54 -69
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +19 -1
- package/dist/index.d.ts +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/memory.d.ts +15 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +147 -47
- package/dist/model.d.ts +14 -12
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +15 -3
- package/dist/observation.d.ts +6 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +38 -17
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +35 -22
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +42 -1
- package/dist/overlay.d.ts +15 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +94 -77
- package/dist/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +0 -16
- package/dist/query.d.ts +40 -6
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +78 -36
- package/dist/reach.d.ts +42 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +294 -0
- package/dist/react.d.ts +41 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +64 -0
- package/dist/read-side.d.ts +16 -5
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +405 -137
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts +12 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +45 -36
- package/dist/replica.d.ts +10 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +122 -79
- package/dist/room-draft.d.ts +29 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +137 -26
- package/dist/rows.d.ts +10 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +85 -50
- package/dist/save.d.ts +32 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +465 -93
- package/dist/search.d.ts +31 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +109 -44
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -0
- package/dist/status.d.ts +2 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +71 -32
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +509 -221
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/wire.d.ts +58 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +65 -67
- package/package.json +31 -7
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/read-side.js
CHANGED
|
@@ -1,62 +1,71 @@
|
|
|
1
|
-
import { and, BasicIndex, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq,
|
|
2
|
-
import {
|
|
1
|
+
import { and, BasicIndex, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, inArray, or, toArray, } from '@tanstack/db';
|
|
2
|
+
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
3
3
|
import { documentKey } from './rows.js';
|
|
4
|
+
import { after, inUnits, ROW_WEIGHT } from './turn.js';
|
|
4
5
|
function every(expressions) {
|
|
5
6
|
const [first, second, ...rest] = expressions;
|
|
6
7
|
return second === undefined ? first : and(first, second, ...rest);
|
|
7
8
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
return second === undefined ? first : or(first, second, ...rest);
|
|
9
|
+
function same(held, value) {
|
|
10
|
+
return Array.isArray(held) ? held.includes(value) : held === value;
|
|
11
11
|
}
|
|
12
|
-
function
|
|
13
|
-
|
|
12
|
+
function ordered(held, [operator, bound]) {
|
|
13
|
+
if (typeof held !== typeof bound || held === null || typeof held === 'object')
|
|
14
|
+
return false;
|
|
15
|
+
const left = held;
|
|
16
|
+
const right = bound;
|
|
17
|
+
if (operator === 'gt')
|
|
18
|
+
return left > right;
|
|
19
|
+
if (operator === 'gte')
|
|
20
|
+
return left >= right;
|
|
21
|
+
if (operator === 'lt')
|
|
22
|
+
return left < right;
|
|
23
|
+
return left <= right;
|
|
14
24
|
}
|
|
15
|
-
function
|
|
25
|
+
function satisfied(test, held) {
|
|
16
26
|
if (test.op === 'eq')
|
|
17
|
-
return
|
|
18
|
-
if (test.op === 'ne')
|
|
19
|
-
return test.value
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const compare = { gt, gte, lt, lte };
|
|
37
|
-
return every(test.bounds.map(([operator, bound]) => compare[operator](field, bound)));
|
|
38
|
-
}
|
|
39
|
-
if (test.op === 'all') {
|
|
40
|
-
return every([not(isUndefined(size)), ...test.values.map((value) => inArray(value, field))]);
|
|
41
|
-
}
|
|
42
|
-
if (test.op === 'size')
|
|
43
|
-
return eq(size, test.size);
|
|
44
|
-
return test.exists ? not(isUndefined(written)) : isUndefined(written);
|
|
27
|
+
return same(held, test.value);
|
|
28
|
+
if (test.op === 'ne')
|
|
29
|
+
return !same(held, test.value);
|
|
30
|
+
if (test.op === 'in')
|
|
31
|
+
return test.values.some((value) => same(held, value));
|
|
32
|
+
if (test.op === 'nin')
|
|
33
|
+
return !test.values.some((value) => same(held, value));
|
|
34
|
+
if (test.op === 'range')
|
|
35
|
+
return test.bounds.every((bound) => ordered(held, bound));
|
|
36
|
+
if (test.op === 'all')
|
|
37
|
+
return Array.isArray(held) && test.values.every((value) => same(held, value));
|
|
38
|
+
return Array.isArray(held) && held.length === test.size;
|
|
39
|
+
}
|
|
40
|
+
function tested(test, reached) {
|
|
41
|
+
if (test.op === 'exists')
|
|
42
|
+
return test.exists === (reached !== undefined);
|
|
43
|
+
if (reached === undefined)
|
|
44
|
+
return test.op === 'ne' || test.op === 'nin';
|
|
45
|
+
return reached.some((held) => satisfied(test, held));
|
|
45
46
|
}
|
|
46
47
|
function conditioned(condition, row) {
|
|
47
48
|
if ('every' in condition)
|
|
48
|
-
return
|
|
49
|
+
return condition.every.every((item) => conditioned(item, row));
|
|
49
50
|
if ('some' in condition)
|
|
50
|
-
return
|
|
51
|
-
return tested(condition.test, row.
|
|
51
|
+
return condition.some.some((item) => conditioned(item, row));
|
|
52
|
+
return tested(condition.test, row.reached[condition.field]);
|
|
53
|
+
}
|
|
54
|
+
function narrowed(query, where, alias) {
|
|
55
|
+
return where === null ? query : query.fn.where((row) => conditioned(where, row[alias]));
|
|
56
|
+
}
|
|
57
|
+
function reached(reach, room, document) {
|
|
58
|
+
return reach === null
|
|
59
|
+
? []
|
|
60
|
+
: [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))];
|
|
52
61
|
}
|
|
53
|
-
function scoped(scope, row, rooms) {
|
|
62
|
+
function scoped(scope, row, rooms, reach) {
|
|
54
63
|
return every([
|
|
55
64
|
eq(row.kind, 'document'),
|
|
56
65
|
...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
|
|
66
|
+
...reached(reach, row.room, row.key),
|
|
57
67
|
...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
|
|
58
68
|
...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
|
|
59
|
-
...(scope.where === null ? [] : [conditioned(scope.where, row)]),
|
|
60
69
|
]);
|
|
61
70
|
}
|
|
62
71
|
function keyed(key, row) {
|
|
@@ -68,37 +77,35 @@ function live(query) {
|
|
|
68
77
|
startSync: true,
|
|
69
78
|
});
|
|
70
79
|
}
|
|
71
|
-
function related(q, rows, scope, field, kept) {
|
|
80
|
+
function related(q, rows, scope, field, kept, reach) {
|
|
72
81
|
const matched = kept === 'source' ? 'target' : 'source';
|
|
73
82
|
const from = q.from({ r: rows });
|
|
74
|
-
|
|
75
|
-
.where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [
|
|
83
|
+
const joined = from
|
|
84
|
+
.where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [inArray(field, r.forms)])]))
|
|
76
85
|
.innerJoin({ m: rows }, ({ r, m }) => eq(r[matched], m.key))
|
|
77
|
-
.where(({ m }) => scoped(scope, m, null))
|
|
86
|
+
.where(({ m }) => scoped(scope, m, null, reach));
|
|
87
|
+
return narrowed(joined, scope.where, 'm')
|
|
78
88
|
.groupBy(({ r }) => r[kept])
|
|
79
89
|
.select(({ r }) => ({ key: r[kept] }));
|
|
80
90
|
}
|
|
81
91
|
function filtered(q, rows, plan) {
|
|
82
92
|
const from = q.from({ d: rows });
|
|
83
|
-
let query = from.where(({ d }) => scoped(plan.root, d, plan.rooms));
|
|
93
|
+
let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach)), plan.root.where, 'd');
|
|
84
94
|
if (plan.hop !== null) {
|
|
85
|
-
const hop = related(q, rows, plan.hop, plan.hop.field, 'source');
|
|
95
|
+
const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
|
|
86
96
|
query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
|
|
87
97
|
}
|
|
88
98
|
if (plan.backlinks !== null) {
|
|
89
|
-
const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target');
|
|
99
|
+
const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach);
|
|
90
100
|
query = query.innerJoin({ k: backlinks }, ({ d, k }) => eq(d.key, k.key));
|
|
91
101
|
}
|
|
92
102
|
return query;
|
|
93
103
|
}
|
|
94
|
-
function
|
|
95
|
-
return
|
|
104
|
+
function bodied(shape) {
|
|
105
|
+
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
96
106
|
}
|
|
97
107
|
function documents(q, rows, plan, shape) {
|
|
98
108
|
let query = filtered(q, rows, plan);
|
|
99
|
-
if (shape.sections !== null || shape.preamble || shape.markdown) {
|
|
100
|
-
query = query.leftJoin({ b: rows }, ({ d, b }) => eq(d.body, b.key));
|
|
101
|
-
}
|
|
102
109
|
for (const { key, options } of shape.orderBy) {
|
|
103
110
|
query = query.orderBy(({ d }) => keyed(key, d), options);
|
|
104
111
|
}
|
|
@@ -107,24 +114,21 @@ function documents(q, rows, plan, shape) {
|
|
|
107
114
|
}
|
|
108
115
|
if (plan.limit !== null)
|
|
109
116
|
query = query.limit(plan.limit);
|
|
110
|
-
return query.select(({ d
|
|
117
|
+
return query.select(({ d }) => ({
|
|
111
118
|
room: d.room,
|
|
112
119
|
path: d.path,
|
|
113
120
|
type: d.type,
|
|
114
|
-
|
|
115
|
-
...(
|
|
116
|
-
...(shape
|
|
117
|
-
...(
|
|
118
|
-
...(shape.preamble ? { preamble: b.preamble } : {}),
|
|
119
|
-
...(shape.markdown ? { markdown: b.markdown } : {}),
|
|
120
|
-
...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d)) } : {}),
|
|
121
|
+
binary: d.binary,
|
|
122
|
+
...(shape.fields === null ? {} : { fields: d.fields }),
|
|
123
|
+
...(bodied(shape) ? { text: d.text } : {}),
|
|
124
|
+
...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
|
|
121
125
|
...(shape.change ? { change: d.change } : {}),
|
|
122
126
|
}));
|
|
123
127
|
}
|
|
124
|
-
function backlinked(q, rows, document) {
|
|
128
|
+
function backlinked(q, rows, document, reach) {
|
|
125
129
|
const from = q.from({ r: rows });
|
|
126
130
|
return from
|
|
127
|
-
.where(({ r }) => eq(r.target, document.key))
|
|
131
|
+
.where(({ r }) => every([eq(r.target, document.key), ...reached(reach, r.room, r.source)]))
|
|
128
132
|
.orderBy(({ r }) => r.path, { stringSort: 'lexical' })
|
|
129
133
|
.orderBy(({ r }) => r.field, { stringSort: 'lexical' })
|
|
130
134
|
.select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
|
|
@@ -162,31 +166,98 @@ function built(rows, plan) {
|
|
|
162
166
|
},
|
|
163
167
|
};
|
|
164
168
|
}
|
|
165
|
-
function
|
|
166
|
-
return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
|
|
167
|
-
}
|
|
168
|
-
function part(row, prefix, names, whole) {
|
|
169
|
+
function sectionsOf(sections, names) {
|
|
169
170
|
if (names === 'all')
|
|
170
|
-
return
|
|
171
|
-
return
|
|
171
|
+
return sections;
|
|
172
|
+
return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
|
|
173
|
+
}
|
|
174
|
+
function bodyOf(row, key, bodies) {
|
|
175
|
+
const text = row.text;
|
|
176
|
+
if (text === null)
|
|
177
|
+
return null;
|
|
178
|
+
const held = bodies?.get(key);
|
|
179
|
+
if (held !== undefined && held.text === text)
|
|
180
|
+
return held.document;
|
|
181
|
+
const read = parse(text, row.path);
|
|
182
|
+
const document = read.ok ? read.document : null;
|
|
183
|
+
bodies?.set(key, { text, document });
|
|
184
|
+
return document;
|
|
185
|
+
}
|
|
186
|
+
function masked(pointers) {
|
|
187
|
+
const root = { whole: false, members: new Map() };
|
|
188
|
+
for (const pointer of pointers) {
|
|
189
|
+
let mask = root;
|
|
190
|
+
for (const token of pointerSegments(pointer)) {
|
|
191
|
+
const next = mask.members.get(token) ?? { whole: false, members: new Map() };
|
|
192
|
+
mask.members.set(token, next);
|
|
193
|
+
mask = next;
|
|
194
|
+
}
|
|
195
|
+
mask.whole = true;
|
|
196
|
+
}
|
|
197
|
+
return root;
|
|
198
|
+
}
|
|
199
|
+
function merged(left, right) {
|
|
200
|
+
if (left === undefined)
|
|
201
|
+
return right;
|
|
202
|
+
const members = new Map(left.members);
|
|
203
|
+
for (const [token, mask] of right.members)
|
|
204
|
+
members.set(token, merged(members.get(token), mask));
|
|
205
|
+
return { whole: left.whole || right.whole, members };
|
|
172
206
|
}
|
|
173
|
-
function
|
|
174
|
-
if (
|
|
175
|
-
return
|
|
207
|
+
function cut(value, mask) {
|
|
208
|
+
if (mask.whole)
|
|
209
|
+
return value;
|
|
210
|
+
if (Array.isArray(value)) {
|
|
211
|
+
const each = {
|
|
212
|
+
whole: false,
|
|
213
|
+
members: new Map([...mask.members].filter(([token]) => !ARRAY_INDEX.test(token))),
|
|
214
|
+
};
|
|
215
|
+
const kept = [];
|
|
216
|
+
value.forEach((item, index) => {
|
|
217
|
+
const part = cut(item, merged(mask.members.get(String(index)), each));
|
|
218
|
+
if (part !== undefined)
|
|
219
|
+
kept[index] = part;
|
|
220
|
+
});
|
|
221
|
+
return kept.length === 0 ? undefined : kept;
|
|
222
|
+
}
|
|
223
|
+
if (typeof value !== 'object' || value === null)
|
|
224
|
+
return undefined;
|
|
225
|
+
const kept = Object.entries(value).flatMap(([key, held]) => {
|
|
226
|
+
const member = mask.members.get(key);
|
|
227
|
+
const part = member === undefined ? undefined : cut(held, member);
|
|
228
|
+
return part === undefined ? [] : [[key, part]];
|
|
229
|
+
});
|
|
230
|
+
return kept.length === 0 ? undefined : Object.fromEntries(kept);
|
|
231
|
+
}
|
|
232
|
+
function selected(fields, pointers) {
|
|
233
|
+
return (cut(fields, masked(pointers)) ?? {});
|
|
234
|
+
}
|
|
235
|
+
function documentOf(key, row, shape, bodies) {
|
|
236
|
+
if (row.type === null) {
|
|
237
|
+
const binary = row.binary;
|
|
238
|
+
return {
|
|
239
|
+
room: row.room,
|
|
240
|
+
path: row.path,
|
|
241
|
+
...(binary === null ? {} : { binaryFile: binary }),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
|
|
176
245
|
return {
|
|
177
246
|
room: row.room,
|
|
178
247
|
path: row.path,
|
|
179
248
|
type: row.type,
|
|
180
249
|
...(shape.fields === null
|
|
181
|
-
? {}
|
|
182
|
-
: { fields: part(row, 'field', shape.fields, row.fields) }),
|
|
183
|
-
...(shape.sections === null
|
|
184
250
|
? {}
|
|
185
251
|
: {
|
|
186
|
-
|
|
252
|
+
fields: shape.fields === 'all'
|
|
253
|
+
? row.fields
|
|
254
|
+
: selected(row.fields, shape.fields),
|
|
187
255
|
}),
|
|
188
|
-
...(shape.
|
|
189
|
-
|
|
256
|
+
...(shape.sections === null || body === null
|
|
257
|
+
? {}
|
|
258
|
+
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
259
|
+
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
260
|
+
...(shape.markdown && body !== null ? { markdown: body.content } : {}),
|
|
190
261
|
...(shape.backlinks
|
|
191
262
|
? {
|
|
192
263
|
backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
|
|
@@ -195,30 +266,40 @@ function documentOf(row, shape) {
|
|
|
195
266
|
...(shape.change ? { change: row.change } : {}),
|
|
196
267
|
};
|
|
197
268
|
}
|
|
198
|
-
function
|
|
269
|
+
function weight(row) {
|
|
270
|
+
return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
|
|
271
|
+
}
|
|
272
|
+
function rowsOfPlan(plan, entries, bodies, each = (row) => row) {
|
|
199
273
|
const shape = plan.shape;
|
|
200
274
|
if (shape.kind === 'documents') {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
275
|
+
const documents = [];
|
|
276
|
+
return after(inUnits(entries, ([key, row]) => {
|
|
277
|
+
documents.push(each(documentOf(key, row, shape, bodies)));
|
|
278
|
+
return weight(row);
|
|
279
|
+
}), () => ({ rows: { documents }, keys: entries.map(([key]) => key) }));
|
|
205
280
|
}
|
|
206
281
|
const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
};
|
|
282
|
+
const groups = [];
|
|
283
|
+
return after(inUnits(bounded, ([, row]) => {
|
|
284
|
+
groups.push(each({
|
|
285
|
+
key: shape.groupBy.map((_, index) => row[`key${index}`]),
|
|
286
|
+
count: row.count,
|
|
287
|
+
}));
|
|
288
|
+
return ROW_WEIGHT;
|
|
289
|
+
}), () => ({ rows: { groups }, keys: bounded.map(([key]) => key) }));
|
|
216
290
|
}
|
|
217
|
-
function reconciled(previous, next) {
|
|
218
|
-
const before =
|
|
219
|
-
const
|
|
291
|
+
export function reconciled(previous, next) {
|
|
292
|
+
const before = previous.result;
|
|
293
|
+
const after = next.result;
|
|
294
|
+
if ('pending' in before || 'pending' in after) {
|
|
295
|
+
if (deepEquals(before, after))
|
|
296
|
+
return null;
|
|
297
|
+
return { keyed: next, message: next };
|
|
298
|
+
}
|
|
299
|
+
const earlier = 'groups' in before ? before.groups : before.documents;
|
|
300
|
+
const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
|
|
220
301
|
const changed = [];
|
|
221
|
-
const rows =
|
|
302
|
+
const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
|
|
222
303
|
const key = next.keys[index];
|
|
223
304
|
const kept = held.get(key);
|
|
224
305
|
if (kept !== undefined && deepEquals(kept, row))
|
|
@@ -227,22 +308,37 @@ function reconciled(previous, next) {
|
|
|
227
308
|
return row;
|
|
228
309
|
});
|
|
229
310
|
const present = new Set(next.keys);
|
|
230
|
-
const survivors = previous.keys.filter((key) => present.has(key));
|
|
231
311
|
const removed = previous.keys.filter((key) => !present.has(key));
|
|
232
|
-
const moved =
|
|
233
|
-
|
|
234
|
-
if (changed.length === 0 && removed.length === 0 && !moved)
|
|
312
|
+
const moved = reordered(previous.keys, next.keys, present);
|
|
313
|
+
const covered = !deepEquals(before.coverage, after.coverage);
|
|
314
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered)
|
|
235
315
|
return null;
|
|
236
316
|
return {
|
|
237
317
|
keyed: {
|
|
238
|
-
result: 'groups' in
|
|
239
|
-
? { groups: rows }
|
|
240
|
-
: { documents: rows },
|
|
318
|
+
result: 'groups' in after
|
|
319
|
+
? { groups: rows, coverage: after.coverage }
|
|
320
|
+
: { documents: rows, coverage: after.coverage },
|
|
241
321
|
keys: next.keys,
|
|
242
322
|
},
|
|
243
|
-
|
|
323
|
+
message: {
|
|
324
|
+
changed,
|
|
325
|
+
removed,
|
|
326
|
+
order: moved ? next.keys : null,
|
|
327
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
328
|
+
},
|
|
244
329
|
};
|
|
245
330
|
}
|
|
331
|
+
function reordered(previous, next, present) {
|
|
332
|
+
let at = 0;
|
|
333
|
+
for (const key of previous) {
|
|
334
|
+
if (!present.has(key))
|
|
335
|
+
continue;
|
|
336
|
+
if (next[at] !== key)
|
|
337
|
+
return true;
|
|
338
|
+
at += 1;
|
|
339
|
+
}
|
|
340
|
+
return at !== next.length;
|
|
341
|
+
}
|
|
246
342
|
export function createReadSide() {
|
|
247
343
|
configureIndexDevMode({ enabled: false });
|
|
248
344
|
let sync;
|
|
@@ -262,23 +358,139 @@ export function createReadSide() {
|
|
|
262
358
|
});
|
|
263
359
|
const shared = new Map();
|
|
264
360
|
function searched(row) {
|
|
265
|
-
const
|
|
361
|
+
const read = row.text === null ? null : parse(row.text, row.path);
|
|
362
|
+
if (read === null || !read.ok)
|
|
363
|
+
return null;
|
|
266
364
|
return {
|
|
267
365
|
room: row.room,
|
|
268
366
|
path: row.path,
|
|
269
367
|
type: row.type,
|
|
270
368
|
fields: row.fields,
|
|
271
|
-
preamble:
|
|
272
|
-
sections:
|
|
369
|
+
preamble: read.document.preamble,
|
|
370
|
+
sections: read.document.sections,
|
|
273
371
|
};
|
|
274
372
|
}
|
|
275
|
-
function
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
373
|
+
function kept(entry, key, row) {
|
|
374
|
+
const held = entry.rows.get(key);
|
|
375
|
+
if (held !== undefined && deepEquals(held, row))
|
|
278
376
|
return;
|
|
279
|
-
entry.
|
|
280
|
-
|
|
281
|
-
|
|
377
|
+
entry.rows.set(key, row);
|
|
378
|
+
}
|
|
379
|
+
function rebuilt(entry) {
|
|
380
|
+
return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
|
|
381
|
+
const present = new Set(keys);
|
|
382
|
+
for (const key of [...entry.rows.keys()])
|
|
383
|
+
if (!present.has(key))
|
|
384
|
+
entry.rows.delete(key);
|
|
385
|
+
for (const key of [...entry.bodies.keys()]) {
|
|
386
|
+
if (!present.has(key))
|
|
387
|
+
entry.bodies.delete(key);
|
|
388
|
+
}
|
|
389
|
+
keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
|
|
390
|
+
return keys;
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function refreshed(entry, dirty) {
|
|
394
|
+
const shape = entry.plan.shape;
|
|
395
|
+
const keys = [...entry.built.live.keys()];
|
|
396
|
+
const named = new Set([...dirty, ...keys.filter((key) => !entry.rows.has(key))]);
|
|
397
|
+
const values = [...named].map((key) => [key, entry.built.live.get(key)]);
|
|
398
|
+
const done = inUnits(values, ([key, value]) => {
|
|
399
|
+
if (value !== undefined)
|
|
400
|
+
kept(entry, key, documentOf(key, value, shape, entry.bodies));
|
|
401
|
+
else {
|
|
402
|
+
entry.rows.delete(key);
|
|
403
|
+
entry.bodies.delete(key);
|
|
404
|
+
}
|
|
405
|
+
return weight(value);
|
|
406
|
+
});
|
|
407
|
+
return after(done, () => keys);
|
|
408
|
+
}
|
|
409
|
+
function current(entry, keys, coverage) {
|
|
410
|
+
const found = keys.map((key) => entry.rows.get(key));
|
|
411
|
+
return {
|
|
412
|
+
result: entry.plan.shape.kind === 'groups'
|
|
413
|
+
? { groups: found, coverage }
|
|
414
|
+
: { documents: found, coverage },
|
|
415
|
+
keys,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function delivered(entry, next) {
|
|
419
|
+
const previous = entry.result;
|
|
420
|
+
entry.result = next;
|
|
421
|
+
const message = previous === undefined ? null : changedFrom(previous, next);
|
|
422
|
+
if (message !== null)
|
|
423
|
+
for (const listener of entry.ready)
|
|
424
|
+
listener(message);
|
|
425
|
+
for (const listener of entry.fresh) {
|
|
426
|
+
entry.ready.add(listener);
|
|
427
|
+
listener(next);
|
|
428
|
+
}
|
|
429
|
+
entry.fresh.clear();
|
|
430
|
+
}
|
|
431
|
+
function run(entry) {
|
|
432
|
+
while (entry.listeners.size > 0 &&
|
|
433
|
+
(entry.whole || entry.dirty.size > 0 || entry.stale || entry.fresh.size > 0)) {
|
|
434
|
+
entry.stale = false;
|
|
435
|
+
const cover = entry.cover();
|
|
436
|
+
if ('pending' in cover) {
|
|
437
|
+
entry.whole = true;
|
|
438
|
+
entry.dirty.clear();
|
|
439
|
+
entry.rows.clear();
|
|
440
|
+
entry.bodies.clear();
|
|
441
|
+
delivered(entry, { result: cover, keys: [] });
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
const whole = entry.whole ||
|
|
445
|
+
entry.plan.shape.kind === 'groups' ||
|
|
446
|
+
entry.result === undefined ||
|
|
447
|
+
'pending' in entry.result.result;
|
|
448
|
+
const dirty = entry.dirty;
|
|
449
|
+
entry.dirty = new Set();
|
|
450
|
+
entry.whole = false;
|
|
451
|
+
const keys = whole ? rebuilt(entry) : refreshed(entry, dirty);
|
|
452
|
+
if (keys instanceof Promise) {
|
|
453
|
+
void keys
|
|
454
|
+
.then((found) => {
|
|
455
|
+
if (entry.listeners.size > 0)
|
|
456
|
+
delivered(entry, current(entry, found, cover.coverage));
|
|
457
|
+
run(entry);
|
|
458
|
+
})
|
|
459
|
+
.catch(() => {
|
|
460
|
+
entry.running = false;
|
|
461
|
+
});
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
delivered(entry, current(entry, keys, cover.coverage));
|
|
465
|
+
}
|
|
466
|
+
entry.running = false;
|
|
467
|
+
}
|
|
468
|
+
function wake(entry) {
|
|
469
|
+
if (entry.subscription === null || entry.running)
|
|
470
|
+
return;
|
|
471
|
+
entry.running = true;
|
|
472
|
+
try {
|
|
473
|
+
run(entry);
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
entry.running = false;
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function recover() {
|
|
481
|
+
for (const entry of shared.values()) {
|
|
482
|
+
if (entry.result === undefined || entry.subscription === null)
|
|
483
|
+
continue;
|
|
484
|
+
const cover = entry.cover();
|
|
485
|
+
const before = entry.result.result;
|
|
486
|
+
const held = 'pending' in cover
|
|
487
|
+
? deepEquals(before, cover)
|
|
488
|
+
: 'coverage' in before && deepEquals(before.coverage, cover.coverage);
|
|
489
|
+
if (held)
|
|
490
|
+
continue;
|
|
491
|
+
entry.stale = true;
|
|
492
|
+
wake(entry);
|
|
493
|
+
}
|
|
282
494
|
}
|
|
283
495
|
return {
|
|
284
496
|
apply(set) {
|
|
@@ -289,38 +501,52 @@ export function createReadSide() {
|
|
|
289
501
|
for (const row of change.after)
|
|
290
502
|
next.set(row.key, row);
|
|
291
503
|
}
|
|
292
|
-
if (next.size
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
504
|
+
if (next.size > 0) {
|
|
505
|
+
sync.begin();
|
|
506
|
+
for (const [key, row] of next) {
|
|
507
|
+
if (row === null) {
|
|
508
|
+
if (rows.has(key))
|
|
509
|
+
sync.write({ type: 'delete', key });
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
|
|
300
513
|
}
|
|
301
|
-
sync.
|
|
514
|
+
sync.commit();
|
|
302
515
|
}
|
|
303
|
-
|
|
516
|
+
recover();
|
|
304
517
|
},
|
|
305
|
-
|
|
518
|
+
recover,
|
|
519
|
+
async query(plan, at) {
|
|
306
520
|
const { live: collection, dispose } = built(rows, plan);
|
|
307
521
|
try {
|
|
308
522
|
await collection.toArrayWhenReady();
|
|
309
|
-
|
|
523
|
+
const entries = [...collection.entries()];
|
|
524
|
+
const held = at();
|
|
525
|
+
const found = await rowsOfPlan(plan, entries, null, structuredClone);
|
|
526
|
+
return { rows: found.rows, at: held };
|
|
310
527
|
}
|
|
311
528
|
finally {
|
|
312
529
|
dispose();
|
|
313
530
|
}
|
|
314
531
|
},
|
|
315
|
-
watch(plan, identity,
|
|
532
|
+
watch(plan, identity, cover, deliverTo) {
|
|
316
533
|
let entry = shared.get(identity);
|
|
317
534
|
if (entry === undefined) {
|
|
318
535
|
const created = {
|
|
319
536
|
plan,
|
|
320
537
|
built: built(rows, plan),
|
|
538
|
+
cover,
|
|
321
539
|
result: undefined,
|
|
540
|
+
rows: new Map(),
|
|
541
|
+
bodies: new Map(),
|
|
322
542
|
listeners: new Set(),
|
|
543
|
+
fresh: new Set(),
|
|
544
|
+
ready: new Set(),
|
|
323
545
|
subscription: null,
|
|
546
|
+
whole: true,
|
|
547
|
+
dirty: new Set(),
|
|
548
|
+
stale: false,
|
|
549
|
+
running: false,
|
|
324
550
|
};
|
|
325
551
|
created.built.live.on('status:change', ({ status }) => {
|
|
326
552
|
if (status === 'cleaned-up' && created.listeners.size === 0)
|
|
@@ -330,29 +556,40 @@ export function createReadSide() {
|
|
|
330
556
|
entry = created;
|
|
331
557
|
}
|
|
332
558
|
const observed = entry;
|
|
333
|
-
observed.listeners.add(
|
|
559
|
+
observed.listeners.add(deliverTo);
|
|
334
560
|
observed.built.live.onFirstReady(() => {
|
|
335
|
-
if (!observed.listeners.has(
|
|
561
|
+
if (!observed.listeners.has(deliverTo))
|
|
336
562
|
return;
|
|
563
|
+
observed.fresh.add(deliverTo);
|
|
337
564
|
if (observed.subscription === null) {
|
|
338
|
-
observed.
|
|
339
|
-
observed.subscription = observed.built.live.subscribeChanges(() => {
|
|
340
|
-
|
|
565
|
+
observed.whole = true;
|
|
566
|
+
observed.subscription = observed.built.live.subscribeChanges((changes) => {
|
|
567
|
+
if (observed.plan.shape.kind === 'groups')
|
|
568
|
+
observed.whole = true;
|
|
569
|
+
else
|
|
570
|
+
for (const { key } of changes)
|
|
571
|
+
observed.dirty.add(key);
|
|
572
|
+
wake(observed);
|
|
341
573
|
}, { includeInitialState: false });
|
|
342
574
|
}
|
|
343
|
-
|
|
575
|
+
wake(observed);
|
|
344
576
|
});
|
|
345
577
|
return () => {
|
|
346
|
-
|
|
578
|
+
observed.fresh.delete(deliverTo);
|
|
579
|
+
observed.ready.delete(deliverTo);
|
|
580
|
+
if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
|
|
347
581
|
return;
|
|
348
582
|
observed.subscription?.unsubscribe();
|
|
349
583
|
observed.subscription = null;
|
|
584
|
+
observed.result = undefined;
|
|
585
|
+
observed.rows.clear();
|
|
586
|
+
observed.bodies.clear();
|
|
587
|
+
observed.dirty.clear();
|
|
588
|
+
observed.whole = true;
|
|
350
589
|
};
|
|
351
590
|
},
|
|
352
|
-
|
|
353
|
-
return rows.toArray
|
|
354
|
-
.filter((row) => row.kind === 'document' && row.type !== null)
|
|
355
|
-
.map(searched);
|
|
591
|
+
room(room) {
|
|
592
|
+
return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
|
|
356
593
|
},
|
|
357
594
|
document(room, path) {
|
|
358
595
|
const row = rows.get(documentKey(room, path));
|
|
@@ -361,6 +598,8 @@ export function createReadSide() {
|
|
|
361
598
|
close() {
|
|
362
599
|
for (const entry of shared.values()) {
|
|
363
600
|
entry.listeners.clear();
|
|
601
|
+
entry.fresh.clear();
|
|
602
|
+
entry.ready.clear();
|
|
364
603
|
entry.subscription?.unsubscribe();
|
|
365
604
|
entry.built.dispose();
|
|
366
605
|
}
|
|
@@ -369,3 +608,32 @@ export function createReadSide() {
|
|
|
369
608
|
},
|
|
370
609
|
};
|
|
371
610
|
}
|
|
611
|
+
function changedFrom(previous, next) {
|
|
612
|
+
const before = previous.result;
|
|
613
|
+
const after = next.result;
|
|
614
|
+
if ('pending' in before || 'pending' in after)
|
|
615
|
+
return deepEquals(before, after) ? null : next;
|
|
616
|
+
const earlier = 'groups' in before ? before.groups : before.documents;
|
|
617
|
+
const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
|
|
618
|
+
const later = 'groups' in after ? after.groups : after.documents;
|
|
619
|
+
const changed = [];
|
|
620
|
+
next.keys.forEach((key, index) => {
|
|
621
|
+
if (held.get(key) !== later[index])
|
|
622
|
+
changed.push([key, later[index]]);
|
|
623
|
+
});
|
|
624
|
+
const present = new Set(next.keys);
|
|
625
|
+
const removed = previous.keys.filter((key) => !present.has(key));
|
|
626
|
+
const moved = reordered(previous.keys, next.keys, present);
|
|
627
|
+
const covered = !deepEquals(before.coverage, after.coverage);
|
|
628
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered)
|
|
629
|
+
return null;
|
|
630
|
+
return {
|
|
631
|
+
changed,
|
|
632
|
+
removed,
|
|
633
|
+
order: moved ? next.keys : null,
|
|
634
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function rowAt(rows, index) {
|
|
638
|
+
return 'groups' in rows ? rows.groups[index] : rows.documents[index];
|
|
639
|
+
}
|