Combine js and ts backends (#307)

This commit is contained in:
Dima 2021-09-23 13:45:27 +03:00 committed by GitHub
parent f27ae5eda9
commit 4e63da83f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 341 additions and 536 deletions

View File

@ -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\""

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View 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
}

View 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
}
}

View File

@ -1,17 +1,28 @@
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.*
private def returnCallback: String =
val argsFormAssingment = args
.map(arg => fixupArgName(arg.name))
.appended("config")
.zipWithIndex
private def returnCallback: String =
val respBody = func.returnType match {
case Some(x) => x match {
case OptionType(_) =>
@ -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,45 +76,23 @@ 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 argsLets = args.map(arg => s" let ${typed(fixupArgName(arg.name), "any")};").mkString("\n")
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
// argument upnacking has two forms.
// argument upnacking 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")
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
}

View File

@ -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
val defaultServiceIdBranch = srv.defaultId.fold("")(x =>
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
}

View 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 = ""
}
}

View File

@ -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
}
}

View 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
}
}

View File

@ -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
"""
}
}

View File

@ -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
}

View File

@ -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"
@ -35,10 +49,9 @@ object TypeScriptCommon {
def returnType(at: ArrowType): String =
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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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`)