mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-06-25 13:51:34 +00:00
Integrate async AquaVM into fluence-js (#88)
This commit is contained in:
222
src/internal/compilerSupport/LegacyCallServiceHandler.ts
Normal file
222
src/internal/compilerSupport/LegacyCallServiceHandler.ts
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2021 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 { CallServiceData, CallServiceResult, CallServiceResultType, ResultCodes } from '../commonTypes';
|
||||
|
||||
/**
|
||||
* @deprecated This class exists to glue legacy RequestFlowBuilder api with restructured async FluencePeer.
|
||||
* v2 version of compiler support should be used instead
|
||||
*/
|
||||
export const callLegacyCallServiceHandler = (
|
||||
req: CallServiceData,
|
||||
commonHandler: CallServiceHandler,
|
||||
particleSpecificHandler?: CallServiceHandler,
|
||||
): CallServiceResult => {
|
||||
// trying particle-specific handler
|
||||
if (particleSpecificHandler !== undefined) {
|
||||
var res = particleSpecificHandler.execute(req);
|
||||
}
|
||||
|
||||
if (res?.result === undefined) {
|
||||
// if it didn't return any result trying to run the common handler
|
||||
res = commonHandler.execute(req);
|
||||
}
|
||||
|
||||
if (res.retCode === undefined) {
|
||||
res = {
|
||||
retCode: ResultCodes.unknownError,
|
||||
result: `The handler did not set any result. Make sure you are calling the right peer and the handler has been registered. Original request data was: serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
||||
};
|
||||
}
|
||||
|
||||
if (res.result === undefined) {
|
||||
res.result = null;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Type for the middleware used in CallServiceHandler middleware chain.
|
||||
* In a nutshell middleware is a function of request, response and function to trigger the next middleware in chain.
|
||||
* Each middleware is free to write additional properties to either request or response object.
|
||||
* When the chain finishes the response is passed back to AVM
|
||||
* @param { CallServiceData } req - information about the air `call` instruction
|
||||
* @param { CallServiceResult } resp - response to be passed to AVM
|
||||
* @param { Function } next - function which invokes next middleware in chain
|
||||
*/
|
||||
export type Middleware = (req: CallServiceData, resp: CallServiceResult, next: Function) => void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
type CallParams = any;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Convenience middleware factory. Registers a handler for a pair of 'serviceId/fnName'.
|
||||
* The return value of the handler is passed back to AVM
|
||||
* @param { string } serviceId - The identifier of service which would be used to make calls from AVM
|
||||
* @param { string } fnName - The identifier of function which would be used to make calls from AVM
|
||||
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => object } handler - The handler which should handle the call. The result is any object passed back to AVM
|
||||
*/
|
||||
export const fnHandler = (
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: (args: any[], callParams: CallParams) => CallServiceResultType,
|
||||
) => {
|
||||
return (req: CallServiceData, resp: CallServiceResult, next: Function): void => {
|
||||
if (req.fnName === fnName && req.serviceId === serviceId) {
|
||||
const res = handler(req.args, req.particleContext);
|
||||
resp.retCode = ResultCodes.success;
|
||||
resp.result = res;
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Convenience middleware factory. Registers a handler for a pair of 'serviceId/fnName'.
|
||||
* Similar to @see { @link fnHandler } but instead returns and empty object immediately runs the handler asynchronously
|
||||
* @param { string } serviceId - The identifier of service which would be used to make calls from AVM
|
||||
* @param { string } fnName - The identifier of function which would be used to make calls from AVM
|
||||
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => void } handler - The handler which should handle the call.
|
||||
*/
|
||||
export const fnAsEventHandler = (
|
||||
serviceId: string, // force format
|
||||
fnName: string,
|
||||
handler: (args: any[], callParams: CallParams) => void,
|
||||
) => {
|
||||
return (req: CallServiceData, resp: CallServiceResult, next: Function): void => {
|
||||
if (req.fnName === fnName && req.serviceId === serviceId) {
|
||||
setTimeout(() => {
|
||||
handler(req.args, req.particleContext);
|
||||
}, 0);
|
||||
|
||||
resp.retCode = ResultCodes.success;
|
||||
resp.result = {};
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
type CallServiceFunction = (req: CallServiceData, resp: CallServiceResult) => void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Class defines the handling of a `call` air instruction executed by AVM on the local peer.
|
||||
* All the execution process is defined by the chain of middlewares - architecture popular among backend web frameworks.
|
||||
* Each middleware has the form of `(req: Call, resp: CallServiceResult, next: Function) => void;`
|
||||
* A handler starts with an empty middleware chain and does nothing.
|
||||
* To execute the handler use @see { @link execute } function
|
||||
*/
|
||||
export class CallServiceHandler {
|
||||
private middlewares: Middleware[] = [];
|
||||
|
||||
/**
|
||||
* Appends middleware to the chain of middlewares
|
||||
* @param { Middleware } middleware
|
||||
*/
|
||||
use(middleware: Middleware): CallServiceHandler {
|
||||
this.middlewares.push(middleware);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the middleware from the chain of middlewares
|
||||
* @param { Middleware } middleware
|
||||
*/
|
||||
unUse(middleware: Middleware): CallServiceHandler {
|
||||
const index = this.middlewares.indexOf(middleware);
|
||||
if (index !== -1) {
|
||||
this.middlewares.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine handler with another one. Combination is done by copying middleware chain from the argument's handler into current one.
|
||||
* Please note, that current handler's middlewares take precedence over the ones from handler to be combined with
|
||||
* @param { CallServiceHandler } other - CallServiceHandler to be combined with
|
||||
*/
|
||||
combineWith(other: CallServiceHandler): CallServiceHandler {
|
||||
this.middlewares = [...this.middlewares, ...other.middlewares];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for registering @see { @link fnHandler } middleware
|
||||
*/
|
||||
on(
|
||||
serviceId: string, // force format
|
||||
fnName: string,
|
||||
handler: (args: any[], callParams: CallParams) => CallServiceResultType,
|
||||
): Function {
|
||||
const mw = fnHandler(serviceId, fnName, handler);
|
||||
this.use(mw);
|
||||
return () => {
|
||||
this.unUse(mw);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for registering @see { @link fnAsEventHandler } middleware
|
||||
*/
|
||||
onEvent(
|
||||
serviceId: string, // force format
|
||||
fnName: string,
|
||||
handler: (args: any[], callParams: CallParams) => void,
|
||||
): Function {
|
||||
const mw = fnAsEventHandler(serviceId, fnName, handler);
|
||||
this.use(mw);
|
||||
return () => {
|
||||
this.unUse(mw);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses middleware chain into a single function.
|
||||
*/
|
||||
buildFunction(): CallServiceFunction {
|
||||
const result = this.middlewares.reduceRight<CallServiceFunction>(
|
||||
(agg, cur) => {
|
||||
return (req, resp) => {
|
||||
cur(req, resp, () => agg(req, resp));
|
||||
};
|
||||
},
|
||||
(req, res) => {},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the handler with the specified Call request. Return the result response
|
||||
*/
|
||||
execute(req: CallServiceData): CallServiceResult {
|
||||
const res: CallServiceResult = {
|
||||
retCode: undefined,
|
||||
result: undefined,
|
||||
};
|
||||
this.buildFunction()(req, res);
|
||||
return res;
|
||||
}
|
||||
}
|
@ -1,5 +1,87 @@
|
||||
/*
|
||||
* Copyright 2021 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 { CallServiceHandler } from './LegacyCallServiceHandler';
|
||||
import { Particle } from '../Particle';
|
||||
|
||||
export { FluencePeer } from '../FluencePeer';
|
||||
export { ResultCodes } from '../../internal/CallServiceHandler';
|
||||
export { RequestFlow } from '../../internal/RequestFlow';
|
||||
export { RequestFlowBuilder } from '../../internal/RequestFlowBuilder';
|
||||
export { CallParams } from '../commonTypes';
|
||||
export { CallParams, ResultCodes } from '../commonTypes';
|
||||
|
||||
/**
|
||||
* @deprecated This class exists to glue legacy RequestFlowBuilder api with restructured async FluencePeer.
|
||||
* v2 version of compiler support should be used instead
|
||||
*/
|
||||
export interface RequestFlow {
|
||||
particle: Particle;
|
||||
handler: CallServiceHandler;
|
||||
timeout?: () => void;
|
||||
error?: (reason?: any) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This class exists to glue legacy RequestFlowBuilder api with restructured async FluencePeer.
|
||||
* v2 version of compiler support should be used instead
|
||||
*/
|
||||
export class RequestFlowBuilder {
|
||||
private _ttl?: number;
|
||||
private _script?: string;
|
||||
private _configs: any = [];
|
||||
private _error: (reason?: any) => void = () => {};
|
||||
private _timeout: () => void = () => {};
|
||||
|
||||
build(): RequestFlow {
|
||||
let h = new CallServiceHandler();
|
||||
for (let c of this._configs) {
|
||||
c(h);
|
||||
}
|
||||
|
||||
return {
|
||||
particle: Particle.createNew(this._script!, this._ttl),
|
||||
handler: h,
|
||||
timeout: this._timeout,
|
||||
error: this._error,
|
||||
};
|
||||
}
|
||||
|
||||
withTTL(ttl: number): RequestFlowBuilder {
|
||||
this._ttl = ttl;
|
||||
return this;
|
||||
}
|
||||
|
||||
handleTimeout(timeout: () => void): RequestFlowBuilder {
|
||||
this._timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
handleScriptError(reject: (reason?: any) => void): RequestFlowBuilder {
|
||||
this._error = reject;
|
||||
return this;
|
||||
}
|
||||
|
||||
withRawScript(script: string): RequestFlowBuilder {
|
||||
this._script = script;
|
||||
return this;
|
||||
}
|
||||
|
||||
disableInjections(): RequestFlowBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
configHandler(h: (handler: CallServiceHandler) => void) {
|
||||
this._configs.push(h);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
518
src/internal/compilerSupport/v2.ts
Normal file
518
src/internal/compilerSupport/v2.ts
Normal file
@ -0,0 +1,518 @@
|
||||
/*
|
||||
* Copyright 2021 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 { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
import { match } from 'ts-pattern';
|
||||
import { CallParams, Fluence, FluencePeer } from '../../index';
|
||||
import { CallServiceData, GenericCallServiceHandler, CallServiceResult, ResultCodes } from '../commonTypes';
|
||||
import { Particle } from '../Particle';
|
||||
|
||||
export { FluencePeer } from '../FluencePeer';
|
||||
export { CallParams } from '../commonTypes';
|
||||
|
||||
/**
|
||||
* Represents the Aqua Option type
|
||||
*/
|
||||
type OptionalType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'optional';
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the void type for functions and callbacks with no return value
|
||||
*/
|
||||
type VoidType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'void';
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents all types other than Optional, Void, Callback and MultiReturn
|
||||
*/
|
||||
type PrimitiveType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'primitive';
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents callbacks used in Aqua function arguments (`func` instruction)
|
||||
*/
|
||||
type CallbackType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'callback';
|
||||
|
||||
/**
|
||||
* Callback definition
|
||||
*/
|
||||
callback: CallbackDef<OptionalType | PrimitiveType, VoidType | OptionalType | PrimitiveType>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the return type for functions which return multiple values
|
||||
*/
|
||||
type MultiReturnType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'multiReturn';
|
||||
|
||||
/**
|
||||
* The description of types of the return values: Array of either primitive or optional types
|
||||
*/
|
||||
returnItems: Array<OptionalType | PrimitiveType>;
|
||||
};
|
||||
|
||||
interface ArgDef<ArgType> {
|
||||
/**
|
||||
* The name of the argument in Aqua language
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The type of the argument
|
||||
*/
|
||||
argType: ArgType;
|
||||
}
|
||||
|
||||
interface CallbackDef<ArgType, ReturnType> {
|
||||
/**
|
||||
* Callback argument definitions: the list of ArgDefs
|
||||
*/
|
||||
argDefs: Array<ArgDef<ArgType>>;
|
||||
|
||||
/**
|
||||
* Definition of the return type of callback
|
||||
*/
|
||||
returnType: ReturnType;
|
||||
}
|
||||
|
||||
interface FunctionBodyDef
|
||||
extends CallbackDef<
|
||||
// force new line
|
||||
OptionalType | PrimitiveType,
|
||||
VoidType | OptionalType | PrimitiveType
|
||||
> {
|
||||
/**
|
||||
* The name of the function in Aqua language
|
||||
*/
|
||||
functionName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of function (`func` instruction) generated by the Aqua compiler
|
||||
*/
|
||||
interface FunctionCallDef
|
||||
extends CallbackDef<
|
||||
OptionalType | PrimitiveType | CallbackType,
|
||||
VoidType | OptionalType | PrimitiveType | MultiReturnType
|
||||
> {
|
||||
/**
|
||||
* The name of the function in Aqua language
|
||||
*/
|
||||
functionName: string;
|
||||
|
||||
/**
|
||||
* Names of the different entities used in generated air script
|
||||
*/
|
||||
names: {
|
||||
/**
|
||||
* The name of the relay variable
|
||||
*/
|
||||
relay: string;
|
||||
|
||||
/**
|
||||
* The name of the serviceId used load variables at the beginning of the script
|
||||
*/
|
||||
getDataSrv: string;
|
||||
|
||||
/**
|
||||
* The name of serviceId is used to execute callbacks for the current particle
|
||||
*/
|
||||
callbackSrv: string;
|
||||
|
||||
/**
|
||||
* The name of the serviceId which is called to propagate return value to the generated function caller
|
||||
*/
|
||||
responseSrv: string;
|
||||
|
||||
/**
|
||||
* The name of the functionName which is called to propagate return value to the generated function caller
|
||||
*/
|
||||
responseFnName: string;
|
||||
|
||||
/**
|
||||
* The name of the serviceId which is called to report errors to the generated function caller
|
||||
*/
|
||||
errorHandlingSrv: string;
|
||||
|
||||
/**
|
||||
* The name of the functionName which is called to report errors to the generated function caller
|
||||
*/
|
||||
errorFnName: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of service registration function (`service` instruction) generated by the Aqua compiler
|
||||
*/
|
||||
interface ServiceDef {
|
||||
/**
|
||||
* Default service id. If the service has no default id the value should be undefined
|
||||
*/
|
||||
defaultServiceId?: string;
|
||||
|
||||
/**
|
||||
* List of functions which the service consists of
|
||||
*/
|
||||
functions: Array<FunctionBodyDef>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function callFunction(rawFnArgs: Array<any>, def: FunctionCallDef, script: string) {
|
||||
const { args, peer, config } = extractFunctionArgs(rawFnArgs, def.argDefs.length);
|
||||
|
||||
if (args.length !== def.argDefs.length) {
|
||||
throw new Error('Incorrect number of arguments. Expecting ${def.argDefs.length}');
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const particle = Particle.createNew(script, config?.ttl);
|
||||
|
||||
for (let i = 0; i < def.argDefs.length; i++) {
|
||||
const argDef = def.argDefs[i];
|
||||
const arg = args[i];
|
||||
|
||||
const [serviceId, fnName, cb] = match(argDef.argType)
|
||||
// for callback arguments we are registering particle-specific callback which executes the passed function
|
||||
.with({ tag: 'callback' }, (callbackDef) => {
|
||||
const fn = async (req: CallServiceData): Promise<CallServiceResult> => {
|
||||
const args = convertArgsFromReqToUserCall(req, callbackDef.callback.argDefs);
|
||||
// arg is function at this point
|
||||
const result = await arg.apply(null, args);
|
||||
let res;
|
||||
switch (callbackDef.callback.returnType.tag) {
|
||||
case 'void':
|
||||
res = {};
|
||||
break;
|
||||
case 'primitive':
|
||||
res = result;
|
||||
break;
|
||||
case 'optional':
|
||||
res = tsToAquaOpt(result);
|
||||
break;
|
||||
}
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: res,
|
||||
};
|
||||
};
|
||||
return [def.names.callbackSrv, argDef.name, fn] as const;
|
||||
})
|
||||
// for optional types we are converting value to array representation in air
|
||||
.with({ tag: 'optional' }, () => {
|
||||
const fn = (req: CallServiceData): CallServiceResult => {
|
||||
// arg is optional at this point
|
||||
const res = tsToAquaOpt(arg);
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: res,
|
||||
};
|
||||
};
|
||||
return [def.names.getDataSrv, argDef.name, fn] as const;
|
||||
})
|
||||
// for primitive types wre are simply passing the value
|
||||
.with({ tag: 'primitive' }, () => {
|
||||
// arg is primitive at this point
|
||||
const fn = (req: CallServiceData): CallServiceResult => ({
|
||||
retCode: ResultCodes.success,
|
||||
result: arg,
|
||||
});
|
||||
return [def.names.getDataSrv, argDef.name, fn] as const;
|
||||
})
|
||||
.exhaustive();
|
||||
|
||||
// registering handlers for every argument of the function
|
||||
peer.internals.regHandler.forParticle(particle.id, serviceId, fnName, cb);
|
||||
}
|
||||
|
||||
// registering handler for function response
|
||||
peer.internals.regHandler.forParticle(particle.id, def.names.responseSrv, def.names.responseFnName, (req) => {
|
||||
const userFunctionReturn = match(def.returnType)
|
||||
.with({ tag: 'primitive' }, () => req.args[0])
|
||||
.with({ tag: 'optional' }, () => aquaOptToTs(req.args[0]))
|
||||
.with({ tag: 'void' }, () => undefined)
|
||||
.with({ tag: 'multiReturn' }, (mr) => {
|
||||
return mr.returnItems.map((x, index) => {
|
||||
return match(x)
|
||||
.with({ tag: 'optional' }, () => aquaOptToTs(req.args[index]))
|
||||
.with({ tag: 'primitive' }, () => req.args[index])
|
||||
.exhaustive();
|
||||
});
|
||||
})
|
||||
.exhaustive();
|
||||
|
||||
setTimeout(() => {
|
||||
resolve(userFunctionReturn);
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: {},
|
||||
};
|
||||
});
|
||||
|
||||
// registering handler for injecting relay variable
|
||||
peer.internals.regHandler.forParticle(particle.id, def.names.getDataSrv, def.names.relay, (req) => {
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: peer.getStatus().relayPeerId,
|
||||
};
|
||||
});
|
||||
|
||||
// registering handler for error reporting
|
||||
peer.internals.regHandler.forParticle(particle.id, def.names.errorHandlingSrv, def.names.errorFnName, (req) => {
|
||||
const [err, _] = req.args;
|
||||
setTimeout(() => {
|
||||
reject(err);
|
||||
}, 0);
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: {},
|
||||
};
|
||||
});
|
||||
|
||||
// registering handler for particle timeout
|
||||
peer.internals.regHandler.timeout(particle.id, () => {
|
||||
reject(`Request timed out for ${def.functionName}`);
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle);
|
||||
});
|
||||
|
||||
// if the function has void type we should resolve immediately for API symmetry with non-void types
|
||||
// to help with debugging we are returning a promise which can be used to track particle errors
|
||||
// we cannot return a bare promise because JS will lift it, so returning an array with the promise
|
||||
if (def.returnType.tag === 'void') {
|
||||
return Promise.resolve([promise]);
|
||||
} else {
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function registerService(args: any[], def: ServiceDef) {
|
||||
const { peer, service, serviceId } = extractRegisterServiceArgs(args, def.defaultServiceId);
|
||||
|
||||
// Checking for missing keys
|
||||
const requiredKeys = def.functions.map((x) => x.functionName);
|
||||
const incorrectServiceDefinitions = Object.keys(service).filter((f) => !(f in requiredKeys));
|
||||
if (!!incorrectServiceDefinitions.length) {
|
||||
throw new Error(
|
||||
`Error registering service ${serviceId}: missing functions: ` +
|
||||
incorrectServiceDefinitions.map((d) => "'" + d + "'").join(', '),
|
||||
);
|
||||
}
|
||||
|
||||
for (let singleFunction of def.functions) {
|
||||
// The function has type of (arg1, arg2, arg3, ... , callParams) => CallServiceResultType | void
|
||||
const userDefinedHandler = service[singleFunction.functionName];
|
||||
|
||||
peer.internals.regHandler.common(serviceId, singleFunction.functionName, async (req) => {
|
||||
const args = convertArgsFromReqToUserCall(req, singleFunction.argDefs);
|
||||
const rawResult = await userDefinedHandler.apply(null, args);
|
||||
const result = match(singleFunction.returnType)
|
||||
.with({ tag: 'primitive' }, () => rawResult)
|
||||
.with({ tag: 'optional' }, () => tsToAquaOpt(rawResult))
|
||||
.with({ tag: 'void' }, () => ({}))
|
||||
.exhaustive();
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: result,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts argument from ts representation (value | null) to air representation ([value] | [])
|
||||
*/
|
||||
const tsToAquaOpt = (arg: unknown | null): any => {
|
||||
return arg === null || arg === undefined ? [] : [arg];
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts argument from air representation ([value] | []) to ts representation (value | null)
|
||||
*/
|
||||
const aquaOptToTs = (opt: Array<unknown>) => {
|
||||
return opt.length === 0 ? null : opt[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts raw arguments which may contain optional types from air representation to ts representation
|
||||
*/
|
||||
const convertArgsFromReqToUserCall = (req: CallServiceData, argDefs: Array<ArgDef<OptionalType | PrimitiveType>>) => {
|
||||
if (req.args.length !== argDefs.length) {
|
||||
throwForReq(req, `incorrect number of arguments, expected ${argDefs.length}`);
|
||||
}
|
||||
|
||||
const argsAccountedForOptional = req.args.map((x, index) => {
|
||||
return match(argDefs[index].argType)
|
||||
.with({ tag: 'optional' }, () => aquaOptToTs(x))
|
||||
.with({ tag: 'primitive' }, () => x)
|
||||
.exhaustive();
|
||||
});
|
||||
|
||||
return [...argsAccountedForOptional, extractCallParams(req, argDefs)];
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts Call Params from CallServiceData and forms tetraplets according to generated function definition
|
||||
*/
|
||||
const extractCallParams = (
|
||||
req: CallServiceData,
|
||||
argDefs: Array<ArgDef<OptionalType | PrimitiveType>>,
|
||||
): CallParams<any> => {
|
||||
let tetraplets: { [key in string]: SecurityTetraplet[] } = {};
|
||||
for (let i = 0; i < req.args.length; i++) {
|
||||
if (argDefs[i]) {
|
||||
tetraplets[argDefs[i].name] = req.tetraplets[i];
|
||||
}
|
||||
}
|
||||
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets,
|
||||
};
|
||||
|
||||
return callParams;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = (
|
||||
args: any[],
|
||||
numberOfExpectedArgs: number,
|
||||
): {
|
||||
peer: FluencePeer;
|
||||
config?: { ttl?: number };
|
||||
args: any[];
|
||||
} => {
|
||||
let peer: FluencePeer;
|
||||
let structuredArgs: any[];
|
||||
let config: any;
|
||||
if (FluencePeer.isInstance(args[0])) {
|
||||
peer = args[0];
|
||||
structuredArgs = args.slice(1, numberOfExpectedArgs + 1);
|
||||
config = args[numberOfExpectedArgs + 2];
|
||||
} else {
|
||||
peer = Fluence.getPeer();
|
||||
structuredArgs = args.slice(0, numberOfExpectedArgs);
|
||||
config = args[numberOfExpectedArgs + 1];
|
||||
}
|
||||
|
||||
return {
|
||||
peer: peer,
|
||||
config: config,
|
||||
args: structuredArgs,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 extractRegisterServiceArgs = (
|
||||
args: any[],
|
||||
defaultServiceId?: string,
|
||||
): { peer: FluencePeer; serviceId: string; service: any } => {
|
||||
let peer: FluencePeer;
|
||||
let serviceId: any;
|
||||
let service: any;
|
||||
if (FluencePeer.isInstance(args[0])) {
|
||||
peer = args[0];
|
||||
} else {
|
||||
peer = Fluence.getPeer();
|
||||
}
|
||||
|
||||
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 (!FluencePeer.isInstance(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,
|
||||
};
|
||||
};
|
||||
|
||||
function throwForReq(req: CallServiceData, message: string) {
|
||||
throw new Error(`${message}, serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`);
|
||||
}
|
Reference in New Issue
Block a user