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
package/dist/core/shutdown.js
CHANGED
|
@@ -17,16 +17,21 @@ export function unregisterDrainable(scope = 'default') {
|
|
|
17
17
|
for (const r of s.resolvers)
|
|
18
18
|
r();
|
|
19
19
|
s.resolvers = [];
|
|
20
|
+
if (scope !== 'default') {
|
|
21
|
+
drainStates.delete(scope);
|
|
22
|
+
}
|
|
20
23
|
}
|
|
21
24
|
}
|
|
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
25
|
export async function drain(timeoutMs, scope = 'default') {
|
|
27
|
-
const s =
|
|
28
|
-
if (s
|
|
26
|
+
const s = drainStates.get(scope);
|
|
27
|
+
if (!s)
|
|
28
|
+
return true;
|
|
29
|
+
if (s.inflight === 0) {
|
|
30
|
+
if (scope !== 'default' && s.resolvers.length === 0) {
|
|
31
|
+
drainStates.delete(scope);
|
|
32
|
+
}
|
|
29
33
|
return true;
|
|
34
|
+
}
|
|
30
35
|
return new Promise((resolve) => {
|
|
31
36
|
let resolved = false;
|
|
32
37
|
const resolver = () => {
|
|
@@ -34,7 +39,6 @@ export async function drain(timeoutMs, scope = 'default') {
|
|
|
34
39
|
return;
|
|
35
40
|
resolved = true;
|
|
36
41
|
clearTimeout(timer);
|
|
37
|
-
// Remove this resolver from the array
|
|
38
42
|
const idx = s.resolvers.indexOf(resolver);
|
|
39
43
|
if (idx >= 0)
|
|
40
44
|
s.resolvers.splice(idx, 1);
|
|
@@ -44,7 +48,6 @@ export async function drain(timeoutMs, scope = 'default') {
|
|
|
44
48
|
if (resolved)
|
|
45
49
|
return;
|
|
46
50
|
resolved = true;
|
|
47
|
-
// Remove this resolver from the array on timeout
|
|
48
51
|
const idx = s.resolvers.indexOf(resolver);
|
|
49
52
|
if (idx >= 0)
|
|
50
53
|
s.resolvers.splice(idx, 1);
|
|
@@ -53,4 +56,10 @@ export async function drain(timeoutMs, scope = 'default') {
|
|
|
53
56
|
s.resolvers.push(resolver);
|
|
54
57
|
});
|
|
55
58
|
}
|
|
56
|
-
|
|
59
|
+
export async function drainAll(timeoutMs) {
|
|
60
|
+
const scopes = Array.from(drainStates.keys());
|
|
61
|
+
if (scopes.length === 0)
|
|
62
|
+
return true;
|
|
63
|
+
const results = await Promise.all(scopes.map(scope => drain(timeoutMs, scope)));
|
|
64
|
+
return results.every(r => r === true);
|
|
65
|
+
}
|
package/dist/core/tenant.cjs
CHANGED
|
@@ -5,25 +5,37 @@ exports.createAsyncTenantStore = createAsyncTenantStore;
|
|
|
5
5
|
const memory_js_1 = require("../stores/memory.js");
|
|
6
6
|
const act_js_1 = require("./act.js");
|
|
7
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
8
|
function createTenantStore(options = {}) {
|
|
14
9
|
const maxSize = options.maxSize ?? limits_js_1.LIMITS.DEFAULT_STORE_MAX_SIZE;
|
|
15
10
|
const autoCleanup = options.autoCleanup ?? true;
|
|
16
11
|
const cleanupIntervalMs = options.cleanupIntervalMs ?? 60_000;
|
|
12
|
+
const maxTenants = options.maxTenants ?? limits_js_1.LIMITS.MAX_TENANTS;
|
|
17
13
|
const tenants = new Map();
|
|
18
14
|
return {
|
|
19
15
|
get(tenantId) {
|
|
20
16
|
let entry = tenants.get(tenantId);
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
const scoped = (0, act_js_1.withStore)(store);
|
|
24
|
-
entry = { store, scoped };
|
|
17
|
+
if (entry) {
|
|
18
|
+
tenants.delete(tenantId);
|
|
25
19
|
tenants.set(tenantId, entry);
|
|
20
|
+
return entry.scoped;
|
|
21
|
+
}
|
|
22
|
+
if (maxTenants !== Number.POSITIVE_INFINITY && tenants.size >= maxTenants) {
|
|
23
|
+
const lruId = tenants.keys().next().value;
|
|
24
|
+
if (lruId !== undefined) {
|
|
25
|
+
const lru = tenants.get(lruId);
|
|
26
|
+
if (lru) {
|
|
27
|
+
try {
|
|
28
|
+
lru.store.destroy();
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
}
|
|
32
|
+
tenants.delete(lruId);
|
|
33
|
+
}
|
|
26
34
|
}
|
|
35
|
+
const store = new memory_js_1.InMemoryStore({ maxSize, autoCleanup, cleanupIntervalMs });
|
|
36
|
+
const scoped = (0, act_js_1.withStore)(store);
|
|
37
|
+
entry = { store, scoped };
|
|
38
|
+
tenants.set(tenantId, entry);
|
|
27
39
|
return entry.scoped;
|
|
28
40
|
},
|
|
29
41
|
evict(tenantId) {
|
|
@@ -44,28 +56,58 @@ function createTenantStore(options = {}) {
|
|
|
44
56
|
},
|
|
45
57
|
};
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* Each tenant gets its own store instance with prefixed keys.
|
|
50
|
-
*/
|
|
51
|
-
function createAsyncTenantStore(storeFactory) {
|
|
59
|
+
function createAsyncTenantStore(storeFactory, options = {}) {
|
|
60
|
+
const maxTenants = options.maxTenants ?? limits_js_1.LIMITS.MAX_TENANTS;
|
|
52
61
|
const tenants = new Map();
|
|
62
|
+
const safeDestroy = (store) => {
|
|
63
|
+
try {
|
|
64
|
+
const result = store.destroy?.();
|
|
65
|
+
if (result && typeof result.then === 'function') {
|
|
66
|
+
;
|
|
67
|
+
result.catch(() => { });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
}
|
|
72
|
+
};
|
|
53
73
|
return {
|
|
54
74
|
get(tenantId) {
|
|
55
75
|
let entry = tenants.get(tenantId);
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
const scoped = (0, act_js_1.withStore)(store);
|
|
59
|
-
entry = { store, scoped };
|
|
76
|
+
if (entry) {
|
|
77
|
+
tenants.delete(tenantId);
|
|
60
78
|
tenants.set(tenantId, entry);
|
|
79
|
+
return entry.scoped;
|
|
61
80
|
}
|
|
81
|
+
if (maxTenants !== Number.POSITIVE_INFINITY && tenants.size >= maxTenants) {
|
|
82
|
+
const lruId = tenants.keys().next().value;
|
|
83
|
+
if (lruId !== undefined) {
|
|
84
|
+
const lru = tenants.get(lruId);
|
|
85
|
+
if (lru)
|
|
86
|
+
safeDestroy(lru.store);
|
|
87
|
+
tenants.delete(lruId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const store = storeFactory(tenantId);
|
|
91
|
+
const scoped = (0, act_js_1.withStore)(store);
|
|
92
|
+
entry = { store, scoped };
|
|
93
|
+
tenants.set(tenantId, entry);
|
|
62
94
|
return entry.scoped;
|
|
63
95
|
},
|
|
64
96
|
evict(tenantId) {
|
|
65
|
-
tenants.
|
|
97
|
+
const entry = tenants.get(tenantId);
|
|
98
|
+
if (entry) {
|
|
99
|
+
safeDestroy(entry.store);
|
|
100
|
+
tenants.delete(tenantId);
|
|
101
|
+
}
|
|
66
102
|
},
|
|
67
103
|
size() {
|
|
68
104
|
return tenants.size;
|
|
69
105
|
},
|
|
106
|
+
destroy() {
|
|
107
|
+
for (const [, entry] of tenants) {
|
|
108
|
+
safeDestroy(entry.store);
|
|
109
|
+
}
|
|
110
|
+
tenants.clear();
|
|
111
|
+
},
|
|
70
112
|
};
|
|
71
113
|
}
|
package/dist/core/tenant.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface TenantStoreOptions {
|
|
|
4
4
|
maxSize?: number;
|
|
5
5
|
autoCleanup?: boolean;
|
|
6
6
|
cleanupIntervalMs?: number;
|
|
7
|
+
maxTenants?: number;
|
|
7
8
|
}
|
|
8
9
|
export interface TenantManager {
|
|
9
10
|
get(tenantId: string): ScopedActSync | ScopedActAsync;
|
|
@@ -11,19 +12,13 @@ export interface TenantManager {
|
|
|
11
12
|
size(): number;
|
|
12
13
|
destroy(): void;
|
|
13
14
|
}
|
|
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
15
|
export declare function createTenantStore(options?: TenantStoreOptions): TenantManager;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
export declare function createAsyncTenantStore(storeFactory: (tenantId: string) => AsyncStateStore): {
|
|
16
|
+
export declare function createAsyncTenantStore(storeFactory: (tenantId: string) => AsyncStateStore, options?: {
|
|
17
|
+
maxTenants?: number;
|
|
18
|
+
}): {
|
|
25
19
|
get: (tenantId: string) => ScopedActAsync;
|
|
26
20
|
evict: (tenantId: string) => void;
|
|
27
21
|
size: () => number;
|
|
22
|
+
destroy: () => void;
|
|
28
23
|
};
|
|
29
24
|
//# sourceMappingURL=tenant.d.ts.map
|
|
@@ -1 +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;
|
|
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;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAGxD,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAa1B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;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;AAWD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,aAAa,CAwDjF;AAOD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,eAAe,EACnD,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACpC;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;IAClB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAkEA"}
|
package/dist/core/tenant.js
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
import { InMemoryStore } from '../stores/memory.js';
|
|
2
2
|
import { withStore } from './act.js';
|
|
3
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
4
|
export function createTenantStore(options = {}) {
|
|
10
5
|
const maxSize = options.maxSize ?? LIMITS.DEFAULT_STORE_MAX_SIZE;
|
|
11
6
|
const autoCleanup = options.autoCleanup ?? true;
|
|
12
7
|
const cleanupIntervalMs = options.cleanupIntervalMs ?? 60_000;
|
|
8
|
+
const maxTenants = options.maxTenants ?? LIMITS.MAX_TENANTS;
|
|
13
9
|
const tenants = new Map();
|
|
14
10
|
return {
|
|
15
11
|
get(tenantId) {
|
|
16
12
|
let entry = tenants.get(tenantId);
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
const scoped = withStore(store);
|
|
20
|
-
entry = { store, scoped };
|
|
13
|
+
if (entry) {
|
|
14
|
+
tenants.delete(tenantId);
|
|
21
15
|
tenants.set(tenantId, entry);
|
|
16
|
+
return entry.scoped;
|
|
17
|
+
}
|
|
18
|
+
if (maxTenants !== Number.POSITIVE_INFINITY && tenants.size >= maxTenants) {
|
|
19
|
+
const lruId = tenants.keys().next().value;
|
|
20
|
+
if (lruId !== undefined) {
|
|
21
|
+
const lru = tenants.get(lruId);
|
|
22
|
+
if (lru) {
|
|
23
|
+
try {
|
|
24
|
+
lru.store.destroy();
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
tenants.delete(lruId);
|
|
29
|
+
}
|
|
22
30
|
}
|
|
31
|
+
const store = new InMemoryStore({ maxSize, autoCleanup, cleanupIntervalMs });
|
|
32
|
+
const scoped = withStore(store);
|
|
33
|
+
entry = { store, scoped };
|
|
34
|
+
tenants.set(tenantId, entry);
|
|
23
35
|
return entry.scoped;
|
|
24
36
|
},
|
|
25
37
|
evict(tenantId) {
|
|
@@ -40,29 +52,58 @@ export function createTenantStore(options = {}) {
|
|
|
40
52
|
},
|
|
41
53
|
};
|
|
42
54
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* Each tenant gets its own store instance with prefixed keys.
|
|
46
|
-
*/
|
|
47
|
-
export function createAsyncTenantStore(storeFactory) {
|
|
55
|
+
export function createAsyncTenantStore(storeFactory, options = {}) {
|
|
56
|
+
const maxTenants = options.maxTenants ?? LIMITS.MAX_TENANTS;
|
|
48
57
|
const tenants = new Map();
|
|
58
|
+
const safeDestroy = (store) => {
|
|
59
|
+
try {
|
|
60
|
+
const result = store.destroy?.();
|
|
61
|
+
if (result && typeof result.then === 'function') {
|
|
62
|
+
;
|
|
63
|
+
result.catch(() => { });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
}
|
|
68
|
+
};
|
|
49
69
|
return {
|
|
50
70
|
get(tenantId) {
|
|
51
71
|
let entry = tenants.get(tenantId);
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
const scoped = withStore(store);
|
|
55
|
-
entry = { store, scoped };
|
|
72
|
+
if (entry) {
|
|
73
|
+
tenants.delete(tenantId);
|
|
56
74
|
tenants.set(tenantId, entry);
|
|
75
|
+
return entry.scoped;
|
|
57
76
|
}
|
|
77
|
+
if (maxTenants !== Number.POSITIVE_INFINITY && tenants.size >= maxTenants) {
|
|
78
|
+
const lruId = tenants.keys().next().value;
|
|
79
|
+
if (lruId !== undefined) {
|
|
80
|
+
const lru = tenants.get(lruId);
|
|
81
|
+
if (lru)
|
|
82
|
+
safeDestroy(lru.store);
|
|
83
|
+
tenants.delete(lruId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const store = storeFactory(tenantId);
|
|
87
|
+
const scoped = withStore(store);
|
|
88
|
+
entry = { store, scoped };
|
|
89
|
+
tenants.set(tenantId, entry);
|
|
58
90
|
return entry.scoped;
|
|
59
91
|
},
|
|
60
92
|
evict(tenantId) {
|
|
61
|
-
tenants.
|
|
93
|
+
const entry = tenants.get(tenantId);
|
|
94
|
+
if (entry) {
|
|
95
|
+
safeDestroy(entry.store);
|
|
96
|
+
tenants.delete(tenantId);
|
|
97
|
+
}
|
|
62
98
|
},
|
|
63
99
|
size() {
|
|
64
100
|
return tenants.size;
|
|
65
101
|
},
|
|
102
|
+
destroy() {
|
|
103
|
+
for (const [, entry] of tenants) {
|
|
104
|
+
safeDestroy(entry.store);
|
|
105
|
+
}
|
|
106
|
+
tenants.clear();
|
|
107
|
+
},
|
|
66
108
|
};
|
|
67
109
|
}
|
|
68
|
-
//# sourceMappingURL=tenant.js.map
|
package/dist/errors.cjs
CHANGED
|
@@ -1,35 +1,8 @@
|
|
|
1
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
2
|
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
|
-
|
|
3
|
+
exports.HedgeTimeoutError = exports.ResourceExhaustedError = exports.RateLimitError = exports.BulkheadOverflowError = exports.CircuitBreakerOpenError = exports.ValidationError = exports.RetryExhaustedError = exports.TotalTimeoutError = exports.TimeoutError = exports.ActlyAbortError = exports.ActlyError = void 0;
|
|
4
|
+
exports.isActlyError = isActlyError;
|
|
31
5
|
class ActlyError extends Error {
|
|
32
|
-
/** The key associated with the failure, if applicable. */
|
|
33
6
|
key;
|
|
34
7
|
constructor(message, options) {
|
|
35
8
|
super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);
|
|
@@ -37,23 +10,49 @@ class ActlyError extends Error {
|
|
|
37
10
|
if (options?.key !== undefined) {
|
|
38
11
|
Object.defineProperty(this, 'key', { value: options.key, enumerable: true });
|
|
39
12
|
}
|
|
40
|
-
// Restore prototype chain after Error inheritance (TS es2022 target
|
|
41
|
-
// may strip it). This ensures `instanceof` works correctly.
|
|
42
13
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
43
14
|
}
|
|
15
|
+
toJSON(opts) {
|
|
16
|
+
const obj = {
|
|
17
|
+
name: this.name,
|
|
18
|
+
code: this.code,
|
|
19
|
+
message: opts?.redact ? sanitizeErrorMessageForJSON(this.message) : this.message,
|
|
20
|
+
};
|
|
21
|
+
if (this.key !== undefined)
|
|
22
|
+
obj.key = this.key;
|
|
23
|
+
if (this.stack !== undefined)
|
|
24
|
+
obj.stack = this.stack;
|
|
25
|
+
for (const prop of Object.keys(this)) {
|
|
26
|
+
if (!(prop in obj)) {
|
|
27
|
+
try {
|
|
28
|
+
obj[prop] = this[prop];
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return obj;
|
|
35
|
+
}
|
|
44
36
|
}
|
|
45
37
|
exports.ActlyError = ActlyError;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
function isActlyError(e) {
|
|
39
|
+
return (e != null &&
|
|
40
|
+
typeof e === 'object' &&
|
|
41
|
+
typeof e.code === 'string' &&
|
|
42
|
+
String(e.code).startsWith('ACTLY_'));
|
|
43
|
+
}
|
|
44
|
+
function sanitizeErrorMessageForJSON(msg) {
|
|
45
|
+
let str;
|
|
46
|
+
if (msg instanceof Error) {
|
|
47
|
+
str = String(msg.message ?? '');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
str = String(msg ?? '');
|
|
51
|
+
}
|
|
52
|
+
if (str.length > 4096)
|
|
53
|
+
str = str.slice(0, 4096) + '…[truncated]';
|
|
54
|
+
return str.replace(/[<>]/g, (c) => (c === '<' ? '<' : '>'));
|
|
55
|
+
}
|
|
57
56
|
class ActlyAbortError extends ActlyError {
|
|
58
57
|
code = 'ACTLY_ABORT';
|
|
59
58
|
constructor(options) {
|
|
@@ -62,17 +61,6 @@ class ActlyAbortError extends ActlyError {
|
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
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
64
|
class TimeoutError extends ActlyError {
|
|
77
65
|
code = 'ACTLY_TIMEOUT';
|
|
78
66
|
ms;
|
|
@@ -82,12 +70,6 @@ class TimeoutError extends ActlyError {
|
|
|
82
70
|
}
|
|
83
71
|
}
|
|
84
72
|
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
73
|
class TotalTimeoutError extends ActlyError {
|
|
92
74
|
code = 'ACTLY_TOTAL_TIMEOUT';
|
|
93
75
|
ms;
|
|
@@ -97,25 +79,6 @@ class TotalTimeoutError extends ActlyError {
|
|
|
97
79
|
}
|
|
98
80
|
}
|
|
99
81
|
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
82
|
class RetryExhaustedError extends ActlyError {
|
|
120
83
|
code = 'ACTLY_RETRY_EXHAUSTED';
|
|
121
84
|
attempts;
|
|
@@ -130,18 +93,10 @@ class RetryExhaustedError extends ActlyError {
|
|
|
130
93
|
}
|
|
131
94
|
}
|
|
132
95
|
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
96
|
class ValidationError extends ActlyError {
|
|
142
97
|
code = 'ACTLY_VALIDATION';
|
|
143
98
|
constructor(message, options) {
|
|
144
|
-
super(message);
|
|
99
|
+
super(message, options);
|
|
145
100
|
if (options?.field !== undefined) {
|
|
146
101
|
Object.defineProperty(this, 'field', { value: options.field, enumerable: true });
|
|
147
102
|
}
|
|
@@ -149,34 +104,46 @@ class ValidationError extends ActlyError {
|
|
|
149
104
|
field;
|
|
150
105
|
}
|
|
151
106
|
exports.ValidationError = ValidationError;
|
|
152
|
-
// ─── Hardening error classes ─────────────────────────────────────────────────
|
|
153
|
-
/** Thrown when a circuit breaker is open and blocks the call. */
|
|
154
107
|
class CircuitBreakerOpenError extends ActlyError {
|
|
155
108
|
code = 'ACTLY_CIRCUIT_OPEN';
|
|
156
|
-
key
|
|
157
|
-
|
|
158
|
-
super(`Circuit breaker open for key "${key}" — retry after ${ms}ms`);
|
|
159
|
-
this.key = key;
|
|
109
|
+
constructor(key, ms, options) {
|
|
110
|
+
super(`Circuit breaker open for key "${key}" — retry after ${ms}ms`, { key, cause: options?.cause });
|
|
160
111
|
}
|
|
161
112
|
}
|
|
162
113
|
exports.CircuitBreakerOpenError = CircuitBreakerOpenError;
|
|
163
|
-
/** Thrown when a bulkhead is full (maxConcurrent reached, queue timed out). */
|
|
164
114
|
class BulkheadOverflowError extends ActlyError {
|
|
165
115
|
code = 'ACTLY_BULKHEAD_FULL';
|
|
166
|
-
key
|
|
167
|
-
|
|
168
|
-
super(`Bulkhead full for key "${key}" — maxConcurrent ${maxConcurrent} reached`);
|
|
169
|
-
this.key = key;
|
|
116
|
+
constructor(key, maxConcurrent, options) {
|
|
117
|
+
super(`Bulkhead full for key "${key}" — maxConcurrent ${maxConcurrent} reached`, { key, cause: options?.cause });
|
|
170
118
|
}
|
|
171
119
|
}
|
|
172
120
|
exports.BulkheadOverflowError = BulkheadOverflowError;
|
|
173
|
-
/** Thrown when a rate limit is exceeded. */
|
|
174
121
|
class RateLimitError extends ActlyError {
|
|
175
122
|
code = 'ACTLY_RATE_LIMIT';
|
|
176
|
-
key
|
|
177
|
-
|
|
178
|
-
super(`Rate limit exceeded for key "${key}" — ${maxCalls} calls per ${windowMs}ms`);
|
|
179
|
-
this.key = key;
|
|
123
|
+
constructor(key, maxCalls, windowMs, options) {
|
|
124
|
+
super(`Rate limit exceeded for key "${key}" — ${maxCalls} calls per ${windowMs}ms`, { key, cause: options?.cause });
|
|
180
125
|
}
|
|
181
126
|
}
|
|
182
127
|
exports.RateLimitError = RateLimitError;
|
|
128
|
+
class ResourceExhaustedError extends ActlyError {
|
|
129
|
+
code = 'ACTLY_RESOURCE_EXHAUSTED';
|
|
130
|
+
current;
|
|
131
|
+
limit;
|
|
132
|
+
constructor(current, limit, options) {
|
|
133
|
+
super(`Actly: resource exhausted — ${current} in-flight calls exceed process limit ${limit}. ` +
|
|
134
|
+
`Set ACTLY_NO_INFLIGHT_LIMIT=1 to disable this guard (at your own risk).`, options);
|
|
135
|
+
this.current = current;
|
|
136
|
+
this.limit = limit;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.ResourceExhaustedError = ResourceExhaustedError;
|
|
140
|
+
class HedgeTimeoutError extends ActlyError {
|
|
141
|
+
code = 'ACTLY_HEDGE_TIMEOUT';
|
|
142
|
+
delayMs;
|
|
143
|
+
constructor(options) {
|
|
144
|
+
const ms = options?.delayMs ?? 0;
|
|
145
|
+
super(`ACT hedge timed out after ${ms}ms`, options);
|
|
146
|
+
this.delayMs = ms;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.HedgeTimeoutError = HedgeTimeoutError;
|