bunsane 0.3.1 → 0.4.0

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