Skip to content

feat(prisma): add experimental @aura-stack/prisma package - #239

Merged
halvaradop merged 4 commits into
masterfrom
feat/add-prisma-pkg
Jul 29, 2026
Merged

feat(prisma): add experimental @aura-stack/prisma package#239
halvaradop merged 4 commits into
masterfrom
feat/add-prisma-pkg

Conversation

@halvaradop

@halvaradop halvaradop commented Jul 29, 2026

Copy link
Copy Markdown
Member

Description

This pull request introduces the experimental @aura-stack/prisma package, which provides a built-in Prisma ORM adapter for the Stateful session strategy in @aura-stack/auth.

The package implements the database adapter interface required by the Stateful session strategy, allowing applications to persist users, sessions, OAuth accounts, devices, and other authentication-related entities using Prisma.

The adapter has been tested against the supported authentication methods and common authentication flows to verify its correctness and compatibility with the current Stateful implementation.

Key Changes

  • Added the experimental @aura-stack/prisma package.
  • Implemented a built-in Prisma adapter for the Stateful session strategy.
  • Implemented the DatabaseAdapter interface.
  • Added integration tests covering the adapter's methods and authentication flows.
  • Verified compatibility with the current Stateful session implementation.

Note

Documentation for @aura-stack/prisma is intentionally omitted from this PR. The package is still experimental and under active development, and its API may change before it is considered stable. Documentation will be added in a future PR once the adapter reaches a more mature state.

@coderabbitai ignore

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
auth Skipped Skipped Jul 29, 2026 9:29pm

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a Prisma package with authentication schemas, domain mappers, and a database adapter. It integrates Prisma-backed stateful authentication and stateless Elysia endpoint tests, adds shared adapter conformance tests, and expands device and OAuth transaction entity types.

Changes

Prisma adapter integration

Layer / File(s) Summary
Schema and entity contracts
packages/core/src/@types/entities.ts, packages/prisma/prisma/*, packages/elysia/prisma/*, packages/prisma/src/model.md
Adds device and OAuth transaction entity fields, Prisma models, enums, migrations, relationships, indexes, and schema documentation.
Package and Prisma configuration
packages/prisma/*, packages/elysia/package.json, packages/elysia/prisma.config.ts, .gitignore, .vscode/settings.json
Adds package metadata, build/test configuration, environment examples, publishing files, Prisma tooling, editor settings, and generated-file ignore rules.
Domain and Prisma mappings
packages/prisma/src/lib/*
Adds JSON helpers, enum conversions, entity mappers, revoke metadata handling, and nullish-value utilities.
Prisma database adapter
packages/prisma/src/adapter.ts, packages/prisma/src/index.ts
Implements persistence operations for users, accounts, sessions, devices, MFA credentials, and OAuth transactions, including deletion, revocation, expiry, and public exports.
Shared adapter conformance tests
packages/shared/*, packages/prisma/test/*
Adds a reusable adapter test suite, exposes it from the shared package, and wires Prisma setup, database resets, and lifecycle cleanup.
Elysia authentication integration
packages/elysia/test/*, packages/elysia/vitest.config.ts, package.json
Adds stateful and stateless authentication apps, protected endpoint tests, separate Vitest projects, and excludes Prisma from the root test command.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • aura-stack-ts/auth#227 — Both changes extend core authentication entity types used by the adapter and session system.

Suggested labels: experimental, feature

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing the experimental @aura-stack/prisma package.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-prisma-pkg

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (3)
packages/prisma/src/client.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial

Unconditional dotenv/config side-effect import in a library entrypoint.

Loading .env automatically on import is typically an application-level concern, not a library one. Consumers of @aura-stack/prisma that already manage their own env loading (or run in environments where .env files shouldn't be read, e.g. production containers) get this side effect forced on them just by importing the package's client module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/client.ts` at line 1, Remove the unconditional
dotenv/config side-effect import from the library entrypoint in client.ts,
ensuring importing the Prisma client no longer automatically loads .env files.
Leave environment configuration responsibility to the consuming application.
packages/prisma/src/adapter.ts (1)

39-50: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

createUser spreads raw domain input into Prisma's typed data, inconsistent with updateUser's explicit-field style.

Spreading ...input bypasses TypeScript's excess-property checking (which only applies to literal properties, not spread ones), so any field on the domain CreateUserInput type that doesn't exist on Prisma's UserCreateInput would be silently forwarded and could trigger a runtime PrismaClientValidationError ("Unknown argument"). updateUser right below (lines 62-82) builds the data object explicitly field-by-field — recommend the same pattern here for consistency and type safety.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/adapter.ts` around lines 39 - 50, Update createUser to
construct Prisma’s data object explicitly from supported CreateUserInput fields
instead of spreading ...input, matching updateUser’s field-by-field pattern.
Preserve the existing conversions for emailVerifiedAt, status,
mfaPreferredMethod, and attributes while excluding unsupported domain-only
fields.
packages/prisma/src/index.ts (1)

1-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exporting a pre-built prismaClient singleton from the package's public entrypoint forces an eager DB connection on import.

prismaAdapter is a flexible factory that takes a caller-supplied PrismaClient, but index.ts also re-exports the concrete prismaClient instance from client.ts, which eagerly constructs a PrismaPg adapter (and thus a connection pool) at module-load time, gated on process.env.DATABASE_URL being set. Any consumer importing @aura-stack/prisma — even just for prismaAdapter or types — pays this side effect and env-var dependency. Consider keeping prismaClient as an internal test-only convenience (as used in packages/prisma/test/setup/prisma.ts) rather than part of the public package surface, letting consumers construct/own their own PrismaClient.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/index.ts` around lines 1 - 6, Remove the public
prismaClient re-export from the package entrypoint in index.ts, while preserving
the prismaAdapter and PrismaAdapterOptions exports and PrismaClient type export.
Keep prismaClient available only through its internal test setup usage, so
importing the package does not eagerly construct a database client or require
DATABASE_URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/prisma/package.json`:
- Line 4: Remove the "private": true declaration from the packages/prisma
package manifest so the package can be published, while preserving its existing
publishConfig and prepublishOnly settings.
- Line 10: Update the build script in package.json to invoke Prisma through the
workspace-installed CLI using pnpm exec prisma generate, then preserve the
existing tsdown step.

In `@packages/prisma/README.md`:
- Line 8: Update the JSR badge link in the README to point to the
`@aura-stack/prisma` package page instead of `@aura-stack/integration`, while
leaving the badge image URL unchanged.

In `@packages/prisma/src/adapter.ts`:
- Around line 153-168: The createOAuthAccount method should reject missing OAuth
access tokens instead of persisting an empty string. Validate input.accessToken
before client.oAuthAccount.create and fail clearly when it is absent, while
preserving the existing token value and account creation flow for valid input.
- Around line 96-108: Validate the required fields in createAccount,
createOAuthAccount, and createCredentialAccount before calling Prisma: userId,
provider, providerUserId, type, accountId, and passwordHash as applicable.
Replace the non-null assertions with explicit checks that throw a clear domain
error when any required value is missing, while preserving the existing
persistence and conversion flow for valid inputs.
- Around line 497-507: Update consumeOAuthTransaction to handle a concurrently
consumed OAuth state without propagating Prisma P2025: prefer an atomic
deleteMany-style operation that checks the affected count and returns null when
no row was deleted, while preserving transaction conversion for a successful
deletion; alternatively catch only P2025 from the existing delete and return
null.

In `@packages/prisma/src/client.ts`:
- Around line 5-7: Validate process.env.DATABASE_URL before constructing
PrismaPg in the module initialization around adapter. If it is unset, fail
immediately with a clear message that DATABASE_URL is required; otherwise pass
the validated value as connectionString.

In `@packages/prisma/src/mappers.ts`:
- Around line 335-339: Update appendRevokeReason to accept a revokedAt timestamp
parameter and use it for metadata instead of creating a new timestamp. Modify
both revokeSession and revokeAllSessions call sites in adapter.ts to pass the
same revokedAt Date value used for the session column update, preserving
consistency between session.revokedAt and session.metadata.revokedAt.
- Around line 146-157: Rename the exported mapper function toMFAStatus and
update every reference to it, especially the call sites in adapter.ts, while
preserving the existing MFAState-to-PrismaMfaState mappings.
- Around line 41-43: Update toJson in packages/prisma/src/mappers.ts to preserve
explicit null semantics for nullable JSON fields: map JSON null to
Prisma.JsonNull, SQL null to Prisma.DbNull where applicable, and leave undefined
as undefined. Replace the current as never coercion with the appropriate Prisma
sentinel-aware return type, while keeping fromJson unchanged.

In `@packages/prisma/test/setup/prisma.ts`:
- Around line 7-8: Add package-level Vitest teardown in the setup module
containing prismaClient so it calls prismaClient.$disconnect() after the test
suite completes, ensuring the PostgreSQL connection pool is closed.

---

Nitpick comments:
In `@packages/prisma/src/adapter.ts`:
- Around line 39-50: Update createUser to construct Prisma’s data object
explicitly from supported CreateUserInput fields instead of spreading ...input,
matching updateUser’s field-by-field pattern. Preserve the existing conversions
for emailVerifiedAt, status, mfaPreferredMethod, and attributes while excluding
unsupported domain-only fields.

In `@packages/prisma/src/client.ts`:
- Line 1: Remove the unconditional dotenv/config side-effect import from the
library entrypoint in client.ts, ensuring importing the Prisma client no longer
automatically loads .env files. Leave environment configuration responsibility
to the consuming application.

In `@packages/prisma/src/index.ts`:
- Around line 1-6: Remove the public prismaClient re-export from the package
entrypoint in index.ts, while preserving the prismaAdapter and
PrismaAdapterOptions exports and PrismaClient type export. Keep prismaClient
available only through its internal test setup usage, so importing the package
does not eagerly construct a database client or require DATABASE_URL.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f5fb147-816a-44f4-aeee-0afb34701940

📥 Commits

Reviewing files that changed from the base of the PR and between dc26fa9 and 6618f45.

⛔ Files ignored due to path filters (3)
  • bun.lock is excluded by !**/*.lock
  • deno.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • package.json
  • packages/core/src/@types/entities.ts
  • packages/prisma/.env.example
  • packages/prisma/.gitignore
  • packages/prisma/.vscode/extensions.json
  • packages/prisma/CHANGELOG.md
  • packages/prisma/README.md
  • packages/prisma/deno.json
  • packages/prisma/package.json
  • packages/prisma/prisma.config.ts
  • packages/prisma/prisma/schema.prisma
  • packages/prisma/src/adapter.ts
  • packages/prisma/src/client.ts
  • packages/prisma/src/index.ts
  • packages/prisma/src/mappers.ts
  • packages/prisma/src/model.md
  • packages/prisma/test/index.test.ts
  • packages/prisma/test/setup/prisma.ts
  • packages/prisma/tsconfig.json
  • packages/prisma/tsdown.config.ts
  • packages/prisma/vitest.config.ts

Comment thread packages/prisma/package.json Outdated
Comment thread packages/prisma/package.json Outdated
Comment thread packages/prisma/README.md Outdated
Comment on lines +96 to +108
createAccount: async (input) => {
const account = await client.account.create({
data: {
id: input.id,
userId: input.userId!,
provider: input.provider!,
providerUserId: input.providerUserId!,
type: toAccountType(input.type!),
status: input.status ? toAccountStatus(input.status) : undefined,
},
})
return toAccount(account)
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the domain create-input types referenced by these adapter functions.
fd entities.ts packages/core/src --exec cat -n {}
rg -n 'CreateAccountInput|CreateOAuthAccountInput|CreateCredentialAccountInput' -C5 packages/core/src

Repository: aura-stack-ts/auth

Length of output: 5513


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== location files =="
fd 'adapter\.ts|types|entities' packages -e ts -e tsx | rg 'packages/.*(adapter\.ts|types|entities)' | sed -n '1,200p'

echo "== target adapter relevant sections =="
if [ -f packages/prisma/src/adapter.ts ]; then
  wc -l packages/prisma/src/adapter.ts
  sed -n '1,230p' packages/prisma/src/adapter.ts | cat -n
fi

echo "== package names and references =="
rg -n '"`@aura-stack/auth`|aura-stack/auth|CreateAccountInput|CreateOAuthAccountInput|CreateCredentialAccountInput|createAccount|createOAuthAccount|createCredentialAccount' packages -g '*.{ts,tsx,js,jsx,json}' -C 3

Repository: aura-stack-ts/auth

Length of output: 32435


Validate required account-creation fields before persisting.

createAccount, createOAuthAccount, and createCredentialAccount consume Partial<...> inputs, but userId, provider, providerUserId, type, accountId, and passwordHash are still required account data. The ! assertions can silently ship undefined into Prisma; validate these fields and throw a clear domain error instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/adapter.ts` around lines 96 - 108, Validate the required
fields in createAccount, createOAuthAccount, and createCredentialAccount before
calling Prisma: userId, provider, providerUserId, type, accountId, and
passwordHash as applicable. Replace the non-null assertions with explicit checks
that throw a clear domain error when any required value is missing, while
preserving the existing persistence and conversion flow for valid inputs.

Comment thread packages/prisma/src/adapter.ts
Comment thread packages/prisma/src/client.ts Outdated
Comment on lines +41 to +43
export const toJson = (value: Record<string, unknown> | null | undefined) => (value ?? undefined) as never

export const fromJson = (value: unknown): Record<string, unknown> | null => (isRecord(value) ? value : null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## locate mappers and prisma files"
fd -a 'mappers\.ts$|schema\.prisma$|client\.ts$|\.prisma$' . | sed 's#^\./##' | head -100

echo
echo "## packages/prisma/src/mappers.ts"
if [ -f packages/prisma/src/mappers.ts ]; then
  cat -n packages/prisma/src/mappers.ts
else
  fd 'mappers\.ts$' . -x sh -c 'echo "--- $1"; cat -n "$1"' sh {}
fi

echo
echo "## search toJson usages/updater"
rg -n "toJson\(|fromJson\(|attributes === undefined|metadata: toJson|Prisma\.JsonNull|typeJsonValue|InputJsonValue" packages/prisma . --glob '!node_modules' --glob '!dist' --glob '!build' || true

echo
echo "## prisma schema json fields"
if [ -f packages/prisma/prisma/schema.prisma ]; then
  rg -n "Json|json|attributes|metadata|JsonNull|DbNull" packages/prisma/prisma/schema.prisma -C 2
fi
if [ -f prisma/schema.prisma ]; then
  rg -n "Json|json|attributes|metadata|JsonNull|DbNull" prisma/schema.prisma -C 2
fi

Repository: aura-stack-ts/auth

Length of output: 17611


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p=Path('packages/prisma/src/mappers.ts')
text=p.read_text() if p.exists() else ''
lines=text.splitlines()
for i,l in enumerate(lines,1):
    if 'toJson' in l or 'fromJson' in l or 'isRecord' in l:
        print(f'{i}: {l}')
PY

Repository: aura-stack-ts/auth

Length of output: 765


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## adapter relevant sections"
sed -n '1,110p' packages/prisma/src/adapter | cat -n
echo "---"
sed -n '210,290p' packages/prisma/src/adapter | cat -n
echo "---"
sed -n '290,370p' packages/prisma/src/adapter | cat -n
echo("---" )
sed -n '380,470p' packages/prisma/src/adapter | cat -n

echo
echo "## package/prisma imports and Prisma client availability"
sed -n '1,60p' packages/prisma/src/adapter.ts | cat -n
lc=$LC_ALL
export LC_ALL=C
npm list `@prisma/client` 2>/dev/null | rg '`@prisma/client`|missing' || true
if command -v pg_typescript 2>/dev/null || command -v tsc 2>/dev/null; then
  echo "compiler available"
else
  echo "compiler unavailable"
fi

echo
echo "## behavioral probe of mapper coercion"
node - <<'JS'
function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
function toJson(value) { return value ?? undefined; }
function fromJson(value) { return isRecord(value) ? value : null; }
for (const input of [undefined, null, {}, {a:1}, []]) {
  console.log(JSON.stringify(input), "=>", toJson(input));
}
console.log("fromJson null =>", fromJson(null));
console.log("fromJson object =>", fromJson({}));
JS

Repository: aura-stack-ts/auth

Length of output: 256


🌐 Web query:

Prisma input JSON null JsonNull DbNull update null field undefined skip unchanged

💡 Result:

In Prisma Client, null and undefined are treated as distinct concepts with different behaviors in update operations [1][2]. For standard fields, passing undefined to an update operation instructs Prisma to ignore that field and perform no change to its value in the database [1][3]. In contrast, passing null explicitly instructs Prisma to update the database column to NULL (provided the field is nullable) [1][4]. For Json fields, which require special handling due to their unique nature in SQL, Prisma provides specific enums to differentiate between intended values [5][6]: - Prisma.JsonNull: Updates the database field to the JSON null value [5][6]. - Prisma.DbNull: Updates the database field to the SQL NULL value [5][6]. To skip updating a field entirely (the equivalent of passing undefined for standard fields), Prisma provides the Prisma.skip symbol [1][2]. The use of Prisma.skip is recommended over undefined when dealing with dynamic update objects, especially if you have enabled the strictUndefinedChecks preview feature, which is designed to prevent accidental data loss or unintended query behavior by strictly managing how undefined values are handled [1][2]. If you are programmatically constructing an update payload, you should ensure that fields you wish to remain unchanged are either omitted from the data object or explicitly set to Prisma.skip [3][7][2]. Using undefined as a fallback (e.g., value ?? undefined) is a common pattern to ensure that falsy values do not incorrectly trigger an update to null [1][3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## adapter relevant sections"
sed -n '1,110p' packages/prisma/src/adapter.ts | cat -n
echo "---"
sed -n '210,290p' packages/prisma/src/adapter.ts | cat -n
echo "---"
sed -n '290,370p' packages/prisma/src/adapter.ts | cat -n
echo "---"
sed -n '380,470p' packages/prisma/src/adapter.ts | cat -n

echo
echo "## adapter imports"
sed -n '1,45p' packages/prisma/src/adapter.ts | cat -n

echo
echo "## behavioral probe of mapper coercion"
node - <<'JS'
function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
function toJson(value) { return value ?? undefined; }
function fromJson(value) { return isRecord(value) ? value : null; }
for (const input of [undefined, null, {}, {a:1}, []]) {
  console.log(JSON.stringify(input), "=>", toJson(input));
}
console.log("fromJson null =>", fromJson(null));
console.log("fromJson object =>", fromJson({}));
JS

Repository: aura-stack-ts/auth

Length of output: 17485


🌐 Web query:

site:prisma.io/docs working with JSON fields Prisma.JsonNull Prisma.DbNull JsonNull value null column sql null update

💡 Result:

When working with JSON fields in Prisma ORM, it is important to distinguish between a database NULL value and a JSON null value [1][2]. Prisma provides specialized enums to handle these cases explicitly, as standard null values can be ambiguous [1][3]. The available Prisma null types are: - Prisma.JsonNull: Represents a JSON null value stored within the JSON field [1][2]. - Prisma.DbNull: Represents a database NULL value (the absence of a value) in the JSON column [1][2]. - Prisma.AnyNull: Used only for filtering, this matches either a JSON null or a database NULL [1][2]. These enums are necessary because, in many SQL databases, there is a technical difference between a column being explicitly set to the JSON value null and the column having no value at all (database NULL) [1][2]. Note that these distinctions do not apply to MongoDB, which does not differentiate between the two [1][2]. Inserting or Updating JSON Fields To explicitly set a JSON field to one of these states during a create, update, or upsert operation, import Prisma from @prisma/client and pass the appropriate enum [1][2]: import { Prisma } from "@prisma/client"; // Setting to a JSON null value await prisma.log.update({ where: { id: 1 }, data: { meta: Prisma.JsonNull, }, }); // Setting to a database NULL await prisma.log.update({ where: { id: 1 }, data: { meta: Prisma.DbNull, }, }); Filtering JSON Fields When filtering for these values, you must explicitly use the equals operator with the desired enum [1][2]. You cannot use standard null shorthand [1][2]. // Filtering for JSON null await prisma.log.findMany({ where: { meta: { equals: Prisma.JsonNull }, }, }); Important Considerations - Versioning: Since Prisma ORM 4.0.0, these null handlers are implemented as objects rather than strings [2][4]. - Ambiguity: If you simply pass null to a JSON field in a query without these enums, the behavior may be ambiguous or result in errors, depending on your Prisma version and configuration [1][3]. - MongoDB: These distinctions are not applicable when using MongoDB, as it does not distinguish between JSON null and database NULL [1][2].

Citations:


Map explicit JSON null to Prisma’s null sentinels.

toJson collapses null and undefined to undefined, so the adapter cannot distinguish “write this to the JSON field” from “leave the field unchanged” for nullable Json columns. Use Prisma sentinels (Prisma.JsonNull for JSON null, Prisma.DbNull for SQL NULL) instead of the as never coercion, or update the returned type to Prisma.InputJsonValue | typeof Prisma.JsonNull | typeof Prisma.DbNull | undefined.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/mappers.ts` around lines 41 - 43, Update toJson in
packages/prisma/src/mappers.ts to preserve explicit null semantics for nullable
JSON fields: map JSON null to Prisma.JsonNull, SQL null to Prisma.DbNull where
applicable, and leave undefined as undefined. Replace the current as never
coercion with the appropriate Prisma sentinel-aware return type, while keeping
fromJson unchanged.

Comment thread packages/prisma/src/mappers.ts Outdated
Comment thread packages/prisma/src/mappers.ts Outdated
Comment thread packages/prisma/test/setup/prisma.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
packages/prisma/src/adapter.ts (1)

267-281: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Two independent new Date() calls can desync the revokedAt column from the metadata timestamp.

revokedAt: new Date() and the new Date() passed into appendRevokeReason on the next line are two separate calls that can differ by milliseconds. revokeAllSessions right below gets this correct by computing revokedAt once and reusing it — this single-session path should follow the same pattern.

🐛 Proposed fix
             await client.session.update({
                 where: { id },
                 data: {
                     status: "REVOKED",
-                    revokedAt: new Date(),
-                    metadata: toJson(appendRevokeReason(existing.metadata as Record<string, unknown> | null, reason, new Date())),
+                    revokedAt,
+                    metadata: toJson(appendRevokeReason(existing.metadata as Record<string, unknown> | null, reason, revokedAt)),
                 },
             })

(with const revokedAt = new Date() declared before the update call.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/adapter.ts` around lines 267 - 281, Update revokeSession
to create one revokedAt timestamp before client.session.update, then reuse that
same value for both the revokedAt field and the timestamp argument to
appendRevokeReason, matching the existing revokeAllSessions pattern.
♻️ Duplicate comments (3)
packages/prisma/src/adapter.ts (2)

437-446: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

consumeOAuthTransaction still races on concurrent consumption (unresolved from a previous review).

findUnique then delete inside $transaction can still throw P2025 if two concurrent calls consume the same state, since the interactive transaction doesn't serialize against the other transaction's delete between these two statements. No P2025 handling was added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/adapter.ts` around lines 437 - 446, Update
consumeOAuthTransaction to handle concurrent consumption safely: catch Prisma
P2025 from the delete or transaction, and return null when another caller has
already consumed the OAuth transaction while preserving normal errors and
successful conversion via toOAuthTransaction.

101-113: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Required account fields still rely on unchecked ! assertions (unresolved from a previous review).

createAccount (input.userId!, input.provider!, input.providerUserId!, input.type!) and createCredentialAccount (input.accountId!, input.passwordHash!) still bypass validation with non-null assertions instead of throwing a clear domain error when these required fields are missing.

Also applies to: 187-195

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/adapter.ts` around lines 101 - 113, Remove the non-null
assertions from required fields in createAccount and createCredentialAccount,
validate each required input before the Prisma calls, and throw the established
domain error when any is missing. Preserve the existing field mappings and
conversions after validation, including userId, provider, providerUserId, type,
accountId, and passwordHash.
packages/prisma/src/lib/mappers.ts (1)

41-43: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

toJson still collapses explicit JSON null into undefined (unresolved from a previous review round).

toJson is unchanged from the earlier flagged version: (value ?? undefined) as never makes it impossible to distinguish "clear this JSON field" from "leave it unchanged" for nullable Json columns. Use Prisma.JsonNull/Prisma.DbNull sentinels instead of the as never coercion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/lib/mappers.ts` around lines 41 - 43, Update toJson in
packages/prisma/src/lib/mappers.ts to preserve explicit null semantics for
nullable JSON fields: map null to the appropriate Prisma.JsonNull or
Prisma.DbNull sentinel and map undefined to the unchanged-field behavior,
removing the current nullish-coalescing expression and as never coercion. Keep
fromJson unchanged.
🧹 Nitpick comments (1)
packages/elysia/prisma/schema.prisma (1)

1-234: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Duplicate Prisma schema across packages/prisma and packages/elysia.

This schema mirrors the models/enums already defined in packages/prisma/prisma/schema.prisma (same User/Account/Session/Device/MfaCredential/OAuthTransaction shapes, same enum value sets consumed by packages/prisma/src/lib/mappers.ts). Maintaining two independent copies means every future migration (new field, renamed enum value, etc.) must be hand-applied twice, and any drift silently breaks one of the two packages' Prisma clients without a compile-time signal.

Consider having packages/elysia's test harness consume the schema from @aura-stack/prisma directly (e.g. point prisma.config.ts's schema at the shared file, or use Prisma's prismaSchemaFolder/multi-file schema support) instead of duplicating it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/elysia/prisma/schema.prisma` around lines 1 - 234, Remove the
duplicated Prisma schema from the Elysia package and configure its test harness
to consume the canonical schema from `@aura-stack/prisma`. Update the relevant
Prisma configuration, such as prisma.config.ts, to reference the shared schema
while preserving the existing generated-client setup and schema behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.gitignore:
- Around line 202-203: Update the generated Prisma ignore patterns in .gitignore
to match src/generated/prisma and generated/prisma directories at any repository
depth, including package-specific paths. Preserve the existing ignore coverage
while removing root-only anchoring that prevents nested generated clients from
being ignored.

In `@packages/elysia/test/stateful/app.ts`:
- Around line 1-17: Update the PrismaClient import in the stateful test setup
around prismaClient to use the generated client exposed by `@aura-stack/prisma`
instead of the unresolved `@/generated/prisma/client.ts` path. Ensure both app.ts
and its related setup flow resolve the same generated client before tests run.
- Around line 21-34: Update the credentials authorize handler to return a stable
sub for each username instead of calling createSecretValue(16) on every login.
Derive or reuse the identifier consistently for the same credentials identity,
while preserving the existing invalid-password rejection and returned user
fields; leave signUp.onCreateUser’s fresh sub generation unchanged.

In `@packages/elysia/test/stateful/setup.ts`:
- Around line 5-16: Update resetDatabase so dependent records are deleted before
user records, preserving the transaction. Reorder the deleteMany calls to clear
credentialAccount, oAuthAccount, account, mfaCredential, oAuthTransaction,
session, device, and finally user, ensuring cascade targets are explicitly
removed before their parents.

In `@packages/prisma/src/adapter.ts`:
- Around line 43-61: Remove the debug console.log of the raw input and output
from createUser, and remove its redundant try/catch wrapper that only logs
before rethrowing. Preserve the existing client.user.create data mapping and
toUser return behavior.
- Around line 88-97: Update the soft-delete branch of deleteUser to write the
Prisma UserStatus value for DELETED, using the forward status mapper or the
schema enum literal; remove the incorrect fromUserStatus conversion and
unnecessary cast while preserving the hard-delete path.

In `@packages/prisma/src/lib/utils.ts`:
- Around line 1-3: Update stripNullishValues to filter out only undefined values
while preserving explicit null entries, so Prisma update callers such as
updateUser, updateOAuthTokens, updateSession, updateDevice, and
updateMfaCredential can clear nullable fields.

---

Outside diff comments:
In `@packages/prisma/src/adapter.ts`:
- Around line 267-281: Update revokeSession to create one revokedAt timestamp
before client.session.update, then reuse that same value for both the revokedAt
field and the timestamp argument to appendRevokeReason, matching the existing
revokeAllSessions pattern.

---

Duplicate comments:
In `@packages/prisma/src/adapter.ts`:
- Around line 437-446: Update consumeOAuthTransaction to handle concurrent
consumption safely: catch Prisma P2025 from the delete or transaction, and
return null when another caller has already consumed the OAuth transaction while
preserving normal errors and successful conversion via toOAuthTransaction.
- Around line 101-113: Remove the non-null assertions from required fields in
createAccount and createCredentialAccount, validate each required input before
the Prisma calls, and throw the established domain error when any is missing.
Preserve the existing field mappings and conversions after validation, including
userId, provider, providerUserId, type, accountId, and passwordHash.

In `@packages/prisma/src/lib/mappers.ts`:
- Around line 41-43: Update toJson in packages/prisma/src/lib/mappers.ts to
preserve explicit null semantics for nullable JSON fields: map null to the
appropriate Prisma.JsonNull or Prisma.DbNull sentinel and map undefined to the
unchanged-field behavior, removing the current nullish-coalescing expression and
as never coercion. Keep fromJson unchanged.

---

Nitpick comments:
In `@packages/elysia/prisma/schema.prisma`:
- Around line 1-234: Remove the duplicated Prisma schema from the Elysia package
and configure its test harness to consume the canonical schema from
`@aura-stack/prisma`. Update the relevant Prisma configuration, such as
prisma.config.ts, to reference the shared schema while preserving the existing
generated-client setup and schema behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5a294c6-f57c-4696-98f6-3964236f3029

📥 Commits

Reviewing files that changed from the base of the PR and between 6618f45 and dcb32d6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • .gitignore
  • .vscode/settings.json
  • packages/elysia/.env.example
  • packages/elysia/package.json
  • packages/elysia/prisma.config.ts
  • packages/elysia/prisma/schema.prisma
  • packages/elysia/test/stateful/app.ts
  • packages/elysia/test/stateful/index.test.ts
  • packages/elysia/test/stateful/setup.ts
  • packages/elysia/test/stateless/app.ts
  • packages/elysia/test/stateless/index.test.ts
  • packages/elysia/vitest.config.ts
  • packages/prisma/.env.example
  • packages/prisma/.vscode/settings.json
  • packages/prisma/README.md
  • packages/prisma/package.json
  • packages/prisma/prisma/schema.prisma
  • packages/prisma/src/adapter.ts
  • packages/prisma/src/index.ts
  • packages/prisma/src/lib/mappers.ts
  • packages/prisma/src/lib/utils.ts
  • packages/prisma/test/setup/prisma.ts
💤 Files with no reviewable changes (1)
  • packages/prisma/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/prisma/.env.example
  • packages/prisma/README.md
  • packages/prisma/test/setup/prisma.ts
  • packages/prisma/prisma/schema.prisma

Comment thread .gitignore Outdated
Comment thread packages/elysia/test/stateful/app.ts
Comment thread packages/elysia/test/stateful/app.ts
Comment thread packages/elysia/test/stateful/setup.ts
Comment thread packages/prisma/src/adapter.ts
Comment thread packages/prisma/src/adapter.ts
Comment thread packages/prisma/src/lib/utils.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
packages/elysia/test/stateful/app.ts (1)

31-35: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use one canonical subject for signup and credential login.

authorize uses credentials:${username}, while onCreateUser uses credentials:${email} even though authorize constructs the email as ${username}@example.com``. For example, the same user can receive credentials:alice during login and `credentials:alice@example.com` during signup. Derive both values through a shared canonical email/subject helper.

💡 Proposed fix
         authorize: ({ credentials }) => {
             const { username, password } = credentials
             if (password === "invalid") {
                 return null
             }
-            const sub = `credentials:${username}`
+            const email = `${username}`@example.com``
+            const sub = `credentials:${email}`
             return {
                 sub,
                 name: username,
-                email: `${username}`@example.com``,
+                email,

Also applies to: 48-52

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/elysia/test/stateful/app.ts` around lines 31 - 35, Use a shared
canonical email/subject derivation for the authorize and onCreateUser flows in
the stateful app. Update the credentials subject currently built from username
and the one built from email so both resolve to the same value, preserving the
`${username}`@example.com`` convention and ensuring signup and credential login
identify the user consistently.
🧹 Nitpick comments (2)
packages/prisma/package.json (1)

50-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move @aura-stack/tsdown-config to devDependencies.

It is only imported by packages/prisma/tsdown.config.ts, which is a tsdown build config, so consumers do not need this package resolved at runtime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/package.json` at line 50, Move `@aura-stack/tsdown-config` from
dependencies to devDependencies in packages/prisma/package.json, keeping its
existing workspace version and ensuring it is not listed in runtime
dependencies.
packages/shared/src/adapter-suite.js (1)

404-422: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

listSessions with filters doesn't actually exercise filtering.

It passes the same { status: "active", deviceId: null } as the previous test and asserts the single created session comes back — an unfiltered query would pass identically. Add a negative case (e.g. status: "revoked", or a deviceId that doesn't match) so the where construction in listSessions is really covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/adapter-suite.js` around lines 404 - 422, Update the
“listSessions with filters” test to include a negative filter case that cannot
match the created active session, such as status “revoked” or a different
deviceId, and assert that no sessions are returned. Keep the existing matching
filter assertion so both matching and filtering behavior exercise listSessions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/elysia/package.json`:
- Line 12: Update the package test scripts so the default test command runs both
stateless and stateful Vitest suites instead of limiting execution to the
stateless project. Remove the --project stateless restriction from test, or
define and invoke a dedicated test:stateful command alongside the existing test
flow, ensuring Prisma-backed authentication tests run by default.

In `@packages/prisma/prisma/migrations/20260729162431_schema/migration.sql`:
- Around line 55-65: Update the OAuthAccount persistence adapter responsible for
the OAuthAccount columns so accessToken, refreshToken, and idToken are
envelope-encrypted before database writes, using encryption keys managed outside
the database. Decrypt these fields when reading records so token renewal can
recover the original values; preserve nullable handling for refreshToken and
idToken, and do not hash reusable tokens.

In `@packages/prisma/src/adapter.ts`:
- Around line 84-92: Make the soft-delete operation atomic by wrapping the user
status update and session revocation in a single client.$transaction. Within
that transaction, replace updateManyAndReturn with updateMany because the
returned sessions are unused, while preserving the existing filters and update
data.

In `@packages/shared/src/adapter-suite.ts`:
- Around line 1-2: Consolidate createAdapterSuite into one source of truth by
deleting the empty TypeScript stub or moving the real implementation into it and
removing the duplicate JavaScript suite; update packages/shared/package.json
lines 36-38 to export the surviving file with a types condition, and verify
packages/prisma/test/index.test.ts lines 2-4 resolves
createAdapterSuite(adapter) and registers a non-zero test suite.

---

Outside diff comments:
In `@packages/elysia/test/stateful/app.ts`:
- Around line 31-35: Use a shared canonical email/subject derivation for the
authorize and onCreateUser flows in the stateful app. Update the credentials
subject currently built from username and the one built from email so both
resolve to the same value, preserving the `${username}`@example.com`` convention
and ensuring signup and credential login identify the user consistently.

---

Nitpick comments:
In `@packages/prisma/package.json`:
- Line 50: Move `@aura-stack/tsdown-config` from dependencies to devDependencies
in packages/prisma/package.json, keeping its existing workspace version and
ensuring it is not listed in runtime dependencies.

In `@packages/shared/src/adapter-suite.js`:
- Around line 404-422: Update the “listSessions with filters” test to include a
negative filter case that cannot match the created active session, such as
status “revoked” or a different deviceId, and assert that no sessions are
returned. Keep the existing matching filter assertion so both matching and
filtering behavior exercise listSessions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d9a2d35-3930-4c36-aa4f-02a671c0e650

📥 Commits

Reviewing files that changed from the base of the PR and between dcb32d6 and 70d6cea.

⛔ Files ignored due to path filters (3)
  • bun.lock is excluded by !**/*.lock
  • deno.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • .gitignore
  • packages/core/src/shared/index.ts
  • packages/elysia/.gitignore
  • packages/elysia/package.json
  • packages/elysia/test/stateful/app.ts
  • packages/elysia/test/stateful/index.test.ts
  • packages/elysia/test/stateful/setup.ts
  • packages/prisma/package.json
  • packages/prisma/prisma/migrations/20260729162431_schema/migration.sql
  • packages/prisma/prisma/migrations/migration_lock.toml
  • packages/prisma/src/adapter.ts
  • packages/prisma/src/lib/utils.ts
  • packages/prisma/test/index.test.ts
  • packages/prisma/test/setup/prisma.ts
  • packages/shared/package.json
  • packages/shared/src/adapter-suite.js
  • packages/shared/src/adapter-suite.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/elysia/test/stateful/index.test.ts
  • packages/prisma/src/lib/utils.ts
  • packages/prisma/test/setup/prisma.ts
  • .gitignore
  • packages/elysia/test/stateful/setup.ts

Comment thread packages/elysia/package.json
Comment thread packages/prisma/src/adapter.ts Outdated
Comment thread packages/shared/src/adapter-suite.ts
@halvaradop
halvaradop merged commit 3ffe4f2 into master Jul 29, 2026
7 checks passed
@halvaradop
halvaradop deleted the feat/add-prisma-pkg branch July 29, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant