extism 0.4.0 → 1.0.0.pre.rc.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21688f8f388970bd00387e58e091cdb71eeef9e8f5bf864dd152229926752ee2
4
- data.tar.gz: 180199c53864f904220ceba2a796ec0105960e9d0243f5d4293c8080bc72c342
3
+ metadata.gz: ed731bd8ff622c67eee229b14588ce7e6311337d5b3c888aca2e116f77758b4b
4
+ data.tar.gz: e0bea72460c008c16dc4747c39ebb09643c48ddecf6c5a5efc0bae754609ebdb
5
5
  SHA512:
6
- metadata.gz: a7dc0b31d1ca8ce1d79edd7894cd178a35ea0e4b02ec9f70f09d3fd77c8fa55129f7660d3e5f557493757eb90437426479ef957e6359106b52694e42fb6a808f
7
- data.tar.gz: 314c6f702820fe6f0e9efa08d02a745fcfdf2aa8b6492477dc77a31da7f3a07c28436339365ecb483ebca60093b91962dabf39e7f69d38f94e117f8f935ac4a2
6
+ metadata.gz: 81e70c7e6b7b649733f68b4c284104155ccec044f9604882b99cb54b626a777aa4e7dcfdc51a224b00600c6ee705f4a9b5761c85b5fb73ebff542be94ae5627c
7
+ data.tar.gz: 39ee1905ea234277c040698eae6098dfcc5d0ffac53185598536ac915dc4999c316bdab88d5ccb716895bf006d2cb25fc6b636640a5402b5b8aaf5b658653fd7
data/.yardopts CHANGED
@@ -1,2 +1 @@
1
- --readme GETTING_STARTED.md
2
- - GETTING_STARTED.md
1
+ --readme README.md
data/Gemfile CHANGED
@@ -1,15 +1,16 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in extism.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
9
- gem "ffi", "~> 1.15.5"
10
-
11
- group :development do
12
- gem "yard", "~> 0.9.28"
13
- gem "rufo", "~> 0.13.0"
14
- gem "minitest", "~> 5.18.0"
15
- end
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in extism.gemspec
6
+ gemspec
7
+
8
+ gem 'ffi', '~> 1.15.5'
9
+ gem 'rake', '~> 13.0'
10
+
11
+ group :development do
12
+ gem 'debug'
13
+ gem 'minitest', '~> 5.20.0'
14
+ gem 'rufo', '~> 0.13.0'
15
+ gem 'yard', '~> 0.9.28'
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ extism (1.0.0.pre.rc.1)
5
+ ffi (>= 1.0.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ debug (1.8.0)
11
+ irb (>= 1.5.0)
12
+ reline (>= 0.3.1)
13
+ ffi (1.15.5)
14
+ io-console (0.6.0)
15
+ irb (1.8.1)
16
+ rdoc
17
+ reline (>= 0.3.8)
18
+ minitest (5.20.0)
19
+ psych (5.1.0)
20
+ stringio
21
+ rake (13.0.6)
22
+ rdoc (6.5.0)
23
+ psych (>= 4.0.0)
24
+ reline (0.3.8)
25
+ io-console (~> 0.5)
26
+ rufo (0.13.0)
27
+ stringio (3.0.8)
28
+ yard (0.9.34)
29
+
30
+ PLATFORMS
31
+ arm64-darwin-22
32
+
33
+ DEPENDENCIES
34
+ debug
35
+ extism!
36
+ ffi (~> 1.15.5)
37
+ minitest (~> 5.20.0)
38
+ rake (~> 13.0)
39
+ rufo (~> 0.13.0)
40
+ yard (~> 0.9.28)
41
+
42
+ BUNDLED WITH
43
+ 2.4.10
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2022 Dylibso, Inc.
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Makefile CHANGED
@@ -1,33 +1,39 @@
1
- RUBYGEMS_API_KEY ?=
2
-
3
- .PHONY: prepare test
4
-
5
- prepare:
6
- bundle install
7
- bundle binstubs --all
8
-
9
- test: prepare
10
- bundle exec rake test
11
-
12
- clean:
13
- rm -f extism-*.gem
14
-
15
- publish-local: clean prepare
16
- gem build extism.gemspec
17
- gem push extism-*.gem
18
-
19
- publish: clean prepare
20
- gem build extism.gemspec
21
- GEM_HOST_API_KEY=$(RUBYGEMS_API_KEY) gem push extism-*.gem
22
-
23
- lint:
24
- bundle exec rufo --check .
25
-
26
- format:
27
- bundle exec rufo .
28
-
29
- docs:
30
- bundle exec yard
31
-
32
- show-docs: docs
33
- open doc/index.html
1
+ RUBYGEMS_API_KEY ?=
2
+
3
+ .PHONY: prepare test
4
+
5
+ prepare:
6
+ bundle install
7
+ bundle binstubs --all
8
+
9
+ test: prepare
10
+ bundle exec rake test
11
+
12
+ clean:
13
+ rm -f extism-*.gem
14
+
15
+ publish-local: clean prepare
16
+ gem build extism.gemspec
17
+ gem push extism-*.gem
18
+
19
+ publish: clean prepare
20
+ gem build extism.gemspec
21
+ GEM_HOST_API_KEY=$(RUBYGEMS_API_KEY) gem push extism-*.gem
22
+
23
+ lint:
24
+ bundle exec rufo --check .
25
+
26
+ format:
27
+ bundle exec rufo .
28
+
29
+ docs:
30
+ bundle exec yard
31
+
32
+ show-docs: docs
33
+ open doc/index.html
34
+
35
+ seed:
36
+ curl -L https://github.com/extism/plugins/releases/latest/download/count_vowels.debug.wasm > wasm/count_vowels.wasm
37
+ curl -L https://github.com/extism/plugins/releases/latest/download/reflect.debug.wasm > wasm/reflect.wasm
38
+ curl -L https://github.com/extism/plugins/releases/latest/download/store_credit.debug.wasm > wasm/store_credit.wasm
39
+
data/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # Extism Ruby Host SDK
2
+
3
+ > **Note**: This houses the 1.0 version of the Ruby SDK and is a work in progress. Please use the ruby SDK in extism/extism until we hit 1.0.
4
+
5
+ This repo houses the ruby gem for integrating with the [Extism](https://extism.org/) runtime. Install this library into your host ruby applications to run Extism plugins.
6
+
7
+ ## Installation
8
+
9
+ You first need to [install the Extism runtime](https://extism.org/docs/install).
10
+
11
+ Add this library to your [Gemfile](https://bundler.io/):
12
+
13
+ ```ruby
14
+ gem 'extism', '1.0.0-rc.1'
15
+ ```
16
+
17
+ Or if installing on the system level:
18
+
19
+ ```
20
+ gem install extism
21
+ ```
22
+
23
+ ## Getting Started
24
+
25
+ First you should require `"extism"`:
26
+
27
+ ```ruby
28
+ require "extism"
29
+ ```
30
+
31
+ ### Creating A Plug-in
32
+
33
+ The primary concept in Extism is the plug-in. You can think of a plug-in as a code module. It has imports and it has exports. These imports and exports define the interface, or your API. You decide what they are called and typed, and what they do. Then the plug-in developer implements them and you can call them.
34
+
35
+ The code for a plug-in exist as a binary wasm module. We can load this with the raw bytes or we can use the manifest to tell Extism how to load it from disk or the web.
36
+
37
+ For simplicity let's load one from the web:
38
+
39
+ ```ruby
40
+ manifest = {
41
+ wasm: [
42
+ { url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm" }
43
+ ]
44
+ }
45
+ plugin = Extism::Plugin.new(manifest)
46
+ ```
47
+
48
+ > **Note**: The schema for this manifest can be found here: https://extism.org/docs/concepts/manifest/
49
+
50
+ ### Calling A Plug-in's Exports
51
+
52
+ This plug-in was written in C 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`:
53
+
54
+ ```ruby
55
+ plugin.call("count_vowels", "Hello, World!")
56
+ # => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
57
+ ```
58
+
59
+ All exports have a simple interface of optional bytes in, and optional bytes out. This plug-in happens to take a string and return a JSON encoded string with a report of results.
60
+
61
+
62
+ ### Plug-in State
63
+
64
+ 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:
65
+
66
+ ```ruby
67
+ plugin.call("count_vowels", "Hello, World!")
68
+ # => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
69
+ plugin.call("count_vowels", "Hello, World!")
70
+ # => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}
71
+ ```
72
+
73
+ These variables will persist until this plug-in is freed or you initialize a new one.
74
+
75
+ ### Configuration
76
+
77
+ 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:
78
+
79
+ ```ruby
80
+ plugin = Extism::Plugin.new(manifest)
81
+ plugin.call("count_vowels", "Yellow, World!")
82
+ # => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
83
+
84
+ plugin = Extism::Plugin.new(manifest, config: { vowels: "aeiouyAEIOUY" })
85
+ plugin.call("count_vowels", "Yellow, World!")
86
+ # => {"count": 4, "total": 4, "vowels": "aeiouAEIOUY"}
87
+ ```
88
+
89
+ ### Host Functions
90
+
91
+ 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 to and invoked from any language inside the plug-in.
92
+
93
+ > *Note*: Host functions can be a complicated topic. Please review this [concept doc](https://extism.org/docs/concepts/host-functions) if you are unsure how they work.
94
+
95
+ ### Host Functions Example
96
+
97
+ We've created a contrived, but familiar example to illustrate this. Suppose you are a stripe-like payments platform.
98
+ When a [charge.succeeded](https://stripe.com/docs/api/events/types#event_types-charge.succeeded) event occurs, we will call the `on_charge_succeeded` function on our merchant's plug-in and let them decide what to do with it. Here our merchant has some very specific requirements, if the account has spent more than $100, their currency is USD, and they have no credits on their account, it will add $10 credit to their account and then send them an email.
99
+
100
+ > *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/store_credit/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages.
101
+
102
+ First let's create the manifest for our plug-in like usual but load up the store_credit plug-in:
103
+
104
+ ```ruby
105
+ manifest = {
106
+ wasm: [
107
+ { url: "https://github.com/extism/plugins/releases/latest/download/store_credit.wasm" }
108
+ ]
109
+ }
110
+ ```
111
+
112
+ But, unlike our original plug-in, this plug-in expects you to provide host functions that satisfy our plug-ins imports.
113
+
114
+ In the ruby sdk, we have a concept for this call an "host environment". An environment is just an object that responds to `host_functions` and returns an array of `Extism::Function`s. We want to expose two capabilities to our plugin, `add_credit(customer_id, amount)` which adds credit to an account and `send_email(customer_id, email)` which sends them an email.
115
+
116
+ ```ruby
117
+
118
+ # This is global is just for demo purposes but would in
119
+ # reality be in a database or something
120
+ CUSTOMER = {
121
+ full_name: 'John Smith',
122
+ customer_id: 'abcd1234',
123
+ total_spend: {
124
+ currency: 'USD',
125
+ amount_in_cents: 20_000
126
+ },
127
+ credit: {
128
+ currency: 'USD',
129
+ amount_in_cents: 0
130
+ }
131
+ }
132
+
133
+ class Environment
134
+ include Extism::HostEnvironment
135
+
136
+ register_import :add_credit, [Extism::ValType::I64, Extism::ValType::I64], [Extism::ValType::I64]
137
+ register_import :send_email, [Extism::ValType::I64, Extism::ValType::I64], []
138
+
139
+ def add_credit(plugin, inputs, outputs, _user_data)
140
+ # add_credit takes a string `customer_id` as the first parameter
141
+ customer_id = plugin.input_as_string(inputs.first)
142
+ # it takes an object `amount` { amount_in_cents: int, currency: string } as the second parameter
143
+ amount = plugin.input_as_json(inputs[1])
144
+
145
+ # we're just going to print it out and add to the CUSTOMER global
146
+ puts "Adding Credit #{amount} to customer #{customer_id}"
147
+ CUSTOMER[:credit][:amount_in_cents] += amount['amount_in_cents']
148
+
149
+ # add_credit returns a Json object with the new customer details
150
+ plugin.return_json(outputs.first, CUSTOMER)
151
+ end
152
+
153
+ def send_email(plugin, inputs, _outputs, _user_data)
154
+ # send_email takes a string `customer_id` as the first parameter
155
+ customer_id = plugin.input_as_string(inputs.first)
156
+ # it takes an object `email` { subject: string, body: string } as the second parameter
157
+ email = plugin.input_as_json(inputs[1])
158
+
159
+ # we'll just print it but you could imagine we'd put something
160
+ # in a database or call an internal api to send this email
161
+ puts "Sending email #{email} to customer #{customer_id}"
162
+
163
+ # it doesn't return anything
164
+ end
165
+ end
166
+ ```
167
+
168
+ 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 merchant specific instance variables in there:
169
+
170
+ ```ruby
171
+ env = Environment.new
172
+ plugin = Extism::Plugin.new(manifest, environment: env)
173
+ ```
174
+
175
+ Now we can invoke the event:
176
+
177
+ ```ruby
178
+ event = {
179
+ event_type: 'charge.succeeded',
180
+ customer: CUSTOMER
181
+ }
182
+ result = plugin.call('on_charge_succeeded', JSON.generate(event))
183
+ ```
184
+
185
+ This will print:
186
+
187
+ ```
188
+ Adding Credit {"amount_in_cents"=>1000, "currency"=>"USD"} for customer abcd1234
189
+ Sending email {"subject"=>"A gift for you John Smith", "body"=>"You have received $10 in store credi
190
+ t!"} to customer abcd1234
191
+ ```
data/Rakefile CHANGED
@@ -1,12 +1,12 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
10
- end
11
-
12
- task default: :test
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,125 @@
1
+ module Extism
2
+ # Represents a reference to a plugin in a host function
3
+ # Use this class to read and write to the memory of the plugin
4
+ # These methods allow you to get data in and out of the plugin
5
+ # in a host function
6
+ class CurrentPlugin
7
+ # let's not let people construct these since it comes from a pointer
8
+ private_class_method :new
9
+
10
+ # Initialize a CurrentPlugin given an pointer
11
+ #
12
+ # @param ptr [FFI::Pointer] the raw pointer to the plugin
13
+ def initialize(ptr)
14
+ @ptr = ptr
15
+ end
16
+
17
+ # Allocates a memory block in the plugin
18
+ #
19
+ # @param amount [Integer] The amount in bytes to allocate
20
+ # @return [Extism::Memory] The reference to the freshly allocated memory
21
+ def alloc(amount)
22
+ offset = LibExtism.extism_current_plugin_memory_alloc(@ptr, amount)
23
+ Memory.new(offset, amount)
24
+ end
25
+
26
+ # Frees the memory block
27
+ #
28
+ # @param memory [Extism::Memory] The memory object you wish to free
29
+ # @return [Extism::Memory] The reference to the freshly allocated memory
30
+ def free(memory)
31
+ LibExtism.extism_current_plugin_memory_free(@ptr, memory.offset)
32
+ end
33
+
34
+ # Gets the memory block at a given offset
35
+ #
36
+ # @raise [Extism::Error] if memory block could not be found
37
+ #
38
+ # @param offset [Integer] The offset pointer to the memory. This is relative to the plugin not the host.
39
+ # @return [Extism::Memory] The reference to the memory block if found
40
+ def memory_at_offset(offset)
41
+ len = LibExtism.extism_current_plugin_memory_length(@ptr, offset)
42
+ raise Extism::Error, "Could not find memory block at offset #{offset}" if len.zero?
43
+
44
+ Memory.new(offset, len)
45
+ end
46
+
47
+ # Gets the input as a string
48
+ #
49
+ # @raise [Extism::Error] if memory block could not be found
50
+ #
51
+ # @param input [Extism::Val] The input val from the host function
52
+ # @return [String] raw bytes as a string
53
+ def input_as_string(input)
54
+ raise ArgumentError, 'input is not an Extism::Val' unless input.instance_of? Extism::Val
55
+
56
+ mem = memory_at_offset(input.value)
57
+ memory_ptr(mem).read_bytes(mem.len)
58
+ end
59
+
60
+ # Gets the input as a string
61
+ #
62
+ # @raise [Extism::Error] if memory block could not be found
63
+ #
64
+ # @param input [Extism::Val] The input val from the host function
65
+ # @return [Hash] The Hash object
66
+ def input_as_json(input)
67
+ raise ArgumentError, 'input is not an Extism::Val' unless input.instance_of? Extism::Val
68
+
69
+ mem = memory_at_offset(input.value)
70
+ str = memory_ptr(mem).read_bytes(mem.len)
71
+ JSON.parse(str)
72
+ end
73
+
74
+ # Sets string to the return of the host function
75
+ #
76
+ # @raise [Extism::Error] if memory block could not be found
77
+ #
78
+ # @param output [Extism::Val] The output val from the host function
79
+ # @param bytes [String] The bytes to set
80
+ def output_string(output, bytes)
81
+ mem = alloc(bytes.length)
82
+ memory_ptr(mem).put_bytes(0, bytes)
83
+ set_return(output, mem.offset)
84
+ end
85
+
86
+ # Sets json to the return of the host function
87
+ #
88
+ # @raise [Extism::Error] if memory block could not be found
89
+ #
90
+ # @param output [Extism::Val] The output val from the host function
91
+ # @param obj [Hash] The hash object to turn to JSON
92
+ def output_json(output, obj)
93
+ bytes = JSON.generate(obj)
94
+ mem = alloc(bytes.length)
95
+ memory_ptr(mem).put_bytes(0, bytes)
96
+ set_return(output, mem.offset)
97
+ end
98
+
99
+ # Sets the return value parameter
100
+ #
101
+ # @raise [Extism::Error] if memory block could not be found
102
+ #
103
+ # @param output [Extism::Val] The output val from the host function
104
+ # @param value [Integer | Float] The i32 value
105
+ def set_return(output, value)
106
+ case output.type
107
+ when :i32, :i64, :f32, :f64
108
+ output.value = value
109
+ else
110
+ raise ArgumentError, "Don't know how to set output type #{output.type}"
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ # Returns a raw pointer (absolute to the host) to the given memory block
117
+ # Be careful with this. it's not exposed for a reason.
118
+ # This is a pointer in host memory so it could read outside of the plugin
119
+ # if manipulated
120
+ def memory_ptr(mem)
121
+ plugin_ptr = LibExtism.extism_current_plugin_memory(@ptr)
122
+ FFI::Pointer.new(plugin_ptr.address + mem.offset)
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,28 @@
1
+ module Extism
2
+ module HostEnvironment
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.class_variable_set(:@@import_funcs, [])
6
+ end
7
+
8
+ def host_functions
9
+ import_funcs = self.class.class_variable_get(:@@import_funcs)
10
+ import_funcs.map do |f|
11
+ name, params, returns = f
12
+ Extism::Function.new(
13
+ name.to_s,
14
+ params,
15
+ returns,
16
+ method(name).to_proc
17
+ )
18
+ end
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ def register_import(func_name, parameters, returns)
24
+ import_funcs = class_variable_get(:@@import_funcs)
25
+ import_funcs << [func_name, parameters, returns]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,77 @@
1
+ module Extism
2
+ # Private module used to interface with the Extism runtime.
3
+ # *Warning*: Do not use or rely on this directly
4
+ # improperly using this interface may enable exploits and the interface
5
+ # might change over time.
6
+ module LibExtism
7
+ extend FFI::Library
8
+ ffi_lib 'extism'
9
+
10
+ def self.from_int_array(ruby_array)
11
+ ptr = FFI::MemoryPointer.new(:int, ruby_array.length)
12
+ ptr.write_array_of_int(ruby_array)
13
+ ptr
14
+ end
15
+
16
+ typedef :uint64, :ExtismMemoryHandle
17
+ typedef :uint64, :ExtismSize
18
+
19
+ enum :ExtismValType, %i[I32 I64 F32 F64 V128 FuncRef ExternRef]
20
+
21
+ class ExtismValUnion < FFI::Union
22
+ layout :i32, :int32,
23
+ :i64, :int64,
24
+ :f32, :float,
25
+ :f64, :double
26
+ end
27
+
28
+ class ExtismVal < FFI::Struct
29
+ layout :t, :ExtismValType,
30
+ :v, ExtismValUnion
31
+ end
32
+
33
+ class ExtismFunction < FFI::Struct
34
+ layout :name, :string,
35
+ :inputs, :pointer,
36
+ :n_inputs, :uint64,
37
+ :outputs, :pointer,
38
+ :n_outputs, :uint64,
39
+ :data, :pointer
40
+ end
41
+
42
+ callback :ExtismFunctionType, [
43
+ :pointer, # plugin
44
+ :pointer, # inputs
45
+ :ExtismSize, # n_inputs
46
+ :pointer, # outputs
47
+ :ExtismSize, # n_outputs
48
+ :pointer # user_data
49
+ ], :void
50
+
51
+ callback :ExtismFreeFunctionType, [], :void
52
+
53
+ attach_function :extism_plugin_id, [:pointer], :pointer
54
+ attach_function :extism_current_plugin_memory, [:pointer], :pointer
55
+ attach_function :extism_current_plugin_memory_alloc, %i[pointer ExtismSize], :ExtismMemoryHandle
56
+ attach_function :extism_current_plugin_memory_length, %i[pointer ExtismMemoryHandle], :ExtismSize
57
+ attach_function :extism_current_plugin_memory_free, %i[pointer ExtismMemoryHandle], :void
58
+ attach_function :extism_function_new,
59
+ %i[string pointer ExtismSize pointer ExtismSize ExtismFunctionType ExtismFreeFunctionType pointer], :pointer
60
+ attach_function :extism_function_free, [:pointer], :void
61
+ attach_function :extism_function_set_namespace, %i[pointer string], :void
62
+ attach_function :extism_plugin_new, %i[pointer ExtismSize pointer ExtismSize bool pointer], :pointer
63
+ attach_function :extism_plugin_new_error_free, [:pointer], :void
64
+ attach_function :extism_plugin_free, [:pointer], :void
65
+ attach_function :extism_plugin_cancel_handle, [:pointer], :pointer
66
+ attach_function :extism_plugin_cancel, [:pointer], :bool
67
+ attach_function :extism_plugin_config, %i[pointer pointer ExtismSize], :bool
68
+ attach_function :extism_plugin_function_exists, %i[pointer string], :bool
69
+ attach_function :extism_plugin_call, %i[pointer string pointer ExtismSize], :int32
70
+ attach_function :extism_error, [:pointer], :string
71
+ attach_function :extism_plugin_error, [:pointer], :string
72
+ attach_function :extism_plugin_output_length, [:pointer], :ExtismSize
73
+ attach_function :extism_plugin_output_data, [:pointer], :pointer
74
+ attach_function :extism_log_file, %i[string string], :bool
75
+ attach_function :extism_version, [], :string
76
+ end
77
+ end
@@ -0,0 +1,86 @@
1
+ module Extism
2
+ # A Plugin represents an instance of your WASM program
3
+ # created from the given manifest.
4
+ class Plugin
5
+ # Intialize a plugin
6
+ #
7
+ # @param wasm [Hash, String] The manifest as a Hash or WASM binary as a String. See https://extism.org/docs/concepts/manifest/.
8
+ # @param wasi [Boolean] Enable WASI support
9
+ # @param config [Hash] The plugin config
10
+ def initialize(wasm, environment: nil, functions: [], wasi: false, config: nil)
11
+ wasm = JSON.generate(wasm) if wasm.instance_of?(Hash)
12
+ code = FFI::MemoryPointer.new(:char, wasm.bytesize)
13
+ errmsg = FFI::MemoryPointer.new(:pointer)
14
+ code.put_bytes(0, wasm)
15
+ if functions.empty? && environment
16
+ unless environment.respond_to?(:host_functions)
17
+ raise ArgumentError 'environment should implement host_functions method'
18
+ end
19
+
20
+ functions = environment.host_functions
21
+ end
22
+ funcs_ptr = FFI::MemoryPointer.new(LibExtism::ExtismFunction)
23
+ funcs_ptr.write_array_of_pointer(functions.map { |f| f.send(:pointer) })
24
+ @plugin = LibExtism.extism_plugin_new(code, wasm.bytesize, funcs_ptr, functions.length, wasi, errmsg)
25
+ if @plugin.null?
26
+ err = errmsg.read_pointer.read_string
27
+ LibExtism.extism_plugin_new_error_free errmsg.read_pointer
28
+ raise Error, err
29
+ end
30
+ $PLUGINS[object_id] = { plugin: @plugin }
31
+ ObjectSpace.define_finalizer(self, $FREE_PLUGIN)
32
+ return unless !config.nil? and @plugin.null?
33
+
34
+ s = JSON.generate(config)
35
+ ptr = FFI::MemoryPointer.from_string(s)
36
+ LibExtism.extism_plugin_config(@plugin, ptr, s.bytesize)
37
+ end
38
+
39
+ # Check if a function exists
40
+ #
41
+ # @param name [String] The name of the function
42
+ # @return [Boolean] Returns true if function exists
43
+ def has_function?(name)
44
+ LibExtism.extism_plugin_function_exists(@plugin, name)
45
+ end
46
+
47
+ # Call a function by name
48
+ #
49
+ # @param name [String] The function name
50
+ # @param data [String] The input data for the function
51
+ # @return [String] The output from the function in String form
52
+ def call(name, data, &block)
53
+ # If no block was passed then use Pointer::read_string
54
+ block ||= ->(buf, len) { buf.read_string(len) }
55
+ input = FFI::MemoryPointer.from_string(data)
56
+ rc = LibExtism.extism_plugin_call(@plugin, name, input, data.bytesize)
57
+ if rc != 0
58
+ err = LibExtism.extism_plugin_error(@plugin)
59
+ raise Error, 'extism_call failed' if err&.empty?
60
+
61
+ raise Error, err
62
+
63
+ end
64
+
65
+ out_len = LibExtism.extism_plugin_output_length(@plugin)
66
+ buf = LibExtism.extism_plugin_output_data(@plugin)
67
+ block.call(buf, out_len)
68
+ end
69
+
70
+ # Free a plugin, this should be called when the plugin is no longer needed
71
+ #
72
+ # @return [void]
73
+ def free
74
+ return if @plugin.null?
75
+
76
+ $PLUGINS.delete(object_id)
77
+ LibExtism.extism_plugin_free(@plugin)
78
+ @plugin = nil
79
+ end
80
+
81
+ # Get a CancelHandle for a plugin
82
+ def cancel_handle
83
+ CancelHandle.new(LibExtism.extism_plugin_cancel_handle(@plugin))
84
+ end
85
+ end
86
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Extism
4
- VERSION = "0.4.0"
4
+ VERSION = '1.0.0-rc.1'
5
5
  end
@@ -0,0 +1,102 @@
1
+ module Extism
2
+ module ValType
3
+ I32 = 0
4
+ I64 = 1
5
+ F32 = 2
6
+ F64 = 3
7
+ V128 = 4
8
+ FUNC_REF = 5
9
+ EXTERN_REF = 6
10
+ end
11
+
12
+ class Val
13
+ def initialize(ptr)
14
+ @c_val = LibExtism::ExtismVal.new(ptr)
15
+ end
16
+
17
+ def type
18
+ case @c_val[:t]
19
+ when :I32
20
+ :i32
21
+ when :I64
22
+ :i64
23
+ when :F32
24
+ :f32
25
+ when :F64
26
+ :f64
27
+ else
28
+ raise "Unsupported wasm value type #{type}"
29
+ end
30
+ end
31
+
32
+ def value
33
+ @c_val[:v][type]
34
+ end
35
+
36
+ def value=(val)
37
+ @c_val[:v][type] = val
38
+ end
39
+ end
40
+
41
+ # A CancelHandle can be used to cancel a running plugin from another thread
42
+ class CancelHandle
43
+ def initialize(handle)
44
+ @handle = handle
45
+ end
46
+
47
+ # Cancel the plugin used to generate the handle
48
+ def cancel
49
+ LibExtism.extism_plugin_cancel(@handle)
50
+ end
51
+ end
52
+
53
+ # Represents a host function
54
+ class Function
55
+ # Create a new host function
56
+ #
57
+ # @param name [String] Must match the import name in Wasm. Doesn't include namespace. All extism host functions are in the env name space
58
+ # @param params [Array[Extism::ValType]] An array of val types matching the import's params
59
+ # @param returns [Array[Extism::ValType]] An array of val types matching the import returns
60
+ # @param func_proc [Proc] A proc that will be executed when the host function is executed
61
+ # @param user_data [Object] Any reference to object you want to be passed back to you when the func is invoked
62
+ # @param on_free [Proc] A proc triggered when this function is freed by the runtime. Not guaranteed to trigger.
63
+ def initialize(name, params, returns, func_proc, user_data: nil, on_free: nil)
64
+ @name = name
65
+ @params = params
66
+ @returns = returns
67
+ @func = func_proc
68
+ @user_data = user_data
69
+ @on_free = on_free
70
+ end
71
+
72
+ private
73
+
74
+ # Gets the pointer to this function.
75
+ # Warning: This should not be used
76
+ def pointer
77
+ return @_pointer if @_pointer
78
+
79
+ free = @on_free || proc {}
80
+ args = LibExtism.from_int_array(@params)
81
+ returns = LibExtism.from_int_array(@returns)
82
+ @_pointer = LibExtism.extism_function_new(@name, args, @params.length, returns, @returns.length, c_func, free,
83
+ nil)
84
+ end
85
+
86
+ def c_func
87
+ @c_func ||= proc do |plugin_ptr, inputs_ptr, inputs_size, outputs_ptr, outputs_size, _data_ptr|
88
+ current_plugin = Extism::CurrentPlugin.send(:new, plugin_ptr)
89
+ val_struct_size = LibExtism::ExtismVal.size
90
+
91
+ inputs = (0...inputs_size).map do |i|
92
+ Val.new(inputs_ptr + i * val_struct_size)
93
+ end
94
+ outputs = (0...outputs_size).map do |i|
95
+ Val.new(outputs_ptr + i * val_struct_size)
96
+ end
97
+
98
+ @func.call(current_plugin, inputs, outputs, @user_data)
99
+ end
100
+ end
101
+ end
102
+ end
data/lib/extism.rb CHANGED
@@ -1,6 +1,11 @@
1
- require "ffi"
2
- require "json"
3
- require_relative "./extism/version"
1
+ require 'ffi'
2
+ require 'json'
3
+ require_relative './extism/version'
4
+ require_relative './extism/plugin'
5
+ require_relative './extism/current_plugin'
6
+ require_relative './extism/libextism'
7
+ require_relative './extism/wasm'
8
+ require_relative './extism/host_environment'
4
9
 
5
10
  module Extism
6
11
  class Error < StandardError
@@ -10,258 +15,24 @@ module Extism
10
15
  #
11
16
  # @return [String] The version string of the Extism runtime
12
17
  def self.extism_version
13
- C.extism_version
18
+ LibExtism.extism_version
14
19
  end
15
20
 
16
21
  # Set log file and level, this is a global configuration
17
22
  # @param name [String] The path to the logfile
18
23
  # @param level [String] The log level. One of {"debug", "error", "info", "trace" }
19
24
  def self.set_log_file(name, level = nil)
20
- if level
21
- level = FFI::MemoryPointer::from_string(level)
22
- end
23
- C.extism_log_file(name, level)
25
+ LibExtism.extism_log_file(name, level)
24
26
  end
25
27
 
26
28
  $PLUGINS = {}
27
- $FREE_PLUGIN = proc { |id|
28
- x = $PLUGINS[id]
29
- if !x.nil?
30
- C.extism_plugin_free(x[:context].pointer, x[:plugin])
31
- $PLUGINS.delete(id)
32
- end
33
- }
34
-
35
- $CONTEXTS = {}
36
- $FREE_CONTEXT = proc { |id|
37
- x = $CONTEXTS[id]
38
- if !x.nil?
39
- C.extism_context_free($CONTEXTS[id])
40
- $CONTEXTS.delete(id)
29
+ $FREE_PLUGIN = proc { |ptr|
30
+ x = $PLUGINS[ptr]
31
+ unless x.nil?
32
+ LibExtism.extism_plugin_free(x[:plugin])
33
+ $PLUGINS.delete(ptr)
41
34
  end
42
35
  }
43
36
 
44
- # A Context is needed to create plugins. The Context
45
- # is where your plugins live. Freeing the context
46
- # frees all of the plugins in its scope.
47
- #
48
- # @example Create and free a context
49
- # ctx = Extism::Context.new
50
- # plugin = ctx.plugin(my_manifest)
51
- # puts plugin.call("my_func", "my-input")
52
- # ctx.free # frees any plugins
53
- #
54
- # @example Use with_context to auto-free
55
- # Extism.with_context do |ctx|
56
- # plugin = ctx.plugin(my_manifest)
57
- # puts plugin.call("my_func", "my-input")
58
- # end # frees context after exiting this block
59
- #
60
- # @attr_reader pointer [FFI::Pointer] Pointer to the Extism context. *Used internally*.
61
- class Context
62
- attr_reader :pointer
63
-
64
- # Initialize a new context
65
- def initialize
66
- @pointer = C.extism_context_new()
67
- $CONTEXTS[self.object_id] = @pointer
68
- ObjectSpace.define_finalizer(self, $FREE_CONTEXT)
69
- end
70
-
71
- # Remove all registered plugins in this context
72
- # @return [void]
73
- def reset
74
- C.extism_context_reset(@pointer)
75
- end
76
-
77
- # Free the context, this should be called when it is no longer needed
78
- # @return [void]
79
- def free
80
- return if @pointer.nil?
81
-
82
- $CONTEXTS.delete(self.object_id)
83
- C.extism_context_free(@pointer)
84
- @pointer = nil
85
- end
86
-
87
- # Create a new plugin from a WASM module or JSON encoded manifest
88
- #
89
- # @see Plugin#new
90
- # @param wasm [Hash, String] The manifest for the plugin. See https://extism.org/docs/concepts/manifest/.
91
- # @param wasi [Boolean] Enable WASI support
92
- # @param config [Hash] The plugin config
93
- # @return [Plugin]
94
- def plugin(wasm, wasi = false, config = nil)
95
- Plugin.new(wasm, wasi, config, self)
96
- end
97
- end
98
-
99
- # A context manager to create contexts and ensure that they get freed.
100
- #
101
- # @example Use with_context to auto-free
102
- # Extism.with_context do |ctx|
103
- # plugin = ctx.plugin(my_manifest)
104
- # puts plugin.call("my_func", "my-input")
105
- # end # frees context after exiting this block
106
- #
107
- # @yield [ctx] Yields the created Context
108
- # @return [Object] returns whatever your block returns
109
- def self.with_context(&block)
110
- ctx = Context.new
111
- begin
112
- x = block.call(ctx)
113
- return x
114
- ensure
115
- ctx.free
116
- end
117
- end
118
-
119
- # A CancelHandle can be used to cancel a running plugin from another thread
120
- class CancelHandle
121
- def initialize(handle)
122
- @handle = handle
123
- end
124
-
125
- # Cancel the plugin used to generate the handle
126
- def cancel
127
- return C.extism_plugin_cancel(@handle)
128
- end
129
- end
130
-
131
- # A Plugin represents an instance of your WASM program from the given manifest.
132
- class Plugin
133
- # Intialize a plugin
134
- #
135
- # @param wasm [Hash, String] The manifest or WASM binary. See https://extism.org/docs/concepts/manifest/.
136
- # @param wasi [Boolean] Enable WASI support
137
- # @param config [Hash] The plugin config
138
- # @param context [Context] The context to manager this plugin
139
- def initialize(wasm, wasi = false, config = nil, context = nil)
140
- if context.nil? then
141
- context = Context.new
142
- end
143
- @context = context
144
- if wasm.class == Hash
145
- wasm = JSON.generate(wasm)
146
- end
147
- code = FFI::MemoryPointer.new(:char, wasm.bytesize)
148
- code.put_bytes(0, wasm)
149
- @plugin = C.extism_plugin_new(context.pointer, code, wasm.bytesize, nil, 0, wasi)
150
- if @plugin < 0
151
- err = C.extism_error(@context.pointer, -1)
152
- if err&.empty?
153
- raise Error.new "extism_plugin_new failed"
154
- else
155
- raise Error.new err
156
- end
157
- end
158
- $PLUGINS[self.object_id] = { :plugin => @plugin, :context => context }
159
- ObjectSpace.define_finalizer(self, $FREE_PLUGIN)
160
- if config != nil and @plugin >= 0
161
- s = JSON.generate(config)
162
- ptr = FFI::MemoryPointer::from_string(s)
163
- C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
164
- end
165
- end
166
-
167
- # Update a plugin with new WASM module or manifest
168
- #
169
- # @param wasm [Hash, String] The manifest or WASM binary. See https://extism.org/docs/concepts/manifest/.
170
- # @param wasi [Boolean] Enable WASI support
171
- # @param config [Hash] The plugin config
172
- # @return [void]
173
- def update(wasm, wasi = false, config = nil)
174
- if wasm.class == Hash
175
- wasm = JSON.generate(wasm)
176
- end
177
- code = FFI::MemoryPointer.new(:char, wasm.bytesize)
178
- code.put_bytes(0, wasm)
179
- ok = C.extism_plugin_update(@context.pointer, @plugin, code, wasm.bytesize, nil, 0, wasi)
180
- if !ok
181
- err = C.extism_error(@context.pointer, @plugin)
182
- if err&.empty?
183
- raise Error.new "extism_plugin_update failed"
184
- else
185
- raise Error.new err
186
- end
187
- end
188
-
189
- if config != nil
190
- s = JSON.generate(config)
191
- ptr = FFI::MemoryPointer::from_string(s)
192
- C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
193
- end
194
- end
195
-
196
- # Check if a function exists
197
- #
198
- # @param name [String] The name of the function
199
- # @return [Boolean] Returns true if function exists
200
- def has_function?(name)
201
- C.extism_plugin_function_exists(@context.pointer, @plugin, name)
202
- end
203
-
204
- # Call a function by name
205
- #
206
- # @param name [String] The function name
207
- # @param data [String] The input data for the function
208
- # @return [String] The output from the function in String form
209
- def call(name, data, &block)
210
- # If no block was passed then use Pointer::read_string
211
- block ||= ->(buf, len) { buf.read_string(len) }
212
- input = FFI::MemoryPointer::from_string(data)
213
- rc = C.extism_plugin_call(@context.pointer, @plugin, name, input, data.bytesize)
214
- if rc != 0
215
- err = C.extism_error(@context.pointer, @plugin)
216
- if err&.empty?
217
- raise Error.new "extism_call failed"
218
- else
219
- raise Error.new err
220
- end
221
- end
222
- out_len = C.extism_plugin_output_length(@context.pointer, @plugin)
223
- buf = C.extism_plugin_output_data(@context.pointer, @plugin)
224
- block.call(buf, out_len)
225
- end
226
-
227
- # Free a plugin, this should be called when the plugin is no longer needed
228
- #
229
- # @return [void]
230
- def free
231
- return if @context.pointer.nil?
232
-
233
- $PLUGINS.delete(self.object_id)
234
- C.extism_plugin_free(@context.pointer, @plugin)
235
- @plugin = -1
236
- end
237
-
238
- # Get a CancelHandle for a plugin
239
- def cancel_handle
240
- return CancelHandle.new(C.extism_plugin_cancel_handle(@context.pointer, @plugin))
241
- end
242
- end
243
-
244
- private
245
-
246
- # Private module used to interface with the Extism runtime.
247
- # *Warning*: Do not use or rely on this directly.
248
- module C
249
- extend FFI::Library
250
- ffi_lib "extism"
251
- attach_function :extism_context_new, [], :pointer
252
- attach_function :extism_context_free, [:pointer], :void
253
- attach_function :extism_plugin_new, [:pointer, :pointer, :uint64, :pointer, :uint64, :bool], :int32
254
- attach_function :extism_plugin_update, [:pointer, :int32, :pointer, :uint64, :pointer, :uint64, :bool], :bool
255
- attach_function :extism_error, [:pointer, :int32], :string
256
- attach_function :extism_plugin_call, [:pointer, :int32, :string, :pointer, :uint64], :int32
257
- attach_function :extism_plugin_function_exists, [:pointer, :int32, :string], :bool
258
- attach_function :extism_plugin_output_length, [:pointer, :int32], :uint64
259
- attach_function :extism_plugin_output_data, [:pointer, :int32], :pointer
260
- attach_function :extism_log_file, [:string, :pointer], :void
261
- attach_function :extism_plugin_free, [:pointer, :int32], :void
262
- attach_function :extism_context_reset, [:pointer], :void
263
- attach_function :extism_version, [], :string
264
- attach_function :extism_plugin_cancel_handle, [:pointer, :int32], :pointer
265
- attach_function :extism_plugin_cancel, [:pointer], :bool
266
- end
37
+ Memory = Struct.new(:offset, :len)
267
38
  end
data/sig/extism.rbs CHANGED
@@ -1,4 +1,4 @@
1
- module Extism
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end
1
+ module Extism
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
Binary file
data/wasm/reflect.wasm ADDED
Binary file
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extism
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.0.pre.rc.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - zach
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-19 00:00:00.000000000 Z
11
+ date: 2023-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -32,15 +32,24 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
34
  - ".yardopts"
35
- - GETTING_STARTED.md
36
35
  - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE
37
38
  - Makefile
39
+ - README.md
38
40
  - Rakefile
39
41
  - example.rb
40
42
  - lib/extism.rb
43
+ - lib/extism/current_plugin.rb
44
+ - lib/extism/host_environment.rb
45
+ - lib/extism/libextism.rb
46
+ - lib/extism/plugin.rb
41
47
  - lib/extism/version.rb
48
+ - lib/extism/wasm.rb
42
49
  - sig/extism.rbs
43
- - user_code.wasm
50
+ - wasm/count_vowels.wasm
51
+ - wasm/reflect.wasm
52
+ - wasm/store_credit.wasm
44
53
  homepage: https://github.com/extism/extism
45
54
  licenses:
46
55
  - MIT
@@ -59,9 +68,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
68
  version: 2.6.0
60
69
  required_rubygems_version: !ruby/object:Gem::Requirement
61
70
  requirements:
62
- - - ">="
71
+ - - ">"
63
72
  - !ruby/object:Gem::Version
64
- version: '0'
73
+ version: 1.3.1
65
74
  requirements: []
66
75
  rubygems_version: 3.4.10
67
76
  signing_key:
data/GETTING_STARTED.md DELETED
@@ -1,35 +0,0 @@
1
- # Extism
2
-
3
- ## Getting Started
4
-
5
- ### Example
6
-
7
- ```ruby
8
- require "extism"
9
- require "json"
10
-
11
- Extism.with_context do |ctx|
12
- manifest = {
13
- :wasm => [{ :path => "../wasm/code.wasm" }],
14
- }
15
- plugin = ctx.plugin(manifest)
16
- res = JSON.parse(plugin.call("count_vowels", "this is a test"))
17
- puts res["count"] # => 4
18
- end
19
- ```
20
-
21
- ### API
22
-
23
- There are two primary classes you need to understand:
24
-
25
- * [Context](Extism/Context.html)
26
- * [Plugin](Extism/Plugin.html)
27
-
28
- #### Context
29
-
30
- The [Context](Extism/Context.html) can be thought of as a session. You need a context to interact with the Extism runtime. The context holds your plugins and when you free the context, it frees your plugins. We recommend using the [Extism.with_context](Extism.html#with_context-class_method) method to ensure that your plugins are cleaned up. But if you need a long lived context for any reason, you can use the constructor [Extism::Context.new](Extism/Context.html#initialize-instance_method).
31
-
32
- #### Plugin
33
-
34
- The [Plugin](Extism/Plugin.html) represents an instance of your WASM program from the given manifest.
35
- The key method to know here is [Extism::Plugin#call](Extism/Plugin.html#call-instance_method) which takes a function name to invoke and some input data, and returns the results from the plugin.
data/user_code.wasm DELETED
Binary file