Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(adapter): Add patch data type to adapters and refactor AdapterBase usage #2906

Merged
merged 6 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions packages/adapter-commons/src/declarations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Query, Params, Paginated, Id, NullableId } from '@feathersjs/feathers'
import { Query, Params, Paginated, Id } from '@feathersjs/feathers'

export type FilterQueryOptions = {
filters?: FilterSettings
Expand Down Expand Up @@ -80,16 +80,22 @@ export interface AdapterParams<
*
* @see {@link https://docs.feathersjs.com/guides/migrating.html#hook-less-service-methods}
*/
export interface InternalServiceMethods<T = any, D = T, P extends AdapterParams = AdapterParams> {
export interface InternalServiceMethods<
Result = any,
Data = Result,
PatchData = Partial<Data>,
Params extends AdapterParams = AdapterParams,
IdType = Id
> {
/**
* Retrieve all resources from this service.
* Does not sanitize the query and should only be used on the server.
*
* @param _params - Service call parameters {@link Params}
*/
$find(_params?: P & { paginate?: PaginationOptions }): Promise<Paginated<T>>
$find(_params?: P & { paginate: false }): Promise<T[]>
$find(params?: P): Promise<T[] | Paginated<T>>
_find(_params?: Params & { paginate?: PaginationOptions }): Promise<Paginated<Result>>
_find(_params?: Params & { paginate: false }): Promise<Result[]>
_find(params?: Params): Promise<Result[] | Paginated<Result>>

/**
* Retrieve a single resource matching the given ID, skipping any service-level hooks.
Expand All @@ -100,7 +106,7 @@ export interface InternalServiceMethods<T = any, D = T, P extends AdapterParams
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#get-id-params|Feathers API Documentation: .get(id, params)}
*/
$get(id: Id, params?: P): Promise<T>
_get(id: IdType, params?: Params): Promise<Result>

/**
* Create a new resource for this service, skipping any service-level hooks.
Expand All @@ -111,9 +117,9 @@ export interface InternalServiceMethods<T = any, D = T, P extends AdapterParams
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#create-data-params|Feathers API Documentation: .create(data, params)}
*/
$create(data: Partial<D>, params?: P): Promise<T>
$create(data: Partial<D>[], params?: P): Promise<T[]>
$create(data: Partial<D> | Partial<D>[], params?: P): Promise<T | T[]>
_create(data: Data, params?: Params): Promise<Result>
_create(data: Data[], params?: Params): Promise<Result[]>
_create(data: Data | Data[], params?: Params): Promise<Result | Result[]>

/**
* Completely replace the resource identified by id, skipping any service-level hooks.
Expand All @@ -125,7 +131,7 @@ export interface InternalServiceMethods<T = any, D = T, P extends AdapterParams
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#update-id-data-params|Feathers API Documentation: .update(id, data, params)}
*/
$update(id: Id, data: D, params?: P): Promise<T>
_update(id: IdType, data: Data, params?: Params): Promise<Result>

/**
* Merge any resources matching the given ID with the given data, skipping any service-level hooks.
Expand All @@ -137,9 +143,9 @@ export interface InternalServiceMethods<T = any, D = T, P extends AdapterParams
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#patch-id-data-params|Feathers API Documentation: .patch(id, data, params)}
*/
$patch(id: null, data: Partial<D>, params?: P): Promise<T[]>
$patch(id: Id, data: Partial<D>, params?: P): Promise<T>
$patch(id: NullableId, data: Partial<D>, params?: P): Promise<T | T[]>
_patch(id: null, data: PatchData, params?: Params): Promise<Result[]>
_patch(id: IdType, data: PatchData, params?: Params): Promise<Result>
_patch(id: IdType | null, data: PatchData, params?: Params): Promise<Result | Result[]>

/**
* Remove resources matching the given ID from the this service, skipping any service-level hooks.
Expand All @@ -150,7 +156,7 @@ export interface InternalServiceMethods<T = any, D = T, P extends AdapterParams
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#remove-id-params|Feathers API Documentation: .remove(id, params)}
*/
$remove(id: null, params?: P): Promise<T[]>
$remove(id: Id, params?: P): Promise<T>
$remove(id: NullableId, params?: P): Promise<T | T[]>
_remove(id: null, params?: Params): Promise<Result[]>
_remove(id: IdType, params?: Params): Promise<Result>
_remove(id: IdType | null, params?: Params): Promise<Result | Result[]>
}
182 changes: 44 additions & 138 deletions packages/adapter-commons/src/service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { BadRequest, MethodNotAllowed } from '@feathersjs/errors'
import { Id, NullableId, Paginated, Query } from '@feathersjs/feathers'
import { Id, Paginated, Query } from '@feathersjs/feathers'
import {
AdapterParams,
AdapterServiceOptions,
Expand All @@ -19,15 +18,17 @@ const alwaysMulti: { [key: string]: boolean } = {
* `__find`, `__get`, `__update`, `__patch` and `__remove` methods.
*/
export abstract class AdapterBase<
T = any,
D = T,
P extends AdapterParams = AdapterParams,
O extends AdapterServiceOptions = AdapterServiceOptions
> implements InternalServiceMethods<T, D, P>
Result = any,
Data = Result,
PatchData = Partial<Data>,
ServiceParams extends AdapterParams = AdapterParams,
Options extends AdapterServiceOptions = AdapterServiceOptions,
IdType = Id
> implements InternalServiceMethods<Result, Data, PatchData, ServiceParams, IdType>
{
options: O
options: Options

constructor(options: O) {
constructor(options: Options) {
this.options = {
id: 'id',
events: [],
Expand All @@ -53,7 +54,7 @@ export abstract class AdapterBase<
* @param params The service call params.
* @returns Wether or not multiple updates are allowed.
*/
allowsMulti(method: string, params: P = {} as P) {
allowsMulti(method: string, params: ServiceParams = {} as ServiceParams) {
const always = alwaysMulti[method]

if (typeof always !== 'undefined') {
Expand All @@ -62,7 +63,7 @@ export abstract class AdapterBase<

const { multi } = this.getOptions(params)

if (multi === true || multi === false) {
if (multi === true || !multi) {
return multi
}

Expand All @@ -76,7 +77,7 @@ export abstract class AdapterBase<
* @param params The parameters for the service method call
* @returns The actual options for this call
*/
getOptions(params: P): O {
getOptions(params: ServiceParams): Options {
const paginate = params.paginate !== undefined ? params.paginate : this.options.paginate

return {
Expand All @@ -86,17 +87,6 @@ export abstract class AdapterBase<
}
}

/**
* Sanitize the incoming data, e.g. removing invalid keywords etc.
*
* @param data The data to sanitize
* @param _params Service call parameters
* @returns The sanitized data
*/
async sanitizeData<X = Partial<D>>(data: X, _params: P) {
return data
}

/**
* Returns a sanitized version of `params.query`, converting filter values
* (like $limit and $skip) into the expected type. Will throw an error if
Expand All @@ -106,7 +96,7 @@ export abstract class AdapterBase<
* @param params The service call parameter.
* @returns A new object containing the sanitized query.
*/
async sanitizeQuery(params: P = {} as P): Promise<Query> {
async sanitizeQuery(params: ServiceParams = {} as ServiceParams): Promise<Query> {
const options = this.getOptions(params)
const { query, filters } = filterQuery(params.query, options)

Expand All @@ -116,160 +106,76 @@ export abstract class AdapterBase<
}
}

abstract $find(_params?: P & { paginate?: PaginationOptions }): Promise<Paginated<T>>
abstract $find(_params?: P & { paginate: false }): Promise<T[]>
abstract $find(params?: P): Promise<T[] | Paginated<T>>

/**
* Retrieve all resources from this service, skipping any service-level hooks but sanitize the query
* with allowed filters and properties by calling `sanitizeQuery`.
* Retrieve all resources from this service.
* Does not sanitize the query and should only be used on the server.
*
* @param params - Service call parameters {@link Params}
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#find-params|Feathers API Documentation: .find(params)}
* @param _params - Service call parameters {@link ServiceParams}
*/
async _find(_params?: P & { paginate?: PaginationOptions }): Promise<Paginated<T>>
async _find(_params?: P & { paginate: false }): Promise<T[]>
async _find(params?: P): Promise<T[] | Paginated<T>>
async _find(params?: P): Promise<T[] | Paginated<T>> {
const query = await this.sanitizeQuery(params)

return this.$find({
...params,
query
})
}

abstract $get(id: Id, params?: P): Promise<T>
abstract _find(_params?: ServiceParams & { paginate?: PaginationOptions }): Promise<Paginated<Result>>
abstract _find(_params?: ServiceParams & { paginate: false }): Promise<Result[]>
abstract _find(params?: ServiceParams): Promise<Result[] | Paginated<Result>>

/**
* Retrieve a single resource matching the given ID, skipping any service-level hooks but sanitize the query
* with allowed filters and properties by calling `sanitizeQuery`.
* Retrieve a single resource matching the given ID, skipping any service-level hooks.
* Does not sanitize the query and should only be used on the server.
*
* @param id - ID of the resource to locate
* @param params - Service call parameters {@link Params}
* @param params - Service call parameters {@link ServiceParams}
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#get-id-params|Feathers API Documentation: .get(id, params)}
*/
async _get(id: Id, params?: P): Promise<T> {
const query = await this.sanitizeQuery(params)

return this.$get(id, {
...params,
query
})
}

abstract $create(data: Partial<D>, params?: P): Promise<T>
abstract $create(data: Partial<D>[], params?: P): Promise<T[]>
abstract $create(data: Partial<D> | Partial<D>[], params?: P): Promise<T | T[]>
abstract _get(id: IdType, params?: ServiceParams): Promise<Result>

/**
* Create a new resource for this service, skipping any service-level hooks, sanitize the data
* and check if multiple updates are allowed.
* Create a new resource for this service, skipping any service-level hooks.
* Does not check if multiple updates are allowed and should only be used on the server.
*
* @param data - Data to insert into this service.
* @param params - Service call parameters {@link Params}
* @param params - Service call parameters {@link ServiceParams}
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#create-data-params|Feathers API Documentation: .create(data, params)}
*/
async _create(data: Partial<D>, params?: P): Promise<T>
async _create(data: Partial<D>[], params?: P): Promise<T[]>
async _create(data: Partial<D> | Partial<D>[], params?: P): Promise<T | T[]>
async _create(data: Partial<D> | Partial<D>[], params?: P): Promise<T | T[]> {
if (Array.isArray(data) && !this.allowsMulti('create', params)) {
throw new MethodNotAllowed('Can not create multiple entries')
}

const payload = Array.isArray(data)
? await Promise.all(data.map((current) => this.sanitizeData(current, params)))
: await this.sanitizeData(data, params)

return this.$create(payload, params)
}

abstract $update(id: Id, data: D, params?: P): Promise<T>
abstract _create(data: Data, params?: ServiceParams): Promise<Result>
abstract _create(data: Data[], params?: ServiceParams): Promise<Result[]>
abstract _create(data: Data | Data[], params?: ServiceParams): Promise<Result | Result[]>

/**
* Replace any resources matching the given ID with the given data, skipping any service-level hooks.
* Completely replace the resource identified by id, skipping any service-level hooks.
* Does not sanitize the query and should only be used on the server.
*
* @param id - ID of the resource to be updated
* @param data - Data to be put in place of the current resource.
* @param params - Service call parameters {@link Params}
* @param params - Service call parameters {@link ServiceParams}
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#update-id-data-params|Feathers API Documentation: .update(id, data, params)}
*/
async _update(id: Id, data: D, params?: P): Promise<T> {
if (id === null || Array.isArray(data)) {
throw new BadRequest("You can not replace multiple instances. Did you mean 'patch'?")
}

const payload = await this.sanitizeData(data, params)
const query = await this.sanitizeQuery(params)

return this.$update(id, payload, {
...params,
query
})
}

abstract $patch(id: null, data: Partial<D>, params?: P): Promise<T[]>
abstract $patch(id: Id, data: Partial<D>, params?: P): Promise<T>
abstract $patch(id: NullableId, data: Partial<D>, params?: P): Promise<T | T[]>
abstract _update(id: IdType, data: Data, params?: ServiceParams): Promise<Result>

/**
* Merge any resources matching the given ID with the given data, skipping any service-level hooks.
* Sanitizes the query and data and checks it multiple updates are allowed.
* Does not sanitize the query and should only be used on the server.
*
* @param id - ID of the resource to be patched
* @param data - Data to merge with the current resource.
* @param params - Service call parameters {@link Params}
* @param params - Service call parameters {@link ServiceParams}
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#patch-id-data-params|Feathers API Documentation: .patch(id, data, params)}
*/
async _patch(id: null, data: Partial<D>, params?: P): Promise<T[]>
async _patch(id: Id, data: Partial<D>, params?: P): Promise<T>
async _patch(id: NullableId, data: Partial<D>, params?: P): Promise<T | T[]>
async _patch(id: NullableId, data: Partial<D>, params?: P): Promise<T | T[]> {
if (id === null && !this.allowsMulti('patch', params)) {
throw new MethodNotAllowed('Can not patch multiple entries')
}

const { $limit, ...query } = await this.sanitizeQuery(params)
const payload = await this.sanitizeData(data, params)

return this.$patch(id, payload, {
...params,
query
})
}

abstract $remove(id: null, params?: P): Promise<T[]>
abstract $remove(id: Id, params?: P): Promise<T>
abstract $remove(id: NullableId, params?: P): Promise<T | T[]>
abstract _patch(id: null, data: PatchData, params?: ServiceParams): Promise<Result[]>
abstract _patch(id: IdType, data: PatchData, params?: ServiceParams): Promise<Result>
abstract _patch(id: IdType | null, data: PatchData, params?: ServiceParams): Promise<Result | Result[]>

/**
* Remove resources matching the given ID from the this service, skipping any service-level hooks.
* Sanitized the query and verifies that multiple updates are allowed.
* Does not sanitize query and should only be used on the server.
*
* @param id - ID of the resource to be removed
* @param params - Service call parameters {@link Params}
* @param params - Service call parameters {@link ServiceParams}
* @see {@link HookLessServiceMethods}
* @see {@link https://docs.feathersjs.com/api/services.html#remove-id-params|Feathers API Documentation: .remove(id, params)}
*/
async _remove(id: null, params?: P): Promise<T[]>
async _remove(id: Id, params?: P): Promise<T>
async _remove(id: NullableId, params?: P): Promise<T | T[]>
async _remove(id: NullableId, params?: P): Promise<T | T[]> {
if (id === null && !this.allowsMulti('remove', params)) {
throw new MethodNotAllowed('Can not remove multiple entries')
}

const { $limit, ...query } = await this.sanitizeQuery(params)

return this.$remove(id, {
...params,
query
})
}
abstract _remove(id: null, params?: ServiceParams): Promise<Result[]>
abstract _remove(id: IdType, params?: ServiceParams): Promise<Result>
abstract _remove(id: IdType | null, params?: ServiceParams): Promise<Result | Result[]>
}
Loading