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,931 @@
1
+ /**
2
+ * Content Export/Import Functions
3
+ *
4
+ * Provides functions to export content entries to JSON format and import from JSON.
5
+ * Supports selective export by type or filter, handles reference resolution, and
6
+ * validates imports against schemas.
7
+ *
8
+ * ## Export Features
9
+ * - Export all entries or filter by content type
10
+ * - Optionally resolve references to include related content
11
+ * - Support for status filtering (export only published, etc.)
12
+ * - Include content type definitions for schema validation during import
13
+ *
14
+ * ## Import Features
15
+ * - Validate all entries against content type schemas before import
16
+ * - Handle reference ID mapping (old IDs to new IDs)
17
+ * - Support for skip, update, or error on duplicate slugs
18
+ * - Dry-run mode to validate without making changes
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Export all published blog posts
23
+ * const exportData = await ctx.runQuery(api.exportImport.exportEntries, {
24
+ * contentTypeName: "blog_post",
25
+ * status: "published",
26
+ * includeReferences: true,
27
+ * });
28
+ *
29
+ * // Import entries with conflict resolution
30
+ * const result = await ctx.runMutation(api.exportImport.importEntries, {
31
+ * data: exportData,
32
+ * onConflict: "skip",
33
+ * importedBy: currentUserId,
34
+ * });
35
+ * ```
36
+ */
37
+ import { v } from "convex/values";
38
+ import { isDeleted } from "./lib/softDelete.js";
39
+ import { query, mutation } from "./_generated/server.js";
40
+ import { contentStatusValidator,
41
+ // contentEntryDoc,
42
+ // contentTypeDoc,
43
+ fieldTypeValidator, } from "./validators.js";
44
+ import { validateContentData, } from "./validation.js";
45
+ import { ensureUniqueSlug } from "./lib/slugUniqueness.js";
46
+ // =============================================================================
47
+ // Export Types and Validators
48
+ // =============================================================================
49
+ /**
50
+ * Field options validator for exported content types.
51
+ * This is a specialized version that uses string for taxonomyId instead of
52
+ * Id<"taxonomies"> since IDs are not portable across Convex deployments
53
+ * during export/import operations.
54
+ */
55
+ const exportedFieldOptionsValidator = v.optional(v.object({
56
+ // Text fields
57
+ minLength: v.optional(v.number()),
58
+ maxLength: v.optional(v.number()),
59
+ pattern: v.optional(v.string()),
60
+ // Number fields
61
+ min: v.optional(v.number()),
62
+ max: v.optional(v.number()),
63
+ step: v.optional(v.number()),
64
+ precision: v.optional(v.number()),
65
+ // Reference fields
66
+ allowedContentTypes: v.optional(v.array(v.string())),
67
+ multiple: v.optional(v.boolean()),
68
+ minItems: v.optional(v.number()),
69
+ // Media fields
70
+ allowedMimeTypes: v.optional(v.array(v.string())),
71
+ maxFileSize: v.optional(v.number()),
72
+ // Select fields
73
+ options: v.optional(v.array(v.object({
74
+ value: v.string(),
75
+ label: v.string(),
76
+ }))),
77
+ // Rich text fields
78
+ allowedBlocks: v.optional(v.array(v.string())),
79
+ allowedMarks: v.optional(v.array(v.string())),
80
+ // Tag fields - taxonomyId as string for portability
81
+ taxonomyId: v.optional(v.string()),
82
+ allowCreate: v.optional(v.boolean()),
83
+ maxTags: v.optional(v.number()),
84
+ minTags: v.optional(v.number()),
85
+ // Category fields
86
+ allowMultiple: v.optional(v.boolean()),
87
+ }));
88
+ /**
89
+ * Field definition validator for exported content types.
90
+ * Reuses fieldTypeValidator from schema but uses exportedFieldOptionsValidator
91
+ * which has string taxonomyId for portability across deployments.
92
+ */
93
+ const exportedFieldDefinitionValidator = v.object({
94
+ name: v.string(),
95
+ label: v.string(),
96
+ type: fieldTypeValidator,
97
+ required: v.boolean(),
98
+ searchable: v.optional(v.boolean()),
99
+ localized: v.optional(v.boolean()),
100
+ description: v.optional(v.string()),
101
+ defaultValue: v.optional(v.any()),
102
+ options: exportedFieldOptionsValidator,
103
+ });
104
+ /**
105
+ * Structure for a single exported content entry.
106
+ * Includes all data needed to recreate the entry on import.
107
+ */
108
+ export const exportedEntryValidator = v.object({
109
+ /** Original entry ID (for reference mapping) */
110
+ _originalId: v.string(),
111
+ /** Content type name (machine-readable) */
112
+ contentTypeName: v.string(),
113
+ /** URL-friendly slug */
114
+ slug: v.string(),
115
+ /** Entry status at time of export */
116
+ status: contentStatusValidator,
117
+ /** Content data */
118
+ data: v.any(),
119
+ /** Locale code if localized */
120
+ locale: v.optional(v.string()),
121
+ /** Version number at time of export */
122
+ version: v.number(),
123
+ /** First published timestamp */
124
+ firstPublishedAt: v.optional(v.number()),
125
+ /** Last published timestamp */
126
+ lastPublishedAt: v.optional(v.number()),
127
+ /** Scheduled publish timestamp */
128
+ scheduledPublishAt: v.optional(v.number()),
129
+ /** User who created the entry */
130
+ createdBy: v.optional(v.string()),
131
+ /** Original creation timestamp */
132
+ createdAt: v.number(),
133
+ });
134
+ /**
135
+ * Structure for an exported content type definition.
136
+ * Allows importing schemas along with content.
137
+ * Uses exportedFieldDefinitionValidator which has string taxonomyId
138
+ * for portability across Convex deployments.
139
+ */
140
+ export const exportedContentTypeValidator = v.object({
141
+ /** Content type name (machine-readable) */
142
+ name: v.string(),
143
+ /** Display name */
144
+ displayName: v.string(),
145
+ /** Description */
146
+ description: v.optional(v.string()),
147
+ /** Field definitions with portable types */
148
+ fields: v.array(exportedFieldDefinitionValidator),
149
+ /** Icon identifier */
150
+ icon: v.optional(v.string()),
151
+ /** Whether this is a singleton type */
152
+ singleton: v.optional(v.boolean()),
153
+ /** Field to generate slugs from */
154
+ slugField: v.optional(v.string()),
155
+ /** Field to use for display titles */
156
+ titleField: v.optional(v.string()),
157
+ });
158
+ /**
159
+ * Complete export package structure.
160
+ * Contains all information needed to import content into another instance.
161
+ */
162
+ export const exportPackageValidator = v.object({
163
+ /** Export format version for compatibility checking */
164
+ version: v.literal("1.0"),
165
+ /** Timestamp when export was created */
166
+ exportedAt: v.number(),
167
+ /** Content type definitions (optional, for schema validation) */
168
+ contentTypes: v.optional(v.array(exportedContentTypeValidator)),
169
+ /** Exported entries */
170
+ entries: v.array(exportedEntryValidator),
171
+ /** Metadata about the export */
172
+ metadata: v.optional(v.object({
173
+ /** Source system identifier */
174
+ source: v.optional(v.string()),
175
+ /** Export description */
176
+ description: v.optional(v.string()),
177
+ /** Total count of entries */
178
+ totalEntries: v.number(),
179
+ /** Breakdown by content type */
180
+ entriesByType: v.optional(v.any()),
181
+ })),
182
+ });
183
+ // =============================================================================
184
+ // Import Types and Validators
185
+ // =============================================================================
186
+ /**
187
+ * Conflict resolution strategy for imports.
188
+ * - "skip": Skip entries with conflicting slugs
189
+ * - "update": Update existing entries with new data
190
+ * - "error": Fail the entire import if any conflicts exist
191
+ */
192
+ export const conflictStrategyValidator = v.union(v.literal("skip"), v.literal("update"), v.literal("error"));
193
+ /**
194
+ * Result for a single imported entry.
195
+ */
196
+ export const importEntryResultValidator = v.object({
197
+ /** Original ID from export */
198
+ originalId: v.string(),
199
+ /** New ID after import (if created/updated) */
200
+ newId: v.optional(v.id("contentEntries")),
201
+ /** Import action taken */
202
+ action: v.union(v.literal("created"), v.literal("updated"), v.literal("skipped"), v.literal("failed")),
203
+ /** Error message if failed */
204
+ error: v.optional(v.string()),
205
+ /** Slug of the entry */
206
+ slug: v.string(),
207
+ /** Content type name */
208
+ contentTypeName: v.string(),
209
+ });
210
+ /**
211
+ * Complete import result.
212
+ */
213
+ export const importResultValidator = v.object({
214
+ /** Whether import was successful */
215
+ success: v.boolean(),
216
+ /** Total entries processed */
217
+ totalProcessed: v.number(),
218
+ /** Number of entries created */
219
+ created: v.number(),
220
+ /** Number of entries updated */
221
+ updated: v.number(),
222
+ /** Number of entries skipped */
223
+ skipped: v.number(),
224
+ /** Number of entries failed */
225
+ failed: v.number(),
226
+ /** Detailed results for each entry */
227
+ results: v.array(importEntryResultValidator),
228
+ /** ID mapping from old to new IDs (for reference updates) */
229
+ idMapping: v.any(),
230
+ /** Validation errors encountered */
231
+ validationErrors: v.optional(v.array(v.string())),
232
+ });
233
+ // =============================================================================
234
+ // Export Function
235
+ // =============================================================================
236
+ /**
237
+ * Arguments for the export function.
238
+ */
239
+ const exportEntriesArgs = v.object({
240
+ /** Filter by content type ID */
241
+ contentTypeId: v.optional(v.id("contentTypes")),
242
+ /** Filter by content type name (alternative to contentTypeId) */
243
+ contentTypeName: v.optional(v.string()),
244
+ /** Filter by status */
245
+ status: v.optional(contentStatusValidator),
246
+ /** Filter by multiple statuses */
247
+ statusIn: v.optional(v.array(contentStatusValidator)),
248
+ /** Filter by locale */
249
+ locale: v.optional(v.string()),
250
+ /** Include content type definitions in export */
251
+ includeContentTypes: v.optional(v.boolean()),
252
+ /** Include soft-deleted entries */
253
+ includeDeleted: v.optional(v.boolean()),
254
+ /** Maximum number of entries to export (default: 1000) */
255
+ limit: v.optional(v.number()),
256
+ /** Export description for metadata */
257
+ description: v.optional(v.string()),
258
+ /** Source identifier for metadata */
259
+ source: v.optional(v.string()),
260
+ });
261
+ /**
262
+ * Export content entries to a JSON-serializable package.
263
+ *
264
+ * This query retrieves content entries matching the specified filters and
265
+ * packages them into a format suitable for import into another system.
266
+ *
267
+ * ## Features
268
+ * - Filter by content type, status, or locale
269
+ * - Optionally include content type definitions for schema validation
270
+ * - Preserves original IDs for reference mapping during import
271
+ * - Includes metadata about the export for traceability
272
+ *
273
+ * @param contentTypeId - Filter by content type ID
274
+ * @param contentTypeName - Filter by content type name (alternative to ID)
275
+ * @param status - Filter by single status
276
+ * @param statusIn - Filter by multiple statuses
277
+ * @param locale - Filter by locale code
278
+ * @param includeContentTypes - Include content type definitions (default: true)
279
+ * @param includeDeleted - Include soft-deleted entries (default: false)
280
+ * @param limit - Maximum entries to export (default: 1000)
281
+ * @param description - Description for export metadata
282
+ * @param source - Source identifier for export metadata
283
+ *
284
+ * @returns ExportPackage containing entries and optional content types
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * // Export all published blog posts
289
+ * const exportData = await ctx.runQuery(api.exportImport.exportEntries, {
290
+ * contentTypeName: "blog_post",
291
+ * status: "published",
292
+ * includeContentTypes: true,
293
+ * });
294
+ *
295
+ * // Export all entries of all types
296
+ * const allData = await ctx.runQuery(api.exportImport.exportEntries, {
297
+ * limit: 5000,
298
+ * description: "Full site backup",
299
+ * });
300
+ * ```
301
+ */
302
+ export const exportEntries = query({
303
+ args: exportEntriesArgs.fields,
304
+ returns: exportPackageValidator,
305
+ handler: async (ctx, args) => {
306
+ const { contentTypeId, contentTypeName, status, statusIn, locale, includeContentTypes = true, includeDeleted = false, limit = 1000, description, source, } = args;
307
+ // Resolve status filter
308
+ const resolvedStatuses = statusIn?.length
309
+ ? statusIn
310
+ : status
311
+ ? [status]
312
+ : undefined;
313
+ // Resolve content type ID from name if needed
314
+ let resolvedContentTypeId = contentTypeId;
315
+ if (!resolvedContentTypeId && contentTypeName) {
316
+ const contentType = await ctx.db
317
+ .query("contentTypes")
318
+ .withIndex("by_name", (q) => q.eq("name", contentTypeName))
319
+ .first();
320
+ if (contentType) {
321
+ resolvedContentTypeId = contentType._id;
322
+ }
323
+ }
324
+ // Build query for entries
325
+ let entriesQuery;
326
+ if (resolvedContentTypeId) {
327
+ entriesQuery = ctx.db
328
+ .query("contentEntries")
329
+ .withIndex("by_content_type", (q) => q.eq("contentTypeId", resolvedContentTypeId));
330
+ }
331
+ else {
332
+ entriesQuery = ctx.db.query("contentEntries");
333
+ }
334
+ // Fetch entries (we'll filter in memory for complex conditions)
335
+ const allEntries = await entriesQuery.take(limit * 2);
336
+ // Apply filters
337
+ let filteredEntries = allEntries;
338
+ // Filter by deleted status
339
+ if (!includeDeleted) {
340
+ filteredEntries = filteredEntries.filter((e) => !isDeleted(e));
341
+ }
342
+ // Filter by status
343
+ if (resolvedStatuses && resolvedStatuses.length > 0) {
344
+ filteredEntries = filteredEntries.filter((e) => resolvedStatuses.includes(e.status));
345
+ }
346
+ // Filter by locale
347
+ if (locale) {
348
+ filteredEntries = filteredEntries.filter((e) => e.locale === locale);
349
+ }
350
+ // Limit results
351
+ filteredEntries = filteredEntries.slice(0, limit);
352
+ // Get unique content type IDs from entries
353
+ const contentTypeIdsSet = new Set();
354
+ for (const entry of filteredEntries) {
355
+ contentTypeIdsSet.add(entry.contentTypeId);
356
+ }
357
+ const contentTypeIds = Array.from(contentTypeIdsSet);
358
+ // Fetch content types
359
+ const contentTypesMap = new Map();
360
+ for (const typeId of contentTypeIds) {
361
+ const contentType = await ctx.db.get(typeId);
362
+ if (contentType) {
363
+ contentTypesMap.set(typeId, contentType);
364
+ }
365
+ }
366
+ // Build exported entries
367
+ const exportedEntries = filteredEntries.map((entry) => {
368
+ const contentType = contentTypesMap.get(entry.contentTypeId);
369
+ return {
370
+ _originalId: entry._id,
371
+ contentTypeName: contentType?.name ?? "unknown",
372
+ slug: entry.slug,
373
+ status: entry.status,
374
+ data: entry.data,
375
+ locale: entry.locale,
376
+ version: entry.version,
377
+ firstPublishedAt: entry.firstPublishedAt,
378
+ lastPublishedAt: entry.lastPublishedAt,
379
+ scheduledPublishAt: entry.scheduledPublishAt,
380
+ createdBy: entry.createdBy,
381
+ createdAt: entry._creationTime,
382
+ };
383
+ });
384
+ // Build exported content types if requested
385
+ let exportedContentTypes;
386
+ if (includeContentTypes) {
387
+ exportedContentTypes = Array.from(contentTypesMap.values())
388
+ .filter((ct) => !ct.deletedAt)
389
+ .map((ct) => ({
390
+ name: ct.name,
391
+ displayName: ct.displayName,
392
+ description: ct.description,
393
+ fields: ct.fields,
394
+ icon: ct.icon,
395
+ singleton: ct.singleton,
396
+ slugField: ct.slugField,
397
+ titleField: ct.titleField,
398
+ }));
399
+ }
400
+ // Build entries by type count
401
+ const entriesByType = {};
402
+ for (const entry of exportedEntries) {
403
+ entriesByType[entry.contentTypeName] =
404
+ (entriesByType[entry.contentTypeName] ?? 0) + 1;
405
+ }
406
+ return {
407
+ version: "1.0",
408
+ exportedAt: Date.now(),
409
+ contentTypes: exportedContentTypes,
410
+ entries: exportedEntries,
411
+ metadata: {
412
+ source,
413
+ description,
414
+ totalEntries: exportedEntries.length,
415
+ entriesByType,
416
+ },
417
+ };
418
+ },
419
+ });
420
+ // =============================================================================
421
+ // Import Function
422
+ // =============================================================================
423
+ /**
424
+ * Arguments for the import function.
425
+ */
426
+ const importEntriesArgs = v.object({
427
+ /** The export package to import */
428
+ data: exportPackageValidator,
429
+ /** How to handle conflicting slugs */
430
+ onConflict: v.optional(conflictStrategyValidator),
431
+ /** Whether to preserve original status or set all to draft */
432
+ preserveStatus: v.optional(v.boolean()),
433
+ /** Whether to run validation only without making changes */
434
+ dryRun: v.optional(v.boolean()),
435
+ /** User ID for audit trail */
436
+ importedBy: v.optional(v.string()),
437
+ /** Filter which content types to import (by name) */
438
+ contentTypeFilter: v.optional(v.array(v.string())),
439
+ });
440
+ /**
441
+ * Import content entries from an export package.
442
+ *
443
+ * This mutation validates and imports content entries from an export package.
444
+ * It supports conflict resolution, dry-run mode, and reference ID mapping.
445
+ *
446
+ * ## Features
447
+ * - Validates all entries against content type schemas before import
448
+ * - Maps old reference IDs to new IDs for reference fields
449
+ * - Supports skip, update, or error on slug conflicts
450
+ * - Dry-run mode validates without making changes
451
+ * - Preserves or resets entry status
452
+ *
453
+ * ## Import Process
454
+ * 1. Validate all entries against existing content type schemas
455
+ * 2. Check for slug conflicts based on onConflict strategy
456
+ * 3. Create or update entries, collecting ID mappings
457
+ * 4. Update reference fields with new IDs (second pass)
458
+ *
459
+ * @param data - The export package to import
460
+ * @param onConflict - How to handle slug conflicts (default: "skip")
461
+ * @param preserveStatus - Keep original status or set to draft (default: false)
462
+ * @param dryRun - Validate only without making changes (default: false)
463
+ * @param importedBy - User ID for audit trail
464
+ * @param contentTypeFilter - Only import entries of these content types
465
+ *
466
+ * @returns ImportResult with details of the import operation
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * // Dry run to validate import
471
+ * const validation = await ctx.runMutation(api.exportImport.importEntries, {
472
+ * data: exportPackage,
473
+ * dryRun: true,
474
+ * });
475
+ *
476
+ * // Import with skip on conflicts
477
+ * const result = await ctx.runMutation(api.exportImport.importEntries, {
478
+ * data: exportPackage,
479
+ * onConflict: "skip",
480
+ * preserveStatus: true,
481
+ * importedBy: currentUserId,
482
+ * });
483
+ * ```
484
+ */
485
+ export const importEntries = mutation({
486
+ args: importEntriesArgs.fields,
487
+ returns: importResultValidator,
488
+ handler: async (ctx, args) => {
489
+ const { data, onConflict = "skip", preserveStatus = false, dryRun = false, importedBy, contentTypeFilter, } = args;
490
+ const results = [];
491
+ const idMapping = {};
492
+ const validationErrors = [];
493
+ let created = 0;
494
+ let updated = 0;
495
+ let skipped = 0;
496
+ let failed = 0;
497
+ // Filter entries by content type if specified
498
+ let entriesToImport = data.entries;
499
+ if (contentTypeFilter && contentTypeFilter.length > 0) {
500
+ entriesToImport = entriesToImport.filter((e) => contentTypeFilter.includes(e.contentTypeName));
501
+ }
502
+ // Build a map of content type name to content type document
503
+ const contentTypeMap = new Map();
504
+ const contentTypeNamesSet = new Set();
505
+ for (const entry of entriesToImport) {
506
+ contentTypeNamesSet.add(entry.contentTypeName);
507
+ }
508
+ const contentTypeNames = Array.from(contentTypeNamesSet);
509
+ for (const typeName of contentTypeNames) {
510
+ const contentType = await ctx.db
511
+ .query("contentTypes")
512
+ .withIndex("by_name", (q) => q.eq("name", typeName))
513
+ .first();
514
+ if (contentType && !contentType.deletedAt && contentType.isActive) {
515
+ contentTypeMap.set(typeName, contentType);
516
+ }
517
+ else {
518
+ validationErrors.push(`Content type "${typeName}" not found or not active`);
519
+ }
520
+ }
521
+ // Validate all entries first
522
+ for (const entry of entriesToImport) {
523
+ const contentType = contentTypeMap.get(entry.contentTypeName);
524
+ if (!contentType) {
525
+ results.push({
526
+ originalId: entry._originalId,
527
+ action: "failed",
528
+ error: `Content type "${entry.contentTypeName}" not found`,
529
+ slug: entry.slug,
530
+ contentTypeName: entry.contentTypeName,
531
+ });
532
+ failed++;
533
+ continue;
534
+ }
535
+ // Build schema for validation
536
+ const schema = {
537
+ name: contentType.name,
538
+ displayName: contentType.displayName,
539
+ description: contentType.description,
540
+ fields: contentType.fields,
541
+ titleField: contentType.titleField,
542
+ slugField: contentType.slugField,
543
+ singleton: contentType.singleton,
544
+ };
545
+ // Validate content data
546
+ const validationResult = validateContentData(entry.data, schema);
547
+ if (!validationResult.valid) {
548
+ const errorMessages = validationResult.errors
549
+ .map((e) => `${e.field}: ${e.message}`)
550
+ .join("; ");
551
+ validationErrors.push(`Entry "${entry.slug}" (${entry.contentTypeName}): ${errorMessages}`);
552
+ results.push({
553
+ originalId: entry._originalId,
554
+ action: "failed",
555
+ error: `Validation failed: ${errorMessages}`,
556
+ slug: entry.slug,
557
+ contentTypeName: entry.contentTypeName,
558
+ });
559
+ failed++;
560
+ continue;
561
+ }
562
+ // Check for existing entry with same slug
563
+ const existingEntry = await ctx.db
564
+ .query("contentEntries")
565
+ .withIndex("by_content_type_and_slug", (q) => q.eq("contentTypeId", contentType._id).eq("slug", entry.slug))
566
+ .filter((q) => q.eq(q.field("deletedAt"), undefined))
567
+ .first();
568
+ if (existingEntry) {
569
+ // Handle conflict based on strategy
570
+ switch (onConflict) {
571
+ case "error":
572
+ validationErrors.push(`Slug conflict: "${entry.slug}" already exists for type "${entry.contentTypeName}"`);
573
+ results.push({
574
+ originalId: entry._originalId,
575
+ action: "failed",
576
+ error: `Slug "${entry.slug}" already exists`,
577
+ slug: entry.slug,
578
+ contentTypeName: entry.contentTypeName,
579
+ });
580
+ failed++;
581
+ continue;
582
+ case "skip":
583
+ results.push({
584
+ originalId: entry._originalId,
585
+ newId: existingEntry._id,
586
+ action: "skipped",
587
+ slug: entry.slug,
588
+ contentTypeName: entry.contentTypeName,
589
+ });
590
+ idMapping[entry._originalId] = existingEntry._id;
591
+ skipped++;
592
+ continue;
593
+ case "update":
594
+ if (!dryRun) {
595
+ // Update existing entry
596
+ const status = preserveStatus
597
+ ? entry.status
598
+ : existingEntry.status;
599
+ await ctx.db.patch(existingEntry._id, {
600
+ data: entry.data,
601
+ status,
602
+ version: existingEntry.version + 1,
603
+ updatedBy: importedBy,
604
+ });
605
+ }
606
+ results.push({
607
+ originalId: entry._originalId,
608
+ newId: existingEntry._id,
609
+ action: "updated",
610
+ slug: entry.slug,
611
+ contentTypeName: entry.contentTypeName,
612
+ });
613
+ idMapping[entry._originalId] = existingEntry._id;
614
+ updated++;
615
+ continue;
616
+ }
617
+ }
618
+ // Create new entry
619
+ if (!dryRun) {
620
+ // Generate search text from searchable fields
621
+ let searchText = "";
622
+ for (const field of contentType.fields) {
623
+ const fieldData = entry.data;
624
+ if (field.searchable && fieldData[field.name]) {
625
+ const value = fieldData[field.name];
626
+ if (typeof value === "string") {
627
+ searchText += ` ${value}`;
628
+ }
629
+ }
630
+ }
631
+ // Ensure unique slug
632
+ const queryFn = async (candidateSlug) => {
633
+ return await ctx.db
634
+ .query("contentEntries")
635
+ .withIndex("by_content_type_and_slug", (q) => q.eq("contentTypeId", contentType._id).eq("slug", candidateSlug))
636
+ .filter((q) => q.eq(q.field("deletedAt"), undefined))
637
+ .first();
638
+ };
639
+ const uniqueSlug = await ensureUniqueSlug(entry.slug, queryFn);
640
+ const newEntryId = await ctx.db.insert("contentEntries", {
641
+ contentTypeId: contentType._id,
642
+ slug: uniqueSlug,
643
+ status: preserveStatus ? entry.status : "draft",
644
+ data: entry.data,
645
+ locale: entry.locale,
646
+ version: 1,
647
+ createdBy: importedBy ?? entry.createdBy,
648
+ updatedBy: importedBy ?? entry.createdBy,
649
+ searchText: searchText.trim() || undefined,
650
+ // Only preserve publication timestamps if preserving status
651
+ firstPublishedAt: preserveStatus ? entry.firstPublishedAt : undefined,
652
+ lastPublishedAt: preserveStatus ? entry.lastPublishedAt : undefined,
653
+ scheduledPublishAt: preserveStatus
654
+ ? entry.scheduledPublishAt
655
+ : undefined,
656
+ });
657
+ results.push({
658
+ originalId: entry._originalId,
659
+ newId: newEntryId,
660
+ action: "created",
661
+ slug: uniqueSlug,
662
+ contentTypeName: entry.contentTypeName,
663
+ });
664
+ idMapping[entry._originalId] = newEntryId;
665
+ created++;
666
+ }
667
+ else {
668
+ // Dry run - simulate creation
669
+ results.push({
670
+ originalId: entry._originalId,
671
+ action: "created",
672
+ slug: entry.slug,
673
+ contentTypeName: entry.contentTypeName,
674
+ });
675
+ created++;
676
+ }
677
+ }
678
+ // Second pass: Update reference fields with new IDs
679
+ if (!dryRun && Object.keys(idMapping).length > 0) {
680
+ for (const result of results) {
681
+ if ((result.action === "created" || result.action === "updated") &&
682
+ result.newId) {
683
+ const entry = await ctx.db.get(result.newId);
684
+ if (!entry)
685
+ continue;
686
+ const contentType = contentTypeMap.get(result.contentTypeName);
687
+ if (!contentType)
688
+ continue;
689
+ const entryData = entry.data;
690
+ let dataChanged = false;
691
+ const updatedData = { ...entryData };
692
+ // Find reference fields and update IDs
693
+ for (const field of contentType.fields) {
694
+ if (field.type === "reference") {
695
+ const value = entryData[field.name];
696
+ if (field.options?.multiple && Array.isArray(value)) {
697
+ const newRefs = value.map((refId) => idMapping[refId] ?? refId);
698
+ if (JSON.stringify(newRefs) !== JSON.stringify(value)) {
699
+ updatedData[field.name] = newRefs;
700
+ dataChanged = true;
701
+ }
702
+ }
703
+ else if (typeof value === "string" && idMapping[value]) {
704
+ updatedData[field.name] = idMapping[value];
705
+ dataChanged = true;
706
+ }
707
+ }
708
+ }
709
+ if (dataChanged) {
710
+ await ctx.db.patch(result.newId, {
711
+ data: updatedData,
712
+ });
713
+ }
714
+ }
715
+ }
716
+ }
717
+ const success = failed === 0 &&
718
+ validationErrors.filter((e) => !e.includes("Slug conflict")).length === 0;
719
+ return {
720
+ success,
721
+ totalProcessed: entriesToImport.length,
722
+ created,
723
+ updated,
724
+ skipped,
725
+ failed,
726
+ results,
727
+ idMapping,
728
+ validationErrors: validationErrors.length > 0 ? validationErrors : undefined,
729
+ };
730
+ },
731
+ });
732
+ // =============================================================================
733
+ // Utility Functions
734
+ // =============================================================================
735
+ /**
736
+ * Get a summary of content that would be exported without actually exporting.
737
+ *
738
+ * This is useful for previewing what an export would contain before
739
+ * running the full export operation.
740
+ */
741
+ export const getExportPreview = query({
742
+ args: {
743
+ contentTypeId: v.optional(v.id("contentTypes")),
744
+ contentTypeName: v.optional(v.string()),
745
+ status: v.optional(contentStatusValidator),
746
+ statusIn: v.optional(v.array(contentStatusValidator)),
747
+ locale: v.optional(v.string()),
748
+ includeDeleted: v.optional(v.boolean()),
749
+ },
750
+ returns: v.object({
751
+ totalEntries: v.number(),
752
+ entriesByType: v.any(),
753
+ entriesByStatus: v.any(),
754
+ contentTypes: v.array(v.string()),
755
+ }),
756
+ handler: async (ctx, args) => {
757
+ const { contentTypeId, contentTypeName, status, statusIn, locale, includeDeleted = false, } = args;
758
+ // Resolve status filter
759
+ const resolvedStatuses = statusIn?.length
760
+ ? statusIn
761
+ : status
762
+ ? [status]
763
+ : undefined;
764
+ // Resolve content type ID
765
+ let resolvedContentTypeId = contentTypeId;
766
+ if (!resolvedContentTypeId && contentTypeName) {
767
+ const contentType = await ctx.db
768
+ .query("contentTypes")
769
+ .withIndex("by_name", (q) => q.eq("name", contentTypeName))
770
+ .first();
771
+ if (contentType) {
772
+ resolvedContentTypeId = contentType._id;
773
+ }
774
+ }
775
+ // Build query
776
+ let entriesQuery;
777
+ if (resolvedContentTypeId) {
778
+ entriesQuery = ctx.db
779
+ .query("contentEntries")
780
+ .withIndex("by_content_type", (q) => q.eq("contentTypeId", resolvedContentTypeId));
781
+ }
782
+ else {
783
+ entriesQuery = ctx.db.query("contentEntries");
784
+ }
785
+ // Fetch all matching entries
786
+ const allEntries = await entriesQuery.collect();
787
+ // Apply filters
788
+ let filteredEntries = allEntries;
789
+ if (!includeDeleted) {
790
+ filteredEntries = filteredEntries.filter((e) => !isDeleted(e));
791
+ }
792
+ if (resolvedStatuses && resolvedStatuses.length > 0) {
793
+ filteredEntries = filteredEntries.filter((e) => resolvedStatuses.includes(e.status));
794
+ }
795
+ if (locale) {
796
+ filteredEntries = filteredEntries.filter((e) => e.locale === locale);
797
+ }
798
+ // Get content types
799
+ const contentTypeIdsSet = new Set();
800
+ for (const entry of filteredEntries) {
801
+ contentTypeIdsSet.add(entry.contentTypeId);
802
+ }
803
+ const contentTypeIds = Array.from(contentTypeIdsSet);
804
+ const contentTypeNames = [];
805
+ const contentTypeNameMap = new Map();
806
+ for (const typeId of contentTypeIds) {
807
+ const contentType = await ctx.db.get(typeId);
808
+ if (contentType) {
809
+ contentTypeNames.push(contentType.name);
810
+ contentTypeNameMap.set(typeId, contentType.name);
811
+ }
812
+ }
813
+ // Count by type
814
+ const entriesByType = {};
815
+ for (const entry of filteredEntries) {
816
+ const typeName = contentTypeNameMap.get(entry.contentTypeId) ?? "unknown";
817
+ entriesByType[typeName] = (entriesByType[typeName] ?? 0) + 1;
818
+ }
819
+ // Count by status
820
+ const entriesByStatus = {};
821
+ for (const entry of filteredEntries) {
822
+ entriesByStatus[entry.status] = (entriesByStatus[entry.status] ?? 0) + 1;
823
+ }
824
+ return {
825
+ totalEntries: filteredEntries.length,
826
+ entriesByType,
827
+ entriesByStatus,
828
+ contentTypes: contentTypeNames,
829
+ };
830
+ },
831
+ });
832
+ /**
833
+ * Validate an export package without importing.
834
+ *
835
+ * Checks that all entries can be validated against existing content type
836
+ * schemas and reports any issues that would occur during import.
837
+ */
838
+ export const validateImportPackage = query({
839
+ args: {
840
+ data: exportPackageValidator,
841
+ contentTypeFilter: v.optional(v.array(v.string())),
842
+ },
843
+ returns: v.object({
844
+ valid: v.boolean(),
845
+ totalEntries: v.number(),
846
+ validEntries: v.number(),
847
+ invalidEntries: v.number(),
848
+ missingContentTypes: v.array(v.string()),
849
+ validationErrors: v.array(v.object({
850
+ slug: v.string(),
851
+ contentTypeName: v.string(),
852
+ errors: v.array(v.string()),
853
+ })),
854
+ }),
855
+ handler: async (ctx, args) => {
856
+ const { data, contentTypeFilter } = args;
857
+ const missingContentTypes = [];
858
+ const validationErrors = [];
859
+ // Filter entries
860
+ let entriesToValidate = data.entries;
861
+ if (contentTypeFilter && contentTypeFilter.length > 0) {
862
+ entriesToValidate = entriesToValidate.filter((e) => contentTypeFilter.includes(e.contentTypeName));
863
+ }
864
+ // Build content type map
865
+ const contentTypeMap = new Map();
866
+ const contentTypeNamesSet = new Set();
867
+ for (const entry of entriesToValidate) {
868
+ contentTypeNamesSet.add(entry.contentTypeName);
869
+ }
870
+ const contentTypeNames = Array.from(contentTypeNamesSet);
871
+ for (const typeName of contentTypeNames) {
872
+ const contentType = await ctx.db
873
+ .query("contentTypes")
874
+ .withIndex("by_name", (q) => q.eq("name", typeName))
875
+ .first();
876
+ if (contentType && !contentType.deletedAt && contentType.isActive) {
877
+ contentTypeMap.set(typeName, contentType);
878
+ }
879
+ else {
880
+ missingContentTypes.push(typeName);
881
+ }
882
+ }
883
+ let validEntries = 0;
884
+ let invalidEntries = 0;
885
+ // Validate each entry
886
+ for (const entry of entriesToValidate) {
887
+ const contentType = contentTypeMap.get(entry.contentTypeName);
888
+ if (!contentType) {
889
+ invalidEntries++;
890
+ validationErrors.push({
891
+ slug: entry.slug,
892
+ contentTypeName: entry.contentTypeName,
893
+ errors: [`Content type "${entry.contentTypeName}" not found`],
894
+ });
895
+ continue;
896
+ }
897
+ // Build schema
898
+ const schema = {
899
+ name: contentType.name,
900
+ displayName: contentType.displayName,
901
+ description: contentType.description,
902
+ fields: contentType.fields,
903
+ titleField: contentType.titleField,
904
+ slugField: contentType.slugField,
905
+ singleton: contentType.singleton,
906
+ };
907
+ // Validate
908
+ const result = validateContentData(entry.data, schema);
909
+ if (result.valid) {
910
+ validEntries++;
911
+ }
912
+ else {
913
+ invalidEntries++;
914
+ validationErrors.push({
915
+ slug: entry.slug,
916
+ contentTypeName: entry.contentTypeName,
917
+ errors: result.errors.map((e) => `${e.field}: ${e.message}`),
918
+ });
919
+ }
920
+ }
921
+ return {
922
+ valid: invalidEntries === 0 && missingContentTypes.length === 0,
923
+ totalEntries: entriesToValidate.length,
924
+ validEntries,
925
+ invalidEntries,
926
+ missingContentTypes,
927
+ validationErrors,
928
+ };
929
+ },
930
+ });
931
+ //# sourceMappingURL=exportImport.js.map