@node-yalc/resilience 0.0.0-stage → 0.0.2

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.
@@ -0,0 +1 @@
1
+ export * from './resilience';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./resilience"), exports);
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../resilience/src/index.ts"],"names":[],"mappings":";;;AAAA,uDAA6B"}
@@ -0,0 +1,26 @@
1
+ export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
2
+ export declare class CircuitBreaker {
3
+ private failureThreshold;
4
+ private recoveryTimeMs;
5
+ private state;
6
+ private failureCount;
7
+ private lastFailureTime;
8
+ constructor(failureThreshold?: number, recoveryTimeMs?: number);
9
+ execute<T>(fn: () => Promise<T>, fallback?: () => T): Promise<T>;
10
+ private recordFailure;
11
+ reset(): void;
12
+ getState(): CircuitState;
13
+ }
14
+ export declare class RateLimiter {
15
+ private capacity;
16
+ private refillRatePerSec;
17
+ private tokens;
18
+ private lastRefillTimestamp;
19
+ constructor(capacity?: number, refillRatePerSec?: number);
20
+ allowRequest(tokensRequested?: number): boolean;
21
+ private refill;
22
+ }
23
+ export declare class Singleflight {
24
+ private inFlightCalls;
25
+ do<T>(key: string, fn: () => Promise<T>): Promise<T>;
26
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Singleflight = exports.RateLimiter = exports.CircuitBreaker = void 0;
4
+ class CircuitBreaker {
5
+ constructor(failureThreshold = 5, recoveryTimeMs = 10000) {
6
+ this.state = 'CLOSED';
7
+ this.failureCount = 0;
8
+ this.lastFailureTime = 0;
9
+ this.failureThreshold = failureThreshold;
10
+ this.recoveryTimeMs = recoveryTimeMs;
11
+ }
12
+ async execute(fn, fallback) {
13
+ const now = Date.now();
14
+ if (this.state === 'OPEN') {
15
+ if (now - this.lastFailureTime > this.recoveryTimeMs) {
16
+ this.state = 'HALF_OPEN';
17
+ }
18
+ else {
19
+ if (fallback)
20
+ return fallback();
21
+ throw new Error('CircuitBreaker is OPEN. Execution blocked.');
22
+ }
23
+ }
24
+ try {
25
+ const result = await fn();
26
+ if (this.state === 'HALF_OPEN') {
27
+ this.reset();
28
+ }
29
+ return result;
30
+ }
31
+ catch (err) {
32
+ this.recordFailure();
33
+ if (fallback)
34
+ return fallback();
35
+ throw err;
36
+ }
37
+ }
38
+ recordFailure() {
39
+ this.failureCount++;
40
+ this.lastFailureTime = Date.now();
41
+ if (this.failureCount >= this.failureThreshold) {
42
+ this.state = 'OPEN';
43
+ }
44
+ }
45
+ reset() {
46
+ this.state = 'CLOSED';
47
+ this.failureCount = 0;
48
+ }
49
+ getState() {
50
+ return this.state;
51
+ }
52
+ }
53
+ exports.CircuitBreaker = CircuitBreaker;
54
+ class RateLimiter {
55
+ constructor(capacity = 100, refillRatePerSec = 10) {
56
+ this.capacity = capacity;
57
+ this.refillRatePerSec = refillRatePerSec;
58
+ this.tokens = capacity;
59
+ this.lastRefillTimestamp = Date.now();
60
+ }
61
+ allowRequest(tokensRequested = 1) {
62
+ this.refill();
63
+ if (this.tokens >= tokensRequested) {
64
+ this.tokens -= tokensRequested;
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+ refill() {
70
+ const now = Date.now();
71
+ const elapsedSecs = (now - this.lastRefillTimestamp) / 1000;
72
+ this.tokens = Math.min(this.capacity, this.tokens + elapsedSecs * this.refillRatePerSec);
73
+ this.lastRefillTimestamp = now;
74
+ }
75
+ }
76
+ exports.RateLimiter = RateLimiter;
77
+ class Singleflight {
78
+ constructor() {
79
+ this.inFlightCalls = new Map();
80
+ }
81
+ async do(key, fn) {
82
+ if (this.inFlightCalls.has(key)) {
83
+ return this.inFlightCalls.get(key);
84
+ }
85
+ const promise = (async () => {
86
+ try {
87
+ return await fn();
88
+ }
89
+ finally {
90
+ this.inFlightCalls.delete(key);
91
+ }
92
+ })();
93
+ this.inFlightCalls.set(key, promise);
94
+ return promise;
95
+ }
96
+ }
97
+ exports.Singleflight = Singleflight;
98
+ //# sourceMappingURL=resilience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience.js","sourceRoot":"","sources":["../../../resilience/src/resilience.ts"],"names":[],"mappings":";;;AAEA,MAAa,cAAc;IAOzB,YAAY,mBAA2B,CAAC,EAAE,iBAAyB,KAAK;QAJhE,UAAK,GAAiB,QAAQ,CAAC;QAC/B,iBAAY,GAAW,CAAC,CAAC;QACzB,oBAAe,GAAW,CAAC,CAAC;QAGlC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;IAEM,KAAK,CAAC,OAAO,CAAI,EAAoB,EAAE,QAAkB;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,GAAG,GAAG,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;gBACrD,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;YAC3B,CAAC;iBAAM,CAAC;gBACN,IAAI,QAAQ;oBAAE,OAAO,QAAQ,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;gBAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,IAAI,QAAQ;gBAAE,OAAO,QAAQ,EAAE,CAAC;YAChC,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,aAAa;QACnB,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC/C,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;QACtB,CAAC;IACH,CAAC;IAEM,KAAK;QACV,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;IACxB,CAAC;IAEM,QAAQ;QACb,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF;AArDD,wCAqDC;AAED,MAAa,WAAW;IAMtB,YAAY,WAAmB,GAAG,EAAE,mBAA2B,EAAE;QAC/D,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;QACvB,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACxC,CAAC;IAEM,YAAY,CAAC,kBAA0B,CAAC;QAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,IAAI,CAAC,MAAM,IAAI,eAAe,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,IAAI,eAAe,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,MAAM;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAAC;QAC5D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACzF,IAAI,CAAC,mBAAmB,GAAG,GAAG,CAAC;IACjC,CAAC;CACF;AA5BD,kCA4BC;AAED,MAAa,YAAY;IAAzB;QACU,kBAAa,GAA8B,IAAI,GAAG,EAAE,CAAC;IAkB/D,CAAC;IAhBQ,KAAK,CAAC,EAAE,CAAI,GAAW,EAAE,EAAoB;QAClD,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAe,CAAC;QACnD,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE;YAC1B,IAAI,CAAC;gBACH,OAAO,MAAM,EAAE,EAAE,CAAC;YACpB,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;QAEL,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACrC,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AAnBD,oCAmBC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-yalc/resilience",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.2",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts"
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './resilience';
@@ -0,0 +1,107 @@
1
+ export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
2
+
3
+ export class CircuitBreaker {
4
+ private failureThreshold: number;
5
+ private recoveryTimeMs: number;
6
+ private state: CircuitState = 'CLOSED';
7
+ private failureCount: number = 0;
8
+ private lastFailureTime: number = 0;
9
+
10
+ constructor(failureThreshold: number = 5, recoveryTimeMs: number = 10000) {
11
+ this.failureThreshold = failureThreshold;
12
+ this.recoveryTimeMs = recoveryTimeMs;
13
+ }
14
+
15
+ public async execute<T>(fn: () => Promise<T>, fallback?: () => T): Promise<T> {
16
+ const now = Date.now();
17
+
18
+ if (this.state === 'OPEN') {
19
+ if (now - this.lastFailureTime > this.recoveryTimeMs) {
20
+ this.state = 'HALF_OPEN';
21
+ } else {
22
+ if (fallback) return fallback();
23
+ throw new Error('CircuitBreaker is OPEN. Execution blocked.');
24
+ }
25
+ }
26
+
27
+ try {
28
+ const result = await fn();
29
+ if (this.state === 'HALF_OPEN') {
30
+ this.reset();
31
+ }
32
+ return result;
33
+ } catch (err) {
34
+ this.recordFailure();
35
+ if (fallback) return fallback();
36
+ throw err;
37
+ }
38
+ }
39
+
40
+ private recordFailure(): void {
41
+ this.failureCount++;
42
+ this.lastFailureTime = Date.now();
43
+ if (this.failureCount >= this.failureThreshold) {
44
+ this.state = 'OPEN';
45
+ }
46
+ }
47
+
48
+ public reset(): void {
49
+ this.state = 'CLOSED';
50
+ this.failureCount = 0;
51
+ }
52
+
53
+ public getState(): CircuitState {
54
+ return this.state;
55
+ }
56
+ }
57
+
58
+ export class RateLimiter {
59
+ private capacity: number;
60
+ private refillRatePerSec: number;
61
+ private tokens: number;
62
+ private lastRefillTimestamp: number;
63
+
64
+ constructor(capacity: number = 100, refillRatePerSec: number = 10) {
65
+ this.capacity = capacity;
66
+ this.refillRatePerSec = refillRatePerSec;
67
+ this.tokens = capacity;
68
+ this.lastRefillTimestamp = Date.now();
69
+ }
70
+
71
+ public allowRequest(tokensRequested: number = 1): boolean {
72
+ this.refill();
73
+ if (this.tokens >= tokensRequested) {
74
+ this.tokens -= tokensRequested;
75
+ return true;
76
+ }
77
+ return false;
78
+ }
79
+
80
+ private refill(): void {
81
+ const now = Date.now();
82
+ const elapsedSecs = (now - this.lastRefillTimestamp) / 1000;
83
+ this.tokens = Math.min(this.capacity, this.tokens + elapsedSecs * this.refillRatePerSec);
84
+ this.lastRefillTimestamp = now;
85
+ }
86
+ }
87
+
88
+ export class Singleflight {
89
+ private inFlightCalls: Map<string, Promise<any>> = new Map();
90
+
91
+ public async do<T>(key: string, fn: () => Promise<T>): Promise<T> {
92
+ if (this.inFlightCalls.has(key)) {
93
+ return this.inFlightCalls.get(key) as Promise<T>;
94
+ }
95
+
96
+ const promise = (async () => {
97
+ try {
98
+ return await fn();
99
+ } finally {
100
+ this.inFlightCalls.delete(key);
101
+ }
102
+ })();
103
+
104
+ this.inFlightCalls.set(key, promise);
105
+ return promise;
106
+ }
107
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.