@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 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.js","sourceRoot":"","sources":["../../src/utils/boundary.ts"],"names":[],"mappings":"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"}
|
package/jest.config.js
ADDED
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
|
+
})
|