Skip to content

Commit

Permalink
feat(client): Improve client side custom method support (#2654)
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl authored Jun 6, 2022
1 parent c9b8f74 commit c138acf
Show file tree
Hide file tree
Showing 15 changed files with 900 additions and 834 deletions.
1,577 changes: 791 additions & 786 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/feathers/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export type CustomMethods<T extends { [key: string]: [any, any] }> = {
[K in keyof T]: (data: T[K][0], params?: Params) => Promise<T[K][1]>
}

export type CustomMethod<T = any, R = T, P extends Params = Params> = (data: T, params?: P) => Promise<R>

export type ServiceMixin<A> = (service: FeathersService<A>, path: string, options: ServiceOptions) => void

export type ServiceGenericType<S> = S extends ServiceInterface<infer T> ? T : any
Expand Down
4 changes: 2 additions & 2 deletions packages/rest-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@
"dependencies": {
"@feathersjs/commons": "^5.0.0-pre.22",
"@feathersjs/errors": "^5.0.0-pre.22",
"@types/node-fetch": "^3.0.2",
"@feathersjs/feathers": "^5.0.0-pre.22",
"@types/superagent": "^4.1.15",
"qs": "^6.10.3"
},
"devDependencies": {
"@feathersjs/express": "^5.0.0-pre.22",
"@feathersjs/feathers": "^5.0.0-pre.22",
"@feathersjs/memory": "^5.0.0-pre.22",
"@feathersjs/tests": "^5.0.0-pre.22",
"@types/node-fetch": "^2.0.2",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.31",
"@types/qs": "^6.9.7",
Expand Down
3 changes: 2 additions & 1 deletion packages/rest-client/src/axios.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Params } from '@feathersjs/feathers'
import { Base, RestClientParams } from './base'

export class AxiosClient extends Base {
export class AxiosClient<T = any, D = Partial<T>, P extends Params = RestClientParams> extends Base<T, D, P> {
request(options: any, params: RestClientParams) {
const config = Object.assign(
{
Expand Down
18 changes: 10 additions & 8 deletions packages/rest-client/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ interface RestClientSettings {
options: any
}

export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<T, D> {
export abstract class Base<T = any, D = Partial<T>, P extends Params = RestClientParams>
implements ServiceInterface<T, D, P>
{
name: string
base: string
connection: any
Expand Down Expand Up @@ -57,7 +59,7 @@ export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<
return ''
}

abstract request(options: any, params: Params): any
abstract request(options: any, params: P): any

methods(this: any, ...names: string[]) {
names.forEach((method) => {
Expand All @@ -83,7 +85,7 @@ export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<
return this
}

find(params: RestClientParams = {}) {
find(params?: P) {
return this.request(
{
url: this.makeUrl(params.query),
Expand All @@ -94,7 +96,7 @@ export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<
).catch(toError)
}

get(id: Id, params: RestClientParams = {}) {
get(id: Id, params?: P) {
if (typeof id === 'undefined') {
return Promise.reject(new Error("id for 'get' can not be undefined"))
}
Expand All @@ -109,7 +111,7 @@ export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<
).catch(toError)
}

create(body: D, params: RestClientParams = {}) {
create(body: D, params?: P) {
return this.request(
{
url: this.makeUrl(params.query),
Expand All @@ -121,7 +123,7 @@ export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<
).catch(toError)
}

update(id: NullableId, body: D, params: RestClientParams = {}) {
update(id: NullableId, body: D, params?: P) {
if (typeof id === 'undefined') {
return Promise.reject(
new Error("id for 'update' can not be undefined, only 'null' when updating multiple entries")
Expand All @@ -139,7 +141,7 @@ export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<
).catch(toError)
}

patch(id: NullableId, body: D, params: RestClientParams = {}) {
patch(id: NullableId, body: D, params?: P) {
if (typeof id === 'undefined') {
return Promise.reject(
new Error("id for 'patch' can not be undefined, only 'null' when updating multiple entries")
Expand All @@ -157,7 +159,7 @@ export abstract class Base<T = any, D = Partial<T>> implements ServiceInterface<
).catch(toError)
}

remove(id: NullableId, params: RestClientParams = {}) {
remove(id: NullableId, params?: P) {
if (typeof id === 'undefined') {
return Promise.reject(
new Error("id for 'remove' can not be undefined, only 'null' when removing multiple entries")
Expand Down
3 changes: 2 additions & 1 deletion packages/rest-client/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { errors } from '@feathersjs/errors'
import { Params } from '@feathersjs/feathers'
import { Base, RestClientParams } from './base'

export class FetchClient extends Base {
export class FetchClient<T = any, D = Partial<T>, P extends Params = RestClientParams> extends Base<T, D, P> {
request(options: any, params: RestClientParams) {
const fetchOptions = Object.assign({}, options, params.connection)

Expand Down
11 changes: 10 additions & 1 deletion packages/rest-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Application, defaultServiceMethods } from '@feathersjs/feathers'

import { Base } from './base'
import { AxiosClient } from './axios'
import { FetchClient } from './fetch'
Expand Down Expand Up @@ -56,13 +58,20 @@ export default function restClient(base = '') {
return new (Service as any)({ base, name, connection, options })
}

const initialize = (app: any) => {
const initialize = (app: Application & { rest: any }) => {
if (app.rest !== undefined) {
throw new Error('Only one default client provider can be configured')
}

app.rest = connection
app.defaultService = defaultService
app.mixins.unshift((service, _location, options) => {
if (options && options.methods && service instanceof Base) {
const customMethods = options.methods.filter((name) => !defaultServiceMethods.includes(name))

service.methods(...customMethods)
}
})
}

initialize.Service = Service
Expand Down
7 changes: 6 additions & 1 deletion packages/rest-client/src/superagent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Params } from '@feathersjs/feathers'
import { Base, RestClientParams } from './base'

export class SuperagentClient extends Base {
export class SuperagentClient<T = any, D = Partial<T>, P extends Params = RestClientParams> extends Base<
T,
D,
P
> {
request(options: any, params: RestClientParams) {
const superagent = this.connection(options.method, options.url)
.set(this.options.headers || {})
Expand Down
10 changes: 6 additions & 4 deletions packages/rest-client/test/axios.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import { ServiceTypes } from './declarations'

describe('Axios REST connector', function () {
const url = 'http://localhost:8889'
const setup = rest(url).axios(axios)
const app = feathers<ServiceTypes>().configure(setup)
const connection = rest(url).axios(axios)
const app = feathers<ServiceTypes>()
.configure(connection)
.use('todos', connection.service('todos'), {
methods: ['get', 'find', 'create', 'patch', 'customMethod']
})
const service = app.service('todos')
let server: Server

service.methods('customMethod')

before(async () => {
server = await createServer().listen(8889)
})
Expand Down
13 changes: 11 additions & 2 deletions packages/rest-client/test/declarations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { CustomMethods } from '@feathersjs/feathers'
import { CustomMethod } from '@feathersjs/feathers'
import { RestService } from '../src'

type Data = { message: string }
type Result = {
data: Data
provider: string
type: string
}

export type ServiceTypes = {
todos: RestService & CustomMethods<{ customMethod: any }>
todos: RestService & {
customMethod: CustomMethod<Data, Result>
}
}
24 changes: 17 additions & 7 deletions packages/rest-client/test/fetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { strict as assert } from 'assert'

// @ts-ignore
import fetch from 'node-fetch'
import { feathers } from '@feathersjs/feathers'
import { clientTests } from '@feathersjs/tests'
Expand All @@ -14,12 +11,25 @@ import { ServiceTypes } from './declarations'

describe('fetch REST connector', function () {
const url = 'http://localhost:8889'
const setup = rest(url).fetch(fetch)
const app = feathers<ServiceTypes>().configure(setup)
const connection = rest(url).fetch(fetch)
const app = feathers<ServiceTypes>()
.configure(connection)
.use('todos', connection.service('todos'), {
methods: ['get', 'find', 'create', 'patch', 'customMethod']
})

const service = app.service('todos')
let server: Server

service.methods('customMethod')
service.hooks({
after: {
customMethod: [
(context) => {
context.result.data.message += '!'
}
]
}
})

before(async () => {
server = await createServer().listen(8889)
Expand Down Expand Up @@ -127,7 +137,7 @@ describe('fetch REST connector', function () {
const result = await service.customMethod({ message: 'hi' })

assert.deepEqual(result, {
data: { message: 'hi' },
data: { message: 'hi!' },
provider: 'rest',
type: 'customMethod'
})
Expand Down
2 changes: 1 addition & 1 deletion packages/rest-client/test/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default (configurer?: any) => {
})
)
// Host our Todos service on the /todos path
.use('/todos', new TodoService(), {
.use('todos', new TodoService(), {
methods: ['find', 'get', 'create', 'patch', 'update', 'remove', 'customMethod']
})
.use(errorHandler)
Expand Down
25 changes: 21 additions & 4 deletions packages/socketio-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { Service, SocketService } from '@feathersjs/transport-commons/client'
import { Socket } from 'socket.io-client'
import { defaultEventMap } from '@feathersjs/feathers'
import { Application, defaultEventMap, defaultServiceMethods } from '@feathersjs/feathers'

export { SocketService }

declare module '@feathersjs/feathers/lib/declarations' {
interface FeathersApplication<Services, Settings> {
/**
* The Socket.io client instance. Usually does not need
* to be accessed directly.
*/
io?: Socket
}
}

export default function socketioClient(connection: Socket, options?: any) {
if (!connection) {
throw new Error('Socket.io connection needs to be provided')
Expand All @@ -18,16 +28,23 @@ export default function socketioClient(connection: Socket, options?: any) {
method: 'emit'
})

return new Service(settings)
return new Service(settings) as any
}

const initialize = function (app: any) {
const initialize = function (app: Application) {
if (app.io !== undefined) {
throw new Error('Only one default client provider can be configured')
}

app.io = connection
app.io = connection as any
app.defaultService = defaultService
app.mixins.unshift((service, _location, options) => {
if (options && options.methods && service instanceof Service) {
const customMethods = options.methods.filter((name) => !defaultServiceMethods.includes(name))

service.methods(...customMethods)
}
})
}

initialize.Service = Service
Expand Down
29 changes: 15 additions & 14 deletions packages/socketio-client/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { strict as assert } from 'assert'
import { Server } from 'http'
import { CustomMethods, feathers } from '@feathersjs/feathers'
import { CustomMethod, feathers } from '@feathersjs/feathers'
import { io, Socket } from 'socket.io-client'
import { clientTests } from '@feathersjs/tests'

Expand All @@ -10,7 +10,9 @@ import socketio, { SocketService } from '../src'

type ServiceTypes = {
'/': SocketService
todos: SocketService & CustomMethods<{ customMethod: any }>
todos: SocketService & {
customMethod: CustomMethod<{ message: string }>
}
[key: string]: any
}

Expand All @@ -20,17 +22,16 @@ describe('@feathersjs/socketio-client', () => {
let socket: Socket
let server: Server

before((done) => {
createServer()
.listen(9988)
.then((srv) => {
server = srv
server.once('listening', () => {
socket = io('http://localhost:9988')
app.configure(socketio(socket))
done()
})
})
before(async () => {
server = await createServer().listen(9988)
socket = io('http://localhost:9988')

const connection = socketio(socket)

app.configure(connection)
app.use('todos', connection.service('todos'), {
methods: ['find', 'get', 'create', 'patch', 'customMethod']
})
})

after((done) => {
Expand Down Expand Up @@ -88,7 +89,7 @@ describe('@feathersjs/socketio-client', () => {
})

it('calls .customMethod', async () => {
const service = app.service('todos').methods('customMethod')
const service = app.service('todos')
const result = await service.customMethod({ message: 'hi' })

assert.deepStrictEqual(result, {
Expand Down
6 changes: 4 additions & 2 deletions packages/transport-commons/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ interface ServiceOptions {
events?: string[]
}

export type SocketService<T = any, D = Partial<any>> = Service<T, D>
export type SocketService<T = any, D = Partial<any>, P extends Params = Params> = Service<T, D, P>

export class Service<T = any, D = Partial<T>> implements ServiceInterface<T, D> {
export class Service<T = any, D = Partial<T>, P extends Params = Params>
implements ServiceInterface<T, D, P>
{
events: string[]
path: string
connection: any
Expand Down

0 comments on commit c138acf

Please sign in to comment.