Skip to content

Commit

Permalink
Run aqua code from CLI (#324)
Browse files Browse the repository at this point in the history
  • Loading branch information
DieMyst authored Oct 13, 2021
1 parent 4e63da8 commit 3844d8f
Show file tree
Hide file tree
Showing 27 changed files with 592 additions and 169 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/test_branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ jobs:
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\""
cd aqua-playground
mv cli/.js/target/scala-3.0.2/cli-fastopt.js npm/aqua.mjs
cd npm
npm i
node aqua.mjs -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
29 changes: 21 additions & 8 deletions backend/ts/src/main/scala/aqua/backend/Header.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,25 @@ package aqua.backend

object Header {

def header(isJs: Boolean): String =
def header(isJs: Boolean, isCommonJS: Boolean): String = {
val imports = if (isCommonJS) {
"""
|const { Fluence, FluencePeer } = require('@fluencelabs/fluence');
|const {
| ResultCodes,
| RequestFlow,
| RequestFlowBuilder,
| CallParams,} = require('@fluencelabs/fluence/dist/internal/compilerSupport/v1${if (isJs) ".js" else ""}');
|""".stripMargin
} else {
s"""import { Fluence, FluencePeer } from '@fluencelabs/fluence';
|import {
| ResultCodes,
| RequestFlow,
| RequestFlowBuilder,
| CallParams
|} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1${if (isJs) ".js" else ""}';""".stripMargin
}
s"""/**
| *
| * This file is auto-generated. Do not edit manually: changes may be erased.
Expand All @@ -11,12 +29,7 @@ object Header {
| * 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 ""}';
|$imports
|""".stripMargin
}
}
4 changes: 2 additions & 2 deletions backend/ts/src/main/scala/aqua/backend/OutputFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import aqua.model.transform.res.AquaRes

case class OutputFile(res: AquaRes) {

def generate(types: Types): String = {
def generate(types: Types, isCommonJS: Boolean): String = {
import types.*
val services = res.services
.map(s => OutputService(s, types))
Expand All @@ -15,7 +15,7 @@ case class OutputFile(res: AquaRes) {
.mkString("\n\n")
val functions =
res.funcs.map(f => OutputFunc(f, types)).map(_.generate).toList.mkString("\n\n")
s"""${Header.header(false)}
s"""${Header.header(false, isCommonJS)}
|
|function ${typed(
s"""missingFields(${typed("obj", "any")}, ${typed("fields", "string[]")})""",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 {
case class JavaScriptBackend(isCommonJS: Boolean) extends Backend {

val ext = ".js"
val tsExt = ".d.ts"
Expand All @@ -18,7 +18,7 @@ object JavaScriptBackend extends Backend {
val functions =
res.funcs.map(f => TypeScriptTypes.funcType(f)).map(_.generate).toList.mkString("\n\n")

val body = s"""${Header.header(false)}
val body = s"""${Header.header(false, false)}
|
|// Services
|$services
Expand All @@ -33,6 +33,6 @@ object JavaScriptBackend extends Backend {
override def generate(res: AquaRes): Seq[Generated] =
if (res.isEmpty) Nil
else {
Generated(ext, OutputFile(res).generate(EmptyTypes)):: typesFile(res) :: Nil
Generated(ext, OutputFile(res).generate(EmptyTypes, isCommonJS)):: typesFile(res) :: Nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, OutputFile(res).generate(TypeScriptTypes)) :: Nil
if (res.isEmpty) Nil else Generated(ext, OutputFile(res).generate(TypeScriptTypes, false)) :: Nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import aqua.model.transform.res.AquaRes

case class TypeScriptTypesFile(res: AquaRes) {
def generate: String =
s"""${Header.header(false)}
s"""${Header.header(false, false)}
|
|// Services
|${res.services.map(TSServiceTypes(_)).map(_.generate).toList.mkString("\n\n")}
Expand Down
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ val declineV = "2.1.0"
name := "aqua-hll"

val commons = Seq(
baseAquaVersion := "0.3.1",
baseAquaVersion := "0.3.2",
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion,
libraryDependencies ++= Seq(
Expand Down Expand Up @@ -56,7 +56,7 @@ lazy val cli = crossProject(JSPlatform, JVMPlatform)

lazy val cliJS = cli.js
.settings(
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.ESModule)),
scalaJSUseMainModuleInitializer := true
)

Expand Down
86 changes: 86 additions & 0 deletions cli/.js/src/main/scala/aqua/CallJsFunction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package aqua

import aqua.model.transform.TransformConfig
import aqua.model.transform.res.FuncRes
import aqua.types.Type

import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.scalajs.js

object CallJsFunction {

// Register a service that returns no result
def registerUnitService(
peer: FluencePeer,
serviceId: String,
fnName: String,
handler: (js.Array[js.Any]) => Unit
) = {
peer.internals.callServiceHandler.use((req, resp, next) => {
if (req.serviceId == serviceId && req.fnName == fnName) {
handler(req.args)
resp.retCode = ResultCodes.success
resp.result = new js.Object {}
}

next()
})
}

// Call a function with generated air script
def funcCallJs(
peer: FluencePeer,
air: String,
args: List[(String, js.Any)],
returnType: Option[Type],
config: TransformConfig
)(implicit ec: ExecutionContext): Future[Any] = {
val resultPromise: Promise[js.Any] = Promise[js.Any]()

val requestBuilder = new RequestFlowBuilder()
val relayPeerId = peer.getStatus().relayPeerId

requestBuilder
.disableInjections()
.withRawScript(air)
.configHandler((handler, r) => {
handler.on(config.getDataService, config.relayVarName.getOrElse("-relay-"), (_, _) => { relayPeerId })
args.foreach { (fnName, arg) =>
handler.on(config.getDataService, fnName, (_, _) => arg)
}
handler.onEvent(
config.callbackService,
config.respFuncName,
(args, _) => {
if (args.length == 1) {
resultPromise.success(args.pop())
} else if (args.length == 0) {
resultPromise.success(())
} else {
resultPromise.success(args)
}
()
}
)
handler.onEvent(
config.errorHandlingService,
config.errorFuncName,
(args, _) => {
resultPromise.failure(new RuntimeException(args.pop().toString))
()
}
)
})
.handleScriptError((err) => {
resultPromise.failure(new RuntimeException("script error: " + err.toString))
})
.handleTimeout(() => {
if (!resultPromise.isCompleted)
resultPromise.failure(new RuntimeException(s"Request timed out"))
})

peer.internals.initiateFlow(requestBuilder.build()).toFuture.flatMap { _ =>
returnType.fold(resultPromise.success(()).future)(_ => resultPromise.future)
}
}
}
136 changes: 136 additions & 0 deletions cli/.js/src/main/scala/aqua/JsTypes.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package aqua

import scala.concurrent.Promise
import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport

/***
* This is description of types from Fluence JS library.
* See here for details https://github.com/fluencelabs/fluence-js
*/

/**
* Particle context. Contains additional information about particle which triggered `call` air instruction from AVM
*/
trait ParticleContext {
def particleId: String
def initPeerId: String
def timestamp: Int
def ttl: Int
def signature: String
}

object ResultCodes {
val success = 0
val unknownError = 1
val exceptionInHandler = 2
}

/**
* Represents the result of the `call` air instruction to be returned into AVM
*/
trait CallServiceResult extends js.Object {
def retCode: Int
def retCode_=(code: Int): Unit
def result: js.Any
def result_=(res: js.Any): Unit
}

/**
* Represents the information passed from AVM when a `call` air instruction is executed on the local peer
*/
trait CallServiceData extends js.Object {
def serviceId: String
def fnName: String
def args: js.Array[js.Any]
def particleContext: ParticleContext
def tetraplets: js.Any
}

trait Internals extends js.Object {
def initiateFlow(r: RequestFlow): js.Promise[js.Any]
def callServiceHandler: CallServiceHandler
}

/**
* Information about Fluence Peer connection
*/
trait PeerStatus extends js.Object {
def isInitialized: Boolean
def isConnected: Boolean
def peerId: String
def relayPeerId: String
}

/**
* This class implements the Fluence protocol for javascript-based environments.
* It provides all the necessary features to communicate with Fluence network
*/
@js.native
@JSImport("@fluencelabs/fluence/dist/internal/compilerSupport/v1.js", "FluencePeer")
class FluencePeer extends js.Object {
val internals: Internals = js.native
def getStatus(): PeerStatus = js.native
def stop(): js.Promise[Unit] = js.native
}

/**
* Public interface to Fluence JS SDK
*/
@js.native
@JSImport("@fluencelabs/fluence", "Fluence")
object Fluence extends js.Object {
def start(str: String): js.Promise[js.Any] = js.native
def stop(): js.Promise[js.Any] = js.native
def getPeer(): FluencePeer = js.native
def getStatus(): PeerStatus = js.native
}

/**
* Class defines the handling of a `call` air intruction executed by AVM on the local peer.
* All the execution process is defined by the chain of middlewares - architecture popular among backend web frameworks.
*/
@js.native
@JSImport("@fluencelabs/fluence/dist/internal/compilerSupport/v1.js", "CallServiceHandler")
class CallServiceHandler extends js.Object {

def on(
serviceId: String,
fnName: String,
handler: js.Function2[js.Array[js.Any], js.Any, js.Any]
): js.Function0[CallServiceHandler] = js.native

def onEvent(
serviceId: String,
fnName: String,
handler: js.Function2[js.Array[js.Any], js.Any, js.Any]
): js.Function0[CallServiceHandler] = js.native

def use(f: js.Function3[CallServiceData, CallServiceResult, js.Function0[Unit], Unit]): CallServiceHandler = js.native
}

/**
* The class represents the current view (and state) of distributed the particle execution process from client's point of view.
* It stores the intermediate particles state during the process. RequestFlow is identified by the id of the particle that is executed during the flow.
* Each RequestFlow contains a separate (unique to the current flow) CallServiceHandler where the handling of `call` AIR instruction takes place
* Please note, that RequestFlow's is handler is combined with the handler from client before the execution occures.
* After the combination middlewares from RequestFlow are executed before client handler's middlewares.
*/
@js.native
@JSImport("@fluencelabs/fluence/dist/internal/compilerSupport/v1.js", "RequestFlow")
class RequestFlow extends js.Object {}

/**
* Builder class for configuring and creating Request Flows
*/
@js.native
@JSImport("@fluencelabs/fluence/dist/internal/compilerSupport/v1.js", "RequestFlowBuilder")
class RequestFlowBuilder extends js.Object {
def withRawScript(air: String): RequestFlowBuilder = js.native
def configHandler(f: js.Function2[CallServiceHandler, js.Any, Unit]): RequestFlowBuilder =
js.native
def disableInjections(): RequestFlowBuilder = js.native
def build(): RequestFlow = js.native
def handleScriptError(f: js.Function1[js.Any, Unit]): RequestFlowBuilder = js.native
def handleTimeout(f: js.Function0[Unit]): RequestFlowBuilder = js.native
}
Loading

0 comments on commit 3844d8f

Please sign in to comment.