Plugins and libraries
Read first: getting-started/scripts, getting-started/background-jobs
This page shows you how to add the plugins your scripts need to your project, load a library from a script, and decide what each plugin is allowed to do.
You’ll add a library with hpy happyview add, see what it writes to happyview.toml and happyview.lock, use the library in a query script, and read the capabilities it asks for. The examples use the happyview.db library and the xyz.statusphere.status collection.
Interpreters and libraries
Section titled “Interpreters and libraries”HappyView runs your scripts inside WebAssembly plugins. Two kinds matter for scripts:
- An interpreter runs scripts in one language. The official ones are
happyview-lua,happyview-javascriptandhappyview-typescript. A script’s language is whichever interpreter claims its file extension. - A library gives scripts a set of functions under a
happyview.*name, such ashappyview.dbfor reading indexed records orhappyview.jobsfor queuing background jobs. A script loads a library withrequirein Lua orimportin JavaScript and TypeScript.
HappyView itself ships with no interpreter and no library. Everything a script can reach beyond the built-in internal.* modules comes from a library your project adds.
Add a plugin to your project
Section titled “Add a plugin to your project”hpy happyview create already added the interpreters you chose, Lua by default. To add a library, or another interpreter, run hpy happyview add from the project root with the library’s name or the interpreter’s language:
hpy happyview add happyview.db # a library, by its namespacehpy happyview add happyview-db # the same library, by its project namehpy happyview add javascript # an interpreter, by its languagehpy looks the name up in the registry and works out whether it’s an interpreter or a library. If a name is both, pass --interpreter or --library. If several registry projects answer to one name, hpy asks which one you mean, or you can name it with --project.
Each plugin publishes up to three channels: latest, beta and alpha. With no option, hpy follows latest, or the first of beta and alpha the plugin publishes if it has no latest. To choose:
hpy happyview add happyview.db --channel alpha # follow a channel: latest, beta or alphahpy happyview add happyview.db --version 1.2.0 # pin one exact version--channel accepts only latest, beta and alpha. You can’t pass both --channel and --version.
What it writes
Section titled “What it writes”add writes an entry to happyview.toml. Interpreters go under [interpreters], keyed by language. Libraries go under [libraries], keyed by namespace:
[interpreters]lua = { did = "did:plc:mo7vq4jbioxwafy5zkcc3eux", name = "happyview-lua", channel = "alpha" }
[libraries]"happyview.db" = { did = "did:plc:mo7vq4jbioxwafy5zkcc3eux", name = "happyview-db", channel = "alpha" }Each entry names the registry project by its publisher’s DID and its name, and follows either a channel or one exact version.
add then resolves every entry to an exact version, downloads each plugin, and writes happyview.lock. The lock pins the version and the CIDs that the downloaded files must match. Commit it, and don’t edit it by hand:
# Written by hpy from happyview.toml. Commit it; don't edit it by hand.
version = 2
[happyview]version = "3.0.0"
[[plugin]]id = "happyview-db"kind = "library"keys = ["happyview.db"]project = "at://did:plc:mo7vq4jbioxwafy5zkcc3eux/software.release.project/happyview-db"version = "1.2.0"channel = "alpha"version_uri = "at://did:plc:mo7vq4jbioxwafy5zkcc3eux/software.release.version/happyview-db.1.2.0"# manifest_cid, package_cid and manifest_blob_cid followdependencies = []A channel moving on to a newer version doesn’t change the lock. Your project keeps the locked version until you run hpy happyview add for that plugin again. Run hpy happyview add with no plugin names to bring the lock up to date with happyview.toml and download it.
If anything fails, such as a name the registry doesn’t know or a download that doesn’t match, add changes neither file.
Restart hpy happyview dev
Section titled “Restart hpy happyview dev”hpy happyview dev puts the locked plugins in place before it starts HappyView. A running dev doesn’t watch the plugins directory, so stop it and start it again after add.
Deploy the locked versions
Section titled “Deploy the locked versions”hpy happyview deploy makes an instance match your project. Plugins come first: it installs each plugin in happyview.lock at its locked version, or moves an installed one to that version. Then it sends your lexicons, scripts and modules. Sign in to the instance first with hpy happyview login.
- A deploy installs exactly what the lock pins. If
happyview.lockdoesn’t matchhappyview.toml, the deploy is refused and tells you to runhpy happyview add. - It lists the plugin steps, such as
install happyview-db 1.2.0, and asks before it changes anything.--yesskips the question.--dry-runshows the changes without making them. - A plugin installed on the instance but missing from the lock is kept. Pass
--prune-pluginsto uninstall it. - The account you deploy with needs the
plugins:createpermission to install plugins.
Use a library in a script
Section titled “Use a library in a script”Once happyview.db is in your project, any script can load it. This query script lists one account’s statuses, newest first:
local db = require("happyview.db")local log = require("internal.logging")
function handle(input, ctx) local page = db.records("xyz.statusphere.status") :did(input.did) :sort("createdAt", "desc") :limit(input.limit) :run()
log.info("statuses read", { did = input.did, count = #page.records }) return { statuses = page.records, cursor = page.cursor }endimport db from "happyview.db";import log from "internal.logging";
export default async function handle(input, ctx) { const page = await db .records("xyz.statusphere.status") .did(input.did) .sort("createdAt", "desc") .limit(input.limit) .run();
log.info("statuses read", { did: input.did, count: page.records.length }); return { statuses: page.records, cursor: page.cursor };}import db from "happyview.db";import log from "internal.logging";
interface Params { did: string; limit?: number;}
export default async function handle(input: Params) { const page = await db .records("xyz.statusphere.status") .did(input.did) .sort("createdAt", "desc") .limit(input.limit) .run();
log.info("statuses read", { did: input.did, count: page.records.length }); return { statuses: page.records, cursor: page.cursor };}require looks for a built-in internal.* module first, then for an installed library with that name. In JavaScript and TypeScript, every library call returns a promise, so await it. In Lua, a library call returns its result directly.
If the library isn’t installed, the script fails when it runs, with an error such as module 'happyview.db' not found -- is the 'happyview.db' library plugin installed?. The build doesn’t check library names, so hpy happyview check won’t catch this. Add the library before you rely on it.
Handle a library error
Section titled “Handle a library error”A library that fails raises an error in your script. The message carries an error code, such as BAD_INPUT or NO_SESSION. Each library’s README lists its codes. Catch the error when you expect it:
local db = require("happyview.db")
function handle(input, ctx) local ok, result = pcall(db.get, input.uri) if not ok then return { error = tostring(result) } end return { record = result }endimport db from "happyview.db";
export default async function handle(input, ctx) { try { return { record: await db.get(input.uri) }; } catch (err) { return { error: err.message }; }}import db from "happyview.db";
export default async function handle(input: { uri: string }) { try { return { record: await db.get(input.uri) }; } catch (err) { return { error: (err as Error).message }; }}The full list of libraries, with what each one does, is in Libraries.
Capabilities
Section titled “Capabilities”Every plugin declares the capabilities it needs, and HappyView grants it exactly those and nothing more. A capability is a group of things the plugin may do through HappyView, such as records:read to look up indexed records or database:write to run any SQL.
Some examples from the official plugins:
| Plugin | Capabilities |
|---|---|
happyview-lua, happyview-javascript, happyview-typescript | library:call, script:host, script:test, wasi:clock, wasi:random, wasi:stdio |
happyview-db | records:read |
happyview-sql | database:read, database:write |
happyview-jobs | jobs:create, jobs:read, jobs:read_any |
happyview-http | network:request:unrestricted |
A library’s capabilities bound what every script can do through it. With happyview-sql installed, any script can run any SQL statement. With happyview-http, any script can call any host. Add only the libraries your scripts use.
An interpreter’s library:call lets scripts call installed libraries. Each library runs with its own capabilities, not the interpreter’s.
Consent is the operator’s call
Section titled “Consent is the operator’s call”Whoever installs a plugin on an instance agrees to the capabilities it asks for. How that happens depends on the way it’s installed:
hpy happyview deploydoesn’t list capabilities, and the instance grants each plugin every capability its manifest declares. Adding a plugin tohappyview.lockis where you agree to them, so read them before you add it.- The dashboard shows them on the install screen’s Permissions tab, with a risk level for each (Low risk, Medium risk, High risk or Critical) and a sentence that says what it allows. Installing grants what is shown there. When an update or a channel switch asks for a capability the installed version doesn’t have, the dashboard shows This version asks for new permissions. Check Allow these permissions to go ahead. If you don’t, the installed version keeps running.
HAPPYVIEW_DEFAULT_PLUGINS: naming a plugin there consents to every capability its manifest declares.
Capabilities don’t replace a caller
Section titled “Capabilities don’t replace a caller”Some library calls also need a user to act as, whatever capabilities the library has. A library acts as ctx.caller_did, and some scripts have none.
The same holds for an anonymous request to a query. Record scripts act as the record’s author, so they can queue jobs. See Background jobs.
Next steps
Section titled “Next steps”- Authentication: sign users in, so scripts have a caller
- Deploying: put your project, and its locked plugins, on an instance
- The CLI: every
hpy happyviewcommand and option - Libraries: every library and the capabilities it asks for
- Plugins: channels, updates, and installing from a URL or the file system
- Plugins admin API
- Developing plugins: build your own library

