auth: let apps declare model-level indexes on derived auth lists - #1017
Conversation
authPlugin()'s per-model config (user/session/account/verification/rateLimit) now accepts `indexes`, threaded through to the derived list's `db.indexes`. An entry covering a column the stack already derives an index for suppresses that derived index for that column and emits only the app's entry (ADR-0035), making it possible to adopt a live constraint's real name or extend a derived column into a composite index. Also fixes the Prisma generator to resolve createdAt/updatedAt in db.indexes against a list's auto-timestamp columns, not only an explicitly declared field, which the verification model's composite-index use case depends on. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PLqppkKWzoMZvy3aCDK6DY
|
Deployment failed for project stack-docs with the following error: Learn More: https://vercel.com/open-saas?upgradeToPro=build-rate-limit |
🦋 Changeset detectedLatest commit: 9fd127f The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e0e222ec4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
borisno2
left a comment
There was a problem hiding this comment.
Single-pass review at high effort (Agent tool unavailable in this session, so no multi-agent fan-out / subagent verify pass ran — this is one careful pass through the diff, cross-file callers, tests, ADR-0035, and CLAUDE.md conventions).
Verified: types (packages/core/src/config/types.ts, packages/auth/src/config/types.ts), the deriveAuthLists suppression logic (packages/auth/src/config/derive-auth-lists.ts), the generator's createdAt/updatedAt auto-timestamp resolution (packages/cli/src/generator/prisma.ts), and all associated tests all typecheck, lint clean, and pass (pnpm vitest run in both packages/auth and packages/cli, plus tsc --noEmit in both packages). The implementation matches ADR-0035 exactly, and the suppress-per-column / plugin-tables-out-of-scope claims in the new docblocks and code comments all check out against the code.
No correctness bugs found in the code itself. One finding: the general db.indexes reference doc (docs/content/reference/config-api.md) wasn't updated to reflect the new createdAt/updatedAt-via-auto-timestamps behavior this PR ships, so it now reads as stale/incomplete relative to the shipped generator behavior — flagged inline on docs/content/how-to/authentication.md since config-api.md itself has no diff lines to anchor a comment to.
Generated by Claude Code
Update the core db.indexes reference to reflect that an entry can now name createdAt/updatedAt when a list's auto-timestamps are enabled for that column, even with no explicitly declared field — per code review on #1017. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PLqppkKWzoMZvy3aCDK6DY
A relationship field's own generator defaults its FK index to indexed whenever isIndexed is omitted, unlike a scalar field. Suppressing a relationship field's derived index therefore has to set isIndexed: false explicitly, or the derived @@index survives and collides with the app's own db.indexes entry. Caught by review on #1017. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PLqppkKWzoMZvy3aCDK6DY
…hl7ali # Conflicts: # packages/auth/src/config/derive-auth-lists.ts
Coverage Report for Core Package Coverage (./packages/core)
File CoverageNo changed files found. |
Coverage Report for UI Package Coverage (./packages/ui)
File CoverageNo changed files found. |
Coverage Report for CLI Package Coverage (./packages/cli)
File Coverage
|
||||||||||||||||||||||||||||||||||||||
Coverage Report for Auth Package Coverage (./packages/auth)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for Storage Package Coverage (./packages/storage)
File CoverageNo changed files found. |
Coverage Report for RAG Package Coverage (./packages/rag)
File CoverageNo changed files found. |
Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)
File CoverageNo changed files found. |
Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)
File CoverageNo changed files found. |
Summary
authPlugin()'s per-model config (user/session/account/verification/rateLimit) now accepts anindexesoption, in the same shape as a list's owndb.indexes(core'sListIndex). Entries are threaded through to the derived list'sdb.indexesat generate time.User.email's derived@unique), the derived index is suppressed for that column only and the app's entry is emitted instead — the application's declaration wins over a derived default (ADR-0035). This makes two previously-unreachable cases expressible:{ fields: ['email'], unique: true, name: 'user_email_key' }) — closes auth: derived auth lists can't adopt a live constraint name, soUser.email/Session.tokencan diff dirty #921.identifierresend-cooldown index onVerification, spanningidentifier+createdAt).db.indexescan now referencecreatedAt/updatedAteven when the list has no explicitly declared field for them and relies ondb.timestampsfor the auto-injected columns — required for theVerificationcomposite-index case above, and generically useful for any list usingdb.timestamps: true.modelName(e.g.AuthVerification).betterAuthPlugins) and adopting better-auth's own 1.7 table-levelindexesoption are explicitly out of scope (tracked separately as auth: upgrade better-auth to 1.7.1 — new requiredaccount.issuercolumn, new@@unique([issuer, accountId]), and a stale^1.3.29peer floor #986).Test plan
packages/auth/tests/generated-fk-shape.test.tscover: composite index + suppression, named-unique adoption onUser.email, per-column suppression (other derived indexes untouched), field-key resolution through a model's column map, the auth: derived auth lists can't adopt a live constraint name, soUser.email/Session.tokencan diff dirty #921 round-trip (User.email+Session.token), unknown-field errors (plain and undermodelNameremapping), and unchanged output when noindexesare configured.packages/cli/src/generator/prisma.test.tscover the genericcreatedAt/updatedAtauto-timestamp resolution indb.indexes, independent of auth.packages/auth/tests/config.test.tsassertions for the newindexes: []field on normalized model config.pnpm lint,pnpm manypkg fix,pnpm formatall pass.core/cli/authpackage test suites pass (1175 + 381 + 279 tests).@opensaas/stack-authminor,@opensaas/stack-climinor,@opensaas/stack-corepatch for the TSDoc clarification).docs/content/reference/auth.md) and how-to guide (docs/content/how-to/authentication.md) updated, cross-linked to core'sdb.indexesreference.Closes #985
🤖 Generated with Claude Code
https://claude.ai/code/session_01PLqppkKWzoMZvy3aCDK6DY
Generated by Claude Code