@feathersjs/adapter-commons 5.0.0-pre.3 → 5.0.0-pre.30

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.
@@ -0,0 +1,156 @@
1
+ import { Query, Params, Paginated, Id, NullableId } from '@feathersjs/feathers'
2
+
3
+ export type FilterQueryOptions = {
4
+ filters?: FilterSettings
5
+ operators?: string[]
6
+ paginate?: PaginationParams
7
+ }
8
+
9
+ export type QueryFilter = (value: any, options: FilterQueryOptions) => any
10
+
11
+ export type FilterSettings = {
12
+ [key: string]: QueryFilter | true
13
+ }
14
+
15
+ export interface PaginationOptions {
16
+ default?: number
17
+ max?: number
18
+ }
19
+
20
+ export type PaginationParams = false | PaginationOptions
21
+
22
+ export interface AdapterServiceOptions {
23
+ /**
24
+ * Whether to allow multiple updates for everything (`true`) or specific methods (e.g. `['create', 'remove']`)
25
+ */
26
+ multi?: boolean | string[]
27
+ /**
28
+ * The name of the id property
29
+ */
30
+ id?: string
31
+ /**
32
+ * Pagination settings for this service
33
+ */
34
+ paginate?: PaginationParams
35
+ /**
36
+ * A list of additional property query operators to allow in a query
37
+ */
38
+ operators?: string[]
39
+ /**
40
+ * An object of additional top level query filters, e.g. `{ $populate: true }`
41
+ * Can also be a converter function like `{ $ignoreCase: (value) => value === 'true' ? true : false }`
42
+ */
43
+ filters?: FilterSettings
44
+ /**
45
+ * @deprecated Use service `events` option when registering the service with `app.use`.
46
+ */
47
+ events?: string[]
48
+ /**
49
+ * @deprecated renamed to `operators`.
50
+ */
51
+ whitelist?: string[]
52
+ }
53
+
54
+ export interface AdapterQuery extends Query {
55
+ $limit?: number
56
+ $skip?: number
57
+ $select?: string[]
58
+ $sort?: { [key: string]: 1 | -1 }
59
+ }
60
+ /**
61
+ * Additional `params` that can be passed to an adapter service method call.
62
+ */
63
+ export interface AdapterParams<
64
+ Q = AdapterQuery,
65
+ A extends Partial<AdapterServiceOptions> = Partial<AdapterServiceOptions>
66
+ > extends Params<Q> {
67
+ adapter?: A
68
+ paginate?: PaginationParams
69
+ }
70
+
71
+ /**
72
+ * Hook-less (internal) service methods. Directly call database adapter service methods
73
+ * without running any service-level hooks or sanitization. This can be useful if you need the raw data
74
+ * from the service and don't want to trigger any of its hooks.
75
+ *
76
+ * Important: These methods are only available internally on the server, not on the client
77
+ * side and only for the Feathers database adapters.
78
+ *
79
+ * These methods do not trigger events.
80
+ *
81
+ * @see {@link https://docs.feathersjs.com/guides/migrating.html#hook-less-service-methods}
82
+ */
83
+ export interface InternalServiceMethods<T = any, D = Partial<T>, P extends AdapterParams = AdapterParams> {
84
+ /**
85
+ * Retrieve all resources from this service.
86
+ * Does not sanitize the query and should only be used on the server.
87
+ *
88
+ * @param _params - Service call parameters {@link Params}
89
+ */
90
+ $find(_params?: P & { paginate?: PaginationOptions }): Promise<Paginated<T>>
91
+ $find(_params?: P & { paginate: false }): Promise<T[]>
92
+ $find(params?: P): Promise<T[] | Paginated<T>>
93
+
94
+ /**
95
+ * Retrieve a single resource matching the given ID, skipping any service-level hooks.
96
+ * Does not sanitize the query and should only be used on the server.
97
+ *
98
+ * @param id - ID of the resource to locate
99
+ * @param params - Service call parameters {@link Params}
100
+ * @see {@link HookLessServiceMethods}
101
+ * @see {@link https://docs.feathersjs.com/api/services.html#get-id-params|Feathers API Documentation: .get(id, params)}
102
+ */
103
+ $get(id: Id, params?: P): Promise<T>
104
+
105
+ /**
106
+ * Create a new resource for this service, skipping any service-level hooks.
107
+ * Does not sanitize data or checks if multiple updates are allowed and should only be used on the server.
108
+ *
109
+ * @param data - Data to insert into this service.
110
+ * @param params - Service call parameters {@link Params}
111
+ * @see {@link HookLessServiceMethods}
112
+ * @see {@link https://docs.feathersjs.com/api/services.html#create-data-params|Feathers API Documentation: .create(data, params)}
113
+ */
114
+ $create(data: Partial<D>, params?: P): Promise<T>
115
+ $create(data: Partial<D>[], params?: P): Promise<T[]>
116
+ $create(data: Partial<D> | Partial<D>[], params?: P): Promise<T | T[]>
117
+
118
+ /**
119
+ * Completely replace the resource identified by id, skipping any service-level hooks.
120
+ * Does not sanitize data or query and should only be used on the server.
121
+ *
122
+ * @param id - ID of the resource to be updated
123
+ * @param data - Data to be put in place of the current resource.
124
+ * @param params - Service call parameters {@link Params}
125
+ * @see {@link HookLessServiceMethods}
126
+ * @see {@link https://docs.feathersjs.com/api/services.html#update-id-data-params|Feathers API Documentation: .update(id, data, params)}
127
+ */
128
+ $update(id: Id, data: D, params?: P): Promise<T>
129
+
130
+ /**
131
+ * Merge any resources matching the given ID with the given data, skipping any service-level hooks.
132
+ * Does not sanitize the data or query and should only be used on the server.
133
+ *
134
+ * @param id - ID of the resource to be patched
135
+ * @param data - Data to merge with the current resource.
136
+ * @param params - Service call parameters {@link Params}
137
+ * @see {@link HookLessServiceMethods}
138
+ * @see {@link https://docs.feathersjs.com/api/services.html#patch-id-data-params|Feathers API Documentation: .patch(id, data, params)}
139
+ */
140
+ $patch(id: null, data: Partial<D>, params?: P): Promise<T[]>
141
+ $patch(id: Id, data: Partial<D>, params?: P): Promise<T>
142
+ $patch(id: NullableId, data: Partial<D>, params?: P): Promise<T | T[]>
143
+
144
+ /**
145
+ * Remove resources matching the given ID from the this service, skipping any service-level hooks.
146
+ * Does not sanitize query and should only be used on the server.
147
+ *
148
+ * @param id - ID of the resource to be removed
149
+ * @param params - Service call parameters {@link Params}
150
+ * @see {@link HookLessServiceMethods}
151
+ * @see {@link https://docs.feathersjs.com/api/services.html#remove-id-params|Feathers API Documentation: .remove(id, params)}
152
+ */
153
+ $remove(id: null, params?: P): Promise<T[]>
154
+ $remove(id: Id, params?: P): Promise<T>
155
+ $remove(id: NullableId, params?: P): Promise<T | T[]>
156
+ }
package/src/index.ts CHANGED
@@ -1,32 +1,29 @@
1
- import { _ } from '@feathersjs/commons';
1
+ import { _ } from '@feathersjs/commons'
2
+ import { Params } from '@feathersjs/feathers'
2
3
 
3
- export { AdapterService, InternalServiceMethods, ServiceOptions, Paginated } from './service';
4
- export { filterQuery, FILTERS, OPERATORS } from './filter-query';
5
- export * from './sort';
4
+ export * from './declarations'
5
+ export * from './service'
6
+ export { filterQuery, FILTERS, OPERATORS } from './query'
7
+ export * from './sort'
6
8
 
7
9
  // Return a function that filters a result object or array
8
10
  // and picks only the fields passed as `params.query.$select`
9
11
  // and additional `otherFields`
10
- export function select (params: any, ...otherFields: any[]) {
11
- const fields = params && params.query && params.query.$select;
12
+ export function select(params: Params, ...otherFields: string[]) {
13
+ const queryFields: string[] | undefined = params?.query?.$select
12
14
 
13
- if (Array.isArray(fields) && otherFields.length) {
14
- fields.push(...otherFields);
15
+ if (!queryFields) {
16
+ return (result: any) => result
15
17
  }
16
18
 
17
- const convert = (result: any) => {
18
- if (!Array.isArray(fields)) {
19
- return result;
20
- }
21
-
22
- return _.pick(result, ...fields);
23
- };
19
+ const resultFields = queryFields.concat(otherFields)
20
+ const convert = (result: any) => _.pick(result, ...resultFields)
24
21
 
25
22
  return (result: any) => {
26
23
  if (Array.isArray(result)) {
27
- return result.map(convert);
24
+ return result.map(convert)
28
25
  }
29
26
 
30
- return convert(result);
31
- };
27
+ return convert(result)
28
+ }
32
29
  }
package/src/query.ts ADDED
@@ -0,0 +1,137 @@
1
+ import { _ } from '@feathersjs/commons'
2
+ import { BadRequest } from '@feathersjs/errors'
3
+ import { Query } from '@feathersjs/feathers'
4
+ import { FilterQueryOptions, FilterSettings } from './declarations'
5
+
6
+ const parse = (value: any) => (typeof value !== 'undefined' ? parseInt(value, 10) : value)
7
+
8
+ const isPlainObject = (value: any) => _.isObject(value) && value.constructor === {}.constructor
9
+
10
+ const validateQueryProperty = (query: any, operators: string[] = []): Query => {
11
+ if (!isPlainObject(query)) {
12
+ return query
13
+ }
14
+
15
+ for (const key of Object.keys(query)) {
16
+ if (key.startsWith('$') && !operators.includes(key)) {
17
+ throw new BadRequest(`Invalid query parameter ${key}`, query)
18
+ }
19
+
20
+ const value = query[key]
21
+
22
+ if (isPlainObject(value)) {
23
+ query[key] = validateQueryProperty(value, operators)
24
+ }
25
+ }
26
+
27
+ return {
28
+ ...query
29
+ }
30
+ }
31
+
32
+ const getFilters = (query: Query, settings: FilterQueryOptions) => {
33
+ const filterNames = Object.keys(settings.filters)
34
+
35
+ return filterNames.reduce((current, key) => {
36
+ const queryValue = query[key]
37
+ const filter = settings.filters[key]
38
+
39
+ if (filter) {
40
+ const value = typeof filter === 'function' ? filter(queryValue, settings) : queryValue
41
+
42
+ if (value !== undefined) {
43
+ current[key] = value
44
+ }
45
+ }
46
+
47
+ return current
48
+ }, {} as { [key: string]: any })
49
+ }
50
+
51
+ const getQuery = (query: Query, settings: FilterQueryOptions) => {
52
+ const keys = Object.keys(query).concat(Object.getOwnPropertySymbols(query) as any as string[])
53
+
54
+ return keys.reduce((result, key) => {
55
+ if (typeof key === 'string' && key.startsWith('$')) {
56
+ if (settings.filters[key] === undefined) {
57
+ throw new BadRequest(`Invalid filter value ${key}`)
58
+ }
59
+ } else {
60
+ result[key] = validateQueryProperty(query[key], settings.operators)
61
+ }
62
+
63
+ return result
64
+ }, {} as Query)
65
+ }
66
+
67
+ export const OPERATORS = ['$in', '$nin', '$lt', '$lte', '$gt', '$gte', '$ne', '$or']
68
+
69
+ export const FILTERS: FilterSettings = {
70
+ $skip: (value: any) => parse(value),
71
+ $sort: (sort: any): { [key: string]: number } => {
72
+ if (typeof sort !== 'object' || Array.isArray(sort)) {
73
+ return sort
74
+ }
75
+
76
+ return Object.keys(sort).reduce((result, key) => {
77
+ result[key] = typeof sort[key] === 'object' ? sort[key] : parse(sort[key])
78
+
79
+ return result
80
+ }, {} as { [key: string]: number })
81
+ },
82
+ $limit: (_limit: any, { paginate }: FilterQueryOptions) => {
83
+ const limit = parse(_limit)
84
+
85
+ if (paginate && (paginate.default || paginate.max)) {
86
+ const base = paginate.default || 0
87
+ const lower = typeof limit === 'number' && !isNaN(limit) && limit >= 0 ? limit : base
88
+ const upper = typeof paginate.max === 'number' ? paginate.max : Number.MAX_VALUE
89
+
90
+ return Math.min(lower, upper)
91
+ }
92
+
93
+ return limit
94
+ },
95
+ $select: (select: any) => {
96
+ if (Array.isArray(select)) {
97
+ return select.map((current) => `${current}`)
98
+ }
99
+
100
+ return select
101
+ },
102
+ $or: (or: any, { operators }: FilterQueryOptions) => {
103
+ if (Array.isArray(or)) {
104
+ return or.map((current) => validateQueryProperty(current, operators))
105
+ }
106
+
107
+ return or
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Converts Feathers special query parameters and pagination settings
113
+ * and returns them separately as `filters` and the rest of the query
114
+ * as `query`. `options` also gets passed the pagination settings and
115
+ * a list of additional `operators` to allow when querying properties.
116
+ *
117
+ * @param query The initial query
118
+ * @param options Options for filtering the query
119
+ * @returns An object with `query` which contains the query without `filters`
120
+ * and `filters` which contains the converted values for each filter.
121
+ */
122
+ export function filterQuery(_query: Query, options: FilterQueryOptions = {}) {
123
+ const query = _query || {}
124
+ const settings = {
125
+ ...options,
126
+ filters: {
127
+ ...FILTERS,
128
+ ...options.filters
129
+ },
130
+ operators: OPERATORS.concat(options.operators || [])
131
+ }
132
+
133
+ return {
134
+ filters: getFilters(query, settings),
135
+ query: getQuery(query, settings)
136
+ }
137
+ }