@classytic/arc 1.1.0 → 2.1.2

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 (322) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
  4. package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
  5. package/dist/HookSystem-BsGV-j2l.mjs +405 -0
  6. package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
  7. package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
  8. package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
  9. package/dist/adapters/index.d.mts +5 -0
  10. package/dist/adapters/index.mjs +3 -0
  11. package/dist/audit/index.d.mts +82 -0
  12. package/dist/audit/index.d.mts.map +1 -0
  13. package/dist/audit/index.mjs +276 -0
  14. package/dist/audit/index.mjs.map +1 -0
  15. package/dist/audit/mongodb.d.mts +5 -0
  16. package/dist/audit/mongodb.mjs +3 -0
  17. package/dist/audited-C3T5DTUx.mjs +141 -0
  18. package/dist/audited-C3T5DTUx.mjs.map +1 -0
  19. package/dist/auth/index.d.mts +189 -0
  20. package/dist/auth/index.d.mts.map +1 -0
  21. package/dist/auth/index.mjs +1102 -0
  22. package/dist/auth/index.mjs.map +1 -0
  23. package/dist/auth/redis-session.d.mts +44 -0
  24. package/dist/auth/redis-session.d.mts.map +1 -0
  25. package/dist/auth/redis-session.mjs +76 -0
  26. package/dist/auth/redis-session.mjs.map +1 -0
  27. package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
  28. package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
  29. package/dist/cache/index.d.mts +146 -0
  30. package/dist/cache/index.d.mts.map +1 -0
  31. package/dist/cache/index.mjs +92 -0
  32. package/dist/cache/index.mjs.map +1 -0
  33. package/dist/caching-Bl28lYsR.mjs +94 -0
  34. package/dist/caching-Bl28lYsR.mjs.map +1 -0
  35. package/dist/chunk-C7Uep-_p.mjs +20 -0
  36. package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
  37. package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
  38. package/dist/cli/commands/describe.d.mts +19 -0
  39. package/dist/cli/commands/describe.d.mts.map +1 -0
  40. package/dist/cli/commands/describe.mjs +239 -0
  41. package/dist/cli/commands/describe.mjs.map +1 -0
  42. package/dist/cli/commands/docs.d.mts +14 -0
  43. package/dist/cli/commands/docs.d.mts.map +1 -0
  44. package/dist/cli/commands/docs.mjs +53 -0
  45. package/dist/cli/commands/docs.mjs.map +1 -0
  46. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
  47. package/dist/cli/commands/generate.d.mts.map +1 -0
  48. package/dist/cli/commands/generate.mjs +358 -0
  49. package/dist/cli/commands/generate.mjs.map +1 -0
  50. package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
  51. package/dist/cli/commands/init.d.mts.map +1 -0
  52. package/dist/cli/commands/{init.js → init.mjs} +807 -616
  53. package/dist/cli/commands/init.mjs.map +1 -0
  54. package/dist/cli/commands/introspect.d.mts +11 -0
  55. package/dist/cli/commands/introspect.d.mts.map +1 -0
  56. package/dist/cli/commands/introspect.mjs +76 -0
  57. package/dist/cli/commands/introspect.mjs.map +1 -0
  58. package/dist/cli/index.d.mts +17 -0
  59. package/dist/cli/index.d.mts.map +1 -0
  60. package/dist/cli/index.mjs +157 -0
  61. package/dist/cli/index.mjs.map +1 -0
  62. package/dist/constants-DdXFXQtN.mjs +85 -0
  63. package/dist/constants-DdXFXQtN.mjs.map +1 -0
  64. package/dist/core/index.d.mts +5 -0
  65. package/dist/core/index.mjs +4 -0
  66. package/dist/createApp-CUgNqegw.mjs +560 -0
  67. package/dist/createApp-CUgNqegw.mjs.map +1 -0
  68. package/dist/defineResource-k0_BDn8v.mjs +2197 -0
  69. package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
  70. package/dist/discovery/index.d.mts +47 -0
  71. package/dist/discovery/index.d.mts.map +1 -0
  72. package/dist/discovery/index.mjs +110 -0
  73. package/dist/discovery/index.mjs.map +1 -0
  74. package/dist/docs/index.d.mts +163 -0
  75. package/dist/docs/index.d.mts.map +1 -0
  76. package/dist/docs/index.mjs +73 -0
  77. package/dist/docs/index.mjs.map +1 -0
  78. package/dist/elevation-BRy3yFWT.mjs +113 -0
  79. package/dist/elevation-BRy3yFWT.mjs.map +1 -0
  80. package/dist/elevation-B_2dRLVP.d.mts +88 -0
  81. package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
  82. package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
  83. package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
  84. package/dist/errorHandler-C1okiriz.mjs +109 -0
  85. package/dist/errorHandler-C1okiriz.mjs.map +1 -0
  86. package/dist/errors-B9bZok84.mjs +212 -0
  87. package/dist/errors-B9bZok84.mjs.map +1 -0
  88. package/dist/errors-ChKiFz62.d.mts +125 -0
  89. package/dist/errors-ChKiFz62.d.mts.map +1 -0
  90. package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
  91. package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
  92. package/dist/eventPlugin-DGR_B2on.mjs +230 -0
  93. package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
  94. package/dist/events/index.d.mts +54 -0
  95. package/dist/events/index.d.mts.map +1 -0
  96. package/dist/events/index.mjs +52 -0
  97. package/dist/events/index.mjs.map +1 -0
  98. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  99. package/dist/events/transports/redis-stream-entry.mjs +178 -0
  100. package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
  101. package/dist/events/transports/redis.d.mts +77 -0
  102. package/dist/events/transports/redis.d.mts.map +1 -0
  103. package/dist/events/transports/redis.mjs +125 -0
  104. package/dist/events/transports/redis.mjs.map +1 -0
  105. package/dist/externalPaths-DlINfKbP.d.mts +51 -0
  106. package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
  107. package/dist/factory/index.d.mts +64 -0
  108. package/dist/factory/index.d.mts.map +1 -0
  109. package/dist/factory/index.mjs +3 -0
  110. package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
  111. package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
  112. package/dist/fields-DyaDVX4J.d.mts +110 -0
  113. package/dist/fields-DyaDVX4J.d.mts.map +1 -0
  114. package/dist/fields-iagOozy0.mjs +115 -0
  115. package/dist/fields-iagOozy0.mjs.map +1 -0
  116. package/dist/hooks/index.d.mts +4 -0
  117. package/dist/hooks/index.mjs +3 -0
  118. package/dist/idempotency/index.d.mts +97 -0
  119. package/dist/idempotency/index.d.mts.map +1 -0
  120. package/dist/idempotency/index.mjs +320 -0
  121. package/dist/idempotency/index.mjs.map +1 -0
  122. package/dist/idempotency/mongodb.d.mts +2 -0
  123. package/dist/idempotency/mongodb.mjs +115 -0
  124. package/dist/idempotency/mongodb.mjs.map +1 -0
  125. package/dist/idempotency/redis.d.mts +2 -0
  126. package/dist/idempotency/redis.mjs +104 -0
  127. package/dist/idempotency/redis.mjs.map +1 -0
  128. package/dist/index.d.mts +261 -0
  129. package/dist/index.d.mts.map +1 -0
  130. package/dist/index.mjs +105 -0
  131. package/dist/index.mjs.map +1 -0
  132. package/dist/integrations/event-gateway.d.mts +47 -0
  133. package/dist/integrations/event-gateway.d.mts.map +1 -0
  134. package/dist/integrations/event-gateway.mjs +44 -0
  135. package/dist/integrations/event-gateway.mjs.map +1 -0
  136. package/dist/integrations/index.d.mts +5 -0
  137. package/dist/integrations/index.mjs +1 -0
  138. package/dist/integrations/jobs.d.mts +104 -0
  139. package/dist/integrations/jobs.d.mts.map +1 -0
  140. package/dist/integrations/jobs.mjs +124 -0
  141. package/dist/integrations/jobs.mjs.map +1 -0
  142. package/dist/integrations/streamline.d.mts +61 -0
  143. package/dist/integrations/streamline.d.mts.map +1 -0
  144. package/dist/integrations/streamline.mjs +126 -0
  145. package/dist/integrations/streamline.mjs.map +1 -0
  146. package/dist/integrations/websocket.d.mts +83 -0
  147. package/dist/integrations/websocket.d.mts.map +1 -0
  148. package/dist/integrations/websocket.mjs +289 -0
  149. package/dist/integrations/websocket.mjs.map +1 -0
  150. package/dist/interface-B01JvPVc.d.mts +78 -0
  151. package/dist/interface-B01JvPVc.d.mts.map +1 -0
  152. package/dist/interface-CZe8IkMf.d.mts +55 -0
  153. package/dist/interface-CZe8IkMf.d.mts.map +1 -0
  154. package/dist/interface-Ch8HU9uM.d.mts +1098 -0
  155. package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
  156. package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
  157. package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
  158. package/dist/keys-BqNejWup.mjs +43 -0
  159. package/dist/keys-BqNejWup.mjs.map +1 -0
  160. package/dist/logger-Df2O2WsW.mjs +79 -0
  161. package/dist/logger-Df2O2WsW.mjs.map +1 -0
  162. package/dist/memory-cQgelFOj.mjs +144 -0
  163. package/dist/memory-cQgelFOj.mjs.map +1 -0
  164. package/dist/migrations/index.d.mts +157 -0
  165. package/dist/migrations/index.d.mts.map +1 -0
  166. package/dist/migrations/index.mjs +261 -0
  167. package/dist/migrations/index.mjs.map +1 -0
  168. package/dist/mongodb-BfJVlUJH.mjs +94 -0
  169. package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
  170. package/dist/mongodb-CGzRbfAK.d.mts +119 -0
  171. package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
  172. package/dist/mongodb-JN-9JA7K.d.mts +72 -0
  173. package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
  174. package/dist/openapi-G3Cw7XuM.mjs +524 -0
  175. package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
  176. package/dist/org/index.d.mts +69 -0
  177. package/dist/org/index.d.mts.map +1 -0
  178. package/dist/org/index.mjs +514 -0
  179. package/dist/org/index.mjs.map +1 -0
  180. package/dist/org/types.d.mts +83 -0
  181. package/dist/org/types.d.mts.map +1 -0
  182. package/dist/org/types.mjs +1 -0
  183. package/dist/permissions/index.d.mts +279 -0
  184. package/dist/permissions/index.d.mts.map +1 -0
  185. package/dist/permissions/index.mjs +579 -0
  186. package/dist/permissions/index.mjs.map +1 -0
  187. package/dist/plugins/index.d.mts +173 -0
  188. package/dist/plugins/index.d.mts.map +1 -0
  189. package/dist/plugins/index.mjs +523 -0
  190. package/dist/plugins/index.mjs.map +1 -0
  191. package/dist/plugins/response-cache.d.mts +88 -0
  192. package/dist/plugins/response-cache.d.mts.map +1 -0
  193. package/dist/plugins/response-cache.mjs +284 -0
  194. package/dist/plugins/response-cache.mjs.map +1 -0
  195. package/dist/plugins/tracing-entry.d.mts +2 -0
  196. package/dist/plugins/tracing-entry.mjs +186 -0
  197. package/dist/plugins/tracing-entry.mjs.map +1 -0
  198. package/dist/pluralize-CEweyOEm.mjs +87 -0
  199. package/dist/pluralize-CEweyOEm.mjs.map +1 -0
  200. package/dist/policies/{index.d.ts → index.d.mts} +204 -169
  201. package/dist/policies/index.d.mts.map +1 -0
  202. package/dist/policies/index.mjs +322 -0
  203. package/dist/policies/index.mjs.map +1 -0
  204. package/dist/presets/{index.d.ts → index.d.mts} +63 -131
  205. package/dist/presets/index.d.mts.map +1 -0
  206. package/dist/presets/index.mjs +144 -0
  207. package/dist/presets/index.mjs.map +1 -0
  208. package/dist/presets/multiTenant.d.mts +25 -0
  209. package/dist/presets/multiTenant.d.mts.map +1 -0
  210. package/dist/presets/multiTenant.mjs +114 -0
  211. package/dist/presets/multiTenant.mjs.map +1 -0
  212. package/dist/presets-BITljm96.mjs +120 -0
  213. package/dist/presets-BITljm96.mjs.map +1 -0
  214. package/dist/presets-DzSMwlKj.d.mts +58 -0
  215. package/dist/presets-DzSMwlKj.d.mts.map +1 -0
  216. package/dist/prisma-DJbMt3yf.mjs +628 -0
  217. package/dist/prisma-DJbMt3yf.mjs.map +1 -0
  218. package/dist/prisma-Dg9GoVdj.d.mts +275 -0
  219. package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
  220. package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
  221. package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
  222. package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
  223. package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
  224. package/dist/redis-D-JAeLtm.d.mts +50 -0
  225. package/dist/redis-D-JAeLtm.d.mts.map +1 -0
  226. package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
  227. package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
  228. package/dist/registry/index.d.mts +12 -0
  229. package/dist/registry/index.d.mts.map +1 -0
  230. package/dist/registry/index.mjs +4 -0
  231. package/dist/requestContext-QQD6ROJc.mjs +56 -0
  232. package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
  233. package/dist/schemaConverter-BwrmWroW.mjs +99 -0
  234. package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
  235. package/dist/schemas/index.d.mts +64 -0
  236. package/dist/schemas/index.d.mts.map +1 -0
  237. package/dist/schemas/index.mjs +83 -0
  238. package/dist/schemas/index.mjs.map +1 -0
  239. package/dist/scope/index.d.mts +22 -0
  240. package/dist/scope/index.d.mts.map +1 -0
  241. package/dist/scope/index.mjs +66 -0
  242. package/dist/scope/index.mjs.map +1 -0
  243. package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
  244. package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
  245. package/dist/sse-B3c3_yZp.mjs +124 -0
  246. package/dist/sse-B3c3_yZp.mjs.map +1 -0
  247. package/dist/testing/index.d.mts +908 -0
  248. package/dist/testing/index.d.mts.map +1 -0
  249. package/dist/testing/index.mjs +1977 -0
  250. package/dist/testing/index.mjs.map +1 -0
  251. package/dist/tracing-Cc7vVQPp.d.mts +71 -0
  252. package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
  253. package/dist/typeGuards-DhMNLuvU.mjs +10 -0
  254. package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
  255. package/dist/types/index.d.mts +947 -0
  256. package/dist/types/index.d.mts.map +1 -0
  257. package/dist/types/index.mjs +15 -0
  258. package/dist/types/index.mjs.map +1 -0
  259. package/dist/types-Beqn1Un7.mjs +39 -0
  260. package/dist/types-Beqn1Un7.mjs.map +1 -0
  261. package/dist/types-CIgB7UUl.d.mts +446 -0
  262. package/dist/types-CIgB7UUl.d.mts.map +1 -0
  263. package/dist/types-aYB4V7uN.d.mts +87 -0
  264. package/dist/types-aYB4V7uN.d.mts.map +1 -0
  265. package/dist/utils/index.d.mts +748 -0
  266. package/dist/utils/index.d.mts.map +1 -0
  267. package/dist/utils/index.mjs +6 -0
  268. package/package.json +194 -68
  269. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  270. package/dist/adapters/index.d.ts +0 -237
  271. package/dist/adapters/index.js +0 -668
  272. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  273. package/dist/audit/index.d.ts +0 -195
  274. package/dist/audit/index.js +0 -319
  275. package/dist/auth/index.d.ts +0 -47
  276. package/dist/auth/index.js +0 -174
  277. package/dist/cli/commands/docs.d.ts +0 -11
  278. package/dist/cli/commands/docs.js +0 -474
  279. package/dist/cli/commands/generate.js +0 -334
  280. package/dist/cli/commands/introspect.d.ts +0 -8
  281. package/dist/cli/commands/introspect.js +0 -338
  282. package/dist/cli/index.d.ts +0 -4
  283. package/dist/cli/index.js +0 -3269
  284. package/dist/core/index.d.ts +0 -220
  285. package/dist/core/index.js +0 -2786
  286. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  287. package/dist/docs/index.d.ts +0 -166
  288. package/dist/docs/index.js +0 -658
  289. package/dist/errors-8WIxGS_6.d.ts +0 -122
  290. package/dist/events/index.d.ts +0 -117
  291. package/dist/events/index.js +0 -89
  292. package/dist/factory/index.d.ts +0 -38
  293. package/dist/factory/index.js +0 -1652
  294. package/dist/hooks/index.d.ts +0 -4
  295. package/dist/hooks/index.js +0 -199
  296. package/dist/idempotency/index.d.ts +0 -323
  297. package/dist/idempotency/index.js +0 -500
  298. package/dist/index-B4t03KQ0.d.ts +0 -1366
  299. package/dist/index.d.ts +0 -135
  300. package/dist/index.js +0 -4756
  301. package/dist/migrations/index.d.ts +0 -185
  302. package/dist/migrations/index.js +0 -274
  303. package/dist/org/index.d.ts +0 -129
  304. package/dist/org/index.js +0 -220
  305. package/dist/permissions/index.d.ts +0 -144
  306. package/dist/permissions/index.js +0 -103
  307. package/dist/plugins/index.d.ts +0 -46
  308. package/dist/plugins/index.js +0 -1069
  309. package/dist/policies/index.js +0 -196
  310. package/dist/presets/index.js +0 -384
  311. package/dist/presets/multiTenant.d.ts +0 -39
  312. package/dist/presets/multiTenant.js +0 -112
  313. package/dist/registry/index.d.ts +0 -16
  314. package/dist/registry/index.js +0 -253
  315. package/dist/testing/index.d.ts +0 -618
  316. package/dist/testing/index.js +0 -48020
  317. package/dist/types/index.d.ts +0 -4
  318. package/dist/types/index.js +0 -8
  319. package/dist/types-B99TBmFV.d.ts +0 -76
  320. package/dist/types-BvckRbs2.d.ts +0 -143
  321. package/dist/utils/index.d.ts +0 -679
  322. package/dist/utils/index.js +0 -931
@@ -0,0 +1,322 @@
1
+ //#region src/policies/PolicyInterface.ts
2
+ /**
3
+ * Create a PermissionCheck from access control statements.
4
+ *
5
+ * Maps Better Auth's statement-based access control model to Arc's
6
+ * PermissionCheck function, which can be used directly in resource permissions.
7
+ *
8
+ * The returned PermissionCheck:
9
+ * 1. Looks up the resource + action in the statements list
10
+ * 2. If no matching statement exists, denies access
11
+ * 3. If a matching statement exists and `checkPermission` is provided,
12
+ * calls it for dynamic verification (e.g., check org role)
13
+ * 4. If `checkPermission` is not provided, allows access based on static statements
14
+ *
15
+ * @example Static statements only
16
+ * ```typescript
17
+ * import { createAccessControlPolicy } from '@classytic/arc/policies';
18
+ *
19
+ * const editorPermissions = createAccessControlPolicy({
20
+ * statements: [
21
+ * { resource: 'product', action: ['create', 'update'] },
22
+ * { resource: 'order', action: ['read'] },
23
+ * ],
24
+ * });
25
+ *
26
+ * // Use in resource config
27
+ * defineResource({
28
+ * name: 'product',
29
+ * permissions: {
30
+ * create: editorPermissions,
31
+ * update: editorPermissions,
32
+ * },
33
+ * });
34
+ * ```
35
+ *
36
+ * @example With dynamic permission check (Better Auth org roles)
37
+ * ```typescript
38
+ * const policy = createAccessControlPolicy({
39
+ * statements: [
40
+ * { resource: 'product', action: ['create', 'update'] },
41
+ * { resource: 'order', action: ['read'] },
42
+ * ],
43
+ * checkPermission: async (userId, resource, action) => {
44
+ * return hasOrgPermission(userId, resource, action);
45
+ * },
46
+ * });
47
+ * ```
48
+ */
49
+ function createAccessControlPolicy(options) {
50
+ const statementMap = /* @__PURE__ */ new Map();
51
+ for (const statement of options.statements) {
52
+ const existing = statementMap.get(statement.resource);
53
+ if (existing) for (const action of statement.action) existing.add(action);
54
+ else statementMap.set(statement.resource, new Set(statement.action));
55
+ }
56
+ const permissionCheck = async (context) => {
57
+ const { user, resource, action } = context;
58
+ const allowedActions = statementMap.get(resource);
59
+ if (!allowedActions || !allowedActions.has(action)) return {
60
+ granted: false,
61
+ reason: `Action '${action}' is not permitted on resource '${resource}'`
62
+ };
63
+ if (options.checkPermission) {
64
+ const userId = user?.id ?? user?._id;
65
+ if (!userId) return {
66
+ granted: false,
67
+ reason: "Authentication required"
68
+ };
69
+ if (!await options.checkPermission(String(userId), resource, action)) return {
70
+ granted: false,
71
+ reason: `User does not have '${action}' permission on '${resource}'`
72
+ };
73
+ }
74
+ return { granted: true };
75
+ };
76
+ return permissionCheck;
77
+ }
78
+
79
+ //#endregion
80
+ //#region src/policies/helpers.ts
81
+ /**
82
+ * Helper to create Fastify middleware from any PolicyEngine implementation
83
+ *
84
+ * This is a convenience function that provides a standard middleware pattern.
85
+ * Most policies can use this instead of implementing toMiddleware() manually.
86
+ *
87
+ * @param policy - Policy engine instance
88
+ * @param operation - Operation name (list, get, create, update, delete)
89
+ * @returns Fastify preHandler middleware
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * class SimplePolicy implements PolicyEngine {
94
+ * can(user, operation) {
95
+ * return { allowed: user.isActive };
96
+ * }
97
+ *
98
+ * toMiddleware(operation) {
99
+ * return createPolicyMiddleware(this, operation);
100
+ * }
101
+ * }
102
+ * ```
103
+ */
104
+ function createPolicyMiddleware(policy, operation) {
105
+ return async function policyMiddleware(request, reply) {
106
+ const context = {
107
+ document: request.document,
108
+ body: request.body,
109
+ params: request.params,
110
+ query: request.query
111
+ };
112
+ const result = await policy.can(request.user, operation, context);
113
+ if (!result.allowed) return reply.code(403).send({
114
+ success: false,
115
+ error: "Access denied",
116
+ message: result.reason || "You do not have permission to perform this action"
117
+ });
118
+ request.policyResult = result;
119
+ if (result.filters && Object.keys(result.filters).length > 0) request._policyFilters = result.filters;
120
+ if (result.fieldMask) request.fieldMask = result.fieldMask;
121
+ if (result.metadata) request.policyMetadata = result.metadata;
122
+ };
123
+ }
124
+ /**
125
+ * Combine multiple policies with AND logic
126
+ *
127
+ * All policies must allow the operation for it to succeed.
128
+ * First denial stops evaluation and returns the denial reason.
129
+ *
130
+ * @param policies - Array of policy engines to combine
131
+ * @returns Combined policy engine
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * const combinedPolicy = combinePolicies(
136
+ * rbacPolicy, // Must have correct role
137
+ * ownershipPolicy, // Must own the resource
138
+ * auditPolicy, // Logs access for compliance
139
+ * );
140
+ *
141
+ * // All three policies must pass for the operation to succeed
142
+ * const result = await combinedPolicy.can(user, 'update', context);
143
+ * ```
144
+ *
145
+ * @example Multi-tenant + RBAC
146
+ * ```typescript
147
+ * const policy = combinePolicies(
148
+ * definePolicy({ tenant: { field: 'organizationId' } }),
149
+ * definePolicy({ roles: { update: ['admin', 'editor'] } }),
150
+ * );
151
+ * ```
152
+ */
153
+ function combinePolicies(...policies) {
154
+ if (policies.length === 0) throw new Error("combinePolicies requires at least one policy");
155
+ if (policies.length === 1) return policies[0];
156
+ return {
157
+ async can(user, operation, context) {
158
+ const results = [];
159
+ for (const policy of policies) {
160
+ const result = await policy.can(user, operation, context);
161
+ if (!result.allowed) return result;
162
+ results.push(result);
163
+ }
164
+ const mergedResult = {
165
+ allowed: true,
166
+ filters: {},
167
+ metadata: {}
168
+ };
169
+ for (const result of results) if (result.filters) Object.assign(mergedResult.filters, result.filters);
170
+ const allExcludes = /* @__PURE__ */ new Set();
171
+ const allIncludes = [];
172
+ for (const result of results) {
173
+ if (result.fieldMask?.exclude) result.fieldMask.exclude.forEach((field) => allExcludes.add(field));
174
+ if (result.fieldMask?.include) allIncludes.push(new Set(result.fieldMask.include));
175
+ }
176
+ if (allExcludes.size > 0 || allIncludes.length > 0) {
177
+ mergedResult.fieldMask = {};
178
+ if (allExcludes.size > 0) mergedResult.fieldMask.exclude = Array.from(allExcludes);
179
+ if (allIncludes.length > 0) {
180
+ const intersection = allIncludes.reduce((acc, set) => {
181
+ return new Set([...acc].filter((x) => set.has(x)));
182
+ });
183
+ if (intersection.size > 0) mergedResult.fieldMask.include = Array.from(intersection);
184
+ }
185
+ }
186
+ for (const result of results) if (result.metadata) Object.assign(mergedResult.metadata, result.metadata);
187
+ if (Object.keys(mergedResult.filters).length === 0) delete mergedResult.filters;
188
+ if (Object.keys(mergedResult.metadata).length === 0) delete mergedResult.metadata;
189
+ return mergedResult;
190
+ },
191
+ toMiddleware(operation) {
192
+ const middlewares = policies.map((p) => p.toMiddleware(operation));
193
+ return async (request, reply) => {
194
+ for (const middleware of middlewares) {
195
+ await middleware(request, reply);
196
+ if (reply.sent) return;
197
+ }
198
+ };
199
+ }
200
+ };
201
+ }
202
+ /**
203
+ * Combine multiple policies with OR logic
204
+ *
205
+ * At least one policy must allow the operation for it to succeed.
206
+ * If all policies deny, returns the first denial reason.
207
+ *
208
+ * @param policies - Array of policy engines to combine
209
+ * @returns Combined policy engine
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * const policy = anyPolicy(
214
+ * ownerPolicy, // User owns the resource
215
+ * adminPolicy, // OR user is admin
216
+ * publicPolicy, // OR resource is public
217
+ * );
218
+ *
219
+ * // Any one of these policies passing allows the operation
220
+ * ```
221
+ */
222
+ function anyPolicy(...policies) {
223
+ if (policies.length === 0) throw new Error("anyPolicy requires at least one policy");
224
+ if (policies.length === 1) return policies[0];
225
+ return {
226
+ async can(user, operation, context) {
227
+ let firstDenial = null;
228
+ for (const policy of policies) {
229
+ const result = await policy.can(user, operation, context);
230
+ if (result.allowed) return result;
231
+ if (!firstDenial) firstDenial = result;
232
+ }
233
+ return firstDenial;
234
+ },
235
+ toMiddleware(operation) {
236
+ return async (request, reply) => {
237
+ const results = [];
238
+ for (const policy of policies) {
239
+ const result = await policy.can(request.user, operation, {
240
+ document: request.document,
241
+ body: request.body,
242
+ params: request.params,
243
+ query: request.query
244
+ });
245
+ if (result.allowed) {
246
+ request.policyResult = result;
247
+ if (result.filters) request._policyFilters = result.filters;
248
+ if (result.fieldMask) request.fieldMask = result.fieldMask;
249
+ if (result.metadata) request.policyMetadata = result.metadata;
250
+ return;
251
+ }
252
+ results.push(result);
253
+ }
254
+ return reply.code(403).send({
255
+ success: false,
256
+ error: "Access denied",
257
+ message: results[0]?.reason || "You do not have permission to perform this action"
258
+ });
259
+ };
260
+ }
261
+ };
262
+ }
263
+ /**
264
+ * Create a pass-through policy that always allows
265
+ *
266
+ * Useful for testing or for routes that don't need authorization.
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const policy = allowAll();
271
+ * const result = await policy.can(user, 'any-operation');
272
+ * // result.allowed === true
273
+ * ```
274
+ */
275
+ function allowAll() {
276
+ return {
277
+ can() {
278
+ return { allowed: true };
279
+ },
280
+ toMiddleware() {
281
+ return async () => {};
282
+ }
283
+ };
284
+ }
285
+ /**
286
+ * Create a policy that always denies
287
+ *
288
+ * Useful for explicitly blocking operations or for testing.
289
+ *
290
+ * @param reason - Denial reason
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * const policy = denyAll('This resource is deprecated');
295
+ * const result = await policy.can(user, 'any-operation');
296
+ * // result.allowed === false
297
+ * // result.reason === 'This resource is deprecated'
298
+ * ```
299
+ */
300
+ function denyAll(reason = "Operation not allowed") {
301
+ return {
302
+ can() {
303
+ return {
304
+ allowed: false,
305
+ reason
306
+ };
307
+ },
308
+ toMiddleware() {
309
+ return async (request, reply) => {
310
+ return reply.code(403).send({
311
+ success: false,
312
+ error: "Access denied",
313
+ message: reason
314
+ });
315
+ };
316
+ }
317
+ };
318
+ }
319
+
320
+ //#endregion
321
+ export { allowAll, anyPolicy, combinePolicies, createAccessControlPolicy, createPolicyMiddleware, denyAll };
322
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/policies/PolicyInterface.ts","../../src/policies/helpers.ts"],"sourcesContent":["/**\n * Policy Interface\n *\n * Pluggable authorization interface for Arc.\n * Apps implement this interface to define custom authorization strategies.\n *\n * @example RBAC Policy\n * ```typescript\n * class RBACPolicy implements PolicyEngine {\n * can(user, operation, context) {\n * return {\n * allowed: user.roles.includes('admin'),\n * reason: 'Admin role required',\n * };\n * }\n * toMiddleware(operation) {\n * return async (request, reply) => {\n * const result = await this.can(request.user, operation);\n * if (!result.allowed) {\n * reply.code(403).send({ error: result.reason });\n * }\n * };\n * }\n * }\n * ```\n *\n * @example ABAC (Attribute-Based) Policy\n * ```typescript\n * class ABACPolicy implements PolicyEngine {\n * can(user, operation, context) {\n * return {\n * allowed: this.evaluateAttributes(user, operation, context),\n * filters: { department: user.department },\n * fieldMask: { exclude: ['salary', 'ssn'] },\n * };\n * }\n * // ...\n * }\n * ```\n */\n\nimport type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { PermissionCheck } from '../permissions/types.js';\n\n/**\n * Policy result returned by can() method\n */\nexport interface PolicyResult {\n /**\n * Whether the operation is allowed\n */\n allowed: boolean;\n\n /**\n * Human-readable reason if denied\n * Returned in 403 error responses\n */\n reason?: string;\n\n /**\n * Query filters to apply (for list operations)\n *\n * @example\n * ```typescript\n * // Multi-tenant filter\n * { organizationId: user.organizationId }\n *\n * // Ownership filter\n * { userId: user.id }\n *\n * // Complex filter\n * { $or: [{ public: true }, { createdBy: user.id }] }\n * ```\n */\n filters?: Record<string, any>;\n\n /**\n * Fields to include/exclude in response\n *\n * @example\n * ```typescript\n * // Hide sensitive fields from non-admins\n * { exclude: ['password', 'ssn', 'salary'] }\n *\n * // Only show specific fields\n * { include: ['name', 'email', 'role'] }\n * ```\n */\n fieldMask?: {\n include?: string[];\n exclude?: string[];\n };\n\n /**\n * Additional context for downstream middleware\n *\n * @example\n * ```typescript\n * {\n * auditLog: { action: 'read', resource: 'patient', userId: user.id },\n * rateLimit: { tier: user.subscriptionTier },\n * }\n * ```\n */\n metadata?: Record<string, any>;\n}\n\n/**\n * Policy context provided to can() method\n */\nexport interface PolicyContext {\n /**\n * The document being accessed (for update/delete/get)\n * Populated by fetchDocument middleware\n */\n document?: any;\n\n /**\n * Request body (for create/update)\n */\n body?: any;\n\n /**\n * Request params (e.g., :id from route)\n */\n params?: any;\n\n /**\n * Request query parameters\n */\n query?: any;\n\n /**\n * Additional app-specific context\n * Can include anything your policy needs to make decisions\n */\n [key: string]: any;\n}\n\n/**\n * Policy Engine Interface\n *\n * Implement this interface to create your own authorization strategy.\n *\n * Arc provides the interface, apps provide the implementation.\n * This follows the same pattern as:\n * - Database drivers (interface: query(), implementation: PostgreSQL, MySQL)\n * - Storage providers (interface: upload(), implementation: S3, Azure)\n * - Authentication strategies (interface: verify(), implementation: JWT, OAuth)\n *\n * @example E-commerce RBAC + Ownership\n * ```typescript\n * class EcommercePolicyEngine implements PolicyEngine {\n * constructor(private config: { roles: Record<string, string[]> }) {}\n *\n * can(user, operation, context) {\n * // Check RBAC\n * const allowedRoles = this.config.roles[operation] || [];\n * if (!user.roles.some(r => allowedRoles.includes(r))) {\n * return { allowed: false, reason: 'Insufficient permissions' };\n * }\n *\n * // Check ownership for update/delete\n * if (['update', 'delete'].includes(operation)) {\n * if (context.document.userId !== user.id) {\n * return { allowed: false, reason: 'Not the owner' };\n * }\n * }\n *\n * // Multi-tenant filter for list\n * if (operation === 'list') {\n * return {\n * allowed: true,\n * filters: { organizationId: user.organizationId },\n * };\n * }\n *\n * return { allowed: true };\n * }\n *\n * toMiddleware(operation) {\n * return async (request, reply) => {\n * const result = await this.can(request.user, operation, {\n * document: request.document,\n * body: request.body,\n * params: request.params,\n * query: request.query,\n * });\n *\n * if (!result.allowed) {\n * reply.code(403).send({ error: result.reason });\n * }\n *\n * // Attach filters/fieldMask to request\n * request.policyResult = result;\n * };\n * }\n * }\n * ```\n *\n * @example HIPAA Compliance\n * ```typescript\n * class HIPAAPolicyEngine implements PolicyEngine {\n * can(user, operation, context) {\n * // Check patient consent\n * // Verify user certifications\n * // Check data sensitivity level\n * // Create audit log entry\n *\n * return {\n * allowed: this.checkHIPAACompliance(user, operation, context),\n * reason: 'HIPAA compliance check failed',\n * metadata: {\n * auditLog: this.createAuditEntry(user, operation),\n * },\n * };\n * }\n *\n * toMiddleware(operation) {\n * // HIPAA-specific middleware with audit logging\n * }\n * }\n * ```\n */\nexport interface PolicyEngine {\n /**\n * Check if user can perform operation\n *\n * @param user - User object from request (request.user)\n * @param operation - Operation name (list, get, create, update, delete, custom)\n * @param context - Additional context (document, body, params, query, etc.)\n * @returns Policy result with allowed/denied and optional filters/fieldMask\n *\n * @example\n * ```typescript\n * const result = await policy.can(request.user, 'update', {\n * document: existingDocument,\n * body: request.body,\n * });\n *\n * if (!result.allowed) {\n * throw new Error(result.reason);\n * }\n * ```\n */\n can(\n user: any,\n operation: string,\n context?: PolicyContext\n ): PolicyResult | Promise<PolicyResult>;\n\n /**\n * Generate Fastify middleware for this policy\n *\n * Called during route registration to create preHandler middleware.\n * Middleware should:\n * 1. Call can() with request context\n * 2. Return 403 if denied\n * 3. Attach result to request for downstream use\n *\n * @param operation - Operation name (list, get, create, update, delete)\n * @returns Fastify preHandler middleware\n *\n * @example\n * ```typescript\n * const middleware = policy.toMiddleware('update');\n * fastify.put('/products/:id', {\n * preHandler: [authenticate, middleware],\n * }, handler);\n * ```\n */\n toMiddleware(\n operation: string\n ): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;\n}\n\n/**\n * Policy factory function signature\n *\n * Policies are typically created via factory functions that accept configuration.\n *\n * @example\n * ```typescript\n * export function definePolicy(config: PolicyConfig): PolicyEngine {\n * return new MyPolicyEngine(config);\n * }\n *\n * // Usage\n * const productPolicy = definePolicy({\n * resource: 'product',\n * roles: { list: ['user'], create: ['admin'] },\n * ownership: { field: 'userId', operations: ['update', 'delete'] },\n * });\n * ```\n */\nexport type PolicyFactory<TConfig = any> = (config: TConfig) => PolicyEngine;\n\n/**\n * Extended Fastify request with policy result\n */\n/**\n * Access control statement\n *\n * Maps to Better Auth's organization permission model\n * where permissions are defined as resource + action pairs.\n */\nexport interface AccessControlStatement {\n /** Resource name (e.g., 'product', 'order') */\n resource: string;\n /** Allowed actions on this resource */\n action: string[];\n}\n\n/**\n * Options for createAccessControlPolicy\n */\nexport interface AccessControlPolicyOptions {\n /** Permission statements defining resource-action pairs */\n statements: AccessControlStatement[];\n /**\n * Optional async permission check against external source (e.g., org role permissions).\n * Called when the static statements allow the action — use this for dynamic checks\n * like verifying the user's org role actually grants the permission.\n *\n * @param userId - ID of the user\n * @param resource - Resource being accessed\n * @param action - Action being performed\n * @returns Whether the user has the permission\n */\n checkPermission?: (userId: string, resource: string, action: string) => Promise<boolean>;\n}\n\n/**\n * Create a PermissionCheck from access control statements.\n *\n * Maps Better Auth's statement-based access control model to Arc's\n * PermissionCheck function, which can be used directly in resource permissions.\n *\n * The returned PermissionCheck:\n * 1. Looks up the resource + action in the statements list\n * 2. If no matching statement exists, denies access\n * 3. If a matching statement exists and `checkPermission` is provided,\n * calls it for dynamic verification (e.g., check org role)\n * 4. If `checkPermission` is not provided, allows access based on static statements\n *\n * @example Static statements only\n * ```typescript\n * import { createAccessControlPolicy } from '@classytic/arc/policies';\n *\n * const editorPermissions = createAccessControlPolicy({\n * statements: [\n * { resource: 'product', action: ['create', 'update'] },\n * { resource: 'order', action: ['read'] },\n * ],\n * });\n *\n * // Use in resource config\n * defineResource({\n * name: 'product',\n * permissions: {\n * create: editorPermissions,\n * update: editorPermissions,\n * },\n * });\n * ```\n *\n * @example With dynamic permission check (Better Auth org roles)\n * ```typescript\n * const policy = createAccessControlPolicy({\n * statements: [\n * { resource: 'product', action: ['create', 'update'] },\n * { resource: 'order', action: ['read'] },\n * ],\n * checkPermission: async (userId, resource, action) => {\n * return hasOrgPermission(userId, resource, action);\n * },\n * });\n * ```\n */\nexport function createAccessControlPolicy(\n options: AccessControlPolicyOptions\n): PermissionCheck {\n // Pre-compute a lookup map: resource -> Set<action> for O(1) checks\n const statementMap = new Map<string, Set<string>>();\n for (const statement of options.statements) {\n const existing = statementMap.get(statement.resource);\n if (existing) {\n for (const action of statement.action) {\n existing.add(action);\n }\n } else {\n statementMap.set(statement.resource, new Set(statement.action));\n }\n }\n\n const permissionCheck: PermissionCheck = async (context) => {\n const { user, resource, action } = context;\n\n // Check if the action is allowed by any statement\n const allowedActions = statementMap.get(resource);\n if (!allowedActions || !allowedActions.has(action)) {\n return {\n granted: false,\n reason: `Action '${action}' is not permitted on resource '${resource}'`,\n };\n }\n\n // If a dynamic permission check is provided, verify against it\n if (options.checkPermission) {\n const userId = user?.id ?? user?._id;\n if (!userId) {\n return {\n granted: false,\n reason: 'Authentication required',\n };\n }\n\n const hasPermission = await options.checkPermission(String(userId), resource, action);\n if (!hasPermission) {\n return {\n granted: false,\n reason: `User does not have '${action}' permission on '${resource}'`,\n };\n }\n }\n\n return { granted: true };\n };\n\n return permissionCheck;\n}\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n policyResult?: PolicyResult;\n }\n}\n","/**\n * Policy Helper Utilities\n *\n * Common operations for working with PolicyEngine implementations.\n */\n\nimport type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { PolicyEngine, PolicyResult } from './PolicyInterface.js';\n\n/**\n * Helper to create Fastify middleware from any PolicyEngine implementation\n *\n * This is a convenience function that provides a standard middleware pattern.\n * Most policies can use this instead of implementing toMiddleware() manually.\n *\n * @param policy - Policy engine instance\n * @param operation - Operation name (list, get, create, update, delete)\n * @returns Fastify preHandler middleware\n *\n * @example\n * ```typescript\n * class SimplePolicy implements PolicyEngine {\n * can(user, operation) {\n * return { allowed: user.isActive };\n * }\n *\n * toMiddleware(operation) {\n * return createPolicyMiddleware(this, operation);\n * }\n * }\n * ```\n */\nexport function createPolicyMiddleware(\n policy: PolicyEngine,\n operation: string\n): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {\n return async function policyMiddleware(\n request: FastifyRequest,\n reply: FastifyReply\n ): Promise<void> {\n // Build context from request\n const context = {\n document: request.document,\n body: request.body,\n params: request.params,\n query: request.query,\n };\n\n // Check policy\n const result = await policy.can(request.user, operation, context);\n\n if (!result.allowed) {\n return reply.code(403).send({\n success: false,\n error: 'Access denied',\n message: result.reason || 'You do not have permission to perform this action',\n });\n }\n\n // Attach result to request for downstream use\n request.policyResult = result;\n\n // Apply policy filters on trusted location (for list operations)\n if (result.filters && Object.keys(result.filters).length > 0) {\n request._policyFilters = result.filters;\n }\n\n // Apply field mask (for response serialization)\n if (result.fieldMask) {\n request.fieldMask = result.fieldMask;\n }\n\n // Attach metadata (for downstream middleware/logging)\n if (result.metadata) {\n request.policyMetadata = result.metadata;\n }\n };\n}\n\n/**\n * Combine multiple policies with AND logic\n *\n * All policies must allow the operation for it to succeed.\n * First denial stops evaluation and returns the denial reason.\n *\n * @param policies - Array of policy engines to combine\n * @returns Combined policy engine\n *\n * @example\n * ```typescript\n * const combinedPolicy = combinePolicies(\n * rbacPolicy, // Must have correct role\n * ownershipPolicy, // Must own the resource\n * auditPolicy, // Logs access for compliance\n * );\n *\n * // All three policies must pass for the operation to succeed\n * const result = await combinedPolicy.can(user, 'update', context);\n * ```\n *\n * @example Multi-tenant + RBAC\n * ```typescript\n * const policy = combinePolicies(\n * definePolicy({ tenant: { field: 'organizationId' } }),\n * definePolicy({ roles: { update: ['admin', 'editor'] } }),\n * );\n * ```\n */\nexport function combinePolicies(...policies: PolicyEngine[]): PolicyEngine {\n if (policies.length === 0) {\n throw new Error('combinePolicies requires at least one policy');\n }\n\n if (policies.length === 1) {\n return policies[0]!;\n }\n\n return {\n async can(user: any, operation: string, context?: any): Promise<PolicyResult> {\n const results: PolicyResult[] = [];\n\n for (const policy of policies) {\n const result = await policy.can(user, operation, context);\n\n if (!result.allowed) {\n // First denial stops evaluation\n return result;\n }\n\n results.push(result);\n }\n\n // Merge all results\n const mergedResult: PolicyResult = {\n allowed: true,\n filters: {},\n metadata: {},\n };\n\n // Merge filters (AND logic - all filters must match)\n for (const result of results) {\n if (result.filters) {\n Object.assign(mergedResult.filters!, result.filters);\n }\n }\n\n // Merge field masks (union of excludes, intersection of includes)\n const allExcludes = new Set<string>();\n const allIncludes: Set<string>[] = [];\n\n for (const result of results) {\n if (result.fieldMask?.exclude) {\n result.fieldMask.exclude.forEach((field) => allExcludes.add(field));\n }\n if (result.fieldMask?.include) {\n allIncludes.push(new Set(result.fieldMask.include));\n }\n }\n\n if (allExcludes.size > 0 || allIncludes.length > 0) {\n mergedResult.fieldMask = {};\n\n if (allExcludes.size > 0) {\n mergedResult.fieldMask.exclude = Array.from(allExcludes);\n }\n\n if (allIncludes.length > 0) {\n // Intersection of all includes\n const intersection = allIncludes.reduce((acc, set) => {\n return new Set([...acc].filter((x) => set.has(x)));\n });\n if (intersection.size > 0) {\n mergedResult.fieldMask.include = Array.from(intersection);\n }\n }\n }\n\n // Merge metadata\n for (const result of results) {\n if (result.metadata) {\n Object.assign(mergedResult.metadata!, result.metadata);\n }\n }\n\n // Clean up empty objects\n if (Object.keys(mergedResult.filters!).length === 0) {\n delete mergedResult.filters;\n }\n if (Object.keys(mergedResult.metadata!).length === 0) {\n delete mergedResult.metadata;\n }\n\n return mergedResult;\n },\n\n toMiddleware(operation: string) {\n const middlewares = policies.map((p) => p.toMiddleware(operation));\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n for (const middleware of middlewares) {\n await middleware(request, reply);\n\n // Stop if response was sent (denial)\n if (reply.sent) {\n return;\n }\n }\n };\n },\n };\n}\n\n/**\n * Combine multiple policies with OR logic\n *\n * At least one policy must allow the operation for it to succeed.\n * If all policies deny, returns the first denial reason.\n *\n * @param policies - Array of policy engines to combine\n * @returns Combined policy engine\n *\n * @example\n * ```typescript\n * const policy = anyPolicy(\n * ownerPolicy, // User owns the resource\n * adminPolicy, // OR user is admin\n * publicPolicy, // OR resource is public\n * );\n *\n * // Any one of these policies passing allows the operation\n * ```\n */\nexport function anyPolicy(...policies: PolicyEngine[]): PolicyEngine {\n if (policies.length === 0) {\n throw new Error('anyPolicy requires at least one policy');\n }\n\n if (policies.length === 1) {\n return policies[0]!;\n }\n\n return {\n async can(user: any, operation: string, context?: any): Promise<PolicyResult> {\n let firstDenial: PolicyResult | null = null;\n\n for (const policy of policies) {\n const result = await policy.can(user, operation, context);\n\n if (result.allowed) {\n // First success stops evaluation\n return result;\n }\n\n if (!firstDenial) {\n firstDenial = result;\n }\n }\n\n // All policies denied - return first denial\n return firstDenial!;\n },\n\n toMiddleware(operation: string) {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n const results: PolicyResult[] = [];\n\n for (const policy of policies) {\n const result = await policy.can(\n request.user,\n operation,\n {\n document: request.document,\n body: request.body,\n params: request.params,\n query: request.query,\n }\n );\n\n if (result.allowed) {\n // First success - attach result and continue\n request.policyResult = result;\n\n if (result.filters) {\n request._policyFilters = result.filters;\n }\n\n if (result.fieldMask) {\n request.fieldMask = result.fieldMask;\n }\n\n if (result.metadata) {\n request.policyMetadata = result.metadata;\n }\n\n return;\n }\n\n results.push(result);\n }\n\n // All policies denied\n return reply.code(403).send({\n success: false,\n error: 'Access denied',\n message: results[0]?.reason || 'You do not have permission to perform this action',\n });\n };\n },\n };\n}\n\n/**\n * Create a pass-through policy that always allows\n *\n * Useful for testing or for routes that don't need authorization.\n *\n * @example\n * ```typescript\n * const policy = allowAll();\n * const result = await policy.can(user, 'any-operation');\n * // result.allowed === true\n * ```\n */\nexport function allowAll(): PolicyEngine {\n return {\n can() {\n return { allowed: true };\n },\n\n toMiddleware() {\n return async () => {\n // No-op - always allow\n };\n },\n };\n}\n\n/**\n * Create a policy that always denies\n *\n * Useful for explicitly blocking operations or for testing.\n *\n * @param reason - Denial reason\n *\n * @example\n * ```typescript\n * const policy = denyAll('This resource is deprecated');\n * const result = await policy.can(user, 'any-operation');\n * // result.allowed === false\n * // result.reason === 'This resource is deprecated'\n * ```\n */\nexport function denyAll(reason = 'Operation not allowed'): PolicyEngine {\n return {\n can() {\n return { allowed: false, reason };\n },\n\n toMiddleware() {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n return reply.code(403).send({\n success: false,\n error: 'Access denied',\n message: reason,\n });\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -1,115 +1,43 @@
1
- import { MultiTenantOptions } from './multiTenant.js';
2
- export { default as multiTenantPreset } from './multiTenant.js';
3
- import { f as PresetResult, a as IRequestContext, c as IControllerResponse, P as PaginatedResult, A as AnyRecord, l as ResourceConfig } from '../index-B4t03KQ0.js';
4
- import 'mongoose';
5
- import 'fastify';
6
- import '../types-B99TBmFV.js';
7
-
8
- /**
9
- * Soft Delete Preset
10
- *
11
- * Adds routes for listing deleted items and restoring them.
12
- */
13
-
14
- interface SoftDeleteOptions {
15
- deletedField?: string;
16
- }
17
- declare function softDeletePreset(options?: SoftDeleteOptions): PresetResult;
18
-
19
- /**
20
- * Slug Lookup Preset
21
- *
22
- * Adds a route to get resource by slug.
23
- */
1
+ import "../elevation-B_2dRLVP.mjs";
2
+ import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-Ch8HU9uM.mjs";
3
+ import "../types-aYB4V7uN.mjs";
4
+ import { AnyRecord, PresetResult, ResourceConfig } from "../types/index.mjs";
5
+ import multiTenantPreset, { MultiTenantOptions } from "./multiTenant.mjs";
24
6
 
7
+ //#region src/presets/softDelete.d.ts
8
+ declare function softDeletePreset(): PresetResult;
9
+ //#endregion
10
+ //#region src/presets/slugLookup.d.ts
25
11
  interface SlugLookupOptions {
26
- slugField?: string;
12
+ slugField?: string;
27
13
  }
28
14
  declare function slugLookupPreset(options?: SlugLookupOptions): PresetResult;
29
-
30
- /**
31
- * Owned By User Preset
32
- *
33
- * Adds ownership validation for update/delete operations.
34
- *
35
- * BEHAVIOR:
36
- * - On update/remove, sets _ownershipCheck on request
37
- * - BaseController enforces ownership before mutation
38
- * - Users can only modify resources where ownerField matches their ID
39
- *
40
- * BYPASS:
41
- * - Users with bypassRoles (default: ['admin', 'superadmin']) skip check
42
- * - Resources without the ownerField are not checked
43
- *
44
- * @example
45
- * defineResource({
46
- * name: 'post',
47
- * presets: [{ name: 'ownedByUser', ownerField: 'authorId' }],
48
- * });
49
- *
50
- * // User A cannot update/delete User B's posts
51
- * // Admins can modify any post
52
- */
53
-
15
+ //#endregion
16
+ //#region src/presets/ownedByUser.d.ts
54
17
  interface OwnedByUserOptions {
55
- ownerField?: string;
56
- bypassRoles?: string[];
18
+ ownerField?: string;
57
19
  }
58
20
  declare function ownedByUserPreset(options?: OwnedByUserOptions): PresetResult;
59
-
60
- /**
61
- * Tree Preset
62
- *
63
- * Adds routes for hierarchical tree structures.
64
- */
65
-
21
+ //#endregion
22
+ //#region src/presets/tree.d.ts
66
23
  interface TreeOptions {
67
- parentField?: string;
24
+ parentField?: string;
68
25
  }
69
26
  declare function treePreset(options?: TreeOptions): PresetResult;
70
-
71
- /**
72
- * Audited Preset
73
- *
74
- * Adds createdBy/updatedBy tracking to resources.
75
- * Works with the audit plugin for full change tracking.
76
- *
77
- * @example
78
- * defineResource({
79
- * name: 'product',
80
- * presets: ['audited'],
81
- * // Fields createdBy, updatedBy auto-populated from user context
82
- * });
83
- */
84
-
27
+ //#endregion
28
+ //#region src/presets/audited.d.ts
85
29
  interface AuditedPresetOptions {
86
- /** Field name for creator (default: 'createdBy') */
87
- createdByField?: string;
88
- /** Field name for updater (default: 'updatedBy') */
89
- updatedByField?: string;
30
+ /** Field name for creator (default: 'createdBy') */
31
+ createdByField?: string;
32
+ /** Field name for updater (default: 'updatedBy') */
33
+ updatedByField?: string;
90
34
  }
91
35
  /**
92
36
  * Audited preset - adds createdBy/updatedBy tracking
93
37
  */
94
38
  declare function auditedPreset(options?: AuditedPresetOptions): PresetResult;
95
-
96
- /**
97
- * Preset Type Interfaces
98
- *
99
- * TypeScript interfaces that document the controller methods required by each preset.
100
- * These interfaces help with type safety when using presets.
101
- *
102
- * @example Using with custom controllers
103
- * ```typescript
104
- * import { BaseController } from '@classytic/arc';
105
- * import type { ISoftDeleteController } from '@classytic/arc/presets';
106
- *
107
- * class ProductController extends BaseController<Product> implements ISoftDeleteController {
108
- * // TypeScript now ensures you have getDeleted() and restore() methods
109
- * }
110
- * ```
111
- */
112
-
39
+ //#endregion
40
+ //#region src/presets/types.d.ts
113
41
  /**
114
42
  * Soft Delete Preset Interface
115
43
  *
@@ -138,16 +66,16 @@ declare function auditedPreset(options?: AuditedPresetOptions): PresetResult;
138
66
  * ```
139
67
  */
140
68
  interface ISoftDeleteController<TDoc = unknown> {
141
- /**
142
- * Get all soft-deleted items
143
- * Called by: GET /deleted
144
- */
145
- getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginatedResult<TDoc>>>;
146
- /**
147
- * Restore a soft-deleted item by ID
148
- * Called by: POST /:id/restore
149
- */
150
- restore(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
69
+ /**
70
+ * Get all soft-deleted items
71
+ * Called by: GET /deleted
72
+ */
73
+ getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginatedResult<TDoc>>>;
74
+ /**
75
+ * Restore a soft-deleted item by ID
76
+ * Called by: POST /:id/restore
77
+ */
78
+ restore(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
151
79
  }
152
80
  /**
153
81
  * Slug Lookup Preset Interface
@@ -175,11 +103,11 @@ interface ISoftDeleteController<TDoc = unknown> {
175
103
  * ```
176
104
  */
177
105
  interface ISlugLookupController<TDoc = unknown> {
178
- /**
179
- * Get a resource by its slug
180
- * Called by: GET /slug/:slug
181
- */
182
- getBySlug(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
106
+ /**
107
+ * Get a resource by its slug
108
+ * Called by: GET /slug/:slug
109
+ */
110
+ getBySlug(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
183
111
  }
184
112
  /**
185
113
  * Tree Preset Interface
@@ -209,16 +137,16 @@ interface ISlugLookupController<TDoc = unknown> {
209
137
  * ```
210
138
  */
211
139
  interface ITreeController<TDoc = unknown> {
212
- /**
213
- * Get the full hierarchical tree
214
- * Called by: GET /tree
215
- */
216
- getTree(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
217
- /**
218
- * Get direct children of a parent node
219
- * Called by: GET /:parent/children
220
- */
221
- getChildren(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
140
+ /**
141
+ * Get the full hierarchical tree
142
+ * Called by: GET /tree
143
+ */
144
+ getTree(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
145
+ /**
146
+ * Get direct children of a parent node
147
+ * Called by: GET /:parent/children
148
+ */
149
+ getChildren(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
222
150
  }
223
151
  /**
224
152
  * Owned By User Preset
@@ -301,36 +229,40 @@ type IAuditedPreset = never;
301
229
  * ```
302
230
  */
303
231
  type IPresetController<TDoc = unknown, TPresets extends 'softDelete' | 'slugLookup' | 'tree' | never = never> = TPresets extends 'softDelete' ? ISoftDeleteController<TDoc> : TPresets extends 'slugLookup' ? ISlugLookupController<TDoc> : TPresets extends 'tree' ? ITreeController<TDoc> : unknown;
304
-
232
+ //#endregion
233
+ //#region src/presets/index.d.ts
305
234
  /**
306
235
  * Convenience alias for multiTenantPreset with public list/get routes
307
236
  * Equivalent to: multiTenantPreset({ allowPublic: ['list', 'get'] })
308
237
  */
309
238
  declare const flexibleMultiTenantPreset: (options?: Omit<MultiTenantOptions, "allowPublic">) => PresetResult;
310
-
311
239
  type PresetFactory = (options?: AnyRecord) => PresetResult;
312
240
  /**
313
241
  * Get preset by name with options
314
242
  */
315
243
  declare function getPreset(nameOrConfig: string | {
316
- name: string;
317
- [key: string]: unknown;
244
+ name: string;
245
+ [key: string]: unknown;
318
246
  }): PresetResult;
319
247
  /**
320
248
  * Register a custom preset
321
249
  */
322
- declare function registerPreset(name: string, factory: PresetFactory): void;
250
+ declare function registerPreset(name: string, factory: PresetFactory, options?: {
251
+ override?: boolean;
252
+ }): void;
323
253
  /**
324
254
  * Get all available preset names
325
255
  */
326
256
  declare function getAvailablePresets(): string[];
327
257
  type PresetInput = string | PresetResult | {
328
- name: string;
329
- [key: string]: unknown;
258
+ name: string;
259
+ [key: string]: unknown;
330
260
  };
331
261
  /**
332
- * Apply presets to resource config
262
+ * Apply presets to resource config.
263
+ * Validates preset combinations for conflicts before merging.
333
264
  */
334
265
  declare function applyPresets<TDoc = AnyRecord>(config: ResourceConfig<TDoc>, presets?: PresetInput[]): ResourceConfig<TDoc>;
335
-
336
- export { type AuditedPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type SoftDeleteOptions, type TreeOptions, applyPresets, auditedPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
266
+ //#endregion
267
+ export { type AuditedPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TreeOptions, applyPresets, auditedPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
268
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/presets/softDelete.ts","../../src/presets/slugLookup.ts","../../src/presets/ownedByUser.ts","../../src/presets/tree.ts","../../src/presets/audited.ts","../../src/presets/types.ts","../../src/presets/index.ts"],"mappings":";;;;;;;iBAWgB,gBAAA,CAAA,GAAoB,YAAA;;;UCFnB,iBAAA;EACf,SAAA;AAAA;AAAA,iBAGc,gBAAA,CAAiB,OAAA,GAAS,iBAAA,GAAyB,YAAA;;;UCqBlD,kBAAA;EACf,UAAA;AAAA;AAAA,iBA4Bc,iBAAA,CAAkB,OAAA,GAAS,kBAAA,GAA0B,YAAA;;;UCtDpD,WAAA;EACf,WAAA;AAAA;AAAA,iBAGc,UAAA,CAAW,OAAA,GAAS,WAAA,GAAmB,YAAA;;;UCItC,oBAAA;EHRA;EGUf,cAAA;;EAEA,cAAA;AAAA;AHRF;;;AAAA,iBGcgB,aAAA,CAAc,OAAA,GAAS,oBAAA,GAA4B,YAAA;;;;;;AHdnE;;;;;;;;;;;;ACqBA;;;;;AA6BA;;;;;;;UGjBiB,qBAAA;EHiBgE;;;;EGZ/E,UAAA,CAAW,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,eAAA,CAAgB,IAAA;EF1CpD;;;;EEgD1B,OAAA,CAAQ,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;AAAA;;;;;;;;;;;ADxC7D;;;;;AAUA;;;;;;;;;;UC0DiB,qBAAA;;AAvCjB;;;EA4CE,SAAA,CAAU,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AAL/D;;UAmCiB,eAAA;EA9BA;;;;EAmCf,OAAA,CAAQ,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;EAnCnB;;;;EAyCxC,WAAA,CAAY,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;AAAA;;;;;AAXjE;;;;;;;;;;;;;;;;KAkCY,kBAAA;;;;;;;;;;;;AAAZ;;;;;AAuBA;;;;;KAAY,kBAAA;;;;;AAwCZ;;;;;;;;;;;;;;;;;KAjBY,cAAA;;;;;;;;;;;;;ACtJZ;;;KDuKY,iBAAA,0FAGR,QAAA,wBACA,qBAAA,CAAsB,IAAA,IACtB,QAAA,wBACE,qBAAA,CAAsB,IAAA,IACtB,QAAA,kBACE,eAAA,CAAgB,IAAA;;;;AH/LxB;;;cIgBa,yBAAA,GACX,OAAA,GAAS,IAAA,CACyD,kBAAA,qBADc,YAAA;AAAA,KAgC7E,aAAA,IAAiB,OAAA,GAAU,SAAA,KAAc,YAAA;;;;iBAc9B,SAAA,CAAU,YAAA;EAAyB,IAAA;EAAA,CAAe,GAAA;AAAA,IAA0B,YAAA;AHxF5F;;;AAAA,iBGsHgB,cAAA,CACd,IAAA,UACA,OAAA,EAAS,aAAA,EACT,OAAA;EAAY,QAAA;AAAA;;;;iBAaE,mBAAA,CAAA;AAAA,KAQX,WAAA,YAAuB,YAAA;EAAiB,IAAA;EAAA,CAAe,GAAA;AAAA;;;;AFtI5D;iBEqLgB,YAAA,QAAoB,SAAA,CAAA,CAClC,MAAA,EAAQ,cAAA,CAAe,IAAA,GACvB,OAAA,GAAS,WAAA,KACR,cAAA,CAAe,IAAA"}