241 lines
5.7 KiB
TypeScript

import { feathers, HookContext, Application as FeathersApplication } from '@feathersjs/feathers'
import { memory, MemoryService } from '@feathersjs/memory'
import { GeneralError } from '@feathersjs/errors'
import {
resolve,
resolveResult,
resolveQuery,
resolveData,
validateData,
validateQuery,
querySyntax,
resolveDispatch,
resolveAll,
Ajv,
FromSchema,
getValidator,
getDataValidator,
virtual
} from '../src'
import { AdapterParams } from '../../memory/node_modules/@feathersjs/adapter-commons/lib'
const fixtureAjv = new Ajv({
coerceTypes: true,
addUsedSchema: false
})
export const userDataSchema = {
$id: 'UserData',
type: 'object',
additionalProperties: false,
required: ['email'],
properties: {
email: { type: 'string' },
password: { type: 'string' }
}
} as const
export const userDataValidator = getValidator(userDataSchema, fixtureAjv)
export const userDataValidatorMap = getDataValidator(userDataSchema, fixtureAjv)
export type UserData = FromSchema<typeof userDataSchema>
export const userDataResolver = resolve<UserData, HookContext<Application>>({
properties: {
password: async () => {
return 'hashed'
}
}
})
export const userSchema = {
$id: 'User',
type: 'object',
additionalProperties: false,
required: ['id', ...userDataSchema.required],
properties: {
...userDataSchema.properties,
id: { type: 'number' },
name: { type: 'string' }
}
} as const
export type User = FromSchema<typeof userSchema>
export const userResolver = resolve<User, HookContext<Application>>({
properties: {
name: async (_value, user) => user.email.split('@')[0]
}
})
export const userExternalResolver = resolve<User, HookContext<Application>>({
properties: {
password: async (): Promise<undefined> => undefined,
email: async () => '[redacted]'
}
})
export const secondUserResolver = resolve<User, HookContext<Application>>({
name: async (value, user) => `${value} (${user.email})`
})
export const messageDataSchema = {
$id: 'MessageData',
type: 'object',
additionalProperties: false,
required: ['text', 'userId'],
properties: {
text: { type: 'string' },
userId: { type: 'number' }
}
} as const
export type MessageData = FromSchema<typeof messageDataSchema>
export const messageSchema = {
$id: 'MessageResult',
type: 'object',
additionalProperties: false,
required: ['id', ...messageDataSchema.required],
properties: {
...messageDataSchema.properties,
id: { type: 'number' },
user: { $ref: 'User' }
}
} as const
export type Message = FromSchema<
typeof messageSchema,
{
references: [typeof userSchema]
}
>
export const messageResolver = resolve<Message, HookContext<Application>>({
user: virtual(async (message, context) => {
const { userId } = message
if (context.params.error === true) {
throw new GeneralError('This is an error')
}
const user = await context.app.service('users').get(userId, context.params)
return user as Message['user']
})
})
export const messageQuerySchema = {
$id: 'MessageQuery',
type: 'object',
additionalProperties: false,
required: [],
properties: {
...querySyntax(messageDataSchema.properties),
$select: {
type: 'array',
items: { type: 'string' }
},
$resolve: {
type: 'array',
items: { type: 'string' }
}
}
} as const
export type MessageQuery = FromSchema<typeof messageQuerySchema>
export const messageQueryValidator = getValidator(messageQuerySchema, fixtureAjv)
export const messageQueryResolver = resolve<MessageQuery, HookContext<Application>>({
userId: async (value, _query, context) => {
if (context.params?.user) {
return context.params.user.id
}
return value
}
})
class MessageService extends MemoryService<Message, MessageData, ServiceParams> {
async customMethod(data: any) {
return data
}
}
const customMethodDataResolver = resolve<any, HookContext<Application>>({
properties: {
userId: async () => 0,
additionalData: async () => 'additional data'
}
})
interface ServiceParams extends AdapterParams {
user?: User
error?: boolean
}
type ServiceTypes = {
users: MemoryService<User, UserData, ServiceParams>
messages: MessageService
paginatedMessages: MemoryService<Message, MessageData, ServiceParams>
}
type Application = FeathersApplication<ServiceTypes>
const app = feathers<ServiceTypes>()
app.use(
'users',
memory({
multi: ['create']
})
)
app.use('messages', new MessageService(), {
methods: ['find', 'get', 'create', 'update', 'patch', 'remove', 'customMethod']
})
app.use('paginatedMessages', memory({ paginate: { default: 10 } }))
app.service('messages').hooks({
around: {
all: [
resolveAll({
result: messageResolver,
query: messageQueryResolver
}),
validateQuery(messageQueryValidator)
],
customMethod: [resolveData(customMethodDataResolver)],
find: [
async (context, next) => {
// A hook that makes sure that virtual properties are not passed to the adapter as `$select`
// An SQL adapter would throw an error if it received a query like this
if (context.params?.query?.$select && context.params?.query?.$select.includes('user')) {
throw new Error('Invalid $select')
}
await next()
}
]
}
})
app
.service('paginatedMessages')
.hooks([
resolveDispatch(),
resolveResult(messageResolver),
validateQuery(messageQueryValidator),
resolveQuery(messageQueryResolver)
])
app
.service('users')
.hooks([resolveDispatch(userExternalResolver), resolveResult(userResolver, secondUserResolver)])
app.service('users').hooks({
create: [validateData(userDataValidator), validateData(userDataValidatorMap), resolveData(userDataResolver)]
})
export { app }