convex-cms 0.0.1

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 (379) hide show
  1. package/dist/cli/commands/admin.d.ts +16 -0
  2. package/dist/cli/commands/admin.d.ts.map +1 -0
  3. package/dist/cli/commands/admin.js +88 -0
  4. package/dist/cli/commands/admin.js.map +1 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +18 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/utils/detectConvexUrl.d.ts +13 -0
  10. package/dist/cli/utils/detectConvexUrl.d.ts.map +1 -0
  11. package/dist/cli/utils/detectConvexUrl.js +48 -0
  12. package/dist/cli/utils/detectConvexUrl.js.map +1 -0
  13. package/dist/cli/utils/openBrowser.d.ts +7 -0
  14. package/dist/cli/utils/openBrowser.d.ts.map +1 -0
  15. package/dist/cli/utils/openBrowser.js +17 -0
  16. package/dist/cli/utils/openBrowser.js.map +1 -0
  17. package/dist/client/admin-config.d.ts +126 -0
  18. package/dist/client/admin-config.d.ts.map +1 -0
  19. package/dist/client/admin-config.js +117 -0
  20. package/dist/client/admin-config.js.map +1 -0
  21. package/dist/client/adminApi.d.ts +2273 -0
  22. package/dist/client/adminApi.d.ts.map +1 -0
  23. package/dist/client/adminApi.js +716 -0
  24. package/dist/client/adminApi.js.map +1 -0
  25. package/dist/client/agentTools.d.ts +933 -0
  26. package/dist/client/agentTools.d.ts.map +1 -0
  27. package/dist/client/agentTools.js +1004 -0
  28. package/dist/client/agentTools.js.map +1 -0
  29. package/dist/client/argTypes.d.ts +212 -0
  30. package/dist/client/argTypes.d.ts.map +1 -0
  31. package/dist/client/argTypes.js +5 -0
  32. package/dist/client/argTypes.js.map +1 -0
  33. package/dist/client/field-types.d.ts +55 -0
  34. package/dist/client/field-types.d.ts.map +1 -0
  35. package/dist/client/field-types.js +152 -0
  36. package/dist/client/field-types.js.map +1 -0
  37. package/dist/client/index.d.ts +189 -0
  38. package/dist/client/index.d.ts.map +1 -0
  39. package/dist/client/index.js +668 -0
  40. package/dist/client/index.js.map +1 -0
  41. package/dist/client/queryBuilder.d.ts +765 -0
  42. package/dist/client/queryBuilder.d.ts.map +1 -0
  43. package/dist/client/queryBuilder.js +970 -0
  44. package/dist/client/queryBuilder.js.map +1 -0
  45. package/dist/client/schema/codegen.d.ts +128 -0
  46. package/dist/client/schema/codegen.d.ts.map +1 -0
  47. package/dist/client/schema/codegen.js +318 -0
  48. package/dist/client/schema/codegen.js.map +1 -0
  49. package/dist/client/schema/defineContentType.d.ts +221 -0
  50. package/dist/client/schema/defineContentType.d.ts.map +1 -0
  51. package/dist/client/schema/defineContentType.js +380 -0
  52. package/dist/client/schema/defineContentType.js.map +1 -0
  53. package/dist/client/schema/index.d.ts +85 -0
  54. package/dist/client/schema/index.d.ts.map +1 -0
  55. package/dist/client/schema/index.js +92 -0
  56. package/dist/client/schema/index.js.map +1 -0
  57. package/dist/client/schema/schemaDrift.d.ts +199 -0
  58. package/dist/client/schema/schemaDrift.d.ts.map +1 -0
  59. package/dist/client/schema/schemaDrift.js +340 -0
  60. package/dist/client/schema/schemaDrift.js.map +1 -0
  61. package/dist/client/schema/typedClient.d.ts +401 -0
  62. package/dist/client/schema/typedClient.d.ts.map +1 -0
  63. package/dist/client/schema/typedClient.js +269 -0
  64. package/dist/client/schema/typedClient.js.map +1 -0
  65. package/dist/client/schema/types.d.ts +477 -0
  66. package/dist/client/schema/types.d.ts.map +1 -0
  67. package/dist/client/schema/types.js +39 -0
  68. package/dist/client/schema/types.js.map +1 -0
  69. package/dist/client/types.d.ts +449 -0
  70. package/dist/client/types.d.ts.map +1 -0
  71. package/dist/client/types.js +149 -0
  72. package/dist/client/types.js.map +1 -0
  73. package/dist/client/workflows.d.ts +51 -0
  74. package/dist/client/workflows.d.ts.map +1 -0
  75. package/dist/client/workflows.js +103 -0
  76. package/dist/client/workflows.js.map +1 -0
  77. package/dist/client/wrapper.d.ts +2198 -0
  78. package/dist/client/wrapper.d.ts.map +1 -0
  79. package/dist/client/wrapper.js +2651 -0
  80. package/dist/client/wrapper.js.map +1 -0
  81. package/dist/component/_generated/api.d.ts +124 -0
  82. package/dist/component/_generated/api.d.ts.map +1 -0
  83. package/dist/component/_generated/api.js +31 -0
  84. package/dist/component/_generated/api.js.map +1 -0
  85. package/dist/component/_generated/component.d.ts +4321 -0
  86. package/dist/component/_generated/component.d.ts.map +1 -0
  87. package/dist/component/_generated/component.js +11 -0
  88. package/dist/component/_generated/component.js.map +1 -0
  89. package/dist/component/_generated/dataModel.d.ts +46 -0
  90. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  91. package/dist/component/_generated/dataModel.js +11 -0
  92. package/dist/component/_generated/dataModel.js.map +1 -0
  93. package/dist/component/_generated/server.d.ts +121 -0
  94. package/dist/component/_generated/server.d.ts.map +1 -0
  95. package/dist/component/_generated/server.js +78 -0
  96. package/dist/component/_generated/server.js.map +1 -0
  97. package/dist/component/auditLog.d.ts +410 -0
  98. package/dist/component/auditLog.d.ts.map +1 -0
  99. package/dist/component/auditLog.js +607 -0
  100. package/dist/component/auditLog.js.map +1 -0
  101. package/dist/component/authorization.d.ts +323 -0
  102. package/dist/component/authorization.d.ts.map +1 -0
  103. package/dist/component/authorization.js +464 -0
  104. package/dist/component/authorization.js.map +1 -0
  105. package/dist/component/authorizationHooks.d.ts +184 -0
  106. package/dist/component/authorizationHooks.d.ts.map +1 -0
  107. package/dist/component/authorizationHooks.js +521 -0
  108. package/dist/component/authorizationHooks.js.map +1 -0
  109. package/dist/component/bulkOperations.d.ts +200 -0
  110. package/dist/component/bulkOperations.d.ts.map +1 -0
  111. package/dist/component/bulkOperations.js +568 -0
  112. package/dist/component/bulkOperations.js.map +1 -0
  113. package/dist/component/contentEntries.d.ts +719 -0
  114. package/dist/component/contentEntries.d.ts.map +1 -0
  115. package/dist/component/contentEntries.js +1617 -0
  116. package/dist/component/contentEntries.js.map +1 -0
  117. package/dist/component/contentEntryMutations.d.ts +505 -0
  118. package/dist/component/contentEntryMutations.d.ts.map +1 -0
  119. package/dist/component/contentEntryMutations.js +1009 -0
  120. package/dist/component/contentEntryMutations.js.map +1 -0
  121. package/dist/component/contentEntryValidation.d.ts +115 -0
  122. package/dist/component/contentEntryValidation.d.ts.map +1 -0
  123. package/dist/component/contentEntryValidation.js +546 -0
  124. package/dist/component/contentEntryValidation.js.map +1 -0
  125. package/dist/component/contentLock.d.ts +328 -0
  126. package/dist/component/contentLock.d.ts.map +1 -0
  127. package/dist/component/contentLock.js +471 -0
  128. package/dist/component/contentLock.js.map +1 -0
  129. package/dist/component/contentTypeMigration.d.ts +411 -0
  130. package/dist/component/contentTypeMigration.d.ts.map +1 -0
  131. package/dist/component/contentTypeMigration.js +805 -0
  132. package/dist/component/contentTypeMigration.js.map +1 -0
  133. package/dist/component/contentTypeMutations.d.ts +975 -0
  134. package/dist/component/contentTypeMutations.d.ts.map +1 -0
  135. package/dist/component/contentTypeMutations.js +768 -0
  136. package/dist/component/contentTypeMutations.js.map +1 -0
  137. package/dist/component/contentTypes.d.ts +538 -0
  138. package/dist/component/contentTypes.d.ts.map +1 -0
  139. package/dist/component/contentTypes.js +304 -0
  140. package/dist/component/contentTypes.js.map +1 -0
  141. package/dist/component/convex.config.d.ts +42 -0
  142. package/dist/component/convex.config.d.ts.map +1 -0
  143. package/dist/component/convex.config.js +43 -0
  144. package/dist/component/convex.config.js.map +1 -0
  145. package/dist/component/documentTypes.d.ts +186 -0
  146. package/dist/component/documentTypes.d.ts.map +1 -0
  147. package/dist/component/documentTypes.js +23 -0
  148. package/dist/component/documentTypes.js.map +1 -0
  149. package/dist/component/eventEmitter.d.ts +281 -0
  150. package/dist/component/eventEmitter.d.ts.map +1 -0
  151. package/dist/component/eventEmitter.js +300 -0
  152. package/dist/component/eventEmitter.js.map +1 -0
  153. package/dist/component/exportImport.d.ts +1120 -0
  154. package/dist/component/exportImport.d.ts.map +1 -0
  155. package/dist/component/exportImport.js +931 -0
  156. package/dist/component/exportImport.js.map +1 -0
  157. package/dist/component/index.d.ts +28 -0
  158. package/dist/component/index.d.ts.map +1 -0
  159. package/dist/component/index.js +142 -0
  160. package/dist/component/index.js.map +1 -0
  161. package/dist/component/lib/deepReferenceResolver.d.ts +252 -0
  162. package/dist/component/lib/deepReferenceResolver.d.ts.map +1 -0
  163. package/dist/component/lib/deepReferenceResolver.js +601 -0
  164. package/dist/component/lib/deepReferenceResolver.js.map +1 -0
  165. package/dist/component/lib/errors.d.ts +306 -0
  166. package/dist/component/lib/errors.d.ts.map +1 -0
  167. package/dist/component/lib/errors.js +407 -0
  168. package/dist/component/lib/errors.js.map +1 -0
  169. package/dist/component/lib/index.d.ts +10 -0
  170. package/dist/component/lib/index.d.ts.map +1 -0
  171. package/dist/component/lib/index.js +33 -0
  172. package/dist/component/lib/index.js.map +1 -0
  173. package/dist/component/lib/mediaReferenceResolver.d.ts +217 -0
  174. package/dist/component/lib/mediaReferenceResolver.d.ts.map +1 -0
  175. package/dist/component/lib/mediaReferenceResolver.js +326 -0
  176. package/dist/component/lib/mediaReferenceResolver.js.map +1 -0
  177. package/dist/component/lib/metadataExtractor.d.ts +245 -0
  178. package/dist/component/lib/metadataExtractor.d.ts.map +1 -0
  179. package/dist/component/lib/metadataExtractor.js +548 -0
  180. package/dist/component/lib/metadataExtractor.js.map +1 -0
  181. package/dist/component/lib/mutationAuth.d.ts +95 -0
  182. package/dist/component/lib/mutationAuth.d.ts.map +1 -0
  183. package/dist/component/lib/mutationAuth.js +146 -0
  184. package/dist/component/lib/mutationAuth.js.map +1 -0
  185. package/dist/component/lib/queries.d.ts +17 -0
  186. package/dist/component/lib/queries.d.ts.map +1 -0
  187. package/dist/component/lib/queries.js +49 -0
  188. package/dist/component/lib/queries.js.map +1 -0
  189. package/dist/component/lib/ragContentChunker.d.ts +423 -0
  190. package/dist/component/lib/ragContentChunker.d.ts.map +1 -0
  191. package/dist/component/lib/ragContentChunker.js +897 -0
  192. package/dist/component/lib/ragContentChunker.js.map +1 -0
  193. package/dist/component/lib/referenceResolver.d.ts +175 -0
  194. package/dist/component/lib/referenceResolver.d.ts.map +1 -0
  195. package/dist/component/lib/referenceResolver.js +293 -0
  196. package/dist/component/lib/referenceResolver.js.map +1 -0
  197. package/dist/component/lib/slugGenerator.d.ts +71 -0
  198. package/dist/component/lib/slugGenerator.d.ts.map +1 -0
  199. package/dist/component/lib/slugGenerator.js +207 -0
  200. package/dist/component/lib/slugGenerator.js.map +1 -0
  201. package/dist/component/lib/slugUniqueness.d.ts +131 -0
  202. package/dist/component/lib/slugUniqueness.d.ts.map +1 -0
  203. package/dist/component/lib/slugUniqueness.js +229 -0
  204. package/dist/component/lib/slugUniqueness.js.map +1 -0
  205. package/dist/component/lib/softDelete.d.ts +18 -0
  206. package/dist/component/lib/softDelete.d.ts.map +1 -0
  207. package/dist/component/lib/softDelete.js +29 -0
  208. package/dist/component/lib/softDelete.js.map +1 -0
  209. package/dist/component/localeFallbackChain.d.ts +410 -0
  210. package/dist/component/localeFallbackChain.d.ts.map +1 -0
  211. package/dist/component/localeFallbackChain.js +467 -0
  212. package/dist/component/localeFallbackChain.js.map +1 -0
  213. package/dist/component/localeFields.d.ts +508 -0
  214. package/dist/component/localeFields.d.ts.map +1 -0
  215. package/dist/component/localeFields.js +592 -0
  216. package/dist/component/localeFields.js.map +1 -0
  217. package/dist/component/mediaAssetMutations.d.ts +235 -0
  218. package/dist/component/mediaAssetMutations.d.ts.map +1 -0
  219. package/dist/component/mediaAssetMutations.js +558 -0
  220. package/dist/component/mediaAssetMutations.js.map +1 -0
  221. package/dist/component/mediaAssets.d.ts +168 -0
  222. package/dist/component/mediaAssets.d.ts.map +1 -0
  223. package/dist/component/mediaAssets.js +618 -0
  224. package/dist/component/mediaAssets.js.map +1 -0
  225. package/dist/component/mediaFolderMutations.d.ts +642 -0
  226. package/dist/component/mediaFolderMutations.d.ts.map +1 -0
  227. package/dist/component/mediaFolderMutations.js +849 -0
  228. package/dist/component/mediaFolderMutations.js.map +1 -0
  229. package/dist/component/mediaUploadMutations.d.ts +136 -0
  230. package/dist/component/mediaUploadMutations.d.ts.map +1 -0
  231. package/dist/component/mediaUploadMutations.js +205 -0
  232. package/dist/component/mediaUploadMutations.js.map +1 -0
  233. package/dist/component/mediaVariantMutations.d.ts +468 -0
  234. package/dist/component/mediaVariantMutations.d.ts.map +1 -0
  235. package/dist/component/mediaVariantMutations.js +737 -0
  236. package/dist/component/mediaVariantMutations.js.map +1 -0
  237. package/dist/component/mediaVariants.d.ts +525 -0
  238. package/dist/component/mediaVariants.d.ts.map +1 -0
  239. package/dist/component/mediaVariants.js +661 -0
  240. package/dist/component/mediaVariants.js.map +1 -0
  241. package/dist/component/ragContentIndexer.d.ts +595 -0
  242. package/dist/component/ragContentIndexer.d.ts.map +1 -0
  243. package/dist/component/ragContentIndexer.js +794 -0
  244. package/dist/component/ragContentIndexer.js.map +1 -0
  245. package/dist/component/rateLimitHooks.d.ts +266 -0
  246. package/dist/component/rateLimitHooks.d.ts.map +1 -0
  247. package/dist/component/rateLimitHooks.js +412 -0
  248. package/dist/component/rateLimitHooks.js.map +1 -0
  249. package/dist/component/roles.d.ts +649 -0
  250. package/dist/component/roles.d.ts.map +1 -0
  251. package/dist/component/roles.js +884 -0
  252. package/dist/component/roles.js.map +1 -0
  253. package/dist/component/scheduledPublish.d.ts +182 -0
  254. package/dist/component/scheduledPublish.d.ts.map +1 -0
  255. package/dist/component/scheduledPublish.js +304 -0
  256. package/dist/component/scheduledPublish.js.map +1 -0
  257. package/dist/component/schema.d.ts +4114 -0
  258. package/dist/component/schema.d.ts.map +1 -0
  259. package/dist/component/schema.js +469 -0
  260. package/dist/component/schema.js.map +1 -0
  261. package/dist/component/taxonomies.d.ts +476 -0
  262. package/dist/component/taxonomies.d.ts.map +1 -0
  263. package/dist/component/taxonomies.js +785 -0
  264. package/dist/component/taxonomies.js.map +1 -0
  265. package/dist/component/taxonomyMutations.d.ts +206 -0
  266. package/dist/component/taxonomyMutations.d.ts.map +1 -0
  267. package/dist/component/taxonomyMutations.js +1001 -0
  268. package/dist/component/taxonomyMutations.js.map +1 -0
  269. package/dist/component/trash.d.ts +265 -0
  270. package/dist/component/trash.d.ts.map +1 -0
  271. package/dist/component/trash.js +621 -0
  272. package/dist/component/trash.js.map +1 -0
  273. package/dist/component/types.d.ts +4 -0
  274. package/dist/component/types.d.ts.map +1 -0
  275. package/dist/component/types.js +2 -0
  276. package/dist/component/types.js.map +1 -0
  277. package/dist/component/userContext.d.ts +508 -0
  278. package/dist/component/userContext.d.ts.map +1 -0
  279. package/dist/component/userContext.js +615 -0
  280. package/dist/component/userContext.js.map +1 -0
  281. package/dist/component/validation.d.ts +387 -0
  282. package/dist/component/validation.d.ts.map +1 -0
  283. package/dist/component/validation.js +1052 -0
  284. package/dist/component/validation.js.map +1 -0
  285. package/dist/component/validators.d.ts +4645 -0
  286. package/dist/component/validators.d.ts.map +1 -0
  287. package/dist/component/validators.js +641 -0
  288. package/dist/component/validators.js.map +1 -0
  289. package/dist/component/versionMutations.d.ts +216 -0
  290. package/dist/component/versionMutations.d.ts.map +1 -0
  291. package/dist/component/versionMutations.js +321 -0
  292. package/dist/component/versionMutations.js.map +1 -0
  293. package/dist/component/webhookTrigger.d.ts +770 -0
  294. package/dist/component/webhookTrigger.d.ts.map +1 -0
  295. package/dist/component/webhookTrigger.js +1413 -0
  296. package/dist/component/webhookTrigger.js.map +1 -0
  297. package/dist/react/index.d.ts +316 -0
  298. package/dist/react/index.d.ts.map +1 -0
  299. package/dist/react/index.js +558 -0
  300. package/dist/react/index.js.map +1 -0
  301. package/dist/test.d.ts +2230 -0
  302. package/dist/test.d.ts.map +1 -0
  303. package/dist/test.js +1107 -0
  304. package/dist/test.js.map +1 -0
  305. package/package.json +95 -0
  306. package/src/cli/commands/admin.ts +104 -0
  307. package/src/cli/index.ts +21 -0
  308. package/src/cli/utils/detectConvexUrl.ts +54 -0
  309. package/src/cli/utils/openBrowser.ts +16 -0
  310. package/src/client/admin-config.ts +138 -0
  311. package/src/client/adminApi.ts +942 -0
  312. package/src/client/agentTools.ts +1311 -0
  313. package/src/client/argTypes.ts +316 -0
  314. package/src/client/field-types.ts +187 -0
  315. package/src/client/index.ts +1301 -0
  316. package/src/client/queryBuilder.ts +1100 -0
  317. package/src/client/schema/codegen.ts +500 -0
  318. package/src/client/schema/defineContentType.ts +501 -0
  319. package/src/client/schema/index.ts +169 -0
  320. package/src/client/schema/schemaDrift.ts +574 -0
  321. package/src/client/schema/typedClient.ts +688 -0
  322. package/src/client/schema/types.ts +666 -0
  323. package/src/client/types.ts +723 -0
  324. package/src/client/workflows.ts +141 -0
  325. package/src/client/wrapper.ts +4304 -0
  326. package/src/component/_generated/api.ts +140 -0
  327. package/src/component/_generated/component.ts +5029 -0
  328. package/src/component/_generated/dataModel.ts +60 -0
  329. package/src/component/_generated/server.ts +156 -0
  330. package/src/component/authorization.ts +647 -0
  331. package/src/component/authorizationHooks.ts +668 -0
  332. package/src/component/bulkOperations.ts +687 -0
  333. package/src/component/contentEntries.ts +1976 -0
  334. package/src/component/contentEntryMutations.ts +1223 -0
  335. package/src/component/contentEntryValidation.ts +707 -0
  336. package/src/component/contentLock.ts +550 -0
  337. package/src/component/contentTypeMigration.ts +1064 -0
  338. package/src/component/contentTypeMutations.ts +969 -0
  339. package/src/component/contentTypes.ts +346 -0
  340. package/src/component/convex.config.ts +44 -0
  341. package/src/component/documentTypes.ts +240 -0
  342. package/src/component/eventEmitter.ts +485 -0
  343. package/src/component/exportImport.ts +1169 -0
  344. package/src/component/index.ts +491 -0
  345. package/src/component/lib/deepReferenceResolver.ts +999 -0
  346. package/src/component/lib/errors.ts +816 -0
  347. package/src/component/lib/index.ts +145 -0
  348. package/src/component/lib/mediaReferenceResolver.ts +495 -0
  349. package/src/component/lib/metadataExtractor.ts +792 -0
  350. package/src/component/lib/mutationAuth.ts +199 -0
  351. package/src/component/lib/queries.ts +79 -0
  352. package/src/component/lib/ragContentChunker.ts +1371 -0
  353. package/src/component/lib/referenceResolver.ts +430 -0
  354. package/src/component/lib/slugGenerator.ts +262 -0
  355. package/src/component/lib/slugUniqueness.ts +333 -0
  356. package/src/component/lib/softDelete.ts +44 -0
  357. package/src/component/localeFallbackChain.ts +673 -0
  358. package/src/component/localeFields.ts +896 -0
  359. package/src/component/mediaAssetMutations.ts +725 -0
  360. package/src/component/mediaAssets.ts +932 -0
  361. package/src/component/mediaFolderMutations.ts +1046 -0
  362. package/src/component/mediaUploadMutations.ts +224 -0
  363. package/src/component/mediaVariantMutations.ts +900 -0
  364. package/src/component/mediaVariants.ts +793 -0
  365. package/src/component/ragContentIndexer.ts +1067 -0
  366. package/src/component/rateLimitHooks.ts +572 -0
  367. package/src/component/roles.ts +1360 -0
  368. package/src/component/scheduledPublish.ts +358 -0
  369. package/src/component/schema.ts +617 -0
  370. package/src/component/taxonomies.ts +949 -0
  371. package/src/component/taxonomyMutations.ts +1210 -0
  372. package/src/component/trash.ts +724 -0
  373. package/src/component/userContext.ts +898 -0
  374. package/src/component/validation.ts +1388 -0
  375. package/src/component/validators.ts +949 -0
  376. package/src/component/versionMutations.ts +392 -0
  377. package/src/component/webhookTrigger.ts +1922 -0
  378. package/src/react/index.ts +898 -0
  379. package/src/test.ts +1580 -0
@@ -0,0 +1,1052 @@
1
+ import { isLocalizedFieldValue, } from "./localeFields.js";
2
+ // =============================================================================
3
+ // Field Value Validators
4
+ // =============================================================================
5
+ /**
6
+ * Validate a text field value against its configuration
7
+ */
8
+ export function validateTextField(value, fieldDef) {
9
+ const errors = [];
10
+ const { name, required, options } = fieldDef;
11
+ // Check required
12
+ if (required && (value === null || value === undefined || value === "")) {
13
+ errors.push({
14
+ field: name,
15
+ message: `${name} is required`,
16
+ code: "REQUIRED",
17
+ });
18
+ return errors;
19
+ }
20
+ // Skip further validation if value is empty and not required
21
+ if (value === null || value === undefined || value === "") {
22
+ return errors;
23
+ }
24
+ // Type check
25
+ if (typeof value !== "string") {
26
+ errors.push({
27
+ field: name,
28
+ message: `${name} must be a string`,
29
+ code: "INVALID_TYPE",
30
+ });
31
+ return errors;
32
+ }
33
+ // Min length
34
+ if (options?.minLength !== undefined && value.length < options.minLength) {
35
+ errors.push({
36
+ field: name,
37
+ message: `${name} must be at least ${options.minLength} characters`,
38
+ code: "MIN_LENGTH",
39
+ });
40
+ }
41
+ // Max length
42
+ if (options?.maxLength !== undefined && value.length > options.maxLength) {
43
+ errors.push({
44
+ field: name,
45
+ message: `${name} must be at most ${options.maxLength} characters`,
46
+ code: "MAX_LENGTH",
47
+ });
48
+ }
49
+ // Pattern
50
+ if (options?.pattern !== undefined) {
51
+ const regex = new RegExp(options.pattern);
52
+ if (!regex.test(value)) {
53
+ errors.push({
54
+ field: name,
55
+ message: `${name} does not match the required pattern`,
56
+ code: "PATTERN_MISMATCH",
57
+ });
58
+ }
59
+ }
60
+ return errors;
61
+ }
62
+ /**
63
+ * Validate a rich text field value against its configuration
64
+ */
65
+ export function validateRichTextField(value, fieldDef) {
66
+ const errors = [];
67
+ const { name, required, options } = fieldDef;
68
+ // Check required
69
+ if (required && (value === null || value === undefined || value === "")) {
70
+ errors.push({
71
+ field: name,
72
+ message: `${name} is required`,
73
+ code: "REQUIRED",
74
+ });
75
+ return errors;
76
+ }
77
+ // Skip further validation if value is empty and not required
78
+ if (value === null || value === undefined || value === "") {
79
+ return errors;
80
+ }
81
+ // Type check
82
+ if (typeof value !== "string") {
83
+ errors.push({
84
+ field: name,
85
+ message: `${name} must be a string`,
86
+ code: "INVALID_TYPE",
87
+ });
88
+ return errors;
89
+ }
90
+ // Max length (strip HTML tags before counting)
91
+ if (options?.maxLength !== undefined) {
92
+ const plainText = value.replace(/<[^>]*>/g, "");
93
+ if (plainText.length > options.maxLength) {
94
+ errors.push({
95
+ field: name,
96
+ message: `${name} content must be at most ${options.maxLength} characters`,
97
+ code: "MAX_LENGTH",
98
+ });
99
+ }
100
+ }
101
+ return errors;
102
+ }
103
+ /**
104
+ * Validate a number field value against its configuration
105
+ */
106
+ export function validateNumberField(value, fieldDef) {
107
+ const errors = [];
108
+ const { name, required, options } = fieldDef;
109
+ // Check required
110
+ if (required && (value === null || value === undefined)) {
111
+ errors.push({
112
+ field: name,
113
+ message: `${name} is required`,
114
+ code: "REQUIRED",
115
+ });
116
+ return errors;
117
+ }
118
+ // Skip further validation if value is empty and not required
119
+ if (value === null || value === undefined) {
120
+ return errors;
121
+ }
122
+ // Type check
123
+ if (typeof value !== "number" || isNaN(value)) {
124
+ errors.push({
125
+ field: name,
126
+ message: `${name} must be a number`,
127
+ code: "INVALID_TYPE",
128
+ });
129
+ return errors;
130
+ }
131
+ // Precision check (step = 1 means integer)
132
+ if (options?.precision === 0 && !Number.isInteger(value)) {
133
+ errors.push({
134
+ field: name,
135
+ message: `${name} must be a whole number`,
136
+ code: "NOT_INTEGER",
137
+ });
138
+ }
139
+ // Min value
140
+ if (options?.min !== undefined && value < options.min) {
141
+ errors.push({
142
+ field: name,
143
+ message: `${name} must be at least ${options.min}`,
144
+ code: "MIN_VALUE",
145
+ });
146
+ }
147
+ // Max value
148
+ if (options?.max !== undefined && value > options.max) {
149
+ errors.push({
150
+ field: name,
151
+ message: `${name} must be at most ${options.max}`,
152
+ code: "MAX_VALUE",
153
+ });
154
+ }
155
+ return errors;
156
+ }
157
+ /**
158
+ * Validate a boolean field value against its configuration
159
+ */
160
+ export function validateBooleanField(value, fieldDef) {
161
+ const errors = [];
162
+ const { name, required } = fieldDef;
163
+ // Check required
164
+ if (required && (value === null || value === undefined)) {
165
+ errors.push({
166
+ field: name,
167
+ message: `${name} is required`,
168
+ code: "REQUIRED",
169
+ });
170
+ return errors;
171
+ }
172
+ // Skip further validation if value is empty and not required
173
+ if (value === null || value === undefined) {
174
+ return errors;
175
+ }
176
+ // Type check
177
+ if (typeof value !== "boolean") {
178
+ errors.push({
179
+ field: name,
180
+ message: `${name} must be a boolean`,
181
+ code: "INVALID_TYPE",
182
+ });
183
+ }
184
+ return errors;
185
+ }
186
+ /**
187
+ * Validate a date or datetime field value against its configuration
188
+ */
189
+ export function validateDateField(value, fieldDef) {
190
+ const errors = [];
191
+ const { name, required, options } = fieldDef;
192
+ // Check required
193
+ if (required && (value === null || value === undefined)) {
194
+ errors.push({
195
+ field: name,
196
+ message: `${name} is required`,
197
+ code: "REQUIRED",
198
+ });
199
+ return errors;
200
+ }
201
+ // Skip further validation if value is empty and not required
202
+ if (value === null || value === undefined) {
203
+ return errors;
204
+ }
205
+ // Type check (must be a valid timestamp)
206
+ if (typeof value !== "number" || isNaN(value)) {
207
+ errors.push({
208
+ field: name,
209
+ message: `${name} must be a valid timestamp`,
210
+ code: "INVALID_TYPE",
211
+ });
212
+ return errors;
213
+ }
214
+ // Min date (using min from options)
215
+ if (options?.min !== undefined && value < options.min) {
216
+ errors.push({
217
+ field: name,
218
+ message: `${name} must be on or after the minimum date`,
219
+ code: "MIN_DATE",
220
+ });
221
+ }
222
+ // Max date (using max from options)
223
+ if (options?.max !== undefined && value > options.max) {
224
+ errors.push({
225
+ field: name,
226
+ message: `${name} must be on or before the maximum date`,
227
+ code: "MAX_DATE",
228
+ });
229
+ }
230
+ return errors;
231
+ }
232
+ /**
233
+ * Validate a reference field value against its configuration.
234
+ *
235
+ * Reference fields store IDs to other content entries. They support:
236
+ * - Single reference: `string` (one entry ID)
237
+ * - Multiple references: `string[]` (array of entry IDs) when `multiple: true`
238
+ *
239
+ * Configuration options:
240
+ * - `allowedContentTypes`: Array of content type names that can be referenced
241
+ * - `multiple`: If true, accepts an array of references
242
+ * - `minItems`: Minimum number of references required (only when `multiple: true`)
243
+ * - `max`: Maximum number of references allowed (only when `multiple: true`)
244
+ *
245
+ * @example
246
+ * ```typescript
247
+ * // Single reference to an author
248
+ * const authorField: FieldDefinition = {
249
+ * name: "author",
250
+ * label: "Author",
251
+ * type: "reference",
252
+ * required: true,
253
+ * options: {
254
+ * allowedContentTypes: ["user"],
255
+ * },
256
+ * };
257
+ *
258
+ * // Multiple references to related posts (1-5 required)
259
+ * const relatedPostsField: FieldDefinition = {
260
+ * name: "relatedPosts",
261
+ * label: "Related Posts",
262
+ * type: "reference",
263
+ * required: true,
264
+ * options: {
265
+ * allowedContentTypes: ["blog_post"],
266
+ * multiple: true,
267
+ * minItems: 1,
268
+ * max: 5,
269
+ * },
270
+ * };
271
+ * ```
272
+ */
273
+ export function validateReferenceField(value, fieldDef) {
274
+ const errors = [];
275
+ const { name, required, options } = fieldDef;
276
+ const multiple = options?.multiple ?? false;
277
+ // Check required
278
+ if (required && (value === null || value === undefined)) {
279
+ errors.push({
280
+ field: name,
281
+ message: `${name} is required`,
282
+ code: "REQUIRED",
283
+ });
284
+ return errors;
285
+ }
286
+ // Skip further validation if value is empty and not required
287
+ if (value === null || value === undefined) {
288
+ return errors;
289
+ }
290
+ // Type check based on multiple setting
291
+ if (multiple) {
292
+ if (!Array.isArray(value)) {
293
+ errors.push({
294
+ field: name,
295
+ message: `${name} must be an array of references`,
296
+ code: "INVALID_TYPE",
297
+ });
298
+ return errors;
299
+ }
300
+ // Check if required and empty array
301
+ if (required && value.length === 0) {
302
+ errors.push({
303
+ field: name,
304
+ message: `${name} requires at least one reference`,
305
+ code: "REQUIRED",
306
+ });
307
+ }
308
+ // Check each item is a string (valid ID format)
309
+ for (const item of value) {
310
+ if (typeof item !== "string") {
311
+ errors.push({
312
+ field: name,
313
+ message: `${name} contains invalid reference IDs`,
314
+ code: "INVALID_TYPE",
315
+ });
316
+ break;
317
+ }
318
+ }
319
+ // Min items validation (only for multiple references)
320
+ if (options?.minItems !== undefined && value.length < options.minItems) {
321
+ errors.push({
322
+ field: name,
323
+ message: `${name} requires at least ${options.minItems} reference${options.minItems === 1 ? "" : "s"}`,
324
+ code: "MIN_ITEMS",
325
+ });
326
+ }
327
+ // Max items (using max from options)
328
+ if (options?.max !== undefined && value.length > options.max) {
329
+ errors.push({
330
+ field: name,
331
+ message: `${name} can have at most ${options.max} reference${options.max === 1 ? "" : "s"}`,
332
+ code: "MAX_ITEMS",
333
+ });
334
+ }
335
+ }
336
+ else {
337
+ if (typeof value !== "string") {
338
+ errors.push({
339
+ field: name,
340
+ message: `${name} must be a reference ID`,
341
+ code: "INVALID_TYPE",
342
+ });
343
+ }
344
+ }
345
+ return errors;
346
+ }
347
+ /**
348
+ * Check if a reference value is valid for a given content type constraint.
349
+ *
350
+ * This is a helper function that can be used in mutation handlers to validate
351
+ * that referenced entries exist and belong to allowed content types.
352
+ *
353
+ * @param referenceId - The content entry ID to validate
354
+ * @param allowedContentTypes - Array of allowed content type names (optional)
355
+ * @param contentTypeLookup - Function to get content type name by entry ID
356
+ * @returns Object with `valid` boolean and optional `error` message
357
+ */
358
+ export async function validateReferenceContentType(referenceId, allowedContentTypes, contentTypeLookup) {
359
+ // If no content type constraints, the reference is valid
360
+ if (!allowedContentTypes || allowedContentTypes.length === 0) {
361
+ return { valid: true };
362
+ }
363
+ // Look up the content type of the referenced entry
364
+ const contentTypeName = await contentTypeLookup(referenceId);
365
+ // If the entry doesn't exist, it's invalid
366
+ if (contentTypeName === null) {
367
+ return {
368
+ valid: false,
369
+ error: `Referenced entry not found: ${referenceId}`,
370
+ };
371
+ }
372
+ // Check if the content type is in the allowed list
373
+ if (!allowedContentTypes.includes(contentTypeName)) {
374
+ return {
375
+ valid: false,
376
+ error: `Reference must be of type: ${allowedContentTypes.join(", ")}. Got: ${contentTypeName}`,
377
+ };
378
+ }
379
+ return { valid: true };
380
+ }
381
+ /**
382
+ * Validate a media field value against its configuration.
383
+ *
384
+ * Media fields store IDs to media assets. They support:
385
+ * - Single reference: `string` (one media asset ID)
386
+ * - Multiple references (gallery): `string[]` (array of media asset IDs) when `multiple: true`
387
+ *
388
+ * Configuration options:
389
+ * - `allowedMimeTypes`: Array of allowed MIME types (supports wildcards like "image/*")
390
+ * - `multiple`: If true, accepts an array of references (gallery mode)
391
+ * - `minItems`: Minimum number of media assets required (only when `multiple: true`)
392
+ * - `max`: Maximum number of media assets allowed (only when `multiple: true`)
393
+ * - `maxFileSize`: Maximum file size in bytes (validated at upload time, not here)
394
+ *
395
+ * Note: MIME type validation requires database lookups and is performed by
396
+ * `validateAllMediaReferences` in the mediaReferenceResolver module.
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * // Single featured image (images only)
401
+ * const featuredImageField: FieldDefinition = {
402
+ * name: "featuredImage",
403
+ * label: "Featured Image",
404
+ * type: "media",
405
+ * required: true,
406
+ * options: {
407
+ * allowedMimeTypes: ["image/*"],
408
+ * },
409
+ * };
410
+ *
411
+ * // Gallery with 2-10 images
412
+ * const galleryField: FieldDefinition = {
413
+ * name: "gallery",
414
+ * label: "Photo Gallery",
415
+ * type: "media",
416
+ * required: true,
417
+ * options: {
418
+ * allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
419
+ * multiple: true,
420
+ * minItems: 2,
421
+ * max: 10,
422
+ * },
423
+ * };
424
+ * ```
425
+ */
426
+ export function validateMediaField(value, fieldDef) {
427
+ const errors = [];
428
+ const { name, required, options } = fieldDef;
429
+ const multiple = options?.multiple ?? false;
430
+ // Check required
431
+ if (required && (value === null || value === undefined)) {
432
+ errors.push({
433
+ field: name,
434
+ message: `${name} is required`,
435
+ code: "REQUIRED",
436
+ });
437
+ return errors;
438
+ }
439
+ // Skip further validation if value is empty and not required
440
+ if (value === null || value === undefined) {
441
+ return errors;
442
+ }
443
+ // Type check based on multiple setting
444
+ if (multiple) {
445
+ if (!Array.isArray(value)) {
446
+ errors.push({
447
+ field: name,
448
+ message: `${name} must be an array of media asset IDs`,
449
+ code: "INVALID_TYPE",
450
+ });
451
+ return errors;
452
+ }
453
+ // Check if required and empty array
454
+ if (required && value.length === 0) {
455
+ errors.push({
456
+ field: name,
457
+ message: `${name} requires at least one media asset`,
458
+ code: "REQUIRED",
459
+ });
460
+ }
461
+ // Check each item is a string (valid ID format)
462
+ for (const item of value) {
463
+ if (typeof item !== "string") {
464
+ errors.push({
465
+ field: name,
466
+ message: `${name} contains invalid media asset IDs`,
467
+ code: "INVALID_TYPE",
468
+ });
469
+ break;
470
+ }
471
+ }
472
+ // Min items validation (only for multiple/gallery media fields)
473
+ if (options?.minItems !== undefined && value.length < options.minItems) {
474
+ errors.push({
475
+ field: name,
476
+ message: `${name} requires at least ${options.minItems} media asset${options.minItems === 1 ? "" : "s"}`,
477
+ code: "MIN_ITEMS",
478
+ });
479
+ }
480
+ // Max items (using max from options)
481
+ if (options?.max !== undefined && value.length > options.max) {
482
+ errors.push({
483
+ field: name,
484
+ message: `${name} can have at most ${options.max} media asset${options.max === 1 ? "" : "s"}`,
485
+ code: "MAX_ITEMS",
486
+ });
487
+ }
488
+ }
489
+ else {
490
+ if (typeof value !== "string") {
491
+ errors.push({
492
+ field: name,
493
+ message: `${name} must be a media asset ID`,
494
+ code: "INVALID_TYPE",
495
+ });
496
+ }
497
+ }
498
+ return errors;
499
+ }
500
+ /**
501
+ * Validate a select field value against its configuration
502
+ */
503
+ export function validateSelectField(value, fieldDef) {
504
+ const errors = [];
505
+ const { name, required, options } = fieldDef;
506
+ // Check required
507
+ if (required && (value === null || value === undefined || value === "")) {
508
+ errors.push({
509
+ field: name,
510
+ message: `${name} is required`,
511
+ code: "REQUIRED",
512
+ });
513
+ return errors;
514
+ }
515
+ // Skip further validation if value is empty and not required
516
+ if (value === null || value === undefined || value === "") {
517
+ return errors;
518
+ }
519
+ // Type check
520
+ if (typeof value !== "string") {
521
+ errors.push({
522
+ field: name,
523
+ message: `${name} must be a string`,
524
+ code: "INVALID_TYPE",
525
+ });
526
+ return errors;
527
+ }
528
+ // Validate against allowed options
529
+ if (options?.options) {
530
+ const allowedValues = options.options.map((opt) => opt.value);
531
+ if (!allowedValues.includes(value)) {
532
+ errors.push({
533
+ field: name,
534
+ message: `${name} has an invalid value`,
535
+ code: "INVALID_TYPE",
536
+ });
537
+ }
538
+ }
539
+ return errors;
540
+ }
541
+ /**
542
+ * Validate a multi-select field value against its configuration
543
+ */
544
+ export function validateMultiSelectField(value, fieldDef) {
545
+ const errors = [];
546
+ const { name, required, options } = fieldDef;
547
+ // Check required
548
+ if (required && (value === null || value === undefined)) {
549
+ errors.push({
550
+ field: name,
551
+ message: `${name} is required`,
552
+ code: "REQUIRED",
553
+ });
554
+ return errors;
555
+ }
556
+ // Skip further validation if value is empty and not required
557
+ if (value === null || value === undefined) {
558
+ return errors;
559
+ }
560
+ // Type check - must be array
561
+ if (!Array.isArray(value)) {
562
+ errors.push({
563
+ field: name,
564
+ message: `${name} must be an array`,
565
+ code: "INVALID_TYPE",
566
+ });
567
+ return errors;
568
+ }
569
+ // Check if required and empty array
570
+ if (required && value.length === 0) {
571
+ errors.push({
572
+ field: name,
573
+ message: `${name} requires at least one selection`,
574
+ code: "REQUIRED",
575
+ });
576
+ }
577
+ // Validate each item is a string and in allowed options
578
+ const allowedValues = options?.options?.map((opt) => opt.value) ?? [];
579
+ for (const item of value) {
580
+ if (typeof item !== "string") {
581
+ errors.push({
582
+ field: name,
583
+ message: `${name} contains invalid values`,
584
+ code: "INVALID_TYPE",
585
+ });
586
+ break;
587
+ }
588
+ if (allowedValues.length > 0 && !allowedValues.includes(item)) {
589
+ errors.push({
590
+ field: name,
591
+ message: `${name} contains an invalid option`,
592
+ code: "INVALID_TYPE",
593
+ });
594
+ break;
595
+ }
596
+ }
597
+ return errors;
598
+ }
599
+ /**
600
+ * Validate a JSON field value
601
+ */
602
+ export function validateJsonField(value, fieldDef) {
603
+ const errors = [];
604
+ const { name, required } = fieldDef;
605
+ // Check required
606
+ if (required && (value === null || value === undefined)) {
607
+ errors.push({
608
+ field: name,
609
+ message: `${name} is required`,
610
+ code: "REQUIRED",
611
+ });
612
+ return errors;
613
+ }
614
+ // JSON fields can be any valid JSON value, so minimal type checking
615
+ // The value has already been parsed if it was a string
616
+ return errors;
617
+ }
618
+ /**
619
+ * Validate a tags field value against its configuration.
620
+ *
621
+ * Tags fields store arrays of taxonomy term IDs for flexible content categorization.
622
+ * They support:
623
+ * - Multiple term selection
624
+ * - Optional inline term creation (when allowCreate is true)
625
+ * - Min/max limits on number of tags
626
+ *
627
+ * Configuration options:
628
+ * - `taxonomyId`: The taxonomy these tags belong to (required at content type level)
629
+ * - `allowCreate`: If true, users can create new tags inline
630
+ * - `minTags`: Minimum number of tags required
631
+ * - `maxTags`: Maximum number of tags allowed
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * const tagsField: FieldDefinition = {
636
+ * name: "tags",
637
+ * label: "Tags",
638
+ * type: "tags",
639
+ * required: true,
640
+ * options: {
641
+ * taxonomyId: "tags_taxonomy_id",
642
+ * allowCreate: true,
643
+ * minTags: 1,
644
+ * maxTags: 10,
645
+ * },
646
+ * };
647
+ * ```
648
+ */
649
+ export function validateTagsField(value, fieldDef) {
650
+ const errors = [];
651
+ const { name, required, options } = fieldDef;
652
+ // Check required
653
+ if (required && (value === null || value === undefined)) {
654
+ errors.push({
655
+ field: name,
656
+ message: `${name} is required`,
657
+ code: "REQUIRED",
658
+ });
659
+ return errors;
660
+ }
661
+ // Skip further validation if value is empty and not required
662
+ if (value === null || value === undefined) {
663
+ return errors;
664
+ }
665
+ // Type check - must be array of strings (term IDs)
666
+ if (!Array.isArray(value)) {
667
+ errors.push({
668
+ field: name,
669
+ message: `${name} must be an array of tag IDs`,
670
+ code: "INVALID_TYPE",
671
+ });
672
+ return errors;
673
+ }
674
+ // Check if required and empty array
675
+ if (required && value.length === 0) {
676
+ errors.push({
677
+ field: name,
678
+ message: `${name} requires at least one tag`,
679
+ code: "REQUIRED",
680
+ });
681
+ return errors;
682
+ }
683
+ // Validate each item is a string
684
+ for (const item of value) {
685
+ if (typeof item !== "string") {
686
+ errors.push({
687
+ field: name,
688
+ message: `${name} contains invalid tag IDs`,
689
+ code: "INVALID_TYPE",
690
+ });
691
+ break;
692
+ }
693
+ }
694
+ // Min tags validation
695
+ const minTags = options?.minTags;
696
+ if (minTags !== undefined && value.length < minTags) {
697
+ errors.push({
698
+ field: name,
699
+ message: `${name} requires at least ${minTags} tag${minTags === 1 ? "" : "s"}`,
700
+ code: "MIN_ITEMS",
701
+ });
702
+ }
703
+ // Max tags validation
704
+ const maxTags = options?.maxTags;
705
+ if (maxTags !== undefined && value.length > maxTags) {
706
+ errors.push({
707
+ field: name,
708
+ message: `${name} can have at most ${maxTags} tag${maxTags === 1 ? "" : "s"}`,
709
+ code: "MAX_ITEMS",
710
+ });
711
+ }
712
+ return errors;
713
+ }
714
+ /**
715
+ * Validate a category field value against its configuration.
716
+ *
717
+ * Category fields store taxonomy term IDs for hierarchical content organization.
718
+ * They support:
719
+ * - Single category selection (default)
720
+ * - Multiple category selection (when allowMultiple is true)
721
+ *
722
+ * Configuration options:
723
+ * - `taxonomyId`: The taxonomy these categories belong to (required at content type level)
724
+ * - `allowMultiple`: If true, accepts an array of category IDs
725
+ *
726
+ * @example
727
+ * ```typescript
728
+ * // Single category selection
729
+ * const categoryField: FieldDefinition = {
730
+ * name: "category",
731
+ * label: "Category",
732
+ * type: "category",
733
+ * required: true,
734
+ * options: {
735
+ * taxonomyId: "categories_taxonomy_id",
736
+ * },
737
+ * };
738
+ *
739
+ * // Multiple category selection
740
+ * const categoriesField: FieldDefinition = {
741
+ * name: "categories",
742
+ * label: "Categories",
743
+ * type: "category",
744
+ * required: true,
745
+ * options: {
746
+ * taxonomyId: "categories_taxonomy_id",
747
+ * allowMultiple: true,
748
+ * },
749
+ * };
750
+ * ```
751
+ */
752
+ export function validateCategoryField(value, fieldDef) {
753
+ const errors = [];
754
+ const { name, required, options } = fieldDef;
755
+ const allowMultiple = options?.allowMultiple ?? false;
756
+ // Check required
757
+ if (required && (value === null || value === undefined)) {
758
+ errors.push({
759
+ field: name,
760
+ message: `${name} is required`,
761
+ code: "REQUIRED",
762
+ });
763
+ return errors;
764
+ }
765
+ // Skip further validation if value is empty and not required
766
+ if (value === null || value === undefined) {
767
+ return errors;
768
+ }
769
+ // Type check based on allowMultiple setting
770
+ if (allowMultiple) {
771
+ if (!Array.isArray(value)) {
772
+ errors.push({
773
+ field: name,
774
+ message: `${name} must be an array of category IDs`,
775
+ code: "INVALID_TYPE",
776
+ });
777
+ return errors;
778
+ }
779
+ // Check if required and empty array
780
+ if (required && value.length === 0) {
781
+ errors.push({
782
+ field: name,
783
+ message: `${name} requires at least one category`,
784
+ code: "REQUIRED",
785
+ });
786
+ }
787
+ // Check each item is a string (valid ID format)
788
+ for (const item of value) {
789
+ if (typeof item !== "string") {
790
+ errors.push({
791
+ field: name,
792
+ message: `${name} contains invalid category IDs`,
793
+ code: "INVALID_TYPE",
794
+ });
795
+ break;
796
+ }
797
+ }
798
+ }
799
+ else {
800
+ // Single category selection
801
+ if (typeof value !== "string") {
802
+ errors.push({
803
+ field: name,
804
+ message: `${name} must be a category ID`,
805
+ code: "INVALID_TYPE",
806
+ });
807
+ }
808
+ }
809
+ return errors;
810
+ }
811
+ /**
812
+ * Validate a single field value (non-localized) based on its type.
813
+ * This is the core validation logic that handles the actual value checking.
814
+ */
815
+ function validateSingleValue(value, fieldDef) {
816
+ const { name, type } = fieldDef;
817
+ switch (type) {
818
+ case "text":
819
+ return validateTextField(value, fieldDef);
820
+ case "richText":
821
+ return validateRichTextField(value, fieldDef);
822
+ case "number":
823
+ return validateNumberField(value, fieldDef);
824
+ case "boolean":
825
+ return validateBooleanField(value, fieldDef);
826
+ case "date":
827
+ case "datetime":
828
+ return validateDateField(value, fieldDef);
829
+ case "reference":
830
+ return validateReferenceField(value, fieldDef);
831
+ case "media":
832
+ return validateMediaField(value, fieldDef);
833
+ case "select":
834
+ return validateSelectField(value, fieldDef);
835
+ case "multiSelect":
836
+ return validateMultiSelectField(value, fieldDef);
837
+ case "json":
838
+ return validateJsonField(value, fieldDef);
839
+ case "tags":
840
+ return validateTagsField(value, fieldDef);
841
+ case "category":
842
+ return validateCategoryField(value, fieldDef);
843
+ default: {
844
+ // Unknown field type
845
+ return [
846
+ {
847
+ field: name,
848
+ message: `Unknown field type: ${type}`,
849
+ code: "INVALID_TYPE",
850
+ },
851
+ ];
852
+ }
853
+ }
854
+ }
855
+ /**
856
+ * Validate a localized field value.
857
+ *
858
+ * For localized fields, the value should be a LocalizedFieldValue structure:
859
+ * `{ "en-US": "Hello", "es-ES": "Hola" }`
860
+ *
861
+ * This function validates:
862
+ * 1. The structure is a valid LocalizedFieldValue
863
+ * 2. Each locale's value passes the field type validation
864
+ * 3. Required locales have values (if specified)
865
+ *
866
+ * @param value - The localized field value to validate
867
+ * @param fieldDef - The field definition
868
+ * @param options - Validation options for localized fields
869
+ * @returns Array of validation errors
870
+ */
871
+ export function validateLocalizedFieldValue(value, fieldDef, options = {}) {
872
+ const errors = [];
873
+ const { name, required } = fieldDef;
874
+ const { locale, requiredLocales } = options;
875
+ // Handle null/undefined for required fields
876
+ if (value === null || value === undefined) {
877
+ if (required) {
878
+ errors.push({
879
+ field: name,
880
+ message: `${name} is required`,
881
+ code: "REQUIRED",
882
+ });
883
+ }
884
+ return errors;
885
+ }
886
+ // Check if the value is a valid LocalizedFieldValue structure
887
+ if (!isLocalizedFieldValue(value)) {
888
+ errors.push({
889
+ field: name,
890
+ message: `${name} must be a localized field structure (object with locale codes as keys)`,
891
+ code: "INVALID_LOCALIZED_STRUCTURE",
892
+ });
893
+ return errors;
894
+ }
895
+ const localizedValue = value;
896
+ const locales = Object.keys(localizedValue);
897
+ // Check if required and empty
898
+ if (required && locales.length === 0) {
899
+ errors.push({
900
+ field: name,
901
+ message: `${name} requires at least one locale value`,
902
+ code: "REQUIRED",
903
+ });
904
+ return errors;
905
+ }
906
+ // Check required locales
907
+ if (requiredLocales && requiredLocales.length > 0) {
908
+ for (const requiredLocale of requiredLocales) {
909
+ if (!(requiredLocale in localizedValue)) {
910
+ errors.push({
911
+ field: name,
912
+ message: `${name} is missing required translation for locale: ${requiredLocale}`,
913
+ code: "MISSING_LOCALE",
914
+ });
915
+ }
916
+ }
917
+ }
918
+ // If a specific locale is specified, validate only that locale
919
+ if (locale) {
920
+ if (locale in localizedValue) {
921
+ // Create a non-localized field definition for single value validation
922
+ const nonLocalizedFieldDef = { ...fieldDef, localized: false };
923
+ const localeErrors = validateSingleValue(localizedValue[locale], nonLocalizedFieldDef);
924
+ // Prefix errors with locale info
925
+ for (const error of localeErrors) {
926
+ errors.push({
927
+ ...error,
928
+ field: `${name}[${locale}]`,
929
+ message: `${name} (${locale}): ${error.message.replace(`${name} `, "")}`,
930
+ });
931
+ }
932
+ }
933
+ }
934
+ else {
935
+ // Validate all locale values
936
+ for (const [localeCode, localeValue] of Object.entries(localizedValue)) {
937
+ // Create a non-localized field definition for single value validation
938
+ const nonLocalizedFieldDef = { ...fieldDef, localized: false, required: false };
939
+ const localeErrors = validateSingleValue(localeValue, nonLocalizedFieldDef);
940
+ // Prefix errors with locale info
941
+ for (const error of localeErrors) {
942
+ errors.push({
943
+ ...error,
944
+ field: `${name}[${localeCode}]`,
945
+ message: `${name} (${localeCode}): ${error.message.replace(`${name} `, "")}`,
946
+ });
947
+ }
948
+ }
949
+ }
950
+ return errors;
951
+ }
952
+ /**
953
+ * Validate a single field value based on its definition.
954
+ *
955
+ * Handles both localized and non-localized fields:
956
+ * - Non-localized fields: Validates the value directly
957
+ * - Localized fields: Validates the LocalizedFieldValue structure and each locale's value
958
+ *
959
+ * @param value - The field value to validate (plain value or LocalizedFieldValue)
960
+ * @param fieldDef - The field definition
961
+ * @param options - Optional validation options for localized fields
962
+ * @returns Array of validation errors
963
+ */
964
+ export function validateFieldValue(value, fieldDef, options) {
965
+ // Check if this is a localized field
966
+ if (fieldDef.localized) {
967
+ return validateLocalizedFieldValue(value, fieldDef, options);
968
+ }
969
+ // Non-localized field - use standard validation
970
+ return validateSingleValue(value, fieldDef);
971
+ }
972
+ /**
973
+ * Validate content data against a content type schema
974
+ *
975
+ * @param data - The content data to validate
976
+ * @param schema - The content type schema defining expected fields
977
+ * @param options - Validation options
978
+ * @returns ValidationResult with any errors found
979
+ *
980
+ * @example
981
+ * ```typescript
982
+ * // Basic validation
983
+ * const result = validateContentData(data, schema);
984
+ *
985
+ * // Validate with localized field support
986
+ * const result = validateContentData(data, schema, {
987
+ * locale: "en-US",
988
+ * requiredLocales: ["en-US", "es-ES"],
989
+ * });
990
+ * ```
991
+ */
992
+ export function validateContentData(data, schema, options = {}) {
993
+ const errors = [];
994
+ const fieldMap = new Map(schema.fields.map((f) => [f.name, f]));
995
+ const { strictFields, locale, requiredLocales } = options;
996
+ // Create localized validation options
997
+ const localizedOptions = {
998
+ locale,
999
+ requiredLocales,
1000
+ };
1001
+ // Validate each defined field
1002
+ for (const fieldDef of schema.fields) {
1003
+ const value = data[fieldDef.name];
1004
+ const fieldErrors = validateFieldValue(value, fieldDef, localizedOptions);
1005
+ errors.push(...fieldErrors);
1006
+ }
1007
+ // Check for unknown fields if strict mode
1008
+ if (strictFields) {
1009
+ for (const key of Object.keys(data)) {
1010
+ if (!fieldMap.has(key)) {
1011
+ errors.push({
1012
+ field: key,
1013
+ message: `Unknown field: ${key}`,
1014
+ code: "UNKNOWN_FIELD",
1015
+ });
1016
+ }
1017
+ }
1018
+ }
1019
+ if (errors.length === 0) {
1020
+ return { valid: true, errors: [] };
1021
+ }
1022
+ return { valid: false, errors };
1023
+ }
1024
+ /**
1025
+ * Apply default values to content data based on field definitions
1026
+ */
1027
+ export function applyFieldDefaults(data, schema) {
1028
+ const result = { ...data };
1029
+ for (const fieldDef of schema.fields) {
1030
+ const { name, defaultValue } = fieldDef;
1031
+ // Only apply default if field is not already set
1032
+ if (result[name] === undefined || result[name] === null) {
1033
+ if (defaultValue !== undefined) {
1034
+ result[name] = defaultValue;
1035
+ }
1036
+ }
1037
+ }
1038
+ return result;
1039
+ }
1040
+ /**
1041
+ * Get the field type from a field definition
1042
+ */
1043
+ export function getFieldType(fieldDef) {
1044
+ return fieldDef.type;
1045
+ }
1046
+ /**
1047
+ * Check if a field is required based on its configuration
1048
+ */
1049
+ export function isFieldRequired(fieldDef) {
1050
+ return fieldDef.required === true;
1051
+ }
1052
+ //# sourceMappingURL=validation.js.map