From 962bd87217212320b1a68f6556a16b8a6b8f757c Mon Sep 17 00:00:00 2001 From: David Luecke Date: Wed, 12 Apr 2023 10:20:15 -0700 Subject: [PATCH] fix(typebox): Implement custom TypeBuilder for backwards compatibility (#3150) --- packages/typebox/src/index.ts | 66 ++++++++++++++++++++++++++--- packages/typebox/test/index.test.ts | 5 +++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/packages/typebox/src/index.ts b/packages/typebox/src/index.ts index c8d5617836..f291cc295e 100644 --- a/packages/typebox/src/index.ts +++ b/packages/typebox/src/index.ts @@ -1,14 +1,66 @@ -import { Type, TObject, TInteger, TOptional, TSchema, TIntersect, ObjectOptions } from '@sinclair/typebox' +import { + TObject, + TInteger, + TOptional, + TSchema, + TIntersect, + ObjectOptions, + ExtendedTypeBuilder, + SchemaOptions, + TNever, + IntersectOptions, + TypeGuard, + Kind +} from '@sinclair/typebox' import { jsonSchema, Validator, DataValidatorMap, Ajv } from '@feathersjs/schema' export * from '@sinclair/typebox' export * from './default-schemas' -// Export new intersect -export const Intersect = Type.Intersect +/** + * Feathers TypeBox customisations. Implements the 0.25.0 fallback for Intersect types. + * @see https://github.com/sinclairzx81/typebox/issues/373 + */ +export class FeathersTypeBuilder extends ExtendedTypeBuilder { + /** `[Standard]` Creates a Intersect type */ + public Intersect(allOf: [], options?: SchemaOptions): TNever + /** `[Standard]` Creates a Intersect type */ + public Intersect(allOf: [...T], options?: SchemaOptions): T[0] + // /** `[Standard]` Creates a Intersect type */ + public Intersect(allOf: [...T], options?: IntersectOptions): TIntersect + public Intersect(allOf: TObject[], options: IntersectOptions = {}) { + const [required, optional] = [new Set(), new Set()] + for (const object of allOf) { + for (const [key, property] of Object.entries(object.properties)) { + if (TypeGuard.TOptional(property) || TypeGuard.TReadonlyOptional(property)) optional.add(key) + } + } + for (const object of allOf) { + for (const key of Object.keys(object.properties)) { + if (!optional.has(key)) required.add(key) + } + } + const properties = {} as Record + for (const object of allOf) { + for (const [key, schema] of Object.entries(object.properties)) { + properties[key] = + properties[key] === undefined + ? schema + : { [Kind]: 'Union', anyOf: [properties[key], { ...schema }] } + } + } + if (required.size > 0) { + return { ...options, [Kind]: 'Object', type: 'object', properties, required: [...required] } as any + } else { + return { ...options, [Kind]: 'Object', type: 'object', properties } as any + } + } +} -// This is necessary to maintain backwards compatibility between 0.25 and 0.26 -Type.Intersect = Type.Composite as any +/** + * Exports our own type builder + */ +export const Type = new FeathersTypeBuilder() export type TDataSchemaMap = { create: TObject @@ -94,7 +146,7 @@ export const queryProperty = { describe('querySyntax', () => { @@ -100,6 +101,10 @@ describe('@feathersjs/schema/typebox', () => { assert.ok(validated) }) + it('exports custom type builder', () => { + assert.ok(Type instanceof FeathersTypeBuilder) + }) + // Test ObjectId validation it('ObjectId', async () => { const schema = Type.Object({