Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/extism/ruby-sdk
Extism Ruby Host SDK
https://github.com/extism/ruby-sdk
Last synced: 2 days ago
JSON representation
Extism Ruby Host SDK
- Host: GitHub
- URL: https://github.com/extism/ruby-sdk
- Owner: extism
- License: bsd-3-clause
- Created: 2023-09-12T17:46:03.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-04-05T22:03:21.000Z (7 months ago)
- Last Synced: 2024-05-18T20:43:19.787Z (6 months ago)
- Language: Ruby
- Size: 8.66 MB
- Stars: 8
- Watchers: 5
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-wasm-runtimes - `Ruby`
README
# Extism Ruby Host SDK
This repo contains the ruby gem for integrating with the [Extism](https://extism.org/) runtime. Install this library into your host ruby application to run Extism plug-ins.
## 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:
```bash
sudo extism lib install latest#=> Fetching https://github.com/extism/extism/releases/download/v0.5.2/libextism-aarch64-apple-darwin-v0.5.2.tar.gz
#=> Copying libextism.dylib to /usr/local/lib/libextism.dylib
#=> Copying extism.h to /usr/local/include/extism.h
```### Install the Gem
Add this library to your [Gemfile](https://bundler.io/):
```ruby
gem 'extism', '~> 1.0.0'
```Or if installing on the system level:
```
gem install extism
```## Getting Started
This guide should walk you through some of the concepts in Extism and this ruby library.
> *Note*: You should be able to follow this guide by copy pasting the code into `irb`.
### 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:
```ruby
# First require the library
require "extism"url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
manifest = Extism::Manifest.from_url url
plugin = Extism::Plugin.new(manifest)
```> **Note**: See [the Manifest docs](https://extism.github.io/ruby-sdk/Extism/Manifest.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/ruby-sdk/Extism/Plugin.html#call-instance_method):
```ruby
plugin.call("count_vowels", "Hello, World!")
# => {"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.
### 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:
```ruby
plugin.call("count_vowels", "Hello, World!")
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
plugin.call("count_vowels", "Hello, World!")
# => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}
```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:
```ruby
plugin = Extism::Plugin.new(manifest)
plugin.call("count_vowels", "Yellow, World!")
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}plugin = Extism::Plugin.new(manifest, config: { vowels: "aeiouyAEIOUY" })
plugin.call("count_vowels", "Yellow, World!")
# => {"count": 4, "total": 4, "vowels": "aeiouAEIOUY"}
```### 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 ruby methods 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:
```ruby
url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"
manifest = Extism::Manifest.from_url(url)
```> *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.
In the ruby sdk, we have a concept for this called a [Host Environment](https://extism.github.io/ruby-sdk/Extism/HostEnvironment.html). An environment is an instance of a class that implements any host functions your plug-in needs.
We want to expose two functions to our plugin, `kv_write(key: String, value: Bytes)` which writes a bytes value to a key and `kv_read(key: String) -> Bytes` which reads the bytes at the given `key`.
```ruby
# pretend this is Redis or something :)
KV_STORE = {}class KvEnvironment
include Extism::HostEnvironment# We need to describe the wasm function signature of each host function
# to register them to this environment
register_import :kv_read, [Extism::ValType::PTR], [Extism::ValType::PTR]
register_import :kv_write, [Extism::ValType::PTR, Extism::ValType::PTR], []def kv_read(plugin, inputs, outputs, _user_data)
key = plugin.input_as_string(inputs.first)
val = KV_STORE[key] || [0].pack('V') # get 4 LE bytes for 0 default
puts "Read from key=#{key}"
plugin.output_string(outputs.first, val)
enddef kv_write(plugin, inputs, _outputs, _user_data)
key = plugin.input_as_string(inputs.first)
val = plugin.input_as_string(inputs[1])
puts "Writing value=#{val.unpack1('V')} from key=#{key}"
KV_STORE[key] = val
end
end
```> *Note*: In order to write host functions you should get familiar with the methods on the [Extism::CurrentPlugin](https://extism.github.io/ruby-sdk/Extism/CurrentPlugin.html) class. The `plugin` parameter is an instance of this class.
Now we just need to create a new host environment and pass it in when loading the plug-in. Here our environment initializer takes no arguments, but you could imagine putting some customer specific instance variables in there:
```ruby
env = KvEnvironment.new
plugin = Extism::Plugin.new(manifest, environment: env)
```Now we can invoke the event:
```ruby
plugin.call("count_vowels", "Hello, World!")
# => Read from key=count-vowels"
# => Writing value=3 from key=count-vowels"
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
plugin.call("count_vowels", "Hello, World!")
# => Read from key=count-vowels"
# => Writing value=6 from key=count-vowels"
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
```