@agentshouse/mdruntime 0.1.0-alpha.9 → 0.1.0-c30ed2b8e04d5

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.
Files changed (130) hide show
  1. package/AGENTS.md +217 -0
  2. package/README.md +43 -0
  3. package/dist/adapter.d.ts +184 -34
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +1 -10
  6. package/dist/attached-model.d.ts +11 -0
  7. package/dist/attached-model.d.ts.map +1 -0
  8. package/dist/attached-model.js +34 -0
  9. package/dist/attachment.d.ts +3 -0
  10. package/dist/attachment.d.ts.map +1 -0
  11. package/dist/attachment.js +8 -0
  12. package/dist/bootstrap.d.ts +7 -9
  13. package/dist/bootstrap.d.ts.map +1 -1
  14. package/dist/bootstrap.js +29 -28
  15. package/dist/change-set.d.ts +33 -0
  16. package/dist/change-set.d.ts.map +1 -0
  17. package/dist/change-set.js +83 -0
  18. package/dist/change.d.ts +9 -0
  19. package/dist/change.d.ts.map +1 -1
  20. package/dist/change.js +6 -5
  21. package/dist/client.d.ts +38 -32
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +114 -183
  24. package/dist/collection.d.ts +13 -16
  25. package/dist/collection.d.ts.map +1 -1
  26. package/dist/collection.js +45 -88
  27. package/dist/connection.d.ts +47 -44
  28. package/dist/connection.d.ts.map +1 -1
  29. package/dist/connection.js +1542 -1158
  30. package/dist/conversation.d.ts +44 -22
  31. package/dist/conversation.d.ts.map +1 -1
  32. package/dist/conversation.js +540 -328
  33. package/dist/durability.js +6 -6
  34. package/dist/file.d.ts +2 -2
  35. package/dist/file.d.ts.map +1 -1
  36. package/dist/file.js +48 -71
  37. package/dist/host.d.ts +12 -2
  38. package/dist/host.d.ts.map +1 -1
  39. package/dist/host.js +97 -119
  40. package/dist/identity.d.ts +1 -0
  41. package/dist/identity.d.ts.map +1 -1
  42. package/dist/identity.js +10 -9
  43. package/dist/index.d.ts +12 -11
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -3
  46. package/dist/log.js +5 -6
  47. package/dist/memory.d.ts +15 -7
  48. package/dist/memory.d.ts.map +1 -1
  49. package/dist/memory.js +255 -220
  50. package/dist/model.d.ts +10 -29
  51. package/dist/model.d.ts.map +1 -1
  52. package/dist/model.js +18 -21
  53. package/dist/observation.d.ts +27 -0
  54. package/dist/observation.d.ts.map +1 -0
  55. package/dist/observation.js +95 -0
  56. package/dist/outcome.d.ts +10 -29
  57. package/dist/outcome.d.ts.map +1 -1
  58. package/dist/outcome.js +1 -23
  59. package/dist/overlay.d.ts +9 -2
  60. package/dist/overlay.d.ts.map +1 -1
  61. package/dist/overlay.js +187 -43
  62. package/dist/paths.d.ts +0 -1
  63. package/dist/paths.d.ts.map +1 -1
  64. package/dist/paths.js +12 -19
  65. package/dist/query.d.ts +215 -10
  66. package/dist/query.d.ts.map +1 -1
  67. package/dist/query.js +421 -15
  68. package/dist/reach.d.ts +40 -0
  69. package/dist/reach.d.ts.map +1 -0
  70. package/dist/reach.js +277 -0
  71. package/dist/read-side.d.ts +18 -0
  72. package/dist/read-side.d.ts.map +1 -0
  73. package/dist/read-side.js +377 -0
  74. package/dist/refresh.d.ts +5 -5
  75. package/dist/refresh.d.ts.map +1 -1
  76. package/dist/refresh.js +49 -30
  77. package/dist/replica.d.ts +25 -5
  78. package/dist/replica.d.ts.map +1 -1
  79. package/dist/replica.js +227 -38
  80. package/dist/room-draft.d.ts +127 -0
  81. package/dist/room-draft.d.ts.map +1 -0
  82. package/dist/room-draft.js +329 -0
  83. package/dist/rows.d.ts +41 -0
  84. package/dist/rows.d.ts.map +1 -0
  85. package/dist/rows.js +100 -0
  86. package/dist/save.d.ts +6 -4
  87. package/dist/save.d.ts.map +1 -1
  88. package/dist/save.js +262 -350
  89. package/dist/search.d.ts +61 -0
  90. package/dist/search.d.ts.map +1 -0
  91. package/dist/search.js +125 -0
  92. package/dist/status.d.ts +4 -6
  93. package/dist/status.d.ts.map +1 -1
  94. package/dist/status.js +9 -13
  95. package/dist/store.d.ts +59 -32
  96. package/dist/store.d.ts.map +1 -1
  97. package/dist/store.js +338 -134
  98. package/dist/transfer.d.ts +5 -0
  99. package/dist/transfer.d.ts.map +1 -1
  100. package/dist/transfer.js +1 -5
  101. package/dist/wire.d.ts +51 -16
  102. package/dist/wire.d.ts.map +1 -1
  103. package/dist/wire.js +77 -108
  104. package/package.json +6 -12
  105. package/dist/adapter.js.map +0 -1
  106. package/dist/bootstrap.js.map +0 -1
  107. package/dist/change.js.map +0 -1
  108. package/dist/client.js.map +0 -1
  109. package/dist/collection.js.map +0 -1
  110. package/dist/connection.js.map +0 -1
  111. package/dist/conversation.js.map +0 -1
  112. package/dist/durability.js.map +0 -1
  113. package/dist/file.js.map +0 -1
  114. package/dist/host.js.map +0 -1
  115. package/dist/identity.js.map +0 -1
  116. package/dist/index.js.map +0 -1
  117. package/dist/log.js.map +0 -1
  118. package/dist/memory.js.map +0 -1
  119. package/dist/model.js.map +0 -1
  120. package/dist/outcome.js.map +0 -1
  121. package/dist/overlay.js.map +0 -1
  122. package/dist/paths.js.map +0 -1
  123. package/dist/query.js.map +0 -1
  124. package/dist/refresh.js.map +0 -1
  125. package/dist/replica.js.map +0 -1
  126. package/dist/save.js.map +0 -1
  127. package/dist/status.js.map +0 -1
  128. package/dist/store.js.map +0 -1
  129. package/dist/transfer.js.map +0 -1
  130. package/dist/wire.js.map +0 -1
package/dist/reach.js ADDED
@@ -0,0 +1,277 @@
1
+ import { unheldView } from './conversation.js';
2
+ import { modelTypes, NO_MODEL } from './model.js';
3
+ import { applied, observing } from './observation.js';
4
+ import { beneath } from './paths.js';
5
+ import { reconciled } from './read-side.js';
6
+ import { moveTarget, readParts } from './room-draft.js';
7
+ import { documentKey } from './rows.js';
8
+ const UNHELD_TYPES = { collection: modelTypes(NO_MODEL), typedSelection: null };
9
+ const UNDECLARED = { kind: 'undeclared' };
10
+ function undeclared() {
11
+ return Promise.resolve(UNDECLARED);
12
+ }
13
+ const NO_CONVERSATION = {
14
+ list: () => Promise.resolve([]),
15
+ read: (conversation) => Promise.resolve(unheldView(conversation)),
16
+ create: undeclared,
17
+ send: undeclared,
18
+ answer: undeclared,
19
+ interrupt: undeclared,
20
+ kill: undeclared,
21
+ cancel: undeclared,
22
+ delete: undeclared,
23
+ };
24
+ const NO_CHAT_DRAFT = {
25
+ write: () => Promise.resolve(),
26
+ discard: () => Promise.resolve(),
27
+ };
28
+ export function holds(reach, room) {
29
+ return (reach === null ||
30
+ reach.rooms.includes(room) ||
31
+ reach.documents.some((document) => document.room === room));
32
+ }
33
+ function wholly(reach, room) {
34
+ return reach === null || reach.rooms.includes(room);
35
+ }
36
+ export function governs(reach, room) {
37
+ if (wholly(reach, room.locator)) {
38
+ return { collection: true, selection: room.typedSelection !== null };
39
+ }
40
+ const selected = (path) => room.typedSelection?.roots.some((root) => beneath(root, path)) ?? false;
41
+ const paths = reach.documents
42
+ .filter((document) => document.room === room.locator)
43
+ .map(({ path }) => path);
44
+ return { collection: paths.some((path) => !selected(path)), selection: paths.some(selected) };
45
+ }
46
+ function covers(reach, room, path) {
47
+ return (reach === null ||
48
+ reach.rooms.includes(room) ||
49
+ reach.documents.some((document) => document.room === room && document.path === path));
50
+ }
51
+ function conversing(reach) {
52
+ return reach === null || reach.conversations;
53
+ }
54
+ export function planReach(reach) {
55
+ return reach === null
56
+ ? null
57
+ : {
58
+ rooms: [...new Set(reach.rooms)].sort(),
59
+ documents: [
60
+ ...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
61
+ ].sort(),
62
+ };
63
+ }
64
+ function roomsWithin(reach, rooms) {
65
+ if (reach === null)
66
+ return rooms;
67
+ return rooms
68
+ .filter((room) => holds(reach, room.locator))
69
+ .map((room) => {
70
+ if (wholly(reach, room.locator))
71
+ return room;
72
+ const governed = governs(reach, room);
73
+ return {
74
+ ...room,
75
+ model: governed.collection ? room.model : modelTypes(NO_MODEL),
76
+ typedSelection: governed.selection ? room.typedSelection : null,
77
+ roomVersion: null,
78
+ attachments: [],
79
+ };
80
+ });
81
+ }
82
+ function typesWithin(reach, room, types) {
83
+ if (room === undefined)
84
+ return structuredClone(UNHELD_TYPES);
85
+ const governed = governs(reach, room);
86
+ return {
87
+ collection: governed.collection ? types.collection : modelTypes(NO_MODEL),
88
+ typedSelection: governed.selection ? types.typedSelection : null,
89
+ };
90
+ }
91
+ function moves(reach, room, move) {
92
+ return move === undefined || covers(reach, room, moveTarget(move));
93
+ }
94
+ function draftWithin(reach, draft) {
95
+ return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
96
+ }
97
+ function draftsWithin(reach, drafts) {
98
+ return drafts.filter((draft) => draftWithin(reach, draft));
99
+ }
100
+ function shownDraft(reach, draft) {
101
+ return draft !== null && draftWithin(reach, draft) ? draft : null;
102
+ }
103
+ function writeWithin(reach, write) {
104
+ return write.outcome === 'written'
105
+ ? { outcome: 'written', roomDraft: shownDraft(reach, write.roomDraft) }
106
+ : { outcome: 'stale', current: shownDraft(reach, write.current) };
107
+ }
108
+ function savedWithin(reach, room, result) {
109
+ if (reach === null || result.outcome === 'saved')
110
+ return result;
111
+ const { file, location, referrers, ...refusal } = result;
112
+ const hidden = file !== undefined && !covers(reach, room, file);
113
+ return {
114
+ ...refusal,
115
+ ...(file === undefined || hidden ? {} : { file }),
116
+ ...(location === undefined || hidden ? {} : { location }),
117
+ ...(referrers === undefined
118
+ ? {}
119
+ : { referrers: referrers.filter((path) => covers(reach, room, path)) }),
120
+ };
121
+ }
122
+ function statusWithin(reach, status) {
123
+ return {
124
+ ...status,
125
+ attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
126
+ };
127
+ }
128
+ function queueWithin(reach, room, entries) {
129
+ return entries.filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
130
+ (change.kind !== 'move' || covers(reach, room, change.to))));
131
+ }
132
+ function unreached(room) {
133
+ return new Error(`No Local Replica for ${room}`);
134
+ }
135
+ export function narrowed(runtime, given) {
136
+ const { md, watch } = runtime;
137
+ let reach = given;
138
+ const open = new Set();
139
+ function following(begin) {
140
+ let stop = begin();
141
+ const restart = () => {
142
+ stop();
143
+ stop = begin();
144
+ };
145
+ open.add(restart);
146
+ return () => {
147
+ open.delete(restart);
148
+ stop();
149
+ };
150
+ }
151
+ const within = (topic, input, deliver) => {
152
+ if (topic === 'rooms') {
153
+ return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
154
+ }
155
+ if (topic === 'sync-status') {
156
+ return following(() => watch(topic, input, (status) => deliver(statusWithin(reach, status))));
157
+ }
158
+ if (topic === 'room-drafts') {
159
+ return following(() => watch(topic, input, (drafts) => deliver(draftsWithin(reach, drafts))));
160
+ }
161
+ if (topic === 'queue') {
162
+ const room = input[0];
163
+ return following(() => {
164
+ if (!holds(reach, room)) {
165
+ deliver([]);
166
+ return () => { };
167
+ }
168
+ return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
169
+ });
170
+ }
171
+ if (topic === 'query') {
172
+ const plan = runtime.plan(input[0], reach);
173
+ let held = null;
174
+ return following(() => {
175
+ let first = true;
176
+ return runtime.watchPlan({ ...plan, reach: planReach(reach) }, (message) => {
177
+ if (!first) {
178
+ held = applied(held, message);
179
+ deliver(message);
180
+ return;
181
+ }
182
+ first = false;
183
+ if (held === null) {
184
+ held = message;
185
+ deliver(message);
186
+ return;
187
+ }
188
+ const next = reconciled(held, message);
189
+ if (next === null)
190
+ return;
191
+ held = next.keyed;
192
+ deliver(next.change);
193
+ });
194
+ });
195
+ }
196
+ if (topic === 'conversation') {
197
+ return following(() => {
198
+ if (conversing(reach))
199
+ return watch(topic, input, deliver);
200
+ deliver(unheldView(input[0]));
201
+ return () => { };
202
+ });
203
+ }
204
+ return watch(topic, input, deliver);
205
+ };
206
+ const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
207
+ const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
208
+ const view = {
209
+ ...md,
210
+ rooms: () => roomsWithin(reach, md.rooms()),
211
+ query: ((input) => runtime.query(runtime.plan(input, reach))),
212
+ search: (input) => runtime.search(input, reach),
213
+ types(room) {
214
+ if (wholly(reach, room))
215
+ return md.types(room);
216
+ if (!holds(reach, room))
217
+ return structuredClone(UNHELD_TYPES);
218
+ const listed = md.rooms().find(({ locator }) => locator === room);
219
+ return typesWithin(reach, listed, md.types(room));
220
+ },
221
+ async save(room, roomDrafts, writtenBy) {
222
+ if (!holds(reach, room))
223
+ throw unreached(room);
224
+ const outside = roomDrafts.find(({ path }) => !covers(reach, room, path) ||
225
+ !moves(reach, room, md.roomDraft.read(room, path)?.parts.move));
226
+ if (outside !== undefined)
227
+ return { outcome: 'refused', rule: 'stale', file: outside.path };
228
+ return savedWithin(reach, room, await md.save(room, roomDrafts, writtenBy));
229
+ },
230
+ history: (room, path, query) => covers(reach, room, path)
231
+ ? md.history(room, path, query)
232
+ : Promise.resolve({ kind: 'room-not-readable', locator: room }),
233
+ reached: (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
234
+ md.reached(room, roomVersion, path),
235
+ log: () => (reach === null ? md.log() : ''),
236
+ roomDraft: {
237
+ list: () => md.roomDraft.list().then((drafts) => draftsWithin(reach, drafts)),
238
+ read: (room, path) => covers(reach, room, path) ? shownDraft(reach, md.roomDraft.read(room, path)) : null,
239
+ async write(room, path, revision, parts) {
240
+ if (!covers(reach, room, path) || !moves(reach, room, readParts(parts).move)) {
241
+ throw unreached(room);
242
+ }
243
+ return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts));
244
+ },
245
+ async discard(room, path, revision) {
246
+ if (!covers(reach, room, path))
247
+ return { outcome: 'stale', current: null };
248
+ return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
249
+ },
250
+ },
251
+ conversation: {
252
+ list: () => conversation().list(),
253
+ read: (id, options) => conversation().read(id, options),
254
+ create: (input) => conversation().create(input),
255
+ send: (id) => conversation().send(id),
256
+ answer: (id, answer) => conversation().answer(id, answer),
257
+ interrupt: (id) => conversation().interrupt(id),
258
+ kill: (id) => conversation().kill(id),
259
+ cancel: (id, turn) => conversation().cancel(id, turn),
260
+ delete: (id) => conversation().delete(id),
261
+ },
262
+ chatDraft: {
263
+ write: (id, draft) => chatDraft().write(id, draft),
264
+ discard: (id) => chatDraft().discard(id),
265
+ },
266
+ observe: observing(within),
267
+ };
268
+ return {
269
+ md: view,
270
+ watch: within,
271
+ narrow(next) {
272
+ reach = next;
273
+ for (const restart of [...open])
274
+ restart();
275
+ },
276
+ };
277
+ }
@@ -0,0 +1,18 @@
1
+ import type { ChangeSet } from './change-set.js';
2
+ import { type KeyedResult, type QueryChange } from './observation.js';
3
+ import type { Plan, QueryResult } from './query.js';
4
+ import type { SearchedDocument } from './search.js';
5
+ export type ReadSide = {
6
+ apply(set: ChangeSet): void;
7
+ query(plan: Plan): Promise<QueryResult>;
8
+ watch(plan: Plan, identity: string, deliver: (message: KeyedResult | QueryChange) => void): () => void;
9
+ documents(): readonly SearchedDocument[];
10
+ document(room: string, path: string): SearchedDocument | null;
11
+ close(): void;
12
+ };
13
+ export declare function reconciled(previous: KeyedResult, next: KeyedResult): {
14
+ keyed: KeyedResult;
15
+ change: QueryChange;
16
+ } | null;
17
+ export declare function createReadSide(): ReadSide;
18
+ //# sourceMappingURL=read-side.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-side.d.ts","sourceRoot":"","sources":["../src/read-side.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAC3F,OAAO,KAAK,EAOV,IAAI,EAIJ,WAAW,EAIZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAqDpD,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACxC,KAAK,CACH,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,KAAK,IAAI,GACpD,MAAM,IAAI,CAAC;IACd,SAAS,IAAI,SAAS,gBAAgB,EAAE,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAC9D,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AA8RF,wBAAgB,UAAU,CACxB,QAAQ,EAAE,WAAW,EACrB,IAAI,EAAE,WAAW,GAChB;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG,IAAI,CA4BpD;AAED,wBAAgB,cAAc,IAAI,QAAQ,CA4HzC"}
@@ -0,0 +1,377 @@
1
+ import { and, BasicIndex, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, gt, gte, inArray, isNull, isUndefined, lt, lte, not, or, toArray, } from '@tanstack/db';
2
+ import { rowsOf } from './observation.js';
3
+ import { documentKey } from './rows.js';
4
+ function every(expressions) {
5
+ const [first, second, ...rest] = expressions;
6
+ return second === undefined ? first : and(first, second, ...rest);
7
+ }
8
+ function some(expressions) {
9
+ const [first, second, ...rest] = expressions;
10
+ return second === undefined ? first : or(first, second, ...rest);
11
+ }
12
+ function equal(field, value) {
13
+ return or(eq(field, value), inArray(value, field));
14
+ }
15
+ function tested(test, field, size, written) {
16
+ if (test.op === 'eq')
17
+ return test.value === null ? isNull(field) : equal(field, test.value);
18
+ if (test.op === 'ne') {
19
+ return test.value === null
20
+ ? not(isNull(field))
21
+ : or(isUndefined(field), isNull(field), not(equal(field, test.value)));
22
+ }
23
+ if (test.op === 'in' || test.op === 'nin') {
24
+ const matches = test.values.length === 0
25
+ ? inArray(field, [])
26
+ : some(test.values.map((value) => (value === null ? isNull(field) : equal(field, value))));
27
+ if (test.op === 'in')
28
+ return matches;
29
+ return some([
30
+ isUndefined(field),
31
+ ...(test.values.includes(null) ? [] : [isNull(field)]),
32
+ not(matches),
33
+ ]);
34
+ }
35
+ if (test.op === 'range') {
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);
45
+ }
46
+ function conditioned(condition, row) {
47
+ if ('every' in condition)
48
+ return every(condition.every.map((item) => conditioned(item, row)));
49
+ if ('some' in condition)
50
+ return some(condition.some.map((item) => conditioned(item, row)));
51
+ return tested(condition.test, row.values[condition.field], row.sizes[condition.field], row.fields[condition.field]);
52
+ }
53
+ function reached(reach, room, document) {
54
+ return reach === null
55
+ ? []
56
+ : [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))];
57
+ }
58
+ function scoped(scope, row, rooms, reach) {
59
+ return every([
60
+ eq(row.kind, 'document'),
61
+ ...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
62
+ ...reached(reach, row.room, row.key),
63
+ ...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
64
+ ...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
65
+ ...(scope.where === null ? [] : [conditioned(scope.where, row)]),
66
+ ]);
67
+ }
68
+ function keyed(key, row) {
69
+ return key.of === 'field' ? row.values[key.field] : row[key.of];
70
+ }
71
+ function live(query) {
72
+ return createLiveQueryCollection({
73
+ query: query,
74
+ startSync: true,
75
+ });
76
+ }
77
+ function related(q, rows, scope, field, kept, reach) {
78
+ const matched = kept === 'source' ? 'target' : 'source';
79
+ const from = q.from({ r: rows });
80
+ return from
81
+ .where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [eq(r.field, field)])]))
82
+ .innerJoin({ m: rows }, ({ r, m }) => eq(r[matched], m.key))
83
+ .where(({ m }) => scoped(scope, m, null, reach))
84
+ .groupBy(({ r }) => r[kept])
85
+ .select(({ r }) => ({ key: r[kept] }));
86
+ }
87
+ function filtered(q, rows, plan) {
88
+ const from = q.from({ d: rows });
89
+ let query = from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach));
90
+ if (plan.hop !== null) {
91
+ const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
92
+ query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
93
+ }
94
+ if (plan.backlinks !== null) {
95
+ const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach);
96
+ query = query.innerJoin({ k: backlinks }, ({ d, k }) => eq(d.key, k.key));
97
+ }
98
+ return query;
99
+ }
100
+ function named(prefix, names, source) {
101
+ return Object.fromEntries(names.map((name, index) => [`${prefix}${index}`, source[name]]));
102
+ }
103
+ function documents(q, rows, plan, shape) {
104
+ let query = filtered(q, rows, plan);
105
+ if (shape.sections !== null || shape.preamble || shape.markdown) {
106
+ query = query.leftJoin({ b: rows }, ({ d, b }) => eq(d.body, b.key));
107
+ }
108
+ for (const { key, options } of shape.orderBy) {
109
+ query = query.orderBy(({ d }) => keyed(key, d), options);
110
+ }
111
+ if (shape.orderBy.length === 0 && plan.limit !== null) {
112
+ query = query.orderBy(({ d }) => d.key, { stringSort: 'lexical' });
113
+ }
114
+ if (plan.limit !== null)
115
+ query = query.limit(plan.limit);
116
+ return query.select(({ d, b }) => ({
117
+ room: d.room,
118
+ path: d.path,
119
+ type: d.type,
120
+ ...(shape.fields === 'all' ? { fields: d.fields } : {}),
121
+ ...(Array.isArray(shape.fields) ? named('field', shape.fields, d.fields) : {}),
122
+ ...(shape.sections === 'all' ? { sections: b.sections } : {}),
123
+ ...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
124
+ ...(shape.preamble ? { preamble: b.preamble } : {}),
125
+ ...(shape.markdown ? { markdown: b.markdown } : {}),
126
+ ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
127
+ ...(shape.change ? { change: d.change } : {}),
128
+ }));
129
+ }
130
+ function backlinked(q, rows, document, reach) {
131
+ const from = q.from({ r: rows });
132
+ return from
133
+ .where(({ r }) => every([eq(r.target, document.key), ...reached(reach, r.room, r.source)]))
134
+ .orderBy(({ r }) => r.path, { stringSort: 'lexical' })
135
+ .orderBy(({ r }) => r.field, { stringSort: 'lexical' })
136
+ .select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
137
+ }
138
+ function groups(q, stage, shape, limit) {
139
+ const keys = shape.groupBy.map((_, index) => `key${index}`);
140
+ const from = q.from({ m: stage });
141
+ let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
142
+ for (const { by, direction } of shape.orderBy) {
143
+ query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
144
+ }
145
+ if (limit !== null && shape.orderBy.length > 0)
146
+ query = query.limit(limit);
147
+ return query.select(({ m }) => ({
148
+ ...Object.fromEntries(keys.map((key) => [key, m[key]])),
149
+ count: count(m.key),
150
+ }));
151
+ }
152
+ function built(rows, plan) {
153
+ const shape = plan.shape;
154
+ if (shape.kind === 'documents') {
155
+ const collection = live((q) => documents(q, rows, plan, shape));
156
+ return { live: collection, dispose: () => void collection.cleanup() };
157
+ }
158
+ const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
159
+ key: d.key,
160
+ ...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
161
+ })));
162
+ const collection = live((q) => groups(q, stage, shape, plan.limit));
163
+ return {
164
+ live: collection,
165
+ dispose: () => {
166
+ void collection.cleanup();
167
+ void stage.cleanup();
168
+ },
169
+ };
170
+ }
171
+ function present(entries) {
172
+ return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
173
+ }
174
+ function part(row, prefix, names, whole) {
175
+ if (names === 'all')
176
+ return whole;
177
+ return present(names.map((name, index) => [name, row[`${prefix}${index}`]]));
178
+ }
179
+ function documentOf(row, shape) {
180
+ if (row.type === null)
181
+ return { room: row.room, path: row.path };
182
+ return {
183
+ room: row.room,
184
+ path: row.path,
185
+ type: row.type,
186
+ ...(shape.fields === null
187
+ ? {}
188
+ : { fields: part(row, 'field', shape.fields, row.fields) }),
189
+ ...(shape.sections === null
190
+ ? {}
191
+ : {
192
+ sections: part(row, 'section', shape.sections, row.sections),
193
+ }),
194
+ ...(shape.preamble ? { preamble: row.preamble } : {}),
195
+ ...(shape.markdown ? { markdown: row.markdown } : {}),
196
+ ...(shape.backlinks
197
+ ? {
198
+ backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
199
+ }
200
+ : {}),
201
+ ...(shape.change ? { change: row.change } : {}),
202
+ };
203
+ }
204
+ function keyedOf(plan, entries) {
205
+ const shape = plan.shape;
206
+ if (shape.kind === 'documents') {
207
+ return {
208
+ result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
209
+ keys: entries.map(([key]) => key),
210
+ };
211
+ }
212
+ const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
213
+ return {
214
+ result: structuredClone({
215
+ groups: bounded.map(([, row]) => ({
216
+ key: shape.groupBy.map((_, index) => row[`key${index}`]),
217
+ count: row.count,
218
+ })),
219
+ }),
220
+ keys: bounded.map(([key]) => key),
221
+ };
222
+ }
223
+ export function reconciled(previous, next) {
224
+ const before = rowsOf(previous.result);
225
+ const held = new Map(previous.keys.map((key, index) => [key, before[index]]));
226
+ const changed = [];
227
+ const rows = rowsOf(next.result).map((row, index) => {
228
+ const key = next.keys[index];
229
+ const kept = held.get(key);
230
+ if (kept !== undefined && deepEquals(kept, row))
231
+ return kept;
232
+ changed.push([key, row]);
233
+ return row;
234
+ });
235
+ const present = new Set(next.keys);
236
+ const survivors = previous.keys.filter((key) => present.has(key));
237
+ const removed = previous.keys.filter((key) => !present.has(key));
238
+ const moved = survivors.length !== next.keys.length ||
239
+ survivors.some((key, index) => key !== next.keys[index]);
240
+ if (changed.length === 0 && removed.length === 0 && !moved)
241
+ return null;
242
+ return {
243
+ keyed: {
244
+ result: 'groups' in next.result
245
+ ? { groups: rows }
246
+ : { documents: rows },
247
+ keys: next.keys,
248
+ },
249
+ change: { changed, removed, order: moved ? next.keys : null },
250
+ };
251
+ }
252
+ export function createReadSide() {
253
+ configureIndexDevMode({ enabled: false });
254
+ let sync;
255
+ const rows = createCollection({
256
+ getKey: (row) => row.key,
257
+ gcTime: 0,
258
+ startSync: true,
259
+ autoIndex: 'eager',
260
+ defaultIndexType: BasicIndex,
261
+ sync: {
262
+ rowUpdateMode: 'full',
263
+ sync: (params) => {
264
+ sync = params;
265
+ params.markReady();
266
+ },
267
+ },
268
+ });
269
+ const shared = new Map();
270
+ function searched(row) {
271
+ const body = rows.get(row.body);
272
+ return {
273
+ room: row.room,
274
+ path: row.path,
275
+ type: row.type,
276
+ fields: row.fields,
277
+ preamble: body.preamble,
278
+ sections: body.sections,
279
+ };
280
+ }
281
+ function publish(entry) {
282
+ const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
283
+ if (next === null)
284
+ return;
285
+ entry.result = next.keyed;
286
+ for (const listener of entry.listeners)
287
+ listener(next.change);
288
+ }
289
+ return {
290
+ apply(set) {
291
+ const next = new Map();
292
+ for (const change of set.documents) {
293
+ for (const row of change.before)
294
+ next.set(row.key, null);
295
+ for (const row of change.after)
296
+ next.set(row.key, row);
297
+ }
298
+ if (next.size === 0)
299
+ return;
300
+ sync.begin();
301
+ for (const [key, row] of next) {
302
+ if (row === null) {
303
+ if (rows.has(key))
304
+ sync.write({ type: 'delete', key });
305
+ continue;
306
+ }
307
+ sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
308
+ }
309
+ sync.commit();
310
+ },
311
+ async query(plan) {
312
+ const { live: collection, dispose } = built(rows, plan);
313
+ try {
314
+ await collection.toArrayWhenReady();
315
+ return keyedOf(plan, [...collection.entries()]).result;
316
+ }
317
+ finally {
318
+ dispose();
319
+ }
320
+ },
321
+ watch(plan, identity, deliver) {
322
+ let entry = shared.get(identity);
323
+ if (entry === undefined) {
324
+ const created = {
325
+ plan,
326
+ built: built(rows, plan),
327
+ result: undefined,
328
+ listeners: new Set(),
329
+ subscription: null,
330
+ };
331
+ created.built.live.on('status:change', ({ status }) => {
332
+ if (status === 'cleaned-up' && created.listeners.size === 0)
333
+ shared.delete(identity);
334
+ });
335
+ shared.set(identity, created);
336
+ entry = created;
337
+ }
338
+ const observed = entry;
339
+ observed.listeners.add(deliver);
340
+ observed.built.live.onFirstReady(() => {
341
+ if (!observed.listeners.has(deliver))
342
+ return;
343
+ if (observed.subscription === null) {
344
+ observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
345
+ observed.subscription = observed.built.live.subscribeChanges(() => {
346
+ publish(observed);
347
+ }, { includeInitialState: false });
348
+ }
349
+ deliver(observed.result);
350
+ });
351
+ return () => {
352
+ if (!observed.listeners.delete(deliver) || observed.listeners.size > 0)
353
+ return;
354
+ observed.subscription?.unsubscribe();
355
+ observed.subscription = null;
356
+ };
357
+ },
358
+ documents() {
359
+ return rows.toArray
360
+ .filter((row) => row.kind === 'document' && row.type !== null)
361
+ .map(searched);
362
+ },
363
+ document(room, path) {
364
+ const row = rows.get(documentKey(room, path));
365
+ return row === undefined || row.type === null ? null : searched(row);
366
+ },
367
+ close() {
368
+ for (const entry of shared.values()) {
369
+ entry.listeners.clear();
370
+ entry.subscription?.unsubscribe();
371
+ entry.built.dispose();
372
+ }
373
+ shared.clear();
374
+ void rows.cleanup();
375
+ },
376
+ };
377
+ }
package/dist/refresh.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { AttachedModelBinding } from './model.js';
2
- import type { Adapter, AdapterFailure, Authorization } from './adapter.js';
1
+ import type { Adapter, AdapterFailure, Authorization, RetryableRefusal } from './adapter.js';
2
+ import type { Remodel } from './bootstrap.js';
3
3
  import { type ConfirmedLayer } from './replica.js';
4
- import { type Unconfirmed } from './transfer.js';
4
+ import { type Refused, type Unconfirmed } from './transfer.js';
5
5
  export type RefreshOutcome = {
6
6
  kind: 'still-current';
7
7
  } | {
@@ -9,6 +9,6 @@ export type RefreshOutcome = {
9
9
  confirmed: ConfirmedLayer;
10
10
  } | {
11
11
  kind: 'beyond-horizon';
12
- } | Unconfirmed | AdapterFailure;
13
- export declare function refreshRoom(adapter: Adapter, authorization: Authorization, confirmed: ConfirmedLayer, binding: AttachedModelBinding | null): Promise<RefreshOutcome>;
12
+ } | RetryableRefusal | Unconfirmed | Refused | AdapterFailure;
13
+ export declare function refreshRoom(adapter: Adapter, authorization: Authorization, confirmed: ConfirmedLayer, remodel: Remodel, continuing: () => boolean): Promise<RefreshOutcome>;
14
14
  //# sourceMappingURL=refresh.d.ts.map