-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #75 from feathersjs/filters
Created support for event filter and permission hooks. Reorganized service hooks because of this.
- Loading branch information
Showing
68 changed files
with
8,506 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
|
||
const traverser = require('traverse'); | ||
import { getByDot, setByDot } from '../hooks/utils'; | ||
|
||
// transformer(item /* modified */, fieldName, value) | ||
export const _transformItems = (items /* modified */, fieldNames, transformer) => { | ||
(Array.isArray(items) ? items : [items]).forEach(item => { | ||
fieldNames.forEach(fieldName => { | ||
transformer(item, fieldName, getByDot(item, fieldName)); | ||
}); | ||
}); | ||
}; | ||
|
||
export const _remove = (items /* modified */, fieldNames) => { | ||
_transformItems(items, fieldNames, (item, fieldName, value) => { | ||
if (value !== undefined) { | ||
setByDot(item, fieldName, undefined, true); | ||
} | ||
}); | ||
}; | ||
|
||
export const _pluck = (items, fieldNames) => { | ||
if (!Array.isArray(items)) { | ||
return _pluckItem(items, fieldNames); | ||
} | ||
|
||
const pluckedItems = []; | ||
|
||
(Array.isArray(items) ? items : [items]).forEach(item => { | ||
pluckedItems.push(_pluckItem(item, fieldNames)); | ||
}); | ||
|
||
return pluckedItems; | ||
}; | ||
|
||
function _pluckItem (item, fieldNames) { | ||
const plucked = {}; | ||
|
||
fieldNames.forEach(fieldName => { | ||
const value = getByDot(item, fieldName); | ||
if (value !== undefined) { // prevent setByDot creating nested empty objects | ||
setByDot(plucked, fieldName, value); | ||
} | ||
}); | ||
|
||
return plucked; | ||
} | ||
|
||
export const _traverse = (items, converter) => { | ||
(Array.isArray(items) ? items : [items]).forEach(item => { | ||
traverser(item).forEach(converter); // replacement is in place | ||
}); | ||
}; | ||
|
||
export const _setFields = (items /* modified */, fieldValue, fieldNames, defaultFieldName) => { | ||
const value = typeof fieldValue === 'function' ? fieldValue() : fieldValue; | ||
|
||
if (!fieldNames.length) fieldNames = [defaultFieldName]; | ||
|
||
(Array.isArray(items) ? items : [items]).forEach(item => { | ||
fieldNames.forEach(fieldName => { | ||
setByDot(item, fieldName, value); | ||
}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
|
||
const errors = require('feathers-errors').errors; | ||
|
||
// processFuncArray must handle case of null param. | ||
module.exports = function Conditionals (processFuncArray) { | ||
if (!(this instanceof Conditionals)) { | ||
return new Conditionals(processFuncArray); | ||
} | ||
|
||
// fnArgs is [hook] for service & permission hooks, [data, connection, hook] for event filters | ||
const iffElse = (predicate, trueFuncs, falseFuncs) => function (...fnArgs) { | ||
if (typeof trueFuncs === 'function') { trueFuncs = [trueFuncs]; } | ||
if (typeof falseFuncs === 'function') { falseFuncs = [falseFuncs]; } | ||
|
||
// Babel 6.17.0 did not transpile something in the old version similar to this | ||
// const runProcessFuncArray = funcs => processFuncArray.call(this, fnArgs, funcs); | ||
var that = this; | ||
var runProcessFuncArray = function (funcs) { | ||
return processFuncArray.call(that, fnArgs, funcs); | ||
}; | ||
|
||
const check = typeof predicate === 'function' ? predicate(...fnArgs) : !!predicate; | ||
|
||
if (!check) { | ||
return runProcessFuncArray(falseFuncs); | ||
} | ||
|
||
if (typeof check.then !== 'function') { | ||
return runProcessFuncArray(trueFuncs); | ||
} | ||
|
||
return check.then(check1 => runProcessFuncArray(check1 ? trueFuncs : falseFuncs)); | ||
}; | ||
|
||
const iff = (predicate, ...rest) => { | ||
const trueHooks = [].concat(rest); | ||
|
||
const iffWithoutElse = function (hook) { | ||
return iffElse(predicate, trueHooks, null).call(this, hook); | ||
}; | ||
iffWithoutElse.else = (...falseHooks) => iffElse(predicate, trueHooks, falseHooks); | ||
|
||
return iffWithoutElse; | ||
}; | ||
|
||
const unless = (unlessFcn, ...rest) => { | ||
if (typeof unlessFcn === 'function') { | ||
return iff(isNot(unlessFcn), ...rest); | ||
} | ||
|
||
return iff(!unlessFcn, ...rest); | ||
}; | ||
|
||
const some = (...rest) => function (...fnArgs) { | ||
const promises = rest.map(fn => fn.apply(this, fnArgs)); | ||
|
||
return Promise.all(promises).then(results => { | ||
return Promise.resolve(results.some(result => !!result)); | ||
}); | ||
}; | ||
|
||
const every = (...rest) => function (...fnArgs) { | ||
const promises = rest.map(fn => fn.apply(this, fnArgs)); | ||
|
||
return Promise.all(promises).then(results => { | ||
return Promise.resolve(results.every(result => !!result)); | ||
}); | ||
}; | ||
|
||
const isNot = (predicate) => { | ||
if (typeof predicate !== 'function') { | ||
throw new errors.MethodNotAllowed('Expected function as param. (isNot)'); | ||
} | ||
|
||
return hook => { | ||
const result = predicate(hook); // Should we pass a clone? (safety vs performance) | ||
|
||
if (!result || typeof result.then !== 'function') { | ||
return !result; | ||
} | ||
|
||
return result.then(result1 => !result1); | ||
}; | ||
}; | ||
|
||
return { | ||
iffElse, | ||
iff, | ||
when: iff, | ||
unless, | ||
some, | ||
every, | ||
isNot | ||
}; | ||
}; | ||
|
||
/** | ||
* Hook to conditionally execute one or another set of hooks. | ||
* | ||
* @param {Function|Promise|boolean} ifFcn - Predicate function(hook). | ||
* @param {Array.function|Function} trueHooks - Hook functions to execute when ifFcn is truthy. | ||
* @param {Array.function|Function} falseHooks - Hook functions to execute when ifFcn is falsey. | ||
* @returns {Object} resulting hook | ||
* | ||
* The predicate is called with hook as a param. | ||
* const isServer = hook => !hook.params.provider; | ||
* iff(isServer, hook.remove( ... )); | ||
* You can use a high order predicate to access other values. | ||
* const isProvider = provider => hook => hook.params.provider === provider; | ||
* iff(isProvider('socketio'), hook.remove( ... )); | ||
* | ||
* The hook functions may be sync, return a Promise, or use a callback. | ||
* feathers-hooks will catch any errors from the predicate or hook Promises. | ||
* | ||
* Examples | ||
* iffElse(isServer, [hookA, hookB], hookC) | ||
* | ||
* iffElse(isServer, | ||
* [ hookA, iffElse(hook => hook.method === 'create', hook1, [hook2, hook3]), hookB ], | ||
* iffElse(isProvider('rest'), [hook4, hook5], hook6]) | ||
* ) | ||
*/ | ||
|
||
/** | ||
* Hook to conditionally execute one or another set of hooks using function chaining. | ||
* | ||
* @param {Function|Promise|boolean} ifFcn - Predicate function(hook). | ||
* @param {Array.function} rest - Hook functions to execute when ifFcn is truthy. | ||
* @returns {Function} iffWithoutElse | ||
* | ||
* Examples: | ||
* iff(isServer, hookA, hookB) | ||
* .else(hookC) | ||
* | ||
* iff(isServer, | ||
* hookA, | ||
* iff(isProvider('rest'), hook1, hook2, hook3) | ||
* .else(hook4, hook5), | ||
* hookB | ||
* ) | ||
* .else( | ||
* iff(hook => hook.method === 'create', hook6, hook7) | ||
* ) | ||
*/ | ||
|
||
/** | ||
* Alias for iff | ||
*/ | ||
|
||
/** | ||
* Hook that executes a set of hooks and returns true if at least one of | ||
* the hooks returns a truthy value and false if none of them do. | ||
* | ||
* @param {Array.function} rest - Hook functions to execute. | ||
* @returns {Boolean} | ||
* | ||
* Example 1 | ||
* service.before({ | ||
* create: hooks.iff(hooks.some(hook1, hook2, ...), hookA, hookB, ...) | ||
* }); | ||
* | ||
* Example 2 - called within a custom hook function | ||
* function (hook) { | ||
* ... | ||
* hooks.some(hook1, hook2, ...).call(this, currentHook) | ||
* .then(bool => { ... }); | ||
* } | ||
*/ | ||
|
||
/** | ||
* Hook that executes a set of hooks and returns true if all of | ||
* the hooks returns a truthy value and false if one of them does not. | ||
* | ||
* @param {Array.function} rest - Hook functions to execute. | ||
* @returns {Boolean} | ||
* | ||
* Example 1 | ||
* service.before({ | ||
* create: hooks.iff(hooks.every(hook1, hook2, ...), hookA, hookB, ...) | ||
* }); | ||
* | ||
* Example 2 - called within a custom hook function | ||
* function (hook) { | ||
* ... | ||
* hooks.every(hook1, hook2, ...).call(this, currentHook) | ||
* .then(bool => { ... }) | ||
* } | ||
*/ | ||
|
||
/** | ||
* Hook to conditionally execute one or another set of hooks using function chaining. | ||
* if the predicate hook function returns a falsey value. | ||
* Equivalent to iff(isNot(isProvider), hook1, hook2, hook3). | ||
* | ||
* @param {Function|Promise|boolean} unlessFcn - Predicate function(hook). | ||
* @param {Array.function} rest - Hook functions to execute when unlessFcn is falsey. | ||
* @returns {Function} iffWithoutElse | ||
* | ||
* Examples: | ||
* unless(isServer, hookA, hookB) | ||
* | ||
* unless(isServer, | ||
* hookA, | ||
* unless(isProvider('rest'), hook1, hook2, hook3), | ||
* hookB | ||
* ) | ||
*/ | ||
|
||
/** | ||
* Negate a predicate. | ||
* | ||
* @param {Function} predicate - returns a boolean or a promise resolving to a boolean. | ||
* @returns {boolean} the not of the predicate result. | ||
* | ||
* const hooks, { iff, isNot, isProvider } from 'feathers-hooks-common'; | ||
* iff(isNot(isProvider('rest')), hooks.remove( ... )); | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
|
||
import { _remove, _pluck, _traverse, _setFields } from '../common/alter-data'; | ||
|
||
export const remove = (...fields) => data => { | ||
_remove(data, fields); | ||
return data; | ||
}; | ||
|
||
export const pluck = (...fields) => data => _pluck(data, fields); | ||
|
||
export const traverse = (converter, getObj) => (data, connection, hook) => { | ||
const items = typeof getObj === 'function' ? getObj(data, connection, hook) : getObj || data; | ||
|
||
_traverse(items, converter); | ||
}; | ||
|
||
export const setFilteredAt = (...fieldNames) => data => { | ||
_setFields(data, () => new Date(), fieldNames, 'filteredAt'); | ||
return data; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
|
||
import { promisify } from 'feathers-socket-commons/lib/utils'; | ||
import Conditionals from '../common/conditionals'; | ||
import makeDebug from 'debug'; | ||
|
||
const debug = makeDebug('filters/conditionals'); | ||
const ev = 'conditionals'; // todo work needed here before merge with feathers-sockets-common | ||
|
||
// https://github.com/feathersjs/feathers-socket-commons/blob/master/src/events.js | ||
// lines 44-69 | ||
const dispatchFilters = (promisify, promise, eventFilters, service, data, connection, hook) => { | ||
if (eventFilters.length) { | ||
eventFilters.forEach(filterFn => { | ||
if (filterFn.length === 4) { // function(data, connection, hook, callback) | ||
promise = promise.then(data => { | ||
if (data) { | ||
return promisify(filterFn, service, data, connection, hook); | ||
} | ||
|
||
return data; | ||
}); | ||
} else { // function(data, connection, hook) | ||
promise = promise.then(data => { | ||
if (data) { | ||
return filterFn.call(service, data, connection, hook); | ||
} | ||
|
||
return data; | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
promise.catch(e => debug(`Error in filter chain for '${ev}' event`, e)); | ||
|
||
return promise; | ||
}; | ||
|
||
const combine = (...eventFilters) => function ([data, connection, hook]) { | ||
let promise = Promise.resolve(data); | ||
|
||
return dispatchFilters(promisify, promise, eventFilters, this, data, connection, hook); | ||
}; | ||
|
||
const conditionals = new Conditionals( | ||
function (filterFnArgs, eventFilters) { | ||
return eventFilters ? combine(...eventFilters).call(this, filterFnArgs) : filterFnArgs[0]; | ||
}); | ||
|
||
export default Object.assign( | ||
{ combine }, | ||
conditionals, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
|
||
module.exports = Object.assign({}, | ||
require('./conditionals'), | ||
require('./alter-data'), | ||
); |
Oops, something went wrong.