mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-02 21:50:18 +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
|
||||
metals.sbt
|
||||
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"
|
||||
|
||||
override def generate(aqua: AquaRes): Seq[Generated] = {
|
||||
val funcs = NonEmptyChain.fromChain(
|
||||
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)
|
||||
}
|
||||
override def generate(res: AquaRes): Seq[Generated] =
|
||||
if (res.isEmpty) Nil else Generated(ext, JavaScriptFile(res).generate) :: Nil
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
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 {
|
||||
|
||||
@ -13,7 +30,13 @@ object JavaScriptFile {
|
||||
| * 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
|
||||
|
||||
}
|
||||
|
@ -7,117 +7,132 @@ import cats.syntax.show.*
|
||||
|
||||
case class JavaScriptFunc(func: FuncRes) {
|
||||
|
||||
import JavaScriptFunc._
|
||||
import JavaScriptCommon._
|
||||
import FuncRes._
|
||||
import func._
|
||||
|
||||
def argsJavaScript: String =
|
||||
argNames.mkString(", ")
|
||||
|
||||
// TODO: use common functions between TypeScript and JavaScript backends
|
||||
private def returnCallback: String = returnType.fold("") { retType =>
|
||||
val respBody = retType 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
|
||||
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 {
|
||||
val setCallbacks = func.args.collect { // Product types are not handled
|
||||
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) =>
|
||||
s"""h.on('$dataServiceId', '$argName', () => {return $argName;});"""
|
||||
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)};});"""
|
||||
case Arg(argName, at: ArrowType) =>
|
||||
val value = s"$argName(${argsCallToJs(
|
||||
at
|
||||
)})"
|
||||
val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value")
|
||||
s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});"""
|
||||
s"""
|
||||
| h.use((req, resp, next) => {
|
||||
| if(req.serviceId === '${conf.callbackService}' && req.fnName === '$argName') {
|
||||
| ${callBackExprBody(at, argName)}
|
||||
| }
|
||||
| next();
|
||||
| });
|
||||
""".stripMargin
|
||||
}
|
||||
.mkString("\n")
|
||||
|
||||
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 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 async function ${func.funcName}(${clientArgName}${if (func.args.isEmpty) ""
|
||||
else ", "}${argsJavaScript}, $configArgName) {
|
||||
| let request;
|
||||
| $configArgName = $configArgName || {};
|
||||
| const promise = new Promise((resolve, reject) => {
|
||||
| var r = new RequestFlowBuilder()
|
||||
| .disableInjections()
|
||||
| .withRawScript(
|
||||
| `
|
||||
|${jsAir.show}
|
||||
| `,
|
||||
| )
|
||||
| .configHandler((h) => {
|
||||
| ${relayVarName.fold("") { r =>
|
||||
s"""h.on('$dataServiceId', '$r', () => {
|
||||
| return client.relayPeerId;
|
||||
| });""".stripMargin
|
||||
}}
|
||||
| export function ${func.funcName}(...args) {
|
||||
| let peer;
|
||||
| ${argsLets}
|
||||
| let config;
|
||||
| if (args[0] instanceof FluencePeer) {
|
||||
| peer = args[0];
|
||||
| ${argsAssignmentStartingFrom1}
|
||||
| } else {
|
||||
| peer = FluencePeer.default;
|
||||
| ${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.connectionInfo.connectedRelay ;
|
||||
| });""".stripMargin }}
|
||||
| $setCallbacks
|
||||
| $returnCallback
|
||||
| h.onEvent('$errorHandlerId', '$errorFuncName', (args) => {
|
||||
| // assuming error is the single argument
|
||||
| h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => {
|
||||
| const [err] = args;
|
||||
| reject(err);
|
||||
| });
|
||||
| })
|
||||
| .handleScriptError(reject)
|
||||
| .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)
|
||||
| }
|
||||
| request = r.build();
|
||||
| });
|
||||
| await ${clientArgName}.initiateFlow(request);
|
||||
| peer.internals.initiateFlow(request);
|
||||
| return ${returnVal};
|
||||
|}
|
||||
""".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}
|
||||
| *
|
||||
| */
|
||||
|import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
|
||||
|import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
|
||||
|import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
|
||||
|import { FluencePeer } from '@fluencelabs/fluence';
|
||||
|import {
|
||||
| ResultCodes,
|
||||
| RequestFlow,
|
||||
| RequestFlowBuilder,
|
||||
| CallParams,
|
||||
|} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1';
|
||||
|""".stripMargin
|
||||
|
||||
}
|
||||
|
@ -7,44 +7,42 @@ import cats.syntax.show.*
|
||||
|
||||
case class TypeScriptFunc(func: FuncRes) {
|
||||
|
||||
import TypeScriptFunc._
|
||||
import TypeScriptCommon._
|
||||
import FuncRes._
|
||||
import func._
|
||||
|
||||
def argsTypescript: String =
|
||||
args.map(ad => s"${ad.name}: " + typeToTs(ad.`type`)).mkString(", ")
|
||||
|
||||
private def returnCallback: String = returnType.fold("") { retType =>
|
||||
val respBody = retType 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: any = args;
|
||||
|$unwrapOpts
|
||||
| return resolve(opt);""".stripMargin
|
||||
case _ =>
|
||||
""" const [res] = args;
|
||||
| resolve(res);""".stripMargin
|
||||
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: any = 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 = {
|
||||
|
||||
@ -55,100 +53,103 @@ case class TypeScriptFunc(func: FuncRes) {
|
||||
|
||||
val setCallbacks = func.args.collect { // Product types are not handled
|
||||
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) =>
|
||||
s"""h.on('$dataServiceId', '$argName', () => {return $argName;});"""
|
||||
s"""h.on('$dataServiceId', '$argName', () => {return ${fixupArgName(argName)};});"""
|
||||
case Arg(argName, at: ArrowType) =>
|
||||
val value = s"$argName(${argsCallToTs(
|
||||
at
|
||||
)})"
|
||||
val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value")
|
||||
s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});"""
|
||||
s"""
|
||||
| h.use((req, resp, next) => {
|
||||
| if(req.serviceId === '${conf.callbackService}' && req.fnName === '$argName') {
|
||||
| ${callBackExprBody(at, argName)}
|
||||
| }
|
||||
| next();
|
||||
| });
|
||||
""".stripMargin
|
||||
}
|
||||
.mkString("\n")
|
||||
|
||||
val returnVal =
|
||||
returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||
func.returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||
|
||||
val clientArgName = genArgName("client")
|
||||
val configArgName = genArgName("config")
|
||||
|
||||
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"""
|
||||
|export async function ${funcName}($clientArgName: FluenceClient${if (args.isEmpty)
|
||||
""
|
||||
else ", "}${argsTypescript}, $configArgName?: $configType): Promise<$retTypeTs> {
|
||||
| let request: RequestFlow;
|
||||
| const promise = new Promise<$retTypeTs>((resolve, reject) => {
|
||||
| const r = new RequestFlowBuilder()
|
||||
| .disableInjections()
|
||||
| .withRawScript(
|
||||
| `
|
||||
|${tsAir.show}
|
||||
| `,
|
||||
| )
|
||||
| .configHandler((h) => {
|
||||
| ${relayVarName.fold("") { r =>
|
||||
s"""h.on('$dataServiceId', '$r', () => {
|
||||
| return $clientArgName.relayPeerId!;
|
||||
| });""".stripMargin
|
||||
}}
|
||||
| export function ${func.funcName}(${funcTypeOverload1}) : ${funcTypeRes};
|
||||
| export function ${func.funcName}(${funcTypeOverload2}) : ${funcTypeRes};
|
||||
| export function ${func.funcName}(...args) {
|
||||
| let peer: FluencePeer;
|
||||
| ${argsLets}
|
||||
| let config;
|
||||
| if (args[0] instanceof FluencePeer) {
|
||||
| peer = args[0];
|
||||
| ${argsAssignmentStartingFrom1}
|
||||
| } else {
|
||||
| peer = FluencePeer.default;
|
||||
| ${argsAssignmentStartingFrom0}
|
||||
| }
|
||||
|
|
||||
| let request: RequestFlow;
|
||||
| const promise = new Promise<$retTypeTs>((resolve, reject) => {
|
||||
| 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
|
||||
| $returnCallback
|
||||
| h.onEvent('$errorHandlerId', '$errorFuncName', (args) => {
|
||||
| // assuming error is the single argument
|
||||
| h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => {
|
||||
| const [err] = args;
|
||||
| reject(err);
|
||||
| });
|
||||
| })
|
||||
| .handleScriptError(reject)
|
||||
| .handleTimeout(() => {
|
||||
| reject('Request timed out for ${funcName}');
|
||||
| reject('Request timed out for ${func.funcName}');
|
||||
| })
|
||||
| if(${configArgName} && ${configArgName}.ttl) {
|
||||
| r.withTTL(${configArgName}.ttl)
|
||||
| }
|
||||
| request = r.build();
|
||||
| });
|
||||
| await $clientArgName.initiateFlow(request!);
|
||||
| peer.internals.initiateFlow(request!);
|
||||
| return ${returnVal};
|
||||
|}
|
||||
""".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
|
||||
|
||||
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 TypeScriptFunc.typeToTs
|
||||
import TypeScriptCommon._
|
||||
|
||||
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 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"""
|
||||
|//${srv.name}
|
||||
|//defaultId = ${srv.defaultId.getOrElse("undefined")}
|
||||
|
|
||||
|${srv.members.map { case (n, v) =>
|
||||
s"//${n}: ${typeToTs(v)}"
|
||||
}.mkString("\n")}
|
||||
|//END ${srv.name}
|
||||
|
|
||||
|""".stripMargin
|
||||
| export interface ${serviceTypeName} {
|
||||
| ${fnDefs}
|
||||
| }
|
||||
|
|
||||
| ${registerServiceArgs}
|
||||
| export function ${registerName}(...args) {
|
||||
| let peer: FluencePeer;
|
||||
| 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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user