@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.
- package/README.md +19 -90
- package/dist/{BaseController-Vu2yc56T.mjs → BaseController-CbKKIflT.mjs} +8 -44
- package/dist/{ResourceRegistry-Dq3_zBQP.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 +38 -3
- package/dist/audit/index.mjs +41 -7
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +5 -5
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/cache/index.d.mts +17 -15
- package/dist/cache/index.mjs +15 -14
- package/dist/{caching-CjybdRwx.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 +2 -2
- package/dist/core/index.mjs +3 -4
- package/dist/{defineResource-C__jkwvs.mjs → core-CcR01lup.mjs} +44 -12
- package/dist/{createActionRouter-DH1YFL9m.mjs → createActionRouter-Bp_5c_2b.mjs} +1 -1
- package/dist/{createApp-CBJUJKGP.mjs → createApp-BuvPma24.mjs} +14 -14
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DxQ6ACbt.mjs → elevation-C7hgL_aI.mjs} +2 -2
- package/dist/{errorHandler-CZDW4EXS.mjs → errorHandler-Bb49BvPD.mjs} +1 -1
- package/dist/{errorHandler-DixGcttC.d.mts → errorHandler-DRQ3EqfL.d.mts} +1 -1
- package/dist/{eventPlugin-BxvaCIZF.d.mts → eventPlugin-CxWgpd6K.d.mts} +1 -1
- package/dist/{eventPlugin-Dl7MoVWH.mjs → eventPlugin-DCUjuiQT.mjs} +1 -1
- package/dist/events/index.d.mts +8 -5
- package/dist/events/index.mjs +34 -17
- 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/{filesUpload-q8oHt--L.mjs → filesUpload-t21LS-py.mjs} +2 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +7 -4
- package/dist/idempotency/index.mjs +9 -11
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-Cibkchnx.d.mts → index-8qw4y6ff.d.mts} +2 -2
- package/dist/{index-C-xjcA6F.d.mts → index-ChIw3776.d.mts} +283 -408
- package/dist/{interface-YrWsmKqE.d.mts → index-Cl0uoKd5.d.mts} +1885 -2741
- package/dist/{index-CtGKT0lf.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 +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/interface-D218ikEo.d.mts +77 -0
- package/dist/{memory-BFAYkf8H.mjs → memory-B5Amv9A1.mjs} +23 -8
- package/dist/{openapi-CXuTG1M9.mjs → openapi-B5F8AddX.mjs} +2 -2
- 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-oNZawnkR.mjs → permissions-Dk6mshja.mjs} +315 -397
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +12 -14
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/presets/search.d.mts +91 -4
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-hM4WhNWY.mjs → presets-fLJVXdVn.mjs} +1 -1
- package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
- package/dist/{queryCachePlugin-DbUVroUG.mjs → queryCachePlugin-DQCEfJis.mjs} +8 -8
- package/dist/{queryParser-Cs-6SHQK.mjs → queryParser-DBqBB6AC.mjs} +1 -1
- package/dist/{redis-MXLp1oOf.d.mts → redis-DqyeggCa.d.mts} +1 -1
- package/dist/{redis-stream-Bz-4q96t.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-C3cWymnW.mjs → resourceToTools-BElv3xPT.mjs} +3 -3
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-CJpt7LGI.mjs → sse-yBCgOLGu.mjs} +1 -1
- package/dist/testing/index.d.mts +6 -5
- package/dist/testing/index.mjs +8 -10
- 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-CoSzA-s-.d.mts → types-Btdda02s.d.mts} +1 -1
- package/dist/{types-CunEX4UX.d.mts → types-Co8k3NyS.d.mts} +9 -9
- package/dist/types-Csi3FLfq.mjs +27 -0
- package/dist/utils/index.d.mts +207 -3
- package/dist/utils/index.mjs +3 -4
- package/dist/{utils-B7FuRr9w.mjs → utils-B2fNOD_i.mjs} +285 -2
- package/dist/{versioning-Cm8qoFDg.mjs → versioning-C2U_bLY0.mjs} +3 -5
- package/package.json +15 -18
- package/skills/arc/SKILL.md +7 -11
- package/skills/arc/references/production.md +0 -41
- package/dist/circuitBreaker-CvXkjfrW.d.mts +0 -206
- package/dist/circuitBreaker-l18oRgL5.mjs +0 -284
- package/dist/core-DNncu0xF.mjs +0 -34
- package/dist/dynamic/index.d.mts +0 -93
- package/dist/dynamic/index.mjs +0 -122
- package/dist/fields-BC7zcmI9.d.mts +0 -121
- package/dist/interface-DplgQO2e.d.mts +0 -54
- package/dist/policies/index.d.mts +0 -425
- package/dist/policies/index.mjs +0 -318
- package/dist/rpc/index.d.mts +0 -90
- package/dist/rpc/index.mjs +0 -248
- /package/dist/{EventTransport-CqZ8FyM_.d.mts → EventTransport-CUw5NNWe.d.mts} +0 -0
- /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
- /package/dist/{applyPermissionResult-bqGpo9ML.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
- /package/dist/{betterAuthOpenApi--rdY15Ld.mjs → betterAuthOpenApi-BBRVhjQN.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-BI8kEKsO.d.mts → errors-CCSsMpXE.d.mts} +0 -0
- /package/dist/{errors-CqWnSqM-.mjs → errors-D5c-5BJL.mjs} +0 -0
- /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
- /package/dist/{fields-CU6FlaDV.mjs → fields-bxkeltzz.mjs} +0 -0
- /package/dist/{interface-B-pe8fhj.d.mts → interface-CSbZdv_3.d.mts} +0 -0
- /package/dist/{loadResources-Bksk8ydA.mjs → loadResources-BAzJItAJ.mjs} +0 -0
- /package/dist/{logger-CDjpjySd.mjs → logger-DLg8-Ueg.mjs} +0 -0
- /package/dist/{metrics-TuOmguhi.mjs → metrics-DuhiSEZI.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{registry-B0Wl7uVV.mjs → registry-B3lRFBWo.mjs} +0 -0
- /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
- /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
- /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
- /package/dist/{storage-BwGQXUpd.d.mts → storage-CVk_SEn2.d.mts} +0 -0
- /package/dist/{store-helpers-DFiZl5TL.mjs → store-helpers-ZCSMJJAX.mjs} +0 -0
- /package/dist/{tracing-xqXzWeaf.d.mts → tracing-65B51Dw3.d.mts} +0 -0
- /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
|
@@ -366,28 +366,6 @@ const runner = new MigrationRunner(mongoose.connection.db);
|
|
|
366
366
|
await runner.up([v2]);
|
|
367
367
|
```
|
|
368
368
|
|
|
369
|
-
## Policies
|
|
370
|
-
|
|
371
|
-
Query-level authorization — modify queries based on user:
|
|
372
|
-
|
|
373
|
-
```typescript
|
|
374
|
-
import { createAccessControlPolicy } from '@classytic/arc/policies';
|
|
375
|
-
|
|
376
|
-
const editorPolicy = createAccessControlPolicy({
|
|
377
|
-
statements: [
|
|
378
|
-
{ resource: 'product', action: ['create', 'update'] },
|
|
379
|
-
{ resource: 'order', action: ['read'] },
|
|
380
|
-
],
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
defineResource({
|
|
384
|
-
permissions: {
|
|
385
|
-
create: editorPolicy,
|
|
386
|
-
update: editorPolicy,
|
|
387
|
-
},
|
|
388
|
-
});
|
|
389
|
-
```
|
|
390
|
-
|
|
391
369
|
## OpenAPI & External Paths
|
|
392
370
|
|
|
393
371
|
Arc auto-generates OpenAPI 3.0 specs from resource definitions. External integrations (auth adapters, custom routes) inject their paths via `ExternalOpenApiPaths`.
|
|
@@ -562,25 +540,6 @@ const relayed = await outbox.relay(); // publishes pending → transport
|
|
|
562
540
|
|
|
563
541
|
**OutboxStore interface**: `save(event)`, `getPending(limit)`, `acknowledge(eventId)` (+ optional `claimPending`, `fail`, `getDeadLettered`, `purge`). When you pass `repository`, arc adapts it internally.
|
|
564
542
|
|
|
565
|
-
## RPC Service Client — Schema Versioning
|
|
566
|
-
|
|
567
|
-
The service client supports a `schemaVersion` option for contract compatibility between services:
|
|
568
|
-
|
|
569
|
-
```typescript
|
|
570
|
-
import { createServiceClient } from '@classytic/arc/rpc';
|
|
571
|
-
|
|
572
|
-
const catalog = createServiceClient({
|
|
573
|
-
baseUrl: 'http://catalog:3000',
|
|
574
|
-
schemaVersion: '1.2.0', // sent as x-arc-schema-version header
|
|
575
|
-
correlationId: () => request.id,
|
|
576
|
-
retry: { maxRetries: 2 },
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
const products = await catalog.resource('product').list();
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
Receiving services can check `request.headers['x-arc-schema-version']` to detect version mismatches.
|
|
583
|
-
|
|
584
543
|
## Bulk Operations Preset
|
|
585
544
|
|
|
586
545
|
Adds bulk CRUD routes — repository must provide `createMany`, `updateMany`, `deleteMany`:
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
//#region src/utils/circuitBreaker.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Circuit Breaker Pattern
|
|
4
|
-
*
|
|
5
|
-
* Wraps external service calls with failure protection.
|
|
6
|
-
* Prevents cascading failures by "opening" the circuit when
|
|
7
|
-
* a service is failing, allowing it time to recover.
|
|
8
|
-
*
|
|
9
|
-
* States:
|
|
10
|
-
* - CLOSED: Normal operation, requests pass through
|
|
11
|
-
* - OPEN: Too many failures, all requests fail fast
|
|
12
|
-
* - HALF_OPEN: Testing if service recovered, limited requests
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* import { CircuitBreaker } from '@classytic/arc/utils';
|
|
16
|
-
*
|
|
17
|
-
* const paymentBreaker = new CircuitBreaker(async (amount) => {
|
|
18
|
-
* return await stripe.charges.create({ amount });
|
|
19
|
-
* }, {
|
|
20
|
-
* failureThreshold: 5,
|
|
21
|
-
* resetTimeout: 30000,
|
|
22
|
-
* timeout: 5000,
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* try {
|
|
26
|
-
* const result = await paymentBreaker.call(100);
|
|
27
|
-
* } catch (error) {
|
|
28
|
-
* // Handle failure or circuit open
|
|
29
|
-
* }
|
|
30
|
-
*/
|
|
31
|
-
declare const CircuitState: {
|
|
32
|
-
readonly CLOSED: "CLOSED";
|
|
33
|
-
readonly OPEN: "OPEN";
|
|
34
|
-
readonly HALF_OPEN: "HALF_OPEN";
|
|
35
|
-
};
|
|
36
|
-
type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];
|
|
37
|
-
interface CircuitBreakerOptions {
|
|
38
|
-
/**
|
|
39
|
-
* Number of failures before opening circuit
|
|
40
|
-
* @default 5
|
|
41
|
-
*/
|
|
42
|
-
failureThreshold?: number;
|
|
43
|
-
/**
|
|
44
|
-
* Time in ms before attempting to close circuit
|
|
45
|
-
* @default 60000 (60 seconds)
|
|
46
|
-
*/
|
|
47
|
-
resetTimeout?: number;
|
|
48
|
-
/**
|
|
49
|
-
* Request timeout in ms
|
|
50
|
-
* @default 10000 (10 seconds)
|
|
51
|
-
*/
|
|
52
|
-
timeout?: number;
|
|
53
|
-
/**
|
|
54
|
-
* Number of successful requests in HALF_OPEN before closing
|
|
55
|
-
* @default 1
|
|
56
|
-
*/
|
|
57
|
-
successThreshold?: number;
|
|
58
|
-
/**
|
|
59
|
-
* Fallback function when circuit is open.
|
|
60
|
-
* Receives the same arguments as the wrapped function.
|
|
61
|
-
*/
|
|
62
|
-
fallback?: (...args: unknown[]) => Promise<unknown>;
|
|
63
|
-
/**
|
|
64
|
-
* Callback when state changes
|
|
65
|
-
*/
|
|
66
|
-
onStateChange?: (from: CircuitState, to: CircuitState) => void;
|
|
67
|
-
/**
|
|
68
|
-
* Callback on error
|
|
69
|
-
*/
|
|
70
|
-
onError?: (error: Error) => void;
|
|
71
|
-
/**
|
|
72
|
-
* Name for logging/monitoring
|
|
73
|
-
*/
|
|
74
|
-
name?: string;
|
|
75
|
-
}
|
|
76
|
-
interface CircuitBreakerStats {
|
|
77
|
-
name?: string;
|
|
78
|
-
state: CircuitState;
|
|
79
|
-
failures: number;
|
|
80
|
-
successes: number;
|
|
81
|
-
totalCalls: number;
|
|
82
|
-
openedAt: number | null;
|
|
83
|
-
lastCallAt: number | null;
|
|
84
|
-
}
|
|
85
|
-
declare class CircuitBreakerError extends Error {
|
|
86
|
-
state: CircuitState;
|
|
87
|
-
constructor(message: string, state: CircuitState);
|
|
88
|
-
}
|
|
89
|
-
declare class CircuitBreaker<T extends (...args: any[]) => Promise<any>> {
|
|
90
|
-
private state;
|
|
91
|
-
private failures;
|
|
92
|
-
private successes;
|
|
93
|
-
private totalCalls;
|
|
94
|
-
private nextAttempt;
|
|
95
|
-
private lastCallAt;
|
|
96
|
-
private openedAt;
|
|
97
|
-
private readonly failureThreshold;
|
|
98
|
-
private readonly resetTimeout;
|
|
99
|
-
private readonly timeout;
|
|
100
|
-
private readonly successThreshold;
|
|
101
|
-
private readonly fallback?;
|
|
102
|
-
private readonly onStateChange?;
|
|
103
|
-
private readonly onError?;
|
|
104
|
-
private readonly name;
|
|
105
|
-
private readonly fn;
|
|
106
|
-
constructor(fn: T, options?: CircuitBreakerOptions);
|
|
107
|
-
/**
|
|
108
|
-
* Call the wrapped function with circuit breaker protection
|
|
109
|
-
*/
|
|
110
|
-
call(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
111
|
-
/**
|
|
112
|
-
* Execute function with timeout
|
|
113
|
-
*/
|
|
114
|
-
private executeWithTimeout;
|
|
115
|
-
/**
|
|
116
|
-
* Handle successful call
|
|
117
|
-
*/
|
|
118
|
-
private onSuccess;
|
|
119
|
-
/**
|
|
120
|
-
* Handle failed call
|
|
121
|
-
*/
|
|
122
|
-
private onFailure;
|
|
123
|
-
/**
|
|
124
|
-
* Change circuit state
|
|
125
|
-
*/
|
|
126
|
-
private setState;
|
|
127
|
-
/**
|
|
128
|
-
* Manually open the circuit
|
|
129
|
-
*/
|
|
130
|
-
open(): void;
|
|
131
|
-
/**
|
|
132
|
-
* Manually close the circuit
|
|
133
|
-
*/
|
|
134
|
-
close(): void;
|
|
135
|
-
/**
|
|
136
|
-
* Get current statistics
|
|
137
|
-
*/
|
|
138
|
-
getStats(): CircuitBreakerStats;
|
|
139
|
-
/**
|
|
140
|
-
* Get current state
|
|
141
|
-
*/
|
|
142
|
-
getState(): CircuitState;
|
|
143
|
-
/**
|
|
144
|
-
* Check if circuit is open
|
|
145
|
-
*/
|
|
146
|
-
isOpen(): boolean;
|
|
147
|
-
/**
|
|
148
|
-
* Check if circuit is closed
|
|
149
|
-
*/
|
|
150
|
-
isClosed(): boolean;
|
|
151
|
-
/**
|
|
152
|
-
* Reset statistics
|
|
153
|
-
*/
|
|
154
|
-
reset(): void;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Create a circuit breaker with sensible defaults
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* const emailBreaker = createCircuitBreaker(
|
|
161
|
-
* async (to, subject, body) => sendEmail(to, subject, body),
|
|
162
|
-
* { name: 'email-service' }
|
|
163
|
-
* );
|
|
164
|
-
*/
|
|
165
|
-
declare function createCircuitBreaker<T extends (...args: any[]) => Promise<any>>(fn: T, options?: CircuitBreakerOptions): CircuitBreaker<T>;
|
|
166
|
-
/**
|
|
167
|
-
* Circuit breaker registry for managing multiple breakers
|
|
168
|
-
*/
|
|
169
|
-
declare class CircuitBreakerRegistry {
|
|
170
|
-
private breakers;
|
|
171
|
-
/**
|
|
172
|
-
* Register a circuit breaker
|
|
173
|
-
*/
|
|
174
|
-
register<T extends (...args: any[]) => Promise<any>>(name: string, fn: T, options?: Omit<CircuitBreakerOptions, "name">): CircuitBreaker<T>;
|
|
175
|
-
/**
|
|
176
|
-
* Get a circuit breaker by name
|
|
177
|
-
*/
|
|
178
|
-
get(name: string): CircuitBreaker<any> | undefined;
|
|
179
|
-
/**
|
|
180
|
-
* Get all breakers
|
|
181
|
-
*/
|
|
182
|
-
getAll(): Map<string, CircuitBreaker<any>>;
|
|
183
|
-
/**
|
|
184
|
-
* Get statistics for all breakers
|
|
185
|
-
*/
|
|
186
|
-
getAllStats(): Record<string, CircuitBreakerStats>;
|
|
187
|
-
/**
|
|
188
|
-
* Reset all breakers
|
|
189
|
-
*/
|
|
190
|
-
resetAll(): void;
|
|
191
|
-
/**
|
|
192
|
-
* Open all breakers
|
|
193
|
-
*/
|
|
194
|
-
openAll(): void;
|
|
195
|
-
/**
|
|
196
|
-
* Close all breakers
|
|
197
|
-
*/
|
|
198
|
-
closeAll(): void;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Create a new CircuitBreakerRegistry instance.
|
|
202
|
-
* Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
|
|
203
|
-
*/
|
|
204
|
-
declare function createCircuitBreakerRegistry(): CircuitBreakerRegistry;
|
|
205
|
-
//#endregion
|
|
206
|
-
export { CircuitBreakerStats as a, createCircuitBreakerRegistry as c, CircuitBreakerRegistry as i, CircuitBreakerError as n, CircuitState as o, CircuitBreakerOptions as r, createCircuitBreaker as s, CircuitBreaker as t };
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
//#region src/utils/circuitBreaker.ts
|
|
2
|
-
/**
|
|
3
|
-
* Circuit Breaker Pattern
|
|
4
|
-
*
|
|
5
|
-
* Wraps external service calls with failure protection.
|
|
6
|
-
* Prevents cascading failures by "opening" the circuit when
|
|
7
|
-
* a service is failing, allowing it time to recover.
|
|
8
|
-
*
|
|
9
|
-
* States:
|
|
10
|
-
* - CLOSED: Normal operation, requests pass through
|
|
11
|
-
* - OPEN: Too many failures, all requests fail fast
|
|
12
|
-
* - HALF_OPEN: Testing if service recovered, limited requests
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* import { CircuitBreaker } from '@classytic/arc/utils';
|
|
16
|
-
*
|
|
17
|
-
* const paymentBreaker = new CircuitBreaker(async (amount) => {
|
|
18
|
-
* return await stripe.charges.create({ amount });
|
|
19
|
-
* }, {
|
|
20
|
-
* failureThreshold: 5,
|
|
21
|
-
* resetTimeout: 30000,
|
|
22
|
-
* timeout: 5000,
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* try {
|
|
26
|
-
* const result = await paymentBreaker.call(100);
|
|
27
|
-
* } catch (error) {
|
|
28
|
-
* // Handle failure or circuit open
|
|
29
|
-
* }
|
|
30
|
-
*/
|
|
31
|
-
const CircuitState = {
|
|
32
|
-
CLOSED: "CLOSED",
|
|
33
|
-
OPEN: "OPEN",
|
|
34
|
-
HALF_OPEN: "HALF_OPEN"
|
|
35
|
-
};
|
|
36
|
-
var CircuitBreakerError = class extends Error {
|
|
37
|
-
state;
|
|
38
|
-
constructor(message, state) {
|
|
39
|
-
super(message);
|
|
40
|
-
this.name = "CircuitBreakerError";
|
|
41
|
-
this.state = state;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
var CircuitBreaker = class {
|
|
45
|
-
state = CircuitState.CLOSED;
|
|
46
|
-
failures = 0;
|
|
47
|
-
successes = 0;
|
|
48
|
-
totalCalls = 0;
|
|
49
|
-
nextAttempt = 0;
|
|
50
|
-
lastCallAt = null;
|
|
51
|
-
openedAt = null;
|
|
52
|
-
failureThreshold;
|
|
53
|
-
resetTimeout;
|
|
54
|
-
timeout;
|
|
55
|
-
successThreshold;
|
|
56
|
-
fallback;
|
|
57
|
-
onStateChange;
|
|
58
|
-
onError;
|
|
59
|
-
name;
|
|
60
|
-
fn;
|
|
61
|
-
constructor(fn, options = {}) {
|
|
62
|
-
this.fn = fn;
|
|
63
|
-
this.failureThreshold = options.failureThreshold ?? 5;
|
|
64
|
-
this.resetTimeout = options.resetTimeout ?? 6e4;
|
|
65
|
-
this.timeout = options.timeout ?? 1e4;
|
|
66
|
-
this.successThreshold = options.successThreshold ?? 1;
|
|
67
|
-
this.fallback = options.fallback;
|
|
68
|
-
this.onStateChange = options.onStateChange;
|
|
69
|
-
this.onError = options.onError;
|
|
70
|
-
this.name = options.name ?? "CircuitBreaker";
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Call the wrapped function with circuit breaker protection
|
|
74
|
-
*/
|
|
75
|
-
async call(...args) {
|
|
76
|
-
this.totalCalls++;
|
|
77
|
-
this.lastCallAt = Date.now();
|
|
78
|
-
if (this.state === CircuitState.OPEN) {
|
|
79
|
-
if (Date.now() < this.nextAttempt) {
|
|
80
|
-
const error = new CircuitBreakerError(`Circuit breaker is OPEN for ${this.name}`, CircuitState.OPEN);
|
|
81
|
-
if (this.fallback) return this.fallback(...args);
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
this.setState(CircuitState.HALF_OPEN);
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const result = await this.executeWithTimeout(args);
|
|
88
|
-
this.onSuccess();
|
|
89
|
-
return result;
|
|
90
|
-
} catch (err) {
|
|
91
|
-
this.onFailure(err instanceof Error ? err : new Error(String(err)));
|
|
92
|
-
throw err;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Execute function with timeout
|
|
97
|
-
*/
|
|
98
|
-
async executeWithTimeout(args) {
|
|
99
|
-
return new Promise((resolve, reject) => {
|
|
100
|
-
const timeoutId = setTimeout(() => {
|
|
101
|
-
reject(/* @__PURE__ */ new Error(`Request timeout after ${this.timeout}ms`));
|
|
102
|
-
}, this.timeout);
|
|
103
|
-
this.fn(...args).then((result) => {
|
|
104
|
-
clearTimeout(timeoutId);
|
|
105
|
-
resolve(result);
|
|
106
|
-
}).catch((error) => {
|
|
107
|
-
clearTimeout(timeoutId);
|
|
108
|
-
reject(error);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Handle successful call
|
|
114
|
-
*/
|
|
115
|
-
onSuccess() {
|
|
116
|
-
this.failures = 0;
|
|
117
|
-
this.successes++;
|
|
118
|
-
if (this.state === CircuitState.HALF_OPEN) {
|
|
119
|
-
if (this.successes >= this.successThreshold) {
|
|
120
|
-
this.setState(CircuitState.CLOSED);
|
|
121
|
-
this.successes = 0;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Handle failed call
|
|
127
|
-
*/
|
|
128
|
-
onFailure(error) {
|
|
129
|
-
this.failures++;
|
|
130
|
-
this.successes = 0;
|
|
131
|
-
if (this.onError) this.onError(error);
|
|
132
|
-
if (this.state === CircuitState.HALF_OPEN || this.failures >= this.failureThreshold) {
|
|
133
|
-
this.setState(CircuitState.OPEN);
|
|
134
|
-
this.nextAttempt = Date.now() + this.resetTimeout;
|
|
135
|
-
this.openedAt = Date.now();
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Change circuit state
|
|
140
|
-
*/
|
|
141
|
-
setState(newState) {
|
|
142
|
-
const oldState = this.state;
|
|
143
|
-
if (oldState !== newState) {
|
|
144
|
-
this.state = newState;
|
|
145
|
-
if (this.onStateChange) this.onStateChange(oldState, newState);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Manually open the circuit
|
|
150
|
-
*/
|
|
151
|
-
open() {
|
|
152
|
-
this.setState(CircuitState.OPEN);
|
|
153
|
-
this.nextAttempt = Date.now() + this.resetTimeout;
|
|
154
|
-
this.openedAt = Date.now();
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Manually close the circuit
|
|
158
|
-
*/
|
|
159
|
-
close() {
|
|
160
|
-
this.failures = 0;
|
|
161
|
-
this.successes = 0;
|
|
162
|
-
this.setState(CircuitState.CLOSED);
|
|
163
|
-
this.openedAt = null;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Get current statistics
|
|
167
|
-
*/
|
|
168
|
-
getStats() {
|
|
169
|
-
return {
|
|
170
|
-
name: this.name,
|
|
171
|
-
state: this.state,
|
|
172
|
-
failures: this.failures,
|
|
173
|
-
successes: this.successes,
|
|
174
|
-
totalCalls: this.totalCalls,
|
|
175
|
-
openedAt: this.openedAt,
|
|
176
|
-
lastCallAt: this.lastCallAt
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Get current state
|
|
181
|
-
*/
|
|
182
|
-
getState() {
|
|
183
|
-
return this.state;
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Check if circuit is open
|
|
187
|
-
*/
|
|
188
|
-
isOpen() {
|
|
189
|
-
return this.state === CircuitState.OPEN;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Check if circuit is closed
|
|
193
|
-
*/
|
|
194
|
-
isClosed() {
|
|
195
|
-
return this.state === CircuitState.CLOSED;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Reset statistics
|
|
199
|
-
*/
|
|
200
|
-
reset() {
|
|
201
|
-
this.failures = 0;
|
|
202
|
-
this.successes = 0;
|
|
203
|
-
this.totalCalls = 0;
|
|
204
|
-
this.lastCallAt = null;
|
|
205
|
-
this.openedAt = null;
|
|
206
|
-
this.setState(CircuitState.CLOSED);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
/**
|
|
210
|
-
* Create a circuit breaker with sensible defaults
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* const emailBreaker = createCircuitBreaker(
|
|
214
|
-
* async (to, subject, body) => sendEmail(to, subject, body),
|
|
215
|
-
* { name: 'email-service' }
|
|
216
|
-
* );
|
|
217
|
-
*/
|
|
218
|
-
function createCircuitBreaker(fn, options) {
|
|
219
|
-
return new CircuitBreaker(fn, options);
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Circuit breaker registry for managing multiple breakers
|
|
223
|
-
*/
|
|
224
|
-
var CircuitBreakerRegistry = class {
|
|
225
|
-
breakers = /* @__PURE__ */ new Map();
|
|
226
|
-
/**
|
|
227
|
-
* Register a circuit breaker
|
|
228
|
-
*/
|
|
229
|
-
register(name, fn, options) {
|
|
230
|
-
const breaker = new CircuitBreaker(fn, {
|
|
231
|
-
...options,
|
|
232
|
-
name
|
|
233
|
-
});
|
|
234
|
-
this.breakers.set(name, breaker);
|
|
235
|
-
return breaker;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Get a circuit breaker by name
|
|
239
|
-
*/
|
|
240
|
-
get(name) {
|
|
241
|
-
return this.breakers.get(name);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Get all breakers
|
|
245
|
-
*/
|
|
246
|
-
getAll() {
|
|
247
|
-
return this.breakers;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Get statistics for all breakers
|
|
251
|
-
*/
|
|
252
|
-
getAllStats() {
|
|
253
|
-
const stats = {};
|
|
254
|
-
for (const [name, breaker] of this.breakers.entries()) stats[name] = breaker.getStats();
|
|
255
|
-
return stats;
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Reset all breakers
|
|
259
|
-
*/
|
|
260
|
-
resetAll() {
|
|
261
|
-
for (const breaker of this.breakers.values()) breaker.reset();
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Open all breakers
|
|
265
|
-
*/
|
|
266
|
-
openAll() {
|
|
267
|
-
for (const breaker of this.breakers.values()) breaker.open();
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Close all breakers
|
|
271
|
-
*/
|
|
272
|
-
closeAll() {
|
|
273
|
-
for (const breaker of this.breakers.values()) breaker.close();
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
/**
|
|
277
|
-
* Create a new CircuitBreakerRegistry instance.
|
|
278
|
-
* Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
|
|
279
|
-
*/
|
|
280
|
-
function createCircuitBreakerRegistry() {
|
|
281
|
-
return new CircuitBreakerRegistry();
|
|
282
|
-
}
|
|
283
|
-
//#endregion
|
|
284
|
-
export { createCircuitBreaker as a, CircuitState as i, CircuitBreakerError as n, createCircuitBreakerRegistry as o, CircuitBreakerRegistry as r, CircuitBreaker as t };
|
package/dist/core-DNncu0xF.mjs
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { n as defineResource } from "./defineResource-C__jkwvs.mjs";
|
|
2
|
-
//#region src/core/defineResourceVariants.ts
|
|
3
|
-
/**
|
|
4
|
-
* Define multiple resources from a shared base config and per-variant overrides.
|
|
5
|
-
*
|
|
6
|
-
* Each variant is independently passed through `defineResource()` — the
|
|
7
|
-
* returned `ResourceDefinition`s are real, fully-registered resources.
|
|
8
|
-
* Register each one's plugin in your app:
|
|
9
|
-
*
|
|
10
|
-
* ```typescript
|
|
11
|
-
* await app.register(articlePublic.toPlugin());
|
|
12
|
-
* await app.register(articleAdmin.toPlugin());
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* @param base Shared config — adapter, queryParser, schemaOptions, hooks, etc.
|
|
16
|
-
* Must NOT include `name` or `prefix` (those are per-variant).
|
|
17
|
-
* @param variants Map of variant key → override. Each variant must declare
|
|
18
|
-
* its own `name` and `prefix`. Other fields override the base.
|
|
19
|
-
* @returns A record where each key from `variants` maps to a real
|
|
20
|
-
* `ResourceDefinition` ready for `.toPlugin()` registration.
|
|
21
|
-
*/
|
|
22
|
-
function defineResourceVariants(base, variants) {
|
|
23
|
-
const out = {};
|
|
24
|
-
for (const key of Object.keys(variants)) {
|
|
25
|
-
const override = variants[key];
|
|
26
|
-
out[key] = defineResource({
|
|
27
|
-
...base,
|
|
28
|
-
...override
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return out;
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
34
|
-
export { defineResourceVariants as t };
|
package/dist/dynamic/index.d.mts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { Kt as ResourceDefinition, r as DataAdapter } from "../interface-YrWsmKqE.mjs";
|
|
2
|
-
import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
|
|
3
|
-
|
|
4
|
-
//#region src/dynamic/ArcDynamicLoader.d.ts
|
|
5
|
-
interface ArcArchitectureSchema {
|
|
6
|
-
/** Application name */
|
|
7
|
-
app: string;
|
|
8
|
-
/** Resources to provision */
|
|
9
|
-
resources: ArcResourceSchema[];
|
|
10
|
-
}
|
|
11
|
-
/** Field type — maps to JSON Schema / Zod types */
|
|
12
|
-
type ArcFieldType = "string" | "number" | "boolean" | "date" | "object" | "array";
|
|
13
|
-
/** Per-field definition — matches Arc's FieldRuleEntry for MCP compatibility */
|
|
14
|
-
interface ArcFieldSchema {
|
|
15
|
-
type: ArcFieldType;
|
|
16
|
-
required?: boolean;
|
|
17
|
-
description?: string;
|
|
18
|
-
enum?: string[];
|
|
19
|
-
min?: number;
|
|
20
|
-
max?: number;
|
|
21
|
-
minLength?: number;
|
|
22
|
-
maxLength?: number;
|
|
23
|
-
/** System-managed fields (createdAt, updatedAt) — excluded from create/update schemas */
|
|
24
|
-
systemManaged?: boolean;
|
|
25
|
-
/** Immutable after creation (e.g. slug, organizationId) */
|
|
26
|
-
immutable?: boolean;
|
|
27
|
-
}
|
|
28
|
-
/** Permission preset name — matches Arc's built-in presets */
|
|
29
|
-
type ArcPermissionPreset = "publicRead" | "publicReadAdminWrite" | "authenticated" | "adminOnly" | "ownerWithAdminBypass" | "fullPublic" | "readOnly";
|
|
30
|
-
/** Fine-grained per-operation permission */
|
|
31
|
-
interface ArcPermissionMap {
|
|
32
|
-
list?: "public" | "auth" | "admin";
|
|
33
|
-
get?: "public" | "auth" | "admin";
|
|
34
|
-
create?: "auth" | "admin";
|
|
35
|
-
update?: "auth" | "admin" | "owner";
|
|
36
|
-
delete?: "auth" | "admin" | "owner";
|
|
37
|
-
}
|
|
38
|
-
interface ArcResourceSchema {
|
|
39
|
-
/** Resource name (e.g., 'product', 'user') — used for URL prefix and tool names */
|
|
40
|
-
name: string;
|
|
41
|
-
/** Display name for docs and MCP descriptions (defaults to capitalized name) */
|
|
42
|
-
displayName?: string;
|
|
43
|
-
/** Custom URL prefix (defaults to `/${name}s`) */
|
|
44
|
-
prefix?: string;
|
|
45
|
-
/** Adapter resolution key — passed to adapterResolver */
|
|
46
|
-
adapterPattern?: string;
|
|
47
|
-
/** Permission preset name or fine-grained per-operation map */
|
|
48
|
-
permissions: ArcPermissionPreset | ArcPermissionMap;
|
|
49
|
-
/** Presets to apply (e.g., 'softDelete', 'slugLookup', 'bulk') */
|
|
50
|
-
presets?: string[];
|
|
51
|
-
/** Field definitions — drives schemaOptions.fieldRules for validation and MCP tool schemas */
|
|
52
|
-
fields?: Record<string, ArcFieldSchema | ArcFieldType>;
|
|
53
|
-
/** Fields allowed for filtering in list operations (drives queryParser + MCP) */
|
|
54
|
-
filterable?: string[];
|
|
55
|
-
/** Fields allowed for sorting (drives queryParser + MCP) */
|
|
56
|
-
sortable?: string[];
|
|
57
|
-
/** CRUD operations to disable (e.g., ['delete'] for append-only resources) */
|
|
58
|
-
disabledRoutes?: string[];
|
|
59
|
-
/** Tenant field name for multi-tenant resources */
|
|
60
|
-
tenantField?: string;
|
|
61
|
-
}
|
|
62
|
-
interface DynamicLoaderContext {
|
|
63
|
-
/** Resolve a data adapter for a resource — receives name and optional pattern key */
|
|
64
|
-
adapterResolver: (resourceName: string, pattern?: string) => DataAdapter;
|
|
65
|
-
/** Resolve custom permission checks beyond built-in presets */
|
|
66
|
-
permissionResolver?: (policy: string) => PermissionCheck;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Load an Arc Architecture Schema (JSON) and produce fully configured ResourceDefinitions.
|
|
70
|
-
*
|
|
71
|
-
* Each resource gets:
|
|
72
|
-
* - Adapter from the resolver
|
|
73
|
-
* - Permissions from presets or fine-grained map
|
|
74
|
-
* - schemaOptions.fieldRules for validation and MCP tool schemas
|
|
75
|
-
* - ArcQueryParser with allowedFilterFields/allowedSortFields for MCP auto-derive
|
|
76
|
-
* - Presets applied
|
|
77
|
-
*/
|
|
78
|
-
declare class ArcDynamicLoader {
|
|
79
|
-
private context;
|
|
80
|
-
constructor(context: DynamicLoaderContext);
|
|
81
|
-
/**
|
|
82
|
-
* Load an AAS definition and return fully constructed ResourceDefinitions.
|
|
83
|
-
* Validates the schema before processing — throws on malformed input.
|
|
84
|
-
*/
|
|
85
|
-
load(schema: ArcArchitectureSchema): ResourceDefinition<unknown>[];
|
|
86
|
-
private buildFieldRules;
|
|
87
|
-
private buildQueryParser;
|
|
88
|
-
private resolvePermissions;
|
|
89
|
-
private resolvePreset;
|
|
90
|
-
private resolveFinGrained;
|
|
91
|
-
}
|
|
92
|
-
//#endregion
|
|
93
|
-
export { ArcArchitectureSchema, ArcDynamicLoader, ArcFieldSchema, ArcFieldType, ArcPermissionMap, ArcPermissionPreset, ArcResourceSchema, DynamicLoaderContext };
|