@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca8707258fdde

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 (220) hide show
  1. package/README.md +2 -2
  2. package/dist/acceptance.d.ts +26 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +384 -0
  5. package/dist/adapter.d.ts +102 -70
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/attached-model.d.ts +3 -10
  8. package/dist/attached-model.d.ts.map +1 -1
  9. package/dist/attached-model.js +29 -47
  10. package/dist/bootstrap.d.ts +16 -14
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +272 -129
  13. package/dist/chain.d.ts +2 -1
  14. package/dist/chain.d.ts.map +1 -1
  15. package/dist/change.d.ts +1 -0
  16. package/dist/change.d.ts.map +1 -1
  17. package/dist/chat-types.d.ts +3 -0
  18. package/dist/chat-types.d.ts.map +1 -0
  19. package/dist/chat-types.js +2 -0
  20. package/dist/client.d.ts +20 -10
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +55 -6
  23. package/dist/collection.d.ts +7 -4
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/collection.js +23 -17
  26. package/dist/compile.d.ts +6 -6
  27. package/dist/compile.d.ts.map +1 -1
  28. package/dist/compile.js +278 -282
  29. package/dist/connection.d.ts +22 -44
  30. package/dist/connection.d.ts.map +1 -1
  31. package/dist/connection.js +1600 -3577
  32. package/dist/context.d.ts +2 -0
  33. package/dist/context.d.ts.map +1 -0
  34. package/dist/context.js +7 -0
  35. package/dist/conversation-row.d.ts +33 -0
  36. package/dist/conversation-row.d.ts.map +1 -0
  37. package/dist/conversation-row.js +47 -0
  38. package/dist/conversation.d.ts +27 -18
  39. package/dist/conversation.d.ts.map +1 -1
  40. package/dist/conversation.js +484 -258
  41. package/dist/durability.d.ts +6 -1
  42. package/dist/durability.d.ts.map +1 -1
  43. package/dist/entry.d.ts +3 -2
  44. package/dist/entry.d.ts.map +1 -1
  45. package/dist/entry.js +18 -8
  46. package/dist/figures.d.ts +48 -0
  47. package/dist/figures.d.ts.map +1 -0
  48. package/dist/figures.js +197 -0
  49. package/dist/file.d.ts +2 -11
  50. package/dist/file.d.ts.map +1 -1
  51. package/dist/file.js +6 -4
  52. package/dist/host.d.ts +3 -7
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +29 -32
  55. package/dist/identity.d.ts +0 -1
  56. package/dist/identity.d.ts.map +1 -1
  57. package/dist/identity.js +0 -3
  58. package/dist/index.d.ts +20 -10
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +6 -1
  61. package/dist/install.d.ts +86 -0
  62. package/dist/install.d.ts.map +1 -0
  63. package/dist/install.js +537 -0
  64. package/dist/job.d.ts +33 -0
  65. package/dist/job.d.ts.map +1 -0
  66. package/dist/job.js +54 -0
  67. package/dist/live.d.ts +5 -4
  68. package/dist/live.d.ts.map +1 -1
  69. package/dist/live.js +62 -20
  70. package/dist/log.d.ts +1 -0
  71. package/dist/log.d.ts.map +1 -1
  72. package/dist/media.d.ts +8 -0
  73. package/dist/media.d.ts.map +1 -0
  74. package/dist/media.js +218 -0
  75. package/dist/memory.d.ts +12 -5
  76. package/dist/memory.d.ts.map +1 -1
  77. package/dist/memory.js +216 -85
  78. package/dist/messages.d.ts +27 -3
  79. package/dist/messages.d.ts.map +1 -1
  80. package/dist/messages.js +97 -19
  81. package/dist/model.d.ts +8 -10
  82. package/dist/model.d.ts.map +1 -1
  83. package/dist/model.js +5 -17
  84. package/dist/observation.d.ts +3 -2
  85. package/dist/observation.d.ts.map +1 -1
  86. package/dist/observation.js +8 -3
  87. package/dist/outcome.d.ts +14 -5
  88. package/dist/outcome.d.ts.map +1 -1
  89. package/dist/outcome.js +19 -2
  90. package/dist/overlay.d.ts +1 -0
  91. package/dist/overlay.d.ts.map +1 -1
  92. package/dist/overlay.js +11 -0
  93. package/dist/parts.d.ts +59 -0
  94. package/dist/parts.d.ts.map +1 -0
  95. package/dist/parts.js +118 -0
  96. package/dist/paths.d.ts +1 -0
  97. package/dist/paths.d.ts.map +1 -1
  98. package/dist/paths.js +4 -0
  99. package/dist/query.d.ts +18 -207
  100. package/dist/query.d.ts.map +1 -1
  101. package/dist/query.js +3 -505
  102. package/dist/questions.d.ts +32 -0
  103. package/dist/questions.d.ts.map +1 -0
  104. package/dist/questions.js +118 -0
  105. package/dist/reach.d.ts +18 -11
  106. package/dist/reach.d.ts.map +1 -1
  107. package/dist/reach.js +206 -126
  108. package/dist/react.d.ts +5 -2
  109. package/dist/react.d.ts.map +1 -1
  110. package/dist/read.d.ts +7 -0
  111. package/dist/read.d.ts.map +1 -0
  112. package/dist/read.js +17 -0
  113. package/dist/readiness.d.ts +3 -17
  114. package/dist/readiness.d.ts.map +1 -1
  115. package/dist/readiness.js +4 -10
  116. package/dist/reads.d.ts +34 -10
  117. package/dist/reads.d.ts.map +1 -1
  118. package/dist/reads.js +114 -86
  119. package/dist/recovery.d.ts +1 -0
  120. package/dist/recovery.d.ts.map +1 -1
  121. package/dist/recovery.js +35 -1
  122. package/dist/refresh.d.ts +7 -5
  123. package/dist/refresh.d.ts.map +1 -1
  124. package/dist/refresh.js +33 -5
  125. package/dist/replica.d.ts +10 -11
  126. package/dist/replica.d.ts.map +1 -1
  127. package/dist/replica.js +52 -20
  128. package/dist/room-draft.d.ts +21 -5
  129. package/dist/room-draft.d.ts.map +1 -1
  130. package/dist/room-draft.js +98 -51
  131. package/dist/room-drafts.d.ts +29 -0
  132. package/dist/room-drafts.d.ts.map +1 -0
  133. package/dist/room-drafts.js +216 -0
  134. package/dist/rows.d.ts +18 -16
  135. package/dist/rows.d.ts.map +1 -1
  136. package/dist/rows.js +45 -153
  137. package/dist/save.d.ts +2 -0
  138. package/dist/save.d.ts.map +1 -1
  139. package/dist/save.js +22 -13
  140. package/dist/saves.d.ts +96 -0
  141. package/dist/saves.d.ts.map +1 -0
  142. package/dist/saves.js +999 -0
  143. package/dist/search.d.ts +12 -63
  144. package/dist/search.d.ts.map +1 -1
  145. package/dist/search.js +9 -52
  146. package/dist/seed.d.ts +16 -2
  147. package/dist/seed.d.ts.map +1 -1
  148. package/dist/seed.js +66 -0
  149. package/dist/shared.d.ts +0 -1
  150. package/dist/shared.d.ts.map +1 -1
  151. package/dist/shared.js +0 -10
  152. package/dist/socket/admission.d.ts +7 -0
  153. package/dist/socket/admission.d.ts.map +1 -0
  154. package/dist/socket/admission.js +39 -0
  155. package/dist/socket/answers.d.ts +6 -0
  156. package/dist/socket/answers.d.ts.map +1 -0
  157. package/dist/socket/answers.js +20 -0
  158. package/dist/socket/reads.d.ts +10 -0
  159. package/dist/socket/reads.d.ts.map +1 -0
  160. package/dist/socket/reads.js +31 -0
  161. package/dist/socket.d.ts +43 -0
  162. package/dist/socket.d.ts.map +1 -0
  163. package/dist/socket.js +96 -0
  164. package/dist/source.d.ts +61 -0
  165. package/dist/source.d.ts.map +1 -0
  166. package/dist/source.js +81 -0
  167. package/dist/status.d.ts +4 -5
  168. package/dist/status.d.ts.map +1 -1
  169. package/dist/status.js +3 -7
  170. package/dist/store/fences.d.ts +16 -0
  171. package/dist/store/fences.d.ts.map +1 -0
  172. package/dist/store/fences.js +26 -0
  173. package/dist/store/installs.d.ts +30 -0
  174. package/dist/store/installs.d.ts.map +1 -0
  175. package/dist/store/installs.js +34 -0
  176. package/dist/store/records.d.ts +212 -0
  177. package/dist/store/records.d.ts.map +1 -0
  178. package/dist/store/records.js +408 -0
  179. package/dist/store/room-held.d.ts +8 -0
  180. package/dist/store/room-held.d.ts.map +1 -0
  181. package/dist/store/room-held.js +17 -0
  182. package/dist/store/watchers.d.ts +15 -0
  183. package/dist/store/watchers.d.ts.map +1 -0
  184. package/dist/store/watchers.js +36 -0
  185. package/dist/store/writes.d.ts +24 -0
  186. package/dist/store/writes.d.ts.map +1 -0
  187. package/dist/store/writes.js +125 -0
  188. package/dist/store.d.ts +76 -158
  189. package/dist/store.d.ts.map +1 -1
  190. package/dist/store.js +117 -403
  191. package/dist/stored.d.ts +66 -55
  192. package/dist/stored.d.ts.map +1 -1
  193. package/dist/stored.js +343 -273
  194. package/dist/tables.d.ts +118 -65
  195. package/dist/tables.d.ts.map +1 -1
  196. package/dist/tables.js +199 -99
  197. package/dist/tallies.d.ts +18 -0
  198. package/dist/tallies.d.ts.map +1 -0
  199. package/dist/tallies.js +129 -0
  200. package/dist/transfer.d.ts +0 -5
  201. package/dist/transfer.d.ts.map +1 -1
  202. package/dist/validation.d.ts +3 -4
  203. package/dist/validation.d.ts.map +1 -1
  204. package/dist/validation.js +42 -100
  205. package/dist/windows.d.ts +1 -12
  206. package/dist/windows.d.ts.map +1 -1
  207. package/dist/windows.js +2 -75
  208. package/dist/wire.d.ts +23 -8
  209. package/dist/wire.d.ts.map +1 -1
  210. package/dist/wire.js +8 -2
  211. package/package.json +2 -2
  212. package/dist/fence.d.ts +0 -6
  213. package/dist/fence.d.ts.map +0 -1
  214. package/dist/fence.js +0 -10
  215. package/dist/fulltext.d.ts +0 -27
  216. package/dist/fulltext.d.ts.map +0 -1
  217. package/dist/fulltext.js +0 -368
  218. package/dist/words.d.ts +0 -19
  219. package/dist/words.d.ts.map +0 -1
  220. package/dist/words.js +0 -150
package/dist/stored.js CHANGED
@@ -1,30 +1,37 @@
1
- import { indexDocument, unindexAll, unindexDocuments } from './fulltext.js';
2
- import { derived } from './rows.js';
1
+ import { instantKey, 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)',
5
- '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)',
5
+ 'CREATE TABLE replica_keys (key INTEGER PRIMARY KEY AUTOINCREMENT, locator TEXT NOT NULL, transfer TEXT NOT NULL, fingerprint TEXT NOT NULL, rederiving INTEGER)',
6
+ 'CREATE TABLE replica_rooms (locator TEXT PRIMARY KEY, key INTEGER NOT NULL, record TEXT NOT NULL, documents INTEGER NOT NULL DEFAULT 0) WITHOUT ROWID',
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, part TEXT, created TEXT, created_instant TEXT, changed_instant TEXT, bytes INTEGER GENERATED ALWAYS AS (CASE WHEN binary IS NULL THEN length(CAST(content AS BLOB)) ELSE binary ->> '$.byteLength' END) STORED, 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, 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)',
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',
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
+ '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
+ 'CREATE INDEX replica_links_target ON replica_links (key, target, kind, keys)',
17
+ 'CREATE TABLE replica_folders (key INTEGER NOT NULL, layer INTEGER NOT NULL, path TEXT NOT NULL, present INTEGER NOT NULL, part TEXT, 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
  ];
24
22
  const CONFIRMED = 0;
25
23
  const OVERLAY = 1;
26
24
  const REFRESHED = 2;
27
- const compositions = new WeakMap();
25
+ const DERIVED_ROWS = 64;
26
+ const ENTRY_WEIGHT = 256;
27
+ const ABSENT = {
28
+ type: null,
29
+ declared: null,
30
+ queried: false,
31
+ folder: '',
32
+ fields: null,
33
+ links: null,
34
+ };
28
35
  function text(value) {
29
36
  return typeof value === 'string' ? value : null;
30
37
  }
@@ -40,55 +47,23 @@ function flag(value) {
40
47
  function paths(list) {
41
48
  return JSON.stringify(list);
42
49
  }
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
50
  function dropDerived(connection, doc) {
72
51
  connection.run('DELETE FROM replica_values WHERE doc = ?', [doc]);
73
52
  connection.run('DELETE FROM replica_links WHERE doc = ?', [doc]);
74
53
  }
75
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?)', [
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);
59
+ }
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(?)', [
78
62
  doc,
79
63
  key,
80
- value.form,
81
- value.held,
82
- value.item,
83
- value.kind,
84
- value.value,
85
- value.size,
86
- flag(value.single),
64
+ JSON.stringify(made.links.map((link) => [link.kind, link.keys, link.places, link.target, link.field])),
87
65
  ]);
88
66
  }
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
67
  }
93
68
  function entryId(connection, key, layer, path) {
94
69
  const [row] = connection.rows('SELECT id FROM replica_entries WHERE key = ? AND layer = ? AND path = ?', [key, layer, path]);
@@ -99,26 +74,10 @@ function removeEntry(connection, key, layer, path) {
99
74
  if (id === null)
100
75
  return;
101
76
  dropDerived(connection, id);
102
- unindexDocuments(connection, [id]);
103
77
  connection.run('DELETE FROM replica_entries WHERE id = ?', [id]);
104
78
  }
105
- function heldIds(connection, sql, bind) {
106
- return connection.rows(sql, bind).map((row) => number(row.id));
107
- }
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
- };
121
- connection.run('INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, binary, revision, file_id, change, written, type, declared, folder, fields) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
79
+ function insertEntry(connection, key, layer, entry, present, made, part = null) {
80
+ connection.run('INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, binary, revision, file_id, created, created_instant, change, changed_instant, written, type, declared, folder, fields, links, part) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
122
81
  key,
123
82
  layer,
124
83
  entry.path,
@@ -129,87 +88,143 @@ function putEntry(connection, key, layer, entry, present, composition, searched
129
88
  entry.binary === null ? null : JSON.stringify(entry.binary),
130
89
  entry.revision,
131
90
  entry.fileId,
91
+ entry.created,
92
+ instantKey(entry.created),
132
93
  entry.change === null ? null : JSON.stringify(entry.change),
94
+ instantKey(entry.change?.time),
133
95
  entry.written,
134
96
  made.type,
135
97
  made.declared,
136
98
  made.folder,
137
99
  made.fields,
100
+ made.links,
101
+ part,
138
102
  ]);
103
+ }
104
+ function textOf(entry) {
105
+ return entry.binary === null ? entry.content : null;
106
+ }
107
+ function putEntry(connection, key, layer, entry, present, composition) {
108
+ removeEntry(connection, key, layer, entry.path);
109
+ const content = textOf(entry);
110
+ insertEntry(connection, key, layer, entry, present, present ? described(entry.path, content, entry.facts) : ABSENT);
139
111
  if (!present)
140
112
  return;
141
113
  const id = entryId(connection, key, layer, entry.path);
142
- 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
- }
114
+ writeDerived(connection, id, key, entry.path, heldOf(entry.path, content, entry.facts), composition);
115
+ }
116
+ function placeEntry(connection, key, entry, part) {
117
+ removeEntry(connection, key, CONFIRMED, entry.path);
118
+ insertEntry(connection, key, CONFIRMED, entry, true, described(entry.path, textOf(entry), entry.facts), part);
146
119
  }
147
120
  function revealed(connection, key, touched) {
148
121
  if (touched.length === 0)
149
122
  return;
150
123
  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
124
  }
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)]);
125
+ function newKey(connection, locator, transfer, fingerprint, built) {
126
+ const owed = built ? null : 0;
127
+ connection.run('INSERT INTO replica_keys (locator, transfer, fingerprint, rederiving) VALUES (?, ?, ?, ?)', [locator, transfer, fingerprint, owed]);
154
128
  const [row] = connection.rows('SELECT last_insert_rowid() AS key');
155
129
  return number(row.key);
156
130
  }
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
- ]);
131
+ function recomposed(connection, key, fingerprint) {
132
+ const [row] = connection.rows('SELECT fingerprint FROM replica_keys WHERE key = ?', [key]);
133
+ if (row === undefined || text(row.fingerprint) === fingerprint)
166
134
  return;
167
- }
168
- connection.run('UPDATE replica_keys SET composition = ?, rederiving = 0 WHERE key = ?', [
169
- JSON.stringify(composition),
170
- key,
171
- ]);
135
+ const held = connection.rows('SELECT 1 FROM replica_entries WHERE key = ? LIMIT 1', [key]);
136
+ connection.run(`UPDATE replica_keys SET fingerprint = ?${held.length === 0 ? '' : ', rederiving = 0'} WHERE key = ?`, [fingerprint, key]);
172
137
  }
173
138
  export function entered(connection, locator, composition, rows) {
174
- const key = newKey(connection, locator, '', composition);
139
+ const key = newKey(connection, locator, '', composedOf(composition).fingerprint, true);
175
140
  for (const entry of rows)
176
- putEntry(connection, key, CONFIRMED, entry, true, composition, false);
141
+ putEntry(connection, key, CONFIRMED, entry, true, composition);
177
142
  return key;
178
143
  }
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
144
  export function stage(connection, request) {
184
- const [held] = connection.rows('SELECT key FROM replica_transfers WHERE locator = ?', [
185
- request.locator,
186
- ]);
187
- 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);
145
+ if (request.key === null) {
146
+ for (const row of connection.rows(STAGED_KEYS, [request.locator])) {
147
+ erasing(connection, number(row.key), request.locator);
148
+ }
202
149
  }
150
+ const key = request.key ??
151
+ newKey(connection, request.locator, request.transfer, request.fingerprint, false);
152
+ recomposed(connection, key, request.fingerprint);
153
+ owing(connection, key);
154
+ for (const entry of request.entries)
155
+ placeEntry(connection, key, entry, request.part);
203
156
  for (const path of request.folders) {
204
- connection.run('INSERT OR REPLACE INTO replica_folders (key, layer, path, present) VALUES (?, ?, ?, 1)', [key, CONFIRMED, path]);
157
+ connection.run('INSERT OR REPLACE INTO replica_folders (key, layer, path, present, part) VALUES (?, ?, ?, 1, ?)', [key, CONFIRMED, path, request.part]);
205
158
  }
206
159
  return key;
207
160
  }
161
+ function owing(connection, key) {
162
+ connection.run('UPDATE replica_keys SET rederiving = coalesce(rederiving, last) FROM (SELECT coalesce(max(id), 0) AS last FROM replica_entries) WHERE key = ?', [key]);
163
+ }
164
+ const STAGED_KEYS = `SELECT k.key, k.transfer FROM replica_keys k WHERE k.locator = ?
165
+ AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
166
+ AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)
167
+ ORDER BY k.key DESC`;
168
+ const UNPARTED = `EXISTS (SELECT 1 FROM replica_entries e WHERE e.key = k.key AND e.part IS NULL)
169
+ OR EXISTS (SELECT 1 FROM replica_folders f WHERE f.key = k.key AND f.part IS NULL)`;
170
+ function partsOf(connection, key, layer) {
171
+ const scoped = layer === null ? '' : ` AND layer = ${layer}`;
172
+ return new Set(connection
173
+ .rows(`SELECT part FROM replica_entries WHERE key = ?${scoped} AND part IS NOT NULL UNION SELECT part FROM replica_folders WHERE key = ?${scoped} AND part IS NOT NULL`, [key, key])
174
+ .map((row) => text(row.part)));
175
+ }
176
+ function resumable(connection, locator) {
177
+ let kept = null;
178
+ for (const row of connection.rows(STAGED_KEYS, [locator])) {
179
+ const key = number(row.key);
180
+ const parted = kept === null &&
181
+ connection.rows(`SELECT 1 FROM replica_keys k WHERE k.key = ? AND NOT (${UNPARTED})`, [key])
182
+ .length > 0;
183
+ if (parted)
184
+ kept = { key, transfer: text(row.transfer) };
185
+ else
186
+ erasing(connection, key, locator);
187
+ }
188
+ return kept;
189
+ }
190
+ export function resume(connection, request) {
191
+ const wanted = new Set(request.parts);
192
+ const held = resumable(connection, request.locator);
193
+ const staged = held === null ? new Set() : partsOf(connection, held.key, null);
194
+ const dropped = [...staged].filter((part) => !wanted.has(part));
195
+ if (held !== null && dropped.length > 0) {
196
+ const named = JSON.stringify(dropped);
197
+ const rows = `SELECT id FROM replica_entries WHERE key = ? AND part IN (SELECT value FROM json_each(?))`;
198
+ for (const table of ['replica_values', 'replica_links']) {
199
+ connection.run(`DELETE FROM ${table} WHERE doc IN (${rows})`, [held.key, named]);
200
+ }
201
+ connection.run(`DELETE FROM replica_entries WHERE id IN (${rows})`, [held.key, named]);
202
+ connection.run('DELETE FROM replica_folders WHERE key = ? AND part IN (SELECT value FROM json_each(?))', [held.key, named]);
203
+ }
204
+ const kept = [...staged].filter((part) => wanted.has(part));
205
+ const publishedParts = request.published === null
206
+ ? new Set()
207
+ : partsOf(connection, request.published, CONFIRMED);
208
+ const copied = request.parts.filter((part) => !staged.has(part) && publishedParts.has(part));
209
+ if (held === null && copied.length === 0) {
210
+ return { key: null, transfer: request.transfer, staged: [] };
211
+ }
212
+ const key = held?.key ?? newKey(connection, request.locator, request.transfer, request.fingerprint, false);
213
+ recomposed(connection, key, request.fingerprint);
214
+ if (copied.length > 0 && request.published !== null) {
215
+ owing(connection, key);
216
+ const named = JSON.stringify(copied);
217
+ connection.run(`INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, binary, revision, file_id, created, created_instant, change, changed_instant, written, type, declared, folder, fields, links, part) SELECT ?, ${CONFIRMED}, path, 1, 1, queried, content, binary, revision, file_id, created, created_instant, change, changed_instant, written, type, declared, folder, fields, links, part FROM replica_entries WHERE key = ? AND layer = ${CONFIRMED} AND present = 1 AND part IN (SELECT value FROM json_each(?)) ORDER BY path`, [key, request.published, named]);
218
+ connection.run(`INSERT OR REPLACE INTO replica_folders (key, layer, path, present, part) SELECT ?, ${CONFIRMED}, path, 1, part FROM replica_folders WHERE key = ? AND layer = ${CONFIRMED} AND present = 1 AND part IN (SELECT value FROM json_each(?))`, [key, request.published, named]);
219
+ }
220
+ return { key, transfer: held?.transfer ?? request.transfer, staged: [...kept, ...copied] };
221
+ }
208
222
  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]);
223
+ 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
224
  if (held.length === 0)
211
225
  return null;
212
- const composition = compositionOf(connection, request.key);
226
+ recomposed(connection, request.key, request.composed.fingerprint);
227
+ const composition = request.composed.composition;
213
228
  for (const entry of request.entries) {
214
229
  putEntry(connection, request.key, REFRESHED, entry, true, composition);
215
230
  }
@@ -220,8 +235,10 @@ export function refreshing(connection, request) {
220
235
  binary: null,
221
236
  revision: null,
222
237
  fileId: null,
238
+ created: null,
223
239
  change: null,
224
240
  written: null,
241
+ facts: null,
225
242
  }, false, composition);
226
243
  }
227
244
  for (const [list, present] of [
@@ -235,7 +252,6 @@ export function refreshing(connection, request) {
235
252
  return null;
236
253
  }
237
254
  function dropLayer(connection, key, layer) {
238
- unindexDocuments(connection, heldIds(connection, 'SELECT id FROM replica_entries WHERE key = ? AND layer = ?', [key, layer]));
239
255
  for (const table of ['replica_values', 'replica_links']) {
240
256
  connection.run(`DELETE FROM ${table} WHERE doc IN (SELECT id FROM replica_entries WHERE key = ? AND layer = ?)`, [key, layer]);
241
257
  }
@@ -254,7 +270,6 @@ function refreshedPaths(connection, key) {
254
270
  function applyRefresh(connection, key) {
255
271
  const touched = refreshedPaths(connection, key);
256
272
  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}`;
257
- unindexDocuments(connection, heldIds(connection, replaced, [key]));
258
273
  for (const table of ['replica_values', 'replica_links']) {
259
274
  connection.run(`DELETE FROM ${table} WHERE doc IN (${replaced})`, [key]);
260
275
  }
@@ -273,33 +288,33 @@ function applyRefresh(connection, key) {
273
288
  connection.run('DELETE FROM replica_folders WHERE key = ? AND layer = ?', [key, REFRESHED]);
274
289
  return touched;
275
290
  }
291
+ function beneathRoots(path) {
292
+ return `EXISTS (SELECT 1 FROM json_each(?) r WHERE ${path} = r.value OR (${path} >= r.value || '/' AND ${path} < r.value || '0'))`;
293
+ }
276
294
  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);
295
+ 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
296
  return number(row.held);
283
297
  }
284
- export function counts(connection, rooms) {
285
- return rooms.map(({ key, roots }) => counted(connection, key, roots));
286
- }
287
298
  function erasing(connection, key, locator) {
288
299
  connection.run('INSERT OR REPLACE INTO replica_erasing (key, locator) VALUES (?, ?)', [
289
300
  key,
290
301
  locator,
291
302
  ]);
292
303
  }
304
+ function saved(connection, key, entry, at) {
305
+ if (!entry.present || entry.created !== null)
306
+ return entry;
307
+ const [held] = connection.rows(`SELECT created FROM replica_entries WHERE key = ? AND layer = ${OVERLAY} AND path = ? AND present = 1`, [key, entry.path]);
308
+ return { ...entry, created: text(held?.created) ?? at };
309
+ }
293
310
  function commitRoom(connection, room) {
294
311
  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);
312
+ recomposed(connection, room.key, room.composed.fingerprint);
313
+ const composition = room.composed.composition;
314
+ if (room.published !== undefined &&
315
+ room.published.from !== null &&
316
+ room.published.from !== room.key) {
317
+ erasing(connection, room.published.from, room.locator);
303
318
  }
304
319
  if (room.refreshed === true) {
305
320
  for (const path of applyRefresh(connection, room.key))
@@ -313,8 +328,9 @@ function commitRoom(connection, room) {
313
328
  removeEntry(connection, room.key, CONFIRMED, path);
314
329
  touched.add(path);
315
330
  }
331
+ const savedAt = new Date().toISOString();
316
332
  for (const entry of room.overlay?.set ?? []) {
317
- putEntry(connection, room.key, OVERLAY, entry, entry.present, composition);
333
+ putEntry(connection, room.key, OVERLAY, saved(connection, room.key, entry, savedAt), entry.present, composition);
318
334
  touched.add(entry.path);
319
335
  }
320
336
  for (const path of room.overlay?.cleared ?? []) {
@@ -324,27 +340,44 @@ function commitRoom(connection, room) {
324
340
  const changed = [...touched];
325
341
  revealed(connection, room.key, changed);
326
342
  if (room.record !== undefined) {
327
- connection.run('INSERT OR REPLACE INTO replica_rooms (locator, key, record) VALUES (?, ?, ?)', [
328
- room.locator,
329
- room.key,
330
- room.record,
331
- ]);
343
+ connection.run(KEEP_MODEL, [room.record, room.record, room.record, room.record]);
344
+ connection.run(`INSERT OR REPLACE INTO replica_rooms (locator, key, record) VALUES (?, ?, ${MODEL_LEFT})`, [room.locator, room.key, room.record, room.record, room.record]);
332
345
  }
333
346
  return room.published === undefined ? changed : null;
334
347
  }
348
+ const SCHEMA = '$.model.snapshot.schema';
349
+ const KEEP_MODEL = `INSERT OR IGNORE INTO replica_models (identity, version, schema)
350
+ SELECT json_extract(?, '$.model.snapshot.identity'), json_extract(?, '$.model.snapshot.version'),
351
+ json_extract(?, '${SCHEMA}')
352
+ WHERE json_type(?, '${SCHEMA}') = 'text'`;
353
+ const MODEL_LEFT = `CASE WHEN json_type(?, '${SCHEMA}') = 'text' THEN json_set(?, '${SCHEMA}', json('null')) ELSE ? END`;
354
+ const UNHELD_MODELS = `DELETE FROM replica_models WHERE NOT EXISTS (
355
+ SELECT 1 FROM replica_rooms r
356
+ WHERE json_extract(r.record, '$.model.snapshot.identity') = replica_models.identity
357
+ AND json_extract(r.record, '$.model.snapshot.version') = replica_models.version
358
+ )`;
359
+ const HELD_RECORDS = `SELECT r.locator, r.key, CASE WHEN m.schema IS NULL THEN r.record
360
+ ELSE json_set(r.record, '${SCHEMA}', m.schema) END AS record
361
+ FROM replica_rooms r
362
+ LEFT JOIN replica_models m
363
+ ON m.identity = json_extract(r.record, '$.model.snapshot.identity')
364
+ AND m.version = json_extract(r.record, '$.model.snapshot.version')`;
335
365
  export function commit(connection, rooms) {
336
- const counted = {};
337
366
  const changed = {};
338
367
  const indexed = {};
339
368
  for (const room of rooms) {
340
369
  changed[room.locator] = commitRoom(connection, room);
341
- counted[room.locator] = counts(connection, [
342
- { key: room.key, roots: room.roots ?? rootsOf(connection, room.locator) },
343
- ])[0];
370
+ connection.run('UPDATE replica_rooms SET documents = ? WHERE locator = ? AND key = ?', [
371
+ counted(connection, room.key, room.roots ?? rootsOf(connection, room.locator)),
372
+ room.locator,
373
+ room.key,
374
+ ]);
344
375
  const [held] = connection.rows('SELECT rederiving FROM replica_keys WHERE key = ?', [room.key]);
345
376
  indexed[room.locator] = held !== undefined && held.rederiving === null;
346
377
  }
347
- return { counts: counted, changed, indexed };
378
+ if (rooms.some((room) => room.record !== undefined))
379
+ connection.run(UNHELD_MODELS);
380
+ return { changed, indexed };
348
381
  }
349
382
  function rootsOf(connection, locator) {
350
383
  const [row] = connection.rows('SELECT record FROM replica_rooms WHERE locator = ?', [locator]);
@@ -353,27 +386,25 @@ function rootsOf(connection, locator) {
353
386
  }
354
387
  export function fenceRooms(connection, locators) {
355
388
  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])) {
389
+ for (const row of connection.rows('SELECT key FROM replica_keys WHERE locator = ? UNION SELECT key FROM replica_rooms WHERE locator = ?', [locator, locator])) {
357
390
  erasing(connection, number(row.key), locator);
358
391
  }
359
392
  connection.run('DELETE FROM replica_rooms WHERE locator = ?', [locator]);
360
- dropTransfer(connection, locator);
361
393
  }
394
+ connection.run(UNHELD_MODELS);
362
395
  return null;
363
396
  }
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);
397
+ export function abandon(connection, locator) {
398
+ for (const row of connection.rows(`SELECT k.key FROM replica_keys k WHERE k.locator = ?
399
+ AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
400
+ AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)`, [locator])) {
401
+ erasing(connection, number(row.key), locator);
402
+ }
403
+ return null;
372
404
  }
373
405
  export function release(connection, key) {
374
406
  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]);
407
+ AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)`, [key]);
377
408
  if (held !== undefined)
378
409
  erasing(connection, key, text(held.locator));
379
410
  return null;
@@ -384,7 +415,6 @@ export function erase(connection, locator, limit) {
384
415
  return { remaining: false };
385
416
  const key = number(held.key);
386
417
  const batch = 'SELECT id FROM replica_entries WHERE key = ? ORDER BY id LIMIT ?';
387
- unindexDocuments(connection, heldIds(connection, batch, [key, limit]));
388
418
  for (const table of ['replica_values', 'replica_links']) {
389
419
  connection.run(`DELETE FROM ${table} WHERE doc IN (${batch})`, [key, limit]);
390
420
  }
@@ -398,28 +428,46 @@ export function erase(connection, locator, limit) {
398
428
  const [more] = connection.rows(`SELECT 1 FROM replica_erasing${locator === null ? '' : ' WHERE locator = ?'} LIMIT 1`, locator === null ? [] : [locator]);
399
429
  return { remaining: more !== undefined };
400
430
  }
401
- export function rederive(connection, key, limit) {
431
+ function cursor(value) {
432
+ return value === null || value === undefined ? null : number(value);
433
+ }
434
+ export function rederive(connection, key, composed, bytes, ms, floor) {
435
+ const started = performance.now();
436
+ recomposed(connection, key, composed.fingerprint);
402
437
  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);
438
+ const deriving = cursor(held?.rederiving);
439
+ if (deriving === null)
440
+ return { done: true, text: 0 };
441
+ const composition = composed.composition;
442
+ let last = deriving;
443
+ let spent = 0;
444
+ let built = 0;
445
+ let read = false;
446
+ reading: for (;;) {
447
+ 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]);
448
+ for (const row of rows) {
449
+ const id = number(row.id);
450
+ const path = text(row.path);
451
+ const size = row.size === null ? 0 : number(row.size);
452
+ dropDerived(connection, id);
453
+ writeDerived(connection, id, key, path, {
454
+ type: text(row.type),
455
+ fields: json(row.fields),
456
+ links: json(row.links) ?? [],
457
+ }, composition);
458
+ built += size;
459
+ last = id;
460
+ spent += ENTRY_WEIGHT + size;
461
+ if (spent >= bytes || (spent >= floor && performance.now() - started >= ms))
462
+ break reading;
463
+ }
464
+ if (rows.length < DERIVED_ROWS) {
465
+ read = true;
466
+ break;
467
+ }
415
468
  }
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),
420
- key,
421
- ]);
422
- return { done };
469
+ connection.run('UPDATE replica_keys SET rederiving = ? WHERE key = ?', [read ? null : last, key]);
470
+ return { done: read, text: built };
423
471
  }
424
472
  function heldEntry(row) {
425
473
  return {
@@ -428,18 +476,33 @@ function heldEntry(row) {
428
476
  binary: json(row.binary),
429
477
  revision: text(row.revision),
430
478
  fileId: text(row.file_id),
479
+ created: text(row.created),
431
480
  change: json(row.change),
432
481
  written: text(row.written),
433
482
  type: text(row.type),
483
+ facts: factsHeld(row),
434
484
  };
435
485
  }
436
- const ENTRY_COLUMNS = 'e.path, e.content, e.binary, e.revision, e.file_id, e.change, e.written, e.type';
486
+ function factsHeld(row) {
487
+ const type = text(row.declared);
488
+ if (type === null)
489
+ return null;
490
+ return {
491
+ type,
492
+ fields: json(row.fields) ?? {},
493
+ links: json(row.links) ?? [],
494
+ };
495
+ }
496
+ const ENTRY_COLUMNS = 'e.path, e.content, e.binary, e.revision, e.file_id, e.created, e.change, e.written, e.type, e.declared, e.fields, e.links';
497
+ function unrefreshed(alias) {
498
+ return `NOT EXISTS (SELECT 1 FROM replica_entries r WHERE r.key = ${alias}.key AND r.layer = ${REFRESHED} AND r.path = ${alias}.path)`;
499
+ }
437
500
  function viewed(view, alias) {
438
501
  if (view === 'visible')
439
502
  return `${alias}.visible = 1 AND ${alias}.present = 1`;
440
503
  if (view === 'confirmed')
441
504
  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)))`;
505
+ return `${alias}.present = 1 AND (${alias}.layer = ${REFRESHED} OR (${alias}.layer = ${CONFIRMED} AND ${unrefreshed(alias)}))`;
443
506
  }
444
507
  export function entries(connection, key, view, list) {
445
508
  if (list.length === 0)
@@ -459,103 +522,111 @@ export function typed(connection, key, view, list) {
459
522
  binary: number(row.binary) === 1,
460
523
  }));
461
524
  }
462
- export function referrers(connection, key, view, targets, kinds) {
525
+ export function referrers(connection, key, view, targets) {
463
526
  if (targets.length === 0)
464
527
  return [];
465
528
  return connection
466
- .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)])
529
+ .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)])
530
+ .map((row) => ({ path: text(row.path), target: text(row.target) }));
531
+ }
532
+ export function folders(connection, key, view, list) {
533
+ 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);
534
+ }
535
+ export function typedWithin(connection, key, view, roots, within) {
536
+ 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);
537
+ }
538
+ const ORPHANS = `SELECT k.key FROM replica_keys k
539
+ WHERE NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
540
+ AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)
541
+ AND (${UNPARTED} OR NOT EXISTS (
542
+ SELECT 1 FROM replica_entries e WHERE e.key = k.key
543
+ UNION ALL SELECT 1 FROM replica_folders f WHERE f.key = k.key
544
+ ))`;
545
+ const STAGED = `SELECT DISTINCT k.locator FROM replica_keys k
546
+ WHERE NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
547
+ AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)
548
+ AND k.key NOT IN (${ORPHANS})`;
549
+ export function opened(connection) {
550
+ return {
551
+ orphans: connection.rows(ORPHANS).map((row) => number(row.key)),
552
+ erasing: connection.rows('SELECT 1 FROM replica_erasing LIMIT 1').length > 0,
553
+ };
554
+ }
555
+ const ROOM_ROWS = `SELECT h.locator, h.key, h.record, r.documents, k.rederiving IS NULL AS indexed
556
+ FROM (${HELD_RECORDS}) h
557
+ JOIN replica_rooms r ON r.locator = h.locator
558
+ LEFT JOIN replica_keys k ON k.key = h.key`;
559
+ function roomRow(row) {
560
+ return {
561
+ locator: text(row.locator),
562
+ key: number(row.key),
563
+ record: text(row.record),
564
+ indexed: number(row.indexed) === 1,
565
+ documents: number(row.documents),
566
+ };
567
+ }
568
+ export function heldRooms(connection, locators) {
569
+ return (locators === null
570
+ ? connection.rows(`${ROOM_ROWS} ORDER BY h.locator`)
571
+ : connection.rows(`${ROOM_ROWS} WHERE h.locator IN (SELECT value FROM json_each(?)) ORDER BY h.locator`, [JSON.stringify(locators)])).map(roomRow);
572
+ }
573
+ export function heldRoom(connection, locator) {
574
+ return heldRooms(connection, [locator])[0] ?? null;
575
+ }
576
+ export function overlaid(connection, key) {
577
+ return connection
578
+ .rows(`SELECT path FROM replica_entries WHERE key = ? AND layer = ${OVERLAY}`, [key])
579
+ .map((row) => text(row.path));
580
+ }
581
+ export function covering(connection) {
582
+ return connection
583
+ .rows(`SELECT r.locator, r.key, r.documents, json_extract(r.record, '$.transfer') AS transfer,
584
+ json_extract(r.record, '$.roomVersion') AS room_version,
585
+ coalesce(json_extract(r.record, '$.inventory.bytes'), 0) AS bytes,
586
+ json_extract(r.record, '$.replica.offline') AS offline,
587
+ k.rederiving IS NULL AS indexed
588
+ FROM replica_rooms r LEFT JOIN replica_keys k ON k.key = r.key ORDER BY r.locator`)
467
589
  .map((row) => ({
468
- path: text(row.path),
469
- target: text(row.target),
470
- kind: text(row.kind),
590
+ locator: text(row.locator),
591
+ key: number(row.key),
592
+ transfer: text(row.transfer),
593
+ roomVersion: text(row.room_version),
594
+ indexed: number(row.indexed) === 1,
595
+ documents: number(row.documents),
596
+ bytes: number(row.bytes),
597
+ offline: text(row.offline),
471
598
  }));
472
599
  }
473
- export function walk(connection, key, view, after, bytes) {
474
- const found = [];
475
- let spent = 0;
476
- let from = after;
477
- 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]);
479
- for (const row of rows) {
480
- const entry = heldEntry(row);
481
- found.push(entry);
482
- spent += 256 + (entry.content?.length ?? 0) * 2;
483
- if (spent >= bytes)
484
- return { entries: found, next: entry.path };
485
- }
486
- if (rows.length < 64)
487
- return { entries: found, next: null };
488
- from = found.at(-1).path;
489
- }
600
+ export function offlineMode(connection, locator) {
601
+ const [held] = connection.rows("SELECT json_extract(record, '$.replica.offline') AS offline FROM replica_rooms WHERE locator = ?", [locator]);
602
+ return text(held?.offline);
490
603
  }
491
- 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) }));
604
+ export function stagedLocators(connection) {
605
+ return connection.rows(STAGED).map((row) => text(row.locator));
495
606
  }
496
- 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]);
607
+ export function heldLocators(connection) {
608
+ return connection
609
+ .rows(`SELECT locator FROM replica_rooms
610
+ UNION SELECT locator FROM authored_room_drafts
611
+ UNION SELECT locator FROM authored_queues
612
+ UNION ${STAGED}`)
613
+ .map((row) => text(row.locator));
614
+ }
615
+ export function demoted(connection) {
616
+ connection.run(`UPDATE replica_rooms SET record = json_set(record, '$.model.status', 'cached')
617
+ WHERE json_extract(record, '$.model.status') = 'current'`);
514
618
  return null;
515
619
  }
516
- export function opened(connection) {
517
- const facts = {};
620
+ export function facts(connection) {
621
+ const held = {};
518
622
  for (const row of connection.rows('SELECT name, value FROM replica_facts')) {
519
- facts[text(row.name)] = text(row.value);
623
+ held[text(row.name)] = text(row.value);
520
624
  }
521
- return {
522
- rooms: connection.rows('SELECT locator, key, record FROM replica_rooms').map((row) => ({
523
- locator: text(row.locator),
524
- key: number(row.key),
525
- record: text(row.record),
526
- })),
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
- keys: connection
533
- .rows('SELECT key, locator, transfer, rederiving FROM replica_keys')
534
- .map((row) => ({
535
- key: number(row.key),
536
- locator: text(row.locator),
537
- transfer: text(row.transfer),
538
- rederiving: row.rederiving === null ? null : number(row.rederiving),
539
- })),
540
- facts,
541
- presentation: connection.rows('SELECT app, value FROM replica_presentation').map((row) => ({
542
- app: text(row.app),
543
- value: text(row.value),
544
- })),
545
- overlaid: connection
546
- .rows(`SELECT key, path FROM replica_entries WHERE layer = ${OVERLAY}`)
547
- .map((row) => ({ key: number(row.key), path: text(row.path) })),
548
- orphans: connection
549
- .rows(`SELECT k.key FROM replica_keys k
550
- 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
- AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)`)
553
- .map((row) => number(row.key)),
554
- erasing: connection.rows('SELECT key, locator FROM replica_erasing').map((row) => ({
555
- key: number(row.key),
556
- locator: text(row.locator),
557
- })),
558
- };
625
+ return held;
626
+ }
627
+ export function presentation(connection, app) {
628
+ const [held] = connection.rows('SELECT value FROM replica_presentation WHERE app = ?', [app]);
629
+ return text(held?.value);
559
630
  }
560
631
  export function ended(connection) {
561
632
  for (const statement of REPLICA_TABLES) {
@@ -563,5 +634,4 @@ export function ended(connection) {
563
634
  if (table !== undefined)
564
635
  connection.run(`DELETE FROM ${table}`);
565
636
  }
566
- unindexAll(connection);
567
637
  }