bunsane 0.3.2 → 0.4.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/CHANGELOG.md +445 -370
- package/core/BatchLoader.ts +56 -32
- package/core/Entity.ts +85 -1020
- package/core/EntityHookManager.ts +52 -754
- package/core/Logger.ts +10 -0
- package/core/RequestContext.ts +94 -85
- package/core/RequestLoaders.ts +98 -5
- package/core/SchedulerManager.ts +28 -600
- package/core/app/cors.ts +2 -11
- package/core/app/preparedStatementWarmup.ts +9 -49
- package/core/app/requestRouter.ts +9 -8
- package/core/app/restRegistry.ts +8 -0
- package/core/archetype/fieldResolvers.ts +85 -40
- package/core/archetype/relationLoader.ts +135 -92
- package/core/cache/CacheManager.ts +91 -302
- package/core/cache/CompressionUtils.ts +34 -3
- package/core/cache/MemoryCache.ts +40 -37
- package/core/cache/RedisCache.ts +4 -4
- package/core/cache/health.ts +30 -0
- package/core/cache/invalidation.ts +96 -0
- package/core/cache/strategies/writeInvalidate.ts +111 -0
- package/core/cache/strategies/writeThrough.ts +233 -0
- package/core/components/BaseComponent.ts +16 -8
- package/core/components/ComponentRegistry.ts +28 -0
- package/core/decorators/IndexedField.ts +1 -1
- package/core/entity/cacheStrategies.ts +97 -0
- package/core/entity/componentAccess.ts +364 -0
- package/core/entity/finders.ts +202 -0
- package/core/entity/pendingOps.ts +72 -0
- package/core/entity/saveEntity.ts +377 -0
- package/core/hooks/dispatcher.ts +439 -0
- package/core/hooks/guards.ts +155 -0
- package/core/hooks/registry.ts +247 -0
- package/core/metadata/definitions/Component.ts +1 -1
- package/core/metadata/index.ts +15 -4
- package/core/middleware/RateLimit.ts +102 -105
- package/core/middleware/RequestId.ts +2 -9
- package/core/middleware/SecurityHeaders.ts +2 -11
- package/core/middleware/headers.ts +28 -0
- package/core/remote/OutboxWorker.ts +213 -183
- package/core/remote/RemoteManager.ts +401 -400
- package/core/remote/types.ts +153 -151
- package/core/requestScope.ts +34 -0
- package/core/scheduler/cronEvaluator.ts +174 -0
- package/core/scheduler/lifecycleHooks.ts +21 -0
- package/core/scheduler/lockCoordinator.ts +27 -0
- package/core/scheduler/metrics.ts +14 -0
- package/core/scheduler/taskRunner.ts +420 -0
- package/database/DatabaseHelper.ts +128 -101
- package/database/IndexingStrategy.ts +72 -2
- package/database/PreparedStatementCache.ts +8 -2
- package/database/cancellable.ts +35 -22
- package/database/index.ts +15 -3
- package/database/instrumentedDb.ts +141 -141
- package/endpoints/archetypes.ts +2 -8
- package/endpoints/tables.ts +6 -1
- package/gql/index.ts +1 -1
- package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
- package/package.json +22 -1
- package/query/CTENode.ts +5 -3
- package/query/ComponentInclusionNode.ts +240 -13
- package/query/OrNode.ts +6 -5
- package/query/Query.ts +157 -46
- package/query/QueryContext.ts +6 -0
- package/query/QueryDAG.ts +7 -2
- package/query/membershipSource.ts +66 -0
- package/storage/LocalStorageProvider.ts +8 -3
- package/studio/dist/assets/index-BMZ67Npg.js +254 -0
- package/studio/dist/assets/index-BpbuYz9g.css +1 -0
- package/studio/{index.html → dist/index.html} +3 -2
- package/swagger/generator.ts +11 -1
- package/upload/UploadManager.ts +8 -6
- package/utils/uuid.ts +40 -10
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.local.json +0 -47
- package/.prettierrc +0 -4
- package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
- package/.serena/memories/architecture.md +0 -154
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
- package/.serena/memories/code_style_and_conventions.md +0 -76
- package/.serena/memories/project_overview.md +0 -43
- package/.serena/memories/schema-dsl-plan.md +0 -107
- package/.serena/memories/suggested_commands.md +0 -80
- package/.serena/memories/typescript-compilation-status.md +0 -54
- package/.serena/project.yml +0 -114
- package/BunSane.jpg +0 -0
- package/CLAUDE.md +0 -198
- package/TODO.md +0 -2
- package/bun.lock +0 -302
- package/bunfig.toml +0 -10
- package/docs/RFC_APP_REFACTOR.md +0 -248
- package/docs/RFC_REFACTOR_TARGETS.md +0 -251
- package/docs/SCALABILITY_PLAN.md +0 -175
- package/studio/bun.lock +0 -482
- package/studio/package.json +0 -39
- package/studio/postcss.config.js +0 -6
- package/studio/src/components/DataTable.tsx +0 -211
- package/studio/src/components/Layout.tsx +0 -13
- package/studio/src/components/PageContainer.tsx +0 -9
- package/studio/src/components/PageHeader.tsx +0 -13
- package/studio/src/components/SearchBar.tsx +0 -57
- package/studio/src/components/Sidebar.tsx +0 -294
- package/studio/src/components/ui/button.tsx +0 -56
- package/studio/src/components/ui/checkbox.tsx +0 -26
- package/studio/src/components/ui/input.tsx +0 -25
- package/studio/src/hooks/useDataTable.ts +0 -131
- package/studio/src/index.css +0 -36
- package/studio/src/lib/api.ts +0 -186
- package/studio/src/lib/utils.ts +0 -13
- package/studio/src/main.tsx +0 -17
- package/studio/src/pages/ArcheType.tsx +0 -239
- package/studio/src/pages/Components.tsx +0 -124
- package/studio/src/pages/EntityInspector.tsx +0 -302
- package/studio/src/pages/QueryRunner.tsx +0 -246
- package/studio/src/pages/Table.tsx +0 -94
- package/studio/src/pages/Welcome.tsx +0 -241
- package/studio/src/routes.tsx +0 -45
- package/studio/src/store/archeTypeSettings.ts +0 -30
- package/studio/src/store/studio.ts +0 -65
- package/studio/src/utils/columnHelpers.tsx +0 -114
- package/studio/studio-instructions.md +0 -81
- package/studio/tailwind.config.js +0 -77
- package/studio/utils.ts +0 -54
- package/studio/vite.config.js +0 -19
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
- package/tests/benchmark/bunfig.toml +0 -9
- package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
- package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
- package/tests/benchmark/fixtures/index.ts +0 -6
- package/tests/benchmark/index.ts +0 -22
- package/tests/benchmark/noop-preload.ts +0 -3
- package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
- package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
- package/tests/benchmark/runners/index.ts +0 -4
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
- package/tests/benchmark/scripts/generate-db.ts +0 -344
- package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
- package/tests/e2e/http.test.ts +0 -130
- package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
- package/tests/fixtures/components/TestOrder.ts +0 -23
- package/tests/fixtures/components/TestProduct.ts +0 -23
- package/tests/fixtures/components/TestUser.ts +0 -20
- package/tests/fixtures/components/index.ts +0 -6
- package/tests/graphql/SchemaGeneration.test.ts +0 -90
- package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
- package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
- package/tests/helpers/MockRedisClient.ts +0 -113
- package/tests/helpers/MockRedisStreamServer.ts +0 -448
- package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
- package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
- package/tests/integration/entity/Entity.persistence.test.ts +0 -333
- package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
- package/tests/integration/loaders/RequestLoaders.abort.test.ts +0 -82
- package/tests/integration/query/Query.abort.test.ts +0 -66
- package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
- package/tests/integration/query/Query.edgeCases.test.ts +0 -595
- package/tests/integration/query/Query.exec.test.ts +0 -576
- package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
- package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
- package/tests/integration/remote/dlq.test.ts +0 -175
- package/tests/integration/remote/event-dispatch.test.ts +0 -114
- package/tests/integration/remote/outbox.test.ts +0 -130
- package/tests/integration/remote/rpc.test.ts +0 -177
- package/tests/pglite-setup.ts +0 -62
- package/tests/setup.ts +0 -164
- package/tests/stress/BenchmarkRunner.ts +0 -203
- package/tests/stress/DataSeeder.ts +0 -190
- package/tests/stress/StressTestReporter.ts +0 -229
- package/tests/stress/cursor-perf-test.ts +0 -171
- package/tests/stress/fixtures/RealisticComponents.ts +0 -235
- package/tests/stress/fixtures/StressTestComponents.ts +0 -58
- package/tests/stress/index.ts +0 -7
- package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
- package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
- package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
- package/tests/unit/BatchLoader.test.ts +0 -196
- package/tests/unit/archetype/ArcheType.test.ts +0 -107
- package/tests/unit/cache/CacheManager.test.ts +0 -498
- package/tests/unit/cache/MemoryCache.test.ts +0 -260
- package/tests/unit/cache/RedisCache.test.ts +0 -411
- package/tests/unit/database/cancellable.test.ts +0 -81
- package/tests/unit/database/instrumentedDb.test.ts +0 -160
- package/tests/unit/entity/Entity.components.test.ts +0 -317
- package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
- package/tests/unit/entity/Entity.reload.test.ts +0 -63
- package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
- package/tests/unit/entity/Entity.test.ts +0 -345
- package/tests/unit/gql/depthLimit.test.ts +0 -203
- package/tests/unit/gql/operationMiddleware.test.ts +0 -293
- package/tests/unit/health/Health.test.ts +0 -129
- package/tests/unit/middleware/AccessLog.test.ts +0 -37
- package/tests/unit/middleware/Middleware.test.ts +0 -98
- package/tests/unit/middleware/RequestId.test.ts +0 -54
- package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
- package/tests/unit/query/FilterBuilder.test.ts +0 -111
- package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
- package/tests/unit/query/Query.emptyString.test.ts +0 -69
- package/tests/unit/query/Query.test.ts +0 -310
- package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
- package/tests/unit/remote/RemoteError.test.ts +0 -55
- package/tests/unit/remote/decorators.test.ts +0 -195
- package/tests/unit/remote/metrics.test.ts +0 -115
- package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
- package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
- package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
- package/tests/unit/schema/schema-integration.test.ts +0 -426
- package/tests/unit/schema/schema.test.ts +0 -580
- package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
- package/tests/unit/upload/RestUpload.test.ts +0 -267
- package/tests/unit/validateEnv.test.ts +0 -82
- package/tests/utils/entity-tracker.ts +0 -57
- package/tests/utils/index.ts +0 -13
- package/tests/utils/test-context.ts +0 -149
package/core/BatchLoader.ts
CHANGED
|
@@ -26,10 +26,19 @@ interface BatchLoaderConfig {
|
|
|
26
26
|
/**
|
|
27
27
|
* LRU-bounded cache for relation lookups.
|
|
28
28
|
* Prevents unbounded memory growth under high cardinality.
|
|
29
|
+
*
|
|
30
|
+
* LRU strategy: a flat `lruOrder` Map keyed by `cacheKey\x00parentId` is kept
|
|
31
|
+
* in access order (delete + re-insert on every read/write). Eviction simply
|
|
32
|
+
* iterates from the front — O(k) where k = eviction batch, not O(n log n).
|
|
33
|
+
* Per-type `lastAccess` timestamp enables O(types) evictOldestType without
|
|
34
|
+
* scanning all entries.
|
|
29
35
|
*/
|
|
30
36
|
class BoundedRelationCache {
|
|
31
37
|
private cache = new Map<string, Map<string, CachedRelation>>();
|
|
32
|
-
|
|
38
|
+
/** Global LRU order: composite key → true. Oldest entries at the front. */
|
|
39
|
+
private lruOrder = new Map<string, true>();
|
|
40
|
+
/** Per-type most-recent access timestamp for O(types) type eviction. */
|
|
41
|
+
private typeLastAccess = new Map<string, number>();
|
|
33
42
|
private totalEntries = 0;
|
|
34
43
|
private config: BatchLoaderConfig;
|
|
35
44
|
|
|
@@ -56,8 +65,13 @@ class BoundedRelationCache {
|
|
|
56
65
|
|
|
57
66
|
const entry = typeCache.get(parentId);
|
|
58
67
|
if (entry) {
|
|
59
|
-
|
|
60
|
-
entry.lastAccessed =
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
entry.lastAccessed = now;
|
|
70
|
+
// Move to end of lruOrder (most recently used)
|
|
71
|
+
const lk = `${cacheKey}\x00${parentId}`;
|
|
72
|
+
this.lruOrder.delete(lk);
|
|
73
|
+
this.lruOrder.set(lk, true);
|
|
74
|
+
this.typeLastAccess.set(cacheKey, now);
|
|
61
75
|
}
|
|
62
76
|
return entry;
|
|
63
77
|
}
|
|
@@ -70,10 +84,15 @@ class BoundedRelationCache {
|
|
|
70
84
|
this.evictLRUEntries(this.config.evictionBatchSize);
|
|
71
85
|
}
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
87
|
+
const isNew = !typeCache.has(parentId);
|
|
88
|
+
if (isNew) this.totalEntries++;
|
|
76
89
|
typeCache.set(parentId, entry);
|
|
90
|
+
|
|
91
|
+
// Move / insert into lruOrder end
|
|
92
|
+
const lk = `${cacheKey}\x00${parentId}`;
|
|
93
|
+
this.lruOrder.delete(lk);
|
|
94
|
+
this.lruOrder.set(lk, true);
|
|
95
|
+
this.typeLastAccess.set(cacheKey, entry.lastAccessed);
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
delete(cacheKey: string, parentId: string): boolean {
|
|
@@ -83,13 +102,18 @@ class BoundedRelationCache {
|
|
|
83
102
|
const existed = typeCache.delete(parentId);
|
|
84
103
|
if (existed) {
|
|
85
104
|
this.totalEntries--;
|
|
105
|
+
this.lruOrder.delete(`${cacheKey}\x00${parentId}`);
|
|
106
|
+
if (typeCache.size === 0) {
|
|
107
|
+
this.typeLastAccess.delete(cacheKey);
|
|
108
|
+
}
|
|
86
109
|
}
|
|
87
110
|
return existed;
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
clear(): void {
|
|
91
114
|
this.cache.clear();
|
|
92
|
-
this.
|
|
115
|
+
this.lruOrder.clear();
|
|
116
|
+
this.typeLastAccess.clear();
|
|
93
117
|
this.totalEntries = 0;
|
|
94
118
|
}
|
|
95
119
|
|
|
@@ -120,20 +144,13 @@ class BoundedRelationCache {
|
|
|
120
144
|
}
|
|
121
145
|
|
|
122
146
|
private evictOldestType(): void {
|
|
123
|
-
//
|
|
147
|
+
// O(types) scan using per-type lastAccess timestamp — no entry-level iteration.
|
|
124
148
|
let oldestKey: string | null = null;
|
|
125
|
-
let
|
|
149
|
+
let oldestAccess = Infinity;
|
|
126
150
|
|
|
127
|
-
for (const [key,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (const [, entry] of typeCache) {
|
|
131
|
-
totalAccess += entry.lastAccessed;
|
|
132
|
-
count++;
|
|
133
|
-
}
|
|
134
|
-
const avgAccess = count > 0 ? totalAccess / count : 0;
|
|
135
|
-
if (avgAccess < oldestAvgAccess) {
|
|
136
|
-
oldestAvgAccess = avgAccess;
|
|
151
|
+
for (const [key, ts] of this.typeLastAccess) {
|
|
152
|
+
if (ts < oldestAccess) {
|
|
153
|
+
oldestAccess = ts;
|
|
137
154
|
oldestKey = key;
|
|
138
155
|
}
|
|
139
156
|
}
|
|
@@ -141,28 +158,33 @@ class BoundedRelationCache {
|
|
|
141
158
|
if (oldestKey) {
|
|
142
159
|
const evictedCache = this.cache.get(oldestKey);
|
|
143
160
|
if (evictedCache) {
|
|
161
|
+
// Remove all lruOrder entries for this type
|
|
162
|
+
for (const parentId of evictedCache.keys()) {
|
|
163
|
+
this.lruOrder.delete(`${oldestKey}\x00${parentId}`);
|
|
164
|
+
}
|
|
144
165
|
this.totalEntries -= evictedCache.size;
|
|
145
166
|
}
|
|
146
167
|
this.cache.delete(oldestKey);
|
|
168
|
+
this.typeLastAccess.delete(oldestKey);
|
|
147
169
|
}
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
private evictLRUEntries(count: number): void {
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
for (const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
173
|
+
// lruOrder is in insertion order — front entries are least recently used.
|
|
174
|
+
// Collect up to `count` keys without materialising the entire map.
|
|
175
|
+
const toEvict: string[] = [];
|
|
176
|
+
for (const lk of this.lruOrder.keys()) {
|
|
177
|
+
if (toEvict.length >= count) break;
|
|
178
|
+
toEvict.push(lk);
|
|
158
179
|
}
|
|
159
180
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
for (const lk of toEvict) {
|
|
182
|
+
// cacheKey = typeId\x00fieldName, parentId = UUID — split at the last \x00
|
|
183
|
+
// so fieldNames with no null bytes are handled correctly.
|
|
184
|
+
const sep = lk.lastIndexOf('\x00');
|
|
185
|
+
if (sep === -1) continue;
|
|
186
|
+
const cacheKey = lk.slice(0, sep);
|
|
187
|
+
const parentId = lk.slice(sep + 1);
|
|
166
188
|
this.delete(cacheKey, parentId);
|
|
167
189
|
}
|
|
168
190
|
}
|
|
@@ -178,6 +200,7 @@ class BoundedRelationCache {
|
|
|
178
200
|
for (const [parentId, entry] of typeCache) {
|
|
179
201
|
if (now > entry.expiresAt) {
|
|
180
202
|
typeCache.delete(parentId);
|
|
203
|
+
this.lruOrder.delete(`${cacheKey}\x00${parentId}`);
|
|
181
204
|
this.totalEntries--;
|
|
182
205
|
prunedCount++;
|
|
183
206
|
}
|
|
@@ -185,6 +208,7 @@ class BoundedRelationCache {
|
|
|
185
208
|
// Remove empty type caches
|
|
186
209
|
if (typeCache.size === 0) {
|
|
187
210
|
this.cache.delete(cacheKey);
|
|
211
|
+
this.typeLastAccess.delete(cacheKey);
|
|
188
212
|
}
|
|
189
213
|
}
|
|
190
214
|
|