Skip to content

Commit

Permalink
fix(typebox): Implement custom TypeBuilder for backwards compatibility (
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl committed Apr 12, 2023
1 parent 988c2b5 commit 962bd87
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 7 deletions.
66 changes: 59 additions & 7 deletions packages/typebox/src/index.ts
Original file line number Diff line number Diff line change
@@ -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<T extends [TObject]>(allOf: [...T], options?: SchemaOptions): T[0]
// /** `[Standard]` Creates a Intersect type */
public Intersect<T extends TObject[]>(allOf: [...T], options?: IntersectOptions): TIntersect<T>
public Intersect(allOf: TObject[], options: IntersectOptions = {}) {
const [required, optional] = [new Set<string>(), new Set<string>()]
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<string, any>
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
Expand Down Expand Up @@ -94,7 +146,7 @@ export const queryProperty = <T extends TSchema, X extends { [key: string]: TSch
Type.Union([
def,
Type.Partial(
Type.Intersect(
Type.Composite(
[
Type.Object({
$gt: def,
Expand Down Expand Up @@ -164,7 +216,7 @@ export const querySyntax = <
const $or = Type.Array(propertySchema)
const $and = Type.Array(Type.Union([propertySchema, Type.Object({ $or })]))

return Type.Intersect(
return Type.Composite(
[
Type.Partial(
Type.Object(
Expand Down
5 changes: 5 additions & 0 deletions packages/typebox/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getValidator,
ObjectIdSchema
} from '../src'
import { FeathersTypeBuilder } from '../src'

describe('@feathersjs/schema/typebox', () => {
describe('querySyntax', () => {
Expand Down Expand Up @@ -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({
Expand Down

0 comments on commit 962bd87

Please sign in to comment.