@agentshouse/mdruntime 0.1.0-cee9c8aa4cd86 → 0.1.0-cef76ea952695

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