feat!: Standalone web JS Client (#243)

- Move marine-related part into FJS repo (fixes DXJ-184)
- Move towards component-oriented architecture (fixes DXJ-183)
- Different JS Client distros for node.js and web (fixes DXJ-185)
- Update libp2p to 0.42.2 (fixes DXJ-26)
- Add JS Client API (fixes DXJ-196, fixes DXJ-177, fixes DXJ-60)
- Add Smoke test for JS Client web (fixes DXJ-253)

---------

Co-authored-by: Anatoly Laskaris <github_me@nahsi.dev>
This commit is contained in:
Pavel
2023-02-13 17:41:35 +03:00
committed by GitHub
parent e02c506d7f
commit 9667c4fec6
212 changed files with 16522 additions and 7886 deletions

22
packages/client/api/.gitignore vendored Normal file
View File

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

View File

@ -0,0 +1,11 @@
# JS Client web
This package is a part of FluenceJS, the official implementation of the Fluence Peer in typescript. See the [FluenceJS repo](https://github.com/fluencelabs/fluence-js) for more info
## Contributing
While the project is still in the early stages of development, you are welcome to track progress and contribute. As the project is undergoing rapid changes, interested contributors should contact the team before embarking on larger pieces of work. All contributors should consult with and agree to our [basic contributing rules](CONTRIBUTING.md).
## License
[Apache 2.0](LICENSE)

View File

@ -0,0 +1,42 @@
{
"_1": "This should actually be named @fluencelabs/js-client.api. Naming it fluence-js is needed for backward compat w/ aqua compiler",
"name": "@fluencelabs/fluence",
"version": "0.60.0",
"description": "JS Client API",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"engines": {
"node": ">=10",
"pnpm": ">=3"
},
"type": "module",
"_2": "dist/internal/ export is needed for backward compat w/ aqua compiler",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./compilerSupport/v5": {
"import": "./dist/compilerSupport/v5.js",
"types": "./dist/compilerSupport/v5.d.ts"
},
"./dist/compilerSupport/v5": {
"import": "./dist/compilerSupport/v5.js",
"types": "./dist/compilerSupport/v5.d.ts"
},
"./dist/internal/compilerSupport/v4": {
"import": "./dist/compilerSupport/v5.js",
"types": "./dist/compilerSupport/v5.d.ts"
}
},
"scripts": {
"build": "tsc"
},
"repository": "https://github.com/fluencelabs/fluence-js",
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"@fluencelabs/interfaces": "0.5.0"
},
"devDependencies": {}
}

View File

@ -0,0 +1,181 @@
/*
* Copyright 2022 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 type { FnConfig, FunctionCallDef, ServiceDef } from '@fluencelabs/interfaces/compilerSupport';
import type { IFluenceClient } from '@fluencelabs/interfaces/fluenceClient';
import { getArgumentTypes } from '@fluencelabs/interfaces/compilerSupport';
import { isFluencePeer } from '@fluencelabs/interfaces/fluenceClient';
import { getDefaultPeer } from '../index.js';
export type { IFluenceClient, CallParams } from '@fluencelabs/interfaces/fluenceClient';
export {
ArrayType,
ArrowType,
ArrowWithCallbacks,
ArrowWithoutCallbacks,
BottomType,
FnConfig,
FunctionCallConstants,
FunctionCallDef,
LabeledProductType,
NilType,
NonArrowType,
OptionType,
ProductType,
ScalarNames,
ScalarType,
ServiceDef,
StructType,
TopType,
UnlabeledProductType,
} from '@fluencelabs/interfaces/compilerSupport';
/**
* Convenience function to support Aqua `func` generation backend
* The compiler only need to generate a call the function and provide the corresponding definitions and the air script
*
* @param rawFnArgs - raw arguments passed by user to the generated function
* @param def - function definition generated by the Aqua compiler
* @param script - air script with function execution logic generated by the Aqua compiler
*/
export const callFunction = async (rawFnArgs: Array<any>, def: FunctionCallDef, script: string): Promise<unknown> => {
const { args, peer, config } = await extractFunctionArgs(rawFnArgs, def);
return peer.compilerSupport.callFunction({
args,
def,
script,
config: config || {},
});
};
/**
* Convenience function to support Aqua `service` generation backend
* The compiler only need to generate a call the function and provide the corresponding definitions and the air script
*
* @param args - raw arguments passed by user to the generated function
* @param def - service definition generated by the Aqua compiler
*/
export const registerService = async (args: any[], def: ServiceDef): Promise<unknown> => {
const { peer, service, serviceId } = await extractServiceArgs(args, def.defaultServiceId);
return peer.compilerSupport.registerService({
def,
service,
serviceId,
});
};
/**
* 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 }
*/
const extractFunctionArgs = async (
args: any[],
def: FunctionCallDef,
): Promise<{
peer: IFluenceClient;
config?: FnConfig;
args: { [key: string]: any };
}> => {
const argumentTypes = getArgumentTypes(def);
const argumentNames = Object.keys(argumentTypes);
const numberOfExpectedArgs = argumentNames.length;
let peer: IFluenceClient;
let structuredArgs: any[];
let config: FnConfig;
if (isFluencePeer(args[0])) {
peer = args[0];
structuredArgs = args.slice(1, numberOfExpectedArgs + 1);
config = args[numberOfExpectedArgs + 1];
} else {
peer = await getDefaultPeer();
structuredArgs = args.slice(0, numberOfExpectedArgs);
config = args[numberOfExpectedArgs];
}
if (structuredArgs.length !== numberOfExpectedArgs) {
throw new Error(`Incorrect number of arguments. Expecting ${numberOfExpectedArgs}`);
}
const argsRes = argumentNames.reduce((acc, name, index) => ({ ...acc, [name]: structuredArgs[index] }), {});
return {
peer: peer,
config: config,
args: argsRes,
};
};
/**
* 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 = async (
args: any[],
defaultServiceId?: string,
): Promise<{ peer: IFluenceClient; serviceId: string; service: any }> => {
let peer: IFluenceClient;
let serviceId: any;
let service: any;
if (isFluencePeer(args[0])) {
peer = args[0];
} else {
peer = await getDefaultPeer();
}
if (typeof args[0] === 'string') {
serviceId = args[0];
} else if (typeof args[1] === 'string') {
serviceId = args[1];
} else {
serviceId = defaultServiceId;
}
// Figuring out which overload is the service.
// If the first argument is not Fluence Peer and it is an object, then it can only be the service def
// If the first argument is peer, we are checking further. The second argument might either be
// an object, that it must be the service object
// or a string, which is the service id. In that case the service is the third argument
if (!isFluencePeer(args[0]) && typeof args[0] === 'object') {
service = args[0];
} else if (typeof args[1] === 'object') {
service = args[1];
} else {
service = args[2];
}
return {
peer: peer,
serviceId: serviceId,
service: service,
};
};

View File

@ -0,0 +1,41 @@
/*
* Copyright 2022 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 { IFluenceClient } from './implementation.js';
export { CallParams as CallParams$$ } from './implementation.js';
export {
ArrayType as ArrayType$$,
ArrowType as ArrowType$$,
ArrowWithCallbacks as ArrowWithCallbacks$$,
ArrowWithoutCallbacks as ArrowWithoutCallbacks$$,
BottomType as BottomType$$,
FnConfig as FnConfig$$,
FunctionCallConstants as FunctionCallConstants$$,
FunctionCallDef as FunctionCallDef$$,
LabeledProductType as LabeledProductType$$,
NilType as NilType$$,
NonArrowType as NonArrowType$$,
OptionType as OptionType$$,
ProductType as ProductType$$,
ScalarNames as ScalarNames$$,
ScalarType as ScalarType$$,
ServiceDef as ServiceDef$$,
StructType as StructType$$,
TopType as TopType$$,
UnlabeledProductType as UnlabeledProductType$$,
callFunction as callFunction$$,
registerService as registerService$$,
} from './implementation.js';

View File

@ -0,0 +1,69 @@
import type { IFluenceClient, ClientOptions } from '@fluencelabs/interfaces/fluenceClient';
export { IFluenceClient, ClientOptions, CallParams } from '@fluencelabs/interfaces/fluenceClient';
// TODO: hack needed to kinda have backward compat with compiler api
export type FluencePeer = IFluenceClient;
const getPeerFromGlobalThis = (): IFluenceClient | undefined => {
// @ts-ignore
return globalThis.defaultPeer;
};
// TODO: DXJ-271
const REJECT_MESSAGE = 'You probably forgot to add script tag. Read about it here: ';
/**
* Wait until the js client script it loaded and return the default peer from globalThis
*/
export const getDefaultPeer = (): Promise<IFluenceClient> => {
return new Promise((resolve, reject) => {
let interval: NodeJS.Timer | undefined;
let hits = 50;
interval = setInterval(() => {
if (hits === 0) {
clearInterval(interval);
reject(REJECT_MESSAGE);
}
let res = getPeerFromGlobalThis();
if (res) {
clearInterval(interval);
resolve(res);
}
hits--;
}, 100);
});
};
/**
* Public interface to Fluence JS
*/
export const Fluence = {
/**
* Initializes the default peer: starts the Aqua VM, initializes the default call service handlers
* and (optionally) connect to the Fluence network
* @param options - object specifying peer configuration
*/
start: async (options?: ClientOptions): Promise<void> => {
const peer = await getDefaultPeer();
return peer.start(options);
},
/**
* Un-initializes the default peer: stops all the underlying workflows, stops the Aqua VM
* and disconnects from the Fluence network
*/
stop: async (): Promise<void> => {
const peer = await getDefaultPeer();
return peer.stop();
},
/**
* Get the default peer instance
* @returns the default peer instance
*/
getPeer: async (): Promise<IFluenceClient> => {
return getDefaultPeer();
},
};

View File

@ -0,0 +1,7 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"exclude": ["node_modules", "dist"]
}