actly 1.2.0 → 1.3.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/CHANGELOG.md +234 -0
- package/MIGRATION.md +141 -0
- package/README.md +326 -218
- package/SECURITY.md +56 -0
- package/dist/core/act.cjs +406 -220
- package/dist/core/act.d.ts +6 -84
- package/dist/core/act.d.ts.map +1 -1
- package/dist/core/act.js +405 -222
- package/dist/core/executor.cjs +0 -27
- package/dist/core/executor.d.ts +0 -41
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +0 -28
- package/dist/core/health.cjs +146 -17
- package/dist/core/health.d.ts +20 -5
- package/dist/core/health.d.ts.map +1 -1
- package/dist/core/health.js +140 -18
- package/dist/core/shutdown.cjs +19 -8
- package/dist/core/shutdown.d.ts +1 -4
- package/dist/core/shutdown.d.ts.map +1 -1
- package/dist/core/shutdown.js +18 -9
- package/dist/core/tenant.cjs +61 -19
- package/dist/core/tenant.d.ts +5 -10
- package/dist/core/tenant.d.ts.map +1 -1
- package/dist/core/tenant.js +61 -20
- package/dist/errors.cjs +70 -103
- package/dist/errors.d.ts +33 -90
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +66 -103
- package/dist/index.cjs +25 -22
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -15
- package/dist/observability.cjs +0 -39
- package/dist/observability.d.ts +19 -78
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +0 -40
- package/dist/policies/bulkhead.cjs +44 -4
- package/dist/policies/bulkhead.d.ts +0 -0
- package/dist/policies/bulkhead.d.ts.map +1 -1
- package/dist/policies/bulkhead.js +44 -5
- package/dist/policies/cache.cjs +22 -142
- package/dist/policies/cache.d.ts +0 -46
- package/dist/policies/cache.d.ts.map +1 -1
- package/dist/policies/cache.js +22 -143
- package/dist/policies/circuitBreaker.cjs +125 -31
- package/dist/policies/circuitBreaker.d.ts +0 -0
- package/dist/policies/circuitBreaker.d.ts.map +1 -1
- package/dist/policies/circuitBreaker.js +125 -32
- package/dist/policies/dedupe.cjs +19 -66
- package/dist/policies/dedupe.d.ts +0 -23
- package/dist/policies/dedupe.d.ts.map +1 -1
- package/dist/policies/dedupe.js +19 -67
- package/dist/policies/noop.cjs +6 -0
- package/dist/policies/noop.d.ts +3 -0
- package/dist/policies/noop.d.ts.map +1 -0
- package/dist/policies/noop.js +3 -0
- package/dist/policies/rateLimit.cjs +15 -5
- package/dist/policies/rateLimit.d.ts +0 -0
- package/dist/policies/rateLimit.d.ts.map +1 -1
- package/dist/policies/rateLimit.js +15 -6
- package/dist/policies/retry.cjs +100 -65
- package/dist/policies/retry.d.ts +0 -27
- package/dist/policies/retry.d.ts.map +1 -1
- package/dist/policies/retry.js +100 -66
- package/dist/policies/timeout.cjs +32 -60
- package/dist/policies/timeout.d.ts +0 -13
- package/dist/policies/timeout.d.ts.map +1 -1
- package/dist/policies/timeout.js +32 -71
- package/dist/stores/base.cjs +0 -10
- package/dist/stores/base.d.ts +4 -67
- package/dist/stores/base.d.ts.map +1 -1
- package/dist/stores/base.js +0 -11
- package/dist/stores/memory.cjs +75 -88
- package/dist/stores/memory.d.ts +7 -74
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +75 -89
- package/dist/testing/index.cjs +56 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +52 -0
- package/dist/types/index.cjs +0 -1
- package/dist/types/index.d.ts +17 -307
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -2
- package/dist/utils/abort.cjs +14 -111
- package/dist/utils/abort.d.ts +3 -74
- package/dist/utils/abort.d.ts.map +1 -1
- package/dist/utils/abort.js +14 -112
- package/dist/utils/abortPool.cjs +1 -2
- package/dist/utils/abortPool.d.ts +0 -0
- package/dist/utils/abortPool.d.ts.map +1 -1
- package/dist/utils/abortPool.js +1 -3
- package/dist/utils/backoff.cjs +3 -21
- package/dist/utils/backoff.d.ts +0 -11
- package/dist/utils/backoff.d.ts.map +1 -1
- package/dist/utils/backoff.js +3 -22
- package/dist/utils/decorator.cjs +44 -0
- package/dist/utils/decorator.d.ts +3 -0
- package/dist/utils/decorator.d.ts.map +1 -0
- package/dist/utils/decorator.js +41 -0
- package/dist/utils/key.cjs +13 -45
- package/dist/utils/key.d.ts +0 -27
- package/dist/utils/key.d.ts.map +1 -1
- package/dist/utils/key.js +13 -46
- package/dist/utils/limits.cjs +9 -27
- package/dist/utils/limits.d.ts +9 -27
- package/dist/utils/limits.d.ts.map +1 -1
- package/dist/utils/limits.js +9 -28
- package/dist/utils/safeCall.cjs +28 -0
- package/dist/utils/safeCall.d.ts +2 -0
- package/dist/utils/safeCall.d.ts.map +1 -0
- package/dist/utils/safeCall.js +25 -0
- package/dist/utils/sanitize.cjs +22 -1
- package/dist/utils/sanitize.d.ts +0 -0
- package/dist/utils/sanitize.d.ts.map +1 -1
- package/dist/utils/sanitize.js +22 -2
- package/dist/utils/validate.cjs +81 -18
- package/dist/utils/validate.d.ts +6 -19
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +81 -19
- package/package.json +38 -4
- package/dist/core/act.js.map +0 -1
- package/dist/core/executor.js.map +0 -1
- package/dist/core/health.js.map +0 -1
- package/dist/core/shutdown.js.map +0 -1
- package/dist/core/tenant.js.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/observability.js.map +0 -1
- package/dist/policies/bulkhead.js.map +0 -1
- package/dist/policies/cache.js.map +0 -1
- package/dist/policies/circuitBreaker.js.map +0 -1
- package/dist/policies/dedupe.js.map +0 -1
- package/dist/policies/rateLimit.js.map +0 -1
- package/dist/policies/retry.js.map +0 -1
- package/dist/policies/timeout.js.map +0 -1
- package/dist/state/store.cjs +0 -14
- package/dist/state/store.d.ts +0 -11
- package/dist/state/store.d.ts.map +0 -1
- package/dist/state/store.js +0 -11
- package/dist/state/store.js.map +0 -1
- package/dist/stores/base.js.map +0 -1
- package/dist/stores/memory.js.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/abort.js.map +0 -1
- package/dist/utils/abortPool.js.map +0 -1
- package/dist/utils/backoff.js.map +0 -1
- package/dist/utils/key.js.map +0 -1
- package/dist/utils/limits.js.map +0 -1
- package/dist/utils/sanitize.js.map +0 -1
- package/dist/utils/validate.js.map +0 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePolicy = usePolicy;
|
|
4
|
+
function usePolicy(options) {
|
|
5
|
+
return function (_target, propertyKey, descriptor) {
|
|
6
|
+
if (!descriptor || !descriptor.value) {
|
|
7
|
+
throw new Error(`Actly: @usePolicy can only be applied to methods with a function value. ` +
|
|
8
|
+
`Got descriptor without value for property "${String(propertyKey)}".`);
|
|
9
|
+
}
|
|
10
|
+
const originalMethod = descriptor.value;
|
|
11
|
+
let actPromise;
|
|
12
|
+
const getAct = () => {
|
|
13
|
+
if (!actPromise)
|
|
14
|
+
actPromise = Promise.resolve().then(() => require('../core/act.js')).then(m => m.act);
|
|
15
|
+
return actPromise;
|
|
16
|
+
};
|
|
17
|
+
const wrappedMethod = async function (...args) {
|
|
18
|
+
const act = await getAct();
|
|
19
|
+
const ctorName = this?.constructor?.name ?? 'Anonymous';
|
|
20
|
+
const key = `${ctorName}.${String(propertyKey)}`;
|
|
21
|
+
const firstArg = args[0];
|
|
22
|
+
const isSignal = firstArg != null &&
|
|
23
|
+
typeof firstArg === 'object' &&
|
|
24
|
+
typeof firstArg.aborted === 'boolean' &&
|
|
25
|
+
typeof firstArg.addEventListener === 'function';
|
|
26
|
+
const signal = isSignal ? firstArg : undefined;
|
|
27
|
+
const fnArgs = signal ? args.slice(1) : args;
|
|
28
|
+
const result = await act(key, async (actSignal) => originalMethod.apply(this, [actSignal, ...fnArgs]), signal ? { ...options, signal } : options);
|
|
29
|
+
if (result.ok)
|
|
30
|
+
return result.value;
|
|
31
|
+
throw result.error;
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
Object.defineProperty(wrappedMethod, 'name', {
|
|
35
|
+
value: typeof propertyKey === 'symbol' ? propertyKey.description ?? 'wrapped' : propertyKey,
|
|
36
|
+
configurable: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
}
|
|
41
|
+
descriptor.value = wrappedMethod;
|
|
42
|
+
return descriptor;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator.d.ts","sourceRoot":"","sources":["../../src/utils/decorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,mBAAmB,CAAA;AA0B9D,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,eAAe,CAoE9D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function usePolicy(options) {
|
|
2
|
+
return function (_target, propertyKey, descriptor) {
|
|
3
|
+
if (!descriptor || !descriptor.value) {
|
|
4
|
+
throw new Error(`Actly: @usePolicy can only be applied to methods with a function value. ` +
|
|
5
|
+
`Got descriptor without value for property "${String(propertyKey)}".`);
|
|
6
|
+
}
|
|
7
|
+
const originalMethod = descriptor.value;
|
|
8
|
+
let actPromise;
|
|
9
|
+
const getAct = () => {
|
|
10
|
+
if (!actPromise)
|
|
11
|
+
actPromise = import('../core/act.js').then(m => m.act);
|
|
12
|
+
return actPromise;
|
|
13
|
+
};
|
|
14
|
+
const wrappedMethod = async function (...args) {
|
|
15
|
+
const act = await getAct();
|
|
16
|
+
const ctorName = this?.constructor?.name ?? 'Anonymous';
|
|
17
|
+
const key = `${ctorName}.${String(propertyKey)}`;
|
|
18
|
+
const firstArg = args[0];
|
|
19
|
+
const isSignal = firstArg != null &&
|
|
20
|
+
typeof firstArg === 'object' &&
|
|
21
|
+
typeof firstArg.aborted === 'boolean' &&
|
|
22
|
+
typeof firstArg.addEventListener === 'function';
|
|
23
|
+
const signal = isSignal ? firstArg : undefined;
|
|
24
|
+
const fnArgs = signal ? args.slice(1) : args;
|
|
25
|
+
const result = await act(key, async (actSignal) => originalMethod.apply(this, [actSignal, ...fnArgs]), signal ? { ...options, signal } : options);
|
|
26
|
+
if (result.ok)
|
|
27
|
+
return result.value;
|
|
28
|
+
throw result.error;
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
Object.defineProperty(wrappedMethod, 'name', {
|
|
32
|
+
value: typeof propertyKey === 'symbol' ? propertyKey.description ?? 'wrapped' : propertyKey,
|
|
33
|
+
configurable: true,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
}
|
|
38
|
+
descriptor.value = wrappedMethod;
|
|
39
|
+
return descriptor;
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/utils/key.cjs
CHANGED
|
@@ -1,50 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Key sanitisation — the first line of defence against:
|
|
4
|
-
*
|
|
5
|
-
* - **Prototype pollution**: `__proto__`, `constructor`, `prototype` as keys
|
|
6
|
-
* bypass namespace isolation in plain-object store adapters.
|
|
7
|
-
* - **Control-char injection**: `\x00`–`\x1f`, `\x7f` break some store
|
|
8
|
-
* backends (Redis Lua, HTTP header logs, JSON-LD contexts).
|
|
9
|
-
* - **CRLF injection**: `\r\n` in keys can break naive log/HTTP serializers.
|
|
10
|
-
* - **Memory exhaustion**: unbounded key length lets a caller store a 10MB
|
|
11
|
-
* string as a key, bloating the store and blocking iteration.
|
|
12
|
-
* - **Namespace collision**: user keys starting with `dedupe:`, `cache:`,
|
|
13
|
-
* or `__inflight:` would clobber internal slots.
|
|
14
|
-
*
|
|
15
|
-
* # Why a dedicated module?
|
|
16
|
-
*
|
|
17
|
-
* Key validation is security-sensitive. Centralising it here means every
|
|
18
|
-
* call site (`act()`, `invalidate()`, `withStore().invalidate()`) applies
|
|
19
|
-
* the same rules. Local inlining risks drift.
|
|
20
|
-
*/
|
|
21
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
3
|
exports.sanitizeKey = sanitizeKey;
|
|
23
4
|
const limits_js_1 = require("./limits.js");
|
|
24
|
-
const RESERVED_PREFIXES = ['dedupe:', 'cache:', '
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
5
|
+
const RESERVED_PREFIXES = ['dedupe:', 'cache:', 'inflight:', 'tenant:'];
|
|
6
|
+
const FORBIDDEN_LITERALS = new Set([
|
|
7
|
+
'__proto__',
|
|
8
|
+
'constructor',
|
|
9
|
+
'prototype',
|
|
10
|
+
'hasOwnProperty',
|
|
11
|
+
'toString',
|
|
12
|
+
'valueOf',
|
|
13
|
+
'isPrototypeOf',
|
|
14
|
+
'propertyIsEnumerable',
|
|
15
|
+
'toLocaleString',
|
|
16
|
+
]);
|
|
36
17
|
const UNSAFE_CHAR = /[\x00-\x08\x0b\x0c\x0d\x0e-\x1f\x7f]/;
|
|
37
|
-
/**
|
|
38
|
-
* Validate a user-supplied key. Throws synchronously on invalid input.
|
|
39
|
-
*
|
|
40
|
-
* Programmer errors throw — they must not be swallowed into an `ActFailure`
|
|
41
|
-
* because the caller's code is broken.
|
|
42
|
-
*
|
|
43
|
-
* @returns the same `key` (for chaining); never transforms it.
|
|
44
|
-
*/
|
|
45
18
|
function sanitizeKey(key) {
|
|
46
19
|
if (typeof key !== 'string') {
|
|
47
|
-
throw new TypeError(`Actly: key must be a string, got ${
|
|
20
|
+
throw new TypeError(`Actly: key must be a string, got ${key === null ? 'null' : typeof key}`);
|
|
48
21
|
}
|
|
49
22
|
if (key.length === 0) {
|
|
50
23
|
throw new RangeError("Actly: key must be non-empty. An empty key collapses every caller " +
|
|
@@ -70,8 +43,3 @@ function sanitizeKey(key) {
|
|
|
70
43
|
}
|
|
71
44
|
return key;
|
|
72
45
|
}
|
|
73
|
-
function typeName(v) {
|
|
74
|
-
if (v === null)
|
|
75
|
-
return 'null';
|
|
76
|
-
return typeof v;
|
|
77
|
-
}
|
package/dist/utils/key.d.ts
CHANGED
|
@@ -1,29 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Key sanitisation — the first line of defence against:
|
|
3
|
-
*
|
|
4
|
-
* - **Prototype pollution**: `__proto__`, `constructor`, `prototype` as keys
|
|
5
|
-
* bypass namespace isolation in plain-object store adapters.
|
|
6
|
-
* - **Control-char injection**: `\x00`–`\x1f`, `\x7f` break some store
|
|
7
|
-
* backends (Redis Lua, HTTP header logs, JSON-LD contexts).
|
|
8
|
-
* - **CRLF injection**: `\r\n` in keys can break naive log/HTTP serializers.
|
|
9
|
-
* - **Memory exhaustion**: unbounded key length lets a caller store a 10MB
|
|
10
|
-
* string as a key, bloating the store and blocking iteration.
|
|
11
|
-
* - **Namespace collision**: user keys starting with `dedupe:`, `cache:`,
|
|
12
|
-
* or `__inflight:` would clobber internal slots.
|
|
13
|
-
*
|
|
14
|
-
* # Why a dedicated module?
|
|
15
|
-
*
|
|
16
|
-
* Key validation is security-sensitive. Centralising it here means every
|
|
17
|
-
* call site (`act()`, `invalidate()`, `withStore().invalidate()`) applies
|
|
18
|
-
* the same rules. Local inlining risks drift.
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Validate a user-supplied key. Throws synchronously on invalid input.
|
|
22
|
-
*
|
|
23
|
-
* Programmer errors throw — they must not be swallowed into an `ActFailure`
|
|
24
|
-
* because the caller's code is broken.
|
|
25
|
-
*
|
|
26
|
-
* @returns the same `key` (for chaining); never transforms it.
|
|
27
|
-
*/
|
|
28
1
|
export declare function sanitizeKey(key: string): string;
|
|
29
2
|
//# sourceMappingURL=key.d.ts.map
|
package/dist/utils/key.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../../src/utils/key.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../../src/utils/key.ts"],"names":[],"mappings":"AAwCA,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAqC/C"}
|
package/dist/utils/key.js
CHANGED
|
@@ -1,47 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Key sanitisation — the first line of defence against:
|
|
3
|
-
*
|
|
4
|
-
* - **Prototype pollution**: `__proto__`, `constructor`, `prototype` as keys
|
|
5
|
-
* bypass namespace isolation in plain-object store adapters.
|
|
6
|
-
* - **Control-char injection**: `\x00`–`\x1f`, `\x7f` break some store
|
|
7
|
-
* backends (Redis Lua, HTTP header logs, JSON-LD contexts).
|
|
8
|
-
* - **CRLF injection**: `\r\n` in keys can break naive log/HTTP serializers.
|
|
9
|
-
* - **Memory exhaustion**: unbounded key length lets a caller store a 10MB
|
|
10
|
-
* string as a key, bloating the store and blocking iteration.
|
|
11
|
-
* - **Namespace collision**: user keys starting with `dedupe:`, `cache:`,
|
|
12
|
-
* or `__inflight:` would clobber internal slots.
|
|
13
|
-
*
|
|
14
|
-
* # Why a dedicated module?
|
|
15
|
-
*
|
|
16
|
-
* Key validation is security-sensitive. Centralising it here means every
|
|
17
|
-
* call site (`act()`, `invalidate()`, `withStore().invalidate()`) applies
|
|
18
|
-
* the same rules. Local inlining risks drift.
|
|
19
|
-
*/
|
|
20
1
|
import { LIMITS } from './limits.js';
|
|
21
|
-
const RESERVED_PREFIXES = ['dedupe:', 'cache:', '
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
2
|
+
const RESERVED_PREFIXES = ['dedupe:', 'cache:', 'inflight:', 'tenant:'];
|
|
3
|
+
const FORBIDDEN_LITERALS = new Set([
|
|
4
|
+
'__proto__',
|
|
5
|
+
'constructor',
|
|
6
|
+
'prototype',
|
|
7
|
+
'hasOwnProperty',
|
|
8
|
+
'toString',
|
|
9
|
+
'valueOf',
|
|
10
|
+
'isPrototypeOf',
|
|
11
|
+
'propertyIsEnumerable',
|
|
12
|
+
'toLocaleString',
|
|
13
|
+
]);
|
|
33
14
|
const UNSAFE_CHAR = /[\x00-\x08\x0b\x0c\x0d\x0e-\x1f\x7f]/;
|
|
34
|
-
/**
|
|
35
|
-
* Validate a user-supplied key. Throws synchronously on invalid input.
|
|
36
|
-
*
|
|
37
|
-
* Programmer errors throw — they must not be swallowed into an `ActFailure`
|
|
38
|
-
* because the caller's code is broken.
|
|
39
|
-
*
|
|
40
|
-
* @returns the same `key` (for chaining); never transforms it.
|
|
41
|
-
*/
|
|
42
15
|
export function sanitizeKey(key) {
|
|
43
16
|
if (typeof key !== 'string') {
|
|
44
|
-
throw new TypeError(`Actly: key must be a string, got ${
|
|
17
|
+
throw new TypeError(`Actly: key must be a string, got ${key === null ? 'null' : typeof key}`);
|
|
45
18
|
}
|
|
46
19
|
if (key.length === 0) {
|
|
47
20
|
throw new RangeError("Actly: key must be non-empty. An empty key collapses every caller " +
|
|
@@ -67,9 +40,3 @@ export function sanitizeKey(key) {
|
|
|
67
40
|
}
|
|
68
41
|
return key;
|
|
69
42
|
}
|
|
70
|
-
function typeName(v) {
|
|
71
|
-
if (v === null)
|
|
72
|
-
return 'null';
|
|
73
|
-
return typeof v;
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=key.js.map
|
package/dist/utils/limits.cjs
CHANGED
|
@@ -1,40 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Centralised numeric limits for all actly inputs.
|
|
4
|
-
*
|
|
5
|
-
* # Why central?
|
|
6
|
-
*
|
|
7
|
-
* Every numeric input that flows into actly (retry.attempts, timeout.ms,
|
|
8
|
-
* cache.ttl, etc.) must be bounded to prevent:
|
|
9
|
-
* - memory exhaustion (e.g. `cache: { ttl: 1e15 }` → 31 700-year TTL)
|
|
10
|
-
* - CPU exhaustion (e.g. `retry: { attempts: 1e9 }`)
|
|
11
|
-
* - timer overflow (e.g. `setTimeout(fn, 1e16)` wraps to 1ms on some engines)
|
|
12
|
-
*
|
|
13
|
-
* The values are deliberately generous: they prevent pathological abuse,
|
|
14
|
-
* not legitimate use. If you need to exceed one of these limits, you almost
|
|
15
|
-
* certainly have a bug.
|
|
16
|
-
*
|
|
17
|
-
* These limits are constants — they are not runtime-configurable. If you
|
|
18
|
-
* need different limits, fork the source or wrap `act()` with your own
|
|
19
|
-
* validation layer.
|
|
20
|
-
*/
|
|
21
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
3
|
exports.LIMITS = void 0;
|
|
23
4
|
exports.LIMITS = Object.freeze({
|
|
24
|
-
/** Maximum key length in characters. Prevents multi-MB keys bloating stores. */
|
|
25
5
|
MAX_KEY_LENGTH: 1024,
|
|
26
|
-
/** Hard ceiling on `retry.attempts`. 100 attempts ≈ 100 fn invocations. */
|
|
27
6
|
MAX_RETRY_ATTEMPTS: 100,
|
|
28
|
-
/** Maximum per-attempt / total timeout in ms. ~27 hours. */
|
|
29
7
|
MAX_TIMEOUT_MS: 100_000_000,
|
|
30
|
-
/** Maximum cache TTL in ms. ~24 hours. */
|
|
31
8
|
MAX_CACHE_TTL: 86_400_000,
|
|
32
|
-
/** Maximum delay between retries in ms. ~5 minutes. */
|
|
33
9
|
MAX_RETRY_DELAY_MS: 300_000,
|
|
34
|
-
/** Maximum dedupe inflightTtl in ms. ~24 hours. */
|
|
35
10
|
MAX_INFLIGHT_TTL: 86_400_000,
|
|
36
|
-
|
|
11
|
+
DEFAULT_INFLIGHT_TTL: 5 * 60_000,
|
|
37
12
|
DEFAULT_STORE_MAX_SIZE: 10_000,
|
|
38
|
-
/** Background sweep interval for the default store. */
|
|
39
13
|
DEFAULT_STORE_CLEANUP_INTERVAL_MS: 60_000,
|
|
14
|
+
MAX_GLOBAL_INFLIGHT: 100_000,
|
|
15
|
+
MAX_CIRCUIT_BREAKER_WINDOW: 1_000_000,
|
|
16
|
+
MAX_BULKHEAD_CONCURRENCY: 1_000_000,
|
|
17
|
+
MAX_BULKHEAD_QUEUE: 1_000_000,
|
|
18
|
+
MAX_RATE_LIMIT_CALLS: 1_000_000,
|
|
19
|
+
MAX_SANITIZED_ERROR_LENGTH: 8_192,
|
|
20
|
+
MAX_HEDGE_DELAY_MS: 300_000,
|
|
21
|
+
MAX_TENANTS: 10_000,
|
|
40
22
|
});
|
package/dist/utils/limits.d.ts
CHANGED
|
@@ -1,39 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralised numeric limits for all actly inputs.
|
|
3
|
-
*
|
|
4
|
-
* # Why central?
|
|
5
|
-
*
|
|
6
|
-
* Every numeric input that flows into actly (retry.attempts, timeout.ms,
|
|
7
|
-
* cache.ttl, etc.) must be bounded to prevent:
|
|
8
|
-
* - memory exhaustion (e.g. `cache: { ttl: 1e15 }` → 31 700-year TTL)
|
|
9
|
-
* - CPU exhaustion (e.g. `retry: { attempts: 1e9 }`)
|
|
10
|
-
* - timer overflow (e.g. `setTimeout(fn, 1e16)` wraps to 1ms on some engines)
|
|
11
|
-
*
|
|
12
|
-
* The values are deliberately generous: they prevent pathological abuse,
|
|
13
|
-
* not legitimate use. If you need to exceed one of these limits, you almost
|
|
14
|
-
* certainly have a bug.
|
|
15
|
-
*
|
|
16
|
-
* These limits are constants — they are not runtime-configurable. If you
|
|
17
|
-
* need different limits, fork the source or wrap `act()` with your own
|
|
18
|
-
* validation layer.
|
|
19
|
-
*/
|
|
20
1
|
export declare const LIMITS: Readonly<{
|
|
21
|
-
/** Maximum key length in characters. Prevents multi-MB keys bloating stores. */
|
|
22
2
|
MAX_KEY_LENGTH: 1024;
|
|
23
|
-
/** Hard ceiling on `retry.attempts`. 100 attempts ≈ 100 fn invocations. */
|
|
24
3
|
MAX_RETRY_ATTEMPTS: 100;
|
|
25
|
-
/** Maximum per-attempt / total timeout in ms. ~27 hours. */
|
|
26
4
|
MAX_TIMEOUT_MS: 100000000;
|
|
27
|
-
/** Maximum cache TTL in ms. ~24 hours. */
|
|
28
5
|
MAX_CACHE_TTL: 86400000;
|
|
29
|
-
/** Maximum delay between retries in ms. ~5 minutes. */
|
|
30
6
|
MAX_RETRY_DELAY_MS: 300000;
|
|
31
|
-
/** Maximum dedupe inflightTtl in ms. ~24 hours. */
|
|
32
7
|
MAX_INFLIGHT_TTL: 86400000;
|
|
33
|
-
|
|
8
|
+
DEFAULT_INFLIGHT_TTL: number;
|
|
34
9
|
DEFAULT_STORE_MAX_SIZE: 10000;
|
|
35
|
-
/** Background sweep interval for the default store. */
|
|
36
10
|
DEFAULT_STORE_CLEANUP_INTERVAL_MS: 60000;
|
|
11
|
+
MAX_GLOBAL_INFLIGHT: 100000;
|
|
12
|
+
MAX_CIRCUIT_BREAKER_WINDOW: 1000000;
|
|
13
|
+
MAX_BULKHEAD_CONCURRENCY: 1000000;
|
|
14
|
+
MAX_BULKHEAD_QUEUE: 1000000;
|
|
15
|
+
MAX_RATE_LIMIT_CALLS: 1000000;
|
|
16
|
+
MAX_SANITIZED_ERROR_LENGTH: 8192;
|
|
17
|
+
MAX_HEDGE_DELAY_MS: 300000;
|
|
18
|
+
MAX_TENANTS: 10000;
|
|
37
19
|
}>;
|
|
38
20
|
export type Limits = typeof LIMITS;
|
|
39
21
|
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/utils/limits.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/utils/limits.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;EA8EjB,CAAA;AAEF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAA"}
|
package/dist/utils/limits.js
CHANGED
|
@@ -1,38 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralised numeric limits for all actly inputs.
|
|
3
|
-
*
|
|
4
|
-
* # Why central?
|
|
5
|
-
*
|
|
6
|
-
* Every numeric input that flows into actly (retry.attempts, timeout.ms,
|
|
7
|
-
* cache.ttl, etc.) must be bounded to prevent:
|
|
8
|
-
* - memory exhaustion (e.g. `cache: { ttl: 1e15 }` → 31 700-year TTL)
|
|
9
|
-
* - CPU exhaustion (e.g. `retry: { attempts: 1e9 }`)
|
|
10
|
-
* - timer overflow (e.g. `setTimeout(fn, 1e16)` wraps to 1ms on some engines)
|
|
11
|
-
*
|
|
12
|
-
* The values are deliberately generous: they prevent pathological abuse,
|
|
13
|
-
* not legitimate use. If you need to exceed one of these limits, you almost
|
|
14
|
-
* certainly have a bug.
|
|
15
|
-
*
|
|
16
|
-
* These limits are constants — they are not runtime-configurable. If you
|
|
17
|
-
* need different limits, fork the source or wrap `act()` with your own
|
|
18
|
-
* validation layer.
|
|
19
|
-
*/
|
|
20
1
|
export const LIMITS = Object.freeze({
|
|
21
|
-
/** Maximum key length in characters. Prevents multi-MB keys bloating stores. */
|
|
22
2
|
MAX_KEY_LENGTH: 1024,
|
|
23
|
-
/** Hard ceiling on `retry.attempts`. 100 attempts ≈ 100 fn invocations. */
|
|
24
3
|
MAX_RETRY_ATTEMPTS: 100,
|
|
25
|
-
/** Maximum per-attempt / total timeout in ms. ~27 hours. */
|
|
26
4
|
MAX_TIMEOUT_MS: 100_000_000,
|
|
27
|
-
/** Maximum cache TTL in ms. ~24 hours. */
|
|
28
5
|
MAX_CACHE_TTL: 86_400_000,
|
|
29
|
-
/** Maximum delay between retries in ms. ~5 minutes. */
|
|
30
6
|
MAX_RETRY_DELAY_MS: 300_000,
|
|
31
|
-
/** Maximum dedupe inflightTtl in ms. ~24 hours. */
|
|
32
7
|
MAX_INFLIGHT_TTL: 86_400_000,
|
|
33
|
-
|
|
8
|
+
DEFAULT_INFLIGHT_TTL: 5 * 60_000,
|
|
34
9
|
DEFAULT_STORE_MAX_SIZE: 10_000,
|
|
35
|
-
/** Background sweep interval for the default store. */
|
|
36
10
|
DEFAULT_STORE_CLEANUP_INTERVAL_MS: 60_000,
|
|
11
|
+
MAX_GLOBAL_INFLIGHT: 100_000,
|
|
12
|
+
MAX_CIRCUIT_BREAKER_WINDOW: 1_000_000,
|
|
13
|
+
MAX_BULKHEAD_CONCURRENCY: 1_000_000,
|
|
14
|
+
MAX_BULKHEAD_QUEUE: 1_000_000,
|
|
15
|
+
MAX_RATE_LIMIT_CALLS: 1_000_000,
|
|
16
|
+
MAX_SANITIZED_ERROR_LENGTH: 8_192,
|
|
17
|
+
MAX_HEDGE_DELAY_MS: 300_000,
|
|
18
|
+
MAX_TENANTS: 10_000,
|
|
37
19
|
});
|
|
38
|
-
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeCall = safeCall;
|
|
4
|
+
const IS_DEV = typeof process !== 'undefined' &&
|
|
5
|
+
typeof process.env === 'object' &&
|
|
6
|
+
process.env !== null &&
|
|
7
|
+
process.env.NODE_ENV !== 'production';
|
|
8
|
+
function safeCall(fn, ...args) {
|
|
9
|
+
if (!fn)
|
|
10
|
+
return;
|
|
11
|
+
try {
|
|
12
|
+
const result = fn(...args);
|
|
13
|
+
if (result !== null && result !== undefined && typeof result.then === 'function') {
|
|
14
|
+
Promise.resolve(result).catch((err) => {
|
|
15
|
+
if (IS_DEV) {
|
|
16
|
+
console.warn('Actly: async observability hook rejected — error swallowed to protect main path. ' +
|
|
17
|
+
'Fix the hook to prevent silent observability loss.', err);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (IS_DEV) {
|
|
24
|
+
console.warn('Actly: observability hook threw — error swallowed to protect main path. ' +
|
|
25
|
+
'Fix the hook to prevent silent observability loss.', err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeCall.d.ts","sourceRoot":"","sources":["../../src/utils/safeCall.ts"],"names":[],"mappings":"AAkBA,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,EAAE,EAC1C,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,EACtC,GAAG,IAAI,EAAE,CAAC,GACT,IAAI,CA2BN"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const IS_DEV = typeof process !== 'undefined' &&
|
|
2
|
+
typeof process.env === 'object' &&
|
|
3
|
+
process.env !== null &&
|
|
4
|
+
process.env.NODE_ENV !== 'production';
|
|
5
|
+
export function safeCall(fn, ...args) {
|
|
6
|
+
if (!fn)
|
|
7
|
+
return;
|
|
8
|
+
try {
|
|
9
|
+
const result = fn(...args);
|
|
10
|
+
if (result !== null && result !== undefined && typeof result.then === 'function') {
|
|
11
|
+
Promise.resolve(result).catch((err) => {
|
|
12
|
+
if (IS_DEV) {
|
|
13
|
+
console.warn('Actly: async observability hook rejected — error swallowed to protect main path. ' +
|
|
14
|
+
'Fix the hook to prevent silent observability loss.', err);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (IS_DEV) {
|
|
21
|
+
console.warn('Actly: observability hook threw — error swallowed to protect main path. ' +
|
|
22
|
+
'Fix the hook to prevent silent observability loss.', err);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/utils/sanitize.cjs
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sanitizeErrorMessage = sanitizeErrorMessage;
|
|
4
4
|
exports.sanitizeError = sanitizeError;
|
|
5
|
+
const limits_js_1 = require("./limits.js");
|
|
6
|
+
const errors_js_1 = require("../errors.js");
|
|
5
7
|
const FORBIDDEN_CHARS = /[\x00-\x1f\x7f]/g;
|
|
6
8
|
const HTML_ENTITIES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
7
9
|
function sanitizeErrorMessage(msg) {
|
|
8
10
|
let str;
|
|
9
11
|
if (msg instanceof Error) {
|
|
10
|
-
str = msg.message;
|
|
12
|
+
str = String(msg.message ?? '');
|
|
11
13
|
}
|
|
12
14
|
else if (typeof msg === 'string') {
|
|
13
15
|
str = msg;
|
|
@@ -17,12 +19,31 @@ function sanitizeErrorMessage(msg) {
|
|
|
17
19
|
}
|
|
18
20
|
str = str.replace(/[&<>"']/g, (c) => HTML_ENTITIES[c]);
|
|
19
21
|
str = str.replace(FORBIDDEN_CHARS, '');
|
|
22
|
+
if (str.length > limits_js_1.LIMITS.MAX_SANITIZED_ERROR_LENGTH) {
|
|
23
|
+
str = str.slice(0, limits_js_1.LIMITS.MAX_SANITIZED_ERROR_LENGTH - 3) + '...';
|
|
24
|
+
}
|
|
20
25
|
return str;
|
|
21
26
|
}
|
|
22
27
|
function sanitizeError(err) {
|
|
23
28
|
if (err instanceof Error) {
|
|
24
29
|
const sanitized = new Error(sanitizeErrorMessage(err.message));
|
|
25
30
|
sanitized.name = err.name;
|
|
31
|
+
try {
|
|
32
|
+
sanitized.stack = err.stack;
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
if (err instanceof errors_js_1.ActlyError) {
|
|
36
|
+
const code = err.code;
|
|
37
|
+
const key = err.key;
|
|
38
|
+
Object.defineProperty(sanitized, 'code', { value: code, enumerable: true });
|
|
39
|
+
if (key !== undefined) {
|
|
40
|
+
Object.defineProperty(sanitized, 'key', { value: key, enumerable: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
Object.defineProperty(sanitized, 'cause', { value: err, enumerable: false });
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
26
47
|
return sanitized;
|
|
27
48
|
}
|
|
28
49
|
return sanitizeErrorMessage(err);
|
package/dist/utils/sanitize.d.ts
CHANGED
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AAQA,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAkBzD;AAQD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAmBnD"}
|
package/dist/utils/sanitize.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { LIMITS } from './limits.js';
|
|
2
|
+
import { ActlyError } from '../errors.js';
|
|
1
3
|
const FORBIDDEN_CHARS = /[\x00-\x1f\x7f]/g;
|
|
2
4
|
const HTML_ENTITIES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
3
5
|
export function sanitizeErrorMessage(msg) {
|
|
4
6
|
let str;
|
|
5
7
|
if (msg instanceof Error) {
|
|
6
|
-
str = msg.message;
|
|
8
|
+
str = String(msg.message ?? '');
|
|
7
9
|
}
|
|
8
10
|
else if (typeof msg === 'string') {
|
|
9
11
|
str = msg;
|
|
@@ -13,14 +15,32 @@ export function sanitizeErrorMessage(msg) {
|
|
|
13
15
|
}
|
|
14
16
|
str = str.replace(/[&<>"']/g, (c) => HTML_ENTITIES[c]);
|
|
15
17
|
str = str.replace(FORBIDDEN_CHARS, '');
|
|
18
|
+
if (str.length > LIMITS.MAX_SANITIZED_ERROR_LENGTH) {
|
|
19
|
+
str = str.slice(0, LIMITS.MAX_SANITIZED_ERROR_LENGTH - 3) + '...';
|
|
20
|
+
}
|
|
16
21
|
return str;
|
|
17
22
|
}
|
|
18
23
|
export function sanitizeError(err) {
|
|
19
24
|
if (err instanceof Error) {
|
|
20
25
|
const sanitized = new Error(sanitizeErrorMessage(err.message));
|
|
21
26
|
sanitized.name = err.name;
|
|
27
|
+
try {
|
|
28
|
+
sanitized.stack = err.stack;
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
if (err instanceof ActlyError) {
|
|
32
|
+
const code = err.code;
|
|
33
|
+
const key = err.key;
|
|
34
|
+
Object.defineProperty(sanitized, 'code', { value: code, enumerable: true });
|
|
35
|
+
if (key !== undefined) {
|
|
36
|
+
Object.defineProperty(sanitized, 'key', { value: key, enumerable: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
Object.defineProperty(sanitized, 'cause', { value: err, enumerable: false });
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
22
43
|
return sanitized;
|
|
23
44
|
}
|
|
24
45
|
return sanitizeErrorMessage(err);
|
|
25
46
|
}
|
|
26
|
-
//# sourceMappingURL=sanitize.js.map
|