sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
5.08k stars 161 forks source link

Support ajv async validation ? #1070

Closed cit-gif closed 1 week ago

cit-gif commented 1 week ago

I see ajv they can support asynchronous, Do you have any plans for asynchronous support? I think this issue will interest a lot of people

TypeRegistry.SetAsync('CheckUser', async(schema, value) => await db.checkUser(value))
const ajv = new Ajv()

ajv.addKeyword({
  keyword: "idExists",
  async: true,
  type: "number",
  validate: checkIdExists,
})

async function checkIdExists(schema, data) {
  // this is just an example, you would want to avoid SQL injection in your code
  const rows = await sql(`SELECT id FROM ${schema.table} WHERE id = ${data}`)
  return !!rows.length // true if record is found
}

const schema = {
  $async: true,
  properties: {
    userId: {
      type: "integer",
      idExists: {table: "users"},
    },
    postId: {
      type: "integer",
      idExists: {table: "posts"},
    },
  },
}

const validate = ajv.compile(schema)

validate({userId: 1, postId: 19})
  .then(function (data) {
    console.log("Data is valid", data) // { userId: 1, postId: 19 }
  })
  .catch(function (err) {
    if (!(err instanceof Ajv.ValidationError)) throw err
    // data is invalid
    console.log("Validation errors:", err.errors)
  })
sinclairzx81 commented 1 week ago

@cit-gif Hi!

TypeBox only provides a strictly synchronous API and there are no plans to support async validation. TypeBox is somewhat principled in this regard where it views performing IO operations inside validation routines as an anti pattern. This mostly because it mixes concerns (IO + validation), has a significant performance cost (await) and can lead to difficulties debugging code.

If you need asynchronous validation, it is recommended to refactor your code in the following way which wraps the sql call in a async check function, and where this function handles integration between IO and validation.

TypeScript Link Here

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

// Provider by Database Driver
declare function sql(query: string): Promise<unknown[]>

// A asynchronous Query -> Value.Parse
async function CheckedResult<Type extends TSchema, Query extends string>(type: Type, query: Query): Promise<Static<Type>[]> {
  const results = await sql(query)
  return Value.Parse(type, results)
}

const results = await CheckedResult(Type.Object({
  id: Type.String(), // required
  name: Type.String(),
  email: Type.String()
}), `
  SELECT id, name, email FROM User WHERE id = '12345'
`)

// type safe results
const { id, name, email } = results[0]

Will close up this issue for now as async validation is out of scope, but if you have other questions on the above approach, feel free to ping on this thread. Cheers S

cit-gif commented 1 week ago

Thanks you very much!