Summary
await <drizzle MySqlSelectBase instance> returns the builder object itself instead of resolving to the query result. The instance has .then inherited from drizzle-orm's QueryPromise base class (via 4+ levels of inheritance), but under Perry the property lookup instance.then returns undefined — so await doesn't see a thenable and just unwraps to the original value.
.execute() on the same instance resolves correctly and is the in-fixture workaround. Found while landing the drizzle-mysql fixture for #489.
Repro
In tests/release/packages/drizzle-mysql/entry.ts, replace any line of the form:
const all = await db.select().from(users).execute();
with the idiomatic drizzle form:
const all = await db.select().from(users);
Perry prints count=undefined then SIGBUS'es; Bun (bun --conditions=perry run entry.ts) and Node both print count=2. Verified by inserting diagnostic logs:
sel: object MySqlSelectBuilder
from: object MySqlSelectBase hasThen= undefined hasExecute= function
So _from.execute resolves to a function but _from.then (inherited from QueryPromise through TypedQueryBuilder → MySqlSelectQueryBuilderBase → MySqlSelectBase) resolves to undefined.
What I tried to minimize (and couldn't)
These all work correctly under Perry — none reproduces the .then === undefined shape:
- 3+ level user-class inheritance with
.then defined on the root.
- Same, in a
compilePackages .js package.
- Same with
static [entityKind] = "..." (computed symbol field) on every class.
- 5-level chain
Root → TQB → QBB → SQB → SBase with .then only on Root, static [entityKind] on every level.
Something more specific in drizzle's class graph (likely mixins, decorators, or constructor-injected fields touching then) is the trigger. The drizzle case is reproducible by reverting the .execute() calls in the fixture entry, so this issue's repro lives there.
Impact
Affects every idiomatic drizzle-orm query: await db.select().from(x), await db.update(x).set(...), etc. Currently worked around with explicit .execute() in the drizzle-mysql fixture; fixing this lets users write idiomatic upstream drizzle code unchanged. Likely benefits other thenable libraries with deep mixin-style inheritance.
Related
Summary
await <drizzle MySqlSelectBase instance>returns the builder object itself instead of resolving to the query result. The instance has.theninherited fromdrizzle-orm'sQueryPromisebase class (via 4+ levels of inheritance), but under Perry the property lookupinstance.thenreturnsundefined— soawaitdoesn't see a thenable and just unwraps to the original value..execute()on the same instance resolves correctly and is the in-fixture workaround. Found while landing thedrizzle-mysqlfixture for #489.Repro
In
tests/release/packages/drizzle-mysql/entry.ts, replace any line of the form:with the idiomatic drizzle form:
Perry prints
count=undefinedthen SIGBUS'es; Bun (bun --conditions=perry run entry.ts) and Node both printcount=2. Verified by inserting diagnostic logs:So
_from.executeresolves to a function but_from.then(inherited fromQueryPromisethroughTypedQueryBuilder → MySqlSelectQueryBuilderBase → MySqlSelectBase) resolves toundefined.What I tried to minimize (and couldn't)
These all work correctly under Perry — none reproduces the
.then === undefinedshape:.thendefined on the root.compilePackages.jspackage.static [entityKind] = "..."(computed symbol field) on every class.Root → TQB → QBB → SQB → SBasewith.thenonly onRoot,static [entityKind]on every level.Something more specific in drizzle's class graph (likely mixins, decorators, or constructor-injected fields touching
then) is the trigger. The drizzle case is reproducible by reverting the.execute()calls in the fixture entry, so this issue's repro lives there.Impact
Affects every idiomatic drizzle-orm query:
await db.select().from(x),await db.update(x).set(...), etc. Currently worked around with explicit.execute()in thedrizzle-mysqlfixture; fixing this lets users write idiomatic upstream drizzle code unchanged. Likely benefits other thenable libraries with deep mixin-style inheritance.Related
.execute()workaround).await thenablereturns the thenable itself instead of calling.then(resolve, reject)#586 —await thenable returns the thenable itself(closed; this is a deeper / drizzle-specific recurrence).