@agentshouse/mdruntime 0.1.0-cec6690d5712e → 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 (392) 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 +207 -94
  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 +18 -0
  17. package/dist/binary.d.ts.map +1 -0
  18. package/dist/binary.js +8 -0
  19. package/dist/bootstrap.d.ts +31 -11
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +311 -92
  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 -14
  29. package/dist/change-set.d.ts.map +1 -1
  30. package/dist/change-set.js +17 -62
  31. package/dist/change.d.ts +46 -0
  32. package/dist/change.d.ts.map +1 -1
  33. package/dist/change.js +39 -0
  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 +51 -27
  38. package/dist/client.d.ts.map +1 -1
  39. package/dist/client.js +133 -26
  40. package/dist/collection.d.ts +34 -9
  41. package/dist/collection.d.ts.map +1 -1
  42. package/dist/collection.js +55 -79
  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 +8 -111
  47. package/dist/connection.d.ts.map +1 -1
  48. package/dist/connection.js +113 -2221
  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 +84 -45
  59. package/dist/conversation.d.ts.map +1 -1
  60. package/dist/conversation.js +1118 -584
  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 +23 -1
  71. package/dist/durability.d.ts.map +1 -1
  72. package/dist/durability.js +9 -0
  73. package/dist/entry.d.ts +20 -0
  74. package/dist/entry.d.ts.map +1 -0
  75. package/dist/entry.js +161 -0
  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 +29 -4
  92. package/dist/host.d.ts.map +1 -1
  93. package/dist/host.js +179 -25
  94. package/dist/identity.d.ts +1 -0
  95. package/dist/identity.d.ts.map +1 -1
  96. package/dist/identity.js +3 -0
  97. package/dist/index.d.ts +42 -12
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +20 -2
  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 +26 -8
  125. package/dist/memory.d.ts.map +1 -1
  126. package/dist/memory.js +307 -97
  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 +11 -12
  131. package/dist/model.d.ts.map +1 -1
  132. package/dist/model.js +7 -15
  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 +42 -9
  140. package/dist/observation.d.ts.map +1 -1
  141. package/dist/observation.js +238 -33
  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 +63 -5
  149. package/dist/outcome.d.ts.map +1 -1
  150. package/dist/outcome.js +70 -0
  151. package/dist/overlay.d.ts +21 -6
  152. package/dist/overlay.d.ts.map +1 -1
  153. package/dist/overlay.js +163 -187
  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 +51 -183
  173. package/dist/query.d.ts.map +1 -1
  174. package/dist/query.js +3 -464
  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 +20 -5
  182. package/dist/reach.d.ts.map +1 -1
  183. package/dist/reach.js +357 -80
  184. package/dist/react.d.ts +23 -7
  185. package/dist/react.d.ts.map +1 -1
  186. package/dist/react.js +40 -18
  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 +55 -0
  191. package/dist/readiness.d.ts.map +1 -0
  192. package/dist/readiness.js +25 -0
  193. package/dist/reads.d.ts +39 -0
  194. package/dist/reads.d.ts.map +1 -0
  195. package/dist/reads.js +178 -0
  196. package/dist/recovery.d.ts +28 -0
  197. package/dist/recovery.d.ts.map +1 -0
  198. package/dist/recovery.js +254 -0
  199. package/dist/refresh.d.ts +17 -14
  200. package/dist/refresh.d.ts.map +1 -1
  201. package/dist/refresh.js +145 -58
  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 +53 -33
  206. package/dist/replica.d.ts.map +1 -1
  207. package/dist/replica.js +349 -167
  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 +82 -11
  212. package/dist/room-draft.d.ts.map +1 -1
  213. package/dist/room-draft.js +490 -78
  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 +31 -35
  218. package/dist/rows.d.ts.map +1 -1
  219. package/dist/rows.js +60 -126
  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 +45 -22
  224. package/dist/save.d.ts.map +1 -1
  225. package/dist/save.js +977 -215
  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 +15 -55
  230. package/dist/search.d.ts.map +1 -1
  231. package/dist/search.js +10 -121
  232. package/dist/seed.d.ts +25 -0
  233. package/dist/seed.d.ts.map +1 -0
  234. package/dist/seed.js +145 -0
  235. package/dist/shared.d.ts +10 -0
  236. package/dist/shared.d.ts.map +1 -0
  237. package/dist/shared.js +339 -0
  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 +5 -2
  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 -116
  290. package/dist/store.d.ts.map +1 -1
  291. package/dist/store.js +164 -494
  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 +9 -0
  310. package/dist/turn.d.ts.map +1 -0
  311. package/dist/turn.js +75 -0
  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 +11 -0
  316. package/dist/windows.d.ts.map +1 -0
  317. package/dist/windows.js +16 -0
  318. package/dist/wire.d.ts +221 -34
  319. package/dist/wire.d.ts.map +1 -1
  320. package/dist/wire.js +74 -31
  321. package/package.json +28 -24
  322. package/AGENTS.md +0 -299
  323. package/dist/attachment.d.ts +0 -3
  324. package/dist/attachment.d.ts.map +0 -1
  325. package/dist/attachment.js +0 -8
  326. package/dist/attempt.d.ts +0 -16
  327. package/dist/attempt.d.ts.map +0 -1
  328. package/dist/attempt.js +0 -57
  329. package/dist/chat/acts.d.ts +0 -34
  330. package/dist/chat/acts.d.ts.map +0 -1
  331. package/dist/chat/acts.js +0 -36
  332. package/dist/chat/context.d.ts +0 -3
  333. package/dist/chat/context.d.ts.map +0 -1
  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 -60
  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 -20
  344. package/dist/chat/list.d.ts.map +0 -1
  345. package/dist/chat/list.js +0 -59
  346. package/dist/chat/markdown.d.ts +0 -19
  347. package/dist/chat/markdown.d.ts.map +0 -1
  348. package/dist/chat/markdown.js +0 -187
  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 -21
  353. package/dist/chat/messages.d.ts.map +0 -1
  354. package/dist/chat/messages.js +0 -151
  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 -645
  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 -80
  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 -33
  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/log.d.ts +0 -5
  388. package/dist/log.d.ts.map +0 -1
  389. package/dist/log.js +0 -14
  390. package/dist/read-side.d.ts +0 -18
  391. package/dist/read-side.d.ts.map +0 -1
  392. package/dist/read-side.js +0 -433
@@ -1,58 +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';
4
+ import { sha256 } from './digest.js';
5
+ import { declaredType } from './outcome.js';
6
+ import { binaryLocalId } from './identity.js';
3
7
  import { normalize } from './paths.js';
4
- export function returnedBy(kind) {
5
- return { kind, code: kind, fields: {}, retryable: false };
8
+ export function returnedBy() {
9
+ return { kind: 'withdrawn', code: 'withdrawn', fields: {}, retryable: false };
6
10
  }
7
11
  export function staleWrite(current) {
8
- return { outcome: 'stale', code: 'stale', retryable: false, current };
12
+ return { outcome: 'stale', current };
9
13
  }
10
- function malformed() {
11
- 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}`);
12
16
  }
13
17
  function plain(value) {
14
18
  return typeof value === 'object' && value !== null && !Array.isArray(value);
15
19
  }
16
- function text(value) {
17
- return typeof value === 'string' ? value : malformed();
20
+ function text(value, at) {
21
+ return typeof value === 'string' ? value : malformed(at, 'text');
18
22
  }
19
- function optionalText(value) {
20
- return value === undefined ? undefined : text(value);
23
+ function optionalText(value, at) {
24
+ return value === undefined ? undefined : text(value, at);
21
25
  }
22
- function markdownBase(value) {
23
- return value === null ? null : optionalText(value);
26
+ function markdownBase(value, at) {
27
+ return value === null ? null : optionalText(value, at);
24
28
  }
25
- function entries(value, each) {
29
+ function entries(value, at, each) {
26
30
  if (!plain(value))
27
- malformed();
31
+ malformed(at, 'an object of named values');
28
32
  const read = {};
29
33
  for (const [name, item] of Object.entries(value))
30
- read[name] = each(item);
34
+ read[name] = each(item, `${at}.${name}`);
31
35
  return read;
32
36
  }
33
- function fieldValue(value) {
37
+ function documentValueAt(item, at) {
38
+ return documentValue(item) ? item : malformed(at, 'a document value');
39
+ }
40
+ function fieldValue(value, at) {
34
41
  if (value === null)
35
42
  return null;
36
- if (!plain(value) || !Object.hasOwn(value, 'value') || !frontmatterValue(value.value)) {
37
- 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');
38
45
  }
39
46
  return { value: value.value };
40
47
  }
41
- function textPart(value) {
48
+ function textPart(value, at) {
42
49
  if (!plain(value))
43
- malformed();
44
- const base = optionalText(value.base);
45
- 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 }) };
46
53
  }
47
- function creationOf(value) {
54
+ function creationOf(value, at) {
48
55
  if (!plain(value))
49
- malformed();
56
+ malformed(at, '{ type, fields, sections }, or { markdown }');
50
57
  if (Object.hasOwn(value, 'markdown'))
51
- return { markdown: text(value.markdown) };
52
- const fields = entries(value.fields, (item) => (frontmatterValue(item) ? item : malformed()));
53
- 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
+ };
54
66
  }
55
- const PART_NAMES = new Set([
67
+ const PART_NAMES = [
56
68
  'fields',
57
69
  'sections',
58
70
  'preamble',
@@ -60,41 +72,59 @@ const PART_NAMES = new Set([
60
72
  'creation',
61
73
  'move',
62
74
  'removal',
63
- ]);
64
- export function readParts(value) {
65
- if (!plain(value) || Object.keys(value).some((name) => !PART_NAMES.has(name)))
66
- malformed();
75
+ 'file',
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);
67
84
  const parts = {};
68
85
  if (value.fields !== undefined) {
69
- parts.fields = entries(value.fields, (item) => {
70
- if (!plain(item) || !Object.hasOwn(item, 'value') || !frontmatterValue(item.value)) {
71
- 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');
72
89
  }
73
90
  return item.base === undefined
74
91
  ? { value: item.value }
75
- : { value: item.value, base: fieldValue(item.base) };
92
+ : { value: item.value, base: fieldValue(item.base, `${at}.base`) };
76
93
  });
77
94
  }
78
95
  if (value.sections !== undefined)
79
- parts.sections = entries(value.sections, textPart);
96
+ parts.sections = entries(value.sections, `${path} sections`, textPart);
80
97
  if (value.preamble !== undefined)
81
- parts.preamble = textPart(value.preamble);
98
+ parts.preamble = textPart(value.preamble, `${path} preamble`);
82
99
  if (value.markdown !== undefined)
83
- parts.markdown = textPart(value.markdown);
100
+ parts.markdown = textPart(value.markdown, `${path} markdown`);
84
101
  if (value.creation !== undefined)
85
- parts.creation = creationOf(value.creation);
102
+ parts.creation = creationOf(value.creation, `${path} creation`);
86
103
  if (value.move !== undefined) {
87
104
  if (!plain(value.move))
88
- malformed();
89
- const base = markdownBase(value.move.base);
90
- 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
+ };
91
113
  }
92
114
  if (value.removal !== undefined) {
93
115
  if (!plain(value.removal))
94
- malformed();
95
- const base = markdownBase(value.removal.base);
116
+ malformed(`${path} removal`, '{ base }');
117
+ const base = markdownBase(value.removal.base, `${path} removal.base`);
96
118
  parts.removal = base === undefined ? {} : { base };
97
119
  }
120
+ if (value.file !== undefined) {
121
+ if (!plain(value.file) || !(value.file.blob instanceof Blob))
122
+ malformed(`${path} file`, '{ blob, base } with a Blob');
123
+ if (Object.keys(value).length > 1)
124
+ throw runtimeError('room-draft-file-exclusive');
125
+ const base = markdownBase(value.file.base, `${path} file.base`);
126
+ parts.file = base === undefined ? { blob: value.file.blob } : { blob: value.file.blob, base };
127
+ }
98
128
  return parts;
99
129
  }
100
130
  function named(record) {
@@ -110,15 +140,76 @@ function kept(prior, base) {
110
140
  throw runtimeError('room-draft-base-missing');
111
141
  return base;
112
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
+ }
113
150
  function editsText(parts) {
114
151
  return (parts.preamble !== undefined ||
115
152
  named(parts.fields).length > 0 ||
116
153
  named(parts.sections).length > 0);
117
154
  }
118
- export function held(current, parts) {
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
+ }
188
+ export function held(current, parts, file) {
119
189
  if (parts.markdown !== undefined && editsText(parts)) {
120
190
  throw runtimeError('room-draft-markdown-exclusive');
121
191
  }
192
+ if (file !== undefined) {
193
+ if (current !== undefined && current.file === undefined) {
194
+ throw runtimeError('room-draft-file-exclusive');
195
+ }
196
+ return { file: { ...file, base: current?.file === undefined ? file.base : current.file.base } };
197
+ }
198
+ if (current?.file !== undefined) {
199
+ if (editsText(parts) ||
200
+ parts.markdown !== undefined ||
201
+ parts.creation !== undefined ||
202
+ (parts.move === undefined && parts.removal === undefined)) {
203
+ throw runtimeError('room-draft-file-exclusive');
204
+ }
205
+ return {
206
+ file: current.file,
207
+ ...(parts.move === undefined ? {} : { move: movePart(current.move, parts.move) }),
208
+ ...(parts.removal === undefined
209
+ ? {}
210
+ : { removal: { base: kept(current.removal, parts.removal.base) } }),
211
+ };
212
+ }
122
213
  const next = {};
123
214
  const fields = named(parts.fields);
124
215
  if (fields.length > 0) {
@@ -148,9 +239,8 @@ export function held(current, parts) {
148
239
  }
149
240
  if (parts.creation !== undefined)
150
241
  next.creation = parts.creation;
151
- if (parts.move !== undefined) {
152
- next.move = { to: parts.move.to, base: kept(current?.move, parts.move.base) };
153
- }
242
+ if (parts.move !== undefined)
243
+ next.move = movePart(current?.move, parts.move);
154
244
  if (parts.removal !== undefined) {
155
245
  next.removal = { base: kept(current?.removal, parts.removal.base) };
156
246
  }
@@ -167,8 +257,10 @@ export function underneath(parts, path, visible) {
167
257
  const markdown = visible?.documents[path] ?? null;
168
258
  const document = visible?.collection.document(path);
169
259
  const report = {};
260
+ const unread = document?.type !== undefined &&
261
+ governing(visible.collection.composition, path).status === 'unavailable';
170
262
  const fields = {};
171
- for (const [name, part] of named(parts.fields)) {
263
+ for (const [name, part] of unread ? [] : named(parts.fields)) {
172
264
  const current = document !== undefined && Object.hasOwn(document.fields, name)
173
265
  ? { value: document.fields[name] }
174
266
  : null;
@@ -178,7 +270,7 @@ export function underneath(parts, path, visible) {
178
270
  if (Object.keys(fields).length > 0)
179
271
  report.fields = fields;
180
272
  const sections = {};
181
- for (const [name, part] of named(parts.sections)) {
273
+ for (const [name, part] of unread ? [] : named(parts.sections)) {
182
274
  const current = document !== undefined && Object.hasOwn(document.sections, name)
183
275
  ? document.sections[name]
184
276
  : null;
@@ -188,8 +280,9 @@ export function underneath(parts, path, visible) {
188
280
  if (Object.keys(sections).length > 0)
189
281
  report.sections = sections;
190
282
  const preamble = document?.preamble ?? null;
191
- if (parts.preamble !== undefined && preamble !== parts.preamble.base)
283
+ if (!unread && parts.preamble !== undefined && preamble !== parts.preamble.base) {
192
284
  report.preamble = preamble;
285
+ }
193
286
  if (parts.markdown !== undefined && markdown !== parts.markdown.base)
194
287
  report.markdown = markdown;
195
288
  if (parts.creation !== undefined && markdown !== null)
@@ -198,19 +291,260 @@ export function underneath(parts, path, visible) {
198
291
  report.move = markdown;
199
292
  if (parts.removal !== undefined && markdown !== parts.removal.base)
200
293
  report.removal = markdown;
294
+ if (parts.file !== undefined) {
295
+ const file = binaryBase(visible, path);
296
+ if (file !== parts.file.base || markdown !== null)
297
+ report.file = file;
298
+ }
201
299
  return report;
202
300
  }
203
- export function changedParts(path, report) {
301
+ export async function heldFile(blob, base) {
302
+ return {
303
+ localId: binaryLocalId(),
304
+ ...(blob instanceof File ? { name: blob.name } : {}),
305
+ byteLength: blob.size,
306
+ mediaType: declaredType(blob),
307
+ sha256: await sha256(blob),
308
+ base,
309
+ };
310
+ }
311
+ export function binaryBase(visible, path) {
312
+ const binary = visible?.binaries[path];
313
+ if (binary === undefined)
314
+ return null;
315
+ return visible?.revisions[path] ?? (binary.state === 'pending' ? binary.localId : null);
316
+ }
317
+ export function changedParts(path, parts, visible) {
318
+ const report = underneath(parts, path, visible);
319
+ const made = parts.file?.base === null;
204
320
  return [
205
- ...(report.creation === undefined ? [] : [{ path, part: 'creation' }]),
206
321
  ...named(report.fields).map(([name]) => ({ path, part: 'field', name })),
207
322
  ...named(report.sections).map(([name]) => ({ path, part: 'section', name })),
208
323
  ...(report.preamble === undefined ? [] : [{ path, part: 'preamble' }]),
209
324
  ...(report.markdown === undefined ? [] : [{ path, part: 'markdown' }]),
210
325
  ...(report.move === undefined ? [] : [{ path, part: 'move' }]),
211
326
  ...(report.removal === undefined ? [] : [{ path, part: 'removal' }]),
327
+ ...(report.file === undefined || made ? [] : [{ path, part: 'file' }]),
212
328
  ];
213
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
+ }
214
548
  function joined(existing, returned) {
215
549
  const merged = { ...existing };
216
550
  for (const [name, part] of named(returned))
@@ -218,34 +552,45 @@ function joined(existing, returned) {
218
552
  merged[name] = part;
219
553
  return Object.keys(merged).length === 0 ? undefined : merged;
220
554
  }
221
- function ended(text) {
555
+ export function lineEnded(text) {
222
556
  return text === '' || text.endsWith('\n') ? text : `${text}\n`;
223
557
  }
224
- function frontmatterOf(fields) {
225
- const opened = parse('---\n---\n');
226
- if (!opened.ok)
227
- return malformed();
228
- let document = opened.document;
229
- for (const [name, part] of fields) {
230
- const next = apply(document, { kind: 'field', name, value: part.value, prior: undefined });
231
- if (next.outcome !== 'applied')
232
- return malformed();
233
- document = next.document;
234
- }
235
- 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();
236
565
  }
237
- function concatenated(markdown, parts) {
566
+ export function partsAfter(markdown, parts, definitions) {
238
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);
239
587
  const pieces = [
240
- ...(fields.length === 0 ? [] : [frontmatterOf(fields)]),
241
- ...(parts.preamble === undefined ? [] : [parts.preamble.value]),
242
- ...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))),
243
590
  ];
244
- if (pieces.length === 0)
245
- return markdown;
246
- return `${ended(markdown)}\n${pieces.map(ended).join('')}`;
591
+ return followedBy(markdown, pieces.join(''));
247
592
  }
248
- export function returnedInto(existing, returned) {
593
+ export function returnedInto(existing, returned, definitions) {
249
594
  if (existing === undefined)
250
595
  return returned;
251
596
  const next = { ...existing };
@@ -265,24 +610,59 @@ export function returnedInto(existing, returned) {
265
610
  delete next.fields;
266
611
  delete next.sections;
267
612
  delete next.preamble;
268
- const value = concatenated(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;
269
616
  next.markdown = { value, base: whole.base };
270
617
  }
271
618
  const creation = existing.creation ?? returned.creation;
272
619
  const move = existing.move ?? returned.move;
273
620
  const removal = existing.removal ?? returned.removal;
621
+ const file = existing.file ?? returned.file;
274
622
  if (creation !== undefined)
275
623
  next.creation = creation;
276
624
  if (move !== undefined)
277
625
  next.move = move;
278
626
  if (removal !== undefined)
279
627
  next.removal = removal;
628
+ if (file !== undefined)
629
+ next.file = file;
280
630
  return next;
281
631
  }
282
632
  export function moveTarget(move) {
283
633
  const to = normalize(move.to);
284
634
  return to === null || to === '' ? move.to : to;
285
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
+ }
286
666
  export function changesOf(path, parts) {
287
667
  const changes = [];
288
668
  const bases = [];
@@ -302,19 +682,51 @@ export function changesOf(path, parts) {
302
682
  bases.push({ kind: 'field', path, name, value: part.base?.value });
303
683
  }
304
684
  for (const [name, part] of named(parts.sections)) {
305
- changes.push({ kind: 'section', path, name, value: part.value });
685
+ changes.push({ kind: 'section', path, name, value: lineEnded(part.value) });
306
686
  bases.push({ kind: 'section', path, name, value: part.base });
307
687
  }
308
688
  if (parts.preamble !== undefined) {
309
- changes.push({ kind: 'preamble', path, value: parts.preamble.value });
689
+ changes.push({ kind: 'preamble', path, value: lineEnded(parts.preamble.value) });
310
690
  bases.push({ kind: 'preamble', path, value: parts.preamble.base });
311
691
  }
312
692
  if (parts.markdown !== undefined) {
313
693
  changes.push({ kind: 'replace', path, content: parts.markdown.value });
314
694
  }
695
+ if (parts.file !== undefined && parts.removal === undefined) {
696
+ const { base, name: _name, ...file } = parts.file;
697
+ const at = parts.move === undefined ? path : moveTarget(parts.move);
698
+ if (base !== null && at !== path)
699
+ changes.push({ kind: 'move', path, to: at });
700
+ changes.push({ kind: 'binary', path: at, ...file });
701
+ if (base === null)
702
+ bases.push({ kind: 'absent', path: at });
703
+ return { changes, bases };
704
+ }
705
+ if (parts.file?.base === null)
706
+ return { changes, bases };
315
707
  if (parts.move !== undefined)
316
708
  changes.push({ kind: 'move', path, to: moveTarget(parts.move) });
317
709
  if (parts.removal !== undefined)
318
710
  changes.push({ kind: 'remove', path });
319
711
  return { changes, bases };
320
712
  }
713
+ export function withdrawnDrafts(drafts, changes) {
714
+ return drafts.flatMap((draft) => {
715
+ const file = draft.parts.file;
716
+ const at = draft.parts.move?.to ?? draft.path;
717
+ const change = changes.find((item) => (item.kind === 'move' || item.kind === 'remove') && item.path === at);
718
+ if (file === undefined || change === undefined)
719
+ return [draft];
720
+ if (change.kind === 'move') {
721
+ return [
722
+ {
723
+ path: draft.path,
724
+ parts: change.to === draft.path ? { file } : { file, move: { to: change.to, base: null } },
725
+ },
726
+ ];
727
+ }
728
+ return file.base === null
729
+ ? []
730
+ : [{ path: draft.path, parts: { file, removal: { base: null } } }];
731
+ });
732
+ }