Skip to content

feat: make webjs db bring-your-own-ORM via a webjs.db verb map - #1469

Merged
vivek7405 merged 4 commits into
mainfrom
feat/byo-orm-db-config
Sep 10, 2026
Merged

feat: make webjs db bring-your-own-ORM via a webjs.db verb map#1469
vivek7405 merged 4 commits into
mainfrom
feat/byo-orm-db-config

Conversation

@vivek7405

@vivek7405 vivek7405 commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #1468

webjs db is now adapter-driven: a "webjs": { "db": { "<verb>": "<command>" } } block in package.json maps a verb to the shell command webjs db <verb> runs instead of the drizzle-kit default, with node_modules/.bin on PATH and extra CLI args appended, so webjs db migrate keeps one spelling across ORMs and the scaffolded start.before, the Dockerfile, CI and the deploy docs all keep working after an ORM swap. Any key is a verb (a block can add webjs 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.ts is the app's own file), but four of the five webjs db verbs 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.js gains readDbCommands(), the pure reader for the webjs.db map, beside readAppTasks().
  • packages/cli/bin/webjs.js checks the map first (a mapped seed overrides the seed-file runner too) and runs the command through runBeforeSteps, so it resolves local binaries exactly as a before step does. The drizzle-kit-missing error and the unknown-verb error both name the block. The banner and webjs help db document it.
  • The JSON schema, the WebjsConfig type, the validator's key count, and the reader-key lockstep test all carry the new db key.

Decisions

  • The scaffold emits no db block. Drizzle stays the default by omission; emitting the Drizzle mapping would only duplicate the default into every app. A comment in create.js says so.
  • Any key is a verb, values are free-form strings. The schema is additionalProperties: { type: string } rather than sealed, since a block adding reset is the point. The boot-time validator does not descend into objects (the same posture as dev / start), so a non-string value is dropped by the reader rather than rejected at boot.
  • Extra args are single-quoted before they join the mapped command string, so --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 bare webjs db reports a missing subcommand, and a signal-killed before step now fails the boot instead of resolving as exit 0.
  • Mapped commands run through the shell, the drizzle-kit default keeps the process.execPath + resolveBin path 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, not npx ....

Test plan

  • Unit: packages/cli/test/app-tasks (reader), packages/server/test/config (schema key set, db shape, d.ts lockstep), test/cli/help.test.mjs (help wording). 296 pass across packages/cli/test, test/cli, packages/server/test/config.
  • CLI spawn (9 tests after the review round: arg quoting, inherited verb names, bare webjs db added): new test/cli/db.test.mjs scaffolds a temp app and runs the real CLI: mapped verb with args, map-only verb, mapped seed, exit-code propagation, no-block default path, unmapped unknown verb. Counterfactual at 68f8e69: with packages/cli/bin/webjs.js reverted to origin/main, 6 of 6 fail; restored, 6 pass.
  • Bun: bun test test/cli/db.test.mjs 6 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.js passes (the scaffold's webjs block still validates against the schema).
  • webjs check inside gallery, examples/blog, website: all pass.
  • Dogfood: website boots in prod mode, 200 on /, /docs/database, /docs/configuration with no broken modulepreload and the new #bring-your-own-orm anchor present. Blog e2e N/A: CLI-only change, nothing the browser fetches changed.
  • Full 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

  • Updated: 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 db section + the 18-key count), packages/cli/README.md, the schema description and the d.ts JSDoc.
  • N/A: scaffold templates (no emitted block by decision, create.js comment added); MCP (the knowledge layer bundles AGENTS.md and the skill, no tool projection changed); editor plugins (no template or language-service surface); marketing copy (no positioning claim changed); README (its webjs db migrate mention still holds).

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 vivek7405 self-assigned this Sep 10, 2026
@vivek7405
vivek7405 marked this pull request as ready for review September 10, 2026 07:59

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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).
  2. The verb lookup indexes a plain object, so webjs db constructor / webjs db toString finds an inherited function and runs its source through the shell (inline).
  3. A bare webjs db now says Unknown db subcommand "undefined" (inline).
  4. 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:

  • readDbCommands repeats the package.json read plus the webjs-block guard from readAppTasks verbatim. A readWebjsBlock(appDir, readFile) the two share would keep the next reader from copying it a third time.
  • runBeforeSteps resolves a signal-killed child as exit 0 (code ?? 0 on the exit event, where code is null when a signal ended it), and the mapped path now inherits that: a webjs db migrate that gets SIGKILLed mid-migration exits the CLI 0, which in start.before boots the server on a half-applied schema. Pre-existing for before steps, but this PR widens it to the db verbs, so worth a code ?? (signal ? 1 : 0) at some point.

Comment thread packages/cli/bin/webjs.js Outdated
Comment thread packages/cli/bin/webjs.js Outdated
Comment thread packages/cli/bin/webjs.js
Comment thread website/app/docs/database/page.ts Outdated
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.
@vivek7405
vivek7405 merged commit 4f685d1 into main Sep 10, 2026
10 checks passed
@vivek7405
vivek7405 deleted the feat/byo-orm-db-config branch September 10, 2026 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: make webjs db BYO-ORM friendly via a webjs.db config block

1 participant