@classytic/arc 2.1.2 → 2.1.3

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 (261) hide show
  1. package/dist/{EventTransport-BD2U0BTc.d.mts → EventTransport-BkUDYZEb.d.mts} +1 -2
  2. package/dist/HookSystem-BsGV-j2l.mjs +1 -2
  3. package/dist/{ResourceRegistry-DsN4KJjV.mjs → ResourceRegistry-7Ic20ZMw.mjs} +1 -2
  4. package/dist/adapters/index.d.mts +4 -4
  5. package/dist/audit/index.d.mts +5 -6
  6. package/dist/audit/index.mjs +2 -3
  7. package/dist/audit/mongodb.d.mts +4 -4
  8. package/dist/audit/mongodb.mjs +1 -1
  9. package/dist/{audited-C3T5DTUx.mjs → audited-CGdLiSlE.mjs} +1 -2
  10. package/dist/auth/index.d.mts +6 -7
  11. package/dist/auth/index.mjs +10 -16
  12. package/dist/auth/redis-session.d.mts +2 -3
  13. package/dist/auth/redis-session.mjs +1 -2
  14. package/dist/{betterAuthOpenApi-BrHKeSAx.mjs → betterAuthOpenApi-DjWDddNc.mjs} +2 -3
  15. package/dist/cache/index.d.mts +3 -4
  16. package/dist/cache/index.mjs +4 -5
  17. package/dist/{caching-Bl28lYsR.mjs → caching-GSDJcA6-.mjs} +1 -2
  18. package/dist/{circuitBreaker-DeY4FCjs.mjs → circuitBreaker-DYhWBW_D.mjs} +1 -2
  19. package/dist/cli/commands/describe.d.mts +1 -2
  20. package/dist/cli/commands/describe.mjs +1 -2
  21. package/dist/cli/commands/docs.d.mts +1 -2
  22. package/dist/cli/commands/docs.mjs +3 -4
  23. package/dist/cli/commands/generate.d.mts +1 -2
  24. package/dist/cli/commands/generate.mjs +2 -3
  25. package/dist/cli/commands/init.d.mts +1 -2
  26. package/dist/cli/commands/init.mjs +6 -7
  27. package/dist/cli/commands/introspect.d.mts +1 -2
  28. package/dist/cli/commands/introspect.mjs +2 -3
  29. package/dist/cli/index.d.mts +1 -2
  30. package/dist/cli/index.mjs +1 -2
  31. package/dist/constants-DdXFXQtN.mjs +1 -2
  32. package/dist/core/index.d.mts +4 -4
  33. package/dist/core/index.mjs +1 -1
  34. package/dist/{createApp-CUgNqegw.mjs → createApp-D2D5XXaV.mjs} +9 -10
  35. package/dist/{defineResource-k0_BDn8v.mjs → defineResource-PXzSJ15_.mjs} +11 -11
  36. package/dist/discovery/index.d.mts +1 -2
  37. package/dist/discovery/index.mjs +1 -2
  38. package/dist/docs/index.d.mts +5 -6
  39. package/dist/docs/index.mjs +5 -4
  40. package/dist/{elevation-B_2dRLVP.d.mts → elevation-DGo5shaX.d.mts} +1 -2
  41. package/dist/{elevation-BRy3yFWT.mjs → elevation-DSTbVvYj.mjs} +4 -4
  42. package/dist/{errorHandler-C1okiriz.mjs → errorHandler-C3GY3_ow.mjs} +2 -3
  43. package/dist/{errorHandler-BbcgBmIH.d.mts → errorHandler-CW3OOeYq.d.mts} +2 -3
  44. package/dist/{errors-ChKiFz62.d.mts → errors-DAWRdiYP.d.mts} +1 -2
  45. package/dist/{errors-B9bZok84.mjs → errors-DBANPbGr.mjs} +1 -2
  46. package/dist/{eventPlugin-DGR_B2on.mjs → eventPlugin-BEOvaDqo.mjs} +2 -3
  47. package/dist/{eventPlugin-CTrLH3mt.d.mts → eventPlugin-H6wDDjGO.d.mts} +2 -3
  48. package/dist/events/index.d.mts +4 -5
  49. package/dist/events/index.mjs +2 -3
  50. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  51. package/dist/events/transports/redis-stream-entry.mjs +1 -2
  52. package/dist/events/transports/redis.d.mts +2 -3
  53. package/dist/events/transports/redis.mjs +1 -2
  54. package/dist/{externalPaths-DlINfKbP.d.mts → externalPaths-SyPF2tgK.d.mts} +1 -2
  55. package/dist/factory/index.d.mts +8 -9
  56. package/dist/factory/index.mjs +1 -1
  57. package/dist/{fastifyAdapter-BkrGrlFi.d.mts → fastifyAdapter-C8DlE0YH.d.mts} +4 -5
  58. package/dist/{fields-DyaDVX4J.d.mts → fields-Bi_AVKSo.d.mts} +2 -3
  59. package/dist/{fields-iagOozy0.mjs → fields-CTd_CrKr.mjs} +2 -3
  60. package/dist/hooks/index.d.mts +3 -3
  61. package/dist/idempotency/index.d.mts +4 -5
  62. package/dist/idempotency/index.mjs +1 -2
  63. package/dist/idempotency/mongodb.d.mts +1 -1
  64. package/dist/idempotency/mongodb.mjs +1 -2
  65. package/dist/idempotency/redis.d.mts +1 -1
  66. package/dist/idempotency/redis.mjs +1 -2
  67. package/dist/index.d.mts +9 -10
  68. package/dist/index.mjs +7 -8
  69. package/dist/integrations/event-gateway.d.mts +2 -3
  70. package/dist/integrations/event-gateway.mjs +2 -3
  71. package/dist/integrations/jobs.d.mts +1 -2
  72. package/dist/integrations/jobs.mjs +1 -2
  73. package/dist/integrations/streamline.d.mts +1 -2
  74. package/dist/integrations/streamline.mjs +1 -2
  75. package/dist/integrations/websocket.d.mts +1 -2
  76. package/dist/integrations/websocket.mjs +1 -2
  77. package/dist/{interface-B01JvPVc.d.mts → interface-CSNjltAc.d.mts} +1 -2
  78. package/dist/{interface-CZe8IkMf.d.mts → interface-DTbsvIWe.d.mts} +1 -2
  79. package/dist/{interface-Ch8HU9uM.d.mts → interface-e9XfSsUV.d.mts} +3 -4
  80. package/dist/{introspectionPlugin-rFdO8ZUa.mjs → introspectionPlugin-B3JkrjwU.mjs} +1 -2
  81. package/dist/{keys-BqNejWup.mjs → keys-DhqDRxv3.mjs} +1 -2
  82. package/dist/{logger-Df2O2WsW.mjs → logger-ByrvQWZO.mjs} +1 -2
  83. package/dist/{memory-cQgelFOj.mjs → memory-B2v7KrCB.mjs} +1 -2
  84. package/dist/migrations/index.d.mts +1 -2
  85. package/dist/migrations/index.mjs +1 -2
  86. package/dist/{mongodb-CGzRbfAK.d.mts → mongodb-ClykrfGo.d.mts} +2 -3
  87. package/dist/{mongodb-BfJVlUJH.mjs → mongodb-DNKEExbf.mjs} +1 -2
  88. package/dist/{mongodb-JN-9JA7K.d.mts → mongodb-Dg8O_gvd.d.mts} +2 -3
  89. package/dist/{openapi-G3Cw7XuM.mjs → openapi-9nB_kiuR.mjs} +5 -4
  90. package/dist/org/index.d.mts +4 -5
  91. package/dist/org/index.mjs +1 -2
  92. package/dist/org/types.d.mts +1 -2
  93. package/dist/permissions/index.d.mts +5 -6
  94. package/dist/permissions/index.mjs +7 -7
  95. package/dist/plugins/index.d.mts +7 -8
  96. package/dist/plugins/index.mjs +7 -8
  97. package/dist/plugins/response-cache.d.mts +1 -2
  98. package/dist/plugins/response-cache.mjs +2 -3
  99. package/dist/plugins/tracing-entry.d.mts +1 -1
  100. package/dist/plugins/tracing-entry.mjs +1 -2
  101. package/dist/{pluralize-CEweyOEm.mjs → pluralize-CM-jZg7p.mjs} +1 -2
  102. package/dist/policies/index.d.mts +4 -5
  103. package/dist/policies/index.mjs +1 -2
  104. package/dist/presets/index.d.mts +4 -5
  105. package/dist/presets/index.mjs +2 -3
  106. package/dist/presets/multiTenant.d.mts +4 -5
  107. package/dist/presets/multiTenant.mjs +1 -2
  108. package/dist/{presets-DzSMwlKj.d.mts → presets-BTeYbw7h.d.mts} +2 -3
  109. package/dist/{presets-BITljm96.mjs → presets-CeFtfDR8.mjs} +1 -2
  110. package/dist/{prisma-Dg9GoVdj.d.mts → prisma-C3iornoK.d.mts} +2 -3
  111. package/dist/prisma-DJbMt3yf.mjs +1 -2
  112. package/dist/{queryCachePlugin-DMBnp2Q0.mjs → queryCachePlugin-B6R0d4av.mjs} +4 -5
  113. package/dist/{queryCachePlugin-7THaI5mt.d.mts → queryCachePlugin-Q6SYuHZ6.d.mts} +2 -3
  114. package/dist/{redis-D-JAeLtm.d.mts → redis-UwjEp8Ea.d.mts} +2 -3
  115. package/dist/{redis-stream-Bdh_vUU8.d.mts → redis-stream-CBg0upHI.d.mts} +2 -3
  116. package/dist/registry/index.d.mts +4 -5
  117. package/dist/registry/index.mjs +2 -2
  118. package/dist/{requestContext-QQD6ROJc.mjs → requestContext-xi6OKBL-.mjs} +1 -2
  119. package/dist/{schemaConverter-BwrmWroW.mjs → schemaConverter-Dtg0Kt9T.mjs} +1 -2
  120. package/dist/schemas/index.d.mts +1 -2
  121. package/dist/schemas/index.mjs +1 -2
  122. package/dist/scope/index.d.mts +2 -3
  123. package/dist/scope/index.mjs +2 -3
  124. package/dist/{sessionManager-jPKLbHE0.d.mts → sessionManager-D_iEHjQl.d.mts} +1 -2
  125. package/dist/{sse-B3c3_yZp.mjs → sse-DkqQ1uxb.mjs} +2 -3
  126. package/dist/testing/index.d.mts +8 -9
  127. package/dist/testing/index.mjs +3 -4
  128. package/dist/{tracing-Cc7vVQPp.d.mts → tracing-8CEbhF0w.d.mts} +1 -2
  129. package/dist/{typeGuards-DhMNLuvU.mjs → typeGuards-DwxA1t_L.mjs} +1 -2
  130. package/dist/types/index.d.mts +6 -7
  131. package/dist/types/index.mjs +1 -2
  132. package/dist/{types-CIgB7UUl.d.mts → types-B0dhNrnd.d.mts} +9 -10
  133. package/dist/types-Beqn1Un7.mjs +1 -2
  134. package/dist/types-DelU6kln.mjs +25 -0
  135. package/dist/{types-aYB4V7uN.d.mts → types-RLkFVgaw.d.mts} +18 -4
  136. package/dist/utils/index.d.mts +5 -6
  137. package/dist/utils/index.mjs +4 -4
  138. package/package.json +1 -1
  139. package/dist/EventTransport-BD2U0BTc.d.mts.map +0 -1
  140. package/dist/HookSystem-BsGV-j2l.mjs.map +0 -1
  141. package/dist/ResourceRegistry-DsN4KJjV.mjs.map +0 -1
  142. package/dist/audit/index.d.mts.map +0 -1
  143. package/dist/audit/index.mjs.map +0 -1
  144. package/dist/audited-C3T5DTUx.mjs.map +0 -1
  145. package/dist/auth/index.d.mts.map +0 -1
  146. package/dist/auth/index.mjs.map +0 -1
  147. package/dist/auth/redis-session.d.mts.map +0 -1
  148. package/dist/auth/redis-session.mjs.map +0 -1
  149. package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +0 -1
  150. package/dist/cache/index.d.mts.map +0 -1
  151. package/dist/cache/index.mjs.map +0 -1
  152. package/dist/caching-Bl28lYsR.mjs.map +0 -1
  153. package/dist/circuitBreaker-DeY4FCjs.mjs.map +0 -1
  154. package/dist/cli/commands/describe.d.mts.map +0 -1
  155. package/dist/cli/commands/describe.mjs.map +0 -1
  156. package/dist/cli/commands/docs.d.mts.map +0 -1
  157. package/dist/cli/commands/docs.mjs.map +0 -1
  158. package/dist/cli/commands/generate.d.mts.map +0 -1
  159. package/dist/cli/commands/generate.mjs.map +0 -1
  160. package/dist/cli/commands/init.d.mts.map +0 -1
  161. package/dist/cli/commands/init.mjs.map +0 -1
  162. package/dist/cli/commands/introspect.d.mts.map +0 -1
  163. package/dist/cli/commands/introspect.mjs.map +0 -1
  164. package/dist/cli/index.d.mts.map +0 -1
  165. package/dist/cli/index.mjs.map +0 -1
  166. package/dist/constants-DdXFXQtN.mjs.map +0 -1
  167. package/dist/createApp-CUgNqegw.mjs.map +0 -1
  168. package/dist/defineResource-k0_BDn8v.mjs.map +0 -1
  169. package/dist/discovery/index.d.mts.map +0 -1
  170. package/dist/discovery/index.mjs.map +0 -1
  171. package/dist/docs/index.d.mts.map +0 -1
  172. package/dist/docs/index.mjs.map +0 -1
  173. package/dist/elevation-BRy3yFWT.mjs.map +0 -1
  174. package/dist/elevation-B_2dRLVP.d.mts.map +0 -1
  175. package/dist/errorHandler-BbcgBmIH.d.mts.map +0 -1
  176. package/dist/errorHandler-C1okiriz.mjs.map +0 -1
  177. package/dist/errors-B9bZok84.mjs.map +0 -1
  178. package/dist/errors-ChKiFz62.d.mts.map +0 -1
  179. package/dist/eventPlugin-CTrLH3mt.d.mts.map +0 -1
  180. package/dist/eventPlugin-DGR_B2on.mjs.map +0 -1
  181. package/dist/events/index.d.mts.map +0 -1
  182. package/dist/events/index.mjs.map +0 -1
  183. package/dist/events/transports/redis-stream-entry.mjs.map +0 -1
  184. package/dist/events/transports/redis.d.mts.map +0 -1
  185. package/dist/events/transports/redis.mjs.map +0 -1
  186. package/dist/externalPaths-DlINfKbP.d.mts.map +0 -1
  187. package/dist/factory/index.d.mts.map +0 -1
  188. package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +0 -1
  189. package/dist/fields-DyaDVX4J.d.mts.map +0 -1
  190. package/dist/fields-iagOozy0.mjs.map +0 -1
  191. package/dist/idempotency/index.d.mts.map +0 -1
  192. package/dist/idempotency/index.mjs.map +0 -1
  193. package/dist/idempotency/mongodb.mjs.map +0 -1
  194. package/dist/idempotency/redis.mjs.map +0 -1
  195. package/dist/index.d.mts.map +0 -1
  196. package/dist/index.mjs.map +0 -1
  197. package/dist/integrations/event-gateway.d.mts.map +0 -1
  198. package/dist/integrations/event-gateway.mjs.map +0 -1
  199. package/dist/integrations/jobs.d.mts.map +0 -1
  200. package/dist/integrations/jobs.mjs.map +0 -1
  201. package/dist/integrations/streamline.d.mts.map +0 -1
  202. package/dist/integrations/streamline.mjs.map +0 -1
  203. package/dist/integrations/websocket.d.mts.map +0 -1
  204. package/dist/integrations/websocket.mjs.map +0 -1
  205. package/dist/interface-B01JvPVc.d.mts.map +0 -1
  206. package/dist/interface-CZe8IkMf.d.mts.map +0 -1
  207. package/dist/interface-Ch8HU9uM.d.mts.map +0 -1
  208. package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +0 -1
  209. package/dist/keys-BqNejWup.mjs.map +0 -1
  210. package/dist/logger-Df2O2WsW.mjs.map +0 -1
  211. package/dist/memory-cQgelFOj.mjs.map +0 -1
  212. package/dist/migrations/index.d.mts.map +0 -1
  213. package/dist/migrations/index.mjs.map +0 -1
  214. package/dist/mongodb-BfJVlUJH.mjs.map +0 -1
  215. package/dist/mongodb-CGzRbfAK.d.mts.map +0 -1
  216. package/dist/mongodb-JN-9JA7K.d.mts.map +0 -1
  217. package/dist/openapi-G3Cw7XuM.mjs.map +0 -1
  218. package/dist/org/index.d.mts.map +0 -1
  219. package/dist/org/index.mjs.map +0 -1
  220. package/dist/org/types.d.mts.map +0 -1
  221. package/dist/permissions/index.d.mts.map +0 -1
  222. package/dist/permissions/index.mjs.map +0 -1
  223. package/dist/plugins/index.d.mts.map +0 -1
  224. package/dist/plugins/index.mjs.map +0 -1
  225. package/dist/plugins/response-cache.d.mts.map +0 -1
  226. package/dist/plugins/response-cache.mjs.map +0 -1
  227. package/dist/plugins/tracing-entry.mjs.map +0 -1
  228. package/dist/pluralize-CEweyOEm.mjs.map +0 -1
  229. package/dist/policies/index.d.mts.map +0 -1
  230. package/dist/policies/index.mjs.map +0 -1
  231. package/dist/presets/index.d.mts.map +0 -1
  232. package/dist/presets/index.mjs.map +0 -1
  233. package/dist/presets/multiTenant.d.mts.map +0 -1
  234. package/dist/presets/multiTenant.mjs.map +0 -1
  235. package/dist/presets-BITljm96.mjs.map +0 -1
  236. package/dist/presets-DzSMwlKj.d.mts.map +0 -1
  237. package/dist/prisma-DJbMt3yf.mjs.map +0 -1
  238. package/dist/prisma-Dg9GoVdj.d.mts.map +0 -1
  239. package/dist/queryCachePlugin-7THaI5mt.d.mts.map +0 -1
  240. package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +0 -1
  241. package/dist/redis-D-JAeLtm.d.mts.map +0 -1
  242. package/dist/redis-stream-Bdh_vUU8.d.mts.map +0 -1
  243. package/dist/registry/index.d.mts.map +0 -1
  244. package/dist/requestContext-QQD6ROJc.mjs.map +0 -1
  245. package/dist/schemaConverter-BwrmWroW.mjs.map +0 -1
  246. package/dist/schemas/index.d.mts.map +0 -1
  247. package/dist/schemas/index.mjs.map +0 -1
  248. package/dist/scope/index.d.mts.map +0 -1
  249. package/dist/scope/index.mjs.map +0 -1
  250. package/dist/sessionManager-jPKLbHE0.d.mts.map +0 -1
  251. package/dist/sse-B3c3_yZp.mjs.map +0 -1
  252. package/dist/testing/index.d.mts.map +0 -1
  253. package/dist/testing/index.mjs.map +0 -1
  254. package/dist/tracing-Cc7vVQPp.d.mts.map +0 -1
  255. package/dist/typeGuards-DhMNLuvU.mjs.map +0 -1
  256. package/dist/types/index.d.mts.map +0 -1
  257. package/dist/types/index.mjs.map +0 -1
  258. package/dist/types-Beqn1Un7.mjs.map +0 -1
  259. package/dist/types-CIgB7UUl.d.mts.map +0 -1
  260. package/dist/types-aYB4V7uN.d.mts.map +0 -1
  261. package/dist/utils/index.d.mts.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"defineResource-k0_BDn8v.mjs","names":["getOrgIdFromScope","getOrgIdFromScope","getOrgIdFromScope"],"sources":["../src/core/AccessControl.ts","../src/core/BodySanitizer.ts","../src/core/QueryResolver.ts","../src/core/BaseController.ts","../src/core/fastifyAdapter.ts","../src/pipeline/pipe.ts","../src/core/createCrudRouter.ts","../src/core/createActionRouter.ts","../src/core/validateResourceConfig.ts","../src/core/defineResource.ts"],"sourcesContent":["/**\n * AccessControl - Composable access control logic extracted from BaseController.\n *\n * Handles ID filtering, policy filter checking, org/tenant scope validation,\n * ownership verification, and fetch-with-access-control patterns.\n *\n * Designed to be used standalone or composed into controllers.\n */\n\nimport type {\n AnyRecord,\n ArcInternalMetadata,\n IRequestContext,\n RequestContext,\n} from '../types/index.js';\nimport { isElevated, isMember, getOrgId as getOrgIdFromScope } from '../scope/types.js';\nimport { MAX_REGEX_LENGTH } from '../constants.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface AccessControlConfig {\n /** Field name used for multi-tenant scoping (default: 'organizationId') */\n tenantField: string;\n /** Primary key field name (default: '_id') */\n idField: string;\n /**\n * Custom filter matching for policy enforcement.\n * Provided by the DataAdapter for non-MongoDB databases (SQL, etc.).\n * Falls back to built-in MongoDB-style matching if not provided.\n */\n matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;\n}\n\n/** Minimal repository interface for access-controlled fetch operations */\nexport interface AccessControlRepository {\n getById(id: string, options?: unknown): Promise<unknown>;\n getOne?: (filter: AnyRecord, options?: unknown) => Promise<unknown>;\n}\n\n// ============================================================================\n// AccessControl Class\n// ============================================================================\n\nexport class AccessControl {\n private readonly tenantField: string;\n private readonly idField: string;\n private readonly _adapterMatchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;\n\n /** Patterns that indicate dangerous regex (nested quantifiers, excessive backtracking).\n * Uses [^...] character classes instead of .+ to avoid backtracking in the detector itself. */\n private static readonly DANGEROUS_REGEX = /(\\{[0-9]+,\\}[^{]*\\{[0-9]+,\\})|(\\+[^+]*\\+)|(\\*[^*]*\\*)|(\\.\\*){3,}|\\\\1/;\n\n /** Forbidden paths that could lead to prototype pollution */\n private static readonly FORBIDDEN_PATHS = ['__proto__', 'constructor', 'prototype'];\n\n constructor(config: AccessControlConfig) {\n this.tenantField = config.tenantField;\n this.idField = config.idField;\n this._adapterMatchesFilter = config.matchesFilter;\n }\n\n // ============================================================================\n // Public Methods\n // ============================================================================\n\n /**\n * Build filter for single-item operations (get/update/delete)\n * Combines ID filter with policy/org filters for proper security enforcement\n */\n buildIdFilter(id: string, req: IRequestContext): AnyRecord {\n const filter: AnyRecord = { [this.idField]: id };\n const arcContext = this._meta(req);\n\n // Apply policy filters (set by permission middleware via req.metadata._policyFilters)\n const policyFilters = arcContext?._policyFilters;\n if (policyFilters) {\n Object.assign(filter, policyFilters);\n }\n\n // Apply org/tenant scope filter — derived from request.scope\n const scope = arcContext?._scope;\n const orgId = scope ? getOrgIdFromScope(scope) : undefined;\n if (orgId && !policyFilters?.[this.tenantField]) {\n filter[this.tenantField] = orgId;\n }\n\n return filter;\n }\n\n /**\n * Check if item matches policy filters (for get/update/delete operations)\n * Validates that fetched item satisfies all policy constraints\n *\n * Delegates to adapter-provided matchesFilter if available (for SQL, etc.),\n * otherwise falls back to built-in MongoDB-style matching.\n */\n checkPolicyFilters(item: AnyRecord, req: IRequestContext): boolean {\n // Policy filters are set by permission middleware via req.metadata._policyFilters\n const arcContext = this._meta(req);\n const policyFilters = arcContext?._policyFilters;\n if (!policyFilters) return true;\n\n // Prefer adapter-provided matching (supports SQL, Prisma, etc.)\n if (this._adapterMatchesFilter) {\n return this._adapterMatchesFilter(item, policyFilters);\n }\n\n // Fallback: built-in MongoDB-style matching\n return this.defaultMatchesPolicyFilters(item, policyFilters);\n }\n\n /**\n * Check org/tenant scope for a document — uses configurable tenantField.\n *\n * SECURITY: When org scope is active (orgId present), documents that are\n * missing the tenant field are DENIED by default. This prevents legacy or\n * unscoped records from leaking across tenants.\n */\n checkOrgScope(item: AnyRecord | null, arcContext: ArcInternalMetadata | RequestContext | undefined): boolean {\n const scope = (arcContext as ArcInternalMetadata | undefined)?._scope;\n const orgId = scope ? getOrgIdFromScope(scope) : undefined;\n if (!item || !orgId) return true;\n // Elevated scope without org → skip check (admin viewing all)\n if (scope && isElevated(scope) && !orgId) return true;\n const itemOrgId = item[this.tenantField];\n // SECURITY: Deny records missing the tenant field when org scope is active.\n // This prevents legacy/unscoped records from leaking across orgs.\n if (!itemOrgId) return false;\n return String(itemOrgId) === String(orgId);\n }\n\n /** Check ownership for update/delete (ownedByUser preset) */\n checkOwnership(item: AnyRecord | null, req: IRequestContext): boolean {\n // Ownership check would need to be passed via req.metadata\n const ownershipCheck = this._meta(req)?._ownershipCheck;\n if (!item || !ownershipCheck) return true;\n const { field, userId } = ownershipCheck;\n const itemOwnerId = item[field];\n if (!itemOwnerId) return true;\n return String(itemOwnerId) === String(userId);\n }\n\n /**\n * Fetch a single document with full access control enforcement.\n * Combines compound DB filter (ID + org + policy) with post-hoc fallback.\n *\n * Takes repository as a parameter to avoid coupling.\n *\n * Replaces the duplicated pattern in get/update/delete:\n * buildIdFilter -> getOne (or getById + checkOrgScope + checkPolicyFilters)\n */\n async fetchWithAccessControl<TDoc>(\n id: string,\n req: IRequestContext,\n repository: AccessControlRepository,\n queryOptions?: unknown,\n ): Promise<TDoc | null> {\n const compoundFilter = this.buildIdFilter(id, req);\n const hasCompoundFilters = Object.keys(compoundFilter).length > 1;\n\n try {\n if (hasCompoundFilters && typeof repository.getOne === 'function') {\n return await repository.getOne(compoundFilter, queryOptions) as TDoc | null;\n }\n\n // Fallback: getById + post-hoc security checks\n const item = await repository.getById(id, queryOptions) as TDoc | null;\n if (!item) return null;\n\n const arcContext = this._meta(req);\n if (!this.checkOrgScope(item as AnyRecord, arcContext) || !this.checkPolicyFilters(item as AnyRecord, req)) {\n return null;\n }\n\n return item;\n } catch (error: unknown) {\n // Repositories (MongoKit, etc.) may throw \"not found\" errors instead of returning null\n if (error instanceof Error && error.message?.includes('not found')) {\n return null;\n }\n throw error;\n }\n }\n\n // ============================================================================\n // Private Helpers\n // ============================================================================\n\n /** Extract typed Arc internal metadata from request */\n private _meta(req: IRequestContext): ArcInternalMetadata | undefined {\n return req.metadata as ArcInternalMetadata | undefined;\n }\n\n /**\n * Check if a value matches a MongoDB query operator\n */\n private matchesOperator(itemValue: unknown, operator: string, filterValue: unknown): boolean {\n const equalsByValue = (a: unknown, b: unknown): boolean => String(a) === String(b);\n\n switch (operator) {\n case '$eq':\n return equalsByValue(itemValue, filterValue);\n case '$ne':\n return !equalsByValue(itemValue, filterValue);\n case '$gt':\n return typeof itemValue === 'number' && typeof filterValue === 'number' && itemValue > filterValue;\n case '$gte':\n return typeof itemValue === 'number' && typeof filterValue === 'number' && itemValue >= filterValue;\n case '$lt':\n return typeof itemValue === 'number' && typeof filterValue === 'number' && itemValue < filterValue;\n case '$lte':\n return typeof itemValue === 'number' && typeof filterValue === 'number' && itemValue <= filterValue;\n case '$in':\n if (!Array.isArray(filterValue)) return false;\n if (Array.isArray(itemValue)) {\n return itemValue.some((v) => filterValue.some((fv) => equalsByValue(v, fv)));\n }\n return filterValue.some((fv) => equalsByValue(itemValue, fv));\n case '$nin':\n if (!Array.isArray(filterValue)) return false;\n if (Array.isArray(itemValue)) {\n return itemValue.every((v) => filterValue.every((fv) => !equalsByValue(v, fv)));\n }\n return filterValue.every((fv) => !equalsByValue(itemValue, fv));\n case '$exists':\n return filterValue ? itemValue !== undefined : itemValue === undefined;\n case '$regex':\n if (typeof itemValue === 'string' && (typeof filterValue === 'string' || filterValue instanceof RegExp)) {\n const regex = typeof filterValue === 'string'\n ? AccessControl.safeRegex(filterValue)\n : filterValue;\n return regex !== null && regex.test(itemValue);\n }\n return false;\n default:\n return false;\n }\n }\n\n /**\n * Check if item matches a single filter condition\n * Supports nested paths (e.g., \"owner.id\", \"metadata.status\")\n */\n private matchesFilter(item: AnyRecord, key: string, filterValue: unknown): boolean {\n // Support nested paths with dot notation\n const itemValue = key.includes('.') ? this.getNestedValue(item, key) : item[key];\n\n // Handle MongoDB query operators\n if (filterValue && typeof filterValue === 'object' && !Array.isArray(filterValue)) {\n const operators = Object.keys(filterValue);\n // Check if this is an operator object (e.g., { $in: [...], $ne: ... })\n if (operators.some(op => op.startsWith('$'))) {\n for (const [operator, opValue] of Object.entries(filterValue as AnyRecord)) {\n if (!this.matchesOperator(itemValue, operator, opValue)) {\n return false;\n }\n }\n return true;\n }\n }\n\n // MongoDB implicit array matching: { field: value } matches if field is\n // an array containing value. Check element-wise before falling back to\n // simple equality.\n if (Array.isArray(itemValue)) {\n return itemValue.some(v => String(v) === String(filterValue));\n }\n\n // Simple equality check - convert to strings for ObjectId compatibility\n // ObjectId instances are only === if they're the same reference,\n // so we need to compare string representations for value equality\n return String(itemValue) === String(filterValue);\n }\n\n /**\n * Built-in MongoDB-style policy filter matching.\n * Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex, $and, $or\n */\n private defaultMatchesPolicyFilters(item: AnyRecord, policyFilters: AnyRecord): boolean {\n // Handle $and operator\n if (policyFilters.$and && Array.isArray(policyFilters.$and)) {\n return policyFilters.$and.every((condition: AnyRecord) => {\n return Object.entries(condition).every(([key, value]) => {\n return this.matchesFilter(item, key, value);\n });\n });\n }\n\n // Handle $or operator\n if (policyFilters.$or && Array.isArray(policyFilters.$or)) {\n return policyFilters.$or.some((condition: AnyRecord) => {\n return Object.entries(condition).every(([key, value]) => {\n return this.matchesFilter(item, key, value);\n });\n });\n }\n\n // Check each policy filter constraint\n for (const [key, value] of Object.entries(policyFilters)) {\n // Skip MongoDB logical operators (already handled above)\n if (key.startsWith('$')) continue;\n\n if (!this.matchesFilter(item, key, value)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Get nested value from object using dot notation (e.g., \"owner.id\")\n * Security: Validates path against forbidden patterns to prevent prototype pollution\n */\n private getNestedValue(obj: AnyRecord, path: string): unknown {\n // Security: Prevent prototype pollution attacks\n if (AccessControl.FORBIDDEN_PATHS.some(p => path.toLowerCase().includes(p))) {\n return undefined;\n }\n\n const keys = path.split('.');\n let value: unknown = obj;\n\n for (const key of keys) {\n if (value == null) return undefined;\n // Security: Block forbidden keys at each level\n if (AccessControl.FORBIDDEN_PATHS.includes(key.toLowerCase())) {\n return undefined;\n }\n value = (value as AnyRecord)[key];\n }\n\n return value;\n }\n\n // ============================================================================\n // Static Helpers\n // ============================================================================\n\n /**\n * Create a safe RegExp from a string, guarding against ReDoS.\n * Returns null if the pattern is invalid or dangerous.\n */\n private static safeRegex(pattern: string): RegExp | null {\n if (pattern.length > MAX_REGEX_LENGTH) return null;\n if (AccessControl.DANGEROUS_REGEX.test(pattern)) return null;\n try {\n return new RegExp(pattern);\n } catch {\n return null;\n }\n }\n}\n","/**\n * BodySanitizer - Composable body sanitization logic extracted from BaseController.\n *\n * Strips readonly fields, system-managed fields, and applies field-level\n * write permissions from request bodies before create/update operations.\n *\n * Designed to be used standalone or composed into controllers.\n */\n\nimport type {\n AnyRecord,\n ArcInternalMetadata,\n IRequestContext,\n RouteSchemaOptions,\n} from '../types/index.js';\nimport { applyFieldWritePermissions, resolveEffectiveRoles } from '../permissions/fields.js';\nimport { isElevated, isMember, PUBLIC_SCOPE } from '../scope/types.js';\nimport { SYSTEM_FIELDS } from '../constants.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface BodySanitizerConfig {\n /** Schema options for field sanitization */\n schemaOptions: RouteSchemaOptions;\n}\n\n// ============================================================================\n// BodySanitizer Class\n// ============================================================================\n\nexport class BodySanitizer {\n private schemaOptions: RouteSchemaOptions;\n\n constructor(config: BodySanitizerConfig) {\n this.schemaOptions = config.schemaOptions;\n }\n\n /**\n * Strip readonly and system-managed fields from request body.\n * Prevents clients from overwriting _id, timestamps, __v, etc.\n *\n * Also applies field-level write permissions when the request has\n * field permission metadata.\n */\n sanitize(body: AnyRecord, _operation: 'create' | 'update', req?: IRequestContext, meta?: ArcInternalMetadata): AnyRecord {\n let sanitized = { ...body };\n\n // Strip universal system fields\n for (const field of SYSTEM_FIELDS) {\n delete sanitized[field];\n }\n\n // Strip fields marked as systemManaged or readonly in fieldRules\n const fieldRules = this.schemaOptions.fieldRules ?? {};\n for (const [field, rules] of Object.entries(fieldRules)) {\n if (rules.systemManaged || rules.readonly) {\n delete sanitized[field];\n }\n }\n\n // Apply field-level write permissions (strip fields user can't write)\n // Merges global user roles with org roles for org-scoped resources\n // Elevated scope (platform admin) skips field restrictions --\n // consistent with requireOrgRole() which also bypasses for elevated scope.\n if (req) {\n const arcContext = meta ?? (req.metadata as ArcInternalMetadata | undefined);\n const scope = arcContext?._scope ?? PUBLIC_SCOPE;\n if (!isElevated(scope)) {\n const fieldPerms = arcContext?.arc?.fields;\n if (fieldPerms) {\n const globalRoles = ((req.user as AnyRecord | undefined)?.roles ?? []) as string[];\n const orgRoles = isMember(scope) ? scope.orgRoles : [];\n const effectiveRoles = resolveEffectiveRoles(globalRoles, orgRoles);\n sanitized = applyFieldWritePermissions(sanitized, fieldPerms, effectiveRoles);\n }\n }\n }\n\n return sanitized;\n }\n}\n","/**\n * QueryResolver - Composable query resolution logic extracted from BaseController.\n *\n * Resolves a request into parsed query options (pagination, filters, sorting,\n * select, populate) in a single pass. Applies org/tenant scope and policy\n * filters from the request metadata.\n *\n * Designed to be used standalone or composed into controllers.\n */\n\nimport type {\n AnyRecord,\n ArcInternalMetadata,\n ControllerQueryOptions,\n IRequestContext,\n QueryParserInterface,\n RouteSchemaOptions,\n UserLike,\n} from '../types/index.js';\nimport { getOrgId as getOrgIdFromScope } from '../scope/types.js';\nimport { DEFAULT_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD } from '../constants.js';\nimport { ArcQueryParser } from '../utils/queryParser.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface QueryResolverConfig {\n /** Query parser instance (default: Arc built-in parser) */\n queryParser?: QueryParserInterface;\n /** Maximum limit for pagination (default: 100) */\n maxLimit?: number;\n /** Default limit for pagination (default: 20) */\n defaultLimit?: number;\n /** Default sort field (default: '-createdAt') */\n defaultSort?: string;\n /** Schema options for field sanitization */\n schemaOptions?: RouteSchemaOptions;\n /** Field name used for multi-tenant scoping (default: 'organizationId') */\n tenantField?: string;\n}\n\n// ============================================================================\n// Default Query Parser\n// ============================================================================\n\nconst defaultParser = new ArcQueryParser();\n\nexport function getDefaultQueryParser(): QueryParserInterface {\n return defaultParser;\n}\n\n// ============================================================================\n// QueryResolver Class\n// ============================================================================\n\nexport class QueryResolver {\n private queryParser: QueryParserInterface;\n private maxLimit: number;\n private defaultLimit: number;\n private defaultSort: string;\n private schemaOptions: RouteSchemaOptions;\n private tenantField: string;\n\n constructor(config: QueryResolverConfig = {}) {\n this.queryParser = config.queryParser ?? getDefaultQueryParser();\n this.maxLimit = config.maxLimit ?? 100;\n this.defaultLimit = config.defaultLimit ?? DEFAULT_LIMIT;\n this.defaultSort = config.defaultSort ?? DEFAULT_SORT;\n this.schemaOptions = config.schemaOptions ?? {};\n this.tenantField = config.tenantField ?? DEFAULT_TENANT_FIELD;\n }\n\n /**\n * Resolve a request into parsed query options -- ONE parse per request.\n * Combines what was previously _buildContext + _parseQueryOptions + _applyFilters.\n */\n resolve(req: IRequestContext, meta?: ArcInternalMetadata): ControllerQueryOptions {\n const parsed = this.queryParser.parse(req.query);\n const arcContext = meta ?? (req.metadata as ArcInternalMetadata | undefined);\n\n // Remove internal params from filters\n delete (parsed.filters as AnyRecord)?._policyFilters;\n\n // Enforce limits\n const limit = Math.min(Math.max(1, parsed.limit || this.defaultLimit), this.maxLimit);\n // Only set page if not using keyset pagination (after/cursor)\n const page = parsed.after ? undefined : (parsed.page ? Math.max(1, parsed.page) : 1);\n\n // Convert sort object to string if needed\n const sortString = parsed.sort\n ? Object.entries(parsed.sort)\n .map(([k, v]) => (v === -1 ? `-${k}` : k))\n .join(',')\n : this.defaultSort;\n\n // Use parsed.select if available, otherwise fall back to raw query string\n const selectString = this.selectToString(parsed.select) ?? (req.query?.select as string);\n\n // Build filters with org + policy scope applied\n const filters = { ...(parsed.filters as AnyRecord) };\n\n // Policy filters (set by permission middleware via req.metadata._policyFilters)\n const policyFilters = arcContext?._policyFilters;\n if (policyFilters) {\n Object.assign(filters, policyFilters);\n }\n\n // Org/tenant scope -- derived from request.scope via metadata\n const scope = arcContext?._scope;\n const orgId = scope ? getOrgIdFromScope(scope) : undefined;\n if (orgId && !policyFilters?.[this.tenantField]) {\n // Only set if not already set by multiTenant preset\n filters[this.tenantField] = orgId;\n }\n\n return {\n page,\n limit,\n sort: sortString,\n select: this.sanitizeSelect(selectString, this.schemaOptions),\n populate: this.sanitizePopulate(parsed.populate, this.schemaOptions),\n // Advanced populate options from MongoKit QueryParser (takes precedence over simple populate)\n populateOptions: parsed.populateOptions,\n filters,\n // MongoKit features\n search: parsed.search,\n after: parsed.after,\n user: req.user as UserLike | undefined,\n context: arcContext,\n };\n }\n\n // ============================================================================\n // Private Helpers\n // ============================================================================\n\n /**\n * Convert parsed select object to string format\n * Converts { name: 1, email: 1, password: 0 } -> 'name email -password'\n */\n private selectToString(select: string | string[] | Record<string, 0 | 1> | undefined): string | undefined {\n if (!select) return undefined;\n\n // Already a string\n if (typeof select === 'string') return select;\n\n // Array of fields\n if (Array.isArray(select)) return select.join(' ');\n\n // Object projection\n if (Object.keys(select).length === 0) return undefined;\n return Object.entries(select)\n .map(([field, include]) => (include === 0 ? `-${field}` : field))\n .join(' ');\n }\n\n /** Sanitize select fields */\n private sanitizeSelect(\n select: string | undefined,\n schemaOptions: RouteSchemaOptions\n ): string | undefined {\n if (!select) return undefined;\n\n const blockedFields = this.getBlockedFields(schemaOptions);\n if (blockedFields.length === 0) return select;\n\n const fields = select.split(/[\\s,]+/).filter(Boolean);\n const sanitized = fields.filter((f) => {\n const fieldName = f.replace(/^-/, '');\n return !blockedFields.includes(fieldName);\n });\n\n return sanitized.length > 0 ? sanitized.join(' ') : undefined;\n }\n\n /** Sanitize populate fields */\n private sanitizePopulate(\n populate: unknown,\n schemaOptions: RouteSchemaOptions\n ): string[] | undefined {\n if (!populate) return undefined;\n\n const allowedPopulate = (schemaOptions.query as AnyRecord | undefined)?.allowedPopulate as string[] | undefined;\n const requested = typeof populate === 'string'\n ? populate.split(',').map((p) => p.trim())\n : Array.isArray(populate) ? populate.map(String) : [];\n\n if (requested.length === 0) return undefined;\n\n // If no allowlist, allow all\n if (!allowedPopulate) return requested;\n\n const sanitized = requested.filter((p) => allowedPopulate.includes(p));\n return sanitized.length > 0 ? sanitized : undefined;\n }\n\n /** Get blocked fields from schema options */\n private getBlockedFields(schemaOptions: RouteSchemaOptions): string[] {\n const fieldRules = schemaOptions.fieldRules ?? {};\n return Object.entries(fieldRules)\n .filter(([, rules]) => rules.systemManaged || rules.hidden)\n .map(([field]) => field);\n }\n}\n","/**\n * Base Controller - Framework-Agnostic CRUD Operations\n *\n * Implements IController interface for framework portability.\n * Works with Fastify, Express, Next.js, or any framework via adapter pattern.\n *\n * Delegates to composed classes for separation of concerns:\n * - AccessControl: ID filtering, policy checks, org scope, ownership\n * - BodySanitizer: Field permissions, system fields\n * - QueryResolver: Parsing, pagination, sort, select/populate\n *\n * @example\n * import { BaseController } from '@classytic/arc';\n *\n * // Use Arc's default query parser (works out of the box)\n * class ProductController extends BaseController {\n * constructor(repository: CrudRepository) {\n * super(repository);\n * }\n * }\n *\n * // Or use MongoKit's parser for advanced MongoDB features ($lookup, aggregations)\n * import { QueryParser } from '@classytic/mongokit';\n * defineResource({\n * name: 'product',\n * queryParser: new QueryParser(),\n * // ...\n * });\n *\n * // Or use a custom parser for SQL databases\n * defineResource({\n * name: 'user',\n * queryParser: new PgQueryParser(),\n * // ...\n * });\n */\n\nimport type {\n AnyRecord,\n ArcInternalMetadata,\n IController,\n IControllerResponse,\n IRequestContext,\n PaginatedResult,\n PaginationParams,\n ParsedQuery,\n QueryParserInterface,\n ResourceCacheConfig,\n RouteSchemaOptions,\n UserLike,\n} from \"../types/index.js\";\nimport { getUserId } from \"../types/index.js\";\nimport {\n DEFAULT_LIMIT,\n DEFAULT_SORT,\n DEFAULT_ID_FIELD,\n DEFAULT_TENANT_FIELD,\n} from \"../constants.js\";\nimport { HookSystem } from \"../hooks/HookSystem.js\";\nimport { getOrgId as getOrgIdFromScope } from \"../scope/types.js\";\nimport { AccessControl } from \"./AccessControl.js\";\nimport { BodySanitizer } from \"./BodySanitizer.js\";\nimport { QueryResolver, getDefaultQueryParser } from \"./QueryResolver.js\";\nimport type { RepositoryLike } from \"../adapters/interface.js\";\nimport { buildQueryKey } from \"../cache/keys.js\";\nimport type { QueryCacheConfig } from \"../cache/QueryCache.js\";\n\n// ============================================================================\n// Controller Options\n// ============================================================================\n\nexport interface BaseControllerOptions {\n /** Schema options for field sanitization */\n schemaOptions?: RouteSchemaOptions;\n /**\n * Query parser instance.\n * Default: Arc built-in query parser (adapter-agnostic).\n * Swap in MongoKit QueryParser, pgkit parser, etc.\n */\n queryParser?: QueryParserInterface;\n /** Maximum limit for pagination (default: 100) */\n maxLimit?: number;\n /** Default limit for pagination (default: 20) */\n defaultLimit?: number;\n /** Default sort field (default: '-createdAt') */\n defaultSort?: string;\n /** Resource name for hook execution (e.g., 'product' -> 'product.created') */\n resourceName?: string;\n /**\n * Field name used for multi-tenant scoping (default: 'organizationId').\n * Override to match your schema: 'workspaceId', 'tenantId', 'teamId', etc.\n */\n tenantField?: string;\n /**\n * Primary key field name (default: '_id').\n * Override for non-MongoDB adapters (e.g., 'id' for SQL databases).\n */\n idField?: string;\n /**\n * Custom filter matching for policy enforcement.\n * Provided by the DataAdapter for non-MongoDB databases (SQL, etc.).\n * Falls back to built-in MongoDB-style matching if not provided.\n */\n matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;\n /** Cache configuration for the resource */\n cache?: ResourceCacheConfig;\n /** Internal preset fields map (slug, tree, etc.) */\n presetFields?: { slugField?: string; parentField?: string };\n}\n\n// ============================================================================\n// Base Controller\n// ============================================================================\n\n/**\n * Framework-agnostic base controller implementing IController.\n *\n * Composes AccessControl, BodySanitizer, and QueryResolver for clean\n * separation of concerns. CRUD methods delegate directly to these\n * composed classes — no intermediate wrapper methods.\n *\n * @template TDoc - The document type\n * @template TRepository - The repository type (defaults to RepositoryLike)\n */\nexport class BaseController<\n TDoc = AnyRecord,\n TRepository extends RepositoryLike = RepositoryLike,\n> implements IController<TDoc> {\n protected repository: TRepository;\n protected schemaOptions: RouteSchemaOptions;\n protected queryParser: QueryParserInterface;\n protected maxLimit: number;\n protected defaultLimit: number;\n protected defaultSort: string;\n protected resourceName?: string;\n protected tenantField: string;\n protected idField: string = DEFAULT_ID_FIELD;\n\n /** Composable access control (ID filtering, policy checks, org scope, ownership) */\n readonly accessControl: AccessControl;\n /** Composable body sanitization (field permissions, system fields) */\n readonly bodySanitizer: BodySanitizer;\n /** Composable query resolution (parsing, pagination, sort, select/populate) */\n readonly queryResolver: QueryResolver;\n\n private _matchesFilter?: (\n item: unknown,\n filters: Record<string, unknown>,\n ) => boolean;\n private _presetFields: { slugField?: string; parentField?: string } = {};\n private _cacheConfig?: ResourceCacheConfig;\n\n constructor(repository: TRepository, options: BaseControllerOptions = {}) {\n this.repository = repository;\n this.schemaOptions = options.schemaOptions ?? {};\n this.queryParser = options.queryParser ?? getDefaultQueryParser();\n this.maxLimit = options.maxLimit ?? 100;\n this.defaultLimit = options.defaultLimit ?? DEFAULT_LIMIT;\n this.defaultSort = options.defaultSort ?? DEFAULT_SORT;\n this.resourceName = options.resourceName;\n this.tenantField = options.tenantField ?? DEFAULT_TENANT_FIELD;\n this.idField = options.idField ?? DEFAULT_ID_FIELD;\n this._matchesFilter = options.matchesFilter;\n if (options.cache) this._cacheConfig = options.cache;\n if (options.presetFields) this._presetFields = options.presetFields;\n\n // Initialize composed classes\n this.accessControl = new AccessControl({\n tenantField: this.tenantField,\n idField: this.idField,\n matchesFilter: this._matchesFilter,\n });\n this.bodySanitizer = new BodySanitizer({\n schemaOptions: this.schemaOptions,\n });\n this.queryResolver = new QueryResolver({\n queryParser: this.queryParser,\n maxLimit: this.maxLimit,\n defaultLimit: this.defaultLimit,\n defaultSort: this.defaultSort,\n schemaOptions: this.schemaOptions,\n tenantField: this.tenantField,\n });\n\n // Bind CRUD methods\n this.list = this.list.bind(this);\n this.get = this.get.bind(this);\n this.create = this.create.bind(this);\n this.update = this.update.bind(this);\n this.delete = this.delete.bind(this);\n }\n\n // ============================================================================\n // Internal Helpers\n // ============================================================================\n\n /** Extract typed Arc internal metadata from request */\n private meta(req: IRequestContext): ArcInternalMetadata | undefined {\n return req.metadata as ArcInternalMetadata | undefined;\n }\n\n /** Get hook system from request context (instance-scoped) */\n private getHooks(req: IRequestContext): HookSystem | null {\n return this.meta(req)?.arc?.hooks ?? null;\n }\n\n // ============================================================================\n // Cache Helpers\n // ============================================================================\n\n /** Resolve cache config for a specific operation, merging per-op overrides */\n private resolveCacheConfig(\n operation: \"list\" | \"byId\",\n ): QueryCacheConfig | null {\n const cfg = this._cacheConfig;\n if (!cfg || cfg.disabled) return null;\n\n const opOverride = cfg[operation];\n return {\n staleTime: opOverride?.staleTime ?? cfg.staleTime ?? 0,\n gcTime: opOverride?.gcTime ?? cfg.gcTime ?? 60,\n tags: cfg.tags,\n };\n }\n\n /** Extract user/org IDs from request for cache key scoping */\n private cacheScope(req: IRequestContext): {\n userId?: string;\n orgId?: string;\n } {\n const userId = getUserId(req.user as UserLike | undefined);\n const arcContext = this.meta(req);\n const scope = arcContext?._scope;\n const orgId = scope ? getOrgIdFromScope(scope) : undefined;\n return { userId, orgId };\n }\n\n // ============================================================================\n // CRUD Operations\n // ============================================================================\n\n async list(\n req: IRequestContext,\n ): Promise<IControllerResponse<PaginatedResult<TDoc>>> {\n const options = this.queryResolver.resolve(req, this.meta(req));\n const cacheConfig = this.resolveCacheConfig(\"list\");\n const qc = req.server?.queryCache;\n\n // Cache-aware read\n if (cacheConfig && qc) {\n const version = await qc.getResourceVersion(this.resourceName!);\n const { userId, orgId } = this.cacheScope(req);\n const key = buildQueryKey(\n this.resourceName!,\n \"list\",\n version,\n options as Record<string, unknown>,\n userId,\n orgId,\n );\n const { data, status } = await qc.get<PaginatedResult<TDoc>>(key);\n\n if (status === \"fresh\") {\n return {\n success: true,\n data,\n status: 200,\n headers: { \"x-cache\": \"HIT\" },\n };\n }\n\n if (status === \"stale\") {\n // SWR: return stale data immediately, revalidate in background\n setImmediate(() => {\n this.executeListQuery(options, req)\n .then((fresh) => qc.set(key, fresh, cacheConfig))\n .catch(() => {});\n });\n return {\n success: true,\n data,\n status: 200,\n headers: { \"x-cache\": \"STALE\" },\n };\n }\n\n // MISS — execute query, cache result, return\n const result = await this.executeListQuery(options, req);\n await qc.set(key, result, cacheConfig);\n return {\n success: true,\n data: result,\n status: 200,\n headers: { \"x-cache\": \"MISS\" },\n };\n }\n\n // No cache — straight to DB\n const result = await this.executeListQuery(options, req);\n return { success: true, data: result, status: 200 };\n }\n\n /** Execute list query through hooks (extracted for cache revalidation) */\n private async executeListQuery(\n options: ParsedQuery,\n req: IRequestContext,\n ): Promise<PaginatedResult<TDoc>> {\n const hooks = this.getHooks(req);\n const repoGetAll = async () =>\n this.repository.getAll(options as PaginationParams<TDoc>);\n const result =\n hooks && this.resourceName\n ? await hooks.executeAround<unknown>(\n this.resourceName,\n \"list\",\n options as unknown,\n repoGetAll as () => Promise<unknown>,\n {\n user: req.user as UserLike | undefined,\n context: this.meta(req),\n },\n )\n : await repoGetAll();\n\n if (Array.isArray(result)) {\n return {\n docs: result as TDoc[],\n page: 1,\n limit: result.length,\n total: result.length,\n pages: 1,\n hasNext: false,\n hasPrev: false,\n };\n }\n\n return result as PaginatedResult<TDoc>;\n }\n\n async get(req: IRequestContext): Promise<IControllerResponse<TDoc>> {\n const id = req.params.id;\n if (!id) {\n return { success: false, error: \"ID parameter is required\", status: 400 };\n }\n\n const options = this.queryResolver.resolve(req, this.meta(req));\n const cacheConfig = this.resolveCacheConfig(\"byId\");\n const qc = req.server?.queryCache;\n\n // Cache-aware read\n if (cacheConfig && qc) {\n const version = await qc.getResourceVersion(this.resourceName!);\n const { userId, orgId } = this.cacheScope(req);\n const key = buildQueryKey(\n this.resourceName!,\n \"get\",\n version,\n { id, ...(options as Record<string, unknown>) },\n userId,\n orgId,\n );\n const { data, status } = await qc.get<TDoc>(key);\n\n if (status === \"fresh\") {\n return {\n success: true,\n data,\n status: 200,\n headers: { \"x-cache\": \"HIT\" },\n };\n }\n\n if (status === \"stale\") {\n setImmediate(() => {\n this.executeGetQuery(id, options, req)\n .then((fresh) => {\n if (fresh) qc.set(key, fresh, cacheConfig);\n })\n .catch(() => {});\n });\n return {\n success: true,\n data,\n status: 200,\n headers: { \"x-cache\": \"STALE\" },\n };\n }\n\n // MISS — execute, cache, return\n const item = await this.executeGetQuery(id, options, req);\n if (!item) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n await qc.set(key, item, cacheConfig);\n return {\n success: true,\n data: item,\n status: 200,\n headers: { \"x-cache\": \"MISS\" },\n };\n }\n\n // No cache\n try {\n const item = await this.executeGetQuery(id, options, req);\n if (!item) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n return { success: true, data: item, status: 200 };\n } catch (error: unknown) {\n if (error instanceof Error && error.message?.includes(\"not found\")) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n throw error;\n }\n }\n\n /** Execute get query through hooks (extracted for cache revalidation) */\n private async executeGetQuery(\n id: string,\n options: ParsedQuery,\n req: IRequestContext,\n ): Promise<TDoc | null> {\n const hooks = this.getHooks(req);\n const fetchItem = async () =>\n this.accessControl.fetchWithAccessControl<TDoc>(\n id,\n req,\n this.repository,\n options,\n );\n const item =\n hooks && this.resourceName\n ? await hooks.executeAround<TDoc | null>(\n this.resourceName,\n \"read\",\n null as TDoc | null,\n fetchItem,\n {\n user: req.user as UserLike | undefined,\n context: this.meta(req),\n },\n )\n : await fetchItem();\n return (item ?? null) as TDoc | null;\n }\n\n async create(req: IRequestContext): Promise<IControllerResponse<TDoc>> {\n const arcContext = this.meta(req);\n const data: AnyRecord = this.bodySanitizer.sanitize(\n (req.body ?? {}) as AnyRecord,\n \"create\",\n req,\n arcContext,\n );\n\n // Inject org/tenant scope\n const scope = arcContext?._scope;\n const createOrgId = scope ? getOrgIdFromScope(scope) : undefined;\n if (createOrgId) {\n data[this.tenantField] = createOrgId;\n }\n\n // Inject user reference\n const userId = getUserId(req.user as UserLike | undefined);\n if (userId) {\n data.createdBy = userId;\n }\n\n const hooks = this.getHooks(req);\n const user = req.user as UserLike | undefined;\n let processedData = data;\n if (hooks && this.resourceName) {\n try {\n processedData = await hooks.executeBefore(\n this.resourceName,\n \"create\",\n data,\n {\n user,\n context: arcContext,\n },\n );\n } catch (err) {\n return {\n success: false,\n error: \"Hook execution failed\",\n details: {\n code: \"BEFORE_CREATE_HOOK_ERROR\",\n message: (err as Error).message,\n },\n status: 400,\n };\n }\n }\n\n const repoCreate = async () =>\n this.repository.create(processedData as Partial<TDoc>, {\n user,\n context: arcContext,\n });\n\n let item: unknown;\n if (hooks && this.resourceName) {\n item = await hooks.executeAround(\n this.resourceName,\n \"create\",\n processedData,\n repoCreate,\n {\n user,\n context: arcContext,\n },\n );\n await hooks.executeAfter(this.resourceName, \"create\", item as AnyRecord, {\n user,\n context: arcContext,\n });\n } else {\n item = await repoCreate();\n }\n\n return {\n success: true,\n data: item as TDoc,\n status: 201,\n meta: { message: \"Created successfully\" },\n };\n }\n\n async update(req: IRequestContext): Promise<IControllerResponse<TDoc>> {\n const id = req.params.id;\n if (!id) {\n return { success: false, error: \"ID parameter is required\", status: 400 };\n }\n\n const arcContext = this.meta(req);\n const data: AnyRecord = this.bodySanitizer.sanitize(\n (req.body ?? {}) as AnyRecord,\n \"update\",\n req,\n arcContext,\n );\n const user = req.user as UserLike | undefined;\n\n const userId = getUserId(user);\n if (userId) {\n data.updatedBy = userId;\n }\n\n const existing = await this.accessControl.fetchWithAccessControl<TDoc>(\n id,\n req,\n this.repository,\n );\n\n if (!existing) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n\n if (!this.accessControl.checkOwnership(existing as AnyRecord, req)) {\n return {\n success: false,\n error: \"You do not have permission to modify this resource\",\n details: { code: \"OWNERSHIP_DENIED\" },\n status: 403,\n };\n }\n\n const hooks = this.getHooks(req);\n let processedData = data;\n if (hooks && this.resourceName) {\n try {\n processedData = await hooks.executeBefore(\n this.resourceName,\n \"update\",\n data,\n {\n user,\n context: arcContext,\n meta: { id, existing },\n },\n );\n } catch (err) {\n return {\n success: false,\n error: \"Hook execution failed\",\n details: {\n code: \"BEFORE_UPDATE_HOOK_ERROR\",\n message: (err as Error).message,\n },\n status: 400,\n };\n }\n }\n\n const repoUpdate = async () =>\n this.repository.update(id, processedData as Partial<TDoc>, {\n user,\n context: arcContext,\n });\n\n let item: unknown;\n if (hooks && this.resourceName) {\n item = await hooks.executeAround(\n this.resourceName,\n \"update\",\n processedData,\n repoUpdate,\n {\n user,\n context: arcContext,\n meta: { id, existing },\n },\n );\n if (item) {\n await hooks.executeAfter(\n this.resourceName,\n \"update\",\n item as AnyRecord,\n {\n user,\n context: arcContext,\n meta: { id, existing },\n },\n );\n }\n } else {\n item = await repoUpdate();\n }\n\n if (!item) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n\n return {\n success: true,\n data: item as TDoc,\n status: 200,\n meta: { message: \"Updated successfully\" },\n };\n }\n\n async delete(\n req: IRequestContext,\n ): Promise<IControllerResponse<{ message: string }>> {\n const id = req.params.id;\n if (!id) {\n return { success: false, error: \"ID parameter is required\", status: 400 };\n }\n\n const arcContext = this.meta(req);\n const user = req.user as UserLike | undefined;\n\n const existing = await this.accessControl.fetchWithAccessControl<TDoc>(\n id,\n req,\n this.repository,\n );\n\n if (!existing) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n\n if (!this.accessControl.checkOwnership(existing as AnyRecord, req)) {\n return {\n success: false,\n error: \"You do not have permission to delete this resource\",\n details: { code: \"OWNERSHIP_DENIED\" },\n status: 403,\n };\n }\n\n const hooks = this.getHooks(req);\n if (hooks && this.resourceName) {\n try {\n await hooks.executeBefore(\n this.resourceName,\n \"delete\",\n existing as AnyRecord,\n {\n user,\n context: arcContext,\n meta: { id },\n },\n );\n } catch (err) {\n return {\n success: false,\n error: \"Hook execution failed\",\n details: {\n code: \"BEFORE_DELETE_HOOK_ERROR\",\n message: (err as Error).message,\n },\n status: 400,\n };\n }\n }\n\n const repoDelete = async () =>\n this.repository.delete(id, {\n user,\n context: arcContext,\n });\n\n let result: unknown;\n if (hooks && this.resourceName) {\n result = await hooks.executeAround(\n this.resourceName,\n \"delete\",\n existing,\n repoDelete,\n {\n user,\n context: arcContext,\n meta: { id },\n },\n );\n } else {\n result = await repoDelete();\n }\n\n const deleteSuccess =\n typeof result === \"object\" && result !== null\n ? (result as { success?: boolean }).success\n : result;\n if (!deleteSuccess) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n\n if (hooks && this.resourceName) {\n await hooks.executeAfter(\n this.resourceName,\n \"delete\",\n existing as AnyRecord,\n {\n user,\n context: arcContext,\n meta: { id },\n },\n );\n }\n\n return {\n success: true,\n data: { message: \"Deleted successfully\" },\n status: 200,\n };\n }\n\n // ============================================================================\n // Preset Methods\n // ============================================================================\n\n async getBySlug(req: IRequestContext): Promise<IControllerResponse<TDoc>> {\n const repo = this.repository as TRepository & {\n getBySlug?: (slug: string, options?: unknown) => Promise<TDoc | null>;\n };\n if (!repo.getBySlug) {\n return {\n success: false,\n error: \"Slug lookup not implemented\",\n status: 501,\n };\n }\n\n const slugField = this._presetFields.slugField ?? \"slug\";\n const slug = (req.params[slugField] ?? req.params.slug) as string;\n const options = this.queryResolver.resolve(req, this.meta(req));\n const arcContext = this.meta(req);\n const item = await repo.getBySlug(slug, options);\n\n if (\n !item ||\n !this.accessControl.checkOrgScope(item as AnyRecord, arcContext)\n ) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n\n return { success: true, data: item as TDoc, status: 200 };\n }\n\n async getDeleted(\n req: IRequestContext,\n ): Promise<IControllerResponse<PaginatedResult<TDoc>>> {\n const repo = this.repository as TRepository & {\n getDeleted?: (\n options?: unknown,\n ) => Promise<TDoc[] | PaginatedResult<TDoc>>;\n };\n if (!repo.getDeleted) {\n return {\n success: false,\n error: \"Soft delete not implemented\",\n status: 501,\n };\n }\n\n const options = this.queryResolver.resolve(req, this.meta(req));\n const result = await repo.getDeleted(options);\n\n if (Array.isArray(result)) {\n return {\n success: true,\n data: {\n docs: result as TDoc[],\n page: 1,\n limit: result.length,\n total: result.length,\n pages: 1,\n hasNext: false,\n hasPrev: false,\n },\n status: 200,\n };\n }\n\n return {\n success: true,\n data: result as PaginatedResult<TDoc>,\n status: 200,\n };\n }\n\n async restore(req: IRequestContext): Promise<IControllerResponse<TDoc>> {\n const repo = this.repository as TRepository & {\n restore?: (id: string) => Promise<TDoc | null>;\n };\n if (!repo.restore) {\n return { success: false, error: \"Restore not implemented\", status: 501 };\n }\n\n const id = req.params.id;\n if (!id) {\n return { success: false, error: \"ID parameter is required\", status: 400 };\n }\n\n const item = await repo.restore(id);\n if (!item) {\n return { success: false, error: \"Resource not found\", status: 404 };\n }\n\n return {\n success: true,\n data: item as TDoc,\n status: 200,\n meta: { message: \"Restored successfully\" },\n };\n }\n\n async getTree(req: IRequestContext): Promise<IControllerResponse<TDoc[]>> {\n const repo = this.repository as TRepository & {\n getTree?: (options?: unknown) => Promise<TDoc[]>;\n };\n if (!repo.getTree) {\n return {\n success: false,\n error: \"Tree structure not implemented\",\n status: 501,\n };\n }\n\n const options = this.queryResolver.resolve(req, this.meta(req));\n const tree = await repo.getTree(options);\n\n return { success: true, data: tree as TDoc[], status: 200 };\n }\n\n async getChildren(\n req: IRequestContext,\n ): Promise<IControllerResponse<TDoc[]>> {\n const repo = this.repository as TRepository & {\n getChildren?: (parentId: string, options?: unknown) => Promise<TDoc[]>;\n };\n if (!repo.getChildren) {\n return {\n success: false,\n error: \"Tree structure not implemented\",\n status: 501,\n };\n }\n\n const parentField = this._presetFields.parentField ?? \"parent\";\n const parentId = (req.params[parentField] ??\n req.params.parent ??\n req.params.id) as string;\n const options = this.queryResolver.resolve(req, this.meta(req));\n const children = await repo.getChildren(parentId, options);\n\n return { success: true, data: children as TDoc[], status: 200 };\n }\n}\n\nexport default BaseController;\n","/**\n * Fastify Adapter for IController\n *\n * Converts between Fastify's request/reply and framework-agnostic IRequestContext/IControllerResponse.\n * This allows controllers implementing IController to work seamlessly with Fastify.\n */\n\nimport type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { FastifyInstance } from 'fastify';\nimport type {\n IController,\n IControllerResponse,\n IRequestContext,\n RequestWithExtras,\n RequestContext,\n ArcInternalMetadata,\n PaginatedResult,\n AnyRecord,\n} from '../types/index.js';\nimport type { ServerAccessor } from '../types/handlers.js';\nimport type { FieldPermissionMap } from '../permissions/fields.js';\nimport { applyFieldReadPermissions, resolveEffectiveRoles } from '../permissions/fields.js';\nimport type { RequestScope } from '../scope/types.js';\nimport { isElevated, isMember, getOrgId as getOrgIdFromScope, PUBLIC_SCOPE } from '../scope/types.js';\n\n/** Type guard for Mongoose-like documents with toObject() */\nfunction isMongooseDoc(obj: unknown): obj is { toObject(): Record<string, unknown> } {\n return !!obj && typeof obj === 'object' && 'toObject' in obj && typeof (obj as Record<string, unknown>).toObject === 'function';\n}\n\n/**\n * Apply field mask to a single object\n * Filters fields based on include/exclude rules\n */\nfunction applyFieldMaskToObject(\n obj: AnyRecord | null | undefined,\n fieldMask: { include?: string[]; exclude?: string[] }\n): AnyRecord | null | undefined {\n if (!obj || typeof obj !== 'object') return obj;\n\n // Normalize Mongoose documents to plain objects\n const plain = isMongooseDoc(obj) ? obj.toObject() as AnyRecord : obj;\n\n const { include, exclude } = fieldMask;\n\n // If include is specified, only include those fields\n if (include && include.length > 0) {\n const filtered: AnyRecord = {};\n for (const field of include) {\n if (field in plain) {\n filtered[field] = plain[field];\n }\n }\n return filtered;\n }\n\n // If exclude is specified, remove those fields\n if (exclude && exclude.length > 0) {\n const filtered: AnyRecord = { ...plain };\n for (const field of exclude) {\n delete filtered[field];\n }\n return filtered;\n }\n\n return plain;\n}\n\n/**\n * Apply field mask to response data (handles both objects and arrays)\n */\nfunction applyFieldMask<T>(\n data: T,\n fieldMask: { include?: string[]; exclude?: string[] } | undefined\n): T {\n if (!fieldMask) return data;\n\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map((item) => applyFieldMaskToObject(item as AnyRecord, fieldMask)) as T;\n }\n\n // Handle single objects\n if (data && typeof data === 'object') {\n return applyFieldMaskToObject(data as AnyRecord, fieldMask) as T;\n }\n\n return data;\n}\n\n/**\n * Create IRequestContext from Fastify request\n *\n * Extracts framework-agnostic context from Fastify-specific request object\n */\nexport function createRequestContext(req: FastifyRequest): IRequestContext {\n const reqWithExtras = req as RequestWithExtras;\n const requestContext = (reqWithExtras.context ?? {}) as RequestContext;\n\n // Build server accessor — exposes events, audit, and log without wrapHandler switching\n // Use 'in' checks because these decorators are only present when their plugins are registered\n const srv = req.server as unknown as Record<string, unknown> | undefined;\n const serverAccessor: ServerAccessor = {\n events: srv && 'events' in srv ? srv.events as ServerAccessor['events'] : undefined,\n audit: srv && 'audit' in srv ? srv.audit as ServerAccessor['audit'] : undefined,\n queryCache: srv && 'queryCache' in srv ? srv.queryCache as ServerAccessor['queryCache'] : undefined,\n log: req.log,\n };\n\n return {\n query: (reqWithExtras.query ?? {}) as Record<string, unknown>,\n body: (reqWithExtras.body ?? {}) as Record<string, unknown>,\n params: (reqWithExtras.params ?? {}) as Record<string, string>,\n headers: reqWithExtras.headers as Record<string, string | undefined>,\n user: reqWithExtras.user\n ? (() => {\n const user = reqWithExtras.user as AnyRecord;\n const rawId = user._id ?? user.id;\n const normalizedId = rawId ? String(rawId) : undefined;\n return {\n ...user,\n // Normalize ID for MongoDB compatibility\n id: normalizedId,\n _id: normalizedId,\n // Preserve original role/roles/permissions as-is\n // Devs can define their own authorization structure\n } as import('../permissions/types.js').UserBase;\n })()\n : null,\n // Typed org/auth context — use this in controller overrides\n context: requestContext,\n // Internal metadata — includes context + Arc internals\n metadata: {\n ...reqWithExtras.context,\n // Include Arc metadata for hook execution\n arc: reqWithExtras.arc,\n // Include scope for org ID and elevation checks\n _scope: reqWithExtras.scope as RequestScope | undefined,\n // Include ownership check for access control\n _ownershipCheck: reqWithExtras._ownershipCheck,\n // Policy filters — ONLY from trusted middleware (req._policyFilters)\n // SECURITY: Never merge user-supplied query._policyFilters — they are untrusted\n _policyFilters: reqWithExtras._policyFilters ?? {},\n // Include logger for logging\n log: reqWithExtras.log,\n },\n // Server accessor — publish events, log, and audit from any handler\n server: serverAccessor,\n };\n}\n\n/**\n * Get typed auth context from an IRequestContext.\n * Use this in controller overrides to access request context.\n *\n * For org scope, use `getControllerScope(req)` instead.\n */\nexport function getControllerContext(req: IRequestContext): RequestContext {\n return (req.context ?? req.metadata ?? {}) as RequestContext;\n}\n\n/**\n * Get request scope from an IRequestContext.\n * Returns the RequestScope set by auth adapters.\n */\nexport function getControllerScope(req: IRequestContext): RequestScope {\n return (req.metadata as ArcInternalMetadata | undefined)?._scope ?? PUBLIC_SCOPE;\n}\n\n/**\n * Compute per-field capability metadata for the current user.\n * Only includes fields that have restrictions — unrestricted fields\n * are omitted (frontend defaults to { readable: true, writable: true }).\n */\nfunction computeFieldCapabilities(\n fieldPerms: FieldPermissionMap,\n effectiveRoles: string[],\n): Record<string, { readable: boolean; writable: boolean }> {\n const caps: Record<string, { readable: boolean; writable: boolean }> = {};\n for (const [field, perm] of Object.entries(fieldPerms)) {\n let readable = true;\n let writable = true;\n switch (perm._type) {\n case 'hidden':\n readable = false;\n writable = false;\n break;\n case 'visibleTo':\n readable = perm.roles?.some((r) => effectiveRoles.includes(r)) ?? false;\n break;\n case 'writableBy':\n writable = perm.roles?.some((r) => effectiveRoles.includes(r)) ?? false;\n break;\n // redactFor: field is readable (but redacted) and writable — no restriction flags\n }\n caps[field] = { readable, writable };\n }\n return caps;\n}\n\n/**\n * Send IControllerResponse via Fastify reply\n *\n * Converts framework-agnostic response to Fastify response\n * Applies field masking if specified in request\n */\nexport function sendControllerResponse<T>(\n reply: FastifyReply,\n response: IControllerResponse<T>,\n request?: FastifyRequest\n): void {\n // Extract field mask from request if available\n const reqWithExtras = request as RequestWithExtras | undefined;\n const fieldMaskConfig = reqWithExtras?.fieldMask;\n\n // Extract field-level permissions from arc metadata (set by arcDecorator)\n const arcMeta = (reqWithExtras as unknown as AnyRecord | undefined)?.arc as AnyRecord | undefined;\n const scope = (reqWithExtras?.scope as RequestScope) ?? PUBLIC_SCOPE;\n\n // Elevated scope (platform admin) skips field restrictions —\n // consistent with requireOrgRole() and _sanitizeBody() bypass logic.\n const fieldPerms = isElevated(scope)\n ? undefined\n : arcMeta?.fields as FieldPermissionMap | undefined;\n\n // Only compute roles when field permissions require them\n const effectiveRoles = fieldPerms\n ? resolveEffectiveRoles(\n ((reqWithExtras?.user as AnyRecord | undefined)?.roles ?? []) as string[],\n isMember(scope) ? scope.orgRoles : [],\n )\n : [];\n\n // Compute field capabilities metadata for frontend consumption (opt-in per resource)\n // Named `fieldCaps` to avoid variable shadowing with createCrudRouter's `fieldPermissions`\n const fieldCaps = fieldPerms\n ? computeFieldCapabilities(fieldPerms, effectiveRoles)\n : undefined;\n\n // Only create permission applicator when needed\n const hasFieldRestrictions = !!(fieldMaskConfig || fieldPerms);\n\n /** Apply both field mask and field-level permissions to a data item */\n const applyPermissions = <D>(data: D): D => {\n let result = fieldMaskConfig ? applyFieldMask(data, fieldMaskConfig) : data;\n if (fieldPerms && result && typeof result === 'object') {\n if (Array.isArray(result)) {\n result = result.map((item) =>\n applyFieldReadPermissions(item as AnyRecord, fieldPerms, effectiveRoles),\n ) as D;\n } else {\n result = applyFieldReadPermissions(result as AnyRecord, fieldPerms, effectiveRoles) as D;\n }\n }\n return result;\n };\n\n // Set custom response headers from controller\n if (response.headers) {\n for (const [key, value] of Object.entries(response.headers)) {\n reply.header(key, value);\n }\n }\n\n // Handle paginated responses specially (flatten to Arc's ApiResponse format)\n if (response.success && response.data && typeof response.data === 'object' && 'docs' in response.data) {\n const paginatedData = response.data as unknown as PaginatedResult<unknown>;\n const filteredDocs = hasFieldRestrictions ? applyPermissions(paginatedData.docs) : paginatedData.docs;\n\n reply.code(response.status ?? 200).send({\n success: true,\n docs: filteredDocs,\n page: paginatedData.page,\n limit: paginatedData.limit,\n total: paginatedData.total,\n pages: paginatedData.pages,\n hasNext: paginatedData.hasNext,\n hasPrev: paginatedData.hasPrev,\n ...(response.meta ?? {}),\n ...(fieldCaps ? { fieldPermissions: fieldCaps } : {}),\n });\n return;\n }\n\n // Handle standard responses\n const filteredData = hasFieldRestrictions ? applyPermissions(response.data) : response.data;\n\n reply.code(response.status ?? (response.success ? 200 : 400)).send({\n success: response.success,\n data: filteredData,\n error: response.error,\n details: response.details,\n ...(response.meta ?? {}),\n ...(fieldCaps ? { fieldPermissions: fieldCaps } : {}),\n });\n}\n\n/**\n * Create Fastify route handler from IController method\n *\n * Wraps framework-agnostic controller method in Fastify-specific handler\n *\n * @example\n * ```typescript\n * const controller = new BaseController(repository);\n *\n * // Create Fastify handler\n * const listHandler = createFastifyHandler(controller.list.bind(controller));\n *\n * // Register route\n * fastify.get('/products', listHandler);\n * ```\n */\nexport function createFastifyHandler<T>(\n controllerMethod: (req: IRequestContext) => Promise<IControllerResponse<T>>\n) {\n return async (req: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const requestContext = createRequestContext(req);\n const response = await controllerMethod(requestContext);\n sendControllerResponse(reply, response, req);\n };\n}\n\n/**\n * Create Fastify adapters for all CRUD methods of an IController\n *\n * Returns Fastify-compatible handlers for each CRUD operation\n *\n * @example\n * ```typescript\n * const controller = new BaseController(repository);\n * const handlers = createCrudHandlers(controller);\n *\n * fastify.get('/', handlers.list);\n * fastify.get('/:id', handlers.get);\n * fastify.post('/', handlers.create);\n * fastify.patch('/:id', handlers.update);\n * fastify.delete('/:id', handlers.delete);\n * ```\n */\nexport function createCrudHandlers<TDoc>(controller: IController<TDoc>) {\n return {\n list: createFastifyHandler(controller.list.bind(controller)),\n get: createFastifyHandler(controller.get.bind(controller)),\n create: createFastifyHandler(controller.create.bind(controller)),\n update: createFastifyHandler(controller.update.bind(controller)),\n delete: createFastifyHandler(controller.delete.bind(controller)),\n };\n}\n","/**\n * pipe() — Compose guards, transforms, and interceptors into a pipeline.\n *\n * Execution order:\n * auth → permission → orgScope → GUARDS → TRANSFORMS → handler (wrapped by INTERCEPTORS)\n *\n * @example\n * ```typescript\n * import { pipe, guard, transform, intercept, defineResource } from '@classytic/arc';\n *\n * // Compose a pipeline\n * const productResource = defineResource({\n * name: 'product',\n * adapter: productAdapter,\n * pipe: pipe(isActive, slugify, timing),\n * });\n *\n * // Per-operation pipelines\n * const productResource = defineResource({\n * name: 'product',\n * adapter: productAdapter,\n * pipe: {\n * create: pipe(isActive, slugify),\n * list: pipe(timing),\n * },\n * });\n * ```\n */\n\nimport type {\n PipelineStep,\n PipelineContext,\n Guard,\n Transform,\n Interceptor,\n NextFunction,\n} from './types.js';\nimport type { IControllerResponse } from '../types/index.js';\nimport { ForbiddenError } from '../utils/index.js';\n\n/**\n * Compose pipeline steps into an ordered array.\n * Accepts guards, transforms, and interceptors in any order.\n */\nexport function pipe(...steps: PipelineStep[]): PipelineStep[] {\n return steps;\n}\n\n/**\n * Check if a step applies to the given operation.\n */\nfunction appliesTo(step: PipelineStep, operation: string): boolean {\n if (!step.operations || step.operations.length === 0) return true;\n return step.operations.includes(operation);\n}\n\n/**\n * Execute a pipeline against a request context.\n *\n * This is the core runtime that createCrudRouter uses to execute pipelines.\n * External usage is not needed — this is wired automatically when `pipe` is set.\n *\n * @param steps - Pipeline steps to execute\n * @param ctx - The pipeline context (extends IRequestContext)\n * @param handler - The actual controller method to call\n * @param operation - The CRUD operation name\n * @returns The controller response (possibly modified by interceptors)\n */\nexport async function executePipeline(\n steps: PipelineStep[],\n ctx: PipelineContext,\n handler: (ctx: PipelineContext) => Promise<IControllerResponse<unknown>>,\n operation: string,\n): Promise<IControllerResponse<unknown>> {\n // Partition by type, filtered to applicable operations\n const guards: Guard[] = [];\n const transforms: Transform[] = [];\n const interceptors: Interceptor[] = [];\n\n for (const step of steps) {\n if (!appliesTo(step, operation)) continue;\n switch (step._type) {\n case 'guard':\n guards.push(step);\n break;\n case 'transform':\n transforms.push(step);\n break;\n case 'interceptor':\n interceptors.push(step);\n break;\n }\n }\n\n // Phase 1: Guards — must all pass\n for (const g of guards) {\n const result = await g.handler(ctx);\n if (!result) {\n throw new ForbiddenError(`Guard '${g.name}' denied access`);\n }\n }\n\n // Phase 2: Transforms — mutate context\n let currentCtx = ctx;\n for (const t of transforms) {\n const result = await t.handler(currentCtx);\n if (result) {\n currentCtx = result;\n }\n }\n\n // Phase 3: Interceptors — wrap handler in onion layers\n // Build the chain from inside-out (last interceptor wraps closest to handler)\n let chain: NextFunction = () => handler(currentCtx);\n\n for (let i = interceptors.length - 1; i >= 0; i--) {\n const interceptor = interceptors[i]!;\n const next = chain;\n chain = () => interceptor.handler(currentCtx, next);\n }\n\n return chain();\n}\n","/**\n * CRUD Router Factory\n *\n * Creates standard REST routes with permission-based access control.\n * Full TypeScript support with proper Fastify types.\n *\n * Features:\n * - Permission-based access control via PermissionCheck functions\n * - Multi-tenant scoping via multiTenant preset\n * - Consistent route patterns\n * - Framework-agnostic controllers via adapter pattern\n */\n\nimport type { FastifyInstance, FastifyRequest, FastifyReply, RouteHandlerMethod } from 'fastify';\nimport type {\n AdditionalRoute,\n CrudController,\n CrudRouterOptions,\n FastifyWithDecorators,\n IController,\n RateLimitConfig,\n RequestWithExtras,\n UserLike,\n} from '../types/index.js';\nimport type { ControllerHandler } from '../types/handlers.js';\nimport type { IControllerResponse, IRequestContext } from '../types/index.js';\nimport type { PermissionCheck, PermissionContext, PermissionResult } from '../permissions/types.js';\nimport type { PipelineConfig, PipelineStep, PipelineContext } from '../pipeline/types.js';\nimport { createCrudHandlers, createFastifyHandler, createRequestContext, sendControllerResponse } from './fastifyAdapter.js';\nimport { executePipeline } from '../pipeline/pipe.js';\nimport { getDefaultCrudSchemas } from '../utils/responseSchemas.js';\nimport { convertRouteSchema } from '../utils/schemaConverter.js';\nimport { requestContext } from '../context/requestContext.js';\nimport { CRUD_OPERATIONS, DEFAULT_UPDATE_METHOD } from '../constants.js';\n\n// ============================================================================\n// Rate Limit Helpers\n// ============================================================================\n\n/**\n * Route-level config shape for @fastify/rate-limit.\n *\n * When the plugin is registered on the instance, it reads `config.rateLimit`\n * from each route to apply per-route overrides.\n */\ninterface RouteRateLimitConfig {\n rateLimit: { max: number; timeWindow: string } | false;\n}\n\n/**\n * Build per-route rate limit config object.\n *\n * Returns a `config` object suitable for Fastify's `route()` options,\n * or `undefined` if no rate limit is configured for this resource.\n *\n * - `RateLimitConfig` object -> apply that limit to the route\n * - `false` -> explicitly disable rate limiting for the route\n * - `undefined` -> no override (inherits instance-level config)\n */\nfunction buildRateLimitConfig(\n rateLimit: RateLimitConfig | false | undefined\n): RouteRateLimitConfig | undefined {\n if (rateLimit === undefined) return undefined;\n\n if (rateLimit === false) {\n return { rateLimit: false };\n }\n\n return {\n rateLimit: {\n max: rateLimit.max,\n timeWindow: rateLimit.timeWindow,\n },\n };\n}\n\n// ============================================================================\n// Permission Helpers\n// ============================================================================\n\n/**\n * Check if a permission requires authentication\n *\n * A permission requires auth if:\n * - It exists AND\n * - It doesn't have _isPublic flag set to true\n *\n * This is used to automatically add fastify.authenticate\n * to the preHandler chain for non-public routes.\n */\nfunction requiresAuthentication(permission: PermissionCheck | undefined): boolean {\n if (!permission) return false; // No permission = public by default\n return !permission._isPublic;\n}\n\n/**\n * Build authentication middleware\n *\n * - Protected routes (requireAuth, requireRoles, etc.): uses fastify.authenticate (fails without token)\n * - Public routes (allowPublic): uses fastify.optionalAuthenticate (parses token if present, doesn't fail)\n *\n * This ensures request.user is populated on public routes when a Bearer token is sent,\n * enabling downstream middleware (e.g. multiTenant flexible filter) to apply org-scoped queries.\n */\nfunction buildAuthMiddleware(\n fastify: FastifyWithDecorators,\n permission: PermissionCheck | undefined\n): RouteHandlerMethod | null {\n if (requiresAuthentication(permission)) {\n // Protected route: require auth (401 if no token)\n return (fastify.authenticate as RouteHandlerMethod) ?? null;\n }\n // Public route: optionally parse auth to populate request.user\n return (fastify.optionalAuthenticate as RouteHandlerMethod) ?? null;\n}\n\n/**\n * Build permission middleware from PermissionCheck function\n *\n * Creates a Fastify preHandler that:\n * 1. Executes the permission check\n * 2. Returns 401 if authentication required but user absent\n * 3. Returns 403 if permission denied\n * 4. Applies query filters from PermissionResult if present\n */\nfunction buildPermissionMiddleware(\n permissionCheck: PermissionCheck | undefined,\n resourceName: string,\n action: string\n): RouteHandlerMethod | null {\n // No permission check = public route\n if (!permissionCheck) return null;\n\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const reqWithExtras = request as RequestWithExtras;\n\n // Build permission context\n const params = request.params as Record<string, string> | undefined;\n const context: PermissionContext = {\n user: (reqWithExtras.user as UserLike | undefined) ?? null,\n request,\n resource: resourceName,\n action,\n resourceId: params?.id,\n params,\n data: request.body as Record<string, unknown> | undefined,\n };\n\n // Execute permission check — catch exceptions so authz bugs don't become 500s\n let result: boolean | PermissionResult;\n try {\n result = await permissionCheck(context);\n } catch (err) {\n request.log?.warn?.({ err, resource: resourceName, action }, 'Permission check threw');\n reply.code(403).send({ success: false, error: 'Permission denied' });\n return;\n }\n\n // Handle boolean result\n if (typeof result === 'boolean') {\n if (!result) {\n reply.code(context.user ? 403 : 401).send({\n success: false,\n error: context.user ? 'Permission denied' : 'Authentication required',\n });\n return;\n }\n return;\n }\n\n // Handle PermissionResult\n const permResult = result as PermissionResult;\n if (!permResult.granted) {\n reply.code(context.user ? 403 : 401).send({\n success: false,\n error: permResult.reason ?? (context.user ? 'Permission denied' : 'Authentication required'),\n });\n return;\n }\n\n // Apply filters from permission result (for ownership patterns)\n if (permResult.filters) {\n reqWithExtras._policyFilters = {\n ...(reqWithExtras._policyFilters ?? {}),\n ...permResult.filters,\n };\n }\n };\n}\n\n/**\n * Create additional routes from preset/custom definitions\n */\nfunction createAdditionalRoutes<TDoc = unknown>(\n fastify: FastifyWithDecorators,\n routes: AdditionalRoute[],\n controller: CrudController<TDoc> | undefined,\n options: {\n tag: string;\n resourceName: string;\n arcDecorator: RouteHandlerMethod;\n rateLimitConfig?: RouteRateLimitConfig;\n cacheMw: RouteHandlerMethod | null;\n idempotencyMw: RouteHandlerMethod | null;\n pipeline?: PipelineConfig;\n }\n): void {\n const { tag, resourceName, arcDecorator, rateLimitConfig, cacheMw, idempotencyMw, pipeline } = options;\n\n for (const route of routes) {\n // Derive logical operation name for pipeline keys and permission actions.\n // Priority: explicit operation > handler name (string) > method+path slug\n const opName = route.operation\n ?? (typeof route.handler === 'string' ? route.handler : `${route.method.toLowerCase()}${route.path.replace(/[/:]/g, '_')}`);\n\n // Resolve handler - wrapHandler is REQUIRED (no auto-detection)\n let handler: RouteHandlerMethod;\n\n if (typeof route.handler === 'string') {\n // String handlers require a controller\n if (!controller) {\n throw new Error(\n `Route ${route.method} ${route.path}: string handler '${route.handler}' requires a controller. ` +\n 'Either provide a controller or use a function handler instead.'\n );\n }\n const ctrl = controller as unknown as Record<string, unknown>;\n const method = ctrl[route.handler];\n if (typeof method !== 'function') {\n throw new Error(`Handler '${route.handler}' not found on controller`);\n }\n // Bind method to controller\n const boundMethod = (method as Function).bind(controller);\n\n // Explicit wrapHandler - no auto-detection\n if (route.wrapHandler) {\n const steps = pipeline ? resolvePipelineSteps(pipeline, opName) : [];\n if (steps.length > 0) {\n handler = createPipelineHandler(\n boundMethod as (ctx: IRequestContext) => Promise<IControllerResponse<unknown>>,\n steps,\n opName,\n resourceName,\n );\n } else {\n handler = createFastifyHandler(boundMethod as ControllerHandler);\n }\n } else {\n handler = boundMethod as RouteHandlerMethod;\n }\n } else {\n // Function handler - use explicit wrapHandler\n if (route.wrapHandler) {\n const steps = pipeline ? resolvePipelineSteps(pipeline, opName) : [];\n if (steps.length > 0) {\n handler = createPipelineHandler(\n route.handler as (ctx: IRequestContext) => Promise<IControllerResponse<unknown>>,\n steps,\n opName,\n resourceName,\n );\n } else {\n handler = createFastifyHandler(route.handler as ControllerHandler);\n }\n } else {\n handler = route.handler as RouteHandlerMethod;\n }\n }\n\n // Build schema with tags (auto-convert Zod schemas, no-op for JSON Schema)\n const routeTags = route.tags ?? (tag ? [tag] : undefined);\n const convertedSchema = route.schema ? convertRouteSchema(route.schema) : undefined;\n const schema = {\n ...(routeTags ? { tags: routeTags } : {}),\n ...(route.summary ? { summary: route.summary } : {}),\n ...(route.description ? { description: route.description } : {}),\n ...(convertedSchema ?? {}),\n } as Record<string, unknown>;\n\n // Build preHandler chain: arc decorator → auth → permission check → custom middlewares\n const authMw = buildAuthMiddleware(fastify, route.permissions);\n const permissionMw = buildPermissionMiddleware(route.permissions, resourceName, opName);\n\n // Resolve preHandler - can be array or function that receives fastify\n const customPreHandlers = typeof route.preHandler === 'function'\n ? (route.preHandler as (fastify: FastifyWithDecorators) => RouteHandlerMethod[])(fastify)\n : (route.preHandler ?? []) as RouteHandlerMethod[];\n\n // Select plugin middleware based on HTTP method\n const pluginMw = route.method === 'GET'\n ? cacheMw\n : ['POST', 'PUT', 'PATCH'].includes(route.method) ? idempotencyMw : null;\n\n const preHandler = [\n arcDecorator,\n authMw, // Authenticate first (populates request.user)\n permissionMw, // Then check permissions\n pluginMw, // Cache (GET) or idempotency (mutations) — after auth\n ...customPreHandlers,\n ].filter(Boolean) as RouteHandlerMethod[];\n\n fastify.route({\n method: route.method,\n url: route.path,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema: schema as Record<string, any>, // Fastify RouteOptions.schema requires this shape\n preHandler: preHandler.length > 0 ? preHandler : undefined,\n handler,\n ...(rateLimitConfig ? { config: rateLimitConfig } : {}),\n });\n }\n}\n\n// ============================================================================\n// Pipeline Helpers\n// ============================================================================\n\n/**\n * Resolve pipeline steps for a specific operation.\n * If pipeline is a flat array, all steps are returned.\n * If it's a per-operation map, only matching steps are returned.\n */\nfunction resolvePipelineSteps(\n pipeline: PipelineConfig | undefined,\n operation: string,\n): PipelineStep[] {\n if (!pipeline) return [];\n if (Array.isArray(pipeline)) return pipeline;\n return pipeline[operation] ?? [];\n}\n\n/**\n * Create a Fastify handler that wraps a controller method with pipeline execution.\n */\nfunction createPipelineHandler<T>(\n controllerMethod: (ctx: IRequestContext) => Promise<IControllerResponse<T>>,\n steps: PipelineStep[],\n operation: string,\n resourceName: string,\n) {\n return async (req: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const reqCtx = createRequestContext(req);\n const pipeCtx: PipelineContext = {\n ...reqCtx,\n resource: resourceName,\n operation,\n };\n const response = await executePipeline(\n steps,\n pipeCtx,\n (ctx) => controllerMethod(ctx) as Promise<IControllerResponse<unknown>>,\n operation,\n );\n sendControllerResponse(reply, response as IControllerResponse<T>, req);\n };\n}\n\n/**\n * Create CRUD routes for a controller\n *\n * @param fastify - Fastify instance with Arc decorators\n * @param controller - CRUD controller with handler methods\n * @param options - Router configuration\n */\nexport function createCrudRouter<TDoc = unknown>(\n fastify: FastifyWithDecorators,\n controller: CrudController<TDoc> | undefined,\n options: CrudRouterOptions = {}\n): void {\n const {\n tag = 'Resource',\n schemas = {},\n permissions = {},\n middlewares = {},\n additionalRoutes = [],\n disableDefaultRoutes = false,\n disabledRoutes = [],\n resourceName = 'unknown',\n schemaOptions,\n rateLimit,\n pipe: pipeline,\n fields: fieldPermissions,\n updateMethod = DEFAULT_UPDATE_METHOD,\n } = options;\n\n // Build per-route rate limit config (applied to every route in this resource)\n const rateLimitConfig = buildRateLimitConfig(rateLimit);\n\n // Optional plugin middleware — injected AFTER auth+permissions, BEFORE custom middlewares.\n // These plugins expose route-level middleware that must run after auth populates\n // request.user, ensuring user-scoped cache keys and idempotency fingerprints.\n //\n // Skip response-cache when QueryCache is active for this resource.\n // QueryCache handles caching at the controller level (data-layer) with SWR,\n // so the HTTP-level response-cache is unnecessary and would cause double caching.\n const resourceHasQueryCache = fastify.hasDecorator('queryCache') &&\n controller && typeof (controller as unknown as Record<string, unknown>)._cacheConfig !== 'undefined' &&\n (controller as unknown as Record<string, unknown>)._cacheConfig !== undefined;\n const cacheMw: RouteHandlerMethod | null = (!resourceHasQueryCache && fastify.hasDecorator('responseCache'))\n ? fastify.responseCache.middleware as RouteHandlerMethod\n : null;\n const idempotencyMw: RouteHandlerMethod | null = fastify.hasDecorator('idempotency')\n ? fastify.idempotency.middleware as RouteHandlerMethod\n : null;\n\n // Pre-build frozen arc metadata — allocated once, shared across all requests\n const arcMeta = Object.freeze({\n resourceName,\n schemaOptions,\n permissions,\n // Include instance-scoped hooks if available (for proper isolation)\n hooks: fastify.arc?.hooks,\n // Include events emitter if available\n events: fastify.events,\n // Field-level permissions for response filtering\n fields: fieldPermissions,\n });\n\n // Arc metadata decorator - sets req.arc with resource configuration and instance-scoped systems\n const arcDecorator: RouteHandlerMethod = async (req, _reply) => {\n (req as unknown as { arc?: unknown }).arc = arcMeta;\n\n // Populate requestContext with resource name for downstream access\n const store = requestContext.get();\n if (store) {\n store.resourceName = resourceName;\n }\n };\n\n // Get middleware for each operation\n const mw = {\n list: (middlewares.list ?? []) as RouteHandlerMethod[],\n get: (middlewares.get ?? []) as RouteHandlerMethod[],\n create: (middlewares.create ?? []) as RouteHandlerMethod[],\n update: (middlewares.update ?? []) as RouteHandlerMethod[],\n delete: (middlewares.delete ?? []) as RouteHandlerMethod[],\n };\n\n // ID params schema\n const idParamsSchema = {\n type: 'object' as const,\n properties: { id: { type: 'string' as const } },\n required: ['id' as const],\n };\n\n // Default response/querystring schemas for fast-json-stringify serialization\n const defaultSchemas = getDefaultCrudSchemas();\n\n /**\n * Build route schema by merging: base (tags/summary) → defaults (response/querystring) → user overrides.\n * User-provided schemas always take precedence. Defaults enable fast-json-stringify when no user schema is set.\n */\n const buildSchema = (\n base: Record<string, unknown>,\n defaults: Record<string, unknown> | undefined,\n userSchema?: Record<string, unknown>,\n ): Record<string, unknown> => ({\n ...defaults,\n ...base,\n ...(userSchema ?? {}),\n });\n\n // Only validate and create handlers when default routes are enabled\n let handlers: ReturnType<typeof createCrudHandlers> | undefined;\n\n if (!disableDefaultRoutes) {\n // Controller is required for default CRUD routes\n if (!controller) {\n throw new Error(\n 'Controller is required when disableDefaultRoutes is not true. ' +\n 'Provide a controller or use defineResource which auto-creates BaseController.'\n );\n }\n\n const ctrl = controller as IController<TDoc>;\n\n // If pipeline is configured, wrap handlers with pipeline execution\n if (pipeline) {\n const ops = CRUD_OPERATIONS;\n const wrapped: Record<string, RouteHandlerMethod> = {};\n for (const op of ops) {\n const steps = resolvePipelineSteps(pipeline, op);\n if (steps.length > 0) {\n const method = ctrl[op].bind(ctrl) as (ctx: IRequestContext) => Promise<IControllerResponse<unknown>>;\n wrapped[op] = createPipelineHandler(\n method,\n steps,\n op,\n resourceName,\n );\n }\n }\n // Create standard handlers first, then override with pipeline-wrapped ones\n const standardHandlers = createCrudHandlers(ctrl);\n handlers = {\n ...standardHandlers,\n ...wrapped,\n };\n } else {\n handlers = createCrudHandlers(ctrl);\n }\n }\n\n // Standard CRUD routes\n if (!disableDefaultRoutes && handlers) {\n // GET / - List all\n if (!disabledRoutes.includes('list')) {\n const authMw = buildAuthMiddleware(fastify, permissions.list);\n const permMw = buildPermissionMiddleware(permissions.list, resourceName, 'list');\n const listPreHandler = [arcDecorator, authMw, permMw, cacheMw, ...mw.list].filter(Boolean) as RouteHandlerMethod[];\n fastify.route({\n method: 'GET',\n url: '/',\n schema: buildSchema({ tags: [tag], summary: `List ${tag}` }, defaultSchemas.list, schemas.list as Record<string, unknown> | undefined),\n preHandler: listPreHandler.length > 0 ? listPreHandler : undefined,\n handler: handlers.list,\n ...(rateLimitConfig ? { config: rateLimitConfig } : {}),\n });\n }\n\n // GET /:id - Get by ID\n if (!disabledRoutes.includes('get')) {\n const authMw = buildAuthMiddleware(fastify, permissions.get);\n const permMw = buildPermissionMiddleware(permissions.get, resourceName, 'get');\n const getPreHandler = [arcDecorator, authMw, permMw, cacheMw, ...mw.get].filter(Boolean) as RouteHandlerMethod[];\n fastify.route({\n method: 'GET',\n url: '/:id',\n schema: buildSchema({ tags: [tag], summary: `Get ${tag} by ID`, params: idParamsSchema }, defaultSchemas.get, schemas.get as Record<string, unknown> | undefined),\n preHandler: getPreHandler.length > 0 ? getPreHandler : undefined,\n handler: handlers.get,\n ...(rateLimitConfig ? { config: rateLimitConfig } : {}),\n });\n }\n\n // POST / - Create\n if (!disabledRoutes.includes('create')) {\n const authMw = buildAuthMiddleware(fastify, permissions.create);\n const permMw = buildPermissionMiddleware(permissions.create, resourceName, 'create');\n const createPreHandler = [arcDecorator, authMw, permMw, idempotencyMw, ...mw.create].filter(Boolean) as RouteHandlerMethod[];\n fastify.route({\n method: 'POST',\n url: '/',\n schema: buildSchema({ tags: [tag], summary: `Create ${tag}` }, defaultSchemas.create, schemas.create as Record<string, unknown> | undefined),\n preHandler: createPreHandler.length > 0 ? createPreHandler : undefined,\n handler: handlers.create,\n ...(rateLimitConfig ? { config: rateLimitConfig } : {}),\n });\n }\n\n // UPDATE /:id - PATCH, PUT, or both\n if (!disabledRoutes.includes('update')) {\n const updateMethods = updateMethod === 'both' ? ['PUT', 'PATCH'] as const : [updateMethod] as const;\n const authMw = buildAuthMiddleware(fastify, permissions.update);\n const permMw = buildPermissionMiddleware(permissions.update, resourceName, 'update');\n const updatePreHandler = [arcDecorator, authMw, permMw, idempotencyMw, ...mw.update].filter(Boolean) as RouteHandlerMethod[];\n for (const method of updateMethods) {\n fastify.route({\n method,\n url: '/:id',\n schema: buildSchema({ tags: [tag], summary: `${method === 'PUT' ? 'Replace' : 'Update'} ${tag}`, params: idParamsSchema }, defaultSchemas.update, schemas.update as Record<string, unknown> | undefined),\n preHandler: updatePreHandler.length > 0 ? updatePreHandler : undefined,\n handler: handlers.update,\n ...(rateLimitConfig ? { config: rateLimitConfig } : {}),\n });\n }\n }\n\n // DELETE /:id - Delete\n if (!disabledRoutes.includes('delete')) {\n const authMw = buildAuthMiddleware(fastify, permissions.delete);\n const permMw = buildPermissionMiddleware(permissions.delete, resourceName, 'delete');\n const deletePreHandler = [arcDecorator, authMw, permMw, ...mw.delete].filter(Boolean) as RouteHandlerMethod[];\n fastify.route({\n method: 'DELETE',\n url: '/:id',\n schema: buildSchema({ tags: [tag], summary: `Delete ${tag}`, params: idParamsSchema }, defaultSchemas.delete, schemas.delete as Record<string, unknown> | undefined),\n preHandler: deletePreHandler.length > 0 ? deletePreHandler : undefined,\n handler: handlers.delete,\n ...(rateLimitConfig ? { config: rateLimitConfig } : {}),\n });\n }\n }\n\n // Additional routes from presets and custom\n if (additionalRoutes.length > 0) {\n createAdditionalRoutes(fastify, additionalRoutes, controller, { tag, resourceName, arcDecorator, rateLimitConfig, cacheMw, idempotencyMw, pipeline });\n }\n}\n\n/**\n * Create permission middleware from PermissionCheck\n * Useful for custom route registration\n */\nexport function createPermissionMiddleware(\n permission: PermissionCheck,\n resourceName: string,\n action: string\n): RouteHandlerMethod | null {\n return buildPermissionMiddleware(permission, resourceName, action);\n}\n\nexport default createCrudRouter;\n","/**\n * Action Router Factory (Stripe Pattern)\n *\n * Consolidates multiple state-transition endpoints into a single unified action endpoint.\n * Instead of separate endpoints for each action (approve, dispatch, receive, cancel),\n * this creates one endpoint: POST /:id/action\n *\n * Benefits:\n * - 40% fewer endpoints\n * - Consistent permission checking\n * - Self-documenting via action enum\n * - Type-safe action validation\n * - Single audit point for all state transitions\n *\n * @example\n * import { createActionRouter } from '@classytic/arc';\n * import { requireRoles } from '@classytic/arc/permissions';\n *\n * createActionRouter(fastify, {\n * tag: 'Inventory - Transfers',\n * actions: {\n * approve: async (id, data, req) => transferService.approve(id, req.user),\n * dispatch: async (id, data, req) => transferService.dispatch(id, data.transport, req.user),\n * receive: async (id, data, req) => transferService.receive(id, data, req.user),\n * cancel: async (id, data, req) => transferService.cancel(id, data.reason, req.user),\n * },\n * actionPermissions: {\n * approve: requireRoles(['admin', 'warehouse-manager']),\n * dispatch: requireRoles(['admin', 'warehouse-staff']),\n * receive: requireRoles(['admin', 'store-manager']),\n * cancel: requireRoles(['admin']),\n * },\n * actionSchemas: {\n * dispatch: {\n * transport: { type: 'object', properties: { driver: { type: 'string' } } }\n * },\n * cancel: {\n * reason: { type: 'string', minLength: 10 }\n * },\n * }\n * });\n */\n\nimport type { FastifyInstance, FastifyRequest, FastifyReply, RouteOptions } from 'fastify';\nimport type { RequestWithExtras, PermissionCheck, PermissionContext, PermissionResult, UserBase } from '../types/index.js';\n\n/**\n * Action handler function\n * @param id - Resource ID\n * @param data - Action-specific data from request body\n * @param req - Full Fastify request object\n * @returns Action result (will be wrapped in success response)\n */\nexport type ActionHandler<TData = any, TResult = any> = (\n id: string,\n data: TData,\n req: RequestWithExtras\n) => Promise<TResult>;\n\n/**\n * Action router configuration\n */\nexport interface ActionRouterConfig {\n /**\n * OpenAPI tag for grouping routes\n */\n tag?: string;\n\n /**\n * Action handlers map\n * @example { approve: (id, data, req) => service.approve(id), ... }\n */\n actions: Record<string, ActionHandler>;\n\n /**\n * Per-action permission checks (PermissionCheck functions)\n * @example { approve: requireRoles(['admin', 'manager']), cancel: requireRoles(['admin']) }\n */\n actionPermissions?: Record<string, PermissionCheck>;\n\n /**\n * Per-action JSON schema for body validation\n * @example { dispatch: { transport: { type: 'object' } } }\n */\n actionSchemas?: Record<string, Record<string, any>>;\n\n /**\n * Global permission check applied to all actions (if action-specific not defined)\n */\n globalAuth?: PermissionCheck;\n\n /**\n * Optional idempotency service\n * If provided, will handle idempotency-key header\n */\n idempotencyService?: IdempotencyService;\n\n /**\n * Custom error handler for action execution failures\n * @param error - The error thrown by action handler\n * @param action - The action that failed\n * @param id - The resource ID\n * @returns Status code and error response\n */\n onError?: (\n error: Error,\n action: string,\n id: string\n ) => { statusCode: number; error: string; code?: string };\n}\n\n/**\n * Idempotency service interface\n * Apps can provide their own implementation\n */\nexport interface IdempotencyService {\n check(key: string, payload: any): Promise<{ isNew: boolean; existingResult?: any }>;\n complete(key: string | undefined, result: any): Promise<void>;\n fail(key: string | undefined, error: Error): Promise<void>;\n}\n\n/**\n * Create action-based state transition endpoint\n *\n * Registers: POST /:id/action\n * Body: { action: string, ...actionData }\n *\n * @param fastify - Fastify instance\n * @param config - Action router configuration\n */\nexport function createActionRouter(fastify: FastifyInstance, config: ActionRouterConfig): void {\n const {\n tag,\n actions,\n actionPermissions = {},\n actionSchemas = {},\n globalAuth,\n idempotencyService,\n onError,\n } = config;\n\n const actionEnum = Object.keys(actions);\n\n if (actionEnum.length === 0) {\n fastify.log.warn('[createActionRouter] No actions defined, skipping route creation');\n return;\n }\n\n // Build unified body schema with action-specific properties\n const bodyProperties: Record<string, any> = {\n action: {\n type: 'string',\n enum: actionEnum,\n description: `Action to perform: ${actionEnum.join(' | ')}`,\n },\n };\n\n // Add action-specific schema properties\n Object.entries(actionSchemas).forEach(([actionName, schema]) => {\n if (schema && typeof schema === 'object') {\n Object.entries(schema).forEach(([propName, propSchema]) => {\n bodyProperties[propName] = {\n ...propSchema,\n description: `${propSchema.description || ''} (for ${actionName} action)`.trim(),\n };\n });\n }\n });\n\n const routeSchema = {\n tags: tag ? [tag] : undefined,\n summary: `Perform action (${actionEnum.join('/')})`,\n description: buildActionDescription(actions, actionPermissions),\n params: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Resource ID' },\n },\n required: ['id'],\n },\n body: {\n type: 'object',\n properties: bodyProperties,\n required: ['action'],\n },\n response: {\n 200: {\n type: 'object',\n properties: {\n success: { type: 'boolean' },\n data: { type: 'object' },\n },\n },\n 400: {\n type: 'object',\n properties: {\n success: { type: 'boolean' },\n error: { type: 'string' },\n },\n },\n 403: {\n type: 'object',\n properties: {\n success: { type: 'boolean' },\n error: { type: 'string' },\n },\n },\n },\n };\n\n // Build preHandlers\n const preHandler = [];\n\n // Determine which actions require authentication\n const hasPublicActions = Object.entries(actionPermissions).some(\n ([, p]) => (p as PermissionCheck)?._isPublic\n ) || (globalAuth && (globalAuth as PermissionCheck)?._isPublic);\n const hasProtectedActions = Object.entries(actionPermissions).some(\n ([, p]) => !(p as PermissionCheck)?._isPublic\n ) || (globalAuth && !(globalAuth as PermissionCheck)?._isPublic);\n\n // If ALL actions are protected, use global auth preHandler.\n // If mixed (some public, some protected), defer auth to per-action check\n // to avoid rejecting unauthenticated requests for public actions.\n if (hasProtectedActions && !hasPublicActions && fastify.authenticate) {\n preHandler.push(fastify.authenticate);\n }\n\n // Register the unified action endpoint\n fastify.post(\n '/:id/action',\n {\n schema: routeSchema,\n preHandler: preHandler.length ? preHandler : undefined,\n },\n async (req: FastifyRequest, reply: FastifyReply) => {\n const { action, ...data } = req.body as { action: string; [key: string]: any };\n const { id } = req.params as { id: string };\n const rawIdempotencyKey = req.headers['idempotency-key'];\n const idempotencyKey = Array.isArray(rawIdempotencyKey)\n ? rawIdempotencyKey[0]\n : rawIdempotencyKey;\n\n // Validate action exists\n const handler = actions[action];\n if (!handler) {\n return reply.code(400).send({\n success: false,\n error: `Invalid action '${action}'. Valid actions: ${actionEnum.join(', ')}`,\n validActions: actionEnum,\n });\n }\n\n // Check permissions: action-specific first, then fallback to globalAuth\n const permissionCheck = actionPermissions[action] ?? globalAuth;\n\n // If mixed public/protected actions, authenticate per-action for protected ones\n if (hasPublicActions && hasProtectedActions && permissionCheck) {\n const isPublicAction = (permissionCheck as PermissionCheck)?._isPublic;\n if (!isPublicAction && fastify.authenticate) {\n try {\n await fastify.authenticate(req, reply);\n } catch {\n // Avoid double-send: authenticate may have already sent a 401\n if (!reply.sent) {\n return reply.code(401).send({\n success: false,\n error: 'Authentication required',\n });\n }\n return;\n }\n // authenticate may send reply without throwing (some implementations)\n if (reply.sent) return;\n }\n }\n\n if (permissionCheck) {\n const reqWithExtras = req as RequestWithExtras;\n const context: PermissionContext = {\n user: (reqWithExtras.user as UserBase | null) ?? null,\n request: req,\n resource: tag ?? 'action',\n action,\n resourceId: id,\n params: req.params as Record<string, string> | undefined,\n data,\n };\n\n // Wrap in try/catch so authz bugs don't produce 500s\n // (consistent with CRUD router's buildPermissionMiddleware)\n let result: boolean | PermissionResult;\n try {\n result = await permissionCheck(context);\n } catch (err) {\n req.log?.warn?.({ err, resource: tag ?? 'action', action }, 'Permission check threw');\n return reply.code(403).send({\n success: false,\n error: 'Permission denied',\n });\n }\n\n if (typeof result === 'boolean') {\n if (!result) {\n return reply.code(context.user ? 403 : 401).send({\n success: false,\n error: context.user ? `Permission denied for '${action}'` : 'Authentication required',\n });\n }\n } else {\n const permResult = result as PermissionResult;\n if (!permResult.granted) {\n return reply.code(context.user ? 403 : 401).send({\n success: false,\n error: permResult.reason ?? (context.user ? `Permission denied for '${action}'` : 'Authentication required'),\n });\n }\n }\n }\n\n try {\n // Idempotency check (optional)\n if (idempotencyKey && idempotencyService) {\n const user = (req as RequestWithExtras).user as UserBase | undefined;\n const payloadForHash = {\n action,\n id,\n data,\n userId: (user?._id as string | undefined)?.toString?.() || user?.id || null,\n };\n\n const idempotencyResult = await idempotencyService.check(\n idempotencyKey,\n payloadForHash\n );\n // Use 'in' to check presence, not truthiness — existingResult may be\n // a valid falsy value (0, false, '', null) from a previous execution.\n if (!idempotencyResult.isNew && 'existingResult' in idempotencyResult) {\n return reply.send({\n success: true,\n data: idempotencyResult.existingResult,\n cached: true,\n });\n }\n }\n\n // Execute the action handler\n const result = await handler(id, data, req as RequestWithExtras);\n\n if (idempotencyService) {\n await idempotencyService.complete(idempotencyKey, result);\n }\n\n return reply.send({\n success: true,\n data: result,\n });\n } catch (error) {\n if (idempotencyService) {\n await idempotencyService.fail(idempotencyKey, error as Error);\n }\n\n // Use custom error handler if provided\n if (onError) {\n const { statusCode, error: errorMsg, code } = onError(error as Error, action, id);\n return reply.code(statusCode).send({\n success: false,\n error: errorMsg,\n code,\n });\n }\n\n // Default error handling\n const err = error as Record<string, unknown>;\n const statusCode = (err.statusCode as number) || (err.status as number) || 500;\n const errorCode = (err.code as string) || 'ACTION_FAILED';\n\n if (statusCode >= 500) {\n req.log.error({ err: error, action, id }, 'Action handler error');\n }\n\n return reply.code(statusCode).send({\n success: false,\n error: err.message || `Failed to execute '${action}' action`,\n code: errorCode,\n });\n }\n }\n );\n\n fastify.log.debug(\n { actions: actionEnum, tag },\n '[createActionRouter] Registered action endpoint: POST /:id/action'\n );\n}\n\n/**\n * Build description with action details\n * Uses _roles metadata from PermissionCheck functions for OpenAPI docs\n */\nfunction buildActionDescription(\n actions: Record<string, ActionHandler>,\n actionPermissions: Record<string, PermissionCheck>\n): string {\n const lines = ['Unified action endpoint for state transitions.\\n\\n**Available actions:**'];\n\n Object.keys(actions).forEach((action) => {\n const perm = actionPermissions[action];\n const roles = (perm as PermissionCheck)?._roles;\n const roleStr = roles?.length ? ` (requires: ${roles.join(' or ')})` : '';\n lines.push(`- \\`${action}\\`${roleStr}`);\n });\n\n return lines.join('\\n');\n}\n","/**\n * Resource Configuration Validator\n *\n * Fail-fast validation at definition time.\n * Invalid configs throw immediately with clear, actionable errors.\n *\n * @example\n * const result = validateResourceConfig(config);\n * if (!result.valid) {\n * console.error(formatValidationErrors(result.errors));\n * }\n */\n\nimport type {\n AdditionalRoute,\n IController,\n PresetResult,\n ResourceConfig,\n} from '../types/index.js';\nimport { getAvailablePresets } from '../presets/index.js';\nimport { CRUD_OPERATIONS } from '../constants.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ConfigError {\n field: string;\n message: string;\n suggestion?: string;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: ConfigError[];\n warnings: ConfigError[];\n}\n\nexport interface ValidateOptions {\n /** Skip controller method validation (for testing) */\n skipControllerCheck?: boolean;\n /** Allow unknown preset names */\n allowUnknownPresets?: boolean;\n /** Custom valid permission keys beyond CRUD */\n additionalPermissionKeys?: string[];\n}\n\n// ============================================================================\n// Core Validation\n// ============================================================================\n\n/**\n * Validate a resource configuration\n */\nexport function validateResourceConfig(\n config: ResourceConfig,\n options: ValidateOptions = {}\n): ValidationResult {\n const errors: ConfigError[] = [];\n const warnings: ConfigError[] = [];\n\n // ========================================\n // Required Fields\n // ========================================\n\n if (!config.name) {\n errors.push({\n field: 'name',\n message: 'Resource name is required',\n suggestion: 'Add a unique resource name (e.g., \"product\", \"user\")',\n });\n } else if (!/^[a-z][a-z0-9-]*$/i.test(config.name)) {\n errors.push({\n field: 'name',\n message: `Invalid resource name \"${config.name}\"`,\n suggestion: 'Use alphanumeric characters and hyphens, starting with a letter',\n });\n }\n\n // Check if any CRUD routes will actually be created\n const crudRoutes = CRUD_OPERATIONS;\n const disabledRoutes = new Set(config.disabledRoutes ?? []);\n const enabledCrudRoutes = crudRoutes.filter(route => !disabledRoutes.has(route));\n const hasCrudRoutes = !config.disableDefaultRoutes && enabledCrudRoutes.length > 0;\n\n // Adapter is required when CRUD routes are enabled\n if (hasCrudRoutes) {\n if (!config.adapter) {\n errors.push({\n field: 'adapter',\n message: 'Data adapter is required when CRUD routes are enabled',\n suggestion: 'Provide an adapter: createMongooseAdapter({ model, repository })',\n });\n } else if (!config.adapter.repository) {\n errors.push({\n field: 'adapter.repository',\n message: 'Adapter must provide a repository',\n suggestion: 'Ensure your adapter returns a valid CrudRepository',\n });\n }\n\n // Controller is auto-created (BaseController) when not provided — this is\n // the intended default. No warning needed; it's not a misconfiguration.\n } else {\n // Service resources (no CRUD routes) don't need adapter or controller\n if (!config.adapter && !config.additionalRoutes?.length) {\n warnings.push({\n field: 'config',\n message: 'Resource has no adapter and no additionalRoutes',\n suggestion: 'Provide either adapter for CRUD or additionalRoutes for custom logic',\n });\n }\n }\n\n // Legacy validation removed - adapter pattern handles this\n\n // ========================================\n // Controller Method Validation\n // ========================================\n\n if (config.controller && !options.skipControllerCheck && !config.disableDefaultRoutes) {\n const ctrl = config.controller as any;\n\n // Check for IController methods (MongoKit-compatible standard)\n const requiredMethods = CRUD_OPERATIONS;\n for (const method of requiredMethods) {\n if (typeof ctrl[method] !== 'function') {\n errors.push({\n field: `controller.${method}`,\n message: `Missing required CRUD method \"${method}\"`,\n suggestion: 'Extend BaseController which implements IController interface',\n });\n }\n }\n }\n\n // Validate additional route handlers exist\n if (config.controller && config.additionalRoutes) {\n validateAdditionalRouteHandlers(config.controller, config.additionalRoutes, errors);\n }\n\n // ========================================\n // Permission Key Validation\n // ========================================\n\n if (config.permissions) {\n validatePermissionKeys(config, options, errors, warnings);\n }\n\n // ========================================\n // Preset Validation\n // ========================================\n\n if (config.presets && !options.allowUnknownPresets) {\n validatePresets(config.presets, errors, warnings);\n }\n\n // ========================================\n // Prefix Validation\n // ========================================\n\n if (config.prefix) {\n if (!config.prefix.startsWith('/')) {\n errors.push({\n field: 'prefix',\n message: `Prefix must start with \"/\" (got \"${config.prefix}\")`,\n suggestion: `Change to \"/${config.prefix}\"`,\n });\n }\n if (config.prefix.endsWith('/') && config.prefix !== '/') {\n warnings.push({\n field: 'prefix',\n message: `Prefix should not end with \"/\" (got \"${config.prefix}\")`,\n suggestion: `Change to \"${config.prefix.slice(0, -1)}\"`,\n });\n }\n }\n\n // ========================================\n // Additional Route Validation\n // ========================================\n\n if (config.additionalRoutes) {\n validateAdditionalRoutes(config.additionalRoutes, errors);\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction validateAdditionalRouteHandlers(\n controller: unknown,\n routes: AdditionalRoute[],\n errors: ConfigError[]\n): void {\n const ctrl = controller as Record<string, unknown>;\n\n for (const route of routes) {\n if (typeof route.handler === 'string') {\n if (typeof ctrl[route.handler] !== 'function') {\n errors.push({\n field: `additionalRoutes[${route.method} ${route.path}]`,\n message: `Handler \"${route.handler}\" not found on controller`,\n suggestion: `Add method \"${route.handler}\" to controller or use a function handler`,\n });\n }\n }\n }\n}\n\nfunction validatePermissionKeys(\n config: ResourceConfig,\n options: ValidateOptions,\n errors: ConfigError[],\n warnings: ConfigError[]\n): void {\n const validKeys = new Set([\n ...CRUD_OPERATIONS,\n ...(options.additionalPermissionKeys ?? []),\n ]);\n\n // Add keys from additional routes\n for (const route of config.additionalRoutes ?? []) {\n if (typeof route.handler === 'string') {\n validKeys.add(route.handler);\n }\n }\n\n // Add preset-specific keys\n for (const preset of config.presets ?? []) {\n const presetName = typeof preset === 'string' ? preset : (preset as { name: string }).name;\n if (presetName === 'softDelete') {\n validKeys.add('deleted');\n validKeys.add('restore');\n }\n if (presetName === 'slugLookup') {\n validKeys.add('getBySlug');\n }\n if (presetName === 'tree') {\n // Semantic keys (intuitive)\n validKeys.add('tree');\n validKeys.add('children');\n // Handler names (exact match)\n validKeys.add('getTree');\n validKeys.add('getChildren');\n }\n }\n\n for (const key of Object.keys(config.permissions ?? {})) {\n if (!validKeys.has(key)) {\n warnings.push({\n field: `permissions.${key}`,\n message: `Unknown permission key \"${key}\"`,\n suggestion: `Valid keys: ${Array.from(validKeys).join(', ')}`,\n });\n }\n }\n}\n\nfunction validatePresets(\n presets: Array<string | PresetResult | { name: string; [key: string]: unknown }>,\n errors: ConfigError[],\n warnings: ConfigError[]\n): void {\n const availablePresets = getAvailablePresets();\n\n for (const preset of presets) {\n // Skip validation for fully-resolved PresetResult objects (custom presets)\n // These have middlewares/additionalRoutes and are ready to use\n if (typeof preset === 'object' && ('middlewares' in preset || 'additionalRoutes' in preset)) {\n // This is a custom preset passed as PresetResult - skip registry validation\n continue;\n }\n\n const presetName = typeof preset === 'string' ? preset : preset.name;\n\n if (!availablePresets.includes(presetName)) {\n errors.push({\n field: 'presets',\n message: `Unknown preset \"${presetName}\"`,\n suggestion: `Available presets: ${availablePresets.join(', ')}`,\n });\n }\n\n // Validate preset options if object form (but not full PresetResult)\n if (typeof preset === 'object') {\n validatePresetOptions(preset, warnings);\n }\n }\n}\n\nfunction validatePresetOptions(\n preset: PresetResult | { name: string; [key: string]: unknown },\n warnings: ConfigError[]\n): void {\n const knownOptions: Record<string, string[]> = {\n slugLookup: ['slugField'],\n tree: ['parentField'],\n softDelete: ['deletedField'],\n ownedByUser: ['ownerField'],\n multiTenant: ['tenantField', 'allowPublic'],\n };\n\n const validOptions = knownOptions[preset.name] ?? [];\n const providedOptions = Object.keys(preset).filter((k) => k !== 'name');\n\n for (const opt of providedOptions) {\n if (!validOptions.includes(opt)) {\n warnings.push({\n field: `presets[${preset.name}].${opt}`,\n message: `Unknown option \"${opt}\" for preset \"${preset.name}\"`,\n suggestion: validOptions.length > 0\n ? `Valid options: ${validOptions.join(', ')}`\n : `Preset \"${preset.name}\" has no configurable options`,\n });\n }\n }\n}\n\nfunction validateAdditionalRoutes(\n routes: AdditionalRoute[],\n errors: ConfigError[]\n): void {\n const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'];\n const seenRoutes = new Set<string>();\n\n for (const [i, route] of routes.entries()) {\n // Method validation\n if (!validMethods.includes(route.method)) {\n errors.push({\n field: `additionalRoutes[${i}].method`,\n message: `Invalid HTTP method \"${route.method}\"`,\n suggestion: `Valid methods: ${validMethods.join(', ')}`,\n });\n }\n\n // Path validation\n if (!route.path) {\n errors.push({\n field: `additionalRoutes[${i}].path`,\n message: 'Route path is required',\n });\n } else if (!route.path.startsWith('/')) {\n errors.push({\n field: `additionalRoutes[${i}].path`,\n message: `Route path must start with \"/\" (got \"${route.path}\")`,\n suggestion: `Change to \"/${route.path}\"`,\n });\n }\n\n // Handler validation\n if (!route.handler) {\n errors.push({\n field: `additionalRoutes[${i}].handler`,\n message: 'Route handler is required',\n });\n }\n\n // Duplicate detection\n const routeKey = `${route.method} ${route.path}`;\n if (seenRoutes.has(routeKey)) {\n errors.push({\n field: `additionalRoutes[${i}]`,\n message: `Duplicate route \"${routeKey}\"`,\n });\n }\n seenRoutes.add(routeKey);\n }\n}\n\n// ============================================================================\n// Formatting\n// ============================================================================\n\n/**\n * Format validation errors for display\n */\nexport function formatValidationErrors(\n resourceName: string,\n result: ValidationResult\n): string {\n const lines: string[] = [];\n\n if (result.errors.length > 0) {\n lines.push(`Resource \"${resourceName}\" validation failed:`);\n lines.push('');\n lines.push('ERRORS:');\n for (const err of result.errors) {\n lines.push(` ✗ ${err.field}: ${err.message}`);\n if (err.suggestion) {\n lines.push(` → ${err.suggestion}`);\n }\n }\n }\n\n if (result.warnings.length > 0) {\n if (lines.length > 0) lines.push('');\n lines.push('WARNINGS:');\n for (const warn of result.warnings) {\n lines.push(` ⚠ ${warn.field}: ${warn.message}`);\n if (warn.suggestion) {\n lines.push(` → ${warn.suggestion}`);\n }\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Validate and throw if invalid\n */\nexport function assertValidConfig(\n config: ResourceConfig,\n options?: ValidateOptions\n): void {\n const result = validateResourceConfig(config, options);\n\n if (!result.valid) {\n const errorMsg = formatValidationErrors(config.name ?? 'unknown', result);\n throw new Error(errorMsg);\n }\n\n // Log warnings in development\n if (result.warnings.length > 0 && process.env.NODE_ENV !== 'production') {\n console.warn(formatValidationErrors(config.name ?? 'unknown', {\n valid: true,\n errors: [],\n warnings: result.warnings,\n }));\n }\n}\n\nexport default validateResourceConfig;\n","/**\n * Resource Definition - Database-Agnostic Single Source of Truth\n *\n * Core abstraction that reduces boilerplate by 60-80%.\n * Works with ANY database via the adapter pattern.\n *\n * @example Mongoose\n * ```typescript\n * import { defineResource, createMongooseAdapter } from '@classytic/arc';\n * import { allowPublic, requireRoles } from '@classytic/arc/permissions';\n *\n * export default defineResource({\n * name: 'product',\n * adapter: createMongooseAdapter({\n * model: ProductModel,\n * repository: productRepository,\n * }),\n * presets: ['softDelete', 'slugLookup'],\n * permissions: {\n * list: allowPublic(),\n * get: allowPublic(),\n * create: requireRoles(['admin']),\n * update: requireRoles(['admin']),\n * delete: requireRoles(['admin']),\n * },\n * });\n * ```\n *\n * @example Prisma (future)\n * ```typescript\n * export default defineResource({\n * name: 'user',\n * adapter: createPrismaAdapter({\n * client: prisma.user,\n * repository: userRepository,\n * }),\n * });\n * ```\n */\n\nimport type { FastifyPluginAsync } from \"fastify\";\nimport { hasEvents } from \"../utils/typeGuards.js\";\nimport type {\n AdditionalRoute,\n AnyRecord,\n CrudController,\n CrudRouteKey,\n CrudSchemas,\n EventDefinition,\n FastifyWithDecorators,\n IController,\n MiddlewareConfig,\n OpenApiSchemas,\n QueryParserInterface,\n RateLimitConfig,\n ResourceCacheConfig,\n ResourceConfig,\n ResourceMetadata,\n ResourcePermissions,\n RouteSchemaOptions,\n} from \"../types/index.js\";\nimport type { DataAdapter } from \"../adapters/interface.js\";\nimport { BaseController } from \"./BaseController.js\";\nimport { createCrudRouter } from \"./createCrudRouter.js\";\nimport { applyPresets } from \"../presets/index.js\";\nimport type { RegisterOptions } from \"../registry/ResourceRegistry.js\";\nimport { assertValidConfig } from \"./validateResourceConfig.js\";\nimport {\n convertOpenApiSchemas,\n convertRouteSchema,\n} from \"../utils/schemaConverter.js\";\nimport { CRUD_OPERATIONS } from \"../constants.js\";\n\ninterface ExtendedResourceConfig<\n TDoc = AnyRecord,\n> extends ResourceConfig<TDoc> {\n _appliedPresets?: string[];\n _controllerOptions?: {\n slugField?: string;\n parentField?: string;\n [key: string]: unknown;\n };\n _hooks?: Array<{\n presetName: string;\n operation: \"create\" | \"update\" | \"delete\" | \"read\" | \"list\";\n phase: \"before\" | \"after\";\n handler: (ctx: AnyRecord) => unknown;\n priority?: number;\n }>;\n}\n\n/**\n * Define a resource with database adapter\n *\n * This is the MAIN entry point for creating Arc resources.\n * The adapter provides both repository and schema metadata.\n */\nexport function defineResource<TDoc = AnyRecord>(\n config: ResourceConfig<TDoc>,\n): ResourceDefinition<TDoc> {\n // Fail-fast validation\n if (!config.skipValidation) {\n assertValidConfig(config as ResourceConfig<AnyRecord>, {\n skipControllerCheck: true,\n });\n\n // Validate permissions are PermissionCheck functions\n if (config.permissions) {\n for (const [key, value] of Object.entries(config.permissions)) {\n if (value !== undefined && typeof value !== \"function\") {\n throw new Error(\n `[Arc] Resource '${config.name}': permissions.${key} must be a PermissionCheck function.\\n` +\n `Use allowPublic(), requireAuth(), or requireRoles(['role']) from @classytic/arc/permissions.`,\n );\n }\n }\n }\n\n // Validate additionalRoutes\n for (const route of config.additionalRoutes ?? []) {\n if (typeof route.permissions !== \"function\") {\n throw new Error(\n `[Arc] Resource '${config.name}' route ${route.method} ${route.path}: ` +\n `permissions is required and must be a PermissionCheck function.\\n` +\n `Use allowPublic() or requireAuth() from @classytic/arc/permissions.`,\n );\n }\n if (typeof route.wrapHandler !== \"boolean\") {\n throw new Error(\n `[Arc] Resource '${config.name}' route ${route.method} ${route.path}: ` +\n `wrapHandler is required.\\n` +\n `Set true for ControllerHandler (context object) or false for FastifyHandler (req, reply).`,\n );\n }\n }\n }\n\n // Extract repository from adapter (if provided)\n const repository = config.adapter?.repository;\n\n // Check if any CRUD routes will actually be created\n const crudRoutes = CRUD_OPERATIONS;\n const disabledRoutes = new Set(config.disabledRoutes ?? []);\n const hasCrudRoutes =\n !config.disableDefaultRoutes &&\n crudRoutes.some((route) => !disabledRoutes.has(route));\n\n // 2. Track presets\n const originalPresets = (config.presets ?? []).map((p) =>\n typeof p === \"string\" ? p : (p as { name: string }).name,\n );\n\n // 3. Apply presets FIRST before controller instantiation\n const resolvedConfig = (\n config.presets?.length ? applyPresets(config, config.presets) : config\n ) as ExtendedResourceConfig<TDoc>;\n\n resolvedConfig._appliedPresets = originalPresets;\n\n // 4. Create or use provided controller using the full resolved config\n let controller = resolvedConfig.controller;\n if (!controller && hasCrudRoutes && repository) {\n // Auto-create BaseController if CRUD routes exist\n controller = new BaseController<TDoc>(repository, {\n resourceName: resolvedConfig.name,\n schemaOptions: resolvedConfig.schemaOptions,\n queryParser: resolvedConfig.queryParser as\n | QueryParserInterface\n | undefined,\n tenantField: resolvedConfig.tenantField,\n idField: resolvedConfig.idField,\n matchesFilter: config.adapter?.matchesFilter,\n cache: resolvedConfig.cache,\n presetFields: resolvedConfig._controllerOptions\n ? {\n slugField: resolvedConfig._controllerOptions.slugField,\n parentField: resolvedConfig._controllerOptions.parentField,\n }\n : undefined,\n }) as IController<TDoc>;\n }\n\n // 5. Build definition\n const resource = new ResourceDefinition({\n ...resolvedConfig,\n adapter: config.adapter,\n controller,\n } as ResolvedResourceConfig<TDoc>);\n\n // Validate controller methods\n if (!config.skipValidation && controller) {\n resource._validateControllerMethods();\n }\n\n // Collect hooks from presets — stored on resource, registered at plugin time via fastify.arc.hooks\n if (resolvedConfig._hooks?.length) {\n resource._pendingHooks.push(\n ...resolvedConfig._hooks.map((hook) => ({\n operation: hook.operation,\n phase: hook.phase,\n handler: hook.handler,\n priority: hook.priority ?? 10,\n })),\n );\n }\n\n // Auto-register with OpenAPI schemas\n if (!config.skipRegistry) {\n try {\n // Get schemas: user-provided or auto-generate from adapter\n let openApiSchemas: OpenApiSchemas | undefined = config.openApiSchemas;\n\n // Auto-generate if not provided and adapter supports it\n if (!openApiSchemas && config.adapter?.generateSchemas) {\n const generated = config.adapter.generateSchemas(config.schemaOptions);\n if (generated) {\n openApiSchemas = generated;\n }\n }\n\n // Auto-detect listQuery schema from queryParser (if not already provided)\n const queryParser = config.queryParser as\n | QueryParserInterface\n | undefined;\n if (!openApiSchemas?.listQuery && queryParser?.getQuerySchema) {\n const querySchema = queryParser.getQuerySchema();\n if (querySchema) {\n openApiSchemas = {\n ...openApiSchemas,\n listQuery: querySchema as unknown as AnyRecord,\n };\n }\n }\n\n // Auto-convert Zod schemas to JSON Schema (no-op for plain JSON Schema)\n if (openApiSchemas) {\n openApiSchemas = convertOpenApiSchemas(openApiSchemas);\n }\n\n // Store registry metadata for lazy registration when toPlugin() is called\n resource._registryMeta = {\n module: config.module,\n openApiSchemas,\n };\n } catch {\n // Schema generation errors are non-fatal — resource still works without OpenAPI metadata\n }\n }\n\n return resource;\n}\n\ninterface ResolvedResourceConfig<\n TDoc = AnyRecord,\n> extends ResourceConfig<TDoc> {\n _appliedPresets?: string[];\n _controllerOptions?: {\n slugField?: string;\n parentField?: string;\n [key: string]: unknown;\n };\n _pendingHooks?: Array<{\n operation: \"create\" | \"update\" | \"delete\" | \"read\" | \"list\";\n phase: \"before\" | \"after\";\n handler: (ctx: AnyRecord) => unknown;\n priority: number;\n }>;\n}\n\nexport class ResourceDefinition<TDoc = AnyRecord> {\n // Identity\n readonly name: string;\n readonly displayName: string;\n readonly tag: string;\n readonly prefix: string;\n\n // Adapter (database abstraction) - optional for service resources\n readonly adapter?: DataAdapter<TDoc>;\n\n // Controller\n readonly controller?: IController<TDoc>;\n\n // Schema & Validation\n readonly schemaOptions: RouteSchemaOptions;\n readonly customSchemas: CrudSchemas;\n\n // Security\n readonly permissions: ResourcePermissions;\n\n // Customization\n readonly additionalRoutes: AdditionalRoute[];\n readonly middlewares: MiddlewareConfig;\n readonly disableDefaultRoutes: boolean;\n readonly disabledRoutes: CrudRouteKey[];\n\n // Events\n readonly events: Record<string, EventDefinition>;\n\n // Rate limiting\n readonly rateLimit?: RateLimitConfig | false;\n\n // Update method\n readonly updateMethod?: \"PUT\" | \"PATCH\" | \"both\";\n\n // Pipeline\n readonly pipe?: import(\"../pipeline/types.js\").PipelineConfig;\n\n // Field-level permissions\n readonly fields?: import(\"../permissions/fields.js\").FieldPermissionMap;\n\n // Cache config\n readonly cache?: ResourceCacheConfig;\n\n // Presets tracking\n readonly _appliedPresets: string[];\n\n // Pending hooks from presets (registered at plugin time via fastify.arc.hooks)\n _pendingHooks: Array<{\n operation: \"create\" | \"update\" | \"delete\" | \"read\" | \"list\";\n phase: \"before\" | \"after\";\n handler: (ctx: AnyRecord) => unknown;\n priority: number;\n }>;\n\n // Registry metadata for lazy registration (populated by defineResource, consumed by toPlugin)\n _registryMeta?: RegisterOptions;\n\n constructor(config: ResolvedResourceConfig<TDoc>) {\n // Identity\n this.name = config.name;\n this.displayName = config.displayName ?? capitalize(config.name) + \"s\";\n this.tag = config.tag ?? this.displayName;\n this.prefix = config.prefix ?? `/${config.name}s`;\n\n // Adapter\n this.adapter = config.adapter;\n\n // Controller\n this.controller = config.controller as IController<TDoc> | undefined;\n\n // Schema & Validation\n this.schemaOptions = config.schemaOptions ?? {};\n this.customSchemas = config.customSchemas ?? {};\n\n // Security\n this.permissions = (config.permissions ?? {}) as ResourcePermissions;\n\n // Customization\n this.additionalRoutes = config.additionalRoutes ?? [];\n this.middlewares = config.middlewares ?? {};\n this.disableDefaultRoutes = config.disableDefaultRoutes ?? false;\n this.disabledRoutes = config.disabledRoutes ?? [];\n\n // Events\n this.events = config.events ?? {};\n\n // Rate limiting\n this.rateLimit = config.rateLimit;\n\n // Update method\n this.updateMethod = config.updateMethod;\n\n // Pipeline\n this.pipe = config.pipe;\n\n // Field-level permissions\n this.fields = config.fields;\n\n // Cache config\n this.cache = config.cache;\n\n // Presets tracking\n this._appliedPresets = config._appliedPresets ?? [];\n\n // Pending hooks from presets\n this._pendingHooks = config._pendingHooks ?? [];\n }\n\n /** Get repository from adapter (if available) */\n get repository() {\n return this.adapter?.repository;\n }\n\n _validateControllerMethods(): void {\n const errors: string[] = [];\n\n // Check if any CRUD routes will actually be created\n const crudRoutes = CRUD_OPERATIONS;\n const disabledRoutes = new Set(this.disabledRoutes ?? []);\n const enabledCrudRoutes = crudRoutes.filter(\n (route) => !disabledRoutes.has(route),\n );\n const hasCrudRoutes =\n !this.disableDefaultRoutes && enabledCrudRoutes.length > 0;\n\n if (hasCrudRoutes) {\n if (!this.controller) {\n errors.push(\"Controller is required when CRUD routes are enabled\");\n } else {\n const ctrl = this.controller as unknown as AnyRecord;\n // Only validate methods for enabled routes\n for (const method of enabledCrudRoutes) {\n if (typeof ctrl[method] !== \"function\") {\n errors.push(`CRUD method '${method}' not found on controller`);\n }\n }\n }\n }\n\n for (const route of this.additionalRoutes) {\n if (typeof route.handler === \"string\") {\n if (!this.controller) {\n errors.push(\n `Route ${route.method} ${route.path}: string handler '${route.handler}' requires a controller`,\n );\n } else {\n const ctrl = this.controller as unknown as Record<string, unknown>;\n if (typeof ctrl[route.handler] !== \"function\") {\n errors.push(\n `Route ${route.method} ${route.path}: handler '${route.handler}' not found`,\n );\n }\n }\n }\n }\n\n if (errors.length > 0) {\n const errorMsg = [\n `Resource '${this.name}' validation failed:`,\n ...errors.map((e) => ` - ${e}`),\n \"\",\n \"Ensure controller implements IController<TDoc> interface.\",\n \"For preset routes (softDelete, tree), add corresponding methods to controller.\",\n ].join(\"\\n\");\n\n throw new Error(errorMsg);\n }\n }\n\n toPlugin(): FastifyPluginAsync {\n const self = this;\n\n return async function resourcePlugin(fastify, _opts): Promise<void> {\n // Register with instance-scoped registry (if arc core plugin is loaded)\n const arc = (fastify as FastifyWithDecorators).arc;\n if (arc?.registry && self._registryMeta) {\n try {\n arc.registry.register(\n self as unknown as ResourceDefinition<AnyRecord>,\n self._registryMeta,\n );\n } catch (err) {\n fastify.log?.warn?.(\n `Failed to register resource '${self.name}' in registry: ${err instanceof Error ? err.message : err}`,\n );\n }\n }\n\n // Register pending hooks from presets with instance-scoped hook system\n if (self._pendingHooks.length > 0) {\n const arc = (fastify as FastifyWithDecorators).arc;\n if (arc?.hooks) {\n for (const hook of self._pendingHooks) {\n arc.hooks.register({\n resource: self.name,\n operation: hook.operation,\n phase: hook.phase,\n handler: hook.handler as (ctx: {\n resource: string;\n operation: string;\n phase: string;\n data?: AnyRecord;\n }) => AnyRecord | Promise<AnyRecord>,\n priority: hook.priority,\n });\n }\n }\n }\n\n // Register cross-resource cache invalidation rules\n const registerRule = (fastify as unknown as Record<string, unknown>)\n .registerCacheInvalidationRule;\n if (self.cache?.invalidateOn && typeof registerRule === \"function\") {\n for (const [pattern, tags] of Object.entries(self.cache.invalidateOn)) {\n (registerRule as (rule: { pattern: string; tags: string[] }) => void)(\n { pattern, tags },\n );\n }\n }\n\n await fastify.register(\n async (instance) => {\n const typedInstance = instance as FastifyWithDecorators;\n\n // Schema generation is handled at define-time (see defineResource, lines ~222-230).\n // No competing runtime generation here.\n let schemas: CrudSchemas | null = null;\n\n // Merge custom schemas (auto-convert Zod schemas within)\n // Uses convertRouteSchema which properly handles nested response schemas\n // e.g. { body: z.object(...), response: { 201: z.object(...) } }\n if (\n self.customSchemas &&\n Object.keys(self.customSchemas).length > 0\n ) {\n schemas = schemas ?? {};\n for (const [op, customSchema] of Object.entries(\n self.customSchemas,\n )) {\n const key = op as keyof CrudSchemas;\n const converted = convertRouteSchema(\n customSchema as Record<string, unknown>,\n );\n schemas[key] = schemas[key]\n ? deepMergeSchemas(\n schemas[key] as AnyRecord,\n converted as AnyRecord,\n )\n : (converted as AnyRecord);\n }\n }\n\n // Pass routes as-is to createCrudRouter\n // String handler resolution and wrapping is handled in createCrudRouter\n const resolvedRoutes = self.additionalRoutes;\n\n // Create CRUD routes\n createCrudRouter(\n typedInstance,\n self.controller as unknown as CrudController<TDoc>,\n {\n tag: self.tag,\n schemas: schemas ?? undefined,\n permissions: self.permissions,\n middlewares: self.middlewares,\n additionalRoutes: resolvedRoutes,\n disableDefaultRoutes: self.disableDefaultRoutes,\n disabledRoutes: self.disabledRoutes,\n resourceName: self.name,\n schemaOptions: self.schemaOptions,\n rateLimit: self.rateLimit,\n updateMethod: self.updateMethod,\n pipe: self.pipe,\n fields: self.fields,\n },\n );\n\n if (self.events && Object.keys(self.events).length > 0) {\n typedInstance.log?.debug?.(\n `Resource '${self.name}' defined ${Object.keys(self.events).length} events`,\n );\n }\n },\n { prefix: self.prefix },\n );\n\n // Emit resource lifecycle event (best-effort)\n if (hasEvents(fastify)) {\n try {\n await fastify.events.publish(\"arc.resource.registered\", {\n resource: self.name,\n prefix: self.prefix,\n presets: self._appliedPresets,\n timestamp: new Date().toISOString(),\n });\n } catch {\n /* lifecycle events are best-effort */\n }\n }\n };\n }\n\n /**\n * Get event definitions for registry\n */\n getEvents(): Array<{\n name: string;\n module: string;\n schema?: AnyRecord;\n description?: string;\n }> {\n return Object.entries(this.events).map(([action, meta]) => ({\n name: `${this.name}:${action}`,\n module: this.name,\n schema: meta.schema,\n description: meta.description,\n }));\n }\n\n /**\n * Get resource metadata\n */\n getMetadata(): ResourceMetadata {\n return {\n name: this.name,\n displayName: this.displayName,\n tag: this.tag,\n prefix: this.prefix,\n presets: this._appliedPresets,\n permissions: this.permissions,\n additionalRoutes: this.additionalRoutes,\n routes: [], // Populated at runtime during registration\n events: Object.keys(this.events),\n };\n }\n}\n\nfunction deepMergeSchemas(base: AnyRecord, override: AnyRecord): AnyRecord {\n if (!override) return base;\n if (!base) return override;\n\n const result: AnyRecord = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (Array.isArray(value) && Array.isArray(result[key])) {\n // Merge arrays with deduplication (e.g., required, enum)\n result[key] = [...new Set([...(result[key] as unknown[]), ...value])];\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = deepMergeSchemas(\n result[key] as AnyRecord,\n value as AnyRecord,\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction capitalize(str: string): string {\n if (!str) return \"\";\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport default defineResource;\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/discovery/index.ts"],"mappings":";;;;UA4CiB,oBAAA;EACf,QAAA,IAAY,kBAAA;EACZ,IAAA;EACA,UAAA;IAAe,IAAA;EAAA;AAAA;AAAA,UAGA,gBAAA;EAmGP;EAjGR,KAAA;EAgGA;;;;;EA1FA,OAAA;EA2FyD;EAzFzD,UAAA;EA2MW;EAzMX,MAAA,IAAU,QAAA,EAAU,oBAAA,EAAsB,QAAA;;EAE1C,UAAA,IAAc,IAAA,UAAc,QAAA;EAuMyC;EArMrE,SAAA;AAAA;AAAA,UAGe,sBAAA,SAA+B,gBAAA;;EAE9C,MAAA;AAAA;;;;;;iBA4EoB,iBAAA,CACpB,OAAA,EAAS,gBAAA,GACR,OAAA,CAAQ,KAAA;EAAQ,QAAA,EAAU,oBAAA;EAAsB,QAAA;AAAA;;cAkHtC,eAAA,EAAiB,kBAAA,CAAmB,sBAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/discovery/index.ts"],"sourcesContent":["/**\n * Arc Auto-Discovery Plugin\n *\n * Automatically discovers and registers resource files matching a glob pattern.\n * Eliminates manual resource registration boilerplate.\n *\n * This is a SEPARATE subpath import — only loaded when explicitly used:\n * import { discoveryPlugin, discoverResources } from '@classytic/arc/discovery';\n *\n * Serverless-safe: only runs at startup, no persistent process needed.\n *\n * @example\n * ```typescript\n * import { discoveryPlugin } from '@classytic/arc/discovery';\n *\n * // Auto-discovers all *.resource.ts files\n * await fastify.register(discoveryPlugin, {\n * paths: ['./src/modules'],\n * pattern: '**\\/*.resource.{ts,js}',\n * });\n *\n * // Or use the helper directly\n * import { discoverResources } from '@classytic/arc/discovery';\n *\n * const resources = await discoverResources({\n * paths: ['./src/modules'],\n * pattern: '**\\/*.resource.{ts,js}',\n * });\n *\n * for (const resource of resources) {\n * await fastify.register(resource.toPlugin());\n * }\n * ```\n */\nimport type { FastifyInstance, FastifyPluginAsync } from 'fastify';\nimport { readdir, stat } from 'node:fs/promises';\nimport { join, resolve, extname } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A discovered resource — must have toPlugin() method */\nexport interface DiscoverableResource {\n toPlugin(): FastifyPluginAsync;\n name?: string;\n definition?: { name: string };\n}\n\nexport interface DiscoveryOptions {\n /** Directories to scan (relative to cwd or absolute) */\n paths: string[];\n /**\n * File name pattern to match.\n * Supports simple globs: *.resource.ts, *.resource.js\n * Default: '*.resource.{ts,js}'\n */\n pattern?: string;\n /** Export name to look for in each file (default: 'default' then first ResourceDefinition) */\n exportName?: string;\n /** Filter function to include/exclude discovered resources */\n filter?: (resource: DiscoverableResource, filePath: string) => boolean;\n /** Called for each discovered resource (for logging) */\n onDiscover?: (name: string, filePath: string) => void;\n /** Whether to scan recursively (default: true) */\n recursive?: boolean;\n}\n\nexport interface DiscoveryPluginOptions extends DiscoveryOptions {\n /** URL prefix applied to all discovered resources */\n prefix?: string;\n}\n\n// ============================================================================\n// File Discovery (pure filesystem, no glob library needed)\n// ============================================================================\n\n/**\n * Match a filename against a simple pattern.\n * Supports: *.resource.ts, *.resource.js, *.resource.{ts,js}\n */\nfunction matchPattern(filename: string, pattern: string): boolean {\n // Expand {ts,js} brace patterns\n if (pattern.includes('{') && pattern.includes('}')) {\n const match = pattern.match(/\\{([^}]+)\\}/);\n if (match) {\n const alternatives = match[1]!.split(',').map((s) => s.trim());\n return alternatives.some((alt) => {\n const expanded = pattern.replace(match[0], alt);\n return matchPattern(filename, expanded);\n });\n }\n }\n\n // Simple wildcard: *.resource.ts → any file ending with .resource.ts\n if (pattern.startsWith('*')) {\n const suffix = pattern.slice(1);\n return filename.endsWith(suffix);\n }\n\n // Exact match\n return filename === pattern;\n}\n\n/**\n * Recursively scan directories for files matching a pattern.\n */\nasync function scanDirectory(\n dir: string,\n pattern: string,\n recursive: boolean\n): Promise<string[]> {\n const results: string[] = [];\n const resolvedDir = resolve(dir);\n\n const entries = await readdir(resolvedDir, { withFileTypes: true }).catch(\n () => [] as { name: string; isFile(): boolean; isDirectory(): boolean }[],\n );\n\n for (const entry of entries) {\n const fullPath = join(resolvedDir, String(entry.name));\n\n if (entry.isDirectory() && recursive) {\n const nested = await scanDirectory(fullPath, pattern, recursive);\n results.push(...nested);\n } else if (entry.isFile()) {\n // Strip any path prefix from pattern (e.g., **/*.resource.ts → *.resource.ts)\n const filePattern = pattern.replace(/^\\*\\*\\//, '');\n if (matchPattern(String(entry.name), filePattern)) {\n results.push(fullPath);\n }\n }\n }\n\n return results;\n}\n\n// ============================================================================\n// Resource Discovery\n// ============================================================================\n\n/**\n * Discover and import resource files.\n *\n * @returns Array of discovered resources with their file paths\n */\nexport async function discoverResources(\n options: DiscoveryOptions\n): Promise<Array<{ resource: DiscoverableResource; filePath: string }>> {\n const {\n paths,\n pattern = '*.resource.{ts,js}',\n exportName,\n filter,\n onDiscover,\n recursive = true,\n } = options;\n\n const discovered: Array<{ resource: DiscoverableResource; filePath: string }> = [];\n\n // Scan all directories\n const allFiles: string[] = [];\n for (const dir of paths) {\n const files = await scanDirectory(dir, pattern, recursive);\n allFiles.push(...files);\n }\n\n // Sort for deterministic registration order\n allFiles.sort();\n\n // Import each file and extract the resource\n for (const filePath of allFiles) {\n const ext = extname(filePath);\n // Only import .js and .mjs files (compiled output). .ts files need a loader.\n // In development with tsx/ts-node, .ts files work too.\n const fileUrl = pathToFileURL(filePath).href;\n\n let module: Record<string, unknown>;\n try {\n module = await import(fileUrl);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to import resource file: ${filePath}\\n${message}`);\n }\n\n // Find the resource export\n let resource: DiscoverableResource | null = null;\n\n // 1. Check specific export name\n if (exportName && module[exportName]) {\n resource = module[exportName] as DiscoverableResource;\n }\n\n // 2. Check default export\n if (!resource && module.default && typeof (module.default as Record<string, unknown>).toPlugin === 'function') {\n resource = module.default as DiscoverableResource;\n }\n\n // 3. Search for first export with toPlugin()\n if (!resource) {\n for (const value of Object.values(module)) {\n if (value && typeof value === 'object' && typeof (value as Record<string, unknown>).toPlugin === 'function') {\n resource = value as DiscoverableResource;\n break;\n }\n }\n }\n\n if (!resource) {\n throw new Error(\n `No resource found in: ${filePath}\\n` +\n 'Resource files must export an object with a toPlugin() method.\\n' +\n 'Use defineResource() or export the resource as default.'\n );\n }\n\n // Apply filter\n if (filter && !filter(resource, filePath)) {\n continue;\n }\n\n const name = resource.definition?.name ?? resource.name ?? filePath;\n onDiscover?.(name, filePath);\n discovered.push({ resource, filePath });\n }\n\n return discovered;\n}\n\n// ============================================================================\n// Fastify Plugin\n// ============================================================================\n\nconst discoveryPluginImpl: FastifyPluginAsync<DiscoveryPluginOptions> = async (\n fastify: FastifyInstance,\n options: DiscoveryPluginOptions\n) => {\n const { prefix, ...discoveryOptions } = options;\n\n const discovered = await discoverResources({\n ...discoveryOptions,\n onDiscover: discoveryOptions.onDiscover ?? ((name, filePath) => {\n fastify.log.debug({ resource: name, file: filePath }, 'Auto-discovered resource');\n }),\n });\n\n // Register all discovered resources\n for (const { resource } of discovered) {\n const plugin = resource.toPlugin();\n if (prefix) {\n await fastify.register(plugin, { prefix });\n } else {\n await fastify.register(plugin);\n }\n }\n\n fastify.log.debug(\n `Auto-discovery: registered ${discovered.length} resource(s)`\n );\n};\n\n/** Auto-discovery plugin for Arc resources */\nexport const discoveryPlugin: FastifyPluginAsync<DiscoveryPluginOptions> = discoveryPluginImpl;\nexport default discoveryPlugin;\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/docs/openapi.ts","../../src/docs/scalar.ts"],"mappings":";;;;;;;;UAuBiB,cAAA;EAgBf;EAdA,KAAA;EAgBa;EAdb,OAAA;EAcmB;EAZnB,WAAA;EAe0B;EAb1B,SAAA;EAoBU;EAlBV,MAAA;EAmBO;EAjBP,SAAA;EAmBW;EAjBX,SAAA;EAkBoB;EAhBpB,eAAA;EAmBiB;EAjBjB,UAAA,GAAa,MAAA;AAAA;AAAA,UAGE,WAAA;EACf,OAAA;EACA,IAAA;IACE,KAAA;IACA,OAAA;IACA,WAAA;EAAA;EAEF,OAAA,GAAU,KAAA;IAAQ,GAAA;IAAa,WAAA;EAAA;EAC/B,KAAA,EAAO,MAAA,SAAe,QAAA;EACtB,UAAA;IACE,OAAA,EAAS,MAAA,SAAe,YAAA;IACxB,eAAA,GAAkB,MAAA,SAAe,cAAA;EAAA;EAEnC,IAAA,EAAM,KAAA;IAAQ,IAAA;IAAc,WAAA;EAAA;EAC5B,QAAA,GAAW,KAAA,CAAM,MAAA;AAAA;AAAA,UAGF,mBAAA;EACf,KAAA;EACA,OAAA;EACA,WAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,UAGQ,QAAA;EACR,GAAA,GAAM,SAAA;EACN,IAAA,GAAO,SAAA;EACP,GAAA,GAAM,SAAA;EACN,KAAA,GAAQ,SAAA;EACR,MAAA,GAAS,SAAA;EACT,OAAA,GAAU,SAAA;EACV,IAAA,GAAO,SAAA;AAAA;AAAA,UAGC,SAAA;EACR,IAAA;EACA,OAAA;EACA,WAAA;EACA,WAAA;EACA,UAAA,GAAa,SAAA;EACb,WAAA,GAAc,WAAA;EACd,SAAA,EAAW,MAAA,SAAe,QAAA;EAC1B,QAAA,GAAW,KAAA,CAAM,MAAA;EAbR;EAeT,kBAAA;IAAuB,IAAA;IAAc,KAAA;EAAA;EAnBrC;EAqBA,gBAAA,GAAmB,KAAA;IAAQ,IAAA;IAAc,IAAA;EAAA;AAAA;AAAA,UAGjC,SAAA;EACR,IAAA;EACA,EAAA;EACA,QAAA;EACA,MAAA,EAAQ,YAAA;EACR,WAAA;AAAA;AAAA,UAGQ,WAAA;EACR,QAAA;EACA,OAAA,EAAS,MAAA;IAAiB,MAAA,EAAQ,YAAA;EAAA;AAAA;AAAA,UAG1B,QAAA;EACR,WAAA;EACA,OAAA,GAAU,MAAA;IAAiB,MAAA,EAAQ,YAAA;EAAA;AAAA;AAAA,UAG3B,YAAA;EACR,IAAA;EACA,MAAA;EACA,UAAA,GAAa,MAAA,SAAe,YAAA;EAC5B,KAAA,GAAQ,YAAA;EACR,QAAA;EACA,IAAA;EACA,WAAA;EACA,OAAA;EACA,oBAAA,aAAiC,YAAA;EACjC,IAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,UAGQ,cAAA;EACR,IAAA;EACA,MAAA;EACA,YAAA;EACA,EAAA;EACA,IAAA;AAAA;AAAA,cAGI,aAAA,EAAe,kBAAA,CAAmB,cAAA;;AA/CO;;;iBAmG/B,gBAAA,CACd,SAAA,EAAW,aAAA,IACX,OAAA,GAAS,mBAAA,EACT,aAAA,GAAgB,oBAAA,KACf,WAAA;AAAA,cAkGF,QAAA;;;UChRgB,aAAA;EDiBf;ECfA,WAAA;EDmBA;ECjBA,OAAA;EDiBmB;ECfnB,KAAA;EDkBe;EChBf,KAAA;;EAEA,WAAA;EDsBsB;ECpBtB,QAAA;EDsB0B;ECpB1B,SAAA;EDqBmC;ECnBnC,SAAA;EDqBM;ECnBN,OAAA;AAAA;AAAA,cAGI,YAAA,EAAc,kBAAA,CAAmB,aAAA;AAAA,cAAa,UAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/docs/scalar.ts"],"sourcesContent":["/**\n * Scalar API Reference Plugin\n *\n * Beautiful, modern API documentation UI.\n * Lighter and more modern than Swagger UI.\n *\n * @example\n * import { scalarPlugin } from '@classytic/arc/docs';\n *\n * await fastify.register(scalarPlugin, {\n * routePrefix: '/docs',\n * specUrl: '/_docs/openapi.json',\n * });\n *\n * // UI available at /docs\n */\n\nimport fp from 'fastify-plugin';\nimport type { FastifyInstance, FastifyPluginAsync } from 'fastify';\n\nexport interface ScalarOptions {\n /** Route prefix for UI (default: '/docs') */\n routePrefix?: string;\n /** OpenAPI spec URL (default: '/_docs/openapi.json') */\n specUrl?: string;\n /** Page title */\n title?: string;\n /** Theme (default: 'default') */\n theme?: 'default' | 'alternate' | 'moon' | 'purple' | 'solarized' | 'bluePlanet' | 'saturn' | 'kepler' | 'mars' | 'deepSpace';\n /** Show sidebar (default: true) */\n showSidebar?: boolean;\n /** Dark mode (default: false) */\n darkMode?: boolean;\n /** Auth roles required to access docs */\n authRoles?: string[];\n /** Custom CSS */\n customCss?: string;\n /** Favicon URL */\n favicon?: string;\n}\n\nconst scalarPlugin: FastifyPluginAsync<ScalarOptions> = async (\n fastify: FastifyInstance,\n opts: ScalarOptions = {}\n) => {\n const {\n routePrefix = '/docs',\n specUrl = '/_docs/openapi.json',\n title = 'API Documentation',\n theme = 'default',\n showSidebar = true,\n darkMode = false,\n authRoles = [],\n customCss = '',\n favicon,\n } = opts;\n\n // Scalar configuration\n const scalarConfig = JSON.stringify({\n spec: { url: specUrl },\n theme,\n showSidebar,\n darkMode,\n ...(favicon && { favicon }),\n });\n\n // HTML template for Scalar\n const html = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>${title}</title>\n ${favicon ? `<link rel=\"icon\" href=\"${favicon}\">` : ''}\n <style>\n body { margin: 0; padding: 0; }\n ${customCss}\n </style>\n</head>\n<body>\n <script id=\"api-reference\" data-url=\"${specUrl}\"></script>\n <script>\n var configuration = ${scalarConfig};\n document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration);\n </script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n</body>\n</html>`;\n\n // Serve UI (not included in OpenAPI spec)\n fastify.get(routePrefix, async (request, reply) => {\n // Check auth if required\n if (authRoles.length > 0) {\n const user = (request as { user?: { roles?: string[] } }).user;\n const hasRole = authRoles.some((role) => user?.roles?.includes(role));\n if (!hasRole && !user?.roles?.includes('superadmin')) {\n reply.code(403).send({ error: 'Access denied' });\n return;\n }\n }\n\n reply.type('text/html').send(html);\n });\n\n // Redirect /docs/ to /docs\n if (!routePrefix.endsWith('/')) {\n fastify.get(`${routePrefix}/`, async (_, reply) => {\n reply.redirect(routePrefix);\n });\n }\n\n fastify.log?.debug?.(`Scalar API docs available at ${routePrefix}`);\n};\n\nexport default fp(scalarPlugin, {\n name: 'arc-scalar',\n fastify: '5.x',\n});\n\nexport { scalarPlugin };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyCA,MAAM,eAAkD,OACtD,SACA,OAAsB,EAAE,KACrB;CACH,MAAM,EACJ,cAAc,SACd,UAAU,uBACV,QAAQ,qBACR,QAAQ,WACR,cAAc,MACd,WAAW,OACX,YAAY,EAAE,EACd,YAAY,IACZ,YACE;CAGJ,MAAM,eAAe,KAAK,UAAU;EAClC,MAAM,EAAE,KAAK,SAAS;EACtB;EACA;EACA;EACA,GAAI,WAAW,EAAE,SAAS;EAC3B,CAAC;CAGF,MAAM,OAAO;;;;;WAKJ,MAAM;IACb,UAAU,0BAA0B,QAAQ,MAAM,GAAG;;;MAGnD,UAAU;;;;yCAIyB,QAAQ;;0BAEvB,aAAa;;;;;;AAQrC,SAAQ,IAAI,aAAa,OAAO,SAAS,UAAU;AAEjD,MAAI,UAAU,SAAS,GAAG;GACxB,MAAM,OAAQ,QAA4C;AAE1D,OAAI,CADY,UAAU,MAAM,SAAS,MAAM,OAAO,SAAS,KAAK,CAAC,IACrD,CAAC,MAAM,OAAO,SAAS,aAAa,EAAE;AACpD,UAAM,KAAK,IAAI,CAAC,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAChD;;;AAIJ,QAAM,KAAK,YAAY,CAAC,KAAK,KAAK;GAClC;AAGF,KAAI,CAAC,YAAY,SAAS,IAAI,CAC5B,SAAQ,IAAI,GAAG,YAAY,IAAI,OAAO,GAAG,UAAU;AACjD,QAAM,SAAS,YAAY;GAC3B;AAGJ,SAAQ,KAAK,QAAQ,gCAAgC,cAAc;;AAGrE,qBAAe,GAAG,cAAc;CAC9B,MAAM;CACN,SAAS;CACV,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"elevation-BRy3yFWT.mjs","names":[],"sources":["../src/scope/elevation.ts"],"sourcesContent":["/**\n * Elevation Plugin — Explicit Platform Admin Access\n *\n * Opt-in Fastify plugin that allows platform admins to explicitly\n * elevate their scope via the `x-arc-scope: platform` header.\n *\n * Without this header, a superadmin is treated as a normal user.\n * This prevents implicit bypass and enables audit logging.\n *\n * ## Lifecycle\n *\n * Elevation wraps `fastify.authenticate` so it always runs AFTER\n * authentication has set `request.user`. This avoids the `onRequest`\n * timing issue where `request.user` doesn't exist yet.\n *\n * Flow: `authenticate()` → user is set → `elevation check` → scope is set\n *\n * Inspired by Stripe Connect's `Stripe-Account` header.\n *\n * @example\n * ```typescript\n * const app = await createApp({\n * auth: { type: 'betterAuth', betterAuth: adapter },\n * elevation: {\n * platformRoles: ['superadmin'],\n * onElevation: (event) => auditLog.write(event),\n * },\n * });\n * ```\n */\n\nimport fp from 'fastify-plugin';\nimport type { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify';\nimport type { RequestScope } from './types.js';\nimport { arcLog } from '../logger/index.js';\n\nconst log = arcLog('elevation');\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ElevationOptions {\n /** Roles that can use elevation (default: ['superadmin']) */\n platformRoles?: string[];\n /** Header name for scope declaration (default: 'x-arc-scope') */\n scopeHeader?: string;\n /** Header name for target organization (default: 'x-organization-id') */\n orgHeader?: string;\n /** Called when elevation happens — use for audit logging */\n onElevation?: (event: ElevationEvent) => void | Promise<void>;\n}\n\nexport interface ElevationEvent {\n userId: string;\n organizationId?: string;\n request: FastifyRequest;\n timestamp: Date;\n}\n\n// ============================================================================\n// Plugin\n// ============================================================================\n\nconst elevationPlugin: FastifyPluginAsync<ElevationOptions> = async (\n fastify: FastifyInstance,\n opts: ElevationOptions = {},\n) => {\n const {\n platformRoles = ['superadmin'],\n scopeHeader = 'x-arc-scope',\n orgHeader = 'x-organization-id',\n onElevation,\n } = opts;\n\n // Elevation requires auth — wrap the authenticate decorator\n if (!fastify.hasDecorator('authenticate')) {\n log.warn(\n 'authenticate decorator not found. ' +\n 'Register auth before elevation. Elevation will not function.',\n );\n return;\n }\n\n const originalAuthenticate = fastify.authenticate;\n\n // Replace authenticate with elevation-aware version\n const authenticateWithElevation = async (\n request: FastifyRequest,\n reply: FastifyReply,\n ): Promise<void> => {\n // Step 1: Run original auth (sets request.user + default scope)\n await originalAuthenticate.call(fastify, request, reply);\n\n // If auth failed and sent a reply, stop\n if (reply.sent) return;\n\n // Step 2: Check elevation header\n const headerValue = request.headers[scopeHeader] as string | undefined;\n if (headerValue !== 'platform') return;\n\n // Step 3: Validate user for elevation\n const user = request.user;\n if (!user) {\n log.debug('Elevation requested but no user after auth');\n reply.code(401).send({\n success: false,\n error: 'Unauthorized',\n message: 'Authentication required for platform elevation',\n code: 'ELEVATION_AUTH_REQUIRED',\n });\n return;\n }\n\n const userRoles = (user.roles ?? []) as string[];\n if (!platformRoles.some((r) => userRoles.includes(r))) {\n log.debug('Elevation rejected — insufficient roles', {\n userId: user.id ?? user._id,\n userRoles,\n required: platformRoles,\n });\n reply.code(403).send({\n success: false,\n error: 'Forbidden',\n message: 'Insufficient privileges for platform elevation',\n code: 'ELEVATION_FORBIDDEN',\n });\n return;\n }\n\n // Step 4: Build elevated scope\n const orgId = request.headers[orgHeader] as string | undefined;\n const userId = String(user.id ?? user._id ?? 'unknown');\n\n const scope: RequestScope = {\n kind: 'elevated',\n organizationId: orgId || undefined,\n elevatedBy: userId,\n };\n\n request.scope = scope;\n log.debug('Scope elevated', { userId, organizationId: orgId });\n\n // Step 5: Fire audit callback\n if (onElevation) {\n try {\n await onElevation({\n userId,\n organizationId: orgId || undefined,\n request,\n timestamp: new Date(),\n });\n } catch {\n // Don't fail the request if audit logging fails\n log.warn('onElevation callback threw — continuing request');\n }\n }\n };\n\n // Overwrite the authenticate decorator\n fastify.authenticate = authenticateWithElevation;\n\n log.debug('Plugin registered', { platformRoles, scopeHeader });\n};\n\nexport default fp(elevationPlugin, {\n name: 'arc-elevation',\n fastify: '5.x',\n});\n\nexport { elevationPlugin };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAM,MAAM,OAAO,YAAY;AA4B/B,MAAM,kBAAwD,OAC5D,SACA,OAAyB,EAAE,KACxB;CACH,MAAM,EACJ,gBAAgB,CAAC,aAAa,EAC9B,cAAc,eACd,YAAY,qBACZ,gBACE;AAGJ,KAAI,CAAC,QAAQ,aAAa,eAAe,EAAE;AACzC,MAAI,KACF,iGAED;AACD;;CAGF,MAAM,uBAAuB,QAAQ;CAGrC,MAAM,4BAA4B,OAChC,SACA,UACkB;AAElB,QAAM,qBAAqB,KAAK,SAAS,SAAS,MAAM;AAGxD,MAAI,MAAM,KAAM;AAIhB,MADoB,QAAQ,QAAQ,iBAChB,WAAY;EAGhC,MAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,MAAM;AACT,OAAI,MAAM,6CAA6C;AACvD,SAAM,KAAK,IAAI,CAAC,KAAK;IACnB,SAAS;IACT,OAAO;IACP,SAAS;IACT,MAAM;IACP,CAAC;AACF;;EAGF,MAAM,YAAa,KAAK,SAAS,EAAE;AACnC,MAAI,CAAC,cAAc,MAAM,MAAM,UAAU,SAAS,EAAE,CAAC,EAAE;AACrD,OAAI,MAAM,2CAA2C;IACnD,QAAQ,KAAK,MAAM,KAAK;IACxB;IACA,UAAU;IACX,CAAC;AACF,SAAM,KAAK,IAAI,CAAC,KAAK;IACnB,SAAS;IACT,OAAO;IACP,SAAS;IACT,MAAM;IACP,CAAC;AACF;;EAIF,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,MAAM,SAAS,OAAO,KAAK,MAAM,KAAK,OAAO,UAAU;AAQvD,UAAQ,QANoB;GAC1B,MAAM;GACN,gBAAgB,SAAS;GACzB,YAAY;GACb;AAGD,MAAI,MAAM,kBAAkB;GAAE;GAAQ,gBAAgB;GAAO,CAAC;AAG9D,MAAI,YACF,KAAI;AACF,SAAM,YAAY;IAChB;IACA,gBAAgB,SAAS;IACzB;IACA,2BAAW,IAAI,MAAM;IACtB,CAAC;UACI;AAEN,OAAI,KAAK,kDAAkD;;;AAMjE,SAAQ,eAAe;AAEvB,KAAI,MAAM,qBAAqB;EAAE;EAAe;EAAa,CAAC;;AAGhE,wBAAe,GAAG,iBAAiB;CACjC,MAAM;CACN,SAAS;CACV,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"elevation-B_2dRLVP.d.mts","names":[],"sources":["../src/scope/types.ts","../src/scope/elevation.ts"],"mappings":";;;;;;AA+BA;;;;;;;;;;;;;;;AAWA;;;;;;;;KAXY,YAAA;EACN,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;EAAgB,cAAA;EAAwB,QAAA;EAAoB,MAAA;AAAA;EAC5D,IAAA;EAAkB,cAAA;EAAyB,UAAA;AAAA;;iBAOjC,QAAA,CAAS,KAAA,EAAO,YAAA,GAAe,KAAA,IAAS,OAAA,CAAQ,YAAA;EAAgB,IAAA;AAAA;;iBAKhE,UAAA,CAAW,KAAA,EAAO,YAAA,GAAe,KAAA,IAAS,OAAA,CAAQ,YAAA;EAAgB,IAAA;AAAA;;iBAKlE,YAAA,CAAa,KAAA,EAAO,YAAA;AAApC;AAAA,iBAKgB,eAAA,CAAgB,KAAA,EAAO,YAAA;;iBASvB,QAAA,CAAS,KAAA,EAAO,YAAA;;iBAOhB,WAAA,CAAY,KAAA,EAAO,YAAA;;iBAMnB,SAAA,CAAU,KAAA,EAAO,YAAA;;cAUpB,YAAA,EAAc,QAAA,CAAS,YAAA;AAvBpC;AAAA,cA0Ba,mBAAA,EAAqB,QAAA,CAAS,YAAA;;;UClD1B,gBAAA;EDAmE;ECElF,aAAA;EDGc;ECDd,WAAA;;EAEA,SAAA;EDDgE;ECGhE,WAAA,IAAe,KAAA,EAAO,cAAA,YAA0B,OAAA;AAAA;AAAA,UAGjC,cAAA;EACf,MAAA;EACA,cAAA;EACA,OAAA,EAAS,cAAA;EACT,SAAA,EAAW,IAAA;AAAA;AAAA,cAOP,eAAA,EAAiB,kBAAA,CAAmB,gBAAA;AAAA,cAAgB,QAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"errorHandler-BbcgBmIH.d.mts","names":[],"sources":["../src/plugins/caching.ts","../src/plugins/sse.ts","../src/plugins/errorHandler.ts"],"mappings":";;;;UAwBiB,WAAA;EAqBf;EAnBA,KAAA;EAqBQ;EAnBR,MAAA;EAmBmB;EAjBnB,OAAA;EAgID;EA9HC,oBAAA;AAAA;AAAA,UAGe,cAAA;EAoCqC;EAlCpD,MAAA;;EAEA,IAAA;;EAEA,WAAA;;EAEA,OAAA;ECTe;EDWf,OAAA;;EAEA,KAAA,GAAQ,WAAA;AAAA;AAAA,cAwBJ,aAAA,EAAe,kBAAA,CAAmB,cAAA;AAAA,cAAc,UAAA;;;UCrCrC,UAAA;EDWf;ECTA,IAAA;EDWQ;ECTR,WAAA;EDSmB;ECPnB,QAAA;EDsHD;ECpHC,SAAA;ED6BmB;EC3BnB,SAAA;ED2BoD;ECzBpD,MAAA,IAAU,KAAA,EAAO,WAAA,WAAsB,OAAA,EAAS,cAAA;AAAA;AAAA,cAO5C,SAAA,EAAW,kBAAA,CAAmB,UAAA;AAAA,cAAU,QAAA;;;UC/B7B,mBAAA;EFiBf;;;EEbA,YAAA;EFqBA;;;EEhBA,OAAA,IAAW,KAAA,EAAO,KAAA,EAAO,OAAA,EAAS,cAAA,YAA0B,OAAA;EFwCxD;;;EEnCJ,QAAA,GAAW,MAAA;IACT,UAAA;IACA,IAAA;IACA,OAAA;EAAA;AAAA;AAAA,iBAcW,oBAAA,CACb,OAAA,EAAS,eAAA,EACT,OAAA,GAAS,mBAAA,GACR,OAAA;AAAA,cAoJU,kBAAA,SAAkB,oBAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"errorHandler-C1okiriz.mjs","names":[],"sources":["../src/plugins/errorHandler.ts"],"sourcesContent":["/**\n * Error Handler Plugin\n *\n * Global error handling for Arc applications.\n * Catches all errors and returns a consistent JSON response.\n *\n * @example\n * import { errorHandlerPlugin } from '@classytic/arc/plugins';\n *\n * await fastify.register(errorHandlerPlugin, {\n * includeStack: process.env.NODE_ENV !== 'production',\n * onError: (error, request) => {\n * // Log to external service (Sentry, etc.)\n * Sentry.captureException(error);\n * }\n * });\n */\n\nimport type { FastifyInstance, FastifyRequest, FastifyReply, FastifyError } from 'fastify';\nimport fp from 'fastify-plugin';\nimport { ArcError, isArcError, ValidationError } from '../utils/errors.js';\n\nexport interface ErrorHandlerOptions {\n /**\n * Include stack trace in error responses (default: false in production)\n */\n includeStack?: boolean;\n\n /**\n * Custom error callback for logging to external services\n */\n onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;\n\n /**\n * Map specific error types to custom responses\n */\n errorMap?: Record<string, {\n statusCode: number;\n code: string;\n message?: string;\n }>;\n}\n\ninterface ErrorResponse {\n success: false;\n error: string;\n code: string;\n timestamp: string;\n requestId?: string;\n details?: Record<string, unknown>;\n stack?: string;\n}\n\nasync function errorHandlerPluginFn(\n fastify: FastifyInstance,\n options: ErrorHandlerOptions = {}\n): Promise<void> {\n const isProduction = process.env.NODE_ENV === 'production';\n const {\n includeStack = !isProduction,\n onError,\n errorMap = {},\n } = options;\n\n fastify.setErrorHandler(async (error: FastifyError | Error, request: FastifyRequest, reply: FastifyReply) => {\n // Call custom error handler if provided\n if (onError) {\n try {\n await onError(error, request);\n } catch (callbackError) {\n request.log.error({ err: callbackError }, 'Error in onError callback');\n }\n }\n\n // Get request ID if available\n const requestId = (request as { id?: string }).id;\n\n // Build base response\n const response: ErrorResponse = {\n success: false,\n error: error.message || 'Internal Server Error',\n code: 'INTERNAL_ERROR',\n timestamp: new Date().toISOString(),\n ...(requestId && { requestId }),\n };\n\n let statusCode = 500;\n\n // Handle ArcError (our error classes)\n if (isArcError(error)) {\n statusCode = error.statusCode;\n response.code = error.code;\n if (error.details) {\n response.details = error.details;\n }\n if (error.requestId) {\n response.requestId = error.requestId;\n }\n // Log cause chain for debugging (cause is now properly serialized via toJSON)\n if (error.cause) {\n request.log.error({ cause: error.cause }, 'Error cause chain');\n }\n }\n // Handle Fastify validation errors\n else if ('validation' in error && Array.isArray((error as FastifyError).validation)) {\n statusCode = 400;\n response.code = 'VALIDATION_ERROR';\n response.error = 'Validation failed';\n response.details = {\n errors: (error as FastifyError).validation?.map((v) => ({\n field: v.instancePath?.replace(/^\\//, '') || v.params?.missingProperty || 'unknown',\n message: v.message || 'Invalid value',\n keyword: v.keyword,\n })),\n };\n }\n // Handle Fastify errors with statusCode\n else if ('statusCode' in error && typeof (error as FastifyError).statusCode === 'number') {\n statusCode = (error as FastifyError).statusCode!;\n response.code = statusCodeToCode(statusCode);\n }\n // Handle mapped error types\n else if (error.name && errorMap[error.name]) {\n const mapping = errorMap[error.name]!;\n statusCode = mapping.statusCode;\n response.code = mapping.code;\n if (mapping.message) {\n response.error = mapping.message;\n }\n }\n // Handle Mongoose validation errors\n else if (error.name === 'ValidationError' && 'errors' in error) {\n statusCode = 400;\n response.code = 'VALIDATION_ERROR';\n const mongooseErrors = (error as { errors: Record<string, { message: string; path: string }> }).errors;\n\n // Security: Don't expose schema field names when details are hidden\n if (includeStack) {\n response.details = {\n errors: Object.entries(mongooseErrors).map(([field, err]) => ({\n field: err.path || field,\n message: err.message,\n })),\n };\n } else {\n response.details = { errorCount: Object.keys(mongooseErrors).length };\n }\n }\n // Handle Mongoose CastError (invalid ObjectId, etc.)\n else if (error.name === 'CastError') {\n statusCode = 400;\n response.code = 'INVALID_ID';\n response.error = 'Invalid identifier format';\n }\n // Handle duplicate key errors (MongoDB)\n else if (error.name === 'MongoServerError' && (error as { code?: number }).code === 11000) {\n statusCode = 409;\n response.code = 'DUPLICATE_KEY';\n response.error = 'Resource already exists';\n const keyValue = (error as { keyValue?: Record<string, unknown> }).keyValue;\n\n // Security: Don't expose schema field names when details are hidden\n if (keyValue && includeStack) {\n response.details = { duplicateFields: Object.keys(keyValue) };\n }\n }\n\n // Include stack trace if enabled\n if (includeStack && error.stack) {\n response.stack = error.stack;\n }\n\n // Log server errors\n if (statusCode >= 500) {\n request.log.error({ err: error, statusCode }, 'Server error');\n } else if (statusCode >= 400) {\n request.log.warn({ err: error, statusCode }, 'Client error');\n }\n\n return reply.status(statusCode).send(response);\n });\n}\n\n/**\n * Map HTTP status code to error code\n */\nfunction statusCodeToCode(statusCode: number): string {\n const codes: Record<number, string> = {\n 400: 'BAD_REQUEST',\n 401: 'UNAUTHORIZED',\n 403: 'FORBIDDEN',\n 404: 'NOT_FOUND',\n 405: 'METHOD_NOT_ALLOWED',\n 409: 'CONFLICT',\n 422: 'UNPROCESSABLE_ENTITY',\n 429: 'RATE_LIMITED',\n 500: 'INTERNAL_ERROR',\n 502: 'BAD_GATEWAY',\n 503: 'SERVICE_UNAVAILABLE',\n 504: 'GATEWAY_TIMEOUT',\n };\n return codes[statusCode] ?? 'ERROR';\n}\n\nexport const errorHandlerPlugin = fp(errorHandlerPluginFn, {\n name: 'arc-error-handler',\n fastify: '5.x',\n});\n\nexport default errorHandlerPlugin;\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-B9bZok84.mjs","names":[],"sources":["../src/utils/errors.ts"],"sourcesContent":["/**\n * Error Classes\n *\n * Standard error types for the Arc framework.\n */\n\nexport interface ErrorDetails {\n code?: string;\n statusCode?: number;\n details?: Record<string, unknown>;\n cause?: Error;\n requestId?: string;\n}\n\n/**\n * Base Arc Error\n *\n * All Arc errors extend this class and produce a consistent error envelope:\n * {\n * success: false,\n * error: \"Human-readable message\",\n * code: \"MACHINE_CODE\",\n * requestId: \"uuid\", // For tracing\n * timestamp: \"ISO date\", // When error occurred\n * details: { ... } // Additional context\n * }\n */\nexport class ArcError extends Error {\n override name: string;\n readonly code: string;\n readonly statusCode: number;\n readonly details?: Record<string, unknown>;\n override readonly cause?: Error;\n readonly timestamp: string;\n requestId?: string;\n\n constructor(message: string, options: ErrorDetails = {}) {\n // Pass cause to native Error for proper chain support (Node 16.9+)\n super(message, options.cause ? { cause: options.cause } : undefined);\n this.name = 'ArcError';\n this.code = options.code ?? 'ARC_ERROR';\n this.statusCode = options.statusCode ?? 500;\n this.details = options.details;\n // cause is now set by super() — keep explicit assignment for TypeScript override\n this.cause = options.cause;\n this.timestamp = new Date().toISOString();\n this.requestId = options.requestId;\n\n // Maintain proper stack trace\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /**\n * Set request ID (typically from request context)\n */\n withRequestId(requestId: string): this {\n this.requestId = requestId;\n return this;\n }\n\n /**\n * Convert to JSON response.\n * Includes cause chain when present for debugging visibility.\n */\n toJSON(): Record<string, unknown> {\n return {\n success: false,\n error: this.message,\n code: this.code,\n timestamp: this.timestamp,\n ...(this.requestId && { requestId: this.requestId }),\n ...(this.details && { details: this.details }),\n ...(this.cause && {\n cause: this.cause instanceof ArcError\n ? this.cause.toJSON()\n : { message: (this.cause as Error).message, name: (this.cause as Error).name },\n }),\n };\n }\n}\n\n/**\n * Not Found Error - 404\n */\nexport class NotFoundError extends ArcError {\n constructor(resource: string, identifier?: string) {\n const message = identifier\n ? `${resource} with identifier '${identifier}' not found`\n : `${resource} not found`;\n\n super(message, {\n code: 'NOT_FOUND',\n statusCode: 404,\n details: { resource, identifier },\n });\n this.name = 'NotFoundError';\n }\n}\n\n/**\n * Validation Error - 400\n */\nexport class ValidationError extends ArcError {\n readonly errors: Array<{ field: string; message: string }>;\n\n constructor(\n message: string,\n errors: Array<{ field: string; message: string }> = []\n ) {\n super(message, {\n code: 'VALIDATION_ERROR',\n statusCode: 400,\n details: { errors },\n });\n this.name = 'ValidationError';\n this.errors = errors;\n }\n}\n\n/**\n * Unauthorized Error - 401\n */\nexport class UnauthorizedError extends ArcError {\n constructor(message = 'Authentication required') {\n super(message, {\n code: 'UNAUTHORIZED',\n statusCode: 401,\n });\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Forbidden Error - 403\n */\nexport class ForbiddenError extends ArcError {\n constructor(message = 'Access denied') {\n super(message, {\n code: 'FORBIDDEN',\n statusCode: 403,\n });\n this.name = 'ForbiddenError';\n }\n}\n\n/**\n * Conflict Error - 409\n */\nexport class ConflictError extends ArcError {\n constructor(message: string, field?: string) {\n super(message, {\n code: 'CONFLICT',\n statusCode: 409,\n details: field ? { field } : undefined,\n });\n this.name = 'ConflictError';\n }\n}\n\n/**\n * Organization Required Error - 403\n */\nexport class OrgRequiredError extends ArcError {\n readonly organizations?: Array<{ id: string; roles?: string[] }>;\n\n constructor(\n message: string,\n organizations?: Array<{ id: string; roles?: string[] }>\n ) {\n super(message, {\n code: 'ORG_SELECTION_REQUIRED',\n statusCode: 403,\n details: organizations ? { organizations } : undefined,\n });\n this.name = 'OrgRequiredError';\n this.organizations = organizations;\n }\n}\n\n/**\n * Organization Access Denied Error - 403\n */\nexport class OrgAccessDeniedError extends ArcError {\n constructor(orgId?: string) {\n super('Organization access denied', {\n code: 'ORG_ACCESS_DENIED',\n statusCode: 403,\n details: orgId ? { organizationId: orgId } : undefined,\n });\n this.name = 'OrgAccessDeniedError';\n }\n}\n\n/**\n * Rate Limit Error - 429\n */\nexport class RateLimitError extends ArcError {\n readonly retryAfter?: number;\n\n constructor(message = 'Too many requests', retryAfter?: number) {\n super(message, {\n code: 'RATE_LIMITED',\n statusCode: 429,\n details: retryAfter ? { retryAfter } : undefined,\n });\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Service Unavailable Error - 503\n */\nexport class ServiceUnavailableError extends ArcError {\n constructor(message = 'Service temporarily unavailable') {\n super(message, {\n code: 'SERVICE_UNAVAILABLE',\n statusCode: 503,\n });\n this.name = 'ServiceUnavailableError';\n }\n}\n\n/**\n * Create error from status code\n */\nexport function createError(\n statusCode: number,\n message: string,\n details?: Record<string, unknown>\n): ArcError {\n const codes: Record<number, string> = {\n 400: 'BAD_REQUEST',\n 401: 'UNAUTHORIZED',\n 403: 'FORBIDDEN',\n 404: 'NOT_FOUND',\n 409: 'CONFLICT',\n 429: 'RATE_LIMITED',\n 500: 'INTERNAL_ERROR',\n 503: 'SERVICE_UNAVAILABLE',\n };\n\n return new ArcError(message, {\n code: codes[statusCode] ?? 'ERROR',\n statusCode,\n details,\n });\n}\n\n/**\n * Check if error is an Arc error\n */\nexport function isArcError(error: unknown): error is ArcError {\n return error instanceof ArcError;\n}\n"],"mappings":"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"}