bunsane 0.2.4 → 0.2.7
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/core/ArcheType.ts +67 -34
- package/core/BatchLoader.ts +215 -30
- package/core/Entity.ts +2 -2
- package/core/RequestContext.ts +15 -10
- package/core/RequestLoaders.ts +4 -2
- package/core/cache/CacheProvider.ts +1 -0
- package/core/cache/MemoryCache.ts +10 -1
- package/core/cache/RedisCache.ts +16 -2
- package/core/validateEnv.ts +8 -0
- package/database/DatabaseHelper.ts +113 -1
- package/database/index.ts +78 -45
- package/docs/SCALABILITY_PLAN.md +175 -0
- package/package.json +13 -2
- package/query/CTENode.ts +44 -24
- package/query/ComponentInclusionNode.ts +181 -91
- package/query/Query.ts +9 -9
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +338 -0
- package/tests/benchmark/bunfig.toml +9 -0
- package/tests/benchmark/fixtures/EcommerceComponents.ts +283 -0
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +301 -0
- package/tests/benchmark/fixtures/RelationTracker.ts +159 -0
- package/tests/benchmark/fixtures/index.ts +6 -0
- package/tests/benchmark/index.ts +22 -0
- package/tests/benchmark/noop-preload.ts +3 -0
- package/tests/benchmark/runners/BenchmarkLoader.ts +132 -0
- package/tests/benchmark/runners/index.ts +4 -0
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +465 -0
- package/tests/benchmark/scripts/generate-db.ts +344 -0
- package/tests/benchmark/scripts/run-benchmarks.ts +97 -0
- package/tests/integration/query/Query.complexAnalysis.test.ts +557 -0
- package/tests/integration/query/Query.explainAnalyze.test.ts +233 -0
- package/tests/stress/fixtures/RealisticComponents.ts +235 -0
- package/tests/stress/scenarios/realistic-scenarios.test.ts +1081 -0
- package/tests/stress/scenarios/timeout-investigation.test.ts +522 -0
- package/tests/unit/BatchLoader.test.ts +139 -25
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for BatchLoader TTL behavior
|
|
2
|
+
* Unit tests for BatchLoader TTL behavior and bounded cache
|
|
3
3
|
*/
|
|
4
4
|
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
5
5
|
import { BatchLoader } from '../../core/BatchLoader';
|
|
@@ -16,15 +16,26 @@ describe('BatchLoader', () => {
|
|
|
16
16
|
expect(stats.entries).toBe(0);
|
|
17
17
|
expect(stats.expired).toBe(0);
|
|
18
18
|
});
|
|
19
|
+
|
|
20
|
+
test('includes memory estimate', () => {
|
|
21
|
+
const stats = BatchLoader.getCacheStats();
|
|
22
|
+
expect(stats.memoryEstimate).toBeDefined();
|
|
23
|
+
expect(typeof stats.memoryEstimate).toBe('string');
|
|
24
|
+
});
|
|
19
25
|
});
|
|
20
26
|
|
|
21
27
|
describe('clearCache()', () => {
|
|
22
28
|
test('clears all cached entries', () => {
|
|
23
|
-
// Access internal cache
|
|
24
|
-
const cache = (BatchLoader as any).cache
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
cache
|
|
29
|
+
// Access internal cache to set up test data
|
|
30
|
+
const cache = (BatchLoader as any).cache;
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
|
|
33
|
+
// Use the cache's set method to add entries
|
|
34
|
+
cache.set('type1\x00value', 'parent1', {
|
|
35
|
+
ids: ['a', 'b'],
|
|
36
|
+
expiresAt: now + 300000,
|
|
37
|
+
lastAccessed: now
|
|
38
|
+
});
|
|
28
39
|
|
|
29
40
|
expect(BatchLoader.getCacheStats().entries).toBe(1);
|
|
30
41
|
|
|
@@ -35,13 +46,21 @@ describe('BatchLoader', () => {
|
|
|
35
46
|
|
|
36
47
|
describe('TTL expiry', () => {
|
|
37
48
|
test('getCacheStats reports expired entries', () => {
|
|
38
|
-
const cache = (BatchLoader as any).cache
|
|
39
|
-
const
|
|
49
|
+
const cache = (BatchLoader as any).cache;
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
|
|
40
52
|
// One fresh entry
|
|
41
|
-
|
|
53
|
+
cache.set('type1\x00value', 'parent1', {
|
|
54
|
+
ids: ['a'],
|
|
55
|
+
expiresAt: now + 300000,
|
|
56
|
+
lastAccessed: now
|
|
57
|
+
});
|
|
42
58
|
// One expired entry
|
|
43
|
-
|
|
44
|
-
|
|
59
|
+
cache.set('type1\x00value', 'parent2', {
|
|
60
|
+
ids: ['b'],
|
|
61
|
+
expiresAt: now - 1000,
|
|
62
|
+
lastAccessed: now - 1000
|
|
63
|
+
});
|
|
45
64
|
|
|
46
65
|
const stats = BatchLoader.getCacheStats();
|
|
47
66
|
expect(stats.entries).toBe(2);
|
|
@@ -49,16 +68,27 @@ describe('BatchLoader', () => {
|
|
|
49
68
|
});
|
|
50
69
|
|
|
51
70
|
test('expired entries are counted correctly for multiple type keys', () => {
|
|
52
|
-
const cache = (BatchLoader as any).cache
|
|
71
|
+
const cache = (BatchLoader as any).cache;
|
|
72
|
+
const now = Date.now();
|
|
53
73
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
// Type 1 entries
|
|
75
|
+
cache.set('type1\x00field', 'p1', {
|
|
76
|
+
ids: ['a'],
|
|
77
|
+
expiresAt: now - 5000,
|
|
78
|
+
lastAccessed: now - 5000
|
|
79
|
+
});
|
|
80
|
+
cache.set('type1\x00field', 'p2', {
|
|
81
|
+
ids: ['b'],
|
|
82
|
+
expiresAt: now + 300000,
|
|
83
|
+
lastAccessed: now
|
|
84
|
+
});
|
|
58
85
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
// Type 2 entry
|
|
87
|
+
cache.set('type2\x00field', 'p3', {
|
|
88
|
+
ids: ['c'],
|
|
89
|
+
expiresAt: now - 1000,
|
|
90
|
+
lastAccessed: now - 1000
|
|
91
|
+
});
|
|
62
92
|
|
|
63
93
|
const stats = BatchLoader.getCacheStats();
|
|
64
94
|
expect(stats.size).toBe(2);
|
|
@@ -67,16 +97,100 @@ describe('BatchLoader', () => {
|
|
|
67
97
|
});
|
|
68
98
|
|
|
69
99
|
test('all entries fresh means zero expired', () => {
|
|
70
|
-
const cache = (BatchLoader as any).cache
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
const cache = (BatchLoader as any).cache;
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
|
|
103
|
+
cache.set('type\x00field', 'p1', {
|
|
104
|
+
ids: ['a'],
|
|
105
|
+
expiresAt: now + 60000,
|
|
106
|
+
lastAccessed: now
|
|
107
|
+
});
|
|
108
|
+
cache.set('type\x00field', 'p2', {
|
|
109
|
+
ids: ['b'],
|
|
110
|
+
expiresAt: now + 60000,
|
|
111
|
+
lastAccessed: now
|
|
112
|
+
});
|
|
113
|
+
cache.set('type\x00field', 'p3', {
|
|
114
|
+
ids: ['c'],
|
|
115
|
+
expiresAt: now + 60000,
|
|
116
|
+
lastAccessed: now
|
|
117
|
+
});
|
|
76
118
|
|
|
77
119
|
const stats = BatchLoader.getCacheStats();
|
|
78
120
|
expect(stats.entries).toBe(3);
|
|
79
121
|
expect(stats.expired).toBe(0);
|
|
80
122
|
});
|
|
81
123
|
});
|
|
124
|
+
|
|
125
|
+
describe('pruneExpiredEntries()', () => {
|
|
126
|
+
test('removes expired entries and returns count', () => {
|
|
127
|
+
const cache = (BatchLoader as any).cache;
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
|
|
130
|
+
// Add mix of fresh and expired entries
|
|
131
|
+
cache.set('type\x00field', 'p1', {
|
|
132
|
+
ids: ['a'],
|
|
133
|
+
expiresAt: now - 5000,
|
|
134
|
+
lastAccessed: now - 5000
|
|
135
|
+
});
|
|
136
|
+
cache.set('type\x00field', 'p2', {
|
|
137
|
+
ids: ['b'],
|
|
138
|
+
expiresAt: now + 300000,
|
|
139
|
+
lastAccessed: now
|
|
140
|
+
});
|
|
141
|
+
cache.set('type\x00field', 'p3', {
|
|
142
|
+
ids: ['c'],
|
|
143
|
+
expiresAt: now - 1000,
|
|
144
|
+
lastAccessed: now - 1000
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(BatchLoader.getCacheStats().entries).toBe(3);
|
|
148
|
+
expect(BatchLoader.getCacheStats().expired).toBe(2);
|
|
149
|
+
|
|
150
|
+
const pruned = BatchLoader.pruneExpiredEntries();
|
|
151
|
+
expect(pruned).toBe(2);
|
|
152
|
+
|
|
153
|
+
const statsAfter = BatchLoader.getCacheStats();
|
|
154
|
+
expect(statsAfter.entries).toBe(1);
|
|
155
|
+
expect(statsAfter.expired).toBe(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('returns 0 when no expired entries', () => {
|
|
159
|
+
const cache = (BatchLoader as any).cache;
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
|
|
162
|
+
cache.set('type\x00field', 'p1', {
|
|
163
|
+
ids: ['a'],
|
|
164
|
+
expiresAt: now + 60000,
|
|
165
|
+
lastAccessed: now
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const pruned = BatchLoader.pruneExpiredEntries();
|
|
169
|
+
expect(pruned).toBe(0);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('bounded cache behavior', () => {
|
|
174
|
+
test('memory estimate increases with entries', () => {
|
|
175
|
+
const cache = (BatchLoader as any).cache;
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
|
|
178
|
+
const statsBefore = BatchLoader.getCacheStats();
|
|
179
|
+
|
|
180
|
+
// Add several entries
|
|
181
|
+
for (let i = 0; i < 100; i++) {
|
|
182
|
+
cache.set(`type${i}\x00field`, `parent${i}`, {
|
|
183
|
+
ids: [`id${i}`],
|
|
184
|
+
expiresAt: now + 60000,
|
|
185
|
+
lastAccessed: now
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const statsAfter = BatchLoader.getCacheStats();
|
|
190
|
+
expect(statsAfter.entries).toBe(100);
|
|
191
|
+
|
|
192
|
+
// Memory estimate should reflect the entries
|
|
193
|
+
expect(statsAfter.memoryEstimate).toBeDefined();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
82
196
|
});
|