8000 Convex implementation by BilalG1 · Pull Request #913 · stack-auth/stack-auth · GitHub
[go: up one dir, main page]

Skip to content

Conversation

BilalG1
Copy link
Collaborator
@BilalG1 BilalG1 commented Sep 23, 2025

Important

Add Convex integration with new auth helpers, update access token handling, and include documentation, examples, and tests for the new features.

  • Features:
    • Add Convex integration with new auth helpers for Convex clients and HTTP in client-app-impl.ts and server-app-impl.ts.
    • Support for Convex context in user APIs and partial user retrieval.
    • Access tokens now include is_anonymous for better anonymous handling in tokens.tsx.
  • Documentation:
    • Add Convex integration guide in docs/templates/others/convex.mdx.
    • Update docs navigation in docs/docs-platform.yml and docs/templates/meta.json.
  • Examples:
    • Add Convex + Next.js example app in examples/convex with auth wiring, functions, schema, and UI.
  • Tests:
    • Add E2E tests for Convex auth flows in convex.test.ts.
    • Update JWT payload checks in backend-helpers.ts and anonymous-comprehensive.test.ts.
  • Chores:
    • Add Convex dependencies in package.json files.
    • Update CI steps for example environments in e2e-api-tests.yaml and e2e-source-of-truth-api-tests.yaml.

This description was created by Ellipsis for aa0983a. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Convex integration: auth helpers for Convex clients/HTTP, Convex-aware user APIs, and partial-user retrieval (token/convex).
    • Access tokens now surface is_anonymous for clearer anonymous handling.
  • Documentation

    • Added Convex integration guide and nav entries.
  • Examples

    • New Convex + Next.js example app with auth wiring, backend functions, schema, and UI.
  • Tests

    • Added E2E tests covering Convex auth flows and JWT payload checks.
  • Chores

    • Added Convex deps, CI env steps, and workspace/test config updates.

Copy link
vercel bot commented Sep 23, 2025

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

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Sep 24, 2025 7:08pm
stack-dashboard Ready Ready Preview Comment Sep 24, 2025 7:08pm
stack-demo Ready Ready Preview Comment Sep 24, 2025 7:08pm
stack-docs Ready Ready Preview Comment Sep 24, 2025 7:08pm

Copy link
Contributor
coderabbitai bot commented Sep 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds Convex integration (examples, docs, CI, tests), centralizes JWT access-token payload construction/decoding to use a validated AccessTokenPayload (prefers is_anonymous with legacy fallback), introduces partial-user APIs and Convex auth helpers in the template SDK, and adds a generic paginated-list utility.

Changes

Cohort / File(s) Summary
Backend tokens & types
apps/backend/src/lib/tokens.tsx, apps/backend/src/lib/types.tsx
Centralized access-token payload construction using imported AccessTokenPayload; decoding now prefers payload.is_anonymous with a legacy role === 'anon' fallback. Removed exported Prettify type.
Shared auth schemas & sessions
packages/stack-shared/src/schema-fields.ts, packages/stack-shared/src/sessions.ts
Added accessTokenPayloadSchema and exported AccessTokenPayload. AccessToken exposes payload (schema-validated). Added InternalSession.getAccessTokenIfNotExpiredYet(minMillisUntilExpiration) and updated token caching/expiry flow.
Paginated list utility
packages/stack-shared/src/utils/paginated-lists.tsx
New generic PaginatedList abstraction and concrete ArrayPaginatedList implementation with cursor semantics, ordering, filtering, merge, and limitPrecision behavior.
Template SDK: Convex + partial-user APIs
packages/template/src/... (integrations/convex.ts, common.ts, index.ts, apps/interfaces/, apps/implementations/, users/index.ts, src/index.ts)
Added getConvexProvidersConfig export; renamed GetUserOptionsGetCurrentUserOptions; added ConvexCtx, GetCurrentPartialUserOptions, TokenPartialUser, SyncedPartialUser/SyncedPartialServerUser; added getPartialUser/usePartialUser overloads and getConvexClientAuth/getConvexHttpClientAuth helpers; updated client/server app implementations and public interfaces.
E2E tests & helpers
apps/e2e/tests/backend/*, apps/e2e/tests/js/convex.test.ts, apps/e2e/package.json
Added Convex end-to-end tests (React/WebSocket and HTTP auth), updated JWT assertions to check is_anonymous and project_id (from aud), and added convex dependency.
Convex example app
examples/convex/** (app, convex, stack, configs, docs)
New Next.js + Convex example including auth config, Convex functions/schema, pages/layout/components/providers, env, tsconfig, ESLint, README, package.json, and Stack client/server wiring.
Docs & templates
docs/docs-platform.yml, docs/templates/meta.json, docs/templates/others/convex.mdx
Added Convex documentation page and navigation/meta entries.
Deps, CI, workspace
packages/*/package.json (js, react, stack, template, packages/template/package-template.json), .github/workflows/*, vitest.workspace.ts
Added convex to several package devDependencies, added CI steps to create Convex env files for examples, and reordered workspace globs in vitest.workspace.ts.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Browser
  participant NextApp as Next.js App
  participant StackClient as StackClientApp
  participant ConvexClient as ConvexReactClient
  participant ConvexServer as Convex Server

  Browser->>NextApp: load page (client)
  NextApp->>StackClient: getConvexClientAuth() → token getter
  NextApp->>ConvexClient: new ConvexReactClient({ auth: getter })
  ConvexClient->>StackClient: invoke token getter (forceRefresh?)
  StackClient->>StackClient: getAccessTokenIfNotExpiredYet(minMs)
  alt cached token valid
    StackClient-->>ConvexClient: return Bearer <JWT>
  else cached token missing/near-expiry
    StackClient->>StackClient: refresh/fetch token
    StackClient-->>ConvexClient: return Bearer <JWT>
  end
  ConvexClient->>ConvexServer: Connect / Authenticate(Bearer <JWT>)
  ConvexServer-->>ConvexClient: Ack
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant InternalSession
  participant Cache
  participant AuthService

  Caller->>InternalSession: getAccessTokenIfNotExpiredYet(minMs)
  InternalSession->>Cache: read stored access token
  alt token exists and (exp - now) >= minMs (bounded by 60s)
    Cache-->>InternalSession: token
    InternalSession-->>Caller: AccessToken.payload (validated)
  else
    InternalSession-->>Caller: null
    Caller->>InternalSession: getOrFetchLikelyValidTokens(...)
    InternalSession->>AuthService: request new token
    AuthService-->>InternalSession: new token
    InternalSession->>Cache: store token
    InternalSession-->>Caller: token
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

A hop, a skip, a Convex leap,
Tokens tidy, schemas keep.
Partial paws map users’ tracks,
Cursors hop and pages stack.
Docs and CI dig tunnels bright—carrots signed and bedtime light. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title “Convex implementation” is concise and directly reflects the primary change of integrating Convex across the codebase, making it clear at a glance what the pull request is about. Although it does not enumerate every detail, it highlights the main focus of the changes. It is sufficiently specific for team members to understand the gist when scanning history.
Description Check ✅ Passed The pull request description begins with the required CONTRIBUTING.md reminder and then provides a thorough summary of new features, documentation updates, examples, tests, and CI changes, covering all major areas of the implementation. It clearly outlines the Convex integration, auth helper enhancements, access token updates, and related chores, fulfilling the repository’s template expectations. The level of detail is sufficient for reviewers to understand the scope and rationale without requiring additional context.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch convex-impl

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.

@BilalG1 BilalG1 changed the base branch from dev to convex September 23, 2025 17:40
Copy link
Contributor
@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Summary

This PR implements Convex integration for Stack Auth, adding support for using Stack Auth's JWT tokens with Convex's real-time backend platform. The implementation includes:

Core Integration Features:

  • New getConvexProvidersConfig() function that provides JWT configuration for Convex authentication using Stack's issuer and JWKS endpoints
  • Client-side authentication helpers (getConvexClientAuth, getConvexHttpClientAuth) that enable Convex clients to use Stack Auth tokens
  • Support for partial user data retrieval (getPartialUser, usePartialUser) with both token-based and Convex context sources
  • New user types (TokenPartialUser, SyncedPartialUser) for optimized data transfer between Stack and external systems

JWT Token Structure Changes:

  • Anonymous users now have role 'authenticated' instead of 'anon' with a new is_anonymous: boolean field for cleaner authorization logic
  • Enhanced JWT payload validation with structured schema validation replacing direct decoding
  • Updated access token generation to use typed AccessTokenPayload structure

Type System Improvements:

  • Renamed GetUserOptions to GetCurrentUserOptions throughout the codebase for better semantic clarity
  • Added GetCurrentPartialUserOptions type supporting both token and Convex context sources
  • New server/client user type variants for different integration scenarios

Developer Experience:

  • Complete example application in examples/convex/ demonstrating Stack Auth + Convex integration with Next.js
  • Comprehensive documentation in docs/templates/others/convex.mdx (though with some issues)
  • Pagination utilities in packages/stack-shared/src/utils/paginated-lists.tsx for handling large datasets
  • Updated test suites to validate anonymous user JWT changes and Convex integration functionality

The integration follows Stack's established patterns for third-party integrations while accommodating Convex's unique authentication and real-time data requirements. The changes maintain backward compatibility through deprecated aliases and legacy token handling.

Important Files Changed

Changed Files
Filename Score Overview
docs/templates/others/convex.mdx 1/5 Documentation file with critical errors including function name mismatches, Supabase references instead of Convex, and incorrect GitHub links
examples/convex/convex/auth.config.ts 2/5 Convex authentication config with hardcoded CloudFlare tunnel URLs and mixed local/production endpoints creating deployment issues
examples/convex/convex/myFunctions.ts 2/5 Example Convex functions with questionable client-side Stack app usage in server context and @ts-ignore suppressing errors
packages/stack-shared/src/utils/paginated-lists.tsx 3/5 Complex pagination framework with intricate cursor management logic and potential performance concerns in sorting validation
examples/convex/eslint.config.mjs 3/5 ESLint config using modern flat format but inconsistent with project's shared configurations used by other examples
packages/template/src/integrations/convex.ts 5/5 Clean Convex JWT provider configuration function with proper environment variable handling and type safety
apps/backend/src/lib/tokens.tsx 4/5 JWT token refactoring moving from 'anon' role to 'is_anonymous' field with proper backward compatibility handling
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts 4/5 Comprehensive Convex integration adding partial user methods and auth helpers with good type safety
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts 5/5 Clean renaming of GetUserOptions to GetCurrentUserOptions maintaining full backward compatibility
packages/stack-shared/src/sessions.ts 4/5 JWT payload validation enhancement with schema-based approach improving type safety but adding validation overhead
examples/convex/package.json 4/5 Well-structured package config for Convex example with parallel dev scripts and modern dependency versions
examples/convex/app/page.tsx 4/5 Complete example React component demonstrating Stack Auth + Convex integration with proper loading states
examples/convex/app/layout.tsx 4/5 Proper provider hierarchy setup combining Stack Auth with Convex client integration
packages/template/src/index.ts 4/5 Clean addition of Convex integration export with maintained alphabetical ordering
examples/convex/components/ConvexClientProvider.tsx 4/5 Well-structured provider component integrating Convex client with Stack Auth authentication
apps/e2e/tests/js/convex.test.ts 4/5 Basic integration test validating Convex client auth setup but lacks comprehensive validation scenarios
examples/convex/README.md 4/5 Generic Convex template README not customized for Stack Auth integration despite functional integration
apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/anonymous-comprehensive.test.ts 4/5 Updated anonymous user tests with proper JWT payload expectations for new token structure
examples/convex/postcss.config.mjs 4/5 Modern Tailwind CSS v4 PostCSS config using unified plugin approach
examples/convex/app/globals.css 4/5 Modern Tailwind v4 global styles with theme support but potential font configuration issues
packages/template/src/lib/stack-app/users/index.ts 5/5 New partial user types for token embedding and external system synchronization with proper type safety
examples/convex/app/loading.tsx 5/5 Standard Next.js loading component following established patterns with helpful Suspense documentation
examples/convex/stack/server.tsx 5/5 Standard Stack server app setup following established example patterns with proper server-only directive

Confidence score: 3/5

  • This PR introduces significant functionality but contains several implementation issues that could cause problems in production
  • Score reflects concerns about hardcoded URLs in auth config, documentation errors, and questionable patterns in example code
  • Pay close attention to docs/templates/others/convex.mdx, examples/convex/convex/auth.config.ts, and examples/convex/convex/myFunctions.ts which need fixes before deployment

Sequence Diagram

sequenceDiagram
    participant User
    participant "Next.js App" as App
    participant "Stack Client" as Stack
    participant "Convex Client" as Convex
    participant "Stack API" as API
    participant "Convex Backend" as Backend

    User->>App: "Access protected resource"
    App->>Stack: "getUser()"
    Stack->>API: "GET /users/me"
    API-->>Stack: "User data + JWT tokens"
    Stack-->>App: "CurrentUser object"

    App->>Convex: "Initialize ConvexReactClient"
    App->>Stack: "getConvexClientAuth({ tokenStore: 'nextjs-cookie' })"
    Stack-->>App: "Auth callback function"
    App->>Convex: "convex.setAuth(authCallback)"

    User->>App: "Execute Convex query/mutation"
    App->>Convex: "useQuery(api.myFunctions.listNumbers)"
    Convex->>Stack: "authCallback({ forceRefreshToken: false })"
    Stack->>Stack: "session.getOrFetchLikelyValidTokens(20_000)"
    Stack-->>Convex: "JWT access token"
    
    Convex->>Backend: "Query with JWT in Authorization header"
    Backend->>Backend: "Verify JWT against Stack JWKS"
    Backend->>Backend: "Extract user info from JWT payload"
    Backend-->>Convex: "Query results with user context"
    Convex-->>App: "Data for authenticated user"
    App-->>User: "Render protected content"

    Note over Stack,Backend: "JWT contains: userId, email, isAnonymous, etc."
Loading

52 files reviewed, 25 comments

Edit Code Review Bot Settings | Greptile

Copy link
@recurseml recurseml bot left a comment

Choose a reason for hiding this comment

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

Review by RecurseML

🔍 Review performed on 7a0bf86..4bc9f48

  Severity     Location     Issue     Delete  
Medium examples/convex/convex/_generated/server.d.ts:31 The 'primaryEmail' property in an API response object should use snake_case as 'primary_email'
Medium packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts:1673 Missing optional chaining or explicit error handling for nested nullable property
Medium packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts:1675 Missing optional chaining or explicit error handling for nested nullable property
Medium examples/convex/convex/_generated/server.js:67 Missing runAsynchronously wrapper for async operations in Node.js environment
✅ Files analyzed, no issues (2)

packages/stack-shared/src/utils/paginated-lists.tsx
examples/convex/app/page.tsx

⏭️ Files skipped (53)
  Locations  
.vscode/settings.json
apps/backend/src/lib/tokens.tsx
apps/backend/src/lib/types.tsx
apps/e2e/package.json
apps/e2e/tests/backend/backend-helpers.ts
apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/anonymous-comprehensive.test.ts
apps/e2e/tests/js/convex.test.ts
docs/docs-platform.yml
docs/templates/meta.json
docs/templates/others/convex.mdx
examples/convex/.gitignore
examples/convex/.prettierrc
examples/convex/README.md
examples/convex/app/globals.css
examples/convex/app/handler/[...stack]/page.tsx
examples/convex/app/layout.tsx
examples/convex/app/loading.tsx
examples/convex/app/server/inner.tsx
examples/convex/app/server/page.tsx
examples/convex/components/ConvexClientProvider.tsx
examples/convex/convex/README.md
examples/convex/convex/_generated/api.d.ts
examples/convex/convex/_generated/api.js
examples/convex/convex/_generated/dataModel.d.ts
examples/convex/convex/auth.config.ts
examples/convex/convex/myFunctions.ts
examples/convex/convex/schema.ts
examples/convex/convex/tsconfig.json
examples/convex/eslint.config.mjs
examples/convex/next.config.ts
examples/convex/package-lock.json
examples/convex/package.json
examples/convex/postcss.config.mjs
examples/convex/public/convex.svg
examples/convex/stack/client.tsx
examples/convex/stack/server.tsx
examples/convex/tsconfig.json
packages/js/package.json
packages/react/package.json
packages/stack-shared/src/sessions.ts
packages/stack/package.json
packages/template/package-template.json
packages/template/package.json
packages/template/src/index.ts
packages/template/src/integrations/convex.ts
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
packages/template/src/lib/stack-app/apps/interfaces/client-app.ts
packages/template/src/lib/stack-app/apps/interfaces/server-app.ts
packages/template/src/lib/stack-app/common.ts
packages/template/src/lib/stack-app/index.ts
packages/template/src/lib/stack-app/users/index.ts
packages/template/src/providers/stack-provider-client.tsx
pnpm-lock.yaml

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

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

⚠️ Outside diff range comments (3)
docs/templates/others/convex.mdx (1)

99-100: Fix example link to point to Convex sample.

The link points to the Supabase example.

Apply this diff:

-You can find the full example [here on GitHub](https://github.com/stack-auth/stack-auth/tree/main/examples/supabase).
+You can find the full example [here on GitHub](https://github.com/stack-auth/stack-auth/tree/main/examples/convex).
apps/backend/src/lib/tokens.tsx (2)

91-98: Anonymous-audience validation uses legacy role; switch to is_anonymous and fix log messages.

With the new payload, role is always "authenticated". The current check will misclassify anonymous tokens.

-    const isAnonymous = payload.role === 'anon';
-    if (aud.endsWith(":anon") && !isAnonymous) {
-      console.warn("Unparsable access token. Role is set to anon, but audience is not an anonymous audience.", { accessToken, payload });
+    const isAnonymous = payload.is_anonymous === true;
+    if (aud.endsWith(":anon") && !isAnonymous) {
+      console.warn("Unparsable access token. Audience is anonymous (:anon) but payload.is_anonymous is false.", { payload });
       return Result.error(new KnownErrors.UnparsableAccessToken());
-    } else if (!aud.endsWith(":anon") && isAnonymous) {
-      console.warn("Unparsable access token. Audience is not an anonymous audience, but role is set to anon.", { accessToken, payload });
+    } else if (!aud.endsWith(":anon") && isAnonymous) {
+      console.warn("Unparsable access token. Audience 
67E6
is non-anonymous but payload.is_anonymous is true.", { payload });
       return Result.error(new KnownErrors.UnparsableAccessToken());
     }

85-85: Do not log raw access tokens.

Leaking JWTs in logs is a security risk. Remove token from logs or truncate/hash it.

-        console.warn("Unparsable access token. This might be a user error, but if it happens frequently, it's a sign of a misconfiguration.", { accessToken, error });
+        console.warn("Unparsable access token. This might be a user error, but if it happens frequently, it's a sign of a misconfiguration.", { error });
🧹 Nitpick comments (32)
examples/convex/.gitignore (2)

7-11: Yarn Berry: also unignore .yarn/sdks.

Aligns with Yarn’s recommended patterns for editor SDKs.

Apply this diff:

 .yarn/*
 !.yarn/patches
 !.yarn/plugins
 !.yarn/releases
 !.yarn/versions
+!.yarn/sdks

16-19: Optionally ignore Turborepo cache.

If the example uses Turbo (common with Next.js), ignore its cache.

Apply this diff:

 # next.js
 /.next/
 /out/
+.turbo
examples/convex/eslint.config.mjs (1)

12-16: Add ignore patterns for build and generated code

Minimize lint churn and CI time by ignoring common outputs and Convex-generated files.

Apply this diff:

-const eslintConfig = [
-  ...compat.extends("next/core-web-vitals", "next/typescript"),
-];
+const eslintConfig = [
+  {
+    ignores: ["node_modules/**", ".next/**", "out/**", "convex/_generated/**"],
+  },
+  ...compat.extends("next/core-web-vitals", "next/typescript"),
+];
examples/convex/.prettierrc (1)

1-2: Empty .prettierrc in examples/convex masks root Prettier config — remove or populate intentionally.

examples/convex/.prettierrc is {}; Prettier stops at the first config file, preventing inheritance of repo-wide rules (examples/convex/package.json declares "prettier"). Delete the file to inherit the root config or add the intended overrides.

examples/convex/convex/tsconfig.json (2)

10-10: Align moduleResolution casing with TS defaults ("bundler").

TS enum values are lowercase in current docs. Use "bundler" for consistency with the other tsconfig.

-    "moduleResolution": "Bundler",
+    "moduleResolution": "bundler",

2-5: JSON comments are fine for TS, but can break non-TS tools.

If any tooling parses this as strict JSON, comments may error. Consider switching to tsconfig.jsonc or removing comments if issues arise.

examples/convex/package.json (1)

21-33: Add engines and a typecheck script for DX.

Helps catch TS errors and ensure correct Node for Next 15.

   "devDependencies": {
     "@eslint/eslintrc": "^3",
@@
     "typescript": "^5"
-  }
+  },
+  "engines": {
+    "node": ">=18.18.0 || >=20"
+  }

And consider:

   "scripts": {
@@
-    "lint": "next lint"
+    "lint": "next lint",
+    "typecheck": "tsc --noEmit"
   },
examples/convex/tsconfig.json (2)

10-11: Keep module/moduleResolution casing consistent across tsconfigs.

You use module: "esnext"/moduleResolution: "bundler" here vs ESNext/Bundler in the Convex tsconfig. Prefer consistent lowercase forms.

-    "module": "esnext",
-    "moduleResolution": "bundler",
+    "module": "esnext",
+    "moduleResolution": "bundler",

And in examples/convex/convex/tsconfig.json, switch to lowercase as noted.


1-27: Consider sharing a base tsconfig to avoid drift.

Both tsconfigs share many options. Create examples/convex/tsconfig.base.json and extends it from each to keep them aligned.

Happy to draft a base config if you want.

examples/convex/postcss.config.mjs (2)

1-5: Add autoprefixer to PostCSS config and devDependencies

Verification: examples/convex/package.json does not include autoprefixer — add it to devDependencies and include "autoprefixer" in examples/convex/postcss.config.mjs plugins (e.g. plugins: ["@tailwindcss/postcss", "autoprefixer"]).


1-3: Use object-form plugin config for PostCSS (matches Tailwind v4 docs)

Replace the array with an object mapping to match Tailwind v4 examples and avoid postcss-load-config resolution issues:

-const config = {
-  plugins: ["@tailwindcss/postcss"],
-};
+const config = {
+  plugins: {
+    "@tailwindcss/postcss": {},
+  },
+};
packages/template/package.json (1)

64-64: Duplicate rimraf in dependencies and devDependencies — keep only in devDependencies.

This avoids installing it for consumers unnecessarily.

Apply:

--- a/packages/template/package.json
+++ b/packages/template/package.json
@@
   "dependencies": {
@@
-    "rimraf": "^5.0.5",
@@
   },
@@
   "devDependencies": {
@@
     "rimraf": "^5.0.5",
@@
   }

Also applies to: 101-101

apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/anonymous-comprehensive.test.ts (1)

37-38: Clarify test semantics and assert both tokens

Title says “different kid and role,” but role is now ‘authenticated’ for both; difference is is_anonymous. Consider renaming and asserting regular payload is_anonymous === false.

docs/templates/others/convex.mdx (1)

61-69: Deduplicate setup sections or clarify flow.

This “Setup a new Next.js project” repeats the earlier project creation. Consider consolidating to a single flow (wizard vs manual) to avoid confusion.

examples/convex/convex/README.md (1)

4-4: Avoid bare URL (MD034).

Wrap the URL to satisfy markdownlint.

Apply this diff:

-See https://docs.convex.dev/functions for more.
+See <https://docs.convex.dev/functions> for more.
packages/template/src/integrations/convex.ts (1)

4-16: Make options optional and tighten the return type.

This improves DX and avoids accidental shape drift of provider objects.

+export type ConvexCustomJwtProvider = {
+  type: "customJwt";
+  issuer: string;
+  jwks: string;
+  algorithm: "ES256";
+};
+
-export function getConvexProvidersConfig(options: {
-  projectId?: string,
-}) {
+export function getConvexProvidersConfig(
+  options: { projectId?: string } = {},
+): ConvexCustomJwtProvider[] {
   const projectId = options.projectId ?? getDefaultProjectId();
   return [
     {
-      type: "customJwt",
+      type: "customJwt",
       issuer: urlString`https://api.stack-auth.com/api/v1/projects/${projectId}`,
       jwks: urlString`https://api.stack-auth.com/api/v1/projects/${projectId}/.well-known/jwks.json?include_anonymous=true`,
-      algorithm: "ES256",
+      algorithm: "ES256",
     },
   ];
 }

Also applies to: 3-3

examples/convex/convex/myFunctions.ts (2)

31-33: Avoid mutating the result array from the DB.

Use a non-mutating reverse for clarity and safety.

-      numbers: numbers.reverse().map((number) => number.value),
+      numbers: numbers.slice().reverse().map((n) => n.value),

21-26: Clamp count to a sane upper bound to avoid large reads.

Prevent accidental large take() calls.

-      .order("desc")
-      .take(args.count);
+      .order("desc");
+    const limit = Math.max(0, Math.min(args.count, 100));
+    const recent = await numbers.take(limit);
+    // Use `recent` below instead of `numbers`

Follow through by replacing subsequent numbers references with recent.

packages/template/src/lib/stack-app/users/index.ts (1)

288-300: Remove duplicate keys in SyncedPartialUser intersection.

TokenPartialUser already includes several fields being re-picked. Avoid duplication for clarity.

-export type SyncedPartialUser = TokenPartialUser & Pick<
-  User,
-  | "id"
-  | "displayName"
-  | "primaryEmail"
-  | "primaryEmailVerified"
-  | "profileImageUrl"
-  | "signedUpAt"
-  | "clientMetadata"
-  | "clientReadOnlyMetadata"
-  | "isAnonymous"
-  | "hasPassword"
->;
+export type SyncedPartialUser = TokenPartialUser & Pick<
+  User,
+  | "profileImageUrl"
+  | "signedUpAt"
+  | "clientMetadata"
+  | "clientReadOnlyMetadata"
+  | "hasPassword"
+>;
examples/convex/stack/client.tsx (1)

3-6: Avoid hard-coded baseUrl; prefer env with sensible default.

Improves portability across environments.

-export const stackClientApp = new StackClientApp({
-  baseUrl: "http://localhost:8102",
-  tokenStore: "nextjs-cookie",
-});
+export const stackClientApp = new StackClientApp({
+  baseUrl: process.env.NEXT_PUBLIC_STACK_BASE_URL || "http://localhost:8102",
+  tokenStore: "nextjs-cookie",
+});
examples/convex/app/handler/[...stack]/page.tsx (1)

5-5: Minor JSX style nit: remove spaces around props.

Matches common formatting and linters.

-   return <StackHandler fullPage app = { stackServerApp } routeProps = { props } />;
+   return <StackHandler fullPage app={stackServerApp} routeProps={props} />;
packages/template/src/lib/stack-app/index.ts (1)

31-34: LGTM — replace remaining GetUserOptions import with GetCurrentUserOptions.

Found one usage: packages/template/src/lib/hooks.tsx — change
import { GetUserOptions as AppGetUserOptions, CurrentInternalUser, CurrentUser, StackClientApp } from "./stack-app";
to
import { GetCurrentUserOptions as AppGetUserOptions, CurrentInternalUser, CurrentUser, StackClientApp } from "./stack-app";

examples/convex/app/globals.css (1)

22-26: Use the intended Geist font and declare color-scheme.

Body currently forces Arial, ignoring the Geist font variables configured in layout. Also declare color-scheme to assist UA theming.

 body {
   color: var(--foreground);
   background: var(--background);
-  font-family: Arial, Helvetica, sans-serif;
+  color-scheme: light dark;
+  font-family: var(--font-geist-sans), system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
 }
examples/convex/components/ConvexClientProvider.tsx (1)

8-10: Avoid redundant tokenStore override if app already uses "nextjs-cookie".

If stackClientApp is constructed with tokenStore "nextjs-cookie", passing it again is redundant. Not a blocker.

-convex.setAuth(
-  stackClientApp.getConvexClientAuth({ tokenStore: "nextjs-cookie" })
-);
+convex.setAuth(stackClientApp.getConvexClientAuth());
packages/template/src/lib/stack-app/common.ts (1)

40-57: Tighten GenericQueryCtx typing (avoid any).

Use unknown instead of any to prevent unsoundness while keeping the generic open.

-      ctx: GenericQueryCtx<any>,
+      ctx: GenericQueryCtx<unknown>,
packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (1)

3-3: Use a type-only import for Convex types.

Prevents pulling "convex/server" into runtime bundles.

-import { GenericQueryCtx } from "convex/server";
+import type { GenericQueryCtx } from "convex/server";
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (1)

906-949: getUser: logic parity with client + anonymous path preserved.

Reads well; duplication noted in TODO is accurate.

Consider extracting the common getUser flow (ensure persistent store, fetch session, anonymous handling) into a shared helper to remove duplication with the client impl.

packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (3)

1595-1611: Token-derived partial user: OK; add small guard for anonymous.

You can early-return on isAnonymous && options.or !== 'anonymous' before reading other payload fields to avoid unnecessary reads. Current behavior is correct as-is.


1627-1644: Remove unnecessary any-casts; types already flow from GetCurrentPartialUserOptions.

The ctx type matches the helper signature; avoid as unknown as any.

-        return await this._getPartialUserFromConvex(options.ctx as unknown as any);
+        return await this._getPartialUserFromConvex(options.ctx);

1679-1683: getConvexHttpClientAuth throws on missing token: OK; document behavior.

Consider a brief JSDoc noting it throws if no token is available to aid consumers.

packages/stack-shared/src/utils/paginated-lists.tsx (2)

219-219: Simplify merge cursor type to plain string.

The cursor is a JSON string; unions like "first" | "last" | [${string}] are misleading and break JSON.parse on non-JSON tokens.

-      override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', "first" | "last" | `[${string}]`, Filter, OrderBy>) {
+      override async _nextOrPrev(type: 'next' | 'prev', { limit, filter, orderBy, cursor }: ImplQueryOptions<'next' | 'prev', string, Filter, OrderBy>) {

29-31: Remove duplicate ImplQueryResult type.

ImplQueryResult is identical to QueryResult. Alias it to avoid drift.

-type ImplQueryResult<Item, Cursor> = { items: { item: Item, itemCursor: Cursor }[], isFirst: boolean, isLast: boolean, cursor: Cursor }
+type ImplQueryResult<Item, Cursor> = QueryResult<Item, Cursor>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a0bf86 and 4bc9f48.

⛔ Files ignored due to path filters (8)
  • examples/convex/convex/_generated/api.d.ts is excluded by !**/_generated/**
  • examples/convex/convex/_generated/api.js is excluded by !**/_generated/**
  • examples/convex/convex/_generated/dataModel.d.ts is excluded by !**/_generated/**
  • examples/convex/convex/_generated/server.d.ts is excluded by !**/_generated/**
  • examples/convex/convex/_generated/server.js is excluded by !**/_generated/**
  • examples/convex/package-lock.json is excluded by !**/package-lock.json
  • examples/convex/public/convex.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (50)
  • .vscode/settings.json (1 hunks)
  • apps/backend/src/lib/tokens.tsx (3 hunks)
  • apps/backend/src/lib/types.tsx (0 hunks)
  • apps/e2e/package.json (1 hunks)
  • apps/e2e/tests/backend/backend-helpers.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/anonymous-comprehensive.test.ts (1 hunks)
  • apps/e2e/tests/js/convex.test.ts (1 hunks)
  • docs/docs-platform.yml (1 hunks)
  • docs/templates/meta.json (1 hunks)
  • docs/templates/others/convex.mdx (1 hunks)
  • examples/convex/.gitignore (1 hunks)
  • examples/convex/.prettierrc (1 hunks)
  • examples/convex/README.md (1 hunks)
  • examples/convex/app/globals.css (1 hunks)
  • examples/convex/app/handler/[...stack]/page.tsx (1 hunks)
  • examples/convex/app/layout.tsx (1 hunks)
  • examples/convex/app/loading.tsx (1 hunks)
  • examples/convex/app/page.tsx (1 hunks)
  • examples/convex/app/server/inner.tsx (1 hunks)
  • examples/convex/app/server/page.tsx (1 hunks)
  • examples/convex/components/ConvexClientProvider.tsx (1 hunks)
  • examples/convex/convex/README.md (1 hunks)
  • examples/convex/convex/auth.config.ts (1 hunks)
  • examples/convex/convex/myFunctions.ts (1 hunks)
  • examples/convex/convex/schema.ts (1 hunks)
  • examples/convex/convex/tsconfig.json (1 hunks)
  • examples/convex/eslint.config.mjs (1 hunks)
  • examples/convex/next.config.ts (1 hunks)
  • examples/convex/package.json (1 hunks)
  • examples/convex/postcss.config.mjs (1 hunks)
  • examples/convex/stack/client.tsx (1 hunks)
  • examples/convex/stack/server.tsx (1 hunks)
  • examples/convex/tsconfig.json (1 hunks)
  • packages/js/package.json (1 hunks)
  • packages/react/package.json (1 hunks)
  • packages/stack-shared/src/sessions.ts (3 hunks)
  • packages/stack-shared/src/utils/paginated-lists.tsx (1 hunks)
  • packages/stack/package.json (1 hunks)
  • packages/template/package-template.json (1 hunks)
  • packages/template/package.json (1 hunks)
  • packages/template/src/index.ts (2 hunks)
  • packages/template/src/integrations/convex.ts (1 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (5 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (3 hunks)
  • packages/template/src/lib/stack-app/apps/interfaces/client-app.ts (4 hunks)
  • packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (2 hunks)
  • packages/template/src/lib/stack-app/common.ts (3 hunks)
  • packages/template/src/lib/stack-app/index.ts (1 hunks)
  • packages/template/src/lib/stack-app/users/index.ts (2 hunks)
  • packages/template/src/providers/stack-provider-client.tsx (0 hunks)
💤 Files with no reviewable changes (2)
  • apps/backend/src/lib/types.tsx
  • packages/template/src/providers/stack-provider-client.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • examples/convex/convex/schema.ts
  • apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/anonymous-comprehensive.test.ts
  • examples/convex/components/ConvexClientProvider.tsx
  • examples/convex/app/server/page.tsx
  • examples/convex/next.config.ts
  • examples/convex/app/handler/[...stack]/page.tsx
  • examples/convex/app/server/inner.tsx
  • examples/convex/convex/myFunctions.ts
  • examples/convex/app/layout.tsx
  • examples/convex/convex/auth.config.ts
  • packages/template/src/lib/stack-app/users/index.ts
  • packages/template/src/integrations/convex.ts
  • apps/e2e/tests/js/convex.test.ts
  • packages/template/src/lib/stack-app/index.ts
  • examples/convex/stack/client.tsx
  • packages/template/src/lib/stack-app/common.ts
  • packages/template/src/index.ts
  • apps/e2e/tests/backend/backend-helpers.ts
  • examples/convex/app/page.tsx
  • packages/template/src/lib/stack-app/apps/interfaces/client-app.ts
  • examples/convex/app/loading.tsx
  • packages/template/src/lib/stack-app/apps/interfaces/server-app.ts
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
  • packages/stack-shared/src/utils/paginated-lists.tsx
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • apps/backend/src/lib/tokens.tsx
  • examples/convex/stack/server.tsx
  • packages/stack-shared/src/sessions.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/anonymous-comprehensive.test.ts
  • apps/e2e/tests/js/convex.test.ts
packages/template/**

📄 CodeRabbit inference engine (AGENTS.md)

When modifying the SDK copies, make changes in packages/template (source of truth)

Files:

  • packages/template/src/lib/stack-app/users/index.ts
  • packages/template/src/integrations/convex.ts
  • packages/template/src/lib/stack-app/index.ts
  • packages/template/src/lib/stack-app/common.ts
  • packages/template/src/index.ts
  • packages/template/src/lib/stack-app/apps/interfaces/client-app.ts
  • packages/template/src/lib/stack-app/apps/interfaces/server-app.ts
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
  • packages/template/package-template.json
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/package.json
packages/js/**

📄 CodeRabbit inference engine (AGENTS.md)

Never modify packages/js; make changes in packages/template instead as packages/js is a copy

Files:

  • packages/js/package.json
packages/stack/**

📄 CodeRabbit inference engine (AGENTS.md)

Never modify packages/stack; make changes in packages/template instead as packages/stack is a copy

Files:

  • packages/stack/package.json
🧬 Code graph analysis (21)
apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/anonymous-comprehensive.test.ts (1)
packages/stack-shared/src/sessions.ts (1)
  • payload (34-37)
examples/convex/components/ConvexClientProvider.tsx (1)
examples/convex/stack/client.tsx (1)
  • stackClientApp (3-6)
examples/convex/app/server/page.tsx (2)
examples/convex/convex/_generated/api.js (2)
  • api (21-21)
  • api (21-21)
examples/convex/app/server/inner.tsx (1)
  • Home (6-31)
examples/convex/app/handler/[...stack]/page.tsx (2)
examples/convex/stack/server.tsx (1)
  • stackServerApp (5-7)
docs/src/app/handler/[...stack]/page.tsx (1)
  • Handler (4-6)
examples/convex/app/server/inner.tsx (2)
examples/convex/convex/_generated/api.js (2)
  • api (21-21)
  • api (21-21)
examples/convex/convex/myFunctions.ts (1)
  • addNumber (38-56)
examples/convex/convex/myFunctions.ts (3)
examples/convex/convex/_generated/server.js (6)
  • query (29-29)
  • query (29-29)
  • mutation (49-49)
  • mutation (49-49)
  • action (72-72)
  • action (72-72)
examples/convex/stack/client.tsx (1)
  • stackClientApp (3-6)
examples/convex/convex/_generated/api.js (2)
  • api (21-21)
  • api (21-21)
examples/convex/app/layout.tsx (2)
examples/convex/stack/server.tsx (1)
  • stackServerApp (5-7)
examples/convex/components/ConvexClientProvider.tsx (1)
  • ConvexClientProvider (12-18)
examples/convex/convex/auth.config.ts (1)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
  • projectId (1338-1340)
packages/template/src/lib/stack-app/users/index.ts (1)
packages/template/src/lib/stack-app/index.ts (2)
  • User (105-105)
  • ServerUser (103-103)
packages/template/src/integrations/convex.ts (2)
packages/template/src/lib/stack-app/apps/implementations/common.ts (1)
  • getDefaultProjectId (67-69)
packages/stack-shared/src/utils/urls.tsx (1)
  • urlString (314-316)
apps/e2e/tests/js/convex.test.ts (2)
apps/e2e/tests/helpers.ts (1)
  • it (11-11)
apps/e2e/tests/js/js-helpers.ts (1)
  • createApp (41-86)
packages/template/src/lib/stack-app/common.ts (1)
packages/template/src/lib/stack-app/index.ts (2)
  • GetCurrentUserOptions (31-31)
  • GetCurrentUserOptions (33-33)
examples/convex/app/page.tsx (3)
examples/convex/app/server/inner.tsx (1)
  • Home (6-31)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
  • useUser (1550-1592)
examples/convex/convex/myFunctions.ts (1)
  • addNumber (38-56)
packages/template/src/lib/stack-app/apps/interfaces/client-app.ts (2)
packages/template/src/lib/stack-app/common.ts (3)
  • TokenStoreInit (64-73)
  • GetCurrentUserOptions (31-38)
  • GetCurrentPartialUserOptions (40-56)
packages/template/src/lib/stack-app/users/index.ts (3)
  • ProjectCurrentUser (277-277)
  • TokenPartialUser (279-286)
  • SyncedPartialUser (288-300)
examples/convex/app/loading.tsx (1)
examples/supabase/app/loading.tsx (1)
  • Loading (1-5)
packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (2)
packages/template/src/lib/stack-app/common.ts (2)
  • GetCurrentUserOptions (31-38)
  • GetCurrentPartialUserOptions (40-56)
packages/template/src/lib/stack-app/users/index.ts (4)
  • ProjectCurrentServerUser (401-401)
  • ServerUser (395-395)
  • TokenPartialUser (279-286)
  • SyncedPartialServerUser (403-406)
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (3)
packages/template/src/lib/stack-app/common.ts (1)
  • GetCurrentUserOptions (31-38)
packages/template/src/lib/stack-app/index.ts (3)
  • GetCurrentUserOptions (31-31)
  • GetCurrentUserOptions (33-33)
  • ServerUser (103-103)
packages/template/src/lib/stack-app/users/index.ts (2)
  • ProjectCurrentServerUser (401-401)
  • ServerUser (395-395)
packages/stack-shared/src/utils/paginated-lists.tsx (2)
packages/stack-shared/src/utils/errors.tsx (1)
  • StackAssertionError (69-85)
packages/stack-shared/src/utils/arrays.tsx (1)
  • range (97-109)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (5)
packages/template/src/lib/stack-app/common.ts (3)
  • GetCurrentUserOptions (31-38)
  • GetCurrentPartialUserOptions (40-56)
  • TokenStoreInit (64-73)
packages/template/src/lib/stack-app/index.ts (2)
  • GetCurrentUserOptions (31-31)
  • GetCurrentUserOptions (33-33)
packages/template/src/lib/stack-app/users/index.ts (3)
  • ProjectCurrentUser (277-277)
  • TokenPartialUser (279-286)
  • SyncedPartialUser (288-300)
packages/stack-shared/src/sessions.ts (1)
  • InternalSession (72-242)
packages/stack-shared/src/utils/errors.tsx (1)
  • throwErr (10-19)
apps/backend/src/lib/tokens.tsx (2)
packages/stack-shared/src/sessions.ts (2)
  • payload (34-37)
  • AccessTokenPayload (23-23)
packages/stack-shared/src/utils/jwt.tsx (1)
  • signJWT (38-54)
packages/stack-shared/src/sessions.ts (1)
packages/stack-shared/src/schema-fields.ts (4)
  • yupObject (247-251)
  • yupString (187-190)
  • yupNumber (191-194)
  • yupBoolean (195-198)
🪛 markdownlint-cli2 (0.18.1)
examples/convex/convex/README.md

4-4: Bare URL used

(MD034, no-bare-urls)

examples/convex/README.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


23-23: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: setup-tests
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: Security Check
🔇 Additional comments (42)
examples/convex/eslint.config.mjs (1)

1-16: LGTM on Flat Config + Next compat setup

Using FlatCompat to bridge Next’s legacy configs in a flat ESLint config looks correct.

packages/template/package-template.json (2)

143-144: LGTM: adding Convex as a devDependency aligns with the template being the source of truth.

Change is localized to the template; version range is reasonable.


143-144: Confirm convex dependency scope (devDependency vs peerDependency) — manual check required

  • If any runtime code shipped to consumers imports convex (convex, convex/react, convex/server) promote it to a peerDependency (optionally optional) and keep it as a devDependency for local builds.
  • If Convex is only used for examples/demo/tooling/codegen, devDependency is correct.
  • Consider gating it with the template preprocessor to avoid adding it to non-template packages.

Automated search in the sandbox failed; run locally to detect runtime imports:
rg -n -S -uu --glob '!/node_modules/' --glob '!/dist/' '\bconvex(?:/|$)'

File: packages/template/package-template.json (around lines 143–144)

Suggested gated variant:

     "tailwindcss": "^3.4.4",
     "tsup": "^8.0.2",
-    "convex": "^1.27.0"
+    "//": "NEXT_LINE_PLATFORM template",
+    "convex": "^1.27.0"
examples/convex/next.config.ts (1)

1-7: LGTM – minimal Next config scaffold is valid.

Typed NextConfig import and default export look good.

examples/convex/package.json (1)

14-20: Version set looks compatible.

Next 15.2.x with React 19 and eslint 9 is consistent; convex ^1.27.0 seems reasonable.

If CI isn’t covering it, please confirm local next dev starts without peer warnings.

packages/template/package.json (1)

103-103: tsup addition aligns with tsup-node script — LGTM.

.vscode/settings.json (1)

116-118: LGTM: organizeImports runs before eslint fixAll

Order makes sense and avoids import-sort churn.

apps/e2e/tests/backend/backend-helpers.ts (1)

211-211: LGTM: payload now asserts is_anonymous boolean

Matches the new token shape and strengthens validation.

docs/templates/meta.json (1)

36-36: Docs nav entry verified — page and platform mapping present
docs/templates/others/convex.mdx exists and docs/docs-platform.yml contains - path: others/convex.mdx with platforms ["next","react","js"].

packages/react/package.json (1)

90-92: No action required — template already updated

packages/template/package.json already contains "tsup": "^8.0.2" and "convex": "^1.27.0", so the edit in packages/react is generated.

apps/e2e/package.json (1)

17-18: Add Convex dep — verified single-version alignment across workspace

Convex is declared as ^1.27.0 in all package.json files that reference it (apps/e2e, examples/convex, packages/js, packages/react, packages/stack, packages/template) — no change required.

packages/stack/package.json (1)

98-100: Do not edit auto-generated packages/stack — template already has "convex"

packages/template/package.json and packages/template/package-template.json already list "convex": "^1.27.0"; no change to packages/stack required.

examples/convex/convex/schema.ts (1)

1-12: LGTM.

Schema is minimal and valid for the example.

docs/docs-platform.yml (1)

276-278: Docs platform entry looks good.

Page enabled for next/react/js; consistent with content scope.

packages/template/src/index.ts (1)

3-4: Export addition LGTM; ensure external docs match symbol name.

Publicly exporting getConvexProvidersConfig from the template is correct.

Verify that consumer docs/snippets import the function with the exact pluralized name and from the correct published package.

examples/convex/app/server/inner.tsx (1)

6-31: LGTM.

Client component correctly consumes a preloaded query and mutation.

docs/templates/others/convex.mdx (1)

84-97: Complete the client snippet: add missing imports and a real Convex client.

convex/_generated/client does not export a convexClient instance. Provide a constructed client and the missing StackClientApp import to avoid copy‑paste errors.

Apply this diff:

-```typescript title="stack/client.ts"
-import { convexClient } from "../convex/_generated/client";
-
-export const stackClientApp = new StackClientApp({
+```typescript title="stack/client.ts"
+import { StackClientApp } from "@stackframe/stack"; // verify package name
+import { ConvexReactClient } from "con
67DE
vex/react";
+const convexClient = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
+
+export const stackClientApp = new StackClientApp({
   projectId: process.env.NEXT_PUBLIC_STACK_PROJECT_ID,
   publishableClientKey: process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY,
   tokenStore: "nextjs-cookie",
   integrations: {
     convex: {
       client: convexClient,
     },
   },
 });


- Ensure the correct import path for `StackClientApp`.
- Document `NEXT_PUBLIC_CONVEX_URL` in the ENV section.

</blockquote></details>
<details>
<summary>examples/convex/app/loading.tsx (1)</summary><blockquote>

`1-6`: **LGTM: minimal Suspense loading placeholder.**

</blockquote></details>
<details>
<summary>examples/convex/stack/server.tsx (1)</summary><blockquote>

`1-7`: **LGTM: server-only app with cookie token store.**

Appropriate for Next.js server routes; complements Convex example.

</blockquote></details>
<details>
<summary>examples/convex/app/server/page.tsx (1)</summary><blockquote>

`5-24`: **LGTM: server preloading is correct.**

Preload + preloadedQueryResult usage and passing Preloaded to Home are correct.

</blockquote></details>
<details>
<summary>examples/convex/components/ConvexClientProvider.tsx (1)</summary><blockquote>

`12-18`: **LGTM: single Convex client instance at module scope.**

Good pattern to avoid recreating clients per render.

</blockquote></details>
<details>
<summary>apps/backend/src/lib/tokens.tsx (1)</summary><blockquote>

`153-164`: **Centralized payload construction looks good.**

Typed payload via AccessTokenPayload (omitting iss/aud) aligns with the new schema and ensures claim consistency.


Confirm all producers set role: "authenticated" (including anonymous) and only toggle is_anonymous, and that no older producers still emit role: "anon".

</blockquote></details>
<details>
<summary>packages/stack-shared/src/sessions.ts (2)</summary><blockquote>

`140-151`: **Good API addition.**

getAccessTokenIfNotExpiredYet with a 60s cap is a sensible guardrail for short-lived tokens.

---

`7-21`: **Schema is strict; ensure all token producers meet it.** signJWT is only used in apps/backend/src/lib/tokens.tsx — generateAccessToken builds the payload with project_id, branch_id, refresh_token_id, role: 'authenticated', name, email, email_verified, selected_team_id (nullable) and is_anonymous, and sets iss/aud via getIssuer/getAudience; tests call createAuthTokens → generateAccessToken. No other signJWT producers found.

</blockquote></details>
<details>
<summary>apps/e2e/tests/js/convex.test.ts (1)</summary><blockquote>

`1-3`: **Vitest environment and globals verified — OK.**

apps/e2e/vitest.config.ts sets test.environment = 'node' and apps/e2e/tests/setup.ts imports/extends Vitest's expect, so the test uses Vitest's global expect and runs in Node.

</blockquote></details>
<details>
<summary>examples/convex/app/layout.tsx (1)</summary><blockquote>

`35-37`: **StackProvider serializes server app — no RSC serialization issue.**

NextStackProvider calls app[stackAppInternalsSymbol].toClientJson() and passes serialized={true} to the client-side StackProviderClient, which reconstructs via StackClientApp[stackAppInternalsSymbol].fromClientJson(...); passing stackServerApp in the Next.js layout (examples/convex/app/layout.tsx) is safe.

</blockquote></details>
<details>
<summary>packages/template/src/lib/stack-app/common.ts (2)</summary><blockquote>

`2-2`: **Good: type-only import avoids bundling Convex server code.**

---

`31-38`: **Rename to GetCurrentUserOptions looks sound.**

</blockquote></details>
<details>
<summary>packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (2)</summary><blockquote>

`28-36`: **useUser overload updates: OK.**

---

`38-45`: **getUser overload updates: OK.**

</blockquote></details>
<details>
<summary>packages/template/src/lib/stack-app/apps/interfaces/client-app.ts (6)</summary><blockquote>

`59-61`: **Convex client auth helpers: API shape looks good.**

---

`63-67`: **useUser overloads migrated to GetCurrentUserOptions: OK.**

---

`69-73`: **getUser overloads migrated to GetCurrentUserOptions: OK.**

---

`74-78`: **New getPartialUser overloads: OK.**

---

`79-83`: **New usePartialUser overloads: OK (see impl note in client).**


Please ensure the React-like implementation does not return a Promise for the convex variant (see comment in client-app-impl.ts).

---

`107-108`: **Constructor overload addition: OK.**

</blockquote></details>
<details>
<summary>packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (2)</summary><blockquote>

`24-24`: **Import swap to GetCurrentUserOptions: OK.**

---

`961-1018`: **useUser: matches new option type and existing behavior.**

</blockquote></details>
<details>
<summary>packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (4)</summary><blockquote>

`586-588`: **Minor: local var before return is fine.**

---

`1509-1544`: **getUser overload migration and flow: OK.**

---

`1546-1593`: **useUser overload migration and flow: OK.**

---

`1667-1683`: **Convex client auth helper: return shape and refresh handling look good.**

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@BilalG1 BilalG1 marked this pull request as draft September 23, 2025 17:52
Copy link
patched-codes bot commented Sep 23, 2025

Documentation Changes Required

1. stack-app.mdx (docs/templates/sdk/objects/stack-app.mdx)

Updates for getUser() and useUser()

  • Update parameter type references from GetUserOptions to GetCurrentUserOptions
  • Mark getUser() as deprecated and recommend using getCurrentUser() instead
  • Add documentation for new getCurrentUser() method with GetCurrentUserOptions
  • Update React hooks documentation to reference new methods

New Methods

  • Add documentation for new getCurrentPartialUser() method with GetCurrentPartialUserOptions
  • Add documentation for new usePartialUser() React hook

Type Updates

  • Update all references from GetUserOptions to GetCurrentUserOptions
  • Add explanation of TokenPartialUser and SyncedPartialServerUser return types

Convex Integration

  • Include new Convex integration options and parameters for relevant methods

2. get-user-or-parameter.mdx (docs/templates/snippets/get-user-or-parameter.mdx)

  • Update to reflect new options in GetCurrentUserOptions and GetCurrentPartialUserOptions
  • Document new 'from' parameter with options 'token' or 'convex'
  • Add documentation for the 'anonymous' option

Please ensure these changes are reflected in the relevant documentation files to keep them up-to-date with the latest code changes.

@BilalG1 BilalG1 marked this pull request as ready for review September 23, 2025 23:36
Copy link
claude bot commented Sep 23, 2025

Claude finished @BilalG1's task —— View job


I'll analyze this and get back to you.

@BilalG1 BilalG1 changed the base branch from convex to dev September 23, 2025 23:51
@N2D4 N2D4 changed the base branch from dev to convex September 24, 2025 10:18
@N2D4 N2D4 self-requested a review September 24, 2025 10:19
@N2D4 N2D4 changed the base branch from convex to dev September 24, 2025 10:42
Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (3)
apps/backend/src/lib/tokens.tsx (3)

82-87: Do not log raw access tokens or full payloads.

Leaking tokens/PII to logs is a security risk. Remove token and sensitive payload fields from logs.

Apply this diff:

-      } else if (error instanceof JOSEError) {
-        console.warn("Unparsable access token. This might be a user error, but if it happens frequently, it's a sign of a misconfiguration.", { accessToken, error });
+      } else if (error instanceof JOSEError) {
+        console.warn("Unparsable access token. If frequent, check configuration.", { error: { name: error.name, message: error.message } });
         return Result.error(new KnownErrors.UnparsableAccessToken());
       }

91-98: Align diagnostics with new is_anonymous flag and avoid sensitive logging.

Messages still reference legacy "role is anon" and log full payload. Use is_anonymous terminology and omit payload.

Apply this diff:

-    const isAnonymous = payload.is_anonymous ?? /* legacy, now we always set role to authenticated, TODO next-release remove */ payload.role === 'anon';
+    const isAnonymous =
+      payload.is_anonymous ??
+      /* legacy, now we always set role to authenticated, TODO next-release remove */ (payload as any).role === 'anon';
     if (aud.endsWith(":anon") && !isAnonymous) {
-      console.warn("Unparsable access token. Role is set to anon, but audience is not an anonymous audience.", { accessToken, payload });
+      console.warn("Unparsable access token: audience ends with ':anon' but is_anonymous=false.");
       return Result.error(new KnownErrors.UnparsableAccessToken());
     } else if (!aud.endsWith(":anon") && isAnonymous) {
-      console.warn("Unparsable access token. Audience is not an anonymous audience, but role is set to anon.", { accessToken, payload });
+      console.warn("Unparsable access token: is_anonymous=true but audience is not anonymous.");
       return Result.error(new KnownErrors.UnparsableAccessToken());
     }

70-79: Critical: untrusted 'aud' is being used to select JWKS/issuers — fix required

packages/stack-shared/src/utils/jwt.tsx decodes the token and calls getPrivateJwks({ audience }) to build the JWK set (lines ~60–67), and apps/backend/src/lib/tokens.tsx (lines ~70–79) also derives allowedIssuers from the decoded aud before verification. An attacker-controlled aud can therefore influence which keys/issuer are used to verify the token. Derive issuer/audience mapping from trusted configuration (tenant/project lookup) or use a preconfigured issuer→JWKS allowlist; do not use unverified token claims to select keys or trusted issuers.

🧹 Nitpick comments (9)
examples/convex/components/ConvexClientProvider.tsx (1)

4-7: Optional: add Convex API typing to the client.

If the generated API types are available, typing the client improves end-to-end safety.

 import { ConvexProvider, ConvexReactClient } from "convex/react";
+// If available:
+// import { api } from "@/convex/_generated/api";
@@
-const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL ?? "http://localhost:1234");
+// If you imported `api`, add the generic:
+// const convex = new ConvexReactClient<typeof api>(process.env.NEXT_PUBLIC_CONVEX_URL ?? "http://localhost:1234");
docs/templates/others/convex.mdx (1)

89-101: Use StackServerApp for Convex server examples to avoid client-only dependencies.

Importing a client instance (configured with "nextjs-cookie") into Convex server functions can pull in client/Next-only code. Prefer a server app instance for server-side ctx flows.

Example replacement snippet:

// convex/myFunctions.ts
import { v } from "convex/values";
import { query } from "./_generated/server";
import { StackServerApp } from "@stackframe/stack";

const stackServerApp = new StackServerApp({
  projectId: process.env.NEXT_PUBLIC_STACK_PROJECT_ID!,
  publishableClientKey: process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY!,
  secretServerKey: process.env.STACK_SECRET_SERVER_KEY!,
  tokenStore: "memory",
});

export const whoAmI = query({
  args: {},
  handler: async (ctx) => {
    const user = await stackServerApp.getPartialUser({ from: "convex", ctx });
    return user; // null when not authenticated
  },
});
vitest.workspace.ts (1)

7-8: Gate examples workspace behind an env flag instead of disabling outright.

Keeps CI stable while enabling local runs when needed.

Apply this diff:

-import { defineWorkspace } from 'vitest/config';
+import { defineWorkspace } from 'vitest/config';

-export default defineWorkspace([
-  'packages/*',
-  'apps/*',
-  'docs',
-  // 'examples/*', there is an issue with examples/convex causing vitest to stall
-]);
+const workspaces = [
+  'packages/*',
+  'apps/*',
+  'docs',
+  ...(process.env.INCLUDE_EXAMPLES ? ['examples/*'] : []),
+];
+
+// TODO: Track the stalling issue (examples/convex) and re-enable by default once fixed.
+export default defineWorkspace(workspaces);
apps/backend/src/lib/tokens.tsx (1)

106-113: Reuse computed isAnonymous instead of recomputing.

Avoid duplication; use the local isAnonymous variable.

Apply this diff:

-    const result = await accessTokenSchema.validate({
+    const result = await accessTokenSchema.validate({
       projectId: aud.split(":")[0],
       userId: payload.sub,
       branchId: branchId,
       refreshTokenId: payload.refresh_token_id ?? payload.refreshTokenId,
       exp: payload.exp,
-      isAnonymous: payload.is_anonymous ?? /* legacy, now we always set role to authenticated, TODO next-release remove */ payload.role === 'anon',
+      isAnonymous,
     });
apps/e2e/tests/js/convex.test.ts (3)

57-59: Avoid hardcoded Convex URL to reduce flakiness.

Use env vars with a fallback.

Apply this diff:

-  const convex = new ConvexReactClient("http://localhost:1234", { webSocketConstructor: MockWebSocket as any, expectAuth: true });
+  const url = process.env.CONVEX_URL ?? process.env.NEXT_PUBLIC_CONVEX_URL ?? "http://localhost:1234";
+  const convex = new ConvexReactClient(url, { webSocketConstructor: MockWebSocket as any, expectAuth: true });

87-89: Avoid hardcoded Convex URL for HTTP client as well.

Apply this diff:

-  const convex = new ConvexHttpClient("http://localhost:1234");
+  const url = process.env.CONVEX_URL ?? process.env.NEXT_PUBLIC_CONVEX_URL ?? "http://localhost:1234";
+  const convex = new ConvexHttpClient(url);

124-131: Prefer inline snapshots for structured objects (repo test guideline).

Switch to toMatchInlineSnapshot for the partial user.

Apply this diff:

-  expect(partialUser).toEqual({
-    id: user.id,
-    displayName: user.displayName,
-    primaryEmail: user.primaryEmail,
-    primaryEmailVerified: user.primaryEmailVerified,
-    isAnonymous: user.isAnonymous,
-  });
+  expect(partialUser).toMatchInlineSnapshot(`
+    {
+      "displayName": ${user.displayName ? `"${user.displayName}"` : "undefined"},
+      "id": "${user.id}",
+      "isAnonymous": ${user.isAnonymous},
+      "primaryEmail": "${user.primaryEmail}",
+      "primaryEmailVerified": ${user.primaryEmailVerified},
+    }
+  `);

Note: Adjust serializer placeholders if your snapshot-serializer.ts formats undefined differently.

packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (1)

3-4: Unify Convex context type to ConvexCtx across the public API

Use the shared ConvexCtx type (from ../../common) instead of importing GenericQueryCtx directly here. This keeps the interface aligned with GetCurrentPartialUserOptions and the server/client implementations.

-import type { GenericQueryCtx } from "convex/server";
-import { AsyncStoreProperty, GetCurrentPartialUserOptions, GetCurrentUserOptions } from "../../common";
+import { AsyncStoreProperty, GetCurrentPartialUserOptions, GetCurrentUserOptions, ConvexCtx } from "../../common";
@@
-    useUser(options: { from: "convex", ctx: GenericQueryCtx<any>, or?: "return-null" | "anonymous" }): ServerUser | null,
+    useUser(options: { from: "convex", ctx: ConvexCtx, or?: "return-null" | "anonymous" }): ServerUser | null,
@@
-    getUser(options: { from: "convex", ctx: GenericQueryCtx<any>, or?: "return-null" | "anonymous" }): Promise<ServerUser | null>,
+    getUser(options: { from: "convex", ctx: ConvexCtx, or?: "return-null" | "anonymous" }): Promise<ServerUser | null>,

Also applies to: 35-35, 44-44

packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)

1599-1615: Minor: unify anonymous gating for token-derived partial user

_currently _getTokenPartialUserFromSession performs anonymous gating. If you adopt the cache refactor above, move the gating to the caller (like shown) to keep the cache pure and reusable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bc9f48 and b86ad35.

📒 Files selected for processing (16)
  • apps/backend/src/lib/tokens.tsx (4 hunks)
  • apps/e2e/tests/backend/backend-helpers.ts (1 hunks)
  • apps/e2e/tests/js/convex.test.ts (1 hunks)
  • docs/templates/others/convex.mdx (1 hunks)
  • examples/convex/.env.development (1 hunks)
  • examples/convex/.env.test.local (1 hunks)
  • examples/convex/components/ConvexClientProvider.tsx (1 hunks)
  • examples/convex/convex/auth.config.ts (1 hunks)
  • examples/convex/convex/myFunctions.ts (1 hunks)
  • examples/convex/package.json (1 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (6 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (5 hunks)
  • packages/template/src/lib/stack-app/apps/interfaces/client-app.ts (4 hunks)
  • packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (2 hunks)
  • packages/template/src/lib/stack-app/common.ts (3 hunks)
  • vitest.workspace.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • examples/convex/.env.test.local
  • examples/convex/.env.development
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/convex/package.json
  • apps/e2e/tests/backend/backend-helpers.ts
  • examples/convex/convex/auth.config.ts
  • examples/convex/convex/myFunctions.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • vitest.workspace.ts
  • examples/convex/components/ConvexClientProvider.tsx
  • apps/e2e/tests/js/convex.test.ts
  • packages/template/src/lib/stack-app/common.ts
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/stack-app/apps/interfaces/client-app.ts
  • apps/backend/src/lib/tokens.tsx
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
  • packages/template/src/lib/stack-app/apps/interfaces/server-app.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/js/convex.test.ts
packages/template/**

📄 CodeRabbit inference engine (AGENTS.md)

When modifying the SDK copies, make changes in packages/template (source of truth)

Files:

  • packages/template/src/lib/stack-app/common.ts
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/stack-app/apps/interfaces/client-app.ts
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
  • packages/template/src/lib/stack-app/apps/interfaces/server-app.ts
🧬 Code graph analysis (7)
apps/e2e/tests/js/convex.test.ts (2)
apps/e2e/tests/helpers.ts (1)
  • it (11-11)
apps/e2e/tests/js/js-helpers.ts (1)
  • createApp (41-86)
packages/template/src/lib/stack-app/common.ts (1)
packages/template/src/lib/stack-app/index.ts (2)
  • GetCurrentUserOptions (31-31)
  • GetCurrentUserOptions (33-33)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (5)
packages/template/src/lib/stack-app/apps/implementations/common.ts (2)
  • createCache (22-27)
  • useAsyncCache (146-191)
packages/template/src/lib/stack-app/users/index.ts (3)
  • TokenPartialUser (279-286)
  • ProjectCurrentUser (277-277)
  • SyncedPartialUser (288-300)
packages/template/src/lib/stack-app/common.ts (4)
  • GetCurrentUserOptions (31-38)
  • GetCurrentPartialUserOptions (44-60)
  • ConvexCtx (40-42)
  • TokenStoreInit (68-77)
packages/stack-shared/src/sessions.ts (1)
  • InternalSession (72-242)
packages/stack-shared/src/utils/errors.tsx (1)
  • throwErr (10-19)
packages/template/src/lib/stack-app/apps/interfaces/client-app.ts (3)
packages/template/src/lib/stack-app/common.ts (3)
  • TokenStoreInit (68-77)
  • GetCurrentUserOptions (31-38)
  • GetCurrentPartialUserOptions (44-60)
packages/template/src/lib/stack-app/index.ts (2)
  • GetCurrentUserOptions (31-31)
  • GetCurrentUserOptions (33-33)
packages/template/src/lib/stack-app/users/index.ts (3)
  • ProjectCurrentUser (277-277)
  • TokenPartialUser (279-286)
  • SyncedPartialUser (288-300)
apps/backend/src/lib/tokens.tsx (3)
packages/stack-shared/src/sessions.ts (2)
  • payload (34-37)
  • AccessTokenPayload (23-23)
packages/stack-shared/src/utils/jwt.tsx (1)
  • signJWT (38-54)
packages/stack-shared/src/utils/env.tsx (1)
  • getEnvVariable (16-58)
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (2)
packages/template/src/lib/stack-app/apps/implementations/common.ts (2)
  • createCache (22-27)
  • useAsyncCache (146-191)
packages/template/src/lib/stack-app/common.ts (2)
  • ConvexCtx (40-42)
  • GetCurrentUserOptions (31-38)
packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (3)
packages/template/src/lib/stack-app/common.ts (2)
  • GetCurrentUserOptions (31-38)
  • GetCurrentPartialUserOptions (44-60)
packages/template/src/lib/stack-app/index.ts (3)
  • GetCurrentUserOptions (31-31)
  • GetCurrentUserOptions (33-33)
  • ServerUser (103-103)
packages/template/src/lib/stack-app/users/index.ts (4)
  • ProjectCurrentServerUser (401-401)
  • ServerUser (395-395)
  • TokenPartialUser (279-286)
  • SyncedPartialServerUser (403-406)
🪛 Biome (2.1.2)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts

[error] 1660-1660: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts

[error] 898-898: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1007-1007: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: Security Check
🔇 Additional comments (13)
examples/convex/components/ConvexClientProvider.tsx (3)

7-7: Fail fast on missing NEXT_PUBLIC_CONVEX_URL (avoid silent localhost in prod).

Defaulting to localhost can mask misconfigurations and break production. Throw in prod; dev can warn and fall back.

-const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL ?? "http://localhost:1234");
+const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL;
+if (!convexUrl) {
+  if (process.env.NODE_ENV === "production") {
+    throw new Error("NEXT_PUBLIC_CONVEX_URL is not set; configure your Convex deployment URL.");
+  } else {
+    console.warn("NEXT_PUBLIC_CONVEX_URL is not set; defaulting to http://localhost:1234 for development.");
+  }
+}
+const convex = new ConvexReactClient(convexUrl ?? "http://localhost:1234");

12-18: Provider setup LGTM — stable singleton client.

Module-scoped client avoids re-instantiation; wrapping children with ConvexProvider is correct.


8-10: Confirm cookie tokenStore security semantics (HttpOnly, SameSite, XSS exposure).

If tokens are client-readable cookies, they’re XSS-exposed. Ensure the "nextjs-cookie" store issues HttpOnly cookies (set server-side) with SameSite=Lax/Strict and short-lived access tokens.

Run to locate and inspect the implementation and usages:

packages/template/src/lib/stack-app/common.ts (1)

44-61: Keep GetCurrentPartialUserOptions intact; only update ConvexCtx reference.

No behavior change; just ensure the union references the new ConvexCtx.

Please verify downstream files don’t import "convex/server" due to this type change. If any do, we should migrate them to structural types or guard with peer optional types.

packages/template/src/lib/stack-app/apps/interfaces/client-app.ts (3)

59-61: Convex client auth helpers: LGTM

Exposing getConvexClientAuth/getConvexHttpClientAuth on the client interface is clear and consistent with the implementation.


74-82: Partial-user overloads on client interface: LGTM

The token/convex variants and the general overload map cleanly to the new types (TokenPartialUser, SyncedPartialUser).


105-106: Constructor overload tidy-up: LGTM

The concrete constructor overload aligns with the rest of the codebase.

packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (3)

155-160: Solid: cached Convex identity subject

Using a cache for ctx.auth.getUserIdentity().subject is a good call to avoid repeated provider calls.


881-904: Convex-based user lookup: LGTM

_getUserByConvex/_useUserByConvex mirror token/apiKey paths and properly honor includeAnonymous.


24-24: Missing server getPartialUser/usePartialUser implementations (interface drift)

Interfaces declare getPartialUser/usePartialUser, but this implementation file does not define them. This will break assignment to StackServerApp unless another class fills the gap.

Would you like me to port the client-side partial-user logic here (token-derived via access token payload; convex-derived via ctx.auth.getUserIdentity), plus React-like usePartialUser with a cache, mirroring the client’s _convexPartialUserCache?

packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (3)

590-592: Minor: LGTM

_getSession refactor (assign + return) is fine; no behavioral change.


1670-1686: Null-safe access to accessToken.token

tokens?.accessToken.token can throw if tokens exists but accessToken is undefined. Use optional chaining on accessToken too.

   return async (args: { forceRefreshToken: boolean }) => {
     const session = await this._getSession(options.tokenStore);
     if (!args.forceRefreshToken) {
       const tokens = await session.getOrFetchLikelyValidTokens(20_000);
-      return tokens?.accessToken.token ?? null;
+      return tokens?.accessToken?.token ?? null;
     }
     const tokens = await session.fetchNewTokens();
-    return tokens?.accessToken.token ?? null;
+    return tokens?.accessToken?.token ?? null;
   };
@@
   const session = await this._getSession(options.tokenStore);
   const tokens = await session.getOrFetchLikelyValidTokens(20_000);
-  return tokens?.accessToken.token ?? throwErr("No access token available");
+  return tokens?.accessToken?.token ?? throwErr("No access token available");

1617-1629: Convex identity mapping: avoid provider-specific field names

email_verified and is_anonymous are not standard across providers; Convex commonly exposes emailVerified. Coerce safely with fallbacks.

   return {
     id: auth.subject,
-    displayName: auth.name ?? null,
-    primaryEmail: auth.email ?? null,
-    primaryEmailVerified: auth.email_verified as boolean,
-    isAnonymous: auth.is_anonymous as boolean,
+    displayName: (auth as any).name ?? null,
+    primaryEmail: (auth as any).email ?? null,
+    primaryEmailVerified: Boolean((auth as any).emailVerified ?? (auth as any).email_verified ?? false),
+    isAnonymous: Boolean((auth as any).isAnonymous ?? (auth as any).is_anonymous ?? false),
   };

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

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)
.github/workflows/lint-and-build.yaml (1)

71-73: Add Convex production env copy — good placement and parity with other examples.

To avoid drift across workflows, consider DRYing these repeated env-copy steps via:

  • A small composite action (e.g., .github/actions/copy-env) parameterized for test/production.
  • Or a single bash step looping over a whitelist of example dirs to copy .env.development -> .env.{test,production}.local.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b86ad35 and 89f3d2e.

📒 Files selected for processing (4)
  • .github/workflows/e2e-api-tests.yaml (1 hunks)
  • .github/workflows/e2e-source-of-truth-api-tests.yaml (1 hunks)
  • .github/workflows/lint-and-build.yaml (1 hunks)
  • apps/backend/src/lib/tokens.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/backend/src/lib/tokens.tsx
🧬 Code graph analysis (1)
apps/backend/src/lib/tokens.tsx (2)
packages/stack-shared/src/sessions.ts (2)
  • payload (34-37)
  • AccessTokenPayload (23-23)
packages/stack-shared/src/utils/jwt.tsx (1)
  • signJWT (38-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: Security Check
🔇 Additional comments (8)
.github/workflows/e2e-api-tests.yaml (1)

87-89: Approve: Convex test env copy added — source verified.
examples/convex/.env.development exists; the workflow copy step is valid.

.github/workflows/e2e-source-of-truth-api-tests.yaml (1)

89-90: Approve — Convex env setup verified.
examples/convex/.env.development exists and workflows (.github/workflows/lint-and-build.yaml, e2e-api-tests.yaml, e2e-source-of-truth-api-tests.yaml) copy it to the expected .env.production.local / .env.test.local files.

apps/backend/src/lib/tokens.tsx (6)

16-16: LGTM! Improved type safety with centralized schema.

The import of AccessTokenPayload from the shared package centralizes the type definition and ensures consistency across the codebase.


91-91: Good migration approach with legacy fallback.

The implementation correctly prioritizes the new is_anonymous field while maintaining backward compatibility with the legacy role === 'anon' check. The TODO comment appropriately marks this as a future cleanup task.


112-112: Consistent with decoding logic.

The validation logic correctly mirrors the decoding approach on Line 91, using the same precedence and fallback pattern for the is_anonymous field.


153-164: Excellent refactor: centralized payload construction.

This change improves maintainability by:

  • Creating a typed payload object that excludes standard JWT fields (iss, aud)
  • Consolidating all payload fields in one location
  • Making the payload structure more explicit and easier to modify

The role is now consistently set to 'authenticated' with anonymity indicated by the is_anonymous field, which aligns with the migration strategy.


170-170: Clean payload usage.

The signJWT call now uses the centralized payload object, making the code more readable and maintainable.


91-98: Verify consistency of anonymous user handling across the codebase. The role→is_anonymous migration in apps/backend/src/lib/tokens.tsx looks implemented; automated search here couldn't scan files — run a repo-wide search for "role === 'anon'", "payload.role", "is_anonymous|isAnonymous", "AccessTokenPayload", "aud.endsWith(':anon')" and update all token consumers, JWT verification code, types and tests to use is_anonymous (remove legacy role-based checks).

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment
3E14

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
packages/stack-shared/src/sessions.ts (2)

146-156: Bug: 0ms expiry not caught due to truthiness check

If a freshly fetched token expires immediately (0ms), the current check won’t throw. Compare directly.

-      const expiresInMillis = newTokens?.accessToken.expiresInMillis;
-      if (expiresInMillis && expiresInMillis < minMillisUntilExpiration) {
+      if (newTokens && newTokens.accessToken.expiresInMillis < minMillisUntilExpiration) {
         throw new StackAssertionError(`Required access token expiry ${minMillisUntilExpiration}ms is too long; access tokens are too short when they're generated (${expiresInMillis}ms)`);
       }

Note: Adjust the error message to compute expiresInMillis inline, or compute it unconditionally and compare without a truthiness guard.


189-197: Critical: sessions with only an access token can never return the cached token

Early-returning null when there’s no refresh token breaks valid sessions that only carry an access token (documented as supported earlier in this file). getOrFetchLikelyValidTokens will then attempt refresh (impossible) and return null.

Fix:

   private _getPotentiallyInvalidAccessTokenIfAvailable(): AccessToken | null {
-    if (!this._refreshToken) return null;
     if (this.isKnownToBeInvalid()) return null;

     const accessToken = this._accessToken.get();
     if (accessToken && !accessToken.isExpired()) return accessToken;

     return null;
   }
🧹 Nitpick comments (3)
packages/stack-shared/src/schema-fields.ts (1)

660-674: selected_team_id: consider validating as UUID

If this is always a team ID, using uuid() makes bugs surface earlier.

Proposed change:

-  selected_team_id: yupString().defined().nullable(),
+  selected_team_id: yupString().uuid().defined().nullable(),
packages/stack-shared/src/sessions.ts (2)

25-28: Missing exp currently treated as “never expires”

Combined with optional exp in the schema, tokens lacking exp appear valid indefinitely. Either make exp required (preferred) or treat missing exp as expired here.

If keeping schema as-is, apply:

   get expiresAt(): Date {
     const { exp } = this.payload;
-    if (exp === undefined) return new Date(8640000000000000);  // max date value
+    if (exp === undefined) return new Date(0); // treat as already expired
     return new Date(exp * 1000);
   }

125-132: Use consistent error type for too-long expiry requirement

Elsewhere you throw StackAssertionError for the same condition; align here for consistency.

   getAccessTokenIfNotExpiredYet(minMillisUntilExpiration: number): AccessToken | null {
     if (minMillisUntilExpiration > 60_000) {
-      throw new Error(`Required access token expiry ${minMillisUntilExpiration}ms is too long; access tokens are too short to be used for more than 60s`);
+      throw new StackAssertionError(`Required access token expiry ${minMillisUntilExpiration}ms is too long; access tokens are too short to be used for more than 60s`);
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89f3d2e and 2b1147a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • examples/convex/.env.development (1 hunks)
  • examples/convex/.eslintrc.js (1 hunks)
  • examples/convex/components/ConvexClientProvider.tsx (1 hunks)
  • examples/convex/package.json (1 hunks)
  • packages/stack-shared/src/schema-fields.ts (2 hunks)
  • packages/stack-shared/src/sessions.ts (3 hunks)
  • vitest.workspace.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • examples/convex/.eslintrc.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • vitest.workspace.ts
  • examples/convex/package.json
  • examples/convex/.env.development
  • examples/convex/components/ConvexClientProvider.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • packages/stack-shared/src/sessions.ts
  • packages/stack-shared/src/schema-fields.ts
🧬 Code graph analysis (1)
packages/stack-shared/src/sessions.ts (1)
packages/stack-shared/src/schema-fields.ts (1)
  • accessTokenPayloadSchema (660-674)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: Security Check
🔇 Additional comments (5)
packages/stack-shared/src/schema-fields.ts (3)

2-2: Direct import path for KnownErrors looks good

Reduces ambiguity and potential cycles. No further action.


5-5: Type-only import optimization is correct

Removes runtime import weight and clarifies intent.


660-674: Verified no “anon” role usage remains Ripgrep found no instances of role: "anon" in TS/JS code.

packages/stack-shared/src/sessions.ts (2)

2-4: New AccessTokenPayload CDAC type from schema is good

Tightens typing and centralizes validation.


19-22: Decoding JWT without signature verification: confirm intended scope

decodeJwt() doesn’t verify signatures. Fine for non-security display/expiry checks, but ensure no security decisions depend on payload here.

If any downstream logic authorizes based on this payload, switch to verified decoding or guard with server-side checks.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

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 (2)
examples/convex/convex/auth.config.ts (2)

3-7: Mark the exported config as const

Prevents accidental mutation and improves TS inference for Convex config objects.

Apply this diff:

 export default {
   providers: getConvexProvidersConfig({
     projectId: process.env.NEXT_PUBLIC_STACK_PROJECT_ID
   }),
-}
+} as const;

5-6: Guard against undefined env var to avoid passing an undefined projectId

If the helper treats undefined vs omission differently, prefer omitting the field when unset.

Apply this diff:

-  providers: getConvexProvidersConfig({
-    projectId: process.env.NEXT_PUBLIC_STACK_PROJECT_ID
-  }),
+  providers: getConvexProvidersConfig(
+    process.env.NEXT_PUBLIC_STACK_PROJECT_ID
+      ? { projectId: process.env.NEXT_PUBLIC_STACK_PROJECT_ID }
+      : {}
+  ),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b1147a and aa0983a.

📒 Files selected for processing (2)
  • docs/templates/others/convex.mdx (1 hunks)
  • examples/convex/convex/auth.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/templates/others/convex.mdx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • examples/convex/convex/auth.config.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: Security Check
🔇 Additional comments (2)
examples/convex/convex/auth.config.ts (2)

3-7: Good move: using the shared helper removes hard-coded URLs and mixed origins

This addresses earlier feedback and keeps the example aligned with the SDK.


1-1: No action required: export path verified getConvexProvidersConfig is re-exported by @stackframe/stack (in packages/template/src/index.ts); the example import is correct.

@BilalG1 BilalG1 enabled auto-merge (squash) September 24, 2025 19:13
@BilalG1 BilalG1 merged commit 3fe82b6 into dev Sep 24, 2025
20 checks passed
@BilalG1 BilalG1 deleted the convex-impl branch September 24, 2025 19:16
@coderabbitai coderabbitai bot mentioned this pull request Oct 10, 2025
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

0