@forgehive/task 0.2.2 → 0.2.4

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 (77) hide show
  1. package/dist/index.d.ts +12 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +66 -13
  4. package/dist/index.js.map +1 -1
  5. package/dist/test/add-listener-with-boundaries.test.js +78 -7
  6. package/dist/test/add-listener-with-boundaries.test.js.map +1 -1
  7. package/dist/test/add-listener.test.js +36 -0
  8. package/dist/test/add-listener.test.js.map +1 -1
  9. package/dist/test/boundary-modes.test.js +45 -5
  10. package/dist/test/boundary-modes.test.js.map +1 -1
  11. package/dist/test/execution-record-boundaries.test.js +12 -2
  12. package/dist/test/execution-record-boundaries.test.js.map +1 -1
  13. package/dist/test/integration-enhanced-records.test.d.ts +2 -0
  14. package/dist/test/integration-enhanced-records.test.d.ts.map +1 -0
  15. package/dist/test/integration-enhanced-records.test.js +467 -0
  16. package/dist/test/integration-enhanced-records.test.js.map +1 -0
  17. package/dist/test/listen-execution-records.test.d.ts +2 -0
  18. package/dist/test/listen-execution-records.test.d.ts.map +1 -0
  19. package/dist/test/listen-execution-records.test.js +223 -0
  20. package/dist/test/listen-execution-records.test.js.map +1 -0
  21. package/dist/test/metrics-collection.test.d.ts +2 -0
  22. package/dist/test/metrics-collection.test.d.ts.map +1 -0
  23. package/dist/test/metrics-collection.test.js +409 -0
  24. package/dist/test/metrics-collection.test.js.map +1 -0
  25. package/dist/test/performance-edge-cases.test.d.ts +2 -0
  26. package/dist/test/performance-edge-cases.test.d.ts.map +1 -0
  27. package/dist/test/performance-edge-cases.test.js +502 -0
  28. package/dist/test/performance-edge-cases.test.js.map +1 -0
  29. package/dist/test/run-boundary.test.js +27 -3
  30. package/dist/test/run-boundary.test.js.map +1 -1
  31. package/dist/test/safe-replay-complex-boundary.test.js +110 -9
  32. package/dist/test/safe-replay-complex-boundary.test.js.map +1 -1
  33. package/dist/test/safe-replay.test.js +35 -5
  34. package/dist/test/safe-replay.test.js.map +1 -1
  35. package/dist/test/safe-run.test.js +46 -4
  36. package/dist/test/safe-run.test.js.map +1 -1
  37. package/dist/test/setmetrics-boundary.test.d.ts +2 -0
  38. package/dist/test/setmetrics-boundary.test.d.ts.map +1 -0
  39. package/dist/test/setmetrics-boundary.test.js +195 -0
  40. package/dist/test/setmetrics-boundary.test.js.map +1 -0
  41. package/dist/test/task-with-boundaries.test.js +63 -2
  42. package/dist/test/task-with-boundaries.test.js.map +1 -1
  43. package/dist/test/timing-capture.test.d.ts +2 -0
  44. package/dist/test/timing-capture.test.d.ts.map +1 -0
  45. package/dist/test/timing-capture.test.js +304 -0
  46. package/dist/test/timing-capture.test.js.map +1 -0
  47. package/dist/test/timing-utilities.test.d.ts +2 -0
  48. package/dist/test/timing-utilities.test.d.ts.map +1 -0
  49. package/dist/test/timing-utilities.test.js +127 -0
  50. package/dist/test/timing-utilities.test.js.map +1 -0
  51. package/dist/types.d.ts +93 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +78 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/utils/boundary.d.ts +3 -0
  56. package/dist/utils/boundary.d.ts.map +1 -1
  57. package/dist/utils/boundary.js +11 -2
  58. package/dist/utils/boundary.js.map +1 -1
  59. package/package.json +3 -2
  60. package/src/index.ts +97 -14
  61. package/src/test/add-listener-with-boundaries.test.ts +78 -7
  62. package/src/test/add-listener.test.ts +36 -0
  63. package/src/test/boundary-modes.test.ts +45 -5
  64. package/src/test/execution-record-boundaries.test.ts +12 -2
  65. package/src/test/listen-execution-records.test.ts +295 -0
  66. package/src/test/metrics-collection.test.ts +476 -0
  67. package/src/test/performance-edge-cases.test.ts +596 -0
  68. package/src/test/run-boundary.test.ts +27 -3
  69. package/src/test/safe-replay-complex-boundary.test.ts +115 -10
  70. package/src/test/safe-replay.test.ts +35 -5
  71. package/src/test/safe-run.test.ts +46 -4
  72. package/src/test/setmetrics-boundary.test.ts +223 -0
  73. package/src/test/task-with-boundaries.test.ts +71 -5
  74. package/src/test/timing-capture.test.ts +348 -0
  75. package/src/test/timing-utilities.test.ts +145 -0
  76. package/src/types.ts +139 -0
  77. package/src/utils/boundary.ts +15 -2
@@ -0,0 +1,596 @@
1
+ /* eslint-disable no-console */
2
+ import { createTask, TimingTracker, validateMetric, createMetric } from '../index'
3
+ import { Schema } from '@forgehive/schema'
4
+
5
+ describe('Performance and Edge Case Tests', () => {
6
+ describe('Performance impact of timing and metrics collection', () => {
7
+ it('should have minimal performance overhead for timing collection', async () => {
8
+ const baselineTask = createTask({
9
+ name: 'baseline-performance',
10
+ schema: new Schema({ iterations: Schema.number() }),
11
+ boundaries: {
12
+ simpleOperation: async (value: number) => {
13
+ return value * 2
14
+ }
15
+ },
16
+ fn: async ({ iterations }, { simpleOperation }) => {
17
+ const results = []
18
+ for (let i = 0; i < iterations; i++) {
19
+ results.push(await simpleOperation(i))
20
+ }
21
+ return { results }
22
+ }
23
+ })
24
+
25
+ const timingTask = createTask({
26
+ name: 'timing-performance',
27
+ schema: new Schema({ iterations: Schema.number() }),
28
+ boundaries: {
29
+ simpleOperation: async (value: number) => {
30
+ return value * 2
31
+ }
32
+ },
33
+ fn: async ({ iterations }, { simpleOperation, setMetrics }) => {
34
+ const results = []
35
+ for (let i = 0; i < iterations; i++) {
36
+ results.push(await simpleOperation(i))
37
+ // Add timing metrics for each operation
38
+ await setMetrics({ type: 'performance', name: 'operation_completed', value: i })
39
+ }
40
+ return { results }
41
+ }
42
+ })
43
+
44
+ const iterations = 100
45
+
46
+ // Measure baseline performance (without extensive metrics)
47
+ const baselineStart = Date.now()
48
+ const [baselineResult, baselineError, baselineRecord] = await baselineTask.safeRun({ iterations })
49
+ const baselineTime = Date.now() - baselineStart
50
+
51
+ expect(baselineError).toBeNull()
52
+ expect(baselineResult?.results).toHaveLength(iterations)
53
+
54
+ // Measure performance with timing and metrics
55
+ const timingStart = Date.now()
56
+ const [timingResult, timingError, timingRecord] = await timingTask.safeRun({ iterations })
57
+ const timingTime = Date.now() - timingStart
58
+
59
+ expect(timingError).toBeNull()
60
+ expect(timingResult?.results).toHaveLength(iterations)
61
+ expect(timingRecord.metrics).toHaveLength(iterations)
62
+
63
+ // Performance overhead should be reasonable (less than 50% increase)
64
+ const overhead = (timingTime - baselineTime) / baselineTime
65
+ expect(overhead).toBeLessThan(0.5) // Less than 50% overhead
66
+
67
+ // Verify timing accuracy - duration should be 0 or greater for fast operations
68
+ expect(baselineRecord.timing?.duration).toBeGreaterThanOrEqual(0)
69
+ expect(timingRecord.timing?.duration).toBeGreaterThanOrEqual(0)
70
+
71
+ console.log('Performance Test Results:')
72
+ console.log(`Baseline: ${baselineTime}ms, With timing: ${timingTime}ms`)
73
+ console.log(`Overhead: ${(overhead * 100).toFixed(1)}%`)
74
+ })
75
+
76
+ it('should handle high-frequency timing operations efficiently', () => {
77
+ const timings: Array<{ startTime: number; endTime: number; duration?: number }> = []
78
+ const iterations = 1000
79
+
80
+ const start = Date.now()
81
+
82
+ for (let i = 0; i < iterations; i++) {
83
+ const tracker = TimingTracker.create()
84
+ tracker.start()
85
+
86
+ // Simulate very brief operation
87
+ for (let j = 0; j < 10; j++) {
88
+ Math.random()
89
+ }
90
+
91
+ const timing = tracker.end()
92
+ if (timing) {
93
+ timings.push(timing)
94
+ }
95
+ }
96
+
97
+ const totalTime = Date.now() - start
98
+
99
+ // Verify all timings were captured
100
+ expect(timings).toHaveLength(iterations)
101
+ timings.forEach(timing => {
102
+ expect(timing).not.toBeNull()
103
+ expect(timing.duration).toBeGreaterThanOrEqual(0)
104
+ })
105
+
106
+ // High-frequency timing should complete reasonably quickly
107
+ expect(totalTime).toBeLessThan(1000) // Less than 1 second for 1000 operations
108
+
109
+ console.log(`High-frequency timing: ${iterations} operations in ${totalTime}ms`)
110
+ })
111
+
112
+ it('should handle large metrics collections without memory issues', async () => {
113
+ const largeMetricsTask = createTask({
114
+ name: 'large-metrics-test',
115
+ schema: new Schema({ metricCount: Schema.number() }),
116
+ boundaries: {},
117
+ fn: async ({ metricCount }, { setMetrics }) => {
118
+ for (let i = 0; i < metricCount; i++) {
119
+ await setMetrics({
120
+ type: i % 3 === 0 ? 'business' : i % 3 === 1 ? 'performance' : 'error',
121
+ name: `metric_${i}`,
122
+ value: Math.random() * 1000
123
+ })
124
+ }
125
+ return { metricsCreated: metricCount }
126
+ }
127
+ })
128
+
129
+ const metricCount = 1000
130
+ const memoryBefore = process.memoryUsage()
131
+
132
+ const [result, error, record] = await largeMetricsTask.safeRun({ metricCount })
133
+
134
+ const memoryAfter = process.memoryUsage()
135
+
136
+ expect(error).toBeNull()
137
+ expect(result?.metricsCreated).toBe(metricCount)
138
+ expect(record.metrics).toHaveLength(metricCount)
139
+
140
+ // Verify metrics are correctly structured
141
+ record.metrics?.forEach((metric, index) => {
142
+ expect(metric).toEqual(expect.objectContaining({
143
+ type: expect.stringMatching(/^(business|performance|error)$/),
144
+ name: `metric_${index}`,
145
+ value: expect.any(Number)
146
+ }))
147
+ })
148
+
149
+ // Memory usage should not explode
150
+ const memoryIncrease = memoryAfter.heapUsed - memoryBefore.heapUsed
151
+ expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024) // Less than 50MB increase
152
+
153
+ console.log(`Large metrics test: ${metricCount} metrics, memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(1)}MB`)
154
+ })
155
+ })
156
+
157
+ describe('Edge cases and error handling', () => {
158
+ it('should handle missing timing data gracefully', async () => {
159
+ // Manually create a broken timing scenario
160
+ const tracker = TimingTracker.create()
161
+ // Don't call start()
162
+ const timing = tracker.end()
163
+
164
+ expect(timing).toBeNull()
165
+
166
+ // Test with multiple end calls
167
+ tracker.start()
168
+ const timing1 = tracker.end()
169
+ const timing2 = tracker.end() // Second end call
170
+
171
+ expect(timing1).not.toBeNull()
172
+ expect(timing2).toBeNull() // Should return null on second call
173
+ })
174
+
175
+ it('should handle invalid metrics gracefully', () => {
176
+ // Test boundary conditions for metric validation
177
+ expect(validateMetric({ type: 'business', name: 'test', value: 0 })).toBe(true)
178
+ expect(validateMetric({ type: 'business', name: 'test', value: -1 })).toBe(true)
179
+ expect(validateMetric({ type: 'business', name: 'test', value: 999999 })).toBe(true)
180
+
181
+ // Test invalid cases
182
+ expect(validateMetric({ type: 'business', name: 'test', value: null })).toBe(false)
183
+ expect(validateMetric({ type: 'business', name: 'test', value: undefined })).toBe(false)
184
+ expect(validateMetric({ type: 'business', name: 'test', value: 'string' })).toBe(false)
185
+ expect(validateMetric({ type: 'business', name: 'test', value: {} })).toBe(false)
186
+ expect(validateMetric({ type: 'business', name: 'test', value: [] })).toBe(false)
187
+
188
+ // Test edge cases with createMetric
189
+ expect(() => createMetric('business', 'test', 0)).not.toThrow()
190
+ expect(() => createMetric('business', 'test', -999)).not.toThrow()
191
+ expect(() => createMetric('business', 'test', 0.0001)).not.toThrow()
192
+
193
+ expect(() => createMetric('', 'test', 1)).toThrow()
194
+ expect(() => createMetric('business', '', 1)).toThrow()
195
+ expect(() => createMetric('business', 'test', NaN)).toThrow()
196
+ expect(() => createMetric('business', 'test', Infinity)).toThrow()
197
+ expect(() => createMetric('business', 'test', -Infinity)).toThrow()
198
+ })
199
+
200
+ it('should handle memory cleanup and long-running tasks', async () => {
201
+ const longRunningTask = createTask({
202
+ name: 'memory-cleanup-test',
203
+ schema: new Schema({ duration: Schema.number() }),
204
+ boundaries: {
205
+ memoryIntensiveOperation: async (iterations: number) => {
206
+ const data = []
207
+ for (let i = 0; i < iterations; i++) {
208
+ data.push(new Array(1000).fill(Math.random()))
209
+ }
210
+ return { processed: data.length, size: data.length * 1000 }
211
+ }
212
+ },
213
+ fn: async ({ duration }, { memoryIntensiveOperation, setMetrics }) => {
214
+ const startTime = Date.now()
215
+ const results = []
216
+
217
+ while (Date.now() - startTime < duration) {
218
+ const result = await memoryIntensiveOperation(100)
219
+ results.push(result)
220
+
221
+ await setMetrics({ type: 'performance', name: 'memory_operations', value: 1 })
222
+ await setMetrics({ type: 'business', name: 'data_processed', value: result.processed })
223
+
224
+ // Force garbage collection opportunity
225
+ if (results.length % 10 === 0) {
226
+ if (global.gc) {
227
+ global.gc()
228
+ }
229
+ }
230
+ }
231
+
232
+ return { operations: results.length, totalProcessed: results.reduce((sum, r) => sum + r.processed, 0) }
233
+ }
234
+ })
235
+
236
+ const memoryBefore = process.memoryUsage()
237
+
238
+ const [result, error, record] = await longRunningTask.safeRun({ duration: 500 }) // 500ms
239
+
240
+ // Allow some time for garbage collection
241
+ await new Promise(resolve => setTimeout(resolve, 100))
242
+ if (global.gc) {
243
+ global.gc()
244
+ }
245
+
246
+ const memoryAfter = process.memoryUsage()
247
+
248
+ expect(error).toBeNull()
249
+ expect(result?.operations).toBeGreaterThan(0)
250
+ expect(record.metrics?.length).toBeGreaterThan(0)
251
+
252
+ // Memory should not grow excessively
253
+ const memoryIncrease = memoryAfter.heapUsed - memoryBefore.heapUsed
254
+ expect(memoryIncrease).toBeLessThan(100 * 1024 * 1024) // Less than 100MB
255
+
256
+ console.log(`Memory cleanup test: ${result?.operations} operations, memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(1)}MB`)
257
+ })
258
+
259
+ it('should handle concurrent task executions without interference', async () => {
260
+ const concurrentTask = createTask({
261
+ name: 'concurrent-test',
262
+ schema: new Schema({ taskId: Schema.string(), delay: Schema.number() }),
263
+ boundaries: {
264
+ processWithDelay: async (taskId: string, delay: number) => {
265
+ await new Promise(resolve => setTimeout(resolve, delay))
266
+ return { taskId, processedAt: Date.now() }
267
+ }
268
+ },
269
+ fn: async ({ taskId, delay }, { processWithDelay, setMetrics, setMetadata }) => {
270
+ await setMetadata('taskId', taskId)
271
+ await setMetadata('concurrencyTest', 'true')
272
+
273
+ await setMetrics({ type: 'business', name: 'concurrent_tasks', value: 1 })
274
+
275
+ const result = await processWithDelay(taskId, delay)
276
+
277
+ await setMetrics({ type: 'performance', name: 'task_delay', value: delay })
278
+ await setMetrics({ type: 'business', name: 'tasks_completed', value: 1 })
279
+
280
+ return { taskId, result, delay }
281
+ }
282
+ })
283
+
284
+ // Launch multiple concurrent tasks
285
+ const tasks = [
286
+ concurrentTask.safeRun({ taskId: 'task-1', delay: 100 }),
287
+ concurrentTask.safeRun({ taskId: 'task-2', delay: 150 }),
288
+ concurrentTask.safeRun({ taskId: 'task-3', delay: 80 }),
289
+ concurrentTask.safeRun({ taskId: 'task-4', delay: 200 }),
290
+ concurrentTask.safeRun({ taskId: 'task-5', delay: 120 })
291
+ ]
292
+
293
+ const results = await Promise.all(tasks)
294
+
295
+ // Verify all tasks completed successfully
296
+ results.forEach(([result, error, record], index) => {
297
+ expect(error).toBeNull()
298
+ expect(result?.taskId).toBe(`task-${index + 1}`)
299
+ expect(record.metrics).toHaveLength(3)
300
+ expect(record.timing).toBeDefined()
301
+ expect(record.boundaries.processWithDelay).toHaveLength(1)
302
+ })
303
+
304
+ // Verify timing isolation between concurrent tasks
305
+ const timings = results.map(([,, record]) => record.timing!)
306
+ timings.forEach((timing, index) => {
307
+ expect(timing.duration).toBeGreaterThanOrEqual([100, 150, 80, 200, 120][index] - 20)
308
+ expect(timing.duration).toBeLessThan([100, 150, 80, 200, 120][index] + 50)
309
+ })
310
+
311
+ console.log(`Concurrent tasks completed: ${results.length}`)
312
+ })
313
+
314
+ it('should handle extreme boundary call counts', async () => {
315
+ const extremeBoundaryTask = createTask({
316
+ name: 'extreme-boundary-test',
317
+ schema: new Schema({ callCount: Schema.number() }),
318
+ boundaries: {
319
+ lightweightOperation: async (index: number) => {
320
+ return { index, doubled: index * 2 }
321
+ }
322
+ },
323
+ fn: async ({ callCount }, { lightweightOperation, setMetrics }) => {
324
+ await setMetrics({ type: 'business', name: 'boundary_test_runs', value: 1 })
325
+
326
+ const results = []
327
+ for (let i = 0; i < callCount; i++) {
328
+ const result = await lightweightOperation(i)
329
+ results.push(result)
330
+
331
+ // Add metrics every 100 calls to avoid excessive metrics
332
+ if (i % 100 === 0) {
333
+ await setMetrics({ type: 'performance', name: 'batch_progress', value: i })
334
+ }
335
+ }
336
+
337
+ await setMetrics({ type: 'business', name: 'total_boundary_calls', value: callCount })
338
+ return { results: results.length, lastResult: results[results.length - 1] }
339
+ }
340
+ })
341
+
342
+ const callCount = 1000
343
+ const start = Date.now()
344
+
345
+ const [result, error, record] = await extremeBoundaryTask.safeRun({ callCount })
346
+
347
+ const executionTime = Date.now() - start
348
+
349
+ expect(error).toBeNull()
350
+ expect(result?.results).toBe(callCount)
351
+ expect(record.boundaries.lightweightOperation).toHaveLength(callCount)
352
+
353
+ // Verify all boundary calls have timing
354
+ record.boundaries.lightweightOperation.forEach((call, index) => {
355
+ expect(call.timing).toBeDefined()
356
+ expect(call.input).toEqual([index])
357
+ expect(call.output).toEqual({ index, doubled: index * 2 })
358
+ })
359
+
360
+ // Performance should be reasonable for high volume
361
+ expect(executionTime).toBeLessThan(5000) // Less than 5 seconds for 1000 calls
362
+
363
+ console.log(`Extreme boundary test: ${callCount} calls in ${executionTime}ms`)
364
+ })
365
+
366
+ it('should handle schema validation edge cases with timing', async () => {
367
+ const validationTask = createTask({
368
+ name: 'validation-edge-case-test',
369
+ schema: new Schema({
370
+ requiredString: Schema.string(),
371
+ optionalNumber: Schema.number().optional(),
372
+ nestedValue: Schema.number()
373
+ }),
374
+ boundaries: {},
375
+ fn: async ({ requiredString, optionalNumber, nestedValue }) => {
376
+ return {
377
+ processed: true,
378
+ stringLength: requiredString.length,
379
+ hasOptional: optionalNumber !== undefined,
380
+ nestedValue: nestedValue
381
+ }
382
+ }
383
+ })
384
+
385
+ // Test with valid input
386
+ const [validResult, validError, validRecord] = await validationTask.safeRun({
387
+ requiredString: 'test',
388
+ optionalNumber: 42,
389
+ nestedValue: 100
390
+ })
391
+
392
+ expect(validError).toBeNull()
393
+ expect(validResult?.processed).toBe(true)
394
+ expect(validRecord.timing).toBeDefined()
395
+ expect(validRecord.type).toBe('success')
396
+
397
+ // Test with invalid input (should fail validation before timing main function)
398
+ const [invalidResult, invalidError, invalidRecord] = await validationTask.safeRun({
399
+ requiredString: 123, // Wrong type
400
+ nestedValue: 'not-a-number' // Wrong type
401
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
402
+ } as any)
403
+
404
+ expect(invalidResult).toBeNull()
405
+ expect(invalidError).not.toBeNull()
406
+ expect(invalidRecord.type).toBe('error')
407
+ expect(invalidRecord.timing).toBeUndefined() // No main function timing on validation error
408
+
409
+ // Test with missing required field
410
+ const [missingResult, missingError, missingRecord] = await validationTask.safeRun({
411
+ nestedValue: 100
412
+ // Missing requiredString
413
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
414
+ } as any)
415
+
416
+ expect(missingResult).toBeNull()
417
+ expect(missingError).not.toBeNull()
418
+ expect(missingRecord.type).toBe('error')
419
+ expect(missingRecord.timing).toBeUndefined()
420
+
421
+ console.log(`Schema validation edge cases: valid=${validRecord.type}, invalid=${invalidRecord.type}, missing=${missingRecord.type}`)
422
+ })
423
+
424
+ it('should handle very large execution records efficiently', async () => {
425
+ const largeRecordTask = createTask({
426
+ name: 'large-record-test',
427
+ schema: new Schema({
428
+ dataSize: Schema.number(),
429
+ metricCount: Schema.number()
430
+ }),
431
+ boundaries: {
432
+ generateLargeData: async (size: number) => {
433
+ const data = []
434
+ for (let i = 0; i < size; i++) {
435
+ data.push({
436
+ id: `item_${i}`,
437
+ data: new Array(100).fill(Math.random()),
438
+ metadata: { index: i, category: `cat_${i % 10}` }
439
+ })
440
+ }
441
+ return { items: data, totalSize: size }
442
+ },
443
+ processLargeDataset: async (dataset: { items: Array<{ id: string; data: number[] }> }) => {
444
+ // Simulate heavy processing
445
+ const processed = dataset.items.map((item: { id: string; data: number[] }) => ({
446
+ id: item.id,
447
+ processed: true,
448
+ checksum: item.data.reduce((sum: number, val: number) => sum + val, 0)
449
+ }))
450
+ return { processed, count: processed.length }
451
+ }
452
+ },
453
+ fn: async ({ dataSize, metricCount }, { generateLargeData, processLargeDataset, setMetrics, setMetadata }) => {
454
+ await setMetadata('dataSize', dataSize.toString())
455
+ await setMetadata('expectedMetrics', metricCount.toString())
456
+
457
+ // Generate large dataset
458
+ const dataset = await generateLargeData(dataSize)
459
+ await setMetrics({ type: 'business', name: 'data_generated', value: dataset.totalSize })
460
+
461
+ // Generate many metrics
462
+ for (let i = 0; i < metricCount; i++) {
463
+ await setMetrics({
464
+ type: i % 3 === 0 ? 'business' : i % 3 === 1 ? 'performance' : 'error',
465
+ name: `large_record_metric_${i}`,
466
+ value: Math.random() * 1000
467
+ })
468
+ }
469
+
470
+ // Process the large dataset
471
+ const result = await processLargeDataset(dataset)
472
+ await setMetrics({ type: 'business', name: 'items_processed', value: result.count })
473
+
474
+ return {
475
+ dataSize: dataset.totalSize,
476
+ processedCount: result.count,
477
+ metricsGenerated: metricCount,
478
+ status: 'completed'
479
+ }
480
+ }
481
+ })
482
+
483
+ const memoryBefore = process.memoryUsage()
484
+ const start = Date.now()
485
+
486
+ const [result, error, record] = await largeRecordTask.safeRun({
487
+ dataSize: 100, // 100 items with 100 random numbers each
488
+ metricCount: 200 // 200 metrics
489
+ })
490
+
491
+ const executionTime = Date.now() - start
492
+ const memoryAfter = process.memoryUsage()
493
+
494
+ expect(error).toBeNull()
495
+ expect(result?.status).toBe('completed')
496
+ expect(result?.dataSize).toBe(100)
497
+ expect(result?.metricsGenerated).toBe(200)
498
+
499
+ // Verify record structure
500
+ expect(record.metrics).toHaveLength(202) // 200 + 2 additional metrics
501
+ expect(record.boundaries.generateLargeData).toHaveLength(1)
502
+ expect(record.boundaries.processLargeDataset).toHaveLength(1)
503
+
504
+ // Verify large data in boundaries is preserved
505
+ const generatedData = record.boundaries.generateLargeData[0].output
506
+ if (generatedData) {
507
+ expect(generatedData.items).toHaveLength(100)
508
+ expect(generatedData.items[0].data).toHaveLength(100)
509
+ }
510
+
511
+ // Performance should be reasonable even with large records
512
+ expect(executionTime).toBeLessThan(2000) // Less than 2 seconds
513
+
514
+ const memoryIncrease = memoryAfter.heapUsed - memoryBefore.heapUsed
515
+ expect(memoryIncrease).toBeLessThan(100 * 1024 * 1024) // Less than 100MB
516
+
517
+ console.log(`Large record test: ${result?.dataSize} items, ${result?.metricsGenerated} metrics, ${executionTime}ms, ${(memoryIncrease / 1024 / 1024).toFixed(1)}MB`)
518
+ })
519
+ })
520
+
521
+ describe('Timing accuracy and precision tests', () => {
522
+ it('should maintain timing accuracy across different execution patterns', async () => {
523
+ const precisionTask = createTask({
524
+ name: 'timing-precision-test',
525
+ schema: new Schema({ pattern: Schema.string() }),
526
+ boundaries: {
527
+ fixedDelay: async (ms: number) => {
528
+ await new Promise(resolve => setTimeout(resolve, ms))
529
+ return { delayed: ms }
530
+ },
531
+ variableDelay: async (baseMs: number) => {
532
+ const actualDelay = baseMs + Math.random() * 20 - 10 // ±10ms variation
533
+ await new Promise(resolve => setTimeout(resolve, actualDelay))
534
+ return { delayed: actualDelay }
535
+ }
536
+ },
537
+ fn: async ({ pattern }, { fixedDelay, variableDelay, setMetrics }) => {
538
+ const timings = []
539
+
540
+ if (pattern === 'fixed') {
541
+ // Test fixed delays
542
+ const delays = [50, 100, 150]
543
+ for (const delay of delays) {
544
+ const start = Date.now()
545
+ await fixedDelay(delay)
546
+ const measured = Date.now() - start
547
+ timings.push({ expected: delay, measured })
548
+ await setMetrics({ type: 'performance', name: 'timing_accuracy', value: Math.abs(measured - delay) })
549
+ }
550
+ } else if (pattern === 'variable') {
551
+ // Test variable delays
552
+ for (let i = 0; i < 5; i++) {
553
+ const baseDelay = 80
554
+ const start = Date.now()
555
+ const result = await variableDelay(baseDelay)
556
+ const measured = Date.now() - start
557
+ timings.push({ expected: result.delayed, measured })
558
+ }
559
+ }
560
+
561
+ return { pattern, timings }
562
+ }
563
+ })
564
+
565
+ // Test fixed timing precision
566
+ const [fixedResult, fixedError, fixedRecord] = await precisionTask.safeRun({ pattern: 'fixed' })
567
+
568
+ expect(fixedError).toBeNull()
569
+ expect(fixedResult?.timings).toHaveLength(3)
570
+
571
+ // Verify boundary timing accuracy
572
+ fixedRecord.boundaries.fixedDelay.forEach((call, index) => {
573
+ const expectedDelay = [50, 100, 150][index]
574
+ const actualDuration = call.timing.duration
575
+ const accuracy = Math.abs(actualDuration! - expectedDelay)
576
+
577
+ // Timing should be within 30ms of expected (allowing for system variance)
578
+ expect(accuracy).toBeLessThan(30)
579
+ })
580
+
581
+ // Test variable timing
582
+ const [variableResult, variableError, variableRecord] = await precisionTask.safeRun({ pattern: 'variable' })
583
+
584
+ expect(variableError).toBeNull()
585
+ expect(variableResult?.timings).toHaveLength(5)
586
+
587
+ // Verify variable timing boundaries
588
+ variableRecord.boundaries.variableDelay.forEach((call) => {
589
+ expect(call.timing.duration).toBeGreaterThan(60) // Should be at least 70ms (80-10)
590
+ expect(call.timing.duration).toBeLessThan(120) // Should be at most 110ms (80+10+20 for variance)
591
+ })
592
+
593
+ console.log(`Timing precision test completed: fixed=${fixedRecord.boundaries.fixedDelay.length} calls, variable=${variableRecord.boundaries.variableDelay.length} calls`)
594
+ })
595
+ })
596
+ })
@@ -12,7 +12,15 @@ describe('Run boundary tests', function () {
12
12
  const runTape = identity.getRunData()
13
13
 
14
14
  expect(runTape.length).toBe(1)
15
- expect(runTape[0]).toEqual({ input: [{ value: 5 }], output: { value: 5 } })
15
+ expect(runTape[0]).toEqual({
16
+ input: [{ value: 5 }],
17
+ output: { value: 5 },
18
+ timing: expect.objectContaining({
19
+ startTime: expect.any(Number),
20
+ endTime: expect.any(Number),
21
+ duration: expect.any(Number)
22
+ })
23
+ })
16
24
  })
17
25
 
18
26
  it('Should only add a record if run is active', async function () {
@@ -41,7 +49,15 @@ describe('Run boundary tests', function () {
41
49
  const runTape = identity.getRunData()
42
50
 
43
51
  expect(runTape.length).toBe(1)
44
- expect(runTape[0]).toEqual({ input: [{ value: 5 }], output: { value: 5 } })
52
+ expect(runTape[0]).toEqual({
53
+ input: [{ value: 5 }],
54
+ output: { value: 5 },
55
+ timing: expect.objectContaining({
56
+ startTime: expect.any(Number),
57
+ endTime: expect.any(Number),
58
+ duration: expect.any(Number)
59
+ })
60
+ })
45
61
  })
46
62
 
47
63
  it('Should have run elements from this run', async function () {
@@ -59,6 +75,14 @@ describe('Run boundary tests', function () {
59
75
  const runTape = identity.getRunData()
60
76
 
61
77
  expect(runTape.length).toBe(1)
62
- expect(runTape[0]).toEqual({ input: [{ value: 5 }], output: { value: 5 } })
78
+ expect(runTape[0]).toEqual({
79
+ input: [{ value: 5 }],
80
+ output: { value: 5 },
81
+ timing: expect.objectContaining({
82
+ startTime: expect.any(Number),
83
+ endTime: expect.any(Number),
84
+ duration: expect.any(Number)
85
+ })
86
+ })
63
87
  })
64
88
  })