@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.
- package/README.md +50 -38
- package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-CbKKIflT.mjs} +193 -143
- package/dist/EventTransport-CUw5NNWe.d.mts +293 -0
- package/dist/{ResourceRegistry-C6uXlWe3.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
- package/dist/adapters/index.d.mts +3 -3
- package/dist/adapters/index.mjs +2 -2
- package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
- package/dist/audit/index.d.mts +135 -11
- package/dist/audit/index.mjs +107 -20
- package/dist/auth/index.d.mts +17 -9
- package/dist/auth/index.mjs +14 -7
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-BuUcUEJq.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +1 -1
- package/dist/cache/index.d.mts +17 -15
- package/dist/cache/index.mjs +15 -14
- package/dist/{caching-IMuYVjTL.mjs → caching-CBpK_SCM.mjs} +8 -3
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -6
- package/dist/{defineResource-tcgySDo1.mjs → core-CcR01lup.mjs} +58 -61
- package/dist/{createActionRouter-BORM8f17.mjs → createActionRouter-Bp_5c_2b.mjs} +3 -3
- package/dist/{createApp-B1EY8zxa.mjs → createApp-BuvPma24.mjs} +15 -14
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DtFxrG0s.mjs → elevation-C7hgL_aI.mjs} +22 -8
- package/dist/{errorHandler-f869_8PQ.mjs → errorHandler-Bb49BvPD.mjs} +59 -7
- package/dist/{errorHandler-Bah5JhBd.d.mts → errorHandler-DRQ3EqfL.d.mts} +37 -2
- package/dist/{eventPlugin-D9DKB2zM.d.mts → eventPlugin-CxWgpd6K.d.mts} +14 -2
- package/dist/{eventPlugin-CDjVTM82.mjs → eventPlugin-DCUjuiQT.mjs} +83 -5
- package/dist/events/index.d.mts +150 -36
- package/dist/events/index.mjs +355 -101
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -2
- package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
- package/dist/{fields-ipsbIRPK.mjs → fields-bxkeltzz.mjs} +18 -5
- package/dist/{filesUpload-C7r7HIeA.mjs → filesUpload-t21LS-py.mjs} +65 -7
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +32 -5
- package/dist/idempotency/index.mjs +119 -12
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-DtDzOBn8.d.mts → index-8qw4y6ff.d.mts} +4 -135
- package/dist/{index-BLXBmWud.d.mts → index-ChIw3776.d.mts} +283 -408
- package/dist/{interface-CMRutPfe.d.mts → index-Cl0uoKd5.d.mts} +1758 -2506
- package/dist/{index-C1meYuDn.d.mts → index-DStwgFUK.d.mts} +81 -7
- package/dist/index.d.mts +7 -8
- package/dist/index.mjs +11 -12
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +26 -8
- package/dist/integrations/mcp/index.mjs +96 -17
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/webhooks.d.mts +5 -0
- package/dist/integrations/webhooks.mjs +6 -0
- package/dist/interface-D218ikEo.d.mts +77 -0
- package/dist/{memory-Cp7_cAko.mjs → memory-B5Amv9A1.mjs} +23 -8
- package/dist/{openapi-CbKUJY_m.mjs → openapi-B5F8AddX.mjs} +3 -3
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -4
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-CH4cNwJi.mjs → permissions-Dk6mshja.mjs} +315 -397
- package/dist/plugins/index.d.mts +7 -7
- package/dist/plugins/index.mjs +14 -16
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +27 -5
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +3 -2
- package/dist/presets/index.mjs +4 -3
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +178 -0
- package/dist/presets/search.mjs +150 -0
- package/dist/{presets-C2xgzW6x.mjs → presets-fLJVXdVn.mjs} +1 -1
- package/dist/{queryCachePlugin-BJJGBTlu.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
- package/dist/{queryCachePlugin-BH-fidlv.mjs → queryCachePlugin-DQCEfJis.mjs} +9 -9
- package/dist/{queryParser-CgCtsjti.mjs → queryParser-DBqBB6AC.mjs} +1 -1
- package/dist/{redis-BM00zaPB.d.mts → redis-DqyeggCa.d.mts} +1 -1
- package/dist/{redis-stream-CrsfUmPt.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{resourceToTools-8s-EsCCe.mjs → resourceToTools-BElv3xPT.mjs} +65 -48
- package/dist/{schemaConverter-Y7nCYaLJ.mjs → schemaConverter-BxFDdtXu.mjs} +1 -1
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-Ad7ypl9e.mjs → sse-yBCgOLGu.mjs} +1 -1
- package/dist/store-helpers-ZCSMJJAX.mjs +57 -0
- package/dist/testing/index.d.mts +9 -17
- package/dist/testing/index.mjs +27 -83
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -31
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-BsbNMEDR.d.mts → types-Btdda02s.d.mts} +1 -1
- package/dist/{types-Ch9pTQbf.d.mts → types-Co8k3NyS.d.mts} +11 -9
- package/dist/types-Csi3FLfq.mjs +27 -0
- package/dist/utils/index.d.mts +208 -4
- package/dist/utils/index.mjs +5 -6
- package/dist/{utils-yYT3HDXt.mjs → utils-B2fNOD_i.mjs} +285 -2
- package/dist/{versioning-CDugduqI.mjs → versioning-C2U_bLY0.mjs} +3 -5
- package/package.json +20 -26
- package/skills/arc/SKILL.md +97 -23
- package/skills/arc/references/auth.md +94 -0
- package/skills/arc/references/events.md +200 -12
- package/skills/arc/references/mcp.md +4 -17
- package/skills/arc/references/multi-tenancy.md +43 -0
- package/skills/arc/references/production.md +34 -60
- package/dist/EventTransport-BXja8NOc.d.mts +0 -135
- package/dist/audit/mongodb.d.mts +0 -2
- package/dist/audit/mongodb.mjs +0 -2
- package/dist/circuitBreaker-cmi5XDv5.mjs +0 -284
- package/dist/circuitBreaker-dTtG-UyS.d.mts +0 -206
- package/dist/core-F0QoWBt2.mjs +0 -34
- package/dist/dynamic/index.d.mts +0 -93
- package/dist/dynamic/index.mjs +0 -122
- package/dist/fields-DpZQa_Q3.d.mts +0 -109
- package/dist/idempotency/mongodb.d.mts +0 -2
- package/dist/idempotency/mongodb.mjs +0 -123
- package/dist/interface-4y979v99.d.mts +0 -54
- package/dist/mongodb-BsP-WbhN.d.mts +0 -127
- package/dist/mongodb-CTcp0hQZ.d.mts +0 -80
- package/dist/mongodb-Utc5k_-0.mjs +0 -90
- package/dist/policies/index.d.mts +0 -432
- package/dist/policies/index.mjs +0 -318
- package/dist/rpc/index.d.mts +0 -90
- package/dist/rpc/index.mjs +0 -248
- /package/dist/{HookSystem-HprTmvVY.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
- /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
- /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
- /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
- /package/dist/{errors-Ck2h67pm.d.mts → errors-CCSsMpXE.d.mts} +0 -0
- /package/dist/{errors-BF2bIOIS.mjs → errors-D5c-5BJL.mjs} +0 -0
- /package/dist/{externalPaths-BnkYrNzp.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
- /package/dist/{interface-DfLGcus7.d.mts → interface-CSbZdv_3.d.mts} +0 -0
- /package/dist/{loadResources-PWd0OCpV.mjs → loadResources-BAzJItAJ.mjs} +0 -0
- /package/dist/{logger-D1YrIImS.mjs → logger-DLg8-Ueg.mjs} +0 -0
- /package/dist/{metrics-B-PU4-Yu.mjs → metrics-DuhiSEZI.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{registry-BiTKT1Dg.mjs → registry-B3lRFBWo.mjs} +0 -0
- /package/dist/{replyHelpers-CxkYGT81.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
- /package/dist/{requestContext-DYvHl113.mjs → requestContext-xHIKedG6.mjs} +0 -0
- /package/dist/{sessionManager-DDCmiNIo.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
- /package/dist/{storage-Dfzt4VTl.d.mts → storage-CVk_SEn2.d.mts} +0 -0
- /package/dist/{tracing-DdN2-wHJ.d.mts → tracing-65B51Dw3.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
- /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 };
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,8 +1,212 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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
|
|
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.
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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-
|
|
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
|
-
|
|
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
|
}
|