├── .eslintrc.json ├── .fluence └── provider-secrets.yaml ├── .github ├── actionlint.yaml ├── release-please │ ├── config.json │ └── manifest.json ├── renovate.json └── workflows │ ├── e2e.yml │ ├── lint.yml │ ├── release.yml │ ├── run-tests.yml │ ├── snapshot.yml │ └── tests.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── DEVELOPING.md ├── LICENSE ├── README.md ├── ci.cjs ├── package.json ├── packages ├── @tests │ ├── .eslintrc.json │ ├── aqua │ │ ├── _aqua │ │ │ ├── finalize_particle.aqua │ │ │ └── smoke_test.aqua │ │ ├── compile-aqua.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── _aqua │ │ │ │ ├── finalize_particle.ts │ │ │ │ └── smoke_test.ts │ │ │ ├── index.ts │ │ │ └── wasmb64.ts │ │ └── tsconfig.json │ ├── smoke │ │ ├── node │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── web-cra-ts │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ ├── favicon.ico │ │ │ │ ├── index.html │ │ │ │ ├── logo192.png │ │ │ │ ├── logo512.png │ │ │ │ ├── manifest.json │ │ │ │ └── robots.txt │ │ │ ├── src │ │ │ │ ├── App.css │ │ │ │ ├── App.test.tsx_ │ │ │ │ ├── App.tsx │ │ │ │ ├── index.css │ │ │ │ ├── index.tsx │ │ │ │ ├── logo.svg │ │ │ │ ├── react-app-env.d.ts │ │ │ │ ├── reportWebVitals.ts │ │ │ │ └── setupTests.ts │ │ │ ├── test │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── web │ │ │ ├── package.json │ │ │ ├── public │ │ │ ├── index.html │ │ │ └── index.js │ │ │ ├── src │ │ │ └── index.ts │ │ │ └── tsconfig.json │ └── test-utils │ │ ├── package.json │ │ ├── src │ │ └── index.ts │ │ └── tsconfig.json └── core │ ├── aqua-to-js │ ├── .eslintrc.json │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── common.ts │ │ ├── constants.ts │ │ ├── generate │ │ │ ├── __test__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── generate.snap.d.ts │ │ │ │ │ ├── generate.snap.js │ │ │ │ │ └── generate.snap.ts │ │ │ │ ├── generate.spec.ts │ │ │ │ └── sources │ │ │ │ │ └── smoke_test.aqua │ │ │ ├── function.ts │ │ │ ├── header.ts │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ └── service.ts │ │ ├── index.ts │ │ └── utils.ts │ └── tsconfig.json │ ├── interfaces │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── compilerSupport │ │ │ └── aquaTypeDefinitions.ts │ │ ├── future.ts │ │ └── index.ts │ └── tsconfig.json │ ├── js-client-isomorphic │ ├── .gitignore │ ├── CHANGELOG.md │ ├── createVersionFile.js │ ├── package.json │ ├── src │ │ ├── fetchers │ │ │ ├── browser.ts │ │ │ └── node.ts │ │ ├── types.ts │ │ └── worker-resolvers │ │ │ ├── browser.ts │ │ │ └── node.ts │ └── tsconfig.json │ ├── js-client │ ├── .npmignore │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── aqua │ │ ├── calc.aqua │ │ ├── node-utils.aqua │ │ ├── services.aqua │ │ ├── single-module-srv.aqua │ │ └── tracing.aqua │ ├── aqua_test │ │ ├── marine-js.aqua │ │ ├── sigService.aqua │ │ └── srv.aqua │ ├── data_for_test │ │ ├── greeting-record.wasm │ │ └── greeting.wasm │ ├── package.json │ ├── src │ │ ├── api.spec.ts │ │ ├── api.ts │ │ ├── clientPeer │ │ │ ├── ClientPeer.ts │ │ │ ├── __test__ │ │ │ │ ├── client.spec.ts │ │ │ │ └── connection.ts │ │ │ ├── checkConnection.ts │ │ │ └── types.ts │ │ ├── compilerSupport │ │ │ ├── __test__ │ │ │ │ └── conversion.spec.ts │ │ │ ├── callFunction.ts │ │ │ ├── conversions.ts │ │ │ ├── registerService.ts │ │ │ ├── services.ts │ │ │ └── types.ts │ │ ├── connection │ │ │ ├── RelayConnection.ts │ │ │ └── interfaces.ts │ │ ├── ephemeral │ │ │ ├── __test__ │ │ │ │ └── ephemeral.spec.ts │ │ │ ├── client.ts │ │ │ └── network.ts │ │ ├── index.ts │ │ ├── jsPeer │ │ │ ├── FluencePeer.ts │ │ │ ├── __test__ │ │ │ │ ├── avm.spec.ts │ │ │ │ ├── par.spec.ts │ │ │ │ ├── parseAst.spec.ts │ │ │ │ └── peer.spec.ts │ │ │ └── errors.ts │ │ ├── jsServiceHost │ │ │ ├── JsServiceHost.ts │ │ │ ├── interfaces.ts │ │ │ └── serviceUtils.ts │ │ ├── keypair │ │ │ ├── __test__ │ │ │ │ └── KeyPair.spec.ts │ │ │ └── index.ts │ │ ├── marine │ │ │ ├── __test__ │ │ │ │ └── marine-js.spec.ts │ │ │ ├── interfaces.ts │ │ │ ├── loader.ts │ │ │ └── worker │ │ │ │ └── index.ts │ │ ├── particle │ │ │ ├── Particle.ts │ │ │ └── interfaces.ts │ │ ├── services │ │ │ ├── NodeUtils.ts │ │ │ ├── Sig.ts │ │ │ ├── SingleModuleSrv.ts │ │ │ ├── Tracing.ts │ │ │ ├── __test__ │ │ │ │ ├── builtInHandler.spec.ts │ │ │ │ ├── jsonBuiltin.spec.ts │ │ │ │ ├── sigService.spec.ts │ │ │ │ └── srv.spec.ts │ │ │ ├── _aqua │ │ │ │ ├── node-utils.ts │ │ │ │ ├── services.ts │ │ │ │ ├── single-module-srv.ts │ │ │ │ └── tracing.ts │ │ │ ├── builtins.ts │ │ │ └── securityGuard.ts │ │ └── util │ │ │ ├── bytes.ts │ │ │ ├── commonTypes.ts │ │ │ ├── libp2pUtils.ts │ │ │ ├── logger.ts │ │ │ ├── testUtils.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ ├── tsconfig.json │ └── vite.config.ts │ ├── marine-worker │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── vite.config.ts │ └── npm-aqua-compiler │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ ├── imports.spec.ts │ └── imports.ts │ ├── test │ └── transitive-deps │ │ ├── A-0.1.0.tgz │ │ ├── B-0.1.0.tgz │ │ ├── C-0.1.0.tgz │ │ ├── C-0.2.0.tgz │ │ ├── D-0.1.0.tgz │ │ ├── D-0.2.0.tgz │ │ ├── empty-project │ │ ├── package-lock.json │ │ └── package.json │ │ └── project │ │ ├── main.aqua │ │ ├── package-lock.json │ │ └── package.json │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── reset.d.ts ├── resources └── license-header.js ├── tsconfig.eslint.json └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 2022, 6 | "project": ["./tsconfig.eslint.json"], 7 | "sourceType": "module" 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/strict-type-checked", 12 | "plugin:import/recommended", 13 | "plugin:import/typescript", 14 | "prettier" 15 | ], 16 | "plugins": [ 17 | "@typescript-eslint", 18 | "import", 19 | "license-header", 20 | "unused-imports" 21 | ], 22 | "ignorePatterns": ["**/node_modules/", "**/dist/", "**/build/", "**/public/"], 23 | "rules": { 24 | "eqeqeq": ["error", "always"], 25 | "no-console": ["error"], 26 | "arrow-body-style": ["error", "always"], 27 | "no-empty": [ 28 | "error", 29 | { 30 | "allowEmptyCatch": true 31 | } 32 | ], 33 | "curly": ["error", "all"], 34 | "no-unused-expressions": ["error"], 35 | "dot-notation": ["off"], 36 | "object-curly-spacing": ["error", "always"], 37 | "padding-line-between-statements": [ 38 | "error", 39 | { 40 | "blankLine": "always", 41 | "prev": "multiline-expression", 42 | "next": "*" 43 | }, 44 | { 45 | "blankLine": "always", 46 | "prev": "*", 47 | "next": "multiline-expression" 48 | }, 49 | { 50 | "blankLine": "always", 51 | "prev": "multiline-block-like", 52 | "next": "*" 53 | }, 54 | { 55 | "blankLine": "always", 56 | "prev": "*", 57 | "next": "multiline-block-like" 58 | }, 59 | { 60 | "blankLine": "always", 61 | "prev": "multiline-const", 62 | "next": "*" 63 | }, 64 | { 65 | "blankLine": "always", 66 | "prev": "*", 67 | "next": "multiline-const" 68 | }, 69 | { 70 | "blankLine": "always", 71 | "prev": "multiline-let", 72 | "next": "*" 73 | }, 74 | { 75 | "blankLine": "always", 76 | "prev": "*", 77 | "next": "multiline-let" 78 | }, 79 | { 80 | "blankLine": "any", 81 | "prev": "case", 82 | "next": "case" 83 | } 84 | ], 85 | "import/extensions": ["error", "ignorePackages"], 86 | "import/no-unresolved": "off", 87 | "import/no-cycle": ["error"], 88 | "import/order": [ 89 | "error", 90 | { 91 | "newlines-between": "always", 92 | "alphabetize": { 93 | "order": "asc", 94 | "caseInsensitive": true 95 | } 96 | } 97 | ], 98 | "node/no-unsupported-features/es-syntax": "off", 99 | "node/no-unpublished-import": "off", 100 | "node/no-missing-import": "off", 101 | "@typescript-eslint/explicit-member-accessibility": [ 102 | "error", 103 | { 104 | "accessibility": "no-public" 105 | } 106 | ], 107 | "@typescript-eslint/strict-boolean-expressions": [ 108 | "error", 109 | { 110 | "allowString": false, 111 | "allowNumber": false, 112 | "allowNullableObject": false, 113 | "allowNullableBoolean": false, 114 | "allowNullableString": false, 115 | "allowNullableNumber": false, 116 | "allowAny": false 117 | } 118 | ], 119 | "@typescript-eslint/consistent-type-assertions": [ 120 | "error", 121 | { 122 | "assertionStyle": "never" 123 | } 124 | ], 125 | "unused-imports/no-unused-imports": "error", 126 | "license-header/header": ["error", "./resources/license-header.js"] 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /.fluence/provider-secrets.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schemas/provider-secrets.json 2 | 3 | # Defines secrets config used for provider set up 4 | 5 | # Documentation: https://github.com/fluencelabs/cli/tree/main/docs/configs/provider-secrets.md 6 | 7 | version: 0 8 | 9 | noxes: 10 | nox-0: 11 | networkKey: t/7R5m9TprvbWFwr2ZKe8hm6UW84ulDq4pVbDvHUCdw= 12 | signingWallet: "0x3785b85a34b65082664bdb2ccb47fba6aab6984b7c16b36ad54145eecc368e4c" 13 | nox-1: 14 | networkKey: +5VkzeaILY+Emk9zsLKJ82dzzF3fMmlFkNhkJVwDMdo= 15 | signingWallet: "0xd9a5eee84979d0cf118f82f7c97ca6980e6a21db0c7271a3d59248841ed04d83" 16 | nox-2: 17 | networkKey: YNq7A6KlPhdyJwq7y3meTIPUVQ34HFwW21MlsDjpxsM= 18 | signingWallet: "0x16e3984df2918538c5dbba032f00a5ecd5900b9c43a36b38ed89b6be1820a9ba" 19 | -------------------------------------------------------------------------------- /.github/actionlint.yaml: -------------------------------------------------------------------------------- 1 | self-hosted-runner: 2 | labels: 3 | - builder 4 | -------------------------------------------------------------------------------- /.github/release-please/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "release-type": "node", 3 | "bump-minor-pre-major": true, 4 | "bump-patch-for-minor-pre-major": true, 5 | "plugins": [ 6 | { 7 | "type": "node-workspace" 8 | } 9 | ], 10 | "packages": { 11 | "packages/core/js-client": {}, 12 | "packages/core/js-client-isomorphic": {}, 13 | "packages/core/marine-worker": {}, 14 | "packages/core/aqua-to-js": {}, 15 | "packages/core/interfaces": {}, 16 | "packages/core/npm-aqua-compiler": {} 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/release-please/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages/core/js-client": "0.9.0", 3 | "packages/core/marine-worker": "0.6.0", 4 | "packages/core/aqua-to-js": "0.3.13", 5 | "packages/core/js-client-isomorphic": "0.6.0", 6 | "packages/core/interfaces": "0.12.0", 7 | "packages/core/npm-aqua-compiler": "0.0.3" 8 | } 9 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>fluencelabs/renovate", 4 | "github>fluencelabs/renovate:npm" 5 | ], 6 | "enabledManagers": ["npm"] 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: "e2e" 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "**.md" 7 | - ".github/**" 8 | - "!.github/workflows/e2e.yml" 9 | - "!.github/workflows/tests.yml" 10 | - "!.github/workflows/snapshot.yml" 11 | - "!.github/workflows/run-tests.yml" 12 | types: 13 | - "labeled" 14 | - "synchronize" 15 | - "opened" 16 | - "reopened" 17 | push: 18 | branches: 19 | - "main" 20 | paths-ignore: 21 | - "**.md" 22 | - ".github/**" 23 | - "!.github/workflows/e2e.yml" 24 | - "!.github/workflows/tests.yml" 25 | - "!.github/workflows/snapshot.yml" 26 | - "!.github/workflows/run-tests.yml" 27 | 28 | concurrency: 29 | group: "${{ github.workflow }}-${{ github.ref }}" 30 | cancel-in-progress: true 31 | 32 | jobs: 33 | js-client: 34 | if: > 35 | github.event_name == 'push' || 36 | contains(github.event.pull_request.labels.*.name, 'e2e') 37 | uses: ./.github/workflows/snapshot.yml 38 | with: 39 | ref: ${{ github.ref }} 40 | 41 | aqua: 42 | needs: 43 | - js-client 44 | 45 | uses: fluencelabs/aqua/.github/workflows/tests.yml@main 46 | with: 47 | js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}" 48 | 49 | fcli: 50 | needs: 51 | - js-client 52 | 53 | uses: fluencelabs/cli/.github/workflows/tests.yml@main 54 | with: 55 | js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}" 56 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | concurrency: 11 | group: "${{ github.workflow }}-${{ github.ref }}" 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | pr: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: amannn/action-semantic-pull-request@v5 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | reviewdog: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Lint actions 30 | uses: reviewdog/action-actionlint@v1 31 | env: 32 | SHELLCHECK_OPTS: "-e SC2086 -e SC2207 -e SC2128" 33 | with: 34 | reporter: github-pr-check 35 | fail_on_error: true 36 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: "test" 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "**.md" 7 | - ".github/**" 8 | - "!.github/workflows/e2e.yml" 9 | - "!.github/workflows/tests.yml" 10 | - "!.github/workflows/snapshot.yml" 11 | - "!.github/workflows/run-tests.yml" 12 | push: 13 | branches: 14 | - "main" 15 | paths-ignore: 16 | - "**.md" 17 | - ".github/**" 18 | - "!.github/workflows/e2e.yml" 19 | - "!.github/workflows/tests.yml" 20 | - "!.github/workflows/snapshot.yml" 21 | - "!.github/workflows/run-tests.yml" 22 | 23 | concurrency: 24 | group: "${{ github.workflow }}-${{ github.ref }}" 25 | cancel-in-progress: true 26 | 27 | jobs: 28 | tests: 29 | name: "js-client" 30 | uses: ./.github/workflows/tests.yml 31 | with: 32 | ref: ${{ github.ref }} 33 | -------------------------------------------------------------------------------- /.github/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Publish snapshot 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | avm-version: 7 | description: "@fluencelabs/avm version" 8 | type: string 9 | default: "null" 10 | marine-js-version: 11 | description: "@fluencelabs/marine-js version" 12 | type: string 13 | default: "null" 14 | ref: 15 | description: "git ref to checkout to" 16 | type: string 17 | default: "main" 18 | outputs: 19 | js-client-snapshots: 20 | description: "js-client snapshots" 21 | value: ${{ jobs.publish-snapshot.outputs.snapshots }} 22 | 23 | env: 24 | FORCE_COLOR: true 25 | CI: true 26 | 27 | jobs: 28 | publish-snapshot: 29 | name: "Publish snapshot" 30 | runs-on: builder 31 | 32 | outputs: 33 | snapshots: "${{ steps.snapshot.outputs.snapshots }}" 34 | 35 | permissions: 36 | contents: read 37 | id-token: write 38 | 39 | steps: 40 | - name: Checkout js-client 41 | uses: actions/checkout@v3 42 | with: 43 | repository: fluencelabs/js-client 44 | ref: ${{ inputs.ref }} 45 | 46 | - uses: pnpm/action-setup@v4 47 | with: 48 | version: 8 49 | 50 | - name: Setup node with self-hosted npm registry 51 | uses: actions/setup-node@v3 52 | with: 53 | node-version: "16" 54 | registry-url: "https://npm.fluence.dev" 55 | cache: "pnpm" 56 | 57 | - name: Override dependencies 58 | uses: fluencelabs/github-actions/pnpm-set-dependency@main 59 | with: 60 | dependencies: | 61 | { 62 | "@fluencelabs/avm": "${{ inputs.avm-version }}", 63 | "@fluencelabs/marine-js": "${{ inputs.marine-js-version }}" 64 | } 65 | 66 | - run: pnpm -r --no-frozen-lockfile i 67 | 68 | - name: Import secrets 69 | uses: hashicorp/vault-action@v2.4.3 70 | with: 71 | url: https://vault.fluence.dev 72 | path: jwt/github 73 | role: ci 74 | method: jwt 75 | jwtGithubAudience: "https://github.com/fluencelabs" 76 | jwtTtl: 300 77 | exportToken: false 78 | secrets: | 79 | kv/npm-registry/basicauth/ci token | NODE_AUTH_TOKEN 80 | 81 | - name: Generate snapshot version 82 | id: version 83 | uses: fluencelabs/github-actions/generate-snapshot-id@main 84 | 85 | - name: Set package version 86 | run: node ci.cjs bump-version ${{ steps.version.outputs.id }} 87 | 88 | - run: pnpm -r build 89 | 90 | - name: Publish snapshots 91 | id: snapshot 92 | uses: fluencelabs/github-actions/pnpm-publish-snapshot@main 93 | with: 94 | id: ${{ steps.version.outputs.id }} 95 | set-version: false 96 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests with workflow_call 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | nox-image: 7 | description: "nox image tag" 8 | type: string 9 | default: "fluencelabs/nox:unstable" 10 | avm-version: 11 | description: "@fluencelabs/avm version" 12 | type: string 13 | default: "null" 14 | marine-js-version: 15 | description: "@fluencelabs/marine-js version" 16 | type: string 17 | default: "null" 18 | ref: 19 | description: "git ref to checkout to" 20 | type: string 21 | default: "main" 22 | 23 | env: 24 | FORCE_COLOR: true 25 | CI: true 26 | FCLI_V_NOX: "${{ inputs.nox-image }}" 27 | 28 | jobs: 29 | js-client: 30 | name: "Run tests" 31 | runs-on: ubuntu-latest 32 | 33 | permissions: 34 | contents: read 35 | id-token: write 36 | 37 | env: 38 | LATEST_NODE_VERSION: 20.x 39 | 40 | strategy: 41 | matrix: 42 | node-version: 43 | - 18.x 44 | - 20.x 45 | 46 | steps: 47 | - name: Import secrets 48 | uses: hashicorp/vault-action@v2.4.3 49 | with: 50 | url: https://vault.fluence.dev 51 | path: jwt/github 52 | role: ci 53 | method: jwt 54 | jwtGithubAudience: "https://github.com/fluencelabs" 55 | jwtTtl: 300 56 | secrets: | 57 | kv/docker-registry/basicauth/ci username | DOCKER_USERNAME ; 58 | kv/docker-registry/basicauth/ci password | DOCKER_PASSWORD 59 | 60 | - name: Login to DockerHub 61 | uses: docker/login-action@v2 62 | with: 63 | registry: docker.fluence.dev 64 | username: ${{ env.DOCKER_USERNAME }} 65 | password: ${{ env.DOCKER_PASSWORD }} 66 | 67 | - name: Checkout 68 | uses: actions/checkout@v3 69 | with: 70 | repository: fluencelabs/fluence-js 71 | ref: ${{ inputs.ref }} 72 | 73 | - name: Setup fcli 74 | uses: fluencelabs/setup-fluence@v1 75 | with: 76 | artifact: fcli 77 | version: unstable 78 | continue-on-error: true 79 | 80 | - name: Initialize FCLI 81 | run: fluence provider init --env=local --no-input 82 | 83 | - name: Run nox network 84 | run: fluence local up 85 | 86 | - name: Setup pnpm 87 | uses: pnpm/action-setup@v4 88 | with: 89 | version: 8 90 | 91 | - name: Setup node ${{ matrix.node-version }} with self-hosted registry 92 | uses: actions/setup-node@v3 93 | with: 94 | node-version: ${{ matrix.node-version }} 95 | registry-url: "https://npm.fluence.dev" 96 | cache: "pnpm" 97 | 98 | - name: Override dependencies 99 | uses: fluencelabs/github-actions/pnpm-set-dependency@main 100 | with: 101 | dependencies: | 102 | { 103 | "@fluencelabs/avm": "${{ inputs.avm-version }}", 104 | "@fluencelabs/marine-js": "${{ inputs.marine-js-version }}" 105 | } 106 | 107 | - run: pnpm -r --no-frozen-lockfile i 108 | - run: pnpm -r build 109 | 110 | - name: Lint code 111 | run: pnpm lint-check 112 | 113 | - run: pnpm -r test 114 | 115 | - name: Dump container logs 116 | if: always() 117 | uses: jwalton/gh-docker-logs@v2 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .idea 3 | .eslintcache 4 | 5 | # Dependency directories 6 | node_modules/ 7 | 8 | # Build directory 9 | dist/ 10 | public/ 11 | .DS_Store 12 | 13 | .fluence/ 14 | # so we have stable peer ids for tests 15 | !.fluence/provider-secrets.yaml 16 | provider.yaml 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | save-exact=true 3 | side-effects-cache=false -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github 2 | .eslintcache 3 | pnpm-lock.yaml 4 | 5 | node_modules 6 | dist 7 | build 8 | public 9 | 10 | .fluence 11 | 12 | **/CHANGELOG.md 13 | # TODO: remove after pnpm-set-deps will add \n symbol at the end of these files 14 | **/package.json 15 | 16 | packages/core/js-client-isomorphic/src/versions.ts 17 | __snapshots__ 18 | packages/@tests/aqua/src/_aqua/** 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribute Code 2 | 3 | You are welcome to contribute to Fluence! 4 | 5 | Things you need to know: 6 | 7 | 1. You need to **agree to the [Contributor License Agreement](https://gist.github.com/fluencelabs-org/3f4cbb3cc14c1c0fb9ad99d8f7316ed7) (CLA)**. This is a common practice in all major Open Source projects. At the current moment, we are unable to accept contributions made on behalf of a company. Only individual contributions will be accepted. 8 | 9 | 2. **Not all proposed contributions can be accepted**. Some features may, e.g., just fit a third-party add-on better. The contribution must fit the overall direction of Fluence and really improve it. The more effort you invest, the better you should clarify in advance whether the contribution fits: the best way would be to just open an issue to discuss the contribution you plan to make. 10 | 11 | ### Contributor License Agreement 12 | 13 | When you contribute, you have to be aware that your contribution is covered by **[Apache License 2.0](./LICENSE)**, but might relicensed under few other software licenses mentioned in the **Contributor License Agreement**. In particular, you need to agree to the Contributor License Agreement. If you agree to its content, you simply have to click on the link posted by the CLA assistant as a comment to the pull request. Click it to check the CLA, then accept it on the following screen if you agree to it. The CLA assistant will save this decision for upcoming contributions and will notify you if there is any change to the CLA in the meantime. 14 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Setting up dev environment 2 | 3 | JS Client uses pnpm to manage monorepo packages. See [pnpm.io](https://pnpm.io/installation) for installation instructions. 4 | 5 | Install dependencies 6 | 7 | ```bash 8 | pnpm install 9 | ``` 10 | 11 | Build all packages 12 | 13 | ``` 14 | pnpm -r build 15 | ``` 16 | 17 | # Running tests 18 | 19 | Tests are split into unit and integration categories. By default integration tests require a locally running Fluence node with 4310 port open for ws connections. The dependency can be started with docker 20 | 21 | ```bash 22 | docker run --rm -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj 23 | ``` 24 | 25 | To run all tests 26 | 27 | ```bash 28 | pnpm -r test 29 | ``` 30 | 31 | To run only unit tests 32 | 33 | ```bash 34 | pnpm -r test:unit 35 | ``` 36 | 37 | To run only integration tests 38 | 39 | ```bash 40 | pnpm -r test:integration 41 | ``` 42 | 43 | # Repo structure: 44 | 45 | TBD 46 | 47 | # Architecture 48 | 49 | TBD 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common-dev-deps", 3 | "version": "0.1.0", 4 | "main": "./dist/index.js", 5 | "typings": "./dist/index.d.ts", 6 | "type": "module", 7 | "engines": { 8 | "node": ">=10", 9 | "pnpm": ">=3" 10 | }, 11 | "scripts": { 12 | "lint-check": "pnpm run prettier --check && pnpm run eslint", 13 | "lint-fix": "pnpm run prettier --write && pnpm run eslint --fix", 14 | "prettier": "prettier .", 15 | "eslint": "eslint --cache \"**/src/**/*.{js,ts}\"" 16 | }, 17 | "author": "Fluence DAO", 18 | "license": "Apache-2.0", 19 | "devDependencies": { 20 | "@total-typescript/ts-reset": "0.5.1", 21 | "@tsconfig/strictest": "2.0.2", 22 | "@types/node": "18.13.0", 23 | "@typescript-eslint/eslint-plugin": "6.7.3", 24 | "@typescript-eslint/parser": "6.7.3", 25 | "eslint": "8.50.0", 26 | "eslint-config-prettier": "9.0.0", 27 | "eslint-plugin-import": "2.28.1", 28 | "eslint-plugin-license-header": "0.6.0", 29 | "eslint-plugin-unused-imports": "3.0.0", 30 | "http-server": "14.1.1", 31 | "prettier": "3.0.3", 32 | "puppeteer": "19.7.2", 33 | "ts-node": "10.9.1", 34 | "typescript": "5.1.6" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/@tests/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": ["**/*.css"], 3 | "rules": { 4 | "no-console": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/@tests/aqua/_aqua/finalize_particle.aqua: -------------------------------------------------------------------------------- 1 | import "@fluencelabs/aqua-lib/builtin.aqua" 2 | export test 3 | 4 | func test(): 5 | on HOST_PEER_ID: 6 | Op.noop() -------------------------------------------------------------------------------- /packages/@tests/aqua/_aqua/smoke_test.aqua: -------------------------------------------------------------------------------- 1 | import "@fluencelabs/registry/resources-api.aqua" 2 | 3 | service HelloWorld("hello-world"): 4 | hello(str: string) -> string 5 | 6 | func resourceTest(label: string) -> ?string, *string: 7 | res, errors <- createResource(label) 8 | <- res, errors 9 | 10 | func helloTest() -> string: 11 | hello <- HelloWorld.hello("Fluence user") 12 | <- hello 13 | 14 | service CalcService: 15 | add(num: f64) -> f64 16 | clear_state() 17 | divide(num: f64) -> f64 18 | multiply(num: f64) -> f64 19 | state() -> f64 20 | subtract(num: f64) -> f64 21 | test_logs() 22 | 23 | data ServiceCreationResult: 24 | success: bool 25 | service_id: ?string 26 | error: ?string 27 | 28 | data RemoveResult: 29 | success: bool 30 | error: ?string 31 | 32 | alias ListServiceResult: []string 33 | 34 | service Srv("single_module_srv"): 35 | create(wasm_b64_content: string) -> ServiceCreationResult 36 | remove(service_id: string) -> RemoveResult 37 | list() -> ListServiceResult 38 | 39 | 40 | func demo_calculation(service_id: string) -> f64: 41 | CalcService service_id 42 | CalcService.test_logs() 43 | CalcService.add(10) 44 | CalcService.multiply(5) 45 | CalcService.subtract(8) 46 | CalcService.divide(6) 47 | res <- CalcService.state() 48 | <- res 49 | 50 | func marineTest(wasm64: string) -> f64: 51 | serviceResult <- Srv.create(wasm64) 52 | res <- demo_calculation(serviceResult.service_id!) 53 | <- res 54 | 55 | 56 | func callHappy(a: string, b: f64, c: f64, d: string -> f64) -> f64: 57 | res <- d("abc") 58 | <- res 59 | -------------------------------------------------------------------------------- /packages/@tests/aqua/compile-aqua.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { writeFile } from "fs/promises"; 18 | import { join, dirname } from "path"; 19 | import { fileURLToPath } from "url"; 20 | 21 | import { compileFromPath } from "@fluencelabs/aqua-api"; 22 | import aquaToJs from "@fluencelabs/aqua-to-js"; 23 | 24 | const files = ["smoke_test", "finalize_particle"]; 25 | 26 | for (const file of files) { 27 | const cr = await compileFromPath({ 28 | filePath: join( 29 | dirname(fileURLToPath(import.meta.url)), 30 | "_aqua", 31 | file + ".aqua", 32 | ), 33 | targetType: "air", 34 | imports: [fileURLToPath(new URL("./node_modules", import.meta.url))], 35 | }); 36 | 37 | if (cr.errors.length > 0) { 38 | throw new Error(cr.errors.join("\n")); 39 | } 40 | 41 | const res = await aquaToJs(cr, "ts"); 42 | 43 | if (res == null) { 44 | throw new Error("AquaToJs gave null value after compilation"); 45 | } 46 | 47 | await writeFile( 48 | fileURLToPath(new URL(join("src", "_aqua", file + ".ts"), import.meta.url)), 49 | res.sources, 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /packages/@tests/aqua/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/aqua_for_test", 3 | "version": "0.1.0", 4 | "description": "Shared aqua code for tests", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "engines": { 8 | "node": ">=10", 9 | "pnpm": ">=3" 10 | }, 11 | "type": "module", 12 | "scripts": { 13 | "build": "tsc", 14 | "compile-aqua": "node --loader ts-node/esm compile-aqua.ts" 15 | }, 16 | "repository": "https://github.com/fluencelabs/fluence-js", 17 | "author": "Fluence DAO", 18 | "license": "Apache-2.0", 19 | "dependencies": { 20 | "base64-js": "1.5.1" 21 | }, 22 | "devDependencies": { 23 | "@fluencelabs/aqua-api": "0.13.0", 24 | "@fluencelabs/aqua-lib": "0.6.0", 25 | "@fluencelabs/aqua-to-js": "workspace:*", 26 | "@fluencelabs/js-client": "workspace:*", 27 | "@fluencelabs/registry": "0.9.0", 28 | "@fluencelabs/trust-graph": "3.1.2", 29 | "ts-node": "10.9.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/@tests/aqua/src/_aqua/finalize_particle.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | /** 4 | * 5 | * This file is generated using: 6 | * @fluencelabs/aqua-api version: 0.12.4-main-cee4448-2196-1 7 | * @fluencelabs/aqua-to-js version: 0.2.0 8 | * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues 9 | * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues 10 | * 11 | */ 12 | import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client'; 13 | 14 | // Making aliases to reduce chance of accidental name collision 15 | import { 16 | v5_callFunction as callFunction$$, 17 | v5_registerService as registerService$$ 18 | } from '@fluencelabs/js-client'; 19 | 20 | 21 | // Functions 22 | export const test_script = ` 23 | (xor 24 | (seq 25 | (seq 26 | (call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-) 27 | (xor 28 | (call -relay- ("op" "noop") []) 29 | (fail :error:) 30 | ) 31 | ) 32 | (call %init_peer_id% ("callbackSrv" "response") []) 33 | ) 34 | (call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0]) 35 | ) 36 | `; 37 | 38 | export type TestParams = [config?: {ttl?: number}] | [peer: IFluenceClient$$, config?: {ttl?: number}]; 39 | 40 | export type TestResult = Promise; 41 | 42 | export function test(...args: TestParams): TestResult { 43 | return callFunction$$( 44 | args, 45 | { 46 | "functionName": "test", 47 | "arrow": { 48 | "domain": { 49 | "fields": {}, 50 | "tag": "labeledProduct" 51 | }, 52 | "codomain": { 53 | "tag": "nil" 54 | }, 55 | "tag": "arrow" 56 | }, 57 | "names": { 58 | "relay": "-relay-", 59 | "getDataSrv": "getDataSrv", 60 | "callbackSrv": "callbackSrv", 61 | "responseSrv": "callbackSrv", 62 | "responseFnName": "response", 63 | "errorHandlingSrv": "errorHandlingSrv", 64 | "errorFnName": "error" 65 | } 66 | }, 67 | test_script 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /packages/@tests/aqua/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Fluence, type ClientConfig } from "@fluencelabs/js-client"; 18 | 19 | import { test as particleTest } from "./_aqua/finalize_particle.js"; 20 | import { 21 | registerHelloWorld, 22 | helloTest, 23 | marineTest, 24 | } from "./_aqua/smoke_test.js"; 25 | import { wasm } from "./wasmb64.js"; 26 | 27 | const relay = { 28 | multiaddr: 29 | "/ip4/127.0.0.1/tcp/999/ws/p2p/12D3KooWBbMuqJJZT7FTFN4fWg3k3ipUKx6KEy7pDy8mdorK5g5o", 30 | peerId: "12D3KooWBbMuqJJZT7FTFN4fWg3k3ipUKx6KEy7pDy8mdorK5g5o", 31 | }; 32 | 33 | function generateRandomUint8Array() { 34 | const uint8Array = new Uint8Array(32); 35 | 36 | for (let i = 0; i < uint8Array.length; i++) { 37 | uint8Array[i] = Math.floor(Math.random() * 256); 38 | } 39 | 40 | return uint8Array; 41 | } 42 | 43 | const optsWithRandomKeyPair = (): ClientConfig => { 44 | return { 45 | keyPair: { 46 | type: "Ed25519", 47 | source: generateRandomUint8Array(), 48 | }, 49 | } as const; 50 | }; 51 | 52 | export type TestResult = 53 | | { type: "success"; data: string } 54 | | { type: "failure"; error: string }; 55 | 56 | export const runTest = async (): Promise => { 57 | try { 58 | console.log("connecting to Fluence Network..."); 59 | console.log("multiaddr: ", relay.multiaddr); 60 | 61 | await Fluence.connect(relay, { 62 | ...optsWithRandomKeyPair(), 63 | CDNUrl: "http://localhost:3001", 64 | }); 65 | 66 | console.log("connected"); 67 | 68 | const relayPeerId = Fluence.getClient().getRelayPeerId(); 69 | console.log("relay:", relayPeerId); 70 | 71 | registerHelloWorld({ 72 | hello(str) { 73 | return "Hello, " + str + "!"; 74 | }, 75 | }); 76 | 77 | const client = Fluence.getClient(); 78 | 79 | console.log("my peer id: ", client.getPeerId()); 80 | 81 | console.log("running hello test..."); 82 | const hello = await helloTest(); 83 | console.log("hello test finished, result: ", hello); 84 | 85 | console.log("running marine test..."); 86 | const marine = await marineTest(wasm); 87 | console.log("marine test finished, result: ", marine); 88 | 89 | console.log("running particle test..."); 90 | 91 | await particleTest(); 92 | 93 | const returnVal = { 94 | hello, 95 | marine, 96 | }; 97 | 98 | return { type: "success", data: JSON.stringify(returnVal) }; 99 | } finally { 100 | console.log("disconnecting from Fluence Network..."); 101 | await Fluence.disconnect(); 102 | console.log("disconnected"); 103 | } 104 | }; 105 | 106 | export const runMain = () => { 107 | runTest() 108 | .then(() => { 109 | console.log("done!"); 110 | }) 111 | .catch((err) => { 112 | console.error("error: ", err); 113 | }); 114 | }; 115 | -------------------------------------------------------------------------------- /packages/@tests/aqua/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "module": "NodeNext" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/@tests/smoke/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/smoke", 3 | "version": "0.1.0", 4 | "description": "Smoke test", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "engines": { 8 | "node": ">=10", 9 | "pnpm": ">=3" 10 | }, 11 | "type": "module", 12 | "scripts": { 13 | "build": "tsc", 14 | "test": "node --loader ts-node/esm ./src/index.ts" 15 | }, 16 | "repository": "https://github.com/fluencelabs/fluence-js", 17 | "author": "Fluence DAO", 18 | "license": "Apache-2.0", 19 | "dependencies": { 20 | "@fluencelabs/js-client": "workspace:*", 21 | "@test/aqua_for_test": "workspace:*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/@tests/smoke/node/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { runTest } from "@test/aqua_for_test"; 18 | 19 | await runTest(); 20 | console.log("Smoke tests succeed!"); 21 | -------------------------------------------------------------------------------- /packages/@tests/smoke/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "exclude": ["node_modules", "dist"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra-ts", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "dependencies": { 7 | "@test/aqua_for_test": "workspace:*", 8 | "@testing-library/jest-dom": "5.16.5", 9 | "@testing-library/react": "13.4.0", 10 | "@testing-library/user-event": "13.5.0", 11 | "@types/jest": "27.5.2", 12 | "@types/node": "16.18.12", 13 | "@types/react": "18.0.27", 14 | "@types/react-dom": "18.0.10", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-scripts": "5.0.1", 18 | "web-vitals": "2.1.4" 19 | }, 20 | "devDependencies": { 21 | "@test/test-utils": "workspace:*", 22 | "puppeteer": "19.7.2" 23 | }, 24 | "scripts": { 25 | "test": "node --loader ts-node/esm ./test/index.ts", 26 | "simulate-cdn": "http-server -p 8766 ../../../client/js-client.web.standalone/dist", 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "_test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/@tests/smoke/web-cra-ts/public/favicon.ico -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/@tests/smoke/web-cra-ts/public/logo192.png -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/@tests/smoke/web-cra-ts/public/logo512.png -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/App.test.tsx_: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { runTest, TestResult } from "@test/aqua_for_test"; 2 | import React from "react"; 3 | import logo from "./logo.svg"; 4 | import "./App.css"; 5 | 6 | function App() { 7 | const [result, setResult] = React.useState(null); 8 | 9 | const onButtonClick = () => { 10 | runTest() 11 | .then((res) => { 12 | setResult(res); 13 | }) 14 | .catch((err) => { 15 | if (err instanceof Error) { 16 | console.log( 17 | JSON.stringify({ 18 | name: err.name, 19 | message: err.message, 20 | stack: err.stack, 21 | }), 22 | ); 23 | } else { 24 | console.log(JSON.stringify(err)); 25 | } 26 | 27 | setResult({ type: "failure", error: err.toString() }); 28 | }); 29 | }; 30 | 31 | return ( 32 |
33 |
34 | logo 35 |

36 | Edit src/App.tsx and save to reload. 37 |

38 | 41 | 42 | {result && result.type === "success" && ( 43 |
{result.data}
44 | )} 45 | {result && result.type === "failure" && ( 46 |
{result.error}
47 | )} 48 | 54 | Learn React 55 | 56 |
57 |
58 | ); 59 | } 60 | 61 | export default App; 62 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById("root") as HTMLElement, 9 | ); 10 | root.render( 11 | 12 | 13 | , 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from "web-vitals"; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/test/index.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | import { dirname, join } from "path"; 3 | import { fileURLToPath } from "url"; 4 | 5 | import { 6 | CDN_PUBLIC_PATH, 7 | createSymlinkIfNotExists, 8 | JS_CLIENT_DEPS_PATH, 9 | startContentServer, 10 | stopServer, 11 | } from "@test/test-utils"; 12 | 13 | const port = 3001; 14 | const uri = `http://localhost:${port}/`; 15 | const __dirname = dirname(fileURLToPath(import.meta.url)); 16 | const publicPath = join(__dirname, "../build/"); 17 | 18 | const test = async () => { 19 | const localServer = await startContentServer(port, publicPath); 20 | 21 | await createSymlinkIfNotExists( 22 | JS_CLIENT_DEPS_PATH, 23 | join(publicPath, "node_modules"), 24 | ); 25 | 26 | console.log("starting puppeteer..."); 27 | const browser = await puppeteer.launch(); 28 | const page = (await browser.pages())[0]; 29 | page.on("console", (message) => 30 | console.log(`${message.type().toUpperCase()}: ${message.text()}`), 31 | ); 32 | 33 | page.on("request", (request) => { 34 | console.log(`INFO: ${request.url()} ${request.method()}`); 35 | }); 36 | 37 | page.on("requestfailed", (request) => { 38 | console.log(`ERROR: ${request.url()} ${request.failure()?.errorText}`); 39 | }); 40 | 41 | console.log("going to the page in browser..."); 42 | await page.goto(uri); 43 | 44 | console.log("clicking button..."); 45 | await page.click("#btn"); 46 | 47 | console.log("waiting for result to appear..."); 48 | const elem = await page.waitForSelector("#res"); 49 | 50 | console.log("getting the content of result div..."); 51 | const content = await elem?.evaluate((x) => x.textContent); 52 | console.log("raw result: ", content); 53 | 54 | await browser.close(); 55 | await stopServer(localServer); 56 | 57 | if (!content) { 58 | throw new Error("smoke test failed!"); 59 | } 60 | }; 61 | 62 | test().then(() => console.log("smoke tests succeed!")); 63 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web-cra-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src", "test"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tests/smoke_web", 3 | "version": "0.1.0", 4 | "description": "Smoke test web", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "engines": { 8 | "node": ">=10", 9 | "pnpm": ">=3" 10 | }, 11 | "type": "module", 12 | "scripts": { 13 | "build": "tsc", 14 | "simulate-cdn": "http-server -p 8765 ../../../client/js-client.web.standalone/dist", 15 | "test": "node --loader ts-node/esm ./src/index.ts", 16 | "serve": "http-server public" 17 | }, 18 | "repository": "https://github.com/fluencelabs/fluence-js", 19 | "author": "Fluence DAO", 20 | "license": "Apache-2.0", 21 | "dependencies": { 22 | "@fluencelabs/js-client-isomorphic": "workspace:*", 23 | "@test/test-utils": "workspace:*" 24 | }, 25 | "devDependencies": { 26 | "puppeteer": "19.7.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Smoke test for web 9 | 10 | 11 | 12 |
13 |

Open console f12

14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web/public/index.js: -------------------------------------------------------------------------------- 1 | import { Fluence, callAquaFunction } from "./js-client.min.js"; 2 | 3 | const relay = { 4 | multiaddr: 5 | "/ip4/127.0.0.1/tcp/999/ws/p2p/12D3KooWBbMuqJJZT7FTFN4fWg3k3ipUKx6KEy7pDy8mdorK5g5o", 6 | peerId: "12D3KooWBbMuqJJZT7FTFN4fWg3k3ipUKx6KEy7pDy8mdorK5g5o", 7 | }; 8 | 9 | const getRelayTime = () => { 10 | const script = ` 11 | (xor 12 | (seq 13 | (seq 14 | (seq 15 | (seq 16 | (call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-) 17 | (call %init_peer_id% ("getDataSrv" "relayPeerId") [] relayPeerId) 18 | ) 19 | (call -relay- ("op" "noop") []) 20 | ) 21 | (xor 22 | (seq 23 | (call relayPeerId ("peer" "timestamp_ms") [] ts) 24 | (call -relay- ("op" "noop") []) 25 | ) 26 | (seq 27 | (call -relay- ("op" "noop") []) 28 | (call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1]) 29 | ) 30 | ) 31 | ) 32 | (xor 33 | (call %init_peer_id% ("callbackSrv" "response") [ts]) 34 | (call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2]) 35 | ) 36 | ) 37 | (call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3]) 38 | )`; 39 | 40 | const def = { 41 | functionName: "getRelayTime", 42 | arrow: { 43 | tag: "arrow", 44 | domain: { 45 | tag: "labeledProduct", 46 | fields: { 47 | relayPeerId: { 48 | tag: "scalar", 49 | name: "string", 50 | }, 51 | }, 52 | }, 53 | codomain: { 54 | tag: "unlabeledProduct", 55 | items: [ 56 | { 57 | tag: "scalar", 58 | name: "u64", 59 | }, 60 | ], 61 | }, 62 | }, 63 | names: { 64 | relay: "-relay-", 65 | getDataSrv: "getDataSrv", 66 | callbackSrv: "callbackSrv", 67 | responseSrv: "callbackSrv", 68 | responseFnName: "response", 69 | errorHandlingSrv: "errorHandlingSrv", 70 | errorFnName: "error", 71 | }, 72 | }; 73 | 74 | const config = {}; 75 | 76 | const args = { relayPeerId: relay.peerId }; 77 | 78 | return callAquaFunction({ 79 | args, 80 | script, 81 | config, 82 | peer: Fluence.defaultClient, 83 | fireAndForget: false 84 | }); 85 | }; 86 | 87 | const main = async () => { 88 | console.log("starting fluence..."); 89 | await Fluence.connect(relay, { 90 | CDNUrl: "http://localhost:3000", 91 | }); 92 | console.log("started fluence"); 93 | 94 | console.log("getting relay time..."); 95 | const relayTime = await getRelayTime(); 96 | console.log("got relay time, ", relayTime); 97 | 98 | console.log("stopping fluence..."); 99 | await Fluence.disconnect(); 100 | console.log("stopped fluence..."); 101 | 102 | return relayTime; 103 | }; 104 | 105 | const btn = document.getElementById("btn"); 106 | 107 | btn.addEventListener("click", () => { 108 | main().then((res) => { 109 | const inner = document.createElement("div"); 110 | inner.id = "res"; 111 | inner.innerText = res; 112 | document.getElementById("res-placeholder").appendChild(inner); 113 | }).catch(err => { 114 | if (err instanceof Error) { 115 | console.log(JSON.stringify({ name: err.name, message: err.message, stack: err.stack })); 116 | return; 117 | } 118 | console.log(JSON.stringify(err)); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import assert from "node:assert"; 18 | import { dirname, join } from "path"; 19 | import { fileURLToPath } from "url"; 20 | 21 | import { 22 | CDN_PUBLIC_PATH, 23 | createSymlinkIfNotExists, 24 | JS_CLIENT_DEPS_PATH, 25 | startContentServer, 26 | stopServer, 27 | } from "@test/test-utils"; 28 | import puppeteer from "puppeteer"; 29 | 30 | const port = 3000; 31 | const uri = `http://localhost:${port}/`; 32 | const __dirname = dirname(fileURLToPath(import.meta.url)); 33 | const publicPath = join(__dirname, "../public/"); 34 | 35 | const test = async () => { 36 | const localServer = await startContentServer(port, publicPath); 37 | 38 | await createSymlinkIfNotExists(CDN_PUBLIC_PATH, join(publicPath, "source")); 39 | 40 | await createSymlinkIfNotExists( 41 | JS_CLIENT_DEPS_PATH, 42 | join(publicPath, "node_modules"), 43 | ); 44 | 45 | console.log("starting puppeteer..."); 46 | const browser = await puppeteer.launch(); 47 | const page = (await browser.pages())[0]; 48 | 49 | assert(page); 50 | 51 | page.on("console", (message) => { 52 | console.log(`${message.type().toUpperCase()}: ${message.text()}`); 53 | }); 54 | 55 | page.on("request", (request) => { 56 | console.log(`INFO: ${request.url()} ${request.method()}`); 57 | }); 58 | 59 | page.on("requestfailed", (request) => { 60 | console.log(`ERROR: ${request.url()} ${request.failure()?.errorText}`); 61 | }); 62 | 63 | console.log("going to the page in browser..."); 64 | await page.goto(uri); 65 | 66 | console.log("clicking button..."); 67 | await page.click("#btn"); 68 | 69 | console.log("waiting for result to appear..."); 70 | const elem = await page.waitForSelector("#res"); 71 | 72 | console.log("getting the content of result div..."); 73 | 74 | const content = await elem?.evaluate((x) => { 75 | return x.textContent; 76 | }); 77 | 78 | console.log("raw result: ", content); 79 | 80 | await browser.close(); 81 | await stopServer(localServer); 82 | 83 | if (content === null || content === undefined) { 84 | throw new Error("smoke test failed!"); 85 | } 86 | }; 87 | 88 | void test().then(() => { 89 | console.log("smoke tests succeed!"); 90 | }); 91 | -------------------------------------------------------------------------------- /packages/@tests/smoke/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "exclude": ["node_modules", "dist", "public"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/@tests/test-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/test-utils", 3 | "version": "0.1.0", 4 | "description": "Test utils", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "engines": { 8 | "node": ">=10", 9 | "pnpm": ">=3" 10 | }, 11 | "type": "module", 12 | "scripts": { 13 | "build": "tsc" 14 | }, 15 | "repository": "https://github.com/fluencelabs/fluence-js", 16 | "author": "Fluence DAO", 17 | "license": "Apache-2.0", 18 | "dependencies": { 19 | "serve-handler": "6.1.5" 20 | }, 21 | "devDependencies": { 22 | "@types/serve-handler": "6.1.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/@tests/test-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { access, symlink } from "fs/promises"; 18 | import { createServer } from "http"; 19 | import type { Server } from "http"; 20 | import { dirname, join } from "path"; 21 | import { fileURLToPath } from "url"; 22 | 23 | import handler from "serve-handler"; 24 | 25 | const __dirname = dirname(fileURLToPath(import.meta.url)); 26 | 27 | export const CDN_PUBLIC_PATH = join( 28 | __dirname, 29 | "../../../core/js-client/dist/browser", 30 | ); 31 | 32 | export const JS_CLIENT_DEPS_PATH = join( 33 | __dirname, 34 | "../../../core/js-client/node_modules", 35 | ); 36 | 37 | export const startCdn = (port: number) => { 38 | return startContentServer(port, CDN_PUBLIC_PATH); 39 | }; 40 | 41 | export const createSymlinkIfNotExists = async ( 42 | target: string, 43 | path: string, 44 | ) => { 45 | try { 46 | await access(path); 47 | } catch { 48 | await symlink(target, path); 49 | } 50 | }; 51 | 52 | export const startContentServer = ( 53 | port: number, 54 | publicDir: string, 55 | ): Promise => { 56 | const server = createServer((request, response) => { 57 | void handler(request, response, { 58 | public: publicDir, 59 | rewrites: [ 60 | { 61 | source: "/js-client.min.js", 62 | destination: "/source/index.min.js", 63 | }, 64 | { 65 | source: "/@fluencelabs/:name([\\w-]+)@:version([\\w-.]+)/dist/:asset", 66 | destination: 67 | "/node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/:name/dist/:asset", 68 | }, 69 | { 70 | source: 71 | "/@fluencelabs/:name([\\w-]+)@:version([\\w-.]+)/dist/:prefix/:asset", 72 | destination: 73 | "/node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/:name/dist/:prefix/:asset", 74 | }, 75 | ], 76 | headers: [ 77 | { 78 | source: "**/*", 79 | headers: [ 80 | { 81 | key: "Cross-Origin-Opener-Policy", 82 | value: "same-origin", 83 | }, 84 | { 85 | key: "Cross-Origin-Embedder-Policy", 86 | value: "require-corp", 87 | }, 88 | ], 89 | }, 90 | ], 91 | }); 92 | }); 93 | 94 | return new Promise((resolve) => { 95 | const result = server.listen(port, () => { 96 | console.log(`server started on port ${port}`); 97 | console.log(`public dir ${publicDir}`); 98 | resolve(result); 99 | }); 100 | }); 101 | }; 102 | 103 | export const stopServer = (app: Server): Promise => { 104 | return new Promise((resolve) => { 105 | app.close(() => { 106 | console.log("server stopped"); 107 | resolve(); 108 | }); 109 | }); 110 | }; 111 | -------------------------------------------------------------------------------- /packages/@tests/test-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src"], 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": ["src/**/__snapshots__/**/*", "src/**/*.js"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fluencelabs/aqua-to-js", 3 | "type": "module", 4 | "version": "0.3.13", 5 | "description": "Tool for generating aqua wrapper", 6 | "main": "dist/index.js", 7 | "files": [ 8 | "dist" 9 | ], 10 | "scripts": { 11 | "test": "vitest run", 12 | "build": "tsc" 13 | }, 14 | "keywords": [], 15 | "author": "Fluence DAO", 16 | "license": "Apache-2.0", 17 | "dependencies": { 18 | "ts-pattern": "5.0.5", 19 | "zod": "3.22.4" 20 | }, 21 | "devDependencies": { 22 | "@fluencelabs/aqua-api": "0.13.0", 23 | "@fluencelabs/aqua-lib": "0.7.3", 24 | "@fluencelabs/interfaces": "workspace:*", 25 | "@fluencelabs/js-client": "workspace:^", 26 | "@fluencelabs/registry": "0.9.0", 27 | "@fluencelabs/spell": "0.5.20", 28 | "@fluencelabs/trust-graph": "0.4.7", 29 | "vitest": "0.34.6" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export const CLIENT = "IFluenceClient$$"; 18 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/generate/__test__/__snapshots__/generate.snap.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | /** 4 | * 5 | * This file is generated using: 6 | * @fluencelabs/aqua-api version: 0.0.0 7 | * @fluencelabs/aqua-to-js version: 0.0.0 8 | * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues 9 | * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues 10 | * 11 | */ 12 | import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client'; 13 | 14 | // Making aliases to reduce chance of accidental name collision 15 | import { 16 | v5_callFunction as callFunction$$, 17 | v5_registerService as registerService$$ 18 | } from '@fluencelabs/js-client'; 19 | 20 | // Services 21 | export interface SrvDef { 22 | create: (wasm_b64_content: string, callParams: ParticleContext$$) => { error: string | null; service_id: string | null; success: boolean; } | Promise<{ error: string | null; service_id: string | null; success: boolean; }>; 23 | list: (callParams: ParticleContext$$) => string[] | Promise; 24 | remove: (service_id: string, callParams: ParticleContext$$) => { error: string | null; success: boolean; } | Promise<{ error: string | null; success: boolean; }>; 25 | } 26 | export function registerSrv(service: SrvDef): void; 27 | export function registerSrv(serviceId: string, service: SrvDef): void; 28 | export function registerSrv(peer: IFluenceClient$$, service: SrvDef): void; 29 | export function registerSrv(peer: IFluenceClient$$, serviceId: string, service: SrvDef): void; 30 | export interface CalcServiceDef { 31 | divide: (num: number, callParams: ParticleContext$$) => number | Promise; 32 | clear_state: (callParams: ParticleContext$$) => void | Promise; 33 | test_logs: (callParams: ParticleContext$$) => void | Promise; 34 | multiply: (num: number, callParams: ParticleContext$$) => number | Promise; 35 | add: (num: number, callParams: ParticleContext$$) => number | Promise; 36 | state: (callParams: ParticleContext$$) => number | Promise; 37 | subtract: (num: number, callParams: ParticleContext$$) => number | Promise; 38 | } 39 | export function registerCalcService(serviceId: string, service: CalcServiceDef): void; 40 | export function registerCalcService(peer: IFluenceClient$$, serviceId: string, service: CalcServiceDef): void; 41 | export interface HelloWorldDef { 42 | hello: (str: string, callParams: ParticleContext$$) => string | Promise; 43 | } 44 | export function registerHelloWorld(service: HelloWorldDef): void; 45 | export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void; 46 | export function registerHelloWorld(peer: IFluenceClient$$, service: HelloWorldDef): void; 47 | export function registerHelloWorld(peer: IFluenceClient$$, serviceId: string, service: HelloWorldDef): void; 48 | 49 | // Functions 50 | export type ResourceTestResultType = [string | null, string[]] 51 | 52 | export type ResourceTestParams = [label: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, label: string, config?: {ttl?: number}]; 53 | 54 | export type ResourceTestResult = Promise; 55 | 56 | export type HelloTestParams = [config?: {ttl?: number}] | [peer: IFluenceClient$$, config?: {ttl?: number}]; 57 | 58 | export type HelloTestResult = Promise; 59 | 60 | export type Demo_calculationParams = [service_id: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, service_id: string, config?: {ttl?: number}]; 61 | 62 | export type Demo_calculationResult = Promise; 63 | 64 | export type MarineTestParams = [wasm64: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, wasm64: string, config?: {ttl?: number}]; 65 | 66 | export type MarineTestResult = Promise; 67 | 68 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { fileURLToPath } from "url"; 18 | 19 | import { compileFromPath } from "@fluencelabs/aqua-api"; 20 | import { beforeAll, describe, expect, it } from "vitest"; 21 | 22 | import { getPackageJsonContent, PackageJson } from "../../utils.js"; 23 | import { generateTypes, generateSources } from "../index.js"; 24 | import { CompilationResult } from "../interfaces.js"; 25 | 26 | let res: Omit; 27 | let pkg: PackageJson; 28 | 29 | describe("Aqua to js/ts compiler", () => { 30 | beforeAll(async () => { 31 | res = await compileFromPath({ 32 | filePath: fileURLToPath( 33 | new URL("./sources/smoke_test.aqua", import.meta.url), 34 | ), 35 | imports: ["./node_modules"], 36 | targetType: "air", 37 | }); 38 | 39 | pkg = { 40 | ...(await getPackageJsonContent()), 41 | version: "0.0.0", 42 | devDependencies: { 43 | "@fluencelabs/aqua-api": "0.0.0", 44 | }, 45 | }; 46 | }); 47 | 48 | it("matches js snapshots", async () => { 49 | const jsResult = generateSources(res, "js", pkg); 50 | const jsTypes = generateTypes(res, pkg); 51 | 52 | await expect(jsResult).toMatchFileSnapshot( 53 | "./__snapshots__/generate.snap.js", 54 | ); 55 | 56 | await expect(jsTypes).toMatchFileSnapshot( 57 | "./__snapshots__/generate.snap.d.ts", 58 | ); 59 | }); 60 | 61 | it("matches ts snapshots", async () => { 62 | const tsResult = generateSources(res, "ts", pkg); 63 | 64 | await expect(tsResult).toMatchFileSnapshot( 65 | "./__snapshots__/generate.snap.ts", 66 | ); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/generate/__test__/sources/smoke_test.aqua: -------------------------------------------------------------------------------- 1 | import "@fluencelabs/registry/resources-api.aqua" 2 | 3 | service HelloWorld("hello-world"): 4 | hello(str: string) -> string 5 | 6 | func resourceTest(label: string) -> ?string, *string: 7 | res, errors <- createResource(label) 8 | <- res, errors 9 | 10 | func helloTest() -> string: 11 | hello <- HelloWorld.hello("Fluence user") 12 | <- hello 13 | 14 | service CalcService: 15 | add(num: f64) -> f64 16 | clear_state() 17 | divide(num: f64) -> f64 18 | multiply(num: f64) -> f64 19 | state() -> f64 20 | subtract(num: f64) -> f64 21 | test_logs() 22 | 23 | data ServiceCreationResult: 24 | success: bool 25 | service_id: ?string 26 | error: ?string 27 | 28 | data RemoveResult: 29 | success: bool 30 | error: ?string 31 | 32 | alias ListServiceResult: []string 33 | 34 | service Srv("single_module_srv"): 35 | create(wasm_b64_content: string) -> ServiceCreationResult 36 | remove(service_id: string) -> RemoveResult 37 | list() -> ListServiceResult 38 | 39 | 40 | func demo_calculation(service_id: string) -> f64: 41 | CalcService service_id 42 | CalcService.test_logs() 43 | CalcService.add(10) 44 | CalcService.multiply(5) 45 | CalcService.subtract(8) 46 | CalcService.divide(6) 47 | res <- CalcService.state() 48 | <- res 49 | 50 | func marineTest(wasm64: string) -> f64: 51 | serviceResult <- Srv.create(wasm64) 52 | res <- demo_calculation(serviceResult.service_id!) 53 | <- res 54 | 55 | 56 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/generate/function.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { capitalize, recursiveRenameLaquaProps } from "../utils.js"; 18 | 19 | import { AquaFunction, TypeGenerator } from "./interfaces.js"; 20 | 21 | export function generateFunctions( 22 | typeGenerator: TypeGenerator, 23 | functions: Record, 24 | ) { 25 | return Object.values(functions) 26 | .map((func) => { 27 | return generateFunction(typeGenerator, func); 28 | }) 29 | .join("\n\n"); 30 | } 31 | 32 | type DeepToType = { [K in keyof T]: DeepToType }; 33 | 34 | function generateFunction(typeGenerator: TypeGenerator, func: AquaFunction) { 35 | const funcDef: DeepToType = func.funcDef; 36 | const scriptConstName = func.funcDef.functionName + "_script"; 37 | return `export const ${scriptConstName} = \` 38 | ${func.script}\`; 39 | 40 | ${typeGenerator.funcType(func)} 41 | export function ${func.funcDef.functionName}(${typeGenerator.type( 42 | "...args", 43 | `${capitalize(func.funcDef.functionName)}Params`, 44 | )})${typeGenerator.type( 45 | "", 46 | `${capitalize(func.funcDef.functionName)}Result`, 47 | )} { 48 | return callFunction$$( 49 | args, 50 | ${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)}, 51 | ${scriptConstName} 52 | ); 53 | }`; 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/generate/header.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { PackageJson } from "../utils.js"; 18 | 19 | import { OutputType } from "./interfaces.js"; 20 | 21 | export default function generateHeader( 22 | { version, devDependencies }: PackageJson, 23 | outputType: OutputType, 24 | ) { 25 | return `/* eslint-disable */ 26 | // @ts-nocheck 27 | /** 28 | * 29 | * This file is generated using: 30 | * @fluencelabs/aqua-api version: ${devDependencies["@fluencelabs/aqua-api"]} 31 | * @fluencelabs/aqua-to-js version: ${version} 32 | * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues 33 | * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues 34 | * 35 | */ 36 | ${ 37 | outputType === "ts" 38 | ? "import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';" 39 | : "" 40 | } 41 | 42 | // Making aliases to reduce chance of accidental name collision 43 | import { 44 | v5_callFunction as callFunction$$, 45 | v5_registerService as registerService$$ 46 | } from '@fluencelabs/js-client';`; 47 | } 48 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/generate/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { PackageJson } from "../utils.js"; 18 | 19 | import { generateFunctions } from "./function.js"; 20 | import header from "./header.js"; 21 | import { 22 | CompilationResult, 23 | JSTypeGenerator, 24 | OutputType, 25 | TSTypeGenerator, 26 | TypeGenerator, 27 | } from "./interfaces.js"; 28 | import { generateServices } from "./service.js"; 29 | 30 | const typeGenerators: Record = { 31 | js: new JSTypeGenerator(), 32 | ts: new TSTypeGenerator(), 33 | }; 34 | 35 | export function generateSources( 36 | { services, functions }: CompilationResult, 37 | outputType: OutputType, 38 | packageJson: PackageJson, 39 | ) { 40 | const typeGenerator = typeGenerators[outputType]; 41 | return `${header(packageJson, outputType)} 42 | 43 | ${ 44 | Object.entries(services).length > 0 45 | ? `// Services 46 | ${generateServices(typeGenerator, services)} 47 | ` 48 | : "" 49 | } 50 | ${ 51 | Object.entries(functions).length > 0 52 | ? `// Functions 53 | ${generateFunctions(typeGenerator, functions)} 54 | ` 55 | : "" 56 | }`; 57 | } 58 | 59 | export function generateTypes( 60 | { services, functions }: CompilationResult, 61 | packageJson: PackageJson, 62 | ) { 63 | const typeGenerator = typeGenerators["ts"]; 64 | 65 | const generatedServices = Object.entries(services) 66 | .map(([srvName, srvDef]) => { 67 | return typeGenerator.serviceType(srvName, srvDef); 68 | }) 69 | .join("\n"); 70 | 71 | const generatedFunctions = Object.entries(functions) 72 | .map(([, funcDef]) => { 73 | return typeGenerator.funcType(funcDef); 74 | }) 75 | .join("\n"); 76 | 77 | return `${header(packageJson, "ts")} 78 | 79 | ${ 80 | Object.entries(services).length > 0 81 | ? `// Services 82 | ${generatedServices} 83 | ` 84 | : "" 85 | } 86 | ${ 87 | Object.entries(functions).length > 0 88 | ? `// Functions 89 | ${generatedFunctions} 90 | ` 91 | : "" 92 | }`; 93 | } 94 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/generate/service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { ServiceDef } from "@fluencelabs/interfaces"; 18 | 19 | import { recursiveRenameLaquaProps } from "../utils.js"; 20 | 21 | import { TypeGenerator } from "./interfaces.js"; 22 | 23 | export function generateServices( 24 | typeGenerator: TypeGenerator, 25 | services: Record, 26 | ) { 27 | const generated = Object.entries(services) 28 | .map(([srvName, srvDef]) => { 29 | return generateService(typeGenerator, srvName, srvDef); 30 | }) 31 | .join("\n\n"); 32 | 33 | return generated + "\n"; 34 | } 35 | 36 | function generateService( 37 | typeGenerator: TypeGenerator, 38 | srvName: string, 39 | srvDef: ServiceDef, 40 | ) { 41 | return [ 42 | typeGenerator.serviceType(srvName, srvDef), 43 | generateRegisterServiceOverload(typeGenerator, srvName, srvDef), 44 | ].join("\n"); 45 | } 46 | 47 | function generateRegisterServiceOverload( 48 | typeGenerator: TypeGenerator, 49 | srvName: string, 50 | srvDef: ServiceDef, 51 | ) { 52 | return [ 53 | `export function register${srvName}(${typeGenerator.type( 54 | "...args", 55 | "any[]", 56 | )}) {`, 57 | " registerService$$(", 58 | " args,", 59 | ` ${serviceToJson(srvDef)}`, 60 | " );", 61 | "}", 62 | ].join("\n"); 63 | } 64 | 65 | function serviceToJson(service: ServiceDef): string { 66 | const record: Record = service; 67 | return JSON.stringify(recursiveRenameLaquaProps(record), null, 4); 68 | } 69 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { generateSources, generateTypes } from "./generate/index.js"; 18 | import { CompilationResult } from "./generate/interfaces.js"; 19 | import { getPackageJsonContent } from "./utils.js"; 20 | 21 | interface JsOutput { 22 | sources: string; 23 | types: string; 24 | } 25 | 26 | interface TsOutput { 27 | sources: string; 28 | } 29 | 30 | type NothingToGenerate = null; 31 | 32 | export default async function aquaToJs( 33 | res: CompilationResult, 34 | outputType: "js", 35 | ): Promise; 36 | 37 | export default async function aquaToJs( 38 | res: CompilationResult, 39 | outputType: "ts", 40 | ): Promise; 41 | 42 | export default async function aquaToJs( 43 | res: CompilationResult, 44 | outputType: "js" | "ts", 45 | ): Promise { 46 | if ( 47 | Object.keys(res.services).length === 0 && 48 | Object.keys(res.functions).length === 0 49 | ) { 50 | return null; 51 | } 52 | 53 | const packageJson = await getPackageJsonContent(); 54 | 55 | if (outputType === "js") { 56 | return { 57 | sources: generateSources(res, "js", packageJson), 58 | types: generateTypes(res, packageJson), 59 | }; 60 | } 61 | 62 | return { 63 | sources: generateSources(res, "ts", packageJson), 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { readFile } from "fs/promises"; 18 | import { join } from "path"; 19 | 20 | import { 21 | ArrowType, 22 | ArrowWithoutCallbacks, 23 | LabeledProductType, 24 | NilType, 25 | SimpleTypes, 26 | UnlabeledProductType, 27 | } from "@fluencelabs/interfaces"; 28 | import { z } from "zod"; 29 | 30 | const packageJsonSchema = z.object({ 31 | name: z.string(), 32 | version: z.string(), 33 | devDependencies: z.object({ 34 | // @fluencelabs/aqua-api version is included as part of the comment at the top of each js and ts file 35 | ["@fluencelabs/aqua-api"]: z.string(), 36 | }), 37 | }); 38 | 39 | export type PackageJson = z.infer; 40 | 41 | export async function getPackageJsonContent(): Promise { 42 | const content = await readFile( 43 | new URL(join("..", "package.json"), import.meta.url), 44 | "utf-8", 45 | ); 46 | 47 | return packageJsonSchema.parse(JSON.parse(content)); 48 | } 49 | 50 | export function getFuncArgs( 51 | domain: 52 | | LabeledProductType> 53 | | UnlabeledProductType 54 | | NilType, 55 | ): [string, SimpleTypes | ArrowWithoutCallbacks][] { 56 | if (domain.tag === "labeledProduct") { 57 | return Object.entries(domain.fields).map(([label, type]) => { 58 | return [label, type]; 59 | }); 60 | } else if (domain.tag === "unlabeledProduct") { 61 | return domain.items.map((type, index) => { 62 | return ["arg" + index, type]; 63 | }); 64 | } else { 65 | return []; 66 | } 67 | } 68 | 69 | export function recursiveRenameLaquaProps(obj: unknown): unknown { 70 | if (typeof obj !== "object" || obj === null) { 71 | return obj; 72 | } 73 | 74 | if (Array.isArray(obj)) { 75 | return obj.map((item) => { 76 | return recursiveRenameLaquaProps(item); 77 | }); 78 | } 79 | 80 | const objType: Record = obj; 81 | 82 | return Object.getOwnPropertyNames(objType).reduce((acc, prop) => { 83 | let accessProp = prop; 84 | 85 | if (prop.includes("Laqua_js")) { 86 | // Last part of the property separated by "_" is a correct name 87 | const refinedProperty = prop.split("_").pop(); 88 | 89 | if (refinedProperty === undefined) { 90 | throw new Error(`Bad property name: ${prop}.`); 91 | } 92 | 93 | if (refinedProperty in objType) { 94 | accessProp = refinedProperty; 95 | } 96 | } 97 | 98 | const accessObj: Record = objType; 99 | 100 | const laquaProp = accessObj[accessProp]; 101 | 102 | if (laquaProp === undefined) { 103 | return acc; 104 | } 105 | 106 | return { 107 | ...acc, 108 | [accessProp]: recursiveRenameLaquaProps(laquaProp), 109 | }; 110 | }, {}); 111 | } 112 | 113 | export function capitalize(str: string) { 114 | return str.slice(0, 1).toUpperCase() + str.slice(1); 115 | } 116 | -------------------------------------------------------------------------------- /packages/core/aqua-to-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules", "dist", "src/**/__test__"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/interfaces/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fluencelabs/interfaces", 3 | "type": "module", 4 | "version": "0.12.0", 5 | "description": "Interfaces", 6 | "main": "./dist/index.js", 7 | "typings": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.js", 11 | "types": "./dist/index.d.ts" 12 | }, 13 | "./fluenceClient": { 14 | "import": "./dist/fluenceClient.js", 15 | "types": "./dist/fluenceClient.d.ts" 16 | }, 17 | "./compilerSupport": { 18 | "import": "./dist/compilerSupport.js", 19 | "types": "./dist/compilerSupport.d.ts" 20 | }, 21 | "./dist/fluenceClient": { 22 | "import": "./dist/fluenceClient.js", 23 | "types": "./dist/fluenceClient.d.ts" 24 | }, 25 | "./dist/compilerSupport": { 26 | "import": "./dist/compilerSupport.js", 27 | "types": "./dist/compilerSupport.d.ts" 28 | } 29 | }, 30 | "typesVersions": { 31 | "*": { 32 | "fluenceClient.d.ts": [ 33 | "./dist/fluenceClient.d.ts" 34 | ], 35 | "compilerSupport.d.ts": [ 36 | "./dist/compilerSupport.d.ts" 37 | ] 38 | } 39 | }, 40 | "engines": { 41 | "node": ">=10", 42 | "pnpm": ">=3" 43 | }, 44 | "scripts": { 45 | "build": "tsc" 46 | }, 47 | "repository": "https://github.com/fluencelabs/fluence-js", 48 | "author": "Fluence DAO", 49 | "license": "Apache-2.0", 50 | "dependencies": {}, 51 | "devDependencies": { 52 | "hotscript": "1.0.13" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/interfaces/src/future.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Call, Pipe, Objects, Tuples, Unions, Fn } from "hotscript"; 18 | 19 | import { 20 | ArrayType, 21 | ArrowType, 22 | LabeledProductType, 23 | NilType, 24 | OptionType, 25 | ScalarType, 26 | SimpleTypes, 27 | StructType, 28 | TopType, 29 | UnlabeledProductType, 30 | } from "./compilerSupport/aquaTypeDefinitions.js"; 31 | 32 | // Type definitions for inferring ts types from air json definition 33 | // In the future we may remove string type declaration and move to type inference. 34 | 35 | type GetTsTypeFromScalar = [T["name"]] extends [ 36 | "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64", 37 | ] 38 | ? number 39 | : [T["name"]] extends ["bool"] 40 | ? boolean 41 | : [T["name"]] extends ["string"] 42 | ? string 43 | : never; 44 | 45 | type MapTuple = { 46 | [K in keyof T]: [T[K]] extends [SimpleTypes] ? GetSimpleType : never; 47 | }; 48 | 49 | type UnpackIfSingle = [T] extends [[infer R]] ? R : T; 50 | 51 | type GetSimpleType = [T] extends [NilType] 52 | ? null 53 | : [T] extends [ArrayType] 54 | ? GetSimpleType[] 55 | : [T] extends [StructType] 56 | ? { [K in keyof T["fields"]]: GetSimpleType } 57 | : [T] extends [OptionType] 58 | ? GetSimpleType | null 59 | : [T] extends [ScalarType] 60 | ? GetTsTypeFromScalar 61 | : [T] extends [TopType] 62 | ? unknown 63 | : never; 64 | 65 | interface Access extends Fn { 66 | return: __GetTsType, T>>; 67 | } 68 | 69 | type __GetTsType = [T] extends [SimpleTypes] 70 | ? GetSimpleType 71 | : [T] extends [UnlabeledProductType] 72 | ? MapTuple 73 | : [T] extends [LabeledProductType] 74 | ? { [K in keyof T["fields"]]: __GetTsType } 75 | : [T] extends [ArrowType] 76 | ? ( 77 | ...t: [H] extends [UnlabeledProductType] 78 | ? MapTuple 79 | : [H] extends [LabeledProductType] 80 | ? Pipe>]> 81 | : [] 82 | ) => [T["codomain"]] extends [UnlabeledProductType] 83 | ? UnpackIfSingle> 84 | : undefined 85 | : never; 86 | 87 | type DeepMutable = { 88 | -readonly [K in keyof T]: DeepMutable; 89 | }; 90 | 91 | export type GetTsType = __GetTsType>; 92 | -------------------------------------------------------------------------------- /packages/core/interfaces/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from "./compilerSupport/aquaTypeDefinitions.js"; 18 | export * from "./future.js"; 19 | -------------------------------------------------------------------------------- /packages/core/interfaces/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/.gitignore: -------------------------------------------------------------------------------- 1 | src/versions.ts -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/createVersionFile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Fluence DAO Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // @ts-check 18 | 19 | import pkg from "./package.json" assert { type: "json" }; 20 | import { writeFile } from "fs/promises"; 21 | import { join, dirname } from "path"; 22 | import { fileURLToPath } from "url"; 23 | 24 | const names = [ 25 | "@fluencelabs/avm", 26 | "@fluencelabs/marine-js", 27 | "@fluencelabs/marine-worker", 28 | ]; 29 | 30 | const entries = Object.entries({ 31 | ...pkg.dependencies, 32 | ...pkg.devDependencies, 33 | }).filter(([name]) => names.includes(name)); 34 | 35 | const output = Object.fromEntries(entries); 36 | 37 | await writeFile( 38 | join(dirname(fileURLToPath(import.meta.url)), "src", "versions.ts"), 39 | `/* eslint-disable */ 40 | export default ${JSON.stringify(output, null, 2)} as const`, 41 | ); 42 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "@fluencelabs/js-client-isomorphic", 4 | "version": "0.6.0", 5 | "description": "Isomorphic entities for js-client", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "build": "tsc", 11 | "prepare": "node createVersionFile.js" 12 | }, 13 | "exports": { 14 | ".": "./dist/types.js", 15 | "./fetcher": { 16 | "node": "./dist/fetchers/node.js", 17 | "default": "./dist/fetchers/browser.js" 18 | }, 19 | "./worker-resolver": { 20 | "node": "./dist/worker-resolvers/node.js", 21 | "default": "./dist/worker-resolvers/browser.js" 22 | } 23 | }, 24 | "dependencies": { 25 | "@fluencelabs/avm": "0.62.0", 26 | "@fluencelabs/marine-js": "0.13.0", 27 | "@fluencelabs/marine-worker": "0.6.0", 28 | "@fluencelabs/threads": "^2.0.0" 29 | }, 30 | "keywords": [], 31 | "author": "Fluence DAO", 32 | "license": "Apache-2.0" 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/src/fetchers/browser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { FetchResourceFn, getVersionedPackage } from "../types.js"; 18 | 19 | /** 20 | * @param pkg name of package with version 21 | * @param assetPath path of required asset in given package 22 | * @param root CDN domain in browser or file system root in node 23 | */ 24 | export const fetchResource: FetchResourceFn = async (pkg, assetPath, root) => { 25 | const refinedAssetPath = assetPath.startsWith("/") 26 | ? assetPath.slice(1) 27 | : assetPath; 28 | 29 | const { name, version } = getVersionedPackage(pkg); 30 | const url = new URL(`${name}@${version}/` + refinedAssetPath, root); 31 | 32 | return fetch(url).catch(() => { 33 | throw new Error(`Cannot fetch from ${url.toString()}`); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/src/fetchers/node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { readFile } from "fs/promises"; 18 | import { createRequire } from "module"; 19 | import { sep, posix, join } from "path"; 20 | 21 | import { FetchResourceFn, getVersionedPackage } from "../types.js"; 22 | 23 | /** 24 | * @param pkg name of package with version 25 | * @param assetPath path of required asset in given package 26 | */ 27 | export const fetchResource: FetchResourceFn = async (pkg, assetPath) => { 28 | const { name } = getVersionedPackage(pkg); 29 | const require = createRequire(import.meta.url); 30 | const packagePathIndex = require.resolve(name); 31 | 32 | // Ensure that windows path is converted to posix path. So we can find a package 33 | const posixPath = packagePathIndex.split(sep).join(posix.sep); 34 | 35 | const matches = new RegExp(`(.+${name})`).exec(posixPath); 36 | 37 | const packagePath = matches?.[0]; 38 | 39 | if (packagePath === undefined) { 40 | throw new Error(`Cannot find dependency ${name} in path ${posixPath}`); 41 | } 42 | 43 | const pathToResource = join(packagePath, assetPath); 44 | 45 | const file = await readFile(pathToResource); 46 | 47 | return new Response(file, { 48 | headers: { 49 | "Content-type": assetPath.endsWith(".wasm") 50 | ? "application/wasm" 51 | : assetPath.endsWith(".js") 52 | ? "application/javascript" 53 | : "application/text", 54 | }, 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker"; 18 | import { ModuleThread } from "@fluencelabs/threads/master"; 19 | 20 | import versions from "./versions.js"; 21 | 22 | export type FetchedPackages = keyof typeof versions; 23 | type VersionedPackage = { name: string; version: string }; 24 | export type GetWorkerFn = ( 25 | pkg: FetchedPackages, 26 | CDNUrl: string, 27 | ) => Promise>; 28 | 29 | export const getVersionedPackage = (pkg: FetchedPackages): VersionedPackage => { 30 | return { 31 | name: pkg, 32 | version: versions[pkg], 33 | }; 34 | }; 35 | 36 | export type FetchResourceFn = ( 37 | pkg: FetchedPackages, 38 | assetPath: string, 39 | root: string, 40 | ) => Promise; 41 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/src/worker-resolvers/browser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker"; 18 | import { BlobWorker, ModuleThread, spawn } from "@fluencelabs/threads/master"; 19 | 20 | import { fetchResource } from "../fetchers/browser.js"; 21 | import type { FetchedPackages, GetWorkerFn } from "../types.js"; 22 | 23 | export const getWorker: GetWorkerFn = async ( 24 | pkg: FetchedPackages, 25 | CDNUrl: string, 26 | ) => { 27 | const fetchWorkerCode = async () => { 28 | const resource = await fetchResource( 29 | pkg, 30 | "/dist/browser/marine-worker.umd.cjs", 31 | CDNUrl, 32 | ); 33 | 34 | return resource.text(); 35 | }; 36 | 37 | const workerCode = await fetchWorkerCode(); 38 | 39 | const workerThread: ModuleThread = 40 | await spawn(BlobWorker.fromText(workerCode)); 41 | 42 | return workerThread; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/src/worker-resolvers/node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { createRequire } from "module"; 18 | import { dirname, relative } from "path"; 19 | import { fileURLToPath } from "url"; 20 | 21 | import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker"; 22 | import { ModuleThread, spawn, Worker } from "@fluencelabs/threads/master"; 23 | 24 | import type { FetchedPackages, GetWorkerFn } from "../types.js"; 25 | import { getVersionedPackage } from "../types.js"; 26 | 27 | export const getWorker: GetWorkerFn = async (pkg: FetchedPackages) => { 28 | const require = createRequire(import.meta.url); 29 | 30 | const pathToThisFile = dirname(fileURLToPath(import.meta.url)); 31 | 32 | const { name } = getVersionedPackage(pkg); 33 | const pathToWorker = require.resolve(name); 34 | 35 | const relativePathToWorker = relative(pathToThisFile, pathToWorker); 36 | 37 | const workerThread: ModuleThread = 38 | await spawn(new Worker(relativePathToWorker)); 39 | 40 | return workerThread; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/core/js-client-isomorphic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"], 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/js-client/.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gitignore 3 | node_modules 4 | types 5 | 6 | src/ 7 | 8 | tsconfig.json 9 | webpack.config.js 10 | 11 | bundle 12 | pkg -------------------------------------------------------------------------------- /packages/core/js-client/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribute Code 2 | 3 | You are welcome to contribute to Fluence. 4 | 5 | Things you need to know: 6 | 7 | 1. You need to **agree to the Contributors License Agreement**. This is a common practice in all major Open Source projects. At the current moment we are unable to accept contributions made on behalf of a company. Only individual contributions will be accepted. 8 | 2. **Not all proposed contributions can be accepted**. Some features may e.g. just fit a third-party add-on better. The contribution must fit the overall direction of Fluence and really improve it. The more effort you invest, the better you should clarify in advance whether the contribution fits: the best way would be to just open an issue to discuss the contribution you plan to make. 9 | 10 | ### Contributor License Agreement 11 | 12 | When you contribute, you have to be aware that your contribution is covered by **Apache License 2.0**, but might relicensed under few other software licenses mentioned in the **Contributor License Agreement**. 13 | In particular you need to agree to the [Contributor License Agreement](https://gist.github.com/fluencelabs-org/3f4cbb3cc14c1c0fb9ad99d8f7316ed7). If you agree to its content, you simply have to click on the link posted by the CLA assistant as a comment to the pull request. Click it to check the CLA, then accept it on the following screen if you agree to it. CLA assistant will save this decision for upcoming contributions and will notify you if there is any change to the CLA in the meantime. 14 | -------------------------------------------------------------------------------- /packages/core/js-client/aqua/calc.aqua: -------------------------------------------------------------------------------- 1 | service Calc("calc"): 2 | add(n: f32) 3 | subtract(n: f32) 4 | multiply(n: f32) 5 | divide(n: f32) 6 | reset() 7 | getResult() -> f32 8 | 9 | 10 | func demoCalc() -> f32: 11 | Calc.add(10) 12 | Calc.multiply(5) 13 | Calc.subtract(8) 14 | Calc.divide(6) 15 | res <- Calc.getResult() 16 | <- res -------------------------------------------------------------------------------- /packages/core/js-client/aqua/node-utils.aqua: -------------------------------------------------------------------------------- 1 | data ReadFileResult: 2 | -- Was the call successful or not 3 | success: bool 4 | -- File content in base64 if the call was successful 5 | content: ?string 6 | -- Error message if the call was unsuccessful 7 | error: ?string 8 | 9 | service NodeUtils("node_utils"): 10 | -- Read file from file system. 11 | -- returns file content in base64 format 12 | read_file(path: string) -> ReadFileResult 13 | -------------------------------------------------------------------------------- /packages/core/js-client/aqua/services.aqua: -------------------------------------------------------------------------------- 1 | -- import SignResult, Sig from "@fluencelabs/aqua-lib/builtin.aqua" 2 | -- export SignResult, Sig 3 | 4 | -- TODO:: fix this issue: https://github.com/fluencelabs/aqua-lib/issues/12 5 | -- and remove copy-paste 6 | 7 | data SignResult: 8 | -- Was call successful or not 9 | success: bool 10 | -- Error message. Will be null if the call is successful 11 | error: ?string 12 | -- Signature as byte array. Will be null if the call is not successful 13 | signature: ?[]u8 14 | 15 | -- Available only on FluenceJS peers 16 | -- The service can also be resolved by it's host peer id 17 | service Sig("sig"): 18 | -- Signs data with the service's private key. 19 | -- Depending on implementation the service might check call params to restrict usage for security reasons. 20 | -- By default it is only allowed to be used on the same peer the particle was initiated 21 | -- and accepts data only from the following sources: 22 | -- trust-graph.get_trust_bytes 23 | -- trust-graph.get_revocation_bytes 24 | -- registry.get_route_bytes 25 | -- registry.get_record_bytes 26 | -- registry.get_host_record_bytes 27 | -- Argument: data - byte array to sign 28 | -- Returns: signature as SignResult structure 29 | sign(data: []u8) -> SignResult 30 | 31 | -- Given the data and signature both as byte arrays, returns true if the signature is correct, false otherwise. 32 | verify(signature: []u8, data: []u8) -> bool 33 | 34 | -- Gets service's public key. 35 | get_peer_id() -> string 36 | -------------------------------------------------------------------------------- /packages/core/js-client/aqua/single-module-srv.aqua: -------------------------------------------------------------------------------- 1 | alias Bytes : []u8 2 | 3 | data ServiceCreationResult: 4 | success: bool 5 | service_id: ?string 6 | error: ?string 7 | 8 | data ReadFileResult: 9 | success: bool 10 | content: ?string 11 | error: ?string 12 | 13 | data RemoveResult: 14 | success: bool 15 | error: ?string 16 | 17 | alias ListServiceResult: []string 18 | 19 | service Srv("single_module_srv"): 20 | -- Used to create a service on a certain node 21 | -- Arguments: 22 | -- bytes – a base64 string containing the .wasm module to add. 23 | -- Returns: service_id – the service ID of the created service. 24 | create(wasm_b64_content: string) -> ServiceCreationResult 25 | 26 | -- Used to remove a service from a certain node 27 | -- Arguments: 28 | -- service_id – ID of the service to remove 29 | remove(service_id: string) -> RemoveResult 30 | 31 | -- Returns a list of services ids running on a peer 32 | list() -> ListServiceResult 33 | -------------------------------------------------------------------------------- /packages/core/js-client/aqua/tracing.aqua: -------------------------------------------------------------------------------- 1 | service Tracing("tracingSrv"): 2 | tracingEvent(arrowName: string, event: string) 3 | -------------------------------------------------------------------------------- /packages/core/js-client/aqua_test/marine-js.aqua: -------------------------------------------------------------------------------- 1 | data GreetingRecordData: 2 | str: string 3 | num: i32 4 | 5 | service Greeting("greeting"): 6 | greeting(name: string) -> string 7 | greeting_record() -> GreetingRecordData 8 | 9 | func call(arg: string) -> string: 10 | res1 <- Greeting.greeting(arg) 11 | res2 <- Greeting.greeting(res1) 12 | res3 <- Greeting.greeting(res2) 13 | <- res3 14 | 15 | service GreetingRecord: 16 | greeting_record() -> GreetingRecordData 17 | log_debug() 18 | log_error() 19 | log_info() 20 | log_trace() 21 | log_warn() 22 | void_fn() 23 | 24 | func call_info(srvId: string): 25 | GreetingRecord srvId 26 | GreetingRecord.log_info() 27 | -------------------------------------------------------------------------------- /packages/core/js-client/aqua_test/sigService.aqua: -------------------------------------------------------------------------------- 1 | module Export 2 | 3 | import SignResult, Sig from "../aqua/services.aqua" 4 | export Sig, DataProvider, callSig 5 | 6 | service DataProvider("data"): 7 | provide_data() -> []u8 8 | 9 | func callSig(sigId: string) -> SignResult: 10 | data <- DataProvider.provide_data() 11 | Sig sigId 12 | signature <- Sig.sign(data) 13 | <- signature 14 | -------------------------------------------------------------------------------- /packages/core/js-client/aqua_test/srv.aqua: -------------------------------------------------------------------------------- 1 | module Export 2 | 3 | import Srv from "../aqua/single-module-srv.aqua" 4 | import NodeUtils from "../aqua/node-utils.aqua" 5 | export happy_path, list_services, file_not_found, service_removed, removing_non_exiting 6 | 7 | service Greeting("greeting"): 8 | greeting(name: string) -> string 9 | 10 | func happy_path(file_path: string) -> string: 11 | file <- NodeUtils.read_file(file_path) 12 | created_service <- Srv.create(file.content!) 13 | Greeting created_service.service_id! 14 | <- Greeting.greeting("test") 15 | 16 | func list_services(file_path: string) -> []string: 17 | file <- NodeUtils.read_file(file_path) 18 | Srv.create(file.content!) 19 | Srv.create(file.content!) 20 | Srv.create(file.content!) 21 | <- Srv.list() 22 | 23 | func file_not_found() -> string: 24 | e <- NodeUtils.read_file("/random/incorrect/file") 25 | <- e.error! 26 | 27 | func service_removed(file_path: string) -> string: 28 | result: *string 29 | 30 | file <- NodeUtils.read_file(file_path) 31 | created_service <- Srv.create(file.content!) 32 | Greeting created_service.service_id! 33 | Srv.remove(created_service.service_id!) 34 | try: 35 | dontcare <- Greeting.greeting("test") 36 | result <<- "ok" 37 | catch e: 38 | result <<- e.message 39 | <- result! 40 | 41 | func removing_non_exiting() -> string: 42 | e <- Srv.remove("random_id") 43 | <- e.error! 44 | 45 | -------------------------------------------------------------------------------- /packages/core/js-client/data_for_test/greeting-record.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/js-client/data_for_test/greeting-record.wasm -------------------------------------------------------------------------------- /packages/core/js-client/data_for_test/greeting.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/js-client/data_for_test/greeting.wasm -------------------------------------------------------------------------------- /packages/core/js-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fluencelabs/js-client", 3 | "version": "0.9.0", 4 | "description": "Client for interacting with Fluence network", 5 | "engines": { 6 | "node": ">=10", 7 | "pnpm": ">=8" 8 | }, 9 | "files": [ 10 | "dist" 11 | ], 12 | "main": "./dist/browser/index.min.js", 13 | "types": "./dist/index.d.ts", 14 | "exports": { 15 | ".": { 16 | "types": "./dist/index.d.ts", 17 | "import": "./dist/index.js" 18 | }, 19 | "./keypair": { 20 | "types": "./dist/keypair/index.d.ts", 21 | "import": "./dist/keypair/index.js" 22 | } 23 | }, 24 | "type": "module", 25 | "scripts": { 26 | "build": "tsc && vite build", 27 | "test": "vitest --threads false run" 28 | }, 29 | "repository": "https://github.com/fluencelabs/fluence-js", 30 | "author": "Fluence DAO", 31 | "license": "Apache-2.0", 32 | "dependencies": { 33 | "@chainsafe/libp2p-noise": "14.0.0", 34 | "@chainsafe/libp2p-yamux": "6.0.1", 35 | "@fluencelabs/avm": "0.62.0", 36 | "@fluencelabs/interfaces": "workspace:*", 37 | "@fluencelabs/js-client-isomorphic": "workspace:*", 38 | "@fluencelabs/fluence-network-environment": "1.2.2", 39 | "@fluencelabs/marine-worker": "0.6.0", 40 | "@fluencelabs/threads": "^2.0.0", 41 | "@libp2p/crypto": "4.0.1", 42 | "@libp2p/identify": "1.0.11", 43 | "@libp2p/interface": "1.1.2", 44 | "@libp2p/peer-id": "4.0.5", 45 | "@libp2p/peer-id-factory": "4.0.5", 46 | "@libp2p/ping": "1.0.10", 47 | "@libp2p/utils": "5.2.2", 48 | "@libp2p/websockets": "8.0.12", 49 | "@multiformats/multiaddr": "12.1.12", 50 | "bs58": "5.0.0", 51 | "debug": "4.3.4", 52 | "int64-buffer": "1.0.1", 53 | "it-length-prefixed": "9.0.3", 54 | "it-map": "3.0.5", 55 | "it-pipe": "3.0.1", 56 | "js-base64": "3.7.5", 57 | "libp2p": "1.2.0", 58 | "multiformats": "11.0.1", 59 | "rxjs": "7.5.5", 60 | "uint8arrays": "4.0.3", 61 | "uuid": "8.3.2", 62 | "zod": "3.22.4" 63 | }, 64 | "devDependencies": { 65 | "@fluencelabs/aqua-api": "0.13.0", 66 | "@rollup/plugin-inject": "5.0.5", 67 | "@types/bs58": "4.0.1", 68 | "@types/debug": "4.1.7", 69 | "@types/node": "20.7.0", 70 | "@types/uuid": "8.3.2", 71 | "esbuild": "0.19.5", 72 | "hotscript": "1.0.13", 73 | "vite": "4.4.11", 74 | "vite-tsconfig-paths": "4.0.3", 75 | "vitest": "0.34.6" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/core/js-client/src/api.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { fileURLToPath } from "url"; 18 | 19 | import { compileFromPath } from "@fluencelabs/aqua-api"; 20 | import { ServiceDef } from "@fluencelabs/interfaces"; 21 | import { assert, describe, expect, it } from "vitest"; 22 | 23 | import { v5_registerService } from "./api.js"; 24 | import { callAquaFunction } from "./compilerSupport/callFunction.js"; 25 | import { withPeer } from "./util/testUtils.js"; 26 | 27 | class CalcParent { 28 | protected _state: number = 0; 29 | 30 | add(n: number) { 31 | this._state += n; 32 | } 33 | 34 | subtract(n: number) { 35 | this._state -= n; 36 | } 37 | } 38 | 39 | class Calc extends CalcParent { 40 | multiply(n: number) { 41 | this._state *= n; 42 | } 43 | 44 | divide(n: number) { 45 | this._state /= n; 46 | } 47 | 48 | reset() { 49 | this._state = 0; 50 | } 51 | 52 | getResult() { 53 | return this._state; 54 | } 55 | } 56 | 57 | describe("User API methods", () => { 58 | it("registers user class service and calls own and inherited methods correctly", async () => { 59 | await withPeer(async (peer) => { 60 | const calcService: Record = new Calc(); 61 | 62 | const { functions, services } = await compileFromPath({ 63 | filePath: fileURLToPath(new URL("../aqua/calc.aqua", import.meta.url)), 64 | }); 65 | 66 | const typedServices: Record = services; 67 | 68 | assert("demoCalc" in functions); 69 | 70 | const { script } = functions["demoCalc"]; 71 | 72 | assert("Calc" in typedServices); 73 | 74 | v5_registerService([peer, "calc", calcService], { 75 | defaultServiceId: "calc", 76 | functions: typedServices["Calc"].functions, 77 | }); 78 | 79 | const res = await callAquaFunction({ 80 | args: {}, 81 | peer, 82 | script, 83 | fireAndForget: false, 84 | }); 85 | 86 | expect(res).toBe(7); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /packages/core/js-client/src/clientPeer/__test__/connection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export const nodes = [ 18 | { 19 | multiaddr: 20 | "/ip4/127.0.0.1/tcp/999/ws/p2p/12D3KooWBbMuqJJZT7FTFN4fWg3k3ipUKx6KEy7pDy8mdorK5g5o", 21 | peerId: "12D3KooWBbMuqJJZT7FTFN4fWg3k3ipUKx6KEy7pDy8mdorK5g5o", 22 | }, 23 | ] as const; 24 | 25 | export const RELAY = nodes[0].multiaddr; 26 | -------------------------------------------------------------------------------- /packages/core/js-client/src/clientPeer/checkConnection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.js"; 18 | import { handleTimeout } from "../particle/Particle.js"; 19 | import { logger } from "../util/logger.js"; 20 | import type { JSONValue } from "../util/types.js"; 21 | 22 | import { ClientPeer } from "./ClientPeer.js"; 23 | 24 | const log = logger("connection"); 25 | 26 | /** 27 | * Checks the network connection by sending a ping-like request to relay node 28 | * @param { ClientPeer } peer - The Fluence Client instance. 29 | */ 30 | export const checkConnection = async ( 31 | peer: ClientPeer, 32 | ttl?: number, 33 | ): Promise => { 34 | const msg = Math.random().toString(36).substring(7); 35 | 36 | const script = ` 37 | (xor 38 | (seq 39 | (call %init_peer_id% ("load" "relay") [] init_relay) 40 | (seq 41 | (call %init_peer_id% ("load" "msg") [] msg) 42 | (seq 43 | (call init_relay ("op" "identity") [msg] result) 44 | (call %init_peer_id% ("callback" "callback") [result]) 45 | ) 46 | ) 47 | ) 48 | (seq 49 | (call init_relay ("op" "identity") []) 50 | (call %init_peer_id% ("callback" "error") [%last_error%]) 51 | ) 52 | )`; 53 | 54 | const particle = await peer.internals.createNewParticle(script, ttl); 55 | 56 | const promise = new Promise((resolve, reject) => { 57 | if (particle instanceof Error) { 58 | reject(particle.message); 59 | return; 60 | } 61 | 62 | peer.internals.regHandler.forParticle( 63 | particle.id, 64 | "load", 65 | "relay", 66 | WrapFnIntoServiceCall(() => { 67 | return peer.getRelayPeerId(); 68 | }), 69 | ); 70 | 71 | peer.internals.regHandler.forParticle( 72 | particle.id, 73 | "load", 74 | "msg", 75 | WrapFnIntoServiceCall(() => { 76 | return msg; 77 | }), 78 | ); 79 | 80 | peer.internals.regHandler.forParticle( 81 | particle.id, 82 | "callback", 83 | "callback", 84 | WrapFnIntoServiceCall((args) => { 85 | const [val] = args; 86 | 87 | setTimeout(() => { 88 | resolve(val ?? null); 89 | }, 0); 90 | 91 | return {}; 92 | }), 93 | ); 94 | 95 | peer.internals.regHandler.forParticle( 96 | particle.id, 97 | "callback", 98 | "error", 99 | WrapFnIntoServiceCall((args) => { 100 | const [error] = args; 101 | 102 | setTimeout(() => { 103 | reject(error); 104 | }, 0); 105 | 106 | return {}; 107 | }), 108 | ); 109 | 110 | peer.internals.initiateParticle( 111 | particle, 112 | () => {}, 113 | handleTimeout(() => { 114 | reject("particle timed out"); 115 | }), 116 | ); 117 | }); 118 | 119 | try { 120 | const result = await promise; 121 | 122 | if (result !== msg) { 123 | log.error( 124 | "unexpected behavior. 'identity' must return the passed arguments.", 125 | ); 126 | } 127 | 128 | return true; 129 | } catch (e) { 130 | log.error( 131 | "error on establishing connection. Relay: %s error: %j", 132 | peer.getRelayPeerId(), 133 | e, 134 | ); 135 | 136 | return false; 137 | } 138 | }; 139 | -------------------------------------------------------------------------------- /packages/core/js-client/src/compilerSupport/callFunction.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { FluencePeer } from "../jsPeer/FluencePeer.js"; 18 | import { logger } from "../util/logger.js"; 19 | import { ArgCallbackFunction } from "../util/testUtils.js"; 20 | import type { JSONValue } from "../util/types.js"; 21 | 22 | import { 23 | errorHandlingService, 24 | injectRelayService, 25 | injectValueService, 26 | registerParticleScopeService, 27 | responseService, 28 | ServiceDescription, 29 | userHandlerService, 30 | } from "./services.js"; 31 | 32 | const log = logger("aqua"); 33 | 34 | /** 35 | * Convenience function which does all the internal work of creating particles 36 | * and making necessary service registrations in order to support Aqua function calls 37 | * 38 | * @param def - function definition generated by the Aqua compiler 39 | * @param script - air script with function execution logic generated by the Aqua compiler 40 | * @param config - options to configure Aqua function execution 41 | * @param peer - Fluence Peer to invoke the function at 42 | * @param args - args in the form of JSON where each key corresponds to the name of the argument 43 | * @returns 44 | */ 45 | 46 | export type CallAquaFunctionArgs = { 47 | script: string; 48 | config?: CallAquaFunctionConfig | undefined; 49 | peer: FluencePeer; 50 | args: { [key: string]: JSONValue | ArgCallbackFunction }; 51 | fireAndForget: boolean; 52 | }; 53 | 54 | export type CallAquaFunctionConfig = { 55 | ttl?: number; 56 | }; 57 | 58 | export const callAquaFunction = async ({ 59 | script, 60 | config = {}, 61 | peer, 62 | args, 63 | fireAndForget, 64 | }: CallAquaFunctionArgs) => { 65 | log.trace("calling aqua function %j", { script, config, args }); 66 | 67 | const particle = await peer.internals.createNewParticle(script, config.ttl); 68 | 69 | return new Promise((resolve, reject) => { 70 | // Registering function args as a services 71 | for (const [name, argVal] of Object.entries(args)) { 72 | let service: ServiceDescription; 73 | 74 | if (typeof argVal === "function") { 75 | service = userHandlerService("callbackSrv", name, argVal); 76 | } else { 77 | service = injectValueService("getDataSrv", name, argVal); 78 | } 79 | 80 | registerParticleScopeService(peer, particle, service); 81 | } 82 | 83 | registerParticleScopeService(peer, particle, responseService(resolve)); 84 | 85 | registerParticleScopeService(peer, particle, injectRelayService(peer)); 86 | 87 | registerParticleScopeService(peer, particle, errorHandlingService(reject)); 88 | 89 | peer.internals.initiateParticle(particle, resolve, reject, fireAndForget); 90 | }); 91 | }; 92 | -------------------------------------------------------------------------------- /packages/core/js-client/src/compilerSupport/registerService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { FluencePeer } from "../jsPeer/FluencePeer.js"; 18 | import { logger } from "../util/logger.js"; 19 | 20 | import { registerGlobalService, userHandlerService } from "./services.js"; 21 | import { ServiceImpl } from "./types.js"; 22 | 23 | const log = logger("aqua"); 24 | 25 | interface RegisterServiceArgs { 26 | peer: FluencePeer; 27 | serviceId: string; 28 | service: ServiceImpl; 29 | } 30 | 31 | type ServiceFunctionPair = [key: string, value: ServiceImpl[string]]; 32 | 33 | // This function iterates on plain object or class instance functions ignoring inherited functions and prototype chain. 34 | const findAllPossibleRegisteredServiceFunctions = ( 35 | service: ServiceImpl, 36 | ): Array => { 37 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 38 | const prototype = Object.getPrototypeOf(service) as ServiceImpl; 39 | 40 | const isClassInstance = prototype.constructor !== Object; 41 | 42 | if (isClassInstance) { 43 | service = prototype; 44 | } 45 | 46 | return Object.getOwnPropertyNames(service) 47 | .map((prop) => { 48 | return [prop, service[prop]]; 49 | }) 50 | .filter((entry): entry is ServiceFunctionPair => { 51 | const [prop, value] = entry; 52 | return typeof value === "function" && prop !== "constructor"; 53 | }); 54 | }; 55 | 56 | export const registerService = ({ 57 | peer, 58 | serviceId, 59 | service, 60 | }: RegisterServiceArgs) => { 61 | log.trace("registering aqua service %o", { serviceId, service }); 62 | 63 | const serviceFunctions = findAllPossibleRegisteredServiceFunctions(service); 64 | 65 | for (const [serviceFunction, handler] of serviceFunctions) { 66 | const userDefinedHandler = handler.bind(service); 67 | 68 | const serviceDescription = userHandlerService( 69 | serviceId, 70 | serviceFunction, 71 | userDefinedHandler, 72 | ); 73 | 74 | registerGlobalService(peer, serviceDescription); 75 | } 76 | 77 | log.trace("aqua service registered %s", serviceId); 78 | }; 79 | -------------------------------------------------------------------------------- /packages/core/js-client/src/compilerSupport/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { ParticleContext } from "../jsServiceHost/interfaces.js"; 18 | import type { JSONArray, JSONValue } from "../util/types.js"; 19 | 20 | export type MaybePromise = T | Promise; 21 | 22 | export type ServiceImpl = Record< 23 | string, 24 | (args: { 25 | args: JSONArray; 26 | context: ParticleContext; 27 | }) => MaybePromise 28 | >; 29 | 30 | export type UserServiceImpl = Record< 31 | string, 32 | (...args: [...JSONArray, ParticleContext]) => MaybePromise 33 | >; 34 | 35 | export type ServiceFnArgs = { args: T; context: ParticleContext }; 36 | -------------------------------------------------------------------------------- /packages/core/js-client/src/connection/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import type { Subscribable } from "rxjs"; 18 | 19 | import { IParticle } from "../particle/interfaces.js"; 20 | import { IStartable } from "../util/commonTypes.js"; 21 | import { PeerIdB58 } from "../util/types.js"; 22 | 23 | /** 24 | * Interface for connection used in Fluence Peer. 25 | */ 26 | export interface IConnection extends IStartable { 27 | /** 28 | * Observable that emits particles received from the connection. 29 | */ 30 | particleSource: Subscribable; 31 | 32 | /** 33 | * Send particle to the network using the connection. 34 | * @param nextPeerIds - list of peer ids to send the particle to 35 | * @param particle - particle to send 36 | */ 37 | sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise; 38 | 39 | /** 40 | * Get peer id of the relay peer. Throws an error if the connection doesn't support relay. 41 | */ 42 | getRelayPeerId(): PeerIdB58; 43 | 44 | /** 45 | * Check if the connection supports relay. 46 | */ 47 | supportsRelay(): boolean; 48 | } 49 | -------------------------------------------------------------------------------- /packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { afterEach, beforeEach, describe, expect, it } from "vitest"; 18 | 19 | import { DEFAULT_CONFIG, FluencePeer } from "../../jsPeer/FluencePeer.js"; 20 | import { ResultCodes } from "../../jsServiceHost/interfaces.js"; 21 | import { KeyPair } from "../../keypair/index.js"; 22 | import { loadMarineDeps } from "../../marine/loader.js"; 23 | import { MarineBackgroundRunner } from "../../marine/worker/index.js"; 24 | import { EphemeralNetworkClient } from "../client.js"; 25 | import { defaultConfig, EphemeralNetwork } from "../network.js"; 26 | 27 | let en: EphemeralNetwork; 28 | let client: FluencePeer; 29 | const relay = defaultConfig.peers[0].peerId; 30 | 31 | // TODO: race condition here. Needs to be fixed 32 | describe.skip("Ephemeral networks tests", () => { 33 | beforeEach(async () => { 34 | en = new EphemeralNetwork(defaultConfig); 35 | await en.up(); 36 | 37 | const kp = await KeyPair.randomEd25519(); 38 | 39 | const marineDeps = await loadMarineDeps("/"); 40 | const marine = new MarineBackgroundRunner(...marineDeps); 41 | 42 | client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, marine, en, relay); 43 | await client.start(); 44 | }); 45 | 46 | afterEach(async () => { 47 | await client.stop(); 48 | await en.down(); 49 | }); 50 | 51 | it("smoke test", async function () { 52 | // arrange 53 | const peers = defaultConfig.peers.map((x) => { 54 | return x.peerId; 55 | }); 56 | 57 | const script = ` 58 | (seq 59 | (call "${relay}" ("op" "noop") []) 60 | (seq 61 | (call "${peers[1]}" ("op" "noop") []) 62 | (seq 63 | (call "${peers[2]}" ("op" "noop") []) 64 | (seq 65 | (call "${peers[3]}" ("op" "noop") []) 66 | (seq 67 | (call "${peers[4]}" ("op" "noop") []) 68 | (seq 69 | (call "${peers[5]}" ("op" "noop") []) 70 | (seq 71 | (call "${relay}" ("op" "noop") []) 72 | (call %init_peer_id% ("test" "test") []) 73 | ) 74 | ) 75 | ) 76 | ) 77 | ) 78 | ) 79 | ) 80 | `; 81 | 82 | const particle = await client.internals.createNewParticle(script); 83 | 84 | const promise = new Promise((resolve) => { 85 | client.internals.regHandler.forParticle( 86 | particle.id, 87 | "test", 88 | "test", 89 | () => { 90 | resolve("success"); 91 | return { 92 | result: "test", 93 | retCode: ResultCodes.success, 94 | }; 95 | }, 96 | ); 97 | }); 98 | 99 | // act 100 | client.internals.initiateParticle( 101 | particle, 102 | () => {}, 103 | () => {}, 104 | ); 105 | 106 | // assert 107 | await expect(promise).resolves.toBe("success"); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /packages/core/js-client/src/ephemeral/client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { FluencePeer, PeerConfig } from "../jsPeer/FluencePeer.js"; 18 | import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js"; 19 | import { KeyPair } from "../keypair/index.js"; 20 | import { IMarineHost } from "../marine/interfaces.js"; 21 | import { PeerIdB58 } from "../util/types.js"; 22 | 23 | import { EphemeralNetwork } from "./network.js"; 24 | 25 | /** 26 | * Ephemeral network client is a FluencePeer that connects to a relay peer in an ephemeral network. 27 | */ 28 | export class EphemeralNetworkClient extends FluencePeer { 29 | constructor( 30 | config: PeerConfig, 31 | keyPair: KeyPair, 32 | marine: IMarineHost, 33 | network: EphemeralNetwork, 34 | relay: PeerIdB58, 35 | ) { 36 | const conn = network.getRelayConnection(keyPair.getPeerId(), relay); 37 | 38 | super(config, keyPair, marine, new JsServiceHost(), conn); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/js-client/src/jsPeer/__test__/par.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { describe, expect, it, assert } from "vitest"; 18 | 19 | import { 20 | CallServiceData, 21 | ResultCodes, 22 | } from "../../jsServiceHost/interfaces.js"; 23 | import { handleTimeout } from "../../particle/Particle.js"; 24 | import { registerHandlersHelper, withPeer } from "../../util/testUtils.js"; 25 | import type { JSONValue } from "../../util/types.js"; 26 | 27 | describe("FluencePeer flow tests", () => { 28 | it("should execute par instruction in parallel", async function () { 29 | await withPeer(async (peer) => { 30 | const script = ` 31 | (par 32 | (seq 33 | (call %init_peer_id% ("flow" "timeout") [1000 "test1"] res1) 34 | (call %init_peer_id% ("callback" "callback1") [res1]) 35 | ) 36 | (seq 37 | (call %init_peer_id% ("flow" "timeout") [1000 "test2"] res2) 38 | (call %init_peer_id% ("callback" "callback2") [res2]) 39 | ) 40 | ) 41 | `; 42 | 43 | const particle = await peer.internals.createNewParticle(script); 44 | 45 | const res = await new Promise((resolve, reject) => { 46 | peer.internals.regHandler.forParticle( 47 | particle.id, 48 | "flow", 49 | "timeout", 50 | (req: CallServiceData) => { 51 | const [timeout, message] = req.args; 52 | assert(typeof timeout === "number"); 53 | assert(message); 54 | 55 | return new Promise((resolve) => { 56 | setTimeout(() => { 57 | const res = { 58 | result: message, 59 | retCode: ResultCodes.success, 60 | }; 61 | 62 | resolve(res); 63 | }, timeout); 64 | }); 65 | }, 66 | ); 67 | 68 | if (particle instanceof Error) { 69 | reject(particle.message); 70 | return; 71 | } 72 | 73 | const values: JSONValue[] = []; 74 | 75 | registerHandlersHelper(peer, particle, { 76 | callback: { 77 | callback1: (args): undefined => { 78 | const [val] = args; 79 | assert(val); 80 | values.push(val); 81 | 82 | if (values.length === 2) { 83 | resolve(values); 84 | } 85 | }, 86 | callback2: (args): undefined => { 87 | const [val] = args; 88 | assert(val); 89 | values.push(val); 90 | 91 | if (values.length === 2) { 92 | resolve(values); 93 | } 94 | }, 95 | }, 96 | }); 97 | 98 | peer.internals.initiateParticle( 99 | particle, 100 | () => {}, 101 | handleTimeout(reject), 102 | ); 103 | }); 104 | 105 | expect(res).toEqual(expect.arrayContaining(["test1", "test1"])); 106 | }); 107 | }, 1800); 108 | }); 109 | -------------------------------------------------------------------------------- /packages/core/js-client/src/jsPeer/__test__/parseAst.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { it, describe, expect } from "vitest"; 18 | 19 | import { withPeer } from "../../util/testUtils.js"; 20 | 21 | describe("Parse ast tests", () => { 22 | it("Correct ast should be parsed correctly", async () => { 23 | await withPeer(async (peer) => { 24 | const air = `(null)`; 25 | const res = await peer.internals.parseAst(air); 26 | 27 | expect(res).toStrictEqual({ 28 | success: true, 29 | data: { Null: null }, 30 | }); 31 | }); 32 | }); 33 | 34 | it("Incorrect ast should result in corresponding error", async () => { 35 | await withPeer(async (peer) => { 36 | const air = `(null`; 37 | const res = await peer.internals.parseAst(air); 38 | 39 | expect(res).toStrictEqual({ 40 | success: false, 41 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 42 | data: expect.stringContaining("error"), 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/core/js-client/src/jsPeer/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Throw when particle times out, e.g. haven't been resolved after TTL is expired 19 | */ 20 | export class ExpirationError extends Error {} 21 | 22 | /** 23 | * Throws when AquaVM interpreter returns an error while executing air script. It could be badly written air or internal bug. 24 | */ 25 | export class InterpreterError extends Error {} 26 | 27 | /** 28 | * Throws when network error occurs while sending particle to relay peer. 29 | */ 30 | export class SendError extends Error {} 31 | -------------------------------------------------------------------------------- /packages/core/js-client/src/jsServiceHost/JsServiceHost.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | CallServiceData, 19 | CallServiceResult, 20 | GenericCallServiceHandler, 21 | IJsServiceHost, 22 | } from "./interfaces.js"; 23 | 24 | export class JsServiceHost implements IJsServiceHost { 25 | private particleScopeHandlers = new Map< 26 | string, 27 | Map 28 | >(); 29 | private commonHandlers = new Map(); 30 | 31 | /** 32 | * Returns true if any handler for the specified serviceId is registered 33 | */ 34 | hasService(serviceId: string): boolean { 35 | return ( 36 | this.commonHandlers.has(serviceId) || 37 | this.particleScopeHandlers.has(serviceId) 38 | ); 39 | } 40 | 41 | /** 42 | * Removes all handlers associated with the specified particle scope 43 | * @param particleId Particle ID to remove handlers for 44 | */ 45 | removeParticleScopeHandlers(particleId: string): void { 46 | this.particleScopeHandlers.delete(particleId); 47 | } 48 | 49 | /** 50 | * Find call service handler for specified particle 51 | * @param serviceId Service ID as specified in `call` air instruction 52 | * @param fnName Function name as specified in `call` air instruction 53 | * @param particleId Particle ID 54 | */ 55 | getHandler( 56 | serviceId: string, 57 | fnName: string, 58 | particleId: string, 59 | ): GenericCallServiceHandler | null { 60 | const key = serviceFnKey(serviceId, fnName); 61 | return ( 62 | this.particleScopeHandlers.get(particleId)?.get(key) ?? 63 | this.commonHandlers.get(key) ?? 64 | null 65 | ); 66 | } 67 | 68 | /** 69 | * Execute service call for specified call service data. Return null if no handler was found 70 | */ 71 | async callService(req: CallServiceData): Promise { 72 | const handler = this.getHandler( 73 | req.serviceId, 74 | req.fnName, 75 | req.particleContext.particleId, 76 | ); 77 | 78 | if (handler === null) { 79 | return null; 80 | } 81 | 82 | const result = await handler(req); 83 | 84 | // Otherwise AVM might break 85 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 86 | if (result.result === undefined) { 87 | result.result = null; 88 | } 89 | 90 | return result; 91 | } 92 | 93 | /** 94 | * Register handler for all particles 95 | */ 96 | registerGlobalHandler( 97 | serviceId: string, 98 | fnName: string, 99 | handler: GenericCallServiceHandler, 100 | ): void { 101 | this.commonHandlers.set(serviceFnKey(serviceId, fnName), handler); 102 | } 103 | 104 | /** 105 | * Register handler which will be called only for particle with the specific id 106 | */ 107 | registerParticleScopeHandler( 108 | particleId: string, 109 | serviceId: string, 110 | fnName: string, 111 | handler: GenericCallServiceHandler, 112 | ): void { 113 | let psh = this.particleScopeHandlers.get(particleId); 114 | 115 | if (psh === undefined) { 116 | psh = new Map(); 117 | this.particleScopeHandlers.set(particleId, psh); 118 | } 119 | 120 | psh.set(serviceFnKey(serviceId, fnName), handler); 121 | } 122 | } 123 | 124 | function serviceFnKey(serviceId: string, fnName: string) { 125 | return `${serviceId}/${fnName}`; 126 | } 127 | -------------------------------------------------------------------------------- /packages/core/js-client/src/jsServiceHost/serviceUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { SecurityTetraplet } from "@fluencelabs/avm"; 18 | 19 | import { FluencePeer } from "../jsPeer/FluencePeer.js"; 20 | import { IParticle } from "../particle/interfaces.js"; 21 | import { builtInServices } from "../services/builtins.js"; 22 | import type { JSONArray } from "../util/types.js"; 23 | 24 | import { 25 | CallServiceData, 26 | CallServiceResult, 27 | CallServiceResultType, 28 | ParticleContext, 29 | ResultCodes, 30 | } from "./interfaces.js"; 31 | 32 | export const WrapFnIntoServiceCall = ( 33 | fn: (args: JSONArray) => CallServiceResultType | undefined, 34 | ) => { 35 | return (req: CallServiceData): CallServiceResult => { 36 | return { 37 | retCode: ResultCodes.success, 38 | result: fn(req.args) ?? null, 39 | }; 40 | }; 41 | }; 42 | 43 | export class ServiceError extends Error { 44 | constructor(message: string) { 45 | super(message); 46 | 47 | Object.setPrototypeOf(this, ServiceError.prototype); 48 | } 49 | } 50 | 51 | export const getParticleContext = ( 52 | particle: IParticle, 53 | tetraplets: SecurityTetraplet[][], 54 | ): ParticleContext => { 55 | return { 56 | particleId: particle.id, 57 | initPeerId: particle.initPeerId, 58 | timestamp: particle.timestamp, 59 | ttl: particle.ttl, 60 | signature: particle.signature, 61 | tetraplets, 62 | }; 63 | }; 64 | 65 | export function registerDefaultServices(peer: FluencePeer) { 66 | Object.entries(builtInServices).forEach(([serviceId, service]) => { 67 | Object.entries(service).forEach(([fnName, fn]) => { 68 | const wrapped = async (req: CallServiceData) => { 69 | const res = await fn(req); 70 | 71 | if ( 72 | res.retCode === ResultCodes.error && 73 | typeof res.result === "string" 74 | ) { 75 | return { 76 | retCode: ResultCodes.error, 77 | result: `("${serviceId}" "${fnName}") ${res.result}`, 78 | }; 79 | } 80 | 81 | return res; 82 | }; 83 | 84 | peer.internals.regHandler.common(serviceId, fnName, wrapped); 85 | }); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /packages/core/js-client/src/keypair/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | generateKeyPairFromSeed, 19 | generateKeyPair, 20 | unmarshalPublicKey, 21 | } from "@libp2p/crypto/keys"; 22 | import type { PrivateKey, PublicKey, PeerId } from "@libp2p/interface"; 23 | import { createFromPrivKey } from "@libp2p/peer-id-factory"; 24 | import bs58 from "bs58"; 25 | import { toUint8Array } from "js-base64"; 26 | 27 | import { KeyPairOptions } from "../clientPeer/types.js"; 28 | 29 | export class KeyPair { 30 | private publicKey: PublicKey; 31 | 32 | private constructor( 33 | private privateKey: PrivateKey, 34 | private libp2pPeerId: PeerId, 35 | ) { 36 | this.publicKey = privateKey.public; 37 | } 38 | 39 | /** 40 | * Key pair in libp2p format. Used for backward compatibility with the current FluencePeer implementation 41 | */ 42 | getLibp2pPeerId() { 43 | return this.libp2pPeerId; 44 | } 45 | 46 | /** 47 | * Return public key inferred from private key 48 | */ 49 | getPublicKey() { 50 | return this.publicKey.bytes; 51 | } 52 | 53 | /** 54 | * Generates new KeyPair from ed25519 private key represented as a 32 byte array 55 | * @param seed - Any sequence of 32 bytes 56 | * @returns - Promise with the created KeyPair 57 | */ 58 | static async fromEd25519SK(seed: Uint8Array): Promise { 59 | const key = await generateKeyPairFromSeed("Ed25519", seed, 256); 60 | const lib2p2Pid = await createFromPrivKey(key); 61 | return new KeyPair(key, lib2p2Pid); 62 | } 63 | 64 | /** 65 | * Generates new KeyPair with a random secret key 66 | * @returns - Promise with the created KeyPair 67 | */ 68 | static async randomEd25519(): Promise { 69 | const key = await generateKeyPair("Ed25519"); 70 | const lib2p2Pid = await createFromPrivKey(key); 71 | return new KeyPair(key, lib2p2Pid); 72 | } 73 | 74 | static verifyWithPublicKey( 75 | publicKey: Uint8Array, 76 | message: Uint8Array, 77 | signature: Uint8Array, 78 | ) { 79 | return unmarshalPublicKey(publicKey).verify(message, signature); 80 | } 81 | 82 | getPeerId(): string { 83 | return this.libp2pPeerId.toString(); 84 | } 85 | 86 | /** 87 | * @returns 32 byte private key 88 | */ 89 | toEd25519PrivateKey(): Uint8Array { 90 | return this.privateKey.marshal().subarray(0, 32); 91 | } 92 | 93 | async signBytes(data: Uint8Array): Promise { 94 | return this.privateKey.sign(data); 95 | } 96 | 97 | async verify(data: Uint8Array, signature: Uint8Array): Promise { 98 | return this.publicKey.verify(data, signature); 99 | } 100 | } 101 | 102 | export const fromBase64Sk = (sk: string): Promise => { 103 | const skArr = toUint8Array(sk); 104 | return KeyPair.fromEd25519SK(skArr); 105 | }; 106 | 107 | export const fromBase58Sk = (sk: string): Promise => { 108 | const skArr = bs58.decode(sk); 109 | return KeyPair.fromEd25519SK(skArr); 110 | }; 111 | 112 | export const fromOpts = (opts: KeyPairOptions): Promise => { 113 | if (opts.source === "random") { 114 | return KeyPair.randomEd25519(); 115 | } 116 | 117 | return KeyPair.fromEd25519SK(opts.source); 118 | }; 119 | -------------------------------------------------------------------------------- /packages/core/js-client/src/marine/__test__/marine-js.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from "fs"; 18 | import * as path from "path"; 19 | import * as url from "url"; 20 | 21 | import { it, describe, expect, beforeAll, assert } from "vitest"; 22 | 23 | import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; 24 | 25 | let aqua: Record; 26 | const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); 27 | 28 | describe("Marine js tests", () => { 29 | beforeAll(async () => { 30 | const pathToAquaFiles = path.join( 31 | __dirname, 32 | "../../../aqua_test/marine-js.aqua", 33 | ); 34 | 35 | const { functions } = await compileAqua(pathToAquaFiles); 36 | aqua = functions; 37 | }); 38 | 39 | it("should call marine service correctly", async () => { 40 | await withPeer(async (peer) => { 41 | // arrange 42 | const wasm = await fs.promises.readFile( 43 | path.join(__dirname, "../../../data_for_test/greeting.wasm"), 44 | ); 45 | 46 | await peer.registerMarineService(wasm, "greeting"); 47 | 48 | // act 49 | assert(aqua["call"]); 50 | const res = await aqua["call"](peer, { arg: "test" }); 51 | 52 | // assert 53 | expect(res).toBe("Hi, Hi, Hi, test"); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/core/js-client/src/marine/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { CallParameters } from "@fluencelabs/marine-worker"; 18 | 19 | import { IStartable } from "../util/commonTypes.js"; 20 | import type { JSONObject, JSONValue, JSONArray } from "../util/types.js"; 21 | 22 | /** 23 | * Contract for marine host implementations. Marine host is responsible for creating calling and removing marine services 24 | */ 25 | export interface IMarineHost extends IStartable { 26 | /** 27 | * Creates marine service from the given module and service id 28 | */ 29 | createService( 30 | serviceModule: ArrayBuffer | SharedArrayBuffer, 31 | serviceId: string, 32 | ): Promise; 33 | 34 | /** 35 | * Removes marine service with the given service id 36 | */ 37 | removeService(serviceId: string): Promise; 38 | 39 | /** 40 | * Returns true if any service with the specified service id is registered 41 | */ 42 | hasService(serviceId: string): Promise; 43 | 44 | /** 45 | * Calls the specified function of the specified service with the given arguments 46 | */ 47 | callService( 48 | serviceId: string, 49 | functionName: string, 50 | args: JSONArray | JSONObject, 51 | callParams?: CallParameters, 52 | ): Promise; 53 | } 54 | -------------------------------------------------------------------------------- /packages/core/js-client/src/marine/loader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher"; 18 | import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver"; 19 | import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker"; 20 | import type { ModuleThread } from "@fluencelabs/threads/master"; 21 | 22 | type StrategyReturnType = [ 23 | marineJsWasm: ArrayBuffer, 24 | avmWasm: ArrayBuffer, 25 | worker: ModuleThread, 26 | ]; 27 | 28 | export const loadMarineDeps = async ( 29 | CDNUrl: string, 30 | ): Promise => { 31 | const [marineJsWasm, avmWasm, worker] = await Promise.all([ 32 | fetchResource( 33 | "@fluencelabs/marine-js", 34 | "/dist/marine-js.wasm", 35 | CDNUrl, 36 | ).then((res) => { 37 | return res.arrayBuffer(); 38 | }), 39 | fetchResource("@fluencelabs/avm", "/dist/avm.wasm", CDNUrl).then((res) => { 40 | return res.arrayBuffer(); 41 | }), 42 | getWorker("@fluencelabs/marine-worker", CDNUrl), 43 | ]); 44 | 45 | return [marineJsWasm, avmWasm, worker]; 46 | }; 47 | -------------------------------------------------------------------------------- /packages/core/js-client/src/marine/worker/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import type { 18 | MarineBackgroundInterface, 19 | LogFunction, 20 | JSONValueNonNullable, 21 | CallParameters, 22 | } from "@fluencelabs/marine-worker"; 23 | import { ModuleThread, Thread } from "@fluencelabs/threads/master"; 24 | 25 | import { MarineLogger, marineLogger } from "../../util/logger.js"; 26 | import type { JSONValue } from "../../util/types.js"; 27 | import { IMarineHost } from "../interfaces.js"; 28 | 29 | export class MarineBackgroundRunner implements IMarineHost { 30 | private loggers = new Map(); 31 | 32 | constructor( 33 | private marineJsWasm: ArrayBuffer, 34 | private avmWasm: ArrayBuffer, 35 | private workerThread: ModuleThread, 36 | ) {} 37 | 38 | async hasService(serviceId: string) { 39 | return this.workerThread.hasService(serviceId); 40 | } 41 | 42 | async removeService(serviceId: string) { 43 | await this.workerThread.removeService(serviceId); 44 | } 45 | 46 | async start(): Promise { 47 | const logfn: LogFunction = (message) => { 48 | const serviceLogger = this.loggers.get(message.service); 49 | 50 | if (serviceLogger === undefined) { 51 | return; 52 | } 53 | 54 | serviceLogger[message.level](message.message); 55 | }; 56 | 57 | this.workerThread.onLogMessage().subscribe(logfn); 58 | await this.workerThread.init(this.marineJsWasm); 59 | await this.createService(this.avmWasm, "avm"); 60 | } 61 | 62 | async createService( 63 | serviceModule: ArrayBuffer | SharedArrayBuffer, 64 | serviceId: string, 65 | ): Promise { 66 | this.loggers.set(serviceId, marineLogger(serviceId)); 67 | await this.workerThread.createService(serviceModule, serviceId); 68 | } 69 | 70 | async callService( 71 | serviceId: string, 72 | functionName: string, 73 | args: Array | Record, 74 | callParams?: CallParameters, 75 | ): Promise { 76 | return this.workerThread.callService( 77 | serviceId, 78 | functionName, 79 | args, 80 | callParams, 81 | ); 82 | } 83 | 84 | async stop(): Promise { 85 | await this.workerThread.terminate(); 86 | await Thread.terminate(this.workerThread); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/core/js-client/src/particle/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import type { PeerIdB58 } from "../util/types.js"; 18 | 19 | /** 20 | * Immutable part of the particle. 21 | */ 22 | export interface IImmutableParticlePart { 23 | /** 24 | * Particle id 25 | */ 26 | readonly id: string; 27 | 28 | /** 29 | * Particle timestamp. Specifies when the particle was created. 30 | */ 31 | readonly timestamp: number; 32 | 33 | /** 34 | * Particle's air script 35 | */ 36 | readonly script: string; 37 | 38 | /** 39 | * Particle's ttl. Specifies how long the particle is valid in milliseconds. 40 | */ 41 | readonly ttl: number; 42 | 43 | /** 44 | * Peer id where the particle was initiated. 45 | */ 46 | readonly initPeerId: PeerIdB58; 47 | 48 | /** 49 | * Particle's signature of concatenation of bytes of all immutable particle fields. 50 | */ 51 | readonly signature: Uint8Array; 52 | } 53 | 54 | /** 55 | * Particle is a data structure that is used to transfer data between peers in Fluence network. 56 | */ 57 | export interface IParticle extends IImmutableParticlePart { 58 | /** 59 | * Mutable particle data 60 | */ 61 | data: Uint8Array; 62 | } 63 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/NodeUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { readFile } from "fs/promises"; 18 | 19 | import { ServiceFnArgs } from "../compilerSupport/types.js"; 20 | import { FluencePeer } from "../jsPeer/FluencePeer.js"; 21 | import { getErrorMessage } from "../util/utils.js"; 22 | 23 | import { registerNodeUtils } from "./_aqua/node-utils.js"; 24 | import { SecurityGuard } from "./securityGuard.js"; 25 | import { defaultGuard } from "./SingleModuleSrv.js"; 26 | 27 | export class NodeUtils { 28 | constructor(private peer: FluencePeer) { 29 | this.securityGuard_readFile = defaultGuard(this.peer); 30 | } 31 | 32 | securityGuard_readFile: SecurityGuard; 33 | 34 | async read_file({ args: [path], context }: ServiceFnArgs<[string]>) { 35 | if (!this.securityGuard_readFile(context)) { 36 | return { 37 | success: false, 38 | error: ["Security guard validation failed"], 39 | content: null, 40 | }; 41 | } 42 | 43 | try { 44 | const data = await readFile(path, "base64"); 45 | 46 | return { 47 | success: true, 48 | content: [data], 49 | error: null, 50 | }; 51 | } catch (err: unknown) { 52 | return { 53 | success: false, 54 | error: [getErrorMessage(err)], 55 | content: null, 56 | }; 57 | } 58 | } 59 | } 60 | 61 | // HACK:: security guard functions must be ported to user API 62 | export const doRegisterNodeUtils = (peer: FluencePeer) => { 63 | registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); 64 | }; 65 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/Sig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { ServiceFnArgs } from "../compilerSupport/types.js"; 18 | import { KeyPair } from "../keypair/index.js"; 19 | import type { PeerIdB58 } from "../util/types.js"; 20 | 21 | import { 22 | allowOnlyParticleOriginatedAt, 23 | allowServiceFn, 24 | and, 25 | or, 26 | SecurityGuard, 27 | } from "./securityGuard.js"; 28 | 29 | export const defaultSigGuard = (peerId: PeerIdB58) => { 30 | return and( 31 | allowOnlyParticleOriginatedAt(peerId), 32 | or( 33 | allowServiceFn("trust-graph", "get_trust_bytes"), 34 | allowServiceFn("trust-graph", "get_revocation_bytes"), 35 | allowServiceFn("registry", "get_key_bytes"), 36 | allowServiceFn("registry", "get_record_bytes"), 37 | allowServiceFn("registry", "get_record_metadata_bytes"), 38 | allowServiceFn("registry", "get_tombstone_bytes"), 39 | ), 40 | ); 41 | }; 42 | 43 | type SignReturnType = 44 | | { 45 | error: []; 46 | signature: [number[]]; 47 | success: true; 48 | } 49 | | { 50 | error: [string]; 51 | signature: []; 52 | success: false; 53 | }; 54 | 55 | export class Sig { 56 | constructor(private keyPair: KeyPair) {} 57 | 58 | /** 59 | * Configurable security guard for sign method 60 | */ 61 | securityGuard: SecurityGuard = () => { 62 | return true; 63 | }; 64 | 65 | /** 66 | * Gets the public key of KeyPair. Required by aqua 67 | */ 68 | get_peer_id() { 69 | return this.keyPair.getPeerId(); 70 | } 71 | 72 | /** 73 | * Signs the data using key pair's private key. Required by aqua 74 | */ 75 | async sign({ 76 | args: [data], 77 | context, 78 | }: ServiceFnArgs<[number[]]>): Promise { 79 | if (!this.securityGuard(context)) { 80 | return { 81 | success: false, 82 | error: ["Security guard validation failed"], 83 | signature: [], 84 | }; 85 | } 86 | 87 | const signedData = await this.keyPair.signBytes(Uint8Array.from(data)); 88 | 89 | return { 90 | success: true, 91 | error: [], 92 | signature: [Array.from(signedData)], 93 | }; 94 | } 95 | 96 | /** 97 | * Verifies the signature. Required by aqua 98 | */ 99 | verify({ 100 | args: [signature, data], 101 | }: ServiceFnArgs<[number[], number[]]>): Promise { 102 | return this.keyPair.verify( 103 | Uint8Array.from(data), 104 | Uint8Array.from(signature), 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/SingleModuleSrv.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { v4 as uuidv4 } from "uuid"; 18 | 19 | import { ServiceFnArgs } from "../compilerSupport/types.js"; 20 | import { FluencePeer } from "../jsPeer/FluencePeer.js"; 21 | import { getErrorMessage } from "../util/utils.js"; 22 | 23 | import { 24 | allowOnlyParticleOriginatedAt, 25 | SecurityGuard, 26 | } from "./securityGuard.js"; 27 | 28 | export const defaultGuard = (peer: FluencePeer) => { 29 | return allowOnlyParticleOriginatedAt(peer.getPeerId()); 30 | }; 31 | 32 | // Service for registering marine modules in js-client's marine runtime 33 | export class Srv { 34 | private services: Set = new Set(); 35 | 36 | constructor(private peer: FluencePeer) { 37 | this.securityGuard_create = defaultGuard(this.peer); 38 | this.securityGuard_remove = defaultGuard(this.peer); 39 | } 40 | 41 | securityGuard_create: SecurityGuard; 42 | 43 | async create({ args: [wasmContent], context }: ServiceFnArgs<[string]>) { 44 | if (!this.securityGuard_create(context)) { 45 | return { 46 | success: false, 47 | error: ["Marine services could be registered on %init_peer_id% only"], 48 | service_id: null, 49 | }; 50 | } 51 | 52 | try { 53 | const newServiceId = uuidv4(); 54 | 55 | const wasmContentBinary = Uint8Array.from(atob(wasmContent), (m) => { 56 | // codePointAt cannot return `undefined` value here as callback is called on every symbol 57 | return m.codePointAt(0) ?? 0; 58 | }); 59 | 60 | // TODO:: figure out why SharedArrayBuffer is not working here 61 | // const sab = new SharedArrayBuffer(buffer.length); 62 | // const tmp = new Uint8Array(sab); 63 | // tmp.set(buffer, 0); 64 | await this.peer.registerMarineService(wasmContentBinary, newServiceId); 65 | this.services.add(newServiceId); 66 | 67 | return { 68 | success: true, 69 | service_id: [newServiceId], 70 | error: null, 71 | }; 72 | } catch (err: unknown) { 73 | return { 74 | success: true, 75 | service_id: null, 76 | error: [getErrorMessage(err)], 77 | }; 78 | } 79 | } 80 | 81 | securityGuard_remove: SecurityGuard; 82 | 83 | async remove({ args: [serviceId], context }: ServiceFnArgs<[string]>) { 84 | if (!this.securityGuard_remove(context)) { 85 | return { 86 | success: false, 87 | error: ["Marine services could be remove on %init_peer_id% only"], 88 | service_id: null, 89 | }; 90 | } 91 | 92 | if (!this.services.has(serviceId)) { 93 | return { 94 | success: false, 95 | error: [`Service with id ${serviceId} not found`], 96 | }; 97 | } 98 | 99 | await this.peer.removeMarineService(serviceId); 100 | this.services.delete(serviceId); 101 | 102 | return { 103 | success: true, 104 | error: null, 105 | }; 106 | } 107 | 108 | list() { 109 | return Array.from(this.services.values()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/Tracing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { ServiceFnArgs } from "../compilerSupport/types.js"; 18 | 19 | export class Tracing { 20 | tracingEvent({ 21 | args: [arrowName, event], 22 | context, 23 | }: ServiceFnArgs<[string, string]>): void { 24 | // This console log is intentional 25 | // eslint-disable-next-line no-console 26 | console.log("[%s] (%s) %s", context.particleId, arrowName, event); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { it, describe, expect, beforeEach, afterEach } from "vitest"; 18 | 19 | import { FluencePeer } from "../../jsPeer/FluencePeer.js"; 20 | import { mkTestPeer } from "../../util/testUtils.js"; 21 | 22 | let peer: FluencePeer; 23 | 24 | describe("Sig service test suite", () => { 25 | afterEach(async () => { 26 | await peer.stop(); 27 | }); 28 | 29 | beforeEach(async () => { 30 | peer = await mkTestPeer(); 31 | await peer.start(); 32 | }); 33 | 34 | it("JSON builtin spec", async () => { 35 | const script = ` 36 | (seq 37 | (seq 38 | (seq 39 | ;; create 40 | (seq 41 | (call %init_peer_id% ("json" "obj") ["name" "nested_first" "num" 1] nested_first) 42 | (call %init_peer_id% ("json" "obj") ["name" "nested_second" "num" 2] nested_second) 43 | ) 44 | (call %init_peer_id% ("json" "obj") ["name" "outer_first" "num" 0 "nested" nested_first] outer_first) 45 | ) 46 | (seq 47 | ;; modify 48 | (seq 49 | (call %init_peer_id% ("json" "put") [outer_first "nested" nested_second] outer_tmp_second) 50 | (call %init_peer_id% ("json" "puts") [outer_tmp_second "name" "outer_second" "num" 3] outer_second) 51 | ) 52 | ;; stringify and parse 53 | (seq 54 | (call %init_peer_id% ("json" "stringify") [outer_first] outer_first_string) 55 | (call %init_peer_id% ("json" "parse") [outer_first_string] outer_first_parsed) 56 | ) 57 | ) 58 | ) 59 | (call %init_peer_id% ("res" "res") [nested_first nested_second outer_first outer_second outer_first_string outer_first_parsed]) 60 | ) 61 | `; 62 | 63 | const promise = new Promise((resolve) => { 64 | peer.internals.regHandler.common("res", "res", (req) => { 65 | resolve(req.args); 66 | return { 67 | result: {}, 68 | retCode: 0, 69 | }; 70 | }); 71 | }); 72 | 73 | const p = await peer.internals.createNewParticle(script); 74 | 75 | peer.internals.initiateParticle( 76 | p, 77 | () => {}, 78 | () => {}, 79 | ); 80 | 81 | const [ 82 | nestedFirst, 83 | nestedSecond, 84 | outerFirst, 85 | outerSecond, 86 | outerFirstString, 87 | outerFirstParsed, 88 | ] = await promise; 89 | 90 | const nfExpected = { name: "nested_first", num: 1 }; 91 | const nsExpected = { name: "nested_second", num: 2 }; 92 | 93 | const ofExpected = { name: "outer_first", nested: nfExpected, num: 0 }; 94 | const ofString = JSON.stringify(ofExpected); 95 | const osExpected = { name: "outer_second", num: 3, nested: nsExpected }; 96 | 97 | expect(nestedFirst).toMatchObject(nfExpected); 98 | expect(nestedSecond).toMatchObject(nsExpected); 99 | expect(outerFirst).toMatchObject(ofExpected); 100 | expect(outerSecond).toMatchObject(osExpected); 101 | expect(outerFirstParsed).toMatchObject(ofExpected); 102 | expect(outerFirstString).toBe(ofString); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/__test__/srv.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as path from "path"; 18 | import * as url from "url"; 19 | 20 | import { it, describe, expect, beforeAll, assert } from "vitest"; 21 | 22 | import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; 23 | import { registerNodeUtils } from "../_aqua/node-utils.js"; 24 | import { NodeUtils } from "../NodeUtils.js"; 25 | 26 | const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); 27 | let aqua: Record; 28 | 29 | describe("Srv service test suite", () => { 30 | beforeAll(async () => { 31 | const pathToAquaFiles = path.join(__dirname, "../../../aqua_test/srv.aqua"); 32 | 33 | const { functions } = await compileAqua(pathToAquaFiles); 34 | aqua = functions; 35 | }); 36 | 37 | it("Use custom srv service, success path", async () => { 38 | await withPeer(async (peer) => { 39 | // arrange 40 | registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); 41 | 42 | const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); 43 | 44 | // act 45 | assert(aqua["happy_path"]); 46 | const res = await aqua["happy_path"](peer, { file_path: wasm }); 47 | 48 | // assert 49 | expect(res).toBe("Hi, test"); 50 | }); 51 | }); 52 | 53 | it("List deployed services", async () => { 54 | await withPeer(async (peer) => { 55 | // arrange 56 | registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); 57 | 58 | const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); 59 | 60 | // act 61 | assert(aqua["list_services"]); 62 | const res = await aqua["list_services"](peer, { file_path: wasm }); 63 | 64 | // assert 65 | expect(res).toHaveLength(3); 66 | }); 67 | }); 68 | 69 | it("Correct error for removed services", async () => { 70 | await withPeer(async (peer) => { 71 | // arrange 72 | registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); 73 | 74 | const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); 75 | 76 | // act 77 | assert(aqua["service_removed"]); 78 | 79 | const res = await aqua["service_removed"](peer, { 80 | file_path: wasm, 81 | }); 82 | 83 | // assert 84 | expect(res).toMatch("No service found for service call"); 85 | }); 86 | }); 87 | 88 | it("Correct error for file not found", async () => { 89 | await withPeer(async (peer) => { 90 | // arrange 91 | registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); 92 | 93 | // act 94 | assert(aqua["file_not_found"]); 95 | const res = await aqua["file_not_found"](peer, {}); 96 | 97 | // assert 98 | expect(res).toMatch( 99 | "ENOENT: no such file or directory, open '/random/incorrect/file'", 100 | ); 101 | }); 102 | }); 103 | 104 | it("Correct error for removing non existing service", async () => { 105 | await withPeer(async (peer) => { 106 | // arrange 107 | registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); 108 | 109 | // act 110 | assert(aqua["removing_non_exiting"]); 111 | const res = await aqua["removing_non_exiting"](peer, {}); 112 | 113 | // assert 114 | expect(res).toMatch("Service with id random_id not found"); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/_aqua/node-utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { registerService } from "../../compilerSupport/registerService.js"; 18 | import { FluencePeer } from "../../jsPeer/FluencePeer.js"; 19 | import { NodeUtils } from "../NodeUtils.js"; 20 | 21 | export function registerNodeUtils( 22 | peer: FluencePeer, 23 | serviceId: string, 24 | service: NodeUtils, 25 | ) { 26 | const nodeUtilsService: Record = service; 27 | 28 | registerService({ 29 | peer, 30 | service: nodeUtilsService, 31 | serviceId, 32 | }); 33 | } 34 | 35 | // Functions 36 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/_aqua/services.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { registerService } from "../../compilerSupport/registerService.js"; 18 | import { FluencePeer } from "../../jsPeer/FluencePeer.js"; 19 | import { ParticleContext } from "../../jsServiceHost/interfaces.js"; 20 | import { Sig } from "../Sig.js"; 21 | 22 | // Services 23 | 24 | export interface SigDef { 25 | get_peer_id: (callParams: ParticleContext) => string | Promise; 26 | sign: ( 27 | data: number[], 28 | callParams: ParticleContext, 29 | ) => 30 | | { error: [string?]; signature: [number[]?]; success: boolean } 31 | | Promise<{ 32 | error: [string?]; 33 | signature: [number[]?]; 34 | success: boolean; 35 | }>; 36 | verify: ( 37 | signature: number[], 38 | data: number[], 39 | callParams: ParticleContext, 40 | ) => boolean | Promise; 41 | } 42 | 43 | export function registerSig( 44 | peer: FluencePeer, 45 | serviceId: string, 46 | service: Sig, 47 | ) { 48 | const sigService: Record = service; 49 | 50 | registerService({ 51 | peer, 52 | service: sigService, 53 | serviceId, 54 | }); 55 | } 56 | 57 | // Functions 58 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/_aqua/single-module-srv.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { registerService } from "../../compilerSupport/registerService.js"; 18 | import { FluencePeer } from "../../jsPeer/FluencePeer.js"; 19 | import { Srv } from "../SingleModuleSrv.js"; 20 | 21 | export function registerSrv( 22 | peer: FluencePeer, 23 | serviceId: string, 24 | service: Srv, 25 | ) { 26 | const singleModuleService: Record = service; 27 | 28 | registerService({ 29 | peer, 30 | serviceId, 31 | service: singleModuleService, 32 | }); 33 | } 34 | 35 | // Functions 36 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/_aqua/tracing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This compiled aqua file was modified to make it work in monorepo 19 | */ 20 | 21 | import { registerService } from "../../compilerSupport/registerService.js"; 22 | import { FluencePeer } from "../../jsPeer/FluencePeer.js"; 23 | import { Tracing } from "../Tracing.js"; 24 | 25 | // Services 26 | 27 | export function registerTracing( 28 | peer: FluencePeer, 29 | serviceId: string, 30 | service: Tracing, 31 | ) { 32 | const tracingService: Record = service; 33 | 34 | registerService({ 35 | peer, 36 | serviceId, 37 | service: tracingService, 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/js-client/src/services/securityGuard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { SecurityTetraplet } from "@fluencelabs/avm"; 18 | 19 | import { ParticleContext } from "../jsServiceHost/interfaces.js"; 20 | import type { PeerIdB58 } from "../util/types.js"; 21 | 22 | // Helpers for validating service function 23 | 24 | /** 25 | * A predicate of call params for sig service's sign method which determines whether signing operation is allowed or not 26 | */ 27 | export type SecurityGuard = (params: ParticleContext) => boolean; 28 | 29 | /** 30 | * Only allow calls when tetraplet for 'data' argument satisfies the predicate 31 | */ 32 | export const allowTetraplet = ( 33 | pred: (tetraplet: SecurityTetraplet) => boolean, 34 | ): SecurityGuard => { 35 | return (params) => { 36 | const t = params.tetraplets[0]?.[0]; 37 | return t !== undefined && pred(t); 38 | }; 39 | }; 40 | 41 | /** 42 | * Only allow data which comes from the specified serviceId and fnName 43 | */ 44 | export const allowServiceFn = ( 45 | serviceId: string, 46 | fnName: string, 47 | ): SecurityGuard => { 48 | return allowTetraplet((t) => { 49 | return t.service_id === serviceId && t.function_name === fnName; 50 | }); 51 | }; 52 | 53 | /** 54 | * Only allow data originated from the specified json_path 55 | */ 56 | export const allowExactJsonPath = (jsonPath: string): SecurityGuard => { 57 | return allowTetraplet((t) => { 58 | return t.lens === jsonPath; 59 | }); 60 | }; 61 | 62 | /** 63 | * Only allow signing when particle is initiated at the specified peer 64 | */ 65 | export const allowOnlyParticleOriginatedAt = ( 66 | peerId: PeerIdB58, 67 | ): SecurityGuard => { 68 | return (params) => { 69 | return params.initPeerId === peerId; 70 | }; 71 | }; 72 | 73 | /** 74 | * Only allow signing when all of the predicates are satisfied. 75 | * Useful for predicates reuse 76 | */ 77 | export const and = (...predicates: SecurityGuard[]): SecurityGuard => { 78 | return (params) => { 79 | return predicates.every((x) => { 80 | return x(params); 81 | }); 82 | }; 83 | }; 84 | 85 | /** 86 | * Only allow signing when any of the predicates are satisfied. 87 | * Useful for predicates reuse 88 | */ 89 | export const or = (...predicates: SecurityGuard[]): SecurityGuard => { 90 | return (params) => { 91 | return predicates.some((x) => { 92 | return x(params); 93 | }); 94 | }; 95 | }; 96 | -------------------------------------------------------------------------------- /packages/core/js-client/src/util/bytes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | type Size = "u32" | "u64"; 18 | 19 | const sizeMap = { 20 | u32: 4, 21 | u64: 8, 22 | } as const; 23 | 24 | function numberToBytes(n: number, s: Size, littleEndian: boolean) { 25 | const size = sizeMap[s]; 26 | const buffer = new ArrayBuffer(8); 27 | const dv = new DataView(buffer); 28 | dv.setBigUint64(0, BigInt(n), littleEndian); 29 | return new Uint8Array(buffer.slice(0, size)); 30 | } 31 | 32 | export function numberToLittleEndianBytes(n: number, s: Size) { 33 | return numberToBytes(n, s, true); 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/js-client/src/util/commonTypes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export interface IStartable { 18 | start(): Promise; 19 | stop(): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/js-client/src/util/libp2pUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { multiaddr, Multiaddr } from "@multiformats/multiaddr"; 18 | 19 | import { RelayOptions } from "../clientPeer/types.js"; 20 | 21 | import { isString } from "./utils.js"; 22 | 23 | export function relayOptionToMultiaddr(relay: RelayOptions): Multiaddr { 24 | const multiaddrString = isString(relay) ? relay : relay.multiaddr; 25 | const ma = multiaddr(multiaddrString); 26 | 27 | const peerId = ma.getPeerId(); 28 | 29 | if (peerId === null) { 30 | throwHasNoPeerId(ma); 31 | } 32 | 33 | return ma; 34 | } 35 | 36 | export function throwHasNoPeerId(ma: Multiaddr): never { 37 | throw new Error( 38 | "Specified multiaddr is invalid or missing peer id: " + ma.toString(), 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/js-client/src/util/logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import debug from "debug"; 18 | 19 | type Logger = (formatter: unknown, ...args: unknown[]) => void; 20 | 21 | export interface CommonLogger { 22 | error: Logger; 23 | trace: Logger; 24 | debug: Logger; 25 | } 26 | 27 | export interface MarineLogger { 28 | warn: Logger; 29 | error: Logger; 30 | debug: Logger; 31 | trace: Logger; 32 | info: Logger; 33 | } 34 | 35 | export function logger(name: string): CommonLogger { 36 | return { 37 | error: debug(`fluence:${name}:error`), 38 | trace: debug(`fluence:${name}:trace`), 39 | debug: debug(`fluence:${name}:debug`), 40 | }; 41 | } 42 | 43 | export function marineLogger(serviceId: string): MarineLogger { 44 | const name = `fluence:marine:${serviceId}`; 45 | return { 46 | warn: debug(`${name}:warn`), 47 | error: debug(`${name}:error`), 48 | debug: debug(`${name}:debug`), 49 | trace: debug(`${name}:trace`), 50 | info: debug(`${name}:info`), 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /packages/core/js-client/src/util/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Peer ID's id as a base58 string (multihash/CIDv0). 19 | */ 20 | export type PeerIdB58 = string; 21 | 22 | export type JSONValue = 23 | | string 24 | | number 25 | | boolean 26 | | null 27 | | { [x: string]: JSONValue } 28 | | Array; 29 | export type JSONObject = { [x: string]: JSONValue }; 30 | export type JSONArray = Array; 31 | -------------------------------------------------------------------------------- /packages/core/js-client/src/util/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export function jsonify(obj: unknown) { 18 | return JSON.stringify(obj, null, 4); 19 | } 20 | 21 | export const isString = (unknown: unknown): unknown is string => { 22 | return unknown !== null && typeof unknown === "string"; 23 | }; 24 | 25 | export const isObject = (unknown: unknown): unknown is object => { 26 | return unknown !== null && typeof unknown === "object"; 27 | }; 28 | 29 | export const getErrorMessage = (error: unknown) => { 30 | if (error instanceof Error) { 31 | return error.message; 32 | } 33 | 34 | return String(error); 35 | }; 36 | 37 | export function zip(arr1: Array, arr2: Array): Array<[A, B]> { 38 | if (arr1.length !== arr2.length) { 39 | throw new Error(`Array length doesn't match`); 40 | } 41 | 42 | const arr = new Array<[A, B]>(arr1.length); 43 | 44 | for (let i = 0; i < arr1.length; i++) { 45 | // Length has been checked above 46 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 47 | arr[i] = [arr1[i]!, arr2[i]!]; 48 | } 49 | 50 | return arr; 51 | } 52 | -------------------------------------------------------------------------------- /packages/core/js-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["vite/client"], 5 | "outDir": "./dist", 6 | "resolveJsonModule": true, 7 | "rootDir": "src" 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": ["node_modules", "dist"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/js-client/vite.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { createRequire } from "module"; 18 | 19 | import inject from "@rollup/plugin-inject"; 20 | import { transform } from "esbuild"; 21 | import { PluginOption, UserConfig } from "vite"; 22 | import tsconfigPaths from "vite-tsconfig-paths"; 23 | 24 | const require = createRequire(import.meta.url); 25 | const esbuildShim = require.resolve("node-stdlib-browser/helpers/esbuild/shim"); 26 | 27 | function minifyEs(): PluginOption { 28 | return { 29 | name: "minifyEs", 30 | renderChunk: { 31 | order: "post", 32 | async handler(code, chunk, outputOptions) { 33 | if ( 34 | outputOptions.format === "es" && 35 | chunk.fileName.endsWith(".min.js") 36 | ) { 37 | return await transform(code, { minify: true }); 38 | } 39 | 40 | return code; 41 | }, 42 | }, 43 | }; 44 | } 45 | 46 | const config: UserConfig = { 47 | build: { 48 | target: "modules", 49 | minify: "esbuild", 50 | lib: { 51 | entry: "./src/index.ts", 52 | name: "js-client", 53 | fileName: () => { 54 | return "index.min.js"; 55 | }, 56 | formats: ["es"], 57 | }, 58 | outDir: "./dist/browser", 59 | rollupOptions: { 60 | plugins: [ 61 | inject({ 62 | global: [esbuildShim, "global"], 63 | process: [esbuildShim, "process"], 64 | Buffer: [esbuildShim, "Buffer"], 65 | }), 66 | ], 67 | }, 68 | }, 69 | plugins: [tsconfigPaths(), minifyEs()], 70 | optimizeDeps: { 71 | esbuildOptions: { 72 | define: { 73 | global: "globalThis", 74 | }, 75 | }, 76 | }, 77 | }; 78 | 79 | export default config; 80 | -------------------------------------------------------------------------------- /packages/core/marine-worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "@fluencelabs/marine-worker", 4 | "version": "0.6.0", 5 | "description": "Marine worker", 6 | "files": [ 7 | "dist" 8 | ], 9 | "main": "./dist/index.js", 10 | "unpkg": "./dist/browser/marine-worker.umd.cjs", 11 | "types": "./dist/index.d.ts", 12 | "scripts": { 13 | "build": "tsc && vite build" 14 | }, 15 | "repository": "https://github.com/fluencelabs/fluence-js", 16 | "author": "Fluence DAO", 17 | "license": "Apache-2.0", 18 | "keywords": [], 19 | "devDependencies": { 20 | "@rollup/plugin-inject": "5.0.3", 21 | "@types/node": "20.4.5", 22 | "node-stdlib-browser": "1.2.0", 23 | "vite": "4.4.11", 24 | "vitest": "0.34.6" 25 | }, 26 | "dependencies": { 27 | "@fluencelabs/marine-js": "0.13.0", 28 | "observable-fns": "0.6.1", 29 | "@fluencelabs/threads": "^2.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/marine-worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"], 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/marine-worker/vite.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Fluence DAO Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { PluginOption } from "vite"; 18 | import { dirname, resolve } from "path"; 19 | import { createRequire } from "module"; 20 | import inject from "@rollup/plugin-inject"; 21 | import { fileURLToPath } from "url"; 22 | 23 | const require = createRequire(import.meta.url); 24 | const esbuildShim = require.resolve("node-stdlib-browser/helpers/esbuild/shim"); 25 | 26 | export default { 27 | build: { 28 | lib: { 29 | entry: resolve(dirname(fileURLToPath(import.meta.url)), "src/index.ts"), 30 | name: "MarineWorker", 31 | }, 32 | outDir: "dist/browser", 33 | }, 34 | plugins: [ 35 | { 36 | // @ts-ignore 37 | ...inject({ 38 | global: [esbuildShim, "global"], 39 | process: [esbuildShim, "process"], 40 | Buffer: [esbuildShim, "Buffer"], 41 | }), 42 | enforce: "post", 43 | } as PluginOption, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.0.3](https://github.com/fluencelabs/js-client/compare/npm-aqua-compiler-v0.0.2...npm-aqua-compiler-v0.0.3) (2024-01-31) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **npm-aqua-compiler:** Support aquaDir inside the project's node_nodules ([#427](https://github.com/fluencelabs/js-client/issues/427)) ([514663a](https://github.com/fluencelabs/js-client/commit/514663a4fde716e216f3112277d4bef6370dfdae)) 9 | 10 | ## [0.0.2](https://github.com/fluencelabs/js-client/compare/npm-aqua-compiler-v0.0.1...npm-aqua-compiler-v0.0.2) (2023-12-15) 11 | 12 | 13 | ### Features 14 | 15 | * **npm-aqua-compiler:** create package ([#401](https://github.com/fluencelabs/js-client/issues/401)) ([d600811](https://github.com/fluencelabs/js-client/commit/d6008110cf0ecaf23a63cfef0bb3f786a6eb0937)) 16 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "@fluencelabs/npm-aqua-compiler", 4 | "version": "0.0.3", 5 | "description": "Tool for converting npm imports to aqua compiler input", 6 | "types": "./dist/imports.d.ts", 7 | "exports": { 8 | ".": "./dist/imports.js" 9 | }, 10 | "scripts": { 11 | "test": "npm i --prefix ./test/transitive-deps/project && vitest run", 12 | "build": "tsc" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "keywords": [], 18 | "author": "Fluence DAO", 19 | "license": "Apache-2.0", 20 | "dependencies": { 21 | "@npmcli/arborist": "^7.2.1", 22 | "treeverse": "3.0.0" 23 | }, 24 | "devDependencies": { 25 | "@types/npmcli__arborist": "5.6.5", 26 | "@types/treeverse": "3.0.4", 27 | "vitest": "0.34.6" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/A-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/npm-aqua-compiler/test/transitive-deps/A-0.1.0.tgz -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/B-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/npm-aqua-compiler/test/transitive-deps/B-0.1.0.tgz -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/C-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/npm-aqua-compiler/test/transitive-deps/C-0.1.0.tgz -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/C-0.2.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/npm-aqua-compiler/test/transitive-deps/C-0.2.0.tgz -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/D-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/npm-aqua-compiler/test/transitive-deps/D-0.1.0.tgz -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/D-0.2.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluencelabs/js-client/e712d88019bc2be5f7517d236b39449c47dbffb5/packages/core/npm-aqua-compiler/test/transitive-deps/D-0.2.0.tgz -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/empty-project/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty-project", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "empty-project", 9 | "version": "0.1.0" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/empty-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty-project", 3 | "version": "0.1.0", 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/project/main.aqua: -------------------------------------------------------------------------------- 1 | use "B.aqua" 2 | 3 | func versionAC() -> string: 4 | <- A.versionC() 5 | 6 | func versionBC() -> string: 7 | <- B.versionC() 8 | 9 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/project/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "project", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "A": "file:../A-0.1.0.tgz", 12 | "B": "file:../B-0.1.0.tgz" 13 | } 14 | }, 15 | "node_modules/A": { 16 | "version": "0.1.0", 17 | "resolved": "file:../A-0.1.0.tgz", 18 | "integrity": "sha512-H0nhbQVQxPm3VwXYiePLJ0oyHa2FxPNNPjOcTdz3YWvIoE0/dZFJ1yrqig7fkrETYEYfLuVJaN0yg1BX/HAScg==", 19 | "dependencies": { 20 | "C": "file:./C-0.2.0.tgz", 21 | "D": "file:./D-0.1.0.tgz" 22 | } 23 | }, 24 | "node_modules/B": { 25 | "version": "0.1.0", 26 | "resolved": "file:../B-0.1.0.tgz", 27 | "integrity": "sha512-u6n6V5KlxIN/GwRQt82gZQAPwYi0OzqQ2wr8ufmygreLK3fPIfO49f13qagbGXaYiRxN9effXaPqZlMIyTygng==", 28 | "dependencies": { 29 | "C": "file:./C-0.1.0.tgz", 30 | "D": "file:./D-0.2.0.tgz" 31 | } 32 | }, 33 | "node_modules/B/node_modules/C": { 34 | "version": "0.1.0", 35 | "resolved": "file:../C-0.1.0.tgz", 36 | "integrity": "sha512-zvzWgHLm+ptWwysP+dJItnogVSca/jvHegWmwi6NmmHFO/wTqlGrMPnC2dEkpXDJBU4X1bUjevFh0q3Xe9e0MA==", 37 | "dependencies": { 38 | "D": "file:./D-0.1.0.tgz" 39 | } 40 | }, 41 | "node_modules/B/node_modules/C/node_modules/D": { 42 | "version": "0.1.0", 43 | "resolved": "file:../D-0.1.0.tgz", 44 | "integrity": "sha512-1rlKmuyzHSGTt9tBhEBY3j7gZvMBg0LnZMogZSucmX4gww4l0+HPQwBIPjJpqOspE2ND8PcLymQoiw06xWXn0g==" 45 | }, 46 | "node_modules/B/node_modules/D": { 47 | "version": "0.2.0", 48 | "resolved": "file:../D-0.2.0.tgz", 49 | "integrity": "sha512-7h1TUU8j60q6BZ0Wq/xDZOUf6iS0S4SgL/lgXOaiyxN76q7ld8Rx/qIxtGKmrWh65v5cjvAG5asbMEkXb6DuYQ==" 50 | }, 51 | "node_modules/C": { 52 | "version": "0.2.0", 53 | "resolved": "file:../C-0.2.0.tgz", 54 | "integrity": "sha512-uNqb8p69kuombZsb3xI/ygeL94WHpwkGR9/GRWgdg+01iKGsRMaZgL5up0UG7D/9DW7NQBozZG8ITPQ8DLgwSQ==", 55 | "dependencies": { 56 | "D": "file:./D-0.2.0.tgz" 57 | } 58 | }, 59 | "node_modules/C/node_modules/D": { 60 | "version": "0.2.0", 61 | "resolved": "file:../D-0.2.0.tgz", 62 | "integrity": "sha512-7h1TUU8j60q6BZ0Wq/xDZOUf6iS0S4SgL/lgXOaiyxN76q7ld8Rx/qIxtGKmrWh65v5cjvAG5asbMEkXb6DuYQ==" 63 | }, 64 | "node_modules/D": { 65 | "version": "0.1.0", 66 | "resolved": "file:../D-0.1.0.tgz", 67 | "integrity": "sha512-1rlKmuyzHSGTt9tBhEBY3j7gZvMBg0LnZMogZSucmX4gww4l0+HPQwBIPjJpqOspE2ND8PcLymQoiw06xWXn0g==" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/test/transitive-deps/project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "A": "file:../A-0.1.0.tgz", 6 | "B": "file:../B-0.1.0.tgz" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/npm-aqua-compiler/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules", "dist", "src/**/__test__"] 9 | } 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/core/*" 3 | - "packages/@tests/aqua" 4 | - "packages/@tests/smoke/*" 5 | - "packages/@tests/test-utils" 6 | -------------------------------------------------------------------------------- /reset.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import "@total-typescript/ts-reset"; 18 | -------------------------------------------------------------------------------- /resources/license-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Fluence DAO 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["packages"], 4 | "exclude": ["node_modules", "dist", "build"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/strictest/tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["es2023", "dom"], 5 | "outDir": "./dist/", 6 | "target": "ESNext", 7 | "module": "ESNext", 8 | "esModuleInterop": true, 9 | "declaration": true, 10 | "moduleResolution": "nodenext" 11 | }, 12 | "files": ["reset.d.ts"] 13 | } 14 | --------------------------------------------------------------------------------