@classytic/arc 2.28.0 → 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -0
  3. package/TRADEMARK.md +22 -0
  4. package/dist/{BaseController-B65zbz9m.mjs → BaseController-De7GqHck.mjs} +25 -24
  5. package/dist/{EventTransport-QmY7A258.d.mts → EventTransport-2uVV34WU.d.mts} +1 -0
  6. package/dist/EventTransport-9mlTgqzE.mjs +104 -0
  7. package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-BcmPyq5I.mjs} +16 -35
  8. package/dist/{QueryCache-DOMT_iq7.d.mts → QueryCache-DFA-QrU9.d.mts} +2 -1
  9. package/dist/{ResourceRegistry-y9Hnypus.mjs → ResourceRegistry-qq0mn2s9.mjs} +3 -2
  10. package/dist/{actionPermissions-CAphcmUG.mjs → actionPermissions-BE5NwaQk.mjs} +1 -0
  11. package/dist/audit/index.d.mts +3 -2
  12. package/dist/audit/index.mjs +3 -2
  13. package/dist/auth/audit.d.mts +1 -0
  14. package/dist/auth/audit.mjs +1 -0
  15. package/dist/auth/index.d.mts +5 -4
  16. package/dist/auth/index.mjs +6 -5
  17. package/dist/auth/redis-session.d.mts +2 -1
  18. package/dist/auth/redis-session.mjs +1 -0
  19. package/dist/authorizationDecision-ChvUQGG1.mjs +266 -0
  20. package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-CDrCJzmB.mjs} +34 -7
  21. package/dist/cache/index.d.mts +4 -3
  22. package/dist/cache/index.mjs +4 -3
  23. package/dist/{caching-_NJeDd1_.mjs → caching-BFHA6Scf.mjs} +2 -1
  24. package/dist/cleanup/index.d.mts +4 -3
  25. package/dist/cleanup/index.mjs +9 -13
  26. package/dist/cli/commands/describe.d.mts +2 -1
  27. package/dist/cli/commands/describe.mjs +3 -2
  28. package/dist/cli/commands/docs.d.mts +1 -0
  29. package/dist/cli/commands/docs.mjs +3 -2
  30. package/dist/cli/commands/doctor.d.mts +1 -0
  31. package/dist/cli/commands/doctor.mjs +1 -0
  32. package/dist/cli/commands/generate.d.mts +2 -1
  33. package/dist/cli/commands/generate.mjs +2 -1
  34. package/dist/cli/commands/init.d.mts +2 -1
  35. package/dist/cli/commands/init.mjs +2 -1
  36. package/dist/cli/commands/introspect.d.mts +1 -0
  37. package/dist/cli/commands/introspect.mjs +2 -1
  38. package/dist/cli/index.d.mts +3 -2
  39. package/dist/cli/index.mjs +3 -2
  40. package/dist/{constants-DNre0wRg.mjs → constants-D3Dv4LAV.mjs} +1 -0
  41. package/dist/context/index.d.mts +1 -0
  42. package/dist/context/index.mjs +3 -2
  43. package/dist/core/index.d.mts +3 -2
  44. package/dist/core/index.mjs +7 -5
  45. package/dist/{core-BGa8TXJR.mjs → core-BGpIxINN.mjs} +4 -49
  46. package/dist/{createActionRouter-C-N7z2EL.mjs → createActionRouter-CDXEdasl.mjs} +8 -7
  47. package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-KrQDzx-k.mjs} +10 -22
  48. package/dist/{createApp-Du5G54TM.mjs → createApp-Df89BEni.mjs} +505 -209
  49. package/dist/defineAggregation-DbPPS8RI.mjs +48 -0
  50. package/dist/{defineResource-2zvVVpxg.mjs → defineResource-Bv6EtN1x.mjs} +90 -72
  51. package/dist/discovery/index.d.mts +1 -0
  52. package/dist/discovery/index.mjs +1 -0
  53. package/dist/docs/index.d.mts +3 -2
  54. package/dist/docs/index.mjs +3 -2
  55. package/dist/{elevation-C9s5hFP_.d.mts → elevation-BXCfVcNG.d.mts} +1 -0
  56. package/dist/{elevation-CIz1GjGm.mjs → elevation-D2l1mh4g.mjs} +4 -3
  57. package/dist/encryption/index.d.mts +2 -1
  58. package/dist/encryption/index.mjs +3 -2
  59. package/dist/{errorHandler-Bq2OeEQL.mjs → errorHandler-C5NeD6bT.mjs} +3 -2
  60. package/dist/{errorHandler-C6yv4ZTW.d.mts → errorHandler-CpYUJmPA.d.mts} +1 -0
  61. package/dist/{errors-DHcHVAgk.d.mts → errors-BGdVwP25.d.mts} +1 -0
  62. package/dist/{errors-DkO8Nk6-.mjs → errors-Cj42AntO.mjs} +1 -0
  63. package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-CkffEihm.mjs} +6 -106
  64. package/dist/{eventPlugin-Bg3cP2_u.d.mts → eventPlugin-GF5nnBIq.d.mts} +2 -1
  65. package/dist/events/index.d.mts +4 -3
  66. package/dist/events/index.mjs +17 -340
  67. package/dist/events/transports/redis-stream-entry.d.mts +2 -1
  68. package/dist/events/transports/redis-stream-entry.mjs +1 -0
  69. package/dist/events/transports/redis.d.mts +2 -1
  70. package/dist/events/transports/redis.mjs +1 -0
  71. package/dist/{externalPaths-BE6wFpYv.d.mts → externalPaths-CmGOxOtD.d.mts} +1 -0
  72. package/dist/factory/index.d.mts +9 -5
  73. package/dist/factory/index.mjs +7 -6
  74. package/dist/{fastify-augmentation-OzYGIiU1.d.mts → fastify-augmentation-zh0_AYHa.d.mts} +13 -4
  75. package/dist/{applyPermissionResult-BnyVE9eD.mjs → fields-CB5Cuzk9.mjs} +2 -107
  76. package/dist/{fields-D3lhgzhD.d.mts → fields-I_EeRWND.d.mts} +1 -0
  77. package/dist/{generate-BnsL9GBm.d.mts → generate-DCGuEbqI.d.mts} +1 -0
  78. package/dist/{generate-D0wZupBg.mjs → generate-EcA6ui4i.mjs} +2 -1
  79. package/dist/{headers-BvlQSDMB.mjs → headers-3zvE0rM3.mjs} +2 -1
  80. package/dist/{health-CB_m4S32.mjs → health-DjZYjM7H.mjs} +1 -0
  81. package/dist/hooks/index.d.mts +2 -1
  82. package/dist/hooks/index.mjs +2 -1
  83. package/dist/idempotency/index.d.mts +3 -2
  84. package/dist/idempotency/index.mjs +5 -4
  85. package/dist/idempotency/redis.d.mts +2 -1
  86. package/dist/idempotency/redis.mjs +1 -0
  87. package/dist/{index-CjVg-Epg.d.mts → index-Bc2ox8_U.d.mts} +6 -5
  88. package/dist/{index-CG4yqMDB.d.mts → index-C-481Zf2.d.mts} +74 -18
  89. package/dist/{index-BMnbzvtO.d.mts → index-CEJNZFtX.d.mts} +242 -24
  90. package/dist/index-CFThn1K9.d.mts +224 -0
  91. package/dist/{index-DPRH0Cme.d.mts → index-CpzxqePp.d.mts} +203 -97
  92. package/dist/index.d.mts +9 -8
  93. package/dist/index.mjs +11 -9
  94. package/dist/{init-tlRSt667.mjs → init-BEiNZBsJ.mjs} +19 -23
  95. package/dist/{init-BPEu8cs-.d.mts → init-Cscs4lab.d.mts} +1 -0
  96. package/dist/integrations/event-gateway.d.mts +3 -2
  97. package/dist/integrations/event-gateway.mjs +2 -1
  98. package/dist/integrations/index.d.mts +4 -3
  99. package/dist/integrations/index.mjs +1 -0
  100. package/dist/integrations/jobs/index.d.mts +2 -1
  101. package/dist/integrations/jobs/index.mjs +108 -32
  102. package/dist/integrations/mcp/index.d.mts +9 -9
  103. package/dist/integrations/mcp/index.mjs +2 -1
  104. package/dist/integrations/mcp/testing.d.mts +3 -2
  105. package/dist/integrations/mcp/testing.mjs +2 -2
  106. package/dist/integrations/streamline.d.mts +1 -0
  107. package/dist/integrations/streamline.mjs +3 -2
  108. package/dist/integrations/webhooks.d.mts +1 -0
  109. package/dist/integrations/webhooks.mjs +1 -0
  110. package/dist/integrations/websocket-pushref-redis.d.mts +2 -1
  111. package/dist/integrations/websocket-pushref-redis.mjs +1 -0
  112. package/dist/integrations/websocket-redis.d.mts +2 -1
  113. package/dist/integrations/websocket-redis.mjs +1 -0
  114. package/dist/integrations/websocket.d.mts +3 -2
  115. package/dist/integrations/websocket.mjs +1 -0
  116. package/dist/{interface-BGZCzOGL.d.mts → interface-BOk3YnTz.d.mts} +1 -0
  117. package/dist/{interface-Uk8ONJv7.d.mts → interface-DEWNOMza.d.mts} +1 -0
  118. package/dist/{interface-n5hpKg1d.mjs → interface-DYzM0q-u.mjs} +1 -0
  119. package/dist/{jsonBody-BlzAoGCe.mjs → jsonBody-BYbLDi5D.mjs} +2 -1
  120. package/dist/{keys-Cd316u_y.mjs → keys-C0_pa1tY.mjs} +1 -0
  121. package/dist/{loadResources-DpU8pfjV.mjs → loadResources-yawi9tAE.mjs} +2 -1
  122. package/dist/{loadResourcesFromEntry-kvGT6EcO.mjs → loadResourcesFromEntry-BAjYZonx.mjs} +2 -1
  123. package/dist/logger/index.d.mts +1 -0
  124. package/dist/logger/index.mjs +1 -0
  125. package/dist/{mcpPlugin-Cs6nRGCt.mjs → mcpPlugin-B_es0hiF.mjs} +52 -60
  126. package/dist/{memory-CmnzXE5Z.mjs → memory-DR6wpa9F.mjs} +2 -1
  127. package/dist/{metrics-fdxI5mn_.mjs → metrics-CUdw_wLB.mjs} +2 -1
  128. package/dist/middleware/index.d.mts +2 -1
  129. package/dist/middleware/index.mjs +3 -2
  130. package/dist/migrations/index.d.mts +1 -3
  131. package/dist/migrations/index.mjs +2 -4
  132. package/dist/module-0pFJA6YW.mjs +78 -0
  133. package/dist/{multipartBody-D0eBvkeq.mjs → multipartBody-wOgj_fnj.mjs} +1 -0
  134. package/dist/{openapi-BLV4OMXb.mjs → openapi-B2L4js1v.mjs} +6 -5
  135. package/dist/permissions/index.d.mts +5 -4
  136. package/dist/permissions/index.mjs +6 -4
  137. package/dist/{permissions-BrtiBKGs.mjs → permissions-L--QDAir.mjs} +518 -316
  138. package/dist/{pipe-BOLmGIxa.mjs → pipe-DRDNsI2T.mjs} +2 -1
  139. package/dist/pipeline/index.d.mts +2 -1
  140. package/dist/pipeline/index.mjs +2 -1
  141. package/dist/{pipeline-BGRnV-To.mjs → pipeline-UPQFatit.mjs} +7 -20
  142. package/dist/plugins/index.d.mts +8 -7
  143. package/dist/plugins/index.mjs +16 -15
  144. package/dist/plugins/response-cache.d.mts +1 -0
  145. package/dist/plugins/response-cache.mjs +2 -1
  146. package/dist/plugins/tracing-entry.d.mts +2 -1
  147. package/dist/plugins/tracing-entry.mjs +2 -1
  148. package/dist/{pluralize-BhrgwZb9.mjs → pluralize-O0FZgESl.mjs} +1 -0
  149. package/dist/{preloadResources-CM4uUaFv.d.mts → preloadResources-CyoJMSiO.d.mts} +117 -12
  150. package/dist/{preloadResources-DSY5HGpS.mjs → preloadResources-Dz6BjzM6.mjs} +1 -0
  151. package/dist/presets/filesUpload.d.mts +6 -5
  152. package/dist/presets/filesUpload.mjs +9 -11
  153. package/dist/presets/index.d.mts +15 -2
  154. package/dist/presets/index.mjs +2 -1
  155. package/dist/presets/multiTenant.d.mts +2 -1
  156. package/dist/presets/multiTenant.mjs +3 -2
  157. package/dist/presets/search.d.mts +3 -2
  158. package/dist/presets/search.mjs +2 -1
  159. package/dist/{presets-DwFDVKP0.mjs → presets-C4-g4_4P.mjs} +27 -10
  160. package/dist/projection-BHw6xlOQ.mjs +18 -0
  161. package/dist/{pushref-registry-BbcnpNTm.d.mts → pushref-registry-Dov9l4qE.d.mts} +1 -0
  162. package/dist/{queryCachePlugin-DSGuSVLW.mjs → queryCachePlugin-BZr8v46E.mjs} +5 -4
  163. package/dist/{queryCachePlugin-br4otiqF.d.mts → queryCachePlugin-BzqxconG.d.mts} +3 -2
  164. package/dist/{rateLimit-DrED-C4v.mjs → rateLimit-CMPY5Qms.mjs} +7 -3
  165. package/dist/{rateLimitKey-BoWRH3e7.mjs → rateLimitKey-Dn5JNmkj.mjs} +1 -0
  166. package/dist/{realtime-DEuDu9io.mjs → realtime-C5NoxWFY.mjs} +20 -8
  167. package/dist/{redis-UDtLaNj6.d.mts → redis-CduhKsJi.d.mts} +2 -1
  168. package/dist/{redis-stream-uIaauNpZ.d.mts → redis-stream-qN6A-RFv.d.mts} +2 -1
  169. package/dist/{registerRoute-DTp18Ah_.mjs → registerRoute-CBN84fc7.mjs} +5 -2
  170. package/dist/registry/index.d.mts +3 -2
  171. package/dist/registry/index.mjs +3 -2
  172. package/dist/{registry-9km_w1aP.mjs → registry-4213U4zb.mjs} +3 -2
  173. package/dist/{renewingLease-3cuQH7h8.mjs → renewingLease-Dll8mGgc.mjs} +1 -0
  174. package/dist/{replyHelpers-67tRawQZ.mjs → replyHelpers-DIiZKGUt.mjs} +2 -1
  175. package/dist/repositoryFilter-f9eAfq_l.mjs +45 -0
  176. package/dist/{requestContext-MrlJwECf.mjs → requestContext-2TxXOJ7T.mjs} +1 -0
  177. package/dist/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-CUsjNX9R.mjs} +1 -0
  178. package/dist/{schedules-CjOd2MG9.mjs → schedules-DdtlICTf.mjs} +3 -2
  179. package/dist/{schemaConverter-DJvodiHj.mjs → schemaConverter-D--wdJM-.mjs} +70 -17
  180. package/dist/{schemaIR-Ca7ySNPx.mjs → schemaIR-Bm2enbnh.mjs} +2 -1
  181. package/dist/schemas/index.d.mts +1 -0
  182. package/dist/schemas/index.mjs +1 -0
  183. package/dist/scim/index.d.mts +1 -0
  184. package/dist/scim/index.mjs +2 -1
  185. package/dist/scope/index.d.mts +3 -2
  186. package/dist/scope/index.mjs +5 -4
  187. package/dist/{scope-DfaPntpo.mjs → scope-DHx5Fous.mjs} +4 -3
  188. package/dist/{sessionManager-BHOM3N_0.d.mts → sessionManager-qimlKV0P.d.mts} +1 -0
  189. package/dist/{simpleEqualityMatcher-Cg05HPsn.mjs → simpleEqualityMatcher-DjQJ7FKA.mjs} +1 -0
  190. package/dist/{sse-BQ1w0dfl.mjs → sse-CUTo9y0X.mjs} +5 -4
  191. package/dist/{sseStream-BQ7Qaynp.mjs → sseStream-B_5XcVVd.mjs} +2 -1
  192. package/dist/{storage-DLUZ04kK.d.mts → storage-Cx9Sz3Ou.d.mts} +1 -0
  193. package/dist/{store-helpers-DY6xq4sM.mjs → store-helpers-D1MP_gRG.mjs} +1 -0
  194. package/dist/{streaming-BQcW6_Kq.mjs → streaming-PSZwi8JV.mjs} +1 -0
  195. package/dist/subscribe-helpers-C0Tb2aUp.mjs +340 -0
  196. package/dist/sync/changelog.d.mts +1 -0
  197. package/dist/sync/changelog.mjs +1 -0
  198. package/dist/testing/cleanupStoreContract.d.mts +2 -1
  199. package/dist/testing/cleanupStoreContract.mjs +1 -0
  200. package/dist/testing/index.d.mts +167 -12
  201. package/dist/testing/index.mjs +522 -205
  202. package/dist/testing/outboxStoreContract.d.mts +1 -0
  203. package/dist/testing/outboxStoreContract.mjs +1 -0
  204. package/dist/testing/storageContract.d.mts +2 -1
  205. package/dist/testing/storageContract.mjs +1 -0
  206. package/dist/{tracing-CsGKQJGg.d.mts → tracing-Dro5eH6j.d.mts} +1 -0
  207. package/dist/{transactionContext-PNdoyLLm.mjs → transactionContext-dAfBVUrk.mjs} +1 -0
  208. package/dist/{typeGuards-C9KXUly6.mjs → typeGuards-B0ryPo3I.mjs} +1 -0
  209. package/dist/types/index.d.mts +6 -5
  210. package/dist/types/index.mjs +1 -0
  211. package/dist/types/storage.d.mts +2 -1
  212. package/dist/types/storage.mjs +1 -0
  213. package/dist/{types-Dmw-6Qeo.d.mts → types-B7XeLEgK.d.mts} +127 -90
  214. package/dist/{types-Cc91nXuA.d.mts → types-ByHn9rEO.d.mts} +1 -0
  215. package/dist/{types-C_ZZRcD6.d.mts → types-C4QE8qnm.d.mts} +1 -0
  216. package/dist/{types-Cu6vVJmh.d.mts → types-CBgvkp8O.d.mts} +82 -12
  217. package/dist/{types-DjC-KUCz.d.mts → types-ChuShpCP.d.mts} +1 -0
  218. package/dist/{types-QFGc3Cjv.d.mts → types-CwcUQ610.d.mts} +2 -6
  219. package/dist/{types-CSyCxl2V.mjs → types-Cys0ko9Z.mjs} +4 -3
  220. package/dist/usage/index.d.mts +3 -2
  221. package/dist/usage/index.mjs +0 -0
  222. package/dist/{userHelpers-Cg_y3fBz.mjs → userHelpers-ChYJ6CdK.mjs} +1 -0
  223. package/dist/utils/index.d.mts +16 -10
  224. package/dist/utils/index.mjs +10 -9
  225. package/dist/{utils-taUVD4p3.mjs → utils-vL8BrDE2.mjs} +3 -2
  226. package/dist/{validate-Bk44F2yT.mjs → validate-_KbifpHF.mjs} +2 -1
  227. package/dist/{versioning-gKWyryT3.mjs → versioning-CHDTor3P.mjs} +3 -2
  228. package/dist/{versioning-DXgY5BUm.d.mts → versioning-D_rhHfZm.d.mts} +5 -2
  229. package/dist/{websocket-_Pd0CgR0.d.mts → websocket-BOXVggMf.d.mts} +2 -1
  230. package/package.json +15 -7
  231. package/skills/arc/SKILL.md +22 -8
  232. package/skills/arc/references/api-reference.md +4 -0
  233. package/skills/arc/references/auth.md +3 -0
  234. package/skills/arc/references/integrations.md +33 -0
  235. package/skills/arc/references/mcp.md +6 -7
  236. package/skills/arc/references/production.md +24 -25
  237. package/skills/arc-code-review/references/anti-patterns.md +3 -3
  238. package/dist/index-CZY4EGga.d.mts +0 -107
  239. package/dist/module-_-M7C5ES.mjs +0 -29
@@ -1,10 +1,12 @@
1
- import { a as QueryCacheConfig } from "./QueryCache-DOMT_iq7.mjs";
2
- import { P as ArcHeaders, i as RequestScope } from "./types-DjC-KUCz.mjs";
3
- import { c as AnyRecord, i as UserBase, t as PermissionCheck } from "./types-Dmw-6Qeo.mjs";
4
- import "./elevation-C9s5hFP_.mjs";
5
- import { n as FieldPermissionMap } from "./fields-D3lhgzhD.mjs";
6
- import { n as EncryptionDirective } from "./types-Cc91nXuA.mjs";
7
- import { n as DomainEvent } from "./EventTransport-QmY7A258.mjs";
1
+
2
+ import { a as QueryCacheConfig } from "./QueryCache-DFA-QrU9.mjs";
3
+ import { P as ArcHeaders, i as RequestScope } from "./types-ChuShpCP.mjs";
4
+ import { a as UserBase, l as AnyRecord, r as PermissionCheck } from "./types-B7XeLEgK.mjs";
5
+ import "./elevation-BXCfVcNG.mjs";
6
+ import { n as FieldPermissionMap } from "./fields-I_EeRWND.mjs";
7
+ import { n as EncryptionDirective } from "./types-ByHn9rEO.mjs";
8
+ import { n as DomainEvent } from "./EventTransport-2uVV34WU.mjs";
9
+ import { Filter } from "@classytic/repo-core/filter";
8
10
  import { KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
9
11
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
10
12
  import { DataAdapter, RepositoryLike } from "@classytic/repo-core/adapter";
@@ -188,6 +190,22 @@ interface ControllerConfigurableOptions {
188
190
  */
189
191
  interface BaseControllerOptions extends ControllerConstructionOptions, ControllerConfigurableOptions {}
190
192
  //#endregion
193
+ //#region src/scope/projection.d.ts
194
+ /**
195
+ * Lightweight projection of `RequestScope` — just the fields tenant-aware
196
+ * hooks and controllers usually read. Full discriminated-union access is
197
+ * still available through the underlying `RequestScope` for advanced
198
+ * branching on `scope.kind`.
199
+ */
200
+ interface RequestScopeProjection$1 {
201
+ /** Tenant the caller is scoped to (member, pinned elevated admin, service key bound to an org). */
202
+ organizationId?: string;
203
+ /** Caller's user id when authenticated — undefined for public / service-only scopes. */
204
+ userId?: string;
205
+ /** Org-level roles (e.g. `['admin', 'warehouse-manager']`) — separate from global `user.roles`. */
206
+ orgRoles?: string[];
207
+ }
208
+ //#endregion
191
209
  //#region src/hooks/HookSystem.d.ts
192
210
  type HookPhase = "before" | "around" | "after";
193
211
  type HookOperation = "create" | "update" | "delete" | "restore" | "read" | "list";
@@ -200,6 +218,18 @@ interface HookContext<T = AnyRecord> {
200
218
  user?: UserBase;
201
219
  context?: RequestContext;
202
220
  meta?: AnyRecord;
221
+ /**
222
+ * Tenant/user projection of the validated `RequestScope` — the same shape
223
+ * `ResourceHookContext.scope` and `IRequestContext.scope` expose, from the
224
+ * same helper, so every surface reads identity identically.
225
+ *
226
+ * THIS is the identity source in a global hook. `ctx.user` comes from the
227
+ * auth adapter and is not scope-validated; the canonical `updatedBy` hook is
228
+ * `ctx.scope?.userId`, not `ctx.user?.id`. `undefined` on public/unscoped
229
+ * routes. Branch on `scope.kind` via `ctx.context._scope` when you need the
230
+ * full discriminated union.
231
+ */
232
+ scope?: RequestScopeProjection$1;
203
233
  }
204
234
  type HookHandler<T = AnyRecord> = (ctx: HookContext<T>) => void | Promise<void> | T | Promise<T>;
205
235
  /**
@@ -230,14 +260,8 @@ declare class HookSystem {
230
260
  private logger;
231
261
  private warn;
232
262
  constructor(options?: HookSystemOptions);
233
- /**
234
- * Generate hook key
235
- */
236
263
  private getKey;
237
- /**
238
- * Register a hook
239
- * Supports both object parameter and positional arguments
240
- */
264
+ /** Supports both the object-parameter and positional argument forms. */
241
265
  register<T = AnyRecord>(resourceOrOptions: string | {
242
266
  name?: string;
243
267
  resource: string;
@@ -247,13 +271,7 @@ declare class HookSystem {
247
271
  priority?: number;
248
272
  dependsOn?: string[];
249
273
  }, operation?: HookOperation, phase?: HookPhase, handler?: HookHandler<T>, priority?: number): () => void;
250
- /**
251
- * Register before hook
252
- */
253
274
  before<T = AnyRecord>(resource: string, operation: HookOperation, handler: HookHandler<T>, priority?: number): () => void;
254
- /**
255
- * Register after hook
256
- */
257
275
  after<T = AnyRecord>(resource: string, operation: HookOperation, handler: HookHandler<T>, priority?: number): () => void;
258
276
  /**
259
277
  * Register around hook — wraps the core operation.
@@ -269,22 +287,13 @@ declare class HookSystem {
269
287
  context?: RequestContext;
270
288
  meta?: AnyRecord;
271
289
  }): Promise<T | undefined>;
272
- /**
273
- * Execute hooks for a given context
274
- */
275
290
  execute<T = AnyRecord>(ctx: HookContext<T>): Promise<T | undefined>;
276
- /**
277
- * Execute before hooks
278
- */
279
291
  executeBefore<T = AnyRecord>(resource: string, operation: HookOperation, data: T, options?: {
280
292
  user?: UserBase;
281
293
  context?: RequestContext;
282
294
  meta?: AnyRecord;
283
295
  }): Promise<T>;
284
- /**
285
- * Execute after hooks
286
- * Errors in after hooks are logged but don't fail the request
287
- */
296
+ /** Errors in after hooks are logged but never fail the request. */
288
297
  executeAfter<T = AnyRecord>(resource: string, operation: HookOperation, result: T | T[], options?: {
289
298
  user?: UserBase;
290
299
  context?: RequestContext;
@@ -297,13 +306,7 @@ declare class HookSystem {
297
306
  * Hooks without names or dependencies pass through in their original order.
298
307
  */
299
308
  private topologicalSort;
300
- /**
301
- * Get all registered hooks
302
- */
303
309
  getAll(): HookRegistration[];
304
- /**
305
- * Get hooks for a specific resource
306
- */
307
310
  getForResource(resource: string): HookRegistration[];
308
311
  /**
309
312
  * Get hooks matching filter criteria.
@@ -347,13 +350,7 @@ declare class HookSystem {
347
350
  * Check if any hooks exist for a specific resource/operation/phase combination.
348
351
  */
349
352
  has(resource: string, operation: HookOperation, phase: HookPhase): boolean;
350
- /**
351
- * Clear all hooks
352
- */
353
353
  clear(): void;
354
- /**
355
- * Clear hooks for a specific resource
356
- */
357
354
  clearResource(resource: string): void;
358
355
  }
359
356
  /**
@@ -464,7 +461,7 @@ interface AccessControlConfig {
464
461
  /** Minimal repository interface for access-controlled fetch operations */
465
462
  interface AccessControlRepository {
466
463
  getById(id: string, options?: QueryOptions): Promise<unknown>;
467
- getOne?: (filter: AnyRecord, options?: QueryOptions) => Promise<unknown>;
464
+ getOne?: (filter: AnyRecord | Filter, options?: QueryOptions) => Promise<unknown>;
468
465
  }
469
466
  declare class AccessControl {
470
467
  private readonly tenantField;
@@ -483,7 +480,7 @@ declare class AccessControl {
483
480
  * Build filter for single-item operations (get/update/delete)
484
481
  * Combines ID filter with policy/org filters for proper security enforcement
485
482
  */
486
- buildIdFilter(id: string, req: IRequestContext): AnyRecord;
483
+ buildIdFilter(id: string, req: IRequestContext): AnyRecord | Filter;
487
484
  /**
488
485
  * Check if a post-fetch item matches the request's `_policyFilters`.
489
486
  *
@@ -657,7 +654,7 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
657
654
  /**
658
655
  * Composable access control (ID filtering, policy checks, org scope, ownership).
659
656
  *
660
- * Not `readonly` since 2.15.0 — `configure()` rebuilds it when the host
657
+ * Not `readonly` — `configure()` rebuilds it when the host
661
658
  * supplies tenant/idField/matchesFilter post-construction. Same model as
662
659
  * `queryResolver` after `setQueryParser` shipped in 2.10.9.
663
660
  */
@@ -665,7 +662,7 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
665
662
  /**
666
663
  * Composable body sanitization (field permissions, system fields).
667
664
  *
668
- * Not `readonly` since 2.15.0 — `configure()` rebuilds it when the host
665
+ * Not `readonly` — `configure()` rebuilds it when the host
669
666
  * supplies schemaOptions/onFieldWriteDenied post-construction.
670
667
  */
671
668
  bodySanitizer: BodySanitizer;
@@ -854,9 +851,9 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
854
851
  */
855
852
  private scheduleCacheRefresh;
856
853
  /** Cached `list()` flow with SWR semantics. Returns null when cache is disabled. */
857
- protected withListCache(req: IRequestContext, options: ParsedQuery): Promise<IControllerResponse<ListResult<TDoc>> | null>;
854
+ protected withListCache(req: IRequestContext, options: ControllerQueryOptions): Promise<IControllerResponse<ListResult<TDoc>> | null>;
858
855
  /** Cached `get()` flow with SWR semantics. Returns null when cache is disabled. */
859
- protected withGetCache(req: IRequestContext, id: string, options: ParsedQuery): Promise<IControllerResponse<TDoc> | null>;
856
+ protected withGetCache(req: IRequestContext, id: string, options: ControllerQueryOptions): Promise<IControllerResponse<TDoc> | null>;
860
857
  list(req: IRequestContext): Promise<IControllerResponse<ListResult<TDoc>>>;
861
858
  /**
862
859
  * Resource-dispatch verbs router. Returns `null` when the request is
@@ -897,10 +894,10 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
897
894
  */
898
895
  protected isFieldExposedForRead(field: string): boolean;
899
896
  /** Execute list query through hooks (extracted for cache revalidation) */
900
- protected executeListQuery(options: ParsedQuery, req: IRequestContext): Promise<ListResult<TDoc>>;
897
+ protected executeListQuery(options: ControllerQueryOptions, req: IRequestContext): Promise<ListResult<TDoc>>;
901
898
  get(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
902
899
  /** Execute get query through hooks (extracted for cache revalidation) */
903
- protected executeGetQuery(id: string, options: ParsedQuery, req: IRequestContext): Promise<{
900
+ protected executeGetQuery(id: string, options: ControllerQueryOptions, req: IRequestContext): Promise<{
904
901
  doc: TDoc | null;
905
902
  reason: FetchDenialReason | null;
906
903
  }>;
@@ -1242,11 +1239,27 @@ interface AuthPluginOptions {
1242
1239
  strictTokenType?: boolean;
1243
1240
  }
1244
1241
  //#endregion
1242
+ //#region src/core/middlewares/rateLimit.d.ts
1243
+ /**
1244
+ * Per-route CORS override, read by `@fastify/cors` off `routeOptions.config.cors`.
1245
+ *
1246
+ * The app-wide policy cannot serve both an API and public assets: an API wants
1247
+ * `credentials: true` with a pinned origin list, while a public asset wants
1248
+ * `origin: "*"` — and `*` with credentials is forbidden by the CORS spec (arc
1249
+ * throws at boot on that combination). Per-route is the granularity that
1250
+ * resolves it. `false` disables CORS for the route entirely.
1251
+ *
1252
+ * Arc does not interpret the value; `@fastify/cors` owns the semantics. Typed
1253
+ * loosely on purpose so a host is never blocked by arc lagging the plugin's
1254
+ * option surface.
1255
+ */
1256
+ type RouteCorsConfig = Record<string, unknown> | false;
1257
+ //#endregion
1245
1258
  //#region src/types/handlers.d.ts
1246
1259
  /**
1247
1260
  * Minimal server accessor — exposes safe, read-only server decorators.
1248
1261
  * Allows controller handlers to publish events, log, and audit
1249
- * without switching to `raw: true`.
1262
+ * without switching to `rawHandler`.
1250
1263
  */
1251
1264
  interface ServerAccessor {
1252
1265
  /** Event bus — publish domain events from any handler */
@@ -1325,11 +1338,9 @@ interface ServerAccessor {
1325
1338
  /**
1326
1339
  * First-class projection of `request._scope` for controller handlers.
1327
1340
  *
1328
- * **v2.10.6:** previously, pulling tenant/user/role info from inside a
1329
- * controller override meant digging through `req.metadata._scope` and
1330
- * calling `getOrgId(scope)` / `getUserId(user)` manually. This projection
1331
- * lifts the two fields most hosts need directly onto `req` so cross-kit
1332
- * controller code reads:
1341
+ * Lifts the two fields most hosts need directly onto `req`, so a controller
1342
+ * override never digs through `req.metadata._scope` and calls
1343
+ * `getOrgId(scope)` / `getUserId(user)` by hand:
1333
1344
  *
1334
1345
  * ```ts
1335
1346
  * async create(req: IRequestContext) {
@@ -1402,7 +1413,7 @@ interface IRequestContext<TBody = unknown, TParams extends Record<string, string
1402
1413
  metadata?: TMetadata;
1403
1414
  /**
1404
1415
  * Fastify server accessor — publish events, log, and audit
1405
- * from any handler without switching to `raw: true`.
1416
+ * from any handler without switching to `rawHandler`.
1406
1417
  *
1407
1418
  * @example
1408
1419
  * ```typescript
@@ -1437,7 +1448,7 @@ interface IControllerResponse<T = unknown> {
1437
1448
  * Controller handler — Arc's standard pattern.
1438
1449
  *
1439
1450
  * Receives a request context object, returns IControllerResponse.
1440
- * Use with `raw: false` in routes.
1451
+ * Use with `handler` in routes.
1441
1452
  *
1442
1453
  * **Generic parameters:**
1443
1454
  * - `TResponse` — shape of `IControllerResponse.data` (default: `unknown`)
@@ -1474,7 +1485,6 @@ interface IControllerResponse<T = unknown> {
1474
1485
  * path: '/products',
1475
1486
  * handler: createProduct,
1476
1487
  * permissions: requireAuth(),
1477
- * raw: false, // Arc wraps this into Fastify handler
1478
1488
  * }]
1479
1489
  * ```
1480
1490
  */
@@ -1483,7 +1493,7 @@ type ControllerHandler<TResponse = unknown, TBody = unknown, TParams extends Rec
1483
1493
  * Fastify native handler
1484
1494
  *
1485
1495
  * Standard Fastify request/reply pattern.
1486
- * Use with `raw: true` in routes.
1496
+ * Use with `rawHandler` in routes.
1487
1497
  *
1488
1498
  * @example
1489
1499
  * ```typescript
@@ -1496,12 +1506,31 @@ type ControllerHandler<TResponse = unknown, TBody = unknown, TParams extends Rec
1496
1506
  * routes: [{
1497
1507
  * method: 'GET',
1498
1508
  * path: '/files/:id/download',
1499
- * handler: downloadFile,
1509
+ * rawHandler: downloadFile,
1500
1510
  * permissions: requireAuth(),
1501
- * raw: true, // Use as-is, no wrapping
1502
1511
  * }]
1503
1512
  * ```
1504
1513
  */
1514
+ type RawRouteHandler = {
1515
+ /**
1516
+ * Fastify-native route handler — the type behind `RouteDefinition.rawHandler`.
1517
+ *
1518
+ * Declared with METHOD syntax and read back through an indexed access, which
1519
+ * buys two properties a plain arrow alias can't have at once:
1520
+ *
1521
+ * - **Bivariant parameters.** `strictFunctionTypes` exempts method
1522
+ * declarations, so a handler annotated `FastifyRequest<{ Body: CreateBody }>`
1523
+ * assigns without a cast. Fastify's own `RouteHandlerMethod` is an arrow
1524
+ * alias, so it rejects those — hosts used to write `as any`.
1525
+ * - **One call signature.** TypeScript contextually types parameters only
1526
+ * when a single constituent is callable, so inline
1527
+ * `rawHandler: async (request, reply) => …` infers instead of reporting
1528
+ * TS7006.
1529
+ *
1530
+ * The runtime always passes the real `(request, reply)` pair.
1531
+ */
1532
+ handler(this: FastifyInstance, request: FastifyRequest, reply: FastifyReply): unknown;
1533
+ }["handler"];
1505
1534
  type FastifyHandler<RouteGeneric extends Record<string, unknown> = Record<string, unknown>> = (request: FastifyRequest<RouteGeneric>, reply: FastifyReply) => Promise<unknown> | unknown;
1506
1535
  /**
1507
1536
  * Union type for route handlers
@@ -1530,11 +1559,11 @@ interface IController<TDoc = unknown> {
1530
1559
  * Flexible controller interface — accepts controllers with any handler style,
1531
1560
  * including class instances with extra methods / private fields.
1532
1561
  *
1533
- * **v2.10.6:** the previous `[key: string]: unknown` index signature made
1534
- * real class instances fail structural assignment (`new ScrapController()`
1535
- * needed a `as unknown as ControllerLike` cast, because class instances
1536
- * don't have an index signature). Dropped — arc only invokes the CRUD
1537
- * methods at runtime, so the rest of the shape is the caller's concern.
1562
+ * Deliberately NO index signature: one would make real class instances fail
1563
+ * structural assignment (`new ScrapController()` would need an
1564
+ * `as unknown as ControllerLike` cast, because class instances don't have
1565
+ * one). Arc only invokes the CRUD methods at runtime, so the rest of the
1566
+ * shape is the caller's concern.
1538
1567
  *
1539
1568
  * The five CRUD slots stay optional so partial controllers (e.g. read-only)
1540
1569
  * assign too. Additional domain methods on the controller are allowed by
@@ -1580,7 +1609,7 @@ interface RouteMcpConfig {
1580
1609
  *
1581
1610
  * - `handler: 'string'` → controller method → full Arc pipeline + MCP tool
1582
1611
  * - `handler: function` → inline handler → full Arc pipeline + MCP tool
1583
- * - `raw: true` → raw Fastify handler → no pipeline, no MCP by default
1612
+ * - `rawHandler` → raw Fastify handler → no pipeline, no MCP by default
1584
1613
  */
1585
1614
  interface RouteDefinition {
1586
1615
  readonly method: RouteMethod;
@@ -1589,14 +1618,14 @@ interface RouteDefinition {
1589
1618
  /**
1590
1619
  * Route handler.
1591
1620
  * - String: controller method name (Arc pipeline) — runtime lookup, no TS coverage on typos.
1592
- * - Function without `raw: true`: receives IRequestContext, returns IControllerResponse (Arc pipeline)
1593
- * - Function with `raw: true`: raw Fastify handler `(request, reply)`
1621
+ * - Function: receives IRequestContext, returns IControllerResponse (Arc pipeline)
1622
+ * - For a raw Fastify handler `(request, reply)`, use {@link rawHandler}
1594
1623
  *
1595
1624
  * Prefer `controllerMethod` (2.16) over the string form when you want the
1596
1625
  * compiler to catch typos. `handler` stays optional when `controllerMethod`
1597
1626
  * is set — exactly one of the two must be declared.
1598
1627
  */
1599
- readonly handler?: string | ControllerHandler | RouteHandlerMethod | ((request: never, reply: never) => unknown);
1628
+ readonly handler?: string | ControllerHandler;
1600
1629
  /**
1601
1630
  * Typed function-reference handler (2.16). Receives the live controller
1602
1631
  * instance and returns the method to invoke — TypeScript catches typos
@@ -1608,11 +1637,15 @@ interface RouteDefinition {
1608
1637
  * handlers go through), so there's no per-request lookup overhead.
1609
1638
  * The returned method is bound to the controller before dispatch.
1610
1639
  *
1640
+ * PIPELINE-ONLY: the referenced method is invoked as a `ControllerHandler`
1641
+ * (one `IRequestContext`), because the router derives "is this raw?" from
1642
+ * {@link rawHandler} alone and this field is mutually exclusive with it.
1643
+ * There is no flag that could make a `controllerMethod` route raw. For a
1644
+ * Fastify-native controller method, put its NAME in `rawHandler` — the
1645
+ * lookup is the same, and the field states the calling convention.
1646
+ *
1611
1647
  * Mutually exclusive with `handler` — passing both throws at boot
1612
- * with a clear "pick one" message. The runtime contract on what the
1613
- * returned function accepts mirrors the `handler` rules: a plain
1614
- * `ControllerHandler` for pipeline routes, a raw Fastify handler when
1615
- * the route is `raw: true`.
1648
+ * with a clear "pick one" message.
1616
1649
  *
1617
1650
  * Typed loosely (`(controller: unknown) => …`) at the type-system
1618
1651
  * boundary because `RouteDefinition` is not generic over the
@@ -1636,14 +1669,33 @@ interface RouteDefinition {
1636
1669
  * });
1637
1670
  * ```
1638
1671
  */
1639
- readonly controllerMethod?: (controller: unknown) => ControllerHandler | RouteHandlerMethod;
1672
+ readonly controllerMethod?: (controller: unknown) => ControllerHandler;
1640
1673
  /** Permission check — REQUIRED */
1641
1674
  readonly permissions: PermissionCheck;
1642
1675
  /**
1643
- * Raw mode — bypasses Arc pipeline. Handler receives raw Fastify
1644
- * request/reply. Default: false.
1645
- */
1646
- readonly raw?: boolean;
1676
+ * Fastify-native handler — receives `(request, reply)` and owns the response.
1677
+ * Bypasses arc's pipeline entirely: no `IControllerResponse` shaping, no
1678
+ * field-write sanitization.
1679
+ *
1680
+ * Mutually exclusive with {@link handler}. The FIELD carries the intent, so
1681
+ * there is no flag to forget: the previous `raw: boolean` let a Fastify-shaped
1682
+ * function sit in `handler` without it (silently run through the pipeline) or
1683
+ * a pipeline handler sit there with it (silently unwrapped), and neither
1684
+ * mismatch was a type error.
1685
+ *
1686
+ * Splitting the two also restores inference. One field carrying both shapes
1687
+ * meant a union of several function types, which has no single contextual
1688
+ * signature — so an inline `handler: async (req) => …` reported TS7006 and
1689
+ * every author annotated `req` by hand. Fastify types its own
1690
+ * `RouteOptions.handler` as ONE function type for exactly this reason; see
1691
+ * {@link RawRouteHandler} for why arc declares its own rather than reusing
1692
+ * `RouteHandlerMethod` directly.
1693
+ *
1694
+ * Accepts a controller-method NAME as well, same as {@link handler} — the
1695
+ * lookup is identical and only the calling convention differs, so a
1696
+ * Fastify-native controller method stays reachable by name.
1697
+ */
1698
+ readonly rawHandler?: string | RawRouteHandler;
1647
1699
  /** Logical operation name (pipeline keys, MCP tool naming). */
1648
1700
  readonly operation?: string;
1649
1701
  /** OpenAPI summary */
@@ -1675,7 +1727,8 @@ interface RouteDefinition {
1675
1727
  */
1676
1728
  readonly tenantScope?: boolean;
1677
1729
  /**
1678
- * SSE streaming mode. Two handler shapes work:
1730
+ * SSE streaming mode. Requires {@link rawHandler} — the handler is invoked
1731
+ * with `(request, reply)` and owns the response. Two shapes work:
1679
1732
  *
1680
1733
  * - Write to `reply.raw` directly (NDJSON, custom SSE — historical contract).
1681
1734
  * - Return a Web `ReadableStream` (Vercel AI SDK's `result.toUIMessageStream()`,
@@ -1706,7 +1759,7 @@ interface RouteDefinition {
1706
1759
  * `onFieldWriteDenied: 'reject' | 'strip'` setting governs how denials
1707
1760
  * are surfaced.
1708
1761
  *
1709
- * `raw: true` routes ALWAYS bypass this — they've opted out of the
1762
+ * `rawHandler` routes ALWAYS bypass this — they've opted out of the
1710
1763
  * pipeline entirely.
1711
1764
  */
1712
1765
  readonly fieldWrite?: boolean;
@@ -1729,6 +1782,22 @@ interface RouteDefinition {
1729
1782
  * it to a dedicated `routes:` entry, or rely on the resource-level limit.
1730
1783
  */
1731
1784
  readonly rateLimit?: RateLimitConfig | false;
1785
+ /**
1786
+ * Per-route CORS override, forwarded to `@fastify/cors` as
1787
+ * `routeOptions.config.cors`. `undefined` inherits the app policy; `false`
1788
+ * disables CORS for this route; an object replaces the app policy for it.
1789
+ *
1790
+ * Needed because one app-wide policy cannot serve both an API and a public
1791
+ * asset: an API wants `credentials: true` with a pinned origin list, a public
1792
+ * asset wants `origin: "*"`, and `*` + credentials is forbidden by the spec
1793
+ * (arc throws at boot on that pair).
1794
+ *
1795
+ * ```ts
1796
+ * { method: "GET", path: "/manifest.json", permissions: allowPublic(),
1797
+ * cors: { origin: "*", credentials: false }, handler }
1798
+ * ```
1799
+ */
1800
+ readonly cors?: RouteCorsConfig;
1732
1801
  /**
1733
1802
  * Fastify route schema. Each slot (`body`, `querystring`, `params`,
1734
1803
  * `headers`, `response[status]`) accepts a plain JSON Schema object
@@ -2318,12 +2387,9 @@ interface ResourceExtensions {
2318
2387
  * Hook context passed to resource-level hook handlers. Mirrors
2319
2388
  * HookSystem's HookContext but with a simpler API for inline use.
2320
2389
  *
2321
- * **v2.10.8:** `context` and a first-class `scope` projection are now
2322
- * forwarded from the internal `HookContext`. Before this release, inline
2323
- * `config.hooks` handlers had no way to reach the caller's tenant or
2324
- * user info — they had to bypass the documented API and push directly
2325
- * into `resource._pendingHooks` to get the raw internal shape. Now the
2326
- * documented DX is complete:
2390
+ * `context` and a first-class `scope` projection are forwarded from the
2391
+ * internal `HookContext`, so an inline handler reaches the caller's tenant
2392
+ * and user through the documented API:
2327
2393
  *
2328
2394
  * ```ts
2329
2395
  * hooks: {
@@ -2358,7 +2424,7 @@ interface ResourceHookContext {
2358
2424
  * First-class projection of request scope — `{ organizationId?, userId?, orgRoles? }`.
2359
2425
  * Populated for every scoped request so multi-tenant hooks don't have to
2360
2426
  * drill into `context._scope.organizationId` themselves. Matches the
2361
- * identically-named field on `IRequestContext` (v2.10.6) so the same
2427
+ * identically-named field on `IRequestContext` so the same
2362
2428
  * read pattern works in controllers and hooks.
2363
2429
  */
2364
2430
  scope?: {
@@ -2670,6 +2736,16 @@ interface PresetResult {
2670
2736
  /** Preset routes — merged into the resource's `routes` array. */
2671
2737
  routes?: RouteDefinition[] | ((permissions: ResourcePermissions) => RouteDefinition[]);
2672
2738
  middlewares?: MiddlewareConfig;
2739
+ /**
2740
+ * Permission gates the preset contributes as SECURE DEFAULTS. Merged per-op
2741
+ * into the resource's `permissions`, but ONLY for operations the host has not
2742
+ * explicitly gated — the host's own permission always wins. Lets a preset make
2743
+ * authorization part of the permission/introspection model (required auth at
2744
+ * `onRequest`, MCP parity) instead of relying on middleware alone. Example:
2745
+ * `ownedByUser` injects `requireAuth()` on update/delete so an ownership
2746
+ * resource is never public-by-omission.
2747
+ */
2748
+ permissions?: Partial<ResourcePermissions>;
2673
2749
  schemaOptions?: RouteSchemaOptions;
2674
2750
  controllerOptions?: Record<string, unknown>;
2675
2751
  hooks?: PresetHook[];
@@ -2828,7 +2904,7 @@ interface ResourceConfig<TDoc = AnyRecord> {
2828
2904
  * object, primitive) is supported via auto-envelope (2.17+) but the
2829
2905
  * explicit envelope is the canonical contract — declared `fields:`
2830
2906
  * permissions, custom `status`, `meta`, and `headers` only work when
2831
- * you return the envelope. Set `raw: true` on a route to opt out of
2907
+ * you return the envelope. Declare a route's function via `rawHandler` to opt out of
2832
2908
  * arc's pipeline entirely (custom streaming, SSE, manual `reply.send()`).
2833
2909
  *
2834
2910
  * **Path collisions with auto-CRUD are detected at `defineResource()` time.**
@@ -2843,7 +2919,7 @@ interface ResourceConfig<TDoc = AnyRecord> {
2843
2919
  * ```typescript
2844
2920
  * routes: [
2845
2921
  * { method: 'GET', path: '/stats', handler: 'getStats', permissions: auth() },
2846
- * { method: 'POST', path: '/webhook', handler: webhookFn, raw: true, permissions: auth() },
2922
+ * { method: 'POST', path: '/webhook', rawHandler: webhookFn, permissions: auth() },
2847
2923
  * ]
2848
2924
  * ```
2849
2925
  *
@@ -3035,6 +3111,18 @@ interface ResourceConfig<TDoc = AnyRecord> {
3035
3111
  skipValidation?: boolean;
3036
3112
  /** Don't register in introspection */
3037
3113
  skipRegistry?: boolean;
3114
+ /**
3115
+ * Fail-closed permission invariant. When `true`, any enabled CRUD WRITE
3116
+ * (create/update/delete) that mounts WITHOUT a permission gate is a FATAL
3117
+ * define-time error — `defineResource()` throws instead of shipping an
3118
+ * unauthenticated write with only a warning. Reads that are ungated stay a
3119
+ * non-fatal `info` hint (public catalogs are legitimate).
3120
+ *
3121
+ * Off by default (public-by-omission still only warns) so existing hosts are
3122
+ * not broken. Opt in per-resource here, or app-wide via the
3123
+ * `ARC_STRICT_PERMISSIONS=true` env — the recommended production posture.
3124
+ */
3125
+ strictPermissions?: boolean;
3038
3126
  /**
3039
3127
  * Default `limit` value when the request omits `?limit=`. Default 20.
3040
3128
  * Surfaced at the resource level (rather than only via a custom
@@ -3238,16 +3326,19 @@ interface ResourceConfig<TDoc = AnyRecord> {
3238
3326
  * "this resource will never work" failures.
3239
3327
  */
3240
3328
  /**
3241
- * One non-fatal diagnostic produced during resource validation.
3329
+ * One diagnostic produced during resource validation.
3242
3330
  *
3243
- * - `severity`: log level the host logger uses (`warn` for misconfigurations
3244
- * the host should clean up, `info` for deprecation hints).
3331
+ * - `severity`: `error` is FATAL — `defineResource()` throws at define-time,
3332
+ * the resource never boots (used by strict security invariants like ungated
3333
+ * CRUD under `strictPermissions`). `warn` flags a misconfiguration the host
3334
+ * should clean up; `info` is a deprecation hint. `warn`/`info` are non-fatal
3335
+ * and flushed through the host logger on first mount.
3245
3336
  * - `code`: stable identifier for the diagnostic — hosts and tests can match
3246
3337
  * on this without parsing the human-readable message.
3247
3338
  * - `message`: pre-formatted line, resource name already interpolated.
3248
3339
  */
3249
3340
  interface ResourceDiagnostic {
3250
- severity: "warn" | "info";
3341
+ severity: "error" | "warn" | "info";
3251
3342
  code: string;
3252
3343
  message: string;
3253
3344
  }
@@ -3787,6 +3878,7 @@ interface ArcInternalMetadata extends RequestContext {
3787
3878
  _ownershipCheck?: {
3788
3879
  field: string;
3789
3880
  userId: string;
3881
+ missingOwner?: "deny" | "allow";
3790
3882
  };
3791
3883
  /** Arc instance references (hooks, field permissions, etc.) */
3792
3884
  arc?: {
@@ -3819,7 +3911,13 @@ interface ControllerQueryOptions {
3819
3911
  */
3820
3912
  lookups?: LookupOption[];
3821
3913
  select?: string | string[] | Record<string, 0 | 1>;
3822
- filters?: Record<string, unknown>;
3914
+ /**
3915
+ * Resolved row filter. Flat equality filters are a plain record; filters
3916
+ * carrying `$`-operators (`$or`/`$and`/`$gte`) are normalized to the portable
3917
+ * repo-core `Filter` IR at the repository boundary (see `toRepositoryFilter`)
3918
+ * so every kit compiles them. Repositories accept both (`FilterInput`).
3919
+ */
3920
+ filters?: Record<string, unknown> | Filter;
3823
3921
  search?: string;
3824
3922
  lean?: boolean;
3825
3923
  after?: string;
@@ -3875,6 +3973,14 @@ interface PopulateOption {
3875
3973
  * parsers (MongoKit, PrismaKit) add fields without breaking Arc's types.
3876
3974
  */
3877
3975
  interface ParsedQuery {
3976
+ /**
3977
+ * Row filter — a plain record in the parser's operator dialect. This is the
3978
+ * PARSER contract: a `QueryParser` (arc built-in, MongoKit, PrismaKit) emits a
3979
+ * record, never repo-core `Filter` IR. IR appears only AFTER security
3980
+ * composition, on `ControllerQueryOptions.filters` (via `toRepositoryFilter`).
3981
+ * Keeping this record-only means `QueryResolver` never has to guess whether a
3982
+ * parsed filter is an IR node it must not blindly spread tenant/policy onto.
3983
+ */
3878
3984
  filters?: Record<string, unknown>;
3879
3985
  limit?: number;
3880
3986
  sort?: string | Record<string, 1 | -1>;
@@ -4189,4 +4295,4 @@ interface ValidateOptions {
4189
4295
  strict?: boolean;
4190
4296
  }
4191
4297
  //#endregion
4192
- export { OpenApiSchemas as $, SlugExt as $t, RequestIdOptions as A, ControllerConfigurableOptions as An, RouteDefinition as At, defineResource as B, RouteHandler as Bt, RequestContext as C, defineHook as Cn, PipelineStep as Ct, HealthCheck as D, ArcListResult as Dn, ActionEntry as Dt, GracefulShutdownOptions as E, ArcGetResult as En, ActionDefinition as Et, FastifyWithDecorators as F, ControllerLike as Ft, OnTenantDeleteConfig as G, AuthenticatorContext as Gt, CrudController as H, AuthHelpers as Ht, MiddlewareHandler as I, FastifyHandler as It, PresetHook as J, BaseController as Jt, ResolvedTenantPurge as K, JwtContext as Kt, RequestWithExtras as L, IController as Lt, EventsDecorator as M, ListResult as Mn, RouteMethod as Mt, FastifyRequestExtras as N, BodySanitizer as Nn, RateLimitConfig as Nt, HealthOptions as O, ArcUpdateResult as On, ActionHandlerFn as Ot, FastifyWithAuth as P, BodySanitizerConfig as Pn, ControllerHandler as Pt, MiddlewareConfig as Q, TreeMixin as Qt, RegisterOptions as R, IControllerResponse as Rt, QueryParserInterface as S, createHookSystem as Sn, PipelineContext as St, CrudRouterOptions as T, ArcDeleteResult as Tn, ResourceCacheConfig as Tt, ResourceConfig as U, AuthPluginOptions as Ut, ResourceDefinition as V, ServerAccessor as Vt, ResourcePermissions as W, Authenticator as Wt, CrudRouteKey as X, SoftDeleteMixin as Xt, PresetResult as Y, SoftDeleteExt as Yt, CrudSchemas as Z, TreeExt as Zt, ControllerQueryOptions as _, afterDelete as _n, Guard as _t, InferAdapterDoc as a, QueryResolverConfig as an, ResourceExtensions as at, ParsedQuery as b, beforeDelete as bn, OperationFilter as bt, TypedController as c, DefineHookOptions as cn, AggMeasureShorthand as ct, PaginationResult as d, HookOperation as dn, AggregationDateRangeRequirement as dt, SlugMixin as en, RouteSchemaOptions as et, IntrospectionData as f, HookPhase as fn, AggregationIndexHint as ft, ArcInternalMetadata as g, afterCreate as gn, AggregationsMap as gt, ResourceMetadata as h, HookSystemOptions as hn, AggregationRateLimit as ht, ValidationResult as i, QueryResolver as in, ResourceHooks as it, ArcDecorator as j, ControllerConstructionOptions as jn, RouteMcpConfig as jt, IntrospectionPluginOptions as k, BaseControllerOptions as kn, ActionsMap as kt, TypedRepository as l, HookContext as ln, AggregationCacheConfig as lt, RegistryStats as m, HookSystem as mn, AggregationMaterializedResult as mt, ConfigError as n, BulkMixin as nn, FieldRule$1 as nt, InferDocType as o, AccessControl as on, EventDefinition as ot, RegistryEntry as p, HookRegistration as pn, AggregationMaterializedContext as pt, PresetFunction as q, TokenPair as qt, ValidateOptions as r, BaseCrudController as rn, ResourceHookContext as rt, InferResourceDoc as s, AccessControlConfig as sn, AggMeasureInput as st, RouteHandlerMethod$1 as t, BulkExt as tn, ArcFieldRule as tt, TypedResourceConfig as u, HookHandler as un, AggregationConfig as ut, LookupOption as v, afterUpdate as vn, Interceptor as vt, ServiceContext as w, ArcCreateResult as wn, Transform as wt, PopulateOption as x, beforeUpdate as xn, PipelineConfig as xt, OwnershipCheck as y, beforeCreate as yn, NextFunction as yt, ResourceRegistry as z, IRequestContext as zt };
4298
+ export { OpenApiSchemas as $, TreeMixin as $t, RequestIdOptions as A, BaseControllerOptions as An, RouteDefinition as At, defineResource as B, RawRouteHandler as Bt, RequestContext as C, createHookSystem as Cn, PipelineStep as Ct, HealthCheck as D, ArcGetResult as Dn, ActionEntry as Dt, GracefulShutdownOptions as E, ArcDeleteResult as En, ActionDefinition as Et, FastifyWithDecorators as F, BodySanitizerConfig as Fn, ControllerLike as Ft, OnTenantDeleteConfig as G, Authenticator as Gt, CrudController as H, ServerAccessor as Ht, MiddlewareHandler as I, FastifyHandler as It, PresetHook as J, TokenPair as Jt, ResolvedTenantPurge as K, AuthenticatorContext as Kt, RequestWithExtras as L, IController as Lt, EventsDecorator as M, ControllerConstructionOptions as Mn, RouteMethod as Mt, FastifyRequestExtras as N, ListResult as Nn, RateLimitConfig as Nt, HealthOptions as O, ArcListResult as On, ActionHandlerFn as Ot, FastifyWithAuth as P, BodySanitizer as Pn, ControllerHandler as Pt, MiddlewareConfig as Q, TreeExt as Qt, RegisterOptions as R, IControllerResponse as Rt, QueryParserInterface as S, beforeUpdate as Sn, PipelineContext as St, CrudRouterOptions as T, ArcCreateResult as Tn, ResourceCacheConfig as Tt, ResourceConfig as U, AuthHelpers as Ut, ResourceDefinition as V, RouteHandler as Vt, ResourcePermissions as W, AuthPluginOptions as Wt, CrudRouteKey as X, SoftDeleteExt as Xt, PresetResult as Y, BaseController as Yt, CrudSchemas as Z, SoftDeleteMixin as Zt, ControllerQueryOptions as _, afterCreate as _n, Guard as _t, InferAdapterDoc as a, QueryResolver as an, ResourceExtensions as at, ParsedQuery as b, beforeCreate as bn, OperationFilter as bt, TypedController as c, AccessControlConfig as cn, AggMeasureShorthand as ct, PaginationResult as d, HookHandler as dn, AggregationDateRangeRequirement as dt, SlugExt as en, RouteSchemaOptions as et, IntrospectionData as f, HookOperation as fn, AggregationIndexHint as ft, ArcInternalMetadata as g, HookSystemOptions as gn, AggregationsMap as gt, ResourceMetadata as h, HookSystem as hn, AggregationRateLimit as ht, ValidationResult as i, BaseCrudController as in, ResourceHooks as it, ArcDecorator as j, ControllerConfigurableOptions as jn, RouteMcpConfig as jt, IntrospectionPluginOptions as k, ArcUpdateResult as kn, ActionsMap as kt, TypedRepository as l, DefineHookOptions as ln, AggregationCacheConfig as lt, RegistryStats as m, HookRegistration as mn, AggregationMaterializedResult as mt, ConfigError as n, BulkExt as nn, FieldRule$1 as nt, InferDocType as o, QueryResolverConfig as on, EventDefinition as ot, RegistryEntry as p, HookPhase as pn, AggregationMaterializedContext as pt, PresetFunction as q, JwtContext as qt, ValidateOptions as r, BulkMixin as rn, ResourceHookContext as rt, InferResourceDoc as s, AccessControl as sn, AggMeasureInput as st, RouteHandlerMethod$1 as t, SlugMixin as tn, ArcFieldRule as tt, TypedResourceConfig as u, HookContext as un, AggregationConfig as ut, LookupOption as v, afterDelete as vn, Interceptor as vt, ServiceContext as w, defineHook as wn, Transform as wt, PopulateOption as x, beforeDelete as xn, PipelineConfig as xt, OwnershipCheck as y, afterUpdate as yn, NextFunction as yt, ResourceRegistry as z, IRequestContext as zt };