@superblocksteam/ai-service-templates 2.0.3-next.46 → 2.0.3-next.53
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/package.json +1 -1
- package/templates.js +4 -4
package/package.json
CHANGED
package/templates.js
CHANGED
|
@@ -4,10 +4,10 @@ export const templates = {
|
|
|
4
4
|
"bootstrap.mjs": "import { register } from \"node:module\";\nimport { pathToFileURL } from \"node:url\";\n\nregister(\"./shim-loader.mjs\", pathToFileURL(\"./\"));\n",
|
|
5
5
|
"package.json": "{\n \"type\": \"module\",\n \"dependencies\": {\n \"acorn\": \"^8.14.0\",\n \"prettier\": \"^3.5.3\",\n \"yaml\": \"^2.7.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.15.18\",\n \"@typescript-eslint/parser\": \"^8.17.0\",\n \"eslint\": \"^9.26.0\",\n \"typescript\": \"^5.8.3\",\n \"vitest\": \"^3.0.9\"\n }\n}",
|
|
6
6
|
"shim-loader.mjs": "import path from \"path\";\nimport { pathToFileURL } from \"url\";\n\nconst overrideMap = {\n \"@superblocksteam/library\": \"./dist/superblocks-library-shim/index.js\",\n};\n\nexport async function resolve(specifier, context, nextResolve) {\n if (overrideMap[specifier]) {\n const fullPath = path.resolve(overrideMap[specifier]);\n return {\n shortCircuit: true,\n url: pathToFileURL(fullPath).href,\n };\n }\n\n return nextResolve(specifier, context);\n}\n",
|
|
7
|
-
"src/do-eval-to-sdk.ts": "import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n// finds all JavaScript files in the `dist/apis-to-transform` directory,\n// imports each module, converts its JSON representation to SDK,\n// and outputs a JSON map with filenames as keys and SDK content as values.\n\nasync function main() {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const apisDir = path.resolve(__dirname, \"./to-sdk\");\n\n const files = fs.readdirSync(apisDir);\n\n const jsFiles = files.filter(\n (file) => file.endsWith(\".js\") && file !== \"__template__.js\",\n );\n\n const results: Record<string, string> = {};\n\n for (const jsFile of jsFiles) {\n const filePath = path.join(apisDir, jsFile);\n const absolutePath = path.resolve(filePath);\n\n try {\n const fileUrl = new URL(`file://${absolutePath}`);\n const module = await import(fileUrl.href);\n\n const defaultExport = module.default;\n\n if (defaultExport && typeof defaultExport.toSDK === \"function\") {\n const sdkData = defaultExport.toSDK();\n\n results[jsFile] = sdkData;\n } else {\n console.warn(`${jsFile}: Default export doesn't have a toSDK method`);\n }\n } catch (error) {\n console.error(`Error processing ${jsFile}:`, error);\n }\n }\n\n console.log(JSON.stringify(results, null, 2));\n}\n\nmain().catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n});\n",
|
|
8
|
-
"src/do-eval-to-yaml.ts": "import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport yaml from \"yaml\";\n\n// finds all JavaScript files in the `dist/apis-to-transform` directory,\n// imports each module, converts its JSON representation to YAML,\n// and outputs a JSON map with filenames as keys and YAML content as values.\n\nasync function main() {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const apisDir = path.resolve(__dirname, \"./to-yaml\");\n\n const files = fs.readdirSync(apisDir);\n\n const jsFiles = files.filter((file) => file.endsWith(\".js\"));\n\n const results: Record<string, string> = {};\n\n for (const jsFile of jsFiles) {\n const filePath = path.join(apisDir, jsFile);\n const absolutePath = path.resolve(filePath);\n\n try {\n const fileUrl = new URL(`file://${absolutePath}`);\n const module = await import(fileUrl.href);\n\n const defaultExport = module.default;\n\n if (defaultExport && typeof defaultExport.toJSON === \"function\") {\n const jsonData = defaultExport.toJSON();\n\n const yamlContent = yaml.stringify(jsonData);\n\n results[jsFile] = yamlContent;\n } else {\n console.warn(`${jsFile}: Default export doesn't have a toJSON method`);\n }\n } catch (error) {\n console.error(`Error processing ${jsFile}:`, error);\n }\n }\n\n console.log(JSON.stringify(results, null, 2));\n}\n\nmain().catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n});\n",
|
|
9
|
-
"src/superblocks-library-shim/index.ts": "/* eslint-disable */\nimport typescript_eslint_parser from \"@typescript-eslint/parser\";\nimport { BlockStatement, CallExpression, ExpressionStatement, FunctionExpression, parse, Program, UnaryExpression } from \"acorn\";\nimport { ESLint } from \"eslint\";\nimport { format } from \"prettier\";\n\nexport type JsonValue =\n | undefined\n | null\n | number\n | string\n | boolean\n | Array<JsonValue>\n | { [key: string]: JsonValue };\nexport type State = { [key: string]: JsonValue };\nexport type Binding<T> = T | ((state: State) => T);\n\nexport const code = (\n fn: string,\n options: { iife?: boolean } = { iife: false },\n): string => {\n // Parse the function string\n const ast = parse(fn, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n });\n\n // Find the function declaration/expression\n let functionNode;\n\n // Walk through the AST to find the function\n if (ast.body[0].type === \"FunctionDeclaration\") {\n // Function declaration: function name() {}\n functionNode = ast.body[0];\n } else if (\n ast.body[0].type === \"ExpressionStatement\" &&\n (ast.body[0].expression.type === \"FunctionExpression\" ||\n ast.body[0].expression.type === \"ArrowFunctionExpression\")\n ) {\n // Function expression: const name = function() {} or const name = () => {}\n functionNode = ast.body[0].expression;\n } else if (\n ast.body[0].type === \"VariableDeclaration\" &&\n (ast.body[0].declarations[0]?.init?.type === \"FunctionExpression\" ||\n ast.body[0].declarations[0]?.init?.type === \"ArrowFunctionExpression\")\n ) {\n // Variable declaration: const name = function() {} or const name = () => {}\n functionNode = ast.body[0].declarations[0]?.init;\n }\n\n if (!functionNode) {\n throw new Error(\"Could not find a function in the provided string\");\n }\n\n // Get the function body\n const body = functionNode.body;\n\n // Extract the source from the original string\n // For arrow functions with an expression body, we return the expression\n if (\n functionNode.type === \"ArrowFunctionExpression\" &&\n body.type !== \"BlockStatement\"\n ) {\n // Arrow function with expression body: () => 42\n const expressionCode = fn.substring(fn.indexOf(\"=>\") + 2).trim();\n return options.iife\n ? `(() => ${expressionCode})()`\n : `return ${expressionCode}`;\n }\n\n // For functions with block bodies, extract content between braces\n const start = body.start + 1; // Skip the opening brace\n const end = body.end - 1; // Skip the closing brace\n\n // Get the raw body content\n const rawBodyLines = fn.substring(start, end).split(\"\\n\");\n\n if (rawBodyLines.length <= 1) {\n // Single line body, just trim it\n const bodyCode = rawBodyLines[0].trim();\n\n if (options.iife) {\n const paramStart = fn.indexOf(\"(\");\n const paramEnd = fn.indexOf(\")\", paramStart);\n const params = fn.substring(paramStart, paramEnd + 1);\n return `(${params} => { ${bodyCode} })()`;\n }\n\n return bodyCode;\n }\n\n // For multi-line bodies, normalize indentation\n // Find the minimum indentation level (ignoring empty lines)\n let minIndent = Infinity;\n for (const line of rawBodyLines) {\n const trimmedLine = line.trimStart();\n if (trimmedLine.length > 0) {\n const indent = line.length - trimmedLine.length;\n minIndent = Math.min(minIndent, indent);\n }\n }\n\n // Remove the common indentation from each line\n const normalizedLines = rawBodyLines.map((line) => {\n if (line.trimStart().length === 0) return \"\";\n return line.substring(minIndent);\n });\n\n // Join the normalized lines\n const bodyCode = normalizedLines\n .join(\" \")\n .replace(/\\s{2,}/g, \" \")\n .trim();\n\n if (options.iife) {\n const paramStart = fn.indexOf(\"(\");\n const paramEnd = fn.indexOf(\")\", paramStart);\n const params = fn.substring(paramStart, paramEnd + 1);\n return `(${params} => { ${bodyCode} })()`;\n }\n\n return bodyCode;\n};\n\nfunction binding(binding: Binding<string>): string {\n if (typeof binding === \"function\") {\n return toJS(binding);\n }\n\n return binding;\n}\n\ninterface Codec {\n toJSON(): JsonValue;\n toSDK(entities: string[]): string;\n}\n\nexport abstract class Block implements Codec {\n protected name: string;\n\n constructor(name: string) {\n this.name = name;\n }\n\n public abstract toJSON(): JsonValue;\n public abstract toSDK(entities: string[]): string;\n\n public static fromJSON(json: any, entities: string[]): Block {\n if (json?.step) {\n return Integration.fromJSON(json, entities);\n }\n\n if (\"conditional\" in json) {\n return Conditional.fromJSON(json, entities);\n }\n\n if (\"loop\" in json) {\n return Loop.fromJSON(json, entities);\n }\n\n throw new Error(\"mysterious block\");\n }\n}\n\nexport abstract class Integration extends Block {\n protected integration: string;\n\n constructor(name: string, integration: string) {\n super(name);\n\n this.integration = integration;\n }\n\n public abstract toJSON(): JsonValue;\n public abstract toSDK(entities: string[]): string;\n\n public static fromJSON(json: any, entities: string[]): Block {\n if (\"javascript\" in json?.step) {\n return JavaScript.fromJSON(json, entities);\n }\n\n if (\"postgres\" in json?.step) {\n return PostgreSQL.fromJSON(json, entities);\n }\n\n if (\"email\" in json?.step) {\n return Email.fromJSON(json, entities);\n }\n\n if (\"restapi\" in json?.step) {\n return RestApi.fromJSON(json, entities);\n }\n\n throw new Error(\"mysterious integration\");\n }\n}\n\nexport class JavaScript extends Integration {\n private fn: (_: State) => JsonValue;\n\n constructor(\n name: string,\n config: {\n fn: (_: State) => JsonValue;\n },\n ) {\n super(name, \"javascript\");\n\n this.fn = config.fn;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n step: {\n integration: \"javascript\",\n javascript: {\n body: code(this.fn.toString(), { iife: false }),\n },\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n return `new JavaScript(\"${this.name}\", { fn: ${signature(code(this.fn.toString()), entities)} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): JavaScript {\n return new JavaScript(json?.name, {\n fn: new Function(\n `{ ${referenced(json?.step?.javascript?.body, entities).join(\", \")} }`,\n json?.step?.javascript?.body,\n ) as (_: State) => JsonValue,\n });\n }\n}\n\nexport class PostgreSQL extends Integration {\n private statement: Binding<string>;\n\n constructor(\n name: string,\n integration: string,\n config: {\n statement: Binding<string>;\n },\n ) {\n super(name, integration);\n\n this.statement = config.statement;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n step: {\n integration: this.integration,\n postgres: {\n statement: binding(this.statement),\n useParameterized: false,\n },\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n return `new PostgreSQL(\"${this.name}\", \"${this.integration}\", { statement: ${typeof this.statement === \"function\" ? signatureV2(toJSBody(this.statement), entities) : `\"${this.statement}\"`} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): PostgreSQL {\n return new PostgreSQL(json?.name, json?.step?.integration, {\n statement: fromBinding(\n json?.step?.postgres?.statement as string,\n entities,\n ),\n });\n }\n}\n\nexport class RestApi extends Integration {\n private method: string;\n private url: Binding<string>;\n private headers?: { key: Binding<string>; value: Binding<string> }[];\n private params?: { key: Binding<string>; value: Binding<string> }[];\n private body?: Binding<JsonValue>;\n\n constructor(\n name: string,\n config: {\n method: string;\n url?: Binding<string>;\n headers?: { key: Binding<string>; value: Binding<string> }[];\n params?: { key: Binding<string>; value: Binding<string> }[];\n body?: Binding<JsonValue>;\n },\n ) {\n super(name, \"restapi\");\n\n this.method = config.method;\n this.url = config.url;\n this.headers = config.headers;\n this.params = config.params;\n this.body = config.body;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n step: {\n integration: \"restapi\",\n restapi: {\n httpMethod: this.method,\n ...(this.url ? { url: binding(this.url, { iife: true, wrap: false }) } : {}),\n ...(this.headers ? { headers: this.headers.map((header) => ({ key: binding(header.key, { iife: true, wrap: false }), value: binding(header.value, { iife: true, wrap: false }) })) } : {}),\n ...(this.params ? { params: this.params.map((param) => ({ key: binding(param.key, { iife: true, wrap: false }), value: binding(param.value, { iife: true, wrap: false }) })) } : {}),\n ...(this.body ? { body: binding(this.body, { iife: true, wrap: false }) } : {}),\n },\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n const parts: string[] = [`method: \"${this.method}\"`];\n\n if (this.url) {\n parts.push(\n `url: ${\n typeof this.url === \"function\"\n ? signature(code(this.url.toString()), entities)\n : `\"${this.url}\"`\n }`,\n );\n }\n\n if (this.headers) {\n const headers = this.headers\n .map(\n (h) =>\n `{ key: ${\n typeof h.key === \"function\"\n ? signature(code(h.key.toString()), entities)\n : `\"${h.key}\"`\n }, value: ${\n typeof h.value === \"function\"\n ? signature(code(h.value.toString()), entities)\n : `\"${h.value}\"`\n } }`,\n )\n .join(\", \");\n parts.push(`headers: [${headers}]`);\n }\n\n if (this.params) {\n const params = this.params\n .map(\n (p) =>\n `{ key: ${\n typeof p.key === \"function\"\n ? signature(code(p.key.toString()), entities)\n : `\"${p.key}\"`\n }, value: ${\n typeof p.value === \"function\"\n ? signature(code(p.value.toString()), entities)\n : `\"${p.value}\"`\n } }`,\n )\n .join(\", \");\n parts.push(`params: [${params}]`);\n }\n\n if (this.body) {\n parts.push(\n `body: ${\n typeof this.body === \"function\"\n ? signature(code(this.body.toString()), entities)\n : JSON.stringify(this.body)\n }`,\n );\n }\n\n return `new RestApi(\"${this.name}\", { ${parts.join(\", \")} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): RestApi {\n throw new Error(\"frank will implement this soon\");\n }\n}\n\nexport class Email extends Integration {\n private subject: Binding<string>;\n private from: Binding<string>; // comma separated list\n private to: Binding<string>; // comma separated list\n private cc?: Binding<string>; // comma separated list\n private bcc?: Binding<string>; // comma separated list\n private body: Binding<string>;\n\n constructor(\n name: string,\n config: {\n from: Binding<string>;\n to: Binding<string>;\n subject: Binding<string>;\n cc?: Binding<string>;\n bcc?: Binding<string>;\n body?: Binding<string>;\n },\n ) {\n super(name, \"email\");\n\n this.from = config.from;\n this.to = config.to;\n this.subject = config.subject;\n this.cc = config.cc;\n this.bcc = config.bcc;\n this.body = config.body;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n step: {\n integration: \"email\",\n email: {\n ...(this.from ? { emailFrom: binding(this.from, { iife: true, wrap: false }) } : {}),\n ...(this.to ? { emailTo: binding(this.to, { iife: true, wrap: false }) } : {}),\n ...(this.cc ? { emailCc: binding(this.cc, { iife: true, wrap: false }) } : {}),\n ...(this.bcc ? { emailBcc: binding(this.bcc, { iife: true, wrap: false }) } : {}),\n ...(this.subject ? { emailSubject: binding(this.subject, { iife: true, wrap: false }) } : {}),\n ...(this.body ? { emailBody: binding(this.body, { iife: true, wrap: false }) } : {}),\n },\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n const parts: string[] = [];\n\n if (this.from) {\n parts.push(\n `from: ${\n typeof this.from === \"function\"\n ? signature(code(this.from.toString()), entities)\n : `\"${this.from}\"`\n }`,\n );\n }\n\n if (this.to) {\n parts.push(\n `to: ${\n typeof this.to === \"function\"\n ? signature(code(this.to.toString()), entities)\n : `\"${this.to}\"`\n }`,\n );\n }\n\n if (this.cc) {\n parts.push(\n `cc: ${\n typeof this.cc === \"function\"\n ? signature(code(this.cc.toString()), entities)\n : `\"${this.cc}\"`\n }`,\n );\n }\n\n if (this.bcc) {\n parts.push(\n `bcc: ${\n typeof this.bcc === \"function\"\n ? signature(code(this.bcc.toString()), entities)\n : `\"${this.bcc}\"`\n }`,\n );\n }\n\n if (this.subject) {\n parts.push(\n `subject: ${\n typeof this.subject === \"function\"\n ? signature(code(this.subject.toString()), entities)\n : `\"${this.subject}\"`\n }`,\n );\n }\n\n if (this.body) {\n parts.push(\n `body: ${\n typeof this.body === \"function\"\n ? signature(code(this.body.toString()), entities)\n : `\"${this.body}\"`\n }`,\n );\n }\n\n return `new Email(\"${this.name}\", { ${parts.join(\", \")} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): Email {\n return new Email(json?.name, {\n from: fromBinding(json?.step?.email?.emailFrom, entities),\n to: fromBinding(json?.step?.email?.emailTo, entities),\n subject: fromBinding(json?.step?.email?.emailSubject, entities),\n body: fromBinding(json?.step?.email?.emailBody, entities),\n cc: json?.step?.email?.emailCc ? fromBinding(json?.step?.email?.emailCc, entities) : undefined,\n bcc: json?.step?.email?.emailBcc ? fromBinding(json?.step?.email?.emailBcc, entities) : undefined,\n });\n }\n}\n\nexport class Databricks extends Integration {\n private statement: Binding<string>;\n\n constructor(\n name: string,\n integration: string,\n config: {\n statement: Binding<string>;\n },\n ) {\n super(name, integration);\n\n this.statement = config.statement;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n step: {\n integration: \"databricks\",\n databricks: {\n runSql: {\n sqlBody: binding(this.statement),\n useParameterized: false,\n },\n },\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n return `new Databricks(\"${this.name}\", \"${this.integration}\", { statement: ${typeof this.statement === \"function\" ? signatureV2(toJSBody(this.statement), entities) : `\"${this.statement}\"`} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): Databricks {\n return new Databricks(json?.name, json?.step?.integration, {\n statement: fromBinding(\n json?.step?.databricks?.runSql?.sqlBody as string,\n entities,\n ),\n });\n }\n}\n\nexport type Condition = {\n when: Binding<boolean>;\n then: Block[];\n};\n\nexport type Conditions = {\n if: Condition;\n elif?: Condition[];\n else?: Block[];\n};\n\nexport class Conditional extends Block {\n public conditions: Conditions;\n\n constructor(name: string, config: Conditions) {\n super(name);\n\n this.conditions = config;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n conditional: {\n if: {\n condition: toJS<boolean>(this.conditions.if.when),\n blocks: this.conditions.if.then.map((block) => block.toJSON()),\n },\n ...(() =>\n this.conditions.elif\n ? {\n elif: this.conditions.elif.map((condition) => ({\n condition: toJS<boolean>(condition.when),\n blocks: condition.then.map((block) => block.toJSON()),\n })),\n }\n : {})(),\n else: this.conditions.else?.map((block) => block.toJSON()),\n },\n };\n }\n\n public static fromJSON(json: any, entities: string[]): Conditional {\n return new Conditional(json?.name, {\n if: {\n when: fromJS<boolean>(json?.conditional?.if?.condition, entities),\n then: json?.conditional?.if?.blocks.map((block: any) =>\n Block.fromJSON(block, entities),\n ),\n },\n });\n }\n\n public toSDK(entities: string[]): string {\n return `new Conditional(\"${this.name}\", { if: ${typeof this.conditions.if.when === \"function\" ? signature(code(this.conditions.if.when.toString(), { iife: true }), entities) : this.conditions.if.when}, then: [${this.conditions.if.then.map((block) => block.toSDK(entities)).join(\",\")}] })`;\n }\n}\n\nexport class Parallel extends Block {\n private over: Binding<JsonValue[]>;\n private blocks: Block[];\n private variables: { item: string; index: string };\n\n constructor(\n name: string,\n config: {\n over: Binding<JsonValue[]>;\n variables: { item: string; index: string };\n blocks: Block[];\n },\n ) {\n super(name);\n\n this.over = config.over;\n this.blocks = config.blocks;\n this.variables = config.variables;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n parallel: {\n dynamic: {\n blocks: this.blocks.map((block) => block.toJSON()),\n paths: toJS<JsonValue[]>(this.over),\n variables: this.variables,\n },\n wait: \"WAIT_ALL\",\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n return `new Parallel(\"${this.name}\", { over: ${this.over.toString()}, variables: ${this.variables}, blocks: ${this.blocks.map((block) => block.toSDK(entities)).join(\",\")} })`;\n }\n}\n\nexport class Loop extends Block {\n private over: Binding<JsonValue[]>; // NOTE(Frank): Only for each loops are supported at the moment.\n private blocks: Block[];\n private variables: { item: string; index: string };\n\n constructor(\n name: string,\n config: {\n over: Binding<JsonValue[]>;\n variables: { item: string; index: string };\n blocks: Block[];\n },\n ) {\n super(name);\n\n this.over = config.over;\n this.variables = config.variables;\n this.blocks = config.blocks;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n loop: {\n range: toJS<JsonValue[]>(this.over),\n type: \"TYPE_FOREACH\",\n variables: this.variables,\n blocks: this.blocks.map((block) => block.toJSON()),\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n return `new Loop(\"${this.name}\", { over: ${this.over.toString()}, variables: ${this.variables}, blocks: ${this.blocks.map((block) => block.toSDK(entities)).join(\",\")} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): Loop {\n return new Loop(json?.name, {\n over: fromJS<JsonValue[]>(json?.loop?.range, entities),\n variables: {\n item: json?.loop?.variables?.item,\n index: json?.loop?.variables?.index,\n },\n blocks: json?.loop?.blocks.map((block: any) =>\n Block.fromJSON(block, entities),\n ),\n });\n }\n}\n\nexport class TryCatch extends Block {\n private try: Block[];\n private catch: Block[];\n private finally?: Block[];\n\n constructor(\n name: string,\n config: {\n try: Block[];\n catch: Block[];\n finally?: Block[];\n },\n ) {\n super(name);\n\n this.try = config.try;\n this.catch = config.catch;\n this.finally = config.finally;\n }\n\n public toJSON(): JsonValue {\n return {\n name: this.name,\n tryCatch: {\n try: { blocks: this.try.map((block) => block.toJSON()) },\n catch: { blocks: this.catch.map((block) => block.toJSON()) },\n finally: { blocks: this.finally?.map((block) => block.toJSON()) },\n },\n };\n }\n\n public toSDK(entities: string[]): string {\n return `new TryCatch(\"${this.name}\", { try: ${this.try.map((block) => block.toSDK(entities)).join(\", \")}, catch: ${this.catch.map((block) => block.toSDK(entities)).join(\", \")}, finally: ${this.finally?.map((block) => block.toSDK(entities)).join(\", \")} })`;\n }\n}\n\nexport class Api {\n private blocks: Block[];\n private name: string;\n\n constructor(name: string, blocks: Block[]) {\n this.name = name;\n this.blocks = blocks;\n }\n\n public toSDK(entities: string[]): string {\n const imports =\n \"import { Api, JavaScript, PostgreSQL, Databricks, Conditional, Parallel, Loop, TryCatch } from '@superblocksteam/library'\";\n return `${imports}\\n\\nexport default new Api(\"${this.name}\", [${this.blocks.map((block) => block.toSDK(entities)).join(\",\")}])`;\n }\n\n public static fromJSON(json: any, entities: string[]): Api {\n // NOTE(Frank): Would probs unmarshal this into the protobuf types and then do the conversion.\n return new Api(\n json?.metadata?.name as string,\n (json.blocks as any[]).map((block) => Block.fromJSON(block, entities)),\n );\n }\n\n public toJSON(): JsonValue {\n const api: {\n metadata: {\n name: string;\n timestamps: {\n updated: string;\n };\n };\n trigger: {\n application: {};\n };\n blocks: JsonValue[];\n } = {\n metadata: {\n name: this.name,\n timestamps: {\n updated: new Date().toISOString(),\n },\n },\n trigger: {\n application: {},\n },\n blocks: [],\n };\n\n this.blocks.forEach((block) => api.blocks.push(block.toJSON()));\n\n return api;\n }\n}\n\nfunction referenced(data: string, entities: string[]): string[] {\n return entities.reduce((acc: string[], entity: string) => {\n if (data.includes(entity)) {\n acc.push(entity);\n }\n return acc;\n }, []);\n}\n\nfunction signature(data: string, entities: string[]): string {\n return `({ ${referenced(data, entities).join(\", \")} }) => {${data}}`;\n}\n\nfunction signatureV2(data: string, entities: string[]): string {\n return `({ ${referenced(data, entities).join(\", \")} }) => ${data}`;\n}\n\n/**\n * Converts a string binding, which comprise 99% of dynamic integration fields, into a function returning an interpolated string.\n *\n * fromBinding(\"https://{{ 'goo' + 'gle' }}.com/{{ Dropdown1.value }}\", ['Dropdown1']) -> ({ Dropdown1 }) => `https://${'goo' + 'gle'}.com/${Dropdown1.value}`\n *\n * @param value - The value to convert.\n * @param entities - The master list of possible entities that could be referenced in the binding.\n *\n * @returns The converted value.\n */\nfunction fromBinding(value: string, entities: string[]): Binding<string> {\n const regex = /\\{\\{\\s*([^}]*)\\s*\\}\\}/g;\n const converted: string = value.replace(regex, (match, content) => `\\${${content.trim()}}`);\n\n // there were no bindings to convert\n if (converted === value) {\n return value;\n }\n\n const args: string[] = [`return \\`${converted}\\``]\n const references: string[] = referenced(converted, entities);\n\n // only add the arguments if we have references\n if (references.length > 0) {\n args.unshift(`{ ${references} }`);\n }\n\n return new Function(...args) as (_: State) => string;\n}\n\n// if we can guarantee that all of the semi-colots are present, then we can just return new lines.\nfunction blockStatementToSingleLineIife(statement: BlockStatement, value: string) {\n let body = value.slice(statement.start, statement.end)\n\n // drop the opening \"{\" and closing \"}\"\n body = body.slice(1, -1)\n \n // attempt to add semicolons if they are missing\n const lines = body.split('\\n').map(line => line.trim()).filter(line => line).map(line => {\n line = line.trimEnd()\n \n // Skip lines that already end with semicolons, braces, or are control flow statements\n if (line.endsWith(';') || line.endsWith('{') || line.endsWith('}')) return line;\n \n // Skip lines that are start of control structures\n if (/^(if|for|while|switch|function|class|return\\s+\\{|else|do)(\\s|\\()/.test(line)) return line;\n \n // Add semicolon to other lines\n return line + ';';\n });\n\n return `{ ${lines.join(' ')} }`\n}\n\nfunction createAst(value: string): Program {\n return parse(value, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n preserveParens: false,\n });\n}\n\nfunction toJSBody<T extends JsonValue>(value: ((state: State) => T)): string {\n if (typeof value !== \"function\") {\n return `{{ ${JSON.stringify(value)} }}`\n }\n\n // parse the code\n const ast = createAst(value.toString());\n const program = ast.body[0];\n\n switch (program.type) {\n case \"ExpressionStatement\":\n const body = ((program as ExpressionStatement).expression as FunctionExpression).body;\n\n if (body.type === 'BlockStatement') {\n return blockStatementToSingleLineIife(body, value.toString())\n }\n\n return value.toString().slice(body.start, body.end)\n case \"FunctionDeclaration\":\n let contents = value.toString().slice(program.body.start, program.body.end)\n\n if (program.body.type !== 'BlockStatement') {\n return contents\n }\n \n if (program.body.body.length === 1 && program.body.body[0].type === 'ReturnStatement') {\n return value.toString().slice(program.body.body[0].argument.start, program.body.body[0].argument.end)\n }\n\n return blockStatementToSingleLineIife(program.body, value.toString())\n }\n\n throw new Error(\"you found a case we haven't handled yet\")\n}\n\nexport function toJS<T extends JsonValue>(value: Binding<T>): string {\n if (typeof value !== \"function\") {\n return `{{ ${JSON.stringify(value)} }}`\n }\n\n const body = toJSBody(value)\n return `{{ (() => ${body})() }}`\n}\n\nexport function fromJS<T extends JsonValue>(\n value: string,\n entities: string[] = [],\n): Binding<T> {\n // determine if there are entities referenced in the value\n const refs = referenced(value, entities).join(\", \")\n\n\n // remove any surrounding whitespace\n value = value.trim()\n\n // require bindings\n if (!value.startsWith(\"{{\") || !value.endsWith(\"}}\")) {\n throw new Error(\"bindings must always be wrapped in double curly braces\");\n }\n\n // remove the double curly braces and trim whitespace\n value = value.slice(2, -2).trim();\n\n let unaryOps: string[] = [];\n let call = (createAst(value).body[0] as ExpressionStatement).expression;\n\n while (call?.type === 'UnaryExpression') {\n unaryOps.push((call as UnaryExpression).operator);\n call = (call as unknown as UnaryExpression).argument as CallExpression;\n }\n \n const fn = (call as CallExpression).callee;\n let body = value;\n let prefix = \"return \";\n\n // handle iife\n if (fn?.type === \"ArrowFunctionExpression\") {\n // i'm not happy with this but I don't see another immediate way to handle this.\n // this will result in a correct result but one that isn't idemopotent.\n if (unaryOps.length > 0) {\n return new Function(`{ ${refs} }`, `${prefix}${body}`) as (_: State) => T;\n }\n\n\n body = value.slice(fn.body.start, fn.body.end)\n\n if (fn.body.type === 'BlockStatement') {\n // drop the opening \"{\" and closing \"}\"\n body = body.slice(1, -1)\n prefix = \"\"\n } \n } else if (refs.length === 0) {\n // we're not an iife nor do we have references.\n // we might have to revisit this implementation since this technically reduces to a static value down to it's most minimal form.\n return eval(value) as T\n }\n\n // we're either an iife or a static value with refs.\n return new Function(`{ ${refs} }`, `${prefix}${body}`) as (_: State) => T;\n}\n\nexport async function beautify(code: string): Promise<string> {\n const eslint = new ESLint({\n fix: true,\n overrideConfig: [\n {\n languageOptions: {\n parser: typescript_eslint_parser,\n parserOptions: {\n ecmaVersion: 2020,\n sourceType: \"module\",\n },\n },\n rules: {\n \"arrow-body-style\": \"error\",\n },\n },\n ],\n });\n\n code = (await eslint.lintText(code))[0]?.output || code;\n\n return await format(code, {\n parser: \"typescript\",\n tabWidth: 2,\n singleQuote: true,\n trailingComma: \"es5\",\n semi: true,\n bracketSpacing: true,\n arrowParens: \"always\",\n bracketSameLine: false,\n embeddedLanguageFormatting: \"auto\",\n quoteProps: \"as-needed\",\n insertPragma: false,\n requirePragma: false,\n useTabs: false,\n endOfLine: \"auto\",\n arrowFunctionParentheses: \"always\",\n plugins: [\"prettier-plugin-organize-imports\"],\n });\n}\n",
|
|
10
|
-
"src/to-sdk/__template__.ts": "import { Api } from \"@superblocksteam/library\";\n\nconst json = {};\n\nconst entities = [];\n\nconst api = Api.fromJSON(json, entities);\n\nexport default {\n toSDK: () => api.toSDK(entities),\n};\n",
|
|
7
|
+
"src/do-eval-to-sdk.ts": "import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n// finds all JavaScript files in the `dist/apis-to-transform` directory,\n// imports each module, converts its JSON representation to SDK,\n// and outputs a JSON map with filenames as keys and SDK content as values.\n\nasync function main() {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const apisDir = path.resolve(__dirname, \"./to-sdk\");\n\n const files = fs.readdirSync(apisDir);\n\n const jsFiles = files.filter(\n (file) => file.endsWith(\".js\") && file !== \"__template__.js\",\n );\n\n const results: Record<string, string> = {};\n\n for (const jsFile of jsFiles) {\n const filePath = path.join(apisDir, jsFile);\n const absolutePath = path.resolve(filePath);\n\n try {\n const fileUrl = new URL(`file://${absolutePath}`);\n const module = await import(fileUrl.href);\n\n const defaultExport = module.default;\n\n if (defaultExport && typeof defaultExport.toSDK === \"function\") {\n const sdkData = await defaultExport.toSDK();\n\n results[jsFile] = sdkData;\n } else {\n console.warn(`${jsFile}: Default export doesn't have a toSDK method`);\n }\n } catch (error) {\n console.error(`Error processing ${jsFile}:`, error);\n }\n }\n\n console.log(JSON.stringify(results, null, 2));\n}\n\nmain().catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n});\n",
|
|
8
|
+
"src/do-eval-to-yaml.ts": "import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport yaml from \"yaml\";\n\n// finds all JavaScript files in the `dist/apis-to-transform` directory,\n// imports each module, converts its JSON representation to YAML,\n// and outputs a JSON map with filenames as keys and YAML content as values.\n\nasync function main() {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const apisDir = path.resolve(__dirname, \"./to-yaml\");\n\n const files = fs.readdirSync(apisDir);\n\n const jsFiles = files.filter((file) => file.endsWith(\".js\"));\n\n const results: Record<string, string> = {};\n\n for (const jsFile of jsFiles) {\n const filePath = path.join(apisDir, jsFile);\n const absolutePath = path.resolve(filePath);\n\n try {\n const fileUrl = new URL(`file://${absolutePath}`);\n const module = await import(fileUrl.href);\n\n const defaultExport = module.default;\n\n if (defaultExport && typeof defaultExport.toJSON === \"function\") {\n const jsonData = await defaultExport.toJSON();\n\n const yamlContent = yaml.stringify(jsonData);\n\n results[jsFile] = yamlContent;\n } else {\n console.warn(`${jsFile}: Default export doesn't have a toJSON method`);\n }\n } catch (error) {\n console.error(`Error processing ${jsFile}:`, error);\n }\n }\n\n console.log(JSON.stringify(results, null, 2));\n}\n\nmain().catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n});\n",
|
|
9
|
+
"src/superblocks-library-shim/index.ts": "/* eslint-disable */\nimport typescript_eslint_parser from \"@typescript-eslint/parser\";\nimport { BlockStatement, CallExpression, ExpressionStatement, FunctionExpression, parse, Program, UnaryExpression } from \"acorn\";\nimport { ESLint } from \"eslint\";\nimport { format } from \"prettier\";\n\nexport type JsonValue =\n | undefined\n | null\n | number\n | string\n | boolean\n | Array<JsonValue>\n | { [key: string]: JsonValue };\nexport type State = { [key: string]: JsonValue };\nexport type Binding<T> = T | ((state: State) => T);\n\nexport const code = (\n fn: string,\n options: { iife?: boolean } = { iife: false },\n): string => {\n // Parse the function string\n const ast = parse(fn, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n });\n\n // Find the function declaration/expression\n let functionNode;\n\n // Walk through the AST to find the function\n if (ast.body[0].type === \"FunctionDeclaration\") {\n // Function declaration: function name() {}\n functionNode = ast.body[0];\n } else if (\n ast.body[0].type === \"ExpressionStatement\" &&\n (ast.body[0].expression.type === \"FunctionExpression\" ||\n ast.body[0].expression.type === \"ArrowFunctionExpression\")\n ) {\n // Function expression: const name = function() {} or const name = () => {}\n functionNode = ast.body[0].expression;\n } else if (\n ast.body[0].type === \"VariableDeclaration\" &&\n (ast.body[0].declarations[0]?.init?.type === \"FunctionExpression\" ||\n ast.body[0].declarations[0]?.init?.type === \"ArrowFunctionExpression\")\n ) {\n // Variable declaration: const name = function() {} or const name = () => {}\n functionNode = ast.body[0].declarations[0]?.init;\n }\n\n if (!functionNode) {\n throw new Error(\"Could not find a function in the provided string\");\n }\n\n // Get the function body\n const body = functionNode.body;\n\n // Extract the source from the original string\n // For arrow functions with an expression body, we return the expression\n if (\n functionNode.type === \"ArrowFunctionExpression\" &&\n body.type !== \"BlockStatement\"\n ) {\n // Arrow function with expression body: () => 42\n const expressionCode = fn.substring(fn.indexOf(\"=>\") + 2).trim();\n return options.iife\n ? `(() => ${expressionCode})()`\n : `return ${expressionCode}`;\n }\n\n // For functions with block bodies, extract content between braces\n const start = body.start + 1; // Skip the opening brace\n const end = body.end - 1; // Skip the closing brace\n\n // Get the raw body content\n const rawBodyLines = fn.substring(start, end).split(\"\\n\");\n\n if (rawBodyLines.length <= 1) {\n // Single line body, just trim it\n const bodyCode = rawBodyLines[0].trim();\n\n if (options.iife) {\n const paramStart = fn.indexOf(\"(\");\n const paramEnd = fn.indexOf(\")\", paramStart);\n const params = fn.substring(paramStart, paramEnd + 1);\n return `(${params} => { ${bodyCode} })()`;\n }\n\n return bodyCode;\n }\n\n // For multi-line bodies, normalize indentation\n // Find the minimum indentation level (ignoring empty lines)\n let minIndent = Infinity;\n for (const line of rawBodyLines) {\n const trimmedLine = line.trimStart();\n if (trimmedLine.length > 0) {\n const indent = line.length - trimmedLine.length;\n minIndent = Math.min(minIndent, indent);\n }\n }\n\n // Remove the common indentation from each line\n const normalizedLines = rawBodyLines.map((line) => {\n if (line.trimStart().length === 0) return \"\";\n return line.substring(minIndent);\n });\n\n // Join the normalized lines\n const bodyCode = normalizedLines\n .join(\" \")\n .replace(/\\s{2,}/g, \" \")\n .trim();\n\n if (options.iife) {\n const paramStart = fn.indexOf(\"(\");\n const paramEnd = fn.indexOf(\")\", paramStart);\n const params = fn.substring(paramStart, paramEnd + 1);\n return `(${params} => { ${bodyCode} })()`;\n }\n\n return bodyCode;\n};\n\nasync function binding(binding: Binding<string>): Promise<string> {\n if (typeof binding === \"function\") {\n return await toJS(binding);\n }\n\n return binding;\n}\n\ninterface Codec {\n toJSON(): Promise<JsonValue>;\n toSDK(entities: string[]): Promise<string>;\n}\n\nexport abstract class Block implements Codec {\n protected name: string;\n\n constructor(name: string) {\n this.name = name;\n }\n\n public abstract toJSON(): Promise<JsonValue>;\n public abstract toSDK(entities: string[]): Promise<string>;\n\n public static fromJSON(json: any, entities: string[]): Block {\n if (json?.step) {\n return Integration.fromJSON(json, entities);\n }\n\n if (\"conditional\" in json) {\n return Conditional.fromJSON(json, entities);\n }\n\n if (\"loop\" in json) {\n return Loop.fromJSON(json, entities);\n }\n\n throw new Error(\"mysterious block\");\n }\n}\n\nexport abstract class Integration extends Block {\n protected integration: string;\n\n constructor(name: string, integration: string) {\n super(name);\n\n this.integration = integration;\n }\n\n public abstract toJSON(): Promise<JsonValue>;\n public abstract toSDK(entities: string[]): Promise<string>;\n\n public static fromJSON(json: any, entities: string[]): Block {\n if (\"javascript\" in json?.step) {\n return JavaScript.fromJSON(json, entities);\n }\n\n if (\"postgres\" in json?.step) {\n return PostgreSQL.fromJSON(json, entities);\n }\n\n if (\"email\" in json?.step) {\n return Email.fromJSON(json, entities);\n }\n\n if (\"restapi\" in json?.step) {\n return RestApi.fromJSON(json, entities);\n }\n\n throw new Error(\"mysterious integration\");\n }\n}\n\nexport class JavaScript extends Integration {\n private fn: (_: State) => JsonValue;\n\n constructor(\n name: string,\n config: {\n fn: (_: State) => JsonValue;\n },\n ) {\n super(name, \"javascript\");\n\n this.fn = config.fn;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n step: {\n integration: \"javascript\",\n javascript: {\n body: code(this.fn.toString(), { iife: false }),\n // body: await toJSBody(this.fn, { block: false }),\n },\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n return `new JavaScript(\"${this.name}\", { fn: ${signature(code(this.fn.toString()), entities)} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): JavaScript {\n return new JavaScript(json?.name, {\n fn: new Function(\n `{ ${referenced(json?.step?.javascript?.body, entities).join(\", \")} }`,\n json?.step?.javascript?.body,\n ) as (_: State) => JsonValue,\n });\n }\n}\n\nexport class PostgreSQL extends Integration {\n private statement: Binding<string>;\n\n constructor(\n name: string,\n integration: string,\n config: {\n statement: Binding<string>;\n },\n ) {\n super(name, integration);\n\n this.statement = config.statement;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n step: {\n integration: this.integration,\n postgres: {\n statement: await binding(this.statement),\n useParameterized: false,\n },\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n return `new PostgreSQL(\"${this.name}\", \"${this.integration}\", { statement: ${typeof this.statement === \"function\" ? signatureV2(await toJSBody(this.statement), entities) : `\"${this.statement}\"`} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): PostgreSQL {\n return new PostgreSQL(json?.name, json?.step?.integration, {\n statement: fromBinding(\n json?.step?.postgres?.statement as string,\n entities,\n ),\n });\n }\n}\n\nexport class RestApi extends Integration {\n private method: string;\n private url: Binding<string>;\n private headers?: { key: Binding<string>; value: Binding<string> }[];\n private params?: { key: Binding<string>; value: Binding<string> }[];\n private body?: Binding<string>;\n\n constructor(\n name: string,\n config: {\n method: string;\n url?: Binding<string>;\n headers?: { key: Binding<string>; value: Binding<string> }[];\n params?: { key: Binding<string>; value: Binding<string> }[];\n body?: Binding<string>;\n },\n ) {\n super(name, \"restapi\");\n\n this.method = config.method;\n this.url = config.url;\n this.headers = config.headers;\n this.params = config.params;\n this.body = config.body;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n step: {\n integration: \"restapi\",\n restapi: {\n httpMethod: this.method,\n ...(this.url ? { url: await binding(this.url) } : {}),\n ...(this.headers ? { headers: await Promise.all(this.headers.map(async (header) => ({ key: await binding(header.key), value: await binding(header.value) }))) } : {}),\n ...(this.params ? { params: await Promise.all(this.params.map(async (param) => ({ key: await binding(param.key), value: await binding(param.value) }))) } : {}),\n ...(this.body ? { body: await binding(this.body) } : {}),\n },\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n const parts: string[] = [`method: \"${this.method}\"`];\n\n if (this.url) {\n parts.push(\n `url: ${\n typeof this.url === \"function\"\n ? signature(code(this.url.toString()), entities)\n : `\"${this.url}\"`\n }`,\n );\n }\n\n if (this.headers) {\n const headers = this.headers\n .map(\n (h) =>\n `{ key: ${\n typeof h.key === \"function\"\n ? signature(code(h.key.toString()), entities)\n : `\"${h.key}\"`\n }, value: ${\n typeof h.value === \"function\"\n ? signature(code(h.value.toString()), entities)\n : `\"${h.value}\"`\n } }`,\n )\n .join(\", \");\n parts.push(`headers: [${headers}]`);\n }\n\n if (this.params) {\n const params = this.params\n .map(\n (p) =>\n `{ key: ${\n typeof p.key === \"function\"\n ? signature(code(p.key.toString()), entities)\n : `\"${p.key}\"`\n }, value: ${\n typeof p.value === \"function\"\n ? signature(code(p.value.toString()), entities)\n : `\"${p.value}\"`\n } }`,\n )\n .join(\", \");\n parts.push(`params: [${params}]`);\n }\n\n if (this.body) {\n parts.push(\n `body: ${\n typeof this.body === \"function\"\n ? signature(code(this.body.toString()), entities)\n : JSON.stringify(this.body)\n }`,\n );\n }\n\n return `new RestApi(\"${this.name}\", { ${parts.join(\", \")} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): RestApi {\n throw new Error(\"frank will implement this soon\");\n }\n}\n\nexport class Email extends Integration {\n private subject: Binding<string>;\n private from: Binding<string>; // comma separated list\n private to: Binding<string>; // comma separated list\n private cc?: Binding<string>; // comma separated list\n private bcc?: Binding<string>; // comma separated list\n private body: Binding<string>;\n\n constructor(\n name: string,\n config: {\n from: Binding<string>;\n to: Binding<string>;\n subject: Binding<string>;\n cc?: Binding<string>;\n bcc?: Binding<string>;\n body?: Binding<string>;\n },\n ) {\n super(name, \"email\");\n\n this.from = config.from;\n this.to = config.to;\n this.subject = config.subject;\n this.cc = config.cc;\n this.bcc = config.bcc;\n this.body = config.body;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n step: {\n integration: \"email\",\n email: {\n ...(this.from ? { emailFrom: await binding(this.from) } : {}),\n ...(this.to ? { emailTo: await binding(this.to) } : {}),\n ...(this.cc ? { emailCc: await binding(this.cc) } : {}),\n ...(this.bcc ? { emailBcc: await binding(this.bcc) } : {}),\n ...(this.subject ? { emailSubject: await binding(this.subject) } : {}),\n ...(this.body ? { emailBody: await binding(this.body) } : {}),\n },\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n const parts: string[] = [];\n\n if (this.from) {\n parts.push(\n `from: ${\n typeof this.from === \"function\"\n ? signature(code(this.from.toString()), entities)\n : `\"${this.from}\"`\n }`,\n );\n }\n\n if (this.to) {\n parts.push(\n `to: ${\n typeof this.to === \"function\"\n ? signature(code(this.to.toString()), entities)\n : `\"${this.to}\"`\n }`,\n );\n }\n\n if (this.cc) {\n parts.push(\n `cc: ${\n typeof this.cc === \"function\"\n ? signature(code(this.cc.toString()), entities)\n : `\"${this.cc}\"`\n }`,\n );\n }\n\n if (this.bcc) {\n parts.push(\n `bcc: ${\n typeof this.bcc === \"function\"\n ? signature(code(this.bcc.toString()), entities)\n : `\"${this.bcc}\"`\n }`,\n );\n }\n\n if (this.subject) {\n parts.push(\n `subject: ${\n typeof this.subject === \"function\"\n ? signature(code(this.subject.toString()), entities)\n : `\"${this.subject}\"`\n }`,\n );\n }\n\n if (this.body) {\n parts.push(\n `body: ${\n typeof this.body === \"function\"\n ? signature(code(this.body.toString()), entities)\n : `\"${this.body}\"`\n }`,\n );\n }\n\n return `new Email(\"${this.name}\", { ${parts.join(\", \")} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): Email {\n return new Email(json?.name, {\n from: fromBinding(json?.step?.email?.emailFrom, entities),\n to: fromBinding(json?.step?.email?.emailTo, entities),\n subject: fromBinding(json?.step?.email?.emailSubject, entities),\n body: fromBinding(json?.step?.email?.emailBody, entities),\n cc: json?.step?.email?.emailCc ? fromBinding(json?.step?.email?.emailCc, entities) : undefined,\n bcc: json?.step?.email?.emailBcc ? fromBinding(json?.step?.email?.emailBcc, entities) : undefined,\n });\n }\n}\n\nexport class Databricks extends Integration {\n private statement: Binding<string>;\n\n constructor(\n name: string,\n integration: string,\n config: {\n statement: Binding<string>;\n },\n ) {\n super(name, integration);\n\n this.statement = config.statement;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n step: {\n integration: \"databricks\",\n databricks: {\n runSql: {\n sqlBody: await binding(this.statement),\n useParameterized: false,\n },\n },\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n return `new Databricks(\"${this.name}\", \"${this.integration}\", { statement: ${typeof this.statement === \"function\" ? signatureV2(await toJSBody(this.statement), entities) : `\"${this.statement}\"`} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): Databricks {\n return new Databricks(json?.name, json?.step?.integration, {\n statement: fromBinding(\n json?.step?.databricks?.runSql?.sqlBody as string,\n entities,\n ),\n });\n }\n}\n\nexport type Condition = {\n when: Binding<boolean>;\n then: Block[];\n};\n\nexport type Conditions = {\n if: Condition;\n elif?: Condition[];\n else?: Block[];\n};\n\nexport class Conditional extends Block {\n public conditions: Conditions;\n\n constructor(name: string, config: Conditions) {\n super(name);\n\n this.conditions = config;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n conditional: {\n if: {\n condition: await toJS<boolean>(this.conditions.if.when),\n blocks: await Promise.all(this.conditions.if.then.map(async (block) => await block.toJSON())),\n },\n ...await (async () =>\n this.conditions.elif\n ? {\n elif: await Promise.all(this.conditions.elif.map(async (condition) => ({\n condition: await toJS<boolean>(condition.when),\n blocks: await Promise.all(condition.then.map(async (block) => await block.toJSON())),\n }))),\n }\n : {})(),\n else: this.conditions.else ? await Promise.all(this.conditions.else.map(async (block) => await block.toJSON())) : undefined,\n },\n };\n }\n\n public static fromJSON(json: any, entities: string[]): Conditional {\n return new Conditional(json?.name, {\n if: {\n when: fromJS<boolean>(json?.conditional?.if?.condition, entities),\n then: json?.conditional?.if?.blocks.map((block: any) =>\n Block.fromJSON(block, entities),\n ),\n },\n });\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n return `new Conditional(\"${this.name}\", { if: ${typeof this.conditions.if.when === \"function\" ? signature(code(this.conditions.if.when.toString(), { iife: true }), entities) : this.conditions.if.when}, then: [${await Promise.all(this.conditions.if.then.map(async (block) => await block.toSDK(entities)).join(\",\"))}] })`;\n }\n}\n\nexport class Parallel extends Block {\n private over: Binding<JsonValue[]>;\n private blocks: Block[];\n private variables: { item: string; index: string };\n\n constructor(\n name: string,\n config: {\n over: Binding<JsonValue[]>;\n variables: { item: string; index: string };\n blocks: Block[];\n },\n ) {\n super(name);\n\n this.over = config.over;\n this.blocks = config.blocks;\n this.variables = config.variables;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n parallel: {\n dynamic: {\n blocks: await Promise.all(this.blocks.map(async (block) => await block.toJSON())),\n paths: await toJS<JsonValue[]>(this.over),\n variables: this.variables,\n },\n wait: \"WAIT_ALL\",\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n return `new Parallel(\"${this.name}\", { over: ${this.over.toString()}, variables: ${this.variables}, blocks: ${await Promise.all(this.blocks.map(async (block) => await block.toSDK(entities)).join(\",\"))} })`;\n }\n}\n\nexport class Loop extends Block {\n private over: Binding<JsonValue[]>; // NOTE(Frank): Only for each loops are supported at the moment.\n private blocks: Block[];\n private variables: { item: string; index: string };\n\n constructor(\n name: string,\n config: {\n over: Binding<JsonValue[]>;\n variables: { item: string; index: string };\n blocks: Block[];\n },\n ) {\n super(name);\n\n this.over = config.over;\n this.variables = config.variables;\n this.blocks = config.blocks;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n loop: {\n range: await toJS<JsonValue[]>(this.over),\n type: \"TYPE_FOREACH\",\n variables: this.variables,\n blocks: await Promise.all(this.blocks.map(async (block) => await block.toJSON())),\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n return `new Loop(\"${this.name}\", { over: ${this.over.toString()}, variables: ${this.variables}, blocks: ${await Promise.all(this.blocks.map(async (block) => await block.toSDK(entities)).join(\",\"))} })`;\n }\n\n public static fromJSON(json: any, entities: string[]): Loop {\n return new Loop(json?.name, {\n over: fromJS<JsonValue[]>(json?.loop?.range, entities),\n variables: {\n item: json?.loop?.variables?.item,\n index: json?.loop?.variables?.index,\n },\n blocks: json?.loop?.blocks.map((block: any) =>\n Block.fromJSON(block, entities),\n ),\n });\n }\n}\n\nexport class TryCatch extends Block {\n private try: Block[];\n private catch: Block[];\n private finally?: Block[];\n\n constructor(\n name: string,\n config: {\n try: Block[];\n catch: Block[];\n finally?: Block[];\n },\n ) {\n super(name);\n\n this.try = config.try;\n this.catch = config.catch;\n this.finally = config.finally;\n }\n\n public async toJSON(): Promise<JsonValue> {\n return {\n name: this.name,\n tryCatch: {\n try: { blocks: await Promise.all(this.try.map(async (block) => await block.toJSON())) },\n catch: { blocks: await Promise.all(this.catch.map(async (block) => await block.toJSON())) },\n finally: { blocks: this.finally ? await Promise.all(this.finally.map(async (block) => await block.toJSON())) : [] },\n },\n };\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n return `new TryCatch(\"${this.name}\", { try: ${await Promise.all(this.try.map(async (block) => await block.toSDK(entities)).join(\",\"))}, catch: ${await Promise.all(this.catch.map(async (block) => await block.toSDK(entities)).join(\",\"))}, finally: ${this.finally?.map(async (block) => await block.toSDK(entities)).join(\", \")} })`;\n }\n}\n\nexport class Api {\n private blocks: Block[];\n private name: string;\n\n constructor(name: string, blocks: Block[]) {\n this.name = name;\n this.blocks = blocks;\n }\n\n public async toSDK(entities: string[]): Promise<string> {\n const imports =\n \"import { Api, JavaScript, PostgreSQL, Databricks, Conditional, Parallel, Loop, TryCatch } from '@superblocksteam/library'\";\n return `${imports}\\n\\nexport default new Api(\"${this.name}\", [${await Promise.all(this.blocks.map(async (block) => await block.toSDK(entities)).join(\",\"))}])`;\n }\n\n public static fromJSON(json: any, entities: string[]): Api {\n // NOTE(Frank): Would probs unmarshal this into the protobuf types and then do the conversion.\n return new Api(\n json?.metadata?.name as string,\n (json.blocks as any[]).map((block) => Block.fromJSON(block, entities)),\n );\n }\n\n public async toJSON(): Promise<JsonValue> {\n const api: {\n metadata: {\n name: string;\n timestamps: {\n updated: string;\n };\n };\n trigger: {\n application: {};\n };\n blocks: JsonValue[];\n } = {\n metadata: {\n name: this.name,\n timestamps: {\n updated: new Date().toISOString(),\n },\n },\n trigger: {\n application: {},\n },\n blocks: [],\n };\n\n api.blocks = await Promise.all(this.blocks.map(async (block) => await block.toJSON()));\n\n return api;\n }\n}\n\nfunction referenced(data: string, entities: string[]): string[] {\n return entities.reduce((acc: string[], entity: string) => {\n if (data.includes(entity)) {\n acc.push(entity);\n }\n return acc;\n }, []);\n}\n\nfunction signature(data: string, entities: string[]): string {\n return `({ ${referenced(data, entities).join(\", \")} }) => {${data}}`;\n}\n\nfunction signatureV2(data: string, entities: string[]): string {\n return `({ ${referenced(data, entities).join(\", \")} }) => ${data}`;\n}\n\n/**\n * Converts a string binding, which comprise 99% of dynamic integration fields, into a function returning an interpolated string.\n *\n * fromBinding(\"https://{{ 'goo' + 'gle' }}.com/{{ Dropdown1.value }}\", ['Dropdown1']) -> ({ Dropdown1 }) => `https://${'goo' + 'gle'}.com/${Dropdown1.value}`\n *\n * @param value - The value to convert.\n * @param entities - The master list of possible entities that could be referenced in the binding.\n *\n * @returns The converted value.\n */\nexport function fromBinding(value: string, entities: string[]): Binding<string> {\n const regex = /\\{\\{\\s*([^}]*)\\s*\\}\\}/g;\n const converted: string = value.replace(regex, (match, content) => `\\${${content.trim()}}`);\n\n // there were no bindings to convert\n if (converted === value) {\n return value;\n }\n\n const args: string[] = [`return \\`${converted}\\``]\n const references: string[] = referenced(converted, entities);\n\n // only add the arguments if we have references\n if (references.length > 0) {\n args.unshift(`{ ${references} }`);\n }\n\n return new Function(...args) as (_: State) => string;\n}\n\nasync function blockStatementToSingleLineIife(statement: BlockStatement, value: string) {\n let body = value.slice(statement.start, statement.end);\n\n return (await beautify(body)).replace(/\\s*\\n\\s*/g, ' ').replace(/\\s+/g, ' ').trim();\n}\n\nfunction createAst(value: string): Program {\n return parse(value, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n preserveParens: false,\n });\n}\n\nasync function toJSBody<T extends JsonValue>(value: ((state: State) => T), options: { block?: boolean } = { block: true }): Promise<string> {\n if (typeof value !== \"function\") {\n return `{{ ${JSON.stringify(value)} }}`\n }\n\n // parse the code\n const ast = createAst(value.toString());\n const program = ast.body[0];\n\n switch (program.type) {\n case \"ExpressionStatement\":\n const body = ((program as ExpressionStatement).expression as FunctionExpression).body;\n\n if (body.type === 'BlockStatement') {\n return await blockStatementToSingleLineIife(body, value.toString())\n }\n\n return value.toString().slice(body.start, body.end)\n case \"FunctionDeclaration\":\n let contents = value.toString().slice(program.body.start, program.body.end)\n\n if (program.body.type !== 'BlockStatement') {\n return contents\n }\n \n if (program.body.body.length === 1 && program.body.body[0].type === 'ReturnStatement') {\n return value.toString().slice(program.body.body[0].argument.start, program.body.body[0].argument.end)\n }\n\n return await blockStatementToSingleLineIife(program.body, value.toString())\n }\n\n throw new Error(\"you found a case we haven't handled yet\")\n}\n\nexport async function toJS<T extends JsonValue>(value: Binding<T>): Promise<string> {\n if (typeof value !== \"function\") {\n return `{{ ${JSON.stringify(value)} }}`\n }\n\n const body = await toJSBody(value)\n return `{{ (() => ${body})() }}`\n}\n\nexport function fromJS<T extends JsonValue>(\n value: string,\n entities: string[] = [],\n): Binding<T> {\n // determine if there are entities referenced in the value\n const refs = referenced(value, entities).join(\", \")\n\n\n // remove any surrounding whitespace\n value = value.trim()\n\n // require bindings\n if (!value.startsWith(\"{{\") || !value.endsWith(\"}}\")) {\n throw new Error(\"bindings must always be wrapped in double curly braces\");\n }\n\n // remove the double curly braces and trim whitespace\n value = value.slice(2, -2).trim();\n\n let unaryOps: string[] = [];\n let call = (createAst(value).body[0] as ExpressionStatement).expression;\n\n while (call?.type === 'UnaryExpression') {\n unaryOps.push((call as UnaryExpression).operator);\n call = (call as unknown as UnaryExpression).argument as CallExpression;\n }\n \n const fn = (call as CallExpression).callee;\n let body = value;\n let prefix = \"return \";\n\n // handle iife\n if (fn?.type === \"ArrowFunctionExpression\") {\n // i'm not happy with this but I don't see another immediate way to handle this.\n // this will result in a correct result but one that isn't idemopotent.\n if (unaryOps.length > 0) {\n return new Function(`{ ${refs} }`, `${prefix}${body}`) as (_: State) => T;\n }\n\n\n body = value.slice(fn.body.start, fn.body.end)\n\n if (fn.body.type === 'BlockStatement') {\n // drop the opening \"{\" and closing \"}\"\n body = body.slice(1, -1)\n prefix = \"\"\n } \n } else if (refs.length === 0) {\n // we're not an iife nor do we have references.\n // we might have to revisit this implementation since this technically reduces to a static value down to it's most minimal form.\n return eval(value) as T\n }\n\n // we're either an iife or a static value with refs.\n return new Function(`{ ${refs} }`, `${prefix}${body}`) as (_: State) => T;\n}\n\nexport async function beautify(code: string): Promise<string> {\n const eslint = new ESLint({\n fix: true,\n overrideConfigFile: true,\n baseConfig: {\n languageOptions: {\n parser: typescript_eslint_parser,\n parserOptions: {\n ecmaVersion: 2020,\n sourceType: \"module\",\n },\n },\n rules: {\n \"arrow-body-style\": \"error\",\n },\n },\n });\n\n code = (await eslint.lintText(code))[0]?.output || code;\n\n return await format(code, {\n parser: \"typescript\",\n tabWidth: 2,\n singleQuote: true,\n trailingComma: \"es5\",\n semi: true,\n bracketSpacing: true,\n arrowParens: \"always\",\n bracketSameLine: false,\n embeddedLanguageFormatting: \"auto\",\n quoteProps: \"as-needed\",\n insertPragma: false,\n requirePragma: false,\n useTabs: false,\n endOfLine: \"auto\",\n arrowFunctionParentheses: \"always\",\n // plugins: [\"prettier-plugin-organize-imports\"],\n });\n}\n",
|
|
10
|
+
"src/to-sdk/__template__.ts": "import { Api } from \"@superblocksteam/library\";\n\nconst json = {};\n\nconst entities = [];\n\nconst api = Api.fromJSON(json, entities);\n\nexport default {\n toSDK: async () => await api.toSDK(entities),\n};\n",
|
|
11
11
|
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"baseUrl\": \".\",\n \"outDir\": \"dist\",\n \"rootDir\": \"src\",\n \"esModuleInterop\": true,\n \"resolveJsonModule\": true,\n \"skipLibCheck\": true,\n \"types\": [\n \"node\"\n ],\n \"paths\": {\n \"@superblocksteam/library\": [\n \"src/superblocks-library-shim\"\n ]\n }\n },\n \"include\": [\n \"src\"\n ],\n \"exclude\": [\n \"node_modules\",\n \"dist\",\n ]\n}"
|
|
12
12
|
}
|
|
13
13
|
};
|