package extism
Install
Dune Dependency
Authors
Maintainers
Sources
md5=21ee8d7196f68738741ab80da783613f
sha512=664ec9d592b8fbd2c7c89de63f419fd2f59b97c06a3f56cc4d3f08bf657d39ce78d9e320dd70d0ae39d3d6a943a9b4801557034f8f5ff0b2f2632c2bbfd22682
Description
Bindings to Extism, the universal plugin system
README
Extism OCaml Host SDK
This repo contains the OCaml package for integrating with the Extism runtime.
Documentation
Documentation is available at 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 or use the Extism CLI to install it.
Add the library to dune
Then add extism
to your dune 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. 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:
open Extism
let 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
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:
# 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
:
# 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 to define your own input/output types.
Typed Plugins
Plug-ins can also use pre-defined functions using Plugin.Typed
:
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
:
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:
# 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:
# 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 = <abstr>
# 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 = <abstr>
# 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 come in.
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:
open Extism
let url =
"https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"
let wasm = Manifest.Wasm.url url
let manifest = Manifest.create [ wasm ]
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 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
.
let make_kv_plugin () =
(* pretend this is Redis or something :) *)
let kv_store = Hashtbl.create 8 in
let 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
in
let 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
Now we can invoke the event:
# let plugin = make_kv_plugin ();;
val plugin : Plugin.t = <abstr>
# 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 = ()
Dependencies (11)
-
uuidm
>= "0.9.8"
-
cmdliner
>= "1.1.1"
-
ppx_inline_test
>= "v0.15.0"
-
extism-manifest
= version
-
ppx_yojson_conv
>= "v0.15.0"
-
bigstringaf
>= "0.9.0"
-
integers
>= "0.3.0"
-
ctypes-foreign
>= "0.22.0"
-
ctypes
>= "0.22.0"
-
dune
>= "3.2"
-
ocaml
>= "4.14.1"
Used by
None
Conflicts
None