@feathersjs/schema 5.0.0-pre.21 → 5.0.0-pre.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/lib/hooks/index.js.map +1 -1
- package/lib/hooks/resolve.d.ts +14 -7
- package/lib/hooks/resolve.js +37 -25
- package/lib/hooks/resolve.js.map +1 -1
- package/lib/hooks/validate.js +3 -3
- package/lib/hooks/validate.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/query.d.ts +10 -10
- package/lib/query.js +32 -26
- package/lib/query.js.map +1 -1
- package/lib/resolver.js +4 -8
- package/lib/resolver.js.map +1 -1
- package/lib/schema.js +1 -1
- package/lib/schema.js.map +1 -1
- package/package.json +10 -9
- package/src/hooks/index.ts +2 -2
- package/src/hooks/resolve.ts +102 -81
- package/src/hooks/validate.ts +18 -18
- package/src/index.ts +8 -8
- package/src/query.ts +73 -64
- package/src/resolver.ts +61 -59
- package/src/schema.ts +27 -27
package/src/resolver.ts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
import { BadRequest } from '@feathersjs/errors'
|
|
2
|
-
import { Schema } from './schema'
|
|
1
|
+
import { BadRequest } from '@feathersjs/errors'
|
|
2
|
+
import { Schema } from './schema'
|
|
3
3
|
|
|
4
4
|
export type PropertyResolver<T, V, C> = (
|
|
5
|
-
value: V|undefined,
|
|
5
|
+
value: V | undefined,
|
|
6
6
|
obj: T,
|
|
7
7
|
context: C,
|
|
8
8
|
status: ResolverStatus<T, C>
|
|
9
|
-
) => Promise<V|undefined
|
|
9
|
+
) => Promise<V | undefined>
|
|
10
10
|
|
|
11
11
|
export type PropertyResolverMap<T, C> = {
|
|
12
12
|
[key in keyof T]?: PropertyResolver<T, T[key], C>
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export type ResolverConverter<T, C> = (
|
|
16
|
-
|
|
15
|
+
export type ResolverConverter<T, C> = (
|
|
16
|
+
obj: any,
|
|
17
|
+
context: C,
|
|
18
|
+
status: ResolverStatus<T, C>
|
|
19
|
+
) => Promise<T | undefined>
|
|
17
20
|
|
|
18
21
|
export interface ResolverConfig<T, C> {
|
|
19
|
-
schema?: Schema<T
|
|
20
|
-
validate?: 'before'|'after'|false
|
|
22
|
+
schema?: Schema<T>
|
|
23
|
+
validate?: 'before' | 'after' | false
|
|
21
24
|
properties: PropertyResolverMap<T, C>
|
|
22
25
|
converter?: ResolverConverter<T, C>
|
|
23
26
|
}
|
|
@@ -30,24 +33,23 @@ export interface ResolverStatus<T, C> {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export class Resolver<T, C> {
|
|
33
|
-
readonly _type!: T
|
|
36
|
+
readonly _type!: T
|
|
34
37
|
|
|
35
|
-
constructor
|
|
36
|
-
}
|
|
38
|
+
constructor(public options: ResolverConfig<T, C>) {}
|
|
37
39
|
|
|
38
|
-
async resolveProperty<D, K extends keyof T>
|
|
40
|
+
async resolveProperty<D, K extends keyof T>(
|
|
39
41
|
name: K,
|
|
40
42
|
data: D,
|
|
41
43
|
context: C,
|
|
42
44
|
status: Partial<ResolverStatus<T, C>> = {}
|
|
43
45
|
): Promise<T[K]> {
|
|
44
|
-
const resolver = this.options.properties[name]
|
|
45
|
-
const value = (data as any)[name]
|
|
46
|
-
const { path = [], stack = [] } = status || {}
|
|
46
|
+
const resolver = this.options.properties[name]
|
|
47
|
+
const value = (data as any)[name]
|
|
48
|
+
const { path = [], stack = [] } = status || {}
|
|
47
49
|
|
|
48
50
|
// This prevents circular dependencies
|
|
49
51
|
if (stack.includes(resolver)) {
|
|
50
|
-
return undefined
|
|
52
|
+
return undefined
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
const resolverStatus = {
|
|
@@ -56,10 +58,10 @@ export class Resolver<T, C> {
|
|
|
56
58
|
stack: [...stack, resolver]
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
return resolver(value, data as any, context, resolverStatus)
|
|
61
|
+
return resolver(value, data as any, context, resolverStatus)
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
async convert
|
|
64
|
+
async convert<D>(data: D, context: C, status?: Partial<ResolverStatus<T, C>>) {
|
|
63
65
|
if (this.options.converter) {
|
|
64
66
|
const { path = [], stack = [] } = status || {}
|
|
65
67
|
|
|
@@ -69,57 +71,57 @@ export class Resolver<T, C> {
|
|
|
69
71
|
return data
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
async resolve<D>
|
|
73
|
-
const { properties: resolvers, schema, validate } = this.options
|
|
74
|
-
const payload = await this.convert(_data, context, status)
|
|
75
|
-
const data = schema && validate === 'before' ? await schema.validate(payload) : payload
|
|
76
|
-
const propertyList = (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
74
|
+
async resolve<D>(_data: D, context: C, status?: Partial<ResolverStatus<T, C>>): Promise<T> {
|
|
75
|
+
const { properties: resolvers, schema, validate } = this.options
|
|
76
|
+
const payload = await this.convert(_data, context, status)
|
|
77
|
+
const data = schema && validate === 'before' ? await schema.validate(payload) : payload
|
|
78
|
+
const propertyList = (
|
|
79
|
+
Array.isArray(status?.properties)
|
|
80
|
+
? status?.properties
|
|
81
|
+
: // By default get all data and resolver keys but remove duplicates
|
|
82
|
+
[...new Set(Object.keys(data).concat(Object.keys(resolvers)))]
|
|
83
|
+
) as (keyof T)[]
|
|
84
|
+
|
|
85
|
+
const result: any = {}
|
|
86
|
+
const errors: any = {}
|
|
87
|
+
let hasErrors = false
|
|
85
88
|
|
|
86
89
|
// Not the most elegant but better performance
|
|
87
|
-
await Promise.all(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
await Promise.all(
|
|
91
|
+
propertyList.map(async (name) => {
|
|
92
|
+
const value = (data as any)[name]
|
|
93
|
+
|
|
94
|
+
if (resolvers[name]) {
|
|
95
|
+
try {
|
|
96
|
+
const resolved = await this.resolveProperty(name, data, context, status)
|
|
97
|
+
|
|
98
|
+
if (resolved !== undefined) {
|
|
99
|
+
result[name] = resolved
|
|
100
|
+
}
|
|
101
|
+
} catch (error: any) {
|
|
102
|
+
// TODO add error stacks
|
|
103
|
+
const convertedError =
|
|
104
|
+
typeof error.toJSON === 'function' ? error.toJSON() : { message: error.message || error }
|
|
105
|
+
|
|
106
|
+
errors[name] = convertedError
|
|
107
|
+
hasErrors = true
|
|
96
108
|
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const convertedError = typeof error.toJSON === 'function'
|
|
100
|
-
? error.toJSON()
|
|
101
|
-
: { message: error.message || error };
|
|
102
|
-
|
|
103
|
-
errors[name] = convertedError;
|
|
104
|
-
hasErrors = true;
|
|
109
|
+
} else if (value !== undefined) {
|
|
110
|
+
result[name] = value
|
|
105
111
|
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
}));
|
|
112
|
+
})
|
|
113
|
+
)
|
|
110
114
|
|
|
111
115
|
if (hasErrors) {
|
|
112
|
-
const propertyName = status?.properties ? ` ${status.properties.join('.')}` : ''
|
|
116
|
+
const propertyName = status?.properties ? ` ${status.properties.join('.')}` : ''
|
|
113
117
|
|
|
114
|
-
throw new BadRequest('Error resolving data' + (propertyName ? ` ${propertyName}` : ''), errors)
|
|
118
|
+
throw new BadRequest('Error resolving data' + (propertyName ? ` ${propertyName}` : ''), errors)
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
return schema && validate === 'after'
|
|
118
|
-
? await schema.validate(result)
|
|
119
|
-
: result;
|
|
121
|
+
return schema && validate === 'after' ? await schema.validate(result) : result
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
export function resolve
|
|
124
|
-
return new Resolver<T, C>(options)
|
|
125
|
+
export function resolve<T, C>(options: ResolverConfig<T, C>) {
|
|
126
|
+
return new Resolver<T, C>(options)
|
|
125
127
|
}
|
package/src/schema.ts
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import Ajv, { AsyncValidateFunction, ValidateFunction } from 'ajv'
|
|
2
|
-
import { FromSchema, JSONSchema } from 'json-schema-to-ts'
|
|
3
|
-
import { BadRequest } from '@feathersjs/errors'
|
|
1
|
+
import Ajv, { AsyncValidateFunction, ValidateFunction } from 'ajv'
|
|
2
|
+
import { FromSchema, JSONSchema } from 'json-schema-to-ts'
|
|
3
|
+
import { BadRequest } from '@feathersjs/errors'
|
|
4
4
|
|
|
5
5
|
export const DEFAULT_AJV = new Ajv({
|
|
6
6
|
coerceTypes: true
|
|
7
|
-
})
|
|
7
|
+
})
|
|
8
8
|
|
|
9
|
-
export { Ajv }
|
|
9
|
+
export { Ajv }
|
|
10
10
|
|
|
11
11
|
export type JSONSchemaDefinition = JSONSchema & {
|
|
12
|
-
$id: string
|
|
13
|
-
$async?: boolean
|
|
12
|
+
$id: string
|
|
13
|
+
$async?: boolean
|
|
14
14
|
properties?: { [key: string]: JSONSchema }
|
|
15
15
|
required?: readonly string[]
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
17
|
|
|
18
18
|
export interface Schema<T> {
|
|
19
|
-
validate
|
|
19
|
+
validate<X = T>(...args: Parameters<ValidateFunction<X>>): Promise<X>
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export class SchemaWrapper<S extends JSONSchemaDefinition> implements Schema<FromSchema<S>> {
|
|
23
|
-
ajv: Ajv
|
|
24
|
-
validator: AsyncValidateFunction
|
|
25
|
-
readonly _type!: FromSchema<S
|
|
23
|
+
ajv: Ajv
|
|
24
|
+
validator: AsyncValidateFunction
|
|
25
|
+
readonly _type!: FromSchema<S>
|
|
26
26
|
|
|
27
|
-
constructor
|
|
28
|
-
this.ajv = ajv
|
|
27
|
+
constructor(public definition: S, ajv: Ajv = DEFAULT_AJV) {
|
|
28
|
+
this.ajv = ajv
|
|
29
29
|
this.validator = this.ajv.compile({
|
|
30
30
|
$async: true,
|
|
31
31
|
...(this.definition as any)
|
|
32
|
-
}) as AsyncValidateFunction
|
|
32
|
+
}) as AsyncValidateFunction
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
get properties
|
|
36
|
-
return this.definition.properties as S['properties']
|
|
35
|
+
get properties() {
|
|
36
|
+
return this.definition.properties as S['properties']
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
get required
|
|
40
|
-
return this.definition.required as S['required']
|
|
39
|
+
get required() {
|
|
40
|
+
return this.definition.required as S['required']
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async validate
|
|
43
|
+
async validate<T = FromSchema<S>>(...args: Parameters<ValidateFunction<T>>) {
|
|
44
44
|
try {
|
|
45
|
-
const validated = await this.validator(...args) as T
|
|
45
|
+
const validated = (await this.validator(...args)) as T
|
|
46
46
|
|
|
47
|
-
return validated
|
|
47
|
+
return validated
|
|
48
48
|
} catch (error: any) {
|
|
49
|
-
throw new BadRequest(error.message, error.errors)
|
|
49
|
+
throw new BadRequest(error.message, error.errors)
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
toJSON
|
|
54
|
-
return this.definition
|
|
53
|
+
toJSON() {
|
|
54
|
+
return this.definition
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function schema
|
|
59
|
-
return new SchemaWrapper(definition, ajv)
|
|
58
|
+
export function schema<S extends JSONSchemaDefinition>(definition: S, ajv: Ajv = DEFAULT_AJV) {
|
|
59
|
+
return new SchemaWrapper(definition, ajv)
|
|
60
60
|
}
|