@classytic/arc 2.9.1 → 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 (139) hide show
  1. package/README.md +19 -90
  2. package/dist/{BaseController-Vu2yc56T.mjs → BaseController-CbKKIflT.mjs} +8 -44
  3. package/dist/{ResourceRegistry-Dq3_zBQP.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
  4. package/dist/adapters/index.d.mts +3 -3
  5. package/dist/adapters/index.mjs +2 -2
  6. package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
  7. package/dist/audit/index.d.mts +38 -3
  8. package/dist/audit/index.mjs +41 -7
  9. package/dist/auth/index.d.mts +4 -4
  10. package/dist/auth/index.mjs +5 -5
  11. package/dist/auth/redis-session.d.mts +1 -1
  12. package/dist/cache/index.d.mts +17 -15
  13. package/dist/cache/index.mjs +15 -14
  14. package/dist/{caching-CjybdRwx.mjs → caching-CBpK_SCM.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/generate.mjs +1 -1
  18. package/dist/cli/commands/init.mjs +1 -1
  19. package/dist/cli/commands/introspect.mjs +1 -1
  20. package/dist/core/index.d.mts +2 -2
  21. package/dist/core/index.mjs +3 -4
  22. package/dist/{defineResource-C__jkwvs.mjs → core-CcR01lup.mjs} +44 -12
  23. package/dist/{createActionRouter-DH1YFL9m.mjs → createActionRouter-Bp_5c_2b.mjs} +1 -1
  24. package/dist/{createApp-CBJUJKGP.mjs → createApp-BuvPma24.mjs} +14 -14
  25. package/dist/docs/index.d.mts +2 -2
  26. package/dist/docs/index.mjs +2 -2
  27. package/dist/{elevation-DxQ6ACbt.mjs → elevation-C7hgL_aI.mjs} +2 -2
  28. package/dist/{errorHandler-CZDW4EXS.mjs → errorHandler-Bb49BvPD.mjs} +1 -1
  29. package/dist/{errorHandler-DixGcttC.d.mts → errorHandler-DRQ3EqfL.d.mts} +1 -1
  30. package/dist/{eventPlugin-BxvaCIZF.d.mts → eventPlugin-CxWgpd6K.d.mts} +1 -1
  31. package/dist/{eventPlugin-Dl7MoVWH.mjs → eventPlugin-DCUjuiQT.mjs} +1 -1
  32. package/dist/events/index.d.mts +8 -5
  33. package/dist/events/index.mjs +34 -17
  34. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  35. package/dist/events/transports/redis.d.mts +1 -1
  36. package/dist/factory/index.d.mts +1 -1
  37. package/dist/factory/index.mjs +2 -2
  38. package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
  39. package/dist/{filesUpload-q8oHt--L.mjs → filesUpload-t21LS-py.mjs} +2 -2
  40. package/dist/hooks/index.d.mts +1 -1
  41. package/dist/hooks/index.mjs +1 -1
  42. package/dist/idempotency/index.d.mts +7 -4
  43. package/dist/idempotency/index.mjs +9 -11
  44. package/dist/idempotency/redis.d.mts +1 -1
  45. package/dist/{index-Cibkchnx.d.mts → index-8qw4y6ff.d.mts} +2 -2
  46. package/dist/{index-C-xjcA6F.d.mts → index-ChIw3776.d.mts} +283 -408
  47. package/dist/{interface-YrWsmKqE.d.mts → index-Cl0uoKd5.d.mts} +1885 -2741
  48. package/dist/{index-CtGKT0lf.d.mts → index-DStwgFUK.d.mts} +81 -7
  49. package/dist/index.d.mts +7 -8
  50. package/dist/index.mjs +11 -12
  51. package/dist/integrations/event-gateway.d.mts +1 -1
  52. package/dist/integrations/event-gateway.mjs +1 -1
  53. package/dist/integrations/index.d.mts +1 -1
  54. package/dist/integrations/mcp/index.d.mts +2 -2
  55. package/dist/integrations/mcp/index.mjs +1 -1
  56. package/dist/integrations/mcp/testing.d.mts +1 -1
  57. package/dist/integrations/mcp/testing.mjs +1 -1
  58. package/dist/interface-D218ikEo.d.mts +77 -0
  59. package/dist/{memory-BFAYkf8H.mjs → memory-B5Amv9A1.mjs} +23 -8
  60. package/dist/{openapi-CXuTG1M9.mjs → openapi-B5F8AddX.mjs} +2 -2
  61. package/dist/org/index.d.mts +2 -2
  62. package/dist/permissions/index.d.mts +3 -4
  63. package/dist/permissions/index.mjs +5 -5
  64. package/dist/{permissions-oNZawnkR.mjs → permissions-Dk6mshja.mjs} +315 -397
  65. package/dist/plugins/index.d.mts +4 -4
  66. package/dist/plugins/index.mjs +12 -14
  67. package/dist/plugins/response-cache.mjs +1 -1
  68. package/dist/plugins/tracing-entry.d.mts +1 -1
  69. package/dist/plugins/tracing-entry.mjs +1 -1
  70. package/dist/presets/filesUpload.d.mts +3 -3
  71. package/dist/presets/filesUpload.mjs +1 -1
  72. package/dist/presets/index.d.mts +1 -1
  73. package/dist/presets/index.mjs +2 -2
  74. package/dist/presets/multiTenant.d.mts +1 -1
  75. package/dist/presets/multiTenant.mjs +1 -1
  76. package/dist/presets/search.d.mts +91 -4
  77. package/dist/presets/search.mjs +1 -1
  78. package/dist/{presets-hM4WhNWY.mjs → presets-fLJVXdVn.mjs} +1 -1
  79. package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
  80. package/dist/{queryCachePlugin-DbUVroUG.mjs → queryCachePlugin-DQCEfJis.mjs} +8 -8
  81. package/dist/{queryParser-Cs-6SHQK.mjs → queryParser-DBqBB6AC.mjs} +1 -1
  82. package/dist/{redis-MXLp1oOf.d.mts → redis-DqyeggCa.d.mts} +1 -1
  83. package/dist/{redis-stream-Bz-4q96t.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
  84. package/dist/registry/index.d.mts +1 -1
  85. package/dist/registry/index.mjs +2 -2
  86. package/dist/{resourceToTools-C3cWymnW.mjs → resourceToTools-BElv3xPT.mjs} +3 -3
  87. package/dist/scope/index.d.mts +1 -1
  88. package/dist/scope/index.mjs +2 -2
  89. package/dist/{sse-CJpt7LGI.mjs → sse-yBCgOLGu.mjs} +1 -1
  90. package/dist/testing/index.d.mts +6 -5
  91. package/dist/testing/index.mjs +8 -10
  92. package/dist/testing/storageContract.d.mts +1 -1
  93. package/dist/types/index.d.mts +4 -4
  94. package/dist/types/index.mjs +1 -31
  95. package/dist/types/storage.d.mts +1 -1
  96. package/dist/{types-CoSzA-s-.d.mts → types-Btdda02s.d.mts} +1 -1
  97. package/dist/{types-CunEX4UX.d.mts → types-Co8k3NyS.d.mts} +9 -9
  98. package/dist/types-Csi3FLfq.mjs +27 -0
  99. package/dist/utils/index.d.mts +207 -3
  100. package/dist/utils/index.mjs +3 -4
  101. package/dist/{utils-B7FuRr9w.mjs → utils-B2fNOD_i.mjs} +285 -2
  102. package/dist/{versioning-Cm8qoFDg.mjs → versioning-C2U_bLY0.mjs} +3 -5
  103. package/package.json +15 -18
  104. package/skills/arc/SKILL.md +7 -11
  105. package/skills/arc/references/production.md +0 -41
  106. package/dist/circuitBreaker-CvXkjfrW.d.mts +0 -206
  107. package/dist/circuitBreaker-l18oRgL5.mjs +0 -284
  108. package/dist/core-DNncu0xF.mjs +0 -34
  109. package/dist/dynamic/index.d.mts +0 -93
  110. package/dist/dynamic/index.mjs +0 -122
  111. package/dist/fields-BC7zcmI9.d.mts +0 -121
  112. package/dist/interface-DplgQO2e.d.mts +0 -54
  113. package/dist/policies/index.d.mts +0 -425
  114. package/dist/policies/index.mjs +0 -318
  115. package/dist/rpc/index.d.mts +0 -90
  116. package/dist/rpc/index.mjs +0 -248
  117. /package/dist/{EventTransport-CqZ8FyM_.d.mts → EventTransport-CUw5NNWe.d.mts} +0 -0
  118. /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
  119. /package/dist/{applyPermissionResult-bqGpo9ML.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
  120. /package/dist/{betterAuthOpenApi--rdY15Ld.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +0 -0
  121. /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
  122. /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
  123. /package/dist/{errors-BI8kEKsO.d.mts → errors-CCSsMpXE.d.mts} +0 -0
  124. /package/dist/{errors-CqWnSqM-.mjs → errors-D5c-5BJL.mjs} +0 -0
  125. /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
  126. /package/dist/{fields-CU6FlaDV.mjs → fields-bxkeltzz.mjs} +0 -0
  127. /package/dist/{interface-B-pe8fhj.d.mts → interface-CSbZdv_3.d.mts} +0 -0
  128. /package/dist/{loadResources-Bksk8ydA.mjs → loadResources-BAzJItAJ.mjs} +0 -0
  129. /package/dist/{logger-CDjpjySd.mjs → logger-DLg8-Ueg.mjs} +0 -0
  130. /package/dist/{metrics-TuOmguhi.mjs → metrics-DuhiSEZI.mjs} +0 -0
  131. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
  132. /package/dist/{registry-B0Wl7uVV.mjs → registry-B3lRFBWo.mjs} +0 -0
  133. /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
  134. /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
  135. /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
  136. /package/dist/{storage-BwGQXUpd.d.mts → storage-CVk_SEn2.d.mts} +0 -0
  137. /package/dist/{store-helpers-DFiZl5TL.mjs → store-helpers-ZCSMJJAX.mjs} +0 -0
  138. /package/dist/{tracing-xqXzWeaf.d.mts → tracing-65B51Dw3.d.mts} +0 -0
  139. /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
@@ -1,8 +1,212 @@
1
- import { J as OpenApiSchemas, X as ParsedQuery, p as AnyRecord, tt as QueryParserInterface } from "../interface-YrWsmKqE.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-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";
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
@@ -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 { 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";
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
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-CqWnSqM-.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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.9.1",
3
+ "version": "2.10.3",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -112,10 +112,6 @@
112
112
  "types": "./dist/events/transports/redis-stream-entry.d.mts",
113
113
  "default": "./dist/events/transports/redis-stream-entry.mjs"
114
114
  },
115
- "./dynamic": {
116
- "types": "./dist/dynamic/index.d.mts",
117
- "default": "./dist/dynamic/index.mjs"
118
- },
119
115
  "./testing": {
120
116
  "types": "./dist/testing/index.d.mts",
121
117
  "default": "./dist/testing/index.mjs"
@@ -124,10 +120,6 @@
124
120
  "types": "./dist/testing/storageContract.d.mts",
125
121
  "default": "./dist/testing/storageContract.mjs"
126
122
  },
127
- "./policies": {
128
- "types": "./dist/policies/index.d.mts",
129
- "default": "./dist/policies/index.mjs"
130
- },
131
123
  "./factory": {
132
124
  "types": "./dist/factory/index.d.mts",
133
125
  "default": "./dist/factory/index.mjs"
@@ -188,10 +180,6 @@
188
180
  "types": "./dist/scope/index.d.mts",
189
181
  "default": "./dist/scope/index.mjs"
190
182
  },
191
- "./rpc": {
192
- "types": "./dist/rpc/index.d.mts",
193
- "default": "./dist/rpc/index.mjs"
194
- },
195
183
  "./mcp": {
196
184
  "types": "./dist/integrations/mcp/index.d.mts",
197
185
  "default": "./dist/integrations/mcp/index.mjs"
@@ -235,7 +223,8 @@
235
223
  "node": ">=22"
236
224
  },
237
225
  "peerDependencies": {
238
- "@classytic/mongokit": ">=3.8.0",
226
+ "@classytic/mongokit": ">=3.10.2",
227
+ "@classytic/repo-core": ">=0.1.0",
239
228
  "@classytic/streamline": ">=2.1.0",
240
229
  "@fastify/cors": ">=11.0.0",
241
230
  "@fastify/helmet": ">=13.0.0",
@@ -255,11 +244,11 @@
255
244
  "@sinclair/typebox": ">=0.34.0",
256
245
  "better-auth": ">=1.6.2",
257
246
  "bullmq": ">=5.0.0",
258
- "fastify": ">=5.0.0",
247
+ "fastify": "^5.8.5",
259
248
  "fastify-raw-body": ">=5.0.0",
260
249
  "ioredis": ">=5.0.0",
261
250
  "mongodb": ">=7.0.0",
262
- "mongoose": ">=9.4.1",
251
+ "mongoose": "^9.4.1",
263
252
  "pino-pretty": ">=13.0.0",
264
253
  "zod": ">=4.0.0"
265
254
  },
@@ -267,6 +256,9 @@
267
256
  "@classytic/mongokit": {
268
257
  "optional": true
269
258
  },
259
+ "@classytic/repo-core": {
260
+ "optional": true
261
+ },
270
262
  "mongodb": {
271
263
  "optional": true
272
264
  },
@@ -352,9 +344,12 @@
352
344
  "secure-json-parse": "^4.1.0"
353
345
  },
354
346
  "devDependencies": {
347
+ "@better-auth/drizzle-adapter": "^1.6.2",
355
348
  "@better-auth/mongo-adapter": "^1.6.2",
356
349
  "@biomejs/biome": "^2.4.11",
357
- "@classytic/mongokit": "^3.8.0",
350
+ "@classytic/mongokit": "^3.10.2",
351
+ "@classytic/repo-core": "^0.1.0",
352
+ "@classytic/sqlitekit": "^0.1.0",
358
353
  "@classytic/streamline": "^2.1.0",
359
354
  "@fastify/cors": "^11.2.0",
360
355
  "@fastify/helmet": "^13.0.2",
@@ -372,8 +367,10 @@
372
367
  "@vitest/coverage-v8": "^3.2.4",
373
368
  "ajv": "^8.18.0",
374
369
  "better-auth": "^1.6.2",
370
+ "better-sqlite3": "^12.9.0",
375
371
  "bullmq": "^5.73.5",
376
372
  "dotenv": "^17.4.2",
373
+ "drizzle-orm": "^0.45.2",
377
374
  "fast-check": "^4.6.0",
378
375
  "fastify-raw-body": "^5.0.0",
379
376
  "ioredis": "^5.10.1",
@@ -381,7 +378,7 @@
381
378
  "knip": "^6.4.1",
382
379
  "mongodb": "^7.1.0",
383
380
  "mongodb-memory-server": "^11.0.1",
384
- "mongoose": "^9.4.1",
381
+ "mongoose": ">=9.4.1",
385
382
  "tsdown": "^0.21.7",
386
383
  "typescript": "^6.0.2",
387
384
  "vitest": "^3.0.0",
@@ -8,11 +8,11 @@ description: |
8
8
  Triggers: arc, fastify resource, defineResource, createApp, BaseController, arc preset,
9
9
  arc auth, arc events, arc jobs, arc websocket, arc mcp, arc plugin, arc testing, arc cli,
10
10
  arc permissions, arc hooks, arc pipeline, arc factory, arc cache, arc QueryCache.
11
- version: 2.9.1
11
+ version: 2.10.3
12
12
  license: MIT
13
13
  metadata:
14
14
  author: Classytic
15
- version: "2.9.1"
15
+ version: "2.10.3"
16
16
  tags:
17
17
  - fastify
18
18
  - rest-api
@@ -692,14 +692,11 @@ class ProductController extends BaseController<Product> {
692
692
  import { createMongooseAdapter } from '@classytic/arc';
693
693
  const adapter = createMongooseAdapter({ model: ProductModel, repository: productRepo });
694
694
 
695
- // Custom adapter — implement CrudRepository interface:
696
- interface CrudRepository<TDoc> {
697
- getAll(params?): Promise<TDoc[] | PaginatedResult<TDoc>>;
698
- getById(id: string): Promise<TDoc | null>;
699
- create(data): Promise<TDoc>;
700
- update(id: string, data): Promise<TDoc | null>;
701
- delete(id: string): Promise<boolean>;
702
- }
695
+ // Custom adapter — implement MinimalRepo from @classytic/repo-core/repository:
696
+ import type { MinimalRepo } from '@classytic/repo-core/repository';
697
+ // MinimalRepo<TDoc> = five-method floor (getAll, getById, create, update, delete)
698
+ // StandardRepo<TDoc> = MinimalRepo + optional batch ops, CAS, soft-delete, etc.
699
+ // Arc feature-detects optional methods at call sites.
703
700
  ```
704
701
 
705
702
  ## Events
@@ -1178,7 +1175,6 @@ import {
1178
1175
  } from '@classytic/arc/scope';
1179
1176
  import { createTenantKeyGenerator } from '@classytic/arc/scope';
1180
1177
  import { createRoleHierarchy } from '@classytic/arc/permissions';
1181
- import { createServiceClient } from '@classytic/arc/rpc';
1182
1178
  import { metricsPlugin, versioningPlugin } from '@classytic/arc/plugins';
1183
1179
  import { webhookPlugin } from '@classytic/arc/integrations/webhooks';
1184
1180
  import { mcpPlugin, createMcpServer, defineTool, definePrompt, fieldRulesToZod, resourceToTools } from '@classytic/arc/mcp';