@classytic/arc 2.11.4 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +16 -12
  2. package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
  3. package/dist/EventTransport-CT_52aWU.d.mts +34 -0
  4. package/dist/EventTransport-DLWoUMHy.mjs +103 -0
  5. package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/audit.d.mts +199 -0
  9. package/dist/auth/audit.mjs +288 -0
  10. package/dist/auth/index.d.mts +3 -3
  11. package/dist/auth/index.mjs +117 -191
  12. package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
  13. package/dist/buildHandler-olo-gt94.mjs +610 -0
  14. package/dist/cache/index.mjs +3 -3
  15. package/dist/cli/commands/describe.d.mts +89 -13
  16. package/dist/cli/commands/describe.mjs +56 -2
  17. package/dist/cli/commands/docs.mjs +2 -2
  18. package/dist/cli/commands/generate.mjs +147 -48
  19. package/dist/cli/commands/init.d.mts +13 -0
  20. package/dist/cli/commands/init.mjs +130 -87
  21. package/dist/cli/commands/introspect.mjs +8 -1
  22. package/dist/context/index.mjs +1 -1
  23. package/dist/core/index.d.mts +3 -3
  24. package/dist/core/index.mjs +5 -5
  25. package/dist/core-DECn6zaU.mjs +1399 -0
  26. package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CBxLLbn3.mjs} +7 -20
  27. package/dist/createAggregationRouter-CRIBv4sC.mjs +114 -0
  28. package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
  29. package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
  30. package/dist/docs/index.d.mts +24 -11
  31. package/dist/docs/index.mjs +2 -2
  32. package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
  33. package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
  34. package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
  35. package/dist/errors-j4aJm1Wg.mjs +184 -0
  36. package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
  37. package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
  38. package/dist/events/index.d.mts +6 -6
  39. package/dist/events/index.mjs +11 -35
  40. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  41. package/dist/events/transports/redis.d.mts +1 -1
  42. package/dist/factory/index.d.mts +2 -2
  43. package/dist/factory/index.mjs +2 -2
  44. package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
  45. package/dist/hooks/index.d.mts +1 -1
  46. package/dist/hooks/index.mjs +1 -1
  47. package/dist/idempotency/index.d.mts +1 -1
  48. package/dist/idempotency/index.mjs +1 -20
  49. package/dist/idempotency/redis.mjs +1 -1
  50. package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
  51. package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
  52. package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
  53. package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
  54. package/dist/index.d.mts +6 -7
  55. package/dist/index.mjs +9 -10
  56. package/dist/integrations/event-gateway.d.mts +1 -1
  57. package/dist/integrations/event-gateway.mjs +1 -1
  58. package/dist/integrations/index.d.mts +1 -1
  59. package/dist/integrations/mcp/index.d.mts +2 -2
  60. package/dist/integrations/mcp/index.mjs +1 -1
  61. package/dist/integrations/mcp/testing.d.mts +1 -1
  62. package/dist/integrations/mcp/testing.mjs +1 -1
  63. package/dist/integrations/streamline.d.mts +60 -11
  64. package/dist/integrations/streamline.mjs +75 -85
  65. package/dist/integrations/websocket.mjs +2 -8
  66. package/dist/middleware/index.d.mts +1 -1
  67. package/dist/middleware/index.mjs +2 -2
  68. package/dist/migrations/index.d.mts +23 -3
  69. package/dist/migrations/index.mjs +0 -7
  70. package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
  71. package/dist/openapi-noXno2CV.mjs +968 -0
  72. package/dist/org/index.d.mts +2 -2
  73. package/dist/org/index.mjs +1 -1
  74. package/dist/permissions/index.d.mts +3 -3
  75. package/dist/permissions/index.mjs +3 -3
  76. package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
  77. package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
  78. package/dist/pipeline/index.d.mts +1 -1
  79. package/dist/pipeline/index.mjs +1 -1
  80. package/dist/plugins/index.d.mts +16 -31
  81. package/dist/plugins/index.mjs +33 -13
  82. package/dist/plugins/response-cache.mjs +1 -1
  83. package/dist/plugins/tracing-entry.mjs +1 -1
  84. package/dist/presets/filesUpload.d.mts +4 -4
  85. package/dist/presets/filesUpload.mjs +6 -9
  86. package/dist/presets/index.d.mts +1 -1
  87. package/dist/presets/index.mjs +1 -1
  88. package/dist/presets/multiTenant.d.mts +1 -1
  89. package/dist/presets/multiTenant.mjs +2 -2
  90. package/dist/presets/search.d.mts +2 -2
  91. package/dist/presets/search.mjs +6 -8
  92. package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
  93. package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
  94. package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
  95. package/dist/registry/index.d.mts +1 -1
  96. package/dist/registry/index.mjs +2 -2
  97. package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
  98. package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-DLL32us3.mjs} +224 -71
  99. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-DrOa-26E.mjs} +41 -36
  100. package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-lYhC2gE5.mjs} +1 -1
  101. package/dist/schemas/index.d.mts +100 -30
  102. package/dist/schemas/index.mjs +86 -29
  103. package/dist/scim/index.d.mts +264 -0
  104. package/dist/scim/index.mjs +963 -0
  105. package/dist/scope/index.d.mts +3 -3
  106. package/dist/scope/index.mjs +4 -4
  107. package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
  108. package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
  109. package/dist/testing/index.d.mts +2 -8
  110. package/dist/testing/index.mjs +16 -24
  111. package/dist/types/index.d.mts +4 -4
  112. package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
  113. package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
  114. package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
  115. package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
  116. package/dist/utils/index.d.mts +2 -2
  117. package/dist/utils/index.mjs +5 -5
  118. package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
  119. package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
  120. package/package.json +24 -34
  121. package/skills/arc/SKILL.md +147 -51
  122. package/skills/arc/references/agent-auth.md +238 -0
  123. package/skills/arc/references/api-reference.md +187 -0
  124. package/skills/arc/references/auth.md +354 -7
  125. package/skills/arc/references/enterprise-auth.md +94 -0
  126. package/skills/arc/references/events.md +8 -6
  127. package/skills/arc/references/mcp.md +2 -2
  128. package/skills/arc/references/multi-tenancy.md +11 -2
  129. package/skills/arc/references/production.md +10 -9
  130. package/skills/arc/references/scim.md +247 -0
  131. package/skills/arc/references/testing.md +1 -1
  132. package/skills/arc-code-review/SKILL.md +141 -0
  133. package/skills/arc-code-review/references/anti-patterns.md +911 -0
  134. package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
  135. package/skills/arc-code-review/references/migration-recipes.md +700 -0
  136. package/skills/arc-code-review/references/mongokit-migration.md +386 -0
  137. package/skills/arc-code-review/references/scaffolding.md +230 -0
  138. package/skills/arc-code-review/references/severity.md +127 -0
  139. package/dist/EventTransport-BFQjw9pB.mjs +0 -133
  140. package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
  141. package/dist/adapters/index.d.mts +0 -3
  142. package/dist/adapters/index.mjs +0 -2
  143. package/dist/adapters-DUUiiimH.mjs +0 -964
  144. package/dist/auth/mongoose.d.mts +0 -191
  145. package/dist/auth/mongoose.mjs +0 -73
  146. package/dist/core-CbcQRIch.mjs +0 -1054
  147. package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
  148. package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
  149. package/dist/errors-D5c-5BJL.mjs +0 -232
  150. package/dist/index-Rg8axYPz.d.mts +0 -370
  151. package/dist/openapi-D7G1V7ex.mjs +0 -557
  152. /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  153. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  154. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  155. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  156. /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  157. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  158. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  159. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  160. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  161. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
  162. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  163. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  164. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  165. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  166. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  167. /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
@@ -1,4 +1,4 @@
1
- import { n as DomainEvent } from "./EventTransport-CYNUXdCJ.mjs";
1
+ import { n as DomainEvent } from "./EventTransport-CT_52aWU.mjs";
2
2
  import { FastifyPluginAsync, FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/plugins/caching.d.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.11.4",
4
- "description": "Resource-oriented backend framework for Fastify clean, minimal, powerful, tree-shakable",
3
+ "version": "2.14.0",
4
+ "description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -24,10 +24,6 @@
24
24
  "types": "./dist/types/storage.d.mts",
25
25
  "default": "./dist/types/storage.mjs"
26
26
  },
27
- "./adapters": {
28
- "types": "./dist/adapters/index.d.mts",
29
- "default": "./dist/adapters/index.mjs"
30
- },
31
27
  "./core": {
32
28
  "types": "./dist/core/index.d.mts",
33
29
  "default": "./dist/core/index.mjs"
@@ -184,10 +180,6 @@
184
180
  "types": "./dist/auth/redis-session.d.mts",
185
181
  "default": "./dist/auth/redis-session.mjs"
186
182
  },
187
- "./auth/mongoose": {
188
- "types": "./dist/auth/mongoose.d.mts",
189
- "default": "./dist/auth/mongoose.mjs"
190
- },
191
183
  "./plugins/response-cache": {
192
184
  "types": "./dist/plugins/response-cache.d.mts",
193
185
  "default": "./dist/plugins/response-cache.mjs"
@@ -203,6 +195,14 @@
203
195
  "./mcp/testing": {
204
196
  "types": "./dist/integrations/mcp/testing.d.mts",
205
197
  "default": "./dist/integrations/mcp/testing.mjs"
198
+ },
199
+ "./scim": {
200
+ "types": "./dist/scim/index.d.mts",
201
+ "default": "./dist/scim/index.mjs"
202
+ },
203
+ "./auth/audit": {
204
+ "types": "./dist/auth/audit.d.mts",
205
+ "default": "./dist/auth/audit.mjs"
206
206
  }
207
207
  },
208
208
  "main": "./dist/index.mjs",
@@ -210,6 +210,9 @@
210
210
  "bin": {
211
211
  "arc": "./bin/arc.js"
212
212
  },
213
+ "publishConfig": {
214
+ "access": "public"
215
+ },
213
216
  "sideEffects": false,
214
217
  "files": [
215
218
  "dist",
@@ -242,9 +245,9 @@
242
245
  "node": ">=22"
243
246
  },
244
247
  "peerDependencies": {
245
- "@classytic/mongokit": ">=3.11.1",
246
- "@classytic/repo-core": ">=0.2.0",
247
- "@classytic/streamline": ">=2.2.0",
248
+ "@classytic/primitives": ">=0.4.0",
249
+ "@classytic/repo-core": ">=0.4.0",
250
+ "@classytic/streamline": ">=2.3.0",
248
251
  "@fastify/cors": ">=11.0.0",
249
252
  "@fastify/helmet": ">=13.0.0",
250
253
  "@fastify/jwt": ">=10.0.0",
@@ -267,24 +270,10 @@
267
270
  "fastify": "^5.8.5",
268
271
  "fastify-raw-body": ">=5.0.0",
269
272
  "ioredis": ">=5.0.0",
270
- "mongodb": ">=7.0.0",
271
- "mongoose": "^9.4.1",
272
273
  "pino-pretty": ">=13.0.0",
273
274
  "zod": ">=4.0.0"
274
275
  },
275
276
  "peerDependenciesMeta": {
276
- "@classytic/mongokit": {
277
- "optional": true
278
- },
279
- "@classytic/repo-core": {
280
- "optional": true
281
- },
282
- "mongodb": {
283
- "optional": true
284
- },
285
- "mongoose": {
286
- "optional": true
287
- },
288
277
  "better-auth": {
289
278
  "optional": true
290
279
  },
@@ -364,14 +353,15 @@
364
353
  "secure-json-parse": "^4.1.0"
365
354
  },
366
355
  "devDependencies": {
367
- "@better-auth/drizzle-adapter": "^1.6.2",
368
- "@better-auth/mongo-adapter": "^1.6.2",
356
+ "@better-auth/drizzle-adapter": "^1.6.9",
357
+ "@better-auth/mongo-adapter": "^1.6.9",
369
358
  "@biomejs/biome": "^2.4.11",
370
359
  "@classytic/dev-tools": "^0.2.0",
371
- "@classytic/mongokit": "^3.11.1",
372
- "@classytic/repo-core": "^0.2.0",
373
- "@classytic/sqlitekit": "^0.2.0",
374
- "@classytic/streamline": "^2.2.0",
360
+ "@classytic/mongokit": "^3.13.0",
361
+ "@classytic/primitives": "^0.4.0",
362
+ "@classytic/repo-core": "^0.4.0",
363
+ "@classytic/sqlitekit": "^0.3.0",
364
+ "@classytic/streamline": "^2.3.0",
375
365
  "@fastify/cors": "^11.2.0",
376
366
  "@fastify/helmet": "^13.0.2",
377
367
  "@fastify/jwt": "^10.0.0",
@@ -388,7 +378,7 @@
388
378
  "@types/qs": "^6.14.0",
389
379
  "@vitest/coverage-v8": "^3.2.4",
390
380
  "ajv": "^8.18.0",
391
- "better-auth": "^1.6.2",
381
+ "better-auth": "^1.6.9",
392
382
  "better-sqlite3": "^12.9.0",
393
383
  "bullmq": "^5.73.5",
394
384
  "dotenv": "^17.4.2",
@@ -8,11 +8,9 @@ description: |
8
8
  Triggers: arc, fastify resource, defineResource, createApp, BaseController, arc preset,
9
9
  arc auth, arc events, arc jobs, arc websocket, arc mcp, arc plugin, arc testing, arc cli,
10
10
  arc permissions, arc hooks, arc pipeline, arc factory, arc cache, arc QueryCache.
11
- version: 2.11.4
12
11
  license: MIT
13
12
  metadata:
14
13
  author: Classytic
15
- version: "2.11.4"
16
14
  tags:
17
15
  - fastify
18
16
  - rest-api
@@ -30,14 +28,13 @@ progressive_disclosure:
30
28
  summary: "Resource-oriented Fastify framework: defineResource(), presets, permissions, QueryCache, events, multi-tenant, OpenAPI, MCP"
31
29
  when_to_use: "Building REST APIs with Fastify, resource CRUD, authentication, presets, caching, events, or production deployment"
32
30
  quick_start: "1. arc init my-api --mongokit --jwt --ts 2. defineResource({ name, adapter, presets, permissions }) 3. createApp({ preset: 'production', resources, auth })"
33
- context_limit: 700
34
31
  ---
35
32
 
36
33
  # @classytic/arc
37
34
 
38
35
  Resource-oriented backend framework for Fastify. **Fastify ≥5.8.5 · Node ≥22 · ESM only.**
39
36
 
40
- One `defineResource()` call → REST + auth + permissions + events + cache + OpenAPI + MCP. Database-agnostic (Mongoose, Drizzle/sqlitekit, custom).
37
+ One `defineResource()` call → REST + auth + permissions + events + cache + OpenAPI + MCP. Database-agnostic (Mongoose, Drizzle/sqlitekit, Prisma, custom).
41
38
 
42
39
  ## Scaffold a project
43
40
 
@@ -102,11 +99,17 @@ Pass `import.meta.url` for dev/prod parity (resolves `src/` in dev, `dist/` in p
102
99
  ## defineResource()
103
100
 
104
101
  ```typescript
105
- import { defineResource, createMongooseAdapter, allowPublic, requireRoles } from '@classytic/arc';
102
+ import { defineResource, allowPublic, requireRoles } from '@classytic/arc';
103
+ import { createMongooseAdapter } from '@classytic/mongokit/adapter';
104
+ import { buildCrudSchemasFromModel } from '@classytic/mongokit';
106
105
 
107
106
  const productResource = defineResource({
108
107
  name: 'product',
109
- adapter: createMongooseAdapter({ model: ProductModel, repository: productRepo }),
108
+ adapter: createMongooseAdapter({
109
+ model: ProductModel,
110
+ repository: productRepo,
111
+ schemaGenerator: buildCrudSchemasFromModel, // required (no built-in fallback)
112
+ }),
110
113
  controller: productController, // optional — auto-built if omitted
111
114
 
112
115
  presets: ['softDelete', 'slugLookup', { name: 'multiTenant', tenantField: 'orgId' }],
@@ -190,20 +193,38 @@ auth: false
190
193
 
191
194
  Decorates `app.authenticate`, `app.optionalAuthenticate`, `app.authorize`.
192
195
 
193
- **Better Auth + Mongoose `populate()`** when BA writes via the native mongo driver but resources `.populate()` against `ref: 'user'`, register stub models from a dedicated subpath (Mongoose stays out of Prisma/Drizzle bundles):
196
+ **Better Auth arc is plugin-agnostic.** `auth.$context.tables` introspection lets the kit overlays read whatever plugins you've enabled no per-plugin code in arc/mongokit/sqlitekit. Tested combinations: `organization`, `twoFactor`, `admin`, `bearer` (built-in), plus `apiKey` from the **separate** `@better-auth/api-key` package.
194
197
 
195
198
  ```typescript
196
- import { registerBetterAuthMongooseModels } from '@classytic/arc/auth/mongoose';
199
+ import { betterAuth } from 'better-auth';
200
+ import { mongodbAdapter } from '@better-auth/mongo-adapter';
201
+ import { organization, twoFactor, admin, bearer } from 'better-auth/plugins';
202
+ import { apiKey } from '@better-auth/api-key'; // ← separate npm package
203
+ import { createBetterAuthOverlay, registerBetterAuthStubs } from '@classytic/mongokit/better-auth';
197
204
 
198
- registerBetterAuthMongooseModels(mongoose, {
199
- plugins: ['organization', 'organization-teams', 'mcp'],
200
- extraCollections: ['passkey', 'ssoProvider'],
205
+ const auth = betterAuth({
206
+ database: mongodbAdapter(mongoose.connection.getClient().db()),
207
+ emailAndPassword: { enabled: true },
208
+ plugins: [organization(), twoFactor(), admin(), bearer(), apiKey({ enableSessionForAPIKeys: true })],
201
209
  });
210
+
211
+ // Bulk-register populate() stubs. extraCollections covers tables not in the plugin map (apikey, passkey, …).
212
+ registerBetterAuthStubs(mongoose, { plugins: ['organization'], extraCollections: ['apikey'] });
213
+
214
+ // Per-resource overlay — DataAdapter ready for defineResource. Async (reads BA's resolved schema once at boot).
215
+ const orgAdapter = await createBetterAuthOverlay({ auth, mongoose, collection: 'organization' });
216
+ const apiKeyAdapter = await createBetterAuthOverlay({ auth, mongoose, collection: 'apikey' });
217
+
218
+ // Sqlitekit is symmetric: { auth, db, collection } + additionalColumns instead of additionalFields.
202
219
  ```
203
220
 
204
- Plugin keys: `organization`, `organization-teams`, `twoFactor`, `jwt`, `oidcProvider`, `mcp`, `deviceAuthorization`. Field-only plugins (admin, username, magicLink, ) need no entry.
221
+ **Multi-role members**: BA stores `member.role = "admin,recruiter,viewer"` (comma-separated string). Arc splits it into `scope.orgRoles = ['admin', 'recruiter', 'viewer']`; `requireOrgRole('admin')` matches. Filtering by exact `?role=admin` will NOT match — use `role[like]=admin`.
222
+
223
+ **API key flow**: client sends `x-api-key: ak_live_...` + `x-organization-id: org_abc` (header required because API-key sessions have no `activeOrganizationId`). Arc adds `apiKeyAuth` to OpenAPI security only when the plugin is active.
224
+
225
+ **Bearer plugin** (`bearer()` from `better-auth/plugins`): SPA / mobile clients use `Authorization: Bearer <session>` instead of cookies — same `auth.api.getSession()` path, no arc config change. Enable both for hybrid apps.
205
226
 
206
- Full auth recipes → [references/auth.md](references/auth.md).
227
+ Full overlay recipes (Tier 1 hand-roll vs Tier 2 factory), plugin matrix, `registerBetterAuthStubs` options, multi-plugin merge, write path, and CLI scaffolding flags → [references/auth.md](references/auth.md). Live end-to-end smoke: [`playground/better-auth/mongo/`](../../playground/better-auth/mongo/) · [`playground/better-auth/sqlite/`](../../playground/better-auth/sqlite/).
207
228
 
208
229
  ## Permissions
209
230
 
@@ -416,7 +437,7 @@ defineResource({ name: 'account-type', tenantField: false }); // company-w
416
437
  defineResource({ name: 'workspace-item', tenantField: 'workspaceId' });
417
438
  ```
418
439
 
419
- Use `tenantField: false` for lookup tables, platform settings, cross-org reports, single-tenant apps.
440
+ Use `tenantField: false` for lookup tables, platform settings, cross-org reports, single-tenant apps. **2.12 auto-inference:** if the Mongoose model has no `organizationId` path (and no other tenant field is configured), arc auto-infers `tenantField: false` instead of generating queries that filter on a non-existent column.
420
441
 
421
442
  ### idField — custom primary key
422
443
 
@@ -456,8 +477,11 @@ Defaults: search/similar inherit `list` perms → `allowPublic()`. Embed → `re
456
477
 
457
478
  ## Adapters
458
479
 
480
+ In arc 2.12 the cross-framework adapter contract lives in `@classytic/repo-core/adapter`. Every kit-specific adapter ships from its kit's `/adapter` subpath; arc has zero kit-bound adapters in `src/`.
481
+
459
482
  ```typescript
460
- import { createMongooseAdapter } from '@classytic/arc';
483
+ // Mongoose from mongokit
484
+ import { createMongooseAdapter } from '@classytic/mongokit/adapter';
461
485
  import { buildCrudSchemasFromModel } from '@classytic/mongokit';
462
486
 
463
487
  const adapter = createMongooseAdapter({
@@ -465,13 +489,25 @@ const adapter = createMongooseAdapter({
465
489
  repository: productRepo,
466
490
  schemaGenerator: buildCrudSchemasFromModel, // no cast needed
467
491
  });
492
+
493
+ // Drizzle — from sqlitekit
494
+ import { createDrizzleAdapter } from '@classytic/sqlitekit/adapter';
495
+ import { buildCrudSchemasFromTable } from '@classytic/sqlitekit';
496
+
497
+ createDrizzleAdapter({ table, repository, schemaGenerator: buildCrudSchemasFromTable });
498
+
499
+ // Prisma — from prismakit
500
+ import { createPrismaAdapter } from '@classytic/prismakit/adapter';
468
501
  ```
469
502
 
470
- `createMongooseAdapter` accepts `AdapterRepositoryInput<TDoc>` — kit-native repos (mongokit, sqlitekit) plug in **without** `as RepositoryLike` casts.
503
+ Custom kits implementing `DataAdapter<TDoc>` from `@classytic/repo-core/adapter` plug in identically. Kit factories accept `AdapterRepositoryInput<TDoc>` — kit-native repos plug in **without** `as RepositoryLike` casts.
471
504
 
472
- **Custom adapter** — implement `MinimalRepo` from `@classytic/repo-core/repository`:
505
+ **Custom adapter** — implement `DataAdapter` / `MinimalRepo` from `@classytic/repo-core/adapter`:
473
506
 
474
507
  ```typescript
508
+ import type {
509
+ DataAdapter, RepositoryLike, AdapterRepositoryInput,
510
+ } from '@classytic/repo-core/adapter';
475
511
  import type { MinimalRepo } from '@classytic/repo-core/repository';
476
512
  // MinimalRepo<TDoc> = 5-method floor (getAll, getById, create, update, delete)
477
513
  // StandardRepo<TDoc> = MinimalRepo + optional batch ops, CAS, soft-delete, …
@@ -496,7 +532,7 @@ class ProductController extends BaseController<Product> {
496
532
 
497
533
  async getFeatured(req: IRequestContext): Promise<IControllerResponse<Product[]>> {
498
534
  const products = await this.repository.getAll({ filters: { isFeatured: true } });
499
- return { success: true, data: products };
535
+ return { data: products };
500
536
  }
501
537
  }
502
538
 
@@ -643,7 +679,9 @@ CRUD events auto-emit: `{resource}.created` / `{resource}.updated` / `{resource}
643
679
 
644
680
  **Transports:** Memory · Redis Pub/Sub (fire-and-forget) · Redis Streams (durable, at-least-once, consumer groups, DLQ).
645
681
 
646
- **EventMeta:** `id`, `timestamp`, optional `schemaVersion`, `correlationId`, `causationId`, `partitionKey`, `source`, `idempotencyKey`, `resource`, `resourceId`, `userId`, `organizationId`, `aggregate: { type, id }`. Use `createChildEvent(parent, ...)` to inherit correlation/causation/source/idempotencyKey.
682
+ **EventMeta:** `id`, `timestamp`, optional `schemaVersion`, `correlationId`, `causationId`, `partitionKey`, `source`, `idempotencyKey`, `resource`, `resourceId`, `userId`, `organizationId`, `aggregate: { type, id }`. Import event types from `@classytic/primitives/events` (`EventMeta`, `DomainEvent`, `EventHandler`, `EventTransport`, `DeadLetteredEvent`, `PublishManyResult`, `createEvent`, `createChildEvent`, `matchEventPattern`); arc re-exports the runtime `MemoryEventTransport` only. Use `createChildEvent(parent, ...)` to inherit correlation/causation/source/idempotencyKey.
683
+
684
+ Calling both `app.events.publish('order.placed', ...)` *and* a notification helper that internally publishes the same logical event triggers a one-shot dev-mode warning ("dual-publish"). Pick one path: manual publish OR `eventStrategy: 'auto'`.
647
685
 
648
686
  **Event Outbox** — at-least-once via transactional outbox. Production: `new EventOutbox({ repository: outboxRepo, transport })` (multi-worker claim, session-threaded writes). Dev: `new EventOutbox({ store: new MemoryOutboxStore(), transport })`. `EventOutbox.store()` auto-maps `meta.idempotencyKey` → `dedupeKey`. `failurePolicy` centralises retry/DLQ.
649
687
 
@@ -660,6 +698,8 @@ throw createDomainError('SELF_REFERRAL', 'Cannot self-refer', 422, { field: 'ref
660
698
 
661
699
  Resolution: `ArcError` → `.statusCode` (Fastify) → `.status` (MongoKit, http-errors) → user `errorMap` → Mongoose/MongoDB → 500. Any error with `.status` or `.statusCode` gets the correct HTTP response.
662
700
 
701
+ `ArcError` implements the `HttpError` throwable contract from `@classytic/repo-core/errors` (`status` getter mirrors `statusCode`, `meta` getter mirrors `details`). Wire envelope: `{ code, message, status, meta?, correlationId? }` — HTTP status code is the success/error discriminator, no redundant `success` field. For non-Arc errors, `toErrorContract(err)` (from `@classytic/repo-core/errors`) serialises any `HttpError` to the canonical `ErrorContract` wire shape; `statusToErrorCode(status)` maps numeric status to canonical `ErrorCode`.
702
+
663
703
  **Class-based mappers:**
664
704
 
665
705
  ```typescript
@@ -827,16 +867,16 @@ const { userId, organizationId, roles: userRoles, orgRoles } = getOrgContext(req
827
867
  permissions: { create: roles('admin', 'editor'), delete: roles('admin') }
828
868
  ```
829
869
 
830
- **Reply helpers** — opt-in via `createApp({ replyHelpers: true })`:
870
+ **Reply helpers** — opt-in via `createApp({ replyHelpers: true })`. Arc has no `{ success, data }` envelope: HTTP status discriminates, single-doc handlers `return doc` or `reply.send(doc)`, errors throw `ArcError` (the global handler serialises to `ErrorContract`). The two decorators cover the cases that DO need framework support:
831
871
 
832
872
  ```typescript
833
- return reply.ok({ name: 'MacBook' }); // 200 { success: true, data }
834
- return reply.fail('Not found', 404); // 404 { success: false, error }
835
- return reply.fail(['err1', 'err2'], 422); // { success: false, errors }
836
- return reply.paginated({ docs, total, page, limit });
873
+ return reply.sendList({ method: 'offset', data, total, page, limit, pages, hasNext, hasPrev });
874
+ return reply.sendList(canonicalListResult); // any kit-shaped paginated/array result
837
875
  return reply.stream(csvReadable, { contentType: 'text/csv', filename: 'export.csv' });
838
876
  ```
839
877
 
878
+ `reply.sendList()` accepts a bare `T[]` or any kit pagination result (`OffsetPaginationResult` / `KeysetPaginationResult` / `AggregatePaginationResult`) and routes through `toCanonicalList` from `@classytic/repo-core/pagination` so the server and the typed `@classytic/arc-next` client share one declaration — `method` as the discriminant cannot drift between them.
879
+
840
880
  **BigInt serialization** — `createApp({ serializeBigInt: true })` converts BigInt → Number in JSON.
841
881
 
842
882
  **Multipart body middleware** — opt-in file upload (no-op for JSON requests, safe to always add):
@@ -872,44 +912,100 @@ defineResource({ name: 'webhook', prefix: '/hooks', skipGlobalPrefix: true })
872
912
  // Registers at /hooks even with createApp({ resourcePrefix: '/api/v1' })
873
913
  ```
874
914
 
915
+ ## Enterprise auth (2.13)
916
+
917
+ Three opt-in surfaces close the procurement-gate gaps without forcing parallel infrastructure. Sessions / refresh / OAuth flows stay in Better Auth's hands.
918
+
919
+ ### SCIM 2.0 — IdP provisioning (`@classytic/arc/scim`)
920
+
921
+ Auto-derived `/scim/v2/Users` + `/scim/v2/Groups` from existing arc resources. Okta / Azure AD / Google Workspace / JumpCloud / OneLogin out of the box. No shadow tables.
922
+
923
+ ```typescript
924
+ import { scimPlugin } from '@classytic/arc/scim';
925
+
926
+ await app.register(scimPlugin, {
927
+ users: { resource: userResource },
928
+ groups: { resource: orgResource },
929
+ bearer: process.env.SCIM_TOKEN, // or: verify: async (req) => …
930
+ });
931
+ ```
932
+
933
+ Mounts `GET/POST/PUT/PATCH/DELETE /scim/v2/Users[/:id]`, same for `Groups`, plus `ServiceProviderConfig` / `ResourceTypes` / `Schemas` discovery. SCIM filter language → arc query DSL. RFC 7644 PatchOp translates to canonical operators (`$set`/`$unset`/`$push`/`$pull`) and flows through `repo.findOneAndUpdate(...)`; PUT goes through `repo.bulkWrite([{ replaceOne }])`. SCIM does **not** run arc's HTTP controller pipeline — audit / multi-tenant / field-policy compose at the kit-plugin layer (`repo.use(...)`) and fire identically for arc REST + SCIM because both surfaces hit the same repository methods. → [references/scim.md](references/scim.md).
934
+
935
+ ### Agent-auth helpers — DPoP + capability mandates
936
+
937
+ For AI-agent flows on protected resources (AP2 / Stripe x402 / MCP authorization). Three new helpers in `@classytic/arc/permissions`:
938
+
939
+ ```typescript
940
+ import { requireAgentScope, requireMandate, requireDPoP } from '@classytic/arc/permissions';
941
+
942
+ defineResource({
943
+ name: 'invoice',
944
+ actions: {
945
+ pay: {
946
+ handler: payInvoice,
947
+ permissions: requireAgentScope({
948
+ capability: 'payment.charge',
949
+ scopes: ['payment.write'],
950
+ requireDPoP: true, // RFC 9449 sender-constrained
951
+ audience: (ctx) => `invoice:${ctx.params?.id}`, // mandate must bind to this resource
952
+ validateAmount: (ctx, m) => (ctx.data as { amount: number }).amount <= (m.cap ?? 0),
953
+ }),
954
+ },
955
+ },
956
+ });
957
+ ```
958
+
959
+ `RequestScope.service` gains optional `mandate` + `dpopJkt` fields. Your `authenticate` callback verifies the mandate JWT (one `jose.jwtVerify()` call) + DPoP proof (one `jose.dpop.verify()` call) and populates them. Arc validates *what's already proved* against the action — no peer-deps on `jose`. → [references/agent-auth.md](references/agent-auth.md).
960
+
961
+ ### Auth-event audit bridge (`@classytic/arc/auth/audit`)
962
+
963
+ BA's `databaseHooks` + endpoint hooks routed through the existing `auditPlugin` — one canonical row shape for resource AND auth events. Single query for "everything user X did".
964
+
965
+ ```typescript
966
+ import { wireBetterAuthAudit } from '@classytic/arc/auth/audit';
967
+
968
+ const audit = wireBetterAuthAudit({
969
+ events: ['session.*', 'user.*', 'mfa.*', 'org.invite.*'],
970
+ });
971
+
972
+ const auth = betterAuth({
973
+ hooks: audit.hooks, // endpoint hooks (MFA, OAuth, password reset)
974
+ databaseHooks: audit.databaseHooks, // sign-in/up/out via session.create/delete
975
+ // ...
976
+ });
977
+
978
+ const app = await createApp({ ... });
979
+ audit.attach(app); // drains boot-time buffer + connects live logger
980
+ ```
981
+
982
+ Buffered until `attach(app)` is called — works for hosts that build BA before Fastify. Manual `audit.emit({ name, subjectId, ... })` for non-BA flows (webhook signature failures, custom MFA).
983
+
984
+ ### What's NOT in arc 2.13
985
+
986
+ SAML / SCIM-EnterpriseUser / device trust / SOC2 attestations / session storage. Reasons + workarounds → [references/enterprise-auth.md](references/enterprise-auth.md). Compliance control matrix → [`docs/compliance/{soc2,hipaa}.md`](../../docs/compliance/).
987
+
875
988
  ## Subpath imports
876
989
 
990
+ The most common imports — full enumeration in [references/api-reference.md](references/api-reference.md).
991
+
877
992
  ```typescript
878
993
  import { defineResource, BaseController, allowPublic } from '@classytic/arc';
879
994
  import { createApp, loadResources } from '@classytic/arc/factory';
880
- import { MemoryCacheStore, RedisCacheStore, QueryCache } from '@classytic/arc/cache';
881
- import { createBetterAuthAdapter, extractBetterAuthOpenApi } from '@classytic/arc/auth';
882
- import { registerBetterAuthMongooseModels } from '@classytic/arc/auth/mongoose';
883
- import { eventPlugin, EventOutbox, MemoryOutboxStore } from '@classytic/arc/events';
884
- import { RedisEventTransport } from '@classytic/arc/events/redis';
885
- import { healthPlugin, gracefulShutdownPlugin, ssePlugin, metricsPlugin, versioningPlugin } from '@classytic/arc/plugins';
886
- import { tracingPlugin } from '@classytic/arc/plugins/tracing';
887
- import { auditPlugin } from '@classytic/arc/audit';
888
- import { idempotencyPlugin } from '@classytic/arc/idempotency';
889
- import { jobsPlugin } from '@classytic/arc/integrations/jobs';
890
- import { websocketPlugin } from '@classytic/arc/integrations/websocket';
891
- import { eventGatewayPlugin } from '@classytic/arc/integrations/event-gateway';
892
- import { webhookPlugin } from '@classytic/arc/integrations/webhooks';
893
- import { createHookSystem } from '@classytic/arc/hooks';
995
+ import { createMongooseAdapter } from '@classytic/mongokit/adapter'; // or sqlitekit/adapter, prismakit/adapter
996
+ import type { DataAdapter, RepositoryLike } from '@classytic/repo-core/adapter';
997
+ import { getUserId, getOrgId, requireOrgId } from '@classytic/arc/scope';
998
+ import { mcpPlugin } from '@classytic/arc/mcp';
894
999
  import { createTestApp, expectArc } from '@classytic/arc/testing';
895
- import { Type, ArcListResponse } from '@classytic/arc/schemas';
896
- import { createStateMachine, CircuitBreaker, withCompensation, defineGuard } from '@classytic/arc/utils';
897
- import { defineMigration } from '@classytic/arc/migrations';
898
- import { mcpPlugin, defineTool, definePrompt, fieldRulesToZod, resourceToTools } from '@classytic/arc/mcp';
899
- import { bulkPreset, multiTenantPreset, type TenantFieldSpec } from '@classytic/arc/presets';
900
- import { createRoleHierarchy } from '@classytic/arc/permissions';
901
- import {
902
- isMember, isService, isElevated, isAuthenticated, hasOrgAccess,
903
- getUserId, getUserRoles, getOrgId, getOrgRoles, getTeamId, getClientId,
904
- getServiceScopes, getScopeContext, getScopeContextMap,
905
- getAncestorOrgIds, isOrgInScope, getRequestScope,
906
- createTenantKeyGenerator,
907
- } from '@classytic/arc/scope';
908
1000
  ```
909
1001
 
910
1002
  ## References
911
1003
 
1004
+ - **[api-reference](references/api-reference.md)** — full subpath import map (every plugin, helper, type)
912
1005
  - **[auth](references/auth.md)** — JWT, Better Auth, API key, custom auth
1006
+ - **[scim](references/scim.md)** — SCIM 2.0 plugin (Okta / Azure AD / Google Workspace provisioning)
1007
+ - **[agent-auth](references/agent-auth.md)** — DPoP + capability mandates (AP2 / x402 / MCP authorization)
1008
+ - **[enterprise-auth](references/enterprise-auth.md)** — what's in vs out of the box for enterprise auth
913
1009
  - **[events](references/events.md)** — Domain events, transports, retry, outbox
914
1010
  - **[integrations](references/integrations.md)** — BullMQ jobs, WebSocket, EventGateway, Streamline, Webhooks
915
1011
  - **[mcp](references/mcp.md)** — MCP tools, custom tools, Better Auth OAuth 2.1