Skip to content

Declare zod as a peer dependency only, and fail loudly on duplicate installs - #22

Open
brainkim wants to merge 1 commit into
mainfrom
fix/zod-peer-dependency
Open

Declare zod as a peer dependency only, and fail loudly on duplicate installs#22
brainkim wants to merge 1 commit into
mainfrom
fix/zod-peer-dependency

Conversation

@brainkim

Copy link
Copy Markdown
Member

Closes part of #20.

The bug

zod was declared in both dependencies and peerDependencies (^4.0.0 in each). That's self-contradictory: the peer contract says the host provides zod, while the dependencies entry lets a second copy be installed alongside it. The drivers in this same manifest (better-sqlite3, postgres, mysql2) already use the correct pattern — devDependency + peer — so zod is now brought in line.

@b9g/zen now has zero runtime dependencies.

Why it matters

1. Bundle size (#20). Consumers could bundle their zod and ours — the nested copy is visible in the paste on #20 (node_modules/@b9g/zen/node_modules/zod/...). The locale tree-shaking bug itself is upstream (colinhacks/zod#5561) and not ours to fix, but shipping a second copy of zod was.

2. Silent schema corruption — the serious one. ddl.ts maps columns with core instanceof z.ZodString and friends. Under a duplicate install, those checks compare against the other copy's classes and all fail, falling through to:

} else {
    // Fallback for unknown types
    sqlType = "TEXT";
}

So every column silently becomes TEXT — numbers, booleans, dates, JSON. No error, no warning. You'd get a quietly wrong schema and find out much later.

The guard

That fallback is no longer a trapdoor. A schema that advertises vendor: "zod" via Standard Schema but fails instanceof z.ZodType can only have come from a duplicate install, so it now throws a TableDefinitionError that names the actual cause.

Crucially this cannot false-positive: Zod types we genuinely don't map (ZodUnion, etc.) still pass instanceof z.ZodType and still take the TEXT fallback. Pinned by tests.

Also

Corrects the Standard Schema error message, which claimed any "Standard Schema-compliant library" would work. It won't — validation goes through ~standard, but DDL generation introspects Zod specifically. The message now says so, and points at the peer dependency.

Verification

  • bun test597 pass / 0 fail (3 new)
  • npm run test:node — passes
  • tsc --noEmit — clean
  • eslint — clean

🤖 Generated with Claude Code

…nstalls

zod was listed in both `dependencies` and `peerDependencies`. That is
self-contradictory — the peer contract says the host provides zod, while the
`dependencies` entry lets a second copy be installed alongside it. The drivers
in this package already use the correct pattern (devDependency + peer), so zod
is now brought in line: @b9g/zen has zero runtime dependencies.

Two consequences of the duplicate, both fixed here:

1. Bundle size (#20). Consumers could bundle their zod *and* ours. The zod
   locale tree-shaking bug is upstream (colinhacks/zod#5561) and not ours to
   fix, but shipping a second copy of zod was.

2. Silent schema corruption. ddl.ts maps columns with `core instanceof
   z.ZodString` and friends. Under a duplicate install those checks compare
   against the *other* copy's classes and all fail, falling through to the
   "unknown type" branch — which types every column as TEXT. No error, no
   warning: numbers, booleans, dates and JSON would all quietly become TEXT.

   The fallback is no longer a trapdoor. A schema that advertises vendor "zod"
   via Standard Schema but fails `instanceof z.ZodType` can only come from a
   duplicate install, so it now throws a TableDefinitionError naming the cause.
   Zod types we genuinely don't map (ZodUnion, etc.) still pass instanceof and
   still fall back to TEXT, so the guard cannot false-positive.

Also corrects the Standard Schema error message, which claimed any
"Standard Schema-compliant library" would work. It won't: validation goes
through ~standard, but DDL generation introspects Zod specifically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Vvtr747kUp1NobqCBeZJi
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.

1 participant