@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-ceb97106cc2fd
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 +273 -175
- package/README.md +21 -11
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +129 -41
- 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 +114 -37
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +49 -16
- 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 +61 -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 +27 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +63 -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 +22 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +155 -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 +629 -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 +34 -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 +28 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +71 -26
- package/dist/collection.d.ts +21 -12
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +271 -23
- package/dist/connection.d.ts +50 -23
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2074 -735
- package/dist/conversation.d.ts +41 -14
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +546 -214
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- 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 +24 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +126 -45
- 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 +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +17 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +159 -46
- package/dist/model.d.ts +17 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +16 -4
- 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 +32 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +42 -1
- package/dist/overlay.d.ts +16 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +118 -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 +46 -6
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +85 -37
- package/dist/reach.d.ts +45 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +306 -0
- package/dist/react.d.ts +44 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +66 -0
- package/dist/read-side.d.ts +16 -5
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +408 -137
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- 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 +51 -36
- package/dist/replica.d.ts +10 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +129 -73
- package/dist/room-draft.d.ts +41 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +139 -28
- package/dist/rows.d.ts +11 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +87 -50
- package/dist/save.d.ts +32 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +465 -92
- package/dist/search.d.ts +37 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +116 -45
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +61 -0
- 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 +6 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +76 -19
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +539 -200
- 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 +162 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +82 -61
- 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,22 @@ 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 } : {}),
|
|
126
|
+
...(shape.fileId ? { fileId: d.fileId } : {}),
|
|
122
127
|
}));
|
|
123
128
|
}
|
|
124
|
-
function backlinked(q, rows, document) {
|
|
129
|
+
function backlinked(q, rows, document, reach) {
|
|
125
130
|
const from = q.from({ r: rows });
|
|
126
131
|
return from
|
|
127
|
-
.where(({ r }) => eq(r.target, document.key))
|
|
132
|
+
.where(({ r }) => every([eq(r.target, document.key), ...reached(reach, r.room, r.source)]))
|
|
128
133
|
.orderBy(({ r }) => r.path, { stringSort: 'lexical' })
|
|
129
134
|
.orderBy(({ r }) => r.field, { stringSort: 'lexical' })
|
|
130
135
|
.select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
|
|
@@ -162,63 +167,142 @@ function built(rows, plan) {
|
|
|
162
167
|
},
|
|
163
168
|
};
|
|
164
169
|
}
|
|
165
|
-
function
|
|
166
|
-
return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
|
|
167
|
-
}
|
|
168
|
-
function part(row, prefix, names, whole) {
|
|
170
|
+
function sectionsOf(sections, names) {
|
|
169
171
|
if (names === 'all')
|
|
170
|
-
return
|
|
171
|
-
return
|
|
172
|
+
return sections;
|
|
173
|
+
return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
|
|
174
|
+
}
|
|
175
|
+
function bodyOf(row, key, bodies) {
|
|
176
|
+
const text = row.text;
|
|
177
|
+
if (text === null)
|
|
178
|
+
return null;
|
|
179
|
+
const held = bodies?.get(key);
|
|
180
|
+
if (held !== undefined && held.text === text)
|
|
181
|
+
return held.document;
|
|
182
|
+
const read = parse(text, row.path);
|
|
183
|
+
const document = read.ok ? read.document : null;
|
|
184
|
+
bodies?.set(key, { text, document });
|
|
185
|
+
return document;
|
|
186
|
+
}
|
|
187
|
+
function masked(pointers) {
|
|
188
|
+
const root = { whole: false, members: new Map() };
|
|
189
|
+
for (const pointer of pointers) {
|
|
190
|
+
let mask = root;
|
|
191
|
+
for (const token of pointerSegments(pointer)) {
|
|
192
|
+
const next = mask.members.get(token) ?? { whole: false, members: new Map() };
|
|
193
|
+
mask.members.set(token, next);
|
|
194
|
+
mask = next;
|
|
195
|
+
}
|
|
196
|
+
mask.whole = true;
|
|
197
|
+
}
|
|
198
|
+
return root;
|
|
199
|
+
}
|
|
200
|
+
function merged(left, right) {
|
|
201
|
+
if (left === undefined)
|
|
202
|
+
return right;
|
|
203
|
+
const members = new Map(left.members);
|
|
204
|
+
for (const [token, mask] of right.members)
|
|
205
|
+
members.set(token, merged(members.get(token), mask));
|
|
206
|
+
return { whole: left.whole || right.whole, members };
|
|
172
207
|
}
|
|
173
|
-
function
|
|
174
|
-
if (
|
|
175
|
-
return
|
|
208
|
+
function cut(value, mask) {
|
|
209
|
+
if (mask.whole)
|
|
210
|
+
return value;
|
|
211
|
+
if (Array.isArray(value)) {
|
|
212
|
+
const each = {
|
|
213
|
+
whole: false,
|
|
214
|
+
members: new Map([...mask.members].filter(([token]) => !ARRAY_INDEX.test(token))),
|
|
215
|
+
};
|
|
216
|
+
const kept = [];
|
|
217
|
+
value.forEach((item, index) => {
|
|
218
|
+
const part = cut(item, merged(mask.members.get(String(index)), each));
|
|
219
|
+
if (part !== undefined)
|
|
220
|
+
kept[index] = part;
|
|
221
|
+
});
|
|
222
|
+
return kept.length === 0 ? undefined : kept;
|
|
223
|
+
}
|
|
224
|
+
if (typeof value !== 'object' || value === null)
|
|
225
|
+
return undefined;
|
|
226
|
+
const kept = Object.entries(value).flatMap(([key, held]) => {
|
|
227
|
+
const member = mask.members.get(key);
|
|
228
|
+
const part = member === undefined ? undefined : cut(held, member);
|
|
229
|
+
return part === undefined ? [] : [[key, part]];
|
|
230
|
+
});
|
|
231
|
+
return kept.length === 0 ? undefined : Object.fromEntries(kept);
|
|
232
|
+
}
|
|
233
|
+
function selected(fields, pointers) {
|
|
234
|
+
return (cut(fields, masked(pointers)) ?? {});
|
|
235
|
+
}
|
|
236
|
+
function documentOf(key, row, shape, bodies) {
|
|
237
|
+
if (row.type === null) {
|
|
238
|
+
const binary = row.binary;
|
|
239
|
+
return {
|
|
240
|
+
room: row.room,
|
|
241
|
+
path: row.path,
|
|
242
|
+
...(binary === null ? {} : { binaryFile: binary }),
|
|
243
|
+
...(shape.fileId ? { fileId: row.fileId } : {}),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
|
|
176
247
|
return {
|
|
177
248
|
room: row.room,
|
|
178
249
|
path: row.path,
|
|
179
250
|
type: row.type,
|
|
180
251
|
...(shape.fields === null
|
|
181
|
-
? {}
|
|
182
|
-
: { fields: part(row, 'field', shape.fields, row.fields) }),
|
|
183
|
-
...(shape.sections === null
|
|
184
252
|
? {}
|
|
185
253
|
: {
|
|
186
|
-
|
|
254
|
+
fields: shape.fields === 'all'
|
|
255
|
+
? row.fields
|
|
256
|
+
: selected(row.fields, shape.fields),
|
|
187
257
|
}),
|
|
188
|
-
...(shape.
|
|
189
|
-
|
|
258
|
+
...(shape.sections === null || body === null
|
|
259
|
+
? {}
|
|
260
|
+
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
261
|
+
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
262
|
+
...(shape.markdown && body !== null ? { markdown: body.content } : {}),
|
|
190
263
|
...(shape.backlinks
|
|
191
264
|
? {
|
|
192
265
|
backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
|
|
193
266
|
}
|
|
194
267
|
: {}),
|
|
195
268
|
...(shape.change ? { change: row.change } : {}),
|
|
269
|
+
...(shape.fileId ? { fileId: row.fileId } : {}),
|
|
196
270
|
};
|
|
197
271
|
}
|
|
198
|
-
function
|
|
272
|
+
function weight(row) {
|
|
273
|
+
return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
|
|
274
|
+
}
|
|
275
|
+
function rowsOfPlan(plan, entries, bodies, each = (row) => row) {
|
|
199
276
|
const shape = plan.shape;
|
|
200
277
|
if (shape.kind === 'documents') {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
278
|
+
const documents = [];
|
|
279
|
+
return after(inUnits(entries, ([key, row]) => {
|
|
280
|
+
documents.push(each(documentOf(key, row, shape, bodies)));
|
|
281
|
+
return weight(row);
|
|
282
|
+
}), () => ({ rows: { documents }, keys: entries.map(([key]) => key) }));
|
|
205
283
|
}
|
|
206
284
|
const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
};
|
|
285
|
+
const groups = [];
|
|
286
|
+
return after(inUnits(bounded, ([, row]) => {
|
|
287
|
+
groups.push(each({
|
|
288
|
+
key: shape.groupBy.map((_, index) => row[`key${index}`]),
|
|
289
|
+
count: row.count,
|
|
290
|
+
}));
|
|
291
|
+
return ROW_WEIGHT;
|
|
292
|
+
}), () => ({ rows: { groups }, keys: bounded.map(([key]) => key) }));
|
|
216
293
|
}
|
|
217
|
-
function reconciled(previous, next) {
|
|
218
|
-
const before =
|
|
219
|
-
const
|
|
294
|
+
export function reconciled(previous, next) {
|
|
295
|
+
const before = previous.result;
|
|
296
|
+
const after = next.result;
|
|
297
|
+
if ('pending' in before || 'pending' in after) {
|
|
298
|
+
if (deepEquals(before, after))
|
|
299
|
+
return null;
|
|
300
|
+
return { keyed: next, message: next };
|
|
301
|
+
}
|
|
302
|
+
const earlier = 'groups' in before ? before.groups : before.documents;
|
|
303
|
+
const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
|
|
220
304
|
const changed = [];
|
|
221
|
-
const rows =
|
|
305
|
+
const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
|
|
222
306
|
const key = next.keys[index];
|
|
223
307
|
const kept = held.get(key);
|
|
224
308
|
if (kept !== undefined && deepEquals(kept, row))
|
|
@@ -227,22 +311,37 @@ function reconciled(previous, next) {
|
|
|
227
311
|
return row;
|
|
228
312
|
});
|
|
229
313
|
const present = new Set(next.keys);
|
|
230
|
-
const survivors = previous.keys.filter((key) => present.has(key));
|
|
231
314
|
const removed = previous.keys.filter((key) => !present.has(key));
|
|
232
|
-
const moved =
|
|
233
|
-
|
|
234
|
-
if (changed.length === 0 && removed.length === 0 && !moved)
|
|
315
|
+
const moved = reordered(previous.keys, next.keys, present);
|
|
316
|
+
const covered = !deepEquals(before.coverage, after.coverage);
|
|
317
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered)
|
|
235
318
|
return null;
|
|
236
319
|
return {
|
|
237
320
|
keyed: {
|
|
238
|
-
result: 'groups' in
|
|
239
|
-
? { groups: rows }
|
|
240
|
-
: { documents: rows },
|
|
321
|
+
result: 'groups' in after
|
|
322
|
+
? { groups: rows, coverage: after.coverage }
|
|
323
|
+
: { documents: rows, coverage: after.coverage },
|
|
241
324
|
keys: next.keys,
|
|
242
325
|
},
|
|
243
|
-
|
|
326
|
+
message: {
|
|
327
|
+
changed,
|
|
328
|
+
removed,
|
|
329
|
+
order: moved ? next.keys : null,
|
|
330
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
331
|
+
},
|
|
244
332
|
};
|
|
245
333
|
}
|
|
334
|
+
function reordered(previous, next, present) {
|
|
335
|
+
let at = 0;
|
|
336
|
+
for (const key of previous) {
|
|
337
|
+
if (!present.has(key))
|
|
338
|
+
continue;
|
|
339
|
+
if (next[at] !== key)
|
|
340
|
+
return true;
|
|
341
|
+
at += 1;
|
|
342
|
+
}
|
|
343
|
+
return at !== next.length;
|
|
344
|
+
}
|
|
246
345
|
export function createReadSide() {
|
|
247
346
|
configureIndexDevMode({ enabled: false });
|
|
248
347
|
let sync;
|
|
@@ -262,23 +361,139 @@ export function createReadSide() {
|
|
|
262
361
|
});
|
|
263
362
|
const shared = new Map();
|
|
264
363
|
function searched(row) {
|
|
265
|
-
const
|
|
364
|
+
const read = row.text === null ? null : parse(row.text, row.path);
|
|
365
|
+
if (read === null || !read.ok)
|
|
366
|
+
return null;
|
|
266
367
|
return {
|
|
267
368
|
room: row.room,
|
|
268
369
|
path: row.path,
|
|
269
370
|
type: row.type,
|
|
270
371
|
fields: row.fields,
|
|
271
|
-
preamble:
|
|
272
|
-
sections:
|
|
372
|
+
preamble: read.document.preamble,
|
|
373
|
+
sections: read.document.sections,
|
|
273
374
|
};
|
|
274
375
|
}
|
|
275
|
-
function
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
376
|
+
function kept(entry, key, row) {
|
|
377
|
+
const held = entry.rows.get(key);
|
|
378
|
+
if (held !== undefined && deepEquals(held, row))
|
|
278
379
|
return;
|
|
279
|
-
entry.
|
|
280
|
-
|
|
281
|
-
|
|
380
|
+
entry.rows.set(key, row);
|
|
381
|
+
}
|
|
382
|
+
function rebuilt(entry) {
|
|
383
|
+
return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
|
|
384
|
+
const present = new Set(keys);
|
|
385
|
+
for (const key of [...entry.rows.keys()])
|
|
386
|
+
if (!present.has(key))
|
|
387
|
+
entry.rows.delete(key);
|
|
388
|
+
for (const key of [...entry.bodies.keys()]) {
|
|
389
|
+
if (!present.has(key))
|
|
390
|
+
entry.bodies.delete(key);
|
|
391
|
+
}
|
|
392
|
+
keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
|
|
393
|
+
return keys;
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function refreshed(entry, dirty) {
|
|
397
|
+
const shape = entry.plan.shape;
|
|
398
|
+
const keys = [...entry.built.live.keys()];
|
|
399
|
+
const named = new Set([...dirty, ...keys.filter((key) => !entry.rows.has(key))]);
|
|
400
|
+
const values = [...named].map((key) => [key, entry.built.live.get(key)]);
|
|
401
|
+
const done = inUnits(values, ([key, value]) => {
|
|
402
|
+
if (value !== undefined)
|
|
403
|
+
kept(entry, key, documentOf(key, value, shape, entry.bodies));
|
|
404
|
+
else {
|
|
405
|
+
entry.rows.delete(key);
|
|
406
|
+
entry.bodies.delete(key);
|
|
407
|
+
}
|
|
408
|
+
return weight(value);
|
|
409
|
+
});
|
|
410
|
+
return after(done, () => keys);
|
|
411
|
+
}
|
|
412
|
+
function current(entry, keys, coverage) {
|
|
413
|
+
const found = keys.map((key) => entry.rows.get(key));
|
|
414
|
+
return {
|
|
415
|
+
result: entry.plan.shape.kind === 'groups'
|
|
416
|
+
? { groups: found, coverage }
|
|
417
|
+
: { documents: found, coverage },
|
|
418
|
+
keys,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function delivered(entry, next) {
|
|
422
|
+
const previous = entry.result;
|
|
423
|
+
entry.result = next;
|
|
424
|
+
const message = previous === undefined ? null : changedFrom(previous, next);
|
|
425
|
+
if (message !== null)
|
|
426
|
+
for (const listener of entry.ready)
|
|
427
|
+
listener(message);
|
|
428
|
+
for (const listener of entry.fresh) {
|
|
429
|
+
entry.ready.add(listener);
|
|
430
|
+
listener(next);
|
|
431
|
+
}
|
|
432
|
+
entry.fresh.clear();
|
|
433
|
+
}
|
|
434
|
+
function run(entry) {
|
|
435
|
+
while (entry.listeners.size > 0 &&
|
|
436
|
+
(entry.whole || entry.dirty.size > 0 || entry.stale || entry.fresh.size > 0)) {
|
|
437
|
+
entry.stale = false;
|
|
438
|
+
const cover = entry.cover();
|
|
439
|
+
if ('pending' in cover) {
|
|
440
|
+
entry.whole = true;
|
|
441
|
+
entry.dirty.clear();
|
|
442
|
+
entry.rows.clear();
|
|
443
|
+
entry.bodies.clear();
|
|
444
|
+
delivered(entry, { result: cover, keys: [] });
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
const whole = entry.whole ||
|
|
448
|
+
entry.plan.shape.kind === 'groups' ||
|
|
449
|
+
entry.result === undefined ||
|
|
450
|
+
'pending' in entry.result.result;
|
|
451
|
+
const dirty = entry.dirty;
|
|
452
|
+
entry.dirty = new Set();
|
|
453
|
+
entry.whole = false;
|
|
454
|
+
const keys = whole ? rebuilt(entry) : refreshed(entry, dirty);
|
|
455
|
+
if (keys instanceof Promise) {
|
|
456
|
+
void keys
|
|
457
|
+
.then((found) => {
|
|
458
|
+
if (entry.listeners.size > 0)
|
|
459
|
+
delivered(entry, current(entry, found, cover.coverage));
|
|
460
|
+
run(entry);
|
|
461
|
+
})
|
|
462
|
+
.catch(() => {
|
|
463
|
+
entry.running = false;
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
delivered(entry, current(entry, keys, cover.coverage));
|
|
468
|
+
}
|
|
469
|
+
entry.running = false;
|
|
470
|
+
}
|
|
471
|
+
function wake(entry) {
|
|
472
|
+
if (entry.subscription === null || entry.running)
|
|
473
|
+
return;
|
|
474
|
+
entry.running = true;
|
|
475
|
+
try {
|
|
476
|
+
run(entry);
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
entry.running = false;
|
|
480
|
+
throw error;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function recover() {
|
|
484
|
+
for (const entry of shared.values()) {
|
|
485
|
+
if (entry.result === undefined || entry.subscription === null)
|
|
486
|
+
continue;
|
|
487
|
+
const cover = entry.cover();
|
|
488
|
+
const before = entry.result.result;
|
|
489
|
+
const held = 'pending' in cover
|
|
490
|
+
? deepEquals(before, cover)
|
|
491
|
+
: 'coverage' in before && deepEquals(before.coverage, cover.coverage);
|
|
492
|
+
if (held)
|
|
493
|
+
continue;
|
|
494
|
+
entry.stale = true;
|
|
495
|
+
wake(entry);
|
|
496
|
+
}
|
|
282
497
|
}
|
|
283
498
|
return {
|
|
284
499
|
apply(set) {
|
|
@@ -289,38 +504,52 @@ export function createReadSide() {
|
|
|
289
504
|
for (const row of change.after)
|
|
290
505
|
next.set(row.key, row);
|
|
291
506
|
}
|
|
292
|
-
if (next.size
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
507
|
+
if (next.size > 0) {
|
|
508
|
+
sync.begin();
|
|
509
|
+
for (const [key, row] of next) {
|
|
510
|
+
if (row === null) {
|
|
511
|
+
if (rows.has(key))
|
|
512
|
+
sync.write({ type: 'delete', key });
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
|
|
300
516
|
}
|
|
301
|
-
sync.
|
|
517
|
+
sync.commit();
|
|
302
518
|
}
|
|
303
|
-
|
|
519
|
+
recover();
|
|
304
520
|
},
|
|
305
|
-
|
|
521
|
+
recover,
|
|
522
|
+
async query(plan, at) {
|
|
306
523
|
const { live: collection, dispose } = built(rows, plan);
|
|
307
524
|
try {
|
|
308
525
|
await collection.toArrayWhenReady();
|
|
309
|
-
|
|
526
|
+
const entries = [...collection.entries()];
|
|
527
|
+
const held = at();
|
|
528
|
+
const found = await rowsOfPlan(plan, entries, null, structuredClone);
|
|
529
|
+
return { rows: found.rows, at: held };
|
|
310
530
|
}
|
|
311
531
|
finally {
|
|
312
532
|
dispose();
|
|
313
533
|
}
|
|
314
534
|
},
|
|
315
|
-
watch(plan, identity,
|
|
535
|
+
watch(plan, identity, cover, deliverTo) {
|
|
316
536
|
let entry = shared.get(identity);
|
|
317
537
|
if (entry === undefined) {
|
|
318
538
|
const created = {
|
|
319
539
|
plan,
|
|
320
540
|
built: built(rows, plan),
|
|
541
|
+
cover,
|
|
321
542
|
result: undefined,
|
|
543
|
+
rows: new Map(),
|
|
544
|
+
bodies: new Map(),
|
|
322
545
|
listeners: new Set(),
|
|
546
|
+
fresh: new Set(),
|
|
547
|
+
ready: new Set(),
|
|
323
548
|
subscription: null,
|
|
549
|
+
whole: true,
|
|
550
|
+
dirty: new Set(),
|
|
551
|
+
stale: false,
|
|
552
|
+
running: false,
|
|
324
553
|
};
|
|
325
554
|
created.built.live.on('status:change', ({ status }) => {
|
|
326
555
|
if (status === 'cleaned-up' && created.listeners.size === 0)
|
|
@@ -330,29 +559,40 @@ export function createReadSide() {
|
|
|
330
559
|
entry = created;
|
|
331
560
|
}
|
|
332
561
|
const observed = entry;
|
|
333
|
-
observed.listeners.add(
|
|
562
|
+
observed.listeners.add(deliverTo);
|
|
334
563
|
observed.built.live.onFirstReady(() => {
|
|
335
|
-
if (!observed.listeners.has(
|
|
564
|
+
if (!observed.listeners.has(deliverTo))
|
|
336
565
|
return;
|
|
566
|
+
observed.fresh.add(deliverTo);
|
|
337
567
|
if (observed.subscription === null) {
|
|
338
|
-
observed.
|
|
339
|
-
observed.subscription = observed.built.live.subscribeChanges(() => {
|
|
340
|
-
|
|
568
|
+
observed.whole = true;
|
|
569
|
+
observed.subscription = observed.built.live.subscribeChanges((changes) => {
|
|
570
|
+
if (observed.plan.shape.kind === 'groups')
|
|
571
|
+
observed.whole = true;
|
|
572
|
+
else
|
|
573
|
+
for (const { key } of changes)
|
|
574
|
+
observed.dirty.add(key);
|
|
575
|
+
wake(observed);
|
|
341
576
|
}, { includeInitialState: false });
|
|
342
577
|
}
|
|
343
|
-
|
|
578
|
+
wake(observed);
|
|
344
579
|
});
|
|
345
580
|
return () => {
|
|
346
|
-
|
|
581
|
+
observed.fresh.delete(deliverTo);
|
|
582
|
+
observed.ready.delete(deliverTo);
|
|
583
|
+
if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
|
|
347
584
|
return;
|
|
348
585
|
observed.subscription?.unsubscribe();
|
|
349
586
|
observed.subscription = null;
|
|
587
|
+
observed.result = undefined;
|
|
588
|
+
observed.rows.clear();
|
|
589
|
+
observed.bodies.clear();
|
|
590
|
+
observed.dirty.clear();
|
|
591
|
+
observed.whole = true;
|
|
350
592
|
};
|
|
351
593
|
},
|
|
352
|
-
|
|
353
|
-
return rows.toArray
|
|
354
|
-
.filter((row) => row.kind === 'document' && row.type !== null)
|
|
355
|
-
.map(searched);
|
|
594
|
+
room(room) {
|
|
595
|
+
return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
|
|
356
596
|
},
|
|
357
597
|
document(room, path) {
|
|
358
598
|
const row = rows.get(documentKey(room, path));
|
|
@@ -361,6 +601,8 @@ export function createReadSide() {
|
|
|
361
601
|
close() {
|
|
362
602
|
for (const entry of shared.values()) {
|
|
363
603
|
entry.listeners.clear();
|
|
604
|
+
entry.fresh.clear();
|
|
605
|
+
entry.ready.clear();
|
|
364
606
|
entry.subscription?.unsubscribe();
|
|
365
607
|
entry.built.dispose();
|
|
366
608
|
}
|
|
@@ -369,3 +611,32 @@ export function createReadSide() {
|
|
|
369
611
|
},
|
|
370
612
|
};
|
|
371
613
|
}
|
|
614
|
+
function changedFrom(previous, next) {
|
|
615
|
+
const before = previous.result;
|
|
616
|
+
const after = next.result;
|
|
617
|
+
if ('pending' in before || 'pending' in after)
|
|
618
|
+
return deepEquals(before, after) ? null : next;
|
|
619
|
+
const earlier = 'groups' in before ? before.groups : before.documents;
|
|
620
|
+
const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
|
|
621
|
+
const later = 'groups' in after ? after.groups : after.documents;
|
|
622
|
+
const changed = [];
|
|
623
|
+
next.keys.forEach((key, index) => {
|
|
624
|
+
if (held.get(key) !== later[index])
|
|
625
|
+
changed.push([key, later[index]]);
|
|
626
|
+
});
|
|
627
|
+
const present = new Set(next.keys);
|
|
628
|
+
const removed = previous.keys.filter((key) => !present.has(key));
|
|
629
|
+
const moved = reordered(previous.keys, next.keys, present);
|
|
630
|
+
const covered = !deepEquals(before.coverage, after.coverage);
|
|
631
|
+
if (changed.length === 0 && removed.length === 0 && !moved && !covered)
|
|
632
|
+
return null;
|
|
633
|
+
return {
|
|
634
|
+
changed,
|
|
635
|
+
removed,
|
|
636
|
+
order: moved ? next.keys : null,
|
|
637
|
+
...(covered ? { coverage: after.coverage } : {}),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function rowAt(rows, index) {
|
|
641
|
+
return 'groups' in rows ? rows.groups[index] : rows.documents[index];
|
|
642
|
+
}
|