Skip to content

feat(core): add experimental Typebox schema validation#163

Merged
halvaradop merged 1 commit into
masterfrom
feat/add-experimental-typebox
May 21, 2026
Merged

feat(core): add experimental Typebox schema validation#163
halvaradop merged 1 commit into
masterfrom
feat/add-experimental-typebox

Conversation

@halvaradop
Copy link
Copy Markdown
Member

@halvaradop halvaradop commented May 21, 2026

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.schema configuration to validate identity data across internal authentication flows, including default fields such as:

  • sub
  • name
  • email
  • image

as 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.schema option now supports:

  • Zod schemas
  • Valibot schemas
  • Arktype schemas
  • Typebox schemas

This validation layer is used to ensure the integrity and consistency of user identity data throughout the authentication lifecycle.

Changes

  • Add experimental Typebox support for identity schemas
  • Extend createAuth to accept Typebox-based identity definitions

usage

import { Type as Typebox } from "typebox"
import { createAuth } from "@aura-stack/auth"

const auth = createAuth({
  oauth: [],
  identity: {
    schema: Typebox.Object({
      // ...
    }),
  },
})

Summary by CodeRabbit

  • New Features

    • createAuth now includes experimental support for TypeBox schema validation, enabling users to extend default User fields with TypeBox schemas alongside existing Zod support.
  • Tests

    • Added comprehensive test coverage for TypeBox schema validation across identity creation, schema derivation, and registry functionality.

Review Change Stack

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 21, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
auth Skipped Skipped May 21, 2026 9:10pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR adds experimental TypeBox schema validation support to the auth stack, enabling createAuth to accept TypeBox-defined user shapes alongside existing Zod, Valibot, and ArkType schemas. It introduces TypeBox-specific type utilities, runtime validation logic, unified schema registry handling, and updates all framework integrations to use the new identity type mapping system.

Changes

TypeBox Schema Validation Support

Layer / File(s) Summary
TypeBox Type System Foundation
packages/core/src/@types/index.ts, packages/core/src/@types/utility.ts, packages/core/src/shared/identity.ts
Adds EditableShapeTypebox, TypeboxShapeToObject, and ConfigSchema conditionals to support static type inference for TypeBox schemas. Introduces UserIdentityTypeBox identity schema and extends Identities union, SchemaTypes, and ReturnShapeType to recognize TypeBox shapes.
Runtime TypeBox Detection
packages/core/src/shared/assert.ts, packages/core/src/shared/identity.ts
Implements isTypeboxEntries type guard to detect TypeBox property schemas at runtime, and adds Typebox branch to createIdentity to construct TypeBox objects from shape definitions.
Validator TypeBox Implementation
packages/core/src/validator/validator.ts, packages/core/src/validator/registry.ts
Extends createValidator to detect and validate TypeBox objects using Value.Check and Value.Clean, and adds TypeBox branches to deriveSchema, deriveSchemaWithJWT, getFullSchema, and error formatting. Widens createSchemaRegistry generic constraint to SchemaTypes.
Framework Integration Type Unification
packages/elysia/src/createAuth.ts, packages/express/src/createAuth.ts, packages/hono/src/createAuth.ts, packages/integration/src/createAuth.ts, packages/next/src/createAuth.ts, packages/react-router/src/createAuth.ts
Updates all framework packages to constrain createAuth generics to Identities instead of EditableShape<UserShape>, and rewires withAuth/api to use FromShapeToObject<Identity> for unified schema-to-object type mapping.
Test Coverage and Documentation
packages/core/package.json, packages/core/CHANGELOG.md, packages/core/test/identity.test.ts, packages/core/test/types.test-d.ts, packages/elysia/package.json
Adds TypeBox dependency, documents experimental support in changelog, and provides comprehensive test coverage for createIdentity, deriveSchema, createSchemaRegistry across modes, and type-level assertions for createAuth with TypeBox schemas.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • aura-stack-ts/auth#160: Extends identity type mappings by adding Valibot support alongside the TypeBox support being introduced in this PR, using the same conditional branch pattern in FromShapeToObject and ConfigSchema.
  • aura-stack-ts/auth#161: Modifies packages/core/src/@types/utility.ts to extend identity schema conditionals for ArkType, directly parallel to this PR's TypeBox additions to the same type inference pipeline.
  • aura-stack-ts/auth#162: Updates the core schema registry and validation pipeline in packages/core/src/validator/registry.ts where this PR layers TypeBox support, so changes overlap in schema derivation and error handling logic.

Suggested labels

feature, experimental, enhancement

Poem

🐰 TypeBox joins the validation dance,
Alongside Zod, Valibot, ArkType in a prance—
Shapes unified, identities aware,
New schema flavors for those who dare! ✨

🚥 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 describes the main change: adding experimental Typebox schema validation support to the core authentication library.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-experimental-typebox

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Copy link
Copy Markdown

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between e98f9f2 and f6d1ad8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • packages/core/CHANGELOG.md
  • packages/core/package.json
  • packages/core/src/@types/index.ts
  • packages/core/src/@types/utility.ts
  • packages/core/src/schema-registry.ts
  • packages/core/src/shared/assert.ts
  • packages/core/src/shared/identity.ts
  • packages/core/src/validator/registry.ts
  • packages/core/src/validator/validator.ts
  • packages/core/test/identity.test.ts
  • packages/core/test/types.test-d.ts
  • packages/elysia/package.json
  • packages/elysia/src/createAuth.ts
  • packages/express/src/createAuth.ts
  • packages/express/test/types.test-d.ts
  • packages/hono/src/createAuth.ts
  • packages/integration/src/createAuth.ts
  • packages/next/src/createAuth.ts
  • packages/react-router/src/createAuth.ts
💤 Files with no reviewable changes (2)
  • packages/express/test/types.test-d.ts
  • packages/core/src/schema-registry.ts

Comment on lines +207 to +213
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)
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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:


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.

Comment on lines +147 to +151
if (IsObject(schema)) {
return Typebox.Object({
user: schema,
expires: Typebox.Optional(Typebox.String()),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 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:


🏁 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 || true

Repository: 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 -S

Repository: 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.

@halvaradop halvaradop merged commit 6781c7c into master May 21, 2026
7 checks passed
@halvaradop halvaradop deleted the feat/add-experimental-typebox branch May 21, 2026 22:02
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