@classytic/arc 2.9.1 → 2.10.8

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 (132) hide show
  1. package/README.md +20 -91
  2. package/dist/{BaseController-Vu2yc56T.mjs → BaseController-DVNKvoX4.mjs} +154 -170
  3. package/dist/{ResourceRegistry-Dq3_zBQP.mjs → ResourceRegistry-CcN2LVrc.mjs} +1 -1
  4. package/dist/actionPermissions-TUVR3uiZ.mjs +22 -0
  5. package/dist/adapters/index.d.mts +3 -3
  6. package/dist/adapters/index.mjs +2 -2
  7. package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
  8. package/dist/audit/index.d.mts +38 -3
  9. package/dist/audit/index.mjs +54 -22
  10. package/dist/auth/index.d.mts +2 -2
  11. package/dist/auth/index.mjs +3 -3
  12. package/dist/cache/index.d.mts +17 -15
  13. package/dist/cache/index.mjs +16 -15
  14. package/dist/{caching-CjybdRwx.mjs → caching-3h93rkJM.mjs} +8 -3
  15. package/dist/cli/commands/describe.mjs +1 -1
  16. package/dist/cli/commands/docs.mjs +2 -2
  17. package/dist/cli/commands/init.mjs +1 -1
  18. package/dist/cli/commands/introspect.mjs +1 -1
  19. package/dist/context/index.d.mts +58 -0
  20. package/dist/context/index.mjs +2 -0
  21. package/dist/core/index.d.mts +2 -2
  22. package/dist/core/index.mjs +3 -4
  23. package/dist/{defineResource-C__jkwvs.mjs → core-3MWJosCH.mjs} +174 -94
  24. package/dist/{createActionRouter-DH1YFL9m.mjs → createActionRouter-C8UUB3Px.mjs} +1 -1
  25. package/dist/{createApp-CBJUJKGP.mjs → createApp-BwnEAO2h.mjs} +53 -19
  26. package/dist/docs/index.d.mts +1 -1
  27. package/dist/docs/index.mjs +2 -2
  28. package/dist/{elevation-DxQ6ACbt.mjs → elevation-Dci0AYLT.mjs} +2 -2
  29. package/dist/errorHandler-2ii4RIYr.d.mts +114 -0
  30. package/dist/{errorHandler-CZDW4EXS.mjs → errorHandler-CSxe7KIM.mjs} +1 -1
  31. package/dist/{eventPlugin-Dl7MoVWH.mjs → eventPlugin-ByU4Cv0e.mjs} +1 -1
  32. package/dist/{eventPlugin-BxvaCIZF.d.mts → eventPlugin-D1ThQ1Pp.d.mts} +1 -1
  33. package/dist/events/index.d.mts +8 -5
  34. package/dist/events/index.mjs +87 -52
  35. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  36. package/dist/events/transports/redis.d.mts +1 -1
  37. package/dist/factory/index.d.mts +1 -1
  38. package/dist/factory/index.mjs +1 -1
  39. package/dist/{types-DZi1aYhm.d.mts → fields-C8Y0XLAu.d.mts} +122 -2
  40. package/dist/hooks/index.d.mts +1 -1
  41. package/dist/idempotency/index.d.mts +5 -2
  42. package/dist/idempotency/index.mjs +46 -37
  43. package/dist/{interface-YrWsmKqE.d.mts → index-BGbpGVyM.d.mts} +2107 -2756
  44. package/dist/{index-CtGKT0lf.d.mts → index-BziRPS4H.d.mts} +81 -7
  45. package/dist/{index-C-xjcA6F.d.mts → index-C_Noptz-.d.mts} +284 -409
  46. package/dist/{index-Cibkchnx.d.mts → index-EqQN6p0W.d.mts} +3 -3
  47. package/dist/index.d.mts +6 -219
  48. package/dist/index.mjs +10 -131
  49. package/dist/integrations/event-gateway.d.mts +1 -1
  50. package/dist/integrations/event-gateway.mjs +1 -1
  51. package/dist/integrations/index.d.mts +1 -1
  52. package/dist/integrations/mcp/index.d.mts +2 -2
  53. package/dist/integrations/mcp/index.mjs +1 -1
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/interface-yhyb_pLY.d.mts +77 -0
  57. package/dist/logger/index.d.mts +81 -0
  58. package/dist/{logger-CDjpjySd.mjs → logger/index.mjs} +1 -6
  59. package/dist/{memory-BFAYkf8H.mjs → memory-DqI-449b.mjs} +23 -8
  60. package/dist/middleware/index.d.mts +109 -0
  61. package/dist/middleware/index.mjs +70 -0
  62. package/dist/multipartBody-CUQGVlM_.mjs +123 -0
  63. package/dist/{openapi-CXuTG1M9.mjs → openapi-DpNpqBmo.mjs} +9 -7
  64. package/dist/org/index.d.mts +2 -2
  65. package/dist/permissions/index.d.mts +3 -4
  66. package/dist/permissions/index.mjs +5 -5
  67. package/dist/{permissions-oNZawnkR.mjs → permissions-wkqRwicB.mjs} +315 -397
  68. package/dist/pipe-CGJxqDGx.mjs +62 -0
  69. package/dist/pipeline/index.d.mts +62 -0
  70. package/dist/pipeline/index.mjs +53 -0
  71. package/dist/plugins/index.d.mts +23 -3
  72. package/dist/plugins/index.mjs +9 -11
  73. package/dist/plugins/response-cache.mjs +1 -1
  74. package/dist/plugins/tracing-entry.mjs +1 -1
  75. package/dist/presets/filesUpload.d.mts +3 -3
  76. package/dist/presets/filesUpload.mjs +255 -1
  77. package/dist/presets/index.d.mts +1 -1
  78. package/dist/presets/index.mjs +2 -2
  79. package/dist/presets/multiTenant.d.mts +1 -1
  80. package/dist/presets/multiTenant.mjs +43 -9
  81. package/dist/presets/search.d.mts +91 -4
  82. package/dist/presets/search.mjs +1 -1
  83. package/dist/{presets-hM4WhNWY.mjs → presets-CrwOvuXI.mjs} +1 -1
  84. package/dist/{queryCachePlugin-DbUVroUG.mjs → queryCachePlugin-ChLNZvFT.mjs} +9 -9
  85. package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-Dumka73q.d.mts} +1 -1
  86. package/dist/{queryParser-Cs-6SHQK.mjs → queryParser-NR__Qiju.mjs} +69 -2
  87. package/dist/{redis-stream-Bz-4q96t.d.mts → redis-stream-bkO88VHx.d.mts} +1 -1
  88. package/dist/registry/index.d.mts +1 -1
  89. package/dist/registry/index.mjs +1 -1
  90. package/dist/{requestContext-DYtmNpm5.mjs → requestContext-C38GskNt.mjs} +1 -1
  91. package/dist/{resourceToTools-C3cWymnW.mjs → resourceToTools-BhF3JV5p.mjs} +8 -3
  92. package/dist/scope/index.d.mts +2 -2
  93. package/dist/scope/index.mjs +2 -2
  94. package/dist/{sse-CJpt7LGI.mjs → sse-D8UeDwis.mjs} +1 -1
  95. package/dist/{store-helpers-DFiZl5TL.mjs → store-helpers-DYYUQbQN.mjs} +4 -0
  96. package/dist/testing/index.d.mts +6 -5
  97. package/dist/testing/index.mjs +17 -10
  98. package/dist/types/index.d.mts +5 -5
  99. package/dist/types/index.mjs +1 -31
  100. package/dist/types-CDnTEpga.mjs +27 -0
  101. package/dist/{types-CoSzA-s-.d.mts → types-CVKBssX5.d.mts} +1 -1
  102. package/dist/{types-CunEX4UX.d.mts → types-CVdgPXBW.d.mts} +20 -7
  103. package/dist/utils/index.d.mts +277 -3
  104. package/dist/utils/index.mjs +4 -5
  105. package/dist/{utils-B7FuRr9w.mjs → utils-LMwVidKy.mjs} +303 -2
  106. package/dist/{versioning-Cm8qoFDg.mjs → versioning-B6mimogM.mjs} +3 -5
  107. package/dist/versioning-CeUXHfjw.d.mts +117 -0
  108. package/package.json +31 -18
  109. package/skills/arc/SKILL.md +8 -12
  110. package/skills/arc/references/production.md +0 -41
  111. package/dist/circuitBreaker-CvXkjfrW.d.mts +0 -206
  112. package/dist/circuitBreaker-l18oRgL5.mjs +0 -284
  113. package/dist/core-DNncu0xF.mjs +0 -34
  114. package/dist/dynamic/index.d.mts +0 -93
  115. package/dist/dynamic/index.mjs +0 -122
  116. package/dist/errorHandler-DixGcttC.d.mts +0 -218
  117. package/dist/fields-BC7zcmI9.d.mts +0 -121
  118. package/dist/filesUpload-q8oHt--L.mjs +0 -377
  119. package/dist/interface-DplgQO2e.d.mts +0 -54
  120. package/dist/policies/index.d.mts +0 -425
  121. package/dist/policies/index.mjs +0 -318
  122. package/dist/rpc/index.d.mts +0 -90
  123. package/dist/rpc/index.mjs +0 -248
  124. /package/dist/{EventTransport-CqZ8FyM_.d.mts → EventTransport-CfVEGaEl.d.mts} +0 -0
  125. /package/dist/{applyPermissionResult-bqGpo9ML.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
  126. /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
  127. /package/dist/{elevation-B6S5csVA.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
  128. /package/dist/{errors-CqWnSqM-.mjs → errors-BqdUDja_.mjs} +0 -0
  129. /package/dist/{fields-CU6FlaDV.mjs → fields-CTMWOUDt.mjs} +0 -0
  130. /package/dist/{keys-qcD-TVJl.mjs → keys-nWQGUTu1.mjs} +0 -0
  131. /package/dist/{types-ZUu_h0jp.mjs → types-D57iXYb8.mjs} +0 -0
  132. /package/dist/{types-BD85MlEK.d.mts → types-tgR4Pt8F.d.mts} +0 -0
@@ -1,4 +1,4 @@
1
- import { Kt as ResourceDefinition } from "./interface-YrWsmKqE.mjs";
1
+ import { B as ResourceDefinition } from "./index-BGbpGVyM.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/integrations/mcp/types.d.ts
@@ -1,11 +1,12 @@
1
- import { b as Authenticator } from "./interface-YrWsmKqE.mjs";
2
- import { n as ElevationOptions } from "./elevation-B6S5csVA.mjs";
1
+ import { Gt as Authenticator } from "./index-BGbpGVyM.mjs";
2
+ import { n as ElevationOptions } from "./elevation-s5ykdNHr.mjs";
3
+ import { o as EventTransport } from "./EventTransport-CfVEGaEl.mjs";
3
4
  import { t as ExternalOpenApiPaths } from "./externalPaths-Bapitwvd.mjs";
4
- import { i as CacheStore } from "./interface-DplgQO2e.mjs";
5
- import { r as QueryCachePluginOptions } from "./queryCachePlugin-CnTZZTC5.mjs";
6
- import { o as EventTransport } from "./EventTransport-CqZ8FyM_.mjs";
7
- import { t as EventPluginOptions } from "./eventPlugin-BxvaCIZF.mjs";
8
- import { a as VersioningOptions, g as CachingOptions, p as SSEOptions, t as ErrorHandlerOptions, u as MetricsOptions } from "./errorHandler-DixGcttC.mjs";
5
+ import { r as CacheStore } from "./interface-yhyb_pLY.mjs";
6
+ import { r as QueryCachePluginOptions } from "./queryCachePlugin-Dumka73q.mjs";
7
+ import { t as EventPluginOptions } from "./eventPlugin-D1ThQ1Pp.mjs";
8
+ import { f as CachingOptions, l as SSEOptions, o as MetricsOptions, t as VersioningOptions } from "./versioning-CeUXHfjw.mjs";
9
+ import { t as ErrorHandlerOptions } from "./errorHandler-2ii4RIYr.mjs";
9
10
  import { r as IdempotencyStore } from "./interface-B-pe8fhj.mjs";
10
11
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
11
12
 
@@ -149,6 +150,18 @@ type HelmetOptions = Record<string, unknown>;
149
150
  type RateLimitOpts = Record<string, unknown> & {
150
151
  max?: number;
151
152
  timeWindow?: string | number;
153
+ /**
154
+ * Path patterns to exempt from rate limiting. Supports exact match
155
+ * (`/health`) or prefix match with trailing `*` (`/api/auth/*`).
156
+ *
157
+ * Implemented by synthesising a `@fastify/rate-limit` `allowList`
158
+ * function. When combined with a user-supplied `allowList`, both
159
+ * are OR-ed together (path OR ip/custom match skips the limit).
160
+ *
161
+ * Use this for endpoints that are hit frequently but independently
162
+ * of user traffic — session heartbeat, webhooks, health probes.
163
+ */
164
+ skipPaths?: string[];
152
165
  };
153
166
  /**
154
167
  * Arc's built-in JWT auth
@@ -1,8 +1,213 @@
1
- import { J as OpenApiSchemas, X as ParsedQuery, p as AnyRecord, tt as QueryParserInterface } from "../interface-YrWsmKqE.mjs";
1
+ import { S as QueryParserInterface, Yt as AnyRecord, b as ParsedQuery, mt as OpenApiSchemas } from "../index-BGbpGVyM.mjs";
2
+ import { n as ErrorMapper } from "../errorHandler-2ii4RIYr.mjs";
2
3
  import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-BI8kEKsO.mjs";
3
- import { a as CircuitBreakerStats, c as createCircuitBreakerRegistry, i as CircuitBreakerRegistry, n as CircuitBreakerError, o as CircuitState, r as CircuitBreakerOptions, s as createCircuitBreaker, t as CircuitBreaker } from "../circuitBreaker-CvXkjfrW.mjs";
4
4
  import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
5
5
 
6
+ //#region src/utils/circuitBreaker.d.ts
7
+ /**
8
+ * Circuit Breaker Pattern
9
+ *
10
+ * Wraps external service calls with failure protection.
11
+ * Prevents cascading failures by "opening" the circuit when
12
+ * a service is failing, allowing it time to recover.
13
+ *
14
+ * States:
15
+ * - CLOSED: Normal operation, requests pass through
16
+ * - OPEN: Too many failures, all requests fail fast
17
+ * - HALF_OPEN: Testing if service recovered, limited requests
18
+ *
19
+ * @example
20
+ * import { CircuitBreaker } from '@classytic/arc/utils';
21
+ *
22
+ * const paymentBreaker = new CircuitBreaker(async (amount) => {
23
+ * return await stripe.charges.create({ amount });
24
+ * }, {
25
+ * failureThreshold: 5,
26
+ * resetTimeout: 30000,
27
+ * timeout: 5000,
28
+ * });
29
+ *
30
+ * try {
31
+ * const result = await paymentBreaker.call(100);
32
+ * } catch (error) {
33
+ * // Handle failure or circuit open
34
+ * }
35
+ */
36
+ declare const CircuitState: {
37
+ readonly CLOSED: "CLOSED";
38
+ readonly OPEN: "OPEN";
39
+ readonly HALF_OPEN: "HALF_OPEN";
40
+ };
41
+ type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];
42
+ interface CircuitBreakerOptions {
43
+ /**
44
+ * Number of failures before opening circuit
45
+ * @default 5
46
+ */
47
+ failureThreshold?: number;
48
+ /**
49
+ * Time in ms before attempting to close circuit
50
+ * @default 60000 (60 seconds)
51
+ */
52
+ resetTimeout?: number;
53
+ /**
54
+ * Request timeout in ms
55
+ * @default 10000 (10 seconds)
56
+ */
57
+ timeout?: number;
58
+ /**
59
+ * Number of successful requests in HALF_OPEN before closing
60
+ * @default 1
61
+ */
62
+ successThreshold?: number;
63
+ /**
64
+ * Fallback function when circuit is open.
65
+ * Receives the same arguments as the wrapped function.
66
+ */
67
+ fallback?: (...args: unknown[]) => Promise<unknown>;
68
+ /**
69
+ * Callback when state changes
70
+ */
71
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
72
+ /**
73
+ * Callback on error
74
+ */
75
+ onError?: (error: Error) => void;
76
+ /**
77
+ * Name for logging/monitoring
78
+ */
79
+ name?: string;
80
+ }
81
+ interface CircuitBreakerStats {
82
+ name?: string;
83
+ state: CircuitState;
84
+ failures: number;
85
+ successes: number;
86
+ totalCalls: number;
87
+ openedAt: number | null;
88
+ lastCallAt: number | null;
89
+ }
90
+ declare class CircuitBreakerError extends Error {
91
+ state: CircuitState;
92
+ constructor(message: string, state: CircuitState);
93
+ }
94
+ declare class CircuitBreaker<T extends (...args: any[]) => Promise<any>> {
95
+ private state;
96
+ private failures;
97
+ private successes;
98
+ private totalCalls;
99
+ private nextAttempt;
100
+ private lastCallAt;
101
+ private openedAt;
102
+ private readonly failureThreshold;
103
+ private readonly resetTimeout;
104
+ private readonly timeout;
105
+ private readonly successThreshold;
106
+ private readonly fallback?;
107
+ private readonly onStateChange?;
108
+ private readonly onError?;
109
+ private readonly name;
110
+ private readonly fn;
111
+ constructor(fn: T, options?: CircuitBreakerOptions);
112
+ /**
113
+ * Call the wrapped function with circuit breaker protection
114
+ */
115
+ call(...args: Parameters<T>): Promise<ReturnType<T>>;
116
+ /**
117
+ * Execute function with timeout
118
+ */
119
+ private executeWithTimeout;
120
+ /**
121
+ * Handle successful call
122
+ */
123
+ private onSuccess;
124
+ /**
125
+ * Handle failed call
126
+ */
127
+ private onFailure;
128
+ /**
129
+ * Change circuit state
130
+ */
131
+ private setState;
132
+ /**
133
+ * Manually open the circuit
134
+ */
135
+ open(): void;
136
+ /**
137
+ * Manually close the circuit
138
+ */
139
+ close(): void;
140
+ /**
141
+ * Get current statistics
142
+ */
143
+ getStats(): CircuitBreakerStats;
144
+ /**
145
+ * Get current state
146
+ */
147
+ getState(): CircuitState;
148
+ /**
149
+ * Check if circuit is open
150
+ */
151
+ isOpen(): boolean;
152
+ /**
153
+ * Check if circuit is closed
154
+ */
155
+ isClosed(): boolean;
156
+ /**
157
+ * Reset statistics
158
+ */
159
+ reset(): void;
160
+ }
161
+ /**
162
+ * Create a circuit breaker with sensible defaults
163
+ *
164
+ * @example
165
+ * const emailBreaker = createCircuitBreaker(
166
+ * async (to, subject, body) => sendEmail(to, subject, body),
167
+ * { name: 'email-service' }
168
+ * );
169
+ */
170
+ declare function createCircuitBreaker<T extends (...args: any[]) => Promise<any>>(fn: T, options?: CircuitBreakerOptions): CircuitBreaker<T>;
171
+ /**
172
+ * Circuit breaker registry for managing multiple breakers
173
+ */
174
+ declare class CircuitBreakerRegistry {
175
+ private breakers;
176
+ /**
177
+ * Register a circuit breaker
178
+ */
179
+ register<T extends (...args: any[]) => Promise<any>>(name: string, fn: T, options?: Omit<CircuitBreakerOptions, "name">): CircuitBreaker<T>;
180
+ /**
181
+ * Get a circuit breaker by name
182
+ */
183
+ get(name: string): CircuitBreaker<any> | undefined;
184
+ /**
185
+ * Get all breakers
186
+ */
187
+ getAll(): Map<string, CircuitBreaker<any>>;
188
+ /**
189
+ * Get statistics for all breakers
190
+ */
191
+ getAllStats(): Record<string, CircuitBreakerStats>;
192
+ /**
193
+ * Reset all breakers
194
+ */
195
+ resetAll(): void;
196
+ /**
197
+ * Open all breakers
198
+ */
199
+ openAll(): void;
200
+ /**
201
+ * Close all breakers
202
+ */
203
+ closeAll(): void;
204
+ }
205
+ /**
206
+ * Create a new CircuitBreakerRegistry instance.
207
+ * Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
208
+ */
209
+ declare function createCircuitBreakerRegistry(): CircuitBreakerRegistry;
210
+ //#endregion
6
211
  //#region src/utils/compensation.d.ts
7
212
  /**
8
213
  * Compensating Transaction — In-Process Rollback Primitive
@@ -92,6 +297,22 @@ interface CompensationDefinition<TCtx extends Record<string, unknown> = Record<s
92
297
  }
93
298
  declare function defineCompensation<TCtx extends Record<string, unknown> = Record<string, unknown>>(name: string, steps: readonly CompensationStep<TCtx>[]): CompensationDefinition<TCtx>;
94
299
  //#endregion
300
+ //#region src/utils/defineErrorMapper.d.ts
301
+ /**
302
+ * Register an `ErrorMapper` with its domain-specific generic argument and
303
+ * have it assign cleanly into `ErrorMapper[]` (no `as unknown as ErrorMapper`).
304
+ *
305
+ * The returned mapper is identical at runtime — `type` and `toResponse` are
306
+ * passed through untouched. Only the declared type widens from
307
+ * `ErrorMapper<T>` to `ErrorMapper` so the array inference works.
308
+ *
309
+ * Safety: the `errorHandlerPlugin` dispatches via `error instanceof mapper.type`
310
+ * before invoking `toResponse`, so the widened callback signature is never
311
+ * called with a non-`T` error at runtime. This helper codifies that invariant
312
+ * in one place.
313
+ */
314
+ declare function defineErrorMapper<T extends Error>(mapper: ErrorMapper<T>): ErrorMapper;
315
+ //#endregion
95
316
  //#region src/utils/defineGuard.d.ts
96
317
  interface GuardConfig<T> {
97
318
  /** Unique name — used as the storage key on the request. */
@@ -561,6 +782,59 @@ declare function convertOpenApiSchemas(schemas: OpenApiSchemas, target?: JsonSch
561
782
  */
562
783
  declare function convertRouteSchema(schema: Record<string, unknown>, target?: JsonSchemaTarget): Record<string, unknown>;
563
784
  //#endregion
785
+ //#region src/utils/simpleEqualityMatcher.d.ts
786
+ /**
787
+ * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
788
+ * matcher for `DataAdapter.matchesFilter` / `BaseController({ matchesFilter })`.
789
+ *
790
+ * **What it does:** for each `[key, expected]` in the filter, compares
791
+ * `item[key]` to `expected` via string coercion (so Mongo `ObjectId` values
792
+ * match their string representation) and returns `true` only if every
793
+ * filter entry matches. Array item values are matched implicitly (contains).
794
+ *
795
+ * **What it does NOT do:**
796
+ * - No `$eq` / `$ne` / `$in` / `$nin` / `$gt` / `$lt` / `$regex` / `$exists`
797
+ * - No `$and` / `$or`
798
+ * - No dot-path traversal (`"owner.id"`)
799
+ * - No schema-specific coercion
800
+ *
801
+ * **Why it exists:** 95%+ of arc's `_policyFilters` are produced by built-in
802
+ * permission helpers and are shaped like `{ ownerId: "u1" }` or
803
+ * `{ organizationId: "org_x" }` — flat equality. For that common shape,
804
+ * this helper is a safe, tested, 15-line defense-in-depth matcher that
805
+ * hosts using minimal repos (no `getOne(compoundFilter)` DB path) can opt
806
+ * into without arc shipping a full Mongo-syntax engine.
807
+ *
808
+ * **When to use:**
809
+ * - Your adapter/repo doesn't natively filter on `getOne(compoundFilter)`
810
+ * - Your `_policyFilters` are flat equality (from arc's built-in permission helpers)
811
+ * - You want defense-in-depth on `validateItemAccess` / `fetchDetailed`'s `getById` fallback
812
+ *
813
+ * **When NOT to use:**
814
+ * - Your `_policyFilters` use operators (`$in`, `$ne`, etc.) — supply a
815
+ * native matcher (mongokit's repo does the filter at the DB layer; for
816
+ * custom repos, wrap the kit's own predicate engine).
817
+ * - You're a mongokit / sqlitekit / Prisma user — the DB-level filter
818
+ * applied by `getOne(compoundFilter)` already covers this.
819
+ *
820
+ * @example
821
+ * ```ts
822
+ * import { simpleEqualityMatcher } from '@classytic/arc/utils';
823
+ *
824
+ * // On a custom adapter
825
+ * const adapter: DataAdapter = {
826
+ * repository,
827
+ * type: 'custom',
828
+ * name: 'in-memory',
829
+ * matchesFilter: simpleEqualityMatcher,
830
+ * };
831
+ *
832
+ * // Or directly on BaseController for ad-hoc controllers
833
+ * new BaseController(repo, { matchesFilter: simpleEqualityMatcher });
834
+ * ```
835
+ */
836
+ declare function simpleEqualityMatcher(item: unknown, filters: Record<string, unknown>): boolean;
837
+ //#endregion
564
838
  //#region src/utils/stateMachine.d.ts
565
839
  /**
566
840
  * State Machine Utility
@@ -691,4 +965,4 @@ declare function hasEvents(instance: FastifyInstance): instance is FastifyInstan
691
965
  events: EventsDecorator;
692
966
  };
693
967
  //#endregion
694
- export { ArcError, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, CircuitBreakerRegistry, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, ConflictError, type ErrorDetails, type EventsDecorator, ForbiddenError, type Guard, type GuardConfig, type JsonSchema, type JsonSchemaTarget, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, type StateMachine, type TransitionConfig, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineGuard, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
968
+ export { ArcError, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, CircuitBreakerRegistry, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, ConflictError, type ErrorDetails, type EventsDecorator, ForbiddenError, type Guard, type GuardConfig, type JsonSchema, type JsonSchemaTarget, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, type StateMachine, type TransitionConfig, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, simpleEqualityMatcher, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
@@ -1,7 +1,6 @@
1
- import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-CqWnSqM-.mjs";
2
- import { n as createQueryParser, t as ArcQueryParser } from "../queryParser-Cs-6SHQK.mjs";
3
- import { a as createCircuitBreaker, i as CircuitState, n as CircuitBreakerError, o as createCircuitBreakerRegistry, r as CircuitBreakerRegistry, t as CircuitBreaker } from "../circuitBreaker-l18oRgL5.mjs";
4
- import { _ as withCompensation, a as getListQueryParams, c as mutationResponse, d as responses, f as successResponseSchema, g as defineCompensation, h as defineGuard, i as getDefaultCrudSchemas, l as paginationSchema, m as handleRaw, n as deleteResponse, o as itemResponse, p as wrapResponse, r as errorResponseSchema, s as listResponse, t as createStateMachine, u as queryParams } from "../utils-B7FuRr9w.mjs";
1
+ import { n as createQueryParser, r as simpleEqualityMatcher, t as ArcQueryParser } from "../queryParser-NR__Qiju.mjs";
2
+ import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-BqdUDja_.mjs";
3
+ import { C as createCircuitBreaker, S as CircuitState, _ as defineCompensation, a as getListQueryParams, b as CircuitBreakerError, c as mutationResponse, d as responses, f as successResponseSchema, g as defineErrorMapper, h as defineGuard, i as getDefaultCrudSchemas, l as paginationSchema, m as handleRaw, n as deleteResponse, o as itemResponse, p as wrapResponse, r as errorResponseSchema, s as listResponse, t as createStateMachine, u as queryParams, v as withCompensation, w as createCircuitBreakerRegistry, x as CircuitBreakerRegistry, y as CircuitBreaker } from "../utils-LMwVidKy.mjs";
5
4
  import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-BxFDdtXu.mjs";
6
5
  import { t as hasEvents } from "../typeGuards-Cj5Rgvlg.mjs";
7
- export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineGuard, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
6
+ export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, simpleEqualityMatcher, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
@@ -1,4 +1,287 @@
1
- import { t as ArcError } from "./errors-CqWnSqM-.mjs";
1
+ import { t as ArcError } from "./errors-BqdUDja_.mjs";
2
+ //#region src/utils/circuitBreaker.ts
3
+ /**
4
+ * Circuit Breaker Pattern
5
+ *
6
+ * Wraps external service calls with failure protection.
7
+ * Prevents cascading failures by "opening" the circuit when
8
+ * a service is failing, allowing it time to recover.
9
+ *
10
+ * States:
11
+ * - CLOSED: Normal operation, requests pass through
12
+ * - OPEN: Too many failures, all requests fail fast
13
+ * - HALF_OPEN: Testing if service recovered, limited requests
14
+ *
15
+ * @example
16
+ * import { CircuitBreaker } from '@classytic/arc/utils';
17
+ *
18
+ * const paymentBreaker = new CircuitBreaker(async (amount) => {
19
+ * return await stripe.charges.create({ amount });
20
+ * }, {
21
+ * failureThreshold: 5,
22
+ * resetTimeout: 30000,
23
+ * timeout: 5000,
24
+ * });
25
+ *
26
+ * try {
27
+ * const result = await paymentBreaker.call(100);
28
+ * } catch (error) {
29
+ * // Handle failure or circuit open
30
+ * }
31
+ */
32
+ const CircuitState = {
33
+ CLOSED: "CLOSED",
34
+ OPEN: "OPEN",
35
+ HALF_OPEN: "HALF_OPEN"
36
+ };
37
+ var CircuitBreakerError = class extends Error {
38
+ state;
39
+ constructor(message, state) {
40
+ super(message);
41
+ this.name = "CircuitBreakerError";
42
+ this.state = state;
43
+ }
44
+ };
45
+ var CircuitBreaker = class {
46
+ state = CircuitState.CLOSED;
47
+ failures = 0;
48
+ successes = 0;
49
+ totalCalls = 0;
50
+ nextAttempt = 0;
51
+ lastCallAt = null;
52
+ openedAt = null;
53
+ failureThreshold;
54
+ resetTimeout;
55
+ timeout;
56
+ successThreshold;
57
+ fallback;
58
+ onStateChange;
59
+ onError;
60
+ name;
61
+ fn;
62
+ constructor(fn, options = {}) {
63
+ this.fn = fn;
64
+ this.failureThreshold = options.failureThreshold ?? 5;
65
+ this.resetTimeout = options.resetTimeout ?? 6e4;
66
+ this.timeout = options.timeout ?? 1e4;
67
+ this.successThreshold = options.successThreshold ?? 1;
68
+ this.fallback = options.fallback;
69
+ this.onStateChange = options.onStateChange;
70
+ this.onError = options.onError;
71
+ this.name = options.name ?? "CircuitBreaker";
72
+ }
73
+ /**
74
+ * Call the wrapped function with circuit breaker protection
75
+ */
76
+ async call(...args) {
77
+ this.totalCalls++;
78
+ this.lastCallAt = Date.now();
79
+ if (this.state === CircuitState.OPEN) {
80
+ if (Date.now() < this.nextAttempt) {
81
+ const error = new CircuitBreakerError(`Circuit breaker is OPEN for ${this.name}`, CircuitState.OPEN);
82
+ if (this.fallback) return this.fallback(...args);
83
+ throw error;
84
+ }
85
+ this.setState(CircuitState.HALF_OPEN);
86
+ }
87
+ try {
88
+ const result = await this.executeWithTimeout(args);
89
+ this.onSuccess();
90
+ return result;
91
+ } catch (err) {
92
+ this.onFailure(err instanceof Error ? err : new Error(String(err)));
93
+ throw err;
94
+ }
95
+ }
96
+ /**
97
+ * Execute function with timeout
98
+ */
99
+ async executeWithTimeout(args) {
100
+ return new Promise((resolve, reject) => {
101
+ const timeoutId = setTimeout(() => {
102
+ reject(/* @__PURE__ */ new Error(`Request timeout after ${this.timeout}ms`));
103
+ }, this.timeout);
104
+ this.fn(...args).then((result) => {
105
+ clearTimeout(timeoutId);
106
+ resolve(result);
107
+ }).catch((error) => {
108
+ clearTimeout(timeoutId);
109
+ reject(error);
110
+ });
111
+ });
112
+ }
113
+ /**
114
+ * Handle successful call
115
+ */
116
+ onSuccess() {
117
+ this.failures = 0;
118
+ this.successes++;
119
+ if (this.state === CircuitState.HALF_OPEN) {
120
+ if (this.successes >= this.successThreshold) {
121
+ this.setState(CircuitState.CLOSED);
122
+ this.successes = 0;
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * Handle failed call
128
+ */
129
+ onFailure(error) {
130
+ this.failures++;
131
+ this.successes = 0;
132
+ if (this.onError) this.onError(error);
133
+ if (this.state === CircuitState.HALF_OPEN || this.failures >= this.failureThreshold) {
134
+ this.setState(CircuitState.OPEN);
135
+ this.nextAttempt = Date.now() + this.resetTimeout;
136
+ this.openedAt = Date.now();
137
+ }
138
+ }
139
+ /**
140
+ * Change circuit state
141
+ */
142
+ setState(newState) {
143
+ const oldState = this.state;
144
+ if (oldState !== newState) {
145
+ this.state = newState;
146
+ if (this.onStateChange) this.onStateChange(oldState, newState);
147
+ }
148
+ }
149
+ /**
150
+ * Manually open the circuit
151
+ */
152
+ open() {
153
+ this.setState(CircuitState.OPEN);
154
+ this.nextAttempt = Date.now() + this.resetTimeout;
155
+ this.openedAt = Date.now();
156
+ }
157
+ /**
158
+ * Manually close the circuit
159
+ */
160
+ close() {
161
+ this.failures = 0;
162
+ this.successes = 0;
163
+ this.setState(CircuitState.CLOSED);
164
+ this.openedAt = null;
165
+ }
166
+ /**
167
+ * Get current statistics
168
+ */
169
+ getStats() {
170
+ return {
171
+ name: this.name,
172
+ state: this.state,
173
+ failures: this.failures,
174
+ successes: this.successes,
175
+ totalCalls: this.totalCalls,
176
+ openedAt: this.openedAt,
177
+ lastCallAt: this.lastCallAt
178
+ };
179
+ }
180
+ /**
181
+ * Get current state
182
+ */
183
+ getState() {
184
+ return this.state;
185
+ }
186
+ /**
187
+ * Check if circuit is open
188
+ */
189
+ isOpen() {
190
+ return this.state === CircuitState.OPEN;
191
+ }
192
+ /**
193
+ * Check if circuit is closed
194
+ */
195
+ isClosed() {
196
+ return this.state === CircuitState.CLOSED;
197
+ }
198
+ /**
199
+ * Reset statistics
200
+ */
201
+ reset() {
202
+ this.failures = 0;
203
+ this.successes = 0;
204
+ this.totalCalls = 0;
205
+ this.lastCallAt = null;
206
+ this.openedAt = null;
207
+ this.setState(CircuitState.CLOSED);
208
+ }
209
+ };
210
+ /**
211
+ * Create a circuit breaker with sensible defaults
212
+ *
213
+ * @example
214
+ * const emailBreaker = createCircuitBreaker(
215
+ * async (to, subject, body) => sendEmail(to, subject, body),
216
+ * { name: 'email-service' }
217
+ * );
218
+ */
219
+ function createCircuitBreaker(fn, options) {
220
+ return new CircuitBreaker(fn, options);
221
+ }
222
+ /**
223
+ * Circuit breaker registry for managing multiple breakers
224
+ */
225
+ var CircuitBreakerRegistry = class {
226
+ breakers = /* @__PURE__ */ new Map();
227
+ /**
228
+ * Register a circuit breaker
229
+ */
230
+ register(name, fn, options) {
231
+ const breaker = new CircuitBreaker(fn, {
232
+ ...options,
233
+ name
234
+ });
235
+ this.breakers.set(name, breaker);
236
+ return breaker;
237
+ }
238
+ /**
239
+ * Get a circuit breaker by name
240
+ */
241
+ get(name) {
242
+ return this.breakers.get(name);
243
+ }
244
+ /**
245
+ * Get all breakers
246
+ */
247
+ getAll() {
248
+ return this.breakers;
249
+ }
250
+ /**
251
+ * Get statistics for all breakers
252
+ */
253
+ getAllStats() {
254
+ const stats = {};
255
+ for (const [name, breaker] of this.breakers.entries()) stats[name] = breaker.getStats();
256
+ return stats;
257
+ }
258
+ /**
259
+ * Reset all breakers
260
+ */
261
+ resetAll() {
262
+ for (const breaker of this.breakers.values()) breaker.reset();
263
+ }
264
+ /**
265
+ * Open all breakers
266
+ */
267
+ openAll() {
268
+ for (const breaker of this.breakers.values()) breaker.open();
269
+ }
270
+ /**
271
+ * Close all breakers
272
+ */
273
+ closeAll() {
274
+ for (const breaker of this.breakers.values()) breaker.close();
275
+ }
276
+ };
277
+ /**
278
+ * Create a new CircuitBreakerRegistry instance.
279
+ * Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
280
+ */
281
+ function createCircuitBreakerRegistry() {
282
+ return new CircuitBreakerRegistry();
283
+ }
284
+ //#endregion
2
285
  //#region src/utils/compensation.ts
3
286
  /**
4
287
  * Run steps in order with automatic compensation on failure.
@@ -70,6 +353,24 @@ function defineCompensation(name, steps) {
70
353
  };
71
354
  }
72
355
  //#endregion
356
+ //#region src/utils/defineErrorMapper.ts
357
+ /**
358
+ * Register an `ErrorMapper` with its domain-specific generic argument and
359
+ * have it assign cleanly into `ErrorMapper[]` (no `as unknown as ErrorMapper`).
360
+ *
361
+ * The returned mapper is identical at runtime — `type` and `toResponse` are
362
+ * passed through untouched. Only the declared type widens from
363
+ * `ErrorMapper<T>` to `ErrorMapper` so the array inference works.
364
+ *
365
+ * Safety: the `errorHandlerPlugin` dispatches via `error instanceof mapper.type`
366
+ * before invoking `toResponse`, so the widened callback signature is never
367
+ * called with a non-`T` error at runtime. This helper codifies that invariant
368
+ * in one place.
369
+ */
370
+ function defineErrorMapper(mapper) {
371
+ return mapper;
372
+ }
373
+ //#endregion
73
374
  //#region src/utils/defineGuard.ts
74
375
  /** Hidden property key for guard context storage on the request object. */
75
376
  const GUARD_STORE_KEY = "__arcGuardContext";
@@ -643,4 +944,4 @@ function createStateMachine(name, transitions = {}, options = {}) {
643
944
  };
644
945
  }
645
946
  //#endregion
646
- export { withCompensation as _, getListQueryParams as a, mutationResponse as c, responses as d, successResponseSchema as f, defineCompensation as g, defineGuard as h, getDefaultCrudSchemas as i, paginationSchema as l, handleRaw as m, deleteResponse as n, itemResponse as o, wrapResponse as p, errorResponseSchema as r, listResponse as s, createStateMachine as t, queryParams as u };
947
+ export { createCircuitBreaker as C, CircuitState as S, defineCompensation as _, getListQueryParams as a, CircuitBreakerError as b, mutationResponse as c, responses as d, successResponseSchema as f, defineErrorMapper as g, defineGuard as h, getDefaultCrudSchemas as i, paginationSchema as l, handleRaw as m, deleteResponse as n, itemResponse as o, wrapResponse as p, errorResponseSchema as r, listResponse as s, createStateMachine as t, queryParams as u, withCompensation as v, createCircuitBreakerRegistry as w, CircuitBreakerRegistry as x, CircuitBreaker as y };