@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 @@
1
+ {"version":3,"file":"boundary.js","sourceRoot":"","sources":["../../src/utils/boundary.ts"],"names":[],"mappings":"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"}
package/jest.config.js ADDED
@@ -0,0 +1,9 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ testMatch: ['**/test/**/*.test.ts'],
6
+ moduleNameMapper: {
7
+ '^@/(.*)$': '<rootDir>/src/$1'
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@forgehive/task",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "dependencies": {
11
+ "@forgehive/schema": "^0.1.0"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@forgehive/schema": "0.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/jest": "^29.5.12",
19
+ "@types/node": "^20.11.19",
20
+ "jest": "^29.7.0",
21
+ "ts-jest": "^29.1.2",
22
+ "typescript": "^5.3.3"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "dev": "tsc --watch",
27
+ "clean": "rm -rf dist",
28
+ "test": "jest"
29
+ }
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,344 @@
1
+ import { Schema, type SchemaType, type InferSchema } from '@shadow/schema'
2
+ import { createBoundary, type Mode, type Boundaries, type WrappedBoundaries, type WrappedBoundaryFunction } from './utils/boundary'
3
+
4
+ export interface Task {
5
+ id: string;
6
+ title: string;
7
+ completed: boolean;
8
+ }
9
+
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ export type BaseFunction = (...args: any[]) => any
12
+
13
+ // Re-export the boundary types for external use
14
+ export type { BoundaryFunction, WrappedBoundaryFunction, Boundaries, WrappedBoundaries, Mode } from './utils/boundary'
15
+
16
+ // Re-export Schema for external use
17
+ export { Schema }
18
+
19
+ export interface TaskConfig<B extends Boundaries = Boundaries> {
20
+ schema?: Schema<Record<string, SchemaType>>
21
+ mode?: Mode
22
+ boundaries?: B
23
+ boundariesData?: Record<string, unknown>
24
+ }
25
+
26
+ /**
27
+ * Represents the record passed to task listeners
28
+ */
29
+ export interface TaskRecord<InputType = unknown, OutputType = unknown> {
30
+ /** The input arguments passed to the task */
31
+ input: InputType;
32
+ /** The output returned by the task (if successful) */
33
+ output?: OutputType;
34
+ /** The error message if the task failed */
35
+ error?: string;
36
+ /** Boundary execution data */
37
+ boundaries?: Record<string, unknown>;
38
+ }
39
+
40
+ export interface TaskInstanceType<Func extends BaseFunction = BaseFunction, B extends Boundaries = Boundaries> {
41
+ getMode: () => Mode
42
+ setMode: (mode: Mode) => void
43
+ setSchema: (base: Schema<Record<string, SchemaType>>) => void
44
+ getSchema: () => Schema<Record<string, SchemaType>> | undefined
45
+
46
+ // Validation methos
47
+ validate: <T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T) => ReturnType<Schema<Record<string, SchemaType>>['safeParse']> | undefined
48
+ isValid: <T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T) => boolean
49
+
50
+ // Listener methods
51
+ addListener: <I = Parameters<Func>[0], O = ReturnType<Func>>(fn: (record: TaskRecord<I, O>) => void) => void
52
+ removeListener: () => void
53
+ emit: (data: Partial<TaskRecord>) => void
54
+
55
+ // Boundary methods
56
+ asBoundary: () => (args: Parameters<Func>[0]) => Promise<ReturnType<Func>>
57
+ getBoundaries: () => WrappedBoundaries<B>
58
+ setBoundariesData: (boundariesData: Record<string, unknown>) => void
59
+ getBondariesData: () => Record<string, unknown>
60
+ getBondariesRunLog: () => Record<string, unknown>
61
+ startRunLog: () => void
62
+ run: (argv?: Parameters<Func>[0]) => Promise<ReturnType<Func>>
63
+ }
64
+
65
+ // Helper type to infer schema type
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ export type InferSchemaType<S> = S extends Schema<any> ? InferSchema<S> : Record<string, unknown>;
68
+
69
+ // Helper type for task function with proper typing
70
+ export type TaskFunction<S, B extends Boundaries> =
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ (argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B>) => Promise<any>;
73
+
74
+ export const Task = class Task<
75
+ B extends Boundaries = Boundaries,
76
+ Func extends BaseFunction = BaseFunction
77
+ > implements TaskInstanceType<Func, B> {
78
+ _fn: Func
79
+ _mode: Mode
80
+ _coolDown: number
81
+
82
+ _boundariesDefinition: B
83
+ _boundaries: WrappedBoundaries<B>
84
+ _boundariesData: Record<string, unknown> | null
85
+
86
+ _schema: Schema<Record<string, SchemaType>> | undefined
87
+ _listener?: ((record: TaskRecord<Parameters<Func>[0], ReturnType<Func>>) => void) | undefined
88
+
89
+ constructor (fn: Func, conf: TaskConfig<B> = {
90
+ schema: undefined,
91
+ mode: 'proxy',
92
+ boundaries: undefined,
93
+ boundariesData: undefined
94
+ }) {
95
+ this._fn = fn
96
+ this._schema = undefined
97
+ if (typeof conf.schema !== 'undefined') {
98
+ this._schema = conf.schema
99
+ }
100
+
101
+ this._mode = conf.mode ?? 'proxy'
102
+ this._boundariesDefinition = conf.boundaries ?? {} as B
103
+
104
+ this._listener = undefined
105
+
106
+ // Cool down time before killing the process on cli runner
107
+ this._coolDown = 1000
108
+
109
+ // Review this assignment
110
+ this._boundariesData = conf.boundariesData ?? null
111
+ this._boundaries = this._createBounderies({
112
+ definition: this._boundariesDefinition,
113
+ baseData: this._boundariesData,
114
+ mode: this._mode
115
+ })
116
+ }
117
+
118
+ getMode (): Mode {
119
+ return this._mode
120
+ }
121
+
122
+ setMode (mode: Mode): void {
123
+ for (const name in this._boundaries) {
124
+ const boundary = this._boundaries[name]
125
+
126
+ boundary.setMode(mode)
127
+ }
128
+
129
+ this._mode = mode
130
+ }
131
+
132
+ setSchema (schema: Schema<Record<string, SchemaType>>): void {
133
+ this._schema = schema
134
+ }
135
+
136
+ getSchema (): Schema<Record<string, SchemaType>> | undefined {
137
+ return this._schema
138
+ }
139
+
140
+ validate<T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T): ReturnType<Schema<Record<string, SchemaType>>['safeParse']> | undefined {
141
+ if (typeof this._schema === 'undefined') {
142
+ return undefined
143
+ }
144
+
145
+ const result = this._schema.safeParse(argv)
146
+
147
+ return result
148
+ }
149
+
150
+ isValid<T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T): boolean {
151
+ if (typeof this._schema === 'undefined') {
152
+ return true
153
+ }
154
+
155
+ const result = this._schema.safeParse(argv)
156
+ return result.success ?? false
157
+ }
158
+
159
+ // Posible improvement to handle multiple listeners, but so far its not needed
160
+ addListener<I = Parameters<Func>[0], O = ReturnType<Func>>(fn: (record: TaskRecord<I, O>) => void): void {
161
+ this._listener = fn as (record: TaskRecord<Parameters<Func>[0], ReturnType<Func>>) => void
162
+ }
163
+
164
+ removeListener (): void {
165
+ this._listener = undefined
166
+ }
167
+
168
+ /*
169
+ The listener get the input/outout of the call
170
+ Plus all the boundary data
171
+ */
172
+ emit (data: Partial<TaskRecord>): void {
173
+ if (typeof this._listener === 'undefined') { return }
174
+
175
+ const event = {
176
+ ...data,
177
+ boundaries: this.getBondariesRunLog()
178
+ } as TaskRecord<Parameters<Func>[0], ReturnType<Func>>
179
+
180
+ this._listener(event)
181
+ }
182
+
183
+ getBoundaries (): WrappedBoundaries<B> {
184
+ return this._boundaries
185
+ }
186
+
187
+ setBoundariesData (boundariesData: Record<string, unknown>): void {
188
+ for (const name in this._boundaries) {
189
+ const boundary = this._boundaries[name]
190
+
191
+ let tape
192
+ if (typeof boundariesData !== 'undefined') {
193
+ tape = boundariesData[name]
194
+ }
195
+
196
+ if (typeof boundary !== 'undefined' && typeof tape !== 'undefined') {
197
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
198
+ boundary.setTape(tape as any)
199
+ }
200
+ }
201
+ }
202
+
203
+ getBondariesData (): Record<string, unknown> {
204
+ const boundaries = this._boundaries
205
+ const boundariesData: Record<string, unknown> = {}
206
+
207
+ for (const name in boundaries) {
208
+ const boundary = boundaries[name]
209
+
210
+ boundariesData[name] = boundary.getTape()
211
+ }
212
+
213
+ return boundariesData
214
+ }
215
+
216
+ _createBounderies ({
217
+ definition,
218
+ baseData,
219
+ mode = 'proxy'
220
+ }: {
221
+ definition: B;
222
+ baseData: Record<string, unknown> | null;
223
+ mode?: Mode;
224
+ }): WrappedBoundaries<B> {
225
+ const boundariesFns: Record<string, WrappedBoundaryFunction> = {}
226
+
227
+ for (const name in definition) {
228
+ const boundary = createBoundary(definition[name])
229
+
230
+ if (baseData !== null && typeof baseData[name] !== 'undefined') {
231
+ const tape = baseData[name]
232
+
233
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
+ boundary.setTape(tape as any)
235
+ }
236
+ boundary.setMode(mode as Mode)
237
+
238
+ boundariesFns[name] = boundary
239
+ }
240
+
241
+ return boundariesFns as WrappedBoundaries<B>
242
+ }
243
+
244
+ getBondariesRunLog (): Record<string, unknown> {
245
+ const boundaries = this._boundaries
246
+ const boundariesRunLog: Record<string, unknown> = {}
247
+
248
+ for (const name in boundaries) {
249
+ const boundary = boundaries[name]
250
+
251
+ boundariesRunLog[name] = boundary.getRunData()
252
+ }
253
+
254
+ return boundariesRunLog
255
+ }
256
+
257
+ startRunLog (): void {
258
+ const boundaries = this._boundaries
259
+
260
+ for (const name in boundaries) {
261
+ const boundary = boundaries[name]
262
+
263
+ boundary.startRun()
264
+ }
265
+ }
266
+
267
+ asBoundary (): (args: Parameters<Func>[0]) => Promise<ReturnType<Func>> {
268
+ return async (args: Parameters<Func>[0]): Promise<ReturnType<Func>> => {
269
+ return await this.run(args)
270
+ }
271
+ }
272
+
273
+ async run (argv?: Parameters<Func>[0]): Promise<ReturnType<Func>> {
274
+ // start run log
275
+ this.startRunLog()
276
+ const boundaries = this._boundaries
277
+
278
+ const q = new Promise<ReturnType<Func>>((resolve, reject) => {
279
+ const isValid = this.isValid(argv)
280
+
281
+ if (!isValid) {
282
+ this.emit({
283
+ input: argv,
284
+ error: 'Invalid input'
285
+ })
286
+
287
+ throw new Error('Invalid input')
288
+ }
289
+
290
+ (async (): Promise<ReturnType<Func>> => {
291
+ // Use proper typing for the function call
292
+ const output = await this._fn(argv as Parameters<Func>[0], boundaries as unknown as Parameters<Func>[1])
293
+
294
+ return output
295
+ })().then((output) => {
296
+ this.emit({
297
+ input: argv,
298
+ output
299
+ })
300
+
301
+ resolve(output)
302
+ }).catch((error) => {
303
+ this.emit({
304
+ input: argv,
305
+ error: error.message
306
+ })
307
+
308
+ reject(error)
309
+ })
310
+ })
311
+
312
+ const result = await q
313
+
314
+ return result
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Helper function to create a task with proper type inference
320
+ * @param schema The schema to validate input against
321
+ * @param boundaries The boundaries to use
322
+ * @param fn The task function
323
+ * @param config Additional task configuration
324
+ * @returns A new Task instance with proper type inference
325
+ */
326
+ export function createTask<
327
+ S extends Schema<Record<string, SchemaType>>,
328
+ B extends Boundaries,
329
+ R
330
+ >(
331
+ schema: S,
332
+ boundaries: B,
333
+ fn: (argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B>) => Promise<R>,
334
+ config?: Omit<TaskConfig<B>, 'schema' | 'boundaries'>
335
+ ): TaskInstanceType<(argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B>) => Promise<R>, B> {
336
+ return new Task(
337
+ fn,
338
+ {
339
+ schema,
340
+ boundaries,
341
+ ...config
342
+ }
343
+ )
344
+ }
@@ -0,0 +1,299 @@
1
+ import { type TaskRecord, createTask, Schema } from '../index'
2
+
3
+ describe('Listener with boundaries tests', () => {
4
+ it('Should record one item and its boundaries tape', async () => {
5
+ const tape: TaskRecord<{ value: number }, { value: number, foo: boolean }>[] = []
6
+
7
+ // Create a schema for the task
8
+ const schema = new Schema({
9
+ value: Schema.number()
10
+ })
11
+
12
+ // Define the boundaries
13
+ const boundaries = {
14
+ fetchExternalData: async (): Promise<{ foo: boolean }> => {
15
+ return { foo: false }
16
+ }
17
+ }
18
+
19
+ // Create the task using createTask
20
+ const task = createTask(
21
+ schema,
22
+ boundaries,
23
+ async (argv, boundaries) => {
24
+ const externalData = await boundaries.fetchExternalData()
25
+ return { ...externalData, ...argv }
26
+ }
27
+ )
28
+
29
+ task.addListener<{ value: number }, { value: number, foo: boolean }>((record) => {
30
+ tape.push(record)
31
+ })
32
+
33
+ await task.run({ value: 5 })
34
+
35
+ expect(tape.length).toBe(1)
36
+ expect(tape[0].input).toEqual({ value: 5 })
37
+ expect(tape[0].output).toEqual({ value: 5, foo: false })
38
+ expect(tape[0].boundaries).toEqual({
39
+ fetchExternalData: [
40
+ { input: [], output: { foo: false } }
41
+ ]
42
+ })
43
+ })
44
+
45
+ it('Should record multiple items and their boundaries tape', async () => {
46
+ const tape: TaskRecord<{ value: number }, { value: number, foo: boolean }>[] = []
47
+
48
+ // Create a schema for the task
49
+ const schema = new Schema({
50
+ value: Schema.number()
51
+ })
52
+
53
+ // Define the boundaries
54
+ const boundaries = {
55
+ fetchExternalData: async (): Promise<{ foo: boolean }> => {
56
+ return { foo: false }
57
+ }
58
+ }
59
+
60
+ // Create the task using createTask
61
+ const task = createTask(
62
+ schema,
63
+ boundaries,
64
+ async (argv, boundaries) => {
65
+ const externalData = await boundaries.fetchExternalData()
66
+ return { ...externalData, ...argv }
67
+ }
68
+ )
69
+
70
+ task.addListener<{ value: number }, { value: number, foo: boolean }>((record) => {
71
+ tape.push(record)
72
+ })
73
+
74
+ await task.run({ value: 5 })
75
+ await task.run({ value: 6 })
76
+
77
+ expect(tape.length).toBe(2)
78
+
79
+ expect(tape[0].input).toEqual({ value: 5 })
80
+ expect(tape[0].output).toEqual({ value: 5, foo: false })
81
+ expect(tape[0].boundaries).toEqual({
82
+ fetchExternalData: [
83
+ { input: [], output: { foo: false } }
84
+ ]
85
+ })
86
+
87
+ expect(tape[1].input).toEqual({ value: 6 })
88
+ expect(tape[1].output).toEqual({ value: 6, foo: false })
89
+ expect(tape[1].boundaries).toEqual({
90
+ fetchExternalData: [
91
+ { input: [], output: { foo: false } }
92
+ ]
93
+ })
94
+ })
95
+
96
+ it('Should record error and its boundaries tape', async () => {
97
+ const tape: TaskRecord<Record<string, unknown>, { value: number, foo: boolean }>[] = []
98
+
99
+ // Create a schema for the task
100
+ const schema = new Schema({
101
+ value: Schema.number().optional()
102
+ })
103
+
104
+ // Define the boundaries
105
+ const boundaries = {
106
+ fetchExternalData: async (): Promise<{ foo: boolean }> => {
107
+ return { foo: false }
108
+ }
109
+ }
110
+
111
+ // Create the task using createTask
112
+ const task = createTask(
113
+ schema,
114
+ boundaries,
115
+ async (argv, boundaries) => {
116
+ const externalData = await boundaries.fetchExternalData()
117
+ if (typeof argv.value === 'undefined') {
118
+ throw new Error('Value is required')
119
+ }
120
+
121
+ return { ...externalData, ...argv as { value: number } }
122
+ }
123
+ )
124
+
125
+ task.addListener<Record<string, unknown>, { value: number, foo: boolean }>((record) => {
126
+ tape.push(record)
127
+ })
128
+
129
+ try {
130
+ await task.run({})
131
+ } catch (e) {
132
+ // Error is expected
133
+ }
134
+
135
+ expect(tape.length).toBe(1)
136
+ expect(tape[0].input).toEqual({})
137
+ expect(tape[0].error).toBe('Value is required')
138
+ expect(tape[0].boundaries).toEqual({
139
+ fetchExternalData: [
140
+ { input: [], output: { foo: false } }
141
+ ]
142
+ })
143
+ })
144
+
145
+ it('Should record error + success and their boundaries tape', async () => {
146
+ const tape: TaskRecord<{ value?: number }, { value: number, foo: boolean }>[] = []
147
+
148
+ // Create a schema for the task
149
+ const schema = new Schema({
150
+ value: Schema.number().optional()
151
+ })
152
+
153
+ // Define the boundaries
154
+ const boundaries = {
155
+ fetchExternalData: async (): Promise<{ foo: boolean }> => {
156
+ return { foo: false }
157
+ }
158
+ }
159
+
160
+ // Create the task using createTask
161
+ const task = createTask(
162
+ schema,
163
+ boundaries,
164
+ async (argv, boundaries) => {
165
+ const externalData = await boundaries.fetchExternalData()
166
+ if (typeof argv.value === 'undefined') {
167
+ throw new Error('Value is required')
168
+ }
169
+
170
+ return { ...externalData, ...argv as { value: number } }
171
+ }
172
+ )
173
+
174
+ task.addListener<{ value?: number }, { value: number, foo: boolean }>((record) => {
175
+ tape.push(record)
176
+ })
177
+
178
+ try {
179
+ await task.run({})
180
+ } catch (e) {
181
+ // Error is expected
182
+ }
183
+ await task.run({ value: 5 })
184
+
185
+ expect(tape.length).toBe(2)
186
+ expect(tape[0].input).toEqual({})
187
+ expect(tape[0].error).toBe('Value is required')
188
+ expect(tape[0].boundaries).toEqual({
189
+ fetchExternalData: [
190
+ { input: [], output: { foo: false } }
191
+ ]
192
+ })
193
+
194
+ expect(tape[1].input).toEqual({ value: 5 })
195
+ expect(tape[1].output).toEqual({ value: 5, foo: false })
196
+ expect(tape[1].boundaries).toEqual({
197
+ fetchExternalData: [
198
+ { input: [], output: { foo: false } }
199
+ ]
200
+ })
201
+ })
202
+
203
+ it('Should record 2 run logs if boundary called twice', async () => {
204
+ const tape: TaskRecord<{ value: number }, { foo: boolean }>[] = []
205
+
206
+ // Create a schema for the task
207
+ const schema = new Schema({
208
+ value: Schema.number()
209
+ })
210
+
211
+ // Define the boundaries
212
+ const boundaries = {
213
+ fetchExternalData: async (): Promise<{ foo: boolean }> => {
214
+ return { foo: false }
215
+ }
216
+ }
217
+
218
+ // Create the task using createTask
219
+ const task = createTask(
220
+ schema,
221
+ boundaries,
222
+ async (argv, boundaries) => {
223
+ await boundaries.fetchExternalData()
224
+ await boundaries.fetchExternalData()
225
+
226
+ return { foo: true }
227
+ }
228
+ )
229
+
230
+ task.addListener<{ value: number }, { foo: boolean }>((record) => {
231
+ tape.push(record)
232
+ })
233
+
234
+ await task.run({ value: 5 })
235
+
236
+ expect(tape.length).toBe(1)
237
+ expect(tape[0].input).toEqual({ value: 5 })
238
+ expect(tape[0].output).toEqual({ foo: true })
239
+ expect(tape[0].boundaries).toEqual({
240
+ fetchExternalData: [
241
+ { input: [], output: { foo: false } },
242
+ { input: [], output: { foo: false } }
243
+ ]
244
+ })
245
+ })
246
+
247
+ it('Should record 2 boundary logs', async () => {
248
+ const tape: TaskRecord<{ value: number }, number>[] = []
249
+
250
+ // Create a schema for the task
251
+ const schema = new Schema({
252
+ value: Schema.number()
253
+ })
254
+
255
+ // Define the boundaries
256
+ const boundaries = {
257
+ add: async (value: number): Promise<number> => {
258
+ return value + 1
259
+ },
260
+ subtract: async (value: number): Promise<number> => {
261
+ return value - 1
262
+ }
263
+ }
264
+
265
+ // Create the task using createTask
266
+ const task = createTask(
267
+ schema,
268
+ boundaries,
269
+ async (argv, boundaries) => {
270
+ let counter = argv.value
271
+
272
+ counter = await boundaries.add(counter)
273
+ counter = await boundaries.subtract(counter)
274
+ counter = await boundaries.subtract(counter)
275
+
276
+ return counter
277
+ }
278
+ )
279
+
280
+ task.addListener<{ value: number }, number>((record) => {
281
+ tape.push(record)
282
+ })
283
+
284
+ await task.run({ value: 5 })
285
+
286
+ expect(tape.length).toBe(1)
287
+ expect(tape[0].input).toEqual({ value: 5 })
288
+ expect(tape[0].output).toBe(4)
289
+ expect(tape[0].boundaries).toEqual({
290
+ add: [
291
+ { input: [5], output: 6 }
292
+ ],
293
+ subtract: [
294
+ { input: [6], output: 5 },
295
+ { input: [5], output: 4 }
296
+ ]
297
+ })
298
+ })
299
+ })