feat(js-client)!: Segregation of responsibility between js-client packages [fixes DXJ-525] (#378)

Schema validation in js-client
This commit is contained in:
Akim
2023-11-19 09:04:10 +07:00
committed by GitHub
parent 638da47bc2
commit f4a550dd22
80 changed files with 2998 additions and 11303 deletions

View File

@ -1,3 +1,3 @@
{
"ignorePatterns": ["src/**/__snapshots__/**/*"]
"ignorePatterns": ["src/**/__snapshots__/**/*", "src/**/*.js"]
}

View File

@ -18,12 +18,14 @@
"ts-pattern": "5.0.5"
},
"devDependencies": {
"@fluencelabs/aqua-api": "0.12.0",
"@fluencelabs/aqua-api": "0.12.4-main-cee4448-2196-1",
"@fluencelabs/aqua-lib": "0.7.3",
"@fluencelabs/interfaces": "workspace:*",
"@fluencelabs/js-client": "workspace:^",
"@fluencelabs/registry": "0.8.7",
"@fluencelabs/spell": "0.5.20",
"@fluencelabs/trust-graph": "0.4.7",
"vitest": "0.34.6"
"vitest": "0.34.6",
"zod": "3.22.4"
}
}

View File

@ -14,13 +14,13 @@
* limitations under the License.
*/
import { ArrowWithoutCallbacks, NonArrowType } from "@fluencelabs/interfaces";
import { ArrowType, NonArrowType } from "@fluencelabs/interfaces";
import { match, P } from "ts-pattern";
import { getFuncArgs } from "./utils.js";
export function genTypeName(
t: NonArrowType | ArrowWithoutCallbacks,
t: NonArrowType | ArrowType,
name: string,
): readonly [string | undefined, string] {
const genType = typeToTs(t);
@ -46,7 +46,7 @@ export function genTypeName(
});
}
export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
export function typeToTs(t: NonArrowType | ArrowType): string {
return match(t)
.with({ tag: "nil" }, () => {
return "null";
@ -120,16 +120,7 @@ export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
return [name, typeToTs(type)];
});
const generic =
args.length === 0
? "null"
: args
.map(([name]) => {
return `'${name}'`;
})
.join(" | ");
args.push(["callParams", `CallParams$$<${generic}>`]);
args.push(["callParams", `ParticleContext$$`]);
const funcArgs = args
.map(([name, type]) => {

View File

@ -0,0 +1,20 @@
/* eslint-disable */
// @ts-nocheck
/**
*
* This file is generated using:
* @fluencelabs/aqua-api version: 0.0.0
* @fluencelabs/aqua-to-js version: 0.0.0
* If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues
*
*/
import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';

View File

@ -0,0 +1,20 @@
/* eslint-disable */
// @ts-nocheck
/**
*
* This file is generated using:
* @fluencelabs/aqua-api version: 0.0.0
* @fluencelabs/aqua-to-js version: 0.0.0
* If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues
*
*/
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';

View File

@ -0,0 +1,20 @@
/* eslint-disable */
// @ts-nocheck
/**
*
* This file is generated using:
* @fluencelabs/aqua-api version: 0.0.0
* @fluencelabs/aqua-to-js version: 0.0.0
* If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues
*
*/
import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';

View File

@ -14,46 +14,55 @@
* limitations under the License.
*/
import url from "url";
import { fileURLToPath } from "url";
import { compileFromPath } from "@fluencelabs/aqua-api";
import { describe, expect, it } from "vitest";
import { beforeAll, describe, expect, it } from "vitest";
import { getPackageJsonContent, PackageJson } from "../../utils.js";
import { generateTypes, generateSources } from "../index.js";
import { CompilationResult } from "../interfaces.js";
let res: Omit<CompilationResult, "funcCall">;
let pkg: PackageJson;
describe("Aqua to js/ts compiler", () => {
it("compiles smoke tests successfully", async () => {
const res = await compileFromPath({
filePath: url.fileURLToPath(
beforeAll(async () => {
res = await compileFromPath({
filePath: fileURLToPath(
new URL("./sources/smoke_test.aqua", import.meta.url),
),
imports: ["./node_modules"],
targetType: "air",
});
const pkg: PackageJson = {
pkg = {
...(await getPackageJsonContent()),
version: "0.0.0",
devDependencies: {
"@fluencelabs/aqua-api": "0.0.0",
},
};
});
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
it("matches js snapshots", async () => {
const jsResult = generateSources(res, "js", pkg);
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
const jsTypes = generateTypes(res, pkg);
expect(jsResult).toMatchSnapshot();
expect(jsTypes).toMatchSnapshot();
await expect(jsResult).toMatchFileSnapshot(
"./__snapshots__/generate.snap.js",
);
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
await expect(jsTypes).toMatchFileSnapshot(
"./__snapshots__/generate.snap.d.ts",
);
});
it("matches ts snapshots", async () => {
const tsResult = generateSources(res, "ts", pkg);
expect(tsResult).toMatchSnapshot();
await expect(tsResult).toMatchFileSnapshot(
"./__snapshots__/generate.snap.ts",
);
});
});

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { recursiveRenameLaquaProps } from "../utils.js";
import { capitalize, recursiveRenameLaquaProps } from "../utils.js";
import { AquaFunction, TypeGenerator } from "./interfaces.js";
@ -40,8 +40,11 @@ ${func.script}\`;
${typeGenerator.funcType(func)}
export function ${func.funcDef.functionName}(${typeGenerator.type(
"...args",
"any[]",
)}) {
`${capitalize(func.funcDef.functionName)}Params`,
)})${typeGenerator.type(
"",
`${capitalize(func.funcDef.functionName)}Result`,
)} {
return callFunction$$(
args,
${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)},

View File

@ -35,12 +35,13 @@ export default function generateHeader(
*/
${
outputType === "ts"
? "import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client';"
? "import type { IFluenceClient as IFluenceClient$$, ParticleContext as ParticleContext$$ } from '@fluencelabs/js-client';"
: ""
}
// Making aliases to reduce chance of accidental name collision
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$,
v5_registerService as registerService$$
} from '@fluencelabs/js-client';`;
}

View File

@ -20,6 +20,8 @@ import { genTypeName, typeToTs } from "../common.js";
import { CLIENT } from "../constants.js";
import { capitalize, getFuncArgs } from "../utils.js";
import { DefaultServiceId } from "./service.js";
export interface TypeGenerator {
type(field: string, type: string): string;
generic(field: string, type: string): string;
@ -54,7 +56,7 @@ export class TSTypeGenerator implements TypeGenerator {
args.push([undefined, `config?: {ttl?: number}`]);
const argsDefs = args.map(([, def]) => {
return " " + def;
return def;
});
const argsDesc = args
@ -66,28 +68,30 @@ export class TSTypeGenerator implements TypeGenerator {
});
const functionOverloads = [
argsDefs.join(",\n"),
[` peer: ${CLIENT}`, ...argsDefs].join(",\n"),
argsDefs.join(", "),
[`peer: ${CLIENT}`, ...argsDefs].join(", "),
];
const [resTypeDesc, resType] = genTypeName(
funcDef.arrow.codomain,
capitalize(funcDef.functionName) + "Result",
capitalize(funcDef.functionName) + "ResultType",
);
const functionOverloadArgsType = functionOverloads
.map((overload) => {
return `[${overload}]`;
})
.join(" | ");
return [
argsDesc.join("\n"),
resTypeDesc ?? "",
functionOverloads
.flatMap((fo) => {
return [
`export function ${funcDef.functionName}(`,
fo,
`): Promise<${resType}>;`,
"",
];
})
.join("\n"),
`export type ${capitalize(
funcDef.functionName,
)}Params = ${functionOverloadArgsType};`,
`export type ${capitalize(
funcDef.functionName,
)}Result = Promise<${resType}>;\n`,
]
.filter((s) => {
return s !== "";
@ -117,13 +121,25 @@ export class TSTypeGenerator implements TypeGenerator {
const serviceDecl = `service: ${srvName}Def`;
const serviceIdDecl = `serviceId: string`;
const registerServiceArgs = [
const functionOverloadsWithDefaultServiceId = [
[serviceDecl],
[serviceIdDecl, serviceDecl],
[peerDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
const functionOverloadsWithoutDefaultServiceId = [
[serviceIdDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
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
(srvDef.defaultServiceId as DefaultServiceId).s_Some__f_value != null
? functionOverloadsWithDefaultServiceId
: functionOverloadsWithoutDefaultServiceId;
return [
interfaces,
...registerServiceArgs.map((registerServiceArg) => {

View File

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

View File

@ -15,7 +15,7 @@
*/
import { generateSources, generateTypes } from "./generate/index.js";
import { CompilationResult, OutputType } from "./generate/interfaces.js";
import { CompilationResult } from "./generate/interfaces.js";
import { getPackageJsonContent } from "./utils.js";
interface JsOutput {
@ -33,6 +33,7 @@ type LanguageOutput = {
};
type NothingToGenerate = null;
type OutputType = "js" | "ts";
export default async function aquaToJs<T extends OutputType>(
res: CompilationResult,
@ -52,8 +53,7 @@ export default async function aquaToJs<T extends OutputType>(
sources: generateSources(res, "js", packageJson),
types: generateTypes(res, packageJson),
}
: // TODO: probably there is a way to remove this type assert
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
: // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
({
sources: generateSources(res, "ts", packageJson),
} as LanguageOutput[T]);

View File

@ -16,7 +16,7 @@
import assert from "assert";
import { readFile } from "fs/promises";
import path from "path";
import { join } from "path";
import {
ArrowType,
@ -27,24 +27,26 @@ import {
SimpleTypes,
UnlabeledProductType,
} from "@fluencelabs/interfaces";
import { z } from "zod";
export interface PackageJson {
name: string;
version: string;
devDependencies: {
["@fluencelabs/aqua-api"]: string;
};
}
const packageJsonSchema = z.object({
name: z.string(),
version: z.string(),
devDependencies: z.object({
// @fluencelabs/aqua-api version is included as part of the comment at the top of each js and ts file
["@fluencelabs/aqua-api"]: z.string(),
}),
});
export type PackageJson = z.infer<typeof packageJsonSchema>;
export async function getPackageJsonContent(): Promise<PackageJson> {
const content = await readFile(
new URL(path.join("..", "package.json"), import.meta.url),
new URL(join("..", "package.json"), import.meta.url),
"utf-8",
);
// TODO: Add validation here
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return JSON.parse(content) as PackageJson;
return packageJsonSchema.parse(JSON.parse(content));
}
export function getFuncArgs(