actly 1.1.5 → 1.2.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 +232 -434
- package/dist/core/act.cjs +353 -35
- package/dist/core/act.d.ts.map +1 -1
- package/dist/core/act.js +355 -37
- package/dist/core/act.js.map +1 -1
- package/dist/core/executor.cjs +2 -0
- package/dist/core/executor.d.ts +6 -1
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +2 -0
- package/dist/core/executor.js.map +1 -1
- package/dist/core/health.cjs +34 -0
- package/dist/core/health.d.ts +18 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +28 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/shutdown.cjs +60 -0
- package/dist/core/shutdown.d.ts +8 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +56 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/tenant.cjs +71 -0
- package/dist/core/tenant.d.ts +29 -0
- package/dist/core/tenant.d.ts.map +1 -0
- package/dist/core/tenant.js +68 -0
- package/dist/core/tenant.js.map +1 -0
- package/dist/errors.cjs +182 -0
- package/dist/errors.d.ts +151 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +171 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +44 -4
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/observability.cjs +45 -0
- package/dist/observability.d.ts +145 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +43 -0
- package/dist/observability.js.map +1 -0
- package/dist/policies/bulkhead.cjs +97 -0
- package/dist/policies/bulkhead.d.ts +3 -0
- package/dist/policies/bulkhead.d.ts.map +1 -0
- package/dist/policies/bulkhead.js +95 -0
- package/dist/policies/bulkhead.js.map +1 -0
- package/dist/policies/cache.cjs +168 -31
- package/dist/policies/cache.d.ts +17 -4
- package/dist/policies/cache.d.ts.map +1 -1
- package/dist/policies/cache.js +168 -31
- package/dist/policies/cache.js.map +1 -1
- package/dist/policies/circuitBreaker.cjs +85 -0
- package/dist/policies/circuitBreaker.d.ts +3 -0
- package/dist/policies/circuitBreaker.d.ts.map +1 -0
- package/dist/policies/circuitBreaker.js +83 -0
- package/dist/policies/circuitBreaker.js.map +1 -0
- package/dist/policies/dedupe.cjs +74 -45
- package/dist/policies/dedupe.d.ts +10 -28
- package/dist/policies/dedupe.d.ts.map +1 -1
- package/dist/policies/dedupe.js +74 -45
- package/dist/policies/dedupe.js.map +1 -1
- package/dist/policies/rateLimit.cjs +35 -0
- package/dist/policies/rateLimit.d.ts +3 -0
- package/dist/policies/rateLimit.d.ts.map +1 -0
- package/dist/policies/rateLimit.js +33 -0
- package/dist/policies/rateLimit.js.map +1 -0
- package/dist/policies/retry.cjs +45 -8
- package/dist/policies/retry.d.ts.map +1 -1
- package/dist/policies/retry.js +45 -8
- package/dist/policies/retry.js.map +1 -1
- package/dist/policies/timeout.cjs +25 -53
- package/dist/policies/timeout.d.ts +2 -33
- package/dist/policies/timeout.d.ts.map +1 -1
- package/dist/policies/timeout.js +31 -48
- package/dist/policies/timeout.js.map +1 -1
- package/dist/state/store.cjs +3 -3
- package/dist/state/store.d.ts +3 -3
- package/dist/state/store.js +3 -3
- package/dist/stores/memory.cjs +126 -53
- package/dist/stores/memory.d.ts +35 -16
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +125 -53
- package/dist/stores/memory.js.map +1 -1
- package/dist/types/index.d.ts +176 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/abort.cjs +108 -38
- package/dist/utils/abort.d.ts +43 -19
- package/dist/utils/abort.d.ts.map +1 -1
- package/dist/utils/abort.js +108 -38
- package/dist/utils/abort.js.map +1 -1
- package/dist/utils/abortPool.cjs +26 -0
- package/dist/utils/abortPool.d.ts +4 -0
- package/dist/utils/abortPool.d.ts.map +1 -0
- package/dist/utils/abortPool.js +22 -0
- package/dist/utils/abortPool.js.map +1 -0
- package/dist/utils/backoff.cjs +12 -1
- package/dist/utils/backoff.d.ts.map +1 -1
- package/dist/utils/backoff.js +12 -1
- package/dist/utils/backoff.js.map +1 -1
- package/dist/utils/key.cjs +77 -0
- package/dist/utils/key.d.ts +29 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +75 -0
- package/dist/utils/key.js.map +1 -0
- package/dist/utils/limits.cjs +40 -0
- package/dist/utils/limits.d.ts +39 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +38 -0
- package/dist/utils/limits.js.map +1 -0
- package/dist/utils/sanitize.cjs +29 -0
- package/dist/utils/sanitize.d.ts +3 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +26 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/validate.cjs +90 -16
- package/dist/utils/validate.d.ts +11 -0
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +85 -16
- package/dist/utils/validate.js.map +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/core/health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAChE,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAOD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAErD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAE/E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,YAAY,CAU1E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
let globalInflight = 0;
|
|
2
|
+
let startTime = Date.now();
|
|
3
|
+
let globalLastError;
|
|
4
|
+
let globalLastSuccessAt;
|
|
5
|
+
export function registerInflight(_scope) {
|
|
6
|
+
globalInflight++;
|
|
7
|
+
}
|
|
8
|
+
export function unregisterInflight(_scope) {
|
|
9
|
+
globalInflight = Math.max(0, globalInflight - 1);
|
|
10
|
+
}
|
|
11
|
+
export function recordError(_scope, code, message) {
|
|
12
|
+
globalLastError = { code, message, timestamp: Date.now() };
|
|
13
|
+
}
|
|
14
|
+
export function recordSuccess(_scope) {
|
|
15
|
+
globalLastSuccessAt = Date.now();
|
|
16
|
+
}
|
|
17
|
+
export function createHealthCheck(store) {
|
|
18
|
+
return () => {
|
|
19
|
+
return {
|
|
20
|
+
storeSize: store.size(),
|
|
21
|
+
pendingInflight: globalInflight,
|
|
22
|
+
uptimeMs: Date.now() - startTime,
|
|
23
|
+
lastError: globalLastError,
|
|
24
|
+
lastSuccessAt: globalLastSuccessAt,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/core/health.ts"],"names":[],"mappings":"AAUA,IAAI,cAAc,GAAG,CAAC,CAAA;AACtB,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;AAC1B,IAAI,eAAiF,CAAA;AACrF,IAAI,mBAAuC,CAAA;AAE3C,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,cAAc,EAAE,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,GAAG,CAAC,CAAC,CAAA;AAClD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAY,EAAE,OAAe;IACvE,eAAe,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;AAC5D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAoB;IACpD,OAAO,GAAG,EAAE;QACV,OAAO;YACL,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE;YACvB,eAAe,EAAE,cAAc;YAC/B,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YAChC,SAAS,EAAE,eAAe;YAC1B,aAAa,EAAE,mBAAmB;SACnC,CAAA;IACH,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerDrainable = registerDrainable;
|
|
4
|
+
exports.unregisterDrainable = unregisterDrainable;
|
|
5
|
+
exports.drain = drain;
|
|
6
|
+
const drainStates = new Map();
|
|
7
|
+
function getState(scope) {
|
|
8
|
+
let s = drainStates.get(scope);
|
|
9
|
+
if (!s) {
|
|
10
|
+
s = { inflight: 0, resolvers: [] };
|
|
11
|
+
drainStates.set(scope, s);
|
|
12
|
+
}
|
|
13
|
+
return s;
|
|
14
|
+
}
|
|
15
|
+
function registerDrainable(scope = 'default') {
|
|
16
|
+
getState(scope).inflight++;
|
|
17
|
+
}
|
|
18
|
+
function unregisterDrainable(scope = 'default') {
|
|
19
|
+
const s = getState(scope);
|
|
20
|
+
s.inflight = Math.max(0, s.inflight - 1);
|
|
21
|
+
if (s.inflight === 0) {
|
|
22
|
+
for (const r of s.resolvers)
|
|
23
|
+
r();
|
|
24
|
+
s.resolvers = [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Wait for all in-flight act() calls in this scope to settle.
|
|
29
|
+
* Returns true if all settled within timeoutMs, false if timed out.
|
|
30
|
+
*/
|
|
31
|
+
async function drain(timeoutMs, scope = 'default') {
|
|
32
|
+
const s = getState(scope);
|
|
33
|
+
if (s.inflight === 0)
|
|
34
|
+
return true;
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
let resolved = false;
|
|
37
|
+
const resolver = () => {
|
|
38
|
+
if (resolved)
|
|
39
|
+
return;
|
|
40
|
+
resolved = true;
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
// Remove this resolver from the array
|
|
43
|
+
const idx = s.resolvers.indexOf(resolver);
|
|
44
|
+
if (idx >= 0)
|
|
45
|
+
s.resolvers.splice(idx, 1);
|
|
46
|
+
resolve(true);
|
|
47
|
+
};
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
if (resolved)
|
|
50
|
+
return;
|
|
51
|
+
resolved = true;
|
|
52
|
+
// Remove this resolver from the array on timeout
|
|
53
|
+
const idx = s.resolvers.indexOf(resolver);
|
|
54
|
+
if (idx >= 0)
|
|
55
|
+
s.resolvers.splice(idx, 1);
|
|
56
|
+
resolve(false);
|
|
57
|
+
}, timeoutMs);
|
|
58
|
+
s.resolvers.push(resolver);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function registerDrainable(scope?: string): void;
|
|
2
|
+
export declare function unregisterDrainable(scope?: string): void;
|
|
3
|
+
/**
|
|
4
|
+
* Wait for all in-flight act() calls in this scope to settle.
|
|
5
|
+
* Returns true if all settled within timeoutMs, false if timed out.
|
|
6
|
+
*/
|
|
7
|
+
export declare function drain(timeoutMs: number, scope?: string): Promise<boolean>;
|
|
8
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/core/shutdown.ts"],"names":[],"mappings":"AAaA,wBAAgB,iBAAiB,CAAC,KAAK,SAAY,GAAG,IAAI,CAEzD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,SAAY,GAAG,IAAI,CAO3D;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BlF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const drainStates = new Map();
|
|
2
|
+
function getState(scope) {
|
|
3
|
+
let s = drainStates.get(scope);
|
|
4
|
+
if (!s) {
|
|
5
|
+
s = { inflight: 0, resolvers: [] };
|
|
6
|
+
drainStates.set(scope, s);
|
|
7
|
+
}
|
|
8
|
+
return s;
|
|
9
|
+
}
|
|
10
|
+
export function registerDrainable(scope = 'default') {
|
|
11
|
+
getState(scope).inflight++;
|
|
12
|
+
}
|
|
13
|
+
export function unregisterDrainable(scope = 'default') {
|
|
14
|
+
const s = getState(scope);
|
|
15
|
+
s.inflight = Math.max(0, s.inflight - 1);
|
|
16
|
+
if (s.inflight === 0) {
|
|
17
|
+
for (const r of s.resolvers)
|
|
18
|
+
r();
|
|
19
|
+
s.resolvers = [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Wait for all in-flight act() calls in this scope to settle.
|
|
24
|
+
* Returns true if all settled within timeoutMs, false if timed out.
|
|
25
|
+
*/
|
|
26
|
+
export async function drain(timeoutMs, scope = 'default') {
|
|
27
|
+
const s = getState(scope);
|
|
28
|
+
if (s.inflight === 0)
|
|
29
|
+
return true;
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
let resolved = false;
|
|
32
|
+
const resolver = () => {
|
|
33
|
+
if (resolved)
|
|
34
|
+
return;
|
|
35
|
+
resolved = true;
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
// Remove this resolver from the array
|
|
38
|
+
const idx = s.resolvers.indexOf(resolver);
|
|
39
|
+
if (idx >= 0)
|
|
40
|
+
s.resolvers.splice(idx, 1);
|
|
41
|
+
resolve(true);
|
|
42
|
+
};
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
if (resolved)
|
|
45
|
+
return;
|
|
46
|
+
resolved = true;
|
|
47
|
+
// Remove this resolver from the array on timeout
|
|
48
|
+
const idx = s.resolvers.indexOf(resolver);
|
|
49
|
+
if (idx >= 0)
|
|
50
|
+
s.resolvers.splice(idx, 1);
|
|
51
|
+
resolve(false);
|
|
52
|
+
}, timeoutMs);
|
|
53
|
+
s.resolvers.push(resolver);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=shutdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../src/core/shutdown.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,IAAI,GAAG,EAA8D,CAAA;AAEzF,SAAS,QAAQ,CAAC,KAAa;IAC7B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAC9B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;QAClC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAK,GAAG,SAAS;IACjD,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAK,GAAG,SAAS;IACnD,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzB,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAA;IACxC,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS;YAAE,CAAC,EAAE,CAAA;QAChC,CAAC,CAAC,SAAS,GAAG,EAAE,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,SAAiB,EAAE,KAAK,GAAG,SAAS;IAC9D,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAEjC,OAAO,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;QACtC,IAAI,QAAQ,GAAG,KAAK,CAAA;QACpB,MAAM,QAAQ,GAAG,GAAG,EAAE;YACpB,IAAI,QAAQ;gBAAE,OAAM;YACpB,QAAQ,GAAG,IAAI,CAAA;YACf,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,sCAAsC;YACtC,MAAM,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACzC,IAAI,GAAG,IAAI,CAAC;gBAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;YACxC,OAAO,CAAC,IAAI,CAAC,CAAA;QACf,CAAC,CAAA;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,QAAQ;gBAAE,OAAM;YACpB,QAAQ,GAAG,IAAI,CAAA;YACf,iDAAiD;YACjD,MAAM,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACzC,IAAI,GAAG,IAAI,CAAC;gBAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;YACxC,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC,EAAE,SAAS,CAAC,CAAA;QAEb,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTenantStore = createTenantStore;
|
|
4
|
+
exports.createAsyncTenantStore = createAsyncTenantStore;
|
|
5
|
+
const memory_js_1 = require("../stores/memory.js");
|
|
6
|
+
const act_js_1 = require("./act.js");
|
|
7
|
+
const limits_js_1 = require("../utils/limits.js");
|
|
8
|
+
/**
|
|
9
|
+
* Manages per-tenant stores for multi-tenant isolation.
|
|
10
|
+
* Each tenant gets its own InMemoryStore — cache/dedupe entries
|
|
11
|
+
* cannot leak across tenants.
|
|
12
|
+
*/
|
|
13
|
+
function createTenantStore(options = {}) {
|
|
14
|
+
const maxSize = options.maxSize ?? limits_js_1.LIMITS.DEFAULT_STORE_MAX_SIZE;
|
|
15
|
+
const autoCleanup = options.autoCleanup ?? true;
|
|
16
|
+
const cleanupIntervalMs = options.cleanupIntervalMs ?? 60_000;
|
|
17
|
+
const tenants = new Map();
|
|
18
|
+
return {
|
|
19
|
+
get(tenantId) {
|
|
20
|
+
let entry = tenants.get(tenantId);
|
|
21
|
+
if (!entry) {
|
|
22
|
+
const store = new memory_js_1.InMemoryStore({ maxSize, autoCleanup, cleanupIntervalMs });
|
|
23
|
+
const scoped = (0, act_js_1.withStore)(store);
|
|
24
|
+
entry = { store, scoped };
|
|
25
|
+
tenants.set(tenantId, entry);
|
|
26
|
+
}
|
|
27
|
+
return entry.scoped;
|
|
28
|
+
},
|
|
29
|
+
evict(tenantId) {
|
|
30
|
+
const entry = tenants.get(tenantId);
|
|
31
|
+
if (entry) {
|
|
32
|
+
entry.store.destroy();
|
|
33
|
+
tenants.delete(tenantId);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
size() {
|
|
37
|
+
return tenants.size;
|
|
38
|
+
},
|
|
39
|
+
destroy() {
|
|
40
|
+
for (const [, entry] of tenants) {
|
|
41
|
+
entry.store.destroy();
|
|
42
|
+
}
|
|
43
|
+
tenants.clear();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Async tenant manager for async stores (Redis, DynamoDB, etc).
|
|
49
|
+
* Each tenant gets its own store instance with prefixed keys.
|
|
50
|
+
*/
|
|
51
|
+
function createAsyncTenantStore(storeFactory) {
|
|
52
|
+
const tenants = new Map();
|
|
53
|
+
return {
|
|
54
|
+
get(tenantId) {
|
|
55
|
+
let entry = tenants.get(tenantId);
|
|
56
|
+
if (!entry) {
|
|
57
|
+
const store = storeFactory(tenantId);
|
|
58
|
+
const scoped = (0, act_js_1.withStore)(store);
|
|
59
|
+
entry = { store, scoped };
|
|
60
|
+
tenants.set(tenantId, entry);
|
|
61
|
+
}
|
|
62
|
+
return entry.scoped;
|
|
63
|
+
},
|
|
64
|
+
evict(tenantId) {
|
|
65
|
+
tenants.delete(tenantId);
|
|
66
|
+
},
|
|
67
|
+
size() {
|
|
68
|
+
return tenants.size;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ScopedActSync, ScopedActAsync } from './act.js';
|
|
2
|
+
import type { AsyncStateStore } from '../stores/base.js';
|
|
3
|
+
export interface TenantStoreOptions {
|
|
4
|
+
maxSize?: number;
|
|
5
|
+
autoCleanup?: boolean;
|
|
6
|
+
cleanupIntervalMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TenantManager {
|
|
9
|
+
get(tenantId: string): ScopedActSync | ScopedActAsync;
|
|
10
|
+
evict(tenantId: string): void;
|
|
11
|
+
size(): number;
|
|
12
|
+
destroy(): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Manages per-tenant stores for multi-tenant isolation.
|
|
16
|
+
* Each tenant gets its own InMemoryStore — cache/dedupe entries
|
|
17
|
+
* cannot leak across tenants.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createTenantStore(options?: TenantStoreOptions): TenantManager;
|
|
20
|
+
/**
|
|
21
|
+
* Async tenant manager for async stores (Redis, DynamoDB, etc).
|
|
22
|
+
* Each tenant gets its own store instance with prefixed keys.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createAsyncTenantStore(storeFactory: (tenantId: string) => AsyncStateStore): {
|
|
25
|
+
get: (tenantId: string) => ScopedActAsync;
|
|
26
|
+
evict: (tenantId: string) => void;
|
|
27
|
+
size: () => number;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=tenant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/core/tenant.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE7D,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAGxE,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,CAAA;IACrD,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,IAAI,MAAM,CAAA;IACd,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,aAAa,CAsCjF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,eAAe,GAClD;IACD,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAA;IACzC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,IAAI,EAAE,MAAM,MAAM,CAAA;CACnB,CAuBA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { InMemoryStore } from '../stores/memory.js';
|
|
2
|
+
import { withStore } from './act.js';
|
|
3
|
+
import { LIMITS } from '../utils/limits.js';
|
|
4
|
+
/**
|
|
5
|
+
* Manages per-tenant stores for multi-tenant isolation.
|
|
6
|
+
* Each tenant gets its own InMemoryStore — cache/dedupe entries
|
|
7
|
+
* cannot leak across tenants.
|
|
8
|
+
*/
|
|
9
|
+
export function createTenantStore(options = {}) {
|
|
10
|
+
const maxSize = options.maxSize ?? LIMITS.DEFAULT_STORE_MAX_SIZE;
|
|
11
|
+
const autoCleanup = options.autoCleanup ?? true;
|
|
12
|
+
const cleanupIntervalMs = options.cleanupIntervalMs ?? 60_000;
|
|
13
|
+
const tenants = new Map();
|
|
14
|
+
return {
|
|
15
|
+
get(tenantId) {
|
|
16
|
+
let entry = tenants.get(tenantId);
|
|
17
|
+
if (!entry) {
|
|
18
|
+
const store = new InMemoryStore({ maxSize, autoCleanup, cleanupIntervalMs });
|
|
19
|
+
const scoped = withStore(store);
|
|
20
|
+
entry = { store, scoped };
|
|
21
|
+
tenants.set(tenantId, entry);
|
|
22
|
+
}
|
|
23
|
+
return entry.scoped;
|
|
24
|
+
},
|
|
25
|
+
evict(tenantId) {
|
|
26
|
+
const entry = tenants.get(tenantId);
|
|
27
|
+
if (entry) {
|
|
28
|
+
entry.store.destroy();
|
|
29
|
+
tenants.delete(tenantId);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
size() {
|
|
33
|
+
return tenants.size;
|
|
34
|
+
},
|
|
35
|
+
destroy() {
|
|
36
|
+
for (const [, entry] of tenants) {
|
|
37
|
+
entry.store.destroy();
|
|
38
|
+
}
|
|
39
|
+
tenants.clear();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Async tenant manager for async stores (Redis, DynamoDB, etc).
|
|
45
|
+
* Each tenant gets its own store instance with prefixed keys.
|
|
46
|
+
*/
|
|
47
|
+
export function createAsyncTenantStore(storeFactory) {
|
|
48
|
+
const tenants = new Map();
|
|
49
|
+
return {
|
|
50
|
+
get(tenantId) {
|
|
51
|
+
let entry = tenants.get(tenantId);
|
|
52
|
+
if (!entry) {
|
|
53
|
+
const store = storeFactory(tenantId);
|
|
54
|
+
const scoped = withStore(store);
|
|
55
|
+
entry = { store, scoped };
|
|
56
|
+
tenants.set(tenantId, entry);
|
|
57
|
+
}
|
|
58
|
+
return entry.scoped;
|
|
59
|
+
},
|
|
60
|
+
evict(tenantId) {
|
|
61
|
+
tenants.delete(tenantId);
|
|
62
|
+
},
|
|
63
|
+
size() {
|
|
64
|
+
return tenants.size;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=tenant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant.js","sourceRoot":"","sources":["../../src/core/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAIpC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAe3C;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAA8B,EAAE;IAChE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,sBAAsB,CAAA;IAChE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAA;IAC/C,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,MAAM,CAAA;IAE7D,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2D,CAAA;IAElF,OAAO;QACL,GAAG,CAAC,QAAgB;YAClB,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACjC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC,CAAA;gBAC5E,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAkB,CAAA;gBAChD,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;gBACzB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;YAC9B,CAAC;YACD,OAAO,KAAK,CAAC,MAAM,CAAA;QACrB,CAAC;QAED,KAAK,CAAC,QAAgB;YACpB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACnC,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;gBACrB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YAC1B,CAAC;QACH,CAAC;QAED,IAAI;YACF,OAAO,OAAO,CAAC,IAAI,CAAA;QACrB,CAAC;QAED,OAAO;YACL,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;gBAChC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;YACvB,CAAC;YACD,OAAO,CAAC,KAAK,EAAE,CAAA;QACjB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,YAAmD;IAMnD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8D,CAAA;IAErF,OAAO;QACL,GAAG,CAAC,QAAgB;YAClB,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACjC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;gBACpC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAmB,CAAA;gBACjD,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;gBACzB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;YAC9B,CAAC;YACD,OAAO,KAAK,CAAC,MAAM,CAAA;QACrB,CAAC;QAED,KAAK,CAAC,QAAgB;YACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;QAED,IAAI;YACF,OAAO,OAAO,CAAC,IAAI,CAAA;QACrB,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/errors.cjs
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Actly error taxonomy.
|
|
4
|
+
*
|
|
5
|
+
* Six classes (one abstract base + five concrete). Each carries a stable
|
|
6
|
+
* `code` field so consumers can switch on strings instead of brittle
|
|
7
|
+
* `instanceof` chains across realm boundaries (e.g. errors serialised
|
|
8
|
+
* over IPC).
|
|
9
|
+
*
|
|
10
|
+
* # Why these six?
|
|
11
|
+
*
|
|
12
|
+
* - `ActlyError` — abstract base for `instanceof ActlyError` checks
|
|
13
|
+
* - `ActlyAbortError` — caller / signal cancellation
|
|
14
|
+
* - `TimeoutError` — per-attempt deadline
|
|
15
|
+
* - `TotalTimeoutError` — operation-wide budget
|
|
16
|
+
* - `RetryExhaustedError`— last attempt's error wrapped with context
|
|
17
|
+
* - `ValidationError` — programmer error (invalid options)
|
|
18
|
+
*
|
|
19
|
+
* NO circuit/bulkhead/rate-limit errors — those policies don't exist in
|
|
20
|
+
* core. Adding orphan error classes violates the "every feature must map
|
|
21
|
+
* to a real runtime failure mode" rule.
|
|
22
|
+
*
|
|
23
|
+
* # `code` field
|
|
24
|
+
*
|
|
25
|
+
* Stable string identifier. Use this for switch statements and telemetry
|
|
26
|
+
* tags. The class name can change across versions; `code` won't.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.RateLimitError = exports.BulkheadOverflowError = exports.CircuitBreakerOpenError = exports.ValidationError = exports.RetryExhaustedError = exports.TotalTimeoutError = exports.TimeoutError = exports.ActlyAbortError = exports.ActlyError = void 0;
|
|
30
|
+
/** Base class for all actly errors. Enables `instanceof ActlyError` checks. */
|
|
31
|
+
class ActlyError extends Error {
|
|
32
|
+
/** The key associated with the failure, if applicable. */
|
|
33
|
+
key;
|
|
34
|
+
constructor(message, options) {
|
|
35
|
+
super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);
|
|
36
|
+
this.name = new.target.name;
|
|
37
|
+
if (options?.key !== undefined) {
|
|
38
|
+
Object.defineProperty(this, 'key', { value: options.key, enumerable: true });
|
|
39
|
+
}
|
|
40
|
+
// Restore prototype chain after Error inheritance (TS es2022 target
|
|
41
|
+
// may strip it). This ensures `instanceof` works correctly.
|
|
42
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.ActlyError = ActlyError;
|
|
46
|
+
/**
|
|
47
|
+
* Thrown when the caller's signal, per-attempt timeout, or total timeout
|
|
48
|
+
* aborts the operation.
|
|
49
|
+
*
|
|
50
|
+
* `cause` carries the original abort reason (e.g. user-supplied
|
|
51
|
+
* `controller.abort(new Error('user-cancelled'))` → cause.message is
|
|
52
|
+
* 'user-cancelled').
|
|
53
|
+
*
|
|
54
|
+
* Wraps the raw abort reason in a typed error so consumers can
|
|
55
|
+
* `instanceof`-check it consistently across all abort sources.
|
|
56
|
+
*/
|
|
57
|
+
class ActlyAbortError extends ActlyError {
|
|
58
|
+
code = 'ACTLY_ABORT';
|
|
59
|
+
constructor(options) {
|
|
60
|
+
const causeMsg = options?.cause instanceof Error ? options.cause.message : String(options?.cause ?? 'aborted');
|
|
61
|
+
super(`Actly operation aborted: ${causeMsg}`, options);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.ActlyAbortError = ActlyAbortError;
|
|
65
|
+
/**
|
|
66
|
+
* Thrown when a per-attempt `timeout` deadline fires.
|
|
67
|
+
*
|
|
68
|
+
* Carries the configured `ms` so callers can log/alert precisely:
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* if (!result.ok && result.error instanceof TimeoutError) {
|
|
72
|
+
* console.log(`attempt timed out after ${result.error.ms}ms`)
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
class TimeoutError extends ActlyError {
|
|
77
|
+
code = 'ACTLY_TIMEOUT';
|
|
78
|
+
ms;
|
|
79
|
+
constructor(ms, options) {
|
|
80
|
+
super(`ACT timed out after ${ms}ms`, options);
|
|
81
|
+
this.ms = ms;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.TimeoutError = TimeoutError;
|
|
85
|
+
/**
|
|
86
|
+
* Thrown when the operation-wide `totalTimeout` budget fires.
|
|
87
|
+
*
|
|
88
|
+
* Distinct from `TimeoutError` (per-attempt) so callers can `instanceof`-check
|
|
89
|
+
* which deadline fired.
|
|
90
|
+
*/
|
|
91
|
+
class TotalTimeoutError extends ActlyError {
|
|
92
|
+
code = 'ACTLY_TOTAL_TIMEOUT';
|
|
93
|
+
ms;
|
|
94
|
+
constructor(ms, options) {
|
|
95
|
+
super(`ACT total timeout exceeded after ${ms}ms`, options);
|
|
96
|
+
this.ms = ms;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.TotalTimeoutError = TotalTimeoutError;
|
|
100
|
+
/**
|
|
101
|
+
* Thrown when all retry attempts are exhausted. The `lastError` is the
|
|
102
|
+
* final attempt's error; `errors` is the array of all attempt errors
|
|
103
|
+
* (useful for debugging patterns across retries).
|
|
104
|
+
*
|
|
105
|
+
* Wraps retry exhaustion with context so consumers can distinguish "single
|
|
106
|
+
* fn error" from "retry exhausted after N attempts".
|
|
107
|
+
*
|
|
108
|
+
* # When is this thrown vs the raw error?
|
|
109
|
+
*
|
|
110
|
+
* `retryPolicy` throws `RetryExhaustedError` when:
|
|
111
|
+
* - `attempts > 1` AND
|
|
112
|
+
* - all attempts failed AND
|
|
113
|
+
* - the default `shouldRetry` (or user-supplied predicate) returned `true`
|
|
114
|
+
* for at least one failure
|
|
115
|
+
*
|
|
116
|
+
* If the user's `shouldRetry` returns `false` on the first attempt, the
|
|
117
|
+
* raw error is thrown (no retries happened — not "exhausted").
|
|
118
|
+
*/
|
|
119
|
+
class RetryExhaustedError extends ActlyError {
|
|
120
|
+
code = 'ACTLY_RETRY_EXHAUSTED';
|
|
121
|
+
attempts;
|
|
122
|
+
lastError;
|
|
123
|
+
errors;
|
|
124
|
+
constructor(options) {
|
|
125
|
+
const lastMsg = options.lastError instanceof Error ? options.lastError.message : String(options.lastError);
|
|
126
|
+
super(`ACT retry exhausted after ${options.attempts} attempts; last error: ${lastMsg}`, { key: options.key, cause: options.lastError });
|
|
127
|
+
this.attempts = options.attempts;
|
|
128
|
+
this.lastError = options.lastError;
|
|
129
|
+
this.errors = options.errors;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.RetryExhaustedError = RetryExhaustedError;
|
|
133
|
+
/**
|
|
134
|
+
* Thrown on invalid option shapes / keys / store contracts. Programmer
|
|
135
|
+
* errors — these surface synchronously, NOT as `ActFailure`, because the
|
|
136
|
+
* caller's code is broken.
|
|
137
|
+
*
|
|
138
|
+
* Wraps the underlying `TypeError` / `RangeError` so consumers catching
|
|
139
|
+
* `ActlyError` get a consistent type for all actly-thrown errors.
|
|
140
|
+
*/
|
|
141
|
+
class ValidationError extends ActlyError {
|
|
142
|
+
code = 'ACTLY_VALIDATION';
|
|
143
|
+
constructor(message, options) {
|
|
144
|
+
super(message);
|
|
145
|
+
if (options?.field !== undefined) {
|
|
146
|
+
Object.defineProperty(this, 'field', { value: options.field, enumerable: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
field;
|
|
150
|
+
}
|
|
151
|
+
exports.ValidationError = ValidationError;
|
|
152
|
+
// ─── Hardening error classes ─────────────────────────────────────────────────
|
|
153
|
+
/** Thrown when a circuit breaker is open and blocks the call. */
|
|
154
|
+
class CircuitBreakerOpenError extends ActlyError {
|
|
155
|
+
code = 'ACTLY_CIRCUIT_OPEN';
|
|
156
|
+
key;
|
|
157
|
+
constructor(key, ms) {
|
|
158
|
+
super(`Circuit breaker open for key "${key}" — retry after ${ms}ms`);
|
|
159
|
+
this.key = key;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.CircuitBreakerOpenError = CircuitBreakerOpenError;
|
|
163
|
+
/** Thrown when a bulkhead is full (maxConcurrent reached, queue timed out). */
|
|
164
|
+
class BulkheadOverflowError extends ActlyError {
|
|
165
|
+
code = 'ACTLY_BULKHEAD_FULL';
|
|
166
|
+
key;
|
|
167
|
+
constructor(key, maxConcurrent) {
|
|
168
|
+
super(`Bulkhead full for key "${key}" — maxConcurrent ${maxConcurrent} reached`);
|
|
169
|
+
this.key = key;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.BulkheadOverflowError = BulkheadOverflowError;
|
|
173
|
+
/** Thrown when a rate limit is exceeded. */
|
|
174
|
+
class RateLimitError extends ActlyError {
|
|
175
|
+
code = 'ACTLY_RATE_LIMIT';
|
|
176
|
+
key;
|
|
177
|
+
constructor(key, maxCalls, windowMs) {
|
|
178
|
+
super(`Rate limit exceeded for key "${key}" — ${maxCalls} calls per ${windowMs}ms`);
|
|
179
|
+
this.key = key;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.RateLimitError = RateLimitError;
|