Skip to content
Merged
Prev Previous commit
Next Next commit
Improve schema and resolver usage
  • Loading branch information
daffl committed Oct 4, 2022
commit 9e28bc8155335f07ac6d5c6d46fc1de8b208e2b2
8 changes: 4 additions & 4 deletions packages/cli/src/authentication/templates/user.schema.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const template = ({
authStrategies,
type,
relative
}: AuthenticationGeneratorContext) => /* ts */ `import { getValidator, getDataValidator, resolve, querySyntax } from '@feathersjs/schema'
}: AuthenticationGeneratorContext) => /* ts */ `import { resolve, jsonSchema } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'
${authStrategies.includes('local') ? `import { passwordHash } from '@feathersjs/authentication-local'` : ''}

Expand All @@ -35,7 +35,7 @@ export const ${camelName}DataSchema = {

export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>

export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)

export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
properties: {
Expand Down Expand Up @@ -76,13 +76,13 @@ export const ${camelName}QuerySchema = {
type: 'object',
additionalProperties: false,
properties: {
...querySyntax(${camelName}Schema.properties)
...jsonSchema.querySyntax(${camelName}Schema.properties)
}
} as const

export type ${upperName}Query = FromSchema<typeof ${camelName}QuerySchema>

export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)

export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
properties: {
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/service/templates/schema.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const template = ({
upperName,
relative,
type
}: ServiceGeneratorContext) => /* ts */ `import { querySyntax, getValidator, getDataValidator, resolve } from '@feathersjs/schema'
}: ServiceGeneratorContext) => /* ts */ `import { jsonSchema, resolve } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'

import type { HookContext } from '${relative}/declarations'
Expand All @@ -28,7 +28,7 @@ export const ${camelName}DataSchema = {

export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>

export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)

export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
properties: {}
Expand Down Expand Up @@ -64,13 +64,13 @@ export const ${camelName}QuerySchema = {
type: 'object',
additionalProperties: false,
properties: {
...querySyntax(${camelName}Schema.properties)
...jsonSchema.querySyntax(${camelName}Schema.properties)
}
} as const

export type ${upperName}Query = FromSchema<typeof ${camelName}QuerySchema>

export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)

export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
properties: {}
Expand Down
3 changes: 2 additions & 1 deletion packages/schema/src/hooks/validate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HookContext, NextFunction } from '@feathersjs/feathers'
import { BadRequest } from '@feathersjs/errors'
import { DataValidatorMap, Schema, Validator } from '../schema'
import { Schema, Validator } from '../schema'
import { DataValidatorMap } from '../json-schema'

export const validateQuery = <H extends HookContext>(schema: Schema<any> | Validator) => {
const validator: Validator = typeof schema === 'function' ? schema : schema.validate.bind(schema)
Expand Down
4 changes: 3 additions & 1 deletion packages/schema/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export type { FromSchema } from 'json-schema-to-ts'
export * from './schema'
export * from './resolver'
export * from './hooks'
export * from './query'
export * from './json-schema'

export * as schemaHooks from './hooks'
export * as jsonSchema from './json-schema'

export type Infer<S extends { _type: any }> = S['_type']

Expand Down
171 changes: 171 additions & 0 deletions packages/schema/src/json-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { _ } from '@feathersjs/commons'
import { JSONSchema } from 'json-schema-to-ts'
import { JSONSchemaDefinition, Ajv, Validator } from './schema'

export type DataSchemaMap = {
create: JSONSchemaDefinition
update?: JSONSchemaDefinition
patch?: JSONSchemaDefinition
}

export type DataValidatorMap = {
create: Validator
update: Validator
patch: Validator
}

/**
* Returns a compiled validation function for a schema 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: JSONSchemaDefinition, validator: Ajv): Validator<T, R> =>
validator.compile({
$async: true,
...(schema as any)
}) as any as Validator<T, R>

/**
* 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 JSON schema definition or a mapping of `create`, `update` and `patch`
* to their respecitve JSON schema
* @param validator The Ajv instance to use as the validator
* @returns A map of validator functions
*/
export const getDataValidator = (
def: JSONSchemaDefinition | DataSchemaMap,
validator: Ajv
): DataValidatorMap => {
const schema = ((def as any).create ? def : { create: def }) as DataSchemaMap

return {
create: getValidator(schema.create, validator),
update: getValidator(schema.update || schema.create, validator),
patch: getValidator(
schema.patch || {
...(schema.create as any),
required: []
},
validator
)
}
}

export type PropertyQuery<D extends JSONSchema> = {
anyOf: [
D,
{
type: 'object'
additionalProperties: false
properties: {
$gt: D
$gte: D
$lt: D
$lte: D
$ne: D
$in: {
type: 'array'
items: D
}
$nin: {
type: 'array'
items: D
}
}
}
]
}

/**
* Create a Feathers query syntax compatible JSON schema definition for a property definition.
*
* @param def The property definition (e.g. `{ type: 'string' }`)
* @returns A JSON schema definition for the Feathers query syntax for this property.
*/
export const queryProperty = <T extends JSONSchema>(def: T) => {
const definition = _.omit(def, 'default')
return {
anyOf: [
definition,
{
type: 'object',
additionalProperties: false,
properties: {
$gt: definition,
$gte: definition,
$lt: definition,
$lte: definition,
$ne: definition,
$in: {
type: 'array',
items: definition
},
$nin: {
type: 'array',
items: definition
}
}
}
]
} as const
}

/**
* Creates Feathers a query syntax compatible JSON schema for multiple properties.
*
* @param definition A map of property definitions
* @returns The JSON schema definition for the Feathers query syntax for multiple properties
*/
export const queryProperties = <T extends { [key: string]: JSONSchema }>(definition: T) =>
Object.keys(definition).reduce((res, key) => {
const result = res as any

result[key] = queryProperty(definition[key])

return result
}, {} as { [K in keyof T]: PropertyQuery<T[K]> })

/**
* Creates a JSON schema for the complete Feathers query syntax including `$limit`, $skip`
* and `$sort` and `$select` for the allowed properties.
*
* @param definition The property definitions to create the query syntax schema for
* @returns A JSON schema for the complete query syntax
*/
export const querySyntax = <T extends { [key: string]: any }>(definition: T) =>
({
$limit: {
type: 'number',
minimum: 0
},
$skip: {
type: 'number',
minimum: 0
},
$sort: {
type: 'object',
properties: Object.keys(definition).reduce((res, key) => {
const result = res as any

result[key] = {
type: 'number',
enum: [1, -1]
}

return result
}, {} as { [K in keyof T]: { readonly type: 'number'; readonly enum: [1, -1] } })
},
$select: {
type: 'array',
items: {
type: 'string',
enum: Object.keys(definition) as any as (keyof T)[]
}
},
...queryProperties(definition)
} as const)
97 changes: 0 additions & 97 deletions packages/schema/src/query.ts

This file was deleted.

Loading