@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.
Files changed (44) hide show
  1. package/README.md +438 -186
  2. package/bin/dsai-tools.mjs +13 -13
  3. package/dist/cli/index.cjs +8192 -2757
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.d.cts +4 -0
  6. package/dist/cli/index.d.ts +4 -0
  7. package/dist/cli/index.js +8190 -2757
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/config/index.cjs +264 -63
  10. package/dist/config/index.cjs.map +1 -1
  11. package/dist/config/index.d.cts +537 -1759
  12. package/dist/config/index.d.ts +537 -1759
  13. package/dist/config/index.js +259 -63
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/icons/index.cjs +1 -1
  16. package/dist/icons/index.cjs.map +1 -1
  17. package/dist/icons/index.d.cts +1 -1
  18. package/dist/icons/index.d.ts +1 -1
  19. package/dist/icons/index.js +1 -1
  20. package/dist/icons/index.js.map +1 -1
  21. package/dist/index.cjs +8093 -3024
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +214 -5
  24. package/dist/index.d.ts +214 -5
  25. package/dist/index.js +8033 -3012
  26. package/dist/index.js.map +1 -1
  27. package/dist/tokens/index.cjs +4457 -737
  28. package/dist/tokens/index.cjs.map +1 -1
  29. package/dist/tokens/index.d.cts +1258 -17
  30. package/dist/tokens/index.d.ts +1258 -17
  31. package/dist/tokens/index.js +4368 -683
  32. package/dist/tokens/index.js.map +1 -1
  33. package/dist/{types-Idj08nad.d.cts → types-CtE9f0G0.d.cts} +293 -3
  34. package/dist/{types-Idj08nad.d.ts → types-CtE9f0G0.d.ts} +293 -3
  35. package/dist/utils/circuit-breaker.cjs +173 -0
  36. package/dist/utils/circuit-breaker.cjs.map +1 -0
  37. package/dist/utils/circuit-breaker.d.cts +123 -0
  38. package/dist/utils/circuit-breaker.d.ts +123 -0
  39. package/dist/utils/circuit-breaker.js +169 -0
  40. package/dist/utils/circuit-breaker.js.map +1 -0
  41. package/package.json +102 -97
  42. package/templates/.dsairc.json +37 -37
  43. package/templates/dsai-config.schema.json +618 -554
  44. 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 'Light'
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, CustomTransform as C, DsaiConfig as D, FormatArgs as F, GlobalConfig as G, IconsConfig as I, LoadConfigOptions as L, OutputFormat as O, Platform as P, ResolvedConfig as R, TokensConfig as T, ThemesConfig as a, ThemeSelectorPattern as b, ResolvedGlobalConfig as c, ResolvedTokensConfig as d, ResolvedIconsConfig as e, ResolvedThemesConfig as f, CustomFormat as g, CustomPreprocessor as h, CustomFilter as i, TokenData as j, TransformOptions as k, Dictionary as l, FileConfig as m, LoadConfigResult as n, LogLevel as o, IconFramework as p };
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":"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","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 };