mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-05-30 09:21:18 +00:00
feat: Support instance context [fixes DXJ-541] (#392)
* Support context * Update doc * Refactor * Cover by doc * Fix lint
This commit is contained in:
parent
44eb1493b3
commit
1578b791ac
12
README.md
12
README.md
@ -164,6 +164,18 @@ localStorage.debug = "fluence:*";
|
|||||||
In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will be default—only to show
|
In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will be default—only to show
|
||||||
messages logged by debug if the "Verbose" log level is enabled.
|
messages logged by debug if the "Verbose" log level is enabled.
|
||||||
|
|
||||||
|
## Low level usage
|
||||||
|
|
||||||
|
JS client also has an API for low level interaction with AVM and Marine JS.
|
||||||
|
It could be handy in advanced scenarios when a user fetches AIR dynamically or generates AIR without default Aqua compiler.
|
||||||
|
|
||||||
|
`callAquaFunction` Allows to call aqua function without schema.
|
||||||
|
|
||||||
|
`registerService` Gives an ability to register service without schema. Passed `service` could be
|
||||||
|
|
||||||
|
- Plain object. In this case all function properties will be registered as AIR service functions.
|
||||||
|
- Class instance. All class methods without inherited ones will be registered as AIR service functions.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
To hack on the Fluence JS Client itself, please refer to the [development page](./DEVELOPING.md).
|
To hack on the Fluence JS Client itself, please refer to the [development page](./DEVELOPING.md).
|
||||||
|
16
packages/core/js-client/aqua/calc.aqua
Normal file
16
packages/core/js-client/aqua/calc.aqua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
service Calc("calc"):
|
||||||
|
add(n: f32)
|
||||||
|
subtract(n: f32)
|
||||||
|
multiply(n: f32)
|
||||||
|
divide(n: f32)
|
||||||
|
reset()
|
||||||
|
getResult() -> f32
|
||||||
|
|
||||||
|
|
||||||
|
func demoCalc() -> f32:
|
||||||
|
Calc.add(10)
|
||||||
|
Calc.multiply(5)
|
||||||
|
Calc.subtract(8)
|
||||||
|
Calc.divide(6)
|
||||||
|
res <- Calc.getResult()
|
||||||
|
<- res
|
84
packages/core/js-client/src/api.spec.ts
Normal file
84
packages/core/js-client/src/api.spec.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 Fluence Labs Limited
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
import { compileFromPath } from "@fluencelabs/aqua-api";
|
||||||
|
import { ServiceDef } from "@fluencelabs/interfaces";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { v5_registerService } from "./api.js";
|
||||||
|
import { callAquaFunction } from "./compilerSupport/callFunction.js";
|
||||||
|
import { withPeer } from "./util/testUtils.js";
|
||||||
|
|
||||||
|
class CalcParent {
|
||||||
|
protected _state: number = 0;
|
||||||
|
|
||||||
|
add(n: number) {
|
||||||
|
this._state += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
subtract(n: number) {
|
||||||
|
this._state -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Calc extends CalcParent {
|
||||||
|
multiply(n: number) {
|
||||||
|
this._state *= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
divide(n: number) {
|
||||||
|
this._state /= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this._state = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getResult() {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("User API methods", () => {
|
||||||
|
it("registers user class service and calls own and inherited methods correctly", async () => {
|
||||||
|
await withPeer(async (peer) => {
|
||||||
|
const calcService: Record<never, unknown> = new Calc();
|
||||||
|
|
||||||
|
const { functions, services } = await compileFromPath({
|
||||||
|
filePath: fileURLToPath(new URL("../aqua/calc.aqua", import.meta.url)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const typedServices: Record<string, ServiceDef> = services;
|
||||||
|
|
||||||
|
const { script } = functions["demoCalc"];
|
||||||
|
|
||||||
|
v5_registerService([peer, "calc", calcService], {
|
||||||
|
defaultServiceId: "calc",
|
||||||
|
functions: typedServices["Calc"].functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await callAquaFunction({
|
||||||
|
args: {},
|
||||||
|
peer,
|
||||||
|
script,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBe(7);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -202,10 +202,16 @@ export const v5_registerService = (
|
|||||||
// Schema for every function in service
|
// Schema for every function in service
|
||||||
const serviceSchema = def.functions.tag === "nil" ? {} : def.functions.fields;
|
const serviceSchema = def.functions.tag === "nil" ? {} : def.functions.fields;
|
||||||
|
|
||||||
// Wrapping service impl to convert their args ts -> aqua and backwards
|
// Wrapping service functions, selecting only those listed in schema, to convert their args js -> aqua and backwards
|
||||||
const wrappedServiceImpl = Object.fromEntries(
|
const wrappedServiceImpl = Object.fromEntries(
|
||||||
Object.entries(serviceImpl).map(([name, func]) => {
|
Object.keys(serviceSchema).map((schemaKey) => {
|
||||||
return [name, wrapJsFunction(func, serviceSchema[name])];
|
return [
|
||||||
|
schemaKey,
|
||||||
|
wrapJsFunction(
|
||||||
|
serviceImpl[schemaKey].bind(serviceImpl),
|
||||||
|
serviceSchema[schemaKey],
|
||||||
|
),
|
||||||
|
] as const;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ const log = logger("aqua");
|
|||||||
|
|
||||||
export type CallAquaFunctionArgs = {
|
export type CallAquaFunctionArgs = {
|
||||||
script: string;
|
script: string;
|
||||||
config: CallAquaFunctionConfig | undefined;
|
config?: CallAquaFunctionConfig | undefined;
|
||||||
peer: FluencePeer;
|
peer: FluencePeer;
|
||||||
args: { [key: string]: JSONValue | ArgCallbackFunction };
|
args: { [key: string]: JSONValue | ArgCallbackFunction };
|
||||||
};
|
};
|
||||||
|
@ -28,27 +28,22 @@ interface RegisterServiceArgs {
|
|||||||
service: ServiceImpl;
|
service: ServiceImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function iterates on plain object or class instance functions ignoring inherited functions and prototype chain.
|
||||||
const findAllPossibleRegisteredServiceFunctions = (
|
const findAllPossibleRegisteredServiceFunctions = (
|
||||||
service: ServiceImpl,
|
service: ServiceImpl,
|
||||||
): Set<string> => {
|
): Array<string> => {
|
||||||
let prototype: Record<string, unknown> = service;
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
const serviceMethods = new Set<string>();
|
const prototype = Object.getPrototypeOf(service) as ServiceImpl;
|
||||||
|
|
||||||
do {
|
const isClassInstance = prototype.constructor !== Object;
|
||||||
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
|
if (isClassInstance) {
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
service = prototype;
|
||||||
prototype = Object.getPrototypeOf(prototype) as Record<string, unknown>;
|
}
|
||||||
} while (prototype.constructor !== Object);
|
|
||||||
|
|
||||||
return serviceMethods;
|
return Object.getOwnPropertyNames(service).filter((prop) => {
|
||||||
|
return typeof service[prop] === "function" && prop !== "constructor";
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerService = ({
|
export const registerService = ({
|
||||||
|
@ -14,13 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { it, describe, expect, beforeEach, afterEach } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { DEFAULT_CONFIG, FluencePeer } from "../../jsPeer/FluencePeer.js";
|
import { DEFAULT_CONFIG, FluencePeer } from "../../jsPeer/FluencePeer.js";
|
||||||
import { ResultCodes } from "../../jsServiceHost/interfaces.js";
|
import { ResultCodes } from "../../jsServiceHost/interfaces.js";
|
||||||
import { KeyPair } from "../../keypair/index.js";
|
import { KeyPair } from "../../keypair/index.js";
|
||||||
|
import { loadMarineDeps } from "../../marine/loader.js";
|
||||||
|
import { MarineBackgroundRunner } from "../../marine/worker/index.js";
|
||||||
import { EphemeralNetworkClient } from "../client.js";
|
import { EphemeralNetworkClient } from "../client.js";
|
||||||
import { EphemeralNetwork, defaultConfig } from "../network.js";
|
import { defaultConfig, EphemeralNetwork } from "../network.js";
|
||||||
|
|
||||||
let en: EphemeralNetwork;
|
let en: EphemeralNetwork;
|
||||||
let client: FluencePeer;
|
let client: FluencePeer;
|
||||||
@ -33,7 +35,11 @@ describe.skip("Ephemeral networks tests", () => {
|
|||||||
await en.up();
|
await en.up();
|
||||||
|
|
||||||
const kp = await KeyPair.randomEd25519();
|
const kp = await KeyPair.randomEd25519();
|
||||||
client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, en, relay);
|
|
||||||
|
const marineDeps = await loadMarineDeps("/");
|
||||||
|
const marine = new MarineBackgroundRunner(...marineDeps);
|
||||||
|
|
||||||
|
client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, marine, en, relay);
|
||||||
await client.start();
|
await client.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,13 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
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 { MarineBackgroundRunner } from "../marine/worker/index.js";
|
import { IMarineHost } from "../marine/interfaces.js";
|
||||||
|
|
||||||
import { EphemeralNetwork } from "./network.js";
|
import { EphemeralNetwork } from "./network.js";
|
||||||
|
|
||||||
@ -32,63 +30,12 @@ export class EphemeralNetworkClient extends FluencePeer {
|
|||||||
constructor(
|
constructor(
|
||||||
config: PeerConfig,
|
config: PeerConfig,
|
||||||
keyPair: KeyPair,
|
keyPair: KeyPair,
|
||||||
|
marine: IMarineHost,
|
||||||
network: EphemeralNetwork,
|
network: EphemeralNetwork,
|
||||||
relay: PeerIdB58,
|
relay: PeerIdB58,
|
||||||
) {
|
) {
|
||||||
const conn = network.getRelayConnection(keyPair.getPeerId(), relay);
|
const conn = network.getRelayConnection(keyPair.getPeerId(), relay);
|
||||||
|
|
||||||
let marineJsWasm: ArrayBuffer;
|
|
||||||
let avmWasm: ArrayBuffer;
|
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
|
||||||
{
|
|
||||||
async getValue() {
|
|
||||||
// TODO: load worker in parallel 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);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
super(config, keyPair, marine, new JsServiceHost(), conn);
|
super(config, keyPair, marine, new JsServiceHost(), conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
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";
|
||||||
@ -24,6 +22,7 @@ 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 { IMarineHost } from "../marine/interfaces.js";
|
import { IMarineHost } from "../marine/interfaces.js";
|
||||||
|
import { loadMarineDeps } from "../marine/loader.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";
|
||||||
import { logger } from "../util/logger.js";
|
import { logger } from "../util/logger.js";
|
||||||
@ -233,55 +232,8 @@ 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([
|
const marineDeps = await loadMarineDeps("/");
|
||||||
fetchResource(
|
const marine = new MarineBackgroundRunner(...marineDeps);
|
||||||
"@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(
|
|
||||||
{
|
|
||||||
async getValue() {
|
|
||||||
// TODO: load worker in parallel 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();
|
||||||
|
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
|
|
||||||
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
|
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js";
|
import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js";
|
||||||
@ -28,6 +26,7 @@ import {
|
|||||||
} 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";
|
||||||
|
import { loadMarineDeps } from "./marine/loader.js";
|
||||||
import { MarineBackgroundRunner } from "./marine/worker/index.js";
|
import { MarineBackgroundRunner } from "./marine/worker/index.js";
|
||||||
|
|
||||||
const DEFAULT_CDN_URL = "https://unpkg.com";
|
const DEFAULT_CDN_URL = "https://unpkg.com";
|
||||||
@ -47,55 +46,8 @@ const createClient = async (
|
|||||||
|
|
||||||
const CDNUrl = config.CDNUrl ?? DEFAULT_CDN_URL;
|
const CDNUrl = config.CDNUrl ?? DEFAULT_CDN_URL;
|
||||||
|
|
||||||
const [marineJsWasm, avmWasm] = await Promise.all([
|
const marineDeps = await loadMarineDeps(CDNUrl);
|
||||||
fetchResource(
|
const marine = new MarineBackgroundRunner(...marineDeps);
|
||||||
"@fluencelabs/marine-js",
|
|
||||||
"/dist/marine-js.wasm",
|
|
||||||
CDNUrl,
|
|
||||||
).then((res) => {
|
|
||||||
return res.arrayBuffer();
|
|
||||||
}),
|
|
||||||
fetchResource("@fluencelabs/avm", "/dist/avm.wasm", CDNUrl).then((res) => {
|
|
||||||
return res.arrayBuffer();
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
|
||||||
{
|
|
||||||
async getValue() {
|
|
||||||
// TODO: load worker in parallel with avm and marine, test that it works
|
|
||||||
return getWorker("@fluencelabs/marine-worker", CDNUrl);
|
|
||||||
},
|
|
||||||
start() {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
stop() {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getValue() {
|
|
||||||
return marineJsWasm;
|
|
||||||
},
|
|
||||||
start(): Promise<void> {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
stop(): Promise<void> {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getValue() {
|
|
||||||
return avmWasm;
|
|
||||||
},
|
|
||||||
start(): Promise<void> {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
stop(): Promise<void> {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(
|
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(
|
||||||
relay,
|
relay,
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
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 { IStartable } from "../util/commonTypes.js";
|
import { IStartable } from "../util/commonTypes.js";
|
||||||
|
|
||||||
@ -52,24 +51,3 @@ export interface IMarineHost extends IStartable {
|
|||||||
callParams?: CallParameters,
|
callParams?: CallParameters,
|
||||||
): Promise<JSONValue>;
|
): Promise<JSONValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for something which can hold a value
|
|
||||||
*/
|
|
||||||
export interface IValueLoader<T> {
|
|
||||||
getValue(): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for something which can load wasm files
|
|
||||||
*/
|
|
||||||
export interface IWasmLoader
|
|
||||||
extends IValueLoader<ArrayBuffer | SharedArrayBuffer>,
|
|
||||||
IStartable {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for something which can thread.js based worker
|
|
||||||
*/
|
|
||||||
export interface IWorkerLoader
|
|
||||||
extends IValueLoader<WorkerImplementation | Promise<WorkerImplementation>>,
|
|
||||||
IStartable {}
|
|
||||||
|
47
packages/core/js-client/src/marine/loader.ts
Normal file
47
packages/core/js-client/src/marine/loader.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 Fluence Labs Limited
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
|
||||||
|
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
|
||||||
|
import { Worker } from "@fluencelabs/threads/master";
|
||||||
|
|
||||||
|
type StrategyReturnType = [
|
||||||
|
marineJsWasm: ArrayBuffer,
|
||||||
|
avmWasm: ArrayBuffer,
|
||||||
|
worker: Worker,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const loadMarineDeps = async (
|
||||||
|
CDNUrl: string,
|
||||||
|
): Promise<StrategyReturnType> => {
|
||||||
|
const [marineJsWasm, avmWasm] = await Promise.all([
|
||||||
|
fetchResource(
|
||||||
|
"@fluencelabs/marine-js",
|
||||||
|
"/dist/marine-js.wasm",
|
||||||
|
CDNUrl,
|
||||||
|
).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
}),
|
||||||
|
fetchResource("@fluencelabs/avm", "/dist/avm.wasm", CDNUrl).then((res) => {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// TODO: load worker in parallel with avm and marine, test that it works
|
||||||
|
const worker = await getWorker("@fluencelabs/marine-worker", CDNUrl);
|
||||||
|
|
||||||
|
return [marineJsWasm, avmWasm, worker];
|
||||||
|
};
|
@ -21,10 +21,15 @@ import type {
|
|||||||
JSONValueNonNullable,
|
JSONValueNonNullable,
|
||||||
CallParameters,
|
CallParameters,
|
||||||
} from "@fluencelabs/marine-worker";
|
} from "@fluencelabs/marine-worker";
|
||||||
import { ModuleThread, Thread, spawn } from "@fluencelabs/threads/master";
|
import {
|
||||||
|
ModuleThread,
|
||||||
|
Thread,
|
||||||
|
spawn,
|
||||||
|
Worker,
|
||||||
|
} from "@fluencelabs/threads/master";
|
||||||
|
|
||||||
import { MarineLogger, marineLogger } from "../../util/logger.js";
|
import { MarineLogger, marineLogger } from "../../util/logger.js";
|
||||||
import { IMarineHost, IWasmLoader, IWorkerLoader } from "../interfaces.js";
|
import { IMarineHost } from "../interfaces.js";
|
||||||
|
|
||||||
export class MarineBackgroundRunner implements IMarineHost {
|
export class MarineBackgroundRunner implements IMarineHost {
|
||||||
private workerThread?: ModuleThread<MarineBackgroundInterface>;
|
private workerThread?: ModuleThread<MarineBackgroundInterface>;
|
||||||
@ -32,9 +37,9 @@ export class MarineBackgroundRunner implements IMarineHost {
|
|||||||
private loggers = new Map<string, MarineLogger>();
|
private loggers = new Map<string, MarineLogger>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private workerLoader: IWorkerLoader,
|
private marineJsWasm: ArrayBuffer,
|
||||||
private controlModuleLoader: IWasmLoader,
|
private avmWasm: ArrayBuffer,
|
||||||
private avmWasmLoader: IWasmLoader,
|
private worker: Worker,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async hasService(serviceId: string) {
|
async hasService(serviceId: string) {
|
||||||
@ -58,16 +63,8 @@ export class MarineBackgroundRunner implements IMarineHost {
|
|||||||
throw new Error("Worker thread already initialized");
|
throw new Error("Worker thread already initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.controlModuleLoader.start();
|
|
||||||
const wasm = this.controlModuleLoader.getValue();
|
|
||||||
|
|
||||||
await this.avmWasmLoader.start();
|
|
||||||
|
|
||||||
await this.workerLoader.start();
|
|
||||||
const worker = await this.workerLoader.getValue();
|
|
||||||
|
|
||||||
const workerThread: ModuleThread<MarineBackgroundInterface> =
|
const workerThread: ModuleThread<MarineBackgroundInterface> =
|
||||||
await spawn<MarineBackgroundInterface>(worker);
|
await spawn<MarineBackgroundInterface>(this.worker);
|
||||||
|
|
||||||
const logfn: LogFunction = (message) => {
|
const logfn: LogFunction = (message) => {
|
||||||
const serviceLogger = this.loggers.get(message.service);
|
const serviceLogger = this.loggers.get(message.service);
|
||||||
@ -80,9 +77,9 @@ export class MarineBackgroundRunner implements IMarineHost {
|
|||||||
};
|
};
|
||||||
|
|
||||||
workerThread.onLogMessage().subscribe(logfn);
|
workerThread.onLogMessage().subscribe(logfn);
|
||||||
await workerThread.init(wasm);
|
await workerThread.init(this.marineJsWasm);
|
||||||
this.workerThread = workerThread;
|
this.workerThread = workerThread;
|
||||||
await this.createService(this.avmWasmLoader.getValue(), "avm");
|
await this.createService(this.avmWasm, "avm");
|
||||||
}
|
}
|
||||||
|
|
||||||
async createService(
|
async createService(
|
||||||
|
@ -23,8 +23,6 @@ import {
|
|||||||
JSONValue,
|
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";
|
||||||
@ -40,6 +38,8 @@ import {
|
|||||||
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 { IMarineHost } from "../marine/interfaces.js";
|
||||||
|
import { loadMarineDeps } from "../marine/loader.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";
|
||||||
|
|
||||||
@ -146,61 +146,9 @@ class NoopConnection implements IConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TestPeer extends FluencePeer {
|
export class TestPeer extends FluencePeer {
|
||||||
constructor(keyPair: KeyPair, connection: IConnection) {
|
constructor(keyPair: KeyPair, connection: IConnection, marine: IMarineHost) {
|
||||||
const jsHost = new JsServiceHost();
|
const jsHost = new JsServiceHost();
|
||||||
|
|
||||||
let marineJsWasm: ArrayBuffer;
|
|
||||||
let avmWasm: ArrayBuffer;
|
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
|
||||||
{
|
|
||||||
async getValue() {
|
|
||||||
// TODO: load worker in parallel 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);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
super(DEFAULT_CONFIG, keyPair, marine, jsHost, connection);
|
super(DEFAULT_CONFIG, keyPair, marine, jsHost, connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +156,11 @@ export class TestPeer extends FluencePeer {
|
|||||||
export const mkTestPeer = async () => {
|
export const mkTestPeer = async () => {
|
||||||
const kp = await KeyPair.randomEd25519();
|
const kp = await KeyPair.randomEd25519();
|
||||||
const conn = new NoopConnection();
|
const conn = new NoopConnection();
|
||||||
return new TestPeer(kp, conn);
|
|
||||||
|
const marineDeps = await loadMarineDeps("/");
|
||||||
|
const marine = new MarineBackgroundRunner(...marineDeps);
|
||||||
|
|
||||||
|
return new TestPeer(kp, conn, marine);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const withPeer = async (action: (p: FluencePeer) => Promise<void>) => {
|
export const withPeer = async (action: (p: FluencePeer) => Promise<void>) => {
|
||||||
@ -232,57 +184,8 @@ export const withClient = async (
|
|||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
|
|
||||||
let marineJsWasm: ArrayBuffer;
|
const marineDeps = await loadMarineDeps("/");
|
||||||
let avmWasm: ArrayBuffer;
|
const marine = new MarineBackgroundRunner(...marineDeps);
|
||||||
|
|
||||||
const marine = new MarineBackgroundRunner(
|
|
||||||
{
|
|
||||||
async getValue() {
|
|
||||||
// TODO: load worker in parallel 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);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user