From 451d5211edcac245173c45a35124cf15cb1c4471 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Sun, 17 May 2020 16:32:05 +0200 Subject: [PATCH] Pod :code field --- babashka.nrepl | 2 +- babashka.pods | 2 +- doc/pods.md | 77 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/babashka.nrepl b/babashka.nrepl index 8f08cd7b..903489fc 160000 --- a/babashka.nrepl +++ b/babashka.nrepl @@ -1 +1 @@ -Subproject commit 8f08cd7bc8f6c89880cbc708b15889c25b32277b +Subproject commit 903489fcb5d23fe1ebf4f6e1e321419ed3739268 diff --git a/babashka.pods b/babashka.pods index 016e20dc..8f16139a 160000 --- a/babashka.pods +++ b/babashka.pods @@ -1 +1 @@ -Subproject commit 016e20dc52d553049694d7496602dbbccbd6a949 +Subproject commit 8f16139ab6e73a91aeb5fe97b398b30670e3fc9b diff --git a/doc/pods.md b/doc/pods.md index 1d844278..f98f94e0 100644 --- a/doc/pods.md +++ b/doc/pods.md @@ -1,7 +1,7 @@ # Pods -Pods are standalone programs that can expose namespaces with vars to -babashka. Pods can be created independently from babashka. Any program can be +Pods are standalone programs that can expose namespaces with vars to babashka or +a JVM. Pods can be created independently from babashka. Any program can be invoked as a pod as long as it implements the _pod protocol_. This protocol is influenced by and built upon battle-tested technologies: @@ -36,6 +36,8 @@ _below_ in Polish and Russian. In Romanian it means _bridge_ ## Implementing your own pod +We will refer to babashka or the JVM, invoking the pod, as the pod client. + ### Examples Beyond the already available pods mentioned above, eductional examples of pods @@ -77,7 +79,7 @@ Pods created by the babashka maintainers use the identifier `babashka`: #### Message and payload format -Exchange of _messages_ between babashka and the pod happens in the +Exchange of _messages_ between pod client and the pod happens in the [bencode](https://en.wikipedia.org/wiki/Bencode) format. Bencode is a bare-bones format that only has four types: @@ -105,12 +107,12 @@ might not be a good EDN library available. So we use bencode as the first encoding and choose one of multiple richer encodings on top of this. More payload formats might be added in the future (e.g. transit). -When calling the `babashka.pods/load-pod` function, babashka will start the pod -and leave the pod running throughout the duration of a babashka script. +When calling the `babashka.pods/load-pod` function, the pod client will start +the pod and leave the pod running throughout the duration of a babashka script. #### describe -The first message that babashka will send to the pod on its stdin is: +The first message that the pod client will send to the pod on its stdin is: ``` clojure {"op" "describe"} @@ -137,10 +139,10 @@ In this reply, the pod declares that payloads will be encoded and decoded using JSON. It also declares that the pod exposes one namespace, `pod.lispyclouds.sqlite` with one var `execute!`. -The pod encodes the above map to bencode and writes it to stdoud. Babashka reads -this message from the pod's stdout. +The pod encodes the above map to bencode and writes it to stdoud. The pod client +reads this message from the pod's stdout. -Upon receiving this message, babashka creates these namespaces and vars. +Upon receiving this message, the pod client creates these namespaces and vars. The optional `ops` value communicates which ops the pod supports, beyond `describe` and `invoke`. It is a map of op names to option maps. In the above @@ -148,7 +150,7 @@ example the pod declares that it supports the `shutdown` op. Since the `shutdown` op does not need any additional options right now, the value is an empty map. -As a babashka user, you can load the pod with: +As a pod user, you can load the pod with: ``` clojure (require '[babashka.pods :as pods]) @@ -162,13 +164,13 @@ As a babashka user, you can load the pod with: #### invoke -When invoking a var that is related to the pod, let's call it a _proxy var_, -babashka reaches out to the pod with the arguments encoded in JSON or EDN. The -pod will then respond with a return value encoded in JSON or EDN. Babashka will -then decode the return value and present the user with that. +When invoking a var that is related to the pod, let's call it a _proxy var_, the +pod client reaches out to the pod with the arguments encoded in JSON or EDN. The +pod will then respond with a return value encoded in JSON or EDN. The pod client +will then decode the return value and present the user with that. -Example: the user invokes `(sql/execute! "select * from foo")`. Babashka sends -this message to the pod: +Example: the user invokes `(sql/execute! "select * from foo")`. The pod client +sends this message to the pod: ``` clojure {"id" "1d17f8fe-4f70-48bf-b6a9-dc004e52d056" @@ -176,7 +178,7 @@ this message to the pod: "args" "[\"select * from foo\"]" ``` -The `id` is unique identifier generated by babashka which correlates this +The `id` is unique identifier generated by the pod client which correlates this request with a response from the pod. An example response from the pod could look like: @@ -188,22 +190,22 @@ An example response from the pod could look like: ``` Here, the `value` payload is the return value of the function invocation. The -field `status` contains `"done"`. This tells babashka that this is the last +field `status` contains `"done"`. This tells the pod client that this is the last message related to the request with `id` `1d17f8fe-4f70-48bf-b6a9-dc004e52d056`. Now you know most there is to know about the pod protocol! #### shutdown -When babashka is about to exit, it sends an `{"op" "shutdown"}` message, if the +When the pod client is about to exit, it sends an `{"op" "shutdown"}` message, if the pod has declared that it supports it in the `describe` response. Then it waits for the pod process to end. This gives the pod a chance to clean up resources before it exits. If the pod does not support the `shutdown` op, the pod process -is killed by babashka. +is killed by the pod client. #### out and err -Pods may send messages with an `out` and `err` string value. Babashka prints +Pods may send messages with an `out` and `err` string value. The Pod Client prints these messages to `*out*` and `*err*`. Stderr from the pod is redirected to `System/err`. @@ -220,7 +222,7 @@ these messages to `*out*` and `*err*`. Stderr from the pod is redirected to #### Error handling Responses may contain an `ex-message` string and `ex-data` payload string (JSON -or EDN) along with an `"error"` value in `status`. This will cause babashka to +or EDN) along with an `"error"` value in `status`. This will cause the pod client to throw an `ex-info` with the associated values. Example: @@ -245,7 +247,7 @@ time in the future. Async functions must be declared as such as part of the "vars" [{"name" "watch" "async" "true"}]}]} ``` -When calling this function from babashka, the return value is a `core.async` +When calling this function from the pod client, the return value is a `core.async` channel on which the values will be received: ``` clojure @@ -259,8 +261,35 @@ channel on which the values will be received: #### Environment -Babashka will set the `BABASHKA_POD` environment variable to `true` when +The pod client will set the `BABASHKA_POD` environment variable to `true` when invoking the pod. This can be used by the invoked program to determine whether it should behave as a pod or not. Added in v0.0.94. + +#### Client side code + +Pods may implement functions and macros by sending arbitrary code to the pod +client in a `"code"` field as part of a `"var"` section. The code is evaluated +by the pod client inside the declared namespace. + +For example, a pod can define a macro called `do-twice`: + +``` clojure +{"format" "json" + "namespaces" + [{"name" "pod.babashka.demo" + "vars" [{"name" "do-twice" "code" "(defmacro do-twice [x] `(do ~x ~x))"}]}]} +``` + +In the pod client: + +``` clojure +(pods/load-pod "pod-babashka-demo") +(require '[pod.babashka.demo :as demo]) +(demo/do-twice (prn :foo)) +;;=> +:foo +:foo +nil +```