@classytic/arc 2.8.5 → 2.10.3

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 (155) hide show
  1. package/README.md +50 -38
  2. package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-CbKKIflT.mjs} +193 -143
  3. package/dist/EventTransport-CUw5NNWe.d.mts +293 -0
  4. package/dist/{ResourceRegistry-C6uXlWe3.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
  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 +135 -11
  9. package/dist/audit/index.mjs +107 -20
  10. package/dist/auth/index.d.mts +17 -9
  11. package/dist/auth/index.mjs +14 -7
  12. package/dist/auth/redis-session.d.mts +1 -1
  13. package/dist/{betterAuthOpenApi-BuUcUEJq.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +1 -1
  14. package/dist/cache/index.d.mts +17 -15
  15. package/dist/cache/index.mjs +15 -14
  16. package/dist/{caching-IMuYVjTL.mjs → caching-CBpK_SCM.mjs} +8 -3
  17. package/dist/cli/commands/describe.mjs +1 -1
  18. package/dist/cli/commands/docs.mjs +2 -2
  19. package/dist/cli/commands/generate.mjs +1 -1
  20. package/dist/cli/commands/init.mjs +1 -1
  21. package/dist/cli/commands/introspect.mjs +1 -1
  22. package/dist/core/index.d.mts +3 -3
  23. package/dist/core/index.mjs +4 -6
  24. package/dist/{defineResource-tcgySDo1.mjs → core-CcR01lup.mjs} +58 -61
  25. package/dist/{createActionRouter-BORM8f17.mjs → createActionRouter-Bp_5c_2b.mjs} +3 -3
  26. package/dist/{createApp-B1EY8zxa.mjs → createApp-BuvPma24.mjs} +15 -14
  27. package/dist/docs/index.d.mts +2 -2
  28. package/dist/docs/index.mjs +2 -2
  29. package/dist/{elevation-DtFxrG0s.mjs → elevation-C7hgL_aI.mjs} +22 -8
  30. package/dist/{errorHandler-f869_8PQ.mjs → errorHandler-Bb49BvPD.mjs} +59 -7
  31. package/dist/{errorHandler-Bah5JhBd.d.mts → errorHandler-DRQ3EqfL.d.mts} +37 -2
  32. package/dist/{eventPlugin-D9DKB2zM.d.mts → eventPlugin-CxWgpd6K.d.mts} +14 -2
  33. package/dist/{eventPlugin-CDjVTM82.mjs → eventPlugin-DCUjuiQT.mjs} +83 -5
  34. package/dist/events/index.d.mts +150 -36
  35. package/dist/events/index.mjs +355 -101
  36. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  37. package/dist/events/transports/redis.d.mts +1 -1
  38. package/dist/factory/index.d.mts +1 -1
  39. package/dist/factory/index.mjs +2 -2
  40. package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
  41. package/dist/{fields-ipsbIRPK.mjs → fields-bxkeltzz.mjs} +18 -5
  42. package/dist/{filesUpload-C7r7HIeA.mjs → filesUpload-t21LS-py.mjs} +65 -7
  43. package/dist/hooks/index.d.mts +1 -1
  44. package/dist/hooks/index.mjs +1 -1
  45. package/dist/idempotency/index.d.mts +32 -5
  46. package/dist/idempotency/index.mjs +119 -12
  47. package/dist/idempotency/redis.d.mts +1 -1
  48. package/dist/{index-DtDzOBn8.d.mts → index-8qw4y6ff.d.mts} +4 -135
  49. package/dist/{index-BLXBmWud.d.mts → index-ChIw3776.d.mts} +283 -408
  50. package/dist/{interface-CMRutPfe.d.mts → index-Cl0uoKd5.d.mts} +1758 -2506
  51. package/dist/{index-C1meYuDn.d.mts → index-DStwgFUK.d.mts} +81 -7
  52. package/dist/index.d.mts +7 -8
  53. package/dist/index.mjs +11 -12
  54. package/dist/integrations/event-gateway.d.mts +1 -1
  55. package/dist/integrations/event-gateway.mjs +1 -1
  56. package/dist/integrations/index.d.mts +1 -1
  57. package/dist/integrations/mcp/index.d.mts +26 -8
  58. package/dist/integrations/mcp/index.mjs +96 -17
  59. package/dist/integrations/mcp/testing.d.mts +1 -1
  60. package/dist/integrations/mcp/testing.mjs +1 -1
  61. package/dist/integrations/webhooks.d.mts +5 -0
  62. package/dist/integrations/webhooks.mjs +6 -0
  63. package/dist/interface-D218ikEo.d.mts +77 -0
  64. package/dist/{memory-Cp7_cAko.mjs → memory-B5Amv9A1.mjs} +23 -8
  65. package/dist/{openapi-CbKUJY_m.mjs → openapi-B5F8AddX.mjs} +3 -3
  66. package/dist/org/index.d.mts +2 -2
  67. package/dist/permissions/index.d.mts +3 -4
  68. package/dist/permissions/index.mjs +5 -5
  69. package/dist/{permissions-CH4cNwJi.mjs → permissions-Dk6mshja.mjs} +315 -397
  70. package/dist/plugins/index.d.mts +7 -7
  71. package/dist/plugins/index.mjs +14 -16
  72. package/dist/plugins/response-cache.mjs +2 -2
  73. package/dist/plugins/tracing-entry.d.mts +1 -1
  74. package/dist/plugins/tracing-entry.mjs +1 -1
  75. package/dist/presets/filesUpload.d.mts +27 -5
  76. package/dist/presets/filesUpload.mjs +1 -1
  77. package/dist/presets/index.d.mts +3 -2
  78. package/dist/presets/index.mjs +4 -3
  79. package/dist/presets/multiTenant.d.mts +1 -1
  80. package/dist/presets/multiTenant.mjs +2 -2
  81. package/dist/presets/search.d.mts +178 -0
  82. package/dist/presets/search.mjs +150 -0
  83. package/dist/{presets-C2xgzW6x.mjs → presets-fLJVXdVn.mjs} +1 -1
  84. package/dist/{queryCachePlugin-BJJGBTlu.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
  85. package/dist/{queryCachePlugin-BH-fidlv.mjs → queryCachePlugin-DQCEfJis.mjs} +9 -9
  86. package/dist/{queryParser-CgCtsjti.mjs → queryParser-DBqBB6AC.mjs} +1 -1
  87. package/dist/{redis-BM00zaPB.d.mts → redis-DqyeggCa.d.mts} +1 -1
  88. package/dist/{redis-stream-CrsfUmPt.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
  89. package/dist/registry/index.d.mts +1 -1
  90. package/dist/registry/index.mjs +2 -2
  91. package/dist/{resourceToTools-8s-EsCCe.mjs → resourceToTools-BElv3xPT.mjs} +65 -48
  92. package/dist/{schemaConverter-Y7nCYaLJ.mjs → schemaConverter-BxFDdtXu.mjs} +1 -1
  93. package/dist/scope/index.d.mts +1 -1
  94. package/dist/scope/index.mjs +2 -2
  95. package/dist/{sse-Ad7ypl9e.mjs → sse-yBCgOLGu.mjs} +1 -1
  96. package/dist/store-helpers-ZCSMJJAX.mjs +57 -0
  97. package/dist/testing/index.d.mts +9 -17
  98. package/dist/testing/index.mjs +27 -83
  99. package/dist/testing/storageContract.d.mts +1 -1
  100. package/dist/types/index.d.mts +4 -4
  101. package/dist/types/index.mjs +1 -31
  102. package/dist/types/storage.d.mts +1 -1
  103. package/dist/{types-BsbNMEDR.d.mts → types-Btdda02s.d.mts} +1 -1
  104. package/dist/{types-Ch9pTQbf.d.mts → types-Co8k3NyS.d.mts} +11 -9
  105. package/dist/types-Csi3FLfq.mjs +27 -0
  106. package/dist/utils/index.d.mts +208 -4
  107. package/dist/utils/index.mjs +5 -6
  108. package/dist/{utils-yYT3HDXt.mjs → utils-B2fNOD_i.mjs} +285 -2
  109. package/dist/{versioning-CDugduqI.mjs → versioning-C2U_bLY0.mjs} +3 -5
  110. package/package.json +20 -26
  111. package/skills/arc/SKILL.md +97 -23
  112. package/skills/arc/references/auth.md +94 -0
  113. package/skills/arc/references/events.md +200 -12
  114. package/skills/arc/references/mcp.md +4 -17
  115. package/skills/arc/references/multi-tenancy.md +43 -0
  116. package/skills/arc/references/production.md +34 -60
  117. package/dist/EventTransport-BXja8NOc.d.mts +0 -135
  118. package/dist/audit/mongodb.d.mts +0 -2
  119. package/dist/audit/mongodb.mjs +0 -2
  120. package/dist/circuitBreaker-cmi5XDv5.mjs +0 -284
  121. package/dist/circuitBreaker-dTtG-UyS.d.mts +0 -206
  122. package/dist/core-F0QoWBt2.mjs +0 -34
  123. package/dist/dynamic/index.d.mts +0 -93
  124. package/dist/dynamic/index.mjs +0 -122
  125. package/dist/fields-DpZQa_Q3.d.mts +0 -109
  126. package/dist/idempotency/mongodb.d.mts +0 -2
  127. package/dist/idempotency/mongodb.mjs +0 -123
  128. package/dist/interface-4y979v99.d.mts +0 -54
  129. package/dist/mongodb-BsP-WbhN.d.mts +0 -127
  130. package/dist/mongodb-CTcp0hQZ.d.mts +0 -80
  131. package/dist/mongodb-Utc5k_-0.mjs +0 -90
  132. package/dist/policies/index.d.mts +0 -432
  133. package/dist/policies/index.mjs +0 -318
  134. package/dist/rpc/index.d.mts +0 -90
  135. package/dist/rpc/index.mjs +0 -248
  136. /package/dist/{HookSystem-HprTmvVY.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
  137. /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
  138. /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
  139. /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
  140. /package/dist/{errors-Ck2h67pm.d.mts → errors-CCSsMpXE.d.mts} +0 -0
  141. /package/dist/{errors-BF2bIOIS.mjs → errors-D5c-5BJL.mjs} +0 -0
  142. /package/dist/{externalPaths-BnkYrNzp.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
  143. /package/dist/{interface-DfLGcus7.d.mts → interface-CSbZdv_3.d.mts} +0 -0
  144. /package/dist/{loadResources-PWd0OCpV.mjs → loadResources-BAzJItAJ.mjs} +0 -0
  145. /package/dist/{logger-D1YrIImS.mjs → logger-DLg8-Ueg.mjs} +0 -0
  146. /package/dist/{metrics-B-PU4-Yu.mjs → metrics-DuhiSEZI.mjs} +0 -0
  147. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
  148. /package/dist/{registry-BiTKT1Dg.mjs → registry-B3lRFBWo.mjs} +0 -0
  149. /package/dist/{replyHelpers-CxkYGT81.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
  150. /package/dist/{requestContext-DYvHl113.mjs → requestContext-xHIKedG6.mjs} +0 -0
  151. /package/dist/{sessionManager-DDCmiNIo.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
  152. /package/dist/{storage-Dfzt4VTl.d.mts → storage-CVk_SEn2.d.mts} +0 -0
  153. /package/dist/{tracing-DdN2-wHJ.d.mts → tracing-65B51Dw3.d.mts} +0 -0
  154. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
  155. /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
@@ -0,0 +1,27 @@
1
+ //#region src/types/base.ts
2
+ /** Extract user ID from a user object (supports both id and _id). */
3
+ function getUserId(user) {
4
+ if (!user) return void 0;
5
+ const id = user.id ?? user._id;
6
+ return id ? String(id) : void 0;
7
+ }
8
+ /**
9
+ * Wrap data in Arc's standard `{ success: true, data }` envelope.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * handler: async (req, reply) => {
14
+ * const data = await getResults();
15
+ * return envelope(data); // → { success: true, data }
16
+ * }
17
+ * ```
18
+ */
19
+ function envelope(data, meta) {
20
+ return {
21
+ success: true,
22
+ data,
23
+ ...meta
24
+ };
25
+ }
26
+ //#endregion
27
+ export { getUserId as n, envelope as t };
@@ -1,8 +1,212 @@
1
- import { Y as OpenApiSchemas, Z as ParsedQuery, m as AnyRecord, nt as QueryParserInterface } from "../interface-CMRutPfe.mjs";
2
- 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-Ck2h67pm.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-dTtG-UyS.mjs";
1
+ import { Kt as QueryParserInterface, Wt as ParsedQuery, dn as AnyRecord, rt as OpenApiSchemas } from "../index-Cl0uoKd5.mjs";
2
+ 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-CCSsMpXE.mjs";
4
3
  import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
5
4
 
5
+ //#region src/utils/circuitBreaker.d.ts
6
+ /**
7
+ * Circuit Breaker Pattern
8
+ *
9
+ * Wraps external service calls with failure protection.
10
+ * Prevents cascading failures by "opening" the circuit when
11
+ * a service is failing, allowing it time to recover.
12
+ *
13
+ * States:
14
+ * - CLOSED: Normal operation, requests pass through
15
+ * - OPEN: Too many failures, all requests fail fast
16
+ * - HALF_OPEN: Testing if service recovered, limited requests
17
+ *
18
+ * @example
19
+ * import { CircuitBreaker } from '@classytic/arc/utils';
20
+ *
21
+ * const paymentBreaker = new CircuitBreaker(async (amount) => {
22
+ * return await stripe.charges.create({ amount });
23
+ * }, {
24
+ * failureThreshold: 5,
25
+ * resetTimeout: 30000,
26
+ * timeout: 5000,
27
+ * });
28
+ *
29
+ * try {
30
+ * const result = await paymentBreaker.call(100);
31
+ * } catch (error) {
32
+ * // Handle failure or circuit open
33
+ * }
34
+ */
35
+ declare const CircuitState: {
36
+ readonly CLOSED: "CLOSED";
37
+ readonly OPEN: "OPEN";
38
+ readonly HALF_OPEN: "HALF_OPEN";
39
+ };
40
+ type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];
41
+ interface CircuitBreakerOptions {
42
+ /**
43
+ * Number of failures before opening circuit
44
+ * @default 5
45
+ */
46
+ failureThreshold?: number;
47
+ /**
48
+ * Time in ms before attempting to close circuit
49
+ * @default 60000 (60 seconds)
50
+ */
51
+ resetTimeout?: number;
52
+ /**
53
+ * Request timeout in ms
54
+ * @default 10000 (10 seconds)
55
+ */
56
+ timeout?: number;
57
+ /**
58
+ * Number of successful requests in HALF_OPEN before closing
59
+ * @default 1
60
+ */
61
+ successThreshold?: number;
62
+ /**
63
+ * Fallback function when circuit is open.
64
+ * Receives the same arguments as the wrapped function.
65
+ */
66
+ fallback?: (...args: unknown[]) => Promise<unknown>;
67
+ /**
68
+ * Callback when state changes
69
+ */
70
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
71
+ /**
72
+ * Callback on error
73
+ */
74
+ onError?: (error: Error) => void;
75
+ /**
76
+ * Name for logging/monitoring
77
+ */
78
+ name?: string;
79
+ }
80
+ interface CircuitBreakerStats {
81
+ name?: string;
82
+ state: CircuitState;
83
+ failures: number;
84
+ successes: number;
85
+ totalCalls: number;
86
+ openedAt: number | null;
87
+ lastCallAt: number | null;
88
+ }
89
+ declare class CircuitBreakerError extends Error {
90
+ state: CircuitState;
91
+ constructor(message: string, state: CircuitState);
92
+ }
93
+ declare class CircuitBreaker<T extends (...args: any[]) => Promise<any>> {
94
+ private state;
95
+ private failures;
96
+ private successes;
97
+ private totalCalls;
98
+ private nextAttempt;
99
+ private lastCallAt;
100
+ private openedAt;
101
+ private readonly failureThreshold;
102
+ private readonly resetTimeout;
103
+ private readonly timeout;
104
+ private readonly successThreshold;
105
+ private readonly fallback?;
106
+ private readonly onStateChange?;
107
+ private readonly onError?;
108
+ private readonly name;
109
+ private readonly fn;
110
+ constructor(fn: T, options?: CircuitBreakerOptions);
111
+ /**
112
+ * Call the wrapped function with circuit breaker protection
113
+ */
114
+ call(...args: Parameters<T>): Promise<ReturnType<T>>;
115
+ /**
116
+ * Execute function with timeout
117
+ */
118
+ private executeWithTimeout;
119
+ /**
120
+ * Handle successful call
121
+ */
122
+ private onSuccess;
123
+ /**
124
+ * Handle failed call
125
+ */
126
+ private onFailure;
127
+ /**
128
+ * Change circuit state
129
+ */
130
+ private setState;
131
+ /**
132
+ * Manually open the circuit
133
+ */
134
+ open(): void;
135
+ /**
136
+ * Manually close the circuit
137
+ */
138
+ close(): void;
139
+ /**
140
+ * Get current statistics
141
+ */
142
+ getStats(): CircuitBreakerStats;
143
+ /**
144
+ * Get current state
145
+ */
146
+ getState(): CircuitState;
147
+ /**
148
+ * Check if circuit is open
149
+ */
150
+ isOpen(): boolean;
151
+ /**
152
+ * Check if circuit is closed
153
+ */
154
+ isClosed(): boolean;
155
+ /**
156
+ * Reset statistics
157
+ */
158
+ reset(): void;
159
+ }
160
+ /**
161
+ * Create a circuit breaker with sensible defaults
162
+ *
163
+ * @example
164
+ * const emailBreaker = createCircuitBreaker(
165
+ * async (to, subject, body) => sendEmail(to, subject, body),
166
+ * { name: 'email-service' }
167
+ * );
168
+ */
169
+ declare function createCircuitBreaker<T extends (...args: any[]) => Promise<any>>(fn: T, options?: CircuitBreakerOptions): CircuitBreaker<T>;
170
+ /**
171
+ * Circuit breaker registry for managing multiple breakers
172
+ */
173
+ declare class CircuitBreakerRegistry {
174
+ private breakers;
175
+ /**
176
+ * Register a circuit breaker
177
+ */
178
+ register<T extends (...args: any[]) => Promise<any>>(name: string, fn: T, options?: Omit<CircuitBreakerOptions, "name">): CircuitBreaker<T>;
179
+ /**
180
+ * Get a circuit breaker by name
181
+ */
182
+ get(name: string): CircuitBreaker<any> | undefined;
183
+ /**
184
+ * Get all breakers
185
+ */
186
+ getAll(): Map<string, CircuitBreaker<any>>;
187
+ /**
188
+ * Get statistics for all breakers
189
+ */
190
+ getAllStats(): Record<string, CircuitBreakerStats>;
191
+ /**
192
+ * Reset all breakers
193
+ */
194
+ resetAll(): void;
195
+ /**
196
+ * Open all breakers
197
+ */
198
+ openAll(): void;
199
+ /**
200
+ * Close all breakers
201
+ */
202
+ closeAll(): void;
203
+ }
204
+ /**
205
+ * Create a new CircuitBreakerRegistry instance.
206
+ * Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
207
+ */
208
+ declare function createCircuitBreakerRegistry(): CircuitBreakerRegistry;
209
+ //#endregion
6
210
  //#region src/utils/compensation.d.ts
7
211
  /**
8
212
  * Compensating Transaction — In-Process Rollback Primitive
@@ -554,7 +758,7 @@ declare function convertOpenApiSchemas(schemas: OpenApiSchemas, target?: JsonSch
554
758
  *
555
759
  * JSON Schema values pass through unchanged. Only Zod schemas are converted.
556
760
  *
557
- * Used for both additionalRoutes and customSchemas (CRUD overrides).
761
+ * Used for both custom routes and customSchemas (CRUD overrides).
558
762
  *
559
763
  * Defaults to `draft-7` so Fastify v5's bundled AJV 8 accepts the output.
560
764
  * Pass `openapi-3.0` (or `openapi-3.1`) when generating OpenAPI documents.
@@ -1,7 +1,6 @@
1
- import { n as createQueryParser, t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
- import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-Y7nCYaLJ.mjs";
3
- import { a as createCircuitBreaker, i as CircuitState, n as CircuitBreakerError, o as createCircuitBreakerRegistry, r as CircuitBreakerRegistry, t as CircuitBreaker } from "../circuitBreaker-cmi5XDv5.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-yYT3HDXt.mjs";
5
- 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-BF2bIOIS.mjs";
6
- import { t as hasEvents } from "../typeGuards-CcFZXgU7.mjs";
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-D5c-5BJL.mjs";
2
+ import { n as createQueryParser, t as ArcQueryParser } from "../queryParser-DBqBB6AC.mjs";
3
+ import { C as createCircuitBreakerRegistry, S as createCircuitBreaker, _ as withCompensation, a as getListQueryParams, b as CircuitBreakerRegistry, 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, v as CircuitBreaker, x as CircuitState, y as CircuitBreakerError } from "../utils-B2fNOD_i.mjs";
4
+ import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-BxFDdtXu.mjs";
5
+ import { t as hasEvents } from "../typeGuards-Cj5Rgvlg.mjs";
7
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, defineGuard, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
@@ -1,4 +1,287 @@
1
- import { t as ArcError } from "./errors-BF2bIOIS.mjs";
1
+ import { t as ArcError } from "./errors-D5c-5BJL.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.
@@ -643,4 +926,4 @@ function createStateMachine(name, transitions = {}, options = {}) {
643
926
  };
644
927
  }
645
928
  //#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 };
929
+ export { createCircuitBreakerRegistry as C, createCircuitBreaker as S, withCompensation as _, getListQueryParams as a, CircuitBreakerRegistry as b, 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, CircuitBreaker as v, CircuitState as x, CircuitBreakerError as y };
@@ -10,7 +10,7 @@ const versioningPlugin = async (fastify, opts) => {
10
10
  const { type, defaultVersion = "1", headerName = "accept-version", responseHeader = "x-api-version", deprecated = [], sunset } = opts;
11
11
  const deprecatedSet = new Set(deprecated);
12
12
  fastify.decorateRequest("apiVersion", defaultVersion);
13
- fastify.addHook("onRequest", async (request) => {
13
+ fastify.addHook("onRequest", async (request, reply) => {
14
14
  let version = defaultVersion;
15
15
  if (type === "header") {
16
16
  const headerValue = request.headers[headerName];
@@ -20,10 +20,8 @@ const versioningPlugin = async (fastify, opts) => {
20
20
  if (match) version = match[1] ?? defaultVersion;
21
21
  }
22
22
  request.apiVersion = version;
23
- });
24
- fastify.addHook("onSend", async (request, reply) => {
25
- reply.header(responseHeader, request.apiVersion);
26
- if (deprecatedSet.has(request.apiVersion)) {
23
+ reply.header(responseHeader, version);
24
+ if (deprecatedSet.has(version)) {
27
25
  reply.header("deprecation", "true");
28
26
  reply.header("sunset", sunset ?? new Date(Date.now() + 2160 * 60 * 60 * 1e3).toISOString());
29
27
  }