@mandujs/core 0.41.2 → 0.43.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/package.json +21 -4
- package/src/auth/__tests__/login.test.ts +420 -419
- package/src/auth/__tests__/reset.test.ts +296 -296
- package/src/brain/adapters/anthropic-oauth.ts +421 -420
- package/src/brain/adapters/index.ts +2 -1
- package/src/brain/adapters/ollama.ts +1 -1
- package/src/brain/adapters/openai-oauth.ts +534 -533
- package/src/brain/brain.ts +2 -1
- package/src/brain/redactor.ts +196 -196
- package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -149
- package/src/bundler/__tests__/cold-start.test.ts +504 -504
- package/src/bundler/__tests__/fast-refresh.test.ts +607 -606
- package/src/bundler/__tests__/hdr.test.ts +1 -1
- package/src/bundler/analyzer.ts +958 -958
- package/src/bundler/build.ts +104 -14
- package/src/bundler/dev.ts +125 -0
- package/src/bundler/hmr-types.ts +1 -0
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -0
- package/src/bundler/plugins/index.ts +14 -0
- package/src/bundler/plugins/react-compiler-lint.ts +253 -0
- package/src/bundler/plugins/react-compiler.ts +162 -0
- package/src/bundler/types.ts +12 -0
- package/src/change/integrity.ts +2 -1
- package/src/client/index.ts +10 -0
- package/src/client/island.ts +38 -11
- package/src/client/router.ts +6 -1
- package/src/config/mandu.ts +57 -0
- package/src/config/validate.ts +42 -0
- package/src/content/collection.ts +844 -809
- package/src/content/content-layer.ts +316 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/digest.ts +133 -133
- package/src/content/generate-types.ts +168 -168
- package/src/content/index.ts +6 -1
- package/src/content/llms-txt.ts +277 -277
- package/src/contract/define.ts +474 -474
- package/src/contract/route-helpers.ts +2 -1
- package/src/contract/zod-utils.ts +158 -155
- package/src/db/index.ts +513 -513
- package/src/desktop/__tests__/smoke.test.ts +100 -100
- package/src/desktop/webview-fallback.ts +583 -583
- package/src/desktop/window.ts +3 -1
- package/src/dev-error-overlay/overlay-client.ts +300 -300
- package/src/devtools/ai/mcp-connector.ts +499 -498
- package/src/devtools/client/components/kitchen-root.tsx +7 -2
- package/src/email/resend.ts +163 -163
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -0
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/graph.ts +898 -898
- package/src/guard/index.ts +16 -0
- package/src/guard/statistics.ts +578 -578
- package/src/guard/tsgolint-bridge.ts +512 -0
- package/src/i18n/locale-resolver.ts +214 -214
- package/src/id/__tests__/id.test.ts +120 -120
- package/src/intent/index.ts +321 -321
- package/src/island/index.ts +39 -23
- package/src/kitchen/api/contract-api.ts +15 -8
- package/src/kitchen/kitchen-ui.ts +2137 -2137
- package/src/lockfile/index.ts +3 -2
- package/src/middleware/oauth/__tests__/oauth.test.ts +575 -574
- package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -642
- package/src/middleware/secure/index.ts +417 -417
- package/src/observability/event-bus.ts +2 -2
- package/src/observability/metrics.ts +334 -334
- package/src/observability/tracing.ts +694 -694
- package/src/openapi/generator.ts +1 -1
- package/src/perf/user-marks.ts +553 -553
- package/src/plugins/registry.ts +387 -387
- package/src/resource/ddl/diff.ts +392 -392
- package/src/resource/ddl/snapshot.ts +448 -447
- package/src/resource/generator-schema.ts +477 -476
- package/src/resource/parser.ts +4 -2
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.ts +422 -422
- package/src/runtime/fast-refresh-types.ts +126 -128
- package/src/runtime/image-handler.ts +206 -195
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +36 -19
- package/src/runtime/session-key.ts +328 -328
- package/src/scheduler/__tests__/scheduler.test.ts +514 -514
- package/src/seo/resolve/index.ts +353 -353
- package/src/spec/load.ts +1 -1
- package/src/testing/reporter.ts +676 -676
- package/src/testing/server.ts +196 -196
- package/src/testing/snapshot.ts +444 -444
- package/src/utils/__tests__/lru-cache.test.ts +186 -186
- package/src/utils/bun.ts +8 -8
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 17 — LRUCache behaviour matrix.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - Default + custom maxSize
|
|
6
|
-
* - Eviction order (LRU semantics, not FIFO)
|
|
7
|
-
* - `get` promotes the entry, `has` does not
|
|
8
|
-
* - `onEvict` fires for LRU pressure / delete / clear
|
|
9
|
-
* - Backward-compat numeric constructor
|
|
10
|
-
* - Stats bookkeeping
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, test, expect } from "bun:test";
|
|
14
|
-
import { LRUCache } from "../lru-cache";
|
|
15
|
-
|
|
16
|
-
describe("LRUCache", () => {
|
|
17
|
-
test("default maxSize is 1000 when no options passed", () => {
|
|
18
|
-
const cache = new LRUCache<string, number>();
|
|
19
|
-
// Insert 1000 — all fit. The 1001st evicts the first.
|
|
20
|
-
for (let i = 0; i < 1000; i++) cache.set(`k${i}`, i);
|
|
21
|
-
expect(cache.size).toBe(1000);
|
|
22
|
-
expect(cache.has("k0")).toBe(true);
|
|
23
|
-
|
|
24
|
-
cache.set("k1000", 1000);
|
|
25
|
-
expect(cache.size).toBe(1000);
|
|
26
|
-
expect(cache.has("k0")).toBe(false);
|
|
27
|
-
expect(cache.has("k1000")).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("numeric constructor is still supported (backward-compat)", () => {
|
|
31
|
-
const cache = new LRUCache<string, string>(3);
|
|
32
|
-
cache.set("a", "1");
|
|
33
|
-
cache.set("b", "2");
|
|
34
|
-
cache.set("c", "3");
|
|
35
|
-
cache.set("d", "4"); // evicts "a"
|
|
36
|
-
expect(cache.has("a")).toBe(false);
|
|
37
|
-
expect(cache.size).toBe(3);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("rejects invalid maxSize", () => {
|
|
41
|
-
expect(() => new LRUCache({ maxSize: 0 })).toThrow();
|
|
42
|
-
expect(() => new LRUCache({ maxSize: -1 })).toThrow();
|
|
43
|
-
expect(() => new LRUCache({ maxSize: Number.NaN })).toThrow();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("evicts least-recently-used entry, not FIFO", () => {
|
|
47
|
-
const cache = new LRUCache<string, number>({ maxSize: 3 });
|
|
48
|
-
cache.set("a", 1);
|
|
49
|
-
cache.set("b", 2);
|
|
50
|
-
cache.set("c", 3);
|
|
51
|
-
// Touch "a" so it becomes most-recently-used.
|
|
52
|
-
cache.get("a");
|
|
53
|
-
// Now inserting "d" must evict "b" (oldest), not "a".
|
|
54
|
-
cache.set("d", 4);
|
|
55
|
-
expect(cache.has("a")).toBe(true);
|
|
56
|
-
expect(cache.has("b")).toBe(false);
|
|
57
|
-
expect(cache.has("c")).toBe(true);
|
|
58
|
-
expect(cache.has("d")).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("has() does NOT promote (read-only probe)", () => {
|
|
62
|
-
const cache = new LRUCache<string, number>({ maxSize: 3 });
|
|
63
|
-
cache.set("a", 1);
|
|
64
|
-
cache.set("b", 2);
|
|
65
|
-
cache.set("c", 3);
|
|
66
|
-
// `has` on "a" must not reorder. Inserting "d" should still evict "a".
|
|
67
|
-
expect(cache.has("a")).toBe(true);
|
|
68
|
-
cache.set("d", 4);
|
|
69
|
-
expect(cache.has("a")).toBe(false);
|
|
70
|
-
expect(cache.has("b")).toBe(true);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("updating an existing key re-inserts as MRU but does not fire onEvict", () => {
|
|
74
|
-
const evicted: Array<[string, number]> = [];
|
|
75
|
-
const cache = new LRUCache<string, number>({
|
|
76
|
-
maxSize: 3,
|
|
77
|
-
onEvict: (k, v) => evicted.push([k, v]),
|
|
78
|
-
});
|
|
79
|
-
cache.set("a", 1);
|
|
80
|
-
cache.set("b", 2);
|
|
81
|
-
cache.set("c", 3);
|
|
82
|
-
cache.set("a", 10); // update, a becomes MRU
|
|
83
|
-
expect(evicted).toHaveLength(0);
|
|
84
|
-
cache.set("d", 4); // should evict "b" (now oldest), not "a"
|
|
85
|
-
expect(evicted).toEqual([["b", 2]]);
|
|
86
|
-
expect(cache.get("a")).toBe(10);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("onEvict fires on LRU pressure with (key, value)", () => {
|
|
90
|
-
const evicted: Array<[string, number]> = [];
|
|
91
|
-
const cache = new LRUCache<string, number>({
|
|
92
|
-
maxSize: 2,
|
|
93
|
-
onEvict: (k, v) => evicted.push([k, v]),
|
|
94
|
-
});
|
|
95
|
-
cache.set("a", 1);
|
|
96
|
-
cache.set("b", 2);
|
|
97
|
-
cache.set("c", 3); // evicts "a"
|
|
98
|
-
cache.set("d", 4); // evicts "b"
|
|
99
|
-
expect(evicted).toEqual([
|
|
100
|
-
["a", 1],
|
|
101
|
-
["b", 2],
|
|
102
|
-
]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("onEvict fires on delete() for present keys only", () => {
|
|
106
|
-
const evicted: Array<[string, number]> = [];
|
|
107
|
-
const cache = new LRUCache<string, number>({
|
|
108
|
-
maxSize: 5,
|
|
109
|
-
onEvict: (k, v) => evicted.push([k, v]),
|
|
110
|
-
});
|
|
111
|
-
cache.set("x", 1);
|
|
112
|
-
expect(cache.delete("x")).toBe(true);
|
|
113
|
-
expect(cache.delete("x")).toBe(false); // already gone
|
|
114
|
-
expect(cache.delete("missing")).toBe(false);
|
|
115
|
-
expect(evicted).toEqual([["x", 1]]);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("onEvict fires for every entry during clear()", () => {
|
|
119
|
-
const evicted: string[] = [];
|
|
120
|
-
const cache = new LRUCache<string, number>({
|
|
121
|
-
maxSize: 10,
|
|
122
|
-
onEvict: (k) => evicted.push(k),
|
|
123
|
-
});
|
|
124
|
-
cache.set("a", 1);
|
|
125
|
-
cache.set("b", 2);
|
|
126
|
-
cache.set("c", 3);
|
|
127
|
-
cache.clear();
|
|
128
|
-
expect(evicted.sort()).toEqual(["a", "b", "c"]);
|
|
129
|
-
expect(cache.size).toBe(0);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("onEvict errors are swallowed (must not corrupt state)", () => {
|
|
133
|
-
const cache = new LRUCache<string, number>({
|
|
134
|
-
maxSize: 2,
|
|
135
|
-
onEvict: () => {
|
|
136
|
-
throw new Error("boom");
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
cache.set("a", 1);
|
|
140
|
-
cache.set("b", 2);
|
|
141
|
-
// Would throw during eviction if errors weren't caught.
|
|
142
|
-
expect(() => cache.set("c", 3)).not.toThrow();
|
|
143
|
-
expect(cache.size).toBe(2);
|
|
144
|
-
expect(cache.has("a")).toBe(false);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("getWithStats records hits/misses; getStats reports hit rate", () => {
|
|
148
|
-
const cache = new LRUCache<string, number>({ maxSize: 10 });
|
|
149
|
-
cache.set("a", 1);
|
|
150
|
-
cache.getWithStats("a"); // hit
|
|
151
|
-
cache.getWithStats("a"); // hit
|
|
152
|
-
cache.getWithStats("b"); // miss
|
|
153
|
-
const stats = cache.getStats();
|
|
154
|
-
expect(stats.hits).toBe(2);
|
|
155
|
-
expect(stats.misses).toBe(1);
|
|
156
|
-
expect(stats.hitRate).toBeCloseTo(2 / 3);
|
|
157
|
-
expect(stats.size).toBe(1);
|
|
158
|
-
expect(stats.maxSize).toBe(10);
|
|
159
|
-
cache.resetStats();
|
|
160
|
-
expect(cache.getStats().hits).toBe(0);
|
|
161
|
-
expect(cache.getStats().misses).toBe(0);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("entries() yields LRU → MRU order", () => {
|
|
165
|
-
const cache = new LRUCache<string, number>({ maxSize: 5 });
|
|
166
|
-
cache.set("a", 1);
|
|
167
|
-
cache.set("b", 2);
|
|
168
|
-
cache.set("c", 3);
|
|
169
|
-
cache.get("a"); // a becomes MRU
|
|
170
|
-
const keys = [...cache.entries()].map(([k]) => k);
|
|
171
|
-
expect(keys).toEqual(["b", "c", "a"]);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("fill-past-max proof: 1001 writes against a 1000-entry cache evict exactly the oldest", () => {
|
|
175
|
-
const cache = new LRUCache<number, number>({ maxSize: 1000 });
|
|
176
|
-
for (let i = 0; i < 1000; i++) cache.set(i, i);
|
|
177
|
-
expect(cache.size).toBe(1000);
|
|
178
|
-
expect(cache.has(0)).toBe(true);
|
|
179
|
-
|
|
180
|
-
cache.set(1000, 1000);
|
|
181
|
-
expect(cache.size).toBe(1000); // still bounded
|
|
182
|
-
expect(cache.has(0)).toBe(false); // oldest gone
|
|
183
|
-
expect(cache.has(1000)).toBe(true); // newest present
|
|
184
|
-
expect(cache.has(500)).toBe(true); // middle survives
|
|
185
|
-
});
|
|
186
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Phase 17 — LRUCache behaviour matrix.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Default + custom maxSize
|
|
6
|
+
* - Eviction order (LRU semantics, not FIFO)
|
|
7
|
+
* - `get` promotes the entry, `has` does not
|
|
8
|
+
* - `onEvict` fires for LRU pressure / delete / clear
|
|
9
|
+
* - Backward-compat numeric constructor
|
|
10
|
+
* - Stats bookkeeping
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, test, expect } from "bun:test";
|
|
14
|
+
import { LRUCache } from "../lru-cache";
|
|
15
|
+
|
|
16
|
+
describe("LRUCache", () => {
|
|
17
|
+
test("default maxSize is 1000 when no options passed", () => {
|
|
18
|
+
const cache = new LRUCache<string, number>();
|
|
19
|
+
// Insert 1000 — all fit. The 1001st evicts the first.
|
|
20
|
+
for (let i = 0; i < 1000; i++) cache.set(`k${i}`, i);
|
|
21
|
+
expect(cache.size).toBe(1000);
|
|
22
|
+
expect(cache.has("k0")).toBe(true);
|
|
23
|
+
|
|
24
|
+
cache.set("k1000", 1000);
|
|
25
|
+
expect(cache.size).toBe(1000);
|
|
26
|
+
expect(cache.has("k0")).toBe(false);
|
|
27
|
+
expect(cache.has("k1000")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("numeric constructor is still supported (backward-compat)", () => {
|
|
31
|
+
const cache = new LRUCache<string, string>(3);
|
|
32
|
+
cache.set("a", "1");
|
|
33
|
+
cache.set("b", "2");
|
|
34
|
+
cache.set("c", "3");
|
|
35
|
+
cache.set("d", "4"); // evicts "a"
|
|
36
|
+
expect(cache.has("a")).toBe(false);
|
|
37
|
+
expect(cache.size).toBe(3);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("rejects invalid maxSize", () => {
|
|
41
|
+
expect(() => new LRUCache({ maxSize: 0 })).toThrow();
|
|
42
|
+
expect(() => new LRUCache({ maxSize: -1 })).toThrow();
|
|
43
|
+
expect(() => new LRUCache({ maxSize: Number.NaN })).toThrow();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("evicts least-recently-used entry, not FIFO", () => {
|
|
47
|
+
const cache = new LRUCache<string, number>({ maxSize: 3 });
|
|
48
|
+
cache.set("a", 1);
|
|
49
|
+
cache.set("b", 2);
|
|
50
|
+
cache.set("c", 3);
|
|
51
|
+
// Touch "a" so it becomes most-recently-used.
|
|
52
|
+
cache.get("a");
|
|
53
|
+
// Now inserting "d" must evict "b" (oldest), not "a".
|
|
54
|
+
cache.set("d", 4);
|
|
55
|
+
expect(cache.has("a")).toBe(true);
|
|
56
|
+
expect(cache.has("b")).toBe(false);
|
|
57
|
+
expect(cache.has("c")).toBe(true);
|
|
58
|
+
expect(cache.has("d")).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("has() does NOT promote (read-only probe)", () => {
|
|
62
|
+
const cache = new LRUCache<string, number>({ maxSize: 3 });
|
|
63
|
+
cache.set("a", 1);
|
|
64
|
+
cache.set("b", 2);
|
|
65
|
+
cache.set("c", 3);
|
|
66
|
+
// `has` on "a" must not reorder. Inserting "d" should still evict "a".
|
|
67
|
+
expect(cache.has("a")).toBe(true);
|
|
68
|
+
cache.set("d", 4);
|
|
69
|
+
expect(cache.has("a")).toBe(false);
|
|
70
|
+
expect(cache.has("b")).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("updating an existing key re-inserts as MRU but does not fire onEvict", () => {
|
|
74
|
+
const evicted: Array<[string, number]> = [];
|
|
75
|
+
const cache = new LRUCache<string, number>({
|
|
76
|
+
maxSize: 3,
|
|
77
|
+
onEvict: (k, v) => evicted.push([k, v]),
|
|
78
|
+
});
|
|
79
|
+
cache.set("a", 1);
|
|
80
|
+
cache.set("b", 2);
|
|
81
|
+
cache.set("c", 3);
|
|
82
|
+
cache.set("a", 10); // update, a becomes MRU
|
|
83
|
+
expect(evicted).toHaveLength(0);
|
|
84
|
+
cache.set("d", 4); // should evict "b" (now oldest), not "a"
|
|
85
|
+
expect(evicted).toEqual([["b", 2]]);
|
|
86
|
+
expect(cache.get("a")).toBe(10);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("onEvict fires on LRU pressure with (key, value)", () => {
|
|
90
|
+
const evicted: Array<[string, number]> = [];
|
|
91
|
+
const cache = new LRUCache<string, number>({
|
|
92
|
+
maxSize: 2,
|
|
93
|
+
onEvict: (k, v) => evicted.push([k, v]),
|
|
94
|
+
});
|
|
95
|
+
cache.set("a", 1);
|
|
96
|
+
cache.set("b", 2);
|
|
97
|
+
cache.set("c", 3); // evicts "a"
|
|
98
|
+
cache.set("d", 4); // evicts "b"
|
|
99
|
+
expect(evicted).toEqual([
|
|
100
|
+
["a", 1],
|
|
101
|
+
["b", 2],
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("onEvict fires on delete() for present keys only", () => {
|
|
106
|
+
const evicted: Array<[string, number]> = [];
|
|
107
|
+
const cache = new LRUCache<string, number>({
|
|
108
|
+
maxSize: 5,
|
|
109
|
+
onEvict: (k, v) => evicted.push([k, v]),
|
|
110
|
+
});
|
|
111
|
+
cache.set("x", 1);
|
|
112
|
+
expect(cache.delete("x")).toBe(true);
|
|
113
|
+
expect(cache.delete("x")).toBe(false); // already gone
|
|
114
|
+
expect(cache.delete("missing")).toBe(false);
|
|
115
|
+
expect(evicted).toEqual([["x", 1]]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("onEvict fires for every entry during clear()", () => {
|
|
119
|
+
const evicted: string[] = [];
|
|
120
|
+
const cache = new LRUCache<string, number>({
|
|
121
|
+
maxSize: 10,
|
|
122
|
+
onEvict: (k) => evicted.push(k),
|
|
123
|
+
});
|
|
124
|
+
cache.set("a", 1);
|
|
125
|
+
cache.set("b", 2);
|
|
126
|
+
cache.set("c", 3);
|
|
127
|
+
cache.clear();
|
|
128
|
+
expect(evicted.sort()).toEqual(["a", "b", "c"]);
|
|
129
|
+
expect(cache.size).toBe(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("onEvict errors are swallowed (must not corrupt state)", () => {
|
|
133
|
+
const cache = new LRUCache<string, number>({
|
|
134
|
+
maxSize: 2,
|
|
135
|
+
onEvict: () => {
|
|
136
|
+
throw new Error("boom");
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
cache.set("a", 1);
|
|
140
|
+
cache.set("b", 2);
|
|
141
|
+
// Would throw during eviction if errors weren't caught.
|
|
142
|
+
expect(() => cache.set("c", 3)).not.toThrow();
|
|
143
|
+
expect(cache.size).toBe(2);
|
|
144
|
+
expect(cache.has("a")).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("getWithStats records hits/misses; getStats reports hit rate", () => {
|
|
148
|
+
const cache = new LRUCache<string, number>({ maxSize: 10 });
|
|
149
|
+
cache.set("a", 1);
|
|
150
|
+
cache.getWithStats("a"); // hit
|
|
151
|
+
cache.getWithStats("a"); // hit
|
|
152
|
+
cache.getWithStats("b"); // miss
|
|
153
|
+
const stats = cache.getStats();
|
|
154
|
+
expect(stats.hits).toBe(2);
|
|
155
|
+
expect(stats.misses).toBe(1);
|
|
156
|
+
expect(stats.hitRate).toBeCloseTo(2 / 3);
|
|
157
|
+
expect(stats.size).toBe(1);
|
|
158
|
+
expect(stats.maxSize).toBe(10);
|
|
159
|
+
cache.resetStats();
|
|
160
|
+
expect(cache.getStats().hits).toBe(0);
|
|
161
|
+
expect(cache.getStats().misses).toBe(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("entries() yields LRU → MRU order", () => {
|
|
165
|
+
const cache = new LRUCache<string, number>({ maxSize: 5 });
|
|
166
|
+
cache.set("a", 1);
|
|
167
|
+
cache.set("b", 2);
|
|
168
|
+
cache.set("c", 3);
|
|
169
|
+
cache.get("a"); // a becomes MRU
|
|
170
|
+
const keys = [...cache.entries()].map(([k]) => k);
|
|
171
|
+
expect(keys).toEqual(["b", "c", "a"]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("fill-past-max proof: 1001 writes against a 1000-entry cache evict exactly the oldest", () => {
|
|
175
|
+
const cache = new LRUCache<number, number>({ maxSize: 1000 });
|
|
176
|
+
for (let i = 0; i < 1000; i++) cache.set(i, i);
|
|
177
|
+
expect(cache.size).toBe(1000);
|
|
178
|
+
expect(cache.has(0)).toBe(true);
|
|
179
|
+
|
|
180
|
+
cache.set(1000, 1000);
|
|
181
|
+
expect(cache.size).toBe(1000); // still bounded
|
|
182
|
+
expect(cache.has(0)).toBe(false); // oldest gone
|
|
183
|
+
expect(cache.has(1000)).toBe(true); // newest present
|
|
184
|
+
expect(cache.has(500)).toBe(true); // middle survives
|
|
185
|
+
});
|
|
186
|
+
});
|
package/src/utils/bun.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export async function readJsonFile(filePath: string): Promise<unknown> {
|
|
2
|
-
try {
|
|
3
|
-
return await Bun.file(filePath).json();
|
|
4
|
-
} catch (error) {
|
|
5
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
6
|
-
throw new Error(`Failed to parse JSON in ${filePath}: ${message}
|
|
7
|
-
}
|
|
8
|
-
}
|
|
1
|
+
export async function readJsonFile(filePath: string): Promise<unknown> {
|
|
2
|
+
try {
|
|
3
|
+
return await Bun.file(filePath).json();
|
|
4
|
+
} catch (error) {
|
|
5
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6
|
+
throw new Error(`Failed to parse JSON in ${filePath}: ${message}`, { cause: error });
|
|
7
|
+
}
|
|
8
|
+
}
|