mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2024-12-04 09:50:17 +00:00
feat(js-client)!: Segregation of responsibility between js-client packages [fixes DXJ-525] (#378)
Schema validation in js-client
This commit is contained in:
parent
638da47bc2
commit
f4a550dd22
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
|||||||
uses: fluencelabs/aqua/.github/workflows/tests.yml@main
|
uses: fluencelabs/aqua/.github/workflows/tests.yml@main
|
||||||
with:
|
with:
|
||||||
js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}"
|
js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}"
|
||||||
nox-image: "fluencelabs/nox:unstable_minimal"
|
nox-image: "fluencelabs/nox:0.4.2"
|
||||||
flox:
|
flox:
|
||||||
needs:
|
needs:
|
||||||
- js-client
|
- js-client
|
||||||
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -6,7 +6,7 @@ on:
|
|||||||
nox-image:
|
nox-image:
|
||||||
description: "nox image tag"
|
description: "nox image tag"
|
||||||
type: string
|
type: string
|
||||||
default: "fluencelabs/nox:0.4.0"
|
default: "fluencelabs/nox:0.4.2"
|
||||||
avm-version:
|
avm-version:
|
||||||
description: "@fluencelabs/avm version"
|
description: "@fluencelabs/avm version"
|
||||||
type: string
|
type: string
|
||||||
|
@ -2,11 +2,13 @@
|
|||||||
.eslintcache
|
.eslintcache
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
|
||||||
**/node_modules
|
node_modules
|
||||||
**/dist
|
dist
|
||||||
**/build
|
build
|
||||||
**/public
|
public
|
||||||
|
|
||||||
**/CHANGELOG.md
|
**/CHANGELOG.md
|
||||||
|
|
||||||
packages/core/js-client-isomorphic/src/versions.ts
|
packages/core/js-client-isomorphic/src/versions.ts
|
||||||
|
__snapshots__
|
||||||
|
packages/@tests/aqua/src/_aqua/**
|
2
ci.cjs
2
ci.cjs
@ -95,7 +95,7 @@ async function checkConsistency(file, versionsMap) {
|
|||||||
|
|
||||||
for (const [name, versionInDep] of versionsMap) {
|
for (const [name, versionInDep] of versionsMap) {
|
||||||
const check = (x, version) => {
|
const check = (x, version) => {
|
||||||
if (version.includes("*")) {
|
if (version.includes("*") || version.includes("^")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
51
packages/@tests/aqua/compile-aqua.ts
Normal file
51
packages/@tests/aqua/compile-aqua.ts
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
@ -11,7 +11,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"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",
|
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||||
"author": "Fluence Labs",
|
"author": "Fluence Labs",
|
||||||
@ -20,10 +20,12 @@
|
|||||||
"base64-js": "1.5.1"
|
"base64-js": "1.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@fluencelabs/aqua-api": "0.12.4-main-cee4448-2196-1",
|
||||||
"@fluencelabs/aqua-lib": "0.6.0",
|
"@fluencelabs/aqua-lib": "0.6.0",
|
||||||
"@fluencelabs/cli": "0.7.2",
|
"@fluencelabs/aqua-to-js": "workspace:*",
|
||||||
"@fluencelabs/js-client": "workspace:^",
|
"@fluencelabs/js-client": "workspace:*",
|
||||||
"@fluencelabs/registry": "0.8.2",
|
"@fluencelabs/registry": "0.8.8-1",
|
||||||
"@fluencelabs/trust-graph": "3.1.2"
|
"@fluencelabs/trust-graph": "3.1.2",
|
||||||
|
"ts-node": "10.9.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,71 +2,68 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* This file is auto-generated. Do not edit manually: changes may be erased.
|
* This file is generated using:
|
||||||
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
* @fluencelabs/aqua-api version: 0.12.4-main-cee4448-2196-1
|
||||||
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
* @fluencelabs/aqua-to-js version: 0.2.0
|
||||||
* Aqua version: 0.12.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 {
|
import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';
|
||||||
IFluenceClient as IFluenceClient$$,
|
|
||||||
CallParams as CallParams$$,
|
// Making aliases to reduce chance of accidental name collision
|
||||||
} from "@fluencelabs/js-client";
|
import {
|
||||||
import {
|
v5_callFunction as callFunction$$,
|
||||||
v5_callFunction as callFunction$$,
|
v5_registerService as registerService$$
|
||||||
v5_registerService as registerService$$,
|
} from '@fluencelabs/js-client';
|
||||||
} from "@fluencelabs/js-client";
|
|
||||||
|
|
||||||
// Services
|
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
export const test_script = `
|
export const test_script = `
|
||||||
(seq
|
(xor
|
||||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
(seq
|
||||||
(xor
|
(seq
|
||||||
(xor
|
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||||
(call -relay- ("op" "noop") [])
|
(xor
|
||||||
(fail %last_error%)
|
(call -relay- ("op" "noop") [])
|
||||||
)
|
(fail :error:)
|
||||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 0])
|
)
|
||||||
)
|
)
|
||||||
)
|
(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(
|
export type TestResult = Promise<void>;
|
||||||
peer: IFluenceClient$$,
|
|
||||||
config?: { ttl?: number },
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
export function test(...args: any) {
|
export function test(...args: TestParams): TestResult {
|
||||||
return callFunction$$(
|
return callFunction$$(
|
||||||
args,
|
args,
|
||||||
{
|
{
|
||||||
functionName: "test",
|
"functionName": "test",
|
||||||
arrow: {
|
"arrow": {
|
||||||
tag: "arrow",
|
"domain": {
|
||||||
domain: {
|
"fields": {},
|
||||||
tag: "labeledProduct",
|
"tag": "labeledProduct"
|
||||||
fields: {},
|
|
||||||
},
|
},
|
||||||
codomain: {
|
"codomain": {
|
||||||
tag: "nil",
|
"tag": "nil"
|
||||||
},
|
},
|
||||||
},
|
"tag": "arrow"
|
||||||
names: {
|
|
||||||
relay: "-relay-",
|
|
||||||
getDataSrv: "getDataSrv",
|
|
||||||
callbackSrv: "callbackSrv",
|
|
||||||
responseSrv: "callbackSrv",
|
|
||||||
responseFnName: "response",
|
|
||||||
errorHandlingSrv: "errorHandlingSrv",
|
|
||||||
errorFnName: "error",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
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
@ -90,11 +90,11 @@ export const runTest = async (): Promise<TestResult> => {
|
|||||||
|
|
||||||
console.log("running marine test...");
|
console.log("running marine test...");
|
||||||
const marine = await marineTest(wasm);
|
const marine = await marineTest(wasm);
|
||||||
|
console.log("marine test finished, result: ", marine);
|
||||||
|
|
||||||
console.log("running particle test...");
|
console.log("running particle test...");
|
||||||
await particleTest();
|
|
||||||
|
|
||||||
console.log("marine test finished, result: ", marine);
|
await particleTest();
|
||||||
|
|
||||||
const returnVal = {
|
const returnVal = {
|
||||||
hello,
|
hello,
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"author": "Fluence Labs",
|
"author": "Fluence Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluencelabs/js-client": "workspace:*",
|
"@fluencelabs/js-client-isomorphic": "workspace:*",
|
||||||
"@test/test-utils": "workspace:*"
|
"@test/test-utils": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -77,7 +77,6 @@ const getRelayTime = () => {
|
|||||||
|
|
||||||
return callAquaFunction({
|
return callAquaFunction({
|
||||||
args,
|
args,
|
||||||
def,
|
|
||||||
script,
|
script,
|
||||||
config,
|
config,
|
||||||
peer: Fluence.defaultClient,
|
peer: Fluence.defaultClient,
|
||||||
|
@ -61,21 +61,16 @@ export const startContentServer = (
|
|||||||
source: "/js-client.min.js",
|
source: "/js-client.min.js",
|
||||||
destination: "/source/index.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",
|
source: "/@fluencelabs/:name([\\w-]+)@:version([\\d.]+)/dist/:asset",
|
||||||
destination: "/node_modules/@fluencelabs/:name/dist/:asset",
|
destination:
|
||||||
|
"/node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/:name/dist/:asset",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source:
|
source:
|
||||||
"/@fluencelabs/:name([\\w-]+)@:version([\\d.]+)/dist/:prefix/:asset",
|
"/@fluencelabs/:name([\\w-]+)@:version([\\d.]+)/dist/:prefix/:asset",
|
||||||
destination: "/node_modules/@fluencelabs/:name/dist/:prefix/:asset",
|
destination:
|
||||||
|
"/node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/:name/dist/:prefix/:asset",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
headers: [
|
headers: [
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"ignorePatterns": ["src/**/__snapshots__/**/*"]
|
"ignorePatterns": ["src/**/__snapshots__/**/*", "src/**/*.js"]
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,14 @@
|
|||||||
"ts-pattern": "5.0.5"
|
"ts-pattern": "5.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fluencelabs/aqua-api": "0.12.0",
|
"@fluencelabs/aqua-api": "0.12.4-main-cee4448-2196-1",
|
||||||
"@fluencelabs/aqua-lib": "0.7.3",
|
"@fluencelabs/aqua-lib": "0.7.3",
|
||||||
"@fluencelabs/interfaces": "workspace:*",
|
"@fluencelabs/interfaces": "workspace:*",
|
||||||
|
"@fluencelabs/js-client": "workspace:^",
|
||||||
"@fluencelabs/registry": "0.8.7",
|
"@fluencelabs/registry": "0.8.7",
|
||||||
"@fluencelabs/spell": "0.5.20",
|
"@fluencelabs/spell": "0.5.20",
|
||||||
"@fluencelabs/trust-graph": "0.4.7",
|
"@fluencelabs/trust-graph": "0.4.7",
|
||||||
"vitest": "0.34.6"
|
"vitest": "0.34.6",
|
||||||
|
"zod": "3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ArrowWithoutCallbacks, NonArrowType } from "@fluencelabs/interfaces";
|
import { ArrowType, NonArrowType } from "@fluencelabs/interfaces";
|
||||||
import { match, P } from "ts-pattern";
|
import { match, P } from "ts-pattern";
|
||||||
|
|
||||||
import { getFuncArgs } from "./utils.js";
|
import { getFuncArgs } from "./utils.js";
|
||||||
|
|
||||||
export function genTypeName(
|
export function genTypeName(
|
||||||
t: NonArrowType | ArrowWithoutCallbacks,
|
t: NonArrowType | ArrowType,
|
||||||
name: string,
|
name: string,
|
||||||
): readonly [string | undefined, string] {
|
): readonly [string | undefined, string] {
|
||||||
const genType = typeToTs(t);
|
const genType = typeToTs(t);
|
||||||
@ -46,7 +46,7 @@ export function genTypeName(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
|
export function typeToTs(t: NonArrowType | ArrowType): string {
|
||||||
return match(t)
|
return match(t)
|
||||||
.with({ tag: "nil" }, () => {
|
.with({ tag: "nil" }, () => {
|
||||||
return "null";
|
return "null";
|
||||||
@ -120,16 +120,7 @@ export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
|
|||||||
return [name, typeToTs(type)];
|
return [name, typeToTs(type)];
|
||||||
});
|
});
|
||||||
|
|
||||||
const generic =
|
args.push(["callParams", `ParticleContext$$`]);
|
||||||
args.length === 0
|
|
||||||
? "null"
|
|
||||||
: args
|
|
||||||
.map(([name]) => {
|
|
||||||
return `'${name}'`;
|
|
||||||
})
|
|
||||||
.join(" | ");
|
|
||||||
|
|
||||||
args.push(["callParams", `CallParams$$<${generic}>`]);
|
|
||||||
|
|
||||||
const funcArgs = args
|
const funcArgs = args
|
||||||
.map(([name, type]) => {
|
.map(([name, type]) => {
|
||||||
|
20
packages/core/aqua-to-js/src/generate/__test__/__snapshots__/generate.snap.d.ts
vendored
Normal file
20
packages/core/aqua-to-js/src/generate/__test__/__snapshots__/generate.snap.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/* 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';
|
||||||
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
|||||||
|
/* 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';
|
||||||
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
|||||||
|
/* 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';
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -14,46 +14,55 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import url from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
import { compileFromPath } from "@fluencelabs/aqua-api";
|
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 { getPackageJsonContent, PackageJson } from "../../utils.js";
|
||||||
import { generateTypes, generateSources } from "../index.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", () => {
|
describe("Aqua to js/ts compiler", () => {
|
||||||
it("compiles smoke tests successfully", async () => {
|
beforeAll(async () => {
|
||||||
const res = await compileFromPath({
|
res = await compileFromPath({
|
||||||
filePath: url.fileURLToPath(
|
filePath: fileURLToPath(
|
||||||
new URL("./sources/smoke_test.aqua", import.meta.url),
|
new URL("./sources/smoke_test.aqua", import.meta.url),
|
||||||
),
|
),
|
||||||
imports: ["./node_modules"],
|
imports: ["./node_modules"],
|
||||||
targetType: "air",
|
targetType: "air",
|
||||||
});
|
});
|
||||||
|
|
||||||
const pkg: PackageJson = {
|
pkg = {
|
||||||
...(await getPackageJsonContent()),
|
...(await getPackageJsonContent()),
|
||||||
version: "0.0.0",
|
version: "0.0.0",
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
"@fluencelabs/aqua-api": "0.0.0",
|
"@fluencelabs/aqua-api": "0.0.0",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
|
it("matches js snapshots", async () => {
|
||||||
// @ts-expect-error don't use compileFromPath directly here
|
|
||||||
const jsResult = generateSources(res, "js", pkg);
|
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);
|
const jsTypes = generateTypes(res, pkg);
|
||||||
|
|
||||||
expect(jsResult).toMatchSnapshot();
|
await expect(jsResult).toMatchFileSnapshot(
|
||||||
expect(jsTypes).toMatchSnapshot();
|
"./__snapshots__/generate.snap.js",
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
|
await expect(jsTypes).toMatchFileSnapshot(
|
||||||
// @ts-expect-error don't use compileFromPath directly here
|
"./__snapshots__/generate.snap.d.ts",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches ts snapshots", async () => {
|
||||||
const tsResult = generateSources(res, "ts", pkg);
|
const tsResult = generateSources(res, "ts", pkg);
|
||||||
|
|
||||||
expect(tsResult).toMatchSnapshot();
|
await expect(tsResult).toMatchFileSnapshot(
|
||||||
|
"./__snapshots__/generate.snap.ts",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { recursiveRenameLaquaProps } from "../utils.js";
|
import { capitalize, recursiveRenameLaquaProps } from "../utils.js";
|
||||||
|
|
||||||
import { AquaFunction, TypeGenerator } from "./interfaces.js";
|
import { AquaFunction, TypeGenerator } from "./interfaces.js";
|
||||||
|
|
||||||
@ -40,8 +40,11 @@ ${func.script}\`;
|
|||||||
${typeGenerator.funcType(func)}
|
${typeGenerator.funcType(func)}
|
||||||
export function ${func.funcDef.functionName}(${typeGenerator.type(
|
export function ${func.funcDef.functionName}(${typeGenerator.type(
|
||||||
"...args",
|
"...args",
|
||||||
"any[]",
|
`${capitalize(func.funcDef.functionName)}Params`,
|
||||||
)}) {
|
)})${typeGenerator.type(
|
||||||
|
"",
|
||||||
|
`${capitalize(func.funcDef.functionName)}Result`,
|
||||||
|
)} {
|
||||||
return callFunction$$(
|
return callFunction$$(
|
||||||
args,
|
args,
|
||||||
${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)},
|
${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)},
|
||||||
|
@ -35,12 +35,13 @@ export default function generateHeader(
|
|||||||
*/
|
*/
|
||||||
${
|
${
|
||||||
outputType === "ts"
|
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 {
|
import {
|
||||||
v5_callFunction as callFunction$$,
|
v5_callFunction as callFunction$$,
|
||||||
v5_registerService as registerService$$,
|
v5_registerService as registerService$$
|
||||||
} from '@fluencelabs/js-client';`;
|
} from '@fluencelabs/js-client';`;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import { genTypeName, typeToTs } from "../common.js";
|
|||||||
import { CLIENT } from "../constants.js";
|
import { CLIENT } from "../constants.js";
|
||||||
import { capitalize, getFuncArgs } from "../utils.js";
|
import { capitalize, getFuncArgs } from "../utils.js";
|
||||||
|
|
||||||
|
import { DefaultServiceId } from "./service.js";
|
||||||
|
|
||||||
export interface TypeGenerator {
|
export interface TypeGenerator {
|
||||||
type(field: string, type: string): string;
|
type(field: string, type: string): string;
|
||||||
generic(field: string, type: string): string;
|
generic(field: string, type: string): string;
|
||||||
@ -54,7 +56,7 @@ export class TSTypeGenerator implements TypeGenerator {
|
|||||||
args.push([undefined, `config?: {ttl?: number}`]);
|
args.push([undefined, `config?: {ttl?: number}`]);
|
||||||
|
|
||||||
const argsDefs = args.map(([, def]) => {
|
const argsDefs = args.map(([, def]) => {
|
||||||
return " " + def;
|
return def;
|
||||||
});
|
});
|
||||||
|
|
||||||
const argsDesc = args
|
const argsDesc = args
|
||||||
@ -66,28 +68,30 @@ export class TSTypeGenerator implements TypeGenerator {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const functionOverloads = [
|
const functionOverloads = [
|
||||||
argsDefs.join(",\n"),
|
argsDefs.join(", "),
|
||||||
[` peer: ${CLIENT}`, ...argsDefs].join(",\n"),
|
[`peer: ${CLIENT}`, ...argsDefs].join(", "),
|
||||||
];
|
];
|
||||||
|
|
||||||
const [resTypeDesc, resType] = genTypeName(
|
const [resTypeDesc, resType] = genTypeName(
|
||||||
funcDef.arrow.codomain,
|
funcDef.arrow.codomain,
|
||||||
capitalize(funcDef.functionName) + "Result",
|
capitalize(funcDef.functionName) + "ResultType",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const functionOverloadArgsType = functionOverloads
|
||||||
|
.map((overload) => {
|
||||||
|
return `[${overload}]`;
|
||||||
|
})
|
||||||
|
.join(" | ");
|
||||||
|
|
||||||
return [
|
return [
|
||||||
argsDesc.join("\n"),
|
argsDesc.join("\n"),
|
||||||
resTypeDesc ?? "",
|
resTypeDesc ?? "",
|
||||||
functionOverloads
|
`export type ${capitalize(
|
||||||
.flatMap((fo) => {
|
funcDef.functionName,
|
||||||
return [
|
)}Params = ${functionOverloadArgsType};`,
|
||||||
`export function ${funcDef.functionName}(`,
|
`export type ${capitalize(
|
||||||
fo,
|
funcDef.functionName,
|
||||||
`): Promise<${resType}>;`,
|
)}Result = Promise<${resType}>;\n`,
|
||||||
"",
|
|
||||||
];
|
|
||||||
})
|
|
||||||
.join("\n"),
|
|
||||||
]
|
]
|
||||||
.filter((s) => {
|
.filter((s) => {
|
||||||
return s !== "";
|
return s !== "";
|
||||||
@ -117,13 +121,25 @@ export class TSTypeGenerator implements TypeGenerator {
|
|||||||
const serviceDecl = `service: ${srvName}Def`;
|
const serviceDecl = `service: ${srvName}Def`;
|
||||||
const serviceIdDecl = `serviceId: string`;
|
const serviceIdDecl = `serviceId: string`;
|
||||||
|
|
||||||
const registerServiceArgs = [
|
const functionOverloadsWithDefaultServiceId = [
|
||||||
[serviceDecl],
|
[serviceDecl],
|
||||||
[serviceIdDecl, serviceDecl],
|
[serviceIdDecl, serviceDecl],
|
||||||
[peerDecl, serviceDecl],
|
[peerDecl, serviceDecl],
|
||||||
[peerDecl, serviceIdDecl, serviceDecl],
|
[peerDecl, serviceIdDecl, serviceDecl],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const functionOverloadsWithoutDefaultServiceId = [
|
||||||
|
[serviceIdDecl, serviceDecl],
|
||||||
|
[peerDecl, serviceIdDecl, serviceDecl],
|
||||||
|
];
|
||||||
|
|
||||||
|
const registerServiceArgs =
|
||||||
|
// This wrong type comes from aqua team. We need to discuss fix with them
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
(srvDef.defaultServiceId as DefaultServiceId).s_Some__f_value != null
|
||||||
|
? functionOverloadsWithDefaultServiceId
|
||||||
|
: functionOverloadsWithoutDefaultServiceId;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
interfaces,
|
interfaces,
|
||||||
...registerServiceArgs.map((registerServiceArg) => {
|
...registerServiceArgs.map((registerServiceArg) => {
|
||||||
|
@ -20,7 +20,8 @@ import { recursiveRenameLaquaProps } from "../utils.js";
|
|||||||
|
|
||||||
import { TypeGenerator } from "./interfaces.js";
|
import { TypeGenerator } from "./interfaces.js";
|
||||||
|
|
||||||
interface DefaultServiceId {
|
// Actual value of defaultServiceId which comes from aqua-api
|
||||||
|
export interface DefaultServiceId {
|
||||||
s_Some__f_value?: string;
|
s_Some__f_value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { generateSources, generateTypes } from "./generate/index.js";
|
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";
|
import { getPackageJsonContent } from "./utils.js";
|
||||||
|
|
||||||
interface JsOutput {
|
interface JsOutput {
|
||||||
@ -33,6 +33,7 @@ type LanguageOutput = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type NothingToGenerate = null;
|
type NothingToGenerate = null;
|
||||||
|
type OutputType = "js" | "ts";
|
||||||
|
|
||||||
export default async function aquaToJs<T extends OutputType>(
|
export default async function aquaToJs<T extends OutputType>(
|
||||||
res: CompilationResult,
|
res: CompilationResult,
|
||||||
@ -52,8 +53,7 @@ export default async function aquaToJs<T extends OutputType>(
|
|||||||
sources: generateSources(res, "js", packageJson),
|
sources: generateSources(res, "js", packageJson),
|
||||||
types: generateTypes(res, 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
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
({
|
({
|
||||||
sources: generateSources(res, "ts", packageJson),
|
sources: generateSources(res, "ts", packageJson),
|
||||||
} as LanguageOutput[T]);
|
} as LanguageOutput[T]);
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import path from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ArrowType,
|
ArrowType,
|
||||||
@ -27,24 +27,26 @@ import {
|
|||||||
SimpleTypes,
|
SimpleTypes,
|
||||||
UnlabeledProductType,
|
UnlabeledProductType,
|
||||||
} from "@fluencelabs/interfaces";
|
} from "@fluencelabs/interfaces";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export interface PackageJson {
|
const packageJsonSchema = z.object({
|
||||||
name: string;
|
name: z.string(),
|
||||||
version: string;
|
version: z.string(),
|
||||||
devDependencies: {
|
devDependencies: z.object({
|
||||||
["@fluencelabs/aqua-api"]: string;
|
// @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> {
|
export async function getPackageJsonContent(): Promise<PackageJson> {
|
||||||
const content = await readFile(
|
const content = await readFile(
|
||||||
new URL(path.join("..", "package.json"), import.meta.url),
|
new URL(join("..", "package.json"), import.meta.url),
|
||||||
"utf-8",
|
"utf-8",
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Add validation here
|
return packageJsonSchema.parse(JSON.parse(content));
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
return JSON.parse(content) as PackageJson;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFuncArgs(
|
export function getFuncArgs(
|
||||||
|
@ -14,60 +14,11 @@
|
|||||||
* limitations under the License.
|
* 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).
|
* Peer ID's id as a base58 string (multihash/CIDv0).
|
||||||
*/
|
*/
|
||||||
export type PeerIdB58 = string;
|
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 =
|
export type JSONValue =
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
|
@ -25,6 +25,11 @@ export type SimpleTypes =
|
|||||||
|
|
||||||
export type NonArrowType = SimpleTypes | ProductType;
|
export type NonArrowType = SimpleTypes | ProductType;
|
||||||
|
|
||||||
|
export type NonArrowSimpleType =
|
||||||
|
| SimpleTypes
|
||||||
|
| UnlabeledProductType
|
||||||
|
| LabeledProductType<SimpleTypes>;
|
||||||
|
|
||||||
export type TopType = {
|
export type TopType = {
|
||||||
/**
|
/**
|
||||||
* Type descriptor. Used for pattern-matching
|
* Type descriptor. Used for pattern-matching
|
||||||
@ -154,7 +159,13 @@ export type ProductType = UnlabeledProductType | LabeledProductType;
|
|||||||
* ArrowType is a profunctor pointing its domain to codomain.
|
* ArrowType is a profunctor pointing its domain to codomain.
|
||||||
* Profunctor means variance: Arrow is contravariant on domain, and variant on 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
|
* 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
|
* Arrow which domain contains only non-arrow types
|
||||||
*/
|
*/
|
||||||
export type ArrowWithoutCallbacks = ArrowType<
|
export type ArrowWithoutCallbacks = ArrowType<UnlabeledProductType>;
|
||||||
UnlabeledProductType | LabeledProductType<SimpleTypes>
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arrow which domain does can contain both non-arrow types and arrows (which themselves cannot contain arrows)
|
* 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 {
|
export interface FunctionCallConstants {
|
||||||
/**
|
/**
|
||||||
@ -232,9 +243,7 @@ export interface FunctionCallDef {
|
|||||||
/**
|
/**
|
||||||
* Underlying arrow which represents function in aqua
|
* Underlying arrow which represents function in aqua
|
||||||
*/
|
*/
|
||||||
arrow: ArrowType<
|
arrow: ArrowWithCallbacks;
|
||||||
LabeledProductType<SimpleTypes | ArrowType<UnlabeledProductType>>
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Names of the different entities used in generated air script
|
* 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
|
* List of functions which the service consists of
|
||||||
*/
|
*/
|
||||||
functions:
|
functions:
|
||||||
| LabeledProductType<ArrowType<LabeledProductType<SimpleTypes>>>
|
| LabeledProductType<
|
||||||
|
ArrowType<LabeledProductType<SimpleTypes> | UnlabeledProductType>
|
||||||
|
>
|
||||||
| NilType;
|
| 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;
|
|
||||||
};
|
|
||||||
|
@ -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;
|
|
@ -15,6 +15,5 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./compilerSupport/aquaTypeDefinitions.js";
|
export * from "./compilerSupport/aquaTypeDefinitions.js";
|
||||||
export * from "./compilerSupport/compilerSupportInterface.js";
|
|
||||||
export * from "./commonTypes.js";
|
export * from "./commonTypes.js";
|
||||||
export * from "./future.js";
|
export * from "./future.js";
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluencelabs/avm": "0.54.0",
|
"@fluencelabs/avm": "0.54.0",
|
||||||
"@fluencelabs/marine-js": "0.7.2",
|
"@fluencelabs/marine-js": "0.8.0",
|
||||||
"@fluencelabs/marine-worker": "0.4.2",
|
"@fluencelabs/marine-worker": "0.4.2",
|
||||||
"@fluencelabs/threads": "^2.0.0"
|
"@fluencelabs/threads": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
@ -14,18 +14,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FetchedPackages, getVersionedPackage } from "../types.js";
|
import { FetchResourceFn, getVersionedPackage } from "../types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pkg name of package with version
|
* @param pkg name of package with version
|
||||||
* @param assetPath path of required asset in given package
|
* @param assetPath path of required asset in given package
|
||||||
* @param root CDN domain in browser or file system root in node
|
* @param root CDN domain in browser or file system root in node
|
||||||
*/
|
*/
|
||||||
export async function fetchResource(
|
export const fetchResource: FetchResourceFn = async (pkg, assetPath, root) => {
|
||||||
pkg: FetchedPackages,
|
|
||||||
assetPath: string,
|
|
||||||
root: string,
|
|
||||||
) {
|
|
||||||
const refinedAssetPath = assetPath.startsWith("/")
|
const refinedAssetPath = assetPath.startsWith("/")
|
||||||
? assetPath.slice(1)
|
? assetPath.slice(1)
|
||||||
: assetPath;
|
: assetPath;
|
||||||
@ -36,4 +32,4 @@ export async function fetchResource(
|
|||||||
return fetch(url).catch(() => {
|
return fetch(url).catch(() => {
|
||||||
throw new Error(`Cannot fetch from ${url.toString()}`);
|
throw new Error(`Cannot fetch from ${url.toString()}`);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
@ -18,21 +18,14 @@ import { readFile } from "fs/promises";
|
|||||||
import { createRequire } from "module";
|
import { createRequire } from "module";
|
||||||
import { sep, posix, join } from "path";
|
import { sep, posix, join } from "path";
|
||||||
|
|
||||||
import { FetchedPackages, getVersionedPackage } from "../types.js";
|
import { FetchResourceFn, getVersionedPackage } from "../types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pkg name of package with version
|
* @param pkg name of package with version
|
||||||
* @param assetPath path of required asset in given package
|
* @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(
|
export const fetchResource: FetchResourceFn = async (pkg, assetPath) => {
|
||||||
pkg: FetchedPackages,
|
|
||||||
assetPath: string,
|
|
||||||
root: string,
|
|
||||||
) {
|
|
||||||
const { name } = getVersionedPackage(pkg);
|
const { name } = getVersionedPackage(pkg);
|
||||||
// TODO: `root` will be handled somehow in the future. For now, we use filesystem root where js-client is running;
|
|
||||||
root = "/";
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const packagePathIndex = require.resolve(name);
|
const packagePathIndex = require.resolve(name);
|
||||||
|
|
||||||
@ -47,7 +40,7 @@ export async function fetchResource(
|
|||||||
throw new Error(`Cannot find dependency ${name} in path ${posixPath}`);
|
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);
|
const file = await readFile(pathToResource);
|
||||||
|
|
||||||
@ -60,4 +53,4 @@ export async function fetchResource(
|
|||||||
: "application/text",
|
: "application/text",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
@ -20,7 +20,7 @@ import versions from "./versions.js";
|
|||||||
|
|
||||||
export type FetchedPackages = keyof typeof versions;
|
export type FetchedPackages = keyof typeof versions;
|
||||||
type VersionedPackage = { name: string; version: string };
|
type VersionedPackage = { name: string; version: string };
|
||||||
export type GetWorker = (
|
export type GetWorkerFn = (
|
||||||
pkg: FetchedPackages,
|
pkg: FetchedPackages,
|
||||||
CDNUrl: string,
|
CDNUrl: string,
|
||||||
) => Promise<Worker>;
|
) => Promise<Worker>;
|
||||||
@ -31,3 +31,9 @@ export const getVersionedPackage = (pkg: FetchedPackages): VersionedPackage => {
|
|||||||
version: versions[pkg],
|
version: versions[pkg],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FetchResourceFn = (
|
||||||
|
pkg: FetchedPackages,
|
||||||
|
assetPath: string,
|
||||||
|
root: string,
|
||||||
|
) => Promise<Response>;
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
import { BlobWorker } from "@fluencelabs/threads/master";
|
import { BlobWorker } from "@fluencelabs/threads/master";
|
||||||
|
|
||||||
import { fetchResource } from "../fetchers/browser.js";
|
import { fetchResource } from "../fetchers/browser.js";
|
||||||
import type { FetchedPackages, GetWorker } from "../types.js";
|
import type { FetchedPackages, GetWorkerFn } from "../types.js";
|
||||||
|
|
||||||
export const getWorker: GetWorker = async (
|
export const getWorker: GetWorkerFn = async (
|
||||||
pkg: FetchedPackages,
|
pkg: FetchedPackages,
|
||||||
CDNUrl: string,
|
CDNUrl: string,
|
||||||
) => {
|
) => {
|
||||||
|
@ -20,10 +20,10 @@ import { fileURLToPath } from "url";
|
|||||||
|
|
||||||
import { Worker } from "@fluencelabs/threads/master";
|
import { Worker } from "@fluencelabs/threads/master";
|
||||||
|
|
||||||
import type { FetchedPackages, GetWorker } from "../types.js";
|
import type { FetchedPackages, GetWorkerFn } from "../types.js";
|
||||||
import { getVersionedPackage } from "../types.js";
|
import { getVersionedPackage } from "../types.js";
|
||||||
|
|
||||||
export const getWorker: GetWorker = (pkg: FetchedPackages) => {
|
export const getWorker: GetWorkerFn = (pkg: FetchedPackages) => {
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
const pathToThisFile = dirname(fileURLToPath(import.meta.url));
|
const pathToThisFile = dirname(fileURLToPath(import.meta.url));
|
||||||
|
@ -43,8 +43,6 @@
|
|||||||
"@libp2p/peer-id-factory": "3.0.3",
|
"@libp2p/peer-id-factory": "3.0.3",
|
||||||
"@libp2p/websockets": "7.0.4",
|
"@libp2p/websockets": "7.0.4",
|
||||||
"@multiformats/multiaddr": "11.3.0",
|
"@multiformats/multiaddr": "11.3.0",
|
||||||
"assert": "2.1.0",
|
|
||||||
"async": "3.2.4",
|
|
||||||
"bs58": "5.0.0",
|
"bs58": "5.0.0",
|
||||||
"buffer": "6.0.3",
|
"buffer": "6.0.3",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
@ -55,14 +53,12 @@
|
|||||||
"libp2p": "0.46.6",
|
"libp2p": "0.46.6",
|
||||||
"multiformats": "11.0.1",
|
"multiformats": "11.0.1",
|
||||||
"rxjs": "7.5.5",
|
"rxjs": "7.5.5",
|
||||||
"ts-pattern": "3.3.3",
|
|
||||||
"uint8arrays": "4.0.3",
|
"uint8arrays": "4.0.3",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"zod": "3.22.4"
|
"zod": "3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fluencelabs/aqua-api": "0.9.3",
|
"@fluencelabs/aqua-api": "0.9.3",
|
||||||
"@fluencelabs/marine-js": "0.7.2",
|
|
||||||
"@rollup/plugin-inject": "5.0.3",
|
"@rollup/plugin-inject": "5.0.3",
|
||||||
"@types/bs58": "4.0.1",
|
"@types/bs58": "4.0.1",
|
||||||
"@types/debug": "4.1.7",
|
"@types/debug": "4.1.7",
|
||||||
|
@ -15,182 +15,201 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
FnConfig,
|
ArrowWithoutCallbacks,
|
||||||
FunctionCallDef,
|
FunctionCallDef,
|
||||||
|
JSONValue,
|
||||||
ServiceDef,
|
ServiceDef,
|
||||||
PassedArgs,
|
SimpleTypes,
|
||||||
ServiceImpl,
|
|
||||||
} from "@fluencelabs/interfaces";
|
} from "@fluencelabs/interfaces";
|
||||||
import { getArgumentTypes } from "@fluencelabs/interfaces";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { CallAquaFunctionConfig } from "./compilerSupport/callFunction.js";
|
||||||
|
import {
|
||||||
|
aqua2js,
|
||||||
|
SchemaValidationError,
|
||||||
|
js2aqua,
|
||||||
|
wrapJsFunction,
|
||||||
|
} from "./compilerSupport/conversions.js";
|
||||||
|
import { ServiceImpl } from "./compilerSupport/types.js";
|
||||||
import { FluencePeer } from "./jsPeer/FluencePeer.js";
|
import { FluencePeer } from "./jsPeer/FluencePeer.js";
|
||||||
|
|
||||||
import { callAquaFunction, Fluence, registerService } from "./index.js";
|
import { callAquaFunction, Fluence, registerService } from "./index.js";
|
||||||
|
|
||||||
export const isFluencePeer = (
|
function validateAquaConfig(
|
||||||
fluencePeerCandidate: unknown,
|
config: unknown,
|
||||||
): fluencePeerCandidate is FluencePeer => {
|
): asserts config is CallAquaFunctionConfig | undefined {
|
||||||
return fluencePeerCandidate instanceof FluencePeer;
|
z.union([
|
||||||
};
|
z.object({
|
||||||
|
ttl: z.number().optional(),
|
||||||
|
}),
|
||||||
|
z.undefined(),
|
||||||
|
]).parse(config);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to support Aqua `func` generation backend
|
* 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
|
* 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 def - function definition generated by the Aqua compiler
|
||||||
* @param script - air script with function execution logic generated by the Aqua compiler
|
* @param script - air script with function execution logic generated by the Aqua compiler
|
||||||
*/
|
*/
|
||||||
export const v5_callFunction = async (
|
export const v5_callFunction = async (
|
||||||
rawFnArgs: unknown[],
|
args: [
|
||||||
|
client: FluencePeer | (JSONValue | ServiceImpl[string]),
|
||||||
|
...args: (JSONValue | ServiceImpl[string])[],
|
||||||
|
],
|
||||||
def: FunctionCallDef,
|
def: FunctionCallDef,
|
||||||
script: string,
|
script: string,
|
||||||
): Promise<unknown> => {
|
): Promise<JSONValue> => {
|
||||||
const { args, client: peer, config } = extractFunctionArgs(rawFnArgs, def);
|
const [peerOrArg, ...rest] = args;
|
||||||
|
|
||||||
return callAquaFunction({
|
if (!(peerOrArg instanceof FluencePeer)) {
|
||||||
args,
|
return await v5_callFunction(
|
||||||
def,
|
[getDefaultPeer(), peerOrArg, ...rest],
|
||||||
|
def,
|
||||||
|
script,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const argNames = Object.keys(
|
||||||
|
def.arrow.domain.tag === "nil" ? [] : def.arrow.domain.fields,
|
||||||
|
);
|
||||||
|
|
||||||
|
const schemaArgCount = argNames.length;
|
||||||
|
|
||||||
|
type FunctionArg = SimpleTypes | ArrowWithoutCallbacks;
|
||||||
|
|
||||||
|
const schemaFunctionArgs: Record<string, FunctionArg> =
|
||||||
|
def.arrow.domain.tag === "nil" ? {} : def.arrow.domain.fields;
|
||||||
|
|
||||||
|
// 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]>(
|
||||||
|
rest.slice(0, schemaArgCount).map((arg, i) => {
|
||||||
|
const argName = argNames[i];
|
||||||
|
const argSchema = schemaFunctionArgs[argName];
|
||||||
|
|
||||||
|
if (argSchema.tag === "arrow") {
|
||||||
|
if (typeof arg !== "function") {
|
||||||
|
throw new SchemaValidationError(
|
||||||
|
[argName],
|
||||||
|
argSchema,
|
||||||
|
"function",
|
||||||
|
arg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [argName, wrapJsFunction(arg, argSchema)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof arg === "function") {
|
||||||
|
throw new SchemaValidationError(
|
||||||
|
[argName],
|
||||||
|
argSchema,
|
||||||
|
"non-function value",
|
||||||
|
arg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [argName, js2aqua(arg, argSchema, { path: [def.functionName] })];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
|
? def.arrow.codomain.items[0]
|
||||||
|
: def.arrow.codomain;
|
||||||
|
|
||||||
|
let result = await callAquaFunction({
|
||||||
script,
|
script,
|
||||||
|
peer: peerOrArg,
|
||||||
|
args: callArgs,
|
||||||
config,
|
config,
|
||||||
peer,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (returnTypeVoid) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aqua2js(result, returnSchema);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDefaultPeer = (): FluencePeer => {
|
||||||
|
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()?",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Fluence.defaultClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDefaultServiceId = (def: ServiceDef) => {
|
||||||
|
if (def.defaultServiceId == null) {
|
||||||
|
throw new Error("Service ID is not provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
return def.defaultServiceId;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RegisterServiceType =
|
||||||
|
| [ServiceImpl]
|
||||||
|
| [string, ServiceImpl]
|
||||||
|
| [FluencePeer, ServiceImpl]
|
||||||
|
| [FluencePeer, string, ServiceImpl];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to support Aqua `service` generation backend
|
* 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
|
* 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
|
* @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
|
* @param def - service definition generated by the Aqua compiler
|
||||||
*/
|
*/
|
||||||
export const v5_registerService = (args: unknown[], def: ServiceDef): void => {
|
export const v5_registerService = (
|
||||||
// TODO: Support this in aqua-to-js package
|
args: RegisterServiceType,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
def: ServiceDef,
|
||||||
const service: ServiceImpl = args.pop() as ServiceImpl;
|
): 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 impl to convert their args ts -> aqua and backwards
|
||||||
|
const wrappedServiceImpl = Object.fromEntries(
|
||||||
|
Object.entries(serviceImpl).map(([name, func]) => {
|
||||||
|
return [name, wrapJsFunction(func, serviceSchema[name])];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
def,
|
service: wrappedServiceImpl,
|
||||||
service,
|
|
||||||
serviceId,
|
|
||||||
peer,
|
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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@ -17,15 +17,56 @@
|
|||||||
import { JSONValue } from "@fluencelabs/interfaces";
|
import { JSONValue } from "@fluencelabs/interfaces";
|
||||||
import { it, describe, expect } from "vitest";
|
import { it, describe, expect } from "vitest";
|
||||||
|
|
||||||
|
import { ExpirationError } from "../../jsPeer/errors.js";
|
||||||
import { CallServiceData } from "../../jsServiceHost/interfaces.js";
|
import { CallServiceData } from "../../jsServiceHost/interfaces.js";
|
||||||
import { doNothing } from "../../jsServiceHost/serviceUtils.js";
|
|
||||||
import { handleTimeout } from "../../particle/Particle.js";
|
import { handleTimeout } from "../../particle/Particle.js";
|
||||||
import { registerHandlersHelper, withClient } from "../../util/testUtils.js";
|
import { registerHandlersHelper, withClient } from "../../util/testUtils.js";
|
||||||
import { checkConnection } from "../checkConnection.js";
|
import { checkConnection } from "../checkConnection.js";
|
||||||
|
|
||||||
import { nodes, RELAY } from "./connection.js";
|
import { nodes, RELAY } from "./connection.js";
|
||||||
|
|
||||||
|
const ONE_SECOND = 1000;
|
||||||
|
|
||||||
describe("FluenceClient usage test suite", () => {
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 () => {
|
it("should make a call through network", async () => {
|
||||||
await withClient(RELAY, {}, async (peer) => {
|
await withClient(RELAY, {}, async (peer) => {
|
||||||
// arrange
|
// arrange
|
||||||
@ -71,7 +112,11 @@ describe("FluenceClient usage test suite", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
peer.internals.initiateParticle(
|
||||||
|
particle,
|
||||||
|
() => {},
|
||||||
|
handleTimeout(reject),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe("hello world!");
|
expect(result).toBe("hello world!");
|
||||||
@ -124,7 +169,11 @@ describe("FluenceClient usage test suite", () => {
|
|||||||
throw particle;
|
throw particle;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer1.internals.initiateParticle(particle, doNothing);
|
peer1.internals.initiateParticle(
|
||||||
|
particle,
|
||||||
|
() => {},
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
|
||||||
expect(await res).toEqual("test");
|
expect(await res).toEqual("test");
|
||||||
});
|
});
|
||||||
@ -172,13 +221,17 @@ describe("FluenceClient usage test suite", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("With connection options: defaultTTL", async () => {
|
it(
|
||||||
await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => {
|
"With connection options: defaultTTL",
|
||||||
const isConnected = await checkConnection(peer);
|
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 () => {
|
it.skip("Should throw correct error when the client tries to send a particle not to the relay", async () => {
|
||||||
@ -206,15 +259,15 @@ describe("FluenceClient usage test suite", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.internals.initiateParticle(particle, (stage) => {
|
peer.internals.initiateParticle(
|
||||||
if (stage.stage === "sendingError") {
|
particle,
|
||||||
reject(stage.errorMessage);
|
() => {},
|
||||||
}
|
(error: Error) => {
|
||||||
});
|
reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await promise;
|
|
||||||
|
|
||||||
await expect(promise).rejects.toMatch(
|
await expect(promise).rejects.toMatch(
|
||||||
"Particle is expected to be sent to only the single peer (relay which client is connected to)",
|
"Particle is expected to be sent to only the single peer (relay which client is connected to)",
|
||||||
);
|
);
|
||||||
|
@ -110,6 +110,7 @@ export const checkConnection = async (
|
|||||||
|
|
||||||
peer.internals.initiateParticle(
|
peer.internals.initiateParticle(
|
||||||
particle,
|
particle,
|
||||||
|
() => {},
|
||||||
handleTimeout(() => {
|
handleTimeout(() => {
|
||||||
reject("particle timed out");
|
reject("particle timed out");
|
||||||
}),
|
}),
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Peer ID's id as a base58 string (multihash/CIDv0).
|
* Peer ID's id as a base58 string (multihash/CIDv0).
|
||||||
*/
|
*/
|
||||||
@ -33,20 +35,30 @@ export type Node = {
|
|||||||
* - string: multiaddr in string format
|
* - string: multiaddr in string format
|
||||||
* - Node: node structure, @see Node
|
* - Node: node structure, @see Node
|
||||||
*/
|
*/
|
||||||
export type RelayOptions = string | Node;
|
export const relaySchema = z.union([
|
||||||
|
z.string(),
|
||||||
|
z.object({
|
||||||
|
peerId: z.string(),
|
||||||
|
multiaddr: z.string(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type RelayOptions = z.infer<typeof relaySchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fluence Peer's key pair types
|
* Fluence Peer's key pair types
|
||||||
*/
|
*/
|
||||||
export type KeyTypes = "RSA" | "Ed25519" | "secp256k1";
|
export type KeyTypes = "RSA" | "Ed25519" | "secp256k1";
|
||||||
|
|
||||||
|
const keyPairOptionsSchema = z.object({
|
||||||
|
type: z.literal("Ed25519"),
|
||||||
|
source: z.union([z.literal("random"), z.instanceof(Uint8Array)]),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options to specify key pair used in Fluence Peer
|
* Options to specify key pair used in Fluence Peer
|
||||||
*/
|
*/
|
||||||
export type KeyPairOptions = {
|
export type KeyPairOptions = z.infer<typeof keyPairOptionsSchema>;
|
||||||
type: "Ed25519";
|
|
||||||
source: "random" | Uint8Array;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fluence JS Client connection states as string literals
|
* Fluence JS Client connection states as string literals
|
||||||
@ -63,17 +75,10 @@ export const ConnectionStates = [
|
|||||||
*/
|
*/
|
||||||
export type ConnectionState = (typeof ConnectionStates)[number];
|
export type ConnectionState = (typeof ConnectionStates)[number];
|
||||||
|
|
||||||
export interface IFluenceInternalApi {
|
|
||||||
/**
|
|
||||||
* Internal API
|
|
||||||
*/
|
|
||||||
internals: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public API of Fluence JS Client
|
* Public API of Fluence JS Client
|
||||||
*/
|
*/
|
||||||
export interface IFluenceClient extends IFluenceInternalApi {
|
export interface IFluenceClient {
|
||||||
/**
|
/**
|
||||||
* Connect to the Fluence network
|
* Connect to the Fluence network
|
||||||
*/
|
*/
|
||||||
@ -107,65 +112,66 @@ export interface IFluenceClient extends IFluenceInternalApi {
|
|||||||
getRelayPeerId(): string;
|
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
|
* Configuration used when initiating Fluence Client
|
||||||
*/
|
*/
|
||||||
export interface ClientConfig {
|
export type ClientConfig = z.infer<typeof configSchema>;
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSONValue, NonArrowType } from "@fluencelabs/interfaces";
|
import { JSONValue, NonArrowSimpleType } from "@fluencelabs/interfaces";
|
||||||
import { it, describe, expect, test } from "vitest";
|
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;
|
const i32 = { tag: "scalar", name: "i32" } as const;
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ const nestedStructs = [
|
|||||||
interface ConversionTestArgs {
|
interface ConversionTestArgs {
|
||||||
aqua: JSONValue;
|
aqua: JSONValue;
|
||||||
ts: JSONValue;
|
ts: JSONValue;
|
||||||
type: NonArrowType;
|
type: NonArrowSimpleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Conversion from aqua to typescript", () => {
|
describe("Conversion from aqua to typescript", () => {
|
||||||
@ -200,8 +200,8 @@ describe("Conversion from aqua to typescript", () => {
|
|||||||
// arrange
|
// arrange
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const tsFromAqua = aqua2ts(aqua, type);
|
const tsFromAqua = aqua2js(aqua, type);
|
||||||
const aquaFromTs = ts2aqua(ts, type);
|
const aquaFromTs = js2aqua(ts, type, { path: [] });
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
expect(tsFromAqua).toStrictEqual(ts);
|
expect(tsFromAqua).toStrictEqual(ts);
|
||||||
@ -231,8 +231,8 @@ describe("Conversion corner cases", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const aqua = ts2aqua(valueInTs, type);
|
const aqua = js2aqua(valueInTs, type, { path: [] });
|
||||||
const ts = aqua2ts(valueInAqua, type);
|
const ts = aqua2js(valueInAqua, type);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
expect(aqua).toStrictEqual({
|
expect(aqua).toStrictEqual({
|
@ -14,18 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import assert from "assert";
|
import { JSONValue } from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
import {
|
|
||||||
FnConfig,
|
|
||||||
FunctionCallDef,
|
|
||||||
getArgumentTypes,
|
|
||||||
isReturnTypeVoid,
|
|
||||||
PassedArgs,
|
|
||||||
} from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
import { logger } from "../util/logger.js";
|
import { logger } from "../util/logger.js";
|
||||||
|
import { ArgCallbackFunction } from "../util/testUtils.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
errorHandlingService,
|
errorHandlingService,
|
||||||
@ -51,95 +44,48 @@ const log = logger("aqua");
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type CallAquaFunctionArgs = {
|
export type CallAquaFunctionArgs = {
|
||||||
def: FunctionCallDef;
|
|
||||||
script: string;
|
script: string;
|
||||||
config: FnConfig;
|
config: CallAquaFunctionConfig | undefined;
|
||||||
peer: FluencePeer;
|
peer: FluencePeer;
|
||||||
args: PassedArgs;
|
args: { [key: string]: JSONValue | ArgCallbackFunction };
|
||||||
|
fireAndForget?: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CallAquaFunctionConfig = {
|
||||||
|
ttl?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const callAquaFunction = async ({
|
export const callAquaFunction = async ({
|
||||||
def,
|
|
||||||
script,
|
script,
|
||||||
config,
|
config = {},
|
||||||
peer,
|
peer,
|
||||||
args,
|
args,
|
||||||
}: CallAquaFunctionArgs) => {
|
}: CallAquaFunctionArgs) => {
|
||||||
// TODO: this function should be rewritten. We can remove asserts if we wont check definition there
|
log.trace("calling aqua function %j", { script, config, args });
|
||||||
log.trace("calling aqua function %j", { def, script, config, args });
|
|
||||||
const argumentTypes = getArgumentTypes(def);
|
|
||||||
|
|
||||||
const particle = await peer.internals.createNewParticle(script, config.ttl);
|
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)) {
|
for (const [name, argVal] of Object.entries(args)) {
|
||||||
const type = argumentTypes[name];
|
|
||||||
let service: ServiceDescription;
|
let service: ServiceDescription;
|
||||||
|
|
||||||
if (type.tag === "arrow") {
|
if (typeof argVal === "function") {
|
||||||
// TODO: Add validation here
|
service = userHandlerService("callbackSrv", name, argVal);
|
||||||
assert(
|
|
||||||
typeof argVal === "function",
|
|
||||||
"Should not be possible, bad types",
|
|
||||||
);
|
|
||||||
|
|
||||||
service = userHandlerService(
|
|
||||||
def.names.callbackSrv,
|
|
||||||
[name, type],
|
|
||||||
argVal,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: Add validation here
|
service = injectValueService("getDataSrv", name, argVal);
|
||||||
assert(
|
|
||||||
typeof argVal !== "function",
|
|
||||||
"Should not be possible, bad types",
|
|
||||||
);
|
|
||||||
|
|
||||||
service = injectValueService(def.names.getDataSrv, name, type, argVal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerParticleScopeService(peer, particle, service);
|
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(
|
registerParticleScopeService(peer, particle, errorHandlingService(reject));
|
||||||
peer,
|
|
||||||
particle,
|
|
||||||
errorHandlingService(def, reject),
|
|
||||||
);
|
|
||||||
|
|
||||||
peer.internals.initiateParticle(particle, (stage) => {
|
peer.internals.initiateParticle(particle, resolve, reject);
|
||||||
// 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})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -14,222 +14,241 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: This file is a mess. Need to refactor it later
|
import {
|
||||||
/* eslint-disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import assert from "assert";
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ArrowType,
|
ArrowType,
|
||||||
ArrowWithoutCallbacks,
|
ArrowWithoutCallbacks,
|
||||||
JSONArray,
|
|
||||||
JSONValue,
|
JSONValue,
|
||||||
NonArrowType,
|
LabeledProductType,
|
||||||
|
NonArrowSimpleType,
|
||||||
|
ScalarType,
|
||||||
|
SimpleTypes,
|
||||||
|
UnlabeledProductType,
|
||||||
} from "@fluencelabs/interfaces";
|
} from "@fluencelabs/interfaces";
|
||||||
import { match } from "ts-pattern";
|
|
||||||
|
|
||||||
import { CallServiceData } from "../jsServiceHost/interfaces.js";
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
import { jsonify } from "../util/utils.js";
|
|
||||||
|
|
||||||
/**
|
import { ServiceImpl } from "./types.js";
|
||||||
* Convert value from its representation in aqua language to representation in typescript
|
|
||||||
* @param value - value as represented in aqua
|
export class SchemaValidationError extends Error {
|
||||||
* @param type - definition of the aqua type
|
constructor(
|
||||||
* @returns value represented in typescript
|
public path: string[],
|
||||||
*/
|
schema: NonArrowSimpleType | ArrowWithoutCallbacks,
|
||||||
export const aqua2ts = (value: JSONValue, type: NonArrowType): JSONValue => {
|
expected: string,
|
||||||
const res = match(type)
|
provided: JSONValue | ServiceImpl[string],
|
||||||
.with({ tag: "nil" }, () => {
|
) {
|
||||||
|
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,
|
||||||
|
): JSONValue {
|
||||||
|
if (schema.tag === "nil") {
|
||||||
|
return null;
|
||||||
|
} else if (schema.tag === "option") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new SchemaValidationError([], schema, "array", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
})
|
} else {
|
||||||
.with({ tag: "option" }, (opt) => {
|
return aqua2js(value[0], schema.type);
|
||||||
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 value.map((y) => {
|
||||||
return null;
|
return aqua2js(y, schema.type);
|
||||||
} 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));
|
|
||||||
});
|
});
|
||||||
|
} else if (schema.tag === "unlabeledProduct") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new SchemaValidationError([], schema, "array", value);
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return value.map((y, i) => {
|
||||||
};
|
return aqua2js(y, schema.items[i]);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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));
|
|
||||||
});
|
});
|
||||||
|
} 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) {
|
return Object.fromEntries(
|
||||||
throw new Error(
|
Object.entries(schema.fields).map(([key, type]) => {
|
||||||
`incorrect number of arguments, expected: ${argTypes.length}, got: ${req.args.length}`,
|
const val = aqua2js(value[key], type);
|
||||||
|
return [key, val];
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
throw new SchemaValidationError([], schema, "never", value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return req.args.map((arg, index) => {
|
export function js2aqua(
|
||||||
return aqua2ts(arg, argTypes[index]);
|
value: JSONValue,
|
||||||
});
|
schema: NonArrowSimpleType,
|
||||||
};
|
{ path }: ValidationContext,
|
||||||
|
): JSONValue {
|
||||||
|
if (schema.tag === "nil") {
|
||||||
|
if (value !== null) {
|
||||||
|
throw new SchemaValidationError(path, schema, "null", value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return value;
|
||||||
* Convert value from its typescript representation to representation in aqua
|
} else if (schema.tag === "option") {
|
||||||
* @param value - the value as represented in typescript
|
// option means 'type | null'
|
||||||
* @param type - definition of the aqua type
|
return value == null ? [] : [js2aqua(value, schema.type, { path })];
|
||||||
* @returns value represented in aqua
|
} else if (schema.tag === "topType") {
|
||||||
*/
|
// topType equals to 'any'
|
||||||
export const ts2aqua = (value: JSONValue, type: NonArrowType): JSONValue => {
|
return value;
|
||||||
const res = match(type)
|
} else if (schema.tag === "bottomType") {
|
||||||
.with({ tag: "nil" }, () => {
|
// bottomType equals to 'never'
|
||||||
return null;
|
throw new SchemaValidationError(path, schema, "never", value);
|
||||||
})
|
} else if (schema.tag === "scalar") {
|
||||||
.with({ tag: "option" }, (opt) => {
|
return isScalar(schema, value, { path });
|
||||||
if (value === null || value === undefined) {
|
} else if (schema.tag === "array") {
|
||||||
return [];
|
if (!Array.isArray(value)) {
|
||||||
} else {
|
throw new SchemaValidationError(path, schema, "array", value);
|
||||||
return [ts2aqua(value, opt.type)];
|
}
|
||||||
}
|
|
||||||
})
|
return value.map((y, i) => {
|
||||||
.with({ tag: "scalar" }, { tag: "bottomType" }, { tag: "topType" }, () => {
|
return js2aqua(y, schema.type, { path: [...path, `[${i}]`] });
|
||||||
return value;
|
});
|
||||||
})
|
} else if (schema.tag === "unlabeledProduct") {
|
||||||
.with({ tag: "array" }, (arr) => {
|
if (!Array.isArray(value)) {
|
||||||
assert(Array.isArray(value), "Should not be possible, bad types");
|
throw new SchemaValidationError(path, schema, "array", value);
|
||||||
return value.map((y) => {
|
}
|
||||||
return ts2aqua(y, arr.type);
|
|
||||||
});
|
return value.map((y, i) => {
|
||||||
})
|
return js2aqua(y, schema.items[i], { path: [...path, `[${i}]`] });
|
||||||
.with({ tag: "struct" }, (x) => {
|
});
|
||||||
return Object.entries(x.fields).reduce((agg, [key, type]) => {
|
} else if (["labeledProduct", "struct"].includes(schema.tag)) {
|
||||||
const val = ts2aqua(value[key], type);
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
||||||
return { ...agg, [key]: val };
|
throw new SchemaValidationError(path, schema, "object", value);
|
||||||
}, {});
|
}
|
||||||
})
|
|
||||||
.with({ tag: "labeledProduct" }, (x) => {
|
return Object.fromEntries(
|
||||||
return Object.entries(x.fields).reduce((agg, [key, type]) => {
|
Object.entries(schema.fields).map(([key, type]) => {
|
||||||
const val = ts2aqua(value[key], type);
|
const val = js2aqua(value[key], type, { path: [...path, key] });
|
||||||
return { ...agg, [key]: val };
|
return [key, val];
|
||||||
}, {});
|
}),
|
||||||
})
|
);
|
||||||
.with({ tag: "unlabeledProduct" }, (x) => {
|
} else {
|
||||||
return x.items.map((type, index) => {
|
throw new SchemaValidationError(path, schema, "never", value);
|
||||||
return ts2aqua(value[index], type);
|
}
|
||||||
});
|
}
|
||||||
})
|
|
||||||
// uncomment to check that every pattern in matched
|
// Wrapping function, converting its arguments to aqua before call and back to js after call.
|
||||||
// .exhaustive()
|
// It makes callbacks and service functions defined by user operate on js types seamlessly
|
||||||
.otherwise(() => {
|
export const wrapJsFunction = (
|
||||||
throw new Error("Unexpected tag: " + jsonify(type));
|
func: ServiceImpl[string],
|
||||||
|
schema:
|
||||||
|
| ArrowWithoutCallbacks
|
||||||
|
| ArrowType<LabeledProductType<SimpleTypes> | UnlabeledProductType>,
|
||||||
|
): ServiceImpl[string] => {
|
||||||
|
return async (...args) => {
|
||||||
|
// These assertions used to correctly destructure tuple. It's impossible to do without asserts due to ts limitations.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const jsonArgs = args.slice(0, args.length - 1) as JSONValue[];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const context = args[args.length - 1] as ParticleContext;
|
||||||
|
|
||||||
|
const schemaArgs =
|
||||||
|
schema.domain.tag === "nil"
|
||||||
|
? []
|
||||||
|
: schema.domain.tag === "unlabeledProduct"
|
||||||
|
? schema.domain.items
|
||||||
|
: Object.values(schema.domain.fields);
|
||||||
|
|
||||||
|
if (schemaArgs.length !== jsonArgs.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Schema and generated air doesn't match. Air has been called with ${jsonArgs.length} args and schema contains ${schemaArgs.length} args`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tsArgs = jsonArgs.map((arg, i) => {
|
||||||
|
return aqua2js(arg, schemaArgs[i]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return res;
|
const returnTypeVoid =
|
||||||
};
|
schema.codomain.tag === "nil" || schema.codomain.items.length === 0;
|
||||||
|
|
||||||
/**
|
const resultSchema =
|
||||||
* Convert return type of the service from it's typescript representation to representation in aqua
|
schema.codomain.tag === "unlabeledProduct" &&
|
||||||
* @param returnValue - the value as represented in typescript
|
schema.codomain.items.length === 1
|
||||||
* @param arrowType - the arrow type which describes the service
|
? schema.codomain.items[0]
|
||||||
* @returns - value represented in aqua
|
: schema.codomain;
|
||||||
*/
|
|
||||||
export const returnType2Aqua = (
|
let result = await func(...tsArgs, context);
|
||||||
returnValue: any,
|
|
||||||
arrowType: ArrowType<NonArrowType>,
|
if (returnTypeVoid) {
|
||||||
) => {
|
result = null;
|
||||||
if (arrowType.codomain.tag === "nil") {
|
}
|
||||||
return {};
|
|
||||||
}
|
return js2aqua(result, resultSchema, { path: [] });
|
||||||
|
};
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
|
@ -14,66 +14,59 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ServiceDef, ServiceImpl } from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
import { logger } from "../util/logger.js";
|
import { logger } from "../util/logger.js";
|
||||||
|
|
||||||
import { registerGlobalService, userHandlerService } from "./services.js";
|
import { registerGlobalService, userHandlerService } from "./services.js";
|
||||||
|
import { ServiceImpl } from "./types.js";
|
||||||
|
|
||||||
const log = logger("aqua");
|
const log = logger("aqua");
|
||||||
|
|
||||||
interface RegisterServiceArgs {
|
interface RegisterServiceArgs {
|
||||||
peer: FluencePeer;
|
peer: FluencePeer;
|
||||||
def: ServiceDef;
|
serviceId: string;
|
||||||
serviceId: string | undefined;
|
|
||||||
service: ServiceImpl;
|
service: ServiceImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findAllPossibleRegisteredServiceFunctions = (
|
||||||
|
service: ServiceImpl,
|
||||||
|
): Set<string> => {
|
||||||
|
let prototype: Record<string, unknown> = service;
|
||||||
|
const serviceMethods = new Set<string>();
|
||||||
|
|
||||||
|
do {
|
||||||
|
Object.getOwnPropertyNames(prototype)
|
||||||
|
.filter((prop) => {
|
||||||
|
return typeof prototype[prop] === "function" && prop !== "constructor";
|
||||||
|
})
|
||||||
|
.forEach((prop) => {
|
||||||
|
return serviceMethods.add(prop);
|
||||||
|
});
|
||||||
|
|
||||||
|
// coercing 'any' type to 'Record' bcs object prototype is actually an object
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
prototype = Object.getPrototypeOf(prototype) as Record<string, unknown>;
|
||||||
|
} while (prototype.constructor !== Object);
|
||||||
|
|
||||||
|
return serviceMethods;
|
||||||
|
};
|
||||||
|
|
||||||
export const registerService = ({
|
export const registerService = ({
|
||||||
peer,
|
peer,
|
||||||
def,
|
serviceId,
|
||||||
serviceId = def.defaultServiceId,
|
|
||||||
service,
|
service,
|
||||||
}: RegisterServiceArgs) => {
|
}: 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", { serviceId, service });
|
||||||
log.trace("registering aqua service %o", { def, serviceId, service });
|
|
||||||
|
|
||||||
// Checking for missing keys
|
const serviceFunctions = findAllPossibleRegisteredServiceFunctions(service);
|
||||||
const requiredKeys =
|
|
||||||
def.functions.tag === "nil" ? [] : Object.keys(def.functions.fields);
|
|
||||||
|
|
||||||
const incorrectServiceDefinitions = requiredKeys.filter((f) => {
|
for (const serviceFunction of serviceFunctions) {
|
||||||
return !(f in service);
|
const handler = service[serviceFunction];
|
||||||
});
|
const userDefinedHandler = handler.bind(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);
|
|
||||||
|
|
||||||
const serviceDescription = userHandlerService(
|
const serviceDescription = userHandlerService(
|
||||||
serviceId,
|
serviceId,
|
||||||
singleFunction,
|
serviceFunction,
|
||||||
userDefinedHandler,
|
userDefinedHandler,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -14,32 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SecurityTetraplet } from "@fluencelabs/avm";
|
import { JSONValue } from "@fluencelabs/interfaces";
|
||||||
import {
|
|
||||||
CallParams,
|
|
||||||
ArrowWithoutCallbacks,
|
|
||||||
FunctionCallDef,
|
|
||||||
NonArrowType,
|
|
||||||
ServiceImpl,
|
|
||||||
JSONValue,
|
|
||||||
} from "@fluencelabs/interfaces";
|
|
||||||
import { fromUint8Array } from "js-base64";
|
|
||||||
import { match } from "ts-pattern";
|
|
||||||
|
|
||||||
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
import {
|
import {
|
||||||
CallServiceData,
|
CallServiceData,
|
||||||
GenericCallServiceHandler,
|
GenericCallServiceHandler,
|
||||||
|
ParticleContext,
|
||||||
ResultCodes,
|
ResultCodes,
|
||||||
} from "../jsServiceHost/interfaces.js";
|
} from "../jsServiceHost/interfaces.js";
|
||||||
import { Particle } from "../particle/Particle.js";
|
import { Particle } from "../particle/Particle.js";
|
||||||
|
|
||||||
import {
|
import { ServiceImpl } from "./types.js";
|
||||||
aquaArgs2Ts,
|
|
||||||
responseServiceValue2ts,
|
|
||||||
returnType2Aqua,
|
|
||||||
ts2aqua,
|
|
||||||
} from "./conversions.js";
|
|
||||||
|
|
||||||
export interface ServiceDescription {
|
export interface ServiceDescription {
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
@ -50,10 +36,10 @@ export interface ServiceDescription {
|
|||||||
/**
|
/**
|
||||||
* Creates a service which injects relay's peer id into aqua space
|
* 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 {
|
return {
|
||||||
serviceId: def.names.getDataSrv,
|
serviceId: "getDataSrv",
|
||||||
fnName: def.names.relay,
|
fnName: "-relay-",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
return {
|
return {
|
||||||
retCode: ResultCodes.success,
|
retCode: ResultCodes.success,
|
||||||
@ -69,7 +55,6 @@ export const injectRelayService = (def: FunctionCallDef, peer: FluencePeer) => {
|
|||||||
export const injectValueService = (
|
export const injectValueService = (
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
fnName: string,
|
fnName: string,
|
||||||
valueType: NonArrowType,
|
|
||||||
value: JSONValue,
|
value: JSONValue,
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
@ -78,7 +63,7 @@ export const injectValueService = (
|
|||||||
handler: () => {
|
handler: () => {
|
||||||
return {
|
return {
|
||||||
retCode: ResultCodes.success,
|
retCode: ResultCodes.success,
|
||||||
result: ts2aqua(value, valueType),
|
result: value,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -87,15 +72,17 @@ export const injectValueService = (
|
|||||||
/**
|
/**
|
||||||
* Creates a service which is used to return value from aqua function into typescript space
|
* Creates a service which is used to return value from aqua function into typescript space
|
||||||
*/
|
*/
|
||||||
export const responseService = (
|
export const responseService = (resolveCallback: (val: JSONValue) => void) => {
|
||||||
def: FunctionCallDef,
|
|
||||||
resolveCallback: (val: JSONValue) => void,
|
|
||||||
) => {
|
|
||||||
return {
|
return {
|
||||||
serviceId: def.names.responseSrv,
|
serviceId: "callbackSrv",
|
||||||
fnName: def.names.responseFnName,
|
fnName: "response",
|
||||||
handler: (req: CallServiceData) => {
|
handler: (req: CallServiceData) => {
|
||||||
const userFunctionReturn = responseServiceValue2ts(req, def.arrow);
|
const userFunctionReturn =
|
||||||
|
req.args.length === 0
|
||||||
|
? null
|
||||||
|
: req.args.length === 1
|
||||||
|
? req.args[0]
|
||||||
|
: req.args;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolveCallback(userFunctionReturn);
|
resolveCallback(userFunctionReturn);
|
||||||
@ -113,12 +100,11 @@ export const responseService = (
|
|||||||
* Creates a service which is used to return errors from aqua function into typescript space
|
* Creates a service which is used to return errors from aqua function into typescript space
|
||||||
*/
|
*/
|
||||||
export const errorHandlingService = (
|
export const errorHandlingService = (
|
||||||
def: FunctionCallDef,
|
|
||||||
rejectCallback: (err: JSONValue) => void,
|
rejectCallback: (err: JSONValue) => void,
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
serviceId: def.names.errorHandlingSrv,
|
serviceId: "errorHandlingSrv",
|
||||||
fnName: def.names.errorFnName,
|
fnName: "error",
|
||||||
handler: (req: CallServiceData) => {
|
handler: (req: CallServiceData) => {
|
||||||
const [err] = req.args;
|
const [err] = req.args;
|
||||||
|
|
||||||
@ -139,21 +125,19 @@ export const errorHandlingService = (
|
|||||||
*/
|
*/
|
||||||
export const userHandlerService = (
|
export const userHandlerService = (
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
arrowType: [string, ArrowWithoutCallbacks],
|
fnName: string,
|
||||||
userHandler: ServiceImpl[string],
|
userHandler: ServiceImpl[string],
|
||||||
) => {
|
) => {
|
||||||
const [fnName, type] = arrowType;
|
|
||||||
return {
|
return {
|
||||||
serviceId,
|
serviceId,
|
||||||
fnName,
|
fnName,
|
||||||
handler: async (req: CallServiceData) => {
|
handler: async (req: CallServiceData) => {
|
||||||
const args: [...JSONValue[], CallParams<string>] = [
|
const args: [...JSONValue[], ParticleContext] = [
|
||||||
...aquaArgs2Ts(req, type),
|
...req.args,
|
||||||
extractCallParams(req, type),
|
req.particleContext,
|
||||||
];
|
];
|
||||||
|
|
||||||
const rawResult = await userHandler.bind(null)(...args);
|
const result = await userHandler.bind(null)(...args);
|
||||||
const result = returnType2Aqua(rawResult, type);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
retCode: ResultCodes.success,
|
retCode: ResultCodes.success,
|
||||||
@ -163,46 +147,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 = (
|
export const registerParticleScopeService = (
|
||||||
peer: FluencePeer,
|
peer: FluencePeer,
|
||||||
particle: Particle,
|
particle: Particle,
|
||||||
|
@ -14,19 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { JSONArray, JSONValue } from "@fluencelabs/interfaces";
|
||||||
Worker,
|
|
||||||
type Worker as WorkerImplementation,
|
|
||||||
} from "@fluencelabs/threads/master";
|
|
||||||
|
|
||||||
import { LazyLoader } from "../interfaces.js";
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
|
|
||||||
export class WorkerLoader extends LazyLoader<WorkerImplementation> {
|
export type MaybePromise<T> = T | Promise<T>;
|
||||||
constructor() {
|
|
||||||
super(() => {
|
export type ServiceImpl = Record<
|
||||||
return new Worker(
|
string,
|
||||||
"../../../node_modules/@fluencelabs/marine-worker/dist/index.js",
|
(...args: [...JSONArray, ParticleContext]) => MaybePromise<JSONValue>
|
||||||
);
|
>;
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,7 +17,7 @@
|
|||||||
import { noise } from "@chainsafe/libp2p-noise";
|
import { noise } from "@chainsafe/libp2p-noise";
|
||||||
import { yamux } from "@chainsafe/libp2p-yamux";
|
import { yamux } from "@chainsafe/libp2p-yamux";
|
||||||
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
||||||
import { Stream } from "@libp2p/interface/connection";
|
import type { Stream } from "@libp2p/interface/connection";
|
||||||
import type { PeerId } from "@libp2p/interface/peer-id";
|
import type { PeerId } from "@libp2p/interface/peer-id";
|
||||||
import { peerIdFromString } from "@libp2p/peer-id";
|
import { peerIdFromString } from "@libp2p/peer-id";
|
||||||
import { webSockets } from "@libp2p/websockets";
|
import { webSockets } from "@libp2p/websockets";
|
||||||
|
@ -91,7 +91,11 @@ describe.skip("Ephemeral networks tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// act
|
// act
|
||||||
client.internals.initiateParticle(particle, () => {});
|
client.internals.initiateParticle(
|
||||||
|
particle,
|
||||||
|
() => {},
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
await expect(promise).resolves.toBe("success");
|
await expect(promise).resolves.toBe("success");
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
||||||
|
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
|
||||||
|
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
|
||||||
|
|
||||||
import { FluencePeer, PeerConfig } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer, PeerConfig } from "../jsPeer/FluencePeer.js";
|
||||||
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
||||||
import { KeyPair } from "../keypair/index.js";
|
import { KeyPair } from "../keypair/index.js";
|
||||||
import { WasmLoaderFromNpm } from "../marine/deps-loader/node.js";
|
|
||||||
import { MarineBackgroundRunner } from "../marine/worker/index.js";
|
import { MarineBackgroundRunner } from "../marine/worker/index.js";
|
||||||
import { WorkerLoader } from "../marine/worker-script/workerLoader.js";
|
|
||||||
|
|
||||||
import { EphemeralNetwork } from "./network.js";
|
import { EphemeralNetwork } from "./network.js";
|
||||||
|
|
||||||
@ -35,25 +35,60 @@ export class EphemeralNetworkClient extends FluencePeer {
|
|||||||
network: EphemeralNetwork,
|
network: EphemeralNetwork,
|
||||||
relay: PeerIdB58,
|
relay: PeerIdB58,
|
||||||
) {
|
) {
|
||||||
const workerLoader = new WorkerLoader();
|
const conn = network.getRelayConnection(keyPair.getPeerId(), relay);
|
||||||
|
|
||||||
const controlModuleLoader = new WasmLoaderFromNpm(
|
let marineJsWasm: ArrayBuffer;
|
||||||
"@fluencelabs/marine-js",
|
let avmWasm: ArrayBuffer;
|
||||||
"marine-js.wasm",
|
|
||||||
);
|
|
||||||
|
|
||||||
const avmModuleLoader = new WasmLoaderFromNpm(
|
|
||||||
"@fluencelabs/avm",
|
|
||||||
"avm.wasm",
|
|
||||||
);
|
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
const marine = new MarineBackgroundRunner(
|
||||||
workerLoader,
|
{
|
||||||
controlModuleLoader,
|
async getValue() {
|
||||||
avmModuleLoader,
|
// TODO: load worker with avm and marine, test that it works
|
||||||
|
return getWorker("@fluencelabs/marine-worker", "/");
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getValue() {
|
||||||
|
return marineJsWasm;
|
||||||
|
},
|
||||||
|
async start(): Promise<void> {
|
||||||
|
marineJsWasm = await fetchResource(
|
||||||
|
"@fluencelabs/marine-js",
|
||||||
|
"/dist/marine-js.wasm",
|
||||||
|
"/",
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getValue() {
|
||||||
|
return avmWasm;
|
||||||
|
},
|
||||||
|
async start(): Promise<void> {
|
||||||
|
avmWasm = await fetchResource(
|
||||||
|
"@fluencelabs/avm",
|
||||||
|
"/dist/avm.wasm",
|
||||||
|
"/",
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const conn = network.getRelayConnection(keyPair.getPeerId(), relay);
|
|
||||||
super(config, keyPair, marine, new JsServiceHost(), conn);
|
super(config, keyPair, marine, new JsServiceHost(), conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
||||||
|
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
|
||||||
|
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import { IConnection } from "../connection/interfaces.js";
|
import { IConnection } from "../connection/interfaces.js";
|
||||||
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
||||||
import { fromBase64Sk, KeyPair } from "../keypair/index.js";
|
import { fromBase64Sk, KeyPair } from "../keypair/index.js";
|
||||||
import {
|
|
||||||
WorkerLoaderFromFs,
|
|
||||||
WasmLoaderFromNpm,
|
|
||||||
} from "../marine/deps-loader/node.js";
|
|
||||||
import { IMarineHost } from "../marine/interfaces.js";
|
import { IMarineHost } from "../marine/interfaces.js";
|
||||||
import { MarineBackgroundRunner } from "../marine/worker/index.js";
|
import { MarineBackgroundRunner } from "../marine/worker/index.js";
|
||||||
import { Particle } from "../particle/Particle.js";
|
import { Particle } from "../particle/Particle.js";
|
||||||
@ -224,24 +222,7 @@ class EphemeralPeer extends FluencePeer {
|
|||||||
export class EphemeralNetwork {
|
export class EphemeralNetwork {
|
||||||
private peers: Map<PeerIdB58, EphemeralPeer> = new Map();
|
private peers: Map<PeerIdB58, EphemeralPeer> = new Map();
|
||||||
|
|
||||||
workerLoader: WorkerLoaderFromFs;
|
constructor(readonly config: EphemeralConfig) {}
|
||||||
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",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the Ephemeral network up
|
* Starts the Ephemeral network up
|
||||||
@ -252,10 +233,54 @@ export class EphemeralNetwork {
|
|||||||
const promises = this.config.peers.map(async (x) => {
|
const promises = this.config.peers.map(async (x) => {
|
||||||
const kp = await fromBase64Sk(x.sk);
|
const kp = await fromBase64Sk(x.sk);
|
||||||
|
|
||||||
|
const [marineJsWasm, avmWasm] = await Promise.all([
|
||||||
|
fetchResource(
|
||||||
|
"@fluencelabs/marine-js",
|
||||||
|
"/dist/marine-js.wasm",
|
||||||
|
"/",
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
}),
|
||||||
|
fetchResource("@fluencelabs/avm", "/dist/avm.wasm", "/").then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
const marine = new MarineBackgroundRunner(
|
||||||
this.workerLoader,
|
{
|
||||||
this.controlModuleLoader,
|
async getValue() {
|
||||||
this.avmModuleLoader,
|
// TODO: load worker with avm and marine, test that it works
|
||||||
|
return getWorker("@fluencelabs/marine-worker", "/");
|
||||||
|
},
|
||||||
|
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 peerId = kp.getPeerId();
|
const peerId = kp.getPeerId();
|
||||||
|
@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
|
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
|
||||||
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
|
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
|
||||||
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js";
|
import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js";
|
||||||
import {
|
import {
|
||||||
ClientConfig,
|
ClientConfig,
|
||||||
|
configSchema,
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
RelayOptions,
|
RelayOptions,
|
||||||
|
relaySchema,
|
||||||
} from "./clientPeer/types.js";
|
} from "./clientPeer/types.js";
|
||||||
import { callAquaFunction } from "./compilerSupport/callFunction.js";
|
import { callAquaFunction } from "./compilerSupport/callFunction.js";
|
||||||
import { registerService } from "./compilerSupport/registerService.js";
|
import { registerService } from "./compilerSupport/registerService.js";
|
||||||
@ -33,34 +36,34 @@ const createClient = async (
|
|||||||
relay: RelayOptions,
|
relay: RelayOptions,
|
||||||
config: ClientConfig = {},
|
config: ClientConfig = {},
|
||||||
): Promise<ClientPeer> => {
|
): Promise<ClientPeer> => {
|
||||||
|
try {
|
||||||
|
relay = relaySchema.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 CDNUrl = config.CDNUrl ?? DEFAULT_CDN_URL;
|
||||||
|
|
||||||
const fetchMarineJsWasm = async () => {
|
const [marineJsWasm, avmWasm] = await Promise.all([
|
||||||
const resource = await fetchResource(
|
fetchResource(
|
||||||
"@fluencelabs/marine-js",
|
"@fluencelabs/marine-js",
|
||||||
"/dist/marine-js.wasm",
|
"/dist/marine-js.wasm",
|
||||||
CDNUrl,
|
CDNUrl,
|
||||||
);
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
return resource.arrayBuffer();
|
}),
|
||||||
};
|
fetchResource("@fluencelabs/avm", "/dist/avm.wasm", CDNUrl).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
const fetchAvmWasm = async () => {
|
}),
|
||||||
const resource = await fetchResource(
|
]);
|
||||||
"@fluencelabs/avm",
|
|
||||||
"/dist/avm.wasm",
|
|
||||||
CDNUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
return resource.arrayBuffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
const marineJsWasm = await fetchMarineJsWasm();
|
|
||||||
const avmWasm = await fetchAvmWasm();
|
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
const marine = new MarineBackgroundRunner(
|
||||||
{
|
{
|
||||||
async getValue() {
|
async getValue() {
|
||||||
|
// TODO: load worker with avm and marine, test that it works
|
||||||
return getWorker("@fluencelabs/marine-worker", CDNUrl);
|
return getWorker("@fluencelabs/marine-worker", CDNUrl);
|
||||||
},
|
},
|
||||||
start() {
|
start() {
|
||||||
@ -174,10 +177,14 @@ export type {
|
|||||||
KeyPairOptions,
|
KeyPairOptions,
|
||||||
} from "./clientPeer/types.js";
|
} from "./clientPeer/types.js";
|
||||||
|
|
||||||
|
export type { ParticleContext } from "./jsServiceHost/interfaces.js";
|
||||||
|
|
||||||
export { v5_callFunction, v5_registerService } from "./api.js";
|
export { v5_callFunction, v5_registerService } from "./api.js";
|
||||||
|
|
||||||
export { 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
|
// Deprecated exports. Later they will be exposed only under js-client/keypair path
|
||||||
export {
|
export {
|
||||||
KeyPair,
|
KeyPair,
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
KeyPairFormat,
|
KeyPairFormat,
|
||||||
serializeAvmArgs,
|
serializeAvmArgs,
|
||||||
} from "@fluencelabs/avm";
|
} from "@fluencelabs/avm";
|
||||||
|
import { JSONValue } from "@fluencelabs/interfaces";
|
||||||
import { fromUint8Array } from "js-base64";
|
import { fromUint8Array } from "js-base64";
|
||||||
import {
|
import {
|
||||||
concatMap,
|
concatMap,
|
||||||
@ -55,7 +56,6 @@ import {
|
|||||||
getActualTTL,
|
getActualTTL,
|
||||||
hasExpired,
|
hasExpired,
|
||||||
Particle,
|
Particle,
|
||||||
ParticleExecutionStage,
|
|
||||||
ParticleQueueItem,
|
ParticleQueueItem,
|
||||||
} from "../particle/Particle.js";
|
} from "../particle/Particle.js";
|
||||||
import { registerSig } from "../services/_aqua/services.js";
|
import { registerSig } from "../services/_aqua/services.js";
|
||||||
@ -67,6 +67,8 @@ import { Tracing } from "../services/Tracing.js";
|
|||||||
import { logger } from "../util/logger.js";
|
import { logger } from "../util/logger.js";
|
||||||
import { jsonify, isString, getErrorMessage } from "../util/utils.js";
|
import { jsonify, isString, getErrorMessage } from "../util/utils.js";
|
||||||
|
|
||||||
|
import { ExpirationError, InterpreterError, SendError } from "./errors.js";
|
||||||
|
|
||||||
const log_particle = logger("particle");
|
const log_particle = logger("particle");
|
||||||
const log_peer = logger("peer");
|
const log_peer = logger("peer");
|
||||||
|
|
||||||
@ -247,11 +249,13 @@ export abstract class FluencePeer {
|
|||||||
/**
|
/**
|
||||||
* Initiates a new particle execution starting from local peer
|
* Initiates a new particle execution starting from local peer
|
||||||
* @param particle - particle to start execution of
|
* @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
|
||||||
*/
|
*/
|
||||||
initiateParticle: (
|
initiateParticle: (
|
||||||
particle: IParticle,
|
particle: IParticle,
|
||||||
onStageChange: (stage: ParticleExecutionStage) => void,
|
onSuccess: (result: JSONValue) => void,
|
||||||
|
onError: (error: Error) => void,
|
||||||
): void => {
|
): void => {
|
||||||
if (!this.isInitialized) {
|
if (!this.isInitialized) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -268,7 +272,8 @@ export abstract class FluencePeer {
|
|||||||
this._incomingParticles.next({
|
this._incomingParticles.next({
|
||||||
particle: particle,
|
particle: particle,
|
||||||
callResults: [],
|
callResults: [],
|
||||||
onStageChange: onStageChange,
|
onSuccess,
|
||||||
|
onError,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -329,6 +334,7 @@ export abstract class FluencePeer {
|
|||||||
registerTracing(this, "tracingSrv", this._classServices.tracing);
|
registerTracing(this, "tracingSrv", this._classServices.tracing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: too long, refactor
|
||||||
private _startParticleProcessing() {
|
private _startParticleProcessing() {
|
||||||
this._particleSourceSubscription = this.connection.particleSource.subscribe(
|
this._particleSourceSubscription = this.connection.particleSource.subscribe(
|
||||||
{
|
{
|
||||||
@ -336,7 +342,8 @@ export abstract class FluencePeer {
|
|||||||
this._incomingParticles.next({
|
this._incomingParticles.next({
|
||||||
particle: p,
|
particle: p,
|
||||||
callResults: [],
|
callResults: [],
|
||||||
onStageChange: () => {},
|
onSuccess: () => {},
|
||||||
|
onError: () => {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -471,10 +478,11 @@ export abstract class FluencePeer {
|
|||||||
item.result.message,
|
item.result.message,
|
||||||
);
|
);
|
||||||
|
|
||||||
item.onStageChange({
|
item.onError(
|
||||||
stage: "interpreterError",
|
new InterpreterError(
|
||||||
errorMessage: item.result.message,
|
`Script interpretation failed: ${item.result.message} (particle id: ${item.particle.id})`,
|
||||||
});
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -493,10 +501,11 @@ export abstract class FluencePeer {
|
|||||||
this.decodeAvmData(item.result.data),
|
this.decodeAvmData(item.result.data),
|
||||||
);
|
);
|
||||||
|
|
||||||
item.onStageChange({
|
item.onError(
|
||||||
stage: "interpreterError",
|
new InterpreterError(
|
||||||
errorMessage: item.result.errorMessage,
|
`Script interpretation failed: ${item.result.errorMessage} (particle id: ${item.particle.id})`,
|
||||||
});
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -508,10 +517,6 @@ export abstract class FluencePeer {
|
|||||||
this.decodeAvmData(item.result.data),
|
this.decodeAvmData(item.result.data),
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
item.onStageChange({ stage: "interpreted" });
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
let connectionPromise: Promise<void> = Promise.resolve();
|
let connectionPromise: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
// send particle further if requested
|
// send particle further if requested
|
||||||
@ -534,8 +539,6 @@ export abstract class FluencePeer {
|
|||||||
"id %s. send successful",
|
"id %s. send successful",
|
||||||
newParticle.id,
|
newParticle.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
item.onStageChange({ stage: "sent" });
|
|
||||||
})
|
})
|
||||||
.catch((e: unknown) => {
|
.catch((e: unknown) => {
|
||||||
log_particle.error(
|
log_particle.error(
|
||||||
@ -544,10 +547,13 @@ export abstract class FluencePeer {
|
|||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
|
|
||||||
item.onStageChange({
|
const message = getErrorMessage(e);
|
||||||
stage: "sendingError",
|
|
||||||
errorMessage: getErrorMessage(e),
|
item.onError(
|
||||||
});
|
new SendError(
|
||||||
|
`Could not send particle: (particle id: ${item.particle.id}, message: ${message})`,
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,7 +566,10 @@ export abstract class FluencePeer {
|
|||||||
args: cr.arguments,
|
args: cr.arguments,
|
||||||
serviceId: cr.serviceId,
|
serviceId: cr.serviceId,
|
||||||
tetraplets: cr.tetraplets,
|
tetraplets: cr.tetraplets,
|
||||||
particleContext: getParticleContext(item.particle),
|
particleContext: getParticleContext(
|
||||||
|
item.particle,
|
||||||
|
cr.tetraplets,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
void this._execSingleCallRequest(req)
|
void this._execSingleCallRequest(req)
|
||||||
@ -582,6 +591,14 @@ export abstract class FluencePeer {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
if (
|
||||||
|
req.serviceId === "callbackSrv" &&
|
||||||
|
req.fnName === "response"
|
||||||
|
) {
|
||||||
|
// Particle already processed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const serviceResult = {
|
const serviceResult = {
|
||||||
result: jsonify(res.result),
|
result: jsonify(res.result),
|
||||||
retCode: res.retCode,
|
retCode: res.retCode,
|
||||||
@ -599,8 +616,6 @@ export abstract class FluencePeer {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
item.onStageChange({ stage: "localWorkDone" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionPromise;
|
return connectionPromise;
|
||||||
@ -623,7 +638,11 @@ export abstract class FluencePeer {
|
|||||||
|
|
||||||
this.jsServiceHost.removeParticleScopeHandlers(particleId);
|
this.jsServiceHost.removeParticleScopeHandlers(particleId);
|
||||||
|
|
||||||
item.onStageChange({ stage: "expired" });
|
item.onError(
|
||||||
|
new ExpirationError(
|
||||||
|
`Particle expired after ttl of ${item.particle.ttl}ms (particle id: ${item.particle.id})`,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private decodeAvmData(data: Uint8Array) {
|
private decodeAvmData(data: Uint8Array) {
|
||||||
|
@ -44,7 +44,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
peer.internals.initiateParticle(
|
||||||
|
particle,
|
||||||
|
() => {},
|
||||||
|
handleTimeout(reject),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res).toBe("1");
|
expect(res).toBe("1");
|
||||||
@ -85,7 +89,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"]);
|
expect(res).toStrictEqual(["1", "2"]);
|
||||||
@ -126,7 +134,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");
|
expect(res).toBe("fast_result");
|
||||||
@ -178,7 +190,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");
|
expect(res).toBe("failed_with_timeout");
|
||||||
|
@ -94,7 +94,11 @@ describe("FluencePeer flow tests", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
peer.internals.initiateParticle(
|
||||||
|
particle,
|
||||||
|
() => {},
|
||||||
|
handleTimeout(reject),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res).toEqual(expect.arrayContaining(["test1", "test1"]));
|
expect(res).toEqual(expect.arrayContaining(["test1", "test1"]));
|
||||||
|
@ -16,36 +16,11 @@
|
|||||||
|
|
||||||
import { it, describe, expect } from "vitest";
|
import { it, describe, expect } from "vitest";
|
||||||
|
|
||||||
import { isFluencePeer } from "../../api.js";
|
|
||||||
import { handleTimeout } from "../../particle/Particle.js";
|
import { handleTimeout } from "../../particle/Particle.js";
|
||||||
import {
|
import { registerHandlersHelper, withPeer } from "../../util/testUtils.js";
|
||||||
mkTestPeer,
|
|
||||||
registerHandlersHelper,
|
|
||||||
withPeer,
|
|
||||||
} from "../../util/testUtils.js";
|
|
||||||
import { FluencePeer } from "../FluencePeer.js";
|
import { FluencePeer } from "../FluencePeer.js";
|
||||||
|
|
||||||
describe("FluencePeer usage test suite", () => {
|
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 () {
|
it("Should successfully call identity on local peer", async function () {
|
||||||
await withPeer(async (peer) => {
|
await withPeer(async (peer) => {
|
||||||
const script = `
|
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");
|
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);
|
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({
|
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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
21
packages/core/js-client/src/jsPeer/errors.ts
Normal file
21
packages/core/js-client/src/jsPeer/errors.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 Fluence Labs Limited
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class ExpirationError extends Error {}
|
||||||
|
|
||||||
|
export class InterpreterError extends Error {}
|
||||||
|
|
||||||
|
export class SendError extends Error {}
|
@ -79,7 +79,7 @@ export enum ResultCodes {
|
|||||||
/**
|
/**
|
||||||
* Particle context. Contains additional information about particle which triggered `call` air instruction from AVM
|
* 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
|
* The identifier of particle which triggered the call
|
||||||
*/
|
*/
|
||||||
@ -104,7 +104,12 @@ export interface ParticleContext {
|
|||||||
* Particle's signature
|
* Particle's signature
|
||||||
*/
|
*/
|
||||||
signature: Uint8Array;
|
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
|
* Represents the information passed from AVM when a `call` air instruction is executed on the local peer
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { SecurityTetraplet } from "@fluencelabs/avm";
|
||||||
import { JSONArray } from "@fluencelabs/interfaces";
|
import { JSONArray } from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
@ -28,10 +29,6 @@ import {
|
|||||||
ResultCodes,
|
ResultCodes,
|
||||||
} from "./interfaces.js";
|
} from "./interfaces.js";
|
||||||
|
|
||||||
export const doNothing = () => {
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WrapFnIntoServiceCall = (
|
export const WrapFnIntoServiceCall = (
|
||||||
fn: (args: JSONArray) => CallServiceResultType | undefined,
|
fn: (args: JSONArray) => CallServiceResultType | undefined,
|
||||||
) => {
|
) => {
|
||||||
@ -51,13 +48,17 @@ export class ServiceError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getParticleContext = (particle: IParticle): ParticleContext => {
|
export const getParticleContext = (
|
||||||
|
particle: IParticle,
|
||||||
|
tetraplets: SecurityTetraplet[][],
|
||||||
|
): ParticleContext => {
|
||||||
return {
|
return {
|
||||||
particleId: particle.id,
|
particleId: particle.id,
|
||||||
initPeerId: particle.initPeerId,
|
initPeerId: particle.initPeerId,
|
||||||
timestamp: particle.timestamp,
|
timestamp: particle.timestamp,
|
||||||
ttl: particle.ttl,
|
ttl: particle.ttl,
|
||||||
signature: particle.signature,
|
signature: particle.signature,
|
||||||
|
tetraplets,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,11 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
|
||||||
CallResultsArray,
|
|
||||||
InterpreterResult,
|
|
||||||
RunParameters,
|
|
||||||
} from "@fluencelabs/avm";
|
|
||||||
import { JSONObject, JSONValue, JSONArray } from "@fluencelabs/interfaces";
|
import { JSONObject, JSONValue, JSONArray } from "@fluencelabs/interfaces";
|
||||||
import { CallParameters } from "@fluencelabs/marine-worker";
|
import { CallParameters } from "@fluencelabs/marine-worker";
|
||||||
import type { Worker as WorkerImplementation } from "@fluencelabs/threads/master";
|
import type { Worker as WorkerImplementation } from "@fluencelabs/threads/master";
|
||||||
@ -58,22 +53,6 @@ export interface IMarineHost extends IStartable {
|
|||||||
): Promise<JSONValue>;
|
): 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
|
* Interface for something which can hold a value
|
||||||
*/
|
*/
|
||||||
@ -94,32 +73,3 @@ export interface IWasmLoader
|
|||||||
export interface IWorkerLoader
|
export interface IWorkerLoader
|
||||||
extends IValueLoader<WorkerImplementation | Promise<WorkerImplementation>>,
|
extends IValueLoader<WorkerImplementation | Promise<WorkerImplementation>>,
|
||||||
IStartable {}
|
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() {}
|
|
||||||
}
|
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CallResultsArray } from "@fluencelabs/avm";
|
import { CallResultsArray } from "@fluencelabs/avm";
|
||||||
|
import { JSONValue } from "@fluencelabs/interfaces";
|
||||||
import { fromUint8Array, toUint8Array } from "js-base64";
|
import { fromUint8Array, toUint8Array } from "js-base64";
|
||||||
import { concat } from "uint8arrays/concat";
|
import { concat } from "uint8arrays/concat";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ExpirationError } from "../jsPeer/errors.js";
|
||||||
import { KeyPair } from "../keypair/index.js";
|
import { KeyPair } from "../keypair/index.js";
|
||||||
import { numberToLittleEndianBytes } from "../util/bytes.js";
|
import { numberToLittleEndianBytes } from "../util/bytes.js";
|
||||||
|
|
||||||
@ -183,15 +185,16 @@ export type ParticleExecutionStage =
|
|||||||
export interface ParticleQueueItem {
|
export interface ParticleQueueItem {
|
||||||
particle: IParticle;
|
particle: IParticle;
|
||||||
callResults: CallResultsArray;
|
callResults: CallResultsArray;
|
||||||
onStageChange: (state: ParticleExecutionStage) => void;
|
onSuccess: (result: JSONValue) => void;
|
||||||
|
onError: (error: Error) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to handle particle at expired stage
|
* Helper function to handle particle at expired stage
|
||||||
*/
|
*/
|
||||||
export const handleTimeout = (fn: () => void) => {
|
export const handleTimeout = (fn: () => void) => {
|
||||||
return (stage: ParticleExecutionStage) => {
|
return (error: Error) => {
|
||||||
if (stage.stage === "expired") {
|
if (error instanceof ExpirationError) {
|
||||||
fn();
|
fn();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -14,58 +14,44 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Buffer } from "buffer";
|
import { readFile } from "fs/promises";
|
||||||
import * as fs from "fs";
|
|
||||||
|
|
||||||
import { CallParams } from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
import { getErrorMessage } from "../util/utils.js";
|
import { getErrorMessage } from "../util/utils.js";
|
||||||
|
|
||||||
import { NodeUtilsDef, registerNodeUtils } from "./_aqua/node-utils.js";
|
import { registerNodeUtils } from "./_aqua/node-utils.js";
|
||||||
import { SecurityGuard } from "./securityGuard.js";
|
import { SecurityGuard } from "./securityGuard.js";
|
||||||
import { defaultGuard } from "./SingleModuleSrv.js";
|
import { defaultGuard } from "./SingleModuleSrv.js";
|
||||||
|
|
||||||
export class NodeUtils implements NodeUtilsDef {
|
export class NodeUtils {
|
||||||
constructor(private peer: FluencePeer) {
|
constructor(private peer: FluencePeer) {
|
||||||
this.securityGuard_readFile = defaultGuard(this.peer);
|
this.securityGuard_readFile = defaultGuard(this.peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
securityGuard_readFile: SecurityGuard<"path">;
|
securityGuard_readFile: SecurityGuard;
|
||||||
|
|
||||||
async read_file(path: string, callParams: CallParams<"path">) {
|
async read_file(path: string, callParams: ParticleContext) {
|
||||||
if (!this.securityGuard_readFile(callParams)) {
|
if (!this.securityGuard_readFile(callParams)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Security guard validation failed",
|
error: ["Security guard validation failed"],
|
||||||
content: null,
|
content: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Strange enough, but Buffer type works here, while reading with encoding 'utf-8' doesn't
|
const data = await readFile(path, "base64");
|
||||||
const data = await new Promise<Buffer>((resolve, reject) => {
|
|
||||||
fs.readFile(path, (err, data) => {
|
|
||||||
if (err != null) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
// TODO: this is strange bug.
|
content: [data],
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
content: data as unknown as string,
|
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: getErrorMessage(err),
|
error: [getErrorMessage(err)],
|
||||||
content: null,
|
content: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CallParams, PeerIdB58 } from "@fluencelabs/interfaces";
|
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
import { KeyPair } from "../keypair/index.js";
|
import { KeyPair } from "../keypair/index.js";
|
||||||
|
|
||||||
import { SigDef } from "./_aqua/services.js";
|
|
||||||
import {
|
import {
|
||||||
allowOnlyParticleOriginatedAt,
|
allowOnlyParticleOriginatedAt,
|
||||||
allowServiceFn,
|
allowServiceFn,
|
||||||
@ -28,7 +28,7 @@ import {
|
|||||||
} from "./securityGuard.js";
|
} from "./securityGuard.js";
|
||||||
|
|
||||||
export const defaultSigGuard = (peerId: PeerIdB58) => {
|
export const defaultSigGuard = (peerId: PeerIdB58) => {
|
||||||
return and<"data">(
|
return and(
|
||||||
allowOnlyParticleOriginatedAt(peerId),
|
allowOnlyParticleOriginatedAt(peerId),
|
||||||
or(
|
or(
|
||||||
allowServiceFn("trust-graph", "get_trust_bytes"),
|
allowServiceFn("trust-graph", "get_trust_bytes"),
|
||||||
@ -43,23 +43,23 @@ export const defaultSigGuard = (peerId: PeerIdB58) => {
|
|||||||
|
|
||||||
type SignReturnType =
|
type SignReturnType =
|
||||||
| {
|
| {
|
||||||
error: null;
|
error: [];
|
||||||
signature: number[];
|
signature: [number[]];
|
||||||
success: true;
|
success: true;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
error: string;
|
error: [string];
|
||||||
signature: null;
|
signature: [];
|
||||||
success: false;
|
success: false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Sig implements SigDef {
|
export class Sig {
|
||||||
constructor(private keyPair: KeyPair) {}
|
constructor(private keyPair: KeyPair) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configurable security guard for sign method
|
* Configurable security guard for sign method
|
||||||
*/
|
*/
|
||||||
securityGuard: SecurityGuard<"data"> = () => {
|
securityGuard: SecurityGuard = () => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,13 +75,13 @@ export class Sig implements SigDef {
|
|||||||
*/
|
*/
|
||||||
async sign(
|
async sign(
|
||||||
data: number[],
|
data: number[],
|
||||||
callParams: CallParams<"data">,
|
context: ParticleContext,
|
||||||
): Promise<SignReturnType> {
|
): Promise<SignReturnType> {
|
||||||
if (!this.securityGuard(callParams)) {
|
if (!this.securityGuard(context)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Security guard validation failed",
|
error: ["Security guard validation failed"],
|
||||||
signature: null,
|
signature: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ export class Sig implements SigDef {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
error: null,
|
error: [],
|
||||||
signature: Array.from(signedData),
|
signature: [Array.from(signedData)],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
import { CallParams } from "@fluencelabs/interfaces";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
import { getErrorMessage } from "../util/utils.js";
|
import { getErrorMessage } from "../util/utils.js";
|
||||||
|
|
||||||
import { SrvDef } from "./_aqua/single-module-srv.js";
|
|
||||||
import {
|
import {
|
||||||
allowOnlyParticleOriginatedAt,
|
allowOnlyParticleOriginatedAt,
|
||||||
SecurityGuard,
|
SecurityGuard,
|
||||||
@ -32,7 +31,8 @@ export const defaultGuard = (peer: FluencePeer) => {
|
|||||||
return allowOnlyParticleOriginatedAt(peer.keyPair.getPeerId());
|
return allowOnlyParticleOriginatedAt(peer.keyPair.getPeerId());
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Srv implements SrvDef {
|
// Service for registering marine modules in js-client's marine runtime
|
||||||
|
export class Srv {
|
||||||
private services: Set<string> = new Set();
|
private services: Set<string> = new Set();
|
||||||
|
|
||||||
constructor(private peer: FluencePeer) {
|
constructor(private peer: FluencePeer) {
|
||||||
@ -40,16 +40,13 @@ export class Srv implements SrvDef {
|
|||||||
this.securityGuard_remove = defaultGuard(this.peer);
|
this.securityGuard_remove = defaultGuard(this.peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
securityGuard_create: SecurityGuard<"wasm_b64_content">;
|
securityGuard_create: SecurityGuard;
|
||||||
|
|
||||||
async create(
|
async create(wasm_b64_content: string, callParams: ParticleContext) {
|
||||||
wasm_b64_content: string,
|
|
||||||
callParams: CallParams<"wasm_b64_content">,
|
|
||||||
) {
|
|
||||||
if (!this.securityGuard_create(callParams)) {
|
if (!this.securityGuard_create(callParams)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Security guard validation failed",
|
error: ["Marine services could be registered on %init_peer_id% only"],
|
||||||
service_id: null,
|
service_id: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -66,25 +63,25 @@ export class Srv implements SrvDef {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
service_id: newServiceId,
|
service_id: [newServiceId],
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
service_id: null,
|
service_id: null,
|
||||||
error: getErrorMessage(err),
|
error: [getErrorMessage(err)],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
securityGuard_remove: SecurityGuard<"service_id">;
|
securityGuard_remove: SecurityGuard;
|
||||||
|
|
||||||
async remove(service_id: string, callParams: CallParams<"service_id">) {
|
async remove(service_id: string, callParams: ParticleContext) {
|
||||||
if (!this.securityGuard_remove(callParams)) {
|
if (!this.securityGuard_remove(callParams)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Security guard validation failed",
|
error: ["Marine services could be remove on %init_peer_id% only"],
|
||||||
service_id: null,
|
service_id: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -92,7 +89,7 @@ export class Srv implements SrvDef {
|
|||||||
if (!this.services.has(service_id)) {
|
if (!this.services.has(service_id)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Service with id ${service_id} not found`,
|
error: [`Service with id ${service_id} not found`],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CallParams } from "@fluencelabs/interfaces";
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
|
|
||||||
import { TracingDef } from "./_aqua/tracing.js";
|
import { TracingDef } from "./_aqua/tracing.js";
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export class Tracing implements TracingDef {
|
|||||||
tracingEvent(
|
tracingEvent(
|
||||||
arrowName: string,
|
arrowName: string,
|
||||||
event: string,
|
event: string,
|
||||||
callParams: CallParams<"arrowName" | "event">,
|
callParams: ParticleContext,
|
||||||
): void {
|
): void {
|
||||||
// This console log is intentional
|
// This console log is intentional
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
|
|
||||||
import { CallParams, JSONArray } from "@fluencelabs/interfaces";
|
import { JSONArray } from "@fluencelabs/interfaces";
|
||||||
import { toUint8Array } from "js-base64";
|
import { toUint8Array } from "js-base64";
|
||||||
import { it, describe, expect, test } from "vitest";
|
import { it, describe, expect, test } from "vitest";
|
||||||
|
|
||||||
import { CallServiceData } from "../../jsServiceHost/interfaces.js";
|
import {
|
||||||
|
CallServiceData,
|
||||||
|
ParticleContext,
|
||||||
|
} from "../../jsServiceHost/interfaces.js";
|
||||||
import { KeyPair } from "../../keypair/index.js";
|
import { KeyPair } from "../../keypair/index.js";
|
||||||
import { builtInServices } from "../builtins.js";
|
import { builtInServices } from "../builtins.js";
|
||||||
import { allowServiceFn } from "../securityGuard.js";
|
import { allowServiceFn } from "../securityGuard.js";
|
||||||
@ -51,32 +54,32 @@ describe("Tests for default handler", () => {
|
|||||||
serviceId | fnName | args | retCode | result
|
serviceId | fnName | args | retCode | result
|
||||||
${"op"} | ${"identity"} | ${[]} | ${0} | ${{}}
|
${"op"} | ${"identity"} | ${[]} | ${0} | ${{}}
|
||||||
${"op"} | ${"identity"} | ${[1]} | ${0} | ${1}
|
${"op"} | ${"identity"} | ${[1]} | ${0} | ${1}
|
||||||
${"op"} | ${"identity"} | ${[1, 2]} | ${1} | ${"identity accepts up to 1 arguments, received 2 arguments"}
|
${"op"} | ${"identity"} | ${[1, 2]} | ${1} | ${"Expected 1 argument(s). Got 2"}
|
||||||
${"op"} | ${"noop"} | ${[1, 2]} | ${0} | ${{}}
|
${"op"} | ${"noop"} | ${[1, 2]} | ${0} | ${{}}
|
||||||
${"op"} | ${"array"} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
|
${"op"} | ${"array"} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
|
||||||
${"op"} | ${"array_length"} | ${[[1, 2, 3]]} | ${0} | ${3}
|
${"op"} | ${"array_length"} | ${[[1, 2, 3]]} | ${0} | ${3}
|
||||||
${"op"} | ${"array_length"} | ${[]} | ${1} | ${"array_length accepts exactly one argument, found: 0"}
|
${"op"} | ${"array_length"} | ${[]} | ${1} | ${"Expected 1 argument(s). Got 0"}
|
||||||
${"op"} | ${"concat"} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
|
${"op"} | ${"concat"} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
|
||||||
${"op"} | ${"concat"} | ${[[1, 2]]} | ${0} | ${[1, 2]}
|
${"op"} | ${"concat"} | ${[[1, 2]]} | ${0} | ${[1, 2]}
|
||||||
${"op"} | ${"concat"} | ${[]} | ${0} | ${[]}
|
${"op"} | ${"concat"} | ${[]} | ${0} | ${[]}
|
||||||
${"op"} | ${"concat"} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"}
|
${"op"} | ${"concat"} | ${[1, [1, 2], 1]} | ${1} | ${"Argument 0 expected to be of type array, Got number"}
|
||||||
${"op"} | ${"string_to_b58"} | ${["test"]} | ${0} | ${"3yZe7d"}
|
${"op"} | ${"string_to_b58"} | ${["test"]} | ${0} | ${"3yZe7d"}
|
||||||
${"op"} | ${"string_to_b58"} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"}
|
${"op"} | ${"string_to_b58"} | ${["test", 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
|
||||||
${"op"} | ${"string_from_b58"} | ${["3yZe7d"]} | ${0} | ${"test"}
|
${"op"} | ${"string_from_b58"} | ${["3yZe7d"]} | ${0} | ${"test"}
|
||||||
${"op"} | ${"string_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"}
|
${"op"} | ${"string_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
|
||||||
${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
|
${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
|
||||||
${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"}
|
${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
|
||||||
${"op"} | ${"bytes_from_b58"} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
|
${"op"} | ${"bytes_from_b58"} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
|
||||||
${"op"} | ${"bytes_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"}
|
${"op"} | ${"bytes_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
|
||||||
${"op"} | ${"sha256_string"} | ${["hello, world!"]} | ${0} | ${"QmVQ8pg6L1tpoWYeq6dpoWqnzZoSLCh7E96fCFXKvfKD3u"}
|
${"op"} | ${"sha256_string"} | ${["hello, world!"]} | ${0} | ${"QmVQ8pg6L1tpoWYeq6dpoWqnzZoSLCh7E96fCFXKvfKD3u"}
|
||||||
${"op"} | ${"sha256_string"} | ${["hello, world!", true]} | ${1} | ${"sha256_string accepts 1 argument, found: 2"}
|
${"op"} | ${"sha256_string"} | ${["hello, world!", true]} | ${1} | ${"Expected 1 argument(s). Got 2"}
|
||||||
${"op"} | ${"sha256_string"} | ${[]} | ${1} | ${"sha256_string accepts 1 argument, found: 0"}
|
${"op"} | ${"sha256_string"} | ${[]} | ${1} | ${"Expected 1 argument(s). Got 0"}
|
||||||
${"op"} | ${"concat_strings"} | ${[]} | ${0} | ${""}
|
${"op"} | ${"concat_strings"} | ${[]} | ${0} | ${""}
|
||||||
${"op"} | ${"concat_strings"} | ${["a", "b", "c"]} | ${0} | ${"abc"}
|
${"op"} | ${"concat_strings"} | ${["a", "b", "c"]} | ${0} | ${"abc"}
|
||||||
${"peer"} | ${"timeout"} | ${[200, []]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"}
|
${"peer"} | ${"timeout"} | ${[200, []]} | ${1} | ${"Argument 1 expected to be of type string, Got array"}
|
||||||
${"peer"} | ${"timeout"} | ${[200, "test"]} | ${0} | ${"test"}
|
${"peer"} | ${"timeout"} | ${[200, "test"]} | ${0} | ${"test"}
|
||||||
${"peer"} | ${"timeout"} | ${[]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"}
|
${"peer"} | ${"timeout"} | ${[]} | ${1} | ${"Expected 2 argument(s). Got 0"}
|
||||||
${"peer"} | ${"timeout"} | ${[200, "test", 1]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"}
|
${"peer"} | ${"timeout"} | ${[200, "test", 1]} | ${1} | ${"Expected 2 argument(s). Got 3"}
|
||||||
${"debug"} | ${"stringify"} | ${[]} | ${0} | ${'"<empty argument list>"'}
|
${"debug"} | ${"stringify"} | ${[]} | ${0} | ${'"<empty argument list>"'}
|
||||||
${"debug"} | ${"stringify"} | ${[{ a: 10, b: 20 }]} | ${0} | ${a10b20}
|
${"debug"} | ${"stringify"} | ${[{ a: 10, b: 20 }]} | ${0} | ${a10b20}
|
||||||
${"debug"} | ${"stringify"} | ${[1, 2, 3, 4]} | ${0} | ${oneTwoThreeFour}
|
${"debug"} | ${"stringify"} | ${[1, 2, 3, 4]} | ${0} | ${oneTwoThreeFour}
|
||||||
@ -149,6 +152,7 @@ describe("Tests for default handler", () => {
|
|||||||
timestamp: 595951200,
|
timestamp: 595951200,
|
||||||
ttl: 595961200,
|
ttl: 595961200,
|
||||||
signature: new Uint8Array([]),
|
signature: new Uint8Array([]),
|
||||||
|
tetraplets: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,6 +189,7 @@ describe("Tests for default handler", () => {
|
|||||||
timestamp: 595951200,
|
timestamp: 595951200,
|
||||||
ttl: 595961200,
|
ttl: 595961200,
|
||||||
signature: new Uint8Array([]),
|
signature: new Uint8Array([]),
|
||||||
|
tetraplets: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -243,14 +248,15 @@ const makeTestTetraplet = (
|
|||||||
initPeerId: string,
|
initPeerId: string,
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
fnName: string,
|
fnName: string,
|
||||||
): CallParams<"data"> => {
|
): ParticleContext => {
|
||||||
return {
|
return {
|
||||||
particleId: "",
|
particleId: "",
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
ttl: 0,
|
ttl: 0,
|
||||||
initPeerId: initPeerId,
|
initPeerId: initPeerId,
|
||||||
tetraplets: {
|
signature: new Uint8Array([]),
|
||||||
data: [
|
tetraplets: [
|
||||||
|
[
|
||||||
{
|
{
|
||||||
peer_pk: initPeerId,
|
peer_pk: initPeerId,
|
||||||
function_name: fnName,
|
function_name: fnName,
|
||||||
@ -258,7 +264,7 @@ const makeTestTetraplet = (
|
|||||||
json_path: "",
|
json_path: "",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -273,7 +279,7 @@ describe("Sig service tests", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.signature).toStrictEqual(testDataSig);
|
expect(res.signature).toStrictEqual([testDataSig]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sig.verify should return true for the correct signature", async () => {
|
it("sig.verify should return true for the correct signature", async () => {
|
||||||
@ -305,7 +311,7 @@ describe("Sig service tests", () => {
|
|||||||
|
|
||||||
expect(signature.success).toBe(true);
|
expect(signature.success).toBe(true);
|
||||||
assert(signature.success);
|
assert(signature.success);
|
||||||
const res = await sig.verify(signature.signature, testData);
|
const res = await sig.verify(signature.signature[0], testData);
|
||||||
|
|
||||||
expect(res).toBe(true);
|
expect(res).toBe(true);
|
||||||
});
|
});
|
||||||
@ -334,7 +340,7 @@ describe("Sig service tests", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(res.success).toBe(false);
|
expect(res.success).toBe(false);
|
||||||
expect(res.error).toBe("Security guard validation failed");
|
expect(res.error).toStrictEqual(["Security guard validation failed"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sig.sign with defaultSigGuard should not allow particles initiated from other peers", async () => {
|
it("sig.sign with defaultSigGuard should not allow particles initiated from other peers", async () => {
|
||||||
@ -352,7 +358,7 @@ describe("Sig service tests", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(res.success).toBe(false);
|
expect(res.success).toBe(false);
|
||||||
expect(res.error).toBe("Security guard validation failed");
|
expect(res.error).toStrictEqual(["Security guard validation failed"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("changing securityGuard should work", async () => {
|
it("changing securityGuard should work", async () => {
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import { it, describe, expect, beforeEach, afterEach } from "vitest";
|
import { it, describe, expect, beforeEach, afterEach } from "vitest";
|
||||||
|
|
||||||
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
||||||
import { doNothing } from "../../jsServiceHost/serviceUtils.js";
|
|
||||||
import { mkTestPeer } from "../../util/testUtils.js";
|
import { mkTestPeer } from "../../util/testUtils.js";
|
||||||
|
|
||||||
let peer: FluencePeer;
|
let peer: FluencePeer;
|
||||||
@ -72,7 +71,12 @@ describe("Sig service test suite", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const p = await peer.internals.createNewParticle(script);
|
const p = await peer.internals.createNewParticle(script);
|
||||||
peer.internals.initiateParticle(p, doNothing);
|
|
||||||
|
peer.internals.initiateParticle(
|
||||||
|
p,
|
||||||
|
() => {},
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
nestedFirst,
|
nestedFirst,
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
|
|
||||||
import { ServiceDef, ServiceImpl } from "@fluencelabs/interfaces";
|
|
||||||
import { it, describe, expect, beforeAll } from "vitest";
|
import { it, describe, expect, beforeAll } from "vitest";
|
||||||
|
|
||||||
import { registerService } from "../../compilerSupport/registerService.js";
|
import { registerService } from "../../compilerSupport/registerService.js";
|
||||||
@ -29,8 +28,6 @@ import { Sig } from "../Sig.js";
|
|||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||||
|
|
||||||
let aqua: Record<string, CompiledFnCall>;
|
let aqua: Record<string, CompiledFnCall>;
|
||||||
let sigDef: ServiceDef;
|
|
||||||
let dataProviderDef: ServiceDef;
|
|
||||||
|
|
||||||
describe("Sig service test suite", () => {
|
describe("Sig service test suite", () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@ -39,11 +36,9 @@ describe("Sig service test suite", () => {
|
|||||||
"../../../aqua_test/sigService.aqua",
|
"../../../aqua_test/sigService.aqua",
|
||||||
);
|
);
|
||||||
|
|
||||||
const { services, functions } = await compileAqua(pathToAquaFiles);
|
const { functions } = await compileAqua(pathToAquaFiles);
|
||||||
|
|
||||||
aqua = functions;
|
aqua = functions;
|
||||||
sigDef = services["Sig"];
|
|
||||||
dataProviderDef = services["DataProvider"];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Use custom sig service, success path", async () => {
|
it("Use custom sig service, success path", async () => {
|
||||||
@ -52,18 +47,16 @@ describe("Sig service test suite", () => {
|
|||||||
const customSig = new Sig(customKeyPair);
|
const customSig = new Sig(customKeyPair);
|
||||||
const data = [1, 2, 3, 4, 5];
|
const data = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
const anyService: Record<never, unknown> = customSig;
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
def: sigDef,
|
|
||||||
serviceId: "CustomSig",
|
serviceId: "CustomSig",
|
||||||
// TODO: fix this after changing registerService signature
|
service: anyService,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
service: customSig as unknown as ServiceImpl,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
def: dataProviderDef,
|
|
||||||
serviceId: "data",
|
serviceId: "data",
|
||||||
service: {
|
service: {
|
||||||
provide_data: () => {
|
provide_data: () => {
|
||||||
@ -81,7 +74,7 @@ describe("Sig service test suite", () => {
|
|||||||
const isSigCorrect = await customSig.verify(
|
const isSigCorrect = await customSig.verify(
|
||||||
// TODO: Use compiled ts wrappers
|
// TODO: Use compiled ts wrappers
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
(result as { signature: number[] }).signature,
|
(result as { signature: [number[]] }).signature[0],
|
||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -95,18 +88,16 @@ describe("Sig service test suite", () => {
|
|||||||
const customSig = new Sig(customKeyPair);
|
const customSig = new Sig(customKeyPair);
|
||||||
const data = [1, 2, 3, 4, 5];
|
const data = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
const anyService: Record<never, unknown> = customSig;
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
def: sigDef,
|
|
||||||
serviceId: "CustomSig",
|
serviceId: "CustomSig",
|
||||||
// TODO: fix this after changing registerService signature
|
service: anyService,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
service: customSig as unknown as ServiceImpl,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
def: dataProviderDef,
|
|
||||||
serviceId: "data",
|
serviceId: "data",
|
||||||
service: {
|
service: {
|
||||||
provide_data: () => {
|
provide_data: () => {
|
||||||
@ -130,7 +121,6 @@ describe("Sig service test suite", () => {
|
|||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer: peer,
|
peer: peer,
|
||||||
def: dataProviderDef,
|
|
||||||
serviceId: "data",
|
serviceId: "data",
|
||||||
service: {
|
service: {
|
||||||
provide_data: () => {
|
provide_data: () => {
|
||||||
@ -146,6 +136,11 @@ describe("Sig service test suite", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(callAsSigRes).toHaveProperty("success", false);
|
expect(callAsSigRes).toHaveProperty("success", false);
|
||||||
|
|
||||||
|
expect(callAsPeerIdRes).toHaveProperty("error", [
|
||||||
|
"Security guard validation failed",
|
||||||
|
]);
|
||||||
|
|
||||||
expect(callAsPeerIdRes).toHaveProperty("success", false);
|
expect(callAsPeerIdRes).toHaveProperty("success", false);
|
||||||
|
|
||||||
sig.securityGuard = () => {
|
sig.securityGuard = () => {
|
||||||
@ -167,7 +162,8 @@ describe("Sig service test suite", () => {
|
|||||||
const isValid = await sig.verify(
|
const isValid = await sig.verify(
|
||||||
// TODO: Use compiled ts wrappers
|
// TODO: Use compiled ts wrappers
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
(callAsSigResAfterGuardChange as { signature: number[] }).signature,
|
(callAsSigResAfterGuardChange as { signature: [number[]] })
|
||||||
|
.signature[0],
|
||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -14,90 +14,21 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* This compiled aqua file was modified to make it work in monorepo
|
|
||||||
*/
|
|
||||||
import { CallParams, ServiceImpl } from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
import { registerService } from "../../compilerSupport/registerService.js";
|
import { registerService } from "../../compilerSupport/registerService.js";
|
||||||
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
||||||
import { NodeUtils } from "../NodeUtils.js";
|
import { NodeUtils } from "../NodeUtils.js";
|
||||||
|
|
||||||
// Services
|
|
||||||
|
|
||||||
export interface NodeUtilsDef {
|
|
||||||
read_file: (
|
|
||||||
path: string,
|
|
||||||
callParams: CallParams<"path">,
|
|
||||||
) =>
|
|
||||||
| { content: string | null; error: string | null; success: boolean }
|
|
||||||
| Promise<{
|
|
||||||
content: string | null;
|
|
||||||
error: string | null;
|
|
||||||
success: boolean;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerNodeUtils(
|
export function registerNodeUtils(
|
||||||
peer: FluencePeer,
|
peer: FluencePeer,
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
service: NodeUtils,
|
service: NodeUtils,
|
||||||
) {
|
) {
|
||||||
|
const nodeUtilsService: Record<never, unknown> = service;
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
// TODO: fix this after changing registerService signature
|
service: nodeUtilsService,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
service: service as unknown as ServiceImpl,
|
|
||||||
serviceId,
|
serviceId,
|
||||||
def: {
|
|
||||||
defaultServiceId: "node_utils",
|
|
||||||
functions: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
read_file: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
path: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "unlabeledProduct",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
tag: "struct",
|
|
||||||
name: "ReadFileResult",
|
|
||||||
fields: {
|
|
||||||
content: {
|
|
||||||
tag: "option",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
tag: "option",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "bool",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,33 +14,29 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* This compiled aqua file was modified to make it work in monorepo
|
|
||||||
*/
|
|
||||||
import { CallParams, ServiceImpl } from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
import { registerService } from "../../compilerSupport/registerService.js";
|
import { registerService } from "../../compilerSupport/registerService.js";
|
||||||
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
||||||
|
import { ParticleContext } from "../../jsServiceHost/interfaces.js";
|
||||||
import { Sig } from "../Sig.js";
|
import { Sig } from "../Sig.js";
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
|
|
||||||
export interface SigDef {
|
export interface SigDef {
|
||||||
get_peer_id: (callParams: CallParams<null>) => string | Promise<string>;
|
get_peer_id: (callParams: ParticleContext) => string | Promise<string>;
|
||||||
sign: (
|
sign: (
|
||||||
data: number[],
|
data: number[],
|
||||||
callParams: CallParams<"data">,
|
callParams: ParticleContext,
|
||||||
) =>
|
) =>
|
||||||
| { error: string | null; signature: number[] | null; success: boolean }
|
| { error: [string?]; signature: [number[]?]; success: boolean }
|
||||||
| Promise<{
|
| Promise<{
|
||||||
error: string | null;
|
error: [string?];
|
||||||
signature: number[] | null;
|
signature: [number[]?];
|
||||||
success: boolean;
|
success: boolean;
|
||||||
}>;
|
}>;
|
||||||
verify: (
|
verify: (
|
||||||
signature: number[],
|
signature: number[],
|
||||||
data: number[],
|
data: number[],
|
||||||
callParams: CallParams<"signature" | "data">,
|
callParams: ParticleContext,
|
||||||
) => boolean | Promise<boolean>;
|
) => boolean | Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,113 +45,12 @@ export function registerSig(
|
|||||||
serviceId: string,
|
serviceId: string,
|
||||||
service: Sig,
|
service: Sig,
|
||||||
) {
|
) {
|
||||||
|
const sigService: Record<never, unknown> = service;
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
// TODO: fix this after changing registerService signature
|
service: sigService,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
service: service as unknown as ServiceImpl,
|
|
||||||
serviceId,
|
serviceId,
|
||||||
def: {
|
|
||||||
defaultServiceId: "sig",
|
|
||||||
functions: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
get_peer_id: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "nil",
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "unlabeledProduct",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sign: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
data: {
|
|
||||||
tag: "array",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "u8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "unlabeledProduct",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
tag: "struct",
|
|
||||||
name: "SignResult",
|
|
||||||
fields: {
|
|
||||||
error: {
|
|
||||||
tag: "option",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
signature: {
|
|
||||||
tag: "option",
|
|
||||||
type: {
|
|
||||||
tag: "array",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "u8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "bool",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
signature: {
|
|
||||||
tag: "array",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "u8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
tag: "array",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "u8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "unlabeledProduct",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
tag: "scalar",
|
|
||||||
name: "bool",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,149 +14,21 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* This compiled aqua file was modified to make it work in monorepo
|
|
||||||
*/
|
|
||||||
import { CallParams, ServiceImpl } from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
import { registerService } from "../../compilerSupport/registerService.js";
|
import { registerService } from "../../compilerSupport/registerService.js";
|
||||||
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
||||||
import { Srv } from "../SingleModuleSrv.js";
|
import { Srv } from "../SingleModuleSrv.js";
|
||||||
|
|
||||||
// Services
|
|
||||||
|
|
||||||
export interface SrvDef {
|
|
||||||
create: (
|
|
||||||
wasm_b64_content: string,
|
|
||||||
callParams: CallParams<"wasm_b64_content">,
|
|
||||||
) =>
|
|
||||||
| { error: string | null; service_id: string | null; success: boolean }
|
|
||||||
| Promise<{
|
|
||||||
error: string | null;
|
|
||||||
service_id: string | null;
|
|
||||||
success: boolean;
|
|
||||||
}>;
|
|
||||||
list: (callParams: CallParams<null>) => string[] | Promise<string[]>;
|
|
||||||
remove: (
|
|
||||||
service_id: string,
|
|
||||||
callParams: CallParams<"service_id">,
|
|
||||||
) =>
|
|
||||||
| { error: string | null; success: boolean }
|
|
||||||
| Promise<{ error: string | null; success: boolean }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerSrv(
|
export function registerSrv(
|
||||||
peer: FluencePeer,
|
peer: FluencePeer,
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
service: Srv,
|
service: Srv,
|
||||||
) {
|
) {
|
||||||
|
const singleModuleService: Record<never, unknown> = service;
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
serviceId,
|
serviceId,
|
||||||
// TODO: fix this after changing registerService signature
|
service: singleModuleService,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
service: service as unknown as ServiceImpl,
|
|
||||||
def: {
|
|
||||||
defaultServiceId: "single_module_srv",
|
|
||||||
functions: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
create: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
wasm_b64_content: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "unlabeledProduct",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
tag: "struct",
|
|
||||||
name: "ServiceCreationResult",
|
|
||||||
fields: {
|
|
||||||
error: {
|
|
||||||
tag: "option",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
service_id: {
|
|
||||||
tag: "option",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "bool",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "nil",
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "unlabeledProduct",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
tag: "array",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
remove: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
service_id: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "unlabeledProduct",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
tag: "struct",
|
|
||||||
name: "RemoveResult",
|
|
||||||
fields: {
|
|
||||||
error: {
|
|
||||||
tag: "option",
|
|
||||||
type: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "bool",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,10 +17,10 @@
|
|||||||
/**
|
/**
|
||||||
* This compiled aqua file was modified to make it work in monorepo
|
* This compiled aqua file was modified to make it work in monorepo
|
||||||
*/
|
*/
|
||||||
import { CallParams, ServiceImpl } from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
import { registerService } from "../../compilerSupport/registerService.js";
|
import { registerService } from "../../compilerSupport/registerService.js";
|
||||||
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../../jsPeer/FluencePeer.js";
|
||||||
|
import { ParticleContext } from "../../jsServiceHost/interfaces.js";
|
||||||
import { Tracing } from "../Tracing.js";
|
import { Tracing } from "../Tracing.js";
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
@ -29,7 +29,7 @@ export interface TracingDef {
|
|||||||
tracingEvent: (
|
tracingEvent: (
|
||||||
arrowName: string,
|
arrowName: string,
|
||||||
event: string,
|
event: string,
|
||||||
callParams: CallParams<"arrowName" | "event">,
|
callParams: ParticleContext,
|
||||||
) => void | Promise<void>;
|
) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,40 +38,11 @@ export function registerTracing(
|
|||||||
serviceId: string,
|
serviceId: string,
|
||||||
service: Tracing,
|
service: Tracing,
|
||||||
) {
|
) {
|
||||||
|
const tracingService: Record<never, unknown> = service;
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
peer,
|
peer,
|
||||||
serviceId,
|
serviceId,
|
||||||
// TODO: fix this after changing registerService signature
|
service: tracingService,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
service: service as unknown as ServiceImpl,
|
|
||||||
def: {
|
|
||||||
defaultServiceId: "tracingSrv",
|
|
||||||
functions: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
tracingEvent: {
|
|
||||||
tag: "arrow",
|
|
||||||
domain: {
|
|
||||||
tag: "labeledProduct",
|
|
||||||
fields: {
|
|
||||||
arrowName: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
event: {
|
|
||||||
tag: "scalar",
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
codomain: {
|
|
||||||
tag: "nil",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions
|
|
||||||
|
@ -14,74 +14,144 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import assert from "assert";
|
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
import { JSONValue } from "@fluencelabs/interfaces";
|
import { JSONValue } from "@fluencelabs/interfaces";
|
||||||
import bs58 from "bs58";
|
import bs58 from "bs58";
|
||||||
import { sha256 } from "multiformats/hashes/sha2";
|
import { sha256 } from "multiformats/hashes/sha2";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
CallServiceData,
|
||||||
CallServiceResult,
|
CallServiceResult,
|
||||||
CallServiceResultType,
|
CallServiceResultType,
|
||||||
GenericCallServiceHandler,
|
GenericCallServiceHandler,
|
||||||
ResultCodes,
|
ResultCodes,
|
||||||
} from "../jsServiceHost/interfaces.js";
|
} from "../jsServiceHost/interfaces.js";
|
||||||
import { getErrorMessage, isString, jsonify } from "../util/utils.js";
|
import { getErrorMessage, jsonify } from "../util/utils.js";
|
||||||
|
|
||||||
const success = (
|
const success = (result: CallServiceResultType): CallServiceResult => {
|
||||||
// TODO: Remove unknown after adding validation to builtin inputs
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
||||||
result: CallServiceResultType | unknown,
|
|
||||||
): CallServiceResult => {
|
|
||||||
return {
|
return {
|
||||||
// TODO: Remove type assertion after adding validation to builtin inputs
|
result,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
result: result as CallServiceResultType,
|
|
||||||
retCode: ResultCodes.success,
|
retCode: ResultCodes.success,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const error = (
|
const error = (error: CallServiceResultType): CallServiceResult => {
|
||||||
// TODO: Remove unknown after adding validation to builtin inputs
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
||||||
error: CallServiceResultType | unknown,
|
|
||||||
): CallServiceResult => {
|
|
||||||
return {
|
return {
|
||||||
// TODO: Remove type assertion after adding validation to builtin inputs
|
result: error,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
result: error as CallServiceResultType,
|
|
||||||
retCode: ResultCodes.error,
|
retCode: ResultCodes.error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const chunk = <T>(arr: T[]): T[][] => {
|
||||||
|
const res: T[][] = [];
|
||||||
|
const chunkSize = 2;
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||||
|
const chunk = arr.slice(i, i + chunkSize);
|
||||||
|
res.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
const errorNotImpl = (methodName: string) => {
|
const errorNotImpl = (methodName: string) => {
|
||||||
return error(
|
return error(
|
||||||
`The JS implementation of Peer does not support "${methodName}"`,
|
`The JS implementation of Peer does not support "${methodName}"`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeJsonImpl = (args: [Record<string, JSONValue>, ...JSONValue[]]) => {
|
const parseWithSchema = <T extends z.ZodTypeAny>(
|
||||||
const [obj, ...kvs] = args;
|
schema: T,
|
||||||
|
req: CallServiceData,
|
||||||
|
): [z.infer<T>, null] | [null, string] => {
|
||||||
|
const result = schema.safeParse(req.args, {
|
||||||
|
errorMap: (issue, ctx) => {
|
||||||
|
if (
|
||||||
|
issue.code === z.ZodIssueCode.invalid_type &&
|
||||||
|
issue.path.length === 1 &&
|
||||||
|
typeof issue.path[0] === "number"
|
||||||
|
) {
|
||||||
|
const [arg] = issue.path;
|
||||||
|
return {
|
||||||
|
message: `Argument ${arg} expected to be of type ${issue.expected}, Got ${issue.received}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const toMerge: Record<string, JSONValue> = {};
|
if (issue.code === z.ZodIssueCode.too_big) {
|
||||||
|
return {
|
||||||
|
message: `Expected ${
|
||||||
|
issue.maximum
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
} argument(s). Got ${ctx.data.length}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < kvs.length / 2; i++) {
|
if (issue.code === z.ZodIssueCode.too_small) {
|
||||||
const k = kvs[i * 2];
|
return {
|
||||||
|
message: `Expected ${
|
||||||
|
issue.minimum
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
} argument(s). Got ${ctx.data.length}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!isString(k)) {
|
if (issue.code === z.ZodIssueCode.invalid_union) {
|
||||||
return error(`Argument ${i * 2 + 1} is expected to be string`);
|
return {
|
||||||
}
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
message: `Expected argument(s). Got ${ctx.data.length}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const v = kvs[i * 2 + 1];
|
return { message: ctx.defaultError };
|
||||||
toMerge[k] = v;
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return [result.data, null];
|
||||||
|
} else {
|
||||||
|
return [null, result.error.errors[0].message];
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = { ...obj, ...toMerge };
|
|
||||||
return success(res);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: These assert made for silencing more stricter ts rules. Will be fixed in DXJ-493
|
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||||
|
type Literal = z.infer<typeof literalSchema>;
|
||||||
|
type Json = Literal | { [key: string]: Json } | Json[];
|
||||||
|
|
||||||
|
const jsonSchema: z.ZodType<Json> = z.lazy(() => {
|
||||||
|
return z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsonImplSchema = z
|
||||||
|
.tuple([z.record(jsonSchema)])
|
||||||
|
.rest(z.tuple([z.string(), jsonSchema]));
|
||||||
|
|
||||||
|
const makeJsonImpl = (args: z.infer<typeof jsonImplSchema>) => {
|
||||||
|
const [obj, ...kvs] = args;
|
||||||
|
return success({ ...obj, ...Object.fromEntries(kvs) });
|
||||||
|
};
|
||||||
|
|
||||||
|
type withSchema = <T extends z.ZodTypeAny>(
|
||||||
|
arg: T,
|
||||||
|
) => (
|
||||||
|
arg1: (value: z.infer<T>) => CallServiceResult | Promise<CallServiceResult>,
|
||||||
|
) => (req: CallServiceData) => CallServiceResult | Promise<CallServiceResult>;
|
||||||
|
|
||||||
|
const withSchema: withSchema = <T extends z.ZodTypeAny>(schema: T) => {
|
||||||
|
return (bound) => {
|
||||||
|
return (req) => {
|
||||||
|
const [value, message] = parseWithSchema(schema, req);
|
||||||
|
|
||||||
|
if (message != null) {
|
||||||
|
return error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bound(value);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const builtInServices: Record<
|
export const builtInServices: Record<
|
||||||
string,
|
string,
|
||||||
Record<string, GenericCallServiceHandler>
|
Record<string, GenericCallServiceHandler>
|
||||||
@ -116,29 +186,16 @@ export const builtInServices: Record<
|
|||||||
return errorNotImpl("peer.get_contact");
|
return errorNotImpl("peer.get_contact");
|
||||||
},
|
},
|
||||||
|
|
||||||
timeout: (req) => {
|
timeout: withSchema(z.tuple([z.number(), z.string()]))(
|
||||||
if (req.args.length !== 2) {
|
([durationMs, msg]) => {
|
||||||
return error(
|
return new Promise((resolve) => {
|
||||||
"timeout accepts exactly two arguments: timeout duration in ms and a message string",
|
setTimeout(() => {
|
||||||
);
|
const res = success(msg);
|
||||||
}
|
resolve(res);
|
||||||
|
}, durationMs);
|
||||||
const durationMs = req.args[0];
|
});
|
||||||
const message = req.args[1];
|
},
|
||||||
|
),
|
||||||
if (typeof durationMs !== "number" || typeof message !== "string") {
|
|
||||||
return error(
|
|
||||||
"timeout accepts exactly two arguments: timeout duration in ms and a message string",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const res = success(message);
|
|
||||||
resolve(res);
|
|
||||||
}, durationMs);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
kad: {
|
kad: {
|
||||||
@ -246,120 +303,48 @@ export const builtInServices: Record<
|
|||||||
return success(req.args);
|
return success(req.args);
|
||||||
},
|
},
|
||||||
|
|
||||||
array_length: (req) => {
|
array_length: withSchema(z.tuple([z.array(z.unknown())]))(([arr]) => {
|
||||||
if (req.args.length !== 1) {
|
return success(arr.length);
|
||||||
return error(
|
}),
|
||||||
"array_length accepts exactly one argument, found: " +
|
|
||||||
req.args.length,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
assert(Array.isArray(req.args[0]));
|
|
||||||
return success(req.args[0].length);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
identity: (req) => {
|
identity: withSchema(z.array(jsonSchema).max(1))((args) => {
|
||||||
if (req.args.length > 1) {
|
return success(args.length === 0 ? {} : args[0]);
|
||||||
return error(
|
}),
|
||||||
`identity accepts up to 1 arguments, received ${req.args.length} arguments`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return success(req.args.length === 0 ? {} : req.args[0]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
concat: (req) => {
|
concat: withSchema(z.array(z.array(z.unknown())))((args) => {
|
||||||
const incorrectArgIndices = req.args //
|
// Schema is used with unknown type to prevent useless runtime check
|
||||||
.map((x, i): [boolean, number] => {
|
|
||||||
return [Array.isArray(x), i];
|
|
||||||
})
|
|
||||||
.filter(([isArray]) => {
|
|
||||||
return !isArray;
|
|
||||||
})
|
|
||||||
.map(([, index]) => {
|
|
||||||
return index;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (incorrectArgIndices.length > 0) {
|
|
||||||
const str = incorrectArgIndices.join(", ");
|
|
||||||
return error(
|
|
||||||
`All arguments of 'concat' must be arrays: arguments ${str} are not`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
return success([].concat(...(req.args as never[][])));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
string_to_b58: (req) => {
|
|
||||||
if (req.args.length !== 1) {
|
|
||||||
return error("string_to_b58 accepts only one string argument");
|
|
||||||
} else {
|
|
||||||
const [input] = req.args;
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
assert(typeof input === "string");
|
|
||||||
return success(bs58.encode(new TextEncoder().encode(input)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
string_from_b58: (req) => {
|
|
||||||
if (req.args.length !== 1) {
|
|
||||||
return error("string_from_b58 accepts only one string argument");
|
|
||||||
} else {
|
|
||||||
const [input] = req.args;
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
assert(typeof input === "string");
|
|
||||||
return success(new TextDecoder().decode(bs58.decode(input)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bytes_to_b58: (req) => {
|
|
||||||
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
|
|
||||||
return error(
|
|
||||||
"bytes_to_b58 accepts only single argument: array of numbers",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
const argumentArray = req.args[0] as number[];
|
|
||||||
return success(bs58.encode(new Uint8Array(argumentArray)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bytes_from_b58: (req) => {
|
|
||||||
if (req.args.length !== 1) {
|
|
||||||
return error("bytes_from_b58 accepts only one string argument");
|
|
||||||
} else {
|
|
||||||
const [input] = req.args;
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
assert(typeof input === "string");
|
|
||||||
return success(Array.from(bs58.decode(input)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
sha256_string: async (req) => {
|
|
||||||
if (req.args.length !== 1) {
|
|
||||||
return error(
|
|
||||||
`sha256_string accepts 1 argument, found: ${req.args.length}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const [input] = req.args;
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
assert(typeof input === "string");
|
|
||||||
const inBuffer = Buffer.from(input);
|
|
||||||
const multihash = await sha256.digest(inBuffer);
|
|
||||||
|
|
||||||
return success(bs58.encode(multihash.bytes));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
concat_strings: (req) => {
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
const res = "".concat(...(req.args as string[]));
|
const arr = args as never[][];
|
||||||
return success(res);
|
|
||||||
},
|
return success(arr.flat());
|
||||||
|
}),
|
||||||
|
|
||||||
|
string_to_b58: withSchema(z.tuple([z.string()]))(([input]) => {
|
||||||
|
return success(bs58.encode(new TextEncoder().encode(input)));
|
||||||
|
}),
|
||||||
|
|
||||||
|
string_from_b58: withSchema(z.tuple([z.string()]))(([input]) => {
|
||||||
|
return success(new TextDecoder().decode(bs58.decode(input)));
|
||||||
|
}),
|
||||||
|
|
||||||
|
bytes_to_b58: withSchema(z.tuple([z.array(z.number())]))(([input]) => {
|
||||||
|
return success(bs58.encode(new Uint8Array(input)));
|
||||||
|
}),
|
||||||
|
|
||||||
|
bytes_from_b58: withSchema(z.tuple([z.string()]))(([input]) => {
|
||||||
|
return success(Array.from(bs58.decode(input)));
|
||||||
|
}),
|
||||||
|
|
||||||
|
sha256_string: withSchema(z.tuple([z.string()]))(async ([input]) => {
|
||||||
|
const inBuffer = Buffer.from(input);
|
||||||
|
const multihash = await sha256.digest(inBuffer);
|
||||||
|
|
||||||
|
return success(bs58.encode(multihash.bytes));
|
||||||
|
}),
|
||||||
|
|
||||||
|
concat_strings: withSchema(z.array(z.string()))((args) => {
|
||||||
|
return success(args.join(""));
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
debug: {
|
debug: {
|
||||||
@ -379,365 +364,187 @@ export const builtInServices: Record<
|
|||||||
},
|
},
|
||||||
|
|
||||||
math: {
|
math: {
|
||||||
add: (req) => {
|
add: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x + y);
|
return success(x + y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
sub: (req) => {
|
sub: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x - y);
|
return success(x - y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
mul: (req) => {
|
mul: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x * y);
|
return success(x * y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
fmul: (req) => {
|
fmul: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(Math.floor(x * y));
|
return success(Math.floor(x * y));
|
||||||
},
|
}),
|
||||||
|
|
||||||
div: (req) => {
|
div: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(Math.floor(x / y));
|
return success(Math.floor(x / y));
|
||||||
},
|
}),
|
||||||
|
|
||||||
rem: (req) => {
|
rem: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x % y);
|
return success(x % y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
pow: (req) => {
|
pow: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(Math.pow(x, y));
|
return success(Math.pow(x, y));
|
||||||
},
|
}),
|
||||||
|
|
||||||
log: (req) => {
|
log: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(Math.log(y) / Math.log(x));
|
return success(Math.log(y) / Math.log(x));
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
cmp: {
|
cmp: {
|
||||||
gt: (req) => {
|
gt: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x > y);
|
return success(x > y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
gte: (req) => {
|
gte: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x >= y);
|
return success(x >= y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
lt: (req) => {
|
lt: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x < y);
|
return success(x < y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
lte: (req) => {
|
lte: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x <= y);
|
return success(x <= y);
|
||||||
},
|
}),
|
||||||
|
|
||||||
cmp: (req) => {
|
cmp: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x, y] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(typeof x === "number" && typeof y === "number");
|
|
||||||
return success(x === y ? 0 : x > y ? 1 : -1);
|
return success(x === y ? 0 : x > y ? 1 : -1);
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
array: {
|
array: {
|
||||||
sum: (req) => {
|
sum: withSchema(z.tuple([z.array(z.number())]))(([xs]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
const [xs] = req.args as [number[]];
|
|
||||||
return success(
|
return success(
|
||||||
xs.reduce((agg, cur) => {
|
xs.reduce((agg, cur) => {
|
||||||
return agg + cur;
|
return agg + cur;
|
||||||
}, 0),
|
}, 0),
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
|
|
||||||
dedup: (req) => {
|
dedup: withSchema(z.tuple([z.array(z.any())]))(([xs]) => {
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [xs] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(Array.isArray(xs));
|
|
||||||
const set = new Set(xs);
|
const set = new Set(xs);
|
||||||
return success(Array.from(set));
|
return success(Array.from(set));
|
||||||
},
|
}),
|
||||||
|
|
||||||
intersect: (req) => {
|
intersect: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
|
||||||
let err;
|
([xs, ys]) => {
|
||||||
|
const intersection = xs.filter((x) => {
|
||||||
|
return ys.includes(x);
|
||||||
|
});
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
return success(intersection);
|
||||||
return err;
|
},
|
||||||
}
|
),
|
||||||
|
|
||||||
const [xs, ys] = req.args;
|
diff: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
|
||||||
// TODO: Remove after adding validation
|
([xs, ys]) => {
|
||||||
assert(Array.isArray(xs) && Array.isArray(ys));
|
const diff = xs.filter((x) => {
|
||||||
|
return !ys.includes(x);
|
||||||
|
});
|
||||||
|
|
||||||
const intersection = xs.filter((x) => {
|
return success(diff);
|
||||||
return ys.includes(x);
|
},
|
||||||
});
|
),
|
||||||
|
|
||||||
return success(intersection);
|
sdiff: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
|
||||||
},
|
([xs, ys]) => {
|
||||||
|
const sdiff = [
|
||||||
|
xs.filter((y) => {
|
||||||
|
return !ys.includes(y);
|
||||||
|
}),
|
||||||
|
ys.filter((x) => {
|
||||||
|
return !xs.includes(x);
|
||||||
|
}),
|
||||||
|
].flat();
|
||||||
|
|
||||||
diff: (req) => {
|
return success(sdiff);
|
||||||
let err;
|
},
|
||||||
|
),
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [xs, ys] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(Array.isArray(xs) && Array.isArray(ys));
|
|
||||||
|
|
||||||
const diff = xs.filter((x) => {
|
|
||||||
return !ys.includes(x);
|
|
||||||
});
|
|
||||||
|
|
||||||
return success(diff);
|
|
||||||
},
|
|
||||||
|
|
||||||
sdiff: (req) => {
|
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [xs, ys] = req.args;
|
|
||||||
// TODO: Remove after adding validation
|
|
||||||
assert(Array.isArray(xs) && Array.isArray(ys));
|
|
||||||
|
|
||||||
const sdiff = [
|
|
||||||
// force new line
|
|
||||||
...xs.filter((y) => {
|
|
||||||
return !ys.includes(y);
|
|
||||||
}),
|
|
||||||
...ys.filter((x) => {
|
|
||||||
return !xs.includes(x);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
return success(sdiff);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
json: {
|
json: {
|
||||||
obj: (req) => {
|
obj: withSchema(
|
||||||
let err;
|
z
|
||||||
|
.array(z.unknown())
|
||||||
|
.refine(
|
||||||
|
(arr) => {
|
||||||
|
return arr.length % 2 === 0;
|
||||||
|
},
|
||||||
|
(arr) => {
|
||||||
|
return {
|
||||||
|
message: "Expected even number of argument(s). Got " + arr.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.transform((args) => {
|
||||||
|
return chunk(args);
|
||||||
|
})
|
||||||
|
.pipe(z.array(z.tuple([z.string(), jsonSchema]))),
|
||||||
|
)((args) => {
|
||||||
|
return makeJsonImpl([{}, ...args]);
|
||||||
|
}),
|
||||||
|
|
||||||
if ((err = checkForArgumentsCountEven(req)) != null) {
|
put: withSchema(
|
||||||
return err;
|
z
|
||||||
}
|
.tuple([z.record(jsonSchema), z.string(), jsonSchema])
|
||||||
|
.transform(
|
||||||
|
([obj, name, value]): [{ [key: string]: Json }, [string, Json]] => {
|
||||||
|
return [obj, [name, value]];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)(makeJsonImpl),
|
||||||
|
|
||||||
// TODO: remove after adding validation
|
puts: withSchema(
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
z
|
||||||
return makeJsonImpl([{}, ...req.args] as [
|
.array(z.unknown())
|
||||||
Record<string, JSONValue>,
|
.refine(
|
||||||
...JSONValue[],
|
(arr) => {
|
||||||
]);
|
return arr.length >= 3;
|
||||||
},
|
},
|
||||||
|
(value) => {
|
||||||
|
return {
|
||||||
|
message: `Expected more than 3 argument(s). Got ${value.length}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(arr) => {
|
||||||
|
return arr.length % 2 === 1;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Argument count must be odd.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.transform((args) => {
|
||||||
|
return [args[0], ...chunk(args.slice(1))];
|
||||||
|
})
|
||||||
|
.pipe(jsonImplSchema),
|
||||||
|
)(makeJsonImpl),
|
||||||
|
|
||||||
put: (req) => {
|
stringify: withSchema(z.tuple([z.record(z.string(), jsonSchema)]))(
|
||||||
let err;
|
([json]) => {
|
||||||
|
const res = JSON.stringify(json);
|
||||||
if ((err = checkForArgumentsCount(req, 3)) != null) {
|
return success(res);
|
||||||
return err;
|
},
|
||||||
}
|
),
|
||||||
|
|
||||||
if ((err = checkForArgumentType(req, 0, "object")) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeJsonImpl(
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
req.args as [Record<string, JSONValue>, ...JSONValue[]],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
puts: (req) => {
|
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCountOdd(req)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCountMoreThan(req, 3)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = checkForArgumentType(req, 0, "object")) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeJsonImpl(
|
|
||||||
// TODO: remove after adding validation
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
req.args as [Record<string, JSONValue>, ...JSONValue[]],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
stringify: (req) => {
|
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = checkForArgumentType(req, 0, "object")) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [json] = req.args;
|
|
||||||
const res = JSON.stringify(json);
|
|
||||||
return success(res);
|
|
||||||
},
|
|
||||||
|
|
||||||
parse: (req) => {
|
|
||||||
let err;
|
|
||||||
|
|
||||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = checkForArgumentType(req, 0, "string")) != null) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [raw] = req.args;
|
|
||||||
|
|
||||||
|
parse: withSchema(z.tuple([z.string()]))(([raw]) => {
|
||||||
try {
|
try {
|
||||||
// TODO: Remove after adding validation
|
// Parsing any argument here yields JSONValue
|
||||||
assert(typeof raw === "string");
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
const json = JSON.parse(raw);
|
const json = JSON.parse(raw) as JSONValue;
|
||||||
return success(json);
|
return success(json);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return error(getErrorMessage(err));
|
return error(getErrorMessage(err));
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
"run-console": {
|
"run-console": {
|
||||||
@ -749,59 +556,3 @@ export const builtInServices: Record<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const checkForArgumentsCount = (
|
|
||||||
req: { args: Array<unknown> },
|
|
||||||
count: number,
|
|
||||||
) => {
|
|
||||||
if (req.args.length !== count) {
|
|
||||||
return error(`Expected ${count} argument(s). Got ${req.args.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkForArgumentsCountMoreThan = (
|
|
||||||
req: { args: Array<unknown> },
|
|
||||||
count: number,
|
|
||||||
) => {
|
|
||||||
if (req.args.length < count) {
|
|
||||||
return error(
|
|
||||||
`Expected more than ${count} argument(s). Got ${req.args.length}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkForArgumentsCountEven = (req: { args: Array<unknown> }) => {
|
|
||||||
if (req.args.length % 2 === 1) {
|
|
||||||
return error(`Expected even number of argument(s). Got ${req.args.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkForArgumentsCountOdd = (req: { args: Array<unknown> }) => {
|
|
||||||
if (req.args.length % 2 === 0) {
|
|
||||||
return error(`Expected odd number of argument(s). Got ${req.args.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkForArgumentType = (
|
|
||||||
req: { args: Array<unknown> },
|
|
||||||
index: number,
|
|
||||||
type: string,
|
|
||||||
) => {
|
|
||||||
const actual = typeof req.args[index];
|
|
||||||
|
|
||||||
if (actual !== type) {
|
|
||||||
return error(
|
|
||||||
`Argument ${index} expected to be of type ${type}, Got ${actual}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
@ -15,25 +15,25 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SecurityTetraplet } from "@fluencelabs/avm";
|
import { SecurityTetraplet } from "@fluencelabs/avm";
|
||||||
import { CallParams, PeerIdB58 } from "@fluencelabs/interfaces";
|
import { PeerIdB58 } from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
type ArgName = string | null;
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
|
|
||||||
|
// Helpers for validating service function
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A predicate of call params for sig service's sign method which determines whether signing operation is allowed or not
|
* A predicate of call params for sig service's sign method which determines whether signing operation is allowed or not
|
||||||
*/
|
*/
|
||||||
export type SecurityGuard<T extends ArgName> = (
|
export type SecurityGuard = (params: ParticleContext) => boolean;
|
||||||
params: CallParams<T>,
|
|
||||||
) => boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only allow calls when tetraplet for 'data' argument satisfies the predicate
|
* Only allow calls when tetraplet for 'data' argument satisfies the predicate
|
||||||
*/
|
*/
|
||||||
export const allowTetraplet = <T extends ArgName>(
|
export const allowTetraplet = (
|
||||||
pred: (tetraplet: SecurityTetraplet) => boolean,
|
pred: (tetraplet: SecurityTetraplet) => boolean,
|
||||||
): SecurityGuard<T> => {
|
): SecurityGuard => {
|
||||||
return (params) => {
|
return (params) => {
|
||||||
const t = params.tetraplets["data"][0];
|
const t = params.tetraplets[0][0];
|
||||||
return pred(t);
|
return pred(t);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -41,10 +41,10 @@ export const allowTetraplet = <T extends ArgName>(
|
|||||||
/**
|
/**
|
||||||
* Only allow data which comes from the specified serviceId and fnName
|
* Only allow data which comes from the specified serviceId and fnName
|
||||||
*/
|
*/
|
||||||
export const allowServiceFn = <T extends ArgName>(
|
export const allowServiceFn = (
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
fnName: string,
|
fnName: string,
|
||||||
): SecurityGuard<T> => {
|
): SecurityGuard => {
|
||||||
return allowTetraplet((t) => {
|
return allowTetraplet((t) => {
|
||||||
return t.service_id === serviceId && t.function_name === fnName;
|
return t.service_id === serviceId && t.function_name === fnName;
|
||||||
});
|
});
|
||||||
@ -53,9 +53,7 @@ export const allowServiceFn = <T extends ArgName>(
|
|||||||
/**
|
/**
|
||||||
* Only allow data originated from the specified json_path
|
* Only allow data originated from the specified json_path
|
||||||
*/
|
*/
|
||||||
export const allowExactJsonPath = <T extends ArgName>(
|
export const allowExactJsonPath = (jsonPath: string): SecurityGuard => {
|
||||||
jsonPath: string,
|
|
||||||
): SecurityGuard<T> => {
|
|
||||||
return allowTetraplet((t) => {
|
return allowTetraplet((t) => {
|
||||||
return t.json_path === jsonPath;
|
return t.json_path === jsonPath;
|
||||||
});
|
});
|
||||||
@ -64,9 +62,9 @@ export const allowExactJsonPath = <T extends ArgName>(
|
|||||||
/**
|
/**
|
||||||
* Only allow signing when particle is initiated at the specified peer
|
* Only allow signing when particle is initiated at the specified peer
|
||||||
*/
|
*/
|
||||||
export const allowOnlyParticleOriginatedAt = <T extends ArgName>(
|
export const allowOnlyParticleOriginatedAt = (
|
||||||
peerId: PeerIdB58,
|
peerId: PeerIdB58,
|
||||||
): SecurityGuard<T> => {
|
): SecurityGuard => {
|
||||||
return (params) => {
|
return (params) => {
|
||||||
return params.initPeerId === peerId;
|
return params.initPeerId === peerId;
|
||||||
};
|
};
|
||||||
@ -76,9 +74,7 @@ export const allowOnlyParticleOriginatedAt = <T extends ArgName>(
|
|||||||
* Only allow signing when all of the predicates are satisfied.
|
* Only allow signing when all of the predicates are satisfied.
|
||||||
* Useful for predicates reuse
|
* Useful for predicates reuse
|
||||||
*/
|
*/
|
||||||
export const and = <T extends ArgName>(
|
export const and = (...predicates: SecurityGuard[]): SecurityGuard => {
|
||||||
...predicates: SecurityGuard<T>[]
|
|
||||||
): SecurityGuard<T> => {
|
|
||||||
return (params) => {
|
return (params) => {
|
||||||
return predicates.every((x) => {
|
return predicates.every((x) => {
|
||||||
return x(params);
|
return x(params);
|
||||||
@ -90,9 +86,7 @@ export const and = <T extends ArgName>(
|
|||||||
* Only allow signing when any of the predicates are satisfied.
|
* Only allow signing when any of the predicates are satisfied.
|
||||||
* Useful for predicates reuse
|
* Useful for predicates reuse
|
||||||
*/
|
*/
|
||||||
export const or = <T extends ArgName>(
|
export const or = (...predicates: SecurityGuard[]): SecurityGuard => {
|
||||||
...predicates: SecurityGuard<T>[]
|
|
||||||
): SecurityGuard<T> => {
|
|
||||||
return (params) => {
|
return (params) => {
|
||||||
return predicates.some((x) => {
|
return predicates.some((x) => {
|
||||||
return x(params);
|
return x(params);
|
||||||
|
@ -20,23 +20,24 @@ import { Path, Aqua } from "@fluencelabs/aqua-api/aqua-api.js";
|
|||||||
import {
|
import {
|
||||||
FunctionCallDef,
|
FunctionCallDef,
|
||||||
JSONArray,
|
JSONArray,
|
||||||
PassedArgs,
|
JSONValue,
|
||||||
ServiceDef,
|
ServiceDef,
|
||||||
} from "@fluencelabs/interfaces";
|
} from "@fluencelabs/interfaces";
|
||||||
|
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
|
||||||
|
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
|
||||||
import { Subject, Subscribable } from "rxjs";
|
import { Subject, Subscribable } from "rxjs";
|
||||||
|
|
||||||
import { ClientPeer, makeClientPeerConfig } from "../clientPeer/ClientPeer.js";
|
import { ClientPeer, makeClientPeerConfig } from "../clientPeer/ClientPeer.js";
|
||||||
import { ClientConfig, RelayOptions } from "../clientPeer/types.js";
|
import { ClientConfig, RelayOptions } from "../clientPeer/types.js";
|
||||||
import { callAquaFunction } from "../compilerSupport/callFunction.js";
|
import { callAquaFunction } from "../compilerSupport/callFunction.js";
|
||||||
|
import { ServiceImpl } from "../compilerSupport/types.js";
|
||||||
import { IConnection } from "../connection/interfaces.js";
|
import { IConnection } from "../connection/interfaces.js";
|
||||||
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
import { CallServiceResultType } from "../jsServiceHost/interfaces.js";
|
import { CallServiceResultType } from "../jsServiceHost/interfaces.js";
|
||||||
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
||||||
import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.js";
|
import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.js";
|
||||||
import { KeyPair } from "../keypair/index.js";
|
import { KeyPair } from "../keypair/index.js";
|
||||||
import { WasmLoaderFromNpm } from "../marine/deps-loader/node.js";
|
|
||||||
import { MarineBackgroundRunner } from "../marine/worker/index.js";
|
import { MarineBackgroundRunner } from "../marine/worker/index.js";
|
||||||
import { WorkerLoader } from "../marine/worker-script/workerLoader.js";
|
|
||||||
import { Particle } from "../particle/Particle.js";
|
import { Particle } from "../particle/Particle.js";
|
||||||
|
|
||||||
export const registerHandlersHelper = (
|
export const registerHandlersHelper = (
|
||||||
@ -73,6 +74,16 @@ interface FunctionInfo {
|
|||||||
funcDef: FunctionCallDef;
|
funcDef: FunctionCallDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for callback passed as aqua function argument
|
||||||
|
*/
|
||||||
|
export type ArgCallbackFunction = ServiceImpl[string];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments passed to Aqua function
|
||||||
|
*/
|
||||||
|
export type PassedArgs = { [key: string]: JSONValue | ArgCallbackFunction };
|
||||||
|
|
||||||
export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
|
export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
|
||||||
await fs.access(aquaFile);
|
await fs.access(aquaFile);
|
||||||
|
|
||||||
@ -92,7 +103,6 @@ export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
|
|||||||
.map(([name, fnInfo]: [string, FunctionInfo]) => {
|
.map(([name, fnInfo]: [string, FunctionInfo]) => {
|
||||||
const callFn = (peer: FluencePeer, args: PassedArgs) => {
|
const callFn = (peer: FluencePeer, args: PassedArgs) => {
|
||||||
return callAquaFunction({
|
return callAquaFunction({
|
||||||
def: fnInfo.funcDef,
|
|
||||||
script: fnInfo.script,
|
script: fnInfo.script,
|
||||||
config: {},
|
config: {},
|
||||||
peer: peer,
|
peer: peer,
|
||||||
@ -136,25 +146,60 @@ class NoopConnection implements IConnection {
|
|||||||
|
|
||||||
export class TestPeer extends FluencePeer {
|
export class TestPeer extends FluencePeer {
|
||||||
constructor(keyPair: KeyPair, connection: IConnection) {
|
constructor(keyPair: KeyPair, connection: IConnection) {
|
||||||
const workerLoader = new WorkerLoader();
|
const jsHost = new JsServiceHost();
|
||||||
|
|
||||||
const controlModuleLoader = new WasmLoaderFromNpm(
|
let marineJsWasm: ArrayBuffer;
|
||||||
"@fluencelabs/marine-js",
|
let avmWasm: ArrayBuffer;
|
||||||
"marine-js.wasm",
|
|
||||||
);
|
|
||||||
|
|
||||||
const avmModuleLoader = new WasmLoaderFromNpm(
|
|
||||||
"@fluencelabs/avm",
|
|
||||||
"avm.wasm",
|
|
||||||
);
|
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
const marine = new MarineBackgroundRunner(
|
||||||
workerLoader,
|
{
|
||||||
controlModuleLoader,
|
async getValue() {
|
||||||
avmModuleLoader,
|
// TODO: load worker with avm and marine, test that it works
|
||||||
|
return getWorker("@fluencelabs/marine-worker", "/");
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getValue() {
|
||||||
|
return marineJsWasm;
|
||||||
|
},
|
||||||
|
async start(): Promise<void> {
|
||||||
|
marineJsWasm = await fetchResource(
|
||||||
|
"@fluencelabs/marine-js",
|
||||||
|
"/dist/marine-js.wasm",
|
||||||
|
"/",
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getValue() {
|
||||||
|
return avmWasm;
|
||||||
|
},
|
||||||
|
async start(): Promise<void> {
|
||||||
|
avmWasm = await fetchResource(
|
||||||
|
"@fluencelabs/avm",
|
||||||
|
"/dist/avm.wasm",
|
||||||
|
"/",
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const jsHost = new JsServiceHost();
|
|
||||||
super(DEFAULT_CONFIG, keyPair, marine, jsHost, connection);
|
super(DEFAULT_CONFIG, keyPair, marine, jsHost, connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,26 +226,63 @@ export const withClient = async (
|
|||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
action: (client: ClientPeer) => Promise<void>,
|
action: (client: ClientPeer) => Promise<void>,
|
||||||
) => {
|
) => {
|
||||||
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 { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(
|
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(
|
||||||
relay,
|
relay,
|
||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let marineJsWasm: ArrayBuffer;
|
||||||
|
let avmWasm: ArrayBuffer;
|
||||||
|
|
||||||
|
const marine = new MarineBackgroundRunner(
|
||||||
|
{
|
||||||
|
async getValue() {
|
||||||
|
// TODO: load worker with avm and marine, test that it works
|
||||||
|
return getWorker("@fluencelabs/marine-worker", "/");
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getValue() {
|
||||||
|
return marineJsWasm;
|
||||||
|
},
|
||||||
|
async start(): Promise<void> {
|
||||||
|
marineJsWasm = await fetchResource(
|
||||||
|
"@fluencelabs/marine-js",
|
||||||
|
"/dist/marine-js.wasm",
|
||||||
|
"/",
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getValue() {
|
||||||
|
return avmWasm;
|
||||||
|
},
|
||||||
|
async start(): Promise<void> {
|
||||||
|
avmWasm = await fetchResource(
|
||||||
|
"@fluencelabs/avm",
|
||||||
|
"/dist/avm.wasm",
|
||||||
|
"/",
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const client = new ClientPeer(peerConfig, relayConfig, keyPair, marine);
|
const client = new ClientPeer(peerConfig, relayConfig, keyPair, marine);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"vitest": "0.34.6"
|
"vitest": "0.34.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluencelabs/marine-js": "0.7.2",
|
"@fluencelabs/marine-js": "0.8.0",
|
||||||
"observable-fns": "0.6.1",
|
"observable-fns": "0.6.1",
|
||||||
"@fluencelabs/threads": "^2.0.0"
|
"@fluencelabs/threads": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import type {
|
|||||||
} from "@fluencelabs/marine-js/dist/types";
|
} from "@fluencelabs/marine-js/dist/types";
|
||||||
import {
|
import {
|
||||||
defaultCallParameters,
|
defaultCallParameters,
|
||||||
JSONValue,
|
|
||||||
logLevelToEnv,
|
logLevelToEnv,
|
||||||
} from "@fluencelabs/marine-js/dist/types";
|
} from "@fluencelabs/marine-js/dist/types";
|
||||||
import { expose } from "@fluencelabs/threads/worker";
|
import { expose } from "@fluencelabs/threads/worker";
|
||||||
@ -140,9 +139,7 @@ const toExpose = {
|
|||||||
throw new Error(`service with id=${serviceId} not found`);
|
throw new Error(`service with id=${serviceId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make MarineService return JSONValue type
|
return srv.call(functionName, args, callParams);
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
return srv.call(functionName, args, callParams) as JSONValue;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onLogMessage() {
|
onLogMessage() {
|
||||||
|
6765
pnpm-lock.yaml
generated
6765
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"include": ["**/src/**/*"]
|
"include": ["packages"],
|
||||||
|
"exclude": ["node_modules", "dist", "build"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user