@classytic/arc 2.11.4 → 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.
Files changed (166) 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-D72ia0EH.mjs +1399 -0
  26. package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
  27. package/dist/createAggregationRouter-CyecOxnO.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 +1 -1
  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-D7G1V7ex.mjs → openapi-CiOMVW1p.mjs} +143 -13
  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-C5coh64w.mjs} +224 -71
  99. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
  100. package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-7Vl611Qs.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/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  152. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  153. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  154. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  155. /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  156. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  157. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  158. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  159. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  160. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
  161. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  162. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  163. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  164. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  165. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  166. /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
@@ -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 — matches Arc's runtime format:
6
- * `{ success, docs: [...], page, limit, total, pages, hasNext, hasPrev }`
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
- success: Type$1.Boolean(),
11
- docs: Type$1.Array(itemSchema),
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
- * Single item response — `{ success, data: {...} }`
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
- success: Type$1.Boolean(),
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
- * Mutation (create/update) response — `{ success, data: {...}, message? }`
31
- */
32
- function ArcMutationResponse(itemSchema) {
50
+ /** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
51
+ function ArcAggregateListResponse(itemSchema) {
33
52
  return Type$1.Object({
34
- success: Type$1.Boolean(),
35
- data: itemSchema,
36
- message: Type$1.Optional(Type$1.String())
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 — `{ success, message }`
68
+ * Delete response — `{ message, id?, soft? }` raw at the top level.
41
69
  */
42
70
  function ArcDeleteResponse() {
43
71
  return Type$1.Object({
44
- success: Type$1.Boolean(),
45
- message: Type$1.Optional(Type$1.String())
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
- * Error response schema
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.Object({
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.String()),
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, ArcItemResponse, ArcListResponse, ArcMutationResponse, ArcPaginationQuery, Type, TypeBoxValidatorCompiler };
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 };