@classytic/arc 2.30.0 → 2.32.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 (242) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -0
  3. package/TRADEMARK.md +22 -0
  4. package/dist/{BaseController-B5DwufbB.mjs → BaseController-Dk34dxhI.mjs} +21 -19
  5. package/dist/{EventTransport-QmY7A258.d.mts → EventTransport-2uVV34WU.d.mts} +1 -0
  6. package/dist/{EventTransport-C6YA8xY9.mjs → EventTransport-BV10D0oG.mjs} +1 -0
  7. package/dist/{HookSystem-WkIougo6.mjs → HookSystem-vimw5D7Z.mjs} +2 -1
  8. package/dist/{QueryCache-DOMT_iq7.d.mts → QueryCache-DFA-QrU9.d.mts} +2 -1
  9. package/dist/{ResourceRegistry-y9Hnypus.mjs → ResourceRegistry-BVr7PgA0.mjs} +18 -15
  10. package/dist/{actionPermissions-CAphcmUG.mjs → actionPermissions-Dti7ebyv.mjs} +1 -0
  11. package/dist/audit/index.d.mts +3 -2
  12. package/dist/audit/index.mjs +2 -1
  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 +11 -9
  17. package/dist/auth/redis-session.d.mts +2 -1
  18. package/dist/auth/redis-session.mjs +1 -0
  19. package/dist/{applyPermissionResult-Cf6zUCvb.mjs → authorizationDecision-__T3EJDA.mjs} +19 -10
  20. package/dist/{buildHandler-BYS3Vkfz.mjs → buildHandler-Dn7rzUJo.mjs} +6 -4
  21. package/dist/cache/index.d.mts +4 -3
  22. package/dist/cache/index.mjs +4 -3
  23. package/dist/{caching-_NJeDd1_.mjs → caching-DFJqHyFs.mjs} +2 -1
  24. package/dist/cleanup/index.d.mts +39 -16
  25. package/dist/cleanup/index.mjs +50 -38
  26. package/dist/cli/commands/describe.d.mts +2 -1
  27. package/dist/cli/commands/describe.mjs +4 -5
  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-J99LGTJI.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 -6
  45. package/dist/{core-poalJLvp.mjs → core-BwwmNSp7.mjs} +3 -2
  46. package/dist/{createActionRouter-BxuywVRB.mjs → createActionRouter-DkJZWo0m.mjs} +14 -10
  47. package/dist/{createAggregationRouter-BiY-lGdY.mjs → createAggregationRouter-B1exOVD8.mjs} +10 -7
  48. package/dist/{createApp-CXrKFciH.mjs → createApp-Bv0m-Q6_.mjs} +625 -229
  49. package/dist/{defineAggregation-ufWjUwml.mjs → defineAggregation-gX4No1gt.mjs} +1 -0
  50. package/dist/{defineResource-CxWqDCAy.mjs → defineResource-XFPPUu2c.mjs} +84 -50
  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-DNXXxgjl.mjs} +4 -3
  57. package/dist/encryption/index.d.mts +2 -1
  58. package/dist/encryption/index.mjs +5 -3
  59. package/dist/{errorHandler-Bq2OeEQL.mjs → errorHandler-B2AZUJUj.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-Bh1KK5ps.mjs} +1 -0
  63. package/dist/{eventPlugin-D2NNR4py.mjs → eventPlugin-Dk_TyXeE.mjs} +8 -6
  64. package/dist/{eventPlugin-Bg3cP2_u.d.mts → eventPlugin-GF5nnBIq.d.mts} +2 -1
  65. package/dist/events/index.d.mts +112 -44
  66. package/dist/events/index.mjs +177 -141
  67. package/dist/events/transports/redis-stream-entry.d.mts +2 -1
  68. package/dist/events/transports/redis-stream-entry.mjs +14 -9
  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 +131 -7
  74. package/dist/fastify-augmentation-BppG5bMT.d.mts +179 -0
  75. package/dist/{fields-CQ_7KQWw.mjs → fields-DEi65pGj.mjs} +6 -10
  76. package/dist/{fields-D3lhgzhD.d.mts → fields-I_EeRWND.d.mts} +1 -0
  77. package/dist/{generate-D0wZupBg.mjs → generate-CRSG0jTv.mjs} +2 -1
  78. package/dist/{generate-BnsL9GBm.d.mts → generate-DCGuEbqI.d.mts} +1 -0
  79. package/dist/{headers-BvlQSDMB.mjs → headers-BXjQYbeC.mjs} +2 -1
  80. package/dist/{health-CB_m4S32.mjs → health-5XnTa2uo.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 +8 -5
  85. package/dist/idempotency/redis.d.mts +2 -1
  86. package/dist/idempotency/redis.mjs +1 -0
  87. package/dist/{index-CG4yqMDB.d.mts → index-C-481Zf2.d.mts} +74 -18
  88. package/dist/{index-Q5gzEIWY.d.mts → index-CpzxqePp.d.mts} +107 -32
  89. package/dist/{index-Cx7bro4R.d.mts → index-DXlHwvuE.d.mts} +5 -4
  90. package/dist/index-ZWhLTp_s.d.mts +290 -0
  91. package/dist/{index-Ch16aAah.d.mts → index-zq6VrjaB.d.mts} +122 -15
  92. package/dist/index.d.mts +9 -8
  93. package/dist/index.mjs +11 -10
  94. package/dist/{init-tlRSt667.mjs → init-BbfP8aky.mjs} +25 -27
  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 +109 -33
  102. package/dist/integrations/mcp/index.d.mts +6 -6
  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 +38 -34
  116. package/dist/{interface-BGZCzOGL.d.mts → interface-BOk3YnTz.d.mts} +1 -0
  117. package/dist/{interface-n5hpKg1d.mjs → interface-BP4IJCBO.mjs} +1 -0
  118. package/dist/{interface-Uk8ONJv7.d.mts → interface-DEWNOMza.d.mts} +1 -0
  119. package/dist/{jsonBody-BlzAoGCe.mjs → jsonBody-DDSR2HFl.mjs} +2 -1
  120. package/dist/{keys-Cd316u_y.mjs → keys-B-HBTGXl.mjs} +1 -0
  121. package/dist/{loadResources-DpU8pfjV.mjs → loadResources-DisEAmkm.mjs} +4 -2
  122. package/dist/{loadResourcesFromEntry-kvGT6EcO.mjs → loadResourcesFromEntry-D1MJp13f.mjs} +2 -1
  123. package/dist/logger/index.d.mts +1 -0
  124. package/dist/logger/index.mjs +1 -0
  125. package/dist/{mcpPlugin-nRzm5g2s.mjs → mcpPlugin-CpfVpKHE.mjs} +38 -38
  126. package/dist/{memory-CmnzXE5Z.mjs → memory-DsuWnj4T.mjs} +5 -4
  127. package/dist/{metrics-fdxI5mn_.mjs → metrics-DdQL8syd.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 -0
  131. package/dist/migrations/index.mjs +3 -2
  132. package/dist/module-Br81am6m.mjs +78 -0
  133. package/dist/{multipartBody-D0eBvkeq.mjs → multipartBody-BnkBLN7v.mjs} +3 -2
  134. package/dist/{openapi-B8Vkbxbn.mjs → openapi-DIRzvxC0.mjs} +6 -5
  135. package/dist/outbox-admin/index.d.mts +85 -0
  136. package/dist/outbox-admin/index.mjs +115 -0
  137. package/dist/permissions/index.d.mts +5 -4
  138. package/dist/permissions/index.mjs +6 -5
  139. package/dist/{permissions-Bfu_8y1U.mjs → permissions-Dl_m0NCe.mjs} +308 -42
  140. package/dist/{pipe-BOLmGIxa.mjs → pipe-ELHCP3zj.mjs} +3 -4
  141. package/dist/pipeline/index.d.mts +2 -1
  142. package/dist/pipeline/index.mjs +2 -1
  143. package/dist/{pipeline-BJnpkY9i.mjs → pipeline-CPA-jx0M.mjs} +17 -14
  144. package/dist/plugins/index.d.mts +8 -7
  145. package/dist/plugins/index.mjs +16 -15
  146. package/dist/plugins/response-cache.d.mts +1 -0
  147. package/dist/plugins/response-cache.mjs +2 -1
  148. package/dist/plugins/tracing-entry.d.mts +2 -1
  149. package/dist/plugins/tracing-entry.mjs +2 -1
  150. package/dist/{pluralize-BhrgwZb9.mjs → pluralize-C2ZkKbsn.mjs} +1 -0
  151. package/dist/{preloadResources-DSY5HGpS.mjs → preloadResources-CDs87fTw.mjs} +1 -0
  152. package/dist/{preloadResources-DCfa2egz.d.mts → preloadResources-T1hmlTEZ.d.mts} +117 -12
  153. package/dist/presets/filesUpload.d.mts +6 -5
  154. package/dist/presets/filesUpload.mjs +10 -12
  155. package/dist/presets/index.d.mts +3 -2
  156. package/dist/presets/index.mjs +2 -1
  157. package/dist/presets/multiTenant.d.mts +2 -1
  158. package/dist/presets/multiTenant.mjs +3 -2
  159. package/dist/presets/search.d.mts +3 -2
  160. package/dist/presets/search.mjs +2 -1
  161. package/dist/{presets-CJOEXLuk.mjs → presets-Dntd_pUO.mjs} +6 -4
  162. package/dist/{projection-UBDgfMwu.mjs → projection-CjHhM5W1.mjs} +2 -1
  163. package/dist/{pushref-registry-BbcnpNTm.d.mts → pushref-registry-Dov9l4qE.d.mts} +1 -0
  164. package/dist/{queryCachePlugin-br4otiqF.d.mts → queryCachePlugin-BzqxconG.d.mts} +3 -2
  165. package/dist/{queryCachePlugin-DSGuSVLW.mjs → queryCachePlugin-Dhps-Ldt.mjs} +6 -5
  166. package/dist/{rateLimit-DrED-C4v.mjs → rateLimit-C7mmAGs3.mjs} +7 -3
  167. package/dist/{rateLimitKey-BoWRH3e7.mjs → rateLimitKey-CccEFBfk.mjs} +1 -0
  168. package/dist/{realtime-B9nmIY5V.mjs → realtime-DVvVZZFz.mjs} +20 -9
  169. package/dist/{redis-UDtLaNj6.d.mts → redis-CduhKsJi.d.mts} +2 -1
  170. package/dist/{redis-stream-uIaauNpZ.d.mts → redis-stream-qN6A-RFv.d.mts} +2 -1
  171. package/dist/{registerRoute-JkxIEBJq.mjs → registerRoute-B_QrjYZS.mjs} +10 -7
  172. package/dist/registry/index.d.mts +3 -2
  173. package/dist/registry/index.mjs +3 -2
  174. package/dist/{registry-BYXUUg21.mjs → registry-DqUmzA9W.mjs} +2 -1
  175. package/dist/{renewingLease-3cuQH7h8.mjs → renewingLease-BpP9wwj7.mjs} +1 -0
  176. package/dist/{replyHelpers-67tRawQZ.mjs → replyHelpers-DS-P0b32.mjs} +2 -1
  177. package/dist/{repositoryFilter-Dz8k3S4P.mjs → repositoryFilter-DotlxG3L.mjs} +1 -0
  178. package/dist/{requestContext-MrlJwECf.mjs → requestContext-B6vQF-sy.mjs} +1 -0
  179. package/dist/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-BhflRrBs.mjs} +1 -0
  180. package/dist/{schedules-CjOd2MG9.mjs → schedules-C2Q3uv2d.mjs} +3 -2
  181. package/dist/{schemaConverter-DJvodiHj.mjs → schemaConverter-Crjvczi_.mjs} +70 -17
  182. package/dist/{schemaIR-Ca7ySNPx.mjs → schemaIR-CnyKK_1j.mjs} +2 -1
  183. package/dist/schemas/index.d.mts +1 -0
  184. package/dist/schemas/index.mjs +1 -0
  185. package/dist/scim/index.d.mts +1 -0
  186. package/dist/scim/index.mjs +2 -1
  187. package/dist/scope/index.d.mts +3 -2
  188. package/dist/scope/index.mjs +5 -4
  189. package/dist/{scope-DfaPntpo.mjs → scope-afz_Ieek.mjs} +4 -3
  190. package/dist/{sessionManager-BHOM3N_0.d.mts → sessionManager-qimlKV0P.d.mts} +1 -0
  191. package/dist/{simpleEqualityMatcher-Cg05HPsn.mjs → simpleEqualityMatcher-C4KiMWUa.mjs} +1 -0
  192. package/dist/{sse-BQ1w0dfl.mjs → sse-B9P4Y-UI.mjs} +7 -5
  193. package/dist/{sseStream-BQ7Qaynp.mjs → sseStream-B3zb_d1h.mjs} +2 -1
  194. package/dist/{storage-DLUZ04kK.d.mts → storage-Cx9Sz3Ou.d.mts} +1 -0
  195. package/dist/{store-helpers-DY6xq4sM.mjs → store-helpers-DmEMHlPQ.mjs} +1 -0
  196. package/dist/{streaming-BQcW6_Kq.mjs → streaming-DPZdQmDz.mjs} +1 -0
  197. package/dist/{subscribe-helpers-DfzsivM6.mjs → subscribe-helpers-sp5xTDVA.mjs} +2 -1
  198. package/dist/sync/changelog.d.mts +1 -0
  199. package/dist/sync/changelog.mjs +1 -0
  200. package/dist/testing/cleanupStoreContract.d.mts +2 -1
  201. package/dist/testing/cleanupStoreContract.mjs +10 -5
  202. package/dist/testing/index.d.mts +166 -12
  203. package/dist/testing/index.mjs +315 -43
  204. package/dist/testing/outboxStoreContract.d.mts +12 -2
  205. package/dist/testing/outboxStoreContract.mjs +110 -20
  206. package/dist/testing/storageContract.d.mts +2 -1
  207. package/dist/testing/storageContract.mjs +11 -5
  208. package/dist/{tracing-CsGKQJGg.d.mts → tracing-Dro5eH6j.d.mts} +1 -0
  209. package/dist/{transactionContext-PNdoyLLm.mjs → transactionContext-BvLKvOIL.mjs} +1 -0
  210. package/dist/{typeGuards-C9KXUly6.mjs → typeGuards-B_5-hKjJ.mjs} +1 -0
  211. package/dist/types/index.d.mts +6 -5
  212. package/dist/types/index.mjs +1 -0
  213. package/dist/types/storage.d.mts +2 -1
  214. package/dist/types/storage.mjs +1 -0
  215. package/dist/{types-ChvvVNP8.d.mts → types-B7XeLEgK.d.mts} +34 -2
  216. package/dist/{types-Cc91nXuA.d.mts → types-ByHn9rEO.d.mts} +1 -0
  217. package/dist/{types-C_ZZRcD6.d.mts → types-C4QE8qnm.d.mts} +1 -0
  218. package/dist/{types-D_n8L6VY.d.mts → types-CT5vu6M0.d.mts} +132 -17
  219. package/dist/{types-DjC-KUCz.d.mts → types-ChuShpCP.d.mts} +1 -0
  220. package/dist/{types-Bmmn-fO0.d.mts → types-CwcUQ610.d.mts} +2 -6
  221. package/dist/{types-CSyCxl2V.mjs → types-DXYX7dIR.mjs} +4 -3
  222. package/dist/usage/index.d.mts +3 -2
  223. package/dist/usage/index.mjs +0 -0
  224. package/dist/{userHelpers-Cg_y3fBz.mjs → userHelpers-BNsOXzOd.mjs} +1 -0
  225. package/dist/utils/index.d.mts +16 -7
  226. package/dist/utils/index.mjs +10 -9
  227. package/dist/{utils-taUVD4p3.mjs → utils-BtOha_Sp.mjs} +3 -2
  228. package/dist/{validate-Bk44F2yT.mjs → validate-PMCPR9El.mjs} +2 -1
  229. package/dist/{versioning-gKWyryT3.mjs → versioning-CKv2jz_U.mjs} +4 -3
  230. package/dist/{versioning-DXgY5BUm.d.mts → versioning-D_rhHfZm.d.mts} +5 -2
  231. package/dist/{websocket-_Pd0CgR0.d.mts → websocket-BOXVggMf.d.mts} +2 -1
  232. package/package.json +557 -539
  233. package/skills/arc/SKILL.md +22 -8
  234. package/skills/arc/references/api-reference.md +4 -0
  235. package/skills/arc/references/auth.md +3 -0
  236. package/skills/arc/references/integrations.md +33 -0
  237. package/skills/arc/references/mcp.md +6 -7
  238. package/skills/arc/references/production.md +24 -25
  239. package/skills/arc-code-review/references/anti-patterns.md +3 -3
  240. package/dist/fastify-augmentation-0WhPnUH7.d.mts +0 -76
  241. package/dist/index-CCzVR-N3.d.mts +0 -107
  242. package/dist/module-_-M7C5ES.mjs +0 -29
@@ -123,7 +123,7 @@ export default defineResource({
123
123
 
124
124
  routes: [
125
125
  { method: 'GET', path: '/featured', handler: 'getFeatured', permissions: allowPublic() },
126
- { method: 'POST', path: '/webhook', handler: webhookFn, raw: true, permissions: requireAuth() },
126
+ { method: 'POST', path: '/webhook', rawHandler: webhookFn, permissions: requireAuth() },
127
127
  ],
128
128
  actions: {
129
129
  approve: { handler: approveOrder, permissions: requireRoles(['admin']) },
@@ -133,6 +133,20 @@ export default defineResource({
133
133
 
134
134
  **Auto-generated:** `GET /`, `GET /:id`, `POST /`, `PATCH /:id`, `DELETE /:id`. Presets add their own routes (see table below).
135
135
 
136
+ ### Custom routes: `handler` vs `rawHandler`
137
+
138
+ A route declares its execution model by WHICH FIELD it fills — there is no `raw` flag (removed in 2.31).
139
+
140
+ | Field | Signature | Behaviour |
141
+ |---|---|---|
142
+ | `handler` | `(ctx: IRequestContext)` or a controller-method name | Arc pipeline: envelope shaping, field-write permissions, MCP tool by default |
143
+ | `rawHandler` | `(request, reply)` or a controller-method name | Fastify-native: owns the response, no pipeline, no MCP unless `mcpHandler` is set |
144
+ | `controllerMethod` | `(c: MyController) => c.method` | Typed ref for the pipeline — TS catches typos |
145
+
146
+ Declaring both `handler` and `rawHandler` throws at boot. `streamResponse: true` requires `rawHandler` (the handler is invoked with `(request, reply)` and owns the socket).
147
+
148
+ Inline `rawHandler` parameters infer — no `req: FastifyRequest` annotation — and typed generics (`FastifyRequest<{ Body: CreateBody }>`) assign without a cast.
149
+
136
150
  **Resource shorthands:**
137
151
 
138
152
  ```typescript
@@ -317,8 +331,8 @@ defineResource({
317
331
  name: 'procurement',
318
332
  routeGuards: [tenantGuard.preHandler],
319
333
  routes: [{
320
- method: 'GET', path: '/summary', raw: true, permissions: requireAuth(),
321
- handler: async (req, reply) => {
334
+ method: 'GET', path: '/summary', permissions: requireAuth(),
335
+ rawHandler: async (req, reply) => {
322
336
  const { orgId } = tenantGuard.from(req);
323
337
  reply.send({ orgId });
324
338
  },
@@ -570,9 +584,9 @@ defineResource({
570
584
  name: 'chat',
571
585
  customRoutesOnly: true,
572
586
  routes: [{
573
- method: 'POST', path: '/', streamResponse: true, raw: true,
587
+ method: 'POST', path: '/', streamResponse: true,
574
588
  permissions: requireAuth(),
575
- handler: async (req) => {
589
+ rawHandler: async (req) => {
576
590
  const result = streamText({ model, messages: req.body.messages });
577
591
  return result.toUIMessageStream(); // arc pipes it for you
578
592
  },
@@ -692,7 +706,7 @@ import { envelope } from '@classytic/arc';
692
706
  import { multipartBody } from '@classytic/arc/middleware';
693
707
 
694
708
  // Typed request — no `(req as any).user`
695
- handler: async (req: ArcRequest, reply) => { req.user?.id; req.scope; req.signal; }
709
+ rawHandler: async (req: ArcRequest, reply) => { req.user?.id; req.scope; req.signal; }
696
710
 
697
711
  // File upload — no-op for JSON requests, safe to always add
698
712
  defineResource({
@@ -706,10 +720,10 @@ defineResource({
706
720
  },
707
721
  });
708
722
 
709
- // SSE — preAuth runs before auth (EventSource can't set headers); raw: true streams the response
723
+ // SSE — preAuth runs before auth (EventSource can't set headers); rawHandler streams the response
710
724
  routes: [
711
725
  { preAuth: [(req) => { req.headers.authorization = `Bearer ${req.query.token}`; }] },
712
- { method: 'GET', path: '/stream', raw: true, handler: async (req, reply) => reply.send(stream) },
726
+ { method: 'GET', path: '/stream', rawHandler: async (req, reply) => reply.send(stream) },
713
727
  ]
714
728
 
715
729
  // Per-route rate limit (2.20) — overrides the resource/app default for ONE endpoint.
@@ -60,6 +60,10 @@ import {
60
60
  allowPublic, requireAuth, requireRoles, requireOwnership,
61
61
  requireOrgMembership, requireOrgRole, requireTeamMembership,
62
62
  requireServiceScope, requireScopeContext, requireOrgInScope,
63
+ // Platform-only role gate — an ORG role can never satisfy it. Required by
64
+ // globally-scoped surfaces (jobsPlugin managementRoutes) that cannot filter by tenant.
65
+ requirePlatformRole,
66
+
63
67
  // Agent-auth (2.13) — DPoP + capability mandates (AP2 / x402 / MCP)
64
68
  requireAgentScope, requireMandate, requireDPoP,
65
69
  type RequireAgentScopeOptions, type RequireMandateOptions,
@@ -484,6 +484,9 @@ auth: false
484
484
  import {
485
485
  allowPublic, requireAuth, requireRoles, requireOwnership,
486
486
  requireOrgMembership, requireOrgRole, requireTeamMembership,
487
+ // Platform-only role gate — an ORG role can never satisfy it. Required by
488
+ // globally-scoped surfaces (jobsPlugin managementRoutes) that cannot filter by tenant.
489
+ requirePlatformRole,
487
490
  allOf, anyOf, when, denyAll,
488
491
  } from '@classytic/arc';
489
492
  ```
@@ -71,11 +71,44 @@ await fastify.jobs.dispatch('process-image', { url: '...', width: 800 }, {
71
71
  removeOnComplete: true,
72
72
  });
73
73
 
74
+ // dispatch returns a JobHandle — the queue is part of the identity because
75
+ // BullMQ ids are queue-LOCAL (two queues can both hold "123").
76
+ const { queue, jobId } = await fastify.jobs.dispatch('send-email', { to: 'a@b.com' });
77
+ const status = await fastify.jobs.getStatus(queue, jobId);
78
+
74
79
  // Get stats
75
80
  const stats = await fastify.jobs.getStats();
76
81
  // { 'send-email': { waiting: 5, active: 2, completed: 100, failed: 3, delayed: 0 } }
77
82
  ```
78
83
 
84
+ ### Management routes (off by default)
85
+
86
+ `GET /jobs/stats` and `GET /jobs/:queue/:id/status` are NOT mounted unless you
87
+ ask for them. A status carries `returnValue` and `failedReason` — your handler's
88
+ own output and error text — so an unauthenticated route turns a job id into a
89
+ read primitive over other tenants' work.
90
+
91
+ ```typescript
92
+ jobsPlugin({
93
+ connection,
94
+ jobs,
95
+ managementRoutes: {
96
+ // Must be PLATFORM-only, verified at boot. These routes are global: /stats
97
+ // spans every queue and a job carries no tenant identity, so an org-role
98
+ // gate would let org A's manager read org B's job.
99
+ operatorPermission: requirePlatformRole('platform-ops'),
100
+ exposeResult: false, // default — opt in knowing what your handlers return
101
+ exposeFailureReason: false, // default
102
+ },
103
+ });
104
+ ```
105
+
106
+ `requireOrgRole('manager')` and the default `requireRoles(['platform-ops'])` are
107
+ REFUSED at boot: both grant on an organization role and return no policy, so
108
+ nothing at request time can tell them apart from a real operator gate. Use
109
+ `requirePlatformRole`, or `allowUnverifiedOperatorPermission: true` for a custom
110
+ check you have verified yourself.
111
+
79
112
  ### Timeout & DLQ
80
113
 
81
114
  Job timeout via `Promise.race` (timer always cleaned up). DLQ queues tracked and closed on shutdown:
@@ -568,12 +568,12 @@ Use to verify the MCP server is alive before configuring Claude CLI.
568
568
 
569
569
  ### ArcRequest — Typed Fastify Request
570
570
 
571
- For `raw: true` routes, use `ArcRequest` instead of `(req as any).user`:
571
+ For `rawHandler` routes, use `ArcRequest` instead of `(req as any).user`:
572
572
 
573
573
  ```typescript
574
574
  import type { ArcRequest } from '@classytic/arc';
575
575
 
576
- handler: async (req: ArcRequest, reply) => {
576
+ rawHandler: async (req: ArcRequest, reply) => {
577
577
  req.user?.id; // typed
578
578
  req.scope.organizationId; // typed (when member)
579
579
  req.signal; // AbortSignal (Fastify 5 built-in)
@@ -585,7 +585,7 @@ handler: async (req: ArcRequest, reply) => {
585
585
  ```typescript
586
586
  import { envelope } from '@classytic/arc';
587
587
 
588
- handler: async (req, reply) => {
588
+ rawHandler: async (req, reply) => {
589
589
  const data = await service.getResults();
590
590
  return reply.send(envelope(data));
591
591
  // → { data }
@@ -601,7 +601,7 @@ Eliminates duplicated `req.user.organizationId || req.headers['x-organization-id
601
601
  ```typescript
602
602
  import { getOrgContext } from '@classytic/arc/scope';
603
603
 
604
- handler: async (req, reply) => {
604
+ rawHandler: async (req, reply) => {
605
605
  const { userId, organizationId, roles, orgRoles } = getOrgContext(req);
606
606
  // Works regardless of auth type (JWT, Better Auth, custom)
607
607
  }
@@ -628,13 +628,12 @@ Run before auth middleware. Use for promoting `?token=` to `Authorization` heade
628
628
  routes: [{
629
629
  method: 'GET',
630
630
  path: '/stream',
631
- raw: true,
632
631
  permissions: requireAuth(),
633
632
  preAuth: [(req) => {
634
633
  const token = req.query?.token;
635
634
  if (token) req.headers.authorization = `Bearer ${token}`;
636
635
  }],
637
- handler: sseHandler,
636
+ rawHandler: sseHandler,
638
637
  }]
639
638
  ```
640
639
 
@@ -648,7 +647,7 @@ routes: [{
648
647
  path: '/stream',
649
648
  streamResponse: true, // SSE headers + no { success, data } wrapper
650
649
  permissions: requireAuth(),
651
- handler: async (request, reply) => {
650
+ rawHandler: async (request, reply) => {
652
651
  const { stream } = await generateStream({ abortSignal: request.signal });
653
652
  return reply.send(stream);
654
653
  },
@@ -696,8 +696,7 @@ defineResource({
696
696
  method: 'POST',
697
697
  path: '/:id/checkout',
698
698
  permissions: requireAuth(),
699
- raw: true,
700
- handler: async (request, reply) => {
699
+ rawHandler: async (request, reply) => {
701
700
  const result = await withCompensation('checkout', steps, { orderId: request.params.id });
702
701
  if (!result.success) return reply.code(422).send({ error: result.error });
703
702
  return reply.send({ data: result.results });
@@ -709,26 +708,26 @@ defineResource({
709
708
  **Compensation errors**: collected in `result.compensationErrors[]` without stopping rollback. **Context**: mutable `Record<string, unknown>` shared across all steps.
710
709
 
711
710
  **Scope**: In-process primitive. Process crash = no compensation. For durable distributed workflows, use Temporal, Inngest, or `@classytic/streamline`.
712
-
713
- ## Multi-Domain Deployment
714
-
715
- Arc supports multi-domain applications through COMPOSITION — it deliberately owns no domain routing, domain databases, certificate provisioning, or wildcard-domain management. The deployment shape:
716
-
717
- ```
718
- api.example.com → arc API
719
- auth.example.com → Better Auth (or auth module)
720
- events.example.com → SSE/WebSocket edge tier
721
- admin.example.com → frontend
722
- customer-domain.com → gateway/domain resolver → tenant context
723
- ```
724
-
725
- The primitives are already there: CORS allowlists, Better Auth trusted origins, tenant scope, request context, `trustProxy` configuration, resource/module prefixes.
726
-
727
- **Custom tenant domains**: resolve the domain at a gateway or an early host plugin, then convert it into a trusted `RequestScope` — a small host-provided resolver is enough:
728
-
729
- ```typescript
730
- // onRequest, before auth: map host → tenant hint
731
- const tenant = await resolveTenantDomain(request.hostname); // → { organizationId } | null
732
- ```
733
-
734
- Then validate membership or service scope through the NORMAL permission path. **Never treat raw `Host` / `X-Forwarded-Host` as tenant authorization by itself** — behind a misconfigured proxy those are attacker-controlled (see the `trustProxy` boot warning). The header selects the tenant *candidate*; membership resolution authorizes it — same rule `resolveOrgFromHeader` follows for `x-organization-id`.
711
+
712
+ ## Multi-Domain Deployment
713
+
714
+ Arc supports multi-domain applications through COMPOSITION — it deliberately owns no domain routing, domain databases, certificate provisioning, or wildcard-domain management. The deployment shape:
715
+
716
+ ```
717
+ api.example.com → arc API
718
+ auth.example.com → Better Auth (or auth module)
719
+ events.example.com → SSE/WebSocket edge tier
720
+ admin.example.com → frontend
721
+ customer-domain.com → gateway/domain resolver → tenant context
722
+ ```
723
+
724
+ The primitives are already there: CORS allowlists, Better Auth trusted origins, tenant scope, request context, `trustProxy` configuration, resource/module prefixes.
725
+
726
+ **Custom tenant domains**: resolve the domain at a gateway or an early host plugin, then convert it into a trusted `RequestScope` — a small host-provided resolver is enough:
727
+
728
+ ```typescript
729
+ // onRequest, before auth: map host → tenant hint
730
+ const tenant = await resolveTenantDomain(request.hostname); // → { organizationId } | null
731
+ ```
732
+
733
+ Then validate membership or service scope through the NORMAL permission path. **Never treat raw `Host` / `X-Forwarded-Host` as tenant authorization by itself** — behind a misconfigured proxy those are attacker-controlled (see the `trustProxy` boot warning). The header selects the tenant *candidate*; membership resolution authorizes it — same rule `resolveOrgFromHeader` follows for `x-organization-id`.
@@ -627,7 +627,7 @@ output_mode: content
627
627
 
628
628
  **Why critical:** `verifySignature(body, ...)` throws `TypeError` if body isn't string/Buffer. Parsed body silently fails verification → forged webhooks accepted.
629
629
 
630
- **Fix:** `verifySignature(req.rawBody, ...)`. Mark webhook routes `raw: true` and ensure `@fastify/raw-body` is registered (arc registers it automatically when needed).
630
+ **Fix:** `verifySignature(req.rawBody, ...)`. Declare webhook routes via `rawHandler` and ensure `@fastify/raw-body` is registered (arc registers it automatically when needed).
631
631
 
632
632
  ---
633
633
 
@@ -655,10 +655,10 @@ Or `presets: [{ name: 'filesUpload', storage, allowedMimeTypes, maxFileSize }]`.
655
655
  ```typescript
656
656
  routes: [
657
657
  {
658
- method: 'GET', path: '/stream', raw: true,
658
+ method: 'GET', path: '/stream',
659
659
  preAuth: [(req) => { req.headers.authorization = `Bearer ${req.query.token}`; }],
660
660
  permissions: requireAuth(),
661
- handler: async (req, reply) => reply.send(stream),
661
+ rawHandler: async (req, reply) => reply.send(stream),
662
662
  },
663
663
  ],
664
664
  ```
@@ -1,76 +0,0 @@
1
- import { mn as HookSystem, z as ResourceRegistry } from "./index-Q5gzEIWY.mjs";
2
- import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
3
- import { h as HealthCheck, r as ArcModuleRegistry, t as ArcModule, u as ScheduleDefinition } from "./types-D_n8L6VY.mjs";
4
- import "./index-CCzVR-N3.mjs";
5
- import { FastifyPluginAsync } from "fastify";
6
- //#region src/core/arcCorePlugin.d.ts
7
- interface ArcCorePluginOptions {
8
- /** Enable event emission for CRUD operations (requires eventPlugin) */
9
- emitEvents?: boolean;
10
- /** Hook system instance (for testing/custom setup) */
11
- hookSystem?: HookSystem;
12
- /** Resource registry instance (for testing/custom setup) */
13
- registry?: ResourceRegistry;
14
- }
15
- interface PluginMeta {
16
- name: string;
17
- version?: string;
18
- options?: Record<string, unknown>;
19
- registeredAt: string;
20
- }
21
- interface ArcCore {
22
- /** Instance-scoped hook system */
23
- hooks: HookSystem;
24
- /** Instance-scoped resource registry */
25
- registry: ResourceRegistry;
26
- /** Whether event emission is enabled */
27
- emitEvents: boolean;
28
- /** External OpenAPI paths contributed by auth adapters or third-party integrations */
29
- externalOpenApiPaths: ExternalOpenApiPaths[];
30
- /** Registered plugins for introspection */
31
- plugins: Map<string, PluginMeta>;
32
- /**
33
- * Module public exports — `bootstrap()` return values keyed by module name
34
- * (see `src/factory/module.ts`). Read via `getModuleExports(fastify, name)`:
35
- * augment `ArcModuleRegistry` once and the name infers the export type;
36
- * otherwise pass it inline. `Partial<ArcModuleRegistry>` gives augmented
37
- * apps typed direct access (`fastify.arc.modules.order`), while the
38
- * `Record<string, unknown>` intersection keeps un-augmented reads open.
39
- * Populated lazily by `registerResources` — absent until a module exports.
40
- */
41
- modules?: Partial<ArcModuleRegistry> & Record<string, unknown>;
42
- /**
43
- * Module-contributed readiness checks, collected in dependency order and
44
- * frozen by `createApp`. The API role merges these into its `healthPlugin`;
45
- * `createWorker` reads this to give its probe listener the identical union
46
- * (the worker preset disables the main health plugin, so this is how module
47
- * checks reach the probe without a double registration). Empty until
48
- * `createApp` populates it.
49
- */
50
- healthChecks?: readonly HealthCheck[];
51
- /**
52
- * The resolved, dependency-ordered module definitions (frozen by createApp).
53
- * Integrations that collect a module arm at their OWN init time — e.g. the
54
- * streamline integration calling `collectModuleWorkflows(fastify, defs)` after
55
- * it has created + decorated its container — read the list from here. Empty
56
- * until createApp populates it.
57
- */
58
- moduleDefinitions?: readonly ArcModule[];
59
- /**
60
- * Module-contributed recurring schedules, resolved once in dependency order
61
- * and frozen after bootstraps. The same definitions are executed by Arc's
62
- * schedules plugin and exposed here for introspection. Empty until collection.
63
- */
64
- scheduledJobs?: readonly Readonly<ScheduleDefinition>[];
65
- }
66
- declare const arcCorePlugin: FastifyPluginAsync<ArcCorePluginOptions>;
67
- declare const _default: FastifyPluginAsync<ArcCorePluginOptions>;
68
- //#endregion
69
- //#region src/types/fastify-augmentation.d.ts
70
- declare module "fastify" {
71
- interface FastifyInstance {
72
- arc?: ArcCore;
73
- }
74
- }
75
- //#endregion
76
- export { arcCorePlugin as a, _default as i, ArcCorePluginOptions as n, PluginMeta as r, ArcCore as t };
@@ -1,107 +0,0 @@
1
- import { a as ModuleContribution, h as HealthCheck, n as ArcModuleInput, r as ArcModuleRegistry, t as ArcModule, u as ScheduleDefinition } from "./types-D_n8L6VY.mjs";
2
- import { FastifyInstance } from "fastify";
3
- //#region src/factory/module/contributions.d.ts
4
- /**
5
- * Collect every module's `healthChecks` in the caller's order. Fails at boot
6
- * on a duplicate check `name` across modules, naming BOTH owning modules.
7
- * The host's app-level checks are appended AFTER this (modules-first,
8
- * host-last) by the caller.
9
- */
10
- declare function collectModuleHealthChecks(modules: readonly ArcModule[]): HealthCheck[];
11
- /**
12
- * Collect every module's `workflows`. Resolves factory contributions (so a
13
- * factory can close over a container the caller has already decorated on
14
- * `fastify`) and flattens to a single opaque array. Arc core deliberately
15
- * does NOT validate or interpret the values — the streamline integration
16
- * owns name/shape checks and registration. Called by the integration at init
17
- * time, not by arc's boot pipeline (workflows depend on the integration's
18
- * container).
19
- */
20
- declare function collectModuleWorkflows(fastify: FastifyInstance, modules: readonly ArcModule[]): Promise<unknown[]>;
21
- /**
22
- * Collect every module's `scheduledJobs`. Resolves factory contributions
23
- * (after bootstraps) and fails at boot on a duplicate job `name` across
24
- * modules. The caller passes the returned definitions to the canonical
25
- * schedules plugin; factories must therefore be resolved only once.
26
- */
27
- declare function collectModuleScheduledJobs(fastify: FastifyInstance, modules: readonly ArcModule[]): Promise<ScheduleDefinition[]>;
28
- //#endregion
29
- //#region src/factory/module/lifecycle.d.ts
30
- /**
31
- * Subscribe every module's `eventHandlers` in dependency order (pass the
32
- * `orderModules`-sorted list). Returns the ordered list of unsubscribe
33
- * functions so the caller can tear them down at shutdown BEFORE module
34
- * `onClose` (while module deps are still alive). Fails at boot on:
35
- * - a duplicate NAMED handler across modules (attributing both owners);
36
- * - a module declaring handlers while the event subsystem is unavailable.
37
- * Resolves factory contributions (after bootstraps) so a handler can close over
38
- * booted engines rather than a global getter.
39
- */
40
- declare function subscribeModuleEventHandlers(fastify: FastifyInstance, modules: readonly ArcModule[]): Promise<Array<() => void | Promise<void>>>;
41
- //#endregion
42
- //#region src/factory/module/order.d.ts
43
- /**
44
- * Order modules for composition by their `dependsOn` edges — a STABLE
45
- * topological sort. Called once at the start of the bootstrap phase; every
46
- * subsequent phase (bootstrap, resources, afterResources, and reverse-order
47
- * onClose) iterates the returned list, so a module's declared dependencies are
48
- * always composed before it.
49
- *
50
- * "Stable" = modules with no edge between them keep their original list order,
51
- * so declaring `dependsOn` on one module never silently reorders an unrelated
52
- * one. Backward compatible: a `modules` array with NO `dependsOn` anywhere is
53
- * returned unchanged.
54
- *
55
- * Fail-fast — throws (never reorders past a broken contract) on:
56
- * - duplicate module names (the name is the graph key)
57
- * - a `dependsOn` name not present in the composed set
58
- * - a self-reference (`dependsOn` includes the module's own name)
59
- * - a dependency cycle (reports the concrete `a → b → … → a` path)
60
- */
61
- declare function orderModules(modules: readonly ArcModule[]): ArcModule[];
62
- //#endregion
63
- //#region src/factory/module/resolve.d.ts
64
- /**
65
- * Resolve any module-input form to a concrete `ArcModule`. A thunk is invoked
66
- * (this is where a dynamic `import()` fires); a promise (or plain module) is
67
- * awaited. Called once per module at the start of the bootstrap phase.
68
- */
69
- declare function resolveModule(input: ArcModuleInput): Promise<ArcModule>;
70
- /** Resolve a `ModuleContribution` to a concrete array (array passthrough or factory call). */
71
- declare function resolveContribution<T>(contribution: ModuleContribution<T> | undefined, fastify: FastifyInstance): Promise<readonly T[]>;
72
- //#endregion
73
- //#region src/factory/module/index.d.ts
74
- /**
75
- * Identity helper for authoring a typed module — mirrors `defineResource`.
76
- * Gives inference + a single obvious construction site; does no work.
77
- *
78
- * `TExports` is inferred from the `bootstrap` return value, so a module
79
- * author gets a typed public export for free:
80
- *
81
- * ```ts
82
- * export const accountingModule = (deps: Deps) =>
83
- * defineModule({
84
- * name: "accounting",
85
- * bootstrap: async () => createAccountingEngine(deps), // TExports inferred
86
- * });
87
- * ```
88
- */
89
- declare function defineModule<TExports = unknown>(module: ArcModule<TExports>): ArcModule<TExports>;
90
- /**
91
- * Typed accessor for a module's public export (its `bootstrap` return value),
92
- * recorded at `fastify.arc.modules[name]`.
93
- *
94
- * Throwing, in line with the fail-fast contract (and `requireOrgId`-style
95
- * accessors): a missing entry means the module either isn't composed, is
96
- * composed AFTER the caller (list order = init order), or returned nothing —
97
- * all wiring bugs that must surface at boot, not as `undefined` downstream.
98
- *
99
- * The type parameter is an assertion, not a proof. Two ways to type it:
100
- * - augment `ArcModuleRegistry` once — then the name alone infers the type:
101
- * `getModuleExports(f, "accounting")` → `AccountingEngine`.
102
- * - or pass it inline: `getModuleExports<AccountingEngine>(f, "accounting")`.
103
- */
104
- declare function getModuleExports<K extends keyof ArcModuleRegistry>(fastify: FastifyInstance, name: K): ArcModuleRegistry[K];
105
- declare function getModuleExports<TExports = unknown>(fastify: FastifyInstance, name: string): TExports;
106
- //#endregion
107
- export { orderModules as a, collectModuleScheduledJobs as c, resolveModule as i, collectModuleWorkflows as l, getModuleExports as n, subscribeModuleEventHandlers as o, resolveContribution as r, collectModuleHealthChecks as s, defineModule as t };
@@ -1,29 +0,0 @@
1
- //#region src/factory/module/index.ts
2
- /**
3
- * Identity helper for authoring a typed module — mirrors `defineResource`.
4
- * Gives inference + a single obvious construction site; does no work.
5
- *
6
- * `TExports` is inferred from the `bootstrap` return value, so a module
7
- * author gets a typed public export for free:
8
- *
9
- * ```ts
10
- * export const accountingModule = (deps: Deps) =>
11
- * defineModule({
12
- * name: "accounting",
13
- * bootstrap: async () => createAccountingEngine(deps), // TExports inferred
14
- * });
15
- * ```
16
- */
17
- function defineModule(module) {
18
- return module;
19
- }
20
- function getModuleExports(fastify, name) {
21
- const modules = fastify.arc?.modules;
22
- if (!modules || !Object.hasOwn(modules, name)) {
23
- const available = modules ? Object.keys(modules) : [];
24
- throw new Error(`[arc] no public export recorded for module "${name}".\nA module's export is its bootstrap() return value; it is readable only AFTER that module's bootstrap ran (dependsOn / list order = init order).\nModules with recorded exports: ${available.length ? available.join(", ") : "(none)"}.`);
25
- }
26
- return modules[name];
27
- }
28
- //#endregion
29
- export { getModuleExports as n, defineModule as t };