@htmless/core 0.1.0

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 (435) hide show
  1. package/dist/ai/content-operations.d.ts +26 -0
  2. package/dist/ai/content-operations.d.ts.map +1 -0
  3. package/dist/ai/content-operations.js +75 -0
  4. package/dist/ai/content-operations.js.map +1 -0
  5. package/dist/ai/image-analyzer.d.ts +18 -0
  6. package/dist/ai/image-analyzer.d.ts.map +1 -0
  7. package/dist/ai/image-analyzer.js +457 -0
  8. package/dist/ai/image-analyzer.js.map +1 -0
  9. package/dist/ai/json-to-schema.d.ts +6 -0
  10. package/dist/ai/json-to-schema.d.ts.map +1 -0
  11. package/dist/ai/json-to-schema.js +155 -0
  12. package/dist/ai/json-to-schema.js.map +1 -0
  13. package/dist/ai/schema-generator.d.ts +34 -0
  14. package/dist/ai/schema-generator.d.ts.map +1 -0
  15. package/dist/ai/schema-generator.js +307 -0
  16. package/dist/ai/schema-generator.js.map +1 -0
  17. package/dist/api/cda/assets.d.ts +4 -0
  18. package/dist/api/cda/assets.d.ts.map +1 -0
  19. package/dist/api/cda/assets.js +82 -0
  20. package/dist/api/cda/assets.js.map +1 -0
  21. package/dist/api/cda/blocks.d.ts +4 -0
  22. package/dist/api/cda/blocks.d.ts.map +1 -0
  23. package/dist/api/cda/blocks.js +92 -0
  24. package/dist/api/cda/blocks.js.map +1 -0
  25. package/dist/api/cda/content.d.ts +4 -0
  26. package/dist/api/cda/content.d.ts.map +1 -0
  27. package/dist/api/cda/content.js +192 -0
  28. package/dist/api/cda/content.js.map +1 -0
  29. package/dist/api/cda/index.d.ts +4 -0
  30. package/dist/api/cda/index.d.ts.map +1 -0
  31. package/dist/api/cda/index.js +21 -0
  32. package/dist/api/cda/index.js.map +1 -0
  33. package/dist/api/cda/media.d.ts +4 -0
  34. package/dist/api/cda/media.d.ts.map +1 -0
  35. package/dist/api/cda/media.js +114 -0
  36. package/dist/api/cda/media.js.map +1 -0
  37. package/dist/api/cda/redirects.d.ts +4 -0
  38. package/dist/api/cda/redirects.d.ts.map +1 -0
  39. package/dist/api/cda/redirects.js +69 -0
  40. package/dist/api/cda/redirects.js.map +1 -0
  41. package/dist/api/cda/schemas.d.ts +4 -0
  42. package/dist/api/cda/schemas.d.ts.map +1 -0
  43. package/dist/api/cda/schemas.js +113 -0
  44. package/dist/api/cda/schemas.js.map +1 -0
  45. package/dist/api/cda/taxonomies.d.ts +4 -0
  46. package/dist/api/cda/taxonomies.d.ts.map +1 -0
  47. package/dist/api/cda/taxonomies.js +79 -0
  48. package/dist/api/cda/taxonomies.js.map +1 -0
  49. package/dist/api/cma/ai.d.ts +4 -0
  50. package/dist/api/cma/ai.d.ts.map +1 -0
  51. package/dist/api/cma/ai.js +458 -0
  52. package/dist/api/cma/ai.js.map +1 -0
  53. package/dist/api/cma/assets.d.ts +4 -0
  54. package/dist/api/cma/assets.d.ts.map +1 -0
  55. package/dist/api/cma/assets.js +166 -0
  56. package/dist/api/cma/assets.js.map +1 -0
  57. package/dist/api/cma/audit.d.ts +4 -0
  58. package/dist/api/cma/audit.d.ts.map +1 -0
  59. package/dist/api/cma/audit.js +74 -0
  60. package/dist/api/cma/audit.js.map +1 -0
  61. package/dist/api/cma/auth.d.ts +4 -0
  62. package/dist/api/cma/auth.d.ts.map +1 -0
  63. package/dist/api/cma/auth.js +123 -0
  64. package/dist/api/cma/auth.js.map +1 -0
  65. package/dist/api/cma/blocks.d.ts +4 -0
  66. package/dist/api/cma/blocks.d.ts.map +1 -0
  67. package/dist/api/cma/blocks.js +249 -0
  68. package/dist/api/cma/blocks.js.map +1 -0
  69. package/dist/api/cma/bulk.d.ts +4 -0
  70. package/dist/api/cma/bulk.d.ts.map +1 -0
  71. package/dist/api/cma/bulk.js +258 -0
  72. package/dist/api/cma/bulk.js.map +1 -0
  73. package/dist/api/cma/cache.d.ts +4 -0
  74. package/dist/api/cma/cache.d.ts.map +1 -0
  75. package/dist/api/cma/cache.js +99 -0
  76. package/dist/api/cma/cache.js.map +1 -0
  77. package/dist/api/cma/codegen.d.ts +4 -0
  78. package/dist/api/cma/codegen.d.ts.map +1 -0
  79. package/dist/api/cma/codegen.js +22 -0
  80. package/dist/api/cma/codegen.js.map +1 -0
  81. package/dist/api/cma/comments.d.ts +4 -0
  82. package/dist/api/cma/comments.d.ts.map +1 -0
  83. package/dist/api/cma/comments.js +164 -0
  84. package/dist/api/cma/comments.js.map +1 -0
  85. package/dist/api/cma/diff.d.ts +4 -0
  86. package/dist/api/cma/diff.d.ts.map +1 -0
  87. package/dist/api/cma/diff.js +88 -0
  88. package/dist/api/cma/diff.js.map +1 -0
  89. package/dist/api/cma/entries.d.ts +4 -0
  90. package/dist/api/cma/entries.d.ts.map +1 -0
  91. package/dist/api/cma/entries.js +736 -0
  92. package/dist/api/cma/entries.js.map +1 -0
  93. package/dist/api/cma/environments.d.ts +4 -0
  94. package/dist/api/cma/environments.d.ts.map +1 -0
  95. package/dist/api/cma/environments.js +160 -0
  96. package/dist/api/cma/environments.js.map +1 -0
  97. package/dist/api/cma/extensions.d.ts +4 -0
  98. package/dist/api/cma/extensions.d.ts.map +1 -0
  99. package/dist/api/cma/extensions.js +82 -0
  100. package/dist/api/cma/extensions.js.map +1 -0
  101. package/dist/api/cma/importexport.d.ts +4 -0
  102. package/dist/api/cma/importexport.d.ts.map +1 -0
  103. package/dist/api/cma/importexport.js +496 -0
  104. package/dist/api/cma/importexport.js.map +1 -0
  105. package/dist/api/cma/index.d.ts +4 -0
  106. package/dist/api/cma/index.d.ts.map +1 -0
  107. package/dist/api/cma/index.js +69 -0
  108. package/dist/api/cma/index.js.map +1 -0
  109. package/dist/api/cma/live.d.ts +4 -0
  110. package/dist/api/cma/live.d.ts.map +1 -0
  111. package/dist/api/cma/live.js +64 -0
  112. package/dist/api/cma/live.js.map +1 -0
  113. package/dist/api/cma/locales.d.ts +4 -0
  114. package/dist/api/cma/locales.d.ts.map +1 -0
  115. package/dist/api/cma/locales.js +117 -0
  116. package/dist/api/cma/locales.js.map +1 -0
  117. package/dist/api/cma/marketplace.d.ts +4 -0
  118. package/dist/api/cma/marketplace.d.ts.map +1 -0
  119. package/dist/api/cma/marketplace.js +550 -0
  120. package/dist/api/cma/marketplace.js.map +1 -0
  121. package/dist/api/cma/redirects.d.ts +4 -0
  122. package/dist/api/cma/redirects.d.ts.map +1 -0
  123. package/dist/api/cma/redirects.js +120 -0
  124. package/dist/api/cma/redirects.js.map +1 -0
  125. package/dist/api/cma/relationships.d.ts +4 -0
  126. package/dist/api/cma/relationships.d.ts.map +1 -0
  127. package/dist/api/cma/relationships.js +47 -0
  128. package/dist/api/cma/relationships.js.map +1 -0
  129. package/dist/api/cma/schemas.d.ts +4 -0
  130. package/dist/api/cma/schemas.d.ts.map +1 -0
  131. package/dist/api/cma/schemas.js +248 -0
  132. package/dist/api/cma/schemas.js.map +1 -0
  133. package/dist/api/cma/search.d.ts +4 -0
  134. package/dist/api/cma/search.d.ts.map +1 -0
  135. package/dist/api/cma/search.js +149 -0
  136. package/dist/api/cma/search.js.map +1 -0
  137. package/dist/api/cma/spaces.d.ts +4 -0
  138. package/dist/api/cma/spaces.d.ts.map +1 -0
  139. package/dist/api/cma/spaces.js +119 -0
  140. package/dist/api/cma/spaces.js.map +1 -0
  141. package/dist/api/cma/taxonomies.d.ts +4 -0
  142. package/dist/api/cma/taxonomies.d.ts.map +1 -0
  143. package/dist/api/cma/taxonomies.js +296 -0
  144. package/dist/api/cma/taxonomies.js.map +1 -0
  145. package/dist/api/cma/templates.d.ts +13 -0
  146. package/dist/api/cma/templates.d.ts.map +1 -0
  147. package/dist/api/cma/templates.js +312 -0
  148. package/dist/api/cma/templates.js.map +1 -0
  149. package/dist/api/cma/uploads.d.ts +4 -0
  150. package/dist/api/cma/uploads.d.ts.map +1 -0
  151. package/dist/api/cma/uploads.js +148 -0
  152. package/dist/api/cma/uploads.js.map +1 -0
  153. package/dist/api/cma/webhooks.d.ts +4 -0
  154. package/dist/api/cma/webhooks.d.ts.map +1 -0
  155. package/dist/api/cma/webhooks.js +175 -0
  156. package/dist/api/cma/webhooks.js.map +1 -0
  157. package/dist/api/cma/white-label.d.ts +4 -0
  158. package/dist/api/cma/white-label.d.ts.map +1 -0
  159. package/dist/api/cma/white-label.js +47 -0
  160. package/dist/api/cma/white-label.js.map +1 -0
  161. package/dist/api/graphql/index.d.ts +4 -0
  162. package/dist/api/graphql/index.d.ts.map +1 -0
  163. package/dist/api/graphql/index.js +57 -0
  164. package/dist/api/graphql/index.js.map +1 -0
  165. package/dist/api/graphql/introspection.d.ts +31 -0
  166. package/dist/api/graphql/introspection.d.ts.map +1 -0
  167. package/dist/api/graphql/introspection.js +90 -0
  168. package/dist/api/graphql/introspection.js.map +1 -0
  169. package/dist/api/graphql/parser.d.ts +14 -0
  170. package/dist/api/graphql/parser.d.ts.map +1 -0
  171. package/dist/api/graphql/parser.js +140 -0
  172. package/dist/api/graphql/parser.js.map +1 -0
  173. package/dist/api/graphql/resolver.d.ts +3 -0
  174. package/dist/api/graphql/resolver.d.ts.map +1 -0
  175. package/dist/api/graphql/resolver.js +138 -0
  176. package/dist/api/graphql/resolver.js.map +1 -0
  177. package/dist/api/preview/content.d.ts +4 -0
  178. package/dist/api/preview/content.d.ts.map +1 -0
  179. package/dist/api/preview/content.js +207 -0
  180. package/dist/api/preview/content.js.map +1 -0
  181. package/dist/api/preview/index.d.ts +4 -0
  182. package/dist/api/preview/index.d.ts.map +1 -0
  183. package/dist/api/preview/index.js +11 -0
  184. package/dist/api/preview/index.js.map +1 -0
  185. package/dist/api/preview/live.d.ts +4 -0
  186. package/dist/api/preview/live.d.ts.map +1 -0
  187. package/dist/api/preview/live.js +41 -0
  188. package/dist/api/preview/live.js.map +1 -0
  189. package/dist/audit/logger.d.ts +13 -0
  190. package/dist/audit/logger.d.ts.map +1 -0
  191. package/dist/audit/logger.js +21 -0
  192. package/dist/audit/logger.js.map +1 -0
  193. package/dist/audit/middleware.d.ts +7 -0
  194. package/dist/audit/middleware.d.ts.map +1 -0
  195. package/dist/audit/middleware.js +39 -0
  196. package/dist/audit/middleware.js.map +1 -0
  197. package/dist/auth/jwt.d.ts +9 -0
  198. package/dist/auth/jwt.d.ts.map +1 -0
  199. package/dist/auth/jwt.js +15 -0
  200. package/dist/auth/jwt.js.map +1 -0
  201. package/dist/auth/middleware.d.ts +24 -0
  202. package/dist/auth/middleware.d.ts.map +1 -0
  203. package/dist/auth/middleware.js +99 -0
  204. package/dist/auth/middleware.js.map +1 -0
  205. package/dist/auth/password.d.ts +3 -0
  206. package/dist/auth/password.d.ts.map +1 -0
  207. package/dist/auth/password.js +9 -0
  208. package/dist/auth/password.js.map +1 -0
  209. package/dist/auth/rate-limit.d.ts +19 -0
  210. package/dist/auth/rate-limit.d.ts.map +1 -0
  211. package/dist/auth/rate-limit.js +77 -0
  212. package/dist/auth/rate-limit.js.map +1 -0
  213. package/dist/auth/rbac.d.ts +10 -0
  214. package/dist/auth/rbac.d.ts.map +1 -0
  215. package/dist/auth/rbac.js +61 -0
  216. package/dist/auth/rbac.js.map +1 -0
  217. package/dist/blocks/core-blocks.d.ts +13 -0
  218. package/dist/blocks/core-blocks.d.ts.map +1 -0
  219. package/dist/blocks/core-blocks.js +113 -0
  220. package/dist/blocks/core-blocks.js.map +1 -0
  221. package/dist/blocks/renderer-sdk.d.ts +75 -0
  222. package/dist/blocks/renderer-sdk.d.ts.map +1 -0
  223. package/dist/blocks/renderer-sdk.js +112 -0
  224. package/dist/blocks/renderer-sdk.js.map +1 -0
  225. package/dist/blocks/seed-core-blocks.d.ts +6 -0
  226. package/dist/blocks/seed-core-blocks.d.ts.map +1 -0
  227. package/dist/blocks/seed-core-blocks.js +37 -0
  228. package/dist/blocks/seed-core-blocks.js.map +1 -0
  229. package/dist/blocks/validator.d.ts +10 -0
  230. package/dist/blocks/validator.d.ts.map +1 -0
  231. package/dist/blocks/validator.js +111 -0
  232. package/dist/blocks/validator.js.map +1 -0
  233. package/dist/cache/cdn-purge.d.ts +41 -0
  234. package/dist/cache/cdn-purge.d.ts.map +1 -0
  235. package/dist/cache/cdn-purge.js +180 -0
  236. package/dist/cache/cdn-purge.js.map +1 -0
  237. package/dist/cache/observability.d.ts +36 -0
  238. package/dist/cache/observability.d.ts.map +1 -0
  239. package/dist/cache/observability.js +110 -0
  240. package/dist/cache/observability.js.map +1 -0
  241. package/dist/cache/tags.d.ts +28 -0
  242. package/dist/cache/tags.d.ts.map +1 -0
  243. package/dist/cache/tags.js +94 -0
  244. package/dist/cache/tags.js.map +1 -0
  245. package/dist/cli/commands/codegen.d.ts +2 -0
  246. package/dist/cli/commands/codegen.d.ts.map +1 -0
  247. package/dist/cli/commands/codegen.js +73 -0
  248. package/dist/cli/commands/codegen.js.map +1 -0
  249. package/dist/cli/commands/dev.d.ts +2 -0
  250. package/dist/cli/commands/dev.d.ts.map +1 -0
  251. package/dist/cli/commands/dev.js +60 -0
  252. package/dist/cli/commands/dev.js.map +1 -0
  253. package/dist/cli/commands/help.d.ts +2 -0
  254. package/dist/cli/commands/help.d.ts.map +1 -0
  255. package/dist/cli/commands/help.js +32 -0
  256. package/dist/cli/commands/help.js.map +1 -0
  257. package/dist/cli/commands/init.d.ts +2 -0
  258. package/dist/cli/commands/init.d.ts.map +1 -0
  259. package/dist/cli/commands/init.js +90 -0
  260. package/dist/cli/commands/init.js.map +1 -0
  261. package/dist/cli/commands/migrate.d.ts +2 -0
  262. package/dist/cli/commands/migrate.d.ts.map +1 -0
  263. package/dist/cli/commands/migrate.js +27 -0
  264. package/dist/cli/commands/migrate.js.map +1 -0
  265. package/dist/cli/commands/seed.d.ts +2 -0
  266. package/dist/cli/commands/seed.d.ts.map +1 -0
  267. package/dist/cli/commands/seed.js +28 -0
  268. package/dist/cli/commands/seed.js.map +1 -0
  269. package/dist/cli/index.d.ts +16 -0
  270. package/dist/cli/index.d.ts.map +1 -0
  271. package/dist/cli/index.js +120 -0
  272. package/dist/cli/index.js.map +1 -0
  273. package/dist/config.d.ts +11 -0
  274. package/dist/config.d.ts.map +1 -0
  275. package/dist/config.js +11 -0
  276. package/dist/config.js.map +1 -0
  277. package/dist/content/advanced-query.d.ts +37 -0
  278. package/dist/content/advanced-query.d.ts.map +1 -0
  279. package/dist/content/advanced-query.js +162 -0
  280. package/dist/content/advanced-query.js.map +1 -0
  281. package/dist/content/cache.d.ts +27 -0
  282. package/dist/content/cache.d.ts.map +1 -0
  283. package/dist/content/cache.js +100 -0
  284. package/dist/content/cache.js.map +1 -0
  285. package/dist/content/diff.d.ts +13 -0
  286. package/dist/content/diff.d.ts.map +1 -0
  287. package/dist/content/diff.js +80 -0
  288. package/dist/content/diff.js.map +1 -0
  289. package/dist/content/localization.d.ts +13 -0
  290. package/dist/content/localization.d.ts.map +1 -0
  291. package/dist/content/localization.js +42 -0
  292. package/dist/content/localization.js.map +1 -0
  293. package/dist/content/materializer.d.ts +26 -0
  294. package/dist/content/materializer.d.ts.map +1 -0
  295. package/dist/content/materializer.js +175 -0
  296. package/dist/content/materializer.js.map +1 -0
  297. package/dist/content/relationships.d.ts +30 -0
  298. package/dist/content/relationships.d.ts.map +1 -0
  299. package/dist/content/relationships.js +123 -0
  300. package/dist/content/relationships.js.map +1 -0
  301. package/dist/db.d.ts +5 -0
  302. package/dist/db.d.ts.map +1 -0
  303. package/dist/db.js +5 -0
  304. package/dist/db.js.map +1 -0
  305. package/dist/events/emitter.d.ts +16 -0
  306. package/dist/events/emitter.d.ts.map +1 -0
  307. package/dist/events/emitter.js +15 -0
  308. package/dist/events/emitter.js.map +1 -0
  309. package/dist/events/sse.d.ts +16 -0
  310. package/dist/events/sse.d.ts.map +1 -0
  311. package/dist/events/sse.js +37 -0
  312. package/dist/events/sse.js.map +1 -0
  313. package/dist/events/wire.d.ts +2 -0
  314. package/dist/events/wire.d.ts.map +1 -0
  315. package/dist/events/wire.js +27 -0
  316. package/dist/events/wire.js.map +1 -0
  317. package/dist/extensions/billing.d.ts +60 -0
  318. package/dist/extensions/billing.d.ts.map +1 -0
  319. package/dist/extensions/billing.js +98 -0
  320. package/dist/extensions/billing.js.map +1 -0
  321. package/dist/extensions/manifest.d.ts +43 -0
  322. package/dist/extensions/manifest.d.ts.map +1 -0
  323. package/dist/extensions/manifest.js +66 -0
  324. package/dist/extensions/manifest.js.map +1 -0
  325. package/dist/extensions/marketplace.d.ts +50 -0
  326. package/dist/extensions/marketplace.d.ts.map +1 -0
  327. package/dist/extensions/marketplace.js +676 -0
  328. package/dist/extensions/marketplace.js.map +1 -0
  329. package/dist/extensions/router.d.ts +4 -0
  330. package/dist/extensions/router.d.ts.map +1 -0
  331. package/dist/extensions/router.js +50 -0
  332. package/dist/extensions/router.js.map +1 -0
  333. package/dist/extensions/runtime.d.ts +29 -0
  334. package/dist/extensions/runtime.d.ts.map +1 -0
  335. package/dist/extensions/runtime.js +201 -0
  336. package/dist/extensions/runtime.js.map +1 -0
  337. package/dist/extensions/sdk.d.ts +146 -0
  338. package/dist/extensions/sdk.d.ts.map +1 -0
  339. package/dist/extensions/sdk.js +114 -0
  340. package/dist/extensions/sdk.js.map +1 -0
  341. package/dist/integrations/sheets.d.ts +41 -0
  342. package/dist/integrations/sheets.d.ts.map +1 -0
  343. package/dist/integrations/sheets.js +230 -0
  344. package/dist/integrations/sheets.js.map +1 -0
  345. package/dist/media/metadata.d.ts +9 -0
  346. package/dist/media/metadata.d.ts.map +1 -0
  347. package/dist/media/metadata.js +84 -0
  348. package/dist/media/metadata.js.map +1 -0
  349. package/dist/media/presets.d.ts +17 -0
  350. package/dist/media/presets.d.ts.map +1 -0
  351. package/dist/media/presets.js +21 -0
  352. package/dist/media/presets.js.map +1 -0
  353. package/dist/media/storage.d.ts +16 -0
  354. package/dist/media/storage.d.ts.map +1 -0
  355. package/dist/media/storage.js +39 -0
  356. package/dist/media/storage.js.map +1 -0
  357. package/dist/media/transforms-engine.d.ts +46 -0
  358. package/dist/media/transforms-engine.d.ts.map +1 -0
  359. package/dist/media/transforms-engine.js +91 -0
  360. package/dist/media/transforms-engine.js.map +1 -0
  361. package/dist/media/transforms.d.ts +15 -0
  362. package/dist/media/transforms.d.ts.map +1 -0
  363. package/dist/media/transforms.js +22 -0
  364. package/dist/media/transforms.js.map +1 -0
  365. package/dist/media/usage.d.ts +21 -0
  366. package/dist/media/usage.d.ts.map +1 -0
  367. package/dist/media/usage.js +105 -0
  368. package/dist/media/usage.js.map +1 -0
  369. package/dist/redis.d.ts +2 -0
  370. package/dist/redis.d.ts.map +1 -0
  371. package/dist/redis.js +9 -0
  372. package/dist/redis.js.map +1 -0
  373. package/dist/schema/codegen.d.ts +6 -0
  374. package/dist/schema/codegen.d.ts.map +1 -0
  375. package/dist/schema/codegen.js +95 -0
  376. package/dist/schema/codegen.js.map +1 -0
  377. package/dist/schema/json-schema.d.ts +42 -0
  378. package/dist/schema/json-schema.d.ts.map +1 -0
  379. package/dist/schema/json-schema.js +68 -0
  380. package/dist/schema/json-schema.js.map +1 -0
  381. package/dist/schema/validator.d.ts +14 -0
  382. package/dist/schema/validator.d.ts.map +1 -0
  383. package/dist/schema/validator.js +103 -0
  384. package/dist/schema/validator.js.map +1 -0
  385. package/dist/sdk/client.d.ts +86 -0
  386. package/dist/sdk/client.d.ts.map +1 -0
  387. package/dist/sdk/client.js +117 -0
  388. package/dist/sdk/client.js.map +1 -0
  389. package/dist/server.d.ts +2 -0
  390. package/dist/server.d.ts.map +1 -0
  391. package/dist/server.js +59 -0
  392. package/dist/server.js.map +1 -0
  393. package/dist/spaces/premium-templates.d.ts +37 -0
  394. package/dist/spaces/premium-templates.d.ts.map +1 -0
  395. package/dist/spaces/premium-templates.js +1603 -0
  396. package/dist/spaces/premium-templates.js.map +1 -0
  397. package/dist/spaces/provisioner.d.ts +16 -0
  398. package/dist/spaces/provisioner.d.ts.map +1 -0
  399. package/dist/spaces/provisioner.js +143 -0
  400. package/dist/spaces/provisioner.js.map +1 -0
  401. package/dist/spaces/template-registry.d.ts +34 -0
  402. package/dist/spaces/template-registry.d.ts.map +1 -0
  403. package/dist/spaces/template-registry.js +55 -0
  404. package/dist/spaces/template-registry.js.map +1 -0
  405. package/dist/spaces/templates.d.ts +44 -0
  406. package/dist/spaces/templates.d.ts.map +1 -0
  407. package/dist/spaces/templates.js +201 -0
  408. package/dist/spaces/templates.js.map +1 -0
  409. package/dist/spaces/white-label.d.ts +20 -0
  410. package/dist/spaces/white-label.d.ts.map +1 -0
  411. package/dist/spaces/white-label.js +32 -0
  412. package/dist/spaces/white-label.js.map +1 -0
  413. package/dist/utils/query-shaping.d.ts +27 -0
  414. package/dist/utils/query-shaping.d.ts.map +1 -0
  415. package/dist/utils/query-shaping.js +69 -0
  416. package/dist/utils/query-shaping.js.map +1 -0
  417. package/dist/utils/space.d.ts +4 -0
  418. package/dist/utils/space.d.ts.map +1 -0
  419. package/dist/utils/space.js +17 -0
  420. package/dist/utils/space.js.map +1 -0
  421. package/dist/webhooks/dispatcher.d.ts +9 -0
  422. package/dist/webhooks/dispatcher.d.ts.map +1 -0
  423. package/dist/webhooks/dispatcher.js +102 -0
  424. package/dist/webhooks/dispatcher.js.map +1 -0
  425. package/package.json +84 -0
  426. package/prisma/migrations/20260403025542_init/migration.sql +315 -0
  427. package/prisma/migrations/20260403034855_add_blocks_and_patterns/migration.sql +40 -0
  428. package/prisma/migrations/20260403041936_add_published_documents/migration.sql +30 -0
  429. package/prisma/migrations/20260403042809_add_locales_and_taxonomies/migration.sql +58 -0
  430. package/prisma/migrations/20260403043033_add_scheduled_unpublish_at/migration.sql +2 -0
  431. package/prisma/migrations/20260403052801_add_environments_and_whitelabel/migration.sql +65 -0
  432. package/prisma/migrations/20260403055920_add_marketplace_and_extension_config/migration.sql +73 -0
  433. package/prisma/migrations/migration_lock.toml +3 -0
  434. package/prisma/schema.prisma +530 -0
  435. package/prisma/seed.ts +154 -0
@@ -0,0 +1,736 @@
1
+ import { Router } from 'express';
2
+ import { nanoid } from 'nanoid';
3
+ import { prisma } from '../../db.js';
4
+ import { requireScope } from '../../auth/middleware.js';
5
+ import { validateEntryData, validateForPublish } from '../../schema/validator.js';
6
+ import { materializeEntry, dematerializeEntry } from '../../content/materializer.js';
7
+ import { invalidateByType } from '../../content/cache.js';
8
+ import { eventBus } from '../../events/emitter.js';
9
+ import { getCDNProvider } from '../../cache/cdn-purge.js';
10
+ const router = Router();
11
+ // ─── Helpers ───
12
+ function getSpaceId(req) {
13
+ return req.params.spaceId ?? req.headers['x-space-id'];
14
+ }
15
+ function generateEtag() {
16
+ return nanoid(16);
17
+ }
18
+ // ─── GET /entries ───
19
+ router.get('/', requireScope('cma:read', 'cma:write'), async (req, res) => {
20
+ const spaceId = getSpaceId(req);
21
+ if (!spaceId) {
22
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
23
+ return;
24
+ }
25
+ const { contentType, status, limit: limitParam, offset: offsetParam, orderBy, order, } = req.query;
26
+ const limit = Math.min(parseInt(limitParam ?? '25', 10), 100);
27
+ const offset = parseInt(offsetParam ?? '0', 10);
28
+ const where = { spaceId };
29
+ if (contentType) {
30
+ const ct = await prisma.contentType.findUnique({
31
+ where: { spaceId_key: { spaceId, key: contentType } },
32
+ });
33
+ if (ct) {
34
+ where.contentTypeId = ct.id;
35
+ }
36
+ else {
37
+ res.json({ items: [], total: 0, limit, offset });
38
+ return;
39
+ }
40
+ }
41
+ if (status) {
42
+ where.state = { status };
43
+ }
44
+ const [entries, total] = await Promise.all([
45
+ prisma.entry.findMany({
46
+ where,
47
+ include: {
48
+ contentType: { select: { key: true, name: true } },
49
+ state: true,
50
+ versions: {
51
+ orderBy: { createdAt: 'desc' },
52
+ take: 1,
53
+ },
54
+ },
55
+ orderBy: { [orderBy ?? 'updatedAt']: order ?? 'desc' },
56
+ take: limit,
57
+ skip: offset,
58
+ }),
59
+ prisma.entry.count({ where }),
60
+ ]);
61
+ const items = entries.map((entry) => ({
62
+ id: entry.id,
63
+ slug: entry.slug,
64
+ contentType: entry.contentType,
65
+ status: entry.state?.status ?? 'draft',
66
+ draftVersionId: entry.state?.draftVersionId,
67
+ publishedVersionId: entry.state?.publishedVersionId,
68
+ latestVersion: entry.versions[0]
69
+ ? {
70
+ id: entry.versions[0].id,
71
+ kind: entry.versions[0].kind,
72
+ data: entry.versions[0].data,
73
+ etag: entry.versions[0].etag,
74
+ createdAt: entry.versions[0].createdAt,
75
+ }
76
+ : null,
77
+ createdAt: entry.createdAt,
78
+ updatedAt: entry.updatedAt,
79
+ }));
80
+ res.json({ items, total, limit, offset });
81
+ });
82
+ // ─── POST /entries ───
83
+ router.post('/', requireScope('cma:write'), async (req, res) => {
84
+ const spaceId = getSpaceId(req);
85
+ if (!spaceId) {
86
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
87
+ return;
88
+ }
89
+ const { contentTypeKey, slug, data } = req.body;
90
+ if (!contentTypeKey || !slug) {
91
+ res.status(400).json({ error: 'validation_error', message: 'contentTypeKey and slug are required' });
92
+ return;
93
+ }
94
+ const contentType = await prisma.contentType.findUnique({
95
+ where: { spaceId_key: { spaceId, key: contentTypeKey } },
96
+ });
97
+ if (!contentType) {
98
+ res.status(404).json({ error: 'not_found', message: `Content type "${contentTypeKey}" not found` });
99
+ return;
100
+ }
101
+ // Validate entry data against schema
102
+ if (data && typeof data === 'object') {
103
+ const validationErrors = await validateEntryData(contentType.id, data);
104
+ if (validationErrors.length > 0) {
105
+ res.status(400).json({ error: 'validation_error', message: 'Entry data is invalid', details: validationErrors });
106
+ return;
107
+ }
108
+ }
109
+ // Check for duplicate slug
110
+ const existingEntry = await prisma.entry.findUnique({
111
+ where: { spaceId_contentTypeId_slug: { spaceId, contentTypeId: contentType.id, slug } },
112
+ });
113
+ if (existingEntry) {
114
+ res.status(409).json({ error: 'conflict', message: `Entry with slug "${slug}" already exists for this content type` });
115
+ return;
116
+ }
117
+ const etag = generateEtag();
118
+ const entry = await prisma.$transaction(async (tx) => {
119
+ const newEntry = await tx.entry.create({
120
+ data: {
121
+ spaceId,
122
+ contentTypeId: contentType.id,
123
+ slug,
124
+ },
125
+ });
126
+ const version = await tx.entryVersion.create({
127
+ data: {
128
+ entryId: newEntry.id,
129
+ kind: 'draft',
130
+ data: data ?? {},
131
+ etag,
132
+ createdById: req.auth.userId,
133
+ },
134
+ });
135
+ await tx.entryState.create({
136
+ data: {
137
+ entryId: newEntry.id,
138
+ status: 'draft',
139
+ draftVersionId: version.id,
140
+ },
141
+ });
142
+ return tx.entry.findUniqueOrThrow({
143
+ where: { id: newEntry.id },
144
+ include: {
145
+ contentType: { select: { key: true, name: true } },
146
+ state: true,
147
+ versions: { orderBy: { createdAt: 'desc' }, take: 1 },
148
+ },
149
+ });
150
+ });
151
+ eventBus.emit('entry.created', { spaceId, userId: req.auth.userId, data: { entryId: entry.id, typeKey: contentTypeKey, slug } });
152
+ res.status(201).json({
153
+ id: entry.id,
154
+ slug: entry.slug,
155
+ contentType: entry.contentType,
156
+ status: entry.state?.status ?? 'draft',
157
+ latestVersion: entry.versions[0]
158
+ ? {
159
+ id: entry.versions[0].id,
160
+ kind: entry.versions[0].kind,
161
+ data: entry.versions[0].data,
162
+ etag: entry.versions[0].etag,
163
+ createdAt: entry.versions[0].createdAt,
164
+ }
165
+ : null,
166
+ createdAt: entry.createdAt,
167
+ updatedAt: entry.updatedAt,
168
+ });
169
+ eventBus.emit('entry.created', { spaceId, userId: req.auth.userId, data: { entryId: entry.id, typeKey: contentTypeKey, slug } });
170
+ });
171
+ // ─── GET /entries/:id ───
172
+ router.get('/:id', requireScope('cma:read', 'cma:write'), async (req, res) => {
173
+ const spaceId = getSpaceId(req);
174
+ if (!spaceId) {
175
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
176
+ return;
177
+ }
178
+ const entry = await prisma.entry.findFirst({
179
+ where: { id: req.params.id, spaceId },
180
+ include: {
181
+ contentType: { select: { key: true, name: true } },
182
+ state: true,
183
+ versions: { orderBy: { createdAt: 'desc' } },
184
+ },
185
+ });
186
+ if (!entry) {
187
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
188
+ return;
189
+ }
190
+ const latestVersion = entry.versions[0] ?? null;
191
+ res.set('ETag', `"${latestVersion?.etag ?? ''}"`);
192
+ res.json({
193
+ id: entry.id,
194
+ slug: entry.slug,
195
+ contentType: entry.contentType,
196
+ status: entry.state?.status ?? 'draft',
197
+ draftVersionId: entry.state?.draftVersionId,
198
+ publishedVersionId: entry.state?.publishedVersionId,
199
+ versions: entry.versions.map((v) => ({
200
+ id: v.id,
201
+ kind: v.kind,
202
+ data: v.data,
203
+ etag: v.etag,
204
+ createdById: v.createdById,
205
+ createdAt: v.createdAt,
206
+ })),
207
+ createdAt: entry.createdAt,
208
+ updatedAt: entry.updatedAt,
209
+ });
210
+ });
211
+ // ─── PATCH /entries/:id ───
212
+ router.patch('/:id', requireScope('cma:write'), async (req, res) => {
213
+ const spaceId = getSpaceId(req);
214
+ if (!spaceId) {
215
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
216
+ return;
217
+ }
218
+ const entry = await prisma.entry.findFirst({
219
+ where: { id: req.params.id, spaceId },
220
+ });
221
+ if (!entry) {
222
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
223
+ return;
224
+ }
225
+ const { slug } = req.body;
226
+ const oldSlug = entry.slug;
227
+ const updated = await prisma.entry.update({
228
+ where: { id: entry.id },
229
+ data: {
230
+ ...(slug !== undefined && { slug }),
231
+ },
232
+ include: {
233
+ contentType: { select: { key: true, name: true } },
234
+ state: true,
235
+ },
236
+ });
237
+ // Auto-create redirect when slug changes
238
+ if (slug !== undefined && slug !== oldSlug) {
239
+ await prisma.redirect.upsert({
240
+ where: { spaceId_fromSlug: { spaceId: spaceId, fromSlug: oldSlug } },
241
+ create: {
242
+ spaceId: spaceId,
243
+ fromSlug: oldSlug,
244
+ toSlug: slug,
245
+ statusCode: 301,
246
+ },
247
+ update: {
248
+ toSlug: slug,
249
+ },
250
+ }).catch(() => {
251
+ // Non-critical — don't fail the entry update if redirect creation fails
252
+ });
253
+ }
254
+ res.json({
255
+ id: updated.id,
256
+ slug: updated.slug,
257
+ contentType: updated.contentType,
258
+ status: updated.state?.status ?? 'draft',
259
+ createdAt: updated.createdAt,
260
+ updatedAt: updated.updatedAt,
261
+ });
262
+ });
263
+ // ─── DELETE /entries/:id ───
264
+ router.delete('/:id', requireScope('cma:write'), async (req, res) => {
265
+ const spaceId = getSpaceId(req);
266
+ if (!spaceId) {
267
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
268
+ return;
269
+ }
270
+ const entry = await prisma.entry.findFirst({
271
+ where: { id: req.params.id, spaceId },
272
+ });
273
+ if (!entry) {
274
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
275
+ return;
276
+ }
277
+ await prisma.entry.delete({ where: { id: entry.id } });
278
+ eventBus.emit('entry.deleted', { spaceId, userId: req.auth.userId, data: { entryId: entry.id } });
279
+ res.status(204).end();
280
+ });
281
+ // ─── POST /entries/:id/save-draft ───
282
+ router.post('/:id/save-draft', requireScope('cma:write'), async (req, res) => {
283
+ const spaceId = getSpaceId(req);
284
+ if (!spaceId) {
285
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
286
+ return;
287
+ }
288
+ const entry = await prisma.entry.findFirst({
289
+ where: { id: req.params.id, spaceId },
290
+ include: { state: true },
291
+ });
292
+ if (!entry) {
293
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
294
+ return;
295
+ }
296
+ // Optimistic concurrency: check If-Match header against current draft etag
297
+ const ifMatch = req.headers['if-match']?.replace(/"/g, '');
298
+ if (ifMatch && entry.state) {
299
+ const currentDraft = await prisma.entryVersion.findUnique({
300
+ where: { id: entry.state.draftVersionId },
301
+ select: { etag: true },
302
+ });
303
+ if (currentDraft && currentDraft.etag !== ifMatch) {
304
+ res.status(412).json({ error: 'precondition_failed', message: 'ETag mismatch — entry was modified concurrently' });
305
+ return;
306
+ }
307
+ }
308
+ const { data } = req.body;
309
+ if (data === undefined) {
310
+ res.status(400).json({ error: 'validation_error', message: 'data is required' });
311
+ return;
312
+ }
313
+ const etag = generateEtag();
314
+ const version = await prisma.$transaction(async (tx) => {
315
+ const newVersion = await tx.entryVersion.create({
316
+ data: {
317
+ entryId: entry.id,
318
+ kind: 'draft',
319
+ data,
320
+ etag,
321
+ createdById: req.auth.userId,
322
+ },
323
+ });
324
+ await tx.entryState.upsert({
325
+ where: { entryId: entry.id },
326
+ update: {
327
+ draftVersionId: newVersion.id,
328
+ status: entry.state?.publishedVersionId ? 'published' : 'draft',
329
+ },
330
+ create: {
331
+ entryId: entry.id,
332
+ status: 'draft',
333
+ draftVersionId: newVersion.id,
334
+ },
335
+ });
336
+ // Touch entry updatedAt
337
+ await tx.entry.update({ where: { id: entry.id }, data: {} });
338
+ return newVersion;
339
+ });
340
+ res.set('ETag', `"${version.etag}"`);
341
+ res.json({
342
+ id: version.id,
343
+ entryId: entry.id,
344
+ kind: version.kind,
345
+ data: version.data,
346
+ etag: version.etag,
347
+ createdAt: version.createdAt,
348
+ });
349
+ eventBus.emit('entry.draftSaved', { spaceId, userId: req.auth.userId, data: { entryId: entry.id, versionId: version.id } });
350
+ });
351
+ // ─── POST /entries/:id/publish ───
352
+ router.post('/:id/publish', requireScope('cma:write'), async (req, res) => {
353
+ const spaceId = getSpaceId(req);
354
+ if (!spaceId) {
355
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
356
+ return;
357
+ }
358
+ const entry = await prisma.entry.findFirst({
359
+ where: { id: req.params.id, spaceId },
360
+ include: { state: true },
361
+ });
362
+ if (!entry) {
363
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
364
+ return;
365
+ }
366
+ if (!entry.state) {
367
+ res.status(400).json({ error: 'invalid_state', message: 'Entry has no state — cannot publish' });
368
+ return;
369
+ }
370
+ // Require If-Match for optimistic concurrency
371
+ const ifMatch = req.headers['if-match']?.replace(/"/g, '');
372
+ if (!ifMatch) {
373
+ res.status(428).json({ error: 'precondition_required', message: 'If-Match header is required to publish — fetch the entry first to get the current ETag' });
374
+ return;
375
+ }
376
+ // Get the current draft version's data to snapshot as published
377
+ const draftVersion = await prisma.entryVersion.findUnique({
378
+ where: { id: entry.state.draftVersionId },
379
+ });
380
+ if (!draftVersion) {
381
+ res.status(400).json({ error: 'invalid_state', message: 'Draft version not found' });
382
+ return;
383
+ }
384
+ if (draftVersion.etag !== ifMatch) {
385
+ res.status(412).json({ error: 'precondition_failed', message: 'ETag mismatch — entry was modified concurrently' });
386
+ return;
387
+ }
388
+ // Validate all required fields before publishing
389
+ const publishErrors = await validateForPublish(entry.contentTypeId, draftVersion.data);
390
+ if (publishErrors.length > 0) {
391
+ res.status(400).json({ error: 'publish_validation_error', message: 'Entry cannot be published — required fields missing', details: publishErrors });
392
+ return;
393
+ }
394
+ const etag = generateEtag();
395
+ const publishedVersion = await prisma.$transaction(async (tx) => {
396
+ const newVersion = await tx.entryVersion.create({
397
+ data: {
398
+ entryId: entry.id,
399
+ kind: 'published',
400
+ data: draftVersion.data,
401
+ etag,
402
+ createdById: req.auth.userId,
403
+ },
404
+ });
405
+ await tx.entryState.update({
406
+ where: { entryId: entry.id },
407
+ data: {
408
+ status: 'published',
409
+ publishedVersionId: newVersion.id,
410
+ },
411
+ });
412
+ // Touch entry updatedAt
413
+ await tx.entry.update({ where: { id: entry.id }, data: {} });
414
+ return newVersion;
415
+ });
416
+ // Materialize the entry into the published_documents read model and
417
+ // invalidate the CDA cache for this content type (fire-and-forget to
418
+ // avoid blocking the response).
419
+ const contentTypeKey = (await prisma.contentType.findUnique({
420
+ where: { id: entry.contentTypeId },
421
+ select: { key: true },
422
+ }))?.key;
423
+ materializeEntry(entry.id).catch(() => { });
424
+ if (contentTypeKey) {
425
+ invalidateByType(spaceId, contentTypeKey).catch(() => { });
426
+ // Purge CDN edge caches by Surrogate-Key tags
427
+ getCDNProvider()
428
+ .purgeByTags([`space:${spaceId}`, `type:${contentTypeKey}`, `entry:${entry.id}`])
429
+ .catch(() => { });
430
+ }
431
+ // Emit before response to ensure event fires
432
+ eventBus.emit('entry.published', { spaceId, userId: req.auth.userId, data: { entryId: entry.id, publishedVersionId: publishedVersion.id, slug: entry.slug } });
433
+ res.set('ETag', `"${publishedVersion.etag}"`);
434
+ res.json({
435
+ id: publishedVersion.id,
436
+ entryId: entry.id,
437
+ kind: publishedVersion.kind,
438
+ data: publishedVersion.data,
439
+ etag: publishedVersion.etag,
440
+ status: 'published',
441
+ createdAt: publishedVersion.createdAt,
442
+ });
443
+ });
444
+ // ─── POST /entries/:id/unpublish ───
445
+ router.post('/:id/unpublish', requireScope('cma:write'), async (req, res) => {
446
+ const spaceId = getSpaceId(req);
447
+ if (!spaceId) {
448
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
449
+ return;
450
+ }
451
+ const entry = await prisma.entry.findFirst({
452
+ where: { id: req.params.id, spaceId },
453
+ include: { state: true },
454
+ });
455
+ if (!entry) {
456
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
457
+ return;
458
+ }
459
+ if (!entry.state || entry.state.status !== 'published') {
460
+ res.status(400).json({ error: 'invalid_state', message: 'Entry is not currently published' });
461
+ return;
462
+ }
463
+ await prisma.$transaction(async (tx) => {
464
+ await tx.entryState.update({
465
+ where: { entryId: entry.id },
466
+ data: {
467
+ status: 'draft',
468
+ publishedVersionId: null,
469
+ },
470
+ });
471
+ // Touch entry updatedAt
472
+ await tx.entry.update({ where: { id: entry.id }, data: {} });
473
+ });
474
+ // Remove from published_documents and invalidate cache
475
+ const contentTypeKey = (await prisma.contentType.findUnique({
476
+ where: { id: entry.contentTypeId },
477
+ select: { key: true },
478
+ }))?.key;
479
+ dematerializeEntry(entry.id).catch(() => { });
480
+ if (contentTypeKey) {
481
+ invalidateByType(spaceId, contentTypeKey).catch(() => { });
482
+ // Purge CDN edge caches by Surrogate-Key tags
483
+ getCDNProvider()
484
+ .purgeByTags([`space:${spaceId}`, `type:${contentTypeKey}`, `entry:${entry.id}`])
485
+ .catch(() => { });
486
+ }
487
+ res.json({
488
+ id: entry.id,
489
+ status: 'draft',
490
+ message: 'Entry unpublished successfully',
491
+ });
492
+ eventBus.emit('entry.unpublished', { spaceId, userId: req.auth.userId, data: { entryId: entry.id } });
493
+ });
494
+ // ─── POST /entries/:id/schedule ───
495
+ router.post('/:id/schedule', requireScope('cma:write'), async (req, res) => {
496
+ const spaceId = getSpaceId(req);
497
+ if (!spaceId) {
498
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
499
+ return;
500
+ }
501
+ const entry = await prisma.entry.findFirst({
502
+ where: { id: req.params.id, spaceId },
503
+ include: { state: true },
504
+ });
505
+ if (!entry) {
506
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
507
+ return;
508
+ }
509
+ const { publishAt, unpublishAt } = req.body;
510
+ if (!publishAt && !unpublishAt) {
511
+ res.status(400).json({ error: 'validation_error', message: 'publishAt or unpublishAt (ISO date) is required' });
512
+ return;
513
+ }
514
+ let scheduledAt = null;
515
+ let scheduledUnpublishAt = null;
516
+ if (publishAt) {
517
+ scheduledAt = new Date(publishAt);
518
+ if (isNaN(scheduledAt.getTime()) || scheduledAt <= new Date()) {
519
+ res.status(400).json({ error: 'validation_error', message: 'publishAt must be a valid future date' });
520
+ return;
521
+ }
522
+ }
523
+ if (unpublishAt) {
524
+ scheduledUnpublishAt = new Date(unpublishAt);
525
+ if (isNaN(scheduledUnpublishAt.getTime()) || scheduledUnpublishAt <= new Date()) {
526
+ res.status(400).json({ error: 'validation_error', message: 'unpublishAt must be a valid future date' });
527
+ return;
528
+ }
529
+ }
530
+ // If only unpublishAt is provided, the entry must be published
531
+ if (!publishAt && unpublishAt && entry.state?.status !== 'published') {
532
+ res.status(400).json({ error: 'invalid_state', message: 'Entry must be published to schedule an unpublish without a publishAt' });
533
+ return;
534
+ }
535
+ const updateData = {};
536
+ if (scheduledAt) {
537
+ updateData.status = 'scheduled';
538
+ updateData.scheduledAt = scheduledAt;
539
+ }
540
+ if (scheduledUnpublishAt) {
541
+ updateData.scheduledUnpublishAt = scheduledUnpublishAt;
542
+ }
543
+ await prisma.entryState.upsert({
544
+ where: { entryId: entry.id },
545
+ update: updateData,
546
+ create: {
547
+ entryId: entry.id,
548
+ status: scheduledAt ? 'scheduled' : entry.state?.status ?? 'draft',
549
+ draftVersionId: entry.state.draftVersionId,
550
+ scheduledAt,
551
+ scheduledUnpublishAt,
552
+ },
553
+ });
554
+ res.json({
555
+ id: entry.id,
556
+ status: scheduledAt ? 'scheduled' : entry.state?.status ?? 'draft',
557
+ ...(scheduledAt && { scheduledAt: scheduledAt.toISOString() }),
558
+ ...(scheduledUnpublishAt && { scheduledUnpublishAt: scheduledUnpublishAt.toISOString() }),
559
+ });
560
+ });
561
+ // ─── POST /entries/:id/revert ───
562
+ router.post('/:id/revert', requireScope('cma:write'), async (req, res) => {
563
+ const spaceId = getSpaceId(req);
564
+ if (!spaceId) {
565
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
566
+ return;
567
+ }
568
+ const entry = await prisma.entry.findFirst({
569
+ where: { id: req.params.id, spaceId },
570
+ include: { state: true },
571
+ });
572
+ if (!entry) {
573
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
574
+ return;
575
+ }
576
+ const { versionId } = req.body;
577
+ if (!versionId) {
578
+ res.status(400).json({ error: 'validation_error', message: 'versionId is required' });
579
+ return;
580
+ }
581
+ const targetVersion = await prisma.entryVersion.findFirst({
582
+ where: { id: versionId, entryId: entry.id },
583
+ });
584
+ if (!targetVersion) {
585
+ res.status(404).json({ error: 'not_found', message: 'Version not found for this entry' });
586
+ return;
587
+ }
588
+ // Create a new draft version with the old version's data
589
+ const etag = generateEtag();
590
+ const newVersion = await prisma.$transaction(async (tx) => {
591
+ const reverted = await tx.entryVersion.create({
592
+ data: {
593
+ entryId: entry.id,
594
+ kind: 'draft',
595
+ data: targetVersion.data,
596
+ etag,
597
+ createdById: req.auth.userId,
598
+ },
599
+ });
600
+ await tx.entryState.update({
601
+ where: { entryId: entry.id },
602
+ data: { draftVersionId: reverted.id },
603
+ });
604
+ await tx.entry.update({ where: { id: entry.id }, data: {} });
605
+ return reverted;
606
+ });
607
+ res.json({
608
+ id: newVersion.id,
609
+ entryId: entry.id,
610
+ kind: 'draft',
611
+ data: newVersion.data,
612
+ etag: newVersion.etag,
613
+ revertedFrom: versionId,
614
+ createdAt: newVersion.createdAt,
615
+ });
616
+ });
617
+ // ─── POST /entries/:id/duplicate ───
618
+ router.post('/:id/duplicate', requireScope('cma:write'), async (req, res) => {
619
+ const spaceId = getSpaceId(req);
620
+ if (!spaceId) {
621
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
622
+ return;
623
+ }
624
+ const entry = await prisma.entry.findFirst({
625
+ where: { id: req.params.id, spaceId },
626
+ include: {
627
+ contentType: { select: { key: true, name: true } },
628
+ versions: { orderBy: { createdAt: 'desc' }, take: 1 },
629
+ },
630
+ });
631
+ if (!entry) {
632
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
633
+ return;
634
+ }
635
+ const latestVersion = entry.versions[0];
636
+ if (!latestVersion) {
637
+ res.status(400).json({ error: 'invalid_state', message: 'Entry has no versions to duplicate' });
638
+ return;
639
+ }
640
+ const copySlug = `${entry.slug}-copy-${Date.now()}`;
641
+ const etag = generateEtag();
642
+ const duplicated = await prisma.$transaction(async (tx) => {
643
+ const newEntry = await tx.entry.create({
644
+ data: {
645
+ spaceId,
646
+ contentTypeId: entry.contentTypeId,
647
+ slug: copySlug,
648
+ },
649
+ });
650
+ const version = await tx.entryVersion.create({
651
+ data: {
652
+ entryId: newEntry.id,
653
+ kind: 'draft',
654
+ data: latestVersion.data,
655
+ etag,
656
+ createdById: req.auth.userId,
657
+ },
658
+ });
659
+ await tx.entryState.create({
660
+ data: {
661
+ entryId: newEntry.id,
662
+ status: 'draft',
663
+ draftVersionId: version.id,
664
+ },
665
+ });
666
+ return tx.entry.findUniqueOrThrow({
667
+ where: { id: newEntry.id },
668
+ include: {
669
+ contentType: { select: { key: true, name: true } },
670
+ state: true,
671
+ versions: { orderBy: { createdAt: 'desc' }, take: 1 },
672
+ },
673
+ });
674
+ });
675
+ eventBus.emit('entry.created', { spaceId, userId: req.auth.userId, data: { entryId: duplicated.id, typeKey: duplicated.contentType.key, slug: duplicated.slug } });
676
+ res.status(201).json({
677
+ id: duplicated.id,
678
+ slug: duplicated.slug,
679
+ contentType: duplicated.contentType,
680
+ status: duplicated.state?.status ?? 'draft',
681
+ latestVersion: duplicated.versions[0]
682
+ ? {
683
+ id: duplicated.versions[0].id,
684
+ kind: duplicated.versions[0].kind,
685
+ data: duplicated.versions[0].data,
686
+ etag: duplicated.versions[0].etag,
687
+ createdAt: duplicated.versions[0].createdAt,
688
+ }
689
+ : null,
690
+ duplicatedFrom: entry.id,
691
+ createdAt: duplicated.createdAt,
692
+ updatedAt: duplicated.updatedAt,
693
+ });
694
+ });
695
+ // ─── GET /entries/:id/versions ───
696
+ router.get('/:id/versions', requireScope('cma:read', 'cma:write'), async (req, res) => {
697
+ const spaceId = getSpaceId(req);
698
+ if (!spaceId) {
699
+ res.status(400).json({ error: 'validation_error', message: 'spaceId is required' });
700
+ return;
701
+ }
702
+ const entry = await prisma.entry.findFirst({
703
+ where: { id: req.params.id, spaceId },
704
+ });
705
+ if (!entry) {
706
+ res.status(404).json({ error: 'not_found', message: 'Entry not found' });
707
+ return;
708
+ }
709
+ const { limit: limitParam, offset: offsetParam } = req.query;
710
+ const limit = Math.min(parseInt(limitParam ?? '25', 10), 100);
711
+ const offset = parseInt(offsetParam ?? '0', 10);
712
+ const [versions, total] = await Promise.all([
713
+ prisma.entryVersion.findMany({
714
+ where: { entryId: entry.id },
715
+ orderBy: { createdAt: 'desc' },
716
+ take: limit,
717
+ skip: offset,
718
+ }),
719
+ prisma.entryVersion.count({ where: { entryId: entry.id } }),
720
+ ]);
721
+ res.json({
722
+ items: versions.map((v) => ({
723
+ id: v.id,
724
+ kind: v.kind,
725
+ data: v.data,
726
+ etag: v.etag,
727
+ createdById: v.createdById,
728
+ createdAt: v.createdAt,
729
+ })),
730
+ total,
731
+ limit,
732
+ offset,
733
+ });
734
+ });
735
+ export default router;
736
+ //# sourceMappingURL=entries.js.map