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,285 +0,0 @@
1
- /**
2
- * Stress Tests - Query Performance Benchmarks
3
- *
4
- * Tests query performance with configurable data volumes
5
- * Default: 10,000 records (smoke test)
6
- * Set STRESS_RECORD_COUNT env var for larger tests
7
- */
8
- import { describe, test, beforeAll, afterAll, expect } from 'bun:test';
9
- import { DataSeeder } from '../DataSeeder';
10
- import { BenchmarkRunner } from '../BenchmarkRunner';
11
- import { StressTestReporter } from '../StressTestReporter';
12
- import { Query, FilterOp } from '../../../query/Query';
13
- import { StressUser, StressProfile } from '../fixtures/StressTestComponents';
14
- import { ensureComponentsRegistered } from '../../utils';
15
-
16
- // Configurable via environment variable
17
- const RECORD_COUNT = parseInt(process.env.STRESS_RECORD_COUNT || '10000', 10);
18
- const BATCH_SIZE = Math.min(5000, Math.floor(RECORD_COUNT / 10) || 1000);
19
-
20
- describe('Stress Tests - Query Performance', () => {
21
- const seeder = new DataSeeder();
22
- const benchmark = new BenchmarkRunner();
23
- const reporter = new StressTestReporter();
24
- let entityIds: string[] = [];
25
- let setupTime = 0;
26
-
27
- beforeAll(async () => {
28
- const startSetup = performance.now();
29
-
30
- // Ensure components are registered
31
- await ensureComponentsRegistered(StressUser, StressProfile);
32
-
33
- // Wait for index creation to settle (prevents deadlocks from concurrent index creation)
34
- await new Promise(resolve => setTimeout(resolve, 3000));
35
-
36
- console.log(`\n Seeding ${RECORD_COUNT.toLocaleString()} records...`);
37
-
38
- const result = await seeder.seed(
39
- StressUser,
40
- (i) => ({
41
- name: `User ${i}`,
42
- email: `user${i}@stress.test`,
43
- age: 18 + (i % 62),
44
- status: ['active', 'inactive', 'pending', 'banned'][i % 4],
45
- score: Math.random() * 1000,
46
- createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000)
47
- }),
48
- {
49
- totalEntities: RECORD_COUNT,
50
- batchSize: BATCH_SIZE,
51
- onProgress: (current, total, elapsed) => {
52
- if (current % (BATCH_SIZE * 2) === 0 || current === total) {
53
- const pct = ((current / total) * 100).toFixed(1);
54
- const rate = ((current / elapsed) * 1000).toFixed(0);
55
- console.log(` Progress: ${pct}% (${rate} records/sec)`);
56
- }
57
- }
58
- }
59
- );
60
-
61
- entityIds = result.entityIds;
62
- console.log(` Seeded in ${(result.totalTime / 1000).toFixed(1)}s (${result.recordsPerSecond.toFixed(0)} records/sec)`);
63
-
64
- // Add profile components to 50% of entities
65
- if (RECORD_COUNT >= 1000) {
66
- console.log(' Adding profile components to 50% of entities...');
67
- await seeder.seedAdditionalComponent(
68
- entityIds.slice(0, Math.floor(entityIds.length / 2)),
69
- StressProfile,
70
- (i) => ({
71
- bio: `This is bio ${i}`,
72
- avatarUrl: `https://example.com/avatar/${i}.png`,
73
- verified: i % 3 === 0
74
- }),
75
- BATCH_SIZE
76
- );
77
- }
78
-
79
- console.log(' Running VACUUM ANALYZE...');
80
- await seeder.optimize();
81
-
82
- setupTime = performance.now() - startSetup;
83
- console.log(` Setup complete in ${(setupTime / 1000).toFixed(1)}s\n`);
84
- });
85
-
86
- afterAll(async () => {
87
- // Print report
88
- const recordCount = await seeder.getRecordCount();
89
- const report = reporter.generateReport(benchmark.getResults(), {
90
- recordCount,
91
- environment: `PostgreSQL, Bun ${Bun.version}`,
92
- duration: setupTime
93
- });
94
- console.log('\n' + report);
95
-
96
- // Cleanup seeded data
97
- console.log('\n Cleaning up test data...');
98
- await seeder.cleanup(entityIds, BATCH_SIZE);
99
- console.log(' Cleanup complete.');
100
- });
101
-
102
- test('indexed equality filter (status = active)', async () => {
103
- const result = await benchmark.runWithOutput(
104
- 'Filter: status = active',
105
- () => new Query()
106
- .with(StressUser, { filters: [{ field: 'status', operator: FilterOp.EQ, value: 'active' }] })
107
- .take(100)
108
- .exec(),
109
- { targetP95: 50, iterations: 15 }
110
- );
111
- expect(result.passed).toBe(true);
112
- });
113
-
114
- test('indexed range filter (age 25-35)', async () => {
115
- const result = await benchmark.runWithOutput(
116
- 'Filter: age 25-35',
117
- () => new Query()
118
- .with(StressUser, {
119
- filters: [
120
- { field: 'age', operator: FilterOp.GTE, value: 25 },
121
- { field: 'age', operator: FilterOp.LTE, value: 35 }
122
- ]
123
- })
124
- .take(100)
125
- .exec(),
126
- { targetP95: 75, iterations: 15 }
127
- );
128
- expect(result.passed).toBe(true);
129
- });
130
-
131
- test('count query', async () => {
132
- const result = await benchmark.runWithOutput(
133
- 'COUNT all',
134
- async () => [await new Query().with(StressUser).count()],
135
- { targetP95: 100, iterations: 15 }
136
- );
137
- expect(result.passed).toBe(true);
138
- });
139
-
140
- test('pagination - shallow offset (1000)', async () => {
141
- const result = await benchmark.runWithOutput(
142
- 'Offset 1000',
143
- () => new Query()
144
- .with(StressUser)
145
- .sortBy(StressUser, 'name', 'ASC')
146
- .take(100)
147
- .offset(1000)
148
- .exec(),
149
- { targetP95: 75, iterations: 15 }
150
- );
151
- expect(result.passed).toBe(true);
152
- });
153
-
154
- test('pagination - deep offset (50% of records)', async () => {
155
- const deepOffset = Math.floor(RECORD_COUNT / 2);
156
- const result = await benchmark.runWithOutput(
157
- `Offset ${deepOffset.toLocaleString()}`,
158
- () => new Query()
159
- .with(StressUser)
160
- .sortBy(StressUser, 'name', 'ASC')
161
- .take(100)
162
- .offset(deepOffset)
163
- .exec(),
164
- { targetP95: 500, iterations: 10 }
165
- );
166
- // Deep pagination is expected to be slower, use a more lenient target
167
- expect(result.timings.p95).toBeLessThan(2000);
168
- });
169
-
170
- test('cursor pagination - first page', async () => {
171
- const result = await benchmark.runWithOutput(
172
- 'Cursor: first page',
173
- () => new Query()
174
- .with(StressUser)
175
- .take(100)
176
- .exec(),
177
- { targetP95: 50, iterations: 15 }
178
- );
179
- expect(result.passed).toBe(true);
180
- });
181
-
182
- test('cursor pagination - from middle (using cursor)', async () => {
183
- // First, get an entity ID from the middle of the dataset
184
- const midpointResults = await new Query()
185
- .with(StressUser)
186
- .take(1)
187
- .offset(Math.floor(RECORD_COUNT / 2))
188
- .exec();
189
-
190
- const cursorId = midpointResults[0]?.id;
191
- expect(cursorId).toBeDefined();
192
-
193
- const result = await benchmark.runWithOutput(
194
- 'Cursor: from middle (O(1))',
195
- () => new Query()
196
- .with(StressUser)
197
- .cursor(cursorId!)
198
- .take(100)
199
- .exec(),
200
- { targetP95: 50, iterations: 15 }
201
- );
202
- // Cursor pagination should be fast regardless of position
203
- expect(result.passed).toBe(true);
204
- });
205
-
206
- test('cursor pagination - near end (using cursor)', async () => {
207
- // Get an entity ID from near the end (90%)
208
- const nearEndResults = await new Query()
209
- .with(StressUser)
210
- .take(1)
211
- .offset(Math.floor(RECORD_COUNT * 0.9))
212
- .exec();
213
-
214
- const cursorId = nearEndResults[0]?.id;
215
- expect(cursorId).toBeDefined();
216
-
217
- const result = await benchmark.runWithOutput(
218
- 'Cursor: near end (O(1))',
219
- () => new Query()
220
- .with(StressUser)
221
- .cursor(cursorId!)
222
- .take(100)
223
- .exec(),
224
- { targetP95: 50, iterations: 15 }
225
- );
226
- // Cursor pagination should be fast regardless of position
227
- expect(result.passed).toBe(true);
228
- });
229
-
230
- test('multi-component join (User + Profile)', async () => {
231
- const result = await benchmark.runWithOutput(
232
- 'Join: User + Profile',
233
- () => new Query()
234
- .with(StressUser)
235
- .with(StressProfile)
236
- .take(100)
237
- .exec(),
238
- { targetP95: 150, iterations: 15 }
239
- );
240
- expect(result.passed).toBe(true);
241
- });
242
-
243
- test('combined filters and sort', async () => {
244
- const result = await benchmark.runWithOutput(
245
- 'Filter + Sort + Limit',
246
- () => new Query()
247
- .with(StressUser, {
248
- filters: [
249
- { field: 'status', operator: FilterOp.EQ, value: 'active' },
250
- { field: 'age', operator: FilterOp.GTE, value: 21 }
251
- ]
252
- })
253
- .sortBy(StressUser, 'score', 'DESC')
254
- .take(50)
255
- .exec(),
256
- { targetP95: 100, iterations: 15 }
257
- );
258
- expect(result.passed).toBe(true);
259
- });
260
-
261
- test('simple query without filters', async () => {
262
- const result = await benchmark.runWithOutput(
263
- 'Simple: take 100',
264
- () => new Query()
265
- .with(StressUser)
266
- .take(100)
267
- .exec(),
268
- { targetP95: 50, iterations: 15 }
269
- );
270
- expect(result.passed).toBe(true);
271
- });
272
-
273
- test('query with populate', async () => {
274
- const result = await benchmark.runWithOutput(
275
- 'Populated: take 50',
276
- () => new Query()
277
- .with(StressUser)
278
- .populate()
279
- .take(50)
280
- .exec(),
281
- { targetP95: 100, iterations: 15 }
282
- );
283
- expect(result.passed).toBe(true);
284
- });
285
- });