Make Insert<> respect .db.auto()/.db.inserted(), and typecheck the public API - #24
Open
brainkim wants to merge 1 commit into
Open
Make Insert<> respect .db.auto()/.db.inserted(), and typecheck the public API#24brainkim wants to merge 1 commit into
brainkim wants to merge 1 commit into
Conversation
…blic API
The README's own Quick Start did not compile:
const user = await db.insert(Users, {email: "alice@...", name: "Alice"});
// ^ Property 'id' is missing
even though `id` is `.db.primary().db.auto()`. The runtime was correct — it
generated the UUID — but TypeScript rejected the call, so the headline example
of a library whose pitch is "get typed objects" did not typecheck.
Cause: Insert<T> was just `z.input<T["schema"]>`. The .db.*() metadata lives in
a runtime side-channel that the type system cannot see, so `z.input` saw
`id: z.string()` and made it required. Both the Insert JSDoc ("respects defaults
and .db.auto() fields") and auto()'s own JSDoc ("Field becomes optional for
insert") already claimed the behaviour; only the types disagreed.
Fix: .db.auto(), .db.inserted() and .db.upserted() now brand their return type
with a phantom DBInsertOptional, exactly as .db.references() already brands with
__refTable/__refAs. Insert<T> reads the brand and makes those keys optional.
Because `db` is typed as ZodDBMethods<this>, the brand survives further chaining
in any order, so .db.auto().db.primary() and .db.primary().db.auto() both work.
Update<T> is already Partial<>, so it needed no change.
Why 597 passing tests missed this: nothing typechecks the public API. `bun test`
does not typecheck, and tsconfig included only `src/**`, so test files and the
README were never compiled. A type-level bug in the public surface could not be
caught.
So this also closes that hole: test/types/public-api.ts holds compile-time tests
(including the README Quick Start verbatim), and `npm run typecheck` now runs
against tsconfig.typecheck.json which includes them. Verified by mutation:
reverting the brand on auto() makes typecheck fail with the original error.
test/*.test.ts is deliberately still excluded — those files have pre-existing
type errors, and folding them in would make the check useless.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Vvtr747kUp1NobqCBeZJi
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.
The bug
The README's own Quick Start does not compile:
The runtime is correct — it generates the UUID. Only TypeScript rejects it. So the headline example of a library whose pitch is "get typed objects" did not typecheck. Same example appears in the getting-started guide and on zendb.org.
Cause
Insert<T>was justz.input<T["schema"]>. The.db.*()metadata lives in a runtime side-channel the type system can't see, soz.inputsawid: z.string()→ required.Both JSDoc comments already claimed the correct behaviour:
Insert— "Infer the insert type (respects defaults and .db.auto() fields)"auto()— "Field becomes optional for insert."Only the types disagreed.
Fix
.db.auto(),.db.inserted()and.db.upserted()now brand their return type with a phantomDBInsertOptional— exactly the mechanism.db.references()already uses with__refTable/__refAs.Insert<T>reads the brand and makes those keys optional.Because
dbis typedZodDBMethods<this>, the brand survives chaining in any order —.db.auto().db.primary()and.db.primary().db.auto()both work. Pinned by a test.Update<T>is alreadyPartial<>, so it needed no change.Why 597 passing tests missed it
Nothing typechecks the public API.
bun testdoesn't typecheck, andtsconfig.jsonincluded onlysrc/**— so test files and the README were never compiled. A type-level bug in the public surface could not be caught.This PR closes that hole:
test/types/public-api.ts— compile-time tests, including the README Quick Start verbatim, plus@ts-expect-errorassertions that required fields and unknown fields are still rejected.npm run typechecknow runstsconfig.typecheck.json, which includes them.test/*.test.tsis deliberately still excluded — those files have pre-existing type errors, and folding them in would make the check useless.Verification
Mutation-tested the guard — reverting the brand on
auto()makes the new check fail with the original error, confirming it isn't passing vacuously:npm run typecheck— clean (and fails on regression, as above)bun test— 594 pass / 0 failnpm run test:node— passes🤖 Generated with Claude Code