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.
- package/LICENSE +0 -0
- package/README.md +262 -232
- package/dist/core/act.cjs +505 -39
- package/dist/core/act.d.ts +81 -10
- package/dist/core/act.d.ts.map +1 -1
- package/dist/core/act.js +504 -40
- package/dist/core/act.js.map +1 -1
- package/dist/core/executor.cjs +21 -9
- package/dist/core/executor.d.ts +31 -6
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +21 -9
- 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 +56 -6
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -4
- 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 +223 -22
- package/dist/policies/cache.d.ts +42 -7
- package/dist/policies/cache.d.ts.map +1 -1
- package/dist/policies/cache.js +223 -22
- 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 +98 -21
- package/dist/policies/dedupe.d.ts +20 -13
- package/dist/policies/dedupe.d.ts.map +1 -1
- package/dist/policies/dedupe.js +98 -21
- 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 +103 -26
- package/dist/policies/retry.d.ts +25 -2
- package/dist/policies/retry.d.ts.map +1 -1
- package/dist/policies/retry.js +103 -26
- package/dist/policies/retry.js.map +1 -1
- package/dist/policies/timeout.cjs +78 -36
- package/dist/policies/timeout.d.ts +7 -18
- package/dist/policies/timeout.d.ts.map +1 -1
- package/dist/policies/timeout.js +84 -31
- package/dist/policies/timeout.js.map +1 -1
- package/dist/state/store.cjs +9 -2
- package/dist/state/store.d.ts +9 -0
- package/dist/state/store.d.ts.map +1 -1
- package/dist/state/store.js +9 -2
- package/dist/state/store.js.map +1 -1
- package/dist/stores/base.cjs +4 -4
- package/dist/stores/base.d.ts +32 -56
- package/dist/stores/base.d.ts.map +1 -1
- package/dist/stores/base.js +4 -4
- package/dist/stores/base.js.map +1 -1
- package/dist/stores/memory.cjs +161 -39
- package/dist/stores/memory.d.ts +64 -24
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +160 -39
- package/dist/stores/memory.js.map +1 -1
- package/dist/types/index.d.ts +315 -37
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/abort.cjs +212 -0
- package/dist/utils/abort.d.ts +79 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +206 -0
- package/dist/utils/abort.js.map +1 -0
- 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 +54 -0
- package/dist/utils/backoff.d.ts +14 -0
- package/dist/utils/backoff.d.ts.map +1 -0
- package/dist/utils/backoff.js +52 -0
- package/dist/utils/backoff.js.map +1 -0
- 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 +153 -0
- package/dist/utils/validate.d.ts +26 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +141 -0
- package/dist/utils/validate.js.map +1 -0
- package/package.json +20 -37
package/dist/policies/timeout.js
CHANGED
|
@@ -1,46 +1,99 @@
|
|
|
1
|
+
import { linkSignal } from '../utils/abort.js';
|
|
2
|
+
import { TimeoutError, TotalTimeoutError } from '../errors.js';
|
|
1
3
|
// ─── Errors ───────────────────────────────────────────────────────────────────
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
//
|
|
5
|
+
// TimeoutError and TotalTimeoutError live in `src/errors.ts` and
|
|
6
|
+
// now extend `ActlyError` (which extends `Error`). Existing `instanceof
|
|
7
|
+
// Error` and `instanceof TimeoutError` checks continue to work; new
|
|
8
|
+
// `instanceof ActlyError` and `.code` field give consumers a stable
|
|
9
|
+
// discriminator for telemetry.
|
|
10
|
+
//
|
|
11
|
+
// Re-exported here for backwards compatibility with code that imports
|
|
12
|
+
// from `'actly/policies/timeout'` (the deep path).
|
|
13
|
+
export { TimeoutError, TotalTimeoutError };
|
|
14
|
+
// ─── Policy ───────────────────────────────────────────────────────────────────
|
|
9
15
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
16
|
+
* Build a timeout policy that throws `ErrorCtor` on deadline.
|
|
17
|
+
*
|
|
18
|
+
* # Cancellation contract
|
|
19
|
+
*
|
|
20
|
+
* Each invocation:
|
|
21
|
+
* 1. Creates a fresh `AbortController` for this attempt.
|
|
22
|
+
* 2. Arms a `setTimeout` that aborts the controller with a fresh `ErrorCtor(ms)`.
|
|
23
|
+
* 3. Links the parent signal: if the parent aborts (e.g. `totalTimeout` or
|
|
24
|
+
* caller cancellation), the child aborts with the parent's reason.
|
|
25
|
+
* 4. Races `fn(childSignal)` against the abort event.
|
|
26
|
+
*
|
|
27
|
+
* The race is critical: it ensures `act()` returns promptly even if `fn`
|
|
28
|
+
* ignores the signal. The underlying `fn` may keep running in the background
|
|
29
|
+
* (resource leak), but the caller is unblocked. This is the best JavaScript
|
|
30
|
+
* can do without cooperation from `fn`.
|
|
31
|
+
*
|
|
32
|
+
* If `fn` cooperates (passes `signal` to `fetch`, `AbortController`, etc.),
|
|
33
|
+
* the underlying work is cancelled properly — no leak.
|
|
34
|
+
*
|
|
35
|
+
* # Error attribution
|
|
36
|
+
*
|
|
37
|
+
* If the per-attempt timer fires, we throw `ErrorCtor(ms)` regardless of
|
|
38
|
+
* what `fn` does. If the parent signal fires first, we throw the parent's
|
|
39
|
+
* reason (could be `TotalTimeoutError`, an `AbortError`, or anything else).
|
|
13
40
|
*/
|
|
14
|
-
export class TotalTimeoutError extends Error {
|
|
15
|
-
constructor(ms) {
|
|
16
|
-
super(`ACT total timeout exceeded after ${ms}ms`);
|
|
17
|
-
this.name = 'TotalTimeoutError';
|
|
18
|
-
this.ms = ms;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
22
|
-
/** Shared race-against-a-timer logic. ErrorCtor lets callers pick the error type. */
|
|
23
41
|
function makeTimeoutPolicy(opts, ErrorCtor) {
|
|
24
|
-
return (fn,
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
return (fn, ctx) => async (parentSignal) => {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
// Pass key to the error ctor for better debugging context.
|
|
45
|
+
const timerError = new ErrorCtor(opts.ms, { key: ctx.key });
|
|
46
|
+
// Arm the per-attempt timer. The error object is allocated once so the
|
|
47
|
+
// stack trace points here (the policy frame), not at setTimeout's
|
|
48
|
+
// internal callback.
|
|
49
|
+
const timer = setTimeout(() => controller.abort(timerError), opts.ms);
|
|
50
|
+
// NOTE: do NOT `unref()` this timer. The timeout IS the operation
|
|
51
|
+
// the caller is awaiting. unref'ing would let Node exit the process
|
|
52
|
+
// while a timeout was pending — silently dropping the operation.
|
|
53
|
+
// The timer is cleared in the finally block below.
|
|
54
|
+
// Link parent → child. Capture the unlink function so we can clean
|
|
55
|
+
// up the listener on success path (contract).
|
|
56
|
+
const unlink = linkSignal(parentSignal, controller);
|
|
57
|
+
try {
|
|
58
|
+
// Race fn against the abort event. If fn settles first, we get its
|
|
59
|
+
// result/error. If the signal aborts first, we reject with reason.
|
|
60
|
+
//
|
|
61
|
+
// We do NOT use AbortSignal.timeout() here because we need to throw
|
|
62
|
+
// our own ErrorCtor, not a DOMException named "TimeoutError".
|
|
63
|
+
return await new Promise((resolve, reject) => {
|
|
64
|
+
if (controller.signal.aborted) {
|
|
65
|
+
reject(controller.signal.reason);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const onAbort = () => reject(controller.signal.reason);
|
|
69
|
+
controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
70
|
+
Promise.resolve(fn(controller.signal)).then((value) => {
|
|
71
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
72
|
+
resolve(value);
|
|
73
|
+
}, (error) => {
|
|
74
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
75
|
+
reject(error);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
unlink();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
30
84
|
}
|
|
31
|
-
// ─── Policies ─────────────────────────────────────────────────────────────────
|
|
32
85
|
/**
|
|
33
|
-
* Races fn against a
|
|
34
|
-
* Rejects with TimeoutError if the deadline fires first.
|
|
86
|
+
* Per-attempt timeout. Races `fn` against a deadline that resets on retry.
|
|
35
87
|
*
|
|
36
|
-
* Place this INSIDE retryPolicy (closer to fn) so each attempt has its
|
|
88
|
+
* Place this INSIDE `retryPolicy` (closer to `fn`) so each attempt has its
|
|
89
|
+
* own clock.
|
|
37
90
|
*/
|
|
38
91
|
export function timeoutPolicy(opts) {
|
|
39
92
|
return makeTimeoutPolicy(opts, TimeoutError);
|
|
40
93
|
}
|
|
41
94
|
/**
|
|
42
|
-
* Races the ENTIRE
|
|
43
|
-
*
|
|
95
|
+
* Operation-wide timeout. Races the ENTIRE chain (all retry attempts +
|
|
96
|
+
* delays) against a hard budget that does NOT reset.
|
|
44
97
|
*
|
|
45
98
|
* Place this as the OUTERMOST policy so the clock starts before any other
|
|
46
99
|
* policy runs and stops regardless of what the inner chain is doing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/policies/timeout.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/policies/timeout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAE9D,iFAAiF;AACjF,EAAE;AACF,iEAAiE;AACjE,wEAAwE;AACxE,oEAAoE;AACpE,oEAAoE;AACpE,+BAA+B;AAC/B,EAAE;AACF,sEAAsE;AACtE,mDAAmD;AAEnD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAA;AAE1C,iFAAiF;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAAS,iBAAiB,CACxB,IAAoB,EACpB,SAAgE;IAEhE,OAAO,CAAC,EAAY,EAAE,GAAkB,EAAY,EAAE,CACpD,KAAK,EAAE,YAAyB,EAAE,EAAE;QAClC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,2DAA2D;QAC3D,MAAM,UAAU,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAA;QAE3D,uEAAuE;QACvE,kEAAkE;QAClE,qBAAqB;QACrB,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,EAClC,IAAI,CAAC,EAAE,CACR,CAAA;QACD,kEAAkE;QAClE,oEAAoE;QACpE,iEAAiE;QACjE,mDAAmD;QAEnD,mEAAmE;QACnE,8CAA8C;QAC9C,MAAM,MAAM,GAAG,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;QAEnD,IAAI,CAAC;YACH,mEAAmE;YACnE,mEAAmE;YACnE,EAAE;YACF,oEAAoE;YACpE,8DAA8D;YAC9D,OAAO,MAAM,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC9C,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC9B,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;oBAChC,OAAM;gBACR,CAAC;gBAED,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBACtD,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;gBAEpE,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CACzC,CAAC,KAAK,EAAE,EAAE;oBACR,UAAU,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;oBACvD,OAAO,CAAC,KAAK,CAAC,CAAA;gBAChB,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;oBACR,UAAU,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;oBACvD,MAAM,CAAC,KAAK,CAAC,CAAA;gBACf,CAAC,CACF,CAAA;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,MAAM,EAAE,CAAA;QACV,CAAC;IACH,CAAC,CAAA;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAI,IAAoB;IACnD,OAAO,iBAAiB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAI,IAAoB;IACxD,OAAO,iBAAiB,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAA;AACnD,CAAC"}
|
package/dist/state/store.cjs
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.InMemoryStore = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated import from `'actly'` directly.
|
|
6
|
+
*
|
|
7
|
+
* This re-export shim exists for backwards compatibility with code that
|
|
8
|
+
* imported `InMemoryStore` from `'actly/state/store'`. It will be removed
|
|
9
|
+
* in a future major release.
|
|
10
|
+
*
|
|
11
|
+
* The implementation lives in `src/stores/memory.ts`.
|
|
12
|
+
*/
|
|
6
13
|
var memory_js_1 = require("../stores/memory.js");
|
|
7
14
|
Object.defineProperty(exports, "InMemoryStore", { enumerable: true, get: function () { return memory_js_1.InMemoryStore; } });
|
package/dist/state/store.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated import from `'actly'` directly.
|
|
3
|
+
*
|
|
4
|
+
* This re-export shim exists for backwards compatibility with code that
|
|
5
|
+
* imported `InMemoryStore` from `'actly/state/store'`. It will be removed
|
|
6
|
+
* in a future major release.
|
|
7
|
+
*
|
|
8
|
+
* The implementation lives in `src/stores/memory.ts`.
|
|
9
|
+
*/
|
|
1
10
|
export { InMemoryStore } from '../stores/memory.js';
|
|
2
11
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/state/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/state/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/state/store.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated import from `'actly'` directly.
|
|
3
|
+
*
|
|
4
|
+
* This re-export shim exists for backwards compatibility with code that
|
|
5
|
+
* imported `InMemoryStore` from `'actly/state/store'`. It will be removed
|
|
6
|
+
* in a future major release.
|
|
7
|
+
*
|
|
8
|
+
* The implementation lives in `src/stores/memory.ts`.
|
|
9
|
+
*/
|
|
3
10
|
export { InMemoryStore } from '../stores/memory.js';
|
|
4
11
|
//# sourceMappingURL=store.js.map
|
package/dist/state/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/state/store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/state/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/stores/base.cjs
CHANGED
|
@@ -5,15 +5,15 @@ exports.isSyncStore = isSyncStore;
|
|
|
5
5
|
exports.isAsyncStore = isAsyncStore;
|
|
6
6
|
// ─── Type guards ──────────────────────────────────────────────────────────────
|
|
7
7
|
/**
|
|
8
|
-
* Narrows AnyStateStore to SyncStateStore via the _sync discriminant.
|
|
9
|
-
* Used by execute() and cachePolicy to branch between sync and async paths.
|
|
8
|
+
* Narrows `AnyStateStore` to `SyncStateStore` via the `_sync` discriminant.
|
|
9
|
+
* Used by `execute()` and `cachePolicy` to branch between sync and async paths.
|
|
10
10
|
*/
|
|
11
11
|
function isSyncStore(store) {
|
|
12
12
|
return store._sync === true;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Narrows AnyStateStore to AsyncStateStore via the _sync discriminant.
|
|
16
|
-
* Provided for symmetry; prefer isSyncStore for the common guard pattern.
|
|
15
|
+
* Narrows `AnyStateStore` to `AsyncStateStore` via the `_sync` discriminant.
|
|
16
|
+
* Provided for symmetry; prefer `isSyncStore` for the common guard pattern.
|
|
17
17
|
*/
|
|
18
18
|
function isAsyncStore(store) {
|
|
19
19
|
return store._sync === false;
|
package/dist/stores/base.d.ts
CHANGED
|
@@ -3,34 +3,28 @@
|
|
|
3
3
|
*
|
|
4
4
|
* # Why synchronous?
|
|
5
5
|
*
|
|
6
|
-
* dedupePolicy must read an in-flight Promise from the store and, if absent,
|
|
7
|
-
* write a new one — all within a single synchronous frame. If get() were
|
|
8
|
-
* two concurrent callers could both observe a miss before either
|
|
9
|
-
* defeating deduplication entirely. There is no lock primitive
|
|
10
|
-
* that can paper over this: the constraint is structural, not
|
|
11
|
-
* level.
|
|
6
|
+
* `dedupePolicy` must read an in-flight Promise from the store and, if absent,
|
|
7
|
+
* write a new one — all within a single synchronous frame. If `get()` were
|
|
8
|
+
* async, two concurrent callers could both observe a miss before either
|
|
9
|
+
* write lands, defeating deduplication entirely. There is no lock primitive
|
|
10
|
+
* in JavaScript that can paper over this: the constraint is structural, not
|
|
11
|
+
* implementation-level.
|
|
12
12
|
*
|
|
13
|
-
* # The _sync discriminant
|
|
13
|
+
* # The `_sync` discriminant
|
|
14
14
|
*
|
|
15
|
-
* `_sync: true` is a runtime tag that lets execute() enforce the dedupe
|
|
16
|
-
* constraint for plain-JS callers who bypass TypeScript. It is NOT part of
|
|
17
|
-
* semantic contract and MUST NOT be used for anything beyond that guard.
|
|
15
|
+
* `_sync: true` is a runtime tag that lets `execute()` enforce the dedupe
|
|
16
|
+
* constraint for plain-JS callers who bypass TypeScript. It is NOT part of
|
|
17
|
+
* the semantic contract and MUST NOT be used for anything beyond that guard.
|
|
18
18
|
* Implementations must set it as a `readonly` literal (`true as const`).
|
|
19
19
|
*
|
|
20
20
|
* # Lifecycle
|
|
21
21
|
*
|
|
22
22
|
* Implementations that hold background resources (timers, connections) should
|
|
23
|
-
* expose a `destroy()` method as a convention
|
|
24
|
-
*
|
|
25
|
-
* requiring Promise returns. See InMemoryStore for the reference pattern.
|
|
26
|
-
*
|
|
27
|
-
* InMemoryStore is the canonical implementation of this interface.
|
|
23
|
+
* expose a `destroy()` method as a convention. See `InMemoryStore` for the
|
|
24
|
+
* reference pattern.
|
|
28
25
|
*/
|
|
29
26
|
export interface SyncStateStore {
|
|
30
|
-
/**
|
|
31
|
-
* Runtime discriminant. Read by isSyncStore() in execute() to enforce the
|
|
32
|
-
* dedupe constraint without an instanceof check. Must be `true as const`.
|
|
33
|
-
*/
|
|
27
|
+
/** Runtime discriminant. Must be `true as const`. */
|
|
34
28
|
readonly _sync: true;
|
|
35
29
|
get<T>(key: string): T | undefined;
|
|
36
30
|
set<T>(key: string, value: T, ttlMs?: number): void;
|
|
@@ -38,13 +32,14 @@ export interface SyncStateStore {
|
|
|
38
32
|
has(key: string): boolean;
|
|
39
33
|
/**
|
|
40
34
|
* Remove all entries synchronously.
|
|
41
|
-
*
|
|
35
|
+
* After `clear()` returns, `size()` returns 0.
|
|
42
36
|
*/
|
|
43
37
|
clear(): void;
|
|
44
38
|
/**
|
|
45
39
|
* Return the count of live (non-expired) entries.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
40
|
+
* Side-effect free: does not mutate the store. Implementations MAY evict
|
|
41
|
+
* expired entries opportunistically during this call, but MUST NOT have
|
|
42
|
+
* observable side effects beyond internal cleanup.
|
|
48
43
|
*/
|
|
49
44
|
size(): number;
|
|
50
45
|
}
|
|
@@ -53,60 +48,41 @@ export interface SyncStateStore {
|
|
|
53
48
|
*
|
|
54
49
|
* # Policy compatibility
|
|
55
50
|
*
|
|
56
|
-
* Compatible with cachePolicy only. Passing an AsyncStateStore to a
|
|
57
|
-
* chain that includes dedupePolicy is a TypeScript error and a
|
|
58
|
-
* execute()
|
|
59
|
-
* requires synchronous access.
|
|
51
|
+
* Compatible with `cachePolicy` only. Passing an `AsyncStateStore` to a
|
|
52
|
+
* policy chain that includes `dedupePolicy` is a TypeScript error and a
|
|
53
|
+
* runtime error — `execute()` throws at chain-build time. See
|
|
54
|
+
* `SyncStateStore` for why dedupe requires synchronous access.
|
|
60
55
|
*
|
|
61
56
|
* # TTL semantics
|
|
62
57
|
*
|
|
63
|
-
* The store is responsible for honouring ttlMs
|
|
58
|
+
* The store is responsible for honouring `ttlMs`. Actly passes it as a hint.
|
|
64
59
|
* Implementations may delegate to a native TTL mechanism (e.g. Redis EXPIRE).
|
|
65
|
-
* There is no enforcement layer above the store boundary.
|
|
66
60
|
*
|
|
67
|
-
* # The _sync discriminant
|
|
61
|
+
* # The `_sync` discriminant
|
|
68
62
|
*
|
|
69
|
-
* `_sync: false` mirrors the discriminant on SyncStateStore
|
|
70
|
-
*
|
|
71
|
-
* access. Must be set as a `readonly` literal (`false as const`).
|
|
72
|
-
*
|
|
73
|
-
* # Contract parity
|
|
74
|
-
*
|
|
75
|
-
* clear() and size() mirror the SyncStateStore contract so callers building
|
|
76
|
-
* against AnyStateStore can rely on both operations regardless of which variant
|
|
77
|
-
* they receive. Async implementations that do not have a native equivalent
|
|
78
|
-
* (e.g. a bounded Redis namespace) must still satisfy the signature.
|
|
63
|
+
* `_sync: false` mirrors the discriminant on `SyncStateStore`. Must be set
|
|
64
|
+
* as a `readonly` literal (`false as const`).
|
|
79
65
|
*/
|
|
80
66
|
export interface AsyncStateStore {
|
|
81
|
-
/**
|
|
82
|
-
* Runtime discriminant. Must be `false as const`.
|
|
83
|
-
*/
|
|
67
|
+
/** Runtime discriminant. Must be `false as const`. */
|
|
84
68
|
readonly _sync: false;
|
|
85
69
|
get<T>(key: string): Promise<T | undefined>;
|
|
86
70
|
set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
|
|
87
71
|
delete(key: string): Promise<void>;
|
|
88
72
|
has(key: string): Promise<boolean>;
|
|
89
|
-
/**
|
|
90
|
-
* Remove all entries managed by this store.
|
|
91
|
-
* For scoped adapters (e.g. a Redis key-prefix namespace), remove only the
|
|
92
|
-
* entries owned by this instance — not the entire backing store.
|
|
93
|
-
*/
|
|
73
|
+
/** Remove all entries managed by this store. */
|
|
94
74
|
clear(): Promise<void>;
|
|
95
|
-
/**
|
|
96
|
-
* Return the count of live (non-expired) entries.
|
|
97
|
-
* Expired entries must not be counted. For eventually-consistent backends,
|
|
98
|
-
* the count is a best-effort snapshot at the time of the call.
|
|
99
|
-
*/
|
|
75
|
+
/** Return the count of live (non-expired) entries. */
|
|
100
76
|
size(): Promise<number>;
|
|
101
77
|
}
|
|
102
78
|
/**
|
|
103
|
-
* Narrows AnyStateStore to SyncStateStore via the _sync discriminant.
|
|
104
|
-
* Used by execute() and cachePolicy to branch between sync and async paths.
|
|
79
|
+
* Narrows `AnyStateStore` to `SyncStateStore` via the `_sync` discriminant.
|
|
80
|
+
* Used by `execute()` and `cachePolicy` to branch between sync and async paths.
|
|
105
81
|
*/
|
|
106
82
|
export declare function isSyncStore(store: SyncStateStore | AsyncStateStore): store is SyncStateStore;
|
|
107
83
|
/**
|
|
108
|
-
* Narrows AnyStateStore to AsyncStateStore via the _sync discriminant.
|
|
109
|
-
* Provided for symmetry; prefer isSyncStore for the common guard pattern.
|
|
84
|
+
* Narrows `AnyStateStore` to `AsyncStateStore` via the `_sync` discriminant.
|
|
85
|
+
* Provided for symmetry; prefer `isSyncStore` for the common guard pattern.
|
|
110
86
|
*/
|
|
111
87
|
export declare function isAsyncStore(store: SyncStateStore | AsyncStateStore): store is AsyncStateStore;
|
|
112
88
|
//# sourceMappingURL=base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/stores/base.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/stores/base.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;IAEpB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAA;IAClC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAEzB;;;OAGG;IACH,KAAK,IAAI,IAAI,CAAA;IAEb;;;;;OAKG;IACH,IAAI,IAAI,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IAErB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAA;IAC3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAElC,gDAAgD;IAChD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtB,sDAAsD;IACtD,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID;;;GAGG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,GAAG,eAAe,GACtC,KAAK,IAAI,cAAc,CAEzB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,GAAG,eAAe,GACtC,KAAK,IAAI,eAAe,CAE1B"}
|
package/dist/stores/base.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// ─── Store interfaces ─────────────────────────────────────────────────────────
|
|
2
2
|
// ─── Type guards ──────────────────────────────────────────────────────────────
|
|
3
3
|
/**
|
|
4
|
-
* Narrows AnyStateStore to SyncStateStore via the _sync discriminant.
|
|
5
|
-
* Used by execute() and cachePolicy to branch between sync and async paths.
|
|
4
|
+
* Narrows `AnyStateStore` to `SyncStateStore` via the `_sync` discriminant.
|
|
5
|
+
* Used by `execute()` and `cachePolicy` to branch between sync and async paths.
|
|
6
6
|
*/
|
|
7
7
|
export function isSyncStore(store) {
|
|
8
8
|
return store._sync === true;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Narrows AnyStateStore to AsyncStateStore via the _sync discriminant.
|
|
12
|
-
* Provided for symmetry; prefer isSyncStore for the common guard pattern.
|
|
11
|
+
* Narrows `AnyStateStore` to `AsyncStateStore` via the `_sync` discriminant.
|
|
12
|
+
* Provided for symmetry; prefer `isSyncStore` for the common guard pattern.
|
|
13
13
|
*/
|
|
14
14
|
export function isAsyncStore(store) {
|
|
15
15
|
return store._sync === false;
|
package/dist/stores/base.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/stores/base.ts"],"names":[],"mappings":"AAAA,iFAAiF;
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/stores/base.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAuFjF,iFAAiF;AAEjF;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAuC;IAEvC,OAAO,KAAK,CAAC,KAAK,KAAK,IAAI,CAAA;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAuC;IAEvC,OAAO,KAAK,CAAC,KAAK,KAAK,KAAK,CAAA;AAC9B,CAAC"}
|