bunsane 0.2.3 → 0.2.7
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.
- package/config/cache.config.ts +2 -0
- package/core/ArcheType.ts +67 -34
- package/core/BatchLoader.ts +215 -30
- package/core/Entity.ts +2 -2
- package/core/RequestContext.ts +15 -10
- package/core/RequestLoaders.ts +4 -2
- package/core/cache/CacheFactory.ts +3 -1
- package/core/cache/CacheProvider.ts +1 -0
- package/core/cache/CacheWarmer.ts +45 -23
- package/core/cache/MemoryCache.ts +10 -1
- package/core/cache/RedisCache.ts +26 -7
- package/core/validateEnv.ts +8 -0
- package/database/DatabaseHelper.ts +113 -1
- package/database/index.ts +78 -45
- package/docs/SCALABILITY_PLAN.md +175 -0
- package/package.json +13 -2
- package/query/CTENode.ts +44 -24
- package/query/ComponentInclusionNode.ts +181 -91
- package/query/Query.ts +9 -9
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +338 -0
- package/tests/benchmark/bunfig.toml +9 -0
- package/tests/benchmark/fixtures/EcommerceComponents.ts +283 -0
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +301 -0
- package/tests/benchmark/fixtures/RelationTracker.ts +159 -0
- package/tests/benchmark/fixtures/index.ts +6 -0
- package/tests/benchmark/index.ts +22 -0
- package/tests/benchmark/noop-preload.ts +3 -0
- package/tests/benchmark/runners/BenchmarkLoader.ts +132 -0
- package/tests/benchmark/runners/index.ts +4 -0
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +465 -0
- package/tests/benchmark/scripts/generate-db.ts +344 -0
- package/tests/benchmark/scripts/run-benchmarks.ts +97 -0
- package/tests/integration/query/Query.complexAnalysis.test.ts +557 -0
- package/tests/integration/query/Query.explainAnalyze.test.ts +233 -0
- package/tests/stress/fixtures/RealisticComponents.ts +235 -0
- package/tests/stress/scenarios/realistic-scenarios.test.ts +1081 -0
- package/tests/stress/scenarios/timeout-investigation.test.ts +522 -0
- package/tests/unit/BatchLoader.test.ts +139 -25
|
@@ -0,0 +1,465 @@
|
|
|
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
|
+
});
|