Particle lifecycle (#21)

Complete rethinking and refactoring of the codebase.

The codebase basically consists these 5 moving parts now: 

1. Fluence client (and the Particle processor which might be merged with the client) - This part is responsible for initiating Request flows, managing existing requests flows (it keeps the queue of received particles), pulling right strings on request flows to update their state etc
2. Fluence connection - This part is responsible for connecting to network, sending\receiving particles
3. RequestFlow - This is where the state of particle execution process is kept. It is basically a state storage with some control levers to update the state. Each request flow contains some particle lifecycle methods and the AquaCallHandler where all callback logic is kept
4. RequestFlowBuilder - This is where requests are prepared by the user (the developer of the client application) before they are ready to be sent into the network.
5. AquaCallHandler - This is how interpreter callbacks are handled. It is very similar to express.js app and is made of middlewares. Aqua handler is the unified api for both callbacks for our Request flows and non-ours (i.e services that are expected to be called be other peers). See `AquaHandler.ts` for details
This commit is contained in:
Pavel
2021-03-03 22:01:05 +03:00
committed by GitHub
parent 77ca5502b2
commit b0ed007399
28 changed files with 1828 additions and 1156 deletions

View File

@ -1,26 +1,34 @@
import { createLocalClient } from '../connection';
import {subscribeForErrors} from "../../api";
import { createClient, FluenceClient } from '../../api.unstable';
import { RequestFlow } from '../../internal/RequestFlow';
import { RequestFlowBuilder } from '../../internal/RequestFlowBuilder';
let client: FluenceClient;
describe('== AIR suite', () => {
afterEach(async () => {
if (client) {
await client.disconnect();
}
});
it('check init_peer_id', async function () {
// arrange
const serviceId = 'test_service';
const fnName = 'return_first_arg';
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [%init_peer_id%])`;
const client = await createLocalClient();
let res;
client.registerCallback(serviceId, fnName, (args, _) => {
res = args[0];
return res;
});
// prettier-ignore
const [request, promise] = new RequestFlowBuilder()
.withRawScript(script)
.buildAsFetch<string[]>(serviceId, fnName);
// act
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [%init_peer_id%])`;
await client.sendScript(script);
client = await createClient();
await client.initiateFlow(request);
const [result] = await promise;
// assert
expect(res).toEqual(client.selfPeerId);
expect(result).toBe(client.selfPeerId);
});
it('call local function', async function () {
@ -28,10 +36,10 @@ describe('== AIR suite', () => {
const serviceId = 'test_service';
const fnName = 'return_first_arg';
const client = await createLocalClient();
client = await createClient();
let res;
client.registerCallback(serviceId, fnName, (args, _) => {
client.aquaCallHandler.on(serviceId, fnName, (args, _) => {
res = args[0];
return res;
});
@ -39,69 +47,66 @@ describe('== AIR suite', () => {
// act
const arg = 'hello';
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") ["${arg}"])`;
await client.sendScript(script);
await client.initiateFlow(RequestFlow.createLocal(script));
// assert
expect(res).toEqual(arg);
});
it('call broken script', async function () {
// arrange
const client = await createLocalClient();
const script = `(incorrect)`;
describe('error handling', () => {
it('call broken script', async function () {
// arrange
const script = `(incorrect)`;
// prettier-ignore
const [request, error] = new RequestFlowBuilder()
.withRawScript(script)
.buildWithErrorHandling();
// act
const promise = client.sendScript(script);
// act
client = await createClient();
await client.initiateFlow(request);
// assert
await expect(promise).rejects.toContain("aqua script can't be parsed");
});
// assert
await expect(error).rejects.toContain("aqua script can't be parsed");
});
it('call script without ttl', async function () {
// arrange
const client = await createLocalClient();
const script = `(call %init_peer_id% ("op" "identity") [""])`;
it('call script without ttl', async function () {
// arrange
const script = `(null)`;
// prettier-ignore
const [request, promise] = new RequestFlowBuilder()
.withTTL(0)
.withRawScript(script)
.buildAsFetch();
// act
const promise = client.sendScript(script, undefined, 0);
// act
client = await createClient();
await client.initiateFlow(request);
// assert
await expect(promise).rejects.toContain('Particle expired');
});
it.skip('call broken script by fetch', async function () {
// arrange
const client = await createLocalClient();
const script = `(incorrect)`;
// act
const promise = client.fetch(script, ['result']);
// assert
await expect(promise).rejects.toContain("aqua script can't be parsed");
// assert
await expect(promise).rejects.toContain('Timed out after');
});
});
it('check particle arguments', async function () {
// arrange
const serviceId = 'test_service';
const fnName = 'return_first_arg';
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [arg1])`;
const client = await createLocalClient();
let res;
client.registerCallback(serviceId, fnName, (args, _) => {
res = args[0];
return res;
});
// prettier-ignore
const [request, promise] = new RequestFlowBuilder()
.withRawScript(script)
.withVariable('arg1', 'hello')
.buildAsFetch<string[]>(serviceId, fnName);
// act
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [arg1])`;
const data = new Map();
data.set('arg1', 'hello');
await client.sendScript(script, data);
client = await createClient();
await client.initiateFlow(request);
const [result] = await promise;
// assert
expect(res).toEqual('hello');
expect(result).toEqual('hello');
});
it('check security tetraplet', async function () {
@ -111,15 +116,15 @@ describe('== AIR suite', () => {
const getDataServiceId = 'get_data_service';
const getDataFnName = 'get_data';
const client = await createLocalClient();
client = await createClient();
client.registerCallback(makeDataServiceId, makeDataFnName, (args, _) => {
client.aquaCallHandler.on(makeDataServiceId, makeDataFnName, (args, _) => {
return {
field: 42,
};
});
let res;
client.registerCallback(getDataServiceId, getDataFnName, (args, tetraplets) => {
client.aquaCallHandler.on(getDataServiceId, getDataFnName, (args, tetraplets) => {
res = {
args: args,
tetraplets: tetraplets,
@ -133,7 +138,7 @@ describe('== AIR suite', () => {
(call %init_peer_id% ("${makeDataServiceId}" "${makeDataFnName}") [] result)
(call %init_peer_id% ("${getDataServiceId}" "${getDataFnName}") [result.$.field])
)`;
await client.sendScript(script);
await client.initiateFlow(new RequestFlowBuilder().withRawScript(script).build());
// assert
const tetraplet = res.tetraplets[0][0];
@ -146,12 +151,12 @@ describe('== AIR suite', () => {
it('check chain of services work properly', async function () {
// arrange
const client = await createLocalClient();
client = await createClient();
const serviceId1 = 'check1';
const fnName1 = 'fn1';
let res1;
client.registerCallback(serviceId1, fnName1, (args, _) => {
client.aquaCallHandler.on(serviceId1, fnName1, (args, _) => {
res1 = args[0];
return res1;
});
@ -159,7 +164,7 @@ describe('== AIR suite', () => {
const serviceId2 = 'check2';
const fnName2 = 'fn2';
let res2;
client.registerCallback(serviceId2, fnName2, (args, _) => {
client.aquaCallHandler.on(serviceId2, fnName2, (args, _) => {
res2 = args[0];
return res2;
});
@ -167,7 +172,7 @@ describe('== AIR suite', () => {
const serviceId3 = 'check3';
const fnName3 = 'fn3';
let res3;
client.registerCallback(serviceId3, fnName3, (args, _) => {
client.aquaCallHandler.on(serviceId3, fnName3, (args, _) => {
res3 = args;
return res3;
});
@ -182,7 +187,7 @@ describe('== AIR suite', () => {
(call %init_peer_id% ("${serviceId2}" "${fnName2}") ["${arg2}"] result2))
(call %init_peer_id% ("${serviceId3}" "${fnName3}") [result1 result2]))
`;
await client.sendScript(script);
await client.initiateFlow(new RequestFlowBuilder().withRawScript(script).build());
// assert
expect(res1).toEqual(arg1);