@classytic/arc 2.41.0 → 2.43.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 (230) hide show
  1. package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
  2. package/dist/{EventTransport-n7qO5Cz3.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
  3. package/dist/{HookSystem-CxvZBo77.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-Cu7uTJp9.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
  6. package/dist/actionPermissions-BotX1YOa.mjs +776 -0
  7. package/dist/audit/index.d.mts +43 -5
  8. package/dist/audit/index.mjs +48 -26
  9. package/dist/auth/audit.mjs +1 -1
  10. package/dist/auth/index.d.mts +43 -13
  11. package/dist/auth/index.mjs +130 -155
  12. package/dist/auth/redis-session.mjs +1 -1
  13. package/dist/{authorizationDecision-2YY_Kaj1.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
  14. package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
  15. package/dist/boundedMap-BULzfXYU.mjs +57 -0
  16. package/dist/{buildHandler-CUhjGHGZ.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
  17. package/dist/cache/index.d.mts +22 -34
  18. package/dist/cache/index.mjs +4 -4
  19. package/dist/{caching-DsWDjIxx.mjs → caching-Cc2M1DwO.mjs} +3 -3
  20. package/dist/claimed-B10RTfkM.mjs +8 -0
  21. package/dist/cleanup/index.d.mts +6 -10
  22. package/dist/cleanup/index.mjs +27 -73
  23. package/dist/cli/commands/describe.d.mts +4 -2
  24. package/dist/cli/commands/describe.mjs +7 -4
  25. package/dist/cli/commands/docs.mjs +3 -3
  26. package/dist/cli/commands/doctor.mjs +1 -1
  27. package/dist/cli/commands/generate.mjs +2 -2
  28. package/dist/cli/commands/init.mjs +2 -2
  29. package/dist/cli/commands/introspect.mjs +2 -2
  30. package/dist/cli/index.mjs +3 -3
  31. package/dist/{constants-BxLgNFF7.mjs → constants-Cu7Mx7_U.mjs} +4 -2
  32. package/dist/context/index.d.mts +2 -5
  33. package/dist/context/index.mjs +18 -38
  34. package/dist/core/index.d.mts +3 -3
  35. package/dist/core/index.mjs +8 -8
  36. package/dist/{core-iNGqYerO.mjs → core-Cb0LjccA.mjs} +5 -4
  37. package/dist/{createActionRouter-BBlbPs3k.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
  38. package/dist/{createAggregationRouter-Dv5SuK2x.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
  39. package/dist/{preloadResources-Bbp4QJ8V.mjs → createApp-D-953ZAq.mjs} +299 -390
  40. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
  41. package/dist/{defineAggregation-UyeurGkp.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
  42. package/dist/{defineResource-DzGT-2LP.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
  43. package/dist/discovery/index.mjs +1 -1
  44. package/dist/docs/index.d.mts +10 -4
  45. package/dist/docs/index.mjs +4 -4
  46. package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
  47. package/dist/{elevation-BF-vhGxY.mjs → elevation-DG8SrLN-.mjs} +41 -12
  48. package/dist/encryption/index.mjs +3 -3
  49. package/dist/{errorHandler-CIMkCOWm.mjs → errorHandler-BnfDviL1.mjs} +26 -5
  50. package/dist/{errors-C_YhELhe.mjs → errors-CtrywMHP.mjs} +58 -23
  51. package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
  52. package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
  53. package/dist/{eventPlugin-CmXaPWLv.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
  54. package/dist/{subscribe-helpers-AVYPgqTl.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
  55. package/dist/events/index.d.mts +33 -8
  56. package/dist/events/index.mjs +49 -53
  57. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  58. package/dist/events/transports/redis-stream-entry.mjs +139 -157
  59. package/dist/events/transports/redis.d.mts +1 -1
  60. package/dist/events/transports/redis.mjs +2 -2
  61. package/dist/factory/index.d.mts +32 -17
  62. package/dist/factory/index.mjs +213 -32
  63. package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
  64. package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
  65. package/dist/{fields-BTyPLHK0.mjs → fields-ToPYpMqS.mjs} +1 -1
  66. package/dist/filter-merge-IpGywvym.mjs +99 -0
  67. package/dist/{generate-D_YmiCNf.mjs → generate-DRTkLlIl.mjs} +2 -2
  68. package/dist/{headers-CNECDE6q.mjs → headers-BbkIF6vA.mjs} +2 -2
  69. package/dist/health-D-Ft6JM6.mjs +22 -0
  70. package/dist/{health-CxQzbJmr.mjs → health-DqbsG-_w.mjs} +115 -55
  71. package/dist/hooks/index.d.mts +2 -2
  72. package/dist/hooks/index.mjs +2 -2
  73. package/dist/idempotency/index.d.mts +31 -7
  74. package/dist/idempotency/index.mjs +155 -98
  75. package/dist/idempotency/redis.d.mts +1 -1
  76. package/dist/idempotency/redis.mjs +48 -11
  77. package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
  78. package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
  79. package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
  80. package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
  81. package/dist/index-JdbZAIsm.d.mts +510 -0
  82. package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
  83. package/dist/index.d.mts +8 -8
  84. package/dist/index.mjs +11 -11
  85. package/dist/{init-Y2MlSN9n.mjs → init-B_oUYDXI.mjs} +1 -1
  86. package/dist/integrations/event-gateway.d.mts +8 -3
  87. package/dist/integrations/event-gateway.mjs +4 -4
  88. package/dist/integrations/index.d.mts +3 -3
  89. package/dist/integrations/index.mjs +1 -1
  90. package/dist/integrations/jobs/index.d.mts +1 -1
  91. package/dist/integrations/jobs/index.mjs +18 -5
  92. package/dist/integrations/mcp/index.d.mts +72 -59
  93. package/dist/integrations/mcp/index.mjs +19 -4
  94. package/dist/integrations/mcp/testing.d.mts +2 -2
  95. package/dist/integrations/mcp/testing.mjs +2 -2
  96. package/dist/integrations/streamline.d.mts +78 -136
  97. package/dist/integrations/streamline.mjs +116 -54
  98. package/dist/integrations/webhooks.d.mts +60 -120
  99. package/dist/integrations/webhooks.mjs +36 -80
  100. package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
  101. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  102. package/dist/integrations/websocket-redis.d.mts +1 -1
  103. package/dist/integrations/websocket-redis.mjs +1 -1
  104. package/dist/integrations/websocket.d.mts +3 -3
  105. package/dist/integrations/websocket.mjs +2 -2
  106. package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
  107. package/dist/{jsonBody-Cq9KYl2Z.mjs → jsonBody-B4YVGcql.mjs} +2 -2
  108. package/dist/{keys-Cmv4cBRa.mjs → keys-B2KtgMLJ.mjs} +7 -18
  109. package/dist/{loadResources-CblF7xt2.mjs → loadResources-Dn4lalw2.mjs} +3 -3
  110. package/dist/{loadResourcesFromEntry-99fIMC3p.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
  111. package/dist/logger/index.d.mts +1 -8
  112. package/dist/logger/index.mjs +3 -153
  113. package/dist/logger-DXjfIqjV.mjs +160 -0
  114. package/dist/{mcpPlugin-xF3Gw-HW.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
  115. package/dist/{memory-CpDZ9NPw.mjs → memory-9Cp7611C.mjs} +36 -34
  116. package/dist/{metrics-BaKnq8ag.mjs → metrics-CWI11eRu.mjs} +39 -72
  117. package/dist/middleware/index.d.mts +2 -2
  118. package/dist/middleware/index.mjs +3 -3
  119. package/dist/migrations/index.mjs +2 -2
  120. package/dist/{module-BkQEesNQ.mjs → module-BSvOQC7E.mjs} +8 -8
  121. package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
  122. package/dist/{multipartBody-C92aJ8am.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
  123. package/dist/{openapi-C3LFRzPq.mjs → openapi-DT_G4VhY.mjs} +48 -10
  124. package/dist/outbox-admin/index.d.mts +3 -3
  125. package/dist/outbox-admin/index.mjs +6 -18
  126. package/dist/{outbox-module-dsCyLxXH.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
  127. package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
  128. package/dist/permissions/index.d.mts +5 -4
  129. package/dist/permissions/index.mjs +8 -6
  130. package/dist/{permissions-DiHXy8Ck.mjs → permissions-Cm0uYHvd.mjs} +149 -33
  131. package/dist/{pipe-CBVmUJ-j.mjs → pipe-BqeGT6UO.mjs} +2 -2
  132. package/dist/pipeline/index.d.mts +2 -2
  133. package/dist/pipeline/index.mjs +2 -2
  134. package/dist/plan-FVR6LP59.mjs +565 -0
  135. package/dist/{BaseController-DYJRZ0EJ.mjs → plugin-BccET1tL.mjs} +1256 -344
  136. package/dist/plugins/index.d.mts +14 -11
  137. package/dist/plugins/index.mjs +44 -22
  138. package/dist/plugins/response-cache.mjs +8 -14
  139. package/dist/plugins/tracing-entry.mjs +2 -2
  140. package/dist/{pluralize-2_U6QZQg.mjs → pluralize-CVIOcgMF.mjs} +1 -1
  141. package/dist/presets/filesUpload.d.mts +2 -2
  142. package/dist/presets/filesUpload.mjs +5 -5
  143. package/dist/presets/index.d.mts +26 -31
  144. package/dist/presets/index.mjs +4 -4
  145. package/dist/presets/multiTenant.d.mts +2 -107
  146. package/dist/presets/multiTenant.mjs +22 -162
  147. package/dist/presets/search.d.mts +2 -2
  148. package/dist/presets/search.mjs +2 -2
  149. package/dist/{presets-BdlugYTc.mjs → presets-Cpvj6fGO.mjs} +99 -107
  150. package/dist/{projection-Uvgp-EEj.mjs → projection-Ce4aQhQk.mjs} +2 -2
  151. package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
  152. package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
  153. package/dist/{queryCachePlugin-B5F09bYr.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
  154. package/dist/{rateLimitKey-CK-EU2L8.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
  155. package/dist/{realtime-FnnD0n3Z.mjs → realtime-DmPE9S54.mjs} +25 -21
  156. package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
  157. package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
  158. package/dist/redisShapes-CQksY67q.d.mts +55 -0
  159. package/dist/registry/index.d.mts +9 -4
  160. package/dist/registry/index.mjs +4 -4
  161. package/dist/{registry-D3y8VEJ1.mjs → registry-Bcz9G1bh.mjs} +10 -8
  162. package/dist/{renewingLease-B3VYh5aU.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
  163. package/dist/{replyHelpers-DPHsdw-5.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
  164. package/dist/{requestContext-cSVKnYPk.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
  165. package/dist/requirement-CbiLdQ1n.d.mts +22 -0
  166. package/dist/requirement-KDdoRcOP.mjs +35 -0
  167. package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
  168. package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
  169. package/dist/{rolldown-runtime-DjDuyglE.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
  170. package/dist/routeValidator-D36SW8qt.mjs +70 -0
  171. package/dist/{runtimeCapabilities-DSA_dH1_.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
  172. package/dist/{schedules-BktJb3Y3.mjs → schedules-C-OpmHBq.mjs} +46 -37
  173. package/dist/{schemaConverter-BLCSmNbr.mjs → schemaConverter-BORek3O-.mjs} +2 -2
  174. package/dist/{schemaIR-K16Bb5BX.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
  175. package/dist/schemas/index.d.mts +10 -0
  176. package/dist/schemas/index.mjs +8 -3
  177. package/dist/scim/index.mjs +2 -2
  178. package/dist/scope/index.d.mts +2 -2
  179. package/dist/scope/index.mjs +6 -6
  180. package/dist/{scope-jSxuBTFM.mjs → scope-DQthe88b.mjs} +4 -4
  181. package/dist/shutdown-MqA81z_P.mjs +59 -0
  182. package/dist/{simpleEqualityMatcher-BiOnyflP.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
  183. package/dist/{sse-CH7Slxc1.mjs → sse-CYzQBVFe.mjs} +11 -15
  184. package/dist/{sseStream-CN-Lh1Tu.mjs → sseStream-B5_Afiqn.mjs} +2 -2
  185. package/dist/{store-helpers-9DX9-1j_.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
  186. package/dist/{streaming-dvppnxsr.mjs → streaming-DjTQhZrN.mjs} +1 -1
  187. package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
  188. package/dist/tenancy--ed9eH_z.mjs +43 -0
  189. package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
  190. package/dist/testing/cleanupStoreContract.mjs +1 -1
  191. package/dist/testing/index.d.mts +193 -175
  192. package/dist/testing/index.mjs +286 -212
  193. package/dist/testing/outboxStoreContract.mjs +137 -2
  194. package/dist/testing/requiredPorts.d.mts +17 -0
  195. package/dist/testing/requiredPorts.mjs +65 -0
  196. package/dist/testing/storageContract.mjs +1 -1
  197. package/dist/timing-qczZ0YC-.mjs +46 -0
  198. package/dist/{transactionContext-DCWMI6hV.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
  199. package/dist/{typeGuards-D7tHFdH6.mjs → typeGuards-CySF12G-.mjs} +1 -1
  200. package/dist/types/index.d.mts +4 -4
  201. package/dist/types/index.mjs +1 -1
  202. package/dist/types/storage.mjs +1 -1
  203. package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
  204. package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
  205. package/dist/{types-CDy3-zw1.mjs → types-Bk9NOgOC.mjs} +4 -4
  206. package/dist/usage/index.d.mts +2 -3
  207. package/dist/usage/index.mjs +5 -5
  208. package/dist/{userHelpers-DDMOZ_Pm.mjs → userHelpers-aklawRaK.mjs} +1 -1
  209. package/dist/utils/index.d.mts +84 -223
  210. package/dist/utils/index.mjs +13 -12
  211. package/dist/{utils-B-zj_b__.mjs → utils-ec1oTo1n.mjs} +315 -404
  212. package/dist/{validate-R0G_JrnK.mjs → validate-9yzHC1e2.mjs} +6 -14
  213. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  214. package/dist/{versioning-DuVWcu-F.mjs → versioning-fhhWHV74.mjs} +4 -4
  215. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  216. package/dist/{workScope-BhsWde4k.mjs → workScope-zpPyge4t.mjs} +2 -2
  217. package/package.json +578 -574
  218. package/skills/arc/SKILL.md +13 -4
  219. package/skills/arc/references/auth.md +8 -5
  220. package/skills/arc/references/integrations.md +1 -1
  221. package/skills/arc/references/mcp.md +8 -6
  222. package/skills/arc/references/multi-tenancy.md +19 -14
  223. package/skills/arc/references/production.md +27 -10
  224. package/dist/actionPermissions-DPwPWPOV.mjs +0 -350
  225. package/dist/index-CFfLKMH4.d.mts +0 -290
  226. package/dist/interface-BIpgzbZl.mjs +0 -17
  227. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  228. package/dist/registerRoute-DRsSHPSM.mjs +0 -364
  229. package/dist/sync/changelog.d.mts +0 -3
  230. package/dist/sync/changelog.mjs +0 -3
@@ -1,15 +1,16 @@
1
1
 
2
2
  import { P as ArcHeaders, i as RequestScope } from "./types-Ddl9OjmE.mjs";
3
- import { d as UserBase, l as PermissionCheck, t as AnyRecord } from "./base-CxrV1LmG.mjs";
4
- import { a as QueryCacheConfig } from "./QueryCache-DFA-QrU9.mjs";
5
- import "./elevation-BXCfVcNG.mjs";
6
- import { n as FieldPermissionMap } from "./fields-I_EeRWND.mjs";
3
+ import { d as UserBase, l as PermissionCheck, t as AnyRecord } from "./base-CZyixEQR.mjs";
4
+ import { a as QueryCacheConfig } from "./QueryCache--5uAuYPW.mjs";
5
+ import "./elevation-BW2J3tNj.mjs";
6
+ import "./requirement-CbiLdQ1n.mjs";
7
+ import { n as FieldPermissionMap } from "./fields-BBSpf8pS.mjs";
7
8
  import { n as EncryptionDirective } from "./types-ByHn9rEO.mjs";
8
- import { n as DomainEvent } from "./EventTransport-CbUKDzZZ.mjs";
9
+ import { n as DomainEvent } from "./EventTransport-CiicKz2Z.mjs";
9
10
  import { Filter } from "@classytic/repo-core/filter";
10
- import { AggDateBucket, AggMeasure, AggTopN, QueryOptions, StandardRepo, TenantPurgeStrategy, TransactionHandle } from "@classytic/repo-core/repository";
11
+ import { AggDateBucket, AggMeasure, AggTopN, QueryOptions, TenantPurgeStrategy, TransactionHandle } from "@classytic/repo-core/repository";
11
12
  import { KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
12
- import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
13
+ import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
13
14
  import { DataAdapter, RepositoryLike } from "@classytic/repo-core/adapter";
14
15
  import { LookupSpec } from "@classytic/repo-core/lookup";
15
16
  import { FieldRule, SchemaBuilderOptions } from "@classytic/repo-core/schema";
@@ -608,6 +609,12 @@ interface ControllerConfigurableOptions {
608
609
  * own `idField` when unset.
609
610
  */
610
611
  idField?: string;
612
+ /**
613
+ * Further unique keys a single-row read and an action target accept, tried in order after
614
+ * `idField` (e.g. `['rmaNumber']` beside `_id`). Each lookup carries the same tenant and row
615
+ * policy as the primary one — an alternate key never widens what a caller can reach.
616
+ */
617
+ lookupFields?: readonly string[];
611
618
  /**
612
619
  * Custom filter matching for policy enforcement.
613
620
  * Provided by the DataAdapter for non-MongoDB databases (SQL, etc.).
@@ -645,6 +652,10 @@ interface ControllerConfigurableOptions {
645
652
  * `configure()` like `writes`.
646
653
  */
647
654
  transactional?: boolean;
655
+ /** Optimistic concurrency — see `ResourceConfig.concurrency`. */
656
+ concurrency?: {
657
+ versionField?: string;
658
+ };
648
659
  }
649
660
  /**
650
661
  * Full constructor options — union of construction-only + configurable.
@@ -1103,9 +1114,9 @@ declare class QueryResolver {
1103
1114
  /** Sanitize advanced populate options against allowedPopulate */
1104
1115
  private sanitizePopulateOptions;
1105
1116
  /**
1106
- * Sanitize lookup/join options. Deny-by-default: `allowedLookups` must be
1107
- * configured for any client-driven lookup to pass; listed collections are
1108
- * then structurally validated to prevent injection.
1117
+ * Client lookups, deny-by-default. An allowed one is structurally validated, and its joined side
1118
+ * is filtered by the resource's server-owned `LookupPolicy.scope` — the base collection's tenant
1119
+ * filter never reaches a join, and a client-supplied `where` is replaced, never merged.
1109
1120
  */
1110
1121
  private sanitizeLookups;
1111
1122
  /**
@@ -1147,10 +1158,7 @@ declare class QueryResolver {
1147
1158
  //#region src/core/BaseCrudController.d.ts
1148
1159
  /**
1149
1160
  * Framework-agnostic CRUD controller implementing IController.
1150
- *
1151
- * Composes AccessControl, BodySanitizer, and QueryResolver. All shared
1152
- * state and helpers are `protected` so the preset mixins (SoftDelete,
1153
- * Tree, Slug, Bulk) can extend cleanly.
1161
+ * Composes AccessControl, BodySanitizer, and QueryResolver.
1154
1162
  *
1155
1163
  * @template TDoc - The document type.
1156
1164
  * @template TRepository - The repository type (defaults to RepositoryLike).
@@ -1164,29 +1172,14 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1164
1172
  protected resourceName?: string;
1165
1173
  protected tenantField: string | false;
1166
1174
  protected idField: string;
1167
- /**
1168
- * Composable access control (ID filtering, policy checks, org scope, ownership).
1169
- *
1170
- * Not `readonly` — `configure()` rebuilds it when the host
1171
- * supplies tenant/idField/matchesFilter post-construction. Same model as
1172
- * `queryResolver` after `setQueryParser` shipped in 2.10.9.
1173
- */
1175
+ /** Alternate unique keys, each with its own access control over the SAME scope. */
1176
+ protected lookupAccess: AccessControl[];
1177
+ private _lookupFields;
1178
+ /** Access control (ID filtering, policy checks, org scope, ownership). */
1174
1179
  accessControl: AccessControl;
1175
- /**
1176
- * Composable body sanitization (field permissions, system fields).
1177
- *
1178
- * Not `readonly` — `configure()` rebuilds it when the host
1179
- * supplies schemaOptions/onFieldWriteDenied post-construction.
1180
- */
1180
+ /** Body sanitization (field permissions, system fields). */
1181
1181
  bodySanitizer: BodySanitizer;
1182
- /**
1183
- * Composable query resolution (parsing, pagination, sort, select/populate).
1184
- *
1185
- * Not `readonly` — `setQueryParser()` rebuilds this resolver to swap in a
1186
- * different parser (e.g. mongokit's `QueryParser`). `defineResource` calls
1187
- * it automatically when a resource supplies both `controller` and
1188
- * `queryParser`.
1189
- */
1182
+ /** Query resolution (parsing, pagination, sort, select/populate). */
1190
1183
  queryResolver: QueryResolver;
1191
1184
  protected _matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;
1192
1185
  protected _presetFields: {
@@ -1195,82 +1188,51 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1195
1188
  };
1196
1189
  protected _cacheConfig?: ResourceCacheConfig;
1197
1190
  /**
1198
- * Retained construction-time values so partial `configure()` rebuilds
1199
- * (e.g. `configure({ schemaOptions })`) reuse them instead of silently
1200
- * falling back to the sub-component defaults (`-createdAt` sort,
1201
- * `reject` write-denial) — `defaultSort: false` matters for kits
1202
- * without a `createdAt` column, `strip` for legacy write semantics.
1191
+ * Retained so partial `configure()` rebuilds reuse them instead of falling back
1192
+ * to sub-component defaults (`-createdAt` sort breaks kits without that column).
1203
1193
  */
1204
1194
  protected _defaultSort?: string | false;
1205
1195
  protected _onFieldWriteDenied?: FieldWriteDenialPolicy;
1206
1196
  protected _onImmutableWrite?: FieldWriteDenialPolicy;
1207
1197
  /**
1208
- * Domain commands bound to the write slots (`ResourceWrites`). Consulted at
1209
- * the PERSISTENCE step only — everything upstream of it (sanitizer, tenant
1210
- * injection, actor stamp, hook sandwich) runs exactly as it does without
1211
- * one, which is the entire point of the seam.
1198
+ * Domain commands bound to the write slots. Consulted at the PERSISTENCE step
1199
+ * only — sanitizer, tenant injection, actor stamp, and hooks run unchanged.
1212
1200
  */
1213
1201
  protected _writes?: ResourceWrites;
1214
1202
  /** Transactional write envelope — see `ResourceConfig.transactional`. */
1215
1203
  protected _transactional: boolean;
1204
+ /** Version field under optimistic concurrency — `null` when the resource did not opt in. */
1205
+ protected _versionField: string | null;
1216
1206
  constructor(repository: TRepository, options?: BaseControllerOptions);
1217
1207
  /**
1218
- * Swap the controller's query parser. Mutates the existing `QueryResolver`
1219
- * in place via `QueryResolver.setParser()` — the resolver instance stays
1220
- * referentially stable, and there is no second copy of `defaultSort` /
1221
- * `tenantField` / `schemaOptions` for the swap to drift away from.
1222
- *
1223
- * Closes the v2.10.9 gap where `defineResource({ controller, queryParser })`
1224
- * forwarded the parser only to auto-constructed controllers. `defineResource`
1225
- * calls this via duck-typing when both `controller` and `queryParser` are
1226
- * supplied; controllers that don't implement it are left untouched.
1208
+ * Bind the resource this controller serves — hooks, audit, events and cache key on it.
1209
+ * `defineResource` calls it for a supplied controller; an unset name is assigned, a
1210
+ * different one throws (the controller would run another resource's hooks).
1211
+ */
1212
+ bindResourceName(name: string): void;
1213
+ /**
1214
+ * Swap the controller's query parser. Mutates the existing `QueryResolver` in
1215
+ * place, so the resolver stays referentially stable. `defineResource` calls this
1216
+ * (duck-typed) when a resource supplies both `controller` and `queryParser`.
1227
1217
  */
1228
1218
  setQueryParser(queryParser: QueryParserInterface): void;
1229
1219
  /**
1230
- * Apply resource-level options to a custom controller AFTER construction.
1231
- *
1232
- * Closes the pre-2.15 footgun where `defineResource({ controller, tenantField,
1233
- * schemaOptions, ... })` warned that the options were "dropped" because the
1234
- * user-supplied controller never received them. Hosts had to remember to
1235
- * forward each one through `super(repo, { ... })` — easy to miss, silently
1236
- * mis-scopes queries when missed.
1237
- *
1238
- * `defineResource()` now calls `controller.configure(resolvedOpts)` after
1239
- * `resolveOrAutoCreateController()` runs. Configure-aware controllers receive
1240
- * the resolved values; arc skips the dropped-options warn for them.
1241
- *
1242
- * Only the keys that affect cross-cutting state (tenant scope, schema/field
1243
- * rules, sort/limit policy, cache, write-denial policy) are honoured —
1244
- * `repository` / `resourceName` are constructor-only because they participate
1245
- * in mixin composition. Each known key rebuilds the affected sub-component
1246
- * (AccessControl / BodySanitizer / QueryResolver) so referentially-stable
1247
- * consumers don't see stale state.
1220
+ * Apply resource-level options to a controller AFTER construction.
1221
+ * `defineResource()` calls this once after resolving the controller, so custom
1222
+ * controllers receive tenant scope, field rules, sort/limit, cache, and
1223
+ * write-denial policy without forwarding them through `super(repo, { … })`.
1248
1224
  *
1249
- * Idempotent: safe to call zero, one, or many times before first request;
1250
- * arc calls it exactly once.
1251
- *
1252
- * Type narrowed to `ControllerConfigurableOptions` — `resourceName` is
1253
- * construction-only and intentionally excluded so accidental "rename
1254
- * the resource at runtime" calls fail to typecheck.
1225
+ * Each key rebuilds only the affected sub-component. Idempotent.
1226
+ * `repository` / `resourceName` are construction-only and excluded by type.
1255
1227
  */
1256
1228
  configure(options: ControllerConfigurableOptions): void;
1257
- /**
1258
- * Get the tenant field name if multi-tenant scoping is enabled.
1259
- * Returns `undefined` when `tenantField` is `false`.
1260
- */
1229
+ /** Tenant field name, or `undefined` when `tenantField` is `false`. */
1261
1230
  protected getTenantField(): string | undefined;
1262
1231
  /**
1263
- * Build the canonical repo-options bag (tenant scope, audit
1264
- * attribution, trace correlation) from the Fastify request. Full
1265
- * contract + rationale on `buildTenantRepoOptions` in
1266
- * `crud/requestPipeline.ts` — this delegate exists so host overrides
1267
- * and duck-typed consumers (aggregation router, MCP bridge) keep the
1268
- * same `protected` entry point.
1269
- *
1270
- * Method kept named `tenantRepoOptions` for back-compat with hosts
1271
- * that spread `...this.tenantRepoOptions(req)` (10+ call sites in
1272
- * arc, plus host overrides). The bag has always grown over time —
1273
- * hosts that don't want audit forwarding never read those keys.
1232
+ * Canonical repo-options bag (tenant scope, audit attribution, trace
1233
+ * correlation) — see `buildTenantRepoOptions` in `crud/requestPipeline.ts`.
1234
+ * Kept as a `protected` entry point for host overrides and duck-typed
1235
+ * consumers (aggregation router, MCP bridge).
1274
1236
  */
1275
1237
  protected tenantRepoOptions(req: IRequestContext): AnyRecord;
1276
1238
  /** Extract typed Arc internal metadata from request */
@@ -1285,18 +1247,11 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1285
1247
  */
1286
1248
  protected resolveRepoId(id: string, existing: AnyRecord | null): string;
1287
1249
  /**
1288
- * Read-side preflight for mutable-target operations (`update`, `delete`).
1289
- *
1290
- * Bundles the four steps that every mutation must do before touching the
1291
- * repo: (1) extract `:id`, (2) fetch under access control + tenant scope,
1292
- * (3) verify ownership, (4) translate the route id to the repo's primary
1293
- * key. Returning `{id, existing, repoId}` keeps the call sites a single
1294
- * line and makes drift between `update` and `delete` structurally
1295
- * impossible — there is one preflight, one denial-reason mapping, one
1296
- * ownership check.
1250
+ * Shared preflight for mutations: extract `:id`, fetch under access control +
1251
+ * tenant scope, verify ownership, translate the route id to the repo primary
1252
+ * key. One path so `update` and `delete` cannot drift.
1297
1253
  *
1298
- * Pass `extraFetchOptions` for callers (e.g. soft-delete restore) that
1299
- * need to widen the fetch (`{ includeDeleted: true }`).
1254
+ * `extraFetchOptions` widens the fetch (e.g. restore's `{ includeDeleted: true }`).
1300
1255
  */
1301
1256
  protected loadMutableTarget(req: IRequestContext, extraFetchOptions?: AnyRecord): Promise<{
1302
1257
  id: string;
@@ -1304,18 +1259,45 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1304
1259
  repoId: string;
1305
1260
  }>;
1306
1261
  /**
1307
- * Centralized 404 thrower. Maps the denial reason from `fetchDetailed()`
1308
- * into a `NotFoundError` so consumers can distinguish "doc doesn't
1309
- * exist" from "doc filtered by policy/org scope" via the error
1310
- * `details.code` set by the global error handler.
1262
+ * Whether the target of an id-bound ACTION is a row this request may read — the same
1263
+ * compound filter (id + tenant + `_policyFilters`) `GET /:id` uses. The action router
1264
+ * refuses the action with a 404 when it is not, so a caller can act on a row iff it can
1265
+ * read it. Skips the read when neither tenancy nor a row policy narrows the request.
1266
+ */
1267
+ private buildLookupAccess;
1268
+ /**
1269
+ * THE single-row lookup — `GET /:id` and the action-target fence both read through it.
1270
+ * The primary key first; when it finds nothing (or the id cannot be that key's type), each
1271
+ * `lookupFields` key in turn, under the same tenant and row policy.
1272
+ */
1273
+ protected fetchByAnyKey(id: string, req: IRequestContext, options: QueryOptions | undefined): Promise<{
1274
+ doc: TDoc | null;
1275
+ reason: FetchDenialReason | null;
1276
+ }>;
1277
+ /** Whether {@link isActionTargetVisible} can answer — it reads through the repository. */
1278
+ canFenceActionTargets(): boolean;
1279
+ isActionTargetVisible(req: IRequestContext, id: string): Promise<boolean>;
1280
+ /** `ETag: "<version>"` for a versioned document under optimistic concurrency. */
1281
+ protected etagHeaders(doc: unknown): Record<string, string> | undefined;
1282
+ /**
1283
+ * The version an update must apply at. `If-Match` must name the CURRENT version or the
1284
+ * request fails 412 before anything is written; without it (or with `*`) the write is a CAS
1285
+ * on the version just loaded, so a concurrent write in between is a 409, never overwritten.
1286
+ */
1287
+ protected expectedVersion(req: IRequestContext, existing: unknown): {
1288
+ version?: number;
1289
+ fromClient: boolean;
1290
+ };
1291
+ /**
1292
+ * 404 thrower. Maps the `fetchDetailed()` denial reason into `details.code` so
1293
+ * "doesn't exist" is distinguishable from "filtered by policy/org scope".
1311
1294
  */
1312
1295
  protected throwNotFound(reason?: FetchDenialReason | null): never;
1313
1296
  /** Resolve cache config for a specific operation, merging per-op overrides */
1314
1297
  protected resolveCacheConfig(operation: "list" | "byId"): QueryCacheConfig | null;
1315
1298
  /**
1316
- * Extract user/org IDs from request for cache key scoping.
1317
- * Only includes orgId when the resource uses tenant-scoped data (tenantField is set).
1318
- * Universal resources (tenantField: false) get shared cache keys.
1299
+ * User/org IDs for cache key scoping. orgId is included only when `tenantField`
1300
+ * is set; universal resources share cache keys.
1319
1301
  */
1320
1302
  protected cacheScope(req: IRequestContext): {
1321
1303
  userId?: string;
@@ -1325,20 +1307,13 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1325
1307
  protected cacheResponse<T>(data: T, cacheStatus: CacheStatus): IControllerResponse<T>;
1326
1308
  /** Required route-id helper shared by get/update/delete. Throws on missing id. */
1327
1309
  protected requireIdParam(req: IRequestContext): string;
1328
- /**
1329
- * Normalizes `repo.exists()` return shapes across adapters. Per
1330
- * StandardRepo's contract, `exists` may return `boolean`, `{ _id }`,
1331
- * or `null` — every truthy non-null shape collapses to `true`.
1332
- */
1310
+ /** Normalizes `repo.exists()` (`boolean | { _id } | null`) to a boolean. */
1333
1311
  protected isExistsTruthy(result: unknown): boolean;
1334
1312
  /** Assemble the per-request hook context from the protected accessors. */
1335
1313
  private hookOpContext;
1336
1314
  /**
1337
- * Run `executeBefore` then `executeAround` (or just the executor if no
1338
- * hooks are wired). Returns the around-phase result directly. Throws an
1339
- * `ArcError` (status 400, code `BEFORE_<OP>_HOOK_ERROR`) when the
1340
- * before-hook fails. Full contract on `executeHookedOp` in
1341
- * `crud/requestPipeline.ts`.
1315
+ * Run before + around hooks (or just the executor if none are wired) and
1316
+ * return the result. A failing before-hook throws 400 `BEFORE_<OP>_HOOK_ERROR`.
1342
1317
  */
1343
1318
  protected runHookedOpUntilResult<TInput, TResult>(req: IRequestContext, args: {
1344
1319
  op: "create" | "update" | "delete";
@@ -1348,14 +1323,10 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1348
1323
  }, executor: (processed: TInput) => Promise<TResult>): Promise<TResult>;
1349
1324
  /**
1350
1325
  * Run a write's PERSISTENCE step, transactionally when the resource asked.
1351
- *
1352
- * Plain mode hands `fn` the live repository. Transactional mode runs it
1353
- * through repo-core's `retryingTransaction`: the callback receives the
1354
- * TX-BOUND repository (so declared verbs and repository calls join the same
1355
- * transaction), transient conflicts re-run `fn` with jittered bounded
1356
- * backoff, everything else — including `VersionConflictError` — surfaces
1357
- * once. Hooks stay OUTSIDE: before/around ran already and must not re-run;
1358
- * after-hooks run post-commit, which they already do by running after this.
1326
+ * Transactional mode uses `retryingTransaction`: `fn` gets the TX-BOUND repo and
1327
+ * re-runs on transient conflicts; anything else (incl. `VersionConflictError`)
1328
+ * surfaces once. Hooks stay OUTSIDE so before/around never re-run and after
1329
+ * runs post-commit.
1359
1330
  */
1360
1331
  protected runWritePersistence<T>(fn: (repo: TRepository, uow?: TransactionHandle) => Promise<T>): Promise<T>;
1361
1332
  /** Assemble the `WriteContext` a `create` verb receives. */
@@ -1363,43 +1334,26 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1363
1334
  /**
1364
1335
  * Assemble the `MutationWriteContext` an `update` / `delete` verb receives.
1365
1336
  *
1366
- * `existing` is the document arc ALREADY loaded to run its permission and
1367
- * tenant checks — `loadMutableTarget` throws before any verb runs when it
1368
- * cannot produce one, which is why the context types it required. Handing
1369
- * it over rather than letting the verb re-fetch is deliberate: a second
1370
- * read can observe a different document than the one the request was
1371
- * authorised against.
1372
- *
1373
- * `id` is the REPOSITORY PRIMARY KEY (`repoId`), never the raw route param —
1374
- * see the note on `MutationWriteContext.id`. Callers pass `resolveRepoId`'s
1375
- * output.
1337
+ * `existing` is the document the request was AUTHORISED against — handed over
1338
+ * so the verb does not re-fetch and possibly observe a different document.
1339
+ * `id` is the REPOSITORY PRIMARY KEY (`resolveRepoId` output), never the route param.
1376
1340
  */
1377
1341
  protected mutationWriteContext(req: IRequestContext, id: string, existing: unknown, repo?: TRepository, uow?: TransactionHandle): MutationWriteContext;
1378
1342
  /**
1379
- * Run `executeAfter` for the given op + data. No-op when hooks aren't
1380
- * wired or `resourceName` isn't set. Caller passes the data shape it
1381
- * wants downstream after-handlers to receive — typically the result for
1382
- * create/update, the original input (`existing`) for delete.
1343
+ * Run after-hooks for `op`. No-op without hooks or `resourceName`. `data` is the
1344
+ * result for create/update, `existing` for delete.
1383
1345
  */
1384
1346
  protected runAfterHook(req: IRequestContext, op: "create" | "update" | "delete" | "list" | "read", data: AnyRecord, meta?: Record<string, unknown>): Promise<void>;
1385
1347
  /**
1386
- * Per-instance single-flight for cache fills — concurrent requests for
1387
- * the SAME cache key coalesce onto one repository query instead of each
1388
- * issuing its own (a popular entry expiring under 1,000 concurrent
1389
- * readers must produce 1 DB query, not 1,000). Keys already encode the
1390
- * resolved query + user/org scope + resource version, so coalescing can
1391
- * never cross tenants, scopes, or invalidation generations. Per-process
1392
- * by design; distributed refresh leases are a store-level concern
1393
- * (planned with the repo-core CacheEngine façade, which ships
1394
- * single-flight natively).
1348
+ * Per-process single-flight for cache fills: concurrent requests for the SAME
1349
+ * key coalesce onto one repository query. Keys encode query + user/org scope +
1350
+ * resource version, so coalescing never crosses tenants or invalidation generations.
1395
1351
  */
1396
1352
  private readonly inFlightCacheFills;
1397
1353
  private cacheSingleFlight;
1398
1354
  /**
1399
- * Register a background SWR refresh for `key` unless one is already in
1400
- * flight. Registration is SYNCHRONOUS (before the deferred tick) so a
1401
- * burst of stale readers arriving in the same tick schedules exactly one
1402
- * revalidation.
1355
+ * Register a background SWR refresh for `key` unless one is in flight.
1356
+ * Registration is SYNCHRONOUS so a same-tick burst of stale readers schedules one.
1403
1357
  */
1404
1358
  private scheduleCacheRefresh;
1405
1359
  /** Cached `list()` flow with SWR semantics. Returns null when cache is disabled. */
@@ -1408,19 +1362,14 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1408
1362
  protected withGetCache(req: IRequestContext, id: string, options: ControllerQueryOptions): Promise<IControllerResponse<TDoc> | null>;
1409
1363
  list(req: IRequestContext): Promise<IControllerResponse<ListResult<TDoc>>>;
1410
1364
  /**
1411
- * Resource-dispatch verbs router. Returns `null` when the request is
1412
- * a regular list query, otherwise returns the dispatch promise.
1413
- *
1414
- * Verbs (mutually exclusive — first match wins):
1365
+ * Resource-dispatch verbs router; `null` for a regular list query.
1366
+ * First match wins:
1415
1367
  * - `?_count=true` → `{ count: number }` via `repo.count()`
1416
1368
  * - `?_distinct=field` → `unknown[]` via `repo.distinct(field)`
1417
1369
  * - `?_exists=true` → `{ exists: boolean }` via `repo.exists()`
1418
1370
  *
1419
- * All verbs share the resolved filter (parsed query + policy filters
1420
- * + tenant scope). Adapters that don't ship the underlying repo
1421
- * method get a `501` so failures surface loudly instead of falling
1422
- * back to a full table scan. Matching + repo invocation live in
1423
- * `crud/resourceVerbs.ts`.
1371
+ * All share the resolved filter (query + policy + tenant scope). A missing repo
1372
+ * method is a `501`, never a fallback to a full scan.
1424
1373
  */
1425
1374
  protected dispatchResourceVerb(req: IRequestContext): Promise<IControllerResponse<unknown>> | null;
1426
1375
  /** Resolve filter + tenant/audit options for a dispatch verb. */
@@ -1438,13 +1387,8 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1438
1387
  exists: boolean;
1439
1388
  }>>;
1440
1389
  /**
1441
- * True when `field` is safe to expose via `_distinct`.
1442
- *
1443
- * Read-side gate only — only `hidden: true` blocks. `systemManaged`
1444
- * is a *write* rule (clients can't PATCH the value); the field is
1445
- * still in every list response, so blocking `_distinct` adds nothing
1446
- * but inconvenience. See `core/fieldRulePredicates.ts` for the
1447
- * canonical predicate shared with `QueryResolver`.
1390
+ * True when `field` is safe to expose via `_distinct`. Only `hidden: true`
1391
+ * blocks — `systemManaged` is a write rule and the field is already readable.
1448
1392
  */
1449
1393
  protected isFieldExposedForRead(field: string): boolean;
1450
1394
  /** Execute list query through hooks (extracted for cache revalidation) */
@@ -1467,7 +1411,7 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
1467
1411
  //#region src/core/mixins/bulk.d.ts
1468
1412
  /** Public surface contributed by BulkMixin. */
1469
1413
  interface BulkExt {
1470
- bulkCreate(req: IRequestContext): Promise<IControllerResponse<AnyRecord[]>>;
1414
+ bulkCreate(req: IRequestContext): Promise<IControllerResponse<object[]>>;
1471
1415
  bulkUpdate(req: IRequestContext): Promise<IControllerResponse<{
1472
1416
  matchedCount: number;
1473
1417
  modifiedCount: number;
@@ -1476,69 +1420,42 @@ interface BulkExt {
1476
1420
  deletedCount: number;
1477
1421
  }>>;
1478
1422
  }
1479
- declare function BulkMixin<TBase extends MixinConstructor<BaseCrudController>>(Base: TBase): TBase & MixinConstructor<BulkExt>;
1423
+ declare function BulkMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<BulkExt>;
1480
1424
  //#endregion
1481
1425
  //#region src/core/mixins/slug.d.ts
1482
1426
  /** Public surface contributed by SlugMixin. */
1483
1427
  interface SlugExt {
1484
- getBySlug(req: IRequestContext): Promise<IControllerResponse<AnyRecord>>;
1428
+ getBySlug(req: IRequestContext): Promise<IControllerResponse<object>>;
1485
1429
  }
1486
- declare function SlugMixin<TBase extends MixinConstructor<BaseCrudController>>(Base: TBase): TBase & MixinConstructor<SlugExt>;
1430
+ declare function SlugMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<SlugExt>;
1487
1431
  //#endregion
1488
1432
  //#region src/core/mixins/tree.d.ts
1489
1433
  /** Public surface contributed by TreeMixin. */
1490
1434
  interface TreeExt {
1491
- getTree(req: IRequestContext): Promise<IControllerResponse<AnyRecord[]>>;
1492
- getChildren(req: IRequestContext): Promise<IControllerResponse<AnyRecord[]>>;
1435
+ getTree(req: IRequestContext): Promise<IControllerResponse<object[]>>;
1436
+ getChildren(req: IRequestContext): Promise<IControllerResponse<object[]>>;
1493
1437
  }
1494
- declare function TreeMixin<TBase extends MixinConstructor<BaseCrudController>>(Base: TBase): TBase & MixinConstructor<TreeExt>;
1438
+ declare function TreeMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<TreeExt>;
1495
1439
  //#endregion
1496
1440
  //#region src/core/mixins/softDelete.d.ts
1497
1441
  /** Public surface contributed by SoftDeleteMixin. */
1498
1442
  interface SoftDeleteExt {
1499
- getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginationResult<AnyRecord>>>;
1500
- restore(req: IRequestContext): Promise<IControllerResponse<AnyRecord>>;
1443
+ getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginationResult<object>>>;
1444
+ restore(req: IRequestContext): Promise<IControllerResponse<object>>;
1501
1445
  }
1502
- declare function SoftDeleteMixin<TBase extends MixinConstructor<BaseCrudController>>(Base: TBase): TBase & MixinConstructor<SoftDeleteExt>;
1446
+ declare function SoftDeleteMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<SoftDeleteExt>;
1503
1447
  //#endregion
1504
1448
  //#region src/core/BaseController.d.ts
1505
- /**
1506
- * The `Arc*Result` types above stay: `src/index.ts` re-exports them from THIS
1507
- * path. `BaseCrudController` and the four mixins (+ their `*Ext` types) were
1508
- * also re-exported here and are not, as of 2.37.1 — `core/index.ts` takes them
1509
- * straight from `./BaseCrudController.js` and `./mixins/*.js`, so this file's
1510
- * copies had no importer. Two public paths to one symbol is a drift seam, not
1511
- * convenience: composition belongs to `core/index.ts`, and this module is the
1512
- * composed `BaseController` itself.
1513
- */
1514
1449
  /**
1515
1450
  * Fully-composed controller shape: all CRUD methods + every preset method
1516
1451
  * (SoftDelete / Tree / Slug / Bulk) typed over the caller-supplied `TDoc`.
1517
1452
  *
1518
- * Composes `BaseCrudController` (list/get/create/update/delete) → `BulkMixin`
1519
- * (bulk*) → `SlugMixin` (getBySlug) → `TreeMixin` (getTree/getChildren) →
1520
- * `SoftDeleteMixin` (getDeleted/restore). CRUD-only hosts extend
1521
- * `BaseCrudController` for a smaller surface; others compose mixins by hand.
1522
- *
1523
- * DECLARATION MERGING, not a generic mixin chain: TypeScript cannot thread one
1524
- * `TDoc` through four chained mixin factories without losing the
1525
- * `extends Constructor<Base>` constraint. So the runtime pins the chain base to
1526
- * `AnyRecord` while the TYPE surface threads `TDoc`, and merging bridges them.
1527
- *
1528
- * `TDoc extends AnyRecord` is LOAD-BEARING: inherited methods return
1529
- * `ListResult<AnyRecord>` and the merged interface returns `ListResult<TDoc>`,
1530
- * so TS's derived-assignable-to-base check needs the bound. Without it the
1531
- * class declaration fails with `Type 'TDoc[]' is not assignable to type
1532
- * 'AnyRecord[]'`. Hosts pick one of:
1533
- * (a) drop the generic — lose return narrowing;
1534
- * (b) `interface IUser extends AnyRecord` — preferred when you own the type;
1535
- * (c) `ArcListResult<typeof this>` etc. when overriding — reads the return
1536
- * type off the class, so the bound never applies.
1537
- *
1538
- * COST: every `TDoc`-narrowed method is redeclared on the interface, so a new
1539
- * mixin method must be added here too.
1453
+ * DECLARATION MERGING, not a generic mixin chain: chained mixin factories cannot thread one
1454
+ * `TDoc`, so the runtime chain is instantiated over `object` and the TYPE surface threads `TDoc`.
1455
+ * Any `TDoc extends object` works — an interface-typed document needs no index signature.
1456
+ * A new mixin method must be redeclared here too.
1540
1457
  */
1541
- interface BaseController<TDoc extends AnyRecord = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> {
1458
+ interface BaseController<TDoc extends object = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> {
1542
1459
  accessControl: AccessControl;
1543
1460
  bodySanitizer: BodySanitizer;
1544
1461
  queryResolver: QueryResolver;
@@ -1567,14 +1484,14 @@ interface BaseController<TDoc extends AnyRecord = AnyRecord, _TRepository extend
1567
1484
  deletedCount: number;
1568
1485
  }>>;
1569
1486
  }
1570
- declare const BaseController_base: (new (repository: any, options?: BaseControllerOptions) => BaseCrudController<AnyRecord, any>) & MixinConstructor<BulkExt> & MixinConstructor<SlugExt> & MixinConstructor<TreeExt> & MixinConstructor<SoftDeleteExt>;
1487
+ declare const BaseController_base: {
1488
+ new (repository: RepositoryLike, options?: BaseControllerOptions): BaseCrudController<object, RepositoryLike>;
1489
+ } & MixinConstructor<BulkExt> & MixinConstructor<SlugExt> & MixinConstructor<TreeExt> & MixinConstructor<SoftDeleteExt>;
1571
1490
  /**
1572
1491
  * Fully-composed controller: `BaseCrudController` + SoftDelete + Tree +
1573
- * Slug + Bulk. Drop-in replacement for the pre-2.11 god class. The
1574
- * companion interface above gives every method full generic precision
1575
- * on `TDoc` via declaration merging.
1492
+ * Slug + Bulk. The companion interface above types every method over `TDoc`.
1576
1493
  */
1577
- declare class BaseController<TDoc extends AnyRecord = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> extends BaseController_base {
1494
+ declare class BaseController<TDoc extends object = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> extends BaseController_base {
1578
1495
  readonly _phantom?: [TDoc, _TRepository];
1579
1496
  }
1580
1497
  //#endregion
@@ -1924,6 +1841,9 @@ interface RouteDefinition {
1924
1841
  * Accepts a controller-method NAME as well, same as {@link handler} — the
1925
1842
  * lookup is identical and only the calling convention differs, so a
1926
1843
  * Fastify-native controller method stays reachable by name.
1844
+ *
1845
+ * An async raw handler that calls `reply.send()` must `return reply` (as `handleRaw`
1846
+ * does): resolving `undefined` while an async `preSerialization` hook runs sends an empty body.
1927
1847
  */
1928
1848
  readonly rawHandler?: string | RawRouteHandler;
1929
1849
  /** Logical operation name (pipeline keys, MCP tool naming). */
@@ -1941,8 +1861,8 @@ interface RouteDefinition {
1941
1861
  /**
1942
1862
  * Opt this custom route into the multiTenant preset's tenant scoping.
1943
1863
  *
1944
- * When `true` AND the resource declares `multiTenantPreset` (or
1945
- * `flexibleMultiTenantPreset`), arc prepends the preset's tenant
1864
+ * When `true` AND the resource declares `multiTenantPreset`, arc
1865
+ * prepends the preset's tenant
1946
1866
  * filter + injection middleware to this route's chain — the same
1947
1867
  * pair `update` gets. The handler can then read the resolved tenant
1948
1868
  * via `getOrgId(req.scope)` / `req._tenantFields` (or, for write
@@ -2139,6 +2059,28 @@ interface ActionDefinition {
2139
2059
  * resource root and drops `id` from the MCP tool's input shape.
2140
2060
  */
2141
2061
  readonly id?: boolean;
2062
+ /**
2063
+ * Where an id-bound action's TARGET is authorized. Default: arc loads it through the
2064
+ * resource's access control (id + tenant + row policy, as `GET /:id` does) and answers
2065
+ * 404 when the caller could not read it. `{ scope: 'handler', reason }` hands that check
2066
+ * to the handler — for a target that is deliberately not a row of this resource's tenant.
2067
+ */
2068
+ readonly target?: {
2069
+ readonly scope: "handler";
2070
+ readonly reason: string;
2071
+ };
2072
+ /**
2073
+ * `{ required: true }`: a call of THIS action without an idempotency key is refused (400
2074
+ * `arc.idempotency_key_required`) and a keyed one is deduplicated; other actions on the
2075
+ * same mount stay optional. Needs `idempotencyPlugin` enabled.
2076
+ */
2077
+ readonly idempotency?: {
2078
+ readonly required: boolean;
2079
+ /** `'handler'`: the handler deduplicates; arc only demands and exposes the key. Needs `owner`. */
2080
+ readonly dedup?: "handler";
2081
+ /** The runner that owns at-most-once under `dedup: 'handler'`. */
2082
+ readonly owner?: string;
2083
+ };
2142
2084
  /**
2143
2085
  * MCP tool generation:
2144
2086
  * - omitted/true: auto-generate
@@ -2817,6 +2759,16 @@ interface FieldRule$1 {
2817
2759
  }
2818
2760
  //#endregion
2819
2761
  //#region src/types/resource/schemas.d.ts
2762
+ /**
2763
+ * One client-requestable join. The base collection's tenant filter never reaches a joined
2764
+ * collection, so its scope is REQUIRED and is always the server's — a client `where` is discarded.
2765
+ */
2766
+ interface LookupPolicy {
2767
+ /** Filter on the JOINED rows (tenant, soft-delete, visibility). `{}` only for a genuinely global collection. */
2768
+ scope: (ctx: {
2769
+ scope: RequestScope | undefined;
2770
+ }) => Record<string, unknown>;
2771
+ }
2820
2772
  /**
2821
2773
  * Schema-shaping options for a resource.
2822
2774
  *
@@ -2913,11 +2865,10 @@ interface RouteSchemaOptions extends SchemaBuilderOptions {
2913
2865
  */
2914
2866
  allowedPopulate?: string[];
2915
2867
  /**
2916
- * Lookup-collection whitelist for `QueryResolver.sanitizeLookups`.
2917
- * When set, only the listed collections may be `$lookup`'d. Omit to
2918
- * disable the whitelist (kit-level rules still apply).
2868
+ * Client-requestable joins, keyed by collection, each with the SERVER-owned scope of its
2869
+ * joined side. Omit to deny every client lookup (the default).
2919
2870
  */
2920
- allowedLookups?: string[];
2871
+ allowedLookups?: Readonly<Record<string, LookupPolicy>>;
2921
2872
  };
2922
2873
  }
2923
2874
  /**
@@ -3070,6 +3021,14 @@ interface ResolvedTenantPurge {
3070
3021
  }
3071
3022
  //#endregion
3072
3023
  //#region src/types/resource/config.d.ts
3024
+ /**
3025
+ * Built-in write routes whose retry must not execute twice: each listed op answers a
3026
+ * keyless call with 400 `arc.idempotency_key_required` and dedupes a keyed one. Needs
3027
+ * `idempotencyPlugin` enabled; boot refuses an op that is not mounted.
3028
+ */
3029
+ interface ResourceIdempotencyConfig {
3030
+ readonly required: readonly ("create" | "update" | "delete")[];
3031
+ }
3073
3032
  /** Standard controller type alias for CRUD operations. */
3074
3033
  type CrudController<TDoc> = IController<TDoc>;
3075
3034
  /** Resource-level permissions — only `PermissionCheck` functions allowed. */
@@ -3140,6 +3099,16 @@ interface ResourceConfig<TDoc = AnyRecord> {
3140
3099
  * that silently degrades is the defect, not a mode.
3141
3100
  */
3142
3101
  transactional?: boolean;
3102
+ /**
3103
+ * Optimistic concurrency over HTTP (RFC 9110). Reads send `ETag: "<version>"`; an update
3104
+ * with `If-Match` applies only at that version (else 412), and EVERY update is a CAS write on
3105
+ * the version it loaded (else 409) — so the version, and the ETag, change on every write.
3106
+ * `versionField` defaults to `'__v'`. Boot-fatal on a repository without the
3107
+ * `optimisticConcurrency` capability.
3108
+ */
3109
+ concurrency?: {
3110
+ versionField?: string;
3111
+ };
3143
3112
  queryParser?: unknown;
3144
3113
  permissions?: ResourcePermissions;
3145
3114
  schemaOptions?: RouteSchemaOptions;
@@ -3382,6 +3351,11 @@ interface ResourceConfig<TDoc = AnyRecord> {
3382
3351
  * ```
3383
3352
  */
3384
3353
  idField?: (keyof TDoc & string) | (string & {});
3354
+ /**
3355
+ * Further unique keys `GET /:id` and an action's `:id` accept, tried in order after `idField`
3356
+ * — a business number beside `_id`. Same tenant and row-policy scope as the primary lookup.
3357
+ */
3358
+ lookupFields?: readonly ((keyof TDoc & string) | (string & {}))[];
3385
3359
  /** For grouping in registry */
3386
3360
  module?: string;
3387
3361
  /** Domain events */
@@ -3513,6 +3487,8 @@ interface ResourceConfig<TDoc = AnyRecord> {
3513
3487
  * registered. Set to `false` to disable for this resource.
3514
3488
  */
3515
3489
  rateLimit?: RateLimitConfig | false;
3490
+ /** Idempotency-key fence for the built-in write routes. See `ResourceIdempotencyConfig`. */
3491
+ idempotency?: ResourceIdempotencyConfig;
3516
3492
  /**
3517
3493
  * QueryCache configuration for this resource. Enables
3518
3494
  * stale-while-revalidate and auto-invalidation. Requires
@@ -3656,7 +3632,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
3656
3632
  readonly permissions: ResourcePermissions;
3657
3633
  readonly routes: readonly RouteDefinition[];
3658
3634
  readonly middlewares: MiddlewareConfig;
3659
- readonly routeGuards?: RouteHandlerMethod$1[];
3635
+ readonly routeGuards?: RouteHandlerMethod[];
3660
3636
  readonly disableDefaultRoutes: boolean;
3661
3637
  readonly disabledRoutes: readonly CrudRouteKey[];
3662
3638
  readonly actions?: ActionsMap;
@@ -3664,6 +3640,8 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
3664
3640
  readonly aggregations?: AggregationsMap;
3665
3641
  readonly events: Record<string, EventDefinition>;
3666
3642
  readonly rateLimit?: RateLimitConfig | false;
3643
+ readonly idempotency?: ResourceIdempotencyConfig;
3644
+ readonly lookupFields?: readonly string[];
3667
3645
  readonly audit?: boolean | {
3668
3646
  operations?: ("create" | "update" | "delete")[];
3669
3647
  };
@@ -3699,6 +3677,12 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
3699
3677
  */
3700
3678
  readonly resolvedTenantPurge: ResolvedTenantPurge;
3701
3679
  readonly idField?: string;
3680
+ /**
3681
+ * The optimistic-concurrency version field, resolved (`concurrency: {}` → `'__v'`);
3682
+ * `undefined` when the resource did not opt in. Published in the resource manifest
3683
+ * so a client sends `If-Match` without repeating the field name.
3684
+ */
3685
+ readonly versionField?: string;
3702
3686
  readonly queryParser?: QueryParserInterface;
3703
3687
  readonly _appliedPresets: string[];
3704
3688
  _pendingHooks: Array<{
@@ -3800,7 +3784,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
3800
3784
  //#region src/core/defineResource.d.ts
3801
3785
  /**
3802
3786
  * `TDoc` is **unconstrained** at this layer. The previous `TDoc
3803
- * extends AnyRecord` bound leaked out of `BaseController`'s
3787
+ * extends object` bound leaked out of `BaseController`'s
3804
3788
  * mixin-composition requirement into every host's adapter boundary:
3805
3789
  * Mongoose's `HydratedDocument<T>`, Prisma's generated row types,
3806
3790
  * and any domain interface without an explicit index signature all
@@ -3811,7 +3795,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
3811
3795
  * known envelope fields.
3812
3796
  *
3813
3797
  * The cast moved inside `resolveOrAutoCreateController` where
3814
- * `BaseController<TDoc extends AnyRecord>` actually requires it.
3798
+ * `BaseController<TDoc extends object>` actually requires it.
3815
3799
  * One internal boundary cast replaces N host-side casts.
3816
3800
  */
3817
3801
  /**
@@ -3864,8 +3848,19 @@ declare class ResourceRegistry {
3864
3848
  * runtime-only handle is still reachable.
3865
3849
  */
3866
3850
  private _adapters;
3851
+ /** The mounted definitions, in registration order — see `getDefinitions`. */
3852
+ private _definitions;
3867
3853
  private _frozen;
3868
3854
  constructor();
3855
+ /**
3856
+ * The live `ResourceDefinition`s that mounted, in registration order — what a host hands to
3857
+ * `mcpPlugin({ resources })` or a docs generator instead of re-collecting them. A resource
3858
+ * declaring `skipRegistry` (incl. `customRoutesOnly`) opted out and is absent. Fresh array per call.
3859
+ * One composed module's share: `getModuleResources(fastify, name)` from `@classytic/arc/factory`.
3860
+ */
3861
+ getDefinitions(): ResourceDefinition<unknown>[];
3862
+ /** The live definition registered under `name`, or `undefined`. */
3863
+ getDefinition(name: string): ResourceDefinition<unknown> | undefined;
3869
3864
  /**
3870
3865
  * Get the live `DataAdapter` for a registered resource — the handle whose
3871
3866
  * `.repository` cascade / cleanup helpers actually call. Returns `undefined`
@@ -3874,8 +3869,13 @@ declare class ResourceRegistry {
3874
3869
  * consumers cast to their kit's adapter type at the use site.
3875
3870
  */
3876
3871
  getAdapter<T = unknown>(name: string): T | undefined;
3872
+ /** Set only by an explicit `strictResources: false`: a duplicate name keeps the FIRST entry. */
3873
+ private _tolerateDuplicates;
3874
+ /** Accept a duplicate resource name (the host opted out of `strictResources`; it was warned). */
3875
+ tolerateDuplicateNames(): void;
3877
3876
  /**
3878
- * Register a resource
3877
+ * Register a resource. A duplicate name throws — a resource missing from the registry still
3878
+ * serves traffic but vanishes from introspection, the permission matrix and MCP.
3879
3879
  */
3880
3880
  register(resource: ResourceDefinition<unknown>, options?: RegisterOptions): this;
3881
3881
  /**
@@ -3955,10 +3955,6 @@ declare class ResourceRegistry {
3955
3955
  * iterate over a defunct registry entry. Clear both halves together.
3956
3956
  */
3957
3957
  reset(): void;
3958
- /** @internal Alias for unfreeze() */
3959
- _unfreeze(): void;
3960
- /** @internal Alias for reset() */
3961
- _clear(): void;
3962
3958
  /**
3963
3959
  * Group by key
3964
3960
  */
@@ -4132,6 +4128,8 @@ interface CrudRouterOptions {
4132
4128
  * registered. Set to `false` to disable for this resource.
4133
4129
  */
4134
4130
  rateLimit?: RateLimitConfig | false;
4131
+ /** Idempotency-key fence for the built-in write routes. */
4132
+ idempotency?: ResourceIdempotencyConfig;
4135
4133
  /** PreHandler guards applied to every route (CRUD + custom + preset). */
4136
4134
  routeGuards?: RouteHandlerMethod[];
4137
4135
  /**
@@ -4167,6 +4165,8 @@ interface RequestContext {
4167
4165
  * them without `as AnyRecord` casts.
4168
4166
  */
4169
4167
  interface ArcInternalMetadata extends RequestContext {
4168
+ /** Trusted resolved tenant dimensions forwarded across transports. */
4169
+ _tenantFields?: Record<string, unknown>;
4170
4170
  /** Policy filters from permission middleware */
4171
4171
  _policyFilters?: Record<string, unknown>;
4172
4172
  /** Request scope from scope resolution */
@@ -4241,6 +4241,8 @@ interface LookupOption {
4241
4241
  single?: boolean;
4242
4242
  /** Field selection on the joined collection */
4243
4243
  select?: string | Record<string, 0 | 1>;
4244
+ /** Filter on the joined rows — set by arc from the resource's `LookupPolicy`, never by the client. */
4245
+ where?: Record<string, unknown>;
4244
4246
  }
4245
4247
  /**
4246
4248
  * Mongoose-compatible populate option for advanced field selection.
@@ -4375,6 +4377,12 @@ interface ResourceMetadata {
4375
4377
  capability?: string;
4376
4378
  raw?: boolean;
4377
4379
  schema?: unknown;
4380
+ /** The route's `config.idempotency` — `required: true` means a key is mandatory. */
4381
+ idempotency?: {
4382
+ required?: boolean;
4383
+ dedup?: "handler";
4384
+ owner?: string;
4385
+ };
4378
4386
  }>;
4379
4387
  routes: Array<{
4380
4388
  method: string;
@@ -4400,6 +4408,10 @@ interface RegistryEntry extends ResourceMetadata {
4400
4408
  * without re-reading the resource definition.
4401
4409
  */
4402
4410
  tenantField?: string | false;
4411
+ /** The resource's primary key field (`_id` when unset). */
4412
+ idField?: string;
4413
+ /** Resolved optimistic-concurrency version field — set only under `concurrency`. */
4414
+ versionField?: string;
4403
4415
  /**
4404
4416
  * Resolved tenant-purge strategy — what arc actually runs when
4405
4417
  * `cascadeDeleteForOrganization` fires for this resource. Computed
@@ -4436,6 +4448,8 @@ interface RegistryEntry extends ResourceMetadata {
4436
4448
  disabledRoutes?: string[];
4437
4449
  /** Rate limit config */
4438
4450
  rateLimit?: RateLimitConfig | false;
4451
+ /** Built-in write ops that require an idempotency key (`defineResource({ idempotency })`). */
4452
+ idempotency?: ResourceIdempotencyConfig;
4439
4453
  /** Per-resource audit opt-in flag (read by `auditPlugin` perResource mode) */
4440
4454
  audit?: boolean | {
4441
4455
  operations?: ("create" | "update" | "delete")[];
@@ -4461,6 +4475,12 @@ interface RegistryEntry extends ResourceMetadata {
4461
4475
  * reading the action definition itself.
4462
4476
  */
4463
4477
  readonly id?: boolean;
4478
+ /** `{ required: true }` — a call of this action requires an idempotency key. */
4479
+ readonly idempotency?: {
4480
+ readonly required: boolean;
4481
+ readonly dedup?: "handler";
4482
+ readonly owner?: string;
4483
+ };
4464
4484
  /** MCP tool generation flag — `false` to skip, object for overrides */
4465
4485
  readonly mcp?: boolean | {
4466
4486
  readonly description?: string;
@@ -4584,26 +4604,5 @@ type InferDocType<T> = T extends DataAdapter<infer D> ? D : T extends ResourceCo
4584
4604
  */
4585
4605
  type InferAdapterDoc<A> = A extends DataAdapter<infer D> ? D : unknown;
4586
4606
  type InferResourceDoc<T> = T extends ResourceConfig<infer D> ? D : never;
4587
- type TypedResourceConfig<TDoc> = ResourceConfig<TDoc>;
4588
- type TypedController<TDoc> = IController<TDoc>;
4589
- type TypedRepository<TDoc> = StandardRepo<TDoc>;
4590
- //#endregion
4591
- //#region src/types/validation.d.ts
4592
- /**
4593
- * Validation result types — produced by `validateResourceConfig` and
4594
- * consumed by `assertValidConfig` / `formatValidationErrors`.
4595
- */
4596
- interface ConfigError {
4597
- field: string;
4598
- message: string;
4599
- code?: string;
4600
- }
4601
- interface ValidationResult {
4602
- valid: boolean;
4603
- errors: ConfigError[];
4604
- }
4605
- interface ValidateOptions {
4606
- strict?: boolean;
4607
- }
4608
4607
  //#endregion
4609
- export { OpenApiSchemas as $, AccessControlConfig as $t, RequestIdOptions as A, WriteVerbKey as An, RouteDefinition as At, defineResource as B, BaseController as Bt, RequestContext as C, ControllerConstructionOptions as Cn, PipelineStep as Ct, HealthCheck as D, MutationWriteContext as Dn, ActionEntry as Dt, GracefulShutdownOptions as E, BodySanitizerConfig as En, ActionDefinition as Et, FastifyWithDecorators as F, IControllerResponse as Fn, AuthPluginOptions as Ft, OnTenantDeleteConfig as G, SlugExt as Gt, CrudController as H, SoftDeleteMixin as Ht, MiddlewareHandler as I, IRequestContext as In, Authenticator as It, PresetHook as J, BulkMixin as Jt, ResolvedTenantPurge as K, SlugMixin as Kt, RequestWithExtras as L, RawRouteHandler as Ln, AuthenticatorContext as Lt, EventsDecorator as M, ControllerLike as Mn, RouteMethod as Mt, FastifyRequestExtras as N, FastifyHandler as Nn, RateLimitConfig as Nt, HealthOptions as O, ResourceWrites as On, ActionHandlerFn as Ot, FastifyWithAuth as P, IController as Pn, AuthHelpers as Pt, MiddlewareConfig as Q, AccessControl as Qt, RegisterOptions as R, RouteHandler as Rn, JwtContext as Rt, QueryParserInterface as S, ControllerConfigurableOptions as Sn, PipelineContext as St, CrudRouterOptions as T, BodySanitizer as Tn, ResourceCacheConfig as Tt, ResourceConfig as U, TreeExt as Ut, ResourceDefinition as V, SoftDeleteExt as Vt, ResourcePermissions as W, TreeMixin as Wt, CrudRouteKey as X, QueryResolver as Xt, PresetResult as Y, BaseCrudController as Yt, CrudSchemas as Z, QueryResolverConfig as Zt, ControllerQueryOptions as _, ArcDeleteResult as _n, Guard as _t, InferAdapterDoc as a, HookRegistration as an, ResourceExtensions as at, ParsedQuery as b, ArcUpdateResult as bn, OperationFilter as bt, TypedController as c, afterCreate as cn, AggMeasureShorthand as ct, PaginationResult as d, beforeCreate as dn, AggregationDateRangeRequirement as dt, DefineHookOptions as en, RouteSchemaOptions as et, IntrospectionData as f, beforeDelete as fn, AggregationIndexHint as ft, ArcInternalMetadata as g, ArcCreateResult as gn, AggregationsMap as gt, ResourceMetadata as h, defineHook as hn, AggregationRateLimit as ht, ValidationResult as i, HookPhase as in, ResourceHooks as it, ArcDecorator as j, ControllerHandler as jn, RouteMcpConfig as jt, IntrospectionPluginOptions as k, WriteContext as kn, ActionsMap as kt, TypedRepository as l, afterDelete as ln, AggregationCacheConfig as lt, RegistryStats as m, createHookSystem as mn, AggregationMaterializedResult as mt, ConfigError as n, HookHandler as nn, FieldRule$1 as nt, InferDocType as o, HookSystem as on, EventDefinition as ot, RegistryEntry as p, beforeUpdate as pn, AggregationMaterializedContext as pt, PresetFunction as q, BulkExt as qt, ValidateOptions as r, HookOperation as rn, ResourceHookContext as rt, InferResourceDoc as s, HookSystemOptions as sn, AggMeasureInput as st, RouteHandlerMethod$1 as t, HookContext as tn, ArcFieldRule as tt, TypedResourceConfig as u, afterUpdate as un, AggregationConfig as ut, LookupOption as v, ArcGetResult as vn, Interceptor as vt, ServiceContext as w, ListResult as wn, Transform as wt, PopulateOption as x, BaseControllerOptions as xn, PipelineConfig as xt, OwnershipCheck as y, ArcListResult as yn, NextFunction as yt, ResourceRegistry as z, ServerAccessor as zn, TokenPair as zt };
4608
+ export { ResourceHookContext as $, AroundHookHandler as $t, RequestWithExtras as A, WriteContext as An, AuthHelpers as At, OnTenantDeleteConfig as B, ServerAccessor as Bn, TreeMixin as Bt, RequestIdOptions as C, ControllerConstructionOptions as Cn, ActionEntry as Ct, FastifyWithAuth as D, BodySanitizerConfig as Dn, RouteMcpConfig as Dt, FastifyRequestExtras as E, BodySanitizer as En, RouteDefinition as Et, ResourceDefinition as F, IController as Fn, TokenPair as Ft, CrudRouteKey as G, BaseCrudController as Gt, PresetFunction as H, SlugMixin as Ht, CrudController as I, IControllerResponse as In, BaseController as It, MiddlewareConfig as J, AccessControl as Jt, CrudSchemas as K, QueryResolver as Kt, ResourceConfig as L, IRequestContext as Ln, SoftDeleteExt as Lt, ResourceRegistry as M, ControllerHandler as Mn, Authenticator as Mt, RouteRow as N, ControllerLike as Nn, AuthenticatorContext as Nt, FastifyWithDecorators as O, MutationWriteContext as On, RouteMethod as Ot, defineResource as P, FastifyHandler as Pn, JwtContext as Pt, FieldRule$1 as Q, FetchResult as Qt, ResourceIdempotencyConfig as R, RawRouteHandler as Rn, SoftDeleteMixin as Rt, IntrospectionPluginOptions as S, ControllerConfigurableOptions as Sn, ActionDefinition as St, EventsDecorator as T, MixinConstructor as Tn, ActionsMap as Tt, PresetHook as U, BulkExt as Ut, ResolvedTenantPurge as V, SlugExt as Vt, PresetResult as W, BulkMixin as Wt, RouteSchemaOptions as X, AccessControlRepository as Xt, OpenApiSchemas as Y, AccessControlConfig as Yt, ArcFieldRule as Z, FetchDenialReason as Zt, ServiceContext as _, ArcDeleteResult as _n, PipelineConfig as _t, IntrospectionData as a, HookRegistration as an, AggregationCacheConfig as at, HealthCheck as b, ArcUpdateResult as bn, Transform as bt, ResourceMetadata as c, afterCreate as cn, AggregationIndexHint as ct, LookupOption as d, beforeCreate as dn, AggregationRateLimit as dt, DefineHookOptions as en, ResourceHooks as et, OwnershipCheck as f, beforeDelete as fn, AggregationsMap as ft, RequestContext as g, ArcCreateResult as gn, OperationFilter as gt, QueryParserInterface as h, defineHook as hn, NextFunction as ht, PaginationResult as i, HookPhase as in, AggMeasureShorthand as it, RegisterOptions as j, WriteVerbKey as jn, AuthPluginOptions as jt, MiddlewareHandler as k, ResourceWrites as kn, RateLimitConfig as kt, ArcInternalMetadata as l, afterDelete as ln, AggregationMaterializedContext as lt, PopulateOption as m, createHookSystem as mn, Interceptor as mt, InferDocType as n, HookHandler as nn, EventDefinition as nt, RegistryEntry as o, HookSystem as on, AggregationConfig as ot, ParsedQuery as p, beforeUpdate as pn, Guard as pt, LookupPolicy as q, QueryResolverConfig as qt, InferResourceDoc as r, HookOperation as rn, AggMeasureInput as rt, RegistryStats as s, HookSystemOptions as sn, AggregationDateRangeRequirement as st, InferAdapterDoc as t, HookContext as tn, ResourceExtensions as tt, ControllerQueryOptions as u, afterUpdate as un, AggregationMaterializedResult as ut, CrudRouterOptions as v, ArcGetResult as vn, PipelineContext as vt, ArcDecorator as w, ListResult as wn, ActionHandlerFn as wt, HealthOptions as x, BaseControllerOptions as xn, ResourceCacheConfig as xt, GracefulShutdownOptions as y, ArcListResult as yn, PipelineStep as yt, ResourcePermissions as z, RouteHandler as zn, TreeExt as zt };