UNPKG

@feathersjs/typebox

Version:

TypeBox integration for @feathersjs/schema

202 lines (182 loc) 6.12 kB
import { Type, TObject, TInteger, TOptional, TSchema, ObjectOptions, TIntersect, TUnion, type TRecord } from '@sinclair/typebox' import { jsonSchema, Validator, DataValidatorMap, Ajv } from '@feathersjs/schema' export * from '@sinclair/typebox' export * from './default-schemas' export type TDataSchemaMap = { create: TObject update?: TObject patch?: TObject } /** * Returns a compiled validation function for a TypeBox object and AJV validator instance. * * @param schema The JSON schema definition * @param validator The AJV validation instance * @returns A compiled validation function */ export const getValidator = <T = any, R = T>( schema: TObject | TIntersect | TUnion<TObject[]> | TRecord, validator: Ajv ): Validator<T, R> => jsonSchema.getValidator(schema as any, validator) /** * Returns compiled validation functions to validate data for the `create`, `update` and `patch` * service methods. If not passed explicitly, the `update` validator will be the same as the `create` * and `patch` will be the `create` validator with no required fields. * * @param def Either general TypeBox object definition or a mapping of `create`, `update` and `patch` * to their respective type object * @param validator The Ajv instance to use as the validator * @returns A map of validator functions */ export const getDataValidator = (def: TObject | TDataSchemaMap, validator: Ajv): DataValidatorMap => jsonSchema.getDataValidator(def as any, validator) /** * A TypeBox utility that converts an array of provided strings into a string enum. * @param allowedValues array of strings for the enum * @returns TypeBox.Type */ export function StringEnum<T extends string[]>(allowedValues: [...T], options?: { default: T[number] }) { return Type.Unsafe<T[number]>({ type: 'string', enum: allowedValues, ...options }) } const arrayOfKeys = <T extends TObject>(type: T) => { const keys = Object.keys(type.properties) return Type.Unsafe<(keyof T['properties'])[]>({ type: 'array', maxItems: keys.length, items: { type: 'string', ...(keys.length > 0 ? { enum: keys } : {}) } }) } /** * Creates the `$sort` Feathers query syntax schema for an object schema * * @param schema The TypeBox object schema * @returns The `$sort` syntax schema */ export function sortDefinition<T extends TObject>(schema: T) { const properties = Object.keys(schema.properties).reduce( (res, key) => { const result = res as any result[key] = Type.Optional(Type.Integer({ minimum: -1, maximum: 1 })) return result }, {} as { [K in keyof T['properties']]: TOptional<TInteger> } ) return Type.Object(properties, { additionalProperties: false }) } /** * Returns the standard Feathers query syntax for a property schema, * including operators like `$gt`, `$lt` etc. for a single property * * @param def The property definition * @param extension Additional properties to add to the property query * @returns The Feathers query syntax schema */ export const queryProperty = <T extends TSchema, X extends { [key: string]: TSchema }>( def: T, extension: X = {} as X ) => Type.Optional( Type.Union([ def, Type.Partial( Type.Intersect( [ Type.Object({ $gt: def, $gte: def, $lt: def, $lte: def, $ne: def, $in: def.type === 'array' ? def : Type.Array(def), $nin: def.type === 'array' ? def : Type.Array(def) }), Type.Object(extension) ], { additionalProperties: false } ) ) ]) ) type QueryProperty<T extends TSchema, X extends { [key: string]: TSchema }> = ReturnType< typeof queryProperty<T, X> > /** * Creates a Feathers query syntax schema for the properties defined in `definition`. * * @param definition The properties to create the Feathers query syntax schema for * @param extensions Additional properties to add to a property query * @returns The Feathers query syntax schema */ export const queryProperties = < T extends TObject, X extends { [K in keyof T['properties']]?: { [key: string]: TSchema } } >( definition: T, extensions: X = {} as X ) => { const properties = Object.keys(definition.properties).reduce( (res, key) => { const result = res as any const value = definition.properties[key] result[key] = queryProperty(value, extensions[key]) return result }, {} as { [K in keyof T['properties']]: QueryProperty<T['properties'][K], X[K]> } ) return Type.Optional(Type.Object(properties, { additionalProperties: false })) } /** * Creates a TypeBox schema for the complete Feathers query syntax including `$limit`, $skip`, `$or` * and `$sort` and `$select` for the allowed properties. * * @param type The properties to create the query syntax for * @param extensions Additional properties to add to the query syntax * @param options Options for the TypeBox object schema * @returns A TypeBox object representing the complete Feathers query syntax for the given properties */ export const querySyntax = < T extends TObject, X extends { [K in keyof T['properties']]?: { [key: string]: TSchema } } >( type: T, extensions: X = {} as X, options: ObjectOptions = { additionalProperties: false } ) => { const propertySchema = queryProperties(type, extensions) const $or = Type.Array(propertySchema) const $and = Type.Array(Type.Union([propertySchema, Type.Object({ $or })])) return Type.Intersect( [ Type.Partial( Type.Object( { $limit: Type.Number({ minimum: 0 }), $skip: Type.Number({ minimum: 0 }), $sort: sortDefinition(type), $select: arrayOfKeys(type), $and, $or }, { additionalProperties: false } ) ), propertySchema ], options ) } export const ObjectIdSchema = () => Type.Union([Type.String({ objectid: true }), Type.Object({}, { additionalProperties: true })])