feat(core): add experimental Typebox schema validation#163
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
📝 WalkthroughWalkthroughThis PR adds experimental TypeBox schema validation support to the auth stack, enabling ChangesTypeBox Schema Validation Support
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/core/src/shared/assert.ts`:
- Around line 207-213: The current isTypeboxEntries guard is too strict and can
throw on null; replace the ad-hoc `"type" in v` check in isTypeboxEntries with
TypeBox's structural guard: import TypeGuard from '`@sinclair/typebox/guard`' and,
after the existing object/null/array checks in isTypeboxEntries, validate each
property value with TypeGuard.TSchema (or the appropriate TypeGuard predicate)
instead of testing for a top-level "type" key so nulls and non-discriminated
schemas like Type.Ref/Type.Union are handled safely.
In `@packages/core/src/validator/registry.ts`:
- Around line 147-151: getFullSchema's TypeBox branch currently defines expires
as Typebox.Optional(Typebox.String()), leaving it a string at runtime while
other adapters coerce to Date; update the TypeBox branch in getFullSchema to
declare expires as a date-compatible schema (e.g., a string with date-time
format) and then adjust the TypeBox path in createValidator to mirror other
adapters by coercing/parsing that field into a JavaScript Date after Value.Check
succeeds (or include a Value.Transform step) so the runtime shape matches the
Zod/Valibot/ArkType branches; reference getFullSchema, the Typebox.Object(...)
creation, the expires property, and createValidator to locate the changes.
🪄 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
Run ID: 712de69d-50d2-4130-9b23-96cfbf1cbe9e
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
packages/core/CHANGELOG.mdpackages/core/package.jsonpackages/core/src/@types/index.tspackages/core/src/@types/utility.tspackages/core/src/schema-registry.tspackages/core/src/shared/assert.tspackages/core/src/shared/identity.tspackages/core/src/validator/registry.tspackages/core/src/validator/validator.tspackages/core/test/identity.test.tspackages/core/test/types.test-d.tspackages/elysia/package.jsonpackages/elysia/src/createAuth.tspackages/express/src/createAuth.tspackages/express/test/types.test-d.tspackages/hono/src/createAuth.tspackages/integration/src/createAuth.tspackages/next/src/createAuth.tspackages/react-router/src/createAuth.ts
💤 Files with no reviewable changes (2)
- packages/express/test/types.test-d.ts
- packages/core/src/schema-registry.ts
| export const isTypeboxEntries = (value: unknown): value is TypeboxType.TProperties => { | ||
| return ( | ||
| typeof value === "object" && | ||
| value !== null && | ||
| !Array.isArray(value) && | ||
| Object.values(value).every((v) => typeof v === "object" && "type" in v) | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In TypeBox, what is the recommended runtime guard to detect whether a value is a valid schema entry (TSchema/TProperties member), and do Type.Union / Type.Ref schemas always include a top-level "type" property?
💡 Result:
Recommended runtime guard: use TypeBox’s built-in recursive TypeGuard helpers (e.g., import { TypeGuard } from '@sinclair/typebox/guard' and then call TypeGuard.TString(...), TypeGuard.TNumber(...), etc.). The project maintainer describes these guard functions as performing a full recursive structural check, offering the most assurance for checking TypeBox schematics at runtime [1]. Whether Type.Union / Type.Ref schemas always include a top-level "type" property: - Type.Ref schemas do not include a JSON-Schema top-level type; instead they are represented as { $ref: 'T' } in the generated JSON Schema (so type is not expected at the top level) [2]. - Type.Union schemas likewise do not require (and typically do not have) a top-level type; unions are represented using JSON Schema composition (anyOf/oneOf/etc.) rather than a type discriminator at the root. The TypeBox package materials show Type.Ref('T') produces { $ref: 'T' } rather than an object with type [2], and TypeBox’s union construction is shown in its docs with Type.Union([...]) mapping to JSON Schema composition rather than a single root type key [2]. So, do not rely on a top-level type property being present for Type.Union or Type.Ref; for runtime checks prefer structural guards (TypeGuard.*) over checking only schema.type or other single-field heuristics [1].
Citations:
- 1: https://dev.to/akaranjkar08/zod-validation-type-safe-apis-forms-in-typescript-complete-guide-3gjj
- 2: https://registry.npmjs.org/%40sinclair%2Ftypebox
TypeBox entry detection is too strict and can throw on null
The guard requires each entry value to have a top-level type key ("type" in v), which can reject valid TypeBox schema entries like Type.Ref / Type.Union that don’t provide a root type discriminator. It also can throw when an entry value is null because "type" in v runs after only typeof v === "object" (where null passes). Use TypeBox’s recursive structural guards (e.g., TypeGuard from @sinclair/typebox/guard) or equivalent schema-aware checks instead of relying on "type" in v.
🤖 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/core/src/shared/assert.ts` around lines 207 - 213, The current
isTypeboxEntries guard is too strict and can throw on null; replace the ad-hoc
`"type" in v` check in isTypeboxEntries with TypeBox's structural guard: import
TypeGuard from '`@sinclair/typebox/guard`' and, after the existing
object/null/array checks in isTypeboxEntries, validate each property value with
TypeGuard.TSchema (or the appropriate TypeGuard predicate) instead of testing
for a top-level "type" key so nulls and non-discriminated schemas like
Type.Ref/Type.Union are handled safely.
| if (IsObject(schema)) { | ||
| return Typebox.Object({ | ||
| user: schema, | ||
| expires: Typebox.Optional(Typebox.String()), | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the idiomatic TypeBox way to accept an ISO datetime string and decode it to a Date instance (z.coerce.date()-like behavior), especially when validation currently uses Value.Check?
💡 Result:
Idiomatic TypeBox approach: use a Transform pipeline from an ISO datetime string to a JS Date, and decode with Value.Decode (not Value.Check) so the string is actually converted. Example (string-in, Date-out): const { Type } = require('@sinclair/typebox') const { Value } = require('@sinclair/typebox/value') // Note: TypeBox does not define date-time formats by default; you can register one // but the simplest approach is to just transform (as TypeBox’s Date examples do). const IsoDateTimeToDate = Type.Transform(Type.String({ format: 'date-time' })).Decode((v) => new Date(v)).Encode((v) => v.toISOString) // 1) Validate + decode (string -> Date) const decoded = Value.Decode(IsoDateTimeToDate, '2024-11-20T14:54:56.334Z') // decoded is a Date // 2) Serialize Date -> string const encoded = Value.Encode(IsoDateTimeToDate, decoded) Why not Value.Check?: - Value.Check only answers “does this runtime value already match the schema?”; it does not run the Decode transform to turn a string into a Date instance. So if your input is still a string, Value.Check against a Date-typed transformed schema won’t give you the Date result you want. - TypeBox’s own discussion explains that Decode/Encode with transforms assume validity and that “Decode: The value is first runtime checked … then executing any embedded Decode callbacks”; in other words, decoding is what triggers the string->Date conversion you’re looking for. [1] If you also want strict ISO datetime validation before converting: - TypeBox validators don’t define the 'date-time' format by default; you need to register it (Ajv-compatible format check implementations are referenced in TypeBox issues/examples). Then you can keep the same Transform and let the format check gate Decode/Value.Check. [2] Putting it together (conceptually): - Register a 'date-time' format checker (optional, for strictness) [2] - Define Type.Transform(Type.String({ format: 'date-time' })) with Decode(v => new Date(v)) [3][2] - Use Value.Decode(schema, isoString) to get a Date instance [2] Notes: - JavaScript’s new Date(value) is permissive about ISO-ish strings; if you need strict RFC3339/ISO8601 parsing, rely on a format checker in the string type (as above) before decoding. [2]
Citations:
- 1: Using Transform types with defaults and Union sinclairzx81/typebox#1184
- 2: How to work with dates and timestamps sinclairzx81/typebox#856
- 3: What is the proper way to parse decoded data? sinclairzx81/typebox#1089
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the specific region
sed -n '120,190p' packages/core/src/validator/registry.ts
# Find where Value.Check / Value.Decode is used in this file
rg -n "Value\.(Check|Decode)" packages/core/src/validator/registry.ts || true
# Find the imports for Value/Typebox in this file
rg -n "from '`@sinclair/typebox`|`@sinclair/typebox/value`|typebox/value|Typebox|Value" packages/core/src/validator/registry.ts || true
# Search broader for Value.Check/Decode usage in the validator package
rg -n "Value\.(Check|Decode)" packages/core/src/validator || trueRepository: aura-stack-ts/auth
Length of output: 3505
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,120p' packages/core/src/validator/validator.ts
sed -n '120,260p' packages/core/src/validator/validator.ts
rg -n "Value\.(Check|Decode)" packages/core/src/validator/validator.ts
rg -n "getFullSchema" packages/core/src/validator -SRepository: aura-stack-ts/auth
Length of output: 2817
TypeBox getFullSchema leaves expires as a string while other adapters return Date
getFullSchema sets expires to Typebox.Optional(Typebox.String()), and createValidator validates TypeBox via Value.Check, returning the input unchanged—so expires stays a string. Zod/Valibot/ArkType branches coerce/transform expires to Date, causing schema-dependent runtime shape drift for the same auth contract.
🤖 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/core/src/validator/registry.ts` around lines 147 - 151,
getFullSchema's TypeBox branch currently defines expires as
Typebox.Optional(Typebox.String()), leaving it a string at runtime while other
adapters coerce to Date; update the TypeBox branch in getFullSchema to declare
expires as a date-compatible schema (e.g., a string with date-time format) and
then adjust the TypeBox path in createValidator to mirror other adapters by
coercing/parsing that field into a JavaScript Date after Value.Check succeeds
(or include a Value.Transform step) so the runtime shape matches the
Zod/Valibot/ArkType branches; reference getFullSchema, the Typebox.Object(...)
creation, the expires property, and createValidator to locate the changes.
Description
This pull request introduces experimental support for Typebox schemas alongside Zod, Arktype and Valibot for defining and validating user identities in Aura Auth.
Valibot schemas can now be used through the
identity.schemaconfiguration to validate identity data across internal authentication flows, including default fields such as:subnameemailimageas well as any additional custom fields defined by the consumer.
Aura Auth internally validates and verifies identity payloads during authentication and session flows.
With this update, the
identity.schemaoption now supports:This validation layer is used to ensure the integrity and consistency of user identity data throughout the authentication lifecycle.
Changes
createAuthto accept Typebox-based identity definitionsusage
Summary by CodeRabbit
New Features
createAuthnow includes experimental support for TypeBox schema validation, enabling users to extend default User fields with TypeBox schemas alongside existing Zod support.Tests