@classytic/arc 1.1.0 → 2.1.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 (322) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
  4. package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
  5. package/dist/HookSystem-BsGV-j2l.mjs +405 -0
  6. package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
  7. package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
  8. package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
  9. package/dist/adapters/index.d.mts +5 -0
  10. package/dist/adapters/index.mjs +3 -0
  11. package/dist/audit/index.d.mts +82 -0
  12. package/dist/audit/index.d.mts.map +1 -0
  13. package/dist/audit/index.mjs +276 -0
  14. package/dist/audit/index.mjs.map +1 -0
  15. package/dist/audit/mongodb.d.mts +5 -0
  16. package/dist/audit/mongodb.mjs +3 -0
  17. package/dist/audited-C3T5DTUx.mjs +141 -0
  18. package/dist/audited-C3T5DTUx.mjs.map +1 -0
  19. package/dist/auth/index.d.mts +189 -0
  20. package/dist/auth/index.d.mts.map +1 -0
  21. package/dist/auth/index.mjs +1102 -0
  22. package/dist/auth/index.mjs.map +1 -0
  23. package/dist/auth/redis-session.d.mts +44 -0
  24. package/dist/auth/redis-session.d.mts.map +1 -0
  25. package/dist/auth/redis-session.mjs +76 -0
  26. package/dist/auth/redis-session.mjs.map +1 -0
  27. package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
  28. package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
  29. package/dist/cache/index.d.mts +146 -0
  30. package/dist/cache/index.d.mts.map +1 -0
  31. package/dist/cache/index.mjs +92 -0
  32. package/dist/cache/index.mjs.map +1 -0
  33. package/dist/caching-Bl28lYsR.mjs +94 -0
  34. package/dist/caching-Bl28lYsR.mjs.map +1 -0
  35. package/dist/chunk-C7Uep-_p.mjs +20 -0
  36. package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
  37. package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
  38. package/dist/cli/commands/describe.d.mts +19 -0
  39. package/dist/cli/commands/describe.d.mts.map +1 -0
  40. package/dist/cli/commands/describe.mjs +239 -0
  41. package/dist/cli/commands/describe.mjs.map +1 -0
  42. package/dist/cli/commands/docs.d.mts +14 -0
  43. package/dist/cli/commands/docs.d.mts.map +1 -0
  44. package/dist/cli/commands/docs.mjs +53 -0
  45. package/dist/cli/commands/docs.mjs.map +1 -0
  46. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
  47. package/dist/cli/commands/generate.d.mts.map +1 -0
  48. package/dist/cli/commands/generate.mjs +358 -0
  49. package/dist/cli/commands/generate.mjs.map +1 -0
  50. package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
  51. package/dist/cli/commands/init.d.mts.map +1 -0
  52. package/dist/cli/commands/{init.js → init.mjs} +807 -616
  53. package/dist/cli/commands/init.mjs.map +1 -0
  54. package/dist/cli/commands/introspect.d.mts +11 -0
  55. package/dist/cli/commands/introspect.d.mts.map +1 -0
  56. package/dist/cli/commands/introspect.mjs +76 -0
  57. package/dist/cli/commands/introspect.mjs.map +1 -0
  58. package/dist/cli/index.d.mts +17 -0
  59. package/dist/cli/index.d.mts.map +1 -0
  60. package/dist/cli/index.mjs +157 -0
  61. package/dist/cli/index.mjs.map +1 -0
  62. package/dist/constants-DdXFXQtN.mjs +85 -0
  63. package/dist/constants-DdXFXQtN.mjs.map +1 -0
  64. package/dist/core/index.d.mts +5 -0
  65. package/dist/core/index.mjs +4 -0
  66. package/dist/createApp-CUgNqegw.mjs +560 -0
  67. package/dist/createApp-CUgNqegw.mjs.map +1 -0
  68. package/dist/defineResource-k0_BDn8v.mjs +2197 -0
  69. package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
  70. package/dist/discovery/index.d.mts +47 -0
  71. package/dist/discovery/index.d.mts.map +1 -0
  72. package/dist/discovery/index.mjs +110 -0
  73. package/dist/discovery/index.mjs.map +1 -0
  74. package/dist/docs/index.d.mts +163 -0
  75. package/dist/docs/index.d.mts.map +1 -0
  76. package/dist/docs/index.mjs +73 -0
  77. package/dist/docs/index.mjs.map +1 -0
  78. package/dist/elevation-BRy3yFWT.mjs +113 -0
  79. package/dist/elevation-BRy3yFWT.mjs.map +1 -0
  80. package/dist/elevation-B_2dRLVP.d.mts +88 -0
  81. package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
  82. package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
  83. package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
  84. package/dist/errorHandler-C1okiriz.mjs +109 -0
  85. package/dist/errorHandler-C1okiriz.mjs.map +1 -0
  86. package/dist/errors-B9bZok84.mjs +212 -0
  87. package/dist/errors-B9bZok84.mjs.map +1 -0
  88. package/dist/errors-ChKiFz62.d.mts +125 -0
  89. package/dist/errors-ChKiFz62.d.mts.map +1 -0
  90. package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
  91. package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
  92. package/dist/eventPlugin-DGR_B2on.mjs +230 -0
  93. package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
  94. package/dist/events/index.d.mts +54 -0
  95. package/dist/events/index.d.mts.map +1 -0
  96. package/dist/events/index.mjs +52 -0
  97. package/dist/events/index.mjs.map +1 -0
  98. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  99. package/dist/events/transports/redis-stream-entry.mjs +178 -0
  100. package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
  101. package/dist/events/transports/redis.d.mts +77 -0
  102. package/dist/events/transports/redis.d.mts.map +1 -0
  103. package/dist/events/transports/redis.mjs +125 -0
  104. package/dist/events/transports/redis.mjs.map +1 -0
  105. package/dist/externalPaths-DlINfKbP.d.mts +51 -0
  106. package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
  107. package/dist/factory/index.d.mts +64 -0
  108. package/dist/factory/index.d.mts.map +1 -0
  109. package/dist/factory/index.mjs +3 -0
  110. package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
  111. package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
  112. package/dist/fields-DyaDVX4J.d.mts +110 -0
  113. package/dist/fields-DyaDVX4J.d.mts.map +1 -0
  114. package/dist/fields-iagOozy0.mjs +115 -0
  115. package/dist/fields-iagOozy0.mjs.map +1 -0
  116. package/dist/hooks/index.d.mts +4 -0
  117. package/dist/hooks/index.mjs +3 -0
  118. package/dist/idempotency/index.d.mts +97 -0
  119. package/dist/idempotency/index.d.mts.map +1 -0
  120. package/dist/idempotency/index.mjs +320 -0
  121. package/dist/idempotency/index.mjs.map +1 -0
  122. package/dist/idempotency/mongodb.d.mts +2 -0
  123. package/dist/idempotency/mongodb.mjs +115 -0
  124. package/dist/idempotency/mongodb.mjs.map +1 -0
  125. package/dist/idempotency/redis.d.mts +2 -0
  126. package/dist/idempotency/redis.mjs +104 -0
  127. package/dist/idempotency/redis.mjs.map +1 -0
  128. package/dist/index.d.mts +261 -0
  129. package/dist/index.d.mts.map +1 -0
  130. package/dist/index.mjs +105 -0
  131. package/dist/index.mjs.map +1 -0
  132. package/dist/integrations/event-gateway.d.mts +47 -0
  133. package/dist/integrations/event-gateway.d.mts.map +1 -0
  134. package/dist/integrations/event-gateway.mjs +44 -0
  135. package/dist/integrations/event-gateway.mjs.map +1 -0
  136. package/dist/integrations/index.d.mts +5 -0
  137. package/dist/integrations/index.mjs +1 -0
  138. package/dist/integrations/jobs.d.mts +104 -0
  139. package/dist/integrations/jobs.d.mts.map +1 -0
  140. package/dist/integrations/jobs.mjs +124 -0
  141. package/dist/integrations/jobs.mjs.map +1 -0
  142. package/dist/integrations/streamline.d.mts +61 -0
  143. package/dist/integrations/streamline.d.mts.map +1 -0
  144. package/dist/integrations/streamline.mjs +126 -0
  145. package/dist/integrations/streamline.mjs.map +1 -0
  146. package/dist/integrations/websocket.d.mts +83 -0
  147. package/dist/integrations/websocket.d.mts.map +1 -0
  148. package/dist/integrations/websocket.mjs +289 -0
  149. package/dist/integrations/websocket.mjs.map +1 -0
  150. package/dist/interface-B01JvPVc.d.mts +78 -0
  151. package/dist/interface-B01JvPVc.d.mts.map +1 -0
  152. package/dist/interface-CZe8IkMf.d.mts +55 -0
  153. package/dist/interface-CZe8IkMf.d.mts.map +1 -0
  154. package/dist/interface-Ch8HU9uM.d.mts +1098 -0
  155. package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
  156. package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
  157. package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
  158. package/dist/keys-BqNejWup.mjs +43 -0
  159. package/dist/keys-BqNejWup.mjs.map +1 -0
  160. package/dist/logger-Df2O2WsW.mjs +79 -0
  161. package/dist/logger-Df2O2WsW.mjs.map +1 -0
  162. package/dist/memory-cQgelFOj.mjs +144 -0
  163. package/dist/memory-cQgelFOj.mjs.map +1 -0
  164. package/dist/migrations/index.d.mts +157 -0
  165. package/dist/migrations/index.d.mts.map +1 -0
  166. package/dist/migrations/index.mjs +261 -0
  167. package/dist/migrations/index.mjs.map +1 -0
  168. package/dist/mongodb-BfJVlUJH.mjs +94 -0
  169. package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
  170. package/dist/mongodb-CGzRbfAK.d.mts +119 -0
  171. package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
  172. package/dist/mongodb-JN-9JA7K.d.mts +72 -0
  173. package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
  174. package/dist/openapi-G3Cw7XuM.mjs +524 -0
  175. package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
  176. package/dist/org/index.d.mts +69 -0
  177. package/dist/org/index.d.mts.map +1 -0
  178. package/dist/org/index.mjs +514 -0
  179. package/dist/org/index.mjs.map +1 -0
  180. package/dist/org/types.d.mts +83 -0
  181. package/dist/org/types.d.mts.map +1 -0
  182. package/dist/org/types.mjs +1 -0
  183. package/dist/permissions/index.d.mts +279 -0
  184. package/dist/permissions/index.d.mts.map +1 -0
  185. package/dist/permissions/index.mjs +579 -0
  186. package/dist/permissions/index.mjs.map +1 -0
  187. package/dist/plugins/index.d.mts +173 -0
  188. package/dist/plugins/index.d.mts.map +1 -0
  189. package/dist/plugins/index.mjs +523 -0
  190. package/dist/plugins/index.mjs.map +1 -0
  191. package/dist/plugins/response-cache.d.mts +88 -0
  192. package/dist/plugins/response-cache.d.mts.map +1 -0
  193. package/dist/plugins/response-cache.mjs +284 -0
  194. package/dist/plugins/response-cache.mjs.map +1 -0
  195. package/dist/plugins/tracing-entry.d.mts +2 -0
  196. package/dist/plugins/tracing-entry.mjs +186 -0
  197. package/dist/plugins/tracing-entry.mjs.map +1 -0
  198. package/dist/pluralize-CEweyOEm.mjs +87 -0
  199. package/dist/pluralize-CEweyOEm.mjs.map +1 -0
  200. package/dist/policies/{index.d.ts → index.d.mts} +204 -169
  201. package/dist/policies/index.d.mts.map +1 -0
  202. package/dist/policies/index.mjs +322 -0
  203. package/dist/policies/index.mjs.map +1 -0
  204. package/dist/presets/{index.d.ts → index.d.mts} +63 -131
  205. package/dist/presets/index.d.mts.map +1 -0
  206. package/dist/presets/index.mjs +144 -0
  207. package/dist/presets/index.mjs.map +1 -0
  208. package/dist/presets/multiTenant.d.mts +25 -0
  209. package/dist/presets/multiTenant.d.mts.map +1 -0
  210. package/dist/presets/multiTenant.mjs +114 -0
  211. package/dist/presets/multiTenant.mjs.map +1 -0
  212. package/dist/presets-BITljm96.mjs +120 -0
  213. package/dist/presets-BITljm96.mjs.map +1 -0
  214. package/dist/presets-DzSMwlKj.d.mts +58 -0
  215. package/dist/presets-DzSMwlKj.d.mts.map +1 -0
  216. package/dist/prisma-DJbMt3yf.mjs +628 -0
  217. package/dist/prisma-DJbMt3yf.mjs.map +1 -0
  218. package/dist/prisma-Dg9GoVdj.d.mts +275 -0
  219. package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
  220. package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
  221. package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
  222. package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
  223. package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
  224. package/dist/redis-D-JAeLtm.d.mts +50 -0
  225. package/dist/redis-D-JAeLtm.d.mts.map +1 -0
  226. package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
  227. package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
  228. package/dist/registry/index.d.mts +12 -0
  229. package/dist/registry/index.d.mts.map +1 -0
  230. package/dist/registry/index.mjs +4 -0
  231. package/dist/requestContext-QQD6ROJc.mjs +56 -0
  232. package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
  233. package/dist/schemaConverter-BwrmWroW.mjs +99 -0
  234. package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
  235. package/dist/schemas/index.d.mts +64 -0
  236. package/dist/schemas/index.d.mts.map +1 -0
  237. package/dist/schemas/index.mjs +83 -0
  238. package/dist/schemas/index.mjs.map +1 -0
  239. package/dist/scope/index.d.mts +22 -0
  240. package/dist/scope/index.d.mts.map +1 -0
  241. package/dist/scope/index.mjs +66 -0
  242. package/dist/scope/index.mjs.map +1 -0
  243. package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
  244. package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
  245. package/dist/sse-B3c3_yZp.mjs +124 -0
  246. package/dist/sse-B3c3_yZp.mjs.map +1 -0
  247. package/dist/testing/index.d.mts +908 -0
  248. package/dist/testing/index.d.mts.map +1 -0
  249. package/dist/testing/index.mjs +1977 -0
  250. package/dist/testing/index.mjs.map +1 -0
  251. package/dist/tracing-Cc7vVQPp.d.mts +71 -0
  252. package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
  253. package/dist/typeGuards-DhMNLuvU.mjs +10 -0
  254. package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
  255. package/dist/types/index.d.mts +947 -0
  256. package/dist/types/index.d.mts.map +1 -0
  257. package/dist/types/index.mjs +15 -0
  258. package/dist/types/index.mjs.map +1 -0
  259. package/dist/types-Beqn1Un7.mjs +39 -0
  260. package/dist/types-Beqn1Un7.mjs.map +1 -0
  261. package/dist/types-CIgB7UUl.d.mts +446 -0
  262. package/dist/types-CIgB7UUl.d.mts.map +1 -0
  263. package/dist/types-aYB4V7uN.d.mts +87 -0
  264. package/dist/types-aYB4V7uN.d.mts.map +1 -0
  265. package/dist/utils/index.d.mts +748 -0
  266. package/dist/utils/index.d.mts.map +1 -0
  267. package/dist/utils/index.mjs +6 -0
  268. package/package.json +194 -68
  269. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  270. package/dist/adapters/index.d.ts +0 -237
  271. package/dist/adapters/index.js +0 -668
  272. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  273. package/dist/audit/index.d.ts +0 -195
  274. package/dist/audit/index.js +0 -319
  275. package/dist/auth/index.d.ts +0 -47
  276. package/dist/auth/index.js +0 -174
  277. package/dist/cli/commands/docs.d.ts +0 -11
  278. package/dist/cli/commands/docs.js +0 -474
  279. package/dist/cli/commands/generate.js +0 -334
  280. package/dist/cli/commands/introspect.d.ts +0 -8
  281. package/dist/cli/commands/introspect.js +0 -338
  282. package/dist/cli/index.d.ts +0 -4
  283. package/dist/cli/index.js +0 -3269
  284. package/dist/core/index.d.ts +0 -220
  285. package/dist/core/index.js +0 -2786
  286. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  287. package/dist/docs/index.d.ts +0 -166
  288. package/dist/docs/index.js +0 -658
  289. package/dist/errors-8WIxGS_6.d.ts +0 -122
  290. package/dist/events/index.d.ts +0 -117
  291. package/dist/events/index.js +0 -89
  292. package/dist/factory/index.d.ts +0 -38
  293. package/dist/factory/index.js +0 -1652
  294. package/dist/hooks/index.d.ts +0 -4
  295. package/dist/hooks/index.js +0 -199
  296. package/dist/idempotency/index.d.ts +0 -323
  297. package/dist/idempotency/index.js +0 -500
  298. package/dist/index-B4t03KQ0.d.ts +0 -1366
  299. package/dist/index.d.ts +0 -135
  300. package/dist/index.js +0 -4756
  301. package/dist/migrations/index.d.ts +0 -185
  302. package/dist/migrations/index.js +0 -274
  303. package/dist/org/index.d.ts +0 -129
  304. package/dist/org/index.js +0 -220
  305. package/dist/permissions/index.d.ts +0 -144
  306. package/dist/permissions/index.js +0 -103
  307. package/dist/plugins/index.d.ts +0 -46
  308. package/dist/plugins/index.js +0 -1069
  309. package/dist/policies/index.js +0 -196
  310. package/dist/presets/index.js +0 -384
  311. package/dist/presets/multiTenant.d.ts +0 -39
  312. package/dist/presets/multiTenant.js +0 -112
  313. package/dist/registry/index.d.ts +0 -16
  314. package/dist/registry/index.js +0 -253
  315. package/dist/testing/index.d.ts +0 -618
  316. package/dist/testing/index.js +0 -48020
  317. package/dist/types/index.d.ts +0 -4
  318. package/dist/types/index.js +0 -8
  319. package/dist/types-B99TBmFV.d.ts +0 -76
  320. package/dist/types-BvckRbs2.d.ts +0 -143
  321. package/dist/utils/index.d.ts +0 -679
  322. package/dist/utils/index.js +0 -931
package/README.md CHANGED
@@ -1,162 +1,45 @@
1
1
  # @classytic/arc
2
2
 
3
- **Database-agnostic resource framework for Fastify**
3
+ Database-agnostic resource framework for Fastify. Define resources, get CRUD routes, permissions, presets, caching, events, and OpenAPI — without boilerplate.
4
4
 
5
- *Think Rails conventions, Django REST Framework patterns, Laravel's Eloquent — but for Fastify.*
5
+ **Requires:** Fastify 5+ | Node.js 22+ | ESM only
6
6
 
7
- Arc provides routing, permissions, and resource patterns. **You choose the database:**
8
- - **MongoDB** → `npm install @classytic/mongokit`
9
- - **PostgreSQL/MySQL/SQLite** → `@classytic/prismakit` (coming soon)
10
-
11
- > **⚠️ ESM Only**: Arc requires Node.js 18+ with ES modules (`"type": "module"` in package.json). CommonJS is not supported. [Migration guide →](https://nodejs.org/api/esm.html)
12
-
13
- ---
14
-
15
- ## Why Arc?
16
-
17
- Building REST APIs in Node.js often means making hundreds of small decisions: How do I structure routes? Where does validation go? How do I handle soft deletes consistently? What about multi-tenant isolation?
18
-
19
- **Arc gives you conventions so you can focus on your domain, not boilerplate.**
20
-
21
- | Without Arc | With Arc |
22
- |-------------|----------|
23
- | Write CRUD routes for every model | `defineResource()` generates them |
24
- | Manually wire controllers to routes | Convention-based auto-wiring |
25
- | Copy-paste soft delete logic | `presets: ['softDelete']` |
26
- | Manually filter by tenant on every query | `presets: ['multiTenant']` auto-filters |
27
- | Hand-roll OpenAPI specs | Auto-generated from resources |
28
-
29
- **Arc is opinionated where it matters, flexible where you need it.**
30
-
31
- ---
32
-
33
- ## Installation
7
+ ## Install
34
8
 
35
9
  ```bash
36
- # Core framework
37
- npm install @classytic/arc
38
-
39
- # Choose your database kit:
40
- npm install @classytic/mongokit # MongoDB/Mongoose
41
- # npm install @classytic/prismakit # PostgreSQL/MySQL/SQLite (coming soon)
10
+ npm install @classytic/arc fastify
11
+ npm install @classytic/mongokit mongoose # MongoDB adapter
42
12
  ```
43
13
 
44
- ### Optional Dependencies
45
-
46
- Arc's security and utility plugins are opt-in via peer dependencies. Install only what you need:
47
-
48
- ```bash
49
- # Security plugins (recommended for production)
50
- npm install @fastify/helmet @fastify/cors @fastify/rate-limit
51
-
52
- # Performance plugins
53
- npm install @fastify/under-pressure
54
-
55
- # Utility plugins
56
- npm install @fastify/sensible @fastify/multipart fastify-raw-body
57
-
58
- # Development logging
59
- npm install pino-pretty
60
- ```
61
-
62
- Or disable plugins you don't need:
63
- ```typescript
64
- createApp({
65
- helmet: false, // Disable if not needed
66
- rateLimit: false, // Disable if not needed
67
- // ...
68
- })
69
- ```
70
-
71
- ## Key Features
72
-
73
- - **Resource-First Architecture** — Define your API as resources with `defineResource()`, not scattered route handlers
74
- - **Presets System** — Composable behaviors like `softDelete`, `slugLookup`, `tree`, `ownedByUser`, `multiTenant`
75
- - **Auto-Generated OpenAPI** — Documentation that stays in sync with your code
76
- - **Database-Agnostic Core** — Works with any database via adapters. MongoDB/Mongoose optimized out of the box, extensible to Prisma, Drizzle, TypeORM, etc.
77
- - **Production Defaults** — Helmet, CORS, rate limiting enabled by default
78
- - **CLI Tooling** — `arc generate resource` scaffolds new resources instantly
79
- - **Environment Presets** — Development, production, and testing configs built-in
80
- - **Type-Safe Presets** — TypeScript interfaces ensure controller methods match preset requirements
81
- - **Ultra-Fast Testing** — In-memory MongoDB support for 10x faster tests
82
-
83
14
  ## Quick Start
84
15
 
85
- ### Using ArcFactory (Recommended)
86
-
87
16
  ```typescript
88
17
  import mongoose from 'mongoose';
89
18
  import { createApp } from '@classytic/arc/factory';
90
- import { productResource } from './resources/product.js';
91
- import config from './config/index.js';
92
19
 
93
- // 1. Connect your database (Arc is database-agnostic)
94
- await mongoose.connect(config.db.uri);
20
+ await mongoose.connect(process.env.DB_URI);
95
21
 
96
- // 2. Create Arc app
97
22
  const app = await createApp({
98
- preset: 'production', // or 'development', 'testing'
99
- auth: { jwt: { secret: config.app.jwtSecret } },
100
- cors: { origin: config.cors.origin },
101
-
102
- // Opt-out security (all enabled by default)
103
- helmet: true, // Set false to disable
104
- rateLimit: true, // Set false to disable
105
- underPressure: true, // Set false to disable
23
+ preset: 'production',
24
+ auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } },
25
+ cors: { origin: process.env.ALLOWED_ORIGINS?.split(',') },
106
26
  });
107
27
 
108
- // 3. Register your resources
109
28
  await app.register(productResource.toPlugin());
110
-
111
29
  await app.listen({ port: 8040, host: '0.0.0.0' });
112
30
  ```
113
31
 
114
- ### Multiple Databases
32
+ ## defineResource
115
33
 
116
- Arc's adapter pattern lets you connect to multiple databases:
34
+ Single API for a full REST resource with routes, permissions, and behaviors:
117
35
 
118
36
  ```typescript
119
- import mongoose from 'mongoose';
120
-
121
- // Connect to multiple databases
122
- const primaryDb = await mongoose.connect(process.env.PRIMARY_DB);
123
- const analyticsDb = mongoose.createConnection(process.env.ANALYTICS_DB);
124
-
125
- // Each resource uses its own adapter
126
- const orderResource = defineResource({
127
- name: 'order',
128
- adapter: createMongooseAdapter({ model: OrderModel, repository: orderRepo }),
129
- });
130
-
131
- const analyticsResource = defineResource({
132
- name: 'analytics',
133
- adapter: createMongooseAdapter({ model: AnalyticsModel, repository: analyticsRepo }),
134
- });
135
- ```
136
-
137
- ### Manual Setup
138
-
139
- ```typescript
140
- import Fastify from 'fastify';
141
- import mongoose from 'mongoose';
142
- import { defineResource, createMongooseAdapter } from '@classytic/arc';
143
-
144
- // Connect your database
145
- await mongoose.connect('mongodb://localhost:27017/myapp');
146
-
147
- const fastify = Fastify();
148
-
149
- // Define and register resources
150
- import { allowPublic, requireRoles } from '@classytic/arc';
37
+ import { defineResource, createMongooseAdapter, allowPublic, requireRoles } from '@classytic/arc';
151
38
 
152
39
  const productResource = defineResource({
153
40
  name: 'product',
154
- adapter: createMongooseAdapter({
155
- model: ProductModel,
156
- repository: productRepository,
157
- }),
158
- controller: productController, // optional; auto-created if omitted
159
- presets: ['softDelete', 'slugLookup'],
41
+ adapter: createMongooseAdapter({ model: ProductModel, repository: productRepo }),
42
+ presets: ['softDelete', 'slugLookup', { name: 'multiTenant', tenantField: 'orgId' }],
160
43
  permissions: {
161
44
  list: allowPublic(),
162
45
  get: allowPublic(),
@@ -164,766 +47,336 @@ const productResource = defineResource({
164
47
  update: requireRoles(['admin']),
165
48
  delete: requireRoles(['admin']),
166
49
  },
167
- });
168
-
169
- await fastify.register(productResource.toPlugin());
170
- ```
171
-
172
- ## Core Concepts
173
-
174
- ### Authentication
175
-
176
- Arc provides **optional** built-in JWT authentication. You can:
177
-
178
- 1. **Use Arc's JWT auth** (default) - Simple, production-ready
179
- 2. **Replace with OAuth** - Google, Facebook, GitHub, etc.
180
- 3. **Use Passport.js** - 500+ authentication strategies
181
- 4. **Create custom auth** - Full control over authentication logic
182
- 5. **Mix multiple strategies** - JWT + API keys + OAuth
183
-
184
- **Arc's auth is NOT mandatory.** Disable it and use any Fastify auth plugin:
185
-
186
- ```typescript
187
- import { createApp } from '@classytic/arc';
188
-
189
- // Disable Arc's JWT auth
190
- const app = await createApp({
191
- auth: false, // Use your own auth strategy
192
- });
193
-
194
- // Use @fastify/oauth2 for Google login
195
- await app.register(require('@fastify/oauth2'), {
196
- name: 'googleOAuth',
197
- credentials: {
198
- client: {
199
- id: process.env.GOOGLE_CLIENT_ID,
200
- secret: process.env.GOOGLE_CLIENT_SECRET,
201
- },
202
- auth: {
203
- authorizeHost: 'https://accounts.google.com',
204
- authorizePath: '/o/oauth2/v2/auth',
205
- tokenHost: 'https://www.googleapis.com',
206
- tokenPath: '/oauth2/v4/token',
207
- },
208
- },
209
- startRedirectPath: '/auth/google',
210
- callbackUri: 'http://localhost:8080/auth/google/callback',
211
- scope: ['profile', 'email'],
212
- });
213
-
214
- // OAuth callback - issue JWT
215
- app.get('/auth/google/callback', async (request, reply) => {
216
- const { token } = await app.googleOAuth.getAccessTokenFromAuthorizationCodeFlow(request);
217
-
218
- // Fetch user info from Google
219
- const userInfo = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
220
- headers: { Authorization: `Bearer ${token.access_token}` },
221
- }).then(r => r.json());
222
-
223
- // Create user in your database
224
- const user = await User.findOneAndUpdate(
225
- { email: userInfo.email },
226
- { email: userInfo.email, name: userInfo.name, googleId: userInfo.id },
227
- { upsert: true, new: true }
228
- );
229
-
230
- // Issue JWT using Arc's auth (or use sessions/cookies)
231
- const jwtToken = app.jwt.sign({ _id: user._id, email: user.email });
232
-
233
- return reply.send({ token: jwtToken, user });
234
- });
235
- ```
236
-
237
- **See [examples/custom-auth-providers.ts](examples/custom-auth-providers.ts) for:**
238
- - OAuth (Google, Facebook)
239
- - Passport.js integration
240
- - Custom authentication strategies
241
- - SAML/SSO for enterprise
242
- - Hybrid auth (JWT + API keys)
243
-
244
- ### Resources
245
-
246
- A resource encapsulates model, repository, controller, and routes:
247
-
248
- ```typescript
249
- import { defineResource, createMongooseAdapter, allowPublic, requireRoles } from '@classytic/arc';
250
-
251
- export default defineResource({
252
- name: 'product',
253
- adapter: createMongooseAdapter({
254
- model: ProductModel,
255
- repository: productRepository,
256
- }),
257
- controller: productController,
258
-
259
- // Presets add common functionality
260
- presets: [
261
- 'softDelete', // deletedAt field, restore endpoint
262
- 'slugLookup', // GET /products/:slug
263
- 'ownedByUser', // createdBy ownership checks
264
- 'multiTenant', // organizationId isolation
265
- 'tree', // Hierarchical data support
266
- ],
267
-
268
- // Permission functions (NOT string arrays)
269
- permissions: {
270
- list: allowPublic(), // Public
271
- get: allowPublic(), // Public
272
- create: requireRoles(['admin', 'editor']), // Restricted
273
- update: requireRoles(['admin', 'editor']),
274
- delete: requireRoles(['admin']),
275
- },
276
-
277
- // Custom routes beyond CRUD
50
+ cache: { staleTime: 30, gcTime: 300, tags: ['catalog'] }, // QueryCache (opt-in)
278
51
  additionalRoutes: [
279
- {
280
- method: 'GET',
281
- path: '/featured',
282
- handler: 'getFeatured', // Controller method name
283
- permissions: allowPublic(), // Permission function
284
- wrapHandler: true, // Arc context pattern (IRequestContext)
285
- },
286
- {
287
- method: 'GET',
288
- path: '/:id/download',
289
- handler: 'downloadFile', // Fastify native handler
290
- permissions: requireAuth(),
291
- wrapHandler: false, // Native Fastify (request, reply)
292
- },
52
+ { method: 'GET', path: '/featured', handler: 'getFeatured', permissions: allowPublic(), wrapHandler: true },
293
53
  ],
294
54
  });
295
- ```
296
-
297
- ### Controllers
298
-
299
- Extend BaseController for built-in security and CRUD:
300
-
301
- ```typescript
302
- import { BaseController } from '@classytic/arc';
303
- import type { IRequestContext, IControllerResponse } from '@classytic/arc';
304
- import type { ISoftDeleteController, ISlugLookupController } from '@classytic/arc/presets';
305
-
306
- // Type-safe controller with preset interfaces
307
- class ProductController
308
- extends BaseController<Product>
309
- implements ISoftDeleteController<Product>, ISlugLookupController<Product>
310
- {
311
- constructor() {
312
- super(productRepository);
313
- }
314
-
315
- // Custom method - Arc context pattern
316
- async getFeatured(req: IRequestContext): Promise<IControllerResponse> {
317
- const { organizationId } = req;
318
-
319
- const products = await this.repository.findAll({
320
- filter: { isFeatured: true, organizationId },
321
- });
322
55
 
323
- return { success: true, data: products };
324
- }
325
-
326
- // Preset methods
327
- async getBySlug(req: IRequestContext): Promise<IControllerResponse> {
328
- const { slug } = req.params;
329
- const product = await this.repository.getBySlug(slug);
330
-
331
- if (!product) {
332
- return { success: false, error: 'Product not found', status: 404 };
333
- }
334
-
335
- return { success: true, data: product };
336
- }
337
- }
338
- ```
339
-
340
- **Preset Type Interfaces:** Arc exports TypeScript interfaces for each preset that requires controller methods:
341
-
342
- - `ISoftDeleteController` - requires `getDeleted()` and `restore()`
343
- - `ISlugLookupController` - requires `getBySlug()`
344
- - `ITreeController` - requires `getTree()` and `getChildren()`
345
-
346
- **Note:** Presets like `multiTenant`, `ownedByUser`, and `audited` don't require controller methods—they work via middleware.
347
-
348
- ### Request Context API
349
-
350
- Controller methods receive `req: IRequestContext`:
351
-
352
- ```typescript
353
- interface IRequestContext {
354
- params: Record<string, string>; // Route params: /users/:id
355
- query: Record<string, unknown>; // Query string: ?page=1
356
- body: unknown; // Request body
357
- user: UserBase | null; // Authenticated user
358
- headers: Record<string, string | undefined>; // Request headers
359
- organizationId?: string; // Multi-tenant org ID
360
- metadata?: Record<string, unknown>; // Custom data, _policyFilters, middleware context
361
- }
56
+ // Auto-generates: GET /, GET /:id, POST /, PATCH /:id, DELETE /:id
57
+ // Plus preset routes: GET /deleted, POST /:id/restore, GET /slug/:slug
362
58
  ```
363
59
 
364
- **Key Fields:**
365
- - `req.metadata` - Custom data from hooks, policies, or middleware
366
- - `req.organizationId` - Set by `multiTenant` preset or org scope plugin
367
- - `req.user` - Set by auth plugin, preserves original auth structure
368
-
369
- ### TypeScript Strict Mode
60
+ ## Authentication
370
61
 
371
- For maximum type safety:
62
+ Auth uses a discriminated union — pick a `type`:
372
63
 
373
64
  ```typescript
374
- import { BaseController, IRequestContext, IControllerResponse } from '@classytic/arc';
375
- import type { ISoftDeleteController, ISlugLookupController } from '@classytic/arc/presets';
376
-
377
- interface Product {
378
- _id: string;
379
- name: string;
380
- slug: string;
381
- price: number;
382
- deletedAt?: Date;
383
- }
384
-
385
- class ProductController
386
- extends BaseController<Product>
387
- implements ISoftDeleteController<Product>, ISlugLookupController<Product>
388
- {
389
- async getBySlug(req: IRequestContext): Promise<IControllerResponse<Product>> {
390
- const { slug } = req.params;
391
- const product = await this.repository.getBySlug(slug);
65
+ // Arc JWT
66
+ auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET, expiresIn: '15m' } }
392
67
 
393
- if (!product) {
394
- return { success: false, error: 'Product not found', status: 404 };
395
- }
68
+ // Better Auth (recommended for SaaS with orgs)
69
+ import { createBetterAuthAdapter } from '@classytic/arc/auth';
70
+ auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth, orgContext: true }) }
396
71
 
397
- return { success: true, data: product };
398
- }
72
+ // Custom plugin
73
+ auth: { type: 'custom', plugin: myAuthPlugin }
399
74
 
400
- async getDeleted(req: IRequestContext): Promise<IControllerResponse<Product[]>> {
401
- const products = await this.repository.findDeleted();
402
- return { success: true, data: products };
403
- }
75
+ // Custom function
76
+ auth: { type: 'authenticator', authenticate: async (req, reply) => { ... } }
404
77
 
405
- async restore(req: IRequestContext): Promise<IControllerResponse<Product>> {
406
- const { id } = req.params;
407
- const product = await this.repository.restore(id);
408
- return { success: true, data: product };
409
- }
410
- }
78
+ // Disabled
79
+ auth: false
411
80
  ```
412
81
 
413
- **Benefits:**
414
- - Compile-time type checking
415
- - IntelliSense autocomplete
416
- - Safe refactoring
82
+ **Decorates:** `app.authenticate`, `app.optionalAuthenticate`, `app.authorize`
417
83
 
418
- ### Repositories
84
+ ## Permissions
419
85
 
420
- Repositories come from your chosen database kit (Arc is database-agnostic):
86
+ Function-based, composable:
421
87
 
422
- **MongoDB with MongoKit:**
423
88
  ```typescript
424
- import { Repository, softDeletePlugin } from '@classytic/mongokit';
425
-
426
- class ProductRepository extends Repository {
427
- constructor() {
428
- super(ProductModel, [softDeletePlugin()]);
429
- }
430
-
431
- async getBySlug(slug) {
432
- return this.Model.findOne({ slug }).lean();
433
- }
89
+ import {
90
+ allowPublic, requireAuth, requireRoles, requireOwnership,
91
+ requireOrgMembership, requireOrgRole, allOf, anyOf, denyAll,
92
+ createDynamicPermissionMatrix,
93
+ } from '@classytic/arc';
94
+
95
+ permissions: {
96
+ list: allowPublic(),
97
+ get: requireAuth(),
98
+ create: requireRoles(['admin', 'editor']),
99
+ update: anyOf(requireOwnership('userId'), requireRoles(['admin'])),
100
+ delete: allOf(requireAuth(), requireRoles(['admin'])),
434
101
  }
435
102
  ```
436
103
 
437
- **Prisma (coming soon):**
438
- ```typescript
439
- import { PrismaRepository } from '@classytic/prismakit';
440
-
441
- class ProductRepository extends PrismaRepository {
442
- // Same interface, different database
443
- }
444
- ```
445
-
446
- ## CLI Commands
447
-
448
- ```bash
449
- # Generate resource scaffold
450
- arc generate resource product --module catalog --presets softDelete,slugLookup
451
-
452
- # Show all registered resources (loads from entry file)
453
- arc introspect --entry ./src/index.js
454
-
455
- # Export OpenAPI spec (loads from entry file)
456
- arc docs ./docs/openapi.json --entry ./src/index.js
457
-
458
- # Note: --entry flag loads your resource definitions into the registry
459
- # Point it to the file that imports all your resources
460
- ```
461
-
462
- ## Environment Presets
463
-
464
- ### Production
465
- - Info-level logging
466
- - Strict CORS (must configure origin)
467
- - Rate limiting: **100 req/min/IP** (configurable via `rateLimit.max` option)
468
- - Helmet with CSP
469
- - Health monitoring (under-pressure)
470
- - All security plugins enabled
471
-
472
- > **💡 Tip**: Default rate limit (100 req/min) may be conservative for high-traffic APIs. Adjust via:
473
- > ```typescript
474
- > createApp({ rateLimit: { max: 300, timeWindow: '1 minute' } })
475
- > ```
476
-
477
- > **Note**: Compression is not included due to known Fastify 5 stream issues. Use a reverse proxy (Nginx, Caddy) or CDN for response compression.
478
-
479
- ### Development
480
- - Debug logging
481
- - Permissive CORS
482
- - Rate limiting: 1000 req/min (development-friendly)
483
- - Relaxed security
484
-
485
- ### Testing
486
- - Silent logging
487
- - No CORS restrictions
488
- - Rate limiting: disabled (test performance)
489
- - Minimal security overhead
490
-
491
- ## Serverless Deployment
492
-
493
- ### AWS Lambda
494
-
495
- ```typescript
496
- import { createLambdaHandler } from './index.factory.js';
497
-
498
- export const handler = await createLambdaHandler();
499
- ```
500
-
501
- ### Google Cloud Run
104
+ **Field-level permissions:**
502
105
 
503
106
  ```typescript
504
- import { cloudRunHandler } from './index.factory.js';
505
- import { createServer } from 'http';
506
-
507
- createServer(cloudRunHandler).listen(process.env.PORT || 8080);
508
- ```
107
+ import { fields } from '@classytic/arc';
509
108
 
510
- ### Vercel
511
-
512
- ```typescript
513
- import { vercelHandler } from './index.factory.js';
514
-
515
- export default vercelHandler;
109
+ fields: {
110
+ password: fields.hidden(),
111
+ salary: fields.visibleTo(['admin', 'hr']),
112
+ role: fields.writableBy(['admin']),
113
+ email: fields.redactFor(['viewer'], '***'),
114
+ }
516
115
  ```
517
116
 
518
- ## Testing Utilities
519
-
520
- ### Test App Creation with In-Memory MongoDB
521
-
522
- Arc's testing utilities now include **in-memory MongoDB by default** for 10x faster tests.
117
+ **Dynamic ACL (DB-managed):**
523
118
 
524
119
  ```typescript
525
- import { createTestApp } from '@classytic/arc/testing';
526
- import type { TestAppResult } from '@classytic/arc/testing';
527
-
528
- describe('API Tests', () => {
529
- let testApp: TestAppResult;
530
-
531
- beforeAll(async () => {
532
- // Creates app + starts in-memory MongoDB automatically
533
- testApp = await createTestApp({
534
- auth: { jwt: { secret: 'test-secret-32-chars-minimum-len' } },
535
- });
536
-
537
- // Connect your models to the in-memory DB
538
- await mongoose.connect(testApp.mongoUri);
539
- });
540
-
541
- afterAll(async () => {
542
- // Cleans up DB and closes app
543
- await testApp.close();
544
- });
545
-
546
- test('GET /products', async () => {
547
- const response = await testApp.app.inject({
548
- method: 'GET',
549
- url: '/products',
550
- });
551
- expect(response.statusCode).toBe(200);
552
- });
120
+ const acl = createDynamicPermissionMatrix({
121
+ resolveRolePermissions: async (ctx) => aclService.getRoleMatrix(orgId),
122
+ cacheStore: new RedisCacheStore({ client: redis, prefix: 'acl:' }),
553
123
  });
554
- ```
555
124
 
556
- **Performance:** In-memory MongoDB requires `mongodb-memory-server` (dev dependency). Tests run 10x faster than external MongoDB.
557
-
558
- ```bash
559
- npm install -D mongodb-memory-server
560
- ```
561
-
562
- **Using External MongoDB:**
563
-
564
- ```typescript
565
- const testApp = await createTestApp({
566
- auth: { jwt: { secret: 'test-secret-32-chars-minimum-len' } },
567
- useInMemoryDb: false,
568
- mongoUri: 'mongodb://localhost:27017/test-db',
569
- });
125
+ permissions: {
126
+ list: acl.canAction('product', 'read'),
127
+ create: acl.canAction('product', 'create'),
128
+ }
570
129
  ```
571
130
 
572
- **Note:** Arc's testing preset disables security plugins for faster tests.
573
-
574
- ### Mock Factories
575
-
576
- ```typescript
577
- import { createMockRepository, createDataFactory } from '@classytic/arc/testing';
578
-
579
- // Mock repository
580
- const mockRepo = createMockRepository({
581
- findById: jest.fn().mockResolvedValue({ _id: '123', name: 'Test' }),
582
- });
583
-
584
- // Data factory
585
- const productFactory = createDataFactory({
586
- name: (i) => `Product ${i}`,
587
- price: (i) => 100 + i * 10,
588
- isActive: () => true,
589
- });
131
+ ## Presets
590
132
 
591
- const products = productFactory.buildMany(5);
592
- ```
133
+ Composable resource behaviors:
593
134
 
594
- ### Database Helpers
135
+ | Preset | Effect | Config |
136
+ |--------|--------|--------|
137
+ | `softDelete` | `GET /deleted`, `POST /:id/restore`, `deletedAt` field | `{ deletedField }` |
138
+ | `slugLookup` | `GET /slug/:slug` | `{ slugField }` |
139
+ | `tree` | `GET /tree`, `GET /:parent/children` | `{ parentField }` |
140
+ | `ownedByUser` | Auto-checks `createdBy` on update/delete | `{ ownerField }` |
141
+ | `multiTenant` | Auto-filters all queries by tenant | `{ tenantField }` |
142
+ | `audited` | Sets `createdBy`/`updatedBy` from user | — |
595
143
 
596
144
  ```typescript
597
- import { withTestDb } from '@classytic/arc/testing';
598
-
599
- describe('Product Repository', () => {
600
- withTestDb((db) => {
601
- it('should create product', async () => {
602
- const product = await Product.create({ name: 'Test' });
603
- expect(product.name).toBe('Test');
604
- });
605
- });
606
- });
145
+ presets: ['softDelete', { name: 'multiTenant', tenantField: 'organizationId' }]
607
146
  ```
608
147
 
609
- ## State Machine
148
+ ## QueryCache
610
149
 
611
- ```typescript
612
- import { createStateMachine } from '@classytic/arc/utils';
613
-
614
- const orderStateMachine = createStateMachine('order', {
615
- submit: {
616
- from: ['draft'],
617
- to: 'pending',
618
- guard: ({ data }) => data.items.length > 0,
619
- after: async ({ from, to, data }) => {
620
- await sendNotification(data.userId, 'Order submitted');
621
- },
622
- },
623
- approve: {
624
- from: ['pending'],
625
- to: 'approved',
626
- },
627
- ship: {
628
- from: ['approved'],
629
- to: 'shipped',
630
- },
631
- cancel: {
632
- from: ['draft', 'pending'],
633
- to: 'cancelled',
634
- },
635
- }, { trackHistory: true });
636
-
637
- // Usage
638
- orderStateMachine.can('submit', 'draft'); // true
639
- orderStateMachine.assert('submit', 'draft'); // throws if invalid
640
- orderStateMachine.getAvailableActions('pending'); // ['approve', 'cancel']
641
- orderStateMachine.getHistory(); // Array of transitions
642
- ```
643
-
644
- ## Hooks System
150
+ TanStack Query-inspired server cache with stale-while-revalidate and auto-invalidation:
645
151
 
646
152
  ```typescript
647
- import { hookRegistry } from '@classytic/arc/hooks';
648
-
649
- // Register hook
650
- hookRegistry.register('product', 'beforeCreate', async (context) => {
651
- context.data.slug = slugify(context.data.name);
153
+ // Enable globally
154
+ const app = await createApp({
155
+ arcPlugins: { queryCache: true }, // Memory store by default
652
156
  });
653
157
 
654
- // Available hooks
655
- // beforeCreate, afterCreate
656
- // beforeUpdate, afterUpdate
657
- // beforeDelete, afterDelete
658
- // beforeList, afterList
659
- ```
660
-
661
- ## Policies
662
-
663
- ```typescript
664
- import { definePolicy } from '@classytic/arc/policies';
665
-
666
- const ownedByUserPolicy = definePolicy({
667
- name: 'ownedByUser',
668
- apply: async (query, req) => {
669
- if (!req.user) throw new Error('Unauthorized');
670
- query.filter.createdBy = req.user._id;
671
- return query;
158
+ // Per-resource config
159
+ defineResource({
160
+ name: 'product',
161
+ cache: {
162
+ staleTime: 30, // seconds fresh
163
+ gcTime: 300, // seconds stale data kept (SWR window)
164
+ tags: ['catalog'],
165
+ invalidateOn: { 'category.*': ['catalog'] }, // cross-resource
672
166
  },
673
167
  });
674
-
675
- // Apply in resource
676
- export default defineResource({
677
- name: 'document',
678
- policies: [ownedByUserPolicy],
679
- // ...
680
- });
681
168
  ```
682
169
 
683
- ## Events
170
+ **How it works:**
171
+ - `GET` requests: cached with `x-cache: HIT | STALE | MISS` header
172
+ - `POST/PATCH/DELETE`: auto-bumps resource version, invalidating all cached queries
173
+ - Cross-resource: category mutation bumps `catalog` tag, invalidates product cache
174
+ - Multi-tenant safe: cache keys scoped by userId + orgId
684
175
 
685
- ```typescript
686
- import { eventPlugin } from '@classytic/arc/events';
176
+ **Runtime modes:**
687
177
 
688
- await fastify.register(eventPlugin);
178
+ | Mode | Store | Config |
179
+ |------|-------|--------|
180
+ | `memory` (default) | `MemoryCacheStore` (50 MiB budget) | Zero config |
181
+ | `distributed` | `RedisCacheStore` | `stores: { queryCache: new RedisCacheStore({ client: redis }) }` |
689
182
 
690
- // Emit event
691
- await fastify.events.publish('order.created', { orderId: '123', userId: '456' });
183
+ ## BaseController
692
184
 
693
- // Subscribe
694
- const unsubscribe = await fastify.events.subscribe('order.created', async (event) => {
695
- await sendConfirmationEmail(event.payload.userId);
696
- });
697
-
698
- // Unsubscribe
699
- unsubscribe();
700
- ```
701
-
702
- ## Introspection
185
+ Override only what you need:
703
186
 
704
187
  ```typescript
705
- import { resourceRegistry } from '@classytic/arc/registry';
706
-
707
- // Get all resources
708
- const resources = resourceRegistry.getAll();
188
+ import { BaseController } from '@classytic/arc';
189
+ import type { IRequestContext, IControllerResponse } from '@classytic/arc';
709
190
 
710
- // Get specific resource
711
- const product = resourceRegistry.get('product');
191
+ class ProductController extends BaseController<Product> {
192
+ constructor() { super(productRepo); }
712
193
 
713
- // Get stats
714
- const stats = resourceRegistry.getStats();
715
- // { total: 15, withPresets: 8, withPolicies: 5 }
194
+ async getFeatured(req: IRequestContext): Promise<IControllerResponse> {
195
+ const products = await this.repository.getAll({ filters: { isFeatured: true } });
196
+ return { success: true, data: products };
197
+ }
198
+ }
716
199
  ```
717
200
 
718
- ## Production Features (Meta/Stripe Tier)
719
-
720
- ### OpenTelemetry Distributed Tracing
721
-
722
- ```typescript
723
- import { tracingPlugin } from '@classytic/arc/plugins';
724
-
725
- await fastify.register(tracingPlugin, {
726
- serviceName: 'my-api',
727
- exporterUrl: 'http://localhost:4318/v1/traces',
728
- sampleRate: 0.1, // Trace 10% of requests
729
- });
730
-
731
- // Custom spans
732
- import { createSpan } from '@classytic/arc/plugins';
733
-
734
- return createSpan(req, 'expensiveOperation', async (span) => {
735
- span.setAttribute('userId', req.user._id);
736
- return await processData();
737
- });
738
- ```
201
+ ## Events
739
202
 
740
- ### Enhanced Health Checks
203
+ Domain event pub/sub with pluggable transports. The factory auto-registers `eventPlugin` — no manual setup needed:
741
204
 
742
205
  ```typescript
743
- import { healthPlugin } from '@classytic/arc/plugins';
744
-
745
- await fastify.register(healthPlugin, {
746
- metrics: true, // Prometheus metrics
747
- checks: [
748
- {
749
- name: 'mongodb',
750
- check: async () => mongoose.connection.readyState === 1,
751
- critical: true,
752
- },
753
- {
754
- name: 'redis',
755
- check: async () => redisClient.ping() === 'PONG',
756
- critical: true,
206
+ // createApp() registers eventPlugin automatically (default: MemoryEventTransport)
207
+ // Transport is sourced from stores.events if provided
208
+ const app = await createApp({
209
+ stores: { events: new RedisEventTransport(redis) }, // optional, defaults to memory
210
+ arcPlugins: {
211
+ events: { // event plugin config (default: true)
212
+ logEvents: true,
213
+ retry: { maxRetries: 3, backoffMs: 1000 },
757
214
  },
758
- ],
215
+ },
759
216
  });
760
217
 
761
- // Endpoints: /_health/live, /_health/ready, /_health/metrics
218
+ await app.events.publish('order.created', { orderId: '123' });
219
+ await app.events.subscribe('order.*', async (event) => { ... });
762
220
  ```
763
221
 
764
- ### Circuit Breaker
765
-
766
- ```typescript
767
- import { CircuitBreaker } from '@classytic/arc/utils';
768
-
769
- const stripeBreaker = new CircuitBreaker(
770
- async (amount) => stripe.charges.create({ amount }),
771
- {
772
- failureThreshold: 5,
773
- resetTimeout: 30000,
774
- fallback: async (amount) => queuePayment(amount),
775
- }
776
- );
777
-
778
- const charge = await stripeBreaker.call(1000);
779
- ```
222
+ CRUD events (`product.created`, `product.updated`, `product.deleted`) emit automatically.
780
223
 
781
- ### Schema Versioning & Migrations
224
+ ## Factory createApp()
782
225
 
783
226
  ```typescript
784
- import { defineMigration, MigrationRunner } from '@classytic/arc/migrations';
785
-
786
- const productV2 = defineMigration({
787
- version: 2,
788
- resource: 'product',
789
- up: async (db) => {
790
- await db.collection('products').updateMany(
791
- {},
792
- { $rename: { oldField: 'newField' } }
793
- );
227
+ const app = await createApp({
228
+ preset: 'production', // production | development | testing | edge
229
+ runtime: 'memory', // memory (default) | distributed (requires Redis)
230
+ auth: { type: 'jwt', jwt: { secret } },
231
+ cors: { origin: ['https://myapp.com'] },
232
+ helmet: true, // false to disable
233
+ rateLimit: { max: 100 }, // false to disable
234
+ arcPlugins: {
235
+ events: true, // event plugin (default: true, false to disable)
236
+ emitEvents: true, // CRUD event emission (default: true)
237
+ queryCache: true, // server cache (default: false)
238
+ sse: true, // server-sent events (default: false)
239
+ caching: true, // ETag + Cache-Control (default: false)
794
240
  },
795
- down: async (db) => {
796
- await db.collection('products').updateMany(
797
- {},
798
- { $rename: { 'newField': 'oldField' } }
799
- );
241
+ stores: { // required when runtime: 'distributed'
242
+ events: new RedisEventTransport({ client: redis }),
243
+ cache: new RedisCacheStore({ client: redis }),
244
+ queryCache: new RedisCacheStore({ client: redis, prefix: 'arc:qc:' }),
800
245
  },
801
246
  });
802
-
803
- const runner = new MigrationRunner(mongoose.connection.db);
804
- await runner.up([productV2]);
805
247
  ```
806
248
 
807
- **See [PRODUCTION_FEATURES.md](../../PRODUCTION_FEATURES.md) for complete guides.**
808
-
809
- ## Battle-Tested Deployments
249
+ **Arc plugins defaults:**
810
250
 
811
- Arc has been validated in multiple production environments:
251
+ | Plugin | Default | Status |
252
+ |--------|---------|--------|
253
+ | `events` | `true` | opt-out — registers `eventPlugin` (provides `fastify.events`) |
254
+ | `emitEvents` | `true` | opt-out — CRUD operations emit domain events |
255
+ | `requestId` | `true` | opt-out |
256
+ | `health` | `true` | opt-out |
257
+ | `gracefulShutdown` | `true` | opt-out |
258
+ | `caching` | `false` | opt-in — ETag + Cache-Control headers |
259
+ | `queryCache` | `false` | opt-in — TanStack Query-inspired server cache |
260
+ | `sse` | `false` | opt-in — Server-Sent Events streaming |
812
261
 
813
- ### Environment Compatibility
262
+ | Preset | Logging | Rate Limit | Security |
263
+ |--------|---------|------------|----------|
264
+ | production | info | 100/min | full |
265
+ | development | debug | 1000/min | relaxed |
266
+ | testing | silent | disabled | minimal |
267
+ | edge | warn | disabled | none (API GW handles) |
814
268
 
815
- | Environment | Status | Notes |
816
- |-------------|--------|-------|
817
- | Docker | ✅ Tested | Use Node 18+ Alpine images |
818
- | Kubernetes | ✅ Tested | Health checks + graceful shutdown built-in |
819
- | AWS Lambda | ✅ Tested | Use `@fastify/aws-lambda` adapter |
820
- | Google Cloud Run | ✅ Tested | Auto-scales, health checks work OOTB |
821
- | Vercel Serverless | ✅ Tested | Use serverless functions adapter |
822
- | Bare Metal / VPS | ✅ Tested | PM2 or systemd recommended |
823
- | Railway / Render | ✅ Tested | Works with zero config |
269
+ ## Real-Time
824
270
 
825
- ### Production Checklist
826
-
827
- Before deploying to production:
271
+ SSE and WebSocket with fail-closed auth (throws at registration if auth missing):
828
272
 
829
273
  ```typescript
830
- import { createApp, validateEnv } from '@classytic/arc';
831
-
832
- // 1. Validate environment variables at startup
833
- validateEnv({
834
- JWT_SECRET: { required: true, min: 32 },
835
- DATABASE_URL: { required: true },
836
- NODE_ENV: { required: true, values: ['production', 'staging'] },
837
- });
838
-
839
- // 2. Use production environment preset
274
+ // SSE via factory
840
275
  const app = await createApp({
841
- environment: 'production',
842
-
843
- // 3. Configure CORS properly (never use origin: true)
844
- cors: {
845
- origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
846
- credentials: true,
847
- },
848
-
849
- // 4. Adjust rate limits for your traffic
850
- rateLimit: {
851
- max: 300, // Requests per window
852
- timeWindow: '1 minute',
853
- ban: 10, // Ban after 10 violations
854
- },
855
-
856
- // 5. Enable health checks
857
- healthCheck: true,
276
+ arcPlugins: { sse: { path: '/events', requireAuth: true, orgScoped: true } },
277
+ });
858
278
 
859
- // 6. Configure logging
860
- logger: {
861
- level: 'info',
862
- redact: ['req.headers.authorization'],
863
- },
279
+ // WebSocket separate plugin
280
+ import { websocketPlugin } from '@classytic/arc/integrations/websocket';
281
+ await app.register(websocketPlugin, {
282
+ auth: true, // fail-closed: throws if authenticate not registered
283
+ resources: ['product', 'order'],
284
+ roomPolicy: (client, room) => ['product', 'order'].includes(room),
285
+ maxMessageBytes: 16384, // 16KB message size cap
286
+ maxSubscriptionsPerClient: 100, // prevent resource exhaustion
864
287
  });
865
288
 
866
- // 7. Graceful shutdown
867
- process.on('SIGTERM', () => app.close());
868
- process.on('SIGINT', () => app.close());
289
+ // EventGateway unified SSE + WebSocket with shared config
290
+ import { eventGatewayPlugin } from '@classytic/arc/integrations/event-gateway';
291
+ await app.register(eventGatewayPlugin, {
292
+ auth: true, orgScoped: true,
293
+ roomPolicy: (client, room) => allowedRooms.includes(room),
294
+ sse: { path: '/api/events', patterns: ['order.*'] },
295
+ ws: { path: '/ws', resources: ['product', 'order'] },
296
+ });
869
297
  ```
870
298
 
871
- ### Multi-Region Deployment
299
+ ## Integrations
872
300
 
873
- For globally distributed apps:
301
+ All separate subpath imports — only loaded when used:
874
302
 
875
303
  ```typescript
876
- // Use read replicas
877
- const app = await createApp({
878
- mongodb: {
879
- primary: process.env.MONGODB_PRIMARY,
880
- replicas: process.env.MONGODB_REPLICAS?.split(','),
881
- readPreference: 'nearest',
882
- },
304
+ // Job Queue (BullMQ)
305
+ import { jobsPlugin, defineJob } from '@classytic/arc/integrations/jobs';
883
306
 
884
- // Distributed tracing for multi-region debugging
885
- tracing: {
886
- enabled: true,
887
- serviceName: `api-${process.env.REGION}`,
888
- exporter: 'zipkin',
889
- },
890
- });
891
- ```
307
+ // WebSocket (room-based, CRUD auto-broadcast)
308
+ import { websocketPlugin } from '@classytic/arc/integrations/websocket';
892
309
 
893
- ### Load Testing Results
310
+ // EventGateway (unified SSE + WebSocket)
311
+ import { eventGatewayPlugin } from '@classytic/arc/integrations/event-gateway';
894
312
 
895
- Arc has been load tested with the following results:
313
+ // Streamline Workflows
314
+ import { streamlinePlugin } from '@classytic/arc/integrations/streamline';
896
315
 
897
- - **Throughput**: 10,000+ req/s (single instance, 4 CPU cores)
898
- - **Latency**: P50: 8ms, P95: 45ms, P99: 120ms
899
- - **Memory**: ~50MB base + ~0.5MB per 1000 requests
900
- - **Connections**: Handles 10,000+ concurrent connections
901
- - **Database**: Tested with 1M+ documents, sub-10ms queries with proper indexes
316
+ // Audit Trail
317
+ import { auditPlugin } from '@classytic/arc/audit';
902
318
 
903
- *Results vary based on hardware, database, and business logic complexity.*
319
+ // Idempotency (exactly-once mutations)
320
+ import { idempotencyPlugin } from '@classytic/arc/idempotency';
904
321
 
905
- ## Performance Tips
906
-
907
- 1. **Use Proxy Compression** - Use Nginx/Caddy or CDN for Brotli/gzip compression
908
- 2. **Enable Memory Monitoring** - Detect leaks early in production
909
- 3. **Use Testing Preset** - Minimal overhead for test suites
910
- 4. **Apply Indexes** - Always index query fields in models
911
- 5. **Use Lean Queries** - Repository returns plain objects by default
912
- 6. **Rate Limiting** - Protect endpoints from abuse
913
- 7. **Validate Early** - Use environment validator at startup
914
- 8. **Distributed Tracing** - Track requests across services (5ms overhead)
915
- 9. **Circuit Breakers** - Prevent cascading failures (<1ms overhead)
916
- 10. **Health Checks** - K8s-compatible liveness/readiness probes
322
+ // OpenTelemetry Tracing
323
+ import { tracingPlugin } from '@classytic/arc/plugins/tracing';
324
+ ```
917
325
 
918
- ## Security Best Practices
326
+ ## CLI
919
327
 
920
- 1. **Opt-out Security** - All plugins enabled by default in production
921
- 2. **Strong Secrets** - Minimum 32 characters for JWT/session secrets
922
- 3. **CORS Configuration** - Never use `origin: true` in production
923
- 4. **Permission Checks** - Always define permissions per operation
924
- 5. **Multi-tenant Isolation** - Use `multiTenant` preset for SaaS apps
925
- 6. **Ownership Checks** - Use `ownedByUser` preset for user data
926
- 7. **Audit Logging** - Track all changes with audit plugin
328
+ ```bash
329
+ arc init my-api --mongokit --better-auth --ts # Scaffold project
330
+ arc generate resource product # Generate resource files
331
+ arc docs ./openapi.json --entry ./dist/index.js # Export OpenAPI
332
+ arc introspect --entry ./dist/index.js # Show resources
333
+ arc doctor # Health check
334
+ ```
335
+
336
+ ## Subpath Imports
337
+
338
+ | Import | Purpose |
339
+ |--------|---------|
340
+ | `@classytic/arc` | Core: `defineResource`, `BaseController`, permissions, errors |
341
+ | `@classytic/arc/factory` | `createApp()`, presets |
342
+ | `@classytic/arc/cache` | `MemoryCacheStore`, `RedisCacheStore`, `QueryCache` |
343
+ | `@classytic/arc/auth` | Auth plugin, Better Auth adapter, session manager |
344
+ | `@classytic/arc/events` | Event plugin, memory transport |
345
+ | `@classytic/arc/events/redis` | Redis event transport |
346
+ | `@classytic/arc/events/redis-stream` | Redis Streams transport |
347
+ | `@classytic/arc/plugins` | Health, graceful shutdown, request ID, SSE, caching |
348
+ | `@classytic/arc/plugins/tracing` | OpenTelemetry |
349
+ | `@classytic/arc/permissions` | All permission functions |
350
+ | `@classytic/arc/scope` | Request scope helpers (`isMember`, `isElevated`, `getOrgId`) |
351
+ | `@classytic/arc/org` | Organization module |
352
+ | `@classytic/arc/hooks` | Lifecycle hooks |
353
+ | `@classytic/arc/presets` | Preset functions + interfaces |
354
+ | `@classytic/arc/audit` | Audit trail |
355
+ | `@classytic/arc/idempotency` | Idempotency |
356
+ | `@classytic/arc/policies` | Policy engine |
357
+ | `@classytic/arc/schemas` | TypeBox helpers |
358
+ | `@classytic/arc/utils` | Errors, circuit breaker, state machine, query parser |
359
+ | `@classytic/arc/testing` | Test utilities, mocks, in-memory DB |
360
+ | `@classytic/arc/migrations` | Schema migrations |
361
+ | `@classytic/arc/integrations/jobs` | BullMQ job queue |
362
+ | `@classytic/arc/integrations/websocket` | WebSocket |
363
+ | `@classytic/arc/integrations/event-gateway` | Unified SSE + WebSocket gateway |
364
+ | `@classytic/arc/integrations/streamline` | Workflow orchestration |
365
+ | `@classytic/arc/docs` | OpenAPI generation |
366
+ | `@classytic/arc/cli` | CLI commands |
367
+
368
+ ## Documentation
369
+
370
+ - [Setup](docs/getting-started/setup.md) — Project setup
371
+ - [Core Concepts](docs/getting-started/core.md) — Resources, controllers, adapters
372
+ - [Authentication](docs/getting-started/auth.md) — JWT, Better Auth, custom auth
373
+ - [Permissions](docs/getting-started/permissions.md) — RBAC, ABAC, field-level
374
+ - [Presets](docs/getting-started/presets.md) — softDelete, multiTenant, tree, etc.
375
+ - [Organizations](docs/getting-started/org.md) — Multi-tenant SaaS
376
+ - [Factory](docs/production-ops/factory.md) — createApp() and environment presets
377
+ - [Events](docs/production-ops/events.md) — Domain events and transports
378
+ - [Plugins](docs/production-ops/plugins.md) — Health, caching, SSE, tracing
379
+ - [Hooks](docs/framework-extension/hooks.md) — Lifecycle hooks
927
380
 
928
381
  ## License
929
382