@forgehive/task 0.2.0 → 0.2.2
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 +20 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -6
- package/dist/index.js.map +1 -1
- package/dist/test/add-listener-with-boundaries.test.js +175 -200
- package/dist/test/add-listener-with-boundaries.test.js.map +1 -1
- package/dist/test/add-listener.test.js +80 -52
- package/dist/test/add-listener.test.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/task-with-boundaries.test.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +78 -28
- package/src/test/add-listener-with-boundaries.test.ts +186 -242
- package/src/test/add-listener.test.ts +90 -64
- package/src/test/execution-record-boundaries.test.ts +266 -0
- package/src/test/task-with-boundaries.test.ts +3 -3
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
import { Task, type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
return _argv
|
|
1
|
+
import { Task, type ExecutionRecord } from '../index'
|
|
2
|
+
|
|
3
|
+
describe('Add listener', () => {
|
|
4
|
+
it('Should add a listener to the task', async () => {
|
|
5
|
+
const tape: ExecutionRecord[] = []
|
|
6
|
+
const task = new Task(async (argv: { value: number }) => {
|
|
7
|
+
return { value: argv.value }
|
|
9
8
|
})
|
|
10
9
|
|
|
11
10
|
task.addListener((record) => {
|
|
12
11
|
tape.push(record)
|
|
13
12
|
})
|
|
14
13
|
|
|
15
|
-
await task.run({ value:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
await task.run({ value: 1 })
|
|
15
|
+
expect(tape).toEqual([{
|
|
16
|
+
input: { value: 1 },
|
|
17
|
+
output: { value: 1 },
|
|
18
|
+
boundaries: {},
|
|
19
|
+
taskName: undefined,
|
|
20
|
+
metadata: {},
|
|
21
|
+
type: 'success'
|
|
22
|
+
}])
|
|
20
23
|
})
|
|
21
24
|
|
|
22
|
-
it('Should
|
|
23
|
-
const tape:
|
|
24
|
-
const task = new Task(
|
|
25
|
-
throw new Error('
|
|
25
|
+
it('Should add a listener to the task and catch error', async () => {
|
|
26
|
+
const tape: ExecutionRecord[] = []
|
|
27
|
+
const task = new Task(async (_argv: { value: number }) => {
|
|
28
|
+
throw new Error('Test error')
|
|
26
29
|
})
|
|
27
30
|
|
|
28
31
|
task.addListener((record) => {
|
|
@@ -30,83 +33,106 @@ describe('Listener tests', () => {
|
|
|
30
33
|
})
|
|
31
34
|
|
|
32
35
|
try {
|
|
33
|
-
await task.run({ value:
|
|
34
|
-
} catch (
|
|
35
|
-
//
|
|
36
|
+
await task.run({ value: 1 })
|
|
37
|
+
} catch (error) {
|
|
38
|
+
// Expected error
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
expect(tape
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
expect(tape).toEqual([{
|
|
42
|
+
input: { value: 1 },
|
|
43
|
+
error: 'Test error',
|
|
44
|
+
boundaries: {},
|
|
45
|
+
taskName: undefined,
|
|
46
|
+
metadata: {},
|
|
47
|
+
type: 'error'
|
|
48
|
+
}])
|
|
41
49
|
})
|
|
42
50
|
|
|
43
|
-
it('Should
|
|
44
|
-
const tape:
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return _argv
|
|
51
|
-
}, {
|
|
52
|
-
schema
|
|
51
|
+
it('Should add a listener to the task and catch error with null input', async () => {
|
|
52
|
+
const tape: ExecutionRecord[] = []
|
|
53
|
+
const task = new Task(async (argv: { value: number | null }) => {
|
|
54
|
+
if (argv.value === null) {
|
|
55
|
+
throw new Error('Value is null')
|
|
56
|
+
}
|
|
57
|
+
return { value: argv.value }
|
|
53
58
|
})
|
|
54
59
|
|
|
55
|
-
task.addListener
|
|
60
|
+
task.addListener((record) => {
|
|
56
61
|
tape.push(record)
|
|
57
62
|
})
|
|
58
63
|
|
|
59
64
|
try {
|
|
60
|
-
await task.run({ value:
|
|
61
|
-
} catch (
|
|
62
|
-
//
|
|
65
|
+
await task.run({ value: null })
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Expected error
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
expect(tape
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
expect(tape).toEqual([{
|
|
71
|
+
input: { value: null },
|
|
72
|
+
error: 'Value is null',
|
|
73
|
+
boundaries: {},
|
|
74
|
+
taskName: undefined,
|
|
75
|
+
metadata: {},
|
|
76
|
+
type: 'error'
|
|
77
|
+
}])
|
|
70
78
|
})
|
|
71
79
|
|
|
72
|
-
it('Should
|
|
73
|
-
const tape:
|
|
74
|
-
const task = new Task(
|
|
75
|
-
return
|
|
80
|
+
it('Should add a listener to the task and call twice', async () => {
|
|
81
|
+
const tape: ExecutionRecord[] = []
|
|
82
|
+
const task = new Task(async (argv: { value: number }) => {
|
|
83
|
+
return { value: argv.value }
|
|
76
84
|
})
|
|
77
85
|
|
|
78
86
|
task.addListener((record) => {
|
|
79
87
|
tape.push(record)
|
|
80
88
|
})
|
|
81
89
|
|
|
82
|
-
await task.run({ value:
|
|
83
|
-
await task.run({ value:
|
|
84
|
-
|
|
85
|
-
expect(tape
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
await task.run({ value: 1 })
|
|
91
|
+
await task.run({ value: 2 })
|
|
92
|
+
|
|
93
|
+
expect(tape).toEqual([
|
|
94
|
+
{
|
|
95
|
+
input: { value: 1 },
|
|
96
|
+
output: { value: 1 },
|
|
97
|
+
boundaries: {},
|
|
98
|
+
taskName: undefined,
|
|
99
|
+
metadata: {},
|
|
100
|
+
type: 'success'
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
input: { value: 2 },
|
|
104
|
+
output: { value: 2 },
|
|
105
|
+
boundaries: {},
|
|
106
|
+
taskName: undefined,
|
|
107
|
+
metadata: {},
|
|
108
|
+
type: 'success'
|
|
109
|
+
}
|
|
110
|
+
])
|
|
91
111
|
})
|
|
92
112
|
|
|
93
|
-
it('Should
|
|
94
|
-
const tape:
|
|
95
|
-
const task = new Task(
|
|
96
|
-
return
|
|
113
|
+
it('Should add a listener and remove it', async () => {
|
|
114
|
+
const tape: ExecutionRecord[] = []
|
|
115
|
+
const task = new Task(async (argv: { value: number }) => {
|
|
116
|
+
return { value: argv.value }
|
|
97
117
|
})
|
|
98
118
|
|
|
99
119
|
task.addListener((record) => {
|
|
100
120
|
tape.push(record)
|
|
101
121
|
})
|
|
102
122
|
|
|
103
|
-
await task.run({ value:
|
|
123
|
+
await task.run({ value: 1 })
|
|
104
124
|
|
|
105
125
|
task.removeListener()
|
|
106
|
-
await task.run({ value: 6 })
|
|
107
126
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
expect(tape
|
|
127
|
+
await task.run({ value: 2 })
|
|
128
|
+
|
|
129
|
+
expect(tape).toEqual([{
|
|
130
|
+
input: { value: 1 },
|
|
131
|
+
output: { value: 1 },
|
|
132
|
+
boundaries: {},
|
|
133
|
+
taskName: undefined,
|
|
134
|
+
metadata: {},
|
|
135
|
+
type: 'success'
|
|
136
|
+
}])
|
|
111
137
|
})
|
|
112
138
|
})
|
|
@@ -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
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createTask, Schema,
|
|
1
|
+
import { createTask, Schema, ExecutionRecord, BoundaryTapeData } from '../index'
|
|
2
2
|
|
|
3
3
|
// Need to add proxy cache mode to the boundaries
|
|
4
4
|
describe('Boundaries tasks tests', () => {
|
|
@@ -148,7 +148,7 @@ describe('Boundaries tasks tests', () => {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// Use the correct type definition for records
|
|
151
|
-
const records:
|
|
151
|
+
const records: ExecutionRecord[] = []
|
|
152
152
|
|
|
153
153
|
// Create a schema for the task that accepts a number
|
|
154
154
|
const schema = new Schema({
|
|
@@ -192,7 +192,7 @@ describe('Boundaries tasks tests', () => {
|
|
|
192
192
|
expect(records.length).toBe(3)
|
|
193
193
|
|
|
194
194
|
// Sort records by input value for consistent testing
|
|
195
|
-
const sortedRecords = [...records].sort((a, b) => a.input.value - b.input.value)
|
|
195
|
+
const sortedRecords = [...records].sort((a, b) => (a.input as {value: number}).value - (b.input as {value: number}).value)
|
|
196
196
|
|
|
197
197
|
// Check record for first task (value: 2)
|
|
198
198
|
expect(sortedRecords[0].input).toEqual({ value: 2 })
|