@forgehive/record-tape 0.1.6 → 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.
- package/README.md +385 -0
- package/dist/index.d.ts +10 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -120
- package/dist/index.js.map +1 -1
- package/dist/tests/data-methods.test.d.ts +2 -0
- package/dist/tests/data-methods.test.d.ts.map +1 -0
- package/dist/tests/data-methods.test.js +129 -0
- package/dist/tests/data-methods.test.js.map +1 -0
- package/dist/tests/index.test.js +3 -3
- package/dist/tests/index.test.js.map +1 -1
- package/dist/tests/log-format.test.js +18 -27
- package/dist/tests/log-format.test.js.map +1 -1
- package/dist/tests/mode.test.js +66 -54
- package/dist/tests/mode.test.js.map +1 -1
- package/dist/tests/safe-run.test.js +110 -242
- package/dist/tests/safe-run.test.js.map +1 -1
- package/dist/tests/save.test.js +13 -13
- package/dist/tests/save.test.js.map +1 -1
- package/dist/tests/task-listener.test.js +35 -31
- package/dist/tests/task-listener.test.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +34 -164
- package/src/tests/data-methods.test.ts +150 -0
- package/src/tests/index.test.ts +4 -9
- package/src/tests/log-format.test.ts +20 -32
- package/src/tests/safe-run.test.ts +119 -294
- package/src/tests/save.test.ts +19 -19
- package/src/tests/task-listener.test.ts +29 -42
- package/src/tests/mode.test.ts +0 -93
|
@@ -5,18 +5,20 @@ const index_1 = require("../index");
|
|
|
5
5
|
describe('Task listener', () => {
|
|
6
6
|
it('Should listen to task events', async () => {
|
|
7
7
|
const tape = new index_1.RecordTape({});
|
|
8
|
-
const task =
|
|
9
|
-
|
|
8
|
+
const task = (0, task_1.createTask)({
|
|
9
|
+
name: 'test',
|
|
10
|
+
schema: new task_1.Schema({}),
|
|
11
|
+
boundaries: {},
|
|
12
|
+
fn: async (_input) => {
|
|
13
|
+
return { value: 1, foo: true };
|
|
14
|
+
}
|
|
10
15
|
});
|
|
11
16
|
task.addListener((record) => {
|
|
12
|
-
|
|
13
|
-
? { input: record.input, error: record.error, boundaries: record.boundaries }
|
|
14
|
-
: { input: record.input, output: record.output, boundaries: record.boundaries };
|
|
15
|
-
tape.addLogItem('test', logItem);
|
|
17
|
+
tape.push(record);
|
|
16
18
|
});
|
|
17
19
|
await task.run({});
|
|
18
20
|
expect(tape.getLog()).toEqual([
|
|
19
|
-
{
|
|
21
|
+
{ type: 'success', input: {}, output: { value: 1, foo: true }, boundaries: {}, metadata: {}, taskName: 'test' }
|
|
20
22
|
]);
|
|
21
23
|
});
|
|
22
24
|
it('Should has errors and sucess items', async () => {
|
|
@@ -27,11 +29,9 @@ describe('Task listener', () => {
|
|
|
27
29
|
return { result: input.value * 2 };
|
|
28
30
|
});
|
|
29
31
|
const tape = new index_1.RecordTape({});
|
|
32
|
+
task.setName('test');
|
|
30
33
|
task.addListener((record) => {
|
|
31
|
-
|
|
32
|
-
? { input: record.input, error: record.error, boundaries: record.boundaries }
|
|
33
|
-
: { input: record.input, output: record.output, boundaries: record.boundaries };
|
|
34
|
-
tape.addLogItem('test', logItem);
|
|
34
|
+
tape.push(record);
|
|
35
35
|
});
|
|
36
36
|
try {
|
|
37
37
|
await task.run({ value: 5 });
|
|
@@ -42,8 +42,8 @@ describe('Task listener', () => {
|
|
|
42
42
|
await task.run({ value: 15 });
|
|
43
43
|
const log = tape.getLog();
|
|
44
44
|
expect(log).toEqual([
|
|
45
|
-
{
|
|
46
|
-
{
|
|
45
|
+
{ type: 'error', input: { value: 5 }, error: 'Value is not between 10 and 20', boundaries: {}, metadata: {}, output: undefined, taskName: 'test' },
|
|
46
|
+
{ type: 'success', input: { value: 15 }, output: { result: 30 }, boundaries: {}, metadata: {}, taskName: 'test' }
|
|
47
47
|
]);
|
|
48
48
|
});
|
|
49
49
|
it('Should get types from task', async () => {
|
|
@@ -58,27 +58,29 @@ describe('Task listener', () => {
|
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
60
|
// Create the task using createTask
|
|
61
|
-
const task = (0, task_1.createTask)(
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const task = (0, task_1.createTask)({
|
|
62
|
+
schema,
|
|
63
|
+
boundaries,
|
|
64
|
+
fn: async (input, { multiply }) => {
|
|
65
|
+
const result = await multiply(input.value);
|
|
66
|
+
return { result };
|
|
67
|
+
}
|
|
64
68
|
});
|
|
65
69
|
const tape = new index_1.RecordTape({});
|
|
66
70
|
task.addListener((record) => {
|
|
67
|
-
|
|
68
|
-
? { input: record.input, error: record.error, boundaries: record.boundaries }
|
|
69
|
-
: { input: record.input, output: record.output, boundaries: record.boundaries };
|
|
70
|
-
tape.addLogItem('test', logItem);
|
|
71
|
+
tape.push(record);
|
|
71
72
|
});
|
|
72
73
|
await task.run({ value: 5 });
|
|
73
74
|
expect(tape.getLog()).toEqual([
|
|
74
75
|
{
|
|
75
|
-
name: 'test',
|
|
76
76
|
type: 'success',
|
|
77
77
|
input: { value: 5 },
|
|
78
78
|
output: { result: 10 },
|
|
79
79
|
boundaries: {
|
|
80
80
|
multiply: [{ input: [5], output: 10 }]
|
|
81
|
-
}
|
|
81
|
+
},
|
|
82
|
+
metadata: {},
|
|
83
|
+
taskName: undefined
|
|
82
84
|
}
|
|
83
85
|
]);
|
|
84
86
|
});
|
|
@@ -95,26 +97,28 @@ describe('Task listener', () => {
|
|
|
95
97
|
}
|
|
96
98
|
};
|
|
97
99
|
// Create the task using createTask
|
|
98
|
-
const task = (0, task_1.createTask)(
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
const task = (0, task_1.createTask)({
|
|
101
|
+
schema,
|
|
102
|
+
boundaries,
|
|
103
|
+
fn: async (input, { multiply }) => {
|
|
104
|
+
const result = await multiply(input.value);
|
|
105
|
+
return { result };
|
|
106
|
+
}
|
|
101
107
|
});
|
|
102
108
|
task.addListener((record) => {
|
|
103
|
-
|
|
104
|
-
? { input: record.input, error: record.error, boundaries: record.boundaries }
|
|
105
|
-
: { input: record.input, output: record.output, boundaries: record.boundaries };
|
|
106
|
-
tape.addLogItem('test', logItem);
|
|
109
|
+
tape.push(record);
|
|
107
110
|
});
|
|
108
111
|
await task.run({ value: 5 });
|
|
109
112
|
expect(tape.getLog()).toEqual([
|
|
110
113
|
{
|
|
111
|
-
name: 'test',
|
|
112
114
|
type: 'success',
|
|
113
115
|
input: { value: 5 },
|
|
114
116
|
output: { result: 10 },
|
|
115
117
|
boundaries: {
|
|
116
118
|
multiply: [{ input: [5], output: 10 }]
|
|
117
|
-
}
|
|
119
|
+
},
|
|
120
|
+
metadata: {},
|
|
121
|
+
taskName: undefined
|
|
118
122
|
}
|
|
119
123
|
]);
|
|
120
124
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-listener.test.js","sourceRoot":"","sources":["../../src/tests/task-listener.test.ts"],"names":[],"mappings":";;AAAA,0CAA0D;AAC1D,
|
|
1
|
+
{"version":3,"file":"task-listener.test.js","sourceRoot":"","sources":["../../src/tests/task-listener.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgehive/record-tape",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"access": "public",
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@forgehive/schema": "^0.1.4",
|
|
11
|
-
"@forgehive/task": "^0.
|
|
11
|
+
"@forgehive/task": "^0.2.0"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"typescript": "^5.3.3"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@forgehive/
|
|
23
|
-
"@forgehive/
|
|
22
|
+
"@forgehive/task": "0.2.1",
|
|
23
|
+
"@forgehive/schema": "0.1.4"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsc",
|
package/src/index.ts
CHANGED
|
@@ -2,185 +2,60 @@ import fs from 'fs'
|
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { type ExecutionRecord, type Boundaries } from '@forgehive/task'
|
|
4
4
|
|
|
5
|
-
export interface
|
|
6
|
-
name: string
|
|
7
|
-
type: 'success' | 'error'
|
|
8
|
-
context?: Record<string, string>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface SuccessLogItem<TInput = unknown, TOutput = unknown> {
|
|
12
|
-
input: TInput
|
|
13
|
-
output: TOutput
|
|
14
|
-
boundaries?: Record<string, unknown>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ErrorLogItem<TInput = unknown> {
|
|
18
|
-
input: TInput
|
|
19
|
-
error: unknown
|
|
20
|
-
boundaries?: Record<string, unknown>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type LogItem<TInput = unknown, TOutput = unknown> = SuccessLogItem<TInput, TOutput> | ErrorLogItem<TInput>
|
|
24
|
-
|
|
25
|
-
// Additional type to handle TaskRecord compatibility
|
|
26
|
-
export type TaskLogItem<TInput = unknown, TOutput = unknown> = LogItem<TInput, TOutput> | {
|
|
27
|
-
input: TInput;
|
|
28
|
-
output?: TOutput;
|
|
29
|
-
error?: unknown;
|
|
30
|
-
boundaries?: Record<string, unknown>;
|
|
5
|
+
export interface GenericExecutionRecord<TInput = unknown, TOutput = unknown, B extends Boundaries = Boundaries> extends ExecutionRecord<TInput, TOutput, B> {
|
|
31
6
|
}
|
|
32
7
|
|
|
33
8
|
interface Config<TInput = unknown, TOutput = unknown, B extends Boundaries = Boundaries> {
|
|
34
9
|
path?: fs.PathLike
|
|
35
|
-
log?:
|
|
10
|
+
log?: GenericExecutionRecord<TInput, TOutput, B>[]
|
|
36
11
|
boundaries?: Record<string, unknown>
|
|
37
12
|
}
|
|
38
13
|
|
|
39
|
-
export type Mode = 'record' | 'replay'
|
|
40
|
-
|
|
41
14
|
export class RecordTape<TInput = unknown, TOutput = unknown, B extends Boundaries = Boundaries> {
|
|
42
15
|
private _path: fs.PathLike | undefined
|
|
43
|
-
private
|
|
44
|
-
private _log: LogRecord<TInput, TOutput, B>[]
|
|
16
|
+
private _log: GenericExecutionRecord<TInput, TOutput, B>[]
|
|
45
17
|
|
|
46
18
|
constructor(config: Config<TInput, TOutput, B> = {}) {
|
|
47
19
|
this._path = typeof config.path === 'string' ? `${config.path}.log` : undefined
|
|
48
20
|
this._log = config.log ?? []
|
|
49
|
-
this._mode = 'record'
|
|
50
21
|
}
|
|
51
22
|
|
|
52
23
|
// Data functions
|
|
53
|
-
getLog():
|
|
24
|
+
getLog(): GenericExecutionRecord<TInput, TOutput, B>[] {
|
|
54
25
|
return this._log
|
|
55
26
|
}
|
|
56
27
|
|
|
57
|
-
|
|
58
|
-
return this.
|
|
28
|
+
getLength(): number {
|
|
29
|
+
return this._log.length
|
|
59
30
|
}
|
|
60
31
|
|
|
61
|
-
|
|
62
|
-
this.
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
addLogItem(name: string, logItem: LogItem<TInput, TOutput>): void {
|
|
66
|
-
if (this._mode === 'replay') {
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Format boundaries to ensure both error and output fields are set if needed
|
|
71
|
-
const formattedBoundaries: Record<string, unknown> = {}
|
|
72
|
-
if (logItem.boundaries) {
|
|
73
|
-
for (const key in logItem.boundaries) {
|
|
74
|
-
// Check if the source is from safe-run (if it has error field in entries)
|
|
75
|
-
const boundaryEntries = logItem.boundaries[key] as Array<Record<string, unknown>>
|
|
76
|
-
const isSafeRun = boundaryEntries.some(entry => entry.error !== undefined)
|
|
77
|
-
|
|
78
|
-
formattedBoundaries[key] = boundaryEntries.map(entry => {
|
|
79
|
-
// Only add error field if it's from safe-run
|
|
80
|
-
return isSafeRun ?
|
|
81
|
-
{
|
|
82
|
-
input: entry.input,
|
|
83
|
-
output: entry.output ?? null,
|
|
84
|
-
error: entry.error ?? null
|
|
85
|
-
} :
|
|
86
|
-
{
|
|
87
|
-
input: entry.input,
|
|
88
|
-
output: entry.output
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Handle LogItem interface - need to type cast to access properties safely
|
|
95
|
-
const typedLogItem = logItem as (SuccessLogItem<TInput, TOutput> | ErrorLogItem<TInput>)
|
|
96
|
-
|
|
97
|
-
if ('output' in typedLogItem && typedLogItem.output !== undefined) {
|
|
98
|
-
const { input, output } = typedLogItem
|
|
99
|
-
this._log.push({
|
|
100
|
-
name,
|
|
101
|
-
type: 'success',
|
|
102
|
-
input,
|
|
103
|
-
output,
|
|
104
|
-
boundaries: formattedBoundaries
|
|
105
|
-
} as LogRecord<TInput, TOutput, B>)
|
|
106
|
-
} else if ('error' in typedLogItem && typedLogItem.error !== undefined) {
|
|
107
|
-
const { input, error } = typedLogItem
|
|
108
|
-
this._log.push({
|
|
109
|
-
name,
|
|
110
|
-
type: 'error',
|
|
111
|
-
input,
|
|
112
|
-
error,
|
|
113
|
-
boundaries: formattedBoundaries
|
|
114
|
-
} as LogRecord<TInput, TOutput, B>)
|
|
115
|
-
} else {
|
|
116
|
-
throw new Error('invalid log item')
|
|
117
|
-
}
|
|
32
|
+
shift(): GenericExecutionRecord<TInput, TOutput, B> | undefined {
|
|
33
|
+
return this._log.shift()
|
|
118
34
|
}
|
|
119
35
|
|
|
120
36
|
push(
|
|
121
|
-
name: string,
|
|
122
37
|
record: ExecutionRecord<TInput, unknown, B>,
|
|
123
|
-
|
|
124
|
-
):
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
error: entry.error ?? null
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
}
|
|
38
|
+
metadata?: Record<string, string>
|
|
39
|
+
): GenericExecutionRecord<TInput, TOutput, B> {
|
|
40
|
+
// Add type if missing
|
|
41
|
+
const recordType = ('type' in record && record.type) ? record.type :
|
|
42
|
+
(record.output !== undefined && record.output !== null) ? 'success' :
|
|
43
|
+
(record.error !== undefined) ? 'error' : 'pending'
|
|
44
|
+
|
|
45
|
+
// Merge metadata from record and parameter (parameter takes precedence)
|
|
46
|
+
const mergedMetadata = { ...record.metadata, ...metadata }
|
|
47
|
+
|
|
48
|
+
const logRecord = {
|
|
49
|
+
...record,
|
|
50
|
+
type: recordType,
|
|
51
|
+
metadata: mergedMetadata
|
|
52
|
+
} as GenericExecutionRecord<TInput, TOutput, B>
|
|
143
53
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if ('output' in record && record.output !== undefined) {
|
|
147
|
-
const input = record.input
|
|
148
|
-
// Handle Promise outputs by setting to null in the log
|
|
149
|
-
const output = record.output instanceof Promise ? null : record.output
|
|
150
|
-
|
|
151
|
-
logRecord = {
|
|
152
|
-
name,
|
|
153
|
-
type: 'success',
|
|
154
|
-
input,
|
|
155
|
-
output,
|
|
156
|
-
boundaries: formattedBoundaries,
|
|
157
|
-
context
|
|
158
|
-
} as LogRecord<TInput, TOutput, B>
|
|
159
|
-
this._log.push(logRecord)
|
|
160
|
-
} else if ('error' in record && record.error !== undefined) {
|
|
161
|
-
const input = record.input
|
|
162
|
-
const error = record.error
|
|
163
|
-
|
|
164
|
-
logRecord = {
|
|
165
|
-
name,
|
|
166
|
-
type: 'error',
|
|
167
|
-
input,
|
|
168
|
-
error,
|
|
169
|
-
boundaries: formattedBoundaries,
|
|
170
|
-
context
|
|
171
|
-
} as LogRecord<TInput, TOutput, B>
|
|
172
|
-
this._log.push(logRecord)
|
|
173
|
-
} else {
|
|
174
|
-
throw new Error('invalid record type')
|
|
175
|
-
}
|
|
54
|
+
this._log.push(logRecord)
|
|
176
55
|
|
|
177
56
|
return logRecord
|
|
178
57
|
}
|
|
179
58
|
|
|
180
|
-
addLogRecord(logRecord: LogRecord<TInput, TOutput, B>): void {
|
|
181
|
-
this._log.push(logRecord)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
59
|
stringify(): string {
|
|
185
60
|
let log = ''
|
|
186
61
|
for (const logItem of this._log) {
|
|
@@ -190,12 +65,12 @@ export class RecordTape<TInput = unknown, TOutput = unknown, B extends Boundarie
|
|
|
190
65
|
return log
|
|
191
66
|
}
|
|
192
67
|
|
|
193
|
-
parse(content: string):
|
|
68
|
+
parse(content: string): GenericExecutionRecord<TInput, TOutput, B>[] {
|
|
194
69
|
const items = content.split('\n')
|
|
195
|
-
const log:
|
|
70
|
+
const log: GenericExecutionRecord<TInput, TOutput, B>[] = []
|
|
196
71
|
for (const item of items) {
|
|
197
72
|
if (item !== '') {
|
|
198
|
-
const data = JSON.parse(item) as
|
|
73
|
+
const data = JSON.parse(item) as GenericExecutionRecord<TInput, TOutput, B>
|
|
199
74
|
log.push(data)
|
|
200
75
|
}
|
|
201
76
|
}
|
|
@@ -218,21 +93,15 @@ export class RecordTape<TInput = unknown, TOutput = unknown, B extends Boundarie
|
|
|
218
93
|
return cache
|
|
219
94
|
}
|
|
220
95
|
|
|
221
|
-
recordFrom(
|
|
222
|
-
// Add
|
|
223
|
-
task._listener = async (
|
|
224
|
-
|
|
225
|
-
if (this.getMode() === 'record') {
|
|
226
|
-
this.addLogItem(name, logItem)
|
|
227
|
-
}
|
|
96
|
+
recordFrom(task: { _listener?: unknown }): void {
|
|
97
|
+
// Add listener for ExecutionRecord
|
|
98
|
+
task._listener = async (executionRecord: ExecutionRecord<TInput, TOutput, B>): Promise<void> => {
|
|
99
|
+
this.push(executionRecord)
|
|
228
100
|
}
|
|
229
|
-
|
|
230
|
-
// Add cache
|
|
231
|
-
task.setBoundariesData(this.compileCache())
|
|
232
101
|
}
|
|
233
102
|
|
|
234
103
|
// Load save functions
|
|
235
|
-
async load(): Promise<
|
|
104
|
+
async load(): Promise<GenericExecutionRecord<TInput, TOutput, B>[]> {
|
|
236
105
|
if (typeof this._path === 'undefined') {
|
|
237
106
|
return []
|
|
238
107
|
}
|
|
@@ -262,7 +131,7 @@ export class RecordTape<TInput = unknown, TOutput = unknown, B extends Boundarie
|
|
|
262
131
|
return this._log
|
|
263
132
|
}
|
|
264
133
|
|
|
265
|
-
loadSync():
|
|
134
|
+
loadSync(): GenericExecutionRecord<TInput, TOutput, B>[] {
|
|
266
135
|
if (typeof this._path === 'undefined') { return [] }
|
|
267
136
|
|
|
268
137
|
const dirpath = path.dirname(this._path.toString())
|
|
@@ -281,6 +150,7 @@ export class RecordTape<TInput = unknown, TOutput = unknown, B extends Boundarie
|
|
|
281
150
|
return this._log
|
|
282
151
|
}
|
|
283
152
|
|
|
153
|
+
// Save functions
|
|
284
154
|
async save(): Promise<void> {
|
|
285
155
|
if (typeof this._path === 'undefined') { return }
|
|
286
156
|
|
|
@@ -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
|
+
})
|
package/src/tests/index.test.ts
CHANGED
|
@@ -53,14 +53,9 @@ describe('Base tests', () => {
|
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
it('Should create a new tape with generic types', () => {
|
|
56
|
-
|
|
57
|
-
type
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const tape = new RecordTape<InputType, OutputType>({ path: emptyPath })
|
|
62
|
-
tape.addLogItem('test', { input: [{name: 'test'}], output: { age: 1 } })
|
|
63
|
-
tape.addLogItem('test', { input: [{name: 'test'}], error: new Error('test') })
|
|
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)
|
|
@@ -82,7 +77,7 @@ describe('Base tests', () => {
|
|
|
82
77
|
|
|
83
78
|
expect(input2).toEqual([{name: 'test'}])
|
|
84
79
|
expect(output2).toBeUndefined()
|
|
85
|
-
expect(error2).toEqual(
|
|
80
|
+
expect(error2).toEqual('test')
|
|
86
81
|
expect(type2).toBe('error')
|
|
87
82
|
})
|
|
88
83
|
})
|