Skip to content

feat: add trigger support#273

Open
ericsampson wants to merge 12 commits into
get-convex:mainfrom
ericsampson:feat/triggers
Open

feat: add trigger support#273
ericsampson wants to merge 12 commits into
get-convex:mainfrom
ericsampson:feat/triggers

Conversation

@ericsampson

@ericsampson ericsampson commented Dec 25, 2025

Copy link
Copy Markdown

Add a triggers configuration option to convexAuth() that allows registering callbacks for auth table modifications:

  • onCreate: called after insert operations
  • onUpdate: called after patch operations (receives old and new doc)
  • onDelete: placeholder for future delete operation support

This mirrors the design/functionality implemented for Convex Better Auth

Triggers are supported for all tables defined in authTables.

The AuthTriggers type is derived from authTables for type safety, ensuring table names and document types are enforced at compile time.

Triggers run within the same transaction as the auth operation, so errors will cause the operation to roll back.

Use cases include audit logging, history tracking, and custom side effects when auth data changes.

test: add trigger tests for users and authAccounts tables

  • Add triggerLog table to test schema for capturing trigger events
  • Configure triggers in auth.ts using loggedTriggers helper
  • Test onCreate triggers fire for both users and authAccounts on sign-up
  • Test onUpdate trigger fires for users on subsequent sign-in
  • Verify oldDocId is passed correctly to onUpdate handlers

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Summary by CodeRabbit

  • New Features

    • Configurable auth triggers: define onCreate, onUpdate, and onDelete handlers for auth-related tables.
  • Documentation

    • Added comprehensive docs and API reference describing trigger configuration, handler signatures, and examples.
  • Tests

    • New test suite validating trigger firing for create, update, and delete flows.
  • Chores

    • Version bumped to 0.0.91 and changelog updated.

✏️ Tip: You can customize this high-level summary in your review settings.

Add a `triggers` configuration option to `convexAuth()` that allows
registering callbacks for auth table modifications:

- onCreate: called after insert operations
- onUpdate: called after patch operations (receives old and new doc)
- onDelete: placeholder for future delete operation support

Triggers are supported for all tables defined in authTables:
- users
- authAccounts
- authSessions
- authRefreshTokens
- authVerificationCodes
- authVerifiers
- authRateLimits

The AuthTriggers type is derived from authTables for type safety,
ensuring table names and document types are enforced at compile time.

Triggers run within the same transaction as the auth operation,
so errors will cause the operation to roll back.

Use cases include audit logging, history tracking, and custom
side effects when auth data changes.

test: add trigger tests for users and authAccounts tables

- Add triggerLog table to test schema for capturing trigger events
- Configure triggers in auth.ts using loggedTriggers helper
- Test onCreate triggers fire for both users and authAccounts on sign-up
- Test onUpdate trigger fires for users on subsequent sign-in
- Verify oldDocId is passed correctly to onUpdate handlers
@vercel

vercel Bot commented Dec 25, 2025

Copy link
Copy Markdown

@ericsampson is attempting to deploy a commit to the Convex Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Dec 25, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds configurable per-table triggers (onCreate, onUpdate, onDelete) to ConvexAuthConfig and wires a triggered MutationCtx wrapper so configured handlers run automatically during auth-related mutations.

Changes

Cohort / File(s) Summary
Changelog & Docs
CHANGELOG.md, docs/pages/advanced.mdx, docs/pages/api_reference/server.mdx
Added v0.0.91 changelog and documentation describing trigger configuration, examples, and API reference entries for new trigger types.
Public Types & Exports
src/server/index.ts, src/server/types.ts, src/server/implementation/types.ts
Introduced and exported AuthTableName, AuthTriggers, TableTriggers<TableName>, OnCreateTrigger, OnUpdateTrigger, OnDeleteTrigger; added triggers?: AuthTriggers to ConvexAuthConfig.
Trigger Runtime (core)
src/server/implementation/mutations/index.ts
Implemented createTriggeredCtx() that wraps MutationCtx DB ops to detect auth-table mutations and invoke configured triggers; updated storeImpl to create/pass the triggered context and accept config.
User / Account flows
src/server/implementation/users.ts, src/server/implementation/mutations/modifyAccount.ts
Propagated config into account/user mutation paths; capture oldDoc when needed and invoke onCreate/onUpdate handlers per config.
Session / Token flow changes
src/server/implementation/mutations/signOut.ts, src/server/implementation/mutations/invalidateSessions.ts, src/server/implementation/mutations/refreshSession.ts, src/server/implementation/sessions.ts, src/server/implementation/refreshTokens.ts
Added config: ConvexAuthConfig parameter to session/refresh-related functions and propagated it through deleteSession / refresh-token cleanup calls.
Tests & Schema
test/convex/schema.ts, test/convex/auth.ts, test/convex/triggers.test.ts
Added triggerLog test table, configured test triggers to log operations, and added tests validating trigger firing on sign-up, re-sign-in (update), and password-reset flows.
Version bump
package.json
Bumped package version to 0.0.91.
Linting
.eslintrc.cjs
Added rule disallowing direct use of _rawCtx MemberExpression.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant storeImpl as storeImpl\n(with config)
    participant createTriggeredCtx as createTriggeredCtx
    participant WrappedCtx as WrappedCtx\n(db proxy)
    participant Mutation as Mutation Handler
    participant DB as Database
    participant Trigger as Trigger Handler

    Client->>storeImpl: invoke mutation (MutationCtx + config)
    storeImpl->>createTriggeredCtx: wrap ctx with trigger logic
    createTriggeredCtx-->>storeImpl: WrappedCtx (proxy)
    storeImpl->>Mutation: call mutation with WrappedCtx
    Mutation->>WrappedCtx: perform db.insert/patch/delete on auth table
    WrappedCtx->>DB: execute DB operation
    DB-->>WrappedCtx: return newDoc / oldDoc
    alt Auth table + triggers configured
        WrappedCtx->>Trigger: invoke onCreate/onUpdate/onDelete (ctx, newDoc?, oldDoc?)
        Trigger->>DB: optional side-effect operations (e.g., logging)
        DB-->>Trigger: ack
        Trigger-->>WrappedCtx: resolved
    end
    WrappedCtx-->>Mutation: return result
    Mutation-->>storeImpl: complete
    storeImpl-->>Client: return response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰
I hop through hooks where mutations play,
On create and update I leap and say—
"Log the tale, old doc and new!"
A tiny paw stamps changes true.
Triggers fired — a carrot-cheer hooray!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add trigger support' clearly and concisely describes the main feature being added across multiple files and documentation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
test/convex/auth.ts (1)

21-43: Consider using proper document types for stronger type safety.

The helper uses { _id: string } as the document type, which works but loses type safety. The actual Doc<T> types from the auth library would provide proper field typing.

For test code this is acceptable, but if this pattern is intended as an example for users, consider:

🔎 Alternative with proper typing
+import type { Doc } from "@convex-dev/auth/server";
+
 function loggedTriggers<T extends AuthTableName>(table: T) {
   return {
-    onCreate: async (ctx: GenericMutationCtx<DataModel>, doc: { _id: string }) => {
+    onCreate: async (ctx: GenericMutationCtx<DataModel>, doc: Doc<T>) => {
       await ctx.db.insert("triggerLog", {
         trigger: `${table}:onCreate` as const,
-        docId: doc._id,
+        docId: doc._id as string,
         timestamp: Date.now(),
       });
     },
     onUpdate: async (
       ctx: GenericMutationCtx<DataModel>,
-      newDoc: { _id: string },
-      oldDoc: { _id: string },
+      newDoc: Doc<T>,
+      oldDoc: Doc<T>,
     ) => {
       await ctx.db.insert("triggerLog", {
         trigger: `${table}:onUpdate` as const,
-        docId: newDoc._id,
+        docId: newDoc._id as string,
         timestamp: Date.now(),
-        oldDocId: oldDoc._id,
+        oldDocId: oldDoc._id as string,
       });
     },
   };
 }
docs/pages/advanced.mdx (1)

168-183: Example references undefined tables.

The example uses auditLog and userHistory tables that users would need to define in their schema. Consider adding a brief note or showing the required schema additions.

🔎 Suggested addition

Add a note before or after the code block:

> **Note:** The `auditLog` and `userHistory` tables in this example are custom tables 
> you would need to define in your schema.
test/convex/triggers.test.ts (2)

49-90: Solid onUpdate trigger test.

The test validates the correct sequence:

  1. After sign-up: only onCreate triggers, no onUpdate
  2. After second sign-in: onUpdate triggers fire with oldDocId correctly set

One consideration: the assertion expect(userUpdateLogs.length).toBeGreaterThanOrEqual(1) on line 85 is somewhat loose. If you expect exactly one update per sign-in, toHaveLength(1) would be more precise.


150-157: Consider extracting shared test setup.

The setupEnv function duplicates environment setup that likely exists in other test files. Consider whether this could be shared via a common test utility.

src/server/implementation/users.ts (1)

210-261: Account trigger logic handles multiple patch scenarios correctly.

The implementation captures oldDoc before any patches (line 239) and fetches newDoc after all patches complete (line 259), ensuring triggers receive accurate before/after snapshots even when multiple fields are updated (userId, emailVerified, phoneVerified).

One observation: with multiple sequential db.patch calls (lines 248, 251, 254), each patch is a separate write. While this works correctly for trigger purposes, consolidating into a single patch could reduce write operations if performance becomes a concern.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2734225 and 55dbdda.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • docs/pages/advanced.mdx
  • docs/pages/api_reference/server.mdx
  • package.json
  • src/server/implementation/mutations/index.ts
  • src/server/implementation/mutations/modifyAccount.ts
  • src/server/implementation/types.ts
  • src/server/implementation/users.ts
  • src/server/index.ts
  • src/server/types.ts
  • test/convex/auth.ts
  • test/convex/schema.ts
  • test/convex/triggers.test.ts
🧰 Additional context used
🧬 Code graph analysis (5)
src/server/types.ts (2)
src/server/implementation/types.ts (1)
  • AuthTriggers (231-233)
src/server/index.ts (1)
  • AuthTriggers (44-44)
test/convex/auth.ts (3)
src/server/implementation/types.ts (1)
  • AuthTableName (165-165)
src/server/index.ts (2)
  • AuthTableName (43-43)
  • convexAuth (15-15)
test/convex/_generated/dataModel.d.ts (1)
  • DataModel (60-60)
src/server/implementation/mutations/modifyAccount.ts (2)
src/server/types.ts (1)
  • ConvexAuthConfig (23-256)
src/server/implementation/provider.ts (1)
  • hash (3-14)
src/server/implementation/users.ts (2)
src/server/index.ts (1)
  • ConvexAuthConfig (30-30)
src/server/types.ts (1)
  • ConvexAuthConfig (23-256)
src/server/implementation/mutations/index.ts (1)
src/server/implementation/mutations/modifyAccount.ts (1)
  • modifyAccountImpl (12-54)
🪛 LanguageTool
docs/pages/advanced.mdx

[style] ~152-~152: ‘new record’ might be wordy. Consider a shorter alternative.
Context: ...r types: - onCreate - Called after a new record is inserted - onUpdate - Called after...

(EN_WORDINESS_PREMIUM_NEW_RECORD)

docs/pages/api_reference/server.mdx

[style] ~2404-~2404: ‘new record’ might be wordy. Consider a shorter alternative.
Context: ...blename)<TableName> Called after a new record is inserted. #### onUpdate? > `option...

(EN_WORDINESS_PREMIUM_NEW_RECORD)

🔇 Additional comments (15)
package.json (1)

3-3: LGTM!

Version bump to 0.0.91 is appropriate for the new triggers feature.

src/server/implementation/types.ts (1)

156-233: LGTM!

Well-designed type system for triggers:

  • AuthTableName correctly derives from authTables ensuring compile-time safety
  • Trigger handlers receive proper typed MutationCtx and Doc<TableName>
  • onUpdate correctly provides both old and new documents for comparison
  • The mapped type AuthTriggers elegantly allows partial configuration per table
src/server/index.ts (1)

42-49: LGTM!

Clean public API exposure of the new trigger types, allowing consumers to import them directly from @convex-dev/auth/server.

test/convex/schema.ts (1)

24-30: LGTM!

Well-designed test table for capturing trigger events:

  • trigger field captures the event type (e.g., "users:onCreate")
  • docId as string is appropriate since it can reference documents from different auth tables
  • oldDocId correctly optional for onCreate triggers
src/server/implementation/mutations/index.ts (1)

148-150: LGTM!

Config parameter correctly passed to modifyAccountImpl, enabling trigger support for account modifications. This follows the same pattern as other mutations that already receive the config.

docs/pages/advanced.mdx (1)

219-225: LGTM on important notes!

Good coverage of critical behavioral details:

  • Transaction scope is clearly explained
  • Error handling/rollback behavior is documented
  • Full MutationCtx access is highlighted
src/server/types.ts (3)

18-18: LGTM!

The import of AuthTriggers from the implementation types is correctly structured using a type-only import.


226-255: Well-documented triggers configuration.

The JSDoc documentation clearly explains the purpose (atomic audit logging, history tracking) and provides a helpful example demonstrating both onCreate and onUpdate handlers for multiple tables. The documentation correctly notes that triggers run in the same transaction as the auth operation.


398-398: LGTM!

Correctly propagates the triggers configuration to the materialized config, enabling runtime access to trigger handlers.

test/convex/triggers.test.ts (2)

13-47: Good test coverage for onCreate triggers.

The test properly validates that:

  1. Both users:onCreate and authAccounts:onCreate triggers fire exactly once
  2. The docId in trigger logs matches the actual created documents

92-148: Comprehensive password change trigger test.

Good coverage of the password reset flow, verifying that authAccounts:onUpdate fires after credential modification.

src/server/implementation/mutations/modifyAccount.ts (1)

37-51: LGTM! Trigger implementation is correct.

The implementation:

  1. Conditionally captures oldDoc only when an onUpdate trigger is configured (efficient)
  2. Re-fetches newDoc after the patch to get the updated state
  3. Correctly guards trigger invocation with both oldDoc and trigger existence checks

The non-null assertion on line 49 is safe since the patch operation succeeded.

src/server/implementation/users.ts (1)

133-158: LGTM! User trigger implementation is correct.

The implementation properly:

  1. Captures oldDoc before the patch operation (line 134-136)
  2. Invokes onUpdate only after a successful patch (lines 147-151)
  3. Fetches the fresh document for onCreate after insert (lines 154-158)
docs/pages/api_reference/server.mdx (2)

1579-1588: LGTM!

Clear documentation of the triggers property with correct type reference and link to the advanced usage guide.


2337-2521: Comprehensive API documentation for trigger types.

The documentation clearly explains:

  • AuthTableName union type derived from authTables
  • AuthTriggers mapped type structure
  • TableTriggers with all three handler types
  • Individual trigger handler signatures with correct parameter types

The code examples effectively illustrate the type structures.

Comment thread CHANGELOG.md Outdated
@ericsampson ericsampson changed the title feat: add trigger support for auth tables feat: add trigger support Dec 25, 2025
- Add triggeredDb wrapper that uses fn.length to skip unnecessary reads
- onCreate: only reads doc if trigger declares doc param (length >= 2)
- onUpdate: reads oldDoc only if declared (length >= 3), newDoc if >= 2
- onDelete: now passes id as second param, doc as optional third param
- Use union types for trigger signatures to allow optional params
- Use AnyCtx (any) for ctx to allow users to access custom tables
- Update docs and API reference with new signatures

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/server/implementation/refreshTokens.ts (1)

100-114: The config parameter is unused in deleteAllRefreshTokens but is deliberately passed by both callers.

Line 102 adds a config: ConvexAuthConfig parameter that is never referenced in the function body (lines 105-113). Both call sites explicitly pass config:

  • src/server/implementation/sessions.ts:108
  • src/server/implementation/mutations/refreshSession.ts:54

This suggests the parameter is intentionally included for API consistency with other functions in the module that accept triggered contexts, even if not currently used. Consider adding a comment explaining why it's reserved if this pattern is deliberate.

src/server/implementation/mutations/createVerificationCode.ts (1)

39-72: Pass triggered context to upsertUserAndAccount for trigger execution.

The function creates a triggered context (ctx = createTriggeredCtx(originalCtx, config) at line 28) but then passes originalCtx to upsertUserAndAccount (line 54-55) instead of ctx. Since upsertUserAndAccount performs writes (creates/updates users and accounts) that may need to fire triggers, it should receive the triggered context.

This is inconsistent with verifyCodeAndSignIn.ts (line 175-176), which correctly passes ctx to the same function. Pass ctx instead of originalCtx to upsertUserAndAccount.

🧹 Nitpick comments (2)
src/server/implementation/triggeredDb.ts (1)

61-75: Type safety bypassed with as any assertions.

All trigger lookups use (triggers as any)[table]?.onCreate which bypasses TypeScript's type checking. While this may be necessary for dynamic table access, consider whether the AuthTriggers type can be made more strongly typed to catch misconfigurations at compile time.

Also applies to: 81-100, 106-125, 131-140

docs/pages/api_reference/server.mdx (1)

2378-2521: Documentation is thorough and accurate.

The documentation for trigger handler types (TableTriggers, OnCreateTrigger, OnUpdateTrigger, OnDeleteTrigger) is comprehensive, with clear type signatures and descriptions.

Optional style improvement: Line 2404 uses "new record" which could be simplified to "record" for conciseness, though this is a minor style preference.

🔎 Optional: Simplify wording
-Called after a new record is inserted.
+Called after a record is inserted.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55dbdda and 2a10ca7.

📒 Files selected for processing (15)
  • docs/pages/advanced.mdx
  • docs/pages/api_reference/server.mdx
  • src/server/implementation/mutations/createVerificationCode.ts
  • src/server/implementation/mutations/index.ts
  • src/server/implementation/mutations/invalidateSessions.ts
  • src/server/implementation/mutations/refreshSession.ts
  • src/server/implementation/mutations/signOut.ts
  • src/server/implementation/mutations/userOAuth.ts
  • src/server/implementation/mutations/verifyCodeAndSignIn.ts
  • src/server/implementation/refreshTokens.ts
  • src/server/implementation/sessions.ts
  • src/server/implementation/triggeredDb.ts
  • src/server/implementation/types.ts
  • test/convex/auth.ts
  • test/convex/triggers.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/convex/triggers.test.ts
🧰 Additional context used
🧬 Code graph analysis (9)
src/server/implementation/mutations/signOut.ts (2)
src/server/implementation/types.ts (1)
  • MutationCtx (138-138)
src/server/implementation/sessions.ts (2)
  • getAuthSessionId (134-141)
  • deleteSession (101-109)
src/server/implementation/mutations/userOAuth.ts (3)
src/server/implementation/types.ts (1)
  • MutationCtx (138-138)
src/server/implementation/provider.ts (2)
  • GetProviderOrThrowFunc (33-36)
  • Config (38-38)
src/server/implementation/triggeredDb.ts (1)
  • createTriggeredCtx (32-153)
src/server/implementation/sessions.ts (2)
src/server/implementation/triggeredDb.ts (1)
  • createTriggeredCtx (32-153)
src/server/implementation/refreshTokens.ts (1)
  • deleteAllRefreshTokens (100-114)
src/server/implementation/mutations/refreshSession.ts (2)
src/server/implementation/triggeredDb.ts (1)
  • createTriggeredCtx (32-153)
src/server/implementation/refreshTokens.ts (1)
  • deleteAllRefreshTokens (100-114)
test/convex/auth.ts (2)
src/server/implementation/types.ts (1)
  • AuthTriggers (234-236)
test-router/convex/auth.ts (1)
  • convexAuth (4-6)
src/server/implementation/mutations/index.ts (3)
src/server/implementation/mutations/signOut.ts (1)
  • signOutImpl (11-24)
src/server/implementation/mutations/modifyAccount.ts (1)
  • modifyAccountImpl (12-54)
src/server/implementation/mutations/invalidateSessions.ts (1)
  • invalidateSessionsImpl (24-42)
src/server/implementation/mutations/invalidateSessions.ts (1)
src/server/implementation/sessions.ts (1)
  • deleteSession (101-109)
src/server/implementation/triggeredDb.ts (1)
src/server/implementation/types.ts (2)
  • AuthTableName (165-165)
  • MutationCtx (138-138)
src/server/implementation/mutations/verifyCodeAndSignIn.ts (5)
src/server/implementation/types.ts (1)
  • MutationCtx (138-138)
src/server/implementation/provider.ts (2)
  • GetProviderOrThrowFunc (33-36)
  • Config (38-38)
src/server/implementation/triggeredDb.ts (1)
  • createTriggeredCtx (32-153)
src/server/implementation/rateLimit.ts (3)
  • isSignInRateLimited (6-16)
  • recordFailedSignIn (18-37)
  • resetSignInRateLimit (39-50)
src/server/implementation/sessions.ts (3)
  • getAuthSessionId (134-141)
  • createNewAndDeleteExistingSession (43-56)
  • maybeGenerateTokensForSession (22-41)
🪛 LanguageTool
docs/pages/api_reference/server.mdx

[style] ~2404-~2404: ‘new record’ might be wordy. Consider a shorter alternative.
Context: ...blename)<TableName> Called after a new record is inserted. #### onUpdate? > `option...

(EN_WORDINESS_PREMIUM_NEW_RECORD)

docs/pages/advanced.mdx

[style] ~152-~152: ‘new record’ might be wordy. Consider a shorter alternative.
Context: ...r types: - onCreate - Called after a new record is inserted - onUpdate - Called after...

(EN_WORDINESS_PREMIUM_NEW_RECORD)

🔇 Additional comments (14)
src/server/implementation/mutations/refreshSession.ts (1)

49-54: LGTM! Triggered context properly scoped to failure path.

The triggered context is created only for the validation failure branch where session cleanup occurs, ensuring delete triggers fire appropriately. The success paths remain unchanged, which is correct since they don't perform deletions.

test/convex/auth.ts (1)

16-93: LGTM! Comprehensive trigger test configuration.

The test setup exercises all three trigger types (onCreate, onUpdate, onDelete) across the main auth tables. Logging to triggerLog enables verification of trigger execution. The unused _doc parameter in onDelete handlers appropriately demonstrates the optional third parameter behavior.

src/server/implementation/mutations/signOut.ts (1)

11-19: LGTM! Config parameter properly propagated.

The config parameter is correctly threaded through to deleteSession, which uses it to create a triggered context for firing delete triggers during session cleanup.

src/server/implementation/mutations/invalidateSessions.ts (1)

24-38: LGTM! Config parameter properly propagated.

The config parameter is correctly threaded through to deleteSession, enabling trigger support for session invalidation operations. This follows the same pattern as signOut.ts.

src/server/implementation/mutations/verifyCodeAndSignIn.ts (2)

17-17: LGTM: Clean separation of contexts for trigger support.

The import of createTriggeredCtx and the immediate creation of a triggered context establishes a clear pattern: originalCtx is used for operations that should not fire triggers (rate limiting, session retrieval), while ctx is used for DB operations that should fire triggers.

Also applies to: 29-35


46-77: No concerns with context usage at lines 73-84. The session functions correctly receive originalCtx because deleteSession creates its own triggered context internally, and createSession performs a plain insert without needing triggers. This is the correct pattern for functions that manage their own trigger context creation.

src/server/implementation/sessions.ts (1)

18-18: LGTM: Consistent trigger context pattern.

The deleteSession function now accepts config and creates its own triggered context internally. This ensures that delete operations on sessions and refresh tokens fire the appropriate triggers. The pattern is consistent with the broader changes across mutations.

Also applies to: 101-109

src/server/implementation/mutations/createVerificationCode.ts (1)

8-8: LGTM: Consistent trigger context creation.

The function follows the same pattern as other mutations: rename the parameter to originalCtx and create a triggered context for DB operations.

Also applies to: 22-28

src/server/implementation/mutations/index.ts (1)

112-112: LGTM: Config propagation enables trigger support.

The changes correctly propagate the config parameter to signOutImpl, modifyAccountImpl, and invalidateSessionsImpl, enabling these functions to create triggered contexts for DB operations.

Also applies to: 149-149, 152-152

src/server/implementation/types.ts (3)

157-165: LGTM: Type-safe table name derivation.

The AuthTableName type is correctly derived from authTables using keyof, ensuring compile-time type safety and automatic updates when auth tables change.


167-194: LGTM: Flexible trigger signatures with optional reads.

The trigger types use union types to allow omitting parameters, which enables the trigger system to skip unnecessary document reads by inspecting function arity. The AnyCtx type allows triggers to access custom tables beyond auth tables.

Note: Using any for AnyCtx trades type safety for flexibility. This is acceptable here since trigger implementations are user-provided and may need access to custom tables, but developers should be aware that type checking within trigger handlers is relaxed.


196-236: LGTM: Well-documented trigger configuration types.

The TableTriggers and AuthTriggers types are well-structured with clear documentation and usage examples. The mapped type approach ensures type safety across all auth tables.

docs/pages/api_reference/server.mdx (2)

1579-1588: LGTM: Clear documentation for triggers configuration.

The documentation clearly describes the triggers field, its type, and purpose. The link to usage examples in the advanced docs provides additional guidance.


2337-2375: LGTM: Comprehensive type documentation.

The documentation for AuthTableName and AuthTriggers is clear and includes both type signatures and source file references, making it easy for developers to understand and use these types.

Comment thread docs/pages/advanced.mdx Outdated
Comment thread docs/pages/advanced.mdx
Comment thread src/server/implementation/mutations/userOAuth.ts Outdated
Comment thread src/server/implementation/triggeredDb.ts Outdated
Comment thread src/server/implementation/triggeredDb.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
docs/pages/advanced.mdx (1)

156-200: Clear and practical usage examples.

The code examples effectively demonstrate trigger configuration with proper type annotations and realistic use cases. The examples show both logging and database operations, covering common patterns developers would need.

Consider adding a brief onDelete example for completeness, though the pattern is clear from the onCreate and onUpdate examples.

Optional: Add onDelete example

You could add an onDelete example to one of the table configurations to demonstrate all three trigger types:

authSessions: {
  onDelete: async (ctx, sessionId, doc) => {
    // Log session termination
    console.log(`Session ${sessionId} deleted`);
  },
},
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a10ca7 and a51f742.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • docs/pages/advanced.mdx
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🧰 Additional context used
🪛 LanguageTool
docs/pages/advanced.mdx

[style] ~152-~152: ‘new record’ might be wordy. Consider a shorter alternative.
Context: ...r types: - onCreate - Called after a new record is inserted - onUpdate - Called after...

(EN_WORDINESS_PREMIUM_NEW_RECORD)

🔇 Additional comments (3)
docs/pages/advanced.mdx (3)

137-154: Well-structured introduction to triggers.

The introduction clearly explains the purpose of triggers, provides relevant use cases, and correctly notes that triggers run atomically with auth operations. The trigger types are documented with appropriate descriptions.

Note: The onDelete timing issue (line 154) has already been flagged in a previous review comment.


202-217: Type safety documentation is accurate and helpful.

The exported types are clearly documented with their signatures, making it easy for developers to understand the type contracts. The signatures correctly match the usage examples shown earlier in the document.


219-226: Important Notes section covers key behaviors.

The notes appropriately document transaction semantics, error handling, context capabilities, and selective trigger firing. These are essential points for developers implementing triggers.

Note: Missing documentation about function.length optimization and nested trigger behavior has already been flagged in a previous review comment.

The trigger is called before deletion so the document can still be read
for archiving/logging purposes.
- Explain that triggers must explicitly declare parameters to receive them
- Warn about default parameters and destructuring breaking detection
- Document that triggers use original ctx so nested triggers don't fire
- Read doc before delete (if needed by trigger)
- Perform the delete
- Fire the trigger after successful deletion

This ensures:
- Consistent with onCreate/onUpdate (all fire after the operation)
- Trigger only runs if delete succeeds
- Doc snapshot still available via pre-read
Pass triggered ctx to all helper functions so triggers fire for:
- userOAuth: upsertUserAndAccount
- createVerificationCode: getAccountOrThrow, getAuthSessionId, upsertUserAndAccount
- verifyCodeAndSignIn: rate limit functions, session functions
- Rename to _rawCtx to signal "don't use directly"
- Add no-restricted-syntax rule to error on _rawCtx.* member access
- This prevents accidentally bypassing triggers
- Move createTriggeredCtx into mutations/index.ts as private function
- storeImpl now wraps context once before dispatching to implementations
- Remove individual wrapping from all mutation implementations
- Delete triggeredDb.ts (functionality inlined)
- Fix deleteSession to not double-wrap (receives already-wrapped ctx)

This ensures all auth operations consistently use triggered context
without implementations needing to know about triggers.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/server/implementation/mutations/index.ts (1)

96-103: Verify trigger receives non-wrapped context intentionally.

The trigger receives the original ctx (not the wrapped triggeredDb context), which is correct for preventing infinite trigger loops. However, this means if a trigger performs ctx.db.insert("users", ...), it won't fire another onCreate trigger.

This behavior is documented in the docs, but consider adding a brief inline comment here explaining why ctx is used instead of a context with triggeredDb.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a51f742 and f89b5fd.

📒 Files selected for processing (5)
  • .eslintrc.cjs
  • docs/pages/advanced.mdx
  • src/server/implementation/mutations/index.ts
  • src/server/implementation/mutations/refreshSession.ts
  • src/server/implementation/sessions.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/server/implementation/mutations/refreshSession.ts
  • src/server/implementation/sessions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/server/implementation/mutations/index.ts (3)
src/server/implementation/types.ts (2)
  • AuthTableName (165-165)
  • MutationCtx (138-138)
src/server/types.ts (1)
  • ConvexAuthConfig (23-256)
src/server/implementation/mutations/signOut.ts (1)
  • signOutImpl (11-24)
🪛 LanguageTool
docs/pages/advanced.mdx

[style] ~152-~152: ‘new record’ might be wordy. Consider a shorter alternative.
Context: ...r types: - onCreate - Called after a new record is inserted - onUpdate - Called after...

(EN_WORDINESS_PREMIUM_NEW_RECORD)

🔇 Additional comments (6)
docs/pages/advanced.mdx (1)

137-246: LGTM! Comprehensive trigger documentation.

The documentation clearly explains the triggers feature with accurate descriptions:

  • Trigger types correctly document timing (onCreate after insert, onUpdate after update, onDelete after deletion with pre-deletion snapshot)
  • Function parameter optimization and nested trigger behavior are well documented
  • Code example demonstrates all trigger types with realistic use cases
src/server/implementation/mutations/index.ts (4)

82-89: LGTM! Table detection is correct.

Using normalizeId to detect the table for an ID is the correct Convex pattern. The linear scan over 7 tables is negligible.


108-148: LGTM! Patch and replace handlers correctly implement trigger optimization.

The function.length optimization correctly avoids unnecessary DB reads:

  • oldDoc fetched only when trigger.length >= 3
  • newDoc fetched only when trigger.length >= 2

The duplication between patch and replace is acceptable given their subtle differences.


150-159: LGTM! Delete handler correctly fires trigger after deletion.

The implementation correctly:

  1. Reads the document before deletion (if trigger needs it)
  2. Performs the delete
  3. Fires the trigger post-deletion with the pre-deletion snapshot

This aligns with the documented behavior.


228-287: LGTM! Clean centralized context wrapping.

The refactoring correctly:

  • Wraps the context once at the entry point
  • Passes the triggered context to all mutation implementations
  • Uses _rawCtx naming to signal it shouldn't be used directly (enforced by ESLint)
.eslintrc.cjs (1)

48-56: LGTM! Good defensive ESLint rule.

The no-restricted-syntax rule with the MemberExpression[object.name='_rawCtx'] selector will catch any property access on _rawCtx, preventing accidental bypass of the triggered context.

This is a solid guard to ensure all auth operations consistently fire triggers.

Clarify why triggers receive the original ctx instead of triggeredDb,
preventing infinite loops when triggers write to auth tables.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/server/implementation/mutations/index.ts (1)

82-89: Consider caching table lookups for repeated operations.

The linear search through AUTH_TABLES is acceptable given the small array size (7 items), but if performance becomes a concern, consider using a Map for O(1) lookups or caching the table name on the first detection.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f89b5fd and 87ef6da.

📒 Files selected for processing (1)
  • src/server/implementation/mutations/index.ts
🔇 Additional comments (6)
src/server/implementation/mutations/index.ts (6)

1-2: LGTM!

The new imports appropriately support the trigger functionality.

Also applies to: 35-35


56-64: LGTM!

The AUTH_TABLES definition is clear and comprehensive for auth table trigger support.


71-78: LGTM!

The early return optimization when no triggers are configured is efficient and appropriate.


164-168: LGTM!

The passthrough methods for read operations (get, query, normalizeId) and the system property are correctly implemented. Using .bind(rawDb) ensures the correct this context is maintained.


231-238: LGTM! Clean wrapper pattern implementation.

The single wrapping point at the entry of storeImpl ensures all mutation implementations consistently receive the triggered context. The _rawCtx naming convention clearly indicates the unwrapped context, and the comment effectively documents the design decision.


94-168: The triggered db wrapper is complete for Convex 1.17.0 MutationCtx.db API.

The implemented wrapper correctly covers the full GenericDatabaseWriter surface: insert, patch, replace, delete, get, query, normalizeId, and system. There are no additional mutation methods like db.table() on MutationCtx.db in Convex 1.17.0 (the table() method belongs to GenericDatabaseWriterWithTable, which is not used in this context). All write operations that should trigger callbacks are properly wrapped, and read-only operations are appropriately delegated.

Likely an incorrect or invalid review comment.

Comment thread src/server/implementation/mutations/index.ts
Comment thread src/server/implementation/mutations/index.ts
Comment thread src/server/implementation/mutations/index.ts
Comment thread src/server/implementation/mutations/index.ts
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.

1 participant