JavaScript Transform
Source: 05_javascript_transform
An HTTP server that applies a JavaScript function to every incoming JSON payload and returns the transformed result. The JS engine is declared as a model so the function is compiled once at startup and reused across all concurrent requests.
Running
melodium run 05_javascript_transform/Compo.toml$ curl -X POST http://127.0.0.1:8080/transform \
-H "Content-Type: application/json" \
-d '{"name":"Alice","score":82}'
{"name":"Alice","score":82,"grade":"B","note":"processed by Mélodium JS engine"}How it works
The Transformer model embeds the JavaScript function directly in the source:
model Transformer() : JavaScriptEngine {
code = "
function transform(input) {
var grade = 'F';
if (input.score >= 90) grade = 'A';
…
return { name: input.name, score: input.score, grade: grade, … };
}
"
}The JS engine compiles this function once when the model is instantiated. startup starts the HttpServer and registers the /transform connection:
treatment main(const port: u16 = 8080)
model server: HttpServer(host=|from_ipv4(|localhost_ipv4()), port=port)
{
startup()
start[http_server=server]()
logReady: logInfoMessage(label="server", message="JavaScript transform service ready")
startup.trigger -> start.trigger
startup.trigger -> logReady.trigger
connection[http_server=server](method=|post(), route="/transform")
status: emit<HttpStatus>(value=|ok())
headers: emit<StringMap>(value=|map([]))
bodyTrigger: trigger<byte>()
connection.data -> bodyTrigger.stream,start --> status.trigger,emit -> connection.status
bodyTrigger.start --------> headers.trigger,emit -> connection.headers
jsTransform()
connection.data -> jsTransform.data,data -> connection.data
}Every request then calls process[engine=engine](code="transform(value)") which invokes the compiled function with the parsed JSON value.
The jsTransform sub-treatment
treatment jsTransform()
model engine: Transformer()
input data: Stream<byte>
output data: Stream<byte>
{
decode()
toJson()
unwrap<Json>()
process[engine=engine](code="transform(value)")
unwrapOr<Json>(default=|null())
toString<Json>()
encode()
Self.data -> decode.data,text -> toJson.text,json -> unwrap.option,value -> process.value,result -> unwrapOr.option,value -> toString.value,into -> encode.text,data -> Self.data
}Two different unwrap strategies are used deliberately:
unwrap<Json>()beforeprocess: malformed JSON is a hard error; there is nothing useful to pass to the JS functionunwrapOr<Json>(default=|null())afterprocess: a JS runtime error produces a gracefulnullresponse rather than crashing the request
The jsTransform sub-treatment declares its own model engine: Transformer(). This model is scoped to that declaration and lives for the entire program execution, so the same instance is reused across every call to jsTransform, and the function is not recompiled per request.
Dependencies
[dependencies]
std = "0.10.2" # core flows, logging, data structures
http = "0.10.2" # HTTP server and client
net = "0.10.2" # IP address helpers
json = "0.10.2" # JSON parsing and serialisation
encoding = "0.10.2" # UTF-8 encode / decode
javascript = "0.10.2" # embedded JavaScript engine