actly 1.1.0 → 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.
Files changed (130) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +262 -232
  3. package/dist/core/act.cjs +505 -39
  4. package/dist/core/act.d.ts +81 -10
  5. package/dist/core/act.d.ts.map +1 -1
  6. package/dist/core/act.js +504 -40
  7. package/dist/core/act.js.map +1 -1
  8. package/dist/core/executor.cjs +21 -9
  9. package/dist/core/executor.d.ts +31 -6
  10. package/dist/core/executor.d.ts.map +1 -1
  11. package/dist/core/executor.js +21 -9
  12. package/dist/core/executor.js.map +1 -1
  13. package/dist/core/health.cjs +34 -0
  14. package/dist/core/health.d.ts +18 -0
  15. package/dist/core/health.d.ts.map +1 -0
  16. package/dist/core/health.js +28 -0
  17. package/dist/core/health.js.map +1 -0
  18. package/dist/core/shutdown.cjs +60 -0
  19. package/dist/core/shutdown.d.ts +8 -0
  20. package/dist/core/shutdown.d.ts.map +1 -0
  21. package/dist/core/shutdown.js +56 -0
  22. package/dist/core/shutdown.js.map +1 -0
  23. package/dist/core/tenant.cjs +71 -0
  24. package/dist/core/tenant.d.ts +29 -0
  25. package/dist/core/tenant.d.ts.map +1 -0
  26. package/dist/core/tenant.js +68 -0
  27. package/dist/core/tenant.js.map +1 -0
  28. package/dist/errors.cjs +182 -0
  29. package/dist/errors.d.ts +151 -0
  30. package/dist/errors.d.ts.map +1 -0
  31. package/dist/errors.js +171 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/index.cjs +56 -6
  34. package/dist/index.d.ts +22 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +24 -4
  37. package/dist/index.js.map +1 -1
  38. package/dist/observability.cjs +45 -0
  39. package/dist/observability.d.ts +145 -0
  40. package/dist/observability.d.ts.map +1 -0
  41. package/dist/observability.js +43 -0
  42. package/dist/observability.js.map +1 -0
  43. package/dist/policies/bulkhead.cjs +97 -0
  44. package/dist/policies/bulkhead.d.ts +3 -0
  45. package/dist/policies/bulkhead.d.ts.map +1 -0
  46. package/dist/policies/bulkhead.js +95 -0
  47. package/dist/policies/bulkhead.js.map +1 -0
  48. package/dist/policies/cache.cjs +223 -22
  49. package/dist/policies/cache.d.ts +42 -7
  50. package/dist/policies/cache.d.ts.map +1 -1
  51. package/dist/policies/cache.js +223 -22
  52. package/dist/policies/cache.js.map +1 -1
  53. package/dist/policies/circuitBreaker.cjs +85 -0
  54. package/dist/policies/circuitBreaker.d.ts +3 -0
  55. package/dist/policies/circuitBreaker.d.ts.map +1 -0
  56. package/dist/policies/circuitBreaker.js +83 -0
  57. package/dist/policies/circuitBreaker.js.map +1 -0
  58. package/dist/policies/dedupe.cjs +98 -21
  59. package/dist/policies/dedupe.d.ts +20 -13
  60. package/dist/policies/dedupe.d.ts.map +1 -1
  61. package/dist/policies/dedupe.js +98 -21
  62. package/dist/policies/dedupe.js.map +1 -1
  63. package/dist/policies/rateLimit.cjs +35 -0
  64. package/dist/policies/rateLimit.d.ts +3 -0
  65. package/dist/policies/rateLimit.d.ts.map +1 -0
  66. package/dist/policies/rateLimit.js +33 -0
  67. package/dist/policies/rateLimit.js.map +1 -0
  68. package/dist/policies/retry.cjs +103 -26
  69. package/dist/policies/retry.d.ts +25 -2
  70. package/dist/policies/retry.d.ts.map +1 -1
  71. package/dist/policies/retry.js +103 -26
  72. package/dist/policies/retry.js.map +1 -1
  73. package/dist/policies/timeout.cjs +78 -36
  74. package/dist/policies/timeout.d.ts +7 -18
  75. package/dist/policies/timeout.d.ts.map +1 -1
  76. package/dist/policies/timeout.js +84 -31
  77. package/dist/policies/timeout.js.map +1 -1
  78. package/dist/state/store.cjs +9 -2
  79. package/dist/state/store.d.ts +9 -0
  80. package/dist/state/store.d.ts.map +1 -1
  81. package/dist/state/store.js +9 -2
  82. package/dist/state/store.js.map +1 -1
  83. package/dist/stores/base.cjs +4 -4
  84. package/dist/stores/base.d.ts +32 -56
  85. package/dist/stores/base.d.ts.map +1 -1
  86. package/dist/stores/base.js +4 -4
  87. package/dist/stores/base.js.map +1 -1
  88. package/dist/stores/memory.cjs +161 -39
  89. package/dist/stores/memory.d.ts +64 -24
  90. package/dist/stores/memory.d.ts.map +1 -1
  91. package/dist/stores/memory.js +160 -39
  92. package/dist/stores/memory.js.map +1 -1
  93. package/dist/types/index.d.ts +315 -37
  94. package/dist/types/index.d.ts.map +1 -1
  95. package/dist/utils/abort.cjs +212 -0
  96. package/dist/utils/abort.d.ts +79 -0
  97. package/dist/utils/abort.d.ts.map +1 -0
  98. package/dist/utils/abort.js +206 -0
  99. package/dist/utils/abort.js.map +1 -0
  100. package/dist/utils/abortPool.cjs +26 -0
  101. package/dist/utils/abortPool.d.ts +4 -0
  102. package/dist/utils/abortPool.d.ts.map +1 -0
  103. package/dist/utils/abortPool.js +22 -0
  104. package/dist/utils/abortPool.js.map +1 -0
  105. package/dist/utils/backoff.cjs +54 -0
  106. package/dist/utils/backoff.d.ts +14 -0
  107. package/dist/utils/backoff.d.ts.map +1 -0
  108. package/dist/utils/backoff.js +52 -0
  109. package/dist/utils/backoff.js.map +1 -0
  110. package/dist/utils/key.cjs +77 -0
  111. package/dist/utils/key.d.ts +29 -0
  112. package/dist/utils/key.d.ts.map +1 -0
  113. package/dist/utils/key.js +75 -0
  114. package/dist/utils/key.js.map +1 -0
  115. package/dist/utils/limits.cjs +40 -0
  116. package/dist/utils/limits.d.ts +39 -0
  117. package/dist/utils/limits.d.ts.map +1 -0
  118. package/dist/utils/limits.js +38 -0
  119. package/dist/utils/limits.js.map +1 -0
  120. package/dist/utils/sanitize.cjs +29 -0
  121. package/dist/utils/sanitize.d.ts +3 -0
  122. package/dist/utils/sanitize.d.ts.map +1 -0
  123. package/dist/utils/sanitize.js +26 -0
  124. package/dist/utils/sanitize.js.map +1 -0
  125. package/dist/utils/validate.cjs +153 -0
  126. package/dist/utils/validate.d.ts +26 -0
  127. package/dist/utils/validate.d.ts.map +1 -0
  128. package/dist/utils/validate.js +141 -0
  129. package/dist/utils/validate.js.map +1 -0
  130. package/package.json +20 -37
package/dist/index.js CHANGED
@@ -1,6 +1,26 @@
1
- export { act } from './core/act.js';
2
- // Exported so consumers can build isolated stores (e.g. per-request in SSR)
1
+ // ─── Primary API ──────────────────────────────────────────────────────────────
2
+ export { act, invalidate, withStore, } from './core/act.js';
3
+ // ─── Execution engine (for custom policy chains) ──────────────────────────────
4
+ export { execute, REQUIRES_SYNC_STORE, } from './core/executor.js';
5
+ // ─── Stores ───────────────────────────────────────────────────────────────────
3
6
  export { InMemoryStore } from './stores/memory.js';
4
- // Exported so callers can instanceof-check against timeout failures
5
- export { TimeoutError, TotalTimeoutError } from './policies/timeout.js';
7
+ export { isSyncStore, isAsyncStore, } from './stores/base.js';
8
+ // ─── Error classes ────────────────────────────────────────────────────────────
9
+ export { ActlyError, ActlyAbortError, TimeoutError, TotalTimeoutError, RetryExhaustedError, ValidationError, } from './errors.js';
10
+ // ─── Utilities (for custom policy authors) ────────────────────────────────────
11
+ export { anySignal, raceAbort, sleep, linkSignal, isAbortError, } from './utils/abort.js';
12
+ export { sanitizeKey, } from './utils/key.js';
13
+ export { computeDelay, } from './utils/backoff.js';
14
+ export { LIMITS, } from './utils/limits.js';
15
+ // ─── Hardening: new error classes ────────────────────────────────────────────
16
+ export { CircuitBreakerOpenError, BulkheadOverflowError, RateLimitError, } from './errors.js';
17
+ // ─── Hardening: health check & graceful shutdown ─────────────────────────────
18
+ export { createHealthCheck, } from './core/health.js';
19
+ export { drain, } from './core/shutdown.js';
20
+ // ─── Hardening: tenant isolation ─────────────────────────────────────────────
21
+ export { createTenantStore, createAsyncTenantStore, } from './core/tenant.js';
22
+ // ─── Hardening: error sanitization ───────────────────────────────────────────
23
+ export { sanitizeErrorMessage, sanitizeError, } from './utils/sanitize.js';
24
+ // ─── Hardening: AbortController pool ─────────────────────────────────────────
25
+ export { acquireController, releaseController, poolSize, } from './utils/abortPool.js';
6
26
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAoBnC,4EAA4E;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,OAAO,EACL,GAAG,EACH,UAAU,EACV,SAAS,GACV,MAAM,eAAe,CAAA;AAOtB,iFAAiF;AAEjF,OAAO,EACL,OAAO,EACP,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAE3B,iFAAiF;AAEjF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EACL,WAAW,EACX,YAAY,GACb,MAAM,kBAAkB,CAAA;AAEzB,iFAAiF;AAEjF,OAAO,EACL,UAAU,EACV,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,GAChB,MAAM,aAAa,CAAA;AAgDpB,iFAAiF;AAEjF,OAAO,EACL,SAAS,EACT,SAAS,EACT,KAAK,EACL,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,WAAW,GACZ,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,YAAY,GACb,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACL,MAAM,GACP,MAAM,mBAAmB,CAAA;AAI1B,gFAAgF;AAEhF,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,GACf,MAAM,aAAa,CAAA;AAEpB,gFAAgF;AAEhF,OAAO,EACL,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,KAAK,GACN,MAAM,oBAAoB,CAAA;AAE3B,gFAAgF;AAEhF,OAAO,EACL,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,kBAAkB,CAAA;AAGzB,gFAAgF;AAEhF,OAAO,EACL,oBAAoB,EACpB,aAAa,GACd,MAAM,qBAAqB,CAAA;AAE5B,gFAAgF;AAEhF,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,GACT,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * Observability hooks.
4
+ *
5
+ * Eight event types covering the entire lifecycle of an `act()` call.
6
+ *
7
+ * # Contract
8
+ *
9
+ * When `options.observability` is `null`/`undefined` (the common case),
10
+ * no event objects are allocated and no function calls are made. The
11
+ * hot path is a single null-check per policy decision.
12
+ *
13
+ * When hooks ARE registered, events are allocated lazily — only when the
14
+ * corresponding event actually fires. A cache hit doesn't allocate an
15
+ * `onRetry` event, for example.
16
+ *
17
+ * # Event shape
18
+ *
19
+ * Every event carries `key`, `traceId`, and `timestamp` for correlation.
20
+ * Event-specific fields are on the same object (no nesting) for flat
21
+ * destructuring in user code.
22
+ *
23
+ * # Ordering
24
+ *
25
+ * For a successful fresh call with retries:
26
+ * onAttempt (1) → onAttempt (2) → onRetry (1→2) → onAttempt (3) → onFinalSuccess
27
+ *
28
+ * For a cache hit:
29
+ * onCacheHit → onFinalSuccess
30
+ *
31
+ * For a dedupe joiner:
32
+ * onDedupeJoin → onFinalSuccess (or onFinalFailure)
33
+ *
34
+ * For a timeout:
35
+ * onAttempt → onTimeout → onFinalFailure
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.hasObservers = hasObservers;
39
+ /**
40
+ * Quick null-check helper. Policies call this once at decision points; if
41
+ * it returns false, no further observability work is done.
42
+ */
43
+ function hasObservers(ctx) {
44
+ return ctx.observability != null;
45
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Observability hooks.
3
+ *
4
+ * Eight event types covering the entire lifecycle of an `act()` call.
5
+ *
6
+ * # Contract
7
+ *
8
+ * When `options.observability` is `null`/`undefined` (the common case),
9
+ * no event objects are allocated and no function calls are made. The
10
+ * hot path is a single null-check per policy decision.
11
+ *
12
+ * When hooks ARE registered, events are allocated lazily — only when the
13
+ * corresponding event actually fires. A cache hit doesn't allocate an
14
+ * `onRetry` event, for example.
15
+ *
16
+ * # Event shape
17
+ *
18
+ * Every event carries `key`, `traceId`, and `timestamp` for correlation.
19
+ * Event-specific fields are on the same object (no nesting) for flat
20
+ * destructuring in user code.
21
+ *
22
+ * # Ordering
23
+ *
24
+ * For a successful fresh call with retries:
25
+ * onAttempt (1) → onAttempt (2) → onRetry (1→2) → onAttempt (3) → onFinalSuccess
26
+ *
27
+ * For a cache hit:
28
+ * onCacheHit → onFinalSuccess
29
+ *
30
+ * For a dedupe joiner:
31
+ * onDedupeJoin → onFinalSuccess (or onFinalFailure)
32
+ *
33
+ * For a timeout:
34
+ * onAttempt → onTimeout → onFinalFailure
35
+ */
36
+ import type { ActSource } from './types/index.js';
37
+ /** Stable discriminator for telemetry. */
38
+ export type ActlyEventType = 'attempt' | 'retry' | 'cache-hit' | 'cache-miss' | 'dedupe-join' | 'timeout' | 'final-success' | 'final-failure';
39
+ /** Common fields on every event. */
40
+ export interface ActlyEventBase {
41
+ /** The `key` passed to `act()`. */
42
+ readonly key: string;
43
+ /** Auto-generated trace ID (or user-supplied via `options.traceId`). */
44
+ readonly traceId: string;
45
+ /** Event timestamp (ms since epoch). */
46
+ readonly timestamp: number;
47
+ /** Discriminator for switch statements. */
48
+ readonly type: ActlyEventType;
49
+ }
50
+ export interface AttemptEvent extends ActlyEventBase {
51
+ readonly type: 'attempt';
52
+ /** 1-based attempt number. */
53
+ readonly attempt: number;
54
+ /** Duration of this attempt in ms (set after attempt settles). */
55
+ readonly durationMs?: number;
56
+ /** Error from this attempt, if it failed. */
57
+ readonly error?: unknown;
58
+ }
59
+ export interface RetryEvent extends ActlyEventBase {
60
+ readonly type: 'retry';
61
+ /** The attempt that just failed. */
62
+ readonly attempt: number;
63
+ /** The delay (ms) before the next attempt. */
64
+ readonly delayMs: number;
65
+ /** Error that triggered the retry. */
66
+ readonly error: unknown;
67
+ }
68
+ export interface CacheHitEvent extends ActlyEventBase {
69
+ readonly type: 'cache-hit';
70
+ /** Age of the cached value in ms. */
71
+ readonly ageMs: number;
72
+ }
73
+ export interface CacheMissEvent extends ActlyEventBase {
74
+ readonly type: 'cache-miss';
75
+ }
76
+ export interface DedupeJoinEvent extends ActlyEventBase {
77
+ readonly type: 'dedupe-join';
78
+ /** This caller's position in the joiner queue (1 = first joiner). */
79
+ readonly joinerPosition: number;
80
+ }
81
+ export interface TimeoutEvent extends ActlyEventBase {
82
+ readonly type: 'timeout';
83
+ /** Which deadline fired. */
84
+ readonly kind: 'per-attempt' | 'total';
85
+ /** The configured ms. */
86
+ readonly ms: number;
87
+ }
88
+ export interface FinalSuccessEvent extends ActlyEventBase {
89
+ readonly type: 'final-success';
90
+ /** Where the value came from. */
91
+ readonly source: ActSource;
92
+ /** Total attempts made (0 for cache hit). */
93
+ readonly attempts: number;
94
+ /** Total wall-clock duration of the act() call. */
95
+ readonly durationMs: number;
96
+ }
97
+ export interface FinalFailureEvent extends ActlyEventBase {
98
+ readonly type: 'final-failure';
99
+ /** Total attempts made. */
100
+ readonly attempts: number;
101
+ /** Total wall-clock duration of the act() call. */
102
+ readonly durationMs: number;
103
+ /** Stable reason for failure — use for telemetry tags. */
104
+ readonly failedBy: 'abort' | 'timeout' | 'total-timeout' | 'retry-exhausted' | 'fn-error' | 'validation';
105
+ /** The final error. */
106
+ readonly error: unknown;
107
+ }
108
+ export type ActlyEvent = AttemptEvent | RetryEvent | CacheHitEvent | CacheMissEvent | DedupeJoinEvent | TimeoutEvent | FinalSuccessEvent | FinalFailureEvent;
109
+ /**
110
+ * User-supplied observability hooks. All optional. When absent, zero
111
+ * overhead is incurred on the hot path.
112
+ */
113
+ export interface ObservabilityHooks {
114
+ onAttempt?: (event: AttemptEvent) => void;
115
+ onRetry?: (event: RetryEvent) => void;
116
+ onCacheHit?: (event: CacheHitEvent) => void;
117
+ onCacheMiss?: (event: CacheMissEvent) => void;
118
+ onDedupeJoin?: (event: DedupeJoinEvent) => void;
119
+ onTimeout?: (event: TimeoutEvent) => void;
120
+ onFinalSuccess?: (event: FinalSuccessEvent) => void;
121
+ onFinalFailure?: (event: FinalFailureEvent) => void;
122
+ }
123
+ /**
124
+ * Internal: thread observability through the policy chain without changing
125
+ * every policy's signature. We attach it to `PolicyContext` as an optional
126
+ * field — policies that emit events check for its presence.
127
+ *
128
+ * `traceId` is also stored here so policies can include it in events.
129
+ */
130
+ export interface ObservabilityContext {
131
+ traceId: string;
132
+ hooks: ObservabilityHooks;
133
+ /** Counter for dedupe joiner position. Per-key, but we approximate by call. */
134
+ joinerCounter: number;
135
+ }
136
+ /**
137
+ * Quick null-check helper. Policies call this once at decision points; if
138
+ * it returns false, no further observability work is done.
139
+ */
140
+ export declare function hasObservers(ctx: {
141
+ observability?: ObservabilityContext;
142
+ }): ctx is {
143
+ observability: ObservabilityContext;
144
+ };
145
+ //# sourceMappingURL=observability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Observability hooks.
3
+ *
4
+ * Eight event types covering the entire lifecycle of an `act()` call.
5
+ *
6
+ * # Contract
7
+ *
8
+ * When `options.observability` is `null`/`undefined` (the common case),
9
+ * no event objects are allocated and no function calls are made. The
10
+ * hot path is a single null-check per policy decision.
11
+ *
12
+ * When hooks ARE registered, events are allocated lazily — only when the
13
+ * corresponding event actually fires. A cache hit doesn't allocate an
14
+ * `onRetry` event, for example.
15
+ *
16
+ * # Event shape
17
+ *
18
+ * Every event carries `key`, `traceId`, and `timestamp` for correlation.
19
+ * Event-specific fields are on the same object (no nesting) for flat
20
+ * destructuring in user code.
21
+ *
22
+ * # Ordering
23
+ *
24
+ * For a successful fresh call with retries:
25
+ * onAttempt (1) → onAttempt (2) → onRetry (1→2) → onAttempt (3) → onFinalSuccess
26
+ *
27
+ * For a cache hit:
28
+ * onCacheHit → onFinalSuccess
29
+ *
30
+ * For a dedupe joiner:
31
+ * onDedupeJoin → onFinalSuccess (or onFinalFailure)
32
+ *
33
+ * For a timeout:
34
+ * onAttempt → onTimeout → onFinalFailure
35
+ */
36
+ /**
37
+ * Quick null-check helper. Policies call this once at decision points; if
38
+ * it returns false, no further observability work is done.
39
+ */
40
+ export function hasObservers(ctx) {
41
+ return ctx.observability != null;
42
+ }
43
+ //# sourceMappingURL=observability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observability.js","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AA0IH;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAA6C;IACxE,OAAO,GAAG,CAAC,aAAa,IAAI,IAAI,CAAA;AAClC,CAAC"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bulkheadPolicy = bulkheadPolicy;
4
+ const executor_js_1 = require("../core/executor.js");
5
+ const errors_js_1 = require("../errors.js");
6
+ const NS = 'bulk:';
7
+ function getState(store, key) {
8
+ return store.get(NS + key) ?? { active: 0, queue: [] };
9
+ }
10
+ function setState(store, key, state) {
11
+ store.set(NS + key, state);
12
+ }
13
+ function bulkheadPolicy(opts) {
14
+ const maxConcurrent = Math.max(1, Math.floor(opts.maxConcurrent));
15
+ const queueTimeoutMs = opts.queueTimeoutMs ?? 0;
16
+ const applier = (fn, ctx) => {
17
+ const syncCtx = ctx;
18
+ return async (signal) => {
19
+ const key = syncCtx.key;
20
+ const acquireSlot = () => {
21
+ // If signal already aborted, reject immediately
22
+ if (signal.aborted)
23
+ return Promise.reject(signal.reason);
24
+ const state = getState(syncCtx.store, key);
25
+ if (state.active < maxConcurrent) {
26
+ state.active++;
27
+ setState(syncCtx.store, key, state);
28
+ return Promise.resolve();
29
+ }
30
+ if (queueTimeoutMs === 0) {
31
+ throw new errors_js_1.BulkheadOverflowError(key, maxConcurrent);
32
+ }
33
+ return new Promise((resolve, reject) => {
34
+ const state2 = getState(syncCtx.store, key);
35
+ const entry = {
36
+ resolve,
37
+ reject,
38
+ signal,
39
+ };
40
+ // Remove entry from queue on signal abort
41
+ entry.onAbort = () => {
42
+ const s = getState(syncCtx.store, key);
43
+ const idx = s.queue.indexOf(entry);
44
+ if (idx >= 0) {
45
+ s.queue.splice(idx, 1);
46
+ setState(syncCtx.store, key, s);
47
+ }
48
+ if (entry.timer)
49
+ clearTimeout(entry.timer);
50
+ reject(signal.reason);
51
+ };
52
+ if (queueTimeoutMs > 0) {
53
+ entry.timer = setTimeout(() => {
54
+ const s = getState(syncCtx.store, key);
55
+ const idx = s.queue.indexOf(entry);
56
+ if (idx >= 0)
57
+ s.queue.splice(idx, 1);
58
+ setState(syncCtx.store, key, s);
59
+ signal.removeEventListener('abort', entry.onAbort);
60
+ reject(new errors_js_1.BulkheadOverflowError(key, maxConcurrent));
61
+ }, queueTimeoutMs);
62
+ }
63
+ signal.addEventListener('abort', entry.onAbort, { once: true });
64
+ state2.queue.push(entry);
65
+ setState(syncCtx.store, key, state2);
66
+ });
67
+ };
68
+ const releaseSlot = () => {
69
+ const state = getState(syncCtx.store, key);
70
+ state.active--;
71
+ if (state.queue.length > 0) {
72
+ const next = state.queue.shift();
73
+ state.active++;
74
+ if (next.timer)
75
+ clearTimeout(next.timer);
76
+ // Remove abort listener from the QUEUED caller's signal (not releaser's)
77
+ if (next.onAbort && next.signal) {
78
+ next.signal.removeEventListener('abort', next.onAbort);
79
+ }
80
+ next.resolve();
81
+ }
82
+ if (state.active < 0)
83
+ state.active = 0;
84
+ setState(syncCtx.store, key, state);
85
+ };
86
+ await acquireSlot();
87
+ try {
88
+ return await fn(signal);
89
+ }
90
+ finally {
91
+ releaseSlot();
92
+ }
93
+ };
94
+ };
95
+ applier[executor_js_1.REQUIRES_SYNC_STORE] = true;
96
+ return applier;
97
+ }
@@ -0,0 +1,3 @@
1
+ import type { PolicyApplier, BulkheadOptions } from '../types/index.js';
2
+ export declare function bulkheadPolicy<T>(opts: BulkheadOptions): PolicyApplier<T>;
3
+ //# sourceMappingURL=bulkhead.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulkhead.d.ts","sourceRoot":"","sources":["../../src/policies/bulkhead.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,aAAa,EAAiB,eAAe,EAAE,MAAM,mBAAmB,CAAA;AA0B7F,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,CAAC,CA0FzE"}
@@ -0,0 +1,95 @@
1
+ import { REQUIRES_SYNC_STORE } from '../core/executor.js';
2
+ import { BulkheadOverflowError } from '../errors.js';
3
+ const NS = 'bulk:';
4
+ function getState(store, key) {
5
+ return store.get(NS + key) ?? { active: 0, queue: [] };
6
+ }
7
+ function setState(store, key, state) {
8
+ store.set(NS + key, state);
9
+ }
10
+ export function bulkheadPolicy(opts) {
11
+ const maxConcurrent = Math.max(1, Math.floor(opts.maxConcurrent));
12
+ const queueTimeoutMs = opts.queueTimeoutMs ?? 0;
13
+ const applier = (fn, ctx) => {
14
+ const syncCtx = ctx;
15
+ return async (signal) => {
16
+ const key = syncCtx.key;
17
+ const acquireSlot = () => {
18
+ // If signal already aborted, reject immediately
19
+ if (signal.aborted)
20
+ return Promise.reject(signal.reason);
21
+ const state = getState(syncCtx.store, key);
22
+ if (state.active < maxConcurrent) {
23
+ state.active++;
24
+ setState(syncCtx.store, key, state);
25
+ return Promise.resolve();
26
+ }
27
+ if (queueTimeoutMs === 0) {
28
+ throw new BulkheadOverflowError(key, maxConcurrent);
29
+ }
30
+ return new Promise((resolve, reject) => {
31
+ const state2 = getState(syncCtx.store, key);
32
+ const entry = {
33
+ resolve,
34
+ reject,
35
+ signal,
36
+ };
37
+ // Remove entry from queue on signal abort
38
+ entry.onAbort = () => {
39
+ const s = getState(syncCtx.store, key);
40
+ const idx = s.queue.indexOf(entry);
41
+ if (idx >= 0) {
42
+ s.queue.splice(idx, 1);
43
+ setState(syncCtx.store, key, s);
44
+ }
45
+ if (entry.timer)
46
+ clearTimeout(entry.timer);
47
+ reject(signal.reason);
48
+ };
49
+ if (queueTimeoutMs > 0) {
50
+ entry.timer = setTimeout(() => {
51
+ const s = getState(syncCtx.store, key);
52
+ const idx = s.queue.indexOf(entry);
53
+ if (idx >= 0)
54
+ s.queue.splice(idx, 1);
55
+ setState(syncCtx.store, key, s);
56
+ signal.removeEventListener('abort', entry.onAbort);
57
+ reject(new BulkheadOverflowError(key, maxConcurrent));
58
+ }, queueTimeoutMs);
59
+ }
60
+ signal.addEventListener('abort', entry.onAbort, { once: true });
61
+ state2.queue.push(entry);
62
+ setState(syncCtx.store, key, state2);
63
+ });
64
+ };
65
+ const releaseSlot = () => {
66
+ const state = getState(syncCtx.store, key);
67
+ state.active--;
68
+ if (state.queue.length > 0) {
69
+ const next = state.queue.shift();
70
+ state.active++;
71
+ if (next.timer)
72
+ clearTimeout(next.timer);
73
+ // Remove abort listener from the QUEUED caller's signal (not releaser's)
74
+ if (next.onAbort && next.signal) {
75
+ next.signal.removeEventListener('abort', next.onAbort);
76
+ }
77
+ next.resolve();
78
+ }
79
+ if (state.active < 0)
80
+ state.active = 0;
81
+ setState(syncCtx.store, key, state);
82
+ };
83
+ await acquireSlot();
84
+ try {
85
+ return await fn(signal);
86
+ }
87
+ finally {
88
+ releaseSlot();
89
+ }
90
+ };
91
+ };
92
+ applier[REQUIRES_SYNC_STORE] = true;
93
+ return applier;
94
+ }
95
+ //# sourceMappingURL=bulkhead.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulkhead.js","sourceRoot":"","sources":["../../src/policies/bulkhead.ts"],"names":[],"mappings":"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"}