feat(js-client)!: Adding strictes eslint and ts config to all packages [fixes DXJ-464] (#355)

* introduce eslint

* Fix all eslint errors

* Eslint fix and some touches

* Fix tests

* Fix misc errors

* change semver

* change semver #2

* Fix path

* Fix path #2

* freeze lock file in CI

* fix package install

* Fix formatting of surrounding files

* Add empty prettier config

* Fix formatting

* Fix build errors

* Remove unused deps

* remove changelog from formatting

* deps cleanup

* make resource importers async

* Refactor

* Fix error message

* remove comment

* more refactoring

* Update packages/core/js-client/src/compilerSupport/registerService.ts

Co-authored-by: shamsartem <shamsartem@gmail.com>

* refactoring

* refactoring fix

* optimize import

* Update packages/@tests/smoke/node/src/index.ts

Co-authored-by: shamsartem <shamsartem@gmail.com>

* Revert package

* Fix pnpm lock

* Lint-fix

* Fix CI

* Update tests

* Fix build

* Fix import

* Use forked threads dep

* Use fixed version

* Update threads

* Fix lint

* Fix test

* Fix test

* Add polyfill for assert

* Add subpath import

* Fix tests

* Fix deps

---------

Co-authored-by: shamsartem <shamsartem@gmail.com>
This commit is contained in:
Akim
2023-10-17 22:14:08 +07:00
committed by GitHub
parent b46933252a
commit 919c7d6ea1
135 changed files with 10529 additions and 9167 deletions

View File

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

View File

@ -2,9 +2,9 @@
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @fluencelabs/js-client bumped to 0.1.7
- The following workspace dependencies were updated
- devDependencies
- @fluencelabs/js-client bumped to 0.1.7
### Dependencies
@ -20,7 +20,6 @@
## 0.0.1 (2023-09-22)
### Features
* **aqua-compiler:** JS-client aqua wrapper [fixes DXJ-461] ([#347](https://github.com/fluencelabs/js-client/issues/347)) ([7fff3b1](https://github.com/fluencelabs/js-client/commit/7fff3b1c0374eef76ab4e665b13cf97b5c50ff70))
- **aqua-compiler:** JS-client aqua wrapper [fixes DXJ-461] ([#347](https://github.com/fluencelabs/js-client/issues/347)) ([7fff3b1](https://github.com/fluencelabs/js-client/commit/7fff3b1c0374eef76ab4e665b13cf97b5c50ff70))

View File

@ -1,31 +1,30 @@
{
"name": "@fluencelabs/aqua-to-js",
"type": "module",
"version": "0.0.4",
"description": "Tool for generating aqua wrapper",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"test": "vitest run",
"build": "tsc"
},
"keywords": [],
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"ts-pattern": "5.0.5"
},
"devDependencies": {
"@fluencelabs/aqua-api": "0.12.0",
"@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",
"typescript": "5.1.6",
"vitest": "0.29.7"
}
"name": "@fluencelabs/aqua-to-js",
"type": "module",
"version": "0.0.4",
"description": "Tool for generating aqua wrapper",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"test": "vitest run",
"build": "tsc"
},
"keywords": [],
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"ts-pattern": "5.0.5"
},
"devDependencies": {
"@fluencelabs/aqua-api": "0.12.0",
"@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"
}
}

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,85 +14,136 @@
* limitations under the License.
*/
import { ArrowType, ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces';
import { match, P } from 'ts-pattern';
import { getFuncArgs } from './utils.js';
import { ArrowWithoutCallbacks, NonArrowType } from "@fluencelabs/interfaces";
import { match, P } from "ts-pattern";
export function genTypeName(t: NonArrowType | ProductType<NonArrowType> | ArrowWithoutCallbacks, name: string): readonly [string | undefined, string] {
const genType = typeToTs(t);
return match(t)
.with(
{ tag: 'nil' },
() => [undefined, 'void'] as const
).with(
{ tag: 'struct' },
() => [`export type ${name} = ${genType}`, name] as const
).with(
{ tag: P.union('labeledProduct', 'unlabeledProduct') },
(item) => {
const args = item.tag === 'labeledProduct'
? Object.values(item.fields)
: item.items;
if (args.length === 1) {
return genTypeName(args[0], name);
}
import { getFuncArgs } from "./utils.js";
return [`export type ${name} = ${genType}`, name] as const;
},
).otherwise(() => [undefined, genType] as const);
export function genTypeName(
t: NonArrowType | ArrowWithoutCallbacks,
name: string,
): readonly [string | undefined, string] {
const genType = typeToTs(t);
return match(t)
.with({ tag: "nil" }, () => {
return [undefined, "void"] as const;
})
.with({ tag: "struct" }, () => {
return [`export type ${name} = ${genType}`, name] as const;
})
.with({ tag: P.union("labeledProduct", "unlabeledProduct") }, (item) => {
const args =
item.tag === "labeledProduct" ? Object.values(item.fields) : item.items;
if (args.length === 1) {
return genTypeName(args[0], name);
}
return [`export type ${name} = ${genType}`, name] as const;
})
.otherwise(() => {
return [undefined, genType] as const;
});
}
export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks | ProductType<NonArrowType>): string {
return match(t)
export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string {
return match(t)
.with({ tag: "nil" }, () => {
return "null";
})
.with({ tag: "option" }, ({ type }) => {
return typeToTs(type) + " | null";
})
.with({ tag: "scalar" }, ({ name }) => {
return match(name)
.with(
{ tag: 'nil' },
() => 'null'
).with(
{ tag: 'option' },
({ type }) => typeToTs(type) + ' | null'
).with(
{ tag: 'scalar' },
({ name }) => match(name)
.with(P.union('u8', 'u16', 'u32', 'u64', 'i8', 'i16', 'i32', 'i64', 'f32', 'f64'), () => 'number')
.with('bool', () => 'boolean')
.with('string', () => 'string')
.with(P._, () => 'any').exhaustive()
).with(
{ tag: 'array' },
({ type }) => typeToTs(type) + '[]'
).with(
{ tag: 'struct' },
({ fields }) => `{ ${Object.entries(fields).map(([field, type]) => `${field}: ${typeToTs(type)};`).join(' ')} }`
).with(
{ tag: 'labeledProduct' },
({ fields }) => `{ ${Object.entries(fields).map(([field, type]) => `${field}: ${typeToTs(type)};`).join(' ')} }`
).with(
{ tag: 'unlabeledProduct' },
({ items }) => `[${items.map(item => typeToTs(item)).join(', ')}]`
).with(
{ tag: 'arrow' },
({ tag, domain, codomain }) => {
const retType = codomain.tag === 'nil'
? 'void'
: codomain.items.length === 1
? typeToTs(codomain.items[0])
: typeToTs(codomain);
const args = getFuncArgs(domain).map(([name, type]) => ([name, typeToTs(type)]));
P.union(
"u8",
"u16",
"u32",
"u64",
"i8",
"i16",
"i32",
"i64",
"f32",
"f64",
),
() => {
return "number";
},
)
.with("bool", () => {
return "boolean";
})
.with("string", () => {
return "string";
})
.with(P._, () => {
return "any";
})
.exhaustive();
})
.with({ tag: "array" }, ({ type }) => {
return typeToTs(type) + "[]";
})
.with({ tag: "struct" }, ({ fields }) => {
return `{ ${Object.entries(fields)
.map(([field, type]) => {
return `${field}: ${typeToTs(type)};`;
})
.join(" ")} }`;
})
.with({ tag: "labeledProduct" }, ({ fields }) => {
return `{ ${Object.entries(fields)
.map(([field, type]) => {
return `${field}: ${typeToTs(type)};`;
})
.join(" ")} }`;
})
.with({ tag: "unlabeledProduct" }, ({ items }) => {
return `[${items
.map((item) => {
return typeToTs(item);
})
.join(", ")}]`;
})
.with({ tag: "arrow" }, ({ domain, codomain }) => {
const retType =
codomain.tag === "nil"
? "void"
: codomain.items.length === 1
? typeToTs(codomain.items[0])
: typeToTs(codomain);
const generic = args.length === 0 ? 'null' : args.map(([name]) => `'${name}'`).join(' | ');
args.push(['callParams', `CallParams$$<${generic}>`]);
const args = getFuncArgs(domain).map(([name, type]) => {
return [name, typeToTs(type)];
});
const funcArgs = args.map(([name, type]) => `${name}: ${type}`).join(', ');
const generic =
args.length === 0
? "null"
: args
.map(([name]) => {
return `'${name}'`;
})
.join(" | ");
return `(${funcArgs}) => ${retType} | Promise<${retType}>`;
}
).with(
{ tag: 'topType' },
() => 'unknown'
).with(
{ tag: 'bottomType' },
() => 'never'
).exhaustive();
}
args.push(["callParams", `CallParams$$<${generic}>`]);
const funcArgs = args
.map(([name, type]) => {
return `${name}: ${type}`;
})
.join(", ");
return `(${funcArgs}) => ${retType} | Promise<${retType}>`;
})
.with({ tag: "topType" }, () => {
return "unknown";
})
.with({ tag: "bottomType" }, () => {
return "never";
})
.exhaustive();
}

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,4 +14,4 @@
* limitations under the License.
*/
export const CLIENT = 'IFluenceClient$$';
export const CLIENT = "IFluenceClient$$";

View File

@ -1,61 +0,0 @@
/*
* 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 {
ArrayType,
BottomType, LabeledProductType,
NilType,
NonArrowType,
OptionType,
ScalarType,
StructType,
TopType, UnlabeledProductType
} from '@fluencelabs/interfaces';
// Type definitions for inferring ts types from air json definition
// In the future we may remove string type declaration and move to type inference.
type GetTsTypeFromScalar<T extends ScalarType> = T['name'] extends 'u8' | 'u16' | 'u32' | 'u64' | 'i8' | 'i16' | 'i32' | 'i64' | 'f32' | 'f64'
? number
: T['name'] extends 'bool'
? boolean
: T['name'] extends 'string'
? string
: never;
type MapTuple<T> = { [K in keyof T]: T[K] extends NonArrowType ? GetTsType<T[K]> : never }
type GetTsType<T extends NonArrowType> = T extends NilType
? null
: T extends ArrayType
? GetTsType<T['type']>[]
: T extends StructType
? { [K in keyof T]: GetTsType<T> }
: T extends OptionType
? GetTsType<T['type']> | null
: T extends ScalarType
? GetTsTypeFromScalar<T>
: T extends TopType
? unknown
: T extends BottomType
? never
: T extends Exclude<UnlabeledProductType<infer H>, NilType>
? MapTuple<H>
: T extends Exclude<LabeledProductType<infer H>, NilType>
? H extends NonArrowType
? { [K in keyof T['fields']]: GetTsType<H> }
: never
: never;

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,36 +14,40 @@
* limitations under the License.
*/
import { describe, expect, it } from 'vitest';
import { generateTypes, generateSources } from '../index.js';
import { compileFromPath } from '@fluencelabs/aqua-api';
import url from 'url';
import { getPackageJsonContent, PackageJson } from '../../utils.js';
import url from "url";
describe('Aqua to js/ts compiler', () => {
it('compiles smoke tests successfully', async () => {
const res = await compileFromPath({
filePath: url.fileURLToPath(new URL('./sources/smoke_test.aqua', import.meta.url)),
imports: ['./node_modules'],
targetType: 'air'
});
const pkg: PackageJson = {
...(await getPackageJsonContent()),
version: '0.0.0',
devDependencies: {
'@fluencelabs/aqua-api': '0.0.0'
},
};
const jsResult = await generateSources(res, 'js', pkg);
const jsTypes = await generateTypes(res, pkg);
expect(jsResult).toMatchSnapshot();
expect(jsTypes).toMatchSnapshot();
import { compileFromPath } from "@fluencelabs/aqua-api";
import { describe, expect, it } from "vitest";
const tsResult = await generateSources(res, 'ts', pkg);
import { getPackageJsonContent, PackageJson } from "../../utils.js";
import { generateTypes, generateSources } from "../index.js";
expect(tsResult).toMatchSnapshot();
describe("Aqua to js/ts compiler", () => {
it("compiles smoke tests successfully", async () => {
const res = await compileFromPath({
filePath: url.fileURLToPath(
new URL("./sources/smoke_test.aqua", import.meta.url),
),
imports: ["./node_modules"],
targetType: "air",
});
});
const pkg: PackageJson = {
...(await getPackageJsonContent()),
version: "0.0.0",
devDependencies: {
"@fluencelabs/aqua-api": "0.0.0",
},
};
const jsResult = generateSources(res, "js", pkg);
const jsTypes = generateTypes(res, pkg);
expect(jsResult).toMatchSnapshot();
expect(jsTypes).toMatchSnapshot();
const tsResult = generateSources(res, "ts", pkg);
expect(tsResult).toMatchSnapshot();
});
});

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,24 +14,38 @@
* limitations under the License.
*/
import { recursiveRenameLaquaProps } from '../utils.js';
import { AquaFunction, TypeGenerator } from './interfaces.js';
import { recursiveRenameLaquaProps } from "../utils.js";
export function generateFunctions(typeGenerator: TypeGenerator, functions: Record<string, AquaFunction>) {
return Object.values(functions).map(func => generateFunction(typeGenerator, func)).join('\n\n');
import { AquaFunction, TypeGenerator } from "./interfaces.js";
export function generateFunctions(
typeGenerator: TypeGenerator,
functions: Record<string, AquaFunction>,
) {
return Object.values(functions)
.map((func) => {
return generateFunction(typeGenerator, func);
})
.join("\n\n");
}
type DeepToType<T> = { [K in keyof T]: DeepToType<T[K]> };
function generateFunction(typeGenerator: TypeGenerator, func: AquaFunction) {
const scriptConstName = func.funcDef.functionName + '_script';
return `export const ${scriptConstName} = \`
const funcDef: DeepToType<typeof func.funcDef> = func.funcDef;
const scriptConstName = func.funcDef.functionName + "_script";
return `export const ${scriptConstName} = \`
${func.script}\`;
${typeGenerator.funcType(func)}
export function ${func.funcDef.functionName}(${typeGenerator.type('...args', 'any[]')}) {
export function ${func.funcDef.functionName}(${typeGenerator.type(
"...args",
"any[]",
)}) {
return callFunction$$(
args,
${JSON.stringify(recursiveRenameLaquaProps(func.funcDef), null, 4)},
${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)},
${scriptConstName}
);
}`
}
}`;
}

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,25 +14,33 @@
* limitations under the License.
*/
import { OutputType } from './interfaces.js';
import { PackageJson } from '../utils.js';
import { PackageJson } from "../utils.js";
export default function generateHeader({ version, devDependencies }: PackageJson, outputType: OutputType) {
return `/* eslint-disable */
import { OutputType } from "./interfaces.js";
export default function generateHeader(
{ version, devDependencies }: PackageJson,
outputType: OutputType,
) {
return `/* eslint-disable */
// @ts-nocheck
/**
*
* This file is generated using:
* @fluencelabs/aqua-api version: ${devDependencies['@fluencelabs/aqua-api']}
* @fluencelabs/aqua-api version: ${devDependencies["@fluencelabs/aqua-api"]}
* @fluencelabs/aqua-to-js version: ${version}
* 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
*
*/
${outputType === 'ts' ? 'import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from \'@fluencelabs/js-client\';' : ''}
${
outputType === "ts"
? "import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client';"
: ""
}
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$,
} from '@fluencelabs/js-client';`;
}
}

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,46 +14,80 @@
* limitations under the License.
*/
import { CompilationResult, JSTypeGenerator, OutputType, TSTypeGenerator, TypeGenerator } from './interfaces.js';
import { PackageJson } from '../utils.js';
import { generateServices } from './service.js';
import { generateFunctions } from './function.js';
import header from './header.js';
import { PackageJson } from "../utils.js";
const typeGenerators: Record<OutputType, TypeGenerator> = {
'js': new JSTypeGenerator(),
'ts': new TSTypeGenerator()
import { generateFunctions } from "./function.js";
import header from "./header.js";
import {
CompilationResult,
JSTypeGenerator,
OutputType,
TSTypeGenerator,
TypeGenerator,
} from "./interfaces.js";
import { generateServices } from "./service.js";
const typeGenerators: Record<OutputType, TypeGenerator> = {
js: new JSTypeGenerator(),
ts: new TSTypeGenerator(),
};
export async function generateSources({ services, functions }: CompilationResult, outputType: OutputType, packageJson: PackageJson) {
const typeGenerator = typeGenerators[outputType];
return `${header(packageJson, outputType)}
export function generateSources(
{ services, functions }: CompilationResult,
outputType: OutputType,
packageJson: PackageJson,
) {
const typeGenerator = typeGenerators[outputType];
return `${header(packageJson, outputType)}
${Object.entries(services).length > 0 ? `// Services
${
Object.entries(services).length > 0
? `// Services
${generateServices(typeGenerator, services)}
` : ''}
${Object.entries(functions).length > 0 ? `// Functions
`
: ""
}
${
Object.entries(functions).length > 0
? `// Functions
${generateFunctions(typeGenerator, functions)}
`: ''}`
`
: ""
}`;
}
export async function generateTypes({ services, functions }: CompilationResult, packageJson: PackageJson) {
const typeGenerator = typeGenerators['ts'];
const generatedServices = Object.entries(services)
.map(([srvName, srvDef]) => typeGenerator.serviceType(srvName, srvDef))
.join('\n');
const generatedFunctions = Object.entries(functions)
.map(([funcName, funcDef]) => typeGenerator.funcType(funcDef))
.join('\n');
return `${header(packageJson, 'ts')}
export function generateTypes(
{ services, functions }: CompilationResult,
packageJson: PackageJson,
) {
const typeGenerator = typeGenerators["ts"];
${Object.entries(services).length > 0 ? `// Services
const generatedServices = Object.entries(services)
.map(([srvName, srvDef]) => {
return typeGenerator.serviceType(srvName, srvDef);
})
.join("\n");
const generatedFunctions = Object.entries(functions)
.map(([, funcDef]) => {
return typeGenerator.funcType(funcDef);
})
.join("\n");
return `${header(packageJson, "ts")}
${
Object.entries(services).length > 0
? `// Services
${generatedServices}
` : ''}
${Object.entries(functions).length > 0 ? `// Functions
`
: ""
}
${
Object.entries(functions).length > 0
? `// Functions
${generatedFunctions}
`: ''}`
}
`
: ""
}`;
}

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,123 +14,160 @@
* limitations under the License.
*/
import { CLIENT } from '../constants.js';
import { FunctionCallDef, ServiceDef } from '@fluencelabs/interfaces';
import { genTypeName, typeToTs } from '../common.js';
import { capitalize, getFuncArgs } from '../utils.js';
import { FunctionCallDef, ServiceDef } from "@fluencelabs/interfaces";
import { genTypeName, typeToTs } from "../common.js";
import { CLIENT } from "../constants.js";
import { capitalize, getFuncArgs } from "../utils.js";
export interface TypeGenerator {
type(field: string, type: string): string;
generic(field: string, type: string): string;
bang(field: string): string;
funcType(funcDef: AquaFunction): string;
serviceType(srvName: string, srvDef: ServiceDef): string;
type(field: string, type: string): string;
generic(field: string, type: string): string;
bang(field: string): string;
funcType(funcDef: AquaFunction): string;
serviceType(srvName: string, srvDef: ServiceDef): string;
}
export class TSTypeGenerator implements TypeGenerator {
bang(field: string): string {
return `${field}!`;
}
bang(field: string): string {
return `${field}!`;
}
generic(field: string, type: string): string {
return `${field}<${type}>`;
}
generic(field: string, type: string): string {
return `${field}<${type}>`;
}
type(field: string, type: string): string {
return `${field}: ${type}`;
}
type(field: string, type: string): string {
return `${field}: ${type}`;
}
funcType({ funcDef }: AquaFunction): string {
const args = getFuncArgs(funcDef.arrow.domain).map(([name, type]) => {
const [typeDesc, t] = genTypeName(type, capitalize(funcDef.functionName) + 'Arg' + capitalize(name));
return [typeDesc, `${name}: ${t}`] as const;
});
args.push([undefined, `config?: {ttl?: number}`]);
funcType({ funcDef }: AquaFunction): string {
const args = getFuncArgs(funcDef.arrow.domain).map(([name, type]) => {
const [typeDesc, t] = genTypeName(
type,
capitalize(funcDef.functionName) + "Arg" + capitalize(name),
);
const argsDefs = args.map(([, def]) => " " + def);
const argsDesc = args.filter(([desc]) => desc !== undefined).map(([desc]) => desc);
return [typeDesc, `${name}: ${t}`] as const;
});
const functionOverloads = [
argsDefs.join(',\n'),
[` peer: ${CLIENT}`, ...argsDefs].join(',\n')
];
const [resTypeDesc, resType] = genTypeName(funcDef.arrow.codomain, capitalize(funcDef.functionName) + "Result");
args.push([undefined, `config?: {ttl?: number}`]);
return [
argsDesc.join('\n'),
resTypeDesc || "",
functionOverloads.flatMap(fo => [
`export function ${funcDef.functionName}(`,
fo,
`): Promise<${resType}>;`,
''
]).join('\n')
].filter(s => s !== '').join('\n\n');
}
const argsDefs = args.map(([, def]) => {
return " " + def;
});
serviceType(srvName: string, srvDef: ServiceDef): string {
const members = srvDef.functions.tag === 'nil' ? [] : Object.entries(srvDef.functions.fields);
const argsDesc = args
.filter(([desc]) => {
return desc !== undefined;
})
.map(([desc]) => {
return desc;
});
const interfaceDefs = members
.map(([name, arrow]) => {
return ` ${name}: ${typeToTs(arrow)};`;
})
.join('\n');
const functionOverloads = [
argsDefs.join(",\n"),
[` peer: ${CLIENT}`, ...argsDefs].join(",\n"),
];
const interfaces = [`export interface ${srvName}Def {`, interfaceDefs, '}'].join('\n');
const peerDecl = `peer: ${CLIENT}`;
const serviceDecl = `service: ${srvName}Def`;
const serviceIdDecl = `serviceId: string`;
const registerServiceArgs = [
[serviceDecl],
[serviceIdDecl, serviceDecl],
[peerDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl]
];
const [resTypeDesc, resType] = genTypeName(
funcDef.arrow.codomain,
capitalize(funcDef.functionName) + "Result",
);
return [interfaces, ...registerServiceArgs.map(registerServiceArg => {
const args = registerServiceArg.join(', ');
return `export function register${srvName}(${args}): void;`
})].join('\n');
}
return [
argsDesc.join("\n"),
resTypeDesc ?? "",
functionOverloads
.flatMap((fo) => {
return [
`export function ${funcDef.functionName}(`,
fo,
`): Promise<${resType}>;`,
"",
];
})
.join("\n"),
]
.filter((s) => {
return s !== "";
})
.join("\n\n");
}
serviceType(srvName: string, srvDef: ServiceDef): string {
const members =
srvDef.functions.tag === "nil"
? []
: Object.entries(srvDef.functions.fields);
const interfaceDefs = members
.map(([name, arrow]) => {
return ` ${name}: ${typeToTs(arrow)};`;
})
.join("\n");
const interfaces = [
`export interface ${srvName}Def {`,
interfaceDefs,
"}",
].join("\n");
const peerDecl = `peer: ${CLIENT}`;
const serviceDecl = `service: ${srvName}Def`;
const serviceIdDecl = `serviceId: string`;
const registerServiceArgs = [
[serviceDecl],
[serviceIdDecl, serviceDecl],
[peerDecl, serviceDecl],
[peerDecl, serviceIdDecl, serviceDecl],
];
return [
interfaces,
...registerServiceArgs.map((registerServiceArg) => {
const args = registerServiceArg.join(", ");
return `export function register${srvName}(${args}): void;`;
}),
].join("\n");
}
}
export class JSTypeGenerator implements TypeGenerator {
bang(field: string): string {
return field;
}
bang(field: string): string {
return field;
}
generic(field: string, type: string): string {
return field;
}
generic(field: string): string {
return field;
}
type(field: string, type: string): string {
return field;
}
type(field: string): string {
return field;
}
funcType(): string {
return '';
}
funcType(): string {
return "";
}
serviceType(): string {
return '';
}
serviceType(): string {
return "";
}
}
export interface AquaFunction {
funcDef: FunctionCallDef;
script: string;
funcDef: FunctionCallDef;
script: string;
}
export interface CompilationResult {
services: Record<string, ServiceDef>;
functions: Record<string, AquaFunction>;
services: Record<string, ServiceDef>;
functions: Record<string, AquaFunction>;
}
export interface EntityGenerator {
generate(compilationResult: CompilationResult): string;
generate(compilationResult: CompilationResult): string;
}
export type OutputType = 'js' | 'ts';
export type OutputType = "js" | "ts";

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,45 +14,74 @@
* limitations under the License.
*/
import { ServiceDef } from '@fluencelabs/interfaces';
import { recursiveRenameLaquaProps } from '../utils.js';
import { TypeGenerator } from './interfaces.js';
import { ServiceDef } from "@fluencelabs/interfaces";
import { recursiveRenameLaquaProps } from "../utils.js";
import { TypeGenerator } from "./interfaces.js";
interface DefaultServiceId {
s_Some__f_value?: string
s_Some__f_value?: string;
}
export function generateServices(typeGenerator: TypeGenerator, services: Record<string, ServiceDef>) {
const generated = Object.entries(services).map(([srvName, srvDef]) => generateService(typeGenerator, srvName, srvDef)).join('\n\n');
export function generateServices(
typeGenerator: TypeGenerator,
services: Record<string, ServiceDef>,
) {
const generated = Object.entries(services)
.map(([srvName, srvDef]) => {
return generateService(typeGenerator, srvName, srvDef);
})
.join("\n\n");
return generated + '\n';
return generated + "\n";
}
function generateService(typeGenerator: TypeGenerator, srvName: string, srvDef: ServiceDef) {
return [
typeGenerator.serviceType(srvName, srvDef),
generateRegisterServiceOverload(typeGenerator, srvName, srvDef)
].join('\n');
function generateService(
typeGenerator: TypeGenerator,
srvName: string,
srvDef: ServiceDef,
) {
return [
typeGenerator.serviceType(srvName, srvDef),
generateRegisterServiceOverload(typeGenerator, srvName, srvDef),
].join("\n");
}
function generateRegisterServiceOverload(typeGenerator: TypeGenerator, srvName: string, srvDef: ServiceDef) {
return [
`export function register${srvName}(${typeGenerator.type('...args', 'any[]')}) {`,
' registerService$$(',
' args,',
` ${serviceToJson(srvDef)}`,
' );',
'}'
].join('\n');
function generateRegisterServiceOverload(
typeGenerator: TypeGenerator,
srvName: string,
srvDef: ServiceDef,
) {
return [
`export function register${srvName}(${typeGenerator.type(
"...args",
"any[]",
)}) {`,
" registerService$$(",
" args,",
` ${serviceToJson(srvDef)}`,
" );",
"}",
].join("\n");
}
function serviceToJson(service: ServiceDef): string {
return JSON.stringify({
...(
(service.defaultServiceId as DefaultServiceId)?.s_Some__f_value
? { defaultServiceId: (service.defaultServiceId as DefaultServiceId).s_Some__f_value }
: {}
),
functions: recursiveRenameLaquaProps(service.functions)
}, null, 4);
}
return JSON.stringify(
{
// This assertion is required because aqua-api gives bad types
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
...((service.defaultServiceId as DefaultServiceId).s_Some__f_value != null
? {
defaultServiceId:
// This assertion is required because aqua-api gives bad types
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
(service.defaultServiceId as DefaultServiceId).s_Some__f_value,
}
: {}),
functions: recursiveRenameLaquaProps(service.functions),
},
null,
4,
);
}

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,34 +14,47 @@
* limitations under the License.
*/
import {
generateSources,
generateTypes,
} from './generate/index.js';
import { CompilationResult, OutputType } from './generate/interfaces.js';
import { getPackageJsonContent } from './utils.js';
import { generateSources, generateTypes } from "./generate/index.js";
import { CompilationResult, OutputType } from "./generate/interfaces.js";
import { getPackageJsonContent } from "./utils.js";
interface JsOutput {
sources: string;
types: string;
sources: string;
types: string;
}
interface TsOutput {
sources: string;
sources: string;
}
type LanguageOutput = {
"js": JsOutput,
"ts": TsOutput
js: JsOutput;
ts: TsOutput;
};
export default async function aquaToJs<T extends OutputType>(res: CompilationResult, outputType: T): Promise<LanguageOutput[T]> {
const packageJson = await getPackageJsonContent();
return outputType === 'js' ? {
sources: await generateSources(res, 'js', packageJson),
types: await generateTypes(res, packageJson)
} : {
sources: await generateSources(res, 'ts', packageJson),
} as LanguageOutput[T];
};
type NothingToGenerate = null;
export default async function aquaToJs<T extends OutputType>(
res: CompilationResult,
outputType: T,
): Promise<LanguageOutput[T] | NothingToGenerate> {
if (
Object.keys(res.services).length === 0 &&
Object.keys(res.functions).length === 0
) {
return null;
}
const packageJson = await getPackageJsonContent();
return outputType === "js"
? {
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
({
sources: generateSources(res, "ts", packageJson),
} as LanguageOutput[T]);
}

View File

@ -1,4 +1,4 @@
/*
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,57 +14,94 @@
* limitations under the License.
*/
import { ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces';
import { readFile } from 'fs/promises';
import path from 'path';
import assert from "assert";
import { readFile } from "fs/promises";
import path from "path";
import {
ArrowType,
ArrowWithoutCallbacks,
JSONValue,
LabeledProductType,
NilType,
SimpleTypes,
UnlabeledProductType,
} from "@fluencelabs/interfaces";
export interface PackageJson {
name: string;
version: string;
devDependencies: {
['@fluencelabs/aqua-api']: string
}
name: string;
version: string;
devDependencies: {
["@fluencelabs/aqua-api"]: string;
};
}
export async function getPackageJsonContent(): Promise<PackageJson> {
const content = await readFile(new URL(path.join('..', 'package.json'), import.meta.url), 'utf-8');
return JSON.parse(content);
const content = await readFile(
new URL(path.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;
}
export function getFuncArgs(domain: ProductType<NonArrowType | ArrowWithoutCallbacks>): [string, NonArrowType | ArrowWithoutCallbacks][] {
if (domain.tag === 'labeledProduct') {
return Object.entries(domain.fields).map(([label, type]) => [label, type]);
} else if (domain.tag === 'unlabeledProduct') {
return domain.items.map((type, index) => ['arg' + index, type]);
} else {
return [];
}
export function getFuncArgs(
domain:
| LabeledProductType<SimpleTypes | ArrowType<UnlabeledProductType>>
| UnlabeledProductType
| NilType,
): [string, SimpleTypes | ArrowWithoutCallbacks][] {
if (domain.tag === "labeledProduct") {
return Object.entries(domain.fields).map(([label, type]) => {
return [label, type];
});
} else if (domain.tag === "unlabeledProduct") {
return domain.items.map((type, index) => {
return ["arg" + index, type];
});
} else {
return [];
}
}
export function recursiveRenameLaquaProps(obj: unknown): unknown {
if (typeof obj !== 'object' || obj === null) return obj;
export function recursiveRenameLaquaProps(obj: JSONValue): unknown {
if (typeof obj !== "object" || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => recursiveRenameLaquaProps(item));
if (Array.isArray(obj)) {
return obj.map((item) => {
return recursiveRenameLaquaProps(item);
});
}
return Object.getOwnPropertyNames(obj).reduce((acc, prop) => {
let accessProp = prop;
if (prop.includes("Laqua_js")) {
// Last part of the property separated by "_" is a correct name
const refinedProperty = prop.split("_").pop();
if (refinedProperty == null) {
throw new Error(`Bad property name: ${prop}.`);
}
if (refinedProperty in obj) {
accessProp = refinedProperty;
}
}
return Object.getOwnPropertyNames(obj).reduce((acc, prop) => {
let accessProp = prop;
if (prop.includes('Laqua_js')) {
// Last part of the property separated by "_" is a correct name
const refinedProperty = prop.split('_').pop()!;
if (refinedProperty in obj) {
accessProp = refinedProperty;
}
}
assert(accessProp in obj);
return {
...acc,
[accessProp]: recursiveRenameLaquaProps(obj[accessProp as keyof typeof obj])
};
}, {});
return {
...acc,
[accessProp]: recursiveRenameLaquaProps(obj[accessProp]),
};
}, {});
}
export function capitalize(str: string) {
return str.slice(0, 1).toUpperCase() + str.slice(1);
}
return str.slice(0, 1).toUpperCase() + str.slice(1);
}

View File

@ -1,10 +1,10 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/**/__test__"],
"extends": "../../../tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/**/__test__"]
}