mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-05-09 16:22:13 +00:00
Fix recursive (#136)
This commit is contained in:
parent
f8abe728c0
commit
a0a7a9e19b
@ -1,211 +0,0 @@
|
||||
import { Fluence, FluencePeer } from '../../..';
|
||||
import { Particle } from '../../../internal/Particle';
|
||||
import { registerHandlersHelper } from '../../util';
|
||||
import { callMeBack, registerHelloWorld } from './gen1';
|
||||
import { callFunction } from '../../../internal/compilerSupport/v2';
|
||||
import { handleTimeout } from '../../../internal/utils';
|
||||
|
||||
describe('Compiler support infrastructure tests', () => {
|
||||
it('Compiled code for function should work', async () => {
|
||||
// arrange
|
||||
await Fluence.start();
|
||||
|
||||
// act
|
||||
const res = new Promise((resolve) => {
|
||||
callMeBack((arg0, arg1, params) => {
|
||||
resolve({
|
||||
arg0: arg0,
|
||||
arg1: arg1,
|
||||
arg0Tetraplet: params.tetraplets.arg0[0], // completion should work here
|
||||
arg1Tetraplet: params.tetraplets.arg1[0], // completion should work here
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(await res).toMatchObject({
|
||||
arg0: 'hello, world',
|
||||
arg1: 42,
|
||||
|
||||
arg0Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
|
||||
arg1Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
});
|
||||
|
||||
await Fluence.stop();
|
||||
});
|
||||
|
||||
it('Compiled code for service should work', async () => {
|
||||
// arrange
|
||||
await Fluence.start();
|
||||
|
||||
// act
|
||||
const helloPromise = new Promise((resolve) => {
|
||||
registerHelloWorld('hello_world', {
|
||||
sayHello: (s, params) => {
|
||||
const tetraplet = params.tetraplets.s; // completion should work here
|
||||
resolve(s);
|
||||
},
|
||||
getNumber: (params) => {
|
||||
// ctx.tetraplets should be {}
|
||||
return 42;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const getNumberPromise = new Promise<string>((resolve, reject) => {
|
||||
const script = `
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("hello_world" "sayHello") ["hello world!"])
|
||||
(call %init_peer_id% ("hello_world" "getNumber") [] result)
|
||||
)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)`;
|
||||
const particle = Particle.createNew(script);
|
||||
registerHandlersHelper(Fluence.getPeer(), particle, {
|
||||
callback: {
|
||||
callback: (args) => {
|
||||
const [val] = args;
|
||||
resolve(val);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Fluence.getPeer().internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(await helloPromise).toBe('hello world!');
|
||||
expect(await getNumberPromise).toBe(42);
|
||||
await Fluence.stop();
|
||||
});
|
||||
|
||||
it('Compiled code for function should work with another peer', async () => {
|
||||
// arrange
|
||||
const peer = new FluencePeer();
|
||||
await peer.start();
|
||||
|
||||
// act
|
||||
const res = new Promise((resolve) => {
|
||||
callMeBack(peer, (arg0, arg1, params) => {
|
||||
resolve({
|
||||
arg0: arg0,
|
||||
arg1: arg1,
|
||||
arg0Tetraplet: params.tetraplets.arg0[0], // completion should work here
|
||||
arg1Tetraplet: params.tetraplets.arg1[0], // completion should work here
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(await res).toMatchObject({
|
||||
arg0: 'hello, world',
|
||||
arg1: 42,
|
||||
|
||||
arg0Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
|
||||
arg1Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
});
|
||||
|
||||
await peer.stop();
|
||||
});
|
||||
|
||||
it('Compiled code for service should work another peer', async () => {
|
||||
// arrange
|
||||
const anotherPeer = new FluencePeer();
|
||||
await anotherPeer.start();
|
||||
|
||||
// act
|
||||
const helloPromise = new Promise((resolve) => {
|
||||
registerHelloWorld(anotherPeer, 'hello_world', {
|
||||
sayHello: (s, params) => {
|
||||
const tetraplet = params.tetraplets.s; // completion should work here
|
||||
resolve(s);
|
||||
},
|
||||
getNumber: (params) => {
|
||||
// ctx.tetraplets should be {}
|
||||
return 42;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const getNumberPromise = new Promise<string>((resolve, reject) => {
|
||||
const script = `
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("hello_world" "sayHello") ["hello world!"])
|
||||
(call %init_peer_id% ("hello_world" "getNumber") [] result)
|
||||
)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)`;
|
||||
const particle = Particle.createNew(script);
|
||||
registerHandlersHelper(anotherPeer, particle, {
|
||||
callback: {
|
||||
callback: (args) => {
|
||||
const [val] = args;
|
||||
resolve(val);
|
||||
},
|
||||
},
|
||||
});
|
||||
anotherPeer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(await helloPromise).toBe('hello world!');
|
||||
expect(await getNumberPromise).toBe(42);
|
||||
|
||||
await anotherPeer.stop();
|
||||
});
|
||||
|
||||
it('Should throw error if particle with incorrect AIR script is initiated', async () => {
|
||||
// arrange;
|
||||
const anotherPeer = new FluencePeer();
|
||||
await anotherPeer.start();
|
||||
|
||||
// act
|
||||
const action = callFunction(
|
||||
[anotherPeer],
|
||||
{
|
||||
functionName: 'dontcare',
|
||||
argDefs: [],
|
||||
returnType: { tag: 'void' },
|
||||
names: {
|
||||
relay: '-relay-',
|
||||
getDataSrv: 'getDataSrv',
|
||||
callbackSrv: 'callbackSrv',
|
||||
responseSrv: 'callbackSrv',
|
||||
responseFnName: 'response',
|
||||
errorHandlingSrv: 'errorHandlingSrv',
|
||||
errorFnName: 'error',
|
||||
},
|
||||
},
|
||||
'incorrect air script',
|
||||
);
|
||||
|
||||
// assert
|
||||
await expect(action).rejects.toMatch(/incorrect air script/);
|
||||
|
||||
await anotherPeer.stop();
|
||||
});
|
||||
});
|
@ -1,193 +0,0 @@
|
||||
import { ResultCodes, RequestFlow, RequestFlowBuilder, CallParams } from '../../../internal/compilerSupport/v1';
|
||||
import { Fluence, FluencePeer } from '../../../index';
|
||||
|
||||
/*
|
||||
|
||||
-- file to generate functions below from
|
||||
|
||||
service HelloWorld("default"):
|
||||
sayHello(s: string)
|
||||
getNumber() -> i32
|
||||
|
||||
func callMeBack(callback: string, i32 -> ()):
|
||||
callback("hello, world", 42)
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
* Aqua version: 0.3.1-231
|
||||
*
|
||||
*/
|
||||
|
||||
function missingFields(obj: any, fields: string[]): string[] {
|
||||
return fields.filter((f) => !(f in obj));
|
||||
}
|
||||
|
||||
// Services
|
||||
|
||||
export interface HelloWorldDef {
|
||||
getNumber: (callParams: CallParams<null>) => number;
|
||||
sayHello: (s: string, callParams: CallParams<'s'>) => void;
|
||||
}
|
||||
|
||||
export function registerHelloWorld(service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(peer: FluencePeer, service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(peer: FluencePeer, serviceId: string, service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(...args: 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 = 'default';
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
const incorrectServiceDefinitions = missingFields(service, ['getNumber', 'sayHello']);
|
||||
if (!!incorrectServiceDefinitions.length) {
|
||||
throw new Error(
|
||||
'Error registering service HelloWorld: missing functions: ' +
|
||||
incorrectServiceDefinitions.map((d) => "'" + d + "'").join(', '),
|
||||
);
|
||||
}
|
||||
|
||||
peer.internals.callServiceHandler.use((req, resp, next) => {
|
||||
if (req.serviceId !== serviceId) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.fnName === 'getNumber') {
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets: {},
|
||||
};
|
||||
resp.retCode = ResultCodes.success;
|
||||
resp.result = service.getNumber(callParams);
|
||||
}
|
||||
|
||||
if (req.fnName === 'sayHello') {
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets: {
|
||||
s: req.tetraplets[0],
|
||||
},
|
||||
};
|
||||
resp.retCode = ResultCodes.success;
|
||||
service.sayHello(req.args[0], callParams);
|
||||
resp.result = {};
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// Functions
|
||||
|
||||
export function callMeBack(
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function callMeBack(
|
||||
peer: FluencePeer,
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function callMeBack(...args: any) {
|
||||
let peer: FluencePeer;
|
||||
let callback: any;
|
||||
let config: any;
|
||||
if (FluencePeer.isInstance(args[0])) {
|
||||
peer = args[0];
|
||||
callback = args[1];
|
||||
config = args[2];
|
||||
} else {
|
||||
peer = Fluence.getPeer();
|
||||
callback = args[0];
|
||||
config = args[1];
|
||||
}
|
||||
|
||||
let request: RequestFlow;
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
const r = new RequestFlowBuilder()
|
||||
.disableInjections()
|
||||
.withRawScript(
|
||||
`
|
||||
(xor
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "callback") ["hello, world" 42])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
`,
|
||||
)
|
||||
.configHandler((h) => {
|
||||
h.on('getDataSrv', '-relay-', () => {
|
||||
return peer.getStatus().relayPeerId;
|
||||
});
|
||||
h.use((req, resp, next) => {
|
||||
if (req.serviceId === 'callbackSrv' && req.fnName === 'callback') {
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets: {
|
||||
arg0: req.tetraplets[0],
|
||||
arg1: req.tetraplets[1],
|
||||
},
|
||||
};
|
||||
resp.retCode = ResultCodes.success;
|
||||
callback(req.args[0], req.args[1], callParams);
|
||||
resp.result = {};
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
h.onEvent('callbackSrv', 'response', (args) => {});
|
||||
h.onEvent('errorHandlingSrv', 'error', (args) => {
|
||||
const [err] = args;
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.handleScriptError(reject)
|
||||
.handleTimeout(() => {
|
||||
reject('Request timed out for callMeBack');
|
||||
});
|
||||
|
||||
if (config && config.ttl) {
|
||||
r.withTTL(config.ttl);
|
||||
}
|
||||
|
||||
request = r.build();
|
||||
});
|
||||
peer.internals.initiateFlow(request!);
|
||||
return Promise.race([promise, Promise.resolve()]);
|
||||
}
|
@ -1,519 +0,0 @@
|
||||
import { CallParams, registerService, callFunction } from '../../../internal/compilerSupport/v2';
|
||||
import { FluencePeer } from '../../../index';
|
||||
|
||||
/*
|
||||
|
||||
-- file to generate functions below from
|
||||
|
||||
service ServiceWithDefaultId("defaultId"):
|
||||
hello(s: string)
|
||||
|
||||
service ServiceWithOUTDefaultId:
|
||||
hello(s: string)
|
||||
|
||||
service MoreMembers:
|
||||
member1()
|
||||
member2(s1: string)
|
||||
member3(s1: string, s2: string)
|
||||
member4(s1: string, s2: string, i: i32) -> i32
|
||||
member5(s1: string, s2: string, i: i32) -> i32
|
||||
|
||||
func f1(callback: string, i32 -> ()):
|
||||
callback("hello, world", 42)
|
||||
|
||||
func f2(num: i32, callback: string, i32 -> ()):
|
||||
callback("hello, world", 42)
|
||||
|
||||
func f3(num: i32, callback: string, i32 -> ()) -> string:
|
||||
callback("hello, world", 42)
|
||||
<- "hello world"
|
||||
|
||||
func callBackZeroArgs(callback: -> ()):
|
||||
callback()
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
* Aqua version: 0.3.1-231
|
||||
*
|
||||
*/
|
||||
|
||||
// Services
|
||||
|
||||
export interface ServiceWithDefaultIdDef {
|
||||
hello: (s: string, callParams: CallParams<'s'>) => void;
|
||||
}
|
||||
export function registerServiceWithDefaultId(service: ServiceWithDefaultIdDef): void;
|
||||
export function registerServiceWithDefaultId(serviceId: string, service: ServiceWithDefaultIdDef): void;
|
||||
export function registerServiceWithDefaultId(peer: FluencePeer, service: ServiceWithDefaultIdDef): void;
|
||||
export function registerServiceWithDefaultId(
|
||||
peer: FluencePeer,
|
||||
serviceId: string,
|
||||
service: ServiceWithDefaultIdDef,
|
||||
): void;
|
||||
|
||||
export function registerServiceWithDefaultId(...args: any) {
|
||||
registerService(args, {
|
||||
defaultServiceId: 'defaultId',
|
||||
functions: [
|
||||
{
|
||||
functionName: 'hello',
|
||||
argDefs: [
|
||||
{
|
||||
name: 's',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export interface ServiceWithOUTDefaultIdDef {
|
||||
hello: (s: string, callParams: CallParams<'s'>) => void;
|
||||
}
|
||||
export function registerServiceWithOUTDefaultId(serviceId: string, service: ServiceWithOUTDefaultIdDef): void;
|
||||
export function registerServiceWithOUTDefaultId(
|
||||
peer: FluencePeer,
|
||||
serviceId: string,
|
||||
service: ServiceWithOUTDefaultIdDef,
|
||||
): void;
|
||||
|
||||
export function registerServiceWithOUTDefaultId(...args: any) {
|
||||
registerService(args, {
|
||||
defaultServiceId: null,
|
||||
functions: [
|
||||
{
|
||||
functionName: 'hello',
|
||||
argDefs: [
|
||||
{
|
||||
name: 's',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export interface MoreMembersDef {
|
||||
member1: (callParams: CallParams<null>) => void;
|
||||
member2: (s1: string, callParams: CallParams<'s1'>) => void;
|
||||
member3: (s1: string, s2: string, callParams: CallParams<'s1' | 's2'>) => void;
|
||||
member4: (s1: string, s2: string, i: number, callParams: CallParams<'s1' | 's2' | 'i'>) => number;
|
||||
member5: (s1: string, s2: string, i: number, callParams: CallParams<'s1' | 's2' | 'i'>) => number;
|
||||
}
|
||||
export function registerMoreMembers(serviceId: string, service: MoreMembersDef): void;
|
||||
export function registerMoreMembers(peer: FluencePeer, serviceId: string, service: MoreMembersDef): void;
|
||||
|
||||
export function registerMoreMembers(...args: any) {
|
||||
registerService(args, {
|
||||
defaultServiceId: null,
|
||||
functions: [
|
||||
{
|
||||
functionName: 'member1',
|
||||
argDefs: [],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
{
|
||||
functionName: 'member2',
|
||||
argDefs: [
|
||||
{
|
||||
name: 's1',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
{
|
||||
functionName: 'member3',
|
||||
argDefs: [
|
||||
{
|
||||
name: 's1',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 's2',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
{
|
||||
functionName: 'member4',
|
||||
argDefs: [
|
||||
{
|
||||
name: 's1',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 's2',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'i',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
functionName: 'member5',
|
||||
argDefs: [
|
||||
{
|
||||
name: 's1',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 's2',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'i',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Functions
|
||||
|
||||
export function f1(
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function f1(
|
||||
peer: FluencePeer,
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function f1(...args: any) {
|
||||
let script = `
|
||||
(xor
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "callback") ["hello, world" 42])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
`;
|
||||
return callFunction(
|
||||
args,
|
||||
{
|
||||
functionName: 'f1',
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
argDefs: [
|
||||
{
|
||||
name: 'callback',
|
||||
argType: {
|
||||
tag: 'callback',
|
||||
callback: {
|
||||
argDefs: [
|
||||
{
|
||||
name: 'arg0',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arg1',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
names: {
|
||||
relay: '-relay-',
|
||||
getDataSrv: 'getDataSrv',
|
||||
callbackSrv: 'callbackSrv',
|
||||
responseSrv: 'callbackSrv',
|
||||
responseFnName: 'response',
|
||||
errorHandlingSrv: 'errorHandlingSrv',
|
||||
errorFnName: 'error',
|
||||
},
|
||||
},
|
||||
script,
|
||||
);
|
||||
}
|
||||
|
||||
export function f2(
|
||||
num: number,
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function f2(
|
||||
peer: FluencePeer,
|
||||
num: number,
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function f2(...args: any) {
|
||||
let script = `
|
||||
(xor
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(call %init_peer_id% ("getDataSrv" "num") [] num)
|
||||
)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "callback") ["hello, world" 42])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
`;
|
||||
return callFunction(
|
||||
args,
|
||||
{
|
||||
functionName: 'f2',
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
argDefs: [
|
||||
{
|
||||
name: 'num',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'callback',
|
||||
argType: {
|
||||
tag: 'callback',
|
||||
callback: {
|
||||
argDefs: [
|
||||
{
|
||||
name: 'arg0',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arg1',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
names: {
|
||||
relay: '-relay-',
|
||||
getDataSrv: 'getDataSrv',
|
||||
callbackSrv: 'callbackSrv',
|
||||
responseSrv: 'callbackSrv',
|
||||
responseFnName: 'response',
|
||||
errorHandlingSrv: 'errorHandlingSrv',
|
||||
errorFnName: 'error',
|
||||
},
|
||||
},
|
||||
script,
|
||||
);
|
||||
}
|
||||
|
||||
export function f3(
|
||||
num: number,
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<string>;
|
||||
export function f3(
|
||||
peer: FluencePeer,
|
||||
num: number,
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<string>;
|
||||
export function f3(...args: any) {
|
||||
let script = `
|
||||
(xor
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(call %init_peer_id% ("getDataSrv" "num") [] num)
|
||||
)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "callback") ["hello, world" 42])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "response") ["hello world"])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
|
||||
)
|
||||
`;
|
||||
return callFunction(
|
||||
args,
|
||||
{
|
||||
functionName: 'f3',
|
||||
returnType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
argDefs: [
|
||||
{
|
||||
name: 'num',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'callback',
|
||||
argType: {
|
||||
tag: 'callback',
|
||||
callback: {
|
||||
argDefs: [
|
||||
{
|
||||
name: 'arg0',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arg1',
|
||||
argType: {
|
||||
tag: 'primitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
names: {
|
||||
relay: '-relay-',
|
||||
getDataSrv: 'getDataSrv',
|
||||
callbackSrv: 'callbackSrv',
|
||||
responseSrv: 'callbackSrv',
|
||||
responseFnName: 'response',
|
||||
errorHandlingSrv: 'errorHandlingSrv',
|
||||
errorFnName: 'error',
|
||||
},
|
||||
},
|
||||
script,
|
||||
);
|
||||
}
|
||||
|
||||
export function callBackZeroArgs(
|
||||
callback: (callParams: CallParams<null>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function callBackZeroArgs(
|
||||
peer: FluencePeer,
|
||||
callback: (callParams: CallParams<null>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function callBackZeroArgs(...args: any) {
|
||||
let script = `
|
||||
(xor
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "callback") [])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
`;
|
||||
return callFunction(
|
||||
args,
|
||||
{
|
||||
functionName: 'callBackZeroArgs',
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
argDefs: [
|
||||
{
|
||||
name: 'callback',
|
||||
argType: {
|
||||
tag: 'callback',
|
||||
callback: {
|
||||
argDefs: [],
|
||||
returnType: {
|
||||
tag: 'void',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
names: {
|
||||
relay: '-relay-',
|
||||
getDataSrv: 'getDataSrv',
|
||||
callbackSrv: 'callbackSrv',
|
||||
responseSrv: 'callbackSrv',
|
||||
responseFnName: 'response',
|
||||
errorHandlingSrv: 'errorHandlingSrv',
|
||||
errorFnName: 'error',
|
||||
},
|
||||
},
|
||||
script,
|
||||
);
|
||||
}
|
@ -158,8 +158,12 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
// act
|
||||
const resMakingPromise = new Promise((resolve) => {
|
||||
peer2.internals.callServiceHandler.onEvent('test', 'test', (args, _) => {
|
||||
resolve(args[0]);
|
||||
peer2.internals.regHandler.common('test', 'test', (req) => {
|
||||
resolve(req.args[0]);
|
||||
return {
|
||||
result: {},
|
||||
retCode: 0,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import each from 'jest-each';
|
||||
import { Fluence, FluencePeer } from '../..';
|
||||
import { forTests } from '../../internal/compilerSupport/v2';
|
||||
import { Fluence, FluencePeer } from '../../..';
|
||||
import { forTests } from '../../../internal/compilerSupport/v2';
|
||||
|
||||
const peer = new FluencePeer();
|
||||
const cfg = { ttl: 1000 };
|
187
src/__test__/unit/compiler/v3.spec.ts
Normal file
187
src/__test__/unit/compiler/v3.spec.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import each from 'jest-each';
|
||||
import { aqua2ts, ts2aqua } from '../../../internal/compilerSupport/v3impl/conversions';
|
||||
|
||||
const i32 = { tag: 'scalar', name: 'i32' } as const;
|
||||
|
||||
const opt_i32 = {
|
||||
tag: 'option',
|
||||
type: i32,
|
||||
} as const;
|
||||
|
||||
const array_i32 = { tag: 'array', type: i32 };
|
||||
|
||||
const array_opt_i32 = { tag: 'array', type: opt_i32 };
|
||||
|
||||
const labeledProduct = {
|
||||
tag: 'labeledProduct',
|
||||
fields: {
|
||||
a: i32,
|
||||
b: opt_i32,
|
||||
c: array_opt_i32,
|
||||
},
|
||||
};
|
||||
|
||||
const struct = {
|
||||
tag: 'struct',
|
||||
name: 'someStruct',
|
||||
fields: {
|
||||
a: i32,
|
||||
b: opt_i32,
|
||||
c: array_opt_i32,
|
||||
},
|
||||
};
|
||||
|
||||
const structs = [
|
||||
{
|
||||
aqua: {
|
||||
a: 1,
|
||||
b: [2],
|
||||
c: [[1], [2]],
|
||||
},
|
||||
|
||||
ts: {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: [1, 2],
|
||||
},
|
||||
},
|
||||
{
|
||||
aqua: {
|
||||
a: 1,
|
||||
b: [],
|
||||
c: [[], [2]],
|
||||
},
|
||||
|
||||
ts: {
|
||||
a: 1,
|
||||
b: null,
|
||||
c: [null, 2],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const labeledProduct2 = {
|
||||
tag: 'labeledProduct',
|
||||
fields: {
|
||||
x: i32,
|
||||
y: i32,
|
||||
},
|
||||
};
|
||||
|
||||
const nestedLabeledProductType = {
|
||||
tag: 'labeledProduct',
|
||||
fields: {
|
||||
a: labeledProduct2,
|
||||
b: {
|
||||
tag: 'option',
|
||||
type: labeledProduct2,
|
||||
},
|
||||
c: {
|
||||
tag: 'array',
|
||||
type: labeledProduct2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const nestedStructs = [
|
||||
{
|
||||
aqua: {
|
||||
a: {
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
b: [
|
||||
{
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
],
|
||||
c: [
|
||||
{
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
{
|
||||
x: 3,
|
||||
y: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
ts: {
|
||||
a: {
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
b: {
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
|
||||
c: [
|
||||
{
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
{
|
||||
x: 3,
|
||||
y: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
aqua: {
|
||||
a: {
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
b: [],
|
||||
c: [],
|
||||
},
|
||||
|
||||
ts: {
|
||||
a: {
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
b: null,
|
||||
c: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Conversion from aqua to typescript', () => {
|
||||
each`
|
||||
aqua | ts | type
|
||||
${1} | ${1} | ${i32}
|
||||
${[]} | ${null} | ${opt_i32}
|
||||
${[1]} | ${1} | ${opt_i32}
|
||||
${[1, 2, 3]} | ${[1, 2, 3]} | ${array_i32}
|
||||
${[]} | ${[]} | ${array_i32}
|
||||
${[[1]]} | ${[1]} | ${array_opt_i32}
|
||||
${[[]]} | ${[null]} | ${array_opt_i32}
|
||||
${[[1], [2]]} | ${[1, 2]} | ${array_opt_i32}
|
||||
${[[], [2]]} | ${[null, 2]} | ${array_opt_i32}
|
||||
${structs[0].aqua} | ${structs[0].ts} | ${labeledProduct}
|
||||
${structs[1].aqua} | ${structs[1].ts} | ${labeledProduct}
|
||||
${structs[0].aqua} | ${structs[0].ts} | ${struct}
|
||||
${structs[1].aqua} | ${structs[1].ts} | ${struct}
|
||||
${nestedStructs[0].aqua} | ${nestedStructs[0].ts} | ${nestedLabeledProductType}
|
||||
${nestedStructs[1].aqua} | ${nestedStructs[1].ts} | ${nestedLabeledProductType}
|
||||
`.test(
|
||||
//
|
||||
'aqua: $aqua. ts: $ts. type: $type',
|
||||
async ({ aqua, ts, type }) => {
|
||||
// arrange
|
||||
|
||||
// act
|
||||
const tsFromAqua = aqua2ts(aqua, type);
|
||||
const aquaFromTs = ts2aqua(ts, type);
|
||||
|
||||
// assert
|
||||
expect(tsFromAqua).toStrictEqual(ts);
|
||||
expect(aquaFromTs).toStrictEqual(aqua);
|
||||
},
|
||||
);
|
||||
});
|
@ -16,14 +16,12 @@
|
||||
|
||||
import { Multiaddr } from 'multiaddr';
|
||||
import { CallServiceData, CallServiceResult, GenericCallServiceHandler, ResultCodes } from './commonTypes';
|
||||
import { CallServiceHandler as LegacyCallServiceHandler } from './compilerSupport/LegacyCallServiceHandler';
|
||||
import { PeerIdB58 } from './commonTypes';
|
||||
import { FluenceConnection } from './FluenceConnection';
|
||||
import { Particle, ParticleExecutionStage, ParticleQueueItem } from './Particle';
|
||||
import { KeyPair } from './KeyPair';
|
||||
import { dataToString, jsonify } from './utils';
|
||||
import { concatMap, filter, pipe, Subject, tap } from 'rxjs';
|
||||
import { RequestFlow } from './compilerSupport/v1';
|
||||
import log from 'loglevel';
|
||||
import { builtInServices } from './builtins/common';
|
||||
import { AvmRunner, InterpreterResult, LogLevel } from '@fluencelabs/avm-runner-interface';
|
||||
@ -212,7 +210,6 @@ export class FluencePeer {
|
||||
await this._connect();
|
||||
}
|
||||
|
||||
this._legacyCallServiceHandler = new LegacyCallServiceHandler();
|
||||
registerDefaultServices(this);
|
||||
|
||||
this._classServices = {
|
||||
@ -242,7 +239,6 @@ export class FluencePeer {
|
||||
await this._disconnect();
|
||||
await this._avmRunner?.terminate();
|
||||
this._avmRunner = undefined;
|
||||
this._legacyCallServiceHandler = null;
|
||||
|
||||
this._particleSpecificHandlers.clear();
|
||||
this._commonHandlers.clear();
|
||||
@ -311,34 +307,6 @@ export class FluencePeer {
|
||||
psh.set(serviceFnKey(serviceId, fnName), handler);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
initiateFlow: (request: RequestFlow): void => {
|
||||
const particle = request.particle;
|
||||
|
||||
this._legacyParticleSpecificHandlers.set(particle.id, {
|
||||
handler: request.handler,
|
||||
error: request.error,
|
||||
timeout: request.timeout,
|
||||
});
|
||||
|
||||
this.internals.initiateParticle(particle, (stage) => {
|
||||
if (stage.stage === 'interpreterError') {
|
||||
request?.error(stage.errorMessage);
|
||||
}
|
||||
|
||||
if (stage.stage === 'expired') {
|
||||
request?.timeout();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
callServiceHandler: this._legacyCallServiceHandler,
|
||||
};
|
||||
}
|
||||
|
||||
@ -544,24 +512,6 @@ export class FluencePeer {
|
||||
log.debug('executing call service handler', jsonify(req));
|
||||
const particleId = req.particleContext.particleId;
|
||||
|
||||
// trying particle-specific handler
|
||||
const lh = this._legacyParticleSpecificHandlers.get(particleId);
|
||||
let res: CallServiceResult = {
|
||||
result: undefined,
|
||||
retCode: undefined,
|
||||
};
|
||||
if (lh !== undefined) {
|
||||
res = lh.handler.execute(req);
|
||||
}
|
||||
|
||||
// if it didn't return any result trying to run the common handler
|
||||
if (res?.result === undefined) {
|
||||
res = this._legacyCallServiceHandler.execute(req);
|
||||
}
|
||||
|
||||
// No result from legacy handler.
|
||||
// Trying to execute async handler
|
||||
if (res.retCode === undefined) {
|
||||
const key = serviceFnKey(req.serviceId, req.fnName);
|
||||
const psh = this._particleSpecificHandlers.get(particleId);
|
||||
let handler: GenericCallServiceHandler;
|
||||
@ -578,11 +528,9 @@ export class FluencePeer {
|
||||
handler = this._commonHandlers.get(key);
|
||||
}
|
||||
|
||||
// if we found a handler, execute it
|
||||
// otherwise return useful error message to AVM
|
||||
res = handler
|
||||
? await handler(req)
|
||||
: {
|
||||
// if no handler is found return useful error message to AVM
|
||||
if (handler === undefined) {
|
||||
return {
|
||||
retCode: ResultCodes.error,
|
||||
result: `No handler has been registered for serviceId='${req.serviceId}' fnName='${
|
||||
req.fnName
|
||||
@ -590,6 +538,9 @@ export class FluencePeer {
|
||||
};
|
||||
}
|
||||
|
||||
// if we found a handler, execute it
|
||||
const res = await handler(req);
|
||||
|
||||
if (res.result === undefined) {
|
||||
res.result = null;
|
||||
}
|
||||
@ -605,23 +556,6 @@ export class FluencePeer {
|
||||
}
|
||||
this._particleQueues.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
private _legacyParticleSpecificHandlers = new Map<
|
||||
string,
|
||||
{
|
||||
handler: LegacyCallServiceHandler;
|
||||
timeout?: () => void;
|
||||
error?: (reason?: any) => void;
|
||||
}
|
||||
>();
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
private _legacyCallServiceHandler: LegacyCallServiceHandler;
|
||||
}
|
||||
|
||||
function isInterpretationSuccessful(result: InterpreterResult) {
|
||||
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* 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 { 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;
|
||||
}
|
||||
}
|
21
src/internal/compilerSupport/v3.ts
Normal file
21
src/internal/compilerSupport/v3.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 { FluencePeer } from '../FluencePeer';
|
||||
export { CallParams } from '../commonTypes';
|
||||
export * from './v3impl/interface';
|
||||
export * from './v3impl/callFunction';
|
||||
export * from './v3impl/registerService';
|
128
src/internal/compilerSupport/v3impl/callFunction.ts
Normal file
128
src/internal/compilerSupport/v3impl/callFunction.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { FnConfig, FunctionCallDef } from './interface';
|
||||
import { FluencePeer } from '../../FluencePeer';
|
||||
import { Fluence } from '../../../index';
|
||||
import { Particle } from '../../Particle';
|
||||
import {
|
||||
injectRelayService,
|
||||
argToServiceDef,
|
||||
registerParticleScopeService,
|
||||
responseService,
|
||||
errorHandlingService,
|
||||
ServiceDescription,
|
||||
userHandlerService,
|
||||
injectValueService,
|
||||
} from './services';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (def.arrow.domain.tag !== 'labeledProduct') {
|
||||
throw new Error('Should be impossible');
|
||||
}
|
||||
|
||||
const argumentTypes = Object.entries(def.arrow.domain.fields);
|
||||
const expectedNumberOfArguments = argumentTypes.length;
|
||||
const { args, peer, config } = extractArgs(rawFnArgs, expectedNumberOfArguments);
|
||||
|
||||
if (args.length !== expectedNumberOfArguments) {
|
||||
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 < expectedNumberOfArguments; i++) {
|
||||
const [name, type] = argumentTypes[i];
|
||||
let service: ServiceDescription;
|
||||
if (type.tag === 'arrow') {
|
||||
service = userHandlerService(def.names.callbackSrv, [name, type], args[i]);
|
||||
} else {
|
||||
service = injectValueService(def.names.getDataSrv, name, type, args[i]);
|
||||
}
|
||||
registerParticleScopeService(peer, particle, service);
|
||||
}
|
||||
|
||||
registerParticleScopeService(peer, particle, responseService(def, resolve));
|
||||
|
||||
registerParticleScopeService(peer, particle, injectRelayService(def, peer));
|
||||
|
||||
registerParticleScopeService(peer, particle, errorHandlingService(def, reject));
|
||||
|
||||
peer.internals.initiateParticle(particle, (stage) => {
|
||||
// If function is void, then it's completed when one of the two conditions is met:
|
||||
// 1. The particle is sent to the network (state 'sent')
|
||||
// 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone')
|
||||
if (isReturnTypeVoid(def) && (stage.stage === 'sent' || stage.stage === 'localWorkDone')) {
|
||||
resolve(undefined);
|
||||
}
|
||||
|
||||
if (stage.stage === 'sendingError') {
|
||||
reject(`Could not send particle for ${def.functionName}: not connected (particle id: ${particle.id})`);
|
||||
}
|
||||
|
||||
if (stage.stage === 'expired') {
|
||||
reject(`Request timed out after ${particle.ttl} for ${def.functionName} (particle id: ${particle.id})`);
|
||||
}
|
||||
|
||||
if (stage.stage === 'interpreterError') {
|
||||
reject(
|
||||
`Script interpretation failed for ${def.functionName}: ${stage.errorMessage} (particle id: ${particle.id})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
const isReturnTypeVoid = (def: FunctionCallDef) => {
|
||||
if (def.arrow.codomain.tag === 'nil') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return def.arrow.codomain.items.length == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 extractArgs = (
|
||||
args: any[],
|
||||
numberOfExpectedArgs: number,
|
||||
): {
|
||||
peer: FluencePeer;
|
||||
config?: FnConfig;
|
||||
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 + 1];
|
||||
} else {
|
||||
peer = Fluence.getPeer();
|
||||
structuredArgs = args.slice(0, numberOfExpectedArgs);
|
||||
config = args[numberOfExpectedArgs];
|
||||
}
|
||||
|
||||
return {
|
||||
peer: peer,
|
||||
config: config,
|
||||
args: structuredArgs,
|
||||
};
|
||||
};
|
184
src/internal/compilerSupport/v3impl/conversions.ts
Normal file
184
src/internal/compilerSupport/v3impl/conversions.ts
Normal file
@ -0,0 +1,184 @@
|
||||
import { jsonify } from '../../utils';
|
||||
import { match } from 'ts-pattern';
|
||||
import { ArrowType, ArrowWithoutCallbacks, NonArrowType, UnlabeledProductType } from './interface';
|
||||
import { CallServiceData } from 'src/internal/commonTypes';
|
||||
|
||||
/**
|
||||
* Convert value from its representation in aqua language to representation in typescript
|
||||
* @param value - value as represented in aqua
|
||||
* @param type - definition of the aqua type
|
||||
* @returns value represented in typescript
|
||||
*/
|
||||
export const aqua2ts = (value: any, type: NonArrowType) => {
|
||||
const res = match(type)
|
||||
.with({ tag: 'nil' }, () => {
|
||||
return null;
|
||||
})
|
||||
.with({ tag: 'option' }, (opt) => {
|
||||
if (value.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return aqua2ts(value[0], opt.type);
|
||||
}
|
||||
})
|
||||
.with({ tag: 'scalar' }, { tag: 'bottomType' }, { tag: 'topType' }, () => {
|
||||
return value;
|
||||
})
|
||||
.with({ tag: 'array' }, (arr) => {
|
||||
return value.map((y) => aqua2ts(y, arr.type));
|
||||
})
|
||||
.with({ tag: 'struct' }, (x) => {
|
||||
return Object.entries(x.fields).reduce((agg, [key, type]) => {
|
||||
const val = aqua2ts(value[key], type);
|
||||
return { ...agg, [key]: val };
|
||||
}, {});
|
||||
})
|
||||
.with({ tag: 'labeledProduct' }, (x) => {
|
||||
return Object.entries(x.fields).reduce((agg, [key, type]) => {
|
||||
const val = aqua2ts(value[key], type);
|
||||
return { ...agg, [key]: val };
|
||||
}, {});
|
||||
})
|
||||
.with({ tag: 'unlabeledProduct' }, (x) => {
|
||||
return x.items.map((type, index) => {
|
||||
return aqua2ts(value[index], type);
|
||||
});
|
||||
})
|
||||
// uncomment to check that every pattern in matched
|
||||
// .exhaustive();
|
||||
.otherwise(() => {
|
||||
throw new Error('Unexpected tag: ' + jsonify(type));
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert call service arguments list from their aqua representation to representation in typescript
|
||||
* @param req - call service data
|
||||
* @param arrow - aqua type definition
|
||||
* @returns arguments in typescript representation
|
||||
*/
|
||||
export const aquaArgs2Ts = (req: CallServiceData, arrow: ArrowWithoutCallbacks) => {
|
||||
const argTypes = match(arrow.domain)
|
||||
.with({ tag: 'labeledProduct' }, (x) => {
|
||||
return Object.values(x.fields);
|
||||
})
|
||||
.with({ tag: 'unlabeledProduct' }, (x) => {
|
||||
return x.items;
|
||||
})
|
||||
.with({ tag: 'nil' }, (x) => {
|
||||
return [];
|
||||
})
|
||||
// uncomment to check that every pattern in matched
|
||||
// .exhaustive()
|
||||
.otherwise(() => {
|
||||
throw new Error('Unexpected tag: ' + jsonify(arrow.domain));
|
||||
});
|
||||
|
||||
if (req.args.length !== argTypes.length) {
|
||||
throw new Error(`incorrect number of arguments, expected: ${argTypes.length}, got: ${req.args.length}`);
|
||||
}
|
||||
|
||||
return req.args.map((arg, index) => {
|
||||
return aqua2ts(arg, argTypes[index]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert value from its typescript representation to representation in aqua
|
||||
* @param value - the value as represented in typescript
|
||||
* @param type - definition of the aqua type
|
||||
* @returns value represented in aqua
|
||||
*/
|
||||
export const ts2aqua = (value: any, type: NonArrowType) => {
|
||||
const res = match(type)
|
||||
.with({ tag: 'nil' }, () => {
|
||||
return null;
|
||||
})
|
||||
.with({ tag: 'option' }, (opt) => {
|
||||
if (value === null) {
|
||||
return [];
|
||||
} else {
|
||||
return [ts2aqua(value, opt.type)];
|
||||
}
|
||||
})
|
||||
.with({ tag: 'scalar' }, { tag: 'bottomType' }, { tag: 'topType' }, () => {
|
||||
return value;
|
||||
})
|
||||
.with({ tag: 'array' }, (arr) => {
|
||||
return value.map((y) => ts2aqua(y, arr.type));
|
||||
})
|
||||
.with({ tag: 'struct' }, (x) => {
|
||||
return Object.entries(x.fields).reduce((agg, [key, type]) => {
|
||||
const val = ts2aqua(value[key], type);
|
||||
return { ...agg, [key]: val };
|
||||
}, {});
|
||||
})
|
||||
.with({ tag: 'labeledProduct' }, (x) => {
|
||||
return Object.entries(x.fields).reduce((agg, [key, type]) => {
|
||||
const val = ts2aqua(value[key], type);
|
||||
return { ...agg, [key]: val };
|
||||
}, {});
|
||||
})
|
||||
.with({ tag: 'unlabeledProduct' }, (x) => {
|
||||
return x.items.map((type, index) => {
|
||||
return ts2aqua(value[index], type);
|
||||
});
|
||||
})
|
||||
// uncomment to check that every pattern in matched
|
||||
// .exhaustive()
|
||||
.otherwise(() => {
|
||||
throw new Error('Unexpected tag: ' + jsonify(type));
|
||||
});
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert return type of the service from it's typescript representation to representation in aqua
|
||||
* @param returnValue - the value as represented in typescript
|
||||
* @param arrowType - the arrow type which describes the service
|
||||
* @returns - value represented in aqua
|
||||
*/
|
||||
export const returnType2Aqua = (returnValue: any, arrowType: ArrowType<NonArrowType>) => {
|
||||
if (arrowType.codomain.tag === 'nil') {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (arrowType.codomain.items.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (arrowType.codomain.items.length === 1) {
|
||||
return ts2aqua(returnValue, arrowType.codomain.items[0]);
|
||||
}
|
||||
|
||||
return arrowType.codomain.items.map((type, index) => {
|
||||
return ts2aqua(returnValue[index], type);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts response value from aqua its representation to representation in typescript
|
||||
* @param req - call service data
|
||||
* @param arrow - aqua type definition
|
||||
* @returns response value in typescript representation
|
||||
*/
|
||||
export const responseServiceValue2ts = (req: CallServiceData, arrow: ArrowType<any>) => {
|
||||
return match(arrow.codomain)
|
||||
.with({ tag: 'nil' }, () => {
|
||||
return undefined;
|
||||
})
|
||||
.with({ tag: 'unlabeledProduct' }, (x) => {
|
||||
if (x.items.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (x.items.length === 1) {
|
||||
return aqua2ts(req.args[0], x.items[0]);
|
||||
}
|
||||
|
||||
return req.args.map((y, index) => aqua2ts(y, x.items[index]));
|
||||
})
|
||||
.exhaustive();
|
||||
};
|
238
src/internal/compilerSupport/v3impl/interface.ts
Normal file
238
src/internal/compilerSupport/v3impl/interface.ts
Normal file
@ -0,0 +1,238 @@
|
||||
type SomeNonArrowTypes = ScalarType | OptionType | ArrayType | StructType | TopType | BottomType;
|
||||
|
||||
export type NonArrowType = SomeNonArrowTypes | ProductType<SomeNonArrowTypes>;
|
||||
|
||||
export type TopType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'topType';
|
||||
};
|
||||
|
||||
export type BottomType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'bottomType';
|
||||
};
|
||||
|
||||
export type OptionType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'option';
|
||||
|
||||
/**
|
||||
* Underlying type of the option
|
||||
*/
|
||||
type: NonArrowType;
|
||||
};
|
||||
|
||||
export type NilType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'nil';
|
||||
};
|
||||
|
||||
export type ArrayType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'array';
|
||||
|
||||
/**
|
||||
* Type of array elements
|
||||
*/
|
||||
type: NonArrowType;
|
||||
};
|
||||
|
||||
/**
|
||||
* All possible scalar type names
|
||||
*/
|
||||
export type ScalarNames =
|
||||
| 'u8'
|
||||
| 'u16'
|
||||
| 'u32'
|
||||
| 'u64'
|
||||
| 'i8'
|
||||
| 'i16'
|
||||
| 'i32'
|
||||
| 'i64'
|
||||
| 'f32'
|
||||
| 'f64'
|
||||
| 'bool'
|
||||
| 'string';
|
||||
|
||||
export type ScalarType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'scalar';
|
||||
|
||||
/**
|
||||
* Name of the scalar type
|
||||
*/
|
||||
name: ScalarNames;
|
||||
};
|
||||
|
||||
export type StructType = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'struct';
|
||||
|
||||
/**
|
||||
* Struct name
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Struct fields
|
||||
*/
|
||||
fields: { [key: string]: NonArrowType };
|
||||
};
|
||||
|
||||
export type LabeledProductType<T> =
|
||||
| {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'labeledProduct';
|
||||
|
||||
/**
|
||||
* Labelled product fields
|
||||
*/
|
||||
fields: { [key: string]: T };
|
||||
}
|
||||
| NilType;
|
||||
|
||||
export type UnlabeledProductType<T> =
|
||||
| {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'unlabeledProduct';
|
||||
|
||||
/**
|
||||
* Items in unlabelled product
|
||||
*/
|
||||
items: Array<T>;
|
||||
}
|
||||
| NilType;
|
||||
|
||||
export type ProductType<T> = UnlabeledProductType<T> | LabeledProductType<T>;
|
||||
|
||||
/**
|
||||
* ArrowType is a profunctor pointing its domain to codomain.
|
||||
* Profunctor means variance: Arrow is contravariant on domain, and variant on codomain.
|
||||
*/
|
||||
export type ArrowType<T> = {
|
||||
/**
|
||||
* Type descriptor. Used for pattern-matching
|
||||
*/
|
||||
tag: 'arrow';
|
||||
|
||||
/**
|
||||
* Where this Arrow is defined
|
||||
*/
|
||||
domain: ProductType<T>;
|
||||
|
||||
/**
|
||||
* Where this Arrow points to
|
||||
*/
|
||||
codomain: UnlabeledProductType<NonArrowType> | NilType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Arrow which domain contains only non-arrow types
|
||||
*/
|
||||
export type ArrowWithoutCallbacks = ArrowType<NonArrowType>;
|
||||
|
||||
/**
|
||||
* Arrow which domain does can contain both non-arrow types and arrows (which themselves cannot contain arrows)
|
||||
*/
|
||||
export type ArrowWithCallbacks = ArrowType<NonArrowType | ArrowWithoutCallbacks>;
|
||||
|
||||
export interface FunctionCallConstants {
|
||||
/**
|
||||
* 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 function (`func` instruction) generated by the Aqua compiler
|
||||
*/
|
||||
export interface FunctionCallDef {
|
||||
/**
|
||||
* The name of the function in Aqua language
|
||||
*/
|
||||
functionName: string;
|
||||
|
||||
/**
|
||||
* Underlying arrow which represents function in aqua
|
||||
*/
|
||||
arrow: ArrowWithCallbacks;
|
||||
|
||||
/**
|
||||
* Names of the different entities used in generated air script
|
||||
*/
|
||||
names: FunctionCallConstants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of service registration function (`service` instruction) generated by the Aqua compiler
|
||||
*/
|
||||
export 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: LabeledProductType<ArrowWithoutCallbacks>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to configure Aqua function execution
|
||||
*/
|
||||
export interface FnConfig {
|
||||
/**
|
||||
* Sets the TTL (time to live) for particle responsible for the function execution
|
||||
* If the option is not set the default TTL from FluencePeer config is used
|
||||
*/
|
||||
ttl?: number;
|
||||
}
|
95
src/internal/compilerSupport/v3impl/registerService.ts
Normal file
95
src/internal/compilerSupport/v3impl/registerService.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { FluencePeer } from '../../FluencePeer';
|
||||
import { Fluence } from '../../../index';
|
||||
import { ServiceDef } from './interface';
|
||||
import { registerGlobalService, userHandlerService } from './services';
|
||||
|
||||
/**
|
||||
* 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 } = extractArgs(args, def.defaultServiceId);
|
||||
|
||||
if (!peer.getStatus().isInitialized) {
|
||||
throw new Error(
|
||||
'Could not register the service because the peer is not initialized. Are you passing the wrong peer to the register function?',
|
||||
);
|
||||
}
|
||||
|
||||
// Checking for missing keys
|
||||
const requiredKeys = def.functions.tag === 'nil' ? [] : Object.keys(def.functions.fields);
|
||||
const incorrectServiceDefinitions = requiredKeys.filter((f) => !(f in service));
|
||||
if (!!incorrectServiceDefinitions.length) {
|
||||
throw new Error(
|
||||
`Error registering service ${serviceId}: missing functions: ` +
|
||||
incorrectServiceDefinitions.map((d) => "'" + d + "'").join(', '),
|
||||
);
|
||||
}
|
||||
|
||||
const singleFunctions = def.functions.tag === 'nil' ? [] : Object.entries(def.functions.fields);
|
||||
for (let singleFunction of singleFunctions) {
|
||||
let [name, type] = singleFunction;
|
||||
// The function has type of (arg1, arg2, arg3, ... , callParams) => CallServiceResultType | void
|
||||
// Account for the fact that user service might be defined as a class - .bind(...)
|
||||
const userDefinedHandler = service[name].bind(service);
|
||||
|
||||
const serviceDescription = userHandlerService(serviceId, singleFunction, userDefinedHandler);
|
||||
registerGlobalService(peer, serviceDescription);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 extractArgs = (
|
||||
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,
|
||||
};
|
||||
};
|
171
src/internal/compilerSupport/v3impl/services.ts
Normal file
171
src/internal/compilerSupport/v3impl/services.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import { SecurityTetraplet } from '@fluencelabs/avm-runner-interface';
|
||||
import { Particle } from 'src/internal/Particle';
|
||||
import { match } from 'ts-pattern';
|
||||
import {
|
||||
CallParams,
|
||||
CallServiceData,
|
||||
CallServiceResult,
|
||||
GenericCallServiceHandler,
|
||||
ResultCodes,
|
||||
} from '../../commonTypes';
|
||||
import { FluencePeer } from '../../FluencePeer';
|
||||
import { aquaArgs2Ts, responseServiceValue2ts, returnType2Aqua, ts2aqua } from './conversions';
|
||||
import { ArrowWithoutCallbacks, FunctionCallConstants, FunctionCallDef, NonArrowType } from './interface';
|
||||
|
||||
export interface ServiceDescription {
|
||||
serviceId: string;
|
||||
fnName: string;
|
||||
handler: GenericCallServiceHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a service which injects relay's peer id into aqua space
|
||||
*/
|
||||
export const injectRelayService = (def: FunctionCallDef, peer: FluencePeer) => {
|
||||
return {
|
||||
serviceId: def.names.getDataSrv,
|
||||
fnName: def.names.relay,
|
||||
handler: (req) => {
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: peer.getStatus().relayPeerId,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a service which injects plain value into aqua space
|
||||
*/
|
||||
export const injectValueService = (serviceId: string, fnName: string, valueType: NonArrowType, value: any) => {
|
||||
return {
|
||||
serviceId: serviceId,
|
||||
fnName: fnName,
|
||||
handler: (req) => {
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: ts2aqua(value, valueType),
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a service which is used to return value from aqua function into typescript space
|
||||
*/
|
||||
export const responseService = (def: FunctionCallDef, resolveCallback: Function) => {
|
||||
return {
|
||||
serviceId: def.names.responseSrv,
|
||||
fnName: def.names.responseFnName,
|
||||
handler: (req) => {
|
||||
const userFunctionReturn = responseServiceValue2ts(req, def.arrow);
|
||||
|
||||
setTimeout(() => {
|
||||
resolveCallback(userFunctionReturn);
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: {},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a service which is used to return errors from aqua function into typescript space
|
||||
*/
|
||||
export const errorHandlingService = (def: FunctionCallDef, rejectCallback: Function) => {
|
||||
return {
|
||||
serviceId: def.names.errorHandlingSrv,
|
||||
fnName: def.names.errorFnName,
|
||||
handler: (req) => {
|
||||
const [err, _] = req.args;
|
||||
setTimeout(() => {
|
||||
rejectCallback(err);
|
||||
}, 0);
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: {},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a service for user-defined service function handler
|
||||
*/
|
||||
export const userHandlerService = (serviceId: string, arrowType: [string, ArrowWithoutCallbacks], userHandler) => {
|
||||
const [fnName, type] = arrowType;
|
||||
return {
|
||||
serviceId,
|
||||
fnName,
|
||||
handler: async (req) => {
|
||||
const args = [...aquaArgs2Ts(req, type), extractCallParams(req, type)];
|
||||
const rawResult = await userHandler.apply(null, args);
|
||||
const result = returnType2Aqua(rawResult, type);
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: result,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts argument of aqua function to a corresponding service.
|
||||
* For arguments of non-arrow types the resulting service injects the argument into aqua space.
|
||||
* For arguments of arrow types the resulting service calls the corresponding function.
|
||||
*/
|
||||
export const argToServiceDef = (
|
||||
arg: any,
|
||||
argName: string,
|
||||
argType: NonArrowType | ArrowWithoutCallbacks,
|
||||
names: FunctionCallConstants,
|
||||
): ServiceDescription => {
|
||||
if (argType.tag === 'arrow') {
|
||||
return userHandlerService(names.callbackSrv, [argName, argType], arg);
|
||||
} else {
|
||||
return injectValueService(names.getDataSrv, argName, arg, argType);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts call params from from call service data according to aqua type definition
|
||||
*/
|
||||
const extractCallParams = (req: CallServiceData, arrow: ArrowWithoutCallbacks): CallParams<any> => {
|
||||
const names = match(arrow.domain)
|
||||
.with({ tag: 'nil' }, () => {
|
||||
return [] as string[];
|
||||
})
|
||||
.with({ tag: 'labeledProduct' }, (x) => {
|
||||
return Object.keys(x.fields);
|
||||
})
|
||||
.with({ tag: 'unlabeledProduct' }, (x) => {
|
||||
return x.items.map((_, index) => 'arg' + index);
|
||||
})
|
||||
.exhaustive();
|
||||
|
||||
let tetraplets: { [key in string]: SecurityTetraplet[] } = {};
|
||||
for (let i = 0; i < req.args.length; i++) {
|
||||
if (names[i]) {
|
||||
tetraplets[names[i]] = req.tetraplets[i];
|
||||
}
|
||||
}
|
||||
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets,
|
||||
};
|
||||
|
||||
return callParams;
|
||||
};
|
||||
|
||||
export const registerParticleScopeService = (peer: FluencePeer, particle: Particle, service: ServiceDescription) => {
|
||||
peer.internals.regHandler.forParticle(particle.id, service.serviceId, service.fnName, service.handler);
|
||||
};
|
||||
|
||||
export const registerGlobalService = (peer: FluencePeer, service: ServiceDescription) => {
|
||||
peer.internals.regHandler.common(service.serviceId, service.fnName, service.handler);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user