@agentshouse/mdruntime 0.1.0-ccbb89b0ca151 → 0.1.0-ccc0e3407f107

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