convex-cms 0.0.2 → 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 (265) hide show
  1. package/admin-dist/nitro.json +15 -0
  2. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  3. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  4. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  5. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  6. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  7. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  8. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  9. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  10. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  11. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  12. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  13. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  14. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  15. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  16. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  17. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  18. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  19. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  20. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  21. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  22. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  23. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  24. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  25. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  26. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  27. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  28. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  29. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  30. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  31. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  32. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  33. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  34. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  35. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  36. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  37. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  38. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  39. package/admin-dist/public/favicon.ico +0 -0
  40. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  41. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  42. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  45. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  46. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  83. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  84. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  88. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  89. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  90. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  91. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  92. package/admin-dist/server/_libs/clsx.mjs +16 -0
  93. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  94. package/admin-dist/server/_libs/convex.mjs +4841 -0
  95. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  96. package/admin-dist/server/_libs/croner.mjs +1 -0
  97. package/admin-dist/server/_libs/crossws.mjs +1 -0
  98. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  99. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  100. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  101. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  102. package/admin-dist/server/_libs/h3.mjs +401 -0
  103. package/admin-dist/server/_libs/hookable.mjs +1 -0
  104. package/admin-dist/server/_libs/isbot.mjs +20 -0
  105. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  106. package/admin-dist/server/_libs/ohash.mjs +1 -0
  107. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  108. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  109. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  110. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  111. package/admin-dist/server/_libs/rou3.mjs +8 -0
  112. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  113. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  114. package/admin-dist/server/_libs/srvx.mjs +719 -0
  115. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  116. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  117. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  118. package/admin-dist/server/_libs/tslib.mjs +39 -0
  119. package/admin-dist/server/_libs/ufo.mjs +54 -0
  120. package/admin-dist/server/_libs/unctx.mjs +1 -0
  121. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  122. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  123. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  124. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  125. package/admin-dist/server/_libs/zod.mjs +4223 -0
  126. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  127. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  128. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  129. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  130. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  131. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  132. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  133. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  134. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  135. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  136. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  137. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  138. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  139. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  140. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  141. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  142. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  143. package/admin-dist/server/_ssr/index.mjs +1275 -0
  144. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  145. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  146. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  147. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  148. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  149. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  150. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  151. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  152. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  153. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  154. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  155. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  156. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  157. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  158. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  159. package/admin-dist/server/favicon.ico +0 -0
  160. package/admin-dist/server/index.mjs +627 -0
  161. package/dist/cli/index.js +0 -0
  162. package/dist/client/admin-config.d.ts +0 -1
  163. package/dist/client/admin-config.d.ts.map +1 -1
  164. package/dist/client/admin-config.js +0 -1
  165. package/dist/client/admin-config.js.map +1 -1
  166. package/dist/client/adminApi.d.ts.map +1 -1
  167. package/dist/client/agentTools.d.ts +1237 -135
  168. package/dist/client/agentTools.d.ts.map +1 -1
  169. package/dist/client/agentTools.js +33 -9
  170. package/dist/client/agentTools.js.map +1 -1
  171. package/dist/client/index.d.ts +1 -1
  172. package/dist/client/index.d.ts.map +1 -1
  173. package/dist/client/index.js.map +1 -1
  174. package/dist/component/_generated/component.d.ts +9 -0
  175. package/dist/component/_generated/component.d.ts.map +1 -1
  176. package/dist/component/mediaAssets.d.ts +35 -0
  177. package/dist/component/mediaAssets.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.js +81 -0
  179. package/dist/component/mediaAssets.js.map +1 -1
  180. package/dist/test.d.ts.map +1 -1
  181. package/dist/test.js +2 -1
  182. package/dist/test.js.map +1 -1
  183. package/package.json +9 -5
  184. package/dist/component/auditLog.d.ts +0 -410
  185. package/dist/component/auditLog.d.ts.map +0 -1
  186. package/dist/component/auditLog.js +0 -607
  187. package/dist/component/auditLog.js.map +0 -1
  188. package/dist/component/types.d.ts +0 -4
  189. package/dist/component/types.d.ts.map +0 -1
  190. package/dist/component/types.js +0 -2
  191. package/dist/component/types.js.map +0 -1
  192. package/src/cli/commands/admin.ts +0 -104
  193. package/src/cli/index.ts +0 -21
  194. package/src/cli/utils/detectConvexUrl.ts +0 -54
  195. package/src/cli/utils/openBrowser.ts +0 -16
  196. package/src/client/admin-config.ts +0 -138
  197. package/src/client/adminApi.ts +0 -942
  198. package/src/client/agentTools.ts +0 -1311
  199. package/src/client/argTypes.ts +0 -316
  200. package/src/client/field-types.ts +0 -187
  201. package/src/client/index.ts +0 -1301
  202. package/src/client/queryBuilder.ts +0 -1100
  203. package/src/client/schema/codegen.ts +0 -500
  204. package/src/client/schema/defineContentType.ts +0 -501
  205. package/src/client/schema/index.ts +0 -169
  206. package/src/client/schema/schemaDrift.ts +0 -574
  207. package/src/client/schema/typedClient.ts +0 -688
  208. package/src/client/schema/types.ts +0 -666
  209. package/src/client/types.ts +0 -723
  210. package/src/client/workflows.ts +0 -141
  211. package/src/client/wrapper.ts +0 -4304
  212. package/src/component/_generated/api.ts +0 -140
  213. package/src/component/_generated/component.ts +0 -5029
  214. package/src/component/_generated/dataModel.ts +0 -60
  215. package/src/component/_generated/server.ts +0 -156
  216. package/src/component/authorization.ts +0 -647
  217. package/src/component/authorizationHooks.ts +0 -668
  218. package/src/component/bulkOperations.ts +0 -687
  219. package/src/component/contentEntries.ts +0 -1976
  220. package/src/component/contentEntryMutations.ts +0 -1223
  221. package/src/component/contentEntryValidation.ts +0 -707
  222. package/src/component/contentLock.ts +0 -550
  223. package/src/component/contentTypeMigration.ts +0 -1064
  224. package/src/component/contentTypeMutations.ts +0 -969
  225. package/src/component/contentTypes.ts +0 -346
  226. package/src/component/convex.config.ts +0 -44
  227. package/src/component/documentTypes.ts +0 -240
  228. package/src/component/eventEmitter.ts +0 -485
  229. package/src/component/exportImport.ts +0 -1169
  230. package/src/component/index.ts +0 -491
  231. package/src/component/lib/deepReferenceResolver.ts +0 -999
  232. package/src/component/lib/errors.ts +0 -816
  233. package/src/component/lib/index.ts +0 -145
  234. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  235. package/src/component/lib/metadataExtractor.ts +0 -792
  236. package/src/component/lib/mutationAuth.ts +0 -199
  237. package/src/component/lib/queries.ts +0 -79
  238. package/src/component/lib/ragContentChunker.ts +0 -1371
  239. package/src/component/lib/referenceResolver.ts +0 -430
  240. package/src/component/lib/slugGenerator.ts +0 -262
  241. package/src/component/lib/slugUniqueness.ts +0 -333
  242. package/src/component/lib/softDelete.ts +0 -44
  243. package/src/component/localeFallbackChain.ts +0 -673
  244. package/src/component/localeFields.ts +0 -896
  245. package/src/component/mediaAssetMutations.ts +0 -725
  246. package/src/component/mediaAssets.ts +0 -932
  247. package/src/component/mediaFolderMutations.ts +0 -1046
  248. package/src/component/mediaUploadMutations.ts +0 -224
  249. package/src/component/mediaVariantMutations.ts +0 -900
  250. package/src/component/mediaVariants.ts +0 -793
  251. package/src/component/ragContentIndexer.ts +0 -1067
  252. package/src/component/rateLimitHooks.ts +0 -572
  253. package/src/component/roles.ts +0 -1360
  254. package/src/component/scheduledPublish.ts +0 -358
  255. package/src/component/schema.ts +0 -617
  256. package/src/component/taxonomies.ts +0 -949
  257. package/src/component/taxonomyMutations.ts +0 -1210
  258. package/src/component/trash.ts +0 -724
  259. package/src/component/userContext.ts +0 -898
  260. package/src/component/validation.ts +0 -1388
  261. package/src/component/validators.ts +0 -949
  262. package/src/component/versionMutations.ts +0 -392
  263. package/src/component/webhookTrigger.ts +0 -1922
  264. package/src/react/index.ts +0 -898
  265. package/src/test.ts +0 -1580
@@ -1,707 +0,0 @@
1
- /**
2
- * Content Entry Validation Internal Functions
3
- *
4
- * Provides internal functions to validate content entry data against its
5
- * content type schema. Used by mutations before creating/updating entries,
6
- * and can be called directly for preview validation.
7
- *
8
- * Validation includes:
9
- * - Required field checks
10
- * - Type correctness validation
11
- * - Custom field constraints (min/max, pattern, etc.)
12
- * - Reference validation (existence and content type constraints)
13
- * - Media reference validation
14
- */
15
-
16
- import { v } from "convex/values";
17
- import { isDeleted } from "./lib/softDelete.js";
18
- import { internalQuery, query } from "./_generated/server.js";
19
- import { Id } from "./_generated/dataModel.js";
20
- import {
21
- validateContentData,
22
- ContentData,
23
- ContentTypeSchema,
24
- FieldDefinition,
25
- ValidationError,
26
- } from "./validation.js";
27
-
28
- // =============================================================================
29
- // Types
30
- // =============================================================================
31
-
32
- /**
33
- * Extended validation result that includes reference validation errors
34
- */
35
- export type ContentEntryValidationResult = {
36
- /** Whether the validation passed */
37
- valid: boolean;
38
- /** Array of validation errors (empty if valid) */
39
- errors: ValidationError[];
40
- /** Content type name (if found) */
41
- contentTypeName?: string;
42
- /** Content type display name (if found) */
43
- contentTypeDisplayName?: string;
44
- /** Whether reference validation was performed */
45
- referencesValidated: boolean;
46
- };
47
-
48
- /**
49
- * Options for content entry validation
50
- */
51
- export interface ValidateContentEntryOptions {
52
- /**
53
- * If true, validates that referenced entries exist and belong to allowed content types.
54
- * This requires additional database queries but provides complete validation.
55
- * Default: true
56
- */
57
- validateReferences?: boolean;
58
-
59
- /**
60
- * If true, reports unknown fields (fields not defined in the content type) as errors.
61
- * Default: false
62
- */
63
- strictFields?: boolean;
64
- }
65
-
66
- // =============================================================================
67
- // Validator Definitions
68
- // =============================================================================
69
-
70
- /**
71
- * Argument validator for validateContentEntry
72
- */
73
- const validateContentEntryArgs = v.object({
74
- /** The content type ID to validate against */
75
- contentTypeId: v.id("contentTypes"),
76
- /** The content data to validate */
77
- data: v.any(),
78
- /** Validation options */
79
- options: v.optional(
80
- v.object({
81
- validateReferences: v.optional(v.boolean()),
82
- strictFields: v.optional(v.boolean()),
83
- }),
84
- ),
85
- });
86
-
87
- /**
88
- * Argument validator for validateContentEntryByTypeName
89
- */
90
- const validateContentEntryByTypeNameArgs = v.object({
91
- /** The content type name to validate against */
92
- contentTypeName: v.string(),
93
- /** The content data to validate */
94
- data: v.any(),
95
- /** Validation options */
96
- options: v.optional(
97
- v.object({
98
- validateReferences: v.optional(v.boolean()),
99
- strictFields: v.optional(v.boolean()),
100
- }),
101
- ),
102
- });
103
-
104
- /**
105
- * Return validator for validation results
106
- */
107
- const validationResultValidator = v.object({
108
- valid: v.boolean(),
109
- errors: v.array(
110
- v.object({
111
- field: v.string(),
112
- message: v.string(),
113
- code: v.string(),
114
- }),
115
- ),
116
- contentTypeName: v.optional(v.string()),
117
- contentTypeDisplayName: v.optional(v.string()),
118
- referencesValidated: v.boolean(),
119
- });
120
-
121
- // =============================================================================
122
- // Internal Query: Validate Content Entry
123
- // =============================================================================
124
-
125
- /**
126
- * Internal query to validate content entry data against its content type schema.
127
- *
128
- * This function performs comprehensive validation including:
129
- * 1. Required field checks - ensures all required fields have values
130
- * 2. Type validation - verifies values match expected types (string, number, etc.)
131
- * 3. Constraint validation - checks min/max length, patterns, allowed values, etc.
132
- * 4. Reference validation - optionally verifies referenced entries exist and have correct types
133
- *
134
- * @param contentTypeId - The ID of the content type to validate against
135
- * @param data - The content data to validate
136
- * @param options - Optional validation options
137
- *
138
- * @returns ValidationResult with any errors found
139
- *
140
- * @example
141
- * ```typescript
142
- * // Validate blog post data before creating
143
- * const result = await ctx.runQuery(internal.contentEntryValidation.validateContentEntry, {
144
- * contentTypeId: blogTypeId,
145
- * data: {
146
- * title: "My Post",
147
- * content: "<p>Hello world!</p>",
148
- * author: authorEntryId,
149
- * },
150
- * });
151
- *
152
- * if (!result.valid) {
153
- * console.error("Validation errors:", result.errors);
154
- * }
155
- * ```
156
- */
157
- export const validateContentEntry = internalQuery({
158
- args: validateContentEntryArgs.fields,
159
- returns: validationResultValidator,
160
- handler: async (ctx, args): Promise<ContentEntryValidationResult> => {
161
- const { contentTypeId, data, options } = args;
162
- const validateReferences = options?.validateReferences ?? true;
163
- const strictFields = options?.strictFields ?? false;
164
-
165
- // Fetch the content type
166
- const contentType = await ctx.db.get(contentTypeId);
167
- if (!contentType) {
168
- return {
169
- valid: false,
170
- errors: [
171
- {
172
- field: "_contentType",
173
- message: `Content type not found: ${contentTypeId}`,
174
- code: "INVALID_CONTENT_TYPE",
175
- },
176
- ],
177
- referencesValidated: false,
178
- };
179
- }
180
-
181
- if (isDeleted(contentType)) {
182
- return {
183
- valid: false,
184
- errors: [
185
- {
186
- field: "_contentType",
187
- message: `Content type has been deleted: ${contentType.name}`,
188
- code: "INVALID_CONTENT_TYPE",
189
- },
190
- ],
191
- contentTypeName: contentType.name,
192
- contentTypeDisplayName: contentType.displayName,
193
- referencesValidated: false,
194
- };
195
- }
196
-
197
- if (!contentType.isActive) {
198
- return {
199
- valid: false,
200
- errors: [
201
- {
202
- field: "_contentType",
203
- message: `Content type is not active: ${contentType.name}`,
204
- code: "INVALID_CONTENT_TYPE",
205
- },
206
- ],
207
- contentTypeName: contentType.name,
208
- contentTypeDisplayName: contentType.displayName,
209
- referencesValidated: false,
210
- };
211
- }
212
-
213
- // Build the schema for validation
214
- const schema: ContentTypeSchema = {
215
- name: contentType.name,
216
- displayName: contentType.displayName,
217
- description: contentType.description,
218
- fields: contentType.fields as FieldDefinition[],
219
- titleField: contentType.titleField,
220
- slugField: contentType.slugField,
221
- singleton: contentType.singleton,
222
- };
223
-
224
- // Perform basic validation
225
- const contentData = data as ContentData;
226
- const basicResult = validateContentData(contentData, schema, {
227
- strictFields,
228
- });
229
-
230
- // Collect all errors
231
- const errors: ValidationError[] = [...basicResult.errors];
232
-
233
- // Perform reference validation if enabled
234
- let referencesValidated = false;
235
- if (validateReferences) {
236
- const referenceErrors = await validateReferences_internal(
237
- ctx,
238
- contentData,
239
- schema.fields,
240
- );
241
- errors.push(...referenceErrors);
242
- referencesValidated = true;
243
- }
244
-
245
- return {
246
- valid: errors.length === 0,
247
- errors,
248
- contentTypeName: contentType.name,
249
- contentTypeDisplayName: contentType.displayName,
250
- referencesValidated,
251
- };
252
- },
253
- });
254
-
255
- /**
256
- * Public query to validate content entry data.
257
- *
258
- * Same as the internal version but exposed as a public query for use
259
- * from the client (e.g., for form validation before submission).
260
- */
261
- export const validateEntry = query({
262
- args: validateContentEntryArgs.fields,
263
- returns: validationResultValidator,
264
- handler: async (ctx, args): Promise<ContentEntryValidationResult> => {
265
- const { contentTypeId, data, options } = args;
266
- const validateReferencesOption = options?.validateReferences ?? true;
267
- const strictFields = options?.strictFields ?? false;
268
-
269
- // Fetch the content type
270
- const contentType = await ctx.db.get(contentTypeId);
271
- if (!contentType) {
272
- return {
273
- valid: false,
274
- errors: [
275
- {
276
- field: "_contentType",
277
- message: `Content type not found: ${contentTypeId}`,
278
- code: "INVALID_CONTENT_TYPE",
279
- },
280
- ],
281
- referencesValidated: false,
282
- };
283
- }
284
-
285
- if (isDeleted(contentType)) {
286
- return {
287
- valid: false,
288
- errors: [
289
- {
290
- field: "_contentType",
291
- message: `Content type has been deleted: ${contentType.name}`,
292
- code: "INVALID_CONTENT_TYPE",
293
- },
294
- ],
295
- contentTypeName: contentType.name,
296
- contentTypeDisplayName: contentType.displayName,
297
- referencesValidated: false,
298
- };
299
- }
300
-
301
- if (!contentType.isActive) {
302
- return {
303
- valid: false,
304
- errors: [
305
- {
306
- field: "_contentType",
307
- message: `Content type is not active: ${contentType.name}`,
308
- code: "INVALID_CONTENT_TYPE",
309
- },
310
- ],
311
- contentTypeName: contentType.name,
312
- contentTypeDisplayName: contentType.displayName,
313
- referencesValidated: false,
314
- };
315
- }
316
-
317
- // Build the schema for validation
318
- const schema: ContentTypeSchema = {
319
- name: contentType.name,
320
- displayName: contentType.displayName,
321
- description: contentType.description,
322
- fields: contentType.fields as FieldDefinition[],
323
- titleField: contentType.titleField,
324
- slugField: contentType.slugField,
325
- singleton: contentType.singleton,
326
- };
327
-
328
- // Perform basic validation
329
- const contentData = data as ContentData;
330
- const basicResult = validateContentData(contentData, schema, {
331
- strictFields,
332
- });
333
-
334
- // Collect all errors
335
- const errors: ValidationError[] = [...basicResult.errors];
336
-
337
- // Perform reference validation if enabled
338
- let referencesValidated = false;
339
- if (validateReferencesOption) {
340
- const referenceErrors = await validateReferences_internal(
341
- ctx,
342
- contentData,
343
- schema.fields,
344
- );
345
- errors.push(...referenceErrors);
346
- referencesValidated = true;
347
- }
348
-
349
- return {
350
- valid: errors.length === 0,
351
- errors,
352
- contentTypeName: contentType.name,
353
- contentTypeDisplayName: contentType.displayName,
354
- referencesValidated,
355
- };
356
- },
357
- });
358
-
359
- /**
360
- * Internal query to validate content entry data by content type name.
361
- *
362
- * Same as validateContentEntry but accepts content type name instead of ID.
363
- * Useful when you know the type name but not the ID.
364
- */
365
- export const validateContentEntryByTypeName = internalQuery({
366
- args: validateContentEntryByTypeNameArgs.fields,
367
- returns: validationResultValidator,
368
- handler: async (ctx, args): Promise<ContentEntryValidationResult> => {
369
- const { contentTypeName, data, options } = args;
370
-
371
- // Find the content type by name
372
- const contentType = await ctx.db
373
- .query("contentTypes")
374
- .withIndex("by_name", (q) => q.eq("name", contentTypeName))
375
- .first();
376
-
377
- if (!contentType) {
378
- return {
379
- valid: false,
380
- errors: [
381
- {
382
- field: "_contentType",
383
- message: `Content type not found: ${contentTypeName}`,
384
- code: "INVALID_CONTENT_TYPE",
385
- },
386
- ],
387
- referencesValidated: false,
388
- };
389
- }
390
-
391
- // Delegate to the ID-based validation
392
- const validateReferencesOption = options?.validateReferences ?? true;
393
- const strictFields = options?.strictFields ?? false;
394
-
395
- if (isDeleted(contentType)) {
396
- return {
397
- valid: false,
398
- errors: [
399
- {
400
- field: "_contentType",
401
- message: `Content type has been deleted: ${contentType.name}`,
402
- code: "INVALID_CONTENT_TYPE",
403
- },
404
- ],
405
- contentTypeName: contentType.name,
406
- contentTypeDisplayName: contentType.displayName,
407
- referencesValidated: false,
408
- };
409
- }
410
-
411
- if (!contentType.isActive) {
412
- return {
413
- valid: false,
414
- errors: [
415
- {
416
- field: "_contentType",
417
- message: `Content type is not active: ${contentType.name}`,
418
- code: "INVALID_CONTENT_TYPE",
419
- },
420
- ],
421
- contentTypeName: contentType.name,
422
- contentTypeDisplayName: contentType.displayName,
423
- referencesValidated: false,
424
- };
425
- }
426
-
427
- // Build the schema for validation
428
- const schema: ContentTypeSchema = {
429
- name: contentType.name,
430
- displayName: contentType.displayName,
431
- description: contentType.description,
432
- fields: contentType.fields as FieldDefinition[],
433
- titleField: contentType.titleField,
434
- slugField: contentType.slugField,
435
- singleton: contentType.singleton,
436
- };
437
-
438
- // Perform basic validation
439
- const contentData = data as ContentData;
440
- const basicResult = validateContentData(contentData, schema, {
441
- strictFields,
442
- });
443
-
444
- // Collect all errors
445
- const errors: ValidationError[] = [...basicResult.errors];
446
-
447
- // Perform reference validation if enabled
448
- let referencesValidated = false;
449
- if (validateReferencesOption) {
450
- const referenceErrors = await validateReferences_internal(
451
- ctx,
452
- contentData,
453
- schema.fields,
454
- );
455
- errors.push(...referenceErrors);
456
- referencesValidated = true;
457
- }
458
-
459
- return {
460
- valid: errors.length === 0,
461
- errors,
462
- contentTypeName: contentType.name,
463
- contentTypeDisplayName: contentType.displayName,
464
- referencesValidated,
465
- };
466
- },
467
- });
468
-
469
- // =============================================================================
470
- // Helper Functions
471
- // =============================================================================
472
-
473
- /**
474
- * Validates reference and media fields by checking that referenced entries exist
475
- * and belong to allowed content types.
476
- *
477
- * @param ctx - The query context for database access
478
- * @param data - The content data being validated
479
- * @param fields - The field definitions from the content type
480
- * @returns Array of validation errors for invalid references
481
- */
482
- async function validateReferences_internal(
483
- ctx: { db: { get: (id: Id<any>) => Promise<any> } },
484
- data: ContentData,
485
- fields: FieldDefinition[],
486
- ): Promise<ValidationError[]> {
487
- const errors: ValidationError[] = [];
488
-
489
- for (const field of fields) {
490
- const value = data[field.name];
491
-
492
- // Skip if no value
493
- if (value === null || value === undefined) {
494
- continue;
495
- }
496
-
497
- if (field.type === "reference") {
498
- const multiple = field.options?.multiple ?? false;
499
- const allowedContentTypes = field.options?.allowedContentTypes;
500
-
501
- if (multiple && Array.isArray(value)) {
502
- // Multiple references
503
- for (let i = 0; i < value.length; i++) {
504
- const refId = value[i];
505
- if (typeof refId === "string") {
506
- const refErrors = await validateSingleReference(
507
- ctx,
508
- refId,
509
- field.name,
510
- allowedContentTypes,
511
- i,
512
- );
513
- errors.push(...refErrors);
514
- }
515
- }
516
- } else if (!multiple && typeof value === "string") {
517
- // Single reference
518
- const refErrors = await validateSingleReference(
519
- ctx,
520
- value,
521
- field.name,
522
- allowedContentTypes,
523
- );
524
- errors.push(...refErrors);
525
- }
526
- }
527
-
528
- if (field.type === "media") {
529
- const multiple = field.options?.multiple ?? false;
530
-
531
- if (multiple && Array.isArray(value)) {
532
- // Multiple media assets
533
- for (let i = 0; i < value.length; i++) {
534
- const assetId = value[i];
535
- if (typeof assetId === "string") {
536
- const mediaErrors = await validateSingleMediaAsset(
537
- ctx,
538
- assetId,
539
- field.name,
540
- field.options?.allowedMimeTypes,
541
- field.options?.maxFileSize,
542
- i,
543
- );
544
- errors.push(...mediaErrors);
545
- }
546
- }
547
- } else if (!multiple && typeof value === "string") {
548
- // Single media asset
549
- const mediaErrors = await validateSingleMediaAsset(
550
- ctx,
551
- value,
552
- field.name,
553
- field.options?.allowedMimeTypes,
554
- field.options?.maxFileSize,
555
- );
556
- errors.push(...mediaErrors);
557
- }
558
- }
559
- }
560
-
561
- return errors;
562
- }
563
-
564
- /**
565
- * Validates a single reference to a content entry.
566
- */
567
- async function validateSingleReference(
568
- ctx: { db: { get: (id: Id<any>) => Promise<any> } },
569
- referenceId: string,
570
- fieldName: string,
571
- allowedContentTypes?: string[],
572
- index?: number,
573
- ): Promise<ValidationError[]> {
574
- const errors: ValidationError[] = [];
575
- const fieldLabel = index !== undefined ? `${fieldName}[${index}]` : fieldName;
576
-
577
- try {
578
- // Try to get the referenced entry
579
- const entry = await ctx.db.get(referenceId as Id<"contentEntries">);
580
-
581
- if (!entry) {
582
- errors.push({
583
- field: fieldLabel,
584
- message: `Referenced entry not found: ${referenceId}`,
585
- code: "INVALID_CONTENT_TYPE",
586
- });
587
- return errors;
588
- }
589
-
590
- // Check if entry is deleted
591
- if (isDeleted(entry)) {
592
- errors.push({
593
- field: fieldLabel,
594
- message: `Referenced entry has been deleted: ${referenceId}`,
595
- code: "INVALID_CONTENT_TYPE",
596
- });
597
- return errors;
598
- }
599
-
600
- // Validate content type constraint
601
- if (allowedContentTypes && allowedContentTypes.length > 0) {
602
- const contentType = await ctx.db.get(entry.contentTypeId);
603
- if (contentType) {
604
- if (!allowedContentTypes.includes(contentType.name)) {
605
- errors.push({
606
- field: fieldLabel,
607
- message: `Reference must be of type: ${allowedContentTypes.join(
608
- ", ",
609
- )}. Got: ${contentType.name}`,
610
- code: "INVALID_CONTENT_TYPE",
611
- });
612
- }
613
- }
614
- }
615
- } catch {
616
- // Invalid ID format
617
- errors.push({
618
- field: fieldLabel,
619
- message: `Invalid reference ID format: ${referenceId}`,
620
- code: "INVALID_TYPE",
621
- });
622
- }
623
-
624
- return errors;
625
- }
626
-
627
- /**
628
- * Validates a single media asset reference.
629
- */
630
- async function validateSingleMediaAsset(
631
- ctx: { db: { get: (id: Id<any>) => Promise<any> } },
632
- assetId: string,
633
- fieldName: string,
634
- allowedMimeTypes?: string[],
635
- maxFileSize?: number,
636
- index?: number,
637
- ): Promise<ValidationError[]> {
638
- const errors: ValidationError[] = [];
639
- const fieldLabel = index !== undefined ? `${fieldName}[${index}]` : fieldName;
640
-
641
- try {
642
- // Try to get the media item
643
- const item = await ctx.db.get(assetId as Id<"mediaItems">);
644
-
645
- if (!item) {
646
- errors.push({
647
- field: fieldLabel,
648
- message: `Media asset not found: ${assetId}`,
649
- code: "INVALID_TYPE",
650
- });
651
- return errors;
652
- }
653
-
654
- // Check if it's an asset (not a folder)
655
- if (item.kind !== "asset") {
656
- errors.push({
657
- field: fieldLabel,
658
- message: `Media reference must be an asset, not a folder: ${assetId}`,
659
- code: "INVALID_TYPE",
660
- });
661
- return errors;
662
- }
663
-
664
- // Check if asset is deleted
665
- if (isDeleted(item)) {
666
- errors.push({
667
- field: fieldLabel,
668
- message: `Media asset has been deleted: ${assetId}`,
669
- code: "INVALID_TYPE",
670
- });
671
- return errors;
672
- }
673
-
674
- // Validate MIME type constraint
675
- if (allowedMimeTypes && allowedMimeTypes.length > 0) {
676
- if (!allowedMimeTypes.includes(item.mimeType)) {
677
- errors.push({
678
- field: fieldLabel,
679
- message: `Media type not allowed. Expected: ${allowedMimeTypes.join(
680
- ", ",
681
- )}. Got: ${item.mimeType}`,
682
- code: "INVALID_MIME_TYPE",
683
- });
684
- }
685
- }
686
-
687
- // Validate file size constraint
688
- if (maxFileSize !== undefined && item.size > maxFileSize) {
689
- const maxSizeKB = Math.round(maxFileSize / 1024);
690
- const actualSizeKB = Math.round(item.size / 1024);
691
- errors.push({
692
- field: fieldLabel,
693
- message: `File too large. Maximum: ${maxSizeKB}KB. Actual: ${actualSizeKB}KB`,
694
- code: "FILE_TOO_LARGE",
695
- });
696
- }
697
- } catch {
698
- // Invalid ID format
699
- errors.push({
700
- field: fieldLabel,
701
- message: `Invalid media asset ID format: ${assetId}`,
702
- code: "INVALID_TYPE",
703
- });
704
- }
705
-
706
- return errors;
707
- }