mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-04-25 01:42:13 +00:00
Extract defaults (#34)
* Extracted default request flow builder behavior out of the constructor * Remove init_peer_id variable, which stands in the way when building apps with aquamarine
This commit is contained in:
parent
0ff10a25de
commit
5355eeb152
@ -123,6 +123,7 @@ export const checkConnection = async (client: FluenceClient, ttl?: number): Prom
|
||||
const callbackService = '_callback';
|
||||
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(
|
||||
`(seq
|
||||
(call init_relay ("op" "identity") [msg] result)
|
||||
|
@ -19,6 +19,7 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
// act
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(
|
||||
`(seq
|
||||
(call init_relay ("op" "identity") ["hello world!"] result)
|
||||
@ -76,7 +77,9 @@ describe('Typescript usage suite', () => {
|
||||
data.set('c', 'some c');
|
||||
data.set('d', 'some d');
|
||||
|
||||
await client1.initiateFlow(new RequestFlowBuilder().withRawScript(script).withVariables(data).build());
|
||||
await client1.initiateFlow(
|
||||
new RequestFlowBuilder().withDefaults().withRawScript(script).withVariables(data).build(),
|
||||
);
|
||||
|
||||
let res = await resMakingPromise;
|
||||
expect(res).toEqual(['some a', 'some b', 'some c', 'some d']);
|
||||
@ -186,6 +189,7 @@ describe('Typescript usage suite', () => {
|
||||
it('xor handling should work with connected client', async function () {
|
||||
// arrange
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
@ -210,6 +214,7 @@ describe('Typescript usage suite', () => {
|
||||
it('xor handling should work with local client', async function () {
|
||||
// arrange
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(
|
||||
`
|
||||
(call %init_peer_id% ("service" "fails") [])
|
||||
|
@ -19,6 +19,7 @@ describe('== AIR suite', () => {
|
||||
|
||||
// prettier-ignore
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(script)
|
||||
.buildAsFetch<string[]>(serviceId, fnName);
|
||||
|
||||
@ -59,6 +60,7 @@ describe('== AIR suite', () => {
|
||||
const script = `(incorrect)`;
|
||||
// prettier-ignore
|
||||
const [request, error] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(script)
|
||||
.buildWithErrorHandling();
|
||||
|
||||
@ -75,6 +77,7 @@ describe('== AIR suite', () => {
|
||||
const script = `(null)`;
|
||||
// prettier-ignore
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withTTL(1)
|
||||
.withRawScript(script)
|
||||
.buildAsFetch();
|
||||
@ -96,6 +99,7 @@ describe('== AIR suite', () => {
|
||||
|
||||
// prettier-ignore
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(script)
|
||||
.withVariable('arg1', 'hello')
|
||||
.buildAsFetch<string[]>(serviceId, fnName);
|
||||
@ -138,7 +142,7 @@ describe('== AIR suite', () => {
|
||||
(call %init_peer_id% ("${makeDataServiceId}" "${makeDataFnName}") [] result)
|
||||
(call %init_peer_id% ("${getDataServiceId}" "${getDataFnName}") [result.$.field])
|
||||
)`;
|
||||
await client.initiateFlow(new RequestFlowBuilder().withRawScript(script).build());
|
||||
await client.initiateFlow(new RequestFlowBuilder().withDefaults().withRawScript(script).build());
|
||||
|
||||
// assert
|
||||
const tetraplet = res.tetraplets[0][0];
|
||||
@ -187,7 +191,7 @@ describe('== AIR suite', () => {
|
||||
(call %init_peer_id% ("${serviceId2}" "${fnName2}") ["${arg2}"] result2))
|
||||
(call %init_peer_id% ("${serviceId3}" "${fnName3}") [result1 result2]))
|
||||
`;
|
||||
await client.initiateFlow(new RequestFlowBuilder().withRawScript(script).build());
|
||||
await client.initiateFlow(new RequestFlowBuilder().withDefaults().withRawScript(script).build());
|
||||
|
||||
// assert
|
||||
expect(res1).toEqual(arg1);
|
||||
|
@ -44,6 +44,7 @@ export const sendParticle = async (
|
||||
onError?: (err) => void,
|
||||
): Promise<string> => {
|
||||
const [req, errorPromise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(particle.script)
|
||||
.withVariables(particle.data)
|
||||
.withTTL(particle.ttl)
|
||||
@ -147,6 +148,7 @@ export const sendParticleAsFetch = async <T>(
|
||||
callbackServiceId: string = '_callback',
|
||||
): Promise<T> => {
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(particle.script)
|
||||
.withVariables(particle.data)
|
||||
.withTTL(particle.ttl)
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { of } from 'ipfs-only-hash';
|
||||
import log from 'loglevel';
|
||||
import { AquaCallHandler } from './AquaHandler';
|
||||
import { DEFAULT_TTL, RequestFlow } from './RequestFlow';
|
||||
@ -28,14 +27,47 @@ const wrapWithXor = (script: string): string => {
|
||||
|
||||
class ScriptBuilder {
|
||||
private script: string;
|
||||
private isXorInjected: boolean;
|
||||
private shouldInjectRelay: boolean;
|
||||
private variables?: string[];
|
||||
|
||||
constructor() {
|
||||
this.isXorInjected = false;
|
||||
this.shouldInjectRelay = false;
|
||||
}
|
||||
|
||||
raw(script: string): ScriptBuilder {
|
||||
this.script = script;
|
||||
return this;
|
||||
}
|
||||
|
||||
withInjectedVariables(fields: string[]): ScriptBuilder {
|
||||
this.variables = fields;
|
||||
return this;
|
||||
}
|
||||
|
||||
wrappedWithXor(): ScriptBuilder {
|
||||
this.isXorInjected = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
withInjectedRelay(): ScriptBuilder {
|
||||
this.shouldInjectRelay = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): string {
|
||||
return this.script;
|
||||
let script = this.script;
|
||||
if (this.withInjectedVariables && this.withInjectedVariables.length > 0) {
|
||||
script = wrapWithVariableInjectionScript(script, this.variables);
|
||||
}
|
||||
if (this.isXorInjected) {
|
||||
script = wrapWithXor(script);
|
||||
}
|
||||
if (this.shouldInjectRelay) {
|
||||
script = wrapWithInjectRelayScript(script);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,56 +85,37 @@ const wrapWithVariableInjectionScript = (script: string, fields: string[]): stri
|
||||
|
||||
const wrapWithInjectRelayScript = (script: string): string => {
|
||||
return `
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("${loadVariablesService}" "${loadRelayFn}") [] ${relayVariableName})
|
||||
(call %init_peer_id% ("op" "identity") [%init_peer_id%] init_peer_id)
|
||||
)
|
||||
${script}
|
||||
)`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builder class for configuring and creating Request Flows
|
||||
*/
|
||||
export class RequestFlowBuilder {
|
||||
private ttl: number = DEFAULT_TTL;
|
||||
private variables = new Map<string, any>();
|
||||
private handlerConfigs: Array<(handler: AquaCallHandler) => void> = [];
|
||||
private buildScript: (sb: ScriptBuilder) => void;
|
||||
private handlerConfigs: Array<(handler: AquaCallHandler, request: RequestFlow) => void> = [];
|
||||
private buildScriptActions: Array<(sb: ScriptBuilder) => void> = [];
|
||||
private onTimeout: () => void;
|
||||
private onError: (error: any) => void;
|
||||
|
||||
/**
|
||||
* Builds the Request flow with current configuration
|
||||
*/
|
||||
build() {
|
||||
if (!this.buildScript) {
|
||||
throw new Error();
|
||||
const sb = new ScriptBuilder();
|
||||
for (let action of this.buildScriptActions) {
|
||||
action(sb);
|
||||
}
|
||||
|
||||
const b = new ScriptBuilder();
|
||||
this.buildScript(b);
|
||||
let script = b.build();
|
||||
script = wrapWithVariableInjectionScript(script, Array.from(this.variables.keys()));
|
||||
script = wrapWithXor(script);
|
||||
script = wrapWithInjectRelayScript(script);
|
||||
let script = sb.build();
|
||||
|
||||
const res = RequestFlow.createLocal(script, this.ttl);
|
||||
res.handler.on(loadVariablesService, loadVariablesFn, (args, _) => {
|
||||
return this.variables.get(args[0]) || {};
|
||||
});
|
||||
res.handler.onEvent(xorHandleService, xorHandleFn, (args) => {
|
||||
let msg;
|
||||
try {
|
||||
msg = JSON.parse(args[0]);
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
|
||||
try {
|
||||
res.raiseError(msg);
|
||||
} catch (e) {
|
||||
log.error('Error handling script executed with error', e);
|
||||
}
|
||||
});
|
||||
|
||||
for (let h of this.handlerConfigs) {
|
||||
h(res.handler);
|
||||
h(res.handler, res);
|
||||
}
|
||||
|
||||
if (this.onTimeout) {
|
||||
@ -115,18 +128,96 @@ export class RequestFlowBuilder {
|
||||
return res;
|
||||
}
|
||||
|
||||
withScript(action: (sb: ScriptBuilder) => void): RequestFlowBuilder {
|
||||
this.buildScript = action;
|
||||
/**
|
||||
* Provides necessary defaults when building requests by hand without the Aquamarine language compiler
|
||||
* Includes: relay and variable injection, error handling with top-level xor wrap
|
||||
*/
|
||||
withDefaults(): RequestFlowBuilder {
|
||||
this.injectRelay();
|
||||
this.injectVariables();
|
||||
this.wrapWithXor();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects `init_relay` variable into the script
|
||||
*/
|
||||
injectRelay(): RequestFlowBuilder {
|
||||
this.configureScript((sb) => {
|
||||
sb.withInjectedRelay();
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers services for variable injection. Required for variables registration to work
|
||||
*/
|
||||
injectVariables(): RequestFlowBuilder {
|
||||
this.configureScript((sb) => {
|
||||
sb.withInjectedVariables(Array.from(this.variables.keys()));
|
||||
});
|
||||
|
||||
this.configHandler((h) => {
|
||||
h.on(loadVariablesService, loadVariablesFn, (args, _) => {
|
||||
return this.variables.get(args[0]) || {};
|
||||
});
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the script with top-level error handling with xor instruction. Will raise error in the Request Flow in xor catches any error
|
||||
*/
|
||||
wrapWithXor(): RequestFlowBuilder {
|
||||
this.configureScript((sb) => {
|
||||
sb.wrappedWithXor();
|
||||
});
|
||||
|
||||
this.configHandler((h, request) => {
|
||||
h.onEvent(xorHandleService, xorHandleFn, (args) => {
|
||||
let msg;
|
||||
try {
|
||||
msg = JSON.parse(args[0]);
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
|
||||
try {
|
||||
request.raiseError(msg);
|
||||
} catch (e) {
|
||||
log.error('Error handling script executed with error', e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use ScriptBuilder provided by action in argument to configure script of the Request Flow
|
||||
*/
|
||||
configureScript(action: (sb: ScriptBuilder) => void): RequestFlowBuilder {
|
||||
this.buildScriptActions.push(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use raw text as script for the Request Flow
|
||||
*/
|
||||
withRawScript(script: string): RequestFlowBuilder {
|
||||
this.buildScript = (sb) => {
|
||||
this.buildScriptActions.push((sb) => {
|
||||
sb.raw(script);
|
||||
};
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify time to live for the request
|
||||
*/
|
||||
withTTL(ttl?: number): RequestFlowBuilder {
|
||||
if (ttl) {
|
||||
this.ttl = ttl;
|
||||
@ -134,26 +225,42 @@ export class RequestFlowBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
configHandler(config: (handler: AquaCallHandler) => void): RequestFlowBuilder {
|
||||
/**
|
||||
* Configure local call handler for the Request Flow
|
||||
*/
|
||||
configHandler(config: (handler: AquaCallHandler, request: RequestFlow) => void): RequestFlowBuilder {
|
||||
this.handlerConfigs.push(config);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies handler for the particle timeout event
|
||||
*/
|
||||
handleTimeout(handler: () => void): RequestFlowBuilder {
|
||||
this.onTimeout = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies handler for any script errors
|
||||
*/
|
||||
handleScriptError(handler: (error) => void): RequestFlowBuilder {
|
||||
this.onError = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a variable to the list of injected variables
|
||||
*/
|
||||
withVariable(name: string, value: any): RequestFlowBuilder {
|
||||
this.variables.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a multiple variable to the list of injected variables.
|
||||
* Variables can be specified in form of either object or a map where keys correspond to variable names
|
||||
*/
|
||||
withVariables(data: Map<string, any> | Record<string, any>): RequestFlowBuilder {
|
||||
if (data instanceof Map) {
|
||||
this.variables = new Map([...Array.from(this.variables.entries()), ...Array.from(data.entries())]);
|
||||
@ -166,6 +273,11 @@ export class RequestFlowBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Request flow with current configuration with a fetch-single-result semantics
|
||||
* returns a tuple of [RequestFlow, promise] where promise is a fetch-like promise resolved when
|
||||
* the execution hits callback service and rejected when particle times out or any error happens
|
||||
*/
|
||||
buildAsFetch<T>(
|
||||
callbackServiceId: string = 'callback',
|
||||
callbackFnName: string = 'callback',
|
||||
@ -189,6 +301,10 @@ export class RequestFlowBuilder {
|
||||
return [this.build(), fetchPromise];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Request flow with current configuration with error handling
|
||||
* returns a tuple of [RequestFlow, promise]. The promise is never resolved and rejected in case of any error in the script
|
||||
*/
|
||||
buildWithErrorHandling(): [RequestFlow, Promise<void>] {
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
this.handleScriptError(reject);
|
||||
|
@ -55,6 +55,7 @@ const requestResponse = async <T>(
|
||||
`;
|
||||
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(script)
|
||||
.withVariables(data)
|
||||
.withTTL(ttl)
|
||||
@ -72,6 +73,7 @@ const requestResponse = async <T>(
|
||||
export const getModules = async (client: FluenceClient, ttl?: number): Promise<string[]> => {
|
||||
let callbackFn = 'getModules';
|
||||
const [req, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
@ -100,6 +102,7 @@ export const getModules = async (client: FluenceClient, ttl?: number): Promise<s
|
||||
export const getInterfaces = async (client: FluenceClient, ttl?: number): Promise<string[]> => {
|
||||
let callbackFn = 'getInterfaces';
|
||||
const [req, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
@ -166,6 +169,7 @@ export const uploadModule = async (
|
||||
data.set('myPeerId', client.selfPeerId);
|
||||
|
||||
const [req, promise] = new RequestFlowBuilder()
|
||||
.withDefaults()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
@ -259,7 +263,11 @@ export const createService = async (
|
||||
* @param {[number]} ttl - Optional ttl for the particle which does the job
|
||||
* @returns { Array<object> } - List of available blueprints
|
||||
*/
|
||||
export const getBlueprints = async (client: FluenceClient, nodeId?: string, ttl?: number): Promise<[{dependencies, id: string, name: string}]> => {
|
||||
export const getBlueprints = async (
|
||||
client: FluenceClient,
|
||||
nodeId?: string,
|
||||
ttl?: number,
|
||||
): Promise<[{ dependencies; id: string; name: string }]> => {
|
||||
let returnValue = 'blueprints';
|
||||
let call = (nodeId: string) => `(call "${nodeId}" ("dist" "list_blueprints") [] ${returnValue})`;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user