@byline/host-tanstack-start 4.19.0 → 5.1.0

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 (322) hide show
  1. package/dist/admin-shell/chrome/admin-app-bar.d.ts +7 -0
  2. package/dist/admin-shell/chrome/admin-app-bar.js +10 -2
  3. package/dist/admin-shell/chrome/session-change-boundary.d.ts +14 -0
  4. package/dist/admin-shell/chrome/session-change-boundary.js +87 -0
  5. package/dist/admin-shell/chrome/session-change-boundary.test.d.ts +8 -0
  6. package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -1
  7. package/dist/admin-shell/chrome/sign-in-page.js +60 -3
  8. package/dist/admin-shell/collections/edit.d.ts +1 -1
  9. package/dist/admin-shell/collections/edit.js +151 -31
  10. package/dist/admin-shell/collections/history.js +4 -2
  11. package/dist/admin-shell/collections/list.js +30 -6
  12. package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
  13. package/dist/admin-shell/collections/restore-version-modal.js +9 -4
  14. package/dist/admin-shell/collections/tree-list.js +16 -6
  15. package/dist/admin-shell/collections/version-history.d.ts +2 -1
  16. package/dist/admin-shell/collections/version-history.js +11 -2
  17. package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
  18. package/dist/admin-shell/document-mutation-notice.js +53 -0
  19. package/dist/admin-shell/document-mutation-state.d.ts +22 -0
  20. package/dist/admin-shell/document-mutation-state.js +71 -0
  21. package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
  22. package/dist/admin-shell/scheduled-publications/list.js +15 -5
  23. package/dist/admin-shell/singletons/history.js +4 -2
  24. package/dist/admin-shell/singletons/view.d.ts +1 -1
  25. package/dist/admin-shell/singletons/view.js +98 -19
  26. package/dist/integrations/byline-admin-services.js +7 -1
  27. package/dist/integrations/byline-field-services.js +2 -9
  28. package/dist/integrations/client-ip.d.ts +19 -0
  29. package/dist/integrations/client-ip.js +34 -0
  30. package/dist/integrations/session-coordination.d.ts +24 -0
  31. package/dist/integrations/session-coordination.js +85 -0
  32. package/dist/integrations/session-middleware.d.ts +11 -0
  33. package/dist/integrations/session-middleware.js +94 -0
  34. package/dist/integrations/session-request-kind.d.ts +9 -0
  35. package/dist/integrations/session-request-kind.js +10 -0
  36. package/dist/integrations/session-request-middleware.d.ts +9 -0
  37. package/dist/integrations/session-request-middleware.js +7 -0
  38. package/dist/integrations/sign-in-body.d.ts +12 -0
  39. package/dist/integrations/sign-in-body.js +52 -0
  40. package/dist/integrations/sign-in-middleware.d.ts +9 -0
  41. package/dist/integrations/sign-in-middleware.js +10 -0
  42. package/dist/routes/create-admin-layout-route.js +46 -31
  43. package/dist/routes/create-collection-edit-route.js +1 -1
  44. package/dist/routes/create-collection-list-route.d.ts +0 -7
  45. package/dist/routes/create-collection-list-route.js +7 -9
  46. package/dist/routes/create-sign-in-route.js +4 -2
  47. package/dist/routes/create-singleton-route.d.ts +2 -0
  48. package/dist/routes/create-singleton-route.js +4 -3
  49. package/dist/server-fns/admin-account/change-password.d.ts +3 -1
  50. package/dist/server-fns/admin-account/change-password.js +9 -3
  51. package/dist/server-fns/admin-account/get.d.ts +3 -1
  52. package/dist/server-fns/admin-account/get.js +4 -1
  53. package/dist/server-fns/admin-account/index.d.ts +5 -5
  54. package/dist/server-fns/admin-account/update.d.ts +3 -1
  55. package/dist/server-fns/admin-account/update.js +4 -1
  56. package/dist/server-fns/admin-activity/get.d.ts +3 -1
  57. package/dist/server-fns/admin-activity/get.js +4 -1
  58. package/dist/server-fns/admin-permissions/get-role-abilities.d.ts +3 -1
  59. package/dist/server-fns/admin-permissions/get-role-abilities.js +4 -1
  60. package/dist/server-fns/admin-permissions/index.d.ts +4 -4
  61. package/dist/server-fns/admin-permissions/list-registered.d.ts +3 -1
  62. package/dist/server-fns/admin-permissions/list-registered.js +4 -1
  63. package/dist/server-fns/admin-permissions/set-role-abilities.d.ts +3 -1
  64. package/dist/server-fns/admin-permissions/set-role-abilities.js +4 -1
  65. package/dist/server-fns/admin-permissions/who-has.d.ts +3 -1
  66. package/dist/server-fns/admin-permissions/who-has.js +4 -1
  67. package/dist/server-fns/admin-roles/create.d.ts +3 -1
  68. package/dist/server-fns/admin-roles/create.js +4 -1
  69. package/dist/server-fns/admin-roles/delete.d.ts +3 -1
  70. package/dist/server-fns/admin-roles/delete.js +4 -1
  71. package/dist/server-fns/admin-roles/get.d.ts +3 -1
  72. package/dist/server-fns/admin-roles/get.js +4 -1
  73. package/dist/server-fns/admin-roles/index.d.ts +6 -6
  74. package/dist/server-fns/admin-roles/list.d.ts +3 -1
  75. package/dist/server-fns/admin-roles/list.js +4 -1
  76. package/dist/server-fns/admin-roles/reorder.d.ts +3 -1
  77. package/dist/server-fns/admin-roles/reorder.js +4 -1
  78. package/dist/server-fns/admin-roles/update.d.ts +3 -1
  79. package/dist/server-fns/admin-roles/update.js +4 -1
  80. package/dist/server-fns/admin-users/create.d.ts +3 -1
  81. package/dist/server-fns/admin-users/create.js +4 -1
  82. package/dist/server-fns/admin-users/delete.d.ts +3 -1
  83. package/dist/server-fns/admin-users/delete.js +4 -1
  84. package/dist/server-fns/admin-users/disable.d.ts +3 -1
  85. package/dist/server-fns/admin-users/disable.js +4 -1
  86. package/dist/server-fns/admin-users/enable.d.ts +3 -1
  87. package/dist/server-fns/admin-users/enable.js +4 -1
  88. package/dist/server-fns/admin-users/get-user-roles.d.ts +3 -1
  89. package/dist/server-fns/admin-users/get-user-roles.js +4 -1
  90. package/dist/server-fns/admin-users/get.d.ts +3 -1
  91. package/dist/server-fns/admin-users/get.js +4 -1
  92. package/dist/server-fns/admin-users/index.d.ts +10 -10
  93. package/dist/server-fns/admin-users/list.d.ts +3 -1
  94. package/dist/server-fns/admin-users/list.js +4 -1
  95. package/dist/server-fns/admin-users/set-password.d.ts +3 -1
  96. package/dist/server-fns/admin-users/set-password.js +4 -1
  97. package/dist/server-fns/admin-users/set-user-roles.d.ts +3 -1
  98. package/dist/server-fns/admin-users/set-user-roles.js +4 -1
  99. package/dist/server-fns/admin-users/update.d.ts +3 -1
  100. package/dist/server-fns/admin-users/update.js +4 -1
  101. package/dist/server-fns/ai/execute.d.ts +3 -1
  102. package/dist/server-fns/ai/execute.js +4 -1
  103. package/dist/server-fns/ai/index.d.ts +1 -1
  104. package/dist/server-fns/analytics/maintenance.d.ts +6 -2
  105. package/dist/server-fns/analytics/maintenance.js +7 -2
  106. package/dist/server-fns/analytics/queries.d.ts +18 -6
  107. package/dist/server-fns/analytics/queries.js +19 -6
  108. package/dist/server-fns/auth/current-user.d.ts +1 -0
  109. package/dist/server-fns/auth/current-user.js +5 -2
  110. package/dist/server-fns/auth/renew.d.ts +13 -0
  111. package/dist/server-fns/auth/renew.js +45 -0
  112. package/dist/server-fns/auth/sign-in.d.ts +2 -1
  113. package/dist/server-fns/auth/sign-in.js +88 -17
  114. package/dist/server-fns/auth/sign-out.d.ts +3 -1
  115. package/dist/server-fns/auth/sign-out.js +38 -6
  116. package/dist/server-fns/collections/audit.d.ts +3 -1
  117. package/dist/server-fns/collections/audit.js +4 -1
  118. package/dist/server-fns/collections/copy-to-locale.d.ts +5 -1
  119. package/dist/server-fns/collections/copy-to-locale.js +7 -2
  120. package/dist/server-fns/collections/create.d.ts +4 -1
  121. package/dist/server-fns/collections/create.js +8 -3
  122. package/dist/server-fns/collections/delete-locale.d.ts +5 -1
  123. package/dist/server-fns/collections/delete-locale.js +7 -2
  124. package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
  125. package/dist/server-fns/collections/delete-outcome.js +4 -0
  126. package/dist/server-fns/collections/delete.d.ts +5 -1
  127. package/dist/server-fns/collections/delete.js +8 -3
  128. package/dist/server-fns/collections/duplicate.d.ts +5 -1
  129. package/dist/server-fns/collections/duplicate.js +8 -3
  130. package/dist/server-fns/collections/get.d.ts +1 -0
  131. package/dist/server-fns/collections/get.js +12 -6
  132. package/dist/server-fns/collections/history.d.ts +3 -1
  133. package/dist/server-fns/collections/history.js +4 -1
  134. package/dist/server-fns/collections/index.d.ts +21 -21
  135. package/dist/server-fns/collections/list.d.ts +3 -1
  136. package/dist/server-fns/collections/list.js +15 -4
  137. package/dist/server-fns/collections/reindex.d.ts +3 -1
  138. package/dist/server-fns/collections/reindex.js +4 -1
  139. package/dist/server-fns/collections/reorder.d.ts +10 -2
  140. package/dist/server-fns/collections/reorder.js +23 -76
  141. package/dist/server-fns/collections/restore-version.d.ts +6 -1
  142. package/dist/server-fns/collections/restore-version.js +8 -2
  143. package/dist/server-fns/collections/save-outcome.d.ts +1 -0
  144. package/dist/server-fns/collections/save-outcome.js +1 -0
  145. package/dist/server-fns/collections/scheduled-publication.d.ts +64 -7
  146. package/dist/server-fns/collections/scheduled-publication.js +42 -21
  147. package/dist/server-fns/collections/set-list-view-preference.d.ts +3 -1
  148. package/dist/server-fns/collections/set-list-view-preference.js +4 -1
  149. package/dist/server-fns/collections/stats.js +4 -1
  150. package/dist/server-fns/collections/status.d.ts +14 -2
  151. package/dist/server-fns/collections/status.js +18 -5
  152. package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
  153. package/dist/server-fns/collections/tree-mutation.js +17 -5
  154. package/dist/server-fns/collections/tree.d.ts +29 -6
  155. package/dist/server-fns/collections/tree.js +32 -17
  156. package/dist/server-fns/collections/update.d.ts +18 -6
  157. package/dist/server-fns/collections/update.js +39 -20
  158. package/dist/server-fns/collections/upload.d.ts +11 -1
  159. package/dist/server-fns/collections/upload.js +6 -2
  160. package/dist/server-fns/document-mutation-errors.d.ts +5 -0
  161. package/dist/server-fns/document-mutation-errors.js +50 -0
  162. package/dist/server-fns/editable-revision.d.ts +9 -0
  163. package/dist/server-fns/editable-revision.js +11 -0
  164. package/dist/server-fns/i18n/set-locale.d.ts +3 -1
  165. package/dist/server-fns/i18n/set-locale.js +18 -3
  166. package/dist/server-fns/preview/disable.d.ts +3 -1
  167. package/dist/server-fns/preview/disable.js +4 -1
  168. package/dist/server-fns/preview/enable.d.ts +3 -1
  169. package/dist/server-fns/preview/enable.js +4 -1
  170. package/dist/server-fns/singleton-document-read.d.ts +1 -1
  171. package/dist/server-fns/singleton-document-read.js +7 -3
  172. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +9 -2
  173. package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -1
  174. package/dist/server-fns/singletons/change-status.d.ts +5 -1
  175. package/dist/server-fns/singletons/change-status.js +7 -1
  176. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +8 -2
  177. package/dist/server-fns/singletons/confirm-scheduled-publish.js +7 -2
  178. package/dist/server-fns/singletons/copy-to-locale.d.ts +5 -1
  179. package/dist/server-fns/singletons/copy-to-locale.js +7 -2
  180. package/dist/server-fns/singletons/find-by-version.d.ts +3 -1
  181. package/dist/server-fns/singletons/find-by-version.js +4 -1
  182. package/dist/server-fns/singletons/get-scheduled-publish.d.ts +3 -1
  183. package/dist/server-fns/singletons/get-scheduled-publish.js +4 -1
  184. package/dist/server-fns/singletons/get.d.ts +6 -4
  185. package/dist/server-fns/singletons/get.js +4 -1
  186. package/dist/server-fns/singletons/history.d.ts +3 -1
  187. package/dist/server-fns/singletons/history.js +4 -1
  188. package/dist/server-fns/singletons/restore-version.d.ts +5 -1
  189. package/dist/server-fns/singletons/restore-version.js +9 -3
  190. package/dist/server-fns/singletons/schedule-publish.d.ts +8 -2
  191. package/dist/server-fns/singletons/schedule-publish.js +7 -2
  192. package/dist/server-fns/singletons/unpublish.d.ts +5 -1
  193. package/dist/server-fns/singletons/unpublish.js +7 -1
  194. package/dist/server-fns/singletons/update.d.ts +6 -4
  195. package/dist/server-fns/singletons/update.js +8 -4
  196. package/dist/server-fns/structural-receipt.d.ts +4 -0
  197. package/dist/server-fns/structural-receipt.js +31 -0
  198. package/package.json +20 -20
  199. package/src/admin-shell/chrome/admin-app-bar.tsx +11 -6
  200. package/src/admin-shell/chrome/session-change-boundary.test.tsx +78 -0
  201. package/src/admin-shell/chrome/session-change-boundary.tsx +94 -0
  202. package/src/admin-shell/chrome/sign-in-page.tsx +68 -3
  203. package/src/admin-shell/collections/edit.tsx +173 -54
  204. package/src/admin-shell/collections/history.tsx +11 -2
  205. package/src/admin-shell/collections/list.tsx +22 -6
  206. package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
  207. package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
  208. package/src/admin-shell/collections/tree-list.tsx +16 -6
  209. package/src/admin-shell/collections/version-history.tsx +13 -3
  210. package/src/admin-shell/document-mutation-notice.tsx +45 -0
  211. package/src/admin-shell/document-mutation-state.test.tsx +146 -0
  212. package/src/admin-shell/document-mutation-state.ts +95 -0
  213. package/src/admin-shell/scheduled-publications/list.tsx +16 -6
  214. package/src/admin-shell/singletons/history.test.tsx +3 -1
  215. package/src/admin-shell/singletons/history.tsx +10 -2
  216. package/src/admin-shell/singletons/view.test.tsx +59 -18
  217. package/src/admin-shell/singletons/view.tsx +110 -29
  218. package/src/integrations/byline-admin-services.ts +7 -1
  219. package/src/integrations/byline-field-services.ts +2 -3
  220. package/src/integrations/client-ip.test.node.ts +45 -0
  221. package/src/integrations/client-ip.ts +54 -0
  222. package/src/integrations/framework-fetch-shape.test.node.ts +56 -0
  223. package/src/integrations/session-client.test.node.ts +121 -0
  224. package/src/integrations/session-coordination.test.node.ts +77 -0
  225. package/src/integrations/session-coordination.ts +96 -0
  226. package/src/integrations/session-middleware.test.node.ts +87 -0
  227. package/src/integrations/session-middleware.ts +107 -0
  228. package/src/integrations/session-request-kind.ts +18 -0
  229. package/src/integrations/session-request-middleware.ts +17 -0
  230. package/src/integrations/sign-in-body.test.node.ts +59 -0
  231. package/src/integrations/sign-in-body.ts +50 -0
  232. package/src/integrations/sign-in-middleware.ts +24 -0
  233. package/src/routes/create-admin-layout-route.tsx +48 -30
  234. package/src/routes/create-collection-edit-route.tsx +1 -0
  235. package/src/routes/create-collection-list-route.tsx +13 -2
  236. package/src/routes/create-sign-in-route.tsx +10 -2
  237. package/src/routes/create-singleton-route.test.node.ts +10 -2
  238. package/src/routes/create-singleton-route.tsx +6 -4
  239. package/src/server-fns/admin-account/change-password.test.node.ts +69 -0
  240. package/src/server-fns/admin-account/change-password.ts +13 -2
  241. package/src/server-fns/admin-account/get.ts +2 -0
  242. package/src/server-fns/admin-account/update.ts +2 -0
  243. package/src/server-fns/admin-activity/get.ts +2 -0
  244. package/src/server-fns/admin-permissions/get-role-abilities.ts +2 -0
  245. package/src/server-fns/admin-permissions/list-registered.ts +5 -4
  246. package/src/server-fns/admin-permissions/set-role-abilities.ts +2 -0
  247. package/src/server-fns/admin-permissions/who-has.ts +2 -0
  248. package/src/server-fns/admin-roles/create.ts +2 -0
  249. package/src/server-fns/admin-roles/delete.ts +2 -0
  250. package/src/server-fns/admin-roles/get.ts +2 -0
  251. package/src/server-fns/admin-roles/list.ts +5 -4
  252. package/src/server-fns/admin-roles/reorder.ts +2 -0
  253. package/src/server-fns/admin-roles/update.ts +2 -0
  254. package/src/server-fns/admin-users/create.ts +2 -0
  255. package/src/server-fns/admin-users/delete.ts +2 -0
  256. package/src/server-fns/admin-users/disable.ts +2 -0
  257. package/src/server-fns/admin-users/enable.ts +2 -0
  258. package/src/server-fns/admin-users/get-user-roles.ts +2 -0
  259. package/src/server-fns/admin-users/get.ts +2 -0
  260. package/src/server-fns/admin-users/list.ts +2 -0
  261. package/src/server-fns/admin-users/set-password.ts +2 -0
  262. package/src/server-fns/admin-users/set-user-roles.ts +2 -0
  263. package/src/server-fns/admin-users/update.ts +2 -0
  264. package/src/server-fns/ai/execute.ts +3 -0
  265. package/src/server-fns/analytics/maintenance.ts +4 -0
  266. package/src/server-fns/analytics/queries.ts +14 -8
  267. package/src/server-fns/auth/current-user.ts +7 -3
  268. package/src/server-fns/auth/renew.test.node.ts +85 -0
  269. package/src/server-fns/auth/renew.ts +54 -0
  270. package/src/server-fns/auth/sign-in.test.node.ts +114 -0
  271. package/src/server-fns/auth/sign-in.ts +80 -27
  272. package/src/server-fns/auth/sign-out.test.node.ts +85 -0
  273. package/src/server-fns/auth/sign-out.ts +44 -19
  274. package/src/server-fns/collections/audit.ts +2 -0
  275. package/src/server-fns/collections/copy-to-locale.ts +43 -36
  276. package/src/server-fns/collections/create.ts +56 -50
  277. package/src/server-fns/collections/delete-locale.ts +43 -32
  278. package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
  279. package/src/server-fns/collections/delete-outcome.ts +27 -16
  280. package/src/server-fns/collections/delete.ts +46 -38
  281. package/src/server-fns/collections/duplicate.ts +40 -32
  282. package/src/server-fns/collections/get.ts +11 -4
  283. package/src/server-fns/collections/history.ts +2 -0
  284. package/src/server-fns/collections/list.ts +10 -3
  285. package/src/server-fns/collections/reindex.ts +2 -0
  286. package/src/server-fns/collections/reorder.ts +34 -124
  287. package/src/server-fns/collections/restore-version.ts +49 -39
  288. package/src/server-fns/collections/revision-input.test.node.ts +122 -0
  289. package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
  290. package/src/server-fns/collections/save-outcome.ts +2 -0
  291. package/src/server-fns/collections/scheduled-publication.ts +149 -115
  292. package/src/server-fns/collections/set-list-view-preference.ts +2 -0
  293. package/src/server-fns/collections/stats.ts +2 -0
  294. package/src/server-fns/collections/status.ts +89 -64
  295. package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
  296. package/src/server-fns/collections/tree-mutation.ts +28 -11
  297. package/src/server-fns/collections/tree.ts +156 -122
  298. package/src/server-fns/collections/update.ts +113 -86
  299. package/src/server-fns/collections/upload-singleton.test.node.ts +10 -7
  300. package/src/server-fns/collections/upload.ts +91 -77
  301. package/src/server-fns/document-mutation-errors.ts +67 -0
  302. package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
  303. package/src/server-fns/editable-revision.test.node.ts +23 -0
  304. package/src/server-fns/editable-revision.ts +18 -0
  305. package/src/server-fns/i18n/set-locale.ts +19 -3
  306. package/src/server-fns/preview/disable.ts +8 -4
  307. package/src/server-fns/preview/enable.ts +9 -5
  308. package/src/server-fns/singleton-document-read.ts +7 -3
  309. package/src/server-fns/singletons/cancel-scheduled-publish.ts +12 -3
  310. package/src/server-fns/singletons/change-status.ts +12 -3
  311. package/src/server-fns/singletons/confirm-scheduled-publish.ts +14 -6
  312. package/src/server-fns/singletons/copy-to-locale.ts +22 -15
  313. package/src/server-fns/singletons/find-by-version.ts +2 -0
  314. package/src/server-fns/singletons/get-scheduled-publish.ts +2 -0
  315. package/src/server-fns/singletons/get.ts +5 -3
  316. package/src/server-fns/singletons/history.ts +2 -0
  317. package/src/server-fns/singletons/restore-version.ts +19 -12
  318. package/src/server-fns/singletons/schedule-publish.ts +20 -7
  319. package/src/server-fns/singletons/singletons.test.node.ts +69 -25
  320. package/src/server-fns/singletons/unpublish.ts +12 -3
  321. package/src/server-fns/singletons/update.ts +23 -16
  322. package/src/server-fns/structural-receipt.ts +41 -0
@@ -20,16 +20,20 @@ import { getAdminBylineClient } from '@byline/client/server'
20
20
  import { createReadContext, ERR_NOT_FOUND, getLogger } from '@byline/core'
21
21
 
22
22
  import { ensureCollection } from '../../integrations/api-utils.js'
23
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
24
+ import { withDocumentMutationErrors } from '../document-mutation-errors.js'
23
25
  import { placeAdminTreeNode, removeAdminTreeNode } from './tree-mutation.js'
24
- import { getAdminTreeParent, getAdminUnplacedTreeDocuments } from './tree-read.js'
26
+ import { getAdminTreeParent } from './tree-read.js'
25
27
 
26
28
  // ---------------------------------------------------------------------------
27
29
  // Place / move a node within the tree (place / reorder / re-parent)
28
30
  // ---------------------------------------------------------------------------
29
31
 
30
32
  export const placeTreeNode = createServerFn({ method: 'POST' })
33
+ .middleware([adminSessionMiddleware])
31
34
  .validator(
32
35
  (input: {
36
+ expectedRevision: number
33
37
  collection: string
34
38
  documentId: string
35
39
  parentDocumentId: string | null
@@ -38,94 +42,118 @@ export const placeTreeNode = createServerFn({ method: 'POST' })
38
42
  reconcile?: boolean
39
43
  }) => input
40
44
  )
41
- .handler(async ({ data }) => {
42
- const { collection: path, documentId } = data
43
- const config = await ensureCollection(path)
44
- if (!config) {
45
- throw ERR_NOT_FOUND({
46
- message: 'Collection not found',
47
- details: { collectionPath: path },
48
- }).log(getLogger())
49
- }
50
- const handle = getAdminBylineClient().collection(path)
51
- const result = await placeAdminTreeNode(handle, documentId, {
52
- parentDocumentId: data.parentDocumentId,
53
- beforeDocumentId: data.beforeDocumentId ?? null,
54
- afterDocumentId: data.afterDocumentId ?? null,
55
- reconcile: data.reconcile,
45
+ .handler(
46
+ withDocumentMutationErrors(async ({ data }) => {
47
+ const { collection: path, documentId } = data
48
+ const config = await ensureCollection(path)
49
+ if (!config) {
50
+ throw ERR_NOT_FOUND({
51
+ message: 'Collection not found',
52
+ details: { collectionPath: path },
53
+ }).log(getLogger())
54
+ }
55
+ const handle = getAdminBylineClient().collection(path)
56
+ const result = await placeAdminTreeNode(handle, documentId, {
57
+ expectedRevision: data.expectedRevision,
58
+ parentDocumentId: data.parentDocumentId,
59
+ beforeDocumentId: data.beforeDocumentId ?? null,
60
+ afterDocumentId: data.afterDocumentId ?? null,
61
+ reconcile: data.reconcile,
62
+ })
63
+ return { status: 'ok' as const, ...result }
56
64
  })
57
- return { status: 'ok' as const, orderKey: result.orderKey }
58
- })
65
+ )
59
66
 
60
67
  // ---------------------------------------------------------------------------
61
68
  // Remove a node from the tree (back to the unplaced state)
62
69
  // ---------------------------------------------------------------------------
63
70
 
64
71
  export const removeFromTree = createServerFn({ method: 'POST' })
65
- .validator((input: { collection: string; documentId: string; reconcile?: boolean }) => input)
66
- .handler(async ({ data }) => {
67
- const { collection: path, documentId } = data
68
- const config = await ensureCollection(path)
69
- if (!config) {
70
- throw ERR_NOT_FOUND({
71
- message: 'Collection not found',
72
- details: { collectionPath: path },
73
- }).log(getLogger())
74
- }
75
- await removeAdminTreeNode(getAdminBylineClient().collection(path), documentId, {
76
- reconcile: data.reconcile,
72
+ .middleware([adminSessionMiddleware])
73
+ .validator(
74
+ (input: {
75
+ expectedRevision: number
76
+ collection: string
77
+ documentId: string
78
+ reconcile?: boolean
79
+ }) => input
80
+ )
81
+ .handler(
82
+ withDocumentMutationErrors(async ({ data }) => {
83
+ const { collection: path, documentId } = data
84
+ const config = await ensureCollection(path)
85
+ if (!config) {
86
+ throw ERR_NOT_FOUND({
87
+ message: 'Collection not found',
88
+ details: { collectionPath: path },
89
+ }).log(getLogger())
90
+ }
91
+ const result = await removeAdminTreeNode(
92
+ getAdminBylineClient().collection(path),
93
+ documentId,
94
+ {
95
+ expectedRevision: data.expectedRevision,
96
+ reconcile: data.reconcile,
97
+ }
98
+ )
99
+ return { status: 'ok' as const, ...result }
77
100
  })
78
- return { status: 'ok' as const }
79
- })
101
+ )
80
102
 
81
103
  // ---------------------------------------------------------------------------
82
104
  // Resolve a document's ancestor chain (root-first), hydrated with titles
83
105
  // ---------------------------------------------------------------------------
84
106
 
85
107
  export const getTreeAncestors = createServerFn({ method: 'GET' })
108
+ .middleware([adminSessionMiddleware])
86
109
  .validator((input: { collection: string; documentId: string }) => input)
87
- .handler(async ({ data }) => {
88
- const { collection: path, documentId } = data
89
- const config = await ensureCollection(path)
90
- if (!config) {
91
- throw ERR_NOT_FOUND({
92
- message: 'Collection not found',
93
- details: { collectionPath: path },
94
- }).log(getLogger())
95
- }
96
- const useAsTitle = config.definition.useAsTitle
97
- // Admin context: read with `status: 'any'` so draft ancestors still show.
98
- const ancestors = await getAdminBylineClient()
99
- .collection(path)
100
- .getAncestors(documentId, { status: 'any' })
101
- return ancestors.map((doc) => {
102
- const fields = doc.fields as Record<string, any> | undefined
103
- const title = useAsTitle ? fields?.[useAsTitle] : undefined
104
- return {
105
- id: doc.id,
106
- title: typeof title === 'string' && title.length > 0 ? title : doc.path,
107
- path: doc.path,
110
+ .handler(
111
+ withDocumentMutationErrors(async ({ data }) => {
112
+ const { collection: path, documentId } = data
113
+ const config = await ensureCollection(path)
114
+ if (!config) {
115
+ throw ERR_NOT_FOUND({
116
+ message: 'Collection not found',
117
+ details: { collectionPath: path },
118
+ }).log(getLogger())
108
119
  }
120
+ const useAsTitle = config.definition.useAsTitle
121
+ // Admin context: read with `status: 'any'` so draft ancestors still show.
122
+ const ancestors = await getAdminBylineClient()
123
+ .collection(path)
124
+ .getAncestors(documentId, { status: 'any' })
125
+ return ancestors.map((doc) => {
126
+ const fields = doc.fields as Record<string, any> | undefined
127
+ const title = useAsTitle ? fields?.[useAsTitle] : undefined
128
+ return {
129
+ id: doc.id,
130
+ title: typeof title === 'string' && title.length > 0 ? title : doc.path,
131
+ path: doc.path,
132
+ }
133
+ })
109
134
  })
110
- })
135
+ )
111
136
 
112
137
  // ---------------------------------------------------------------------------
113
138
  // Resolve a document's placement state in the tree (unplaced / root / child)
114
139
  // ---------------------------------------------------------------------------
115
140
 
116
141
  export const getTreeParent = createServerFn({ method: 'GET' })
142
+ .middleware([adminSessionMiddleware])
117
143
  .validator((input: { collection: string; documentId: string }) => input)
118
- .handler(async ({ data }) => {
119
- const { collection: path, documentId } = data
120
- const config = await ensureCollection(path)
121
- if (!config) {
122
- throw ERR_NOT_FOUND({
123
- message: 'Collection not found',
124
- details: { collectionPath: path },
125
- }).log(getLogger())
126
- }
127
- return getAdminTreeParent(getAdminBylineClient().collection(path), documentId)
128
- })
144
+ .handler(
145
+ withDocumentMutationErrors(async ({ data }) => {
146
+ const { collection: path, documentId } = data
147
+ const config = await ensureCollection(path)
148
+ if (!config) {
149
+ throw ERR_NOT_FOUND({
150
+ message: 'Collection not found',
151
+ details: { collectionPath: path },
152
+ }).log(getLogger())
153
+ }
154
+ return getAdminTreeParent(getAdminBylineClient().collection(path), documentId)
155
+ })
156
+ )
129
157
 
130
158
  // ---------------------------------------------------------------------------
131
159
  // Read the whole collection tree as ordered, depth-tagged rows for the built-in
@@ -135,6 +163,7 @@ export const getTreeParent = createServerFn({ method: 'GET' })
135
163
  // ---------------------------------------------------------------------------
136
164
 
137
165
  export interface CollectionTreeRow {
166
+ revision: number
138
167
  id: string
139
168
  /** Parent document id, or `null` for a root or unplaced node. Drives the
140
169
  * client-side tree reconstruction for drag-to-reorder / re-parent. */
@@ -149,71 +178,76 @@ export interface CollectionTreeRow {
149
178
  }
150
179
 
151
180
  export const getCollectionTree = createServerFn({ method: 'GET' })
181
+ .middleware([adminSessionMiddleware])
152
182
  .validator((input: { collection: string; locale?: string }) => input)
153
- .handler(async ({ data }) => {
154
- const { collection: path } = data
155
- const config = await ensureCollection(path)
156
- if (!config) {
157
- throw ERR_NOT_FOUND({
158
- message: 'Collection not found',
159
- details: { collectionPath: path },
160
- }).log(getLogger())
161
- }
162
- const handle = getAdminBylineClient().collection(path)
163
- // Shared deliberately across the structural and unplaced reads: the
164
- // beforeRead predicate cache is per-ReadContext, so sharing gives both
165
- // halves one predicate snapshot — a time-dependent predicate (embargo
166
- // cutoff) evaluated twice could otherwise disagree between the placed
167
- // forest and the unplaced list. The request-memoized context factory
168
- // keeps this sharing safe (one request authority per request).
169
- const readContext = createReadContext()
170
-
171
- const forest = await handle.getSubtree({
172
- status: 'any',
173
- locale: data.locale,
174
- _readContext: readContext,
175
- })
176
- const rows: CollectionTreeRow[] = []
177
- const placed = new Set<string>()
178
- const walk = (nodes: typeof forest, depth: number, parentId: string | null): void => {
179
- for (const node of nodes) {
180
- const doc = node.document
183
+ .handler(
184
+ withDocumentMutationErrors(async ({ data }) => {
185
+ const { collection: path } = data
186
+ const config = await ensureCollection(path)
187
+ if (!config) {
188
+ throw ERR_NOT_FOUND({
189
+ message: 'Collection not found',
190
+ details: { collectionPath: path },
191
+ }).log(getLogger())
192
+ }
193
+ const handle = getAdminBylineClient().collection(path)
194
+ // Shared deliberately across the structural and unplaced reads: the
195
+ // beforeRead predicate cache is per-ReadContext, so sharing gives both
196
+ // halves one predicate snapshot — a time-dependent predicate (embargo
197
+ // cutoff) evaluated twice could otherwise disagree between the placed
198
+ // forest and the unplaced list. The request-memoized context factory
199
+ // keeps this sharing safe (one request authority per request).
200
+ const readContext = createReadContext()
201
+
202
+ const { forest, unplaced } = await handle.getTreeForEdit({
203
+ locale: data.locale,
204
+ _readContext: readContext,
205
+ })
206
+ const rows: CollectionTreeRow[] = []
207
+ const placed = new Set<string>()
208
+ const walk = (nodes: typeof forest, depth: number, parentId: string | null): void => {
209
+ for (const node of nodes) {
210
+ const doc = node.document
211
+ rows.push({
212
+ id: doc.id,
213
+ revision: doc.revision,
214
+ parentId,
215
+ depth,
216
+ unplaced: false,
217
+ status: doc.status,
218
+ path: doc.path,
219
+ createdAt: doc.createdAt,
220
+ updatedAt: doc.updatedAt,
221
+ fields: doc.fields as Record<string, any>,
222
+ })
223
+ placed.add(doc.id)
224
+ walk(node.children, depth + 1, doc.id)
225
+ }
226
+ }
227
+ walk(forest, 0, null)
228
+
229
+ // Surface documents not yet in the tree (e.g. freshly created) so they
230
+ // remain reachable. Trees are small by design, so a single wide read is fine.
231
+ for (const doc of unplaced) {
181
232
  rows.push({
182
233
  id: doc.id,
183
- parentId,
184
- depth,
185
- unplaced: false,
234
+ revision: doc.revision,
235
+ parentId: null,
236
+ depth: 0,
237
+ unplaced: true,
186
238
  status: doc.status,
187
239
  path: doc.path,
188
240
  createdAt: doc.createdAt,
189
241
  updatedAt: doc.updatedAt,
190
242
  fields: doc.fields as Record<string, any>,
191
243
  })
192
- placed.add(doc.id)
193
- walk(node.children, depth + 1, doc.id)
194
244
  }
195
- }
196
- walk(forest, 0, null)
197
-
198
- // Surface documents not yet in the tree (e.g. freshly created) so they
199
- // remain reachable. Trees are small by design, so a single wide read is fine.
200
- const unplaced = await getAdminUnplacedTreeDocuments(handle, placed, readContext, data.locale)
201
- for (const doc of unplaced) {
202
- rows.push({
203
- id: doc.id,
204
- parentId: null,
205
- depth: 0,
206
- unplaced: true,
207
- status: doc.status,
208
- path: doc.path,
209
- createdAt: doc.createdAt,
210
- updatedAt: doc.updatedAt,
211
- fields: doc.fields as Record<string, any>,
212
- })
213
- }
214
245
 
215
- return {
216
- rows,
217
- included: { collection: { path: config.collection.path, labels: config.definition.labels } },
218
- }
219
- })
246
+ return {
247
+ rows,
248
+ included: {
249
+ collection: { path: config.collection.path, labels: config.definition.labels },
250
+ },
251
+ }
252
+ })
253
+ )
@@ -11,78 +11,89 @@ import { createServerFn } from '@tanstack/react-start'
11
11
  import { getAdminRequestContext } from '@byline/client/server'
12
12
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from '@byline/core'
13
13
  import type { DocumentPatch } from '@byline/core/patches'
14
- import type {
15
- DocumentLifecycleContext,
16
- UpdateDocumentWithPatchesResult,
17
- } from '@byline/core/services'
18
- import { updateDocumentSystemFields, updateDocumentWithPatches } from '@byline/core/services'
14
+ import type { DocumentLifecycleContext } from '@byline/core/services'
15
+ import { saveDocument, updateDocumentSystemFields } from '@byline/core/services'
19
16
 
20
17
  import { ensureCollection } from '../../integrations/api-utils.js'
18
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
19
+ import { withDocumentMutationErrors } from '../document-mutation-errors.js'
21
20
  import { toCommittedDocumentHookFailureResponse } from './save-outcome.js'
22
21
 
23
22
  // ---------------------------------------------------------------------------
24
- // Apply patches (patch-based update — creates a new immutable version)
25
- //
26
- // Document-grain system fields (`path`, `availableLocales`) are deliberately
27
- // NOT handled here: they are written through their own non-versioned path
28
- // (`updateCollectionDocumentSystemFields` below) so that editing them does not
29
- // mint a new version or reset workflow status. See docs/08-internationalization/index.md.
23
+ // Save content patches and optional metadata in one guarded transaction.
24
+ // Empty patches retain the non-versioned metadata-only behavior.
30
25
  // ---------------------------------------------------------------------------
31
26
 
32
27
  export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POST' })
28
+ .middleware([adminSessionMiddleware])
33
29
  .validator(
34
30
  (input: {
35
31
  collection: string
36
32
  id: string
37
33
  patches: DocumentPatch[]
38
- versionId?: string
34
+ expectedRevision: number
35
+ path?: string | null
36
+ availableLocales?: string[]
39
37
  locale?: string
40
38
  }) => input
41
39
  )
42
- .handler(async ({ data: input }) => {
43
- const { collection: path, id, patches, versionId, locale } = input
44
- const logger = getLogger()
45
- const config = await ensureCollection(path)
46
- if (!config) {
47
- throw ERR_NOT_FOUND({
48
- message: 'Collection not found',
49
- details: { collectionPath: path },
50
- }).log(logger)
51
- }
40
+ .handler(
41
+ withDocumentMutationErrors(async ({ data: input }) => {
42
+ const {
43
+ collection: path,
44
+ id,
45
+ patches,
46
+ expectedRevision,
47
+ locale,
48
+ path: explicitPath,
49
+ availableLocales,
50
+ } = input
51
+ const logger = getLogger()
52
+ const config = await ensureCollection(path)
53
+ if (!config) {
54
+ throw ERR_NOT_FOUND({
55
+ message: 'Collection not found',
56
+ details: { collectionPath: path },
57
+ }).log(logger)
58
+ }
52
59
 
53
- const serverConfig = getServerConfig()
54
- const ctx: DocumentLifecycleContext = {
55
- db: serverConfig.db,
56
- definition: config.definition,
57
- collectionId: config.collection.id,
58
- collectionVersion: config.collection.version,
59
- collectionPath: path,
60
- logger,
61
- defaultLocale: serverConfig.i18n.content.defaultLocale,
62
- slugifier: serverConfig.slugifier,
63
- requestContext: await getAdminRequestContext(),
64
- }
60
+ const serverConfig = getServerConfig()
61
+ const ctx: DocumentLifecycleContext = {
62
+ db: serverConfig.db,
63
+ definition: config.definition,
64
+ collectionId: config.collection.id,
65
+ collectionVersion: config.collection.version,
66
+ collectionPath: path,
67
+ logger,
68
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
69
+ slugifier: serverConfig.slugifier,
70
+ requestContext: await getAdminRequestContext(),
71
+ }
65
72
 
66
- let result: UpdateDocumentWithPatchesResult
67
- try {
68
- result = await updateDocumentWithPatches(ctx, {
69
- documentId: id,
70
- patches,
71
- documentVersionId: versionId,
72
- locale: locale ?? serverConfig.i18n.content.defaultLocale,
73
- })
74
- } catch (error) {
75
- const committedFailure = toCommittedDocumentHookFailureResponse(error)
76
- if (committedFailure != null) return committedFailure
77
- throw error
78
- }
73
+ let result: Awaited<ReturnType<typeof saveDocument>>
74
+ try {
75
+ result = await saveDocument(ctx, {
76
+ documentId: id,
77
+ patches,
78
+ expectedRevision,
79
+ path: explicitPath ?? undefined,
80
+ availableLocales,
81
+ locale: locale ?? serverConfig.i18n.content.defaultLocale,
82
+ })
83
+ } catch (error) {
84
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
85
+ if (committedFailure != null) return committedFailure
86
+ throw error
87
+ }
79
88
 
80
- return {
81
- status: 'ok' as const,
82
- documentId: result.documentId,
83
- documentVersionId: result.documentVersionId,
84
- }
85
- })
89
+ return {
90
+ status: 'ok' as const,
91
+ documentId: result.documentId,
92
+ ...('documentVersionId' in result ? { documentVersionId: result.documentVersionId } : {}),
93
+ revision: result.revision,
94
+ }
95
+ })
96
+ )
86
97
 
87
98
  // ---------------------------------------------------------------------------
88
99
  // System-managed, document-grain fields (path + advertised locales)
@@ -93,10 +104,12 @@ export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POS
93
104
  // ---------------------------------------------------------------------------
94
105
 
95
106
  export const updateCollectionDocumentSystemFields = createServerFn({ method: 'POST' })
107
+ .middleware([adminSessionMiddleware])
96
108
  .validator(
97
109
  (input: {
98
110
  collection: string
99
111
  id: string
112
+ expectedRevision: number
100
113
  locale?: string
101
114
  /** Path override; `null`/omitted means no path write. */
102
115
  path?: string | null
@@ -104,39 +117,53 @@ export const updateCollectionDocumentSystemFields = createServerFn({ method: 'PO
104
117
  availableLocales?: string[]
105
118
  }) => input
106
119
  )
107
- .handler(async ({ data: input }) => {
108
- const { collection: path, id, locale, path: explicitPath, availableLocales } = input
109
- const logger = getLogger()
110
- const config = await ensureCollection(path)
111
- if (!config) {
112
- throw ERR_NOT_FOUND({
113
- message: 'Collection not found',
114
- details: { collectionPath: path },
115
- }).log(logger)
116
- }
120
+ .handler(
121
+ withDocumentMutationErrors(async ({ data: input }) => {
122
+ const {
123
+ collection: path,
124
+ id,
125
+ expectedRevision,
126
+ locale,
127
+ path: explicitPath,
128
+ availableLocales,
129
+ } = input
130
+ const logger = getLogger()
131
+ const config = await ensureCollection(path)
132
+ if (!config) {
133
+ throw ERR_NOT_FOUND({
134
+ message: 'Collection not found',
135
+ details: { collectionPath: path },
136
+ }).log(logger)
137
+ }
117
138
 
118
- const serverConfig = getServerConfig()
119
- const ctx: DocumentLifecycleContext = {
120
- db: serverConfig.db,
121
- definition: config.definition,
122
- collectionId: config.collection.id,
123
- collectionVersion: config.collection.version,
124
- collectionPath: path,
125
- logger,
126
- defaultLocale: serverConfig.i18n.content.defaultLocale,
127
- slugifier: serverConfig.slugifier,
128
- requestContext: await getAdminRequestContext(),
129
- }
139
+ const serverConfig = getServerConfig()
140
+ const ctx: DocumentLifecycleContext = {
141
+ db: serverConfig.db,
142
+ definition: config.definition,
143
+ collectionId: config.collection.id,
144
+ collectionVersion: config.collection.version,
145
+ collectionPath: path,
146
+ logger,
147
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
148
+ slugifier: serverConfig.slugifier,
149
+ requestContext: await getAdminRequestContext(),
150
+ }
130
151
 
131
- await updateDocumentSystemFields(ctx, {
132
- documentId: id,
133
- locale: locale ?? serverConfig.i18n.content.defaultLocale,
134
- path: explicitPath,
135
- availableLocales,
136
- // A failed after-hook happens after commit. Retrying this same admin save
137
- // must re-run cache/search reconciliation when DB values are now current.
138
- reconcile: true,
152
+ try {
153
+ const result = await updateDocumentSystemFields(ctx, {
154
+ expectedRevision,
155
+ documentId: id,
156
+ locale: locale ?? serverConfig.i18n.content.defaultLocale,
157
+ path: explicitPath,
158
+ availableLocales,
159
+ // Reconciliation still requires a current observation.
160
+ reconcile: true,
161
+ })
162
+ return { status: 'ok' as const, revision: result.revision }
163
+ } catch (error) {
164
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
165
+ if (committedFailure != null) return committedFailure
166
+ throw error
167
+ }
139
168
  })
140
-
141
- return { status: 'ok' as const }
142
- })
169
+ )
@@ -7,12 +7,7 @@
7
7
  */
8
8
 
9
9
  import { AdminAuth, createRequestContext } from '@byline/auth'
10
- import {
11
- defineServerConfig,
12
- defineSingleton,
13
- type IDbAdapter,
14
- type IStorageProvider,
15
- } from '@byline/core'
10
+ import { defineServerConfig, defineSingleton, type IStorageProvider } from '@byline/core'
16
11
  import { defineLogger } from '@byline/core/logger'
17
12
  import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
18
13
 
@@ -25,6 +20,9 @@ vi.mock('@tanstack/react-start', () => ({
25
20
  createServerFn: () => {
26
21
  let validate = (input: unknown) => input
27
22
  const chain = {
23
+ middleware() {
24
+ return chain
25
+ },
28
26
  validator(validator: (input: unknown) => unknown) {
29
27
  validate = validator
30
28
  return chain
@@ -54,6 +52,11 @@ vi.mock('../../integrations/byline-core.js', () => ({
54
52
  import { ensureCollection, ensureDocumentResource } from '../../integrations/api-utils.js'
55
53
  import { uploadField } from './upload.js'
56
54
 
55
+ // Handler-level tests isolate transport; session-middleware tests exercise the real pre-handler boundary.
56
+ vi.mock('../../integrations/session-middleware.js', () => ({ adminSessionMiddleware: {} }))
57
+
58
+ import { testAdapter } from '../../../../core/src/storage/db-adapter.test-helper.js'
59
+
57
60
  const SERVER_CONFIG = Symbol.for('__byline_server_config__')
58
61
  const BYLINE_LOGGER = Symbol.for('__byline_logger__')
59
62
  const previousServer = (globalThis as Record<PropertyKey, unknown>)[SERVER_CONFIG]
@@ -112,7 +115,7 @@ beforeAll(() => {
112
115
  )
113
116
  defineServerConfig({
114
117
  collections: [singleton],
115
- db: {} as IDbAdapter,
118
+ db: testAdapter(),
116
119
  storage,
117
120
  i18n: {
118
121
  admin: { defaultLocale: 'en', locales: ['en'] },