kobako 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +16 -0
- data/Cargo.lock +3 -3
- data/README.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +7 -0
- data/crates/kobako-runtime/Cargo.toml +1 -1
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-wasmtime/CHANGELOG.md +7 -0
- data/crates/kobako-wasmtime/Cargo.toml +2 -2
- data/crates/kobako-wasmtime/README.md +1 -1
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/lib/kobako/catalog/extensions.rb +114 -0
- data/lib/kobako/catalog/services.rb +12 -0
- data/lib/kobako/catalog.rb +6 -4
- data/lib/kobako/extension.rb +47 -0
- data/lib/kobako/sandbox.rb +27 -14
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +25 -0
- data/sig/kobako/catalog/services.rbs +2 -0
- data/sig/kobako/extension.rbs +20 -0
- data/sig/kobako/sandbox.rbs +2 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8298cb68a22a8100ea0ab330902a1d772049e990a35aa154b9233429f5c43b22
|
|
4
|
+
data.tar.gz: 9a1ae245d152dab877fc3104c157eab78a38fc15cbff3e94baf187eb955d4e35
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1aabea83fa046f0e5f9cc6ae3600e0f69cd045d38d3ecdcd91c6e71cbc19d29946ecd200ca44cc7941af329300d0f20a5733585a341e810ef19522fb083b4513
|
|
7
|
+
data.tar.gz: 23e5eccfa72eca1b6c39144c7d552a0eaf1a8467342fae504799744e8b4fee4036ad1d27a3e2311f0929cb300b74e04183ec9c8e3499043257860e9779c181ce
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.16.0","wasm/kobako-core":"0.10.0","wasm/kobako-mruby":"0.10.0","wasm/kobako-io":"0.10.0","wasm/kobako-json":"0.10.0","wasm/kobako-regexp":"0.10.0","wasm/kobako-baker":"0.10.0","crates/kobako-codec":"0.10.0","crates/kobako-runtime":"0.10.0","crates/kobako-wasmtime":"0.10.0","crates/kobako":"0.10.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.16.0](https://github.com/elct9620/kobako/compare/v0.15.0...v0.16.0) (2026-07-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **sandbox:** add the Extension install mechanism ([ad8e4da](https://github.com/elct9620/kobako/commit/ad8e4da703e4ceee85959663d77bb1e26c26791a))
|
|
9
|
+
* **sdk:** add the Extension install mechanism to the Rust host SDK ([4043f76](https://github.com/elct9620/kobako/commit/4043f764b7038619a30c16542ed38d566e4a72a9))
|
|
10
|
+
* **tasks:** break each module down by language in stats:<slug> ([b496e3c](https://github.com/elct9620/kobako/commit/b496e3cb23150f8f073cc1b972978cfbebfbb1e6))
|
|
11
|
+
* **tasks:** report code sizes per publishable module in stats:all ([f554930](https://github.com/elct9620/kobako/commit/f554930e669a82bed0c7305c5cbbab96c3ae79da))
|
|
12
|
+
* **tasks:** split impl vs inline test per module in stats:all ([cefa38d](https://github.com/elct9620/kobako/commit/cefa38d96ff79509b947269ea5c361ead0610bfa))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **tasks:** count Rust inline tests in the stats code-to-test ratio ([134c970](https://github.com/elct9620/kobako/commit/134c9704a5a9bb6fb01c1aa7968f907d1436e5f2))
|
|
18
|
+
|
|
3
19
|
## [0.15.0](https://github.com/elct9620/kobako/compare/v0.14.0...v0.15.0) (2026-07-11)
|
|
4
20
|
|
|
5
21
|
|
data/Cargo.lock
CHANGED
|
@@ -929,7 +929,7 @@ dependencies = [
|
|
|
929
929
|
|
|
930
930
|
[[package]]
|
|
931
931
|
name = "kobako"
|
|
932
|
-
version = "0.
|
|
932
|
+
version = "0.16.0"
|
|
933
933
|
dependencies = [
|
|
934
934
|
"kobako-runtime",
|
|
935
935
|
"kobako-wasmtime",
|
|
@@ -938,11 +938,11 @@ dependencies = [
|
|
|
938
938
|
|
|
939
939
|
[[package]]
|
|
940
940
|
name = "kobako-runtime"
|
|
941
|
-
version = "0.
|
|
941
|
+
version = "0.10.0"
|
|
942
942
|
|
|
943
943
|
[[package]]
|
|
944
944
|
name = "kobako-wasmtime"
|
|
945
|
-
version = "0.
|
|
945
|
+
version = "0.10.0"
|
|
946
946
|
dependencies = [
|
|
947
947
|
"kobako-runtime",
|
|
948
948
|
"libc",
|
data/README.md
CHANGED
|
@@ -69,7 +69,7 @@ The host embeds the sandbox and owns the SPEC wire codec. Choose by your host la
|
|
|
69
69
|
| Frontend | Package | Add it | Best for |
|
|
70
70
|
|----------|---------|--------|----------|
|
|
71
71
|
| Ruby gem | `kobako` (RubyGems) | `gem install kobako` | A Ruby host — Services, Handles, snippets, and pooling out of the box |
|
|
72
|
-
| Rust SDK | `kobako` (crates.io) | `kobako = "0.
|
|
72
|
+
| Rust SDK | `kobako` (crates.io) | `kobako = "0.9"` | A Rust host — the same behavior contract behind an idiomatic Rust API |
|
|
73
73
|
| Low-level crates | `kobako-wasmtime` + `kobako-runtime` + `kobako-codec` | Cargo deps | A custom host, or driving the wire directly in another language |
|
|
74
74
|
|
|
75
75
|
The Rust crates are documented on [crates.io](https://crates.io/crates/kobako); the Ruby gem is this README. Two runnable Rust hosts show the choice: [`plugin-rs`](examples/plugin-rs) builds on the SDK, and [`wire-rs`](examples/wire-rs) assembles a host by hand on the low-level crates.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.9.0...kobako-runtime-v0.10.0) (2026-07-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **kobako-runtime:** Synchronize kobako crates versions
|
|
9
|
+
|
|
3
10
|
## [0.9.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.8.0...kobako-runtime-v0.9.0) (2026-07-11)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.9.0...kobako-wasmtime-v0.10.0) (2026-07-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **kobako-wasmtime:** Synchronize kobako crates versions
|
|
9
|
+
|
|
3
10
|
## [0.9.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.8.0...kobako-wasmtime-v0.9.0) (2026-07-11)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
[package]
|
|
17
17
|
name = "kobako-wasmtime"
|
|
18
|
-
version = "0.
|
|
18
|
+
version = "0.10.0"
|
|
19
19
|
edition = "2021"
|
|
20
20
|
description = "wasmtime implementation of the kobako host runtime contract."
|
|
21
21
|
license = "Apache-2.0"
|
|
@@ -28,7 +28,7 @@ categories = ["wasm", "virtualization"]
|
|
|
28
28
|
# The engine-neutral contract this crate implements. The version pin
|
|
29
29
|
# rides the linked release group; the path keeps in-tree builds (and
|
|
30
30
|
# the Ruby gem, which ships both crates) resolving locally.
|
|
31
|
-
kobako-runtime = { version = "0.
|
|
31
|
+
kobako-runtime = { version = "0.10.0", path = "../kobako-runtime" }
|
|
32
32
|
# wasmtime — host-side embedder for kobako.wasm. We disable default-features
|
|
33
33
|
# and opt back in only what kobako needs: a Cranelift-backed runtime that can
|
|
34
34
|
# compile a pre-built wasm32-wasip1 module on the host triple, plus the `wat`
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/ext/kobako/Cargo.toml
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Catalog
|
|
5
|
+
# Kobako::Catalog::Extensions — per-Sandbox registry of installed
|
|
6
|
+
# Extensions. Composes each Extension onto the sibling registries at
|
|
7
|
+
# install time (its +source+ into +Catalog::Snippets+, its +backend+
|
|
8
|
+
# path into +Catalog::Services+), asserts declared dependencies are
|
|
9
|
+
# present when the Sandbox seals, and resolves each callable-backed
|
|
10
|
+
# path to a fresh object at the start of every invocation.
|
|
11
|
+
#
|
|
12
|
+
# Sealing and the reject-after-install guard are governed by the owning
|
|
13
|
+
# Sandbox through +Catalog::Services#sealed?+, the shared seal signal;
|
|
14
|
+
# this registry only records entries and enforces the Extension-shape
|
|
15
|
+
# and dependency rules.
|
|
16
|
+
class Extensions
|
|
17
|
+
def initialize
|
|
18
|
+
@entries = [] # : Array[untyped]
|
|
19
|
+
@asserted = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Install +extension+: validate its shape, register its +source+ as a
|
|
23
|
+
# preloaded snippet, and — when it carries a +backend+ — reserve the
|
|
24
|
+
# backend path in +services+. A callable provider reserves the path
|
|
25
|
+
# with a placeholder the per-invocation refresh replaces; a fixed
|
|
26
|
+
# provider binds its object directly. Raises +ArgumentError+ on a
|
|
27
|
+
# malformed Extension; a malformed +name+ or +backend.path+ surfaces
|
|
28
|
+
# through the +snippets+ / +services+ registration it routes to.
|
|
29
|
+
def install(extension, snippets:, services:)
|
|
30
|
+
validate!(extension)
|
|
31
|
+
snippets.register(code: extension.source, name: extension.name)
|
|
32
|
+
backend = extension.backend
|
|
33
|
+
services.bind(backend.path, initial_object(backend.provider)) if backend
|
|
34
|
+
@entries << extension
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Assert every installed Extension's +depends_on+ names a fellow
|
|
39
|
+
# installed Extension. Runs once, when the Sandbox first seals; an
|
|
40
|
+
# unmet dependency raises +ArgumentError+ naming the gap, before the
|
|
41
|
+
# guest runs. Idempotent across later invocations.
|
|
42
|
+
def seal!
|
|
43
|
+
return self if @asserted
|
|
44
|
+
|
|
45
|
+
@asserted = true
|
|
46
|
+
assert_dependencies!
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Resolve each callable-backed path to this invocation's object and
|
|
51
|
+
# refresh it behind its already-sealed path in +services+. Distinct
|
|
52
|
+
# providers yield distinct objects; one provider shared by several
|
|
53
|
+
# Extensions is invoked once and its result shared, so provider
|
|
54
|
+
# identity is resource identity. Fixed providers are left untouched —
|
|
55
|
+
# they stay the object bound at install.
|
|
56
|
+
def refresh_backends!(services)
|
|
57
|
+
resolved = {} # : Hash[untyped, untyped]
|
|
58
|
+
resolved.compare_by_identity
|
|
59
|
+
@entries.each { |extension| refresh_backend(extension, services, resolved) }
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# Resolve one Extension's callable-backed path against the shared
|
|
66
|
+
# per-invocation +resolved+ cache (keyed by provider identity) and
|
|
67
|
+
# refresh it in +services+. A fixed provider is skipped — its object
|
|
68
|
+
# stays as bound at install.
|
|
69
|
+
def refresh_backend(extension, services, resolved)
|
|
70
|
+
backend = extension.backend
|
|
71
|
+
return unless backend
|
|
72
|
+
|
|
73
|
+
provider = backend.provider
|
|
74
|
+
return unless callable?(provider)
|
|
75
|
+
|
|
76
|
+
object = resolved.fetch(provider) { resolved[provider] = provider.call }
|
|
77
|
+
services.refresh(backend.path, object)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Enforce the Extension-shape checks +#preload+ / +#bind+ do not: a
|
|
81
|
+
# mandatory String +source+ (the install/bind boundary) and a
|
|
82
|
+
# +backend+ that, when present, exposes +path+ and +provider+.
|
|
83
|
+
def validate!(extension)
|
|
84
|
+
source = extension.source
|
|
85
|
+
raise ArgumentError, "Extension #source must be a String, got #{source.class}" unless source.is_a?(String)
|
|
86
|
+
|
|
87
|
+
backend = extension.backend
|
|
88
|
+
return if backend.nil?
|
|
89
|
+
return if backend.respond_to?(:path) && backend.respond_to?(:provider)
|
|
90
|
+
|
|
91
|
+
raise ArgumentError, "Extension #backend must expose #path and #provider"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def assert_dependencies!
|
|
95
|
+
names = @entries.map(&:name)
|
|
96
|
+
@entries.each do |extension|
|
|
97
|
+
(extension.depends_on || []).each do |dependency|
|
|
98
|
+
next if names.include?(dependency)
|
|
99
|
+
|
|
100
|
+
raise ArgumentError,
|
|
101
|
+
"Extension #{extension.name.inspect} depends on #{dependency.inspect}, which is not installed"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def callable?(provider) = provider.respond_to?(:call)
|
|
107
|
+
|
|
108
|
+
# A callable provider reserves its path with a placeholder that the
|
|
109
|
+
# per-invocation refresh replaces before any dispatch; a fixed
|
|
110
|
+
# provider is the bound object itself.
|
|
111
|
+
def initial_object(provider) = callable?(provider) ? nil : provider
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -65,6 +65,18 @@ module Kobako
|
|
|
65
65
|
@bindings[target_str]
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Replace the object bound at an already-registered +path+ without the
|
|
69
|
+
# collision or seal checks +#bind+ applies. +Catalog::Extensions+ calls
|
|
70
|
+
# this during +begin_invocation!+ to swap a callable backend provider's
|
|
71
|
+
# per-invocation result behind a path whose Frame 1 membership was
|
|
72
|
+
# fixed at the seal. A path that was never registered is left untouched,
|
|
73
|
+
# so the sealed key set can never grow here.
|
|
74
|
+
def refresh(path, object)
|
|
75
|
+
key = path.to_s
|
|
76
|
+
@bindings[key] = object if @bindings.key?(key)
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
68
80
|
# Encode the preamble as msgpack bytes for stdin Frame 1 delivery —
|
|
69
81
|
# a flat array of the bound constant paths, in bind order:
|
|
70
82
|
# +["MyService::KV", "File"]+. Routes through Kobako::Codec::Encoder
|
data/lib/kobako/catalog.rb
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
require_relative "catalog/handles"
|
|
4
4
|
require_relative "catalog/services"
|
|
5
5
|
require_relative "catalog/snippets"
|
|
6
|
+
require_relative "catalog/extensions"
|
|
6
7
|
|
|
7
8
|
module Kobako
|
|
8
9
|
# Kobako::Catalog — Sandbox-level configuration and per-invocation
|
|
9
|
-
# allocation tables. Houses the
|
|
10
|
-
#
|
|
11
|
-
# +Catalog::Snippets+ (preloaded source / bytecode entries),
|
|
12
|
-
# +Catalog::
|
|
10
|
+
# allocation tables. Houses the host-side registries the Sandbox owns:
|
|
11
|
+
# +Catalog::Services+ (path→Service binding registry),
|
|
12
|
+
# +Catalog::Snippets+ (preloaded source / bytecode entries),
|
|
13
|
+
# +Catalog::Extensions+ (installed Extensions, composed onto Services /
|
|
14
|
+
# Snippets), and +Catalog::Handles+ (per-invocation Handle ID allocator).
|
|
13
15
|
#
|
|
14
16
|
# See {SPEC.md Refinement → Internal Concepts}[link:../../SPEC.md] for
|
|
15
17
|
# how Catalog fits alongside Transport and Runtime.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
# Kobako::Extension — a guest idiom paired with an optional host backend,
|
|
5
|
+
# installed on a Sandbox via +Sandbox#install+. It composes the existing
|
|
6
|
+
# +#preload+ (the guest +source+) and +#bind+ (the +backend+) verbs into
|
|
7
|
+
# one setup unit, so guest code sees a native-style constant whose pure
|
|
8
|
+
# methods run in-guest and whose privileged methods dispatch to the
|
|
9
|
+
# backend.
|
|
10
|
+
#
|
|
11
|
+
# The four readers form the contract +#install+ duck-types on:
|
|
12
|
+
#
|
|
13
|
+
# * +name+ — a Symbol matching +/\A[A-Z]\w*\z/+, the preloaded snippet's
|
|
14
|
+
# canonical backtrace name and the +depends_on+ match key. Independent
|
|
15
|
+
# of any bound path.
|
|
16
|
+
# * +source+ — the mruby idiom as a String; always present, since an
|
|
17
|
+
# Extension always carries a guest idiom. A host object with no idiom
|
|
18
|
+
# is bound with +#bind+ directly.
|
|
19
|
+
# * +backend+ — an +Extension::Backend+ or +nil+ for a pure-guest
|
|
20
|
+
# Extension.
|
|
21
|
+
# * +depends_on+ — Symbol names of Extensions that must also be
|
|
22
|
+
# installed, checked for presence at the first invocation.
|
|
23
|
+
#
|
|
24
|
+
# +Kobako::Extension+ is the bundled value type; any object exposing the
|
|
25
|
+
# four readers is equally valid, so a Host App or gem may supply its own.
|
|
26
|
+
class Extension < Data.define(:name, :source, :backend, :depends_on)
|
|
27
|
+
# Kobako::Extension::Backend — the host attachment of an Extension,
|
|
28
|
+
# pairing +path+ (the constant path the backend binds at, single-segment
|
|
29
|
+
# +"File"+ or nested +"MyApp::Store"+, spelling the guest constant the
|
|
30
|
+
# idiom routes to) with +provider+ (the source of the bound object).
|
|
31
|
+
#
|
|
32
|
+
# A +provider+ that is not itself callable is the bound object, resolved
|
|
33
|
+
# once for the Sandbox's life; a callable provider is invoked once per
|
|
34
|
+
# invocation to yield that invocation's object, so a fresh object backs
|
|
35
|
+
# the path every invocation. Callability is the sole discriminator — a
|
|
36
|
+
# fixed backend that is itself callable is supplied through a
|
|
37
|
+
# non-callable wrapper.
|
|
38
|
+
class Backend < Data.define(:path, :provider)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# +backend+ and +depends_on+ default to absent so the common
|
|
42
|
+
# pure-idiom and single-backend shapes stay terse.
|
|
43
|
+
def initialize(name:, source:, backend: nil, depends_on: [])
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/kobako/sandbox.rb
CHANGED
|
@@ -48,29 +48,21 @@ module Kobako
|
|
|
48
48
|
# so use +#stdout_truncated?+ to observe overflow. Populated on every
|
|
49
49
|
# outcome — including a rescued +TrapError+, after which it holds the
|
|
50
50
|
# bytes written before the trap fired — mirroring +#usage+.
|
|
51
|
-
def stdout
|
|
52
|
-
@stdout_capture.bytes
|
|
53
|
-
end
|
|
51
|
+
def stdout = @stdout_capture.bytes
|
|
54
52
|
|
|
55
53
|
# Returns the bytes the guest wrote to stderr during the most recent
|
|
56
54
|
# invocation as a UTF-8 String, clipped at +stderr_limit+. Empty before
|
|
57
55
|
# any invocation. Mirror of +#stdout+.
|
|
58
|
-
def stderr
|
|
59
|
-
@stderr_capture.bytes
|
|
60
|
-
end
|
|
56
|
+
def stderr = @stderr_capture.bytes
|
|
61
57
|
|
|
62
58
|
# Returns +true+ iff stdout capture during the most recent invocation
|
|
63
59
|
# exceeded +stdout_limit+. Resets to +false+ at the start of the next
|
|
64
60
|
# invocation.
|
|
65
|
-
def stdout_truncated?
|
|
66
|
-
@stdout_capture.truncated?
|
|
67
|
-
end
|
|
61
|
+
def stdout_truncated? = @stdout_capture.truncated?
|
|
68
62
|
|
|
69
63
|
# Returns +true+ iff stderr capture during the most recent invocation
|
|
70
64
|
# exceeded +stderr_limit+. Mirror of +#stdout_truncated?+.
|
|
71
|
-
def stderr_truncated?
|
|
72
|
-
@stderr_capture.truncated?
|
|
73
|
-
end
|
|
65
|
+
def stderr_truncated? = @stderr_capture.truncated?
|
|
74
66
|
|
|
75
67
|
# Returns the +Kobako::Usage+ value object for the most recent
|
|
76
68
|
# invocation. Carries +wall_time+ (Float seconds the guest export call spent
|
|
@@ -102,6 +94,7 @@ module Kobako
|
|
|
102
94
|
@handler = Catalog::Handles.new
|
|
103
95
|
@services = Kobako::Catalog::Services.new(handler: @handler)
|
|
104
96
|
@snippets = Catalog::Snippets.new
|
|
97
|
+
@extensions = Catalog::Extensions.new
|
|
105
98
|
@runtime = build_runtime!
|
|
106
99
|
install_dispatch_proc!
|
|
107
100
|
reset_invocation_state!
|
|
@@ -121,6 +114,22 @@ module Kobako
|
|
|
121
114
|
self
|
|
122
115
|
end
|
|
123
116
|
|
|
117
|
+
# Install one or more Extensions — each a guest idiom (+source+) paired
|
|
118
|
+
# with an optional host +backend+, composed onto the Sandbox through
|
|
119
|
+
# +#preload+ and +#bind+. An Extension is any object exposing
|
|
120
|
+
# +name+ / +source+ / +backend+ / +depends_on+; +Kobako::Extension+ is
|
|
121
|
+
# the bundled value type. Returns +self+.
|
|
122
|
+
#
|
|
123
|
+
# Raises +ArgumentError+ for a malformed Extension, a call after the
|
|
124
|
+
# first invocation seals registration, or — at that first invocation —
|
|
125
|
+
# an unmet +depends_on+.
|
|
126
|
+
def install(*extensions)
|
|
127
|
+
raise ArgumentError, "cannot install after first Sandbox invocation" if @services.sealed?
|
|
128
|
+
|
|
129
|
+
extensions.each { |extension| @extensions.install(extension, snippets: @snippets, services: @services) }
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
124
133
|
# Register a snippet on this Sandbox in one of two forms:
|
|
125
134
|
#
|
|
126
135
|
# * +preload(code: source, name: Name)+ — +source+ is mruby source
|
|
@@ -241,14 +250,18 @@ module Kobako
|
|
|
241
250
|
end
|
|
242
251
|
end
|
|
243
252
|
|
|
244
|
-
# Per-invocation prologue. Seals the Service / snippet
|
|
245
|
-
# first call (idempotent
|
|
253
|
+
# Per-invocation prologue. Seals the Service / snippet / Extension
|
|
254
|
+
# registries on first call (idempotent — asserting Extension
|
|
255
|
+
# dependencies then), refreshes each callable Extension backend to this
|
|
256
|
+
# invocation's fresh object, and zeros the per-invocation capability
|
|
246
257
|
# state — capture buffers, truncation predicates, and the
|
|
247
258
|
# +Catalog::Handles+ counter — before the guest runs. The
|
|
248
259
|
# +Catalog::Handles+ itself is held as +@handler+ and never exposed
|
|
249
260
|
# beyond this class — it is not part of the Host App's surface.
|
|
250
261
|
def begin_invocation!
|
|
251
262
|
@services.seal!
|
|
263
|
+
@extensions.seal!
|
|
264
|
+
@extensions.refresh_backends!(@services)
|
|
252
265
|
@handler.reset!
|
|
253
266
|
reset_invocation_state!
|
|
254
267
|
end
|
data/lib/kobako/version.rb
CHANGED
data/lib/kobako.rb
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
module Catalog
|
|
3
|
+
class Extensions
|
|
4
|
+
def initialize: () -> void
|
|
5
|
+
|
|
6
|
+
def install: (untyped extension, snippets: Kobako::Catalog::Snippets, services: Kobako::Catalog::Services) -> self
|
|
7
|
+
|
|
8
|
+
def seal!: () -> self
|
|
9
|
+
|
|
10
|
+
def refresh_backends!: (Kobako::Catalog::Services services) -> self
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def refresh_backend: (untyped extension, Kobako::Catalog::Services services, Hash[untyped, untyped] resolved) -> void
|
|
15
|
+
|
|
16
|
+
def validate!: (untyped extension) -> void
|
|
17
|
+
|
|
18
|
+
def assert_dependencies!: () -> void
|
|
19
|
+
|
|
20
|
+
def callable?: (untyped provider) -> bool
|
|
21
|
+
|
|
22
|
+
def initial_object: (untyped provider) -> untyped
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
class Extension < Data
|
|
3
|
+
attr_reader name: Symbol
|
|
4
|
+
attr_reader source: String
|
|
5
|
+
attr_reader backend: Kobako::Extension::Backend?
|
|
6
|
+
attr_reader depends_on: Array[Symbol]
|
|
7
|
+
|
|
8
|
+
def self.new: (name: Symbol, source: String, ?backend: Kobako::Extension::Backend?, ?depends_on: Array[Symbol]) -> Kobako::Extension
|
|
9
|
+
|
|
10
|
+
def initialize: (name: Symbol, source: String, ?backend: Kobako::Extension::Backend?, ?depends_on: Array[Symbol]) -> void
|
|
11
|
+
|
|
12
|
+
class Backend < Data
|
|
13
|
+
attr_reader path: String
|
|
14
|
+
attr_reader provider: untyped
|
|
15
|
+
|
|
16
|
+
def self.new: (path: String, provider: untyped) -> Kobako::Extension::Backend
|
|
17
|
+
| (String path, untyped provider) -> Kobako::Extension::Backend
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/sig/kobako/sandbox.rbs
CHANGED
|
@@ -31,6 +31,8 @@ module Kobako
|
|
|
31
31
|
|
|
32
32
|
def bind: (Symbol | String path, untyped object) -> Kobako::Sandbox
|
|
33
33
|
|
|
34
|
+
def install: (*untyped extensions) -> Kobako::Sandbox
|
|
35
|
+
|
|
34
36
|
def preload: (code: String, name: Symbol | String) -> Kobako::Sandbox
|
|
35
37
|
| (binary: String) -> Kobako::Sandbox
|
|
36
38
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kobako
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aotokitsuruya
|
|
@@ -91,6 +91,7 @@ files:
|
|
|
91
91
|
- lib/kobako.rb
|
|
92
92
|
- lib/kobako/capture.rb
|
|
93
93
|
- lib/kobako/catalog.rb
|
|
94
|
+
- lib/kobako/catalog/extensions.rb
|
|
94
95
|
- lib/kobako/catalog/handles.rb
|
|
95
96
|
- lib/kobako/catalog/services.rb
|
|
96
97
|
- lib/kobako/catalog/snippets.rb
|
|
@@ -103,6 +104,7 @@ files:
|
|
|
103
104
|
- lib/kobako/codec/state.rb
|
|
104
105
|
- lib/kobako/codec/utils.rb
|
|
105
106
|
- lib/kobako/errors.rb
|
|
107
|
+
- lib/kobako/extension.rb
|
|
106
108
|
- lib/kobako/fault.rb
|
|
107
109
|
- lib/kobako/handle.rb
|
|
108
110
|
- lib/kobako/outcome.rb
|
|
@@ -129,6 +131,7 @@ files:
|
|
|
129
131
|
- sig/kobako.rbs
|
|
130
132
|
- sig/kobako/capture.rbs
|
|
131
133
|
- sig/kobako/catalog.rbs
|
|
134
|
+
- sig/kobako/catalog/extensions.rbs
|
|
132
135
|
- sig/kobako/catalog/handles.rbs
|
|
133
136
|
- sig/kobako/catalog/services.rbs
|
|
134
137
|
- sig/kobako/catalog/snippets.rbs
|
|
@@ -141,6 +144,7 @@ files:
|
|
|
141
144
|
- sig/kobako/codec/state.rbs
|
|
142
145
|
- sig/kobako/codec/utils.rbs
|
|
143
146
|
- sig/kobako/errors.rbs
|
|
147
|
+
- sig/kobako/extension.rbs
|
|
144
148
|
- sig/kobako/fault.rbs
|
|
145
149
|
- sig/kobako/handle.rbs
|
|
146
150
|
- sig/kobako/outcome.rbs
|