From 03a9d2143dfc9dd84338de8c421d917654cffb06 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 2 Aug 2026 23:19:24 -0400 Subject: [PATCH] Allow fetch in db library vm contexts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Db library scripts are basically a JS module scoped to the db, and handlers already get fetch — but runLibrary's own vm context only bound sql/console, so any library calling fetch (e.g. the chat db's OpenAI call) threw ReferenceError. Since library calls are often fire-and-forget inside setTimeout (no rejection handler), that crashed the whole process. Bind fetch in all three places a library script runs: request-time (runLibrary), the library-editor save eval, and repl. Also add `bliss link` to bliss-cli for jumping straight to a structure/route/template/db's workshop editor URL, including resolving a db by its require('db')(alias) name. --- CLAUDE.md | 2 +- bliss-cli/SKILL.md | 17 +++++++++++++++++ bliss-cli/bliss | 38 ++++++++++++++++++++++++++++++++++++++ index.js | 9 ++++++--- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 11619e3..682e5fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,7 @@ The Express app has two kinds of routes: User route handlers run in `vm.createContext()` with a restricted API: - `require('eta')` — Eta template instance scoped to the structure -- `require('db')` — getter for named database instances. `require('db')(alias)` returns `{ library, sql }`: `sql` is the raw `better-sqlite3` instance for that db, and `library` is `module.exports` from that db's own "library" script (arbitrary JS, edited via `/workshop/:structure_id/db/:db_id/library`, run once per request with `sql` bound in its own vm context — see `bootstrapContext` in `index.js`). Handlers typically call helper functions off `library` rather than writing raw SQL inline. +- `require('db')` — getter for named database instances. `require('db')(alias)` returns `{ library, sql }`: `sql` is the raw `better-sqlite3` instance for that db, and `library` is `module.exports` from that db's own "library" script (arbitrary JS, edited via `/workshop/:structure_id/db/:db_id/library`, run once per request with `sql`, `console`, and `fetch` bound in its own vm context — see `runLibrary` in `index.js`). Handlers typically call helper functions off `library` rather than writing raw SQL inline. - `require('push')` — web-push library - `require('files')` — `{ saveFile }` helper - `console.log` — writes to the app's `logs` table (not stdout) diff --git a/bliss-cli/SKILL.md b/bliss-cli/SKILL.md index 7f52057..efebf72 100644 --- a/bliss-cli/SKILL.md +++ b/bliss-cli/SKILL.md @@ -74,6 +74,23 @@ bliss preview-route # rendered HTML of a GET route bliss preview-template # rendered HTML using the template's test_object ``` +## Links + +Jump straight into the workshop editor for something instead of constructing +the URL by hand: + +```bash +bliss link structure +bliss link route +bliss link template +bliss link db # by numeric db id +bliss link db # or by the alias used in require('db')(alias) +``` + +`bliss link db` resolves an alias (e.g. `chat`, as in `require('db')('chat')`) +by looking at the structure's attached dbs — handy since the same alias name +often points to a different db per structure (aliased/cloned dbs). + ## Editing code (handlers, templates, library) Handlers/templates/library are multiline code. Write the code to a temp file and diff --git a/bliss-cli/bliss b/bliss-cli/bliss index eb673cf..2c110cf 100755 --- a/bliss-cli/bliss +++ b/bliss-cli/bliss @@ -212,6 +212,44 @@ const commands = { ); out(await c.getText(`/workshop/${sid}/template/${tid}/preview`)); }, + async link({ pos }) { + const [type, sid, ref] = pos; + need( + type && sid, + "usage: bliss link structure | route | template | db ", + ); + switch (type) { + case "structure": + out(`${c.BASE}/workshop/${sid}`); + break; + case "route": + need(ref, "usage: bliss link route "); + out(`${c.BASE}/workshop/${sid}/route/${ref}`); + break; + case "template": + need(ref, "usage: bliss link template "); + out(`${c.BASE}/workshop/${sid}/template/${ref}`); + break; + case "db": { + need(ref, "usage: bliss link db "); + let dbId = ref; + if (!/^\d+$/.test(ref)) { + // ref is an alias (the string passed to require('db')(alias) in + // handler code) rather than a numeric db id — resolve it. + const { dbs } = await c.getJSON(`/plumbing/structures/${sid}`); + const match = dbs.find((d) => d.alias === ref); + need(match, `no db aliased "${ref}" on structure ${sid}`); + dbId = match.id; + } + out(`${c.BASE}/workshop/${sid}/db/${dbId}`); + break; + } + default: + throw new Error( + `unknown link type "${type}" (want structure|route|template|db)`, + ); + } + }, async upload({ pos }) { const [sid, filepath] = pos; need(sid && filepath, "usage: bliss upload "); diff --git a/index.js b/index.js index b4aef76..8cf29cd 100644 --- a/index.js +++ b/index.js @@ -103,10 +103,11 @@ function makeConsole(structureId, routeId) { }; } -// Evaluate a db's "library" script with `sql` and `console` bound, returning -// its exports. Each library runs in its own context so nothing leaks between dbs. +// Evaluate a db's "library" script with `sql`, `console`, and `fetch` bound, +// returning its exports. Each library runs in its own context so nothing +// leaks between dbs. function runLibrary(sql, librarySource, console) { - const libContext = vm.createContext({ sql, console, module: { exports: null } }); + const libContext = vm.createContext({ sql, console, fetch, module: { exports: null } }); vm.runInContext(librarySource, libContext); return libContext.module.exports; } @@ -548,6 +549,7 @@ app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => { let context = vm.createContext({ module: { exports: null }, sql: dbInstance, + fetch, console: { log: (...args) => capturedOutput.push(inspectArgs(args)), }, @@ -569,6 +571,7 @@ app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => { let context = vm.createContext({ module: { exports: null }, sql: dbInstance, + fetch, console: { log: (...args) => capturedOutput.push(inspectArgs(args)), },