bunsane 0.1.4 → 0.2.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 (257) hide show
  1. package/.claude/settings.local.json +47 -0
  2. package/.claude/skills/update-memory.md +74 -0
  3. package/.prettierrc +4 -0
  4. package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
  5. package/.serena/memories/architecture.md +154 -0
  6. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
  7. package/.serena/memories/code_style_and_conventions.md +76 -0
  8. package/.serena/memories/project_overview.md +43 -0
  9. package/.serena/memories/schema-dsl-plan.md +107 -0
  10. package/.serena/memories/suggested_commands.md +80 -0
  11. package/.serena/memories/typescript-compilation-status.md +54 -0
  12. package/.serena/project.yml +114 -0
  13. package/TODO.md +1 -7
  14. package/bun.lock +150 -4
  15. package/bunfig.toml +10 -0
  16. package/config/cache.config.ts +77 -0
  17. package/config/upload.config.ts +4 -5
  18. package/core/App.ts +870 -123
  19. package/core/ArcheType.ts +2268 -377
  20. package/core/BatchLoader.ts +181 -71
  21. package/core/Config.ts +153 -0
  22. package/core/Decorators.ts +4 -1
  23. package/core/Entity.ts +621 -92
  24. package/core/EntityHookManager.ts +1 -1
  25. package/core/EntityInterface.ts +3 -1
  26. package/core/EntityManager.ts +1 -13
  27. package/core/ErrorHandler.ts +8 -2
  28. package/core/Logger.ts +9 -0
  29. package/core/Middleware.ts +34 -0
  30. package/core/RequestContext.ts +5 -1
  31. package/core/RequestLoaders.ts +227 -93
  32. package/core/SchedulerManager.ts +193 -52
  33. package/core/cache/CacheAnalytics.ts +399 -0
  34. package/core/cache/CacheFactory.ts +145 -0
  35. package/core/cache/CacheManager.ts +520 -0
  36. package/core/cache/CacheProvider.ts +34 -0
  37. package/core/cache/CacheWarmer.ts +157 -0
  38. package/core/cache/CompressionUtils.ts +110 -0
  39. package/core/cache/MemoryCache.ts +251 -0
  40. package/core/cache/MultiLevelCache.ts +180 -0
  41. package/core/cache/NoOpCache.ts +53 -0
  42. package/core/cache/RedisCache.ts +464 -0
  43. package/core/cache/TTLStrategy.ts +254 -0
  44. package/core/cache/index.ts +6 -0
  45. package/core/components/BaseComponent.ts +120 -0
  46. package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
  47. package/core/components/Decorators.ts +88 -0
  48. package/core/components/Interfaces.ts +7 -0
  49. package/core/components/index.ts +5 -0
  50. package/core/decorators/EntityHooks.ts +0 -3
  51. package/core/decorators/IndexedField.ts +26 -0
  52. package/core/decorators/ScheduledTask.ts +0 -47
  53. package/core/events/EntityLifecycleEvents.ts +1 -1
  54. package/core/health.ts +112 -0
  55. package/core/metadata/definitions/ArcheType.ts +14 -0
  56. package/core/metadata/definitions/Component.ts +9 -0
  57. package/core/metadata/definitions/gqlObject.ts +1 -1
  58. package/core/metadata/index.ts +42 -1
  59. package/core/metadata/metadata-storage.ts +28 -2
  60. package/core/middleware/AccessLog.ts +59 -0
  61. package/core/middleware/RequestId.ts +38 -0
  62. package/core/middleware/SecurityHeaders.ts +62 -0
  63. package/core/middleware/index.ts +3 -0
  64. package/core/scheduler/DistributedLock.ts +266 -0
  65. package/core/scheduler/index.ts +15 -0
  66. package/core/validateEnv.ts +92 -0
  67. package/database/DatabaseHelper.ts +416 -40
  68. package/database/IndexingStrategy.ts +342 -0
  69. package/database/PreparedStatementCache.ts +226 -0
  70. package/database/index.ts +32 -7
  71. package/database/sqlHelpers.ts +14 -2
  72. package/endpoints/archetypes.ts +362 -0
  73. package/endpoints/components.ts +58 -0
  74. package/endpoints/entity.ts +80 -0
  75. package/endpoints/index.ts +27 -0
  76. package/endpoints/query.ts +93 -0
  77. package/endpoints/stats.ts +76 -0
  78. package/endpoints/tables.ts +212 -0
  79. package/endpoints/types.ts +155 -0
  80. package/gql/ArchetypeOperations.ts +32 -86
  81. package/gql/Generator.ts +27 -315
  82. package/gql/GeneratorV2.ts +37 -0
  83. package/gql/builders/InputTypeBuilder.ts +99 -0
  84. package/gql/builders/ResolverBuilder.ts +234 -0
  85. package/gql/builders/TypeDefBuilder.ts +105 -0
  86. package/gql/builders/index.ts +3 -0
  87. package/gql/decorators/Upload.ts +1 -1
  88. package/gql/depthLimit.ts +85 -0
  89. package/gql/graph/GraphNode.ts +224 -0
  90. package/gql/graph/SchemaGraph.ts +278 -0
  91. package/gql/helpers.ts +8 -2
  92. package/gql/index.ts +56 -4
  93. package/gql/middleware.ts +79 -0
  94. package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
  95. package/gql/orchestration/index.ts +1 -0
  96. package/gql/scanner/ServiceScanner.ts +347 -0
  97. package/gql/schema/index.ts +458 -0
  98. package/gql/strategies/TypeGenerationStrategy.ts +329 -0
  99. package/gql/types.ts +1 -0
  100. package/gql/utils/TypeSignature.ts +220 -0
  101. package/gql/utils/index.ts +1 -0
  102. package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
  103. package/gql/visitors/DeduplicationVisitor.ts +82 -0
  104. package/gql/visitors/GraphVisitor.ts +78 -0
  105. package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
  106. package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
  107. package/gql/visitors/TypeCollectorVisitor.ts +79 -0
  108. package/gql/visitors/VisitorComposer.ts +96 -0
  109. package/gql/visitors/index.ts +7 -0
  110. package/package.json +59 -37
  111. package/plugins/index.ts +2 -2
  112. package/query/CTENode.ts +97 -0
  113. package/query/ComponentInclusionNode.ts +689 -0
  114. package/query/FilterBuilder.ts +127 -0
  115. package/query/FilterBuilderRegistry.ts +202 -0
  116. package/query/OrNode.ts +517 -0
  117. package/query/OrQuery.ts +42 -0
  118. package/query/Query.ts +1022 -0
  119. package/query/QueryContext.ts +170 -0
  120. package/query/QueryDAG.ts +122 -0
  121. package/query/QueryNode.ts +65 -0
  122. package/query/SourceNode.ts +53 -0
  123. package/query/builders/FullTextSearchBuilder.ts +236 -0
  124. package/query/index.ts +21 -0
  125. package/scheduler/index.ts +40 -8
  126. package/service/Service.ts +2 -1
  127. package/service/ServiceRegistry.ts +6 -5
  128. package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
  129. package/storage/S3StorageProvider.ts +316 -0
  130. package/{core/storage → storage}/StorageProvider.ts +7 -3
  131. package/studio/bun.lock +482 -0
  132. package/studio/index.html +13 -0
  133. package/studio/package.json +39 -0
  134. package/studio/postcss.config.js +6 -0
  135. package/studio/src/components/DataTable.tsx +211 -0
  136. package/studio/src/components/Layout.tsx +13 -0
  137. package/studio/src/components/PageContainer.tsx +9 -0
  138. package/studio/src/components/PageHeader.tsx +13 -0
  139. package/studio/src/components/SearchBar.tsx +57 -0
  140. package/studio/src/components/Sidebar.tsx +294 -0
  141. package/studio/src/components/ui/button.tsx +56 -0
  142. package/studio/src/components/ui/checkbox.tsx +26 -0
  143. package/studio/src/components/ui/input.tsx +25 -0
  144. package/studio/src/hooks/useDataTable.ts +131 -0
  145. package/studio/src/index.css +36 -0
  146. package/studio/src/lib/api.ts +186 -0
  147. package/studio/src/lib/utils.ts +13 -0
  148. package/studio/src/main.tsx +17 -0
  149. package/studio/src/pages/ArcheType.tsx +239 -0
  150. package/studio/src/pages/Components.tsx +124 -0
  151. package/studio/src/pages/EntityInspector.tsx +302 -0
  152. package/studio/src/pages/QueryRunner.tsx +246 -0
  153. package/studio/src/pages/Table.tsx +94 -0
  154. package/studio/src/pages/Welcome.tsx +241 -0
  155. package/studio/src/routes.tsx +45 -0
  156. package/studio/src/store/archeTypeSettings.ts +30 -0
  157. package/studio/src/store/studio.ts +65 -0
  158. package/studio/src/utils/columnHelpers.tsx +114 -0
  159. package/studio/studio-instructions.md +81 -0
  160. package/studio/tailwind.config.js +77 -0
  161. package/studio/tsconfig.json +24 -0
  162. package/studio/utils.ts +54 -0
  163. package/studio/vite.config.js +19 -0
  164. package/swagger/generator.ts +1 -1
  165. package/tests/e2e/http.test.ts +126 -0
  166. package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
  167. package/tests/fixtures/components/TestOrder.ts +23 -0
  168. package/tests/fixtures/components/TestProduct.ts +23 -0
  169. package/tests/fixtures/components/TestUser.ts +20 -0
  170. package/tests/fixtures/components/index.ts +6 -0
  171. package/tests/graphql/SchemaGeneration.test.ts +90 -0
  172. package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
  173. package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
  174. package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
  175. package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
  176. package/tests/integration/entity/Entity.persistence.test.ts +333 -0
  177. package/tests/integration/query/Query.exec.test.ts +523 -0
  178. package/tests/pglite-setup.ts +61 -0
  179. package/tests/setup.ts +164 -0
  180. package/tests/stress/BenchmarkRunner.ts +203 -0
  181. package/tests/stress/DataSeeder.ts +190 -0
  182. package/tests/stress/StressTestReporter.ts +229 -0
  183. package/tests/stress/cursor-perf-test.ts +171 -0
  184. package/tests/stress/fixtures/StressTestComponents.ts +58 -0
  185. package/tests/stress/index.ts +7 -0
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
  187. package/tests/unit/BatchLoader.test.ts +82 -0
  188. package/tests/unit/archetype/ArcheType.test.ts +107 -0
  189. package/tests/unit/cache/CacheManager.test.ts +347 -0
  190. package/tests/unit/cache/MemoryCache.test.ts +260 -0
  191. package/tests/unit/cache/RedisCache.test.ts +411 -0
  192. package/tests/unit/entity/Entity.components.test.ts +244 -0
  193. package/tests/unit/entity/Entity.test.ts +345 -0
  194. package/tests/unit/gql/depthLimit.test.ts +203 -0
  195. package/tests/unit/gql/operationMiddleware.test.ts +293 -0
  196. package/tests/unit/health/Health.test.ts +129 -0
  197. package/tests/unit/middleware/AccessLog.test.ts +37 -0
  198. package/tests/unit/middleware/Middleware.test.ts +98 -0
  199. package/tests/unit/middleware/RequestId.test.ts +54 -0
  200. package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
  201. package/tests/unit/query/FilterBuilder.test.ts +111 -0
  202. package/tests/unit/query/Query.test.ts +308 -0
  203. package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
  204. package/tests/unit/schema/schema-integration.test.ts +426 -0
  205. package/tests/unit/schema/schema.test.ts +580 -0
  206. package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
  207. package/tests/unit/upload/RestUpload.test.ts +267 -0
  208. package/tests/unit/validateEnv.test.ts +82 -0
  209. package/tests/utils/entity-tracker.ts +57 -0
  210. package/tests/utils/index.ts +13 -0
  211. package/tests/utils/test-context.ts +149 -0
  212. package/tsconfig.json +5 -1
  213. package/types/archetype.types.ts +6 -0
  214. package/types/hooks.types.ts +1 -1
  215. package/types/query.types.ts +110 -0
  216. package/types/scheduler.types.ts +68 -7
  217. package/types/upload.types.ts +1 -0
  218. package/{core → upload}/FileValidator.ts +10 -1
  219. package/upload/RestUpload.ts +130 -0
  220. package/{core/components → upload}/UploadComponent.ts +11 -11
  221. package/{core → upload}/UploadManager.ts +3 -3
  222. package/upload/index.ts +23 -7
  223. package/utils/UploadHelper.ts +27 -6
  224. package/utils/cronParser.ts +16 -6
  225. package/.github/workflows/deploy-docs.yml +0 -57
  226. package/core/Components.ts +0 -202
  227. package/core/EntityCache.ts +0 -15
  228. package/core/Query.ts +0 -880
  229. package/docs/README.md +0 -149
  230. package/docs/_coverpage.md +0 -36
  231. package/docs/_sidebar.md +0 -23
  232. package/docs/api/core.md +0 -568
  233. package/docs/api/hooks.md +0 -554
  234. package/docs/api/index.md +0 -222
  235. package/docs/api/query.md +0 -678
  236. package/docs/api/service.md +0 -744
  237. package/docs/core-concepts/archetypes.md +0 -512
  238. package/docs/core-concepts/components.md +0 -498
  239. package/docs/core-concepts/entity.md +0 -314
  240. package/docs/core-concepts/hooks.md +0 -683
  241. package/docs/core-concepts/query.md +0 -588
  242. package/docs/core-concepts/services.md +0 -647
  243. package/docs/examples/code-examples.md +0 -425
  244. package/docs/getting-started.md +0 -337
  245. package/docs/index.html +0 -97
  246. package/tests/bench/insert.bench.ts +0 -60
  247. package/tests/bench/relations.bench.ts +0 -270
  248. package/tests/bench/sorting.bench.ts +0 -416
  249. package/tests/component-hooks-simple.test.ts +0 -117
  250. package/tests/component-hooks.test.ts +0 -1461
  251. package/tests/component.test.ts +0 -339
  252. package/tests/errorHandling.test.ts +0 -155
  253. package/tests/hooks.test.ts +0 -667
  254. package/tests/query-sorting.test.ts +0 -101
  255. package/tests/query.test.ts +0 -81
  256. package/tests/relations.test.ts +0 -170
  257. package/tests/scheduler.test.ts +0 -724
@@ -1,270 +0,0 @@
1
- import { describe, test, beforeAll, beforeEach, expect } from "bun:test";
2
- import App from "core/App";
3
- import { BaseComponent, CompData, Component } from "core/Components";
4
- import { Entity } from "core/Entity";
5
- import { BatchLoader } from "core/BatchLoader";
6
- import db from "database";
7
-
8
- let app: App;
9
-
10
- beforeAll(async () => {
11
- app = new App();
12
- app.init();
13
- await app.waitForAppReady();
14
- });
15
-
16
- beforeEach(async () => {
17
- await db`TRUNCATE TABLE entities CASCADE;`;
18
- Bun.sleep(1000);
19
- });
20
-
21
- @Component
22
- class AuthorComponent extends BaseComponent {
23
- @CompData()
24
- value: string = "";
25
- }
26
-
27
- @Component
28
- class TitleComponent extends BaseComponent {
29
- @CompData()
30
- value: string = "";
31
- }
32
-
33
- @Component
34
- class UserComponent extends BaseComponent {
35
- @CompData()
36
- name: string = "";
37
- }
38
-
39
- describe('Relations Benchmark - Performance Guarantees', () => {
40
- /**
41
- * PERFORMANCE GUARANTEE MATRIX
42
- * ============================
43
- *
44
- * Based on comprehensive benchmarking, BunSane provides the following
45
- * measurable performance guarantees for relation loading:
46
- *
47
- * SCALE GUARANTEES:
48
- * - 1,000 posts + 100 users: < 100ms (typical: ~50ms)
49
- * - 5,000 posts + 500 users: < 300ms (typical: ~100ms)
50
- * - 10,000 posts + 1,000 users: < 500ms (typical: ~120ms)
51
- *
52
- * SCALABILITY GUARANTEES:
53
- * - Linear scaling: Performance grows sub-linearly with dataset size
54
- * - Batch efficiency: 10x data increase results in <3x time increase
55
- * - Consistency: Standard deviation < 20% of average time
56
- *
57
- * MEMORY GUARANTEES:
58
- * - Memory overhead: < 50MB for loading 1,000 unique entities
59
- * - No memory leaks: Efficient garbage collection
60
- * - Batched loading: No memory proportional to total relations
61
- *
62
- * QUERY EFFICIENCY GUARANTEES:
63
- * - N+1 prevention: Constant query count regardless of relation count
64
- * - Batch optimization: Single query for relation data + entity loading
65
- * - Index utilization: Leverages PostgreSQL GIN indexes for JSON data
66
- */
67
- test('Guaranteed linear scalability for batched relations', async () => {
68
- const scales = [
69
- { users: 100, posts: 1000, maxTime: 100 },
70
- { users: 500, posts: 5000, maxTime: 300 },
71
- { users: 1000, posts: 10000, maxTime: 500 }
72
- ];
73
-
74
- const results = [];
75
-
76
- for (const scale of scales) {
77
- // Create users
78
- const users: Entity[] = [];
79
- for (let i = 0; i < scale.users; i++) {
80
- const user = Entity.Create().add(UserComponent, { name: `User${i}` });
81
- users.push(user);
82
- }
83
- await Promise.all(users.map(u => u.save()));
84
-
85
- // Create posts with random authors
86
- const batchSize = 1000;
87
- const posts: Entity[] = [];
88
- const batches = Math.ceil(scale.posts / batchSize);
89
-
90
- for (let batch = 0; batch < batches; batch++) {
91
- const batchPosts: Entity[] = [];
92
- const start = batch * batchSize;
93
- const end = Math.min(start + batchSize, scale.posts);
94
-
95
- for (let i = start; i < end; i++) {
96
- const randomUser = users[Math.floor(Math.random() * users.length)]!;
97
- const post = Entity.Create()
98
- .add(TitleComponent, { value: `Post${i}` })
99
- .add(AuthorComponent, { value: randomUser.id });
100
- batchPosts.push(post);
101
- }
102
- await Promise.all(batchPosts.map(p => p.save()));
103
- posts.push(...batchPosts);
104
- }
105
-
106
- // Benchmark batched loading with multiple runs for consistency
107
- const runs = 3;
108
- const times = [];
109
-
110
- for (let run = 0; run < runs; run++) {
111
- const startTime = performance.now();
112
- const loader = async (ids: string[]) => {
113
- return await Entity.LoadMultiple(ids);
114
- };
115
-
116
- const result = await BatchLoader.loadRelatedEntitiesBatched(posts, AuthorComponent, loader);
117
- const endTime = performance.now();
118
-
119
- const time = endTime - startTime;
120
- times.push(time);
121
-
122
- // Validate result correctness
123
- expect(result.size).toBe(scale.users);
124
- }
125
-
126
- const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
127
- const maxTime = Math.max(...times);
128
- const stdDev = Math.sqrt(times.reduce((sum, time) => sum + Math.pow(time - avgTime, 2), 0) / times.length);
129
-
130
- results.push({
131
- scale,
132
- avgTime,
133
- maxTime,
134
- stdDev,
135
- times
136
- });
137
-
138
- console.log(`Scale ${scale.posts} posts, ${scale.users} users:`);
139
- console.log(` Average: ${avgTime.toFixed(2)}ms`);
140
- console.log(` Maximum: ${maxTime.toFixed(2)}ms`);
141
- console.log(` Std Dev: ${stdDev.toFixed(2)}ms`);
142
- console.log(` All runs: [${times.map(t => t.toFixed(2)).join(', ')}]ms`);
143
-
144
- // PERFORMANCE GUARANTEE: Must complete within expected time
145
- expect(maxTime).toBeLessThan(scale.maxTime);
146
-
147
- // CONSISTENCY GUARANTEE: Standard deviation should be low (< 20% of average)
148
- expect(stdDev).toBeLessThan(avgTime * 0.2);
149
-
150
- await db`TRUNCATE TABLE entities CASCADE;`;
151
- }
152
-
153
- // SCALABILITY GUARANTEE: Performance should scale roughly linearly
154
- for (let i = 1; i < results.length; i++) {
155
- const prev = results[i - 1]!;
156
- const curr = results[i]!;
157
- const scaleRatio = curr.scale.posts / prev.scale.posts;
158
- const timeRatio = curr.avgTime / prev.avgTime;
159
-
160
- console.log(`Scale ratio: ${scaleRatio.toFixed(2)}x, Time ratio: ${timeRatio.toFixed(2)}x`);
161
-
162
- // Time should not grow faster than 1.5x the scale ratio
163
- expect(timeRatio).toBeLessThan(scaleRatio * 1.5);
164
- }
165
- });
166
-
167
- test('N+1 prevention guarantee', async () => {
168
- // Create test data
169
- const users: Entity[] = [];
170
- for (let i = 0; i < 50; i++) {
171
- const user = Entity.Create().add(UserComponent, { name: `User${i}` });
172
- users.push(user);
173
- }
174
- await Promise.all(users.map(u => u.save()));
175
-
176
- const posts: Entity[] = [];
177
- for (let i = 0; i < 500; i++) {
178
- const randomUser = users[Math.floor(Math.random() * users.length)]!;
179
- const post = Entity.Create()
180
- .add(TitleComponent, { value: `Post${i}` })
181
- .add(AuthorComponent, { value: randomUser.id });
182
- posts.push(post);
183
- }
184
- await Promise.all(posts.map(p => p.save()));
185
-
186
- // Count database queries during batched loading by monitoring logs
187
- const loader = async (ids: string[]) => {
188
- return await Entity.LoadMultiple(ids);
189
- };
190
-
191
- const startTime = performance.now();
192
- const result = await BatchLoader.loadRelatedEntitiesBatched(posts, AuthorComponent, loader);
193
- const endTime = performance.now();
194
-
195
- const batchedTime = endTime - startTime;
196
-
197
- console.log(`Batched loading: ${batchedTime.toFixed(2)}ms`);
198
- console.log(`Loaded ${result.size} unique authors for ${posts.length} posts`);
199
-
200
- // BATCHING EFFICIENCY GUARANTEE: Should efficiently batch queries
201
- // The batched approach should complete quickly due to reduced query overhead
202
-
203
- // PERFORMANCE GUARANTEE: Should complete within reasonable time
204
- expect(batchedTime).toBeLessThan(200);
205
-
206
- // PERFORMANCE GUARANTEE: Should complete within reasonable time
207
- expect(batchedTime).toBeLessThan(200);
208
-
209
- // CORRECTNESS GUARANTEE: Should load all unique authors
210
- expect(result.size).toBeGreaterThan(0);
211
- expect(result.size).toBeLessThanOrEqual(users.length);
212
- });
213
-
214
- test('Memory efficiency guarantee', async () => {
215
- const initialMemory = process.memoryUsage();
216
-
217
- // Create large dataset
218
- const users: Entity[] = [];
219
- for (let i = 0; i < 1000; i++) {
220
- const user = Entity.Create().add(UserComponent, { name: `User${i}` });
221
- users.push(user);
222
- }
223
- await Promise.all(users.map(u => u.save()));
224
-
225
- const posts: Entity[] = [];
226
- for (let i = 0; i < 10000; i++) {
227
- const randomUser = users[Math.floor(Math.random() * users.length)]!;
228
- const post = Entity.Create()
229
- .add(TitleComponent, { value: `Post${i}` })
230
- .add(AuthorComponent, { value: randomUser.id });
231
- posts.push(post);
232
- }
233
- await Promise.all(posts.map(p => p.save()));
234
-
235
- const beforeLoadMemory = process.memoryUsage();
236
-
237
- // Perform batched loading
238
- const loader = async (ids: string[]) => {
239
- return await Entity.LoadMultiple(ids);
240
- };
241
-
242
- const result = await BatchLoader.loadRelatedEntitiesBatched(posts, AuthorComponent, loader);
243
-
244
- const afterLoadMemory = process.memoryUsage();
245
-
246
- // Force garbage collection if available
247
- if (global.gc) {
248
- global.gc();
249
- }
250
-
251
- const afterGCMemory = process.memoryUsage();
252
-
253
- const loadMemoryIncrease = afterLoadMemory.heapUsed - beforeLoadMemory.heapUsed;
254
- const finalMemoryIncrease = afterGCMemory.heapUsed - initialMemory.heapUsed;
255
-
256
- console.log(`Initial memory: ${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
257
- console.log(`Before load: ${(beforeLoadMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
258
- console.log(`After load: ${(afterLoadMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
259
- console.log(`After GC: ${(afterGCMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
260
- console.log(`Load memory increase: ${(loadMemoryIncrease / 1024 / 1024).toFixed(2)} MB`);
261
- console.log(`Final memory increase: ${(finalMemoryIncrease / 1024 / 1024).toFixed(2)} MB`);
262
-
263
- // MEMORY EFFICIENCY GUARANTEE: Should not use excessive memory
264
- // Loading 1000 unique entities should use less than 50MB additional memory
265
- expect(loadMemoryIncrease).toBeLessThan(50 * 1024 * 1024); // 50MB
266
-
267
- // CORRECTNESS GUARANTEE
268
- expect(result.size).toBe(users.length);
269
- });
270
- });
@@ -1,416 +0,0 @@
1
- import { describe, test, beforeAll, beforeEach, expect } from "bun:test";
2
- import App from "core/App";
3
- import { BaseComponent, CompData, Component } from "core/Components";
4
- import { Entity } from "core/Entity";
5
- import Query from "core/Query";
6
- import db from "database";
7
- import ComponentRegistry from "core/ComponentRegistry";
8
-
9
- let app: App;
10
-
11
- beforeAll(async () => {
12
- app = new App();
13
- app.init();
14
- await app.waitForAppReady();
15
- });
16
-
17
- beforeEach(async () => {
18
- await db`TRUNCATE TABLE entities CASCADE;`;
19
- await Bun.sleep(1000);
20
- });
21
-
22
- @Component
23
- class UserComponent extends BaseComponent {
24
- @CompData()
25
- name: string = "";
26
-
27
- @CompData()
28
- age: number = 0;
29
-
30
- @CompData()
31
- score: number = 0;
32
-
33
- @CompData()
34
- createdAt: string = "";
35
- }
36
-
37
- @Component
38
- class PostComponent extends BaseComponent {
39
- @CompData()
40
- title: string = "";
41
-
42
- @CompData()
43
- content: string = "";
44
-
45
- @CompData()
46
- likes: number = 0;
47
-
48
- @CompData()
49
- publishedAt: string = "";
50
- }
51
-
52
- describe('Sorting Benchmark - Performance Guarantees', () => {
53
- /**
54
- * SORTING PERFORMANCE GUARANTEE MATRIX
55
- * ====================================
56
- *
57
- * Based on comprehensive benchmarking, BunSane provides the following
58
- * measurable performance guarantees for sorting operations:
59
- *
60
- * SCALE GUARANTEES:
61
- * - 1,000 entities: < 50ms (typical: ~20ms)
62
- * - 5,000 entities: < 150ms (typical: ~60ms)
63
- * - 10,000 entities: < 300ms (typical: ~100ms)
64
- * - 50,000 entities: < 800ms (typical: ~300ms)
65
- *
66
- * SCALABILITY GUARANTEES:
67
- * - Linear scaling: Performance grows sub-linearly with dataset size
68
- * - Sort efficiency: 10x data increase results in <4x time increase
69
- * - Consistency: Standard deviation < 15% of average time
70
- *
71
- * MEMORY GUARANTEES:
72
- * - Memory overhead: < 30MB for sorting 10,000 entities
73
- * - No memory leaks: Efficient garbage collection
74
- * - Streaming processing: Memory usage doesn't scale linearly with data size
75
- *
76
- * QUERY EFFICIENCY GUARANTEES:
77
- * - Single query: Sorting operations use single optimized SQL query
78
- * - Index utilization: Leverages PostgreSQL JSONB GIN indexes
79
- * - No N+1: Sorting doesn't trigger additional queries
80
- */
81
- test('Guaranteed linear scalability for sorting operations', async () => {
82
- await db`TRUNCATE TABLE entities CASCADE;`;
83
- const scales = [
84
- { entities: 1000, maxTime: 50 },
85
- { entities: 5000, maxTime: 150 },
86
- { entities: 10000, maxTime: 300 },
87
- { entities: 50000, maxTime: 800 }
88
- ];
89
-
90
- const results = [];
91
-
92
- for (const scale of scales) {
93
- // Create test entities with varied data for realistic sorting
94
- const entities: Entity[] = [];
95
- const batchSize = 1000;
96
- const batches = Math.ceil(scale.entities / batchSize);
97
-
98
- for (let batch = 0; batch < batches; batch++) {
99
- const batchEntities: Entity[] = [];
100
- const start = batch * batchSize;
101
- const end = Math.min(start + batchSize, scale.entities);
102
-
103
- for (let i = start; i < end; i++) {
104
- const user = Entity.Create().add(UserComponent, {
105
- name: `User${i}`,
106
- age: Math.floor(Math.random() * 80) + 18, // 18-98 years old
107
- score: Math.floor(Math.random() * 1000), // 0-999 score
108
- createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString()
109
- });
110
- batchEntities.push(user);
111
- }
112
- await Promise.all(batchEntities.map(e => e.save()));
113
- entities.push(...batchEntities);
114
- }
115
-
116
- // Benchmark different sorting scenarios
117
- const sortScenarios = [
118
- { name: 'Single property ASC', query: () => new Query().with(UserComponent).sortBy(UserComponent, "age", "ASC") },
119
- { name: 'Single property DESC', query: () => new Query().with(UserComponent).sortBy(UserComponent, "score", "DESC") },
120
- { name: 'Multiple properties', query: () => new Query().with(UserComponent).orderBy([
121
- { component: "UserComponent", property: "age", direction: "DESC" },
122
- { component: "UserComponent", property: "name", direction: "ASC" }
123
- ])},
124
- { name: 'With nulls first', query: () => new Query().with(UserComponent).sortBy(UserComponent, "score", "ASC", true) }
125
- ];
126
-
127
- const scenarioResults = [];
128
-
129
- for (const scenario of sortScenarios) {
130
- // Multiple runs for better consistency statistics
131
- const runs = 5;
132
- const times = [];
133
-
134
- for (let run = 0; run < runs; run++) {
135
- // Add small delay between runs to reduce caching effects
136
- if (run > 0) {
137
- await new Promise(resolve => setTimeout(resolve, 10));
138
- }
139
-
140
- const startTime = performance.now();
141
-
142
- const result = await scenario.query().exec();
143
-
144
- const endTime = performance.now();
145
- const time = endTime - startTime;
146
- times.push(time);
147
-
148
- // Validate result correctness (only for first run to save time)
149
- if (run === 0) {
150
- expect(result.length).toBe(scale.entities);
151
- if (scenario.name === 'Single property ASC') {
152
- // Verify sorting order for first scenario
153
- const ages = await Promise.all(result.map(async (e) => {
154
- const comp = await e.get(UserComponent);
155
- return comp?.age || 0;
156
- }));
157
- for (let i = 1; i < ages.length; i++) {
158
- expect(ages[i]).toBeGreaterThanOrEqual(ages[i - 1]!);
159
- }
160
- }
161
- }
162
- }
163
-
164
- const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
165
- const maxTime = Math.max(...times);
166
- const stdDev = Math.sqrt(times.reduce((sum, time) => sum + Math.pow(time - avgTime, 2), 0) / times.length);
167
-
168
- // Use median for more robust consistency check
169
- const sortedTimes = [...times].sort((a, b) => a - b);
170
- const medianTime = sortedTimes[Math.floor(sortedTimes.length / 2)]!;
171
-
172
- scenarioResults.push({
173
- scenario: scenario.name,
174
- avgTime,
175
- maxTime,
176
- stdDev,
177
- medianTime,
178
- times
179
- });
180
-
181
- console.log(`${scenario.name} - Scale ${scale.entities} entities:`);
182
- console.log(` Average: ${avgTime.toFixed(2)}ms`);
183
- console.log(` Median: ${medianTime.toFixed(2)}ms`);
184
- console.log(` Maximum: ${maxTime.toFixed(2)}ms`);
185
- console.log(` Std Dev: ${stdDev.toFixed(2)}ms`);
186
- console.log(` All runs: [${times.map(t => t.toFixed(2)).join(', ')}]ms`);
187
-
188
- // PERFORMANCE GUARANTEE: Must complete within expected time
189
- expect(maxTime).toBeLessThan(scale.maxTime);
190
-
191
- // CONSISTENCY GUARANTEE: Use median-based check for robustness
192
- // Allow up to 50% variance from median for database operations
193
- const maxVariance = medianTime * 0.50;
194
- expect(stdDev).toBeLessThan(maxVariance);
195
- }
196
-
197
- results.push({
198
- scale,
199
- scenarios: scenarioResults
200
- });
201
-
202
- await db`TRUNCATE TABLE entities CASCADE;`;
203
- }
204
-
205
- // SCALABILITY GUARANTEE: Performance should scale roughly linearly
206
- for (let i = 1; i < results.length; i++) {
207
- const prev = results[i - 1]!;
208
- const curr = results[i]!;
209
-
210
- for (const scenario of curr.scenarios) {
211
- const prevScenario = prev.scenarios.find(s => s.scenario === scenario.scenario);
212
- if (prevScenario) {
213
- const scaleRatio = curr.scale.entities / prev.scale.entities;
214
- const timeRatio = scenario.avgTime / prevScenario.avgTime;
215
-
216
- console.log(`${scenario.scenario} - Scale ratio: ${scaleRatio.toFixed(2)}x, Time ratio: ${timeRatio.toFixed(2)}x`);
217
-
218
- // Time should not grow faster than 4x the scale ratio for sorting operations
219
- expect(timeRatio).toBeLessThan(scaleRatio * 4);
220
- }
221
- }
222
- }
223
- }, 60000);
224
-
225
- test('Memory efficiency guarantee for sorting', async () => {
226
- await db`TRUNCATE TABLE entities CASCADE;`;
227
- const initialMemory = process.memoryUsage();
228
-
229
- // Create large dataset
230
- const entities: Entity[] = [];
231
- for (let i = 0; i < 10000; i++) {
232
- const user = Entity.Create().add(UserComponent, {
233
- name: `User${i}`,
234
- age: Math.floor(Math.random() * 80) + 18,
235
- score: Math.floor(Math.random() * 1000),
236
- createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString()
237
- });
238
- entities.push(user);
239
- }
240
- await Promise.all(entities.map(e => e.save()));
241
-
242
- const beforeSortMemory = process.memoryUsage();
243
-
244
- // Perform sorting operation
245
- const startTime = performance.now();
246
- const sortedEntities = await new Query()
247
- .with(UserComponent)
248
- .sortBy(UserComponent, "score", "DESC")
249
- .exec();
250
- const endTime = performance.now();
251
-
252
- const afterSortMemory = process.memoryUsage();
253
-
254
- // Force garbage collection if available
255
- if (global.gc) {
256
- global.gc();
257
- }
258
-
259
- const afterGCMemory = process.memoryUsage();
260
-
261
- const sortMemoryIncrease = afterSortMemory.heapUsed - beforeSortMemory.heapUsed;
262
- const finalMemoryIncrease = afterGCMemory.heapUsed - initialMemory.heapUsed;
263
- const sortTime = endTime - startTime;
264
-
265
- console.log(`Initial memory: ${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
266
- console.log(`Before sort: ${(beforeSortMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
267
- console.log(`After sort: ${(afterSortMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
268
- console.log(`After GC: ${(afterGCMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`);
269
- console.log(`Sort memory increase: ${(sortMemoryIncrease / 1024 / 1024).toFixed(2)} MB`);
270
- console.log(`Final memory increase: ${(finalMemoryIncrease / 1024 / 1024).toFixed(2)} MB`);
271
- console.log(`Sort time: ${sortTime.toFixed(2)}ms`);
272
-
273
- // MEMORY EFFICIENCY GUARANTEE: Sorting should not use excessive memory
274
- // Sorting 10,000 entities should use less than 30MB additional memory
275
- expect(sortMemoryIncrease).toBeLessThan(30 * 1024 * 1024); // 30MB
276
-
277
- // PERFORMANCE GUARANTEE: Should complete within reasonable time
278
- expect(sortTime).toBeLessThan(300); // 300ms
279
-
280
- // CORRECTNESS GUARANTEE: Should return all entities
281
- expect(sortedEntities.length).toBe(10000);
282
-
283
- // Verify sorting order
284
- const scores = await Promise.all(sortedEntities.map(async (e) => {
285
- const comp = await e.get(UserComponent);
286
- return comp?.score || 0;
287
- }));
288
- for (let i = 1; i < scores.length; i++) {
289
- expect(scores[i]).toBeLessThanOrEqual(scores[i - 1]!);
290
- }
291
- });
292
-
293
- test('Query efficiency guarantee for sorting', async () => {
294
- await db`TRUNCATE TABLE entities CASCADE;`;
295
- // Create test data
296
- const entities: Entity[] = [];
297
- for (let i = 0; i < 5000; i++) {
298
- const user = Entity.Create().add(UserComponent, {
299
- name: `User${i}`,
300
- age: Math.floor(Math.random() * 80) + 18,
301
- score: Math.floor(Math.random() * 1000),
302
- createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString()
303
- });
304
- entities.push(user);
305
- }
306
- await Promise.all(entities.map(e => e.save()));
307
-
308
- // Test different sorting scenarios
309
- const scenarios = [
310
- { name: 'Simple sort', query: new Query().with(UserComponent).sortBy(UserComponent, "age", "ASC") },
311
- { name: 'Complex sort', query: new Query().with(UserComponent).orderBy([
312
- { component: "UserComponent", property: "score", direction: "DESC" },
313
- { component: "UserComponent", property: "name", direction: "ASC" }
314
- ])},
315
- { name: 'Sort with filter', query: new Query()
316
- .with(UserComponent, Query.filters(Query.filter("age", ">", 30)))
317
- .sortBy(UserComponent, "score", "DESC") },
318
- { name: 'Sort with pagination', query: new Query()
319
- .with(UserComponent)
320
- .sortBy(UserComponent, "age", "DESC")
321
- .take(100)
322
- .offset(100) }
323
- ];
324
-
325
- for (const scenario of scenarios) {
326
- const startTime = performance.now();
327
- const result = await scenario.query.exec();
328
- const endTime = performance.now();
329
-
330
- const queryTime = endTime - startTime;
331
-
332
- console.log(`${scenario.name}:`);
333
- console.log(` Time: ${queryTime.toFixed(2)}ms`);
334
- console.log(` Results: ${result.length}`);
335
-
336
- // PERFORMANCE GUARANTEE: Should complete within reasonable time
337
- expect(queryTime).toBeLessThan(200);
338
-
339
- // CORRECTNESS GUARANTEE: Should return expected number of results
340
- if (scenario.name === 'Sort with pagination') {
341
- expect(result.length).toBeLessThanOrEqual(100);
342
- } else if (scenario.name === 'Sort with filter') {
343
- expect(result.length).toBeLessThan(5000);
344
- } else {
345
- expect(result.length).toBe(5000);
346
- }
347
- }
348
- });
349
-
350
- test('Sorting correctness guarantee', async () => {
351
- await db`TRUNCATE TABLE entities CASCADE;`;
352
- // Create predictable test data
353
- const entities: Entity[] = [];
354
- const testData = [
355
- { name: "Alice", age: 25, score: 100, createdAt: "2023-01-01T00:00:00Z" },
356
- { name: "Bob", age: 30, score: 95, createdAt: "2023-01-02T00:00:00Z" },
357
- { name: "Charlie", age: 20, score: 110, createdAt: "2023-01-03T00:00:00Z" },
358
- { name: "Diana", age: 35, score: 85, createdAt: "2023-01-04T00:00:00Z" },
359
- { name: "Eve", age: 28, score: 105, createdAt: "2023-01-05T00:00:00Z" }
360
- ];
361
-
362
- for (const data of testData) {
363
- const user = Entity.Create().add(UserComponent, data);
364
- entities.push(user);
365
- }
366
- await Promise.all(entities.map(e => e.save()));
367
-
368
- // Test ascending sort by age
369
- const ageAscResult = await new Query()
370
- .with(UserComponent)
371
- .sortBy(UserComponent, "age", "ASC")
372
- .exec();
373
-
374
- const ageAscValues = await Promise.all(ageAscResult.map(async (e) => {
375
- const comp = await e.get(UserComponent);
376
- return comp?.age || 0;
377
- }));
378
- expect(ageAscValues).toEqual([20, 25, 28, 30, 35]);
379
-
380
- // Test descending sort by score
381
- const scoreDescResult = await new Query()
382
- .with(UserComponent)
383
- .sortBy(UserComponent, "score", "DESC")
384
- .exec();
385
-
386
- const scoreDescValues = await Promise.all(scoreDescResult.map(async (e) => {
387
- const comp = await e.get(UserComponent);
388
- return comp?.score || 0;
389
- }));
390
- expect(scoreDescValues).toEqual([110, 105, 100, 95, 85]);
391
-
392
- // Test multi-property sort (age DESC, then name ASC)
393
- const multiSortResult = await new Query()
394
- .with(UserComponent)
395
- .orderBy([
396
- { component: "UserComponent", property: "age", direction: "DESC" },
397
- { component: "UserComponent", property: "name", direction: "ASC" }
398
- ])
399
- .exec();
400
-
401
- const multiSortValues = await Promise.all(multiSortResult.map(async (e) => {
402
- const comp = await e.get(UserComponent);
403
- return {
404
- age: comp?.age || 0,
405
- name: comp?.name || ""
406
- };
407
- }));
408
-
409
- // Should be sorted by age DESC: 35, 30, 28, 25, 20
410
- expect(multiSortValues[0]).toEqual({ age: 35, name: "Diana" });
411
- expect(multiSortValues[1]).toEqual({ age: 30, name: "Bob" });
412
- expect(multiSortValues[2]).toEqual({ age: 28, name: "Eve" });
413
- expect(multiSortValues[3]).toEqual({ age: 25, name: "Alice" });
414
- expect(multiSortValues[4]).toEqual({ age: 20, name: "Charlie" });
415
- });
416
- });