sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.56k stars 148 forks source link

Support for async validations? #921

Closed M-Gonzalo closed 1 week ago

M-Gonzalo commented 1 week ago

Suppose I needed to do something like:

TypeSystem.Format('known-user', async (userId) => await UserModel.exists(userId))

Is there a way of expressing this in typebox? Or at least a similar way of getting the same result

sinclairzx81 commented 1 week ago

@M-Gonzalo Hi, Apologies for the delay.

Async validation isn't supported in TypeBox by default. The reason being that Promise resolution is generally considered a distinct operation to validation (and where you should await or .then() prior call Check).

It is possible to implement you're own async check function if you need however, I would expect a async check to return a checked value if possible, the following implements a Parse like function as well as an AsyncParse that loosely achieves this.

import { Type, Static, TSchema, StaticDecode } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'

// ----------------------------------------------------------------------------------------
// Synchronous Parse
// ----------------------------------------------------------------------------------------
export function Parse<T extends TSchema, R = StaticDecode<T>>(schema: T, value: unknown): R {
  const cloned = Value.Clone(value)                     // clone because value ops can be mutable
  const defaulted = Value.Default(schema, cloned)       // initialize defaults for value
  const converted = Value.Convert(schema, defaulted)    // convert mismatched types for value
  const cleaned = Value.Clean(schema, converted)        // remove unknown properties
  return Value.Decode(schema, cleaned)                  // run decode transforms (optional)
}
// ----------------------------------------------------------------------------------------
// Asynchronous Parse
// ----------------------------------------------------------------------------------------
async function AsyncParse<T extends TSchema>(schema: T, promise: Promise<unknown>): Promise<Static<T>> {
  return Parse(schema, await promise)
}

const User = Type.Object({
  id: Type.String(),
  email: Type.String()
})

// some async function
async function getUser(): Promise<unknown> { return {id: '', email: '' } }

// parses get user asynchronously
const user = await AsyncParse(User, getUser())

Will close off this issue for now, but if you have any other questions, feel free to ping on this thread. Cheers S