Skip to content

Commit 7d03b57

Browse files
authored
feat(schema): Virtual property resolvers (#2900)
1 parent 834091c commit 7d03b57

File tree

11 files changed

+14818
-85
lines changed

11 files changed

+14818
-85
lines changed

docs/api/schema/resolvers.md

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,31 @@ Property resolver functions should only return a value and not have side effects
9898

9999
</BlockQuote>
100100

101+
## Virtual property resolvers
102+
103+
Virtual resolvers are property resolvers that do not use the `value` but instead always return a value of their own. The parameters are (`(data, context, status)`). The above example can be written like this:
104+
105+
```ts
106+
import { resolve, virtual } from '@feathersjs/schema'
107+
108+
const userResolver = resolve<User, MyContext>({
109+
isDrinkingAge: virtual(async (user, context) => {
110+
const drinkingAge = await context.getDrinkingAge(user.country)
111+
112+
return user.age >= drinkingAge
113+
}),
114+
fullName: virtual(async (user, context) => {
115+
return `${user.firstName} ${user.lastName}`
116+
})
117+
})
118+
```
119+
120+
<BlockQuote type="warning" label="Important">
121+
122+
Virtual resolvers should always be used when combined with a [database adapter](../databases/adapters.md) in order to make valid [$select queries](../databases/querying.md#select). Otherwise queries could try to select fields that do not exist in the database which will throw an error.
123+
124+
</BlockQuote>
125+
101126
## Options
102127

103128
A resolver takes the following options as the second parameter:
@@ -194,8 +219,14 @@ app.service('users').hooks({
194219

195220
Result resolvers use the `schemaHooks.resolveResult(...resolvers)` hook and resolve the data that is returned by a service call ([context.result](../hooks.md#context-result) in a hook). This can be used to populate associations or add other computed properties etc. It is possible to pass multiple resolvers which will run in the order they are passed, using the previous data. `schemaHooks.resolveResult` can be used as an `around` and `after` hook.
196221

222+
<BlockQuote type="warning" label="Important">
223+
224+
Use as an `around` hook is recommended since this will ensure that database adapters will be able to handle [$select queries](../databases/querying.md#select) properly when using [virtual properties](#virtual-property-resolvers).
225+
226+
</BlockQuote>
227+
197228
```ts
198-
import { schemaHooks, resolve } from '@feathersjs/schema'
229+
import { schemaHooks, resolve, virtual } from '@feathersjs/schema'
199230
import { Type } from '@feathersjs/typebox'
200231
import type { Static } from '@feathersjs/typebox'
201232
import type { HookContext } from '../declarations'
@@ -225,16 +256,14 @@ const messageSchema = Type.Object(
225256
type Message = Static<typeof messageSchema>
226257

227258
export const messageResolver = resolve<Message, HookContext>({
228-
properties: {
229-
user: async (_value, message, context) => {
230-
// Populate the user associated via `userId`
231-
return context.app.service('users').get(message.userId)
232-
}
233-
}
259+
user: virtual(async (message, context) => {
260+
// Populate the user associated via `userId`
261+
return context.app.service('users').get(message.userId)
262+
})
234263
})
235264

236265
app.service('messages').hooks({
237-
after: {
266+
around: {
238267
all: [schemaHooks.resolveResult(messageResolver)]
239268
}
240269
})
@@ -262,10 +291,8 @@ const userSchema = Type.Object(
262291
type User = Static<typeof userSchema>
263292

264293
export const userExternalResolver = resolve<User, HookContext>({
265-
properties: {
266-
// Always hide the password for external responses
267-
password: async () => undefined
268-
}
294+
// Always hide the password for external responses
295+
password: async () => undefined
269296
})
270297

271298
// Dispatch should be resolved on every method
@@ -310,15 +337,13 @@ export const userQuerySchema = querySyntax(userQueryProperties)
310337
export type UserQuery = Static<typeof userQuerySchema>
311338

312339
export const userQueryResolver = resolve<UserQuery, HookContext>({
313-
properties: {
314-
// If there is an authenticated user, they can only see their own data
315-
id: async (value, query, context) => {
316-
if (context.params.user) {
317-
return context.params.user.id
318-
}
319-
320-
return value
340+
// If there is an authenticated user, they can only see their own data
341+
id: async (value, query, context) => {
342+
if (context.params.user) {
343+
return context.params.user.id
321344
}
345+
346+
return value
322347
}
323348
})
324349

docs/guides/basics/hooks.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,16 @@ export const message = (app: Application) => {
139139
around: {
140140
all: [
141141
logRuntime,
142-
authenticate('jwt')
143-
]
142+
authenticate('jwt'),
143+
schemaHooks.resolveExternal(messageExternalResolver),
144+
schemaHooks.resolveResult(messageResolver)
145+
],
146+
find: [],
147+
get: [],
148+
create: [],
149+
update: [],
150+
patch: [],
151+
remove: []
144152
},
145153
before: {
146154
all: [
@@ -151,7 +159,7 @@ export const message = (app: Application) => {
151159
]
152160
},
153161
after: {
154-
all: [schemaHooks.resolveResult(messageResolver), schemaHooks.resolveExternal(messageExternalResolver)]
162+
all: []
155163
},
156164
error: {
157165
all: []

docs/guides/basics/schemas.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ Update the `src/services/messages/messages.schema.js` file like this:
214214

215215
<DatabaseBlock global-id="sql">
216216

217-
```ts{7,14-16,22-25,38-44,50,60-64}
218-
import { resolve } from '@feathersjs/schema'
217+
```ts{1, 7,14-16,22-25,38-44,50,60-64}
218+
import { resolve, virtual } from '@feathersjs/schema'
219219
import { Type, getDataValidator, getValidator, querySyntax } from '@feathersjs/typebox'
220220
import type { Static } from '@feathersjs/typebox'
221221
@@ -236,10 +236,10 @@ export const messageSchema = Type.Object(
236236
)
237237
export type Message = Static<typeof messageSchema>
238238
export const messageResolver = resolve<Message, HookContext>({
239-
user: async (_value, message, context) => {
239+
user: virtual(async (message, context) => {
240240
// Associate the user that sent the message
241241
return context.app.service('users').get(message.userId)
242-
}
242+
})
243243
})
244244
245245
export const messageExternalResolver = resolve<Message, HookContext>({})
@@ -289,8 +289,8 @@ export const messageQueryResolver = resolve<MessageQuery, HookContext>({
289289

290290
<DatabaseBlock global-id="mongodb">
291291

292-
```ts{7,14-16,22-25,38-44,50,60-64}
293-
import { resolve } from '@feathersjs/schema'
292+
```ts{1,7,14-16,22-25,38-44,50,60-64}
293+
import { resolve, virtual } from '@feathersjs/schema'
294294
import { Type, getDataValidator, getValidator, querySyntax } from '@feathersjs/typebox'
295295
import type { Static } from '@feathersjs/typebox'
296296
@@ -311,10 +311,10 @@ export const messageSchema = Type.Object(
311311
)
312312
export type Message = Static<typeof messageSchema>
313313
export const messageResolver = resolve<Message, HookContext>({
314-
user: async (_value, message, context) => {
314+
user: virtual(async (message, context) => {
315315
// Associate the user that sent the message
316316
return context.app.service('users').get(message.userId)
317-
}
317+
})
318318
})
319319
320320
export const messageExternalResolver = resolve<Message, HookContext>({})

0 commit comments

Comments
 (0)