bunsane 0.3.1 → 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.
Files changed (224) hide show
  1. package/CHANGELOG.md +445 -318
  2. package/config/cache.config.ts +35 -1
  3. package/core/App.ts +24 -1064
  4. package/core/ArcheType.ts +78 -2110
  5. package/core/BatchLoader.ts +56 -32
  6. package/core/Entity.ts +85 -1043
  7. package/core/EntityHookManager.ts +52 -754
  8. package/core/Logger.ts +10 -0
  9. package/core/RequestContext.ts +64 -6
  10. package/core/RequestLoaders.ts +187 -36
  11. package/core/SchedulerManager.ts +28 -600
  12. package/core/app/bootstrap.ts +133 -0
  13. package/core/app/cors.ts +85 -0
  14. package/core/app/graphqlSetup.ts +56 -0
  15. package/core/app/healthEndpoints.ts +31 -0
  16. package/core/app/metricsCollector.ts +27 -0
  17. package/core/app/preparedStatementWarmup.ts +15 -0
  18. package/core/app/processHandlers.ts +43 -0
  19. package/core/app/requestRouter.ts +310 -0
  20. package/core/app/restRegistry.ts +80 -0
  21. package/core/app/shutdown.ts +97 -0
  22. package/core/app/studioRouter.ts +83 -0
  23. package/core/archetype/customTypes.ts +100 -0
  24. package/core/archetype/decorators.ts +171 -0
  25. package/core/archetype/fieldResolvers.ts +666 -0
  26. package/core/archetype/helpers.ts +29 -0
  27. package/core/archetype/relationLoader.ts +161 -0
  28. package/core/archetype/schemaBuilder.ts +141 -0
  29. package/core/archetype/weaver.ts +218 -0
  30. package/core/archetype/zodSchemaBuilder.ts +527 -0
  31. package/core/cache/CacheManager.ts +173 -267
  32. package/core/cache/CompressionUtils.ts +34 -3
  33. package/core/cache/MemoryCache.ts +40 -37
  34. package/core/cache/RedisCache.ts +4 -4
  35. package/core/cache/health.ts +30 -0
  36. package/core/cache/invalidation.ts +96 -0
  37. package/core/cache/strategies/writeInvalidate.ts +111 -0
  38. package/core/cache/strategies/writeThrough.ts +233 -0
  39. package/core/components/BaseComponent.ts +16 -8
  40. package/core/components/ComponentRegistry.ts +28 -0
  41. package/core/decorators/IndexedField.ts +1 -1
  42. package/core/entity/cacheStrategies.ts +97 -0
  43. package/core/entity/componentAccess.ts +364 -0
  44. package/core/entity/finders.ts +202 -0
  45. package/core/entity/pendingOps.ts +72 -0
  46. package/core/entity/saveEntity.ts +377 -0
  47. package/core/hooks/dispatcher.ts +439 -0
  48. package/core/hooks/guards.ts +155 -0
  49. package/core/hooks/registry.ts +247 -0
  50. package/core/metadata/definitions/Component.ts +1 -1
  51. package/core/metadata/index.ts +15 -4
  52. package/core/middleware/AccessLog.ts +8 -1
  53. package/core/middleware/RateLimit.ts +102 -105
  54. package/core/middleware/RequestId.ts +2 -9
  55. package/core/middleware/SecurityHeaders.ts +2 -11
  56. package/core/middleware/headers.ts +28 -0
  57. package/core/remote/OutboxWorker.ts +213 -183
  58. package/core/remote/RemoteManager.ts +401 -400
  59. package/core/remote/types.ts +153 -151
  60. package/core/requestScope.ts +34 -0
  61. package/core/scheduler/cronEvaluator.ts +174 -0
  62. package/core/scheduler/lifecycleHooks.ts +21 -0
  63. package/core/scheduler/lockCoordinator.ts +27 -0
  64. package/core/scheduler/metrics.ts +14 -0
  65. package/core/scheduler/taskRunner.ts +420 -0
  66. package/database/DatabaseHelper.ts +128 -101
  67. package/database/IndexingStrategy.ts +72 -2
  68. package/database/PreparedStatementCache.ts +20 -5
  69. package/database/cancellable.ts +35 -0
  70. package/database/index.ts +15 -3
  71. package/database/instrumentedDb.ts +141 -0
  72. package/endpoints/archetypes.ts +2 -8
  73. package/endpoints/tables.ts +6 -1
  74. package/gql/index.ts +1 -1
  75. package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
  76. package/package.json +22 -1
  77. package/query/CTENode.ts +5 -3
  78. package/query/ComponentInclusionNode.ts +240 -13
  79. package/query/OrNode.ts +6 -5
  80. package/query/Query.ts +203 -59
  81. package/query/QueryContext.ts +6 -0
  82. package/query/QueryDAG.ts +7 -2
  83. package/query/membershipSource.ts +66 -0
  84. package/storage/LocalStorageProvider.ts +8 -3
  85. package/studio/dist/assets/index-BMZ67Npg.js +254 -0
  86. package/studio/dist/assets/index-BpbuYz9g.css +1 -0
  87. package/studio/{index.html → dist/index.html} +3 -2
  88. package/swagger/generator.ts +11 -1
  89. package/upload/UploadManager.ts +8 -6
  90. package/utils/uuid.ts +40 -10
  91. package/.claude/settings.local.json +0 -47
  92. package/.prettierrc +0 -4
  93. package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
  94. package/.serena/memories/architecture.md +0 -154
  95. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
  96. package/.serena/memories/code_style_and_conventions.md +0 -76
  97. package/.serena/memories/project_overview.md +0 -43
  98. package/.serena/memories/schema-dsl-plan.md +0 -107
  99. package/.serena/memories/suggested_commands.md +0 -80
  100. package/.serena/memories/typescript-compilation-status.md +0 -54
  101. package/.serena/project.yml +0 -114
  102. package/BunSane.jpg +0 -0
  103. package/CLAUDE.md +0 -198
  104. package/TODO.md +0 -2
  105. package/bun.lock +0 -302
  106. package/bunfig.toml +0 -10
  107. package/docs/SCALABILITY_PLAN.md +0 -175
  108. package/studio/bun.lock +0 -482
  109. package/studio/package.json +0 -39
  110. package/studio/postcss.config.js +0 -6
  111. package/studio/src/components/DataTable.tsx +0 -211
  112. package/studio/src/components/Layout.tsx +0 -13
  113. package/studio/src/components/PageContainer.tsx +0 -9
  114. package/studio/src/components/PageHeader.tsx +0 -13
  115. package/studio/src/components/SearchBar.tsx +0 -57
  116. package/studio/src/components/Sidebar.tsx +0 -294
  117. package/studio/src/components/ui/button.tsx +0 -56
  118. package/studio/src/components/ui/checkbox.tsx +0 -26
  119. package/studio/src/components/ui/input.tsx +0 -25
  120. package/studio/src/hooks/useDataTable.ts +0 -131
  121. package/studio/src/index.css +0 -36
  122. package/studio/src/lib/api.ts +0 -186
  123. package/studio/src/lib/utils.ts +0 -13
  124. package/studio/src/main.tsx +0 -17
  125. package/studio/src/pages/ArcheType.tsx +0 -239
  126. package/studio/src/pages/Components.tsx +0 -124
  127. package/studio/src/pages/EntityInspector.tsx +0 -302
  128. package/studio/src/pages/QueryRunner.tsx +0 -246
  129. package/studio/src/pages/Table.tsx +0 -94
  130. package/studio/src/pages/Welcome.tsx +0 -241
  131. package/studio/src/routes.tsx +0 -45
  132. package/studio/src/store/archeTypeSettings.ts +0 -30
  133. package/studio/src/store/studio.ts +0 -65
  134. package/studio/src/utils/columnHelpers.tsx +0 -114
  135. package/studio/studio-instructions.md +0 -81
  136. package/studio/tailwind.config.js +0 -77
  137. package/studio/utils.ts +0 -54
  138. package/studio/vite.config.js +0 -19
  139. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
  140. package/tests/benchmark/bunfig.toml +0 -9
  141. package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
  142. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
  143. package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
  144. package/tests/benchmark/fixtures/index.ts +0 -6
  145. package/tests/benchmark/index.ts +0 -22
  146. package/tests/benchmark/noop-preload.ts +0 -3
  147. package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
  148. package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
  149. package/tests/benchmark/runners/index.ts +0 -4
  150. package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
  151. package/tests/benchmark/scripts/generate-db.ts +0 -344
  152. package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
  153. package/tests/e2e/http.test.ts +0 -130
  154. package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
  155. package/tests/fixtures/components/TestOrder.ts +0 -23
  156. package/tests/fixtures/components/TestProduct.ts +0 -23
  157. package/tests/fixtures/components/TestUser.ts +0 -20
  158. package/tests/fixtures/components/index.ts +0 -6
  159. package/tests/graphql/SchemaGeneration.test.ts +0 -90
  160. package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
  161. package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
  162. package/tests/helpers/MockRedisClient.ts +0 -113
  163. package/tests/helpers/MockRedisStreamServer.ts +0 -448
  164. package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
  165. package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
  166. package/tests/integration/entity/Entity.persistence.test.ts +0 -333
  167. package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
  168. package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
  169. package/tests/integration/query/Query.edgeCases.test.ts +0 -595
  170. package/tests/integration/query/Query.exec.test.ts +0 -576
  171. package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
  172. package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
  173. package/tests/integration/remote/dlq.test.ts +0 -175
  174. package/tests/integration/remote/event-dispatch.test.ts +0 -114
  175. package/tests/integration/remote/outbox.test.ts +0 -130
  176. package/tests/integration/remote/rpc.test.ts +0 -177
  177. package/tests/pglite-setup.ts +0 -62
  178. package/tests/setup.ts +0 -164
  179. package/tests/stress/BenchmarkRunner.ts +0 -203
  180. package/tests/stress/DataSeeder.ts +0 -190
  181. package/tests/stress/StressTestReporter.ts +0 -229
  182. package/tests/stress/cursor-perf-test.ts +0 -171
  183. package/tests/stress/fixtures/RealisticComponents.ts +0 -235
  184. package/tests/stress/fixtures/StressTestComponents.ts +0 -58
  185. package/tests/stress/index.ts +0 -7
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
  187. package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
  188. package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
  189. package/tests/unit/BatchLoader.test.ts +0 -196
  190. package/tests/unit/archetype/ArcheType.test.ts +0 -107
  191. package/tests/unit/cache/CacheManager.test.ts +0 -367
  192. package/tests/unit/cache/MemoryCache.test.ts +0 -260
  193. package/tests/unit/cache/RedisCache.test.ts +0 -411
  194. package/tests/unit/entity/Entity.components.test.ts +0 -317
  195. package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
  196. package/tests/unit/entity/Entity.reload.test.ts +0 -63
  197. package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
  198. package/tests/unit/entity/Entity.test.ts +0 -345
  199. package/tests/unit/gql/depthLimit.test.ts +0 -203
  200. package/tests/unit/gql/operationMiddleware.test.ts +0 -293
  201. package/tests/unit/health/Health.test.ts +0 -129
  202. package/tests/unit/middleware/AccessLog.test.ts +0 -37
  203. package/tests/unit/middleware/Middleware.test.ts +0 -98
  204. package/tests/unit/middleware/RequestId.test.ts +0 -54
  205. package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
  206. package/tests/unit/query/FilterBuilder.test.ts +0 -111
  207. package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
  208. package/tests/unit/query/Query.emptyString.test.ts +0 -69
  209. package/tests/unit/query/Query.test.ts +0 -310
  210. package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
  211. package/tests/unit/remote/RemoteError.test.ts +0 -55
  212. package/tests/unit/remote/decorators.test.ts +0 -195
  213. package/tests/unit/remote/metrics.test.ts +0 -115
  214. package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
  215. package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
  216. package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
  217. package/tests/unit/schema/schema-integration.test.ts +0 -426
  218. package/tests/unit/schema/schema.test.ts +0 -580
  219. package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
  220. package/tests/unit/upload/RestUpload.test.ts +0 -267
  221. package/tests/unit/validateEnv.test.ts +0 -82
  222. package/tests/utils/entity-tracker.ts +0 -57
  223. package/tests/utils/index.ts +0 -13
  224. package/tests/utils/test-context.ts +0 -149
@@ -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
- private accessOrder: string[] = []; // Tracks global access order for LRU eviction
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
- // Update access time for LRU
60
- entry.lastAccessed = Date.now();
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
- if (!typeCache.has(parentId)) {
74
- this.totalEntries++;
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.accessOrder = [];
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
- // Find the type cache with oldest average access time
147
+ // O(types) scan using per-type lastAccess timestamp no entry-level iteration.
124
148
  let oldestKey: string | null = null;
125
- let oldestAvgAccess = Infinity;
149
+ let oldestAccess = Infinity;
126
150
 
127
- for (const [key, typeCache] of this.cache) {
128
- let totalAccess = 0;
129
- let count = 0;
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
- // Collect all entries with their access times
152
- const entries: Array<{ cacheKey: string; parentId: string; lastAccessed: number }> = [];
153
-
154
- for (const [cacheKey, typeCache] of this.cache) {
155
- for (const [parentId, entry] of typeCache) {
156
- entries.push({ cacheKey, parentId, lastAccessed: entry.lastAccessed });
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
- // Sort by lastAccessed ascending (oldest first)
161
- entries.sort((a, b) => a.lastAccessed - b.lastAccessed);
162
-
163
- // Evict the oldest entries
164
- const toEvict = entries.slice(0, count);
165
- for (const { cacheKey, parentId } of toEvict) {
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