mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
Combine js and ts backends (#307)
This commit is contained in:
parent
f27ae5eda9
commit
4e63da83f5
5
.github/workflows/test_branch.yml
vendored
5
.github/workflows/test_branch.yml
vendored
@ -51,13 +51,8 @@ jobs:
|
||||
run: |
|
||||
git clone https://github.com/fluencelabs/aqua-playground.git
|
||||
cd aqua-playground
|
||||
rm -rf src/compiled/examples/*
|
||||
npm i
|
||||
cd ..
|
||||
sbt "cli/run -i aqua-playground/aqua/examples -o aqua-playground/src/compiled/examples -m aqua-playground/node_modules -c \"UNIQUE_CONST = 1\" -c \"ANOTHER_CONST = \\\"ab\\\"\""
|
||||
cd aqua-playground
|
||||
npm run examples
|
||||
cd ..
|
||||
sbt "cliJS/fastOptJS"
|
||||
rm -rf aqua-playground/src/compiled/examples/*
|
||||
node cli/.js/target/scala-3.0.2/cli-fastopt.js -i aqua-playground/aqua/examples -o aqua-playground/src/compiled/examples -m aqua-playground/node_modules -c "UNIQUE_CONST = 1" -c "ANOTHER_CONST = \"ab\""
|
||||
|
@ -1,13 +0,0 @@
|
||||
package aqua.backend.js
|
||||
|
||||
import aqua.backend.{Backend, Generated}
|
||||
import aqua.model.transform.res.AquaRes
|
||||
import cats.data.NonEmptyChain
|
||||
|
||||
object JavaScriptBackend extends Backend {
|
||||
|
||||
val ext = ".js"
|
||||
|
||||
override def generate(res: AquaRes): Seq[Generated] =
|
||||
if (res.isEmpty) Nil else Generated(ext, JavaScriptFile(res).generate) :: Nil
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package aqua.backend.js
|
||||
|
||||
import aqua.backend.air.FuncAirGen
|
||||
import aqua.model.transform.res.FuncRes
|
||||
import aqua.types.*
|
||||
import cats.syntax.show.*
|
||||
|
||||
object JavaScriptCommon {
|
||||
|
||||
// TODO: handle cases if there is already peer_ or config_ variable defined
|
||||
def fixupArgName(arg: String): String =
|
||||
if (arg == "peer" || arg == "config") {
|
||||
arg + "_"
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
|
||||
def callBackExprBody(at: ArrowType, callbackName: String): String = {
|
||||
val arrowArgumentsToCallbackArgumentsList =
|
||||
at.domain.toList.zipWithIndex
|
||||
.map((`type`, idx) => {
|
||||
val valueFromArg = s"req.args[$idx]"
|
||||
`type` match {
|
||||
case OptionType(t) =>
|
||||
s"${valueFromArg}.length === 0 ? null : ${valueFromArg}[0]"
|
||||
case _ => valueFromArg
|
||||
}
|
||||
})
|
||||
.concat(List("callParams"))
|
||||
.mkString(", ")
|
||||
|
||||
val callCallbackStatement = s"$callbackName($arrowArgumentsToCallbackArgumentsList)"
|
||||
|
||||
val callCallbackStatementAndReturn =
|
||||
at.res.fold(s"${callCallbackStatement}; resp.result = {}")(`type` =>
|
||||
`type` match {
|
||||
case OptionType(t) => s"""
|
||||
| var respResult = ${callCallbackStatement};
|
||||
| resp.result = respResult === null ? [] : [respResult]
|
||||
|""".stripMargin
|
||||
case _ => s"resp.result = ${callCallbackStatement}"
|
||||
}
|
||||
)
|
||||
|
||||
val tetraplets = FuncRes
|
||||
.arrowArgs(at)
|
||||
.zipWithIndex
|
||||
.map((x, idx) => {
|
||||
s"${x.name}: req.tetraplets[${idx}]"
|
||||
})
|
||||
.mkString(",")
|
||||
|
||||
s"""
|
||||
| const callParams = {
|
||||
| ...req.particleContext,
|
||||
| tetraplets: {
|
||||
| ${tetraplets}
|
||||
| },
|
||||
| };
|
||||
| resp.retCode = ResultCodes.success;
|
||||
| ${callCallbackStatementAndReturn}
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package aqua.backend.js
|
||||
|
||||
import aqua.backend.Version
|
||||
import aqua.model.transform.res.AquaRes
|
||||
|
||||
case class JavaScriptFile(res: AquaRes) {
|
||||
|
||||
import JavaScriptFile.Header
|
||||
|
||||
def generate: String =
|
||||
s"""${Header}
|
||||
|
|
||||
|function missingFields(obj, fields) {
|
||||
| return fields.filter(f => !(f in obj))
|
||||
|}
|
||||
|
|
||||
|// Services
|
||||
|${res.services.map(JavaScriptService(_)).map(_.generate).toList.mkString("\n\n")}
|
||||
|
|
||||
|// Functions
|
||||
|${res.funcs.map(JavaScriptFunc(_)).map(_.generate).toList.mkString("\n\n")}
|
||||
|""".stripMargin
|
||||
|
||||
}
|
||||
|
||||
object JavaScriptFile {
|
||||
|
||||
val Header: String =
|
||||
s"""/**
|
||||
| *
|
||||
| * This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
| * Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
| * If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
| * Aqua version: ${Version.version}
|
||||
| *
|
||||
| */
|
||||
|import { Fluence, FluencePeer } from '@fluencelabs/fluence';
|
||||
|import {
|
||||
| ResultCodes,
|
||||
| RequestFlow,
|
||||
| RequestFlowBuilder,
|
||||
| CallParams,
|
||||
|} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1.js';
|
||||
|""".stripMargin
|
||||
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
package aqua.backend.js
|
||||
|
||||
import aqua.backend.air.FuncAirGen
|
||||
import aqua.model.transform.res.FuncRes
|
||||
import aqua.types.*
|
||||
import cats.syntax.show.*
|
||||
|
||||
case class JavaScriptFunc(func: FuncRes) {
|
||||
|
||||
import JavaScriptCommon._
|
||||
import FuncRes._
|
||||
import func._
|
||||
|
||||
private def returnCallback: String =
|
||||
val respBody = func.returnType match {
|
||||
case Some(x) => x match {
|
||||
case OptionType(_) =>
|
||||
""" let [opt] = args;
|
||||
| if (Array.isArray(opt)) {
|
||||
| if (opt.length === 0) { resolve(null); }
|
||||
| opt = opt[0];
|
||||
| }
|
||||
| return resolve(opt);""".stripMargin
|
||||
case pt: ProductType =>
|
||||
val unwrapOpts = pt.toList.zipWithIndex.collect { case (OptionType(_), i) =>
|
||||
s"""
|
||||
| if(Array.isArray(opt[$i])) {
|
||||
| if (opt[$i].length === 0) { opt[$i] = null; }
|
||||
| else {opt[$i] = opt[$i][0]; }
|
||||
| }""".stripMargin
|
||||
}.mkString
|
||||
|
||||
s""" let opt = args;
|
||||
|$unwrapOpts
|
||||
| return resolve(opt);""".stripMargin
|
||||
case _ =>
|
||||
""" const [res] = args;
|
||||
| resolve(res);""".stripMargin
|
||||
}
|
||||
case None => ""
|
||||
}
|
||||
s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => {
|
||||
| $respBody
|
||||
|});
|
||||
|""".stripMargin
|
||||
|
||||
def generate: String = {
|
||||
|
||||
val jsAir = FuncAirGen(func).generate
|
||||
|
||||
val setCallbacks = func.args.collect { // Product types are not handled
|
||||
case Arg(argName, OptionType(_)) =>
|
||||
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)} === null ? [] : [${fixupArgName(argName)}];});"""
|
||||
case Arg(argName, _: DataType) =>
|
||||
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)};});"""
|
||||
case Arg(argName, at: ArrowType) =>
|
||||
s"""
|
||||
| h.use((req, resp, next) => {
|
||||
| if(req.serviceId === '${conf.callbackService}' && req.fnName === '$argName') {
|
||||
| ${callBackExprBody(at, argName)}
|
||||
| }
|
||||
| next();
|
||||
| });
|
||||
""".stripMargin
|
||||
}
|
||||
.mkString("\n")
|
||||
|
||||
val returnVal =
|
||||
func.returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||
|
||||
val clientArgName = genArgName("client")
|
||||
val configArgName = genArgName("config")
|
||||
|
||||
val funcName = s"${func.funcName}"
|
||||
|
||||
val argsLets = args.map(arg => s"let ${fixupArgName(arg.name)};").mkString("\n")
|
||||
|
||||
val argsFormAssingment = args
|
||||
.map(arg => fixupArgName(arg.name))
|
||||
.concat(List("config"))
|
||||
.zipWithIndex
|
||||
|
||||
// Argument unpacking has two forms:
|
||||
// One starting from the first (by index) argument,
|
||||
// One starting from zero
|
||||
val argsAssignmentStartingFrom1 = argsFormAssingment.map((name, ix) => s"${name} = args[${ix + 1}];").mkString("\n")
|
||||
val argsAssignmentStartingFrom0 = argsFormAssingment.map((name, ix) => s"${name} = args[${ix}];").mkString("\n")
|
||||
|
||||
s"""
|
||||
| export function ${func.funcName}(...args) {
|
||||
| let peer;
|
||||
| ${argsLets}
|
||||
| let config;
|
||||
| if (FluencePeer.isInstance(args[0])) {
|
||||
| peer = args[0];
|
||||
| ${argsAssignmentStartingFrom1}
|
||||
| } else {
|
||||
| peer = Fluence.getPeer();
|
||||
| ${argsAssignmentStartingFrom0}
|
||||
| }
|
||||
|
|
||||
| let request;
|
||||
| const promise = new Promise((resolve, reject) => {
|
||||
| const r = new RequestFlowBuilder()
|
||||
| .disableInjections()
|
||||
| .withRawScript(
|
||||
| `
|
||||
| ${jsAir.show}
|
||||
| `,
|
||||
| )
|
||||
| .configHandler((h) => {
|
||||
| ${conf.relayVarName.fold("") { r =>
|
||||
s"""h.on('${conf.getDataService}', '$r', () => {
|
||||
| return peer.getStatus().relayPeerId;
|
||||
| });""".stripMargin }}
|
||||
| $setCallbacks
|
||||
| $returnCallback
|
||||
| h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => {
|
||||
| const [err] = args;
|
||||
| reject(err);
|
||||
| });
|
||||
| })
|
||||
| .handleScriptError(reject)
|
||||
| .handleTimeout(() => {
|
||||
| reject('Request timed out for ${func.funcName}');
|
||||
| })
|
||||
| if(${configArgName} && ${configArgName}.ttl) {
|
||||
| r.withTTL(${configArgName}.ttl)
|
||||
| }
|
||||
| request = r.build();
|
||||
| });
|
||||
| peer.internals.initiateFlow(request);
|
||||
| return ${returnVal};
|
||||
|}
|
||||
""".stripMargin
|
||||
}
|
||||
|
||||
}
|
22
backend/ts/src/main/scala/aqua/backend/Header.scala
Normal file
22
backend/ts/src/main/scala/aqua/backend/Header.scala
Normal file
@ -0,0 +1,22 @@
|
||||
package aqua.backend
|
||||
|
||||
object Header {
|
||||
|
||||
def header(isJs: Boolean): String =
|
||||
s"""/**
|
||||
| *
|
||||
| * This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
| * Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
| * If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
| * Aqua version: ${Version.version}
|
||||
| *
|
||||
| */
|
||||
|import { Fluence, FluencePeer } from '@fluencelabs/fluence';
|
||||
|import {
|
||||
| ResultCodes,
|
||||
| RequestFlow,
|
||||
| RequestFlowBuilder,
|
||||
| CallParams,
|
||||
|} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1${if (isJs) ".js" else ""}';
|
||||
|""".stripMargin
|
||||
}
|
33
backend/ts/src/main/scala/aqua/backend/OutputFile.scala
Normal file
33
backend/ts/src/main/scala/aqua/backend/OutputFile.scala
Normal file
@ -0,0 +1,33 @@
|
||||
package aqua.backend
|
||||
|
||||
import aqua.backend.ts.{TSFuncTypes, TSServiceTypes}
|
||||
import aqua.backend.{Header, OutputService}
|
||||
import aqua.model.transform.res.AquaRes
|
||||
|
||||
case class OutputFile(res: AquaRes) {
|
||||
|
||||
def generate(types: Types): String = {
|
||||
import types.*
|
||||
val services = res.services
|
||||
.map(s => OutputService(s, types))
|
||||
.map(_.generate)
|
||||
.toList
|
||||
.mkString("\n\n")
|
||||
val functions =
|
||||
res.funcs.map(f => OutputFunc(f, types)).map(_.generate).toList.mkString("\n\n")
|
||||
s"""${Header.header(false)}
|
||||
|
|
||||
|function ${typed(
|
||||
s"""missingFields(${typed("obj", "any")}, ${typed("fields", "string[]")})""",
|
||||
"string[]")} {
|
||||
| return fields.filter(f => !(f in obj))
|
||||
|}
|
||||
|
|
||||
|// Services
|
||||
|$services
|
||||
|// Functions
|
||||
|$functions
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,26 @@
|
||||
package aqua.backend.ts
|
||||
package aqua.backend
|
||||
|
||||
import aqua.backend.air.FuncAirGen
|
||||
import aqua.backend.ts.TypeScriptCommon.{callBackExprBody, fixupArgName}
|
||||
import aqua.backend.ts.{TSFuncTypes, TypeScriptCommon}
|
||||
import aqua.model.transform.res.FuncRes
|
||||
import aqua.types.*
|
||||
import aqua.model.transform.res.FuncRes.Arg
|
||||
import aqua.types.{ArrowType, DataType, OptionType, ProductType}
|
||||
import cats.syntax.show.*
|
||||
|
||||
case class TypeScriptFunc(func: FuncRes) {
|
||||
case class OutputFunc(func: FuncRes, types: Types) {
|
||||
|
||||
import TypeScriptCommon._
|
||||
import FuncRes._
|
||||
import func._
|
||||
import FuncRes.*
|
||||
import TypeScriptCommon.*
|
||||
import types.*
|
||||
import func.*
|
||||
val funcTypes = types.funcType(func)
|
||||
import funcTypes.*
|
||||
|
||||
val argsFormAssingment = args
|
||||
.map(arg => fixupArgName(arg.name))
|
||||
.appended("config")
|
||||
.zipWithIndex
|
||||
|
||||
private def returnCallback: String =
|
||||
val respBody = func.returnType match {
|
||||
@ -29,7 +40,7 @@ case class TypeScriptFunc(func: FuncRes) {
|
||||
| }""".stripMargin
|
||||
}.mkString
|
||||
|
||||
s""" let opt: any = args;
|
||||
s""" let ${typed("opt", "any")} = args;
|
||||
|$unwrapOpts
|
||||
| return resolve(opt);""".stripMargin
|
||||
case _ =>
|
||||
@ -46,9 +57,6 @@ case class TypeScriptFunc(func: FuncRes) {
|
||||
|
||||
val tsAir = FuncAirGen(func).generate
|
||||
|
||||
val retTypeTs = func.returnType
|
||||
.fold("void")(typeToTs)
|
||||
|
||||
val setCallbacks = func.args.collect { // Product types are not handled
|
||||
case Arg(argName, OptionType(_)) =>
|
||||
s""" h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)} === null ? [] : [${fixupArgName(argName)}];});"""
|
||||
@ -68,27 +76,11 @@ case class TypeScriptFunc(func: FuncRes) {
|
||||
val returnVal =
|
||||
func.returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||
|
||||
val clientArgName = genArgName("client")
|
||||
val configArgName = genArgName("config")
|
||||
|
||||
val configType = "{ttl?: number}"
|
||||
val codeLeftSpace = " " * 20
|
||||
|
||||
val funcName = s"${func.funcName}"
|
||||
|
||||
val argsTypescript = args
|
||||
.map(arg => s"${fixupArgName(arg.name)}: " + typeToTs(arg.`type`))
|
||||
.concat(List(s"config?: $configType"))
|
||||
|
||||
// defines different types for overloaded service registration function.
|
||||
var funcTypeOverload1 = argsTypescript.mkString(", ")
|
||||
var funcTypeOverload2 = ("peer: FluencePeer" :: argsTypescript).mkString(", ")
|
||||
|
||||
val argsLets = args.map(arg => s" let ${fixupArgName(arg.name)}: any;").mkString("\n")
|
||||
|
||||
val argsFormAssingment = args
|
||||
.map(arg => fixupArgName(arg.name))
|
||||
.concat(List("config"))
|
||||
.zipWithIndex
|
||||
val argsLets = args.map(arg => s" let ${typed(fixupArgName(arg.name), "any")};").mkString("\n")
|
||||
|
||||
// argument upnacking has two forms.
|
||||
// One starting from the first (by index) argument,
|
||||
@ -96,17 +88,11 @@ case class TypeScriptFunc(func: FuncRes) {
|
||||
val argsAssignmentStartingFrom1 = argsFormAssingment.map((name, ix) => s" ${name} = args[${ix + 1}];").mkString("\n")
|
||||
val argsAssignmentStartingFrom0 = argsFormAssingment.map((name, ix) => s" ${name} = args[${ix}];").mkString("\n")
|
||||
|
||||
val funcTypeRes = s"Promise<$retTypeTs>"
|
||||
|
||||
val codeLeftSpace = " " * 20
|
||||
|
||||
s"""
|
||||
|export function ${func.funcName}(${funcTypeOverload1}) : ${funcTypeRes};
|
||||
|export function ${func.funcName}(${funcTypeOverload2}) : ${funcTypeRes};
|
||||
|export function ${func.funcName}(...args: any) {
|
||||
| let peer: FluencePeer;
|
||||
s"""${funcTypes.generate}
|
||||
|export function ${func.funcName}(${typed("...args", "any")}) {
|
||||
| let ${typed("peer", "FluencePeer")};
|
||||
|${argsLets}
|
||||
| let config: any;
|
||||
| let ${typed("config", "any")};
|
||||
| if (FluencePeer.isInstance(args[0])) {
|
||||
| peer = args[0];
|
||||
|${argsAssignmentStartingFrom1}
|
||||
@ -115,8 +101,8 @@ case class TypeScriptFunc(func: FuncRes) {
|
||||
|${argsAssignmentStartingFrom0}
|
||||
| }
|
||||
|
|
||||
| let request: RequestFlow;
|
||||
| const promise = new Promise<$retTypeTs>((resolve, reject) => {
|
||||
| let ${typed("request", "RequestFlow")};
|
||||
| const promise = new ${generic("Promise", retTypeTs._2)}((resolve, reject) => {
|
||||
| const r = new RequestFlowBuilder()
|
||||
| .disableInjections()
|
||||
| .withRawScript(`
|
||||
@ -146,7 +132,7 @@ case class TypeScriptFunc(func: FuncRes) {
|
||||
|
|
||||
| request = r.build();
|
||||
| });
|
||||
| peer.internals.initiateFlow(request!);
|
||||
| peer.internals.initiateFlow(${bang("request")});
|
||||
| return ${returnVal};
|
||||
|}""".stripMargin
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
package aqua.backend.js
|
||||
package aqua.backend
|
||||
|
||||
import aqua.backend.air.FuncAirGen
|
||||
import aqua.model.transform.res.FuncRes
|
||||
import aqua.types.*
|
||||
import cats.syntax.show.*
|
||||
import aqua.backend.ts.TypeScriptCommon.callBackExprBody
|
||||
import aqua.backend.ts.TypeScriptCommon
|
||||
import aqua.model.transform.res.ServiceRes
|
||||
import aqua.types.ArrowType
|
||||
|
||||
case class JavaScriptService(srv: ServiceRes) {
|
||||
case class OutputService(srv: ServiceRes, types: Types) {
|
||||
|
||||
import JavaScriptCommon._
|
||||
import TypeScriptCommon.*
|
||||
import types.*
|
||||
val serviceTypes = types.serviceType(srv)
|
||||
import serviceTypes.*
|
||||
|
||||
def fnHandler(arrow: ArrowType, memberName: String) = {
|
||||
s"""
|
||||
| if (req.fnName === '${memberName}') {
|
||||
| ${callBackExprBody(arrow, "service." + memberName)}
|
||||
| }
|
||||
""".stripMargin
|
||||
s"""if (req.fnName === '${memberName}') {
|
||||
|${callBackExprBody(arrow, "service." + memberName, 12)}
|
||||
}""".stripMargin
|
||||
}
|
||||
|
||||
def generate: String =
|
||||
@ -25,22 +25,21 @@ case class JavaScriptService(srv: ServiceRes) {
|
||||
}
|
||||
.mkString("\n\n")
|
||||
|
||||
val registerName = s"register${srv.name}"
|
||||
|
||||
val defaultServiceIdBranch = srv.defaultId.fold("")(x =>
|
||||
s"""
|
||||
| else {
|
||||
| serviceId = ${x}
|
||||
|}""".stripMargin
|
||||
s"""else {
|
||||
| serviceId = ${x}
|
||||
| }""".stripMargin
|
||||
)
|
||||
|
||||
val membersNames = srv.members.map(_._1)
|
||||
|
||||
s"""
|
||||
|export function ${registerName}(...args) {
|
||||
| let peer;
|
||||
| let serviceId;
|
||||
| let service;
|
||||
|${serviceTypes.generate}
|
||||
|
|
||||
|export function register${srv.name}(${typed("...args", "any")}) {
|
||||
| let ${typed("peer", "FluencePeer")};
|
||||
| let ${typed("serviceId", "any")};
|
||||
| let ${typed("service", "any")};
|
||||
| if (FluencePeer.isInstance(args[0])) {
|
||||
| peer = args[0];
|
||||
| } else {
|
||||
@ -66,21 +65,21 @@ case class JavaScriptService(srv: ServiceRes) {
|
||||
| service = args[2];
|
||||
| }
|
||||
|
|
||||
| const incorrectServiceDefinitions = missingFields(service, [${membersNames.map { n => s"'$n'"}.mkString(", ")}]);
|
||||
| if (!incorrectServiceDefinitions.length) {
|
||||
| const incorrectServiceDefinitions = missingFields(service, [${membersNames.map { n => s"'$n'" }.mkString(", ")}]);
|
||||
| if (!!incorrectServiceDefinitions.length) {
|
||||
| throw new Error("Error registering service ${srv.name}: missing functions: " + incorrectServiceDefinitions.map((d) => "'" + d + "'").join(", "))
|
||||
| }
|
||||
|
|
||||
| peer.internals.callServiceHandler.use((req, resp, next) => {
|
||||
| if (req.serviceId !== serviceId) {
|
||||
| next();
|
||||
| return;
|
||||
| }
|
||||
| return;
|
||||
| }
|
||||
|
|
||||
|${fnHandlers}
|
||||
| ${fnHandlers}
|
||||
|
|
||||
| next();
|
||||
| });
|
||||
| }
|
||||
| next();
|
||||
| });
|
||||
|}
|
||||
""".stripMargin
|
||||
}
|
36
backend/ts/src/main/scala/aqua/backend/Types.scala
Normal file
36
backend/ts/src/main/scala/aqua/backend/Types.scala
Normal file
@ -0,0 +1,36 @@
|
||||
package aqua.backend
|
||||
|
||||
import aqua.backend.ts.{TSFuncTypes, TSServiceTypes}
|
||||
import aqua.model.transform.res.{FuncRes, ServiceRes}
|
||||
|
||||
trait Types {
|
||||
def typed(field: String, `type`: String): String
|
||||
def generic(field: String, `type`: String): String
|
||||
def bang(field: String): String
|
||||
def funcType(f: FuncRes): FuncTypes
|
||||
def serviceType(s: ServiceRes): ServiceTypes
|
||||
}
|
||||
|
||||
trait FuncTypes {
|
||||
def retTypeTs: (Option[String], String)
|
||||
def generate: String
|
||||
}
|
||||
|
||||
trait ServiceTypes {
|
||||
def generate: String
|
||||
}
|
||||
|
||||
object EmptyTypes extends Types {
|
||||
override def typed(field: String, `type`: String): String = field
|
||||
override def generic(field: String, `type`: String): String = field
|
||||
override def bang(field: String): String = field
|
||||
override def funcType(f: FuncRes): FuncTypes = new FuncTypes {
|
||||
override def retTypeTs: (Option[String], String) = (None, "")
|
||||
override def generate: String = ""
|
||||
}
|
||||
override def serviceType(s: ServiceRes): ServiceTypes = new ServiceTypes {
|
||||
override def generate: String = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
package aqua.backend.js
|
||||
|
||||
import aqua.backend.ts.TypeScriptTypes
|
||||
import aqua.backend.{Backend, EmptyTypes, Generated, Header, OutputFile, OutputFunc, OutputService}
|
||||
import aqua.model.transform.res.AquaRes
|
||||
|
||||
object JavaScriptBackend extends Backend {
|
||||
|
||||
val ext = ".js"
|
||||
val tsExt = ".d.ts"
|
||||
|
||||
def typesFile(res: AquaRes): Generated = {
|
||||
val services = res.services
|
||||
.map(s => TypeScriptTypes.serviceType(s))
|
||||
.map(_.generate)
|
||||
.toList
|
||||
.mkString("\n\n")
|
||||
val functions =
|
||||
res.funcs.map(f => TypeScriptTypes.funcType(f)).map(_.generate).toList.mkString("\n\n")
|
||||
|
||||
val body = s"""${Header.header(false)}
|
||||
|
|
||||
|// Services
|
||||
|$services
|
||||
|
|
||||
|// Functions
|
||||
|$functions
|
||||
|""".stripMargin
|
||||
|
||||
Generated(tsExt, body)
|
||||
}
|
||||
|
||||
override def generate(res: AquaRes): Seq[Generated] =
|
||||
if (res.isEmpty) Nil
|
||||
else {
|
||||
Generated(ext, OutputFile(res).generate(EmptyTypes)):: typesFile(res) :: Nil
|
||||
}
|
||||
}
|
37
backend/ts/src/main/scala/aqua/backend/ts/TSFuncTypes.scala
Normal file
37
backend/ts/src/main/scala/aqua/backend/ts/TSFuncTypes.scala
Normal file
@ -0,0 +1,37 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.FuncTypes
|
||||
import aqua.backend.ts.TypeScriptCommon.{fixupArgName, typeToTs, genTypeName}
|
||||
import aqua.model.transform.res.FuncRes
|
||||
import aqua.types.*
|
||||
|
||||
case class TSFuncTypes(func: FuncRes) extends FuncTypes {
|
||||
import TypeScriptTypes._
|
||||
|
||||
override val retTypeTs = func.returnType
|
||||
.fold((None, "void")) { t => genTypeName(t, func.funcName.capitalize + "Result") }
|
||||
|
||||
override def generate = {
|
||||
val configType = "?: {ttl?: number}"
|
||||
|
||||
val argsTypescript = func.args
|
||||
.map { arg =>
|
||||
val (typeDesc, t) = genTypeName(arg.`type`, func.funcName.capitalize + "Arg" + arg.name.capitalize)
|
||||
(typeDesc, s"${typed(fixupArgName(arg.name), t)}")
|
||||
} :+ (None, s"config$configType")
|
||||
|
||||
val args = argsTypescript.map(_._2)
|
||||
val argsDesc = argsTypescript.map(_._1).flatten
|
||||
|
||||
// defines different types for overloaded service registration function.
|
||||
var funcTypeOverload1 = args.mkString(", ")
|
||||
var funcTypeOverload2 = (typed("peer", "FluencePeer") :: args).mkString(", ")
|
||||
|
||||
val (resTypeDesc, resType) = retTypeTs
|
||||
|
||||
s"""${argsDesc.mkString("\n")}
|
||||
|${resTypeDesc.getOrElse("")}
|
||||
|export function ${func.funcName}(${funcTypeOverload1}): ${generic("Promise", resType)};
|
||||
|export function ${func.funcName}(${funcTypeOverload2}): ${generic("Promise", resType)};""".stripMargin
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.ServiceTypes
|
||||
import aqua.backend.ts.TypeScriptCommon.fnDef
|
||||
import aqua.model.transform.res.ServiceRes
|
||||
|
||||
case class TSServiceTypes(srv: ServiceRes) extends ServiceTypes {
|
||||
import TypeScriptTypes._
|
||||
|
||||
private val serviceTypeName = s"${srv.name}Def";
|
||||
|
||||
def registerServiceArgs = {
|
||||
|
||||
// defined arguments used in overloads below
|
||||
val peerDecl = s"${typed("peer", "FluencePeer")}";
|
||||
val serviceIdDecl = s"${typed("serviceId", "string")}";
|
||||
val serviceDecl = s"${typed("service", serviceTypeName)}"
|
||||
|
||||
// Service registration functions has several overloads.
|
||||
// Depending on whether the the service has the default id or not
|
||||
// there would be different number of overloads
|
||||
// This variable contain defines the list of lists where
|
||||
// the outmost list describes the list of overloads
|
||||
// and the innermost one defines the list of arguments in the overload
|
||||
val registerServiceArgsSource = srv.defaultId.fold(
|
||||
List(
|
||||
List(serviceIdDecl, serviceDecl),
|
||||
List(peerDecl, serviceIdDecl, serviceDecl)
|
||||
)
|
||||
)(_ =>
|
||||
List(
|
||||
List(serviceDecl),
|
||||
List(serviceIdDecl, serviceDecl),
|
||||
List(peerDecl, serviceDecl),
|
||||
List(peerDecl, serviceIdDecl, serviceDecl)
|
||||
)
|
||||
)
|
||||
|
||||
// Service registration functions has several overloads.
|
||||
// Depending on whether the the service has the default id or not
|
||||
// there would be different number of overloads
|
||||
// This variable contain defines the list of lists where
|
||||
// the outmost list describes the list of overloads
|
||||
// and the innermost one defines the list of arguments in the overload
|
||||
registerServiceArgsSource.map { x =>
|
||||
val args = x.mkString(", ")
|
||||
s"export function register${srv.name}(${args}): void;"
|
||||
}
|
||||
.mkString("\n")
|
||||
}
|
||||
|
||||
def exportInterface = {
|
||||
val fnDefs = srv.members.map { case (name, arrow) =>
|
||||
s"${typed(name, fnDef(arrow))};"
|
||||
}
|
||||
.mkString("\n")
|
||||
|
||||
s"""export interface ${serviceTypeName} {
|
||||
| ${fnDefs}
|
||||
|}""".stripMargin
|
||||
}
|
||||
|
||||
def generate = {
|
||||
s"""$exportInterface
|
||||
|$registerServiceArgs
|
||||
"""
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.{Backend, Generated}
|
||||
import aqua.backend.{Backend, Generated, OutputFile}
|
||||
import aqua.model.transform.res.AquaRes
|
||||
import cats.data.NonEmptyChain
|
||||
|
||||
@ -9,5 +9,5 @@ object TypeScriptBackend extends Backend {
|
||||
val ext = ".ts"
|
||||
|
||||
override def generate(res: AquaRes): Seq[Generated] =
|
||||
if (res.isEmpty) Nil else Generated(ext, TypeScriptFile(res).generate) :: Nil
|
||||
if (res.isEmpty) Nil else Generated(ext, OutputFile(res).generate(TypeScriptTypes)) :: Nil
|
||||
}
|
||||
|
@ -7,6 +7,20 @@ import cats.syntax.show.*
|
||||
|
||||
object TypeScriptCommon {
|
||||
|
||||
def genTypeName(t: Type, name: String): (Option[String], String) = {
|
||||
val genType = typeToTs(t)
|
||||
t match {
|
||||
case tt: ProductType =>
|
||||
val gen = s"export type $name = $genType"
|
||||
(Some(gen), name)
|
||||
case tt: StructType =>
|
||||
val gen = s"export type $name = $genType"
|
||||
(Some(gen), name)
|
||||
case _ => (None, genType)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def typeToTs(t: Type): String = t match {
|
||||
case OptionType(t) => typeToTs(t) + " | null"
|
||||
case ArrayType(t) => typeToTs(t) + "[]"
|
||||
@ -14,7 +28,7 @@ object TypeScriptCommon {
|
||||
case pt: ProductType =>
|
||||
"[" + pt.toList.map(typeToTs).mkString(", ") + "]"
|
||||
case st: StructType =>
|
||||
s"{ ${st.fields.map(typeToTs).toNel.map(kv => kv._1 + ": " + kv._2).toList.mkString("; ")} }"
|
||||
s"{ ${st.fields.map(typeToTs).toNel.map(kv => kv._1 + ": " + kv._2 + ";").toList.mkString(" ")} }"
|
||||
case st: ScalarType if ScalarType.number(st) => "number"
|
||||
case ScalarType.bool => "boolean"
|
||||
case ScalarType.string => "string"
|
||||
@ -37,8 +51,7 @@ object TypeScriptCommon {
|
||||
at.res.fold("void")(typeToTs)
|
||||
|
||||
def fnDef(at: ArrowType): String =
|
||||
val args = argsToTs(at)
|
||||
.concat(List(callParamsArg(at)))
|
||||
val args = (argsToTs(at) :+ callParamsArg(at))
|
||||
.mkString(", ")
|
||||
|
||||
val retType = returnType(at)
|
||||
@ -63,9 +76,6 @@ object TypeScriptCommon {
|
||||
}
|
||||
s"callParams: CallParams<${generic}>"
|
||||
|
||||
def argsCallToTs(at: ArrowType): List[String] =
|
||||
FuncRes.arrowArgIndices(at).map(idx => s"args[$idx]")
|
||||
|
||||
def callBackExprBody(at: ArrowType, callbackName: String, leftSpace: Int): String = {
|
||||
val arrowArgumentsToCallbackArgumentsList =
|
||||
at.domain.toList
|
||||
|
@ -1,45 +0,0 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.Version
|
||||
import aqua.model.transform.res.AquaRes
|
||||
|
||||
case class TypeScriptFile(res: AquaRes) {
|
||||
|
||||
import TypeScriptFile.Header
|
||||
|
||||
def generate: String =
|
||||
s"""${Header}
|
||||
|
|
||||
|function missingFields(obj: any, fields: string[]): string[] {
|
||||
| return fields.filter(f => !(f in obj))
|
||||
|}
|
||||
|
|
||||
|// Services
|
||||
|${res.services.map(TypeScriptService(_)).map(_.generate).toList.mkString("\n\n")}
|
||||
|// Functions
|
||||
|${res.funcs.map(TypeScriptFunc(_)).map(_.generate).toList.mkString("\n\n")}
|
||||
|""".stripMargin
|
||||
|
||||
}
|
||||
|
||||
object TypeScriptFile {
|
||||
|
||||
val Header: String =
|
||||
s"""/**
|
||||
| *
|
||||
| * This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
| * Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
| * If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
| * Aqua version: ${Version.version}
|
||||
| *
|
||||
| */
|
||||
|import { Fluence, FluencePeer } from '@fluencelabs/fluence';
|
||||
|import {
|
||||
| ResultCodes,
|
||||
| RequestFlow,
|
||||
| RequestFlowBuilder,
|
||||
| CallParams,
|
||||
|} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1';
|
||||
|""".stripMargin
|
||||
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.air.FuncAirGen
|
||||
import aqua.model.transform.res.FuncRes
|
||||
import aqua.types.*
|
||||
import cats.syntax.show.*
|
||||
import aqua.model.transform.res.ServiceRes
|
||||
|
||||
case class TypeScriptService(srv: ServiceRes) {
|
||||
|
||||
import TypeScriptCommon._
|
||||
|
||||
def fnHandler(arrow: ArrowType, memberName: String) = {
|
||||
s"""if (req.fnName === '${memberName}') {
|
||||
|${callBackExprBody(arrow, "service." + memberName, 12)}
|
||||
}""".stripMargin
|
||||
}
|
||||
|
||||
def generate: String =
|
||||
val fnHandlers = srv.members
|
||||
.map{ case (name, arrow) =>
|
||||
fnHandler(arrow, name)
|
||||
}
|
||||
.mkString("\n\n")
|
||||
|
||||
val fnDefs = srv.members
|
||||
.map{ case (name, arrow) =>
|
||||
s"${name}: ${fnDef(arrow)};"
|
||||
}
|
||||
.mkString("\n")
|
||||
|
||||
val serviceTypeName = s"${srv.name}Def";
|
||||
|
||||
val registerName = s"register${srv.name}"
|
||||
|
||||
// defined arguments used in overloads below
|
||||
val peerDecl = "peer: FluencePeer";
|
||||
val serviceIdDecl = "serviceId: string";
|
||||
val serviceDecl = s"service: ${serviceTypeName}"
|
||||
|
||||
// Service registration functions has several overloads.
|
||||
// Depending on whether the the service has the default id or not
|
||||
// there would be different number of overloads
|
||||
// This variable contain defines the list of lists where
|
||||
// the outmost list describes the list of overloads
|
||||
// and the innermost one defines the list of arguments in the overload
|
||||
val registerServiceArgsSource = srv.defaultId.fold(
|
||||
List(
|
||||
List(serviceIdDecl, serviceDecl),
|
||||
List(peerDecl, serviceIdDecl, serviceDecl)
|
||||
)
|
||||
)(_ =>
|
||||
List(
|
||||
List(serviceDecl),
|
||||
List(serviceIdDecl, serviceDecl),
|
||||
List(peerDecl, serviceDecl),
|
||||
List(peerDecl, serviceIdDecl, serviceDecl),
|
||||
)
|
||||
)
|
||||
|
||||
// Service registration functions has several overloads.
|
||||
// Depending on whether the the service has the default id or not
|
||||
// there would be different number of overloads
|
||||
// This variable contain defines the list of lists where
|
||||
// the outmost list describes the list of overloads
|
||||
// and the innermost one defines the list of arguments in the overload
|
||||
val registerServiceArgs = registerServiceArgsSource.map{ x =>
|
||||
val args = x.mkString(", ")
|
||||
s"export function ${registerName}(${args}): void;"
|
||||
}
|
||||
.mkString("\n");
|
||||
|
||||
val defaultServiceIdBranch = srv.defaultId.fold("")(x =>
|
||||
s"""else {
|
||||
| serviceId = ${x}
|
||||
| }""".stripMargin
|
||||
)
|
||||
|
||||
val membersNames = srv.members.map(_._1)
|
||||
|
||||
s"""
|
||||
|export interface ${serviceTypeName} {
|
||||
| ${fnDefs}
|
||||
|}
|
||||
|
|
||||
|$registerServiceArgs
|
||||
|export function ${registerName}(...args: any) {
|
||||
| let peer: FluencePeer;
|
||||
| let serviceId: any;
|
||||
| let service: any;
|
||||
| if (FluencePeer.isInstance(args[0])) {
|
||||
| peer = args[0];
|
||||
| } else {
|
||||
| peer = Fluence.getPeer();
|
||||
| }
|
||||
|
|
||||
| if (typeof args[0] === 'string') {
|
||||
| serviceId = args[0];
|
||||
| } else if (typeof args[1] === 'string') {
|
||||
| serviceId = args[1];
|
||||
| } ${defaultServiceIdBranch}
|
||||
|
|
||||
| // Figuring out which overload is the service.
|
||||
| // If the first argument is not Fluence Peer and it is an object, then it can only be the service def
|
||||
| // If the first argument is peer, we are checking further. The second argument might either be
|
||||
| // an object, that it must be the service object
|
||||
| // or a string, which is the service id. In that case the service is the third argument
|
||||
| if (!(FluencePeer.isInstance(args[0])) && typeof args[0] === 'object') {
|
||||
| service = args[0];
|
||||
| } else if (typeof args[1] === 'object') {
|
||||
| service = args[1];
|
||||
| } else {
|
||||
| service = args[2];
|
||||
| }
|
||||
|
|
||||
| const incorrectServiceDefinitions = missingFields(service, [${membersNames.map { n => s"'$n'" }.mkString(", ")}]);
|
||||
| if (!!incorrectServiceDefinitions.length) {
|
||||
| throw new Error("Error registering service ${srv.name}: missing functions: " + incorrectServiceDefinitions.map((d) => "'" + d + "'").join(", "))
|
||||
| }
|
||||
|
|
||||
| peer.internals.callServiceHandler.use((req, resp, next) => {
|
||||
| if (req.serviceId !== serviceId) {
|
||||
| next();
|
||||
| return;
|
||||
| }
|
||||
|
|
||||
| ${fnHandlers}
|
||||
|
|
||||
| next();
|
||||
| });
|
||||
|}
|
||||
""".stripMargin
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.{FuncTypes, ServiceTypes, Types}
|
||||
import aqua.model.transform.res.{FuncRes, ServiceRes}
|
||||
|
||||
object TypeScriptTypes extends Types {
|
||||
override def typed(field: String, t: String): String = s"$field: $t"
|
||||
override def generic(field: String, t: String): String = s"$field<$t>"
|
||||
override def bang(field: String): String = s"$field!"
|
||||
def funcType(f: FuncRes): FuncTypes = TSFuncTypes(f)
|
||||
def serviceType(s: ServiceRes): ServiceTypes = TSServiceTypes(s)
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.Header
|
||||
import aqua.model.transform.res.AquaRes
|
||||
|
||||
case class TypeScriptTypesFile(res: AquaRes) {
|
||||
def generate: String =
|
||||
s"""${Header.header(false)}
|
||||
|
|
||||
|// Services
|
||||
|${res.services.map(TSServiceTypes(_)).map(_.generate).toList.mkString("\n\n")}
|
||||
|
|
||||
|// Functions
|
||||
|${res.funcs.map(TSFuncTypes(_)).map(_.generate).toList.mkString("\n\n")}
|
||||
|""".stripMargin
|
||||
}
|
@ -52,7 +52,7 @@ lazy val cli = crossProject(JSPlatform, JVMPlatform)
|
||||
"co.fs2" %%% "fs2-io" % fs2V
|
||||
)
|
||||
)
|
||||
.dependsOn(compiler, `backend-air`, `backend-ts`, `backend-js`)
|
||||
.dependsOn(compiler, `backend-air`, `backend-ts`)
|
||||
|
||||
lazy val cliJS = cli.js
|
||||
.settings(
|
||||
@ -166,10 +166,3 @@ lazy val `backend-ts` = crossProject(JVMPlatform, JSPlatform)
|
||||
.in(file("backend/ts"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(`backend-air`)
|
||||
|
||||
lazy val `backend-js` = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("backend/js"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(`backend-air`)
|
||||
|
Loading…
Reference in New Issue
Block a user