Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/extism/ocaml-sdk
Extism OCaml Host SDK
https://github.com/extism/ocaml-sdk
Last synced: about 1 month ago
JSON representation
Extism OCaml Host SDK
- Host: GitHub
- URL: https://github.com/extism/ocaml-sdk
- Owner: extism
- License: bsd-3-clause
- Created: 2023-09-13T15:08:36.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-05-22T22:47:07.000Z (7 months ago)
- Last Synced: 2024-05-22T23:41:58.884Z (7 months ago)
- Language: OCaml
- Homepage:
- Size: 3.1 MB
- Stars: 12
- Watchers: 5
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-wasm-runtimes - `OCaml`
README
# Extism OCaml Host SDK
This repo contains the OCaml package for integrating with the [Extism](https://extism.org/) runtime.
> **Note**: If you're unsure what Extism is or what an SDK is see our homepage: [https://extism.org](https://extism.org).
## Documentation
Documentation is available at [https://extism.github.io/ocaml-sdk](https://extism.github.io/ocaml-sdk)
## Installation
### Install the Extism Runtime Dependency
For this library, you first need to install the Extism Runtime. You can [download the shared object directly from a release](https://github.com/extism/extism/releases) or use the [Extism CLI](https://github.com/extism/cli) to install it.
### Add the library to dune
Then add `extism` to your [dune](https://dune.build) depdendencies:
```
(libraries extism)
```If you're generating an opam file using dune then add `extism` to your `dune-project` package `depends` section:
```
(package
(depends
(extism (>= 1.1.0))))
```Installing the `extism` package on opam will also install the `extism-call` executable, which can be used
to execute Extism plugins.## Getting Started
This guide should walk you through some of the concepts in Extism and the OCaml bindings.
### Creating A Plug-in
The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file.
Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:
```ocaml
open Extismlet wasm = Manifest.Wasm.url "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
let manifest = Manifest.create [wasm]
let plugin = Plugin.of_manifest_exn manifest
```> **Note**: See [the Manifest docs](https://extism.github.io/ocaml-sdk/extism-manifest/Extism_manifest/index.html) as it has a rich schema and a lot of options.
### Calling A Plug-in's Exports
This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [Extism.Plugin.call](https://extism.github.io/ocaml-sdk/extism/Extism/Plugin/index.html#val-call):
```ocaml
# Plugin.call_string_exn plugin ~name:"count_vowels" "Hello, world!";;
- : string = "{\"count\":3,\"total\":3,\"vowels\":\"aeiouAEIOU\"}"
```All exports have a simple interface of bytes-in and bytes-out. This plug-in happens to take a string and return a JSON encoded string with a report of results.
This library also allows for calls to be typed, when the input and output types are not strings. Instead of getting the output as a JSON encoded string, we can
convert it directly to `Yojson.Safe.t`:```ocaml
# Plugin.call_exn Type.string Type.json plugin ~name:"count_vowels" "Hello, world!";;
- : Yojson.Safe.t =
`Assoc
[("count", `Int 3); ("total", `Int 6); ("vowels", `String "aeiouAEIOU")]
```See [Extism.Type.S](https://extism.github.io/ocaml-sdk/extism/Extism/Type/module-type-S/index.html) to define your own input/output types.
### Typed Plugins
Plug-ins can also use pre-defined functions using `Plugin.Typed`:
```ocaml
module Example = struct
include Plugin.Typed.Init ()let count_vowels = exn @@ fn "count_vowels" Type.string Type.json
end
```This can then be initialized using an existing `Plugin.t`:
```ocaml
let example = Example.of_plugin_exn plugin in
let res = Example.count_vowels example "this is a test" in
print_endline (Yojson.Safe.to_string res)
```### Plug-in State
Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:
```ocaml
# Plugin.call_string_exn plugin ~name:"count_vowels" "Hello, world!" |> print_endline;;
{"count":3,"total":9,"vowels":"aeiouAEIOU"}
- : unit = ()
# Plugin.call_string_exn plugin ~name:"count_vowels" "Hello, world!" |> print_endline;;
{"count":3,"total":12,"vowels":"aeiouAEIOU"}
- : unit = ()
```These variables will persist until this plug-in is freed or you initialize a new one.
### Configuration
Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:
```ocaml
# let manifest = Manifest.create [wasm];;
val manifest : Extism_manifest.t =
{Extism.Manifest.wasm =
[Extism.Manifest.Wasm.Url
{Extism.Manifest.Wasm.url =
"https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm";
headers = None; meth = None; name = None; hash = None}];
memory = None; config = None; allowed_hosts = None; allowed_paths = None;
timeout_ms = None}# let plugin = Plugin.of_manifest_exn manifest;;
val plugin : Plugin.t =
# Plugin.call_string_exn plugin ~name:"count_vowels" "Yellow, world!" |> print_endline;;
{"count":3,"total":3,"vowels":"aeiouAEIOU"}
- : unit = ()# let plugin = Plugin.of_manifest_exn @@ Manifest.with_config ["vowels", Some "aeiouAEIOUY"] manifest;;
val plugin : Plugin.t =
# Plugin.call_string_exn plugin ~name:"count_vowels" "Yellow, world!" |> print_endline;;
{"count":4,"total":4,"vowels":"aeiouAEIOUY"}
- : unit = ()
```### Host Functions
Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store!
Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in.
[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some OCaml functions you write which can be passed down and invoked from any language inside the plug-in.
Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in:
```ocaml
open Extismlet url =
"https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"let wasm = Manifest.Wasm.url url
let manifest = Manifest.create [ wasm ]
```> *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages.
Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store.
Using [Extism.Function](https://extism.github.io/ocaml-sdk/extism/Extism/Function/index.html) we can define a host function that can be called from the guest plug-in. In this example we will create a function to help us load plugins and setup the host functions.
We want to expose two functions to our plugin (in OCaml types): `val kv_write: string -> string -> unit` which writes a bytes value to a key and `val kv_read: string -> string` which reads the bytes at the given `key`.
```ocaml
let make_kv_plugin () =
(* pretend this is Redis or something :) *)
let kv_store = Hashtbl.create 8 inlet kv_read =
let open Val_type in
Function.create "kv_read" ~params:[ ptr ] ~results:[ ptr ] ~user_data:()
@@ fun plugin () ->
let key = Host_function.input_string plugin in
Printf.printf "Reading from key=%s\n" key;
let value =
try Hashtbl.find kv_store key
with Not_found -> String.init 4 (fun _ -> char_of_int 0)
in
Host_function.output_string plugin value
inlet kv_write =
let open Val_type in
Function.create "kv_write" ~params:[ ptr; ptr ] ~results:[] ~user_data:()
@@ fun plugin () ->
let key = Host_function.input_string ~index:0 plugin in
let value = Host_function.input_string ~index:1 plugin in
Printf.printf "Write value=%s to key=%s\n" value key;
Hashtbl.replace kv_store key value
in(* Create a plugin from the manifest with the kv host functions *)
Plugin.of_manifest_exn ~functions:[ kv_read; kv_write ] ~wasi:true manifest
```> *Note*: In order to write host functions you should get familiar with the methods on the [Extism.Host_function](https://extism.github.io/ocaml-sdk/extism/Extism/Host_function/index.html) module.
Now we can invoke the event:
```ocaml
# let plugin = make_kv_plugin ();;
val plugin : Plugin.t =
# Extism.Plugin.call_string_exn plugin ~name:"count_vowels" "Hello, world" |> print_endline;;
Reading from key=count-vowels
Write value=^C^@^@^@ to key=count-vowels
{"count":3,"total":3,"vowels":"aeiouAEIOU"}
- : unit = ()
# Extism.Plugin.call_string_exn plugin ~name:"count_vowels" "Hello, world" |> print_endline;;
Reading from key=count-vowels
Write value=^F^@^@^@ to key=count-vowels
{"count":3,"total":6,"vowels":"aeiouAEIOU"}
- : unit = ()
```