@agentshouse/mdruntime 0.1.0-c907ac1fbed44 → 0.1.0-c9260f23cccf4

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 (100) hide show
  1. package/README.md +2 -1
  2. package/dist/adapter.d.ts +25 -7
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +2 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +28 -4
  7. package/dist/bootstrap.d.ts +8 -8
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +90 -84
  10. package/dist/chain.d.ts.map +1 -1
  11. package/dist/chain.js +7 -3
  12. package/dist/client.d.ts +4 -4
  13. package/dist/client.d.ts.map +1 -1
  14. package/dist/client.js +52 -5
  15. package/dist/collection.d.ts +5 -0
  16. package/dist/collection.d.ts.map +1 -1
  17. package/dist/collection.js +23 -2
  18. package/dist/compile.d.ts.map +1 -1
  19. package/dist/compile.js +81 -29
  20. package/dist/connection.d.ts +5 -2
  21. package/dist/connection.d.ts.map +1 -1
  22. package/dist/connection.js +654 -359
  23. package/dist/conversation.d.ts +5 -5
  24. package/dist/conversation.d.ts.map +1 -1
  25. package/dist/conversation.js +126 -33
  26. package/dist/entry.d.ts +1 -0
  27. package/dist/entry.d.ts.map +1 -1
  28. package/dist/entry.js +1 -1
  29. package/dist/file.d.ts +2 -11
  30. package/dist/file.d.ts.map +1 -1
  31. package/dist/file.js +6 -4
  32. package/dist/fulltext.d.ts.map +1 -1
  33. package/dist/fulltext.js +8 -6
  34. package/dist/host.d.ts.map +1 -1
  35. package/dist/host.js +4 -2
  36. package/dist/index.d.ts +3 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/live.d.ts.map +1 -1
  40. package/dist/live.js +2 -1
  41. package/dist/media.d.ts +8 -0
  42. package/dist/media.d.ts.map +1 -0
  43. package/dist/media.js +208 -0
  44. package/dist/memory.d.ts +2 -2
  45. package/dist/memory.d.ts.map +1 -1
  46. package/dist/memory.js +47 -9
  47. package/dist/model.d.ts +6 -0
  48. package/dist/model.d.ts.map +1 -1
  49. package/dist/model.js +7 -1
  50. package/dist/observation.d.ts +1 -0
  51. package/dist/observation.d.ts.map +1 -1
  52. package/dist/observation.js +1 -1
  53. package/dist/outcome.d.ts +13 -2
  54. package/dist/outcome.d.ts.map +1 -1
  55. package/dist/outcome.js +16 -2
  56. package/dist/paths.d.ts +1 -0
  57. package/dist/paths.d.ts.map +1 -1
  58. package/dist/paths.js +4 -0
  59. package/dist/query.d.ts +6 -0
  60. package/dist/query.d.ts.map +1 -1
  61. package/dist/reach.d.ts +8 -1
  62. package/dist/reach.d.ts.map +1 -1
  63. package/dist/reach.js +51 -17
  64. package/dist/readiness.d.ts +1 -2
  65. package/dist/readiness.d.ts.map +1 -1
  66. package/dist/reads.d.ts.map +1 -1
  67. package/dist/reads.js +10 -19
  68. package/dist/refresh.d.ts.map +1 -1
  69. package/dist/refresh.js +1 -5
  70. package/dist/replica.d.ts +3 -4
  71. package/dist/replica.d.ts.map +1 -1
  72. package/dist/replica.js +4 -3
  73. package/dist/room-draft.d.ts +17 -2
  74. package/dist/room-draft.d.ts.map +1 -1
  75. package/dist/room-draft.js +40 -6
  76. package/dist/rows.d.ts +15 -6
  77. package/dist/rows.d.ts.map +1 -1
  78. package/dist/rows.js +102 -74
  79. package/dist/save.d.ts +2 -0
  80. package/dist/save.d.ts.map +1 -1
  81. package/dist/save.js +11 -5
  82. package/dist/status.d.ts +5 -0
  83. package/dist/status.d.ts.map +1 -1
  84. package/dist/status.js +3 -1
  85. package/dist/store.d.ts +16 -19
  86. package/dist/store.d.ts.map +1 -1
  87. package/dist/store.js +23 -29
  88. package/dist/stored.d.ts +9 -19
  89. package/dist/stored.d.ts.map +1 -1
  90. package/dist/stored.js +198 -196
  91. package/dist/tables.d.ts +14 -16
  92. package/dist/tables.d.ts.map +1 -1
  93. package/dist/tables.js +6 -9
  94. package/dist/wire.d.ts +8 -4
  95. package/dist/wire.d.ts.map +1 -1
  96. package/dist/wire.js +2 -1
  97. package/dist/words.d.ts.map +1 -1
  98. package/dist/words.js +8 -2
  99. package/package.json +7 -7
  100. package/AGENTS.md +0 -312
package/dist/stored.js CHANGED
@@ -1,19 +1,18 @@
1
+ import { composedOf } from './collection.js';
1
2
  import { indexDocument, unindexAll, unindexDocuments } from './fulltext.js';
2
- import { derived } from './rows.js';
3
+ import { described, derived } from './rows.js';
3
4
  export const REPLICA_TABLES = [
4
- 'CREATE TABLE replica_keys (key INTEGER PRIMARY KEY AUTOINCREMENT, locator TEXT NOT NULL, transfer TEXT NOT NULL, composition TEXT NOT NULL, rederiving INTEGER)',
5
+ 'CREATE TABLE replica_keys (key INTEGER PRIMARY KEY AUTOINCREMENT, locator TEXT NOT NULL, transfer TEXT NOT NULL, fingerprint TEXT NOT NULL, rederiving INTEGER, searching INTEGER)',
5
6
  'CREATE TABLE replica_rooms (locator TEXT PRIMARY KEY, key INTEGER NOT NULL, record TEXT NOT NULL) WITHOUT ROWID',
6
- 'CREATE TABLE replica_transfers (locator TEXT PRIMARY KEY, key INTEGER NOT NULL, record TEXT NOT NULL) WITHOUT ROWID',
7
- 'CREATE TABLE replica_transfer_parts (locator TEXT NOT NULL, token TEXT NOT NULL, part BLOB NOT NULL)',
8
- 'CREATE INDEX replica_transfer_parts_locator ON replica_transfer_parts (locator)',
9
7
  'CREATE TABLE replica_entries (id INTEGER PRIMARY KEY, key INTEGER NOT NULL, layer INTEGER NOT NULL, path TEXT NOT NULL, visible INTEGER NOT NULL, present INTEGER NOT NULL, queried INTEGER NOT NULL, binary TEXT, revision TEXT, file_id TEXT, change TEXT, written TEXT, type TEXT, declared TEXT, folder TEXT NOT NULL, fields TEXT, content TEXT)',
10
8
  'CREATE UNIQUE INDEX replica_entries_path ON replica_entries (key, layer, path)',
11
- 'CREATE INDEX replica_entries_visible ON replica_entries (key, visible, path)',
9
+ 'CREATE INDEX replica_entries_visible ON replica_entries (key, visible, present, path, binary)',
12
10
  'CREATE INDEX replica_entries_folder ON replica_entries (key, folder, path)',
13
- 'CREATE TABLE replica_values (doc INTEGER NOT NULL, key INTEGER NOT NULL, form TEXT NOT NULL, held INTEGER NOT NULL, item INTEGER NOT NULL, kind TEXT NOT NULL, value, size INTEGER, single INTEGER NOT NULL, PRIMARY KEY (doc, form, held, item)) WITHOUT ROWID',
14
- 'CREATE INDEX replica_values_lookup ON replica_values (key, form, kind, value)',
15
- 'CREATE TABLE replica_links (doc INTEGER NOT NULL, key INTEGER NOT NULL, kind TEXT NOT NULL, form TEXT NOT NULL, target TEXT NOT NULL, field TEXT NOT NULL, PRIMARY KEY (doc, kind, form, target)) WITHOUT ROWID',
16
- 'CREATE INDEX replica_links_target ON replica_links (key, target, kind, form)',
11
+ 'CREATE INDEX replica_entries_type ON replica_entries (key, type, visible, present, queried, folder)',
12
+ 'CREATE INDEX replica_entries_key ON replica_entries (key)',
13
+ 'CREATE TABLE replica_values (doc INTEGER NOT NULL, keys TEXT NOT NULL, places TEXT NOT NULL, item INTEGER NOT NULL, kind TEXT NOT NULL, value, size INTEGER, PRIMARY KEY (doc, keys, places, item)) WITHOUT ROWID',
14
+ 'CREATE TABLE replica_links (doc INTEGER NOT NULL, key INTEGER NOT NULL, kind TEXT NOT NULL, keys TEXT NOT NULL, places TEXT NOT NULL, target TEXT NOT NULL, field TEXT NOT NULL, PRIMARY KEY (doc, kind, keys, places, target)) WITHOUT ROWID',
15
+ 'CREATE INDEX replica_links_target ON replica_links (key, target, kind, keys)',
17
16
  'CREATE TABLE replica_folders (key INTEGER NOT NULL, layer INTEGER NOT NULL, path TEXT NOT NULL, present INTEGER NOT NULL, PRIMARY KEY (key, layer, path)) WITHOUT ROWID',
18
17
  'CREATE TABLE replica_erasing (key INTEGER PRIMARY KEY, locator TEXT NOT NULL)',
19
18
  'CREATE TABLE replica_received (key TEXT PRIMARY KEY, head TEXT NOT NULL) WITHOUT ROWID',
@@ -24,7 +23,17 @@ export const REPLICA_TABLES = [
24
23
  const CONFIRMED = 0;
25
24
  const OVERLAY = 1;
26
25
  const REFRESHED = 2;
27
- const compositions = new WeakMap();
26
+ const DERIVED_ROWS = 64;
27
+ const ENTRY_WEIGHT = 256;
28
+ const ABSENT = {
29
+ type: null,
30
+ declared: null,
31
+ queried: false,
32
+ folder: '',
33
+ fields: null,
34
+ values: [],
35
+ links: [],
36
+ };
28
37
  function text(value) {
29
38
  return typeof value === 'string' ? value : null;
30
39
  }
@@ -40,84 +49,54 @@ function flag(value) {
40
49
  function paths(list) {
41
50
  return JSON.stringify(list);
42
51
  }
43
- function compositionOf(connection, key) {
44
- let held = compositions.get(connection);
45
- if (held === undefined) {
46
- held = new Map();
47
- compositions.set(connection, held);
48
- }
49
- const [row] = connection.rows('SELECT composition FROM replica_keys WHERE key = ?', [key]);
50
- const textual = text(row?.composition) ?? 'null';
51
- const known = held.get(textual);
52
- if (known !== undefined)
53
- return known;
54
- const parsed = JSON.parse(textual) ?? {
55
- model: { status: 'unavailable', reason: 'not-acquired' },
56
- selection: null,
57
- };
58
- held.clear();
59
- held.set(textual, parsed);
60
- return parsed;
61
- }
62
- export function fingerprint(composition) {
63
- const shape = (model) => model.status === 'unavailable' ? null : model.types;
64
- return JSON.stringify({
65
- model: shape(composition.model),
66
- selection: composition.selection === null
67
- ? null
68
- : { roots: composition.selection.roots, model: shape(composition.selection.model) },
69
- });
70
- }
71
52
  function dropDerived(connection, doc) {
72
53
  connection.run('DELETE FROM replica_values WHERE doc = ?', [doc]);
73
54
  connection.run('DELETE FROM replica_links WHERE doc = ?', [doc]);
74
55
  }
56
+ function scalarText(value) {
57
+ if (value === Infinity)
58
+ return 'Infinity';
59
+ if (value === -Infinity)
60
+ return '-Infinity';
61
+ return JSON.stringify(value);
62
+ }
75
63
  function writeDerived(connection, doc, key, made) {
76
- for (const value of made.values) {
77
- connection.run('INSERT INTO replica_values (doc, key, form, held, item, kind, value, size, single) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [
64
+ if (made.values.length > 0) {
65
+ const rows = made.values.map((value) => `[${JSON.stringify(value.keys)},${JSON.stringify(value.places)},${value.item},${JSON.stringify(value.kind)},${scalarText(value.value)},${JSON.stringify(value.size)}]`);
66
+ connection.run('INSERT INTO replica_values (doc, keys, places, item, kind, value, size) SELECT ?, value ->> 0, value ->> 1, value ->> 2, value ->> 3, value ->> 4, value ->> 5 FROM json_each(?)', [doc, `[${rows.join(',')}]`]);
67
+ }
68
+ if (made.links.length > 0) {
69
+ connection.run('INSERT OR IGNORE INTO replica_links (doc, key, kind, keys, places, target, field) SELECT ?, ?, value ->> 0, value ->> 1, value ->> 2, value ->> 3, value ->> 4 FROM json_each(?)', [
78
70
  doc,
79
71
  key,
80
- value.form,
81
- value.held,
82
- value.item,
83
- value.kind,
84
- value.value,
85
- value.size,
86
- flag(value.single),
72
+ JSON.stringify(made.links.map((link) => [link.kind, link.keys, link.places, link.target, link.field])),
87
73
  ]);
88
74
  }
89
- for (const link of made.links) {
90
- connection.run('INSERT OR IGNORE INTO replica_links (doc, key, kind, form, target, field) VALUES (?, ?, ?, ?, ?, ?)', [doc, key, link.kind, link.form, link.target, link.field]);
91
- }
92
75
  }
93
76
  function entryId(connection, key, layer, path) {
94
77
  const [row] = connection.rows('SELECT id FROM replica_entries WHERE key = ? AND layer = ? AND path = ?', [key, layer, path]);
95
78
  return row === undefined ? null : number(row.id);
96
79
  }
97
- function removeEntry(connection, key, layer, path) {
80
+ function words() {
81
+ return { dropped: [], made: [] };
82
+ }
83
+ function wordsWritten(connection, owed) {
84
+ unindexDocuments(connection, owed.dropped);
85
+ for (const { id, path, content } of owed.made)
86
+ indexDocument(connection, id, path, content);
87
+ }
88
+ function removeEntry(connection, key, layer, path, owed) {
98
89
  const id = entryId(connection, key, layer, path);
99
90
  if (id === null)
100
91
  return;
101
92
  dropDerived(connection, id);
102
- unindexDocuments(connection, [id]);
93
+ owed.dropped.push(id);
103
94
  connection.run('DELETE FROM replica_entries WHERE id = ?', [id]);
104
95
  }
105
96
  function heldIds(connection, sql, bind) {
106
97
  return connection.rows(sql, bind).map((row) => number(row.id));
107
98
  }
108
- function putEntry(connection, key, layer, entry, present, composition, searched = true) {
109
- removeEntry(connection, key, layer, entry.path);
110
- const made = present
111
- ? derived(entry.path, entry.binary === null ? entry.content : null, composition)
112
- : {
113
- type: null,
114
- declared: null,
115
- queried: false,
116
- folder: '',
117
- fields: null,
118
- values: [],
119
- links: [],
120
- };
99
+ function insertEntry(connection, key, layer, entry, present, made) {
121
100
  connection.run('INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, binary, revision, file_id, change, written, type, declared, folder, fields) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
122
101
  key,
123
102
  layer,
@@ -136,82 +115,76 @@ function putEntry(connection, key, layer, entry, present, composition, searched
136
115
  made.folder,
137
116
  made.fields,
138
117
  ]);
118
+ }
119
+ function textOf(entry) {
120
+ return entry.binary === null ? entry.content : null;
121
+ }
122
+ function putEntry(connection, key, layer, entry, present, composition, owed, searched = true) {
123
+ removeEntry(connection, key, layer, entry.path, owed);
124
+ const content = textOf(entry);
125
+ const made = present ? derived(entry.path, content, composition) : ABSENT;
126
+ insertEntry(connection, key, layer, entry, present, made);
139
127
  if (!present)
140
128
  return;
141
129
  const id = entryId(connection, key, layer, entry.path);
142
130
  writeDerived(connection, id, key, made);
143
- if (searched && entry.binary === null && entry.content !== null && made.queried) {
144
- indexDocument(connection, id, entry.path, entry.content);
145
- }
131
+ if (searched && content !== null && made.queried)
132
+ owed.made.push({ id, path: entry.path, content });
133
+ }
134
+ function placeEntry(connection, key, entry, owed) {
135
+ removeEntry(connection, key, CONFIRMED, entry.path, owed);
136
+ insertEntry(connection, key, CONFIRMED, entry, true, described(entry.path, textOf(entry)));
146
137
  }
147
138
  function revealed(connection, key, touched) {
148
139
  if (touched.length === 0)
149
140
  return;
150
141
  connection.run(`UPDATE replica_entries SET visible = CASE WHEN layer = ${OVERLAY} THEN present ELSE NOT EXISTS (SELECT 1 FROM replica_entries o WHERE o.key = replica_entries.key AND o.layer = ${OVERLAY} AND o.path = replica_entries.path) END WHERE key = ? AND layer IN (${CONFIRMED}, ${OVERLAY}) AND path IN (SELECT value FROM json_each(?))`, [key, paths(touched)]);
151
142
  }
152
- function newKey(connection, locator, transfer, composition) {
153
- connection.run('INSERT INTO replica_keys (locator, transfer, composition, rederiving) VALUES (?, ?, ?, NULL)', [locator, transfer, JSON.stringify(composition)]);
143
+ function newKey(connection, locator, transfer, fingerprint, built) {
144
+ const owed = built ? null : 0;
145
+ connection.run('INSERT INTO replica_keys (locator, transfer, fingerprint, rederiving, searching) VALUES (?, ?, ?, ?, ?)', [locator, transfer, fingerprint, owed, owed]);
154
146
  const [row] = connection.rows('SELECT last_insert_rowid() AS key');
155
147
  return number(row.key);
156
148
  }
157
- function recomposed(connection, key, composition) {
158
- const [row] = connection.rows('SELECT composition FROM replica_keys WHERE key = ?', [key]);
159
- const before = json(row?.composition);
160
- const held = connection.rows('SELECT 1 FROM replica_entries WHERE key = ? LIMIT 1', [key]);
161
- if (held.length === 0 || (before !== null && fingerprint(before) === fingerprint(composition))) {
162
- connection.run('UPDATE replica_keys SET composition = ? WHERE key = ?', [
163
- JSON.stringify(composition),
164
- key,
165
- ]);
149
+ function recomposed(connection, key, fingerprint) {
150
+ const [row] = connection.rows('SELECT fingerprint FROM replica_keys WHERE key = ?', [key]);
151
+ if (row === undefined || text(row.fingerprint) === fingerprint)
166
152
  return;
167
- }
168
- connection.run('UPDATE replica_keys SET composition = ?, rederiving = 0 WHERE key = ?', [
169
- JSON.stringify(composition),
170
- key,
171
- ]);
153
+ const held = connection.rows('SELECT 1 FROM replica_entries WHERE key = ? LIMIT 1', [key]);
154
+ connection.run(`UPDATE replica_keys SET fingerprint = ?${held.length === 0 ? '' : ', rederiving = 0'} WHERE key = ?`, [fingerprint, key]);
172
155
  }
173
156
  export function entered(connection, locator, composition, rows) {
174
- const key = newKey(connection, locator, '', composition);
175
- for (const entry of rows)
176
- putEntry(connection, key, CONFIRMED, entry, true, composition, false);
157
+ const key = newKey(connection, locator, '', composedOf(composition).fingerprint, true);
158
+ const owed = words();
159
+ for (const entry of rows) {
160
+ putEntry(connection, key, CONFIRMED, entry, true, composition, owed, false);
161
+ }
162
+ wordsWritten(connection, owed);
177
163
  return key;
178
164
  }
179
- function dropTransfer(connection, locator) {
180
- connection.run('DELETE FROM replica_transfers WHERE locator = ?', [locator]);
181
- connection.run('DELETE FROM replica_transfer_parts WHERE locator = ?', [locator]);
182
- }
183
165
  export function stage(connection, request) {
184
- const [held] = connection.rows('SELECT key FROM replica_transfers WHERE locator = ?', [
185
- request.locator,
186
- ]);
187
166
  const key = request.key ??
188
- (held === undefined
189
- ? newKey(connection, request.locator, request.transfer, request.composition)
190
- : number(held.key));
191
- recomposed(connection, key, request.composition);
192
- connection.run('INSERT OR REPLACE INTO replica_transfers (locator, key, record) VALUES (?, ?, ?)', [request.locator, key, request.record]);
193
- if (request.written !== undefined) {
194
- connection.run('DELETE FROM replica_transfer_parts WHERE locator = ? AND token = ?', [
195
- request.locator,
196
- request.written,
197
- ]);
198
- }
199
- const composition = compositionOf(connection, key);
200
- for (const entry of request.entries) {
201
- putEntry(connection, key, CONFIRMED, entry, true, composition);
202
- }
167
+ newKey(connection, request.locator, request.transfer, request.fingerprint, false);
168
+ recomposed(connection, key, request.fingerprint);
169
+ connection.run('UPDATE replica_keys SET rederiving = coalesce(rederiving, last), searching = coalesce(searching, last) FROM (SELECT coalesce(max(id), 0) AS last FROM replica_entries) WHERE key = ?', [key]);
170
+ const owed = words();
171
+ for (const entry of request.entries)
172
+ placeEntry(connection, key, entry, owed);
203
173
  for (const path of request.folders) {
204
174
  connection.run('INSERT OR REPLACE INTO replica_folders (key, layer, path, present) VALUES (?, ?, ?, 1)', [key, CONFIRMED, path]);
205
175
  }
176
+ wordsWritten(connection, owed);
206
177
  return key;
207
178
  }
208
179
  export function refreshing(connection, request) {
209
- const held = connection.rows('SELECT 1 FROM replica_rooms WHERE key = ? UNION ALL SELECT 1 FROM replica_transfers WHERE key = ?', [request.key, request.key]);
180
+ const held = connection.rows('SELECT 1 FROM replica_keys k WHERE k.key = ? AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)', [request.key]);
210
181
  if (held.length === 0)
211
182
  return null;
212
- const composition = compositionOf(connection, request.key);
183
+ recomposed(connection, request.key, request.composed.fingerprint);
184
+ const composition = request.composed.composition;
185
+ const owed = words();
213
186
  for (const entry of request.entries) {
214
- putEntry(connection, request.key, REFRESHED, entry, true, composition);
187
+ putEntry(connection, request.key, REFRESHED, entry, true, composition, owed);
215
188
  }
216
189
  for (const path of request.removals) {
217
190
  putEntry(connection, request.key, REFRESHED, {
@@ -222,7 +195,7 @@ export function refreshing(connection, request) {
222
195
  fileId: null,
223
196
  change: null,
224
197
  written: null,
225
- }, false, composition);
198
+ }, false, composition, owed);
226
199
  }
227
200
  for (const [list, present] of [
228
201
  [request.folders, 1],
@@ -232,6 +205,7 @@ export function refreshing(connection, request) {
232
205
  connection.run('INSERT OR REPLACE INTO replica_folders (key, layer, path, present) VALUES (?, ?, ?, ?)', [request.key, REFRESHED, path, present]);
233
206
  }
234
207
  }
208
+ wordsWritten(connection, owed);
235
209
  return null;
236
210
  }
237
211
  function dropLayer(connection, key, layer) {
@@ -273,12 +247,11 @@ function applyRefresh(connection, key) {
273
247
  connection.run('DELETE FROM replica_folders WHERE key = ? AND layer = ?', [key, REFRESHED]);
274
248
  return touched;
275
249
  }
250
+ function beneathRoots(path) {
251
+ return `EXISTS (SELECT 1 FROM json_each(?) r WHERE ${path} = r.value OR (${path} >= r.value || '/' AND ${path} < r.value || '0'))`;
252
+ }
276
253
  function counted(connection, key, roots) {
277
- const outside = roots.map(() => '(path = ? OR (path >= ? AND path < ?))').join(' OR ');
278
- const bind = [key];
279
- for (const root of roots)
280
- bind.push(root, `${root}/`, `${root}0`);
281
- const [row] = connection.rows(`SELECT count(*) AS held FROM replica_entries WHERE key = ? AND visible = 1 AND present = 1 AND (binary IS NOT NULL OR substr(path, -3) = '.md')${roots.length === 0 ? '' : ` AND NOT (${outside})`}`, bind);
254
+ const [row] = connection.rows(`SELECT count(*) AS held FROM replica_entries WHERE key = ? AND visible = 1 AND present = 1 AND (binary IS NOT NULL OR substr(path, -3) = '.md') AND NOT ${beneathRoots('replica_entries.path')}`, [key, JSON.stringify(roots)]);
282
255
  return number(row.held);
283
256
  }
284
257
  export function counts(connection, rooms) {
@@ -292,33 +265,32 @@ function erasing(connection, key, locator) {
292
265
  }
293
266
  function commitRoom(connection, room) {
294
267
  const touched = new Set();
295
- if (room.composition !== undefined)
296
- recomposed(connection, room.key, room.composition);
297
- const composition = compositionOf(connection, room.key);
298
- if (room.published !== undefined) {
299
- if (room.published.from !== null && room.published.from !== room.key) {
300
- erasing(connection, room.published.from, room.locator);
301
- }
302
- dropTransfer(connection, room.locator);
268
+ recomposed(connection, room.key, room.composed.fingerprint);
269
+ const composition = room.composed.composition;
270
+ if (room.published !== undefined &&
271
+ room.published.from !== null &&
272
+ room.published.from !== room.key) {
273
+ erasing(connection, room.published.from, room.locator);
303
274
  }
304
275
  if (room.refreshed === true) {
305
276
  for (const path of applyRefresh(connection, room.key))
306
277
  touched.add(path);
307
278
  }
279
+ const owed = words();
308
280
  for (const entry of room.confirmed?.set ?? []) {
309
- putEntry(connection, room.key, CONFIRMED, entry, true, composition);
281
+ putEntry(connection, room.key, CONFIRMED, entry, true, composition, owed);
310
282
  touched.add(entry.path);
311
283
  }
312
284
  for (const path of room.confirmed?.removed ?? []) {
313
- removeEntry(connection, room.key, CONFIRMED, path);
285
+ removeEntry(connection, room.key, CONFIRMED, path, owed);
314
286
  touched.add(path);
315
287
  }
316
288
  for (const entry of room.overlay?.set ?? []) {
317
- putEntry(connection, room.key, OVERLAY, entry, entry.present, composition);
289
+ putEntry(connection, room.key, OVERLAY, entry, entry.present, composition, owed);
318
290
  touched.add(entry.path);
319
291
  }
320
292
  for (const path of room.overlay?.cleared ?? []) {
321
- removeEntry(connection, room.key, OVERLAY, path);
293
+ removeEntry(connection, room.key, OVERLAY, path, owed);
322
294
  touched.add(path);
323
295
  }
324
296
  const changed = [...touched];
@@ -330,6 +302,7 @@ function commitRoom(connection, room) {
330
302
  room.record,
331
303
  ]);
332
304
  }
305
+ wordsWritten(connection, owed);
333
306
  return room.published === undefined ? changed : null;
334
307
  }
335
308
  export function commit(connection, rooms) {
@@ -341,8 +314,11 @@ export function commit(connection, rooms) {
341
314
  counted[room.locator] = counts(connection, [
342
315
  { key: room.key, roots: room.roots ?? rootsOf(connection, room.locator) },
343
316
  ])[0];
344
- const [held] = connection.rows('SELECT rederiving FROM replica_keys WHERE key = ?', [room.key]);
345
- indexed[room.locator] = held !== undefined && held.rederiving === null;
317
+ const [held] = connection.rows('SELECT rederiving, searching FROM replica_keys WHERE key = ?', [
318
+ room.key,
319
+ ]);
320
+ indexed[room.locator] =
321
+ held !== undefined && held.rederiving === null && held.searching === null;
346
322
  }
347
323
  return { counts: counted, changed, indexed };
348
324
  }
@@ -353,27 +329,24 @@ function rootsOf(connection, locator) {
353
329
  }
354
330
  export function fenceRooms(connection, locators) {
355
331
  for (const locator of locators) {
356
- for (const row of connection.rows('SELECT key FROM replica_keys WHERE locator = ? UNION SELECT key FROM replica_rooms WHERE locator = ? UNION SELECT key FROM replica_transfers WHERE locator = ?', [locator, locator, locator])) {
332
+ for (const row of connection.rows('SELECT key FROM replica_keys WHERE locator = ? UNION SELECT key FROM replica_rooms WHERE locator = ?', [locator, locator])) {
357
333
  erasing(connection, number(row.key), locator);
358
334
  }
359
335
  connection.run('DELETE FROM replica_rooms WHERE locator = ?', [locator]);
360
- dropTransfer(connection, locator);
361
336
  }
362
337
  return null;
363
338
  }
364
- export function abandon(connection, locator, kept) {
365
- const [held] = connection.rows('SELECT key FROM replica_transfers WHERE locator = ?', [locator]);
366
- dropTransfer(connection, locator);
367
- if (held === undefined)
368
- return null;
369
- if (!kept)
370
- erasing(connection, number(held.key), locator);
371
- return number(held.key);
339
+ export function abandon(connection, locator) {
340
+ for (const row of connection.rows(`SELECT k.key FROM replica_keys k WHERE k.locator = ?
341
+ AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
342
+ AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)`, [locator])) {
343
+ erasing(connection, number(row.key), locator);
344
+ }
345
+ return null;
372
346
  }
373
347
  export function release(connection, key) {
374
348
  const [held] = connection.rows(`SELECT k.locator FROM replica_keys k WHERE k.key = ?
375
- AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
376
- AND NOT EXISTS (SELECT 1 FROM replica_transfers t WHERE t.key = k.key)`, [key]);
349
+ AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)`, [key]);
377
350
  if (held !== undefined)
378
351
  erasing(connection, key, text(held.locator));
379
352
  return null;
@@ -398,28 +371,59 @@ export function erase(connection, locator, limit) {
398
371
  const [more] = connection.rows(`SELECT 1 FROM replica_erasing${locator === null ? '' : ' WHERE locator = ?'} LIMIT 1`, locator === null ? [] : [locator]);
399
372
  return { remaining: more !== undefined };
400
373
  }
401
- export function rederive(connection, key, limit) {
402
- const [held] = connection.rows('SELECT rederiving FROM replica_keys WHERE key = ?', [key]);
403
- if (held === undefined || held.rederiving === null)
404
- return { done: true };
405
- const after = number(held.rederiving);
406
- const composition = compositionOf(connection, key);
407
- const rows = connection.rows('SELECT id, path, content FROM replica_entries WHERE key = ? AND present = 1 AND id > ? ORDER BY id LIMIT ?', [key, after, limit]);
408
- for (const row of rows) {
409
- const id = number(row.id);
410
- const path = text(row.path);
411
- const made = derived(path, text(row.content), composition);
412
- dropDerived(connection, id);
413
- connection.run('UPDATE replica_entries SET type = ?, declared = ?, queried = ?, fields = ? WHERE id = ?', [made.type, made.declared, flag(made.queried), made.fields, id]);
414
- writeDerived(connection, id, key, made);
415
- }
416
- const last = rows.at(-1);
417
- const done = rows.length < limit || last === undefined;
418
- connection.run('UPDATE replica_keys SET rederiving = ? WHERE key = ?', [
419
- done ? null : number(last.id),
374
+ function cursor(value) {
375
+ return value === null || value === undefined ? null : number(value);
376
+ }
377
+ export function rederive(connection, key, composed, bytes, ms, floor) {
378
+ const started = performance.now();
379
+ recomposed(connection, key, composed.fingerprint);
380
+ const [held] = connection.rows('SELECT rederiving, searching FROM replica_keys WHERE key = ?', [
420
381
  key,
421
382
  ]);
422
- return { done };
383
+ const deriving = cursor(held?.rederiving);
384
+ const searching = cursor(held?.searching);
385
+ if (deriving === null && searching === null)
386
+ return { done: true, text: 0 };
387
+ const composition = composed.composition;
388
+ let last = Math.min(deriving ?? Infinity, searching ?? Infinity);
389
+ let spent = 0;
390
+ let built = 0;
391
+ const owed = words();
392
+ const reached = (done) => {
393
+ wordsWritten(connection, owed);
394
+ const next = (from) => (from === null || done ? null : Math.max(from, last));
395
+ connection.run('UPDATE replica_keys SET rederiving = ?, searching = ? WHERE key = ?', [
396
+ next(deriving),
397
+ next(searching),
398
+ key,
399
+ ]);
400
+ return { done, text: built };
401
+ };
402
+ for (;;) {
403
+ const rows = connection.rows('SELECT id, path, content FROM replica_entries WHERE key = ? AND present = 1 AND id > ? ORDER BY id LIMIT ?', [key, last, DERIVED_ROWS]);
404
+ for (const row of rows) {
405
+ const id = number(row.id);
406
+ const path = text(row.path);
407
+ const content = text(row.content);
408
+ const owedRows = deriving !== null && id > deriving;
409
+ const owedWords = searching !== null && id > searching && content !== null;
410
+ if (owedRows) {
411
+ const made = derived(path, content, composition);
412
+ dropDerived(connection, id);
413
+ writeDerived(connection, id, key, made);
414
+ }
415
+ if (owedWords && described(path, content).queried)
416
+ owed.made.push({ id, path, content });
417
+ last = id;
418
+ built += content?.length ?? 0;
419
+ spent += ENTRY_WEIGHT + (content?.length ?? 0);
420
+ if (spent >= bytes || (spent >= floor && performance.now() - started >= ms)) {
421
+ return reached(false);
422
+ }
423
+ }
424
+ if (rows.length < DERIVED_ROWS)
425
+ return reached(true);
426
+ }
423
427
  }
424
428
  function heldEntry(row) {
425
429
  return {
@@ -434,12 +438,31 @@ function heldEntry(row) {
434
438
  };
435
439
  }
436
440
  const ENTRY_COLUMNS = 'e.path, e.content, e.binary, e.revision, e.file_id, e.change, e.written, e.type';
441
+ function unrefreshed(alias) {
442
+ return `NOT EXISTS (SELECT 1 FROM replica_entries r WHERE r.key = ${alias}.key AND r.layer = ${REFRESHED} AND r.path = ${alias}.path)`;
443
+ }
437
444
  function viewed(view, alias) {
438
445
  if (view === 'visible')
439
446
  return `${alias}.visible = 1 AND ${alias}.present = 1`;
440
447
  if (view === 'confirmed')
441
448
  return `${alias}.layer = ${CONFIRMED}`;
442
- return `${alias}.present = 1 AND (${alias}.layer = ${REFRESHED} OR (${alias}.layer = ${CONFIRMED} AND NOT EXISTS (SELECT 1 FROM replica_entries r WHERE r.key = ${alias}.key AND r.layer = ${REFRESHED} AND r.path = ${alias}.path)))`;
449
+ return `${alias}.present = 1 AND (${alias}.layer = ${REFRESHED} OR (${alias}.layer = ${CONFIRMED} AND ${unrefreshed(alias)}))`;
450
+ }
451
+ function layers(view) {
452
+ if (view !== 'refreshed')
453
+ return [viewed(view, 'e')];
454
+ return [
455
+ `e.layer = ${REFRESHED} AND e.present = 1`,
456
+ `e.layer = ${CONFIRMED} AND e.present = 1 AND ${unrefreshed('e')}`,
457
+ ];
458
+ }
459
+ function pathOrdered(connection, columns, key, view, condition, after, limit) {
460
+ const beyond = after === null ? '' : ' AND e.path > ?';
461
+ const parts = layers(view).map((layer) => `SELECT ${columns} FROM replica_entries e WHERE e.key = ? AND ${layer}${condition}${beyond}`);
462
+ return connection.rows(`${parts.join(' UNION ALL ')} ORDER BY path LIMIT ?`, [
463
+ ...parts.flatMap(() => (after === null ? [key] : [key, after])),
464
+ limit,
465
+ ]);
443
466
  }
444
467
  export function entries(connection, key, view, list) {
445
468
  if (list.length === 0)
@@ -475,7 +498,7 @@ export function walk(connection, key, view, after, bytes) {
475
498
  let spent = 0;
476
499
  let from = after;
477
500
  for (;;) {
478
- const rows = connection.rows(`SELECT ${ENTRY_COLUMNS} FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')}${from === null ? '' : ' AND e.path > ?'} ORDER BY e.path LIMIT 64`, from === null ? [key] : [key, from]);
501
+ const rows = pathOrdered(connection, ENTRY_COLUMNS, key, view, '', from, 64);
479
502
  for (const row of rows) {
480
503
  const entry = heldEntry(row);
481
504
  found.push(entry);
@@ -489,29 +512,13 @@ export function walk(connection, key, view, after, bytes) {
489
512
  }
490
513
  }
491
514
  export function files(connection, key, view, after, limit) {
492
- return connection
493
- .rows(`SELECT e.path, e.revision FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')} AND e.content IS NOT NULL AND e.revision IS NOT NULL${after === null ? '' : ' AND e.path > ?'} ORDER BY e.path LIMIT ?`, after === null ? [key, limit] : [key, after, limit])
494
- .map((row) => ({ path: text(row.path), revision: text(row.revision) }));
515
+ return pathOrdered(connection, 'e.path, e.revision', key, view, ' AND e.content IS NOT NULL AND e.revision IS NOT NULL', after, limit).map((row) => ({ path: text(row.path), revision: text(row.revision) }));
516
+ }
517
+ export function folders(connection, key, view, list) {
518
+ return list.filter((path) => connection.rows(`SELECT 1 FROM replica_folders f WHERE f.key = ? AND f.layer = ${CONFIRMED} AND f.present = 1 AND f.path = ? UNION ALL SELECT 1 FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')} AND e.path >= ? AND e.path < ? LIMIT 1`, [key, path, key, `${path}/`, `${path}0`]).length > 0);
495
519
  }
496
520
  export function typedWithin(connection, key, view, roots, within) {
497
- const inside = roots.map(() => '(e.path = ? OR (e.path >= ? AND e.path < ?))').join(' OR ');
498
- const bind = [key];
499
- for (const root of roots)
500
- bind.push(root, `${root}/`, `${root}0`);
501
- const placed = roots.length === 0 ? (within ? ' AND 0' : '') : ` AND ${within ? '' : 'NOT '}(${inside})`;
502
- return (connection.rows(`SELECT 1 FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')} AND e.declared IS NOT NULL AND e.declared <> 'any'${placed} LIMIT 1`, bind).length > 0);
503
- }
504
- export function transferPart(connection, locator, token) {
505
- const [held] = connection.rows('SELECT part FROM replica_transfer_parts WHERE locator = ? AND token = ?', [locator, token]);
506
- return held?.part instanceof Uint8Array ? held.part : null;
507
- }
508
- export function keepPart(connection, request) {
509
- connection.run('DELETE FROM replica_transfer_parts WHERE locator = ? AND token = ?', [
510
- request.locator,
511
- request.token,
512
- ]);
513
- connection.run('INSERT INTO replica_transfer_parts (locator, token, part) SELECT ?, ?, ? WHERE EXISTS (SELECT 1 FROM replica_transfers WHERE locator = ?)', [request.locator, request.token, request.part, request.locator]);
514
- return null;
521
+ return (connection.rows(`SELECT 1 FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')} AND e.declared IS NOT NULL AND e.declared <> 'any' AND ${within ? '' : 'NOT '}${beneathRoots('e.path')} LIMIT 1`, [key, JSON.stringify(roots)]).length > 0);
515
522
  }
516
523
  export function opened(connection) {
517
524
  const facts = {};
@@ -524,18 +531,14 @@ export function opened(connection) {
524
531
  key: number(row.key),
525
532
  record: text(row.record),
526
533
  })),
527
- transfers: connection.rows('SELECT locator, key, record FROM replica_transfers').map((row) => ({
528
- locator: text(row.locator),
529
- key: number(row.key),
530
- record: text(row.record),
531
- })),
532
534
  keys: connection
533
- .rows('SELECT key, locator, transfer, rederiving FROM replica_keys')
535
+ .rows('SELECT key, locator, transfer, rederiving, searching FROM replica_keys')
534
536
  .map((row) => ({
535
537
  key: number(row.key),
536
538
  locator: text(row.locator),
537
539
  transfer: text(row.transfer),
538
- rederiving: row.rederiving === null ? null : number(row.rederiving),
540
+ rederiving: cursor(row.rederiving),
541
+ searching: cursor(row.searching),
539
542
  })),
540
543
  facts,
541
544
  presentation: connection.rows('SELECT app, value FROM replica_presentation').map((row) => ({
@@ -548,7 +551,6 @@ export function opened(connection) {
548
551
  orphans: connection
549
552
  .rows(`SELECT k.key FROM replica_keys k
550
553
  WHERE NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
551
- AND NOT EXISTS (SELECT 1 FROM replica_transfers t WHERE t.key = k.key)
552
554
  AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)`)
553
555
  .map((row) => number(row.key)),
554
556
  erasing: connection.rows('SELECT key, locator FROM replica_erasing').map((row) => ({