@dyrected/core 2.5.53 → 2.5.55

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 (405) hide show
  1. package/package.json +1 -1
  2. package/dist/__tests__/admin-auth.test.d.ts +0 -2
  3. package/dist/__tests__/admin-auth.test.d.ts.map +0 -1
  4. package/dist/__tests__/admin-auth.test.js +0 -440
  5. package/dist/__tests__/admin-auth.test.js.map +0 -1
  6. package/dist/__tests__/app.test.d.ts +0 -2
  7. package/dist/__tests__/app.test.d.ts.map +0 -1
  8. package/dist/__tests__/app.test.js +0 -27
  9. package/dist/__tests__/app.test.js.map +0 -1
  10. package/dist/__tests__/config.test.d.ts +0 -2
  11. package/dist/__tests__/config.test.d.ts.map +0 -1
  12. package/dist/__tests__/config.test.js +0 -91
  13. package/dist/__tests__/config.test.js.map +0 -1
  14. package/dist/__tests__/deleteMany.test.d.ts +0 -2
  15. package/dist/__tests__/deleteMany.test.d.ts.map +0 -1
  16. package/dist/__tests__/deleteMany.test.js +0 -75
  17. package/dist/__tests__/deleteMany.test.js.map +0 -1
  18. package/dist/__tests__/depth.test.d.ts +0 -2
  19. package/dist/__tests__/depth.test.d.ts.map +0 -1
  20. package/dist/__tests__/depth.test.js +0 -81
  21. package/dist/__tests__/depth.test.js.map +0 -1
  22. package/dist/__tests__/dynamic-options.test.d.ts +0 -2
  23. package/dist/__tests__/dynamic-options.test.d.ts.map +0 -1
  24. package/dist/__tests__/dynamic-options.test.js +0 -132
  25. package/dist/__tests__/dynamic-options.test.js.map +0 -1
  26. package/dist/__tests__/email.service.test.d.ts +0 -2
  27. package/dist/__tests__/email.service.test.d.ts.map +0 -1
  28. package/dist/__tests__/email.service.test.js +0 -46
  29. package/dist/__tests__/email.service.test.js.map +0 -1
  30. package/dist/__tests__/field-inference.test-types.d.ts +0 -24
  31. package/dist/__tests__/field-inference.test-types.d.ts.map +0 -1
  32. package/dist/__tests__/field-inference.test-types.js +0 -87
  33. package/dist/__tests__/field-inference.test-types.js.map +0 -1
  34. package/dist/__tests__/fixtures/editorial-workflow.d.ts +0 -9
  35. package/dist/__tests__/fixtures/editorial-workflow.d.ts.map +0 -1
  36. package/dist/__tests__/fixtures/editorial-workflow.js +0 -12
  37. package/dist/__tests__/fixtures/editorial-workflow.js.map +0 -1
  38. package/dist/__tests__/hooks.test.d.ts +0 -2
  39. package/dist/__tests__/hooks.test.d.ts.map +0 -1
  40. package/dist/__tests__/hooks.test.js +0 -320
  41. package/dist/__tests__/hooks.test.js.map +0 -1
  42. package/dist/__tests__/mocks.d.ts +0 -74
  43. package/dist/__tests__/mocks.d.ts.map +0 -1
  44. package/dist/__tests__/mocks.js +0 -159
  45. package/dist/__tests__/mocks.js.map +0 -1
  46. package/dist/__tests__/openapi.test.d.ts +0 -2
  47. package/dist/__tests__/openapi.test.d.ts.map +0 -1
  48. package/dist/__tests__/openapi.test.js +0 -95
  49. package/dist/__tests__/openapi.test.js.map +0 -1
  50. package/dist/__tests__/parse-sort.test.d.ts +0 -2
  51. package/dist/__tests__/parse-sort.test.d.ts.map +0 -1
  52. package/dist/__tests__/parse-sort.test.js +0 -24
  53. package/dist/__tests__/parse-sort.test.js.map +0 -1
  54. package/dist/__tests__/preferences.test.d.ts +0 -2
  55. package/dist/__tests__/preferences.test.d.ts.map +0 -1
  56. package/dist/__tests__/preferences.test.js +0 -168
  57. package/dist/__tests__/preferences.test.js.map +0 -1
  58. package/dist/__tests__/router.test.d.ts +0 -2
  59. package/dist/__tests__/router.test.d.ts.map +0 -1
  60. package/dist/__tests__/router.test.js +0 -52
  61. package/dist/__tests__/router.test.js.map +0 -1
  62. package/dist/__tests__/where.test.d.ts +0 -2
  63. package/dist/__tests__/where.test.d.ts.map +0 -1
  64. package/dist/__tests__/where.test.js +0 -105
  65. package/dist/__tests__/where.test.js.map +0 -1
  66. package/dist/__tests__/workflow.test.d.ts +0 -2
  67. package/dist/__tests__/workflow.test.d.ts.map +0 -1
  68. package/dist/__tests__/workflow.test.js +0 -497
  69. package/dist/__tests__/workflow.test.js.map +0 -1
  70. package/dist/app-B2tg7Djj.d.cts +0 -1575
  71. package/dist/app-B2tg7Djj.d.ts +0 -1575
  72. package/dist/app-BElen1tP.d.cts +0 -1690
  73. package/dist/app-BElen1tP.d.ts +0 -1690
  74. package/dist/app-BOrsS7Tz.d.cts +0 -1771
  75. package/dist/app-BOrsS7Tz.d.ts +0 -1771
  76. package/dist/app-Bh4_Opv0.d.cts +0 -1522
  77. package/dist/app-Bh4_Opv0.d.ts +0 -1522
  78. package/dist/app-BibuoHQG.d.cts +0 -1764
  79. package/dist/app-BibuoHQG.d.ts +0 -1764
  80. package/dist/app-Bv9gaDAN.d.cts +0 -561
  81. package/dist/app-Bv9gaDAN.d.ts +0 -561
  82. package/dist/app-BvG3bRc8.d.cts +0 -419
  83. package/dist/app-BvG3bRc8.d.ts +0 -419
  84. package/dist/app-C-HgyliB.d.cts +0 -1694
  85. package/dist/app-C-HgyliB.d.ts +0 -1694
  86. package/dist/app-C3B9N1KR.d.cts +0 -1522
  87. package/dist/app-C3B9N1KR.d.ts +0 -1522
  88. package/dist/app-Cc2mG3zi.d.cts +0 -373
  89. package/dist/app-Cc2mG3zi.d.ts +0 -373
  90. package/dist/app-Ctfmo2Re.d.cts +0 -1692
  91. package/dist/app-Ctfmo2Re.d.ts +0 -1692
  92. package/dist/app-D0ffogDd.d.cts +0 -1690
  93. package/dist/app-D0ffogDd.d.ts +0 -1690
  94. package/dist/app-D2uR47gK.d.cts +0 -1690
  95. package/dist/app-D2uR47gK.d.ts +0 -1690
  96. package/dist/app-DDJJa0ep.d.cts +0 -1621
  97. package/dist/app-DDJJa0ep.d.ts +0 -1621
  98. package/dist/app-DO1s9YW1.d.cts +0 -1621
  99. package/dist/app-DO1s9YW1.d.ts +0 -1621
  100. package/dist/app-DTP3-9PJ.d.cts +0 -561
  101. package/dist/app-DTP3-9PJ.d.ts +0 -561
  102. package/dist/app-DZt5nslu.d.cts +0 -374
  103. package/dist/app-DZt5nslu.d.ts +0 -377
  104. package/dist/app-DbKDGYTI.d.cts +0 -566
  105. package/dist/app-DbKDGYTI.d.ts +0 -566
  106. package/dist/app-Dk3SzV1a.d.cts +0 -374
  107. package/dist/app-Dk3SzV1a.d.ts +0 -374
  108. package/dist/app-DnX9mNsh.d.cts +0 -374
  109. package/dist/app-DnX9mNsh.d.ts +0 -374
  110. package/dist/app-DqRO-CMi.d.cts +0 -1457
  111. package/dist/app-DqRO-CMi.d.ts +0 -1457
  112. package/dist/app-DvaFpOtj.d.cts +0 -398
  113. package/dist/app-DvaFpOtj.d.ts +0 -398
  114. package/dist/app-FGzip4XM.d.cts +0 -1563
  115. package/dist/app-FGzip4XM.d.ts +0 -1563
  116. package/dist/app-KApbf4XL.d.cts +0 -362
  117. package/dist/app-KApbf4XL.d.ts +0 -362
  118. package/dist/app-T0alZAE0.d.cts +0 -383
  119. package/dist/app-T0alZAE0.d.ts +0 -383
  120. package/dist/app-aW2FMuYM.d.cts +0 -1759
  121. package/dist/app-aW2FMuYM.d.ts +0 -1759
  122. package/dist/app-config-2vF6W56z.d.cts +0 -1335
  123. package/dist/app-config-2vF6W56z.d.ts +0 -1335
  124. package/dist/app-config-3XBnu9Xz.d.cts +0 -1379
  125. package/dist/app-config-3XBnu9Xz.d.ts +0 -1379
  126. package/dist/app-config-BLMX-9MN.d.cts +0 -1045
  127. package/dist/app-config-BLMX-9MN.d.ts +0 -1045
  128. package/dist/app-config-CaEQH0Kb.d.cts +0 -1383
  129. package/dist/app-config-CaEQH0Kb.d.ts +0 -1383
  130. package/dist/app-config-DLK6bCaP.d.cts +0 -1390
  131. package/dist/app-config-DLK6bCaP.d.ts +0 -1390
  132. package/dist/app-config-oOarB6Fw.d.cts +0 -1411
  133. package/dist/app-config-oOarB6Fw.d.ts +0 -1411
  134. package/dist/app-oQt5-9MU.d.cts +0 -1560
  135. package/dist/app-oQt5-9MU.d.ts +0 -1560
  136. package/dist/app-rZj1VFer.d.cts +0 -1621
  137. package/dist/app-rZj1VFer.d.ts +0 -1621
  138. package/dist/app-wo82JRHl.d.cts +0 -445
  139. package/dist/app-wo82JRHl.d.ts +0 -445
  140. package/dist/app.d.ts +0 -21
  141. package/dist/app.d.ts.map +0 -1
  142. package/dist/app.js +0 -56
  143. package/dist/app.js.map +0 -1
  144. package/dist/auth/jexl.d.ts +0 -10
  145. package/dist/auth/jexl.d.ts.map +0 -1
  146. package/dist/auth/jexl.js +0 -22
  147. package/dist/auth/jexl.js.map +0 -1
  148. package/dist/auth/password.d.ts +0 -10
  149. package/dist/auth/password.d.ts.map +0 -1
  150. package/dist/auth/password.js +0 -28
  151. package/dist/auth/password.js.map +0 -1
  152. package/dist/auth/token.d.ts +0 -22
  153. package/dist/auth/token.d.ts.map +0 -1
  154. package/dist/auth/token.js +0 -40
  155. package/dist/auth/token.js.map +0 -1
  156. package/dist/chunk-22JTWD74.js +0 -1784
  157. package/dist/chunk-23URSKPI.js +0 -2371
  158. package/dist/chunk-2JMA3M5S.js +0 -2475
  159. package/dist/chunk-3FZEUK36.js +0 -2470
  160. package/dist/chunk-3VUH2MNW.js +0 -1903
  161. package/dist/chunk-44O2LDPT.js +0 -1513
  162. package/dist/chunk-45JDQDA2.js +0 -1899
  163. package/dist/chunk-4C4L6SD2.js +0 -1524
  164. package/dist/chunk-4EDMZAM5.js +0 -2692
  165. package/dist/chunk-52T4RCFU.js +0 -1972
  166. package/dist/chunk-6ZXEGKT6.js +0 -619
  167. package/dist/chunk-7GGHK75W.js +0 -1835
  168. package/dist/chunk-ALNQINX7.js +0 -2496
  169. package/dist/chunk-B6KDKYXB.js +0 -2502
  170. package/dist/chunk-BEONZV6M.js +0 -1951
  171. package/dist/chunk-C4GEQ5RP.js +0 -1993
  172. package/dist/chunk-CQDVPMEU.js +0 -613
  173. package/dist/chunk-DOJHZ7XN.js +0 -2394
  174. package/dist/chunk-EH7G3LLL.js +0 -2065
  175. package/dist/chunk-EXXOPW3I.js +0 -2483
  176. package/dist/chunk-F4ZC6TXR.js +0 -2065
  177. package/dist/chunk-FCXE5CVC.js +0 -1528
  178. package/dist/chunk-FDQYPPG3.js +0 -2698
  179. package/dist/chunk-FURYJ254.mjs +0 -33
  180. package/dist/chunk-G2QQLBHW.js +0 -24
  181. package/dist/chunk-GM4WW6IE.js +0 -66
  182. package/dist/chunk-GZODLJ3C.js +0 -1861
  183. package/dist/chunk-IIY3E7Q7.js +0 -1996
  184. package/dist/chunk-JJN4J5NS.js +0 -2069
  185. package/dist/chunk-MRUHCNDM.js +0 -1940
  186. package/dist/chunk-NKDX67AW.js +0 -2698
  187. package/dist/chunk-NYUB4AHZ.js +0 -1951
  188. package/dist/chunk-OD4SLW7H.js +0 -1930
  189. package/dist/chunk-OE5MH3NB.js +0 -2070
  190. package/dist/chunk-PH36WQYS.js +0 -1875
  191. package/dist/chunk-PKNFV7KE.js +0 -2469
  192. package/dist/chunk-SUGK7UYL.js +0 -311
  193. package/dist/chunk-TIJHR54M.js +0 -1879
  194. package/dist/chunk-TUUHGLB5.js +0 -2609
  195. package/dist/chunk-UBTRANFX.js +0 -2476
  196. package/dist/chunk-VJWUG2XK.js +0 -1951
  197. package/dist/chunk-W6KURRMW.js +0 -2471
  198. package/dist/chunk-Y4AYI52W.js +0 -631
  199. package/dist/chunk-YDOBB7MY.js +0 -1457
  200. package/dist/chunk-YNJ3YC4N.js +0 -2483
  201. package/dist/chunk-ZFAOBRHT.js +0 -2709
  202. package/dist/controllers/admin-auth.controller.d.ts +0 -68
  203. package/dist/controllers/admin-auth.controller.d.ts.map +0 -1
  204. package/dist/controllers/admin-auth.controller.js +0 -414
  205. package/dist/controllers/admin-auth.controller.js.map +0 -1
  206. package/dist/controllers/auth.controller.d.ts +0 -125
  207. package/dist/controllers/auth.controller.d.ts.map +0 -1
  208. package/dist/controllers/auth.controller.js +0 -323
  209. package/dist/controllers/auth.controller.js.map +0 -1
  210. package/dist/controllers/collection.controller.d.ts +0 -157
  211. package/dist/controllers/collection.controller.d.ts.map +0 -1
  212. package/dist/controllers/collection.controller.js +0 -779
  213. package/dist/controllers/collection.controller.js.map +0 -1
  214. package/dist/controllers/global.controller.d.ts +0 -17
  215. package/dist/controllers/global.controller.d.ts.map +0 -1
  216. package/dist/controllers/global.controller.js +0 -132
  217. package/dist/controllers/global.controller.js.map +0 -1
  218. package/dist/controllers/media.controller.d.ts +0 -38
  219. package/dist/controllers/media.controller.d.ts.map +0 -1
  220. package/dist/controllers/media.controller.js +0 -162
  221. package/dist/controllers/media.controller.js.map +0 -1
  222. package/dist/controllers/preview.controller.d.ts +0 -37
  223. package/dist/controllers/preview.controller.d.ts.map +0 -1
  224. package/dist/controllers/preview.controller.js +0 -48
  225. package/dist/controllers/preview.controller.js.map +0 -1
  226. package/dist/index-BKYugiYp.d.cts +0 -1988
  227. package/dist/index-BKYugiYp.d.ts +0 -1988
  228. package/dist/index-BSWCiAen.d.cts +0 -1983
  229. package/dist/index-BSWCiAen.d.ts +0 -1983
  230. package/dist/index-BfLDb3b0.d.cts +0 -1467
  231. package/dist/index-BfLDb3b0.d.ts +0 -1467
  232. package/dist/index-Bi4sX_eC.d.cts +0 -1491
  233. package/dist/index-Bi4sX_eC.d.ts +0 -1491
  234. package/dist/index-Bp7PDOYG.d.cts +0 -1750
  235. package/dist/index-Bp7PDOYG.d.ts +0 -1750
  236. package/dist/index-ByXooLy-.d.cts +0 -1762
  237. package/dist/index-ByXooLy-.d.ts +0 -1762
  238. package/dist/index-C9Vp1DCT.d.cts +0 -1563
  239. package/dist/index-C9Vp1DCT.d.ts +0 -1563
  240. package/dist/index-CFezd8p1.d.cts +0 -1986
  241. package/dist/index-CFezd8p1.d.ts +0 -1986
  242. package/dist/index-CdQwnbfh.d.cts +0 -1889
  243. package/dist/index-CdQwnbfh.d.ts +0 -1889
  244. package/dist/index-D38_E0gf.d.cts +0 -336
  245. package/dist/index-D38_E0gf.d.ts +0 -336
  246. package/dist/index-DR2z1rrf.d.cts +0 -1503
  247. package/dist/index-DR2z1rrf.d.ts +0 -1503
  248. package/dist/index-DTrpTru7.d.cts +0 -1881
  249. package/dist/index-DTrpTru7.d.ts +0 -1881
  250. package/dist/index-DfAmTZXk.d.cts +0 -1749
  251. package/dist/index-DfAmTZXk.d.ts +0 -1749
  252. package/dist/index-DfkPT4NR.d.cts +0 -1584
  253. package/dist/index-DfkPT4NR.d.ts +0 -1584
  254. package/dist/index-DzUlE5Hc.d.cts +0 -1751
  255. package/dist/index-DzUlE5Hc.d.ts +0 -1751
  256. package/dist/index-M_m6dXZV.d.cts +0 -1889
  257. package/dist/index-M_m6dXZV.d.ts +0 -1889
  258. package/dist/index-RylhgOwj.d.cts +0 -340
  259. package/dist/index-RylhgOwj.d.ts +0 -340
  260. package/dist/index-qhLus8ZO.d.cts +0 -1881
  261. package/dist/index-qhLus8ZO.d.ts +0 -1881
  262. package/dist/index-xB1lMg79.d.cts +0 -1456
  263. package/dist/index-xB1lMg79.d.ts +0 -1456
  264. package/dist/index.d.mts +0 -417
  265. package/dist/index.d.ts.map +0 -1
  266. package/dist/index.js.map +0 -1
  267. package/dist/index.mjs +0 -1974
  268. package/dist/middleware/auth.d.ts +0 -18
  269. package/dist/middleware/auth.d.ts.map +0 -1
  270. package/dist/middleware/auth.js +0 -45
  271. package/dist/middleware/auth.js.map +0 -1
  272. package/dist/password-ACI2YUGZ.js +0 -8
  273. package/dist/router.d.ts +0 -8
  274. package/dist/router.d.ts.map +0 -1
  275. package/dist/router.js +0 -625
  276. package/dist/router.js.map +0 -1
  277. package/dist/server.d.ts.map +0 -1
  278. package/dist/server.js.map +0 -1
  279. package/dist/services/audit.service.d.ts +0 -23
  280. package/dist/services/audit.service.d.ts.map +0 -1
  281. package/dist/services/audit.service.js +0 -28
  282. package/dist/services/audit.service.js.map +0 -1
  283. package/dist/services/defaults.service.d.ts +0 -8
  284. package/dist/services/defaults.service.d.ts.map +0 -1
  285. package/dist/services/defaults.service.js +0 -55
  286. package/dist/services/defaults.service.js.map +0 -1
  287. package/dist/services/email-template.d.ts +0 -46
  288. package/dist/services/email-template.d.ts.map +0 -1
  289. package/dist/services/email-template.js +0 -88
  290. package/dist/services/email-template.js.map +0 -1
  291. package/dist/services/email.service.d.ts +0 -33
  292. package/dist/services/email.service.d.ts.map +0 -1
  293. package/dist/services/email.service.js +0 -96
  294. package/dist/services/email.service.js.map +0 -1
  295. package/dist/services/media.service.d.ts +0 -20
  296. package/dist/services/media.service.d.ts.map +0 -1
  297. package/dist/services/media.service.js +0 -49
  298. package/dist/services/media.service.js.map +0 -1
  299. package/dist/services/population.service.d.ts +0 -20
  300. package/dist/services/population.service.d.ts.map +0 -1
  301. package/dist/services/population.service.js +0 -194
  302. package/dist/services/population.service.js.map +0 -1
  303. package/dist/token-7QG5DBME.mjs +0 -10
  304. package/dist/types/access.d.ts +0 -38
  305. package/dist/types/access.d.ts.map +0 -1
  306. package/dist/types/access.js +0 -2
  307. package/dist/types/access.js.map +0 -1
  308. package/dist/types/adapters.d.ts +0 -146
  309. package/dist/types/adapters.d.ts.map +0 -1
  310. package/dist/types/adapters.js +0 -2
  311. package/dist/types/adapters.js.map +0 -1
  312. package/dist/types/admin-auth.d.ts +0 -132
  313. package/dist/types/admin-auth.d.ts.map +0 -1
  314. package/dist/types/admin-auth.js +0 -2
  315. package/dist/types/admin-auth.js.map +0 -1
  316. package/dist/types/admin.d.ts +0 -65
  317. package/dist/types/admin.d.ts.map +0 -1
  318. package/dist/types/admin.js +0 -2
  319. package/dist/types/admin.js.map +0 -1
  320. package/dist/types/app-config.d.ts +0 -143
  321. package/dist/types/app-config.d.ts.map +0 -1
  322. package/dist/types/app-config.js +0 -2
  323. package/dist/types/app-config.js.map +0 -1
  324. package/dist/types/documents.d.ts +0 -65
  325. package/dist/types/documents.d.ts.map +0 -1
  326. package/dist/types/documents.js +0 -2
  327. package/dist/types/documents.js.map +0 -1
  328. package/dist/types/hooks.d.ts +0 -122
  329. package/dist/types/hooks.d.ts.map +0 -1
  330. package/dist/types/hooks.js +0 -2
  331. package/dist/types/hooks.js.map +0 -1
  332. package/dist/types/index.d.ts +0 -13
  333. package/dist/types/index.d.ts.map +0 -1
  334. package/dist/types/index.js +0 -13
  335. package/dist/types/index.js.map +0 -1
  336. package/dist/types/request.d.ts +0 -42
  337. package/dist/types/request.d.ts.map +0 -1
  338. package/dist/types/request.js +0 -2
  339. package/dist/types/request.js.map +0 -1
  340. package/dist/types/schema-config.d.ts +0 -303
  341. package/dist/types/schema-config.d.ts.map +0 -1
  342. package/dist/types/schema-config.js +0 -2
  343. package/dist/types/schema-config.js.map +0 -1
  344. package/dist/types/schema-core.d.ts +0 -294
  345. package/dist/types/schema-core.d.ts.map +0 -1
  346. package/dist/types/schema-core.js +0 -2
  347. package/dist/types/schema-core.js.map +0 -1
  348. package/dist/types/schema-inference.d.ts +0 -73
  349. package/dist/types/schema-inference.d.ts.map +0 -1
  350. package/dist/types/schema-inference.js +0 -2
  351. package/dist/types/schema-inference.js.map +0 -1
  352. package/dist/types/workflows.d.ts +0 -84
  353. package/dist/types/workflows.d.ts.map +0 -1
  354. package/dist/types/workflows.js +0 -7
  355. package/dist/types/workflows.js.map +0 -1
  356. package/dist/utils/admin-auth.d.ts +0 -5
  357. package/dist/utils/admin-auth.d.ts.map +0 -1
  358. package/dist/utils/admin-auth.js +0 -39
  359. package/dist/utils/admin-auth.js.map +0 -1
  360. package/dist/utils/config.d.ts +0 -8
  361. package/dist/utils/config.d.ts.map +0 -1
  362. package/dist/utils/config.js +0 -238
  363. package/dist/utils/config.js.map +0 -1
  364. package/dist/utils/hooks.d.ts +0 -41
  365. package/dist/utils/hooks.d.ts.map +0 -1
  366. package/dist/utils/hooks.js +0 -173
  367. package/dist/utils/hooks.js.map +0 -1
  368. package/dist/utils/openapi.d.ts +0 -6
  369. package/dist/utils/openapi.d.ts.map +0 -1
  370. package/dist/utils/openapi.js +0 -842
  371. package/dist/utils/openapi.js.map +0 -1
  372. package/dist/utils/parse-sort.d.ts +0 -7
  373. package/dist/utils/parse-sort.d.ts.map +0 -1
  374. package/dist/utils/parse-sort.js +0 -23
  375. package/dist/utils/parse-sort.js.map +0 -1
  376. package/dist/utils/parse-where.d.ts +0 -63
  377. package/dist/utils/parse-where.d.ts.map +0 -1
  378. package/dist/utils/parse-where.js +0 -196
  379. package/dist/utils/parse-where.js.map +0 -1
  380. package/dist/utils/readonly-db.d.ts +0 -9
  381. package/dist/utils/readonly-db.d.ts.map +0 -1
  382. package/dist/utils/readonly-db.js +0 -21
  383. package/dist/utils/readonly-db.js.map +0 -1
  384. package/dist/utils/setup-prompt.d.ts +0 -11
  385. package/dist/utils/setup-prompt.d.ts.map +0 -1
  386. package/dist/utils/setup-prompt.js +0 -863
  387. package/dist/utils/setup-prompt.js.map +0 -1
  388. package/dist/utils/swagger.d.ts +0 -11
  389. package/dist/utils/swagger.d.ts.map +0 -1
  390. package/dist/utils/swagger.js +0 -62
  391. package/dist/utils/swagger.js.map +0 -1
  392. package/dist/utils/upload-validation.d.ts +0 -23
  393. package/dist/utils/upload-validation.d.ts.map +0 -1
  394. package/dist/utils/upload-validation.js +0 -44
  395. package/dist/utils/upload-validation.js.map +0 -1
  396. package/dist/utils/where-sanitizer.d.ts +0 -10
  397. package/dist/utils/where-sanitizer.d.ts.map +0 -1
  398. package/dist/utils/where-sanitizer.js +0 -67
  399. package/dist/utils/where-sanitizer.js.map +0 -1
  400. package/dist/where-sanitizer-7Q4JXMX6.js +0 -57
  401. package/dist/where-sanitizer-DQIWTQZW.js +0 -50
  402. package/dist/workflows.d.ts +0 -54
  403. package/dist/workflows.d.ts.map +0 -1
  404. package/dist/workflows.js +0 -282
  405. package/dist/workflows.js.map +0 -1
package/dist/index.mjs DELETED
@@ -1,1974 +0,0 @@
1
- // src/app.ts
2
- import { Hono } from "hono";
3
- import { cors } from "hono/cors";
4
- import { requestId } from "hono/request-id";
5
-
6
- // src/services/population.service.ts
7
- var PopulationService = class {
8
- constructor(db, collections) {
9
- this.db = db;
10
- this.collections = collections;
11
- }
12
- db;
13
- collections;
14
- /**
15
- * Recursively populate relationship fields in a document or array of documents.
16
- */
17
- async populate(args) {
18
- const { data, fields, currentDepth, maxDepth } = args;
19
- if (currentDepth >= maxDepth || !data) {
20
- return data;
21
- }
22
- if (Array.isArray(data)) {
23
- return Promise.all(data.map((item) => this.populate({ ...args, data: item })));
24
- }
25
- const populatedDoc = { ...data };
26
- for (const field of fields) {
27
- const value = populatedDoc[field.name];
28
- if (field.type === "relationship" && field.relationTo && value) {
29
- const relatedCollection = this.collections.find((c) => c.slug === field.relationTo);
30
- if (!relatedCollection) continue;
31
- if (Array.isArray(value)) {
32
- populatedDoc[field.name] = await Promise.all(
33
- value.map(async (id) => {
34
- const doc = await this.db.findOne({ collection: field.relationTo, id });
35
- if (!doc) return id;
36
- return this.populate({
37
- data: doc,
38
- fields: relatedCollection.fields,
39
- currentDepth: currentDepth + 1,
40
- maxDepth
41
- });
42
- })
43
- );
44
- } else if (typeof value === "string") {
45
- const doc = await this.db.findOne({ collection: field.relationTo, id: value });
46
- if (doc) {
47
- populatedDoc[field.name] = await this.populate({
48
- data: doc,
49
- fields: relatedCollection.fields,
50
- currentDepth: currentDepth + 1,
51
- maxDepth
52
- });
53
- }
54
- }
55
- }
56
- if ((field.type === "array" || field.type === "object") && field.fields && value) {
57
- populatedDoc[field.name] = await this.populate({
58
- data: value,
59
- fields: field.fields,
60
- currentDepth,
61
- // Nested fields don't consume depth, only relationships do
62
- maxDepth
63
- });
64
- }
65
- if (field.type === "blocks" && field.blocks && Array.isArray(value)) {
66
- populatedDoc[field.name] = await Promise.all(
67
- value.map(async (blockData) => {
68
- const blockConfig = field.blocks.find((b) => b.slug === blockData.blockType);
69
- if (!blockConfig) return blockData;
70
- return this.populate({
71
- data: blockData,
72
- fields: blockConfig.fields,
73
- currentDepth,
74
- maxDepth
75
- });
76
- })
77
- );
78
- }
79
- }
80
- return populatedDoc;
81
- }
82
- /**
83
- * Helper to populate a PaginatedResult
84
- */
85
- async populateResult(result, fields, maxDepth) {
86
- if (maxDepth <= 0) return result;
87
- const populatedDocs = await this.populate({
88
- data: result.docs,
89
- fields,
90
- currentDepth: 0,
91
- maxDepth
92
- });
93
- return {
94
- ...result,
95
- docs: populatedDocs
96
- };
97
- }
98
- };
99
-
100
- // src/services/defaults.service.ts
101
- var DefaultsService = class {
102
- /**
103
- * Recursively apply default values to a data object based on field definitions.
104
- */
105
- static apply(fields, data = {}) {
106
- const result = { ...data || {} };
107
- fields.forEach((field) => {
108
- const value = result[field.name];
109
- if (value === void 0 || value === null) {
110
- if (field.defaultValue !== void 0) {
111
- result[field.name] = field.defaultValue;
112
- } else {
113
- if (field.type === "boolean") result[field.name] = false;
114
- else if (field.type === "array") result[field.name] = [];
115
- else if (field.type === "multiSelect") result[field.name] = [];
116
- else if (field.type === "object") {
117
- result[field.name] = this.apply(field.fields || [], {});
118
- }
119
- }
120
- } else if (field.type === "object" && field.fields) {
121
- result[field.name] = this.apply(field.fields, value);
122
- } else if (field.type === "array" && field.fields && Array.isArray(value)) {
123
- result[field.name] = value.map((item) => this.apply(field.fields, item));
124
- }
125
- });
126
- return result;
127
- }
128
- };
129
-
130
- // src/controllers/collection.controller.ts
131
- var CollectionController = class {
132
- constructor(collection) {
133
- this.collection = collection;
134
- }
135
- collection;
136
- async find(c) {
137
- const config2 = c.get("config");
138
- const db = config2.db;
139
- if (!db) return c.json({ message: "Database not configured" }, 500);
140
- const limit = Number(c.req.query("limit")) || 10;
141
- const page = Number(c.req.query("page")) || 1;
142
- const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
143
- const sort = c.req.query("sort") || void 0;
144
- let where = void 0;
145
- const whereRaw = c.req.query("where");
146
- if (whereRaw) {
147
- try {
148
- where = JSON.parse(decodeURIComponent(whereRaw));
149
- } catch {
150
- }
151
- }
152
- let result = await db.find({
153
- collection: this.collection.slug,
154
- limit,
155
- page,
156
- sort,
157
- where
158
- });
159
- result.docs = result.docs.map((doc) => DefaultsService.apply(this.collection.fields, doc));
160
- if (depth > 0) {
161
- const populationService = new PopulationService(db, config2.collections);
162
- result = await populationService.populateResult(result, this.collection.fields, depth);
163
- }
164
- return c.json(result);
165
- }
166
- async findOne(c) {
167
- const config2 = c.get("config");
168
- const db = config2.db;
169
- if (!db) return c.json({ message: "Database not configured" }, 500);
170
- const id = c.req.param("id");
171
- const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
172
- if (!id) return c.json({ message: "Missing ID" }, 400);
173
- const doc = await db.findOne({ collection: this.collection.slug, id });
174
- if (!doc) return c.json({ message: "Not Found" }, 404);
175
- const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
176
- if (depth > 0 && docWithDefaults) {
177
- const populationService = new PopulationService(db, config2.collections);
178
- const populatedDoc = await populationService.populate({
179
- data: docWithDefaults,
180
- fields: this.collection.fields,
181
- currentDepth: 0,
182
- maxDepth: depth
183
- });
184
- return c.json(populatedDoc);
185
- }
186
- return c.json(docWithDefaults);
187
- }
188
- async create(c) {
189
- const config2 = c.get("config");
190
- const db = config2.db;
191
- if (!db) return c.json({ message: "Database not configured" }, 500);
192
- const contentType = c.req.header("Content-Type") || "";
193
- if (contentType.toLowerCase().includes("multipart/form-data")) {
194
- return this.upload(c);
195
- }
196
- const body = await c.req.json();
197
- const doc = await db.create({ collection: this.collection.slug, data: body });
198
- return c.json(doc, 201);
199
- }
200
- async upload(c) {
201
- const config2 = c.get("config");
202
- const storage = config2.storage;
203
- if (!storage) return c.json({ message: "Storage not configured" }, 500);
204
- const formData = await c.req.formData();
205
- const file = formData.get("file");
206
- if (!file) return c.json({ message: "No file uploaded" }, 400);
207
- const buffer = Buffer.from(await file.arrayBuffer());
208
- const siteId = c.get("siteId");
209
- const workspaceId = c.get("workspaceId");
210
- const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId;
211
- const fileData = await storage.upload({
212
- filename: file.name,
213
- buffer,
214
- mimeType: file.type,
215
- prefix
216
- });
217
- const otherData = {};
218
- formData.forEach((value, key) => {
219
- if (key !== "file" && typeof value === "string") {
220
- otherData[key] = value;
221
- }
222
- });
223
- const doc = await config2.db.create({
224
- collection: this.collection.slug,
225
- data: {
226
- ...otherData,
227
- ...fileData
228
- }
229
- });
230
- return c.json(doc, 201);
231
- }
232
- async update(c) {
233
- const db = c.get("config").db;
234
- if (!db) return c.json({ message: "Database not configured" }, 500);
235
- const id = c.req.param("id");
236
- if (!id) return c.json({ message: "Missing ID" }, 400);
237
- const body = await c.req.json();
238
- const doc = await db.update({ collection: this.collection.slug, id, data: body });
239
- return c.json(doc);
240
- }
241
- async delete(c) {
242
- const db = c.get("config").db;
243
- if (!db) return c.json({ message: "Database not configured" }, 500);
244
- const id = c.req.param("id");
245
- if (!id) return c.json({ message: "Missing ID" }, 400);
246
- await db.delete({ collection: this.collection.slug, id });
247
- return c.json({ message: "Deleted" });
248
- }
249
- async seed(c) {
250
- const config2 = c.get("config");
251
- const db = config2.db;
252
- if (!db) return c.json({ message: "Database not configured" }, 500);
253
- const body = await c.req.json();
254
- const initialData = body.data;
255
- if (!initialData || !Array.isArray(initialData)) {
256
- return c.json({ message: "Invalid initial data" }, 400);
257
- }
258
- const result = await db.find({ collection: this.collection.slug, limit: 1 });
259
- if (result.total > 0) {
260
- return c.json({ message: "Collection is not empty, skipping seed" });
261
- }
262
- console.log(`[dyrected/core] Auto-seeding collection: ${this.collection.slug}`);
263
- const createdDocs = [];
264
- for (const data of initialData) {
265
- const doc = await db.create({ collection: this.collection.slug, data });
266
- createdDocs.push(doc);
267
- }
268
- return c.json({ message: "Seed successful", count: createdDocs.length }, 201);
269
- }
270
- };
271
-
272
- // src/controllers/global.controller.ts
273
- var GlobalController = class {
274
- constructor(global) {
275
- this.global = global;
276
- }
277
- global;
278
- async get(c) {
279
- const config2 = c.get("config");
280
- const db = config2.db;
281
- if (!db) return c.json({ message: "Database not configured" }, 500);
282
- const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
283
- const data = await db.getGlobal({ slug: this.global.slug });
284
- const dataWithDefaults = DefaultsService.apply(this.global.fields, data);
285
- if (depth > 0 && dataWithDefaults) {
286
- const populationService = new PopulationService(db, config2.collections);
287
- const populatedData = await populationService.populate({
288
- data: dataWithDefaults,
289
- fields: this.global.fields,
290
- currentDepth: 1,
291
- maxDepth: depth
292
- });
293
- return c.json(populatedData);
294
- }
295
- return c.json(dataWithDefaults);
296
- }
297
- async update(c) {
298
- const db = c.get("config").db;
299
- if (!db) return c.json({ message: "Database not configured" }, 500);
300
- const body = await c.req.json();
301
- const data = await db.updateGlobal({ slug: this.global.slug, data: body });
302
- return c.json(data);
303
- }
304
- async seed(c) {
305
- const config2 = c.get("config");
306
- const db = config2.db;
307
- if (!db) return c.json({ message: "Database not configured" }, 500);
308
- const body = await c.req.json();
309
- const initialData = body.data;
310
- if (!initialData) {
311
- return c.json({ message: "Invalid initial data" }, 400);
312
- }
313
- const existing = await db.getGlobal({ slug: this.global.slug });
314
- if (existing && Object.keys(existing).length > 0) {
315
- return c.json({ message: "Global is not empty, skipping seed" });
316
- }
317
- console.log(`[dyrected/core] Auto-seeding global: ${this.global.slug}`);
318
- await db.updateGlobal({ slug: this.global.slug, data: initialData });
319
- return c.json({ message: "Seed successful", data: initialData }, 201);
320
- }
321
- };
322
-
323
- // src/controllers/media.controller.ts
324
- var MediaController = class {
325
- collection;
326
- constructor(collection = "media") {
327
- this.collection = collection;
328
- }
329
- async upload(c) {
330
- const config2 = c.get("config");
331
- const storage = config2.storage;
332
- const imageService = config2.image;
333
- if (!storage) {
334
- return c.json({ message: "Storage not configured" }, 500);
335
- }
336
- const body = await c.req.parseBody();
337
- const file = body["file"];
338
- const focalPointStr = body["focalPoint"];
339
- const focalPoint = focalPointStr ? JSON.parse(focalPointStr) : void 0;
340
- if (!file) {
341
- return c.json({ message: "No file uploaded" }, 400);
342
- }
343
- const buffer = Buffer.from(await file.arrayBuffer());
344
- const siteId = c.get("siteId");
345
- const workspaceId = c.get("workspaceId");
346
- const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId || "default";
347
- let imageMetadata = {};
348
- let imageSizes = {};
349
- if (imageService && file.type.startsWith("image/")) {
350
- let colConfig = config2.collections.find((col) => col.slug === this.collection);
351
- if (!colConfig && config2.onSchemaFetch && siteId) {
352
- const dynamic = await config2.onSchemaFetch(siteId);
353
- colConfig = dynamic.collections?.find((col) => col.slug === this.collection);
354
- }
355
- try {
356
- const processed = await imageService.process({
357
- buffer,
358
- mimeType: file.type,
359
- config: colConfig?.upload,
360
- focalPoint
361
- });
362
- imageMetadata = processed.metadata;
363
- imageSizes = processed.sizes;
364
- } catch (err) {
365
- console.error("[MediaController] Image processing failed:", err);
366
- }
367
- }
368
- const fileData = await storage.upload({
369
- filename: file.name,
370
- buffer,
371
- mimeType: file.type,
372
- prefix
373
- });
374
- const finalFileData = {
375
- ...fileData,
376
- ...imageMetadata,
377
- focalPoint,
378
- sizes: {}
379
- };
380
- if (imageSizes) {
381
- for (const [sizeName, sizeData] of Object.entries(imageSizes)) {
382
- const ext = file.name.split(".").pop();
383
- const baseName = file.name.substring(0, file.name.lastIndexOf("."));
384
- const sizeFilename = `${baseName}-${sizeName}.${ext}`;
385
- try {
386
- const sizeFileData = await storage.upload({
387
- filename: sizeFilename,
388
- buffer: sizeData.buffer,
389
- mimeType: file.type,
390
- prefix
391
- });
392
- finalFileData.sizes[sizeName] = {
393
- ...sizeFileData,
394
- width: sizeData.width,
395
- height: sizeData.height
396
- };
397
- } catch (err) {
398
- console.error(`[MediaController] Failed to upload size ${sizeName}:`, err);
399
- }
400
- }
401
- }
402
- const db = config2.db;
403
- if (!db) return c.json({ message: "Database not configured" }, 500);
404
- const doc = await db.create({
405
- collection: this.collection,
406
- data: finalFileData
407
- });
408
- return c.json(doc, 201);
409
- }
410
- async find(c) {
411
- const db = c.get("config").db;
412
- if (!db) return c.json({ message: "Database not configured" }, 500);
413
- const limit = Number(c.req.query("limit")) || 10;
414
- const page = Number(c.req.query("page")) || 1;
415
- const result = await db.find({
416
- collection: this.collection,
417
- limit,
418
- page
419
- });
420
- return c.json(result);
421
- }
422
- async delete(c) {
423
- const config2 = c.get("config");
424
- const storage = config2.storage;
425
- const db = config2.db;
426
- if (!db) return c.json({ message: "Database not configured" }, 500);
427
- const id = c.req.param("id");
428
- if (!id) return c.json({ message: "Missing ID" }, 400);
429
- const doc = await db.findOne({ collection: this.collection, id });
430
- if (!doc) return c.json({ message: "Not Found" }, 404);
431
- if (storage) {
432
- await storage.delete({ filename: doc.filename });
433
- if (doc.sizes) {
434
- for (const size of Object.values(doc.sizes)) {
435
- if (size.filename) {
436
- await storage.delete({ filename: size.filename });
437
- }
438
- }
439
- }
440
- }
441
- await db.delete({ collection: this.collection, id });
442
- return c.json({ message: "Deleted" });
443
- }
444
- async serve(c) {
445
- const config2 = c.get("config");
446
- const storage = config2.storage;
447
- if (!storage || !storage.resolve) {
448
- return c.json({ message: "Storage not configured for serving" }, 404);
449
- }
450
- const filename = c.req.param("filename");
451
- if (!filename) return c.json({ message: "Missing filename" }, 400);
452
- let res = await storage.resolve({ filename });
453
- if (!res && !filename.includes("/")) {
454
- res = await storage.resolve({ filename: `default/${filename}` });
455
- }
456
- if (!res) return c.json({ message: "Not Found" }, 404);
457
- c.header("Content-Type", res.mimeType);
458
- return c.body(res.buffer);
459
- }
460
- };
461
-
462
- // src/auth/password.ts
463
- import { promisify } from "util";
464
- import { scrypt, randomBytes, timingSafeEqual } from "crypto";
465
- var scryptAsync = promisify(scrypt);
466
- var SALT_LEN = 16;
467
- var KEY_LEN = 64;
468
- async function hashPassword(plain) {
469
- const salt = randomBytes(SALT_LEN).toString("hex");
470
- const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
471
- return `${salt}:${derivedKey.toString("hex")}`;
472
- }
473
- async function verifyPassword(plain, stored) {
474
- const [salt, storedHash] = stored.split(":");
475
- if (!salt || !storedHash) return false;
476
- const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
477
- const storedBuffer = Buffer.from(storedHash, "hex");
478
- if (derivedKey.length !== storedBuffer.length) return false;
479
- return timingSafeEqual(derivedKey, storedBuffer);
480
- }
481
-
482
- // src/auth/token.ts
483
- import { SignJWT, jwtVerify, decodeJwt } from "jose";
484
- import { TextEncoder } from "util";
485
- function getSecret() {
486
- const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET;
487
- if (!secret) {
488
- throw new Error(
489
- "[dyrected/core] DYRECTED_JWT_SECRET is not set. Add it to your environment variables to enable auth collections."
490
- );
491
- }
492
- return new TextEncoder().encode(secret);
493
- }
494
- var DEFAULT_EXPIRY = "7d";
495
- async function signCollectionToken(payload, expiresIn = DEFAULT_EXPIRY) {
496
- return new SignJWT({ ...payload }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(expiresIn).sign(getSecret());
497
- }
498
- async function verifyCollectionToken(token) {
499
- const { payload } = await jwtVerify(token, getSecret());
500
- return payload;
501
- }
502
-
503
- // src/services/email.service.ts
504
- var _devSend = null;
505
- var _devSendPromise = null;
506
- async function getDevSend() {
507
- if (_devSend) return _devSend;
508
- if (_devSendPromise) return _devSendPromise;
509
- _devSendPromise = (async () => {
510
- try {
511
- const nodemailer = await import("nodemailer");
512
- const account = await nodemailer.default.createTestAccount();
513
- const transport = nodemailer.default.createTransport({
514
- host: "smtp.ethereal.email",
515
- port: 587,
516
- auth: { user: account.user, pass: account.pass }
517
- });
518
- console.log("[dyrected/core] No email config \u2014 using Ethereal for dev email preview.");
519
- console.log(`[dyrected/core] Ethereal login: https://ethereal.email user: ${account.user} pass: ${account.pass}`);
520
- _devSend = async ({ to, subject, html }) => {
521
- const info = await transport.sendMail({ from: '"Dyrected Dev" <dev@dyrected.local>', to, subject, html });
522
- console.log(`[dyrected/core] Email preview URL: ${nodemailer.default.getTestMessageUrl(info)}`);
523
- };
524
- return _devSend;
525
- } catch {
526
- console.warn("[dyrected/core] nodemailer not available \u2014 emails will not be sent in dev.");
527
- return null;
528
- }
529
- })();
530
- return _devSendPromise;
531
- }
532
- async function sendEmail(config2, payload) {
533
- if (config2.email) {
534
- await config2.email.send(payload);
535
- return;
536
- }
537
- if (process.env.NODE_ENV !== "production") {
538
- const devSend = await getDevSend();
539
- await devSend?.(payload);
540
- }
541
- }
542
- function buildWelcomeEmail(config2, args) {
543
- const custom = config2.email?.templates?.welcome?.(args);
544
- return {
545
- subject: custom?.subject ?? "Welcome \u2014 your account is ready",
546
- html: custom?.html ?? `
547
- <div style="font-family:sans-serif;max-width:600px;margin:0 auto">
548
- <h2>Welcome!</h2>
549
- <p>Your account has been created. You can now log in with:</p>
550
- <p><strong>${args.email}</strong></p>
551
- </div>`
552
- };
553
- }
554
- function buildInviteEmail(config2, args) {
555
- const custom = config2.email?.templates?.invite?.(args);
556
- return {
557
- subject: custom?.subject ?? "You've been invited",
558
- html: custom?.html ?? `
559
- <div style="font-family:sans-serif;max-width:600px;margin:0 auto">
560
- <h2>You've been invited</h2>
561
- ${args.invitedByEmail ? `<p>Invited by <strong>${args.invitedByEmail}</strong>.</p>` : ""}
562
- <p>Use the token below to accept your invitation. It expires in 7 days.</p>
563
- <pre style="background:#f4f4f4;padding:12px;border-radius:4px;word-break:break-all">${args.token}</pre>
564
- </div>`
565
- };
566
- }
567
- function buildResetPasswordEmail(config2, args) {
568
- const custom = config2.email?.templates?.resetPassword?.(args);
569
- return {
570
- subject: custom?.subject ?? "Reset your password",
571
- html: custom?.html ?? `
572
- <div style="font-family:sans-serif;max-width:600px;margin:0 auto">
573
- <h2>Reset your password</h2>
574
- <p>Use the token below to reset your password. It expires in 1 hour.</p>
575
- <pre style="background:#f4f4f4;padding:12px;border-radius:4px;word-break:break-all">${args.token}</pre>
576
- </div>`
577
- };
578
- }
579
- function buildPasswordChangedEmail(config2, args) {
580
- const custom = config2.email?.templates?.passwordChanged?.(args);
581
- return {
582
- subject: custom?.subject ?? "Your password has been changed",
583
- html: custom?.html ?? `
584
- <div style="font-family:sans-serif;max-width:600px;margin:0 auto">
585
- <h2>Password changed</h2>
586
- <p>The password for <strong>${args.email}</strong> was just changed.</p>
587
- <p>If you did not make this change, please contact support immediately.</p>
588
- </div>`
589
- };
590
- }
591
-
592
- // src/controllers/auth.controller.ts
593
- var AuthController = class {
594
- constructor(collection) {
595
- this.collection = collection;
596
- }
597
- collection;
598
- // ---------------------------------------------------------------------------
599
- // GET /init
600
- // Checks if the first user needs to be created.
601
- // ---------------------------------------------------------------------------
602
- async init(c) {
603
- const db = c.get("config").db;
604
- if (!db) return c.json({ message: "Database not configured" }, 500);
605
- const result = await db.find({
606
- collection: this.collection.slug,
607
- limit: 1
608
- });
609
- return c.json({
610
- initialized: result.total > 0
611
- });
612
- }
613
- // ---------------------------------------------------------------------------
614
- // POST /first-user
615
- // Creates the first user if none exist.
616
- // ---------------------------------------------------------------------------
617
- async registerFirstUser(c) {
618
- const db = c.get("config").db;
619
- if (!db) return c.json({ message: "Database not configured" }, 500);
620
- const check = await db.find({
621
- collection: this.collection.slug,
622
- limit: 1
623
- });
624
- if (check.total > 0) {
625
- return c.json({ error: true, message: "Initial user already exists." }, 403);
626
- }
627
- const body = await c.req.json().catch(() => null);
628
- if (!body?.email || !body?.password) {
629
- return c.json({ error: true, message: "email and password are required." }, 400);
630
- }
631
- const hashedPassword = await hashPassword(body.password);
632
- const user = await db.create({
633
- collection: this.collection.slug,
634
- data: {
635
- ...body,
636
- password: hashedPassword,
637
- roles: ["admin"]
638
- // Default first user to admin
639
- }
640
- });
641
- const token = await signCollectionToken({
642
- sub: user.id,
643
- email: user.email,
644
- collection: this.collection.slug
645
- });
646
- const { subject, html } = buildWelcomeEmail(config, { email: body.email });
647
- sendEmail(config, { to: body.email, subject, html }).catch(
648
- (err) => console.error("[dyrected/core] Failed to send welcome email:", err)
649
- );
650
- const { password: _, ...safeUser } = user;
651
- return c.json({ token, user: safeUser });
652
- }
653
- // ---------------------------------------------------------------------------
654
- // POST /login
655
- // ---------------------------------------------------------------------------
656
- async login(c) {
657
- const db = c.get("config").db;
658
- if (!db) return c.json({ message: "Database not configured" }, 500);
659
- const body = await c.req.json().catch(() => null);
660
- if (!body?.email || !body?.password) {
661
- return c.json({ error: true, message: "email and password are required." }, 400);
662
- }
663
- const result = await db.find({
664
- collection: this.collection.slug,
665
- where: { email: body.email },
666
- limit: 1
667
- });
668
- const user = result.docs[0];
669
- if (!user) {
670
- return c.json({ error: true, message: "Invalid email or password." }, 401);
671
- }
672
- const valid = await verifyPassword(body.password, user.password);
673
- if (!valid) {
674
- return c.json({ error: true, message: "Invalid email or password." }, 401);
675
- }
676
- const token = await signCollectionToken({
677
- sub: user.id,
678
- email: user.email,
679
- collection: this.collection.slug
680
- });
681
- const { password: _, ...safeUser } = user;
682
- return c.json({ token, user: safeUser });
683
- }
684
- // ---------------------------------------------------------------------------
685
- // POST /logout
686
- // Auth collections use stateless JWTs — logout is handled client-side.
687
- // This endpoint exists so clients have a consistent API surface.
688
- // ---------------------------------------------------------------------------
689
- async logout(c) {
690
- return c.json({ success: true, message: "Logged out. Discard your token." });
691
- }
692
- // ---------------------------------------------------------------------------
693
- // GET /me
694
- // ---------------------------------------------------------------------------
695
- async me(c) {
696
- const db = c.get("config").db;
697
- if (!db) return c.json({ message: "Database not configured" }, 500);
698
- const requestUser = c.get("user");
699
- if (!requestUser) {
700
- return c.json({ error: true, message: "Authentication required." }, 401);
701
- }
702
- const user = await db.findOne({ collection: this.collection.slug, id: requestUser.sub });
703
- if (!user) {
704
- return c.json({ error: true, message: "User not found." }, 404);
705
- }
706
- const { password: _, ...safeUser } = user;
707
- return c.json(safeUser);
708
- }
709
- // ---------------------------------------------------------------------------
710
- // POST /refresh-token
711
- // ---------------------------------------------------------------------------
712
- async refreshToken(c) {
713
- const requestUser = c.get("user");
714
- if (!requestUser) {
715
- return c.json({ error: true, message: "Authentication required." }, 401);
716
- }
717
- const token = await signCollectionToken({
718
- sub: requestUser.sub,
719
- email: requestUser.email,
720
- collection: this.collection.slug
721
- });
722
- return c.json({ token });
723
- }
724
- // ---------------------------------------------------------------------------
725
- // POST /forgot-password
726
- // Requires config.email to be set. Silently succeeds if email not found
727
- // to prevent email enumeration.
728
- // ---------------------------------------------------------------------------
729
- async forgotPassword(c) {
730
- const config2 = c.get("config");
731
- const db = config2.db;
732
- if (!db) return c.json({ message: "Database not configured" }, 500);
733
- const body = await c.req.json().catch(() => null);
734
- if (!body?.email) {
735
- return c.json({ error: true, message: "email is required." }, 400);
736
- }
737
- const result = await db.find({
738
- collection: this.collection.slug,
739
- where: { email: body.email },
740
- limit: 1
741
- });
742
- const user = result.docs[0];
743
- if (user) {
744
- const resetToken = await signCollectionToken(
745
- { sub: user.id, email: user.email, collection: this.collection.slug, purpose: "reset" },
746
- "1h"
747
- );
748
- try {
749
- const { subject, html } = buildResetPasswordEmail(config2, { token: resetToken });
750
- await sendEmail(config2, { to: user.email, subject, html });
751
- } catch (err) {
752
- console.error("[dyrected/core] Failed to send password reset email:", err);
753
- }
754
- }
755
- return c.json({
756
- success: true,
757
- message: "If an account with that email exists, a reset link has been sent."
758
- });
759
- }
760
- // ---------------------------------------------------------------------------
761
- // POST /reset-password
762
- // Expects { token: string, password: string } in body.
763
- // The token is the reset JWT issued by /forgot-password.
764
- // ---------------------------------------------------------------------------
765
- async resetPassword(c) {
766
- const db = c.get("config").db;
767
- if (!db) return c.json({ message: "Database not configured" }, 500);
768
- const body = await c.req.json().catch(() => null);
769
- if (!body?.token || !body?.password) {
770
- return c.json({ error: true, message: "token and password are required." }, 400);
771
- }
772
- let payload;
773
- try {
774
- payload = await verifyCollectionToken(body.token);
775
- } catch {
776
- return c.json({ error: true, message: "Reset token is invalid or has expired." }, 400);
777
- }
778
- if (payload.collection !== this.collection.slug || payload.purpose !== "reset") {
779
- return c.json({ error: true, message: "Reset token is invalid or has expired." }, 400);
780
- }
781
- const hashedPassword = await hashPassword(body.password);
782
- await db.update({
783
- collection: this.collection.slug,
784
- id: payload.sub,
785
- data: { password: hashedPassword }
786
- });
787
- const { subject, html } = buildPasswordChangedEmail(config, { email: payload.email });
788
- sendEmail(config, { to: payload.email, subject, html }).catch(
789
- (err) => console.error("[dyrected/core] Failed to send password-changed email:", err)
790
- );
791
- return c.json({ success: true, message: "Password has been reset. You can now log in." });
792
- }
793
- // ---------------------------------------------------------------------------
794
- // POST /invite
795
- // Requires auth. Issues a signed invite token and emails it to the invitee.
796
- // ---------------------------------------------------------------------------
797
- async invite(c) {
798
- const config2 = c.get("config");
799
- const db = config2.db;
800
- if (!db) return c.json({ message: "Database not configured" }, 500);
801
- const requestUser = c.get("user");
802
- if (!requestUser) {
803
- return c.json({ error: true, message: "Authentication required." }, 401);
804
- }
805
- const body = await c.req.json().catch(() => null);
806
- if (!body?.email) {
807
- return c.json({ error: true, message: "email is required." }, 400);
808
- }
809
- const existing = await db.find({
810
- collection: this.collection.slug,
811
- where: { email: body.email },
812
- limit: 1
813
- });
814
- if (existing.total > 0) {
815
- return c.json({ error: true, message: "An account with that email already exists." }, 409);
816
- }
817
- const inviteToken = await signCollectionToken(
818
- { sub: body.email, email: body.email, collection: this.collection.slug, purpose: "invite" },
819
- "7d"
820
- );
821
- try {
822
- const { subject, html } = buildInviteEmail(config2, {
823
- token: inviteToken,
824
- invitedByEmail: requestUser.email
825
- });
826
- await sendEmail(config2, { to: body.email, subject, html });
827
- } catch (err) {
828
- console.error("[dyrected/core] Failed to send invite email:", err);
829
- }
830
- return c.json({ success: true, message: `Invite sent to ${body.email}.` });
831
- }
832
- // ---------------------------------------------------------------------------
833
- // POST /accept-invite
834
- // Public. Validates the invite token and creates the user account.
835
- // Body: { token, password, ...extraFields }
836
- // ---------------------------------------------------------------------------
837
- async acceptInvite(c) {
838
- const config2 = c.get("config");
839
- const db = config2.db;
840
- if (!db) return c.json({ message: "Database not configured" }, 500);
841
- const body = await c.req.json().catch(() => null);
842
- if (!body?.token || !body?.password) {
843
- return c.json({ error: true, message: "token and password are required." }, 400);
844
- }
845
- let payload;
846
- try {
847
- payload = await verifyCollectionToken(body.token);
848
- } catch {
849
- return c.json({ error: true, message: "Invite token is invalid or has expired." }, 400);
850
- }
851
- if (payload.collection !== this.collection.slug || payload.purpose !== "invite") {
852
- return c.json({ error: true, message: "Invite token is invalid or has expired." }, 400);
853
- }
854
- const inviteeEmail = payload.sub;
855
- const existing = await db.find({
856
- collection: this.collection.slug,
857
- where: { email: inviteeEmail },
858
- limit: 1
859
- });
860
- if (existing.total > 0) {
861
- return c.json({ error: true, message: "An account with that email already exists." }, 409);
862
- }
863
- const { token: _t, password: _p, ...extraFields } = body;
864
- const hashedPassword = await hashPassword(body.password);
865
- const user = await db.create({
866
- collection: this.collection.slug,
867
- data: { ...extraFields, email: inviteeEmail, password: hashedPassword }
868
- });
869
- const sessionToken = await signCollectionToken({
870
- sub: user.id,
871
- email: inviteeEmail,
872
- collection: this.collection.slug
873
- });
874
- const { subject, html } = buildWelcomeEmail(config2, { email: inviteeEmail });
875
- sendEmail(config2, { to: inviteeEmail, subject, html }).catch(
876
- (err) => console.error("[dyrected/core] Failed to send welcome email:", err)
877
- );
878
- const { password: _, ...safeUser } = user;
879
- return c.json({ token: sessionToken, user: safeUser }, 201);
880
- }
881
- };
882
-
883
- // src/controllers/preview.controller.ts
884
- import { SignJWT as SignJWT2, jwtVerify as jwtVerify2 } from "jose";
885
- import { TextEncoder as TextEncoder2 } from "util";
886
- var PreviewController = class {
887
- getSecret() {
888
- const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET || "dyrected-preview-secret-change-me";
889
- return new TextEncoder2().encode(secret);
890
- }
891
- /**
892
- * POST /api/preview-token
893
- * Generates a short-lived token for previewing unsaved data.
894
- */
895
- async createToken(c) {
896
- const body = await c.req.json().catch(() => null);
897
- if (!body?.collectionSlug || !body?.data) {
898
- return c.json({ error: true, message: "collectionSlug and data are required." }, 400);
899
- }
900
- const token = await new SignJWT2({
901
- collectionSlug: body.collectionSlug,
902
- documentId: body.documentId,
903
- data: body.data
904
- }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(this.getSecret());
905
- const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
906
- return c.json({ token, expiresAt });
907
- }
908
- /**
909
- * GET /api/preview-data?token=<jwt>
910
- * Returns the data stored in the preview token.
911
- */
912
- async getData(c) {
913
- const token = c.req.query("token");
914
- if (!token) {
915
- return c.json({ error: true, message: "token query parameter is required." }, 400);
916
- }
917
- try {
918
- const { payload } = await jwtVerify2(token, this.getSecret());
919
- return c.json(payload);
920
- } catch (err) {
921
- return c.json({ error: true, message: "Invalid or expired preview token." }, 401);
922
- }
923
- }
924
- };
925
-
926
- // src/middleware/auth.ts
927
- function requireAuth() {
928
- return async (c, next) => {
929
- const authHeader = c.req.header("Authorization");
930
- const token = authHeader?.replace(/^Bearer\s+/i, "");
931
- if (!token) {
932
- return c.json({ error: true, message: "Authentication required." }, 401);
933
- }
934
- try {
935
- const user = await verifyCollectionToken(token);
936
- c.set("user", user);
937
- await next();
938
- } catch {
939
- return c.json({ error: true, message: "Invalid or expired token." }, 401);
940
- }
941
- };
942
- }
943
- function optionalAuth() {
944
- return async (c, next) => {
945
- const authHeader = c.req.header("Authorization");
946
- const token = authHeader?.replace(/^Bearer\s+/i, "");
947
- if (token) {
948
- try {
949
- const user = await verifyCollectionToken(token);
950
- c.set("user", user);
951
- } catch {
952
- }
953
- }
954
- await next();
955
- };
956
- }
957
-
958
- // src/utils/openapi.ts
959
- function generateOpenApi(config2) {
960
- const spec = {
961
- openapi: "3.0.0",
962
- info: {
963
- title: "Dyrected API",
964
- version: "1.0.0",
965
- description: "Automatically generated OpenAPI specification for the Dyrected project."
966
- },
967
- components: {
968
- schemas: {},
969
- securitySchemes: {
970
- ApiKeyAuth: {
971
- type: "apiKey",
972
- in: "header",
973
- name: "x-api-key"
974
- }
975
- }
976
- },
977
- paths: {},
978
- security: [{ ApiKeyAuth: [] }]
979
- };
980
- for (const collection of config2.collections) {
981
- spec.components.schemas[collection.slug] = collectionToSchema(collection);
982
- }
983
- for (const global of config2.globals) {
984
- spec.components.schemas[global.slug] = globalToSchema(global);
985
- }
986
- for (const collection of config2.collections) {
987
- const slug = collection.slug;
988
- const path = `/api/collections/${slug}`;
989
- const labels = collection.labels || { singular: slug, plural: `${slug}s` };
990
- spec.paths[path] = {
991
- get: {
992
- tags: ["Collections"],
993
- summary: `Find ${labels.plural}`,
994
- parameters: [
995
- { name: "limit", in: "query", schema: { type: "integer", default: 10 } },
996
- { name: "page", in: "query", schema: { type: "integer", default: 1 } },
997
- { name: "where", in: "query", schema: { type: "string" }, description: "JSON filter" },
998
- { name: "sort", in: "query", schema: { type: "string" }, description: "Sort field (e.g. -createdAt)" }
999
- ],
1000
- responses: {
1001
- 200: {
1002
- description: "Success",
1003
- content: {
1004
- "application/json": {
1005
- schema: {
1006
- type: "object",
1007
- properties: {
1008
- docs: { type: "array", items: { $ref: `#/components/schemas/${slug}` } },
1009
- total: { type: "integer" },
1010
- limit: { type: "integer" },
1011
- page: { type: "integer" }
1012
- }
1013
- }
1014
- }
1015
- }
1016
- }
1017
- }
1018
- },
1019
- post: {
1020
- tags: ["Collections"],
1021
- summary: `Create ${labels.singular}`,
1022
- requestBody: {
1023
- required: true,
1024
- content: {
1025
- "application/json": {
1026
- schema: { $ref: `#/components/schemas/${slug}` }
1027
- }
1028
- }
1029
- },
1030
- responses: {
1031
- 201: {
1032
- description: "Created",
1033
- content: {
1034
- "application/json": {
1035
- schema: { $ref: `#/components/schemas/${slug}` }
1036
- }
1037
- }
1038
- }
1039
- }
1040
- }
1041
- };
1042
- spec.paths[`${path}/{id}`] = {
1043
- get: {
1044
- tags: ["Collections"],
1045
- summary: `Get a single ${labels.singular}`,
1046
- parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
1047
- responses: {
1048
- 200: {
1049
- description: "Success",
1050
- content: {
1051
- "application/json": {
1052
- schema: { $ref: `#/components/schemas/${slug}` }
1053
- }
1054
- }
1055
- }
1056
- }
1057
- },
1058
- patch: {
1059
- tags: ["Collections"],
1060
- summary: `Update ${labels.singular}`,
1061
- parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
1062
- requestBody: {
1063
- required: true,
1064
- content: {
1065
- "application/json": {
1066
- schema: { $ref: `#/components/schemas/${slug}` }
1067
- }
1068
- }
1069
- },
1070
- responses: {
1071
- 200: {
1072
- description: "Updated",
1073
- content: {
1074
- "application/json": {
1075
- schema: { $ref: `#/components/schemas/${slug}` }
1076
- }
1077
- }
1078
- }
1079
- }
1080
- },
1081
- delete: {
1082
- tags: ["Collections"],
1083
- summary: `Delete ${labels.singular}`,
1084
- parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
1085
- responses: {
1086
- 204: { description: "Deleted" }
1087
- }
1088
- }
1089
- };
1090
- }
1091
- for (const global of config2.globals) {
1092
- const slug = global.slug;
1093
- const path = `/api/globals/${slug}`;
1094
- spec.paths[path] = {
1095
- get: {
1096
- tags: ["Globals"],
1097
- summary: `Get ${global.label || slug}`,
1098
- responses: {
1099
- 200: {
1100
- description: "Success",
1101
- content: {
1102
- "application/json": {
1103
- schema: { $ref: `#/components/schemas/${slug}` }
1104
- }
1105
- }
1106
- }
1107
- }
1108
- },
1109
- patch: {
1110
- tags: ["Globals"],
1111
- summary: `Update ${global.label || slug}`,
1112
- requestBody: {
1113
- required: true,
1114
- content: {
1115
- "application/json": {
1116
- schema: { $ref: `#/components/schemas/${slug}` }
1117
- }
1118
- }
1119
- },
1120
- responses: {
1121
- 200: {
1122
- description: "Updated",
1123
- content: {
1124
- "application/json": {
1125
- schema: { $ref: `#/components/schemas/${slug}` }
1126
- }
1127
- }
1128
- }
1129
- }
1130
- }
1131
- };
1132
- }
1133
- if (config2.storage) {
1134
- spec.paths["/api/media"] = {
1135
- get: {
1136
- tags: ["Media"],
1137
- summary: "List Media",
1138
- responses: {
1139
- 200: {
1140
- description: "Success",
1141
- content: {
1142
- "application/json": {
1143
- schema: {
1144
- type: "object",
1145
- properties: {
1146
- docs: { type: "array", items: { type: "object", additionalProperties: true } }
1147
- }
1148
- }
1149
- }
1150
- }
1151
- }
1152
- }
1153
- },
1154
- post: {
1155
- tags: ["Media"],
1156
- summary: "Upload Media",
1157
- requestBody: {
1158
- content: {
1159
- "multipart/form-data": {
1160
- schema: {
1161
- type: "object",
1162
- properties: {
1163
- file: { type: "string", format: "binary" }
1164
- }
1165
- }
1166
- }
1167
- }
1168
- },
1169
- responses: {
1170
- 201: { description: "Uploaded" }
1171
- }
1172
- }
1173
- };
1174
- }
1175
- return spec;
1176
- }
1177
- function collectionToSchema(collection) {
1178
- const { properties, required } = fieldsToProperties(collection.fields);
1179
- return {
1180
- type: "object",
1181
- properties: {
1182
- id: { type: "string" },
1183
- createdAt: { type: "string", format: "date-time" },
1184
- updatedAt: { type: "string", format: "date-time" },
1185
- ...properties
1186
- },
1187
- required: ["id", ...required]
1188
- };
1189
- }
1190
- function globalToSchema(global) {
1191
- const { properties, required } = fieldsToProperties(global.fields);
1192
- return {
1193
- type: "object",
1194
- properties,
1195
- required
1196
- };
1197
- }
1198
- function fieldsToProperties(fields) {
1199
- const props = {};
1200
- const required = [];
1201
- for (const field of fields) {
1202
- props[field.name] = fieldToSchema(field);
1203
- if (field.required) {
1204
- required.push(field.name);
1205
- }
1206
- }
1207
- return { properties: props, required };
1208
- }
1209
- function fieldToSchema(field) {
1210
- let schema = {};
1211
- switch (field.type) {
1212
- case "text":
1213
- case "textarea":
1214
- case "email":
1215
- case "url":
1216
- schema = { type: "string" };
1217
- break;
1218
- case "number":
1219
- schema = { type: "number" };
1220
- break;
1221
- case "boolean":
1222
- schema = { type: "boolean" };
1223
- break;
1224
- case "date":
1225
- schema = { type: "string", format: "date-time" };
1226
- break;
1227
- case "select":
1228
- schema = { type: "string", enum: field.options?.map((o) => typeof o === "string" ? o : o.value) };
1229
- break;
1230
- case "multiSelect":
1231
- schema = { type: "array", items: { type: "string", enum: field.options?.map((o) => typeof o === "string" ? o : o.value) } };
1232
- break;
1233
- case "relationship":
1234
- schema = { type: "string", description: `ID of a ${field.collection} record` };
1235
- break;
1236
- case "object": {
1237
- const { properties, required } = fieldsToProperties(field.fields || []);
1238
- schema = { type: "object", properties, required };
1239
- break;
1240
- }
1241
- case "array": {
1242
- const { properties, required } = fieldsToProperties(field.fields || []);
1243
- schema = { type: "array", items: { type: "object", properties, required } };
1244
- break;
1245
- }
1246
- case "json":
1247
- case "richText":
1248
- schema = { type: "object", additionalProperties: true };
1249
- break;
1250
- case "blocks":
1251
- schema = {
1252
- type: "array",
1253
- items: {
1254
- oneOf: field.blocks?.map((block) => {
1255
- const { properties, required } = fieldsToProperties(block.fields);
1256
- return {
1257
- type: "object",
1258
- properties: {
1259
- blockType: { type: "string", enum: [block.slug] },
1260
- ...properties
1261
- },
1262
- required: ["blockType", ...required]
1263
- };
1264
- })
1265
- }
1266
- };
1267
- break;
1268
- default:
1269
- schema = { type: "string" };
1270
- }
1271
- if (field.label) schema.description = field.label;
1272
- return schema;
1273
- }
1274
-
1275
- // src/utils/swagger.ts
1276
- function getSwaggerHtml(specUrl = "/api/openapi.json") {
1277
- return `
1278
- <!DOCTYPE html>
1279
- <html lang="en">
1280
- <head>
1281
- <meta charset="utf-8" />
1282
- <meta name="viewport" content="width=device-width, initial-scale=1" />
1283
- <meta name="description" content="SwaggerUI" />
1284
- <title>Dyrected API Documentation</title>
1285
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
1286
- </head>
1287
- <body>
1288
- <div id="swagger-ui"></div>
1289
- <script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js" charset="UTF-8"></script>
1290
- <script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js" charset="UTF-8"></script>
1291
- <script>
1292
- window.onload = () => {
1293
- // Forward the apikey query param when loading the spec and making API calls
1294
- const params = new URLSearchParams(window.location.search);
1295
- const apiKey = params.get('apikey');
1296
- const specUrlWithKey = apiKey ? '${specUrl}?apikey=' + encodeURIComponent(apiKey) : '${specUrl}';
1297
-
1298
- window.ui = SwaggerUIBundle({
1299
- url: specUrlWithKey,
1300
- dom_id: '#swagger-ui',
1301
- presets: [
1302
- SwaggerUIBundle.presets.apis,
1303
- SwaggerUIStandalonePreset
1304
- ],
1305
- layout: "BaseLayout",
1306
- deepLinking: true,
1307
- showExtensions: true,
1308
- showCommonExtensions: true,
1309
- // Inject x-api-key header on every request made from the Swagger UI
1310
- requestInterceptor: (request) => {
1311
- if (apiKey) {
1312
- request.headers['x-api-key'] = apiKey;
1313
- }
1314
- return request;
1315
- }
1316
- });
1317
- };
1318
- </script>
1319
- </body>
1320
- </html>
1321
- `;
1322
- }
1323
-
1324
- // src/auth/jexl.ts
1325
- import jexl from "jexl";
1326
- async function evaluateAccess(expression, context) {
1327
- if (expression === void 0 || expression === null) return false;
1328
- if (typeof expression === "boolean") return expression;
1329
- try {
1330
- const result = await jexl.eval(expression, context);
1331
- return !!result;
1332
- } catch (err) {
1333
- console.error("[dyrected/core] Jexl evaluation failed:", err);
1334
- return false;
1335
- }
1336
- }
1337
-
1338
- // src/router.ts
1339
- function accessGate(target, action) {
1340
- return async (c, next) => {
1341
- const user = c.get("user");
1342
- const accessExpr = target.access?.[action];
1343
- if (accessExpr === void 0 || accessExpr === null) {
1344
- return await next();
1345
- }
1346
- const accessArgs = { user, req: c.req, doc: null };
1347
- const allowed = await evaluateAccess(accessExpr, accessArgs);
1348
- if (!allowed) {
1349
- return c.json({ error: true, message: `Access denied: ${action} on ${target.slug}` }, 403);
1350
- }
1351
- await next();
1352
- };
1353
- }
1354
- function registerRoutes(app, config2) {
1355
- app.get("/api/schemas", optionalAuth(), async (c) => {
1356
- const siteId = c.req.header("X-Site-Id");
1357
- let collections = [...config2.collections];
1358
- let globals = [...config2.globals];
1359
- if (siteId && config2.onSchemaFetch) {
1360
- const dynamic = await config2.onSchemaFetch(siteId);
1361
- if (dynamic.collections) collections = [...collections, ...dynamic.collections];
1362
- if (dynamic.globals) globals = [...globals, ...dynamic.globals];
1363
- if (dynamic.admin) {
1364
- config2.admin = { ...config2.admin, ...dynamic.admin };
1365
- }
1366
- }
1367
- const user = c.get("user");
1368
- const accessArgs = { user, req: c.req, doc: null };
1369
- const resolveAccess = async (access) => {
1370
- if (access === void 0 || access === null) return true;
1371
- if (typeof access === "function") {
1372
- try {
1373
- const result = await access(accessArgs);
1374
- return typeof result === "boolean" ? result : !!result;
1375
- } catch (err) {
1376
- console.error("[dyrected/core] Functional access check failed:", err);
1377
- return false;
1378
- }
1379
- }
1380
- if (typeof access === "string" || typeof access === "boolean") {
1381
- return evaluateAccess(access, accessArgs);
1382
- }
1383
- return true;
1384
- };
1385
- const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
1386
- slug: col.slug,
1387
- labels: col.labels,
1388
- access: {
1389
- read: await resolveAccess(col.access?.read),
1390
- create: await resolveAccess(col.access?.create),
1391
- update: await resolveAccess(col.access?.update),
1392
- delete: await resolveAccess(col.access?.delete)
1393
- },
1394
- fields: await Promise.all(col.fields.map(async (f) => ({
1395
- name: f.name,
1396
- type: f.type,
1397
- label: f.label,
1398
- required: f.required,
1399
- defaultValue: f.defaultValue,
1400
- options: f.options,
1401
- relationTo: f.relationTo,
1402
- hasMany: f.hasMany,
1403
- fields: f.fields,
1404
- blocks: f.blocks,
1405
- admin: f.admin,
1406
- access: {
1407
- read: await resolveAccess(f.access?.read),
1408
- update: await resolveAccess(f.access?.update)
1409
- }
1410
- }))),
1411
- upload: !!col.upload,
1412
- auth: !!col.auth,
1413
- admin: col.admin
1414
- })));
1415
- const filteredGlobals = await Promise.all(globals.filter((glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId).map(async (glb) => ({
1416
- slug: glb.slug,
1417
- label: glb.label,
1418
- access: {
1419
- read: await resolveAccess(glb.access?.read),
1420
- update: await resolveAccess(glb.access?.update)
1421
- },
1422
- fields: await Promise.all(glb.fields.map(async (f) => ({
1423
- name: f.name,
1424
- type: f.type,
1425
- label: f.label,
1426
- required: f.required,
1427
- defaultValue: f.defaultValue,
1428
- options: f.options,
1429
- relationTo: f.relationTo,
1430
- hasMany: f.hasMany,
1431
- fields: f.fields,
1432
- blocks: f.blocks,
1433
- admin: f.admin,
1434
- access: {
1435
- read: await resolveAccess(f.access?.read),
1436
- update: await resolveAccess(f.access?.update)
1437
- }
1438
- }))),
1439
- admin: glb.admin
1440
- })));
1441
- return c.json({
1442
- collections: filteredCollections,
1443
- globals: filteredGlobals,
1444
- admin: config2.admin || {}
1445
- });
1446
- });
1447
- app.get("/api/openapi.json", (c) => {
1448
- return c.json(generateOpenApi(config2));
1449
- });
1450
- app.get("/api/docs", (c) => {
1451
- return c.html(getSwaggerHtml());
1452
- });
1453
- app.get("/api/media/:filename{.+$}", async (c) => {
1454
- const mediaController = new MediaController("media");
1455
- return mediaController.serve(c);
1456
- });
1457
- app.get("/media/:filename{.+$}", async (c) => {
1458
- const mediaController = new MediaController("media");
1459
- return mediaController.serve(c);
1460
- });
1461
- if (config2.storage) {
1462
- const uploadCollections = config2.collections.filter((c) => c.upload);
1463
- for (const col of uploadCollections) {
1464
- const mediaController = new MediaController(col.slug);
1465
- const prefix = `/api/collections/${col.slug}`;
1466
- app.get(`${prefix}/media`, accessGate(col, "read"), (c) => mediaController.find(c));
1467
- app.get(`${prefix}/media/:filename{.+$}`, (c) => mediaController.serve(c));
1468
- app.post(`${prefix}/media`, accessGate(col, "create"), (c) => mediaController.upload(c));
1469
- app.delete(`${prefix}/media/:id`, accessGate(col, "delete"), (c) => mediaController.delete(c));
1470
- }
1471
- }
1472
- for (const collection of config2.collections) {
1473
- if (!collection.auth) continue;
1474
- const path = `/api/collections/${collection.slug}`;
1475
- const authController = new AuthController(collection);
1476
- app.post(`${path}/login`, (c) => authController.login(c));
1477
- app.post(`${path}/logout`, (c) => authController.logout(c));
1478
- app.get(`${path}/init`, (c) => authController.init(c));
1479
- app.post(`${path}/first-user`, (c) => authController.registerFirstUser(c));
1480
- app.get(`${path}/me`, requireAuth(), (c) => authController.me(c));
1481
- app.post(`${path}/refresh-token`, requireAuth(), (c) => authController.refreshToken(c));
1482
- app.post(`${path}/forgot-password`, (c) => authController.forgotPassword(c));
1483
- app.post(`${path}/reset-password`, (c) => authController.resetPassword(c));
1484
- app.post(`${path}/invite`, requireAuth(), (c) => authController.invite(c));
1485
- app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
1486
- }
1487
- for (const collection of config2.collections) {
1488
- const path = `/api/collections/${collection.slug}`;
1489
- const controller = new CollectionController(collection);
1490
- app.get(path, accessGate(collection, "read"), (c) => controller.find(c));
1491
- app.post(path, accessGate(collection, "create"), (c) => controller.create(c));
1492
- app.post(`${path}/media`, accessGate(collection, "create"), (c) => controller.create(c));
1493
- app.get(`${path}/:id`, accessGate(collection, "read"), (c) => controller.findOne(c));
1494
- app.patch(`${path}/:id`, accessGate(collection, "update"), (c) => controller.update(c));
1495
- app.delete(`${path}/:id`, accessGate(collection, "delete"), (c) => controller.delete(c));
1496
- app.post(`${path}/seed`, (c) => controller.seed(c));
1497
- }
1498
- for (const global of config2.globals) {
1499
- const path = `/api/globals/${global.slug}`;
1500
- const controller = new GlobalController(global);
1501
- app.get(path, accessGate(global, "read"), (c) => controller.get(c));
1502
- app.patch(path, accessGate(global, "update"), (c) => controller.update(c));
1503
- app.post(`${path}/seed`, (c) => controller.seed(c));
1504
- }
1505
- const previewController = new PreviewController();
1506
- app.post("/api/preview-token", requireAuth(), (c) => previewController.createToken(c));
1507
- app.get("/api/preview-data", (c) => previewController.getData(c));
1508
- app.all("/api/collections/:slug/:id?", async (c) => {
1509
- const slug = c.req.param("slug");
1510
- const id = c.req.param("id");
1511
- const siteId = c.req.header("X-Site-Id") || c.get("siteId");
1512
- const config3 = c.get("config");
1513
- if (config3.collections.some((col) => col.slug === slug)) {
1514
- return c.json({ message: "Method Not Allowed" }, 405);
1515
- }
1516
- if (config3.onSchemaFetch && siteId) {
1517
- const dynamic = await config3.onSchemaFetch(siteId);
1518
- let collection = dynamic.collections?.find((col) => col.slug === slug);
1519
- if (!collection && slug === "media") {
1520
- collection = {
1521
- slug: "media",
1522
- labels: { singular: "Media", plural: "Media" },
1523
- upload: true,
1524
- fields: []
1525
- };
1526
- }
1527
- if (collection) {
1528
- if (collection.auth && id) {
1529
- const authController = new AuthController(collection);
1530
- const method2 = c.req.method;
1531
- if (method2 === "POST" && id === "login") return authController.login(c);
1532
- if (method2 === "POST" && id === "logout") return authController.logout(c);
1533
- if (method2 === "GET" && id === "me") return authController.me(c);
1534
- if (method2 === "POST" && id === "refresh-token") return authController.refreshToken(c);
1535
- if (method2 === "POST" && id === "forgot-password") return authController.forgotPassword(c);
1536
- if (method2 === "POST" && id === "reset-password") return authController.resetPassword(c);
1537
- }
1538
- const controller = new CollectionController(collection);
1539
- const method = c.req.method;
1540
- if (id) {
1541
- if (method === "GET") return controller.findOne(c);
1542
- if (method === "PATCH") return controller.update(c);
1543
- if (method === "DELETE") return controller.delete(c);
1544
- if (method === "POST" && id === "media") return controller.create(c);
1545
- } else {
1546
- if (method === "GET") return controller.find(c);
1547
- if (method === "POST") return controller.create(c);
1548
- }
1549
- }
1550
- }
1551
- return c.json({ message: `Collection "${slug}" not found` }, 404);
1552
- });
1553
- app.all("/api/globals/:slug", async (c) => {
1554
- const slug = c.req.param("slug");
1555
- const siteId = c.req.header("X-Site-Id") || c.get("siteId");
1556
- const config3 = c.get("config");
1557
- if (config3.globals.some((glb) => glb.slug === slug)) {
1558
- return c.json({ message: "Method Not Allowed" }, 405);
1559
- }
1560
- if (config3.onSchemaFetch && siteId) {
1561
- const dynamic = await config3.onSchemaFetch(siteId);
1562
- const global = dynamic.globals?.find((glb) => glb.slug === slug);
1563
- if (global) {
1564
- const controller = new GlobalController(global);
1565
- if (c.req.method === "GET") return controller.get(c);
1566
- if (c.req.method === "PATCH") return controller.update(c);
1567
- }
1568
- }
1569
- return c.json({ message: `Global "${slug}" not found` }, 404);
1570
- });
1571
- }
1572
-
1573
- // src/utils/config.ts
1574
- function normalizeConfig(config2) {
1575
- const normalizedCollections = config2.collections.map((col) => {
1576
- const useTimestamps = col.timestamps !== false;
1577
- if (!useTimestamps) return col;
1578
- const timestampFields = [
1579
- {
1580
- name: "createdAt",
1581
- type: "date",
1582
- label: "Created At",
1583
- admin: {
1584
- readOnly: true,
1585
- hidden: false,
1586
- description: "The date this record was created."
1587
- }
1588
- },
1589
- {
1590
- name: "updatedAt",
1591
- type: "date",
1592
- label: "Updated At",
1593
- admin: {
1594
- readOnly: true,
1595
- hidden: false,
1596
- description: "The date this record was last updated."
1597
- }
1598
- }
1599
- ];
1600
- const existingFieldNames = col.fields.map((f) => f.name);
1601
- const fieldsToInject = timestampFields.filter((f) => !existingFieldNames.includes(f.name));
1602
- return {
1603
- ...col,
1604
- fields: [...col.fields, ...fieldsToInject]
1605
- };
1606
- });
1607
- return {
1608
- ...config2,
1609
- collections: normalizedCollections
1610
- };
1611
- }
1612
-
1613
- // src/app.ts
1614
- async function createDyrectedApp(rawConfig) {
1615
- const config2 = normalizeConfig(rawConfig);
1616
- const app = new Hono();
1617
- if (config2.db?.sync) {
1618
- await config2.db.sync(config2.collections, config2.globals);
1619
- }
1620
- app.use("*", requestId());
1621
- app.use("*", async (c, next) => {
1622
- const start = Date.now();
1623
- await next();
1624
- const ms = Date.now() - start;
1625
- console.log(`[dyrected/api] ${c.req.method} ${c.req.path} ${c.res.status} - ${ms}ms`);
1626
- });
1627
- app.use("*", cors());
1628
- app.use("*", async (c, next) => {
1629
- c.set("config", config2);
1630
- if (!c.get("siteId")) {
1631
- c.set("siteId", "default");
1632
- }
1633
- await next();
1634
- });
1635
- app.get("/health", (c) => c.json({ status: "ok", version: "0.0.1" }));
1636
- app.get("/routes", (c) => {
1637
- const routes = app.routes.map((r) => ({ method: r.method, path: r.path }));
1638
- return c.json({ routes });
1639
- });
1640
- app.onError((err, c) => {
1641
- console.error(`[dyrected/core] Uncaught Error:`, err);
1642
- return c.json({
1643
- message: err.message || "Internal Server Error",
1644
- stack: process.env.NODE_ENV === "development" ? err.stack : void 0
1645
- }, 500);
1646
- });
1647
- registerRoutes(app, config2);
1648
- return app;
1649
- }
1650
-
1651
- // src/services/media.service.ts
1652
- var MediaService = class {
1653
- /**
1654
- * Fetches metadata for a given URL.
1655
- * Supports YouTube and Vimeo.
1656
- */
1657
- static async fetchMetadata(url) {
1658
- if (!url) return null;
1659
- if (url.includes("youtube.com") || url.includes("youtu.be")) {
1660
- const videoId = this.extractYoutubeId(url);
1661
- if (videoId) {
1662
- return {
1663
- provider: "youtube",
1664
- provider_id: videoId,
1665
- thumbnail: `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`,
1666
- embedUrl: `https://www.youtube.com/embed/${videoId}`,
1667
- type: "video"
1668
- };
1669
- }
1670
- }
1671
- if (url.includes("vimeo.com")) {
1672
- const vimeoId = this.extractVimeoId(url);
1673
- if (vimeoId) {
1674
- return {
1675
- provider: "vimeo",
1676
- provider_id: vimeoId,
1677
- thumbnail: "",
1678
- // Requires oEmbed API for reliable thumbnails
1679
- embedUrl: `https://player.vimeo.com/video/${vimeoId}`,
1680
- type: "video"
1681
- };
1682
- }
1683
- }
1684
- return null;
1685
- }
1686
- static extractYoutubeId(url) {
1687
- const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
1688
- const match = url.match(regExp);
1689
- return match && match[2].length === 11 ? match[2] : null;
1690
- }
1691
- static extractVimeoId(url) {
1692
- const match = url.match(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:$|\/|\?)/);
1693
- return match ? match[1] : null;
1694
- }
1695
- };
1696
-
1697
- // src/utils/setup-prompt.ts
1698
- function generateAIPrompt(activeTab, config2) {
1699
- const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
1700
- const backendPkg = activeTab === "nuxt" ? "@dyrected/nuxt" : "@dyrected/next";
1701
- const isSelfHosted = config2.isSelfHosted ?? (config2.baseUrl?.includes("localhost") || !config2.apiKey);
1702
- const baseIntro = isSelfHosted ? `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${config2.siteName || "new"} project using ${frameworkLabel}. This is a SELF-HOSTED installation.
1703
- The backend is already configured via ${backendPkg}.
1704
- Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams.` : `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${config2.siteName || "new"} project using ${frameworkLabel}. Complete the entire setup automatically, prioritizing DATA PRESERVATION and marketing independence.`;
1705
- const credentials = `
1706
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1707
- SITE CREDENTIALS
1708
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1709
- ${config2.siteName ? `Site Name : ${config2.siteName}
1710
- ` : ""}${isSelfHosted ? "" : `Site ID : ${config2.siteId}
1711
- API Key : ${config2.apiKey}
1712
- `}API URL : ${config2.baseUrl || "http://localhost:3000"}
1713
- `;
1714
- const importantNotes = `
1715
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1716
- IMPORTANT NOTES \u2014 READ BEFORE ACTING
1717
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1718
- - \`client.collection(slug)\` is the primary API entrypoint. Do NOT use \`client.collections\`.
1719
- - Blocks are stored as \`[{ blockType: '<slug>', ...fields }]\` \u2014 always switch on \`blockType\` when rendering.
1720
- - MARKETING INDEPENDENCE: Always use a dynamic \`pages\` collection with a catch-all route. Marketing should create pages without a developer. (Exceptions: Special pages like Contact or those with complex forms may use static routes).
1721
- - AUTO-SEEDING: Use \`initialData\` in all data fetches to ensure the site is never empty on first load.
1722
- - Globals use \`client.global(slug).get()\` and \`client.global(slug).update(data)\`.
1723
- - Relationship fields are populated to the specified \`depth\` (default: 1). Set \`depth: 0\` for IDs only.
1724
- `;
1725
- const strategy = `
1726
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1727
- PHASE 0 \u2014 DATA PRESERVATION & STRATEGY
1728
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1729
- 1. BACKUP: If an existing site is provided, you MUST extract all current content (text, assets, structure) and save it into a \`migration/\` folder as structured .md files BEFORE modifying any code. Never lose data.
1730
- 2. DISCOVERY: If NO existing site is provided, STOP and ask the user:
1731
- - "What are your core content types (e.g. Services, Team, Blog)?"
1732
- - "How do you want your marketing team to manage the page layouts?"
1733
- 3. ARCHITECTURAL CREATIVITY: Design the CMS for longevity. Use \`blocks\` for flexible page builders, \`globals\` for site settings, and \`collections\` for repeated content.
1734
-
1735
- STEP 1 \u2014 CONTENT MODEL (dyrected.config.ts)
1736
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1737
- Use \`defineCollection\`, \`defineGlobal\`, and \`defineConfig\` from '@dyrected/core'.
1738
-
1739
- SUPPORTED FIELD TYPES:
1740
- Primitive : text | textarea | richText | number | boolean | date | email | url | json
1741
- Choice : select | multiSelect (requires \`options: [{ label, value }]\`)
1742
- Structural : array | object (requires nested \`fields: [...]\`)
1743
- Relation : relationship (requires \`collection: '<slug>'\`)
1744
- Media : image (use a relationship to an upload collection)
1745
- Blocks : blocks (requires \`blocks: [{ slug, labels, fields }]\`)
1746
-
1747
- COLLECTION OPTIONS:
1748
- \`upload: true\` \u2014 turns this collection into a media library (file uploads)
1749
- \`auth: true\` \u2014 adds login/register/me endpoints; password field is auto-added
1750
- \`admin.group\` \u2014 groups this collection under a sidebar heading
1751
- \`admin.useAsTitle\` \u2014 field to use as the display title in the admin list view
1752
- \`admin.hidden\` \u2014 hide from sidebar (useful for internal/system collections)
1753
-
1754
- FIELD OPTIONS:
1755
- \`required\` \u2014 validation
1756
- \`unique\` \u2014 database-level uniqueness
1757
- \`defaultValue\` \u2014 fallback value
1758
- \`admin.condition\` \u2014 "expression" \u2014 Jexl string expression to show/hide field (e.g. "status == \\"published\\"")
1759
- \`admin.layout\` \u2014 "radio" | "dropdown" \u2014 Visual layout for select/multiSelect
1760
- \`admin.direction\` \u2014 "vertical" | "horizontal" \u2014 Layout direction for radio groups
1761
- \`admin.readOnly\` \u2014 display-only in the form
1762
- \`admin.hidden\` \u2014 completely hidden from editor UI
1763
- \`access.read\` \u2014 ({ user }) => boolean \u2014 field-level read access
1764
- \`access.update\` \u2014 ({ user }) => boolean \u2014 field-level write access
1765
- \`hooks.beforeChange\` \u2014 [async (value) => newValue] \u2014 transform value before save
1766
- \`hooks.afterRead\` \u2014 [async (value) => newValue] \u2014 transform value after read
1767
-
1768
- BLOCKS EXPLAINED:
1769
- A \`blocks\` field stores an ordered array of typed content blocks.
1770
- Each block has a \`blockType\` discriminator and its own set of fields.
1771
- The admin UI renders a drag-and-drop block editor automatically.
1772
- On the frontend, iterate the array and switch on \`block.blockType\`.
1773
-
1774
- COMPLETE EXAMPLE:
1775
- \`\`\`typescript
1776
- import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
1777
-
1778
- // \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1779
- const media = defineCollection({
1780
- slug: 'media',
1781
- labels: { singular: 'Media Item', plural: 'Media' },
1782
- upload: true,
1783
- fields: [
1784
- { name: 'alt', type: 'text', label: 'Alt Text' },
1785
- { name: 'caption', type: 'textarea', label: 'Caption' },
1786
- ],
1787
- })
1788
-
1789
- // \u2500\u2500 Authentication collection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1790
- const customers = defineCollection({
1791
- slug: 'customers',
1792
- labels: { singular: 'Customer', plural: 'Customers' },
1793
- auth: true, // adds /customers/login, /customers/me, etc.
1794
- admin: { group: 'Membership' },
1795
- fields: [
1796
- { name: 'name', type: 'text', required: true },
1797
- { name: 'email', type: 'email', required: true, unique: true },
1798
- // 'password' is auto-added when auth: true
1799
- { name: 'avatar', type: 'relationship', relationTo: 'media' },
1800
- { name: 'role', type: 'select', admin: { layout: 'radio' }, options: [
1801
- { label: 'Member', value: 'member' },
1802
- { label: 'VIP', value: 'vip' },
1803
- ]},
1804
- ],
1805
- })
1806
-
1807
- // \u2500\u2500 Pages with blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1808
- const pages = defineCollection({
1809
- slug: 'pages',
1810
- labels: { singular: 'Page', plural: 'Pages' },
1811
- admin: { useAsTitle: 'title', group: 'Content' },
1812
- fields: [
1813
- { name: 'title', type: 'text', required: true },
1814
- { name: 'slug', type: 'text', required: true, unique: true },
1815
- { name: 'seo', type: 'object', fields: [
1816
- { name: 'metaTitle', type: 'text' },
1817
- { name: 'metaDescription', type: 'textarea' },
1818
- { name: 'ogImage', type: 'relationship', relationTo: 'media' },
1819
- ]},
1820
- {
1821
- name: 'layout',
1822
- type: 'blocks',
1823
- label: 'Page Layout',
1824
- blocks: [
1825
- {
1826
- slug: 'hero',
1827
- labels: { singular: 'Hero', plural: 'Heroes' },
1828
- fields: [
1829
- { name: 'heading', type: 'text', required: true },
1830
- { name: 'subheading', type: 'textarea' },
1831
- { name: 'image', type: 'relationship', relationTo: 'media' },
1832
- { name: 'ctaLabel', type: 'text' },
1833
- { name: 'ctaLink', type: 'url' },
1834
- ],
1835
- },
1836
- {
1837
- slug: 'richContent',
1838
- labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
1839
- fields: [
1840
- { name: 'content', type: 'richText', required: true },
1841
- ],
1842
- },
1843
- ],
1844
- },
1845
- ],
1846
- })
1847
-
1848
- export default defineConfig({
1849
- collections: [media, customers, pages],
1850
- admin: {
1851
- branding: {
1852
- primaryColor: '#4f46e5',
1853
- logo: '/logo.png',
1854
- }
1855
- }
1856
- })
1857
- \`\`\`
1858
-
1859
- ${isSelfHosted ? "" : `
1860
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1861
- STEP 2 \u2014 CHOOSE YOUR MODE
1862
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1863
- The developer can choose between two modes:
1864
-
1865
- 1. CLOUD MODE (Managed)
1866
- - Use the SITE CREDENTIALS above.
1867
- - Point baseUrl to ${config2.baseUrl}.
1868
- - Content is stored in Dyrected Cloud.
1869
-
1870
- 2. SELF-HOSTED MODE (Core)
1871
- - Do NOT use apiKey/siteId (unless for proxying).
1872
- - Use a database adapter like \`SqliteAdapter\` from '@dyrected/db-sqlite'.
1873
- - Content is stored locally in the developer's project.
1874
- `}
1875
-
1876
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1877
- STEP ${isSelfHosted ? "2" : "3"} \u2014 MOUNTING THE ADMIN UI
1878
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1879
- The Admin UI can be mounted on any path (e.g. /cms-admin).
1880
- Pass the \`basename\` prop to the \`<AdminUI />\` component to match your route.
1881
-
1882
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1883
- STEP ${isSelfHosted ? "3" : "4"} \u2014 FRONTEND IMPLEMENTATION
1884
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1885
- `;
1886
- const frameworks = {
1887
- next: `Install \`@dyrected/sdk\` (or \`@dyrected/next\` if you want Next.js server helpers).
1888
-
1889
- SDK CLIENT SETUP (\`lib/dyrected.ts\`):
1890
- \`\`\`ts
1891
- import { createClient } from '@dyrected/sdk'
1892
-
1893
- export const dyrected = createClient({
1894
- baseUrl: '${config2.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
1895
- apiKey: '${config2.apiKey}',
1896
- siteId: '${config2.siteId}',`}
1897
- })
1898
- \`\`\``,
1899
- nuxt: `Install \`@dyrected/nuxt\` and add it to \`nuxt.config.ts\`:
1900
- \`\`\`ts
1901
- export default defineNuxtConfig({
1902
- modules: ['@dyrected/nuxt'],
1903
- dyrected: {
1904
- baseUrl: '${config2.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
1905
- apiKey: '${config2.apiKey}',
1906
- siteId: '${config2.siteId}',`}
1907
- },
1908
- })
1909
- \`\`\`
1910
-
1911
- MOUNTING THE ADMIN DASHBOARD (\`pages/cms-admin.vue\`):
1912
- \`\`\`vue
1913
- <script setup lang="ts">
1914
- definePageMeta({ layout: false })
1915
- </script>
1916
-
1917
- <template>
1918
- <ClientOnly>
1919
- <DyrectedAdmin basename="/cms-admin" />
1920
- </ClientOnly>
1921
- </template>
1922
- \`\`\`
1923
- `,
1924
- react: `Install \`@dyrected/sdk\`:
1925
-
1926
- CLIENT SETUP (\`lib/dyrected.ts\`):
1927
- \`\`\`ts
1928
- import { createClient } from '@dyrected/sdk'
1929
-
1930
- export const dyrected = createClient({
1931
- baseUrl: '${config2.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
1932
- apiKey: '${config2.apiKey}',
1933
- siteId: '${config2.siteId}',`}
1934
- })
1935
- \`\`\`
1936
- `,
1937
- vue: `Install \`@dyrected/sdk\`:
1938
-
1939
- CLIENT SETUP (\`lib/dyrected.ts\`):
1940
- \`\`\`ts
1941
- import { createClient } from '@dyrected/sdk'
1942
-
1943
- export const dyrected = createClient({
1944
- baseUrl: '${config2.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
1945
- apiKey: '${config2.apiKey}',
1946
- siteId: '${config2.siteId}',`}
1947
- })
1948
- \`\`\`
1949
- `
1950
- };
1951
- return baseIntro + credentials + importantNotes + strategy + (frameworks[activeTab] || frameworks.next) + `
1952
-
1953
- API Reference: ${config2.baseUrl || "http://localhost:3000"}/api/docs`;
1954
- }
1955
-
1956
- // src/index.ts
1957
- function defineCollection(config2) {
1958
- return config2;
1959
- }
1960
- function defineGlobal(config2) {
1961
- return config2;
1962
- }
1963
- function defineConfig(config2) {
1964
- return config2;
1965
- }
1966
- export {
1967
- MediaService,
1968
- PopulationService,
1969
- createDyrectedApp,
1970
- defineCollection,
1971
- defineConfig,
1972
- defineGlobal,
1973
- generateAIPrompt
1974
- };