@beechcms/core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (389) hide show
  1. package/dist/auth/account-email.d.ts +3 -0
  2. package/dist/auth/account-email.d.ts.map +1 -0
  3. package/dist/auth/account-email.js +9 -0
  4. package/dist/auth/password-reset-token.repository.d.ts +13 -2
  5. package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
  6. package/dist/auth/user.repository.d.ts +69 -4
  7. package/dist/auth/user.repository.d.ts.map +1 -1
  8. package/dist/automations-grammar-words.d.ts +2 -0
  9. package/dist/automations-grammar-words.d.ts.map +1 -0
  10. package/dist/automations-grammar-words.js +8 -0
  11. package/dist/automations.repository.interface.d.ts +19 -0
  12. package/dist/automations.repository.interface.d.ts.map +1 -0
  13. package/dist/automations.repository.interface.js +3 -0
  14. package/dist/automations.runner.interface.d.ts +10 -0
  15. package/dist/automations.runner.interface.d.ts.map +1 -0
  16. package/dist/automations.runner.interface.js +3 -0
  17. package/dist/automations.runner.stub.d.ts +5 -0
  18. package/dist/automations.runner.stub.d.ts.map +1 -0
  19. package/dist/automations.runner.stub.js +7 -0
  20. package/dist/automations.types.d.ts +75 -0
  21. package/dist/automations.types.d.ts.map +1 -0
  22. package/dist/automations.types.js +3 -0
  23. package/dist/clock.d.ts +25 -0
  24. package/dist/clock.d.ts.map +1 -0
  25. package/dist/clock.js +11 -0
  26. package/dist/common/canonical-json.d.ts +31 -0
  27. package/dist/common/canonical-json.d.ts.map +1 -0
  28. package/dist/common/canonical-json.js +91 -0
  29. package/dist/content/content-scan.repository.d.ts +1 -1
  30. package/dist/content/content.repository.d.ts +154 -3
  31. package/dist/content/content.repository.d.ts.map +1 -1
  32. package/dist/content/content.repository.js +62 -0
  33. package/dist/content/deletion-ledger.d.ts +39 -0
  34. package/dist/content/deletion-ledger.d.ts.map +1 -0
  35. package/dist/content/deletion-ledger.js +3 -0
  36. package/dist/content/seed-media-purge.repository.d.ts +34 -0
  37. package/dist/content/seed-media-purge.repository.d.ts.map +1 -0
  38. package/dist/content/seed-media-purge.repository.js +3 -0
  39. package/dist/content/seed.repository.d.ts +14 -0
  40. package/dist/content/seed.repository.d.ts.map +1 -1
  41. package/dist/content-scan.repository.d.ts +9 -0
  42. package/dist/content-scan.repository.d.ts.map +1 -0
  43. package/dist/content-scan.repository.js +3 -0
  44. package/dist/content.repository.d.ts +247 -0
  45. package/dist/content.repository.d.ts.map +1 -0
  46. package/dist/content.repository.js +59 -0
  47. package/dist/dashboard-layout/content-view.d.ts +757 -0
  48. package/dist/dashboard-layout/content-view.d.ts.map +1 -0
  49. package/dist/dashboard-layout/content-view.js +285 -0
  50. package/dist/dashboard-layout/content-view.repository.d.ts +39 -0
  51. package/dist/dashboard-layout/content-view.repository.d.ts.map +1 -0
  52. package/dist/dashboard-layout/content-view.repository.js +3 -0
  53. package/dist/dashboard-layout/dashboard-layout.d.ts +5 -0
  54. package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
  55. package/dist/dashboard-layout/dashboard-layout.js +17 -9
  56. package/dist/dashboard-layout/kanban/kanban.d.ts +32 -18
  57. package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -1
  58. package/dist/dashboard-layout/kanban/kanban.js +45 -21
  59. package/dist/dashboard-layout/seed-layout.d.ts +22 -31
  60. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
  61. package/dist/dashboard-layout/seed-layout.js +93 -18
  62. package/dist/dashboard-layout/seed-layout.repository.d.ts +1 -5
  63. package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -1
  64. package/dist/dashboard-layout.d.ts +154 -0
  65. package/dist/dashboard-layout.d.ts.map +1 -0
  66. package/dist/dashboard-layout.js +186 -0
  67. package/dist/dashboard-layout.repository.d.ts +18 -0
  68. package/dist/dashboard-layout.repository.d.ts.map +1 -0
  69. package/dist/dashboard-layout.repository.js +3 -0
  70. package/dist/dashboard-permissions.d.ts +3 -0
  71. package/dist/dashboard-permissions.d.ts.map +1 -0
  72. package/dist/dashboard-permissions.js +11 -0
  73. package/dist/dashboard-scopes.d.ts +12 -0
  74. package/dist/dashboard-scopes.d.ts.map +1 -0
  75. package/dist/dashboard-scopes.js +30 -0
  76. package/dist/define-seed.d.ts +3 -0
  77. package/dist/define-seed.d.ts.map +1 -0
  78. package/dist/define-seed.js +5 -0
  79. package/dist/demo-data.repository.d.ts +5 -0
  80. package/dist/demo-data.repository.d.ts.map +1 -0
  81. package/dist/demo-data.repository.js +3 -0
  82. package/dist/email.provider.d.ts +32 -0
  83. package/dist/email.provider.d.ts.map +1 -0
  84. package/dist/email.provider.js +4 -0
  85. package/dist/engine/ddl/ddl.d.ts +247 -0
  86. package/dist/engine/ddl/ddl.d.ts.map +1 -0
  87. package/dist/engine/ddl/ddl.js +653 -0
  88. package/dist/engine/ddl/schema-mutator.d.ts +29 -0
  89. package/dist/engine/ddl/schema-mutator.d.ts.map +1 -0
  90. package/dist/engine/ddl/schema-mutator.js +3 -0
  91. package/dist/engine/ddl/seed-ddl-destructive.d.ts +40 -0
  92. package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +1 -0
  93. package/dist/engine/ddl/seed-ddl-destructive.js +149 -0
  94. package/dist/engine/ddl/seed-ddl.d.ts +43 -0
  95. package/dist/engine/ddl/seed-ddl.d.ts.map +1 -0
  96. package/dist/engine/ddl/seed-ddl.js +115 -0
  97. package/dist/engine/ddl/seed-purge-guards.d.ts +7 -0
  98. package/dist/engine/ddl/seed-purge-guards.d.ts.map +1 -0
  99. package/dist/engine/ddl/seed-purge-guards.js +19 -0
  100. package/dist/engine/engine.d.ts +4 -3
  101. package/dist/engine/engine.d.ts.map +1 -1
  102. package/dist/engine/engine.js +4 -3
  103. package/dist/engine/introspection/introspection.d.ts +101 -0
  104. package/dist/engine/introspection/introspection.d.ts.map +1 -0
  105. package/dist/engine/introspection/introspection.js +127 -0
  106. package/dist/engine/introspection/relation-audit.d.ts +46 -0
  107. package/dist/engine/introspection/relation-audit.d.ts.map +1 -0
  108. package/dist/engine/introspection/relation-audit.js +103 -0
  109. package/dist/engine/introspection/schema-fingerprint.d.ts +74 -0
  110. package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -0
  111. package/dist/engine/introspection/schema-fingerprint.js +102 -0
  112. package/dist/engine/introspection.d.ts +101 -0
  113. package/dist/engine/introspection.d.ts.map +1 -0
  114. package/dist/engine/introspection.js +127 -0
  115. package/dist/engine/localization/localization.d.ts +127 -0
  116. package/dist/engine/localization/localization.d.ts.map +1 -0
  117. package/dist/engine/localization/localization.js +212 -0
  118. package/dist/engine/localization.d.ts +127 -0
  119. package/dist/engine/localization.d.ts.map +1 -0
  120. package/dist/engine/localization.js +212 -0
  121. package/dist/engine/privacy/policies.d.ts +58 -0
  122. package/dist/engine/privacy/policies.d.ts.map +1 -0
  123. package/dist/engine/privacy/policies.js +191 -0
  124. package/dist/engine/privacy/privacy.service.d.ts +78 -0
  125. package/dist/engine/privacy/privacy.service.d.ts.map +1 -0
  126. package/dist/engine/privacy/privacy.service.js +138 -0
  127. package/dist/engine/privacy.service.d.ts.map +1 -1
  128. package/dist/engine/query/active-clause.d.ts +19 -0
  129. package/dist/engine/query/active-clause.d.ts.map +1 -0
  130. package/dist/engine/query/active-clause.js +27 -0
  131. package/dist/engine/query/query.d.ts +36 -0
  132. package/dist/engine/query/query.d.ts.map +1 -0
  133. package/dist/engine/query/query.js +393 -0
  134. package/dist/engine/query/relations.d.ts +28 -0
  135. package/dist/engine/query/relations.d.ts.map +1 -0
  136. package/dist/engine/query/relations.js +25 -0
  137. package/dist/engine/query/serialize.d.ts +22 -0
  138. package/dist/engine/query/serialize.d.ts.map +1 -0
  139. package/dist/engine/query/serialize.js +175 -0
  140. package/dist/engine/schema-fingerprint.d.ts +73 -0
  141. package/dist/engine/schema-fingerprint.d.ts.map +1 -0
  142. package/dist/engine/schema-fingerprint.js +97 -0
  143. package/dist/engine/seed-registry.d.ts.map +1 -1
  144. package/dist/engine/seeds/candidate-ids.d.ts +14 -0
  145. package/dist/engine/seeds/candidate-ids.d.ts.map +1 -0
  146. package/dist/engine/seeds/candidate-ids.js +40 -0
  147. package/dist/engine/seeds/define-seed.d.ts +3 -0
  148. package/dist/engine/seeds/define-seed.d.ts.map +1 -0
  149. package/dist/engine/seeds/define-seed.js +5 -0
  150. package/dist/engine/seeds/seed-definition-diff.d.ts +38 -0
  151. package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -0
  152. package/dist/engine/seeds/seed-definition-diff.js +51 -0
  153. package/dist/engine/seeds/seed-registry.d.ts +88 -0
  154. package/dist/engine/seeds/seed-registry.d.ts.map +1 -0
  155. package/dist/engine/seeds/seed-registry.js +194 -0
  156. package/dist/engine/seeds/seed-types-generator.d.ts +25 -0
  157. package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -0
  158. package/dist/engine/seeds/seed-types-generator.js +116 -0
  159. package/dist/engine/seeds/seed-validation.d.ts +35 -0
  160. package/dist/engine/seeds/seed-validation.d.ts.map +1 -0
  161. package/dist/engine/seeds/seed-validation.js +384 -0
  162. package/dist/engine/seeds/seeds.d.ts +20 -0
  163. package/dist/engine/seeds/seeds.d.ts.map +1 -0
  164. package/dist/engine/seeds/seeds.js +21 -0
  165. package/dist/engine/seeds/tag-migration.d.ts +73 -0
  166. package/dist/engine/seeds/tag-migration.d.ts.map +1 -0
  167. package/dist/engine/seeds/tag-migration.js +212 -0
  168. package/dist/engine/seeds/tag-options.d.ts +15 -0
  169. package/dist/engine/seeds/tag-options.d.ts.map +1 -0
  170. package/dist/engine/seeds/tag-options.js +111 -0
  171. package/dist/engine/seeds/tag-value.d.ts +31 -0
  172. package/dist/engine/seeds/tag-value.d.ts.map +1 -0
  173. package/dist/engine/seeds/tag-value.js +51 -0
  174. package/dist/engine/types.d.ts +59 -7
  175. package/dist/engine/types.d.ts.map +1 -1
  176. package/dist/engine/validation/cache.d.ts.map +1 -1
  177. package/dist/engine/validation/cache.js +20 -4
  178. package/dist/engine/validation/emptiness.d.ts +12 -0
  179. package/dist/engine/validation/emptiness.d.ts.map +1 -0
  180. package/dist/engine/validation/emptiness.js +33 -0
  181. package/dist/engine/validation/index.d.ts +11 -0
  182. package/dist/engine/validation/index.d.ts.map +1 -1
  183. package/dist/engine/validation/index.js +29 -33
  184. package/dist/engine/validation/primitives.d.ts +12 -0
  185. package/dist/engine/validation/primitives.d.ts.map +1 -1
  186. package/dist/engine/validation/primitives.js +22 -0
  187. package/dist/engine/validation/richtext-sanitizer.d.ts +9 -1
  188. package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
  189. package/dist/engine/validation/richtext-sanitizer.js +36 -7
  190. package/dist/engine/validation/schema-builders.d.ts +16 -0
  191. package/dist/engine/validation/schema-builders.d.ts.map +1 -1
  192. package/dist/engine/validation/schema-builders.js +125 -18
  193. package/dist/engine.d.ts +144 -0
  194. package/dist/engine.d.ts.map +1 -0
  195. package/dist/engine.js +741 -0
  196. package/dist/file-types.d.ts +13 -0
  197. package/dist/file-types.d.ts.map +1 -0
  198. package/dist/file-types.js +85 -0
  199. package/dist/hooks.d.ts +29 -0
  200. package/dist/hooks.d.ts.map +1 -0
  201. package/dist/hooks.js +3 -0
  202. package/dist/id-generator.d.ts +26 -0
  203. package/dist/id-generator.d.ts.map +1 -0
  204. package/dist/id-generator.js +12 -0
  205. package/dist/idempotency.repository.d.ts +13 -0
  206. package/dist/idempotency.repository.d.ts.map +1 -0
  207. package/dist/idempotency.repository.js +3 -0
  208. package/dist/index.d.ts +34 -11
  209. package/dist/index.d.ts.map +1 -1
  210. package/dist/index.js +34 -11
  211. package/dist/kanban-position.repository.d.ts +19 -0
  212. package/dist/kanban-position.repository.d.ts.map +1 -0
  213. package/dist/kanban-position.repository.js +3 -0
  214. package/dist/kanban.d.ts +75 -0
  215. package/dist/kanban.d.ts.map +1 -0
  216. package/dist/kanban.js +82 -0
  217. package/dist/layout-permissions.d.ts +3 -0
  218. package/dist/layout-permissions.d.ts.map +1 -0
  219. package/dist/layout-permissions.js +11 -0
  220. package/dist/media/file-types.d.ts +12 -0
  221. package/dist/media/file-types.d.ts.map +1 -1
  222. package/dist/media/file-types.js +24 -4
  223. package/dist/media/image-transformer.d.ts +34 -0
  224. package/dist/media/image-transformer.d.ts.map +1 -0
  225. package/dist/media/image-transformer.js +3 -0
  226. package/dist/media/magic-bytes.d.ts.map +1 -1
  227. package/dist/media/magic-bytes.js +19 -9
  228. package/dist/media/media-transform.d.ts +92 -0
  229. package/dist/media/media-transform.d.ts.map +1 -0
  230. package/dist/media/media-transform.js +197 -0
  231. package/dist/media/media.repository.d.ts +5 -0
  232. package/dist/media/media.repository.d.ts.map +1 -1
  233. package/dist/media.repository.d.ts +61 -0
  234. package/dist/media.repository.d.ts.map +1 -0
  235. package/dist/media.repository.js +3 -0
  236. package/dist/notifications/notification.repository.d.ts +11 -2
  237. package/dist/notifications/notification.repository.d.ts.map +1 -1
  238. package/dist/oauth/authorization-code.repository.d.ts +10 -5
  239. package/dist/oauth/authorization-code.repository.d.ts.map +1 -1
  240. package/dist/oauth/role-guard.d.ts +30 -11
  241. package/dist/oauth/role-guard.d.ts.map +1 -1
  242. package/dist/oauth/role-guard.js +27 -5
  243. package/dist/oauth/token.repository.d.ts +2 -0
  244. package/dist/oauth/token.repository.d.ts.map +1 -1
  245. package/dist/policies.d.ts +10 -0
  246. package/dist/policies.d.ts.map +1 -0
  247. package/dist/policies.js +33 -0
  248. package/dist/queue/queue.interface.d.ts +9 -1
  249. package/dist/queue/queue.interface.d.ts.map +1 -1
  250. package/dist/queue/queue.stub.d.ts +1 -1
  251. package/dist/queue/queue.stub.d.ts.map +1 -1
  252. package/dist/queue/queue.stub.js +2 -3
  253. package/dist/queue.interface.d.ts +39 -0
  254. package/dist/queue.interface.d.ts.map +1 -0
  255. package/dist/queue.interface.js +3 -0
  256. package/dist/queue.stub.d.ts +6 -0
  257. package/dist/queue.stub.d.ts.map +1 -0
  258. package/dist/queue.stub.js +8 -0
  259. package/dist/rate-limit/token-bucket-rate-limiter.d.ts +7 -0
  260. package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
  261. package/dist/rate-limit/token-bucket-rate-limiter.js +66 -0
  262. package/dist/rbac/evaluate.d.ts +57 -0
  263. package/dist/rbac/evaluate.d.ts.map +1 -0
  264. package/dist/rbac/evaluate.js +113 -0
  265. package/dist/rbac/invitation.repository.d.ts +84 -0
  266. package/dist/rbac/invitation.repository.d.ts.map +1 -0
  267. package/dist/rbac/invitation.repository.js +3 -0
  268. package/dist/rbac/permissions.d.ts +39 -0
  269. package/dist/rbac/permissions.d.ts.map +1 -0
  270. package/dist/rbac/permissions.js +43 -0
  271. package/dist/rbac/types.d.ts +130 -0
  272. package/dist/rbac/types.d.ts.map +1 -0
  273. package/dist/rbac/types.js +4 -0
  274. package/dist/relations.d.ts +28 -0
  275. package/dist/relations.d.ts.map +1 -0
  276. package/dist/relations.js +25 -0
  277. package/dist/richtext-render.d.ts +11 -0
  278. package/dist/richtext-render.d.ts.map +1 -0
  279. package/dist/richtext-render.js +87 -0
  280. package/dist/richtext.d.ts +11 -0
  281. package/dist/richtext.d.ts.map +1 -0
  282. package/dist/richtext.js +13 -0
  283. package/dist/scheduler.interface.d.ts +4 -0
  284. package/dist/scheduler.interface.d.ts.map +1 -0
  285. package/dist/scheduler.interface.js +3 -0
  286. package/dist/scheduler.stub.d.ts +5 -0
  287. package/dist/scheduler.stub.d.ts.map +1 -0
  288. package/dist/scheduler.stub.js +7 -0
  289. package/dist/schema/canonical.d.ts +15 -0
  290. package/dist/schema/canonical.d.ts.map +1 -0
  291. package/dist/schema/canonical.js +37 -0
  292. package/dist/schema/define.d.ts +42 -0
  293. package/dist/schema/define.d.ts.map +1 -0
  294. package/dist/schema/define.js +56 -0
  295. package/dist/schema/emit.d.ts +14 -0
  296. package/dist/schema/emit.d.ts.map +1 -0
  297. package/dist/schema/emit.js +43 -0
  298. package/dist/schema/index.d.ts +12 -0
  299. package/dist/schema/index.d.ts.map +1 -0
  300. package/dist/schema/index.js +13 -0
  301. package/dist/schema/manifest-seeds.d.ts +12 -0
  302. package/dist/schema/manifest-seeds.d.ts.map +1 -0
  303. package/dist/schema/manifest-seeds.js +48 -0
  304. package/dist/schema/manifest-validation.d.ts +13 -0
  305. package/dist/schema/manifest-validation.d.ts.map +1 -0
  306. package/dist/schema/manifest-validation.js +41 -0
  307. package/dist/schema/manifest.types.d.ts +54 -0
  308. package/dist/schema/manifest.types.d.ts.map +1 -0
  309. package/dist/schema/manifest.types.js +7 -0
  310. package/dist/schema-mutator.d.ts +26 -0
  311. package/dist/schema-mutator.d.ts.map +1 -0
  312. package/dist/schema-mutator.js +3 -0
  313. package/dist/search/search.repository.d.ts +5 -4
  314. package/dist/search/search.repository.d.ts.map +1 -1
  315. package/dist/search/vector-extractor.d.ts.map +1 -1
  316. package/dist/search/vector-extractor.js +23 -2
  317. package/dist/search/vector.repository.d.ts +2 -1
  318. package/dist/search/vector.repository.d.ts.map +1 -1
  319. package/dist/security/time-trap-token.repository.d.ts +5 -3
  320. package/dist/security/time-trap-token.repository.d.ts.map +1 -1
  321. package/dist/seed-ddl-destructive.d.ts +40 -0
  322. package/dist/seed-ddl-destructive.d.ts.map +1 -0
  323. package/dist/seed-ddl-destructive.js +149 -0
  324. package/dist/seed-ddl.d.ts +40 -0
  325. package/dist/seed-ddl.d.ts.map +1 -0
  326. package/dist/seed-ddl.js +100 -0
  327. package/dist/seed-layout.d.ts +145 -0
  328. package/dist/seed-layout.d.ts.map +1 -0
  329. package/dist/seed-layout.js +192 -0
  330. package/dist/seed-layout.repository.d.ts +22 -0
  331. package/dist/seed-layout.repository.d.ts.map +1 -0
  332. package/dist/seed-layout.repository.js +3 -0
  333. package/dist/seed-registry.d.ts +71 -0
  334. package/dist/seed-registry.d.ts.map +1 -0
  335. package/dist/seed-registry.js +143 -0
  336. package/dist/seed-types-generator.d.ts +1 -1
  337. package/dist/seed-types-generator.d.ts.map +1 -1
  338. package/dist/seed-validation.d.ts +23 -0
  339. package/dist/seed-validation.d.ts.map +1 -0
  340. package/dist/seed-validation.js +224 -0
  341. package/dist/seed.repository.d.ts +37 -0
  342. package/dist/seed.repository.d.ts.map +1 -0
  343. package/dist/seed.repository.js +3 -0
  344. package/dist/seeds.d.ts +20 -0
  345. package/dist/seeds.d.ts.map +1 -0
  346. package/dist/seeds.js +21 -0
  347. package/dist/settings/site-settings.repository.d.ts +8 -0
  348. package/dist/settings/site-settings.repository.d.ts.map +1 -1
  349. package/dist/site-settings.repository.d.ts +16 -0
  350. package/dist/site-settings.repository.d.ts.map +1 -0
  351. package/dist/site-settings.repository.js +3 -0
  352. package/dist/slug-utils.d.ts +18 -0
  353. package/dist/slug-utils.d.ts.map +1 -0
  354. package/dist/slug-utils.js +32 -0
  355. package/dist/storage.d.ts +46 -0
  356. package/dist/storage.d.ts.map +1 -0
  357. package/dist/storage.js +3 -0
  358. package/dist/transfer/csv.d.ts +34 -0
  359. package/dist/transfer/csv.d.ts.map +1 -0
  360. package/dist/transfer/csv.js +135 -0
  361. package/dist/transfer/flat-seed.d.ts +13 -0
  362. package/dist/transfer/flat-seed.d.ts.map +1 -0
  363. package/dist/transfer/flat-seed.js +50 -0
  364. package/dist/transfer/index.d.ts +7 -0
  365. package/dist/transfer/index.d.ts.map +1 -0
  366. package/dist/transfer/index.js +8 -0
  367. package/dist/transfer/ndjson.d.ts +25 -0
  368. package/dist/transfer/ndjson.d.ts.map +1 -0
  369. package/dist/transfer/ndjson.js +62 -0
  370. package/dist/transfer/row-mapping.d.ts +36 -0
  371. package/dist/transfer/row-mapping.d.ts.map +1 -0
  372. package/dist/transfer/row-mapping.js +97 -0
  373. package/dist/transfer/transfer.constants.d.ts +20 -0
  374. package/dist/transfer/transfer.constants.d.ts.map +1 -0
  375. package/dist/transfer/transfer.constants.js +21 -0
  376. package/dist/transfer/transfer.types.d.ts +46 -0
  377. package/dist/transfer/transfer.types.d.ts.map +1 -0
  378. package/dist/transfer/transfer.types.js +6 -0
  379. package/dist/types.d.ts +243 -0
  380. package/dist/types.d.ts.map +1 -0
  381. package/dist/types.js +3 -0
  382. package/dist/validation.d.ts +50 -0
  383. package/dist/validation.d.ts.map +1 -0
  384. package/dist/validation.js +660 -0
  385. package/dist/webhook-validation.d.ts +3 -0
  386. package/dist/webhook-validation.d.ts.map +1 -0
  387. package/dist/webhook-validation.js +6 -0
  388. package/package.json +11 -6
  389. package/dist/.tsbuildinfo +0 -1
@@ -16,6 +16,14 @@ export interface NotificationRecord {
16
16
  isRead: boolean;
17
17
  createdAt: number;
18
18
  }
19
+ /** Input of {@link INotificationRepository.create}. */
20
+ export type CreateNotificationRecord = Omit<NotificationRecord, 'id' | 'createdAt' | 'isRead'> & {
21
+ /**
22
+ * Stable identifier of the upstream event (e.g. a QStash message id). Creating twice with the
23
+ * same key stores one notification, so at-least-once delivery cannot inflate the inbox.
24
+ */
25
+ dedupeKey?: string;
26
+ };
19
27
  export interface NotificationStats {
20
28
  totalCount: number;
21
29
  latestCreatedAt: number;
@@ -40,9 +48,10 @@ export interface INotificationRepository {
40
48
  /**
41
49
  * Insert a new notification and return its generated id so callers (e.g.
42
50
  * the public form submission flow) can correlate the notification with the
43
- * triggering request.
51
+ * triggering request. With `dedupeKey`, a repeated key stores nothing and returns the id of the
52
+ * notification the first call created.
44
53
  */
45
- create(record: Omit<NotificationRecord, 'id' | 'createdAt' | 'isRead'>): Promise<string>;
54
+ create(record: CreateNotificationRecord): Promise<string>;
46
55
  markRead(notificationId: string): Promise<void>;
47
56
  markUnread(notificationId: string): Promise<void>;
48
57
  markAllRead(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAExF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
1
+ {"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,uDAAuD;AACvD,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG;IAC/F;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEzD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
@@ -19,15 +19,20 @@ export interface IOAuthAuthorizationCodeRepository {
19
19
  /** Persists a new single-use authorization code. Hash only, never plaintext. */
20
20
  save(record: NewAuthorizationCode): Promise<void>;
21
21
  /**
22
- * Returns the code regardless of its consumed state, provided it is unexpired.
23
- * Callers MUST inspect `consumedAt`: a non-null value means replay, which
24
- * requires cascade revocation via IOAuthTokenRepository.revokeByAuthorizationCode.
22
+ * Returns the code regardless of its consumed OR expired state — replay must be
23
+ * detectable even after the 60s TTL, since tokens derived from the code outlive it
24
+ * by 30 days. Callers MUST inspect `consumedAt` (replay signal, requires cascade
25
+ * revocation via IOAuthTokenRepository.revokeByAuthorizationCode) and `expiresAt`
26
+ * separately (an unconsumed-but-expired code is simply invalid, nothing to revoke).
25
27
  */
26
- findByHash(codeHash: string, nowTimestamp: number): Promise<AuthorizationCodeRecord | null>;
28
+ findByHash(codeHash: string): Promise<AuthorizationCodeRecord | null>;
27
29
  /**
28
30
  * Atomically marks the code consumed. Returns true only for the first caller;
29
- * every subsequent call returns false, which is the replay signal.
31
+ * every subsequent call returns false, which is the replay signal. Expiry is
32
+ * enforced here — redemption, unlike replay detection, must respect the TTL.
30
33
  */
31
34
  consumeByHash(codeHash: string, nowTimestamp: number): Promise<boolean>;
35
+ /** Invalidates every unconsumed code issued to a (client, user) pair, e.g. on consent revoke. */
36
+ invalidateByClientAndUser(clientId: string, userId: string): Promise<number>;
32
37
  }
33
38
  //# sourceMappingURL=authorization-code.repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authorization-code.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/authorization-code.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,iCAAiC;IAChD,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjD;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IAE3F;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACxE"}
1
+ {"version":3,"file":"authorization-code.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/authorization-code.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,iCAAiC;IAChD,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjD;;;;;;OAMG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IAErE;;;;OAIG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE,iGAAiG;IACjG,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAC7E"}
@@ -1,32 +1,51 @@
1
1
  import type { OAuthScope } from './scopes.js';
2
+ import type { EffectivePermissions } from '../rbac/types.js';
2
3
  /** Outcome of a role arbitration. `deniedScopes` is empty when fully granted. */
3
4
  export interface ScopeGrantDecision {
4
5
  grantedScopes: OAuthScope[];
5
6
  deniedScopes: OAuthScope[];
6
7
  }
7
8
  /**
8
- * Arbitrates which of the requested scopes a given user role may grant.
9
+ * Arbitrates which of the requested scopes a given caller may grant.
9
10
  *
10
11
  * This is the ONLY place role-based authorization may live in the OAuth flow.
11
- * `/oauth/authorize` and `/oauth/token` must never branch on `role` themselves,
12
- * so introducing a real role system later requires swapping the implementation
13
- * bound in repositoryMiddleware and nothing else.
12
+ * `/oauth/authorize` and `/oauth/token` must never branch on authority themselves.
13
+ *
14
+ * The parameter is the caller's RESOLVED authority, not a role string: the role
15
+ * string is the pre-RBAC vocabulary and is on its way out.
14
16
  */
15
17
  export interface IRoleGuard {
16
18
  /**
17
- * @param role - The resource owner's role claim, or undefined when absent.
19
+ * @param effective - The resource owner's effective permissions, already folded.
18
20
  * @param requestedScopes - Scopes the client asked for, already validated.
19
21
  */
20
- arbitrate(role: string | undefined, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
22
+ arbitrate(effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
21
23
  }
22
24
  /**
23
- * Stub guard for the pre-roles world: grants every requested scope to every role.
25
+ * Stub guard: grants every requested scope to every caller.
24
26
  *
25
- * This permissiveness is EXPLICIT and directly tested, not an accidental default.
26
- * When the roles feature lands, replace this binding with a real adapter; the
27
- * behaviour change will then be visible as a failing test here, by design.
27
+ * Retained as the explicit, directly-tested permissive baseline and as the test
28
+ * double for suites that are not exercising arbitration. It is NO LONGER the
29
+ * production binding — `repositoryMiddleware` binds {@link PermissionRoleGuard}.
28
30
  */
29
31
  export declare class AllowAllRoleGuard implements IRoleGuard {
30
- arbitrate(_role: string | undefined, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
32
+ arbitrate(_effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
33
+ }
34
+ /**
35
+ * Production guard: only a platform-wide administrator may delegate authority to an
36
+ * OAuth/MCP client.
37
+ *
38
+ * Every OAuth scope in this system (`schema:read`, `schema:write`) drives seed-schema
39
+ * tooling, which brief §2 keeps OUT of the dashboard permission axis entirely — there is
40
+ * deliberately no `manage_seeds` permission to map onto. So the question this guard
41
+ * answers is not "which seed?" but "may this account hand platform authority to a
42
+ * client at all?", and the marker for that is holding `manage_users` at
43
+ * {@link GLOBAL_SCOPE} — the same SuperAdmin marker the last-global-admin guard uses.
44
+ *
45
+ * A seed-scoped collaborator therefore cannot mint an MCP token, no matter which
46
+ * scopes the client requests. Denial is all-or-nothing: there is no partial grant.
47
+ */
48
+ export declare class PermissionRoleGuard implements IRoleGuard {
49
+ arbitrate(effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
31
50
  }
32
51
  //# sourceMappingURL=role-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role-guard.d.ts","sourceRoot":"","sources":["../../src/oauth/role-guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,UAAU,EAAE,CAAA;IAC3B,YAAY,EAAE,UAAU,EAAE,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,eAAe,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACzG;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAC5C,SAAS,CACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC;CAG/B"}
1
+ {"version":3,"file":"role-guard.d.ts","sourceRoot":"","sources":["../../src/oauth/role-guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAE5D,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,UAAU,EAAE,CAAA;IAC3B,YAAY,EAAE,UAAU,EAAE,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,CACP,SAAS,EAAE,oBAAoB,EAC/B,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC,CAAA;CAC/B;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAC5C,SAAS,CACb,UAAU,EAAE,oBAAoB,EAChC,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC;CAG/B;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IAC9C,SAAS,CACb,SAAS,EAAE,oBAAoB,EAC/B,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC;CAM/B"}
@@ -1,14 +1,36 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  /**
4
- * Stub guard for the pre-roles world: grants every requested scope to every role.
4
+ * Stub guard: grants every requested scope to every caller.
5
5
  *
6
- * This permissiveness is EXPLICIT and directly tested, not an accidental default.
7
- * When the roles feature lands, replace this binding with a real adapter; the
8
- * behaviour change will then be visible as a failing test here, by design.
6
+ * Retained as the explicit, directly-tested permissive baseline and as the test
7
+ * double for suites that are not exercising arbitration. It is NO LONGER the
8
+ * production binding — `repositoryMiddleware` binds {@link PermissionRoleGuard}.
9
9
  */
10
10
  export class AllowAllRoleGuard {
11
- async arbitrate(_role, requestedScopes) {
11
+ async arbitrate(_effective, requestedScopes) {
12
+ return { grantedScopes: [...requestedScopes], deniedScopes: [] };
13
+ }
14
+ }
15
+ /**
16
+ * Production guard: only a platform-wide administrator may delegate authority to an
17
+ * OAuth/MCP client.
18
+ *
19
+ * Every OAuth scope in this system (`schema:read`, `schema:write`) drives seed-schema
20
+ * tooling, which brief §2 keeps OUT of the dashboard permission axis entirely — there is
21
+ * deliberately no `manage_seeds` permission to map onto. So the question this guard
22
+ * answers is not "which seed?" but "may this account hand platform authority to a
23
+ * client at all?", and the marker for that is holding `manage_users` at
24
+ * {@link GLOBAL_SCOPE} — the same SuperAdmin marker the last-global-admin guard uses.
25
+ *
26
+ * A seed-scoped collaborator therefore cannot mint an MCP token, no matter which
27
+ * scopes the client requests. Denial is all-or-nothing: there is no partial grant.
28
+ */
29
+ export class PermissionRoleGuard {
30
+ async arbitrate(effective, requestedScopes) {
31
+ if (!effective.global.has('manage_users')) {
32
+ return { grantedScopes: [], deniedScopes: [...requestedScopes] };
33
+ }
12
34
  return { grantedScopes: [...requestedScopes], deniedScopes: [] };
13
35
  }
14
36
  }
@@ -27,6 +27,8 @@ export interface IOAuthTokenRepository {
27
27
  save(record: NewOAuthToken): Promise<void>;
28
28
  /** Finds an unexpired, unrevoked token by hash and type. */
29
29
  findActiveByHash(tokenHash: string, tokenType: OAuthTokenType, nowTimestamp: number): Promise<OAuthTokenRecord | null>;
30
+ /** Finds a token by hash and type in any state (expired or revoked included). */
31
+ findByHash(tokenHash: string, tokenType: OAuthTokenType): Promise<OAuthTokenRecord | null>;
30
32
  /** Revokes a single token. False when already revoked or absent. */
31
33
  revokeByHash(tokenHash: string, nowTimestamp: number): Promise<boolean>;
32
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"token.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/token.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEjD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,cAAc,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,uFAAuF;IACvF,qBAAqB,EAAE,MAAM,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1C,4DAA4D;IAC5D,gBAAgB,CACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,cAAc,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAEnC,oEAAoE;IACpE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE;;;;OAIG;IACH,yBAAyB,CAAC,qBAAqB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE/F;;;OAGG;IACH,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAElG,qFAAqF;IACrF,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAA;CACvG"}
1
+ {"version":3,"file":"token.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/token.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEjD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,cAAc,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,uFAAuF;IACvF,qBAAqB,EAAE,MAAM,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1C,4DAA4D;IAC5D,gBAAgB,CACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,cAAc,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAEnC,iFAAiF;IACjF,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAE1F,oEAAoE;IACpE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE;;;;OAIG;IACH,yBAAyB,CAAC,qBAAqB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE/F;;;OAGG;IACH,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAElG,qFAAqF;IACrF,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAA;CACvG"}
@@ -0,0 +1,10 @@
1
+ import type { Branch } from './types.js';
2
+ export declare function sha256hex(value: string): Promise<string>;
3
+ export declare function verifyHashField(stored: string, candidate: string): Promise<boolean>;
4
+ /**
5
+ * Risolve le policy di un branch applicando i valori di default.
6
+ * Tutta la logica di accesso ai campi deve passare per questa funzione,
7
+ * mai con inline `branch.policies?.x ?? default`.
8
+ */
9
+ export declare function resolvePolicies(branch: Branch): Required<NonNullable<Branch['policies']>>;
10
+ //# sourceMappingURL=policies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9D;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEzF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAgBzF"}
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export async function sha256hex(value) {
4
+ const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
5
+ return Array.from(new Uint8Array(buf))
6
+ .map((b) => b.toString(16).padStart(2, '0'))
7
+ .join('');
8
+ }
9
+ export async function verifyHashField(stored, candidate) {
10
+ return stored === (await sha256hex(candidate));
11
+ }
12
+ /**
13
+ * Risolve le policy di un branch applicando i valori di default.
14
+ * Tutta la logica di accesso ai campi deve passare per questa funzione,
15
+ * mai con inline `branch.policies?.x ?? default`.
16
+ */
17
+ export function resolvePolicies(branch) {
18
+ const privacy = branch.policies?.privacy ?? 'plain';
19
+ // Non-plain privacy implies hidden by default: the CMS hashes/encrypts on write,
20
+ // so returning the stored value would leak the digest to readers.
21
+ const defaultVisibility = privacy !== 'plain' ? 'hidden' : 'full';
22
+ // Repeaters live in a single JSON column — never filterable, sortable, or
23
+ // searchable/facetable in v1, regardless of what's set on the branch.
24
+ const isRepeater = branch.type === 'repeater';
25
+ return {
26
+ privacy,
27
+ visibility: branch.policies?.visibility ?? defaultVisibility,
28
+ search: isRepeater ? false : branch.policies?.search ?? true,
29
+ filter: isRepeater ? false : branch.policies?.filter ?? true,
30
+ sort: isRepeater ? false : branch.policies?.sort ?? true,
31
+ public: branch.policies?.public ?? true,
32
+ };
33
+ }
@@ -12,7 +12,8 @@ export interface QueueMessage<T = unknown> {
12
12
  }
13
13
  /**
14
14
  * Execution context handed to every job. Botanical Invariant: jobs receive the
15
- * engine-mediated repository, NEVER a raw D1Database. `env` is a read-only,
15
+ * engine-mediated repository, NEVER a raw D1Database, and the engine-mediated
16
+ * queue port, NEVER a raw transport binding. `env` is a read-only,
16
17
  * stringly-typed view of bindings/secrets for things like fetch targets — it
17
18
  * deliberately does not expose `DB`.
18
19
  */
@@ -21,6 +22,13 @@ export interface JobContext {
21
22
  bucket: BeechBucket;
22
23
  clock: IClock;
23
24
  idGenerator: IIdGenerator;
25
+ /**
26
+ * Producer port, so a job can schedule its own successor. Required for cursor-style
27
+ * jobs that process a bounded slice per invocation, persist their offset and re-enqueue
28
+ * (bulk import): without it a handler would have to reach into `env` for the raw QUEUE
29
+ * binding, which is exactly the transport coupling `repository` exists to prevent for D1.
30
+ */
31
+ queue: IQueueService;
24
32
  env: Record<string, string | undefined>;
25
33
  }
26
34
  /** A single background worker. Receives the decoded payload + context. */
@@ -1 +1 @@
1
- {"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../../src/queue/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACxC;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CACpC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,UAAU,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACvD"}
1
+ {"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../../src/queue/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAA;IACpB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACxC;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CACpC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,UAAU,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACvD"}
@@ -1,5 +1,5 @@
1
1
  import type { IQueueService } from './queue.interface.js';
2
- /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
2
+ /** Drops messages without a transport. */
3
3
  export declare class NoOpQueueService implements IQueueService {
4
4
  enqueue<T>(_name: string, _payload: T): Promise<boolean>;
5
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../../src/queue/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAI/D"}
1
+ {"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../../src/queue/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0CAA0C;AAC1C,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAG/D"}
@@ -1,9 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
- /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
3
+ /** Drops messages without a transport. */
4
4
  export class NoOpQueueService {
5
5
  async enqueue(_name, _payload) {
6
- // intentionally empty; reports success since there is nothing to fail
7
- return true;
6
+ return false;
8
7
  }
9
8
  }
@@ -0,0 +1,39 @@
1
+ import type { ContentRepository } from './content.repository.js';
2
+ import type { BeechBucket } from './storage.js';
3
+ import type { IClock } from './clock.js';
4
+ import type { IIdGenerator } from './id-generator.js';
5
+ /**
6
+ * The envelope put on the wire / queue. `name` selects the handler from the
7
+ * JobRegistry; `payload` is the developer-supplied, JSON-serializable body.
8
+ */
9
+ export interface QueueMessage<T = unknown> {
10
+ name: string;
11
+ payload: T;
12
+ }
13
+ /**
14
+ * Execution context handed to every job. Botanical Invariant: jobs receive the
15
+ * engine-mediated repository, NEVER a raw D1Database. `env` is a read-only,
16
+ * stringly-typed view of bindings/secrets for things like fetch targets — it
17
+ * deliberately does not expose `DB`.
18
+ */
19
+ export interface JobContext {
20
+ repository: ContentRepository;
21
+ bucket: BeechBucket;
22
+ clock: IClock;
23
+ idGenerator: IIdGenerator;
24
+ env: Record<string, string | undefined>;
25
+ }
26
+ /** A single background worker. Receives the decoded payload + context. */
27
+ export type JobHandler<T = unknown> = (payload: T, context: JobContext) => Promise<void>;
28
+ /** Name -> handler map, supplied via BeechConfig.jobs. */
29
+ export type JobRegistry = Record<string, JobHandler<any>>;
30
+ /**
31
+ * Producer port. `enqueue` schedules deferred work and returns once the message
32
+ * is accepted by the transport. Like INotificationService, implementations MUST
33
+ * decide their own fire-and-forget vs inline semantics and MUST NOT let a
34
+ * transport failure crash the request that called enqueue.
35
+ */
36
+ export interface IQueueService {
37
+ enqueue<T>(name: string, payload: T): Promise<void>;
38
+ }
39
+ //# sourceMappingURL=queue.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../src/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACxC;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CACpC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,UAAU,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpD"}
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { IQueueService } from './queue.interface.js';
2
+ /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
3
+ export declare class NoOpQueueService implements IQueueService {
4
+ enqueue<T>(_name: string, _payload: T): Promise<void>;
5
+ }
6
+ //# sourceMappingURL=queue.stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../src/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5D"}
@@ -0,0 +1,8 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
4
+ export class NoOpQueueService {
5
+ async enqueue(_name, _payload) {
6
+ // intentionally empty
7
+ }
8
+ }
@@ -5,6 +5,8 @@ export interface TokenBucketOptions {
5
5
  refillRatePerSecond?: number;
6
6
  clock?: IClock;
7
7
  maxIdleTimeSeconds?: number;
8
+ maxBuckets?: number;
9
+ pruneIntervalSeconds?: number;
8
10
  }
9
11
  export declare class TokenBucketRateLimiter implements IRateLimiter {
10
12
  private readonly buckets;
@@ -12,8 +14,13 @@ export declare class TokenBucketRateLimiter implements IRateLimiter {
12
14
  private readonly refillRatePerSecond;
13
15
  private readonly clock;
14
16
  private readonly maxIdleTimeSeconds;
17
+ private readonly maxBuckets;
18
+ private readonly pruneIntervalSeconds;
19
+ private lastPruneTimestamp;
15
20
  constructor(options?: TokenBucketOptions);
21
+ private static readonly MAX_EVICTION_SCAN;
16
22
  private pruneExpiredBuckets;
23
+ private evictRefilledBucket;
17
24
  checkLimit(key: string): Promise<RateLimitResult>;
18
25
  reset(): void;
19
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token-bucket-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/token-bucket-rate-limiter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAQhD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;gBAE/B,OAAO,CAAC,EAAE,kBAAkB;IAQxC,OAAO,CAAC,mBAAmB;IASrB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAsCvD,KAAK,IAAI,IAAI;CAGd"}
1
+ {"version":3,"file":"token-bucket-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/token-bucket-rate-limiter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAQhD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IAC7C,OAAO,CAAC,kBAAkB,CAAI;gBAElB,OAAO,CAAC,EAAE,kBAAkB;IAcxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAK;IAE9C,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,mBAAmB;IAsBrB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAqDvD,KAAK,IAAI,IAAI;CAId"}
@@ -7,27 +7,90 @@ export class TokenBucketRateLimiter {
7
7
  refillRatePerSecond;
8
8
  clock;
9
9
  maxIdleTimeSeconds;
10
+ maxBuckets;
11
+ pruneIntervalSeconds;
12
+ lastPruneTimestamp = 0;
10
13
  constructor(options) {
11
14
  this.capacity = options?.capacity ?? 17;
12
15
  // Default: 1 token every 3.53 seconds (~0.283286 tokens/sec)
13
16
  this.refillRatePerSecond = options?.refillRatePerSecond ?? (1 / 3.53);
14
17
  this.clock = options?.clock ?? SystemClock;
15
18
  this.maxIdleTimeSeconds = options?.maxIdleTimeSeconds ?? 3600; // 1 hour idle TTL
19
+ this.maxBuckets = options?.maxBuckets ?? 5000; // Prevent unbounded memory growth
20
+ this.pruneIntervalSeconds = options?.pruneIntervalSeconds ?? 60; // Throttle O(n) scans
16
21
  }
22
+ // Bound the eviction scan so a flood of unique keys (each forcing an eviction attempt)
23
+ // cannot turn checkLimit into an O(maxBuckets) operation per request. Only the oldest
24
+ // slice of the map is examined; if nothing there qualifies, admission of the new key is
25
+ // denied instead of evicting something it shouldn't (see below).
26
+ static MAX_EVICTION_SCAN = 32;
17
27
  pruneExpiredBuckets(now) {
18
28
  if (this.buckets.size < 500)
19
29
  return;
30
+ if (now - this.lastPruneTimestamp < this.pruneIntervalSeconds)
31
+ return;
32
+ this.lastPruneTimestamp = now;
20
33
  for (const [key, state] of this.buckets.entries()) {
21
34
  if (now - state.lastRefillTimestamp > this.maxIdleTimeSeconds) {
22
35
  this.buckets.delete(key);
23
36
  }
37
+ else {
38
+ // Since Map preserves insertion order and checkLimit re-inserts on every access,
39
+ // entries are monotonically ordered by lastRefillTimestamp. The first non-expired
40
+ // entry guarantees all subsequent entries are also active, allowing an immediate O(1) exit.
41
+ break;
42
+ }
24
43
  }
25
44
  }
45
+ // Evicts the oldest bucket ONLY if it has refilled to full capacity, in which case it is
46
+ // behaviourally equivalent to an absent bucket and dropping it loses no state. A bucket
47
+ // that hasn't refilled — including one sitting at 0 tokens and actively blocking its key —
48
+ // is never evicted: doing so would let an attacker flood distinct keys to silently reset a
49
+ // victim's exhausted bucket and bypass its limit (#458).
50
+ //
51
+ // When nothing in the scanned slice is eligible, the caller must not admit a new bucket
52
+ // anyway (#634) — that would make the map grow without bound under a distinct-key flood.
53
+ // Instead, report how long until the earliest-refilling scanned bucket would become a
54
+ // legitimate eviction candidate, so the caller can deny admission with a meaningful
55
+ // retryAfterSeconds rather than guessing.
56
+ evictRefilledBucket(now) {
57
+ let scanned = 0;
58
+ let minSecondsUntilRefilled = this.maxIdleTimeSeconds;
59
+ for (const [key, state] of this.buckets.entries()) {
60
+ if (scanned >= TokenBucketRateLimiter.MAX_EVICTION_SCAN)
61
+ break;
62
+ scanned++;
63
+ const elapsed = Math.max(0, now - state.lastRefillTimestamp);
64
+ const refilled = Math.min(this.capacity, state.tokens + elapsed * this.refillRatePerSecond);
65
+ if (refilled >= this.capacity) {
66
+ this.buckets.delete(key);
67
+ return { evicted: true, retryAfterSeconds: 0 };
68
+ }
69
+ if (this.refillRatePerSecond > 0) {
70
+ const secondsUntilRefilled = (this.capacity - refilled) / this.refillRatePerSecond;
71
+ minSecondsUntilRefilled = Math.min(minSecondsUntilRefilled, secondsUntilRefilled);
72
+ }
73
+ }
74
+ return { evicted: false, retryAfterSeconds: Math.max(1, Math.ceil(minSecondsUntilRefilled)) };
75
+ }
26
76
  async checkLimit(key) {
27
77
  const now = this.clock.now() / 1000; // fractional seconds for continuous refill
28
78
  this.pruneExpiredBuckets(now);
29
79
  let bucket = this.buckets.get(key);
30
80
  if (!bucket) {
81
+ if (this.buckets.size >= this.maxBuckets) {
82
+ const { evicted, retryAfterSeconds } = this.evictRefilledBucket(now);
83
+ if (!evicted) {
84
+ // No safe eviction candidate: admitting a bucket for this never-seen key would
85
+ // grow the map past maxBuckets. Deny it instead of allocating state (#634).
86
+ return {
87
+ isAllowed: false,
88
+ retryAfterSeconds,
89
+ limit: this.capacity,
90
+ remaining: 0,
91
+ };
92
+ }
93
+ }
31
94
  bucket = {
32
95
  tokens: this.capacity,
33
96
  lastRefillTimestamp: now,
@@ -37,6 +100,8 @@ export class TokenBucketRateLimiter {
37
100
  const elapsed = Math.max(0, now - bucket.lastRefillTimestamp);
38
101
  bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsed * this.refillRatePerSecond);
39
102
  bucket.lastRefillTimestamp = now;
103
+ // Re-insert to maintain LRU access order
104
+ this.buckets.delete(key);
40
105
  }
41
106
  if (bucket.tokens >= 1) {
42
107
  bucket.tokens -= 1;
@@ -59,5 +124,6 @@ export class TokenBucketRateLimiter {
59
124
  }
60
125
  reset() {
61
126
  this.buckets.clear();
127
+ this.lastPruneTimestamp = 0;
62
128
  }
63
129
  }
@@ -0,0 +1,57 @@
1
+ import { type Permission, type Scope } from './permissions.js';
2
+ import type { EffectivePermissions, PermissionAssignment, RoleRecord } from './types.js';
3
+ /**
4
+ * Folds a user's active assignments into their effective authority.
5
+ *
6
+ * The model is strictly additive: every assignment can only widen the result, and
7
+ * assignments referencing an unknown role are ignored rather than treated as an error.
8
+ *
9
+ * @param assignments - Active assignments, already decay-filtered by the repository.
10
+ * @param roles - The roles those assignments reference; extra roles are harmless.
11
+ */
12
+ export declare function buildEffectivePermissions(assignments: readonly PermissionAssignment[], roles: readonly RoleRecord[]): EffectivePermissions;
13
+ /**
14
+ * Returns everything the caller may do within one scope: the union of their global
15
+ * permissions and the permissions granted on that specific seed.
16
+ */
17
+ export declare function permissionsForScope(effective: EffectivePermissions, scope: Scope): ReadonlySet<Permission>;
18
+ /**
19
+ * The single authorization question: may this caller perform `permission` on `scope`?
20
+ *
21
+ * A global grant satisfies any scope. A scoped grant satisfies only its own seed and
22
+ * never {@link GLOBAL_SCOPE} — that asymmetry is what stops horizontal escalation.
23
+ */
24
+ export declare function hasPermission(effective: EffectivePermissions, permission: Permission, scope: Scope): boolean;
25
+ /**
26
+ * The anti-escalation rule: an actor may only hand out authority they already hold.
27
+ *
28
+ * A grant is legal only when the actor holds every permission being granted, at the
29
+ * scope it is being granted on. Consequently an actor scoped to seed X can never mint
30
+ * a global assignment, and can never include a permission absent from their own set —
31
+ * regardless of what the role itself contains.
32
+ *
33
+ * @param actor - The granting user's effective authority.
34
+ * @param targetScope - The scope the new assignment would apply to.
35
+ * @param targetPermissions - The permissions carried by the role being assigned.
36
+ */
37
+ export declare function canGrant(actor: EffectivePermissions, targetScope: Scope, targetPermissions: readonly Permission[]): boolean;
38
+ /**
39
+ * Every permission the actor holds at ANY scope — global or seed-scoped — folded into
40
+ * one set.
41
+ *
42
+ * This is deliberately NOT an authorization primitive: it answers "could this actor
43
+ * ever grant X somewhere", not "may this actor do X here". Only {@link hasPermission}
44
+ * and {@link canGrant} answer the latter, and every route-level decision must keep
45
+ * using them.
46
+ */
47
+ export declare function permissionsHeldAnywhere(effective: EffectivePermissions): ReadonlySet<Permission>;
48
+ /**
49
+ * Whether the actor holds `permission` on at least one scope.
50
+ *
51
+ * Backs the coarse route gate for administration endpoints whose scope is not in the
52
+ * URL: the gate keeps out callers with no administrative authority at all, and the
53
+ * slice then makes the exact per-scope decision with {@link hasPermission} /
54
+ * {@link canGrant}. Never use it as the final authorization check.
55
+ */
56
+ export declare function hasPermissionAnywhere(effective: EffectivePermissions, permission: Permission): boolean;
57
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/rbac/evaluate.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAExF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,SAAS,oBAAoB,EAAE,EAC5C,KAAK,EAAE,SAAS,UAAU,EAAE,GAC3B,oBAAoB,CAuBtB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,oBAAoB,EAC/B,KAAK,EAAE,KAAK,GACX,WAAW,CAAC,UAAU,CAAC,CAKzB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,oBAAoB,EAC/B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,KAAK,GACX,OAAO,CAIT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,oBAAoB,EAC3B,WAAW,EAAE,KAAK,EAClB,iBAAiB,EAAE,SAAS,UAAU,EAAE,GACvC,OAAO,CAMT;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,oBAAoB,GAAG,WAAW,CAAC,UAAU,CAAC,CAMhG;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,oBAAoB,EAC/B,UAAU,EAAE,UAAU,GACrB,OAAO,CAMT"}