Last active
April 23, 2025 00:18
-
-
Save Siedrix/c70f1297f96f77668ef4fbe289eb731a to your computer and use it in GitHub Desktop.
Mini forge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { TaskInstanceType } from './lib/task'; | |
import { getPrice } from './tasks/stocks/getPrice'; | |
const tasks: Record<string, TaskInstanceType> = { | |
foo: getPrice | |
} | |
async function main() { | |
const params = process.argv.slice(2); | |
const taskName = params[0]; | |
const args = JSON.parse(params[1]); | |
console.log('Running with:', taskName, args); | |
const task = tasks[taskName]; | |
if (!task) { | |
console.error('Task not found'); | |
process.exit(1); | |
} | |
try { | |
const result = await task.run(args); | |
console.log('Result:', result); | |
} catch (error) { | |
console.error('Error:', error); | |
process.exit(1); | |
} | |
const log = task.getLog(); | |
console.log('================================'); | |
console.log('Log:', log); | |
console.log('================================'); | |
} | |
main().catch(console.error); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createTask } from '../../lib/task-with-boundaries' | |
import { z } from 'zod' | |
const zodSchema = z.object({ | |
ticker: z.string() | |
}) | |
const boundaries = { | |
// Add your boundary functions here | |
fetchYahooFinance: async (ticker: string) => { | |
const url = `https://query1.finance.yahoo.com/v8/finance/chart/${ticker}` | |
const response = await fetch(url) | |
const data = await response.json() | |
const quote = data.chart.result[0].meta | |
const price = quote.regularMarketPrice | |
const currency = quote.currency | |
return { | |
price, | |
currency | |
} | |
} | |
} | |
export const getPrice = createTask( | |
zodSchema, | |
boundaries, | |
async function ({ ticker }, { fetchYahooFinance }) { | |
const { price, currency } = await fetchYahooFinance(ticker) | |
console.log('================================'); | |
console.log('Price:', price); | |
console.log('================================'); | |
return { | |
status: 'Ok', | |
ticker, | |
price, | |
currency, | |
timestamp: new Date().toISOString() | |
} | |
} | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add the task and task with boundaries on the src/lib folder | |
Add the cli on the src folder | |
Create a tsconfig of something like | |
{ | |
"compilerOptions": { | |
"target": "ES2020", | |
"module": "commonjs", | |
"declaration": true, | |
"outDir": "./dist", | |
"strict": true, | |
"esModuleInterop": true, | |
"skipLibCheck": true, | |
"forceConsistentCasingInFileNames": true, | |
"resolveJsonModule": true | |
}, | |
"include": ["src/**/*"], | |
"exclude": ["node_modules"] | |
} | |
Then do `ts-node src/cli.ts foo '{"ticker": "AAPL"}'` | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from 'zod'; | |
// Basic type definitions | |
export type BaseFunction = (...args: any[]) => any; | |
// Simplified boundary types | |
export type BoundaryFunction<TReturn = any> = (...args: any[]) => Promise<TReturn>; | |
export type Boundaries = Record<string, BoundaryFunction>; | |
/** | |
* Represents a record of a boundary function call | |
*/ | |
export interface BoundaryRecord<TInput = any[], TOutput = any> { | |
input: TInput; | |
output?: TOutput; | |
error?: string; | |
} | |
/** | |
* Represents a wrapped boundary function with additional methods | |
*/ | |
export interface WrappedBoundaryFunction<Func extends BoundaryFunction = BoundaryFunction> { | |
(...args: Parameters<Func>): Promise<ReturnType<Func>>; | |
getRunData: () => Array<BoundaryRecord<Parameters<Func>, Awaited<ReturnType<Func>>>>; | |
startRun: () => void; | |
} | |
/** | |
* Represents a collection of wrapped boundary functions | |
*/ | |
export type WrappedBoundaries<B extends Boundaries = Boundaries> = { | |
[K in keyof B]: WrappedBoundaryFunction<B[K]> | |
}; | |
/** | |
* Creates a simplified boundary wrapper | |
*/ | |
export function createBoundary<Func extends BoundaryFunction>(fn: Func): WrappedBoundaryFunction<Func> { | |
type FuncInput = Parameters<Func>; | |
type FuncOutput = Awaited<ReturnType<Func>>; | |
type RecordType = BoundaryRecord<FuncInput, FuncOutput>; | |
let runLog: RecordType[] = []; | |
let hasRun: boolean = false; | |
const wrappedFn = async (...args: Parameters<Func>): Promise<ReturnType<Func>> => { | |
const record: RecordType = { | |
input: args | |
}; | |
try { | |
const result = await fn(...args); | |
record.output = result as FuncOutput; | |
if (hasRun) { | |
runLog.push(record); | |
} | |
return result; | |
} catch (error: any) { | |
record.error = error.message; | |
if (hasRun) { | |
runLog.push(record); | |
} | |
throw error; | |
} | |
}; | |
// Run log management | |
wrappedFn.startRun = function(): void { | |
runLog = []; | |
hasRun = true; | |
}; | |
wrappedFn.getRunData = function(): Array<RecordType> { | |
return runLog; | |
}; | |
return wrappedFn; | |
} | |
/** | |
* Represents the record of a task execution | |
*/ | |
export interface TaskRecord<InputType = unknown, OutputType = unknown> { | |
/** The input arguments passed to the task */ | |
input: InputType; | |
/** The output returned by the task (if successful) */ | |
output?: OutputType; | |
/** The error message if the task failed */ | |
error?: string; | |
/** Timestamp of when this task was executed */ | |
timestamp: number; | |
/** Boundary execution data */ | |
boundaries?: Record<string, unknown>; | |
} | |
export interface TaskInstanceType<Func extends BaseFunction = BaseFunction, B extends Boundaries = Boundaries> { | |
// Run the task | |
run: (argv?: Parameters<Func>[0]) => Promise<ReturnType<Func>>; | |
// Validation methods | |
validate: <T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T) => z.SafeParseReturnType<T, T> | undefined; | |
isValid: <T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T) => boolean; | |
// Logging methods | |
getLog: () => TaskRecord<Parameters<Func>[0], ReturnType<Func>> | undefined; | |
// Listener methods | |
addListener: <I = Parameters<Func>[0], O = ReturnType<Func>>(fn: (record: TaskRecord<I, O>) => void) => void; | |
removeListener: () => void; | |
// Boundary methods | |
getBoundaries: () => WrappedBoundaries<B>; | |
setBoundariesData: (data: Record<string, unknown>) => void; | |
getBoundariesRunLog: () => Record<string, unknown>; | |
} | |
export class Task< | |
Func extends BaseFunction = BaseFunction, | |
B extends Boundaries = Boundaries | |
> implements TaskInstanceType<Func, B> { | |
private _fn: Func; | |
private _schema?: z.ZodType; | |
private _listener?: (record: TaskRecord<Parameters<Func>[0], ReturnType<Func>>) => void; | |
private _lastLog?: TaskRecord<Parameters<Func>[0], ReturnType<Func>>; | |
private _boundaries: WrappedBoundaries<B>; | |
constructor( | |
fn: Func, | |
config: { | |
schema?: z.ZodType; | |
boundaries?: B; | |
boundariesData?: Record<string, unknown>; | |
} = {} | |
) { | |
this._fn = fn; | |
this._schema = config.schema; | |
this._boundaries = this._createBoundaries(config.boundaries || {} as B); | |
// Set boundaries data if provided | |
if (config.boundariesData) { | |
this.setBoundariesData(config.boundariesData); | |
} | |
} | |
/** | |
* Create wrapped boundaries from boundary definitions | |
*/ | |
private _createBoundaries(definition: B): WrappedBoundaries<B> { | |
const boundariesFns: Record<string, WrappedBoundaryFunction> = {}; | |
for (const name in definition) { | |
boundariesFns[name] = createBoundary(definition[name]); | |
} | |
return boundariesFns as WrappedBoundaries<B>; | |
} | |
/** | |
* Get the wrapped boundaries | |
*/ | |
getBoundaries(): WrappedBoundaries<B> { | |
return this._boundaries; | |
} | |
/** | |
* Set boundaries data (for backward compatibility) | |
* Note: In this simplified version, this is a no-op | |
*/ | |
setBoundariesData(data: Record<string, unknown>): void { | |
// This is kept for backward compatibility | |
// In a simplified version, we don't need to do anything with this data | |
} | |
/** | |
* Get the run log data from all boundaries | |
*/ | |
getBoundariesRunLog(): Record<string, unknown> { | |
const boundaries = this._boundaries; | |
const boundariesRunLog: Record<string, unknown> = {}; | |
for (const name in boundaries) { | |
const boundary = boundaries[name]; | |
boundariesRunLog[name] = boundary.getRunData(); | |
} | |
return boundariesRunLog; | |
} | |
/** | |
* Start the run log for all boundaries | |
*/ | |
private startRunLog(): void { | |
const boundaries = this._boundaries; | |
for (const name in boundaries) { | |
const boundary = boundaries[name]; | |
boundary.startRun(); | |
} | |
} | |
/** | |
* Validate input against the schema | |
*/ | |
validate<T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T): z.SafeParseReturnType<T, T> | undefined { | |
if (!this._schema) { | |
return undefined; | |
} | |
return this._schema.safeParse(argv) as z.SafeParseReturnType<T, T>; | |
} | |
/** | |
* Check if the input is valid according to the schema | |
*/ | |
isValid<T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T): boolean { | |
if (!this._schema) { | |
return true; | |
} | |
const result = this._schema.safeParse(argv); | |
return result.success; | |
} | |
/** | |
* Add a listener for task execution events | |
*/ | |
addListener<I = Parameters<Func>[0], O = ReturnType<Func>>(fn: (record: TaskRecord<I, O>) => void): void { | |
this._listener = fn as (record: TaskRecord<Parameters<Func>[0], ReturnType<Func>>) => void; | |
} | |
/** | |
* Remove the current listener | |
*/ | |
removeListener(): void { | |
this._listener = undefined; | |
} | |
/** | |
* Get the last execution log | |
*/ | |
getLog(): TaskRecord<Parameters<Func>[0], ReturnType<Func>> | undefined { | |
return this._lastLog; | |
} | |
/** | |
* Emit a task execution event | |
*/ | |
private emit(data: Partial<TaskRecord>): void { | |
const record = { | |
...data, | |
timestamp: Date.now(), | |
boundaries: this.getBoundariesRunLog() | |
} as TaskRecord<Parameters<Func>[0], ReturnType<Func>>; | |
// Store the log | |
this._lastLog = record; | |
// Notify listener if exists | |
if (this._listener) { | |
this._listener(record); | |
} | |
} | |
/** | |
* Run the task with the given arguments | |
*/ | |
async run(argv?: Parameters<Func>[0]): Promise<ReturnType<Func>> { | |
// Start run log for boundaries | |
this.startRunLog(); | |
try { | |
// Validate input if schema exists | |
if (this._schema) { | |
const validation = this._schema.safeParse(argv); | |
if (!validation.success) { | |
const formattedErrors = validation.error.errors.map(err => | |
`${err.path.join('.')}: ${err.message}` | |
).join(', '); | |
const errorMessage = formattedErrors | |
? `Invalid input on: ${formattedErrors}` | |
: 'Invalid input'; | |
this.emit({ | |
input: argv, | |
error: errorMessage | |
}); | |
throw new Error(errorMessage); | |
} | |
} | |
// Execute the task function with boundaries | |
const output = await this._fn( | |
argv as Parameters<Func>[0], | |
this._boundaries as unknown as Parameters<Func>[1] | |
); | |
// Log success | |
this.emit({ | |
input: argv, | |
output | |
}); | |
return output; | |
} catch (error: any) { | |
// Log error | |
this.emit({ | |
input: argv, | |
error: error.message | |
}); | |
throw error; | |
} | |
} | |
} | |
/** | |
* Helper function to create a task with proper type inference | |
*/ | |
export function createTask< | |
TSchema extends z.ZodType, | |
B extends Boundaries, | |
TReturn | |
>( | |
schema: TSchema, | |
boundaries: B, | |
fn: (argv: z.infer<TSchema>, boundaries: WrappedBoundaries<B>) => Promise<TReturn>, | |
config?: { | |
boundariesData?: Record<string, unknown>; | |
} | |
): TaskInstanceType<(argv: z.infer<TSchema>, boundaries: WrappedBoundaries<B>) => Promise<TReturn>, B> { | |
return new Task( | |
fn, | |
{ | |
schema, | |
boundaries, | |
...config | |
} | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from 'zod'; | |
// Basic type definitions | |
export type BaseFunction = (...args: any[]) => any; | |
/** | |
* Represents the record of a task execution | |
*/ | |
export interface TaskRecord<InputType = unknown, OutputType = unknown> { | |
/** The input arguments passed to the task */ | |
input: InputType; | |
/** The output returned by the task (if successful) */ | |
output?: OutputType; | |
/** The error message if the task failed */ | |
error?: string; | |
/** Timestamp of when this task was executed */ | |
timestamp: number; | |
} | |
export interface TaskInstanceType<Func extends BaseFunction = BaseFunction> { | |
// Run the task | |
run: (argv?: Parameters<Func>[0]) => Promise<ReturnType<Func>>; | |
// Validation methods | |
validate: <T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T) => z.SafeParseReturnType<T, T> | undefined; | |
isValid: <T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T) => boolean; | |
// Logging methods | |
getLog: () => TaskRecord<Parameters<Func>[0], ReturnType<Func>> | undefined; | |
// Listener methods | |
addListener: <I = Parameters<Func>[0], O = ReturnType<Func>>(fn: (record: TaskRecord<I, O>) => void) => void; | |
removeListener: () => void; | |
setBoundariesData: (data: Record<string, unknown>) => void; | |
} | |
export class Task<Func extends BaseFunction = BaseFunction> implements TaskInstanceType<Func> { | |
private _fn: Func; | |
private _schema?: z.ZodType; | |
private _listener?: (record: TaskRecord<Parameters<Func>[0], ReturnType<Func>>) => void; | |
private _lastLog?: TaskRecord<Parameters<Func>[0], ReturnType<Func>>; | |
constructor( | |
fn: Func, | |
config: { | |
schema?: z.ZodType; | |
} = {} | |
) { | |
this._fn = fn; | |
this._schema = config.schema; | |
} | |
/** | |
* Validate input against the schema | |
*/ | |
validate<T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T): z.SafeParseReturnType<T, T> | undefined { | |
if (!this._schema) { | |
return undefined; | |
} | |
return this._schema.safeParse(argv) as z.SafeParseReturnType<T, T>; | |
} | |
/** | |
* Check if the input is valid according to the schema | |
*/ | |
isValid<T extends Record<string, unknown> = Parameters<Func>[0]>(argv?: T): boolean { | |
if (!this._schema) { | |
return true; | |
} | |
const result = this._schema.safeParse(argv); | |
return result.success; | |
} | |
/** | |
* Add a listener for task execution events | |
*/ | |
addListener<I = Parameters<Func>[0], O = ReturnType<Func>>(fn: (record: TaskRecord<I, O>) => void): void { | |
this._listener = fn as (record: TaskRecord<Parameters<Func>[0], ReturnType<Func>>) => void; | |
} | |
/** | |
* Remove the current listener | |
*/ | |
removeListener(): void { | |
this._listener = undefined; | |
} | |
setBoundariesData(data: Record<string, unknown>): void { | |
// just a mock for now | |
} | |
/** | |
* Get the last execution log | |
*/ | |
getLog(): TaskRecord<Parameters<Func>[0], ReturnType<Func>> | undefined { | |
return this._lastLog; | |
} | |
/** | |
* Emit a task execution event | |
*/ | |
private emit(data: Partial<TaskRecord>): void { | |
const record = { | |
...data, | |
timestamp: Date.now() | |
} as TaskRecord<Parameters<Func>[0], ReturnType<Func>>; | |
// Store the log | |
this._lastLog = record; | |
// Notify listener if exists | |
if (this._listener) { | |
this._listener(record); | |
} | |
} | |
/** | |
* Run the task with the given arguments | |
*/ | |
async run(argv?: Parameters<Func>[0]): Promise<ReturnType<Func>> { | |
try { | |
// Validate input if schema exists | |
if (this._schema) { | |
const validation = this._schema.safeParse(argv); | |
if (!validation.success) { | |
const formattedErrors = validation.error.errors.map(err => | |
`${err.path.join('.')}: ${err.message}` | |
).join(', '); | |
const errorMessage = formattedErrors | |
? `Invalid input on: ${formattedErrors}` | |
: 'Invalid input'; | |
this.emit({ | |
input: argv, | |
error: errorMessage | |
}); | |
throw new Error(errorMessage); | |
} | |
} | |
// Execute the task function | |
const output = await this._fn(argv as Parameters<Func>[0]); | |
// Log success | |
this.emit({ | |
input: argv, | |
output | |
}); | |
return output; | |
} catch (e) { | |
const error = e as Error; | |
// Log error | |
this.emit({ | |
input: argv, | |
error: error.message | |
}); | |
throw error; | |
} | |
} | |
} | |
/** | |
* Helper function to create a task with proper type inference | |
*/ | |
export function createTask< | |
TSchema extends z.ZodType, | |
TReturn | |
>( | |
schema: TSchema, | |
fn: (argv: z.infer<TSchema>) => Promise<TReturn> | |
): TaskInstanceType<(argv: z.infer<TSchema>) => Promise<TReturn>> { | |
return new Task( | |
fn, | |
{ | |
schema | |
} | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment