Fruitful Docs
Reference

Plugin runtime environment

What capture hooks and the transform may use inside the sandbox, and what does not exist there.

Package code runs in a capability-free sandbox: QuickJS compiled to WebAssembly, with an empty import table. fetch, require, process, and the Node built-ins are not merely blocked; they do not exist as symbols. Memory and wall-clock are bounded. The same sandbox ships in the server and the Desktop app, so a package behaves identically wherever it runs.

Entry points

ModuleExportsCalled with
transform.hooks.filenormalize (optional), materializeRecords, buildViewextract items; the root record and its related records
capture.hooks.filethe functions listed in capture.hooks.exportsthe page API, limited to the declared capabilities

Entry point names are fixed by the runtime id (fruitful-page-plugin@1); the manifest never restates them.

Always available

Bare ECMAScript: JSON, Math, Date, RegExp, String, Array, Object, Map, Set, encodeURIComponent, decodeURIComponent, parseInt, parseFloat.

Available when declared

stdlib on the hooks block links pure, pinned polyfills. Declare only what the module uses:

"hooks": { "file": "blog-plugin.js", "stdlib": ["url", "intl", "base64"] }
ModuleProvides
urlURL, URLSearchParams, TextEncoder, TextDecoder
intlIntl.NumberFormat, Intl.PluralRules, Intl.Locale, Intl.getCanonicalLocales, Number.prototype.toLocaleString
base64atob, btoa, Uint8Array.fromBase64, Uint8Array.prototype.toBase64

The sandbox reads no ambient locale: pass one explicitly (value.toLocaleString('en-US')). To read a base64url identifier, use Uint8Array.fromBase64(value, { alphabet: 'base64url' }) and TextDecoder, never Buffer.

Not available

Buffer, require and every node: built-in, process, fetch, setTimeout and other timers, crypto, fs. The bundler builds with esbuild in browser mode and bundles everything, so a node: import fails plugin generate with "Could not resolve"; a typecheck against Node types will not catch it earlier.

Capture hook operations

A capture hook's import table is exactly the operations its declared capabilities grant:

CapabilityOperations
browser.dom.readwaitForVisible(selector), count(selector)
browser.dom.clickclick(selector)
browser.dom.typetype(selector, text); refuses password fields and credential-looking input

Each operation is clamped by a timeout (10 s by default, 30 s at most). An operation the export did not declare is absent at link time, not denied at call time. Hooks are written against CaptureApi from workspace.packages.universal.lib.

Marshalling

Arguments and results cross the boundary as JSON values. No host objects or functions enter the guest; nothing the guest creates can hold a reference to the host. A guest that throws surfaces as a sandbox error with the message; a guest that exceeds the time limit is terminated.

On this page