@dsai-io/tools 0.0.1 → 1.1.0
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 +438 -186
- package/bin/dsai-tools.mjs +13 -13
- package/dist/cli/index.cjs +8192 -2757
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +4 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +8190 -2757
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.cjs +264 -63
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.d.cts +537 -1759
- package/dist/config/index.d.ts +537 -1759
- package/dist/config/index.js +259 -63
- package/dist/config/index.js.map +1 -1
- package/dist/icons/index.cjs +1 -1
- package/dist/icons/index.cjs.map +1 -1
- package/dist/icons/index.d.cts +1 -1
- package/dist/icons/index.d.ts +1 -1
- package/dist/icons/index.js +1 -1
- package/dist/icons/index.js.map +1 -1
- package/dist/index.cjs +8093 -3024
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -5
- package/dist/index.d.ts +214 -5
- package/dist/index.js +8033 -3012
- package/dist/index.js.map +1 -1
- package/dist/tokens/index.cjs +4457 -737
- package/dist/tokens/index.cjs.map +1 -1
- package/dist/tokens/index.d.cts +1258 -17
- package/dist/tokens/index.d.ts +1258 -17
- package/dist/tokens/index.js +4368 -683
- package/dist/tokens/index.js.map +1 -1
- package/dist/{types-Idj08nad.d.cts → types-CtE9f0G0.d.cts} +293 -3
- package/dist/{types-Idj08nad.d.ts → types-CtE9f0G0.d.ts} +293 -3
- package/dist/utils/circuit-breaker.cjs +173 -0
- package/dist/utils/circuit-breaker.cjs.map +1 -0
- package/dist/utils/circuit-breaker.d.cts +123 -0
- package/dist/utils/circuit-breaker.d.ts +123 -0
- package/dist/utils/circuit-breaker.js +169 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/package.json +102 -97
- package/templates/.dsairc.json +37 -37
- package/templates/dsai-config.schema.json +618 -554
- package/templates/dsai.config.mjs +281 -221
|
@@ -11,6 +11,10 @@ interface DsaiConfig {
|
|
|
11
11
|
tokens?: TokensConfig;
|
|
12
12
|
/** Icon generation configuration */
|
|
13
13
|
icons?: IconsConfig;
|
|
14
|
+
/** Path aliases for component installation */
|
|
15
|
+
aliases?: AliasesConfig;
|
|
16
|
+
/** Component distribution configuration */
|
|
17
|
+
components?: ComponentsConfig;
|
|
14
18
|
/** Global settings */
|
|
15
19
|
global?: GlobalConfig;
|
|
16
20
|
}
|
|
@@ -130,6 +134,10 @@ interface TokensConfig {
|
|
|
130
134
|
* @example '_variables.scss' or 'path/to/custom-base.scss'
|
|
131
135
|
*/
|
|
132
136
|
scssImportHeader?: string;
|
|
137
|
+
/**
|
|
138
|
+
* SCSS/CSS output style options
|
|
139
|
+
*/
|
|
140
|
+
scss?: ScssOutputConfig;
|
|
133
141
|
/** Theme mode settings */
|
|
134
142
|
themes?: ThemesConfig;
|
|
135
143
|
/** Custom Style Dictionary transforms */
|
|
@@ -194,11 +202,33 @@ interface TokensConfig {
|
|
|
194
202
|
* Controls which steps run and their paths
|
|
195
203
|
*/
|
|
196
204
|
pipeline?: TokensBuildPipeline;
|
|
205
|
+
/**
|
|
206
|
+
* Postprocess configuration for CSS file transformations
|
|
207
|
+
* Applied after SASS compilation
|
|
208
|
+
*/
|
|
209
|
+
postprocess?: PostprocessConfig;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Postprocess configuration for CSS file transformations
|
|
213
|
+
*/
|
|
214
|
+
interface PostprocessConfig {
|
|
215
|
+
/** Whether postprocessing is enabled */
|
|
216
|
+
enabled?: boolean;
|
|
217
|
+
/** Directory containing CSS files to process */
|
|
218
|
+
cssDir?: string;
|
|
219
|
+
/** File names to process */
|
|
220
|
+
files?: string[];
|
|
221
|
+
/** Replacement rules to apply */
|
|
222
|
+
replacements?: Array<{
|
|
223
|
+
description?: string;
|
|
224
|
+
from: string | RegExp;
|
|
225
|
+
to: string;
|
|
226
|
+
}>;
|
|
197
227
|
}
|
|
198
228
|
/**
|
|
199
229
|
* Build pipeline step names
|
|
200
230
|
*/
|
|
201
|
-
type BuildPipelineStep = 'validate' | 'transform' | 'style-dictionary' | 'sync' | 'sass-theme' | 'sass-theme-minified' | 'postprocess' | 'sass-utilities' | 'sass-utilities-minified' | 'bundle';
|
|
231
|
+
type BuildPipelineStep = 'validate' | 'snapshot' | 'preprocess' | 'transform' | 'style-dictionary' | 'multi-theme' | 'sync' | 'sass-theme' | 'sass-theme-minified' | 'postprocess' | 'sass-utilities' | 'sass-utilities-minified' | 'bundle';
|
|
202
232
|
/**
|
|
203
233
|
* Build pipeline paths configuration
|
|
204
234
|
*/
|
|
@@ -263,18 +293,181 @@ interface BuildSummary {
|
|
|
263
293
|
themes: string[];
|
|
264
294
|
};
|
|
265
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* CSS/SCSS output style options
|
|
298
|
+
* Controls whether to generate expanded, compressed, or both formats
|
|
299
|
+
*/
|
|
300
|
+
interface ScssOutputConfig {
|
|
301
|
+
/**
|
|
302
|
+
* Output format styles to generate
|
|
303
|
+
* - 'expanded': Human-readable, formatted CSS
|
|
304
|
+
* - 'compressed': Minified CSS for production
|
|
305
|
+
* @default ['expanded']
|
|
306
|
+
*/
|
|
307
|
+
outputStyles?: ('expanded' | 'compressed')[];
|
|
308
|
+
/**
|
|
309
|
+
* Generate source maps for debugging
|
|
310
|
+
* @default false
|
|
311
|
+
*/
|
|
312
|
+
generateSourceMaps?: boolean;
|
|
313
|
+
/**
|
|
314
|
+
* Suffix for minified/compressed output files
|
|
315
|
+
* @default '.min'
|
|
316
|
+
* @example '.min' produces 'theme.min.css' alongside 'theme.css'
|
|
317
|
+
*/
|
|
318
|
+
minifiedSuffix?: string;
|
|
319
|
+
/**
|
|
320
|
+
* Entry SCSS file for theme compilation
|
|
321
|
+
* Relative to config file location
|
|
322
|
+
* @example 'src/scss/dsai-theme-bs.scss'
|
|
323
|
+
*/
|
|
324
|
+
themeEntry?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Entry SCSS file for utilities compilation
|
|
327
|
+
* Relative to config file location
|
|
328
|
+
* @example 'src/scss/dsai-utilities.scss'
|
|
329
|
+
*/
|
|
330
|
+
utilitiesEntry?: string;
|
|
331
|
+
/**
|
|
332
|
+
* Output directory for compiled CSS files
|
|
333
|
+
* Relative to config file location
|
|
334
|
+
* @default 'src/generated'
|
|
335
|
+
*/
|
|
336
|
+
cssOutputDir?: string;
|
|
337
|
+
/**
|
|
338
|
+
* Additional Sass load paths for @use and @import resolution
|
|
339
|
+
* @default ['node_modules']
|
|
340
|
+
*/
|
|
341
|
+
loadPaths?: string[];
|
|
342
|
+
/**
|
|
343
|
+
* Target CSS framework for variable naming conventions
|
|
344
|
+
* Controls how token names are mapped to framework-specific names
|
|
345
|
+
* @default 'bootstrap'
|
|
346
|
+
*/
|
|
347
|
+
framework?: FrameworkTarget;
|
|
348
|
+
/**
|
|
349
|
+
* Custom name mappings for token → framework variable names
|
|
350
|
+
* Merged with framework defaults (custom mappings take precedence)
|
|
351
|
+
* @example { 'typography-text-base': 'font-size-base', 'typography-heading-h1': 'h1-font-size' }
|
|
352
|
+
*/
|
|
353
|
+
nameMapping?: Record<string, string>;
|
|
354
|
+
/**
|
|
355
|
+
* Output path for generated SCSS variables file
|
|
356
|
+
* Relative to config file location
|
|
357
|
+
* @default 'src/scss/_variables.scss'
|
|
358
|
+
*/
|
|
359
|
+
variablesOutput?: string;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Supported CSS framework targets
|
|
363
|
+
* Each framework has its own naming conventions for variables
|
|
364
|
+
*/
|
|
365
|
+
type FrameworkTarget = 'bootstrap' | 'shadcn' | 'tailwind' | 'mui' | 'custom';
|
|
366
|
+
/**
|
|
367
|
+
* Framework mapping configuration
|
|
368
|
+
* Defines how Figma token names map to framework-specific variable names
|
|
369
|
+
*/
|
|
370
|
+
interface FrameworkMappingConfig {
|
|
371
|
+
/**
|
|
372
|
+
* Framework identifier
|
|
373
|
+
*/
|
|
374
|
+
framework: FrameworkTarget;
|
|
375
|
+
/**
|
|
376
|
+
* Token name → framework variable name mappings
|
|
377
|
+
* Keys are Figma/DTCG token names, values are framework variable names
|
|
378
|
+
*/
|
|
379
|
+
mappings: Record<string, string>;
|
|
380
|
+
/**
|
|
381
|
+
* Pattern-based mappings using regex
|
|
382
|
+
* Applied after explicit mappings
|
|
383
|
+
* @example [{ pattern: /^typography-heading-(.+)$/, replacement: 'h$1-font-size' }]
|
|
384
|
+
*/
|
|
385
|
+
patterns?: FrameworkMappingPattern[];
|
|
386
|
+
/**
|
|
387
|
+
* Variable prefix for this framework
|
|
388
|
+
* @example '$' for SCSS, '--' for CSS custom properties
|
|
389
|
+
*/
|
|
390
|
+
variablePrefix?: string;
|
|
391
|
+
/**
|
|
392
|
+
* File header comment
|
|
393
|
+
*/
|
|
394
|
+
header?: string;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Pattern-based name mapping rule
|
|
398
|
+
*/
|
|
399
|
+
interface FrameworkMappingPattern {
|
|
400
|
+
/**
|
|
401
|
+
* Regex pattern to match token names
|
|
402
|
+
*/
|
|
403
|
+
pattern: RegExp | string;
|
|
404
|
+
/**
|
|
405
|
+
* Replacement string (supports $1, $2, etc. for capture groups)
|
|
406
|
+
*/
|
|
407
|
+
replacement: string;
|
|
408
|
+
/**
|
|
409
|
+
* Optional description for documentation
|
|
410
|
+
*/
|
|
411
|
+
description?: string;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Individual theme definition
|
|
415
|
+
* Specifies how a theme is discovered and built
|
|
416
|
+
*/
|
|
417
|
+
interface ThemeDefinition {
|
|
418
|
+
/**
|
|
419
|
+
* Whether this is the default theme (uses :root selector)
|
|
420
|
+
* Only one theme can be default
|
|
421
|
+
* @default false
|
|
422
|
+
*/
|
|
423
|
+
isDefault?: boolean;
|
|
424
|
+
/**
|
|
425
|
+
* File suffix pattern for this theme
|
|
426
|
+
* null means files without any theme suffix (default theme)
|
|
427
|
+
* e.g., '-dark' matches files like 'foundation-dark.json'
|
|
428
|
+
* @default null for default theme, '-{themeName}' for others
|
|
429
|
+
*/
|
|
430
|
+
suffix?: string | null;
|
|
431
|
+
/**
|
|
432
|
+
* CSS selector for this theme
|
|
433
|
+
* @example ':root' for default, '[data-dsai-theme="dark"]' for dark
|
|
434
|
+
*/
|
|
435
|
+
selector: string;
|
|
436
|
+
/**
|
|
437
|
+
* Optional media query for automatic switching
|
|
438
|
+
* @example '(prefers-color-scheme: dark)'
|
|
439
|
+
*/
|
|
440
|
+
mediaQuery?: string;
|
|
441
|
+
/**
|
|
442
|
+
* Optional data attribute (derived from selector if not specified)
|
|
443
|
+
*/
|
|
444
|
+
dataAttribute?: string;
|
|
445
|
+
/**
|
|
446
|
+
* Custom output file names per format
|
|
447
|
+
* If not specified, uses default naming pattern with theme suffix
|
|
448
|
+
*/
|
|
449
|
+
outputFiles?: Partial<Record<OutputFormat, string>>;
|
|
450
|
+
}
|
|
266
451
|
/**
|
|
267
452
|
* Theme configuration
|
|
268
453
|
*/
|
|
269
454
|
interface ThemesConfig {
|
|
455
|
+
/**
|
|
456
|
+
* Enable/disable theme processing
|
|
457
|
+
* @default true
|
|
458
|
+
*/
|
|
459
|
+
enabled?: boolean;
|
|
270
460
|
/**
|
|
271
461
|
* Auto-detect available modes from Figma export
|
|
462
|
+
* When true, scans for files with theme suffixes
|
|
463
|
+
* When false, only builds themes explicitly defined in definitions
|
|
272
464
|
* @default true
|
|
273
465
|
*/
|
|
274
466
|
autoDetect?: boolean;
|
|
275
467
|
/**
|
|
276
468
|
* Default theme mode (uses :root selector)
|
|
277
|
-
* @default '
|
|
469
|
+
* @default 'light'
|
|
470
|
+
* @deprecated Use definitions with isDefault: true instead
|
|
278
471
|
*/
|
|
279
472
|
default?: string;
|
|
280
473
|
/**
|
|
@@ -286,6 +479,12 @@ interface ThemesConfig {
|
|
|
286
479
|
* CSS selector patterns for themes
|
|
287
480
|
*/
|
|
288
481
|
selectorPattern?: ThemeSelectorPattern;
|
|
482
|
+
/**
|
|
483
|
+
* Explicit theme definitions
|
|
484
|
+
* Key is theme name, value is theme configuration
|
|
485
|
+
* When specified, provides explicit control over theme builds
|
|
486
|
+
*/
|
|
487
|
+
definitions?: Record<string, ThemeDefinition>;
|
|
289
488
|
}
|
|
290
489
|
/**
|
|
291
490
|
* Theme selector pattern configuration
|
|
@@ -443,14 +642,70 @@ interface IconsConfig {
|
|
|
443
642
|
*/
|
|
444
643
|
prefix?: string;
|
|
445
644
|
}
|
|
645
|
+
/**
|
|
646
|
+
* Path aliases for component installation.
|
|
647
|
+
* Controls where `dsai add` writes files in the consumer's project.
|
|
648
|
+
*/
|
|
649
|
+
interface AliasesConfig {
|
|
650
|
+
/** Import alias prefix used in tsconfig paths (e.g., "@/", "~/", "@dsai/") @default '@/' */
|
|
651
|
+
importAlias?: string;
|
|
652
|
+
/** Where UI components are installed @default 'src/components/ui' */
|
|
653
|
+
ui?: string;
|
|
654
|
+
/** Where shared hooks are installed @default 'src/hooks' */
|
|
655
|
+
hooks?: string;
|
|
656
|
+
/** Where utility functions are installed @default 'src/lib/utils' */
|
|
657
|
+
utils?: string;
|
|
658
|
+
/** Where higher-level composed components go @default 'src/components' */
|
|
659
|
+
components?: string;
|
|
660
|
+
/** Where lib files go @default 'src/lib' */
|
|
661
|
+
lib?: string;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Configuration for component distribution (shadcn-style).
|
|
665
|
+
*/
|
|
666
|
+
interface ComponentsConfig {
|
|
667
|
+
/** Enable component distribution features @default true */
|
|
668
|
+
enabled?: boolean;
|
|
669
|
+
/** Registry URL or local path @default 'https://registry.dsai.dev' */
|
|
670
|
+
registryUrl?: string;
|
|
671
|
+
/** Whether to use TypeScript (.tsx) or JavaScript (.jsx) @default true */
|
|
672
|
+
tsx?: boolean;
|
|
673
|
+
/** Overwrite existing files when adding components @default false */
|
|
674
|
+
overwrite?: boolean;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Resolved theme definition with all required fields
|
|
678
|
+
*/
|
|
679
|
+
interface ResolvedThemeDefinition {
|
|
680
|
+
/** Whether this is the default theme */
|
|
681
|
+
isDefault: boolean;
|
|
682
|
+
/** File suffix pattern (null for default theme) */
|
|
683
|
+
suffix: string | null;
|
|
684
|
+
/** CSS selector */
|
|
685
|
+
selector: string;
|
|
686
|
+
/** Optional media query */
|
|
687
|
+
mediaQuery?: string;
|
|
688
|
+
/** Optional data attribute */
|
|
689
|
+
dataAttribute?: string;
|
|
690
|
+
/** Output file names per format */
|
|
691
|
+
outputFiles: Record<OutputFormat, string>;
|
|
692
|
+
}
|
|
446
693
|
/**
|
|
447
694
|
* Resolved themes config with all defaults applied
|
|
448
695
|
*/
|
|
449
696
|
interface ResolvedThemesConfig {
|
|
697
|
+
/** Whether themes are enabled */
|
|
698
|
+
enabled: boolean;
|
|
699
|
+
/** Auto-detect themes from files */
|
|
450
700
|
autoDetect: boolean;
|
|
701
|
+
/** Default theme name (for backward compat) */
|
|
451
702
|
default: string;
|
|
703
|
+
/** Modes to ignore */
|
|
452
704
|
ignoreModes: string[];
|
|
705
|
+
/** Selector patterns */
|
|
453
706
|
selectorPattern: Required<ThemeSelectorPattern>;
|
|
707
|
+
/** Resolved theme definitions */
|
|
708
|
+
definitions: Record<string, ResolvedThemeDefinition>;
|
|
454
709
|
}
|
|
455
710
|
/**
|
|
456
711
|
* Resolved tokens config with all defaults applied
|
|
@@ -486,6 +741,19 @@ interface ResolvedTokensConfig {
|
|
|
486
741
|
watch: boolean;
|
|
487
742
|
watchDirectories: string[];
|
|
488
743
|
pipeline?: TokensBuildPipeline;
|
|
744
|
+
scss?: {
|
|
745
|
+
cssOutputDir?: string;
|
|
746
|
+
};
|
|
747
|
+
postprocess?: {
|
|
748
|
+
enabled?: boolean;
|
|
749
|
+
cssDir?: string;
|
|
750
|
+
files?: string[];
|
|
751
|
+
replacements?: Array<{
|
|
752
|
+
description?: string;
|
|
753
|
+
from: string | RegExp;
|
|
754
|
+
to: string;
|
|
755
|
+
}>;
|
|
756
|
+
};
|
|
489
757
|
}
|
|
490
758
|
/**
|
|
491
759
|
* Resolved icons config with all defaults applied
|
|
@@ -498,6 +766,26 @@ interface ResolvedIconsConfig {
|
|
|
498
766
|
optimize: boolean;
|
|
499
767
|
prefix: string;
|
|
500
768
|
}
|
|
769
|
+
/**
|
|
770
|
+
* Resolved aliases config with all defaults applied
|
|
771
|
+
*/
|
|
772
|
+
interface ResolvedAliasesConfig {
|
|
773
|
+
importAlias: string;
|
|
774
|
+
ui: string;
|
|
775
|
+
hooks: string;
|
|
776
|
+
utils: string;
|
|
777
|
+
components: string;
|
|
778
|
+
lib: string;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Resolved components config with all defaults applied
|
|
782
|
+
*/
|
|
783
|
+
interface ResolvedComponentsConfig {
|
|
784
|
+
enabled: boolean;
|
|
785
|
+
registryUrl: string;
|
|
786
|
+
tsx: boolean;
|
|
787
|
+
overwrite: boolean;
|
|
788
|
+
}
|
|
501
789
|
/**
|
|
502
790
|
* Resolved global config with all defaults applied
|
|
503
791
|
*/
|
|
@@ -512,6 +800,8 @@ interface ResolvedGlobalConfig {
|
|
|
512
800
|
interface ResolvedConfig {
|
|
513
801
|
tokens: ResolvedTokensConfig;
|
|
514
802
|
icons: ResolvedIconsConfig;
|
|
803
|
+
aliases: ResolvedAliasesConfig;
|
|
804
|
+
components: ResolvedComponentsConfig;
|
|
515
805
|
global: ResolvedGlobalConfig;
|
|
516
806
|
/** Absolute path to config file (if loaded from file) */
|
|
517
807
|
configPath?: string;
|
|
@@ -543,4 +833,4 @@ interface LoadConfigResult {
|
|
|
543
833
|
warnings: string[];
|
|
544
834
|
}
|
|
545
835
|
|
|
546
|
-
export type { BuildSummary as B,
|
|
836
|
+
export type { AliasesConfig as A, BuildSummary as B, ComponentsConfig as C, Dictionary as D, FileConfig as F, GlobalConfig as G, IconFramework as I, LoadConfigOptions as L, OutputFormat as O, Platform as P, ResolvedAliasesConfig as R, ScssOutputConfig as S, ThemeDefinition as T, ResolvedComponentsConfig as a, CustomFilter as b, CustomFormat as c, CustomPreprocessor as d, CustomTransform as e, DsaiConfig as f, FormatArgs as g, FrameworkMappingConfig as h, FrameworkMappingPattern as i, FrameworkTarget as j, IconsConfig as k, LoadConfigResult as l, LogLevel as m, ResolvedConfig as n, ResolvedGlobalConfig as o, ResolvedIconsConfig as p, ResolvedThemeDefinition as q, ResolvedThemesConfig as r, ResolvedTokensConfig as s, ThemeSelectorPattern as t, ThemesConfig as u, TokenData as v, TokensConfig as w, TransformOptions as x };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* @dsai-io/tools - DSAi Design System Build Tools */
|
|
4
|
+
|
|
5
|
+
// src/utils/circuit-breaker.ts
|
|
6
|
+
var CircuitState = /* @__PURE__ */ ((CircuitState2) => {
|
|
7
|
+
CircuitState2["CLOSED"] = "CLOSED";
|
|
8
|
+
CircuitState2["OPEN"] = "OPEN";
|
|
9
|
+
CircuitState2["HALF_OPEN"] = "HALF_OPEN";
|
|
10
|
+
return CircuitState2;
|
|
11
|
+
})(CircuitState || {});
|
|
12
|
+
var CircuitBreakerOpenError = class extends Error {
|
|
13
|
+
constructor(circuitName, resetAt) {
|
|
14
|
+
super(`Circuit breaker "${circuitName}" is open. Will retry at ${resetAt.toISOString()}`);
|
|
15
|
+
this.circuitName = circuitName;
|
|
16
|
+
this.resetAt = resetAt;
|
|
17
|
+
this.name = "CircuitBreakerOpenError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var CircuitBreaker = class {
|
|
21
|
+
state = "CLOSED" /* CLOSED */;
|
|
22
|
+
failures = 0;
|
|
23
|
+
successes = 0;
|
|
24
|
+
totalCalls = 0;
|
|
25
|
+
openedAt;
|
|
26
|
+
resetAt;
|
|
27
|
+
lastError;
|
|
28
|
+
failureThreshold;
|
|
29
|
+
cooldownMs;
|
|
30
|
+
timeout;
|
|
31
|
+
name;
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
this.failureThreshold = config.failureThreshold ?? 5;
|
|
34
|
+
this.cooldownMs = config.cooldownMs ?? 3e4;
|
|
35
|
+
this.timeout = config.timeout ?? 1e4;
|
|
36
|
+
this.name = config.name ?? "CircuitBreaker";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute a function with circuit breaker protection
|
|
40
|
+
*/
|
|
41
|
+
async execute(fn) {
|
|
42
|
+
this.totalCalls++;
|
|
43
|
+
if (this.state === "OPEN" /* OPEN */ && this.canAttemptReset()) {
|
|
44
|
+
this.state = "HALF_OPEN" /* HALF_OPEN */;
|
|
45
|
+
this.failures = 0;
|
|
46
|
+
}
|
|
47
|
+
if (this.state === "OPEN" /* OPEN */) {
|
|
48
|
+
if (!this.resetAt) {
|
|
49
|
+
throw new Error(`Circuit breaker "${this.name}" is open but has no reset time`);
|
|
50
|
+
}
|
|
51
|
+
throw new CircuitBreakerOpenError(this.name, this.resetAt);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const result = await this.executeWithTimeout(fn);
|
|
55
|
+
this.onSuccess();
|
|
56
|
+
return result;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
this.onFailure(error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Execute function with timeout
|
|
64
|
+
*/
|
|
65
|
+
async executeWithTimeout(fn) {
|
|
66
|
+
return Promise.race([
|
|
67
|
+
fn(),
|
|
68
|
+
new Promise((_, reject) => {
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
reject(new Error(`Circuit breaker timeout after ${this.timeout}ms`));
|
|
71
|
+
}, this.timeout);
|
|
72
|
+
})
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Handle successful execution
|
|
77
|
+
*/
|
|
78
|
+
onSuccess() {
|
|
79
|
+
this.successes++;
|
|
80
|
+
if (this.state === "HALF_OPEN" /* HALF_OPEN */) {
|
|
81
|
+
this.state = "CLOSED" /* CLOSED */;
|
|
82
|
+
this.failures = 0;
|
|
83
|
+
this.openedAt = void 0;
|
|
84
|
+
this.resetAt = void 0;
|
|
85
|
+
this.lastError = void 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Handle failed execution
|
|
90
|
+
*/
|
|
91
|
+
onFailure(error) {
|
|
92
|
+
this.failures++;
|
|
93
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
94
|
+
if (this.state === "HALF_OPEN" /* HALF_OPEN */) {
|
|
95
|
+
this.openCircuit();
|
|
96
|
+
} else if (this.failures >= this.failureThreshold) {
|
|
97
|
+
this.openCircuit();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Open the circuit
|
|
102
|
+
*/
|
|
103
|
+
openCircuit() {
|
|
104
|
+
this.state = "OPEN" /* OPEN */;
|
|
105
|
+
this.openedAt = /* @__PURE__ */ new Date();
|
|
106
|
+
this.resetAt = new Date(Date.now() + this.cooldownMs);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if circuit can attempt reset
|
|
110
|
+
*/
|
|
111
|
+
canAttemptReset() {
|
|
112
|
+
if (!this.resetAt) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return Date.now() >= this.resetAt.getTime();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get current circuit breaker state
|
|
119
|
+
*/
|
|
120
|
+
getState() {
|
|
121
|
+
return this.state;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get circuit breaker statistics
|
|
125
|
+
*/
|
|
126
|
+
getStats() {
|
|
127
|
+
return {
|
|
128
|
+
state: this.state,
|
|
129
|
+
failures: this.failures,
|
|
130
|
+
successes: this.successes,
|
|
131
|
+
totalCalls: this.totalCalls,
|
|
132
|
+
openedAt: this.openedAt,
|
|
133
|
+
resetAt: this.resetAt,
|
|
134
|
+
lastError: this.lastError
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Manually reset circuit breaker
|
|
139
|
+
*/
|
|
140
|
+
reset() {
|
|
141
|
+
this.state = "CLOSED" /* CLOSED */;
|
|
142
|
+
this.failures = 0;
|
|
143
|
+
this.successes = 0;
|
|
144
|
+
this.totalCalls = 0;
|
|
145
|
+
this.openedAt = void 0;
|
|
146
|
+
this.resetAt = void 0;
|
|
147
|
+
this.lastError = void 0;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Check if circuit is open
|
|
151
|
+
*/
|
|
152
|
+
isOpen() {
|
|
153
|
+
return this.state === "OPEN" /* OPEN */;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if circuit is closed
|
|
157
|
+
*/
|
|
158
|
+
isClosed() {
|
|
159
|
+
return this.state === "CLOSED" /* CLOSED */;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Check if circuit is half-open
|
|
163
|
+
*/
|
|
164
|
+
isHalfOpen() {
|
|
165
|
+
return this.state === "HALF_OPEN" /* HALF_OPEN */;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
exports.CircuitBreaker = CircuitBreaker;
|
|
170
|
+
exports.CircuitBreakerOpenError = CircuitBreakerOpenError;
|
|
171
|
+
exports.CircuitState = CircuitState;
|
|
172
|
+
//# sourceMappingURL=circuit-breaker.cjs.map
|
|
173
|
+
//# sourceMappingURL=circuit-breaker.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/circuit-breaker.ts"],"names":["CircuitState"],"mappings":";;;;;AAQO,IAAK,YAAA,qBAAAA,aAAAA,KAAL;AAEL,EAAAA,cAAA,QAAA,CAAA,GAAS,QAAA;AAET,EAAAA,cAAA,MAAA,CAAA,GAAO,MAAA;AAEP,EAAAA,cAAA,WAAA,CAAA,GAAY,WAAA;AANF,EAAA,OAAAA,aAAAA;AAAA,CAAA,EAAA,YAAA,IAAA,EAAA;AA8CL,IAAM,uBAAA,GAAN,cAAsC,KAAA,CAAM;AAAA,EACjD,WAAA,CACkB,aACA,OAAA,EAChB;AACA,IAAA,KAAA,CAAM,oBAAoB,WAAW,CAAA,yBAAA,EAA4B,OAAA,CAAQ,WAAA,EAAa,CAAA,CAAE,CAAA;AAHxE,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,yBAAA;AAAA,EACd;AACF;AAMO,IAAM,iBAAN,MAAqB;AAAA,EAClB,KAAA,GAAsB,QAAA;AAAA,EACtB,QAAA,GAAW,CAAA;AAAA,EACX,SAAA,GAAY,CAAA;AAAA,EACZ,UAAA,GAAa,CAAA;AAAA,EACb,QAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EAES,gBAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EAEjB,WAAA,CAAY,MAAA,GAA+B,EAAC,EAAG;AAC7C,IAAA,IAAA,CAAK,gBAAA,GAAmB,OAAO,gBAAA,IAAoB,CAAA;AACnD,IAAA,IAAA,CAAK,UAAA,GAAa,OAAO,UAAA,IAAc,GAAA;AACvC,IAAA,IAAA,CAAK,OAAA,GAAU,OAAO,OAAA,IAAW,GAAA;AACjC,IAAA,IAAA,CAAK,IAAA,GAAO,OAAO,IAAA,IAAQ,gBAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAW,EAAA,EAAkC;AACjD,IAAA,IAAA,CAAK,UAAA,EAAA;AAGL,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,eAAqB,IAAA,CAAK,iBAAgB,EAAG;AAC9D,MAAA,IAAA,CAAK,KAAA,GAAQ,WAAA;AACb,MAAA,IAAA,CAAK,QAAA,GAAW,CAAA;AAAA,IAClB;AAGA,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,aAAmB;AACpC,MAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,IAAA,CAAK,IAAI,CAAA,+BAAA,CAAiC,CAAA;AAAA,MAChF;AACA,MAAA,MAAM,IAAI,uBAAA,CAAwB,IAAA,CAAK,IAAA,EAAM,KAAK,OAAO,CAAA;AAAA,IAC3D;AAEA,IAAA,IAAI;AAEF,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,kBAAA,CAAmB,EAAE,CAAA;AAG/C,MAAA,IAAA,CAAK,SAAA,EAAU;AAEf,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AAEd,MAAA,IAAA,CAAK,UAAU,KAAK,CAAA;AACpB,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBAAsB,EAAA,EAAkC;AACpE,IAAA,OAAO,QAAQ,IAAA,CAAK;AAAA,MAClB,EAAA,EAAG;AAAA,MACH,IAAI,OAAA,CAAW,CAAC,CAAA,EAAG,MAAA,KAAW;AAC5B,QAAA,UAAA,CAAW,MAAM;AACf,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,IAAA,CAAK,OAAO,IAAI,CAAC,CAAA;AAAA,QACrE,CAAA,EAAG,KAAK,OAAO,CAAA;AAAA,MACjB,CAAC;AAAA,KACF,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAA,GAAkB;AACxB,IAAA,IAAA,CAAK,SAAA,EAAA;AAEL,IAAA,IAAI,IAAA,CAAK,UAAU,WAAA,kBAAwB;AAEzC,MAAA,IAAA,CAAK,KAAA,GAAQ,QAAA;AACb,MAAA,IAAA,CAAK,QAAA,GAAW,CAAA;AAChB,MAAA,IAAA,CAAK,QAAA,GAAW,MAAA;AAChB,MAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AACf,MAAA,IAAA,CAAK,SAAA,GAAY,MAAA;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,KAAA,EAAsB;AACtC,IAAA,IAAA,CAAK,QAAA,EAAA;AACL,IAAA,IAAA,CAAK,YAAY,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAEtE,IAAA,IAAI,IAAA,CAAK,UAAU,WAAA,kBAAwB;AAEzC,MAAA,IAAA,CAAK,WAAA,EAAY;AAAA,IACnB,CAAA,MAAA,IAAW,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,gBAAA,EAAkB;AAEjD,MAAA,IAAA,CAAK,WAAA,EAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAA,GAAoB;AAC1B,IAAA,IAAA,CAAK,KAAA,GAAQ,MAAA;AACb,IAAA,IAAA,CAAK,QAAA,uBAAe,IAAA,EAAK;AACzB,IAAA,IAAA,CAAK,UAAU,IAAI,IAAA,CAAK,KAAK,GAAA,EAAI,GAAI,KAAK,UAAU,CAAA;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAA,GAA2B;AACjC,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,IAAK,IAAA,CAAK,QAAQ,OAAA,EAAQ;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAgC;AAC9B,IAAA,OAAO;AAAA,MACL,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,WAAW,IAAA,CAAK;AAAA,KAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,QAAA;AACb,IAAA,IAAA,CAAK,QAAA,GAAW,CAAA;AAChB,IAAA,IAAA,CAAK,SAAA,GAAY,CAAA;AACjB,IAAA,IAAA,CAAK,UAAA,GAAa,CAAA;AAClB,IAAA,IAAA,CAAK,QAAA,GAAW,MAAA;AAChB,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AACf,IAAA,IAAA,CAAK,SAAA,GAAY,MAAA;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,GAAkB;AAChB,IAAA,OAAO,KAAK,KAAA,KAAU,MAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAoB;AAClB,IAAA,OAAO,KAAK,KAAA,KAAU,QAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAA,GAAsB;AACpB,IAAA,OAAO,KAAK,KAAA,KAAU,WAAA;AAAA,EACxB;AACF","file":"circuit-breaker.cjs","sourcesContent":["/**\r\n * @fileoverview Circuit Breaker pattern implementation\r\n * Prevents cascade failures by tracking error rates and temporarily blocking requests\r\n */\r\n\r\n/**\r\n * Circuit breaker states\r\n */\r\nexport enum CircuitState {\r\n /** Normal operation - requests are allowed */\r\n CLOSED = 'CLOSED',\r\n /** Failing - all requests are rejected immediately */\r\n OPEN = 'OPEN',\r\n /** Testing recovery - limited requests are allowed */\r\n HALF_OPEN = 'HALF_OPEN',\r\n}\r\n\r\n/**\r\n * Circuit breaker configuration\r\n */\r\nexport interface CircuitBreakerConfig {\r\n /** Number of failures before opening circuit */\r\n failureThreshold?: number;\r\n /** Time in ms before attempting to close circuit */\r\n cooldownMs?: number;\r\n /** Request timeout in ms */\r\n timeout?: number;\r\n /** Name for logging */\r\n name?: string;\r\n}\r\n\r\n/**\r\n * Circuit breaker statistics\r\n */\r\nexport interface CircuitBreakerStats {\r\n /** Current state */\r\n state: CircuitState;\r\n /** Total failures */\r\n failures: number;\r\n /** Total successes */\r\n successes: number;\r\n /** Total calls */\r\n totalCalls: number;\r\n /** Time circuit opened (if open) */\r\n openedAt?: Date;\r\n /** Time circuit will attempt to close */\r\n resetAt?: Date;\r\n /** Last error */\r\n lastError?: string;\r\n}\r\n\r\n/**\r\n * Circuit breaker error thrown when circuit is open\r\n */\r\nexport class CircuitBreakerOpenError extends Error {\r\n constructor(\r\n public readonly circuitName: string,\r\n public readonly resetAt: Date\r\n ) {\r\n super(`Circuit breaker \"${circuitName}\" is open. Will retry at ${resetAt.toISOString()}`);\r\n this.name = 'CircuitBreakerOpenError';\r\n }\r\n}\r\n\r\n/**\r\n * Circuit breaker implementation\r\n * Tracks failures and opens circuit to prevent cascade failures\r\n */\r\nexport class CircuitBreaker {\r\n private state: CircuitState = CircuitState.CLOSED;\r\n private failures = 0;\r\n private successes = 0;\r\n private totalCalls = 0;\r\n private openedAt?: Date;\r\n private resetAt?: Date;\r\n private lastError?: string;\r\n\r\n private readonly failureThreshold: number;\r\n private readonly cooldownMs: number;\r\n private readonly timeout: number;\r\n private readonly name: string;\r\n\r\n constructor(config: CircuitBreakerConfig = {}) {\r\n this.failureThreshold = config.failureThreshold ?? 5;\r\n this.cooldownMs = config.cooldownMs ?? 30000; // 30 seconds\r\n this.timeout = config.timeout ?? 10000; // 10 seconds\r\n this.name = config.name ?? 'CircuitBreaker';\r\n }\r\n\r\n /**\r\n * Execute a function with circuit breaker protection\r\n */\r\n async execute<T>(fn: () => Promise<T>): Promise<T> {\r\n this.totalCalls++;\r\n\r\n // Check if circuit should transition from OPEN to HALF_OPEN\r\n if (this.state === CircuitState.OPEN && this.canAttemptReset()) {\r\n this.state = CircuitState.HALF_OPEN;\r\n this.failures = 0; // Reset failure count for half-open state\r\n }\r\n\r\n // Reject immediately if circuit is open\r\n if (this.state === CircuitState.OPEN) {\r\n if (!this.resetAt) {\r\n throw new Error(`Circuit breaker \"${this.name}\" is open but has no reset time`);\r\n }\r\n throw new CircuitBreakerOpenError(this.name, this.resetAt);\r\n }\r\n\r\n try {\r\n // Execute with timeout\r\n const result = await this.executeWithTimeout(fn);\r\n\r\n // Record success\r\n this.onSuccess();\r\n\r\n return result;\r\n } catch (error) {\r\n // Record failure\r\n this.onFailure(error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Execute function with timeout\r\n */\r\n private async executeWithTimeout<T>(fn: () => Promise<T>): Promise<T> {\r\n return Promise.race([\r\n fn(),\r\n new Promise<T>((_, reject) => {\r\n setTimeout(() => {\r\n reject(new Error(`Circuit breaker timeout after ${this.timeout}ms`));\r\n }, this.timeout);\r\n }),\r\n ]);\r\n }\r\n\r\n /**\r\n * Handle successful execution\r\n */\r\n private onSuccess(): void {\r\n this.successes++;\r\n\r\n if (this.state === CircuitState.HALF_OPEN) {\r\n // Successful call in HALF_OPEN state closes the circuit\r\n this.state = CircuitState.CLOSED;\r\n this.failures = 0;\r\n this.openedAt = undefined;\r\n this.resetAt = undefined;\r\n this.lastError = undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Handle failed execution\r\n */\r\n private onFailure(error: unknown): void {\r\n this.failures++;\r\n this.lastError = error instanceof Error ? error.message : String(error);\r\n\r\n if (this.state === CircuitState.HALF_OPEN) {\r\n // Failure in HALF_OPEN state immediately reopens circuit\r\n this.openCircuit();\r\n } else if (this.failures >= this.failureThreshold) {\r\n // Threshold reached, open circuit\r\n this.openCircuit();\r\n }\r\n }\r\n\r\n /**\r\n * Open the circuit\r\n */\r\n private openCircuit(): void {\r\n this.state = CircuitState.OPEN;\r\n this.openedAt = new Date();\r\n this.resetAt = new Date(Date.now() + this.cooldownMs);\r\n }\r\n\r\n /**\r\n * Check if circuit can attempt reset\r\n */\r\n private canAttemptReset(): boolean {\r\n if (!this.resetAt) {\r\n return false;\r\n }\r\n return Date.now() >= this.resetAt.getTime();\r\n }\r\n\r\n /**\r\n * Get current circuit breaker state\r\n */\r\n getState(): CircuitState {\r\n return this.state;\r\n }\r\n\r\n /**\r\n * Get circuit breaker statistics\r\n */\r\n getStats(): CircuitBreakerStats {\r\n return {\r\n state: this.state,\r\n failures: this.failures,\r\n successes: this.successes,\r\n totalCalls: this.totalCalls,\r\n openedAt: this.openedAt,\r\n resetAt: this.resetAt,\r\n lastError: this.lastError,\r\n };\r\n }\r\n\r\n /**\r\n * Manually reset circuit breaker\r\n */\r\n reset(): void {\r\n this.state = CircuitState.CLOSED;\r\n this.failures = 0;\r\n this.successes = 0;\r\n this.totalCalls = 0;\r\n this.openedAt = undefined;\r\n this.resetAt = undefined;\r\n this.lastError = undefined;\r\n }\r\n\r\n /**\r\n * Check if circuit is open\r\n */\r\n isOpen(): boolean {\r\n return this.state === CircuitState.OPEN;\r\n }\r\n\r\n /**\r\n * Check if circuit is closed\r\n */\r\n isClosed(): boolean {\r\n return this.state === CircuitState.CLOSED;\r\n }\r\n\r\n /**\r\n * Check if circuit is half-open\r\n */\r\n isHalfOpen(): boolean {\r\n return this.state === CircuitState.HALF_OPEN;\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Circuit Breaker pattern implementation
|
|
3
|
+
* Prevents cascade failures by tracking error rates and temporarily blocking requests
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Circuit breaker states
|
|
7
|
+
*/
|
|
8
|
+
declare enum CircuitState {
|
|
9
|
+
/** Normal operation - requests are allowed */
|
|
10
|
+
CLOSED = "CLOSED",
|
|
11
|
+
/** Failing - all requests are rejected immediately */
|
|
12
|
+
OPEN = "OPEN",
|
|
13
|
+
/** Testing recovery - limited requests are allowed */
|
|
14
|
+
HALF_OPEN = "HALF_OPEN"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Circuit breaker configuration
|
|
18
|
+
*/
|
|
19
|
+
interface CircuitBreakerConfig {
|
|
20
|
+
/** Number of failures before opening circuit */
|
|
21
|
+
failureThreshold?: number;
|
|
22
|
+
/** Time in ms before attempting to close circuit */
|
|
23
|
+
cooldownMs?: number;
|
|
24
|
+
/** Request timeout in ms */
|
|
25
|
+
timeout?: number;
|
|
26
|
+
/** Name for logging */
|
|
27
|
+
name?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Circuit breaker statistics
|
|
31
|
+
*/
|
|
32
|
+
interface CircuitBreakerStats {
|
|
33
|
+
/** Current state */
|
|
34
|
+
state: CircuitState;
|
|
35
|
+
/** Total failures */
|
|
36
|
+
failures: number;
|
|
37
|
+
/** Total successes */
|
|
38
|
+
successes: number;
|
|
39
|
+
/** Total calls */
|
|
40
|
+
totalCalls: number;
|
|
41
|
+
/** Time circuit opened (if open) */
|
|
42
|
+
openedAt?: Date;
|
|
43
|
+
/** Time circuit will attempt to close */
|
|
44
|
+
resetAt?: Date;
|
|
45
|
+
/** Last error */
|
|
46
|
+
lastError?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Circuit breaker error thrown when circuit is open
|
|
50
|
+
*/
|
|
51
|
+
declare class CircuitBreakerOpenError extends Error {
|
|
52
|
+
readonly circuitName: string;
|
|
53
|
+
readonly resetAt: Date;
|
|
54
|
+
constructor(circuitName: string, resetAt: Date);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Circuit breaker implementation
|
|
58
|
+
* Tracks failures and opens circuit to prevent cascade failures
|
|
59
|
+
*/
|
|
60
|
+
declare class CircuitBreaker {
|
|
61
|
+
private state;
|
|
62
|
+
private failures;
|
|
63
|
+
private successes;
|
|
64
|
+
private totalCalls;
|
|
65
|
+
private openedAt?;
|
|
66
|
+
private resetAt?;
|
|
67
|
+
private lastError?;
|
|
68
|
+
private readonly failureThreshold;
|
|
69
|
+
private readonly cooldownMs;
|
|
70
|
+
private readonly timeout;
|
|
71
|
+
private readonly name;
|
|
72
|
+
constructor(config?: CircuitBreakerConfig);
|
|
73
|
+
/**
|
|
74
|
+
* Execute a function with circuit breaker protection
|
|
75
|
+
*/
|
|
76
|
+
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
77
|
+
/**
|
|
78
|
+
* Execute function with timeout
|
|
79
|
+
*/
|
|
80
|
+
private executeWithTimeout;
|
|
81
|
+
/**
|
|
82
|
+
* Handle successful execution
|
|
83
|
+
*/
|
|
84
|
+
private onSuccess;
|
|
85
|
+
/**
|
|
86
|
+
* Handle failed execution
|
|
87
|
+
*/
|
|
88
|
+
private onFailure;
|
|
89
|
+
/**
|
|
90
|
+
* Open the circuit
|
|
91
|
+
*/
|
|
92
|
+
private openCircuit;
|
|
93
|
+
/**
|
|
94
|
+
* Check if circuit can attempt reset
|
|
95
|
+
*/
|
|
96
|
+
private canAttemptReset;
|
|
97
|
+
/**
|
|
98
|
+
* Get current circuit breaker state
|
|
99
|
+
*/
|
|
100
|
+
getState(): CircuitState;
|
|
101
|
+
/**
|
|
102
|
+
* Get circuit breaker statistics
|
|
103
|
+
*/
|
|
104
|
+
getStats(): CircuitBreakerStats;
|
|
105
|
+
/**
|
|
106
|
+
* Manually reset circuit breaker
|
|
107
|
+
*/
|
|
108
|
+
reset(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Check if circuit is open
|
|
111
|
+
*/
|
|
112
|
+
isOpen(): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Check if circuit is closed
|
|
115
|
+
*/
|
|
116
|
+
isClosed(): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Check if circuit is half-open
|
|
119
|
+
*/
|
|
120
|
+
isHalfOpen(): boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { CircuitBreaker, type CircuitBreakerConfig, CircuitBreakerOpenError, type CircuitBreakerStats, CircuitState };
|