1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
import { Project, ScriptTarget, Type, Node, SyntaxKind } from 'ts-morph'
import path from 'path'
import { v4 as uuid } from 'uuid'
import { Ctx } from './types'
const RESULT_TYPE_NAME = '__$result'
export interface CtxOptions {
filePath: string
}
export const createContext = (options: CtxOptions): Ctx => {
const project = new Project({
compilerOptions: {
target: ScriptTarget.ES3,
},
})
const typeChecker = project.getTypeChecker()
const sourceFile = project.addSourceFileAtPath(path.resolve(options.filePath))
const entryPoint = sourceFile.getExportedDeclarations().get('main')?.[0]
if (!entryPoint) {
throw new Error('No "main" entrypoint defined in source file')
}
const typeToString = (ty: Type | undefined): string =>
ty ? typeChecker.compilerObject.typeToString(ty.compilerType) : ''
const [resultTypeNode] = sourceFile.addStatements(
`type ${RESULT_TYPE_NAME} = {}`
)
const getResultExpr = (resultKey?: string) =>
`(${RESULT_TYPE_NAME}[${JSON.stringify(resultKey)})`
const addResult = (name: string, ty: string): Node | undefined =>
resultTypeNode
?.asKind(SyntaxKind.TypeAliasDeclaration)
?.getChildAtIndexIfKind(3, SyntaxKind.TypeLiteral)
?.addProperty({
name: JSON.stringify(name),
type: `{ output: ${ty} }`,
})
const createResult = (ty: string): [string, Node | undefined] => {
const resultKey = uuid()
const node = addResult(resultKey, ty)
return [resultKey, node]
}
const customEffects: Record<string, (args: Type[], ctx: Ctx) => any> = {}
const getTypeValue = (ty: Type | undefined): any => {
try {
return JSON.parse(typeToString(ty))
} catch(_) { return }
}
const ctx: Ctx = {
sourceFile,
typeChecker,
entryPoint,
typeToString,
getTypeValue,
createResult,
getResultExpr,
printResultNode: () => console.log(resultTypeNode?.print()),
addCustomEffect: (name, expr) => {
const func = eval(expr)
Object.assign(customEffects, { [name]: func })
},
runCustomEffect: async (name, args) => {
const output = await customEffects[name]?.(args, ctx)
if (output) {
const [resultKey, _] = createResult(`${JSON.stringify(output)}`)
return [resultKey]
}
return []
},
hasCustomEffect: (name) => customEffects[name] !== undefined,
}
return ctx
}
|