dineway 0.2.33 → 0.2.35

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 (458) hide show
  1. package/dist/{activity-CfryM8jO.mjs → activity-BbtYp_4V.mjs} +2 -2
  2. package/dist/{activity-events-BVo9rMUz.mjs → activity-events-CiFN61xq.mjs} +1 -1
  3. package/dist/{actor-DrJJsvy6.mjs → actor-DxUAJgQX.mjs} +20 -6
  4. package/dist/{adapters-DHt3f2lu.d.mts → adapters-Rol-inz6.d.mts} +5 -7
  5. package/dist/after-BVaUn7bK.mjs +24 -0
  6. package/dist/{allowed-origins-DGSyhRCz.mjs → allowed-origins-Cp7BI3L9.mjs} +1 -1
  7. package/dist/api/route-utils.d.mts +10 -10
  8. package/dist/api/route-utils.mjs +23 -19
  9. package/dist/api/schemas/index.d.mts +1 -1
  10. package/dist/api/schemas/index.mjs +5 -5
  11. package/dist/{api-CTf1SLmg.mjs → api-CwhrR7H5.mjs} +55 -1215
  12. package/dist/api-tokens-DA18zxza.mjs +3 -0
  13. package/dist/{api-tokens-CSomx-l3.mjs → api-tokens-OAgvq4xg.mjs} +1 -1
  14. package/dist/{apply-Bs2jXsms.mjs → apply-BTfAReIv.mjs} +109 -93
  15. package/dist/astro/index.d.mts +16 -12
  16. package/dist/astro/index.mjs +50 -9
  17. package/dist/astro/middleware/auth.d.mts +9 -9
  18. package/dist/astro/middleware/auth.mjs +29 -8
  19. package/dist/astro/middleware/redirect.mjs +8 -7
  20. package/dist/astro/middleware/request-context.mjs +17 -10
  21. package/dist/astro/middleware/seed.d.mts +1 -1
  22. package/dist/astro/middleware/seed.mjs +93 -71
  23. package/dist/astro/middleware/setup.mjs +1 -1
  24. package/dist/astro/middleware/workers.d.mts +6 -0
  25. package/dist/astro/middleware/workers.mjs +8 -0
  26. package/dist/astro/middleware.mjs +287 -159
  27. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +7 -7
  28. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +7 -7
  29. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  30. package/dist/astro/routes/api/admin/api-tokens/index.mjs +7 -7
  31. package/dist/astro/routes/api/admin/briefing.mjs +25 -23
  32. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -4
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +11 -10
  34. package/dist/astro/routes/api/admin/byline-fields/index.mjs +11 -10
  35. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +11 -10
  36. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +22 -20
  37. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +22 -20
  38. package/dist/astro/routes/api/admin/bylines/index.mjs +22 -20
  39. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +18 -16
  40. package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -7
  41. package/dist/astro/routes/api/admin/comments/bulk.mjs +14 -13
  42. package/dist/astro/routes/api/admin/comments/counts.mjs +8 -7
  43. package/dist/astro/routes/api/admin/comments/index.mjs +14 -13
  44. package/dist/astro/routes/api/admin/context/_id_/history.mjs +42 -31
  45. package/dist/astro/routes/api/admin/context/_id_/index.mjs +42 -31
  46. package/dist/astro/routes/api/admin/context/_id_/review.mjs +47 -36
  47. package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +47 -36
  48. package/dist/astro/routes/api/admin/context/diff.mjs +46 -35
  49. package/dist/astro/routes/api/admin/context/index.mjs +47 -36
  50. package/dist/astro/routes/api/admin/context/stale.mjs +46 -35
  51. package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +42 -31
  52. package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +46 -35
  53. package/dist/astro/routes/api/admin/hitl-requests/index.mjs +46 -35
  54. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +43 -32
  55. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  56. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +7 -7
  57. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +7 -7
  58. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +71 -66
  59. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +71 -66
  60. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +63 -58
  61. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +70 -65
  62. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +70 -65
  63. package/dist/astro/routes/api/admin/plugins/index.mjs +63 -58
  64. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  65. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +63 -58
  66. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +70 -65
  67. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +63 -58
  68. package/dist/astro/routes/api/admin/plugins/updates.mjs +63 -58
  69. package/dist/astro/routes/api/admin/review-requests/_id_/image.mjs +26 -23
  70. package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +39 -25
  71. package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +55 -43
  72. package/dist/astro/routes/api/admin/review-requests/_id_/revoke.mjs +10 -9
  73. package/dist/astro/routes/api/admin/review-requests/index.mjs +53 -41
  74. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +63 -58
  75. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  76. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +63 -58
  77. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +20 -12
  78. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  79. package/dist/astro/routes/api/admin/users/_id_/index.mjs +40 -32
  80. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  81. package/dist/astro/routes/api/admin/users/index.mjs +7 -7
  82. package/dist/astro/routes/api/auth/dev-bypass.mjs +6 -5
  83. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  84. package/dist/astro/routes/api/auth/invite/complete.mjs +12 -12
  85. package/dist/astro/routes/api/auth/invite/index.mjs +9 -9
  86. package/dist/astro/routes/api/auth/invite/register-options.mjs +11 -11
  87. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  88. package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -11
  89. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  90. package/dist/astro/routes/api/auth/me.mjs +11 -11
  91. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  92. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +5 -4
  93. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +5 -4
  94. package/dist/astro/routes/api/auth/passkey/_id_.mjs +7 -7
  95. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  96. package/dist/astro/routes/api/auth/passkey/options.mjs +14 -13
  97. package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -11
  98. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -12
  99. package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -12
  100. package/dist/astro/routes/api/auth/setup-token/index.mjs +32 -23
  101. package/dist/astro/routes/api/auth/setup-token/verify.mjs +32 -26
  102. package/dist/astro/routes/api/auth/signup/complete.mjs +12 -12
  103. package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
  104. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  105. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts +73 -2
  106. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +91 -49
  107. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +19 -16
  108. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  109. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +8 -7
  110. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +8 -7
  111. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +8 -7
  112. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +24 -21
  113. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +34 -22
  114. package/dist/astro/routes/api/content/_collection_/_id_/release-readiness.mjs +9 -9
  115. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +8 -7
  116. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  117. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +34 -22
  118. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +23 -19
  119. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  120. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +8 -7
  121. package/dist/astro/routes/api/content/_collection_/_id_.mjs +14 -13
  122. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  123. package/dist/astro/routes/api/content/_collection_/index.mjs +14 -13
  124. package/dist/astro/routes/api/content/_collection_/trash.mjs +8 -8
  125. package/dist/astro/routes/api/dashboard.mjs +16 -11
  126. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  127. package/dist/astro/routes/api/health.mjs +1 -1
  128. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  129. package/dist/astro/routes/api/import/probe.mjs +25 -24
  130. package/dist/astro/routes/api/import/wordpress/analyze.mjs +12 -11
  131. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  132. package/dist/astro/routes/api/import/wordpress/execute.mjs +50 -42
  133. package/dist/astro/routes/api/import/wordpress/media.d.mts +9 -9
  134. package/dist/astro/routes/api/import/wordpress/media.mjs +14 -14
  135. package/dist/astro/routes/api/import/wordpress/prepare.mjs +51 -40
  136. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +10 -10
  137. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +49 -38
  138. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  139. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +29 -27
  140. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  141. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +9 -9
  142. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +55 -47
  143. package/dist/astro/routes/api/integration/artifact.mjs +11 -8
  144. package/dist/astro/routes/api/integration/dispatch.mjs +17 -14
  145. package/dist/astro/routes/api/integrations/_provider_.mjs +11 -8
  146. package/dist/astro/routes/api/integrations/index.mjs +10 -7
  147. package/dist/astro/routes/api/manifest.mjs +5 -5
  148. package/dist/astro/routes/api/mcp.mjs +97 -88
  149. package/dist/astro/routes/api/media/_id_/confirm.mjs +12 -12
  150. package/dist/astro/routes/api/media/_id_.mjs +9 -9
  151. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  152. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  153. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  154. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  155. package/dist/astro/routes/api/media/upload-url.mjs +13 -13
  156. package/dist/astro/routes/api/media.mjs +16 -16
  157. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +47 -36
  158. package/dist/astro/routes/api/menus/_name_/items.mjs +47 -36
  159. package/dist/astro/routes/api/menus/_name_/reorder.mjs +47 -36
  160. package/dist/astro/routes/api/menus/_name_/translations.mjs +47 -36
  161. package/dist/astro/routes/api/menus/_name_.mjs +47 -36
  162. package/dist/astro/routes/api/menus/index.mjs +47 -36
  163. package/dist/astro/routes/api/oauth/authorize.mjs +9 -7
  164. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -6
  165. package/dist/astro/routes/api/oauth/device/code.mjs +11 -9
  166. package/dist/astro/routes/api/oauth/device/token.mjs +10 -8
  167. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  168. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -6
  169. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -6
  170. package/dist/astro/routes/api/oauth/token.mjs +8 -6
  171. package/dist/astro/routes/api/openapi.json.mjs +5 -5
  172. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  173. package/dist/astro/routes/api/redirects/404s/index.mjs +15 -14
  174. package/dist/astro/routes/api/redirects/404s/summary.mjs +15 -14
  175. package/dist/astro/routes/api/redirects/_id_.mjs +47 -37
  176. package/dist/astro/routes/api/redirects/index.mjs +47 -37
  177. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  178. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
  179. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +65 -60
  180. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +65 -60
  181. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +65 -60
  182. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +65 -60
  183. package/dist/astro/routes/api/schema/collections/index.mjs +65 -60
  184. package/dist/astro/routes/api/schema/index.mjs +12 -11
  185. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +63 -58
  186. package/dist/astro/routes/api/schema/orphans/index.mjs +63 -58
  187. package/dist/astro/routes/api/search/enable.mjs +15 -14
  188. package/dist/astro/routes/api/search/index.mjs +14 -13
  189. package/dist/astro/routes/api/search/rebuild.mjs +15 -14
  190. package/dist/astro/routes/api/search/stats.mjs +9 -8
  191. package/dist/astro/routes/api/search/suggest.mjs +14 -13
  192. package/dist/astro/routes/api/sections/_slug_.mjs +47 -36
  193. package/dist/astro/routes/api/sections/index.mjs +47 -36
  194. package/dist/astro/routes/api/settings/email.mjs +5 -5
  195. package/dist/astro/routes/api/settings/reviews.mjs +11 -10
  196. package/dist/astro/routes/api/settings.mjs +47 -36
  197. package/dist/astro/routes/api/setup/admin-verify.mjs +14 -14
  198. package/dist/astro/routes/api/setup/admin.mjs +12 -12
  199. package/dist/astro/routes/api/setup/dev-bypass.mjs +35 -31
  200. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  201. package/dist/astro/routes/api/setup/index.mjs +37 -33
  202. package/dist/astro/routes/api/setup/status.mjs +3 -3
  203. package/dist/astro/routes/api/snapshot.mjs +11 -9
  204. package/dist/astro/routes/api/system/heartbeat.mjs +2 -2
  205. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +50 -39
  206. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +50 -39
  207. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +50 -39
  208. package/dist/astro/routes/api/taxonomies/index.mjs +50 -39
  209. package/dist/astro/routes/api/themes/preview.mjs +8 -6
  210. package/dist/astro/routes/api/typegen.mjs +4 -4
  211. package/dist/astro/routes/api/well-known/auth.mjs +3 -3
  212. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +3 -3
  213. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +3 -3
  214. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +46 -35
  215. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +47 -36
  216. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +47 -36
  217. package/dist/astro/routes/api/widget-areas/_name_.mjs +44 -33
  218. package/dist/astro/routes/api/widget-areas/index.mjs +47 -36
  219. package/dist/astro/routes/api/widget-components.mjs +2 -2
  220. package/dist/astro/routes/robots.txt.mjs +11 -10
  221. package/dist/astro/routes/sitemap-_collection_.xml.mjs +20 -18
  222. package/dist/astro/routes/sitemap.xml.mjs +19 -17
  223. package/dist/astro/session-driver.d.mts +1 -1
  224. package/dist/astro/session-driver.mjs +4 -4
  225. package/dist/astro/types.d.mts +13 -13
  226. package/dist/atomic-authority-CnBQJ14T.mjs +33 -0
  227. package/dist/{audit-CYjfxG9i.mjs → audit-F5iWkbcJ.mjs} +11 -4
  228. package/dist/auth/providers/github.d.mts +1 -1
  229. package/dist/auth/providers/google.d.mts +1 -1
  230. package/dist/{auth-control-guard-CITT8ZA_.mjs → auth-control-guard-DPOSBNFI.mjs} +1 -1
  231. package/dist/{authorize-KcI0pAfN.mjs → authorize-CXIjxIfC.mjs} +1 -1
  232. package/dist/{briefing-BgQTljQt.mjs → briefing-5zf0YZbR.mjs} +445 -447
  233. package/dist/{briefing-r3hXCDQC.mjs → briefing-DiupPvyN.mjs} +1 -1
  234. package/dist/{bundle-S6zx-V4D.mjs → bundle-DImnxo71.mjs} +3 -3
  235. package/dist/{byline-Ctil4rij.mjs → byline-DEZKlzD9.mjs} +107 -113
  236. package/dist/{byline-fields-51ja6JDa.d.mts → byline-fields-BO96fXLv.d.mts} +8 -8
  237. package/dist/{byline-fields-DiCyW6eQ.mjs → byline-fields-CY6V0ew1.mjs} +1 -1
  238. package/dist/{byline-fields-QpmruT5r.mjs → byline-fields-DGNKitg3.mjs} +3 -3
  239. package/dist/{byline-registry-rkr73qSE.mjs → byline-registry-CkuVVeJJ.mjs} +46 -76
  240. package/dist/{bylines-v_ZUQnR-.mjs → bylines-CXyKpVLQ.mjs} +6 -6
  241. package/dist/{bylines-BcuBpzVi.mjs → bylines-gSAfpHl4.mjs} +4 -4
  242. package/dist/{cache-V2BCplaY.mjs → cache-DZDOBJv3.mjs} +2 -2
  243. package/dist/{chunks-CnuL55JE.mjs → chunks-BnG3LPMN.mjs} +1 -1
  244. package/dist/cli/index.mjs +452 -263
  245. package/dist/cli/plugin-kit.d.mts +9 -9
  246. package/dist/cli/plugin-kit.mjs +2 -2
  247. package/dist/client/external-auth-headers.d.mts +1 -1
  248. package/dist/client/index.d.mts +11 -11
  249. package/dist/client/index.mjs +2 -2
  250. package/dist/{client-factory-C-y9VZDX.mjs → client-factory-6vLLT06O.mjs} +1 -1
  251. package/dist/{comment-_nvJSS-A.mjs → comment-kgLTdtQD.mjs} +15 -8
  252. package/dist/{comment-reaction-DhVgJ17S.mjs → comment-reaction-Cun5eIXL.mjs} +1 -1
  253. package/dist/{comments-D4mIDjvA.mjs → comments-ZYK_cZvx.mjs} +1 -1
  254. package/dist/{content-CVCrcuvD.mjs → content-D9xKUSno.mjs} +287 -165
  255. package/dist/{content-refresh-QvBIyb_g.mjs → content-refresh-9M9cc55-.mjs} +170 -193
  256. package/dist/{context-JjKucxBm.mjs → context-BU8AbLmY.mjs} +2 -2
  257. package/dist/{context-MftkHbWx.mjs → context-C5lPtfeh.mjs} +95 -77
  258. package/dist/{context-route-helpers-e1APrqSm.mjs → context-route-helpers-CWAHJFl3.mjs} +2 -2
  259. package/dist/{cron-mGVPQ5e6.mjs → cron-BVYo08mi.mjs} +36 -32
  260. package/dist/d1-binding-D6HZa3ks.mjs +8 -0
  261. package/dist/d1-dialect-BBxPg6Om.mjs +48 -0
  262. package/dist/{dashboard-ZJ7sMcBI.mjs → dashboard-BnD5krLg.mjs} +5 -5
  263. package/dist/database/batch.d.mts +22 -0
  264. package/dist/database/batch.mjs +92 -0
  265. package/dist/database/d1/index.d.mts +18 -0
  266. package/dist/database/d1/index.mjs +8 -0
  267. package/dist/db/d1.d.mts +7 -0
  268. package/dist/db/d1.mjs +13 -0
  269. package/dist/db/index.d.mts +4 -4
  270. package/dist/db/index.mjs +10 -7
  271. package/dist/db/libsql.d.mts +1 -1
  272. package/dist/db/libsql.mjs +46 -3
  273. package/dist/db/postgres.d.mts +1 -1
  274. package/dist/db/sqlite.d.mts +1 -1
  275. package/dist/{device-flow-eOcL5m0Q.mjs → device-flow-Prqg6ycD.mjs} +130 -94
  276. package/dist/{dialect-helpers-fk43GbDb.mjs → dialect-helpers-C6xtO-Ui.mjs} +1 -1
  277. package/dist/{discovery-CJkuHGhZ.mjs → discovery-CYcZJ1o3.mjs} +5 -5
  278. package/dist/{enrich-KErvJOuI.mjs → enrich-BjF-J4sF.mjs} +1 -1
  279. package/dist/{evidence-6IUv9ML3.mjs → evidence-krrA3uiG.mjs} +7 -7
  280. package/dist/{field-defs-cache-CwrIyZFT.mjs → field-defs-cache-BKKFFf_V.mjs} +5 -5
  281. package/dist/{forgeway-account-lYJ8iX3G.mjs → forgeway-account-CnWOIusE.mjs} +1 -1
  282. package/dist/{fts-manager-DX7ZkrCa.mjs → fts-manager-DA74x_uP.mjs} +72 -57
  283. package/dist/{hitl-requests-CmYVunm-.mjs → hitl-requests-CGjA1jND.mjs} +2 -2
  284. package/dist/{hitl-route-helpers-CRm537zS.mjs → hitl-route-helpers-BxSL0iZB.mjs} +5 -5
  285. package/dist/{import-DqRAgsKr.mjs → import-CayDCPaH.mjs} +9 -9
  286. package/dist/{index-Co7xtjkd.d.mts → index-mlJSqFkG.d.mts} +7 -4
  287. package/dist/index.d.mts +16 -16
  288. package/dist/index.mjs +103 -96
  289. package/dist/{integration-credentials-qudN6yRr.mjs → integration-credentials-Br16M1VE.mjs} +2 -2
  290. package/dist/{jsonld-BpvnSp2m.d.mts → jsonld-BUiFEiCZ.d.mts} +2 -2
  291. package/dist/{load-fCOciq0Q.mjs → load-B2oitobd.mjs} +1 -1
  292. package/dist/{loader-BDje8ME2.mjs → loader-Cgsx23wU.mjs} +4 -4
  293. package/dist/{manifest-schema-CfKD0thC.mjs → manifest-schema-DBXM_g8l.mjs} +1 -1
  294. package/dist/{mcp-reviewed-executor-DmhM3Cy6.mjs → mcp-reviewed-executor-RJ71uyQb.mjs} +7 -7
  295. package/dist/media/image-endpoint.mjs +1 -1
  296. package/dist/media/index.d.mts +1 -1
  297. package/dist/media/index.mjs +2 -2
  298. package/dist/media/local-runtime.d.mts +11 -11
  299. package/dist/media/local-runtime.mjs +3 -3
  300. package/dist/{media-dvRjj4po.mjs → media-BZqwtRTZ.mjs} +12 -4
  301. package/dist/{media-allowlist-DaG6lNoy.mjs → media-allowlist-CyByjUXh.mjs} +1 -1
  302. package/dist/{menus-CZbz5L_V.mjs → menus-2o-DVcwA.mjs} +8 -8
  303. package/dist/{menus-CpuR4Qkm.mjs → menus-5liJusbp.mjs} +138 -90
  304. package/dist/migrations-C7YXse7V.mjs +76 -0
  305. package/dist/oauth-authority-DOjME4Hh.mjs +43 -0
  306. package/dist/{oauth-authorization-Jf5e3azH.mjs → oauth-authorization-B_mmz7O2.mjs} +89 -98
  307. package/dist/{oauth-clients-tovQ4D4h.mjs → oauth-clients-Cr4pdIAE.mjs} +2 -2
  308. package/dist/object-cache/memory.d.mts +1 -1
  309. package/dist/{object-cache-D51qOseS.mjs → object-cache-BGjIlY9s.mjs} +1 -1
  310. package/dist/page/index.d.mts +10 -10
  311. package/dist/page/index.mjs +1 -1
  312. package/dist/{parse-IsGn-5lt.mjs → parse-BscLr3p2.mjs} +1 -1
  313. package/dist/plugin-utils.d.mts +9 -9
  314. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  315. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  316. package/dist/{plugins-DXyLI3Hb.mjs → plugins-f48yrwfI.mjs} +35 -84
  317. package/dist/{policy-DKdNMv8y.mjs → policy-CrK1vl8l.mjs} +2 -2
  318. package/dist/{preview-BXmS1SXL.mjs → preview-E1xKZGsX.mjs} +2 -2
  319. package/dist/{provider-loader-DDq7R_RN.d.mts → provider-loader-H2wdGD0o.d.mts} +1 -1
  320. package/dist/{public-url-Ci7UbC0W.d.mts → public-url-CI700CW4.d.mts} +1 -1
  321. package/dist/{public-url-BQ9vf0AS.d.mts → public-url-zoO-QbN6.d.mts} +2 -2
  322. package/dist/{publication-authorization-C-vYPGD8.mjs → publication-authorization-Dq_20SZR.mjs} +458 -33
  323. package/dist/{query-DcB9uGTW.mjs → query-BkWWdxyb.mjs} +4 -4
  324. package/dist/{query-CrW2yI6G.mjs → query-D6oNo9iz.mjs} +15 -15
  325. package/dist/{rate-limit-DEkoxTsP.mjs → rate-limit-RaBJEgyt.mjs} +2 -2
  326. package/dist/{redirect-DG-o0CKV.mjs → redirect-HLI3wT5Y.mjs} +26 -14
  327. package/dist/{redirects-Bjp4iKJU.mjs → redirects-D16GFaOa.mjs} +5 -5
  328. package/dist/{redirects-N8gmWhVM.mjs → redirects-DpRiuRWn.mjs} +3 -3
  329. package/dist/{registry-D-IV_h50.mjs → registry-DcV93OL_.mjs} +183 -186
  330. package/dist/release-CZfAeyXm.mjs +5148 -0
  331. package/dist/request-context.d.mts +8 -0
  332. package/dist/{request-meta-BW0the1R.mjs → request-meta-C6yGqnGD.mjs} +1 -1
  333. package/dist/{resolve-pZ4NQM-X.mjs → resolve-CpOV2jt_.mjs} +1 -1
  334. package/dist/review-payload-LLIi1sxO.mjs +1386 -0
  335. package/dist/{review-requests-DICrYH2-.mjs → review-requests-BF2eEKr8.mjs} +93 -37
  336. package/dist/{review-requests-BTKfeWlS.mjs → review-requests-C7prIbCo.mjs} +2 -2
  337. package/dist/{review-route-helpers-DaZBBps8.mjs → review-route-helpers-BYx3Px90.mjs} +2 -2
  338. package/dist/{revision-De7OmYVD.mjs → revision-090sZyGc.mjs} +28 -22
  339. package/dist/revocation-DZ8e4JCS.mjs +53 -0
  340. package/dist/{runner-DJiRFyLI.mjs → runner-CVeq0266.mjs} +594 -491
  341. package/dist/{runner-Dye73myO.d.mts → runner-Cxl-vyk2.d.mts} +1 -1
  342. package/dist/{runs-DEujUDOj.mjs → runs-B8kCirJ6.mjs} +5 -5
  343. package/dist/runtime-env-v-1UXPAH.mjs +19 -0
  344. package/dist/runtime.d.mts +10 -10
  345. package/dist/runtime.mjs +5 -5
  346. package/dist/schema-DFeC5axi.mjs +8 -0
  347. package/dist/{search-DxUKN9Zz.mjs → search-C9qOBgvo.mjs} +4 -4
  348. package/dist/{secrets-AdCrGg1c.mjs → secrets-BP2YTPWn.mjs} +10 -11
  349. package/dist/{sections-BPFOEMK6.mjs → sections-CkoQY7Z9.mjs} +2 -2
  350. package/dist/seed/index.d.mts +2 -2
  351. package/dist/seed/index.mjs +29 -25
  352. package/dist/seo/index.d.mts +3 -3
  353. package/dist/seo/index.mjs +11 -10
  354. package/dist/{seo-GfovzqpU.mjs → seo-Bypxm1iD.mjs} +1 -1
  355. package/dist/{seo-DWHdo5aW.mjs → seo-DYpc_rQ1.mjs} +25 -15
  356. package/dist/{service-BrNKVCjC.mjs → service-DOFqfUVv.mjs} +46 -21
  357. package/dist/{service-DbgjHWyW.mjs → service-mJBl4ZP4.mjs} +2 -2
  358. package/dist/{session-user-D8WwGEPH.mjs → session-user-DacoiKrJ.mjs} +1 -1
  359. package/dist/{settings-BKEHw6Bu.mjs → settings-2p7TuNWG.mjs} +2 -2
  360. package/dist/{settings-CAsQLi0N.mjs → settings-DRFy8JQx.mjs} +8 -8
  361. package/dist/{setup-complete-CHCYS5KV.mjs → setup-complete-CWMnrNFN.mjs} +1 -1
  362. package/dist/{site-activity-7oezuHXK.mjs → site-activity-BrN2NviL.mjs} +11 -4
  363. package/dist/{site-context-CA7rbxux.mjs → site-context-D9oETfxF.mjs} +50 -37
  364. package/dist/{site-url-DoZf_jVj.mjs → site-url-DoTPKRND.mjs} +1 -1
  365. package/dist/state-D8AogXCV.mjs +30 -0
  366. package/dist/storage/local.d.mts +1 -1
  367. package/dist/storage/local.mjs +1 -1
  368. package/dist/storage/s3.d.mts +1 -1
  369. package/dist/storage/s3.mjs +3 -3
  370. package/dist/{store-c4qx3gVh.mjs → store-CjmmGlVw.mjs} +37 -12
  371. package/dist/submission-BLvkvewK.mjs +411 -0
  372. package/dist/{taxonomies-ALDiRQ84.mjs → taxonomies-C5ayPp3T.mjs} +3 -3
  373. package/dist/{taxonomies-93DV-1QT.mjs → taxonomies-DW6Fps-w.mjs} +7 -7
  374. package/dist/{taxonomy-BOBTJ_aE.mjs → taxonomy-DWxxLpp5.mjs} +3 -3
  375. package/dist/{tokens-c-qxNqzC.mjs → tokens-I3tSjoFI.mjs} +1 -1
  376. package/dist/{trusted-proxy-CdEl6g6B.mjs → trusted-proxy-CvDP0g0F.mjs} +4 -12
  377. package/dist/{types-6KmeXInJ.d.mts → types-BFoQ5a1v.d.mts} +1 -1
  378. package/dist/{types-BpSTdH1H.mjs → types-BqxIv8Il.mjs} +2 -2
  379. package/dist/{types-BRCUO8NR.d.mts → types-CVRNRAA5.d.mts} +1 -0
  380. package/dist/{types-CfpX1og5.d.mts → types-Tt_IGMno.d.mts} +360 -172
  381. package/dist/ui/server-runtime.d.mts +13 -13
  382. package/dist/ui/server-runtime.mjs +28 -26
  383. package/dist/{user-D0NJ7gYf.mjs → user-1-YV30lf.mjs} +2 -2
  384. package/dist/{utils-BK2efsHv.mjs → utils-BoWkoty8.mjs} +2 -2
  385. package/dist/{validate-CDWVODvc.d.mts → validate-CCOsvPg2.d.mts} +3 -3
  386. package/dist/{validate-C9g9iX3q.mjs → validate-CupwWNWL.mjs} +3 -3
  387. package/dist/version-DpT-k_Ny.mjs +6 -0
  388. package/dist/{widgets-Cm5Qx2Np.mjs → widgets-DbOctkXv.mjs} +3 -3
  389. package/dist/{wordpress-slugs-WdPZaVXf.mjs → wordpress-slugs-pQlLXbKn.mjs} +1 -1
  390. package/dist/{worker-Bhuu7eqS.mjs → worker-wjRq4FI9.mjs} +76 -47
  391. package/dist/workers-build-QVrquLLe.mjs +36 -0
  392. package/dist/{zod-generator-DS5oZNaL.mjs → zod-generator-BzACAoR5.mjs} +2 -2
  393. package/package.json +34 -8
  394. package/dist/after-BfniwgIf.mjs +0 -9
  395. package/dist/api-tokens-CmT83Bgj.mjs +0 -3
  396. package/dist/db/libsql-http.d.mts +0 -27
  397. package/dist/db/libsql-http.mjs +0 -29
  398. package/dist/libsql-dialect-CANq6UNI.mjs +0 -47
  399. package/dist/oauth-user-lookup-B451pZmd.mjs +0 -25
  400. package/dist/review-payload-f1L4lzQg.mjs +0 -111
  401. package/dist/revocation-CkTOhdlw.mjs +0 -42
  402. package/dist/schema-IjuRxkqq.mjs +0 -8
  403. package/dist/submission-C47cHBIg.mjs +0 -67
  404. package/dist/transaction-BGQgnwUi.mjs +0 -28
  405. package/dist/version-CJqc1mwj.mjs +0 -6
  406. /package/dist/{base64-F8-DUraK.mjs → base64-Cz-aU0X1.mjs} +0 -0
  407. /package/dist/{challenge-store-CDKfye2r.mjs → challenge-store-DFGsDtCj.mjs} +0 -0
  408. /package/dist/{components-DAq_4iPX.mjs → components-gMTtJqHg.mjs} +0 -0
  409. /package/dist/{config-CVssduLe.mjs → config-XW5tMrH8.mjs} +0 -0
  410. /package/dist/{content-index-names-Bbu1FY1r.mjs → content-index-names-CDc2IQIx.mjs} +0 -0
  411. /package/dist/{context-types-CPWAJRs5.mjs → context-types-DLD_HnKT.mjs} +0 -0
  412. /package/dist/{default-BeIzUa1e.mjs → default-w3MwLng7.mjs} +0 -0
  413. /package/dist/{email-console-Bfi_exVg.mjs → email-console-CpvgEN1w.mjs} +0 -0
  414. /package/dist/{error-DLg1H2LT.mjs → error-B-44r5TD.mjs} +0 -0
  415. /package/dist/{escape-C6lkys0j.mjs → escape-B1GM0yJU.mjs} +0 -0
  416. /package/dist/{experimental-workflows-B9x8delL.mjs → experimental-workflows-fzJHB3ZI.mjs} +0 -0
  417. /package/dist/{hash-jhbF98tT.mjs → hash-DvMZXj2L.mjs} +0 -0
  418. /package/dist/{media-url-CL3NzgLh.mjs → media-url-D-elxQjL.mjs} +0 -0
  419. /package/dist/{mime-CDTtL7jE.mjs → mime-D_ae-u_U.mjs} +0 -0
  420. /package/dist/{mode-DJeacFCZ.mjs → mode-BJwb_MgK.mjs} +0 -0
  421. /package/dist/{normalize-Ev3FiBMx.mjs → normalize-5rct_U0I.mjs} +0 -0
  422. /package/dist/{oauth-state-store-Do1a_o3H.mjs → oauth-state-store-D2H9hKcN.mjs} +0 -0
  423. /package/dist/{options-1dBRkoCl.mjs → options-BcqHWpc-.mjs} +0 -0
  424. /package/dist/{output-8j9oltTf.mjs → output-CdmbQioY.mjs} +0 -0
  425. /package/dist/{parser-C9rzPuSZ.mjs → parser-ehIlj32k.mjs} +0 -0
  426. /package/dist/{passkey-config-CRN44T04.mjs → passkey-config-sUQoLEj_.mjs} +0 -0
  427. /package/dist/{patterns-3XE9bU4r.mjs → patterns-CZ7D4y_R.mjs} +0 -0
  428. /package/dist/{placeholder-D03pGvqM.d.mts → placeholder-CQKI6vLd.d.mts} +0 -0
  429. /package/dist/{portable-text-BdRm4uZq.d.mts → portable-text-BE7nszX6.d.mts} +0 -0
  430. /package/dist/{portable-text-tIk9_uz_.mjs → portable-text-DH3Om_2D.mjs} +0 -0
  431. /package/dist/{process-cache-B-L-A9zg.mjs → process-cache-MHM4Sqzs.mjs} +0 -0
  432. /package/dist/{provider-loader-C4i82ll1.mjs → provider-loader-Dnv2neOu.mjs} +0 -0
  433. /package/dist/{public-discovery-CyDzK1-S.mjs → public-discovery-CTql6jdZ.mjs} +0 -0
  434. /package/dist/{public-url-BVIjzMo3.mjs → public-url-4sMO36Gw.mjs} +0 -0
  435. /package/dist/{public-url-oudwI7K0.mjs → public-url-DNt9A1yS.mjs} +0 -0
  436. /package/dist/{redirect-Dp5u-8dH.mjs → redirect-Cjjb5VYr.mjs} +0 -0
  437. /package/dist/{request-cache-BruBnFAR.mjs → request-cache-DPmV5nk4.mjs} +0 -0
  438. /package/dist/{seo-contributions-BAtRDXEQ.mjs → seo-contributions-atY3jZEV.mjs} +0 -0
  439. /package/dist/{service-DeX1PRNt.mjs → service-CeEi6nax.mjs} +0 -0
  440. /package/dist/{setup-nonce-D-CEYlw7.mjs → setup-nonce-BUmfCiY1.mjs} +0 -0
  441. /package/dist/{sidecar-client-Bw2C0XTf.mjs → sidecar-client-CbrX8nuw.mjs} +0 -0
  442. /package/dist/{slugify-KSsVD_D3.mjs → slugify-Di8Q_MFq.mjs} +0 -0
  443. /package/dist/{ssrf-C7tTSD1r.mjs → ssrf-COxLChKv.mjs} +0 -0
  444. /package/dist/{stable-hash-DF7R5eN8.mjs → stable-hash-DEtJgcp7.mjs} +0 -0
  445. /package/dist/{status-Bba6RbkS.mjs → status-DBSB6tdS.mjs} +0 -0
  446. /package/dist/{transport-ktLL9r2O.d.mts → transport-CZbtxgKZ.d.mts} +0 -0
  447. /package/dist/{transport-BddGhTAI.mjs → transport-aPKvM53n.mjs} +0 -0
  448. /package/dist/{types-CKIt_jmM.d.mts → types-1qU0-N1P.d.mts} +0 -0
  449. /package/dist/{types-BQZFeZjd.mjs → types-BKsk4nnM.mjs} +0 -0
  450. /package/dist/{types-CLMOusxP.d.mts → types-BWsEjujW.d.mts} +0 -0
  451. /package/dist/{types--3oJQ0hs.d.mts → types-CAD4OvmF.d.mts} +0 -0
  452. /package/dist/{types-BbkkkAb6.mjs → types-ChSk0dmw.mjs} +0 -0
  453. /package/dist/{types-C0DPvhR5.mjs → types-CyBH89BS.mjs} +0 -0
  454. /package/dist/{types-CU3Wn2HC.d.mts → types-DNPP-eu4.d.mts} +0 -0
  455. /package/dist/{types-CPaVj_GC.d.mts → types-SddyDti7.d.mts} +0 -0
  456. /package/dist/{url-C_pLSrGZ.mjs → url-Ce0qtSdB.mjs} +0 -0
  457. /package/dist/{url-Dumsft7G.mjs → url-DfqolpO2.mjs} +0 -0
  458. /package/dist/{validate-VPnKoIzW.mjs → validate-Co3j6T_K.mjs} +0 -0
@@ -1,1204 +1,21 @@
1
- import { r as validatePluginIdentifier, t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
2
- import { r as isI18nEnabled } from "./config-CVssduLe.mjs";
3
- import { r as encodeBase64, t as decodeBase64 } from "./base64-F8-DUraK.mjs";
4
- import { n as ScheduledNotDueError, t as DinewayValidationError } from "./types-BpSTdH1H.mjs";
5
- import { o as invalidateCollectionCache } from "./object-cache-D51qOseS.mjs";
6
- import { t as RevisionRepository } from "./revision-De7OmYVD.mjs";
7
- import { t as ContentRepository } from "./content-CVCrcuvD.mjs";
8
- import { t as MediaRepository } from "./media-dvRjj4po.mjs";
9
- import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-CnuL55JE.mjs";
10
- import { t as UserRepository } from "./user-D0NJ7gYf.mjs";
11
- import { t as CommentRepository } from "./comment-_nvJSS-A.mjs";
12
- import { t as withTransaction } from "./transaction-BGQgnwUi.mjs";
13
- import { t as RedirectRepository } from "./redirect-DG-o0CKV.mjs";
14
- import { r as requestCached } from "./request-cache-BruBnFAR.mjs";
15
- import { t as BylineRepository } from "./byline-Ctil4rij.mjs";
16
- import { r as parseAllowedMimeTypes, t as matchesMimeAllowlist } from "./mime-CDTtL7jE.mjs";
17
- import { t as SeoRepository } from "./seo-DWHdo5aW.mjs";
18
- import { i as invalidateRedirectCache } from "./cache-V2BCplaY.mjs";
19
- import { d as PluginStateRepository } from "./briefing-BgQTljQt.mjs";
20
- import { n as hashString } from "./hash-jhbF98tT.mjs";
21
- import { n as SchemaRegistry, t as SchemaError } from "./registry-D-IV_h50.mjs";
22
- import { n as VERSION, t as COMMIT } from "./version-CJqc1mwj.mjs";
23
- import { a as reconcileManifestAccess, i as pluginManifestSchema, r as normalizeManifestRoute } from "./manifest-schema-CfKD0thC.mjs";
24
- import { t as DinewayStorageError } from "./types-BQZFeZjd.mjs";
1
+ import { atomicBatch, rowUnchanged } from "./database/batch.mjs";
2
+ import { r as validatePluginIdentifier } from "./validate-Co3j6T_K.mjs";
3
+ import { t as contentWriteChecks } from "./atomic-authority-CnBQJ14T.mjs";
4
+ import { t as after } from "./after-BVaUn7bK.mjs";
5
+ import { o as invalidateCollectionCache } from "./object-cache-BGjIlY9s.mjs";
6
+ import { t as RevisionRepository } from "./revision-090sZyGc.mjs";
7
+ import { t as ContentRepository } from "./content-D9xKUSno.mjs";
8
+ import { t as MediaRepository } from "./media-BZqwtRTZ.mjs";
9
+ import { u as PluginStateRepository } from "./briefing-5zf0YZbR.mjs";
10
+ import { n as hashString } from "./hash-DvMZXj2L.mjs";
11
+ import { n as SchemaRegistry, t as SchemaError } from "./registry-DcV93OL_.mjs";
12
+ import { n as VERSION, t as COMMIT } from "./version-DpT-k_Ny.mjs";
13
+ import { a as reconcileManifestAccess, i as pluginManifestSchema, r as normalizeManifestRoute } from "./manifest-schema-DBXM_g8l.mjs";
14
+ import { t as DinewayStorageError } from "./types-BKsk4nnM.mjs";
25
15
  import { sql } from "kysely";
16
+ import { z } from "zod";
26
17
  import { createGzipDecoder, unpackTar } from "modern-tar";
27
18
 
28
- //#region src/api/rev.ts
29
- /**
30
- * Generate a _rev token from a content item's version and updatedAt.
31
- */
32
- function encodeRev(item) {
33
- return encodeBase64(`${item.version}:${item.updatedAt}`);
34
- }
35
- /**
36
- * Decode a _rev token into its components.
37
- * Returns null if the token is malformed.
38
- */
39
- function decodeRev(rev) {
40
- try {
41
- const decoded = decodeBase64(rev);
42
- const colonIdx = decoded.indexOf(":");
43
- if (colonIdx === -1) return null;
44
- const version = parseInt(decoded.slice(0, colonIdx), 10);
45
- const updatedAt = decoded.slice(colonIdx + 1);
46
- if (isNaN(version) || !updatedAt) return null;
47
- return {
48
- version,
49
- updatedAt
50
- };
51
- } catch {
52
- return null;
53
- }
54
- }
55
- /**
56
- * Validate a _rev token against a content item.
57
- * Returns null if valid (or if no _rev provided), or an error message if invalid.
58
- */
59
- function validateRev(rev, item) {
60
- if (!rev) return { valid: true };
61
- const decoded = decodeRev(rev);
62
- if (!decoded) return {
63
- valid: false,
64
- message: "Malformed _rev token"
65
- };
66
- if (decoded.version !== item.version || decoded.updatedAt !== item.updatedAt) return {
67
- valid: false,
68
- message: "Content has been modified since last read (version conflict)"
69
- };
70
- return { valid: true };
71
- }
72
-
73
- //#endregion
74
- //#region src/api/handlers/validate-media-fields.ts
75
- function asMediaRef(value) {
76
- if (value === null || value === void 0) return null;
77
- if (typeof value !== "object" || Array.isArray(value)) return null;
78
- return value;
79
- }
80
- function fail(message) {
81
- return {
82
- success: false,
83
- error: {
84
- code: "INVALID_MIME_FOR_FIELD",
85
- message
86
- }
87
- };
88
- }
89
- async function loadMediaFieldsForCollection(db, collectionSlug) {
90
- const rows = await db.selectFrom("_dineway_fields").innerJoin("_dineway_collections", "_dineway_collections.id", "_dineway_fields.collection_id").select(["_dineway_fields.slug", "_dineway_fields.validation"]).where("_dineway_collections.slug", "=", collectionSlug).where("_dineway_fields.type", "in", ["file", "image"]).execute();
91
- const out = [];
92
- for (const row of rows) {
93
- const list = parseAllowedMimeTypes(row.validation);
94
- if (!list) continue;
95
- out.push({
96
- slug: row.slug,
97
- allowedMimeTypes: list
98
- });
99
- }
100
- return out;
101
- }
102
- async function validateMediaFields(db, collectionSlug, data) {
103
- const fields = await requestCached(`mediaFields:${collectionSlug}`, () => loadMediaFieldsForCollection(db, collectionSlug));
104
- if (fields.length === 0) return {
105
- success: true,
106
- data: true
107
- };
108
- const localIds = /* @__PURE__ */ new Set();
109
- for (const field of fields) {
110
- const ref = asMediaRef(data[field.slug]);
111
- if (!ref) continue;
112
- if ((typeof ref.provider === "string" ? ref.provider : "local") === "local" && typeof ref.id === "string") localIds.add(ref.id);
113
- }
114
- const mimeById = /* @__PURE__ */ new Map();
115
- const idList = [...localIds];
116
- if (idList.length > 0) for (const batch of chunks(idList, SQL_BATCH_SIZE)) {
117
- const rows = await db.selectFrom("media").select(["id", "mime_type"]).where("id", "in", batch).execute();
118
- for (const row of rows) mimeById.set(row.id, row.mime_type);
119
- }
120
- for (const field of fields) {
121
- const value = data[field.slug];
122
- if (value === null || value === void 0) continue;
123
- const ref = asMediaRef(value);
124
- if (!ref) continue;
125
- const provider = typeof ref.provider === "string" ? ref.provider : "local";
126
- let mime;
127
- if (provider === "local") {
128
- if (typeof ref.id !== "string") return fail(`Field '${field.slug}' references media with an invalid id`);
129
- mime = mimeById.get(ref.id);
130
- if (!mime) return fail(`Field '${field.slug}' references media with unknown MIME type`);
131
- } else {
132
- if (typeof ref.mimeType !== "string") return fail(`Field '${field.slug}' requires a mimeType declaration for non-local media`);
133
- mime = ref.mimeType;
134
- }
135
- if (!matchesMimeAllowlist(mime, field.allowedMimeTypes)) return fail(`Field '${field.slug}' does not accept ${mime}`);
136
- }
137
- return {
138
- success: true,
139
- data: true
140
- };
141
- }
142
-
143
- //#endregion
144
- //#region src/api/handlers/content.ts
145
- /**
146
- * Extract a slug source (title or name) from content data.
147
- * Returns null if no suitable string field is found.
148
- */
149
- function getSlugSource(data) {
150
- if (typeof data.title === "string" && data.title.length > 0) return data.title;
151
- if (typeof data.name === "string" && data.name.length > 0) return data.name;
152
- return null;
153
- }
154
- /** Default SEO values for content without an explicit SEO row */
155
- const SEO_DEFAULTS = {
156
- title: null,
157
- description: null,
158
- image: null,
159
- canonical: null,
160
- noIndex: false
161
- };
162
- var ContentUpdateApiError = class extends Error {
163
- constructor(code, message) {
164
- super(message);
165
- this.code = code;
166
- this.name = "ContentUpdateApiError";
167
- }
168
- };
169
- /**
170
- * Check if a collection has SEO enabled.
171
- */
172
- async function collectionHasSeo(db, collection) {
173
- return (await db.selectFrom("_dineway_collections").select("has_seo").where("slug", "=", collection).executeTakeFirst())?.has_seo === 1;
174
- }
175
- async function collectionSupportsRevisions(db, collection) {
176
- const row = await db.selectFrom("_dineway_collections").select("supports").where("slug", "=", collection).executeTakeFirst();
177
- if (!row?.supports) return false;
178
- try {
179
- const supports = JSON.parse(row.supports);
180
- return Array.isArray(supports) && supports.includes("revisions");
181
- } catch {
182
- return false;
183
- }
184
- }
185
- async function getCollectionFieldSlugs(db, collection) {
186
- const collectionRow = await db.selectFrom("_dineway_collections").select("id").where("slug", "=", collection).executeTakeFirst();
187
- if (!collectionRow) return null;
188
- return (await db.selectFrom("_dineway_fields").select("slug").where("collection_id", "=", collectionRow.id).orderBy("sort_order", "asc").orderBy("created_at", "asc").execute()).map((row) => row.slug);
189
- }
190
- async function validateContentDataFields(db, collection, data) {
191
- const allowedSlugs = await getCollectionFieldSlugs(db, collection);
192
- if (!allowedSlugs) return null;
193
- const allowed = new Set(allowedSlugs);
194
- const unknownFields = Object.keys(data).filter((field) => !allowed.has(field));
195
- if (unknownFields.length === 0) return null;
196
- return {
197
- success: false,
198
- error: {
199
- code: "VALIDATION_ERROR",
200
- message: `Unknown ${unknownFields.length === 1 ? "field" : "fields"} ${unknownFields.map((field) => `"${field}"`).join(", ")} for collection "${collection}". Allowed fields: ${allowedSlugs.join(", ")}.`
201
- }
202
- };
203
- }
204
- async function createSlugChangeRedirect(db, collection, oldSlug, newSlug, contentId) {
205
- const collectionRow = await db.selectFrom("_dineway_collections").select("url_pattern").where("slug", "=", collection).executeTakeFirst();
206
- await new RedirectRepository(db).createAutoRedirect(collection, oldSlug, newSlug, contentId, collectionRow?.url_pattern ?? null);
207
- invalidateRedirectCache();
208
- }
209
- /**
210
- * Hydrate SEO data on a single content item if the collection has SEO enabled.
211
- */
212
- async function hydrateSeo(db, collection, item, hasSeo) {
213
- if (!hasSeo) return;
214
- item.seo = await new SeoRepository(db).get(collection, item.id);
215
- }
216
- /**
217
- * Hydrate SEO data on multiple content items using a single batch query.
218
- */
219
- async function hydrateSeoMany(db, collection, items, hasSeo) {
220
- if (!hasSeo || items.length === 0) return;
221
- const seoMap = await new SeoRepository(db).getMany(collection, items.map((i) => i.id));
222
- for (const item of items) item.seo = seoMap.get(item.id) ?? { ...SEO_DEFAULTS };
223
- }
224
- async function hydrateBylines(db, collection, item) {
225
- const bylineRepo = new BylineRepository(db);
226
- const localeOpt = item.locale ? { locale: item.locale } : void 0;
227
- const bylines = await bylineRepo.getContentBylines(collection, item.id, localeOpt);
228
- if (bylines.length > 0) {
229
- item.bylines = bylines.map((c) => ({
230
- ...c,
231
- source: "explicit"
232
- }));
233
- item.byline = bylines[0]?.byline ?? null;
234
- return;
235
- }
236
- if (item.primaryBylineId) {
237
- item.bylines = [];
238
- item.byline = null;
239
- return;
240
- }
241
- if (item.authorId) {
242
- const fallback = await bylineRepo.findByUserId(item.authorId, localeOpt);
243
- if (fallback) {
244
- item.bylines = [{
245
- byline: fallback,
246
- sortOrder: 0,
247
- roleLabel: null,
248
- source: "inferred"
249
- }];
250
- item.byline = fallback;
251
- return;
252
- }
253
- }
254
- item.bylines = [];
255
- item.byline = null;
256
- }
257
- /**
258
- * Batch-hydrate bylines for multiple items using two bulk queries instead of N+1.
259
- */
260
- async function hydrateBylinesMany(db, collection, items) {
261
- if (items.length === 0) return;
262
- const bylineRepo = new BylineRepository(db);
263
- const localeBuckets = /* @__PURE__ */ new Map();
264
- for (const item of items) {
265
- const key = item.locale ?? null;
266
- const bucket = localeBuckets.get(key);
267
- if (bucket) bucket.push(item);
268
- else localeBuckets.set(key, [item]);
269
- }
270
- const bylinesMap = /* @__PURE__ */ new Map();
271
- const itemsNeedingAuthorCheck = [];
272
- for (const [locale, bucket] of localeBuckets) {
273
- const localeOpt = locale ? { locale } : void 0;
274
- const credits = await bylineRepo.getContentBylinesMany(collection, bucket.map((item) => item.id), localeOpt);
275
- for (const [id, list] of credits) bylinesMap.set(id, list);
276
- for (const item of bucket) {
277
- const explicit = credits.get(item.id);
278
- if (explicit && explicit.length > 0) continue;
279
- if (item.authorId && !item.primaryBylineId) itemsNeedingAuthorCheck.push(item);
280
- }
281
- }
282
- const fallbackByItem = /* @__PURE__ */ new Map();
283
- const authorBuckets = /* @__PURE__ */ new Map();
284
- for (const item of itemsNeedingAuthorCheck) {
285
- const key = item.locale ?? null;
286
- const bucket = authorBuckets.get(key);
287
- if (bucket) bucket.push(item);
288
- else authorBuckets.set(key, [item]);
289
- }
290
- for (const [locale, bucket] of authorBuckets) {
291
- const authorIds = bucket.map((item) => item.authorId).filter((id) => id !== null);
292
- const uniqueAuthorIds = [...new Set(authorIds)];
293
- if (uniqueAuthorIds.length === 0) continue;
294
- const localeOpt = locale ? { locale } : void 0;
295
- const authorBylineMap = await bylineRepo.findByUserIds(uniqueAuthorIds, localeOpt);
296
- for (const item of bucket) {
297
- if (!item.authorId) continue;
298
- const fallback = authorBylineMap.get(item.authorId);
299
- if (fallback) fallbackByItem.set(item.id, fallback);
300
- }
301
- }
302
- for (const item of items) {
303
- const explicit = bylinesMap.get(item.id);
304
- if (explicit && explicit.length > 0) {
305
- item.bylines = explicit.map((c) => ({
306
- ...c,
307
- source: "explicit"
308
- }));
309
- item.byline = explicit[0]?.byline ?? null;
310
- continue;
311
- }
312
- if (item.primaryBylineId) {
313
- item.bylines = [];
314
- item.byline = null;
315
- continue;
316
- }
317
- const fallback = fallbackByItem.get(item.id);
318
- if (fallback) {
319
- item.bylines = [{
320
- byline: fallback,
321
- sortOrder: 0,
322
- roleLabel: null,
323
- source: "inferred"
324
- }];
325
- item.byline = fallback;
326
- continue;
327
- }
328
- item.bylines = [];
329
- item.byline = null;
330
- }
331
- }
332
- /**
333
- * Resolve an identifier (ID or slug) to a real content ID.
334
- * Returns the ID if found, null if not found.
335
- * When locale is provided, slug lookups are scoped to that locale.
336
- */
337
- async function resolveId(repo, collection, identifier, locale) {
338
- return (await repo.findByIdOrSlug(collection, identifier, locale))?.id ?? null;
339
- }
340
- /**
341
- * Resolve an identifier (ID or slug) to a real content ID,
342
- * including trashed (soft-deleted) items.
343
- */
344
- async function resolveIdIncludingTrashed(repo, collection, identifier, locale) {
345
- return (await repo.findByIdOrSlugIncludingTrashed(collection, identifier, locale))?.id ?? null;
346
- }
347
- async function resolveSearchColumns(db, collection) {
348
- const columns = ["slug"];
349
- const fieldSlugs = await getCollectionFieldSlugs(db, collection);
350
- if (!fieldSlugs) return columns;
351
- const fields = new Set(fieldSlugs);
352
- for (const candidate of ["title", "name"]) if (fields.has(candidate)) columns.push(candidate);
353
- return columns;
354
- }
355
- const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
356
- function normalizeDateBound(value, edge) {
357
- if (!value) return void 0;
358
- if (!DATE_ONLY_RE.test(value)) return value;
359
- return edge === "start" ? `${value}T00:00:00.000Z` : `${value}T23:59:59.999Z`;
360
- }
361
- /**
362
- * Create content list handler
363
- */
364
- async function handleContentList(db, collection, params) {
365
- try {
366
- const repo = new ContentRepository(db);
367
- const where = {};
368
- if (params.status) where.status = params.status;
369
- if (params.locale) where.locale = params.locale;
370
- if (params.authorId) where.authorId = params.authorId;
371
- if (params.dateField && (params.dateFrom || params.dateTo)) where.dateFilter = {
372
- field: params.dateField,
373
- from: normalizeDateBound(params.dateFrom, "start"),
374
- to: normalizeDateBound(params.dateTo, "end")
375
- };
376
- const q = params.q?.trim();
377
- if (q) {
378
- where.q = q;
379
- where.searchColumns = await resolveSearchColumns(db, collection);
380
- }
381
- const result = await repo.findMany(collection, {
382
- cursor: params.cursor,
383
- limit: params.limit || 50,
384
- where: Object.keys(where).length > 0 ? where : void 0,
385
- orderBy: params.orderBy ? {
386
- field: params.orderBy,
387
- direction: params.order || "desc"
388
- } : void 0
389
- });
390
- const hasSeo = await collectionHasSeo(db, collection);
391
- await hydrateSeoMany(db, collection, result.items, hasSeo);
392
- await hydrateBylinesMany(db, collection, result.items);
393
- return {
394
- success: true,
395
- data: {
396
- items: result.items,
397
- nextCursor: result.nextCursor,
398
- total: result.total
399
- }
400
- };
401
- } catch (error) {
402
- console.error("Content list error:", error);
403
- return {
404
- success: false,
405
- error: {
406
- code: "CONTENT_LIST_ERROR",
407
- message: "Failed to list content"
408
- }
409
- };
410
- }
411
- }
412
- async function handleContentAuthors(db, collection) {
413
- try {
414
- const authorIds = await new ContentRepository(db).findDistinctAuthorIds(collection);
415
- if (authorIds.length === 0) return {
416
- success: true,
417
- data: { items: [] }
418
- };
419
- return {
420
- success: true,
421
- data: { items: (await new UserRepository(db).findByIds(authorIds)).map((user) => ({
422
- id: user.id,
423
- name: user.name,
424
- email: user.email,
425
- avatarUrl: user.avatarUrl
426
- })).toSorted((a, b) => (a.name ?? a.email).localeCompare(b.name ?? b.email)) }
427
- };
428
- } catch (error) {
429
- console.error("Content authors error:", error);
430
- return {
431
- success: false,
432
- error: {
433
- code: "CONTENT_AUTHORS_ERROR",
434
- message: "Failed to list content authors"
435
- }
436
- };
437
- }
438
- }
439
- /**
440
- * Get single content item
441
- */
442
- async function handleContentGet(db, collection, id, locale) {
443
- try {
444
- const item = await new ContentRepository(db).findByIdOrSlug(collection, id, locale);
445
- if (!item) return {
446
- success: false,
447
- error: {
448
- code: "NOT_FOUND",
449
- message: `Content item not found: ${id}`
450
- }
451
- };
452
- await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
453
- await hydrateBylines(db, collection, item);
454
- return {
455
- success: true,
456
- data: {
457
- item,
458
- _rev: encodeRev(item)
459
- }
460
- };
461
- } catch (error) {
462
- console.error("Content get error:", error);
463
- return {
464
- success: false,
465
- error: {
466
- code: "CONTENT_GET_ERROR",
467
- message: "Failed to get content"
468
- }
469
- };
470
- }
471
- }
472
- /**
473
- * Get a content item by id, including trashed items.
474
- * Used by restore endpoint for ownership checks on soft-deleted items.
475
- */
476
- async function handleContentGetIncludingTrashed(db, collection, id, locale) {
477
- try {
478
- const item = await new ContentRepository(db).findByIdOrSlugIncludingTrashed(collection, id, locale);
479
- if (!item) return {
480
- success: false,
481
- error: {
482
- code: "NOT_FOUND",
483
- message: `Content item not found: ${id}`
484
- }
485
- };
486
- await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
487
- await hydrateBylines(db, collection, item);
488
- return {
489
- success: true,
490
- data: {
491
- item,
492
- _rev: encodeRev(item)
493
- }
494
- };
495
- } catch (error) {
496
- console.error("Content get error:", error);
497
- return {
498
- success: false,
499
- error: {
500
- code: "CONTENT_GET_ERROR",
501
- message: "Failed to get content"
502
- }
503
- };
504
- }
505
- }
506
- /**
507
- * Create content item.
508
- *
509
- * Content + SEO writes are wrapped in a transaction so either both succeed
510
- * or neither does. If `body.seo` is provided for a non-SEO collection, the
511
- * API returns a validation error rather than silently dropping it.
512
- */
513
- async function handleContentCreate(db, collection, body) {
514
- try {
515
- const hasSeo = await collectionHasSeo(db, collection);
516
- const usesDraftRevisions = await collectionSupportsRevisions(db, collection);
517
- if (body.seo && !hasSeo) return {
518
- success: false,
519
- error: {
520
- code: "VALIDATION_ERROR",
521
- message: `Collection "${collection}" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`
522
- }
523
- };
524
- const fieldCheck = await validateContentDataFields(db, collection, body.data);
525
- if (fieldCheck) return fieldCheck;
526
- const mimeCheck = await validateMediaFields(db, collection, body.data);
527
- if (!mimeCheck.success) return mimeCheck;
528
- const item = await withTransaction(db, async (trx) => {
529
- const repo = new ContentRepository(trx);
530
- const bylineRepo = new BylineRepository(trx);
531
- let slug = body.slug;
532
- if (!slug) {
533
- const slugSource = getSlugSource(body.data);
534
- if (slugSource) slug = await repo.generateUniqueSlug(collection, slugSource, body.locale);
535
- }
536
- let created = await repo.create({
537
- type: collection,
538
- slug,
539
- data: body.data,
540
- status: body.status || "draft",
541
- authorId: body.authorId,
542
- locale: body.locale,
543
- translationOf: body.translationOf,
544
- createdAt: body.createdAt,
545
- publishedAt: body.publishedAt
546
- });
547
- if (usesDraftRevisions && created.status === "draft") {
548
- const revision = await new RevisionRepository(trx).create({
549
- collection,
550
- entryId: created.id,
551
- data: created.data,
552
- authorId: body.authorId
553
- });
554
- await repo.setDraftRevision(collection, created.id, revision.id);
555
- const revisioned = await repo.findById(collection, created.id);
556
- if (!revisioned) throw new Error("Failed to reload content Draft Revision");
557
- created = revisioned;
558
- }
559
- if (body.bylines !== void 0) {
560
- const credits = await bylineRepo.setContentBylines(collection, created.id, body.bylines);
561
- created.primaryBylineId = credits[0]?.byline.translationGroup ?? null;
562
- }
563
- await hydrateBylines(trx, collection, created);
564
- if (body.seo && hasSeo) {
565
- const seoRepo = new SeoRepository(trx);
566
- created.seo = await seoRepo.upsert(collection, created.id, body.seo);
567
- } else if (hasSeo) created.seo = { ...SEO_DEFAULTS };
568
- return created;
569
- });
570
- return {
571
- success: true,
572
- data: {
573
- item,
574
- _rev: encodeRev(item)
575
- }
576
- };
577
- } catch (error) {
578
- console.error("Content create error:", error);
579
- return {
580
- success: false,
581
- error: {
582
- code: "CONTENT_CREATE_ERROR",
583
- message: "Failed to create content"
584
- }
585
- };
586
- }
587
- }
588
- /**
589
- * Update content item.
590
- * If `_rev` is provided, validates it against the current version before writing.
591
- * No `_rev` = blind write (backwards-compatible for admin UI).
592
- *
593
- * Content + SEO writes are wrapped in a transaction for atomicity.
594
- */
595
- async function handleContentUpdate(db, collection, id, body) {
596
- try {
597
- const hasSeo = await collectionHasSeo(db, collection);
598
- if (body.seo && !hasSeo) return {
599
- success: false,
600
- error: {
601
- code: "VALIDATION_ERROR",
602
- message: `Collection "${collection}" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`
603
- }
604
- };
605
- if (body.data) {
606
- const fieldCheck = await validateContentDataFields(db, collection, body.data);
607
- if (fieldCheck) return fieldCheck;
608
- const mimeCheck = await validateMediaFields(db, collection, body.data);
609
- if (!mimeCheck.success) return mimeCheck;
610
- }
611
- const resolvedId = await resolveId(new ContentRepository(db), collection, id, body.locale) ?? id;
612
- const item = await withTransaction(db, async (trx) => {
613
- const trxRepo = new ContentRepository(trx);
614
- const bylineRepo = new BylineRepository(trx);
615
- const existing = body._rev || body.slug ? await trxRepo.findById(collection, resolvedId) : null;
616
- if (body._rev) {
617
- if (!existing) throw new ContentUpdateApiError("NOT_FOUND", `Content item not found: ${id}`);
618
- const revCheck = validateRev(body._rev, existing);
619
- if (!revCheck.valid) throw new ContentUpdateApiError("CONFLICT", revCheck.message);
620
- }
621
- let oldSlug;
622
- if (body.slug && existing?.slug && existing.slug !== body.slug) oldSlug = existing.slug;
623
- const updated = await trxRepo.update(collection, resolvedId, {
624
- data: body.data,
625
- slug: body.slug,
626
- status: body.status,
627
- authorId: body.authorId,
628
- publishedAt: body.publishedAt
629
- });
630
- if (body.bylines !== void 0) updated.primaryBylineId = (await bylineRepo.setContentBylines(collection, resolvedId, body.bylines))[0]?.byline.translationGroup ?? null;
631
- if (oldSlug && body.slug) await createSlugChangeRedirect(trx, collection, oldSlug, body.slug, resolvedId);
632
- if (isI18nEnabled() && body.data && updated.translationGroup) await syncNonTranslatableFields(trx, collection, updated.id, updated.translationGroup, body.data);
633
- if (body.seo && hasSeo) updated.seo = await new SeoRepository(trx).upsert(collection, resolvedId, body.seo);
634
- else if (hasSeo) updated.seo = await new SeoRepository(trx).get(collection, resolvedId);
635
- await hydrateBylines(trx, collection, updated);
636
- return updated;
637
- });
638
- return {
639
- success: true,
640
- data: {
641
- item,
642
- _rev: encodeRev(item)
643
- }
644
- };
645
- } catch (error) {
646
- if (error instanceof ContentUpdateApiError) return {
647
- success: false,
648
- error: {
649
- code: error.code,
650
- message: error.message
651
- }
652
- };
653
- console.error("Content update error:", error);
654
- return {
655
- success: false,
656
- error: {
657
- code: "CONTENT_UPDATE_ERROR",
658
- message: "Failed to update content"
659
- }
660
- };
661
- }
662
- }
663
- /**
664
- * Duplicate content item.
665
- *
666
- * Only copies SEO data if the collection has SEO enabled.
667
- * Always returns consistent `seo` shape for SEO-enabled collections.
668
- */
669
- async function handleContentDuplicate(db, collection, id, authorId) {
670
- try {
671
- const hasSeo = await collectionHasSeo(db, collection);
672
- return {
673
- success: true,
674
- data: { item: await withTransaction(db, async (trx) => {
675
- const repo = new ContentRepository(trx);
676
- const bylineRepo = new BylineRepository(trx);
677
- const resolvedId = await resolveId(repo, collection, id) ?? id;
678
- const dup = await repo.duplicate(collection, resolvedId, authorId);
679
- await bylineRepo.copyContentBylines(collection, resolvedId, dup.id);
680
- if (hasSeo) {
681
- const seoRepo = new SeoRepository(trx);
682
- await seoRepo.copyForDuplicate(collection, resolvedId, dup.id);
683
- dup.seo = await seoRepo.get(collection, dup.id);
684
- }
685
- await hydrateBylines(trx, collection, dup);
686
- return dup;
687
- }) }
688
- };
689
- } catch (err) {
690
- if (err instanceof DinewayValidationError) return {
691
- success: false,
692
- error: {
693
- code: "NOT_FOUND",
694
- message: err.message
695
- }
696
- };
697
- console.error("Content duplicate error:", err);
698
- return {
699
- success: false,
700
- error: {
701
- code: "CONTENT_DUPLICATE_ERROR",
702
- message: "Failed to duplicate content"
703
- }
704
- };
705
- }
706
- }
707
- /**
708
- * Delete content item (soft delete - moves to trash)
709
- */
710
- async function handleContentDelete(db, collection, id) {
711
- try {
712
- const result = await withTransaction(db, async (trx) => {
713
- const repo = new ContentRepository(trx);
714
- const resolvedId = await resolveId(repo, collection, id) ?? id;
715
- return {
716
- id: resolvedId,
717
- deleted: await repo.delete(collection, resolvedId)
718
- };
719
- });
720
- if (!result.deleted) return {
721
- success: false,
722
- error: {
723
- code: "NOT_FOUND",
724
- message: `Content item not found: ${id}`
725
- }
726
- };
727
- return {
728
- success: true,
729
- data: {
730
- deleted: true,
731
- id: result.id
732
- }
733
- };
734
- } catch (error) {
735
- console.error("Content delete error:", error);
736
- return {
737
- success: false,
738
- error: {
739
- code: "CONTENT_DELETE_ERROR",
740
- message: "Failed to delete content"
741
- }
742
- };
743
- }
744
- }
745
- /**
746
- * Restore content item from trash
747
- */
748
- async function handleContentRestore(db, collection, id) {
749
- try {
750
- const item = await withTransaction(db, async (trx) => {
751
- const repo = new ContentRepository(trx);
752
- const resolvedId = await resolveIdIncludingTrashed(repo, collection, id) ?? id;
753
- return repo.restore(collection, resolvedId);
754
- });
755
- if (!item) return {
756
- success: false,
757
- error: {
758
- code: "NOT_FOUND",
759
- message: `Trashed content item not found: ${id}`
760
- }
761
- };
762
- return {
763
- success: true,
764
- data: {
765
- restored: true,
766
- item
767
- }
768
- };
769
- } catch (error) {
770
- console.error("Content restore error:", error);
771
- return {
772
- success: false,
773
- error: {
774
- code: "CONTENT_RESTORE_ERROR",
775
- message: "Failed to restore content"
776
- }
777
- };
778
- }
779
- }
780
- /**
781
- * Permanently delete content item (cannot be undone).
782
- * Also cleans up associated SEO data.
783
- */
784
- async function handleContentPermanentDelete(db, collection, id) {
785
- try {
786
- const resolvedId = await resolveIdIncludingTrashed(new ContentRepository(db), collection, id) ?? id;
787
- if (!await withTransaction(db, async (trx) => {
788
- const wasDeleted = await new ContentRepository(trx).permanentDelete(collection, resolvedId);
789
- if (wasDeleted) {
790
- await new SeoRepository(trx).delete(collection, resolvedId);
791
- await new CommentRepository(trx).deleteByContent(collection, resolvedId);
792
- await new RevisionRepository(trx).deleteByEntry(collection, resolvedId);
793
- }
794
- return wasDeleted;
795
- })) return {
796
- success: false,
797
- error: {
798
- code: "NOT_FOUND",
799
- message: `Content item not found: ${id}`
800
- }
801
- };
802
- return {
803
- success: true,
804
- data: {
805
- deleted: true,
806
- id: resolvedId
807
- }
808
- };
809
- } catch (error) {
810
- console.error("Content permanent delete error:", error);
811
- return {
812
- success: false,
813
- error: {
814
- code: "CONTENT_DELETE_ERROR",
815
- message: "Failed to permanently delete content"
816
- }
817
- };
818
- }
819
- }
820
- /**
821
- * List trashed content items
822
- */
823
- async function handleContentListTrashed(db, collection, options = {}) {
824
- try {
825
- const result = await new ContentRepository(db).findTrashed(collection, {
826
- limit: options.limit,
827
- cursor: options.cursor
828
- });
829
- return {
830
- success: true,
831
- data: {
832
- items: result.items.map((item) => ({
833
- id: item.id,
834
- type: item.type,
835
- slug: item.slug,
836
- status: item.status,
837
- data: item.data,
838
- authorId: item.authorId,
839
- createdAt: item.createdAt,
840
- updatedAt: item.updatedAt,
841
- publishedAt: item.publishedAt,
842
- deletedAt: item.deletedAt
843
- })),
844
- nextCursor: result.nextCursor
845
- }
846
- };
847
- } catch (error) {
848
- console.error("Content list trashed error:", error);
849
- return {
850
- success: false,
851
- error: {
852
- code: "CONTENT_LIST_ERROR",
853
- message: "Failed to list trashed content"
854
- }
855
- };
856
- }
857
- }
858
- /**
859
- * Count trashed content items
860
- */
861
- async function handleContentCountTrashed(db, collection) {
862
- try {
863
- return {
864
- success: true,
865
- data: { count: await new ContentRepository(db).countTrashed(collection) }
866
- };
867
- } catch (error) {
868
- console.error("Content count trashed error:", error);
869
- return {
870
- success: false,
871
- error: {
872
- code: "CONTENT_COUNT_ERROR",
873
- message: "Failed to count trashed content"
874
- }
875
- };
876
- }
877
- }
878
- /**
879
- * Schedule content for future publishing
880
- */
881
- async function handleContentSchedule(db, collection, id, scheduledAt) {
882
- try {
883
- const item = await withTransaction(db, async (trx) => {
884
- const repo = new ContentRepository(trx);
885
- const resolvedId = await resolveId(repo, collection, id) ?? id;
886
- return repo.schedule(collection, resolvedId, scheduledAt);
887
- });
888
- await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
889
- return {
890
- success: true,
891
- data: { item }
892
- };
893
- } catch (error) {
894
- if (error instanceof DinewayValidationError) return {
895
- success: false,
896
- error: {
897
- code: "VALIDATION_ERROR",
898
- message: error.message
899
- }
900
- };
901
- console.error("Content schedule error:", error);
902
- return {
903
- success: false,
904
- error: {
905
- code: "CONTENT_SCHEDULE_ERROR",
906
- message: "Failed to schedule content"
907
- }
908
- };
909
- }
910
- }
911
- /**
912
- * Unschedule content (revert to draft)
913
- */
914
- async function handleContentUnschedule(db, collection, id) {
915
- try {
916
- const item = await withTransaction(db, async (trx) => {
917
- const repo = new ContentRepository(trx);
918
- const resolvedId = await resolveId(repo, collection, id) ?? id;
919
- return repo.unschedule(collection, resolvedId);
920
- });
921
- await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
922
- return {
923
- success: true,
924
- data: { item }
925
- };
926
- } catch (error) {
927
- if (error instanceof DinewayValidationError) return {
928
- success: false,
929
- error: {
930
- code: "VALIDATION_ERROR",
931
- message: error.message
932
- }
933
- };
934
- console.error("Content unschedule error:", error);
935
- return {
936
- success: false,
937
- error: {
938
- code: "CONTENT_UNSCHEDULE_ERROR",
939
- message: "Failed to unschedule content"
940
- }
941
- };
942
- }
943
- }
944
- /**
945
- * Publish content immediately.
946
- *
947
- * Wrapped in a transaction because publish performs multiple writes
948
- * (syncDataColumns, slug sync, status/revision update) that must
949
- * be atomic to prevent FTS shadow table corruption on crash.
950
- */
951
- async function handleContentPublish(db, collection, id, options = {}) {
952
- try {
953
- const item = await withTransaction(db, async (trx) => {
954
- const repo = new ContentRepository(trx);
955
- const resolvedId = await resolveId(repo, collection, id) ?? id;
956
- const existing = await repo.findById(collection, resolvedId);
957
- const published = await repo.publish(collection, resolvedId, options.publishedAt, options.requireScheduledDue);
958
- if (existing?.status === "published" && existing.slug && published.slug && existing.slug !== published.slug) await createSlugChangeRedirect(trx, collection, existing.slug, published.slug, resolvedId);
959
- return published;
960
- });
961
- await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
962
- return {
963
- success: true,
964
- data: { item }
965
- };
966
- } catch (error) {
967
- if (error instanceof DinewayValidationError) return {
968
- success: false,
969
- error: {
970
- code: "VALIDATION_ERROR",
971
- message: error.message
972
- }
973
- };
974
- if (error instanceof ScheduledNotDueError) return {
975
- success: false,
976
- error: {
977
- code: "NOT_DUE",
978
- message: error.message
979
- }
980
- };
981
- console.error("Content publish error:", error);
982
- return {
983
- success: false,
984
- error: {
985
- code: "CONTENT_PUBLISH_ERROR",
986
- message: "Failed to publish content"
987
- }
988
- };
989
- }
990
- }
991
- /**
992
- * Unpublish content (revert to draft).
993
- *
994
- * Wrapped in a transaction — unpublish may create a draft revision
995
- * from the live version then update the status, which is multi-step.
996
- */
997
- async function handleContentUnpublish(db, collection, id) {
998
- try {
999
- const item = await withTransaction(db, async (trx) => {
1000
- const repo = new ContentRepository(trx);
1001
- const resolvedId = await resolveId(repo, collection, id) ?? id;
1002
- return repo.unpublish(collection, resolvedId);
1003
- });
1004
- await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
1005
- return {
1006
- success: true,
1007
- data: { item }
1008
- };
1009
- } catch (error) {
1010
- if (error instanceof DinewayValidationError) return {
1011
- success: false,
1012
- error: {
1013
- code: "VALIDATION_ERROR",
1014
- message: error.message
1015
- }
1016
- };
1017
- console.error("Content unpublish error:", error);
1018
- return {
1019
- success: false,
1020
- error: {
1021
- code: "CONTENT_UNPUBLISH_ERROR",
1022
- message: "Failed to unpublish content"
1023
- }
1024
- };
1025
- }
1026
- }
1027
- /**
1028
- * Count scheduled content items
1029
- */
1030
- async function handleContentCountScheduled(db, collection) {
1031
- try {
1032
- return {
1033
- success: true,
1034
- data: { count: await new ContentRepository(db).countScheduled(collection) }
1035
- };
1036
- } catch (error) {
1037
- console.error("Content count scheduled error:", error);
1038
- return {
1039
- success: false,
1040
- error: {
1041
- code: "CONTENT_COUNT_ERROR",
1042
- message: "Failed to count scheduled content"
1043
- }
1044
- };
1045
- }
1046
- }
1047
- /**
1048
- * Discard draft changes (revert to live version)
1049
- */
1050
- async function handleContentDiscardDraft(db, collection, id) {
1051
- try {
1052
- const item = await withTransaction(db, async (trx) => {
1053
- const repo = new ContentRepository(trx);
1054
- const resolvedId = await resolveId(repo, collection, id) ?? id;
1055
- return repo.discardDraft(collection, resolvedId);
1056
- });
1057
- await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
1058
- return {
1059
- success: true,
1060
- data: { item }
1061
- };
1062
- } catch (error) {
1063
- if (error instanceof DinewayValidationError) return {
1064
- success: false,
1065
- error: {
1066
- code: "NOT_FOUND",
1067
- message: error.message
1068
- }
1069
- };
1070
- console.error("Content discard draft error:", error);
1071
- return {
1072
- success: false,
1073
- error: {
1074
- code: "CONTENT_DISCARD_DRAFT_ERROR",
1075
- message: "Failed to discard draft"
1076
- }
1077
- };
1078
- }
1079
- }
1080
- /**
1081
- * Compare live and draft revisions
1082
- */
1083
- async function handleContentCompare(db, collection, id) {
1084
- try {
1085
- const entry = await new ContentRepository(db).findByIdOrSlug(collection, id);
1086
- if (!entry) return {
1087
- success: false,
1088
- error: {
1089
- code: "NOT_FOUND",
1090
- message: `Content item not found: ${id}`
1091
- }
1092
- };
1093
- const revisionRepo = new RevisionRepository(db);
1094
- const live = entry.liveRevisionId ? await revisionRepo.findById(entry.liveRevisionId) : null;
1095
- const draft = entry.draftRevisionId ? await revisionRepo.findById(entry.draftRevisionId) : null;
1096
- return {
1097
- success: true,
1098
- data: {
1099
- hasChanges: entry.draftRevisionId !== null && entry.draftRevisionId !== entry.liveRevisionId,
1100
- live: live?.data ?? null,
1101
- draft: draft?.data ?? null
1102
- }
1103
- };
1104
- } catch (error) {
1105
- console.error("Content compare error:", error);
1106
- return {
1107
- success: false,
1108
- error: {
1109
- code: "CONTENT_COMPARE_ERROR",
1110
- message: "Failed to compare revisions"
1111
- }
1112
- };
1113
- }
1114
- }
1115
- /**
1116
- * Get all translations for a content item.
1117
- * Returns the item's translation group members with locale and status info.
1118
- */
1119
- async function handleContentTranslations(db, collection, id) {
1120
- try {
1121
- const repo = new ContentRepository(db);
1122
- const item = await repo.findByIdOrSlug(collection, id);
1123
- if (!item) return {
1124
- success: false,
1125
- error: {
1126
- code: "NOT_FOUND",
1127
- message: `Content item not found: ${id}`
1128
- }
1129
- };
1130
- if (!item.translationGroup) return {
1131
- success: true,
1132
- data: {
1133
- translationGroup: item.id,
1134
- translations: [{
1135
- id: item.id,
1136
- locale: item.locale,
1137
- slug: item.slug,
1138
- status: item.status,
1139
- updatedAt: item.updatedAt
1140
- }]
1141
- }
1142
- };
1143
- const translations = await repo.findTranslations(collection, item.translationGroup);
1144
- return {
1145
- success: true,
1146
- data: {
1147
- translationGroup: item.translationGroup,
1148
- translations: translations.map((t) => ({
1149
- id: t.id,
1150
- locale: t.locale,
1151
- slug: t.slug,
1152
- status: t.status,
1153
- updatedAt: t.updatedAt
1154
- }))
1155
- }
1156
- };
1157
- } catch (error) {
1158
- if (error instanceof Error) console.error("Content translations error:", error);
1159
- return {
1160
- success: false,
1161
- error: {
1162
- code: "CONTENT_TRANSLATIONS_ERROR",
1163
- message: "Failed to get translations"
1164
- }
1165
- };
1166
- }
1167
- }
1168
- /**
1169
- * Sync non-translatable fields to sibling locales.
1170
- *
1171
- * When a content item is updated and it belongs to a translation group,
1172
- * any non-translatable fields in the update data are written to all other
1173
- * rows in the same translation group within the same transaction.
1174
- *
1175
- * Non-translatable fields are **copied, not linked** — each row owns its
1176
- * own data. This keeps queries simple and avoids cross-row joins.
1177
- */
1178
- async function syncNonTranslatableFields(trx, collectionSlug, updatedItemId, translationGroup, data) {
1179
- const collection = await trx.selectFrom("_dineway_collections").select("id").where("slug", "=", collectionSlug).executeTakeFirst();
1180
- if (!collection) return;
1181
- const nonTranslatableSlugs = (await trx.selectFrom("_dineway_fields").select("slug").where("collection_id", "=", collection.id).where("translatable", "=", 0).execute()).map((f) => f.slug);
1182
- if (nonTranslatableSlugs.length === 0) return;
1183
- const syncData = {};
1184
- for (const slug of nonTranslatableSlugs) if (slug in data) syncData[slug] = data[slug];
1185
- if (Object.keys(syncData).length === 0) return;
1186
- validateIdentifier(collectionSlug, "collection slug");
1187
- const tableName = `ec_${collectionSlug}`;
1188
- const setClauses = Object.entries(syncData).map(([key, value]) => {
1189
- validateIdentifier(key, "field slug");
1190
- const serialized = typeof value === "object" && value !== null ? JSON.stringify(value) : value;
1191
- return sql`${sql.ref(key)} = ${serialized}`;
1192
- });
1193
- await sql`
1194
- UPDATE ${sql.ref(tableName)}
1195
- SET ${sql.join(setClauses, sql`, `)}
1196
- WHERE translation_group = ${translationGroup}
1197
- AND id != ${updatedItemId}
1198
- `.execute(trx);
1199
- }
1200
-
1201
- //#endregion
1202
19
  //#region src/api/handlers/manifest.ts
1203
20
  /**
1204
21
  * Manifest generation handlers
@@ -1322,6 +139,9 @@ function formatLabel(name) {
1322
139
  //#endregion
1323
140
  //#region src/api/handlers/revision.ts
1324
141
  /**
142
+ * Revision history handlers
143
+ */
144
+ /**
1325
145
  * List revisions for a content entry
1326
146
  */
1327
147
  async function handleRevisionList(db, collection, entryId, params = {}) {
@@ -1378,7 +198,12 @@ async function handleRevisionGet(db, revisionId) {
1378
198
  async function handleRevisionRestore(db, revisionId, callerUserId) {
1379
199
  try {
1380
200
  const revisionRepo = new RevisionRepository(db);
1381
- const revision = await revisionRepo.findById(revisionId);
201
+ const original = await db.selectFrom("revisions").selectAll().where("id", "=", revisionId).executeTakeFirst();
202
+ const revision = original ? {
203
+ collection: original.collection,
204
+ entryId: original.entry_id,
205
+ data: z.record(z.string(), z.unknown()).parse(JSON.parse(original.data))
206
+ } : null;
1382
207
  if (!revision) return {
1383
208
  success: false,
1384
209
  error: {
@@ -1387,22 +212,37 @@ async function handleRevisionRestore(db, revisionId, callerUserId) {
1387
212
  }
1388
213
  };
1389
214
  const { _slug, ...fieldData } = revision.data;
1390
- const item = await withTransaction(db, async (trx) => {
1391
- const contentRepo = new ContentRepository(trx);
1392
- const txRevisionRepo = new RevisionRepository(trx);
1393
- const restored = await contentRepo.update(revision.collection, revision.entryId, {
1394
- data: fieldData,
1395
- slug: typeof _slug === "string" ? _slug : void 0
1396
- });
1397
- await txRevisionRepo.create({
1398
- collection: revision.collection,
1399
- entryId: revision.entryId,
1400
- data: revision.data,
1401
- authorId: callerUserId
1402
- });
1403
- return restored;
215
+ const contentRepo = new ContentRepository(db);
216
+ const snapshot = await contentRepo.snapshot(revision.collection, revision.entryId);
217
+ if (!snapshot || !original) throw new Error("Content is unavailable");
218
+ const prepared = revisionRepo.prepareCreateSnapshot({
219
+ collection: revision.collection,
220
+ entryId: revision.entryId,
221
+ data: sql`${original.data}`,
222
+ authorId: callerUserId
223
+ });
224
+ const preserveLive = !!snapshot.item.liveRevisionId;
225
+ await atomicBatch(db, {
226
+ checks: [
227
+ snapshot.check,
228
+ rowUnchanged("revisions", original),
229
+ ...contentWriteChecks(revision.collection, revision.entryId, "edit")
230
+ ],
231
+ statements: [
232
+ prepared.statement,
233
+ contentRepo.prepareUpdate(revision.collection, revision.entryId, {
234
+ data: preserveLive ? void 0 : fieldData,
235
+ slug: !preserveLive && typeof _slug === "string" ? _slug : void 0
236
+ }),
237
+ sql`UPDATE ${sql.table(`ec_${revision.collection}`)} SET draft_revision_id = ${prepared.id} WHERE id = ${revision.entryId}`.compile(db)
238
+ ]
239
+ });
240
+ invalidateCollectionCache(revision.collection);
241
+ const item = await contentRepo.findById(revision.collection, revision.entryId);
242
+ if (!item) throw new Error("Failed to reload restored content");
243
+ after(async () => {
244
+ await revisionRepo.pruneOldRevisions(revision.collection, revision.entryId, 50);
1404
245
  });
1405
- revisionRepo.pruneOldRevisions(revision.collection, revision.entryId, 50).catch(() => {});
1406
246
  return {
1407
247
  success: true,
1408
248
  data: { item }
@@ -2948,4 +1788,4 @@ async function handleThemeGetDetail(marketplaceUrl, themeId) {
2948
1788
  }
2949
1789
 
2950
1790
  //#endregion
2951
- export { handleContentUpdate as $, handleRevisionGet as A, handleContentDiscardDraft as B, handleSchemaFieldReorder as C, handleMediaGet as D, handleMediaDelete as E, handleContentCompare as F, handleContentListTrashed as G, handleContentGet as H, handleContentCountScheduled as I, handleContentRestore as J, handleContentPermanentDelete as K, handleContentCountTrashed as L, handleRevisionRestore as M, generateManifest as N, handleMediaList as O, handleContentAuthors as P, handleContentUnschedule as Q, handleContentCreate as R, handleSchemaFieldList as S, handleMediaCreate as T, handleContentGetIncludingTrashed as U, handleContentDuplicate as V, handleContentList as W, handleContentTranslations as X, handleContentSchedule as Y, handleContentUnpublish as Z, handleSchemaCollectionList as _, handleMarketplaceUninstall as a, handleSchemaFieldDelete as b, handleMarketplaceUpdatePreview as c, createPluginBundleStore as d, decodeRev as et, handleOrphanedTableList as f, handleSchemaCollectionGet as g, handleSchemaCollectionDelete as h, handleMarketplaceSearch as i, handleRevisionList as j, handleMediaUpdate as k, handleThemeGetDetail as l, handleSchemaCollectionCreate as m, handleMarketplaceInstall as n, handleMarketplaceUpdate as o, handleOrphanedTableRegister as p, handleContentPublish as q, handleMarketplaceInstallPreview as r, handleMarketplaceUpdateCheck as s, handleMarketplaceGetPlugin as t, validateRev as tt, handleThemeSearch as u, handleSchemaCollectionUpdate as v, handleSchemaFieldUpdate as w, handleSchemaFieldGet as x, handleSchemaFieldCreate as y, handleContentDelete as z };
1791
+ export { handleRevisionGet as A, handleSchemaFieldReorder as C, handleMediaGet as D, handleMediaDelete as E, handleRevisionRestore as M, generateManifest as N, handleMediaList as O, handleSchemaFieldList as S, handleMediaCreate as T, handleSchemaCollectionList as _, handleMarketplaceUninstall as a, handleSchemaFieldDelete as b, handleMarketplaceUpdatePreview as c, createPluginBundleStore as d, handleOrphanedTableList as f, handleSchemaCollectionGet as g, handleSchemaCollectionDelete as h, handleMarketplaceSearch as i, handleRevisionList as j, handleMediaUpdate as k, handleThemeGetDetail as l, handleSchemaCollectionCreate as m, handleMarketplaceInstall as n, handleMarketplaceUpdate as o, handleOrphanedTableRegister as p, handleMarketplaceInstallPreview as r, handleMarketplaceUpdateCheck as s, handleMarketplaceGetPlugin as t, handleThemeSearch as u, handleSchemaCollectionUpdate as v, handleSchemaFieldUpdate as w, handleSchemaFieldGet as x, handleSchemaFieldCreate as y };