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