FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(typebox): support nested extension query keys by tonycoder-hub · Pull Request #3697 · feathersjs/feathers · GitHub

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .ts  (2) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
55 changes: 39 additions & 16 deletions packages/typebox/src/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,35 @@ export function StringEnum<T extends string[]>(allowedValues: [...T], options?:
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 QueryExtensions<T extends TObject> = {
[K in keyof T['properties']]?: { [key: string]: TSchema }
}

type NoQueryExtensions = Record<never, never>

type ExtensionPropertyKeys<X> = {
[K in Extract<keyof X, string>]: {
[P in Extract<keyof NonNullable<X[K]>, string>]: P extends `$${string}` ? never : `${K}.${P}`
}[Extract<keyof NonNullable<X[K]>, string>]
}[Extract<keyof X, string>]

type QuerySyntaxKey<T extends TObject, X extends QueryExtensions<T>> =
| Extract<keyof T['properties'], string>
| ExtensionPropertyKeys<X>

const querySyntaxKeys = <T extends TObject, X extends QueryExtensions<T>>(type: T, extensions: X) =>
[
...Object.keys(type.properties),
...Object.entries(extensions).flatMap(([property, extension]) =>
Object.keys(extension || {})
.filter((key) => !key.startsWith('$'))
.map((key) => `${property}.${key}`)
)
] as QuerySyntaxKey<T, X>[]

const arrayOfKeys = <T extends TObject, X extends QueryExtensions<T>>(type: T, extensions: X) => {
const keys = querySyntaxKeys(type, extensions)
return Type.Unsafe<QuerySyntaxKey<T, X>[]>({
type: 'array',
maxItems: keys.length,
items: {
Expand All @@ -72,16 +98,19 @@ const arrayOfKeys = <T extends TObject>(type: T) => {
* @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(
export function sortDefinition<T extends TObject, X extends QueryExtensions<T> = NoQueryExtensions>(
schema: T,
extensions: X = {} as X
) {
const properties = querySyntaxKeys(schema, extensions).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> }
{} as { [K in QuerySyntaxKey<T, X>]: TOptional<TInteger> }
)

return Type.Object(properties, { additionalProperties: false })
Expand Down Expand Up @@ -133,10 +162,7 @@ type QueryProperty<T extends TSchema, X extends { [key: string]: TSchema }> = Re
* @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 } }
>(
export const queryProperties = <T extends TObject, X extends QueryExtensions<T> = NoQueryExtensions>(
definition: T,
extensions: X = {} as X
) => {
Expand Down Expand Up @@ -164,10 +190,7 @@ export const queryProperties = <
* @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 } }
>(
export const querySyntax = <T extends TObject, X extends QueryExtensions<T> = NoQueryExtensions>(
type: T,
extensions: X = {} as X,
options: ObjectOptions = { additionalProperties: false }
Expand All @@ -183,8 +206,8 @@ export const querySyntax = <
{
$limit: Type.Number({ minimum: 0 }),
$skip: Type.Number({ minimum: 0 }),
$sort: sortDefinition(type),
$select: arrayOfKeys(type),
$sort: sortDefinition(type, extensions),
$select: arrayOfKeys(type, extensions),
$and,
$or
},
Expand Down
66 changes: 66 additions & 0 deletions packages/typebox/test/index.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,72 @@ describe('@feathersjs/schema/typebox', () => {

assert.ok(validated)
})

it('query syntax can sort by nested extension properties', async () => {
const schema = Type.Object({
profile: Type.Object({})
})
const querySchema = querySyntax(schema, {
profile: {
name: Type.String()
}
})
const validator = new Ajv().compile(querySchema)

type Query = Static<typeof querySchema>

const query: Query = {
$sort: {
'profile.name': 1
}
}

const validated = await validator(query)

assert.ok(validated)
})

it('query syntax can select nested extension properties', async () => {
const schema = Type.Object({
profile: Type.Object({})
})
const querySchema = querySyntax(schema, {
profile: {
name: Type.String()
}
})
const validator = new Ajv().compile(querySchema)

type Query = Static<typeof querySchema>

const query: Query = {
$select: ['profile.name']
}

const validated = await validator(query)

assert.ok(validated)
})

it('query syntax does not treat extension operators as sortable properties', async () => {
const schema = Type.Object({
name: Type.String()
})
const querySchema = querySyntax(schema, {
name: {
$ilike: Type.String()
}
})
const validator = new Ajv().compile(querySchema)

const validated = await validator({
$sort: {
'name.$ilike': 1
}
})

assert.ok(!validated)
})
})

it('$in and $nin works with array type', async () => {
Expand Down

Back | FazBrowse Home | New Git URL