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,1081 +0,0 @@
1
- /**
2
- * Realistic Stress Test Scenarios
3
- *
4
- * Simulates real-world e-commerce query patterns with:
5
- * - 1000+ entities seeded with multiple components
6
- * - Multi-filter queries
7
- * - Multi-component joins
8
- * - OR queries
9
- * - Aggregations
10
- * - Complex sorting and pagination
11
- *
12
- * Run with: bun run test:pglite -- tests/stress/scenarios/realistic-scenarios.test.ts
13
- * Configure: STRESS_ENTITY_COUNT=5000 for larger tests
14
- */
15
- import { describe, test, beforeAll, afterAll, expect } from 'bun:test';
16
- import { DataSeeder } from '../DataSeeder';
17
- import { BenchmarkRunner } from '../BenchmarkRunner';
18
- import { StressTestReporter } from '../StressTestReporter';
19
- import { Query, FilterOp } from '../../../query/Query';
20
- import { OrQuery } from '../../../query/OrQuery';
21
- import {
22
- Product,
23
- Inventory,
24
- Pricing,
25
- Vendor,
26
- ProductMetrics,
27
- CATEGORIES,
28
- SUBCATEGORIES,
29
- REGIONS,
30
- WAREHOUSES,
31
- CURRENCIES,
32
- VENDOR_TIERS,
33
- PRODUCT_STATUSES,
34
- STOCK_STATUSES
35
- } from '../fixtures/RealisticComponents';
36
- import { ensureComponentsRegistered } from '../../utils';
37
-
38
- // Configuration via environment variables
39
- const ENTITY_COUNT = parseInt(process.env.STRESS_ENTITY_COUNT || '1000', 10);
40
- const BATCH_SIZE = Math.min(500, Math.floor(ENTITY_COUNT / 10) || 100);
41
-
42
- // Helper to generate realistic test data
43
- function generateProductData(index: number): Record<string, any> {
44
- const category = CATEGORIES[index % CATEGORIES.length];
45
- const subcategories = SUBCATEGORIES[category];
46
- const subcategory = subcategories[index % subcategories.length];
47
- const status = PRODUCT_STATUSES[index % PRODUCT_STATUSES.length];
48
- const now = new Date();
49
- const createdDaysAgo = Math.floor(Math.random() * 365);
50
-
51
- return {
52
- name: `Product ${index} - ${subcategory}`,
53
- sku: `SKU-${String(index).padStart(6, '0')}`,
54
- description: `This is a ${subcategory.toLowerCase()} product in the ${category} category. High quality item with great reviews.`,
55
- category,
56
- subcategory,
57
- tags: [category.toLowerCase(), subcategory.toLowerCase(), `tag${index % 10}`],
58
- status,
59
- rating: 1 + (Math.random() * 4),
60
- reviewCount: Math.floor(Math.random() * 500),
61
- createdAt: new Date(now.getTime() - createdDaysAgo * 24 * 60 * 60 * 1000),
62
- updatedAt: new Date(now.getTime() - Math.floor(createdDaysAgo / 2) * 24 * 60 * 60 * 1000)
63
- };
64
- }
65
-
66
- function generateInventoryData(index: number): Record<string, any> {
67
- const quantity = Math.floor(Math.random() * 1000);
68
- const reservedQuantity = Math.floor(quantity * Math.random() * 0.3);
69
- const reorderPoint = 10 + Math.floor(Math.random() * 40);
70
-
71
- let stockStatus: string;
72
- if (quantity === 0) stockStatus = 'out_of_stock';
73
- else if (quantity < reorderPoint) stockStatus = 'low_stock';
74
- else if (index % 20 === 0) stockStatus = 'backordered';
75
- else stockStatus = 'in_stock';
76
-
77
- return {
78
- quantity,
79
- reservedQuantity,
80
- warehouseId: WAREHOUSES[index % WAREHOUSES.length],
81
- reorderPoint,
82
- maxStock: 500 + Math.floor(Math.random() * 500),
83
- stockStatus,
84
- lastRestocked: new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000)
85
- };
86
- }
87
-
88
- function generatePricingData(index: number): Record<string, any> {
89
- const basePrice = 10 + Math.random() * 990;
90
- const costPrice = basePrice * (0.3 + Math.random() * 0.4);
91
- const isOnSale = index % 5 === 0;
92
- const discountPercent = isOnSale ? 10 + Math.floor(Math.random() * 40) : 0;
93
- const salePrice = isOnSale ? basePrice * (1 - discountPercent / 100) : basePrice;
94
-
95
- return {
96
- basePrice,
97
- salePrice,
98
- costPrice,
99
- currency: CURRENCIES[index % CURRENCIES.length],
100
- discountPercent,
101
- isOnSale,
102
- saleStartDate: isOnSale ? new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) : null,
103
- saleEndDate: isOnSale ? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) : null,
104
- profit: salePrice - costPrice
105
- };
106
- }
107
-
108
- function generateVendorData(index: number): Record<string, any> {
109
- const vendorIndex = index % 50;
110
- return {
111
- vendorId: `VENDOR-${String(vendorIndex).padStart(3, '0')}`,
112
- vendorName: `Vendor ${vendorIndex} Inc.`,
113
- region: REGIONS[vendorIndex % REGIONS.length],
114
- vendorRating: 3 + Math.random() * 2,
115
- isVerified: vendorIndex % 3 !== 0,
116
- totalSales: Math.floor(Math.random() * 100000),
117
- tier: VENDOR_TIERS[Math.floor(vendorIndex / 12.5)]
118
- };
119
- }
120
-
121
- function generateMetricsData(index: number): Record<string, any> {
122
- const viewCount = Math.floor(Math.random() * 10000);
123
- const cartAddCount = Math.floor(viewCount * (0.1 + Math.random() * 0.2));
124
- const purchaseCount = Math.floor(cartAddCount * (0.2 + Math.random() * 0.3));
125
-
126
- return {
127
- viewCount,
128
- purchaseCount,
129
- cartAddCount,
130
- wishlistCount: Math.floor(Math.random() * 500),
131
- returnCount: Math.floor(purchaseCount * Math.random() * 0.1),
132
- conversionRate: purchaseCount / (viewCount || 1),
133
- lastPurchased: purchaseCount > 0
134
- ? new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000)
135
- : null,
136
- popularityScore: viewCount > 5000 ? 'high' : viewCount > 1000 ? 'medium' : 'low'
137
- };
138
- }
139
-
140
- describe('Realistic E-Commerce Stress Tests', () => {
141
- const seeder = new DataSeeder();
142
- const benchmark = new BenchmarkRunner();
143
- const reporter = new StressTestReporter();
144
- let entityIds: string[] = [];
145
- let setupTime = 0;
146
-
147
- beforeAll(async () => {
148
- const startSetup = performance.now();
149
-
150
- console.log(`\n Registering components...`);
151
- await ensureComponentsRegistered(
152
- Product,
153
- Inventory,
154
- Pricing,
155
- Vendor,
156
- ProductMetrics
157
- );
158
-
159
- // Allow index creation to settle
160
- await new Promise(resolve => setTimeout(resolve, 2000));
161
-
162
- console.log(` Seeding ${ENTITY_COUNT.toLocaleString()} product entities...`);
163
-
164
- // Seed primary Product component
165
- const result = await seeder.seed(
166
- Product,
167
- generateProductData,
168
- {
169
- totalEntities: ENTITY_COUNT,
170
- batchSize: BATCH_SIZE,
171
- onProgress: (current, total, elapsed) => {
172
- if (current % (BATCH_SIZE * 5) === 0 || current === total) {
173
- const pct = ((current / total) * 100).toFixed(1);
174
- const rate = ((current / elapsed) * 1000).toFixed(0);
175
- console.log(` Product: ${pct}% (${rate}/sec)`);
176
- }
177
- }
178
- }
179
- );
180
- entityIds = result.entityIds;
181
- console.log(` Products seeded: ${result.recordsPerSecond.toFixed(0)}/sec`);
182
-
183
- // Add Inventory to all products
184
- console.log(` Adding Inventory components...`);
185
- await seeder.seedAdditionalComponent(
186
- entityIds,
187
- Inventory,
188
- generateInventoryData,
189
- BATCH_SIZE
190
- );
191
-
192
- // Add Pricing to all products
193
- console.log(` Adding Pricing components...`);
194
- await seeder.seedAdditionalComponent(
195
- entityIds,
196
- Pricing,
197
- generatePricingData,
198
- BATCH_SIZE
199
- );
200
-
201
- // Add Vendor to 80% of products
202
- const vendorEntityIds = entityIds.slice(0, Math.floor(entityIds.length * 0.8));
203
- console.log(` Adding Vendor components to ${vendorEntityIds.length} products...`);
204
- await seeder.seedAdditionalComponent(
205
- vendorEntityIds,
206
- Vendor,
207
- generateVendorData,
208
- BATCH_SIZE
209
- );
210
-
211
- // Add ProductMetrics to 60% of products
212
- const metricsEntityIds = entityIds.slice(0, Math.floor(entityIds.length * 0.6));
213
- console.log(` Adding ProductMetrics to ${metricsEntityIds.length} products...`);
214
- await seeder.seedAdditionalComponent(
215
- metricsEntityIds,
216
- ProductMetrics,
217
- generateMetricsData,
218
- BATCH_SIZE
219
- );
220
-
221
- console.log(' Running VACUUM ANALYZE...');
222
- await seeder.optimize();
223
-
224
- setupTime = performance.now() - startSetup;
225
- console.log(` Setup complete in ${(setupTime / 1000).toFixed(1)}s\n`);
226
- }, 120000);
227
-
228
- afterAll(async () => {
229
- // Print report
230
- const recordCount = await seeder.getRecordCount();
231
- const report = reporter.generateReport(benchmark.getResults(), {
232
- recordCount,
233
- environment: `PGlite/PostgreSQL, Bun ${Bun.version}`,
234
- duration: setupTime
235
- });
236
- console.log('\n' + report);
237
-
238
- // Cleanup
239
- console.log('\n Cleaning up test data...');
240
- await seeder.cleanup(entityIds, BATCH_SIZE);
241
- console.log(' Cleanup complete.');
242
- }, 60000);
243
-
244
- // ============================================================
245
- // SINGLE COMPONENT FILTER TESTS
246
- // ============================================================
247
-
248
- describe('Single Component Filters', () => {
249
- test('filter by category (indexed)', async () => {
250
- const result = await benchmark.runWithOutput(
251
- 'Product: category=Electronics',
252
- () => new Query()
253
- .with(Product, {
254
- filters: [{ field: 'category', operator: FilterOp.EQ, value: 'Electronics' }]
255
- })
256
- .take(100)
257
- .exec(),
258
- { targetP95: 100, iterations: 10 }
259
- );
260
- expect(result.rowsReturned).toBeGreaterThan(0);
261
- expect(result.passed).toBe(true);
262
- });
263
-
264
- test('filter by status (indexed)', async () => {
265
- const result = await benchmark.runWithOutput(
266
- 'Product: status=active',
267
- () => new Query()
268
- .with(Product, {
269
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
270
- })
271
- .take(100)
272
- .exec(),
273
- { targetP95: 100, iterations: 10 }
274
- );
275
- expect(result.rowsReturned).toBeGreaterThan(0);
276
- });
277
-
278
- test('filter by rating range', async () => {
279
- const result = await benchmark.runWithOutput(
280
- 'Product: rating >= 4',
281
- () => new Query()
282
- .with(Product, {
283
- filters: [{ field: 'rating', operator: FilterOp.GTE, value: 4 }]
284
- })
285
- .take(100)
286
- .exec(),
287
- { targetP95: 100, iterations: 10 }
288
- );
289
- expect(result.rowsReturned).toBeGreaterThan(0);
290
- });
291
-
292
- test('filter by stock status', async () => {
293
- const result = await benchmark.runWithOutput(
294
- 'Inventory: stockStatus=in_stock',
295
- () => new Query()
296
- .with(Inventory, {
297
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
298
- })
299
- .take(100)
300
- .exec(),
301
- { targetP95: 100, iterations: 10 }
302
- );
303
- expect(result.rowsReturned).toBeGreaterThan(0);
304
- });
305
-
306
- test('filter by price range', async () => {
307
- const result = await benchmark.runWithOutput(
308
- 'Pricing: 50 <= basePrice <= 200',
309
- () => new Query()
310
- .with(Pricing, {
311
- filters: [
312
- { field: 'basePrice', operator: FilterOp.GTE, value: 50 },
313
- { field: 'basePrice', operator: FilterOp.LTE, value: 200 }
314
- ]
315
- })
316
- .take(100)
317
- .exec(),
318
- { targetP95: 100, iterations: 10 }
319
- );
320
- expect(result.rowsReturned).toBeGreaterThan(0);
321
- });
322
-
323
- test('filter by boolean (isOnSale)', async () => {
324
- const result = await benchmark.runWithOutput(
325
- 'Pricing: isOnSale=true',
326
- () => new Query()
327
- .with(Pricing, {
328
- filters: [{ field: 'isOnSale', operator: FilterOp.EQ, value: true }]
329
- })
330
- .take(100)
331
- .exec(),
332
- { targetP95: 100, iterations: 10 }
333
- );
334
- expect(result.rowsReturned).toBeGreaterThan(0);
335
- });
336
- });
337
-
338
- // ============================================================
339
- // MULTI-FILTER SINGLE COMPONENT TESTS
340
- // ============================================================
341
-
342
- describe('Multi-Filter Single Component', () => {
343
- test('active products in Electronics with high rating', async () => {
344
- const result = await benchmark.runWithOutput(
345
- 'Product: active + Electronics + rating>=4',
346
- () => new Query()
347
- .with(Product, {
348
- filters: [
349
- { field: 'status', operator: FilterOp.EQ, value: 'active' },
350
- { field: 'category', operator: FilterOp.EQ, value: 'Electronics' },
351
- { field: 'rating', operator: FilterOp.GTE, value: 4 }
352
- ]
353
- })
354
- .take(50)
355
- .exec(),
356
- { targetP95: 150, iterations: 10 }
357
- );
358
- expect(result.passed).toBe(true);
359
- });
360
-
361
- test('low stock items below reorder point', async () => {
362
- const result = await benchmark.runWithOutput(
363
- 'Inventory: low_stock + qty < 20',
364
- () => new Query()
365
- .with(Inventory, {
366
- filters: [
367
- { field: 'stockStatus', operator: FilterOp.EQ, value: 'low_stock' },
368
- { field: 'quantity', operator: FilterOp.LT, value: 20 }
369
- ]
370
- })
371
- .take(50)
372
- .exec(),
373
- { targetP95: 150, iterations: 10 }
374
- );
375
- expect(result.passed).toBe(true);
376
- });
377
-
378
- test('high margin sale items', async () => {
379
- const result = await benchmark.runWithOutput(
380
- 'Pricing: onSale + profit > 100',
381
- () => new Query()
382
- .with(Pricing, {
383
- filters: [
384
- { field: 'isOnSale', operator: FilterOp.EQ, value: true },
385
- { field: 'profit', operator: FilterOp.GT, value: 100 }
386
- ]
387
- })
388
- .take(50)
389
- .exec(),
390
- { targetP95: 150, iterations: 10 }
391
- );
392
- expect(result.passed).toBe(true);
393
- });
394
-
395
- test('verified gold/platinum vendors with high rating', async () => {
396
- const result = await benchmark.runWithOutput(
397
- 'Vendor: verified + rating>=4 + tier=gold',
398
- () => new Query()
399
- .with(Vendor, {
400
- filters: [
401
- { field: 'isVerified', operator: FilterOp.EQ, value: true },
402
- { field: 'vendorRating', operator: FilterOp.GTE, value: 4 },
403
- { field: 'tier', operator: FilterOp.EQ, value: 'gold' }
404
- ]
405
- })
406
- .take(50)
407
- .exec(),
408
- { targetP95: 150, iterations: 10 }
409
- );
410
- expect(result.passed).toBe(true);
411
- });
412
- });
413
-
414
- // ============================================================
415
- // MULTI-COMPONENT JOIN TESTS
416
- // ============================================================
417
-
418
- describe('Multi-Component Joins', () => {
419
- test('2-way join: Product + Inventory', async () => {
420
- const result = await benchmark.runWithOutput(
421
- 'Join: Product + Inventory',
422
- () => new Query()
423
- .with(Product)
424
- .with(Inventory)
425
- .take(100)
426
- .exec(),
427
- { targetP95: 150, iterations: 10 }
428
- );
429
- expect(result.rowsReturned).toBeGreaterThan(0);
430
- expect(result.passed).toBe(true);
431
- });
432
-
433
- test('3-way join: Product + Inventory + Pricing', async () => {
434
- const result = await benchmark.runWithOutput(
435
- 'Join: Product + Inventory + Pricing',
436
- () => new Query()
437
- .with(Product)
438
- .with(Inventory)
439
- .with(Pricing)
440
- .take(100)
441
- .exec(),
442
- { targetP95: 200, iterations: 10 }
443
- );
444
- expect(result.rowsReturned).toBeGreaterThan(0);
445
- });
446
-
447
- test('4-way join: Product + Inventory + Pricing + Vendor', async () => {
448
- const result = await benchmark.runWithOutput(
449
- 'Join: Product + Inventory + Pricing + Vendor',
450
- () => new Query()
451
- .with(Product)
452
- .with(Inventory)
453
- .with(Pricing)
454
- .with(Vendor)
455
- .take(100)
456
- .exec(),
457
- { targetP95: 300, iterations: 10 }
458
- );
459
- expect(result.rowsReturned).toBeGreaterThan(0);
460
- });
461
-
462
- test('5-way join (all components)', async () => {
463
- const result = await benchmark.runWithOutput(
464
- 'Join: All 5 components',
465
- () => new Query()
466
- .with(Product)
467
- .with(Inventory)
468
- .with(Pricing)
469
- .with(Vendor)
470
- .with(ProductMetrics)
471
- .take(100)
472
- .exec(),
473
- { targetP95: 400, iterations: 10 }
474
- );
475
- expect(result.rowsReturned).toBeGreaterThan(0);
476
- });
477
- });
478
-
479
- // ============================================================
480
- // MULTI-COMPONENT WITH FILTERS TESTS
481
- // ============================================================
482
-
483
- describe('Multi-Component with Filters', () => {
484
- test('active Electronics in stock', async () => {
485
- const result = await benchmark.runWithOutput(
486
- 'Product(active+Electronics) + Inventory(in_stock)',
487
- () => new Query()
488
- .with(Product, {
489
- filters: [
490
- { field: 'status', operator: FilterOp.EQ, value: 'active' },
491
- { field: 'category', operator: FilterOp.EQ, value: 'Electronics' }
492
- ]
493
- })
494
- .with(Inventory, {
495
- filters: [
496
- { field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }
497
- ]
498
- })
499
- .take(50)
500
- .exec(),
501
- { targetP95: 200, iterations: 10 }
502
- );
503
- expect(result.passed).toBe(true);
504
- });
505
-
506
- test('on-sale products with low stock', async () => {
507
- const result = await benchmark.runWithOutput(
508
- 'Pricing(onSale) + Inventory(low_stock)',
509
- () => new Query()
510
- .with(Pricing, {
511
- filters: [{ field: 'isOnSale', operator: FilterOp.EQ, value: true }]
512
- })
513
- .with(Inventory, {
514
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'low_stock' }]
515
- })
516
- .take(50)
517
- .exec(),
518
- { targetP95: 200, iterations: 10 }
519
- );
520
- expect(result.passed).toBe(true);
521
- });
522
-
523
- test('high-rated products from verified vendors', async () => {
524
- const result = await benchmark.runWithOutput(
525
- 'Product(rating>=4) + Vendor(verified)',
526
- () => new Query()
527
- .with(Product, {
528
- filters: [{ field: 'rating', operator: FilterOp.GTE, value: 4 }]
529
- })
530
- .with(Vendor, {
531
- filters: [{ field: 'isVerified', operator: FilterOp.EQ, value: true }]
532
- })
533
- .take(50)
534
- .exec(),
535
- { targetP95: 200, iterations: 10 }
536
- );
537
- expect(result.passed).toBe(true);
538
- });
539
-
540
- test('complex: active, in stock, on sale, price range', async () => {
541
- const result = await benchmark.runWithOutput(
542
- 'Complex 4-filter multi-component',
543
- () => new Query()
544
- .with(Product, {
545
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
546
- })
547
- .with(Inventory, {
548
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
549
- })
550
- .with(Pricing, {
551
- filters: [
552
- { field: 'isOnSale', operator: FilterOp.EQ, value: true },
553
- { field: 'salePrice', operator: FilterOp.LTE, value: 500 }
554
- ]
555
- })
556
- .take(50)
557
- .exec(),
558
- { targetP95: 300, iterations: 10 }
559
- );
560
- expect(result.passed).toBe(true);
561
- });
562
-
563
- test('full pipeline: active + in_stock + onSale + verified + high popularity', async () => {
564
- const result = await benchmark.runWithOutput(
565
- 'Full pipeline: 5-component filtered',
566
- () => new Query()
567
- .with(Product, {
568
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
569
- })
570
- .with(Inventory, {
571
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
572
- })
573
- .with(Pricing, {
574
- filters: [{ field: 'isOnSale', operator: FilterOp.EQ, value: true }]
575
- })
576
- .with(Vendor, {
577
- filters: [{ field: 'isVerified', operator: FilterOp.EQ, value: true }]
578
- })
579
- .with(ProductMetrics, {
580
- filters: [{ field: 'popularityScore', operator: FilterOp.EQ, value: 'high' }]
581
- })
582
- .take(50)
583
- .exec(),
584
- { targetP95: 500, iterations: 10 }
585
- );
586
- expect(result.passed).toBe(true);
587
- });
588
- });
589
-
590
- // ============================================================
591
- // OR QUERY TESTS
592
- // ============================================================
593
-
594
- describe('OR Queries', () => {
595
- test('OR: active OR pending products', async () => {
596
- const orQuery = new OrQuery([
597
- { component: Product, filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }] },
598
- { component: Product, filters: [{ field: 'status', operator: FilterOp.EQ, value: 'pending' }] }
599
- ]);
600
-
601
- const result = await benchmark.runWithOutput(
602
- 'OR: status=active OR status=pending',
603
- () => new Query()
604
- .with(orQuery)
605
- .take(100)
606
- .exec(),
607
- { targetP95: 200, iterations: 10 }
608
- );
609
- expect(result.rowsReturned).toBeGreaterThan(0);
610
- });
611
-
612
- test('OR: low_stock OR out_of_stock', async () => {
613
- const orQuery = new OrQuery([
614
- { component: Inventory, filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'low_stock' }] },
615
- { component: Inventory, filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'out_of_stock' }] }
616
- ]);
617
-
618
- const result = await benchmark.runWithOutput(
619
- 'OR: low_stock OR out_of_stock',
620
- () => new Query()
621
- .with(orQuery)
622
- .take(100)
623
- .exec(),
624
- { targetP95: 200, iterations: 10 }
625
- );
626
- expect(result.rowsReturned).toBeGreaterThan(0);
627
- });
628
-
629
- test('OR: Electronics OR Clothing categories', async () => {
630
- const orQuery = new OrQuery([
631
- { component: Product, filters: [{ field: 'category', operator: FilterOp.EQ, value: 'Electronics' }] },
632
- { component: Product, filters: [{ field: 'category', operator: FilterOp.EQ, value: 'Clothing' }] }
633
- ]);
634
-
635
- const result = await benchmark.runWithOutput(
636
- 'OR: Electronics OR Clothing',
637
- () => new Query()
638
- .with(orQuery)
639
- .take(100)
640
- .exec(),
641
- { targetP95: 200, iterations: 10 }
642
- );
643
- expect(result.rowsReturned).toBeGreaterThan(0);
644
- });
645
- });
646
-
647
- // ============================================================
648
- // EXCLUSION TESTS (without)
649
- // ============================================================
650
-
651
- describe('Component Exclusion (without)', () => {
652
- test('products without Vendor', async () => {
653
- const result = await benchmark.runWithOutput(
654
- 'Product without Vendor',
655
- () => new Query()
656
- .with(Product)
657
- .without(Vendor)
658
- .take(100)
659
- .exec(),
660
- { targetP95: 150, iterations: 10 }
661
- );
662
- // ~20% of products don't have vendors
663
- expect(result.rowsReturned).toBeGreaterThan(0);
664
- });
665
-
666
- test('products without ProductMetrics', async () => {
667
- const result = await benchmark.runWithOutput(
668
- 'Product without Metrics',
669
- () => new Query()
670
- .with(Product)
671
- .without(ProductMetrics)
672
- .take(100)
673
- .exec(),
674
- { targetP95: 150, iterations: 10 }
675
- );
676
- // ~40% of products don't have metrics
677
- expect(result.rowsReturned).toBeGreaterThan(0);
678
- });
679
-
680
- test('in-stock products from non-verified vendors', async () => {
681
- const result = await benchmark.runWithOutput(
682
- 'Inventory(in_stock) + Vendor(!verified)',
683
- () => new Query()
684
- .with(Inventory, {
685
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
686
- })
687
- .with(Vendor, {
688
- filters: [{ field: 'isVerified', operator: FilterOp.EQ, value: false }]
689
- })
690
- .take(50)
691
- .exec(),
692
- { targetP95: 200, iterations: 10 }
693
- );
694
- expect(result.passed).toBe(true);
695
- });
696
- });
697
-
698
- // ============================================================
699
- // SORTING TESTS
700
- // ============================================================
701
-
702
- describe('Sorting', () => {
703
- test('sort by rating DESC', async () => {
704
- const result = await benchmark.runWithOutput(
705
- 'Product sorted by rating DESC',
706
- () => new Query()
707
- .with(Product)
708
- .sortBy(Product, 'rating', 'DESC')
709
- .take(100)
710
- .exec(),
711
- { targetP95: 150, iterations: 10 }
712
- );
713
- expect(result.rowsReturned).toBeGreaterThan(0);
714
- });
715
-
716
- test('sort by basePrice ASC', async () => {
717
- const result = await benchmark.runWithOutput(
718
- 'Pricing sorted by basePrice ASC',
719
- () => new Query()
720
- .with(Pricing)
721
- .sortBy(Pricing, 'basePrice', 'ASC')
722
- .take(100)
723
- .exec(),
724
- { targetP95: 150, iterations: 10 }
725
- );
726
- expect(result.rowsReturned).toBeGreaterThan(0);
727
- });
728
-
729
- test('filter + sort: active products by rating', async () => {
730
- const result = await benchmark.runWithOutput(
731
- 'Product(active) sorted by rating',
732
- () => new Query()
733
- .with(Product, {
734
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
735
- })
736
- .sortBy(Product, 'rating', 'DESC')
737
- .take(50)
738
- .exec(),
739
- { targetP95: 200, iterations: 10 }
740
- );
741
- expect(result.passed).toBe(true);
742
- });
743
-
744
- test('multi-component filter + sort: on-sale by discount', async () => {
745
- const result = await benchmark.runWithOutput(
746
- 'Pricing(onSale) sorted by discount',
747
- () => new Query()
748
- .with(Product, {
749
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
750
- })
751
- .with(Pricing, {
752
- filters: [{ field: 'isOnSale', operator: FilterOp.EQ, value: true }]
753
- })
754
- .sortBy(Pricing, 'discountPercent', 'DESC')
755
- .take(50)
756
- .exec(),
757
- { targetP95: 250, iterations: 10 }
758
- );
759
- expect(result.passed).toBe(true);
760
- });
761
- });
762
-
763
- // ============================================================
764
- // PAGINATION TESTS
765
- // ============================================================
766
-
767
- describe('Pagination', () => {
768
- test('offset pagination: page 1', async () => {
769
- const result = await benchmark.runWithOutput(
770
- 'Offset: page 1 (0-50)',
771
- () => new Query()
772
- .with(Product)
773
- .take(50)
774
- .offset(0)
775
- .exec(),
776
- { targetP95: 100, iterations: 10 }
777
- );
778
- expect(result.rowsReturned).toBe(50);
779
- });
780
-
781
- test('offset pagination: page 10', async () => {
782
- const result = await benchmark.runWithOutput(
783
- 'Offset: page 10 (450-500)',
784
- () => new Query()
785
- .with(Product)
786
- .take(50)
787
- .offset(450)
788
- .exec(),
789
- { targetP95: 150, iterations: 10 }
790
- );
791
- expect(result.rowsReturned).toBeGreaterThanOrEqual(0);
792
- });
793
-
794
- test('cursor pagination: first page', async () => {
795
- const result = await benchmark.runWithOutput(
796
- 'Cursor: first page',
797
- () => new Query()
798
- .with(Product)
799
- .take(50)
800
- .exec(),
801
- { targetP95: 100, iterations: 10 }
802
- );
803
- expect(result.rowsReturned).toBe(50);
804
- });
805
-
806
- test('cursor pagination: from middle', async () => {
807
- // Get a cursor from the middle
808
- const midpoint = await new Query()
809
- .with(Product)
810
- .take(1)
811
- .offset(Math.floor(ENTITY_COUNT / 2))
812
- .exec();
813
-
814
- const cursorId = midpoint[0]?.id;
815
- if (!cursorId) {
816
- console.log(' Skipping cursor test - no midpoint found');
817
- return;
818
- }
819
-
820
- const result = await benchmark.runWithOutput(
821
- 'Cursor: from middle',
822
- () => new Query()
823
- .with(Product)
824
- .cursor(cursorId)
825
- .take(50)
826
- .exec(),
827
- { targetP95: 100, iterations: 10 }
828
- );
829
- expect(result.passed).toBe(true);
830
- });
831
-
832
- test('filtered pagination: active products page 5', async () => {
833
- const result = await benchmark.runWithOutput(
834
- 'Filtered offset: active page 5',
835
- () => new Query()
836
- .with(Product, {
837
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
838
- })
839
- .take(50)
840
- .offset(200)
841
- .exec(),
842
- { targetP95: 200, iterations: 10 }
843
- );
844
- expect(result.passed).toBe(true);
845
- });
846
- });
847
-
848
- // ============================================================
849
- // AGGREGATION TESTS
850
- // ============================================================
851
-
852
- describe('Aggregations', () => {
853
- test('count all products', async () => {
854
- const result = await benchmark.runWithOutput(
855
- 'COUNT: all products',
856
- async () => [await new Query().with(Product).count()],
857
- { targetP95: 100, iterations: 10 }
858
- );
859
- expect(result.passed).toBe(true);
860
- });
861
-
862
- test('count filtered: active products', async () => {
863
- const result = await benchmark.runWithOutput(
864
- 'COUNT: active products',
865
- async () => [await new Query()
866
- .with(Product, {
867
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
868
- })
869
- .count()],
870
- { targetP95: 100, iterations: 10 }
871
- );
872
- expect(result.passed).toBe(true);
873
- });
874
-
875
- test('count multi-component: active + in_stock', async () => {
876
- const result = await benchmark.runWithOutput(
877
- 'COUNT: active + in_stock',
878
- async () => [await new Query()
879
- .with(Product, {
880
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
881
- })
882
- .with(Inventory, {
883
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
884
- })
885
- .count()],
886
- { targetP95: 200, iterations: 10 }
887
- );
888
- expect(result.passed).toBe(true);
889
- });
890
- });
891
-
892
- // ============================================================
893
- // POPULATE (EAGER LOADING) TESTS
894
- // ============================================================
895
-
896
- describe('Populate / Eager Loading', () => {
897
- test('populate single component', async () => {
898
- const result = await benchmark.runWithOutput(
899
- 'Populate: Product',
900
- () => new Query()
901
- .with(Product)
902
- .populate()
903
- .take(50)
904
- .exec(),
905
- { targetP95: 150, iterations: 10 }
906
- );
907
- expect(result.rowsReturned).toBe(50);
908
- });
909
-
910
- test('populate multi-component', async () => {
911
- const result = await benchmark.runWithOutput(
912
- 'Populate: Product + Pricing',
913
- () => new Query()
914
- .with(Product)
915
- .with(Pricing)
916
- .populate()
917
- .take(50)
918
- .exec(),
919
- { targetP95: 200, iterations: 10 }
920
- );
921
- expect(result.rowsReturned).toBe(50);
922
- });
923
-
924
- test('filtered populate', async () => {
925
- const result = await benchmark.runWithOutput(
926
- 'Filtered Populate: active + in_stock',
927
- () => new Query()
928
- .with(Product, {
929
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
930
- })
931
- .with(Inventory, {
932
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
933
- })
934
- .populate()
935
- .take(50)
936
- .exec(),
937
- { targetP95: 250, iterations: 10 }
938
- );
939
- expect(result.passed).toBe(true);
940
- });
941
- });
942
-
943
- // ============================================================
944
- // REAL-WORLD SCENARIO TESTS
945
- // ============================================================
946
-
947
- describe('Real-World Scenarios', () => {
948
- test('homepage featured products: active + in_stock + high rating + sorted', async () => {
949
- const result = await benchmark.runWithOutput(
950
- 'Homepage: featured products',
951
- () => new Query()
952
- .with(Product, {
953
- filters: [
954
- { field: 'status', operator: FilterOp.EQ, value: 'active' },
955
- { field: 'rating', operator: FilterOp.GTE, value: 4 }
956
- ]
957
- })
958
- .with(Inventory, {
959
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
960
- })
961
- .sortBy(Product, 'rating', 'DESC')
962
- .take(20)
963
- .exec(),
964
- { targetP95: 250, iterations: 10 }
965
- );
966
- expect(result.passed).toBe(true);
967
- });
968
-
969
- test('category page: Electronics with filters and pagination', async () => {
970
- const result = await benchmark.runWithOutput(
971
- 'Category: Electronics page 2',
972
- () => new Query()
973
- .with(Product, {
974
- filters: [
975
- { field: 'category', operator: FilterOp.EQ, value: 'Electronics' },
976
- { field: 'status', operator: FilterOp.EQ, value: 'active' }
977
- ]
978
- })
979
- .with(Inventory, {
980
- filters: [
981
- { field: 'stockStatus', operator: FilterOp.IN, value: ['in_stock', 'low_stock'] }
982
- ]
983
- })
984
- .with(Pricing)
985
- .sortBy(Pricing, 'basePrice', 'ASC')
986
- .take(24)
987
- .offset(24)
988
- .exec(),
989
- { targetP95: 300, iterations: 10 }
990
- );
991
- expect(result.passed).toBe(true);
992
- });
993
-
994
- test('sale page: on-sale products sorted by discount', async () => {
995
- const result = await benchmark.runWithOutput(
996
- 'Sale Page: sorted by discount',
997
- () => new Query()
998
- .with(Product, {
999
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
1000
- })
1001
- .with(Inventory, {
1002
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
1003
- })
1004
- .with(Pricing, {
1005
- filters: [{ field: 'isOnSale', operator: FilterOp.EQ, value: true }]
1006
- })
1007
- .sortBy(Pricing, 'discountPercent', 'DESC')
1008
- .take(20)
1009
- .exec(),
1010
- { targetP95: 300, iterations: 10 }
1011
- );
1012
- expect(result.passed).toBe(true);
1013
- });
1014
-
1015
- test('admin: low stock alert query', async () => {
1016
- const result = await benchmark.runWithOutput(
1017
- 'Admin: low stock alert',
1018
- () => new Query()
1019
- .with(Product, {
1020
- filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }]
1021
- })
1022
- .with(Inventory, {
1023
- filters: [
1024
- { field: 'stockStatus', operator: FilterOp.IN, value: ['low_stock', 'out_of_stock'] }
1025
- ]
1026
- })
1027
- .with(Vendor)
1028
- .populate()
1029
- .take(100)
1030
- .exec(),
1031
- { targetP95: 400, iterations: 10 }
1032
- );
1033
- expect(result.passed).toBe(true);
1034
- });
1035
-
1036
- test('vendor dashboard: products by vendor with metrics', async () => {
1037
- const result = await benchmark.runWithOutput(
1038
- 'Vendor Dashboard: products + metrics',
1039
- () => new Query()
1040
- .with(Product)
1041
- .with(Vendor, {
1042
- filters: [{ field: 'vendorId', operator: FilterOp.EQ, value: 'VENDOR-001' }]
1043
- })
1044
- .with(Inventory)
1045
- .with(ProductMetrics)
1046
- .populate()
1047
- .take(50)
1048
- .exec(),
1049
- { targetP95: 400, iterations: 10 }
1050
- );
1051
- expect(result.passed).toBe(true);
1052
- });
1053
-
1054
- test('search results: price range + category + sorted', async () => {
1055
- const result = await benchmark.runWithOutput(
1056
- 'Search: price 100-500 in Electronics',
1057
- () => new Query()
1058
- .with(Product, {
1059
- filters: [
1060
- { field: 'category', operator: FilterOp.EQ, value: 'Electronics' },
1061
- { field: 'status', operator: FilterOp.EQ, value: 'active' }
1062
- ]
1063
- })
1064
- .with(Pricing, {
1065
- filters: [
1066
- { field: 'basePrice', operator: FilterOp.GTE, value: 100 },
1067
- { field: 'basePrice', operator: FilterOp.LTE, value: 500 }
1068
- ]
1069
- })
1070
- .with(Inventory, {
1071
- filters: [{ field: 'stockStatus', operator: FilterOp.EQ, value: 'in_stock' }]
1072
- })
1073
- .sortBy(Product, 'rating', 'DESC')
1074
- .take(24)
1075
- .exec(),
1076
- { targetP95: 350, iterations: 10 }
1077
- );
1078
- expect(result.passed).toBe(true);
1079
- });
1080
- });
1081
- });