plugin

package
v0.24.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// FetchNotFoundCode is the JSON-RPC error code a fetcher returns for a
	// missing source or path, mapped back to ErrFetchNotFound on the host.
	FetchNotFoundCode = -32001
	// FetchDeniedCode reports an access the fetcher refused: credentials,
	// permissions. It is permanent, and the host never retries it.
	FetchDeniedCode = -32002
	// FetchUnavailableCode reports a backend that could not answer right now
	// (network blip, upstream 503). The host retries these.
	FetchUnavailableCode = -32003
)
View Source
const (
	FetchRetryAttempts = 3
	FetchRetryDelay    = 150 * time.Millisecond
)

Fetch retries: fetch operations are idempotent reads, so a transport hiccup or an unavailable backend is retried a bounded number of times with a short linear backoff. Permanent errors (not found, denied) are never retried.

View Source
const (
	// ProtocolVersion is the JSON-RPC plugin protocol version supported by this host.
	ProtocolVersion = "1.0"
	// NamespacePrefix is where plugins declaring a bare name register: a
	// plugin declaring "hello" becomes plugin.hello, keeping third-party
	// code visibly namespaced and unable to collide with single-word
	// built-in libraries. A name containing a dot is used verbatim — its
	// author owns the namespace ("paul.hello", or "scriptling.sqlite" for
	// first-party plugins whose imports match compiled-in builds) — and
	// registration refuses names already taken by another library.
	NamespacePrefix = "plugin."
	// PluginPeerEnv is the environment variable set (to the host version) on
	// every executable spawned as a plugin peer. Multi-role executables check
	// it to divert a bare invocation into plugin mode without a subcommand.
	PluginPeerEnv = "SCRIPTLING_PLUGIN_PEER"
)
View Source
const ControlLibraryName = "scriptling.plugin"
View Source
const DefaultFetchTimeout = 30 * time.Second

DefaultFetchTimeout bounds fetch.read / fetch.glob calls when the caller provides no deadline of its own.

View Source
const DefaultHandshakeTimeout = 15 * time.Second

DefaultHandshakeTimeout caps how long handshake() will wait for the plugin protocol handshake to complete. It bounds detection of broken or unresponsive plugins while still tolerating slow subprocess startup under load (cold container starts, network filesystems, parallel test execution). The caller's context deadline always takes precedence if it is shorter.

View Source
const DefaultMaxParallelPluginLoads = 5

DefaultMaxParallelPluginLoads caps how many plugin processes are started concurrently. Process spawn plus handshake dominates load time, so batches start in parallel, but an unbounded burst of subprocesses would hurt constrained hosts more than sequential loading helps them.

View Source
const DefaultReleaseTimeout = 2 * time.Second

DefaultReleaseTimeout is used by Release and GC finalizers when no caller context is available. Use ReleaseWithContext for request-scoped cleanup.

Variables

View Source
var (
	ErrFetchNotFound    = errors.New("fetch source not found")
	ErrFetchDenied      = errors.New("fetch access denied")
	ErrFetchUnavailable = errors.New("fetch backend unavailable")
)

Fetch error sentinels. Fetcher implementations wrap them (fmt.Errorf("%w: ...", ErrFetchNotFound)) and the server transports each as its error code above; the client maps the codes back, so hosts can tell a plain miss from a refusal from a flaky backend.

View Source
var ErrManagerClosed = errors.New("plugin manager closed")

ErrManagerClosed is returned by Manager load operations after shutdown has begun. A closed Manager is terminal and cannot be used to load more plugins.

Functions

func CompiledInNames added in v0.23.0

func CompiledInNames() []string

CompiledInNames returns the sorted names of all registered compiled-in plugins. Hosts use it to keep a discovered external plugin from shadowing (or being shadowed by) a compiled-in one.

func Logger added in v0.11.0

func Logger(ctx context.Context) logger.Logger

Logger returns a call-scoped proxy to the manager's host logger. It uses ctx to find the active plugin call runtime, so plugin code should request it from functions, constructors, methods, or property accessors and should not store it globally. If ctx is not attached to an active plugin call, Logger returns a no-op logger.

func MatchGlob added in v0.23.0

func MatchGlob(pattern, name string) bool

MatchGlob reports whether name, a slash path relative to a source root, matches pattern in the fetch glob language. Fetcher implementations can serve Glob by matching their known paths with this helper instead of reimplementing the semantics.

func NewControlLibrary

func NewControlLibrary(manager *Manager, registrar Registrar, scriptRegistrar ScriptLibraryRegistrar, unregistrar LibraryUnregistrar) *object.Library

func NormalizeLibraryName

func NormalizeLibraryName(name string) string

NormalizeLibraryName returns the library name a script imports. A bare name registers under the plugin namespace ("hello" -> "plugin.hello"); a dotted name is the author's namespace and is used verbatim ("paul.hello", "scriptling.sqlite").

func RegisterClientLibrary added in v0.15.0

func RegisterClientLibrary(registrar Registrar, client *Client)

RegisterClientLibrary registers the proxy library for a single already-handshaked client. Useful when you have a Client obtained via LoadClientFromIO or similar and want to expose it without a full Manager.

func RegisterCompiledIn added in v0.23.0

func RegisterCompiledIn(name, description string, build CompiledInBuild)

RegisterCompiledIn records a compiled-in plugin. It is called from init() in build-tag-guarded files (e.g. //go:build plugin_sqlite), so a binary only carries the plugins its build flags selected. Registering the same name twice panics: it means two tagged files disagree.

func RegisterLibraries

func RegisterLibraries(registrar Registrar, manager *Manager, policy ...*Policy)

func Release

func Release(obj object.Object) error

func ReleaseWithContext

func ReleaseWithContext(ctx context.Context, obj object.Object) error

ReleaseWithContext explicitly releases a remote plugin object using ctx.

Types

type Callback

type Callback interface {
	Call(ctx context.Context, args ...any) (Value, error)
}

Callback is a host callback passed into a plugin call. It is valid only until the outer plugin function, constructor, or method returns.

type CallbackRef

type CallbackRef struct {
	ID string `json:"id"`
}

CallbackRef identifies a callback valid during one outer plugin call.

type ClassSchema

type ClassSchema struct {
	Name        string           `json:"name"`
	Description string           `json:"description,omitempty"`
	Constructor FunctionSchema   `json:"constructor,omitempty"`
	Methods     []FunctionSchema `json:"methods,omitempty"`
	Properties  []PropertySchema `json:"properties,omitempty"`
	Source      string           `json:"source,omitempty"`
}

ClassSchema describes a plugin class and its methods.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a connection to a loaded plugin. Over stdio it is a bidirectional JSON-RPC peer (built on jsonrpc.Peer) — outbound calls to the plugin and inbound host callbacks (callback.call, host.log) share one stream. Over HTTP it is a unidirectional JSON-RPC client (built on jsonrpc.HTTPTransport); callbacks are not available over HTTP.

func LoadClient added in v0.12.1

func LoadClient(ctx context.Context, path string, args []string) (*Client, error)

LoadClient spawns an executable and performs the plugin protocol handshake. The returned client has Metadata populated from the handshake result. args, if non-empty, are passed as command-line arguments to the executable. A standalone client carries no host security policy; managers that want one delivered should load through a Manager with SetPolicy.

func LoadClientFromIO added in v0.15.0

func LoadClientFromIO(ctx context.Context, in io.ReadCloser, out io.WriteCloser) (*Client, error)

LoadClientFromIO connects to a plugin server over an existing bidirectional stream and performs the plugin protocol handshake. Use this when the server is already running and accessible via in-process pipes (e.g. tests, embedded servers). The caller is responsible for closing in/out when done.

func SpawnClient added in v0.12.1

func SpawnClient(ctx context.Context, path string, args []string) (*Client, error)

SpawnClient spawns an executable without performing the plugin handshake. The caller is responsible for any handshake exchange via Call. args, if non-empty, are passed as command-line arguments to the executable.

func (*Client) Batch added in v0.12.1

func (c *Client) Batch(ctx context.Context, requests []batchRequest) ([]json.RawMessage, error)

Batch sends multiple raw JSON-RPC requests in one batch frame and returns results in the same order as requests. Batch does not support host callbacks.

func (*Client) Call added in v0.12.1

func (c *Client) Call(ctx context.Context, method string, params any, out any) error

Call sends a raw JSON-RPC request to the executable and unmarshals the result into out (which may be nil to ignore the result). params may be any JSON-marshalable value (struct, map, slice, scalar). It is the low-level building block for non-plugin JSON-RPC peers; plugin callers should prefer CallFunction / NewObject / CallMethod which use the plugin method names.

func (*Client) CallFunction

func (c *Client) CallFunction(ctx context.Context, name string, args []Value, kwargs map[string]Value) (Value, error)

func (*Client) CallFunctionWithCallbacks

func (c *Client) CallFunctionWithCallbacks(ctx context.Context, name string, args []Value, kwargs map[string]Value, callbacks *callbackSet) (Value, error)

func (*Client) CallMethod

func (c *Client) CallMethod(ctx context.Context, objectID, method string, args []Value, kwargs map[string]Value) (Value, error)

func (*Client) CallMethodWithCallbacks

func (c *Client) CallMethodWithCallbacks(ctx context.Context, objectID, method string, args []Value, kwargs map[string]Value, callbacks *callbackSet) (Value, error)

func (*Client) Close

func (c *Client) Close() error

Close shuts down this plugin process. The plugin.shutdown notification is best-effort: peers that do not implement the plugin protocol (e.g. raw JSON-RPC executables loaded via LoadPath(scriptling=false)) will return a method-not-found error which is intentionally ignored. Handshaken Scriptling plugins still report shutdown RPC errors. Real failures — the process not exiting, or exiting with a non-zero status — are also reported.

func (*Client) DestroyObject

func (c *Client) DestroyObject(ctx context.Context, objectID string) error

func (*Client) FetchFile added in v0.23.0

func (c *Client) FetchFile(ctx context.Context, source, path string) ([]byte, error)

FetchFile reads one file from a source. path is a slash path relative to the source; an empty path denotes a source that is itself a single file (a script). Transport failures and unavailable backends are retried a bounded number of times (fetch reads are idempotent); permanent errors are not.

func (*Client) FetchGlob added in v0.23.0

func (c *Client) FetchGlob(ctx context.Context, source, pattern string) ([]FetchEntry, error)

FetchGlob returns the entries of a source whose paths match pattern in the fetch glob language (see MatchGlob). No match is an empty result; a missing source is an error wrapping ErrFetchNotFound. Retried like FetchFile.

func (*Client) HandshakeDone added in v0.12.1

func (c *Client) HandshakeDone() bool

HandshakeDone reports whether the plugin protocol handshake was completed. call_function uses this to route automatically: handshook clients use the typed plugin transport (function.call), non-handshook clients send the method name directly as a raw JSON-RPC request.

func (*Client) Health

func (c *Client) Health() error

Health reports whether this plugin process and stdio transport are healthy.

func (*Client) Metadata

func (c *Client) Metadata() Metadata

func (*Client) NewObject

func (c *Client) NewObject(ctx context.Context, class string, args []Value, kwargs map[string]Value) (*RemoteRef, error)

func (*Client) NewObjectWithCallbacks

func (c *Client) NewObjectWithCallbacks(ctx context.Context, class string, args []Value, kwargs map[string]Value, callbacks *callbackSet) (*RemoteRef, error)

func (*Client) Path added in v0.12.1

func (c *Client) Path() string

Path returns the filesystem path of the executable this client runs.

func (*Client) Scheme added in v0.23.0

func (c *Client) Scheme() string

Scheme returns the source scheme the peer's fetcher serves, as advertised in its handshake. Empty when the plugin has no fetcher.

func (*Client) SetName added in v0.12.1

func (c *Client) SetName(name string)

SetName overrides the library name used to register this client. It is only meaningful before a client is added to a Manager and is intended for raw (non-plugin-handshake) clients whose name would otherwise be empty.

func (*Client) SupportsFetch added in v0.23.0

func (c *Client) SupportsFetch() bool

SupportsFetch reports whether this peer has a fetcher: a non-empty scheme in the handshake says so, which is the whole advertisement. Fetch calls are refused (without contacting the peer) when it does not, so plugins without fetchers keep working unchanged.

type CompiledInBuild added in v0.23.0

type CompiledInBuild func(policy *Policy) (*object.Library, string)

CompiledInBuild produces the two halves a compiled-in plugin registers: a native library and, when non-empty, the script source of the user-facing module. The native library registers under a twin name (scriptling._sqlite) and the script module under the public name (scriptling.sqlite), so scripts see exactly the namespace an external plugin of the same name would give them — and script-defined surfaces (like the ORM kit) execute host-side in both modes.

policy is the host security context for the interpreter being configured; nil means no restrictions.

type ConstantSchema

type ConstantSchema struct {
	Name  string `json:"name"`
	Value Value  `json:"value"`
}

ConstantSchema describes a plugin constant.

type FetchEntry added in v0.23.0

type FetchEntry struct {
	Name  string `json:"name"`
	IsDir bool   `json:"is_dir"`
}

FetchEntry is one match returned by Fetcher.Glob: Name is the entry's slash path relative to the source root (full path, not a bare base name).

func GlobDisk added in v0.23.0

func GlobDisk(root, pattern string) ([]FetchEntry, error)

GlobDisk is the reference Glob for a fetcher serving files from a directory: it walks root and returns every entry whose path relative to root matches pattern (directories included, with is_dir set).

Symlink defense comes built in: each symlink encountered is resolved to its real path and must stay inside root, so a link planted in the served tree cannot serve files from outside it. Directory symlinks are not followed (the walk would leave root); file symlinks that resolve inside root are served as files. Fetchers with richer backends implement Glob themselves but owe their users the same containment guarantee.

type Fetcher added in v0.23.0

type Fetcher interface {
	Read(ctx context.Context, source, path string) ([]byte, error)
	Glob(ctx context.Context, source, pattern string) ([]FetchEntry, error)
}

Fetcher serves file content for sources under a registered scheme. Read is called with the full source string and a slash path relative to it (empty for a source that denotes a single file, such as a script) and returns the file's bytes; an error wrapping ErrFetchNotFound is a miss. Data travels base64-encoded on the wire so binary assets survive intact. There is no conditional-read machinery — the host does not cache what a plugin serves, so a plugin whose backend is slow caches behind its own Read, where the freshness rules live; the host stays a dumb pipe.

Glob matches a pattern in the fetch glob language (see MatchGlob) against the source's tree and returns every match, directories included. It answers in one call what a directory-by-directory walk would need one round trip per level for, which is the point: existence is a wildcard-free pattern, a listing is "<dir>/*", a whole subtree is "<dir>/**". No match is an empty result, never an error; errors mean the fetcher could not answer. The MatchGlob and GlobDisk helpers implement the semantics for in-memory and disk-backed fetchers respectively.

type FunctionSchema

type FunctionSchema struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Source      string `json:"source,omitempty"`
}

FunctionSchema describes a plugin function or supplied wrapper source.

type LibraryChecker added in v0.23.0

type LibraryChecker interface {
	HasLibrary(name string) bool
}

LibraryChecker is implemented by registrars that can report whether a library name is already taken; the plugin loader refuses plugins whose verbatim (dotted) name collides with it.

type LibraryUnregistrar added in v0.12.1

type LibraryUnregistrar interface {
	UnregisterLibrary(name string)
	UnregisterScriptLibrary(name string)
}

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

func NewManager

func NewManager(log logger.Logger, crashHandler ...func(name string, err error)) *Manager

NewManager creates an empty plugin manager. If log is not nil, plugin log records emitted through Logger(ctx) are forwarded to it. If crashHandler is provided, it is called when a loaded plugin process exits unexpectedly.

func (*Manager) AddDir

func (m *Manager) AddDir(dir string)

AddDir adds a directory whose executable files should be loaded as plugins.

func (*Manager) Close

func (m *Manager) Close() error

Close shuts down all loaded plugin processes and clears the local client map. For scoped managers this fully releases all locally loaded plugins without touching the parent's plugins. Close is safe to call concurrently and more than once; every caller waits for the same shutdown and receives its result.

func (*Manager) Get

func (m *Manager) Get(name string) (*Client, bool)

Get returns a loaded plugin client by short or fully-qualified library name. It checks the local map first; if not found and this is a scope with a parent, it falls back to the parent (and so on up the chain). Local always wins.

func (*Manager) Health

func (m *Manager) Health() map[string]error

Health returns loaded plugins whose process or stdio transport is unhealthy.

func (*Manager) List

func (m *Manager) List() []Metadata

List returns metadata for all loaded plugins sorted by library name. If this Manager is a scope, parent plugins are included in the result. A child scope cannot load a name that an ancestor already owns, so name collisions between local and parent are not possible; the seen-map guard is kept as a safety net in case of direct manager manipulation.

func (*Manager) Load

func (m *Manager) Load(ctx context.Context) error

Load eagerly starts all executable plugins in configured plugin directories. The starts run concurrently, capped at SetMaxParallelPluginLoads, while registration stays in directory order so naming behaves exactly as sequential loading: the first executable declaring a library name wins.

func (*Manager) LoadPath added in v0.12.1

func (m *Manager) LoadPath(ctx context.Context, name, path string, scriptling bool, args []string) (*Client, error)

LoadPath starts a single executable, or connects to an http(s) JSON-RPC endpoint, and registers it under name. Executable identity is by absolute path; HTTP identity is by URL.

If scriptling is true, the plugin protocol handshake is performed and the client can be driven through CallFunction / CallMethod / call_function / call_method. If scriptling is false, the handshake is skipped and call_function sends the function name directly as the JSON-RPC method.

args, if non-empty, are passed as command-line arguments to the executable (e.g. ["--json-rpc", "./setup.py"] when spawning `scriptling` itself).

name is normalised into the plugin.* namespace (e.g. "widgets" becomes "plugin.widgets"); the returned client's Metadata().Name reflects that.

func (*Manager) LoadPlugin added in v0.23.0

func (m *Manager) LoadPlugin(ctx context.Context, path string, args []string) (*Client, error)

LoadPlugin starts a single executable plugin, performing the plugin protocol handshake, and registers it under the library name it declares — the same naming rule directory discovery uses, so a plugin behaves identically however it is loaded. args, if non-empty, are passed as command-line arguments to the executable.

Executable identity is the resolved absolute path: loading an executable that is already registered (e.g. discovered earlier via LoadPath or an explicit load ahead of a --plugin-dir scan) returns the existing client.

func (*Manager) LoadPlugins added in v0.23.0

func (m *Manager) LoadPlugins(ctx context.Context, specs []PluginSpec) error

LoadPlugins starts each executable plugin, at most SetMaxParallelPluginLoads at a time, and registers them in the order the specs are given: a library name declared by two plugins resolves to the first spec, exactly as sequential loading resolves it. The first failed start (in spec order) is returned as an error; plugins started before it are kept. Embedders use this (or Load) for capped parallel loading.

func (*Manager) LoadURL added in v0.12.1

func (m *Manager) LoadURL(ctx context.Context, name, rawURL string, scriptling, insecureSkipTLS bool, headers ...map[string]string) (*Client, error)

LoadURL connects to an HTTP(S) JSON-RPC endpoint and registers it under name. If scriptling is true, the plugin protocol handshake is performed. If insecureSkipTLS is true, HTTPS certificate verification is skipped. Optional headers are sent with every HTTP request.

func (*Manager) NewScope added in v0.12.3

func (m *Manager) NewScope(opts ...ScopeOption) *Manager

NewScope creates a child Manager that inherits the logger and shared HTTP transports from this Manager. Plugins loaded into the scope are invisible to the parent and to other scopes. When the scope is closed, only its locally loaded plugins are unloaded; the parent's plugins are unaffected.

Calling Get or List on the scope chains to the parent for fallback: the scope sees its own plugins first and parent plugins where there is no clash.

The scope does not inherit the parent's dirs or crash handler.

func (*Manager) SetCrashHandler

func (m *Manager) SetCrashHandler(handler func(name string, err error))

SetCrashHandler installs a callback for loaded plugin processes that exit unexpectedly. The handler is not called for normal manager shutdown.

func (*Manager) SetLogger added in v0.11.0

func (m *Manager) SetLogger(log logger.Logger)

SetLogger installs the host logger used for log records emitted by plugins.

func (*Manager) SetMaxParallelPluginLoads added in v0.23.0

func (m *Manager) SetMaxParallelPluginLoads(n int)

SetMaxParallelPluginLoads caps how many plugin processes are started concurrently by Load and LoadPlugins. Values below 1 mean one at a time; the default is DefaultMaxParallelPluginLoads. Registration order is the input order whatever the cap, so name collisions resolve identically to sequential loading.

func (*Manager) SetPolicy added in v0.23.0

func (m *Manager) SetPolicy(policy *Policy)

SetPolicy sets the security policy delivered to every plugin this manager handshakes with. Call it before Load/LoadPlugin/LoadURL — the policy rides the handshake, which is the first message on each connection. A nil policy (the default) tells plugins the host imposes no restrictions. Scopes created earlier still see the policy through the parent chain at handshake time.

func (*Manager) Unload added in v0.12.1

func (m *Manager) Unload(name string) error

Unload closes a client registered via LoadPath and removes it from the manager. It is intended for runtime-loaded executables; calling Unload on a plugin discovered via Load also works but the plugin will not be restarted. Returns an error if no client is registered under name (after normalisation).

func (*Manager) Warnings

func (m *Manager) Warnings() []string

Warnings returns non-fatal plugin load warnings collected by the manager.

type Metadata

type Metadata struct {
	Name         string   `json:"name"`
	Version      string   `json:"version"`
	Description  string   `json:"description"`
	Transport    string   `json:"transport,omitempty"`
	Capabilities []string `json:"capabilities,omitempty"`
	Scheme       string   `json:"scheme,omitempty"` // the source scheme this plugin's fetcher serves
	Schema       Schema   `json:"schema"`
}

Metadata describes a loaded plugin library.

type NetworkPolicy added in v0.23.0

type NetworkPolicy struct {
	RequireHTTPS    bool     `json:"require_https,omitempty"`
	AllowIPLiterals bool     `json:"allow_ip_literals,omitempty"`
	AllowLoopback   bool     `json:"allow_loopback,omitempty"`
	AllowPrivateIPs bool     `json:"allow_private_ips,omitempty"`
	AllowHosts      []string `json:"allow_hosts,omitempty"`
	DenyHosts       []string `json:"deny_hosts,omitempty"`
	AllowedCIDRs    []string `json:"allowed_cidrs,omitempty"`
	DeniedCIDRs     []string `json:"denied_cidrs,omitempty"`
}

NetworkPolicy is the wire form of the host's outbound network policy. It carries exactly the fields of netsecurity.Config, so a Guard rebuilt from it enforces the same rules the host applies to requests and websockets, including DNS-rebinding protection via validated-IP dialing.

A nil *NetworkPolicy means the host imposes no network restriction.

type PluginSpec added in v0.23.0

type PluginSpec struct {
	Path     string
	Args     []string
	Env      []string
	Headers  map[string]string
	Insecure bool
}

PluginSpec names one plugin to load: an executable path with optional command-line arguments and environment entries, or an http(s) URL of a remote JSON-RPC plugin server. Env entries are KEY=VALUE strings layered on top of the inherited environment (existing keys are overridden); they apply to executables only, an HTTP server owns its own environment. Headers ride every HTTP request (e.g. Authorization for a bearer token). Insecure skips TLS certificate verification for https URLs.

type Policy added in v0.23.0

type Policy struct {
	// AllowedPaths restricts filesystem locations (database files, storage
	// directories) exactly like the fs/pathlib libraries. nil = unrestricted.
	AllowedPaths []string `json:"allowed_paths,omitempty"`
	// Network restricts outbound connections. nil = unrestricted.
	Network *NetworkPolicy `json:"network,omitempty"`
}

Policy is the security context a host sends to plugins in the scriptling.handshake params. Plugins that understand it (advertised via the "policy" capability) enforce it on every operation that opens a file or a network connection; plugins that predate it simply ignore the field.

A nil *Policy means no restrictions. A non-nil Policy with nil AllowedPaths leaves filesystem access unrestricted; a nil Network leaves network access unrestricted. This mirrors the nil-semantics of fssecurity.Config and netsecurity.Config.

func PolicyFromSecurity added in v0.23.0

func PolicyFromSecurity(cfg *netsecurity.Config, allowedPaths []string) *Policy

PolicyFromSecurity converts the host-side security configuration into the wire Policy form. A nil cfg and nil allowedPaths yield a nil Policy (no restrictions); otherwise only non-nil parts are carried.

func (*Policy) Guard added in v0.23.0

func (p *Policy) Guard() (*netsecurity.Guard, error)

Guard returns a netsecurity.Guard enforcing the policy's network rules, or (nil, nil) when the network is unrestricted. The returned Guard validates every resolved IP at dial time, so connections made through its DialContext get the same SSRF and DNS-rebinding protection as host-side requests.

func (*Policy) NetworkEnabled added in v0.23.0

func (p *Policy) NetworkEnabled() bool

NetworkEnabled reports whether the policy carries a network policy at all. Callers use this to decide between a guarded dialer and the default one.

func (*Policy) PathAllowed added in v0.23.0

func (p *Policy) PathAllowed(path string) bool

PathAllowed reports whether path is within the policy's allowed paths. A nil policy (or nil AllowedPaths) allows everything, matching fssecurity.

type PolicySource added in v0.23.0

type PolicySource interface {
	Policy() *Policy
}

PolicySource supplies the effective security policy at call time. The host guarantees a plugin's handshake completes before its first function call, so external plugins can read Server.Policy() lazily inside connect/open.

type PropertySchema added in v0.11.0

type PropertySchema struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Settable    bool   `json:"settable,omitempty"`
}

PropertySchema describes a plugin class property.

type RPCError

type RPCError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

func (*RPCError) Error

func (e *RPCError) Error() string

type Registrar

type Registrar interface {
	RegisterLibrary(*object.Library)
}

type RemoteRef

type RemoteRef struct {
	Library string `json:"library"`
	Class   string `json:"class"`
	ID      string `json:"id"`
}

RemoteRef identifies an object stored in a plugin process.

type Schema

type Schema struct {
	Functions []FunctionSchema `json:"functions"`
	Classes   []ClassSchema    `json:"classes"`
	Constants []ConstantSchema `json:"constants"`
}

Schema describes functions, classes, and constants exposed by a plugin.

type ScopeOption added in v0.12.3

type ScopeOption func(*Manager)

ScopeOption configures a scoped Manager created by NewScope.

func WithTransport added in v0.12.3

func WithTransport(mode TransportMode) ScopeOption

WithTransport sets the transport restriction for a scoped Manager. Use TransportHTTP to permit only HTTP(S) plugins, TransportStdio for only stdio executables, or TransportAll (default) to allow both.

type ScriptLibraryRegistrar

type ScriptLibraryRegistrar interface {
	RegisterScriptLibrary(name string, script string) error
}

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(name, version, description string) *Server

func (*Server) Constant

func (s *Server) Constant(name string, value any) *Server

func (*Server) ObjectCount added in v0.15.0

func (s *Server) ObjectCount() int

ObjectCount returns the number of live server-side objects. Useful in tests.

func (*Server) Policy added in v0.23.0

func (s *Server) Policy() *Policy

Policy returns the security policy the host delivered in its handshake, or nil when the host sent none (no restrictions). Registration code reads it lazily inside connect/open closures because the handshake is the first message on every connection.

func (*Server) RegisterBuiltin added in v0.15.0

func (s *Server) RegisterBuiltin(name string, fn object.BuiltinFunction) *Server

RegisterBuiltin registers a raw builtin function directly, bypassing the FunctionBuilder reflection layer. Use this when the function is already an object.BuiltinFunction (e.g. a closure that wraps a script handler).

func (*Server) RegisterBuiltinClass added in v0.15.0

func (s *Server) RegisterBuiltinClass(name string, class *object.Class) *Server

RegisterBuiltinClass registers a scriptling *object.Class directly, bypassing the ClassBuilder. Use this when the class was loaded from a scriptling module (e.g. via Scriptling.Eval) rather than built from Go code.

func (*Server) RegisterClass

func (s *Server) RegisterClass(builder *object.ClassBuilder) *Server

func (*Server) RegisterFetcher added in v0.23.0

func (s *Server) RegisterFetcher(scheme string, f Fetcher) *Server

RegisterFetcher registers f as this plugin's fetcher: the host routes <scheme>:// sources here, attaches the plugin's library automatically, and asks for files only as imports resolve. The whole fetcher contract is this one call — one plugin serves one scheme, with the standard layout (modules under lib/, scripts as bare scheme:// sources). It must be called before Run / ServeHTTP, like the other registration methods. Built-in schemes (http, https, file) are rejected, as is a second registration.

func (*Server) RegisterFunc

func (s *Server) RegisterFunc(name string, builder *object.FunctionBuilder) *Server

func (*Server) RegisterLibrary added in v0.23.0

func (s *Server) RegisterLibrary(lib *object.Library) *Server

RegisterLibrary ingests a pre-built *object.Library — functions, classes (held as class constants), and constants — into the server. It is how a single registration implementation serves both plugin modes: the same library handed to RegisterLibrary here, or to a Scriptling instance's RegisterLibrary for compiled-in plugins.

func (*Server) RegisterScriptClass

func (s *Server) RegisterScriptClass(name string, source string) *Server

func (*Server) RegisterScriptFunc

func (s *Server) RegisterScriptFunc(name string, source string) *Server

func (*Server) Run

func (s *Server) Run() error

func (*Server) RunIO

func (s *Server) RunIO(input io.Reader, output io.Writer) error

RunIO serves the plugin protocol over a bidirectional stream (stdio). The jsonrpc.Peer handles framing, batch dispatch, notification detection and response correlation in both directions; dispatch runs in the peer's server.

RunIO blocks until the input reaches EOF — normally because the host closed the child's stdin after plugin.shutdown (which destroys objects and returns a response). The server need not force-exit on shutdown: the response is written before the host closes stdin, so there is no flush-before-exit race. Object finalizers run on plugin.shutdown and again on EOF (idempotent).

func (*Server) ServeHTTP added in v0.12.1

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP serves the Scriptling plugin JSON-RPC protocol over HTTP. Mount it at a path such as /json-rpc and load it with plugin.Manager.LoadURL or scriptling.plugin.load(..., scriptling=True).

The handler answers whoever reaches it: it ships no authentication of its own, so whatever fronts the endpoint (auth middleware, TLS plus credentials at a reverse proxy, a loopback-only bind) is the access control. Credentials arrive as headers on r; see the php-server example for a token check.

HTTP plugin transport supports normal plugin calls, object lifecycle, and batches. Host callbacks and plugin.Logger(ctx) require the bidirectional stdio transport and are not available over HTTP. Framing (POST handling, batches, notifications, error responses, 204 on no-response) is provided by jsonrpc.Server.ServeHTTP; this server only supplies the plugin methods.

func (*Server) Wrapper

func (s *Server) Wrapper(name string, source string) *Server

type StaticPolicy added in v0.23.0

type StaticPolicy struct {
	P *Policy
}

StaticPolicy is a PolicySource with a fixed value. It is the in-process counterpart of Server.Policy(): compiled-in plugins receive one built from the interpreter's security configuration, external plugins read the policy the handshake delivered.

func (*StaticPolicy) Policy added in v0.23.0

func (s *StaticPolicy) Policy() *Policy

Policy returns the fixed policy.

type TransportMode added in v0.12.3

type TransportMode int

TransportMode restricts which plugin transport protocols a Manager or scope will accept when LoadPath or LoadURL is called.

const (
	// TransportAll permits both stdio/executable and HTTP(S) plugins (default).
	TransportAll TransportMode = iota
	// TransportHTTP permits only HTTP(S) endpoints; loading executables fails.
	TransportHTTP
	// TransportStdio permits only stdio executables; loading HTTP URLs fails.
	TransportStdio
)

type Value

type Value struct {
	Type     string           `json:"type"`
	Value    any              `json:"value,omitempty"`
	Items    []Value          `json:"items,omitempty"`
	Entries  map[string]Value `json:"entries,omitempty"`
	Remote   *RemoteRef       `json:"remote,omitempty"`
	Callback *CallbackRef     `json:"callback,omitempty"`
}

Value is the JSON-RPC transport representation of Scriptling values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL