feat(aqua-api): return JS and TS sources from API [LNG-164] (#730)

This commit is contained in:
Dima 2023-06-07 15:07:14 +03:00 committed by GitHub
parent 21cb3937ac
commit 0b66aa96ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 609 additions and 359 deletions

View File

@ -40,7 +40,7 @@ jobs:
run: sbt cliJS/fullLinkJS
- name: JS API build
run: sbt aqua-api/fullLinkJS
run: sbt aqua-apiJS/fullLinkJS
- name: JS LSP build
run: sbt language-server-apiJS/fullLinkJS
@ -55,7 +55,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: aqua-api
path: api/aqua-api/target/scala-*/aqua-api-opt/main.js
path: api/aqua-api/.js/target/scala-*/aqua-api-opt/main.js
- name: Upload aqua-lsp artifact
uses: actions/upload-artifact@v3

View File

@ -63,7 +63,7 @@ jobs:
- name: JS API build
env:
SNAPSHOT: ${{ steps.version.outputs.id }}
run: sbt aqua-api/fastOptJS
run: sbt aqua-apiJS/fastOptJS
- name: Upload aqua-cli artifact
uses: actions/upload-artifact@v3
@ -75,7 +75,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: aqua-api
path: api/aqua-api/target/scala-*/aqua-api-fastopt.js
path: api/aqua-api/.js/target/scala-*/aqua-api-fastopt.js
- name: Upload aqua-lsp artifact
uses: actions/upload-artifact@v3

View File

@ -2,11 +2,13 @@ import type {FunctionCallDef, ServiceDef} from "@fluencelabs/fluence/dist/intern
export class AquaConfig {
constructor(logLevel: string, constants: string[], noXor: boolean, noRelay: boolean);
constructor(logLevel: string, constants: string[], noXor: boolean, noRelay: boolean, targetType: string);
logLevel?: string
constants?: string[]
noXor?: boolean
noRelay?: boolean
targetType?: string
}
export class AquaFunction {
@ -14,11 +16,19 @@ export class AquaFunction {
script: string
}
export class GeneratedSource {
name: string
tsSource?: string
jsSource?: string
tsTypes?: string
}
export class CompilationResult {
services: Record<string, ServiceDef>
functions: Record<string, AquaFunction>
functionCall?: AquaFunction
errors: string[]
generatedSources: GeneratedSource[]
}
export class Input {

View File

@ -5247,7 +5247,7 @@
"node_modules/node-fetch": {
"name": "@achingbrain/node-fetch",
"version": "2.6.7",
"resolved": "https://npm.fluence.dev/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g==",
"dev": true,
"license": "MIT",
@ -11491,8 +11491,7 @@
"dev": true
},
"node-fetch": {
"version": "npm:@achingbrain/node-fetch@2.6.7",
"resolved": "https://npm.fluence.dev/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
"version": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g==",
"dev": true
},

View File

@ -9,8 +9,8 @@
"meta-utils.js"
],
"scripts": {
"move:scalajs": "cp ../aqua-api/target/scala-3.1.3/aqua-api-opt/main.js ./aqua-api.js",
"move:fast": "cp ../aqua-api/target/scala-3.1.3/aqua-api-fastopt/main.js ./aqua-api.js"
"move:scalajs": "cp ../aqua-api/.js/target/scala-3.3.0/aqua-api-opt/main.js ./aqua-api.js",
"move:fast": "cp ../aqua-api/.js/target/scala-3.3.0/aqua-api-fastopt/main.js ./aqua-api.js"
},
"repository": {
"type": "git",

View File

@ -0,0 +1,229 @@
package api
import api.types.{AquaConfig, AquaFunction, CompilationResult, GeneratedSource, Input}
import aqua.ErrorRendering.showError
import aqua.raw.value.ValueRaw
import aqua.api.{APICompilation, AquaAPIConfig}
import aqua.api.TargetType.*
import aqua.backend.air.AirBackend
import aqua.backend.{AirFunction, Backend, Generated}
import aqua.compiler.*
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.logging.{LogFormatter, LogLevels}
import aqua.constants.Constants
import aqua.io.*
import aqua.raw.ops.Call
import aqua.run.{CallInfo, CallPreparer, CliFunc, FuncCompiler, RunPreparer}
import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.FileSpan.F
import aqua.parser.lift.{FileSpan, Span}
import aqua.parser.{ArrowReturnError, BlockIndentError, LexerError, ParserError}
import aqua.semantics.{CompilerState, HeaderError, RulesViolated, WrongAST}
import aqua.{AquaIO, SpanParser}
import aqua.model.transform.{Transform, TransformConfig}
import aqua.backend.api.APIBackend
import aqua.backend.js.JavaScriptBackend
import aqua.backend.ts.TypeScriptBackend
import aqua.definitions.FunctionDef
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.data.Validated.{invalidNec, validNec, Invalid, Valid}
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import cats.syntax.show.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.scalajs.js.{|, undefined, Promise, UndefOr}
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.annotation.*
import aqua.js.{FunctionDefJs, ServiceDefJs, VarJson}
import aqua.model.AquaContext
import aqua.raw.ops.CallArrowRawTag
import aqua.raw.value.{LiteralRaw, VarRaw}
import aqua.res.AquaRes
import cats.Applicative
@JSExportTopLevel("Aqua")
object AquaAPI extends App with Logging {
/**
* All-in-one function that support different inputs and backends
* @param input can be a path to aqua file, string with a code or a function call
* @param imports list of paths
* @param aquaConfigJS compiler config
* @return compiler results depends on input and config
*/
@JSExport
def compile(
input: types.Input | types.Path | types.Call,
imports: js.Array[String],
aquaConfigJS: js.UndefOr[AquaConfig]
): Promise[CompilationResult] = {
aquaConfigJS.toOption
.map(cjs => AquaConfig.fromJS(cjs))
.getOrElse(
validNec(AquaAPIConfig())
).map { config =>
val importsList = imports.toList
input match {
case i: (types.Input | types.Path) =>
compileAll(i, importsList, config)
case c: types.Call =>
compileCall(c, importsList, config)
}
} match {
case Valid(v) => v.unsafeToFuture().toJSPromise
case Invalid(errs) => js.Promise.resolve(CompilationResult.errs(errs.toChain.toList))
}
}
// Compile all non-call inputs
private def compileAll(
input: types.Input | types.Path,
imports: List[String],
config: AquaAPIConfig
): IO[CompilationResult] = {
val backend: Backend = config.targetType match {
case AirType => APIBackend
case TypeScriptType => TypeScriptBackend()
case JavaScriptType => JavaScriptBackend()
}
extension (res: IO[ValidatedNec[String, Chain[AquaCompiled[FileModuleId]]]])
def toResult: IO[CompilationResult] = res.map(
_.andThen { compiled =>
config.targetType match {
case AirType => validNec(generatedToAirResult(compiled))
case TypeScriptType => compiledToTsSourceResult(compiled)
case JavaScriptType => compiledToJsSourceResult(compiled)
}
}.leftMap(errorsToResult).merge
)
input match {
case i: types.Input =>
APICompilation
.compileString(
i.input,
imports,
config,
backend
)
.toResult
case p: types.Path =>
APICompilation
.compilePath(
p.path,
imports,
config,
backend
)
.toResult
}
}
private def compileCall(call: types.Call, imports: List[String], config: AquaAPIConfig) = {
val path = call.input match {
case i: types.Input => i.input
case p: types.Path => p.path
}
extension (res: IO[ValidatedNec[String, (FunctionDef, String)]])
def callToResult: IO[CompilationResult] = res.map(
_.map { case (definitions, air) =>
CompilationResult.result(call = Some(AquaFunction(FunctionDefJs(definitions), air)))
}.leftMap(errorsToResult).merge
)
APICompilation
.compileCall(
call.functionCall,
path,
imports,
config,
vr => VarJson.checkDataGetServices(vr, Some(call.arguments)).map(_._1)
)
.callToResult
}
private def errorsToResult(errors: NonEmptyChain[String]): CompilationResult = {
CompilationResult.errs(errors.toChain.toList)
}
private def findBySuffix(
compiled: Seq[Generated],
suffix: String,
errorMsg: String
): ValidatedNec[String, String] = {
Validated
.fromOption(
compiled.find(_.suffix == suffix).map(_.content), {
logger.error(errorMsg)
NonEmptyChain.one(errorMsg)
}
)
}
extension (res: Chain[Validated[NonEmptyChain[String], GeneratedSource]])
def toSourcesResult: ValidatedNec[String, CompilationResult] =
res.sequence.map(_.toList.toJSArray).map(sources => CompilationResult.result(sources = sources))
private def compiledToTsSourceResult(
compiled: Chain[AquaCompiled[FileModuleId]]
): ValidatedNec[String, CompilationResult] = {
compiled.map { c =>
findBySuffix(
c.compiled,
TypeScriptBackend.ext,
"Unreachable. TypeScript backend must generate content."
)
.map(GeneratedSource.tsSource(c.sourceId.toString, _))
}.toSourcesResult
}
private def compiledToJsSourceResult(
compiled: Chain[AquaCompiled[FileModuleId]]
): ValidatedNec[String, CompilationResult] = {
compiled.map { c =>
findBySuffix(
c.compiled,
JavaScriptBackend.dtsExt,
"Unreachable. JavaScript backend must generate '.d.ts' content."
).andThen { tsTypes =>
findBySuffix(
c.compiled,
JavaScriptBackend.ext,
"Unreachable. JavaScript backend must generate '.js' content."
).map(jsSource => GeneratedSource.jsSource(c.sourceId.toString, jsSource, tsTypes))
}
}.toSourcesResult
}
private def generatedToAirResult(
compiled: Chain[AquaCompiled[FileModuleId]]
): CompilationResult = {
val generated = compiled.toList.flatMap(_.compiled)
val serviceDefs = generated.flatMap(_.services).map(s => s.name -> ServiceDefJs(s))
val functions = generated.flatMap(
_.air.map(as => (as.name, AquaFunction(FunctionDefJs(as.funcDef), as.air)))
)
CompilationResult.result(
js.Dictionary.apply(serviceDefs: _*),
js.Dictionary.apply(functions: _*)
)
}
}

View File

@ -0,0 +1,73 @@
package api.types
import aqua.api.AquaAPIConfig
import aqua.api.TargetType.*
import aqua.js.{FunctionDefJs, ServiceDefJs}
import aqua.model.transform.TransformConfig
import cats.data.Validated.{Invalid, Valid, invalidNec, validNec}
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}
import scala.scalajs.js.|
@JSExportTopLevel("Input")
class Input(
@JSExport
val input: String
)
@JSExportTopLevel("Path")
class Path(
@JSExport
val path: String
)
@JSExportTopLevel("Call")
class Call(
@JSExport
val functionCall: String,
@JSExport
val arguments: js.Dynamic,
@JSExport
val input: Path | Input
)
@JSExportTopLevel("AquaConfig")
class AquaConfig(
@JSExport
val logLevel: js.UndefOr[String],
@JSExport
val constants: js.UndefOr[js.Array[String]],
@JSExport
val noXor: js.UndefOr[Boolean],
@JSExport
val noRelay: js.UndefOr[Boolean],
@JSExport
val targetType: js.UndefOr[String]
)
object AquaConfig {
def fromJS(cjs: AquaConfig): ValidatedNec[String, AquaAPIConfig] = {
cjs.targetType.toOption
.map(_.toLowerCase())
.map {
case "typescript" => validNec(TypeScriptType)
case "javascript" => validNec(JavaScriptType)
case "air" => validNec(AirType)
case _ => invalidNec("Target can be only 'typescript', 'javascript', or 'air'")
}
.getOrElse(validNec(AirType))
.map { target =>
AquaAPIConfig(
target,
cjs.logLevel.getOrElse("info"),
cjs.constants.map(_.toList).getOrElse(Nil),
cjs.noXor.getOrElse(false),
cjs.noRelay.getOrElse(false)
)
}
}
}

View File

@ -0,0 +1,66 @@
package api.types
import aqua.js.{FunctionDefJs, ServiceDefJs}
import aqua.model.transform.TransformConfig
import cats.data.Validated.{Invalid, Valid, invalidNec, validNec}
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}
import scala.scalajs.js.|
@JSExportTopLevel("AquaFunction")
case class AquaFunction(
@JSExport
funcDef: FunctionDefJs,
@JSExport
script: String
)
@JSExportTopLevel("GeneratedSource")
case class GeneratedSource(
@JSExport
val name: String,
@JSExport
val tsSource: js.UndefOr[String],
@JSExport
val jsSource: js.UndefOr[String],
@JSExport
val tsTypes: js.UndefOr[String]
)
object GeneratedSource {
def tsSource(name: String, tsSource: String) = new GeneratedSource(name, tsSource, null, null)
def jsSource(name: String, jsSource: String, tsTypes: String) = new GeneratedSource(name, null, jsSource, tsTypes)
}
@JSExportTopLevel("CompilationResult")
class CompilationResult(
@JSExport
val services: js.Dictionary[ServiceDefJs],
@JSExport
val functions: js.Dictionary[AquaFunction],
@JSExport
val functionCall: js.UndefOr[AquaFunction],
@JSExport
val generatedSources: js.Array[GeneratedSource],
@JSExport
val errors: js.Array[String]
)
object CompilationResult {
def result(
services: js.Dictionary[ServiceDefJs] = js.Dictionary(),
functions: js.Dictionary[AquaFunction] = js.Dictionary(),
call: Option[AquaFunction] = None,
sources: js.Array[GeneratedSource] = js.Array()
): CompilationResult =
new CompilationResult(services, functions, call.orNull, sources, js.Array())
def errs(
errors: List[String]
): CompilationResult =
CompilationResult(js.Dictionary(), js.Dictionary(), null, null, errors.toJSArray)
}

View File

@ -0,0 +1,18 @@
package aqua.api
import cats.effect.{IO, IOApp}
import aqua.backend.js.JavaScriptBackend
import aqua.backend.ts.TypeScriptBackend
import aqua.api.TargetType.JavaScriptType
object Test extends IOApp.Simple {
override def run: IO[Unit] = {
val input =
"""func getNumber(number: u32) -> u32:
| <- number
|""".stripMargin
APICompilation.compileString(input, Nil, AquaAPIConfig(targetType = JavaScriptType), JavaScriptBackend()).map {
res =>
println(res)
}
}
}

View File

@ -0,0 +1,164 @@
package aqua.api
import aqua.ErrorRendering.showError
import aqua.raw.value.ValueRaw
import aqua.api.AquaAPIConfig
import aqua.backend.{AirFunction, Backend, Generated}
import aqua.compiler.*
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.logging.{LogFormatter, LogLevels}
import aqua.constants.Constants
import aqua.io.*
import aqua.raw.ops.Call
import aqua.run.{CallInfo, CallPreparer, CliFunc, FuncCompiler, RunPreparer}
import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.FileSpan.F
import aqua.parser.lift.{FileSpan, Span}
import aqua.parser.{ArrowReturnError, BlockIndentError, LexerError, ParserError}
import aqua.semantics.{CompilerState, HeaderError, RulesViolated, WrongAST}
import aqua.{AquaIO, SpanParser}
import aqua.model.transform.{Transform, TransformConfig}
import aqua.backend.api.APIBackend
import aqua.definitions.FunctionDef
import aqua.model.AquaContext
import aqua.res.AquaRes
import cats.Applicative
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.data.Validated.{Invalid, Valid, invalid, invalidNec, validNec}
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import cats.syntax.show.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import scribe.Logging
object APICompilation {
def compileCall(
functionStr: String,
pathStr: String,
imports: List[String],
aquaConfig: AquaAPIConfig,
fillWithTypes: List[ValueRaw] => ValidatedNec[String, List[ValueRaw]]
): IO[ValidatedNec[String, (FunctionDef, String)]] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
(
LogLevels.levelFromString(aquaConfig.logLevel),
Constants.parse(aquaConfig.constants)
).mapN { (level, constants) =>
val transformConfig = aquaConfig.getTransformConfig.copy(constants = constants)
LogFormatter.initLogger(Some(level))
new FuncCompiler[IO](
Some(RelativePath(Path(pathStr))),
imports.map(Path.apply),
transformConfig
).compile().map { contextV =>
contextV.andThen { context =>
CliFunc
.fromString(functionStr)
.leftMap(errs => NonEmptyChain.fromNonEmptyList(errs))
.andThen { cliFunc =>
FuncCompiler.findFunction(context, cliFunc).andThen { arrow =>
fillWithTypes(cliFunc.args).andThen { argsWithTypes =>
val func = cliFunc.copy(args = argsWithTypes)
val preparer = new RunPreparer(
func,
arrow,
transformConfig
)
preparer.prepare().map { ci =>
(ci.definitions, ci.air)
}
}
}
}
}.leftMap(_.map(_.show).distinct)
}
} match {
case Valid(pr) => pr
case Invalid(errs) => IO.pure(Invalid(NonEmptyChain.fromNonEmptyList(errs)))
}
}
def compilePath(
pathStr: String,
imports: List[String],
aquaConfig: AquaAPIConfig,
backend: Backend
): IO[ValidatedNec[String, Chain[AquaCompiled[FileModuleId]]]] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
val path = Path(pathStr)
val sources = new AquaFileSources[IO](path, imports.map(Path.apply))
compileRaw(
aquaConfig,
sources,
backend
)
}
def compileString(
input: String,
imports: List[String],
aquaConfig: AquaAPIConfig,
backend: Backend
): IO[ValidatedNec[String, Chain[AquaCompiled[FileModuleId]]]] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
val path = Path("")
val strSources: AquaFileSources[IO] =
new AquaFileSources[IO](path, imports.map(Path.apply)) {
override def sources: IO[ValidatedNec[AquaFileError, Chain[(FileModuleId, String)]]] = {
IO.pure(Valid(Chain.one((FileModuleId(path), input))))
}
}
compileRaw(
aquaConfig,
strSources,
backend
)
}
private def compileRaw(
aquaConfig: AquaAPIConfig,
sources: AquaSources[IO, AquaFileError, FileModuleId],
backend: Backend
): IO[ValidatedNec[String, Chain[AquaCompiled[FileModuleId]]]] = {
(
LogLevels.levelFromString(aquaConfig.logLevel),
Constants.parse(aquaConfig.constants)
).traverseN { (level, constants) =>
LogFormatter.initLogger(Some(level))
val config = AquaCompilerConf(constants)
val transformConfig = aquaConfig.getTransformConfig
CompilerAPI
.compile[IO, AquaFileError, FileModuleId, FileSpan.F](
sources,
SpanParser.parser,
new AirValidator[IO] {
override def init(): IO[Unit] = Applicative[IO].pure(())
override def validate(airs: List[AirFunction]): IO[ValidatedNec[String, Unit]] =
Applicative[IO].pure(validNec(()))
},
new Backend.Transform:
override def transform(ex: AquaContext): AquaRes =
Transform.contextRes(ex, transformConfig)
override def generate(aqua: AquaRes): Seq[Generated] = backend.generate(aqua)
,
config
).map(_.leftMap(_.map(_.show).distinct))
}.map(_.leftMap(NonEmptyChain.fromNonEmptyList).andThen(identity))
}
}

View File

@ -1,233 +0,0 @@
package aqua.api
import aqua.ErrorRendering.showError
import aqua.api.types.{AquaAPIConfig, AquaConfig, AquaFunction, CompilationResult, Input}
import aqua.backend.{AirFunction, Backend, Generated}
import aqua.compiler.*
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.logging.{LogFormatter, LogLevels}
import aqua.constants.Constants
import aqua.io.*
import aqua.raw.ops.Call
import aqua.run.{CallInfo, CallPreparer, CliFunc, FuncCompiler, RunPreparer}
import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.FileSpan.F
import aqua.parser.lift.{FileSpan, Span}
import aqua.parser.{ArrowReturnError, BlockIndentError, LexerError, ParserError}
import aqua.semantics.{CompilerState, HeaderError, RulesViolated, WrongAST}
import aqua.{AquaIO, SpanParser}
import aqua.model.transform.{Transform, TransformConfig}
import aqua.backend.api.APIBackend
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.data.Validated.{Invalid, Valid, invalidNec, validNec}
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import cats.syntax.show.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.scalajs.js.|
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.annotation.*
import scala.scalajs.js.{UndefOr, undefined}
import aqua.js.{FunctionDefJs, ServiceDefJs, VarJson}
import aqua.model.AquaContext
import aqua.raw.ops.CallArrowRawTag
import aqua.raw.value.{LiteralRaw, VarRaw}
import aqua.res.AquaRes
import cats.Applicative
@JSExportTopLevel("Aqua")
object AquaAPI extends App with Logging {
def getTag(serviceId: String, value: VarRaw) = {
CallArrowRawTag.service(
LiteralRaw.quote(serviceId),
value.name,
Call(List.empty, List(Call.Export(value.name, value.baseType)))
)
}
@JSExport
def compile(
input: types.Input | types.Path | types.Call,
imports: js.Array[String],
aquaConfigJS: js.UndefOr[AquaConfig]
) = {
val aquaConfig: AquaAPIConfig =
aquaConfigJS.toOption.map(cjs => AquaAPIConfig.fromJS(cjs)).getOrElse(AquaAPIConfig())
val importsList = imports.toList
input match {
case i: types.Input => compileString(i.input, importsList, aquaConfig)
case p: types.Path => compilePath(p.path, importsList, aquaConfig)
case c: types.Call =>
val path = c.input match {
case i: types.Input => i.input
case p: types.Path => p.path
}
compileCall(c.functionCall, c.arguments, path, importsList, aquaConfig)
}
}
def compileCall(
functionStr: String,
arguments: js.Dynamic,
pathStr: String,
imports: List[String],
aquaConfig: AquaAPIConfig
): js.Promise[CompilationResult] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
(
LogLevels.levelFromString(aquaConfig.logLevel),
Constants.parse(aquaConfig.constants)
).mapN { (level, constants) =>
val transformConfig = aquaConfig.getTransformConfig.copy(constants = constants)
LogFormatter.initLogger(Some(level))
new FuncCompiler[IO](
Some(RelativePath(Path(pathStr))),
imports.map(Path.apply),
transformConfig
).compile()
.map { contextV =>
contextV.andThen { context =>
CliFunc
.fromString(functionStr)
.leftMap(errs => NonEmptyChain.fromNonEmptyList(errs))
.andThen { cliFunc =>
FuncCompiler.findFunction(context, cliFunc).andThen { arrow =>
VarJson.checkDataGetServices(cliFunc.args, Some(arguments)).andThen {
case (argsWithTypes, _) =>
val func = cliFunc.copy(args = argsWithTypes)
val preparer = new RunPreparer(
func,
arrow,
transformConfig
)
preparer.prepare().map { ci =>
AquaFunction(FunctionDefJs(ci.definitions), ci.air)
}
}
}
}
}
}
.flatMap {
case Valid(result) => IO.pure(CompilationResult.result(js.Dictionary(), js.Dictionary(), Some(result)))
case Invalid(err) =>
val errs = err.map(_.show).distinct.toChain.toList
IO.pure(CompilationResult.errs(errs))
}
.unsafeToFuture()
.toJSPromise
} match {
case Valid(pr) => pr
case Invalid(err) => js.Promise.resolve(CompilationResult.errs(err.toList))
}
}
def compilePath(
pathStr: String,
imports: List[String],
aquaConfig: AquaAPIConfig
): js.Promise[CompilationResult] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
val path = Path(pathStr)
val sources = new AquaFileSources[IO](path, imports.map(Path.apply))
compileRaw(aquaConfig, sources)
}
def compileString(
input: String,
imports: List[String],
aquaConfig: AquaAPIConfig
): js.Promise[CompilationResult] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
val path = Path("")
val strSources: AquaFileSources[IO] =
new AquaFileSources[IO](path, imports.map(Path.apply)) {
override def sources: IO[ValidatedNec[AquaFileError, Chain[(FileModuleId, String)]]] = {
IO.pure(Valid(Chain.one((FileModuleId(path), input))))
}
}
compileRaw(aquaConfig, strSources)
}
def compileRaw(
aquaConfig: AquaAPIConfig,
sources: AquaSources[IO, AquaFileError, FileModuleId]
): js.Promise[CompilationResult] = {
(
LogLevels.levelFromString(aquaConfig.logLevel),
Constants.parse(aquaConfig.constants)
).mapN { (level, constants) =>
LogFormatter.initLogger(Some(level))
val config = AquaCompilerConf(constants)
val transformConfig = aquaConfig.getTransformConfig
val proc = for {
res <- CompilerAPI
.compile[IO, AquaFileError, FileModuleId, FileSpan.F](
sources,
SpanParser.parser,
new AirValidator[IO] {
override def init(): IO[Unit] = Applicative[IO].pure(())
override def validate(airs: List[AirFunction]): IO[ValidatedNec[String, Unit]] =
Applicative[IO].pure(validNec(()))
},
new Backend.Transform:
override def transform(ex: AquaContext): AquaRes =
Transform.contextRes(ex, transformConfig)
override def generate(aqua: AquaRes): Seq[Generated] = APIBackend.generate(aqua)
,
config
)
jsResult <- res match {
case Valid(compiled) =>
val allGenerated: List[Generated] = compiled.toList.flatMap(_.compiled)
val serviceDefs = allGenerated.flatMap(_.services).map(s => s.name -> ServiceDefJs(s))
val functions = allGenerated.flatMap(
_.air.map(as => (as.name, AquaFunction(FunctionDefJs(as.funcDef), as.air)))
)
IO.pure(
CompilationResult.result(
js.Dictionary.apply(serviceDefs: _*),
js.Dictionary.apply(functions: _*),
None
)
)
case Invalid(errChain) =>
val errors = errChain.map(_.show).distinct.toChain.toList
IO.pure[CompilationResult](CompilationResult.errs(errors))
}
} yield {
jsResult
}
proc.unsafeToFuture().toJSPromise
} match {
case Valid(pr) => pr
case Invalid(err) => js.Promise.resolve(CompilationResult.errs(err.toList))
}
}
}

View File

@ -0,0 +1,18 @@
package aqua.api
import aqua.model.transform.TransformConfig
enum TargetType:
case TypeScriptType, JavaScriptType, AirType
case class AquaAPIConfig(
targetType: TargetType = TargetType.AirType,
logLevel: String = "info",
constants: List[String] = Nil,
noXor: Boolean = false,
noRelay: Boolean = false
) {
def getTransformConfig: TransformConfig =
if (noRelay) TransformConfig(relayVarName = None, wrapWithXor = !noXor)
else TransformConfig(wrapWithXor = !noXor)
}

View File

@ -1,103 +0,0 @@
package aqua.api.types
import aqua.api.*
import aqua.js.{FunctionDefJs, ServiceDefJs}
import aqua.model.transform.TransformConfig
import scala.scalajs.js
import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}
import scala.scalajs.js.|
import scala.scalajs.js.JSConverters.*
@JSExportTopLevel("AquaFunction")
case class AquaFunction(
@JSExport
funcDef: FunctionDefJs,
@JSExport
script: String
)
@JSExportTopLevel("Input")
class Input(
@JSExport
val input: String
)
@JSExportTopLevel("Path")
class Path(
@JSExport
val path: String
)
@JSExportTopLevel("Call")
class Call(
@JSExport
val functionCall: String,
@JSExport
val arguments: js.Dynamic,
@JSExport
val input: Path | Input
)
@JSExportTopLevel("AquaConfig")
class AquaConfig(
@JSExport
val logLevel: js.UndefOr[String],
@JSExport
val constants: js.UndefOr[js.Array[String]],
@JSExport
val noXor: js.UndefOr[Boolean],
@JSExport
val noRelay: js.UndefOr[Boolean]
)
@JSExportTopLevel("CompilationResult")
class CompilationResult(
@JSExport
val services: js.Dictionary[ServiceDefJs],
@JSExport
val functions: js.Dictionary[AquaFunction],
@JSExport
val functionCall: js.UndefOr[AquaFunction],
@JSExport
val errors: js.Array[String]
)
object CompilationResult {
def result(
services: js.Dictionary[ServiceDefJs],
functions: js.Dictionary[AquaFunction],
call: Option[AquaFunction]
): CompilationResult =
new CompilationResult(services, functions, call.orNull, js.Array())
def errs(
errors: List[String]
): CompilationResult =
CompilationResult(js.Dictionary(), js.Dictionary(), null, errors.toJSArray)
}
case class AquaAPIConfig(
logLevel: String = "info",
constants: List[String] = Nil,
noXor: Boolean = false,
noRelay: Boolean = false
) {
def getTransformConfig: TransformConfig =
if (noRelay) TransformConfig(relayVarName = None, wrapWithXor = !noXor)
else TransformConfig(wrapWithXor = !noXor)
}
object AquaAPIConfig {
def fromJS(cjs: AquaConfig): AquaAPIConfig = {
AquaAPIConfig(
cjs.logLevel.getOrElse("info"),
cjs.constants.map(_.toList).getOrElse(Nil),
cjs.noXor.getOrElse(false),
cjs.noRelay.getOrElse(false)
)
}
}

View File

@ -4,10 +4,7 @@ import aqua.backend.ts.TypeScriptTypes
import aqua.backend.*
import aqua.res.AquaRes
case class JavaScriptBackend(isOldFluenceJs: Boolean, client: String = Backend.client) extends Backend {
val ext = ".js"
val tsExt = ".d.ts"
case class JavaScriptBackend(isOldFluenceJs: Boolean = false, client: String = Backend.client) extends Backend {
val types = TypeScriptTypes(client)
def typesFile(res: AquaRes): Generated = {
@ -31,13 +28,18 @@ case class JavaScriptBackend(isOldFluenceJs: Boolean, client: String = Backend.c
|
|/* eslint-enable */""".stripMargin
Generated(tsExt, body, Nil)
Generated(JavaScriptBackend.dtsExt, body, Nil)
}
override def generate(res: AquaRes): Seq[Generated] =
if (res.isEmpty) Nil
else {
val (airs, script) = OutputFile(res).generate(EmptyTypes, true, isOldFluenceJs)
Generated(ext, script, airs) :: typesFile(res) :: Nil
Generated(JavaScriptBackend.ext, script, airs) :: typesFile(res) :: Nil
}
}
object JavaScriptBackend {
val ext = ".js"
val dtsExt = ".d.ts"
}

View File

@ -4,14 +4,16 @@ import aqua.backend.{Backend, Generated, OutputFile}
import aqua.res.AquaRes
import cats.data.NonEmptyChain
case class TypeScriptBackend(isOldFluenceJs: Boolean, client: String = Backend.client) extends Backend {
val ext = ".ts"
case class TypeScriptBackend(isOldFluenceJs: Boolean = false, client: String = Backend.client) extends Backend {
override def generate(res: AquaRes): Seq[Generated] =
if (res.isEmpty) Nil
else {
val (airs, script) = OutputFile(res).generate(TypeScriptTypes(client), false, isOldFluenceJs)
Generated(ext, script, airs) :: Nil
val (airs, script) = OutputFile(res).generate(TypeScriptTypes(client), isJs = false, isOldFluenceJs)
Generated(TypeScriptBackend.ext, script, airs) :: Nil
}
}
object TypeScriptBackend {
val ext = ".ts"
}

View File

@ -143,16 +143,21 @@ lazy val `js-imports` = project
.settings(commons: _*)
.dependsOn(`js-exports`, transform.js)
lazy val `aqua-api` = project
lazy val `aqua-api` = crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("api/aqua-api"))
.enablePlugins(ScalaJSPlugin)
.settings(commons: _*)
.dependsOn(`aqua-run`, `backend-api`)
lazy val `aqua-apiJS` = `aqua-api`.js
.settings(
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
scalaJSUseMainModuleInitializer := true,
Test / test := {}
)
.dependsOn(`js-exports`, `aqua-run`.js, `backend-api`.js)
.enablePlugins(ScalaJSPlugin)
.dependsOn(`js-exports`)
lazy val types = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)