actly 1.1.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/MIGRATION.md +141 -0
  3. package/README.md +334 -428
  4. package/SECURITY.md +56 -0
  5. package/dist/core/act.cjs +630 -126
  6. package/dist/core/act.d.ts +6 -84
  7. package/dist/core/act.d.ts.map +1 -1
  8. package/dist/core/act.js +630 -129
  9. package/dist/core/executor.cjs +1 -26
  10. package/dist/core/executor.d.ts +2 -38
  11. package/dist/core/executor.d.ts.map +1 -1
  12. package/dist/core/executor.js +1 -27
  13. package/dist/core/health.cjs +163 -0
  14. package/dist/core/health.d.ts +33 -0
  15. package/dist/core/health.d.ts.map +1 -0
  16. package/dist/core/health.js +150 -0
  17. package/dist/core/shutdown.cjs +71 -0
  18. package/dist/core/shutdown.d.ts +5 -0
  19. package/dist/core/shutdown.d.ts.map +1 -0
  20. package/dist/core/shutdown.js +65 -0
  21. package/dist/core/tenant.cjs +113 -0
  22. package/dist/core/tenant.d.ts +24 -0
  23. package/dist/core/tenant.d.ts.map +1 -0
  24. package/dist/core/tenant.js +109 -0
  25. package/dist/errors.cjs +149 -0
  26. package/dist/errors.d.ts +94 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +134 -0
  29. package/dist/index.cjs +51 -8
  30. package/dist/index.d.ts +22 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +14 -6
  33. package/dist/observability.cjs +6 -0
  34. package/dist/observability.d.ts +86 -0
  35. package/dist/observability.d.ts.map +1 -0
  36. package/dist/observability.js +3 -0
  37. package/dist/policies/bulkhead.cjs +137 -0
  38. package/dist/policies/bulkhead.d.ts +3 -0
  39. package/dist/policies/bulkhead.d.ts.map +1 -0
  40. package/dist/policies/bulkhead.js +134 -0
  41. package/dist/policies/cache.cjs +84 -67
  42. package/dist/policies/cache.d.ts +0 -33
  43. package/dist/policies/cache.d.ts.map +1 -1
  44. package/dist/policies/cache.js +84 -68
  45. package/dist/policies/circuitBreaker.cjs +179 -0
  46. package/dist/policies/circuitBreaker.d.ts +3 -0
  47. package/dist/policies/circuitBreaker.d.ts.map +1 -0
  48. package/dist/policies/circuitBreaker.js +176 -0
  49. package/dist/policies/dedupe.cjs +46 -64
  50. package/dist/policies/dedupe.d.ts +0 -41
  51. package/dist/policies/dedupe.d.ts.map +1 -1
  52. package/dist/policies/dedupe.js +46 -65
  53. package/dist/policies/noop.cjs +6 -0
  54. package/dist/policies/noop.d.ts +3 -0
  55. package/dist/policies/noop.d.ts.map +1 -0
  56. package/dist/policies/noop.js +3 -0
  57. package/dist/policies/rateLimit.cjs +45 -0
  58. package/dist/policies/rateLimit.d.ts +3 -0
  59. package/dist/policies/rateLimit.d.ts.map +1 -0
  60. package/dist/policies/rateLimit.js +42 -0
  61. package/dist/policies/retry.cjs +129 -57
  62. package/dist/policies/retry.d.ts +0 -27
  63. package/dist/policies/retry.d.ts.map +1 -1
  64. package/dist/policies/retry.js +129 -58
  65. package/dist/policies/timeout.cjs +47 -103
  66. package/dist/policies/timeout.d.ts +2 -46
  67. package/dist/policies/timeout.d.ts.map +1 -1
  68. package/dist/policies/timeout.js +46 -102
  69. package/dist/stores/base.cjs +0 -10
  70. package/dist/stores/base.d.ts +4 -67
  71. package/dist/stores/base.d.ts.map +1 -1
  72. package/dist/stores/base.js +0 -11
  73. package/dist/stores/memory.cjs +147 -87
  74. package/dist/stores/memory.d.ts +11 -59
  75. package/dist/stores/memory.d.ts.map +1 -1
  76. package/dist/stores/memory.js +146 -88
  77. package/dist/testing/index.cjs +56 -0
  78. package/dist/testing/index.d.ts +6 -0
  79. package/dist/testing/index.d.ts.map +1 -0
  80. package/dist/testing/index.js +52 -0
  81. package/dist/types/index.cjs +0 -1
  82. package/dist/types/index.d.ts +62 -179
  83. package/dist/types/index.d.ts.map +1 -1
  84. package/dist/types/index.js +0 -2
  85. package/dist/utils/abort.cjs +46 -73
  86. package/dist/utils/abort.d.ts +4 -51
  87. package/dist/utils/abort.d.ts.map +1 -1
  88. package/dist/utils/abort.js +46 -74
  89. package/dist/utils/abortPool.cjs +25 -0
  90. package/dist/utils/abortPool.d.ts +4 -0
  91. package/dist/utils/abortPool.d.ts.map +1 -0
  92. package/dist/utils/abortPool.js +20 -0
  93. package/dist/utils/backoff.cjs +8 -15
  94. package/dist/utils/backoff.d.ts +0 -11
  95. package/dist/utils/backoff.d.ts.map +1 -1
  96. package/dist/utils/backoff.js +8 -16
  97. package/dist/utils/decorator.cjs +44 -0
  98. package/dist/utils/decorator.d.ts +3 -0
  99. package/dist/utils/decorator.d.ts.map +1 -0
  100. package/dist/utils/decorator.js +41 -0
  101. package/dist/utils/key.cjs +45 -0
  102. package/dist/utils/key.d.ts +2 -0
  103. package/dist/utils/key.d.ts.map +1 -0
  104. package/dist/utils/key.js +42 -0
  105. package/dist/utils/limits.cjs +22 -0
  106. package/dist/utils/limits.d.ts +21 -0
  107. package/dist/utils/limits.d.ts.map +1 -0
  108. package/dist/utils/limits.js +19 -0
  109. package/dist/utils/safeCall.cjs +28 -0
  110. package/dist/utils/safeCall.d.ts +2 -0
  111. package/dist/utils/safeCall.d.ts.map +1 -0
  112. package/dist/utils/safeCall.js +25 -0
  113. package/dist/utils/sanitize.cjs +50 -0
  114. package/dist/utils/sanitize.d.ts +3 -0
  115. package/dist/utils/sanitize.d.ts.map +1 -0
  116. package/dist/utils/sanitize.js +46 -0
  117. package/dist/utils/validate.cjs +163 -26
  118. package/dist/utils/validate.d.ts +6 -8
  119. package/dist/utils/validate.d.ts.map +1 -1
  120. package/dist/utils/validate.js +158 -27
  121. package/package.json +42 -7
  122. package/dist/core/act.js.map +0 -1
  123. package/dist/core/executor.js.map +0 -1
  124. package/dist/index.js.map +0 -1
  125. package/dist/policies/cache.js.map +0 -1
  126. package/dist/policies/dedupe.js.map +0 -1
  127. package/dist/policies/retry.js.map +0 -1
  128. package/dist/policies/timeout.js.map +0 -1
  129. package/dist/state/store.cjs +0 -14
  130. package/dist/state/store.d.ts +0 -11
  131. package/dist/state/store.d.ts.map +0 -1
  132. package/dist/state/store.js +0 -11
  133. package/dist/state/store.js.map +0 -1
  134. package/dist/stores/base.js.map +0 -1
  135. package/dist/stores/memory.js.map +0 -1
  136. package/dist/types/index.js.map +0 -1
  137. package/dist/utils/abort.js.map +0 -1
  138. package/dist/utils/backoff.js.map +0 -1
  139. package/dist/utils/validate.js.map +0 -1
@@ -1,79 +1,31 @@
1
1
  import type { SyncStateStore } from './base.js';
2
2
  export interface InMemoryStoreOptions {
3
- /**
4
- * Periodically sweep and remove expired entries in the background.
5
- *
6
- * Disabled by default. The store evicts lazily on `get()` / `has()` access,
7
- * which is sufficient for most use cases. Enable `autoCleanup` when the
8
- * store is long-lived and accumulates many TTL'd entries that are never
9
- * re-read — for example, a server-side cache that receives write-heavy
10
- * traffic with low subsequent read rates.
11
- */
12
3
  autoCleanup?: boolean;
13
- /**
14
- * Interval between background sweeps in milliseconds.
15
- * Defaults to 30 000 (30 seconds). Ignored when `autoCleanup` is false.
16
- */
17
4
  cleanupIntervalMs?: number;
18
- /**
19
- * Maximum number of live entries the store will hold.
20
- *
21
- * When `set()` would exceed this limit, the least-recently-used entry is
22
- * evicted before the new one is inserted (LRU semantics). Updates to an
23
- * existing key do not trigger eviction.
24
- *
25
- * Defaults to `Infinity` (unbounded). Set a finite value for long-running
26
- * caches with high-cardinality keys to bound memory usage.
27
- *
28
- * The LRU order is updated on `get()` and `set()` — both move the accessed
29
- * key to the most-recent position.
30
- */
31
5
  maxSize?: number;
6
+ memoryPressureCleanup?: boolean;
32
7
  }
33
- /**
34
- * Reference `SyncStateStore` implementation backed by a `Map`.
35
- *
36
- * # LRU semantics
37
- *
38
- * `Map` iteration order is insertion order, so we implement LRU by
39
- * `delete` + `set` on every access — the most-recently-touched key ends up
40
- * at the end of the iteration, and the oldest is `entries.keys().next().value`.
41
- *
42
- * # Expiry
43
- *
44
- * Lazy on `get()` / `has()`: expired entries are deleted when touched.
45
- * Background sweep (optional) reclaims entries that are never re-read.
46
- */
47
8
  export declare class InMemoryStore implements SyncStateStore {
48
9
  readonly _sync: true;
49
- private readonly entries;
10
+ private static finalizer;
11
+ private readonly map;
50
12
  private readonly maxSize;
13
+ private head?;
14
+ private tail?;
51
15
  private cleanupTimer;
16
+ private memoryListener;
52
17
  constructor(options?: InMemoryStoreOptions);
53
18
  get<T>(key: string): T | undefined;
54
19
  set<T>(key: string, value: T, ttlMs?: number): void;
55
20
  delete(key: string): void;
56
21
  has(key: string): boolean;
57
22
  clear(): void;
58
- /**
59
- * Return the count of live (non-expired) entries.
60
- *
61
- * Pure query — does NOT touch LRU order. Expired entries discovered during
62
- * the scan are evicted opportunistically (they were already invisible to
63
- * `get()`, so eviction has no observable effect beyond memory reclamation).
64
- *
65
- * Two-pass to avoid mutating the Map during iteration (spec-safe).
66
- */
67
23
  size(): number;
68
- /**
69
- * Stop the background cleanup timer and release internal state.
70
- * Safe to call multiple times — subsequent calls are no-ops.
71
- */
72
24
  destroy(): void;
73
- /**
74
- * Sweep all entries and remove those past their expiry time.
75
- * Called by the autoCleanup interval; not part of the public contract.
76
- */
77
- private _sweep;
25
+ private appendTail;
26
+ private removeNode;
27
+ private moveToTail;
28
+ private sweep;
78
29
  }
30
+ export declare function createDefaultStore(): InMemoryStore;
79
31
  //# sourceMappingURL=memory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAwB/C,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAE1B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAID;;;;;;;;;;;;;GAaG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,KAAK,EAAG,IAAI,CAAS;IAE9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,YAAY,CAA4C;gBAEpD,OAAO,GAAE,oBAAyB;IA0B9C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAiBlC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAcnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAYzB,KAAK,IAAI,IAAI;IAIb;;;;;;;;OAQG;IACH,IAAI,IAAI,MAAM;IAiBd;;;OAGG;IACH,OAAO,IAAI,IAAI;IAOf;;;OAGG;IACH,OAAO,CAAC,MAAM;CAUf"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAgC/C,MAAM,WAAW,oBAAoB;IAKnC,WAAW,CAAC,EAAE,OAAO,CAAA;IAGrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAU1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAQhB,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAcD,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,KAAK,EAAG,IAAI,CAAS;IAS9B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAkE;IAS1F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,IAAI,CAAC,CAAS;IACtB,OAAO,CAAC,IAAI,CAAC,CAAS;IACtB,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,cAAc,CAA4C;gBAEtD,OAAO,GAAE,oBAAyB;IAkD9C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAclC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IA+BnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAOzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAYzB,KAAK,IAAI,IAAI;IAYb,IAAI,IAAI,MAAM;IAUd,OAAO,IAAI,IAAI;IAyBf,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,KAAK;CAoBd;AAOD,wBAAgB,kBAAkB,IAAI,aAAa,CAMlD"}
@@ -1,137 +1,195 @@
1
+ import { LIMITS } from '../utils/limits.js';
1
2
  function isUnrefable(t) {
2
3
  return typeof t.unref === 'function';
3
4
  }
4
- // ─── Implementation ───────────────────────────────────────────────────────────
5
- /**
6
- * Reference `SyncStateStore` implementation backed by a `Map`.
7
- *
8
- * # LRU semantics
9
- *
10
- * `Map` iteration order is insertion order, so we implement LRU by
11
- * `delete` + `set` on every access — the most-recently-touched key ends up
12
- * at the end of the iteration, and the oldest is `entries.keys().next().value`.
13
- *
14
- * # Expiry
15
- *
16
- * Lazy on `get()` / `has()`: expired entries are deleted when touched.
17
- * Background sweep (optional) reclaims entries that are never re-read.
18
- */
19
5
  export class InMemoryStore {
20
6
  _sync = true;
21
- entries = new Map();
7
+ static finalizer;
8
+ static {
9
+ if (typeof FinalizationRegistry === 'function') {
10
+ InMemoryStore.finalizer = new FinalizationRegistry((timer) => {
11
+ try {
12
+ clearInterval(timer);
13
+ }
14
+ catch { }
15
+ });
16
+ }
17
+ }
18
+ map = new Map();
22
19
  maxSize;
20
+ head;
21
+ tail;
23
22
  cleanupTimer;
23
+ memoryListener;
24
24
  constructor(options = {}) {
25
- const { autoCleanup = false, cleanupIntervalMs = 30_000, maxSize = Number.POSITIVE_INFINITY, } = options;
25
+ const { autoCleanup = false, cleanupIntervalMs = 30_000, maxSize = LIMITS.DEFAULT_STORE_MAX_SIZE, memoryPressureCleanup = false, } = options;
26
26
  if (!Number.isFinite(maxSize) || maxSize <= 0) {
27
- // Infinity is allowed (unbounded); any other non-positive finite value
28
- // is a programmer error.
29
27
  if (maxSize !== Number.POSITIVE_INFINITY) {
30
28
  throw new RangeError(`Actly: InMemoryStore maxSize must be a positive finite number or Infinity, got ${maxSize}`);
31
29
  }
32
30
  }
31
+ if (maxSize !== Number.POSITIVE_INFINITY && !Number.isInteger(maxSize)) {
32
+ throw new RangeError(`Actly: InMemoryStore maxSize must be a positive integer or Infinity, got ${maxSize}`);
33
+ }
33
34
  this.maxSize = maxSize;
34
35
  if (autoCleanup) {
35
- const timer = setInterval(() => this._sweep(), cleanupIntervalMs);
36
+ const timer = setInterval(() => this.sweep(), cleanupIntervalMs);
36
37
  if (isUnrefable(timer))
37
38
  timer.unref();
38
39
  this.cleanupTimer = timer;
40
+ InMemoryStore.finalizer?.register(this, timer, this);
41
+ }
42
+ if (memoryPressureCleanup) {
43
+ const processOn = process.on;
44
+ if (typeof processOn === 'function') {
45
+ const memoryListener = () => {
46
+ try {
47
+ this.sweep();
48
+ }
49
+ catch { }
50
+ };
51
+ processOn.call(process, 'memory', memoryListener);
52
+ this.memoryListener = memoryListener;
53
+ }
39
54
  }
40
55
  }
41
56
  get(key) {
42
- const entry = this.entries.get(key);
43
- if (!entry)
57
+ const node = this.map.get(key);
58
+ if (!node)
44
59
  return undefined;
45
- if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
46
- this.entries.delete(key);
60
+ if (node.expiresAt !== null && Date.now() > node.expiresAt) {
61
+ this.removeNode(node);
62
+ this.map.delete(key);
47
63
  return undefined;
48
64
  }
49
- // LRU refresh: move to most-recent position.
50
- // delete + set is the canonical pattern for reordering a Map.
51
- this.entries.delete(key);
52
- this.entries.set(key, entry);
53
- return entry.value;
65
+ this.moveToTail(node);
66
+ return node.value;
54
67
  }
55
68
  set(key, value, ttlMs) {
56
- // Evict if at capacity AND adding a new key (updates don't grow size).
57
- if (!this.entries.has(key) && this.entries.size >= this.maxSize) {
58
- const oldest = this.entries.keys().next().value;
59
- if (oldest !== undefined)
60
- this.entries.delete(oldest);
61
- }
62
- const expiresAt = ttlMs != null && ttlMs > 0 ? Date.now() + ttlMs : null;
63
- // delete + set ensures the key is moved to the most-recent position
64
- // even on update, keeping LRU order consistent.
65
- this.entries.delete(key);
66
- this.entries.set(key, { value, expiresAt });
69
+ const existing = this.map.get(key);
70
+ const now = Date.now();
71
+ if (existing) {
72
+ existing.value = value;
73
+ existing.expiresAt = ttlMs != null && Number.isFinite(ttlMs) && ttlMs > 0 ? now + ttlMs : null;
74
+ existing.insertedAt = now;
75
+ this.moveToTail(existing);
76
+ return;
77
+ }
78
+ while (this.map.size >= this.maxSize && this.head) {
79
+ const evict = this.head;
80
+ this.removeNode(evict);
81
+ this.map.delete(evict.key);
82
+ }
83
+ const node = {
84
+ key,
85
+ value,
86
+ expiresAt: ttlMs != null && Number.isFinite(ttlMs) && ttlMs > 0 ? now + ttlMs : null,
87
+ insertedAt: now,
88
+ };
89
+ this.map.set(key, node);
90
+ this.appendTail(node);
67
91
  }
68
92
  delete(key) {
69
- this.entries.delete(key);
93
+ const node = this.map.get(key);
94
+ if (!node)
95
+ return;
96
+ this.removeNode(node);
97
+ this.map.delete(key);
70
98
  }
71
99
  has(key) {
72
- // Inline the expiry check to avoid the LRU side-effect of get().
73
- // `has()` should be a pure query, not a touch.
74
- const entry = this.entries.get(key);
75
- if (!entry)
100
+ const node = this.map.get(key);
101
+ if (!node)
76
102
  return false;
77
- if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
78
- this.entries.delete(key);
103
+ if (node.expiresAt !== null && Date.now() > node.expiresAt) {
104
+ this.removeNode(node);
105
+ this.map.delete(key);
79
106
  return false;
80
107
  }
81
108
  return true;
82
109
  }
83
110
  clear() {
84
- this.entries.clear();
111
+ this.map.clear();
112
+ this.head = undefined;
113
+ this.tail = undefined;
85
114
  }
86
- /**
87
- * Return the count of live (non-expired) entries.
88
- *
89
- * Pure query — does NOT touch LRU order. Expired entries discovered during
90
- * the scan are evicted opportunistically (they were already invisible to
91
- * `get()`, so eviction has no observable effect beyond memory reclamation).
92
- *
93
- * Two-pass to avoid mutating the Map during iteration (spec-safe).
94
- */
95
115
  size() {
96
- const now = Date.now();
97
- const expired = [];
98
- let count = 0;
99
- for (const [key, entry] of this.entries) {
100
- if (entry.expiresAt !== null && entry.expiresAt <= now) {
101
- expired.push(key);
102
- }
103
- else {
104
- count++;
105
- }
106
- }
107
- for (const key of expired)
108
- this.entries.delete(key);
109
- return count;
116
+ return this.map.size;
110
117
  }
111
- /**
112
- * Stop the background cleanup timer and release internal state.
113
- * Safe to call multiple times — subsequent calls are no-ops.
114
- */
115
118
  destroy() {
116
119
  if (this.cleanupTimer !== undefined) {
117
120
  clearInterval(this.cleanupTimer);
118
121
  this.cleanupTimer = undefined;
122
+ InMemoryStore.finalizer?.unregister(this);
123
+ }
124
+ if (this.memoryListener) {
125
+ const processOff = process.off;
126
+ if (typeof processOff === 'function') {
127
+ processOff.call(process, 'memory', this.memoryListener);
128
+ }
129
+ this.memoryListener = undefined;
119
130
  }
131
+ this.map.clear();
132
+ this.head = undefined;
133
+ this.tail = undefined;
120
134
  }
121
- /**
122
- * Sweep all entries and remove those past their expiry time.
123
- * Called by the autoCleanup interval; not part of the public contract.
124
- */
125
- _sweep() {
126
- const now = Date.now();
127
- const expired = [];
128
- for (const [key, entry] of this.entries) {
129
- if (entry.expiresAt !== null && now > entry.expiresAt) {
130
- expired.push(key);
135
+ appendTail(node) {
136
+ if (this.tail) {
137
+ this.tail.next = node;
138
+ node.prev = this.tail;
139
+ node.next = undefined;
140
+ }
141
+ else {
142
+ this.head = node;
143
+ }
144
+ this.tail = node;
145
+ }
146
+ removeNode(node) {
147
+ if (node.prev) {
148
+ node.prev.next = node.next;
149
+ }
150
+ else {
151
+ this.head = node.next;
152
+ }
153
+ if (node.next) {
154
+ node.next.prev = node.prev;
155
+ }
156
+ else {
157
+ this.tail = node.prev;
158
+ }
159
+ node.prev = undefined;
160
+ node.next = undefined;
161
+ }
162
+ moveToTail(node) {
163
+ if (this.tail === node)
164
+ return;
165
+ this.removeNode(node);
166
+ this.appendTail(node);
167
+ }
168
+ sweep() {
169
+ try {
170
+ const now = Date.now();
171
+ const expired = [];
172
+ for (const [key, node] of this.map) {
173
+ if (node.expiresAt !== null && now > node.expiresAt) {
174
+ expired.push(key);
175
+ }
131
176
  }
177
+ for (const key of expired) {
178
+ const node = this.map.get(key);
179
+ if (node) {
180
+ this.removeNode(node);
181
+ this.map.delete(key);
182
+ }
183
+ }
184
+ }
185
+ catch {
132
186
  }
133
- for (const key of expired)
134
- this.entries.delete(key);
135
187
  }
136
188
  }
137
- //# sourceMappingURL=memory.js.map
189
+ export function createDefaultStore() {
190
+ return new InMemoryStore({
191
+ maxSize: LIMITS.DEFAULT_STORE_MAX_SIZE,
192
+ autoCleanup: true,
193
+ cleanupIntervalMs: LIMITS.DEFAULT_STORE_CLEANUP_INTERVAL_MS,
194
+ });
195
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.waitForObsHook = waitForObsHook;
4
+ exports.isActlyEventType = isActlyEventType;
5
+ function waitForObsHook(hooks, hookName, timeoutMs = 5000) {
6
+ let resolveFn;
7
+ let rejectFn;
8
+ const promise = new Promise((resolve, reject) => {
9
+ resolveFn = resolve;
10
+ rejectFn = reject;
11
+ });
12
+ let settled = false;
13
+ const timer = setTimeout(() => {
14
+ if (settled)
15
+ return;
16
+ settled = true;
17
+ hooks[hookName] = previousHook;
18
+ rejectFn(new Error(`Actly: waitForObsHook timed out after ${timeoutMs}ms waiting for "${String(hookName)}".`));
19
+ }, timeoutMs);
20
+ const unrefable = timer;
21
+ if (typeof unrefable.unref === 'function')
22
+ unrefable.unref();
23
+ const previousHook = hooks[hookName];
24
+ const cleanup = () => {
25
+ if (settled)
26
+ return;
27
+ settled = true;
28
+ clearTimeout(timer);
29
+ hooks[hookName] = previousHook;
30
+ };
31
+ hooks[hookName] = (event) => {
32
+ if (typeof previousHook === 'function') {
33
+ try {
34
+ previousHook(event);
35
+ }
36
+ catch { }
37
+ }
38
+ if (settled)
39
+ return;
40
+ cleanup();
41
+ resolveFn(event);
42
+ };
43
+ return Object.assign(promise, { cancel: cleanup });
44
+ }
45
+ function isActlyEventType(value) {
46
+ return (value === 'attempt' ||
47
+ value === 'retry' ||
48
+ value === 'cache-hit' ||
49
+ value === 'cache-miss' ||
50
+ value === 'dedupe-join' ||
51
+ value === 'timeout' ||
52
+ value === 'final-success' ||
53
+ value === 'final-failure' ||
54
+ value === 'backpressure' ||
55
+ value === 'watchdog');
56
+ }
@@ -0,0 +1,6 @@
1
+ import type { ObservabilityHooks, ActlyEventType } from '../observability.js';
2
+ export declare function waitForObsHook<K extends keyof ObservabilityHooks>(hooks: ObservabilityHooks, hookName: K, timeoutMs?: number): Promise<Parameters<NonNullable<ObservabilityHooks[K]>>[0]> & {
3
+ cancel: () => void;
4
+ };
5
+ export declare function isActlyEventType(value: unknown): value is ActlyEventType;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAyB7E,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,kBAAkB,EAC/D,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,CAAC,EACX,SAAS,SAAO,GACf,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,CA+CrF;AAMD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAaxE"}
@@ -0,0 +1,52 @@
1
+ export function waitForObsHook(hooks, hookName, timeoutMs = 5000) {
2
+ let resolveFn;
3
+ let rejectFn;
4
+ const promise = new Promise((resolve, reject) => {
5
+ resolveFn = resolve;
6
+ rejectFn = reject;
7
+ });
8
+ let settled = false;
9
+ const timer = setTimeout(() => {
10
+ if (settled)
11
+ return;
12
+ settled = true;
13
+ hooks[hookName] = previousHook;
14
+ rejectFn(new Error(`Actly: waitForObsHook timed out after ${timeoutMs}ms waiting for "${String(hookName)}".`));
15
+ }, timeoutMs);
16
+ const unrefable = timer;
17
+ if (typeof unrefable.unref === 'function')
18
+ unrefable.unref();
19
+ const previousHook = hooks[hookName];
20
+ const cleanup = () => {
21
+ if (settled)
22
+ return;
23
+ settled = true;
24
+ clearTimeout(timer);
25
+ hooks[hookName] = previousHook;
26
+ };
27
+ hooks[hookName] = (event) => {
28
+ if (typeof previousHook === 'function') {
29
+ try {
30
+ previousHook(event);
31
+ }
32
+ catch { }
33
+ }
34
+ if (settled)
35
+ return;
36
+ cleanup();
37
+ resolveFn(event);
38
+ };
39
+ return Object.assign(promise, { cancel: cleanup });
40
+ }
41
+ export function isActlyEventType(value) {
42
+ return (value === 'attempt' ||
43
+ value === 'retry' ||
44
+ value === 'cache-hit' ||
45
+ value === 'cache-miss' ||
46
+ value === 'dedupe-join' ||
47
+ value === 'timeout' ||
48
+ value === 'final-success' ||
49
+ value === 'final-failure' ||
50
+ value === 'backpressure' ||
51
+ value === 'watchdog');
52
+ }
@@ -1,3 +1,2 @@
1
1
  "use strict";
2
- // ─── Public surface ────────────────────────────────────────────────────────────
3
2
  Object.defineProperty(exports, "__esModule", { value: true });