UCOD Parser

Description

This parser is designed specifically for UCOD nodes and flows. It efficiently interprets UCOD files, extracting nodes and flows to produce a corresponding JSON representation.

Usage

import { parseNode } from 'mod.ts';
/**
 * For node use
 * import { parseNode } from '@ucod-io/parser'
 */

const parsedNode = parseNode(`
export const $$name = "Addition";
export default (a: number, b: number): number => a + b;
`);
console.log(parsedNode);
{
  "name": "Addition",
  "description": "",
  "state": [],
  "config": [],
  "ports": {
    "flow": { "in": false, "out": [] },
    "data": {
      "in": [
        { "name": "a", "type": "number", "optional": false },
        { "name": "b", "type": "number", "optional": false }
      ],
      "out": [{ "name": "number", "type": "number" }]
    }
  }
}

API

Functions

parseNode

parseNode takes the code of a node as a string and returns a JSON representation of the node.

import { parseNode } from '@ucod-io/parser';
const additionCode = `
export const $$name = "Addition";
export default (a: number, b: number): number => a + b;
`;
const parsedNode = parseNode(additionCode);

parseFlow

parseFlow takes a json representation of the flow and returns a parsed flow JSON.

import { parseFlow } from '@ucod-io/parser';

const flow = {
  name: 'additionFlow',
  version: '1.0.0',
  nodes: [
    {
      id: 'core.math.addition',
      code: addition.code,
    },
    {
      id: 'core.math.randomNumber',
      code: randomNumber.code,
    },
  ],
  connections: [
    ['core.math.randomNumber', 'core.math.addition'],
    ['core.math.randomNumber', 'core.math.addition'],
  ],
};

const parsedFlow = parseFlow(flow);

parseAst

To allow the usage of the parser in the browser, the parser can be used with an AST instead of a string. The AST is generated by @swc/wasm-web.

import { parseAst } from '@ucod-io/parser';
import { useEffect, useState } from 'react';
import initSwc, { parseSync } from '@swc/wasm-web';

export default function App() {
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    async function importAndRunSwcOnMount() {
      await initSwc();
      setInitialized(true);
    }
    importAndRunSwcOnMount();
  }, []);

  function parse() {
    if (!initialized) {
      return;
    }
    const additionCode = `
  export const $$name = "Addition";
  export default (a: number, b: number): number => a + b;
  `;
    const result = parseSync(additionCode, {
      syntax: 'typescript',
      target: 'esnext',
      comments: true,
    });
    const ast = result.body;
    const parsedNode = parseAst(ast);
    console.log(parsedNode);
  }

  return (
    <div className='App'>
      <button onClick={parse}>Parse</button>
    </div>
  );
}

parseAst accepts the body of the AST as a parameter. The AST body is the result of the parseSync function of @swc/wasm-web. For more information about the SWC project, please visit the SWC website.

Interfaces

ParsedNode

interface ParsedNode {
  name: string;
  description: string;
  state: ParsedNodeProperty[];
  config: ParsedNodeProperty[];
  ports: {
    flow: {
      in: boolean;
      out: string[];
    };
    data: {
      in: ParsedNodePort[];
      out: ParsedNodePort[];
    };
  };
}
Field Type Description optional default value example
name string The name of the node false - 'Addition'
description string The description of the node true '' 'This node adds two numbers'
state ParsedNodeProperty[] The state properties of the node true [] [{name: 'result', type: 'number', optional: false}]
config ParsedNodeProperty[] The config properties of the node true [] [{name: 'result', type: 'number', optional: false}]
ports ParsedNodePorts The ports of the node false - {flow: {in: false, out: []}, data: {in: [{name: 'a', type: 'number', optional: false}], out: [{name: 'result', type: 'number'}]}}

Examples

Addition

export const $$name = 'Addition';
export const $$description = 'This node adds two numbers';
export default (a: number, b: number): number => a + b;

Http Request

import axios from 'axios';
interface Config {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
  url: string;
  headers?: Record<string, string>;
}
export const $$name = 'Http Request';
export const $$description = 'Make an HTTP request';
export const $$exec = true;

export const $$config: Config = {
  method: 'GET',
  url: 'https://example.com',
  headers: {
    'Content-Type': 'application/json',
  },
};
export const $$state = {
  data: null,
  error: null,
};
export default async (url?: string, method?: string, [err, suc]: Array<Function>): {
  data: any;
} => {
  const config: Config = {
    method: method || $$config.method,
    url: url || $$config.url,
    headers: $$config.headers,
  };
  try {
    const response = await axios(config);
    $$state.data = response.data;
    suc(response.data);
    return response.data;
  } catch (error) {
    $$state.error = error;
    err(error);
    return error;
  }
};

Importent Notes

Node Name

The Node name is required and must be exported and defined with the $$name variable. And it must be a string.

example

export const $$name = 'Addition';

Node Description

The Node description is optional but if you want to add a description to your node you have to export and define it with the $$description variable. And it must be a string. example:

export const $$description = 'This node adds two numbers';

Node State and Config

The Node state/config is optional but if you want to add a state/config to your node you have to export and define it with the $$state or/and $$config variable(s).

Typing

You can leave the typing of the state/config to the parser. It will automatically detect the type and mark them all as required.

export const $$state = {
  data: null,
  error: null,
};
export const $$config = {
  method: 'GET',
  url: 'https://example.com',
};

Inline Typing

You can also define the type of the state/config using typescript inline typing

export const $$state: {
  data: any;
  error: any;
} = {
  data: null,
  error: null,
};
export const $$config: {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
  url: string;
} = {
  method: 'GET',
  url: 'https://example.com',
};

Interface Typing

You can also define the type of the state using typescript interfaces.

interface State {
  data: any;
  error: any;
}
interface Config {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
  url: string;
}
export const $$state: State = {
  data: null,
  error: null,
};
export const $$config: Config = {
  method: 'GET',
  url: 'https://example.com',
};

Output Ports

Function Output

If you want to add an output to your node you have to explicitly define the return type of the default function.

export default (a: number, b: number): number => a + b;

you can also define the output type using typescript interfaces.

interface Output {
  data: any;
  error: any;
}
export default (a: number, b: number): Output => {
  return {
    data: a + b,
    error: null,
  };
};

Or you can define the output type using typescript inline typing.

export default (a: number, b: number): {
  data: any;
  error: any;
} => {
  return {
    data: a + b,
    error: null,
  };
};

Callback functions

Callback functions are functions that are passed as parameters to the default function. The type of the callback functions should be:

  • Array<Function>
  • Array<UCallback>
  • Function[]
  • UCallback[]

The callback functions should be called in the default function as the last parameter.

export default (a: number, b: number, [err, suc]: Array<Function>) => {};
export default (a: number, b: number, [err, suc]: Array<UCallback>) => {};
export default (a: number, b: number, [err, suc]: Function[]) => {};
export default (a: number, b: number, [err, suc]: UCallback[]) => {};

Tests

To run the UCOD-Parser execute this command

make test

Benchmarks

To run the UCOD-Parser benchmarks execute this command

make bench

Results

Benchmark Time (avg) iter/s (min..max) p75 p99 p995
Addition V1 105.44 ms/iter 9.5 (98.09 ms … 117.86 ms) 109 ms 117.86 ms 117.86 ms
Addition V2 101.83 µs/iter 9,820.7 (85.96 µs … 2.59 ms) 98.32 µs 190.21 µs 208.66 µs
Addition V5 97.24 µs/iter 10,284.2 (88.09 µs … 730.42 µs) 97.43 µs 161.98 µs 177.62 µs
summary Addition V5 1.05x faster than Addition V2 1084.36x faster than Addition V1
Benchmark Time (avg) iter/s (min..max) p75 p99 p995
Request V1 108.84 ms/iter 9.2 (101.15 ms … 126.58 ms) 113.82 ms 126.58 ms 126.58 ms
Request V2 163.29 µs/iter 6,124.0 (148.55 µs … 2.27 ms) 162.29 µs 239.24 µs 273.15 µs
Request V5 125.18 µs/iter 7,988.8 (115.45 µs … 714.9 µs) 123.63 µs 195.98 µs 223.82 µs
summary Request V5 1.3x faster than Request V2 869.52x faster than Request V1

License

Elastic License 2.0