Skip to content

feat(plugin-auth): use better-auth modelName/fields mapping for snake_case compatibility - #897

Merged
hotlong merged 5 commits into
mainfrom
copilot/enhancement-better-auth-model-mapping
Mar 10, 2026
Merged

feat(plugin-auth): use better-auth modelName/fields mapping for snake_case compatibility#897
hotlong merged 5 commits into
mainfrom
copilot/enhancement-better-auth-model-mapping

Conversation

CopilotAI commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

better-auth sends camelCase field names (emailVerified, userId, createdAt) to the adapter, but ObjectStack's protocol layer expects snake_case (email_verified, user_id, created_at). This causes query failures on sign-in, sign-up, and account lookup.

Approach

Leverage better-auth's official modelName / fields schema customization API combined with createAdapterFactory from better-auth/adapters. The factory wrapper handles all camelCase ↔ snake_case transformation automatically — no manual conversion in the adapter.

Changes

  • New auth-schema-config.ts — Mapping constants for all 4 core models (AUTH_USER_CONFIG, AUTH_SESSION_CONFIG, AUTH_ACCOUNT_CONFIG, AUTH_VERIFICATION_CONFIG) plus 7 plugin table schemas (AUTH_ORGANIZATION_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_ORG_SESSION_FIELDS) with helper builders buildOrganizationPluginSchema() and buildTwoFactorPluginSchema()
  • objectql-adapter.ts — New createObjectQLAdapterFactory() using createAdapterFactory; legacy createObjectQLAdapter() retained for backward compat
  • auth-manager.tscreateAuthInstance() now passes model/field config to betterAuth(); createDatabaseConfig() uses the new factory; new buildPluginList() conditionally registers organization, twoFactor, and magicLink plugins based on AuthPluginConfig flags, each with its own snake_case schema mapping
  • Tests — 53 tests covering core schema config validation, plugin schema mappings, factory creation, config propagation, and plugin registration verification (all passing)
  • Docs — README, authentication guide, ROADMAP updated

Plugin Table Coverage

In addition to the 4 core models, all plugin tables are mapped (per issue requirement "需要考虑所有插件用到的表,包括org等"):

PluginTablesKey Field Mappings
Organizationsys_organization, sys_member, sys_invitation, sys_team, sys_team_memberorganizationIdorganization_id, inviterIdinviter_id, activeOrganizationIdactive_organization_id, etc.
Two-Factorsys_two_factor + user field extensionbackupCodesbackup_codes, twoFactorEnabledtwo_factor_enabled
Magic Link(reuses verification table)No extra mapping needed

Example

// auth-schema-config.tsexportconstAUTH_SESSION_CONFIG={modelName: SystemObjectName.SESSION,// 'sys_session'fields: {userId: 'user_id',expiresAt: 'expires_at',ipAddress: 'ip_address',userAgent: 'user_agent',createdAt: 'created_at',updatedAt: 'updated_at',},}asconst;// auth-manager.ts — applied to betterAuth() configconstconfig: BetterAuthOptions={database: createObjectQLAdapterFactory(dataEngine),session: { ...AUTH_SESSION_CONFIG,expiresIn: 604800},user: { ...AUTH_USER_CONFIG},account: { ...AUTH_ACCOUNT_CONFIG},verification: { ...AUTH_VERIFICATION_CONFIG},plugins: [organization({schema: buildOrganizationPluginSchema()}),twoFactor({schema: buildTwoFactorPluginSchema()}),],};

The factory reads the schema built by getAuthTables(options) and applies transformInput/transformOutput/transformWhereClause before data reaches the ObjectQL adapter — single-point mapping, no runtime conversion in adapter code.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Enhancement] Use better-auth modelName and fields mapping for ObjectQL adapter (snake_case compatibility)</issue_title>
<issue_description>## Context
Currently, the ObjectQL adapter for better-auth passes field names as-is from better-auth's camelCase queries to ObjectStack's snake_case database schema. This causes query failures (e.g., login, account lookup) when field names don't match (see Bug objectstack-ai/spec#893).

Proposal

Implement Approach B: declare model/field mapping in the better-auth configuration using its official modelName and fields options for each core auth model.
需要考虑所有插件用到的表,包括org等

Details

Instead of runtime camelCase ↔ snake_case conversion in the adapter, leverage better-auth's documented schema customization:

exportconstauth=betterAuth({user: {modelName: "sys_user",fields: {emailVerified: "email_verified",createdAt: "created_at",updatedAt: "updated_at",},},session: {modelName: "sys_session",fields: {userId: "user_id",expiresAt: "expires_at",ipAddress: "ip_address",userAgent: "user_agent",createdAt: "created_at",updatedAt: "updated_at",},},account: {modelName: "sys_account",fields: {userId: "user_id",providerId: "provider_id",accountId: "account_id",accessToken: "access_token",refreshToken: "refresh_token",idToken: "id_token",accessTokenExpiresAt: "access_token_expires_at",refreshTokenExpiresAt: "refresh_token_expires_at",createdAt: "created_at",updatedAt: "updated_at",},},verification: {modelName: "sys_verification",fields: {expiresAt: "expires_at",createdAt: "created_at",updatedAt: "updated_at",},},});

This ensures better-auth always uses the correct field names for data persistence and querying via ObjectQL.

Benefits

  • Single-point schema mapping, no runtime conversion
  • Cleaner adapter logic
  • Official/documented API usage
  • Reduces risk of edge-case bugs

Tasks

  • Add modelName and fields for all core models in better-auth config
  • Update auth-manager.ts to construct config accordingly
  • Review/adjust adapter and tests as needed
  • Validate sign-in, sign-up, and all account workflows
  • Document the mapping approach in README

相关问题: #893
</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercelBot commented Mar 10, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
objectstack-playReadyReadyPreview, CommentMar 10, 2026 3:36am
specReadyReadyPreview, CommentMar 10, 2026 3:36am

Request Review

…_case compatibility
- Add auth-schema-config.ts with model/field mapping constants for all 4 core auth models
- Refactor objectql-adapter.ts to use createAdapterFactory from better-auth/adapters
- Update auth-manager.ts to pass user/session/account/verification config with modelName and fields
- Add tests for schema config, factory adapter, and config verification
- Update README and authentication guide documentation
- Keep legacy createObjectQLAdapter for backward compatibility
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… ROADMAP
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
CopilotAI changed the title [WIP] Enhance ObjectQL adapter for better-auth field mappingfeat(plugin-auth): use better-auth modelName/fields mapping for snake_case compatibilityMar 10, 2026
@hotlong
hotlong marked this pull request as ready for review March 10, 2026 03:15
CopilotAI review requested due to automatic review settings March 10, 2026 03:15
@hotlong

Copy link
Copy Markdown
Contributor

@copilot 仔细评估是否完成了任务中要求的所有开放

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates @objectstack/plugin-auth to use better-auth’s official modelName / fields mapping along with createAdapterFactory, enabling automatic camelCase ↔ snake_case transformations so ObjectQL queries match ObjectStack’s snake_case auth objects.

Changes:

  • Added AUTH_*_CONFIG constants declaring model/field mappings for user/session/account/verification.
  • Introduced createObjectQLAdapterFactory() using createAdapterFactory and updated AuthManager to pass mapping config into betterAuth().
  • Updated tests and documentation to reflect the new mapping approach while retaining the legacy createObjectQLAdapter() for backward compatibility.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
packages/plugins/plugin-auth/src/objectql-adapter.tsAdds factory-based adapter using createAdapterFactory; keeps legacy adapter.
packages/plugins/plugin-auth/src/objectql-adapter.test.tsAdds assertions for mapping constants and minimal factory test.
packages/plugins/plugin-auth/src/index.tsExports new schema-config module from the package entrypoint.
packages/plugins/plugin-auth/src/auth-schema-config.tsIntroduces AUTH_*_CONFIG constants for modelName/fields mapping.
packages/plugins/plugin-auth/src/auth-manager.tsApplies mapping constants to betterAuth() config and uses new adapter factory.
packages/plugins/plugin-auth/src/auth-manager.test.tsUpdates tests to validate mapping propagation into betterAuth() config.
packages/plugins/plugin-auth/README.mdDocuments model/field mapping approach and adapter factory usage.
content/docs/guides/authentication.mdxUpdates guide to explain modelName/fields mapping and adapter factory behavior.
ROADMAP.mdNotes enhancement in migration section for the new mapping approach.

Comment on lines +233 to +246
```typescript
// Schema mapping constants (auth-schema-config.ts)
import {
AUTH_USER_CONFIG,
AUTH_SESSION_CONFIG,
AUTH_ACCOUNT_CONFIG,
AUTH_VERIFICATION_CONFIG,
} from '@objectstack/plugin-auth';

// Applied to the betterAuth() config:
const auth = betterAuth({
database: (options) => ({
id: 'objectql',
...adapter,
transaction: async (cb) => cb(adapter),
}),
// ... other config
database: createObjectQLAdapterFactory(dataEngine),
user: { ...AUTH_USER_CONFIG },
session: { ...AUTH_SESSION_CONFIG, expiresIn: 604800 },

CopilotAIMar 10, 2026

Copy link

Choose a reason for hiding this comment

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

In the README example, createObjectQLAdapterFactory(dataEngine) is used but not imported. This snippet won’t compile as-is; include createObjectQLAdapterFactory in the import list (or show a separate import) so readers can copy/paste successfully.

Copilot uses AI. Check for mistakes.
Comment on lines +586 to +587
better-auth internally uses camelCase model and field names (`user`, `emailVerified`, `userId`).
The plugin bridges this gap using better-auth's official **`modelName` / `fields` schema customisation API**:

CopilotAIMar 10, 2026

Copy link

Choose a reason for hiding this comment

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

This sentence says better-auth uses “camelCase model and field names”, but the model names shown (user, session, etc.) are not camelCase—only the field names are. Adjust wording to avoid confusion (e.g., “better-auth uses fixed model names like user and camelCase field names like emailVerified / userId”).

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +114
describe('createObjectQLAdapterFactory', () => {
it('should return a function (adapter factory)', () => {
const mockEngine = {
insert: vi.fn(),
findOne: vi.fn(),
find: vi.fn(),
count: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
} as unknown as IDataEngine;

const factory = createObjectQLAdapterFactory(mockEngine);
expect(typeof factory).toBe('function');
});
});

CopilotAIMar 10, 2026

Copy link

Choose a reason for hiding this comment

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

createObjectQLAdapterFactory is only tested for returning a function. Since this PR relies on createAdapterFactory for correct modelName/fields transformations, it would be valuable to add a test that invokes the returned factory with a minimal BetterAuthOptions-like object and asserts that adapter calls into dataEngine with the expected transformed model name and snake_case fields/where-clause keys.

Copilot uses AI. Check for mistakes.
CopilotAIand others added 2 commits March 10, 2026 03:28
…agicLink plugins
- Add AUTH_ORGANIZATION_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_INVITATION_SCHEMA,
AUTH_TEAM_SCHEMA, AUTH_TEAM_MEMBER_SCHEMA for organization plugin tables
- Add AUTH_TWO_FACTOR_SCHEMA and AUTH_TWO_FACTOR_USER_FIELDS for 2FA plugin
- Add AUTH_ORG_SESSION_FIELDS for org plugin session extensions
- Add buildOrganizationPluginSchema() and buildTwoFactorPluginSchema() helpers
- Wire up plugin registration in AuthManager.buildPluginList() based on
AuthPluginConfig flags (organization, twoFactor, magicLink)
- Add 14 new tests for plugin schema configs and plugin registration
- Update README documentation with plugin table mappings
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

CopilotAI commented Mar 10, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 仔细评估是否完成了任务中要求的所有开放

Evaluated all open tasks from the issue. The original implementation only covered the 4 core models (user, session, account, verification). Per the issue requirement "需要考虑所有插件用到的表,包括org等", commit 4930f6e adds:

Organization plugin tables:

  • sys_organization (organization, member, invitation, team, teamMember) — all with snake_case field mappings
  • Session extension fields (active_organization_id, active_team_id)

Two-Factor plugin:

  • sys_two_factor with backup_codes/user_id mappings
  • User field extension (two_factor_enabled)

Magic Link plugin:

  • Registered with default no-op sendMagicLink handler (reuses verification table, no extra mapping needed)

Wiring:

  • AuthManager.buildPluginList() conditionally registers plugins based on AuthPluginConfig flags (organization, twoFactor, magicLink)
  • Each plugin receives its schema option with the correct snake_case mappings via buildOrganizationPluginSchema() / buildTwoFactorPluginSchema() helpers

All 53 tests pass, build succeeds, CodeQL clean.

Sign up for freeto 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.

[Enhancement] Use better-auth modelName and fields mapping for ObjectQL adapter (snake_case compatibility)

3 participants

@hotlong