Context
Found during review of #1543 (test coverage for #1241's codecFor/mapped-increment gap).
The gap
packages/auth/src/adapter/index.ts's resolveField/isBigInt/toColumn/toFieldKey all resolve a name via better-auth's getDefaultFieldName (from @better-auth/core/db/adapter), which tries a direct hit first against the model's own field keys before falling back to a reverse lookup by fieldName:
let f = schema[model]?.fields[field];
if (!f) {
const result = Object.entries(schema[model]!.fields!).find(([_, f]) => f.fieldName === field);
...
}
This makes the resolution ambiguous when an app's authPlugin({ <model>: { fields: {...} } }) remap sends one field's column to a string that happens to equal a different field's own (unmapped) default key in the same model — e.g.:
authPlugin({
rateLimit: {
enabled: true,
storage: 'database',
// lastRequest's physical column is now "count" — which collides with
// the rateLimit model's own unmapped `count` field key.
fields: { count: 'total_count', lastRequest: 'count' },
},
})
Here, the physical column "count" hits schema[model].fields['count'] directly and resolves to the original count field instead of lastRequest. resolveField/isBigInt then check count's (non-bigint) attributes instead of lastRequest's, so incrementOne/outward silently skip the BigInt widening/narrowing a real int8 column needs — wrong values or precision loss on a remapped bigint column, with no error raised.
What to do
Pick one:
- (a) Add a generate-time check in
packages/auth/src/config/derive-auth-lists.ts that rejects a fields remap whose target string equals another field's own default key in the same model, naming the model and the colliding fields.
- (b) In
packages/auth/src/adapter/index.ts, avoid re-deriving the field key via getDefaultFieldName's ambiguous direct-hit-first logic — carry the field key resolved once (e.g. from sqlWhere's own resolution path) forward explicitly instead of re-resolving from a bare column string.
Acceptance criteria
Context
Found during review of #1543 (test coverage for #1241's
codecFor/mapped-increment gap).The gap
packages/auth/src/adapter/index.ts'sresolveField/isBigInt/toColumn/toFieldKeyall resolve a name via better-auth'sgetDefaultFieldName(from@better-auth/core/db/adapter), which tries a direct hit first against the model's own field keys before falling back to a reverse lookup byfieldName:This makes the resolution ambiguous when an app's
authPlugin({ <model>: { fields: {...} } })remap sends one field's column to a string that happens to equal a different field's own (unmapped) default key in the same model — e.g.:Here, the physical column
"count"hitsschema[model].fields['count']directly and resolves to the originalcountfield instead oflastRequest.resolveField/isBigIntthen checkcount's (non-bigint) attributes instead oflastRequest's, soincrementOne/outwardsilently skip theBigIntwidening/narrowing a realint8column needs — wrong values or precision loss on a remapped bigint column, with no error raised.What to do
Pick one:
packages/auth/src/config/derive-auth-lists.tsthat rejects afieldsremap whose target string equals another field's own default key in the same model, naming the model and the colliding fields.packages/auth/src/adapter/index.ts, avoid re-deriving the field key viagetDefaultFieldName's ambiguous direct-hit-first logic — carry the field key resolved once (e.g. fromsqlWhere's own resolution path) forward explicitly instead of re-resolving from a bare column string.Acceptance criteria
fieldsremap that creates this kind of collision either fails fast at generate/config time with a clear error naming the two fields, or resolves correctly end-to-endincrementOne(or another read/write path) against arateLimit-like model configured with a colliding remap, and asserts the correct field's attributes (in particularbigint) are used