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.

Optional field tracing

The transform operations receive (input, context, trace?). ContentPipelineModule types this optional collector; it is created inside the sandbox and grants no host, DOM, storage, or network access. Existing packages can ignore it and keep their existing return shapes.

OperationCollector methodSource and target
normalizelinkExtractField({ item, path, sources })Input items → returned normalized item
materializeRecordslinkRecordField({ record, path, sources })Input items → returned semantic record
buildViewlinkViewField({ view, path, sources })Root/supporting records → returned View

Each item source is { item, path }; each record source is { record, path }. Use actual input and returned objects. Paths are JSON Pointers, relative to item.fields for extracts and to the JSON root for records and Views. A context source is { context: true, path: '/capturedAt' }. Multiple sources remain multiple contributors. Calls apply only during their operation; retained collectors are inactive.

Fruitful records the captured HTML digest, exact locked binding, original entry and field match, selector, and match occurrence automatically. The package declares how its JavaScript connects fields; those claims should have fixture coverage. A missing normalizer and the required View /record identity need no extra annotations. Other unreported relationships remain gaps.

Tracing does not change binding files, semantic Lexicons, or the manifest format. Publish annotations as a new immutable plugin version through the usual generate and validation workflow. Older captures retain their original package resolution and available evidence; an updated plugin does not retrospectively change them. See normalization and DevTools.

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