@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.
- package/README.md +16 -12
- package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
- package/dist/EventTransport-CT_52aWU.d.mts +34 -0
- package/dist/EventTransport-DLWoUMHy.mjs +103 -0
- package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/audit.d.mts +199 -0
- package/dist/auth/audit.mjs +288 -0
- package/dist/auth/index.d.mts +3 -3
- package/dist/auth/index.mjs +117 -191
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/describe.d.mts +89 -13
- package/dist/cli/commands/describe.mjs +56 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +147 -48
- package/dist/cli/commands/init.d.mts +13 -0
- package/dist/cli/commands/init.mjs +130 -87
- package/dist/cli/commands/introspect.mjs +8 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/core-DECn6zaU.mjs +1399 -0
- package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CBxLLbn3.mjs} +7 -20
- package/dist/createAggregationRouter-CRIBv4sC.mjs +114 -0
- package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
- package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
- package/dist/docs/index.d.mts +24 -11
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
- package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
- package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
- package/dist/errors-j4aJm1Wg.mjs +184 -0
- package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
- package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
- package/dist/events/index.d.mts +6 -6
- package/dist/events/index.mjs +11 -35
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
- package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
- package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +9 -10
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +60 -11
- package/dist/integrations/streamline.mjs +75 -85
- package/dist/integrations/websocket.mjs +2 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/migrations/index.d.mts +23 -3
- package/dist/migrations/index.mjs +0 -7
- package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
- package/dist/openapi-noXno2CV.mjs +968 -0
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
- package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +16 -31
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +6 -9
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +6 -8
- package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
- package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-DLL32us3.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-DrOa-26E.mjs} +41 -36
- package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-lYhC2gE5.mjs} +1 -1
- package/dist/schemas/index.d.mts +100 -30
- package/dist/schemas/index.mjs +86 -29
- package/dist/scim/index.d.mts +264 -0
- package/dist/scim/index.mjs +963 -0
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +4 -4
- package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
- package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -8
- package/dist/testing/index.mjs +16 -24
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
- package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
- package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +147 -51
- package/skills/arc/references/agent-auth.md +238 -0
- package/skills/arc/references/api-reference.md +187 -0
- package/skills/arc/references/auth.md +354 -7
- package/skills/arc/references/enterprise-auth.md +94 -0
- package/skills/arc/references/events.md +8 -6
- package/skills/arc/references/mcp.md +2 -2
- package/skills/arc/references/multi-tenancy.md +11 -2
- package/skills/arc/references/production.md +10 -9
- package/skills/arc/references/scim.md +247 -0
- package/skills/arc/references/testing.md +1 -1
- package/skills/arc-code-review/SKILL.md +141 -0
- package/skills/arc-code-review/references/anti-patterns.md +911 -0
- package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
- package/skills/arc-code-review/references/migration-recipes.md +700 -0
- package/skills/arc-code-review/references/mongokit-migration.md +386 -0
- package/skills/arc-code-review/references/scaffolding.md +230 -0
- package/skills/arc-code-review/references/severity.md +127 -0
- package/dist/EventTransport-BFQjw9pB.mjs +0 -133
- package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-DUUiiimH.mjs +0 -964
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-CbcQRIch.mjs +0 -1054
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-Rg8axYPz.d.mts +0 -370
- package/dist/openapi-D7G1V7ex.mjs +0 -557
- /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
- /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
- /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
- /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
- /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
- /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
- /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
- /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
- /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
- /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
- /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Resource-oriented backend framework for Fastify
|
|
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/
|
|
246
|
-
"@classytic/repo-core": ">=0.
|
|
247
|
-
"@classytic/streamline": ">=2.
|
|
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.
|
|
368
|
-
"@better-auth/mongo-adapter": "^1.6.
|
|
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.
|
|
372
|
-
"@classytic/
|
|
373
|
-
"@classytic/
|
|
374
|
-
"@classytic/
|
|
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.
|
|
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",
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -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,
|
|
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({
|
|
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
|
|
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 {
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
`
|
|
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/
|
|
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 {
|
|
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.
|
|
834
|
-
return reply.
|
|
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 {
|
|
881
|
-
import {
|
|
882
|
-
import {
|
|
883
|
-
import {
|
|
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
|