feat(aqua-compiler)!: JS-client aqua wrapper [fixes DXJ-461] (#347)

* Implement wrapper compiler

* Small formatting fixes

* Add simple test

* Package rename

* Add new package to release-please

* Misc fixes

* Fix object type

* Update package name

* Revert "Fix object type"

This reverts commit 572f1e10e2.

* Set string type

* Make generator func async

* Cleanup

* Code refactoring

* Fix PR comments

* Fix file path

* Refactor generate module

* Change header text

* Rename package

* Move some deps to devDeps

* Add a comment

* New index file

* Move aqua-api to devDeps

* Add desc

* Fix header

* Change return type

* Fix all tests

* Fix func

* Fix index file

* Add file entry

* Change package name (again)

* Snapshot testing

* Add func name

* Fix return type

* Fix nox images

* Update aurora image

* Conditional services

* Use function instead classes

* Refactor header

* Import same type

* Make named export

* Type generator array
This commit is contained in:
Akim 2023-09-21 15:32:27 +07:00 committed by GitHub
parent 580aff0042
commit 7fff3b1c03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 2600 additions and 38 deletions

View File

@ -7,7 +7,7 @@ networks:
services:
aurora:
image: docker.fluence.dev/aurora:main-9e7523f-4-1
image: docker.fluence.dev/aurora:0.2.11
ports:
- 8545:8545
networks:

View File

@ -9,6 +9,7 @@
],
"packages": {
"packages/core/js-client": {},
"packages/core/marine-worker": {}
"packages/core/marine-worker": {},
"packages/core/aqua-to-js": {}
}
}

View File

@ -1,4 +1,5 @@
{
"packages/core/js-client": "0.1.6",
"packages/core/marine-worker": "0.3.2"
"packages/core/marine-worker": "0.3.2",
"packages/core/aqua-to-js": "0.0.0"
}

View File

@ -43,7 +43,7 @@ jobs:
uses: fluencelabs/aqua/.github/workflows/tests.yml@main
with:
js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}"
nox-image: "docker.fluence.dev/nox:ipfs_renovate-air-interpreter-wasm-0-x_3158_2"
nox-image: "fluencelabs/nox:unstable_minimal"
flox:
needs:
- js-client
@ -51,4 +51,4 @@ jobs:
uses: fluencelabs/flox/.github/workflows/tests.yml@main
with:
js-client-snapshots: "${{ needs.js-client.outputs.js-client-snapshots }}"
nox-image: "docker.fluence.dev/nox:ipfs_renovate-air-interpreter-wasm-0-x_3158_2"
nox-image: "fluencelabs/nox:unstable_minimal"

View File

@ -28,4 +28,4 @@ jobs:
uses: ./.github/workflows/tests.yml
with:
ref: ${{ github.ref }}
nox-image: "docker.fluence.dev/nox:ipfs_renovate-air-interpreter-wasm-0-x_3135_1"
nox-image: "fluencelabs/nox:unstable_minimal"

View File

@ -0,0 +1,29 @@
{
"name": "@fluencelabs/aqua-to-js",
"type": "module",
"version": "0.0.0",
"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"
}
}

View File

@ -0,0 +1,98 @@
/*
* 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 { ArrowType, ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces';
import { match, P } from 'ts-pattern';
import { getFuncArgs } from './utils.js';
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);
}
return [`export type ${name} = ${genType}`, name] as const;
},
).otherwise(() => [undefined, genType] as const);
}
export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks | ProductType<NonArrowType>): string {
return match(t)
.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)]));
const generic = args.length === 0 ? 'null' : args.map(([name]) => `'${name}'`).join(' | ');
args.push(['callParams', `CallParams$$<${generic}>`]);
const funcArgs = args.map(([name, type]) => `${name}: ${type}`).join(', ');
return `(${funcArgs}) => ${retType} | Promise<${retType}>`;
}
).with(
{ tag: 'topType' },
() => 'unknown'
).with(
{ tag: 'bottomType' },
() => 'never'
).exhaustive();
}

View File

@ -0,0 +1,17 @@
/*
* 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.
*/
export const CLIENT = 'IFluenceClient$$';

View File

@ -0,0 +1,61 @@
/*
* 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

@ -0,0 +1,49 @@
/*
* 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 { 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';
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();
const tsResult = await generateSources(res, 'ts', pkg);
expect(tsResult).toMatchSnapshot();
});
});

View File

@ -0,0 +1,55 @@
import "@fluencelabs/registry/resources-api.aqua"
service HelloWorld("hello-world"):
hello(str: string) -> string
func resourceTest(label: string) -> ?string, *string:
res, errors <- createResource(label)
<- res, errors
func helloTest() -> string:
hello <- HelloWorld.hello("Fluence user")
<- hello
service CalcService:
add(num: f64) -> f64
clear_state()
divide(num: f64) -> f64
multiply(num: f64) -> f64
state() -> f64
subtract(num: f64) -> f64
test_logs()
data ServiceCreationResult:
success: bool
service_id: ?string
error: ?string
data RemoveResult:
success: bool
error: ?string
alias ListServiceResult: []string
service Srv("single_module_srv"):
create(wasm_b64_content: string) -> ServiceCreationResult
remove(service_id: string) -> RemoveResult
list() -> ListServiceResult
func demo_calculation(service_id: string) -> f64:
CalcService service_id
CalcService.test_logs()
CalcService.add(10)
CalcService.multiply(5)
CalcService.subtract(8)
CalcService.divide(6)
res <- CalcService.state()
<- res
func marineTest(wasm64: string) -> f64:
serviceResult <- Srv.create(wasm64)
res <- demo_calculation(serviceResult.service_id!)
<- res

View File

@ -0,0 +1,37 @@
/*
* 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 { recursiveRenameLaquaProps } from '../utils.js';
import { AquaFunction, TypeGenerator } from './interfaces.js';
export function generateFunctions(typeGenerator: TypeGenerator, functions: Record<string, AquaFunction>) {
return Object.values(functions).map(func => generateFunction(typeGenerator, func)).join('\n\n');
}
function generateFunction(typeGenerator: TypeGenerator, func: AquaFunction) {
const scriptConstName = func.funcDef.functionName + '_script';
return `export const ${scriptConstName} = \`
${func.script}\`;
${typeGenerator.funcType(func)}
export function ${func.funcDef.functionName}(${typeGenerator.type('...args', 'any[]')}) {
return callFunction$$(
args,
${JSON.stringify(recursiveRenameLaquaProps(func.funcDef), null, 4)},
${scriptConstName}
);
}`
}

View File

@ -0,0 +1,38 @@
/*
* 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 { OutputType } from './interfaces.js';
import { PackageJson } from '../utils.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-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\';' : ''}
import {
v5_callFunction as callFunction$$,
v5_registerService as registerService$$,
} from '@fluencelabs/js-client';`;
}

View File

@ -0,0 +1,59 @@
/*
* 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 { 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';
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)}
${Object.entries(services).length > 0 ? `// Services
${generateServices(typeGenerator, services)}
` : ''}
${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')}
${Object.entries(services).length > 0 ? `// Services
${generatedServices}
` : ''}
${Object.entries(functions).length > 0 ? `// Functions
${generatedFunctions}
`: ''}`
}

View File

@ -0,0 +1,136 @@
/*
* 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 { CLIENT } from '../constants.js';
import { FunctionCallDef, ServiceDef } from '@fluencelabs/interfaces';
import { genTypeName, typeToTs } from '../common.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;
}
export class TSTypeGenerator implements TypeGenerator {
bang(field: string): string {
return `${field}!`;
}
generic(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}`]);
const argsDefs = args.map(([, def]) => " " + def);
const argsDesc = args.filter(([desc]) => desc !== undefined).map(([desc]) => desc);
const functionOverloads = [
argsDefs.join(',\n'),
[` peer: ${CLIENT}`, ...argsDefs].join(',\n')
];
const [resTypeDesc, resType] = genTypeName(funcDef.arrow.codomain, capitalize(funcDef.functionName) + "Result");
return [
argsDesc.join('\n'),
resTypeDesc || "",
functionOverloads.flatMap(fo => [
`export function ${funcDef.functionName}(`,
fo,
`): Promise<${resType}>;`,
''
]).join('\n')
].filter(s => 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;
}
generic(field: string, type: string): string {
return field;
}
type(field: string, type: string): string {
return field;
}
funcType(): string {
return '';
}
serviceType(): string {
return '';
}
}
export interface AquaFunction {
funcDef: FunctionCallDef;
script: string;
}
export interface CompilationResult {
services: Record<string, ServiceDef>;
functions: Record<string, AquaFunction>;
}
export interface EntityGenerator {
generate(compilationResult: CompilationResult): string;
}
export type OutputType = 'js' | 'ts';

View File

@ -0,0 +1,58 @@
/*
* 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 { ServiceDef } from '@fluencelabs/interfaces';
import { recursiveRenameLaquaProps } from '../utils.js';
import { TypeGenerator } from './interfaces.js';
interface DefaultServiceId {
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');
return generated + '\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 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);
}

View File

@ -0,0 +1,47 @@
/*
* 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 {
generateSources,
generateTypes,
} from './generate/index.js';
import { CompilationResult, OutputType } from './generate/interfaces.js';
import { getPackageJsonContent } from './utils.js';
interface JsOutput {
sources: string;
types: string;
}
interface TsOutput {
sources: string;
}
type LanguageOutput = {
"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];
};

View File

@ -0,0 +1,70 @@
/*
* 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 { ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces';
import { readFile } from 'fs/promises';
import path from 'path';
export interface PackageJson {
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);
}
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 recursiveRenameLaquaProps(obj: unknown): unknown {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) {
return obj.map(item => 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 in obj) {
accessProp = refinedProperty;
}
}
return {
...acc,
[accessProp]: recursiveRenameLaquaProps(obj[accessProp as keyof typeof obj])
};
}, {});
}
export function capitalize(str: string) {
return str.slice(0, 1).toUpperCase() + str.slice(1);
}

View File

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

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
type SomeNonArrowTypes = ScalarType | OptionType | ArrayType | StructType | TopType | BottomType;
type SimpleTypes = ScalarType | OptionType | ArrayType | StructType | TopType | BottomType | NilType;
export type NonArrowType = SomeNonArrowTypes | ProductType<SomeNonArrowTypes>;
export type NonArrowType = SimpleTypes | ProductType<SimpleTypes>;
export type TopType = {
/**
@ -108,35 +108,31 @@ export type StructType = {
fields: { [key: string]: NonArrowType };
};
export type LabeledProductType<T> =
| {
/**
* Type descriptor. Used for pattern-matching
*/
tag: 'labeledProduct';
export type LabeledProductType<T> = {
/**
* Type descriptor. Used for pattern-matching
*/
tag: 'labeledProduct';
/**
* Labelled product fields
*/
fields: { [key: string]: T };
}
| NilType;
/**
* Labelled product fields
*/
fields: { [key: string]: T };
};
export type UnlabeledProductType<T> =
| {
/**
* Type descriptor. Used for pattern-matching
*/
tag: 'unlabeledProduct';
export type UnlabeledProductType<T> = {
/**
* Type descriptor. Used for pattern-matching
*/
tag: 'unlabeledProduct';
/**
* Items in unlabelled product
*/
items: Array<T>;
}
| NilType;
/**
* Items in unlabelled product
*/
items: Array<T>;
};
export type ProductType<T> = UnlabeledProductType<T> | LabeledProductType<T>;
export type ProductType<T> = UnlabeledProductType<T> | LabeledProductType<T> | NilType;
/**
* ArrowType is a profunctor pointing its domain to codomain.
@ -238,7 +234,7 @@ export interface ServiceDef {
/**
* List of functions which the service consists of
*/
functions: LabeledProductType<ArrowWithoutCallbacks>;
functions: LabeledProductType<ArrowWithoutCallbacks> | NilType;
}
/**

111
pnpm-lock.yaml generated
View File

@ -126,6 +126,40 @@ importers:
specifier: 6.1.1
version: 6.1.1
packages/core/aqua-to-js:
dependencies:
ts-pattern:
specifier: 5.0.5
version: 5.0.5
devDependencies:
'@fluencelabs/aqua-api':
specifier: 0.12.0
version: 0.12.0
'@fluencelabs/aqua-lib':
specifier: 0.7.3
version: 0.7.3
'@fluencelabs/interfaces':
specifier: workspace:*
version: link:../interfaces
'@fluencelabs/js-client':
specifier: workspace:*
version: link:../js-client
'@fluencelabs/registry':
specifier: 0.8.7
version: 0.8.7
'@fluencelabs/spell':
specifier: 0.5.20
version: 0.5.20
'@fluencelabs/trust-graph':
specifier: 0.4.7
version: 0.4.7
typescript:
specifier: 5.1.6
version: 5.1.6
vitest:
specifier: 0.29.7
version: 0.29.7
packages/core/interfaces:
devDependencies:
'@fluencelabs/avm':
@ -1114,6 +1148,16 @@ packages:
'@babel/helper-plugin-utils': 7.22.5
dev: false
/@babel/plugin-syntax-flow@7.22.5(@babel/core@7.22.10):
resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.22.10
'@babel/helper-plugin-utils': 7.22.5
dev: false
/@babel/plugin-syntax-flow@7.22.5(@babel/core@7.22.5):
resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==}
engines: {node: '>=6.9.0'}
@ -1198,6 +1242,16 @@ packages:
'@babel/helper-plugin-utils': 7.22.5
dev: false
/@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.10):
resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.22.10
'@babel/helper-plugin-utils': 7.22.5
dev: false
/@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.5):
resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==}
engines: {node: '>=6.9.0'}
@ -2313,6 +2367,20 @@ packages:
'@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.5)
dev: false
/@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.22.10):
resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.22.10
'@babel/helper-annotate-as-pure': 7.22.5
'@babel/helper-module-imports': 7.22.5
'@babel/helper-plugin-utils': 7.22.5
'@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.10)
'@babel/types': 7.22.5
dev: false
/@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.22.5):
resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==}
engines: {node: '>=6.9.0'}
@ -3778,6 +3846,14 @@ packages:
resolution: {integrity: sha512-ifjtCM93KO3LhzPkMxqmXhwLmrg/scjOiyTihEVg7ns5N+BVzaK1eWzdOdqGdl9ZVoah43pdlQUepEo7VdRmsw==}
dev: true
/@fluencelabs/aqua-lib@0.7.0:
resolution: {integrity: sha512-mJEaxfAQb6ogVM4l4qw7INK6kvLA2Y161ErwL7IVeVSkKXIeYq/qio2p2au35LYvhBNsKc7XP2qc0uztCmxZzA==}
dev: true
/@fluencelabs/aqua-lib@0.7.3:
resolution: {integrity: sha512-+JVbWmHeGB+X/BSqmk6/B0gwWJ4bEAxkepVTN8l0mVrJ5zRRmYaCKVplWy6Z3W012m3VVK3A1o3rm/fgfVrQkw==}
dev: true
/@fluencelabs/aqua@0.9.1-374(jest@27.5.1)(node-fetch@3.3.2)(typescript@4.7.2):
resolution: {integrity: sha512-jF6oVE4h7bP/dQArKEfsy4UxbQbzACfVIBY/TFUL5D3np4ssjxrh15Y3gl1PwSWjlaPcDeFvAuStmcqfYQmLqQ==}
hasBin: true
@ -4063,6 +4139,29 @@ packages:
'@fluencelabs/trust-graph': 3.0.4
dev: true
/@fluencelabs/registry@0.8.7:
resolution: {integrity: sha512-43bmb1v4p5ORvaiLBrUAl+hRPo3luxxBVrJgqTvipJa2OEg2wCRA/Wo9s4M7Lchnv3NoYLOyNTzNyFopQRKILA==}
dependencies:
'@fluencelabs/aqua-lib': 0.7.0
'@fluencelabs/trust-graph': 0.4.1
dev: true
/@fluencelabs/spell@0.5.20:
resolution: {integrity: sha512-QFbknWwALLUWMzpWkFt34McuwTz9xwQuiPP1zXqhPqVZ1J6g8F3gwHHtzgHFW5Z7WrRmwsL+IQtFJy8YZubhDw==}
dev: true
/@fluencelabs/trust-graph@0.4.1:
resolution: {integrity: sha512-V/6ts4q/Y0uKMS6orVpPyxfdd99YFMkm9wN9U2IFtlBUWNsQZG369FK9qEizwsSRCqTchMHYs8Vh4wgZ2uRfuQ==}
dependencies:
'@fluencelabs/aqua-lib': 0.7.3
dev: true
/@fluencelabs/trust-graph@0.4.7:
resolution: {integrity: sha512-e4TxWimUh9GBWjqSO8WGsSqjZfyIs6f39/8Pzfo6PCcNoSf8FPaaO817Pw4FmAXYEKR1IalIUX3CDdym3NlHWw==}
dependencies:
'@fluencelabs/aqua-lib': 0.7.3
dev: true
/@fluencelabs/trust-graph@3.0.4:
resolution: {integrity: sha512-4CWe/dBuZwrj5iU6mTrLz5JCSy5v1fw7dYjD65Pz05xWAbLH2jw72YIJfbMX0utzb1qiM8CooXv1XKPgutCIHQ==}
dependencies:
@ -10416,8 +10515,8 @@ packages:
'@babel/plugin-transform-react-jsx': ^7.14.9
eslint: ^8.1.0
dependencies:
'@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.22.5)
'@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.5)
'@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.22.10)
'@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.10)
eslint: 8.43.0
lodash: 4.17.21
string-natural-compare: 3.0.1
@ -19542,7 +19641,7 @@ packages:
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.3
acorn: 8.9.0
acorn: 8.10.0
commander: 2.20.3
source-map-support: 0.5.21
dev: false
@ -19858,6 +19957,10 @@ packages:
/ts-pattern@3.3.3:
resolution: {integrity: sha512-Z5EFi6g6wyX3uDFHqxF5W5c5h663oZg9O6aOiAT7fqNu0HPSfCxtHzrQ7SblTy738Mrg2Ezorky8H5aUOm8Pvg==}
/ts-pattern@5.0.5:
resolution: {integrity: sha512-tL0w8U/pgaacOmkb9fRlYzWEUDCfVjjv9dD4wHTgZ61MjhuMt46VNWTG747NqW6vRzoWIKABVhFSOJ82FvXrfA==}
dev: false
/tsconfck@2.1.1(typescript@4.7.2):
resolution: {integrity: sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==}
engines: {node: ^14.13.1 || ^16 || >=18}
@ -20532,7 +20635,7 @@ packages:
'@vitest/runner': 0.29.7
'@vitest/spy': 0.29.7
'@vitest/utils': 0.29.7
acorn: 8.9.0
acorn: 8.10.0
acorn-walk: 8.2.0
cac: 6.7.14
chai: 4.3.7