Compare commits

..

54 Commits

Author SHA1 Message Date
fluencebot
25faa5af30
chore: release main (#439)
* chore: release main

* chore: Regenerate pnpm lock file
2024-02-24 02:07:03 +07:00
Akim
702ad605a8
fix(deps)!: Update avm to 0.62 (#438)
* Update avm

* Update nox image

* Fix lint

* Bump marine

* fix lint

* update tag
2024-02-24 01:37:00 +07:00
Akim
3b1371f968
chore: Rename master to main (#437)
Update branch
2024-02-23 17:09:25 +07:00
fluencebot
b269cf4100
chore: release master (#436) 2024-02-23 02:59:05 +02:00
Anatolios Laskaris
6b934b4bcb
chore: Revert "chore: release master (#434)" (#435)
Revert "chore: release master (#434)"

This reverts commit 31d82047a7e6aed6f432a34695db855bd8fd6259.
2024-02-23 02:57:23 +02:00
fluencebot
31d82047a7
chore: release master (#434) 2024-02-23 02:55:29 +02:00
Anatolios Laskaris
e8417d069a
fix: Rename testnet to dar (#433)
Rename testnet to dar
2024-02-23 02:54:43 +02:00
fluencebot
0edea02701
chore: release master (#432) 2024-02-09 10:50:35 +00:00
Akim
d7070fd71e
fix(js-client): Handle null as user input value (#431)
Handle null as user input value
2024-02-09 17:19:56 +07:00
fluencebot
0417ba410a
chore: release master (#430) 2024-02-09 07:57:18 +07:00
Akim
2b1d0f7f05
fix(js-client): Improve logging of conversion API (#429)
* Improve logging of conversion API

* Fix arg name
2024-02-09 07:49:33 +07:00
fluencebot
00db991332
chore: release master (#428) 2024-02-01 09:10:04 +00:00
Akim
514663a4fd
fix(npm-aqua-compiler): Support aquaDir inside the project's node_nodules (#427)
When aqua dir inside the project's node_nodules dir, return only the subtree based on that internal path
2024-01-31 13:50:53 +00:00
Akim
fa38328fdd
fix: Dial interval (#421)
Fix dial interval
2024-01-31 14:32:37 +01:00
fluencebot
628e60173b
chore: release master (#426) 2024-01-31 11:01:41 +00:00
Ivan Boldyrev
8ac029b6d3
feat(js-client)!: Multiformat MsgPack for particle data (#422)
* feat(particle)!: Multiformat MsgPack for particle data

* Fix types

* fix(ci): use nox with msgpack protocol

* fix(avm): avm 0.59.0

* Fix uint64

* Fix

* fix(ci): enable nox debug logs

* Fix commonJS import

* Revert "fix(ci): enable nox debug logs"

This reverts commit ce5bc2e26346ceaead466d2ceaeb25d22a75fe49.

---------

Co-authored-by: Akim Mamedov <akim99999999@gmail.com>
Co-authored-by: folex <0xdxdy@gmail.com>
Co-authored-by: Akim <59872966+akim-bow@users.noreply.github.com>
2024-01-30 00:20:59 +07:00
fluencebot
fe661dbd2c
chore: release master (#424) 2024-01-26 15:27:08 -03:00
renovate[bot]
e21ecc1ede
fix(deps): update dependency @fluencelabs/avm to v0.59.0 (#423)
* fix(deps): update dependency @fluencelabs/avm to v0.59.0

* fix(ci): use nox with avm 0.59

* fix(ci): use nox @ unstable

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: folex <0xdxdy@gmail.com>
2024-01-26 15:23:15 -03:00
fluencebot
6f0df6425b
chore: release master (#409) 2024-01-19 09:36:44 +00:00
Akim
a8a14735b3
fix: Update libp2p deps (#419)
* Update libp2p packages

* Try another libp2p version

* Add test resolutions

* Bump ver

* remove override

* redo removing override

* fix

* Fix

* Fix

* Fix

* Fix

* Deny connections from internal nox network

* Fix eslint

* Fix review
2024-01-19 09:25:12 +00:00
Akim
5696e3beba
fix(js-client): Improve logging (#418)
* Improve logging

* Improve further connection logs
2024-01-17 18:30:10 +07:00
Akim
15f96dc5f7
feat(js-client)!: Remove getter for secret key (#416)
* Remove secret key

* Node -> Relay

* Fix doc
2024-01-12 08:25:45 +00:00
Akim
4d90414190
fix(js-client): Remove union with undefined of methods for getting random peer (#417)
Fix types
2024-01-11 22:20:08 +07:00
renovate[bot]
5d7ae85e58
fix(deps)!: update dependency @fluencelabs/avm to v0.55.0 (#407)
fix(deps): update dependency @fluencelabs/avm to v0.55.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-28 20:50:47 +00:00
Akim
f5425b4746
fix: Enable async loading of all dependency resources (#408)
* Async loading

* Fix lint
2023-12-29 03:33:28 +07:00
Akim Mamedov
1e42e58a83 Fix web server url patterns 2023-12-19 18:46:41 +07:00
Akim
165e96b702
fix: Install before build (#406)
* Try to fix CI

* Try to fix CI
2023-12-19 17:46:11 +07:00
Akim Mamedov
57f8a851eb JSON stringify errors 2023-12-18 22:36:36 +07:00
Akim Mamedov
61964ccb1e Attempt to improve error logging 2023-12-18 22:27:41 +07:00
Akim Mamedov
fd23160f81 Fix logging for web tests 2023-12-18 22:11:15 +07:00
Akim
05180c1020
fix(e2e): Fix nox multiaddr in an attempt to stabilize tests (#405)
* Adjust nox external addresses

* Revert deleting aurora entry
2023-12-18 21:17:18 +07:00
fluencebot
cea65efc1a
chore: release master (#404)
* chore: release master

* chore: Regenerate pnpm lock file
2023-12-16 01:40:43 +07:00
Akim Mamedov
3610a6cca1 Update manifest again 2023-12-16 01:30:02 +07:00
Akim Mamedov
042431238a Change released version 2023-12-16 01:26:58 +07:00
Akim
86a73027e5
feat(js-client): Add fire-and-forget flag [DXJ-562] (#400)
* Add fire-and-forget flag

* Fix test

* Store particle behavior in queue item

* Fix tests
2023-12-16 01:21:31 +07:00
Akim
9b629eef2e
fix(js-client): Remove log truncation. (#403)
Remove log truncation.
Introducing additional log level
2023-12-15 17:20:28 +00:00
Akim
d6008110cf
feat(npm-aqua-compiler): create package (#401)
* Add npm-aqua-compiler package

* Release new package

* Remove noUncheckedIndexedAccess from tsconfig.json

* Fix a test script

* Fix length checks

* Fix

* Update error description

* Try to choose a nicer err message

* New import format and API

* Fix error message

* Improve test

* Don't add empty string key when globalImports prop is empty

* Fix exports
2023-12-15 23:14:07 +07:00
fluencebot
ac407c204d
chore: release master (#394) 2023-12-06 15:26:51 +00:00
Akim
1578b791ac
feat: Support instance context [fixes DXJ-541] (#392)
* Support context

* Update doc

* Refactor

* Cover by doc

* Fix lint
2023-12-06 16:03:20 +01:00
Akim
44eb1493b3
feat(js-client): Update libp2p ecosystem [fixes DXJ-551] (#393)
* Update libp2p ecosystem
2023-12-06 10:52:28 +00:00
fluencebot
bf0ed95dff
chore: release master (#390) 2023-11-24 02:51:13 +07:00
Akim Mamedov
fdd0ca0ea2 feat: Additional export from js-client 2023-11-24 02:36:12 +07:00
Akim Mamedov
8dd30ea35b Additional export from js-client 2023-11-24 02:29:46 +07:00
fluencebot
59e852878c
chore: release master (#389) 2023-11-24 02:17:01 +07:00
Akim
04c278b783
feat!: Force release of interfaces (#388)
Force release of interfaces
2023-11-24 02:14:28 +07:00
fluencebot
65a9e44aac
chore: release master (#387) 2023-11-24 00:53:01 +07:00
Akim
b5a6296225
feat: Temporarily expose internal js client API (#386)
Expose
2023-11-24 00:49:12 +07:00
fluencebot
5e917a650f
chore: release master (#379)
* chore: release master

* chore: Regenerate pnpm lock file
2023-11-23 17:53:51 +07:00
Akim
98462bfdf6
chore: Review fixes at #378 (#383)
* Review fixes

* remove logs

* Fixes

* Todo to remove prefix later

* Refactor service signatures

* Fixes

* Update lock file

* Fix lockfile

* Update deps

* More fixes and renames

* Fix compiler

* Peer refactoring and cutting onConnectionChange API

* Revert deleted API
2023-11-23 04:18:10 +07:00
Akim
f4a550dd22
feat(js-client)!: Segregation of responsibility between js-client packages [fixes DXJ-525] (#378)
Schema validation in js-client
2023-11-19 09:04:10 +07:00
fluencebot
638da47bc2
chore: release master (#377) 2023-11-16 00:29:20 +07:00
Akim
f5e9923974
fix(js-client): Fix CDN flow and move it in modules [fixes DXJ-527] (#376)
* Use type module in html

* Fix autocommit

* Try fix template

* Try fix template

* Try fix template

* Try fix template

* Try fix template

* Fix test

* Update packages/@tests/smoke/web/public/index.html

Co-authored-by: shamsartem <shamsartem@gmail.com>

* Minify es bundle

* Remove autocommit

* Fix lint

* Remove minimal

* Change nox version

* Change nox version

* Fix CDN

---------

Co-authored-by: shamsartem <shamsartem@gmail.com>
2023-11-10 21:29:49 +07:00
fluencebot
1803d83ce7
chore: release master (#375)
* chore: release master

* chore: Regenerate pnpm lock file
2023-11-06 21:03:27 +07:00
Akim
b460491fbd
fix: JS-client bugs and tech debt [fixes DXJ-520] (#374)
Fix various bugs and a pair of TODO's
2023-11-06 11:42:24 +01:00
143 changed files with 10869 additions and 16236 deletions

View File

@ -1,4 +1,5 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
@ -20,13 +21,7 @@
],
"ignorePatterns": ["**/node_modules/", "**/dist/", "**/build/", "**/public/"],
"rules": {
"eqeqeq": [
"error",
"always",
{
"null": "ignore"
}
],
"eqeqeq": ["error", "always"],
"no-console": ["error"],
"arrow-body-style": ["error", "always"],
"no-empty": [

View File

@ -44,8 +44,8 @@ services:
- -w=9991
- -x=10.50.10.10
- --external-maddrs
- /dns4/nox-1/tcp/7771
- /dns4/nox-1/tcp/9991/ws
- /ip4/127.0.0.1/tcp/7771
- /ip4/127.0.0.1/tcp/9991/ws
- --allow-private-ips
- --local
# - --bootstraps=/dns/nox-1/tcp/7771
@ -75,8 +75,8 @@ services:
- -w=9992
- -x=10.50.10.20
- --external-maddrs
- /dns4/nox-2/tcp/7772
- /dns4/nox-2/tcp/9992/ws
- /ip4/127.0.0.1/tcp/7772
- /ip4/127.0.0.1/tcp/9992/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWQdpukY3p2DhDfUfDgphAqsGu5ZUrmQ4mcHSGrRag6gQK
@ -105,8 +105,8 @@ services:
- -w=9993
- -x=10.50.10.30
- --external-maddrs
- /dns4/nox-3/tcp/7773
- /dns4/nox-3/tcp/9993/ws
- /ip4/127.0.0.1/tcp/7773
- /ip4/127.0.0.1/tcp/9993/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWRT8V5awYdEZm6aAV9HWweCEbhWd7df4wehqHZXAB7yMZ
@ -135,8 +135,8 @@ services:
- -w=9994
- -x=10.50.10.40
- --external-maddrs
- /dns4/nox-4/tcp/7774
- /dns4/nox-4/tcp/9994/ws
- /ip4/127.0.0.1/tcp/7774
- /ip4/127.0.0.1/tcp/9994/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWBzLSu9RL7wLP6oUowzCbkCj2AGBSXkHSJKuq4wwTfwof
@ -165,8 +165,8 @@ services:
- -w=9995
- -x=10.50.10.50
- --external-maddrs
- /dns4/nox-5/tcp/7775
- /dns4/nox-5/tcp/9995/ws
- /ip4/127.0.0.1/tcp/7775
- /ip4/127.0.0.1/tcp/9995/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWBf6hFgrnXwHkBnwPGMysP3b1NJe5HGtAWPYfwmQ2MBiU
@ -196,8 +196,8 @@ services:
- --bootstraps=/dns/nox-1/tcp/7771
- -x=10.50.10.60
- --external-maddrs
- /dns4/nox-6/tcp/7776
- /dns4/nox-6/tcp/9996/ws
- /ip4/127.0.0.1/tcp/7776
- /ip4/127.0.0.1/tcp/9996/ws
- --allow-private-ips
# 12D3KooWPisGn7JhooWhggndz25WM7vQ2JmA121EV8jUDQ5xMovJ
- -k=5Qh8bB1sF28uLPwr3HTvEksCeC6mAWQvebCfcgv9y6j4qKwSzNKm2tzLUg4nACUEo2KZpBw11gNCnwaAdM7o1pEn

View File

@ -11,6 +11,8 @@
"packages/core/js-client": {},
"packages/core/js-client-isomorphic": {},
"packages/core/marine-worker": {},
"packages/core/aqua-to-js": {}
"packages/core/aqua-to-js": {},
"packages/core/interfaces": {},
"packages/core/npm-aqua-compiler": {}
}
}

View File

@ -1,6 +1,8 @@
{
"packages/core/js-client": "0.4.2",
"packages/core/marine-worker": "0.4.1",
"packages/core/aqua-to-js": "0.2.0",
"packages/core/js-client-isomorphic": "0.2.1"
"packages/core/js-client": "0.9.0",
"packages/core/marine-worker": "0.6.0",
"packages/core/aqua-to-js": "0.3.13",
"packages/core/js-client-isomorphic": "0.6.0",
"packages/core/interfaces": "0.12.0",
"packages/core/npm-aqua-compiler": "0.0.3"
}

View File

@ -15,7 +15,7 @@ on:
- "reopened"
push:
branches:
- "master"
- "main"
paths-ignore:
- "**.md"
- ".github/**"
@ -43,7 +43,7 @@ jobs:
uses: fluencelabs/aqua/.github/workflows/tests.yml@main
with:
js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}"
nox-image: "fluencelabs/nox:unstable_minimal"
nox-image: "docker.fluence.dev/nox:renovate-avm_4905_1"
flox:
needs:
- js-client
@ -51,4 +51,4 @@ jobs:
uses: fluencelabs/flox/.github/workflows/tests.yml@main
with:
js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}"
nox-image: "fluencelabs/nox:unstable_minimal"
nox-image: "docker.fluence.dev/nox:renovate-avm_4905_1"

View File

@ -3,7 +3,7 @@ name: "release-please"
on:
push:
branches:
- "master"
- "main"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"

View File

@ -10,7 +10,7 @@ on:
- "!.github/workflows/snapshot.yml"
push:
branches:
- "master"
- "main"
paths-ignore:
- "**.md"
- ".github/**"
@ -28,4 +28,4 @@ jobs:
uses: ./.github/workflows/tests.yml
with:
ref: ${{ github.ref }}
nox-image: "fluencelabs/nox:unstable_minimal"
nox-image: "docker.fluence.dev/nox:renovate-avm_4905_1"

View File

@ -14,7 +14,7 @@ on:
ref:
description: "git ref to checkout to"
type: string
default: "master"
default: "main"
outputs:
js-client-snapshots:
description: "js-client snapshots"

View File

@ -6,7 +6,7 @@ on:
nox-image:
description: "nox image tag"
type: string
default: "fluencelabs/nox:minimal_0.2.9"
default: "fluencelabs/nox:0.4.2"
avm-version:
description: "@fluencelabs/avm version"
type: string
@ -18,7 +18,7 @@ on:
ref:
description: "git ref to checkout to"
type: string
default: "master"
default: "main"
env:
NOX_IMAGE: "${{ inputs.nox-image }}"
@ -34,6 +34,9 @@ jobs:
contents: read
id-token: write
env:
LATEST_NODE_VERSION: 20.x
strategy:
matrix:
node-version:
@ -88,10 +91,6 @@ jobs:
registry-url: "https://npm.fluence.dev"
cache: "pnpm"
- run: pnpm -r i
- run: pnpm -r build
- run: pnpm lint-check
- name: Override dependencies
uses: fluencelabs/github-actions/pnpm-set-dependency@main
with:
@ -102,6 +101,11 @@ jobs:
}
- run: pnpm -r --no-frozen-lockfile i
- run: pnpm -r build
- name: Lint code
run: pnpm lint-check
- run: pnpm -r test
- name: Dump container logs

4
.gitignore vendored
View File

@ -6,6 +6,6 @@
node_modules/
# Build directory
**/dist
**/public
dist/
public/
.DS_Store

View File

@ -2,11 +2,15 @@
.eslintcache
pnpm-lock.yaml
**/node_modules
**/dist
**/build
**/public
node_modules
dist
build
public
**/CHANGELOG.md
# TODO: remove after pnpm-set-deps will add \n symbol at the end of these files
**/package.json
packages/core/js-client/src/versions.ts
packages/core/js-client-isomorphic/src/versions.ts
__snapshots__
packages/@tests/aqua/src/_aqua/**

View File

@ -25,7 +25,7 @@ This is the Javascript client for the [Fluence](https://fluence.network) network
### HTML page
Add a script tag with the JS Client bundle to your `index.html`. The easiest way to do this is using a CDN (
Add a script tag with the JS Client module to your `index.html`. The easiest way to do this is using a CDN (
like [JSDELIVR](https://www.jsdelivr.com/) or [UNPKG](https://unpkg.com/)).
Here is an example using the JSDELIVR CDN:
@ -33,33 +33,22 @@ Here is an example using the JSDELIVR CDN:
```html
<head>
<title>Cool App</title>
<script src="https://cdn.jsdelivr.net/npm/@fluencelabs/js-client/dist/browser/index.min.js"></script>
<script type="module">
import {
Fluence,
randomKras,
} from "https://cdn.jsdelivr.net/npm/@fluencelabs/js-client/dist/browser/index.min.js";
Fluence.connect(randomKras());
</script>
</head>
```
If you cannot or don't want to use a CDN, feel free to get the script directly from
the [npm package](https://www.npmjs.com/package/@fluencelabs/js-client) and host it yourself. You can find the script in
the `/dist` directory of the package. (Note: this option means that developers understand what they are doing and know
the `/dist/browser` directory of the package. (Note: this option means that developers understand what they are doing and know
how to serve this file from their own web server.)
After importing JS-client to HTML page the client is available as `window.Fluence` variable.
To get a specific network you can peek at
```
https://cdn.jsdelivr.net/npm/@fluencelabs/js-client/dist/network.js
```
and hardcode selected network. So initialization would look like this
```javascript
// Passing 1 kras network config from ./dist/network.js above
window.Fluence.connect({
multiaddr:
"/dns4/0-kras.fluence.dev/tcp/9000/wss/p2p/12D3KooWSD5PToNiLQwKDXsu8JSysCwUt8BVUJEqCHcDe7P5h45e",
peerId: "12D3KooWSD5PToNiLQwKDXsu8JSysCwUt8BVUJEqCHcDe7P5h45e",
});
```
## Usage in an Application
Once you've added the client, you can compile [Aqua](https://github.com/fluencelabs/aqua) and run it in your application. To compile Aqua, use [Fluence CLI](https://github.com/fluencelabs/cli).
@ -171,6 +160,18 @@ localStorage.debug = "fluence:*";
In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will be default—only to show
messages logged by debug if the "Verbose" log level is enabled.
## Low level usage
JS client also has an API for low level interaction with AVM and Marine JS.
It could be handy in advanced scenarios when a user fetches AIR dynamically or generates AIR without default Aqua compiler.
`callAquaFunction` Allows to call aqua function without schema.
`registerService` Gives an ability to register service without schema. Passed `service` could be
- Plain object. In this case all function properties will be registered as AIR service functions.
- Class instance. All class methods without inherited ones will be registered as AIR service functions.
## Development
To hack on the Fluence JS Client itself, please refer to the [development page](./DEVELOPING.md).

2
ci.cjs
View File

@ -95,7 +95,7 @@ async function checkConsistency(file, versionsMap) {
for (const [name, versionInDep] of versionsMap) {
const check = (x, version) => {
if (version.includes("*")) {
if (version.includes("*") || version.includes("^")) {
return;
}

View File

@ -1,21 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
bundle/
dist
esm
types
# Dependency directories
node_modules/
jspm_packages/
.idea

View File

@ -0,0 +1,51 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { writeFile } from "fs/promises";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { compileFromPath } from "@fluencelabs/aqua-api";
import aquaToJs from "@fluencelabs/aqua-to-js";
const files = ["smoke_test", "finalize_particle"];
for (const file of files) {
const cr = await compileFromPath({
filePath: join(
dirname(fileURLToPath(import.meta.url)),
"_aqua",
file + ".aqua",
),
targetType: "air",
imports: [fileURLToPath(new URL("./node_modules", import.meta.url))],
});
if (cr.errors.length > 0) {
throw new Error(cr.errors.join("\n"));
}
const res = await aquaToJs(cr, "ts");
if (res == null) {
throw new Error("AquaToJs gave null value after compilation");
}
await writeFile(
fileURLToPath(new URL(join("src", "_aqua", file + ".ts"), import.meta.url)),
res.sources,
);
}

View File

@ -11,7 +11,7 @@
"type": "module",
"scripts": {
"build": "tsc",
"compile-aqua": "fluence aqua -i ./_aqua -o ./src/_aqua"
"compile-aqua": "node --loader ts-node/esm compile-aqua.ts"
},
"repository": "https://github.com/fluencelabs/fluence-js",
"author": "Fluence Labs",
@ -20,10 +20,12 @@
"base64-js": "1.5.1"
},
"devDependencies": {
"@fluencelabs/aqua-api": "0.13.0",
"@fluencelabs/aqua-lib": "0.6.0",
"@fluencelabs/cli": "0.7.2",
"@fluencelabs/js-client": "workspace:^",
"@fluencelabs/registry": "0.8.2",
"@fluencelabs/trust-graph": "3.1.2"
"@fluencelabs/aqua-to-js": "workspace:*",
"@fluencelabs/js-client": "workspace:*",
"@fluencelabs/registry": "0.9.0",
"@fluencelabs/trust-graph": "3.1.2",
"ts-node": "10.9.1"
}
}

View File

@ -2,71 +2,68 @@
// @ts-nocheck
/**
*
* This file is auto-generated. Do not edit manually: changes may be erased.
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* Aqua version: 0.12.0
* This file is generated using:
* @fluencelabs/aqua-api version: 0.12.4-main-cee4448-2196-1
* @fluencelabs/aqua-to-js version: 0.2.0
* If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues
*
*/
import type {
IFluenceClient as IFluenceClient$$,
CallParams as CallParams$$,
} from "@fluencelabs/js-client";
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$,
} from "@fluencelabs/js-client";
import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';
// Services
// Functions
export const test_script = `
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(xor
(xor
(call -relay- ("op" "noop") [])
(fail %last_error%)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 0])
)
)
`;
(xor
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(xor
(call -relay- ("op" "noop") [])
(fail :error:)
)
)
(call %init_peer_id% ("callbackSrv" "response") [])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export function test(config?: { ttl?: number }): Promise<void>;
export type TestParams = [config?: {ttl?: number}] | [peer: IFluenceClient$$, config?: {ttl?: number}];
export function test(
peer: IFluenceClient$$,
config?: { ttl?: number },
): Promise<void>;
export type TestResult = Promise<void>;
export function test(...args: any) {
return callFunction$$(
args,
{
functionName: "test",
arrow: {
tag: "arrow",
domain: {
tag: "labeledProduct",
fields: {},
export function test(...args: TestParams): TestResult {
return callFunction$$(
args,
{
"functionName": "test",
"arrow": {
"domain": {
"fields": {},
"tag": "labeledProduct"
},
codomain: {
tag: "nil",
"codomain": {
"tag": "nil"
},
},
names: {
relay: "-relay-",
getDataSrv: "getDataSrv",
callbackSrv: "callbackSrv",
responseSrv: "callbackSrv",
responseFnName: "response",
errorHandlingSrv: "errorHandlingSrv",
errorFnName: "error",
},
"tag": "arrow"
},
test_script,
);
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
test_script
);
}
/* eslint-enable */

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,7 @@
* limitations under the License.
*/
import { Fluence } from "@fluencelabs/js-client";
import type { ClientConfig } from "@fluencelabs/js-client";
import { fromByteArray } from "base64-js";
import { Fluence, type ClientConfig } from "@fluencelabs/js-client";
import { test as particleTest } from "./_aqua/finalize_particle.js";
import {
@ -57,10 +55,6 @@ export type TestResult =
export const runTest = async (): Promise<TestResult> => {
try {
Fluence.onConnectionStateChange((state) => {
console.info("connection state changed: ", state);
});
console.log("connecting to Fluence Network...");
console.log("multiaddr: ", relay.multiaddr);
@ -83,7 +77,6 @@ export const runTest = async (): Promise<TestResult> => {
const client = Fluence.getClient();
console.log("my peer id: ", client.getPeerId());
console.log("my sk id: ", fromByteArray(client.getPeerSecretKey()));
console.log("running hello test...");
const hello = await helloTest();
@ -91,11 +84,11 @@ export const runTest = async (): Promise<TestResult> => {
console.log("running marine test...");
const marine = await marineTest(wasm);
console.log("marine test finished, result: ", marine);
console.log("running particle test...");
await particleTest();
console.log("marine test finished, result: ", marine);
await particleTest();
const returnVal = {
hello,

View File

@ -1,21 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
bundle/
dist
esm
types
# Dependency directories
node_modules/
jspm_packages/
.idea

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import "@fluencelabs/js-client";
import { runTest } from "@test/aqua_for_test";
await runTest();

View File

@ -1,25 +1 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
public/js-client.min.js
build/

View File

@ -7,8 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<!-- Ideally we want to use 'async' here. Currently, it's not supported. -->
<script src="js-client.min.js"></script>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a

View File

@ -12,6 +12,18 @@ function App() {
setResult(res);
})
.catch((err) => {
if (err instanceof Error) {
console.log(
JSON.stringify({
name: err.name,
message: err.message,
stack: err.stack,
}),
);
} else {
console.log(JSON.stringify(err));
}
setResult({ type: "failure", error: err.toString() });
});
};

View File

@ -17,7 +17,7 @@ const publicPath = join(__dirname, "../build/");
const test = async () => {
const localServer = await startContentServer(port, publicPath);
await createSymlinkIfNotExists(CDN_PUBLIC_PATH, join(publicPath, "source"));
await createSymlinkIfNotExists(
JS_CLIENT_DEPS_PATH,
join(publicPath, "node_modules"),

View File

@ -1,21 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
dist
# Dependency directories
node_modules/
jspm_packages/
.idea
public/js-client.min.js

View File

@ -19,7 +19,7 @@
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"@fluencelabs/js-client": "workspace:*",
"@fluencelabs/js-client-isomorphic": "workspace:*",
"@test/test-utils": "workspace:*"
},
"devDependencies": {

View File

@ -16,8 +16,9 @@
<div id="res-placeholder"></div>
<script src="js-client.min.js"></script>
<script src="index.js"></script>
<!-- Importing js-client from local server that is used instead of the CDN -->
<script type="module" src="js-client.min.js"></script>
<script type="module" src="index.js"></script>
</main>
</body>

View File

@ -1,8 +1,8 @@
const fluence = globalThis.fluence;
import { Fluence, callAquaFunction, randomStage } from "./js-client.min.js";
const relay = {
multiaddr:
"/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR",
"/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR",
peerId: "12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR",
};
@ -74,18 +74,19 @@ const getRelayTime = () => {
const config = {};
const args = { relayPeerId: relay.peerId };
return fluence.callAquaFunction({
return callAquaFunction({
args,
def,
script,
config,
peer: fluence.defaultClient,
peer: Fluence.defaultClient,
fireAndForget: false
});
};
const main = async () => {
console.log("starting fluence...");
fluence.defaultClient = await fluence.clientFactory(relay, {
await Fluence.connect(relay, {
CDNUrl: "http://localhost:3000",
});
console.log("started fluence");
@ -95,7 +96,7 @@ const main = async () => {
console.log("got relay time, ", relayTime);
console.log("stopping fluence...");
await fluence.defaultClient.stop();
await Fluence.disconnect();
console.log("stopped fluence...");
return relayTime;
@ -109,5 +110,11 @@ btn.addEventListener("click", () => {
inner.id = "res";
inner.innerText = res;
document.getElementById("res-placeholder").appendChild(inner);
}).catch(err => {
if (err instanceof Error) {
console.log(JSON.stringify({ name: err.name, message: err.message, stack: err.stack }));
return;
}
console.log(JSON.stringify(err));
});
});

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
import assert from "node:assert";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
@ -45,6 +46,8 @@ const test = async () => {
const browser = await puppeteer.launch();
const page = (await browser.pages())[0];
assert(page);
page.on("console", (message) => {
console.log(`${message.type().toUpperCase()}: ${message.text()}`);
});
@ -77,7 +80,7 @@ const test = async () => {
await browser.close();
await stopServer(localServer);
if (content == null) {
if (content === null || content === undefined) {
throw new Error("smoke test failed!");
}
};

View File

@ -61,21 +61,16 @@ export const startContentServer = (
source: "/js-client.min.js",
destination: "/source/index.min.js",
},
// TODO:
// something like this
// {
// source: "/@fluencelabs/:name(\\w+)@:version([\\d.]+)/:path*",
// destination: "/deps/@fluencelabs/:name/:path",
// }
// not supported for some reason. Need to manually iterate over all possible paths
{
source: "/@fluencelabs/:name([\\w-]+)@:version([\\d.]+)/dist/:asset",
destination: "/node_modules/@fluencelabs/:name/dist/:asset",
source: "/@fluencelabs/:name([\\w-]+)@:version([\\w-.]+)/dist/:asset",
destination:
"/node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/:name/dist/:asset",
},
{
source:
"/@fluencelabs/:name([\\w-]+)@:version([\\d.]+)/dist/:prefix/:asset",
destination: "/node_modules/@fluencelabs/:name/dist/:prefix/:asset",
"/@fluencelabs/:name([\\w-]+)@:version([\\w-.]+)/dist/:prefix/:asset",
destination:
"/node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/:name/dist/:prefix/:asset",
},
],
headers: [

View File

@ -1,3 +1,3 @@
{
"ignorePatterns": ["src/**/__snapshots__/**/*"]
"ignorePatterns": ["src/**/__snapshots__/**/*", "src/**/*.js"]
}

View File

@ -18,6 +18,114 @@
* devDependencies
* @fluencelabs/js-client bumped to 0.2.1
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.5.1
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/interfaces bumped to 0.9.0
* @fluencelabs/js-client bumped to 0.5.2
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.5.3
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.5.4
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/interfaces bumped to 0.10.0
* @fluencelabs/js-client bumped to 0.6.0
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/interfaces bumped to 0.11.0
* @fluencelabs/js-client bumped to 0.7.0
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.8.0
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.8.1
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.8.2
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.8.3
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.8.4
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/interfaces bumped to 0.12.0
* @fluencelabs/js-client bumped to 0.9.0
## [0.3.5](https://github.com/fluencelabs/js-client/compare/aqua-to-js-v0.3.4...aqua-to-js-v0.3.5) (2023-12-15)
### Features
* **npm-aqua-compiler:** create package ([#401](https://github.com/fluencelabs/js-client/issues/401)) ([d600811](https://github.com/fluencelabs/js-client/commit/d6008110cf0ecaf23a63cfef0bb3f786a6eb0937))
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.5.5
## [0.3.0](https://github.com/fluencelabs/js-client/compare/aqua-to-js-v0.2.0...aqua-to-js-v0.3.0) (2023-11-22)
### ⚠ BREAKING CHANGES
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378))
### Features
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378)) ([f4a550d](https://github.com/fluencelabs/js-client/commit/f4a550dd226846dfc2ade1ccc35a286dc3be2fed))
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.5.0
## [0.2.0](https://github.com/fluencelabs/js-client/compare/aqua-to-js-v0.1.0...aqua-to-js-v0.2.0) (2023-10-25)

View File

@ -1,7 +1,7 @@
{
"name": "@fluencelabs/aqua-to-js",
"type": "module",
"version": "0.2.0",
"version": "0.3.13",
"description": "Tool for generating aqua wrapper",
"main": "dist/index.js",
"files": [
@ -18,12 +18,14 @@
"ts-pattern": "5.0.5"
},
"devDependencies": {
"@fluencelabs/aqua-api": "0.12.0",
"@fluencelabs/aqua-api": "0.13.0",
"@fluencelabs/aqua-lib": "0.7.3",
"@fluencelabs/interfaces": "workspace:*",
"@fluencelabs/registry": "0.8.7",
"@fluencelabs/js-client": "workspace:^",
"@fluencelabs/registry": "0.9.0",
"@fluencelabs/spell": "0.5.20",
"@fluencelabs/trust-graph": "0.4.7",
"vitest": "0.34.6"
"vitest": "0.34.6",
"zod": "3.22.4"
}
}

View File

@ -14,13 +14,13 @@
* limitations under the License.
*/
import { ArrowWithoutCallbacks, NonArrowType } from "@fluencelabs/interfaces";
import { ArrowType, NonArrowType } from "@fluencelabs/interfaces";
import { match, P } from "ts-pattern";
import { getFuncArgs } from "./utils.js";
export function genTypeName(
t: NonArrowType | ArrowWithoutCallbacks,
t: NonArrowType | ArrowType,
name: string,
): readonly [string | undefined, string] {
const genType = typeToTs(t);
@ -35,7 +35,7 @@ export function genTypeName(
const args =
item.tag === "labeledProduct" ? Object.values(item.fields) : item.items;
if (args.length === 1) {
if (args.length === 1 && "0" in args) {
return genTypeName(args[0], name);
}
@ -46,7 +46,7 @@ export function genTypeName(
});
}
export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
export function typeToTs(t: NonArrowType | ArrowType): string {
return match(t)
.with({ tag: "nil" }, () => {
return "null";
@ -112,7 +112,7 @@ export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
const retType =
codomain.tag === "nil"
? "void"
: codomain.items.length === 1
: codomain.items.length === 1 && "0" in codomain.items
? typeToTs(codomain.items[0])
: typeToTs(codomain);
@ -120,16 +120,7 @@ export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
return [name, typeToTs(type)];
});
const generic =
args.length === 0
? "null"
: args
.map(([name]) => {
return `'${name}'`;
})
.join(" | ");
args.push(["callParams", `CallParams$$<${generic}>`]);
args.push(["callParams", `ParticleContext$$`]);
const funcArgs = args
.map(([name, type]) => {

View File

@ -0,0 +1,67 @@
/* eslint-disable */
// @ts-nocheck
/**
*
* This file is generated using:
* @fluencelabs/aqua-api version: 0.0.0
* @fluencelabs/aqua-to-js version: 0.0.0
* If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues
*
*/
import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';
// Services
export interface SrvDef {
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; }>;
list: (callParams: ParticleContext$$) => string[] | Promise<string[]>;
remove: (service_id: string, callParams: ParticleContext$$) => { error: string | null; success: boolean; } | Promise<{ error: string | null; success: boolean; }>;
}
export function registerSrv(service: SrvDef): void;
export function registerSrv(serviceId: string, service: SrvDef): void;
export function registerSrv(peer: IFluenceClient$$, service: SrvDef): void;
export function registerSrv(peer: IFluenceClient$$, serviceId: string, service: SrvDef): void;
export interface CalcServiceDef {
divide: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
clear_state: (callParams: ParticleContext$$) => void | Promise<void>;
test_logs: (callParams: ParticleContext$$) => void | Promise<void>;
multiply: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
add: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
state: (callParams: ParticleContext$$) => number | Promise<number>;
subtract: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
}
export function registerCalcService(serviceId: string, service: CalcServiceDef): void;
export function registerCalcService(peer: IFluenceClient$$, serviceId: string, service: CalcServiceDef): void;
export interface HelloWorldDef {
hello: (str: string, callParams: ParticleContext$$) => string | Promise<string>;
}
export function registerHelloWorld(service: HelloWorldDef): void;
export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void;
export function registerHelloWorld(peer: IFluenceClient$$, service: HelloWorldDef): void;
export function registerHelloWorld(peer: IFluenceClient$$, serviceId: string, service: HelloWorldDef): void;
// Functions
export type ResourceTestResultType = [string | null, string[]]
export type ResourceTestParams = [label: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, label: string, config?: {ttl?: number}];
export type ResourceTestResult = Promise<ResourceTestResultType>;
export type HelloTestParams = [config?: {ttl?: number}] | [peer: IFluenceClient$$, config?: {ttl?: number}];
export type HelloTestResult = Promise<string>;
export type Demo_calculationParams = [service_id: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, service_id: string, config?: {ttl?: number}];
export type Demo_calculationResult = Promise<number>;
export type MarineTestParams = [wasm64: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, wasm64: string, config?: {ttl?: number}];
export type MarineTestResult = Promise<number>;

View File

@ -0,0 +1,900 @@
/* eslint-disable */
// @ts-nocheck
/**
*
* This file is generated using:
* @fluencelabs/aqua-api version: 0.0.0
* @fluencelabs/aqua-to-js version: 0.0.0
* If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues
*
*/
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';
// Services
export function registerSrv(...args) {
registerService$$(
args,
{
"defaultServiceId": "single_module_srv",
"functions": {
"fields": {
"create": {
"domain": {
"fields": {
"wasm_b64_content": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "ServiceCreationResult",
"fields": {
"error": {
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
"service_id": {
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
"success": {
"name": "bool",
"tag": "scalar"
}
},
"tag": "struct"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"list": {
"domain": {
"tag": "nil"
},
"codomain": {
"items": [
{
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "array"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"remove": {
"domain": {
"fields": {
"service_id": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "RemoveResult",
"fields": {
"error": {
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
"success": {
"name": "bool",
"tag": "scalar"
}
},
"tag": "struct"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
}
},
"tag": "labeledProduct"
}
}
);
}
export function registerCalcService(...args) {
registerService$$(
args,
{
"functions": {
"fields": {
"divide": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"clear_state": {
"domain": {
"tag": "nil"
},
"codomain": {
"tag": "nil"
},
"tag": "arrow"
},
"test_logs": {
"domain": {
"tag": "nil"
},
"codomain": {
"tag": "nil"
},
"tag": "arrow"
},
"multiply": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"add": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"state": {
"domain": {
"tag": "nil"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"subtract": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
}
},
"tag": "labeledProduct"
}
}
);
}
export function registerHelloWorld(...args) {
registerService$$(
args,
{
"defaultServiceId": "hello-world",
"functions": {
"fields": {
"hello": {
"domain": {
"fields": {
"str": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "string",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
}
},
"tag": "labeledProduct"
}
}
);
}
// Functions
export const resourceTest_script = `
(xor
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "label") [] -label-arg-)
)
(new $resource_id
(seq
(seq
(seq
(call %init_peer_id% ("peer" "timestamp_sec") [] ret)
(xor
(seq
(seq
(call -relay- ("registry" "get_key_bytes") [-label-arg- [] ret [] ""] ret-0)
(xor
(call %init_peer_id% ("sig" "sign") [ret-0] ret-1)
(fail :error:)
)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(match ret-1.$.success false
(ap ret-1.$.error.[0] $error)
)
(seq
(ap :error: -if-error-)
(xor
(match :error:.$.error_code 10001
(new $successful
(seq
(seq
(seq
(seq
(seq
(seq
(ap ret-1.$.signature ret-1_flat)
(call -relay- ("registry" "get_key_id") [-label-arg- %init_peer_id%] ret-2)
)
(call -relay- ("op" "string_to_b58") [ret-2] ret-3)
)
(call -relay- ("kad" "neighborhood") [ret-3 [] []] ret-4)
)
(par
(fold ret-4 n-0
(par
(xor
(xor
(seq
(seq
(seq
(call n-0 ("peer" "timestamp_sec") [] ret-5)
(call n-0 ("trust-graph" "get_weight") [%init_peer_id% ret-5] ret-6)
)
(call n-0 ("registry" "register_key") [-label-arg- [] ret [] "" ret-1_flat.$.[0] ret-6 ret-5] ret-7)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(seq
(match ret-7.$.success true
(ap true $successful)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap :error: -if-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap ret-7.$.error $error)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(seq
(seq
(ap :error: -else-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap -else-error- -if-else-error-)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
(fail -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
)
)
)
)
)
(null)
)
(fail :error:)
)
(next n-0)
)
(never)
)
(null)
)
)
(new $status
(new $result-1
(seq
(seq
(seq
(par
(seq
(new $successful_test
(seq
(seq
(fold $successful successful_fold_var
(seq
(seq
(ap successful_fold_var $successful_test)
(canon -relay- $successful_test #successful_iter_canon)
)
(xor
(match #successful_iter_canon.length 1
(null)
)
(next successful_fold_var)
)
)
(never)
)
(canon -relay- $successful_test #successful_result_canon)
)
(ap #successful_result_canon successful_gate)
)
)
(ap "ok" $status)
)
(seq
(call -relay- ("peer" "timeout") [6000 "timeout"] ret-8)
(ap ret-8 $status)
)
)
(new $status_test
(seq
(seq
(fold $status status_fold_var
(seq
(seq
(ap status_fold_var $status_test)
(canon -relay- $status_test #status_iter_canon)
)
(xor
(match #status_iter_canon.length 1
(null)
)
(next status_fold_var)
)
)
(never)
)
(canon -relay- $status_test #status_result_canon)
)
(ap #status_result_canon status_gate)
)
)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(match status_gate.$.[0] "ok"
(ap true $result-1)
)
(seq
(ap :error: -if-error-)
(xor
(match :error:.$.error_code 10001
(ap false $result-1)
)
(seq
(seq
(ap :error: -else-error-)
(xor
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(ap -else-error- -if-else-error-)
)
)
(fail -if-else-error-)
)
)
)
)
)
)
)
)
(new $result-1_test
(seq
(seq
(fold $result-1 result-1_fold_var
(seq
(seq
(ap result-1_fold_var $result-1_test)
(canon -relay- $result-1_test #result-1_iter_canon)
)
(xor
(match #result-1_iter_canon.length 1
(null)
)
(next result-1_fold_var)
)
)
(never)
)
(canon -relay- $result-1_test #result-1_result_canon)
)
(ap #result-1_result_canon result-1_gate)
)
)
)
)
)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(match result-1_gate.$.[0] false
(ap "resource wasn't created: timeout exceeded" $error)
)
(seq
(ap :error: -if-error-)
(xor
(match :error:.$.error_code 10001
(ap ret-2 $resource_id)
)
(seq
(seq
(ap :error: -else-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap -else-error- -if-else-error-)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
(fail -if-else-error-)
)
)
)
)
)
)
)
)
)
)
(seq
(seq
(ap :error: -else-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap -else-error- -if-else-error-)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
(fail -if-else-error-)
)
)
)
)
)
)
)
)
(fail :error:)
)
)
(canon %init_peer_id% $resource_id #-resource_id-fix-0)
)
(ap #-resource_id-fix-0 -resource_id-flat-0)
)
)
)
(canon %init_peer_id% $error #error_canon)
)
(call %init_peer_id% ("callbackSrv" "response") [-resource_id-flat-0 #error_canon])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export function resourceTest(...args) {
return callFunction$$(
args,
{
"functionName": "resourceTest",
"arrow": {
"domain": {
"fields": {
"label": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
{
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "array"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
resourceTest_script
);
}
export const helloTest_script = `
(xor
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("hello-world" "hello") ["Fluence user"] ret)
)
(call %init_peer_id% ("callbackSrv" "response") [ret])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export function helloTest(...args) {
return callFunction$$(
args,
{
"functionName": "helloTest",
"arrow": {
"domain": {
"fields": {},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "string",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
helloTest_script
);
}
export const demo_calculation_script = `
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "service_id") [] -service_id-arg-)
)
(call %init_peer_id% (-service_id-arg- "test_logs") [])
)
(call %init_peer_id% (-service_id-arg- "add") [10] ret)
)
(call %init_peer_id% (-service_id-arg- "multiply") [5] ret-0)
)
(call %init_peer_id% (-service_id-arg- "subtract") [8] ret-1)
)
(call %init_peer_id% (-service_id-arg- "divide") [6] ret-2)
)
(call %init_peer_id% (-service_id-arg- "state") [] ret-3)
)
(call %init_peer_id% ("callbackSrv" "response") [ret-3])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export function demo_calculation(...args) {
return callFunction$$(
args,
{
"functionName": "demo_calculation",
"arrow": {
"domain": {
"fields": {
"service_id": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
demo_calculation_script
);
}
export const marineTest_script = `
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "wasm64") [] -wasm64-arg-)
)
(call %init_peer_id% ("single_module_srv" "create") [-wasm64-arg-] ret)
)
(call %init_peer_id% (ret.$.service_id.[0] "test_logs") [])
)
(call %init_peer_id% (ret.$.service_id.[0] "add") [10] ret-0)
)
(call %init_peer_id% (ret.$.service_id.[0] "multiply") [5] ret-1)
)
(call %init_peer_id% (ret.$.service_id.[0] "subtract") [8] ret-2)
)
(call %init_peer_id% (ret.$.service_id.[0] "divide") [6] ret-3)
)
(call %init_peer_id% (ret.$.service_id.[0] "state") [] ret-4)
)
(call %init_peer_id% ("callbackSrv" "response") [ret-4])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export function marineTest(...args) {
return callFunction$$(
args,
{
"functionName": "marineTest",
"arrow": {
"domain": {
"fields": {
"wasm64": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
marineTest_script
);
}

View File

@ -0,0 +1,938 @@
/* eslint-disable */
// @ts-nocheck
/**
*
* This file is generated using:
* @fluencelabs/aqua-api version: 0.0.0
* @fluencelabs/aqua-to-js version: 0.0.0
* If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues
*
*/
import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';
// Services
export interface SrvDef {
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; }>;
list: (callParams: ParticleContext$$) => string[] | Promise<string[]>;
remove: (service_id: string, callParams: ParticleContext$$) => { error: string | null; success: boolean; } | Promise<{ error: string | null; success: boolean; }>;
}
export function registerSrv(service: SrvDef): void;
export function registerSrv(serviceId: string, service: SrvDef): void;
export function registerSrv(peer: IFluenceClient$$, service: SrvDef): void;
export function registerSrv(peer: IFluenceClient$$, serviceId: string, service: SrvDef): void;
export function registerSrv(...args: any[]) {
registerService$$(
args,
{
"defaultServiceId": "single_module_srv",
"functions": {
"fields": {
"create": {
"domain": {
"fields": {
"wasm_b64_content": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "ServiceCreationResult",
"fields": {
"error": {
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
"service_id": {
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
"success": {
"name": "bool",
"tag": "scalar"
}
},
"tag": "struct"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"list": {
"domain": {
"tag": "nil"
},
"codomain": {
"items": [
{
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "array"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"remove": {
"domain": {
"fields": {
"service_id": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "RemoveResult",
"fields": {
"error": {
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
"success": {
"name": "bool",
"tag": "scalar"
}
},
"tag": "struct"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
}
},
"tag": "labeledProduct"
}
}
);
}
export interface CalcServiceDef {
divide: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
clear_state: (callParams: ParticleContext$$) => void | Promise<void>;
test_logs: (callParams: ParticleContext$$) => void | Promise<void>;
multiply: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
add: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
state: (callParams: ParticleContext$$) => number | Promise<number>;
subtract: (num: number, callParams: ParticleContext$$) => number | Promise<number>;
}
export function registerCalcService(serviceId: string, service: CalcServiceDef): void;
export function registerCalcService(peer: IFluenceClient$$, serviceId: string, service: CalcServiceDef): void;
export function registerCalcService(...args: any[]) {
registerService$$(
args,
{
"functions": {
"fields": {
"divide": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"clear_state": {
"domain": {
"tag": "nil"
},
"codomain": {
"tag": "nil"
},
"tag": "arrow"
},
"test_logs": {
"domain": {
"tag": "nil"
},
"codomain": {
"tag": "nil"
},
"tag": "arrow"
},
"multiply": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"add": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"state": {
"domain": {
"tag": "nil"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"subtract": {
"domain": {
"fields": {
"num": {
"name": "f64",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
}
},
"tag": "labeledProduct"
}
}
);
}
export interface HelloWorldDef {
hello: (str: string, callParams: ParticleContext$$) => string | Promise<string>;
}
export function registerHelloWorld(service: HelloWorldDef): void;
export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void;
export function registerHelloWorld(peer: IFluenceClient$$, service: HelloWorldDef): void;
export function registerHelloWorld(peer: IFluenceClient$$, serviceId: string, service: HelloWorldDef): void;
export function registerHelloWorld(...args: any[]) {
registerService$$(
args,
{
"defaultServiceId": "hello-world",
"functions": {
"fields": {
"hello": {
"domain": {
"fields": {
"str": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "string",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
}
},
"tag": "labeledProduct"
}
}
);
}
// Functions
export const resourceTest_script = `
(xor
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "label") [] -label-arg-)
)
(new $resource_id
(seq
(seq
(seq
(call %init_peer_id% ("peer" "timestamp_sec") [] ret)
(xor
(seq
(seq
(call -relay- ("registry" "get_key_bytes") [-label-arg- [] ret [] ""] ret-0)
(xor
(call %init_peer_id% ("sig" "sign") [ret-0] ret-1)
(fail :error:)
)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(match ret-1.$.success false
(ap ret-1.$.error.[0] $error)
)
(seq
(ap :error: -if-error-)
(xor
(match :error:.$.error_code 10001
(new $successful
(seq
(seq
(seq
(seq
(seq
(seq
(ap ret-1.$.signature ret-1_flat)
(call -relay- ("registry" "get_key_id") [-label-arg- %init_peer_id%] ret-2)
)
(call -relay- ("op" "string_to_b58") [ret-2] ret-3)
)
(call -relay- ("kad" "neighborhood") [ret-3 [] []] ret-4)
)
(par
(fold ret-4 n-0
(par
(xor
(xor
(seq
(seq
(seq
(call n-0 ("peer" "timestamp_sec") [] ret-5)
(call n-0 ("trust-graph" "get_weight") [%init_peer_id% ret-5] ret-6)
)
(call n-0 ("registry" "register_key") [-label-arg- [] ret [] "" ret-1_flat.$.[0] ret-6 ret-5] ret-7)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(seq
(match ret-7.$.success true
(ap true $successful)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap :error: -if-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap ret-7.$.error $error)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(seq
(seq
(ap :error: -else-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap -else-error- -if-else-error-)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
(fail -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
)
)
)
)
)
(null)
)
(fail :error:)
)
(next n-0)
)
(never)
)
(null)
)
)
(new $status
(new $result-1
(seq
(seq
(seq
(par
(seq
(new $successful_test
(seq
(seq
(fold $successful successful_fold_var
(seq
(seq
(ap successful_fold_var $successful_test)
(canon -relay- $successful_test #successful_iter_canon)
)
(xor
(match #successful_iter_canon.length 1
(null)
)
(next successful_fold_var)
)
)
(never)
)
(canon -relay- $successful_test #successful_result_canon)
)
(ap #successful_result_canon successful_gate)
)
)
(ap "ok" $status)
)
(seq
(call -relay- ("peer" "timeout") [6000 "timeout"] ret-8)
(ap ret-8 $status)
)
)
(new $status_test
(seq
(seq
(fold $status status_fold_var
(seq
(seq
(ap status_fold_var $status_test)
(canon -relay- $status_test #status_iter_canon)
)
(xor
(match #status_iter_canon.length 1
(null)
)
(next status_fold_var)
)
)
(never)
)
(canon -relay- $status_test #status_result_canon)
)
(ap #status_result_canon status_gate)
)
)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(match status_gate.$.[0] "ok"
(ap true $result-1)
)
(seq
(ap :error: -if-error-)
(xor
(match :error:.$.error_code 10001
(ap false $result-1)
)
(seq
(seq
(ap :error: -else-error-)
(xor
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(ap -else-error- -if-else-error-)
)
)
(fail -if-else-error-)
)
)
)
)
)
)
)
)
(new $result-1_test
(seq
(seq
(fold $result-1 result-1_fold_var
(seq
(seq
(ap result-1_fold_var $result-1_test)
(canon -relay- $result-1_test #result-1_iter_canon)
)
(xor
(match #result-1_iter_canon.length 1
(null)
)
(next result-1_fold_var)
)
)
(never)
)
(canon -relay- $result-1_test #result-1_result_canon)
)
(ap #result-1_result_canon result-1_gate)
)
)
)
)
)
)
(new -if-else-error-
(new -else-error-
(new -if-error-
(xor
(match result-1_gate.$.[0] false
(ap "resource wasn't created: timeout exceeded" $error)
)
(seq
(ap :error: -if-error-)
(xor
(match :error:.$.error_code 10001
(ap ret-2 $resource_id)
)
(seq
(seq
(ap :error: -else-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap -else-error- -if-else-error-)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
(fail -if-else-error-)
)
)
)
)
)
)
)
)
)
)
(seq
(seq
(ap :error: -else-error-)
(xor
(seq
(match :error:.$.error_code 10001
(ap -if-error- -if-else-error-)
)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
(seq
(ap -else-error- -if-else-error-)
(new $-ephemeral-stream-
(new #-ephemeral-canon-
(canon -relay- $-ephemeral-stream- #-ephemeral-canon-)
)
)
)
)
)
(fail -if-else-error-)
)
)
)
)
)
)
)
)
(fail :error:)
)
)
(canon %init_peer_id% $resource_id #-resource_id-fix-0)
)
(ap #-resource_id-fix-0 -resource_id-flat-0)
)
)
)
(canon %init_peer_id% $error #error_canon)
)
(call %init_peer_id% ("callbackSrv" "response") [-resource_id-flat-0 #error_canon])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export type ResourceTestResultType = [string | null, string[]]
export type ResourceTestParams = [label: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, label: string, config?: {ttl?: number}];
export type ResourceTestResult = Promise<ResourceTestResultType>;
export function resourceTest(...args: ResourceTestParams): ResourceTestResult {
return callFunction$$(
args,
{
"functionName": "resourceTest",
"arrow": {
"domain": {
"fields": {
"label": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "option"
},
{
"type": {
"name": "string",
"tag": "scalar"
},
"tag": "array"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
resourceTest_script
);
}
export const helloTest_script = `
(xor
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("hello-world" "hello") ["Fluence user"] ret)
)
(call %init_peer_id% ("callbackSrv" "response") [ret])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export type HelloTestParams = [config?: {ttl?: number}] | [peer: IFluenceClient$$, config?: {ttl?: number}];
export type HelloTestResult = Promise<string>;
export function helloTest(...args: HelloTestParams): HelloTestResult {
return callFunction$$(
args,
{
"functionName": "helloTest",
"arrow": {
"domain": {
"fields": {},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "string",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
helloTest_script
);
}
export const demo_calculation_script = `
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "service_id") [] -service_id-arg-)
)
(call %init_peer_id% (-service_id-arg- "test_logs") [])
)
(call %init_peer_id% (-service_id-arg- "add") [10] ret)
)
(call %init_peer_id% (-service_id-arg- "multiply") [5] ret-0)
)
(call %init_peer_id% (-service_id-arg- "subtract") [8] ret-1)
)
(call %init_peer_id% (-service_id-arg- "divide") [6] ret-2)
)
(call %init_peer_id% (-service_id-arg- "state") [] ret-3)
)
(call %init_peer_id% ("callbackSrv" "response") [ret-3])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export type Demo_calculationParams = [service_id: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, service_id: string, config?: {ttl?: number}];
export type Demo_calculationResult = Promise<number>;
export function demo_calculation(...args: Demo_calculationParams): Demo_calculationResult {
return callFunction$$(
args,
{
"functionName": "demo_calculation",
"arrow": {
"domain": {
"fields": {
"service_id": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
demo_calculation_script
);
}
export const marineTest_script = `
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "wasm64") [] -wasm64-arg-)
)
(call %init_peer_id% ("single_module_srv" "create") [-wasm64-arg-] ret)
)
(call %init_peer_id% (ret.$.service_id.[0] "test_logs") [])
)
(call %init_peer_id% (ret.$.service_id.[0] "add") [10] ret-0)
)
(call %init_peer_id% (ret.$.service_id.[0] "multiply") [5] ret-1)
)
(call %init_peer_id% (ret.$.service_id.[0] "subtract") [8] ret-2)
)
(call %init_peer_id% (ret.$.service_id.[0] "divide") [6] ret-3)
)
(call %init_peer_id% (ret.$.service_id.[0] "state") [] ret-4)
)
(call %init_peer_id% ("callbackSrv" "response") [ret-4])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [:error: 0])
)
`;
export type MarineTestParams = [wasm64: string, config?: {ttl?: number}] | [peer: IFluenceClient$$, wasm64: string, config?: {ttl?: number}];
export type MarineTestResult = Promise<number>;
export function marineTest(...args: MarineTestParams): MarineTestResult {
return callFunction$$(
args,
{
"functionName": "marineTest",
"arrow": {
"domain": {
"fields": {
"wasm64": {
"name": "string",
"tag": "scalar"
}
},
"tag": "labeledProduct"
},
"codomain": {
"items": [
{
"name": "f64",
"tag": "scalar"
}
],
"tag": "unlabeledProduct"
},
"tag": "arrow"
},
"names": {
"relay": "-relay-",
"getDataSrv": "getDataSrv",
"callbackSrv": "callbackSrv",
"responseSrv": "callbackSrv",
"responseFnName": "response",
"errorHandlingSrv": "errorHandlingSrv",
"errorFnName": "error"
}
},
marineTest_script
);
}

View File

@ -14,46 +14,55 @@
* limitations under the License.
*/
import url from "url";
import { fileURLToPath } from "url";
import { compileFromPath } from "@fluencelabs/aqua-api";
import { describe, expect, it } from "vitest";
import { beforeAll, describe, expect, it } from "vitest";
import { getPackageJsonContent, PackageJson } from "../../utils.js";
import { generateTypes, generateSources } from "../index.js";
import { CompilationResult } from "../interfaces.js";
let res: Omit<CompilationResult, "funcCall">;
let pkg: PackageJson;
describe("Aqua to js/ts compiler", () => {
it("compiles smoke tests successfully", async () => {
const res = await compileFromPath({
filePath: url.fileURLToPath(
beforeAll(async () => {
res = await compileFromPath({
filePath: fileURLToPath(
new URL("./sources/smoke_test.aqua", import.meta.url),
),
imports: ["./node_modules"],
targetType: "air",
});
const pkg: PackageJson = {
pkg = {
...(await getPackageJsonContent()),
version: "0.0.0",
devDependencies: {
"@fluencelabs/aqua-api": "0.0.0",
},
};
});
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
it("matches js snapshots", async () => {
const jsResult = generateSources(res, "js", pkg);
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
const jsTypes = generateTypes(res, pkg);
expect(jsResult).toMatchSnapshot();
expect(jsTypes).toMatchSnapshot();
await expect(jsResult).toMatchFileSnapshot(
"./__snapshots__/generate.snap.js",
);
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
await expect(jsTypes).toMatchFileSnapshot(
"./__snapshots__/generate.snap.d.ts",
);
});
it("matches ts snapshots", async () => {
const tsResult = generateSources(res, "ts", pkg);
expect(tsResult).toMatchSnapshot();
await expect(tsResult).toMatchFileSnapshot(
"./__snapshots__/generate.snap.ts",
);
});
});

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { recursiveRenameLaquaProps } from "../utils.js";
import { capitalize, recursiveRenameLaquaProps } from "../utils.js";
import { AquaFunction, TypeGenerator } from "./interfaces.js";
@ -40,8 +40,11 @@ ${func.script}\`;
${typeGenerator.funcType(func)}
export function ${func.funcDef.functionName}(${typeGenerator.type(
"...args",
"any[]",
)}) {
`${capitalize(func.funcDef.functionName)}Params`,
)})${typeGenerator.type(
"",
`${capitalize(func.funcDef.functionName)}Result`,
)} {
return callFunction$$(
args,
${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)},

View File

@ -35,12 +35,13 @@ export default function generateHeader(
*/
${
outputType === "ts"
? "import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client';"
? "import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';"
: ""
}
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';`;
}

View File

@ -54,7 +54,7 @@ export class TSTypeGenerator implements TypeGenerator {
args.push([undefined, `config?: {ttl?: number}`]);
const argsDefs = args.map(([, def]) => {
return " " + def;
return def;
});
const argsDesc = args
@ -66,28 +66,30 @@ export class TSTypeGenerator implements TypeGenerator {
});
const functionOverloads = [
argsDefs.join(",\n"),
[` peer: ${CLIENT}`, ...argsDefs].join(",\n"),
argsDefs.join(", "),
[`peer: ${CLIENT}`, ...argsDefs].join(", "),
];
const [resTypeDesc, resType] = genTypeName(
funcDef.arrow.codomain,
capitalize(funcDef.functionName) + "Result",
capitalize(funcDef.functionName) + "ResultType",
);
const functionOverloadArgsType = functionOverloads
.map((overload) => {
return `[${overload}]`;
})
.join(" | ");
return [
argsDesc.join("\n"),
resTypeDesc ?? "",
functionOverloads
.flatMap((fo) => {
return [
`export function ${funcDef.functionName}(`,
fo,
`): Promise<${resType}>;`,
"",
];
})
.join("\n"),
`export type ${capitalize(
funcDef.functionName,
)}Params = ${functionOverloadArgsType};`,
`export type ${capitalize(
funcDef.functionName,
)}Result = Promise<${resType}>;\n`,
]
.filter((s) => {
return s !== "";
@ -117,13 +119,23 @@ export class TSTypeGenerator implements TypeGenerator {
const serviceDecl = `service: ${srvName}Def`;
const serviceIdDecl = `serviceId: string`;
const registerServiceArgs = [
const functionOverloadsWithDefaultServiceId = [
[serviceDecl],
[serviceIdDecl, serviceDecl],
[peerDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
const functionOverloadsWithoutDefaultServiceId = [
[serviceIdDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
const registerServiceArgs =
srvDef.defaultServiceId === undefined
? functionOverloadsWithoutDefaultServiceId
: functionOverloadsWithDefaultServiceId;
return [
interfaces,
...registerServiceArgs.map((registerServiceArg) => {

View File

@ -14,16 +14,12 @@
* limitations under the License.
*/
import { ServiceDef } from "@fluencelabs/interfaces";
import { JSONValue, ServiceDef } from "@fluencelabs/interfaces";
import { recursiveRenameLaquaProps } from "../utils.js";
import { TypeGenerator } from "./interfaces.js";
interface DefaultServiceId {
s_Some__f_value?: string;
}
export function generateServices(
typeGenerator: TypeGenerator,
services: Record<string, ServiceDef>,
@ -67,21 +63,6 @@ function generateRegisterServiceOverload(
}
function serviceToJson(service: ServiceDef): string {
return JSON.stringify(
{
// This assertion is required because aqua-api gives bad types
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
...((service.defaultServiceId as DefaultServiceId).s_Some__f_value != null
? {
defaultServiceId:
// This assertion is required because aqua-api gives bad types
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
(service.defaultServiceId as DefaultServiceId).s_Some__f_value,
}
: {}),
functions: recursiveRenameLaquaProps(service.functions),
},
null,
4,
);
const record: Record<never, JSONValue> = service;
return JSON.stringify(recursiveRenameLaquaProps(record), null, 4);
}

View File

@ -15,7 +15,7 @@
*/
import { generateSources, generateTypes } from "./generate/index.js";
import { CompilationResult, OutputType } from "./generate/interfaces.js";
import { CompilationResult } from "./generate/interfaces.js";
import { getPackageJsonContent } from "./utils.js";
interface JsOutput {
@ -27,17 +27,22 @@ interface TsOutput {
sources: string;
}
type LanguageOutput = {
js: JsOutput;
ts: TsOutput;
};
type NothingToGenerate = null;
export default async function aquaToJs<T extends OutputType>(
export default async function aquaToJs(
res: CompilationResult,
outputType: T,
): Promise<LanguageOutput[T] | NothingToGenerate> {
outputType: "js",
): Promise<JsOutput | NothingToGenerate>;
export default async function aquaToJs(
res: CompilationResult,
outputType: "ts",
): Promise<TsOutput | NothingToGenerate>;
export default async function aquaToJs(
res: CompilationResult,
outputType: "js" | "ts",
): Promise<JsOutput | TsOutput | NothingToGenerate> {
if (
Object.keys(res.services).length === 0 &&
Object.keys(res.functions).length === 0
@ -47,14 +52,14 @@ export default async function aquaToJs<T extends OutputType>(
const packageJson = await getPackageJsonContent();
return outputType === "js"
? {
sources: generateSources(res, "js", packageJson),
types: generateTypes(res, packageJson),
}
: // TODO: probably there is a way to remove this type assert
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
({
sources: generateSources(res, "ts", packageJson),
} as LanguageOutput[T]);
if (outputType === "js") {
return {
sources: generateSources(res, "js", packageJson),
types: generateTypes(res, packageJson),
};
}
return {
sources: generateSources(res, "ts", packageJson),
};
}

View File

@ -14,9 +14,8 @@
* limitations under the License.
*/
import assert from "assert";
import { readFile } from "fs/promises";
import path from "path";
import { join } from "path";
import {
ArrowType,
@ -27,24 +26,26 @@ import {
SimpleTypes,
UnlabeledProductType,
} from "@fluencelabs/interfaces";
import { z } from "zod";
export interface PackageJson {
name: string;
version: string;
devDependencies: {
["@fluencelabs/aqua-api"]: string;
};
}
const packageJsonSchema = z.object({
name: z.string(),
version: z.string(),
devDependencies: z.object({
// @fluencelabs/aqua-api version is included as part of the comment at the top of each js and ts file
["@fluencelabs/aqua-api"]: z.string(),
}),
});
export type PackageJson = z.infer<typeof packageJsonSchema>;
export async function getPackageJsonContent(): Promise<PackageJson> {
const content = await readFile(
new URL(path.join("..", "package.json"), import.meta.url),
new URL(join("..", "package.json"), import.meta.url),
"utf-8",
);
// TODO: Add validation here
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return JSON.parse(content) as PackageJson;
return packageJsonSchema.parse(JSON.parse(content));
}
export function getFuncArgs(
@ -84,7 +85,7 @@ export function recursiveRenameLaquaProps(obj: JSONValue): unknown {
// Last part of the property separated by "_" is a correct name
const refinedProperty = prop.split("_").pop();
if (refinedProperty == null) {
if (refinedProperty === undefined) {
throw new Error(`Bad property name: ${prop}.`);
}
@ -93,11 +94,15 @@ export function recursiveRenameLaquaProps(obj: JSONValue): unknown {
}
}
assert(accessProp in obj);
const laquaProp = obj[accessProp];
if (laquaProp === undefined) {
return acc;
}
return {
...acc,
[accessProp]: recursiveRenameLaquaProps(obj[accessProp]),
[accessProp]: recursiveRenameLaquaProps(laquaProp),
};
}, {});
}

View File

@ -1,7 +1,6 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "./dist"
},

View File

@ -1,5 +1,71 @@
# Changelog
## [0.12.0](https://github.com/fluencelabs/js-client/compare/interfaces-v0.11.0...interfaces-v0.12.0) (2024-02-23)
### ⚠ BREAKING CHANGES
* **deps:** Update avm to 0.62 ([#438](https://github.com/fluencelabs/js-client/issues/438))
### Bug Fixes
* **deps:** Update avm to 0.62 ([#438](https://github.com/fluencelabs/js-client/issues/438)) ([702ad60](https://github.com/fluencelabs/js-client/commit/702ad605a8e9217f66d3992f31ae8461283ff0b1))
## [0.11.0](https://github.com/fluencelabs/js-client/compare/interfaces-v0.10.0...interfaces-v0.11.0) (2024-01-26)
### ⚠ BREAKING CHANGES
* **deps:** update dependency @fluencelabs/avm to v0.59.0 #423
### Bug Fixes
* **deps:** update dependency @fluencelabs/avm to v0.59.0 [#423](https://github.com/fluencelabs/js-client/issues/423) ([e21ecc1](https://github.com/fluencelabs/js-client/commit/e21ecc1edec5f34f2a56726eb62833774f814fef))
## [0.10.0](https://github.com/fluencelabs/js-client/compare/interfaces-v0.9.0...interfaces-v0.10.0) (2024-01-19)
### ⚠ BREAKING CHANGES
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([#407](https://github.com/fluencelabs/js-client/issues/407))
### Bug Fixes
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([5d7ae85](https://github.com/fluencelabs/js-client/commit/5d7ae85e585b8ce1d89f347a0a31d2212fc5a792))
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([#407](https://github.com/fluencelabs/js-client/issues/407)) ([5d7ae85](https://github.com/fluencelabs/js-client/commit/5d7ae85e585b8ce1d89f347a0a31d2212fc5a792))
## [0.9.0](https://github.com/fluencelabs/js-client/compare/interfaces-v0.8.2...interfaces-v0.9.0) (2023-11-23)
### ⚠ BREAKING CHANGES
* Force release of interfaces ([#388](https://github.com/fluencelabs/js-client/issues/388))
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378))
* Bump avm ([#361](https://github.com/fluencelabs/js-client/issues/361))
* **js-client:** Adding strictes eslint and ts config to all packages [fixes DXJ-464] ([#355](https://github.com/fluencelabs/js-client/issues/355))
### Features
* **aqua-compiler:** JS-client aqua wrapper [fixes DXJ-461] ([#347](https://github.com/fluencelabs/js-client/issues/347)) ([7fff3b1](https://github.com/fluencelabs/js-client/commit/7fff3b1c0374eef76ab4e665b13cf97b5c50ff70))
* Force release of interfaces ([#388](https://github.com/fluencelabs/js-client/issues/388)) ([04c278b](https://github.com/fluencelabs/js-client/commit/04c278b7830aaae5bd83194511de3f942ddd4955))
* **js-client:** Adding strictes eslint and ts config to all packages [fixes DXJ-464] ([#355](https://github.com/fluencelabs/js-client/issues/355)) ([919c7d6](https://github.com/fluencelabs/js-client/commit/919c7d6ea1e9c153ff7a367873c85fb36624125d))
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378)) ([f4a550d](https://github.com/fluencelabs/js-client/commit/f4a550dd226846dfc2ade1ccc35a286dc3be2fed))
* remove obsolete packages [fixes DXJ-462] ([#337](https://github.com/fluencelabs/js-client/issues/337)) ([e7e6176](https://github.com/fluencelabs/js-client/commit/e7e617661f39e1df36a703d5dad93ba52a338919))
### Bug Fixes
* **deps:** Bump avm to 0.54 ([14e91b6](https://github.com/fluencelabs/js-client/commit/14e91b6e00e625792051aee2c82651e5679e3575))
* **deps:** update dependency @fluencelabs/avm to v0.46.0 ([#338](https://github.com/fluencelabs/js-client/issues/338)) ([8e6918c](https://github.com/fluencelabs/js-client/commit/8e6918c4da5bc4cdfe1c840312f477d782d9ca20))
* **deps:** update dependency @fluencelabs/avm to v0.47.0 ([#341](https://github.com/fluencelabs/js-client/issues/341)) ([f186f20](https://github.com/fluencelabs/js-client/commit/f186f209366c29f12e6677e03564ee2fa14b51ae))
* **deps:** update dependency @fluencelabs/avm to v0.48.0 ([#350](https://github.com/fluencelabs/js-client/issues/350)) ([945908a](https://github.com/fluencelabs/js-client/commit/945908a992976f2ad953bcaa3918741f890ffeeb))
* **tests:** Repair integration tests [fixes DXJ-506] ([#364](https://github.com/fluencelabs/js-client/issues/364)) ([36c7619](https://github.com/fluencelabs/js-client/commit/36c7619b4a1e8e2426aaf5592a14e96dafefb273))
### Miscellaneous Chores
* Bump avm ([#361](https://github.com/fluencelabs/js-client/issues/361)) ([29ec812](https://github.com/fluencelabs/js-client/commit/29ec812fc1c5ee812cceb4034776b344e5cadfe5))
## [0.8.2](https://github.com/fluencelabs/js-client/compare/interfaces-v0.8.1...interfaces-v0.8.2) (2023-08-24)
### Features

View File

@ -1,7 +1,7 @@
{
"name": "@fluencelabs/interfaces",
"type": "module",
"version": "0.8.2",
"version": "0.12.0",
"description": "Interfaces",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
@ -49,8 +49,6 @@
"license": "Apache-2.0",
"dependencies": {},
"devDependencies": {
"@multiformats/multiaddr": "11.3.0",
"@fluencelabs/avm": "0.54.0",
"hotscript": "1.0.13"
}
}

View File

@ -14,60 +14,11 @@
* limitations under the License.
*/
import type { SecurityTetraplet } from "@fluencelabs/avm";
import { InterfaceToType, MaybePromise } from "./utils.js";
/**
* Peer ID's id as a base58 string (multihash/CIDv0).
*/
export type PeerIdB58 = string;
/**
* Additional information about a service call
* @typeparam ArgName
*/
export type CallParams<ArgName extends string | null> = {
/**
* The identifier of particle which triggered the call
*/
particleId: string;
/**
* The peer id which created the particle
*/
initPeerId: PeerIdB58;
/**
* Particle's timestamp when it was created
*/
timestamp: number;
/**
* Time to live in milliseconds. The time after the particle should be expired
*/
ttl: number;
/**
* Particle's signature
*/
signature?: string;
/**
* Security tetraplets
*/
tetraplets: ArgName extends string
? Record<ArgName, InterfaceToType<SecurityTetraplet>[]>
: Record<string, never>;
};
export type ServiceImpl = Record<
string,
(
...args: [...JSONArray, CallParams<string>]
) => MaybePromise<JSONValue | undefined>
>;
export type JSONValue =
| string
| number
@ -75,5 +26,5 @@ export type JSONValue =
| null
| { [x: string]: JSONValue }
| Array<JSONValue>;
export type JSONArray = Array<JSONValue>;
export type JSONObject = { [x: string]: JSONValue };
export type JSONArray = Array<JSONValue>;

View File

@ -25,6 +25,11 @@ export type SimpleTypes =
export type NonArrowType = SimpleTypes | ProductType;
export type NonArrowSimpleType =
| SimpleTypes
| UnlabeledProductType
| LabeledProductType<SimpleTypes>;
export type TopType = {
/**
* Type descriptor. Used for pattern-matching
@ -154,7 +159,13 @@ export type ProductType = UnlabeledProductType | LabeledProductType;
* ArrowType is a profunctor pointing its domain to codomain.
* Profunctor means variance: Arrow is contravariant on domain, and variant on codomain.
*/
export type ArrowType<T extends LabeledProductType | UnlabeledProductType> = {
export type ArrowType<
T extends
| LabeledProductType<SimpleTypes | ArrowType<UnlabeledProductType>>
| UnlabeledProductType =
| LabeledProductType<SimpleTypes | ArrowType<UnlabeledProductType>>
| UnlabeledProductType,
> = {
/**
* Type descriptor. Used for pattern-matching
*/
@ -174,14 +185,14 @@ export type ArrowType<T extends LabeledProductType | UnlabeledProductType> = {
/**
* Arrow which domain contains only non-arrow types
*/
export type ArrowWithoutCallbacks = ArrowType<
UnlabeledProductType | LabeledProductType<SimpleTypes>
>;
export type ArrowWithoutCallbacks = ArrowType<UnlabeledProductType>;
/**
* Arrow which domain does can contain both non-arrow types and arrows (which themselves cannot contain arrows)
*/
export type ArrowWithCallbacks = ArrowType<LabeledProductType>;
export type ArrowWithCallbacks = ArrowType<
LabeledProductType<SimpleTypes | ArrowWithoutCallbacks>
>;
export interface FunctionCallConstants {
/**
@ -232,9 +243,7 @@ export interface FunctionCallDef {
/**
* Underlying arrow which represents function in aqua
*/
arrow: ArrowType<
LabeledProductType<SimpleTypes | ArrowType<UnlabeledProductType>>
>;
arrow: ArrowWithCallbacks;
/**
* Names of the different entities used in generated air script
@ -255,37 +264,8 @@ export interface ServiceDef {
* List of functions which the service consists of
*/
functions:
| LabeledProductType<ArrowType<LabeledProductType<SimpleTypes>>>
| LabeledProductType<
ArrowType<LabeledProductType<SimpleTypes> | UnlabeledProductType>
>
| NilType;
}
/**
* Options to configure Aqua function execution
*/
export interface FnConfig {
/**
* Sets the TTL (time to live) for particle responsible for the function execution
* If the option is not set the default TTL from FluencePeer config is used
*/
ttl?: number;
}
export const getArgumentTypes = (
def: FunctionCallDef,
): {
[key: string]: NonArrowType | ArrowWithoutCallbacks;
} => {
if (def.arrow.domain.tag !== "labeledProduct") {
throw new Error("Should be impossible");
}
return def.arrow.domain.fields;
};
export const isReturnTypeVoid = (def: FunctionCallDef): boolean => {
if (def.arrow.codomain.tag === "nil") {
return true;
}
return def.arrow.codomain.items.length === 0;
};

View File

@ -1,103 +0,0 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { JSONValue } from "../commonTypes.js";
import {
FnConfig,
FunctionCallDef,
ServiceDef,
} from "./aquaTypeDefinitions.js";
/**
* Type for callback passed as aqua function argument
*/
export type ArgCallbackFunction = (
...args: JSONValue[]
) => JSONValue | Promise<JSONValue>;
/**
* Arguments passed to Aqua function
*/
export type PassedArgs = { [key: string]: JSONValue | ArgCallbackFunction };
/**
* Arguments for callAquaFunction function
*/
// TODO: move to js-client side
export interface CallAquaFunctionArgs {
/**
* Peer to call the function on
*/
peer: unknown;
/**
* Function definition
*/
def: FunctionCallDef;
/**
* Air script used by the aqua function
*/
script: string;
/**
* Function configuration
*/
config: FnConfig;
/**
* Arguments to pass to the function
*/
args: PassedArgs;
}
/**
* Call a function from Aqua script
*/
export type CallAquaFunctionType = (
args: CallAquaFunctionArgs,
) => Promise<unknown>;
/**
* Arguments for registerService function
*/
export interface RegisterServiceArgs {
/**
* Peer to register the service on
*/
peer: unknown;
/**
* Service definition
*/
def: ServiceDef;
/**
* Service id
*/
serviceId: string | undefined;
/**
* Service implementation
*/
service: unknown;
}
/**
* Register a service defined in Aqua on a Fluence peer
*/
export type RegisterServiceType = (args: RegisterServiceArgs) => void;

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
import { Call, Pipe, Objects, Tuples, Unions, Fn } from "hotscript";
import {
ArrayType,
ArrowType,
@ -25,8 +27,7 @@ import {
StructType,
TopType,
UnlabeledProductType,
} from "@fluencelabs/interfaces";
import { Call, Pipe, Objects, Tuples, Unions, Fn } from "hotscript";
} from "./compilerSupport/aquaTypeDefinitions.js";
// Type definitions for inferring ts types from air json definition
// In the future we may remove string type declaration and move to type inference.

View File

@ -15,6 +15,5 @@
*/
export * from "./compilerSupport/aquaTypeDefinitions.js";
export * from "./compilerSupport/compilerSupportInterface.js";
export * from "./commonTypes.js";
export * from "./future.js";

View File

@ -0,0 +1 @@
src/versions.ts

View File

@ -1,5 +1,93 @@
# Changelog
## [0.6.0](https://github.com/fluencelabs/js-client/compare/js-client-isomorphic-v0.5.0...js-client-isomorphic-v0.6.0) (2024-02-23)
### ⚠ BREAKING CHANGES
* **deps:** Update avm to 0.62 ([#438](https://github.com/fluencelabs/js-client/issues/438))
### Bug Fixes
* **deps:** Update avm to 0.62 ([#438](https://github.com/fluencelabs/js-client/issues/438)) ([702ad60](https://github.com/fluencelabs/js-client/commit/702ad605a8e9217f66d3992f31ae8461283ff0b1))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/marine-worker bumped from 0.5.1 to 0.6.0
## [0.5.0](https://github.com/fluencelabs/js-client/compare/js-client-isomorphic-v0.4.0...js-client-isomorphic-v0.5.0) (2024-01-26)
### ⚠ BREAKING CHANGES
* **deps:** update dependency @fluencelabs/avm to v0.59.0 #423
### Bug Fixes
* **deps:** update dependency @fluencelabs/avm to v0.59.0 [#423](https://github.com/fluencelabs/js-client/issues/423) ([e21ecc1](https://github.com/fluencelabs/js-client/commit/e21ecc1edec5f34f2a56726eb62833774f814fef))
## [0.4.0](https://github.com/fluencelabs/js-client/compare/js-client-isomorphic-v0.3.1...js-client-isomorphic-v0.4.0) (2024-01-19)
### ⚠ BREAKING CHANGES
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([#407](https://github.com/fluencelabs/js-client/issues/407))
### Bug Fixes
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([5d7ae85](https://github.com/fluencelabs/js-client/commit/5d7ae85e585b8ce1d89f347a0a31d2212fc5a792))
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([#407](https://github.com/fluencelabs/js-client/issues/407)) ([5d7ae85](https://github.com/fluencelabs/js-client/commit/5d7ae85e585b8ce1d89f347a0a31d2212fc5a792))
* Enable async loading of all dependency resources ([#408](https://github.com/fluencelabs/js-client/issues/408)) ([f5425b4](https://github.com/fluencelabs/js-client/commit/f5425b4746f436f84a41bae6584adb8b200ba33d))
## [0.3.1](https://github.com/fluencelabs/js-client/compare/js-client-isomorphic-v0.3.0...js-client-isomorphic-v0.3.1) (2023-12-15)
### Features
* **npm-aqua-compiler:** create package ([#401](https://github.com/fluencelabs/js-client/issues/401)) ([d600811](https://github.com/fluencelabs/js-client/commit/d6008110cf0ecaf23a63cfef0bb3f786a6eb0937))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/marine-worker bumped from 0.5.0 to 0.5.1
## [0.3.0](https://github.com/fluencelabs/js-client/compare/js-client-isomorphic-v0.2.2...js-client-isomorphic-v0.3.0) (2023-11-22)
### ⚠ BREAKING CHANGES
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378))
### Features
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378)) ([f4a550d](https://github.com/fluencelabs/js-client/commit/f4a550dd226846dfc2ade1ccc35a286dc3be2fed))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/marine-worker bumped from 0.4.2 to 0.5.0
## [0.2.2](https://github.com/fluencelabs/js-client/compare/js-client-isomorphic-v0.2.1...js-client-isomorphic-v0.2.2) (2023-11-06)
### Bug Fixes
* JS-client bugs and tech debt [fixes DXJ-520] ([#374](https://github.com/fluencelabs/js-client/issues/374)) ([b460491](https://github.com/fluencelabs/js-client/commit/b460491fbd0d07e3507a6c70f162014580c6d6da))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/marine-worker bumped from 0.4.1 to 0.4.2
## [0.2.1](https://github.com/fluencelabs/js-client/compare/js-client-isomorphic-v0.2.0...js-client-isomorphic-v0.2.1) (2023-10-30)

View File

@ -1,14 +1,14 @@
{
"type": "module",
"name": "@fluencelabs/js-client-isomorphic",
"version": "0.2.1",
"version": "0.6.0",
"description": "Isomorphic entities for js-client",
"files": [
"dist"
],
"main": "index.js",
"scripts": {
"build": "tsc"
"build": "tsc",
"prepare": "node createVersionFile.js"
},
"exports": {
".": "./dist/types.js",
@ -22,9 +22,9 @@
}
},
"dependencies": {
"@fluencelabs/avm": "0.54.0",
"@fluencelabs/marine-js": "0.7.2",
"@fluencelabs/marine-worker": "workspace:*",
"@fluencelabs/avm": "0.62.0",
"@fluencelabs/marine-js": "0.13.0",
"@fluencelabs/marine-worker": "0.6.0",
"@fluencelabs/threads": "^2.0.0"
},
"keywords": [],

View File

@ -14,25 +14,22 @@
* limitations under the License.
*/
import type { VersionedPackage } from "../types.js";
import { FetchResourceFn, getVersionedPackage } from "../types.js";
/**
* @param pkg name of package with version
* @param assetPath path of required asset in given package
* @param root CDN domain in browser or file system root in node
*/
export async function fetchResource(
pkg: VersionedPackage,
assetPath: string,
root: string,
) {
export const fetchResource: FetchResourceFn = async (pkg, assetPath, root) => {
const refinedAssetPath = assetPath.startsWith("/")
? assetPath.slice(1)
: assetPath;
const url = new URL(`${pkg.name}@${pkg.version}/` + refinedAssetPath, root);
const { name, version } = getVersionedPackage(pkg);
const url = new URL(`${name}@${version}/` + refinedAssetPath, root);
return fetch(url).catch(() => {
throw new Error(`Cannot fetch from ${url.toString()}`);
});
}
};

View File

@ -18,35 +18,29 @@ import { readFile } from "fs/promises";
import { createRequire } from "module";
import { sep, posix, join } from "path";
import type { VersionedPackage } from "../types.js";
import { FetchResourceFn, getVersionedPackage } from "../types.js";
/**
* @param pkg name of package with version
* @param assetPath path of required asset in given package
* @param root CDN domain in browser or js-client itself in node
*/
export async function fetchResource(
pkg: VersionedPackage,
assetPath: string,
root: string,
) {
// TODO: `root` will be handled somehow in the future. For now, we use filesystem root where js-client is running;
root = "/";
export const fetchResource: FetchResourceFn = async (pkg, assetPath) => {
const { name } = getVersionedPackage(pkg);
const require = createRequire(import.meta.url);
const packagePathIndex = require.resolve(pkg.name);
const packagePathIndex = require.resolve(name);
// Ensure that windows path is converted to posix path. So we can find a package
const posixPath = packagePathIndex.split(sep).join(posix.sep);
const matches = new RegExp(`(.+${pkg.name})`).exec(posixPath);
const matches = new RegExp(`(.+${name})`).exec(posixPath);
const packagePath = matches?.[0];
if (packagePath == null) {
throw new Error(`Cannot find dependency ${pkg.name} in path ${posixPath}`);
if (packagePath === undefined) {
throw new Error(`Cannot find dependency ${name} in path ${posixPath}`);
}
const pathToResource = join(root, packagePath, assetPath);
const pathToResource = join(packagePath, assetPath);
const file = await readFile(pathToResource);
@ -59,4 +53,4 @@ export async function fetchResource(
: "application/text",
},
});
}
};

View File

@ -14,10 +14,27 @@
* limitations under the License.
*/
import { Worker } from "@fluencelabs/threads/master";
import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker";
import { ModuleThread } from "@fluencelabs/threads/master";
export type VersionedPackage = { name: string; version: string };
export type GetWorker = (
pkg: VersionedPackage,
import versions from "./versions.js";
export type FetchedPackages = keyof typeof versions;
type VersionedPackage = { name: string; version: string };
export type GetWorkerFn = (
pkg: FetchedPackages,
CDNUrl: string,
) => Promise<Worker>;
) => Promise<ModuleThread<MarineBackgroundInterface>>;
export const getVersionedPackage = (pkg: FetchedPackages): VersionedPackage => {
return {
name: pkg,
version: versions[pkg],
};
};
export type FetchResourceFn = (
pkg: FetchedPackages,
assetPath: string,
root: string,
) => Promise<Response>;

View File

@ -14,13 +14,14 @@
* limitations under the License.
*/
import { BlobWorker } from "@fluencelabs/threads/master";
import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker";
import { BlobWorker, ModuleThread, spawn } from "@fluencelabs/threads/master";
import { fetchResource } from "../fetchers/browser.js";
import type { GetWorker, VersionedPackage } from "../types.js";
import type { FetchedPackages, GetWorkerFn } from "../types.js";
export const getWorker: GetWorker = async (
pkg: VersionedPackage,
export const getWorker: GetWorkerFn = async (
pkg: FetchedPackages,
CDNUrl: string,
) => {
const fetchWorkerCode = async () => {
@ -34,5 +35,9 @@ export const getWorker: GetWorker = async (
};
const workerCode = await fetchWorkerCode();
return BlobWorker.fromText(workerCode);
const workerThread: ModuleThread<MarineBackgroundInterface> =
await spawn<MarineBackgroundInterface>(BlobWorker.fromText(workerCode));
return workerThread;
};

View File

@ -18,18 +18,24 @@ import { createRequire } from "module";
import { dirname, relative } from "path";
import { fileURLToPath } from "url";
import { Worker } from "@fluencelabs/threads/master";
import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker";
import { ModuleThread, spawn, Worker } from "@fluencelabs/threads/master";
import type { GetWorker, VersionedPackage } from "../types.js";
import type { FetchedPackages, GetWorkerFn } from "../types.js";
import { getVersionedPackage } from "../types.js";
export const getWorker: GetWorker = (pkg: VersionedPackage) => {
export const getWorker: GetWorkerFn = async (pkg: FetchedPackages) => {
const require = createRequire(import.meta.url);
const pathToThisFile = dirname(fileURLToPath(import.meta.url));
const pathToWorker = require.resolve(pkg.name);
const { name } = getVersionedPackage(pkg);
const pathToWorker = require.resolve(name);
const relativePathToWorker = relative(pathToThisFile, pathToWorker);
return Promise.resolve(new Worker(relativePathToWorker));
const workerThread: ModuleThread<MarineBackgroundInterface> =
await spawn<MarineBackgroundInterface>(new Worker(relativePathToWorker));
return workerThread;
};

View File

@ -1,26 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
bundle/
dist
esm
types
# Dependency directories
node_modules/
jspm_packages/
.idea
# workaround to make integration tests work
src/marine/worker-script/index.js
src/versions.ts

View File

@ -6,6 +6,204 @@
* dependencies
* @fluencelabs/js-client-isomorphic bumped to 0.2.0
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/interfaces bumped to 0.9.0
## [0.9.0](https://github.com/fluencelabs/js-client/compare/js-client-v0.8.4...js-client-v0.9.0) (2024-02-23)
### ⚠ BREAKING CHANGES
* **deps:** Update avm to 0.62 ([#438](https://github.com/fluencelabs/js-client/issues/438))
### Bug Fixes
* **deps:** Update avm to 0.62 ([#438](https://github.com/fluencelabs/js-client/issues/438)) ([702ad60](https://github.com/fluencelabs/js-client/commit/702ad605a8e9217f66d3992f31ae8461283ff0b1))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/interfaces bumped to 0.12.0
* @fluencelabs/js-client-isomorphic bumped to 0.6.0
* @fluencelabs/marine-worker bumped from 0.5.1 to 0.6.0
## [0.8.4](https://github.com/fluencelabs/js-client/compare/js-client-v0.8.3...js-client-v0.8.4) (2024-02-23)
### Bug Fixes
* Rename testnet to dar ([#433](https://github.com/fluencelabs/js-client/issues/433)) ([e8417d0](https://github.com/fluencelabs/js-client/commit/e8417d069a8cc2244f392b028e7464c9917ec063))
## [0.8.3](https://github.com/fluencelabs/js-client/compare/js-client-v0.8.2...js-client-v0.8.3) (2024-02-09)
### Bug Fixes
* **js-client:** Handle null as user input value ([#431](https://github.com/fluencelabs/js-client/issues/431)) ([d7070fd](https://github.com/fluencelabs/js-client/commit/d7070fd71ef8524bdc47c0c84afe0a62ae80dca6))
## [0.8.2](https://github.com/fluencelabs/js-client/compare/js-client-v0.8.1...js-client-v0.8.2) (2024-02-09)
### Bug Fixes
* **js-client:** Improve logging of conversion API ([#429](https://github.com/fluencelabs/js-client/issues/429)) ([2b1d0f7](https://github.com/fluencelabs/js-client/commit/2b1d0f7f05de237733ad6f6c5aeb1ba787313dd7))
## [0.8.1](https://github.com/fluencelabs/js-client/compare/js-client-v0.8.0...js-client-v0.8.1) (2024-01-31)
### Bug Fixes
* Dial interval ([#421](https://github.com/fluencelabs/js-client/issues/421)) ([fa38328](https://github.com/fluencelabs/js-client/commit/fa38328fddac076831ecd0a352d802a0281c4ab4))
## [0.8.0](https://github.com/fluencelabs/js-client/compare/js-client-v0.7.0...js-client-v0.8.0) (2024-01-29)
### ⚠ BREAKING CHANGES
* **js-client:** Multiformat MsgPack for particle data ([#422](https://github.com/fluencelabs/js-client/issues/422))
### Features
* **js-client:** Multiformat MsgPack for particle data ([#422](https://github.com/fluencelabs/js-client/issues/422)) ([8ac029b](https://github.com/fluencelabs/js-client/commit/8ac029b6d336114a90559fb3fee18f7493beb1e0))
## [0.7.0](https://github.com/fluencelabs/js-client/compare/js-client-v0.6.0...js-client-v0.7.0) (2024-01-26)
### ⚠ BREAKING CHANGES
* **deps:** update dependency @fluencelabs/avm to v0.59.0 #423
### Bug Fixes
* **deps:** update dependency @fluencelabs/avm to v0.59.0 [#423](https://github.com/fluencelabs/js-client/issues/423) ([e21ecc1](https://github.com/fluencelabs/js-client/commit/e21ecc1edec5f34f2a56726eb62833774f814fef))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/interfaces bumped to 0.11.0
* @fluencelabs/js-client-isomorphic bumped to 0.5.0
## [0.6.0](https://github.com/fluencelabs/js-client/compare/js-client-v0.5.5...js-client-v0.6.0) (2024-01-19)
### ⚠ BREAKING CHANGES
* **js-client:** Remove getter for secret key ([#416](https://github.com/fluencelabs/js-client/issues/416))
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([#407](https://github.com/fluencelabs/js-client/issues/407))
### Features
* **js-client:** Remove getter for secret key ([#416](https://github.com/fluencelabs/js-client/issues/416)) ([15f96dc](https://github.com/fluencelabs/js-client/commit/15f96dc5f78693ac9bb86f21b79ed035ffd97977))
### Bug Fixes
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([5d7ae85](https://github.com/fluencelabs/js-client/commit/5d7ae85e585b8ce1d89f347a0a31d2212fc5a792))
* **deps:** update dependency @fluencelabs/avm to v0.55.0 ([#407](https://github.com/fluencelabs/js-client/issues/407)) ([5d7ae85](https://github.com/fluencelabs/js-client/commit/5d7ae85e585b8ce1d89f347a0a31d2212fc5a792))
* Enable async loading of all dependency resources ([#408](https://github.com/fluencelabs/js-client/issues/408)) ([f5425b4](https://github.com/fluencelabs/js-client/commit/f5425b4746f436f84a41bae6584adb8b200ba33d))
* **js-client:** Improve logging ([#418](https://github.com/fluencelabs/js-client/issues/418)) ([5696e3b](https://github.com/fluencelabs/js-client/commit/5696e3beba9453e5981a599bab2662d87dc1ddd2))
* **js-client:** Remove union with undefined of methods for getting random peer ([#417](https://github.com/fluencelabs/js-client/issues/417)) ([4d90414](https://github.com/fluencelabs/js-client/commit/4d90414190feb6772c7afe0869dee7636616f4c4))
* Update libp2p deps ([#419](https://github.com/fluencelabs/js-client/issues/419)) ([a8a1473](https://github.com/fluencelabs/js-client/commit/a8a14735b34495d2426b1f59f53c75d69c7faf66))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/interfaces bumped to 0.10.0
* @fluencelabs/js-client-isomorphic bumped to 0.4.0
## [0.5.5](https://github.com/fluencelabs/js-client/compare/js-client-v0.5.4...js-client-v0.5.5) (2023-12-15)
### Features
* **js-client:** Add fire-and-forget flag [DXJ-562] ([#400](https://github.com/fluencelabs/js-client/issues/400)) ([86a7302](https://github.com/fluencelabs/js-client/commit/86a73027e523cf3db4fc9cf58fc7625e44638d0a))
* **npm-aqua-compiler:** create package ([#401](https://github.com/fluencelabs/js-client/issues/401)) ([d600811](https://github.com/fluencelabs/js-client/commit/d6008110cf0ecaf23a63cfef0bb3f786a6eb0937))
### Bug Fixes
* **js-client:** Remove log truncation. ([#403](https://github.com/fluencelabs/js-client/issues/403)) ([9b629ee](https://github.com/fluencelabs/js-client/commit/9b629eef2e188331cfb338efe775f20dac9bc2fb))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/js-client-isomorphic bumped to 0.3.1
* @fluencelabs/marine-worker bumped from 0.5.0 to 0.5.1
## [0.5.4](https://github.com/fluencelabs/js-client/compare/js-client-v0.5.3...js-client-v0.5.4) (2023-12-06)
### Features
* **js-client:** Update libp2p ecosystem [fixes DXJ-551] ([#393](https://github.com/fluencelabs/js-client/issues/393)) ([44eb149](https://github.com/fluencelabs/js-client/commit/44eb1493b3a40fea56cfe726f7e18cd646ca6f92))
* Support instance context [fixes DXJ-541] ([#392](https://github.com/fluencelabs/js-client/issues/392)) ([1578b79](https://github.com/fluencelabs/js-client/commit/1578b791ac4ff483dc73aa3fd5083b1eeef79be8))
## [0.5.3](https://github.com/fluencelabs/js-client/compare/js-client-v0.5.2...js-client-v0.5.3) (2023-11-23)
### Features
* Additional export from js-client ([fdd0ca0](https://github.com/fluencelabs/js-client/commit/fdd0ca0ea26407dbfd94d43e2a5ec1bbea7b96ff))
## [0.5.1](https://github.com/fluencelabs/js-client/compare/js-client-v0.5.0...js-client-v0.5.1) (2023-11-23)
### Features
* Temporarily expose internal js client API ([#386](https://github.com/fluencelabs/js-client/issues/386)) ([b5a6296](https://github.com/fluencelabs/js-client/commit/b5a6296225563dc6039b3f7c83e11075d9893045))
## [0.5.0](https://github.com/fluencelabs/js-client/compare/js-client-v0.4.4...js-client-v0.5.0) (2023-11-22)
### ⚠ BREAKING CHANGES
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378))
### Features
* **js-client:** Segregation of responsibility between js-client packages [fixes DXJ-525] ([#378](https://github.com/fluencelabs/js-client/issues/378)) ([f4a550d](https://github.com/fluencelabs/js-client/commit/f4a550dd226846dfc2ade1ccc35a286dc3be2fed))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/js-client-isomorphic bumped to 0.3.0
* @fluencelabs/marine-worker bumped from 0.4.2 to 0.5.0
## [0.4.4](https://github.com/fluencelabs/js-client/compare/js-client-v0.4.3...js-client-v0.4.4) (2023-11-10)
### Bug Fixes
* **js-client:** Fix CDN flow and move it in modules [fixes DXJ-527] ([#376](https://github.com/fluencelabs/js-client/issues/376)) ([f5e9923](https://github.com/fluencelabs/js-client/commit/f5e99239741bc8907c3b4febdc53e46d7a43e46e))
## [0.4.3](https://github.com/fluencelabs/js-client/compare/js-client-v0.4.2...js-client-v0.4.3) (2023-11-06)
### Bug Fixes
* JS-client bugs and tech debt [fixes DXJ-520] ([#374](https://github.com/fluencelabs/js-client/issues/374)) ([b460491](https://github.com/fluencelabs/js-client/commit/b460491fbd0d07e3507a6c70f162014580c6d6da))
### Dependencies
* The following workspace dependencies were updated
* dependencies
* @fluencelabs/js-client-isomorphic bumped to 0.2.2
* @fluencelabs/marine-worker bumped from 0.4.1 to 0.4.2
## [0.4.2](https://github.com/fluencelabs/js-client/compare/js-client-v0.4.1...js-client-v0.4.2) (2023-10-30)

View File

@ -0,0 +1,16 @@
service Calc("calc"):
add(n: f32)
subtract(n: f32)
multiply(n: f32)
divide(n: f32)
reset()
getResult() -> f32
func demoCalc() -> f32:
Calc.add(10)
Calc.multiply(5)
Calc.subtract(8)
Calc.divide(6)
res <- Calc.getResult()
<- res

View File

@ -1,10 +1,10 @@
data GreetingRecord:
data GreetingRecordData:
str: string
num: i32
service Greeting("greeting"):
greeting(name: string) -> string
greeting_record() -> GreetingRecord
greeting_record() -> GreetingRecordData
func call(arg: string) -> string:
res1 <- Greeting.greeting(arg)
@ -13,7 +13,7 @@ func call(arg: string) -> string:
<- res3
service GreetingRecord:
greeting_record() -> GreetingRecord
greeting_record() -> GreetingRecordData
log_debug()
log_error()
log_info()

View File

@ -1,6 +1,6 @@
{
"name": "@fluencelabs/js-client",
"version": "0.4.2",
"version": "0.9.0",
"description": "Client for interacting with Fluence network",
"engines": {
"node": ">=10",
@ -24,51 +24,50 @@
"type": "module",
"scripts": {
"build": "tsc && vite build",
"test": "vitest --threads false run",
"prepare": "node createVersionFile.js"
"test": "vitest --threads false run"
},
"repository": "https://github.com/fluencelabs/fluence-js",
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"@chainsafe/libp2p-noise": "13.0.0",
"@chainsafe/libp2p-yamux": "5.0.0",
"@fluencelabs/avm": "0.54.0",
"@chainsafe/libp2p-noise": "14.0.0",
"@chainsafe/libp2p-yamux": "6.0.1",
"@fluencelabs/avm": "0.62.0",
"@fluencelabs/interfaces": "workspace:*",
"@fluencelabs/js-client-isomorphic": "workspace:*",
"@fluencelabs/marine-worker": "0.4.1",
"@libp2p/crypto": "2.0.3",
"@libp2p/interface": "0.1.2",
"@libp2p/peer-id": "3.0.2",
"@libp2p/peer-id-factory": "3.0.3",
"@libp2p/websockets": "7.0.4",
"@multiformats/multiaddr": "11.3.0",
"assert": "2.1.0",
"async": "3.2.4",
"@fluencelabs/marine-worker": "0.6.0",
"@fluencelabs/threads": "^2.0.0",
"@libp2p/crypto": "4.0.1",
"@libp2p/identify": "1.0.11",
"@libp2p/interface": "1.1.2",
"@libp2p/peer-id": "4.0.5",
"@libp2p/peer-id-factory": "4.0.5",
"@libp2p/ping": "1.0.10",
"@libp2p/utils": "5.2.2",
"@libp2p/websockets": "8.0.12",
"@multiformats/multiaddr": "12.1.12",
"bs58": "5.0.0",
"buffer": "6.0.3",
"debug": "4.3.4",
"it-length-prefixed": "8.0.4",
"it-map": "2.0.0",
"it-pipe": "2.0.5",
"int64-buffer": "1.0.1",
"it-length-prefixed": "9.0.3",
"it-map": "3.0.5",
"it-pipe": "3.0.1",
"js-base64": "3.7.5",
"libp2p": "0.46.6",
"libp2p": "1.2.0",
"multiformats": "11.0.1",
"rxjs": "7.5.5",
"@fluencelabs/threads": "^2.0.0",
"ts-pattern": "3.3.3",
"uint8arrays": "4.0.3",
"uuid": "8.3.2",
"zod": "3.22.4"
},
"devDependencies": {
"@fluencelabs/aqua-api": "0.9.3",
"@fluencelabs/marine-js": "0.7.2",
"@rollup/plugin-inject": "5.0.3",
"@fluencelabs/aqua-api": "0.13.0",
"@rollup/plugin-inject": "5.0.5",
"@types/bs58": "4.0.1",
"@types/debug": "4.1.7",
"@types/node": "20.7.0",
"@types/uuid": "8.3.2",
"esbuild": "0.19.5",
"hotscript": "1.0.13",
"vite": "4.4.11",
"vite-tsconfig-paths": "4.0.3",

View File

@ -0,0 +1,89 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { fileURLToPath } from "url";
import { compileFromPath } from "@fluencelabs/aqua-api";
import { ServiceDef } from "@fluencelabs/interfaces";
import { assert, describe, expect, it } from "vitest";
import { v5_registerService } from "./api.js";
import { callAquaFunction } from "./compilerSupport/callFunction.js";
import { withPeer } from "./util/testUtils.js";
class CalcParent {
protected _state: number = 0;
add(n: number) {
this._state += n;
}
subtract(n: number) {
this._state -= n;
}
}
class Calc extends CalcParent {
multiply(n: number) {
this._state *= n;
}
divide(n: number) {
this._state /= n;
}
reset() {
this._state = 0;
}
getResult() {
return this._state;
}
}
describe("User API methods", () => {
it("registers user class service and calls own and inherited methods correctly", async () => {
await withPeer(async (peer) => {
const calcService: Record<never, unknown> = new Calc();
const { functions, services } = await compileFromPath({
filePath: fileURLToPath(new URL("../aqua/calc.aqua", import.meta.url)),
});
const typedServices: Record<string, ServiceDef> = services;
assert("demoCalc" in functions);
const { script } = functions["demoCalc"];
assert("Calc" in typedServices);
v5_registerService([peer, "calc", calcService], {
defaultServiceId: "calc",
functions: typedServices["Calc"].functions,
});
const res = await callAquaFunction({
args: {},
peer,
script,
fireAndForget: false,
});
expect(res).toBe(7);
});
});
});

View File

@ -15,182 +15,220 @@
*/
import type {
FnConfig,
ArrowWithoutCallbacks,
FunctionCallDef,
JSONValue,
ServiceDef,
PassedArgs,
ServiceImpl,
SimpleTypes,
} from "@fluencelabs/interfaces";
import { getArgumentTypes } from "@fluencelabs/interfaces";
import { z } from "zod";
import { CallAquaFunctionConfig } from "./compilerSupport/callFunction.js";
import {
aqua2js,
js2aqua,
SchemaValidationError,
wrapJsFunction,
} from "./compilerSupport/conversions.js";
import { ServiceImpl, UserServiceImpl } from "./compilerSupport/types.js";
import { FluencePeer } from "./jsPeer/FluencePeer.js";
import { zip } from "./util/utils.js";
import { callAquaFunction, Fluence, registerService } from "./index.js";
export const isFluencePeer = (
fluencePeerCandidate: unknown,
): fluencePeerCandidate is FluencePeer => {
return fluencePeerCandidate instanceof FluencePeer;
};
function validateAquaConfig(
config: unknown,
): asserts config is CallAquaFunctionConfig | undefined {
z.union([
z.object({
ttl: z.number().optional(),
}),
z.undefined(),
]).parse(config);
}
// TODO: remove v5 prefix from functions
/**
* Convenience function to support Aqua `func` generation backend
* The compiler only need to generate a call the function and provide the corresponding definitions and the air script
*
* @param rawFnArgs - raw arguments passed by user to the generated function
* @param args - raw arguments passed by user to the generated function
* @param def - function definition generated by the Aqua compiler
* @param script - air script with function execution logic generated by the Aqua compiler
*/
export const v5_callFunction = async (
rawFnArgs: unknown[],
args: [
client: FluencePeer | (JSONValue | UserServiceImpl[string]),
...args: (JSONValue | UserServiceImpl[string])[],
],
def: FunctionCallDef,
script: string,
): Promise<unknown> => {
const { args, client: peer, config } = extractFunctionArgs(rawFnArgs, def);
): Promise<JSONValue> => {
const [peerOrArg, ...rest] = args;
return callAquaFunction({
args,
def,
if (!(peerOrArg instanceof FluencePeer)) {
return await v5_callFunction(
[getDefaultPeer(), peerOrArg, ...rest],
def,
script,
);
}
const schemaFunctionArgs: Record<string, FunctionArg> =
def.arrow.domain.tag === "nil" ? {} : def.arrow.domain.fields;
const schemaFunctionArgEntries = Object.entries(schemaFunctionArgs);
const schemaArgCount = schemaFunctionArgEntries.length;
type FunctionArg = SimpleTypes | ArrowWithoutCallbacks;
// if there are more args than expected in schema (schemaArgCount) then last arg is config
const config = schemaArgCount < rest.length ? rest.pop() : undefined;
validateAquaConfig(config);
const callArgs = Object.fromEntries<JSONValue | ServiceImpl[string]>(
zip(rest.slice(0, schemaArgCount), schemaFunctionArgEntries).map(
([arg, [argName, argType]]) => {
if (argType.tag === "arrow") {
if (typeof arg !== "function") {
throw new SchemaValidationError(
[argName],
argType,
"function",
arg,
);
}
return [argName, wrapJsFunction(arg, argType, argName)];
}
if (typeof arg === "function") {
throw new SchemaValidationError(
[argName],
argType,
"non-function value",
arg,
);
}
return [argName, js2aqua(arg, argType, { path: [argName] })];
},
),
);
const returnTypeVoid =
def.arrow.codomain.tag === "nil" || def.arrow.codomain.items.length === 0;
const returnSchema =
def.arrow.codomain.tag === "unlabeledProduct" &&
def.arrow.codomain.items.length === 1 &&
"0" in def.arrow.codomain.items
? def.arrow.codomain.items[0]
: def.arrow.codomain;
let result = await callAquaFunction({
script,
peer: peerOrArg,
args: callArgs,
config,
peer,
fireAndForget: returnTypeVoid,
});
if (returnTypeVoid) {
result = null;
}
return aqua2js(result, returnSchema, {
path: [`${def.functionName}ReturnValue`],
});
};
const getDefaultPeer = (): FluencePeer => {
if (Fluence.defaultClient === undefined) {
throw new Error(
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
);
}
return Fluence.defaultClient;
};
const getDefaultServiceId = (def: ServiceDef) => {
if (def.defaultServiceId === undefined) {
throw new Error("Service ID is not provided");
}
return def.defaultServiceId;
};
type RegisterServiceType =
| [UserServiceImpl]
| [string, UserServiceImpl]
| [FluencePeer, UserServiceImpl]
| [FluencePeer, string, UserServiceImpl];
/**
* Convenience function to support Aqua `service` generation backend
* The compiler only need to generate a call the function and provide the corresponding definitions and the air script
* @param args - raw arguments passed by user to the generated function
* TODO: dont forget to add jsdoc for new arg
* @param def - service definition generated by the Aqua compiler
*/
export const v5_registerService = (args: unknown[], def: ServiceDef): void => {
// TODO: Support this in aqua-to-js package
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const service: ServiceImpl = args.pop() as ServiceImpl;
export const v5_registerService = (
args: RegisterServiceType,
def: ServiceDef,
): void => {
if (args.length === 1) {
v5_registerService(
[getDefaultPeer(), getDefaultServiceId(def), args[0]],
def,
);
const { peer, serviceId } = extractServiceArgs(args, def.defaultServiceId);
return;
}
if (args.length === 2) {
if (args[0] instanceof FluencePeer) {
v5_registerService([args[0], getDefaultServiceId(def), args[1]], def);
return;
}
v5_registerService([getDefaultPeer(), args[0], args[1]], def);
return;
}
const [peer, serviceId, serviceImpl] = args;
// Schema for every function in service
const serviceSchema = def.functions.tag === "nil" ? {} : def.functions.fields;
// Wrapping service functions, selecting only those listed in schema, to convert their args js -> aqua and backwards
const wrappedServiceImpl = Object.fromEntries(
Object.entries(serviceSchema).map(([schemaKey, schemaValue]) => {
const serviceImplValue = serviceImpl[schemaKey];
if (serviceImplValue === undefined) {
throw new Error(
`Service function ${schemaKey} listed in Aqua schema but wasn't provided in schema implementation object or class instance. Check that your Aqua service definition matches passed service implementation`,
);
}
return [
schemaKey,
wrapJsFunction(
serviceImplValue.bind(serviceImpl),
schemaValue,
schemaKey,
),
] as const;
}),
);
registerService({
def,
service,
serviceId,
service: wrappedServiceImpl,
peer,
serviceId,
});
};
function isConfig(arg: unknown): arg is FnConfig {
return typeof arg === "object" && arg !== null;
}
/**
* Arguments could be passed in one these configurations:
* [...actualArgs]
* [peer, ...actualArgs]
* [...actualArgs, config]
* [peer, ...actualArgs, config]
*
* This function select the appropriate configuration and returns
* arguments in a structured way of: { peer, config, args }
*/
function extractFunctionArgs(
args: unknown[],
def: FunctionCallDef,
): {
client: FluencePeer;
config: FnConfig;
args: PassedArgs;
} {
const argumentTypes = getArgumentTypes(def);
const argumentNames = Object.keys(argumentTypes);
const numberOfExpectedArgs = argumentNames.length;
let peer: FluencePeer;
let config: FnConfig;
if (isFluencePeer(args[0])) {
peer = args[0];
args = args.slice(1);
} else {
if (Fluence.defaultClient == null) {
throw new Error(
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
);
}
peer = Fluence.defaultClient;
}
const maybeConfig = args[numberOfExpectedArgs];
if (isConfig(maybeConfig)) {
config = maybeConfig;
} else {
config = {};
}
const structuredArgs = args.slice(0, numberOfExpectedArgs);
if (structuredArgs.length !== numberOfExpectedArgs) {
throw new Error(
`Incorrect number of arguments. Expecting ${numberOfExpectedArgs}`,
);
}
const argsRes = argumentNames.reduce((acc, name, index) => {
return { ...acc, [name]: structuredArgs[index] };
}, {});
return {
client: peer,
args: argsRes,
config: config,
};
}
/**
* Arguments could be passed in one these configurations:
* [serviceObject]
* [peer, serviceObject]
* [defaultId, serviceObject]
* [peer, defaultId, serviceObject]
*
* Where serviceObject is the raw object with function definitions passed by user
*
* This function select the appropriate configuration and returns
* arguments in a structured way of: { peer, serviceId, service }
*/
const extractServiceArgs = (
args: unknown[],
defaultServiceId?: string,
): {
peer: FluencePeer;
serviceId: string | undefined;
} => {
let peer: FluencePeer;
let serviceId: string | undefined;
if (isFluencePeer(args[0])) {
peer = args[0];
args = args.slice(1);
} else {
if (Fluence.defaultClient == null) {
throw new Error(
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
);
}
peer = Fluence.defaultClient;
}
if (typeof args[0] === "string") {
serviceId = args[0];
} else {
serviceId = defaultServiceId;
}
return {
peer,
serviceId,
};
};

View File

@ -60,7 +60,7 @@ export const makeClientPeerConfig = async (
relayConfig: {
peerId: keyPair.getLibp2pPeerId(),
relayAddress: relayAddress,
...(config.connectionOptions?.dialTimeoutMs != null
...(config.connectionOptions?.dialTimeoutMs !== undefined
? {
dialTimeout: config.connectionOptions.dialTimeoutMs,
}
@ -90,14 +90,6 @@ export class ClientPeer extends FluencePeer implements IFluenceClient {
);
}
getPeerId(): string {
return this.keyPair.getPeerId();
}
getPeerSecretKey(): Uint8Array {
return this.keyPair.toEd25519PrivateKey();
}
connectionState: ConnectionState = "disconnected";
connectionStateChangeHandler: (state: ConnectionState) => void = () => {};

View File

@ -15,17 +15,58 @@
*/
import { JSONValue } from "@fluencelabs/interfaces";
import { it, describe, expect } from "vitest";
import { it, describe, expect, assert } from "vitest";
import { ExpirationError } from "../../jsPeer/errors.js";
import { CallServiceData } from "../../jsServiceHost/interfaces.js";
import { doNothing } from "../../jsServiceHost/serviceUtils.js";
import { handleTimeout } from "../../particle/Particle.js";
import { registerHandlersHelper, withClient } from "../../util/testUtils.js";
import { checkConnection } from "../checkConnection.js";
import { nodes, RELAY } from "./connection.js";
const ONE_SECOND = 1000;
describe("FluenceClient usage test suite", () => {
it("Should stop particle processing after TTL is reached", async () => {
await withClient(RELAY, { defaultTtlMs: 600 }, async (peer) => {
const script = `
(seq
(call %init_peer_id% ("load" "relay") [] init_relay)
(call init_relay ("peer" "timeout") [60000 "Do you really want to wait for so long?"])
)`;
const particle = await peer.internals.createNewParticle(script);
const start = Date.now();
const promise = new Promise<JSONValue>((resolve, reject) => {
registerHandlersHelper(peer, particle, {
load: {
relay: () => {
return peer.getRelayPeerId();
},
},
callbackSrv: {
response: () => {
resolve({});
return "";
},
},
});
peer.internals.initiateParticle(particle, resolve, reject, false);
});
await expect(promise).rejects.toThrow(ExpirationError);
expect(
Date.now() - 500,
"Particle processing didn't stop after TTL is reached",
).toBeGreaterThanOrEqual(start);
});
});
it("should make a call through network", async () => {
await withClient(RELAY, {}, async (peer) => {
// arrange
@ -62,6 +103,7 @@ describe("FluenceClient usage test suite", () => {
callback: {
callback: (args): undefined => {
const [val] = args;
assert(val);
resolve(val);
},
error: (args): undefined => {
@ -71,7 +113,11 @@ describe("FluenceClient usage test suite", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(result).toBe("hello world!");
@ -124,7 +170,11 @@ describe("FluenceClient usage test suite", () => {
throw particle;
}
peer1.internals.initiateParticle(particle, doNothing);
peer1.internals.initiateParticle(
particle,
() => {},
() => {},
);
expect(await res).toEqual("test");
});
@ -172,13 +222,17 @@ describe("FluenceClient usage test suite", () => {
);
});
it("With connection options: defaultTTL", async () => {
await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => {
const isConnected = await checkConnection(peer);
it(
"With connection options: defaultTTL",
async () => {
await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => {
const isConnected = await checkConnection(peer);
expect(isConnected).toBeFalsy();
});
});
expect(isConnected).toBeFalsy();
});
},
ONE_SECOND,
);
});
it.skip("Should throw correct error when the client tries to send a particle not to the relay", async () => {
@ -206,15 +260,15 @@ describe("FluenceClient usage test suite", () => {
},
});
peer.internals.initiateParticle(particle, (stage) => {
if (stage.stage === "sendingError") {
reject(stage.errorMessage);
}
});
peer.internals.initiateParticle(
particle,
() => {},
(error: Error) => {
reject(error);
},
);
});
await promise;
await expect(promise).rejects.toMatch(
"Particle is expected to be sent to only the single peer (relay which client is connected to)",
);

View File

@ -20,6 +20,6 @@ export const nodes = [
"/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR",
peerId: "12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR",
},
];
] as const;
export const RELAY = nodes[0].multiaddr;

View File

@ -86,7 +86,7 @@ export const checkConnection = async (
const [val] = args;
setTimeout(() => {
resolve(val);
resolve(val ?? null);
}, 0);
return {};
@ -110,6 +110,7 @@ export const checkConnection = async (
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(() => {
reject("particle timed out");
}),

View File

@ -14,39 +14,53 @@
* limitations under the License.
*/
import { z } from "zod";
/**
* Peer ID's id as a base58 string (multihash/CIDv0).
*/
export type PeerIdB58 = string;
/**
* Node of the Fluence network specified as a pair of node's multiaddr and it's peer id
*/
export type Node = {
peerId: PeerIdB58;
multiaddr: string;
};
const relaySchema = z.object({
peerId: z.string(),
multiaddr: z.string(),
});
/**
* A node in Fluence network a client can connect to.
* Relay of the Fluence network specified as a pair of node's multiaddr and it's peer id
*/
export type Relay = z.infer<typeof relaySchema>;
export const relayOptionsSchema = z.union([z.string(), relaySchema]);
/**
* A relay in Fluence network a client can connect to.
* Can be in the form of:
* - string: multiaddr in string format
* - Node: node structure, @see Node
* - Relay: relay structure, @see Relay
*/
export type RelayOptions = string | Node;
export type RelayOptions = z.infer<typeof relayOptionsSchema>;
/**
* Fluence Peer's key pair types
*/
export type KeyTypes = "RSA" | "Ed25519" | "secp256k1";
const keyPairOptionsSchema = z.object({
/**
* Key pair type. Only Ed25519 is supported for now.
*/
type: z.literal("Ed25519"),
/**
* Key pair source. Could be byte array or generated randomly.
*/
source: z.union([z.literal("random"), z.instanceof(Uint8Array)]),
});
/**
* Options to specify key pair used in Fluence Peer
*/
export type KeyPairOptions = {
type: "Ed25519";
source: "random" | Uint8Array;
};
export type KeyPairOptions = z.infer<typeof keyPairOptionsSchema>;
/**
* Fluence JS Client connection states as string literals
@ -63,17 +77,10 @@ export const ConnectionStates = [
*/
export type ConnectionState = (typeof ConnectionStates)[number];
export interface IFluenceInternalApi {
/**
* Internal API
*/
internals: unknown;
}
/**
* Public API of Fluence JS Client
*/
export interface IFluenceClient extends IFluenceInternalApi {
export interface IFluenceClient {
/**
* Connect to the Fluence network
*/
@ -91,11 +98,6 @@ export interface IFluenceClient extends IFluenceInternalApi {
handler: (state: ConnectionState) => void,
): ConnectionState;
/**
* Return peer's secret key as byte array.
*/
getPeerSecretKey(): Uint8Array;
/**
* Return peer's public key as a base58 string (multihash/CIDv0).
*/
@ -107,65 +109,66 @@ export interface IFluenceClient extends IFluenceInternalApi {
getRelayPeerId(): string;
}
export const configSchema = z
.object({
/**
* Specify the KeyPair to be used to identify the Fluence Peer.
* Will be generated randomly if not specified
*/
keyPair: keyPairOptionsSchema,
/**
* Options to configure the connection to the Fluence network
*/
connectionOptions: z
.object({
/**
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
* The options allows to specify the timeout for that message in milliseconds.
* If not specified the default timeout will be used
*/
skipCheckConnection: z.boolean(),
/**
* The dialing timeout in milliseconds
*/
dialTimeoutMs: z.number(),
/**
* The maximum number of inbound streams for the libp2p node.
* Default: 1024
*/
maxInboundStreams: z.number(),
/**
* The maximum number of outbound streams for the libp2p node.
* Default: 1024
*/
maxOutboundStreams: z.number(),
})
.partial(),
/**
* Sets the default TTL for all particles originating from the peer with no TTL specified.
* If the originating particle's TTL is defined then that value will be used
* If the option is not set default TTL will be 7000
*/
defaultTtlMs: z.number(),
/**
* Property for passing custom CDN Url to load dependencies from browser. https://unpkg.com used by default
*/
CDNUrl: z.string(),
/**
* Enables\disabled various debugging features
*/
debug: z
.object({
/**
* If set to true, newly initiated particle ids will be printed to console.
* Useful to see what particle id is responsible for aqua function
*/
printParticleId: z.boolean(),
})
.partial(),
})
.partial();
/**
* Configuration used when initiating Fluence Client
*/
export interface ClientConfig {
/**
* Specify the KeyPair to be used to identify the Fluence Peer.
* Will be generated randomly if not specified
*/
keyPair?: KeyPairOptions;
/**
* Options to configure the connection to the Fluence network
*/
connectionOptions?: {
/**
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
* The options allows to specify the timeout for that message in milliseconds.
* If not specified the default timeout will be used
*/
skipCheckConnection?: boolean;
/**
* The dialing timeout in milliseconds
*/
dialTimeoutMs?: number;
/**
* The maximum number of inbound streams for the libp2p node.
* Default: 1024
*/
maxInboundStreams?: number;
/**
* The maximum number of outbound streams for the libp2p node.
* Default: 1024
*/
maxOutboundStreams?: number;
};
/**
* Sets the default TTL for all particles originating from the peer with no TTL specified.
* If the originating particle's TTL is defined then that value will be used
* If the option is not set default TTL will be 7000
*/
defaultTtlMs?: number;
/**
* Property for passing custom CDN Url to load dependencies from browser. https://unpkg.com used by default
*/
CDNUrl?: string;
/**
* Enables\disabled various debugging features
*/
debug?: {
/**
* If set to true, newly initiated particle ids will be printed to console.
* Useful to see what particle id is responsible for aqua function
*/
printParticleId?: boolean;
};
}
export type ClientConfig = z.infer<typeof configSchema>;

View File

@ -14,10 +14,10 @@
* limitations under the License.
*/
import { JSONValue, NonArrowType } from "@fluencelabs/interfaces";
import { JSONValue, NonArrowSimpleType } from "@fluencelabs/interfaces";
import { it, describe, expect, test } from "vitest";
import { aqua2ts, ts2aqua } from "../conversions.js";
import { aqua2js, js2aqua } from "../conversions.js";
const i32 = { tag: "scalar", name: "i32" } as const;
@ -76,7 +76,7 @@ const structs = [
c: [null, 2],
},
},
];
] as const;
const labeledProduct2 = {
tag: "labeledProduct",
@ -167,18 +167,19 @@ const nestedStructs = [
c: [],
},
},
];
] as const;
interface ConversionTestArgs {
aqua: JSONValue;
ts: JSONValue;
type: NonArrowType;
type: NonArrowSimpleType;
}
describe("Conversion from aqua to typescript", () => {
test.each`
aqua | ts | type
${1} | ${1} | ${i32}
${null} | ${null} | ${opt_i32}
${[]} | ${null} | ${opt_i32}
${[1]} | ${1} | ${opt_i32}
${[1, 2, 3]} | ${[1, 2, 3]} | ${array_i32}
@ -200,12 +201,16 @@ describe("Conversion from aqua to typescript", () => {
// arrange
// act
const tsFromAqua = aqua2ts(aqua, type);
const aquaFromTs = ts2aqua(ts, type);
const tsFromAqua = aqua2js(aqua, type, { path: [] });
const aquaFromTs = js2aqua(ts, type, { path: [] });
// assert
expect(tsFromAqua).toStrictEqual(ts);
expect(aquaFromTs).toStrictEqual(aqua);
// 'null' -> 'null' -> [] ; 'null' not equal []
if (aqua !== null || ts !== null) {
expect(aquaFromTs).toStrictEqual(aqua);
}
},
);
});
@ -231,8 +236,8 @@ describe("Conversion corner cases", () => {
};
// act
const aqua = ts2aqua(valueInTs, type);
const ts = aqua2ts(valueInAqua, type);
const aqua = js2aqua(valueInTs, type, { path: [] });
const ts = aqua2js(valueInAqua, type, { path: [] });
// assert
expect(aqua).toStrictEqual({

View File

@ -14,18 +14,11 @@
* limitations under the License.
*/
import assert from "assert";
import {
FnConfig,
FunctionCallDef,
getArgumentTypes,
isReturnTypeVoid,
PassedArgs,
} from "@fluencelabs/interfaces";
import { JSONValue } from "@fluencelabs/interfaces";
import { FluencePeer } from "../jsPeer/FluencePeer.js";
import { logger } from "../util/logger.js";
import { ArgCallbackFunction } from "../util/testUtils.js";
import {
errorHandlingService,
@ -51,95 +44,49 @@ const log = logger("aqua");
* @returns
*/
type CallAquaFunctionArgs = {
def: FunctionCallDef;
export type CallAquaFunctionArgs = {
script: string;
config: FnConfig;
config?: CallAquaFunctionConfig | undefined;
peer: FluencePeer;
args: PassedArgs;
args: { [key: string]: JSONValue | ArgCallbackFunction };
fireAndForget: boolean;
};
export type CallAquaFunctionConfig = {
ttl?: number;
};
export const callAquaFunction = async ({
def,
script,
config,
config = {},
peer,
args,
fireAndForget,
}: CallAquaFunctionArgs) => {
// TODO: this function should be rewritten. We can remove asserts if we wont check definition there
log.trace("calling aqua function %j", { def, script, config, args });
const argumentTypes = getArgumentTypes(def);
log.trace("calling aqua function %j", { script, config, args });
const particle = await peer.internals.createNewParticle(script, config.ttl);
return new Promise((resolve, reject) => {
return new Promise<JSONValue>((resolve, reject) => {
// Registering function args as a services
for (const [name, argVal] of Object.entries(args)) {
const type = argumentTypes[name];
let service: ServiceDescription;
if (type.tag === "arrow") {
// TODO: Add validation here
assert(
typeof argVal === "function",
"Should not be possible, bad types",
);
service = userHandlerService(
def.names.callbackSrv,
[name, type],
argVal,
);
if (typeof argVal === "function") {
service = userHandlerService("callbackSrv", name, argVal);
} else {
// TODO: Add validation here
assert(
typeof argVal !== "function",
"Should not be possible, bad types",
);
service = injectValueService(def.names.getDataSrv, name, type, argVal);
service = injectValueService("getDataSrv", name, argVal);
}
registerParticleScopeService(peer, particle, service);
}
registerParticleScopeService(peer, particle, responseService(def, resolve));
registerParticleScopeService(peer, particle, responseService(resolve));
registerParticleScopeService(peer, particle, injectRelayService(def, peer));
registerParticleScopeService(peer, particle, injectRelayService(peer));
registerParticleScopeService(
peer,
particle,
errorHandlingService(def, reject),
);
registerParticleScopeService(peer, particle, errorHandlingService(reject));
peer.internals.initiateParticle(particle, (stage) => {
// If function is void, then it's completed when one of the two conditions is met:
// 1. The particle is sent to the network (state 'sent')
// 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone')
if (
isReturnTypeVoid(def) &&
(stage.stage === "sent" || stage.stage === "localWorkDone")
) {
resolve(undefined);
}
if (stage.stage === "sendingError") {
reject(
`Could not send particle for ${def.functionName}: not connected (particle id: ${particle.id})`,
);
}
if (stage.stage === "expired") {
reject(
`Particle expired after ttl of ${particle.ttl}ms for function ${def.functionName} (particle id: ${particle.id})`,
);
}
if (stage.stage === "interpreterError") {
reject(
`Script interpretation failed for ${def.functionName}: ${stage.errorMessage} (particle id: ${particle.id})`,
);
}
});
peer.internals.initiateParticle(particle, resolve, reject, fireAndForget);
});
};

View File

@ -14,222 +14,252 @@
* limitations under the License.
*/
// TODO: This file is a mess. Need to refactor it later
/* eslint-disable */
// @ts-nocheck
import assert from "assert";
import type {
import {
ArrowType,
ArrowWithoutCallbacks,
JSONArray,
JSONValue,
NonArrowType,
LabeledProductType,
NonArrowSimpleType,
ScalarType,
SimpleTypes,
UnlabeledProductType,
} from "@fluencelabs/interfaces";
import { match } from "ts-pattern";
import { CallServiceData } from "../jsServiceHost/interfaces.js";
import { jsonify } from "../util/utils.js";
import { zip } from "../util/utils.js";
/**
* Convert value from its representation in aqua language to representation in typescript
* @param value - value as represented in aqua
* @param type - definition of the aqua type
* @returns value represented in typescript
*/
export const aqua2ts = (value: JSONValue, type: NonArrowType): JSONValue => {
const res = match(type)
.with({ tag: "nil" }, () => {
import { ServiceImpl, UserServiceImpl } from "./types.js";
export class SchemaValidationError extends Error {
constructor(
public path: string[],
schema: NonArrowSimpleType | ArrowWithoutCallbacks,
expected: string,
provided: JSONValue | UserServiceImpl[string],
) {
const given =
provided === null
? "null"
: Array.isArray(provided)
? "array"
: typeof provided;
const message = `Aqua type mismatch. Path: ${path.join(
".",
)}; Expected: ${expected}; Given: ${given}; \nSchema: ${JSON.stringify(
schema,
)}; \nTry recompiling rust services and aqua. Make sure you are using up-to-date versions of aqua libraries`;
super(message);
}
}
interface ValidationContext {
path: string[];
}
const numberTypes = [
"u8",
"u16",
"u32",
"u64",
"i8",
"i16",
"i32",
"i64",
"f32",
"f64",
] as const;
function isScalar(
schema: ScalarType,
arg: JSONValue,
{ path }: ValidationContext,
) {
if (numberTypes.includes(schema.name)) {
if (typeof arg !== "number") {
throw new SchemaValidationError(path, schema, "number", arg);
}
} else if (schema.name === "bool") {
if (typeof arg !== "boolean") {
throw new SchemaValidationError(path, schema, "boolean", arg);
}
} else if (schema.name === "string") {
if (typeof arg !== "string") {
throw new SchemaValidationError(path, schema, "string", arg);
}
} else {
throw new SchemaValidationError(path, schema, schema.name, arg);
}
return arg;
}
export function aqua2js(
value: JSONValue,
schema: NonArrowSimpleType,
{ path }: ValidationContext,
): JSONValue {
if (schema.tag === "nil") {
return null;
} else if (schema.tag === "option") {
if (!Array.isArray(value) && value !== null) {
throw new SchemaValidationError(path, schema, "array or null", value);
}
if (value !== null && "0" in value) {
return aqua2js(value[0], schema.type, { path: [...path, "?"] });
} else {
return null;
})
.with({ tag: "option" }, (opt) => {
assert(Array.isArray(value), "Should not be possible, bad types");
}
} else if (
schema.tag === "scalar" ||
schema.tag === "bottomType" ||
schema.tag === "topType"
) {
return value;
} else if (schema.tag === "array") {
if (!Array.isArray(value)) {
throw new SchemaValidationError([], schema, "array", value);
}
if (value.length === 0) {
return null;
} else {
return aqua2ts(value[0], opt.type);
}
})
.with({ tag: "scalar" }, { tag: "bottomType" }, { tag: "topType" }, () => {
return value;
})
.with({ tag: "array" }, (arr) => {
assert(Array.isArray(value), "Should not be possible, bad types");
return value.map((y) => {
return aqua2ts(y, arr.type);
});
})
.with({ tag: "struct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = aqua2ts(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "labeledProduct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = aqua2ts(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "unlabeledProduct" }, (x) => {
return x.items.map((type, index) => {
return aqua2ts(value[index], type);
});
})
// uncomment to check that every pattern in matched
// .exhaustive();
.otherwise(() => {
throw new Error("Unexpected tag: " + jsonify(type));
return value.map((y, i) => {
return aqua2js(y, schema.type, { path: [...path, `[${i}]`] });
});
} else if (schema.tag === "unlabeledProduct") {
if (!Array.isArray(value)) {
throw new SchemaValidationError([], schema, "array", value);
}
return res;
};
/**
* Convert call service arguments list from their aqua representation to representation in typescript
* @param req - call service data
* @param arrow - aqua type definition
* @returns arguments in typescript representation
*/
export const aquaArgs2Ts = (
req: CallServiceData,
arrow: ArrowWithoutCallbacks,
): JSONArray => {
const argTypes = match(arrow.domain)
.with({ tag: "labeledProduct" }, (x) => {
return Object.values(x.fields);
})
.with({ tag: "unlabeledProduct" }, (x) => {
return x.items;
})
.with({ tag: "nil" }, (x) => {
return [];
})
// uncomment to check that every pattern in matched
// .exhaustive()
.otherwise(() => {
throw new Error("Unexpected tag: " + jsonify(arrow.domain));
return zip(value, schema.items).map(([v, s], i) => {
return aqua2js(v, s, { path: [...path, `[${i}]`] });
});
} else if (["labeledProduct", "struct"].includes(schema.tag)) {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new SchemaValidationError([], schema, "object", value);
}
if (req.args.length !== argTypes.length) {
throw new Error(
`incorrect number of arguments, expected: ${argTypes.length}, got: ${req.args.length}`,
return Object.fromEntries(
Object.entries(schema.fields).map(([key, type]) => {
let v = value[key];
if (v === undefined) {
v = null;
}
const val = aqua2js(v, type, { path: [...path, key] });
return [key, val];
}),
);
} else {
throw new SchemaValidationError([], schema, "never", value);
}
}
return req.args.map((arg, index) => {
return aqua2ts(arg, argTypes[index]);
});
};
export function js2aqua(
value: JSONValue,
schema: NonArrowSimpleType,
{ path }: ValidationContext,
): JSONValue {
if (schema.tag === "nil") {
if (value !== null) {
throw new SchemaValidationError(path, schema, "null", value);
}
/**
* Convert value from its typescript representation to representation in aqua
* @param value - the value as represented in typescript
* @param type - definition of the aqua type
* @returns value represented in aqua
*/
export const ts2aqua = (value: JSONValue, type: NonArrowType): JSONValue => {
const res = match(type)
.with({ tag: "nil" }, () => {
return null;
})
.with({ tag: "option" }, (opt) => {
if (value === null || value === undefined) {
return [];
} else {
return [ts2aqua(value, opt.type)];
}
})
.with({ tag: "scalar" }, { tag: "bottomType" }, { tag: "topType" }, () => {
return value;
})
.with({ tag: "array" }, (arr) => {
assert(Array.isArray(value), "Should not be possible, bad types");
return value.map((y) => {
return ts2aqua(y, arr.type);
});
})
.with({ tag: "struct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = ts2aqua(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "labeledProduct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = ts2aqua(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "unlabeledProduct" }, (x) => {
return x.items.map((type, index) => {
return ts2aqua(value[index], type);
});
})
// uncomment to check that every pattern in matched
// .exhaustive()
.otherwise(() => {
throw new Error("Unexpected tag: " + jsonify(type));
return value;
} else if (schema.tag === "option") {
// option means 'type | null'
return value === null
? []
: [js2aqua(value, schema.type, { path: [...path, "?"] })];
} else if (schema.tag === "topType") {
// topType equals to 'any'
return value;
} else if (schema.tag === "bottomType") {
// bottomType equals to 'never'
throw new SchemaValidationError(path, schema, "never", value);
} else if (schema.tag === "scalar") {
return isScalar(schema, value, { path });
} else if (schema.tag === "array") {
if (!Array.isArray(value)) {
throw new SchemaValidationError(path, schema, "array", value);
}
return value.map((y, i) => {
return js2aqua(y, schema.type, { path: [...path, `[${i}]`] });
});
} else if (schema.tag === "unlabeledProduct") {
if (!Array.isArray(value)) {
throw new SchemaValidationError(path, schema, "array", value);
}
return zip(value, schema.items).map(([v, s], i) => {
return js2aqua(v, s, { path: [...path, `[${i}]`] });
});
} else if (["labeledProduct", "struct"].includes(schema.tag)) {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new SchemaValidationError(path, schema, "object", value);
}
return Object.fromEntries(
Object.entries(schema.fields).map(([key, type]) => {
let v = value[key];
if (v === undefined) {
v = null;
}
const val = js2aqua(v, type, { path: [...path, key] });
return [key, val];
}),
);
} else {
throw new SchemaValidationError(path, schema, "never", value);
}
}
// Wrapping function, converting its arguments to aqua before call and back to js after call.
// It makes callbacks and service functions defined by user operate on js types seamlessly
export const wrapJsFunction = (
func: UserServiceImpl[string],
schema:
| ArrowWithoutCallbacks
| ArrowType<LabeledProductType<SimpleTypes> | UnlabeledProductType>,
funcName: string,
): ServiceImpl[string] => {
return async ({ args, context }) => {
const schemaArgs =
schema.domain.tag === "nil"
? []
: schema.domain.tag === "unlabeledProduct"
? schema.domain.items
: Object.values(schema.domain.fields);
if (schemaArgs.length !== args.length) {
throw new Error(
`Schema and generated air doesn't match. Air has been called with ${args.length} args and schema contains ${schemaArgs.length} args`,
);
}
const jsArgs = zip(args, schemaArgs).map(([arg, schemaArg], i) => {
return aqua2js(arg, schemaArg, { path: [`${funcName}Args`, `[${i}]`] });
});
return res;
};
/**
* Convert return type of the service from it's typescript representation to representation in aqua
* @param returnValue - the value as represented in typescript
* @param arrowType - the arrow type which describes the service
* @returns - value represented in aqua
*/
export const returnType2Aqua = (
returnValue: any,
arrowType: ArrowType<NonArrowType>,
) => {
if (arrowType.codomain.tag === "nil") {
return {};
}
if (arrowType.codomain.items.length === 0) {
return {};
}
if (arrowType.codomain.items.length === 1) {
return ts2aqua(returnValue, arrowType.codomain.items[0]);
}
return arrowType.codomain.items.map((type, index) => {
return ts2aqua(returnValue[index], type);
});
};
/**
* Converts response value from aqua its representation to representation in typescript
* @param req - call service data
* @param arrow - aqua type definition
* @returns response value in typescript representation
*/
export const responseServiceValue2ts = (
req: CallServiceData,
arrow: ArrowType<any>,
) => {
return match(arrow.codomain)
.with({ tag: "nil" }, () => {
return null;
})
.with({ tag: "unlabeledProduct" }, (x) => {
if (x.items.length === 0) {
return null;
}
if (x.items.length === 1) {
return aqua2ts(req.args[0], x.items[0]);
}
return req.args.map((y, index) => {
return aqua2ts(y, x.items[index]);
});
})
.exhaustive();
const returnTypeVoid =
schema.codomain.tag === "nil" || schema.codomain.items.length === 0;
const resultSchema =
schema.codomain.tag === "unlabeledProduct" &&
schema.codomain.items.length === 1 &&
"0" in schema.codomain.items
? schema.codomain.items[0]
: schema.codomain;
let result = await func(...jsArgs, context);
if (returnTypeVoid) {
result = null;
}
return js2aqua(result, resultSchema, { path: [`${funcName}ReturnValue`] });
};
};

View File

@ -14,66 +14,60 @@
* limitations under the License.
*/
import type { ServiceDef, ServiceImpl } from "@fluencelabs/interfaces";
import { FluencePeer } from "../jsPeer/FluencePeer.js";
import { logger } from "../util/logger.js";
import { registerGlobalService, userHandlerService } from "./services.js";
import { ServiceImpl } from "./types.js";
const log = logger("aqua");
interface RegisterServiceArgs {
peer: FluencePeer;
def: ServiceDef;
serviceId: string | undefined;
serviceId: string;
service: ServiceImpl;
}
type ServiceFunctionPair = [key: string, value: ServiceImpl[string]];
// This function iterates on plain object or class instance functions ignoring inherited functions and prototype chain.
const findAllPossibleRegisteredServiceFunctions = (
service: ServiceImpl,
): Array<ServiceFunctionPair> => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const prototype = Object.getPrototypeOf(service) as ServiceImpl;
const isClassInstance = prototype.constructor !== Object;
if (isClassInstance) {
service = prototype;
}
return Object.getOwnPropertyNames(service)
.map((prop) => {
return [prop, service[prop]];
})
.filter((entry): entry is ServiceFunctionPair => {
const [prop, value] = entry;
return typeof value === "function" && prop !== "constructor";
});
};
export const registerService = ({
peer,
def,
serviceId = def.defaultServiceId,
serviceId,
service,
}: RegisterServiceArgs) => {
// TODO: Need to refactor this. We can compute function types from service implementation, making func more type safe
log.trace("registering aqua service %o", { def, serviceId, service });
log.trace("registering aqua service %o", { serviceId, service });
// Checking for missing keys
const requiredKeys =
def.functions.tag === "nil" ? [] : Object.keys(def.functions.fields);
const serviceFunctions = findAllPossibleRegisteredServiceFunctions(service);
const incorrectServiceDefinitions = requiredKeys.filter((f) => {
return !(f in service);
});
if (serviceId == null) {
throw new Error("Service ID must be specified");
}
if (incorrectServiceDefinitions.length > 0) {
throw new Error(
`Error registering service ${serviceId}: missing functions: ` +
incorrectServiceDefinitions
.map((d) => {
return "'" + d + "'";
})
.join(", "),
);
}
const singleFunctions =
def.functions.tag === "nil" ? [] : Object.entries(def.functions.fields);
for (const singleFunction of singleFunctions) {
const [name] = singleFunction;
// The function has type of (arg1, arg2, arg3, ... , callParams) => CallServiceResultType | void
// Account for the fact that user service might be defined as a class - .bind(...)
const userDefinedHandler = service[name].bind(service);
for (const [serviceFunction, handler] of serviceFunctions) {
const userDefinedHandler = handler.bind(service);
const serviceDescription = userHandlerService(
serviceId,
singleFunction,
serviceFunction,
userDefinedHandler,
);

View File

@ -14,17 +14,7 @@
* limitations under the License.
*/
import { SecurityTetraplet } from "@fluencelabs/avm";
import {
CallParams,
ArrowWithoutCallbacks,
FunctionCallDef,
NonArrowType,
ServiceImpl,
JSONValue,
} from "@fluencelabs/interfaces";
import { fromUint8Array } from "js-base64";
import { match } from "ts-pattern";
import { JSONValue } from "@fluencelabs/interfaces";
import { FluencePeer } from "../jsPeer/FluencePeer.js";
import {
@ -34,12 +24,7 @@ import {
} from "../jsServiceHost/interfaces.js";
import { Particle } from "../particle/Particle.js";
import {
aquaArgs2Ts,
responseServiceValue2ts,
returnType2Aqua,
ts2aqua,
} from "./conversions.js";
import { ServiceImpl } from "./types.js";
export interface ServiceDescription {
serviceId: string;
@ -50,10 +35,10 @@ export interface ServiceDescription {
/**
* Creates a service which injects relay's peer id into aqua space
*/
export const injectRelayService = (def: FunctionCallDef, peer: FluencePeer) => {
export const injectRelayService = (peer: FluencePeer) => {
return {
serviceId: def.names.getDataSrv,
fnName: def.names.relay,
serviceId: "getDataSrv",
fnName: "-relay-",
handler: () => {
return {
retCode: ResultCodes.success,
@ -69,7 +54,6 @@ export const injectRelayService = (def: FunctionCallDef, peer: FluencePeer) => {
export const injectValueService = (
serviceId: string,
fnName: string,
valueType: NonArrowType,
value: JSONValue,
) => {
return {
@ -78,7 +62,7 @@ export const injectValueService = (
handler: () => {
return {
retCode: ResultCodes.success,
result: ts2aqua(value, valueType),
result: value,
};
},
};
@ -87,15 +71,17 @@ export const injectValueService = (
/**
* Creates a service which is used to return value from aqua function into typescript space
*/
export const responseService = (
def: FunctionCallDef,
resolveCallback: (val: JSONValue) => void,
) => {
export const responseService = (resolveCallback: (val: JSONValue) => void) => {
return {
serviceId: def.names.responseSrv,
fnName: def.names.responseFnName,
serviceId: "callbackSrv",
fnName: "response",
handler: (req: CallServiceData) => {
const userFunctionReturn = responseServiceValue2ts(req, def.arrow);
const userFunctionReturn =
req.args.length === 0
? null
: req.args.length === 1 && "0" in req.args
? req.args[0]
: req.args;
setTimeout(() => {
resolveCallback(userFunctionReturn);
@ -113,17 +99,19 @@ export const responseService = (
* Creates a service which is used to return errors from aqua function into typescript space
*/
export const errorHandlingService = (
def: FunctionCallDef,
rejectCallback: (err: JSONValue) => void,
) => {
return {
serviceId: def.names.errorHandlingSrv,
fnName: def.names.errorFnName,
serviceId: "errorHandlingSrv",
fnName: "error",
handler: (req: CallServiceData) => {
const [err] = req.args;
setTimeout(() => {
rejectCallback(err);
rejectCallback(
err ??
"Unknown error happened when executing aqua code. No error text was passed to 'errorHandlingSrv.error' function, probably because AIR code was modified or aqua compiler didn't produce the correct call",
);
}, 0);
return {
@ -139,21 +127,19 @@ export const errorHandlingService = (
*/
export const userHandlerService = (
serviceId: string,
arrowType: [string, ArrowWithoutCallbacks],
fnName: string,
userHandler: ServiceImpl[string],
) => {
const [fnName, type] = arrowType;
return {
serviceId,
fnName,
handler: async (req: CallServiceData) => {
const args: [...JSONValue[], CallParams<string>] = [
...aquaArgs2Ts(req, type),
extractCallParams(req, type),
];
const { args, particleContext: context } = req;
const rawResult = await userHandler.bind(null)(...args);
const result = returnType2Aqua(rawResult, type);
const result = await userHandler.bind(null)({
args,
context,
});
return {
retCode: ResultCodes.success,
@ -163,46 +149,6 @@ export const userHandlerService = (
};
};
/**
* Extracts call params from from call service data according to aqua type definition
*/
const extractCallParams = (
req: CallServiceData,
arrow: ArrowWithoutCallbacks,
): CallParams<string> => {
const names: (string | undefined)[] = match(arrow.domain)
.with({ tag: "nil" }, () => {
return [];
})
.with({ tag: "unlabeledProduct" }, (x) => {
return x.items.map((_, index) => {
return "arg" + index;
});
})
.with({ tag: "labeledProduct" }, (x) => {
return Object.keys(x.fields);
})
.exhaustive();
const tetraplets: Record<string, SecurityTetraplet[]> = {};
for (let i = 0; i < req.args.length; i++) {
const name = names[i];
if (name != null) {
tetraplets[name] = req.tetraplets[i];
}
}
const callParams = {
...req.particleContext,
signature: fromUint8Array(req.particleContext.signature),
tetraplets,
};
return callParams;
};
export const registerParticleScopeService = (
peer: FluencePeer,
particle: Particle,

View File

@ -14,8 +14,23 @@
* limitations under the License.
*/
export type InterfaceToType<T extends object> = {
[K in keyof T]: T[K];
};
import { JSONArray, JSONValue } from "@fluencelabs/interfaces";
import { ParticleContext } from "../jsServiceHost/interfaces.js";
export type MaybePromise<T> = T | Promise<T>;
export type ServiceImpl = Record<
string,
(args: {
args: JSONArray;
context: ParticleContext;
}) => MaybePromise<JSONValue>
>;
export type UserServiceImpl = Record<
string,
(...args: [...JSONArray, ParticleContext]) => MaybePromise<JSONValue>
>;
export type ServiceFnArgs<T> = { args: T; context: ParticleContext };

View File

@ -17,9 +17,10 @@
import { noise } from "@chainsafe/libp2p-noise";
import { yamux } from "@chainsafe/libp2p-yamux";
import { PeerIdB58 } from "@fluencelabs/interfaces";
import { Stream } from "@libp2p/interface/connection";
import type { PeerId } from "@libp2p/interface/peer-id";
import { identify } from "@libp2p/identify";
import type { PeerId } from "@libp2p/interface";
import { peerIdFromString } from "@libp2p/peer-id";
import { ping } from "@libp2p/ping";
import { webSockets } from "@libp2p/websockets";
import { all } from "@libp2p/websockets/filters";
import { multiaddr, type Multiaddr } from "@multiformats/multiaddr";
@ -27,18 +28,14 @@ import { decode, encode } from "it-length-prefixed";
import map from "it-map";
import { pipe } from "it-pipe";
import { createLibp2p, Libp2p } from "libp2p";
import { identifyService } from "libp2p/identify";
import { pingService } from "libp2p/ping";
import { Subject } from "rxjs";
import { fromString } from "uint8arrays/from-string";
import { toString } from "uint8arrays/to-string";
import { KeyPair } from "../keypair/index.js";
import { IParticle } from "../particle/interfaces.js";
import {
buildParticleMessage,
Particle,
serializeToString,
serializeParticle,
} from "../particle/Particle.js";
import { throwHasNoPeerId } from "../util/libp2pUtils.js";
import { logger } from "../util/logger.js";
@ -81,6 +78,23 @@ export interface RelayConnectionConfig {
maxOutboundStreams: number;
}
type DenyCondition = (ma: Multiaddr) => boolean;
const dockerNoxDenyCondition: DenyCondition = (ma) => {
const [routingProtocol] = ma.stringTuples();
const host = routingProtocol?.[1];
// Nox proposes 3 multiaddr to discover when used inside docker network,
// e.g.: [/dns/nox-1, /ip4/10.50.10.10, /ip4/127.0.0.1]
// First 2 of them are unreachable outside the docker network
// Libp2p cannot handle these scenarios correctly, creating interruptions which affect e2e tests execution.
return (
host === undefined || host.startsWith("nox-") || host.startsWith("10.50.10")
);
};
const denyConditions = [dockerNoxDenyCondition];
/**
* Implementation for JS peers which connects to Fluence through relay node
*/
@ -93,7 +107,7 @@ export class RelayConnection implements IConnection {
this.relayAddress = multiaddr(this.config.relayAddress);
const peerId = this.relayAddress.getPeerId();
if (peerId == null) {
if (peerId === null) {
throwHasNoPeerId(this.relayAddress);
}
@ -126,21 +140,25 @@ export class RelayConnection implements IConnection {
streamMuxers: [yamux()],
connectionEncryption: [noise()],
connectionManager: {
...(this.config.dialTimeoutMs != null
...(this.config.dialTimeoutMs !== undefined
? {
dialTimeout: this.config.dialTimeoutMs,
}
: {}),
},
connectionGater: {
// By default, this function forbids connections to private peers. For example multiaddr with ip 127.0.0.1 isn't allowed
denyDialMultiaddr: () => {
return Promise.resolve(false);
// By default, this function forbids connections to private peers. For example, multiaddr with ip 127.0.0.1 isn't allowed
denyDialMultiaddr: (ma: Multiaddr) => {
return Promise.resolve(
denyConditions.some((dc) => {
return dc(ma);
}),
);
},
},
services: {
identify: identifyService(),
ping: pingService(),
identify: identify(),
ping: ping(),
},
});
@ -181,33 +199,39 @@ export class RelayConnection implements IConnection {
);
}
log.trace("sending particle...");
// Reusing active connection here
const stream = await this.lib2p2Peer.dialProtocol(
this.relayAddress,
PROTOCOL_NAME,
);
log.trace("created stream with id ", stream.id);
log.trace(
"sending particle %s to %s",
particle.id,
this.relayAddress.toString(),
);
const sink = stream.sink;
await pipe([fromString(serializeToString(particle))], encode(), sink);
log.trace("data written to sink");
await pipe([serializeParticle(particle)], encode, sink);
log.trace(
"particle %s sent to %s",
particle.id,
this.relayAddress.toString(),
);
}
// Await will appear after uncommenting lines in func body
// eslint-disable-next-line @typescript-eslint/require-await
private async processIncomingMessage(msg: string, stream: Stream) {
private async processIncomingMessage(msg: Uint8Array) {
let particle: Particle | undefined;
try {
particle = Particle.fromString(msg);
particle = Particle.deserialize(msg);
log.trace(
"got particle from stream with id %s and particle id %s",
stream.id,
"received particle %s from %s",
particle.id,
this.relayAddress.toString(),
);
const initPeerId = peerIdFromString(particle.initPeerId);
@ -260,16 +284,16 @@ export class RelayConnection implements IConnection {
({ stream }) => {
void pipe(
stream.source,
decode(),
decode,
(source) => {
return map(source, (buf) => {
return toString(buf.subarray());
return buf.subarray();
});
},
async (source) => {
try {
for await (const msg of source) {
await this.processIncomingMessage(msg, stream);
await this.processIncomingMessage(msg);
}
} catch (e) {
log.error("connection closed: %j", e);

View File

@ -14,13 +14,15 @@
* limitations under the License.
*/
import { it, describe, expect, beforeEach, afterEach } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { DEFAULT_CONFIG, FluencePeer } from "../../jsPeer/FluencePeer.js";
import { ResultCodes } from "../../jsServiceHost/interfaces.js";
import { KeyPair } from "../../keypair/index.js";
import { loadMarineDeps } from "../../marine/loader.js";
import { MarineBackgroundRunner } from "../../marine/worker/index.js";
import { EphemeralNetworkClient } from "../client.js";
import { EphemeralNetwork, defaultConfig } from "../network.js";
import { defaultConfig, EphemeralNetwork } from "../network.js";
let en: EphemeralNetwork;
let client: FluencePeer;
@ -33,7 +35,11 @@ describe.skip("Ephemeral networks tests", () => {
await en.up();
const kp = await KeyPair.randomEd25519();
client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, en, relay);
const marineDeps = await loadMarineDeps("/");
const marine = new MarineBackgroundRunner(...marineDeps);
client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, marine, en, relay);
await client.start();
});
@ -91,7 +97,11 @@ describe.skip("Ephemeral networks tests", () => {
});
// act
client.internals.initiateParticle(particle, () => {});
client.internals.initiateParticle(
particle,
() => {},
() => {},
);
// assert
await expect(promise).resolves.toBe("success");

View File

@ -19,9 +19,7 @@ import { PeerIdB58 } from "@fluencelabs/interfaces";
import { FluencePeer, PeerConfig } from "../jsPeer/FluencePeer.js";
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
import { KeyPair } from "../keypair/index.js";
import { WasmLoaderFromNpm } from "../marine/deps-loader/node.js";
import { MarineBackgroundRunner } from "../marine/worker/index.js";
import { WorkerLoader } from "../marine/worker-script/workerLoader.js";
import { IMarineHost } from "../marine/interfaces.js";
import { EphemeralNetwork } from "./network.js";
@ -32,28 +30,12 @@ export class EphemeralNetworkClient extends FluencePeer {
constructor(
config: PeerConfig,
keyPair: KeyPair,
marine: IMarineHost,
network: EphemeralNetwork,
relay: PeerIdB58,
) {
const workerLoader = new WorkerLoader();
const controlModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/marine-js",
"marine-js.wasm",
);
const avmModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/avm",
"avm.wasm",
);
const marine = new MarineBackgroundRunner(
workerLoader,
controlModuleLoader,
avmModuleLoader,
);
const conn = network.getRelayConnection(keyPair.getPeerId(), relay);
super(config, keyPair, marine, new JsServiceHost(), conn);
}
}

View File

@ -21,11 +21,8 @@ import { IConnection } from "../connection/interfaces.js";
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
import { fromBase64Sk, KeyPair } from "../keypair/index.js";
import {
WorkerLoaderFromFs,
WasmLoaderFromNpm,
} from "../marine/deps-loader/node.js";
import { IMarineHost } from "../marine/interfaces.js";
import { loadMarineDeps } from "../marine/loader.js";
import { MarineBackgroundRunner } from "../marine/worker/index.js";
import { Particle } from "../particle/Particle.js";
import { logger } from "../util/logger.js";
@ -33,10 +30,12 @@ import { logger } from "../util/logger.js";
const log = logger("ephemeral");
interface EphemeralConfig {
peers: Array<{
peerId: PeerIdB58;
sk: string;
}>;
peers: Readonly<
Array<{
peerId: PeerIdB58;
sk: string;
}>
>;
}
export const defaultConfig = {
@ -122,7 +121,7 @@ export const defaultConfig = {
sk: "RbgKmG6soWW9uOi7yRedm+0Qck3f3rw6MSnDP7AcBQs=",
},
],
};
} as const;
export interface IEphemeralConnection extends IConnection {
readonly selfPeerId: PeerIdB58;
@ -224,24 +223,7 @@ class EphemeralPeer extends FluencePeer {
export class EphemeralNetwork {
private peers: Map<PeerIdB58, EphemeralPeer> = new Map();
workerLoader: WorkerLoaderFromFs;
controlModuleLoader: WasmLoaderFromNpm;
avmModuleLoader: WasmLoaderFromNpm;
constructor(readonly config: EphemeralConfig) {
// shared worker for all the peers
this.workerLoader = new WorkerLoaderFromFs("../../marine/worker-script");
this.controlModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/marine-js",
"marine-js.wasm",
);
this.avmModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/avm",
"avm.wasm",
);
}
constructor(readonly config: EphemeralConfig) {}
/**
* Starts the Ephemeral network up
@ -252,11 +234,8 @@ export class EphemeralNetwork {
const promises = this.config.peers.map(async (x) => {
const kp = await fromBase64Sk(x.sk);
const marine = new MarineBackgroundRunner(
this.workerLoader,
this.controlModuleLoader,
this.avmModuleLoader,
);
const marineDeps = await loadMarineDeps("/");
const marine = new MarineBackgroundRunner(...marineDeps);
const peerId = kp.getPeerId();
@ -271,17 +250,17 @@ export class EphemeralNetwork {
const peers = await Promise.all(promises);
for (let i = 0; i < peers.length; i++) {
for (let j = 0; j < i; j++) {
peers.forEach((peer, i) => {
const otherPeers = peers.slice(0, i);
otherPeers.forEach((otherPeer, j) => {
if (i === j) {
continue;
return;
}
peers[i].ephemeralConnection.connectToOther(
peers[j].ephemeralConnection,
);
}
}
peer.ephemeralConnection.connectToOther(otherPeer.ephemeralConnection);
});
});
const startPromises = peers.map((x) => {
return x.start();
@ -290,7 +269,7 @@ export class EphemeralNetwork {
await Promise.all(startPromises);
for (const p of peers) {
this.peers.set(p.keyPair.getPeerId(), p);
this.peers.set(p.getPeerId(), p);
}
}

View File

@ -14,97 +14,40 @@
* limitations under the License.
*/
import type { VersionedPackage } from "@fluencelabs/js-client-isomorphic";
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
import { ZodError } from "zod";
import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js";
import {
ClientConfig,
configSchema,
ConnectionState,
RelayOptions,
relayOptionsSchema,
} from "./clientPeer/types.js";
import { callAquaFunction } from "./compilerSupport/callFunction.js";
import { registerService } from "./compilerSupport/registerService.js";
import { loadMarineDeps } from "./marine/loader.js";
import { MarineBackgroundRunner } from "./marine/worker/index.js";
import versions from "./versions.js";
const DEFAULT_CDN_URL = "https://unpkg.com";
const getVersionedPackage = (pkg: keyof typeof versions): VersionedPackage => {
return {
name: pkg,
version: versions[pkg],
};
};
const createClient = async (
relay: RelayOptions,
config: ClientConfig = {},
): Promise<ClientPeer> => {
try {
relay = relayOptionsSchema.parse(relay);
config = configSchema.parse(config);
} catch (e) {
if (e instanceof ZodError) {
throw new Error(JSON.stringify(e.format()));
}
}
const CDNUrl = config.CDNUrl ?? DEFAULT_CDN_URL;
const fetchMarineJsWasm = async () => {
const resource = await fetchResource(
getVersionedPackage("@fluencelabs/marine-js"),
"/dist/marine-js.wasm",
CDNUrl,
);
return resource.arrayBuffer();
};
const fetchAvmWasm = async () => {
const resource = await fetchResource(
getVersionedPackage("@fluencelabs/avm"),
"/dist/avm.wasm",
CDNUrl,
);
return resource.arrayBuffer();
};
const marineJsWasm = await fetchMarineJsWasm();
const avmWasm = await fetchAvmWasm();
const marine = new MarineBackgroundRunner(
{
async getValue() {
return getWorker(
getVersionedPackage("@fluencelabs/marine-worker"),
CDNUrl,
);
},
start() {
return Promise.resolve(undefined);
},
stop() {
return Promise.resolve(undefined);
},
},
{
getValue() {
return marineJsWasm;
},
start(): Promise<void> {
return Promise.resolve(undefined);
},
stop(): Promise<void> {
return Promise.resolve(undefined);
},
},
{
getValue() {
return avmWasm;
},
start(): Promise<void> {
return Promise.resolve(undefined);
},
stop(): Promise<void> {
return Promise.resolve(undefined);
},
},
);
const marineDeps = await loadMarineDeps(CDNUrl);
const marine = new MarineBackgroundRunner(...marineDeps);
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(
relay,
@ -168,7 +111,7 @@ export const Fluence: FluencePublicApi = {
* @returns IFluenceClient instance
*/
getClient: function () {
if (this.defaultClient == null) {
if (this.defaultClient === undefined) {
throw new Error(
"Fluence client is not initialized. Call Fluence.connect() first",
);
@ -178,22 +121,23 @@ export const Fluence: FluencePublicApi = {
},
};
export type { ClientConfig, IFluenceClient } from "./clientPeer/types.js";
export type {
ClientConfig,
IFluenceClient,
ConnectionState,
Relay,
RelayOptions,
KeyPairOptions,
} from "./clientPeer/types.js";
export type { ParticleContext } from "./jsServiceHost/interfaces.js";
export { v5_callFunction, v5_registerService } from "./api.js";
// @ts-expect-error Writing to global object like this prohibited by ts
globalThis.new_fluence = Fluence;
// @ts-expect-error Writing to global object like this prohibited by ts
globalThis.fluence = {
clientFactory: createClient,
callAquaFunction,
registerService,
};
export { createClient, callAquaFunction, registerService };
export { ClientPeer } from "./clientPeer/ClientPeer.js";
// Deprecated exports. Later they will be exposed only under js-client/keypair path
export {
KeyPair,
@ -203,3 +147,6 @@ export {
} from "./keypair/index.js";
export * from "./network.js";
// TODO: Remove this export after DXJ-535 is done!
export { js2aqua, aqua2js } from "./compilerSupport/conversions.js";

View File

@ -14,20 +14,19 @@
* limitations under the License.
*/
import { Buffer } from "buffer";
import {
deserializeAvmResult,
InterpreterResult,
KeyPairFormat,
serializeAvmArgs,
} from "@fluencelabs/avm";
import { defaultCallParameters } from "@fluencelabs/marine-js/dist/types";
import { JSONValue } from "@fluencelabs/interfaces";
import { fromUint8Array } from "js-base64";
import {
concatMap,
filter,
groupBy,
GroupedObservable,
lastValueFrom,
mergeMap,
pipe,
@ -56,7 +55,6 @@ import {
getActualTTL,
hasExpired,
Particle,
ParticleExecutionStage,
ParticleQueueItem,
} from "../particle/Particle.js";
import { registerSig } from "../services/_aqua/services.js";
@ -66,9 +64,12 @@ import { defaultSigGuard, Sig } from "../services/Sig.js";
import { Srv } from "../services/SingleModuleSrv.js";
import { Tracing } from "../services/Tracing.js";
import { logger } from "../util/logger.js";
import { jsonify, isString, getErrorMessage } from "../util/utils.js";
import { getErrorMessage, isString, jsonify } from "../util/utils.js";
import { ExpirationError, InterpreterError, SendError } from "./errors.js";
const log_particle = logger("particle");
const log_particle_data = logger("particle:data");
const log_peer = logger("peer");
export type PeerConfig = {
@ -105,7 +106,7 @@ export const DEFAULT_CONFIG: PeerConfig = {
export abstract class FluencePeer {
constructor(
protected readonly config: PeerConfig,
readonly keyPair: KeyPair,
protected readonly keyPair: KeyPair,
protected readonly marineHost: IMarineHost,
protected readonly jsServiceHost: IJsServiceHost,
protected readonly connection: IConnection,
@ -113,6 +114,10 @@ export abstract class FluencePeer {
this._initServices();
}
getPeerId(): string {
return this.keyPair.getPeerId();
}
async start(): Promise<void> {
log_peer.trace("starting Fluence peer");
@ -122,7 +127,7 @@ export abstract class FluencePeer {
await this.marineHost.start();
this._startParticleProcessing();
this.startParticleProcessing();
this.isInitialized = true;
await this.connection.start();
log_peer.trace("started Fluence peer");
@ -142,7 +147,7 @@ export abstract class FluencePeer {
await this._incomingParticlePromise;
log_peer.trace("All particles finished execution");
this._stopParticleProcessing();
this.stopParticleProcessing();
await this.marineHost.stop();
await this.connection.stop();
this.isInitialized = false;
@ -159,7 +164,7 @@ export abstract class FluencePeer {
* @param serviceId - the service id by which the service can be accessed in aqua
*/
async registerMarineService(
wasm: SharedArrayBuffer | Buffer,
wasm: ArrayBuffer | SharedArrayBuffer,
serviceId: string,
): Promise<void> {
if (this.jsServiceHost.hasService(serviceId)) {
@ -201,12 +206,7 @@ export abstract class FluencePeer {
new Error("Can't use avm: peer is not initialized");
}
const res = await this.marineHost.callService(
"avm",
"ast",
[air],
defaultCallParameters,
);
const res = await this.marineHost.callService("avm", "ast", [air]);
if (!isString(res)) {
throw new Error(
@ -253,11 +253,15 @@ export abstract class FluencePeer {
/**
* Initiates a new particle execution starting from local peer
* @param particle - particle to start execution of
* @param onStageChange - callback for reacting on particle state changes
* @param onSuccess - callback which is called when particle execution succeed
* @param onError - callback which is called when particle execution fails
* @param fireAndForget - determines whether particle has fire-and-forget behavior
*/
initiateParticle: (
particle: IParticle,
onStageChange: (stage: ParticleExecutionStage) => void,
onSuccess: (result: JSONValue) => void,
onError: (error: Error) => void,
fireAndForget: boolean = true,
): void => {
if (!this.isInitialized) {
throw new Error(
@ -274,7 +278,9 @@ export abstract class FluencePeer {
this._incomingParticles.next({
particle: particle,
callResults: [],
onStageChange: onStageChange,
onSuccess,
onError,
fireAndForget,
});
},
@ -301,7 +307,7 @@ export abstract class FluencePeer {
// Queues for incoming and outgoing particles
private _incomingParticles = new Subject<ParticleQueueItem>();
private _timeouts: Array<NodeJS.Timeout> = [];
private timeouts: Record<string, NodeJS.Timeout> = {};
private _particleSourceSubscription?: Unsubscribable;
private _incomingParticlePromise?: Promise<void>;
@ -335,315 +341,96 @@ export abstract class FluencePeer {
registerTracing(this, "tracingSrv", this._classServices.tracing);
}
private _startParticleProcessing() {
this._particleSourceSubscription = this.connection.particleSource.subscribe(
{
next: (p) => {
this._incomingParticles.next({
particle: p,
callResults: [],
onStageChange: () => {},
});
},
},
);
this._incomingParticlePromise = lastValueFrom(
this._incomingParticles.pipe(
tap((item) => {
log_particle.debug("id %s. received:", item.particle.id);
log_particle.trace("id %s. data: %j", item.particle.id, {
initPeerId: item.particle.initPeerId,
timestamp: item.particle.timestamp,
ttl: item.particle.ttl,
signature: item.particle.signature,
});
log_particle.trace(
"id %s. script: %s",
item.particle.id,
item.particle.script,
);
log_particle.trace(
"id %s. call results: %j",
item.particle.id,
item.callResults,
);
}),
filterExpiredParticles(this._expireParticle.bind(this)),
groupBy((item) => {
return fromUint8Array(item.particle.signature);
}),
mergeMap((group$) => {
let prevData: Uint8Array = Buffer.from([]);
let firstRun = true;
return group$.pipe(
concatMap(async (item) => {
if (firstRun) {
const timeout = setTimeout(() => {
this._expireParticle(item);
}, getActualTTL(item.particle));
this._timeouts.push(timeout);
firstRun = false;
}
if (!this.isInitialized) {
// If `.stop()` was called return null to stop particle processing immediately
return null;
}
// IMPORTANT!
// AVM runner execution and prevData <-> newData swapping
// MUST happen sequentially (in a critical section).
// Otherwise the race might occur corrupting the prevData
log_particle.debug(
"id %s. sending particle to interpreter",
item.particle.id,
);
log_particle.trace(
"id %s. prevData: %s",
item.particle.id,
this.decodeAvmData(prevData).slice(0, 50),
);
const args = serializeAvmArgs(
{
initPeerId: item.particle.initPeerId,
currentPeerId: this.keyPair.getPeerId(),
timestamp: item.particle.timestamp,
ttl: item.particle.ttl,
keyFormat: KeyPairFormat.Ed25519,
particleId: item.particle.id,
secretKeyBytes: this.keyPair.toEd25519PrivateKey(),
},
item.particle.script,
prevData,
item.particle.data,
item.callResults,
);
let avmCallResult: InterpreterResult | Error;
try {
const res = await this.marineHost.callService(
"avm",
"invoke",
args,
defaultCallParameters,
);
avmCallResult = deserializeAvmResult(res);
} catch (e) {
avmCallResult = e instanceof Error ? e : new Error(String(e));
}
if (
!(avmCallResult instanceof Error) &&
avmCallResult.retCode === 0
) {
const newData = Buffer.from(avmCallResult.data);
prevData = newData;
}
return {
...item,
result: avmCallResult,
};
}),
filter((item): item is NonNullable<typeof item> => {
return item !== null;
}),
filterExpiredParticles<
ParticleQueueItem & {
result: Error | InterpreterResult;
}
>(this._expireParticle.bind(this)),
mergeMap(async (item) => {
// If peer was stopped, do not proceed further
if (!this.isInitialized) {
return;
}
// Do not continue if there was an error in particle interpretation
if (item.result instanceof Error) {
log_particle.error(
"id %s. interpreter failed: %s",
item.particle.id,
item.result.message,
);
item.onStageChange({
stage: "interpreterError",
errorMessage: item.result.message,
});
return;
}
if (item.result.retCode !== 0) {
log_particle.error(
"id %s. interpreter failed: retCode: %d, message: %s",
item.particle.id,
item.result.retCode,
item.result.errorMessage,
);
log_particle.trace(
"id %s. avm data: %s",
item.particle.id,
this.decodeAvmData(item.result.data),
);
item.onStageChange({
stage: "interpreterError",
errorMessage: item.result.errorMessage,
});
return;
}
log_particle.trace(
"id %s. interpreter result: retCode: %d, avm data: %s",
item.particle.id,
item.result.retCode,
this.decodeAvmData(item.result.data),
);
setTimeout(() => {
item.onStageChange({ stage: "interpreted" });
}, 0);
let connectionPromise: Promise<void> = Promise.resolve();
// send particle further if requested
if (item.result.nextPeerPks.length > 0) {
const newParticle = cloneWithNewData(
item.particle,
Buffer.from(item.result.data),
);
log_particle.debug(
"id %s. sending particle into network. Next peer ids: %s",
newParticle.id,
item.result.nextPeerPks.toString(),
);
connectionPromise = this.connection
.sendParticle(item.result.nextPeerPks, newParticle)
.then(() => {
log_particle.trace(
"id %s. send successful",
newParticle.id,
);
item.onStageChange({ stage: "sent" });
})
.catch((e: unknown) => {
log_particle.error(
"id %s. send failed %j",
newParticle.id,
e,
);
item.onStageChange({
stage: "sendingError",
errorMessage: getErrorMessage(e),
});
});
}
// execute call requests if needed
// and put particle with the results back to queue
if (item.result.callRequests.length > 0) {
for (const [key, cr] of item.result.callRequests) {
const req = {
fnName: cr.functionName,
args: cr.arguments,
serviceId: cr.serviceId,
tetraplets: cr.tetraplets,
particleContext: getParticleContext(item.particle),
};
void this._execSingleCallRequest(req)
.catch((err): CallServiceResult => {
if (err instanceof ServiceError) {
return {
retCode: ResultCodes.error,
result: err.message,
};
}
return {
retCode: ResultCodes.error,
result: `Service call failed. fnName="${
req.fnName
}" serviceId="${
req.serviceId
}" error: ${getErrorMessage(err)}`,
};
})
.then((res) => {
const serviceResult = {
result: jsonify(res.result),
retCode: res.retCode,
};
const newParticle = cloneWithNewData(
item.particle,
Buffer.from([]),
);
this._incomingParticles.next({
...item,
particle: newParticle,
callResults: [[key, serviceResult]],
});
});
}
} else {
item.onStageChange({ stage: "localWorkDone" });
}
return connectionPromise;
}),
);
}),
),
{ defaultValue: undefined },
);
}
private _expireParticle(item: ParticleQueueItem) {
const particleId = item.particle.id;
private async sendParticleToRelay(
item: ParticleQueueItem & { result: InterpreterResult },
) {
const newParticle = cloneWithNewData(item.particle, item.result.data);
log_particle.debug(
"id %s. particle has expired after %d. Deleting particle-related queues and handlers",
item.particle.id,
item.particle.ttl,
"id %s. sending particle into network. Next peer ids: %s",
newParticle.id,
item.result.nextPeerPks.toString(),
);
this.jsServiceHost.removeParticleScopeHandlers(particleId);
try {
await this.connection.sendParticle(item.result.nextPeerPks, newParticle);
log_particle.trace("id %s. send successful", newParticle.id);
} catch (e) {
const message = getErrorMessage(e);
log_particle.error("id %s. send failed %s", newParticle.id, message);
item.onStageChange({ stage: "expired" });
item.onError(
new SendError(
`Could not send particle: (particle id: ${item.particle.id}, message: ${message})`,
),
);
}
}
private decodeAvmData(data: Uint8Array) {
return new TextDecoder().decode(data.buffer);
private async execCallRequests(
item: ParticleQueueItem & { result: InterpreterResult },
) {
return Promise.all(
item.result.callRequests.map(async ([key, cr]) => {
const req = {
fnName: cr.functionName,
args: cr.arguments,
serviceId: cr.serviceId,
tetraplets: cr.tetraplets,
particleContext: getParticleContext(item.particle, cr.tetraplets),
};
let res: CallServiceResult;
try {
res = await this.execCallRequest(req);
} catch (err) {
if (err instanceof ServiceError) {
res = {
retCode: ResultCodes.error,
result: err.message,
};
}
res = {
retCode: ResultCodes.error,
result: `Service call failed. fnName="${req.fnName}" serviceId="${
req.serviceId
}" error: ${getErrorMessage(err)}`,
};
}
const serviceResult = {
result: jsonify(res.result),
retCode: res.retCode,
};
const newParticle = cloneWithNewData(
item.particle,
Uint8Array.from([]),
);
this._incomingParticles.next({
...item,
particle: newParticle,
callResults: [[key, serviceResult]],
});
}),
);
}
private async _execSingleCallRequest(
private async execCallRequest(
req: CallServiceData,
): Promise<CallServiceResult> {
const particleId = req.particleContext.particleId;
log_particle.debug("id %s. executing call service handler %j", particleId, {
serviceId: req.serviceId,
functionName: req.fnName,
});
log_particle.trace(
"id %s. executing call service handler %j",
"id %s. executing call service handler detailed %j",
particleId,
req,
);
@ -654,7 +441,6 @@ export abstract class FluencePeer {
req.serviceId,
req.fnName,
req.args,
defaultCallParameters,
);
return {
@ -684,9 +470,242 @@ export abstract class FluencePeer {
return res;
}
private _stopParticleProcessing() {
private mapParticleGroup(
group$: GroupedObservable<string, ParticleQueueItem>,
) {
let prevData = Uint8Array.from([]);
return group$.pipe(
concatMap(async (item) => {
if (!(group$.key in this.timeouts)) {
this.timeouts[group$.key] = setTimeout(() => {
this.expireParticle(item);
}, getActualTTL(item.particle));
}
// IMPORTANT!
// AVM runner execution and prevData <-> newData swapping
// MUST happen sequentially (in a critical section).
// Otherwise the race might occur corrupting the prevData
log_particle.debug(
"id %s. sending particle to interpreter",
item.particle.id,
);
log_particle_data.trace(
"id %s. prevData: %s",
item.particle.id,
this.decodeAvmData(prevData),
);
const args = serializeAvmArgs(
{
initPeerId: item.particle.initPeerId,
currentPeerId: this.keyPair.getPeerId(),
timestamp: item.particle.timestamp,
ttl: item.particle.ttl,
keyFormat: KeyPairFormat.Ed25519,
particleId: item.particle.id,
secretKeyBytes: this.keyPair.toEd25519PrivateKey(),
},
item.particle.script,
prevData,
item.particle.data,
item.callResults,
);
const [, , , forthParam] = args;
if (
typeof forthParam === "object" &&
"hard_limit_enabled" in forthParam
) {
forthParam["hard_limit_enabled"] = false;
}
let avmCallResult: InterpreterResult | Error;
try {
const res = await this.marineHost.callService("avm", "invoke", args);
avmCallResult = deserializeAvmResult(res);
// TODO: This is bug in @fluencelabs/avm package. 'avmCallResult.data' actually number array, not Uint8Array as stated in type.
avmCallResult.data = Uint8Array.from(avmCallResult.data);
} catch (e) {
avmCallResult = e instanceof Error ? e : new Error(String(e));
}
if (!(avmCallResult instanceof Error) && avmCallResult.retCode === 0) {
prevData = avmCallResult.data;
}
return {
...item,
result: avmCallResult,
};
}),
filterExpiredParticles<
ParticleQueueItem & {
result: Error | InterpreterResult;
}
>(this.expireParticle.bind(this)),
filter(() => {
// If peer was stopped, do not proceed further
return this.isInitialized;
}),
mergeMap(async (item) => {
// Do not continue if there was an error in particle interpretation
if (item.result instanceof Error) {
log_particle.error(
"id %s. interpreter failed: %s",
item.particle.id,
item.result.message,
);
item.onError(
new InterpreterError(
`Script interpretation failed: ${item.result.message} (particle id: ${item.particle.id})`,
),
);
return;
}
if (item.result.retCode !== 0) {
log_particle.error(
"id %s. interpreter failed: retCode: %d, message: %s",
item.particle.id,
item.result.retCode,
item.result.errorMessage,
);
log_particle_data.trace(
"id %s. avm data: %s",
item.particle.id,
this.decodeAvmData(item.result.data),
);
item.onError(
new InterpreterError(
`Script interpretation failed: ${item.result.errorMessage} (particle id: ${item.particle.id})`,
),
);
return;
}
log_particle.trace(
"id %s. interpreter result: retCode: %d",
item.particle.id,
item.result.retCode,
);
log_particle_data.trace(
"id %s. avm data: %s",
item.particle.id,
this.decodeAvmData(item.result.data),
);
let connectionPromise: Promise<void> = Promise.resolve();
// send particle further if requested
if (item.result.nextPeerPks.length > 0) {
// TS doesn't allow to pass just 'item'
connectionPromise = this.sendParticleToRelay({
...item,
result: item.result,
});
}
// execute call requests if needed
// and put particle with the results back to queue
if (item.result.callRequests.length > 0) {
// TS doesn't allow to pass just 'item'
void this.execCallRequests({ ...item, result: item.result });
} else if (item.fireAndForget === true) {
// Local work done.
item.onSuccess(null);
}
return connectionPromise;
}),
);
}
private startParticleProcessing() {
this._particleSourceSubscription = this.connection.particleSource.subscribe(
{
next: (particle) => {
this._incomingParticles.next({
particle,
callResults: [],
onSuccess: () => {},
onError: () => {},
});
},
},
);
this._incomingParticlePromise = lastValueFrom(
this._incomingParticles.pipe(
tap((item) => {
log_particle.debug("id %s. received:", item.particle.id);
log_particle.trace("id %s. data: %j", item.particle.id, {
initPeerId: item.particle.initPeerId,
timestamp: item.particle.timestamp,
ttl: item.particle.ttl,
signature: item.particle.signature,
});
log_particle.trace(
"id %s. script: %s",
item.particle.id,
item.particle.script,
);
log_particle.trace(
"id %s. call results: %j",
item.particle.id,
item.callResults,
);
}),
filterExpiredParticles(this.expireParticle.bind(this)),
groupBy((item) => {
return fromUint8Array(item.particle.signature);
}),
mergeMap(this.mapParticleGroup.bind(this)),
),
{ defaultValue: undefined },
);
}
private expireParticle(item: ParticleQueueItem) {
const particleId = item.particle.id;
log_particle.debug(
"id %s. particle has expired after %d. Deleting particle-related queues and handlers",
item.particle.id,
item.particle.ttl,
);
this.jsServiceHost.removeParticleScopeHandlers(particleId);
item.onError(
new ExpirationError(
`Particle expired after ttl of ${item.particle.ttl}ms (particle id: ${item.particle.id})`,
),
);
}
private decodeAvmData(data: Uint8Array) {
return new TextDecoder().decode(data.buffer);
}
private stopParticleProcessing() {
// do not hang if the peer has been stopped while some of the timeouts are still being executed
this._timeouts.forEach((timeout) => {
Object.values(this.timeouts).forEach((timeout) => {
clearTimeout(timeout);
});
}

View File

@ -15,7 +15,7 @@
*/
import { JSONValue } from "@fluencelabs/interfaces";
import { it, describe, expect } from "vitest";
import { it, describe, expect, assert } from "vitest";
import { handleTimeout } from "../../particle/Particle.js";
import { registerHandlersHelper, withPeer } from "../../util/testUtils.js";
@ -39,12 +39,17 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
print: {
print: (args): undefined => {
const [res] = args;
assert(res);
resolve(res);
},
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(res).toBe("1");
@ -76,6 +81,7 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
registerHandlersHelper(peer, particle, {
print: {
print: (args): undefined => {
assert(args[0]);
res.push(args[0]);
if (res.length === 2) {
@ -85,7 +91,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(res).toStrictEqual(["1", "2"]);
@ -126,7 +136,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(res).toBe("fast_result");
@ -178,7 +192,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(res).toBe("failed_with_timeout");

View File

@ -14,10 +14,8 @@
* limitations under the License.
*/
import assert from "assert";
import { JSONValue } from "@fluencelabs/interfaces";
import { describe, expect, it } from "vitest";
import { describe, expect, it, assert } from "vitest";
import {
CallServiceData,
@ -52,6 +50,7 @@ describe("FluencePeer flow tests", () => {
(req: CallServiceData) => {
const [timeout, message] = req.args;
assert(typeof timeout === "number");
assert(message);
return new Promise((resolve) => {
setTimeout(() => {
@ -77,6 +76,7 @@ describe("FluencePeer flow tests", () => {
callback: {
callback1: (args): undefined => {
const [val] = args;
assert(val);
values.push(val);
if (values.length === 2) {
@ -85,6 +85,7 @@ describe("FluencePeer flow tests", () => {
},
callback2: (args): undefined => {
const [val] = args;
assert(val);
values.push(val);
if (values.length === 2) {
@ -94,10 +95,14 @@ describe("FluencePeer flow tests", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(res).toEqual(expect.arrayContaining(["test1", "test1"]));
});
}, 1500);
}, 1800);
});

View File

@ -16,36 +16,11 @@
import { it, describe, expect } from "vitest";
import { isFluencePeer } from "../../api.js";
import { handleTimeout } from "../../particle/Particle.js";
import {
mkTestPeer,
registerHandlersHelper,
withPeer,
} from "../../util/testUtils.js";
import { registerHandlersHelper, withPeer } from "../../util/testUtils.js";
import { FluencePeer } from "../FluencePeer.js";
describe("FluencePeer usage test suite", () => {
it("should perform test for FluencePeer class correctly", async () => {
// arrange
const peer = await mkTestPeer();
const number = 1;
const object = { str: "Hello!" };
const undefinedVal = undefined;
// act
const isPeerPeer = isFluencePeer(peer);
const isNumberPeer = isFluencePeer(number);
const isObjectPeer = isFluencePeer(object);
const isUndefinedPeer = isFluencePeer(undefinedVal);
// act
expect(isPeerPeer).toBe(true);
expect(isNumberPeer).toBe(false);
expect(isObjectPeer).toBe(false);
expect(isUndefinedPeer).toBe(false);
});
it("Should successfully call identity on local peer", async function () {
await withPeer(async (peer) => {
const script = `
@ -72,7 +47,11 @@ describe("FluencePeer usage test suite", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(res).toBe("test");
@ -130,7 +109,11 @@ describe("FluencePeer usage test suite", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
expect(res).toBe(null);
@ -167,7 +150,11 @@ describe("FluencePeer usage test suite", () => {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(reject),
);
});
await expect(promise).rejects.toMatchObject({
@ -205,6 +192,6 @@ async function callIncorrectService(peer: FluencePeer) {
},
});
peer.internals.initiateParticle(particle, handleTimeout(reject));
peer.internals.initiateParticle(particle, () => {}, handleTimeout(reject));
});
}

View File

@ -14,19 +14,17 @@
* limitations under the License.
*/
import {
Worker,
type Worker as WorkerImplementation,
} from "@fluencelabs/threads/master";
/**
* Throw when particle times out, e.g. haven't been resolved after TTL is expired
*/
export class ExpirationError extends Error {}
import { LazyLoader } from "../interfaces.js";
/**
* Throws when AquaVM interpreter returns an error while executing air script. It could be badly written air or internal bug.
*/
export class InterpreterError extends Error {}
export class WorkerLoader extends LazyLoader<WorkerImplementation> {
constructor() {
super(() => {
return new Worker(
"../../../node_modules/@fluencelabs/marine-worker/dist/index.js",
);
});
}
}
/**
* Throws when network error occurs while sending particle to relay peer.
*/
export class SendError extends Error {}

View File

@ -79,7 +79,7 @@ export enum ResultCodes {
/**
* Particle context. Contains additional information about particle which triggered `call` air instruction from AVM
*/
export interface ParticleContext {
export type ParticleContext = {
/**
* The identifier of particle which triggered the call
*/
@ -104,7 +104,12 @@ export interface ParticleContext {
* Particle's signature
*/
signature: Uint8Array;
}
/**
* Security Tetraplets received from AVM and copied here
*/
tetraplets: SecurityTetraplet[][];
};
/**
* Represents the information passed from AVM when a `call` air instruction is executed on the local peer

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
import { SecurityTetraplet } from "@fluencelabs/avm";
import { JSONArray } from "@fluencelabs/interfaces";
import { FluencePeer } from "../jsPeer/FluencePeer.js";
@ -28,10 +29,6 @@ import {
ResultCodes,
} from "./interfaces.js";
export const doNothing = () => {
return undefined;
};
export const WrapFnIntoServiceCall = (
fn: (args: JSONArray) => CallServiceResultType | undefined,
) => {
@ -51,20 +48,40 @@ export class ServiceError extends Error {
}
}
export const getParticleContext = (particle: IParticle): ParticleContext => {
export const getParticleContext = (
particle: IParticle,
tetraplets: SecurityTetraplet[][],
): ParticleContext => {
return {
particleId: particle.id,
initPeerId: particle.initPeerId,
timestamp: particle.timestamp,
ttl: particle.ttl,
signature: particle.signature,
tetraplets,
};
};
export function registerDefaultServices(peer: FluencePeer) {
Object.entries(builtInServices).forEach(([serviceId, service]) => {
Object.entries(service).forEach(([fnName, fn]) => {
peer.internals.regHandler.common(serviceId, fnName, fn);
const wrapped = async (req: CallServiceData) => {
const res = await fn(req);
if (
res.retCode === ResultCodes.error &&
typeof res.result === "string"
) {
return {
retCode: ResultCodes.error,
result: `("${serviceId}" "${fnName}") ${res.result}`,
};
}
return res;
};
peer.internals.regHandler.common(serviceId, fnName, wrapped);
});
});
}

View File

@ -19,8 +19,7 @@ import {
generateKeyPair,
unmarshalPublicKey,
} from "@libp2p/crypto/keys";
import type { PrivateKey, PublicKey } from "@libp2p/interface/keys";
import type { PeerId } from "@libp2p/interface/peer-id";
import type { PrivateKey, PublicKey, PeerId } from "@libp2p/interface";
import { createFromPrivKey } from "@libp2p/peer-id-factory";
import bs58 from "bs58";
import { toUint8Array } from "js-base64";
@ -91,11 +90,11 @@ export class KeyPair {
return this.privateKey.marshal().subarray(0, 32);
}
signBytes(data: Uint8Array): Promise<Uint8Array> {
async signBytes(data: Uint8Array): Promise<Uint8Array> {
return this.privateKey.sign(data);
}
verify(data: Uint8Array, signature: Uint8Array): Promise<boolean> {
async verify(data: Uint8Array, signature: Uint8Array): Promise<boolean> {
return this.publicKey.verify(data, signature);
}
}

View File

@ -18,7 +18,7 @@ import * as fs from "fs";
import * as path from "path";
import * as url from "url";
import { it, describe, expect, beforeAll } from "vitest";
import { it, describe, expect, beforeAll, assert } from "vitest";
import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js";
@ -46,6 +46,7 @@ describe("Marine js tests", () => {
await peer.registerMarineService(wasm, "greeting");
// act
assert(aqua["call"]);
const res = await aqua["call"](peer, { arg: "test" });
// assert

View File

@ -1,98 +0,0 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Buffer } from "buffer";
import fs from "fs";
import { createRequire } from "module";
import path from "path";
import {
Worker,
type Worker as WorkerImplementation,
} from "@fluencelabs/threads/master";
import { LazyLoader } from "../interfaces.js";
const require = createRequire(import.meta.url);
const bufferToSharedArrayBuffer = (buffer: Buffer): SharedArrayBuffer => {
const sab = new SharedArrayBuffer(buffer.length);
const tmp = new Uint8Array(sab);
tmp.set(buffer, 0);
return sab;
};
/**
* Load wasm file from npm package. Only works in nodejs environment.
* The function returns SharedArrayBuffer compatible with FluenceAppService methods.
* @param source - object specifying the source of the file. Consist two fields: package name and file path.
* @returns SharedArrayBuffer with the wasm file
*/
export const loadWasmFromNpmPackage = async (source: {
package: string;
file: string;
}): Promise<SharedArrayBuffer> => {
const packagePath = require.resolve(source.package);
const filePath = path.join(path.dirname(packagePath), source.file);
return loadWasmFromFileSystem(filePath);
};
/**
* Load wasm file from the file system. Only works in nodejs environment.
* The functions returns SharedArrayBuffer compatible with FluenceAppService methods.
* @param filePath - path to the wasm file
* @returns SharedArrayBuffer with the wasm fileWorker
*/
export const loadWasmFromFileSystem = async (
filePath: string,
): Promise<SharedArrayBuffer> => {
const buffer = await fs.promises.readFile(filePath);
return bufferToSharedArrayBuffer(buffer);
};
export class WasmLoaderFromFs extends LazyLoader<SharedArrayBuffer> {
constructor(filePath: string) {
super(() => {
return loadWasmFromFileSystem(filePath);
});
}
}
export class WasmLoaderFromNpm extends LazyLoader<SharedArrayBuffer> {
constructor(pkg: string, file: string) {
super(() => {
return loadWasmFromNpmPackage({ package: pkg, file: file });
});
}
}
export class WorkerLoaderFromFs extends LazyLoader<WorkerImplementation> {
constructor(scriptPath: string) {
super(() => {
return new Worker(scriptPath);
});
}
}
export class WorkerLoaderFromNpm extends LazyLoader<WorkerImplementation> {
constructor(pkg: string, file: string) {
super(() => {
const packagePath = require.resolve(pkg);
const scriptPath = path.join(path.dirname(packagePath), file);
return new Worker(scriptPath);
});
}
}

View File

@ -14,15 +14,10 @@
* limitations under the License.
*/
import {
CallResultsArray,
InterpreterResult,
RunParameters,
} from "@fluencelabs/avm";
import { JSONObject, JSONValue, JSONArray } from "@fluencelabs/interfaces";
import type { Worker as WorkerImplementation } from "@fluencelabs/threads/master";
import { CallParameters } from "@fluencelabs/marine-worker";
import { IStartable, CallParameters } from "../util/commonTypes.js";
import { IStartable } from "../util/commonTypes.js";
/**
* Contract for marine host implementations. Marine host is responsible for creating calling and removing marine services
@ -53,72 +48,6 @@ export interface IMarineHost extends IStartable {
serviceId: string,
functionName: string,
args: JSONArray | JSONObject,
callParams: CallParameters,
callParams?: CallParameters,
): Promise<JSONValue>;
}
/**
* Interface for different implementations of AVM runner
*/
export interface IAvmRunner extends IStartable {
/**
* Run AVM interpreter with the specified parameters
*/
run(
runParams: RunParameters,
air: string,
prevData: Uint8Array,
data: Uint8Array,
callResults: CallResultsArray,
): Promise<InterpreterResult | Error>;
}
/**
* Interface for something which can hold a value
*/
export interface IValueLoader<T> {
getValue(): T;
}
/**
* Interface for something which can load wasm files
*/
export interface IWasmLoader
extends IValueLoader<ArrayBuffer | SharedArrayBuffer>,
IStartable {}
/**
* Interface for something which can thread.js based worker
*/
export interface IWorkerLoader
extends IValueLoader<WorkerImplementation | Promise<WorkerImplementation>>,
IStartable {}
/**
* Lazy loader for some value. Value is loaded only when `start` method is called
*/
export class LazyLoader<T> implements IStartable, IValueLoader<T> {
private value: T | null = null;
constructor(private loadValue: () => Promise<T> | T) {}
getValue(): T {
if (this.value == null) {
throw new Error(
"Value has not been loaded. Call `start` method to load the value.",
);
}
return this.value;
}
async start() {
if (this.value !== null) {
return;
}
this.value = await this.loadValue();
}
async stop() {}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker";
import type { ModuleThread } from "@fluencelabs/threads/master";
type StrategyReturnType = [
marineJsWasm: ArrayBuffer,
avmWasm: ArrayBuffer,
worker: ModuleThread<MarineBackgroundInterface>,
];
export const loadMarineDeps = async (
CDNUrl: string,
): Promise<StrategyReturnType> => {
const [marineJsWasm, avmWasm, worker] = await Promise.all([
fetchResource(
"@fluencelabs/marine-js",
"/dist/marine-js.wasm",
CDNUrl,
).then((res) => {
return res.arrayBuffer();
}),
fetchResource("@fluencelabs/avm", "/dist/avm.wasm", CDNUrl).then((res) => {
return res.arrayBuffer();
}),
getWorker("@fluencelabs/marine-worker", CDNUrl),
]);
return [marineJsWasm, avmWasm, worker];
};

View File

@ -16,101 +16,63 @@
import { JSONValue } from "@fluencelabs/interfaces";
import type {
JSONArray,
JSONObject,
MarineBackgroundInterface,
LogFunction,
JSONValueNonNullable,
CallParameters,
} from "@fluencelabs/marine-js/dist/types";
import { LogFunction, logLevelToEnv } from "@fluencelabs/marine-js/dist/types";
import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker";
import { ModuleThread, Thread, spawn } from "@fluencelabs/threads/master";
} from "@fluencelabs/marine-worker";
import { ModuleThread, Thread } from "@fluencelabs/threads/master";
import { MarineLogger, marineLogger } from "../../util/logger.js";
import { IMarineHost, IWasmLoader, IWorkerLoader } from "../interfaces.js";
import { IMarineHost } from "../interfaces.js";
export class MarineBackgroundRunner implements IMarineHost {
private workerThread?: ModuleThread<MarineBackgroundInterface>;
private loggers = new Map<string, MarineLogger>();
constructor(
private workerLoader: IWorkerLoader,
private controlModuleLoader: IWasmLoader,
private avmWasmLoader: IWasmLoader,
private marineJsWasm: ArrayBuffer,
private avmWasm: ArrayBuffer,
private workerThread: ModuleThread<MarineBackgroundInterface>,
) {}
async hasService(serviceId: string) {
if (this.workerThread == null) {
throw new Error("Worker is not initialized");
}
return this.workerThread.hasService(serviceId);
}
async removeService(serviceId: string) {
if (this.workerThread == null) {
throw new Error("Worker is not initialized");
}
await this.workerThread.removeService(serviceId);
}
async start(): Promise<void> {
if (this.workerThread != null) {
throw new Error("Worker thread already initialized");
}
await this.controlModuleLoader.start();
const wasm = this.controlModuleLoader.getValue();
await this.avmWasmLoader.start();
await this.workerLoader.start();
const worker = await this.workerLoader.getValue();
const workerThread: ModuleThread<MarineBackgroundInterface> =
await spawn<MarineBackgroundInterface>(worker);
const logfn: LogFunction = (message) => {
const serviceLogger = this.loggers.get(message.service);
if (serviceLogger == null) {
if (serviceLogger === undefined) {
return;
}
serviceLogger[message.level](message.message);
};
workerThread.onLogMessage().subscribe(logfn);
await workerThread.init(wasm);
this.workerThread = workerThread;
await this.createService(this.avmWasmLoader.getValue(), "avm");
this.workerThread.onLogMessage().subscribe(logfn);
await this.workerThread.init(this.marineJsWasm);
await this.createService(this.avmWasm, "avm");
}
async createService(
serviceModule: ArrayBuffer | SharedArrayBuffer,
serviceId: string,
): Promise<void> {
if (this.workerThread == null) {
throw new Error("Worker is not initialized");
}
// The logging level is controlled by the environment variable passed to enable debug logs.
// We enable all possible log levels passing the control for exact printouts to the logger
const env = logLevelToEnv("info");
this.loggers.set(serviceId, marineLogger(serviceId));
await this.workerThread.createService(serviceModule, serviceId, env);
await this.workerThread.createService(serviceModule, serviceId);
}
async callService(
serviceId: string,
functionName: string,
args: JSONArray | JSONObject,
callParams: CallParameters,
args: Array<JSONValueNonNullable> | Record<string, JSONValueNonNullable>,
callParams?: CallParameters,
): Promise<JSONValue> {
if (this.workerThread == null) {
throw new Error("Worker is not initialized");
}
return this.workerThread.callService(
serviceId,
functionName,
@ -120,10 +82,6 @@ export class MarineBackgroundRunner implements IMarineHost {
}
async stop(): Promise<void> {
if (this.workerThread == null) {
return;
}
await this.workerThread.terminate();
await Thread.terminate(this.workerThread);
}

View File

@ -52,57 +52,57 @@ export const stage: Relay[] = [
export const testNet: Relay[] = [
{
multiaddr:
"/dns4/0-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWMhVpgfQxBLkQkJed8VFNvgN4iE6MD7xCybb1ZYWW2Gtz",
"/dns4/0-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWMhVpgfQxBLkQkJed8VFNvgN4iE6MD7xCybb1ZYWW2Gtz",
peerId: "12D3KooWMhVpgfQxBLkQkJed8VFNvgN4iE6MD7xCybb1ZYWW2Gtz",
},
{
multiaddr:
"/dns4/1-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE9",
"/dns4/1-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE9",
peerId: "12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE9",
},
{
multiaddr:
"/dns4/2-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er",
"/dns4/2-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er",
peerId: "12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er",
},
{
multiaddr:
"/dns4/3-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb",
"/dns4/3-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb",
peerId: "12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb",
},
{
multiaddr:
"/dns4/4-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWJbJFaZ3k5sNd8DjQgg3aERoKtBAnirEvPV8yp76kEXHB",
"/dns4/4-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWJbJFaZ3k5sNd8DjQgg3aERoKtBAnirEvPV8yp76kEXHB",
peerId: "12D3KooWJbJFaZ3k5sNd8DjQgg3aERoKtBAnirEvPV8yp76kEXHB",
},
{
multiaddr:
"/dns4/5-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWCKCeqLPSgMnDjyFsJuWqREDtKNHx1JEBiwaMXhCLNTRb",
"/dns4/5-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWCKCeqLPSgMnDjyFsJuWqREDtKNHx1JEBiwaMXhCLNTRb",
peerId: "12D3KooWCKCeqLPSgMnDjyFsJuWqREDtKNHx1JEBiwaMXhCLNTRb",
},
{
multiaddr:
"/dns4/6-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWKnRcsTpYx9axkJ6d69LPfpPXrkVLe96skuPTAo76LLVH",
"/dns4/6-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWKnRcsTpYx9axkJ6d69LPfpPXrkVLe96skuPTAo76LLVH",
peerId: "12D3KooWKnRcsTpYx9axkJ6d69LPfpPXrkVLe96skuPTAo76LLVH",
},
{
multiaddr:
"/dns4/7-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWBSdm6TkqnEFrgBuSkpVE3dR1kr6952DsWQRNwJZjFZBv",
"/dns4/7-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWBSdm6TkqnEFrgBuSkpVE3dR1kr6952DsWQRNwJZjFZBv",
peerId: "12D3KooWBSdm6TkqnEFrgBuSkpVE3dR1kr6952DsWQRNwJZjFZBv",
},
{
multiaddr:
"/dns4/8-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWGzNvhSDsgFoHwpWHAyPf1kcTYCGeRBPfznL8J6qdyu2H",
"/dns4/8-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWGzNvhSDsgFoHwpWHAyPf1kcTYCGeRBPfznL8J6qdyu2H",
peerId: "12D3KooWGzNvhSDsgFoHwpWHAyPf1kcTYCGeRBPfznL8J6qdyu2H",
},
{
multiaddr:
"/dns4/9-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWF7gjXhQ4LaKj6j7ntxsPpGk34psdQicN2KNfBi9bFKXg",
"/dns4/9-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWF7gjXhQ4LaKj6j7ntxsPpGk34psdQicN2KNfBi9bFKXg",
peerId: "12D3KooWF7gjXhQ4LaKj6j7ntxsPpGk34psdQicN2KNfBi9bFKXg",
},
{
multiaddr:
"/dns4/10-testnet.fluence.dev/tcp/9000/wss/p2p/12D3KooWB9P1xmV3c7ZPpBemovbwCiRRTKd3Kq2jsVPQN4ZukDfy",
"/dns4/10-dar.fluence.dev/tcp/9000/wss/p2p/12D3KooWB9P1xmV3c7ZPpBemovbwCiRRTKd3Kq2jsVPQN4ZukDfy",
peerId: "12D3KooWB9P1xmV3c7ZPpBemovbwCiRRTKd3Kq2jsVPQN4ZukDfy",
},
];
@ -165,21 +165,23 @@ export const kras: Relay[] = [
},
];
export const randomKras = () => {
export const randomKras = (): Relay => {
return randomItem(kras);
};
export const randomTestNet = () => {
export const randomTestNet = (): Relay => {
return randomItem(testNet);
};
export const randomStage = () => {
export const randomStage = (): Relay => {
return randomItem(stage);
};
function randomItem(arr: Relay[]) {
function randomItem(arr: Relay[]): Relay {
const index = randomInt(0, arr.length);
return arr[index];
// This array access always defined
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return arr[index] as Relay;
}
function randomInt(min: number, max: number) {

Some files were not shown because too many files have changed in this diff Show More