mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-06-15 17:11:22 +00:00
Review fixes
This commit is contained in:
@ -34,10 +34,18 @@ for (const file of files) {
|
|||||||
imports: [fileURLToPath(new URL("./node_modules", import.meta.url))],
|
imports: [fileURLToPath(new URL("./node_modules", import.meta.url))],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (cr.warnings.length > 0) {
|
||||||
|
console.log(cr.warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cr.errors.length > 0) {
|
||||||
|
throw new Error(cr.errors.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
const res = await aquaToJs(cr, "ts");
|
const res = await aquaToJs(cr, "ts");
|
||||||
|
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
throw new Error(cr.errors.join("\n"));
|
throw new Error("AquaToJs gave null value after compilation");
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeFile(
|
await writeFile(
|
||||||
|
@ -14,15 +14,17 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Fluence Labs",
|
"author": "Fluence Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"ts-pattern": "5.0.5"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fluencelabs/aqua-api": "0.12.0",
|
"@fluencelabs/aqua-api": "0.12.0",
|
||||||
"@fluencelabs/aqua-lib": "0.7.3",
|
"@fluencelabs/aqua-lib": "0.7.3",
|
||||||
"@fluencelabs/interfaces": "workspace:*",
|
"@fluencelabs/interfaces": "workspace:*",
|
||||||
"@fluencelabs/js-client": "workspace:^",
|
"@fluencelabs/js-client": "0.4.3",
|
||||||
"@fluencelabs/registry": "0.8.7",
|
"@fluencelabs/registry": "0.8.7",
|
||||||
"@fluencelabs/spell": "0.5.20",
|
"@fluencelabs/spell": "0.5.20",
|
||||||
"@fluencelabs/trust-graph": "0.4.7",
|
"@fluencelabs/trust-graph": "0.4.7",
|
||||||
"ts-pattern": "5.0.5",
|
|
||||||
"vitest": "0.34.6",
|
"vitest": "0.34.6",
|
||||||
"zod": "3.22.4"
|
"zod": "3.22.4"
|
||||||
}
|
}
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* @typedef {import("@fluencelabs/js-client").NonArrowSimpleType} NonArrowSimpleType
|
|
||||||
* @typedef {import("@fluencelabs/js-client").JSONValue} JSONValue
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert value from its representation in aqua language to representation in typescript
|
|
||||||
* @param {JSONValue} value - value as represented in aqua
|
|
||||||
* @param {NonArrowSimpleType} schema - definition of the aqua schema
|
|
||||||
* @returns {JSONValue} value represented in typescript
|
|
||||||
*/
|
|
||||||
export function aqua2ts(value, schema) {
|
|
||||||
if (schema.tag === "nil") {
|
|
||||||
return null;
|
|
||||||
} else if (schema.tag === "option") {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
throw new Error("Bad schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.length === 0) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return aqua2ts(value[0], schema.type);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
schema.tag === "scalar" ||
|
|
||||||
schema.tag === "bottomType" ||
|
|
||||||
schema.tag === "topType"
|
|
||||||
) {
|
|
||||||
return value;
|
|
||||||
} else if (schema.tag === "array") {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
throw new Error("Bad schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.map((y) => {
|
|
||||||
return aqua2ts(y, schema.type);
|
|
||||||
});
|
|
||||||
} else if (schema.tag === "unlabeledProduct") {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
throw new Error("Bad schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.map((y, i) => {
|
|
||||||
return aqua2ts(y, schema.items[i]);
|
|
||||||
});
|
|
||||||
} else if (schema.tag === "struct" || schema.tag === "labeledProduct") {
|
|
||||||
if (typeof value !== "object" || value == null || Array.isArray(value)) {
|
|
||||||
throw new Error("Bad schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.entries(schema.fields).reduce((agg, [key, type]) => {
|
|
||||||
const val = aqua2ts(value[key], type);
|
|
||||||
return { ...agg, [key]: val };
|
|
||||||
}, {});
|
|
||||||
} else {
|
|
||||||
throw new Error("Unexpected tag: " + JSON.stringify(schema));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert value from its typescript representation to representation in aqua
|
|
||||||
* @param value {JSONValue} the value as represented in typescript
|
|
||||||
* @param schema {NonArrowSimpleType} - definition of the aqua type
|
|
||||||
* @returns {JSONValue} represented in aqua
|
|
||||||
*/
|
|
||||||
export function ts2aqua(value, schema) {
|
|
||||||
if (schema.tag === "nil") {
|
|
||||||
return null;
|
|
||||||
} else if (schema.tag === "option") {
|
|
||||||
return value == null ? [] : [ts2aqua(value, schema.type)];
|
|
||||||
} else if (
|
|
||||||
schema.tag === "scalar" ||
|
|
||||||
schema.tag === "bottomType" ||
|
|
||||||
schema.tag === "topType"
|
|
||||||
) {
|
|
||||||
return value;
|
|
||||||
} else if (schema.tag === "array") {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
throw new Error("Bad schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.map((y) => {
|
|
||||||
return ts2aqua(y, schema.type);
|
|
||||||
});
|
|
||||||
} else if (schema.tag === "unlabeledProduct") {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
throw new Error("Bad schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.map((y, i) => {
|
|
||||||
return ts2aqua(y, schema.items[i]);
|
|
||||||
});
|
|
||||||
} else if (schema.tag === "struct" || schema.tag === "labeledProduct") {
|
|
||||||
if (typeof value !== "object" || value == null || Array.isArray(value)) {
|
|
||||||
throw new Error("Bad schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.entries(schema.fields).reduce((agg, [key, type]) => {
|
|
||||||
const val = ts2aqua(value[key], type);
|
|
||||||
return { ...agg, [key]: val };
|
|
||||||
}, {});
|
|
||||||
} else {
|
|
||||||
throw new Error("Unexpected tag: " + JSON.stringify(schema));
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,60 +29,23 @@ export function generateFunctions(
|
|||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeepToType<T> = { [K in keyof T]: DeepToType<T[K]> };
|
||||||
|
|
||||||
function generateFunction(typeGenerator: TypeGenerator, func: AquaFunction) {
|
function generateFunction(typeGenerator: TypeGenerator, func: AquaFunction) {
|
||||||
|
const funcDef: DeepToType<typeof func.funcDef> = func.funcDef;
|
||||||
const scriptConstName = func.funcDef.functionName + "_script";
|
const scriptConstName = func.funcDef.functionName + "_script";
|
||||||
const codomain = func.funcDef.arrow.codomain;
|
|
||||||
|
|
||||||
const valueSchema =
|
|
||||||
codomain.tag === "unlabeledProduct" && codomain.items.length === 1
|
|
||||||
? codomain.items[0]
|
|
||||||
: codomain;
|
|
||||||
|
|
||||||
const valueSchemaString = JSON.stringify(
|
|
||||||
recursiveRenameLaquaProps(valueSchema),
|
|
||||||
null,
|
|
||||||
4,
|
|
||||||
);
|
|
||||||
|
|
||||||
const domain = func.funcDef.arrow.domain;
|
|
||||||
const argNames = domain.tag === "nil" ? [] : Object.keys(domain.fields);
|
|
||||||
|
|
||||||
return `export const ${scriptConstName} = \`
|
return `export const ${scriptConstName} = \`
|
||||||
${func.script}\`;
|
${func.script}\`;
|
||||||
|
|
||||||
${typeGenerator.funcType(func)}
|
${typeGenerator.funcType(func)}
|
||||||
export async function ${func.funcDef.functionName}(${typeGenerator.type(
|
export function ${func.funcDef.functionName}(${typeGenerator.type(
|
||||||
"...args",
|
"...args",
|
||||||
"any[]",
|
"any[]",
|
||||||
)}) {
|
)}) {
|
||||||
const argNames = [${argNames
|
return callFunction$$(
|
||||||
.map((arg) => {
|
args,
|
||||||
return `"${arg}"`;
|
${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)},
|
||||||
})
|
${scriptConstName}
|
||||||
.join(", ")}];
|
);
|
||||||
const argCount = argNames.length;
|
|
||||||
let peer = undefined;
|
|
||||||
if (args[0] instanceof FluencePeer$$) {
|
|
||||||
peer = args[0];
|
|
||||||
args = args.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const callArgs = Object.fromEntries(args.slice(0, argCount).map((arg, i) => [argNames[i], arg]));
|
|
||||||
|
|
||||||
const params = ({
|
|
||||||
peer,
|
|
||||||
args: callArgs,
|
|
||||||
config: args[argCount]
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await callFunction$$({
|
|
||||||
script: ${scriptConstName},
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
return aqua2ts(result,
|
|
||||||
${valueSchemaString}
|
|
||||||
);
|
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
import { ServiceDef } from "@fluencelabs/interfaces";
|
import { ServiceDef } from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
|
import { recursiveRenameLaquaProps } from "../utils.js";
|
||||||
|
|
||||||
import { TypeGenerator } from "./interfaces.js";
|
import { TypeGenerator } from "./interfaces.js";
|
||||||
|
|
||||||
export interface DefaultServiceId {
|
export interface DefaultServiceId {
|
||||||
@ -40,45 +42,46 @@ function generateService(
|
|||||||
srvName: string,
|
srvName: string,
|
||||||
srvDef: ServiceDef,
|
srvDef: ServiceDef,
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
const defaultServiceId = (srvDef.defaultServiceId as DefaultServiceId)
|
|
||||||
.s_Some__f_value;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
typeGenerator.serviceType(srvName, srvDef),
|
typeGenerator.serviceType(srvName, srvDef),
|
||||||
generateRegisterServiceOverload(typeGenerator, srvName, defaultServiceId),
|
generateRegisterServiceOverload(typeGenerator, srvName, srvDef),
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateRegisterServiceOverload(
|
function generateRegisterServiceOverload(
|
||||||
typeGenerator: TypeGenerator,
|
typeGenerator: TypeGenerator,
|
||||||
srvName: string,
|
srvName: string,
|
||||||
srvDefaultId?: string,
|
srvDef: ServiceDef,
|
||||||
) {
|
) {
|
||||||
return `export function register${srvName}(${typeGenerator.type(
|
return [
|
||||||
"...args",
|
`export function register${srvName}(${typeGenerator.type(
|
||||||
"any[]",
|
"...args",
|
||||||
)}) {
|
"any[]",
|
||||||
const service = args.pop();
|
)}) {`,
|
||||||
const defaultServiceId = ${
|
" registerService$$(",
|
||||||
srvDefaultId != null ? `"${srvDefaultId}"` : "undefined"
|
" args,",
|
||||||
};
|
` ${serviceToJson(srvDef)}`,
|
||||||
|
" );",
|
||||||
const params = args[0] instanceof FluencePeer$$ ? ({
|
"}",
|
||||||
peer: args[0],
|
].join("\n");
|
||||||
serviceId: args[1] ?? defaultServiceId
|
}
|
||||||
}) : ({
|
|
||||||
peer: undefined,
|
function serviceToJson(service: ServiceDef): string {
|
||||||
serviceId: args[0] ?? defaultServiceId
|
return JSON.stringify(
|
||||||
});
|
{
|
||||||
|
// This assertion is required because aqua-api gives bad types
|
||||||
if (params.serviceId == null) {
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
throw new Error("Service ID is not provided");
|
...((service.defaultServiceId as DefaultServiceId).s_Some__f_value != null
|
||||||
}
|
? {
|
||||||
|
defaultServiceId:
|
||||||
registerService$$({
|
// This assertion is required because aqua-api gives bad types
|
||||||
service,
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
...params
|
(service.defaultServiceId as DefaultServiceId).s_Some__f_value,
|
||||||
});
|
}
|
||||||
}`;
|
: {}),
|
||||||
|
functions: recursiveRenameLaquaProps(service.functions),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
4,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -57,8 +57,7 @@
|
|||||||
"rxjs": "7.5.5",
|
"rxjs": "7.5.5",
|
||||||
"uint8arrays": "4.0.3",
|
"uint8arrays": "4.0.3",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"zod": "3.22.4",
|
"zod": "3.22.4"
|
||||||
"zod-validation-error": "2.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fluencelabs/aqua-api": "0.9.3",
|
"@fluencelabs/aqua-api": "0.9.3",
|
||||||
|
@ -14,77 +14,155 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CallAquaFunctionArgs } from "./compilerSupport/callFunction.js";
|
import type {
|
||||||
|
FunctionCallDef,
|
||||||
|
JSONValue,
|
||||||
|
SimpleTypes,
|
||||||
|
ArrowWithoutCallbacks,
|
||||||
|
ServiceDef,
|
||||||
|
} from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
|
import { CallAquaFunctionConfig } from "./compilerSupport/callFunction.js";
|
||||||
|
import {
|
||||||
|
aqua2ts,
|
||||||
|
ts2aqua,
|
||||||
|
wrapFunction,
|
||||||
|
wrapServiceFunction,
|
||||||
|
} from "./compilerSupport/conversions.js";
|
||||||
import { ServiceImpl } from "./compilerSupport/types.js";
|
import { ServiceImpl } from "./compilerSupport/types.js";
|
||||||
import { FluencePeer } from "./jsPeer/FluencePeer.js";
|
import { FluencePeer } from "./jsPeer/FluencePeer.js";
|
||||||
|
|
||||||
import { callAquaFunction, Fluence, registerService } from "./index.js";
|
import { callAquaFunction, Fluence, registerService } from "./index.js";
|
||||||
|
|
||||||
export const isFluencePeer = (
|
|
||||||
fluencePeerCandidate: unknown,
|
|
||||||
): fluencePeerCandidate is FluencePeer => {
|
|
||||||
return fluencePeerCandidate instanceof FluencePeer;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CallAquaFunctionArgsTuned = Pick<CallAquaFunctionArgs, "args" | "script"> &
|
|
||||||
Partial<Pick<CallAquaFunctionArgs, "config" | "peer">>;
|
|
||||||
|
|
||||||
type RegisterServiceArgs = {
|
|
||||||
peer?: FluencePeer;
|
|
||||||
service: ServiceImpl;
|
|
||||||
serviceId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to support Aqua `func` generation backend
|
* Convenience function to support Aqua `func` generation backend
|
||||||
* The compiler only need to generate a call the function and provide the air script
|
* 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 - function definition generated by the Aqua compiler
|
||||||
|
* @param script - air script with function execution logic generated by the Aqua compiler
|
||||||
*/
|
*/
|
||||||
export const v5_callFunction = async ({
|
export const v5_callFunction = async (
|
||||||
config = {},
|
args: (JSONValue | ((...args: JSONValue[]) => JSONValue))[],
|
||||||
peer,
|
def: FunctionCallDef,
|
||||||
args,
|
script: string,
|
||||||
script,
|
): Promise<unknown> => {
|
||||||
}: CallAquaFunctionArgsTuned): Promise<unknown> => {
|
const argNames = Object.keys(def.arrow);
|
||||||
if (peer == null) {
|
const argCount = argNames.length;
|
||||||
if (Fluence.defaultClient == null) {
|
|
||||||
throw new Error(
|
|
||||||
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const functionArgs: Record<string, SimpleTypes | ArrowWithoutCallbacks> =
|
||||||
|
def.arrow.domain.tag === "nil" ? {} : def.arrow.domain.fields;
|
||||||
|
|
||||||
|
let peer: FluencePeer | undefined;
|
||||||
|
|
||||||
|
if (args[0] instanceof FluencePeer) {
|
||||||
|
peer = args[0];
|
||||||
|
args = args.slice(1);
|
||||||
|
} else {
|
||||||
peer = Fluence.defaultClient;
|
peer = Fluence.defaultClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callAquaFunction({
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
args,
|
const config =
|
||||||
script,
|
argCount < args.length
|
||||||
config,
|
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
(args.pop() as CallAquaFunctionConfig | undefined)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (peer == null) {
|
||||||
|
throw new Error(
|
||||||
|
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const callArgs = Object.fromEntries<
|
||||||
|
JSONValue | ((...args: JSONValue[]) => JSONValue)
|
||||||
|
>(
|
||||||
|
args.slice(0, argCount).map((arg, i) => {
|
||||||
|
const argSchema = functionArgs[argNames[i]];
|
||||||
|
|
||||||
|
if (argSchema.tag === "arrow") {
|
||||||
|
if (typeof arg !== "function") {
|
||||||
|
throw new Error("Argument and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrappedFunction = wrapFunction(arg, argSchema);
|
||||||
|
|
||||||
|
return [argNames[i], wrappedFunction];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof arg === "function") {
|
||||||
|
throw new Error("Argument and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return [argNames[i], ts2aqua(arg, argSchema)];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const params = {
|
||||||
peer,
|
peer,
|
||||||
|
args: callArgs,
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await callAquaFunction({
|
||||||
|
script,
|
||||||
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const valueSchema =
|
||||||
|
def.arrow.codomain.tag === "unlabeledProduct" &&
|
||||||
|
def.arrow.codomain.items.length === 1
|
||||||
|
? def.arrow.codomain.items[0]
|
||||||
|
: def.arrow.codomain;
|
||||||
|
|
||||||
|
return aqua2ts(result, valueSchema);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to support Aqua `service` generation backend
|
* Convenience function to support Aqua `service` generation backend
|
||||||
* The compiler only need to generate a call the function and provide the air script
|
* The compiler only need to generate a call the function and provide the corresponding definitions and the air script
|
||||||
|
* @param args - raw arguments passed by user to the generated function
|
||||||
|
* @param def - service definition generated by the Aqua compiler
|
||||||
*/
|
*/
|
||||||
export const v5_registerService = ({
|
export const v5_registerService = (args: unknown[], def: ServiceDef): void => {
|
||||||
serviceId,
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
service,
|
const serviceImpl = args.pop() as ServiceImpl;
|
||||||
peer,
|
let peer: FluencePeer | undefined;
|
||||||
}: RegisterServiceArgs): void => {
|
let serviceId = def.defaultServiceId;
|
||||||
if (peer == null) {
|
|
||||||
if (Fluence.defaultClient == null) {
|
|
||||||
throw new Error(
|
|
||||||
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (args[0] instanceof FluencePeer) {
|
||||||
|
peer = args[0];
|
||||||
|
args = args.slice(1);
|
||||||
|
} else {
|
||||||
peer = Fluence.defaultClient;
|
peer = Fluence.defaultClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (peer == null) {
|
||||||
|
throw new Error(
|
||||||
|
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof args[0] === "string") {
|
||||||
|
serviceId = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceId == null) {
|
||||||
|
throw new Error("Service ID is not provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceSchema = def.functions.tag === "nil" ? {} : def.functions.fields;
|
||||||
|
|
||||||
|
const wrappedServiceImpl = Object.fromEntries(
|
||||||
|
Object.entries(serviceImpl).map(([name, func]) => {
|
||||||
|
return [name, wrapServiceFunction(func, serviceSchema[name])];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
registerService({
|
registerService({
|
||||||
service,
|
service: wrappedServiceImpl,
|
||||||
serviceId,
|
|
||||||
peer,
|
peer,
|
||||||
|
serviceId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { JSONValue, NonArrowSimpleType } from "@fluencelabs/interfaces";
|
import { JSONValue, NonArrowSimpleType } from "@fluencelabs/interfaces";
|
||||||
import { it, describe, expect, test } from "vitest";
|
import { it, describe, expect, test } from "vitest";
|
||||||
|
|
||||||
import { aqua2ts, ts2aqua } from "../converters.js";
|
import { aqua2ts, ts2aqua } from "../conversions.js";
|
||||||
|
|
||||||
const i32 = { tag: "scalar", name: "i32" } as const;
|
const i32 = { tag: "scalar", name: "i32" } as const;
|
||||||
|
|
@ -17,7 +17,9 @@
|
|||||||
import { JSONValue } from "@fluencelabs/interfaces";
|
import { JSONValue } from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
import { logger } from "../util/logger.js";
|
import { logger } from "../util/logger.js";
|
||||||
|
import { ArgCallbackFunction } from "../util/testUtils.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
errorHandlingService,
|
errorHandlingService,
|
||||||
@ -28,7 +30,6 @@ import {
|
|||||||
ServiceDescription,
|
ServiceDescription,
|
||||||
userHandlerService,
|
userHandlerService,
|
||||||
} from "./services.js";
|
} from "./services.js";
|
||||||
import { ServiceImpl } from "./types.js";
|
|
||||||
|
|
||||||
const log = logger("aqua");
|
const log = logger("aqua");
|
||||||
|
|
||||||
@ -46,9 +47,9 @@ const log = logger("aqua");
|
|||||||
|
|
||||||
export type CallAquaFunctionArgs = {
|
export type CallAquaFunctionArgs = {
|
||||||
script: string;
|
script: string;
|
||||||
config?: CallAquaFunctionConfig;
|
config: CallAquaFunctionConfig | undefined;
|
||||||
peer: FluencePeer;
|
peer: FluencePeer;
|
||||||
args: { [key: string]: JSONValue | ServiceImpl[string] };
|
args: { [key: string]: JSONValue | ArgCallbackFunction };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CallAquaFunctionConfig = {
|
export type CallAquaFunctionConfig = {
|
||||||
@ -65,12 +66,21 @@ export const callAquaFunction = async ({
|
|||||||
|
|
||||||
const particle = await peer.internals.createNewParticle(script, config.ttl);
|
const particle = await peer.internals.createNewParticle(script, config.ttl);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<JSONValue>((resolve, reject) => {
|
||||||
for (const [name, argVal] of Object.entries(args)) {
|
for (const [name, argVal] of Object.entries(args)) {
|
||||||
let service: ServiceDescription;
|
let service: ServiceDescription;
|
||||||
|
|
||||||
if (typeof argVal === "function") {
|
if (typeof argVal === "function") {
|
||||||
service = userHandlerService("callbackSrv", name, argVal);
|
service = userHandlerService(
|
||||||
|
"callbackSrv",
|
||||||
|
name,
|
||||||
|
(...args: [...JSONValue[], ParticleContext]) => {
|
||||||
|
// Impossible to extract all element except the last one and coerce type
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const jsonArgs = args.slice(0, args.length - 1) as JSONValue[];
|
||||||
|
return argVal(jsonArgs);
|
||||||
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
service = injectValueService("getDataSrv", name, argVal);
|
service = injectValueService("getDataSrv", name, argVal);
|
||||||
}
|
}
|
||||||
|
166
packages/core/js-client/src/compilerSupport/conversions.ts
Normal file
166
packages/core/js-client/src/compilerSupport/conversions.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 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 {
|
||||||
|
ArrowWithCallbacks,
|
||||||
|
ArrowWithoutCallbacks,
|
||||||
|
JSONValue,
|
||||||
|
NonArrowSimpleType,
|
||||||
|
} from "@fluencelabs/interfaces";
|
||||||
|
|
||||||
|
import { ParticleContext } from "../jsServiceHost/interfaces.js";
|
||||||
|
|
||||||
|
import { MaybePromise } from "./types.js";
|
||||||
|
|
||||||
|
export function aqua2ts(
|
||||||
|
value: JSONValue,
|
||||||
|
schema: NonArrowSimpleType,
|
||||||
|
): JSONValue {
|
||||||
|
if (schema.tag === "nil") {
|
||||||
|
return null;
|
||||||
|
} else if (schema.tag === "option") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error("Value and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return aqua2ts(value[0], schema.type);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
schema.tag === "scalar" ||
|
||||||
|
schema.tag === "bottomType" ||
|
||||||
|
schema.tag === "topType"
|
||||||
|
) {
|
||||||
|
return value;
|
||||||
|
} else if (schema.tag === "array") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error("Value and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map((y) => {
|
||||||
|
return aqua2ts(y, schema.type);
|
||||||
|
});
|
||||||
|
} else if (schema.tag === "unlabeledProduct") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error("Value and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map((y, i) => {
|
||||||
|
return aqua2ts(y, schema.items[i]);
|
||||||
|
});
|
||||||
|
} else if (["labeledProduct", "struct"].includes(schema.tag)) {
|
||||||
|
if (typeof value !== "object" || value == null || Array.isArray(value)) {
|
||||||
|
throw new Error("Value and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(schema.fields).reduce((agg, [key, type]) => {
|
||||||
|
const val = aqua2ts(value[key], type);
|
||||||
|
return { ...agg, [key]: val };
|
||||||
|
}, {});
|
||||||
|
} else {
|
||||||
|
throw new Error("Unexpected tag: " + JSON.stringify(schema));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ts2aqua(
|
||||||
|
value: JSONValue,
|
||||||
|
schema: NonArrowSimpleType,
|
||||||
|
): JSONValue {
|
||||||
|
if (schema.tag === "nil") {
|
||||||
|
return null;
|
||||||
|
} else if (schema.tag === "option") {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
return value == null ? [] : ([ts2aqua(value, schema.type)] as [JSONValue]);
|
||||||
|
} else if (
|
||||||
|
schema.tag === "scalar" ||
|
||||||
|
schema.tag === "bottomType" ||
|
||||||
|
schema.tag === "topType"
|
||||||
|
) {
|
||||||
|
return value;
|
||||||
|
} else if (schema.tag === "array") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error("Value and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map((y) => {
|
||||||
|
return ts2aqua(y, schema.type);
|
||||||
|
});
|
||||||
|
} else if (schema.tag === "unlabeledProduct") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error("Value and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map((y, i) => {
|
||||||
|
return ts2aqua(y, schema.items[i]);
|
||||||
|
});
|
||||||
|
} else if (["labeledProduct", "struct"].includes(schema.tag)) {
|
||||||
|
if (typeof value !== "object" || value == null || Array.isArray(value)) {
|
||||||
|
throw new Error("Value and schema doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(schema.fields).reduce((agg, [key, type]) => {
|
||||||
|
const val = ts2aqua(value[key], type);
|
||||||
|
return { ...agg, [key]: val };
|
||||||
|
}, {});
|
||||||
|
} else {
|
||||||
|
throw new Error("Unexpected tag: " + JSON.stringify(schema));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wrapFunction = (
|
||||||
|
value: (...args: JSONValue[]) => JSONValue,
|
||||||
|
schema: ArrowWithoutCallbacks,
|
||||||
|
): ((...args: JSONValue[]) => JSONValue) => {
|
||||||
|
return (...args) => {
|
||||||
|
const schemaArgs =
|
||||||
|
schema.codomain.tag === "nil" ? [] : schema.codomain.items;
|
||||||
|
|
||||||
|
const tsArgs = args.map((arg, i) => {
|
||||||
|
return aqua2ts(arg, schemaArgs[i]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = value(...tsArgs);
|
||||||
|
return ts2aqua(result, schema.codomain);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wrapServiceFunction = (
|
||||||
|
value: (
|
||||||
|
...args: [...JSONValue[], ParticleContext]
|
||||||
|
) => MaybePromise<JSONValue>,
|
||||||
|
schema: ArrowWithCallbacks,
|
||||||
|
): ((
|
||||||
|
...args: [...JSONValue[], ParticleContext]
|
||||||
|
) => MaybePromise<JSONValue>) => {
|
||||||
|
return async (...args) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const jsonArgs = args.slice(0, args.length - 1) as JSONValue[];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const context = args[args.length - 1] as ParticleContext;
|
||||||
|
|
||||||
|
const schemaArgs =
|
||||||
|
schema.codomain.tag === "nil" ? [] : schema.codomain.items;
|
||||||
|
|
||||||
|
const tsArgs = jsonArgs.map((arg, i) => {
|
||||||
|
return aqua2ts(arg, schemaArgs[i]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await value(...tsArgs, context);
|
||||||
|
return ts2aqua(result, schema.codomain);
|
||||||
|
};
|
||||||
|
};
|
@ -204,6 +204,4 @@ export {
|
|||||||
|
|
||||||
export { FluencePeer } from "./jsPeer/FluencePeer.js";
|
export { FluencePeer } from "./jsPeer/FluencePeer.js";
|
||||||
|
|
||||||
export type { JSONValue, NonArrowSimpleType } from "@fluencelabs/interfaces";
|
|
||||||
|
|
||||||
export * from "./network.js";
|
export * from "./network.js";
|
||||||
|
@ -16,36 +16,11 @@
|
|||||||
|
|
||||||
import { it, describe, expect } from "vitest";
|
import { it, describe, expect } from "vitest";
|
||||||
|
|
||||||
import { isFluencePeer } from "../../api.js";
|
|
||||||
import { handleTimeout } from "../../particle/Particle.js";
|
import { handleTimeout } from "../../particle/Particle.js";
|
||||||
import {
|
import { registerHandlersHelper, withPeer } from "../../util/testUtils.js";
|
||||||
mkTestPeer,
|
|
||||||
registerHandlersHelper,
|
|
||||||
withPeer,
|
|
||||||
} from "../../util/testUtils.js";
|
|
||||||
import { FluencePeer } from "../FluencePeer.js";
|
import { FluencePeer } from "../FluencePeer.js";
|
||||||
|
|
||||||
describe("FluencePeer usage test suite", () => {
|
describe("FluencePeer usage test suite", () => {
|
||||||
it("should perform test for FluencePeer class correctly", async () => {
|
|
||||||
// arrange
|
|
||||||
const peer = await mkTestPeer();
|
|
||||||
const number = 1;
|
|
||||||
const object = { str: "Hello!" };
|
|
||||||
const undefinedVal = undefined;
|
|
||||||
|
|
||||||
// act
|
|
||||||
const isPeerPeer = isFluencePeer(peer);
|
|
||||||
const isNumberPeer = isFluencePeer(number);
|
|
||||||
const isObjectPeer = isFluencePeer(object);
|
|
||||||
const isUndefinedPeer = isFluencePeer(undefinedVal);
|
|
||||||
|
|
||||||
// act
|
|
||||||
expect(isPeerPeer).toBe(true);
|
|
||||||
expect(isNumberPeer).toBe(false);
|
|
||||||
expect(isObjectPeer).toBe(false);
|
|
||||||
expect(isUndefinedPeer).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should successfully call identity on local peer", async function () {
|
it("Should successfully call identity on local peer", async function () {
|
||||||
await withPeer(async (peer) => {
|
await withPeer(async (peer) => {
|
||||||
const script = `
|
const script = `
|
||||||
|
@ -30,10 +30,7 @@ import { ClientConfig, RelayOptions } from "../clientPeer/types.js";
|
|||||||
import { callAquaFunction } from "../compilerSupport/callFunction.js";
|
import { callAquaFunction } from "../compilerSupport/callFunction.js";
|
||||||
import { IConnection } from "../connection/interfaces.js";
|
import { IConnection } from "../connection/interfaces.js";
|
||||||
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
|
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||||
import {
|
import { CallServiceResultType } from "../jsServiceHost/interfaces.js";
|
||||||
CallServiceResultType,
|
|
||||||
ParticleContext,
|
|
||||||
} from "../jsServiceHost/interfaces.js";
|
|
||||||
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
||||||
import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.js";
|
import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.js";
|
||||||
import { KeyPair } from "../keypair/index.js";
|
import { KeyPair } from "../keypair/index.js";
|
||||||
@ -80,7 +77,7 @@ interface FunctionInfo {
|
|||||||
* Type for callback passed as aqua function argument
|
* Type for callback passed as aqua function argument
|
||||||
*/
|
*/
|
||||||
export type ArgCallbackFunction = (
|
export type ArgCallbackFunction = (
|
||||||
...args: [...JSONValue[], ParticleContext]
|
...args: JSONValue[]
|
||||||
) => JSONValue | Promise<JSONValue>;
|
) => JSONValue | Promise<JSONValue>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@ -161,6 +161,10 @@ importers:
|
|||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
|
|
||||||
packages/core/aqua-to-js:
|
packages/core/aqua-to-js:
|
||||||
|
dependencies:
|
||||||
|
ts-pattern:
|
||||||
|
specifier: 5.0.5
|
||||||
|
version: 5.0.5
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@fluencelabs/aqua-api':
|
'@fluencelabs/aqua-api':
|
||||||
specifier: 0.12.0
|
specifier: 0.12.0
|
||||||
@ -172,7 +176,7 @@ importers:
|
|||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../interfaces
|
version: link:../interfaces
|
||||||
'@fluencelabs/js-client':
|
'@fluencelabs/js-client':
|
||||||
specifier: workspace:^
|
specifier: 0.4.3
|
||||||
version: link:../js-client
|
version: link:../js-client
|
||||||
'@fluencelabs/registry':
|
'@fluencelabs/registry':
|
||||||
specifier: 0.8.7
|
specifier: 0.8.7
|
||||||
@ -183,9 +187,6 @@ importers:
|
|||||||
'@fluencelabs/trust-graph':
|
'@fluencelabs/trust-graph':
|
||||||
specifier: 0.4.7
|
specifier: 0.4.7
|
||||||
version: 0.4.7
|
version: 0.4.7
|
||||||
ts-pattern:
|
|
||||||
specifier: 5.0.5
|
|
||||||
version: 5.0.5
|
|
||||||
vitest:
|
vitest:
|
||||||
specifier: 0.34.6
|
specifier: 0.34.6
|
||||||
version: 0.34.6
|
version: 0.34.6
|
||||||
@ -291,9 +292,6 @@ importers:
|
|||||||
zod:
|
zod:
|
||||||
specifier: 3.22.4
|
specifier: 3.22.4
|
||||||
version: 3.22.4
|
version: 3.22.4
|
||||||
zod-validation-error:
|
|
||||||
specifier: 2.1.0
|
|
||||||
version: 2.1.0(zod@3.22.4)
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@fluencelabs/aqua-api':
|
'@fluencelabs/aqua-api':
|
||||||
specifier: 0.9.3
|
specifier: 0.9.3
|
||||||
@ -14060,7 +14058,7 @@ packages:
|
|||||||
|
|
||||||
/ts-pattern@5.0.5:
|
/ts-pattern@5.0.5:
|
||||||
resolution: {integrity: sha512-tL0w8U/pgaacOmkb9fRlYzWEUDCfVjjv9dD4wHTgZ61MjhuMt46VNWTG747NqW6vRzoWIKABVhFSOJ82FvXrfA==}
|
resolution: {integrity: sha512-tL0w8U/pgaacOmkb9fRlYzWEUDCfVjjv9dD4wHTgZ61MjhuMt46VNWTG747NqW6vRzoWIKABVhFSOJ82FvXrfA==}
|
||||||
dev: true
|
dev: false
|
||||||
|
|
||||||
/tsconfck@2.1.1(typescript@5.1.6):
|
/tsconfck@2.1.1(typescript@5.1.6):
|
||||||
resolution: {integrity: sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==}
|
resolution: {integrity: sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==}
|
||||||
@ -15220,14 +15218,5 @@ packages:
|
|||||||
engines: {node: '>=12.20'}
|
engines: {node: '>=12.20'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/zod-validation-error@2.1.0(zod@3.22.4):
|
|
||||||
resolution: {integrity: sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
zod: ^3.18.0
|
|
||||||
dependencies:
|
|
||||||
zod: 3.22.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/zod@3.22.4:
|
/zod@3.22.4:
|
||||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||||
|
Reference in New Issue
Block a user