@agentshouse/mdruntime 0.1.0-c97456d63baba → 0.1.0-c9b1e59e5baeb

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 (133) hide show
  1. package/AGENTS.md +221 -221
  2. package/README.md +8 -8
  3. package/dist/adapter.d.ts +35 -6
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/bootstrap.d.ts +2 -1
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +5 -1
  8. package/dist/change-set.d.ts +5 -2
  9. package/dist/change-set.d.ts.map +1 -1
  10. package/dist/change-set.js +8 -2
  11. package/dist/chat/earlier.d.ts +10 -0
  12. package/dist/chat/earlier.d.ts.map +1 -0
  13. package/dist/chat/earlier.js +54 -0
  14. package/dist/chat/kit.js +1 -1
  15. package/dist/chat/list.d.ts +2 -1
  16. package/dist/chat/list.d.ts.map +1 -1
  17. package/dist/chat/list.js +40 -4
  18. package/dist/chat/markdown.d.ts +1 -0
  19. package/dist/chat/markdown.d.ts.map +1 -1
  20. package/dist/chat/markdown.js +78 -20
  21. package/dist/chat/message.d.ts.map +1 -1
  22. package/dist/chat/message.js +9 -2
  23. package/dist/chat/messages.d.ts.map +1 -1
  24. package/dist/chat/messages.js +36 -16
  25. package/dist/chat/overlay.d.ts +1 -0
  26. package/dist/chat/overlay.d.ts.map +1 -1
  27. package/dist/chat/overlay.js +10 -0
  28. package/dist/chat/pieces.d.ts +62 -0
  29. package/dist/chat/pieces.d.ts.map +1 -0
  30. package/dist/chat/pieces.js +1110 -0
  31. package/dist/chat/provider.d.ts +1 -0
  32. package/dist/chat/provider.d.ts.map +1 -1
  33. package/dist/chat/provider.js +22 -14
  34. package/dist/chat/question.d.ts +1 -1
  35. package/dist/chat/question.d.ts.map +1 -1
  36. package/dist/chat/question.js +7 -18
  37. package/dist/chat/ready.d.ts +1 -1
  38. package/dist/chat/ready.d.ts.map +1 -1
  39. package/dist/chat/ready.js +1 -1
  40. package/dist/chat/steady.d.ts +3 -0
  41. package/dist/chat/steady.d.ts.map +1 -0
  42. package/dist/chat/steady.js +26 -0
  43. package/dist/chat/style.js +1 -1
  44. package/dist/chat.d.ts +2 -2
  45. package/dist/chat.d.ts.map +1 -1
  46. package/dist/chat.js +2 -2
  47. package/dist/client.d.ts +6 -2
  48. package/dist/client.d.ts.map +1 -1
  49. package/dist/client.js +16 -5
  50. package/dist/collection.d.ts +4 -3
  51. package/dist/collection.d.ts.map +1 -1
  52. package/dist/collection.js +128 -63
  53. package/dist/connection.d.ts +13 -5
  54. package/dist/connection.d.ts.map +1 -1
  55. package/dist/connection.js +506 -228
  56. package/dist/conversation.d.ts +11 -0
  57. package/dist/conversation.d.ts.map +1 -1
  58. package/dist/conversation.js +195 -41
  59. package/dist/entry.d.ts.map +1 -1
  60. package/dist/entry.js +3 -2
  61. package/dist/host.d.ts +9 -7
  62. package/dist/host.d.ts.map +1 -1
  63. package/dist/host.js +58 -9
  64. package/dist/index.d.ts +6 -5
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +1 -0
  67. package/dist/memory.d.ts +7 -3
  68. package/dist/memory.d.ts.map +1 -1
  69. package/dist/memory.js +24 -11
  70. package/dist/observation.d.ts +2 -0
  71. package/dist/observation.d.ts.map +1 -1
  72. package/dist/observation.js +9 -3
  73. package/dist/outcome.d.ts +8 -2
  74. package/dist/outcome.d.ts.map +1 -1
  75. package/dist/outcome.js +3 -0
  76. package/dist/overlay.d.ts +5 -4
  77. package/dist/overlay.d.ts.map +1 -1
  78. package/dist/overlay.js +197 -132
  79. package/dist/query.d.ts +27 -1
  80. package/dist/query.d.ts.map +1 -1
  81. package/dist/query.js +36 -7
  82. package/dist/reach.d.ts +1 -2
  83. package/dist/reach.d.ts.map +1 -1
  84. package/dist/reach.js +17 -4
  85. package/dist/react.d.ts +3 -3
  86. package/dist/react.d.ts.map +1 -1
  87. package/dist/react.js +7 -17
  88. package/dist/read-side.d.ts +3 -1
  89. package/dist/read-side.d.ts.map +1 -1
  90. package/dist/read-side.js +256 -69
  91. package/dist/reads.d.ts +11 -0
  92. package/dist/reads.d.ts.map +1 -0
  93. package/dist/reads.js +98 -0
  94. package/dist/refresh.d.ts +4 -4
  95. package/dist/refresh.d.ts.map +1 -1
  96. package/dist/refresh.js +39 -14
  97. package/dist/replica.d.ts +11 -5
  98. package/dist/replica.d.ts.map +1 -1
  99. package/dist/replica.js +79 -41
  100. package/dist/room-draft.d.ts +12 -0
  101. package/dist/room-draft.d.ts.map +1 -1
  102. package/dist/room-draft.js +11 -12
  103. package/dist/rows.d.ts +3 -1
  104. package/dist/rows.d.ts.map +1 -1
  105. package/dist/rows.js +8 -1
  106. package/dist/save.d.ts +4 -2
  107. package/dist/save.d.ts.map +1 -1
  108. package/dist/save.js +195 -111
  109. package/dist/search.d.ts +27 -6
  110. package/dist/search.d.ts.map +1 -1
  111. package/dist/search.js +89 -7
  112. package/dist/seed.d.ts +16 -0
  113. package/dist/seed.d.ts.map +1 -0
  114. package/dist/seed.js +75 -0
  115. package/dist/shared.d.ts +4 -0
  116. package/dist/shared.d.ts.map +1 -1
  117. package/dist/shared.js +63 -33
  118. package/dist/store.d.ts +20 -4
  119. package/dist/store.d.ts.map +1 -1
  120. package/dist/store.js +404 -317
  121. package/dist/turn.d.ts +5 -3
  122. package/dist/turn.d.ts.map +1 -1
  123. package/dist/turn.js +26 -7
  124. package/dist/windows.d.ts +26 -0
  125. package/dist/windows.d.ts.map +1 -0
  126. package/dist/windows.js +93 -0
  127. package/dist/wire.d.ts +16 -0
  128. package/dist/wire.d.ts.map +1 -1
  129. package/dist/wire.js +4 -0
  130. package/package.json +2 -2
  131. package/dist/acquisition.d.ts +0 -14
  132. package/dist/acquisition.d.ts.map +0 -1
  133. package/dist/acquisition.js +0 -24
package/dist/read-side.js CHANGED
@@ -2,6 +2,7 @@ import { and, BasicIndex, configureIndexDevMode, count, createCollection, create
2
2
  import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
3
3
  import { documentKey } from './rows.js';
4
4
  import { after, inUnits, ROW_WEIGHT } from './turn.js';
5
+ import { beyond, cursorOf, MAXIMUM_WINDOW, windowed } from './windows.js';
5
6
  function every(expressions) {
6
7
  const [first, second, ...rest] = expressions;
7
8
  return second === undefined ? first : and(first, second, ...rest);
@@ -59,10 +60,12 @@ function reached(reach, room, document) {
59
60
  ? []
60
61
  : [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))];
61
62
  }
62
- function scoped(scope, row, rooms, reach) {
63
+ function scoped(scope, row, rooms, reach, paths = null, folder = null) {
63
64
  return every([
64
65
  eq(row.kind, 'document'),
65
66
  ...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
67
+ ...(paths === null ? [] : [inArray(row.path, [...paths])]),
68
+ ...(folder === null ? [] : [eq(row.folder, folder)]),
66
69
  ...reached(reach, row.room, row.key),
67
70
  ...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
68
71
  ...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
@@ -90,7 +93,7 @@ function related(q, rows, scope, field, kept, reach) {
90
93
  }
91
94
  function filtered(q, rows, plan) {
92
95
  const from = q.from({ d: rows });
93
- let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach)), plan.root.where, 'd');
96
+ let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach, plan.paths, plan.folder)), plan.root.where, 'd');
94
97
  if (plan.hop !== null) {
95
98
  const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
96
99
  query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
@@ -104,25 +107,56 @@ function filtered(q, rows, plan) {
104
107
  function bodied(shape) {
105
108
  return shape.sections !== null || shape.preamble || shape.markdown;
106
109
  }
110
+ function orderingOf(options) {
111
+ const collated = options.stringSort === 'locale';
112
+ return {
113
+ direction: options.direction,
114
+ nulls: options.nulls ?? 'first',
115
+ stringSort: options.stringSort ?? 'locale',
116
+ locale: collated ? options.locale : undefined,
117
+ localeOptions: collated
118
+ ? options.localeOptions
119
+ : undefined,
120
+ };
121
+ }
122
+ function storedValue(key, row) {
123
+ return key.of === 'field' ? row.values[key.field] : row[key.of];
124
+ }
125
+ function documentOrder(shape) {
126
+ return shape.orderBy.map(({ options }) => orderingOf(options));
127
+ }
128
+ function documentCursor(shape, row) {
129
+ return cursorOf([
130
+ ...shape.orderBy.map((_, index) => row[`order${index}`]),
131
+ documentKey(row.room, row.path),
132
+ ]);
133
+ }
107
134
  function documents(q, rows, plan, shape) {
108
135
  let query = filtered(q, rows, plan);
136
+ const cursor = plan.after?.values ?? null;
137
+ if (cursor !== null) {
138
+ const orderings = documentOrder(shape);
139
+ query = query.fn.where(({ d }) => beyond([...shape.orderBy.map(({ key }) => storedValue(key, d)), d.key], cursor, orderings));
140
+ }
109
141
  for (const { key, options } of shape.orderBy) {
110
142
  query = query.orderBy(({ d }) => keyed(key, d), options);
111
143
  }
112
- if (shape.orderBy.length === 0 && plan.limit !== null) {
144
+ if (shape.orderBy.length > 0 || plan.limit !== null || plan.after !== null) {
113
145
  query = query.orderBy(({ d }) => d.key, { stringSort: 'lexical' });
114
146
  }
115
147
  if (plan.limit !== null)
116
- query = query.limit(plan.limit);
148
+ query = query.limit(plan.limit + 1);
117
149
  return query.select(({ d }) => ({
118
150
  room: d.room,
119
151
  path: d.path,
120
152
  type: d.type,
121
153
  binary: d.binary,
154
+ ...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
122
155
  ...(shape.fields === null ? {} : { fields: d.fields }),
123
156
  ...(bodied(shape) ? { text: d.text } : {}),
124
157
  ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
125
158
  ...(shape.change ? { change: d.change } : {}),
159
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
126
160
  }));
127
161
  }
128
162
  function backlinked(q, rows, document, reach) {
@@ -133,15 +167,13 @@ function backlinked(q, rows, document, reach) {
133
167
  .orderBy(({ r }) => r.field, { stringSort: 'lexical' })
134
168
  .select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
135
169
  }
136
- function groups(q, stage, shape, limit) {
170
+ function groups(q, stage, shape) {
137
171
  const keys = shape.groupBy.map((_, index) => `key${index}`);
138
172
  const from = q.from({ m: stage });
139
173
  let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
140
174
  for (const { by, direction } of shape.orderBy) {
141
175
  query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
142
176
  }
143
- if (limit !== null && shape.orderBy.length > 0)
144
- query = query.limit(limit);
145
177
  return query.select(({ m }) => ({
146
178
  ...Object.fromEntries(keys.map((key) => [key, m[key]])),
147
179
  count: count(m.key),
@@ -151,15 +183,27 @@ function built(rows, plan) {
151
183
  const shape = plan.shape;
152
184
  if (shape.kind === 'documents') {
153
185
  const collection = live((q) => documents(q, rows, plan, shape));
154
- return { live: collection, dispose: () => void collection.cleanup() };
186
+ const count = plan.total
187
+ ? live((q) => filtered(q, rows, plan).select(({ d }) => ({ key: d.key })))
188
+ : null;
189
+ return {
190
+ live: collection,
191
+ count,
192
+ dispose: () => {
193
+ void collection.cleanup();
194
+ if (count !== null)
195
+ void count.cleanup();
196
+ },
197
+ };
155
198
  }
156
199
  const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
157
200
  key: d.key,
158
201
  ...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
159
202
  })));
160
- const collection = live((q) => groups(q, stage, shape, plan.limit));
203
+ const collection = live((q) => groups(q, stage, shape));
161
204
  return {
162
205
  live: collection,
206
+ count: null,
163
207
  dispose: () => {
164
208
  void collection.cleanup();
165
209
  void stage.cleanup();
@@ -239,6 +283,7 @@ function documentOf(key, row, shape, bodies) {
239
283
  room: row.room,
240
284
  path: row.path,
241
285
  ...(binary === null ? {} : { binaryFile: binary }),
286
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
242
287
  };
243
288
  }
244
289
  const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
@@ -260,33 +305,75 @@ function documentOf(key, row, shape, bodies) {
260
305
  ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
261
306
  ...(shape.backlinks
262
307
  ? {
263
- backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
308
+ backlinks: row.backlinks
309
+ .slice(0, MAXIMUM_WINDOW)
310
+ .map(({ path, type, field }) => ({ path, type, field })),
264
311
  }
265
312
  : {}),
266
313
  ...(shape.change ? { change: row.change } : {}),
314
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
267
315
  };
268
316
  }
269
317
  function weight(row) {
270
318
  return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
271
319
  }
272
- function rowsOfPlan(plan, entries, bodies, each = (row) => row) {
320
+ const HELD_PER_WEIGHT = 16;
321
+ function groupValues(shape, [key, row]) {
322
+ return [...shape.orderBy.map(({ by }) => (by === 'count' ? row.count : row[`key${by}`])), key];
323
+ }
324
+ function groupOrder(shape) {
325
+ return shape.orderBy.map(({ direction }) => ({
326
+ direction,
327
+ nulls: 'first',
328
+ stringSort: 'locale',
329
+ locale: undefined,
330
+ localeOptions: undefined,
331
+ }));
332
+ }
333
+ function windowOf(plan, entries, total) {
273
334
  const shape = plan.shape;
335
+ if (shape.kind === 'documents') {
336
+ return {
337
+ ...windowed(entries, plan.limit, plan.after?.text, ([, row]) => documentCursor(shape, row)),
338
+ total,
339
+ };
340
+ }
341
+ const cursor = plan.after?.values ?? null;
342
+ const orderings = groupOrder(shape);
343
+ const following = cursor === null
344
+ ? entries
345
+ : entries.filter((entry) => beyond(groupValues(shape, entry), cursor, orderings));
346
+ return {
347
+ ...windowed(following, plan.limit, plan.after?.text, (entry) => cursorOf(groupValues(shape, entry))),
348
+ total: plan.total ? entries.length : undefined,
349
+ };
350
+ }
351
+ function framed(rows, { next, total }) {
352
+ return {
353
+ ...rows,
354
+ ...(next === undefined ? {} : { next }),
355
+ ...(total === undefined ? {} : { total }),
356
+ };
357
+ }
358
+ function rowsOfPlan(plan, entries, total, bodies, each = (row) => row) {
359
+ const shape = plan.shape;
360
+ const { kept, ...window } = windowOf(plan, entries, total);
361
+ const keys = kept.map(([key]) => key);
274
362
  if (shape.kind === 'documents') {
275
363
  const documents = [];
276
- return after(inUnits(entries, ([key, row]) => {
364
+ return after(inUnits(kept, ([key, row]) => {
277
365
  documents.push(each(documentOf(key, row, shape, bodies)));
278
366
  return weight(row);
279
- }), () => ({ rows: { documents }, keys: entries.map(([key]) => key) }));
367
+ }), () => ({ rows: framed({ documents }, window), keys, ...window }));
280
368
  }
281
- const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
282
369
  const groups = [];
283
- return after(inUnits(bounded, ([, row]) => {
370
+ return after(inUnits(kept, ([, row]) => {
284
371
  groups.push(each({
285
372
  key: shape.groupBy.map((_, index) => row[`key${index}`]),
286
373
  count: row.count,
287
374
  }));
288
375
  return ROW_WEIGHT;
289
- }), () => ({ rows: { groups }, keys: bounded.map(([key]) => key) }));
376
+ }), () => ({ rows: framed({ groups }, window), keys, ...window }));
290
377
  }
291
378
  export function reconciled(previous, next) {
292
379
  const before = previous.result;
@@ -311,13 +398,16 @@ export function reconciled(previous, next) {
311
398
  const removed = previous.keys.filter((key) => !present.has(key));
312
399
  const moved = reordered(previous.keys, next.keys, present);
313
400
  const covered = !deepEquals(before.coverage, after.coverage);
314
- if (changed.length === 0 && removed.length === 0 && !moved && !covered)
401
+ const framing = reframed(before, after);
402
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
315
403
  return null;
404
+ }
405
+ const window = { next: after.next, total: after.total };
316
406
  return {
317
407
  keyed: {
318
408
  result: 'groups' in after
319
- ? { groups: rows, coverage: after.coverage }
320
- : { documents: rows, coverage: after.coverage },
409
+ ? framed({ groups: rows, coverage: after.coverage }, window)
410
+ : framed({ documents: rows, coverage: after.coverage }, window),
321
411
  keys: next.keys,
322
412
  },
323
413
  message: {
@@ -325,9 +415,17 @@ export function reconciled(previous, next) {
325
415
  removed,
326
416
  order: moved ? next.keys : null,
327
417
  ...(covered ? { coverage: after.coverage } : {}),
418
+ ...framing,
328
419
  },
329
420
  };
330
421
  }
422
+ function reframed(before, after) {
423
+ const next = before.next === after.next ? {} : { next: after.next ?? null };
424
+ const total = before.total === after.total || after.total === undefined ? {} : { total: after.total };
425
+ return Object.keys(next).length === 0 && Object.keys(total).length === 0
426
+ ? null
427
+ : { ...next, ...total };
428
+ }
331
429
  function reordered(previous, next, present) {
332
430
  let at = 0;
333
431
  for (const key of previous) {
@@ -357,6 +455,9 @@ export function createReadSide() {
357
455
  },
358
456
  });
359
457
  const shared = new Map();
458
+ let writing = false;
459
+ let publishing = null;
460
+ let closed = false;
360
461
  function searched(row) {
361
462
  const read = row.text === null ? null : parse(row.text, row.path);
362
463
  if (read === null || !read.ok)
@@ -376,8 +477,11 @@ export function createReadSide() {
376
477
  return;
377
478
  entry.rows.set(key, row);
378
479
  }
480
+ function counted(entry) {
481
+ return entry.built.count === null ? undefined : entry.built.count.size;
482
+ }
379
483
  function rebuilt(entry) {
380
- return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
484
+ return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], counted(entry), entry.bodies), ({ rows: found, keys, next, total }) => {
381
485
  const present = new Set(keys);
382
486
  for (const key of [...entry.rows.keys()])
383
487
  if (!present.has(key))
@@ -387,32 +491,34 @@ export function createReadSide() {
387
491
  entry.bodies.delete(key);
388
492
  }
389
493
  keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
390
- return keys;
494
+ return { keys, next, total };
391
495
  });
392
496
  }
393
497
  function refreshed(entry, dirty) {
394
498
  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)]);
499
+ const { kept: inWindow, next, total, } = windowOf(entry.plan, [...entry.built.live.entries()], counted(entry));
500
+ const keys = inWindow.map(([key]) => key);
501
+ const present = new Set(keys);
502
+ for (const key of [...entry.rows.keys()]) {
503
+ if (present.has(key))
504
+ continue;
505
+ entry.rows.delete(key);
506
+ entry.bodies.delete(key);
507
+ }
508
+ const values = inWindow.filter(([key]) => dirty.has(key) || !entry.rows.has(key));
398
509
  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
- }
510
+ kept(entry, key, documentOf(key, value, shape, entry.bodies));
405
511
  return weight(value);
406
512
  });
407
- return after(done, () => keys);
513
+ return after(done, () => ({ keys, next, total }));
408
514
  }
409
- function current(entry, keys, coverage) {
410
- const found = keys.map((key) => entry.rows.get(key));
515
+ function current(entry, found, coverage) {
516
+ const rows = found.keys.map((key) => entry.rows.get(key));
411
517
  return {
412
518
  result: entry.plan.shape.kind === 'groups'
413
- ? { groups: found, coverage }
414
- : { documents: found, coverage },
415
- keys,
519
+ ? framed({ groups: rows, coverage }, found)
520
+ : framed({ documents: rows, coverage }, found),
521
+ keys: found.keys,
416
522
  };
417
523
  }
418
524
  function delivered(entry, next) {
@@ -448,25 +554,26 @@ export function createReadSide() {
448
554
  const dirty = entry.dirty;
449
555
  entry.dirty = new Set();
450
556
  entry.whole = false;
451
- const keys = whole ? rebuilt(entry) : refreshed(entry, dirty);
452
- if (keys instanceof Promise) {
453
- void keys
454
- .then((found) => {
557
+ const found = whole ? rebuilt(entry) : refreshed(entry, dirty);
558
+ if (found instanceof Promise) {
559
+ void found
560
+ .then((window) => {
455
561
  if (entry.listeners.size > 0)
456
- delivered(entry, current(entry, found, cover.coverage));
457
- run(entry);
562
+ delivered(entry, current(entry, window, cover.coverage));
563
+ entry.running = false;
564
+ wake(entry);
458
565
  })
459
566
  .catch(() => {
460
567
  entry.running = false;
461
568
  });
462
569
  return;
463
570
  }
464
- delivered(entry, current(entry, keys, cover.coverage));
571
+ delivered(entry, current(entry, found, cover.coverage));
465
572
  }
466
573
  entry.running = false;
467
574
  }
468
575
  function wake(entry) {
469
- if (entry.subscription === null || entry.running)
576
+ if (entry.subscription === null || entry.running || writing || publishing !== null)
470
577
  return;
471
578
  entry.running = true;
472
579
  try {
@@ -492,37 +599,92 @@ export function createReadSide() {
492
599
  wake(entry);
493
600
  }
494
601
  }
602
+ function committed(next) {
603
+ if (closed || next.size === 0)
604
+ return;
605
+ sync.begin();
606
+ for (const [key, row] of next) {
607
+ if (row === null) {
608
+ if (rows.has(key))
609
+ sync.write({ type: 'delete', key });
610
+ continue;
611
+ }
612
+ sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
613
+ }
614
+ sync.commit();
615
+ }
616
+ function published(set) {
617
+ let unit = new Map();
618
+ return inUnits(set.documents, (change) => {
619
+ if (closed)
620
+ return 0;
621
+ const shifted = rows.size / HELD_PER_WEIGHT;
622
+ let spent = 0;
623
+ for (const row of change.before) {
624
+ unit.set(row.key, null);
625
+ spent += ROW_WEIGHT + shifted;
626
+ }
627
+ for (const row of change.after) {
628
+ unit.set(row.key, row);
629
+ spent += weight(row) + shifted;
630
+ }
631
+ return spent;
632
+ }, () => {
633
+ committed(unit);
634
+ unit = new Map();
635
+ });
636
+ }
637
+ function released() {
638
+ recover();
639
+ for (const entry of shared.values())
640
+ wake(entry);
641
+ }
642
+ function publishedLater(applied) {
643
+ const fence = applied.then(() => { }, () => { });
644
+ publishing = fence;
645
+ void fence.then(() => {
646
+ if (publishing !== fence)
647
+ return;
648
+ publishing = null;
649
+ released();
650
+ });
651
+ return applied;
652
+ }
653
+ async function settled() {
654
+ while (publishing !== null)
655
+ await publishing;
656
+ }
495
657
  return {
496
- apply(set) {
497
- const next = new Map();
498
- for (const change of set.documents) {
499
- for (const row of change.before)
500
- next.set(row.key, null);
501
- for (const row of change.after)
502
- next.set(row.key, row);
658
+ apply(set, next = () => { }) {
659
+ const pending = publishing;
660
+ if (pending !== null) {
661
+ return publishedLater(pending.then(() => after(published(set), next)));
503
662
  }
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 });
513
- }
514
- sync.commit();
663
+ writing = true;
664
+ let applied;
665
+ try {
666
+ applied = published(set);
667
+ }
668
+ finally {
669
+ writing = false;
515
670
  }
516
- recover();
671
+ if (applied instanceof Promise)
672
+ return publishedLater(applied.then(next));
673
+ released();
674
+ next();
517
675
  },
518
676
  recover,
677
+ settled,
519
678
  async query(plan, at) {
520
- const { live: collection, dispose } = built(rows, plan);
679
+ const { live: collection, count, dispose } = built(rows, plan);
521
680
  try {
522
681
  await collection.toArrayWhenReady();
682
+ if (count !== null)
683
+ await count.toArrayWhenReady();
684
+ await settled();
523
685
  const entries = [...collection.entries()];
524
686
  const held = at();
525
- const found = await rowsOfPlan(plan, entries, null, structuredClone);
687
+ const found = await rowsOfPlan(plan, entries, count?.size, null, structuredClone);
526
688
  return { rows: found.rows, at: held };
527
689
  }
528
690
  finally {
@@ -543,13 +705,16 @@ export function createReadSide() {
543
705
  fresh: new Set(),
544
706
  ready: new Set(),
545
707
  subscription: null,
708
+ counting: null,
546
709
  whole: true,
547
710
  dirty: new Set(),
548
711
  stale: false,
549
712
  running: false,
550
713
  };
551
714
  created.built.live.on('status:change', ({ status }) => {
552
- if (status === 'cleaned-up' && created.listeners.size === 0)
715
+ if (status !== 'cleaned-up' || created.listeners.size > 0)
716
+ return;
717
+ if (shared.get(identity) === created)
553
718
  shared.delete(identity);
554
719
  });
555
720
  shared.set(identity, created);
@@ -557,7 +722,14 @@ export function createReadSide() {
557
722
  }
558
723
  const observed = entry;
559
724
  observed.listeners.add(deliverTo);
560
- observed.built.live.onFirstReady(() => {
725
+ const ready = (then) => observed.built.live.onFirstReady(() => observed.built.count === null ? then() : observed.built.count.onFirstReady(then));
726
+ const settled = () => {
727
+ if (observed.built.count === null)
728
+ wake(observed);
729
+ else
730
+ queueMicrotask(() => wake(observed));
731
+ };
732
+ ready(() => {
561
733
  if (!observed.listeners.has(deliverTo))
562
734
  return;
563
735
  observed.fresh.add(deliverTo);
@@ -569,8 +741,13 @@ export function createReadSide() {
569
741
  else
570
742
  for (const { key } of changes)
571
743
  observed.dirty.add(key);
572
- wake(observed);
744
+ settled();
573
745
  }, { includeInitialState: false });
746
+ observed.counting =
747
+ observed.built.count?.subscribeChanges(() => {
748
+ observed.stale = true;
749
+ settled();
750
+ }, { includeInitialState: false }) ?? null;
574
751
  }
575
752
  wake(observed);
576
753
  });
@@ -581,11 +758,16 @@ export function createReadSide() {
581
758
  return;
582
759
  observed.subscription?.unsubscribe();
583
760
  observed.subscription = null;
761
+ observed.counting?.unsubscribe();
762
+ observed.counting = null;
584
763
  observed.result = undefined;
585
764
  observed.rows.clear();
586
765
  observed.bodies.clear();
587
766
  observed.dirty.clear();
588
767
  observed.whole = true;
768
+ if (shared.get(identity) === observed)
769
+ shared.delete(identity);
770
+ observed.built.dispose();
589
771
  };
590
772
  },
591
773
  room(room) {
@@ -596,11 +778,13 @@ export function createReadSide() {
596
778
  return row === undefined || row.type === null ? null : searched(row);
597
779
  },
598
780
  close() {
781
+ closed = true;
599
782
  for (const entry of shared.values()) {
600
783
  entry.listeners.clear();
601
784
  entry.fresh.clear();
602
785
  entry.ready.clear();
603
786
  entry.subscription?.unsubscribe();
787
+ entry.counting?.unsubscribe();
604
788
  entry.built.dispose();
605
789
  }
606
790
  shared.clear();
@@ -625,13 +809,16 @@ function changedFrom(previous, next) {
625
809
  const removed = previous.keys.filter((key) => !present.has(key));
626
810
  const moved = reordered(previous.keys, next.keys, present);
627
811
  const covered = !deepEquals(before.coverage, after.coverage);
628
- if (changed.length === 0 && removed.length === 0 && !moved && !covered)
812
+ const framing = reframed(before, after);
813
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
629
814
  return null;
815
+ }
630
816
  return {
631
817
  changed,
632
818
  removed,
633
819
  order: moved ? next.keys : null,
634
820
  ...(covered ? { coverage: after.coverage } : {}),
821
+ ...framing,
635
822
  };
636
823
  }
637
824
  function rowAt(rows, index) {
@@ -0,0 +1,11 @@
1
+ import type { Adapter } from './adapter.js';
2
+ import type { Reads } from './bootstrap.js';
3
+ export type DoorReads = Reads & {
4
+ carried(locator: string): number;
5
+ finished(locator: string, turn: number): void;
6
+ opened(): void;
7
+ applied(): void;
8
+ lost(): void;
9
+ };
10
+ export declare function doorReads(adapter: Adapter, declaration: (locator: string) => unknown, generation: () => number): DoorReads;
11
+ //# sourceMappingURL=reads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reads.d.ts","sourceRoot":"","sources":["../src/reads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAOR,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG;IAC9B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;IAChB,IAAI,IAAI,IAAI,CAAC;CACd,CAAC;AAiCF,wBAAgB,SAAS,CACvB,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,EACzC,UAAU,EAAE,MAAM,MAAM,GACvB,SAAS,CA0FX"}