@agentshouse/mdruntime 0.1.0-cefbc8bb8a1c0 → 0.1.0-cf08ada271a7f

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