@feathersjs/schema 5.0.0-pre.20 → 5.0.0-pre.23
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 +47 -0
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +19 -0
- package/lib/hooks/index.js.map +1 -0
- package/lib/hooks/resolve.d.ts +21 -0
- package/lib/hooks/resolve.js +142 -0
- package/lib/hooks/resolve.js.map +1 -0
- package/lib/hooks/validate.d.ts +4 -0
- package/lib/hooks/validate.js +41 -0
- package/lib/hooks/validate.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js.map +1 -1
- package/lib/query.d.ts +9 -9
- package/lib/query.js +32 -26
- package/lib/query.js.map +1 -1
- package/lib/resolver.d.ts +3 -0
- package/lib/resolver.js +14 -10
- package/lib/resolver.js.map +1 -1
- package/lib/schema.js +1 -1
- package/lib/schema.js.map +1 -1
- package/package.json +10 -8
- package/src/hooks/index.ts +2 -0
- package/src/hooks/resolve.ts +191 -0
- package/src/hooks/validate.ts +44 -0
- package/src/index.ts +9 -7
- package/src/query.ts +73 -64
- package/src/resolver.ts +74 -57
- package/src/schema.ts +27 -27
- package/lib/hooks.d.ts +0 -8
- package/lib/hooks.js +0 -119
- package/lib/hooks.js.map +0 -1
- package/src/hooks.ts +0 -143
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { HookContext, NextFunction } from '@feathersjs/feathers'
|
|
2
|
+
import { compose } from '@feathersjs/hooks'
|
|
3
|
+
import { Resolver, ResolverStatus } from '../resolver'
|
|
4
|
+
|
|
5
|
+
const getContext = <H extends HookContext>(context: H) => {
|
|
6
|
+
return {
|
|
7
|
+
...context,
|
|
8
|
+
params: {
|
|
9
|
+
...context.params,
|
|
10
|
+
query: {}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getData = <H extends HookContext>(context: H) => {
|
|
16
|
+
const isPaginated = context.method === 'find' && context.result.data
|
|
17
|
+
const data = isPaginated ? context.result.data : context.result
|
|
18
|
+
|
|
19
|
+
return { isPaginated, data }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const runResolvers = async <T, H extends HookContext>(
|
|
23
|
+
resolvers: Resolver<T, H>[],
|
|
24
|
+
data: any,
|
|
25
|
+
ctx: H,
|
|
26
|
+
status?: Partial<ResolverStatus<T, H>>
|
|
27
|
+
) => {
|
|
28
|
+
let current: any = data
|
|
29
|
+
|
|
30
|
+
for (const resolver of resolvers) {
|
|
31
|
+
if (resolver && typeof resolver.resolve === 'function') {
|
|
32
|
+
current = await resolver.resolve(current, ctx, status)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return current as T
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ResolverSetting<H extends HookContext> = Resolver<any, H> | Resolver<any, H>[]
|
|
40
|
+
|
|
41
|
+
export type DataResolvers<H extends HookContext> = {
|
|
42
|
+
create: Resolver<any, H>
|
|
43
|
+
patch: Resolver<any, H>
|
|
44
|
+
update: Resolver<any, H>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ResolveAllSettings<H extends HookContext> = {
|
|
48
|
+
data?: DataResolvers<H>
|
|
49
|
+
query?: Resolver<any, H>
|
|
50
|
+
result?: Resolver<any, H>
|
|
51
|
+
dispatch?: Resolver<any, H>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const DISPATCH = Symbol('@feathersjs/schema/dispatch')
|
|
55
|
+
|
|
56
|
+
export const getDispatch = (value: any) =>
|
|
57
|
+
typeof value === 'object' && value !== null && value[DISPATCH] !== undefined ? value[DISPATCH] : value
|
|
58
|
+
|
|
59
|
+
export const resolveQuery =
|
|
60
|
+
<T, H extends HookContext>(...resolvers: Resolver<T, H>[]) =>
|
|
61
|
+
async (context: H, next?: NextFunction) => {
|
|
62
|
+
const ctx = getContext(context)
|
|
63
|
+
const data = context?.params?.query || {}
|
|
64
|
+
const query = await runResolvers(resolvers, data, ctx)
|
|
65
|
+
|
|
66
|
+
context.params = {
|
|
67
|
+
...context.params,
|
|
68
|
+
query
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof next === 'function') {
|
|
72
|
+
return next()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const resolveData =
|
|
77
|
+
<H extends HookContext>(settings: DataResolvers<H> | Resolver<any, H>) =>
|
|
78
|
+
async (context: H, next?: NextFunction) => {
|
|
79
|
+
if (context.method === 'create' || context.method === 'patch' || context.method === 'update') {
|
|
80
|
+
const resolvers = settings instanceof Resolver ? [settings] : [settings[context.method]]
|
|
81
|
+
const ctx = getContext(context)
|
|
82
|
+
const data = context.data
|
|
83
|
+
|
|
84
|
+
const status = {
|
|
85
|
+
originalContext: context
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (Array.isArray(data)) {
|
|
89
|
+
context.data = await Promise.all(data.map((current) => runResolvers(resolvers, current, ctx, status)))
|
|
90
|
+
} else {
|
|
91
|
+
context.data = await runResolvers(resolvers, data, ctx, status)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof next === 'function') {
|
|
96
|
+
return next()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const resolveResult =
|
|
101
|
+
<T, H extends HookContext>(...resolvers: Resolver<T, H>[]) =>
|
|
102
|
+
async (context: H, next?: NextFunction) => {
|
|
103
|
+
if (typeof next === 'function') {
|
|
104
|
+
const { $resolve: properties, ...query } = context.params?.query || {}
|
|
105
|
+
const resolve = {
|
|
106
|
+
originalContext: context,
|
|
107
|
+
...context.params.resolve,
|
|
108
|
+
properties
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
context.params = {
|
|
112
|
+
...context.params,
|
|
113
|
+
resolve,
|
|
114
|
+
query
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await next()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const ctx = getContext(context)
|
|
121
|
+
const status = context.params.resolve
|
|
122
|
+
const { isPaginated, data } = getData(context)
|
|
123
|
+
|
|
124
|
+
const result = Array.isArray(data)
|
|
125
|
+
? await Promise.all(data.map(async (current) => runResolvers(resolvers, current, ctx, status)))
|
|
126
|
+
: await runResolvers(resolvers, data, ctx, status)
|
|
127
|
+
|
|
128
|
+
if (isPaginated) {
|
|
129
|
+
context.result.data = result
|
|
130
|
+
} else {
|
|
131
|
+
context.result = result
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const resolveDispatch =
|
|
136
|
+
<T, H extends HookContext>(...resolvers: Resolver<T, H>[]) =>
|
|
137
|
+
async (context: H, next?: NextFunction) => {
|
|
138
|
+
if (typeof next === 'function') {
|
|
139
|
+
await next()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const ctx = getContext(context)
|
|
143
|
+
const status = context.params.resolve
|
|
144
|
+
const { isPaginated, data } = getData(context)
|
|
145
|
+
const resolveAndGetDispatch = async (current: any) => {
|
|
146
|
+
const resolved = await runResolvers(resolvers, current, ctx, status)
|
|
147
|
+
|
|
148
|
+
return Object.keys(resolved).reduce((res, key) => {
|
|
149
|
+
res[key] = getDispatch(current[key])
|
|
150
|
+
|
|
151
|
+
return res
|
|
152
|
+
}, {} as any)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result = await (Array.isArray(data)
|
|
156
|
+
? Promise.all(data.map(resolveAndGetDispatch))
|
|
157
|
+
: resolveAndGetDispatch(data))
|
|
158
|
+
const dispatch = isPaginated
|
|
159
|
+
? {
|
|
160
|
+
...context.result,
|
|
161
|
+
data: result
|
|
162
|
+
}
|
|
163
|
+
: result
|
|
164
|
+
|
|
165
|
+
context.dispatch = dispatch
|
|
166
|
+
Object.defineProperty(context.result, DISPATCH, {
|
|
167
|
+
value: dispatch,
|
|
168
|
+
enumerable: false,
|
|
169
|
+
configurable: false
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const resolveAll = <H extends HookContext>(map: ResolveAllSettings<H>) => {
|
|
174
|
+
const middleware = []
|
|
175
|
+
|
|
176
|
+
middleware.push(resolveDispatch(map.dispatch))
|
|
177
|
+
|
|
178
|
+
if (map.result) {
|
|
179
|
+
middleware.push(resolveResult(map.result))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (map.query) {
|
|
183
|
+
middleware.push(resolveQuery(map.query))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (map.data) {
|
|
187
|
+
middleware.push(resolveData(map.data))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return compose(middleware)
|
|
191
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { HookContext, NextFunction } from '@feathersjs/feathers'
|
|
2
|
+
import { BadRequest } from '../../../errors/lib'
|
|
3
|
+
import { Schema } from '../schema'
|
|
4
|
+
|
|
5
|
+
export const validateQuery =
|
|
6
|
+
<H extends HookContext>(schema: Schema<any>) =>
|
|
7
|
+
async (context: H, next?: NextFunction) => {
|
|
8
|
+
const data = context?.params?.query || {}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const query = await schema.validate(data)
|
|
12
|
+
|
|
13
|
+
context.params = {
|
|
14
|
+
...context.params,
|
|
15
|
+
query
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof next === 'function') {
|
|
19
|
+
return next()
|
|
20
|
+
}
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
throw error.ajv ? new BadRequest(error.message, error.errors) : error
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const validateData =
|
|
27
|
+
<H extends HookContext>(schema: Schema<any>) =>
|
|
28
|
+
async (context: H, next?: NextFunction) => {
|
|
29
|
+
const data = context.data
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (Array.isArray(data)) {
|
|
33
|
+
context.data = await Promise.all(data.map((current) => schema.validate(current)))
|
|
34
|
+
} else {
|
|
35
|
+
context.data = await schema.validate(data)
|
|
36
|
+
}
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
throw error.ajv ? new BadRequest(error.message, error.errors) : error
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof next === 'function') {
|
|
42
|
+
return next()
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { ResolverStatus
|
|
1
|
+
import { ResolverStatus } from './resolver'
|
|
2
2
|
|
|
3
|
-
export * from './schema'
|
|
4
|
-
export * from './resolver'
|
|
5
|
-
export * from './hooks'
|
|
6
|
-
export * from './query'
|
|
3
|
+
export * from './schema'
|
|
4
|
+
export * from './resolver'
|
|
5
|
+
export * from './hooks'
|
|
6
|
+
export * from './query'
|
|
7
7
|
|
|
8
|
-
export type Infer<S extends { _type: any }> = S['_type']
|
|
8
|
+
export type Infer<S extends { _type: any }> = S['_type']
|
|
9
|
+
|
|
10
|
+
export type Combine<S extends { _type: any }, U> = Pick<Infer<S>, Exclude<keyof Infer<S>, keyof U>> & U
|
|
9
11
|
|
|
10
12
|
declare module '@feathersjs/feathers/lib/declarations' {
|
|
11
13
|
interface Params {
|
|
12
|
-
resolve?: ResolverStatus<any, HookContext
|
|
14
|
+
resolve?: ResolverStatus<any, HookContext>
|
|
13
15
|
}
|
|
14
16
|
}
|
package/src/query.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ } from '@feathersjs/commons'
|
|
2
|
+
import { JSONSchema } from 'json-schema-to-ts'
|
|
2
3
|
|
|
3
4
|
export type PropertyQuery<D extends JSONSchema> = {
|
|
4
5
|
anyOf: [
|
|
5
6
|
D,
|
|
6
7
|
{
|
|
7
|
-
type: 'object'
|
|
8
|
-
additionalProperties: false
|
|
8
|
+
type: 'object'
|
|
9
|
+
additionalProperties: false
|
|
9
10
|
properties: {
|
|
10
|
-
$gt: D
|
|
11
|
-
$gte: D
|
|
12
|
-
$lt: D
|
|
13
|
-
$lte: D
|
|
14
|
-
$ne: D
|
|
11
|
+
$gt: D
|
|
12
|
+
$gte: D
|
|
13
|
+
$lt: D
|
|
14
|
+
$lte: D
|
|
15
|
+
$ne: D
|
|
15
16
|
$in: {
|
|
16
|
-
type: 'array'
|
|
17
|
+
type: 'array'
|
|
17
18
|
items: D
|
|
18
|
-
}
|
|
19
|
+
}
|
|
19
20
|
$nin: {
|
|
20
|
-
type: 'array'
|
|
21
|
+
type: 'array'
|
|
21
22
|
items: D
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -25,64 +26,72 @@ export type PropertyQuery<D extends JSONSchema> = {
|
|
|
25
26
|
]
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export const queryProperty = <T extends JSONSchema>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
export const queryProperty = <T extends JSONSchema>(def: T) => {
|
|
30
|
+
const definition = _.omit(def, 'default')
|
|
31
|
+
return {
|
|
32
|
+
anyOf: [
|
|
33
|
+
definition,
|
|
34
|
+
{
|
|
35
|
+
type: 'object',
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
properties: {
|
|
38
|
+
$gt: definition,
|
|
39
|
+
$gte: definition,
|
|
40
|
+
$lt: definition,
|
|
41
|
+
$lte: definition,
|
|
42
|
+
$ne: definition,
|
|
43
|
+
$in: {
|
|
44
|
+
type: 'array',
|
|
45
|
+
items: definition
|
|
46
|
+
},
|
|
47
|
+
$nin: {
|
|
48
|
+
type: 'array',
|
|
49
|
+
items: definition
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
53
|
+
]
|
|
54
|
+
} as const
|
|
55
|
+
}
|
|
52
56
|
|
|
53
|
-
export const queryProperties = <T extends { [key: string]: JSONSchema }>
|
|
57
|
+
export const queryProperties = <T extends { [key: string]: JSONSchema }>(definition: T) =>
|
|
54
58
|
Object.keys(definition).reduce((res, key) => {
|
|
55
|
-
|
|
59
|
+
const result = res as any
|
|
60
|
+
|
|
61
|
+
result[key] = queryProperty(definition[key])
|
|
56
62
|
|
|
57
|
-
return
|
|
63
|
+
return result
|
|
58
64
|
}, {} as { [K in keyof T]: PropertyQuery<T[K]> })
|
|
59
65
|
|
|
60
|
-
export const querySyntax = <T extends { [key: string]: JSONSchema }>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
(res
|
|
73
|
-
|
|
74
|
-
enum: [1, -1]
|
|
75
|
-
}
|
|
66
|
+
export const querySyntax = <T extends { [key: string]: JSONSchema }>(definition: T) =>
|
|
67
|
+
({
|
|
68
|
+
$limit: {
|
|
69
|
+
type: 'number',
|
|
70
|
+
minimum: 0
|
|
71
|
+
},
|
|
72
|
+
$skip: {
|
|
73
|
+
type: 'number',
|
|
74
|
+
minimum: 0
|
|
75
|
+
},
|
|
76
|
+
$sort: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: Object.keys(definition).reduce((res, key) => {
|
|
79
|
+
const result = res as any
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
type: '
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
result[key] = {
|
|
82
|
+
type: 'number',
|
|
83
|
+
enum: [1, -1]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
}, {} as { [K in keyof T]: { readonly type: 'number'; readonly enum: [1, -1] } })
|
|
88
|
+
},
|
|
89
|
+
$select: {
|
|
90
|
+
type: 'array',
|
|
91
|
+
items: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
enum: Object.keys(definition) as any as (keyof T)[]
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
...queryProperties(definition)
|
|
97
|
+
} as const)
|
package/src/resolver.ts
CHANGED
|
@@ -1,49 +1,55 @@
|
|
|
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
|
+
obj: any,
|
|
17
|
+
context: C,
|
|
18
|
+
status: ResolverStatus<T, C>
|
|
19
|
+
) => Promise<T | undefined>
|
|
20
|
+
|
|
15
21
|
export interface ResolverConfig<T, C> {
|
|
16
|
-
schema?: Schema<T
|
|
17
|
-
validate?: 'before'|'after'|false
|
|
22
|
+
schema?: Schema<T>
|
|
23
|
+
validate?: 'before' | 'after' | false
|
|
18
24
|
properties: PropertyResolverMap<T, C>
|
|
25
|
+
converter?: ResolverConverter<T, C>
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
export interface ResolverStatus<T, C> {
|
|
22
|
-
path: string[]
|
|
23
|
-
originalContext?: C
|
|
24
|
-
properties?: string[]
|
|
25
|
-
stack: PropertyResolver<T, any, C>[]
|
|
29
|
+
path: string[]
|
|
30
|
+
originalContext?: C
|
|
31
|
+
properties?: string[]
|
|
32
|
+
stack: PropertyResolver<T, any, C>[]
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export class Resolver<T, C> {
|
|
29
|
-
readonly _type!: T
|
|
36
|
+
readonly _type!: T
|
|
30
37
|
|
|
31
|
-
constructor
|
|
32
|
-
}
|
|
38
|
+
constructor(public options: ResolverConfig<T, C>) {}
|
|
33
39
|
|
|
34
|
-
async resolveProperty<D, K extends keyof T>
|
|
40
|
+
async resolveProperty<D, K extends keyof T>(
|
|
35
41
|
name: K,
|
|
36
42
|
data: D,
|
|
37
43
|
context: C,
|
|
38
44
|
status: Partial<ResolverStatus<T, C>> = {}
|
|
39
45
|
): Promise<T[K]> {
|
|
40
|
-
const resolver = this.options.properties[name]
|
|
41
|
-
const value = (data as any)[name]
|
|
42
|
-
const { path = [], stack = [] } = status || {}
|
|
46
|
+
const resolver = this.options.properties[name]
|
|
47
|
+
const value = (data as any)[name]
|
|
48
|
+
const { path = [], stack = [] } = status || {}
|
|
43
49
|
|
|
44
50
|
// This prevents circular dependencies
|
|
45
51
|
if (stack.includes(resolver)) {
|
|
46
|
-
return undefined
|
|
52
|
+
return undefined
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
const resolverStatus = {
|
|
@@ -52,59 +58,70 @@ export class Resolver<T, C> {
|
|
|
52
58
|
stack: [...stack, resolver]
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
return resolver(value, data as any, context, resolverStatus)
|
|
61
|
+
return resolver(value, data as any, context, resolverStatus)
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
async
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const propertyList = (Array.isArray(status?.properties)
|
|
62
|
-
? status?.properties
|
|
63
|
-
// By default get all data and resolver keys but remove duplicates
|
|
64
|
-
: [...new Set(Object.keys(data).concat(Object.keys(resolvers)))]
|
|
65
|
-
) as (keyof T)[];
|
|
64
|
+
async convert<D>(data: D, context: C, status?: Partial<ResolverStatus<T, C>>) {
|
|
65
|
+
if (this.options.converter) {
|
|
66
|
+
const { path = [], stack = [] } = status || {}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let hasErrors = false;
|
|
68
|
+
return this.options.converter(data, context, { ...status, path, stack })
|
|
69
|
+
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const value = (data as any)[name];
|
|
71
|
+
return data
|
|
72
|
+
}
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
78
88
|
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
// Not the most elegant but better performance
|
|
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
|
|
81
108
|
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const convertedError = typeof error.toJSON === 'function'
|
|
85
|
-
? error.toJSON()
|
|
86
|
-
: { message: error.message || error };
|
|
87
|
-
|
|
88
|
-
errors[name] = convertedError;
|
|
89
|
-
hasErrors = true;
|
|
109
|
+
} else if (value !== undefined) {
|
|
110
|
+
result[name] = value
|
|
90
111
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
}));
|
|
112
|
+
})
|
|
113
|
+
)
|
|
95
114
|
|
|
96
115
|
if (hasErrors) {
|
|
97
|
-
const propertyName = status?.properties ? ` ${status.properties.join('.')}` : ''
|
|
116
|
+
const propertyName = status?.properties ? ` ${status.properties.join('.')}` : ''
|
|
98
117
|
|
|
99
|
-
throw new BadRequest('Error resolving data' + propertyName, errors)
|
|
118
|
+
throw new BadRequest('Error resolving data' + (propertyName ? ` ${propertyName}` : ''), errors)
|
|
100
119
|
}
|
|
101
120
|
|
|
102
|
-
return schema && validate === 'after'
|
|
103
|
-
? await schema.validate(result)
|
|
104
|
-
: result;
|
|
121
|
+
return schema && validate === 'after' ? await schema.validate(result) : result
|
|
105
122
|
}
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
export function resolve
|
|
109
|
-
return new Resolver<T, C>(options)
|
|
125
|
+
export function resolve<T, C>(options: ResolverConfig<T, C>) {
|
|
126
|
+
return new Resolver<T, C>(options)
|
|
110
127
|
}
|