@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
@@ -1,3 +1,11 @@
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
+
1
9
  import { createServerFn } from '@tanstack/react-start'
2
10
 
3
11
  import { getAdminRequestContext } from '@byline/client/server'
@@ -14,6 +22,8 @@ import { getLogger, withLogContext } from '@byline/core/logger'
14
22
  import { uploadField as coreUploadField } from '@byline/core/services'
15
23
 
16
24
  import { ensureDocumentResource } from '../../integrations/api-utils.js'
25
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
26
+ import { withDocumentMutationErrors } from '../document-mutation-errors.js'
17
27
 
18
28
  /**
19
29
  * Result of an upload through the host transport. The legacy top-level
@@ -25,6 +35,7 @@ export interface UploadFieldResult {
25
35
  /** Present when the upload endpoint created a document (createDocument=true, the default). */
26
36
  documentId?: string
27
37
  documentVersionId?: string
38
+ revision?: number
28
39
  storedFile: StoredFileValue
29
40
  }
30
41
 
@@ -148,89 +159,92 @@ function resolveUploadField(
148
159
  * without depending on TanStack Start server-function transport details.
149
160
  */
150
161
  export const uploadCollectionField = createServerFn({ method: 'POST' })
162
+ .middleware([adminSessionMiddleware])
151
163
  .validator(parseUploadFormData)
152
- .handler(async ({ data }) => {
153
- const { collectionPath, shouldCreateDocument, fieldName, file, fields } = data
154
- const logger = getLogger()
164
+ .handler(
165
+ withDocumentMutationErrors(async ({ data }) => {
166
+ const { collectionPath, shouldCreateDocument, fieldName, file, fields } = data
167
+ const logger = getLogger()
155
168
 
156
- return withLogContext(
157
- { domain: 'api', module: 'upload', function: 'uploadCollectionField' },
158
- async () => {
159
- // Upload is the one field transport shared by both document-resource
160
- // kinds. The core service selects the kind-aware ability and rejects
161
- // singleton document creation before hooks or storage; the admin form
162
- // always requests the field-only branch (`createDocument=false`).
163
- const config = await ensureDocumentResource(collectionPath)
164
- if (config == null) {
165
- throw ERR_NOT_FOUND(
166
- {
167
- message: 'Document resource not found.',
168
- details: { collectionPath },
169
- },
170
- uploadCollectionField
171
- ).log(logger)
172
- }
169
+ return withLogContext(
170
+ { domain: 'api', module: 'upload', function: 'uploadCollectionField' },
171
+ async () => {
172
+ // Upload is the one field transport shared by both document-resource
173
+ // kinds. The core service selects the kind-aware ability and rejects
174
+ // singleton document creation before hooks or storage; the admin form
175
+ // always requests the field-only branch (`createDocument=false`).
176
+ const config = await ensureDocumentResource(collectionPath)
177
+ if (config == null) {
178
+ throw ERR_NOT_FOUND(
179
+ {
180
+ message: 'Document resource not found.',
181
+ details: { collectionPath },
182
+ },
183
+ uploadCollectionField
184
+ ).log(logger)
185
+ }
173
186
 
174
- const serverConfig = getServerConfig()
175
- const targetField = resolveUploadField(config.definition, collectionPath, fieldName)
176
- // Per-field storage routing falls through to the site-wide default.
177
- const storage = targetField.upload?.storage ?? serverConfig.storage
178
- if (!storage) {
179
- throw new Error(
180
- `No storage provider configured for field '${targetField.name}' on collection ` +
181
- `'${collectionPath}'. Set either field.upload.storage or the site-wide ` +
182
- 'ServerConfig.storage.'
183
- )
184
- }
187
+ const serverConfig = getServerConfig()
188
+ const targetField = resolveUploadField(config.definition, collectionPath, fieldName)
189
+ // Per-field storage routing falls through to the site-wide default.
190
+ const storage = targetField.upload?.storage ?? serverConfig.storage
191
+ if (!storage) {
192
+ throw new Error(
193
+ `No storage provider configured for field '${targetField.name}' on collection ` +
194
+ `'${collectionPath}'. Set either field.upload.storage or the site-wide ` +
195
+ 'ServerConfig.storage.'
196
+ )
197
+ }
185
198
 
186
- let buffer: Buffer
187
- try {
188
- buffer = Buffer.from(await file.arrayBuffer())
189
- } catch (err: unknown) {
190
- logger.error({ err, collectionPath }, 'failed to read file buffer')
191
- throw new Error('Failed to read uploaded file.')
192
- }
199
+ let buffer: Buffer
200
+ try {
201
+ buffer = Buffer.from(await file.arrayBuffer())
202
+ } catch (err: unknown) {
203
+ logger.error({ err, collectionPath }, 'failed to read file buffer')
204
+ throw new Error('Failed to read uploaded file.')
205
+ }
193
206
 
194
- const ctx: FieldUploadContext = {
195
- db: serverConfig.db,
196
- definition: config.definition,
197
- collectionId: config.collection.id,
198
- collectionVersion: config.collection.version,
199
- collectionPath,
200
- fieldName: targetField.name,
201
- storage,
202
- logger,
203
- defaultLocale: serverConfig.i18n.content.defaultLocale,
204
- slugifier: serverConfig.slugifier,
205
- filenameSlugifier: serverConfig.uploads?.filenameSlugifier,
206
- requestContext: await getAdminRequestContext(),
207
- imageProcessor: {
208
- extractMeta: extractImageMeta,
209
- isBypassMimeType,
210
- generateVariants: ({ buffer, mimeType, storedFile, storage, upload, logger }) =>
211
- generateImageVariants(
212
- buffer,
213
- mimeType,
214
- storedFile,
215
- storage,
216
- upload.sizes ?? [],
217
- logger
218
- ),
219
- },
220
- }
207
+ const ctx: FieldUploadContext = {
208
+ db: serverConfig.db,
209
+ definition: config.definition,
210
+ collectionId: config.collection.id,
211
+ collectionVersion: config.collection.version,
212
+ collectionPath,
213
+ fieldName: targetField.name,
214
+ storage,
215
+ logger,
216
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
217
+ slugifier: serverConfig.slugifier,
218
+ filenameSlugifier: serverConfig.uploads?.filenameSlugifier,
219
+ requestContext: await getAdminRequestContext(),
220
+ imageProcessor: {
221
+ extractMeta: extractImageMeta,
222
+ isBypassMimeType,
223
+ generateVariants: ({ buffer, mimeType, storedFile, storage, upload, logger }) =>
224
+ generateImageVariants(
225
+ buffer,
226
+ mimeType,
227
+ storedFile,
228
+ storage,
229
+ upload.sizes ?? [],
230
+ logger
231
+ ),
232
+ },
233
+ }
221
234
 
222
- return coreUploadField(ctx, {
223
- buffer,
224
- originalFilename: file.name || 'upload',
225
- mimeType: file.type || 'application/octet-stream',
226
- fileSize: file.size,
227
- fields,
228
- shouldCreateDocument,
229
- locale: serverConfig.i18n.content.defaultLocale,
230
- })
231
- }
232
- )
233
- })
235
+ return coreUploadField(ctx, {
236
+ buffer,
237
+ originalFilename: file.name || 'upload',
238
+ mimeType: file.type || 'application/octet-stream',
239
+ fileSize: file.size,
240
+ fields,
241
+ shouldCreateDocument,
242
+ locale: serverConfig.i18n.content.defaultLocale,
243
+ })
244
+ }
245
+ )
246
+ })
247
+ )
234
248
 
235
249
  /**
236
250
  * Upload a file to an upload-enabled collection.
@@ -0,0 +1,67 @@
1
+ import {
2
+ BylineError,
3
+ ErrorCodes,
4
+ getDocumentRevisionValidationDetails,
5
+ getDocumentStaleDetails,
6
+ getLockConflictDetails,
7
+ } from '@byline/core'
8
+ import { getDocumentHookCommittedDetails } from '@byline/core/services'
9
+
10
+ import { committedTreeReceipt } from './structural-receipt.js'
11
+
12
+ /** Error subclasses lose custom properties in the server-function serializer.
13
+ * Throw only validated plain data for document failures; never transport causes,
14
+ * SQL, hook messages or an unverified current revision. */
15
+ export function documentMutationError(error: unknown): unknown {
16
+ const stale = getDocumentStaleDetails(error)
17
+ if (stale)
18
+ return {
19
+ code: ErrorCodes.DOCUMENT_STALE,
20
+ message: 'This document has changed. Reload before making changes.',
21
+ details: stale,
22
+ }
23
+ const validation = getDocumentRevisionValidationDetails(error)
24
+ if (validation)
25
+ return {
26
+ code: ErrorCodes.VALIDATION,
27
+ message: 'Reload this document before making changes.',
28
+ details: validation,
29
+ }
30
+ const lock = getLockConflictDetails(error)
31
+ if (lock)
32
+ return {
33
+ code: ErrorCodes.LOCK_CONFLICT,
34
+ message:
35
+ 'This change could not be saved because another operation was using the document. Reload before trying again.',
36
+ details: lock,
37
+ }
38
+ const tree = committedTreeReceipt(error)
39
+ if (tree)
40
+ return {
41
+ code: ErrorCodes.TREE_HOOK_COMMITTED,
42
+ message: 'The structure was saved, but a follow-up action failed.',
43
+ details: tree,
44
+ }
45
+ const committed = getDocumentHookCommittedDetails(error)
46
+ if (committed)
47
+ return {
48
+ code: ErrorCodes.DOCUMENT_HOOK_COMMITTED,
49
+ message: 'The document was saved, but a follow-up action failed.',
50
+ details: committed,
51
+ }
52
+ if (error instanceof BylineError && error.code === ErrorCodes.NOT_FOUND)
53
+ return { code: ErrorCodes.NOT_FOUND, message: 'This document is no longer available.' }
54
+ return error
55
+ }
56
+
57
+ export function withDocumentMutationErrors<T, R>(
58
+ handler: (input: T) => Promise<R>
59
+ ): (input: T) => Promise<R> {
60
+ return async (input) => {
61
+ try {
62
+ return await handler(input)
63
+ } catch (error) {
64
+ throw documentMutationError(error)
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,211 @@
1
+ import { createRequire } from 'node:module'
2
+ import { dirname, join } from 'node:path'
3
+
4
+ import {
5
+ ERR_CONFLICT,
6
+ ERR_DOCUMENT_HOOK_COMMITTED,
7
+ ERR_DOCUMENT_STALE,
8
+ ERR_LOCK_CONFLICT,
9
+ ERR_NOT_FOUND,
10
+ ERR_TREE_HOOK_COMMITTED,
11
+ ERR_VALIDATION,
12
+ getDocumentRevisionValidationDetails,
13
+ getDocumentStaleDetails,
14
+ getLockConflictDetails,
15
+ } from '@byline/core'
16
+ import { getDocumentHookCommittedDetails } from '@byline/core/services'
17
+ import { beforeAll, describe, expect, it, vi } from 'vitest'
18
+
19
+ import { documentMutationError, withDocumentMutationErrors } from './document-mutation-errors.js'
20
+
21
+ // Exercise the installed Start HTTP handler and its real Seroval protocol. Only
22
+ // manifest lookup and request-local response state are supplied by this harness;
23
+ // the production application's compiled actions are covered by browser tests.
24
+ const require = createRequire(import.meta.url)
25
+ const startRequire = createRequire(require.resolve('@tanstack/react-start/package.json'))
26
+ const serverPackage = startRequire.resolve('@tanstack/start-server-core/package.json')
27
+ const esm = join(dirname(serverPackage), 'src')
28
+ const serverRequire = createRequire(serverPackage)
29
+ let handler: (input: { request: Request; context: object; serverFnId: string }) => Promise<Response>
30
+ let seroval: {
31
+ toJSONAsync(value: unknown): Promise<unknown>
32
+ fromCrossJSON(value: unknown, options: object): unknown
33
+ }
34
+ let runWithStartContext: (context: object, fn: () => Promise<Response>) => Promise<Response>
35
+ let action: (input: { data: unknown }) => Promise<unknown>
36
+
37
+ beforeAll(async () => {
38
+ vi.doMock(join(esm, 'getServerFnById.ts'), () => ({ getServerFnById: async () => action }))
39
+ vi.doMock(join(esm, 'request-response.ts'), () => ({
40
+ getResponse: () => ({ status: 200, headers: new Headers() }),
41
+ }))
42
+ handler = (await import(join(esm, 'server-functions-handler.ts'))).handleServerAction
43
+ seroval = await import(serverRequire.resolve('seroval'))
44
+ runWithStartContext = (
45
+ await import(
46
+ join(
47
+ dirname(serverRequire.resolve('@tanstack/start-storage-context/package.json')),
48
+ 'dist/esm/index.js'
49
+ )
50
+ )
51
+ ).runWithStartContext
52
+ })
53
+
54
+ async function roundTrip(data: unknown, operation: (data: unknown) => Promise<unknown>) {
55
+ const guarded = withDocumentMutationErrors(operation)
56
+ action = async ({ data }) => {
57
+ try {
58
+ return { result: await guarded(data) }
59
+ } catch (error) {
60
+ return { error }
61
+ }
62
+ }
63
+ const request = new Request('http://localhost/_serverFn/document-mutation', {
64
+ method: 'POST',
65
+ headers: { 'content-type': 'application/json', 'x-tsr-serverFn': 'true' },
66
+ body: JSON.stringify(await seroval.toJSONAsync({ data })),
67
+ })
68
+ const response = await runWithStartContext(
69
+ {
70
+ request,
71
+ startOptions: {},
72
+ contextAfterGlobalMiddlewares: {},
73
+ executedRequestMiddlewares: new Set(),
74
+ handlerType: 'serverFn',
75
+ getRouter: () => {
76
+ throw new Error('Router not used')
77
+ },
78
+ },
79
+ () => handler({ request, context: {}, serverFnId: 'document-mutation' })
80
+ )
81
+ const wire = await response.text()
82
+ const decoded = seroval.fromCrossJSON(JSON.parse(wire), {})
83
+ if (typeof decoded !== 'object' || decoded === null) throw new Error('Expected wire envelope')
84
+ return {
85
+ value: { error: Reflect.get(decoded, 'error'), result: Reflect.get(decoded, 'result') },
86
+ wire,
87
+ }
88
+ }
89
+
90
+ const staleCases = [
91
+ { reason: 'revision_mismatch', documentId: 'doc', expectedRevision: 1, currentRevision: 2 },
92
+ {
93
+ reason: 'version_parent_mismatch',
94
+ documentId: 'doc',
95
+ previousVersionId: 'old',
96
+ currentVersionId: 'new',
97
+ },
98
+ {
99
+ reason: 'singleton_slot_changed',
100
+ singletonPath: 'settings',
101
+ expectedState: 'empty',
102
+ currentState: 'document',
103
+ },
104
+ ] as const
105
+
106
+ describe('document failures over Start HTTP serialization', () => {
107
+ for (const details of staleCases) {
108
+ it(`retains ${details.reason} and excludes diagnostics`, async () => {
109
+ const { value, wire } = await roundTrip({}, async () => {
110
+ throw ERR_DOCUMENT_STALE({
111
+ message: 'private driver detail',
112
+ details,
113
+ cause: new Error('SELECT secret'),
114
+ logExtra: { private: true },
115
+ })
116
+ })
117
+ expect(getDocumentStaleDetails(value.error)).toEqual(details)
118
+ expect(wire).not.toMatch(/private driver|SELECT secret|logExtra/)
119
+ })
120
+ }
121
+ for (const reason of ['missing_document_revision', 'invalid_document_revision'] as const) {
122
+ it(`retains reload-required validation: ${reason}`, async () => {
123
+ const { value } = await roundTrip({}, async () => {
124
+ throw ERR_VALIDATION({ message: 'internal', details: { reason } })
125
+ })
126
+ expect(getDocumentRevisionValidationDetails(value.error)).toEqual({ reason })
127
+ expect(getDocumentStaleDetails(value.error)).toBeNull()
128
+ })
129
+ }
130
+ it('retains a confirmed rollback without relabelling it stale', async () => {
131
+ const details = { reason: 'lock_conflict', rolledBack: true, retryable: true }
132
+ const { value, wire } = await roundTrip({}, async () => {
133
+ throw ERR_LOCK_CONFLICT({
134
+ message: 'driver message',
135
+ details,
136
+ cause: new Error('SQL private'),
137
+ })
138
+ })
139
+ expect(getLockConflictDetails(value.error)).toEqual(details)
140
+ expect(getDocumentStaleDetails(value.error)).toBeNull()
141
+ expect(wire).not.toMatch(/driver message|SQL private/)
142
+ })
143
+ it('keeps committed hook warnings separate and preserves their committed revision', async () => {
144
+ const details = {
145
+ phase: 'afterUpdate',
146
+ documentId: 'doc',
147
+ documentVersionId: 'version',
148
+ revision: 3,
149
+ sideEffectCode: 'ERR_UNHANDLED',
150
+ }
151
+ const { value, wire } = await roundTrip({}, async () => {
152
+ throw ERR_DOCUMENT_HOOK_COMMITTED({
153
+ message: 'private hook failure',
154
+ details,
155
+ cause: new Error('secret hook'),
156
+ })
157
+ })
158
+ expect(getDocumentHookCommittedDetails(value.error)).toEqual(details)
159
+ expect(getDocumentStaleDetails(value.error)).toBeNull()
160
+ expect(getLockConflictDetails(value.error)).toBeNull()
161
+ expect(wire).not.toMatch(/private hook failure|secret hook/)
162
+ })
163
+ it('round-trips safe integer observations and receipts as numbers', async () => {
164
+ const { value } = await roundTrip(
165
+ { expectedRevision: Number.MAX_SAFE_INTEGER - 1 },
166
+ async (data) => {
167
+ expect(data).toEqual({ expectedRevision: Number.MAX_SAFE_INTEGER - 1 })
168
+ return { documentId: 'doc', revision: Number.MAX_SAFE_INTEGER }
169
+ }
170
+ )
171
+ expect(value.result).toEqual({ documentId: 'doc', revision: Number.MAX_SAFE_INTEGER })
172
+ })
173
+ it('does not classify unrelated or malformed conflicts by message', () => {
174
+ const conflict = ERR_CONFLICT({ message: 'This document has changed.' })
175
+ expect(documentMutationError(conflict)).toBe(conflict)
176
+ const malformed = { code: 'ERR_DOCUMENT_STALE', details: { reason: 'revision_mismatch' } }
177
+ expect(documentMutationError(malformed)).toBe(malformed)
178
+ })
179
+ })
180
+
181
+ it('transports the authorized structural receipt on committed tree hook failure without diagnostics', async () => {
182
+ const receipt = {
183
+ documentId: 'doc',
184
+ revision: 3,
185
+ affectedDocuments: [{ documentId: 'allowed-sibling', revision: 4 }],
186
+ scheduledPublicationsNeedReconfirmation: true,
187
+ }
188
+ const { value, wire } = await roundTrip({}, async () => {
189
+ throw ERR_TREE_HOOK_COMMITTED({
190
+ message: 'private hook diagnostics',
191
+ cause: new Error('SQL secret'),
192
+ details: { ...receipt, change: 'reorder', affectedDocumentIds: ['private'], internal: true },
193
+ })
194
+ })
195
+ expect(value.error).toEqual({
196
+ code: 'ERR_TREE_HOOK_COMMITTED',
197
+ message: 'The structure was saved, but a follow-up action failed.',
198
+ details: receipt,
199
+ })
200
+ expect(wire).not.toMatch(/private|SQL secret|internal/)
201
+ })
202
+ it('transports unavailable-document errors without provider details', async () => {
203
+ const { value, wire } = await roundTrip({}, async () => {
204
+ throw ERR_NOT_FOUND({ message: 'private unavailable reason', details: { internal: true } })
205
+ })
206
+ expect(value.error).toEqual({
207
+ code: 'ERR_NOT_FOUND',
208
+ message: 'This document is no longer available.',
209
+ })
210
+ expect(wire).not.toMatch(/private|internal/)
211
+ })
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { withEditableRevision } from './editable-revision.js'
4
+
5
+ describe('editable response revision binding', () => {
6
+ const observed = { id: 'document', versionId: 'current', revision: 7 }
7
+ it('restores only the source revision without changing populated targets', () => {
8
+ const related = { id: 'related', versionId: 'published', fields: {} }
9
+ expect(
10
+ withEditableRevision({ id: 'document', versionId: 'current', fields: { related } }, observed)
11
+ ).toEqual({ id: 'document', versionId: 'current', revision: 7, fields: { related } })
12
+ expect(related).not.toHaveProperty('revision')
13
+ })
14
+ it.each([
15
+ { id: 'document', versionId: 'historical' },
16
+ { id: 'another-document', versionId: 'current' },
17
+ ])('rejects a transferred observation: %o', (document) => {
18
+ expect(() => withEditableRevision(document, observed)).toThrow('source observation')
19
+ })
20
+ it('rejects a row removed from the editable result', () => {
21
+ expect(() => withEditableRevision(observed, undefined)).toThrow('source observation')
22
+ })
23
+ })
@@ -0,0 +1,18 @@
1
+ import { ERR_DATABASE, isDocumentRevision } from '@byline/core'
2
+
3
+ /** Restore metadata stripped by schema parsing only onto the exact source version.
4
+ * Never attach by array position: filtering/reordering must not transfer tokens. */
5
+ export function withEditableRevision<T extends object>(
6
+ document: T,
7
+ observation: { id: string; versionId: string; revision: number } | undefined
8
+ ): T & { revision: number } {
9
+ if (
10
+ !observation ||
11
+ !isDocumentRevision(observation.revision) ||
12
+ Reflect.get(document, 'id') !== observation.id ||
13
+ Reflect.get(document, 'versionId') !== observation.versionId
14
+ ) {
15
+ throw ERR_DATABASE({ message: 'Editable response no longer matches its source observation' })
16
+ }
17
+ return { ...document, revision: observation.revision }
18
+ }
@@ -25,7 +25,7 @@
25
25
  * switcher works on the sign-in surface — the DB write simply skips.
26
26
  */
27
27
 
28
- import { createServerFn } from '@tanstack/react-start'
28
+ import { createMiddleware, createServerFn } from '@tanstack/react-start'
29
29
 
30
30
  import type { AccountResponse } from '@byline/admin/admin-account'
31
31
  import { setPreferredLocaleCommand } from '@byline/admin/admin-account'
@@ -34,6 +34,7 @@ import { getAdminRequestContext } from '@byline/client/server'
34
34
 
35
35
  import { clearAdminLocaleCookie, setAdminLocaleCookie } from '../../i18n/locale-cookie.js'
36
36
  import { bylineCore } from '../../integrations/byline-core.js'
37
+ import { expectedSession } from '../../integrations/session-coordination.js'
37
38
 
38
39
  export interface SetAdminLocaleInput {
39
40
  /** BCP 47 tag, or `null` to clear the preference. */
@@ -53,9 +54,18 @@ export interface SetAdminLocaleResult {
53
54
  account: AccountResponse | null
54
55
  }
55
56
 
57
+ const localeSession = createMiddleware({ type: 'function' }).client(({ next }) =>
58
+ next({
59
+ sendContext: {
60
+ expectedSessionId: expectedSession(),
61
+ },
62
+ })
63
+ )
64
+
56
65
  export const setAdminLocaleFn = createServerFn({ method: 'POST' })
66
+ .middleware([localeSession])
57
67
  .validator((input: SetAdminLocaleInput) => input)
58
- .handler(async ({ data }): Promise<SetAdminLocaleResult> => {
68
+ .handler(async ({ data, context: caller }): Promise<SetAdminLocaleResult> => {
59
69
  const core = bylineCore()
60
70
  const locales = core.config.i18n.admin.locales
61
71
 
@@ -87,6 +97,9 @@ export const setAdminLocaleFn = createServerFn({ method: 'POST' })
87
97
 
88
98
  try {
89
99
  const context = await getAdminRequestContext()
100
+ if (!caller.expectedSessionId || caller.expectedSessionId !== context.sessionId) {
101
+ return { ok: true as const, locale: data.locale, account: null }
102
+ }
90
103
  const account = await setPreferredLocaleCommand(
91
104
  context,
92
105
  { locale: data.locale },
@@ -94,7 +107,10 @@ export const setAdminLocaleFn = createServerFn({ method: 'POST' })
94
107
  )
95
108
  return { ok: true as const, locale: data.locale, account }
96
109
  } catch (err) {
97
- if (err instanceof AuthError && err.code === AuthErrorCodes.UNAUTHENTICATED) {
110
+ if (
111
+ err instanceof AuthError &&
112
+ (err.code === AuthErrorCodes.UNAUTHENTICATED || err.code === AuthErrorCodes.ACCESS_EXPIRED)
113
+ ) {
98
114
  // Expected on the pre-auth path. Cookie already written.
99
115
  return { ok: true as const, locale: data.locale, account: null }
100
116
  }
@@ -18,7 +18,11 @@ import { createServerFn } from '@tanstack/react-start'
18
18
 
19
19
  import { clearPreviewCookie } from '@byline/client/server'
20
20
 
21
- export const disablePreviewModeFn = createServerFn({ method: 'POST' }).handler(async () => {
22
- clearPreviewCookie()
23
- return { status: 'ok' as const, preview: false as const }
24
- })
21
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
22
+
23
+ export const disablePreviewModeFn = createServerFn({ method: 'POST' })
24
+ .middleware([adminSessionMiddleware])
25
+ .handler(async () => {
26
+ clearPreviewCookie()
27
+ return { status: 'ok' as const, preview: false as const }
28
+ })
@@ -19,8 +19,12 @@ import { createServerFn } from '@tanstack/react-start'
19
19
 
20
20
  import { getAdminRequestContext, setPreviewCookie } from '@byline/client/server'
21
21
 
22
- export const enablePreviewModeFn = createServerFn({ method: 'POST' }).handler(async () => {
23
- await getAdminRequestContext()
24
- setPreviewCookie()
25
- return { status: 'ok' as const, preview: true as const }
26
- })
22
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
23
+
24
+ export const enablePreviewModeFn = createServerFn({ method: 'POST' })
25
+ .middleware([adminSessionMiddleware])
26
+ .handler(async () => {
27
+ await getAdminRequestContext()
28
+ setPreviewCookie()
29
+ return { status: 'ok' as const, preview: true as const }
30
+ })
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { getAdminBylineClient } from '@byline/client/server'
10
- import { getCollectionDefinition } from '@byline/core'
10
+ import { ERR_NOT_FOUND, getCollectionDefinition } from '@byline/core'
11
11
 
12
12
  import { resolveAdminDocumentRead } from './admin-document-presentation.js'
13
13
  import { serialise } from './serialise.js'
@@ -27,6 +27,10 @@ export interface SingletonDocumentReadInput {
27
27
  */
28
28
  export async function readSingletonDocument(data: SingletonDocumentReadInput) {
29
29
  const handle = getAdminBylineClient().singleton(data.singleton)
30
- const { options } = resolveAdminDocumentRead(getCollectionDefinition(data.singleton), data)
31
- return serialise(await handle.get(options))
30
+ const {
31
+ options: { status: _status, ...options },
32
+ } = resolveAdminDocumentRead(getCollectionDefinition(data.singleton), data)
33
+ const snapshot = await handle.getForEdit(options)
34
+ if (snapshot == null) throw ERR_NOT_FOUND({ message: 'Singleton document is unavailable' })
35
+ return snapshot.state === 'empty' ? null : serialise(snapshot.document)
32
36
  }
@@ -10,10 +10,19 @@ import { createServerFn } from '@tanstack/react-start'
10
10
 
11
11
  import { getAdminBylineClient } from '@byline/client/server'
12
12
 
13
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
14
+ import { withDocumentMutationErrors } from '../document-mutation-errors.js'
13
15
  import { serialise } from '../serialise.js'
14
16
 
15
17
  export const cancelSingletonScheduledPublish = createServerFn({ method: 'POST' })
16
- .validator((input: { singleton: string }) => input)
17
- .handler(async ({ data }) =>
18
- serialise(await getAdminBylineClient().singleton(data.singleton).cancelScheduledPublish())
18
+ .middleware([adminSessionMiddleware])
19
+ .validator((input: { expectedRevision: number; singleton: string }) => input)
20
+ .handler(
21
+ withDocumentMutationErrors(async ({ data }) =>
22
+ serialise(
23
+ await getAdminBylineClient()
24
+ .singleton(data.singleton)
25
+ .cancelScheduledPublish({ expectedRevision: data.expectedRevision })
26
+ )
27
+ )
19
28
  )