bunsane 0.2.4 → 0.2.8

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 (37) hide show
  1. package/core/ArcheType.ts +67 -34
  2. package/core/BatchLoader.ts +215 -30
  3. package/core/Entity.ts +2 -2
  4. package/core/RequestContext.ts +15 -10
  5. package/core/RequestLoaders.ts +4 -2
  6. package/core/cache/CacheProvider.ts +1 -0
  7. package/core/cache/MemoryCache.ts +10 -1
  8. package/core/cache/RedisCache.ts +16 -2
  9. package/core/validateEnv.ts +8 -0
  10. package/database/DatabaseHelper.ts +113 -1
  11. package/database/index.ts +78 -45
  12. package/docs/SCALABILITY_PLAN.md +175 -0
  13. package/package.json +13 -2
  14. package/query/CTENode.ts +44 -24
  15. package/query/ComponentInclusionNode.ts +195 -95
  16. package/query/Query.ts +9 -9
  17. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +338 -0
  18. package/tests/benchmark/bunfig.toml +9 -0
  19. package/tests/benchmark/fixtures/EcommerceComponents.ts +283 -0
  20. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +301 -0
  21. package/tests/benchmark/fixtures/RelationTracker.ts +159 -0
  22. package/tests/benchmark/fixtures/index.ts +6 -0
  23. package/tests/benchmark/index.ts +22 -0
  24. package/tests/benchmark/noop-preload.ts +3 -0
  25. package/tests/benchmark/query-lateral-benchmark.test.ts +372 -0
  26. package/tests/benchmark/runners/BenchmarkLoader.ts +132 -0
  27. package/tests/benchmark/runners/index.ts +4 -0
  28. package/tests/benchmark/scenarios/query-benchmarks.test.ts +465 -0
  29. package/tests/benchmark/scripts/generate-db.ts +344 -0
  30. package/tests/benchmark/scripts/run-benchmarks.ts +97 -0
  31. package/tests/integration/query/Query.complexAnalysis.test.ts +557 -0
  32. package/tests/integration/query/Query.edgeCases.test.ts +595 -0
  33. package/tests/integration/query/Query.explainAnalyze.test.ts +233 -0
  34. package/tests/stress/fixtures/RealisticComponents.ts +235 -0
  35. package/tests/stress/scenarios/realistic-scenarios.test.ts +1081 -0
  36. package/tests/stress/scenarios/timeout-investigation.test.ts +522 -0
  37. package/tests/unit/BatchLoader.test.ts +139 -25
@@ -0,0 +1,1081 @@
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
+ });