actly 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +262 -232
- package/dist/core/act.cjs +505 -39
- package/dist/core/act.d.ts +81 -10
- package/dist/core/act.d.ts.map +1 -1
- package/dist/core/act.js +504 -40
- package/dist/core/act.js.map +1 -1
- package/dist/core/executor.cjs +21 -9
- package/dist/core/executor.d.ts +31 -6
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +21 -9
- package/dist/core/executor.js.map +1 -1
- package/dist/core/health.cjs +34 -0
- package/dist/core/health.d.ts +18 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +28 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/shutdown.cjs +60 -0
- package/dist/core/shutdown.d.ts +8 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +56 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/tenant.cjs +71 -0
- package/dist/core/tenant.d.ts +29 -0
- package/dist/core/tenant.d.ts.map +1 -0
- package/dist/core/tenant.js +68 -0
- package/dist/core/tenant.js.map +1 -0
- package/dist/errors.cjs +182 -0
- package/dist/errors.d.ts +151 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +171 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +56 -6
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/dist/observability.cjs +45 -0
- package/dist/observability.d.ts +145 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +43 -0
- package/dist/observability.js.map +1 -0
- package/dist/policies/bulkhead.cjs +97 -0
- package/dist/policies/bulkhead.d.ts +3 -0
- package/dist/policies/bulkhead.d.ts.map +1 -0
- package/dist/policies/bulkhead.js +95 -0
- package/dist/policies/bulkhead.js.map +1 -0
- package/dist/policies/cache.cjs +223 -22
- package/dist/policies/cache.d.ts +42 -7
- package/dist/policies/cache.d.ts.map +1 -1
- package/dist/policies/cache.js +223 -22
- package/dist/policies/cache.js.map +1 -1
- package/dist/policies/circuitBreaker.cjs +85 -0
- package/dist/policies/circuitBreaker.d.ts +3 -0
- package/dist/policies/circuitBreaker.d.ts.map +1 -0
- package/dist/policies/circuitBreaker.js +83 -0
- package/dist/policies/circuitBreaker.js.map +1 -0
- package/dist/policies/dedupe.cjs +98 -21
- package/dist/policies/dedupe.d.ts +20 -13
- package/dist/policies/dedupe.d.ts.map +1 -1
- package/dist/policies/dedupe.js +98 -21
- package/dist/policies/dedupe.js.map +1 -1
- package/dist/policies/rateLimit.cjs +35 -0
- package/dist/policies/rateLimit.d.ts +3 -0
- package/dist/policies/rateLimit.d.ts.map +1 -0
- package/dist/policies/rateLimit.js +33 -0
- package/dist/policies/rateLimit.js.map +1 -0
- package/dist/policies/retry.cjs +103 -26
- package/dist/policies/retry.d.ts +25 -2
- package/dist/policies/retry.d.ts.map +1 -1
- package/dist/policies/retry.js +103 -26
- package/dist/policies/retry.js.map +1 -1
- package/dist/policies/timeout.cjs +78 -36
- package/dist/policies/timeout.d.ts +7 -18
- package/dist/policies/timeout.d.ts.map +1 -1
- package/dist/policies/timeout.js +84 -31
- package/dist/policies/timeout.js.map +1 -1
- package/dist/state/store.cjs +9 -2
- package/dist/state/store.d.ts +9 -0
- package/dist/state/store.d.ts.map +1 -1
- package/dist/state/store.js +9 -2
- package/dist/state/store.js.map +1 -1
- package/dist/stores/base.cjs +4 -4
- package/dist/stores/base.d.ts +32 -56
- package/dist/stores/base.d.ts.map +1 -1
- package/dist/stores/base.js +4 -4
- package/dist/stores/base.js.map +1 -1
- package/dist/stores/memory.cjs +161 -39
- package/dist/stores/memory.d.ts +64 -24
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +160 -39
- package/dist/stores/memory.js.map +1 -1
- package/dist/types/index.d.ts +315 -37
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/abort.cjs +212 -0
- package/dist/utils/abort.d.ts +79 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +206 -0
- package/dist/utils/abort.js.map +1 -0
- package/dist/utils/abortPool.cjs +26 -0
- package/dist/utils/abortPool.d.ts +4 -0
- package/dist/utils/abortPool.d.ts.map +1 -0
- package/dist/utils/abortPool.js +22 -0
- package/dist/utils/abortPool.js.map +1 -0
- package/dist/utils/backoff.cjs +54 -0
- package/dist/utils/backoff.d.ts +14 -0
- package/dist/utils/backoff.d.ts.map +1 -0
- package/dist/utils/backoff.js +52 -0
- package/dist/utils/backoff.js.map +1 -0
- package/dist/utils/key.cjs +77 -0
- package/dist/utils/key.d.ts +29 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +75 -0
- package/dist/utils/key.js.map +1 -0
- package/dist/utils/limits.cjs +40 -0
- package/dist/utils/limits.d.ts +39 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +38 -0
- package/dist/utils/limits.js.map +1 -0
- package/dist/utils/sanitize.cjs +29 -0
- package/dist/utils/sanitize.d.ts +3 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +26 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/validate.cjs +153 -0
- package/dist/utils/validate.d.ts +26 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +141 -0
- package/dist/utils/validate.js.map +1 -0
- package/package.json +20 -37
package/dist/stores/memory.cjs
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
26
|
-
if (!
|
|
53
|
+
const node = this.map.get(key);
|
|
54
|
+
if (!node)
|
|
27
55
|
return undefined;
|
|
28
|
-
if (
|
|
29
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
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.
|
|
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
|
-
//
|
|
44
|
-
|
|
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.
|
|
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
|
-
*
|
|
57
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
81
|
-
//
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
}
|
package/dist/stores/memory.d.ts
CHANGED
|
@@ -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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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
|
-
*
|
|
44
|
-
*
|
|
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;
|
|
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"}
|
package/dist/stores/memory.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
23
|
-
if (!
|
|
49
|
+
const node = this.map.get(key);
|
|
50
|
+
if (!node)
|
|
24
51
|
return undefined;
|
|
25
|
-
if (
|
|
26
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
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.
|
|
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
|
-
//
|
|
41
|
-
|
|
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.
|
|
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
|
-
*
|
|
54
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
78
|
-
//
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"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"}
|