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.
Files changed (214) hide show
  1. package/CHANGELOG.md +445 -370
  2. package/core/BatchLoader.ts +56 -32
  3. package/core/Entity.ts +85 -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 +4 -4
  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 +16 -8
  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 +364 -0
  28. package/core/entity/finders.ts +202 -0
  29. package/core/entity/pendingOps.ts +72 -0
  30. package/core/entity/saveEntity.ts +377 -0
  31. package/core/hooks/dispatcher.ts +439 -0
  32. package/core/hooks/guards.ts +155 -0
  33. package/core/hooks/registry.ts +247 -0
  34. package/core/metadata/definitions/Component.ts +1 -1
  35. package/core/metadata/index.ts +15 -4
  36. package/core/middleware/RateLimit.ts +102 -105
  37. package/core/middleware/RequestId.ts +2 -9
  38. package/core/middleware/SecurityHeaders.ts +2 -11
  39. package/core/middleware/headers.ts +28 -0
  40. package/core/remote/OutboxWorker.ts +213 -183
  41. package/core/remote/RemoteManager.ts +401 -400
  42. package/core/remote/types.ts +153 -151
  43. package/core/requestScope.ts +34 -0
  44. package/core/scheduler/cronEvaluator.ts +174 -0
  45. package/core/scheduler/lifecycleHooks.ts +21 -0
  46. package/core/scheduler/lockCoordinator.ts +27 -0
  47. package/core/scheduler/metrics.ts +14 -0
  48. package/core/scheduler/taskRunner.ts +420 -0
  49. package/database/DatabaseHelper.ts +128 -101
  50. package/database/IndexingStrategy.ts +72 -2
  51. package/database/PreparedStatementCache.ts +8 -2
  52. package/database/cancellable.ts +35 -22
  53. package/database/index.ts +15 -3
  54. package/database/instrumentedDb.ts +141 -141
  55. package/endpoints/archetypes.ts +2 -8
  56. package/endpoints/tables.ts +6 -1
  57. package/gql/index.ts +1 -1
  58. package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
  59. package/package.json +22 -1
  60. package/query/CTENode.ts +5 -3
  61. package/query/ComponentInclusionNode.ts +240 -13
  62. package/query/OrNode.ts +6 -5
  63. package/query/Query.ts +157 -46
  64. package/query/QueryContext.ts +6 -0
  65. package/query/QueryDAG.ts +7 -2
  66. package/query/membershipSource.ts +66 -0
  67. package/storage/LocalStorageProvider.ts +8 -3
  68. package/studio/dist/assets/index-BMZ67Npg.js +254 -0
  69. package/studio/dist/assets/index-BpbuYz9g.css +1 -0
  70. package/studio/{index.html → dist/index.html} +3 -2
  71. package/swagger/generator.ts +11 -1
  72. package/upload/UploadManager.ts +8 -6
  73. package/utils/uuid.ts +40 -10
  74. package/.claude/scheduled_tasks.lock +0 -1
  75. package/.claude/settings.local.json +0 -47
  76. package/.prettierrc +0 -4
  77. package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
  78. package/.serena/memories/architecture.md +0 -154
  79. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
  80. package/.serena/memories/code_style_and_conventions.md +0 -76
  81. package/.serena/memories/project_overview.md +0 -43
  82. package/.serena/memories/schema-dsl-plan.md +0 -107
  83. package/.serena/memories/suggested_commands.md +0 -80
  84. package/.serena/memories/typescript-compilation-status.md +0 -54
  85. package/.serena/project.yml +0 -114
  86. package/BunSane.jpg +0 -0
  87. package/CLAUDE.md +0 -198
  88. package/TODO.md +0 -2
  89. package/bun.lock +0 -302
  90. package/bunfig.toml +0 -10
  91. package/docs/RFC_APP_REFACTOR.md +0 -248
  92. package/docs/RFC_REFACTOR_TARGETS.md +0 -251
  93. package/docs/SCALABILITY_PLAN.md +0 -175
  94. package/studio/bun.lock +0 -482
  95. package/studio/package.json +0 -39
  96. package/studio/postcss.config.js +0 -6
  97. package/studio/src/components/DataTable.tsx +0 -211
  98. package/studio/src/components/Layout.tsx +0 -13
  99. package/studio/src/components/PageContainer.tsx +0 -9
  100. package/studio/src/components/PageHeader.tsx +0 -13
  101. package/studio/src/components/SearchBar.tsx +0 -57
  102. package/studio/src/components/Sidebar.tsx +0 -294
  103. package/studio/src/components/ui/button.tsx +0 -56
  104. package/studio/src/components/ui/checkbox.tsx +0 -26
  105. package/studio/src/components/ui/input.tsx +0 -25
  106. package/studio/src/hooks/useDataTable.ts +0 -131
  107. package/studio/src/index.css +0 -36
  108. package/studio/src/lib/api.ts +0 -186
  109. package/studio/src/lib/utils.ts +0 -13
  110. package/studio/src/main.tsx +0 -17
  111. package/studio/src/pages/ArcheType.tsx +0 -239
  112. package/studio/src/pages/Components.tsx +0 -124
  113. package/studio/src/pages/EntityInspector.tsx +0 -302
  114. package/studio/src/pages/QueryRunner.tsx +0 -246
  115. package/studio/src/pages/Table.tsx +0 -94
  116. package/studio/src/pages/Welcome.tsx +0 -241
  117. package/studio/src/routes.tsx +0 -45
  118. package/studio/src/store/archeTypeSettings.ts +0 -30
  119. package/studio/src/store/studio.ts +0 -65
  120. package/studio/src/utils/columnHelpers.tsx +0 -114
  121. package/studio/studio-instructions.md +0 -81
  122. package/studio/tailwind.config.js +0 -77
  123. package/studio/utils.ts +0 -54
  124. package/studio/vite.config.js +0 -19
  125. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
  126. package/tests/benchmark/bunfig.toml +0 -9
  127. package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
  128. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
  129. package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
  130. package/tests/benchmark/fixtures/index.ts +0 -6
  131. package/tests/benchmark/index.ts +0 -22
  132. package/tests/benchmark/noop-preload.ts +0 -3
  133. package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
  134. package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
  135. package/tests/benchmark/runners/index.ts +0 -4
  136. package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
  137. package/tests/benchmark/scripts/generate-db.ts +0 -344
  138. package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
  139. package/tests/e2e/http.test.ts +0 -130
  140. package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
  141. package/tests/fixtures/components/TestOrder.ts +0 -23
  142. package/tests/fixtures/components/TestProduct.ts +0 -23
  143. package/tests/fixtures/components/TestUser.ts +0 -20
  144. package/tests/fixtures/components/index.ts +0 -6
  145. package/tests/graphql/SchemaGeneration.test.ts +0 -90
  146. package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
  147. package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
  148. package/tests/helpers/MockRedisClient.ts +0 -113
  149. package/tests/helpers/MockRedisStreamServer.ts +0 -448
  150. package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
  151. package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
  152. package/tests/integration/entity/Entity.persistence.test.ts +0 -333
  153. package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
  154. package/tests/integration/loaders/RequestLoaders.abort.test.ts +0 -82
  155. package/tests/integration/query/Query.abort.test.ts +0 -66
  156. package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
  157. package/tests/integration/query/Query.edgeCases.test.ts +0 -595
  158. package/tests/integration/query/Query.exec.test.ts +0 -576
  159. package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
  160. package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
  161. package/tests/integration/remote/dlq.test.ts +0 -175
  162. package/tests/integration/remote/event-dispatch.test.ts +0 -114
  163. package/tests/integration/remote/outbox.test.ts +0 -130
  164. package/tests/integration/remote/rpc.test.ts +0 -177
  165. package/tests/pglite-setup.ts +0 -62
  166. package/tests/setup.ts +0 -164
  167. package/tests/stress/BenchmarkRunner.ts +0 -203
  168. package/tests/stress/DataSeeder.ts +0 -190
  169. package/tests/stress/StressTestReporter.ts +0 -229
  170. package/tests/stress/cursor-perf-test.ts +0 -171
  171. package/tests/stress/fixtures/RealisticComponents.ts +0 -235
  172. package/tests/stress/fixtures/StressTestComponents.ts +0 -58
  173. package/tests/stress/index.ts +0 -7
  174. package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
  175. package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
  176. package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
  177. package/tests/unit/BatchLoader.test.ts +0 -196
  178. package/tests/unit/archetype/ArcheType.test.ts +0 -107
  179. package/tests/unit/cache/CacheManager.test.ts +0 -498
  180. package/tests/unit/cache/MemoryCache.test.ts +0 -260
  181. package/tests/unit/cache/RedisCache.test.ts +0 -411
  182. package/tests/unit/database/cancellable.test.ts +0 -81
  183. package/tests/unit/database/instrumentedDb.test.ts +0 -160
  184. package/tests/unit/entity/Entity.components.test.ts +0 -317
  185. package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
  186. package/tests/unit/entity/Entity.reload.test.ts +0 -63
  187. package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
  188. package/tests/unit/entity/Entity.test.ts +0 -345
  189. package/tests/unit/gql/depthLimit.test.ts +0 -203
  190. package/tests/unit/gql/operationMiddleware.test.ts +0 -293
  191. package/tests/unit/health/Health.test.ts +0 -129
  192. package/tests/unit/middleware/AccessLog.test.ts +0 -37
  193. package/tests/unit/middleware/Middleware.test.ts +0 -98
  194. package/tests/unit/middleware/RequestId.test.ts +0 -54
  195. package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
  196. package/tests/unit/query/FilterBuilder.test.ts +0 -111
  197. package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
  198. package/tests/unit/query/Query.emptyString.test.ts +0 -69
  199. package/tests/unit/query/Query.test.ts +0 -310
  200. package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
  201. package/tests/unit/remote/RemoteError.test.ts +0 -55
  202. package/tests/unit/remote/decorators.test.ts +0 -195
  203. package/tests/unit/remote/metrics.test.ts +0 -115
  204. package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
  205. package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
  206. package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
  207. package/tests/unit/schema/schema-integration.test.ts +0 -426
  208. package/tests/unit/schema/schema.test.ts +0 -580
  209. package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
  210. package/tests/unit/upload/RestUpload.test.ts +0 -267
  211. package/tests/unit/validateEnv.test.ts +0 -82
  212. package/tests/utils/entity-tracker.ts +0 -57
  213. package/tests/utils/index.ts +0 -13
  214. package/tests/utils/test-context.ts +0 -149
@@ -1,465 +0,0 @@
1
- /**
2
- * Query Performance Benchmarks
3
- *
4
- * Tests query performance against pre-generated benchmark databases.
5
- * Uses BENCHMARK_TIER env var to select database tier.
6
- *
7
- * Run:
8
- * BENCHMARK_TIER=xs bun test tests/benchmark/scenarios/query-benchmarks.test.ts
9
- * bun run bench:run:xs
10
- */
11
- import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
12
- import { createHash } from 'node:crypto';
13
- import { BenchUser, BenchProduct, BenchOrder, BenchOrderItem, BenchReview } from '../fixtures/EcommerceComponents';
14
- import { Query, FilterOp } from '../../../query/Query';
15
- import { BenchmarkRunner, type BenchmarkResult } from '../../stress/BenchmarkRunner';
16
- import { ComponentRegistry } from '../../../core/components';
17
- import { getMetadataStorage } from '../../../core/metadata';
18
-
19
- // Generate type_id same way as framework
20
- function generateTypeId(name: string): string {
21
- return createHash('sha256').update(name).digest('hex');
22
- }
23
-
24
- // Tier is set by run-benchmarks.ts wrapper
25
- const tier = process.env.BENCHMARK_TIER || 'xs';
26
- let runner: BenchmarkRunner;
27
- const results: BenchmarkResult[] = [];
28
-
29
- beforeAll(async () => {
30
- runner = new BenchmarkRunner();
31
-
32
- // Debug: verify environment
33
- console.log(`[DEBUG] POSTGRES_HOST: ${process.env.POSTGRES_HOST}`);
34
- console.log(`[DEBUG] POSTGRES_PORT: ${process.env.POSTGRES_PORT}`);
35
- console.log(`[DEBUG] USE_PGLITE: ${process.env.USE_PGLITE}`);
36
- console.log(`[DEBUG] BUNSANE_USE_DIRECT_PARTITION: ${process.env.BUNSANE_USE_DIRECT_PARTITION}`);
37
-
38
- // Manually register components without triggering partition table creation
39
- // ComponentRegistry is already the singleton instance (exported as default)
40
- const registry = ComponentRegistry as any; // Access private members
41
- const storage = getMetadataStorage();
42
-
43
- const components = [
44
- { name: 'BenchUser', ctor: BenchUser },
45
- { name: 'BenchProduct', ctor: BenchProduct },
46
- { name: 'BenchOrder', ctor: BenchOrder },
47
- { name: 'BenchOrderItem', ctor: BenchOrderItem },
48
- { name: 'BenchReview', ctor: BenchReview },
49
- ];
50
-
51
- for (const { name, ctor } of components) {
52
- const typeId = generateTypeId(name);
53
- // Register in ComponentRegistry's internal maps
54
- registry.componentsMap.set(name, typeId);
55
- registry.typeIdToName.set(typeId, name);
56
- registry.typeIdToCtor.set(typeId, ctor);
57
- // Also register in metadata storage
58
- storage.getComponentId(name);
59
- }
60
-
61
- console.log(`\n=== Query Benchmarks [${tier.toUpperCase()}] ===\n`);
62
- });
63
-
64
- afterAll(async () => {
65
- // Print summary
66
- console.log('\n=== Benchmark Summary ===');
67
- const summary = runner.getSummary();
68
- console.log(`Passed: ${summary.passed}/${summary.total}`);
69
-
70
- if (results.length > 0) {
71
- console.log('\nDetailed Results:');
72
- for (const r of results) {
73
- const status = r.passed ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
74
- console.log(` ${status} ${r.name.padEnd(40)} p95=${r.timings.p95.toFixed(1).padStart(8)}ms rows=${String(r.rowsReturned).padStart(6)}`);
75
- }
76
- }
77
- });
78
-
79
- describe(`Query Benchmarks [${tier.toUpperCase()}]`, () => {
80
- describe('Single Component Queries', () => {
81
- test('indexed field filter (user by status)', async () => {
82
- const result = await runner.run(
83
- 'indexed-filter-status',
84
- async () => {
85
- return await new Query()
86
- .with(BenchUser, {
87
- filters: [Query.filter('status', FilterOp.EQ, 'active')]
88
- })
89
- .take(100)
90
- .exec();
91
- },
92
- { iterations: 20, targetP95: 100 }
93
- );
94
- results.push(result);
95
- expect(result.passed).toBe(true);
96
- });
97
-
98
- test('indexed field filter (product by category)', async () => {
99
- const result = await runner.run(
100
- 'indexed-filter-category',
101
- async () => {
102
- return await new Query()
103
- .with(BenchProduct, {
104
- filters: [Query.filter('category', FilterOp.EQ, 'Electronics')]
105
- })
106
- .take(100)
107
- .exec();
108
- },
109
- { iterations: 20, targetP95: 100 }
110
- );
111
- results.push(result);
112
- expect(result.passed).toBe(true);
113
- });
114
-
115
- test('numeric range filter (product by price)', async () => {
116
- const result = await runner.run(
117
- 'numeric-range-price',
118
- async () => {
119
- return await new Query()
120
- .with(BenchProduct, {
121
- filters: [
122
- Query.filter('price', FilterOp.GTE, 50),
123
- Query.filter('price', FilterOp.LTE, 200)
124
- ]
125
- })
126
- .take(100)
127
- .exec();
128
- },
129
- { iterations: 20, targetP95: 100 }
130
- );
131
- results.push(result);
132
- expect(result.passed).toBe(true);
133
- });
134
-
135
- test('sorting with pagination (products by rating DESC)', async () => {
136
- const result = await runner.run(
137
- 'sort-rating-desc',
138
- async () => {
139
- return await new Query()
140
- .with(BenchProduct)
141
- .sortBy(BenchProduct, 'rating', 'DESC')
142
- .take(50)
143
- .offset(100)
144
- .exec();
145
- },
146
- { iterations: 20, targetP95: 150 }
147
- );
148
- results.push(result);
149
- expect(result.passed).toBe(true);
150
- });
151
- });
152
-
153
- describe('Multi-Component Queries', () => {
154
- test('two components (order + order item)', async () => {
155
- const result = await runner.run(
156
- 'multi-2-components',
157
- async () => {
158
- return await new Query()
159
- .with(BenchOrder, {
160
- filters: [Query.filter('status', FilterOp.EQ, 'delivered')]
161
- })
162
- .with(BenchOrderItem)
163
- .take(50)
164
- .exec();
165
- },
166
- { iterations: 15, targetP95: 200 }
167
- );
168
- results.push(result);
169
- expect(result.passed).toBe(true);
170
- });
171
-
172
- test('three components (user + order + item)', async () => {
173
- const result = await runner.run(
174
- 'multi-3-components',
175
- async () => {
176
- return await new Query()
177
- .with(BenchUser, {
178
- filters: [Query.filter('tier', FilterOp.EQ, 'premium')]
179
- })
180
- .with(BenchOrder)
181
- .with(BenchOrderItem)
182
- .take(50)
183
- .exec();
184
- },
185
- { iterations: 15, targetP95: 300 }
186
- );
187
- results.push(result);
188
- expect(result.passed).toBe(true);
189
- });
190
- });
191
-
192
- describe('Foreign Key Relation Queries', () => {
193
- test('orders by userId', async () => {
194
- // First get a user ID
195
- const users = await new Query()
196
- .with(BenchUser, {
197
- filters: [Query.filter('orderCount', FilterOp.GT, 0)]
198
- })
199
- .take(1)
200
- .populate()
201
- .exec();
202
-
203
- if (users.length === 0) {
204
- console.log('Skipping: no users with orders found');
205
- return;
206
- }
207
-
208
- const userId = users[0]!.id;
209
-
210
- const result = await runner.run(
211
- 'fk-orders-by-user',
212
- async () => {
213
- return await new Query()
214
- .with(BenchOrder, {
215
- filters: [Query.filter('userId', FilterOp.EQ, userId)]
216
- })
217
- .take(100)
218
- .exec();
219
- },
220
- { iterations: 20, targetP95: 100 }
221
- );
222
- results.push(result);
223
- expect(result.passed).toBe(true);
224
- });
225
-
226
- test('reviews by productId', async () => {
227
- // First get a product ID
228
- const products = await new Query()
229
- .with(BenchProduct, {
230
- filters: [Query.filter('reviewCount', FilterOp.GT, 0)]
231
- })
232
- .take(1)
233
- .populate()
234
- .exec();
235
-
236
- if (products.length === 0) {
237
- console.log('Skipping: no products with reviews found');
238
- return;
239
- }
240
-
241
- const productId = products[0]!.id;
242
-
243
- const result = await runner.run(
244
- 'fk-reviews-by-product',
245
- async () => {
246
- return await new Query()
247
- .with(BenchReview, {
248
- filters: [Query.filter('productId', FilterOp.EQ, productId)]
249
- })
250
- .take(100)
251
- .exec();
252
- },
253
- { iterations: 20, targetP95: 100 }
254
- );
255
- results.push(result);
256
- expect(result.passed).toBe(true);
257
- });
258
-
259
- test('order items by orderId', async () => {
260
- // First get an order ID
261
- const orders = await new Query()
262
- .with(BenchOrder, {
263
- filters: [Query.filter('itemCount', FilterOp.GT, 0)]
264
- })
265
- .take(1)
266
- .populate()
267
- .exec();
268
-
269
- if (orders.length === 0) {
270
- console.log('Skipping: no orders with items found');
271
- return;
272
- }
273
-
274
- const orderId = orders[0]!.id;
275
-
276
- const result = await runner.run(
277
- 'fk-items-by-order',
278
- async () => {
279
- return await new Query()
280
- .with(BenchOrderItem, {
281
- filters: [Query.filter('orderId', FilterOp.EQ, orderId)]
282
- })
283
- .take(100)
284
- .exec();
285
- },
286
- { iterations: 20, targetP95: 100 }
287
- );
288
- results.push(result);
289
- expect(result.passed).toBe(true);
290
- });
291
- });
292
-
293
- describe('Complex Queries with Sorting', () => {
294
- test('multi-component with filter and sort', async () => {
295
- const result = await runner.run(
296
- 'complex-filter-sort',
297
- async () => {
298
- return await new Query()
299
- .with(BenchProduct, {
300
- filters: [
301
- Query.filter('status', FilterOp.EQ, 'active'),
302
- Query.filter('stock', FilterOp.GT, 10)
303
- ]
304
- })
305
- .with(BenchReview)
306
- .sortBy(BenchProduct, 'rating', 'DESC')
307
- .take(50)
308
- .exec();
309
- },
310
- { iterations: 15, targetP95: 300 }
311
- );
312
- results.push(result);
313
- expect(result.passed).toBe(true);
314
- });
315
-
316
- test('date range with sorting', async () => {
317
- const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
318
-
319
- const result = await runner.run(
320
- 'date-range-sorted',
321
- async () => {
322
- return await new Query()
323
- .with(BenchOrder, {
324
- filters: [
325
- Query.filter('orderedAt', FilterOp.GTE, thirtyDaysAgo.toISOString())
326
- ]
327
- })
328
- .sortBy(BenchOrder, 'total', 'DESC')
329
- .take(100)
330
- .exec();
331
- },
332
- { iterations: 15, targetP95: 200 }
333
- );
334
- results.push(result);
335
- expect(result.passed).toBe(true);
336
- });
337
- });
338
-
339
- describe('Pagination Performance', () => {
340
- test('deep pagination (offset 1000)', async () => {
341
- const result = await runner.run(
342
- 'pagination-offset-1000',
343
- async () => {
344
- return await new Query()
345
- .with(BenchProduct)
346
- .take(50)
347
- .offset(1000)
348
- .exec();
349
- },
350
- { iterations: 15, targetP95: 200 }
351
- );
352
- results.push(result);
353
- expect(result.passed).toBe(true);
354
- });
355
-
356
- test('very deep pagination (offset 5000)', async () => {
357
- const result = await runner.run(
358
- 'pagination-offset-5000',
359
- async () => {
360
- return await new Query()
361
- .with(BenchProduct)
362
- .take(50)
363
- .offset(5000)
364
- .exec();
365
- },
366
- { iterations: 10, targetP95: 500 }
367
- );
368
- results.push(result);
369
- // Less strict for deep pagination
370
- expect(result.timings.p95).toBeLessThan(1000);
371
- });
372
- });
373
-
374
- describe('Count and Aggregations', () => {
375
- test('count query', async () => {
376
- const result = await runner.run(
377
- 'count-products',
378
- async () => {
379
- const count = await new Query()
380
- .with(BenchProduct, {
381
- filters: [Query.filter('status', FilterOp.EQ, 'active')]
382
- })
383
- .count();
384
- return [{ count }];
385
- },
386
- { iterations: 20, targetP95: 100 }
387
- );
388
- results.push(result);
389
- expect(result.passed).toBe(true);
390
- });
391
-
392
- test('sum aggregation', async () => {
393
- const result = await runner.run(
394
- 'sum-order-totals',
395
- async () => {
396
- const sum = await new Query()
397
- .with(BenchOrder, {
398
- filters: [Query.filter('status', FilterOp.EQ, 'delivered')]
399
- })
400
- .sum(BenchOrder, 'total');
401
- return [{ sum }];
402
- },
403
- { iterations: 20, targetP95: 150 }
404
- );
405
- results.push(result);
406
- expect(result.passed).toBe(true);
407
- });
408
-
409
- test('average aggregation', async () => {
410
- const result = await runner.run(
411
- 'avg-product-price',
412
- async () => {
413
- const avg = await new Query()
414
- .with(BenchProduct, {
415
- filters: [Query.filter('category', FilterOp.EQ, 'Electronics')]
416
- })
417
- .average(BenchProduct, 'price');
418
- return [{ avg }];
419
- },
420
- { iterations: 20, targetP95: 150 }
421
- );
422
- results.push(result);
423
- expect(result.passed).toBe(true);
424
- });
425
- });
426
-
427
- describe('Populate Performance', () => {
428
- test('populate single component', async () => {
429
- const result = await runner.run(
430
- 'populate-single',
431
- async () => {
432
- return await new Query()
433
- .with(BenchUser, {
434
- filters: [Query.filter('tier', FilterOp.EQ, 'premium')]
435
- })
436
- .populate()
437
- .take(50)
438
- .exec();
439
- },
440
- { iterations: 15, targetP95: 200 }
441
- );
442
- results.push(result);
443
- expect(result.passed).toBe(true);
444
- });
445
-
446
- test('populate multi-component', async () => {
447
- const result = await runner.run(
448
- 'populate-multi',
449
- async () => {
450
- return await new Query()
451
- .with(BenchProduct, {
452
- filters: [Query.filter('status', FilterOp.EQ, 'active')]
453
- })
454
- .with(BenchReview)
455
- .populate()
456
- .take(30)
457
- .exec();
458
- },
459
- { iterations: 10, targetP95: 500 }
460
- );
461
- results.push(result);
462
- expect(result.passed).toBe(true);
463
- });
464
- });
465
- });