@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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -0
  3. package/dist/index.d.ts +92 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +191 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/test/add-listener-with-boundaries.test.d.ts +2 -0
  8. package/dist/test/add-listener-with-boundaries.test.d.ts.map +1 -0
  9. package/dist/test/add-listener-with-boundaries.test.js +230 -0
  10. package/dist/test/add-listener-with-boundaries.test.js.map +1 -0
  11. package/dist/test/add-listener.test.d.ts +2 -0
  12. package/dist/test/add-listener.test.d.ts.map +1 -0
  13. package/dist/test/add-listener.test.js +92 -0
  14. package/dist/test/add-listener.test.js.map +1 -0
  15. package/dist/test/boundary-modes.test.d.ts +2 -0
  16. package/dist/test/boundary-modes.test.d.ts.map +1 -0
  17. package/dist/test/boundary-modes.test.js +184 -0
  18. package/dist/test/boundary-modes.test.js.map +1 -0
  19. package/dist/test/change-mode.test.d.ts +2 -0
  20. package/dist/test/change-mode.test.d.ts.map +1 -0
  21. package/dist/test/change-mode.test.js +39 -0
  22. package/dist/test/change-mode.test.js.map +1 -0
  23. package/dist/test/index.test.d.ts +2 -0
  24. package/dist/test/index.test.d.ts.map +1 -0
  25. package/dist/test/index.test.js +48 -0
  26. package/dist/test/index.test.js.map +1 -0
  27. package/dist/test/run-boundary.test.d.ts +2 -0
  28. package/dist/test/run-boundary.test.d.ts.map +1 -0
  29. package/dist/test/run-boundary.test.js +49 -0
  30. package/dist/test/run-boundary.test.js.map +1 -0
  31. package/dist/test/run-without-args.test.d.ts +2 -0
  32. package/dist/test/run-without-args.test.d.ts.map +1 -0
  33. package/dist/test/run-without-args.test.js +29 -0
  34. package/dist/test/run-without-args.test.js.map +1 -0
  35. package/dist/test/task-with-boundaries.test.d.ts +2 -0
  36. package/dist/test/task-with-boundaries.test.d.ts.map +1 -0
  37. package/dist/test/task-with-boundaries.test.js +102 -0
  38. package/dist/test/task-with-boundaries.test.js.map +1 -0
  39. package/dist/test/validation.test.d.ts +2 -0
  40. package/dist/test/validation.test.d.ts.map +1 -0
  41. package/dist/test/validation.test.js +166 -0
  42. package/dist/test/validation.test.js.map +1 -0
  43. package/dist/utils/boundary.d.ts +44 -0
  44. package/dist/utils/boundary.d.ts.map +1 -0
  45. package/dist/utils/boundary.js +145 -0
  46. package/dist/utils/boundary.js.map +1 -0
  47. package/jest.config.js +9 -0
  48. package/package.json +30 -0
  49. package/src/index.ts +344 -0
  50. package/src/test/add-listener-with-boundaries.test.ts +299 -0
  51. package/src/test/add-listener.test.ts +110 -0
  52. package/src/test/boundary-modes.test.ts +215 -0
  53. package/src/test/change-mode.test.ts +45 -0
  54. package/src/test/index.test.ts +62 -0
  55. package/src/test/run-boundary.test.ts +64 -0
  56. package/src/test/run-without-args.test.ts +33 -0
  57. package/src/test/task-with-boundaries.test.ts +137 -0
  58. package/src/test/validation.test.ts +194 -0
  59. package/src/utils/boundary.ts +178 -0
  60. 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
+ }