@forgehive/task 0.1.13 → 0.2.1

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 (32) hide show
  1. package/dist/index.d.ts +37 -24
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +58 -13
  4. package/dist/index.js.map +1 -1
  5. package/dist/test/add-listener-with-boundaries.test.js +186 -181
  6. package/dist/test/add-listener-with-boundaries.test.js.map +1 -1
  7. package/dist/test/add-listener.test.js +80 -52
  8. package/dist/test/add-listener.test.js.map +1 -1
  9. package/dist/test/safe-replay-complex-boundary.test.js +36 -27
  10. package/dist/test/safe-replay-complex-boundary.test.js.map +1 -1
  11. package/dist/test/safe-replay.test.js +23 -14
  12. package/dist/test/safe-replay.test.js.map +1 -1
  13. package/dist/test/safe-run.test.js +41 -16
  14. package/dist/test/safe-run.test.js.map +1 -1
  15. package/dist/test/task-boundary-mocking.test.js +17 -7
  16. package/dist/test/task-boundary-mocking.test.js.map +1 -1
  17. package/dist/test/task-execution-log.test.d.ts +2 -0
  18. package/dist/test/task-execution-log.test.d.ts.map +1 -0
  19. package/dist/test/task-execution-log.test.js +207 -0
  20. package/dist/test/task-execution-log.test.js.map +1 -0
  21. package/dist/test/task-with-boundaries.test.js +56 -24
  22. package/dist/test/task-with-boundaries.test.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/index.ts +109 -40
  25. package/src/test/add-listener-with-boundaries.test.ts +202 -252
  26. package/src/test/add-listener.test.ts +90 -64
  27. package/src/test/safe-replay-complex-boundary.test.ts +18 -10
  28. package/src/test/safe-replay.test.ts +21 -12
  29. package/src/test/safe-run.test.ts +20 -15
  30. package/src/test/task-boundary-mocking.test.ts +8 -6
  31. package/src/test/task-execution-log.test.ts +246 -0
  32. package/src/test/task-with-boundaries.test.ts +45 -44
@@ -1,28 +1,31 @@
1
- import { Task, type TaskRecord } from '../index'
2
- import { Schema, type InferSchema } from '@forgehive/schema'
3
-
4
- describe('Listener tests', () => {
5
- it('Should record one item', async () => {
6
- const tape: TaskRecord<{ value: number }, { value: number }>[] = []
7
- const task = new Task(function (_argv: { value: number }) {
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: 5 })
16
-
17
- expect(tape.length).toBe(1)
18
- expect(tape[0].input).toEqual({ value: 5 })
19
- expect(tape[0].output).toEqual({ value: 5 })
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 record execution error', async () => {
23
- const tape: TaskRecord<{ value: number }, never>[] = []
24
- const task = new Task(function (_argv: { value: number }) {
25
- throw new Error('This should happen')
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: 5 })
34
- } catch (e) {
35
- // Error is expected
36
+ await task.run({ value: 1 })
37
+ } catch (error) {
38
+ // Expected error
36
39
  }
37
40
 
38
- expect(tape.length).toBe(1)
39
- expect(tape[0].input).toEqual({ value: 5 })
40
- expect(tape[0].error).toBe('This should happen')
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 record validation error', async () => {
44
- const tape: TaskRecord<{ value: number | null }, { value: number }>[] = []
45
- const schema = new Schema({
46
- value: Schema.number().min(5)
47
- })
48
-
49
- const task = new Task(function (_argv: InferSchema<typeof schema>) {
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<{ value: number | null }, { value: number }>((record) => {
60
+ task.addListener((record) => {
56
61
  tape.push(record)
57
62
  })
58
63
 
59
64
  try {
60
- await task.run({ value: 3 })
61
- } catch (e) {
62
- // Error is expected
65
+ await task.run({ value: null })
66
+ } catch (error) {
67
+ // Expected error
63
68
  }
64
69
 
65
- expect(tape.length).toBe(1)
66
- expect(tape[0].input).toEqual({ value: 3 })
67
- expect(tape[0].error).toContain('Invalid input on:')
68
- expect(tape[0].error).toContain('value:')
69
- expect(tape[0].error).toContain('Number must be greater than or equal to 5')
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 record multiple records', async () => {
73
- const tape: TaskRecord<{ value: number }, { value: number }>[] = []
74
- const task = new Task(function (_argv: { value: number }) {
75
- return _argv
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: 5 })
83
- await task.run({ value: 6 })
84
-
85
- expect(tape.length).toBe(2)
86
- expect(tape[0].input).toEqual({ value: 5 })
87
- expect(tape[0].output).toEqual({ value: 5 })
88
-
89
- expect(tape[1].input).toEqual({ value: 6 })
90
- expect(tape[1].output).toEqual({ value: 6 })
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 stop recording', async () => {
94
- const tape: TaskRecord<{ value: number }, { value: number }>[] = []
95
- const task = new Task(function (_argv: { value: number }) {
96
- return _argv
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: 5 })
123
+ await task.run({ value: 1 })
104
124
 
105
125
  task.removeListener()
106
- await task.run({ value: 6 })
107
126
 
108
- expect(tape.length).toBe(1)
109
- expect(tape[0].input).toEqual({ value: 5 })
110
- expect(tape[0].output).toEqual({ value: 5 })
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
  })
@@ -1,7 +1,14 @@
1
1
  import { Schema } from '@forgehive/schema'
2
- import { createTask, ExecutionRecord } from '../index'
2
+ import { createTask, ExecutionRecord, getExecutionRecordType } from '../index'
3
3
 
4
4
  describe('Complex boundary replay tests', () => {
5
+ // Helper function to create ExecutionRecord with computed type
6
+ function createExecutionRecord<T, U>(partial: Omit<ExecutionRecord<T, U>, 'type'>): ExecutionRecord<T, U> {
7
+ return {
8
+ ...partial,
9
+ type: getExecutionRecordType(partial)
10
+ }
11
+ }
5
12
  // Define types for our portfolio data
6
13
  type Stock = {
7
14
  ticker: string;
@@ -81,10 +88,11 @@ describe('Complex boundary replay tests', () => {
81
88
  })
82
89
 
83
90
  // Create the portfolio value calculation task
84
- calculatePortfolioValue = createTask(
91
+ calculatePortfolioValue = createTask({
92
+ name: 'calculatePortfolioValue',
85
93
  schema,
86
94
  boundaries,
87
- async ({ userId }, { fetchPortfolio, fetchPrice }) => {
95
+ fn: async ({ userId }, { fetchPortfolio, fetchPrice }) => {
88
96
  // First fetch the portfolio for the user
89
97
  const portfolio = await fetchPortfolio(userId)
90
98
 
@@ -114,7 +122,7 @@ describe('Complex boundary replay tests', () => {
114
122
  stocks: stocksWithValue
115
123
  }
116
124
  }
117
- )
125
+ })
118
126
  })
119
127
 
120
128
  it('Should calculate portfolio value using multiple boundaries', async () => {
@@ -158,7 +166,7 @@ describe('Complex boundary replay tests', () => {
158
166
 
159
167
  it('Should replay portfolio calculation from an execution log', async () => {
160
168
  // First create a portfolio value calculation execution log
161
- const executionLog: ExecutionRecord = {
169
+ const executionLog: ExecutionRecord = createExecutionRecord({
162
170
  input: { userId: 'user1' },
163
171
  output: {
164
172
  id: 'portfolio1',
@@ -200,7 +208,7 @@ describe('Complex boundary replay tests', () => {
200
208
  }
201
209
  ]
202
210
  }
203
- }
211
+ })
204
212
 
205
213
  // Use replay mode for all boundaries
206
214
  const [replayResult, replayError, replayLog] = await calculatePortfolioValue.safeReplay(
@@ -235,7 +243,7 @@ describe('Complex boundary replay tests', () => {
235
243
 
236
244
  it('Should handle errors during replay', async () => {
237
245
  // Create an execution log with an error in one of the price fetches
238
- const executionLog: ExecutionRecord = {
246
+ const executionLog: ExecutionRecord = createExecutionRecord({
239
247
  input: { userId: 'user1' },
240
248
  error: 'Price data not available for ticker: GOOGL',
241
249
  boundaries: {
@@ -268,7 +276,7 @@ describe('Complex boundary replay tests', () => {
268
276
  }
269
277
  ]
270
278
  }
271
- }
279
+ })
272
280
 
273
281
  // Use replay mode for all boundaries
274
282
  const [replayResult, replayError, replayLog] = await calculatePortfolioValue.safeReplay(
@@ -303,7 +311,7 @@ describe('Complex boundary replay tests', () => {
303
311
  priceData['AAPL'] = 195.00 // Different from replay data
304
312
 
305
313
  // Create an execution log with historical data
306
- const executionLog: ExecutionRecord = {
314
+ const executionLog: ExecutionRecord = createExecutionRecord({
307
315
  input: { userId: 'user1' },
308
316
  output: {
309
317
  id: 'portfolio1',
@@ -345,7 +353,7 @@ describe('Complex boundary replay tests', () => {
345
353
  }
346
354
  ]
347
355
  }
348
- }
356
+ })
349
357
 
350
358
  // Use replay mode for portfolio but proxy mode for prices
351
359
  const [replayResult, replayError, replayLog] = await calculatePortfolioValue.safeReplay(
@@ -1,7 +1,15 @@
1
1
  import { Schema } from '@forgehive/schema'
2
- import { createTask, ExecutionRecord } from '../index'
2
+ import { createTask, ExecutionRecord, getExecutionRecordType } from '../index'
3
3
 
4
4
  describe('safeReplay functionality tests', () => {
5
+ // Helper function to create ExecutionRecord with computed type
6
+ function createExecutionRecord<T, U>(partial: Omit<ExecutionRecord<T, U>, 'type'>): ExecutionRecord<T, U> {
7
+ return {
8
+ ...partial,
9
+ type: getExecutionRecordType(partial)
10
+ }
11
+ }
12
+
5
13
  // Common variables
6
14
  let prices: Record<string, number>
7
15
  let boundaries: {
@@ -38,22 +46,23 @@ describe('safeReplay functionality tests', () => {
38
46
  }
39
47
 
40
48
  // Create the task using createTask
41
- getTickerPrice = createTask(
49
+ getTickerPrice = createTask({
50
+ name: 'getTickerPrice',
42
51
  schema,
43
52
  boundaries,
44
- async ({ ticker }, { fetchData }) => {
53
+ fn: async ({ ticker }, { fetchData }) => {
45
54
  const price = await fetchData(ticker)
46
55
  return {
47
56
  ticker,
48
57
  price
49
58
  }
50
59
  }
51
- )
60
+ })
52
61
  })
53
62
 
54
63
  it('Should replay a previous execution using the execution log and replay the fetchData boundary', async () => {
55
64
  // Create a manual execution log
56
- const executionLog: ExecutionRecord = {
65
+ const executionLog: ExecutionRecord = createExecutionRecord({
57
66
  input: { ticker: 'AAPL' },
58
67
  output: {
59
68
  ticker: 'AAPL',
@@ -67,7 +76,7 @@ describe('safeReplay functionality tests', () => {
67
76
  }
68
77
  ]
69
78
  }
70
- }
79
+ })
71
80
 
72
81
  // No safeReplay method yet, this will be implemented later
73
82
  // This will be our test for that functionality
@@ -101,7 +110,7 @@ describe('safeReplay functionality tests', () => {
101
110
 
102
111
  it('Should execute with mixed boundaries modes', async () => {
103
112
  // Create a manual execution log for testing
104
- const executionLog: ExecutionRecord = {
113
+ const executionLog: ExecutionRecord = createExecutionRecord({
105
114
  input: { ticker: 'AAPL' },
106
115
  output: {
107
116
  ticker: 'AAPL',
@@ -115,7 +124,7 @@ describe('safeReplay functionality tests', () => {
115
124
  }
116
125
  ]
117
126
  }
118
- }
127
+ })
119
128
 
120
129
  // Use mixed mode - replay for fetchData but execute logAccess
121
130
  const [replayResult, replayError, replayLog] = await getTickerPrice.safeReplay(
@@ -153,7 +162,7 @@ describe('safeReplay functionality tests', () => {
153
162
 
154
163
  it('Should properly handle errors in boundary replay mode', async () => {
155
164
  // Create a manual execution log with an error in the boundary
156
- const executionLog: ExecutionRecord = {
165
+ const executionLog: ExecutionRecord = createExecutionRecord({
157
166
  input: { ticker: 'AAPL' },
158
167
  output: null,
159
168
  error: 'API error: Rate limit exceeded',
@@ -165,7 +174,7 @@ describe('safeReplay functionality tests', () => {
165
174
  }
166
175
  ]
167
176
  }
168
- }
177
+ })
169
178
 
170
179
  // Use replay mode for fetchData
171
180
  const [replayResult, replayError, replayLog] = await getTickerPrice.safeReplay(
@@ -189,7 +198,7 @@ describe('safeReplay functionality tests', () => {
189
198
 
190
199
  it('Should handle boundaries with both output and error as null', async () => {
191
200
  // Create a manual execution log with null output and error in the boundary
192
- const executionLog: ExecutionRecord = {
201
+ const executionLog: ExecutionRecord = createExecutionRecord({
193
202
  input: { ticker: 'AAPL' },
194
203
  output: { ticker: 'AAPL', price: 160.23 },
195
204
  boundaries: {
@@ -201,7 +210,7 @@ describe('safeReplay functionality tests', () => {
201
210
  }
202
211
  ]
203
212
  }
204
- }
213
+ })
205
214
 
206
215
  // Use replay mode for fetchData
207
216
  const [replayResult, replayError, replayLog] = await getTickerPrice.safeReplay(
@@ -15,14 +15,15 @@ describe('Task safeRun tests', () => {
15
15
  }
16
16
 
17
17
  // Create the task
18
- const successTask = createTask(
18
+ const successTask = createTask({
19
+ name: 'successTask',
19
20
  schema,
20
21
  boundaries,
21
- async function ({ value }, { fetchData }) {
22
+ fn: async function ({ value }, { fetchData }) {
22
23
  const result = await fetchData(value)
23
24
  return { result, success: true }
24
25
  }
25
- )
26
+ })
26
27
 
27
28
  // Call safeRun with valid input
28
29
  const [result, error, record] = await successTask.safeRun({ value: 5 })
@@ -58,14 +59,15 @@ describe('Task safeRun tests', () => {
58
59
  }
59
60
 
60
61
  // Create the task
61
- const errorTask = createTask(
62
+ const errorTask = createTask({
63
+ name: 'errorTask',
62
64
  schema,
63
65
  boundaries,
64
- async function ({ value }, { fetchData }) {
66
+ fn: async function ({ value }, { fetchData }) {
65
67
  const result = await fetchData(value)
66
68
  return { result, success: true }
67
69
  }
68
- )
70
+ })
69
71
 
70
72
  // Call safeRun with problematic input that will cause an error
71
73
  const [result, error, record] = await errorTask.safeRun({ value: -5 })
@@ -101,14 +103,15 @@ describe('Task safeRun tests', () => {
101
103
  }
102
104
 
103
105
  // Create the task
104
- const validationTask = createTask(
106
+ const validationTask = createTask({
107
+ name: 'validationTask',
105
108
  schema,
106
109
  boundaries,
107
- async function ({ value }, { fetchData }) {
110
+ fn: async function ({ value }, { fetchData }) {
108
111
  const result = await fetchData(value)
109
112
  return { result, success: true }
110
113
  }
111
- )
114
+ })
112
115
 
113
116
  // Call safeRun with invalid input that will fail schema validation
114
117
  const [result, error, record] = await validationTask.safeRun({ value: 0 })
@@ -142,14 +145,15 @@ describe('Task safeRun tests', () => {
142
145
  }
143
146
 
144
147
  // Create the task
145
- const listenerTask = createTask(
148
+ const listenerTask = createTask({
149
+ name: 'listenerTask',
146
150
  schema,
147
151
  boundaries,
148
- async function ({ value }, { fetchData }) {
152
+ fn: async function ({ value }, { fetchData }) {
149
153
  const result = await fetchData(value)
150
154
  return result
151
155
  }
152
- )
156
+ })
153
157
 
154
158
  // Create a mock listener
155
159
  const originalListener = jest.fn()
@@ -206,15 +210,16 @@ describe('Task safeRun tests', () => {
206
210
  }
207
211
 
208
212
  // Create a task that uses multiple boundaries
209
- const multiBoundaryTask = createTask(
213
+ const multiBoundaryTask = createTask({
214
+ name: 'multiBoundaryTask',
210
215
  schema,
211
216
  boundaries,
212
- async function ({ values }, { doubleValue, sumValues }) {
217
+ fn: async function ({ values }, { doubleValue, sumValues }) {
213
218
  const doubled = await Promise.all(values.map(value => doubleValue(value)))
214
219
  const total = await sumValues(doubled)
215
220
  return { doubled, total }
216
221
  }
217
- )
222
+ })
218
223
 
219
224
  // Call safeRun
220
225
  const [result, error, record] = await multiBoundaryTask.safeRun({ values: [1, 2, 3] })
@@ -17,14 +17,15 @@ describe('Task boundary mocking', () => {
17
17
  }
18
18
 
19
19
  // Create the task using createTask
20
- const multiplyTask = createTask(
20
+ const multiplyTask = createTask({
21
+ name: 'multiplyTask',
21
22
  schema,
22
23
  boundaries,
23
- async function ({ value }, { fetchExternalData }) {
24
+ fn: async function ({ value }, { fetchExternalData }) {
24
25
  const result = value * await fetchExternalData(value)
25
26
  return result
26
27
  }
27
- )
28
+ })
28
29
 
29
30
  // Create mock for fetchExternalData boundary that returns a specific value
30
31
  const mockFetchData = jest.fn().mockResolvedValue(5)
@@ -71,15 +72,16 @@ describe('Task boundary mocking', () => {
71
72
  }
72
73
 
73
74
  // Create the task
74
- const calculateTask = createTask(
75
+ const calculateTask = createTask({
76
+ name: 'calculateTask',
75
77
  schema,
76
78
  boundaries,
77
- async function ({ value }, { doubleValue, tripleValue }) {
79
+ fn: async function ({ value }, { doubleValue, tripleValue }) {
78
80
  const doubled = await doubleValue(value)
79
81
  const tripled = await tripleValue(value)
80
82
  return doubled + tripled
81
83
  }
82
- )
84
+ })
83
85
 
84
86
  // Create wrapped mock functions
85
87
  const mockDoubleValue = jest.fn().mockResolvedValue(10)