@forgehive/task 0.2.1 → 0.2.3
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/README.md +164 -10
- package/dist/index.d.ts +18 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -10
- package/dist/index.js.map +1 -1
- package/dist/test/execution-record-boundaries.test.d.ts +2 -0
- package/dist/test/execution-record-boundaries.test.d.ts.map +1 -0
- package/dist/test/execution-record-boundaries.test.js +220 -0
- package/dist/test/execution-record-boundaries.test.js.map +1 -0
- package/dist/test/listen-execution-records.test.d.ts +2 -0
- package/dist/test/listen-execution-records.test.d.ts.map +1 -0
- package/dist/test/listen-execution-records.test.js +223 -0
- package/dist/test/listen-execution-records.test.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +104 -14
- package/src/test/execution-record-boundaries.test.ts +266 -0
- package/src/test/listen-execution-records.test.ts +295 -0
package/src/index.ts
CHANGED
|
@@ -50,9 +50,6 @@ export interface ReplayConfig<B extends Boundaries = Boundaries> {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// ToDo: Add a type for the boundaries data
|
|
54
|
-
|
|
55
|
-
|
|
56
53
|
// Make BoundaryLog generic
|
|
57
54
|
export type BoundaryLog<I extends unknown[] = unknown[], O = unknown> = BoundaryRecord<I, O>;
|
|
58
55
|
|
|
@@ -139,10 +136,19 @@ type BoundaryData = Array<{input: unknown[], output?: unknown}>
|
|
|
139
136
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
137
|
export type InferSchemaType<S> = S extends Schema<any> ? InferSchema<S> : Record<string, unknown>;
|
|
141
138
|
|
|
139
|
+
// Type for execution record boundaries that are automatically injected
|
|
140
|
+
// When adding new execution boundaries, add their types here
|
|
141
|
+
export type ExecutionRecordBoundaries = {
|
|
142
|
+
setMetadata: (key: string, value: string) => Promise<void>
|
|
143
|
+
// Future execution boundaries can be added here:
|
|
144
|
+
// setContext: (context: Record<string, unknown>) => Promise<void>
|
|
145
|
+
// addTag: (tag: string) => Promise<void>
|
|
146
|
+
}
|
|
147
|
+
|
|
142
148
|
// Helper type for task function with proper typing
|
|
143
149
|
export type TaskFunction<S, B extends Boundaries> =
|
|
144
150
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
-
(argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B>) => Promise<any>;
|
|
151
|
+
(argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B> & ExecutionRecordBoundaries) => Promise<any>;
|
|
146
152
|
|
|
147
153
|
/**
|
|
148
154
|
* Utility function to compute the execution record type based on output and error state
|
|
@@ -163,7 +169,11 @@ export const Task = class Task<
|
|
|
163
169
|
B extends Boundaries = Boundaries,
|
|
164
170
|
Func extends BaseFunction = BaseFunction
|
|
165
171
|
> implements TaskInstanceType<Func, B> {
|
|
166
|
-
public version: string = '0.1.
|
|
172
|
+
public version: string = '0.1.8'
|
|
173
|
+
|
|
174
|
+
// Static property for global listener
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
176
|
+
static globalListener?: (record: ExecutionRecord<any, any, any>) => Promise<void>
|
|
167
177
|
|
|
168
178
|
_fn: Func
|
|
169
179
|
_mode: Mode
|
|
@@ -181,6 +191,29 @@ export const Task = class Task<
|
|
|
181
191
|
_schema: Schema<Record<string, SchemaType>> | undefined
|
|
182
192
|
_listener?: ((record: ExecutionRecord<Parameters<Func>[0], ReturnType<Func>, B>) => void) | undefined
|
|
183
193
|
|
|
194
|
+
// Static method to set global listener
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
+
static listenExecutionRecords(listener: (record: ExecutionRecord<any, any, any>) => Promise<void>): void {
|
|
197
|
+
this.globalListener = listener
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Static method to emit to global listener with error handling
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
202
|
+
static emitExecutionRecord(record: ExecutionRecord<any, any, any>): void {
|
|
203
|
+
if (this.globalListener) {
|
|
204
|
+
// Call listener on next tick to avoid blocking task execution
|
|
205
|
+
process.nextTick(async () => {
|
|
206
|
+
try {
|
|
207
|
+
await this.globalListener!(record)
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// Log error but don't affect task execution
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.error('ExecutionRecord listener error:', error)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
184
217
|
constructor (fn: Func, conf: TaskConfig<B> = {
|
|
185
218
|
name: undefined,
|
|
186
219
|
description: undefined,
|
|
@@ -297,9 +330,13 @@ export const Task = class Task<
|
|
|
297
330
|
Plus all the boundary data
|
|
298
331
|
*/
|
|
299
332
|
emit (data: ExecutionRecord<Parameters<Func>[0], ReturnType<Func>, B>): void {
|
|
300
|
-
|
|
333
|
+
// Emit to instance listener
|
|
334
|
+
if (typeof this._listener !== 'undefined') {
|
|
335
|
+
this._listener(data)
|
|
336
|
+
}
|
|
301
337
|
|
|
302
|
-
|
|
338
|
+
// Emit to global listener (non-blocking)
|
|
339
|
+
Task.emitExecutionRecord(data)
|
|
303
340
|
}
|
|
304
341
|
|
|
305
342
|
getBoundaries (): WrappedBoundaries<B> {
|
|
@@ -355,6 +392,28 @@ export const Task = class Task<
|
|
|
355
392
|
this._boundaryMocks = {}
|
|
356
393
|
}
|
|
357
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Creates execution record boundaries that modify execution metadata and logging
|
|
397
|
+
* These boundaries are automatically injected into all task executions
|
|
398
|
+
*
|
|
399
|
+
* To add a new execution boundary:
|
|
400
|
+
* 1. Add the boundary function here
|
|
401
|
+
* 2. Update the ExecutionRecordBoundaries type to include the new boundary
|
|
402
|
+
* 3. That's it! The boundary will be available in all tasks automatically
|
|
403
|
+
*/
|
|
404
|
+
_createExecutionBoundaries(metadata: Record<string, string>): Record<string, WrappedBoundaryFunction> {
|
|
405
|
+
return {
|
|
406
|
+
// Allows setting metadata key-value pairs from within task execution
|
|
407
|
+
setMetadata: createBoundary(async (...args: unknown[]): Promise<void> => {
|
|
408
|
+
const [key, value] = args as [string, string]
|
|
409
|
+
metadata[key] = value
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
// Future execution boundaries can be added here:
|
|
413
|
+
// addMetrics: createBoundary(async (...args: unknown[]): Promise<void> => { ... }),
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
358
417
|
_createBounderies ({
|
|
359
418
|
definition,
|
|
360
419
|
baseData,
|
|
@@ -431,9 +490,16 @@ export const Task = class Task<
|
|
|
431
490
|
mode: this._mode
|
|
432
491
|
})
|
|
433
492
|
|
|
493
|
+
// Create and inject execution record boundaries (setMetadata, etc.)
|
|
494
|
+
const executionRecordBoundaries = this._createExecutionBoundaries(metadata)
|
|
495
|
+
const allBoundaries = {
|
|
496
|
+
...executionBoundaries,
|
|
497
|
+
...executionRecordBoundaries
|
|
498
|
+
}
|
|
499
|
+
|
|
434
500
|
// Start run for each boundary
|
|
435
|
-
for (const name in
|
|
436
|
-
const boundary =
|
|
501
|
+
for (const name in allBoundaries) {
|
|
502
|
+
const boundary = allBoundaries[name]
|
|
437
503
|
boundary.startRun()
|
|
438
504
|
}
|
|
439
505
|
|
|
@@ -470,7 +536,7 @@ export const Task = class Task<
|
|
|
470
536
|
// Execute the task function
|
|
471
537
|
output = await this._fn(
|
|
472
538
|
argv as Parameters<Func>[0],
|
|
473
|
-
|
|
539
|
+
allBoundaries as unknown as Parameters<Func>[1]
|
|
474
540
|
)
|
|
475
541
|
|
|
476
542
|
logItem.output = output
|
|
@@ -507,6 +573,9 @@ export const Task = class Task<
|
|
|
507
573
|
}
|
|
508
574
|
}
|
|
509
575
|
|
|
576
|
+
// Filter out setMetadata boundary calls from the logs (they should not appear in execution record)
|
|
577
|
+
// Note: setMetadata is not part of the original boundaries definition, so it won't be in boundariesRunLog anyway
|
|
578
|
+
|
|
510
579
|
// Set boundaries in log item before emitting
|
|
511
580
|
logItem.boundaries = boundariesRunLog
|
|
512
581
|
|
|
@@ -566,9 +635,18 @@ export const Task = class Task<
|
|
|
566
635
|
boundaryModes: config.boundaries
|
|
567
636
|
})
|
|
568
637
|
|
|
638
|
+
// Create and inject execution record boundaries (setMetadata, etc.)
|
|
639
|
+
// Clone the metadata to avoid mutating the original metadata
|
|
640
|
+
const replayMetadata = { ...(logItem.metadata || {}) }
|
|
641
|
+
const executionRecordBoundaries = this._createExecutionBoundaries(replayMetadata)
|
|
642
|
+
const allBoundaries = {
|
|
643
|
+
...executionBoundaries,
|
|
644
|
+
...executionRecordBoundaries
|
|
645
|
+
}
|
|
646
|
+
|
|
569
647
|
// Start run for each boundary
|
|
570
|
-
for (const name in
|
|
571
|
-
const boundary =
|
|
648
|
+
for (const name in allBoundaries) {
|
|
649
|
+
const boundary = allBoundaries[name]
|
|
572
650
|
boundary.startRun()
|
|
573
651
|
}
|
|
574
652
|
|
|
@@ -603,7 +681,7 @@ export const Task = class Task<
|
|
|
603
681
|
// Execute the task function with replay boundaries
|
|
604
682
|
output = await this._fn(
|
|
605
683
|
argv,
|
|
606
|
-
|
|
684
|
+
allBoundaries as unknown as Parameters<Func>[1]
|
|
607
685
|
)
|
|
608
686
|
|
|
609
687
|
logItem.output = output
|
|
@@ -632,9 +710,15 @@ export const Task = class Task<
|
|
|
632
710
|
}
|
|
633
711
|
}
|
|
634
712
|
|
|
713
|
+
// Filter out setMetadata boundary calls from the logs (they should not appear in execution record)
|
|
714
|
+
// Note: setMetadata is not part of the original boundaries definition, so it won't be in boundariesRunLog anyway
|
|
715
|
+
|
|
635
716
|
// Set boundaries in log item before emitting
|
|
636
717
|
logItem.boundaries = boundariesRunLog
|
|
637
718
|
|
|
719
|
+
// Update the log item metadata with changes made during replay
|
|
720
|
+
logItem.metadata = replayMetadata
|
|
721
|
+
|
|
638
722
|
// Emit the log item
|
|
639
723
|
this.emit(logItem)
|
|
640
724
|
|
|
@@ -684,6 +768,12 @@ export const Task = class Task<
|
|
|
684
768
|
// Call the task's safeRun method
|
|
685
769
|
const [outcome, error, log] = await this.safeRun(eventArgs)
|
|
686
770
|
|
|
771
|
+
// Extend log metadata with environment info
|
|
772
|
+
log.metadata = {
|
|
773
|
+
...log.metadata,
|
|
774
|
+
environment: 'hive-lambda'
|
|
775
|
+
}
|
|
776
|
+
|
|
687
777
|
// Send log to Hive if environment variables are present
|
|
688
778
|
await this._sendToHive(log)
|
|
689
779
|
|
|
@@ -823,7 +913,7 @@ export interface CreateTaskConfig<
|
|
|
823
913
|
description?: string
|
|
824
914
|
schema: S
|
|
825
915
|
boundaries: B
|
|
826
|
-
fn: (argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B>) => Promise<R>
|
|
916
|
+
fn: (argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B> & ExecutionRecordBoundaries) => Promise<R>
|
|
827
917
|
mode?: Mode
|
|
828
918
|
boundariesData?: BoundaryTapeData
|
|
829
919
|
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { createTask, Schema } from '../index'
|
|
2
|
+
|
|
3
|
+
describe('execution-record-boundaries', () => {
|
|
4
|
+
describe('setMetadata boundary', () => {
|
|
5
|
+
it('should add metadata to execution record when setMetadata is called', async () => {
|
|
6
|
+
// Create a schema
|
|
7
|
+
const schema = new Schema({
|
|
8
|
+
value: Schema.number()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
// Create a task that uses setMetadata
|
|
12
|
+
const task = createTask({
|
|
13
|
+
name: 'metadata-test-task',
|
|
14
|
+
schema,
|
|
15
|
+
boundaries: {
|
|
16
|
+
multiply: async (value: number) => value * 2
|
|
17
|
+
},
|
|
18
|
+
fn: async ({ value }, { multiply, setMetadata }) => {
|
|
19
|
+
await setMetadata('userId', '12345')
|
|
20
|
+
await setMetadata('executionType', 'background')
|
|
21
|
+
|
|
22
|
+
const result = await multiply(value)
|
|
23
|
+
return { result }
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Run the task with safeRun
|
|
28
|
+
const [result, error, record] = await task.safeRun({ value: 5 })
|
|
29
|
+
|
|
30
|
+
// Verify the execution was successful
|
|
31
|
+
expect(error).toBeNull()
|
|
32
|
+
expect(result).toEqual({ result: 10 })
|
|
33
|
+
|
|
34
|
+
// Verify the metadata is included in the record
|
|
35
|
+
expect(record.metadata).toEqual({
|
|
36
|
+
userId: '12345',
|
|
37
|
+
executionType: 'background'
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Verify the task name and other fields are correct
|
|
41
|
+
expect(record.taskName).toBe('metadata-test-task')
|
|
42
|
+
expect(record.input).toEqual({ value: 5 })
|
|
43
|
+
expect(record.output).toEqual({ result: 10 })
|
|
44
|
+
expect(record.type).toBe('success')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should not include setMetadata calls in boundary logs', async () => {
|
|
48
|
+
// Create a schema
|
|
49
|
+
const schema = new Schema({
|
|
50
|
+
value: Schema.number()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Create a task that uses setMetadata and other boundaries
|
|
54
|
+
const task = createTask({
|
|
55
|
+
name: 'boundary-log-test',
|
|
56
|
+
schema,
|
|
57
|
+
boundaries: {
|
|
58
|
+
multiply: async (value: number) => value * 2,
|
|
59
|
+
fetchData: async (data: string) => `fetched-${data}`
|
|
60
|
+
},
|
|
61
|
+
fn: async ({ value }, { multiply, fetchData, setMetadata }) => {
|
|
62
|
+
await setMetadata('step', 'start')
|
|
63
|
+
|
|
64
|
+
const multiplied = await multiply(value)
|
|
65
|
+
await setMetadata('step', 'multiplied')
|
|
66
|
+
|
|
67
|
+
const fetched = await fetchData('test')
|
|
68
|
+
await setMetadata('step', 'completed')
|
|
69
|
+
|
|
70
|
+
return { multiplied, fetched }
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Run the task with safeRun
|
|
75
|
+
const [result, error, record] = await task.safeRun({ value: 3 })
|
|
76
|
+
|
|
77
|
+
// Verify the execution was successful
|
|
78
|
+
expect(error).toBeNull()
|
|
79
|
+
expect(result).toEqual({ multiplied: 6, fetched: 'fetched-test' })
|
|
80
|
+
|
|
81
|
+
// Verify the metadata is updated to the final value
|
|
82
|
+
expect(record.metadata).toEqual({
|
|
83
|
+
step: 'completed'
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Verify that only the actual boundaries appear in the logs
|
|
87
|
+
expect(record.boundaries).toHaveProperty('multiply')
|
|
88
|
+
expect(record.boundaries).toHaveProperty('fetchData')
|
|
89
|
+
expect(record.boundaries).not.toHaveProperty('setMetadata')
|
|
90
|
+
|
|
91
|
+
// Verify the boundary logs contain the expected calls
|
|
92
|
+
expect(record.boundaries.multiply).toHaveLength(1)
|
|
93
|
+
expect(record.boundaries.multiply[0]).toEqual({
|
|
94
|
+
input: [3],
|
|
95
|
+
output: 6
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
expect(record.boundaries.fetchData).toHaveLength(1)
|
|
99
|
+
expect(record.boundaries.fetchData[0]).toEqual({
|
|
100
|
+
input: ['test'],
|
|
101
|
+
output: 'fetched-test'
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should preserve metadata in error scenarios', async () => {
|
|
106
|
+
// Create a schema
|
|
107
|
+
const schema = new Schema({
|
|
108
|
+
value: Schema.number()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Create a task that sets metadata before throwing an error
|
|
112
|
+
const task = createTask({
|
|
113
|
+
name: 'error-metadata-test',
|
|
114
|
+
schema,
|
|
115
|
+
boundaries: {},
|
|
116
|
+
fn: async ({ value }, { setMetadata }) => {
|
|
117
|
+
await setMetadata('errorContext', 'validation')
|
|
118
|
+
await setMetadata('userId', '67890')
|
|
119
|
+
|
|
120
|
+
if (value < 0) {
|
|
121
|
+
throw new Error('Value cannot be negative')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { result: value }
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Run the task with a value that will cause an error
|
|
129
|
+
const [result, error, record] = await task.safeRun({ value: -1 })
|
|
130
|
+
|
|
131
|
+
// Verify the execution failed as expected
|
|
132
|
+
expect(result).toBeNull()
|
|
133
|
+
expect(error).not.toBeNull()
|
|
134
|
+
expect(error?.message).toBe('Value cannot be negative')
|
|
135
|
+
|
|
136
|
+
// Verify the metadata is preserved in the error record
|
|
137
|
+
expect(record.metadata).toEqual({
|
|
138
|
+
errorContext: 'validation',
|
|
139
|
+
userId: '67890'
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Verify other record fields
|
|
143
|
+
expect(record.taskName).toBe('error-metadata-test')
|
|
144
|
+
expect(record.input).toEqual({ value: -1 })
|
|
145
|
+
expect(record.error).toBe('Value cannot be negative')
|
|
146
|
+
expect(record.type).toBe('error')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should preserve existing metadata when adding new metadata', async () => {
|
|
150
|
+
// Create a schema
|
|
151
|
+
const schema = new Schema({
|
|
152
|
+
value: Schema.number()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// Create a task that uses setMetadata
|
|
156
|
+
const task = createTask({
|
|
157
|
+
name: 'preserve-metadata-test',
|
|
158
|
+
schema,
|
|
159
|
+
boundaries: {},
|
|
160
|
+
fn: async ({ value }, { setMetadata }) => {
|
|
161
|
+
await setMetadata('step1', 'completed')
|
|
162
|
+
await setMetadata('step2', 'in-progress')
|
|
163
|
+
await setMetadata('step1', 'updated') // Overwrite step1
|
|
164
|
+
|
|
165
|
+
return { result: value * 2 }
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Run the task with initial metadata context
|
|
170
|
+
const [result, error, record] = await task.safeRun({ value: 4 })
|
|
171
|
+
|
|
172
|
+
// Verify the execution was successful
|
|
173
|
+
expect(error).toBeNull()
|
|
174
|
+
expect(result).toEqual({ result: 8 })
|
|
175
|
+
|
|
176
|
+
// Verify the metadata contains the final values
|
|
177
|
+
expect(record.metadata).toEqual({
|
|
178
|
+
step1: 'updated',
|
|
179
|
+
step2: 'in-progress'
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should work with setMetadata in safeReplay', async () => {
|
|
184
|
+
// Create a schema
|
|
185
|
+
const schema = new Schema({
|
|
186
|
+
value: Schema.number()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Use a counter to ensure different values between original and replay
|
|
190
|
+
let executionCounter = 0
|
|
191
|
+
|
|
192
|
+
// Create a task with boundaries and metadata
|
|
193
|
+
const task = createTask({
|
|
194
|
+
name: 'replay-metadata-test',
|
|
195
|
+
schema,
|
|
196
|
+
boundaries: {
|
|
197
|
+
fetchData: async (value: number) => value * 3
|
|
198
|
+
},
|
|
199
|
+
fn: async ({ value }, { fetchData, setMetadata }) => {
|
|
200
|
+
executionCounter++
|
|
201
|
+
await setMetadata('replayTest', 'true')
|
|
202
|
+
await setMetadata('executionNumber', executionCounter.toString())
|
|
203
|
+
|
|
204
|
+
const result = await fetchData(value)
|
|
205
|
+
return { result }
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// First, run the task normally to get an execution record
|
|
210
|
+
const [, , originalRecord] = await task.safeRun({ value: 2 })
|
|
211
|
+
|
|
212
|
+
// Verify original metadata was set
|
|
213
|
+
expect(originalRecord.metadata).toHaveProperty('replayTest', 'true')
|
|
214
|
+
expect(originalRecord.metadata).toHaveProperty('executionNumber', '1')
|
|
215
|
+
|
|
216
|
+
// Now replay the task
|
|
217
|
+
const [replayResult, replayError, replayRecord] = await task.safeReplay(
|
|
218
|
+
originalRecord,
|
|
219
|
+
{ boundaries: { fetchData: 'replay' } }
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
// Verify the replay was successful
|
|
223
|
+
expect(replayError).toBeNull()
|
|
224
|
+
expect(replayResult).toEqual({ result: 6 })
|
|
225
|
+
|
|
226
|
+
// Verify that the replay has its own metadata (setMetadata was called again)
|
|
227
|
+
expect(replayRecord.metadata).toHaveProperty('replayTest', 'true')
|
|
228
|
+
expect(replayRecord.metadata).toHaveProperty('executionNumber', '2')
|
|
229
|
+
|
|
230
|
+
// The execution numbers should be different since setMetadata was called again
|
|
231
|
+
expect(replayRecord.metadata?.executionNumber).not.toBe(originalRecord.metadata?.executionNumber)
|
|
232
|
+
expect(originalRecord.metadata?.executionNumber).toBe('1')
|
|
233
|
+
expect(replayRecord.metadata?.executionNumber).toBe('2')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should handle empty metadata gracefully', async () => {
|
|
237
|
+
// Create a schema
|
|
238
|
+
const schema = new Schema({
|
|
239
|
+
value: Schema.number()
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// Create a task that doesn't use setMetadata
|
|
243
|
+
const task = createTask({
|
|
244
|
+
name: 'no-metadata-test',
|
|
245
|
+
schema,
|
|
246
|
+
boundaries: {
|
|
247
|
+
multiply: async (value: number) => value * 2
|
|
248
|
+
},
|
|
249
|
+
fn: async ({ value }, { multiply }) => {
|
|
250
|
+
const result = await multiply(value)
|
|
251
|
+
return { result }
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// Run the task without using setMetadata
|
|
256
|
+
const [result, error, record] = await task.safeRun({ value: 7 })
|
|
257
|
+
|
|
258
|
+
// Verify the execution was successful
|
|
259
|
+
expect(error).toBeNull()
|
|
260
|
+
expect(result).toEqual({ result: 14 })
|
|
261
|
+
|
|
262
|
+
// Verify the metadata is empty but defined
|
|
263
|
+
expect(record.metadata).toEqual({})
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
})
|