@agentshouse/mdruntime 0.1.0-cedb03eaa48b6 → 0.1.0-cedd665dffa4c

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 (395) hide show
  1. package/README.md +24 -19
  2. package/dist/acceptance.d.ts +3 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +299 -0
  5. package/dist/act-lanes.d.ts +30 -0
  6. package/dist/act-lanes.d.ts.map +1 -0
  7. package/dist/act-lanes.js +225 -0
  8. package/dist/actions.d.ts +26 -0
  9. package/dist/actions.d.ts.map +1 -0
  10. package/dist/actions.js +302 -0
  11. package/dist/adapter.d.ts +173 -125
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +6 -10
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +35 -41
  16. package/dist/binary.d.ts +0 -5
  17. package/dist/binary.d.ts.map +1 -1
  18. package/dist/binary.js +1 -1
  19. package/dist/bootstrap.d.ts +30 -11
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +309 -113
  22. package/dist/cell.d.ts +6 -0
  23. package/dist/cell.d.ts.map +1 -0
  24. package/dist/cell.js +9 -0
  25. package/dist/chain.d.ts +12 -0
  26. package/dist/chain.d.ts.map +1 -0
  27. package/dist/chain.js +64 -0
  28. package/dist/change-set.d.ts +13 -16
  29. package/dist/change-set.d.ts.map +1 -1
  30. package/dist/change-set.js +17 -89
  31. package/dist/change.d.ts +36 -1
  32. package/dist/change.d.ts.map +1 -1
  33. package/dist/change.js +17 -3
  34. package/dist/chat-types.d.ts +3 -0
  35. package/dist/chat-types.d.ts.map +1 -0
  36. package/dist/chat-types.js +11 -0
  37. package/dist/client.d.ts +37 -26
  38. package/dist/client.d.ts.map +1 -1
  39. package/dist/client.js +113 -23
  40. package/dist/collection.d.ts +32 -21
  41. package/dist/collection.d.ts.map +1 -1
  42. package/dist/collection.js +53 -318
  43. package/dist/compile.d.ts +18 -0
  44. package/dist/compile.d.ts.map +1 -0
  45. package/dist/compile.js +723 -0
  46. package/dist/connection.d.ts +7 -137
  47. package/dist/connection.d.ts.map +1 -1
  48. package/dist/connection.js +110 -3602
  49. package/dist/context.d.ts +2 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/{chat/context.js → context.js} +2 -5
  52. package/dist/conversation-live.d.ts +150 -0
  53. package/dist/conversation-live.d.ts.map +1 -0
  54. package/dist/conversation-live.js +259 -0
  55. package/dist/conversation-row.d.ts +40 -0
  56. package/dist/conversation-row.d.ts.map +1 -0
  57. package/dist/conversation-row.js +66 -0
  58. package/dist/conversation.d.ts +75 -49
  59. package/dist/conversation.d.ts.map +1 -1
  60. package/dist/conversation.js +1029 -806
  61. package/dist/declaration.d.ts +10 -0
  62. package/dist/declaration.d.ts.map +1 -0
  63. package/dist/declaration.js +24 -0
  64. package/dist/digest.d.ts +7 -0
  65. package/dist/digest.d.ts.map +1 -0
  66. package/dist/digest.js +113 -0
  67. package/dist/discovery.d.ts +10 -0
  68. package/dist/discovery.d.ts.map +1 -0
  69. package/dist/discovery.js +150 -0
  70. package/dist/durability.d.ts +20 -4
  71. package/dist/durability.d.ts.map +1 -1
  72. package/dist/durability.js +9 -0
  73. package/dist/entry.d.ts +5 -2
  74. package/dist/entry.d.ts.map +1 -1
  75. package/dist/entry.js +33 -29
  76. package/dist/expectation.d.ts +26 -13
  77. package/dist/expectation.d.ts.map +1 -1
  78. package/dist/expectation.js +72 -19
  79. package/dist/failure.d.ts +3 -0
  80. package/dist/failure.d.ts.map +1 -1
  81. package/dist/failure.js +6 -0
  82. package/dist/figures.d.ts +49 -0
  83. package/dist/figures.d.ts.map +1 -0
  84. package/dist/figures.js +199 -0
  85. package/dist/file.d.ts +2 -19
  86. package/dist/file.d.ts.map +1 -1
  87. package/dist/file.js +8 -122
  88. package/dist/holdings.d.ts +29 -0
  89. package/dist/holdings.d.ts.map +1 -0
  90. package/dist/holdings.js +95 -0
  91. package/dist/host.d.ts +21 -8
  92. package/dist/host.d.ts.map +1 -1
  93. package/dist/host.js +119 -51
  94. package/dist/identity.d.ts +0 -2
  95. package/dist/identity.d.ts.map +1 -1
  96. package/dist/identity.js +0 -6
  97. package/dist/index.d.ts +41 -16
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +19 -4
  100. package/dist/install.d.ts +60 -0
  101. package/dist/install.d.ts.map +1 -0
  102. package/dist/install.js +537 -0
  103. package/dist/lifecycle.d.ts +7 -0
  104. package/dist/lifecycle.d.ts.map +1 -0
  105. package/dist/lifecycle.js +278 -0
  106. package/dist/listing.d.ts +52 -0
  107. package/dist/listing.d.ts.map +1 -0
  108. package/dist/listing.js +152 -0
  109. package/dist/live.d.ts +25 -0
  110. package/dist/live.d.ts.map +1 -0
  111. package/dist/live.js +206 -0
  112. package/dist/loaded.d.ts +5 -0
  113. package/dist/loaded.d.ts.map +1 -0
  114. package/dist/loaded.js +40 -0
  115. package/dist/locks.d.ts +7 -0
  116. package/dist/locks.d.ts.map +1 -0
  117. package/dist/locks.js +32 -0
  118. package/dist/lost.d.ts +5 -0
  119. package/dist/lost.d.ts.map +1 -0
  120. package/dist/lost.js +49 -0
  121. package/dist/media.d.ts +13 -0
  122. package/dist/media.d.ts.map +1 -0
  123. package/dist/media.js +269 -0
  124. package/dist/memory.d.ts +19 -7
  125. package/dist/memory.d.ts.map +1 -1
  126. package/dist/memory.js +281 -118
  127. package/dist/messages.d.ts +58 -0
  128. package/dist/messages.d.ts.map +1 -0
  129. package/dist/messages.js +162 -0
  130. package/dist/model.d.ts +8 -11
  131. package/dist/model.d.ts.map +1 -1
  132. package/dist/model.js +6 -23
  133. package/dist/models.d.ts +10 -0
  134. package/dist/models.d.ts.map +1 -0
  135. package/dist/models.js +153 -0
  136. package/dist/name.d.ts +4 -0
  137. package/dist/name.d.ts.map +1 -0
  138. package/dist/name.js +122 -0
  139. package/dist/observation.d.ts +38 -10
  140. package/dist/observation.d.ts.map +1 -1
  141. package/dist/observation.js +232 -44
  142. package/dist/opened.d.ts +22 -0
  143. package/dist/opened.d.ts.map +1 -0
  144. package/dist/opened.js +107 -0
  145. package/dist/opening.d.ts +4 -0
  146. package/dist/opening.d.ts.map +1 -0
  147. package/dist/opening.js +53 -0
  148. package/dist/outcome.d.ts +51 -9
  149. package/dist/outcome.d.ts.map +1 -1
  150. package/dist/outcome.js +62 -2
  151. package/dist/overlay.d.ts +8 -6
  152. package/dist/overlay.d.ts.map +1 -1
  153. package/dist/overlay.js +121 -182
  154. package/dist/page-metadata.d.ts +8 -0
  155. package/dist/page-metadata.d.ts.map +1 -0
  156. package/dist/page-metadata.js +19 -0
  157. package/dist/parts.d.ts +4 -0
  158. package/dist/parts.d.ts.map +1 -0
  159. package/dist/parts.js +13 -0
  160. package/dist/paths.d.ts +2 -0
  161. package/dist/paths.d.ts.map +1 -1
  162. package/dist/paths.js +26 -0
  163. package/dist/preparation.d.ts +41 -0
  164. package/dist/preparation.d.ts.map +1 -0
  165. package/dist/preparation.js +314 -0
  166. package/dist/presence.d.ts +58 -0
  167. package/dist/presence.d.ts.map +1 -0
  168. package/dist/presence.js +231 -0
  169. package/dist/queries.d.ts +16 -0
  170. package/dist/queries.d.ts.map +1 -0
  171. package/dist/queries.js +308 -0
  172. package/dist/query.d.ts +21 -207
  173. package/dist/query.d.ts.map +1 -1
  174. package/dist/query.js +3 -493
  175. package/dist/{chat/questions.d.ts → questions.d.ts} +5 -7
  176. package/dist/questions.d.ts.map +1 -0
  177. package/dist/{chat/questions.js → questions.js} +11 -7
  178. package/dist/queue.d.ts +40 -0
  179. package/dist/queue.d.ts.map +1 -0
  180. package/dist/queue.js +480 -0
  181. package/dist/reach.d.ts +21 -11
  182. package/dist/reach.d.ts.map +1 -1
  183. package/dist/reach.js +342 -112
  184. package/dist/react.d.ts +17 -3
  185. package/dist/react.d.ts.map +1 -1
  186. package/dist/react.js +33 -1
  187. package/dist/{chat/read.d.ts → read.d.ts} +2 -0
  188. package/dist/read.d.ts.map +1 -0
  189. package/dist/{chat/read.js → read.js} +4 -0
  190. package/dist/readiness.d.ts +22 -20
  191. package/dist/readiness.d.ts.map +1 -1
  192. package/dist/readiness.js +5 -13
  193. package/dist/reads.d.ts +38 -10
  194. package/dist/reads.d.ts.map +1 -1
  195. package/dist/reads.js +163 -83
  196. package/dist/recovery.d.ts +5 -2
  197. package/dist/recovery.d.ts.map +1 -1
  198. package/dist/recovery.js +65 -8
  199. package/dist/refresh.d.ts +18 -15
  200. package/dist/refresh.d.ts.map +1 -1
  201. package/dist/refresh.js +145 -60
  202. package/dist/refusal.d.ts +5 -0
  203. package/dist/refusal.d.ts.map +1 -0
  204. package/dist/refusal.js +55 -0
  205. package/dist/replica.d.ts +49 -36
  206. package/dist/replica.d.ts.map +1 -1
  207. package/dist/replica.js +322 -198
  208. package/dist/retried.d.ts +30 -0
  209. package/dist/retried.d.ts.map +1 -0
  210. package/dist/retried.js +72 -0
  211. package/dist/room-draft.d.ts +55 -13
  212. package/dist/room-draft.d.ts.map +1 -1
  213. package/dist/room-draft.js +416 -122
  214. package/dist/room-drafts.d.ts +24 -0
  215. package/dist/room-drafts.d.ts.map +1 -0
  216. package/dist/room-drafts.js +220 -0
  217. package/dist/rows.d.ts +32 -31
  218. package/dist/rows.d.ts.map +1 -1
  219. package/dist/rows.js +59 -124
  220. package/dist/runtime.d.ts +34 -0
  221. package/dist/runtime.d.ts.map +1 -0
  222. package/dist/runtime.js +1 -0
  223. package/dist/save.d.ts +43 -49
  224. package/dist/save.d.ts.map +1 -1
  225. package/dist/save.js +736 -302
  226. package/dist/saves.d.ts +32 -0
  227. package/dist/saves.d.ts.map +1 -0
  228. package/dist/saves.js +426 -0
  229. package/dist/search.d.ts +12 -95
  230. package/dist/search.d.ts.map +1 -1
  231. package/dist/search.js +10 -266
  232. package/dist/seed.d.ts +11 -2
  233. package/dist/seed.d.ts.map +1 -1
  234. package/dist/seed.js +71 -1
  235. package/dist/shared.d.ts +6 -7
  236. package/dist/shared.d.ts.map +1 -1
  237. package/dist/shared.js +75 -104
  238. package/dist/shown.d.ts +33 -0
  239. package/dist/shown.d.ts.map +1 -0
  240. package/dist/shown.js +309 -0
  241. package/dist/socket/admission.d.ts +7 -0
  242. package/dist/socket/admission.d.ts.map +1 -0
  243. package/dist/socket/admission.js +39 -0
  244. package/dist/socket/answers.d.ts +6 -0
  245. package/dist/socket/answers.d.ts.map +1 -0
  246. package/dist/socket/answers.js +20 -0
  247. package/dist/socket/lanes.d.ts +28 -0
  248. package/dist/socket/lanes.d.ts.map +1 -0
  249. package/dist/socket/lanes.js +103 -0
  250. package/dist/socket/reads.d.ts +10 -0
  251. package/dist/socket/reads.d.ts.map +1 -0
  252. package/dist/socket/reads.js +31 -0
  253. package/dist/socket/session.d.ts +38 -0
  254. package/dist/socket/session.d.ts.map +1 -0
  255. package/dist/socket/session.js +23 -0
  256. package/dist/socket.d.ts +67 -0
  257. package/dist/socket.d.ts.map +1 -0
  258. package/dist/socket.js +164 -0
  259. package/dist/source.d.ts +59 -0
  260. package/dist/source.d.ts.map +1 -0
  261. package/dist/source.js +93 -0
  262. package/dist/status.d.ts +4 -5
  263. package/dist/status.d.ts.map +1 -1
  264. package/dist/status.js +3 -7
  265. package/dist/store/installs.d.ts +30 -0
  266. package/dist/store/installs.d.ts.map +1 -0
  267. package/dist/store/installs.js +40 -0
  268. package/dist/store/readiness.d.ts +11 -0
  269. package/dist/store/readiness.d.ts.map +1 -0
  270. package/dist/store/readiness.js +68 -0
  271. package/dist/store/records.d.ts +217 -0
  272. package/dist/store/records.d.ts.map +1 -0
  273. package/dist/store/records.js +464 -0
  274. package/dist/store/room-held.d.ts +8 -0
  275. package/dist/store/room-held.d.ts.map +1 -0
  276. package/dist/store/room-held.js +17 -0
  277. package/dist/store/rooms-shown.d.ts +8 -0
  278. package/dist/store/rooms-shown.d.ts.map +1 -0
  279. package/dist/store/rooms-shown.js +13 -0
  280. package/dist/store/space.d.ts +11 -0
  281. package/dist/store/space.d.ts.map +1 -0
  282. package/dist/store/space.js +41 -0
  283. package/dist/store/watchers.d.ts +17 -0
  284. package/dist/store/watchers.d.ts.map +1 -0
  285. package/dist/store/watchers.js +56 -0
  286. package/dist/store/writes.d.ts +24 -0
  287. package/dist/store/writes.d.ts.map +1 -0
  288. package/dist/store/writes.js +125 -0
  289. package/dist/store.d.ts +114 -163
  290. package/dist/store.d.ts.map +1 -1
  291. package/dist/store.js +163 -750
  292. package/dist/stored.d.ts +167 -0
  293. package/dist/stored.d.ts.map +1 -0
  294. package/dist/stored.js +703 -0
  295. package/dist/surface.d.ts +89 -0
  296. package/dist/surface.d.ts.map +1 -0
  297. package/dist/surface.js +222 -0
  298. package/dist/sync.d.ts +19 -0
  299. package/dist/sync.d.ts.map +1 -0
  300. package/dist/sync.js +535 -0
  301. package/dist/tables.d.ts +419 -0
  302. package/dist/tables.d.ts.map +1 -0
  303. package/dist/tables.js +758 -0
  304. package/dist/tallies.d.ts +18 -0
  305. package/dist/tallies.d.ts.map +1 -0
  306. package/dist/tallies.js +128 -0
  307. package/dist/transfer.d.ts +0 -5
  308. package/dist/transfer.d.ts.map +1 -1
  309. package/dist/turn.d.ts +4 -2
  310. package/dist/turn.d.ts.map +1 -1
  311. package/dist/turn.js +46 -22
  312. package/dist/validation.d.ts +29 -0
  313. package/dist/validation.d.ts.map +1 -0
  314. package/dist/validation.js +98 -0
  315. package/dist/windows.d.ts +1 -16
  316. package/dist/windows.d.ts.map +1 -1
  317. package/dist/windows.js +2 -79
  318. package/dist/wire.d.ts +74 -9
  319. package/dist/wire.d.ts.map +1 -1
  320. package/dist/wire.js +26 -6
  321. package/package.json +19 -19
  322. package/AGENTS.md +0 -305
  323. package/dist/attempt.d.ts +0 -16
  324. package/dist/attempt.d.ts.map +0 -1
  325. package/dist/attempt.js +0 -70
  326. package/dist/chat/acts.d.ts +0 -33
  327. package/dist/chat/acts.d.ts.map +0 -1
  328. package/dist/chat/acts.js +0 -36
  329. package/dist/chat/context.d.ts +0 -3
  330. package/dist/chat/context.d.ts.map +0 -1
  331. package/dist/chat/earlier.d.ts +0 -10
  332. package/dist/chat/earlier.d.ts.map +0 -1
  333. package/dist/chat/earlier.js +0 -54
  334. package/dist/chat/input.d.ts +0 -9
  335. package/dist/chat/input.d.ts.map +0 -1
  336. package/dist/chat/input.js +0 -99
  337. package/dist/chat/kit.d.ts +0 -61
  338. package/dist/chat/kit.d.ts.map +0 -1
  339. package/dist/chat/kit.js +0 -123
  340. package/dist/chat/lasting.d.ts +0 -6
  341. package/dist/chat/lasting.d.ts.map +0 -1
  342. package/dist/chat/lasting.js +0 -19
  343. package/dist/chat/list.d.ts +0 -27
  344. package/dist/chat/list.d.ts.map +0 -1
  345. package/dist/chat/list.js +0 -99
  346. package/dist/chat/markdown.d.ts +0 -20
  347. package/dist/chat/markdown.d.ts.map +0 -1
  348. package/dist/chat/markdown.js +0 -188
  349. package/dist/chat/message.d.ts +0 -6
  350. package/dist/chat/message.d.ts.map +0 -1
  351. package/dist/chat/message.js +0 -192
  352. package/dist/chat/messages.d.ts +0 -22
  353. package/dist/chat/messages.d.ts.map +0 -1
  354. package/dist/chat/messages.js +0 -155
  355. package/dist/chat/name.d.ts +0 -6
  356. package/dist/chat/name.d.ts.map +0 -1
  357. package/dist/chat/name.js +0 -37
  358. package/dist/chat/opened.d.ts +0 -11
  359. package/dist/chat/opened.d.ts.map +0 -1
  360. package/dist/chat/opened.js +0 -17
  361. package/dist/chat/overlay.d.ts +0 -31
  362. package/dist/chat/overlay.d.ts.map +0 -1
  363. package/dist/chat/overlay.js +0 -97
  364. package/dist/chat/parts.d.ts +0 -57
  365. package/dist/chat/parts.d.ts.map +0 -1
  366. package/dist/chat/parts.js +0 -119
  367. package/dist/chat/process.d.ts +0 -20
  368. package/dist/chat/process.d.ts.map +0 -1
  369. package/dist/chat/process.js +0 -32
  370. package/dist/chat/provider.d.ts +0 -63
  371. package/dist/chat/provider.d.ts.map +0 -1
  372. package/dist/chat/provider.js +0 -628
  373. package/dist/chat/question.d.ts +0 -9
  374. package/dist/chat/question.d.ts.map +0 -1
  375. package/dist/chat/question.js +0 -69
  376. package/dist/chat/questions.d.ts.map +0 -1
  377. package/dist/chat/read.d.ts.map +0 -1
  378. package/dist/chat/ready.d.ts +0 -34
  379. package/dist/chat/ready.d.ts.map +0 -1
  380. package/dist/chat/ready.js +0 -91
  381. package/dist/chat/style.d.ts +0 -4
  382. package/dist/chat/style.d.ts.map +0 -1
  383. package/dist/chat/style.js +0 -302
  384. package/dist/chat.d.ts +0 -14
  385. package/dist/chat.d.ts.map +0 -1
  386. package/dist/chat.js +0 -8
  387. package/dist/fence.d.ts +0 -6
  388. package/dist/fence.d.ts.map +0 -1
  389. package/dist/fence.js +0 -10
  390. package/dist/log.d.ts +0 -5
  391. package/dist/log.d.ts.map +0 -1
  392. package/dist/log.js +0 -14
  393. package/dist/read-side.d.ts +0 -27
  394. package/dist/read-side.d.ts.map +0 -1
  395. package/dist/read-side.js +0 -824
@@ -1,60 +1,70 @@
1
- import { apply, equalValue, frontmatterValue, parse, serialize } from '@agentshouse/mdmodel';
1
+ import { apply, create, documentValue, equalValue, parse, serialize, } from '@agentshouse/mdmodel';
2
+ import { definitionsAt, governing } from './collection.js';
2
3
  import { runtimeError } from './failure.js';
3
- import { hexDigest } from './file.js';
4
+ import { sha256 } from './digest.js';
5
+ import { declaredType } from './outcome.js';
4
6
  import { binaryLocalId } from './identity.js';
5
7
  import { normalize } from './paths.js';
6
- export function returnedBy(kind) {
7
- return { kind, code: kind, fields: {}, retryable: false };
8
+ export function returnedBy() {
9
+ return { kind: 'withdrawn', code: 'withdrawn', fields: {}, retryable: false };
8
10
  }
9
11
  export function staleWrite(current) {
10
- return { outcome: 'stale', code: 'stale', retryable: false, current };
12
+ return { outcome: 'stale', current };
11
13
  }
12
- function malformed() {
13
- throw runtimeError('room-draft-malformed');
14
+ function malformed(at, needs) {
15
+ throw runtimeError('room-draft-malformed', at === undefined ? 'room-draft-malformed' : `room-draft-malformed at ${at}: ${needs}`);
14
16
  }
15
17
  function plain(value) {
16
18
  return typeof value === 'object' && value !== null && !Array.isArray(value);
17
19
  }
18
- function text(value) {
19
- return typeof value === 'string' ? value : malformed();
20
+ function text(value, at) {
21
+ return typeof value === 'string' ? value : malformed(at, 'text');
20
22
  }
21
- function optionalText(value) {
22
- return value === undefined ? undefined : text(value);
23
+ function optionalText(value, at) {
24
+ return value === undefined ? undefined : text(value, at);
23
25
  }
24
- function markdownBase(value) {
25
- return value === null ? null : optionalText(value);
26
+ function markdownBase(value, at) {
27
+ return value === null ? null : optionalText(value, at);
26
28
  }
27
- function entries(value, each) {
29
+ function entries(value, at, each) {
28
30
  if (!plain(value))
29
- malformed();
31
+ malformed(at, 'an object of named values');
30
32
  const read = {};
31
33
  for (const [name, item] of Object.entries(value))
32
- read[name] = each(item);
34
+ read[name] = each(item, `${at}.${name}`);
33
35
  return read;
34
36
  }
35
- function fieldValue(value) {
37
+ function documentValueAt(item, at) {
38
+ return documentValue(item) ? item : malformed(at, 'a document value');
39
+ }
40
+ function fieldValue(value, at) {
36
41
  if (value === null)
37
42
  return null;
38
- if (!plain(value) || !Object.hasOwn(value, 'value') || !frontmatterValue(value.value)) {
39
- malformed();
43
+ if (!plain(value) || !Object.hasOwn(value, 'value') || !documentValue(value.value)) {
44
+ malformed(at, '{ value } with a document value, or null where the document lacks it');
40
45
  }
41
46
  return { value: value.value };
42
47
  }
43
- function textPart(value) {
48
+ function textPart(value, at) {
44
49
  if (!plain(value))
45
- malformed();
46
- const base = optionalText(value.base);
47
- return { value: text(value.value), ...(base === undefined ? {} : { base }) };
50
+ malformed(at, '{ value, base }');
51
+ const base = optionalText(value.base, `${at}.base`);
52
+ return { value: text(value.value, `${at}.value`), ...(base === undefined ? {} : { base }) };
48
53
  }
49
- function creationOf(value) {
54
+ function creationOf(value, at) {
50
55
  if (!plain(value))
51
- malformed();
56
+ malformed(at, '{ type, fields, sections }, or { markdown }');
52
57
  if (Object.hasOwn(value, 'markdown'))
53
- return { markdown: text(value.markdown) };
54
- const fields = entries(value.fields, (item) => (frontmatterValue(item) ? item : malformed()));
55
- return { type: text(value.type), fields, sections: entries(value.sections, text) };
58
+ return { markdown: text(value.markdown, `${at}.markdown`) };
59
+ if (typeof value.type !== 'string')
60
+ malformed(`${at}.type`, 'a type name, or markdown in place of type, fields and sections');
61
+ return {
62
+ type: value.type,
63
+ fields: entries(value.fields, `${at}.fields`, documentValueAt),
64
+ sections: entries(value.sections, `${at}.sections`, text),
65
+ };
56
66
  }
57
- const PART_NAMES = new Set([
67
+ const PART_NAMES = [
58
68
  'fields',
59
69
  'sections',
60
70
  'preamble',
@@ -63,47 +73,57 @@ const PART_NAMES = new Set([
63
73
  'move',
64
74
  'removal',
65
75
  'file',
66
- ]);
67
- export function readParts(value) {
68
- if (!plain(value) || Object.keys(value).some((name) => !PART_NAMES.has(name)))
69
- malformed();
76
+ ];
77
+ const NAMED_PARTS = `a part named ${PART_NAMES.slice(0, -1).join(', ')} or ${PART_NAMES.slice(-1).join('')}`;
78
+ export function readParts(value, path) {
79
+ if (!plain(value))
80
+ malformed(path, 'an object of parts');
81
+ const unknown = Object.keys(value).find((name) => !PART_NAMES.includes(name));
82
+ if (unknown !== undefined)
83
+ malformed(`${path} ${unknown}`, NAMED_PARTS);
70
84
  const parts = {};
71
85
  if (value.fields !== undefined) {
72
- parts.fields = entries(value.fields, (item) => {
73
- if (!plain(item) || !Object.hasOwn(item, 'value') || !frontmatterValue(item.value)) {
74
- malformed();
86
+ parts.fields = entries(value.fields, `${path} fields`, (item, at) => {
87
+ if (!plain(item) || !Object.hasOwn(item, 'value') || !documentValue(item.value)) {
88
+ malformed(at, '{ value, base } with a document value');
75
89
  }
76
90
  return item.base === undefined
77
91
  ? { value: item.value }
78
- : { value: item.value, base: fieldValue(item.base) };
92
+ : { value: item.value, base: fieldValue(item.base, `${at}.base`) };
79
93
  });
80
94
  }
81
95
  if (value.sections !== undefined)
82
- parts.sections = entries(value.sections, textPart);
96
+ parts.sections = entries(value.sections, `${path} sections`, textPart);
83
97
  if (value.preamble !== undefined)
84
- parts.preamble = textPart(value.preamble);
98
+ parts.preamble = textPart(value.preamble, `${path} preamble`);
85
99
  if (value.markdown !== undefined)
86
- parts.markdown = textPart(value.markdown);
100
+ parts.markdown = textPart(value.markdown, `${path} markdown`);
87
101
  if (value.creation !== undefined)
88
- parts.creation = creationOf(value.creation);
102
+ parts.creation = creationOf(value.creation, `${path} creation`);
89
103
  if (value.move !== undefined) {
90
104
  if (!plain(value.move))
91
- malformed();
92
- const base = markdownBase(value.move.base);
93
- parts.move = { to: text(value.move.to), ...(base === undefined ? {} : { base }) };
105
+ malformed(`${path} move`, '{ to, room, base }');
106
+ const base = markdownBase(value.move.base, `${path} move.base`);
107
+ const room = optionalText(value.move.room, `${path} move.room`);
108
+ parts.move = {
109
+ to: text(value.move.to, `${path} move.to`),
110
+ ...(room === undefined ? {} : { room }),
111
+ ...(base === undefined ? {} : { base }),
112
+ };
94
113
  }
95
114
  if (value.removal !== undefined) {
96
115
  if (!plain(value.removal))
97
- malformed();
98
- const base = markdownBase(value.removal.base);
116
+ malformed(`${path} removal`, '{ base }');
117
+ const base = markdownBase(value.removal.base, `${path} removal.base`);
99
118
  parts.removal = base === undefined ? {} : { base };
100
119
  }
101
120
  if (value.file !== undefined) {
102
121
  if (!plain(value.file) || !(value.file.blob instanceof Blob))
103
- malformed();
122
+ malformed(`${path} file`, '{ blob, base } with a Blob');
104
123
  if (Object.keys(value).length > 1)
105
124
  throw runtimeError('room-draft-file-exclusive');
106
- parts.file = { blob: value.file.blob };
125
+ const base = markdownBase(value.file.base, `${path} file.base`);
126
+ parts.file = base === undefined ? { blob: value.file.blob } : { blob: value.file.blob, base };
107
127
  }
108
128
  return parts;
109
129
  }
@@ -120,11 +140,51 @@ function kept(prior, base) {
120
140
  throw runtimeError('room-draft-base-missing');
121
141
  return base;
122
142
  }
143
+ function movePart(current, move) {
144
+ return {
145
+ to: move.to,
146
+ ...(move.room === undefined ? {} : { room: move.room }),
147
+ base: kept(current, move.base),
148
+ };
149
+ }
123
150
  function editsText(parts) {
124
151
  return (parts.preamble !== undefined ||
125
152
  named(parts.fields).length > 0 ||
126
153
  named(parts.sections).length > 0);
127
154
  }
155
+ function partNames(parts) {
156
+ const names = [];
157
+ for (const [name, part] of Object.entries(parts)) {
158
+ if (name === 'preamble' || name === 'markdown')
159
+ names.push(name);
160
+ else if ((name === 'fields' || name === 'sections') && plain(part))
161
+ names.push(...Object.keys(part).map((one) => `${name}/${one}`));
162
+ else
163
+ return null;
164
+ }
165
+ return names.sort().join('\u0000');
166
+ }
167
+ function withBaseOf(prior, part) {
168
+ return plain(part) && plain(prior) && Object.hasOwn(prior, 'base')
169
+ ? { ...part, base: prior.base }
170
+ : part;
171
+ }
172
+ export function gatheredParts(earlier, later) {
173
+ if (!plain(earlier) || !plain(later))
174
+ return null;
175
+ const named = partNames(earlier);
176
+ if (named === null || named !== partNames(later))
177
+ return null;
178
+ return Object.fromEntries(Object.entries(later).map(([name, part]) => [
179
+ name,
180
+ name === 'fields' || name === 'sections'
181
+ ? Object.fromEntries(Object.entries(part).map(([one, value]) => [
182
+ one,
183
+ withBaseOf(earlier[name][one], value),
184
+ ]))
185
+ : withBaseOf(earlier[name], part),
186
+ ]));
187
+ }
128
188
  export function held(current, parts, file) {
129
189
  if (parts.markdown !== undefined && editsText(parts)) {
130
190
  throw runtimeError('room-draft-markdown-exclusive');
@@ -144,9 +204,7 @@ export function held(current, parts, file) {
144
204
  }
145
205
  return {
146
206
  file: current.file,
147
- ...(parts.move === undefined
148
- ? {}
149
- : { move: { to: parts.move.to, base: kept(current.move, parts.move.base) } }),
207
+ ...(parts.move === undefined ? {} : { move: movePart(current.move, parts.move) }),
150
208
  ...(parts.removal === undefined
151
209
  ? {}
152
210
  : { removal: { base: kept(current.removal, parts.removal.base) } }),
@@ -181,9 +239,8 @@ export function held(current, parts, file) {
181
239
  }
182
240
  if (parts.creation !== undefined)
183
241
  next.creation = parts.creation;
184
- if (parts.move !== undefined) {
185
- next.move = { to: parts.move.to, base: kept(current?.move, parts.move.base) };
186
- }
242
+ if (parts.move !== undefined)
243
+ next.move = movePart(current?.move, parts.move);
187
244
  if (parts.removal !== undefined) {
188
245
  next.removal = { base: kept(current?.removal, parts.removal.base) };
189
246
  }
@@ -200,8 +257,10 @@ export function underneath(parts, path, visible) {
200
257
  const markdown = visible?.documents[path] ?? null;
201
258
  const document = visible?.collection.document(path);
202
259
  const report = {};
260
+ const unread = document?.type !== undefined &&
261
+ governing(visible.collection.composition, path).status === 'unavailable';
203
262
  const fields = {};
204
- for (const [name, part] of named(parts.fields)) {
263
+ for (const [name, part] of unread ? [] : named(parts.fields)) {
205
264
  const current = document !== undefined && Object.hasOwn(document.fields, name)
206
265
  ? { value: document.fields[name] }
207
266
  : null;
@@ -211,7 +270,7 @@ export function underneath(parts, path, visible) {
211
270
  if (Object.keys(fields).length > 0)
212
271
  report.fields = fields;
213
272
  const sections = {};
214
- for (const [name, part] of named(parts.sections)) {
273
+ for (const [name, part] of unread ? [] : named(parts.sections)) {
215
274
  const current = document !== undefined && Object.hasOwn(document.sections, name)
216
275
  ? document.sections[name]
217
276
  : null;
@@ -221,8 +280,9 @@ export function underneath(parts, path, visible) {
221
280
  if (Object.keys(sections).length > 0)
222
281
  report.sections = sections;
223
282
  const preamble = document?.preamble ?? null;
224
- if (parts.preamble !== undefined && preamble !== parts.preamble.base)
283
+ if (!unread && parts.preamble !== undefined && preamble !== parts.preamble.base) {
225
284
  report.preamble = preamble;
285
+ }
226
286
  if (parts.markdown !== undefined && markdown !== parts.markdown.base)
227
287
  report.markdown = markdown;
228
288
  if (parts.creation !== undefined && markdown !== null)
@@ -238,33 +298,253 @@ export function underneath(parts, path, visible) {
238
298
  }
239
299
  return report;
240
300
  }
241
- const UNTYPED_MEDIA_TYPE = 'application/octet-stream';
242
301
  export async function heldFile(blob, base) {
243
- const bytes = await blob.arrayBuffer();
244
302
  return {
245
- blob,
246
303
  localId: binaryLocalId(),
247
- byteLength: bytes.byteLength,
248
- mediaType: blob.type === '' ? UNTYPED_MEDIA_TYPE : blob.type,
249
- sha256: hexDigest(await crypto.subtle.digest('SHA-256', bytes)),
304
+ ...(blob instanceof File ? { name: blob.name } : {}),
305
+ byteLength: blob.size,
306
+ mediaType: declaredType(blob),
307
+ sha256: await sha256(blob),
250
308
  base,
251
309
  };
252
310
  }
253
311
  export function binaryBase(visible, path) {
254
- return visible?.binaries[path] === undefined ? null : (visible.revisions[path] ?? null);
312
+ const binary = visible?.binaries[path];
313
+ if (binary === undefined)
314
+ return null;
315
+ return visible?.revisions[path] ?? (binary.state === 'pending' ? binary.localId : null);
255
316
  }
256
- export function changedParts(path, report) {
317
+ export function changedParts(path, parts, visible) {
318
+ const report = underneath(parts, path, visible);
319
+ const made = parts.file?.base === null;
257
320
  return [
258
- ...(report.creation === undefined ? [] : [{ path, part: 'creation' }]),
259
321
  ...named(report.fields).map(([name]) => ({ path, part: 'field', name })),
260
322
  ...named(report.sections).map(([name]) => ({ path, part: 'section', name })),
261
323
  ...(report.preamble === undefined ? [] : [{ path, part: 'preamble' }]),
262
324
  ...(report.markdown === undefined ? [] : [{ path, part: 'markdown' }]),
263
325
  ...(report.move === undefined ? [] : [{ path, part: 'move' }]),
264
326
  ...(report.removal === undefined ? [] : [{ path, part: 'removal' }]),
265
- ...(report.file === undefined ? [] : [{ path, part: 'file' }]),
327
+ ...(report.file === undefined || made ? [] : [{ path, part: 'file' }]),
266
328
  ];
267
329
  }
330
+ function marksOf(fields, sections, rest) {
331
+ return {
332
+ ...(Object.keys(fields).length === 0 ? {} : { fields }),
333
+ ...(Object.keys(sections).length === 0 ? {} : { sections }),
334
+ ...(rest.preamble === undefined ? {} : { preamble: rest.preamble }),
335
+ ...(rest.markdown === undefined ? {} : { markdown: rest.markdown }),
336
+ ...(rest.file === undefined ? {} : { file: rest.file }),
337
+ };
338
+ }
339
+ export function marksOver(kept, under) {
340
+ return marksOf({ ...under.fields, ...kept.fields }, { ...under.sections, ...kept.sections }, {
341
+ preamble: kept.preamble ?? under.preamble,
342
+ markdown: kept.markdown ?? under.markdown,
343
+ file: kept.file ?? under.file,
344
+ });
345
+ }
346
+ export function heldMarks(marks, parts) {
347
+ const whole = parts.markdown !== undefined;
348
+ const heldIn = (record, part) => Object.fromEntries(named(record).filter(([name]) => whole || own(part, name) !== undefined));
349
+ return marksOf(heldIn(marks.fields, parts.fields), heldIn(marks.sections, parts.sections), {
350
+ preamble: whole || parts.preamble !== undefined ? marks.preamble : undefined,
351
+ markdown: whole ? marks.markdown : undefined,
352
+ file: parts.file === undefined ? undefined : marks.file,
353
+ });
354
+ }
355
+ function followedBy(first, then) {
356
+ const kept = first.replace(/(?:\r?\n)+$/u, '');
357
+ return kept.trim() === '' ? then : `${kept}\n\n${then.replace(/^(?:\r?\n)+/u, '')}`;
358
+ }
359
+ function bothSides(current, value) {
360
+ return lineEnded(current) === lineEnded(value) ? value : followedBy(current, value);
361
+ }
362
+ export function partsBetween(path, before, after) {
363
+ const changes = [];
364
+ for (const name of new Set([...Object.keys(before.fields), ...Object.keys(after.fields)])) {
365
+ const value = own(after.fields, name);
366
+ if (!equalValue(own(before.fields, name), value)) {
367
+ changes.push({ kind: 'field', path, name, value });
368
+ }
369
+ }
370
+ if (after.preamble !== before.preamble) {
371
+ changes.push({ kind: 'preamble', path, value: after.preamble });
372
+ }
373
+ for (const [name, value] of Object.entries(after.sections)) {
374
+ if (own(before.sections, name) !== value)
375
+ changes.push({ kind: 'section', path, name, value });
376
+ }
377
+ return changes;
378
+ }
379
+ export function applyParts(document, changes, definition) {
380
+ const parts = new Map();
381
+ for (const change of changes) {
382
+ if (change.kind === 'field')
383
+ parts.set(`field ${change.name}`, {
384
+ kind: 'field',
385
+ name: change.name,
386
+ value: change.value,
387
+ prior: own(document.fields, change.name),
388
+ });
389
+ if (change.kind === 'preamble')
390
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
391
+ if (change.kind === 'section')
392
+ parts.set(`section ${change.name}`, {
393
+ kind: 'section',
394
+ name: change.name,
395
+ value: change.value,
396
+ prior: own(document.sections, change.name),
397
+ });
398
+ }
399
+ if (parts.size === 0)
400
+ return { outcome: 'applied', document };
401
+ return apply(document, [...parts.values()], definition);
402
+ }
403
+ function wholeJoined(part, current, path, composition, change) {
404
+ const definitions = definitionsAt(composition, path);
405
+ const read = (text) => {
406
+ const parsed = parse(text, path, definitions);
407
+ return parsed.ok ? parsed.document : undefined;
408
+ };
409
+ const base = read(part.base);
410
+ const mine = read(part.value);
411
+ const now = read(current);
412
+ if (base === undefined ||
413
+ mine === undefined ||
414
+ now === undefined ||
415
+ mine.type !== base.type ||
416
+ now.type !== base.type ||
417
+ Object.keys(base.sections).some((name) => !Object.hasOwn(mine.sections, name))) {
418
+ return null;
419
+ }
420
+ const definition = definitions(base.type);
421
+ const changes = partsBetween(path, base, mine);
422
+ const again = applyParts(base, changes, definition);
423
+ if (again.outcome !== 'applied' || serialize(again.document) !== part.value)
424
+ return null;
425
+ const fields = {};
426
+ const sections = {};
427
+ let preamble;
428
+ const over = [];
429
+ for (const one of changes) {
430
+ if (one.kind === 'field') {
431
+ const held = own(now.fields, one.name);
432
+ if (!equalValue(held, own(base.fields, one.name)) && !equalValue(held, one.value)) {
433
+ fields[one.name] = { current: held === undefined ? null : { value: held }, change };
434
+ }
435
+ over.push(one);
436
+ continue;
437
+ }
438
+ if (one.kind === 'preamble') {
439
+ const value = now.preamble === base.preamble ? one.value : bothSides(now.preamble, one.value);
440
+ if (value !== one.value)
441
+ preamble = { change };
442
+ over.push({ ...one, value });
443
+ continue;
444
+ }
445
+ const prior = own(base.sections, one.name);
446
+ const held = own(now.sections, one.name);
447
+ if (held === undefined && prior !== undefined)
448
+ return null;
449
+ const value = held === undefined || held === prior ? one.value : bothSides(held, one.value);
450
+ if (value !== one.value)
451
+ sections[one.name] = { change };
452
+ over.push({ ...one, value });
453
+ }
454
+ const joined = applyParts(now, over, definition);
455
+ if (joined.outcome !== 'applied')
456
+ return null;
457
+ return {
458
+ value: serialize(joined.document),
459
+ marks: marksOf(fields, sections, { preamble }),
460
+ };
461
+ }
462
+ export function joinedOver(parts, marks, path, visible) {
463
+ const report = underneath(parts, path, visible);
464
+ const change = visible.changes[path] ?? null;
465
+ const next = { ...parts };
466
+ const fields = {};
467
+ const sections = {};
468
+ const rest = {};
469
+ let rebased = false;
470
+ const text = (value, current, mark) => {
471
+ const joined = bothSides(current, value);
472
+ if (joined !== value)
473
+ mark();
474
+ return { value: joined, base: current };
475
+ };
476
+ const now = visible.collection.document(path);
477
+ if (now !== undefined) {
478
+ for (const [name, current] of named(report.fields)) {
479
+ const { value } = parts.fields[name];
480
+ next.fields = { ...next.fields, [name]: { value, base: current } };
481
+ if (!sameField(current, { value }))
482
+ fields[name] = { current, change };
483
+ rebased = true;
484
+ }
485
+ const removed = [];
486
+ for (const [name, current] of named(report.sections)) {
487
+ if (current === null) {
488
+ removed.push(name);
489
+ continue;
490
+ }
491
+ const joined = text(parts.sections[name].value, current, () => {
492
+ sections[name] = { change };
493
+ });
494
+ next.sections = { ...next.sections, [name]: joined };
495
+ rebased = true;
496
+ }
497
+ if (typeof report.preamble === 'string') {
498
+ next.preamble = text(parts.preamble.value, report.preamble, () => {
499
+ rest.preamble = { change };
500
+ });
501
+ rebased = true;
502
+ }
503
+ const whole = removed.length === 0
504
+ ? null
505
+ : applyParts(now, changesOf(path, next).changes, definitionsAt(visible.collection.composition, path)(now.type));
506
+ if (whole?.outcome === 'applied') {
507
+ delete next.fields;
508
+ delete next.sections;
509
+ delete next.preamble;
510
+ next.markdown = { value: serialize(whole.document), base: visible.documents[path] };
511
+ for (const name of removed)
512
+ sections[name] = { change };
513
+ rebased = true;
514
+ }
515
+ }
516
+ if (typeof report.markdown === 'string' && parts.markdown !== undefined) {
517
+ const current = report.markdown;
518
+ const whole = wholeJoined(parts.markdown, current, path, visible.collection.composition, change);
519
+ const value = whole?.value ?? bothSides(current, parts.markdown.value);
520
+ next.markdown = { value, base: current };
521
+ if (whole !== null) {
522
+ Object.assign(fields, whole.marks.fields);
523
+ Object.assign(sections, whole.marks.sections);
524
+ if (whole.marks.preamble !== undefined)
525
+ rest.preamble = whole.marks.preamble;
526
+ }
527
+ else if (value !== parts.markdown.value)
528
+ rest.markdown = { change };
529
+ rebased = true;
530
+ }
531
+ if (typeof report.move === 'string' && parts.move !== undefined) {
532
+ next.move = { ...parts.move, base: report.move };
533
+ rebased = true;
534
+ }
535
+ if (typeof report.removal === 'string' && parts.removal !== undefined) {
536
+ next.removal = { base: report.removal };
537
+ rebased = true;
538
+ }
539
+ if (typeof report.file === 'string' && parts.file !== undefined && parts.file.base !== null) {
540
+ next.file = { ...parts.file, base: report.file };
541
+ if (visible.binaries[path]?.sha256 !== parts.file.sha256) {
542
+ rest.file = { current: report.file, change };
543
+ }
544
+ rebased = true;
545
+ }
546
+ return rebased ? { parts: next, marks: marksOver(marksOf(fields, sections, rest), marks) } : null;
547
+ }
268
548
  function joined(existing, returned) {
269
549
  const merged = { ...existing };
270
550
  for (const [name, part] of named(returned))
@@ -272,34 +552,45 @@ function joined(existing, returned) {
272
552
  merged[name] = part;
273
553
  return Object.keys(merged).length === 0 ? undefined : merged;
274
554
  }
275
- function ended(text) {
555
+ export function lineEnded(text) {
276
556
  return text === '' || text.endsWith('\n') ? text : `${text}\n`;
277
557
  }
278
- function frontmatterOf(fields) {
279
- const opened = parse('---\n---\n');
280
- if (!opened.ok)
281
- return malformed();
282
- let document = opened.document;
283
- for (const [name, part] of fields) {
284
- const next = apply(document, { kind: 'field', name, value: part.value, prior: undefined });
285
- if (next.outcome !== 'applied')
286
- return malformed();
287
- document = next.document;
288
- }
289
- return serialize(document);
558
+ function written(start, changes, definition) {
559
+ const next = apply(start, changes, definition);
560
+ return next.outcome === 'applied' ? next.document : malformed();
561
+ }
562
+ function empty(definition) {
563
+ const parsed = parse('', '', definition);
564
+ return parsed.ok ? parsed.document : malformed();
290
565
  }
291
- export function partsAfter(markdown, parts) {
566
+ export function partsAfter(markdown, parts, definitions) {
292
567
  const fields = named(parts.fields);
568
+ const sections = named(parts.sections);
569
+ if (fields.length === 0 && sections.length === 0 && parts.preamble === undefined)
570
+ return markdown;
571
+ const whole = parse(markdown, '', definitions);
572
+ const type = whole.ok ? whole.document.type : undefined;
573
+ const definition = definitions?.(type);
574
+ if (type !== undefined && definition === undefined)
575
+ return null;
576
+ const valued = fields.length === 0
577
+ ? empty(definition)
578
+ : written(create(type, definition), fields.map(([name, part]) => ({
579
+ kind: 'field',
580
+ name,
581
+ value: part.value,
582
+ prior: undefined,
583
+ })), definition);
584
+ const head = parts.preamble === undefined
585
+ ? valued
586
+ : written(valued, [{ kind: 'preamble', value: lineEnded(parts.preamble.value), prior: valued.preamble }], definition);
293
587
  const pieces = [
294
- ...(fields.length === 0 ? [] : [frontmatterOf(fields)]),
295
- ...(parts.preamble === undefined ? [] : [parts.preamble.value]),
296
- ...named(parts.sections).map(([name, part]) => `## ${name}\n${part.value}`),
588
+ ...(fields.length === 0 && parts.preamble === undefined ? [] : [serialize(head)]),
589
+ ...sections.map(([name, part]) => serialize(written(empty(definition), [{ kind: 'section', name, value: lineEnded(part.value), prior: undefined }], definition))),
297
590
  ];
298
- if (pieces.length === 0)
299
- return markdown;
300
- return `${ended(markdown)}\n${pieces.map(ended).join('')}`;
591
+ return followedBy(markdown, pieces.join(''));
301
592
  }
302
- export function returnedInto(existing, returned) {
593
+ export function returnedInto(existing, returned, definitions) {
303
594
  if (existing === undefined)
304
595
  return returned;
305
596
  const next = { ...existing };
@@ -319,7 +610,9 @@ export function returnedInto(existing, returned) {
319
610
  delete next.fields;
320
611
  delete next.sections;
321
612
  delete next.preamble;
322
- const value = partsAfter(whole.value, existing.markdown === undefined ? existing : returned);
613
+ const value = partsAfter(whole.value, existing.markdown === undefined ? existing : returned, definitions);
614
+ if (value === null)
615
+ return null;
323
616
  next.markdown = { value, base: whole.base };
324
617
  }
325
618
  const creation = existing.creation ?? returned.creation;
@@ -340,6 +633,36 @@ export function moveTarget(move) {
340
633
  const to = normalize(move.to);
341
634
  return to === null || to === '' ? move.to : to;
342
635
  }
636
+ export function readSavedParts(named) {
637
+ const read = named.map(({ path, parts }) => ({ path, parts: readParts(parts, path) }));
638
+ read.forEach(({ path }, at) => {
639
+ if (path === '' || normalize(path) !== path)
640
+ malformed(path, 'a path inside the Room, such as notes/a.md');
641
+ if (read.findIndex((one) => one.path === path) !== at)
642
+ malformed(path, 'each path once in one save');
643
+ });
644
+ return read;
645
+ }
646
+ export function partsChanges(named) {
647
+ const changes = [];
648
+ const preconditions = [];
649
+ for (const { path, parts } of readSavedParts(named)) {
650
+ const parted = held(undefined, parts);
651
+ const made = changesOf(path, parted);
652
+ changes.push(...made.changes);
653
+ preconditions.push(...made.bases);
654
+ for (const change of made.changes) {
655
+ if (change.kind === 'create')
656
+ preconditions.push({ kind: 'absent', path });
657
+ if (change.kind === 'move')
658
+ preconditions.push({ kind: 'absent', path: change.to });
659
+ }
660
+ const revision = parted.markdown?.base ?? parted.move?.base ?? parted.removal?.base;
661
+ if (typeof revision === 'string')
662
+ preconditions.push({ kind: 'revision', path, revision });
663
+ }
664
+ return { changes, preconditions };
665
+ }
343
666
  export function changesOf(path, parts) {
344
667
  const changes = [];
345
668
  const bases = [];
@@ -359,23 +682,23 @@ export function changesOf(path, parts) {
359
682
  bases.push({ kind: 'field', path, name, value: part.base?.value });
360
683
  }
361
684
  for (const [name, part] of named(parts.sections)) {
362
- changes.push({ kind: 'section', path, name, value: part.value });
685
+ changes.push({ kind: 'section', path, name, value: lineEnded(part.value) });
363
686
  bases.push({ kind: 'section', path, name, value: part.base });
364
687
  }
365
688
  if (parts.preamble !== undefined) {
366
- changes.push({ kind: 'preamble', path, value: parts.preamble.value });
689
+ changes.push({ kind: 'preamble', path, value: lineEnded(parts.preamble.value) });
367
690
  bases.push({ kind: 'preamble', path, value: parts.preamble.base });
368
691
  }
369
692
  if (parts.markdown !== undefined) {
370
693
  changes.push({ kind: 'replace', path, content: parts.markdown.value });
371
694
  }
372
695
  if (parts.file !== undefined && parts.removal === undefined) {
373
- const { base, ...file } = parts.file;
696
+ const { base, name: _name, ...file } = parts.file;
374
697
  const at = parts.move === undefined ? path : moveTarget(parts.move);
375
698
  if (base !== null && at !== path)
376
699
  changes.push({ kind: 'move', path, to: at });
377
700
  changes.push({ kind: 'binary', path: at, ...file });
378
- if (base === null && at !== path)
701
+ if (base === null)
379
702
  bases.push({ kind: 'absent', path: at });
380
703
  return { changes, bases };
381
704
  }
@@ -387,35 +710,6 @@ export function changesOf(path, parts) {
387
710
  changes.push({ kind: 'remove', path });
388
711
  return { changes, bases };
389
712
  }
390
- function rebased(before, after) {
391
- return before === undefined ? after : { ...after, base: before.base };
392
- }
393
- function rebasedAll(before, after) {
394
- return {
395
- ...before,
396
- ...Object.fromEntries(Object.entries(after).map(([name, part]) => [name, rebased(before?.[name], part)])),
397
- };
398
- }
399
- export function joinedDraft(carried, saved) {
400
- const draft = saved.find((item) => item.path === carried.path);
401
- if (draft === undefined)
402
- return carried;
403
- const held = carried.parts;
404
- const next = draft.parts;
405
- return {
406
- path: carried.path,
407
- parts: {
408
- ...held,
409
- ...next,
410
- ...(next.fields === undefined ? {} : { fields: rebasedAll(held.fields, next.fields) }),
411
- ...(next.sections === undefined
412
- ? {}
413
- : { sections: rebasedAll(held.sections, next.sections) }),
414
- ...(next.preamble === undefined ? {} : { preamble: rebased(held.preamble, next.preamble) }),
415
- ...(next.markdown === undefined ? {} : { markdown: rebased(held.markdown, next.markdown) }),
416
- },
417
- };
418
- }
419
713
  export function withdrawnDrafts(drafts, changes) {
420
714
  return drafts.flatMap((draft) => {
421
715
  const file = draft.parts.file;