@classytic/arc 2.11.3 → 2.13.1
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 +27 -18
- 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/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
- 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 +5 -5
- package/dist/auth/index.mjs +117 -191
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.d.mts +3 -3
- 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 +237 -112
- 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-D72ia0EH.mjs +1399 -0
- package/dist/{createActionRouter-u3ql2EDo.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
- package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
- package/dist/{createApp-BFxtdKy6.mjs → createApp-XX2-N0Yd.mjs} +31 -27
- package/dist/defineEvent-D5h7EvAx.mjs +188 -0
- package/dist/docs/index.d.mts +2 -2
- 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-KrFIQ097.mjs → eventPlugin-CaKTYkYM.mjs} +35 -137
- package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-qXpqTebY.d.mts} +57 -7
- package/dist/events/index.d.mts +164 -5
- package/dist/events/index.mjs +133 -209
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +204 -31
- 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-C8Y0XLAu.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 +3 -3
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-BYCqHCVu.d.mts → index-BTqLEvhu.d.mts} +164 -4
- package/dist/{index-6u4_Gg6G.d.mts → index-BtW7qYwa.d.mts} +661 -281
- package/dist/{index-BdXnTPRj.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-DdQ3O9Pg.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 +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- 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-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- 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-BGUn7Ki1.mjs → openapi-CiOMVW1p.mjs} +143 -13
- 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 +18 -33
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +5 -5
- 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-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
- package/dist/redis-stream-D6HzR1Z_.d.mts +232 -0
- 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-ByZpgjeH.mjs → resourceToTools-C5coh64w.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
- package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-7Vl611Qs.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-BhrzxvyQ.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/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-BH7dEGvU.d.mts → types-BvqwCCSx.d.mts} +77 -29
- package/dist/{types-tgR4Pt8F.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-9beEMe25.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-M9lNLhO8.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +521 -785
- 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-CfVEGaEl.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-D0tT2Tyo.mjs +0 -949
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-DnUsRpuX.mjs +0 -1049
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-Co3lnVmJ.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-BbMrcvGp.d.mts +0 -362
- package/dist/redis-stream-CM8TXTix.d.mts +0 -110
- /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-s5ykdNHr.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
- /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BD5nw6St.d.mts} +0 -0
- /package/dist/{interface-CkkWm5uR.d.mts → interface-DfLGcus7.d.mts} +0 -0
- /package/dist/{interface-Da0r7Lna.d.mts → interface-beEtJyWM.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-BneOJkpi.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/{sessionManager-D-oNWHz3.d.mts → sessionManager-C4Le_UB3.d.mts} +0 -0
- /package/dist/{storage-BwGQXUpd.d.mts → storage-Dfzt4VTl.d.mts} +0 -0
- /package/dist/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +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/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
package/dist/schemas/index.mjs
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
|
+
import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
|
|
1
2
|
import { TypeBoxValidatorCompiler } from "@fastify/type-provider-typebox";
|
|
2
3
|
import { Type, Type as Type$1 } from "@sinclair/typebox";
|
|
3
4
|
//#region src/schemas/index.ts
|
|
4
5
|
/**
|
|
5
|
-
* Paginated list response —
|
|
6
|
-
*
|
|
6
|
+
* Paginated list response — full union of every wire shape `toCanonicalList`
|
|
7
|
+
* emits. Mirrors `PaginatedResult<T>` from `@classytic/repo-core/pagination`:
|
|
8
|
+
*
|
|
9
|
+
* - `{ method: 'offset', data, page, limit, total, pages, hasNext, hasPrev }`
|
|
10
|
+
* - `{ method: 'keyset', data, limit, hasMore, next: string | null }`
|
|
11
|
+
* - `{ method: 'aggregate', ...same as offset }`
|
|
12
|
+
* - `{ data }` (bare list, no `method`)
|
|
13
|
+
*
|
|
14
|
+
* Use `ArcOffsetListResponse` / `ArcKeysetListResponse` /
|
|
15
|
+
* `ArcAggregateListResponse` / `ArcBareListResponse` to pin a single
|
|
16
|
+
* variant when your endpoint never emits the others. HTTP status
|
|
17
|
+
* discriminates success vs error — no `success` field.
|
|
7
18
|
*/
|
|
8
19
|
function ArcListResponse(itemSchema) {
|
|
20
|
+
return Type$1.Union([
|
|
21
|
+
ArcOffsetListResponse(itemSchema),
|
|
22
|
+
ArcKeysetListResponse(itemSchema),
|
|
23
|
+
ArcAggregateListResponse(itemSchema),
|
|
24
|
+
ArcBareListResponse(itemSchema)
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
/** Offset variant — `{ method: 'offset', data, page, limit, total, pages, hasNext, hasPrev }`. */
|
|
28
|
+
function ArcOffsetListResponse(itemSchema) {
|
|
9
29
|
return Type$1.Object({
|
|
10
|
-
|
|
11
|
-
|
|
30
|
+
method: Type$1.Literal("offset"),
|
|
31
|
+
data: Type$1.Array(itemSchema),
|
|
12
32
|
page: Type$1.Integer(),
|
|
13
33
|
limit: Type$1.Integer(),
|
|
14
34
|
total: Type$1.Integer(),
|
|
@@ -17,48 +37,81 @@ function ArcListResponse(itemSchema) {
|
|
|
17
37
|
hasPrev: Type$1.Boolean()
|
|
18
38
|
});
|
|
19
39
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
function ArcItemResponse(itemSchema) {
|
|
40
|
+
/** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
|
|
41
|
+
function ArcKeysetListResponse(itemSchema) {
|
|
24
42
|
return Type$1.Object({
|
|
25
|
-
|
|
26
|
-
data: itemSchema
|
|
43
|
+
method: Type$1.Literal("keyset"),
|
|
44
|
+
data: Type$1.Array(itemSchema),
|
|
45
|
+
limit: Type$1.Integer(),
|
|
46
|
+
hasMore: Type$1.Boolean(),
|
|
47
|
+
next: Type$1.Union([Type$1.String(), Type$1.Null()])
|
|
27
48
|
});
|
|
28
49
|
}
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
*/
|
|
32
|
-
function ArcMutationResponse(itemSchema) {
|
|
50
|
+
/** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
|
|
51
|
+
function ArcAggregateListResponse(itemSchema) {
|
|
33
52
|
return Type$1.Object({
|
|
34
|
-
|
|
35
|
-
data: itemSchema,
|
|
36
|
-
|
|
53
|
+
method: Type$1.Literal("aggregate"),
|
|
54
|
+
data: Type$1.Array(itemSchema),
|
|
55
|
+
page: Type$1.Integer(),
|
|
56
|
+
limit: Type$1.Integer(),
|
|
57
|
+
total: Type$1.Integer(),
|
|
58
|
+
pages: Type$1.Integer(),
|
|
59
|
+
hasNext: Type$1.Boolean(),
|
|
60
|
+
hasPrev: Type$1.Boolean()
|
|
37
61
|
});
|
|
38
62
|
}
|
|
63
|
+
/** Bare variant — `{ data }`, no `method` discriminant. */
|
|
64
|
+
function ArcBareListResponse(itemSchema) {
|
|
65
|
+
return Type$1.Object({ data: Type$1.Array(itemSchema) });
|
|
66
|
+
}
|
|
39
67
|
/**
|
|
40
|
-
* Delete response — `{
|
|
68
|
+
* Delete response — `{ message, id?, soft? }` raw at the top level.
|
|
41
69
|
*/
|
|
42
70
|
function ArcDeleteResponse() {
|
|
43
71
|
return Type$1.Object({
|
|
44
|
-
|
|
45
|
-
|
|
72
|
+
message: Type$1.String(),
|
|
73
|
+
id: Type$1.Optional(Type$1.String()),
|
|
74
|
+
soft: Type$1.Optional(Type$1.Boolean())
|
|
46
75
|
});
|
|
47
76
|
}
|
|
48
77
|
/**
|
|
49
|
-
*
|
|
78
|
+
* Single field-scoped error detail — `Type.Unsafe<ErrorDetail>` over the
|
|
79
|
+
* canonical JSON Schema `errorDetailSchema` from
|
|
80
|
+
* `@classytic/repo-core/errors`. One schema constant, two adapters
|
|
81
|
+
* (JSON-Schema + TypeBox), zero drift surface — the schema and the TS
|
|
82
|
+
* type both come from repo-core.
|
|
83
|
+
*
|
|
84
|
+
* Exported standalone so hosts can embed it in custom 422 / 409 response
|
|
85
|
+
* schemas.
|
|
86
|
+
*/
|
|
87
|
+
function ArcErrorDetail() {
|
|
88
|
+
return Type$1.Unsafe(errorDetailSchema);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Error response schema — `Type.Unsafe<ErrorContract>` over the canonical
|
|
92
|
+
* JSON Schema `errorContractSchema` from `@classytic/repo-core/errors`.
|
|
93
|
+
* Same trick as `ArcErrorDetail`: the schema bytes and the TS type both
|
|
94
|
+
* come from repo-core, so the JSON-Schema sibling
|
|
95
|
+
* (`errorContractSchema`) and this TypeBox helper cannot drift —
|
|
96
|
+
* literally one source.
|
|
50
97
|
*/
|
|
51
98
|
function ArcErrorResponse() {
|
|
52
|
-
return Type$1.
|
|
53
|
-
success: Type$1.Literal(false),
|
|
54
|
-
error: Type$1.String(),
|
|
55
|
-
code: Type$1.Optional(Type$1.String()),
|
|
56
|
-
message: Type$1.Optional(Type$1.String())
|
|
57
|
-
});
|
|
99
|
+
return Type$1.Unsafe(errorContractSchema);
|
|
58
100
|
}
|
|
59
101
|
/**
|
|
60
102
|
* Standard pagination + sorting + filtering query parameters.
|
|
61
103
|
* Matches Arc's list endpoint conventions.
|
|
104
|
+
*
|
|
105
|
+
* `select` accepts every shape `QueryResolver` preserves DB-agnostically
|
|
106
|
+
* (gotcha #5):
|
|
107
|
+
*
|
|
108
|
+
* - `string` — `"name email -password"` (Mongoose space-separated)
|
|
109
|
+
* - `string[]` — `["name", "email", "-password"]` (Arc parser)
|
|
110
|
+
* - `Record<string, 0 | 1>` — `{ name: 1, email: 1, password: 0 }` (Mongo projection)
|
|
111
|
+
*
|
|
112
|
+
* Narrowing `select` to `string` would have rejected the array and
|
|
113
|
+
* projection forms at the request-validation gate even though arc passes
|
|
114
|
+
* them through unchanged.
|
|
62
115
|
*/
|
|
63
116
|
function ArcPaginationQuery() {
|
|
64
117
|
return Type$1.Object({
|
|
@@ -72,9 +125,13 @@ function ArcPaginationQuery() {
|
|
|
72
125
|
default: 20
|
|
73
126
|
})),
|
|
74
127
|
sort: Type$1.Optional(Type$1.String()),
|
|
75
|
-
select: Type$1.Optional(Type$1.
|
|
128
|
+
select: Type$1.Optional(Type$1.Union([
|
|
129
|
+
Type$1.String(),
|
|
130
|
+
Type$1.Array(Type$1.String()),
|
|
131
|
+
Type$1.Record(Type$1.String(), Type$1.Union([Type$1.Literal(0), Type$1.Literal(1)]))
|
|
132
|
+
])),
|
|
76
133
|
populate: Type$1.Optional(Type$1.Any())
|
|
77
134
|
}, { additionalProperties: true });
|
|
78
135
|
}
|
|
79
136
|
//#endregion
|
|
80
|
-
export { ArcDeleteResponse, ArcErrorResponse,
|
|
137
|
+
export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, Type, TypeBoxValidatorCompiler };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
2
|
+
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
3
|
+
|
|
4
|
+
//#region src/scim/mapping.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* SCIM 2.0 ↔ arc resource mapping
|
|
7
|
+
*
|
|
8
|
+
* Most arc apps store users / orgs in a shape that doesn't match SCIM
|
|
9
|
+
* verbatim. This module owns the bidirectional translation:
|
|
10
|
+
*
|
|
11
|
+
* - **Inbound** (`scimToResource`): SCIM JSON → arc resource shape
|
|
12
|
+
* - **Outbound** (`resourceToScim`): arc resource → SCIM JSON
|
|
13
|
+
*
|
|
14
|
+
* Defaults track Better Auth's `user` / `organization` schema so apps using
|
|
15
|
+
* the BA overlay get a working SCIM endpoint with zero mapping config.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Mapping spec for a single SCIM resource type. Apps override only the
|
|
19
|
+
* fields that diverge from the BA defaults.
|
|
20
|
+
*/
|
|
21
|
+
interface ScimResourceMapping {
|
|
22
|
+
/** SCIM resource schema URI (e.g. `urn:ietf:params:scim:schemas:core:2.0:User`). */
|
|
23
|
+
schema: string;
|
|
24
|
+
/**
|
|
25
|
+
* SCIM attribute → backend field. Used by both filter parser and
|
|
26
|
+
* outbound serializer. Omit an attribute to mark it non-filterable.
|
|
27
|
+
*/
|
|
28
|
+
attributes: Record<string, string>;
|
|
29
|
+
/** Inverse for outbound serialization — built from `attributes` by default. */
|
|
30
|
+
reverseAttributes?: Record<string, string>;
|
|
31
|
+
/** Custom inbound transform — runs after attribute mapping. */
|
|
32
|
+
fromScim?: (scim: Record<string, unknown>, mapped: Record<string, unknown>) => Record<string, unknown>;
|
|
33
|
+
/** Custom outbound transform — runs after attribute mapping. */
|
|
34
|
+
toScim?: (resource: Record<string, unknown>, mapped: Record<string, unknown>) => Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
declare const SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
|
|
37
|
+
declare const SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
|
|
38
|
+
declare const SCIM_ENTERPRISE_USER_SCHEMA = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
|
|
39
|
+
declare const DEFAULT_USER_MAPPING: ScimResourceMapping;
|
|
40
|
+
declare const DEFAULT_GROUP_MAPPING: ScimResourceMapping;
|
|
41
|
+
/** Translate inbound SCIM JSON → resource shape. */
|
|
42
|
+
declare function scimToResource(scim: Record<string, unknown>, mapping: ScimResourceMapping): Record<string, unknown>;
|
|
43
|
+
/** Translate resource → SCIM JSON. */
|
|
44
|
+
declare function resourceToScim(resource: Record<string, unknown>, mapping: ScimResourceMapping, baseUrl: string): Record<string, unknown>;
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/scim/types.d.ts
|
|
47
|
+
/**
|
|
48
|
+
* One arc resource bound into the SCIM surface.
|
|
49
|
+
*
|
|
50
|
+
* Pass the resource definition you already register with arc — the plugin
|
|
51
|
+
* reads `resource.adapter.repository`. Whatever kit plugins (`auditPlugin`,
|
|
52
|
+
* `multiTenantPlugin`, `fieldFilterPlugin`, …) you wire at construction
|
|
53
|
+
* time fire for SCIM the same way they fire for arc REST, because both
|
|
54
|
+
* surfaces call the same repository methods.
|
|
55
|
+
*/
|
|
56
|
+
interface ScimResourceBinding {
|
|
57
|
+
resource: {
|
|
58
|
+
name: string;
|
|
59
|
+
adapter: {
|
|
60
|
+
repository: RepositoryLike;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Override the SCIM ↔ resource mapping. Defaults to the BA-aligned shape
|
|
65
|
+
* (BA `user.email` ↔ SCIM `userName`, etc.). Pass a partial — only the
|
|
66
|
+
* diverging fields need to be overridden.
|
|
67
|
+
*/
|
|
68
|
+
mapping?: Partial<ScimResourceMapping>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Configuration for `scimPlugin`. One of `bearer` / `verify` is required.
|
|
72
|
+
*/
|
|
73
|
+
interface ScimPluginOptions {
|
|
74
|
+
/** User resource binding (mounts `/scim/v2/Users`). */
|
|
75
|
+
users: ScimResourceBinding;
|
|
76
|
+
/** Group / organization resource binding (mounts `/scim/v2/Groups`). Optional. */
|
|
77
|
+
groups?: ScimResourceBinding;
|
|
78
|
+
/**
|
|
79
|
+
* Static bearer token. Mutually exclusive with `verify` — pass one or the other.
|
|
80
|
+
*/
|
|
81
|
+
bearer?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Custom verifier — runs on every SCIM request. Return `true` to accept,
|
|
84
|
+
* `false` (or throw) to reject. Use for OIDC-secured deployments where
|
|
85
|
+
* the SCIM token is a short-lived JWT.
|
|
86
|
+
*/
|
|
87
|
+
verify?: (request: FastifyRequest) => boolean | Promise<boolean>;
|
|
88
|
+
/**
|
|
89
|
+
* Mount prefix. Defaults to `/scim/v2` — RFC-compliant, what every IdP expects.
|
|
90
|
+
*/
|
|
91
|
+
prefix?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Maximum page size SCIM clients can request via `count` — defaults to 200
|
|
94
|
+
* (Okta's default). Larger values may degrade IdP-side reconciliation.
|
|
95
|
+
*/
|
|
96
|
+
maxResults?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Optional structured-log hook — called once per SCIM request with a
|
|
99
|
+
* canonical observability payload. Defaults to `request.log.info(...)`.
|
|
100
|
+
*/
|
|
101
|
+
observe?: (event: ScimObservedEvent) => void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Single observability event emitted per SCIM request. Stable shape so
|
|
105
|
+
* downstream metrics / logging stacks can pin dashboards without arc-version
|
|
106
|
+
* drift.
|
|
107
|
+
*/
|
|
108
|
+
interface ScimObservedEvent {
|
|
109
|
+
/** SCIM resource type (`Users` / `Groups`) or `discovery` for meta endpoints. */
|
|
110
|
+
resourceType: "Users" | "Groups" | "discovery";
|
|
111
|
+
/**
|
|
112
|
+
* SCIM operation. CRUD: `list`, `get`, `create`, `replace`, `patch`, `delete`.
|
|
113
|
+
* Discovery: `discovery.<endpoint>`.
|
|
114
|
+
*/
|
|
115
|
+
op: string;
|
|
116
|
+
/** HTTP status code returned. */
|
|
117
|
+
status: number;
|
|
118
|
+
/** Wall-clock duration in ms (rounded). */
|
|
119
|
+
durationMs: number;
|
|
120
|
+
/** SCIM error type when the request failed (`invalidFilter`, etc.). Undefined on success. */
|
|
121
|
+
scimType?: string;
|
|
122
|
+
/** Path component (`/Users`, `/Users/:id`, `/ServiceProviderConfig`, …). */
|
|
123
|
+
path: string;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/scim/errors.d.ts
|
|
127
|
+
/**
|
|
128
|
+
* SCIM 2.0 error responses (RFC 7644 §3.12)
|
|
129
|
+
*
|
|
130
|
+
* Wire shape:
|
|
131
|
+
* ```json
|
|
132
|
+
* {
|
|
133
|
+
* "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
|
134
|
+
* "status": "400",
|
|
135
|
+
* "scimType": "invalidFilter",
|
|
136
|
+
* "detail": "Attribute 'xyz' is not filterable"
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
type ScimType = "invalidFilter" | "tooMany" | "uniqueness" | "mutability" | "invalidSyntax" | "invalidPath" | "noTarget" | "invalidValue" | "invalidVers" | "sensitive";
|
|
141
|
+
declare class ScimError extends Error {
|
|
142
|
+
readonly statusCode: number;
|
|
143
|
+
readonly scimType?: ScimType;
|
|
144
|
+
constructor(statusCode: number, scimType: ScimType | undefined, detail: string);
|
|
145
|
+
toResponse(): Record<string, unknown>;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/scim/filter.d.ts
|
|
149
|
+
/**
|
|
150
|
+
* SCIM 2.0 filter language parser (RFC 7644 §3.4.2.2)
|
|
151
|
+
*
|
|
152
|
+
* Translates SCIM filter expressions into arc's query DSL so existing
|
|
153
|
+
* resources can serve `/scim/v2/Users?filter=...` without per-resource glue.
|
|
154
|
+
*
|
|
155
|
+
* Supports the subset every IdP actually emits in production:
|
|
156
|
+
* - Comparison ops: eq, ne, co, sw, ew, gt, ge, lt, le, pr (present)
|
|
157
|
+
* - Logical: and, or, not
|
|
158
|
+
* - Grouping: ( )
|
|
159
|
+
* - Attribute paths: `userName`, `name.familyName`, `emails[type eq "work"].value`
|
|
160
|
+
*
|
|
161
|
+
* Out of scope (yields a 400 with a clear reason):
|
|
162
|
+
* - Complex value paths beyond one level
|
|
163
|
+
* - Sub-attribute traversal in operands
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* parseScimFilter('userName eq "alice@acme.com"')
|
|
167
|
+
* → { userName: 'alice@acme.com' }
|
|
168
|
+
*
|
|
169
|
+
* parseScimFilter('active eq true and name.familyName sw "S"')
|
|
170
|
+
* → { $and: [{ active: true }, { 'name.familyName': { $regex: '^S' } }] }
|
|
171
|
+
*/
|
|
172
|
+
type FilterNode = {
|
|
173
|
+
kind: "and";
|
|
174
|
+
left: FilterNode;
|
|
175
|
+
right: FilterNode;
|
|
176
|
+
} | {
|
|
177
|
+
kind: "or";
|
|
178
|
+
left: FilterNode;
|
|
179
|
+
right: FilterNode;
|
|
180
|
+
} | {
|
|
181
|
+
kind: "not";
|
|
182
|
+
child: FilterNode;
|
|
183
|
+
} | {
|
|
184
|
+
kind: "compare";
|
|
185
|
+
attr: string;
|
|
186
|
+
op: "eq" | "ne" | "co" | "sw" | "ew" | "gt" | "ge" | "lt" | "le";
|
|
187
|
+
value: string | number | boolean | null;
|
|
188
|
+
} | {
|
|
189
|
+
kind: "present";
|
|
190
|
+
attr: string;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Parse a SCIM 2.0 filter expression and translate to arc/Mongo query shape.
|
|
194
|
+
*
|
|
195
|
+
* @param filter Raw filter string from `?filter=...`
|
|
196
|
+
* @param mapAttr Mapping function: SCIM attr → backend field name. Return
|
|
197
|
+
* `undefined` to deny (yields 400 invalidFilter). Use `IDENTITY_MAP` when
|
|
198
|
+
* the resource exposes SCIM-named attributes directly.
|
|
199
|
+
*/
|
|
200
|
+
declare function parseScimFilter(filter: string, mapAttr: (scimAttr: string) => string | undefined): Record<string, unknown>;
|
|
201
|
+
/** Pass-through mapper for resources that already use SCIM attribute names. */
|
|
202
|
+
declare const IDENTITY_MAP: (a: string) => string;
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/scim/patch.d.ts
|
|
205
|
+
/**
|
|
206
|
+
* SCIM 2.0 PATCH parser (RFC 7644 §3.5.2)
|
|
207
|
+
*
|
|
208
|
+
* Translates SCIM PATCH operations into a flat update object the resource's
|
|
209
|
+
* existing PATCH handler can apply. Supports the three operations every IdP
|
|
210
|
+
* actually emits: `add`, `replace`, `remove`.
|
|
211
|
+
*
|
|
212
|
+
* **Path support**:
|
|
213
|
+
* - Simple attribute: `userName` → `{ userName: <value> }`
|
|
214
|
+
* - Sub-attribute: `name.familyName` → `{ 'name.familyName': <value> }`
|
|
215
|
+
* - No path (op-level value): `replace` with object value → spread into update
|
|
216
|
+
* - Multi-value with filter: `emails[type eq "work"].value` — parsed but
|
|
217
|
+
* translated to a `$set` on the matching array element by index lookup
|
|
218
|
+
* (host resolves index via the supplied `lookupArrayIndex` callback)
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* parseScimPatch({
|
|
222
|
+
* schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
|
|
223
|
+
* Operations: [
|
|
224
|
+
* { op: 'replace', path: 'displayName', value: 'Alice S.' },
|
|
225
|
+
* { op: 'add', path: 'emails', value: [{ type: 'work', value: 'a@x.com' }] },
|
|
226
|
+
* { op: 'remove', path: 'emails[type eq "old"]' },
|
|
227
|
+
* ],
|
|
228
|
+
* })
|
|
229
|
+
* → { $set: { displayName: 'Alice S.' }, $push: { emails: ... }, $pull: { emails: ... } }
|
|
230
|
+
*/
|
|
231
|
+
interface ScimPatchRequest {
|
|
232
|
+
schemas?: readonly string[];
|
|
233
|
+
Operations?: readonly ScimPatchOperation[];
|
|
234
|
+
operations?: readonly ScimPatchOperation[];
|
|
235
|
+
}
|
|
236
|
+
interface ScimPatchOperation {
|
|
237
|
+
op: string;
|
|
238
|
+
path?: string;
|
|
239
|
+
value?: unknown;
|
|
240
|
+
}
|
|
241
|
+
interface ScimUpdate {
|
|
242
|
+
/** Direct field assignments → resource's `update` payload. */
|
|
243
|
+
$set: Record<string, unknown>;
|
|
244
|
+
/** Multi-value array additions → `$push` on the field. */
|
|
245
|
+
$push: Record<string, unknown>;
|
|
246
|
+
/** Multi-value removals — caller resolves which element. */
|
|
247
|
+
$pull: Record<string, unknown>;
|
|
248
|
+
/** Field unset (remove without filter). */
|
|
249
|
+
$unset: Record<string, true>;
|
|
250
|
+
}
|
|
251
|
+
declare function parseScimPatch(req: ScimPatchRequest): ScimUpdate;
|
|
252
|
+
/**
|
|
253
|
+
* Flatten a {@link ScimUpdate} into a plain `{ field: value }` object suitable
|
|
254
|
+
* for arc resource `PATCH` handlers that expect a partial document. Drops
|
|
255
|
+
* `$push` / `$pull` / `$unset` semantics — use {@link parseScimPatch} directly
|
|
256
|
+
* when the host needs the full op stream (e.g. to issue array mutations).
|
|
257
|
+
*/
|
|
258
|
+
declare function scimUpdateToFlatPatch(update: ScimUpdate): Record<string, unknown>;
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/scim/index.d.ts
|
|
261
|
+
declare const scimPlugin: FastifyPluginAsync<ScimPluginOptions>;
|
|
262
|
+
declare const _default: FastifyPluginAsync<ScimPluginOptions>;
|
|
263
|
+
//#endregion
|
|
264
|
+
export { DEFAULT_GROUP_MAPPING, DEFAULT_USER_MAPPING, type FilterNode, IDENTITY_MAP, SCIM_ENTERPRISE_USER_SCHEMA, SCIM_GROUP_SCHEMA, SCIM_USER_SCHEMA, ScimError, type ScimObservedEvent, type ScimPatchRequest, type ScimPluginOptions, type ScimResourceBinding, type ScimResourceMapping, type ScimType, type ScimUpdate, _default as default, parseScimFilter, parseScimPatch, resourceToScim, scimPlugin, scimToResource, scimUpdateToFlatPatch };
|