From 7fff3b1c0374eef76ab4e665b13cf97b5c50ff70 Mon Sep 17 00:00:00 2001 From: Akim <59872966+akim-bow@users.noreply.github.com> Date: Thu, 21 Sep 2023 15:32:27 +0700 Subject: [PATCH] feat(aqua-compiler)!: JS-client aqua wrapper [fixes DXJ-461] (#347) * Implement wrapper compiler * Small formatting fixes * Add simple test * Package rename * Add new package to release-please * Misc fixes * Fix object type * Update package name * Revert "Fix object type" This reverts commit 572f1e10e2416f13d7e2aaa02113a6369d6cdbe3. * Set string type * Make generator func async * Cleanup * Code refactoring * Fix PR comments * Fix file path * Refactor generate module * Change header text * Rename package * Move some deps to devDeps * Add a comment * New index file * Move aqua-api to devDeps * Add desc * Fix header * Change return type * Fix all tests * Fix func * Fix index file * Add file entry * Change package name (again) * Snapshot testing * Add func name * Fix return type * Fix nox images * Update aurora image * Conditional services * Use function instead classes * Refactor header * Import same type * Make named export * Type generator array --- .github/e2e/docker-compose.yml | 2 +- .github/release-please/config.json | 3 +- .github/release-please/manifest.json | 3 +- .github/workflows/e2e.yml | 4 +- .github/workflows/run-tests.yml | 2 +- packages/core/aqua-to-js/package.json | 29 + packages/core/aqua-to-js/src/common.ts | 98 + packages/core/aqua-to-js/src/constants.ts | 17 + packages/core/aqua-to-js/src/future.ts | 61 + .../__snapshots__/generate.spec.ts.snap | 1697 +++++++++++++++++ .../src/generate/__test__/generate.spec.ts | 49 + .../generate/__test__/sources/smoke_test.aqua | 55 + .../core/aqua-to-js/src/generate/function.ts | 37 + .../core/aqua-to-js/src/generate/header.ts | 38 + .../core/aqua-to-js/src/generate/index.ts | 59 + .../aqua-to-js/src/generate/interfaces.ts | 136 ++ .../core/aqua-to-js/src/generate/service.ts | 58 + packages/core/aqua-to-js/src/index.ts | 47 + packages/core/aqua-to-js/src/utils.ts | 70 + packages/core/aqua-to-js/tsconfig.json | 10 + .../compilerSupport/aquaTypeDefinitions.ts | 52 +- pnpm-lock.yaml | 111 +- 22 files changed, 2600 insertions(+), 38 deletions(-) create mode 100644 packages/core/aqua-to-js/package.json create mode 100644 packages/core/aqua-to-js/src/common.ts create mode 100644 packages/core/aqua-to-js/src/constants.ts create mode 100644 packages/core/aqua-to-js/src/future.ts create mode 100644 packages/core/aqua-to-js/src/generate/__test__/__snapshots__/generate.spec.ts.snap create mode 100644 packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts create mode 100644 packages/core/aqua-to-js/src/generate/__test__/sources/smoke_test.aqua create mode 100644 packages/core/aqua-to-js/src/generate/function.ts create mode 100644 packages/core/aqua-to-js/src/generate/header.ts create mode 100644 packages/core/aqua-to-js/src/generate/index.ts create mode 100644 packages/core/aqua-to-js/src/generate/interfaces.ts create mode 100644 packages/core/aqua-to-js/src/generate/service.ts create mode 100644 packages/core/aqua-to-js/src/index.ts create mode 100644 packages/core/aqua-to-js/src/utils.ts create mode 100644 packages/core/aqua-to-js/tsconfig.json diff --git a/.github/e2e/docker-compose.yml b/.github/e2e/docker-compose.yml index 7b26e302..132db943 100644 --- a/.github/e2e/docker-compose.yml +++ b/.github/e2e/docker-compose.yml @@ -7,7 +7,7 @@ networks: services: aurora: - image: docker.fluence.dev/aurora:main-9e7523f-4-1 + image: docker.fluence.dev/aurora:0.2.11 ports: - 8545:8545 networks: diff --git a/.github/release-please/config.json b/.github/release-please/config.json index 34dc0209..957c55c3 100644 --- a/.github/release-please/config.json +++ b/.github/release-please/config.json @@ -9,6 +9,7 @@ ], "packages": { "packages/core/js-client": {}, - "packages/core/marine-worker": {} + "packages/core/marine-worker": {}, + "packages/core/aqua-to-js": {} } } diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json index beac6029..74dad67e 100644 --- a/.github/release-please/manifest.json +++ b/.github/release-please/manifest.json @@ -1,4 +1,5 @@ { "packages/core/js-client": "0.1.6", - "packages/core/marine-worker": "0.3.2" + "packages/core/marine-worker": "0.3.2", + "packages/core/aqua-to-js": "0.0.0" } diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0bf64718..601fe7e8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -43,7 +43,7 @@ jobs: uses: fluencelabs/aqua/.github/workflows/tests.yml@main with: js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}" - nox-image: "docker.fluence.dev/nox:ipfs_renovate-air-interpreter-wasm-0-x_3158_2" + nox-image: "fluencelabs/nox:unstable_minimal" flox: needs: - js-client @@ -51,4 +51,4 @@ jobs: uses: fluencelabs/flox/.github/workflows/tests.yml@main with: js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}" - nox-image: "docker.fluence.dev/nox:ipfs_renovate-air-interpreter-wasm-0-x_3158_2" \ No newline at end of file + nox-image: "fluencelabs/nox:unstable_minimal" \ No newline at end of file diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 01733b37..ba85acea 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -28,4 +28,4 @@ jobs: uses: ./.github/workflows/tests.yml with: ref: ${{ github.ref }} - nox-image: "docker.fluence.dev/nox:ipfs_renovate-air-interpreter-wasm-0-x_3135_1" + nox-image: "fluencelabs/nox:unstable_minimal" diff --git a/packages/core/aqua-to-js/package.json b/packages/core/aqua-to-js/package.json new file mode 100644 index 00000000..150d5239 --- /dev/null +++ b/packages/core/aqua-to-js/package.json @@ -0,0 +1,29 @@ +{ + "name": "@fluencelabs/aqua-to-js", + "type": "module", + "version": "0.0.0", + "description": "Tool for generating aqua wrapper", + "main": "dist/index.js", + "files": ["dist"], + "scripts": { + "test": "vitest run", + "build": "tsc" + }, + "keywords": [], + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": { + "ts-pattern": "5.0.5" + }, + "devDependencies": { + "@fluencelabs/aqua-api": "0.12.0", + "@fluencelabs/aqua-lib": "0.7.3", + "@fluencelabs/interfaces": "workspace:*", + "@fluencelabs/js-client": "workspace:*", + "@fluencelabs/registry": "0.8.7", + "@fluencelabs/spell": "0.5.20", + "@fluencelabs/trust-graph": "0.4.7", + "typescript": "5.1.6", + "vitest": "0.29.7" + } +} diff --git a/packages/core/aqua-to-js/src/common.ts b/packages/core/aqua-to-js/src/common.ts new file mode 100644 index 00000000..c4d8aac0 --- /dev/null +++ b/packages/core/aqua-to-js/src/common.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ArrowType, ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces'; +import { match, P } from 'ts-pattern'; +import { getFuncArgs } from './utils.js'; + +export function genTypeName(t: NonArrowType | ProductType | ArrowWithoutCallbacks, name: string): readonly [string | undefined, string] { + const genType = typeToTs(t); + return match(t) + .with( + { tag: 'nil' }, + () => [undefined, 'void'] as const + ).with( + { tag: 'struct' }, + () => [`export type ${name} = ${genType}`, name] as const + ).with( + { tag: P.union('labeledProduct', 'unlabeledProduct') }, + (item) => { + const args = item.tag === 'labeledProduct' + ? Object.values(item.fields) + : item.items; + + if (args.length === 1) { + return genTypeName(args[0], name); + } + + return [`export type ${name} = ${genType}`, name] as const; + }, + ).otherwise(() => [undefined, genType] as const); +} + +export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks | ProductType): string { + return match(t) + .with( + { tag: 'nil' }, + () => 'null' + ).with( + { tag: 'option' }, + ({ type }) => typeToTs(type) + ' | null' + ).with( + { tag: 'scalar' }, + ({ name }) => match(name) + .with(P.union('u8', 'u16', 'u32', 'u64', 'i8', 'i16', 'i32', 'i64', 'f32', 'f64'), () => 'number') + .with('bool', () => 'boolean') + .with('string', () => 'string') + .with(P._, () => 'any').exhaustive() + ).with( + { tag: 'array' }, + ({ type }) => typeToTs(type) + '[]' + ).with( + { tag: 'struct' }, + ({ fields }) => `{ ${Object.entries(fields).map(([field, type]) => `${field}: ${typeToTs(type)};`).join(' ')} }` + ).with( + { tag: 'labeledProduct' }, + ({ fields }) => `{ ${Object.entries(fields).map(([field, type]) => `${field}: ${typeToTs(type)};`).join(' ')} }` + ).with( + { tag: 'unlabeledProduct' }, + ({ items }) => `[${items.map(item => typeToTs(item)).join(', ')}]` + ).with( + { tag: 'arrow' }, + ({ tag, domain, codomain }) => { + const retType = codomain.tag === 'nil' + ? 'void' + : codomain.items.length === 1 + ? typeToTs(codomain.items[0]) + : typeToTs(codomain); + + const args = getFuncArgs(domain).map(([name, type]) => ([name, typeToTs(type)])); + + const generic = args.length === 0 ? 'null' : args.map(([name]) => `'${name}'`).join(' | '); + args.push(['callParams', `CallParams$$<${generic}>`]); + + const funcArgs = args.map(([name, type]) => `${name}: ${type}`).join(', '); + + return `(${funcArgs}) => ${retType} | Promise<${retType}>`; + } + ).with( + { tag: 'topType' }, + () => 'unknown' + ).with( + { tag: 'bottomType' }, + () => 'never' + ).exhaustive(); +} \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/constants.ts b/packages/core/aqua-to-js/src/constants.ts new file mode 100644 index 00000000..f6126517 --- /dev/null +++ b/packages/core/aqua-to-js/src/constants.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const CLIENT = 'IFluenceClient$$'; \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/future.ts b/packages/core/aqua-to-js/src/future.ts new file mode 100644 index 00000000..c894eda0 --- /dev/null +++ b/packages/core/aqua-to-js/src/future.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ArrayType, + BottomType, LabeledProductType, + NilType, + NonArrowType, + OptionType, + ScalarType, + StructType, + TopType, UnlabeledProductType +} from '@fluencelabs/interfaces'; + +// Type definitions for inferring ts types from air json definition +// In the future we may remove string type declaration and move to type inference. + +type GetTsTypeFromScalar = T['name'] extends 'u8' | 'u16' | 'u32' | 'u64' | 'i8' | 'i16' | 'i32' | 'i64' | 'f32' | 'f64' + ? number + : T['name'] extends 'bool' + ? boolean + : T['name'] extends 'string' + ? string + : never; + +type MapTuple = { [K in keyof T]: T[K] extends NonArrowType ? GetTsType : never } + +type GetTsType = T extends NilType + ? null + : T extends ArrayType + ? GetTsType[] + : T extends StructType + ? { [K in keyof T]: GetTsType } + : T extends OptionType + ? GetTsType | null + : T extends ScalarType + ? GetTsTypeFromScalar + : T extends TopType + ? unknown + : T extends BottomType + ? never + : T extends Exclude, NilType> + ? MapTuple + : T extends Exclude, NilType> + ? H extends NonArrowType + ? { [K in keyof T['fields']]: GetTsType } + : never + : never; \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/generate/__test__/__snapshots__/generate.spec.ts.snap b/packages/core/aqua-to-js/src/generate/__test__/__snapshots__/generate.spec.ts.snap new file mode 100644 index 00000000..2d36ff08 --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/__test__/__snapshots__/generate.spec.ts.snap @@ -0,0 +1,1697 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Aqua to js/ts compiler > compiles smoke tests successfully 1`] = ` +"/* eslint-disable */ +// @ts-nocheck +/** + * + * This file is generated using: + * @fluencelabs/aqua-api version: 0.0.0 + * @fluencelabs/aqua-to-js version: 0.0.0 + * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues + * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues + * + */ + + +import { + v5_callFunction as callFunction$$, + v5_registerService as registerService$$, +} from '@fluencelabs/js-client'; + +// Services + +export function registerSrv(...args) { + registerService$$( + args, + { + \\"defaultServiceId\\": \\"single_module_srv\\", + \\"functions\\": { + \\"fields\\": { + \\"create\\": { + \\"domain\\": { + \\"fields\\": { + \\"wasm_b64_content\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"ServiceCreationResult\\", + \\"fields\\": { + \\"error\\": { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + \\"service_id\\": { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + \\"success\\": { + \\"name\\": \\"bool\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"struct\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"list\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"array\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"remove\\": { + \\"domain\\": { + \\"fields\\": { + \\"service_id\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"RemoveResult\\", + \\"fields\\": { + \\"error\\": { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + \\"success\\": { + \\"name\\": \\"bool\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"struct\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + } +} + ); +} + + +export function registerCalcService(...args) { + registerService$$( + args, + { + \\"functions\\": { + \\"fields\\": { + \\"add\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"clear_state\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"tag\\": \\"nil\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"divide\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"multiply\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"state\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"subtract\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"test_logs\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"tag\\": \\"nil\\" + }, + \\"tag\\": \\"arrow\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + } +} + ); +} + + +export function registerHelloWorld(...args) { + registerService$$( + args, + { + \\"defaultServiceId\\": \\"hello-world\\", + \\"functions\\": { + \\"fields\\": { + \\"hello\\": { + \\"domain\\": { + \\"fields\\": { + \\"str\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + } +} + ); +} + + +// Functions +export const resourceTest_script = \` +(seq + (seq + (seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (call %init_peer_id% (\\"getDataSrv\\" \\"label\\") [] label) + ) + (xor + (new $resource_id + (seq + (seq + (seq + (call %init_peer_id% (\\"peer\\" \\"timestamp_sec\\") [] t) + (xor + (seq + (seq + (call -relay- (\\"registry\\" \\"get_key_bytes\\") [label [] t [] \\"\\"] bytes) + (xor + (call %init_peer_id% (\\"sig\\" \\"sign\\") [bytes] result) + (fail %last_error%) + ) + ) + (xor + (match result.$.success false + (ap result.$.error.[0] $error) + ) + (new $successful + (seq + (seq + (seq + (seq + (seq + (seq + (ap result.$.signature result_flat) + (call -relay- (\\"registry\\" \\"get_key_id\\") [label %init_peer_id%] id) + ) + (call -relay- (\\"op\\" \\"string_to_b58\\") [id] k) + ) + (call -relay- (\\"kad\\" \\"neighborhood\\") [k [] []] nodes) + ) + (par + (fold nodes n-0 + (par + (xor + (xor + (seq + (seq + (seq + (call n-0 (\\"peer\\" \\"timestamp_sec\\") [] t-0) + (call n-0 (\\"trust-graph\\" \\"get_weight\\") [%init_peer_id% t-0] weight) + ) + (call n-0 (\\"registry\\" \\"register_key\\") [label [] t [] \\"\\" result_flat.$.[0] weight t-0] result-0) + ) + (xor + (seq + (match result-0.$.success true + (ap true $successful) + ) + (new $-ephemeral-stream- + (new #-ephemeral-canon- + (canon -relay- $-ephemeral-stream- #-ephemeral-canon-) + ) + ) + ) + (seq + (ap result-0.$.error $error) + (new $-ephemeral-stream- + (new #-ephemeral-canon- + (canon -relay- $-ephemeral-stream- #-ephemeral-canon-) + ) + ) + ) + ) + ) + (null) + ) + (fail %last_error%) + ) + (next n-0) + ) + (never) + ) + (null) + ) + ) + (new $status + (new $result-1 + (seq + (seq + (seq + (par + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"sub\\") [1 1] sub) + (new $successful_test + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"add\\") [sub 1] successful_incr) + (fold $successful successful_fold_var + (seq + (seq + (ap successful_fold_var $successful_test) + (canon -relay- $successful_test #successful_iter_canon) + ) + (xor + (match #successful_iter_canon.length successful_incr + (null) + ) + (next successful_fold_var) + ) + ) + (never) + ) + ) + (canon -relay- $successful_test #successful_result_canon) + ) + (ap #successful_result_canon successful_gate) + ) + ) + ) + (call -relay- (\\"math\\" \\"sub\\") [1 1] sub-0) + ) + (ap \\"ok\\" $status) + ) + (call -relay- (\\"peer\\" \\"timeout\\") [6000 \\"timeout\\"] $status) + ) + (new $status_test + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"add\\") [0 1] status_incr) + (fold $status status_fold_var + (seq + (seq + (ap status_fold_var $status_test) + (canon -relay- $status_test #status_iter_canon) + ) + (xor + (match #status_iter_canon.length status_incr + (null) + ) + (next status_fold_var) + ) + ) + (never) + ) + ) + (canon -relay- $status_test #status_result_canon) + ) + (ap #status_result_canon status_gate) + ) + ) + ) + (xor + (match status_gate.$.[0] \\"ok\\" + (ap true $result-1) + ) + (ap false $result-1) + ) + ) + (new $result-1_test + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"add\\") [0 1] result-1_incr) + (fold $result-1 result-1_fold_var + (seq + (seq + (ap result-1_fold_var $result-1_test) + (canon -relay- $result-1_test #result-1_iter_canon) + ) + (xor + (match #result-1_iter_canon.length result-1_incr + (null) + ) + (next result-1_fold_var) + ) + ) + (never) + ) + ) + (canon -relay- $result-1_test #result-1_result_canon) + ) + (ap #result-1_result_canon result-1_gate) + ) + ) + ) + ) + ) + ) + (xor + (match result-1_gate.$.[0] false + (ap \\"resource wasn't created: timeout exceeded\\" $error) + ) + (ap id $resource_id) + ) + ) + ) + ) + ) + (fail %last_error%) + ) + ) + (canon %init_peer_id% $resource_id #-resource_id-fix-0) + ) + (ap #-resource_id-fix-0 -resource_id-flat-0) + ) + ) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (canon %init_peer_id% $error #error_canon) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [-resource_id-flat-0 #error_canon]) +) +\`; + + +export function resourceTest(...args) { + return callFunction$$( + args, + { + \\"functionName\\": \\"resourceTest\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": { + \\"label\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"array\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + resourceTest_script + ); +} + +export const helloTest_script = \` +(seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (xor + (call %init_peer_id% (\\"hello-world\\" \\"hello\\") [\\"Fluence user\\"] hello) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [hello]) +) +\`; + + +export function helloTest(...args) { + return callFunction$$( + args, + { + \\"functionName\\": \\"helloTest\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": {}, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + helloTest_script + ); +} + +export const demo_calculation_script = \` +(seq + (seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (call %init_peer_id% (\\"getDataSrv\\" \\"service_id\\") [] service_id) + ) + (xor + (seq + (seq + (seq + (seq + (seq + (call %init_peer_id% (service_id \\"test_logs\\") []) + (call %init_peer_id% (service_id \\"add\\") [10]) + ) + (call %init_peer_id% (service_id \\"multiply\\") [5]) + ) + (call %init_peer_id% (service_id \\"subtract\\") [8]) + ) + (call %init_peer_id% (service_id \\"divide\\") [6]) + ) + (call %init_peer_id% (service_id \\"state\\") [] res) + ) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [res]) +) +\`; + + +export function demo_calculation(...args) { + return callFunction$$( + args, + { + \\"functionName\\": \\"demo_calculation\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": { + \\"service_id\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + demo_calculation_script + ); +} + +export const marineTest_script = \` +(seq + (seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (call %init_peer_id% (\\"getDataSrv\\" \\"wasm64\\") [] wasm64) + ) + (xor + (seq + (seq + (seq + (seq + (seq + (seq + (call %init_peer_id% (\\"single_module_srv\\" \\"create\\") [wasm64] serviceResult) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"test_logs\\") []) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"add\\") [10]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"multiply\\") [5]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"subtract\\") [8]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"divide\\") [6]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"state\\") [] res) + ) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [res]) +) +\`; + + +export function marineTest(...args) { + return callFunction$$( + args, + { + \\"functionName\\": \\"marineTest\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": { + \\"wasm64\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + marineTest_script + ); +} +" +`; + +exports[`Aqua to js/ts compiler > compiles smoke tests successfully 2`] = ` +"/* eslint-disable */ +// @ts-nocheck +/** + * + * This file is generated using: + * @fluencelabs/aqua-api version: 0.0.0 + * @fluencelabs/aqua-to-js version: 0.0.0 + * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues + * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues + * + */ +import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client'; + +import { + v5_callFunction as callFunction$$, + v5_registerService as registerService$$, +} from '@fluencelabs/js-client'; + +// Services +export interface SrvDef { + create: (wasm_b64_content: string, callParams: CallParams$$<'wasm_b64_content'>) => { error: string | null; service_id: string | null; success: boolean; } | Promise<{ error: string | null; service_id: string | null; success: boolean; }>; + list: (callParams: CallParams$$) => string[] | Promise; + remove: (service_id: string, callParams: CallParams$$<'service_id'>) => { error: string | null; success: boolean; } | Promise<{ error: string | null; success: boolean; }>; +} +export function registerSrv(service: SrvDef): void; +export function registerSrv(serviceId: string, service: SrvDef): void; +export function registerSrv(peer: IFluenceClient$$, service: SrvDef): void; +export function registerSrv(peer: IFluenceClient$$, serviceId: string, service: SrvDef): void; +export interface CalcServiceDef { + add: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + clear_state: (callParams: CallParams$$) => void | Promise; + divide: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + multiply: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + state: (callParams: CallParams$$) => number | Promise; + subtract: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + test_logs: (callParams: CallParams$$) => void | Promise; +} +export function registerCalcService(service: CalcServiceDef): void; +export function registerCalcService(serviceId: string, service: CalcServiceDef): void; +export function registerCalcService(peer: IFluenceClient$$, service: CalcServiceDef): void; +export function registerCalcService(peer: IFluenceClient$$, serviceId: string, service: CalcServiceDef): void; +export interface HelloWorldDef { + hello: (str: string, callParams: CallParams$$<'str'>) => string | Promise; +} +export function registerHelloWorld(service: HelloWorldDef): void; +export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void; +export function registerHelloWorld(peer: IFluenceClient$$, service: HelloWorldDef): void; +export function registerHelloWorld(peer: IFluenceClient$$, serviceId: string, service: HelloWorldDef): void; + +// Functions +export type ResourceTestResult = [string | null, string[]] + +export function resourceTest( + label: string, + config?: {ttl?: number} +): Promise; + +export function resourceTest( + peer: IFluenceClient$$, + label: string, + config?: {ttl?: number} +): Promise; + +export function helloTest( + config?: {ttl?: number} +): Promise; + +export function helloTest( + peer: IFluenceClient$$, + config?: {ttl?: number} +): Promise; + +export function demo_calculation( + service_id: string, + config?: {ttl?: number} +): Promise; + +export function demo_calculation( + peer: IFluenceClient$$, + service_id: string, + config?: {ttl?: number} +): Promise; + +export function marineTest( + wasm64: string, + config?: {ttl?: number} +): Promise; + +export function marineTest( + peer: IFluenceClient$$, + wasm64: string, + config?: {ttl?: number} +): Promise; + +" +`; + +exports[`Aqua to js/ts compiler > compiles smoke tests successfully 3`] = ` +"/* eslint-disable */ +// @ts-nocheck +/** + * + * This file is generated using: + * @fluencelabs/aqua-api version: 0.0.0 + * @fluencelabs/aqua-to-js version: 0.0.0 + * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues + * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues + * + */ +import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client'; + +import { + v5_callFunction as callFunction$$, + v5_registerService as registerService$$, +} from '@fluencelabs/js-client'; + +// Services +export interface SrvDef { + create: (wasm_b64_content: string, callParams: CallParams$$<'wasm_b64_content'>) => { error: string | null; service_id: string | null; success: boolean; } | Promise<{ error: string | null; service_id: string | null; success: boolean; }>; + list: (callParams: CallParams$$) => string[] | Promise; + remove: (service_id: string, callParams: CallParams$$<'service_id'>) => { error: string | null; success: boolean; } | Promise<{ error: string | null; success: boolean; }>; +} +export function registerSrv(service: SrvDef): void; +export function registerSrv(serviceId: string, service: SrvDef): void; +export function registerSrv(peer: IFluenceClient$$, service: SrvDef): void; +export function registerSrv(peer: IFluenceClient$$, serviceId: string, service: SrvDef): void; +export function registerSrv(...args: any[]) { + registerService$$( + args, + { + \\"defaultServiceId\\": \\"single_module_srv\\", + \\"functions\\": { + \\"fields\\": { + \\"create\\": { + \\"domain\\": { + \\"fields\\": { + \\"wasm_b64_content\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"ServiceCreationResult\\", + \\"fields\\": { + \\"error\\": { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + \\"service_id\\": { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + \\"success\\": { + \\"name\\": \\"bool\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"struct\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"list\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"array\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"remove\\": { + \\"domain\\": { + \\"fields\\": { + \\"service_id\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"RemoveResult\\", + \\"fields\\": { + \\"error\\": { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + \\"success\\": { + \\"name\\": \\"bool\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"struct\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + } +} + ); +} + +export interface CalcServiceDef { + add: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + clear_state: (callParams: CallParams$$) => void | Promise; + divide: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + multiply: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + state: (callParams: CallParams$$) => number | Promise; + subtract: (num: number, callParams: CallParams$$<'num'>) => number | Promise; + test_logs: (callParams: CallParams$$) => void | Promise; +} +export function registerCalcService(service: CalcServiceDef): void; +export function registerCalcService(serviceId: string, service: CalcServiceDef): void; +export function registerCalcService(peer: IFluenceClient$$, service: CalcServiceDef): void; +export function registerCalcService(peer: IFluenceClient$$, serviceId: string, service: CalcServiceDef): void; +export function registerCalcService(...args: any[]) { + registerService$$( + args, + { + \\"functions\\": { + \\"fields\\": { + \\"add\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"clear_state\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"tag\\": \\"nil\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"divide\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"multiply\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"state\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"subtract\\": { + \\"domain\\": { + \\"fields\\": { + \\"num\\": { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"test_logs\\": { + \\"domain\\": { + \\"tag\\": \\"nil\\" + }, + \\"codomain\\": { + \\"tag\\": \\"nil\\" + }, + \\"tag\\": \\"arrow\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + } +} + ); +} + +export interface HelloWorldDef { + hello: (str: string, callParams: CallParams$$<'str'>) => string | Promise; +} +export function registerHelloWorld(service: HelloWorldDef): void; +export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void; +export function registerHelloWorld(peer: IFluenceClient$$, service: HelloWorldDef): void; +export function registerHelloWorld(peer: IFluenceClient$$, serviceId: string, service: HelloWorldDef): void; +export function registerHelloWorld(...args: any[]) { + registerService$$( + args, + { + \\"defaultServiceId\\": \\"hello-world\\", + \\"functions\\": { + \\"fields\\": { + \\"hello\\": { + \\"domain\\": { + \\"fields\\": { + \\"str\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + } +} + ); +} + + +// Functions +export const resourceTest_script = \` +(seq + (seq + (seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (call %init_peer_id% (\\"getDataSrv\\" \\"label\\") [] label) + ) + (xor + (new $resource_id + (seq + (seq + (seq + (call %init_peer_id% (\\"peer\\" \\"timestamp_sec\\") [] t) + (xor + (seq + (seq + (call -relay- (\\"registry\\" \\"get_key_bytes\\") [label [] t [] \\"\\"] bytes) + (xor + (call %init_peer_id% (\\"sig\\" \\"sign\\") [bytes] result) + (fail %last_error%) + ) + ) + (xor + (match result.$.success false + (ap result.$.error.[0] $error) + ) + (new $successful + (seq + (seq + (seq + (seq + (seq + (seq + (ap result.$.signature result_flat) + (call -relay- (\\"registry\\" \\"get_key_id\\") [label %init_peer_id%] id) + ) + (call -relay- (\\"op\\" \\"string_to_b58\\") [id] k) + ) + (call -relay- (\\"kad\\" \\"neighborhood\\") [k [] []] nodes) + ) + (par + (fold nodes n-0 + (par + (xor + (xor + (seq + (seq + (seq + (call n-0 (\\"peer\\" \\"timestamp_sec\\") [] t-0) + (call n-0 (\\"trust-graph\\" \\"get_weight\\") [%init_peer_id% t-0] weight) + ) + (call n-0 (\\"registry\\" \\"register_key\\") [label [] t [] \\"\\" result_flat.$.[0] weight t-0] result-0) + ) + (xor + (seq + (match result-0.$.success true + (ap true $successful) + ) + (new $-ephemeral-stream- + (new #-ephemeral-canon- + (canon -relay- $-ephemeral-stream- #-ephemeral-canon-) + ) + ) + ) + (seq + (ap result-0.$.error $error) + (new $-ephemeral-stream- + (new #-ephemeral-canon- + (canon -relay- $-ephemeral-stream- #-ephemeral-canon-) + ) + ) + ) + ) + ) + (null) + ) + (fail %last_error%) + ) + (next n-0) + ) + (never) + ) + (null) + ) + ) + (new $status + (new $result-1 + (seq + (seq + (seq + (par + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"sub\\") [1 1] sub) + (new $successful_test + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"add\\") [sub 1] successful_incr) + (fold $successful successful_fold_var + (seq + (seq + (ap successful_fold_var $successful_test) + (canon -relay- $successful_test #successful_iter_canon) + ) + (xor + (match #successful_iter_canon.length successful_incr + (null) + ) + (next successful_fold_var) + ) + ) + (never) + ) + ) + (canon -relay- $successful_test #successful_result_canon) + ) + (ap #successful_result_canon successful_gate) + ) + ) + ) + (call -relay- (\\"math\\" \\"sub\\") [1 1] sub-0) + ) + (ap \\"ok\\" $status) + ) + (call -relay- (\\"peer\\" \\"timeout\\") [6000 \\"timeout\\"] $status) + ) + (new $status_test + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"add\\") [0 1] status_incr) + (fold $status status_fold_var + (seq + (seq + (ap status_fold_var $status_test) + (canon -relay- $status_test #status_iter_canon) + ) + (xor + (match #status_iter_canon.length status_incr + (null) + ) + (next status_fold_var) + ) + ) + (never) + ) + ) + (canon -relay- $status_test #status_result_canon) + ) + (ap #status_result_canon status_gate) + ) + ) + ) + (xor + (match status_gate.$.[0] \\"ok\\" + (ap true $result-1) + ) + (ap false $result-1) + ) + ) + (new $result-1_test + (seq + (seq + (seq + (call -relay- (\\"math\\" \\"add\\") [0 1] result-1_incr) + (fold $result-1 result-1_fold_var + (seq + (seq + (ap result-1_fold_var $result-1_test) + (canon -relay- $result-1_test #result-1_iter_canon) + ) + (xor + (match #result-1_iter_canon.length result-1_incr + (null) + ) + (next result-1_fold_var) + ) + ) + (never) + ) + ) + (canon -relay- $result-1_test #result-1_result_canon) + ) + (ap #result-1_result_canon result-1_gate) + ) + ) + ) + ) + ) + ) + (xor + (match result-1_gate.$.[0] false + (ap \\"resource wasn't created: timeout exceeded\\" $error) + ) + (ap id $resource_id) + ) + ) + ) + ) + ) + (fail %last_error%) + ) + ) + (canon %init_peer_id% $resource_id #-resource_id-fix-0) + ) + (ap #-resource_id-fix-0 -resource_id-flat-0) + ) + ) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (canon %init_peer_id% $error #error_canon) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [-resource_id-flat-0 #error_canon]) +) +\`; + +export type ResourceTestResult = [string | null, string[]] + +export function resourceTest( + label: string, + config?: {ttl?: number} +): Promise; + +export function resourceTest( + peer: IFluenceClient$$, + label: string, + config?: {ttl?: number} +): Promise; + +export function resourceTest(...args: any[]) { + return callFunction$$( + args, + { + \\"functionName\\": \\"resourceTest\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": { + \\"label\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"option\\" + }, + { + \\"type\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + }, + \\"tag\\": \\"array\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + resourceTest_script + ); +} + +export const helloTest_script = \` +(seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (xor + (call %init_peer_id% (\\"hello-world\\" \\"hello\\") [\\"Fluence user\\"] hello) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [hello]) +) +\`; + +export function helloTest( + config?: {ttl?: number} +): Promise; + +export function helloTest( + peer: IFluenceClient$$, + config?: {ttl?: number} +): Promise; + +export function helloTest(...args: any[]) { + return callFunction$$( + args, + { + \\"functionName\\": \\"helloTest\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": {}, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + helloTest_script + ); +} + +export const demo_calculation_script = \` +(seq + (seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (call %init_peer_id% (\\"getDataSrv\\" \\"service_id\\") [] service_id) + ) + (xor + (seq + (seq + (seq + (seq + (seq + (call %init_peer_id% (service_id \\"test_logs\\") []) + (call %init_peer_id% (service_id \\"add\\") [10]) + ) + (call %init_peer_id% (service_id \\"multiply\\") [5]) + ) + (call %init_peer_id% (service_id \\"subtract\\") [8]) + ) + (call %init_peer_id% (service_id \\"divide\\") [6]) + ) + (call %init_peer_id% (service_id \\"state\\") [] res) + ) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [res]) +) +\`; + +export function demo_calculation( + service_id: string, + config?: {ttl?: number} +): Promise; + +export function demo_calculation( + peer: IFluenceClient$$, + service_id: string, + config?: {ttl?: number} +): Promise; + +export function demo_calculation(...args: any[]) { + return callFunction$$( + args, + { + \\"functionName\\": \\"demo_calculation\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": { + \\"service_id\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + demo_calculation_script + ); +} + +export const marineTest_script = \` +(seq + (seq + (seq + (call %init_peer_id% (\\"getDataSrv\\" \\"-relay-\\") [] -relay-) + (call %init_peer_id% (\\"getDataSrv\\" \\"wasm64\\") [] wasm64) + ) + (xor + (seq + (seq + (seq + (seq + (seq + (seq + (call %init_peer_id% (\\"single_module_srv\\" \\"create\\") [wasm64] serviceResult) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"test_logs\\") []) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"add\\") [10]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"multiply\\") [5]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"subtract\\") [8]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"divide\\") [6]) + ) + (call %init_peer_id% (serviceResult.$.service_id.[0] \\"state\\") [] res) + ) + (call %init_peer_id% (\\"errorHandlingSrv\\" \\"error\\") [%last_error% 0]) + ) + ) + (call %init_peer_id% (\\"callbackSrv\\" \\"response\\") [res]) +) +\`; + +export function marineTest( + wasm64: string, + config?: {ttl?: number} +): Promise; + +export function marineTest( + peer: IFluenceClient$$, + wasm64: string, + config?: {ttl?: number} +): Promise; + +export function marineTest(...args: any[]) { + return callFunction$$( + args, + { + \\"functionName\\": \\"marineTest\\", + \\"arrow\\": { + \\"domain\\": { + \\"fields\\": { + \\"wasm64\\": { + \\"name\\": \\"string\\", + \\"tag\\": \\"scalar\\" + } + }, + \\"tag\\": \\"labeledProduct\\" + }, + \\"codomain\\": { + \\"items\\": [ + { + \\"name\\": \\"f64\\", + \\"tag\\": \\"scalar\\" + } + ], + \\"tag\\": \\"unlabeledProduct\\" + }, + \\"tag\\": \\"arrow\\" + }, + \\"names\\": { + \\"relay\\": \\"-relay-\\", + \\"getDataSrv\\": \\"getDataSrv\\", + \\"callbackSrv\\": \\"callbackSrv\\", + \\"responseSrv\\": \\"callbackSrv\\", + \\"responseFnName\\": \\"response\\", + \\"errorHandlingSrv\\": \\"errorHandlingSrv\\", + \\"errorFnName\\": \\"error\\" + } +}, + marineTest_script + ); +} +" +`; diff --git a/packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts b/packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts new file mode 100644 index 00000000..e2ba65f8 --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { generateTypes, generateSources } from '../index.js'; +import { compileFromPath } from '@fluencelabs/aqua-api'; +import url from 'url'; +import { getPackageJsonContent, PackageJson } from '../../utils.js'; + +describe('Aqua to js/ts compiler', () => { + it('compiles smoke tests successfully', async () => { + const res = await compileFromPath({ + filePath: url.fileURLToPath(new URL('./sources/smoke_test.aqua', import.meta.url)), + imports: ['./node_modules'], + targetType: 'air' + }); + + const pkg: PackageJson = { + ...(await getPackageJsonContent()), + version: '0.0.0', + devDependencies: { + '@fluencelabs/aqua-api': '0.0.0' + }, + }; + + const jsResult = await generateSources(res, 'js', pkg); + const jsTypes = await generateTypes(res, pkg); + + expect(jsResult).toMatchSnapshot(); + expect(jsTypes).toMatchSnapshot(); + + const tsResult = await generateSources(res, 'ts', pkg); + + expect(tsResult).toMatchSnapshot(); + }); +}); \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/generate/__test__/sources/smoke_test.aqua b/packages/core/aqua-to-js/src/generate/__test__/sources/smoke_test.aqua new file mode 100644 index 00000000..adfd9843 --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/__test__/sources/smoke_test.aqua @@ -0,0 +1,55 @@ +import "@fluencelabs/registry/resources-api.aqua" + +service HelloWorld("hello-world"): + hello(str: string) -> string + +func resourceTest(label: string) -> ?string, *string: + res, errors <- createResource(label) + <- res, errors + +func helloTest() -> string: + hello <- HelloWorld.hello("Fluence user") + <- hello + +service CalcService: + add(num: f64) -> f64 + clear_state() + divide(num: f64) -> f64 + multiply(num: f64) -> f64 + state() -> f64 + subtract(num: f64) -> f64 + test_logs() + +data ServiceCreationResult: + success: bool + service_id: ?string + error: ?string + +data RemoveResult: + success: bool + error: ?string + +alias ListServiceResult: []string + +service Srv("single_module_srv"): + create(wasm_b64_content: string) -> ServiceCreationResult + remove(service_id: string) -> RemoveResult + list() -> ListServiceResult + + +func demo_calculation(service_id: string) -> f64: + CalcService service_id + CalcService.test_logs() + CalcService.add(10) + CalcService.multiply(5) + CalcService.subtract(8) + CalcService.divide(6) + res <- CalcService.state() + <- res + +func marineTest(wasm64: string) -> f64: + serviceResult <- Srv.create(wasm64) + res <- demo_calculation(serviceResult.service_id!) + <- res + + diff --git a/packages/core/aqua-to-js/src/generate/function.ts b/packages/core/aqua-to-js/src/generate/function.ts new file mode 100644 index 00000000..d76ad2c4 --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/function.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { recursiveRenameLaquaProps } from '../utils.js'; +import { AquaFunction, TypeGenerator } from './interfaces.js'; + +export function generateFunctions(typeGenerator: TypeGenerator, functions: Record) { + return Object.values(functions).map(func => generateFunction(typeGenerator, func)).join('\n\n'); +} + +function generateFunction(typeGenerator: TypeGenerator, func: AquaFunction) { + const scriptConstName = func.funcDef.functionName + '_script'; + return `export const ${scriptConstName} = \` +${func.script}\`; + +${typeGenerator.funcType(func)} +export function ${func.funcDef.functionName}(${typeGenerator.type('...args', 'any[]')}) { + return callFunction$$( + args, + ${JSON.stringify(recursiveRenameLaquaProps(func.funcDef), null, 4)}, + ${scriptConstName} + ); +}` +} \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/generate/header.ts b/packages/core/aqua-to-js/src/generate/header.ts new file mode 100644 index 00000000..335ea0cc --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/header.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OutputType } from './interfaces.js'; +import { PackageJson } from '../utils.js'; + +export default function generateHeader({ version, devDependencies }: PackageJson, outputType: OutputType) { + return `/* eslint-disable */ +// @ts-nocheck +/** + * + * This file is generated using: + * @fluencelabs/aqua-api version: ${devDependencies['@fluencelabs/aqua-api']} + * @fluencelabs/aqua-to-js version: ${version} + * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues + * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues + * + */ +${outputType === 'ts' ? 'import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from \'@fluencelabs/js-client\';' : ''} + +import { + v5_callFunction as callFunction$$, + v5_registerService as registerService$$, +} from '@fluencelabs/js-client';`; +} \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/generate/index.ts b/packages/core/aqua-to-js/src/generate/index.ts new file mode 100644 index 00000000..cb1aa4ac --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/index.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CompilationResult, JSTypeGenerator, OutputType, TSTypeGenerator, TypeGenerator } from './interfaces.js'; +import { PackageJson } from '../utils.js'; +import { generateServices } from './service.js'; +import { generateFunctions } from './function.js'; +import header from './header.js'; + +const typeGenerators: Record = { + 'js': new JSTypeGenerator(), + 'ts': new TSTypeGenerator() +}; + +export async function generateSources({ services, functions }: CompilationResult, outputType: OutputType, packageJson: PackageJson) { + const typeGenerator = typeGenerators[outputType]; + return `${header(packageJson, outputType)} + +${Object.entries(services).length > 0 ? `// Services +${generateServices(typeGenerator, services)} +` : ''} +${Object.entries(functions).length > 0 ? `// Functions +${generateFunctions(typeGenerator, functions)} +`: ''}` +} + +export async function generateTypes({ services, functions }: CompilationResult, packageJson: PackageJson) { + const typeGenerator = typeGenerators['ts']; + + const generatedServices = Object.entries(services) + .map(([srvName, srvDef]) => typeGenerator.serviceType(srvName, srvDef)) + .join('\n'); + + const generatedFunctions = Object.entries(functions) + .map(([funcName, funcDef]) => typeGenerator.funcType(funcDef)) + .join('\n'); + + return `${header(packageJson, 'ts')} + +${Object.entries(services).length > 0 ? `// Services +${generatedServices} +` : ''} +${Object.entries(functions).length > 0 ? `// Functions +${generatedFunctions} +`: ''}` +} \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/generate/interfaces.ts b/packages/core/aqua-to-js/src/generate/interfaces.ts new file mode 100644 index 00000000..0769a2a8 --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/interfaces.ts @@ -0,0 +1,136 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CLIENT } from '../constants.js'; +import { FunctionCallDef, ServiceDef } from '@fluencelabs/interfaces'; +import { genTypeName, typeToTs } from '../common.js'; +import { capitalize, getFuncArgs } from '../utils.js'; + +export interface TypeGenerator { + type(field: string, type: string): string; + generic(field: string, type: string): string; + bang(field: string): string; + funcType(funcDef: AquaFunction): string; + serviceType(srvName: string, srvDef: ServiceDef): string; +} + +export class TSTypeGenerator implements TypeGenerator { + bang(field: string): string { + return `${field}!`; + } + + generic(field: string, type: string): string { + return `${field}<${type}>`; + } + + type(field: string, type: string): string { + return `${field}: ${type}`; + } + + funcType({ funcDef }: AquaFunction): string { + const args = getFuncArgs(funcDef.arrow.domain).map(([name, type]) => { + const [typeDesc, t] = genTypeName(type, capitalize(funcDef.functionName) + 'Arg' + capitalize(name)); + return [typeDesc, `${name}: ${t}`] as const; + }); + args.push([undefined, `config?: {ttl?: number}`]); + + const argsDefs = args.map(([, def]) => " " + def); + const argsDesc = args.filter(([desc]) => desc !== undefined).map(([desc]) => desc); + + const functionOverloads = [ + argsDefs.join(',\n'), + [` peer: ${CLIENT}`, ...argsDefs].join(',\n') + ]; + + const [resTypeDesc, resType] = genTypeName(funcDef.arrow.codomain, capitalize(funcDef.functionName) + "Result"); + + return [ + argsDesc.join('\n'), + resTypeDesc || "", + functionOverloads.flatMap(fo => [ + `export function ${funcDef.functionName}(`, + fo, + `): Promise<${resType}>;`, + '' + ]).join('\n') + ].filter(s => s !== '').join('\n\n'); + } + + serviceType(srvName: string, srvDef: ServiceDef): string { + const members = srvDef.functions.tag === 'nil' ? [] : Object.entries(srvDef.functions.fields); + + const interfaceDefs = members + .map(([name, arrow]) => { + return ` ${name}: ${typeToTs(arrow)};`; + }) + .join('\n'); + + const interfaces = [`export interface ${srvName}Def {`, interfaceDefs, '}'].join('\n'); + + const peerDecl = `peer: ${CLIENT}`; + const serviceDecl = `service: ${srvName}Def`; + const serviceIdDecl = `serviceId: string`; + const registerServiceArgs = [ + [serviceDecl], + [serviceIdDecl, serviceDecl], + [peerDecl, serviceDecl], + [peerDecl, serviceIdDecl, serviceDecl] + ]; + + return [interfaces, ...registerServiceArgs.map(registerServiceArg => { + const args = registerServiceArg.join(', '); + return `export function register${srvName}(${args}): void;` + })].join('\n'); + } +} + +export class JSTypeGenerator implements TypeGenerator { + bang(field: string): string { + return field; + } + + generic(field: string, type: string): string { + return field; + } + + type(field: string, type: string): string { + return field; + } + + funcType(): string { + return ''; + } + + serviceType(): string { + return ''; + } +} + +export interface AquaFunction { + funcDef: FunctionCallDef; + script: string; +} + +export interface CompilationResult { + services: Record; + functions: Record; +} + +export interface EntityGenerator { + generate(compilationResult: CompilationResult): string; +} + +export type OutputType = 'js' | 'ts'; \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/generate/service.ts b/packages/core/aqua-to-js/src/generate/service.ts new file mode 100644 index 00000000..dfea63f2 --- /dev/null +++ b/packages/core/aqua-to-js/src/generate/service.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ServiceDef } from '@fluencelabs/interfaces'; +import { recursiveRenameLaquaProps } from '../utils.js'; +import { TypeGenerator } from './interfaces.js'; + +interface DefaultServiceId { + s_Some__f_value?: string +} + +export function generateServices(typeGenerator: TypeGenerator, services: Record) { + const generated = Object.entries(services).map(([srvName, srvDef]) => generateService(typeGenerator, srvName, srvDef)).join('\n\n'); + + return generated + '\n'; +} + +function generateService(typeGenerator: TypeGenerator, srvName: string, srvDef: ServiceDef) { + return [ + typeGenerator.serviceType(srvName, srvDef), + generateRegisterServiceOverload(typeGenerator, srvName, srvDef) + ].join('\n'); +} + +function generateRegisterServiceOverload(typeGenerator: TypeGenerator, srvName: string, srvDef: ServiceDef) { + return [ + `export function register${srvName}(${typeGenerator.type('...args', 'any[]')}) {`, + ' registerService$$(', + ' args,', + ` ${serviceToJson(srvDef)}`, + ' );', + '}' + ].join('\n'); +} + +function serviceToJson(service: ServiceDef): string { + return JSON.stringify({ + ...( + (service.defaultServiceId as DefaultServiceId)?.s_Some__f_value + ? { defaultServiceId: (service.defaultServiceId as DefaultServiceId).s_Some__f_value } + : {} + ), + functions: recursiveRenameLaquaProps(service.functions) + }, null, 4); +} \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/index.ts b/packages/core/aqua-to-js/src/index.ts new file mode 100644 index 00000000..081713b7 --- /dev/null +++ b/packages/core/aqua-to-js/src/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + generateSources, + generateTypes, +} from './generate/index.js'; +import { CompilationResult, OutputType } from './generate/interfaces.js'; +import { getPackageJsonContent } from './utils.js'; + +interface JsOutput { + sources: string; + types: string; +} + +interface TsOutput { + sources: string; +} + +type LanguageOutput = { + "js": JsOutput, + "ts": TsOutput +}; + +export default async function aquaToJs(res: CompilationResult, outputType: T): Promise { + const packageJson = await getPackageJsonContent(); + + return outputType === 'js' ? { + sources: await generateSources(res, 'js', packageJson), + types: await generateTypes(res, packageJson) + } : { + sources: await generateSources(res, 'ts', packageJson), + } as LanguageOutput[T]; +}; \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/utils.ts b/packages/core/aqua-to-js/src/utils.ts new file mode 100644 index 00000000..a18657c3 --- /dev/null +++ b/packages/core/aqua-to-js/src/utils.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces'; +import { readFile } from 'fs/promises'; +import path from 'path'; + +export interface PackageJson { + name: string; + version: string; + devDependencies: { + ['@fluencelabs/aqua-api']: string + } +} + +export async function getPackageJsonContent(): Promise { + const content = await readFile(new URL(path.join('..', 'package.json'), import.meta.url), 'utf-8'); + return JSON.parse(content); +} + +export function getFuncArgs(domain: ProductType): [string, NonArrowType | ArrowWithoutCallbacks][] { + if (domain.tag === 'labeledProduct') { + return Object.entries(domain.fields).map(([label, type]) => [label, type]); + } else if (domain.tag === 'unlabeledProduct') { + return domain.items.map((type, index) => ['arg' + index, type]); + } else { + return []; + } +} + +export function recursiveRenameLaquaProps(obj: unknown): unknown { + if (typeof obj !== 'object' || obj === null) return obj; + + if (Array.isArray(obj)) { + return obj.map(item => recursiveRenameLaquaProps(item)); + } + + return Object.getOwnPropertyNames(obj).reduce((acc, prop) => { + let accessProp = prop; + if (prop.includes('Laqua_js')) { + // Last part of the property separated by "_" is a correct name + const refinedProperty = prop.split('_').pop()!; + if (refinedProperty in obj) { + accessProp = refinedProperty; + } + } + + return { + ...acc, + [accessProp]: recursiveRenameLaquaProps(obj[accessProp as keyof typeof obj]) + }; + }, {}); +} + +export function capitalize(str: string) { + return str.slice(0, 1).toUpperCase() + str.slice(1); +} \ No newline at end of file diff --git a/packages/core/aqua-to-js/tsconfig.json b/packages/core/aqua-to-js/tsconfig.json new file mode 100644 index 00000000..1dbe1a1d --- /dev/null +++ b/packages/core/aqua-to-js/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "esModuleInterop": true, + "resolveJsonModule": true, + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "src/**/__test__"], +} diff --git a/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts b/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts index fa7822fc..e18d3c78 100644 --- a/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts +++ b/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -type SomeNonArrowTypes = ScalarType | OptionType | ArrayType | StructType | TopType | BottomType; +type SimpleTypes = ScalarType | OptionType | ArrayType | StructType | TopType | BottomType | NilType; -export type NonArrowType = SomeNonArrowTypes | ProductType; +export type NonArrowType = SimpleTypes | ProductType; export type TopType = { /** @@ -108,35 +108,31 @@ export type StructType = { fields: { [key: string]: NonArrowType }; }; -export type LabeledProductType = - | { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'labeledProduct'; +export type LabeledProductType = { + /** + * Type descriptor. Used for pattern-matching + */ + tag: 'labeledProduct'; - /** - * Labelled product fields - */ - fields: { [key: string]: T }; - } - | NilType; + /** + * Labelled product fields + */ + fields: { [key: string]: T }; +}; -export type UnlabeledProductType = - | { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'unlabeledProduct'; +export type UnlabeledProductType = { + /** + * Type descriptor. Used for pattern-matching + */ + tag: 'unlabeledProduct'; - /** - * Items in unlabelled product - */ - items: Array; - } - | NilType; + /** + * Items in unlabelled product + */ + items: Array; +}; -export type ProductType = UnlabeledProductType | LabeledProductType; +export type ProductType = UnlabeledProductType | LabeledProductType | NilType; /** * ArrowType is a profunctor pointing its domain to codomain. @@ -238,7 +234,7 @@ export interface ServiceDef { /** * List of functions which the service consists of */ - functions: LabeledProductType; + functions: LabeledProductType | NilType; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23cd7c22..96dd4989 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,40 @@ importers: specifier: 6.1.1 version: 6.1.1 + packages/core/aqua-to-js: + dependencies: + ts-pattern: + specifier: 5.0.5 + version: 5.0.5 + devDependencies: + '@fluencelabs/aqua-api': + specifier: 0.12.0 + version: 0.12.0 + '@fluencelabs/aqua-lib': + specifier: 0.7.3 + version: 0.7.3 + '@fluencelabs/interfaces': + specifier: workspace:* + version: link:../interfaces + '@fluencelabs/js-client': + specifier: workspace:* + version: link:../js-client + '@fluencelabs/registry': + specifier: 0.8.7 + version: 0.8.7 + '@fluencelabs/spell': + specifier: 0.5.20 + version: 0.5.20 + '@fluencelabs/trust-graph': + specifier: 0.4.7 + version: 0.4.7 + typescript: + specifier: 5.1.6 + version: 5.1.6 + vitest: + specifier: 0.29.7 + version: 0.29.7 + packages/core/interfaces: devDependencies: '@fluencelabs/avm': @@ -1114,6 +1148,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: false + /@babel/plugin-syntax-flow@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + /@babel/plugin-syntax-flow@7.22.5(@babel/core@7.22.5): resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==} engines: {node: '>=6.9.0'} @@ -1198,6 +1242,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: false + /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.5): resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} engines: {node: '>=6.9.0'} @@ -2313,6 +2367,20 @@ packages: '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.5) dev: false + /@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.10) + '@babel/types': 7.22.5 + dev: false + /@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.22.5): resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==} engines: {node: '>=6.9.0'} @@ -3778,6 +3846,14 @@ packages: resolution: {integrity: sha512-ifjtCM93KO3LhzPkMxqmXhwLmrg/scjOiyTihEVg7ns5N+BVzaK1eWzdOdqGdl9ZVoah43pdlQUepEo7VdRmsw==} dev: true + /@fluencelabs/aqua-lib@0.7.0: + resolution: {integrity: sha512-mJEaxfAQb6ogVM4l4qw7INK6kvLA2Y161ErwL7IVeVSkKXIeYq/qio2p2au35LYvhBNsKc7XP2qc0uztCmxZzA==} + dev: true + + /@fluencelabs/aqua-lib@0.7.3: + resolution: {integrity: sha512-+JVbWmHeGB+X/BSqmk6/B0gwWJ4bEAxkepVTN8l0mVrJ5zRRmYaCKVplWy6Z3W012m3VVK3A1o3rm/fgfVrQkw==} + dev: true + /@fluencelabs/aqua@0.9.1-374(jest@27.5.1)(node-fetch@3.3.2)(typescript@4.7.2): resolution: {integrity: sha512-jF6oVE4h7bP/dQArKEfsy4UxbQbzACfVIBY/TFUL5D3np4ssjxrh15Y3gl1PwSWjlaPcDeFvAuStmcqfYQmLqQ==} hasBin: true @@ -4063,6 +4139,29 @@ packages: '@fluencelabs/trust-graph': 3.0.4 dev: true + /@fluencelabs/registry@0.8.7: + resolution: {integrity: sha512-43bmb1v4p5ORvaiLBrUAl+hRPo3luxxBVrJgqTvipJa2OEg2wCRA/Wo9s4M7Lchnv3NoYLOyNTzNyFopQRKILA==} + dependencies: + '@fluencelabs/aqua-lib': 0.7.0 + '@fluencelabs/trust-graph': 0.4.1 + dev: true + + /@fluencelabs/spell@0.5.20: + resolution: {integrity: sha512-QFbknWwALLUWMzpWkFt34McuwTz9xwQuiPP1zXqhPqVZ1J6g8F3gwHHtzgHFW5Z7WrRmwsL+IQtFJy8YZubhDw==} + dev: true + + /@fluencelabs/trust-graph@0.4.1: + resolution: {integrity: sha512-V/6ts4q/Y0uKMS6orVpPyxfdd99YFMkm9wN9U2IFtlBUWNsQZG369FK9qEizwsSRCqTchMHYs8Vh4wgZ2uRfuQ==} + dependencies: + '@fluencelabs/aqua-lib': 0.7.3 + dev: true + + /@fluencelabs/trust-graph@0.4.7: + resolution: {integrity: sha512-e4TxWimUh9GBWjqSO8WGsSqjZfyIs6f39/8Pzfo6PCcNoSf8FPaaO817Pw4FmAXYEKR1IalIUX3CDdym3NlHWw==} + dependencies: + '@fluencelabs/aqua-lib': 0.7.3 + dev: true + /@fluencelabs/trust-graph@3.0.4: resolution: {integrity: sha512-4CWe/dBuZwrj5iU6mTrLz5JCSy5v1fw7dYjD65Pz05xWAbLH2jw72YIJfbMX0utzb1qiM8CooXv1XKPgutCIHQ==} dependencies: @@ -10416,8 +10515,8 @@ packages: '@babel/plugin-transform-react-jsx': ^7.14.9 eslint: ^8.1.0 dependencies: - '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.22.5) - '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.5) + '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.22.10) + '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.10) eslint: 8.43.0 lodash: 4.17.21 string-natural-compare: 3.0.1 @@ -19542,7 +19641,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.3 - acorn: 8.9.0 + acorn: 8.10.0 commander: 2.20.3 source-map-support: 0.5.21 dev: false @@ -19858,6 +19957,10 @@ packages: /ts-pattern@3.3.3: resolution: {integrity: sha512-Z5EFi6g6wyX3uDFHqxF5W5c5h663oZg9O6aOiAT7fqNu0HPSfCxtHzrQ7SblTy738Mrg2Ezorky8H5aUOm8Pvg==} + /ts-pattern@5.0.5: + resolution: {integrity: sha512-tL0w8U/pgaacOmkb9fRlYzWEUDCfVjjv9dD4wHTgZ61MjhuMt46VNWTG747NqW6vRzoWIKABVhFSOJ82FvXrfA==} + dev: false + /tsconfck@2.1.1(typescript@4.7.2): resolution: {integrity: sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==} engines: {node: ^14.13.1 || ^16 || >=18} @@ -20532,7 +20635,7 @@ packages: '@vitest/runner': 0.29.7 '@vitest/spy': 0.29.7 '@vitest/utils': 0.29.7 - acorn: 8.9.0 + acorn: 8.10.0 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7