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
package/dist/core/act.cjs CHANGED
@@ -1,34 +1,110 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.act = act;
4
+ exports.invalidate = invalidate;
5
+ exports.withStore = withStore;
4
6
  const executor_js_1 = require("./executor.js");
5
7
  const retry_js_1 = require("../policies/retry.js");
6
8
  const timeout_js_1 = require("../policies/timeout.js");
7
9
  const dedupe_js_1 = require("../policies/dedupe.js");
8
10
  const cache_js_1 = require("../policies/cache.js");
9
11
  const memory_js_1 = require("../stores/memory.js");
12
+ const base_js_1 = require("../stores/base.js");
13
+ const abort_js_1 = require("../utils/abort.js");
14
+ const validate_js_1 = require("../utils/validate.js");
10
15
  // Module-level default store so cache and dedupe persist across calls.
11
16
  // Always an InMemoryStore (SyncStateStore) — required because the default
12
17
  // chain may include dedupePolicy, which mandates synchronous store access.
13
- // For SSR isolation or per-test control, construct an InMemoryStore and
14
- // call execute() directly with an explicit store.
18
+ // For SSR isolation or per-test control, use `withStore()` or call
19
+ // `execute()` directly with an explicit store.
15
20
  const defaultStore = new memory_js_1.InMemoryStore();
21
+ // Namespace prefixes used by policies. Kept here (not in policy files) so
22
+ // `invalidate()` can resolve cache keys without importing policy internals.
23
+ const CACHE_NS = 'cache:';
16
24
  /**
17
- * Execute fn with the given reliability policies.
25
+ * Normalise `dedupe: true` shorthand to `DedupeOptions`.
26
+ * Returns `undefined` if dedupe is disabled or absent.
27
+ */
28
+ function normalizeDedupe(opt) {
29
+ if (opt === true)
30
+ return { enabled: true };
31
+ if (opt && typeof opt === 'object' && opt.enabled) {
32
+ return {
33
+ enabled: true,
34
+ ...(opt.inflightTtl !== undefined ? { inflightTtl: opt.inflightTtl } : {}),
35
+ };
36
+ }
37
+ return undefined;
38
+ }
39
+ /**
40
+ * Build the policy chain from `ActOptions`. The order is fixed and
41
+ * documented in `executor.ts`. Policies with no effect (e.g. `retry.attempts: 1`)
42
+ * are skipped — they would be pure overhead.
43
+ */
44
+ function buildPolicies(options) {
45
+ const dedupe = normalizeDedupe(options.dedupe);
46
+ const policies = [];
47
+ // 0. Outermost: hard wall-clock budget over the entire operation.
48
+ // If it fires, no inner policy can extend the deadline.
49
+ if (options.totalTimeout && options.totalTimeout.ms > 0) {
50
+ policies.push((0, timeout_js_1.totalTimeoutPolicy)(options.totalTimeout));
51
+ }
52
+ // 1. Cache: a hit short-circuits everything below it.
53
+ if (options.cache && options.cache.ttl > 0) {
54
+ policies.push((0, cache_js_1.cachePolicy)(options.cache));
55
+ }
56
+ // 2. Dedupe: collapses concurrent callers before retry fires.
57
+ if (dedupe) {
58
+ policies.push((0, dedupe_js_1.dedupePolicy)(dedupe));
59
+ }
60
+ // 3. Retry: owns the attempt loop.
61
+ // `attempts: 1` is a no-op — skip to avoid overhead.
62
+ if (options.retry && options.retry.attempts > 1) {
63
+ policies.push((0, retry_js_1.retryPolicy)(options.retry));
64
+ }
65
+ // 4. Innermost: per-attempt clock. Resets on every retry.
66
+ if (options.timeout && options.timeout.ms > 0) {
67
+ policies.push((0, timeout_js_1.timeoutPolicy)(options.timeout));
68
+ }
69
+ return policies;
70
+ }
71
+ /**
72
+ * Build a root AbortController from `options.signal`.
73
+ *
74
+ * - If no user signal: returns a fresh controller that never aborts unless
75
+ * an outer timeout policy aborts it.
76
+ * - If user signal is already aborted: returns a controller that is already
77
+ * aborted with the user's reason (so the operation rejects immediately).
78
+ * - Otherwise: links the user signal to the controller.
79
+ */
80
+ function buildRootSignal(userSignal) {
81
+ const controller = new AbortController();
82
+ if (userSignal)
83
+ (0, abort_js_1.linkSignal)(userSignal, controller);
84
+ return controller;
85
+ }
86
+ /**
87
+ * Execute `fn` with the given reliability policies.
18
88
  *
19
89
  * @param key Stable identifier for this action. Scopes dedupe + cache.
20
- * @param fn The async work to run.
90
+ * @param fn The async work to run. Receives an `AbortSignal` for
91
+ * cooperative cancellation (legacy `() => Promise<T>` is
92
+ * still accepted — the signal is simply ignored).
21
93
  * @param options Which policies to apply and how. All fields are optional.
22
94
  *
23
- * @returns ActResult<T> — always resolves, never throws.
24
- * Check result.ok before reading result.value.
95
+ * @returns `ActResult<T>` — always resolves, never throws.
96
+ * Check `result.ok` before reading `result.value`.
25
97
  *
26
98
  * @example
27
- * const result = await act('user:42', () => fetchUser(42), {
28
- * retry: { attempts: 3, delayMs: 200, backoff: 'exponential' },
29
- * timeout: { ms: 5_000 },
30
- * dedupe: true,
31
- * cache: { ttl: 60_000 },
99
+ * // With cooperative cancellation
100
+ * const result = await act('user:42', async (signal) => {
101
+ * return fetch(`/api/users/42`, { signal })
102
+ * }, {
103
+ * retry: { attempts: 3, delayMs: 200, backoff: 'exponential' },
104
+ * timeout: { ms: 5_000 },
105
+ * totalTimeout: { ms: 12_000 },
106
+ * dedupe: true,
107
+ * cache: { ttl: 60_000 },
32
108
  * })
33
109
  *
34
110
  * if (result.ok) {
@@ -38,36 +114,108 @@ const defaultStore = new memory_js_1.InMemoryStore();
38
114
  * }
39
115
  */
40
116
  async function act(key, fn, options = {}) {
117
+ // Validate input upfront. Programmer errors throw — they should not be
118
+ // swallowed into an ActFailure because the caller's code is broken.
119
+ (0, validate_js_1.assertKey)(key);
120
+ (0, validate_js_1.assertOptions)(options);
41
121
  const meta = { attempts: 1, source: 'fresh' };
42
- // Normalise dedupe: true → { enabled: true } so the rest of the function
43
- // always works with the object form.
44
- const dedupe = typeof options.dedupe === 'boolean'
45
- ? { enabled: options.dedupe }
46
- : options.dedupe;
47
- // Outermost -> innermost. See executor.ts for why this ordering matters.
48
- const policies = [];
49
- // totalTimeout sits before everything — it's a hard wall-clock budget over
50
- // the entire operation. If it fires, no inner policy can extend the deadline.
51
- if (options.totalTimeout && options.totalTimeout.ms > 0) {
52
- policies.push((0, timeout_js_1.totalTimeoutPolicy)(options.totalTimeout)); // 0. hardest outer wall
53
- }
54
- if (options.cache && options.cache.ttl > 0) {
55
- policies.push((0, cache_js_1.cachePolicy)(options.cache)); // 1. skip all on hit
56
- }
57
- if (dedupe?.enabled) {
58
- policies.push((0, dedupe_js_1.dedupePolicy)()); // 2. collapse concurrent callers
59
- }
60
- if (options.retry && options.retry.attempts > 1) {
61
- policies.push((0, retry_js_1.retryPolicy)(options.retry)); // 3. own the attempt loop
62
- }
63
- if (options.timeout && options.timeout.ms > 0) {
64
- policies.push((0, timeout_js_1.timeoutPolicy)(options.timeout)); // 4. innermost — per-attempt clock
122
+ const rootController = buildRootSignal(options.signal);
123
+ // Fast-fail if the user signal is already aborted. We do this after
124
+ // validation so the caller still gets a TypeError for bad options rather
125
+ // than a silent abort.
126
+ if (rootController.signal.aborted) {
127
+ return { ok: false, error: rootController.signal.reason, attempts: 0 };
65
128
  }
129
+ const policies = buildPolicies(options);
66
130
  try {
67
- const value = await (0, executor_js_1.execute)({ key, fn, policies, store: defaultStore, meta });
131
+ const value = await (0, executor_js_1.execute)({
132
+ key,
133
+ fn,
134
+ policies,
135
+ store: defaultStore,
136
+ meta,
137
+ signal: rootController.signal,
138
+ });
68
139
  return { ok: true, value, source: meta.source, attempts: meta.attempts };
69
140
  }
70
141
  catch (error) {
71
142
  return { ok: false, error, attempts: meta.attempts };
72
143
  }
73
144
  }
145
+ /**
146
+ * Invalidate the cached value for `key` on the default module-level store.
147
+ *
148
+ * Only clears the cache slot — does not affect in-flight dedupe entries
149
+ * (those will settle on their own). Returns `true` if a cache entry was
150
+ * removed, `false` otherwise.
151
+ *
152
+ * Useful when you know the underlying data has changed and you want the
153
+ * next `act()` call to re-run `fn` instead of serving stale cache:
154
+ *
155
+ * ```ts
156
+ * await act('user:42', () => fetchUser(42), { cache: { ttl: 60_000 } })
157
+ * // ... user updates their profile ...
158
+ * invalidate('user:42') // next call will re-fetch
159
+ * ```
160
+ */
161
+ function invalidate(key) {
162
+ const cacheKey = CACHE_NS + key;
163
+ const existed = defaultStore.has(cacheKey);
164
+ defaultStore.delete(cacheKey);
165
+ return existed;
166
+ }
167
+ function withStore(store) {
168
+ const scopedAct = async (key, fn, options = {}) => {
169
+ (0, validate_js_1.assertKey)(key);
170
+ (0, validate_js_1.assertOptions)(options);
171
+ const meta = { attempts: 1, source: 'fresh' };
172
+ const rootController = buildRootSignal(options.signal);
173
+ if (rootController.signal.aborted) {
174
+ return { ok: false, error: rootController.signal.reason, attempts: 0 };
175
+ }
176
+ const policies = buildPolicies(options);
177
+ try {
178
+ const value = await (0, executor_js_1.execute)({
179
+ key,
180
+ fn,
181
+ policies,
182
+ store,
183
+ meta,
184
+ signal: rootController.signal,
185
+ });
186
+ return { ok: true, value, source: meta.source, attempts: meta.attempts };
187
+ }
188
+ catch (error) {
189
+ return { ok: false, error, attempts: meta.attempts };
190
+ }
191
+ };
192
+ // Build the `invalidate` implementation. The runtime branch on
193
+ // `isSyncStore` selects the correct path; the cast through `unknown`
194
+ // is required because TypeScript cannot narrow the union return type
195
+ // (`boolean | Promise<boolean>`) to match either overload signature
196
+ // individually. The overloads at the call site guarantee callers see
197
+ // the correct type.
198
+ const invalidateImpl = (key) => {
199
+ (0, validate_js_1.assertKey)(key);
200
+ const cacheKey = CACHE_NS + key;
201
+ if ((0, base_js_1.isSyncStore)(store)) {
202
+ const existed = store.has(cacheKey);
203
+ store.delete(cacheKey);
204
+ return existed;
205
+ }
206
+ // Async store branch.
207
+ return (async () => {
208
+ const existed = await store.has(cacheKey);
209
+ await store.delete(cacheKey);
210
+ return existed;
211
+ })();
212
+ };
213
+ // Attach `invalidate` and `store` to the function object. We use
214
+ // `Object.assign` rather than mutation so the types narrow cleanly at
215
+ // the call site. The cast through `unknown` is necessary because the
216
+ // implementation signature is wider than either overload.
217
+ return Object.assign(scopedAct, {
218
+ invalidate: invalidateImpl,
219
+ store,
220
+ });
221
+ }
@@ -1,20 +1,26 @@
1
- import type { ActFn, ActOptions, ActResult } from '../types/index.js';
1
+ import type { ActFn, ActOptions, ActResult, AnyStateStore, SyncStateStore, AsyncStateStore } from '../types/index.js';
2
2
  /**
3
- * Execute fn with the given reliability policies.
3
+ * Execute `fn` with the given reliability policies.
4
4
  *
5
5
  * @param key Stable identifier for this action. Scopes dedupe + cache.
6
- * @param fn The async work to run.
6
+ * @param fn The async work to run. Receives an `AbortSignal` for
7
+ * cooperative cancellation (legacy `() => Promise<T>` is
8
+ * still accepted — the signal is simply ignored).
7
9
  * @param options Which policies to apply and how. All fields are optional.
8
10
  *
9
- * @returns ActResult<T> — always resolves, never throws.
10
- * Check result.ok before reading result.value.
11
+ * @returns `ActResult<T>` — always resolves, never throws.
12
+ * Check `result.ok` before reading `result.value`.
11
13
  *
12
14
  * @example
13
- * const result = await act('user:42', () => fetchUser(42), {
14
- * retry: { attempts: 3, delayMs: 200, backoff: 'exponential' },
15
- * timeout: { ms: 5_000 },
16
- * dedupe: true,
17
- * cache: { ttl: 60_000 },
15
+ * // With cooperative cancellation
16
+ * const result = await act('user:42', async (signal) => {
17
+ * return fetch(`/api/users/42`, { signal })
18
+ * }, {
19
+ * retry: { attempts: 3, delayMs: 200, backoff: 'exponential' },
20
+ * timeout: { ms: 5_000 },
21
+ * totalTimeout: { ms: 12_000 },
22
+ * dedupe: true,
23
+ * cache: { ttl: 60_000 },
18
24
  * })
19
25
  *
20
26
  * if (result.ok) {
@@ -24,4 +30,69 @@ import type { ActFn, ActOptions, ActResult } from '../types/index.js';
24
30
  * }
25
31
  */
26
32
  export declare function act<T>(key: string, fn: ActFn<T>, options?: ActOptions): Promise<ActResult<T>>;
33
+ /**
34
+ * Invalidate the cached value for `key` on the default module-level store.
35
+ *
36
+ * Only clears the cache slot — does not affect in-flight dedupe entries
37
+ * (those will settle on their own). Returns `true` if a cache entry was
38
+ * removed, `false` otherwise.
39
+ *
40
+ * Useful when you know the underlying data has changed and you want the
41
+ * next `act()` call to re-run `fn` instead of serving stale cache:
42
+ *
43
+ * ```ts
44
+ * await act('user:42', () => fetchUser(42), { cache: { ttl: 60_000 } })
45
+ * // ... user updates their profile ...
46
+ * invalidate('user:42') // next call will re-fetch
47
+ * ```
48
+ */
49
+ export declare function invalidate(key: string): boolean;
50
+ /** Result of `withStore()` for a sync store: `act` + sync `invalidate`. */
51
+ export interface ScopedActSync {
52
+ <T>(key: string, fn: ActFn<T>, options?: ActOptions): Promise<ActResult<T>>;
53
+ invalidate(key: string): boolean;
54
+ /** The store this scope is bound to. Useful for `store.destroy()` etc. */
55
+ readonly store: AnyStateStore;
56
+ }
57
+ /** Result of `withStore()` for an async store: `act` + async `invalidate`. */
58
+ export interface ScopedActAsync {
59
+ <T>(key: string, fn: ActFn<T>, options?: ActOptions): Promise<ActResult<T>>;
60
+ invalidate(key: string): Promise<boolean>;
61
+ readonly store: AnyStateStore;
62
+ }
63
+ /**
64
+ * Create a scoped `act` function bound to an explicit store.
65
+ *
66
+ * Use this for:
67
+ * - **SSR request isolation**: one store per request, no cross-request
68
+ * cache/dedupe leakage.
69
+ * - **Multi-tenant scenarios**: one store per tenant, no cross-tenant
70
+ * data leakage.
71
+ * - **Test isolation**: each test gets a fresh store, no shared state.
72
+ *
73
+ * The returned function has the same signature as `act()`. It also exposes
74
+ * an `invalidate(key)` method and a `store` reference for cleanup.
75
+ *
76
+ * For sync stores, `invalidate` returns `boolean` synchronously.
77
+ * For async stores, `invalidate` returns `Promise<boolean>`.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { withStore, InMemoryStore } from 'actly'
82
+ *
83
+ * const store = new InMemoryStore({ maxSize: 1000, autoCleanup: true })
84
+ * const act = withStore(store)
85
+ *
86
+ * try {
87
+ * await act('user:42', () => fetchUser(42), { cache: { ttl: 60_000 } })
88
+ * // ... user updates their profile ...
89
+ * act.invalidate('user:42') // next call re-fetches
90
+ * } finally {
91
+ * store.destroy()
92
+ * }
93
+ * ```
94
+ */
95
+ export declare function withStore(store: SyncStateStore): ScopedActSync;
96
+ export declare function withStore(store: AsyncStateStore): ScopedActAsync;
97
+ export declare function withStore(store: AnyStateStore): ScopedActSync | ScopedActAsync;
27
98
  //# sourceMappingURL=act.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../src/core/act.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAA0B,MAAM,mBAAmB,CAAA;AAe7F;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,GAAG,CAAC,CAAC,EACzB,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EACZ,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAwCvB"}
1
+ {"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../src/core/act.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,UAAU,EACV,SAAS,EAGT,aAAa,EACb,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAA;AAgG1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,GAAG,CAAC,CAAC,EACzB,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EACZ,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CA+BvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAK/C;AAID,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3E,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAC9B;AAED,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3E,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,aAAa,CAAA;AAC/D,wBAAgB,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,cAAc,CAAA;AACjE,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,GAAG,cAAc,CAAA"}
package/dist/core/act.js CHANGED
@@ -4,28 +4,102 @@ import { timeoutPolicy, totalTimeoutPolicy } from '../policies/timeout.js';
4
4
  import { dedupePolicy } from '../policies/dedupe.js';
5
5
  import { cachePolicy } from '../policies/cache.js';
6
6
  import { InMemoryStore } from '../stores/memory.js';
7
+ import { isSyncStore } from '../stores/base.js';
8
+ import { linkSignal } from '../utils/abort.js';
9
+ import { assertKey, assertOptions, } from '../utils/validate.js';
7
10
  // Module-level default store so cache and dedupe persist across calls.
8
11
  // Always an InMemoryStore (SyncStateStore) — required because the default
9
12
  // chain may include dedupePolicy, which mandates synchronous store access.
10
- // For SSR isolation or per-test control, construct an InMemoryStore and
11
- // call execute() directly with an explicit store.
13
+ // For SSR isolation or per-test control, use `withStore()` or call
14
+ // `execute()` directly with an explicit store.
12
15
  const defaultStore = new InMemoryStore();
16
+ // Namespace prefixes used by policies. Kept here (not in policy files) so
17
+ // `invalidate()` can resolve cache keys without importing policy internals.
18
+ const CACHE_NS = 'cache:';
13
19
  /**
14
- * Execute fn with the given reliability policies.
20
+ * Normalise `dedupe: true` shorthand to `DedupeOptions`.
21
+ * Returns `undefined` if dedupe is disabled or absent.
22
+ */
23
+ function normalizeDedupe(opt) {
24
+ if (opt === true)
25
+ return { enabled: true };
26
+ if (opt && typeof opt === 'object' && opt.enabled) {
27
+ return {
28
+ enabled: true,
29
+ ...(opt.inflightTtl !== undefined ? { inflightTtl: opt.inflightTtl } : {}),
30
+ };
31
+ }
32
+ return undefined;
33
+ }
34
+ /**
35
+ * Build the policy chain from `ActOptions`. The order is fixed and
36
+ * documented in `executor.ts`. Policies with no effect (e.g. `retry.attempts: 1`)
37
+ * are skipped — they would be pure overhead.
38
+ */
39
+ function buildPolicies(options) {
40
+ const dedupe = normalizeDedupe(options.dedupe);
41
+ const policies = [];
42
+ // 0. Outermost: hard wall-clock budget over the entire operation.
43
+ // If it fires, no inner policy can extend the deadline.
44
+ if (options.totalTimeout && options.totalTimeout.ms > 0) {
45
+ policies.push(totalTimeoutPolicy(options.totalTimeout));
46
+ }
47
+ // 1. Cache: a hit short-circuits everything below it.
48
+ if (options.cache && options.cache.ttl > 0) {
49
+ policies.push(cachePolicy(options.cache));
50
+ }
51
+ // 2. Dedupe: collapses concurrent callers before retry fires.
52
+ if (dedupe) {
53
+ policies.push(dedupePolicy(dedupe));
54
+ }
55
+ // 3. Retry: owns the attempt loop.
56
+ // `attempts: 1` is a no-op — skip to avoid overhead.
57
+ if (options.retry && options.retry.attempts > 1) {
58
+ policies.push(retryPolicy(options.retry));
59
+ }
60
+ // 4. Innermost: per-attempt clock. Resets on every retry.
61
+ if (options.timeout && options.timeout.ms > 0) {
62
+ policies.push(timeoutPolicy(options.timeout));
63
+ }
64
+ return policies;
65
+ }
66
+ /**
67
+ * Build a root AbortController from `options.signal`.
68
+ *
69
+ * - If no user signal: returns a fresh controller that never aborts unless
70
+ * an outer timeout policy aborts it.
71
+ * - If user signal is already aborted: returns a controller that is already
72
+ * aborted with the user's reason (so the operation rejects immediately).
73
+ * - Otherwise: links the user signal to the controller.
74
+ */
75
+ function buildRootSignal(userSignal) {
76
+ const controller = new AbortController();
77
+ if (userSignal)
78
+ linkSignal(userSignal, controller);
79
+ return controller;
80
+ }
81
+ /**
82
+ * Execute `fn` with the given reliability policies.
15
83
  *
16
84
  * @param key Stable identifier for this action. Scopes dedupe + cache.
17
- * @param fn The async work to run.
85
+ * @param fn The async work to run. Receives an `AbortSignal` for
86
+ * cooperative cancellation (legacy `() => Promise<T>` is
87
+ * still accepted — the signal is simply ignored).
18
88
  * @param options Which policies to apply and how. All fields are optional.
19
89
  *
20
- * @returns ActResult<T> — always resolves, never throws.
21
- * Check result.ok before reading result.value.
90
+ * @returns `ActResult<T>` — always resolves, never throws.
91
+ * Check `result.ok` before reading `result.value`.
22
92
  *
23
93
  * @example
24
- * const result = await act('user:42', () => fetchUser(42), {
25
- * retry: { attempts: 3, delayMs: 200, backoff: 'exponential' },
26
- * timeout: { ms: 5_000 },
27
- * dedupe: true,
28
- * cache: { ttl: 60_000 },
94
+ * // With cooperative cancellation
95
+ * const result = await act('user:42', async (signal) => {
96
+ * return fetch(`/api/users/42`, { signal })
97
+ * }, {
98
+ * retry: { attempts: 3, delayMs: 200, backoff: 'exponential' },
99
+ * timeout: { ms: 5_000 },
100
+ * totalTimeout: { ms: 12_000 },
101
+ * dedupe: true,
102
+ * cache: { ttl: 60_000 },
29
103
  * })
30
104
  *
31
105
  * if (result.ok) {
@@ -35,37 +109,109 @@ const defaultStore = new InMemoryStore();
35
109
  * }
36
110
  */
37
111
  export async function act(key, fn, options = {}) {
112
+ // Validate input upfront. Programmer errors throw — they should not be
113
+ // swallowed into an ActFailure because the caller's code is broken.
114
+ assertKey(key);
115
+ assertOptions(options);
38
116
  const meta = { attempts: 1, source: 'fresh' };
39
- // Normalise dedupe: true → { enabled: true } so the rest of the function
40
- // always works with the object form.
41
- const dedupe = typeof options.dedupe === 'boolean'
42
- ? { enabled: options.dedupe }
43
- : options.dedupe;
44
- // Outermost -> innermost. See executor.ts for why this ordering matters.
45
- const policies = [];
46
- // totalTimeout sits before everything — it's a hard wall-clock budget over
47
- // the entire operation. If it fires, no inner policy can extend the deadline.
48
- if (options.totalTimeout && options.totalTimeout.ms > 0) {
49
- policies.push(totalTimeoutPolicy(options.totalTimeout)); // 0. hardest outer wall
50
- }
51
- if (options.cache && options.cache.ttl > 0) {
52
- policies.push(cachePolicy(options.cache)); // 1. skip all on hit
53
- }
54
- if (dedupe?.enabled) {
55
- policies.push(dedupePolicy()); // 2. collapse concurrent callers
56
- }
57
- if (options.retry && options.retry.attempts > 1) {
58
- policies.push(retryPolicy(options.retry)); // 3. own the attempt loop
59
- }
60
- if (options.timeout && options.timeout.ms > 0) {
61
- policies.push(timeoutPolicy(options.timeout)); // 4. innermost — per-attempt clock
117
+ const rootController = buildRootSignal(options.signal);
118
+ // Fast-fail if the user signal is already aborted. We do this after
119
+ // validation so the caller still gets a TypeError for bad options rather
120
+ // than a silent abort.
121
+ if (rootController.signal.aborted) {
122
+ return { ok: false, error: rootController.signal.reason, attempts: 0 };
62
123
  }
124
+ const policies = buildPolicies(options);
63
125
  try {
64
- const value = await execute({ key, fn, policies, store: defaultStore, meta });
126
+ const value = await execute({
127
+ key,
128
+ fn,
129
+ policies,
130
+ store: defaultStore,
131
+ meta,
132
+ signal: rootController.signal,
133
+ });
65
134
  return { ok: true, value, source: meta.source, attempts: meta.attempts };
66
135
  }
67
136
  catch (error) {
68
137
  return { ok: false, error, attempts: meta.attempts };
69
138
  }
70
139
  }
140
+ /**
141
+ * Invalidate the cached value for `key` on the default module-level store.
142
+ *
143
+ * Only clears the cache slot — does not affect in-flight dedupe entries
144
+ * (those will settle on their own). Returns `true` if a cache entry was
145
+ * removed, `false` otherwise.
146
+ *
147
+ * Useful when you know the underlying data has changed and you want the
148
+ * next `act()` call to re-run `fn` instead of serving stale cache:
149
+ *
150
+ * ```ts
151
+ * await act('user:42', () => fetchUser(42), { cache: { ttl: 60_000 } })
152
+ * // ... user updates their profile ...
153
+ * invalidate('user:42') // next call will re-fetch
154
+ * ```
155
+ */
156
+ export function invalidate(key) {
157
+ const cacheKey = CACHE_NS + key;
158
+ const existed = defaultStore.has(cacheKey);
159
+ defaultStore.delete(cacheKey);
160
+ return existed;
161
+ }
162
+ export function withStore(store) {
163
+ const scopedAct = async (key, fn, options = {}) => {
164
+ assertKey(key);
165
+ assertOptions(options);
166
+ const meta = { attempts: 1, source: 'fresh' };
167
+ const rootController = buildRootSignal(options.signal);
168
+ if (rootController.signal.aborted) {
169
+ return { ok: false, error: rootController.signal.reason, attempts: 0 };
170
+ }
171
+ const policies = buildPolicies(options);
172
+ try {
173
+ const value = await execute({
174
+ key,
175
+ fn,
176
+ policies,
177
+ store,
178
+ meta,
179
+ signal: rootController.signal,
180
+ });
181
+ return { ok: true, value, source: meta.source, attempts: meta.attempts };
182
+ }
183
+ catch (error) {
184
+ return { ok: false, error, attempts: meta.attempts };
185
+ }
186
+ };
187
+ // Build the `invalidate` implementation. The runtime branch on
188
+ // `isSyncStore` selects the correct path; the cast through `unknown`
189
+ // is required because TypeScript cannot narrow the union return type
190
+ // (`boolean | Promise<boolean>`) to match either overload signature
191
+ // individually. The overloads at the call site guarantee callers see
192
+ // the correct type.
193
+ const invalidateImpl = (key) => {
194
+ assertKey(key);
195
+ const cacheKey = CACHE_NS + key;
196
+ if (isSyncStore(store)) {
197
+ const existed = store.has(cacheKey);
198
+ store.delete(cacheKey);
199
+ return existed;
200
+ }
201
+ // Async store branch.
202
+ return (async () => {
203
+ const existed = await store.has(cacheKey);
204
+ await store.delete(cacheKey);
205
+ return existed;
206
+ })();
207
+ };
208
+ // Attach `invalidate` and `store` to the function object. We use
209
+ // `Object.assign` rather than mutation so the types narrow cleanly at
210
+ // the call site. The cast through `unknown` is necessary because the
211
+ // implementation signature is wider than either overload.
212
+ return Object.assign(scopedAct, {
213
+ invalidate: invalidateImpl,
214
+ store,
215
+ });
216
+ }
71
217
  //# sourceMappingURL=act.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"act.js","sourceRoot":"","sources":["../../src/core/act.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAa,eAAe,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAS,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAQ,uBAAuB,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAS,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAO,qBAAqB,CAAA;AAEpD,uEAAuE;AACvE,0EAA0E;AAC1E,2EAA2E;AAC3E,wEAAwE;AACxE,kDAAkD;AAClD,MAAM,YAAY,GAAG,IAAI,aAAa,EAAE,CAAA;AAExC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,GAAW,EACX,EAAY,EACZ,UAAsB,EAAE;IAExB,MAAM,IAAI,GAAY,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;IAEtD,yEAAyE;IACzE,qCAAqC;IACrC,MAAM,MAAM,GAAG,OAAO,OAAO,CAAC,MAAM,KAAK,SAAS;QAChD,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE;QAC7B,CAAC,CAAC,OAAO,CAAC,MAAM,CAAA;IAElB,yEAAyE;IACzE,MAAM,QAAQ,GAA4B,EAAE,CAAA;IAE5C,2EAA2E;IAC3E,8EAA8E;IAC9E,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QACxD,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAI,OAAO,CAAC,YAAY,CAAC,CAAC,CAAA,CAAC,wBAAwB;IACrF,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA,CAAe,qBAAqB;IAClF,CAAC;IAED,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAK,CAAC,CAAA,CAA2B,iCAAiC;IAC9F,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA,CAAe,0BAA0B;IACvF,CAAC;IAED,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QAC9C,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA,CAAW,mCAAmC;IAChG,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAA;QAC7E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAA;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAA;IACtD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"act.js","sourceRoot":"","sources":["../../src/core/act.ts"],"names":[],"mappings":"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"}