diff --git a/README.md b/README.md index a18e74ca..a63929b0 100644 --- a/README.md +++ b/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 messages logged by debug if the "Verbose" log level is enabled. +## Low level usage + +JS client also has an API for low level interaction with AVM and Marine JS. +It could be handy in advanced scenarios when a user fetches AIR dynamically or generates AIR without default Aqua compiler. + +`callAquaFunction` Allows to call aqua function without schema. + +`registerService` Gives an ability to register service without schema. Passed `service` could be + +- Plain object. In this case all function properties will be registered as AIR service functions. +- Class instance. All class methods without inherited ones will be registered as AIR service functions. + ## Development To hack on the Fluence JS Client itself, please refer to the [development page](./DEVELOPING.md). diff --git a/packages/core/js-client/aqua/calc.aqua b/packages/core/js-client/aqua/calc.aqua new file mode 100644 index 00000000..b90b5f12 --- /dev/null +++ b/packages/core/js-client/aqua/calc.aqua @@ -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 \ No newline at end of file diff --git a/packages/core/js-client/src/api.spec.ts b/packages/core/js-client/src/api.spec.ts new file mode 100644 index 00000000..6abae83b --- /dev/null +++ b/packages/core/js-client/src/api.spec.ts @@ -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 = new Calc(); + + const { functions, services } = await compileFromPath({ + filePath: fileURLToPath(new URL("../aqua/calc.aqua", import.meta.url)), + }); + + const typedServices: Record = 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); + }); + }); +}); diff --git a/packages/core/js-client/src/api.ts b/packages/core/js-client/src/api.ts index 5648f7c7..87ca6a27 100644 --- a/packages/core/js-client/src/api.ts +++ b/packages/core/js-client/src/api.ts @@ -202,10 +202,16 @@ export const v5_registerService = ( // 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 + // Wrapping service functions, selecting only those listed in schema, to convert their args js -> aqua and backwards const wrappedServiceImpl = Object.fromEntries( - Object.entries(serviceImpl).map(([name, func]) => { - return [name, wrapJsFunction(func, serviceSchema[name])]; + Object.keys(serviceSchema).map((schemaKey) => { + return [ + schemaKey, + wrapJsFunction( + serviceImpl[schemaKey].bind(serviceImpl), + serviceSchema[schemaKey], + ), + ] as const; }), ); diff --git a/packages/core/js-client/src/compilerSupport/callFunction.ts b/packages/core/js-client/src/compilerSupport/callFunction.ts index f53d1807..f07cc107 100644 --- a/packages/core/js-client/src/compilerSupport/callFunction.ts +++ b/packages/core/js-client/src/compilerSupport/callFunction.ts @@ -46,7 +46,7 @@ const log = logger("aqua"); export type CallAquaFunctionArgs = { script: string; - config: CallAquaFunctionConfig | undefined; + config?: CallAquaFunctionConfig | undefined; peer: FluencePeer; args: { [key: string]: JSONValue | ArgCallbackFunction }; }; diff --git a/packages/core/js-client/src/compilerSupport/registerService.ts b/packages/core/js-client/src/compilerSupport/registerService.ts index 28b547fe..1471142f 100644 --- a/packages/core/js-client/src/compilerSupport/registerService.ts +++ b/packages/core/js-client/src/compilerSupport/registerService.ts @@ -28,27 +28,22 @@ interface RegisterServiceArgs { service: ServiceImpl; } +// This function iterates on plain object or class instance functions ignoring inherited functions and prototype chain. const findAllPossibleRegisteredServiceFunctions = ( service: ServiceImpl, -): Set => { - let prototype: Record = service; - const serviceMethods = new Set(); +): Array => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const prototype = Object.getPrototypeOf(service) as ServiceImpl; - do { - Object.getOwnPropertyNames(prototype) - .filter((prop) => { - return typeof prototype[prop] === "function" && prop !== "constructor"; - }) - .forEach((prop) => { - return serviceMethods.add(prop); - }); + const isClassInstance = prototype.constructor !== Object; - // 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; - } while (prototype.constructor !== Object); + if (isClassInstance) { + service = prototype; + } - return serviceMethods; + return Object.getOwnPropertyNames(service).filter((prop) => { + return typeof service[prop] === "function" && prop !== "constructor"; + }); }; export const registerService = ({ diff --git a/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts b/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts index f813dca9..c706eae4 100644 --- a/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts +++ b/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts @@ -14,13 +14,15 @@ * limitations under the License. */ -import { it, describe, expect, beforeEach, afterEach } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { DEFAULT_CONFIG, FluencePeer } from "../../jsPeer/FluencePeer.js"; import { ResultCodes } from "../../jsServiceHost/interfaces.js"; import { KeyPair } from "../../keypair/index.js"; +import { loadMarineDeps } from "../../marine/loader.js"; +import { MarineBackgroundRunner } from "../../marine/worker/index.js"; import { EphemeralNetworkClient } from "../client.js"; -import { EphemeralNetwork, defaultConfig } from "../network.js"; +import { defaultConfig, EphemeralNetwork } from "../network.js"; let en: EphemeralNetwork; let client: FluencePeer; @@ -33,7 +35,11 @@ describe.skip("Ephemeral networks tests", () => { await en.up(); const kp = await KeyPair.randomEd25519(); - client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, en, relay); + + const marineDeps = await loadMarineDeps("/"); + const marine = new MarineBackgroundRunner(...marineDeps); + + client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, marine, en, relay); await client.start(); }); diff --git a/packages/core/js-client/src/ephemeral/client.ts b/packages/core/js-client/src/ephemeral/client.ts index bac36dd7..fe41d193 100644 --- a/packages/core/js-client/src/ephemeral/client.ts +++ b/packages/core/js-client/src/ephemeral/client.ts @@ -15,13 +15,11 @@ */ 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 { JsServiceHost } from "../jsServiceHost/JsServiceHost.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"; @@ -32,63 +30,12 @@ export class EphemeralNetworkClient extends FluencePeer { constructor( config: PeerConfig, keyPair: KeyPair, + marine: IMarineHost, network: EphemeralNetwork, relay: PeerIdB58, ) { 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 { - marineJsWasm = await fetchResource( - "@fluencelabs/marine-js", - "/dist/marine-js.wasm", - "/", - ).then((res) => { - return res.arrayBuffer(); - }); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - { - getValue() { - return avmWasm; - }, - async start(): Promise { - avmWasm = await fetchResource( - "@fluencelabs/avm", - "/dist/avm.wasm", - "/", - ).then((res) => { - return res.arrayBuffer(); - }); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - ); - super(config, keyPair, marine, new JsServiceHost(), conn); } } diff --git a/packages/core/js-client/src/ephemeral/network.ts b/packages/core/js-client/src/ephemeral/network.ts index f4b42122..1958e96e 100644 --- a/packages/core/js-client/src/ephemeral/network.ts +++ b/packages/core/js-client/src/ephemeral/network.ts @@ -15,8 +15,6 @@ */ 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 { IConnection } from "../connection/interfaces.js"; @@ -24,6 +22,7 @@ import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js"; import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js"; import { fromBase64Sk, 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 { Particle } from "../particle/Particle.js"; import { logger } from "../util/logger.js"; @@ -233,55 +232,8 @@ export class EphemeralNetwork { const promises = this.config.peers.map(async (x) => { 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( - { - 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 { - return Promise.resolve(undefined); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - { - getValue() { - return avmWasm; - }, - start(): Promise { - return Promise.resolve(undefined); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - ); + const marineDeps = await loadMarineDeps("/"); + const marine = new MarineBackgroundRunner(...marineDeps); const peerId = kp.getPeerId(); diff --git a/packages/core/js-client/src/index.ts b/packages/core/js-client/src/index.ts index ef5d8a91..3584c849 100644 --- a/packages/core/js-client/src/index.ts +++ b/packages/core/js-client/src/index.ts @@ -14,8 +14,6 @@ * 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 { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js"; @@ -28,6 +26,7 @@ import { } from "./clientPeer/types.js"; import { callAquaFunction } from "./compilerSupport/callFunction.js"; import { registerService } from "./compilerSupport/registerService.js"; +import { loadMarineDeps } from "./marine/loader.js"; import { MarineBackgroundRunner } from "./marine/worker/index.js"; const DEFAULT_CDN_URL = "https://unpkg.com"; @@ -47,55 +46,8 @@ const createClient = async ( const CDNUrl = config.CDNUrl ?? DEFAULT_CDN_URL; - 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(); - }), - ]); - - 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 { - return Promise.resolve(undefined); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - { - getValue() { - return avmWasm; - }, - start(): Promise { - return Promise.resolve(undefined); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - ); + const marineDeps = await loadMarineDeps(CDNUrl); + const marine = new MarineBackgroundRunner(...marineDeps); const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig( relay, diff --git a/packages/core/js-client/src/marine/interfaces.ts b/packages/core/js-client/src/marine/interfaces.ts index 32474375..996479d4 100644 --- a/packages/core/js-client/src/marine/interfaces.ts +++ b/packages/core/js-client/src/marine/interfaces.ts @@ -16,7 +16,6 @@ import { JSONObject, JSONValue, JSONArray } from "@fluencelabs/interfaces"; import { CallParameters } from "@fluencelabs/marine-worker"; -import type { Worker as WorkerImplementation } from "@fluencelabs/threads/master"; import { IStartable } from "../util/commonTypes.js"; @@ -52,24 +51,3 @@ export interface IMarineHost extends IStartable { callParams?: CallParameters, ): Promise; } - -/** - * Interface for something which can hold a value - */ -export interface IValueLoader { - getValue(): T; -} - -/** - * Interface for something which can load wasm files - */ -export interface IWasmLoader - extends IValueLoader, - IStartable {} - -/** - * Interface for something which can thread.js based worker - */ -export interface IWorkerLoader - extends IValueLoader>, - IStartable {} diff --git a/packages/core/js-client/src/marine/loader.ts b/packages/core/js-client/src/marine/loader.ts new file mode 100644 index 00000000..1c64042f --- /dev/null +++ b/packages/core/js-client/src/marine/loader.ts @@ -0,0 +1,47 @@ +/** + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { 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 => { + 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]; +}; diff --git a/packages/core/js-client/src/marine/worker/index.ts b/packages/core/js-client/src/marine/worker/index.ts index 96aee666..17126c3d 100644 --- a/packages/core/js-client/src/marine/worker/index.ts +++ b/packages/core/js-client/src/marine/worker/index.ts @@ -21,10 +21,15 @@ import type { JSONValueNonNullable, CallParameters, } 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 { IMarineHost, IWasmLoader, IWorkerLoader } from "../interfaces.js"; +import { IMarineHost } from "../interfaces.js"; export class MarineBackgroundRunner implements IMarineHost { private workerThread?: ModuleThread; @@ -32,9 +37,9 @@ export class MarineBackgroundRunner implements IMarineHost { private loggers = new Map(); constructor( - private workerLoader: IWorkerLoader, - private controlModuleLoader: IWasmLoader, - private avmWasmLoader: IWasmLoader, + private marineJsWasm: ArrayBuffer, + private avmWasm: ArrayBuffer, + private worker: Worker, ) {} async hasService(serviceId: string) { @@ -58,16 +63,8 @@ export class MarineBackgroundRunner implements IMarineHost { 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 = - await spawn(worker); + await spawn(this.worker); const logfn: LogFunction = (message) => { const serviceLogger = this.loggers.get(message.service); @@ -80,9 +77,9 @@ export class MarineBackgroundRunner implements IMarineHost { }; workerThread.onLogMessage().subscribe(logfn); - await workerThread.init(wasm); + await workerThread.init(this.marineJsWasm); this.workerThread = workerThread; - await this.createService(this.avmWasmLoader.getValue(), "avm"); + await this.createService(this.avmWasm, "avm"); } async createService( diff --git a/packages/core/js-client/src/util/testUtils.ts b/packages/core/js-client/src/util/testUtils.ts index e2f983c4..6e502abb 100644 --- a/packages/core/js-client/src/util/testUtils.ts +++ b/packages/core/js-client/src/util/testUtils.ts @@ -23,8 +23,6 @@ import { JSONValue, ServiceDef, } 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 { ClientPeer, makeClientPeerConfig } from "../clientPeer/ClientPeer.js"; @@ -40,6 +38,8 @@ import { import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js"; import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.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 { Particle } from "../particle/Particle.js"; @@ -146,61 +146,9 @@ class NoopConnection implements IConnection { } export class TestPeer extends FluencePeer { - constructor(keyPair: KeyPair, connection: IConnection) { + constructor(keyPair: KeyPair, connection: IConnection, marine: IMarineHost) { 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 { - marineJsWasm = await fetchResource( - "@fluencelabs/marine-js", - "/dist/marine-js.wasm", - "/", - ).then((res) => { - return res.arrayBuffer(); - }); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - { - getValue() { - return avmWasm; - }, - async start(): Promise { - avmWasm = await fetchResource( - "@fluencelabs/avm", - "/dist/avm.wasm", - "/", - ).then((res) => { - return res.arrayBuffer(); - }); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - ); - super(DEFAULT_CONFIG, keyPair, marine, jsHost, connection); } } @@ -208,7 +156,11 @@ export class TestPeer extends FluencePeer { export const mkTestPeer = async () => { const kp = await KeyPair.randomEd25519(); 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) => { @@ -232,57 +184,8 @@ export const withClient = async ( config, ); - 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 { - marineJsWasm = await fetchResource( - "@fluencelabs/marine-js", - "/dist/marine-js.wasm", - "/", - ).then((res) => { - return res.arrayBuffer(); - }); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - { - getValue() { - return avmWasm; - }, - async start(): Promise { - avmWasm = await fetchResource( - "@fluencelabs/avm", - "/dist/avm.wasm", - "/", - ).then((res) => { - return res.arrayBuffer(); - }); - }, - stop(): Promise { - return Promise.resolve(undefined); - }, - }, - ); + const marineDeps = await loadMarineDeps("/"); + const marine = new MarineBackgroundRunner(...marineDeps); const client = new ClientPeer(peerConfig, relayConfig, keyPair, marine);