feat: make webjs db bring-your-own-ORM via a webjs.db verb map - #1469
Merged
Conversation
Drizzle is the scaffold default, not lock-in: the runtime never imports it
and db/connection.server.ts is the app's own file. But the webjs db verbs
contradicted that. generate / migrate / push / studio resolved the app's
drizzle-kit binary and exited 1 with any other ORM installed, while
`webjs db migrate` is the spelling baked into the scaffolded dev.before and
start.before tasks, the Dockerfile, CI, and the deployment docs. So an ORM
swap meant rewriting all of that instead of one thing.
A "webjs": { "db": { "<verb>": "<command>" } } block in package.json now
maps a verb to the shell command webjs db <verb> runs instead, with
node_modules/.bin on PATH like a before step and the extra CLI args
appended. Any key is a verb, so a block can add subcommands. A verb the
block does not name keeps its default, so an app with no block is
unchanged and the scaffold emits none. The drizzle-kit-missing error and
the unknown-verb error both name the block as the way forward.
The schema, the WebjsConfig type, and the reader-key lockstep test all
carry the new key. Docs: AGENTS.md, the skill's built-ins reference, the
database and configuration docs pages, and the CLI README.
Closes #1468
vivek7405
marked this pull request as ready for review
September 10, 2026 07:59
vivek7405
commented
Sep 10, 2026
vivek7405
left a comment
Collaborator
Author
There was a problem hiding this comment.
The seam is the right shape: a pure reader beside readAppTasks, the mapped path reusing runBeforeSteps so PATH handling matches a before step, and the drizzle-kit default untouched. Four things to fix, all small, the first one real:
- Extra args are joined into the shell string unquoted, so any arg with a space or a metacharacter is re-split or expanded by the shell (inline).
- The verb lookup indexes a plain object, so
webjs db constructor/webjs db toStringfinds an inherited function and runs its source through the shell (inline). - A bare
webjs dbnow saysUnknown db subcommand "undefined"(inline). webjs db <verb>in the database docs is unescaped, so the parser eats it (inline).
Two smaller notes, no action needed unless you want them in this PR:
readDbCommandsrepeats the package.json read plus thewebjs-block guard fromreadAppTasksverbatim. AreadWebjsBlock(appDir, readFile)the two share would keep the next reader from copying it a third time.runBeforeStepsresolves a signal-killed child as exit 0 (code ?? 0on theexitevent, wherecodeis null when a signal ended it), and the mapped path now inherits that: awebjs db migratethat gets SIGKILLed mid-migration exits the CLI 0, which instart.beforeboots the server on a half-applied schema. Pre-existing forbeforesteps, but this PR widens it to the db verbs, so worth acode ?? (signal ? 1 : 0)at some point.
Three review findings on the webjs db verb map and one on the step runner. Args appended to a mapped command went into the shell string raw, so --name "add users" arrived as two words and a $ or ; was expanded. Each arg is now single-quoted (shellQuote), so the mapped path matches the real argv the drizzle-kit default already passes. The verb lookups indexed plain objects, so webjs db constructor found an inherited function on both the mapped map and the kit map. Both now use an own-property check. A bare webjs db reported an unknown "undefined" subcommand; it now says the subcommand is missing. runBeforeSteps resolved a signal-killed child as exit 0, which the mapped webjs db migrate in start.before inherited. A signal now maps to 1 so an OOM-killed migrate aborts the boot.
Inside the html template a raw <verb> parses as an element and the code-block reads textContent, so the placeholder vanished from the rendered sample.
readDbCommands duplicated the package.json parse and webjs-block guard from readAppTasks. Both now go through readWebjsBlock.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1468
webjs dbis now adapter-driven: a"webjs": { "db": { "<verb>": "<command>" } }block inpackage.jsonmaps a verb to the shell commandwebjs db <verb>runs instead of the drizzle-kit default, withnode_modules/.binon PATH and extra CLI args appended, sowebjs db migratekeeps one spelling across ORMs and the scaffoldedstart.before, the Dockerfile, CI and the deploy docs all keep working after an ORM swap. Any key is a verb (a block can addwebjs db reset); an unmapped verb keeps its default, so an app with no block is unchanged.Summary
Drizzle was already a default rather than lock-in (the runtime never imports it,
db/connection.server.tsis the app's own file), but four of the fivewebjs dbverbs resolved drizzle-kit and exited 1 with any other ORM installed, and the drizzle-kit-missing error told a Prisma user to install drizzle-kit. Now:packages/cli/lib/app-tasks.jsgainsreadDbCommands(), the pure reader for thewebjs.dbmap, besidereadAppTasks().packages/cli/bin/webjs.jschecks the map first (a mappedseedoverrides the seed-file runner too) and runs the command throughrunBeforeSteps, so it resolves local binaries exactly as abeforestep does. The drizzle-kit-missing error and the unknown-verb error both name the block. The banner andwebjs help dbdocument it.WebjsConfigtype, the validator's key count, and the reader-key lockstep test all carry the newdbkey.Decisions
dbblock. Drizzle stays the default by omission; emitting the Drizzle mapping would only duplicate the default into every app. A comment increate.jssays so.additionalProperties: { type: string }rather than sealed, since a block addingresetis the point. The boot-time validator does not descend into objects (the same posture asdev/start), so a non-string value is dropped by the reader rather than rejected at boot.--name "add users"reaches the ORM as one word and nothing is shell-expanded, matching the real argv the drizzle-kit default passes. Verb lookups are own-property checks on both maps, a barewebjs dbreports a missing subcommand, and a signal-killedbeforestep now fails the boot instead of resolving as exit 0.process.execPath+resolveBinpath from Make webjs db/test commands runtime-native (Bun-safe, drop npx/node --test) #570, so the npx-free Bun-image guarantee for the default is untouched. Docs tell a BYO user to write the bare binary, notnpx ....Test plan
packages/cli/test/app-tasks(reader),packages/server/test/config(schema key set,dbshape,d.tslockstep),test/cli/help.test.mjs(help wording). 296 pass acrosspackages/cli/test,test/cli,packages/server/test/config.webjs dbadded): newtest/cli/db.test.mjsscaffolds a temp app and runs the real CLI: mapped verb with args, map-only verb, mappedseed, exit-code propagation, no-block default path, unmapped unknown verb. Counterfactual at 68f8e69: withpackages/cli/bin/webjs.jsreverted toorigin/main, 6 of 6 fail; restored, 6 pass.bun test test/cli/db.test.mjs6 pass. Bun's resolver finds a global drizzle-kit from a temp dir, so the no-block test accepts either the not-installed hint or drizzle-kit's own config error, and asserts the mapped path was not taken in both.test/scaffolds/scaffold-integration.test.jspasses (the scaffold'swebjsblock still validates against the schema).webjs checkinsidegallery,examples/blog,website: all pass./,/docs/database,/docs/configurationwith no broken modulepreload and the new#bring-your-own-ormanchor present. Blog e2e N/A: CLI-only change, nothing the browser fetches changed.npm test: 4595 of 4601 pass. The 5 failures (test/bun/listener.test.mjs, the listener-overhead parity, and the 3 elision-verify assertions) are the known linked-worktree set that resolves the primary checkout's framework copy; they pass in a primary checkout and in CI.Docs surfaces
AGENTS.md(CLI reference + config block paragraph),.agents/skills/webjs/references/built-ins.md(new "Bring your own ORM" section),website/app/docs/database/page.ts(CLI list + new section),website/app/docs/configuration/page.ts(webjs dbsection + the 18-key count),packages/cli/README.md, the schema description and thed.tsJSDoc.create.jscomment added); MCP (the knowledge layer bundlesAGENTS.mdand the skill, no tool projection changed); editor plugins (no template or language-service surface); marketing copy (no positioning claim changed); README (itswebjs db migratemention still holds).