├── .github └── workflows │ ├── publish.yaml │ └── tests.yaml ├── .gitignore ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── cli │ ├── cmd │ │ ├── em-wrapper.ts │ │ └── functions │ │ │ ├── deploy.ts │ │ │ ├── evaluate │ │ │ ├── app-simulator.ts │ │ │ ├── arweave-evaluate.ts │ │ │ ├── common.ts │ │ │ ├── evaluate.ts │ │ │ ├── fetch-bundle.ts │ │ │ └── readers │ │ │ │ ├── arweave │ │ │ │ ├── arweave-reader.ts │ │ │ │ └── graphql.ts │ │ │ │ └── reader.interface.ts │ │ │ ├── function-test.ts │ │ │ ├── model.ts │ │ │ ├── read.ts │ │ │ └── write.ts │ ├── index.ts │ └── utils │ │ └── common.ts ├── common │ ├── em.ts │ ├── exceptions │ │ ├── deployOpFailure.ts │ │ ├── readFailure.ts │ │ └── writeOpFailure.ts │ ├── functions │ │ ├── deploy.ts │ │ ├── read.ts │ │ └── write.ts │ ├── model.ts │ ├── utils │ │ └── commons.ts │ └── vars.ts ├── index.ts └── test │ └── test-function.ts ├── tests ├── cli-function.tests.ts └── testdata │ ├── user-registry-init.json │ └── user-registry.js ├── tsconfig.build.json └── tsconfig.json /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publishing Package 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | # Setup .npmrc file to publish to npm 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: '16.x' 14 | registry-url: 'https://registry.npmjs.org' 15 | - run: npm install 16 | - run: npm install -g typescript 17 | - run: npm run deliver 18 | env: 19 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - dev 7 | - main 8 | - master 9 | - "releases/*" 10 | 11 | jobs: 12 | # unit tests 13 | tests: 14 | name: "Unit tests" 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - run: npm install 19 | - run: npm run test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | .env 37 | /ar-local/wallet.json 38 | **/wallet.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3em Logo 3 |

Execution Machine JS SDK

4 | 5 |

6 | A Javascript SDK to interact with The Execution Machine (EXM) 7 |

8 |

9 | 10 | ## General 11 | 12 | EXM can be used through a JS SDK in order to perform operations with the platform. To install the SDK. 13 | 14 | ```shell 15 | $ npm i @execution-machine/sdk 16 | ``` 17 | 18 | After installing the npm package as described above. You can initialize EXM by doing the following 19 | 20 | ```javascript 21 | // import { Exm } from '@execution-machine/sdk' 22 | const { Exm } = require('@execution-machine/sdk'); 23 | 24 | const exmInstance = new Exm({ token: 'MY_EXM_TOKEN' }); 25 | ``` 26 | 27 | ## Docs 28 | 29 | Documentation for SDK is available online. Please [Click here](https://docs.exm.dev/trustless-serverless-functions/introduction/js-sdk) for more information. -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testMatch: [ 6 | "**/tests/**/*.tests.ts" 7 | ] 8 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@execution-machine/sdk", 3 | "version": "0.1.9", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "jest", 10 | "deliver": "npm run build && npm publish --access public" 11 | }, 12 | "bin": { 13 | "exm": "dist/cli/index.js", 14 | "exm-cli": "dist/cli/index.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/exmbuild/exm-js-sdk.git" 19 | }, 20 | "author": "", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/exmbuild/exm-js-sdk/issues" 24 | }, 25 | "homepage": "https://github.com/exmbuild/exm-js-sdk#readme", 26 | "dependencies": { 27 | "@types/node": "^18.0.0", 28 | "chalk": "^4.1.2", 29 | "cli-box": "^6.0.10", 30 | "commander": "^9.3.0", 31 | "dotenv": "^16.0.1", 32 | "find-cache-dir": "^3.3.2", 33 | "isomorphic-fetch": "^3.0.0", 34 | "prompt-confirm": "^2.0.4", 35 | "three-em-0-3-09": "npm:@three-em/node@^0.3.09", 36 | "three-em-0-3-12": "npm:@three-em/node@^0.3.12", 37 | "three-em-0-3-13": "npm:@three-em/node@^0.3.13", 38 | "three-em-0-3-14": "npm:@three-em/node@^0.3.14", 39 | "three-em-0-3-15": "npm:@three-em/node@^0.3.15", 40 | "three-em-0-3-16": "npm:@three-em/node@^0.3.16", 41 | "three-em-0-3-17": "npm:@three-em/node@^0.3.17", 42 | "three-em-0-3-20": "npm:@three-em/node@^0.3.20", 43 | "three-em-0-3-21": "npm:@three-em/node@^0.3.21", 44 | "three-em-0-3-22": "npm:@three-em/node@^0.3.22" 45 | }, 46 | "devDependencies": { 47 | "@types/jest": "^29.1.0", 48 | "jest": "^29.1.1", 49 | "ts-jest": "^29.0.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/cli/cmd/em-wrapper.ts: -------------------------------------------------------------------------------- 1 | import {Exm} from "../../common/em"; 2 | 3 | export const getEmToken = () => process.env.EXM_TOKEN || process.env.TOKEN; 4 | 5 | export const em = new Exm({ 6 | token: getEmToken() 7 | }); -------------------------------------------------------------------------------- /src/cli/cmd/functions/deploy.ts: -------------------------------------------------------------------------------- 1 | import {readFileSync} from "fs"; 2 | import {DeployOpts} from "./model"; 3 | import {ContractType, Tag} from "../../../common/model"; 4 | import {em} from "../em-wrapper"; 5 | import {error} from "../../utils/common"; 6 | import {figureOutContractType} from "../../../common/utils/commons"; 7 | 8 | 9 | const Confirm = require('prompt-confirm'); 10 | const chalk = require('chalk'); 11 | 12 | const boxMarks = { 13 | nw: '╭', 14 | n: '─', 15 | ne: '╮', 16 | e: '│', 17 | se: '╯', 18 | s: '─', 19 | sw: '╰', 20 | w: '│' 21 | }; 22 | 23 | export const functionDeployCmd = async (opts: DeployOpts) => { 24 | let contentType: ContractType = figureOutContractType(opts.type || opts.src, opts.type !== undefined); 25 | let initState: string; 26 | 27 | if (opts.initState) { 28 | initState = opts.initState; 29 | } else if (opts.initStateSrc) { 30 | initState = readFileSync(opts.initStateSrc, 'utf8'); 31 | } else { 32 | error(`Deployment requires --init-state or --init-state-src to be passed`); 33 | } 34 | 35 | let contractData: Uint8Array; 36 | 37 | if(!opts.token) { 38 | error('EXM Token (--token) is required to deploy functions through EXM'); 39 | } 40 | 41 | if(opts.contractTx) { 42 | const fetchContractSourceTx = await fetch(`https://arweave.net/${opts.contractTx}`); 43 | if(fetchContractSourceTx.ok) { 44 | contractData = new Uint8Array(await fetchContractSourceTx.arrayBuffer()); 45 | } else { 46 | console.error(`Source function transaction ${opts.contractTx} is invalid or does not exist.`); 47 | } 48 | } else if(opts.src) { 49 | contractData = await readFileSync(opts.src); 50 | } else { 51 | error(`Deployment requires --contract-tx or --src`); 52 | } 53 | 54 | const confirmation = await new Confirm(`Do you want to deploy this function?`).run(); 55 | if(confirmation) { 56 | 57 | if(opts.token) { 58 | em.changeToken(opts.token); 59 | } 60 | 61 | const Box = require('cli-box'); 62 | const beginDeployment = await em.functions.deploy(contractData, initState, contentType); 63 | 64 | const resultBox = new Box({ 65 | w: 100, 66 | h: 4, 67 | stringify: false, 68 | marks: boxMarks, 69 | hAlign: 'left', 70 | vAlign: 'top' 71 | }, `Function deployed 🎉 72 | ▸ EXM Function ID : ${beginDeployment.id} 73 | ▸ EXM Function Source : https://arweave.net/${beginDeployment.id} 74 | ▸ Function URL : https://${beginDeployment.id}.exm.run`); 75 | 76 | console.log(resultBox.stringify()); 77 | 78 | const functionUrlExample = new Box({ 79 | w: 100, 80 | h: 8, 81 | stringify: false, 82 | marks: boxMarks, 83 | hAlign: 'left', 84 | vAlign: 'top' 85 | }, `${chalk.blue('Id: ')} ${beginDeployment.id} 86 | 87 | curl --location --request POST 'https://${beginDeployment.id}.exm.run' 88 | --header 'Content-Type: application/json' 89 | --data-raw '{}' ${chalk.blue('<= Any JSON body')} 90 | 91 | Documentation: https://docs.exm.dev/trustless-serverless-functions/function-urls 92 | `); 93 | 94 | console.log(functionUrlExample.stringify()); 95 | } 96 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/app-simulator.ts: -------------------------------------------------------------------------------- 1 | import {simulateContract, SimulateExecutionContext} from "three-em-0-3-16"; 2 | 3 | export interface ExmRunContext extends SimulateExecutionContext { 4 | version: string; 5 | [k: string]: any 6 | } 7 | 8 | export const runExmFunction = (context: ExmRunContext) => { 9 | const isVersion = (executionVersion: string) => (executorVersion: string) => { 10 | return (executionVersion.includes(`^${executorVersion}`) || executionVersion.includes(executorVersion)) 11 | } 12 | 13 | if(isVersion(context.version)('0.3.12')) { 14 | const simulateContract = require('three-em-0-3-12').simulateContract; 15 | return simulateContract(context.contractId, context.interactions, context.contractInitState, context.maybeConfig, context.maybeCache, context.maybeBundledContract, context.maybeSettings, context.maybeExmContext); 16 | } else if(isVersion(context.version)('0.3.13')) { 17 | const simulateContract = require('three-em-0-3-13').simulateContract; 18 | return simulateContract(context); 19 | } else if(isVersion(context.version)('0.3.14')) { 20 | const simulateContract = require('three-em-0-3-14').simulateContract; 21 | return simulateContract(context); 22 | } else if(isVersion(context.version)('0.3.15')) { 23 | const simulateContract = require('three-em-0-3-15').simulateContract; 24 | return simulateContract(context); 25 | } else if(isVersion(context.version)('0.3.16')) { 26 | const simulateContract = require('three-em-0-3-16').simulateContract; 27 | return simulateContract(context); 28 | } else if(isVersion(context.version)('0.3.17')) { 29 | const simulateContract = require('three-em-0-3-16').simulateContract; 30 | return simulateContract(context); 31 | } else if(isVersion(context.version)('0.3.20')) { 32 | const simulateContract = require('three-em-0-3-16').simulateContract; 33 | return simulateContract(context); 34 | } else { 35 | const simulateContract = require('three-em-0-3-09').simulateContract; 36 | return simulateContract(context.contractId, context.interactions, context.contractInitState, context.maybeConfig, context.maybeCache, context.maybeBundledContract, context.maybeSettings, context.maybeExmContext); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/arweave-evaluate.ts: -------------------------------------------------------------------------------- 1 | import {ArweaveReader, Paginator} from "./readers/arweave/arweave-reader"; 2 | import {buildContext, CommonEvaluateOpts} from "./common"; 3 | import {cacheBundle} from "./fetch-bundle"; 4 | import {runExmFunction} from "./app-simulator"; 5 | 6 | export const arweaveEvaluate = async (opts: CommonEvaluateOpts) => { 7 | const arweaveReader = new ArweaveReader(); 8 | 9 | let fetchBundles = true; 10 | let cursor = undefined; 11 | let state = undefined; 12 | 13 | while (fetchBundles) { 14 | let paginator: Paginator = { exmFunctionId: opts.exmFunctionId }; 15 | 16 | if(cursor) { 17 | paginator.after = cursor; 18 | } 19 | 20 | const bundles = await arweaveReader.fetchBundles(paginator); 21 | if(bundles.length > 0) { 22 | const lastBundle = bundles[bundles.length - 1]; 23 | cursor = lastBundle.after; 24 | } 25 | 26 | for (const bundleElement of bundles) { 27 | let bundleData = await cacheBundle(bundleElement.id, opts.cache); 28 | const run = await runExmFunction({ 29 | version: bundleElement.threeEmExecutorVersion, 30 | contractId: opts.exmFunctionId, 31 | interactions: bundleData.entities.map((i) => i.raw), 32 | contractInitState: JSON.stringify(state?.state), 33 | maybeConfig: undefined, 34 | maybeCache: false, 35 | maybeBundledContract: bundleElement.isExmFunctionExmDeployed, 36 | maybeSettings: { 37 | 'LAZY_EVALUATION': true 38 | }, 39 | maybeExmContext: buildContext(bundleData.exmContext) 40 | }); 41 | state = run; 42 | } 43 | 44 | if(bundles.length <= 0) { 45 | fetchBundles = false; 46 | } 47 | } 48 | 49 | return state; 50 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/common.ts: -------------------------------------------------------------------------------- 1 | import {Tag} from "../../../../common/model"; 2 | 3 | export interface ExmContext { 4 | requests: Record 5 | } 6 | 7 | export interface BundleTxEntity { 8 | raw: { 9 | id: string, 10 | owner: string, 11 | quantity: string, 12 | reward: string, 13 | tags: Tag[], 14 | input: any 15 | }, 16 | metadata: { 17 | createdBy: string, 18 | createdWhen: number, 19 | executorVersion: string, 20 | internalId: number; 21 | } 22 | } 23 | 24 | export interface BundleBody { 25 | entities: BundleTxEntity[], 26 | exmContext: ExmContext 27 | } 28 | 29 | export interface CommonEvaluateOpts { 30 | exmFunctionId: string 31 | cache?: boolean 32 | } 33 | 34 | export const buildContext = (exmContext: ExmContext) => { 35 | if(exmContext) { 36 | if(!exmContext.requests) { 37 | exmContext.requests = {}; 38 | } 39 | return exmContext; 40 | } else { 41 | return { 42 | requests: {} 43 | } 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/evaluate.ts: -------------------------------------------------------------------------------- 1 | import {arweaveEvaluate} from "./arweave-evaluate"; 2 | 3 | export const evaluateExmFunction = async (txId: string) => { 4 | const readState = JSON.stringify((await arweaveEvaluate({ 5 | exmFunctionId: txId, 6 | cache: true, 7 | })).state, null, 2); 8 | console.log(readState); 9 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/fetch-bundle.ts: -------------------------------------------------------------------------------- 1 | import findCacheDir from "find-cache-dir"; 2 | import { writeFile } from 'fs'; 3 | import path from 'path'; 4 | import {BundleBody} from "./common"; 5 | 6 | let __cacheDir = undefined; 7 | 8 | export const cacheBundle = async (bundleId: string, cache?: boolean): Promise => { 9 | if(cache) { 10 | __cacheDir = findCacheDir({ 11 | name: 'exm', 12 | create: true 13 | }); 14 | } 15 | 16 | const fetchBundle = await fetch(`https://arweave.net/${bundleId}`); 17 | if(fetchBundle.ok) { 18 | const bundleData = await fetchBundle.json(); 19 | if (cache) { 20 | if(__cacheDir) { 21 | writeFile(path.join(__cacheDir, `${bundleId}.json`), JSON.stringify(bundleData), () => { 22 | }); 23 | } else { 24 | console.log(`Cache Dir not found. Skipping ⏭️\n`); 25 | } 26 | } 27 | return bundleData; 28 | } 29 | 30 | throw new Error(`Bundle ${bundleId} could not be fetched`); 31 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/readers/arweave/arweave-reader.ts: -------------------------------------------------------------------------------- 1 | import {BundleQueryResponse, ReaderInterface} from "../reader.interface"; 2 | import {BundleBody} from "../../common"; 3 | import {postRequest} from "../../../../../../common/utils/commons"; 4 | import GQLResultInterface from "./graphql"; 5 | 6 | export interface Paginator { 7 | exmFunctionId: string; 8 | after?: string; 9 | } 10 | 11 | export class ArweaveReader implements ReaderInterface { 12 | 13 | async fetchBundles(paginator: Paginator): Promise[]> { 14 | const req = await postRequest(`https://arweave.net/graphql`, this.buildGqlQuery(paginator.exmFunctionId, paginator.after)); 15 | const jsonResp: GQLResultInterface = await req.json(); 16 | return jsonResp.data.transactions.edges.map((i) => ({ 17 | id: i.node.id, 18 | pseudoTimestamp: Number(i.node.tags.find((tag) => tag.name === 'Pseudo-Timestamp')!.value), 19 | functionId: (i.node?.tags || []).find((tag) => tag.name === 'Function')!.value, 20 | blockHeight: i.node?.block?.height || 0, 21 | threeEmExecutorVersion: (i.node?.tags || []).find((tag) => tag.name === '3EM-Executor-Version')?.value!, 22 | after: i.cursor, 23 | isExmFunctionExmDeployed: (i.node?.tags || []).find((tag) => tag.name === '3EM-Function-Deployed')?.value === 'true' || true 24 | })); 25 | } 26 | 27 | private buildGqlQuery(exmFunctionId: string, after?: string) { 28 | const query = `# Write your query or mutation here 29 | query { 30 | transactions( 31 | tags: [{ name: "App-Name", values: ["EM"] }, { name: "Type", values: ["Serverless"] }, { name: "Function", values: ["${exmFunctionId}"] }] 32 | owners: ["kljUVhFCOmFqIow8veT8WYFu65RrOcfzL3m672eatao"] 33 | sort: HEIGHT_ASC 34 | first: 100 35 | ${after ? `after: "${after}"` : ''} 36 | ) { 37 | edges { 38 | node { 39 | id 40 | owner { 41 | address 42 | } 43 | block { 44 | height 45 | } 46 | tags { 47 | name 48 | value 49 | } 50 | } 51 | cursor 52 | } 53 | pageInfo { 54 | hasNextPage 55 | } 56 | } 57 | }`; 58 | return { 59 | query, 60 | variables: {} 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/readers/arweave/graphql.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface GQLPageInfoInterface { 4 | hasNextPage: boolean; 5 | } 6 | 7 | export interface GQLOwnerInterface { 8 | address: string; 9 | key: string; 10 | } 11 | 12 | export interface GQLAmountInterface { 13 | winston: string; 14 | ar: string; 15 | } 16 | 17 | export interface GQLMetaDataInterface { 18 | size: number; 19 | type: string; 20 | } 21 | 22 | export interface GQLTagInterface { 23 | name: string; 24 | value: string; 25 | } 26 | 27 | export interface GQLBlockInterface { 28 | id: string; 29 | timestamp: number; 30 | height: number; 31 | previous: string; 32 | } 33 | 34 | export interface GQLNodeInterface { 35 | id: string; 36 | anchor: string; 37 | signature: string; 38 | recipient: string; 39 | owner: GQLOwnerInterface; 40 | fee: GQLAmountInterface; 41 | quantity: GQLAmountInterface; 42 | data: GQLMetaDataInterface; 43 | tags: GQLTagInterface[]; 44 | block: GQLBlockInterface; 45 | parent: { 46 | id: string; 47 | }; 48 | bundledIn: { 49 | id: string; 50 | }; 51 | } 52 | 53 | export interface GQLEdgeInterface { 54 | cursor: string; 55 | node: GQLNodeInterface; 56 | } 57 | 58 | export interface GQLTransactionsResultInterface { 59 | pageInfo: GQLPageInfoInterface; 60 | edges: GQLEdgeInterface[]; 61 | } 62 | 63 | export default interface GQLResultInterface { 64 | data: { 65 | transactions: GQLTransactionsResultInterface; 66 | }; 67 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/evaluate/readers/reader.interface.ts: -------------------------------------------------------------------------------- 1 | import { BundleBody } from "../common"; 2 | 3 | export interface BundleQueryResponse { 4 | id: string; 5 | pseudoTimestamp: number; 6 | functionId: string; 7 | blockHeight: number; 8 | threeEmExecutorVersion: string; 9 | isExmFunctionExmDeployed: boolean; 10 | after?: After 11 | } 12 | 13 | export interface ReaderInterface { 14 | fetchBundles: (paginator: Paginator) => Promise[]> 15 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/function-test.ts: -------------------------------------------------------------------------------- 1 | import {FunctionTestOpts} from "./model"; 2 | import {error} from "../../utils/common"; 3 | import {readFileSync} from "fs"; 4 | import {figureOutContractType} from "../../../common/utils/commons"; 5 | import {createWrite, TestFunction} from "../../../test/test-function"; 6 | import {ContractType} from "../../../common/model"; 7 | import {SimulateContractType} from "three-em-0-3-21"; 8 | 9 | export const functionTest = async (opts: FunctionTestOpts) => { 10 | if(!opts.src) { 11 | error("A function source path must be specified"); 12 | return; 13 | } 14 | 15 | let initState = undefined; 16 | 17 | if(opts.initStateSrc) { 18 | initState = readFileSync(opts.initStateSrc, 'utf8') 19 | } else { 20 | if (!opts.initState) { 21 | error("Initial state must be specified and it must be a JSON-valid expression"); 22 | return; 23 | } 24 | initState = opts.initState; 25 | } 26 | 27 | if(!opts.input || opts.input.length <= 0) { 28 | error("Single or multiple inputs must be specified"); 29 | return; 30 | } 31 | 32 | const bufferSource = readFileSync(opts.src); 33 | const contractType = figureOutContractType(opts.type || opts.src, opts.type !== undefined); 34 | 35 | const writeOperations = opts.input.map((input) => createWrite(input, [], undefined, true)); 36 | const testFunction = await TestFunction({ 37 | functionType: contractType == ContractType.WASM ? SimulateContractType.WASM : SimulateContractType.JAVASCRIPT, 38 | functionInitState: JSON.parse(initState), 39 | functionSource: bufferSource, 40 | writes: writeOperations 41 | }); 42 | 43 | console.log(JSON.stringify({ 44 | state: testFunction.state, 45 | validity: testFunction.validity 46 | }, null, 2)); 47 | return testFunction; 48 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/model.ts: -------------------------------------------------------------------------------- 1 | import {ContractType} from "../../../common/model"; 2 | 3 | export interface DeployOpts { 4 | useArweave?: boolean; 5 | wallet?: string; 6 | src?: string; 7 | contractTx?: string; 8 | initState?: string; 9 | initStateSrc?: string; 10 | token?: string; 11 | type?: "wasm" | "js" | "evm" | ContractType; 12 | } 13 | 14 | export interface FunctionTestOpts { 15 | src?: string; 16 | input: Array 17 | initState?: string 18 | initStateSrc?: string; 19 | type?: string; 20 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/read.ts: -------------------------------------------------------------------------------- 1 | import {em} from "../em-wrapper"; 2 | 3 | export const functionReadCmd = async (txId: string) => { 4 | await em.functions.read(txId).then((r) => { 5 | console.log(JSON.stringify(r, null, 2)); 6 | }); 7 | } -------------------------------------------------------------------------------- /src/cli/cmd/functions/write.ts: -------------------------------------------------------------------------------- 1 | import { parse } from "dotenv"; 2 | import {Tag} from "../../../common/model"; 3 | import {em} from "../em-wrapper"; 4 | import {WriteOpFailure} from "../../../common/exceptions/writeOpFailure"; 5 | 6 | export const functionWriteCmd = async (txId: string, opts: any) => { 7 | const tagsObj = parse(opts.tags.join('\n')); 8 | const tagsArray: Tag[] = Object.keys(tagsObj).map((i) => ({ 9 | name: i, 10 | value: tagsObj[i] 11 | })); 12 | 13 | let input: any; 14 | 15 | try { 16 | const parsedInput = JSON.parse(opts.input); 17 | input = JSON.stringify(parsedInput); 18 | } catch(e) { 19 | input = opts.input; 20 | } 21 | 22 | if(opts.token) { 23 | em.changeToken(opts.token); 24 | } 25 | 26 | await em.functions.writeRaw(txId, [ 27 | { 28 | input, 29 | tags: tagsArray 30 | } 31 | ]).then(({ data: { pseudoId, execution: { state, validity } } }) => { 32 | if (validity[pseudoId]) { 33 | console.log( 34 | `Write query ${pseudoId} (pseudo-id) was successfully executed`, 35 | ); 36 | } else { 37 | console.log( 38 | `Write query ${pseudoId} (pseudo-id) could not be executed`, 39 | ); 40 | } 41 | if(opts.showOutput) { 42 | console.log(`\n${JSON.stringify({ state, validity}, null, 2)}`) 43 | } 44 | }).catch((e) => { 45 | if(e instanceof WriteOpFailure) { 46 | if(e.statusCode === 403) { 47 | console.log('403: Write operation could not be completed because the resource could not be accessed or token is invalid.'); 48 | } else { 49 | console.error(e.message); 50 | } 51 | } else { 52 | console.error(e.message); 53 | } 54 | process.exit(1); 55 | }); 56 | } -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "commander"; 2 | import {functionReadCmd} from "./cmd/functions/read"; 3 | import {functionWriteCmd} from "./cmd/functions/write"; 4 | import {functionDeployCmd} from "./cmd/functions/deploy"; 5 | import {evaluateExmFunction} from "./cmd/functions/evaluate/evaluate"; 6 | import {functionTest} from "./cmd/functions/function-test"; 7 | 8 | const program = new Command(); 9 | 10 | program.name('exm') 11 | .description('A CLI to interact with the Execution Machine.') 12 | .version('0.1.5'); 13 | 14 | program.command('function:read') 15 | .alias('fx:r') 16 | .description('Read the state of a function in EXM.') 17 | .argument('', 'Arweave ID of function.') 18 | .action(functionReadCmd); 19 | 20 | program.command('function:write') 21 | .alias('fx:w') 22 | .description('Read the state of a function in EXM.') 23 | .argument('', 'Arweave ID of function.') 24 | .requiredOption('-i, --input ', 'Input to be passed to the function') 25 | .option('-t, --tags ', 'Tags to be used during write operation evaluation. Usage: --tags tag1=value1 --tags tag2="value 2"', (value: string, previous: string[]) => previous.concat([value]), []) 26 | .option('-t, --token ', 'Execution Machine API Token to be used.') 27 | .option('--show-output', 'Show optimistic execution output from write operation') 28 | .action(functionWriteCmd); 29 | 30 | program.command('function:deploy') 31 | .alias('fx:d') 32 | .description('Deploy a EXM compatible function to Arweave.') 33 | .option('-s, --src ', 'Path to source code of function. Example: /Documents/function.wasm .') 34 | .option('--contract-tx ', 'ID of Source Function already deployed to Arweave.') 35 | .option('-i, --init-state ', 'Init State for Function to be deployed under init function.') 36 | .option('--init-state-src ', 'Path to init state file.') 37 | .option('-t, --type ', 'Type of function to be deployed.') 38 | .option('-t, --token ', 'Execution Machine API Token to be used.') 39 | .action(functionDeployCmd); 40 | 41 | program.command('function:evaluate') 42 | .alias('fx:e') 43 | .description('Evaluates the state of an EXM application') 44 | .argument('', 'ID of deployed function') 45 | .action(evaluateExmFunction); 46 | 47 | program.command('function:test') 48 | .alias('fx:t') 49 | .description('Test a function inside a simulated EXM environment') 50 | .option('-s, --src ', 'Path to source code of function. Example: /Documents/function.js') 51 | .option('--init-state ', 'JSON value containing the initial state of the function') 52 | .option('--init-state-src ', 'Path to init state file.') 53 | .option('-t, --type ', 'Type of function to be deployed.') 54 | .option('-i, --input ', `Inputs to be used during evaluation. Usage: --input '{\"someProperty\":\"value\"} --input '{}'`, (value: string, previous: string[]) => previous.concat([value]), []) 55 | // @ts-ignore 56 | .action(functionTest); 57 | 58 | program.parse(process.argv); 59 | -------------------------------------------------------------------------------- /src/cli/utils/common.ts: -------------------------------------------------------------------------------- 1 | export const error = (message: string) => { 2 | console.error(message); 3 | process.exit(1); 4 | } -------------------------------------------------------------------------------- /src/common/em.ts: -------------------------------------------------------------------------------- 1 | import {ContractType, DeployOpts, EmOpts, WriteAction} from "./model"; 2 | import {writeFunction} from "./functions/write"; 3 | import {readFunction} from "./functions/read"; 4 | import {deployFunction} from "./functions/deploy"; 5 | 6 | export class Exm { 7 | 8 | constructor(private readonly opts: EmOpts) { 9 | if(global && !global.fetch) { 10 | require('isomorphic-fetch'); 11 | } 12 | } 13 | 14 | /** 15 | * Changes the token passed in constructor 16 | * @param token 17 | */ 18 | changeToken(token: string) { 19 | this.opts.token = token; 20 | } 21 | 22 | /** 23 | * Gets the methods related to EXM Functions product 24 | */ 25 | get functions() { 26 | return { 27 | writeRaw: async (functionId: string, writeOps: Array | WriteAction, ignoreState?: boolean) => writeFunction(functionId, writeOps, this.opts.token, ignoreState, true), 28 | write: async (functionId: string, inputs: any | Array, ignoreState?: boolean) => writeFunction(functionId, inputs, this.opts.token, ignoreState, false), 29 | read: async (functionId: string) => readFunction(functionId), 30 | deploy: async(contractSrc: Uint8Array, contractInitState: any, contractType: ContractType, opts?: DeployOpts) => deployFunction(contractSrc, contractInitState, contractType, this.opts.token, opts) 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/common/exceptions/deployOpFailure.ts: -------------------------------------------------------------------------------- 1 | export class DeployOpFailure extends Error { 2 | constructor(message: string, public statusCode?: number) { 3 | super(message); 4 | } 5 | } -------------------------------------------------------------------------------- /src/common/exceptions/readFailure.ts: -------------------------------------------------------------------------------- 1 | export class ReadFailure extends Error { 2 | constructor(message?: string) { 3 | super(message); 4 | } 5 | } -------------------------------------------------------------------------------- /src/common/exceptions/writeOpFailure.ts: -------------------------------------------------------------------------------- 1 | export class WriteOpFailure extends Error { 2 | constructor(message: string, public statusCode?: number) { 3 | super(message); 4 | } 5 | } -------------------------------------------------------------------------------- /src/common/functions/deploy.ts: -------------------------------------------------------------------------------- 1 | import {ContractType, DeployOpBody, DeployOpResult, DeployOpts} from "../model"; 2 | import {postRequest} from "../utils/commons"; 3 | import {EmVars} from "../vars"; 4 | import {DeployOpFailure} from "../exceptions/deployOpFailure"; 5 | 6 | /** 7 | * This method allows you to deploy a function application through EXM. 8 | * No AR or Wallets are needed to execute the deployment. 9 | * 10 | * @param contractSrc Bytes (UInt8Array) of source code of function to be deployed 11 | * @param contractInitState String representing the initial state of the contract. 12 | * @param contractType JS, EVM, WASM. 13 | * @param emToken EM Token to authenticate request 14 | * @param opts Options for deployment 15 | */ 16 | export const deployFunction = async(contractSrc: Uint8Array, contractInitState: any, contractType: ContractType, emToken: string, opts?: DeployOpts) => { 17 | try { 18 | const bytes = Array.from(contractSrc.values()); 19 | const body: Partial = { 20 | contractSrc: bytes 21 | }; 22 | body.contentType = contractType || ContractType.JS; 23 | let initState = '{}'; 24 | 25 | if(contractInitState) { 26 | if(typeof contractInitState === 'object') { 27 | initState = JSON.stringify(contractInitState); 28 | } else { 29 | initState = String(contractInitState); 30 | } 31 | } 32 | 33 | body.initState = initState; 34 | body.contractOwner = (opts || {}).ownerAddress || ""; 35 | 36 | const data = await postRequest(`${EmVars.EM_BACKEND_URL}/contract/deploy?token=${emToken}`, body); 37 | 38 | if (data.ok) { 39 | const json = await data.json(); 40 | return json as DeployOpResult; 41 | } else { 42 | throw new DeployOpFailure(`${data.status}: Function application could not be deployed.`, data.status); 43 | } 44 | } catch(e) { 45 | if(e instanceof DeployOpFailure) { 46 | throw e; 47 | } else { 48 | throw new DeployOpFailure(`${e.toString()}`); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/common/functions/read.ts: -------------------------------------------------------------------------------- 1 | import {ReadResult} from "../model"; 2 | import {EmVars} from "../vars"; 3 | import {ReadFailure} from "../exceptions/readFailure"; 4 | 5 | /** 6 | * This method allows you to read the state of a function being used in EM (Execution Machine) 7 | * No token is needed since states are technically public from an evaluation perspective 8 | * 9 | * @param functionId The function ID (An Arweave valid TX id) to obtain the state for 10 | */ 11 | export const readFunction = async (functionId: string): Promise> => { 12 | const fetchState = await fetch(`${EmVars.EM_READ_URL}/${functionId}`); 13 | if(fetchState.ok) { 14 | return (await fetchState.json()) as ReadResult; 15 | } else { 16 | throw new ReadFailure(`EM was not able to read state for function ${functionId}`); 17 | } 18 | } -------------------------------------------------------------------------------- /src/common/functions/write.ts: -------------------------------------------------------------------------------- 1 | import {EmVars} from "../vars"; 2 | import {postRequest} from "../utils/commons"; 3 | import {WriteAction, WriteOpBody, WriteOpResult} from "../model"; 4 | import {WriteOpFailure} from "../exceptions/writeOpFailure"; 5 | 6 | const inputToString = (input: any) => { 7 | if(typeof input !== 'string') { 8 | input = JSON.stringify(input); 9 | } 10 | 11 | return input; 12 | } 13 | 14 | const cleanInput = (writeAction: WriteAction) => { 15 | if(!writeAction.input) { 16 | throw new Error(`Property 'input' is required in a write operation`); 17 | } 18 | if(!writeAction.tags) { 19 | writeAction.tags = []; 20 | } 21 | 22 | writeAction.input = inputToString(writeAction.input); 23 | 24 | return writeAction; 25 | } 26 | 27 | /** 28 | * This method allows you to execute write operations inside EM Trustless, Serverless Functions. 29 | * By requesting to the module `em-backend`, it creates a write operation which will be automatically processed. 30 | * Note that the result returned by this function is optimistic as the final processing for the global state of the function 31 | * Might take a few more seconds in order for it to be available and propagated. 32 | * 33 | * 34 | * @param functionId The function ID (An Arweave valid TX id) to be processed 35 | * @param writeOps A single operation or an array of operations to be sent. Note a limit of 499 write operations are enforced. 36 | * @param emToken EM Token to authenticate request 37 | * @param ignoreState Whether optimistic state should not be returned 38 | * @param raw Whether it's a write input or an input to be constructed. 39 | */ 40 | export const writeFunction = async (functionId: string, writeOps: WriteAction[] | any, emToken: string, ignoreState: boolean | undefined, raw: boolean): Promise> => { 41 | let body: Partial = { 42 | functionId 43 | }; 44 | 45 | let inputs: WriteAction[] = []; 46 | 47 | if(raw) { 48 | let writeOpsScope: WriteAction | WriteAction[] = writeOps; 49 | if(Array.isArray(writeOpsScope)) { 50 | writeOpsScope.forEach((i, index) => { 51 | writeOpsScope[index] = cleanInput(i); 52 | }); 53 | } else { 54 | writeOpsScope = cleanInput(writeOpsScope); 55 | } 56 | 57 | inputs = Array.isArray(writeOpsScope) ? writeOpsScope : [writeOpsScope]; 58 | } else { 59 | if(Array.isArray(writeOps)) { 60 | inputs = writeOps.map((i) => ({ input: inputToString(i), tags: [] })); 61 | } else { 62 | inputs = [{ input: inputToString(writeOps), tags: [] }] 63 | } 64 | } 65 | 66 | body.inputs = inputs; 67 | 68 | if(body.inputs.length > 499) { 69 | throw new WriteOpFailure("Only 499 writes are allowed in a single query."); 70 | } 71 | 72 | let reqUrl = `${EmVars.EM_BACKEND_URL}/transactions?token=${emToken}`; 73 | if(ignoreState) { 74 | reqUrl = `${reqUrl}&ignoreState=true`; 75 | } 76 | 77 | const data = await postRequest(reqUrl, body); 78 | let bodyJson: any = await data.json(); 79 | 80 | if(data.ok) { 81 | return bodyJson as WriteOpResult 82 | } else { 83 | if(bodyJson && bodyJson.statusCode && bodyJson.error && bodyJson.message) { 84 | throw new WriteOpFailure(`${bodyJson.statusCode}: Internal server error while sending write operation to function ${functionId}. "Error: ${bodyJson.message}"`, bodyJson.statusCode); 85 | } else { 86 | throw new WriteOpFailure(`${data.status}: Something went wrong, write operation for function ${functionId} could not be sent.`); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/common/model.ts: -------------------------------------------------------------------------------- 1 | export enum ContractType { 2 | JS = "application/javascript", // 'application/javascript', 'application/typescript' 3 | EVM = "application/octet-stream", // 'application/vnd.exm.evm', 'application/vnd.exm.sol' 4 | WASM = "application/wasm", // 'application/vnd.exm.wasm+rust', 'application/vnd.exm.wasm+c', 'application/vnd.exm.wasm+cpp' 5 | } 6 | 7 | export interface EmOpts { 8 | token: string; 9 | } 10 | 11 | export interface Tag { 12 | name: string; 13 | value: string; 14 | } 15 | 16 | export interface WriteAction { 17 | input: T, 18 | tags: Array 19 | } 20 | 21 | export interface WriteOpBody { 22 | functionId: string; 23 | inputs: Array> 24 | } 25 | 26 | export interface WriteOpResult { 27 | status: "SUCCESS", 28 | data: { 29 | pseudoId: string; 30 | execution: { 31 | state: T, 32 | validity: Record 33 | } 34 | } 35 | } 36 | 37 | export interface ReadResult { 38 | state: T; 39 | } 40 | 41 | export interface DeployOpBody { 42 | contractSrc: Array; 43 | contentType: string; 44 | initState: string; 45 | contractOwner?: string; 46 | } 47 | 48 | export interface DeployOpResult { 49 | id: string; 50 | } 51 | 52 | export interface DeployOpts { 53 | ownerAddress?: string; 54 | } -------------------------------------------------------------------------------- /src/common/utils/commons.ts: -------------------------------------------------------------------------------- 1 | import {ContractType} from "../model"; 2 | 3 | export const postRequest = (url: string, body: any) => { 4 | return fetch(url, { 5 | method: 'POST', 6 | headers: { 7 | 'Accept': 'application/json', 8 | 'Content-Type': 'application/json' 9 | }, 10 | body: JSON.stringify(body) 11 | }); 12 | } 13 | 14 | export const guidGenerator = () => { 15 | const S4 = function() { 16 | return (((1+Math.random())*0x10000)|0).toString(16).substring(1); 17 | }; 18 | return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); 19 | } 20 | 21 | export const figureOutContractType = (input: string | undefined, byType: boolean): ContractType => { 22 | let contentType: ContractType = ContractType.JS; 23 | if(input) { 24 | const lowerCaseInput = input.toLowerCase(); 25 | 26 | if (byType) { 27 | switch (lowerCaseInput) { 28 | case "wasm": 29 | contentType = ContractType.WASM; 30 | break; 31 | case "evm": 32 | contentType = ContractType.EVM; 33 | break; 34 | case "js": 35 | contentType = ContractType.JS; 36 | break; 37 | } 38 | } else { 39 | if (lowerCaseInput.endsWith(".js")) { 40 | contentType = ContractType.JS; 41 | } else if (lowerCaseInput.endsWith(".wasm")) { 42 | contentType = ContractType.WASM; 43 | } else { 44 | contentType = ContractType.JS; 45 | } 46 | } 47 | } 48 | 49 | return contentType; 50 | } -------------------------------------------------------------------------------- /src/common/vars.ts: -------------------------------------------------------------------------------- 1 | export class EmVars { 2 | public static EM_BACKEND_URL: string = 'https://api.exm.dev/api'; 3 | public static EM_READ_URL: string = 'https://api.exm.dev/read'; 4 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common/em'; 2 | export * from './common/model'; 3 | export * from './common/functions/write'; 4 | export * from './common/functions/read'; 5 | export * from './common/functions/deploy'; 6 | export * from './common/exceptions/readFailure'; 7 | export * from './common/exceptions/writeOpFailure'; 8 | export * from './test/test-function'; -------------------------------------------------------------------------------- /src/test/test-function.ts: -------------------------------------------------------------------------------- 1 | import {ExecuteConfig, simulateContract, SimulateContractType as ContractType, SimulateInput, Tag} from "three-em-0-3-22"; 2 | import {guidGenerator} from "../common/utils/commons"; 3 | 4 | // @ts-ignore 5 | export const FunctionType = Object.assign({}, ContractType); 6 | export type FunctionType = ContractType; 7 | 8 | export type WriteInput = SimulateInput; 9 | export type ExecutionConfig = ExecuteConfig; 10 | 11 | export interface TestFunctionOpts { 12 | functionSource: Uint8Array, 13 | functionType: ContractType, 14 | functionInitState: any; 15 | writes: WriteInput[]; 16 | gatewayConfig?: ExecutionConfig 17 | settings?: Record | undefined | null, 18 | exmContext?: any | undefined | null 19 | } 20 | 21 | export const createWrite = (input: any, tags?: Array, opts?: Partial>, inputIsJson?: boolean): SimulateInput => { 22 | return { 23 | id: opts?.id || guidGenerator(), 24 | owner: opts?.owner || "@test", 25 | quantity: opts?.quantity || "0", 26 | reward: opts?.reward || "0", 27 | target: opts?.target, 28 | tags: tags || [], 29 | block: opts?.block, 30 | input: inputIsJson ? input : JSON.stringify(input) 31 | }; 32 | } 33 | 34 | export const TestFunction = async (opts: TestFunctionOpts) => { 35 | return await simulateContract({ 36 | contractId: "", 37 | maybeContractSource: { 38 | // @ts-ignore 39 | contractSrc: opts.functionSource, 40 | contractType: opts.functionType 41 | }, 42 | contractInitState: JSON.stringify(opts.functionInitState), 43 | maybeConfig: opts.gatewayConfig, 44 | maybeSettings: { 45 | ...(opts.settings || {}), 46 | 'TX_DATE': `${new Date().getTime()}` 47 | }, 48 | maybeExmContext: opts.exmContext, 49 | interactions: opts.writes, 50 | maybeCache: false 51 | }); 52 | } -------------------------------------------------------------------------------- /tests/cli-function.tests.ts: -------------------------------------------------------------------------------- 1 | import {functionTest} from "../src/cli/cmd/functions/function-test"; 2 | import {TestFunction, FunctionType, createWrite} from "../src"; 3 | import {readFileSync} from "fs"; 4 | 5 | describe('CLI Functions module', () => { 6 | 7 | test('function test successful', async () => { 8 | const testUserRegistry = await functionTest({ 9 | input: [`{"username": "Andres"}`, `{"username": "Superman"}`], 10 | src: './tests/testdata/user-registry.js', 11 | initState: '{"users":[]}', 12 | type: "js" 13 | }); 14 | expect(testUserRegistry.state).toEqual({ 15 | "users": [ 16 | { 17 | "username": "Andres" 18 | }, 19 | { 20 | "username": "Superman" 21 | } 22 | ] 23 | }); 24 | }); 25 | 26 | test('function test successful (init-state file)', async () => { 27 | const testUserRegistry = await functionTest({ 28 | input: [`{"username": "Andres"}`, `{"username": "Superman"}`], 29 | src: './tests/testdata/user-registry.js', 30 | initStateSrc: './tests/testdata/user-registry-init.json', 31 | type: "js" 32 | }); 33 | expect(testUserRegistry.state).toEqual({ 34 | "users": [ 35 | { 36 | "username": "Deadpool" 37 | }, 38 | { 39 | "username": "Andres" 40 | }, 41 | { 42 | "username": "Superman" 43 | } 44 | ] 45 | }); 46 | }); 47 | 48 | test('Function from import', async () => { 49 | const testUserRegistry = TestFunction({ 50 | functionSource: readFileSync("./tests/testdata/user-registry.js"), 51 | functionType: FunctionType.JAVASCRIPT, 52 | functionInitState: { 53 | users: [] 54 | }, 55 | writes: [createWrite({ username: "Andres" })] 56 | }) 57 | }) 58 | 59 | }); -------------------------------------------------------------------------------- /tests/testdata/user-registry-init.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [{ 3 | "username": "Deadpool" 4 | }] 5 | } -------------------------------------------------------------------------------- /tests/testdata/user-registry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param state is the current state your application holds 4 | * @param action is an object containing { input, caller } . Most of the times you will only use `action.input` which contains the input passed as a write operation 5 | * @returns {Promise<{ users: Array<{ username: string}> }>} 6 | */ 7 | export async function handle(state, action) { 8 | const { username } = action.input; 9 | state.users.push({ username }); 10 | return { state }; 11 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "tests", "dist", "**/*tests.ts"] 4 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2020", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true, 13 | "skipLibCheck": true, 14 | "strictNullChecks": false, 15 | "noImplicitAny": false, 16 | "strictBindCallApply": false, 17 | "forceConsistentCasingInFileNames": false, 18 | "noFallthroughCasesInSwitch": false, 19 | "esModuleInterop": true 20 | }, 21 | "exclude": ["node_modules", "tests", "dist", "**/*tests.ts"] 22 | } --------------------------------------------------------------------------------