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
@@ -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
- });