@forgehive/record-tape 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.
@@ -0,0 +1,150 @@
1
+ import { RecordTape } from '../index'
2
+
3
+ describe('RecordTape Data Methods', () => {
4
+ let tape: RecordTape
5
+
6
+ beforeEach(() => {
7
+ tape = new RecordTape()
8
+ })
9
+
10
+ describe('getLength', () => {
11
+ test('should return 0 for empty tape', () => {
12
+ expect(tape.getLength()).toBe(0)
13
+ })
14
+
15
+ test('should return correct length after adding records', () => {
16
+ const record1 = {
17
+ input: { userId: 1 },
18
+ output: { name: 'John' },
19
+ taskName: 'getUser',
20
+ boundaries: {},
21
+ type: 'success' as const
22
+ }
23
+
24
+ const record2 = {
25
+ input: { userId: 2 },
26
+ output: { name: 'Jane' },
27
+ taskName: 'getUser',
28
+ boundaries: {},
29
+ type: 'success' as const
30
+ }
31
+
32
+ tape.push(record1)
33
+ expect(tape.getLength()).toBe(1)
34
+
35
+ tape.push(record2)
36
+ expect(tape.getLength()).toBe(2)
37
+ })
38
+
39
+ test('should return correct length after removing records', () => {
40
+ const record = {
41
+ input: { userId: 1 },
42
+ output: { name: 'John' },
43
+ taskName: 'getUser',
44
+ boundaries: {},
45
+ type: 'success' as const
46
+ }
47
+
48
+ tape.push(record)
49
+ tape.push(record)
50
+ expect(tape.getLength()).toBe(2)
51
+
52
+ tape.shift()
53
+ expect(tape.getLength()).toBe(1)
54
+
55
+ tape.shift()
56
+ expect(tape.getLength()).toBe(0)
57
+ })
58
+ })
59
+
60
+ describe('shift', () => {
61
+ test('should return undefined for empty tape', () => {
62
+ expect(tape.shift()).toBeUndefined()
63
+ })
64
+
65
+ test('should return and remove first record', () => {
66
+ const record1 = {
67
+ input: { userId: 1 },
68
+ output: { name: 'John' },
69
+ taskName: 'getUser',
70
+ boundaries: {},
71
+ type: 'success' as const
72
+ }
73
+
74
+ const record2 = {
75
+ input: { userId: 2 },
76
+ output: { name: 'Jane' },
77
+ taskName: 'getUser',
78
+ boundaries: {},
79
+ type: 'success' as const
80
+ }
81
+
82
+ tape.push(record1)
83
+ tape.push(record2)
84
+
85
+ const shiftedRecord = tape.shift()
86
+
87
+ // Should return the first record
88
+ expect(shiftedRecord).toEqual(expect.objectContaining({
89
+ taskName: 'getUser',
90
+ input: { userId: 1 },
91
+ output: { name: 'John' },
92
+ type: 'success'
93
+ }))
94
+
95
+ // Should have removed the first record
96
+ expect(tape.getLength()).toBe(1)
97
+ expect(tape.getLog()[0]).toEqual(expect.objectContaining({
98
+ taskName: 'getUser',
99
+ input: { userId: 2 },
100
+ output: { name: 'Jane' },
101
+ type: 'success'
102
+ }))
103
+ })
104
+
105
+ test('should work correctly with multiple shifts', () => {
106
+ const records = [
107
+ {
108
+ input: { userId: 1 },
109
+ output: { name: 'John' },
110
+ taskName: 'getUser',
111
+ boundaries: {},
112
+ type: 'success' as const
113
+ },
114
+ {
115
+ input: { userId: 2 },
116
+ output: { name: 'Jane' },
117
+ taskName: 'getUser',
118
+ boundaries: {},
119
+ type: 'success' as const
120
+ },
121
+ {
122
+ input: { userId: 3 },
123
+ output: { name: 'Bob' },
124
+ taskName: 'getUser',
125
+ boundaries: {},
126
+ type: 'success' as const
127
+ }
128
+ ]
129
+
130
+ records.forEach(record => tape.push(record))
131
+ expect(tape.getLength()).toBe(3)
132
+
133
+ const first = tape.shift()
134
+ expect(first?.input).toEqual({ userId: 1 })
135
+ expect(tape.getLength()).toBe(2)
136
+
137
+ const second = tape.shift()
138
+ expect(second?.input).toEqual({ userId: 2 })
139
+ expect(tape.getLength()).toBe(1)
140
+
141
+ const third = tape.shift()
142
+ expect(third?.input).toEqual({ userId: 3 })
143
+ expect(tape.getLength()).toBe(0)
144
+
145
+ const fourth = tape.shift()
146
+ expect(fourth).toBeUndefined()
147
+ expect(tape.getLength()).toBe(0)
148
+ })
149
+ })
150
+ })
@@ -53,14 +53,9 @@ describe('Base tests', () => {
53
53
  })
54
54
 
55
55
  it('Should create a new tape with generic types', () => {
56
- type InputType = [{ name: string }]
57
- type OutputType = {
58
- age: number
59
- }
60
-
61
- const tape = new RecordTape<InputType, OutputType>({ path: emptyPath })
62
- tape.addLogRecord({ name: 'test', input: [{name: 'test'}], output: { age: 1 }, type: 'success', boundaries: {} })
63
- tape.addLogRecord({ name: 'test', input: [{name: 'test'}], error: 'test', type: 'error', boundaries: {} })
56
+ const tape = new RecordTape({ path: emptyPath })
57
+ tape.push({ input: [{name: 'test'}], output: { age: 1 }, type: 'success', boundaries: {}, taskName: 'test' })
58
+ tape.push({ input: [{name: 'test'}], error: 'test', type: 'error', boundaries: {}, taskName: 'test' })
64
59
 
65
60
  const data = tape.getLog()
66
61
  expect(data.length).toBe(2)
@@ -6,13 +6,13 @@ describe('Test log item formating', () => {
6
6
  type OutputType = boolean
7
7
 
8
8
  const tape = new RecordTape<InputType, OutputType>({})
9
- tape.addLogRecord({ name: 'name', input: true, output: true, boundaries: {}, type: 'success' })
10
- tape.addLogRecord({ name: 'name', input: true, error: 'invalid data', boundaries: {}, type: 'error' })
9
+ tape.push({ input: true, output: true, boundaries: {}, type: 'success', taskName: 'name' })
10
+ tape.push({ input: true, error: 'invalid data', boundaries: {}, type: 'error', taskName: 'name' })
11
11
 
12
12
  const str = tape.stringify()
13
13
 
14
- expect(str).toEqual(`{"name":"name","input":true,"output":true,"boundaries":{},"type":"success"}
15
- {"name":"name","input":true,"error":"invalid data","boundaries":{},"type":"error"}
14
+ expect(str).toEqual(`{"input":true,"output":true,"boundaries":{},"type":"success","taskName":"name","metadata":{}}
15
+ {"input":true,"error":"invalid data","boundaries":{},"type":"error","taskName":"name","metadata":{}}
16
16
  `)
17
17
  })
18
18
 
@@ -21,16 +21,16 @@ describe('Test log item formating', () => {
21
21
  type OutputType = boolean
22
22
 
23
23
  const tape = new RecordTape<InputType, OutputType>({})
24
- tape.addLogRecord({ name: 'name', input: true, output: true, boundaries: {}, type: 'success' })
25
- tape.addLogRecord({ name: 'name', input: true, error: 'invalid data', boundaries: {}, type: 'error' })
24
+ tape.push({ input: true, output: true, boundaries: {}, type: 'success', taskName: 'name' })
25
+ tape.push({ input: true, error: 'invalid data', boundaries: {}, type: 'error', taskName: 'name' })
26
26
 
27
27
  const str = tape.stringify()
28
28
  const tape2 = new RecordTape<InputType, OutputType>({})
29
29
  const records = tape2.parse(str)
30
30
 
31
31
  expect(records).toEqual([
32
- { name: 'name', input: true, output: true, boundaries: {}, type: 'success' },
33
- { name: 'name', input: true, error: 'invalid data', boundaries: {}, type: 'error' }
32
+ { input: true, output: true, boundaries: {}, type: 'success', taskName: 'name', metadata: {} },
33
+ { input: true, error: 'invalid data', boundaries: {}, type: 'error', taskName: 'name', metadata: {} }
34
34
  ])
35
35
  })
36
36
  })
@@ -1,358 +1,170 @@
1
+ import { createTask, Schema } from '@forgehive/task'
1
2
  import { RecordTape } from '../index'
2
- import { createTask, Schema, type ExecutionRecord, type TaskRecord } from '@forgehive/task'
3
3
 
4
- describe('RecordTape safeRun integration tests', () => {
5
- it('should record log items directly from safeRun result', async () => {
6
- // Create a schema
7
- const schema = new Schema({
8
- value: Schema.number()
9
- })
10
-
11
- // Define the boundaries
12
- const boundaries = {
13
- fetchData: async (value: number): Promise<number> => {
14
- return value * 2
15
- }
16
- }
4
+ describe('Safe run', () => {
5
+ it('Should run a simple task with no boundaries and register to a tape', async () => {
6
+ const tape = new RecordTape<{ value: number }, { doubled: number }>({})
17
7
 
18
- // Create the task
19
8
  const task = createTask({
20
- schema,
21
- boundaries,
22
- fn: async function ({ value }, { fetchData }) {
23
- const result = await fetchData(value)
24
- return { result, success: true }
9
+ name: 'simple-task',
10
+ schema: new Schema({
11
+ value: Schema.number()
12
+ }),
13
+ boundaries: {},
14
+ fn: async ({ value }) => {
15
+ return { doubled: value * 2 }
25
16
  }
26
17
  })
27
18
 
28
- // Create a record tape
29
- const tape = new RecordTape<{ value: number }, { result: number; success: boolean }, typeof boundaries>()
30
-
31
- // Run the task with safeRun and directly use the logItem
32
- const [result, error, record] = await task.safeRun({ value: 5 })
33
- tape.push('test-task', record)
34
-
35
- // Verify the execution was successful
36
- expect(error).toBeNull()
37
- expect(result).toEqual({ result: 10, success: true })
38
-
39
- // Get the recorded log from the tape
40
- const recordedLog = tape.getLog()
41
-
42
- // Verify the log was recorded correctly
43
- expect(recordedLog).toHaveLength(1)
44
-
45
- const logItem = recordedLog[0]
46
- expect(logItem.name).toEqual('test-task')
47
- expect(logItem.type).toEqual('success')
48
- expect(logItem.input).toEqual({ value: 5 })
49
- expect(logItem.output).toEqual({ result: 10, success: true })
50
- expect(logItem.boundaries).toEqual({
51
- fetchData: [{
52
- input: [5],
53
- output: 10,
54
- error: null
55
- }]
56
- })
57
- })
58
-
59
- it('should record log items from safeRun successfully', async () => {
60
- // Create a schema
61
- const schema = new Schema({
62
- value: Schema.number()
19
+ task.addListener((record) => {
20
+ tape.push(record)
63
21
  })
64
22
 
65
- // Define the boundaries
66
- const boundaries = {
67
- fetchData: async (value: number): Promise<number> => {
68
- return value * 2
69
- }
70
- }
71
-
72
- // Create the task
73
- const task = createTask({
74
- schema,
75
- boundaries,
76
- fn: async function ({ value }, { fetchData }) {
77
- const result = await fetchData(value)
78
- return { result, success: true }
79
- }
80
- })
81
-
82
- // Create a record tape
83
- const tape = new RecordTape<{ value: number }, { result: number; success: boolean }, typeof boundaries>()
84
-
85
- // Add listener to record the log items
86
- task.addListener((record: TaskRecord<{ value: number }, { result: number; success: boolean }>) => {
87
- // Manually ensure boundary records have error field for consistency with safeRun
88
- if (record.boundaries && record.boundaries.fetchData && Array.isArray(record.boundaries.fetchData)) {
89
- record.boundaries.fetchData = record.boundaries.fetchData.map((entry: Record<string, unknown>) => ({
90
- ...entry,
91
- error: entry.error ?? null,
92
- output: entry.output ?? null
93
- }))
94
- }
95
-
96
- // Add the record using push method
97
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
- tape.push('test-task', record as any)
99
- })
100
-
101
- // Run the task with safeRun
23
+ // Test simple execution
102
24
  const [result, error] = await task.safeRun({ value: 5 })
103
25
 
104
- // Verify the execution was successful
105
26
  expect(error).toBeNull()
106
- expect(result).toEqual({ result: 10, success: true })
107
-
108
- // Get the recorded log from the tape
109
- const recordedLog = tape.getLog()
27
+ expect(result).toEqual({ doubled: 10 })
110
28
 
111
- // Verify the log was recorded correctly
112
- expect(recordedLog).toHaveLength(1)
113
- expect(recordedLog[0]).toEqual({
114
- name: 'test-task',
29
+ const log = tape.getLog()
30
+ expect(log).toHaveLength(1)
31
+ expect(log[0]).toEqual({
115
32
  type: 'success',
116
33
  input: { value: 5 },
117
- output: { result: 10, success: true },
118
- boundaries: {
119
- fetchData: [{
120
- input: [5],
121
- output: 10,
122
- error: null
123
- }]
124
- },
34
+ output: { doubled: 10 },
35
+ boundaries: {},
125
36
  metadata: {},
126
- taskName: undefined
37
+ taskName: 'simple-task'
127
38
  })
128
39
  })
129
40
 
130
- it('should record error log items from safeRun', async () => {
131
- // Create a schema
132
- const schema = new Schema({
133
- value: Schema.number()
134
- })
41
+ it('Should run a task with boundaries and register to a tape', async () => {
42
+ const tape = new RecordTape<{ value: number }, { result: number; success: boolean }>({})
135
43
 
136
- // Define the boundaries with a function that will throw an error
137
- const boundaries = {
138
- fetchData: async (value: number): Promise<number> => {
139
- if (value < 0) {
140
- throw new Error('Value cannot be negative')
141
- }
142
- return value * 2
143
- }
144
- }
145
-
146
- // Create the task
147
44
  const task = createTask({
148
- schema,
149
- boundaries,
150
- fn: async function ({ value }, { fetchData }) {
151
- const result = await fetchData(value)
152
- return { result, success: true }
45
+ name: 'test',
46
+ schema: new Schema({
47
+ value: Schema.number().min(10).max(20)
48
+ }),
49
+ boundaries: {
50
+ multiply: async (value: number): Promise<number> => {
51
+ return value * 2
52
+ }
53
+ },
54
+ fn: async ({ value }, { multiply }) => {
55
+ const result = await multiply(value)
56
+ return { result, success: result > 10 }
153
57
  }
154
58
  })
155
59
 
156
- // Create a record tape
157
- const tape = new RecordTape<{ value: number }, { result: number; success: boolean }, typeof boundaries>()
158
-
159
- // Add listener to record the log items
160
- task.addListener((record: TaskRecord<{ value: number }, { result: number; success: boolean }>) => {
161
- // Manually ensure boundary records have error field for consistency with safeRun
162
- if (record.boundaries && record.boundaries.fetchData && Array.isArray(record.boundaries.fetchData)) {
163
- record.boundaries.fetchData = record.boundaries.fetchData.map((entry: Record<string, unknown>) => ({
164
- ...entry,
165
- error: entry.error ?? null,
166
- output: entry.output ?? null
167
- }))
168
- }
169
-
170
- // Add the record using push method
171
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
- tape.push('test-task', record as any)
60
+ task.addListener((record) => {
61
+ tape.push(record)
173
62
  })
174
63
 
175
- // Run the task with safeRun with a value that will cause an error
176
- const [result, error] = await task.safeRun({ value: -5 })
64
+ // Test invalid input
65
+ let [result, error] = await task.safeRun({ value: 5 })
177
66
 
178
- // Verify the execution failed as expected
179
- expect(result).toBeNull()
180
- expect(error).not.toBeNull()
181
- expect(error?.message).toContain('Value cannot be negative')
67
+ expect(error).toBeDefined()
68
+ expect(error?.message).toContain('Invalid input')
182
69
 
183
- // Get the recorded log from the tape
184
- const recordedLog = tape.getLog()
70
+ // Test valid input
71
+ ;[result, error] = await task.safeRun({ value: 15 })
72
+ expect(error).toBeNull()
73
+ expect(result).toEqual({ result: 30, success: true })
185
74
 
186
- // Verify the error log was recorded correctly
187
- expect(recordedLog).toHaveLength(1)
188
- expect(recordedLog[0]).toEqual({
189
- name: 'test-task',
75
+ const log = tape.getLog()
76
+ expect(log).toHaveLength(2)
77
+ expect(log[0]).toEqual({
190
78
  type: 'error',
191
- input: { value: -5 },
192
- error: 'Value cannot be negative',
79
+ input: { value: 5 },
80
+ error: 'Invalid input on: value: Number must be greater than or equal to 10',
193
81
  boundaries: {
194
- fetchData: [{
195
- input: [-5],
196
- error: 'Value cannot be negative',
197
- output: null
198
- }]
82
+ multiply: []
199
83
  },
200
84
  metadata: {},
201
- output: undefined,
202
- taskName: undefined
85
+ taskName: 'test'
203
86
  })
204
- })
205
87
 
206
- it('should handle error records directly with push', async () => {
207
- // Create a schema
208
- const schema = new Schema({
209
- value: Schema.number()
88
+ expect(log[1]).toEqual({
89
+ type: 'success',
90
+ input: { value: 15 },
91
+ output: { result: 30, success: true },
92
+ boundaries: {
93
+ multiply: [{
94
+ input: [15],
95
+ output: 30
96
+ }]
97
+ },
98
+ metadata: {},
99
+ taskName: 'test'
210
100
  })
101
+ })
211
102
 
212
- // Define the boundaries with a function that will throw an error
213
- const boundaries = {
214
- fetchData: async (value: number): Promise<number> => {
215
- if (value < 0) {
216
- throw new Error('Value cannot be negative')
217
- }
218
- return value * 2
219
- }
220
- }
103
+ it('Should run a task with boundaries and register errors to a tape', async () => {
104
+ const tape = new RecordTape<{ value: number }, { result: number }>({})
221
105
 
222
- // Create the task
223
106
  const task = createTask({
224
- schema,
225
- boundaries,
226
- fn: async function ({ value }, { fetchData }) {
227
- const result = await fetchData(value)
228
- return { result, success: true }
107
+ name: 'test',
108
+ schema: new Schema({
109
+ value: Schema.number()
110
+ }),
111
+ boundaries: {
112
+ divide: async (value: number): Promise<number> => {
113
+ if (value === 0) {
114
+ throw new Error('Division by zero')
115
+ }
116
+ return 100 / value
117
+ }
118
+ },
119
+ fn: async ({ value }, { divide }) => {
120
+ const result = await divide(value)
121
+ return { result }
229
122
  }
230
123
  })
231
124
 
232
- // Create a record tape
233
- const tape = new RecordTape<{ value: number }, { result: number; success: boolean }, typeof boundaries>()
234
-
235
- // Run the task with safeRun with a value that will cause an error
236
- const [result, error, record] = await task.safeRun({ value: -5 })
125
+ task.addListener((record) => {
126
+ tape.push(record)
127
+ })
237
128
 
238
- // Push the error record directly with type parameter
239
- tape.push('test-error', record)
129
+ // Test division by zero
130
+ const [, error] = await task.safeRun({ value: 0 })
240
131
 
241
- // Verify the execution failed as expected
242
- expect(result).toBeNull()
243
- expect(error).not.toBeNull()
244
- expect(error instanceof Error).toBe(true)
245
- if (error instanceof Error) {
246
- expect(error.message).toContain('Value cannot be negative')
247
- }
132
+ expect(error).toBeDefined()
133
+ expect(error?.message).toBe('Division by zero')
248
134
 
249
- // Get the recorded log from the tape
250
- const recordedLog = tape.getLog()
135
+ // Test valid input
136
+ const [secondResult, secondError] = await task.safeRun({ value: 10 })
137
+ expect(secondError).toBeNull()
138
+ expect(secondResult).toEqual({ result: 10 })
251
139
 
252
- // Verify the error log was recorded correctly
253
- expect(recordedLog).toHaveLength(1)
254
- expect(recordedLog[0]).toEqual({
255
- name: 'test-error',
140
+ const log = tape.getLog()
141
+ expect(log).toHaveLength(2)
142
+ expect(log[0]).toEqual({
256
143
  type: 'error',
257
- input: { value: -5 },
258
- error: 'Value cannot be negative',
144
+ input: { value: 0 },
145
+ error: 'Division by zero',
259
146
  boundaries: {
260
- fetchData: [{
261
- input: [-5],
262
- output: null,
263
- error: 'Value cannot be negative'
147
+ divide: [{
148
+ input: [0],
149
+ error: 'Division by zero'
264
150
  }]
265
151
  },
266
152
  metadata: {},
267
- output: undefined,
268
- taskName: undefined
153
+ taskName: 'test'
269
154
  })
270
- })
271
-
272
- it('should handle custom execution records with push', async () => {
273
- // Create a record tape
274
- const tape = new RecordTape<{ value: number }, { result: number }, { fetchData: (n: number) => Promise<number> }>()
275
155
 
276
- // Create a custom execution record
277
- const customRecord: ExecutionRecord<{ value: number }, { result: number }, { fetchData: (n: number) => Promise<number> }> = {
278
- input: { value: 10 },
279
- output: { result: 20 },
280
- type: 'success',
281
- boundaries: {
282
- fetchData: [
283
- {
284
- input: [10],
285
- output: 20
286
- }
287
- ]
288
- }
289
- }
290
-
291
- // Push the custom record
292
- tape.push('custom-record', customRecord)
293
-
294
- // Get the recorded log from the tape
295
- const recordedLog = tape.getLog()
296
-
297
- // Verify the log was recorded correctly
298
- expect(recordedLog).toHaveLength(1)
299
- expect(recordedLog[0]).toEqual({
300
- name: 'custom-record',
156
+ expect(log[1]).toEqual({
301
157
  type: 'success',
302
158
  input: { value: 10 },
303
- output: { result: 20 },
159
+ output: { result: 10 },
304
160
  boundaries: {
305
- fetchData: [{
161
+ divide: [{
306
162
  input: [10],
307
- output: 20,
308
- error: null
163
+ output: 10
309
164
  }]
310
165
  },
311
- metadata: {}
312
- })
313
- })
314
-
315
- it('should handle execution records with Promise outputs correctly', async () => {
316
- // Create a record tape
317
- const tape = new RecordTape<{ value: number }, { result: number }, { fetchData: (n: number) => Promise<number> }>()
318
-
319
- // Create a custom execution record with a Promise output
320
- const promiseResult = Promise.resolve({ result: 30 })
321
- const promiseRecord: ExecutionRecord<{ value: number }, Promise<{ result: number }>, { fetchData: (n: number) => Promise<number> }> = {
322
- input: { value: 15 },
323
- output: promiseResult,
324
- type: 'success',
325
- boundaries: {
326
- fetchData: [
327
- {
328
- input: [15],
329
- output: 30
330
- }
331
- ]
332
- }
333
- }
334
-
335
- // Push the record with Promise output using type parameter
336
- tape.push('promise-record', promiseRecord)
337
-
338
- // Get the recorded log from the tape
339
- const recordedLog = tape.getLog()
340
-
341
- // Verify the log was recorded correctly, with Promise output set to null
342
- expect(recordedLog).toHaveLength(1)
343
- expect(recordedLog[0]).toEqual({
344
- name: 'promise-record',
345
- type: 'success',
346
- input: { value: 15 },
347
- output: null, // Promise output should be set to null
348
- boundaries: {
349
- fetchData: [{
350
- input: [15],
351
- output: 30,
352
- error: null
353
- }]
354
- },
355
- metadata: {}
166
+ metadata: {},
167
+ taskName: 'test'
356
168
  })
357
169
  })
358
170
  })