@forgehive/task 0.1.0
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/LICENSE +21 -0
- package/README.md +241 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/test/add-listener-with-boundaries.test.d.ts +2 -0
- package/dist/test/add-listener-with-boundaries.test.d.ts.map +1 -0
- package/dist/test/add-listener-with-boundaries.test.js +230 -0
- package/dist/test/add-listener-with-boundaries.test.js.map +1 -0
- package/dist/test/add-listener.test.d.ts +2 -0
- package/dist/test/add-listener.test.d.ts.map +1 -0
- package/dist/test/add-listener.test.js +92 -0
- package/dist/test/add-listener.test.js.map +1 -0
- package/dist/test/boundary-modes.test.d.ts +2 -0
- package/dist/test/boundary-modes.test.d.ts.map +1 -0
- package/dist/test/boundary-modes.test.js +184 -0
- package/dist/test/boundary-modes.test.js.map +1 -0
- package/dist/test/change-mode.test.d.ts +2 -0
- package/dist/test/change-mode.test.d.ts.map +1 -0
- package/dist/test/change-mode.test.js +39 -0
- package/dist/test/change-mode.test.js.map +1 -0
- package/dist/test/index.test.d.ts +2 -0
- package/dist/test/index.test.d.ts.map +1 -0
- package/dist/test/index.test.js +48 -0
- package/dist/test/index.test.js.map +1 -0
- package/dist/test/run-boundary.test.d.ts +2 -0
- package/dist/test/run-boundary.test.d.ts.map +1 -0
- package/dist/test/run-boundary.test.js +49 -0
- package/dist/test/run-boundary.test.js.map +1 -0
- package/dist/test/run-without-args.test.d.ts +2 -0
- package/dist/test/run-without-args.test.d.ts.map +1 -0
- package/dist/test/run-without-args.test.js +29 -0
- package/dist/test/run-without-args.test.js.map +1 -0
- package/dist/test/task-with-boundaries.test.d.ts +2 -0
- package/dist/test/task-with-boundaries.test.d.ts.map +1 -0
- package/dist/test/task-with-boundaries.test.js +102 -0
- package/dist/test/task-with-boundaries.test.js.map +1 -0
- package/dist/test/validation.test.d.ts +2 -0
- package/dist/test/validation.test.d.ts.map +1 -0
- package/dist/test/validation.test.js +166 -0
- package/dist/test/validation.test.js.map +1 -0
- package/dist/utils/boundary.d.ts +44 -0
- package/dist/utils/boundary.d.ts.map +1 -0
- package/dist/utils/boundary.js +145 -0
- package/dist/utils/boundary.js.map +1 -0
- package/jest.config.js +9 -0
- package/package.json +30 -0
- package/src/index.ts +344 -0
- package/src/test/add-listener-with-boundaries.test.ts +299 -0
- package/src/test/add-listener.test.ts +110 -0
- package/src/test/boundary-modes.test.ts +215 -0
- package/src/test/change-mode.test.ts +45 -0
- package/src/test/index.test.ts +62 -0
- package/src/test/run-boundary.test.ts +64 -0
- package/src/test/run-without-args.test.ts +33 -0
- package/src/test/task-with-boundaries.test.ts +137 -0
- package/src/test/validation.test.ts +194 -0
- package/src/utils/boundary.ts +178 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Task, type TaskInstanceType } from '../index'
|
|
2
|
+
import { Schema, type InferSchema } from '@shadow/schema'
|
|
3
|
+
|
|
4
|
+
describe('Validation tests', () => {
|
|
5
|
+
let task: TaskInstanceType
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
const schema = new Schema({
|
|
9
|
+
value: Schema.number()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
task = new Task(function (argv: InferSchema<typeof schema>) {
|
|
13
|
+
return argv
|
|
14
|
+
}, {
|
|
15
|
+
schema
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('Should be invalid', () => {
|
|
20
|
+
const check = task.isValid({ value: null })
|
|
21
|
+
|
|
22
|
+
expect(check).toBe(false)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('Should be valid', () => {
|
|
26
|
+
const check = task.isValid({ value: 5 })
|
|
27
|
+
|
|
28
|
+
expect(check).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('Should validate data as part of run function', async () => {
|
|
32
|
+
try {
|
|
33
|
+
await task.run({ value: null })
|
|
34
|
+
// If we get here, the test should fail
|
|
35
|
+
expect('no error thrown').toBeUndefined()
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Test passes if we get here
|
|
38
|
+
expect(e).toBeDefined()
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('Should work well', async () => {
|
|
43
|
+
const result = await task.run({ value: 5 })
|
|
44
|
+
expect(result.value).toBe(5)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('Validation tests on param', () => {
|
|
49
|
+
let task: TaskInstanceType
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
const schema = new Schema({
|
|
53
|
+
value: Schema.number()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
task = new Task(function (argv: InferSchema<typeof schema>) {
|
|
57
|
+
return argv
|
|
58
|
+
}, {
|
|
59
|
+
schema
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('Should be invalid', () => {
|
|
64
|
+
const check = task.isValid({ value: null })
|
|
65
|
+
|
|
66
|
+
expect(check).toBe(false)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('Should be valid', () => {
|
|
70
|
+
const check = task.isValid({ value: 5 })
|
|
71
|
+
|
|
72
|
+
expect(check).toBe(true)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('Should validate data as part of run function', async () => {
|
|
76
|
+
try {
|
|
77
|
+
await task.run({ value: null })
|
|
78
|
+
// If we get here, the test should fail
|
|
79
|
+
expect('no error thrown').toBeUndefined()
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// Test passes if we get here
|
|
82
|
+
expect(e).toBeDefined()
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('Should work well', async () => {
|
|
87
|
+
try {
|
|
88
|
+
const result = await task.run({ value: 5 })
|
|
89
|
+
expect(result.value).toBe(5)
|
|
90
|
+
} catch (e) {
|
|
91
|
+
expect('error thrown: ' + e).toBeUndefined()
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('Validation multiple values tests', () => {
|
|
97
|
+
let task: TaskInstanceType
|
|
98
|
+
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
const schema = new Schema({
|
|
101
|
+
value: Schema.number(),
|
|
102
|
+
increment: Schema.number()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
task = new Task(function (argv: InferSchema<typeof schema>) {
|
|
106
|
+
return argv
|
|
107
|
+
}, {
|
|
108
|
+
schema
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('Should be on both invalid but fail in first', async () => {
|
|
113
|
+
try {
|
|
114
|
+
await task.run({ value: null })
|
|
115
|
+
// If we get here, the test should fail
|
|
116
|
+
expect('no error thrown').toBeUndefined()
|
|
117
|
+
} catch (e) {
|
|
118
|
+
// Test passes if we get here
|
|
119
|
+
expect(e).toBeDefined()
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('Should be on both invalid but fail in increment', async () => {
|
|
124
|
+
try {
|
|
125
|
+
await task.run({ value: 5 })
|
|
126
|
+
// If we get here, the test should fail
|
|
127
|
+
expect('no error thrown').toBeUndefined()
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// Test passes if we get here
|
|
130
|
+
expect(e).toBeDefined()
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('Should work well', async () => {
|
|
135
|
+
const result = await task.run({ value: 5, increment: 1 })
|
|
136
|
+
|
|
137
|
+
expect(result.value).toBe(5)
|
|
138
|
+
expect(result.increment).toBe(1)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('Get Schema', () => {
|
|
143
|
+
it('Object as string', async () => {
|
|
144
|
+
const add2 = new Task(function (int: number) {
|
|
145
|
+
return int + 2
|
|
146
|
+
}, {
|
|
147
|
+
schema: new Schema({
|
|
148
|
+
value: Schema.number()
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const schema = add2.getSchema()
|
|
153
|
+
const schemaDescription = schema?.describe() ?? {}
|
|
154
|
+
|
|
155
|
+
expect(JSON.stringify(schemaDescription)).toBe('{"value":{"type":"number"}}')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('Empty object as string', async () => {
|
|
159
|
+
const add2 = new Task(function (int: number) {
|
|
160
|
+
return int + 2
|
|
161
|
+
}, {})
|
|
162
|
+
|
|
163
|
+
const schema = add2.getSchema()
|
|
164
|
+
|
|
165
|
+
expect(schema).toBeUndefined()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('Set Schema', () => {
|
|
170
|
+
it('Object as string', async () => {
|
|
171
|
+
const add2 = new Task(function (int: number) {
|
|
172
|
+
return int + 2
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
add2.setSchema(new Schema({
|
|
176
|
+
value: Schema.number()
|
|
177
|
+
}))
|
|
178
|
+
|
|
179
|
+
const schema = add2.getSchema()
|
|
180
|
+
const schemaDescription = schema?.describe() ?? {}
|
|
181
|
+
|
|
182
|
+
expect(JSON.stringify(schemaDescription)).toBe('{"value":{"type":"number"}}')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('Empty object as string', async () => {
|
|
186
|
+
const add2 = new Task(function (int: number) {
|
|
187
|
+
return int + 2
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const schema = add2.getSchema()
|
|
191
|
+
|
|
192
|
+
expect(schema).toBeUndefined()
|
|
193
|
+
})
|
|
194
|
+
})
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as assert from 'assert'
|
|
2
|
+
|
|
3
|
+
// Define generic types for input and output
|
|
4
|
+
type BaseBoundary = (...args: unknown[]) => unknown
|
|
5
|
+
|
|
6
|
+
export type Mode = 'proxy' | 'proxy-pass' | 'proxy-catch' | 'replay'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents a boundary function that can be called within a task
|
|
10
|
+
* Using any here for compatibility with existing tests
|
|
11
|
+
* @template TReturn - The return type of the function
|
|
12
|
+
*/
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export type BoundaryFunction<TReturn = any> = (...args: any[]) => Promise<TReturn>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Represents a record of a boundary function call
|
|
18
|
+
* @template TInput - The type of input data
|
|
19
|
+
* @template TOutput - The type of output data
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
export interface BoundaryRecord<TInput = any[], TOutput = any> {
|
|
23
|
+
input: TInput
|
|
24
|
+
output?: TOutput
|
|
25
|
+
error?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Represents a wrapped boundary function with additional methods
|
|
30
|
+
*/
|
|
31
|
+
export interface WrappedBoundaryFunction<Func extends BoundaryFunction = BoundaryFunction> {
|
|
32
|
+
(...args: Parameters<Func>): Promise<ReturnType<Func>>
|
|
33
|
+
getTape: () => Array<BoundaryRecord<Parameters<Func>, Awaited<ReturnType<Func>>>>
|
|
34
|
+
setTape: (newTape: Array<BoundaryRecord<Parameters<Func>, Awaited<ReturnType<Func>>>>) => void
|
|
35
|
+
getMode: () => Mode
|
|
36
|
+
setMode: (newMode: Mode) => void
|
|
37
|
+
startRun: () => void
|
|
38
|
+
stopRun: () => void
|
|
39
|
+
getRunData: () => Array<BoundaryRecord<Parameters<Func>, Awaited<ReturnType<Func>>>>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Represents a collection of boundary functions
|
|
44
|
+
*/
|
|
45
|
+
export type Boundaries = Record<string, BoundaryFunction>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Represents a collection of wrapped boundary functions
|
|
49
|
+
*/
|
|
50
|
+
export type WrappedBoundaries<B extends Boundaries = Boundaries> = {
|
|
51
|
+
[K in keyof B]: WrappedBoundaryFunction<B[K]>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const createBoundary = <Func extends BaseBoundary>(fn: Func): WrappedBoundaryFunction<Func extends BoundaryFunction ? Func : never> => {
|
|
55
|
+
type FuncInput = Parameters<Func>;
|
|
56
|
+
type FuncOutput = Awaited<ReturnType<Func>>;
|
|
57
|
+
type RecordType = BoundaryRecord<FuncInput, FuncOutput>;
|
|
58
|
+
|
|
59
|
+
let runLog: RecordType[] = []
|
|
60
|
+
let cacheTape: RecordType[] = []
|
|
61
|
+
let mode: Mode = 'proxy'
|
|
62
|
+
let hasRun: boolean = false
|
|
63
|
+
|
|
64
|
+
const wrappedFn = async (...args: Parameters<Func>): Promise<ReturnType<Func>> => {
|
|
65
|
+
const findRecord = (record: FuncInput, tape: RecordType[]): RecordType | undefined => {
|
|
66
|
+
const result = tape.find((item) => {
|
|
67
|
+
if (typeof item === 'undefined') { return false }
|
|
68
|
+
|
|
69
|
+
let error
|
|
70
|
+
try {
|
|
71
|
+
assert.deepEqual(record, item.input)
|
|
72
|
+
} catch (e) {
|
|
73
|
+
error = e
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return typeof error === 'undefined'
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const record: RecordType = {
|
|
83
|
+
input: args
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (mode === 'proxy-pass') {
|
|
87
|
+
const record = findRecord(args, cacheTape)
|
|
88
|
+
|
|
89
|
+
if (typeof record !== 'undefined') {
|
|
90
|
+
return await (async (): Promise<ReturnType<Func>> => {
|
|
91
|
+
return record.output as unknown as ReturnType<Func>
|
|
92
|
+
})()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (mode === 'replay') {
|
|
97
|
+
return await (async (): Promise<ReturnType<Func>> => {
|
|
98
|
+
const record = findRecord(args, cacheTape)
|
|
99
|
+
|
|
100
|
+
if (typeof record === 'undefined') {
|
|
101
|
+
throw new Error('No tape value for this inputs')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof record.error !== 'undefined') {
|
|
105
|
+
throw new Error(record.error)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return record.output as unknown as ReturnType<Func>
|
|
109
|
+
})()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return await (async (): Promise<ReturnType<Func>> => {
|
|
113
|
+
let result, error: Error | undefined
|
|
114
|
+
try {
|
|
115
|
+
result = await fn(...args)
|
|
116
|
+
} catch (e) {
|
|
117
|
+
error = e as Error
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof error !== 'undefined') {
|
|
121
|
+
const prevRecord: RecordType | undefined = findRecord(args, cacheTape)
|
|
122
|
+
if (mode === 'proxy-catch' && typeof prevRecord !== 'undefined') {
|
|
123
|
+
return await (async (): Promise<ReturnType<Func>> => {
|
|
124
|
+
return prevRecord.output as unknown as ReturnType<Func>
|
|
125
|
+
})()
|
|
126
|
+
} else {
|
|
127
|
+
record.error = error.message
|
|
128
|
+
|
|
129
|
+
if (hasRun) { runLog.push(record) }
|
|
130
|
+
cacheTape.push(record)
|
|
131
|
+
|
|
132
|
+
throw error
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
record.output = result as FuncOutput
|
|
136
|
+
|
|
137
|
+
if (hasRun) { runLog.push(record) }
|
|
138
|
+
cacheTape.push(record)
|
|
139
|
+
|
|
140
|
+
return result as ReturnType<Func>
|
|
141
|
+
}
|
|
142
|
+
})()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// tape cache
|
|
146
|
+
wrappedFn.getTape = function (): Array<RecordType> {
|
|
147
|
+
return cacheTape
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
wrappedFn.setTape = function (newTape: Array<RecordType>): void {
|
|
151
|
+
cacheTape = newTape
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Mode
|
|
155
|
+
wrappedFn.getMode = function (): Mode {
|
|
156
|
+
return mode
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
wrappedFn.setMode = function (newMode: Mode): void {
|
|
160
|
+
mode = newMode
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// run log
|
|
164
|
+
wrappedFn.startRun = function (): void {
|
|
165
|
+
runLog = []
|
|
166
|
+
hasRun = true
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
wrappedFn.stopRun = function (): void {
|
|
170
|
+
hasRun = false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
wrappedFn.getRunData = function (): Array<RecordType> {
|
|
174
|
+
return runLog
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return wrappedFn as unknown as WrappedBoundaryFunction<Func extends BoundaryFunction ? Func : never>
|
|
178
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2017",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"moduleResolution": "node",
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"baseUrl": ".",
|
|
18
|
+
"paths": {
|
|
19
|
+
"@/*": ["./src/*"]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"],
|
|
23
|
+
"exclude": ["node_modules", "dist/*"]
|
|
24
|
+
}
|