actly 1.1.0 → 1.1.5

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 (70) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +284 -52
  3. package/dist/core/act.cjs +183 -35
  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 +181 -35
  7. package/dist/core/act.js.map +1 -1
  8. package/dist/core/executor.cjs +19 -9
  9. package/dist/core/executor.d.ts +25 -5
  10. package/dist/core/executor.d.ts.map +1 -1
  11. package/dist/core/executor.js +19 -9
  12. package/dist/core/executor.js.map +1 -1
  13. package/dist/index.cjs +13 -3
  14. package/dist/index.d.ts +7 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +8 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/policies/cache.cjs +85 -21
  19. package/dist/policies/cache.d.ts +29 -7
  20. package/dist/policies/cache.d.ts.map +1 -1
  21. package/dist/policies/cache.js +85 -21
  22. package/dist/policies/cache.js.map +1 -1
  23. package/dist/policies/dedupe.cjs +68 -20
  24. package/dist/policies/dedupe.d.ts +38 -13
  25. package/dist/policies/dedupe.d.ts.map +1 -1
  26. package/dist/policies/dedupe.js +68 -20
  27. package/dist/policies/dedupe.js.map +1 -1
  28. package/dist/policies/retry.cjs +65 -25
  29. package/dist/policies/retry.d.ts +25 -2
  30. package/dist/policies/retry.d.ts.map +1 -1
  31. package/dist/policies/retry.js +65 -25
  32. package/dist/policies/retry.js.map +1 -1
  33. package/dist/policies/timeout.cjs +87 -17
  34. package/dist/policies/timeout.d.ts +28 -8
  35. package/dist/policies/timeout.d.ts.map +1 -1
  36. package/dist/policies/timeout.js +87 -17
  37. package/dist/policies/timeout.js.map +1 -1
  38. package/dist/state/store.cjs +9 -2
  39. package/dist/state/store.d.ts +9 -0
  40. package/dist/state/store.d.ts.map +1 -1
  41. package/dist/state/store.js +9 -2
  42. package/dist/state/store.js.map +1 -1
  43. package/dist/stores/base.cjs +4 -4
  44. package/dist/stores/base.d.ts +32 -56
  45. package/dist/stores/base.d.ts.map +1 -1
  46. package/dist/stores/base.js +4 -4
  47. package/dist/stores/base.js.map +1 -1
  48. package/dist/stores/memory.cjs +77 -28
  49. package/dist/stores/memory.d.ts +44 -23
  50. package/dist/stores/memory.d.ts.map +1 -1
  51. package/dist/stores/memory.js +77 -28
  52. package/dist/stores/memory.js.map +1 -1
  53. package/dist/types/index.d.ts +141 -36
  54. package/dist/types/index.d.ts.map +1 -1
  55. package/dist/utils/abort.cjs +142 -0
  56. package/dist/utils/abort.d.ts +55 -0
  57. package/dist/utils/abort.d.ts.map +1 -0
  58. package/dist/utils/abort.js +136 -0
  59. package/dist/utils/abort.js.map +1 -0
  60. package/dist/utils/backoff.cjs +43 -0
  61. package/dist/utils/backoff.d.ts +14 -0
  62. package/dist/utils/backoff.d.ts.map +1 -0
  63. package/dist/utils/backoff.js +41 -0
  64. package/dist/utils/backoff.js.map +1 -0
  65. package/dist/utils/validate.cjs +79 -0
  66. package/dist/utils/validate.d.ts +15 -0
  67. package/dist/utils/validate.d.ts.map +1 -0
  68. package/dist/utils/validate.js +72 -0
  69. package/dist/utils/validate.js.map +1 -0
  70. package/package.json +19 -37
@@ -3,22 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.REQUIRES_SYNC_STORE = void 0;
4
4
  exports.execute = execute;
5
5
  const base_js_1 = require("../stores/base.js");
6
- // Symbol stamped onto PolicyApplier functions by dedupePolicy.
7
- // Lets execute() detect a dedupe policy without importing the policy module
8
- // (which would create a circular dep) or doing fragile name-sniffing.
6
+ /**
7
+ * Symbol stamped onto `PolicyApplier` functions by `dedupePolicy`.
8
+ *
9
+ * Lets `execute()` detect a dedupe policy without importing the policy
10
+ * module (which would create a circular dep) or doing fragile name-sniffing.
11
+ */
9
12
  exports.REQUIRES_SYNC_STORE = Symbol('actly.requiresSyncStore');
10
13
  /**
11
14
  * Pure execution engine.
12
15
  *
13
- * This file imports nothing from /policies.
14
- * It operates on PolicyApplier<T> — a type alias defined in /types.
15
- * Policy implementations live in /policies and are wired in core/act.ts.
16
+ * This file imports nothing from `/policies`. It operates on `PolicyApplier<T>`
17
+ * — a type alias defined in `/types`. Policy implementations live in
18
+ * `/policies` and are wired in `core/act.ts`.
19
+ *
20
+ * # Public API
21
+ *
22
+ * Exported so consumers can build custom policy chains with explicit stores
23
+ * (e.g. for SSR request isolation or multi-tenant scenarios where the
24
+ * module-level default store is wrong).
16
25
  */
17
26
  async function execute(input) {
18
27
  // Guard: if any policy in the chain requires a sync store, the provided
19
28
  // store must be synchronous. An async store + dedupePolicy is a silent
20
- // correctness failure, not just a performance issue catch it here rather
21
- // than letting it produce subtly wrong dedupe behaviour at runtime.
29
+ // correctness failure catch it here rather than letting it produce
30
+ // subtly wrong dedupe behaviour at runtime.
22
31
  const needsSync = input.policies.some(p => p[exports.REQUIRES_SYNC_STORE]);
23
32
  if (needsSync && !(0, base_js_1.isSyncStore)(input.store)) {
24
33
  throw new Error('Actly: dedupePolicy requires a SyncStateStore (store._sync === true). ' +
@@ -33,5 +42,6 @@ async function execute(input) {
33
42
  // Build the call chain from inside out.
34
43
  // reduceRight ensures policies[0] becomes the outermost wrapper (runs first).
35
44
  const wrapped = input.policies.reduceRight((inner, applyPolicy) => applyPolicy(inner, ctx), input.fn);
36
- return wrapped();
45
+ // Call the outermost wrapper with the root signal.
46
+ return wrapped(input.signal);
37
47
  }
@@ -1,13 +1,19 @@
1
1
  import type { ActFn, PolicyApplier, AnyStateStore, RunMeta } from '../types/index.js';
2
+ /**
3
+ * Symbol stamped onto `PolicyApplier` functions by `dedupePolicy`.
4
+ *
5
+ * Lets `execute()` detect a dedupe policy without importing the policy
6
+ * module (which would create a circular dep) or doing fragile name-sniffing.
7
+ */
2
8
  export declare const REQUIRES_SYNC_STORE: unique symbol;
3
9
  export interface ExecutorInput<T> {
4
10
  key: string;
5
11
  fn: ActFn<T>;
6
12
  /**
7
13
  * Policies ordered outermost -> innermost.
8
- * policies[0] intercepts first; policies[last] is closest to fn.
14
+ * `policies[0]` intercepts first; `policies[last]` is closest to `fn`.
9
15
  *
10
- * Canonical order: [totalTimeout, cache, dedupe, retry, timeout]
16
+ * Canonical order: `[totalTimeout, cache, dedupe, retry, timeout]`
11
17
  * totalTimeout -> hard wall-clock budget over the entire operation
12
18
  * cache -> a hit skips everything below it
13
19
  * dedupe -> collapses concurrent callers before retry fires
@@ -17,13 +23,27 @@ export interface ExecutorInput<T> {
17
23
  policies: ReadonlyArray<PolicyApplier<T>>;
18
24
  store: AnyStateStore;
19
25
  meta: RunMeta;
26
+ /**
27
+ * Root AbortSignal for the operation. Propagated inward through the
28
+ * policy chain: each policy receives it as the `signal` argument to
29
+ * its wrapped `ActFn`. The outermost policy may layer its own signal
30
+ * (e.g. `totalTimeoutPolicy` arms a timer) and pass the composite
31
+ * inward.
32
+ */
33
+ signal: AbortSignal;
20
34
  }
21
35
  /**
22
36
  * Pure execution engine.
23
37
  *
24
- * This file imports nothing from /policies.
25
- * It operates on PolicyApplier<T> — a type alias defined in /types.
26
- * Policy implementations live in /policies and are wired in core/act.ts.
38
+ * This file imports nothing from `/policies`. It operates on `PolicyApplier<T>`
39
+ * — a type alias defined in `/types`. Policy implementations live in
40
+ * `/policies` and are wired in `core/act.ts`.
41
+ *
42
+ * # Public API
43
+ *
44
+ * Exported so consumers can build custom policy chains with explicit stores
45
+ * (e.g. for SSR request isolation or multi-tenant scenarios where the
46
+ * module-level default store is wrong).
27
47
  */
28
48
  export declare function execute<T>(input: ExecutorInput<T>): Promise<T>;
29
49
  //# sourceMappingURL=executor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,aAAa,EAEb,aAAa,EACb,OAAO,EACR,MAAM,mBAAmB,CAAA;AAM1B,eAAO,MAAM,mBAAmB,eAAoC,CAAA;AAEpE,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,GAAG,EAAO,MAAM,CAAA;IAChB,EAAE,EAAQ,KAAK,CAAC,CAAC,CAAC,CAAA;IAClB;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IACzC,KAAK,EAAK,aAAa,CAAA;IACvB,IAAI,EAAM,OAAO,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA8BpE"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,aAAa,EAEb,aAAa,EACb,OAAO,EACR,MAAM,mBAAmB,CAAA;AAG1B;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,eAAoC,CAAA;AAEpE,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,GAAG,EAAO,MAAM,CAAA;IAChB,EAAE,EAAQ,KAAK,CAAC,CAAC,CAAC,CAAA;IAClB;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IACzC,KAAK,EAAK,aAAa,CAAA;IACvB,IAAI,EAAM,OAAO,CAAA;IACjB;;;;;;OAMG;IACH,MAAM,EAAI,WAAW,CAAA;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA+BpE"}
@@ -1,20 +1,29 @@
1
1
  import { isSyncStore } from '../stores/base.js';
2
- // Symbol stamped onto PolicyApplier functions by dedupePolicy.
3
- // Lets execute() detect a dedupe policy without importing the policy module
4
- // (which would create a circular dep) or doing fragile name-sniffing.
2
+ /**
3
+ * Symbol stamped onto `PolicyApplier` functions by `dedupePolicy`.
4
+ *
5
+ * Lets `execute()` detect a dedupe policy without importing the policy
6
+ * module (which would create a circular dep) or doing fragile name-sniffing.
7
+ */
5
8
  export const REQUIRES_SYNC_STORE = Symbol('actly.requiresSyncStore');
6
9
  /**
7
10
  * Pure execution engine.
8
11
  *
9
- * This file imports nothing from /policies.
10
- * It operates on PolicyApplier<T> — a type alias defined in /types.
11
- * Policy implementations live in /policies and are wired in core/act.ts.
12
+ * This file imports nothing from `/policies`. It operates on `PolicyApplier<T>`
13
+ * — a type alias defined in `/types`. Policy implementations live in
14
+ * `/policies` and are wired in `core/act.ts`.
15
+ *
16
+ * # Public API
17
+ *
18
+ * Exported so consumers can build custom policy chains with explicit stores
19
+ * (e.g. for SSR request isolation or multi-tenant scenarios where the
20
+ * module-level default store is wrong).
12
21
  */
13
22
  export async function execute(input) {
14
23
  // Guard: if any policy in the chain requires a sync store, the provided
15
24
  // store must be synchronous. An async store + dedupePolicy is a silent
16
- // correctness failure, not just a performance issue catch it here rather
17
- // than letting it produce subtly wrong dedupe behaviour at runtime.
25
+ // correctness failure catch it here rather than letting it produce
26
+ // subtly wrong dedupe behaviour at runtime.
18
27
  const needsSync = input.policies.some(p => p[REQUIRES_SYNC_STORE]);
19
28
  if (needsSync && !isSyncStore(input.store)) {
20
29
  throw new Error('Actly: dedupePolicy requires a SyncStateStore (store._sync === true). ' +
@@ -29,6 +38,7 @@ export async function execute(input) {
29
38
  // Build the call chain from inside out.
30
39
  // reduceRight ensures policies[0] becomes the outermost wrapper (runs first).
31
40
  const wrapped = input.policies.reduceRight((inner, applyPolicy) => applyPolicy(inner, ctx), input.fn);
32
- return wrapped();
41
+ // Call the outermost wrapper with the root signal.
42
+ return wrapped(input.signal);
33
43
  }
34
44
  //# sourceMappingURL=executor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,+DAA+D;AAC/D,4EAA4E;AAC5E,sEAAsE;AACtE,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAA;AAqBpE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAI,KAAuB;IACtD,wEAAwE;IACxE,uEAAuE;IACvE,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CACnC,CAAC,CAAC,EAAE,CAAE,CAA4D,CAAC,mBAAmB,CAAC,CACxF,CAAA;IACD,IAAI,SAAS,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,wEAAwE;YACxE,uDAAuD;YACvD,kEAAkE,CACnE,CAAA;IACH,CAAC;IAED,MAAM,GAAG,GAAkB;QACzB,GAAG,EAAI,KAAK,CAAC,GAAG;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAG,KAAK,CAAC,IAAI;KAClB,CAAA;IAED,wCAAwC;IACxC,8EAA8E;IAC9E,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,CACxC,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,EAC/C,KAAK,CAAC,EAAE,CACT,CAAA;IAED,OAAO,OAAO,EAAE,CAAA;AAClB,CAAC"}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAA;AA6BpE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAI,KAAuB;IACtD,wEAAwE;IACxE,uEAAuE;IACvE,qEAAqE;IACrE,4CAA4C;IAC5C,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CACnC,CAAC,CAAC,EAAE,CAAE,CAA4D,CAAC,mBAAmB,CAAC,CACxF,CAAA;IACD,IAAI,SAAS,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,wEAAwE;YACxE,uDAAuD;YACvD,kEAAkE,CACnE,CAAA;IACH,CAAC;IAED,MAAM,GAAG,GAAkB;QACzB,GAAG,EAAI,KAAK,CAAC,GAAG;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAG,KAAK,CAAC,IAAI;KAClB,CAAA;IAED,wCAAwC;IACxC,8EAA8E;IAC9E,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,CACxC,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,EAC/C,KAAK,CAAC,EAAE,CACT,CAAA;IAED,mDAAmD;IACnD,OAAO,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;AAC9B,CAAC"}
package/dist/index.cjs CHANGED
@@ -1,12 +1,22 @@
1
1
  "use strict";
2
+ // ─── Primary API ──────────────────────────────────────────────────────────────
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TotalTimeoutError = exports.TimeoutError = exports.InMemoryStore = exports.act = void 0;
4
+ exports.TotalTimeoutError = exports.TimeoutError = exports.isAsyncStore = exports.isSyncStore = exports.InMemoryStore = exports.REQUIRES_SYNC_STORE = exports.execute = exports.withStore = exports.invalidate = exports.act = void 0;
4
5
  var act_js_1 = require("./core/act.js");
5
6
  Object.defineProperty(exports, "act", { enumerable: true, get: function () { return act_js_1.act; } });
6
- // Exported so consumers can build isolated stores (e.g. per-request in SSR)
7
+ Object.defineProperty(exports, "invalidate", { enumerable: true, get: function () { return act_js_1.invalidate; } });
8
+ Object.defineProperty(exports, "withStore", { enumerable: true, get: function () { return act_js_1.withStore; } });
9
+ // ─── Execution engine (for custom policy chains) ──────────────────────────────
10
+ var executor_js_1 = require("./core/executor.js");
11
+ Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return executor_js_1.execute; } });
12
+ Object.defineProperty(exports, "REQUIRES_SYNC_STORE", { enumerable: true, get: function () { return executor_js_1.REQUIRES_SYNC_STORE; } });
13
+ // ─── Stores ───────────────────────────────────────────────────────────────────
7
14
  var memory_js_1 = require("./stores/memory.js");
8
15
  Object.defineProperty(exports, "InMemoryStore", { enumerable: true, get: function () { return memory_js_1.InMemoryStore; } });
9
- // Exported so callers can instanceof-check against timeout failures
16
+ var base_js_1 = require("./stores/base.js");
17
+ Object.defineProperty(exports, "isSyncStore", { enumerable: true, get: function () { return base_js_1.isSyncStore; } });
18
+ Object.defineProperty(exports, "isAsyncStore", { enumerable: true, get: function () { return base_js_1.isAsyncStore; } });
19
+ // ─── Error classes ────────────────────────────────────────────────────────────
10
20
  var timeout_js_1 = require("./policies/timeout.js");
11
21
  Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return timeout_js_1.TimeoutError; } });
12
22
  Object.defineProperty(exports, "TotalTimeoutError", { enumerable: true, get: function () { return timeout_js_1.TotalTimeoutError; } });
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- export { act } from './core/act.js';
2
- export type { ActFn, ActResult, ActSuccess, ActFailure, ActSource, ActOptions, RetryOptions, TimeoutOptions, DedupeOptions, CacheOptions, StateStore, SyncStateStore, AsyncStateStore, } from './types/index.js';
1
+ export { act, invalidate, withStore, } from './core/act.js';
2
+ export type { ScopedActSync, ScopedActAsync, } from './core/act.js';
3
+ export { execute, REQUIRES_SYNC_STORE, } from './core/executor.js';
3
4
  export { InMemoryStore } from './stores/memory.js';
4
5
  export type { InMemoryStoreOptions } from './stores/memory.js';
5
- export { TimeoutError, TotalTimeoutError } from './policies/timeout.js';
6
+ export { isSyncStore, isAsyncStore, } from './stores/base.js';
7
+ export { TimeoutError, TotalTimeoutError, } from './policies/timeout.js';
8
+ export type { ActFn, ActResult, ActSuccess, ActFailure, ActSource, ActOptions, RetryOptions, TimeoutOptions, DedupeOptions, CacheOptions, PolicyApplier, PolicyContext, RunMeta, StateStore, // v1.0 alias for SyncStateStore (zero breakage)
9
+ SyncStateStore, AsyncStateStore, AnyStateStore, } from './types/index.js';
6
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAEnC,YAAY,EACV,KAAK,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,YAAY,EAEZ,UAAU,EAEV,cAAc,EACd,eAAe,GAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAG9D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,GAAG,EACH,UAAU,EACV,SAAS,GACV,MAAM,eAAe,CAAA;AAEtB,YAAY,EACV,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA;AAItB,OAAO,EACL,OAAO,EACP,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAE9D,OAAO,EACL,WAAW,EACX,YAAY,GACb,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EACL,YAAY,EACZ,iBAAiB,GAClB,MAAM,uBAAuB,CAAA;AAI9B,YAAY,EAEV,KAAK,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EAGV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,YAAY,EAGZ,aAAa,EACb,aAAa,EACb,OAAO,EAGP,UAAU,EAAS,gDAAgD;AACnE,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,10 @@
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 { TimeoutError, TotalTimeoutError, } from './policies/timeout.js';
6
10
  //# 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,YAAY,EACZ,iBAAiB,GAClB,MAAM,uBAAuB,CAAA"}
@@ -3,41 +3,105 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cachePolicy = cachePolicy;
4
4
  const base_js_1 = require("../stores/base.js");
5
5
  const NS = 'cache:';
6
+ const INFLIGHT_NS = '__inflight:cache:';
6
7
  /**
7
- * Short-circuits the entire downstream chain on a cache hit.
8
- * On a miss, runs fn and stores the result with TTL.
8
+ * Short-circuit the entire downstream chain on a cache hit.
9
+ * On a miss, run `fn` and store the result with TTL.
9
10
  *
10
- * Must be the OUTERMOST policy so a hit skips dedupe, timeout, and retry.
11
+ * # Single-flight (fixes cache stampede)
11
12
  *
12
- * Supports both SyncStateStore and AsyncStateStore. The async branch
13
- * introduces two await points (get + set) but has no correctness
14
- * requirement for same-tick execution worst case is a cache stampede
15
- * on a simultaneous miss, which is standard behaviour without locking.
13
+ * On a sync store, the policy also stores the in-flight Promise under a
14
+ * separate `__inflight:cache:<key>` slot. Concurrent callers that miss the
15
+ * cache but find an in-flight Promise join it instead of launching duplicate
16
+ * work. This is the same mechanism `dedupePolicy` uses, applied internally
17
+ * to cache misses so users don't need to combine `cache` + `dedupe` to
18
+ * avoid stampedes.
19
+ *
20
+ * On an async store, single-flight is not possible (the same sync-store
21
+ * constraint as dedupe applies). Stampedes are a known limitation — document
22
+ * and pair with dedupe at a higher layer if you need single-flight semantics.
23
+ *
24
+ * # Fail-open writes
25
+ *
26
+ * If `store.set()` throws (e.g. Redis transient error), we swallow the error
27
+ * and return the value anyway. The caller gets their result; the next call
28
+ * will simply re-run `fn` and try to cache again. Caching is an optimisation,
29
+ * not a correctness requirement.
30
+ *
31
+ * # Cache hit semantics
32
+ *
33
+ * On a cache hit, `meta.source` is set to `'cache'` and `meta.attempts` is
34
+ * set to `0` — no work was performed. (v1.1.0 reported `attempts: 1` on
35
+ * cache hits, which was inconsistent with the documented meaning of
36
+ * `attempts`. v1.1.5 fixes this.)
37
+ *
38
+ * Failures are NEVER cached. Only successful values are stored.
16
39
  */
17
40
  function cachePolicy(opts) {
18
- return (fn, ctx) => async () => {
41
+ return (fn, ctx) => async (signal) => {
19
42
  const key = NS + ctx.key;
43
+ const inflightKey = INFLIGHT_NS + ctx.key;
44
+ // ─── Sync store: fast path with single-flight ────────────────────────
20
45
  if ((0, base_js_1.isSyncStore)(ctx.store)) {
21
- // Fast path — synchronous store, no await needed
46
+ // 1. Cache hit?
22
47
  const hit = ctx.store.get(key);
23
48
  if (hit) {
24
49
  ctx.meta.source = 'cache';
50
+ ctx.meta.attempts = 0;
25
51
  return hit.value;
26
52
  }
27
- const value = await fn();
28
- ctx.store.set(key, { value }, opts.ttl);
29
- return value;
30
- }
31
- else {
32
- // Async store path
33
- const hit = await ctx.store.get(key);
34
- if (hit) {
35
- ctx.meta.source = 'cache';
36
- return hit.value;
53
+ // 2. In-flight single-flight hit? Join it.
54
+ const inflight = ctx.store.get(inflightKey);
55
+ if (inflight)
56
+ return inflight;
57
+ // 3. Originator: launch fn, cache on success (fail-open).
58
+ const promise = Promise.resolve(fn(signal)).then((value) => {
59
+ try {
60
+ ctx.store.set(key, { value }, opts.ttl);
61
+ }
62
+ catch {
63
+ // Fail-open: cache write failure should not surface to caller.
64
+ // The value is still valid; next call will just re-run fn.
65
+ }
66
+ return value;
67
+ }, (err) => { throw err; });
68
+ // Try to publish the in-flight promise so concurrent callers can
69
+ // join (single-flight). If store.set throws, single-flight is
70
+ // disabled for this call — concurrent callers will all run fn.
71
+ // That's still correct, just less efficient.
72
+ try {
73
+ ctx.store.set(inflightKey, promise);
74
+ // Cleanup the in-flight slot on settle. The `.catch(() => {})`
75
+ // suppresses the unhandled-rejection warning that would otherwise
76
+ // fire on the `.finally()` chain — the original `promise` (with
77
+ // its rejection) is returned to the caller, which has its own
78
+ // try/catch via act().
79
+ promise.finally(() => {
80
+ try {
81
+ ctx.store.delete(inflightKey);
82
+ }
83
+ catch { /* ignore */ }
84
+ }).catch(() => { });
85
+ }
86
+ catch {
87
+ // Single-flight unavailable; proceed without publishing.
37
88
  }
38
- const value = await fn();
89
+ return promise;
90
+ }
91
+ // ─── Async store: no single-flight (race window unavoidable) ─────────
92
+ const hit = await ctx.store.get(key);
93
+ if (hit) {
94
+ ctx.meta.source = 'cache';
95
+ ctx.meta.attempts = 0;
96
+ return hit.value;
97
+ }
98
+ const value = await fn(signal);
99
+ try {
39
100
  await ctx.store.set(key, { value }, opts.ttl);
40
- return value;
41
101
  }
102
+ catch {
103
+ // Fail-open: see sync path comment.
104
+ }
105
+ return value;
42
106
  };
43
107
  }
@@ -1,14 +1,36 @@
1
1
  import type { CacheOptions, PolicyApplier } from '../types/index.js';
2
2
  /**
3
- * Short-circuits the entire downstream chain on a cache hit.
4
- * On a miss, runs fn and stores the result with TTL.
3
+ * Short-circuit the entire downstream chain on a cache hit.
4
+ * On a miss, run `fn` and store the result with TTL.
5
5
  *
6
- * Must be the OUTERMOST policy so a hit skips dedupe, timeout, and retry.
6
+ * # Single-flight (fixes cache stampede)
7
7
  *
8
- * Supports both SyncStateStore and AsyncStateStore. The async branch
9
- * introduces two await points (get + set) but has no correctness
10
- * requirement for same-tick execution worst case is a cache stampede
11
- * on a simultaneous miss, which is standard behaviour without locking.
8
+ * On a sync store, the policy also stores the in-flight Promise under a
9
+ * separate `__inflight:cache:<key>` slot. Concurrent callers that miss the
10
+ * cache but find an in-flight Promise join it instead of launching duplicate
11
+ * work. This is the same mechanism `dedupePolicy` uses, applied internally
12
+ * to cache misses so users don't need to combine `cache` + `dedupe` to
13
+ * avoid stampedes.
14
+ *
15
+ * On an async store, single-flight is not possible (the same sync-store
16
+ * constraint as dedupe applies). Stampedes are a known limitation — document
17
+ * and pair with dedupe at a higher layer if you need single-flight semantics.
18
+ *
19
+ * # Fail-open writes
20
+ *
21
+ * If `store.set()` throws (e.g. Redis transient error), we swallow the error
22
+ * and return the value anyway. The caller gets their result; the next call
23
+ * will simply re-run `fn` and try to cache again. Caching is an optimisation,
24
+ * not a correctness requirement.
25
+ *
26
+ * # Cache hit semantics
27
+ *
28
+ * On a cache hit, `meta.source` is set to `'cache'` and `meta.attempts` is
29
+ * set to `0` — no work was performed. (v1.1.0 reported `attempts: 1` on
30
+ * cache hits, which was inconsistent with the documented meaning of
31
+ * `attempts`. v1.1.5 fixes this.)
32
+ *
33
+ * Failures are NEVER cached. Only successful values are stored.
12
34
  */
13
35
  export declare function cachePolicy<T>(opts: CacheOptions): PolicyApplier<T>;
14
36
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/policies/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,YAAY,EAAE,aAAa,EAAiB,MAAM,mBAAmB,CAAA;AAU1F;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CA6BnE"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/policies/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,YAAY,EAAE,aAAa,EAAiB,MAAM,mBAAmB,CAAA;AAY1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CAuEnE"}
@@ -1,41 +1,105 @@
1
1
  import { isSyncStore } from '../stores/base.js';
2
2
  const NS = 'cache:';
3
+ const INFLIGHT_NS = '__inflight:cache:';
3
4
  /**
4
- * Short-circuits the entire downstream chain on a cache hit.
5
- * On a miss, runs fn and stores the result with TTL.
5
+ * Short-circuit the entire downstream chain on a cache hit.
6
+ * On a miss, run `fn` and store the result with TTL.
6
7
  *
7
- * Must be the OUTERMOST policy so a hit skips dedupe, timeout, and retry.
8
+ * # Single-flight (fixes cache stampede)
8
9
  *
9
- * Supports both SyncStateStore and AsyncStateStore. The async branch
10
- * introduces two await points (get + set) but has no correctness
11
- * requirement for same-tick execution worst case is a cache stampede
12
- * on a simultaneous miss, which is standard behaviour without locking.
10
+ * On a sync store, the policy also stores the in-flight Promise under a
11
+ * separate `__inflight:cache:<key>` slot. Concurrent callers that miss the
12
+ * cache but find an in-flight Promise join it instead of launching duplicate
13
+ * work. This is the same mechanism `dedupePolicy` uses, applied internally
14
+ * to cache misses so users don't need to combine `cache` + `dedupe` to
15
+ * avoid stampedes.
16
+ *
17
+ * On an async store, single-flight is not possible (the same sync-store
18
+ * constraint as dedupe applies). Stampedes are a known limitation — document
19
+ * and pair with dedupe at a higher layer if you need single-flight semantics.
20
+ *
21
+ * # Fail-open writes
22
+ *
23
+ * If `store.set()` throws (e.g. Redis transient error), we swallow the error
24
+ * and return the value anyway. The caller gets their result; the next call
25
+ * will simply re-run `fn` and try to cache again. Caching is an optimisation,
26
+ * not a correctness requirement.
27
+ *
28
+ * # Cache hit semantics
29
+ *
30
+ * On a cache hit, `meta.source` is set to `'cache'` and `meta.attempts` is
31
+ * set to `0` — no work was performed. (v1.1.0 reported `attempts: 1` on
32
+ * cache hits, which was inconsistent with the documented meaning of
33
+ * `attempts`. v1.1.5 fixes this.)
34
+ *
35
+ * Failures are NEVER cached. Only successful values are stored.
13
36
  */
14
37
  export function cachePolicy(opts) {
15
- return (fn, ctx) => async () => {
38
+ return (fn, ctx) => async (signal) => {
16
39
  const key = NS + ctx.key;
40
+ const inflightKey = INFLIGHT_NS + ctx.key;
41
+ // ─── Sync store: fast path with single-flight ────────────────────────
17
42
  if (isSyncStore(ctx.store)) {
18
- // Fast path — synchronous store, no await needed
43
+ // 1. Cache hit?
19
44
  const hit = ctx.store.get(key);
20
45
  if (hit) {
21
46
  ctx.meta.source = 'cache';
47
+ ctx.meta.attempts = 0;
22
48
  return hit.value;
23
49
  }
24
- const value = await fn();
25
- ctx.store.set(key, { value }, opts.ttl);
26
- return value;
27
- }
28
- else {
29
- // Async store path
30
- const hit = await ctx.store.get(key);
31
- if (hit) {
32
- ctx.meta.source = 'cache';
33
- return hit.value;
50
+ // 2. In-flight single-flight hit? Join it.
51
+ const inflight = ctx.store.get(inflightKey);
52
+ if (inflight)
53
+ return inflight;
54
+ // 3. Originator: launch fn, cache on success (fail-open).
55
+ const promise = Promise.resolve(fn(signal)).then((value) => {
56
+ try {
57
+ ctx.store.set(key, { value }, opts.ttl);
58
+ }
59
+ catch {
60
+ // Fail-open: cache write failure should not surface to caller.
61
+ // The value is still valid; next call will just re-run fn.
62
+ }
63
+ return value;
64
+ }, (err) => { throw err; });
65
+ // Try to publish the in-flight promise so concurrent callers can
66
+ // join (single-flight). If store.set throws, single-flight is
67
+ // disabled for this call — concurrent callers will all run fn.
68
+ // That's still correct, just less efficient.
69
+ try {
70
+ ctx.store.set(inflightKey, promise);
71
+ // Cleanup the in-flight slot on settle. The `.catch(() => {})`
72
+ // suppresses the unhandled-rejection warning that would otherwise
73
+ // fire on the `.finally()` chain — the original `promise` (with
74
+ // its rejection) is returned to the caller, which has its own
75
+ // try/catch via act().
76
+ promise.finally(() => {
77
+ try {
78
+ ctx.store.delete(inflightKey);
79
+ }
80
+ catch { /* ignore */ }
81
+ }).catch(() => { });
82
+ }
83
+ catch {
84
+ // Single-flight unavailable; proceed without publishing.
34
85
  }
35
- const value = await fn();
86
+ return promise;
87
+ }
88
+ // ─── Async store: no single-flight (race window unavoidable) ─────────
89
+ const hit = await ctx.store.get(key);
90
+ if (hit) {
91
+ ctx.meta.source = 'cache';
92
+ ctx.meta.attempts = 0;
93
+ return hit.value;
94
+ }
95
+ const value = await fn(signal);
96
+ try {
36
97
  await ctx.store.set(key, { value }, opts.ttl);
37
- return value;
38
98
  }
99
+ catch {
100
+ // Fail-open: see sync path comment.
101
+ }
102
+ return value;
39
103
  };
40
104
  }
41
105
  //# sourceMappingURL=cache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/policies/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,MAAM,EAAE,GAAG,QAAQ,CAAA;AAOnB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAI,IAAkB;IAC/C,OAAO,CAAC,EAAY,EAAE,GAAkB,EAAY,EAAE,CACpD,KAAK,IAAI,EAAE;QACT,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC,GAAG,CAAA;QAExB,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,iDAAiD;YACjD,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAgB,GAAG,CAAC,CAAA;YAC7C,IAAI,GAAG,EAAE,CAAC;gBACR,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAA;gBACzB,OAAO,GAAG,CAAC,KAAK,CAAA;YAClB,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAA;YACxB,GAAG,CAAC,KAAK,CAAC,GAAG,CAAgB,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;YACtD,OAAO,KAAK,CAAA;QACd,CAAC;aAAM,CAAC;YACN,mBAAmB;YACnB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAgB,GAAG,CAAC,CAAA;YACnD,IAAI,GAAG,EAAE,CAAC;gBACR,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAA;gBACzB,OAAO,GAAG,CAAC,KAAK,CAAA;YAClB,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAA;YACxB,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAgB,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;YAC5D,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACL,CAAC"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/policies/cache.ts"],"names":[],"mappings":"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"}