opacacms 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 (399) hide show
  1. package/bun.lock +34 -0
  2. package/dist/admin/api-client.d.ts +8 -0
  3. package/dist/admin/auth-client.d.ts +940 -0
  4. package/dist/admin/custom-field.d.ts +71 -0
  5. package/dist/admin/index.d.ts +11 -0
  6. package/dist/admin/react.d.ts +3 -0
  7. package/dist/admin/router.d.ts +7 -0
  8. package/dist/admin/stores/admin-queries.d.ts +32 -0
  9. package/dist/admin/stores/auth.d.ts +33 -0
  10. package/dist/admin/stores/column-visibility.d.ts +21 -0
  11. package/dist/admin/stores/config.d.ts +7 -0
  12. package/dist/admin/stores/media.d.ts +44 -0
  13. package/dist/admin/stores/query.d.ts +4 -0
  14. package/dist/admin/stores/ui.d.ts +11 -0
  15. package/dist/admin/ui/admin-client.d.ts +7 -0
  16. package/dist/admin/ui/admin-layout.d.ts +14 -0
  17. package/dist/admin/ui/components/ColumnVisibilityToggle.d.ts +10 -0
  18. package/dist/admin/ui/components/DataDetailSheet.d.ts +13 -0
  19. package/dist/admin/ui/components/DataDetailView.d.ts +9 -0
  20. package/dist/admin/ui/components/Table.d.ts +10 -0
  21. package/dist/admin/ui/components/fields/ArrayField.d.ts +13 -0
  22. package/dist/admin/ui/components/fields/BlocksField.d.ts +17 -0
  23. package/dist/admin/ui/components/fields/BooleanField.d.ts +13 -0
  24. package/dist/admin/ui/components/fields/CollapsibleField.d.ts +16 -0
  25. package/dist/admin/ui/components/fields/DateField.d.ts +13 -0
  26. package/dist/admin/ui/components/fields/FileField.d.ts +23 -0
  27. package/dist/admin/ui/components/fields/GroupField.d.ts +13 -0
  28. package/dist/admin/ui/components/fields/JoinField.d.ts +15 -0
  29. package/dist/admin/ui/components/fields/NumberField.d.ts +14 -0
  30. package/dist/admin/ui/components/fields/RadioField.d.ts +17 -0
  31. package/dist/admin/ui/components/fields/RelationshipField.d.ts +16 -0
  32. package/dist/admin/ui/components/fields/RowField.d.ts +12 -0
  33. package/dist/admin/ui/components/fields/SelectField.d.ts +18 -0
  34. package/dist/admin/ui/components/fields/TabsField.d.ts +15 -0
  35. package/dist/admin/ui/components/fields/TextAreaField.d.ts +14 -0
  36. package/dist/admin/ui/components/fields/TextField.d.ts +14 -0
  37. package/dist/admin/ui/components/fields/VirtualField.d.ts +8 -0
  38. package/dist/admin/ui/components/fields/index.d.ts +28 -0
  39. package/dist/admin/ui/components/fields/richtext-editor/index.d.ts +10 -0
  40. package/dist/admin/ui/components/fields/richtext-editor/nodes/ImageComponent.d.ts +7 -0
  41. package/dist/admin/ui/components/fields/richtext-editor/nodes/ImageNode.d.ts +27 -0
  42. package/dist/admin/ui/components/fields/richtext-editor/plugins/ComponentPickerPlugin.d.ts +1 -0
  43. package/dist/admin/ui/components/fields/richtext-editor/plugins/EditableSyncPlugin.d.ts +5 -0
  44. package/dist/admin/ui/components/fields/richtext-editor/plugins/NotionToolbarPlugin.d.ts +1 -0
  45. package/dist/admin/ui/components/fields/richtext-editor/plugins/SimpleToolbarPlugin.d.ts +1 -0
  46. package/dist/admin/ui/components/fields/richtext-editor/plugins/ValueSyncPlugin.d.ts +5 -0
  47. package/dist/admin/ui/components/fields/utils.d.ts +1 -0
  48. package/dist/admin/ui/components/link.d.ts +8 -0
  49. package/dist/admin/ui/components/media/AssetManagerModal.d.ts +17 -0
  50. package/dist/admin/ui/components/toast.d.ts +10 -0
  51. package/dist/admin/ui/components/ui/accordion.d.ts +11 -0
  52. package/dist/admin/ui/components/ui/alert-dialog.d.ts +12 -0
  53. package/dist/admin/ui/components/ui/blocks.d.ts +5 -0
  54. package/dist/admin/ui/components/ui/breadcrumbs.d.ts +7 -0
  55. package/dist/admin/ui/components/ui/button.d.ts +7 -0
  56. package/dist/admin/ui/components/ui/collapsible.d.ts +16 -0
  57. package/dist/admin/ui/components/ui/dialog.d.ts +27 -0
  58. package/dist/admin/ui/components/ui/group.d.ts +6 -0
  59. package/dist/admin/ui/components/ui/index.d.ts +17 -0
  60. package/dist/admin/ui/components/ui/input.d.ts +5 -0
  61. package/dist/admin/ui/components/ui/join.d.ts +7 -0
  62. package/dist/admin/ui/components/ui/label.d.ts +3 -0
  63. package/dist/admin/ui/components/ui/radio-group.d.ts +13 -0
  64. package/dist/admin/ui/components/ui/relationship-detail-sheet.d.ts +9 -0
  65. package/dist/admin/ui/components/ui/relationship.d.ts +8 -0
  66. package/dist/admin/ui/components/ui/scroll-area.d.ts +7 -0
  67. package/dist/admin/ui/components/ui/select.d.ts +37 -0
  68. package/dist/admin/ui/components/ui/separator.d.ts +8 -0
  69. package/dist/admin/ui/components/ui/sheet.d.ts +28 -0
  70. package/dist/admin/ui/components/ui/tabs.d.ts +17 -0
  71. package/dist/admin/ui/components/ui/utils.d.ts +1 -0
  72. package/dist/admin/ui/hooks/use-debounce.d.ts +1 -0
  73. package/dist/admin/ui/views/collection-list-view.d.ts +5 -0
  74. package/dist/admin/ui/views/dashboard-view.d.ts +10 -0
  75. package/dist/admin/ui/views/document-edit-view.d.ts +7 -0
  76. package/dist/admin/ui/views/global-edit-view.d.ts +19 -0
  77. package/dist/admin/ui/views/init-view.d.ts +4 -0
  78. package/dist/admin/ui/views/login-view.d.ts +4 -0
  79. package/dist/admin/ui/views/media-registry-view.d.ts +7 -0
  80. package/dist/admin/ui/views/settings-view.d.ts +7 -0
  81. package/dist/admin/webcomponent.d.ts +1 -0
  82. package/dist/api.d.ts +6 -0
  83. package/dist/auth/index.d.ts +2107 -0
  84. package/dist/auth/migrations.d.ts +5 -0
  85. package/dist/auth/premissions.d.ts +6 -0
  86. package/dist/chunk-16vgcf3k.js +88 -0
  87. package/dist/chunk-2zm8cy1w.js +9482 -0
  88. package/dist/chunk-5gvbp2qa.js +167 -0
  89. package/dist/chunk-62ev8gnc.js +41 -0
  90. package/dist/chunk-6dhs73zq.js +126 -0
  91. package/dist/chunk-6ew02s0c.js +472 -0
  92. package/dist/chunk-7a9kn0np.js +116 -0
  93. package/dist/chunk-8gkhn1d4.js +309 -0
  94. package/dist/chunk-8sqjbsgt.js +42 -0
  95. package/dist/chunk-9kxpbcb1.js +85 -0
  96. package/dist/chunk-cvdd4eqh.js +110 -0
  97. package/dist/chunk-d3ffeqp9.js +87 -0
  98. package/dist/chunk-dy5t83hr.js +261 -0
  99. package/dist/chunk-f3nvxn63.js +17 -0
  100. package/dist/chunk-hmhcense.js +1352 -0
  101. package/dist/chunk-j4d50hrx.js +20 -0
  102. package/dist/chunk-jwjk85ze.js +15 -0
  103. package/dist/chunk-kwp83w8b.js +250 -0
  104. package/dist/chunk-s8mqwnm1.js +14 -0
  105. package/dist/chunk-srsac177.js +85 -0
  106. package/dist/chunk-v521d72w.js +10 -0
  107. package/dist/chunk-xa7rjsn2.js +20 -0
  108. package/dist/chunk-xg35h5a3.js +15 -0
  109. package/dist/chunk-ybbbqj63.js +130 -0
  110. package/dist/chunk-zvwb67nd.js +332 -0
  111. package/dist/cli/commands/generate-types.d.ts +1 -0
  112. package/dist/cli/commands/init.d.ts +1 -0
  113. package/dist/cli/commands/migrate-commands.d.ts +5 -0
  114. package/dist/cli/commands/seed-command.d.ts +2 -0
  115. package/dist/cli/d1-mock.d.ts +30 -0
  116. package/dist/cli/index.d.ts +5 -0
  117. package/dist/cli/index.test.d.ts +1 -0
  118. package/dist/cli/r2-mock.d.ts +46 -0
  119. package/dist/cli/seeding.d.ts +17 -0
  120. package/dist/client.d.ts +51 -0
  121. package/dist/config-utils.d.ts +6 -0
  122. package/dist/config.d.ts +10 -0
  123. package/dist/db/adapter.d.ts +34 -0
  124. package/dist/db/better-sqlite.d.ts +40 -0
  125. package/dist/db/bun-sqlite.d.ts +40 -0
  126. package/dist/db/d1.d.ts +42 -0
  127. package/dist/db/kysely/data-mapper.d.ts +6 -0
  128. package/dist/db/kysely/field-mapper.d.ts +22 -0
  129. package/dist/db/kysely/migration-generator.d.ts +9 -0
  130. package/dist/db/kysely/query-builder.d.ts +9 -0
  131. package/dist/db/kysely/schema-builder.d.ts +15 -0
  132. package/dist/db/kysely/sql-utils.d.ts +1 -0
  133. package/dist/db/postgres.d.ts +51 -0
  134. package/dist/db/sqlite.d.ts +41 -0
  135. package/dist/db/system-schema.d.ts +2 -0
  136. package/dist/index.d.ts +6 -0
  137. package/dist/runtimes/bun.d.ts +17 -0
  138. package/dist/runtimes/cloudflare-workers.d.ts +10 -0
  139. package/dist/runtimes/next.d.ts +16 -0
  140. package/dist/runtimes/node.d.ts +18 -0
  141. package/dist/schema/collection.d.ts +100 -0
  142. package/dist/schema/fields/base.d.ts +83 -0
  143. package/dist/schema/fields/index.d.ts +135 -0
  144. package/dist/schema/global.d.ts +82 -0
  145. package/dist/schema/index.d.ts +4 -0
  146. package/dist/schema/infer.d.ts +55 -0
  147. package/dist/server/admin-router.d.ts +9 -0
  148. package/dist/server/admin.d.ts +18 -0
  149. package/dist/server/assets.d.ts +47 -0
  150. package/dist/server/collection-router.d.ts +14 -0
  151. package/dist/server/handlers.d.ts +76 -0
  152. package/dist/server/middlewares/admin.d.ts +6 -0
  153. package/dist/server/middlewares/auth.d.ts +16 -0
  154. package/dist/server/middlewares/context.d.ts +9 -0
  155. package/dist/server/middlewares/cors.d.ts +3 -0
  156. package/dist/server/middlewares/database-init.d.ts +11 -0
  157. package/dist/server/middlewares/rate-limit.d.ts +3 -0
  158. package/dist/server/router.d.ts +7 -0
  159. package/dist/server/setup-middlewares.d.ts +17 -0
  160. package/dist/server/system-router.d.ts +9 -0
  161. package/dist/server.d.ts +6 -0
  162. package/dist/src/admin/index.css +47 -0
  163. package/dist/src/admin/index.js +176 -0
  164. package/dist/src/admin/webcomponent.js +19 -0
  165. package/dist/src/api.js +27 -0
  166. package/dist/src/cli/index.js +157 -0
  167. package/dist/src/client.js +9 -0
  168. package/dist/src/db/bun-sqlite.js +523 -0
  169. package/dist/src/db/d1.js +568 -0
  170. package/dist/src/db/postgres.js +520 -0
  171. package/dist/src/db/sqlite.js +534 -0
  172. package/dist/src/index.js +20 -0
  173. package/dist/src/runtimes/bun.js +36 -0
  174. package/dist/src/runtimes/cloudflare-workers.js +29 -0
  175. package/dist/src/runtimes/next.js +26 -0
  176. package/dist/src/runtimes/node.js +38 -0
  177. package/dist/src/server.js +27 -0
  178. package/dist/src/storage/index.js +355 -0
  179. package/dist/storage/adapters/cloudflare-r2.d.ts +6 -0
  180. package/dist/storage/adapters/local.d.ts +6 -0
  181. package/dist/storage/adapters/s3.d.ts +13 -0
  182. package/dist/storage/errors.d.ts +12 -0
  183. package/dist/storage/index.d.ts +5 -0
  184. package/dist/storage/types.d.ts +31 -0
  185. package/dist/types.d.ts +484 -0
  186. package/dist/utils/lexical.d.ts +5 -0
  187. package/dist/utils/logger.d.ts +35 -0
  188. package/dist/validation.d.ts +300 -0
  189. package/dist/validator.d.ts +9 -0
  190. package/global.d.ts +11 -0
  191. package/package.json +151 -0
  192. package/src/admin/api-client.ts +63 -0
  193. package/src/admin/auth-client.ts +40 -0
  194. package/src/admin/custom-field.ts +179 -0
  195. package/src/admin/index.ts +15 -0
  196. package/src/admin/react.tsx +72 -0
  197. package/src/admin/router.ts +9 -0
  198. package/src/admin/stores/admin-queries.ts +121 -0
  199. package/src/admin/stores/auth.ts +61 -0
  200. package/src/admin/stores/column-visibility.ts +67 -0
  201. package/src/admin/stores/config.ts +15 -0
  202. package/src/admin/stores/media.ts +95 -0
  203. package/src/admin/stores/query.ts +13 -0
  204. package/src/admin/stores/ui.ts +29 -0
  205. package/src/admin/ui/admin-client.tsx +283 -0
  206. package/src/admin/ui/admin-layout.tsx +276 -0
  207. package/src/admin/ui/components/ColumnVisibilityToggle.tsx +141 -0
  208. package/src/admin/ui/components/DataDetailSheet.tsx +141 -0
  209. package/src/admin/ui/components/DataDetailView.tsx +175 -0
  210. package/src/admin/ui/components/Table.tsx +67 -0
  211. package/src/admin/ui/components/fields/ArrayField.tsx +166 -0
  212. package/src/admin/ui/components/fields/BlocksField.tsx +202 -0
  213. package/src/admin/ui/components/fields/BooleanField.tsx +50 -0
  214. package/src/admin/ui/components/fields/CollapsibleField.tsx +75 -0
  215. package/src/admin/ui/components/fields/DateField.tsx +45 -0
  216. package/src/admin/ui/components/fields/FileField.tsx +322 -0
  217. package/src/admin/ui/components/fields/GroupField.tsx +50 -0
  218. package/src/admin/ui/components/fields/JoinField.tsx +23 -0
  219. package/src/admin/ui/components/fields/NumberField.tsx +46 -0
  220. package/src/admin/ui/components/fields/RadioField.tsx +62 -0
  221. package/src/admin/ui/components/fields/RelationshipField.tsx +278 -0
  222. package/src/admin/ui/components/fields/RowField.tsx +40 -0
  223. package/src/admin/ui/components/fields/SelectField.tsx +59 -0
  224. package/src/admin/ui/components/fields/TabsField.tsx +101 -0
  225. package/src/admin/ui/components/fields/TextAreaField.tsx +54 -0
  226. package/src/admin/ui/components/fields/TextField.tsx +49 -0
  227. package/src/admin/ui/components/fields/VirtualField.tsx +53 -0
  228. package/src/admin/ui/components/fields/index.tsx +371 -0
  229. package/src/admin/ui/components/fields/richtext-editor/index.tsx +211 -0
  230. package/src/admin/ui/components/fields/richtext-editor/nodes/ImageComponent.tsx +142 -0
  231. package/src/admin/ui/components/fields/richtext-editor/nodes/ImageNode.tsx +95 -0
  232. package/src/admin/ui/components/fields/richtext-editor/plugins/ComponentPickerPlugin.tsx +226 -0
  233. package/src/admin/ui/components/fields/richtext-editor/plugins/EditableSyncPlugin.tsx +16 -0
  234. package/src/admin/ui/components/fields/richtext-editor/plugins/NotionToolbarPlugin.tsx +184 -0
  235. package/src/admin/ui/components/fields/richtext-editor/plugins/SimpleToolbarPlugin.tsx +240 -0
  236. package/src/admin/ui/components/fields/richtext-editor/plugins/ValueSyncPlugin.tsx +40 -0
  237. package/src/admin/ui/components/fields/utils.ts +1 -0
  238. package/src/admin/ui/components/link.tsx +41 -0
  239. package/src/admin/ui/components/media/AssetManagerModal.tsx +334 -0
  240. package/src/admin/ui/components/toast.tsx +72 -0
  241. package/src/admin/ui/components/ui/accordion.tsx +51 -0
  242. package/src/admin/ui/components/ui/alert-dialog.tsx +98 -0
  243. package/src/admin/ui/components/ui/blocks.tsx +32 -0
  244. package/src/admin/ui/components/ui/breadcrumbs.tsx +59 -0
  245. package/src/admin/ui/components/ui/button.tsx +26 -0
  246. package/src/admin/ui/components/ui/collapsible.tsx +124 -0
  247. package/src/admin/ui/components/ui/dialog.tsx +79 -0
  248. package/src/admin/ui/components/ui/group.tsx +20 -0
  249. package/src/admin/ui/components/ui/index.ts +17 -0
  250. package/src/admin/ui/components/ui/input.tsx +12 -0
  251. package/src/admin/ui/components/ui/join.tsx +53 -0
  252. package/src/admin/ui/components/ui/label.tsx +11 -0
  253. package/src/admin/ui/components/ui/radio-group.tsx +75 -0
  254. package/src/admin/ui/components/ui/relationship-detail-sheet.tsx +122 -0
  255. package/src/admin/ui/components/ui/relationship.tsx +58 -0
  256. package/src/admin/ui/components/ui/scroll-area.tsx +19 -0
  257. package/src/admin/ui/components/ui/select.tsx +187 -0
  258. package/src/admin/ui/components/ui/separator.tsx +21 -0
  259. package/src/admin/ui/components/ui/sheet.tsx +106 -0
  260. package/src/admin/ui/components/ui/tabs.tsx +116 -0
  261. package/src/admin/ui/components/ui/utils.ts +3 -0
  262. package/src/admin/ui/hooks/use-debounce.ts +15 -0
  263. package/src/admin/ui/styles/_locale-switcher.scss +33 -0
  264. package/src/admin/ui/styles/accordion.scss +60 -0
  265. package/src/admin/ui/styles/animations.scss +41 -0
  266. package/src/admin/ui/styles/asset-manager.scss +547 -0
  267. package/src/admin/ui/styles/badge.scss +13 -0
  268. package/src/admin/ui/styles/base.scss +22 -0
  269. package/src/admin/ui/styles/button.scss +161 -0
  270. package/src/admin/ui/styles/card.scss +13 -0
  271. package/src/admin/ui/styles/collapsible.scss +75 -0
  272. package/src/admin/ui/styles/data-detail.scss +92 -0
  273. package/src/admin/ui/styles/dialog.scss +102 -0
  274. package/src/admin/ui/styles/empty-state.scss +22 -0
  275. package/src/admin/ui/styles/group.scss +19 -0
  276. package/src/admin/ui/styles/index.scss +33 -0
  277. package/src/admin/ui/styles/input.scss +80 -0
  278. package/src/admin/ui/styles/label.scss +12 -0
  279. package/src/admin/ui/styles/layout.scss +56 -0
  280. package/src/admin/ui/styles/lexical.scss +469 -0
  281. package/src/admin/ui/styles/loading.scss +102 -0
  282. package/src/admin/ui/styles/media-registry.scss +597 -0
  283. package/src/admin/ui/styles/pagination.scss +20 -0
  284. package/src/admin/ui/styles/radio-group.scss +66 -0
  285. package/src/admin/ui/styles/row.scss +17 -0
  286. package/src/admin/ui/styles/scrollbar.scss +36 -0
  287. package/src/admin/ui/styles/select.scss +121 -0
  288. package/src/admin/ui/styles/separator.scss +14 -0
  289. package/src/admin/ui/styles/sheet.scss +152 -0
  290. package/src/admin/ui/styles/sidebar.scss +148 -0
  291. package/src/admin/ui/styles/switch.scss +59 -0
  292. package/src/admin/ui/styles/table.scss +207 -0
  293. package/src/admin/ui/styles/tabs.scss +62 -0
  294. package/src/admin/ui/styles/toast.scss +45 -0
  295. package/src/admin/ui/styles/variables.scss +24 -0
  296. package/src/admin/ui/views/collection-list-view.tsx +720 -0
  297. package/src/admin/ui/views/dashboard-view.tsx +263 -0
  298. package/src/admin/ui/views/document-edit-view.tsx +384 -0
  299. package/src/admin/ui/views/global-edit-view.tsx +226 -0
  300. package/src/admin/ui/views/init-view.tsx +182 -0
  301. package/src/admin/ui/views/login-view.tsx +123 -0
  302. package/src/admin/ui/views/media-registry-view.tsx +1104 -0
  303. package/src/admin/ui/views/settings-view.tsx +729 -0
  304. package/src/admin/webcomponent.tsx +15 -0
  305. package/src/api.ts +9 -0
  306. package/src/auth/index.ts +194 -0
  307. package/src/auth/migrations.ts +87 -0
  308. package/src/auth/premissions.ts +46 -0
  309. package/src/cli/commands/generate-types.ts +116 -0
  310. package/src/cli/commands/init.ts +95 -0
  311. package/src/cli/commands/migrate-commands.ts +160 -0
  312. package/src/cli/commands/seed-command.ts +11 -0
  313. package/src/cli/d1-mock.ts +101 -0
  314. package/src/cli/index.test.ts +84 -0
  315. package/src/cli/index.ts +183 -0
  316. package/src/cli/r2-mock.ts +217 -0
  317. package/src/cli/seeding.ts +405 -0
  318. package/src/client.ts +181 -0
  319. package/src/config-utils.ts +102 -0
  320. package/src/config.ts +49 -0
  321. package/src/db/adapter.ts +53 -0
  322. package/src/db/better-sqlite.ts +630 -0
  323. package/src/db/bun-sqlite.ts +646 -0
  324. package/src/db/d1.ts +711 -0
  325. package/src/db/kysely/data-mapper.ts +142 -0
  326. package/src/db/kysely/field-mapper.ts +148 -0
  327. package/src/db/kysely/migration-generator.ts +223 -0
  328. package/src/db/kysely/query-builder.ts +92 -0
  329. package/src/db/kysely/schema-builder.ts +439 -0
  330. package/src/db/kysely/sql-utils.ts +13 -0
  331. package/src/db/postgres.ts +621 -0
  332. package/src/db/sqlite.ts +658 -0
  333. package/src/db/system-schema.ts +121 -0
  334. package/src/index.ts +13 -0
  335. package/src/runtimes/README.md +59 -0
  336. package/src/runtimes/bun.ts +49 -0
  337. package/src/runtimes/cloudflare-workers.ts +38 -0
  338. package/src/runtimes/next.ts +26 -0
  339. package/src/runtimes/node.ts +52 -0
  340. package/src/schema/collection.ts +184 -0
  341. package/src/schema/fields/base.ts +164 -0
  342. package/src/schema/fields/index.ts +427 -0
  343. package/src/schema/global.ts +145 -0
  344. package/src/schema/index.ts +4 -0
  345. package/src/schema/infer.ts +72 -0
  346. package/src/server/admin-router.ts +20 -0
  347. package/src/server/admin.ts +142 -0
  348. package/src/server/assets.ts +306 -0
  349. package/src/server/collection-router.ts +55 -0
  350. package/src/server/handlers.ts +722 -0
  351. package/src/server/middlewares/admin.ts +27 -0
  352. package/src/server/middlewares/auth.ts +89 -0
  353. package/src/server/middlewares/context.ts +17 -0
  354. package/src/server/middlewares/cors.ts +24 -0
  355. package/src/server/middlewares/database-init.ts +74 -0
  356. package/src/server/middlewares/rate-limit.ts +71 -0
  357. package/src/server/router.ts +47 -0
  358. package/src/server/setup-middlewares.ts +58 -0
  359. package/src/server/system-router.ts +35 -0
  360. package/src/server.ts +9 -0
  361. package/src/storage/adapters/cloudflare-r2.ts +136 -0
  362. package/src/storage/adapters/local.ts +146 -0
  363. package/src/storage/adapters/s3.ts +186 -0
  364. package/src/storage/errors.ts +46 -0
  365. package/src/storage/index.ts +5 -0
  366. package/src/storage/types.ts +39 -0
  367. package/src/types.ts +577 -0
  368. package/src/utils/lexical.ts +37 -0
  369. package/src/utils/logger.ts +73 -0
  370. package/src/validation.ts +429 -0
  371. package/src/validator.ts +179 -0
  372. package/test/admin-custom-field.test.ts +162 -0
  373. package/test/admin-react-field.test.tsx +134 -0
  374. package/test/api-features.test.ts +78 -0
  375. package/test/api.test.ts +178 -0
  376. package/test/auth.test.ts +62 -0
  377. package/test/cli-integration.test.ts +146 -0
  378. package/test/cli.test.ts +25 -0
  379. package/test/db/postgres.test.ts +95 -0
  380. package/test/db/sqlite-filter.test.ts +53 -0
  381. package/test/db/sqlite.test.ts +82 -0
  382. package/test/engine-features.test.ts +79 -0
  383. package/test/globals.test.ts +74 -0
  384. package/test/integration-tmp/db-app/opacacms.config.ts +15 -0
  385. package/test/integration-tmp/my-sqlite-app/opacacms.config.ts +25 -0
  386. package/test/integration-tmp/my-test-app/index.ts +8 -0
  387. package/test/integration-tmp/my-test-app/opacacms.config.ts +16 -0
  388. package/test/integration-tmp/my-test-app/package.json +12 -0
  389. package/test/populate.test.ts +79 -0
  390. package/test/runtimes.test.ts +43 -0
  391. package/test/schema-builder.test.ts +107 -0
  392. package/test/schema-features.test.ts +63 -0
  393. package/test/seeding.test.ts +68 -0
  394. package/test/storage/local.test.ts +72 -0
  395. package/test/storage/s3.test.ts +60 -0
  396. package/test/structural-data.test.ts +100 -0
  397. package/test/test-setup.ts +11 -0
  398. package/test/validation.test.ts +162 -0
  399. package/tsconfig.json +42 -0
@@ -0,0 +1,472 @@
1
+ import {
2
+ logger
3
+ } from "./chunk-62ev8gnc.js";
4
+ import {
5
+ flattenFields,
6
+ getRelationalFields,
7
+ mapFieldToPostgresType,
8
+ mapFieldToSQLiteType,
9
+ toSnakeCase
10
+ } from "./chunk-cvdd4eqh.js";
11
+ import {
12
+ getSystemCollections,
13
+ init_system_schema
14
+ } from "./chunk-ybbbqj63.js";
15
+
16
+ // src/db/kysely/data-mapper.ts
17
+ function toCamelCase(str) {
18
+ if (str.startsWith("_"))
19
+ return str;
20
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
21
+ }
22
+ function flattenPayload(payload, prefix = "", excludeKeys = []) {
23
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload))
24
+ return payload;
25
+ const result = {};
26
+ for (const key in payload) {
27
+ if (Object.hasOwn(payload, key)) {
28
+ const value = payload[key];
29
+ if (value === undefined)
30
+ continue;
31
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !excludeKeys.includes(key)) {
32
+ if (key.startsWith("_") && prefix === "") {
33
+ result[key] = value;
34
+ } else {
35
+ const flatNested = flattenPayload(value, `${prefix}${key}__`);
36
+ Object.assign(result, flatNested);
37
+ }
38
+ } else {
39
+ if (value === undefined)
40
+ continue;
41
+ const colName = `${prefix}${toSnakeCase(key)}`;
42
+ if (typeof value === "object" && value !== null) {
43
+ result[colName] = JSON.stringify(value);
44
+ } else {
45
+ result[colName] = value;
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+ function unflattenRow(row) {
53
+ if (typeof row !== "object" || row === null || Array.isArray(row))
54
+ return row;
55
+ const result = {};
56
+ for (const key in row) {
57
+ if (Object.hasOwn(row, key)) {
58
+ let value = row[key];
59
+ if (typeof value === "string") {
60
+ const trimmed = value.trim();
61
+ if (trimmed.startsWith("[") && trimmed.endsWith("]") || trimmed.startsWith("{") && trimmed.endsWith("}")) {
62
+ try {
63
+ value = JSON.parse(value);
64
+ } catch (e) {}
65
+ }
66
+ }
67
+ if (key === "created_at" || key === "updated_at" || key === "createdAt" || key === "updatedAt") {
68
+ if (typeof value === "string" && !value.includes("T") && value.includes(" ")) {
69
+ value = `${value.replace(" ", "T")}Z`;
70
+ }
71
+ result[toCamelCase(key)] = value;
72
+ continue;
73
+ }
74
+ if (key.startsWith("_")) {
75
+ result[key] = value;
76
+ continue;
77
+ }
78
+ const parts = key.split("__");
79
+ if (parts.length === 1) {
80
+ result[toCamelCase(key)] = value;
81
+ continue;
82
+ }
83
+ let current = result;
84
+ let collision = false;
85
+ const path = [];
86
+ for (let i = 0;i < parts.length - 1; i++) {
87
+ const part = toCamelCase(parts[i]);
88
+ if (current[part] !== undefined && (typeof current[part] !== "object" || current[part] === null)) {
89
+ collision = true;
90
+ break;
91
+ }
92
+ path.push({ obj: current, key: part });
93
+ if (!current[part]) {
94
+ current[part] = {};
95
+ }
96
+ current = current[part];
97
+ }
98
+ if (!collision) {
99
+ const lastPart = toCamelCase(parts[parts.length - 1]);
100
+ if (current[lastPart] !== undefined && typeof current[lastPart] === "object") {
101
+ collision = true;
102
+ } else {
103
+ current[lastPart] = value;
104
+ }
105
+ }
106
+ if (collision) {
107
+ result[toCamelCase(key)] = value;
108
+ }
109
+ }
110
+ }
111
+ return result;
112
+ }
113
+
114
+ // src/db/kysely/query-builder.ts
115
+ function buildKyselyWhere(eb, query) {
116
+ if (!query || Object.keys(query).length === 0)
117
+ return;
118
+ const conditions = [];
119
+ for (const [key, value] of Object.entries(query)) {
120
+ if (value === undefined)
121
+ continue;
122
+ if (key === "or" && Array.isArray(value)) {
123
+ const orConditions = value.map((v) => buildKyselyWhere(eb, v)).filter((c) => c !== undefined);
124
+ if (orConditions.length > 0) {
125
+ conditions.push(eb.or(orConditions));
126
+ }
127
+ continue;
128
+ }
129
+ if (key === "and" && Array.isArray(value)) {
130
+ const andConditions = value.map((v) => buildKyselyWhere(eb, v)).filter((c) => c !== undefined);
131
+ if (andConditions.length > 0) {
132
+ conditions.push(eb.and(andConditions));
133
+ }
134
+ continue;
135
+ }
136
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
137
+ for (const [op, val] of Object.entries(value)) {
138
+ if (val === undefined)
139
+ continue;
140
+ switch (op) {
141
+ case "gt":
142
+ conditions.push(eb(key, ">", val));
143
+ break;
144
+ case "gte":
145
+ conditions.push(eb(key, ">=", val));
146
+ break;
147
+ case "lt":
148
+ conditions.push(eb(key, "<", val));
149
+ break;
150
+ case "lte":
151
+ conditions.push(eb(key, "<=", val));
152
+ break;
153
+ case "like":
154
+ conditions.push(eb(key, "like", String(val)));
155
+ break;
156
+ case "ne":
157
+ if (val === null)
158
+ conditions.push(eb(key, "is not", null));
159
+ else
160
+ conditions.push(eb(key, "!=", val));
161
+ break;
162
+ case "in":
163
+ if (Array.isArray(val))
164
+ conditions.push(eb(key, "in", val));
165
+ break;
166
+ case "is":
167
+ if (val === null)
168
+ conditions.push(eb(key, "is", null));
169
+ break;
170
+ default:
171
+ if (val === null)
172
+ conditions.push(eb(key, "is", null));
173
+ else
174
+ conditions.push(eb(key, "=", val));
175
+ break;
176
+ }
177
+ }
178
+ } else {
179
+ if (value === null) {
180
+ conditions.push(eb(key, "is", null));
181
+ } else {
182
+ conditions.push(eb(key, "=", value));
183
+ }
184
+ }
185
+ }
186
+ if (conditions.length === 0)
187
+ return;
188
+ if (conditions.length === 1)
189
+ return conditions[0];
190
+ return eb.and(conditions);
191
+ }
192
+
193
+ // src/db/kysely/schema-builder.ts
194
+ import { sql } from "kysely";
195
+ init_system_schema();
196
+
197
+ // src/db/kysely/sql-utils.ts
198
+ function assertSafeIdentifier(identifier) {
199
+ if (typeof identifier !== "string" || !identifier) {
200
+ throw new Error(`Invalid identifier: must be a non-empty string. Got: ${identifier}`);
201
+ }
202
+ const isValid = /^[a-zA-Z0-9_-]+$/.test(identifier);
203
+ if (!isValid) {
204
+ throw new Error(`API Error: Unsafe SQL identifier detected: "${identifier}". Identifiers can only contain alphanumeric characters, underscores, and hyphens.`);
205
+ }
206
+ }
207
+
208
+ // src/db/kysely/schema-builder.ts
209
+ async function pushSchema(db, dialect, collections, globals = [], options = {}) {
210
+ const rawSchemas = [...getSystemCollections(), ...collections, ...globals];
211
+ const allSchemas = [];
212
+ const seenSlugs = new Set;
213
+ for (const schema of rawSchemas) {
214
+ if (!seenSlugs.has(schema.slug)) {
215
+ seenSlugs.add(schema.slug);
216
+ allSchemas.push(schema);
217
+ }
218
+ }
219
+ const { pushDestructive } = options;
220
+ logger.debug(`Starting schema push via Kysely (${dialect})...`);
221
+ const mapType = dialect === "postgres" ? mapFieldToPostgresType : mapFieldToSQLiteType;
222
+ let existingTables = [];
223
+ try {
224
+ if (dialect === "postgres") {
225
+ const res = await sql`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'`.execute(db);
226
+ existingTables = res.rows.map((r) => r.table_name);
227
+ } else {
228
+ const res = await sql`SELECT name FROM sqlite_master WHERE type='table'`.execute(db);
229
+ existingTables = res.rows.map((r) => r.name);
230
+ }
231
+ } catch (_) {
232
+ logger.warn("Failed to fetch existing tables:");
233
+ }
234
+ logger.info(`Existing tables: ${existingTables.join(", ")}`);
235
+ const existingTableSet = new Set(existingTables);
236
+ const expectedTableNames = new Set;
237
+ for (const collection of allSchemas) {
238
+ const slug = collection.slug;
239
+ const tableName = toSnakeCase(slug);
240
+ assertSafeIdentifier(tableName);
241
+ expectedTableNames.add(tableName);
242
+ const flattenedFields = flattenFields(collection.fields);
243
+ const hasTimestamps = collection.timestamps !== false;
244
+ const versions = collection.versions;
245
+ if (!existingTableSet.has(tableName)) {
246
+ logger.info(` -> Creating table: ${logger.format("green", `"${tableName}"`)}`);
247
+ let builder = db.schema.createTable(tableName).ifNotExists();
248
+ builder = builder.addColumn("id", "text", (col) => col.primaryKey());
249
+ for (const field of flattenedFields) {
250
+ if (field.type === "relationship" && "hasMany" in field && field.hasMany)
251
+ continue;
252
+ const colName = toSnakeCase(field.name);
253
+ if (colName === "id")
254
+ continue;
255
+ assertSafeIdentifier(colName);
256
+ const colType = mapType(field);
257
+ builder = builder.addColumn(colName, colType, (col) => {
258
+ let c = col;
259
+ if (field.unique)
260
+ c = c.unique();
261
+ if (field.required)
262
+ c = c.notNull();
263
+ if (field.defaultValue !== undefined)
264
+ c = c.defaultTo(field.defaultValue);
265
+ return c;
266
+ });
267
+ }
268
+ if (versions?.drafts) {
269
+ builder = builder.addColumn("_status", "text", (col) => col.defaultTo("draft"));
270
+ }
271
+ if (hasTimestamps) {
272
+ const ts = collection.timestamps ?? {};
273
+ const config = typeof ts === "object" ? ts : {};
274
+ const createdField = toSnakeCase(config.createdAt ?? "createdAt");
275
+ const updatedField = toSnakeCase(config.updatedAt ?? "updatedAt");
276
+ const tsType = dialect === "postgres" ? "timestamp with time zone" : "text";
277
+ builder = builder.addColumn(createdField, tsType, (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`));
278
+ builder = builder.addColumn(updatedField, tsType, (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`));
279
+ }
280
+ await builder.execute();
281
+ if (versions) {
282
+ const versionsTable = `${slug}_versions`.toLowerCase();
283
+ assertSafeIdentifier(versionsTable);
284
+ expectedTableNames.add(versionsTable);
285
+ if (!existingTableSet.has(versionsTable)) {
286
+ logger.info(` -> Creating versions table: ${logger.format("green", `"${versionsTable}"`)}`);
287
+ await db.schema.createTable(versionsTable).addColumn("id", "text", (col) => col.primaryKey()).addColumn("_parent_id", "text", (col) => col.notNull()).addColumn("_version_data", dialect === "postgres" ? "jsonb" : "text", (col) => col.notNull()).addColumn("_status", "text").addColumn("created_at", dialect === "postgres" ? "timestamp with time zone" : "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
288
+ }
289
+ }
290
+ } else {
291
+ let existingColumns = [];
292
+ try {
293
+ if (dialect === "postgres") {
294
+ const res = await sql`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ${slug}`.execute(db);
295
+ existingColumns = res.rows.map((r) => r.column_name);
296
+ } else {
297
+ const res = await sql`PRAGMA table_info(${sql.id(slug)})`.execute(db);
298
+ existingColumns = res.rows.map((r) => r.name);
299
+ }
300
+ } catch (e) {
301
+ logger.warn(`Failed to fetch columns for ${slug}:`, e);
302
+ }
303
+ const existingColSet = new Set(existingColumns);
304
+ for (const field of flattenedFields) {
305
+ if (field.type === "relationship" && "hasMany" in field && field.hasMany)
306
+ continue;
307
+ const colName = toSnakeCase(field.name);
308
+ if (colName === "id")
309
+ continue;
310
+ assertSafeIdentifier(colName);
311
+ if (!existingColSet.has(colName)) {
312
+ logger.info(` -> Adding missing column: ${logger.format("green", `${slug}.${colName}`)}`);
313
+ const colType = mapType(field);
314
+ await db.schema.alterTable(slug).addColumn(colName, colType).execute();
315
+ }
316
+ }
317
+ if (versions?.drafts && !existingColSet.has("_status")) {
318
+ logger.info(` -> Adding missing status column to: ${slug}`);
319
+ await db.schema.alterTable(slug).addColumn("_status", "text", (col) => col.defaultTo("draft")).execute();
320
+ }
321
+ if (hasTimestamps) {
322
+ const ts = collection.timestamps ?? {};
323
+ const config = typeof ts === "object" ? ts : {};
324
+ const createdField = toSnakeCase(config.createdAt ?? "createdAt");
325
+ const updatedField = toSnakeCase(config.updatedAt ?? "updatedAt");
326
+ const tsType = dialect === "postgres" ? "timestamp with time zone" : "text";
327
+ if (!existingColSet.has(createdField)) {
328
+ logger.info(` -> Adding missing timestamp column: ${slug}.${createdField}`);
329
+ await db.schema.alterTable(slug).addColumn(createdField, tsType, (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
330
+ }
331
+ if (!existingColSet.has(updatedField)) {
332
+ logger.info(` -> Adding missing timestamp column: ${slug}.${updatedField}`);
333
+ await db.schema.alterTable(slug).addColumn(updatedField, tsType, (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
334
+ }
335
+ }
336
+ if (versions) {
337
+ const versionsTable = `${slug}_versions`.toLowerCase();
338
+ expectedTableNames.add(versionsTable);
339
+ if (!existingTableSet.has(versionsTable)) {
340
+ logger.info(` -> Creating missing versions table for: ${slug}`);
341
+ await db.schema.createTable(versionsTable).addColumn("id", "text", (col) => col.primaryKey()).addColumn("_parent_id", "text", (col) => col.notNull()).addColumn("_version_data", dialect === "postgres" ? "jsonb" : "text", (col) => col.notNull()).addColumn("_status", "text").addColumn("created_at", dialect === "postgres" ? "timestamp with time zone" : "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
342
+ }
343
+ }
344
+ if (pushDestructive) {
345
+ const expectedCols = new Set(flattenedFields.filter((f) => !(f.type === "relationship" && f.hasMany)).map((f) => toSnakeCase(f.name)));
346
+ if (hasTimestamps) {
347
+ const ts = collection.timestamps ?? {};
348
+ const config = typeof ts === "object" ? ts : {};
349
+ expectedCols.add(toSnakeCase(config.createdAt ?? "createdAt"));
350
+ expectedCols.add(toSnakeCase(config.updatedAt ?? "updatedAt"));
351
+ }
352
+ if (versions?.drafts)
353
+ expectedCols.add("_status");
354
+ expectedCols.add("id");
355
+ for (const existingCol of existingColumns) {
356
+ if (!expectedCols.has(existingCol)) {
357
+ logger.info(` -> Dropping stale column: ${logger.format("red", `${slug}.${existingCol}`)}`);
358
+ try {
359
+ await db.schema.alterTable(slug).dropColumn(existingCol).execute();
360
+ } catch (_) {
361
+ logger.warn(`Failed to drop column ${existingCol}.`);
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+ const relationalFields = getRelationalFields(collection.fields);
368
+ for (const field of relationalFields) {
369
+ if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
370
+ const sourceIdType = "text";
371
+ const colName = toSnakeCase(field.name);
372
+ const joinTableName = `${slug}_${colName}_relations`.toLowerCase();
373
+ assertSafeIdentifier(joinTableName);
374
+ expectedTableNames.add(joinTableName);
375
+ if (!existingTableSet.has(joinTableName)) {
376
+ logger.info(` -> Creating relation table: ${logger.format("green", `"${joinTableName}"`)}`);
377
+ await db.schema.createTable(joinTableName).ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("source_id", sourceIdType, (col) => col.notNull()).addColumn("target_id", "text", (col) => col.notNull()).addColumn("order", "integer", (col) => col.defaultTo(0)).execute();
378
+ }
379
+ }
380
+ }
381
+ for (const field of relationalFields) {
382
+ if (field.type === "blocks" && field.blocks && Array.isArray(field.blocks)) {
383
+ const parentIdType = "text";
384
+ for (const block of field.blocks) {
385
+ const blockName = toSnakeCase(field.name);
386
+ const blockSlug = toSnakeCase(block.slug);
387
+ assertSafeIdentifier(blockName);
388
+ assertSafeIdentifier(blockSlug);
389
+ const blockTableName = `${slug}_${blockName}_${blockSlug}`.toLowerCase();
390
+ assertSafeIdentifier(blockTableName);
391
+ expectedTableNames.add(blockTableName);
392
+ if (!existingTableSet.has(blockTableName)) {
393
+ logger.info(` -> Creating block table: ${logger.format("green", `"${blockTableName}"`)}`);
394
+ let bBuilder = db.schema.createTable(blockTableName).ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("_parent_id", parentIdType, (col) => col.notNull()).addColumn("_order", "integer", (col) => col.defaultTo(0)).addColumn("block_type", "text", (col) => col.notNull().defaultTo(block.slug));
395
+ const blockFlattened = flattenFields(block.fields);
396
+ for (const bField of blockFlattened) {
397
+ const bColName = toSnakeCase(bField.name);
398
+ if (bColName === "id")
399
+ continue;
400
+ assertSafeIdentifier(bColName);
401
+ const bColType = mapType(bField);
402
+ bBuilder = bBuilder.addColumn(bColName, bColType, (col) => {
403
+ let c = col;
404
+ if (bField.unique)
405
+ c = c.unique();
406
+ if (bField.required)
407
+ c = c.notNull();
408
+ if (bField.defaultValue !== undefined)
409
+ c = c.defaultTo(bField.defaultValue);
410
+ return c;
411
+ });
412
+ }
413
+ const tsType = dialect === "postgres" ? "timestamp with time zone" : "text";
414
+ bBuilder = bBuilder.addColumn("created_at", tsType, (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`));
415
+ bBuilder = bBuilder.addColumn("updated_at", tsType, (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`));
416
+ await bBuilder.execute();
417
+ } else {
418
+ let existingBCols = [];
419
+ try {
420
+ if (dialect === "postgres") {
421
+ const res = await sql`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ${blockTableName}`.execute(db);
422
+ existingBCols = res.rows.map((r) => r.column_name);
423
+ } else {
424
+ const res = await sql`PRAGMA table_info(${sql.id(blockTableName)})`.execute(db);
425
+ existingBCols = res.rows.map((r) => r.name);
426
+ }
427
+ } catch (_) {}
428
+ const existingBColSet = new Set(existingBCols);
429
+ const blockFlattened = flattenFields(block.fields);
430
+ for (const bField of blockFlattened) {
431
+ const bColName = toSnakeCase(bField.name);
432
+ if (bColName === "id")
433
+ continue;
434
+ if (!existingBColSet.has(bColName)) {
435
+ const bColType = mapType(bField);
436
+ await db.schema.alterTable(blockTableName).addColumn(bColName, bColType).execute();
437
+ }
438
+ }
439
+ if (!existingBColSet.has("_parent_id")) {
440
+ await db.schema.alterTable(blockTableName).addColumn("_parent_id", "text", (col) => col.notNull().defaultTo("")).execute();
441
+ }
442
+ if (!existingBColSet.has("_order")) {
443
+ await db.schema.alterTable(blockTableName).addColumn("_order", "integer", (col) => col.defaultTo(0)).execute();
444
+ }
445
+ if (!existingBColSet.has("block_type")) {
446
+ await db.schema.alterTable(blockTableName).addColumn("block_type", "text", (col) => col.notNull().defaultTo(block.slug)).execute();
447
+ }
448
+ }
449
+ }
450
+ }
451
+ }
452
+ }
453
+ if (pushDestructive) {
454
+ for (const tableName of existingTables) {
455
+ if (!expectedTableNames.has(tableName) && !tableName.startsWith("better_auth") && !tableName.startsWith("__kysely")) {
456
+ logger.info(` -> Dropping stale table: ${logger.format("red", `"${tableName}"`)}`);
457
+ try {
458
+ await db.schema.dropTable(tableName).ifExists().cascade().execute();
459
+ } catch (_) {
460
+ try {
461
+ await db.schema.dropTable(tableName).ifExists().execute();
462
+ } catch (_2) {
463
+ logger.warn(`Failed to drop table ${tableName}`);
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ logger.success(`Schema push to Kysely (${dialect}) completed.`);
470
+ }
471
+
472
+ export { flattenPayload, unflattenRow, buildKyselyWhere, pushSchema };
@@ -0,0 +1,116 @@
1
+ import"./chunk-8sqjbsgt.js";
2
+
3
+ // src/cli/commands/generate-types.ts
4
+ import fs from "node:fs";
5
+ import { resolve } from "node:path";
6
+ async function generateTypesCommand(url, token, out) {
7
+ console.log(`[OpacaCMS] Fetching metadata from ${url}/api/__admin/metadata...`);
8
+ let data;
9
+ try {
10
+ const res = await fetch(`${url.replace(/\/$/, "")}/api/__admin/metadata`, {
11
+ headers: {
12
+ Authorization: `Bearer ${token}`
13
+ }
14
+ });
15
+ if (!res.ok) {
16
+ if (res.status === 401 || res.status === 403) {
17
+ throw new Error("Authentication failed. Ensure your API Key is correct and has the necessary permissions.");
18
+ }
19
+ const errorText = await res.text().catch(() => "Unknown error");
20
+ throw new Error(`Server responded with ${res.status}: ${errorText}`);
21
+ }
22
+ data = await res.json();
23
+ } catch (err) {
24
+ console.error(`[OpacaCMS] Failed to fetch metadata: ${err.message}`);
25
+ process.exit(1);
26
+ }
27
+ console.log(`[OpacaCMS] Successfully fetched metadata for ${data.collections.length} collections and ${data.globals?.length || 0} globals.`);
28
+ let dtsContent = `// Automatically generated by OpacaCMS CLI
29
+ // Do not edit this file manually
30
+
31
+ export interface OpacaAsset {
32
+ assetId: string;
33
+ url: string;
34
+ filename: string;
35
+ mime_type: string;
36
+ filesize: number;
37
+ meta?: Record<string, any>;
38
+ }
39
+
40
+ export interface OpacaCollections {
41
+ collections: {
42
+ `;
43
+ for (const collection of data.collections) {
44
+ dtsContent += ` "${collection.slug}": {
45
+ `;
46
+ dtsContent += ` id: string | number;
47
+ `;
48
+ for (const field of collection.fields) {
49
+ const typeStr = mapFieldToTsType(field.type);
50
+ const isOptional = field.required ? "" : "?";
51
+ dtsContent += ` ${field.name}${isOptional}: ${typeStr};
52
+ `;
53
+ }
54
+ if (collection.timestamps) {
55
+ dtsContent += ` createdAt: string;
56
+ `;
57
+ dtsContent += ` updatedAt: string;
58
+ `;
59
+ }
60
+ dtsContent += ` };
61
+ `;
62
+ }
63
+ dtsContent += ` };
64
+ `;
65
+ if (data.globals && data.globals.length > 0) {
66
+ dtsContent += ` globals: {
67
+ `;
68
+ for (const globalConf of data.globals) {
69
+ dtsContent += ` "${globalConf.slug}": {
70
+ `;
71
+ for (const field of globalConf.fields) {
72
+ const typeStr = mapFieldToTsType(field.type);
73
+ const isOptional = field.required ? "" : "?";
74
+ dtsContent += ` ${field.name}${isOptional}: ${typeStr};
75
+ `;
76
+ }
77
+ dtsContent += ` };
78
+ `;
79
+ }
80
+ dtsContent += ` };
81
+ `;
82
+ } else {
83
+ dtsContent += ` globals: Record<string, any>;
84
+ `;
85
+ }
86
+ dtsContent += `}
87
+ `;
88
+ const outputPath = resolve(process.cwd(), out || "opaca-types.d.ts");
89
+ fs.writeFileSync(outputPath, dtsContent);
90
+ console.log(`[OpacaCMS] Types successfully generated at ${outputPath}`);
91
+ }
92
+ function mapFieldToTsType(type) {
93
+ switch (type) {
94
+ case "text":
95
+ case "textarea":
96
+ case "richtext":
97
+ case "select":
98
+ case "date":
99
+ return "string";
100
+ case "number":
101
+ return "number";
102
+ case "boolean":
103
+ return "boolean";
104
+ case "json":
105
+ return "any | any[]";
106
+ case "file":
107
+ return "OpacaAsset";
108
+ case "relationship":
109
+ return "string | number | any";
110
+ default:
111
+ return "any";
112
+ }
113
+ }
114
+ export {
115
+ generateTypesCommand
116
+ };