mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-03 22:20:17 +00:00
Add support for the new features of JS SDK API (#251)
This commit is contained in:
parent
d881f5bdbe
commit
3e1618c734
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,4 +5,4 @@
|
|||||||
.bloop
|
.bloop
|
||||||
metals.sbt
|
metals.sbt
|
||||||
target
|
target
|
||||||
project/target
|
project/target
|
||||||
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/target": true
|
||||||
|
}
|
||||||
|
}
|
@ -8,24 +8,6 @@ object JavaScriptBackend extends Backend {
|
|||||||
|
|
||||||
val ext = ".js"
|
val ext = ".js"
|
||||||
|
|
||||||
override def generate(aqua: AquaRes): Seq[Generated] = {
|
override def generate(res: AquaRes): Seq[Generated] =
|
||||||
val funcs = NonEmptyChain.fromChain(
|
if (res.isEmpty) Nil else Generated(ext, JavaScriptFile(res).generate) :: Nil
|
||||||
aqua.funcs
|
|
||||||
.map(JavaScriptFunc(_))
|
|
||||||
)
|
|
||||||
funcs
|
|
||||||
.map(fs =>
|
|
||||||
Seq(
|
|
||||||
Generated(
|
|
||||||
ext,
|
|
||||||
JavaScriptFile.Header + "\n\n" + fs
|
|
||||||
.map(_.generate)
|
|
||||||
.toChain
|
|
||||||
.toList
|
|
||||||
.mkString("\n\n")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.getOrElse(Seq.empty)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
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(_._2)
|
||||||
|
.map(idx => s"req.args[$idx]")
|
||||||
|
.concat(List("callParams"))
|
||||||
|
.mkString(", ")
|
||||||
|
|
||||||
|
val callCallbackStatement = s"$callbackName(${arrowArgumentsToCallbackArgumentsList})"
|
||||||
|
|
||||||
|
val callCallbackStatementAndReturn =
|
||||||
|
at.res.fold(s"${callCallbackStatement}; resp.result = {}")(_ =>
|
||||||
|
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,6 +1,23 @@
|
|||||||
package aqua.backend.js
|
package aqua.backend.js
|
||||||
|
|
||||||
import aqua.backend.Version
|
import aqua.backend.Version
|
||||||
|
import aqua.model.transform.res.AquaRes
|
||||||
|
|
||||||
|
case class JavaScriptFile(res: AquaRes) {
|
||||||
|
|
||||||
|
import JavaScriptFile.Header
|
||||||
|
|
||||||
|
def generate: String =
|
||||||
|
s"""${Header}
|
||||||
|
|
|
||||||
|
|// 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 {
|
object JavaScriptFile {
|
||||||
|
|
||||||
@ -13,7 +30,13 @@ object JavaScriptFile {
|
|||||||
| * Aqua version: ${Version.version}
|
| * Aqua version: ${Version.version}
|
||||||
| *
|
| *
|
||||||
| */
|
| */
|
||||||
|import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable.js';
|
|import { FluencePeer } from '@fluencelabs/fluence';
|
||||||
|
|import {
|
||||||
|
| ResultCodes,
|
||||||
|
| RequestFlow,
|
||||||
|
| RequestFlowBuilder,
|
||||||
|
| CallParams,
|
||||||
|
|} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1.js';
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,117 +7,132 @@ import cats.syntax.show.*
|
|||||||
|
|
||||||
case class JavaScriptFunc(func: FuncRes) {
|
case class JavaScriptFunc(func: FuncRes) {
|
||||||
|
|
||||||
import JavaScriptFunc._
|
import JavaScriptCommon._
|
||||||
import FuncRes._
|
import FuncRes._
|
||||||
import func._
|
import func._
|
||||||
|
|
||||||
def argsJavaScript: String =
|
private def returnCallback: String =
|
||||||
argNames.mkString(", ")
|
val respBody = func.returnType match {
|
||||||
|
case Some(x) => x match {
|
||||||
// TODO: use common functions between TypeScript and JavaScript backends
|
case OptionType(_) =>
|
||||||
private def returnCallback: String = returnType.fold("") { retType =>
|
""" let [opt] = args;
|
||||||
val respBody = retType match {
|
| if (Array.isArray(opt)) {
|
||||||
case OptionType(_) =>
|
| if (opt.length === 0) { resolve(null); }
|
||||||
""" let [opt] = args;
|
| opt = opt[0];
|
||||||
| if (Array.isArray(opt)) {
|
| }
|
||||||
| if (opt.length === 0) { resolve(null); }
|
| return resolve(opt);""".stripMargin
|
||||||
| opt = opt[0];
|
case pt: ProductType =>
|
||||||
| }
|
val unwrapOpts = pt.toList.zipWithIndex.collect { case (OptionType(_), i) =>
|
||||||
| return resolve(opt);""".stripMargin
|
s"""
|
||||||
case pt: ProductType =>
|
| if(Array.isArray(opt[$i])) {
|
||||||
val unwrapOpts = pt.toList.zipWithIndex.collect { case (OptionType(_), i) =>
|
| if (opt[$i].length === 0) { opt[$i] = null; }
|
||||||
s"""
|
| else {opt[$i] = opt[$i][0]; }
|
||||||
| if(Array.isArray(opt[$i])) {
|
| }""".stripMargin
|
||||||
| if (opt[$i].length === 0) { opt[$i] = null; }
|
}.mkString
|
||||||
| else {opt[$i] = opt[$i][0]; }
|
|
||||||
| }""".stripMargin
|
|
||||||
}.mkString
|
|
||||||
|
|
||||||
s""" let opt = args;
|
|
||||||
|$unwrapOpts
|
|
||||||
| return resolve(opt);""".stripMargin
|
|
||||||
case _ =>
|
|
||||||
""" const [res] = args;
|
|
||||||
| resolve(res);""".stripMargin
|
|
||||||
|
|
||||||
|
s""" let opt = args;
|
||||||
|
|$unwrapOpts
|
||||||
|
| return resolve(opt);""".stripMargin
|
||||||
|
case _ =>
|
||||||
|
""" const [res] = args;
|
||||||
|
| resolve(res);""".stripMargin
|
||||||
|
}
|
||||||
|
case None => ""
|
||||||
}
|
}
|
||||||
s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => {
|
s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => {
|
||||||
| $respBody
|
| $respBody
|
||||||
|});
|
|});
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
}
|
|
||||||
|
|
||||||
def generate: String = {
|
def generate: String = {
|
||||||
|
|
||||||
val jsAir = FuncAirGen(func).generate
|
val jsAir = FuncAirGen(func).generate
|
||||||
|
|
||||||
val setCallbacks = func.args.collect {
|
val setCallbacks = func.args.collect { // Product types are not handled
|
||||||
case Arg(argName, OptionType(_)) =>
|
case Arg(argName, OptionType(_)) =>
|
||||||
s"""h.on('$dataServiceId', '$argName', () => {return $argName === null ? [] : [$argName];});"""
|
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)} === null ? [] : [${fixupArgName(argName)}];});"""
|
||||||
case Arg(argName, _: DataType) =>
|
case Arg(argName, _: DataType) =>
|
||||||
s"""h.on('$dataServiceId', '$argName', () => {return $argName;});"""
|
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)};});"""
|
||||||
case Arg(argName, at: ArrowType) =>
|
case Arg(argName, at: ArrowType) =>
|
||||||
val value = s"$argName(${argsCallToJs(
|
s"""
|
||||||
at
|
| h.use((req, resp, next) => {
|
||||||
)})"
|
| if(req.serviceId === '${conf.callbackService}' && req.fnName === '$argName') {
|
||||||
val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value")
|
| ${callBackExprBody(at, argName)}
|
||||||
s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});"""
|
| }
|
||||||
|
| next();
|
||||||
|
| });
|
||||||
|
""".stripMargin
|
||||||
}
|
}
|
||||||
.mkString("\n")
|
.mkString("\n")
|
||||||
|
|
||||||
val returnVal =
|
val returnVal =
|
||||||
returnType.headOption.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
func.returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||||
|
|
||||||
val clientArgName = genArgName("client")
|
val clientArgName = genArgName("client")
|
||||||
val configArgName = genArgName("config")
|
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"""
|
s"""
|
||||||
|export async function ${func.funcName}(${clientArgName}${if (func.args.isEmpty) ""
|
| export function ${func.funcName}(...args) {
|
||||||
else ", "}${argsJavaScript}, $configArgName) {
|
| let peer;
|
||||||
| let request;
|
| ${argsLets}
|
||||||
| $configArgName = $configArgName || {};
|
| let config;
|
||||||
| const promise = new Promise((resolve, reject) => {
|
| if (args[0] instanceof FluencePeer) {
|
||||||
| var r = new RequestFlowBuilder()
|
| peer = args[0];
|
||||||
| .disableInjections()
|
| ${argsAssignmentStartingFrom1}
|
||||||
| .withRawScript(
|
| } else {
|
||||||
| `
|
| peer = FluencePeer.default;
|
||||||
|${jsAir.show}
|
| ${argsAssignmentStartingFrom0}
|
||||||
| `,
|
| }
|
||||||
| )
|
|
|
||||||
| .configHandler((h) => {
|
| let request;
|
||||||
| ${relayVarName.fold("") { r =>
|
| const promise = new Promise((resolve, reject) => {
|
||||||
s"""h.on('$dataServiceId', '$r', () => {
|
| const r = new RequestFlowBuilder()
|
||||||
| return client.relayPeerId;
|
| .disableInjections()
|
||||||
| });""".stripMargin
|
| .withRawScript(
|
||||||
}}
|
| `
|
||||||
|
| ${jsAir.show}
|
||||||
|
| `,
|
||||||
|
| )
|
||||||
|
| .configHandler((h) => {
|
||||||
|
| ${conf.relayVarName.fold("") { r =>
|
||||||
|
s"""h.on('${conf.getDataService}', '$r', () => {
|
||||||
|
| return peer.connectionInfo.connectedRelay ;
|
||||||
|
| });""".stripMargin }}
|
||||||
| $setCallbacks
|
| $setCallbacks
|
||||||
| $returnCallback
|
| $returnCallback
|
||||||
| h.onEvent('$errorHandlerId', '$errorFuncName', (args) => {
|
| h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => {
|
||||||
| // assuming error is the single argument
|
|
||||||
| const [err] = args;
|
| const [err] = args;
|
||||||
| reject(err);
|
| reject(err);
|
||||||
| });
|
| });
|
||||||
| })
|
| })
|
||||||
| .handleScriptError(reject)
|
| .handleScriptError(reject)
|
||||||
| .handleTimeout(() => {
|
| .handleTimeout(() => {
|
||||||
| reject('Request timed out for ${funcName}');
|
| reject('Request timed out for ${func.funcName}');
|
||||||
| })
|
| })
|
||||||
| if(${configArgName}.ttl) {
|
| if(${configArgName} && ${configArgName}.ttl) {
|
||||||
| r.withTTL(${configArgName}.ttl)
|
| r.withTTL(${configArgName}.ttl)
|
||||||
| }
|
| }
|
||||||
| request = r.build();
|
| request = r.build();
|
||||||
| });
|
| });
|
||||||
| await ${clientArgName}.initiateFlow(request);
|
| peer.internals.initiateFlow(request);
|
||||||
| return ${returnVal};
|
| return ${returnVal};
|
||||||
|}
|
|}
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object JavaScriptFunc {
|
|
||||||
|
|
||||||
def argsCallToJs(at: ArrowType): String =
|
|
||||||
FuncRes.arrowArgIndices(at).map(idx => s"args[$idx]").mkString(", ")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
package aqua.backend.js
|
||||||
|
|
||||||
|
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 JavaScriptService(srv: ServiceRes) {
|
||||||
|
|
||||||
|
import JavaScriptCommon._
|
||||||
|
|
||||||
|
def fnHandler(arrow: ArrowType, memberName: String) = {
|
||||||
|
s"""
|
||||||
|
| if (req.fnName === '${memberName}') {
|
||||||
|
| ${callBackExprBody(arrow, "service." + memberName)}
|
||||||
|
| }
|
||||||
|
""".stripMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate: String =
|
||||||
|
val fnHandlers = srv.members
|
||||||
|
.map{ case (name, arrow) =>
|
||||||
|
fnHandler(arrow, name)
|
||||||
|
}
|
||||||
|
.mkString("\n\n")
|
||||||
|
|
||||||
|
val registerName = s"register${srv.name}"
|
||||||
|
|
||||||
|
val defaultServiceIdBranch = srv.defaultId.fold("")(x =>
|
||||||
|
s"""
|
||||||
|
| else {
|
||||||
|
| serviceId = ${x}
|
||||||
|
|}""".stripMargin
|
||||||
|
)
|
||||||
|
|
||||||
|
s"""
|
||||||
|
| export function ${registerName}(...args) {
|
||||||
|
| let peer;
|
||||||
|
| let serviceId;
|
||||||
|
| let service;
|
||||||
|
| if (args[0] instanceof FluencePeer) {
|
||||||
|
| peer = args[0];
|
||||||
|
| } else {
|
||||||
|
| peer = FluencePeer.default;
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| if (typeof args[0] === 'string') {
|
||||||
|
| serviceId = args[0];
|
||||||
|
| } else if (typeof args[1] === 'string') {
|
||||||
|
| serviceId = args[1];
|
||||||
|
| } ${defaultServiceIdBranch}
|
||||||
|
|
|
||||||
|
| if (!(args[0] instanceof FluencePeer) && typeof args[0] === 'object') {
|
||||||
|
| service = args[0];
|
||||||
|
| } else if (typeof args[1] === 'object') {
|
||||||
|
| service = args[1];
|
||||||
|
| } else {
|
||||||
|
| service = args[2];
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| peer.internals.callServiceHandler.use((req, resp, next) => {
|
||||||
|
| if (req.serviceId !== serviceId) {
|
||||||
|
| next();
|
||||||
|
| return;
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| ${fnHandlers}
|
||||||
|
|
|
||||||
|
| next();
|
||||||
|
| });
|
||||||
|
| }
|
||||||
|
""".stripMargin
|
||||||
|
}
|
105
backend/ts/src/main/scala/aqua/backend/ts/TypeScriptCommon.scala
Normal file
105
backend/ts/src/main/scala/aqua/backend/ts/TypeScriptCommon.scala
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package aqua.backend.ts
|
||||||
|
|
||||||
|
import aqua.backend.air.FuncAirGen
|
||||||
|
import aqua.model.transform.res.FuncRes
|
||||||
|
import aqua.types.*
|
||||||
|
import cats.syntax.show.*
|
||||||
|
|
||||||
|
object TypeScriptCommon {
|
||||||
|
|
||||||
|
def typeToTs(t: Type): String = t match {
|
||||||
|
case OptionType(t) => typeToTs(t) + " | null"
|
||||||
|
case ArrayType(t) => typeToTs(t) + "[]"
|
||||||
|
case StreamType(t) => typeToTs(t) + "[]"
|
||||||
|
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(";")}}"
|
||||||
|
case st: ScalarType if ScalarType.number(st) => "number"
|
||||||
|
case ScalarType.bool => "boolean"
|
||||||
|
case ScalarType.string => "string"
|
||||||
|
case lt: LiteralType if lt.oneOf.exists(ScalarType.number) => "number"
|
||||||
|
case lt: LiteralType if lt.oneOf(ScalarType.bool) => "boolean"
|
||||||
|
case lt: LiteralType if lt.oneOf(ScalarType.string) => "string"
|
||||||
|
case _: DataType => "any"
|
||||||
|
case at: ArrowType => fnDef(at)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 returnType(at: ArrowType): String =
|
||||||
|
at.res.fold("void")(typeToTs)
|
||||||
|
|
||||||
|
def fnDef(at: ArrowType): String =
|
||||||
|
val args = argsToTs(at)
|
||||||
|
.concat(List(callParamsArg(at)))
|
||||||
|
.mkString(", ")
|
||||||
|
|
||||||
|
val retType = returnType(at)
|
||||||
|
|
||||||
|
s"(${args}) => ${retType}"
|
||||||
|
|
||||||
|
def argsToTs(at: ArrowType): List[String] =
|
||||||
|
FuncRes
|
||||||
|
.arrowArgs(at)
|
||||||
|
.map(nt => nt.name + ": " + typeToTs(nt.`type`))
|
||||||
|
|
||||||
|
def callParamsArg(at: ArrowType): String =
|
||||||
|
val args = FuncRes.arrowArgs(at)
|
||||||
|
val generic = if (args.length > 0) {
|
||||||
|
val prep = args
|
||||||
|
.map(_.name)
|
||||||
|
.mkString("' | '")
|
||||||
|
|
||||||
|
"'" + prep + "'"
|
||||||
|
} else {
|
||||||
|
"null"
|
||||||
|
}
|
||||||
|
s"callParams: CallParams<${generic}>"
|
||||||
|
|
||||||
|
def argsCallToTs(at: ArrowType): List[String] =
|
||||||
|
FuncRes.arrowArgIndices(at).map(idx => s"args[$idx]")
|
||||||
|
|
||||||
|
def callBackExprBody(at: ArrowType, callbackName: String): String = {
|
||||||
|
val arrowArgumentsToCallbackArgumentsList =
|
||||||
|
at.domain.toList
|
||||||
|
.zipWithIndex
|
||||||
|
.map(_._2)
|
||||||
|
.map(idx => s"req.args[$idx]")
|
||||||
|
.concat(List("callParams"))
|
||||||
|
.mkString(", ")
|
||||||
|
|
||||||
|
val callCallbackStatement = s"$callbackName(${arrowArgumentsToCallbackArgumentsList})"
|
||||||
|
|
||||||
|
val callCallbackStatementAndReturn =
|
||||||
|
at.res.fold(s"${callCallbackStatement}; resp.result = {}")(_ =>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -30,9 +30,13 @@ object TypeScriptFile {
|
|||||||
| * Aqua version: ${Version.version}
|
| * Aqua version: ${Version.version}
|
||||||
| *
|
| *
|
||||||
| */
|
| */
|
||||||
|import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
|
|import { FluencePeer } from '@fluencelabs/fluence';
|
||||||
|import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
|
|import {
|
||||||
|import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
|
| ResultCodes,
|
||||||
|
| RequestFlow,
|
||||||
|
| RequestFlowBuilder,
|
||||||
|
| CallParams,
|
||||||
|
|} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1';
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,44 +7,42 @@ import cats.syntax.show.*
|
|||||||
|
|
||||||
case class TypeScriptFunc(func: FuncRes) {
|
case class TypeScriptFunc(func: FuncRes) {
|
||||||
|
|
||||||
import TypeScriptFunc._
|
import TypeScriptCommon._
|
||||||
import FuncRes._
|
import FuncRes._
|
||||||
import func._
|
import func._
|
||||||
|
|
||||||
def argsTypescript: String =
|
private def returnCallback: String =
|
||||||
args.map(ad => s"${ad.name}: " + typeToTs(ad.`type`)).mkString(", ")
|
val respBody = func.returnType match {
|
||||||
|
case Some(x) => x match {
|
||||||
private def returnCallback: String = returnType.fold("") { retType =>
|
case OptionType(_) =>
|
||||||
val respBody = retType match {
|
""" let [opt] = args;
|
||||||
case OptionType(_) =>
|
| if (Array.isArray(opt)) {
|
||||||
""" let [opt] = args;
|
| if (opt.length === 0) { resolve(null); }
|
||||||
| if (Array.isArray(opt)) {
|
| opt = opt[0];
|
||||||
| if (opt.length === 0) { resolve(null); }
|
| }
|
||||||
| opt = opt[0];
|
| return resolve(opt);""".stripMargin
|
||||||
| }
|
case pt: ProductType =>
|
||||||
| return resolve(opt);""".stripMargin
|
val unwrapOpts = pt.toList.zipWithIndex.collect { case (OptionType(_), i) =>
|
||||||
case pt: ProductType =>
|
s"""
|
||||||
val unwrapOpts = pt.toList.zipWithIndex.collect { case (OptionType(_), i) =>
|
| if(Array.isArray(opt[$i])) {
|
||||||
s"""
|
| if (opt[$i].length === 0) { opt[$i] = null; }
|
||||||
| if(Array.isArray(opt[$i])) {
|
| else {opt[$i] = opt[$i][0]; }
|
||||||
| if (opt[$i].length === 0) { opt[$i] = null; }
|
| }""".stripMargin
|
||||||
| else {opt[$i] = opt[$i][0]; }
|
}.mkString
|
||||||
| }""".stripMargin
|
|
||||||
}.mkString
|
|
||||||
|
|
||||||
s""" let opt: any = args;
|
|
||||||
|$unwrapOpts
|
|
||||||
| return resolve(opt);""".stripMargin
|
|
||||||
case _ =>
|
|
||||||
""" const [res] = args;
|
|
||||||
| resolve(res);""".stripMargin
|
|
||||||
|
|
||||||
|
s""" let opt: any = args;
|
||||||
|
|$unwrapOpts
|
||||||
|
| return resolve(opt);""".stripMargin
|
||||||
|
case _ =>
|
||||||
|
""" const [res] = args;
|
||||||
|
| resolve(res);""".stripMargin
|
||||||
|
}
|
||||||
|
case None => ""
|
||||||
}
|
}
|
||||||
s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => {
|
s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => {
|
||||||
| $respBody
|
| $respBody
|
||||||
|});
|
|});
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
}
|
|
||||||
|
|
||||||
def generate: String = {
|
def generate: String = {
|
||||||
|
|
||||||
@ -55,100 +53,103 @@ case class TypeScriptFunc(func: FuncRes) {
|
|||||||
|
|
||||||
val setCallbacks = func.args.collect { // Product types are not handled
|
val setCallbacks = func.args.collect { // Product types are not handled
|
||||||
case Arg(argName, OptionType(_)) =>
|
case Arg(argName, OptionType(_)) =>
|
||||||
s"""h.on('$dataServiceId', '$argName', () => {return $argName === null ? [] : [$argName];});"""
|
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)} === null ? [] : [${fixupArgName(argName)}];});"""
|
||||||
case Arg(argName, _: DataType) =>
|
case Arg(argName, _: DataType) =>
|
||||||
s"""h.on('$dataServiceId', '$argName', () => {return $argName;});"""
|
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)};});"""
|
||||||
case Arg(argName, at: ArrowType) =>
|
case Arg(argName, at: ArrowType) =>
|
||||||
val value = s"$argName(${argsCallToTs(
|
s"""
|
||||||
at
|
| h.use((req, resp, next) => {
|
||||||
)})"
|
| if(req.serviceId === '${conf.callbackService}' && req.fnName === '$argName') {
|
||||||
val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value")
|
| ${callBackExprBody(at, argName)}
|
||||||
s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});"""
|
| }
|
||||||
|
| next();
|
||||||
|
| });
|
||||||
|
""".stripMargin
|
||||||
}
|
}
|
||||||
.mkString("\n")
|
.mkString("\n")
|
||||||
|
|
||||||
val returnVal =
|
val returnVal =
|
||||||
returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
func.returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||||
|
|
||||||
val clientArgName = genArgName("client")
|
val clientArgName = genArgName("client")
|
||||||
val configArgName = genArgName("config")
|
val configArgName = genArgName("config")
|
||||||
|
|
||||||
val configType = "{ttl?: number}"
|
val configType = "{ttl?: number}"
|
||||||
|
|
||||||
|
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)};").mkString("\n")
|
||||||
|
|
||||||
|
val argsFormAssingment = args
|
||||||
|
.map(arg => fixupArgName(arg.name))
|
||||||
|
.concat(List("config"))
|
||||||
|
.zipWithIndex
|
||||||
|
|
||||||
|
// 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>"
|
||||||
|
|
||||||
s"""
|
s"""
|
||||||
|export async function ${funcName}($clientArgName: FluenceClient${if (args.isEmpty)
|
| export function ${func.funcName}(${funcTypeOverload1}) : ${funcTypeRes};
|
||||||
""
|
| export function ${func.funcName}(${funcTypeOverload2}) : ${funcTypeRes};
|
||||||
else ", "}${argsTypescript}, $configArgName?: $configType): Promise<$retTypeTs> {
|
| export function ${func.funcName}(...args) {
|
||||||
| let request: RequestFlow;
|
| let peer: FluencePeer;
|
||||||
| const promise = new Promise<$retTypeTs>((resolve, reject) => {
|
| ${argsLets}
|
||||||
| const r = new RequestFlowBuilder()
|
| let config;
|
||||||
| .disableInjections()
|
| if (args[0] instanceof FluencePeer) {
|
||||||
| .withRawScript(
|
| peer = args[0];
|
||||||
| `
|
| ${argsAssignmentStartingFrom1}
|
||||||
|${tsAir.show}
|
| } else {
|
||||||
| `,
|
| peer = FluencePeer.default;
|
||||||
| )
|
| ${argsAssignmentStartingFrom0}
|
||||||
| .configHandler((h) => {
|
| }
|
||||||
| ${relayVarName.fold("") { r =>
|
|
|
||||||
s"""h.on('$dataServiceId', '$r', () => {
|
| let request: RequestFlow;
|
||||||
| return $clientArgName.relayPeerId!;
|
| const promise = new Promise<$retTypeTs>((resolve, reject) => {
|
||||||
| });""".stripMargin
|
| const r = new RequestFlowBuilder()
|
||||||
}}
|
| .disableInjections()
|
||||||
|
| .withRawScript(
|
||||||
|
| `
|
||||||
|
| ${tsAir.show}
|
||||||
|
| `,
|
||||||
|
| )
|
||||||
|
| .configHandler((h) => {
|
||||||
|
| ${conf.relayVarName.fold("") { r =>
|
||||||
|
s"""h.on('${conf.getDataService}', '$r', () => {
|
||||||
|
| return peer.connectionInfo.connectedRelay ;
|
||||||
|
| });""".stripMargin }}
|
||||||
| $setCallbacks
|
| $setCallbacks
|
||||||
| $returnCallback
|
| $returnCallback
|
||||||
| h.onEvent('$errorHandlerId', '$errorFuncName', (args) => {
|
| h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => {
|
||||||
| // assuming error is the single argument
|
|
||||||
| const [err] = args;
|
| const [err] = args;
|
||||||
| reject(err);
|
| reject(err);
|
||||||
| });
|
| });
|
||||||
| })
|
| })
|
||||||
| .handleScriptError(reject)
|
| .handleScriptError(reject)
|
||||||
| .handleTimeout(() => {
|
| .handleTimeout(() => {
|
||||||
| reject('Request timed out for ${funcName}');
|
| reject('Request timed out for ${func.funcName}');
|
||||||
| })
|
| })
|
||||||
| if(${configArgName} && ${configArgName}.ttl) {
|
| if(${configArgName} && ${configArgName}.ttl) {
|
||||||
| r.withTTL(${configArgName}.ttl)
|
| r.withTTL(${configArgName}.ttl)
|
||||||
| }
|
| }
|
||||||
| request = r.build();
|
| request = r.build();
|
||||||
| });
|
| });
|
||||||
| await $clientArgName.initiateFlow(request!);
|
| peer.internals.initiateFlow(request!);
|
||||||
| return ${returnVal};
|
| return ${returnVal};
|
||||||
|}
|
|}
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object TypeScriptFunc {
|
|
||||||
|
|
||||||
def typeToTs(t: Type): String = t match {
|
|
||||||
case OptionType(t) => typeToTs(t) + " | null"
|
|
||||||
case ArrayType(t) => typeToTs(t) + "[]"
|
|
||||||
case StreamType(t) => typeToTs(t) + "[]"
|
|
||||||
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(";")}}"
|
|
||||||
case st: ScalarType if ScalarType.number(st) => "number"
|
|
||||||
case ScalarType.bool => "boolean"
|
|
||||||
case ScalarType.string => "string"
|
|
||||||
case lt: LiteralType if lt.oneOf.exists(ScalarType.number) => "number"
|
|
||||||
case lt: LiteralType if lt.oneOf(ScalarType.bool) => "boolean"
|
|
||||||
case lt: LiteralType if lt.oneOf(ScalarType.string) => "string"
|
|
||||||
case _: DataType => "any"
|
|
||||||
case at: ArrowType =>
|
|
||||||
s"(${argsToTs(at)}) => ${FuncRes
|
|
||||||
.arrowToRes(at)
|
|
||||||
.fold("void")(typeToTs)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def argsToTs(at: ArrowType): String =
|
|
||||||
FuncRes
|
|
||||||
.arrowArgs(at)
|
|
||||||
.map(nt => nt.name + ": " + typeToTs(nt.`type`))
|
|
||||||
.mkString(", ")
|
|
||||||
|
|
||||||
def argsCallToTs(at: ArrowType): String =
|
|
||||||
FuncRes.arrowArgIndices(at).map(idx => s"args[$idx]").mkString(", ")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,20 +1,125 @@
|
|||||||
package aqua.backend.ts
|
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
|
import aqua.model.transform.res.ServiceRes
|
||||||
|
|
||||||
case class TypeScriptService(srv: ServiceRes) {
|
case class TypeScriptService(srv: ServiceRes) {
|
||||||
|
|
||||||
import TypeScriptFunc.typeToTs
|
import TypeScriptCommon._
|
||||||
|
|
||||||
|
def fnHandler(arrow: ArrowType, memberName: String) = {
|
||||||
|
s"""
|
||||||
|
| if (req.fnName === '${memberName}') {
|
||||||
|
| ${callBackExprBody(arrow, "service." + memberName)}
|
||||||
|
| }
|
||||||
|
""".stripMargin
|
||||||
|
}
|
||||||
|
|
||||||
def generate: String =
|
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
|
||||||
|
)
|
||||||
|
|
||||||
s"""
|
s"""
|
||||||
|//${srv.name}
|
| export interface ${serviceTypeName} {
|
||||||
|//defaultId = ${srv.defaultId.getOrElse("undefined")}
|
| ${fnDefs}
|
||||||
|
|
| }
|
||||||
|${srv.members.map { case (n, v) =>
|
|
|
||||||
s"//${n}: ${typeToTs(v)}"
|
| ${registerServiceArgs}
|
||||||
}.mkString("\n")}
|
| export function ${registerName}(...args) {
|
||||||
|//END ${srv.name}
|
| let peer: FluencePeer;
|
||||||
|
|
| let serviceId;
|
||||||
|""".stripMargin
|
| let service;
|
||||||
|
| if (args[0] instanceof FluencePeer) {
|
||||||
|
| peer = args[0];
|
||||||
|
| } else {
|
||||||
|
| peer = FluencePeer.default;
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| if (typeof args[0] === 'string') {
|
||||||
|
| serviceId = args[0];
|
||||||
|
| } else if (typeof args[1] === 'string') {
|
||||||
|
| serviceId = args[1];
|
||||||
|
| } ${defaultServiceIdBranch}
|
||||||
|
|
|
||||||
|
| if (!(args[0] instanceof FluencePeer) && typeof args[0] === 'object') {
|
||||||
|
| service = args[0];
|
||||||
|
| } else if (typeof args[1] === 'object') {
|
||||||
|
| service = args[1];
|
||||||
|
| } else {
|
||||||
|
| service = args[2];
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| peer.internals.callServiceHandler.use((req, resp, next) => {
|
||||||
|
| if (req.serviceId !== serviceId) {
|
||||||
|
| next();
|
||||||
|
| return;
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| ${fnHandlers}
|
||||||
|
|
|
||||||
|
| next();
|
||||||
|
| });
|
||||||
|
| }
|
||||||
|
""".stripMargin
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user