@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
@@ -14,6 +14,8 @@ import type { DocumentLifecycleContext } from '@byline/core/services'
14
14
  import { restoreDocumentVersion as restoreDocumentVersionService } from '@byline/core/services'
15
15
 
16
16
  import { ensureCollection } from '../../integrations/api-utils.js'
17
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
18
+ import { withDocumentMutationErrors } from '../document-mutation-errors.js'
17
19
  import { toCommittedDocumentHookFailureResponse } from './save-outcome.js'
18
20
 
19
21
  // ---------------------------------------------------------------------------
@@ -21,46 +23,54 @@ import { toCommittedDocumentHookFailureResponse } from './save-outcome.js'
21
23
  // ---------------------------------------------------------------------------
22
24
 
23
25
  export const restoreDocumentVersion = createServerFn({ method: 'POST' })
24
- .validator((input: { collection: string; id: string; versionId: string }) => input)
25
- .handler(async ({ data: input }) => {
26
- const { collection: path, id, versionId } = input
27
- const logger = getLogger()
28
- const config = await ensureCollection(path)
29
- if (!config) {
30
- throw ERR_NOT_FOUND({
31
- message: 'Collection not found',
32
- details: { collectionPath: path },
33
- }).log(logger)
34
- }
26
+ .middleware([adminSessionMiddleware])
27
+ .validator(
28
+ (input: { expectedRevision: number; collection: string; id: string; versionId: string }) =>
29
+ input
30
+ )
31
+ .handler(
32
+ withDocumentMutationErrors(async ({ data: input }) => {
33
+ const { collection: path, id, versionId } = input
34
+ const logger = getLogger()
35
+ const config = await ensureCollection(path)
36
+ if (!config) {
37
+ throw ERR_NOT_FOUND({
38
+ message: 'Collection not found',
39
+ details: { collectionPath: path },
40
+ }).log(logger)
41
+ }
35
42
 
36
- const serverConfig = getServerConfig()
37
- const ctx: DocumentLifecycleContext = {
38
- db: serverConfig.db,
39
- definition: config.definition,
40
- collectionId: config.collection.id,
41
- collectionVersion: config.collection.version,
42
- collectionPath: path,
43
- logger,
44
- defaultLocale: serverConfig.i18n.content.defaultLocale,
45
- slugifier: serverConfig.slugifier,
46
- requestContext: await getAdminRequestContext(),
47
- }
43
+ const serverConfig = getServerConfig()
44
+ const ctx: DocumentLifecycleContext = {
45
+ db: serverConfig.db,
46
+ definition: config.definition,
47
+ collectionId: config.collection.id,
48
+ collectionVersion: config.collection.version,
49
+ collectionPath: path,
50
+ logger,
51
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
52
+ slugifier: serverConfig.slugifier,
53
+ requestContext: await getAdminRequestContext(),
54
+ }
48
55
 
49
- try {
50
- const result = await restoreDocumentVersionService(ctx, {
51
- documentId: id,
52
- sourceVersionId: versionId,
53
- })
56
+ try {
57
+ const result = await restoreDocumentVersionService(ctx, {
58
+ documentId: id,
59
+ expectedRevision: input.expectedRevision,
60
+ sourceVersionId: versionId,
61
+ })
54
62
 
55
- return {
56
- status: 'ok' as const,
57
- documentId: result.documentId,
58
- documentVersionId: result.documentVersionId,
59
- sourceVersionId: result.sourceVersionId,
63
+ return {
64
+ status: 'ok' as const,
65
+ documentId: result.documentId,
66
+ revision: result.revision,
67
+ documentVersionId: result.documentVersionId,
68
+ sourceVersionId: result.sourceVersionId,
69
+ }
70
+ } catch (error) {
71
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
72
+ if (committedFailure != null) return committedFailure
73
+ throw error
60
74
  }
61
- } catch (error) {
62
- const committedFailure = toCommittedDocumentHookFailureResponse(error)
63
- if (committedFailure != null) return committedFailure
64
- throw error
65
- }
66
- })
75
+ })
76
+ )
@@ -0,0 +1,122 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ // Handler-level tests isolate transport; session-middleware tests exercise the real pre-handler boundary.
10
+ vi.mock('../../integrations/session-middleware.js', () => ({ adminSessionMiddleware: {} }))
11
+
12
+ import { createSuperAdminContext } from '@byline/auth'
13
+ import {
14
+ defineCollection,
15
+ defineServerConfig,
16
+ getDocumentRevisionValidationDetails,
17
+ } from '@byline/core'
18
+ import { defineLogger } from '@byline/core/logger'
19
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
20
+
21
+ import { testAdapter } from '../../../../core/src/storage/db-adapter.test-helper.js'
22
+
23
+ const fixtures = vi.hoisted(() => ({ ensureCollection: vi.fn(), getAdminRequestContext: vi.fn() }))
24
+ // Simulate only the compiler's binding of a validator to a handler. These are
25
+ // the real exported host handlers and core services, not mocked lifecycle calls.
26
+ vi.mock('@tanstack/react-start', () => ({
27
+ createServerFn: () => {
28
+ let validate = (value: unknown) => value
29
+ const chain = {
30
+ middleware() {
31
+ return chain
32
+ },
33
+ validator(fn: (value: unknown) => unknown) {
34
+ validate = fn
35
+ return chain
36
+ },
37
+ handler(fn: (args: { data: unknown }) => Promise<unknown>) {
38
+ return (args: { data: unknown }) => fn({ data: validate(args.data) })
39
+ },
40
+ }
41
+ return chain
42
+ },
43
+ }))
44
+ vi.mock('../../integrations/api-utils.js', () => ({ ensureCollection: fixtures.ensureCollection }))
45
+ vi.mock('@byline/client/server', () => ({
46
+ getAdminRequestContext: fixtures.getAdminRequestContext,
47
+ }))
48
+
49
+ import { deleteDocument } from './delete.js'
50
+ import { unpublishDocument, updateDocumentStatus } from './status.js'
51
+ import {
52
+ updateCollectionDocumentSystemFields,
53
+ updateCollectionDocumentWithPatches,
54
+ } from './update.js'
55
+
56
+ const write = vi.fn((): never => {
57
+ throw new Error('Unexpected document write')
58
+ })
59
+ beforeEach(() => {
60
+ vi.clearAllMocks()
61
+ const silent = () => {}
62
+ defineLogger({
63
+ log: silent,
64
+ fatal: silent,
65
+ error: silent,
66
+ warn: silent,
67
+ info: silent,
68
+ debug: silent,
69
+ trace: silent,
70
+ silent,
71
+ })
72
+ const definition = defineCollection({
73
+ path: 'pages',
74
+ labels: { singular: 'Page', plural: 'Pages' },
75
+ fields: [],
76
+ })
77
+ fixtures.ensureCollection.mockResolvedValue({
78
+ definition,
79
+ collection: { id: 'collection', version: 1 },
80
+ })
81
+ fixtures.getAdminRequestContext.mockResolvedValue(createSuperAdminContext({ id: 'editor' }))
82
+ defineServerConfig({
83
+ collections: [definition],
84
+ db: testAdapter({ withTransaction: write }),
85
+ i18n: {
86
+ admin: { defaultLocale: 'en', locales: [] },
87
+ content: { defaultLocale: 'en', locales: ['en'] },
88
+ },
89
+ })
90
+ })
91
+
92
+ const operations = [
93
+ ['save', updateCollectionDocumentWithPatches, { patches: [] }],
94
+ ['metadata', updateCollectionDocumentSystemFields, { path: 'changed' }],
95
+ ['status', updateDocumentStatus, { status: 'published' }],
96
+ ['unpublish', unpublishDocument, {}],
97
+ ['delete', deleteDocument, {}],
98
+ ] as const
99
+
100
+ describe('raw host revision inputs', () => {
101
+ for (const [name, operation, extra] of operations) {
102
+ for (const revision of [undefined, null, '1', 0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1]) {
103
+ it(`${name} rejects ${String(revision)} without writing`, async () => {
104
+ const data = {
105
+ collection: 'pages',
106
+ id: 'document',
107
+ ...extra,
108
+ ...(revision === undefined ? {} : { expectedRevision: revision }),
109
+ }
110
+ const error = await Reflect.apply(operation, undefined, [{ data }]).catch(
111
+ (error: unknown) => error
112
+ )
113
+ expect(error).toMatchObject({ code: 'ERR_VALIDATION' })
114
+ expect(getDocumentRevisionValidationDetails(error)).toEqual({
115
+ reason:
116
+ revision === undefined ? 'missing_document_revision' : 'invalid_document_revision',
117
+ })
118
+ expect(write).not.toHaveBeenCalled()
119
+ })
120
+ }
121
+ }
122
+ })
@@ -23,6 +23,7 @@ describe('collection save outcome transport', () => {
23
23
  phase: 'afterCreate',
24
24
  documentId: 'doc-1',
25
25
  documentVersionId: 'version-1',
26
+ revision: 2,
26
27
  sideEffectCode: ErrorCodes.STORAGE,
27
28
  privatePath: '/private/cache/key',
28
29
  },
@@ -34,6 +35,7 @@ describe('collection save outcome transport', () => {
34
35
  status: 'committed-hook-failed',
35
36
  documentId: 'doc-1',
36
37
  documentVersionId: 'version-1',
38
+ revision: 2,
37
39
  sideEffectFailure: { phase: 'afterCreate', code: 'ERR_STORAGE' },
38
40
  })
39
41
  expect(JSON.stringify(response)).not.toContain('secret hook failure')
@@ -49,6 +51,7 @@ describe('collection save outcome transport', () => {
49
51
  phase: 'afterSave',
50
52
  documentId: 'doc-singleton',
51
53
  documentVersionId: 'version-singleton',
54
+ revision: 2,
52
55
  sideEffectCode: ErrorCodes.UNHANDLED,
53
56
  },
54
57
  })
@@ -16,6 +16,7 @@ export interface CollectionDocumentCommittedHookFailureResponse {
16
16
  status: 'committed-hook-failed'
17
17
  documentId: string
18
18
  documentVersionId: string
19
+ revision: number
19
20
  sideEffectFailure: {
20
21
  phase: DocumentHookCommittedPhase
21
22
  code: DocumentHookSideEffectCode
@@ -36,6 +37,7 @@ export function toCommittedDocumentHookFailureResponse(
36
37
  status: 'committed-hook-failed',
37
38
  documentId: details.documentId,
38
39
  documentVersionId: details.documentVersionId,
40
+ revision: details.revision,
39
41
  sideEffectFailure: {
40
42
  phase: details.phase,
41
43
  code: details.sideEffectCode,
@@ -21,6 +21,8 @@ import {
21
21
  import { listDocumentPublishSchedules } from '@byline/core/services'
22
22
 
23
23
  import { bylineCore } from '../../integrations/byline-core.js'
24
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
25
+ import { withDocumentMutationErrors } from '../document-mutation-errors.js'
24
26
  import { omitScheduleExecutionState } from './scheduled-publication-response.js'
25
27
  import { serialise } from './utils.js'
26
28
 
@@ -66,6 +68,7 @@ export type SerializedDocumentPublishSchedule = Omit<
66
68
  }
67
69
 
68
70
  export interface ScheduledPublicationListItem extends SerializedDocumentPublishSchedule {
71
+ revision: number
69
72
  collectionPath: string
70
73
  collectionLabel: string
71
74
  documentPath: string | null
@@ -136,52 +139,84 @@ async function scheduledPublicationRuntime(): Promise<ScheduledPublicationRuntim
136
139
  }
137
140
 
138
141
  /** Authenticated feature discovery for the admin shell and health warning. */
139
- export const getScheduledPublicationRuntime = createServerFn({ method: 'GET' }).handler(
140
- async () => {
141
- await getAdminRequestContext()
142
- return scheduledPublicationRuntime()
143
- }
144
- )
142
+ export const getScheduledPublicationRuntime = createServerFn({ method: 'GET' })
143
+ .middleware([adminSessionMiddleware])
144
+ .handler(
145
+ withDocumentMutationErrors(async () => {
146
+ await getAdminRequestContext()
147
+ return scheduledPublicationRuntime()
148
+ })
149
+ )
145
150
 
146
151
  export const scheduleCollectionDocumentPublish = createServerFn({ method: 'POST' })
152
+ .middleware([adminSessionMiddleware])
147
153
  .validator(
148
- (input: { collection: string; id: string; publishAt: string; expectedVersionId: string }) =>
149
- input
154
+ (input: {
155
+ expectedRevision: number
156
+ collection: string
157
+ id: string
158
+ publishAt: string
159
+ expectedVersionId: string
160
+ }) => input
161
+ )
162
+ .handler(
163
+ withDocumentMutationErrors(async ({ data }) => {
164
+ assertFeatureEnabled()
165
+ const schedule = await getAdminBylineClient()
166
+ .collection(data.collection)
167
+ .schedulePublish(data.id, {
168
+ publishAt: data.publishAt,
169
+ expectedRevision: data.expectedRevision,
170
+ expectedVersionId: data.expectedVersionId,
171
+ })
172
+ return { ...serializeSchedule(schedule), revision: schedule.revision }
173
+ })
150
174
  )
151
- .handler(async ({ data }) => {
152
- assertFeatureEnabled()
153
- const schedule = await getAdminBylineClient()
154
- .collection(data.collection)
155
- .schedulePublish(data.id, {
156
- publishAt: data.publishAt,
157
- expectedVersionId: data.expectedVersionId,
158
- })
159
- return serializeSchedule(schedule)
160
- })
161
175
 
162
176
  export const confirmCollectionDocumentScheduledPublish = createServerFn({ method: 'POST' })
163
- .validator((input: { collection: string; id: string; expectedVersionId: string }) => input)
164
- .handler(async ({ data }) => {
165
- assertFeatureEnabled()
166
- const schedule = await getAdminBylineClient()
167
- .collection(data.collection)
168
- .confirmScheduledPublish(data.id, { expectedVersionId: data.expectedVersionId })
169
- return serializeSchedule(schedule)
170
- })
177
+ .middleware([adminSessionMiddleware])
178
+ .validator(
179
+ (input: {
180
+ expectedRevision: number
181
+ collection: string
182
+ id: string
183
+ expectedVersionId: string
184
+ }) => input
185
+ )
186
+ .handler(
187
+ withDocumentMutationErrors(async ({ data }) => {
188
+ assertFeatureEnabled()
189
+ const schedule = await getAdminBylineClient()
190
+ .collection(data.collection)
191
+ .confirmScheduledPublish(data.id, {
192
+ expectedRevision: data.expectedRevision,
193
+ expectedVersionId: data.expectedVersionId,
194
+ })
195
+ return { ...serializeSchedule(schedule), revision: schedule.revision }
196
+ })
197
+ )
171
198
 
172
199
  export const cancelCollectionDocumentScheduledPublish = createServerFn({ method: 'POST' })
173
- .validator((input: { collection: string; id: string }) => input)
174
- .handler(async ({ data }) => {
175
- assertFeatureEnabled()
176
- const schedule = await getAdminBylineClient()
177
- .collection(data.collection)
178
- .cancelScheduledPublish(data.id)
179
- return schedule == null
180
- ? { status: 'not_found' as const }
181
- : { status: 'cancelled' as const, schedule: serializeSchedule(schedule) }
182
- })
200
+ .middleware([adminSessionMiddleware])
201
+ .validator((input: { expectedRevision: number; collection: string; id: string }) => input)
202
+ .handler(
203
+ withDocumentMutationErrors(async ({ data }) => {
204
+ assertFeatureEnabled()
205
+ const schedule = await getAdminBylineClient()
206
+ .collection(data.collection)
207
+ .cancelScheduledPublish(data.id, { expectedRevision: data.expectedRevision })
208
+ return schedule.schedule == null
209
+ ? { status: 'not_found' as const, revision: schedule.revision }
210
+ : {
211
+ status: 'cancelled' as const,
212
+ schedule: serializeSchedule(schedule.schedule),
213
+ revision: schedule.revision,
214
+ }
215
+ })
216
+ )
183
217
 
184
218
  export const listScheduledPublications = createServerFn({ method: 'GET' })
219
+ .middleware([adminSessionMiddleware])
185
220
  .validator(
186
221
  (input: {
187
222
  states?: readonly DocumentPublishScheduleState[]
@@ -190,87 +225,86 @@ export const listScheduledPublications = createServerFn({ method: 'GET' })
190
225
  pageSize?: number
191
226
  }) => input
192
227
  )
193
- .handler(async ({ data }) => {
194
- assertFeatureEnabled()
195
- if (data.states?.some((state) => state !== 'armed' && state !== 'needs_reconfirm') === true) {
196
- throw ERR_VALIDATION({ message: 'state must be armed or needs_reconfirm' })
197
- }
198
- if (data.lastAuthorizedBy != null && !UUID_RE.test(data.lastAuthorizedBy)) {
199
- throw ERR_VALIDATION({ message: 'lastAuthorizedBy must be a UUID' })
200
- }
201
- const core = bylineCore()
202
- const page = data.page ?? 1
203
- const pageSize = data.pageSize ?? 25
204
- const requestContext = await getAdminRequestContext()
205
- const result = await listDocumentPublishSchedules(core, requestContext, {
206
- states: data.states,
207
- lastAuthorizedBy: data.lastAuthorizedBy,
208
- page,
209
- pageSize,
210
- })
211
- const collections = new Map(
212
- core.collections.map((definition) => {
213
- const record = core.getCollectionRecord(definition.path)
214
- return [record.collectionId, definition] as const
228
+ .handler(
229
+ withDocumentMutationErrors(async ({ data }) => {
230
+ assertFeatureEnabled()
231
+ if (data.states?.some((state) => state !== 'armed' && state !== 'needs_reconfirm') === true) {
232
+ throw ERR_VALIDATION({ message: 'state must be armed or needs_reconfirm' })
233
+ }
234
+ if (data.lastAuthorizedBy != null && !UUID_RE.test(data.lastAuthorizedBy)) {
235
+ throw ERR_VALIDATION({ message: 'lastAuthorizedBy must be a UUID' })
236
+ }
237
+ const core = bylineCore()
238
+ const page = data.page ?? 1
239
+ const pageSize = data.pageSize ?? 25
240
+ const requestContext = await getAdminRequestContext()
241
+ const result = await listDocumentPublishSchedules(core, requestContext, {
242
+ states: data.states,
243
+ lastAuthorizedBy: data.lastAuthorizedBy,
244
+ page,
245
+ pageSize,
215
246
  })
216
- )
217
- // Resolve authorizer names in one query for the whole page rather than one
218
- // per row. `last_authorized_by` is deliberately not a foreign key — deleting
219
- // an admin account must not revoke a schedule it authorized — so an id here
220
- // may simply have no user row left, and the caller falls back to the id.
221
- const authorizerIds = [
222
- ...new Set(
223
- result.schedules
224
- .map((schedule) => schedule.lastAuthorizedBy)
225
- .filter((id): id is string => id != null)
226
- ),
227
- ]
228
- const authorizerNames = new Map<string, string>()
229
- // `adminStore` is optional on core — an installation can run without the
230
- // admin subsystem — so a missing store simply means no names to resolve and
231
- // the column falls back to ids.
232
- const adminUsers = core.adminStore?.adminUsers
233
- if (authorizerIds.length > 0 && adminUsers != null) {
234
- const users = await adminUsers.getByIds(authorizerIds)
235
- for (const user of users) {
236
- const fullName = [user.given_name, user.family_name].filter(Boolean).join(' ').trim()
237
- authorizerNames.set(user.id, fullName.length > 0 ? fullName : user.email)
247
+ const collections = new Map(
248
+ core.collections.map((definition) => {
249
+ const record = core.getCollectionRecord(definition.path)
250
+ return [record.collectionId, definition] as const
251
+ })
252
+ )
253
+ // Resolve authorizer names in one query for the whole page rather than one
254
+ // per row. `last_authorized_by` is deliberately not a foreign key — deleting
255
+ // an admin account must not revoke a schedule it authorized — so an id here
256
+ // may simply have no user row left, and the caller falls back to the id.
257
+ const authorizerIds = [
258
+ ...new Set(
259
+ result.schedules
260
+ .map((schedule) => schedule.lastAuthorizedBy)
261
+ .filter((id): id is string => id != null)
262
+ ),
263
+ ]
264
+ const authorizerNames = new Map<string, string>()
265
+ // `adminStore` is optional on core — an installation can run without the
266
+ // admin subsystem — so a missing store simply means no names to resolve and
267
+ // the column falls back to ids.
268
+ const adminUsers = core.adminStore?.adminUsers
269
+ if (authorizerIds.length > 0 && adminUsers != null) {
270
+ const users = await adminUsers.getByIds(authorizerIds)
271
+ for (const user of users) {
272
+ const fullName = [user.given_name, user.family_name].filter(Boolean).join(' ').trim()
273
+ authorizerNames.set(user.id, fullName.length > 0 ? fullName : user.email)
274
+ }
238
275
  }
239
- }
240
276
 
241
- const schedules = await Promise.all(
242
- result.schedules.map(async (schedule): Promise<ScheduledPublicationListItem> => {
243
- const definition = collections.get(schedule.collectionId)
244
- const documentPath = await core.db.queries.documents.getCurrentPath({
245
- collection_id: schedule.collectionId,
246
- document_id: schedule.documentId,
277
+ const schedules = await Promise.all(
278
+ result.schedules.map(async (schedule): Promise<ScheduledPublicationListItem> => {
279
+ const definition = collections.get(schedule.collectionId)
280
+ return {
281
+ ...serializeSchedule(schedule),
282
+ collectionPath: definition?.path ?? schedule.collectionId,
283
+ collectionLabel:
284
+ definition == null
285
+ ? schedule.collectionId
286
+ : isSingleton(definition)
287
+ ? definition.label
288
+ : definition.labels.plural,
289
+ documentPath: schedule.documentPath,
290
+ revision: schedule.revision,
291
+ lastAuthorizedByName:
292
+ schedule.lastAuthorizedBy == null
293
+ ? null
294
+ : (authorizerNames.get(schedule.lastAuthorizedBy) ?? null),
295
+ }
247
296
  })
248
- return {
249
- ...serializeSchedule(schedule),
250
- collectionPath: definition?.path ?? schedule.collectionId,
251
- collectionLabel:
252
- definition == null
253
- ? schedule.collectionId
254
- : isSingleton(definition)
255
- ? definition.label
256
- : definition.labels.plural,
257
- documentPath,
258
- lastAuthorizedByName:
259
- schedule.lastAuthorizedBy == null
260
- ? null
261
- : (authorizerNames.get(schedule.lastAuthorizedBy) ?? null),
262
- }
263
- })
264
- )
297
+ )
265
298
 
266
- return {
267
- schedules,
268
- meta: {
269
- page,
270
- pageSize,
271
- total: result.total,
272
- totalPages: Math.max(1, Math.ceil(result.total / pageSize)),
273
- },
274
- runtime: await scheduledPublicationRuntime(),
275
- } satisfies ScheduledPublicationListResponse
276
- })
299
+ return {
300
+ schedules,
301
+ meta: {
302
+ page,
303
+ pageSize,
304
+ total: result.total,
305
+ totalPages: Math.max(1, Math.ceil(result.total / pageSize)),
306
+ },
307
+ runtime: await scheduledPublicationRuntime(),
308
+ } satisfies ScheduledPublicationListResponse
309
+ })
310
+ )
@@ -27,6 +27,7 @@ import { ERR_NOT_FOUND, getLogger } from '@byline/core'
27
27
 
28
28
  import { ensureCollection } from '../../integrations/api-utils.js'
29
29
  import { bylineCore } from '../../integrations/byline-core.js'
30
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
30
31
 
31
32
  export interface SetListViewPreferenceInput {
32
33
  collection: string
@@ -38,6 +39,7 @@ export interface SetListViewPreferenceInput {
38
39
  }
39
40
 
40
41
  export const setListViewPreference = createServerFn({ method: 'POST' })
42
+ .middleware([adminSessionMiddleware])
41
43
  .validator((input: SetListViewPreferenceInput) => input)
42
44
  .handler(async ({ data }) => {
43
45
  // Scopes are keyed by collection path — refuse to mint rows for
@@ -11,6 +11,7 @@ import { createServerFn } from '@tanstack/react-start'
11
11
  import { getAdminBylineClient } from '@byline/client/server'
12
12
 
13
13
  import { ensureCollection } from '../../integrations/api-utils.js'
14
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
14
15
 
15
16
  // ---------------------------------------------------------------------------
16
17
  // Shared types
@@ -26,6 +27,7 @@ export interface CollectionStatusCount {
26
27
  // ---------------------------------------------------------------------------
27
28
 
28
29
  const getCollectionStatsFn = createServerFn({ method: 'GET' })
30
+ .middleware([adminSessionMiddleware])
29
31
  .validator((input: { collection: string }) => input)
30
32
  .handler(async ({ data }) => {
31
33
  const config = await ensureCollection(data.collection)