Fix more comments

This commit is contained in:
Akim Mamedov 2023-11-16 20:10:18 +07:00
parent 166475e26c
commit 26bfc36985
14 changed files with 127 additions and 72 deletions

View File

@ -45,7 +45,7 @@ describe("Aqua to js/ts compiler", () => {
}; };
}); });
it("matches js snapshot", async () => { it("matches js snapshots", async () => {
const jsResult = generateSources(res, "js", pkg); const jsResult = generateSources(res, "js", pkg);
const jsTypes = generateTypes(res, pkg); const jsTypes = generateTypes(res, pkg);
@ -58,7 +58,7 @@ describe("Aqua to js/ts compiler", () => {
); );
}); });
it("matches ts snapshot", async () => { it("matches ts snapshots", async () => {
const tsResult = generateSources(res, "ts", pkg); const tsResult = generateSources(res, "ts", pkg);
await expect(tsResult).toMatchFileSnapshot( await expect(tsResult).toMatchFileSnapshot(

View File

@ -39,6 +39,7 @@ ${
: "" : ""
} }
// Making aliases to reduce chance of accidental name collision
import { import {
v5_callFunction as callFunction$$, v5_callFunction as callFunction$$,
v5_registerService as registerService$$, v5_registerService as registerService$$,

View File

@ -119,19 +119,24 @@ export class TSTypeGenerator implements TypeGenerator {
const serviceDecl = `service: ${srvName}Def`; const serviceDecl = `service: ${srvName}Def`;
const serviceIdDecl = `serviceId: string`; const serviceIdDecl = `serviceId: string`;
const functionOverloadsWithDefaultServiceId = [
[serviceDecl],
[serviceIdDecl, serviceDecl],
[peerDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
const functionOverloadsWithoutDefaultServiceId = [
[serviceIdDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
const registerServiceArgs = const registerServiceArgs =
// This wrong type comes from aqua team. We need to discuss fix with them
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
(srvDef.defaultServiceId as DefaultServiceId).s_Some__f_value != null (srvDef.defaultServiceId as DefaultServiceId).s_Some__f_value != null
? [ ? functionOverloadsWithDefaultServiceId
[serviceDecl], : functionOverloadsWithoutDefaultServiceId;
[serviceIdDecl, serviceDecl],
[peerDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
]
: [
[serviceIdDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
return [ return [
interfaces, interfaces,

View File

@ -20,6 +20,7 @@ import { recursiveRenameLaquaProps } from "../utils.js";
import { TypeGenerator } from "./interfaces.js"; import { TypeGenerator } from "./interfaces.js";
// Actual value of defaultServiceId which comes from aqua-api
export interface DefaultServiceId { export interface DefaultServiceId {
s_Some__f_value?: string; s_Some__f_value?: string;
} }

View File

@ -18,12 +18,39 @@ import {
ArrowWithoutCallbacks, ArrowWithoutCallbacks,
FunctionCallDef, FunctionCallDef,
JSONValue, JSONValue,
ScalarType,
SimpleTypes, SimpleTypes,
UnlabeledProductType, UnlabeledProductType,
} from "@fluencelabs/interfaces"; } from "@fluencelabs/interfaces";
import { typeToTs } from "../common.js"; import { typeToTs } from "../common.js";
const numberTypes = [
"u8",
"u16",
"u32",
"u64",
"i8",
"i16",
"i32",
"i64",
"f32",
"f64",
];
function isScalar(schema: ScalarType, arg: JSONValue) {
if (numberTypes.includes(schema.name)) {
return typeof arg === "number";
} else if (schema.name === "bool") {
return typeof arg === "boolean";
} else if (schema.name === "string") {
return typeof arg === "string";
} else {
// Should not be possible
return false;
}
}
export function validateFunctionCall( export function validateFunctionCall(
schema: FunctionCallDef, schema: FunctionCallDef,
...args: JSONValue[] ...args: JSONValue[]
@ -47,12 +74,12 @@ export function validateFunctionCall(
export function validateFunctionCallArg( export function validateFunctionCallArg(
schema: SimpleTypes | UnlabeledProductType | ArrowWithoutCallbacks, schema: SimpleTypes | UnlabeledProductType | ArrowWithoutCallbacks,
arg: JSONValue, arg: JSONValue,
argIndex: number, argPosition: number,
) { ) {
if (!isTypeMatchesSchema(schema, arg)) { if (!isTypeMatchesSchema(schema, arg)) {
const expectedType = typeToTs(schema); const expectedType = typeToTs(schema);
throw new Error( throw new Error(
`Argument ${argIndex} doesn't match schema. Expected type: ${expectedType}`, `Argument ${argPosition} doesn't match schema. Expected type: ${expectedType}`,
); );
} }
} }
@ -66,29 +93,7 @@ export function isTypeMatchesSchema(
} else if (schema.tag === "option") { } else if (schema.tag === "option") {
return arg === null || isTypeMatchesSchema(schema.type, arg); return arg === null || isTypeMatchesSchema(schema.type, arg);
} else if (schema.tag === "scalar") { } else if (schema.tag === "scalar") {
if ( return isScalar(schema, arg);
[
"u8",
"u16",
"u32",
"u64",
"i8",
"i16",
"i32",
"i64",
"f32",
"f64",
].includes(schema.name)
) {
return typeof arg === "number";
} else if (schema.name === "bool") {
return typeof arg === "boolean";
} else if (schema.name === "string") {
return typeof arg === "string";
} else {
// Should not be possible
return false;
}
} else if (schema.tag === "array") { } else if (schema.tag === "array") {
return ( return (
Array.isArray(arg) && Array.isArray(arg) &&

View File

@ -33,6 +33,7 @@ const packageJsonSchema = z.object({
name: z.string(), name: z.string(),
version: z.string(), version: z.string(),
devDependencies: z.object({ devDependencies: z.object({
// This version used in header file
["@fluencelabs/aqua-api"]: z.string(), ["@fluencelabs/aqua-api"]: z.string(),
}), }),
}); });

View File

@ -33,6 +33,17 @@ import { FluencePeer } from "./jsPeer/FluencePeer.js";
import { callAquaFunction, Fluence, registerService } from "./index.js"; import { callAquaFunction, Fluence, registerService } from "./index.js";
const isAquaConfig = (
config: JSONValue | ServiceImpl[string] | undefined,
): config is CallAquaFunctionConfig => {
return (
typeof config === "object" &&
config !== null &&
!Array.isArray(config) &&
["undefined", "number"].includes(typeof config["ttl"])
);
};
/** /**
* 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 corresponding definitions and the air script * The compiler only need to generate a call the function and provide the corresponding definitions and the air script
@ -47,9 +58,11 @@ export const v5_callFunction = async (
script: string, script: string,
): Promise<unknown> => { ): Promise<unknown> => {
const argNames = Object.keys(def.arrow); const argNames = Object.keys(def.arrow);
const argCount = argNames.length; const schemaArgCount = argNames.length;
const functionArgs: Record<string, SimpleTypes | ArrowWithoutCallbacks> = type FunctionArg = SimpleTypes | ArrowWithoutCallbacks;
const schemaFunctionArgs: Record<string, FunctionArg> =
def.arrow.domain.tag === "nil" ? {} : def.arrow.domain.fields; def.arrow.domain.tag === "nil" ? {} : def.arrow.domain.fields;
let peer: FluencePeer | undefined; let peer: FluencePeer | undefined;
@ -61,26 +74,26 @@ export const v5_callFunction = async (
peer = Fluence.defaultClient; peer = Fluence.defaultClient;
} }
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const config =
argCount < args.length
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
(args.pop() as CallAquaFunctionConfig | undefined)
: undefined;
if (peer == null) { if (peer == null) {
throw new Error( throw new Error(
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?", "Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
); );
} }
// if args more than expected in schema (schemaArgCount) then last arg is config
const config = schemaArgCount < args.length ? args.pop() : undefined;
if (!isAquaConfig(config)) {
throw new Error("Config should be object type");
}
const callArgs = Object.fromEntries<JSONValue | ServiceImpl[string]>( const callArgs = Object.fromEntries<JSONValue | ServiceImpl[string]>(
args.slice(0, argCount).map((arg, i) => { args.slice(0, schemaArgCount).map((arg, i) => {
const argSchema = functionArgs[argNames[i]]; const argSchema = schemaFunctionArgs[argNames[i]];
if (argSchema.tag === "arrow") { if (argSchema.tag === "arrow") {
if (typeof arg !== "function") { if (typeof arg !== "function") {
throw new Error("Argument and schema doesn't match"); throw new Error("Argument and schema don't match");
} }
const wrappedFunction = wrapFunction(arg, argSchema); const wrappedFunction = wrapFunction(arg, argSchema);
@ -89,7 +102,7 @@ export const v5_callFunction = async (
} }
if (typeof arg === "function") { if (typeof arg === "function") {
throw new Error("Argument and schema doesn't match"); throw new Error("Argument and schema don't match");
} }
return [argNames[i], ts2aqua(arg, argSchema)]; return [argNames[i], ts2aqua(arg, argSchema)];
@ -111,13 +124,13 @@ export const v5_callFunction = async (
fireAndForget: returnTypeVoid, fireAndForget: returnTypeVoid,
}); });
const valueSchema = const returnSchema =
def.arrow.codomain.tag === "unlabeledProduct" && def.arrow.codomain.tag === "unlabeledProduct" &&
def.arrow.codomain.items.length === 1 def.arrow.codomain.items.length === 1
? def.arrow.codomain.items[0] ? def.arrow.codomain.items[0]
: def.arrow.codomain; : def.arrow.codomain;
return aqua2ts(result, valueSchema); return aqua2ts(result, returnSchema);
}; };
/** /**
@ -145,7 +158,13 @@ export const v5_registerService = (args: unknown[], def: ServiceDef): void => {
); );
} }
if (typeof args[0] === "string") { if (args.length === 2) {
if (typeof args[0] !== "string") {
throw new Error(
`Service ID should be of type string. ${typeof args[0]} provided.`,
);
}
serviceId = args[0]; serviceId = args[0];
} }
@ -153,8 +172,10 @@ export const v5_registerService = (args: unknown[], def: ServiceDef): void => {
throw new Error("Service ID is not provided"); throw new Error("Service ID is not provided");
} }
// Schema for every function in service
const serviceSchema = def.functions.tag === "nil" ? {} : def.functions.fields; const serviceSchema = def.functions.tag === "nil" ? {} : def.functions.fields;
// Wrapping service impl to convert their args ts -> aqua and backwards
const wrappedServiceImpl = Object.fromEntries( const wrappedServiceImpl = Object.fromEntries(
Object.entries(serviceImpl).map(([name, func]) => { Object.entries(serviceImpl).map(([name, func]) => {
return [name, wrapFunction(func, serviceSchema[name])]; return [name, wrapFunction(func, serviceSchema[name])];

View File

@ -25,8 +25,10 @@ import { checkConnection } from "../checkConnection.js";
import { nodes, RELAY } from "./connection.js"; import { nodes, RELAY } from "./connection.js";
const ONE_SECOND = 1000;
describe("FluenceClient usage test suite", () => { describe("FluenceClient usage test suite", () => {
it("Should resolve at TTL when fire and forget behavior is used", async () => { it("Should stop particle processing after TTL is reached", async () => {
await withClient(RELAY, { defaultTtlMs: 600 }, async (peer) => { await withClient(RELAY, { defaultTtlMs: 600 }, async (peer) => {
const script = ` const script = `
(seq (seq
@ -36,7 +38,7 @@ describe("FluenceClient usage test suite", () => {
const particle = await peer.internals.createNewParticle(script); const particle = await peer.internals.createNewParticle(script);
const now = Date.now(); const start = Date.now();
const promise = new Promise<JSONValue>((resolve, reject) => { const promise = new Promise<JSONValue>((resolve, reject) => {
registerHandlersHelper(peer, particle, { registerHandlersHelper(peer, particle, {
@ -51,7 +53,11 @@ describe("FluenceClient usage test suite", () => {
}); });
await expect(promise).rejects.toThrow(ExpirationError); await expect(promise).rejects.toThrow(ExpirationError);
expect(Date.now() - 500).toBeGreaterThanOrEqual(now);
expect(
Date.now() - 500,
"Particle processing didn't stop after TTL is reached",
).toBeGreaterThanOrEqual(start);
}); });
}); });
@ -209,13 +215,17 @@ describe("FluenceClient usage test suite", () => {
); );
}); });
it("With connection options: defaultTTL", async () => { it(
await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => { "With connection options: defaultTTL",
const isConnected = await checkConnection(peer); async () => {
await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => {
const isConnected = await checkConnection(peer);
expect(isConnected).toBeFalsy(); expect(isConnected).toBeFalsy();
}); });
}, 1000); },
ONE_SECOND,
);
}); });
it.skip("Should throw correct error when the client tries to send a particle not to the relay", async () => { it.skip("Should throw correct error when the client tries to send a particle not to the relay", async () => {
@ -247,15 +257,11 @@ describe("FluenceClient usage test suite", () => {
particle, particle,
() => {}, () => {},
(error: Error) => { (error: Error) => {
if (error instanceof SendError) { reject(error);
reject(error.message);
}
}, },
); );
}); });
await promise;
await expect(promise).rejects.toMatch( await expect(promise).rejects.toMatch(
"Particle is expected to be sent to only the single peer (relay which client is connected to)", "Particle is expected to be sent to only the single peer (relay which client is connected to)",
); );

View File

@ -69,6 +69,7 @@ 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<JSONValue>((resolve, reject) => { return new Promise<JSONValue>((resolve, reject) => {
// Registering function args as a services
for (const [name, argVal] of Object.entries(args)) { for (const [name, argVal] of Object.entries(args)) {
let service: ServiceDescription; let service: ServiceDescription;

View File

@ -147,12 +147,12 @@ export const wrapFunction = (
const result = await value(...tsArgs, context); const result = await value(...tsArgs, context);
const valueSchema = const resultSchema =
schema.codomain.tag === "unlabeledProduct" && schema.codomain.tag === "unlabeledProduct" &&
schema.codomain.items.length === 1 schema.codomain.items.length === 1
? schema.codomain.items[0] ? schema.codomain.items[0]
: schema.codomain; : schema.codomain;
return ts2aqua(result, valueSchema); return ts2aqua(result, resultSchema);
}; };
}; };

View File

@ -37,6 +37,7 @@ export class EphemeralNetworkClient extends FluencePeer {
) { ) {
const workerLoader = new WorkerLoader(); const workerLoader = new WorkerLoader();
// TODO: use js-client-isomorphic
const controlModuleLoader = new WasmLoaderFromNpm( const controlModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/marine-js", "@fluencelabs/marine-js",
"marine-js.wasm", "marine-js.wasm",

View File

@ -232,6 +232,7 @@ export class EphemeralNetwork {
// shared worker for all the peers // shared worker for all the peers
this.workerLoader = new WorkerLoaderFromFs("../../marine/worker-script"); this.workerLoader = new WorkerLoaderFromFs("../../marine/worker-script");
// TODO: use js-client-isomorphic
this.controlModuleLoader = new WasmLoaderFromNpm( this.controlModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/marine-js", "@fluencelabs/marine-js",
"marine-js.wasm", "marine-js.wasm",

View File

@ -538,6 +538,17 @@ export abstract class FluencePeer {
"id %s. send successful", "id %s. send successful",
newParticle.id, newParticle.id,
); );
if (
this.jsServiceHost.getHandler(
"callbackSrv",
"response",
item.particle.id,
) == null
) {
// try to finish script if fire-and-forget enabled
item.onSuccess({});
}
}) })
.catch((e: unknown) => { .catch((e: unknown) => {
log_particle.error( log_particle.error(
@ -623,10 +634,9 @@ export abstract class FluencePeer {
"callbackSrv", "callbackSrv",
"response", "response",
item.particle.id, item.particle.id,
) == null && ) == null
item.result.nextPeerPks.length === 0
) { ) {
// try to finish script // try to finish script if fire-and-forget enabled
item.onSuccess({}); item.onSuccess({});
} }
} }

View File

@ -148,6 +148,7 @@ export class TestPeer extends FluencePeer {
constructor(keyPair: KeyPair, connection: IConnection) { constructor(keyPair: KeyPair, connection: IConnection) {
const workerLoader = new WorkerLoader(); const workerLoader = new WorkerLoader();
// TODO: use js-client-isomorphic
const controlModuleLoader = new WasmLoaderFromNpm( const controlModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/marine-js", "@fluencelabs/marine-js",
"marine-js.wasm", "marine-js.wasm",
@ -193,6 +194,7 @@ export const withClient = async (
) => { ) => {
const workerLoader = new WorkerLoader(); const workerLoader = new WorkerLoader();
// TODO: use js-client-isomorphic
const controlModuleLoader = new WasmLoaderFromNpm( const controlModuleLoader = new WasmLoaderFromNpm(
"@fluencelabs/marine-js", "@fluencelabs/marine-js",
"marine-js.wasm", "marine-js.wasm",