@classytic/arc 2.3.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +187 -18
  2. package/bin/arc.js +11 -3
  3. package/dist/BaseController-CkM5dUh_.mjs +1031 -0
  4. package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
  5. package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
  6. package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
  7. package/dist/adapters/index.d.mts +3 -5
  8. package/dist/adapters/index.mjs +2 -3
  9. package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
  10. package/dist/audit/index.d.mts +4 -7
  11. package/dist/audit/index.mjs +2 -29
  12. package/dist/audit/mongodb.d.mts +1 -4
  13. package/dist/audit/mongodb.mjs +2 -3
  14. package/dist/auth/index.d.mts +7 -9
  15. package/dist/auth/index.mjs +65 -63
  16. package/dist/auth/redis-session.d.mts +1 -1
  17. package/dist/auth/redis-session.mjs +1 -2
  18. package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
  19. package/dist/cache/index.d.mts +23 -23
  20. package/dist/cache/index.mjs +4 -6
  21. package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
  22. package/dist/chunk-BpYLSNr0.mjs +14 -0
  23. package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
  24. package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
  25. package/dist/cli/commands/describe.mjs +24 -7
  26. package/dist/cli/commands/docs.mjs +6 -7
  27. package/dist/cli/commands/doctor.d.mts +10 -0
  28. package/dist/cli/commands/doctor.mjs +156 -0
  29. package/dist/cli/commands/generate.mjs +66 -17
  30. package/dist/cli/commands/init.mjs +315 -45
  31. package/dist/cli/commands/introspect.mjs +2 -4
  32. package/dist/cli/index.d.mts +1 -10
  33. package/dist/cli/index.mjs +4 -153
  34. package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
  35. package/dist/core/index.d.mts +3 -5
  36. package/dist/core/index.mjs +5 -4
  37. package/dist/core-C1XCMtqM.mjs +185 -0
  38. package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
  39. package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
  40. package/dist/discovery/index.mjs +37 -5
  41. package/dist/docs/index.d.mts +6 -9
  42. package/dist/docs/index.mjs +3 -21
  43. package/dist/dynamic/index.d.mts +93 -0
  44. package/dist/dynamic/index.mjs +122 -0
  45. package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
  46. package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
  47. package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
  48. package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
  49. package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
  50. package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
  51. package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
  52. package/dist/events/index.d.mts +72 -7
  53. package/dist/events/index.mjs +216 -4
  54. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  55. package/dist/events/transports/redis-stream-entry.mjs +19 -7
  56. package/dist/events/transports/redis.d.mts +1 -1
  57. package/dist/events/transports/redis.mjs +3 -4
  58. package/dist/factory/index.d.mts +23 -9
  59. package/dist/factory/index.mjs +48 -3
  60. package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
  61. package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
  62. package/dist/hooks/index.d.mts +1 -3
  63. package/dist/hooks/index.mjs +2 -3
  64. package/dist/idempotency/index.d.mts +5 -5
  65. package/dist/idempotency/index.mjs +3 -7
  66. package/dist/idempotency/mongodb.d.mts +1 -1
  67. package/dist/idempotency/mongodb.mjs +4 -5
  68. package/dist/idempotency/redis.d.mts +1 -1
  69. package/dist/idempotency/redis.mjs +2 -5
  70. package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
  73. package/dist/index.d.mts +100 -105
  74. package/dist/index.mjs +85 -58
  75. package/dist/integrations/event-gateway.d.mts +1 -1
  76. package/dist/integrations/event-gateway.mjs +8 -4
  77. package/dist/integrations/index.d.mts +4 -2
  78. package/dist/integrations/index.mjs +1 -1
  79. package/dist/integrations/jobs.d.mts +2 -2
  80. package/dist/integrations/jobs.mjs +63 -14
  81. package/dist/integrations/mcp/index.d.mts +219 -0
  82. package/dist/integrations/mcp/index.mjs +572 -0
  83. package/dist/integrations/mcp/testing.d.mts +53 -0
  84. package/dist/integrations/mcp/testing.mjs +104 -0
  85. package/dist/integrations/streamline.mjs +39 -19
  86. package/dist/integrations/webhooks.d.mts +56 -0
  87. package/dist/integrations/webhooks.mjs +139 -0
  88. package/dist/integrations/websocket-redis.d.mts +46 -0
  89. package/dist/integrations/websocket-redis.mjs +50 -0
  90. package/dist/integrations/websocket.d.mts +68 -2
  91. package/dist/integrations/websocket.mjs +96 -13
  92. package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
  93. package/dist/interface-DGmPxakH.d.mts +2213 -0
  94. package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
  95. package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
  96. package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
  97. package/dist/metrics-Csh4nsvv.mjs +224 -0
  98. package/dist/migrations/index.d.mts +113 -44
  99. package/dist/migrations/index.mjs +84 -102
  100. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  101. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  102. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  103. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  104. package/dist/org/index.d.mts +12 -14
  105. package/dist/org/index.mjs +92 -119
  106. package/dist/org/types.d.mts +2 -2
  107. package/dist/org/types.mjs +1 -1
  108. package/dist/permissions/index.d.mts +4 -278
  109. package/dist/permissions/index.mjs +4 -579
  110. package/dist/permissions-CA5zg0yK.mjs +751 -0
  111. package/dist/plugins/index.d.mts +104 -107
  112. package/dist/plugins/index.mjs +203 -313
  113. package/dist/plugins/response-cache.mjs +4 -69
  114. package/dist/plugins/tracing-entry.d.mts +1 -1
  115. package/dist/plugins/tracing-entry.mjs +24 -11
  116. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  117. package/dist/policies/index.d.mts +2 -2
  118. package/dist/policies/index.mjs +80 -83
  119. package/dist/presets/index.d.mts +26 -19
  120. package/dist/presets/index.mjs +2 -142
  121. package/dist/presets/multiTenant.d.mts +1 -4
  122. package/dist/presets/multiTenant.mjs +4 -6
  123. package/dist/presets-C9QXJV1u.mjs +422 -0
  124. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  125. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  126. package/dist/queryParser-CgCtsjti.mjs +352 -0
  127. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  128. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  129. package/dist/registry/index.d.mts +1 -4
  130. package/dist/registry/index.mjs +3 -4
  131. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  132. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  133. package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
  134. package/dist/rpc/index.d.mts +90 -0
  135. package/dist/rpc/index.mjs +248 -0
  136. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  137. package/dist/schemas/index.d.mts +30 -30
  138. package/dist/schemas/index.mjs +2 -4
  139. package/dist/scope/index.d.mts +13 -2
  140. package/dist/scope/index.mjs +18 -5
  141. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  142. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  143. package/dist/testing/index.d.mts +551 -567
  144. package/dist/testing/index.mjs +1744 -1799
  145. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  146. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  147. package/dist/types/index.d.mts +4 -946
  148. package/dist/types/index.mjs +2 -4
  149. package/dist/types-BJmgxNbF.d.mts +275 -0
  150. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  151. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  152. package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
  153. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  154. package/dist/utils/index.d.mts +254 -351
  155. package/dist/utils/index.mjs +7 -6
  156. package/dist/utils-Dc0WhlIl.mjs +594 -0
  157. package/dist/versioning-BzfeHmhj.mjs +37 -0
  158. package/package.json +44 -10
  159. package/skills/arc/SKILL.md +518 -0
  160. package/skills/arc/references/auth.md +250 -0
  161. package/skills/arc/references/events.md +272 -0
  162. package/skills/arc/references/integrations.md +385 -0
  163. package/skills/arc/references/mcp.md +431 -0
  164. package/skills/arc/references/production.md +610 -0
  165. package/skills/arc/references/testing.md +183 -0
  166. package/dist/audited-CGdLiSlE.mjs +0 -140
  167. package/dist/chunk-C7Uep-_p.mjs +0 -20
  168. package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
  169. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  170. package/dist/interface-BtdYtQUA.d.mts +0 -1114
  171. package/dist/presets-BTeYbw7h.d.mts +0 -57
  172. package/dist/presets-CeFtfDR8.mjs +0 -119
  173. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  174. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  175. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.3.0",
3
+ "version": "2.4.2",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -108,6 +108,10 @@
108
108
  "types": "./dist/events/transports/redis-stream-entry.d.mts",
109
109
  "default": "./dist/events/transports/redis-stream-entry.mjs"
110
110
  },
111
+ "./dynamic": {
112
+ "types": "./dist/dynamic/index.d.mts",
113
+ "default": "./dist/dynamic/index.mjs"
114
+ },
111
115
  "./testing": {
112
116
  "types": "./dist/testing/index.d.mts",
113
117
  "default": "./dist/testing/index.mjs"
@@ -144,6 +148,10 @@
144
148
  "types": "./dist/integrations/websocket.d.mts",
145
149
  "default": "./dist/integrations/websocket.mjs"
146
150
  },
151
+ "./integrations/websocket-redis": {
152
+ "types": "./dist/integrations/websocket-redis.d.mts",
153
+ "default": "./dist/integrations/websocket-redis.mjs"
154
+ },
147
155
  "./integrations/jobs": {
148
156
  "types": "./dist/integrations/jobs.d.mts",
149
157
  "default": "./dist/integrations/jobs.mjs"
@@ -152,6 +160,10 @@
152
160
  "types": "./dist/integrations/event-gateway.d.mts",
153
161
  "default": "./dist/integrations/event-gateway.mjs"
154
162
  },
163
+ "./integrations/webhooks": {
164
+ "types": "./dist/integrations/webhooks.d.mts",
165
+ "default": "./dist/integrations/webhooks.mjs"
166
+ },
155
167
  "./auth/redis": {
156
168
  "types": "./dist/auth/redis-session.d.mts",
157
169
  "default": "./dist/auth/redis-session.mjs"
@@ -163,6 +175,18 @@
163
175
  "./scope": {
164
176
  "types": "./dist/scope/index.d.mts",
165
177
  "default": "./dist/scope/index.mjs"
178
+ },
179
+ "./rpc": {
180
+ "types": "./dist/rpc/index.d.mts",
181
+ "default": "./dist/rpc/index.mjs"
182
+ },
183
+ "./mcp": {
184
+ "types": "./dist/integrations/mcp/index.d.mts",
185
+ "default": "./dist/integrations/mcp/index.mjs"
186
+ },
187
+ "./mcp/testing": {
188
+ "types": "./dist/integrations/mcp/testing.d.mts",
189
+ "default": "./dist/integrations/mcp/testing.mjs"
166
190
  }
167
191
  },
168
192
  "main": "./dist/index.mjs",
@@ -173,26 +197,31 @@
173
197
  "sideEffects": false,
174
198
  "files": [
175
199
  "dist",
176
- "bin"
200
+ "bin",
201
+ "skills"
177
202
  ],
178
203
  "scripts": {
179
204
  "build": "tsdown",
180
205
  "dev": "tsdown --watch",
181
206
  "typecheck": "tsc --noEmit",
207
+ "lint": "biome check src/",
208
+ "lint:fix": "biome check --fix src/",
209
+ "lint:all": "biome check src/ tests/",
182
210
  "test": "vitest run",
183
211
  "test:watch": "vitest",
184
212
  "test:ui": "vitest --ui",
185
213
  "test:coverage": "vitest run --coverage",
186
214
  "test:e2e": "vitest run tests/e2e",
187
215
  "test:unit": "vitest run tests/core tests/hooks tests/utils tests/plugins",
188
- "prepublishOnly": "npm run typecheck && npm test && npm run build"
216
+ "smoke": "node scripts/smoke-test.mjs",
217
+ "prepublishOnly": "npm run typecheck && npm test && npm run build && npm run smoke"
189
218
  },
190
219
  "engines": {
191
220
  "node": ">=22"
192
221
  },
193
222
  "peerDependencies": {
194
- "@classytic/mongokit": ">=3.3.2",
195
- "@classytic/streamline": ">=1.0.0",
223
+ "@classytic/mongokit": ">=3.4.3",
224
+ "@classytic/streamline": ">=2.0.0",
196
225
  "@fastify/cors": "^11.0.0",
197
226
  "@fastify/helmet": "^13.0.0",
198
227
  "@fastify/jwt": "^10.0.0",
@@ -202,14 +231,14 @@
202
231
  "@fastify/type-provider-typebox": "^6.0.0",
203
232
  "@fastify/under-pressure": "^9.0.0",
204
233
  "@fastify/websocket": "^11.0.0",
205
- "@opentelemetry/api": "^1.0.0",
234
+ "@modelcontextprotocol/sdk": ">=1.28.0",
206
235
  "@opentelemetry/auto-instrumentations-node": ">=0.40.0",
207
236
  "@opentelemetry/exporter-trace-otlp-http": ">=0.50.0",
208
237
  "@opentelemetry/instrumentation-http": ">=0.50.0",
209
238
  "@opentelemetry/instrumentation-mongodb": ">=0.40.0",
210
239
  "@opentelemetry/sdk-node": ">=0.50.0",
211
240
  "@sinclair/typebox": "^0.34.0",
212
- "better-auth": ">=1.0.0",
241
+ "better-auth": ">=1.5.5",
213
242
  "bullmq": "^5.0.0",
214
243
  "fastify": "^5.7.4",
215
244
  "fastify-raw-body": "^5.0.0",
@@ -297,6 +326,9 @@
297
326
  },
298
327
  "zod": {
299
328
  "optional": true
329
+ },
330
+ "@modelcontextprotocol/sdk": {
331
+ "optional": true
300
332
  }
301
333
  },
302
334
  "dependencies": {
@@ -304,11 +336,13 @@
304
336
  "qs": "^6.14.1"
305
337
  },
306
338
  "devDependencies": {
307
- "@classytic/mongokit": "^3.3.2",
339
+ "@biomejs/biome": "^2.4.10",
340
+ "@classytic/mongokit": "^3.4.3",
308
341
  "@fastify/jwt": "^10.0.0",
309
342
  "@fastify/multipart": "^9.0.0",
310
343
  "@fastify/type-provider-typebox": "^6.0.0",
311
344
  "@fastify/websocket": "^11.0.0",
345
+ "@modelcontextprotocol/sdk": "^1.29.0",
312
346
  "@sinclair/typebox": "^0.34.0",
313
347
  "@types/node": "^22.10.0",
314
348
  "@types/qs": "^6.14.0",
@@ -317,8 +351,8 @@
317
351
  "jsonwebtoken": "^9.0.0",
318
352
  "mongodb": "^7.1.0",
319
353
  "mongodb-memory-server": "^11.0.1",
320
- "tsdown": "^0.20.3",
321
- "typescript": "^5.7.2",
354
+ "tsdown": "^0.21.7",
355
+ "typescript": "^6.0.2",
322
356
  "vitest": "^3.0.0",
323
357
  "ws": "^8.0.0",
324
358
  "zod": "^4.3.6"
@@ -0,0 +1,518 @@
1
+ ---
2
+ name: arc
3
+ description: |
4
+ @classytic/arc — Resource-oriented backend framework for Fastify.
5
+ Use when building REST APIs with Fastify, resource CRUD, defineResource, createApp,
6
+ permissions, presets, database adapters, hooks, events, QueryCache, authentication,
7
+ multi-tenant SaaS, OpenAPI, job queues, WebSocket, MCP tools, or production deployment.
8
+ Triggers: arc, fastify resource, defineResource, createApp, BaseController, arc preset,
9
+ arc auth, arc events, arc jobs, arc websocket, arc mcp, arc plugin, arc testing, arc cli,
10
+ arc permissions, arc hooks, arc pipeline, arc factory, arc cache, arc QueryCache.
11
+ version: 2.4.2
12
+ license: MIT
13
+ metadata:
14
+ author: Classytic
15
+ version: "2.4.1"
16
+ tags:
17
+ - fastify
18
+ - rest-api
19
+ - resource-framework
20
+ - crud
21
+ - permissions
22
+ - multi-tenant
23
+ - presets
24
+ - typescript
25
+ - cache
26
+ - events
27
+ - openapi
28
+ progressive_disclosure:
29
+ entry_point:
30
+ summary: "Resource-oriented Fastify framework: defineResource(), presets, permissions, QueryCache, events, multi-tenant, OpenAPI"
31
+ when_to_use: "Building REST APIs with Fastify, resource CRUD, authentication, presets, caching, events, or production deployment"
32
+ quick_start: "1. npm install @classytic/arc fastify 2. createApp({ preset: 'production', auth: { type: 'jwt', jwt: { secret } } }) 3. defineResource({ name, adapter, presets, permissions })"
33
+ context_limit: 700
34
+ ---
35
+
36
+ # @classytic/arc
37
+
38
+ Resource-oriented backend framework for Fastify. Database-agnostic, tree-shakable, production-ready.
39
+
40
+ **Requires:** Fastify `^5.7.4` | Node.js `>=22` | ESM only
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ # Install the Arc agent skill (Claude Code / AI agents)
46
+ npx skills add classytic/arc
47
+
48
+ # Install the npm package
49
+ npm install @classytic/arc fastify
50
+ npm install @classytic/mongokit mongoose # MongoDB adapter
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```typescript
56
+ import { createApp } from '@classytic/arc/factory';
57
+ import mongoose from 'mongoose';
58
+
59
+ await mongoose.connect(process.env.DB_URI);
60
+
61
+ const app = await createApp({
62
+ preset: 'production',
63
+ auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } },
64
+ cors: { origin: process.env.ALLOWED_ORIGINS?.split(',') },
65
+ });
66
+
67
+ await app.register(productResource.toPlugin());
68
+ await app.listen({ port: 8040, host: '0.0.0.0' });
69
+ ```
70
+
71
+ ## defineResource()
72
+
73
+ Single API to define a full REST resource:
74
+
75
+ ```typescript
76
+ import { defineResource, createMongooseAdapter, allowPublic, requireRoles } from '@classytic/arc';
77
+
78
+ const productResource = defineResource({
79
+ name: 'product',
80
+ adapter: createMongooseAdapter({ model: ProductModel, repository: productRepo }),
81
+ controller: productController, // optional — auto-created if omitted
82
+ presets: ['softDelete', 'slugLookup', { name: 'multiTenant', tenantField: 'orgId' }],
83
+ permissions: {
84
+ list: allowPublic(),
85
+ get: allowPublic(),
86
+ create: requireRoles(['admin']),
87
+ update: requireRoles(['admin']),
88
+ delete: requireRoles(['admin']),
89
+ },
90
+ cache: { staleTime: 30, gcTime: 300, tags: ['catalog'] },
91
+ additionalRoutes: [
92
+ { method: 'GET', path: '/featured', handler: 'getFeatured', permissions: allowPublic(), wrapHandler: true },
93
+ ],
94
+ });
95
+
96
+ await fastify.register(productResource.toPlugin());
97
+ // Auto-generates: GET /, GET /:id, POST /, PATCH /:id, DELETE /:id
98
+ ```
99
+
100
+ ## Authentication
101
+
102
+ Auth uses a **discriminated union** with `type` field:
103
+
104
+ ```typescript
105
+ // Arc JWT (with optional token extraction and revocation)
106
+ auth: {
107
+ type: 'jwt',
108
+ jwt: { secret, expiresIn: '15m', refreshSecret, refreshExpiresIn: '7d' },
109
+ tokenExtractor: (req) => req.cookies?.['auth-token'] ?? null, // optional
110
+ isRevoked: async (decoded) => redis.sismember('revoked', decoded.jti), // optional
111
+ }
112
+
113
+ // Better Auth (recommended for SaaS with orgs)
114
+ import { createBetterAuthAdapter } from '@classytic/arc/auth';
115
+ auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth, orgContext: true }) }
116
+
117
+ // Custom Fastify plugin (must decorate fastify.authenticate)
118
+ auth: { type: 'custom', plugin: myAuthPlugin }
119
+
120
+ // Custom function (decorates fastify.authenticate directly)
121
+ auth: { type: 'authenticator', authenticate: async (req, reply) => { ... } }
122
+
123
+ // Disabled
124
+ auth: false
125
+ ```
126
+
127
+ **Decorates:** `app.authenticate`, `app.optionalAuthenticate`, `app.authorize`
128
+
129
+ ## Permissions
130
+
131
+ Function-based. A `PermissionCheck` returns `boolean | { granted, reason?, filters? }`:
132
+
133
+ ```typescript
134
+ import {
135
+ allowPublic, requireAuth, requireRoles, requireOwnership,
136
+ requireOrgMembership, requireOrgRole, requireTeamMembership,
137
+ allOf, anyOf, when, denyAll,
138
+ createDynamicPermissionMatrix,
139
+ } from '@classytic/arc';
140
+
141
+ permissions: {
142
+ list: allowPublic(),
143
+ get: requireAuth(),
144
+ create: requireRoles(['admin', 'editor']),
145
+ update: anyOf(requireOwnership('userId'), requireRoles(['admin'])),
146
+ delete: allOf(requireAuth(), requireRoles(['admin'])),
147
+ }
148
+ ```
149
+
150
+ **Custom permission:**
151
+
152
+ ```typescript
153
+ const requirePro = (): PermissionCheck => async (ctx) => {
154
+ if (!ctx.user) return { granted: false, reason: 'Auth required' };
155
+ return { granted: ctx.user.plan === 'pro' };
156
+ };
157
+ ```
158
+
159
+ **Field-level permissions:**
160
+
161
+ ```typescript
162
+ import { fields } from '@classytic/arc';
163
+
164
+ fields: {
165
+ password: fields.hidden(),
166
+ salary: fields.visibleTo(['admin', 'hr']),
167
+ role: fields.writableBy(['admin']),
168
+ email: fields.redactFor(['viewer'], '***'),
169
+ }
170
+ ```
171
+
172
+ **Dynamic ACL (DB-managed):**
173
+
174
+ ```typescript
175
+ const acl = createDynamicPermissionMatrix({
176
+ resolveRolePermissions: async (ctx) => aclService.getRoleMatrix(orgId),
177
+ cacheStore: new RedisCacheStore({ client: redis, prefix: 'acl:' }),
178
+ });
179
+ permissions: { list: acl.canAction('product', 'read') }
180
+ ```
181
+
182
+ ## Presets
183
+
184
+ | Preset | Routes Added | Controller Interface | Config |
185
+ |--------|-------------|---------------------|--------|
186
+ | `softDelete` | GET /deleted, POST /:id/restore | `ISoftDeleteController` | `{ deletedField }` |
187
+ | `slugLookup` | GET /slug/:slug | `ISlugLookupController` | `{ slugField }` |
188
+ | `tree` | GET /tree, GET /:parent/children | `ITreeController` | `{ parentField }` |
189
+ | `ownedByUser` | none (middleware) | — | `{ ownerField }` |
190
+ | `multiTenant` | none (middleware) | — | `{ tenantField }` |
191
+ | `audited` | none (middleware) | — | — |
192
+ | `bulk` | POST/PATCH/DELETE /bulk | — | `{ operations?, maxCreateItems? }` |
193
+
194
+ ```typescript
195
+ presets: ['softDelete', { name: 'multiTenant', tenantField: 'organizationId' }]
196
+ // Bulk: presets: ['bulk'] or bulkPreset({ operations: ['createMany', 'updateMany'] })
197
+ ```
198
+
199
+ ## QueryCache
200
+
201
+ TanStack Query-inspired server cache with stale-while-revalidate and auto-invalidation on mutations.
202
+
203
+ ```typescript
204
+ // Enable globally
205
+ const app = await createApp({ arcPlugins: { queryCache: true } });
206
+
207
+ // Per-resource config
208
+ defineResource({
209
+ name: 'product',
210
+ cache: {
211
+ staleTime: 30, // seconds fresh (no revalidation)
212
+ gcTime: 300, // seconds stale data kept (SWR window)
213
+ tags: ['catalog'], // cross-resource grouping
214
+ invalidateOn: { 'category.*': ['catalog'] }, // event pattern → tag targets
215
+ list: { staleTime: 60 }, // per-operation override
216
+ byId: { staleTime: 10 },
217
+ },
218
+ });
219
+ ```
220
+
221
+ **Auto-invalidation:** POST/PATCH/DELETE bumps resource version. Old cached queries expire naturally via TTL.
222
+
223
+ **Runtime modes:** `memory` (default, zero-config) | `distributed` (requires `stores.queryCache: RedisCacheStore`)
224
+
225
+ **Response header:** `x-cache: HIT | STALE | MISS`
226
+
227
+ ## BaseController
228
+
229
+ ```typescript
230
+ import { BaseController } from '@classytic/arc';
231
+ import type { IRequestContext, IControllerResponse } from '@classytic/arc';
232
+
233
+ class ProductController extends BaseController<Product> {
234
+ constructor() { super(productRepo); }
235
+
236
+ async getFeatured(req: IRequestContext): Promise<IControllerResponse> {
237
+ const products = await this.repository.getAll({ filters: { isFeatured: true } });
238
+ return { success: true, data: products };
239
+ }
240
+ }
241
+ ```
242
+
243
+ **IRequestContext:** `{ params, query, body, user, headers, context, metadata, server }` — `user` is `Record<string, unknown> | undefined` (guard with `if (req.user)` on public routes)
244
+
245
+ **IControllerResponse:** `{ success, data?, error?, status?, meta?, headers? }`
246
+
247
+ ## Adapters (Database-Agnostic)
248
+
249
+ ```typescript
250
+ // Mongoose
251
+ import { createMongooseAdapter } from '@classytic/arc';
252
+ const adapter = createMongooseAdapter({ model: ProductModel, repository: productRepo });
253
+
254
+ // Custom adapter — implement CrudRepository interface:
255
+ interface CrudRepository<TDoc> {
256
+ getAll(params?): Promise<TDoc[] | PaginatedResult<TDoc>>;
257
+ getById(id: string): Promise<TDoc | null>;
258
+ create(data): Promise<TDoc>;
259
+ update(id: string, data): Promise<TDoc | null>;
260
+ delete(id: string): Promise<boolean>;
261
+ }
262
+ ```
263
+
264
+ ## Events
265
+
266
+ The factory auto-registers `eventPlugin` — no manual setup needed:
267
+
268
+ ```typescript
269
+ // createApp() registers eventPlugin automatically (default: MemoryEventTransport)
270
+ const app = await createApp({
271
+ stores: { events: new RedisEventTransport(redis) }, // optional, defaults to memory
272
+ arcPlugins: {
273
+ events: { // event plugin config (default: true, false to disable)
274
+ logEvents: true,
275
+ retry: { maxRetries: 3, backoffMs: 1000 },
276
+ },
277
+ },
278
+ });
279
+
280
+ await app.events.publish('order.created', { orderId: '123' });
281
+ await app.events.subscribe('order.*', async (event) => { ... });
282
+ ```
283
+
284
+ CRUD events auto-emit: `{resource}.created`, `{resource}.updated`, `{resource}.deleted`.
285
+
286
+ **Transports:** Memory (default) | Redis Pub/Sub (fire-and-forget) | Redis Streams (durable, at-least-once, consumer groups, DLQ)
287
+
288
+ **Event Outbox** — at-least-once delivery via transactional outbox pattern. Arc ships `OutboxStore` interface + `MemoryOutboxStore` (dev). You implement the store for your DB (Mongoose, Drizzle, Knex, etc.). Cleanup via optional `purge()` contract or native DB tools (TTL index, pg_cron, key expiry).
289
+
290
+ ## Factory — createApp()
291
+
292
+ ```typescript
293
+ const app = await createApp({
294
+ preset: 'production', // production | development | testing | edge
295
+ runtime: 'memory', // memory (default) | distributed
296
+ auth: { type: 'jwt', jwt: { secret } },
297
+ cors: { origin: ['https://myapp.com'] },
298
+ helmet: true, // false to disable
299
+ rateLimit: { max: 100 }, // false to disable
300
+ ajv: { keywords: ['x-internal'] }, // custom AJV keywords for schema validation
301
+ arcPlugins: {
302
+ events: true, // event plugin (default: true, false to disable)
303
+ emitEvents: true, // CRUD event emission (default: true)
304
+ queryCache: true, // server cache (default: false)
305
+ sse: true, // SSE streaming (default: false)
306
+ caching: true, // ETag + Cache-Control (default: false)
307
+ },
308
+ stores: { // required when runtime: 'distributed'
309
+ events: new RedisEventTransport({ client: redis }),
310
+ queryCache: new RedisCacheStore({ client: redis }),
311
+ },
312
+ });
313
+ ```
314
+
315
+ ## Hooks
316
+
317
+ ```typescript
318
+ import { createHookSystem, beforeCreate, afterUpdate } from '@classytic/arc/hooks';
319
+
320
+ const hooks = createHookSystem();
321
+ beforeCreate(hooks, 'product', async (ctx) => { ctx.data.slug = slugify(ctx.data.name); });
322
+ afterUpdate(hooks, 'product', async (ctx) => { await invalidateCache(ctx.result._id); });
323
+ ```
324
+
325
+ ## Pipeline
326
+
327
+ ```typescript
328
+ import { guard, transform, intercept } from '@classytic/arc';
329
+
330
+ defineResource({
331
+ pipe: {
332
+ create: [
333
+ guard('verified', async (ctx) => ctx.user?.verified === true),
334
+ transform('inject', async (ctx) => { ctx.body.createdBy = ctx.user._id; }),
335
+ ],
336
+ },
337
+ });
338
+ ```
339
+
340
+ ## Query Parsing
341
+
342
+ Arc's default parser handles filters, sort, select, populate, and pagination. Swap in MongoKit's `QueryParser` for $lookup joins.
343
+
344
+ ```
345
+ GET /products?page=2&limit=20&sort=-createdAt&select=name,price
346
+ GET /products?price[gte]=100&status[in]=active,featured&search=keyword
347
+ GET /products?after=<cursor_id>&limit=20 # keyset pagination
348
+ GET /products?populate=category # ref-based populate
349
+ GET /products?populate[category][select]=name,slug # populate with field select
350
+ GET /products?populate[category][select]=-internal # exclude fields
351
+ GET /products?populate[category][match][isActive]=true # populate with filter
352
+ ```
353
+
354
+ **Lookup/join (no refs — $lookup via MongoKit QueryParser):**
355
+
356
+ ```
357
+ GET /products?lookup[cat][from]=categories&lookup[cat][localField]=categorySlug&lookup[cat][foreignField]=slug&lookup[cat][single]=true
358
+ GET /products?lookup[cat][from]=categories&...&lookup[cat][select]=name,slug
359
+ ```
360
+
361
+ Operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `like`, `regex`, `exists`
362
+
363
+ **Custom query parser (e.g., MongoKit for $lookup support):**
364
+
365
+ ```typescript
366
+ import { QueryParser } from '@classytic/mongokit';
367
+
368
+ defineResource({
369
+ name: 'product',
370
+ adapter: createMongooseAdapter({ model: ProductModel, repository: productRepo }),
371
+ queryParser: new QueryParser(), // enables lookup, advanced populate, keyset pagination
372
+ schemaOptions: {
373
+ query: {
374
+ allowedPopulate: ['category', 'brand'], // whitelist populate paths
375
+ allowedLookups: ['categories', 'brands'], // whitelist lookup collections
376
+ },
377
+ },
378
+ });
379
+ ```
380
+
381
+ ## Error Classes
382
+
383
+ ```typescript
384
+ import { ArcError, NotFoundError, ValidationError, UnauthorizedError, ForbiddenError } from '@classytic/arc';
385
+ throw new NotFoundError('Product not found'); // 404
386
+ ```
387
+
388
+ ## Compensating Transaction
389
+
390
+ In-process rollback for multi-step operations. Not a distributed saga — use Temporal/Streamline for that.
391
+
392
+ ```typescript
393
+ import { withCompensation } from '@classytic/arc/utils';
394
+
395
+ const result = await withCompensation('checkout', [
396
+ { name: 'reserve', execute: reserveStock, compensate: releaseStock },
397
+ { name: 'charge', execute: chargeCard, compensate: refundCard },
398
+ { name: 'notify', execute: sendEmail, fireAndForget: true }, // non-blocking
399
+ ], { orderId }, {
400
+ onStepComplete: (name, res) => fastify.events.publish(`checkout.${name}.done`, res),
401
+ });
402
+ // result: { success, completedSteps, results, failedStep?, error?, compensationErrors? }
403
+ ```
404
+
405
+ ## CLI
406
+
407
+ ```bash
408
+ arc init my-api --mongokit --better-auth --ts
409
+ arc generate resource product # standard resource
410
+ arc generate resource product --mcp # resource + MCP tools file
411
+ arc generate mcp analytics # standalone MCP tools file
412
+ arc docs ./openapi.json --entry ./dist/index.js
413
+ arc introspect --entry ./dist/index.js
414
+ arc doctor
415
+ ```
416
+
417
+ Set `"mcp": true` in `.arcrc` to always generate `.mcp.ts` files with resources.
418
+
419
+ ## MCP (AI Agent Tools)
420
+
421
+ Expose Arc resources as MCP tools for AI agents. Stateless by default — fresh server per request, zero session overhead.
422
+
423
+ See [mcp reference](references/mcp.md) for full details.
424
+
425
+ ```typescript
426
+ import { mcpPlugin } from '@classytic/arc/mcp';
427
+
428
+ // Stateless (default) — production-ready, scalable
429
+ await app.register(mcpPlugin, {
430
+ resources: [productResource, orderResource],
431
+ auth: false, // or: getAuth() | custom function
432
+ exclude: ['credential'],
433
+ overrides: { product: { operations: ['list', 'get'] } },
434
+ });
435
+
436
+ // Stateful — when you need server-initiated messages
437
+ await app.register(mcpPlugin, { resources, stateful: true, sessionTtlMs: 600000 });
438
+ ```
439
+
440
+ Connect Claude CLI: `claude mcp add --transport http my-api http://localhost:3000/mcp`
441
+
442
+ **Auth** — three modes, user chooses: `false` | `getAuth()` (Better Auth OAuth 2.1) | custom function:
443
+
444
+ ```typescript
445
+ auth: async (headers) => {
446
+ if (headers['x-api-key'] !== process.env.MCP_KEY) return null;
447
+ return { userId: 'bot', organizationId: 'org-1', roles: ['admin'] };
448
+ },
449
+ ```
450
+
451
+ **Guards** for custom tools: `guard(requireAuth, requireOrg, requireRole('admin'), handler)`
452
+
453
+ **Multi-tenancy**: `organizationId` from auth flows into BaseController org-scoping automatically.
454
+
455
+ **Permission filters**: `PermissionResult.filters` from resource permissions flow into MCP tools — same as REST. Define once, works everywhere:
456
+
457
+ ```typescript
458
+ permissions: {
459
+ list: (ctx) => ({
460
+ granted: !!ctx.user,
461
+ filters: { orgId: ctx.user?.orgId, branchId: ctx.user?.branchId },
462
+ }),
463
+ }
464
+ // MCP tools automatically scope queries by orgId + branchId
465
+ ```
466
+
467
+ **Project structure** — custom MCP tools co-located with resources:
468
+
469
+ ```
470
+ src/resources/order/
471
+ order.resource.ts
472
+ order.mcp.ts ← defineTool('fulfill_order', { ... })
473
+ ```
474
+
475
+ Generate: `arc generate resource order --mcp` | Wire: `extraTools: [fulfillOrderTool]`
476
+
477
+ ## Subpath Imports
478
+
479
+ ```typescript
480
+ import { defineResource, BaseController, allowPublic } from '@classytic/arc';
481
+ import { createApp } from '@classytic/arc/factory';
482
+ import { MemoryCacheStore, RedisCacheStore, QueryCache } from '@classytic/arc/cache';
483
+ import { createBetterAuthAdapter, extractBetterAuthOpenApi } from '@classytic/arc/auth';
484
+ import type { ExternalOpenApiPaths } from '@classytic/arc/docs';
485
+ import { eventPlugin } from '@classytic/arc/events';
486
+ import { RedisEventTransport } from '@classytic/arc/events/redis';
487
+ import { healthPlugin, gracefulShutdownPlugin } from '@classytic/arc/plugins';
488
+ import { tracingPlugin } from '@classytic/arc/plugins/tracing';
489
+ import { auditPlugin } from '@classytic/arc/audit';
490
+ import { idempotencyPlugin } from '@classytic/arc/idempotency';
491
+ import { ssePlugin } from '@classytic/arc/plugins';
492
+ import { jobsPlugin } from '@classytic/arc/integrations/jobs';
493
+ import { websocketPlugin } from '@classytic/arc/integrations/websocket';
494
+ import { eventGatewayPlugin } from '@classytic/arc/integrations/event-gateway';
495
+ import { createHookSystem } from '@classytic/arc/hooks';
496
+ import { createTestApp } from '@classytic/arc/testing';
497
+ import { Type, ArcListResponse } from '@classytic/arc/schemas';
498
+ import { createStateMachine, CircuitBreaker, withCompensation, defineCompensation } from '@classytic/arc/utils';
499
+ import { defineMigration } from '@classytic/arc/migrations';
500
+ import { isMember, isElevated, getOrgId, getUserId, getUserRoles } from '@classytic/arc/scope';
501
+ import { createTenantKeyGenerator } from '@classytic/arc/scope';
502
+ import { createRoleHierarchy } from '@classytic/arc/permissions';
503
+ import { createServiceClient } from '@classytic/arc/rpc';
504
+ import { metricsPlugin, versioningPlugin } from '@classytic/arc/plugins';
505
+ import { webhookPlugin } from '@classytic/arc/integrations/webhooks';
506
+ import { mcpPlugin, createMcpServer, defineTool, definePrompt, fieldRulesToZod, resourceToTools } from '@classytic/arc/mcp';
507
+ import { EventOutbox, MemoryOutboxStore } from '@classytic/arc/events';
508
+ import { bulkPreset } from '@classytic/arc/presets';
509
+ ```
510
+
511
+ ## References (Progressive Disclosure)
512
+
513
+ - **[auth](references/auth.md)** — JWT, Better Auth, API key auth, custom auth, multi-tenant
514
+ - **[events](references/events.md)** — Domain events, transports, retry, outbox pattern, auto-emission
515
+ - **[integrations](references/integrations.md)** — BullMQ jobs, WebSocket, EventGateway, Streamline, Webhooks
516
+ - **[mcp](references/mcp.md)** — MCP tools for AI agents, auto-generation from resources, custom tools, Better Auth OAuth 2.1
517
+ - **[production](references/production.md)** — Health, audit, idempotency, tracing, metrics, versioning, SSE, QueryCache, bulk ops, saga, RPC schema versioning, tenant rate limiting
518
+ - **[testing](references/testing.md)** — Test app, mocks, data factories, in-memory MongoDB