Skip to content

authPlugin: adoptBetterAuthTables() recipe + adoption docs (app User vs Auth identity)#509

Merged
borisno2 merged 1 commit into
mainfrom
claude/issue-483-auth-identity-adoption
Jun 3, 2026
Merged

authPlugin: adoptBetterAuthTables() recipe + adoption docs (app User vs Auth identity)#509
borisno2 merged 1 commit into
mainfrom
claude/issue-483-auth-identity-adoption

Conversation

@borisno2

@borisno2 borisno2 commented Jun 3, 2026

Copy link
Copy Markdown
Member

Implements #483
Part of #480

What this adds (D3 of PRD #480)

A convenience "adopt existing better-auth tables" recipe plus documentation that ties together the keys/field derivation (#481) and schema placement (#482) into a one-stop adoption path.

  • adoptBetterAuthTables() recipe (packages/auth/src/config/adopt-better-auth-tables.ts): a pure function returning the AuthConfig adoption fragment — the plugin-level schema plus each model's modelName (and optional column fields maps) — preset to the conventions of a standard separate-schema better-auth install (AuthUser/AuthSession/AuthAccount/AuthVerification in an auth schema). Spread it into authPlugin alongside the rest of your config. Options: schema, modelNamePrefix, per-model fields.
  • Exported from the package root (adoptBetterAuthTables, AdoptBetterAuthTablesOptions, AdoptBetterAuthTablesConfig).
  • App User ≠ Auth identity: because the recipe's derived user key is AuthUser (not User), the plugin only ever adds/extends its derived keys — an app's own domain User is never extended or overwritten. Linking the app User to the Auth identity is documented as the application's concern (a relationship({ ref: 'AuthUser' }) the app declares).

Documentation

  • Authentication guide: new "Adopting an Existing better-auth Installation" section (app User vs Auth identity, the recipe + customisation, Schema-parity / clean-diff check, and linking the two).
  • packages/auth/CLAUDE.md + README.md: adoption-recipe subsection.
  • Migration guides (migrating-from-keystone.md, migration.md): reference the adoption path (no destructive auth migration).

Tests (packages/auth/tests/adopt-better-auth-tables.test.ts)

  • Recipe defaults and schema/modelNamePrefix/fields options.
  • Composition: spreads into authPlugin alongside the rest of the auth config.
  • Clean-diff adoption assertion: every Auth list lands in the auth schema with @@map + @@schema (auto-timestamps preserved) while the app's separate public.User stays untouched; the datasource lists both schemas.
  • Field column maps flow through to the derived field-level @map / FK @map.

Verification

  • pnpm build — pass
  • packages/auth tests — 133 passed (8 files); packages/core — 562 passed; packages/cli — 266 passed
  • pnpm lint — 0 errors (4 pre-existing warnings in untouched files)
  • pnpm manypkg check — pass
  • pnpm format:check — pass
  • docs link-check — pass

Changeset: minor for @opensaas/stack-auth. No claude-plugins/* changes, so no plugin-version bump.

https://claude.ai/code/session_01ULd2HCT8dUve9aa9gKi6sX


Generated by Claude Code

…ser vs Auth identity)

Adds a convenience "adopt existing better-auth tables" recipe that presets the
plugin-level schema + per-model modelName/fields knobs to match a pre-existing
separate-schema better-auth install, so a migrator doesn't reconstruct the auth
config from scratch. Combined with the keys/field derivation (#481) and schema
placement (#482), the derived Auth lists diff clean (Schema parity) against the
live database — no destructive auth migration. The app's own domain User is left
untouched; linking it to the Auth identity is documented as the app's concern.

- packages/auth/src/config/adopt-better-auth-tables.ts: pure recipe returning the
  AuthConfig adoption fragment (schema, modelNamePrefix, per-model field maps)
- export adoptBetterAuthTables + its option/config types from the package root
- tests: recipe defaults/options, composition with authPlugin, and a clean-diff
  adoption assertion (Auth lists land in the auth schema with @@map/@@Schema while
  the app's separate User stays in public, untouched)
- docs: authentication guide section (app User vs Auth identity + linking + recipe),
  packages/auth CLAUDE.md + README, and migration-guide references
- changeset: minor for @opensaas/stack-auth

Implements #483
Part of #480

https://claude.ai/code/session_01ULd2HCT8dUve9aa9gKi6sX
@vercel

vercel Bot commented Jun 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-docs Ready Ready Preview, Comment Jun 3, 2026 9:53pm

@changeset-bot

changeset-bot Bot commented Jun 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 80f0ebf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@opensaas/stack-auth Minor
@opensaas/stack-cli Minor
@opensaas/stack-core Minor
@opensaas/stack-rag Minor
@opensaas/stack-storage-s3 Minor
@opensaas/stack-storage-vercel Minor
@opensaas/stack-storage Minor
@opensaas/stack-tiptap Minor
@opensaas/stack-ui Minor

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

@borisno2 borisno2 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — PR #509: adoptBetterAuthTables() recipe (D3 of #480, implements #483)

Verdict: APPROVE (posted as a Comment since the PR was authored by an integration identity). This is a clean, well-scoped, well-documented change. The recipe is a pure function that only sets adoption knobs, it spreads cleanly into authPlugin, and the clean-diff guarantee is genuinely asserted by the tests. One minor test-naming/coverage nit and a couple of doc/optional suggestions below — none blocking.

I reviewed the diff against the surrounding (merged) source: config/types.ts (AuthConfig/AuthModelConfig), config/index.ts (normalizeAuthConfig), config/derive-auth-lists.ts, and config/plugin.ts.

Correctness — verified against the real pipeline

  • Returns a correct AuthConfig fragment. AdoptBetterAuthTablesConfig = Pick<AuthConfig, 'schema' | 'user' | 'session' | 'account' | 'verification'> is concrete and spreads cleanly. buildModel() returns AuthModelConfig ({ modelName, fields? }); assigning it to the session slot (typed SessionConfig & AuthModelConfig) is sound because every SessionConfig member is optional, so this composes without a cast. Confirmed.
  • Only adoption knobs, no side effects. The function is pure: it sets schema + per-model modelName (+ optional fields) and nothing else. Providers, sessionFields, extendUserList, etc. all remain the developer's to add — exactly as documented.
  • Plugin-level schema actually reaches the generator. normalizeModelConfig(..., defaultSchema) (in config/index.ts) propagates the plugin-level schema onto every model.schema, and plugin.ts#beforeGenerate collects model.schema (not the plugin-level field) into the datasource schemas. So schema: 'auth' correctly lands on all four lists. Confirmed end-to-end.
  • Clean-diff / "app User untouched" guarantee holds and is asserted. Because the derived user key is AuthUser (not User), plugin.ts#init's add-vs-extend only extends a list that shares the derived key — the app's public.User is never touched. The test lands every Auth list in the auth schema ... and leaves the app User untouched asserts the real shapes:
    • each Auth list db is { timestamps: true, map: 'Auth…', schema: 'auth' } (ADR-0004 auto-timestamps preserved + @@map + @@schema);
    • the app User keeps subjectId, gains none of email/emailVerified/sessions, and stays in public;
    • db.schemas is ['public', 'auth'].
      This is a genuine assertion of the adoption guarantee, not a rubber-stamp.
  • App-User ≠ Auth-identity separation is correct in code (separate keys) and clearly documented (auth guide section + linking subsection: linking is the app's concern via relationship({ ref: 'AuthUser' })).
  • Field column maps flow through to field-level @map and the FK @map — asserted by the name: 'full_name' / session.userId: 'user_id' test against fields.name.db?.map and fields.user.db?.foreignKey.
  • No any / no casts in the new public surface. adopt-better-auth-tables.ts is fully typed; option and return types are concrete. (The pre-existing betterAuthPlugins?: any[] in AuthConfig is out of scope here.)
  • Docs are accurate and non-destructive. The auth-guide section, README, CLAUDE.md, and both migration guides consistently frame this as "modelled for runtime/types, no destructive auth migration" with a Schema-parity diff check — no false claims of a migration.

Minor (non-blocking)

  1. Test name vs. assertion mismatch — 'public' schema path is under-covered. adopt-better-auth-tables.test.ts:

    it('omits the schema when explicitly set to public', ...)
    only asserts fragment.schema is 'public' — it does not assert anything is "omitted." This matters because the behavior is the opposite of what the name implies: passing schema: 'public' sets model.schema = 'public' on all four models, so beforeGenerate emits schemas: ['public'] and @@schema("public") on every Auth list (it is treated as an explicit single-schema install, not as "no schema"). That is arguably the intended behavior for "adopt a public-schema better-auth install," but the test name reads as a no-op-greenfield claim it doesn't verify. Suggest either (a) rename to keeps the schema when explicitly set to public, or (b) add a generationConfig() assertion showing the actual emitted db.schemas/db.schema for the schema: 'public' case so the behavior is pinned down. The recipe's schema JSDoc ("Pass 'public' … for an install that is not on a separate schema") is consistent with (a)/the explicit-schema behavior.

  2. Greenfield/schema: undefined is not directly exercised here. Not required (the underlying schema-placement behavior is tested in plugin-schema-placement.test.ts, and the recipe always defaults schema: 'auth'), so this is just a note, not a request.

  3. modelNamePrefix: '' escape hatch. The JSDoc rightly warns that an empty prefix yields User/Session/... which would collide-and-extend an app User. Good that it's documented; no code change needed. Optional: a one-line test asserting adoptBetterAuthTables({ modelNamePrefix: '' }).user.modelName === 'User' would lock the documented escape-hatch behavior.

Process

  • Changeset present and correctly scoped (@opensaas/stack-auth, minor) with a usage example. No claude-plugins/* changes, so no plugin-version bump needed — correct.

Nice work. The single thing worth tidying before merge is the test-name/coverage point (#1); everything else is optional polish.


Generated by Claude Code

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🔵 Lines 89.49% (🎯 65%) 571 / 638
🔵 Statements 88.65% (🎯 65%) 578 / 652
🔵 Functions 98.07% (🎯 62%) 102 / 104
🔵 Branches 76.89% (🎯 50%) 376 / 489
File CoverageNo changed files found.
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 76.03% 92 / 121
🔵 Statements 75.39% 95 / 126
🔵 Functions 75.6% 31 / 41
🔵 Branches 65.78% 75 / 114
File CoverageNo changed files found.
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 72.61% 1233 / 1698
🔵 Statements 72.42% 1279 / 1766
🔵 Functions 81.04% 171 / 211
🔵 Branches 60.32% 514 / 852
File CoverageNo changed files found.
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 74.64% 159 / 213
🔵 Statements 69.74% 166 / 238
🔵 Functions 83.11% 64 / 77
🔵 Branches 70.67% 94 / 133
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/auth/src/config/adopt-better-auth-tables.ts 100% 100% 100% 100%
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 42.44% 73 / 172
🔵 Statements 42.77% 74 / 173
🔵 Functions 42.85% 15 / 35
🔵 Branches 40.13% 61 / 152
File CoverageNo changed files found.
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 47.97% 355 / 740
🔵 Statements 48.14% 377 / 783
🔵 Functions 54.26% 70 / 129
🔵 Branches 42.55% 180 / 423
File CoverageNo changed files found.
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 40 / 40
🔵 Statements 100% 40 / 40
🔵 Functions 100% 9 / 9
🔵 Branches 100% 19 / 19
File CoverageNo changed files found.
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 38 / 38
🔵 Statements 100% 38 / 38
🔵 Functions 100% 8 / 8
🔵 Branches 100% 22 / 22
File CoverageNo changed files found.
Generated in workflow #1115 for commit 80f0ebf by the Vitest Coverage Report Action

@borisno2 borisno2 merged commit fdc48f8 into main Jun 3, 2026
6 checks passed
@borisno2 borisno2 deleted the claude/issue-483-auth-identity-adoption branch June 3, 2026 22:07
@github-actions github-actions Bot mentioned this pull request Jun 3, 2026
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.

2 participants