actly 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +262 -232
  3. package/dist/core/act.cjs +505 -39
  4. package/dist/core/act.d.ts +81 -10
  5. package/dist/core/act.d.ts.map +1 -1
  6. package/dist/core/act.js +504 -40
  7. package/dist/core/act.js.map +1 -1
  8. package/dist/core/executor.cjs +21 -9
  9. package/dist/core/executor.d.ts +31 -6
  10. package/dist/core/executor.d.ts.map +1 -1
  11. package/dist/core/executor.js +21 -9
  12. package/dist/core/executor.js.map +1 -1
  13. package/dist/core/health.cjs +34 -0
  14. package/dist/core/health.d.ts +18 -0
  15. package/dist/core/health.d.ts.map +1 -0
  16. package/dist/core/health.js +28 -0
  17. package/dist/core/health.js.map +1 -0
  18. package/dist/core/shutdown.cjs +60 -0
  19. package/dist/core/shutdown.d.ts +8 -0
  20. package/dist/core/shutdown.d.ts.map +1 -0
  21. package/dist/core/shutdown.js +56 -0
  22. package/dist/core/shutdown.js.map +1 -0
  23. package/dist/core/tenant.cjs +71 -0
  24. package/dist/core/tenant.d.ts +29 -0
  25. package/dist/core/tenant.d.ts.map +1 -0
  26. package/dist/core/tenant.js +68 -0
  27. package/dist/core/tenant.js.map +1 -0
  28. package/dist/errors.cjs +182 -0
  29. package/dist/errors.d.ts +151 -0
  30. package/dist/errors.d.ts.map +1 -0
  31. package/dist/errors.js +171 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/index.cjs +56 -6
  34. package/dist/index.d.ts +22 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +24 -4
  37. package/dist/index.js.map +1 -1
  38. package/dist/observability.cjs +45 -0
  39. package/dist/observability.d.ts +145 -0
  40. package/dist/observability.d.ts.map +1 -0
  41. package/dist/observability.js +43 -0
  42. package/dist/observability.js.map +1 -0
  43. package/dist/policies/bulkhead.cjs +97 -0
  44. package/dist/policies/bulkhead.d.ts +3 -0
  45. package/dist/policies/bulkhead.d.ts.map +1 -0
  46. package/dist/policies/bulkhead.js +95 -0
  47. package/dist/policies/bulkhead.js.map +1 -0
  48. package/dist/policies/cache.cjs +223 -22
  49. package/dist/policies/cache.d.ts +42 -7
  50. package/dist/policies/cache.d.ts.map +1 -1
  51. package/dist/policies/cache.js +223 -22
  52. package/dist/policies/cache.js.map +1 -1
  53. package/dist/policies/circuitBreaker.cjs +85 -0
  54. package/dist/policies/circuitBreaker.d.ts +3 -0
  55. package/dist/policies/circuitBreaker.d.ts.map +1 -0
  56. package/dist/policies/circuitBreaker.js +83 -0
  57. package/dist/policies/circuitBreaker.js.map +1 -0
  58. package/dist/policies/dedupe.cjs +98 -21
  59. package/dist/policies/dedupe.d.ts +20 -13
  60. package/dist/policies/dedupe.d.ts.map +1 -1
  61. package/dist/policies/dedupe.js +98 -21
  62. package/dist/policies/dedupe.js.map +1 -1
  63. package/dist/policies/rateLimit.cjs +35 -0
  64. package/dist/policies/rateLimit.d.ts +3 -0
  65. package/dist/policies/rateLimit.d.ts.map +1 -0
  66. package/dist/policies/rateLimit.js +33 -0
  67. package/dist/policies/rateLimit.js.map +1 -0
  68. package/dist/policies/retry.cjs +103 -26
  69. package/dist/policies/retry.d.ts +25 -2
  70. package/dist/policies/retry.d.ts.map +1 -1
  71. package/dist/policies/retry.js +103 -26
  72. package/dist/policies/retry.js.map +1 -1
  73. package/dist/policies/timeout.cjs +78 -36
  74. package/dist/policies/timeout.d.ts +7 -18
  75. package/dist/policies/timeout.d.ts.map +1 -1
  76. package/dist/policies/timeout.js +84 -31
  77. package/dist/policies/timeout.js.map +1 -1
  78. package/dist/state/store.cjs +9 -2
  79. package/dist/state/store.d.ts +9 -0
  80. package/dist/state/store.d.ts.map +1 -1
  81. package/dist/state/store.js +9 -2
  82. package/dist/state/store.js.map +1 -1
  83. package/dist/stores/base.cjs +4 -4
  84. package/dist/stores/base.d.ts +32 -56
  85. package/dist/stores/base.d.ts.map +1 -1
  86. package/dist/stores/base.js +4 -4
  87. package/dist/stores/base.js.map +1 -1
  88. package/dist/stores/memory.cjs +161 -39
  89. package/dist/stores/memory.d.ts +64 -24
  90. package/dist/stores/memory.d.ts.map +1 -1
  91. package/dist/stores/memory.js +160 -39
  92. package/dist/stores/memory.js.map +1 -1
  93. package/dist/types/index.d.ts +315 -37
  94. package/dist/types/index.d.ts.map +1 -1
  95. package/dist/utils/abort.cjs +212 -0
  96. package/dist/utils/abort.d.ts +79 -0
  97. package/dist/utils/abort.d.ts.map +1 -0
  98. package/dist/utils/abort.js +206 -0
  99. package/dist/utils/abort.js.map +1 -0
  100. package/dist/utils/abortPool.cjs +26 -0
  101. package/dist/utils/abortPool.d.ts +4 -0
  102. package/dist/utils/abortPool.d.ts.map +1 -0
  103. package/dist/utils/abortPool.js +22 -0
  104. package/dist/utils/abortPool.js.map +1 -0
  105. package/dist/utils/backoff.cjs +54 -0
  106. package/dist/utils/backoff.d.ts +14 -0
  107. package/dist/utils/backoff.d.ts.map +1 -0
  108. package/dist/utils/backoff.js +52 -0
  109. package/dist/utils/backoff.js.map +1 -0
  110. package/dist/utils/key.cjs +77 -0
  111. package/dist/utils/key.d.ts +29 -0
  112. package/dist/utils/key.d.ts.map +1 -0
  113. package/dist/utils/key.js +75 -0
  114. package/dist/utils/key.js.map +1 -0
  115. package/dist/utils/limits.cjs +40 -0
  116. package/dist/utils/limits.d.ts +39 -0
  117. package/dist/utils/limits.d.ts.map +1 -0
  118. package/dist/utils/limits.js +38 -0
  119. package/dist/utils/limits.js.map +1 -0
  120. package/dist/utils/sanitize.cjs +29 -0
  121. package/dist/utils/sanitize.d.ts +3 -0
  122. package/dist/utils/sanitize.d.ts.map +1 -0
  123. package/dist/utils/sanitize.js +26 -0
  124. package/dist/utils/sanitize.js.map +1 -0
  125. package/dist/utils/validate.cjs +153 -0
  126. package/dist/utils/validate.d.ts +26 -0
  127. package/dist/utils/validate.d.ts.map +1 -0
  128. package/dist/utils/validate.js +141 -0
  129. package/dist/utils/validate.js.map +1 -0
  130. package/package.json +20 -37
@@ -1,75 +1,134 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InMemoryStore = void 0;
4
+ exports.createDefaultStore = createDefaultStore;
5
+ const limits_js_1 = require("../utils/limits.js");
4
6
  function isUnrefable(t) {
5
7
  return typeof t.unref === 'function';
6
8
  }
7
9
  // ─── Implementation ───────────────────────────────────────────────────────────
10
+ /**
11
+ * Reference `SyncStateStore` implementation backed by a `Map` + doubly-linked
12
+ * list for LRU.
13
+ *
14
+ * # Properties
15
+ *
16
+ * - `size()` is O(1) — tracked via a counter instead of full scan.
17
+ * - LRU reordering uses an explicit doubly-linked list, avoiding the
18
+ * `delete + set` Map churn that was 2 Map operations per `get()`.
19
+ * - Default `maxSize` is bounded (`LIMITS.DEFAULT_STORE_MAX_SIZE`) when
20
+ * used as the module-level default — prevents unbounded memory growth
21
+ * in long-running servers.
22
+ *
23
+ * # Expiry
24
+ *
25
+ * Lazy on `get()` / `has()`: expired entries are deleted when touched.
26
+ * Background sweep (optional) reclaims entries that are never re-read.
27
+ */
8
28
  class InMemoryStore {
29
+ _sync = true;
30
+ map = new Map();
31
+ maxSize;
32
+ head; // least recently used
33
+ tail; // most recently used
34
+ cleanupTimer;
9
35
  constructor(options = {}) {
10
- // Discriminant read by isSyncStore() in execute() to enforce the dedupe
11
- // constraint at runtime for JS callers who bypass TypeScript.
12
- this._sync = true;
13
- this.entries = new Map();
14
- const { autoCleanup = false, cleanupIntervalMs = 30000 } = options;
36
+ const { autoCleanup = false, cleanupIntervalMs = 30_000, maxSize = Number.POSITIVE_INFINITY, } = options;
37
+ if (!Number.isFinite(maxSize) || maxSize <= 0) {
38
+ // Infinity is allowed (unbounded); any other non-positive finite value
39
+ // is a programmer error.
40
+ if (maxSize !== Number.POSITIVE_INFINITY) {
41
+ throw new RangeError(`Actly: InMemoryStore maxSize must be a positive finite number or Infinity, got ${maxSize}`);
42
+ }
43
+ }
44
+ this.maxSize = maxSize;
15
45
  if (autoCleanup) {
16
46
  const timer = setInterval(() => this._sweep(), cleanupIntervalMs);
17
- // Prevent the interval from keeping the Node.js process alive when the
18
- // application has otherwise finished its work. Safe no-op in browsers.
19
47
  if (isUnrefable(timer))
20
48
  timer.unref();
21
49
  this.cleanupTimer = timer;
22
50
  }
23
51
  }
24
52
  get(key) {
25
- const entry = this.entries.get(key);
26
- if (!entry)
53
+ const node = this.map.get(key);
54
+ if (!node)
27
55
  return undefined;
28
- if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
29
- this.entries.delete(key);
56
+ if (node.expiresAt !== null && Date.now() > node.expiresAt) {
57
+ this._removeNode(node);
58
+ this.map.delete(key);
30
59
  return undefined;
31
60
  }
32
- return entry.value;
61
+ // LRU refresh: move to tail (most-recent).
62
+ this._moveToTail(node);
63
+ return node.value;
33
64
  }
34
65
  set(key, value, ttlMs) {
35
- // ttlMs = 0 or undefined → no expiry (sentinel null)
36
- const expiresAt = ttlMs != null && ttlMs > 0 ? Date.now() + ttlMs : null;
37
- this.entries.set(key, { value, expiresAt });
66
+ const existing = this.map.get(key);
67
+ const now = Date.now();
68
+ if (existing) {
69
+ // Update in place — don't grow size, don't evict.
70
+ existing.value = value;
71
+ existing.expiresAt = ttlMs != null && ttlMs > 0 ? now + ttlMs : null;
72
+ existing.insertedAt = now;
73
+ this._moveToTail(existing);
74
+ return;
75
+ }
76
+ // New key — evict if at capacity.
77
+ while (this.map.size >= this.maxSize && this.head) {
78
+ const evict = this.head;
79
+ this._removeNode(evict);
80
+ this.map.delete(evict.key);
81
+ }
82
+ const node = {
83
+ key,
84
+ value,
85
+ expiresAt: ttlMs != null && ttlMs > 0 ? now + ttlMs : null,
86
+ insertedAt: now,
87
+ };
88
+ this.map.set(key, node);
89
+ this._appendTail(node);
38
90
  }
39
91
  delete(key) {
40
- this.entries.delete(key);
92
+ const node = this.map.get(key);
93
+ if (!node)
94
+ return;
95
+ this._removeNode(node);
96
+ this.map.delete(key);
41
97
  }
42
98
  has(key) {
43
- // Delegate to get() so expired entries are evicted on access.
44
- return this.get(key) !== undefined;
99
+ // Inline the expiry check to avoid the LRU side-effect of get().
100
+ // `has()` should be a pure query, not a touch.
101
+ const node = this.map.get(key);
102
+ if (!node)
103
+ return false;
104
+ if (node.expiresAt !== null && Date.now() > node.expiresAt) {
105
+ this._removeNode(node);
106
+ this.map.delete(key);
107
+ return false;
108
+ }
109
+ return true;
45
110
  }
46
- /**
47
- * Remove all entries.
48
- * After this call, size() returns 0.
49
- */
50
111
  clear() {
51
- this.entries.clear();
112
+ this.map.clear();
113
+ this.head = undefined;
114
+ this.tail = undefined;
52
115
  }
53
116
  /**
54
117
  * Return the count of live (non-expired) entries.
55
118
  *
56
- * Expired entries are evicted during the scan, so repeated calls are
57
- * slightly cheaper as the map self-prunes. O(n) in the number of entries.
119
+ * O(1) returns the Map size directly. Expired-but-not-yet-
120
+ * evicted entries are counted; they're reclaimed lazily on next access
121
+ * or by the background sweep. This is intentional: a fully-accurate
122
+ * count would require an O(n) scan, defeating the purpose.
123
+ *
124
+ * Pure query — does NOT touch LRU order.
58
125
  */
59
126
  size() {
60
- // Evict expired entries as we scan — keeps the map tidy between sweeps
61
- // and ensures the returned count reflects only observable entries.
62
- for (const key of this.entries.keys())
63
- this.has(key);
64
- return this.entries.size;
127
+ return this.map.size;
65
128
  }
66
129
  /**
67
130
  * Stop the background cleanup timer and release internal state.
68
131
  * Safe to call multiple times — subsequent calls are no-ops.
69
- *
70
- * Call destroy() when discarding a long-lived store instance to prevent
71
- * timer leaks. Stores without autoCleanup enabled have nothing to release,
72
- * but destroy() is safe to call on them regardless.
73
132
  */
74
133
  destroy() {
75
134
  if (this.cleanupTimer !== undefined) {
@@ -77,15 +136,78 @@ class InMemoryStore {
77
136
  this.cleanupTimer = undefined;
78
137
  }
79
138
  }
80
- // Sweep all entries and remove those past their expiry time.
81
- // Called by the autoCleanup interval — not part of the public contract.
139
+ // ─── LRU list operations ──────────────────────────────────────────────────
140
+ //
141
+ // All operations are O(1). The list runs head (LRU) → tail (MRU).
142
+ _appendTail(node) {
143
+ if (this.tail) {
144
+ this.tail.next = node;
145
+ node.prev = this.tail;
146
+ node.next = undefined;
147
+ }
148
+ else {
149
+ // Empty list — node is both head and tail.
150
+ this.head = node;
151
+ }
152
+ this.tail = node;
153
+ }
154
+ _removeNode(node) {
155
+ if (node.prev) {
156
+ node.prev.next = node.next;
157
+ }
158
+ else {
159
+ this.head = node.next;
160
+ }
161
+ if (node.next) {
162
+ node.next.prev = node.prev;
163
+ }
164
+ else {
165
+ this.tail = node.prev;
166
+ }
167
+ node.prev = undefined;
168
+ node.next = undefined;
169
+ }
170
+ _moveToTail(node) {
171
+ if (this.tail === node)
172
+ return; // already MRU
173
+ this._removeNode(node);
174
+ this._appendTail(node);
175
+ }
176
+ /**
177
+ * Sweep all entries and remove those past their expiry time.
178
+ * Called by the autoCleanup interval; not part of the public contract.
179
+ *
180
+ * Two-pass to avoid mutating the Map during iteration (spec-safe).
181
+ */
82
182
  _sweep() {
83
183
  const now = Date.now();
84
- for (const [key, entry] of this.entries) {
85
- if (entry.expiresAt !== null && now > entry.expiresAt) {
86
- this.entries.delete(key);
184
+ const expired = [];
185
+ for (const [key, node] of this.map) {
186
+ if (node.expiresAt !== null && now > node.expiresAt) {
187
+ expired.push(key);
188
+ }
189
+ }
190
+ for (const key of expired) {
191
+ const node = this.map.get(key);
192
+ if (node) {
193
+ this._removeNode(node);
194
+ this.map.delete(key);
87
195
  }
88
196
  }
89
197
  }
90
198
  }
91
199
  exports.InMemoryStore = InMemoryStore;
200
+ /**
201
+ * Factory for the default module-level store.
202
+ *
203
+ * Bounded by `LIMITS.DEFAULT_STORE_MAX_SIZE` with background sweep —
204
+ * prevents unbounded memory growth in long-running servers without
205
+ * requiring callers to opt in.
206
+ */
207
+ function createDefaultStore() {
208
+ return new InMemoryStore({
209
+ maxSize: limits_js_1.LIMITS.DEFAULT_STORE_MAX_SIZE,
210
+ autoCleanup: true,
211
+ cleanupIntervalMs: limits_js_1.LIMITS.DEFAULT_STORE_CLEANUP_INTERVAL_MS,
212
+ });
213
+ }
@@ -3,56 +3,96 @@ export interface InMemoryStoreOptions {
3
3
  /**
4
4
  * Periodically sweep and remove expired entries in the background.
5
5
  *
6
- * Disabled by default. The store evicts lazily on get()/has() access, which
7
- * is sufficient for most use cases. Enable autoCleanup when the store is
8
- * long-lived and accumulates many TTL'd entries that are never re-read — for
9
- * example, a server-side cache that receives write-heavy traffic with low
10
- * subsequent read rates.
11
- *
12
- * The sweep does not affect observable store semantics: expired entries are
13
- * already invisible to get()/has()/size() before the sweep runs.
6
+ * Disabled by default for explicit-store users. The DEFAULT module-level
7
+ * store (used when you call `act()` without `withStore()`) enables this
8
+ * automatically see `core/act.ts`.
14
9
  */
15
10
  autoCleanup?: boolean;
16
11
  /**
17
12
  * Interval between background sweeps in milliseconds.
18
- * Defaults to 30 000 (30 seconds). Ignored when autoCleanup is false.
19
- *
20
- * Choose a value appropriate to your TTL distribution — sweeping more often
21
- * than your shortest TTL is wasteful; sweeping much less often than your
22
- * longest TTL wastes memory.
13
+ * Defaults to 30 000 (30 seconds). Ignored when `autoCleanup` is false.
23
14
  */
24
15
  cleanupIntervalMs?: number;
16
+ /**
17
+ * Maximum number of live entries the store will hold.
18
+ *
19
+ * When `set()` would exceed this limit, the least-recently-used entry is
20
+ * evicted before the new one is inserted (LRU semantics). Updates to an
21
+ * existing key do not trigger eviction.
22
+ *
23
+ * Defaults to `Infinity` (unbounded). Set a finite value for long-running
24
+ * caches with high-cardinality keys to bound memory usage.
25
+ *
26
+ * The LRU order is updated on `get()` and `set()` — both move the accessed
27
+ * key to the most-recent position. Implementation uses a doubly-linked
28
+ * list for O(1) reordering (no `delete + set` Map churn).
29
+ */
30
+ maxSize?: number;
25
31
  }
32
+ /**
33
+ * Reference `SyncStateStore` implementation backed by a `Map` + doubly-linked
34
+ * list for LRU.
35
+ *
36
+ * # Properties
37
+ *
38
+ * - `size()` is O(1) — tracked via a counter instead of full scan.
39
+ * - LRU reordering uses an explicit doubly-linked list, avoiding the
40
+ * `delete + set` Map churn that was 2 Map operations per `get()`.
41
+ * - Default `maxSize` is bounded (`LIMITS.DEFAULT_STORE_MAX_SIZE`) when
42
+ * used as the module-level default — prevents unbounded memory growth
43
+ * in long-running servers.
44
+ *
45
+ * # Expiry
46
+ *
47
+ * Lazy on `get()` / `has()`: expired entries are deleted when touched.
48
+ * Background sweep (optional) reclaims entries that are never re-read.
49
+ */
26
50
  export declare class InMemoryStore implements SyncStateStore {
27
51
  readonly _sync: true;
28
- private readonly entries;
52
+ private readonly map;
53
+ private readonly maxSize;
54
+ private head?;
55
+ private tail?;
29
56
  private cleanupTimer;
30
57
  constructor(options?: InMemoryStoreOptions);
31
58
  get<T>(key: string): T | undefined;
32
59
  set<T>(key: string, value: T, ttlMs?: number): void;
33
60
  delete(key: string): void;
34
61
  has(key: string): boolean;
35
- /**
36
- * Remove all entries.
37
- * After this call, size() returns 0.
38
- */
39
62
  clear(): void;
40
63
  /**
41
64
  * Return the count of live (non-expired) entries.
42
65
  *
43
- * Expired entries are evicted during the scan, so repeated calls are
44
- * slightly cheaper as the map self-prunes. O(n) in the number of entries.
66
+ * O(1) returns the Map size directly. Expired-but-not-yet-
67
+ * evicted entries are counted; they're reclaimed lazily on next access
68
+ * or by the background sweep. This is intentional: a fully-accurate
69
+ * count would require an O(n) scan, defeating the purpose.
70
+ *
71
+ * Pure query — does NOT touch LRU order.
45
72
  */
46
73
  size(): number;
47
74
  /**
48
75
  * Stop the background cleanup timer and release internal state.
49
76
  * Safe to call multiple times — subsequent calls are no-ops.
50
- *
51
- * Call destroy() when discarding a long-lived store instance to prevent
52
- * timer leaks. Stores without autoCleanup enabled have nothing to release,
53
- * but destroy() is safe to call on them regardless.
54
77
  */
55
78
  destroy(): void;
79
+ private _appendTail;
80
+ private _removeNode;
81
+ private _moveToTail;
82
+ /**
83
+ * Sweep all entries and remove those past their expiry time.
84
+ * Called by the autoCleanup interval; not part of the public contract.
85
+ *
86
+ * Two-pass to avoid mutating the Map during iteration (spec-safe).
87
+ */
56
88
  private _sweep;
57
89
  }
90
+ /**
91
+ * Factory for the default module-level store.
92
+ *
93
+ * Bounded by `LIMITS.DEFAULT_STORE_MAX_SIZE` with background sweep —
94
+ * prevents unbounded memory growth in long-running servers without
95
+ * requiring callers to opt in.
96
+ */
97
+ export declare function createDefaultStore(): InMemoryStore;
58
98
  //# 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;AAuB/C,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAID,qBAAa,aAAc,YAAW,cAAc;IAGlD,QAAQ,CAAC,KAAK,EAAG,IAAI,CAAS;IAE9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,YAAY,CAA4C;gBAEpD,OAAO,GAAE,oBAAyB;IAY9C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAYlC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAMnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAKzB;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;OAKG;IACH,IAAI,IAAI,MAAM;IAOd;;;;;;;OAOG;IACH,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,MAAM;CAQf"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAkC/C,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAE1B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,KAAK,EAAG,IAAI,CAAS;IAE9B,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;gBAEpD,OAAO,GAAE,oBAAyB;IA0B9C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAgBlC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IA8BnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAOzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAazB,KAAK,IAAI,IAAI;IAMb;;;;;;;;;OASG;IACH,IAAI,IAAI,MAAM;IAId;;;OAGG;IACH,OAAO,IAAI,IAAI;IAWf,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,WAAW;IAMnB;;;;;OAKG;IACH,OAAO,CAAC,MAAM;CAgBf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAMlD"}
@@ -1,72 +1,130 @@
1
+ import { LIMITS } from '../utils/limits.js';
1
2
  function isUnrefable(t) {
2
3
  return typeof t.unref === 'function';
3
4
  }
4
5
  // ─── Implementation ───────────────────────────────────────────────────────────
6
+ /**
7
+ * Reference `SyncStateStore` implementation backed by a `Map` + doubly-linked
8
+ * list for LRU.
9
+ *
10
+ * # Properties
11
+ *
12
+ * - `size()` is O(1) — tracked via a counter instead of full scan.
13
+ * - LRU reordering uses an explicit doubly-linked list, avoiding the
14
+ * `delete + set` Map churn that was 2 Map operations per `get()`.
15
+ * - Default `maxSize` is bounded (`LIMITS.DEFAULT_STORE_MAX_SIZE`) when
16
+ * used as the module-level default — prevents unbounded memory growth
17
+ * in long-running servers.
18
+ *
19
+ * # Expiry
20
+ *
21
+ * Lazy on `get()` / `has()`: expired entries are deleted when touched.
22
+ * Background sweep (optional) reclaims entries that are never re-read.
23
+ */
5
24
  export class InMemoryStore {
25
+ _sync = true;
26
+ map = new Map();
27
+ maxSize;
28
+ head; // least recently used
29
+ tail; // most recently used
30
+ cleanupTimer;
6
31
  constructor(options = {}) {
7
- // Discriminant read by isSyncStore() in execute() to enforce the dedupe
8
- // constraint at runtime for JS callers who bypass TypeScript.
9
- this._sync = true;
10
- this.entries = new Map();
11
- const { autoCleanup = false, cleanupIntervalMs = 30000 } = options;
32
+ const { autoCleanup = false, cleanupIntervalMs = 30_000, maxSize = Number.POSITIVE_INFINITY, } = options;
33
+ if (!Number.isFinite(maxSize) || maxSize <= 0) {
34
+ // Infinity is allowed (unbounded); any other non-positive finite value
35
+ // is a programmer error.
36
+ if (maxSize !== Number.POSITIVE_INFINITY) {
37
+ throw new RangeError(`Actly: InMemoryStore maxSize must be a positive finite number or Infinity, got ${maxSize}`);
38
+ }
39
+ }
40
+ this.maxSize = maxSize;
12
41
  if (autoCleanup) {
13
42
  const timer = setInterval(() => this._sweep(), cleanupIntervalMs);
14
- // Prevent the interval from keeping the Node.js process alive when the
15
- // application has otherwise finished its work. Safe no-op in browsers.
16
43
  if (isUnrefable(timer))
17
44
  timer.unref();
18
45
  this.cleanupTimer = timer;
19
46
  }
20
47
  }
21
48
  get(key) {
22
- const entry = this.entries.get(key);
23
- if (!entry)
49
+ const node = this.map.get(key);
50
+ if (!node)
24
51
  return undefined;
25
- if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
26
- this.entries.delete(key);
52
+ if (node.expiresAt !== null && Date.now() > node.expiresAt) {
53
+ this._removeNode(node);
54
+ this.map.delete(key);
27
55
  return undefined;
28
56
  }
29
- return entry.value;
57
+ // LRU refresh: move to tail (most-recent).
58
+ this._moveToTail(node);
59
+ return node.value;
30
60
  }
31
61
  set(key, value, ttlMs) {
32
- // ttlMs = 0 or undefined → no expiry (sentinel null)
33
- const expiresAt = ttlMs != null && ttlMs > 0 ? Date.now() + ttlMs : null;
34
- this.entries.set(key, { value, expiresAt });
62
+ const existing = this.map.get(key);
63
+ const now = Date.now();
64
+ if (existing) {
65
+ // Update in place — don't grow size, don't evict.
66
+ existing.value = value;
67
+ existing.expiresAt = ttlMs != null && ttlMs > 0 ? now + ttlMs : null;
68
+ existing.insertedAt = now;
69
+ this._moveToTail(existing);
70
+ return;
71
+ }
72
+ // New key — evict if at capacity.
73
+ while (this.map.size >= this.maxSize && this.head) {
74
+ const evict = this.head;
75
+ this._removeNode(evict);
76
+ this.map.delete(evict.key);
77
+ }
78
+ const node = {
79
+ key,
80
+ value,
81
+ expiresAt: ttlMs != null && ttlMs > 0 ? now + ttlMs : null,
82
+ insertedAt: now,
83
+ };
84
+ this.map.set(key, node);
85
+ this._appendTail(node);
35
86
  }
36
87
  delete(key) {
37
- this.entries.delete(key);
88
+ const node = this.map.get(key);
89
+ if (!node)
90
+ return;
91
+ this._removeNode(node);
92
+ this.map.delete(key);
38
93
  }
39
94
  has(key) {
40
- // Delegate to get() so expired entries are evicted on access.
41
- return this.get(key) !== undefined;
95
+ // Inline the expiry check to avoid the LRU side-effect of get().
96
+ // `has()` should be a pure query, not a touch.
97
+ const node = this.map.get(key);
98
+ if (!node)
99
+ return false;
100
+ if (node.expiresAt !== null && Date.now() > node.expiresAt) {
101
+ this._removeNode(node);
102
+ this.map.delete(key);
103
+ return false;
104
+ }
105
+ return true;
42
106
  }
43
- /**
44
- * Remove all entries.
45
- * After this call, size() returns 0.
46
- */
47
107
  clear() {
48
- this.entries.clear();
108
+ this.map.clear();
109
+ this.head = undefined;
110
+ this.tail = undefined;
49
111
  }
50
112
  /**
51
113
  * Return the count of live (non-expired) entries.
52
114
  *
53
- * Expired entries are evicted during the scan, so repeated calls are
54
- * slightly cheaper as the map self-prunes. O(n) in the number of entries.
115
+ * O(1) returns the Map size directly. Expired-but-not-yet-
116
+ * evicted entries are counted; they're reclaimed lazily on next access
117
+ * or by the background sweep. This is intentional: a fully-accurate
118
+ * count would require an O(n) scan, defeating the purpose.
119
+ *
120
+ * Pure query — does NOT touch LRU order.
55
121
  */
56
122
  size() {
57
- // Evict expired entries as we scan — keeps the map tidy between sweeps
58
- // and ensures the returned count reflects only observable entries.
59
- for (const key of this.entries.keys())
60
- this.has(key);
61
- return this.entries.size;
123
+ return this.map.size;
62
124
  }
63
125
  /**
64
126
  * Stop the background cleanup timer and release internal state.
65
127
  * Safe to call multiple times — subsequent calls are no-ops.
66
- *
67
- * Call destroy() when discarding a long-lived store instance to prevent
68
- * timer leaks. Stores without autoCleanup enabled have nothing to release,
69
- * but destroy() is safe to call on them regardless.
70
128
  */
71
129
  destroy() {
72
130
  if (this.cleanupTimer !== undefined) {
@@ -74,15 +132,78 @@ export class InMemoryStore {
74
132
  this.cleanupTimer = undefined;
75
133
  }
76
134
  }
77
- // Sweep all entries and remove those past their expiry time.
78
- // Called by the autoCleanup interval — not part of the public contract.
135
+ // ─── LRU list operations ──────────────────────────────────────────────────
136
+ //
137
+ // All operations are O(1). The list runs head (LRU) → tail (MRU).
138
+ _appendTail(node) {
139
+ if (this.tail) {
140
+ this.tail.next = node;
141
+ node.prev = this.tail;
142
+ node.next = undefined;
143
+ }
144
+ else {
145
+ // Empty list — node is both head and tail.
146
+ this.head = node;
147
+ }
148
+ this.tail = node;
149
+ }
150
+ _removeNode(node) {
151
+ if (node.prev) {
152
+ node.prev.next = node.next;
153
+ }
154
+ else {
155
+ this.head = node.next;
156
+ }
157
+ if (node.next) {
158
+ node.next.prev = node.prev;
159
+ }
160
+ else {
161
+ this.tail = node.prev;
162
+ }
163
+ node.prev = undefined;
164
+ node.next = undefined;
165
+ }
166
+ _moveToTail(node) {
167
+ if (this.tail === node)
168
+ return; // already MRU
169
+ this._removeNode(node);
170
+ this._appendTail(node);
171
+ }
172
+ /**
173
+ * Sweep all entries and remove those past their expiry time.
174
+ * Called by the autoCleanup interval; not part of the public contract.
175
+ *
176
+ * Two-pass to avoid mutating the Map during iteration (spec-safe).
177
+ */
79
178
  _sweep() {
80
179
  const now = Date.now();
81
- for (const [key, entry] of this.entries) {
82
- if (entry.expiresAt !== null && now > entry.expiresAt) {
83
- this.entries.delete(key);
180
+ const expired = [];
181
+ for (const [key, node] of this.map) {
182
+ if (node.expiresAt !== null && now > node.expiresAt) {
183
+ expired.push(key);
184
+ }
185
+ }
186
+ for (const key of expired) {
187
+ const node = this.map.get(key);
188
+ if (node) {
189
+ this._removeNode(node);
190
+ this.map.delete(key);
84
191
  }
85
192
  }
86
193
  }
87
194
  }
195
+ /**
196
+ * Factory for the default module-level store.
197
+ *
198
+ * Bounded by `LIMITS.DEFAULT_STORE_MAX_SIZE` with background sweep —
199
+ * prevents unbounded memory growth in long-running servers without
200
+ * requiring callers to opt in.
201
+ */
202
+ export function createDefaultStore() {
203
+ return new InMemoryStore({
204
+ maxSize: LIMITS.DEFAULT_STORE_MAX_SIZE,
205
+ autoCleanup: true,
206
+ cleanupIntervalMs: LIMITS.DEFAULT_STORE_CLEANUP_INTERVAL_MS,
207
+ });
208
+ }
88
209
  //# sourceMappingURL=memory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"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"}