convex-cms 0.0.1 → 0.0.3

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 (267) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +99 -0
  3. package/admin-dist/nitro.json +15 -0
  4. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  5. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  6. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  7. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  8. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  9. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  10. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  11. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  12. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  13. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  14. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  15. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  16. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  17. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  18. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  19. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  20. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  21. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  22. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  23. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  24. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  25. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  26. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  27. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  28. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  29. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  30. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  31. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  32. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  33. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  34. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  35. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  36. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  37. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  38. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  39. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  40. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  41. package/admin-dist/public/favicon.ico +0 -0
  42. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  45. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  46. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  83. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  84. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  88. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  89. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  90. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  91. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  92. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  93. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  94. package/admin-dist/server/_libs/clsx.mjs +16 -0
  95. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  96. package/admin-dist/server/_libs/convex.mjs +4841 -0
  97. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  98. package/admin-dist/server/_libs/croner.mjs +1 -0
  99. package/admin-dist/server/_libs/crossws.mjs +1 -0
  100. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  101. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  102. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  103. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  104. package/admin-dist/server/_libs/h3.mjs +401 -0
  105. package/admin-dist/server/_libs/hookable.mjs +1 -0
  106. package/admin-dist/server/_libs/isbot.mjs +20 -0
  107. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  108. package/admin-dist/server/_libs/ohash.mjs +1 -0
  109. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  110. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  111. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  112. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  113. package/admin-dist/server/_libs/rou3.mjs +8 -0
  114. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  115. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  116. package/admin-dist/server/_libs/srvx.mjs +719 -0
  117. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  118. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  119. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  120. package/admin-dist/server/_libs/tslib.mjs +39 -0
  121. package/admin-dist/server/_libs/ufo.mjs +54 -0
  122. package/admin-dist/server/_libs/unctx.mjs +1 -0
  123. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  124. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  125. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  126. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  127. package/admin-dist/server/_libs/zod.mjs +4223 -0
  128. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  129. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  130. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  131. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  132. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  133. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  134. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  135. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  136. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  137. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  138. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  139. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  140. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  141. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  142. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  143. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  144. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  145. package/admin-dist/server/_ssr/index.mjs +1275 -0
  146. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  147. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  148. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  149. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  150. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  151. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  152. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  153. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  154. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  155. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  156. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  157. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  158. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  159. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  160. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  161. package/admin-dist/server/favicon.ico +0 -0
  162. package/admin-dist/server/index.mjs +627 -0
  163. package/dist/cli/index.js +0 -0
  164. package/dist/client/admin-config.d.ts +0 -1
  165. package/dist/client/admin-config.d.ts.map +1 -1
  166. package/dist/client/admin-config.js +0 -1
  167. package/dist/client/admin-config.js.map +1 -1
  168. package/dist/client/adminApi.d.ts.map +1 -1
  169. package/dist/client/agentTools.d.ts +1237 -135
  170. package/dist/client/agentTools.d.ts.map +1 -1
  171. package/dist/client/agentTools.js +33 -9
  172. package/dist/client/agentTools.js.map +1 -1
  173. package/dist/client/index.d.ts +1 -1
  174. package/dist/client/index.d.ts.map +1 -1
  175. package/dist/client/index.js.map +1 -1
  176. package/dist/component/_generated/component.d.ts +9 -0
  177. package/dist/component/_generated/component.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.d.ts +35 -0
  179. package/dist/component/mediaAssets.d.ts.map +1 -1
  180. package/dist/component/mediaAssets.js +81 -0
  181. package/dist/component/mediaAssets.js.map +1 -1
  182. package/dist/test.d.ts.map +1 -1
  183. package/dist/test.js +2 -1
  184. package/dist/test.js.map +1 -1
  185. package/package.json +24 -9
  186. package/dist/component/auditLog.d.ts +0 -410
  187. package/dist/component/auditLog.d.ts.map +0 -1
  188. package/dist/component/auditLog.js +0 -607
  189. package/dist/component/auditLog.js.map +0 -1
  190. package/dist/component/types.d.ts +0 -4
  191. package/dist/component/types.d.ts.map +0 -1
  192. package/dist/component/types.js +0 -2
  193. package/dist/component/types.js.map +0 -1
  194. package/src/cli/commands/admin.ts +0 -104
  195. package/src/cli/index.ts +0 -21
  196. package/src/cli/utils/detectConvexUrl.ts +0 -54
  197. package/src/cli/utils/openBrowser.ts +0 -16
  198. package/src/client/admin-config.ts +0 -138
  199. package/src/client/adminApi.ts +0 -942
  200. package/src/client/agentTools.ts +0 -1311
  201. package/src/client/argTypes.ts +0 -316
  202. package/src/client/field-types.ts +0 -187
  203. package/src/client/index.ts +0 -1301
  204. package/src/client/queryBuilder.ts +0 -1100
  205. package/src/client/schema/codegen.ts +0 -500
  206. package/src/client/schema/defineContentType.ts +0 -501
  207. package/src/client/schema/index.ts +0 -169
  208. package/src/client/schema/schemaDrift.ts +0 -574
  209. package/src/client/schema/typedClient.ts +0 -688
  210. package/src/client/schema/types.ts +0 -666
  211. package/src/client/types.ts +0 -723
  212. package/src/client/workflows.ts +0 -141
  213. package/src/client/wrapper.ts +0 -4304
  214. package/src/component/_generated/api.ts +0 -140
  215. package/src/component/_generated/component.ts +0 -5029
  216. package/src/component/_generated/dataModel.ts +0 -60
  217. package/src/component/_generated/server.ts +0 -156
  218. package/src/component/authorization.ts +0 -647
  219. package/src/component/authorizationHooks.ts +0 -668
  220. package/src/component/bulkOperations.ts +0 -687
  221. package/src/component/contentEntries.ts +0 -1976
  222. package/src/component/contentEntryMutations.ts +0 -1223
  223. package/src/component/contentEntryValidation.ts +0 -707
  224. package/src/component/contentLock.ts +0 -550
  225. package/src/component/contentTypeMigration.ts +0 -1064
  226. package/src/component/contentTypeMutations.ts +0 -969
  227. package/src/component/contentTypes.ts +0 -346
  228. package/src/component/convex.config.ts +0 -44
  229. package/src/component/documentTypes.ts +0 -240
  230. package/src/component/eventEmitter.ts +0 -485
  231. package/src/component/exportImport.ts +0 -1169
  232. package/src/component/index.ts +0 -491
  233. package/src/component/lib/deepReferenceResolver.ts +0 -999
  234. package/src/component/lib/errors.ts +0 -816
  235. package/src/component/lib/index.ts +0 -145
  236. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  237. package/src/component/lib/metadataExtractor.ts +0 -792
  238. package/src/component/lib/mutationAuth.ts +0 -199
  239. package/src/component/lib/queries.ts +0 -79
  240. package/src/component/lib/ragContentChunker.ts +0 -1371
  241. package/src/component/lib/referenceResolver.ts +0 -430
  242. package/src/component/lib/slugGenerator.ts +0 -262
  243. package/src/component/lib/slugUniqueness.ts +0 -333
  244. package/src/component/lib/softDelete.ts +0 -44
  245. package/src/component/localeFallbackChain.ts +0 -673
  246. package/src/component/localeFields.ts +0 -896
  247. package/src/component/mediaAssetMutations.ts +0 -725
  248. package/src/component/mediaAssets.ts +0 -932
  249. package/src/component/mediaFolderMutations.ts +0 -1046
  250. package/src/component/mediaUploadMutations.ts +0 -224
  251. package/src/component/mediaVariantMutations.ts +0 -900
  252. package/src/component/mediaVariants.ts +0 -793
  253. package/src/component/ragContentIndexer.ts +0 -1067
  254. package/src/component/rateLimitHooks.ts +0 -572
  255. package/src/component/roles.ts +0 -1360
  256. package/src/component/scheduledPublish.ts +0 -358
  257. package/src/component/schema.ts +0 -617
  258. package/src/component/taxonomies.ts +0 -949
  259. package/src/component/taxonomyMutations.ts +0 -1210
  260. package/src/component/trash.ts +0 -724
  261. package/src/component/userContext.ts +0 -898
  262. package/src/component/validation.ts +0 -1388
  263. package/src/component/validators.ts +0 -949
  264. package/src/component/versionMutations.ts +0 -392
  265. package/src/component/webhookTrigger.ts +0 -1922
  266. package/src/react/index.ts +0 -898
  267. package/src/test.ts +0 -1580
@@ -1,392 +0,0 @@
1
- /**
2
- * Version Mutation Functions
3
- *
4
- * Provides internal mutation functions for creating and managing version snapshots.
5
- * These functions are used internally by the CMS to maintain version history
6
- * for content entries.
7
- *
8
- * Version snapshots capture:
9
- * - Complete content data at a point in time
10
- * - Slug and status when the snapshot was created
11
- * - Metadata about who created it and why
12
- * - Whether the snapshot represents a published version
13
- */
14
-
15
- import { v } from "convex/values";
16
- import { isDeleted } from "./lib/softDelete.js";
17
- import { mutation, internalMutation } from "./_generated/server.js";
18
- import {
19
- createVersionSnapshotArgs,
20
- contentVersionDoc,
21
- contentEntryDoc,
22
- rollbackVersionArgs,
23
- } from "./validators.js";
24
- import {
25
- versionEntryNotFound,
26
- versionEntryDeleted,
27
- versionNotFound,
28
- versionMismatch,
29
- versionRollbackFailed,
30
- internalError,
31
- } from "./lib/errors.js";
32
-
33
- // =============================================================================
34
- // Create Version Snapshot (Internal)
35
- // =============================================================================
36
-
37
- /**
38
- * Internal mutation to create a version snapshot of a content entry.
39
- *
40
- * This function captures the complete state of a content entry at a specific
41
- * point in time, storing it in the contentVersions table. Snapshots are used
42
- * for:
43
- *
44
- * - **Version History**: Track changes over time for audit and review
45
- * - **Rollback Support**: Allow reverting to previous versions
46
- * - **Publishing Records**: Mark which versions were published
47
- * - **Content Comparison**: Enable diff/compare between versions
48
- *
49
- * The snapshot includes:
50
- * - versionNumber: Current version number from the entry
51
- * - data: Complete content data snapshot
52
- * - slug: Slug at the time of snapshot
53
- * - status: Entry status when snapshot was created
54
- * - changeDescription: Optional description of changes
55
- * - createdBy: User who triggered the snapshot
56
- * - wasPublished: Whether this is a published version
57
- * - publishedAt: Timestamp if this is a published version
58
- *
59
- * @param entryId - The ID of the content entry to snapshot
60
- * @param changeDescription - Optional description of what changed
61
- * @param createdBy - User ID for audit trail
62
- * @param wasPublished - Whether this snapshot represents a publish action
63
- *
64
- * @returns The created version snapshot document
65
- *
66
- * @throws Error if the entry does not exist
67
- * @throws Error if the entry has been soft-deleted
68
- *
69
- * @example
70
- * ```typescript
71
- * // Called internally before a destructive operation
72
- * await ctx.runMutation(internal.versionMutations.createVersionSnapshot, {
73
- * entryId: entryId,
74
- * changeDescription: "Pre-update snapshot",
75
- * createdBy: userId,
76
- * });
77
- *
78
- * // Called during publish to mark as published version
79
- * await ctx.runMutation(internal.versionMutations.createVersionSnapshot, {
80
- * entryId: entryId,
81
- * changeDescription: "Published to production",
82
- * createdBy: userId,
83
- * wasPublished: true,
84
- * });
85
- * ```
86
- */
87
- export const createVersionSnapshot = internalMutation({
88
- args: createVersionSnapshotArgs.fields,
89
- returns: contentVersionDoc,
90
- handler: async (ctx, args) => {
91
- const {
92
- entryId,
93
- changeDescription,
94
- createdBy,
95
- wasPublished = false,
96
- } = args;
97
-
98
- const entry = await ctx.db.get(entryId);
99
-
100
- if (!entry) {
101
- throw versionEntryNotFound((entryId as unknown) as string);
102
- }
103
-
104
- // Do not allow snapshots of deleted entries
105
- if (isDeleted(entry)) {
106
- throw versionEntryDeleted((entryId as unknown) as string);
107
- }
108
-
109
- const now = Date.now();
110
-
111
- // Create the version snapshot with complete entry state
112
- const versionId = await ctx.db.insert("contentVersions", {
113
- entryId,
114
- versionNumber: entry.version,
115
- data: entry.data,
116
- slug: entry.slug,
117
- status: entry.status,
118
- changeDescription,
119
- createdBy,
120
- wasPublished,
121
- publishedAt: wasPublished ? now : undefined,
122
- });
123
-
124
- // Retrieve and return the created version
125
- const version = await ctx.db.get(versionId);
126
-
127
- if (!version) {
128
- throw internalError("Failed to create version snapshot");
129
- }
130
-
131
- return version;
132
- },
133
- });
134
-
135
- // =============================================================================
136
- // Check for Duplicate Version (Internal Helper)
137
- // =============================================================================
138
-
139
- /**
140
- * Internal mutation to check if a version snapshot already exists.
141
- *
142
- * This can be used before creating a snapshot to avoid duplicates,
143
- * particularly useful when the same version might be snapshotted
144
- * multiple times (e.g., multiple publishes without content changes).
145
- *
146
- * @param entryId - The content entry ID
147
- * @param versionNumber - The version number to check for
148
- *
149
- * @returns true if a version with this number exists, false otherwise
150
- */
151
- export const versionExists = internalMutation({
152
- args: {
153
- entryId: v.id("contentEntries"),
154
- versionNumber: v.number(),
155
- },
156
- returns: v.boolean(),
157
- handler: async (ctx, args) => {
158
- const { entryId, versionNumber } = args;
159
-
160
- const existing = await ctx.db
161
- .query("contentVersions")
162
- .withIndex("by_entry_and_version", (q) =>
163
- q.eq("entryId", entryId).eq("versionNumber", versionNumber),
164
- )
165
- .first();
166
-
167
- return existing !== null;
168
- },
169
- });
170
-
171
- // =============================================================================
172
- // Create Snapshot If Changed (Internal Helper)
173
- // =============================================================================
174
-
175
- /**
176
- * Internal mutation to create a version snapshot only if the version
177
- * doesn't already exist. This is a convenience function that combines
178
- * the existence check and creation.
179
- *
180
- * Useful for scenarios where you want to snapshot but only if the
181
- * current version hasn't been captured yet (e.g., auto-save scenarios).
182
- *
183
- * @param entryId - The content entry ID
184
- * @param changeDescription - Optional description of changes
185
- * @param createdBy - User ID for audit trail
186
- * @param wasPublished - Whether this is a published version
187
- *
188
- * @returns The version snapshot (new or existing), or null if entry not found
189
- */
190
- export const createVersionSnapshotIfNotExists = internalMutation({
191
- args: createVersionSnapshotArgs.fields,
192
- returns: v.union(contentVersionDoc, v.null()),
193
- handler: async (ctx, args) => {
194
- const {
195
- entryId,
196
- changeDescription,
197
- createdBy,
198
- wasPublished = false,
199
- } = args;
200
-
201
- const entry = await ctx.db.get(entryId);
202
-
203
- if (!entry) {
204
- return null;
205
- }
206
-
207
- // Do not process deleted entries
208
- if (isDeleted(entry)) {
209
- return null;
210
- }
211
-
212
- // Check if this version already has a snapshot
213
- const existing = await ctx.db
214
- .query("contentVersions")
215
- .withIndex("by_entry_and_version", (q) =>
216
- q.eq("entryId", entryId).eq("versionNumber", entry.version),
217
- )
218
- .first();
219
-
220
- // Return existing if found
221
- if (existing) {
222
- return existing;
223
- }
224
-
225
- const now = Date.now();
226
-
227
- // Create new snapshot
228
- const versionId = await ctx.db.insert("contentVersions", {
229
- entryId,
230
- versionNumber: entry.version,
231
- data: entry.data,
232
- slug: entry.slug,
233
- status: entry.status,
234
- changeDescription,
235
- createdBy,
236
- wasPublished,
237
- publishedAt: wasPublished ? now : undefined,
238
- });
239
-
240
- const version = await ctx.db.get(versionId);
241
-
242
- return version ?? null;
243
- },
244
- });
245
-
246
- // =============================================================================
247
- // Rollback Version (Public)
248
- // =============================================================================
249
-
250
- /**
251
- * Mutation to restore a content entry to a previous version.
252
- *
253
- * This is the core rollback functionality that allows users to revert content
254
- * to any previously captured version state. Importantly, rollback is a
255
- * **non-destructive operation** - it creates a new version with the restored
256
- * content rather than actually "going back in time."
257
- *
258
- * ## How Rollback Works
259
- *
260
- * 1. **Validate**: Ensure the entry and target version exist and are accessible
261
- * 2. **Snapshot Current State**: Create a version snapshot of the current state
262
- * before making any changes (preserves ability to "undo" the rollback)
263
- * 3. **Restore Content**: Update the entry's `data` and `slug` from the target version
264
- * 4. **Increment Version**: The entry's version number is incremented (normal update behavior)
265
- * 5. **Create Rollback Snapshot**: Create a new version snapshot documenting the rollback
266
- *
267
- * ## What Gets Restored
268
- *
269
- * - **data**: The complete content data object from the target version
270
- * - **slug**: The URL-friendly slug from the target version
271
- *
272
- * ## What Does NOT Get Restored
273
- *
274
- * - **status**: The current publish status is preserved (a rollback doesn't
275
- * unpublish or publish content automatically)
276
- * - **scheduledPublishAt**: Scheduling is not affected
277
- * - **Publishing timestamps**: firstPublishedAt/lastPublishedAt are preserved
278
- *
279
- * ## Use Cases
280
- *
281
- * - **Accidental Changes**: Undo unwanted edits by restoring to a known good state
282
- * - **Content Review**: Compare and restore to previously approved versions
283
- * - **A/B Testing**: Switch between content variants by rolling back
284
- * - **Emergency Fixes**: Quickly revert problematic changes in production
285
- *
286
- * @param entryId - The ID of the content entry to roll back
287
- * @param versionNumber - The version number to restore to
288
- * @param updatedBy - Optional user ID for audit trail
289
- *
290
- * @returns The updated content entry with restored content
291
- *
292
- * @throws Error if the entry does not exist
293
- * @throws Error if the entry has been soft-deleted
294
- * @throws Error if the target version does not exist
295
- * @throws Error if the target version doesn't belong to this entry
296
- *
297
- * @example
298
- * ```typescript
299
- * // Restore entry to version 3
300
- * const restored = await ctx.runMutation(api.versionMutations.rollbackVersion, {
301
- * entryId: myEntryId,
302
- * versionNumber: 3,
303
- * updatedBy: currentUserId,
304
- * });
305
- *
306
- * console.log(`Rolled back to version 3, now at version ${restored.version}`);
307
- * // Note: The entry is now at a new version number (e.g., 7), not version 3
308
- * // The content matches what was in version 3
309
- * ```
310
- */
311
- export const rollbackVersion = mutation({
312
- args: rollbackVersionArgs.fields,
313
- returns: contentEntryDoc,
314
- handler: async (ctx, args) => {
315
- const { entryId, versionNumber, updatedBy } = args;
316
-
317
- // Step 1: Validate the entry exists and is not deleted
318
- const entry = await ctx.db.get(entryId);
319
-
320
- if (!entry) {
321
- throw versionEntryNotFound((entryId as unknown) as string);
322
- }
323
-
324
- if (isDeleted(entry)) {
325
- throw versionEntryDeleted((entryId as unknown) as string);
326
- }
327
-
328
- // Step 2: Retrieve the target version to restore
329
- const targetVersion = await ctx.db
330
- .query("contentVersions")
331
- .withIndex("by_entry_and_version", (q) =>
332
- q.eq("entryId", entryId).eq("versionNumber", versionNumber),
333
- )
334
- .first();
335
-
336
- if (!targetVersion) {
337
- throw versionNotFound((entryId as unknown) as string, versionNumber);
338
- }
339
-
340
- // Security: Verify the version belongs to this entry (defensive check)
341
- if (targetVersion.entryId !== entryId) {
342
- throw versionMismatch(
343
- (entryId as unknown) as string,
344
- (targetVersion._id as unknown) as string,
345
- );
346
- }
347
-
348
- // Step 3: Snapshot the current state before rollback (for undo capability)
349
- // This allows users to "undo" a rollback by rolling back to this snapshot
350
- await ctx.db.insert("contentVersions", {
351
- entryId,
352
- versionNumber: entry.version,
353
- data: entry.data,
354
- slug: entry.slug,
355
- status: entry.status,
356
- changeDescription: `Pre-rollback snapshot (before restoring to version ${versionNumber})`,
357
- createdBy: updatedBy,
358
- wasPublished: false,
359
- });
360
-
361
- // Step 4: Update the entry with restored content
362
- // Note: We restore data and slug, but preserve the current status
363
- const newVersionNumber = entry.version + 1;
364
-
365
- await ctx.db.patch(entryId, {
366
- data: targetVersion.data,
367
- slug: targetVersion.slug,
368
- version: newVersionNumber,
369
- updatedBy,
370
- });
371
-
372
- // Step 5: Create a snapshot documenting the rollback
373
- await ctx.db.insert("contentVersions", {
374
- entryId,
375
- versionNumber: newVersionNumber,
376
- data: targetVersion.data,
377
- slug: targetVersion.slug,
378
- status: entry.status, // Preserve current status
379
- changeDescription: `Rolled back to version ${versionNumber}`,
380
- createdBy: updatedBy,
381
- wasPublished: false,
382
- });
383
-
384
- const updatedEntry = await ctx.db.get(entryId);
385
-
386
- if (!updatedEntry) {
387
- throw versionRollbackFailed((entryId as unknown) as string);
388
- }
389
-
390
- return updatedEntry;
391
- },
392
- });