@cldmv/slothlet-vine
v1.1.3
Published
Vines between slothlet api trees — location-transparent forwarding leaves over pluggable transports (Web Worker postMessage, worker_threads, process IPC, WebSocket), permission-gated by slothlet itself.
Maintainers
Readme
@cldmv/slothlet-vine
Vines between slothlet api trees.
Slothlet composes a folder of modules into an api tree. A vine connects two trees across an execution boundary — a Web Worker, another thread, another process, or another machine — by mounting forwarding leaves: stubs that live at the callee's identical logical path in the caller's tree, so self.exts.foo.bar() works the same whether foo is co-located or isolated. Slothlet cannot tell a vine leaf from a real one — including its permission identity, so a rule targeting exts.foo.bar gates the forwarding stub exactly as it would gate the real leaf, before it dispatches.
That gating follows slothlet's own rule about who is calling: a call made by a MODULE (self.exts.foo.bar()) is checked against the permission rules, and a denied one never runs the stub body, so it never reaches the wire. A call made through the bound handle slothlet() returned — the host itself — carries host standing and is not checked. That carve-out is slothlet's design, not a vine gap, but it does mean "permission-gated" describes module-initiated calls; a host that forwards on someone else's behalf is responsible for its own authorization.
✨ What's New
Latest: v1.1.2 (September 2026)
@cldmv/slothletpeer floor raised to>=3.20.0(#31) — the suite now runs against slothlet 3.20.0, so that is the version the peer range guarantees. No runtime source changed; the rest of the release is dev-tooling bumps.- View full v1.1.2 Changelog
Recent Releases
- v1.1.1 (September 2026) — CI-only: release-flow caller workflows synced to the current v4 templates (Changelog)
- v1.1.0 (September 2026) — cross-vine event forwarding in both directions, gated by the emitter's own permissions (Changelog)
- v1.0.2 (September 2026) — dev-only:
@cldmv/slothletdev pin3.15.0→3.15.1(peer floor unchanged) (Changelog) - v1.0.1 (August 2026) — dev-only:
eslint10.9.0→10.9.1(Changelog)
📚 For complete version history and detailed release notes, see the docs/changelog/ folder.
🚀 Key Features
🎯 Location-Transparent Forwarding
grow() mounts one stub per far-side leaf at the identical dotted path slothlet would use locally — self.exts.pdfViewer.open() reads the same whether pdfViewer runs in-process, in a worker, or in another machine entirely.
🔐 Permission-Gated by Slothlet Itself
A mounted stub is a real slothlet leaf as far as slothlet's own permission system is concerned. There is no separate vine-side authorization layer to configure, audit, or drift out of sync with the rest of the api tree.
📦 Data-Only by Design
A function argument or return value is refused at the edge with a named, catchable error — never a silent clone-crash, and never a live closure smuggled across an isolation boundary.
🔌 Five Built-In Transports, Pluggable Contract
loopback, post-message, worker-threads, process, and websocket ship as independent subpath exports (only what you import is pulled in). Anything that can implement the small Channel interface — send, onMessage, close, an optional onClose, and a capabilities declaration — can host a vine.
⏱ Settle-Once Correlation & Budgets
Every call is correlated by callId and bounded by a per-call budget. A pending call always settles — success, error, or timeout — even against a far side that never answers.
🧪 Reusable Conformance Harness
@cldmv/slothlet-vine/testing exports the same framework-injected test suite the five built-in transports are held to, so a custom transport can be verified against the identical contract.
🛡 100% Test Coverage
Statements, branches, functions, and lines — held to the same bar as slothlet itself.
📦 Installation
Requirements
- A slothlet instance to grow from or serve —
@cldmv/slothlet>=3.14.0(peer dependency) - ESM (
import); CommonJS interop follows whatever your bundler/runtime provides for a"type": "module"package - The
websockettransport additionally needs the optional peerws>=8.0.0— only if you import it
Install
npm install @cldmv/slothlet-vine🚀 Quick Start
import * as vine from "@cldmv/slothlet-vine";
import { createPair } from "@cldmv/slothlet-vine/transport/loopback";
const [near, far] = createPair();
// serve this instance's leaves to the far side
const serving = await vine.serve(workerApi, far, { paths: ["exts"] });
// mount the far tree's leaves into this instance, at identical paths
const link = await vine.grow(hostApi, near, { budgetMs: 5000 });
await hostApi.exts.pdfViewer.open("a.pdf"); // executes on the serving instance
await link.close(); // stubs unmounted; in-flight calls settle VINE_CLOSED
serving.close();Swap transport/loopback for any of the other four built-in transports, or your own Channel implementation, without changing anything above createPair()/connect(). With a real boundary, create the channel in the same tick as the worker, child or socket it wraps — before any await — so the far side's one-shot surface frame always has a listener to land on; after that, the transport queues it until grow() is ready (see Transports → Create the channel before you await anything). Single-word leaves, context carried by the namespace — never growVine()-style camelCase that repeats the package's own name.
📚 Configuration
grow() and serve() both take (api, channel, options) — the local slothlet instance, the transport seam, and an options object. The complete reference — every option, every return field — lives in docs/CONFIGURATION.md.
| Function | Option | Type | Default | Description |
| --------- | ------------- | ---------- | -------------------- | ------------------------------------------------------------------------- |
| serve() | paths | string[] | every callable leaf | Dotted prefixes to publish |
| serve() | modules | string[] | — | Extra moduleIDs to union in (runtime add() mounts leaves() can't see) |
| grow() | budgetMs | number | 30000 | Per-call settle budget; exceeded → VINE_BUDGET |
| grow() | handshakeMs | number | budgetMs | Deadline for the initial surface (leaf-manifest) frame |
| grow() | paths | string[] | every published leaf | Dotted prefixes to mount (grow-side mirror of serve()'s own filter) |
serving.close()/link.close() never close the channel itself — a channel may outlive one grow/serve pairing, and one a consumer handed in is not this call's to tear down.
📚 Documentation
docs/ isn't included in the published npm package (only src, schemas, README.md, and LICENSE ship — see DESIGN.md), so every link below is absolute and works the same from npmjs.com, an editor previewing the installed package, or GitHub itself.
Reference
- Design & Protocol — the normative Channel contract, frame schema,
grow/servesemantics, and error taxonomy. If a guide below and this disagree, this wins. - Configuration Reference — every
grow()/serve()option, with defaults, and whatlink/servingreturn. - Changelog — all release notes.
Technical Guides
- Transports — the five built-in transports, when to reach for each, and the death-detection/ownership details that differ between them.
- Writing a Custom Transport — implementing the Channel contract, the uniform send-failure policy, and verifying it with the shared conformance harness.
- Error Reference — the
VINE_*code list,VineError/VineRemoteError, and why a remoteVINE_*code is never adopted as-is. - Permissions — how slothlet's own permission system gates a mounted stub exactly like a real leaf.
- Using a Vine in the Browser — a full Web Worker walkthrough with the
post-messagetransport.
🔗 Links
- npm: @cldmv/slothlet-vine
- GitHub: CLDMV/slothlet-vine
- Issues: GitHub Issues
- Releases: GitHub Releases
📄 License
Apache-2.0 © Shinrai / CLDMV
