@dyrected/core 2.6.3 → 2.6.4

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 (352) hide show
  1. package/dist/__tests__/access-features.test.d.ts +2 -0
  2. package/dist/__tests__/access-features.test.d.ts.map +1 -0
  3. package/dist/__tests__/access-features.test.js +159 -0
  4. package/dist/__tests__/access-features.test.js.map +1 -0
  5. package/dist/__tests__/access-parity.test.d.ts +2 -0
  6. package/dist/__tests__/access-parity.test.d.ts.map +1 -0
  7. package/dist/__tests__/access-parity.test.js +338 -0
  8. package/dist/__tests__/access-parity.test.js.map +1 -0
  9. package/dist/__tests__/access.test.d.ts +2 -0
  10. package/dist/__tests__/access.test.d.ts.map +1 -0
  11. package/dist/__tests__/access.test.js +137 -0
  12. package/dist/__tests__/access.test.js.map +1 -0
  13. package/dist/__tests__/admin-auth.test.d.ts +2 -0
  14. package/dist/__tests__/admin-auth.test.d.ts.map +1 -0
  15. package/dist/__tests__/admin-auth.test.js +440 -0
  16. package/dist/__tests__/admin-auth.test.js.map +1 -0
  17. package/dist/__tests__/app.test.d.ts +2 -0
  18. package/dist/__tests__/app.test.d.ts.map +1 -0
  19. package/dist/__tests__/app.test.js +112 -0
  20. package/dist/__tests__/app.test.js.map +1 -0
  21. package/dist/__tests__/audit.test.d.ts +2 -0
  22. package/dist/__tests__/audit.test.d.ts.map +1 -0
  23. package/dist/__tests__/audit.test.js +125 -0
  24. package/dist/__tests__/audit.test.js.map +1 -0
  25. package/dist/__tests__/auth-hydration.test.d.ts +2 -0
  26. package/dist/__tests__/auth-hydration.test.d.ts.map +1 -0
  27. package/dist/__tests__/auth-hydration.test.js +90 -0
  28. package/dist/__tests__/auth-hydration.test.js.map +1 -0
  29. package/dist/__tests__/auth-invite.test.d.ts +2 -0
  30. package/dist/__tests__/auth-invite.test.d.ts.map +1 -0
  31. package/dist/__tests__/auth-invite.test.js +152 -0
  32. package/dist/__tests__/auth-invite.test.js.map +1 -0
  33. package/dist/__tests__/auth-lockout.test.d.ts +2 -0
  34. package/dist/__tests__/auth-lockout.test.d.ts.map +1 -0
  35. package/dist/__tests__/auth-lockout.test.js +154 -0
  36. package/dist/__tests__/auth-lockout.test.js.map +1 -0
  37. package/dist/__tests__/auth-sessions.test.d.ts +2 -0
  38. package/dist/__tests__/auth-sessions.test.d.ts.map +1 -0
  39. package/dist/__tests__/auth-sessions.test.js +118 -0
  40. package/dist/__tests__/auth-sessions.test.js.map +1 -0
  41. package/dist/__tests__/config.test.d.ts +2 -0
  42. package/dist/__tests__/config.test.d.ts.map +1 -0
  43. package/dist/__tests__/config.test.js +402 -0
  44. package/dist/__tests__/config.test.js.map +1 -0
  45. package/dist/__tests__/define-field.test.d.ts +2 -0
  46. package/dist/__tests__/define-field.test.d.ts.map +1 -0
  47. package/dist/__tests__/define-field.test.js +121 -0
  48. package/dist/__tests__/define-field.test.js.map +1 -0
  49. package/dist/__tests__/deleteMany.test.d.ts +2 -0
  50. package/dist/__tests__/deleteMany.test.d.ts.map +1 -0
  51. package/dist/__tests__/deleteMany.test.js +75 -0
  52. package/dist/__tests__/deleteMany.test.js.map +1 -0
  53. package/dist/__tests__/depth.test.d.ts +2 -0
  54. package/dist/__tests__/depth.test.d.ts.map +1 -0
  55. package/dist/__tests__/depth.test.js +115 -0
  56. package/dist/__tests__/depth.test.js.map +1 -0
  57. package/dist/__tests__/dynamic-options.test.d.ts +2 -0
  58. package/dist/__tests__/dynamic-options.test.d.ts.map +1 -0
  59. package/dist/__tests__/dynamic-options.test.js +168 -0
  60. package/dist/__tests__/dynamic-options.test.js.map +1 -0
  61. package/dist/__tests__/email.service.test.d.ts +2 -0
  62. package/dist/__tests__/email.service.test.d.ts.map +1 -0
  63. package/dist/__tests__/email.service.test.js +52 -0
  64. package/dist/__tests__/email.service.test.js.map +1 -0
  65. package/dist/__tests__/field-inference.test-types.d.ts +161 -0
  66. package/dist/__tests__/field-inference.test-types.d.ts.map +1 -0
  67. package/dist/__tests__/field-inference.test-types.js +87 -0
  68. package/dist/__tests__/field-inference.test-types.js.map +1 -0
  69. package/dist/__tests__/fixtures/editorial-workflow.d.ts +14 -0
  70. package/dist/__tests__/fixtures/editorial-workflow.d.ts.map +1 -0
  71. package/dist/__tests__/fixtures/editorial-workflow.js +12 -0
  72. package/dist/__tests__/fixtures/editorial-workflow.js.map +1 -0
  73. package/dist/__tests__/hooks.test.d.ts +2 -0
  74. package/dist/__tests__/hooks.test.d.ts.map +1 -0
  75. package/dist/__tests__/hooks.test.js +320 -0
  76. package/dist/__tests__/hooks.test.js.map +1 -0
  77. package/dist/__tests__/mocks.d.ts +75 -0
  78. package/dist/__tests__/mocks.d.ts.map +1 -0
  79. package/dist/__tests__/mocks.js +170 -0
  80. package/dist/__tests__/mocks.js.map +1 -0
  81. package/dist/__tests__/observability.test.d.ts +2 -0
  82. package/dist/__tests__/observability.test.d.ts.map +1 -0
  83. package/dist/__tests__/observability.test.js +233 -0
  84. package/dist/__tests__/observability.test.js.map +1 -0
  85. package/dist/__tests__/openapi.test.d.ts +2 -0
  86. package/dist/__tests__/openapi.test.d.ts.map +1 -0
  87. package/dist/__tests__/openapi.test.js +105 -0
  88. package/dist/__tests__/openapi.test.js.map +1 -0
  89. package/dist/__tests__/parse-sort.test.d.ts +2 -0
  90. package/dist/__tests__/parse-sort.test.d.ts.map +1 -0
  91. package/dist/__tests__/parse-sort.test.js +24 -0
  92. package/dist/__tests__/parse-sort.test.js.map +1 -0
  93. package/dist/__tests__/preferences.test.d.ts +2 -0
  94. package/dist/__tests__/preferences.test.d.ts.map +1 -0
  95. package/dist/__tests__/preferences.test.js +168 -0
  96. package/dist/__tests__/preferences.test.js.map +1 -0
  97. package/dist/__tests__/router.test.d.ts +2 -0
  98. package/dist/__tests__/router.test.d.ts.map +1 -0
  99. package/dist/__tests__/router.test.js +70 -0
  100. package/dist/__tests__/router.test.js.map +1 -0
  101. package/dist/__tests__/search.test.d.ts +2 -0
  102. package/dist/__tests__/search.test.d.ts.map +1 -0
  103. package/dist/__tests__/search.test.js +174 -0
  104. package/dist/__tests__/search.test.js.map +1 -0
  105. package/dist/__tests__/upload-validation.test.d.ts +2 -0
  106. package/dist/__tests__/upload-validation.test.d.ts.map +1 -0
  107. package/dist/__tests__/upload-validation.test.js +41 -0
  108. package/dist/__tests__/upload-validation.test.js.map +1 -0
  109. package/dist/__tests__/where.test.d.ts +2 -0
  110. package/dist/__tests__/where.test.d.ts.map +1 -0
  111. package/dist/__tests__/where.test.js +105 -0
  112. package/dist/__tests__/where.test.js.map +1 -0
  113. package/dist/__tests__/workflow.test.d.ts +2 -0
  114. package/dist/__tests__/workflow.test.d.ts.map +1 -0
  115. package/dist/__tests__/workflow.test.js +684 -0
  116. package/dist/__tests__/workflow.test.js.map +1 -0
  117. package/dist/app-config-C9VJ6rTv.d.cts +2092 -0
  118. package/dist/app-config-C9VJ6rTv.d.ts +2092 -0
  119. package/dist/app-config-CYV70eoF.d.cts +2100 -0
  120. package/dist/app-config-CYV70eoF.d.ts +2100 -0
  121. package/dist/app.d.ts +35 -0
  122. package/dist/app.d.ts.map +1 -0
  123. package/dist/app.js +164 -0
  124. package/dist/app.js.map +1 -0
  125. package/dist/auth/access.d.ts +5 -0
  126. package/dist/auth/access.d.ts.map +1 -0
  127. package/dist/auth/access.js +59 -0
  128. package/dist/auth/access.js.map +1 -0
  129. package/dist/auth/jexl.d.ts +13 -0
  130. package/dist/auth/jexl.d.ts.map +1 -0
  131. package/dist/auth/jexl.js +30 -0
  132. package/dist/auth/jexl.js.map +1 -0
  133. package/dist/auth/lockout.d.ts +11 -0
  134. package/dist/auth/lockout.d.ts.map +1 -0
  135. package/dist/auth/lockout.js +37 -0
  136. package/dist/auth/lockout.js.map +1 -0
  137. package/dist/auth/password.d.ts +10 -0
  138. package/dist/auth/password.d.ts.map +1 -0
  139. package/dist/auth/password.js +28 -0
  140. package/dist/auth/password.js.map +1 -0
  141. package/dist/auth/sessions.d.ts +39 -0
  142. package/dist/auth/sessions.d.ts.map +1 -0
  143. package/dist/auth/sessions.js +126 -0
  144. package/dist/auth/sessions.js.map +1 -0
  145. package/dist/auth/token.d.ts +23 -0
  146. package/dist/auth/token.d.ts.map +1 -0
  147. package/dist/auth/token.js +40 -0
  148. package/dist/auth/token.js.map +1 -0
  149. package/dist/chunk-23NM4LDU.js +2883 -0
  150. package/dist/chunk-2TKDCCIR.js +2622 -0
  151. package/dist/chunk-3SFITPX4.js +2628 -0
  152. package/dist/chunk-CQVTQ67D.js +2632 -0
  153. package/dist/chunk-ELHRRPKX.js +2863 -0
  154. package/dist/chunk-HRLSFTLR.js +3061 -0
  155. package/dist/chunk-QTIAQBNH.js +3097 -0
  156. package/dist/chunk-T2DGC4MD.js +3008 -0
  157. package/dist/chunk-TLU77TLE.js +3203 -0
  158. package/dist/chunk-YEBCT77K.js +3269 -0
  159. package/dist/chunk-YXEKNN5X.js +3085 -0
  160. package/dist/controllers/admin-auth.controller.d.ts +71 -0
  161. package/dist/controllers/admin-auth.controller.d.ts.map +1 -0
  162. package/dist/controllers/admin-auth.controller.js +450 -0
  163. package/dist/controllers/admin-auth.controller.js.map +1 -0
  164. package/dist/controllers/audit.controller.d.ts +42 -0
  165. package/dist/controllers/audit.controller.d.ts.map +1 -0
  166. package/dist/controllers/audit.controller.js +149 -0
  167. package/dist/controllers/audit.controller.js.map +1 -0
  168. package/dist/controllers/auth.controller.d.ts +143 -0
  169. package/dist/controllers/auth.controller.d.ts.map +1 -0
  170. package/dist/controllers/auth.controller.js +572 -0
  171. package/dist/controllers/auth.controller.js.map +1 -0
  172. package/dist/controllers/collection.controller.d.ts +170 -0
  173. package/dist/controllers/collection.controller.d.ts.map +1 -0
  174. package/dist/controllers/collection.controller.js +1033 -0
  175. package/dist/controllers/collection.controller.js.map +1 -0
  176. package/dist/controllers/global.controller.d.ts +20 -0
  177. package/dist/controllers/global.controller.d.ts.map +1 -0
  178. package/dist/controllers/global.controller.js +182 -0
  179. package/dist/controllers/global.controller.js.map +1 -0
  180. package/dist/controllers/media.controller.d.ts +39 -0
  181. package/dist/controllers/media.controller.d.ts.map +1 -0
  182. package/dist/controllers/media.controller.js +178 -0
  183. package/dist/controllers/media.controller.js.map +1 -0
  184. package/dist/controllers/preview.controller.d.ts +37 -0
  185. package/dist/controllers/preview.controller.d.ts.map +1 -0
  186. package/dist/controllers/preview.controller.js +48 -0
  187. package/dist/controllers/preview.controller.js.map +1 -0
  188. package/dist/index.cjs +707 -27
  189. package/dist/index.d.cts +77 -5
  190. package/dist/index.d.ts +77 -5
  191. package/dist/index.d.ts.map +1 -0
  192. package/dist/index.js +37 -1
  193. package/dist/index.js.map +1 -0
  194. package/dist/middleware/auth.d.ts +19 -0
  195. package/dist/middleware/auth.d.ts.map +1 -0
  196. package/dist/middleware/auth.js +157 -0
  197. package/dist/middleware/auth.js.map +1 -0
  198. package/dist/middleware/rate-limit.d.ts +9 -0
  199. package/dist/middleware/rate-limit.d.ts.map +1 -0
  200. package/dist/middleware/rate-limit.js +75 -0
  201. package/dist/middleware/rate-limit.js.map +1 -0
  202. package/dist/network.d.ts +6 -0
  203. package/dist/network.d.ts.map +1 -0
  204. package/dist/network.js +70 -0
  205. package/dist/network.js.map +1 -0
  206. package/dist/observability.d.ts +113 -0
  207. package/dist/observability.d.ts.map +1 -0
  208. package/dist/observability.js +497 -0
  209. package/dist/observability.js.map +1 -0
  210. package/dist/router.d.ts +8 -0
  211. package/dist/router.d.ts.map +1 -0
  212. package/dist/router.js +704 -0
  213. package/dist/router.js.map +1 -0
  214. package/dist/server.cjs +861 -54
  215. package/dist/server.d.cts +9 -1
  216. package/dist/server.d.ts +9 -1
  217. package/dist/server.d.ts.map +1 -0
  218. package/dist/server.js +189 -26
  219. package/dist/server.js.map +1 -0
  220. package/dist/services/audit.service.d.ts +23 -0
  221. package/dist/services/audit.service.d.ts.map +1 -0
  222. package/dist/services/audit.service.js +39 -0
  223. package/dist/services/audit.service.js.map +1 -0
  224. package/dist/services/defaults.service.d.ts +8 -0
  225. package/dist/services/defaults.service.d.ts.map +1 -0
  226. package/dist/services/defaults.service.js +55 -0
  227. package/dist/services/defaults.service.js.map +1 -0
  228. package/dist/services/email-template.d.ts +46 -0
  229. package/dist/services/email-template.d.ts.map +1 -0
  230. package/dist/services/email-template.js +88 -0
  231. package/dist/services/email-template.js.map +1 -0
  232. package/dist/services/email.service.d.ts +34 -0
  233. package/dist/services/email.service.d.ts.map +1 -0
  234. package/dist/services/email.service.js +129 -0
  235. package/dist/services/email.service.js.map +1 -0
  236. package/dist/services/media.service.d.ts +20 -0
  237. package/dist/services/media.service.d.ts.map +1 -0
  238. package/dist/services/media.service.js +49 -0
  239. package/dist/services/media.service.js.map +1 -0
  240. package/dist/services/population.service.d.ts +20 -0
  241. package/dist/services/population.service.d.ts.map +1 -0
  242. package/dist/services/population.service.js +224 -0
  243. package/dist/services/population.service.js.map +1 -0
  244. package/dist/types/access.d.ts +48 -0
  245. package/dist/types/access.d.ts.map +1 -0
  246. package/dist/types/access.js +2 -0
  247. package/dist/types/access.js.map +1 -0
  248. package/dist/types/adapters.d.ts +152 -0
  249. package/dist/types/adapters.d.ts.map +1 -0
  250. package/dist/types/adapters.js +2 -0
  251. package/dist/types/adapters.js.map +1 -0
  252. package/dist/types/admin-auth.d.ts +132 -0
  253. package/dist/types/admin-auth.d.ts.map +1 -0
  254. package/dist/types/admin-auth.js +2 -0
  255. package/dist/types/admin-auth.js.map +1 -0
  256. package/dist/types/admin.d.ts +86 -0
  257. package/dist/types/admin.d.ts.map +1 -0
  258. package/dist/types/admin.js +2 -0
  259. package/dist/types/admin.js.map +1 -0
  260. package/dist/types/app-config.d.ts +279 -0
  261. package/dist/types/app-config.d.ts.map +1 -0
  262. package/dist/types/app-config.js +2 -0
  263. package/dist/types/app-config.js.map +1 -0
  264. package/dist/types/documents.d.ts +65 -0
  265. package/dist/types/documents.d.ts.map +1 -0
  266. package/dist/types/documents.js +2 -0
  267. package/dist/types/documents.js.map +1 -0
  268. package/dist/types/hooks.d.ts +122 -0
  269. package/dist/types/hooks.d.ts.map +1 -0
  270. package/dist/types/hooks.js +2 -0
  271. package/dist/types/hooks.js.map +1 -0
  272. package/dist/types/index.d.ts +13 -0
  273. package/dist/types/index.d.ts.map +1 -0
  274. package/dist/types/index.js +13 -0
  275. package/dist/types/index.js.map +1 -0
  276. package/dist/types/request.d.ts +42 -0
  277. package/dist/types/request.d.ts.map +1 -0
  278. package/dist/types/request.js +2 -0
  279. package/dist/types/request.js.map +1 -0
  280. package/dist/types/schema-config.d.ts +443 -0
  281. package/dist/types/schema-config.d.ts.map +1 -0
  282. package/dist/types/schema-config.js +2 -0
  283. package/dist/types/schema-config.js.map +1 -0
  284. package/dist/types/schema-core.d.ts +628 -0
  285. package/dist/types/schema-core.d.ts.map +1 -0
  286. package/dist/types/schema-core.js +2 -0
  287. package/dist/types/schema-core.js.map +1 -0
  288. package/dist/types/schema-inference.d.ts +73 -0
  289. package/dist/types/schema-inference.d.ts.map +1 -0
  290. package/dist/types/schema-inference.js +2 -0
  291. package/dist/types/schema-inference.js.map +1 -0
  292. package/dist/types/workflows.d.ts +121 -0
  293. package/dist/types/workflows.d.ts.map +1 -0
  294. package/dist/types/workflows.js +7 -0
  295. package/dist/types/workflows.js.map +1 -0
  296. package/dist/utils/access-control.d.ts +35 -0
  297. package/dist/utils/access-control.d.ts.map +1 -0
  298. package/dist/utils/access-control.js +161 -0
  299. package/dist/utils/access-control.js.map +1 -0
  300. package/dist/utils/admin-auth.d.ts +5 -0
  301. package/dist/utils/admin-auth.d.ts.map +1 -0
  302. package/dist/utils/admin-auth.js +39 -0
  303. package/dist/utils/admin-auth.js.map +1 -0
  304. package/dist/utils/block-references.d.ts +10 -0
  305. package/dist/utils/block-references.d.ts.map +1 -0
  306. package/dist/utils/block-references.js +100 -0
  307. package/dist/utils/block-references.js.map +1 -0
  308. package/dist/utils/collection-search.d.ts +11 -0
  309. package/dist/utils/collection-search.d.ts.map +1 -0
  310. package/dist/utils/collection-search.js +204 -0
  311. package/dist/utils/collection-search.js.map +1 -0
  312. package/dist/utils/config.d.ts +8 -0
  313. package/dist/utils/config.d.ts.map +1 -0
  314. package/dist/utils/config.js +336 -0
  315. package/dist/utils/config.js.map +1 -0
  316. package/dist/utils/hooks.d.ts +41 -0
  317. package/dist/utils/hooks.d.ts.map +1 -0
  318. package/dist/utils/hooks.js +177 -0
  319. package/dist/utils/hooks.js.map +1 -0
  320. package/dist/utils/openapi.d.ts +6 -0
  321. package/dist/utils/openapi.d.ts.map +1 -0
  322. package/dist/utils/openapi.js +981 -0
  323. package/dist/utils/openapi.js.map +1 -0
  324. package/dist/utils/parse-sort.d.ts +43 -0
  325. package/dist/utils/parse-sort.d.ts.map +1 -0
  326. package/dist/utils/parse-sort.js +70 -0
  327. package/dist/utils/parse-sort.js.map +1 -0
  328. package/dist/utils/parse-where.d.ts +69 -0
  329. package/dist/utils/parse-where.d.ts.map +1 -0
  330. package/dist/utils/parse-where.js +202 -0
  331. package/dist/utils/parse-where.js.map +1 -0
  332. package/dist/utils/readonly-db.d.ts +9 -0
  333. package/dist/utils/readonly-db.d.ts.map +1 -0
  334. package/dist/utils/readonly-db.js +21 -0
  335. package/dist/utils/readonly-db.js.map +1 -0
  336. package/dist/utils/swagger.d.ts +11 -0
  337. package/dist/utils/swagger.d.ts.map +1 -0
  338. package/dist/utils/swagger.js +62 -0
  339. package/dist/utils/swagger.js.map +1 -0
  340. package/dist/utils/upload-validation.d.ts +23 -0
  341. package/dist/utils/upload-validation.d.ts.map +1 -0
  342. package/dist/utils/upload-validation.js +46 -0
  343. package/dist/utils/upload-validation.js.map +1 -0
  344. package/dist/utils/where-sanitizer.d.ts +10 -0
  345. package/dist/utils/where-sanitizer.d.ts.map +1 -0
  346. package/dist/utils/where-sanitizer.js +67 -0
  347. package/dist/utils/where-sanitizer.js.map +1 -0
  348. package/dist/workflows.d.ts +86 -0
  349. package/dist/workflows.d.ts.map +1 -0
  350. package/dist/workflows.js +405 -0
  351. package/dist/workflows.js.map +1 -0
  352. package/package.json +1 -1
package/dist/server.cjs CHANGED
@@ -1064,21 +1064,629 @@ function createSessionId() {
1064
1064
  return globalThis.crypto?.randomUUID?.() ? globalThis.crypto.randomUUID() : `sess_${Date.now()}_${Math.random().toString(36).slice(2)}`;
1065
1065
  }
1066
1066
 
1067
+ // src/utils/hooks.ts
1068
+ var import_jexl3 = __toESM(require("jexl"), 1);
1069
+
1070
+ // src/utils/declarative-hooks.ts
1071
+ var import_jexl2 = __toESM(require("jexl"), 1);
1072
+ var SURFACE_ROOTS = {
1073
+ "collection.beforeRead": ["req", "user", "query"],
1074
+ "collection.afterRead": ["req", "user", "doc"],
1075
+ "collection.beforeChange": ["req", "user", "data", "doc", "operation"],
1076
+ "global.beforeRead": ["req", "user", "query"],
1077
+ "global.afterRead": ["req", "user", "doc"],
1078
+ "global.beforeChange": ["req", "user", "data", "doc", "operation"],
1079
+ "field.beforeChange": ["value", "data", "originalDoc", "user"],
1080
+ "field.admin.onChange": ["value", "siblingData", "data"]
1081
+ };
1082
+ var ACCESS_ROOTS = ["user", "req", "doc", "data", "id", "config"];
1083
+ var ADMIN_CONDITION_RESERVED_ROOTS = ["data", "siblingData", "user", "id"];
1084
+ var PREVIEW_URL_RESERVED_ROOTS = ["siteUrl", "id"];
1085
+ var COMPILE_CACHE = /* @__PURE__ */ new Map();
1086
+ function compileExpression(expression) {
1087
+ const cached = COMPILE_CACHE.get(expression);
1088
+ if (cached) return cached;
1089
+ const compiled = import_jexl2.default.compile(expression);
1090
+ const next = { ast: compiled._ast };
1091
+ COMPILE_CACHE.set(expression, next);
1092
+ return next;
1093
+ }
1094
+ function isPlainObject(value) {
1095
+ return !!value && typeof value === "object" && !Array.isArray(value);
1096
+ }
1097
+ function quoteSegment(value) {
1098
+ return `["${value.replace(/"/g, '\\"')}"]`;
1099
+ }
1100
+ function fieldSegment(field, index) {
1101
+ return typeof field.name === "string" && field.name.length > 0 ? `fields${quoteSegment(field.name)}` : `fields[${index}]`;
1102
+ }
1103
+ function blockSegment(block, index) {
1104
+ return typeof block.slug === "string" && block.slug.length > 0 ? `blocks${quoteSegment(block.slug)}` : `blocks[${index}]`;
1105
+ }
1106
+ function collectionSegment(collection, index) {
1107
+ return typeof collection.slug === "string" && collection.slug.length > 0 ? `collections${quoteSegment(collection.slug)}` : `collections[${index}]`;
1108
+ }
1109
+ function globalSegment(global, index) {
1110
+ return typeof global.slug === "string" && global.slug.length > 0 ? `globals${quoteSegment(global.slug)}` : `globals[${index}]`;
1111
+ }
1112
+ function colorize(value, code, enabled) {
1113
+ return enabled ? `\x1B[${code}m${value}\x1B[0m` : value;
1114
+ }
1115
+ function collectRootIdentifiers(node, roots, seen) {
1116
+ if (!node || typeof node !== "object" || seen.has(node)) return;
1117
+ seen.add(node);
1118
+ const candidate = node;
1119
+ if (candidate.type === "Identifier") {
1120
+ if (candidate.from) {
1121
+ collectRootIdentifiers(candidate.from, roots, seen);
1122
+ return;
1123
+ }
1124
+ if (typeof candidate.value === "string" && candidate.value !== "null") {
1125
+ roots.add(candidate.value);
1126
+ }
1127
+ return;
1128
+ }
1129
+ if (Array.isArray(node)) {
1130
+ for (const entry of node) collectRootIdentifiers(entry, roots, seen);
1131
+ return;
1132
+ }
1133
+ for (const value of Object.values(node)) {
1134
+ if (Array.isArray(value)) {
1135
+ for (const entry of value) collectRootIdentifiers(entry, roots, seen);
1136
+ continue;
1137
+ }
1138
+ if (value && typeof value === "object") {
1139
+ collectRootIdentifiers(value, roots, seen);
1140
+ }
1141
+ }
1142
+ }
1143
+ function getInvalidRoots(expression, allowedRoots) {
1144
+ const { ast } = compileExpression(expression);
1145
+ const roots = /* @__PURE__ */ new Set();
1146
+ collectRootIdentifiers(ast, roots, /* @__PURE__ */ new Set());
1147
+ const allowed = new Set(allowedRoots);
1148
+ return [...roots].filter((root) => !allowed.has(root));
1149
+ }
1150
+ function validateDeclarativeHookExpression(expression, surface, path) {
1151
+ try {
1152
+ const invalidRoots = getInvalidRoots(expression, SURFACE_ROOTS[surface]);
1153
+ if (invalidRoots.length === 0) return [];
1154
+ return [
1155
+ {
1156
+ path,
1157
+ surface,
1158
+ expression,
1159
+ message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${SURFACE_ROOTS[surface].map((root) => `"${root}"`).join(", ")}`
1160
+ }
1161
+ ];
1162
+ } catch (error) {
1163
+ return [
1164
+ {
1165
+ path,
1166
+ surface,
1167
+ expression,
1168
+ message: `has invalid syntax: ${error.message}`
1169
+ }
1170
+ ];
1171
+ }
1172
+ }
1173
+ function validateDeclarativeAccessExpression(expression, surface, path) {
1174
+ try {
1175
+ const invalidRoots = getInvalidRoots(expression, ACCESS_ROOTS);
1176
+ if (invalidRoots.length === 0) return [];
1177
+ return [
1178
+ {
1179
+ path,
1180
+ surface,
1181
+ expression,
1182
+ message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${ACCESS_ROOTS.map((root) => `"${root}"`).join(", ")}`
1183
+ }
1184
+ ];
1185
+ } catch (error) {
1186
+ return [
1187
+ {
1188
+ path,
1189
+ surface,
1190
+ expression,
1191
+ message: `has invalid syntax: ${error.message}`
1192
+ }
1193
+ ];
1194
+ }
1195
+ }
1196
+ function validateAdminConditionExpression(expression, path, allowedRoots) {
1197
+ try {
1198
+ const invalidRoots = getInvalidRoots(expression, allowedRoots);
1199
+ if (invalidRoots.length === 0) return [];
1200
+ return [
1201
+ {
1202
+ path,
1203
+ surface: "field.admin.condition",
1204
+ expression,
1205
+ message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${allowedRoots.map((root) => `"${root}"`).join(", ")}`
1206
+ }
1207
+ ];
1208
+ } catch (error) {
1209
+ return [
1210
+ {
1211
+ path,
1212
+ surface: "field.admin.condition",
1213
+ expression,
1214
+ message: `has invalid syntax: ${error.message}`
1215
+ }
1216
+ ];
1217
+ }
1218
+ }
1219
+ function validatePreviewUrlExpression(expression, path, allowedRoots) {
1220
+ try {
1221
+ const invalidRoots = getInvalidRoots(expression, allowedRoots);
1222
+ if (invalidRoots.length === 0) return [];
1223
+ return [
1224
+ {
1225
+ path,
1226
+ surface: "collection.admin.previewUrl",
1227
+ expression,
1228
+ message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${allowedRoots.map((root) => `"${root}"`).join(", ")}`
1229
+ }
1230
+ ];
1231
+ } catch (error) {
1232
+ return [
1233
+ {
1234
+ path,
1235
+ surface: "collection.admin.previewUrl",
1236
+ expression,
1237
+ message: `has invalid syntax: ${error.message}`
1238
+ }
1239
+ ];
1240
+ }
1241
+ }
1242
+ function assertValidDeclarativeHookExpression(expression, surface, path) {
1243
+ const issues = validateDeclarativeHookExpression(expression, surface, path);
1244
+ if (issues.length === 0) return;
1245
+ throw new Error(`Invalid declarative hook at ${path}: ${issues[0].message}`);
1246
+ }
1247
+ function assertValidDeclarativeHookResult(result, surface, path) {
1248
+ if (isPlainObject(result)) return;
1249
+ throw new Error(`Invalid declarative hook result at ${path}: ${surface} expressions must return an object`);
1250
+ }
1251
+ function collectFieldIssues(fields, path, issues) {
1252
+ fields.forEach((field, index) => {
1253
+ const fieldPath = `${path}.${fieldSegment(field, index)}`;
1254
+ field.hooks?.beforeChange?.forEach((hook, hookIndex) => {
1255
+ if (typeof hook !== "string") return;
1256
+ issues.push(
1257
+ ...validateDeclarativeHookExpression(
1258
+ hook,
1259
+ "field.beforeChange",
1260
+ `${fieldPath}.hooks.beforeChange[${hookIndex}]`
1261
+ )
1262
+ );
1263
+ });
1264
+ field.hooks?.afterRead?.forEach((hook, hookIndex) => {
1265
+ if (typeof hook !== "string") return;
1266
+ issues.push({
1267
+ path: `${fieldPath}.hooks.afterRead[${hookIndex}]`,
1268
+ surface: "unsupported",
1269
+ expression: hook,
1270
+ message: "declarative strings are not supported on field hooks.afterRead"
1271
+ });
1272
+ });
1273
+ if (typeof field.admin?.hooks?.onChange === "string") {
1274
+ issues.push(
1275
+ ...validateDeclarativeHookExpression(
1276
+ field.admin.hooks.onChange,
1277
+ "field.admin.onChange",
1278
+ `${fieldPath}.admin.hooks.onChange`
1279
+ )
1280
+ );
1281
+ }
1282
+ if (typeof field.admin?.hooks?.options === "string") {
1283
+ issues.push({
1284
+ path: `${fieldPath}.admin.hooks.options`,
1285
+ surface: "unsupported",
1286
+ expression: field.admin.hooks.options,
1287
+ message: "declarative strings are not supported on field admin.hooks.options"
1288
+ });
1289
+ }
1290
+ if (Array.isArray(field.fields)) {
1291
+ collectFieldIssues(field.fields, fieldPath, issues);
1292
+ }
1293
+ if (Array.isArray(field.blocks)) {
1294
+ field.blocks.forEach((block, blockIndex) => {
1295
+ if (!Array.isArray(block.fields)) return;
1296
+ collectFieldIssues(block.fields, `${fieldPath}.${blockSegment(block, blockIndex)}`, issues);
1297
+ });
1298
+ }
1299
+ });
1300
+ }
1301
+ function collectFieldNames(fields) {
1302
+ return fields.flatMap(
1303
+ (field) => typeof field.name === "string" && field.name.length > 0 ? [field.name] : []
1304
+ );
1305
+ }
1306
+ function collectAdminConditionIssues(fields, path, rootFieldNames, issues) {
1307
+ const scopeFieldNames = collectFieldNames(fields);
1308
+ const allowedRoots = [.../* @__PURE__ */ new Set([...ADMIN_CONDITION_RESERVED_ROOTS, ...rootFieldNames, ...scopeFieldNames])];
1309
+ fields.forEach((field, index) => {
1310
+ const fieldPath = `${path}.${fieldSegment(field, index)}`;
1311
+ if (typeof field.admin?.condition === "string") {
1312
+ issues.push(
1313
+ ...validateAdminConditionExpression(
1314
+ field.admin.condition,
1315
+ `${fieldPath}.admin.condition`,
1316
+ allowedRoots
1317
+ )
1318
+ );
1319
+ }
1320
+ if (Array.isArray(field.fields)) {
1321
+ collectAdminConditionIssues(field.fields, fieldPath, rootFieldNames, issues);
1322
+ }
1323
+ if (Array.isArray(field.blocks)) {
1324
+ field.blocks.forEach((block, blockIndex) => {
1325
+ if (!Array.isArray(block.fields)) return;
1326
+ collectAdminConditionIssues(
1327
+ block.fields,
1328
+ `${fieldPath}.${blockSegment(block, blockIndex)}`,
1329
+ rootFieldNames,
1330
+ issues
1331
+ );
1332
+ });
1333
+ }
1334
+ });
1335
+ }
1336
+ function collectFieldAccessIssues(fields, path, issues) {
1337
+ fields.forEach((field, index) => {
1338
+ const fieldPath = `${path}.${fieldSegment(field, index)}`;
1339
+ if (typeof field.access?.read === "string") {
1340
+ issues.push(...validateDeclarativeAccessExpression(field.access.read, "field.access", `${fieldPath}.access.read`));
1341
+ }
1342
+ if (typeof field.access?.create === "string") {
1343
+ issues.push(
1344
+ ...validateDeclarativeAccessExpression(field.access.create, "field.access", `${fieldPath}.access.create`)
1345
+ );
1346
+ }
1347
+ if (typeof field.access?.update === "string") {
1348
+ issues.push(
1349
+ ...validateDeclarativeAccessExpression(field.access.update, "field.access", `${fieldPath}.access.update`)
1350
+ );
1351
+ }
1352
+ if (Array.isArray(field.fields)) {
1353
+ collectFieldAccessIssues(field.fields, fieldPath, issues);
1354
+ }
1355
+ if (Array.isArray(field.blocks)) {
1356
+ field.blocks.forEach((block, blockIndex) => {
1357
+ if (!Array.isArray(block.fields)) return;
1358
+ collectFieldAccessIssues(
1359
+ block.fields,
1360
+ `${fieldPath}.${blockSegment(block, blockIndex)}`,
1361
+ issues
1362
+ );
1363
+ });
1364
+ }
1365
+ });
1366
+ }
1367
+ function collectHookIssues(hooks, scope, path, issues) {
1368
+ if (!hooks) return;
1369
+ const supported = {
1370
+ beforeRead: `${scope}.beforeRead`,
1371
+ afterRead: `${scope}.afterRead`,
1372
+ beforeChange: `${scope}.beforeChange`
1373
+ };
1374
+ for (const [key, value] of Object.entries(hooks)) {
1375
+ if (!Array.isArray(value)) continue;
1376
+ value.forEach((hook, index) => {
1377
+ if (typeof hook !== "string") return;
1378
+ const surface = supported[key];
1379
+ if (!surface) {
1380
+ issues.push({
1381
+ path: `${path}.${key}[${index}]`,
1382
+ surface: "unsupported",
1383
+ expression: hook,
1384
+ message: `declarative strings are not supported on ${scope} hooks.${key}`
1385
+ });
1386
+ return;
1387
+ }
1388
+ issues.push(...validateDeclarativeHookExpression(hook, surface, `${path}.${key}[${index}]`));
1389
+ });
1390
+ }
1391
+ }
1392
+ function collectDeclarativeHookValidationIssues(config) {
1393
+ const issues = [];
1394
+ config.blocks?.forEach((block, blockIndex) => {
1395
+ if (!Array.isArray(block.fields)) return;
1396
+ collectFieldIssues(
1397
+ block.fields,
1398
+ blockSegment(block, blockIndex),
1399
+ issues
1400
+ );
1401
+ });
1402
+ config.collections.forEach((collection, collectionIndex) => {
1403
+ const collectionPath = collectionSegment(collection, collectionIndex);
1404
+ collectHookIssues(
1405
+ collection.hooks,
1406
+ "collection",
1407
+ `${collectionPath}.hooks`,
1408
+ issues
1409
+ );
1410
+ collectFieldIssues(collection.fields, collectionPath, issues);
1411
+ });
1412
+ config.globals?.forEach((global, globalIndex) => {
1413
+ const globalPath = globalSegment(global, globalIndex);
1414
+ collectHookIssues(
1415
+ global.hooks,
1416
+ "global",
1417
+ `${globalPath}.hooks`,
1418
+ issues
1419
+ );
1420
+ collectFieldIssues(global.fields, globalPath, issues);
1421
+ });
1422
+ return issues;
1423
+ }
1424
+ function collectDeclarativeAccessValidationIssues(config) {
1425
+ const issues = [];
1426
+ Object.entries(config.accessPolicies ?? {}).forEach(([name, value]) => {
1427
+ if (typeof value !== "string") return;
1428
+ issues.push(
1429
+ ...validateDeclarativeAccessExpression(value, "config.accessPolicy", `accessPolicies.${name}`)
1430
+ );
1431
+ });
1432
+ config.blocks?.forEach((block, blockIndex) => {
1433
+ if (!Array.isArray(block.fields)) return;
1434
+ collectFieldAccessIssues(
1435
+ block.fields,
1436
+ blockSegment(block, blockIndex),
1437
+ issues
1438
+ );
1439
+ });
1440
+ config.collections.forEach((collection, collectionIndex) => {
1441
+ const collectionPath = collectionSegment(collection, collectionIndex);
1442
+ if (typeof collection.access?.read === "string") {
1443
+ issues.push(
1444
+ ...validateDeclarativeAccessExpression(
1445
+ collection.access.read,
1446
+ "collection.access",
1447
+ `${collectionPath}.access.read`
1448
+ )
1449
+ );
1450
+ }
1451
+ if (typeof collection.access?.create === "string") {
1452
+ issues.push(
1453
+ ...validateDeclarativeAccessExpression(
1454
+ collection.access.create,
1455
+ "collection.access",
1456
+ `${collectionPath}.access.create`
1457
+ )
1458
+ );
1459
+ }
1460
+ if (typeof collection.access?.update === "string") {
1461
+ issues.push(
1462
+ ...validateDeclarativeAccessExpression(
1463
+ collection.access.update,
1464
+ "collection.access",
1465
+ `${collectionPath}.access.update`
1466
+ )
1467
+ );
1468
+ }
1469
+ if (typeof collection.access?.delete === "string") {
1470
+ issues.push(
1471
+ ...validateDeclarativeAccessExpression(
1472
+ collection.access.delete,
1473
+ "collection.access",
1474
+ `${collectionPath}.access.delete`
1475
+ )
1476
+ );
1477
+ }
1478
+ if (typeof collection.access?.readAudit === "string") {
1479
+ issues.push(
1480
+ ...validateDeclarativeAccessExpression(
1481
+ collection.access.readAudit,
1482
+ "collection.access",
1483
+ `${collectionPath}.access.readAudit`
1484
+ )
1485
+ );
1486
+ }
1487
+ collectFieldAccessIssues(collection.fields, collectionPath, issues);
1488
+ });
1489
+ config.globals?.forEach((global, globalIndex) => {
1490
+ const globalPath = globalSegment(global, globalIndex);
1491
+ if (typeof global.access?.read === "string") {
1492
+ issues.push(
1493
+ ...validateDeclarativeAccessExpression(global.access.read, "global.access", `${globalPath}.access.read`)
1494
+ );
1495
+ }
1496
+ if (typeof global.access?.update === "string") {
1497
+ issues.push(
1498
+ ...validateDeclarativeAccessExpression(
1499
+ global.access.update,
1500
+ "global.access",
1501
+ `${globalPath}.access.update`
1502
+ )
1503
+ );
1504
+ }
1505
+ collectFieldAccessIssues(global.fields, globalPath, issues);
1506
+ });
1507
+ return issues;
1508
+ }
1509
+ function collectAdminConditionValidationIssues(config) {
1510
+ const issues = [];
1511
+ config.blocks?.forEach((block, blockIndex) => {
1512
+ if (!Array.isArray(block.fields)) return;
1513
+ collectAdminConditionIssues(
1514
+ block.fields,
1515
+ blockSegment(block, blockIndex),
1516
+ collectFieldNames(block.fields),
1517
+ issues
1518
+ );
1519
+ });
1520
+ config.collections.forEach((collection, collectionIndex) => {
1521
+ const collectionPath = collectionSegment(collection, collectionIndex);
1522
+ const rootFieldNames = collectFieldNames(collection.fields);
1523
+ collectAdminConditionIssues(collection.fields, collectionPath, rootFieldNames, issues);
1524
+ });
1525
+ config.globals?.forEach((global, globalIndex) => {
1526
+ const globalPath = globalSegment(global, globalIndex);
1527
+ const rootFieldNames = collectFieldNames(global.fields);
1528
+ collectAdminConditionIssues(global.fields, globalPath, rootFieldNames, issues);
1529
+ });
1530
+ return issues;
1531
+ }
1532
+ function collectPreviewUrlValidationIssues(config) {
1533
+ const issues = [];
1534
+ config.collections.forEach((collection, collectionIndex) => {
1535
+ if (typeof collection.admin?.previewUrl !== "string") return;
1536
+ const collectionPath = collectionSegment(collection, collectionIndex);
1537
+ const allowedRoots = [
1538
+ .../* @__PURE__ */ new Set([...PREVIEW_URL_RESERVED_ROOTS, ...collectFieldNames(collection.fields)])
1539
+ ];
1540
+ issues.push(
1541
+ ...validatePreviewUrlExpression(
1542
+ collection.admin.previewUrl,
1543
+ `${collectionPath}.admin.previewUrl`,
1544
+ allowedRoots
1545
+ )
1546
+ );
1547
+ });
1548
+ return issues;
1549
+ }
1550
+ function assertValidAdminConditionsInConfig(config, source = "config") {
1551
+ const diagnostics = collectAdminConditionValidationIssues(config).map((issue) => ({
1552
+ severity: "error",
1553
+ source: "adminCondition",
1554
+ path: issue.path,
1555
+ surface: issue.surface,
1556
+ message: issue.message,
1557
+ expression: issue.expression
1558
+ }));
1559
+ if (diagnostics.length === 0) return;
1560
+ throw new ConfigValidationError(source, diagnostics);
1561
+ }
1562
+ function assertValidPreviewUrlsInConfig(config, source = "config") {
1563
+ const diagnostics = collectPreviewUrlValidationIssues(config).map((issue) => ({
1564
+ severity: "error",
1565
+ source: "previewUrl",
1566
+ path: issue.path,
1567
+ surface: issue.surface,
1568
+ message: issue.message,
1569
+ expression: issue.expression
1570
+ }));
1571
+ if (diagnostics.length === 0) return;
1572
+ throw new ConfigValidationError(source, diagnostics);
1573
+ }
1574
+ function collectConfigDiagnostics(config) {
1575
+ const hookIssues = collectDeclarativeHookValidationIssues(config).map((issue) => ({
1576
+ severity: "error",
1577
+ source: "declarativeHook",
1578
+ path: issue.path,
1579
+ surface: issue.surface,
1580
+ message: issue.message,
1581
+ expression: issue.expression
1582
+ }));
1583
+ const accessIssues = collectDeclarativeAccessValidationIssues(config).map((issue) => ({
1584
+ severity: "error",
1585
+ source: "declarativeAccess",
1586
+ path: issue.path,
1587
+ surface: issue.surface,
1588
+ message: issue.message,
1589
+ expression: issue.expression
1590
+ }));
1591
+ const adminConditionIssues = collectAdminConditionValidationIssues(config).map((issue) => ({
1592
+ severity: "error",
1593
+ source: "adminCondition",
1594
+ path: issue.path,
1595
+ surface: issue.surface,
1596
+ message: issue.message,
1597
+ expression: issue.expression
1598
+ }));
1599
+ const previewUrlIssues = collectPreviewUrlValidationIssues(config).map((issue) => ({
1600
+ severity: "error",
1601
+ source: "previewUrl",
1602
+ path: issue.path,
1603
+ surface: issue.surface,
1604
+ message: issue.message,
1605
+ expression: issue.expression
1606
+ }));
1607
+ return [...hookIssues, ...accessIssues, ...adminConditionIssues, ...previewUrlIssues];
1608
+ }
1609
+ function formatConfigDiagnostics(source, diagnostics, options = {}) {
1610
+ const color = options.color ?? false;
1611
+ const header = `${colorize("Config validation failed", 31, color)} in ${colorize(source, 36, color)}`;
1612
+ const lines = diagnostics.flatMap((issue, index) => [
1613
+ `${colorize(`${index + 1}.`, 31, color)} ${colorize(issue.path, 1, color)}`,
1614
+ ` ${issue.message}`,
1615
+ ` ${colorize("surface:", 90, color)} ${issue.surface}`
1616
+ ]);
1617
+ return `${header}
1618
+ ${lines.join("\n")}`;
1619
+ }
1620
+ var ConfigValidationError = class extends Error {
1621
+ diagnostics;
1622
+ source;
1623
+ constructor(source, diagnostics) {
1624
+ super(formatConfigDiagnostics(source, diagnostics));
1625
+ this.name = "ConfigValidationError";
1626
+ this.source = source;
1627
+ this.diagnostics = diagnostics;
1628
+ }
1629
+ };
1630
+ function assertValidDeclarativeHooksInConfig(config, source = "config") {
1631
+ const diagnostics = collectDeclarativeHookValidationIssues(config).map((issue) => ({
1632
+ severity: "error",
1633
+ source: "declarativeHook",
1634
+ path: issue.path,
1635
+ surface: issue.surface,
1636
+ message: issue.message,
1637
+ expression: issue.expression
1638
+ }));
1639
+ if (diagnostics.length === 0) return;
1640
+ throw new ConfigValidationError(source, diagnostics);
1641
+ }
1642
+ function assertValidDeclarativeAccessInConfig(config, source = "config") {
1643
+ const diagnostics = collectDeclarativeAccessValidationIssues(config).map((issue) => ({
1644
+ severity: "error",
1645
+ source: "declarativeAccess",
1646
+ path: issue.path,
1647
+ surface: issue.surface,
1648
+ message: issue.message,
1649
+ expression: issue.expression
1650
+ }));
1651
+ if (diagnostics.length === 0) return;
1652
+ throw new ConfigValidationError(source, diagnostics);
1653
+ }
1654
+
1067
1655
  // src/utils/hooks.ts
1068
1656
  async function runCollectionHooks(hooks, args, options = {}) {
1069
1657
  if (!hooks || hooks.length === 0) {
1070
- return args.data ?? args.doc ?? void 0;
1658
+ return args.data ?? args.doc ?? args.query ?? void 0;
1071
1659
  }
1072
- let currentPayload = args.data ?? args.doc ?? void 0;
1660
+ const payloadKey = args.data !== void 0 ? "data" : args.doc !== void 0 ? "doc" : "query" in args ? "query" : void 0;
1661
+ let currentPayload = payloadKey === void 0 ? void 0 : args[payloadKey];
1073
1662
  for (const hook of hooks) {
1074
1663
  try {
1075
- const result = await hook({
1664
+ const hookArgs = {
1076
1665
  ...args,
1077
- data: args.data !== void 0 ? currentPayload : void 0,
1078
- doc: args.doc !== void 0 ? currentPayload : void 0
1079
- });
1666
+ ...payloadKey !== void 0 ? { [payloadKey]: currentPayload } : {}
1667
+ };
1668
+ const result = typeof hook === "string" ? await (() => {
1669
+ if (options.surface) {
1670
+ assertValidDeclarativeHookExpression(
1671
+ hook,
1672
+ options.surface,
1673
+ options.path ?? options.surface
1674
+ );
1675
+ }
1676
+ return import_jexl3.default.eval(hook, hookArgs);
1677
+ })() : await hook(hookArgs);
1080
1678
  if (result !== void 0) {
1081
- currentPayload = result;
1679
+ if (typeof hook === "string" && options.surface) {
1680
+ assertValidDeclarativeHookResult(
1681
+ result,
1682
+ options.surface,
1683
+ options.path ?? options.surface
1684
+ );
1685
+ }
1686
+ currentPayload = typeof hook === "string" && currentPayload && typeof currentPayload === "object" && !Array.isArray(currentPayload) && result && typeof result === "object" && !Array.isArray(result) ? {
1687
+ ...currentPayload,
1688
+ ...result
1689
+ } : result;
1082
1690
  }
1083
1691
  } catch (err) {
1084
1692
  if (options.isolated) {
@@ -1093,17 +1701,17 @@ async function runCollectionHooks(hooks, args, options = {}) {
1093
1701
  }
1094
1702
  return currentPayload;
1095
1703
  }
1096
- async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
1704
+ async function executeFieldBeforeChange(fields, data, originalDoc, user, db, pathPrefix = "") {
1097
1705
  if (!data || typeof data !== "object") return data;
1098
1706
  const result = { ...data };
1099
1707
  for (const field of fields) {
1100
1708
  if (!field.name) continue;
1709
+ const fieldPath = pathPrefix ? `${pathPrefix}.${field.name}` : field.name;
1101
1710
  const value = result[field.name];
1102
1711
  const origValue = originalDoc?.[field.name];
1103
1712
  let updatedValue = value;
1104
1713
  if (field.hooks?.beforeChange) {
1105
1714
  for (const hook of field.hooks.beforeChange) {
1106
- const typedHook = hook;
1107
1715
  const hookArgs = {
1108
1716
  value: updatedValue,
1109
1717
  originalDoc: originalDoc ?? void 0,
@@ -1111,7 +1719,14 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
1111
1719
  user,
1112
1720
  db
1113
1721
  };
1114
- updatedValue = await typedHook(hookArgs);
1722
+ updatedValue = typeof hook === "string" ? await (() => {
1723
+ assertValidDeclarativeHookExpression(
1724
+ hook,
1725
+ "field.beforeChange",
1726
+ fieldPath
1727
+ );
1728
+ return import_jexl3.default.eval(hook, hookArgs);
1729
+ })() : await hook(hookArgs);
1115
1730
  }
1116
1731
  result[field.name] = updatedValue;
1117
1732
  }
@@ -1122,7 +1737,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
1122
1737
  updatedValue,
1123
1738
  origValue,
1124
1739
  user,
1125
- db
1740
+ db,
1741
+ fieldPath
1126
1742
  );
1127
1743
  } else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
1128
1744
  const arrayResult = [];
@@ -1135,7 +1751,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
1135
1751
  item,
1136
1752
  origItem,
1137
1753
  user,
1138
- db
1754
+ db,
1755
+ fieldPath
1139
1756
  )
1140
1757
  );
1141
1758
  }
@@ -1155,7 +1772,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
1155
1772
  blockData,
1156
1773
  origBlock,
1157
1774
  user,
1158
- db
1775
+ db,
1776
+ fieldPath
1159
1777
  )
1160
1778
  );
1161
1779
  } else {
@@ -1285,12 +1903,12 @@ function validateUpload(file, config) {
1285
1903
  }
1286
1904
 
1287
1905
  // src/auth/jexl.ts
1288
- var import_jexl2 = __toESM(require("jexl"), 1);
1906
+ var import_jexl4 = __toESM(require("jexl"), 1);
1289
1907
  async function evaluateAccess(expression, context) {
1290
1908
  if (expression === void 0 || expression === null) return false;
1291
1909
  if (typeof expression === "boolean") return expression;
1292
1910
  try {
1293
- const result = await import_jexl2.default.eval(expression, context);
1911
+ const result = await import_jexl4.default.eval(expression, context);
1294
1912
  if (result && typeof result === "object" && !Array.isArray(result)) {
1295
1913
  return result;
1296
1914
  }
@@ -2173,6 +2791,10 @@ var CollectionController = class {
2173
2791
  query: where,
2174
2792
  user,
2175
2793
  db: readonlyDb
2794
+ },
2795
+ {
2796
+ surface: "collection.beforeRead",
2797
+ path: `collection:${this.collection.slug}.hooks.beforeRead`
2176
2798
  }
2177
2799
  );
2178
2800
  if (beforeReadResult !== void 0) {
@@ -2455,6 +3077,9 @@ var CollectionController = class {
2455
3077
  user,
2456
3078
  operation: "create",
2457
3079
  db: readonlyDb
3080
+ }, {
3081
+ surface: "collection.beforeChange",
3082
+ path: `collection:${this.collection.slug}.hooks.beforeChange`
2458
3083
  });
2459
3084
  const doc = this.collection.workflow ? (await createWorkflowDocument({
2460
3085
  config,
@@ -2489,6 +3114,9 @@ var CollectionController = class {
2489
3114
  req: c.req,
2490
3115
  user,
2491
3116
  db: readonlyDb
3117
+ }, {
3118
+ surface: "collection.afterRead",
3119
+ path: `collection:${this.collection.slug}.hooks.afterRead`
2492
3120
  });
2493
3121
  const finalDoc = await executeFieldAfterRead(
2494
3122
  this.collection.fields,
@@ -2586,6 +3214,9 @@ var CollectionController = class {
2586
3214
  user,
2587
3215
  operation: "create",
2588
3216
  db: readonlyDb
3217
+ }, {
3218
+ surface: "collection.beforeChange",
3219
+ path: `collection:${this.collection.slug}.hooks.beforeChange`
2589
3220
  });
2590
3221
  const doc = await db.create({
2591
3222
  collection: this.collection.slug,
@@ -2608,6 +3239,9 @@ var CollectionController = class {
2608
3239
  req: c.req,
2609
3240
  user,
2610
3241
  db: readonlyDb
3242
+ }, {
3243
+ surface: "collection.afterRead",
3244
+ path: `collection:${this.collection.slug}.hooks.afterRead`
2611
3245
  });
2612
3246
  const finalDoc = await executeFieldAfterRead(
2613
3247
  this.collection.fields,
@@ -2716,6 +3350,9 @@ var CollectionController = class {
2716
3350
  user,
2717
3351
  operation: "update",
2718
3352
  db: readonlyDb
3353
+ }, {
3354
+ surface: "collection.beforeChange",
3355
+ path: `collection:${this.collection.slug}.hooks.beforeChange`
2719
3356
  });
2720
3357
  const doc = this.collection.workflow ? (await saveWorkflowDraft({
2721
3358
  config,
@@ -2753,6 +3390,9 @@ var CollectionController = class {
2753
3390
  req: c.req,
2754
3391
  user,
2755
3392
  db: readonlyDb
3393
+ }, {
3394
+ surface: "collection.afterRead",
3395
+ path: `collection:${this.collection.slug}.hooks.afterRead`
2756
3396
  });
2757
3397
  const finalDoc = await executeFieldAfterRead(
2758
3398
  this.collection.fields,
@@ -3147,6 +3787,9 @@ var GlobalController = class {
3147
3787
  query: void 0,
3148
3788
  user,
3149
3789
  db: readonlyDb
3790
+ }, {
3791
+ surface: "global.beforeRead",
3792
+ path: `global:${this.global.slug}.hooks.beforeRead`
3150
3793
  });
3151
3794
  let data = await db.getGlobal({ slug: this.global.slug });
3152
3795
  const isEmpty = !data || isFunctionallyEmpty(data);
@@ -3184,6 +3827,10 @@ var GlobalController = class {
3184
3827
  req: c.req,
3185
3828
  user,
3186
3829
  db: readonlyDb
3830
+ },
3831
+ {
3832
+ surface: "global.afterRead",
3833
+ path: `global:${this.global.slug}.hooks.afterRead`
3187
3834
  }
3188
3835
  );
3189
3836
  const docWithFieldHooks = await executeFieldAfterRead(
@@ -3266,6 +3913,9 @@ var GlobalController = class {
3266
3913
  user,
3267
3914
  operation: "update",
3268
3915
  db: readonlyDb
3916
+ }, {
3917
+ surface: "global.beforeChange",
3918
+ path: `global:${this.global.slug}.hooks.beforeChange`
3269
3919
  });
3270
3920
  const updated = await db.updateGlobal({ slug: this.global.slug, data });
3271
3921
  await runCollectionHooks(
@@ -3285,6 +3935,9 @@ var GlobalController = class {
3285
3935
  req: c.req,
3286
3936
  user,
3287
3937
  db: readonlyDb
3938
+ }, {
3939
+ surface: "global.afterRead",
3940
+ path: `global:${this.global.slug}.hooks.afterRead`
3288
3941
  });
3289
3942
  const finalDoc = await executeFieldAfterRead(
3290
3943
  this.global.fields,
@@ -3438,6 +4091,12 @@ function mergeDynamicConfig(baseConfig, dynamic) {
3438
4091
  return {
3439
4092
  ...baseConfig,
3440
4093
  ...normalizedDynamic.blocks ? { blocks: normalizedDynamic.blocks } : {},
4094
+ ...dynamic.accessPolicies ? {
4095
+ accessPolicies: {
4096
+ ...baseConfig.accessPolicies ?? {},
4097
+ ...dynamic.accessPolicies
4098
+ }
4099
+ } : {},
3441
4100
  ...dynamic.admin ? { admin: { ...baseConfig.admin, ...dynamic.admin } } : {},
3442
4101
  ...dynamic.adminAuth ? { adminAuth: { ...baseConfig.adminAuth, ...dynamic.adminAuth } } : {},
3443
4102
  collections: normalizedDynamic.collections ? [...baseConfig.collections, ...normalizedDynamic.collections] : baseConfig.collections,
@@ -3611,6 +4270,9 @@ var MediaController = class {
3611
4270
  }
3612
4271
  };
3613
4272
 
4273
+ // src/controllers/auth.controller.ts
4274
+ var import_node_crypto2 = require("crypto");
4275
+
3614
4276
  // src/auth/lockout.ts
3615
4277
  var DEFAULT_MAX_LOGIN_ATTEMPTS = 5;
3616
4278
  var DEFAULT_LOCK_TIME_MS = 10 * 60 * 1e3;
@@ -3655,6 +4317,7 @@ var emailTokens = {
3655
4317
  border: "#dde0d7",
3656
4318
  accent: "#b6ff2e",
3657
4319
  code: "#f1f3ec",
4320
+ codeBorder: "#e2e5dc",
3658
4321
  dangerSurface: "#fff2f0",
3659
4322
  dangerBorder: "#ffc9c2",
3660
4323
  dangerText: "#9f251b"
@@ -3704,7 +4367,13 @@ function table(content, style = "width:100%") {
3704
4367
  }
3705
4368
  function detailBox(content, monospace = false) {
3706
4369
  const font = monospace ? emailTokens.mono : emailTokens.font;
3707
- return table(row(escapeHtml(content), `padding:14px 16px;font-family:${font};font-size:13px;line-height:1.5;font-weight:${monospace ? "400" : "700"};color:${emailTokens.colors.text};word-break:break-all`), `width:100%;background:${emailTokens.colors.code};border-radius:${emailTokens.radius.control}`);
4370
+ return table(
4371
+ row(
4372
+ escapeHtml(content),
4373
+ `padding:14px 16px;font-family:${font};font-size:13px;line-height:1.5;font-weight:${monospace ? "400" : "700"};color:${emailTokens.colors.text};word-break:break-all`
4374
+ ),
4375
+ `width:100%;background:${emailTokens.colors.code};border:1px solid ${emailTokens.colors.codeBorder};border-radius:${emailTokens.radius.control}`
4376
+ );
3708
4377
  }
3709
4378
  function ctaButton(label, href) {
3710
4379
  const safeHref = safeHttpUrl(href);
@@ -3714,22 +4383,46 @@ function ctaButton(label, href) {
3714
4383
  "padding:8px 0 24px"
3715
4384
  ), "width:auto");
3716
4385
  }
4386
+ function safeLinkDetailBox(href) {
4387
+ const safeHref = safeHttpUrl(href);
4388
+ if (!safeHref) return "";
4389
+ return detailBox(safeHref, true);
4390
+ }
3717
4391
  function alertBox(content) {
3718
4392
  return table(row(escapeHtml(content), `padding:13px 16px;font-family:${emailTokens.font};font-size:13px;line-height:1.5;color:${emailTokens.colors.dangerText}`), `width:100%;background:${emailTokens.colors.dangerSurface};border:1px solid ${emailTokens.colors.dangerBorder};border-radius:${emailTokens.radius.control}`);
3719
4393
  }
3720
4394
  function layout({ preheader, title, content, footer }) {
3721
4395
  return `<!doctype html>
3722
- <html lang="en">
3723
- <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"></head>
3724
- <body style="margin:0;padding:0;background:${emailTokens.colors.canvas}">
3725
- <div style="display:none;max-height:0;overflow:hidden;opacity:0;color:transparent">${escapeHtml(preheader)}</div>
3726
- ${table(row(
3727
- table(
3728
- row("&nbsp;", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel("Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(content, "padding:0 32px 32px") + row(`${divider()}${paragraph(footer, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "0")}`, "padding:0 32px 28px"),
3729
- `width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
4396
+ <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
4397
+ <head>
4398
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
4399
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4400
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
4401
+ <meta name="x-apple-disable-message-reformatting">
4402
+ <meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
4403
+ <meta name="color-scheme" content="light">
4404
+ <meta name="supported-color-schemes" content="light">
4405
+ <title>${escapeHtml(title)}</title>
4406
+ </head>
4407
+ <body style="margin:0;padding:0;background-color:${emailTokens.colors.canvas};word-spacing:normal;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%">
4408
+ <div style="display:none;max-height:0;max-width:0;overflow:hidden;opacity:0;mso-hide:all;color:transparent;font-size:1px;line-height:1px">
4409
+ ${escapeHtml(preheader)}&#8203;&#847;&zwnj;&nbsp;&#8199;&#8199;&#847;&zwnj;&nbsp;
4410
+ </div>
4411
+ <center lang="en" style="width:100%;background-color:${emailTokens.colors.canvas}">
4412
+ ${table(
4413
+ row(
4414
+ table(
4415
+ row("&nbsp;", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel("Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(content, "padding:0 32px 32px") + row(
4416
+ `${divider()}${paragraph(footer, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "0")}`,
4417
+ "padding:0 32px 28px"
4418
+ ),
4419
+ `width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
4420
+ ),
4421
+ "padding:32px 12px"
3730
4422
  ),
3731
- "padding:32px 12px"
3732
- ), `width:100%;background:${emailTokens.colors.canvas}`)}
4423
+ `width:100%;background:${emailTokens.colors.canvas}`
4424
+ )}
4425
+ </center>
3733
4426
  </body>
3734
4427
  </html>`;
3735
4428
  }
@@ -3815,12 +4508,14 @@ function buildWelcomeEmail(config, args) {
3815
4508
  }
3816
4509
  function buildInviteEmail(config, args) {
3817
4510
  const custom = config.email?.templates?.invite?.(args);
4511
+ const inviteLink = args.url;
4512
+ const inviteLinkBlock = inviteLink ? safeLinkDetailBox(inviteLink) : "";
3818
4513
  return {
3819
4514
  subject: custom?.subject ?? "You've been invited",
3820
4515
  html: custom?.html ?? layout({
3821
- preheader: "You've been invited to join a Dyrected account.",
4516
+ preheader: "You've been invited to join a Dyrected admin area.",
3822
4517
  title: "You've been invited",
3823
- content: `${args.invitedByEmail ? paragraph(`You were invited by ${args.invitedByEmail}.`) : ""}${paragraph("Use the token below to accept your invitation. It expires in 7 days.")}${sectionLabel("Invitation token")}${detailBox(args.token, true)}`,
4518
+ content: inviteLink ? `${args.invitedByEmail ? paragraph(`You were invited by ${args.invitedByEmail}.`) : ""}${paragraph("Use the invitation link below to create your account. The link expires in 7 days.")}${ctaButton("Accept invitation", inviteLink)}${sectionLabel("Invitation link")}${inviteLinkBlock}${paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")}` : `${args.invitedByEmail ? paragraph(`You were invited by ${args.invitedByEmail}.`) : ""}${paragraph("Use the invitation token below to accept your invitation. It expires in 7 days.")}${sectionLabel("Invitation token")}${detailBox(args.token, true)}`,
3824
4519
  footer: "If you weren't expecting this invitation, you can safely ignore this email."
3825
4520
  })
3826
4521
  };
@@ -3828,12 +4523,13 @@ function buildInviteEmail(config, args) {
3828
4523
  function buildResetPasswordEmail(config, args) {
3829
4524
  const custom = config.email?.templates?.resetPassword?.(args);
3830
4525
  const resetLink = args.url;
4526
+ const resetLinkBlock = resetLink ? safeLinkDetailBox(resetLink) : "";
3831
4527
  return {
3832
4528
  subject: custom?.subject ?? "Reset your password",
3833
4529
  html: custom?.html ?? layout({
3834
4530
  preheader: "Reset your Dyrected password.",
3835
4531
  title: "Reset your password",
3836
- content: `${paragraph("We received a request to reset your password. The reset link and token expire in 1 hour.")}${resetLink ? ctaButton("Reset password", resetLink) : ""}${sectionLabel(resetLink ? "Or use this token" : "Reset token")}${detailBox(args.token, true)}`,
4532
+ content: resetLink ? `${paragraph("We received a request to reset your password. The reset link expires in 1 hour.")}${ctaButton("Reset password", resetLink)}${sectionLabel("Reset link")}${resetLinkBlock}${paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")}` : `${paragraph("We received a request to reset your password. The reset token expires in 1 hour.")}${sectionLabel("Reset token")}${detailBox(args.token, true)}`,
3837
4533
  footer: "If you didn't request a password reset, you can safely ignore this email."
3838
4534
  })
3839
4535
  };
@@ -3866,6 +4562,36 @@ var AuthController = class {
3866
4562
  } = user;
3867
4563
  return safeUser;
3868
4564
  }
4565
+ hasField(name) {
4566
+ return (this.collection.fields || []).some((field) => field.name === name);
4567
+ }
4568
+ async buildPendingInviteData(email, extraFields = {}) {
4569
+ const {
4570
+ id: _id,
4571
+ password: _password,
4572
+ email: _email,
4573
+ ...safeExtraFields
4574
+ } = extraFields;
4575
+ const data = {
4576
+ ...safeExtraFields,
4577
+ email,
4578
+ password: await hashPassword((0, import_node_crypto2.randomBytes)(32).toString("hex"))
4579
+ };
4580
+ if (this.hasField("status")) {
4581
+ data.status = "pending";
4582
+ }
4583
+ return data;
4584
+ }
4585
+ buildAcceptedInviteData(hashedPassword, extraFields) {
4586
+ const data = {
4587
+ ...extraFields,
4588
+ password: hashedPassword
4589
+ };
4590
+ if (this.hasField("status")) {
4591
+ data.status = "active";
4592
+ }
4593
+ return data;
4594
+ }
3869
4595
  clearLockoutState(c, userId) {
3870
4596
  const db = c.get("config").db;
3871
4597
  if (!db) return Promise.resolve(null);
@@ -4000,6 +4726,12 @@ var AuthController = class {
4000
4726
  401
4001
4727
  );
4002
4728
  }
4729
+ if (user.status === "pending") {
4730
+ return c.json(
4731
+ { error: true, message: "This invitation has not been accepted yet." },
4732
+ 403
4733
+ );
4734
+ }
4003
4735
  const lockedUntilMs = getLockedUntilMs(user.lockedUntil);
4004
4736
  if (lockedUntilMs !== null && lockedUntilMs > Date.now()) {
4005
4737
  const retryAfterSeconds = Math.max(
@@ -4264,6 +4996,7 @@ var AuthController = class {
4264
4996
  // ---------------------------------------------------------------------------
4265
4997
  // POST /invite
4266
4998
  // Requires auth. Issues a signed invite token and emails it to the invitee.
4999
+ // If inviteUrl is provided, the email uses a clickable acceptance URL.
4267
5000
  // ---------------------------------------------------------------------------
4268
5001
  async invite(c) {
4269
5002
  const config = c.get("config");
@@ -4277,17 +5010,32 @@ var AuthController = class {
4277
5010
  if (!body?.email) {
4278
5011
  return c.json({ error: true, message: "email is required." }, 400);
4279
5012
  }
5013
+ const inviteData = body?.data && typeof body.data === "object" && !Array.isArray(body.data) ? body.data : {};
4280
5014
  const existing = await db.find({
4281
5015
  collection: this.collection.slug,
4282
5016
  where: { email: body.email },
4283
5017
  limit: 1
4284
5018
  });
4285
- if (existing.total > 0) {
5019
+ const existingUser = existing.docs[0];
5020
+ const existingIsPending = existingUser?.status === "pending";
5021
+ if (existingUser && !existingIsPending) {
4286
5022
  return c.json(
4287
5023
  { error: true, message: "An account with that email already exists." },
4288
5024
  409
4289
5025
  );
4290
5026
  }
5027
+ if (!existingUser) {
5028
+ await db.create({
5029
+ collection: this.collection.slug,
5030
+ data: await this.buildPendingInviteData(body.email, inviteData)
5031
+ });
5032
+ } else if (Object.keys(inviteData).length > 0) {
5033
+ await db.update({
5034
+ collection: this.collection.slug,
5035
+ id: String(existingUser.id),
5036
+ data: await this.buildPendingInviteData(body.email, inviteData)
5037
+ });
5038
+ }
4291
5039
  const inviteToken = await signCollectionToken(
4292
5040
  {
4293
5041
  sub: body.email,
@@ -4297,10 +5045,13 @@ var AuthController = class {
4297
5045
  },
4298
5046
  "7d"
4299
5047
  );
5048
+ const inviteUrl = body?.inviteUrl;
5049
+ const url = inviteUrl ? `${inviteUrl}${inviteUrl.includes("?") ? "&" : "?"}inviteToken=${encodeURIComponent(inviteToken)}` : void 0;
4300
5050
  try {
4301
5051
  const { subject, html } = buildInviteEmail(config, {
4302
5052
  token: inviteToken,
4303
- invitedByEmail: requestUser.email
5053
+ invitedByEmail: requestUser.email,
5054
+ url
4304
5055
  });
4305
5056
  await sendEmail(config, { to: body.email, subject, html });
4306
5057
  } catch (err) {
@@ -4310,7 +5061,12 @@ var AuthController = class {
4310
5061
  email: body.email
4311
5062
  });
4312
5063
  }
4313
- return c.json({ success: true, message: `Invite sent to ${body.email}.` });
5064
+ return c.json({
5065
+ success: true,
5066
+ message: `Invite sent to ${body.email}.`,
5067
+ token: inviteToken,
5068
+ inviteUrl: url
5069
+ });
4314
5070
  }
4315
5071
  // ---------------------------------------------------------------------------
4316
5072
  // POST /accept-invite
@@ -4349,7 +5105,9 @@ var AuthController = class {
4349
5105
  where: { email: inviteeEmail },
4350
5106
  limit: 1
4351
5107
  });
4352
- if (existing.total > 0) {
5108
+ const existingUser = existing.docs[0];
5109
+ const existingIsPending = existingUser?.status === "pending";
5110
+ if (existingUser && !existingIsPending) {
4353
5111
  return c.json(
4354
5112
  { error: true, message: "An account with that email already exists." },
4355
5113
  409
@@ -4357,9 +5115,17 @@ var AuthController = class {
4357
5115
  }
4358
5116
  const { token: _t, password: _p, ...extraFields } = body;
4359
5117
  const hashedPassword = await hashPassword(body.password);
4360
- const user = await db.create({
5118
+ const user = existingUser ? await db.update({
5119
+ collection: this.collection.slug,
5120
+ id: String(existingUser.id),
5121
+ data: this.buildAcceptedInviteData(hashedPassword, extraFields)
5122
+ }) : await db.create({
4361
5123
  collection: this.collection.slug,
4362
- data: { ...extraFields, email: inviteeEmail, password: hashedPassword }
5124
+ data: {
5125
+ ...extraFields,
5126
+ email: inviteeEmail,
5127
+ ...this.buildAcceptedInviteData(hashedPassword, {})
5128
+ }
4363
5129
  });
4364
5130
  const sessionToken = await issueAuthSessionToken({
4365
5131
  config,
@@ -4385,7 +5151,7 @@ var AuthController = class {
4385
5151
  };
4386
5152
 
4387
5153
  // src/controllers/admin-auth.controller.ts
4388
- var import_node_crypto2 = require("crypto");
5154
+ var import_node_crypto3 = require("crypto");
4389
5155
  var import_node_util3 = require("util");
4390
5156
  var import_jose2 = require("jose");
4391
5157
  var AdminAuthController = class {
@@ -4594,7 +5360,7 @@ var AdminAuthController = class {
4594
5360
  access.roles,
4595
5361
  access.data
4596
5362
  ),
4597
- password: await hashPassword((0, import_node_crypto2.randomBytes)(32).toString("hex"))
5363
+ password: await hashPassword((0, import_node_crypto3.randomBytes)(32).toString("hex"))
4598
5364
  }
4599
5365
  });
4600
5366
  } else if (user) {
@@ -4680,7 +5446,7 @@ var AdminAuthController = class {
4680
5446
  }
4681
5447
  async buildOidcStartUrl(provider, returnTo, siteId, origin) {
4682
5448
  const discovery = await this.getOidcDiscovery(provider);
4683
- const nonce = (0, import_node_crypto2.randomBytes)(16).toString("hex");
5449
+ const nonce = (0, import_node_crypto3.randomBytes)(16).toString("hex");
4684
5450
  const state = await this.signState({
4685
5451
  providerId: provider.id,
4686
5452
  returnTo,
@@ -6247,6 +7013,7 @@ function getSwaggerHtml(specUrl) {
6247
7013
  }
6248
7014
 
6249
7015
  // src/router.ts
7016
+ var SERIALIZED_ADMIN_HOOK_PREFIX = "__dyrected_fn__:";
6250
7017
  function accessGate(config, target, action) {
6251
7018
  return async (c, next) => {
6252
7019
  const user = c.get("user");
@@ -6273,7 +7040,7 @@ function serializeFieldForApi(f) {
6273
7040
  if (serialized.admin?.hooks) {
6274
7041
  const hooks = { ...serialized.admin.hooks };
6275
7042
  if (typeof hooks.onChange === "function") {
6276
- hooks.onChange = hooks.onChange.toString();
7043
+ hooks.onChange = `${SERIALIZED_ADMIN_HOOK_PREFIX}${hooks.onChange.toString()}`;
6277
7044
  }
6278
7045
  if (typeof hooks.options === "function") {
6279
7046
  hooks.options = hooks.options.toString();
@@ -6307,6 +7074,10 @@ function registerRoutes(app, config) {
6307
7074
  app.get("/api/schemas", optionalAuth(config), async (c) => {
6308
7075
  const siteId = c.req.header("X-Site-Id");
6309
7076
  const requestConfig = siteId && config.onSchemaFetch ? mergeDynamicConfig(config, await config.onSchemaFetch(siteId)) : config;
7077
+ assertValidDeclarativeHooksInConfig(requestConfig, "/api/schemas");
7078
+ assertValidDeclarativeAccessInConfig(requestConfig, "/api/schemas");
7079
+ assertValidAdminConditionsInConfig(requestConfig, "/api/schemas");
7080
+ assertValidPreviewUrlsInConfig(requestConfig, "/api/schemas");
6310
7081
  const collections = [...requestConfig.collections];
6311
7082
  const globals = [...requestConfig.globals];
6312
7083
  const user = c.get("user");
@@ -6421,7 +7192,18 @@ function registerRoutes(app, config) {
6421
7192
  globals: filteredGlobals,
6422
7193
  admin: requestConfig.admin || {},
6423
7194
  adminAuth: getPublicAdminAuthConfig(requestConfig.adminAuth, collections),
6424
- hasStorage: !!requestConfig.storage
7195
+ hasStorage: !!requestConfig.storage,
7196
+ configDiagnostics: collectConfigDiagnostics(requestConfig),
7197
+ adminHealth: {
7198
+ emailConfigured: !!requestConfig.email,
7199
+ secureAuthSecretConfigured: !!process.env.DYRECTED_JWT_SECRET,
7200
+ authCollectionConfigured: requestConfig.collections.some(
7201
+ (collection) => !!collection.auth
7202
+ ),
7203
+ uploadCollectionConfigured: requestConfig.collections.some(
7204
+ (collection) => !!collection.upload
7205
+ )
7206
+ }
6425
7207
  });
6426
7208
  });
6427
7209
  app.get(
@@ -7116,6 +7898,10 @@ var AUTH_SESSIONS_COLLECTION_CONFIG = {
7116
7898
  };
7117
7899
  function normalizeConfig(config) {
7118
7900
  const schemaAwareConfig = normalizeSchemaFragment(config);
7901
+ assertValidDeclarativeHooksInConfig(schemaAwareConfig, "config");
7902
+ assertValidDeclarativeAccessInConfig(schemaAwareConfig, "config");
7903
+ assertValidAdminConditionsInConfig(schemaAwareConfig, "config");
7904
+ assertValidPreviewUrlsInConfig(schemaAwareConfig, "config");
7119
7905
  const collections = schemaAwareConfig?.collections || [];
7120
7906
  const globals = schemaAwareConfig?.globals || [];
7121
7907
  const needsAudit = collections.some((col) => col.audit);
@@ -7178,6 +7964,27 @@ function normalizeConfig(config) {
7178
7964
  }
7179
7965
  ];
7180
7966
  }
7967
+ if (!existingFieldNames.has("status")) {
7968
+ fields = [
7969
+ ...fields,
7970
+ {
7971
+ name: "status",
7972
+ type: "select",
7973
+ label: "Status",
7974
+ defaultValue: "active",
7975
+ options: [
7976
+ { value: "active", label: "Active" },
7977
+ { value: "pending", label: "Pending" }
7978
+ ],
7979
+ access: {
7980
+ update: "user.roles && 'admin' in user.roles && user.id != id"
7981
+ },
7982
+ admin: {
7983
+ condition: `!(data.roles && "admin" in data.roles)`
7984
+ }
7985
+ }
7986
+ ];
7987
+ }
7181
7988
  fields = fields.map((field) => {
7182
7989
  if (field.name === "email") {
7183
7990
  return {
@@ -7217,6 +8024,15 @@ function normalizeConfig(config) {
7217
8024
  }
7218
8025
  };
7219
8026
  }
8027
+ if (field.name === "status") {
8028
+ return {
8029
+ ...field,
8030
+ access: {
8031
+ ...field.access || {},
8032
+ update: "user.roles && 'admin' in user.roles && user.id != id"
8033
+ }
8034
+ };
8035
+ }
7220
8036
  return field;
7221
8037
  });
7222
8038
  }
@@ -7254,9 +8070,7 @@ function normalizeConfig(config) {
7254
8070
  }
7255
8071
  }
7256
8072
  const updatedFieldNames = new Set(fields.map((f) => f.name));
7257
- const fieldsToInject = SYSTEM_FIELDS.filter(
7258
- (f) => !updatedFieldNames.has(f.name)
7259
- );
8073
+ const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
7260
8074
  const workflow = col.workflow || (col.drafts ? simplePublishingWorkflow() : void 0);
7261
8075
  return {
7262
8076
  ...col,
@@ -7264,20 +8078,13 @@ function normalizeConfig(config) {
7264
8078
  fields: [...fields, ...fieldsToInject]
7265
8079
  };
7266
8080
  });
7267
- const hasAuditCollection = normalizedCollections.some(
7268
- (col) => col.slug === AUDIT_COLLECTION_SLUG
7269
- );
8081
+ const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
7270
8082
  const systemCollections = [];
7271
- if (needsAudit && !hasAuditCollection)
7272
- systemCollections.push(AUDIT_COLLECTION);
7273
- if (needsWorkflow && !normalizedCollections.some(
7274
- (col) => col.slug === WORKFLOW_HISTORY_COLLECTION
7275
- )) {
8083
+ if (needsAudit && !hasAuditCollection) systemCollections.push(AUDIT_COLLECTION);
8084
+ if (needsWorkflow && !normalizedCollections.some((col) => col.slug === WORKFLOW_HISTORY_COLLECTION)) {
7276
8085
  systemCollections.push(WORKFLOW_HISTORY_COLLECTION_CONFIG);
7277
8086
  }
7278
- if (needsWorkflow && !normalizedCollections.some(
7279
- (col) => col.slug === LIFECYCLE_EVENTS_COLLECTION
7280
- )) {
8087
+ if (needsWorkflow && !normalizedCollections.some((col) => col.slug === LIFECYCLE_EVENTS_COLLECTION)) {
7281
8088
  systemCollections.push(LIFECYCLE_EVENTS_COLLECTION_CONFIG);
7282
8089
  }
7283
8090
  if (needsAuthSessions && !normalizedCollections.some((col) => col.slug === AUTH_SESSIONS_COLLECTION)) {