@superblocksteam/ai-service-templates 2.0.3-next.46

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.txt ADDED
@@ -0,0 +1,87 @@
1
+ Superblocks Community Software License
2
+
3
+ This Superblocks Community License Agreement (the “Agreement”) sets forth the terms on which DayZero
4
+ Software, Inc. d/b/a Superblocks (“Superblocks”) makes available certain software made available by Superblocks
5
+ under this Agreement (the “Software”). BY INSTALLING, DOWNLOADING, ACCESSING, USING OR
6
+ DISTRIBUTING ANY OF THE SOFTWARE, YOU AGREE TO THE TERMS AND CONDITIONS OF THIS
7
+ AGREEMENT.IF YOU DO NOT AGREE TO SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE
8
+ SOFTWARE. IF YOU ARE RECEIVING THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU
9
+ REPRESENT AND WARRANT THAT YOU HAVE THE ACTUAL AUTHORITY TO AGREE TO THE
10
+ TERMS AND CONDITIONS OF THIS AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you,
11
+ an individual, or the entity on whose behalf you are receiving the Software.
12
+
13
+ 1. LICENSE GRANT AND CONDITIONS.
14
+
15
+ 1.1 License. Subject to the terms and conditions of this Agreement, Superblocks hereby grants to Licensee a
16
+ non-exclusive, royalty-free, worldwide, non-transferable, non-sublicenseable license during the term of this
17
+ Agreement to: (a) use the Software; (b) prepare modifications and derivative works of the Software; (c)
18
+ distribute the Software (including without limitation in source code or object code form); and (d) reproduce
19
+ copies of the Software (the “License”). Licensee is not granted the right to, and Licensee shall not, exercise the
20
+ License for an Excluded Purpose. For purposes of this Agreement, “Excluded Purpose” means making
21
+ available any software-as-a-service, platform-as-a-service, infrastructure-as-a-service or other similar online
22
+ service that competes with Superblocks’ products or services.
23
+
24
+ 1.2 Conditions. In consideration of the License, Licensee’s distribution of the Software is subject to the
25
+ following conditions:
26
+
27
+ a. Licensee must cause any Software modified by Licensee to carry prominent notices stating that Licensee
28
+ modified the Software.
29
+ b. On each Software copy, Licensee shall reproduce and not remove or alter all Superblocks or third party
30
+ copyright or other proprietary notices contained in the Software, and Licensee must provide the notice
31
+ below with each copy.
32
+
33
+ “This software is made available by DayZero Software, Inc. d/b/a Superblocks, under the terms of the
34
+ Superblocks Community License Agreement. © 2022 DayZero Software, Inc.”
35
+
36
+ 1.3 Licensee Modifications. Licensee may add its own copyright notices to modifications made by Licensee
37
+ and may provide additional or different license terms and conditions for use, reproduction, or distribution of
38
+ Licensee’s modifications. While redistributing the Software or modifications thereof, Licensee may choose to
39
+ offer, for a fee or free of charge, support, warranty, indemnity, or other obligations. Licensee, and not
40
+ Superblocks, will be responsible for any such obligations.
41
+
42
+ 1.4 No Sublicensing. The License does not include the right to sublicense the Software, however, each recipient
43
+ to which Licensee provides the Software may exercise the Licenses so long as such recipient agrees to the terms
44
+ and conditions of this Agreement.
45
+
46
+ 2. TERM AND TERMINATION. This Agreement will continue unless and until earlier terminated as set forth
47
+ herein. If Licensee breaches any of its conditions or obligations under this Agreement, this Agreement will
48
+ terminate automatically and the License will terminate automatically and permanently.
49
+
50
+ 3. INTELLECTUAL PROPERTY. As between the parties, Superblocks will retain all right, title, and interest in
51
+ the Software, and all intellectual property rights therein. Superblocks hereby reserves all rights not expressly
52
+ granted to Licensee in this Agreement. Superblocks hereby reserves all rights in its trademarks and service
53
+ marks, and no licenses therein are granted in this Agreement.
54
+
55
+ 4. DISCLAIMER. SUPERBLOCKS HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND
56
+ CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY
57
+ DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR
58
+ PURPOSE, WITH RESPECT TO THE SOFTWARE.
59
+
60
+ 5. LIMITATION OF LIABILITY. SUPERBLOCKS WILL NOT BE LIABLE FOR ANY DAMAGES OF
61
+ ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL,
62
+ SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY
63
+ THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL APPLY TO
64
+ THE EXTENT PERMITTED BY APPLICABLE LAW.
65
+
66
+ 6. GENERAL.
67
+
68
+ 6.1  Governing Law. This Agreement will be governed by and interpreted in accordance with the laws of the
69
+ state of California, without reference to its conflict of laws principles. If Licensee is located within the United
70
+ States, all disputes arising out of this Agreement are subject to the exclusive jurisdiction of courts located in San
71
+ Mateo County, California. USA. If Licensee is located outside of the United States, any dispute, controversy or
72
+ claim arising out of or relating to this Agreement will be referred to and finally determined by arbitration in
73
+ accordance with the JAMS International Arbitration Rules. The tribunal will consist of one neutral arbitrator.
74
+ The place of arbitration will be Palo Alto, California. The language to be used in the arbitral proceedings will be
75
+ English. Judgment upon the award rendered by the arbitrator may be entered in any court having jurisdiction
76
+ thereof.
77
+
78
+ 6.2.  Assignment. Licensee may not assign its rights under this Agreement to any third party. Superblocks may
79
+ freely assign its rights under this Agreement to any third party.
80
+
81
+ 6.3.  Other.  This Agreement is the entire agreement between the parties regarding the subject matter hereof.
82
+ No amendment or modification of this Agreement will be valid or binding upon the parties unless made in
83
+ writing and signed by the duly authorized representatives of both parties. In the event that any provision,
84
+ including without limitation any condition, of this Agreement is held to be unenforceable, this Agreement and
85
+ all licenses and rights granted hereunder will immediately terminate. Waiver by Superblocks of a breach of any
86
+ provision of this Agreement or the failure by Superblocks to exercise any right hereunder will not be construed
87
+ as a waiver of any subsequent breach of that right or as a waiver of any other right.
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@superblocksteam/ai-service-templates",
3
+ "version": "2.0.3-next.46",
4
+ "type": "module",
5
+ "dependencies": {},
6
+ "exports": {
7
+ "./reify-template": {
8
+ "types": "./reify-template.d.ts",
9
+ "default": "./reify-template.js"
10
+ },
11
+ "./templates": {
12
+ "types": "./templates.d.ts",
13
+ "default": "./templates.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "reify-template.js",
18
+ "templates.js",
19
+ "scripts"
20
+ ],
21
+ "devDependencies": {
22
+ "@types/node": "^22.15.17",
23
+ "vitest": "^3.0.9"
24
+ },
25
+ "scripts": {
26
+ "build": "node ./scripts/build.js",
27
+ "postinstall": "pnpm run build",
28
+ "test": "vitest run"
29
+ }
30
+ }
@@ -0,0 +1,47 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Recreates a directory structure with files and their content based on a file map.
6
+ *
7
+ * @param {string} targetDirectoryPath - The path to the target directory where the structure will be created.
8
+ * @param {object} fileMap - An object where keys are relative file paths
9
+ * (e.g., "src/index.js", "assets/image.png")
10
+ * and values are the string content for those files.
11
+ * @throws {Error} If targetDirectoryPath is not a string or fileMap is not an object.
12
+ */
13
+ export function reifyTemplate(targetDirectoryPath, fileMap) {
14
+ if (typeof targetDirectoryPath !== 'string') {
15
+ throw new Error('targetDirectoryPath must be a string.');
16
+ }
17
+ if (typeof fileMap !== 'object' || fileMap === null) {
18
+ throw new Error('fileMap must be a non-null object.');
19
+ }
20
+
21
+ const resolvedTargetDirectory = path.resolve(targetDirectoryPath);
22
+ console.log(`Recreating structure in: ${resolvedTargetDirectory}`);
23
+
24
+ for (const relativeFilePath in fileMap) {
25
+ if (Object.hasOwnProperty.call(fileMap, relativeFilePath)) {
26
+ const content = fileMap[relativeFilePath];
27
+ // Ensure content is a string, as it's being written as utf8
28
+ const fileContent = typeof content === 'string' ? content : String(content);
29
+
30
+ const absoluteFilePath = path.join(resolvedTargetDirectory, relativeFilePath);
31
+ const directoryForFile = path.dirname(absoluteFilePath);
32
+
33
+ try {
34
+ if (!fs.existsSync(directoryForFile)) {
35
+ fs.mkdirSync(directoryForFile, { recursive: true });
36
+ }
37
+
38
+ fs.writeFileSync(absoluteFilePath, fileContent, 'utf8');
39
+
40
+ } catch (error) {
41
+ console.error(`Error processing file ${relativeFilePath} at ${absoluteFilePath}:`, error.message);
42
+ throw error;
43
+ }
44
+ }
45
+ }
46
+ console.log(`Successfully recreated file structure in ${resolvedTargetDirectory}.`);
47
+ }
@@ -0,0 +1,81 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const packageDir = path.join(__dirname, '..');
9
+ const outputFile = path.join(packageDir, 'templates.js');
10
+ const subdirectoriesData = {};
11
+
12
+ const deniedSubdirectories = [
13
+ "node_modules",
14
+ "scripts",
15
+ ];
16
+
17
+ const deniedFileExtensions = [
18
+ ".test.ts",
19
+ ".gitkeep",
20
+ ];
21
+
22
+ /**
23
+ * Recursively collects files and their content from a directory.
24
+ * @param {string} currentDirPath - The current directory to scan.
25
+ * @param {string} basePathForRelative - The base path to make file paths relative to.
26
+ * @param {object} filesMap - The object to store file paths and content.
27
+ */
28
+ function collectFiles(currentDirPath, basePathForRelative, filesMap) {
29
+ const entries = fs.readdirSync(currentDirPath);
30
+ for (const entry of entries) {
31
+ const fullPath = path.join(currentDirPath, entry);
32
+ const stat = fs.statSync(fullPath);
33
+
34
+ if (stat.isFile()) {
35
+ // Skip thebuild script itself or the output file.
36
+ if (fullPath === __filename || fullPath === outputFile) {
37
+ continue;
38
+ }
39
+ // Skip files with denied extensions.
40
+ if (deniedFileExtensions.some(ext => entry.endsWith(ext))) {
41
+ continue;
42
+ }
43
+ // Store paths relative to the subdirectory's root.
44
+ const relativePath = path.relative(basePathForRelative, fullPath);
45
+ const content = fs.readFileSync(fullPath, 'utf8');
46
+ filesMap[relativePath] = content;
47
+ } else if (stat.isDirectory()) {
48
+ // Standard directories to ignore during recursion.
49
+ if (entry === 'node_modules' || entry.startsWith('.')) {
50
+ continue;
51
+ }
52
+ collectFiles(fullPath, basePathForRelative, filesMap); // Recurse
53
+ }
54
+ }
55
+ }
56
+
57
+ // Iterate over items in the package directory to find subdirectories.
58
+ const topLevelEntries = fs.readdirSync(packageDir);
59
+ for (const entryName of topLevelEntries) {
60
+ const entryPath = path.join(packageDir, entryName);
61
+ const stat = fs.statSync(entryPath);
62
+
63
+ // Process only directories, ignoring hidden ones,
64
+ // AND check against the denylist.
65
+ if (
66
+ stat.isDirectory() &&
67
+ !entryName.startsWith('.') &&
68
+ !deniedSubdirectories.includes(entryName)
69
+ ) {
70
+ subdirectoriesData[entryName] = {};
71
+ // The base for relative paths is the subdirectory itself.
72
+ collectFiles(entryPath, entryPath, subdirectoriesData[entryName]);
73
+ }
74
+ }
75
+
76
+ const outputContent = `// This file is auto-generated by the build script. Do not edit directly.
77
+ export const templates = ${JSON.stringify(subdirectoriesData, null, 2)};
78
+ `;
79
+
80
+ fs.writeFileSync(outputFile, outputContent, 'utf8');
81
+ console.log(`Successfully generated ${path.relative(process.cwd(), outputFile)}`);
package/templates.js ADDED
@@ -0,0 +1,13 @@
1
+ // This file is auto-generated by the build script. Do not edit directly.
2
+ export const templates = {
3
+ "api-builder-to-yaml": {
4
+ "bootstrap.mjs": "import { register } from \"node:module\";\nimport { pathToFileURL } from \"node:url\";\n\nregister(\"./shim-loader.mjs\", pathToFileURL(\"./\"));\n",
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
+ "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",
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
+ }
13
+ };