@agentshouse/mdruntime 0.1.0-cd9e28a590f84 → 0.1.0-cdded6c080913

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 (260) hide show
  1. package/README.md +1 -1
  2. package/dist/acceptance.d.ts +6 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +389 -0
  5. package/dist/actions.d.ts +20 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +250 -0
  8. package/dist/adapter.d.ts +23 -25
  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 +8 -4
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +209 -78
  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.d.ts +1 -0
  22. package/dist/change.d.ts.map +1 -1
  23. package/dist/chat-types.d.ts +5 -0
  24. package/dist/chat-types.d.ts.map +1 -0
  25. package/dist/chat-types.js +13 -0
  26. package/dist/client.d.ts +17 -2
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +3 -0
  29. package/dist/collection.d.ts +2 -1
  30. package/dist/collection.d.ts.map +1 -1
  31. package/dist/compile.d.ts +7 -7
  32. package/dist/compile.d.ts.map +1 -1
  33. package/dist/compile.js +229 -301
  34. package/dist/connection.d.ts +5 -141
  35. package/dist/connection.d.ts.map +1 -1
  36. package/dist/connection.js +98 -5279
  37. package/dist/context.d.ts +2 -0
  38. package/dist/context.d.ts.map +1 -0
  39. package/dist/context.js +7 -0
  40. package/dist/conversation-row.d.ts +33 -0
  41. package/dist/conversation-row.d.ts.map +1 -0
  42. package/dist/conversation-row.js +53 -0
  43. package/dist/conversation.d.ts +16 -6
  44. package/dist/conversation.d.ts.map +1 -1
  45. package/dist/conversation.js +366 -216
  46. package/dist/discovery.d.ts +10 -0
  47. package/dist/discovery.d.ts.map +1 -0
  48. package/dist/discovery.js +175 -0
  49. package/dist/durability.d.ts +15 -0
  50. package/dist/durability.d.ts.map +1 -1
  51. package/dist/entry.d.ts +2 -2
  52. package/dist/entry.d.ts.map +1 -1
  53. package/dist/entry.js +17 -8
  54. package/dist/figures.d.ts +48 -0
  55. package/dist/figures.d.ts.map +1 -0
  56. package/dist/figures.js +197 -0
  57. package/dist/holdings.d.ts +29 -0
  58. package/dist/holdings.d.ts.map +1 -0
  59. package/dist/holdings.js +95 -0
  60. package/dist/host.d.ts +2 -2
  61. package/dist/host.d.ts.map +1 -1
  62. package/dist/host.js +22 -15
  63. package/dist/identity.d.ts +0 -1
  64. package/dist/identity.d.ts.map +1 -1
  65. package/dist/identity.js +0 -3
  66. package/dist/index.d.ts +19 -4
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +4 -0
  69. package/dist/install.d.ts +56 -0
  70. package/dist/install.d.ts.map +1 -0
  71. package/dist/install.js +560 -0
  72. package/dist/job.d.ts +33 -0
  73. package/dist/job.d.ts.map +1 -0
  74. package/dist/job.js +54 -0
  75. package/dist/lifecycle.d.ts +7 -0
  76. package/dist/lifecycle.d.ts.map +1 -0
  77. package/dist/lifecycle.js +267 -0
  78. package/dist/listing.d.ts +53 -0
  79. package/dist/listing.d.ts.map +1 -0
  80. package/dist/listing.js +175 -0
  81. package/dist/live.d.ts +5 -4
  82. package/dist/live.d.ts.map +1 -1
  83. package/dist/live.js +62 -20
  84. package/dist/locks.d.ts +9 -0
  85. package/dist/locks.d.ts.map +1 -0
  86. package/dist/locks.js +61 -0
  87. package/dist/log.d.ts +1 -0
  88. package/dist/log.d.ts.map +1 -1
  89. package/dist/lost.d.ts +5 -0
  90. package/dist/lost.d.ts.map +1 -0
  91. package/dist/lost.js +61 -0
  92. package/dist/memory.d.ts +4 -1
  93. package/dist/memory.d.ts.map +1 -1
  94. package/dist/memory.js +43 -4
  95. package/dist/messages.d.ts +25 -1
  96. package/dist/messages.d.ts.map +1 -1
  97. package/dist/messages.js +84 -5
  98. package/dist/model.d.ts +2 -9
  99. package/dist/model.d.ts.map +1 -1
  100. package/dist/model.js +0 -9
  101. package/dist/models.d.ts +14 -0
  102. package/dist/models.d.ts.map +1 -0
  103. package/dist/models.js +193 -0
  104. package/dist/observation.d.ts +2 -2
  105. package/dist/observation.d.ts.map +1 -1
  106. package/dist/observation.js +7 -2
  107. package/dist/opening.d.ts +4 -0
  108. package/dist/opening.d.ts.map +1 -0
  109. package/dist/opening.js +56 -0
  110. package/dist/outcome.d.ts +4 -1
  111. package/dist/outcome.d.ts.map +1 -1
  112. package/dist/outcome.js +3 -0
  113. package/dist/overlay.d.ts +1 -0
  114. package/dist/overlay.d.ts.map +1 -1
  115. package/dist/overlay.js +11 -0
  116. package/dist/parts.d.ts +59 -0
  117. package/dist/parts.d.ts.map +1 -0
  118. package/dist/parts.js +118 -0
  119. package/dist/preparation.d.ts +20 -0
  120. package/dist/preparation.d.ts.map +1 -0
  121. package/dist/preparation.js +226 -0
  122. package/dist/queries.d.ts +16 -0
  123. package/dist/queries.d.ts.map +1 -0
  124. package/dist/queries.js +272 -0
  125. package/dist/query.d.ts +13 -212
  126. package/dist/query.d.ts.map +1 -1
  127. package/dist/query.js +3 -524
  128. package/dist/questions.d.ts +34 -0
  129. package/dist/questions.d.ts.map +1 -0
  130. package/dist/questions.js +122 -0
  131. package/dist/reach.d.ts +12 -10
  132. package/dist/reach.d.ts.map +1 -1
  133. package/dist/reach.js +165 -109
  134. package/dist/react.d.ts +9 -2
  135. package/dist/react.d.ts.map +1 -1
  136. package/dist/read.d.ts +7 -0
  137. package/dist/read.d.ts.map +1 -0
  138. package/dist/read.js +17 -0
  139. package/dist/reads.d.ts +36 -0
  140. package/dist/reads.d.ts.map +1 -0
  141. package/dist/reads.js +150 -0
  142. package/dist/recovery.d.ts.map +1 -1
  143. package/dist/recovery.js +9 -1
  144. package/dist/refresh.d.ts +1 -1
  145. package/dist/refresh.d.ts.map +1 -1
  146. package/dist/refresh.js +8 -20
  147. package/dist/refusal.d.ts +5 -0
  148. package/dist/refusal.d.ts.map +1 -0
  149. package/dist/refusal.js +56 -0
  150. package/dist/replica.d.ts +8 -8
  151. package/dist/replica.d.ts.map +1 -1
  152. package/dist/replica.js +50 -18
  153. package/dist/room-draft.d.ts +2 -2
  154. package/dist/room-draft.d.ts.map +1 -1
  155. package/dist/room-drafts.d.ts +24 -0
  156. package/dist/room-drafts.d.ts.map +1 -0
  157. package/dist/room-drafts.js +216 -0
  158. package/dist/rows.d.ts +14 -22
  159. package/dist/rows.d.ts.map +1 -1
  160. package/dist/rows.js +48 -189
  161. package/dist/runtime.d.ts +42 -0
  162. package/dist/runtime.d.ts.map +1 -0
  163. package/dist/runtime.js +1 -0
  164. package/dist/save.d.ts.map +1 -1
  165. package/dist/save.js +11 -8
  166. package/dist/saves.d.ts +70 -0
  167. package/dist/saves.d.ts.map +1 -0
  168. package/dist/saves.js +1004 -0
  169. package/dist/search.d.ts.map +1 -1
  170. package/dist/search.js +1 -1
  171. package/dist/seed.d.ts +11 -2
  172. package/dist/seed.d.ts.map +1 -1
  173. package/dist/seed.js +71 -0
  174. package/dist/shown.d.ts +31 -0
  175. package/dist/shown.d.ts.map +1 -0
  176. package/dist/shown.js +294 -0
  177. package/dist/socket/admission.d.ts +7 -0
  178. package/dist/socket/admission.d.ts.map +1 -0
  179. package/dist/socket/admission.js +39 -0
  180. package/dist/socket/answers.d.ts +6 -0
  181. package/dist/socket/answers.d.ts.map +1 -0
  182. package/dist/socket/answers.js +20 -0
  183. package/dist/socket/lanes.d.ts +20 -0
  184. package/dist/socket/lanes.d.ts.map +1 -0
  185. package/dist/socket/lanes.js +77 -0
  186. package/dist/socket/reads.d.ts +10 -0
  187. package/dist/socket/reads.d.ts.map +1 -0
  188. package/dist/socket/reads.js +31 -0
  189. package/dist/socket/rooms.d.ts +43 -0
  190. package/dist/socket/rooms.d.ts.map +1 -0
  191. package/dist/socket/rooms.js +90 -0
  192. package/dist/socket/session.d.ts +39 -0
  193. package/dist/socket/session.d.ts.map +1 -0
  194. package/dist/socket/session.js +23 -0
  195. package/dist/socket.d.ts +70 -0
  196. package/dist/socket.d.ts.map +1 -0
  197. package/dist/socket.js +175 -0
  198. package/dist/source.d.ts +61 -0
  199. package/dist/source.d.ts.map +1 -0
  200. package/dist/source.js +81 -0
  201. package/dist/status.d.ts +0 -2
  202. package/dist/status.d.ts.map +1 -1
  203. package/dist/status.js +0 -6
  204. package/dist/store/fences.d.ts +16 -0
  205. package/dist/store/fences.d.ts.map +1 -0
  206. package/dist/store/fences.js +26 -0
  207. package/dist/store/installs.d.ts +30 -0
  208. package/dist/store/installs.d.ts.map +1 -0
  209. package/dist/store/installs.js +34 -0
  210. package/dist/store/readiness.d.ts +10 -0
  211. package/dist/store/readiness.d.ts.map +1 -0
  212. package/dist/store/readiness.js +52 -0
  213. package/dist/store/records.d.ts +212 -0
  214. package/dist/store/records.d.ts.map +1 -0
  215. package/dist/store/records.js +408 -0
  216. package/dist/store/room-held.d.ts +8 -0
  217. package/dist/store/room-held.d.ts.map +1 -0
  218. package/dist/store/room-held.js +17 -0
  219. package/dist/store/space.d.ts +11 -0
  220. package/dist/store/space.d.ts.map +1 -0
  221. package/dist/store/space.js +43 -0
  222. package/dist/store/watchers.d.ts +17 -0
  223. package/dist/store/watchers.d.ts.map +1 -0
  224. package/dist/store/watchers.js +57 -0
  225. package/dist/store/writes.d.ts +24 -0
  226. package/dist/store/writes.d.ts.map +1 -0
  227. package/dist/store/writes.js +125 -0
  228. package/dist/store.d.ts +107 -153
  229. package/dist/store.d.ts.map +1 -1
  230. package/dist/store.js +143 -396
  231. package/dist/stored.d.ts +58 -64
  232. package/dist/stored.d.ts.map +1 -1
  233. package/dist/stored.js +244 -156
  234. package/dist/surface.d.ts +88 -0
  235. package/dist/surface.d.ts.map +1 -0
  236. package/dist/surface.js +222 -0
  237. package/dist/sync.d.ts +20 -0
  238. package/dist/sync.d.ts.map +1 -0
  239. package/dist/sync.js +531 -0
  240. package/dist/tables.d.ts +110 -48
  241. package/dist/tables.d.ts.map +1 -1
  242. package/dist/tables.js +209 -54
  243. package/dist/tallies.d.ts +18 -0
  244. package/dist/tallies.d.ts.map +1 -0
  245. package/dist/tallies.js +129 -0
  246. package/dist/validation.d.ts.map +1 -1
  247. package/dist/validation.js +7 -4
  248. package/dist/windows.d.ts +1 -12
  249. package/dist/windows.d.ts.map +1 -1
  250. package/dist/windows.js +2 -75
  251. package/dist/wire.d.ts +16 -1
  252. package/dist/wire.d.ts.map +1 -1
  253. package/dist/wire.js +6 -0
  254. package/package.json +2 -2
  255. package/dist/fence.d.ts +0 -6
  256. package/dist/fence.d.ts.map +0 -1
  257. package/dist/fence.js +0 -10
  258. package/dist/phrase.d.ts +0 -3
  259. package/dist/phrase.d.ts.map +0 -1
  260. package/dist/phrase.js +0 -8
package/dist/stored.js CHANGED
@@ -1,20 +1,21 @@
1
+ import { instantKey, sql } from '@agentshouse/mdmodel';
1
2
  import { composedOf } from './collection.js';
2
- import { described, derived } from './rows.js';
3
+ import { described, derived, heldOf } from './rows.js';
3
4
  export const REPLICA_TABLES = [
4
5
  'CREATE TABLE replica_keys (key INTEGER PRIMARY KEY AUTOINCREMENT, locator TEXT NOT NULL, transfer TEXT NOT NULL, fingerprint TEXT NOT NULL, rederiving INTEGER)',
5
- 'CREATE TABLE replica_rooms (locator TEXT PRIMARY KEY, key INTEGER NOT NULL, record TEXT NOT NULL) WITHOUT ROWID',
6
- '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, outline TEXT)',
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)",
7
9
  'CREATE UNIQUE INDEX replica_entries_path ON replica_entries (key, layer, path)',
8
10
  'CREATE INDEX replica_entries_visible ON replica_entries (key, visible, present, path, binary)',
9
11
  'CREATE INDEX replica_entries_folder ON replica_entries (key, folder, path)',
10
12
  'CREATE INDEX replica_entries_type ON replica_entries (key, type, visible, present, queried, folder)',
11
13
  'CREATE INDEX replica_entries_key ON replica_entries (key)',
12
- 'CREATE TABLE replica_values (doc INTEGER NOT NULL, keys TEXT NOT NULL, places TEXT NOT NULL, item INTEGER NOT NULL, kind TEXT NOT NULL, value, size INTEGER, PRIMARY KEY (doc, keys, places, item)) WITHOUT ROWID',
14
+ sql.VALUE_TABLE,
13
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',
14
16
  'CREATE INDEX replica_links_target ON replica_links (key, target, kind, keys)',
15
- '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',
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',
16
18
  'CREATE TABLE replica_erasing (key INTEGER PRIMARY KEY, locator TEXT NOT NULL)',
17
- 'CREATE TABLE replica_opened (locator TEXT NOT NULL, path TEXT NOT NULL, PRIMARY KEY (locator, path)) WITHOUT ROWID',
18
19
  'CREATE TABLE replica_facts (name TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID',
19
20
  'CREATE TABLE replica_presentation (app TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID',
20
21
  ];
@@ -29,8 +30,7 @@ const ABSENT = {
29
30
  queried: false,
30
31
  folder: '',
31
32
  fields: null,
32
- values: [],
33
- links: [],
33
+ links: null,
34
34
  };
35
35
  function text(value) {
36
36
  return typeof value === 'string' ? value : null;
@@ -51,17 +51,11 @@ function dropDerived(connection, doc) {
51
51
  connection.run('DELETE FROM replica_values WHERE doc = ?', [doc]);
52
52
  connection.run('DELETE FROM replica_links WHERE doc = ?', [doc]);
53
53
  }
54
- function scalarText(value) {
55
- if (value === Infinity)
56
- return 'Infinity';
57
- if (value === -Infinity)
58
- return '-Infinity';
59
- return JSON.stringify(value);
60
- }
61
- function writeDerived(connection, doc, key, made) {
54
+ function writeDerived(connection, doc, key, path, held, composition) {
55
+ const made = derived(path, held, composition);
62
56
  if (made.values.length > 0) {
63
- 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)}]`);
64
- 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(',')}]`]);
57
+ const inserted = sql.insertedValues(doc, made.values);
58
+ connection.run(inserted.sql, inserted.bind);
65
59
  }
66
60
  if (made.links.length > 0) {
67
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(?)', [
@@ -82,8 +76,8 @@ function removeEntry(connection, key, layer, path) {
82
76
  dropDerived(connection, id);
83
77
  connection.run('DELETE FROM replica_entries WHERE id = ?', [id]);
84
78
  }
85
- function insertEntry(connection, key, layer, entry, present, made) {
86
- connection.run('INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, outline, 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
87
81
  key,
88
82
  layer,
89
83
  entry.path,
@@ -91,16 +85,20 @@ function insertEntry(connection, key, layer, entry, present, made) {
91
85
  flag(present),
92
86
  flag(made.queried),
93
87
  entry.binary === null ? entry.content : null,
94
- entry.binary === null && entry.outline !== null ? JSON.stringify(entry.outline) : null,
95
88
  entry.binary === null ? null : JSON.stringify(entry.binary),
96
89
  entry.revision,
97
90
  entry.fileId,
91
+ entry.created,
92
+ instantKey(entry.created),
98
93
  entry.change === null ? null : JSON.stringify(entry.change),
94
+ instantKey(entry.change?.time),
99
95
  entry.written,
100
96
  made.type,
101
97
  made.declared,
102
98
  made.folder,
103
99
  made.fields,
100
+ made.links,
101
+ part,
104
102
  ]);
105
103
  }
106
104
  function textOf(entry) {
@@ -109,16 +107,15 @@ function textOf(entry) {
109
107
  function putEntry(connection, key, layer, entry, present, composition) {
110
108
  removeEntry(connection, key, layer, entry.path);
111
109
  const content = textOf(entry);
112
- const made = present ? derived(entry.path, content, entry.outline, composition) : ABSENT;
113
- insertEntry(connection, key, layer, entry, present, made);
110
+ insertEntry(connection, key, layer, entry, present, present ? described(entry.path, content, entry.facts) : ABSENT);
114
111
  if (!present)
115
112
  return;
116
113
  const id = entryId(connection, key, layer, entry.path);
117
- writeDerived(connection, id, key, made);
114
+ writeDerived(connection, id, key, entry.path, heldOf(entry.path, content, entry.facts), composition);
118
115
  }
119
- function placeEntry(connection, key, entry) {
116
+ function placeEntry(connection, key, entry, part) {
120
117
  removeEntry(connection, key, CONFIRMED, entry.path);
121
- insertEntry(connection, key, CONFIRMED, entry, true, described(entry.path, textOf(entry), entry.outline));
118
+ insertEntry(connection, key, CONFIRMED, entry, true, described(entry.path, textOf(entry), entry.facts), part);
122
119
  }
123
120
  function revealed(connection, key, touched) {
124
121
  if (touched.length === 0)
@@ -145,17 +142,83 @@ export function entered(connection, locator, composition, rows) {
145
142
  return key;
146
143
  }
147
144
  export function stage(connection, request) {
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
+ }
149
+ }
148
150
  const key = request.key ??
149
151
  newKey(connection, request.locator, request.transfer, request.fingerprint, false);
150
152
  recomposed(connection, key, request.fingerprint);
151
- connection.run('UPDATE replica_keys SET rederiving = coalesce(rederiving, last) FROM (SELECT coalesce(max(id), 0) AS last FROM replica_entries) WHERE key = ?', [key]);
153
+ owing(connection, key);
152
154
  for (const entry of request.entries)
153
- placeEntry(connection, key, entry);
155
+ placeEntry(connection, key, entry, request.part);
154
156
  for (const path of request.folders) {
155
- 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]);
156
158
  }
157
159
  return key;
158
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
+ }
159
222
  export function refreshing(connection, request) {
160
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]);
161
224
  if (held.length === 0)
@@ -169,12 +232,13 @@ export function refreshing(connection, request) {
169
232
  putEntry(connection, request.key, REFRESHED, {
170
233
  path,
171
234
  content: null,
172
- outline: null,
173
235
  binary: null,
174
236
  revision: null,
175
237
  fileId: null,
238
+ created: null,
176
239
  change: null,
177
240
  written: null,
241
+ facts: null,
178
242
  }, false, composition);
179
243
  }
180
244
  for (const [list, present] of [
@@ -231,15 +295,18 @@ function counted(connection, key, roots) {
231
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)]);
232
296
  return number(row.held);
233
297
  }
234
- export function counts(connection, rooms) {
235
- return rooms.map(({ key, roots }) => counted(connection, key, roots));
236
- }
237
298
  function erasing(connection, key, locator) {
238
299
  connection.run('INSERT OR REPLACE INTO replica_erasing (key, locator) VALUES (?, ?)', [
239
300
  key,
240
301
  locator,
241
302
  ]);
242
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
+ }
243
310
  function commitRoom(connection, room) {
244
311
  const touched = new Set();
245
312
  recomposed(connection, room.key, room.composed.fingerprint);
@@ -261,8 +328,9 @@ function commitRoom(connection, room) {
261
328
  removeEntry(connection, room.key, CONFIRMED, path);
262
329
  touched.add(path);
263
330
  }
331
+ const savedAt = new Date().toISOString();
264
332
  for (const entry of room.overlay?.set ?? []) {
265
- putEntry(connection, room.key, OVERLAY, entry, entry.present, composition);
333
+ putEntry(connection, room.key, OVERLAY, saved(connection, room.key, entry, savedAt), entry.present, composition);
266
334
  touched.add(entry.path);
267
335
  }
268
336
  for (const path of room.overlay?.cleared ?? []) {
@@ -272,27 +340,44 @@ function commitRoom(connection, room) {
272
340
  const changed = [...touched];
273
341
  revealed(connection, room.key, changed);
274
342
  if (room.record !== undefined) {
275
- connection.run('INSERT OR REPLACE INTO replica_rooms (locator, key, record) VALUES (?, ?, ?)', [
276
- room.locator,
277
- room.key,
278
- room.record,
279
- ]);
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]);
280
345
  }
281
346
  return room.published === undefined ? changed : null;
282
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')`;
283
365
  export function commit(connection, rooms) {
284
- const counted = {};
285
366
  const changed = {};
286
367
  const indexed = {};
287
368
  for (const room of rooms) {
288
369
  changed[room.locator] = commitRoom(connection, room);
289
- counted[room.locator] = counts(connection, [
290
- { key: room.key, roots: room.roots ?? rootsOf(connection, room.locator) },
291
- ])[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
+ ]);
292
375
  const [held] = connection.rows('SELECT rederiving FROM replica_keys WHERE key = ?', [room.key]);
293
376
  indexed[room.locator] = held !== undefined && held.rederiving === null;
294
377
  }
295
- return { counts: counted, changed, indexed };
378
+ if (rooms.some((room) => room.record !== undefined))
379
+ connection.run(UNHELD_MODELS);
380
+ return { changed, indexed };
296
381
  }
297
382
  function rootsOf(connection, locator) {
298
383
  const [row] = connection.rows('SELECT record FROM replica_rooms WHERE locator = ?', [locator]);
@@ -305,8 +390,8 @@ export function fenceRooms(connection, locators) {
305
390
  erasing(connection, number(row.key), locator);
306
391
  }
307
392
  connection.run('DELETE FROM replica_rooms WHERE locator = ?', [locator]);
308
- connection.run('DELETE FROM replica_opened WHERE locator = ?', [locator]);
309
393
  }
394
+ connection.run(UNHELD_MODELS);
310
395
  return null;
311
396
  }
312
397
  export function abandon(connection, locator) {
@@ -359,17 +444,20 @@ export function rederive(connection, key, composed, bytes, ms, floor) {
359
444
  let built = 0;
360
445
  let read = false;
361
446
  reading: for (;;) {
362
- const rows = connection.rows('SELECT id, path, content, outline FROM replica_entries WHERE key = ? AND present = 1 AND id > ? ORDER BY id LIMIT ?', [key, last, DERIVED_ROWS]);
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]);
363
448
  for (const row of rows) {
364
449
  const id = number(row.id);
365
450
  const path = text(row.path);
366
- const content = text(row.content);
367
- const made = derived(path, content, json(row.outline), composition);
451
+ const size = row.size === null ? 0 : number(row.size);
368
452
  dropDerived(connection, id);
369
- writeDerived(connection, id, key, made);
370
- built += content?.length ?? 0;
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;
371
459
  last = id;
372
- spent += ENTRY_WEIGHT + (content?.length ?? 0);
460
+ spent += ENTRY_WEIGHT + size;
373
461
  if (spent >= bytes || (spent >= floor && performance.now() - started >= ms))
374
462
  break reading;
375
463
  }
@@ -381,63 +469,31 @@ export function rederive(connection, key, composed, bytes, ms, floor) {
381
469
  connection.run('UPDATE replica_keys SET rederiving = ? WHERE key = ?', [read ? null : last, key]);
382
470
  return { done: read, text: built };
383
471
  }
384
- function outlinedAgain(connection, row, key, content, composition) {
385
- const id = number(row.id);
386
- const path = text(row.path);
387
- const made = derived(path, content, json(row.outline), composition);
388
- connection.run('UPDATE replica_entries SET content = ?, type = ?, declared = ?, queried = ?, folder = ?, fields = ? WHERE id = ?', [content, made.type, made.declared, flag(made.queried), made.folder, made.fields, id]);
389
- dropDerived(connection, id);
390
- writeDerived(connection, id, key, made);
391
- }
392
- export function holdTexts(connection, request) {
393
- const held = [];
394
- for (const { path, revision, content } of request.texts) {
395
- const [row] = connection.rows(`SELECT id, path, outline FROM replica_entries WHERE key = ? AND layer = ${CONFIRMED} AND path = ? AND revision = ? AND content IS NULL AND outline IS NOT NULL`, [request.key, path, revision]);
396
- if (row === undefined)
397
- continue;
398
- outlinedAgain(connection, row, request.key, content, request.composed.composition);
399
- connection.run('INSERT OR IGNORE INTO replica_opened (locator, path) VALUES (?, ?)', [
400
- request.locator,
401
- path,
402
- ]);
403
- held.push(path);
404
- }
405
- return held;
406
- }
407
- export function unholdTexts(connection, request) {
408
- connection.run('DELETE FROM replica_opened WHERE locator = ?', [request.locator]);
409
- const rows = connection.rows(`SELECT id, path, outline, octet_length(content) AS bytes FROM replica_entries WHERE key = ? AND layer = ${CONFIRMED} AND visible = 1 AND content IS NOT NULL AND outline IS NOT NULL`, [request.key]);
410
- for (const row of rows) {
411
- outlinedAgain(connection, row, request.key, null, request.composed.composition);
412
- }
413
- return {
414
- paths: rows.map((row) => text(row.path)),
415
- bytes: rows.reduce((total, row) => total + number(row.bytes), 0),
416
- };
417
- }
418
- export function remembered(connection, request) {
419
- connection.run(`INSERT OR IGNORE INTO replica_opened (locator, path) SELECT ?, path FROM replica_entries WHERE key = ? AND layer = ${CONFIRMED} AND content IS NOT NULL AND binary IS NULL AND path IN (SELECT value FROM json_each(?))`, [request.locator, request.key, paths(request.paths)]);
420
- return null;
421
- }
422
- export function wanted(connection, request) {
423
- return connection
424
- .rows(`SELECT e.path, e.revision FROM replica_opened o JOIN replica_entries e ON e.key = ? AND e.layer = ${CONFIRMED} AND e.path = o.path WHERE o.locator = ? AND e.content IS NULL AND e.outline IS NOT NULL AND e.revision IS NOT NULL`, [request.key, request.locator])
425
- .map((row) => ({ path: text(row.path), revision: text(row.revision) }));
426
- }
427
472
  function heldEntry(row) {
428
473
  return {
429
474
  path: text(row.path),
430
475
  content: text(row.content),
431
- outline: json(row.outline),
432
476
  binary: json(row.binary),
433
477
  revision: text(row.revision),
434
478
  fileId: text(row.file_id),
479
+ created: text(row.created),
435
480
  change: json(row.change),
436
481
  written: text(row.written),
437
482
  type: text(row.type),
483
+ facts: factsHeld(row),
438
484
  };
439
485
  }
440
- const ENTRY_COLUMNS = 'e.path, e.content, e.outline, 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';
441
497
  function unrefreshed(alias) {
442
498
  return `NOT EXISTS (SELECT 1 FROM replica_entries r WHERE r.key = ${alias}.key AND r.layer = ${REFRESHED} AND r.path = ${alias}.path)`;
443
499
  }
@@ -448,22 +504,6 @@ function viewed(view, alias) {
448
504
  return `${alias}.layer = ${CONFIRMED}`;
449
505
  return `${alias}.present = 1 AND (${alias}.layer = ${REFRESHED} OR (${alias}.layer = ${CONFIRMED} AND ${unrefreshed(alias)}))`;
450
506
  }
451
- function layers(view) {
452
- if (view !== 'refreshed')
453
- return [viewed(view, 'e')];
454
- return [
455
- `e.layer = ${REFRESHED} AND e.present = 1`,
456
- `e.layer = ${CONFIRMED} AND e.present = 1 AND ${unrefreshed('e')}`,
457
- ];
458
- }
459
- function pathOrdered(connection, columns, key, view, condition, after, limit) {
460
- const beyond = after === null ? '' : ' AND e.path > ?';
461
- const parts = layers(view).map((layer) => `SELECT ${columns} FROM replica_entries e WHERE e.key = ? AND ${layer}${condition}${beyond}`);
462
- return connection.rows(`${parts.join(' UNION ALL ')} ORDER BY path LIMIT ?`, [
463
- ...parts.flatMap(() => (after === null ? [key] : [key, after])),
464
- limit,
465
- ]);
466
- }
467
507
  export function entries(connection, key, view, list) {
468
508
  if (list.length === 0)
469
509
  return [];
@@ -482,19 +522,12 @@ export function typed(connection, key, view, list) {
482
522
  binary: number(row.binary) === 1,
483
523
  }));
484
524
  }
485
- export function referrers(connection, key, view, targets, kinds) {
525
+ export function referrers(connection, key, view, targets) {
486
526
  if (targets.length === 0)
487
527
  return [];
488
528
  return connection
489
- .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)])
490
- .map((row) => ({
491
- path: text(row.path),
492
- target: text(row.target),
493
- kind: text(row.kind),
494
- }));
495
- }
496
- export function files(connection, key, view, after, limit) {
497
- 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) }));
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) }));
498
531
  }
499
532
  export function folders(connection, key, view, list) {
500
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);
@@ -502,44 +535,99 @@ export function folders(connection, key, view, list) {
502
535
  export function typedWithin(connection, key, view, roots, within) {
503
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);
504
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})`;
505
549
  export function opened(connection) {
506
- const facts = {};
507
- for (const row of connection.rows('SELECT name, value FROM replica_facts')) {
508
- facts[text(row.name)] = text(row.value);
509
- }
510
550
  return {
511
- rooms: connection.rows('SELECT locator, key, record FROM replica_rooms').map((row) => ({
512
- locator: text(row.locator),
513
- key: number(row.key),
514
- record: text(row.record),
515
- })),
516
- keys: connection
517
- .rows('SELECT key, locator, transfer, rederiving FROM replica_keys')
518
- .map((row) => ({
519
- key: number(row.key),
520
- locator: text(row.locator),
521
- transfer: text(row.transfer),
522
- rederiving: cursor(row.rederiving),
523
- })),
524
- facts,
525
- presentation: connection.rows('SELECT app, value FROM replica_presentation').map((row) => ({
526
- app: text(row.app),
527
- value: text(row.value),
528
- })),
529
- overlaid: connection
530
- .rows(`SELECT key, path FROM replica_entries WHERE layer = ${OVERLAY}`)
531
- .map((row) => ({ key: number(row.key), path: text(row.path) })),
532
- orphans: connection
533
- .rows(`SELECT k.key FROM replica_keys k
534
- WHERE NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
535
- AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)`)
536
- .map((row) => number(row.key)),
537
- erasing: connection.rows('SELECT key, locator FROM replica_erasing').map((row) => ({
538
- key: number(row.key),
539
- locator: text(row.locator),
540
- })),
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),
541
566
  };
542
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`)
589
+ .map((row) => ({
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),
598
+ }));
599
+ }
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);
603
+ }
604
+ export function stagedLocators(connection) {
605
+ return connection.rows(STAGED).map((row) => text(row.locator));
606
+ }
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'`);
618
+ return null;
619
+ }
620
+ export function facts(connection) {
621
+ const held = {};
622
+ for (const row of connection.rows('SELECT name, value FROM replica_facts')) {
623
+ held[text(row.name)] = text(row.value);
624
+ }
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);
630
+ }
543
631
  export function ended(connection) {
544
632
  for (const statement of REPLICA_TABLES) {
545
633
  const table = /^CREATE TABLE (\w+)/u.exec(statement)?.[1];