Add support for the new features of JS SDK API (#251)

This commit is contained in:
Pavel 2021-09-08 13:16:59 +03:00 committed by GitHub
parent d881f5bdbe
commit 3e1618c734
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 564 additions and 197 deletions

2
.gitignore vendored
View File

@ -5,4 +5,4 @@
.bloop
metals.sbt
target
project/target
project/target

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"files.watcherExclude": {
"**/target": true
}
}

View File

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

View File

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

View File

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

View File

@ -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(", ")
}

View File

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

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

View File

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

View File

@ -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(", ")
}

View File

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