8000 Exports withHooks by bertho-zero · Pull Request #875 · feathersjs/feathers · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ node_modules

# Users Environment Variables
.lock-wscript

# IDEs
.idea
199 changes: 115 additions & 84 deletions lib/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,102 @@ const {
makeArguments
} = hooks;

function getHookArray (hooks, type) {
return hooks && hooks[type] && Array.isArray(hooks[type])
? hooks[type]
: hooks && hooks[type]
? [hooks[type]]
: [];
}

const withHooks = function withHooks ({
app,
service,
method
}) {
return (hooks = {}) => (...args) => {
const returnHook = args[args.length - 1] === true
? args.pop() : false;

// A reference to the original method
const _super = service._super ? service._super.bind(service) : service[method].bind(service);
// Create the hook object that gets passed through
const hookObject = createHookObject(method, args, {
type: 'before', // initial hook object type
service,
app
});

// Process all before hooks
return processHooks.call(service, getHookArray(hooks, 'before'), hookObject)
// Use the hook object to call the original method
.then(hookObject => {
// If `hookObject.result` is set, skip the original method
if (typeof hookObject.result !== 'undefined') {
return hookObject;
}

// Otherwise, call it with arguments created from the hook object
const promise = _super(...makeArguments(hookObject));

if (!isPromise(promise)) {
throw new Error(`Service method '${hookObject.method}' for '${hookObject.path}' service must return a promise`);
}

return promise.then(result => {
hookObject.result = result;

return hookObject;
});
})
// Make a (shallow) copy of hookObject from `before` hooks and update type
.then(hookObject => Object.assign({}, hookObject, { type: 'after' }))
// Run through all `after` hooks
.then(hookObject => {
// Combine all app and service `after` and `finally` hooks and process
const hookChain = getHookArray(hooks, 'after')
.concat(getHookArray(hooks, 'finally'));

return processHooks.call(service, hookChain, hookObject);
})
.then(hookObject =>
// Finally, return the result
// Or the hook object if the `returnHook` flag is set
returnHook ? hookObject : hookObject.result
)
// Handle errors
.catch(error => {
// Combine all app and service `error` and `finally` hooks and process
const hookChain = getHookArray(hooks, 'error')
.concat(getHookArray(hooks, 'finally'));

// A shallow copy of the hook object
const errorHookObject = _.omit(Object.assign({}, error.hook, hookObject, {
type: 'error',
original: error.hook,
error
}), 'result');

return processHooks.call(service, hookChain, errorHookObject)
.catch(error => {
errorHookObject.error = error;

return errorHookObject;
})
.then(hook => {
if (returnHook) {
// Either resolve or reject with the hook object
return typeof hook.result !== 'undefined' ? hook : Promise.reject(hook);
}

// Otherwise return either the result if set (to swallow errors)
// Or reject with the hook error
return typeof hook.result !== 'undefined' ? hook.result : Promise.reject(hook.error);
});
});
};
};

// A service mixin that adds `service.hooks()` method and functionality
const hookMixin = exports.hookMixin = function hookMixin (service) {
if (typeof service.hooks === 'function') {
Expand All @@ -30,97 +126,30 @@ const hookMixin = exports.hookMixin = function hookMixin (service) {
mixin[method] = function () {
const service = this;
const args = Array.from(arguments);
// If the last argument is `true` we want to return
// the actual hook object instead of the result
const returnHook = args[args.length - 1] === true
? args.pop() : false;

// A reference to the original method
const _super = service._super.bind(service);
// Create the hook object that gets passed through
const hookObject = createHookObject(method, args, {
type: 'before', // initial hook object type
service,
app
});

// A hook that validates the arguments and will always be the very first
const validateHook = context => {
validateArguments(method, args);
validateArguments(method, args[args.length - 1] === true ? args.slice(0, -1) : args);

return context;
};
// The `before` hook chain (including the validation hook)
const beforeHooks = [ validateHook, ...getHooks(app, service, 'before', method) ];

// Process all before hooks
return processHooks.call(service, beforeHooks, hookObject)
// Use the hook object to call the original method
.then(hookObject => {
// If `hookObject.result` is set, skip the original method
if (typeof hookObject.result !== 'undefined') {
return hookObject;
}

// Otherwise, call it with arguments created from the hook object
const promise = _super(...makeArguments(hookObject));

if (!isPromise(promise)) {
throw new Error(`Service method '${hookObject.method}' for '${hookObject.path}' service must return a promise`);
}
// Needed
service._super = service._super.bind(service);

return promise.then(result => {
hookObject.result = result;

return hookObject;
});
})
// Make a (shallow) copy of hookObject from `before` hooks and update type
.then(hookObject => Object.assign({}, hookObject, { type: 'after' }))
// Run through all `after` hooks
.then(hookObject => {
// Combine all app and service `after` and `finally` hooks and process
const afterHooks = getHooks(app, service, 'after', method, true);
const finallyHooks = getHooks(app, service, 'finally', method, true);
const hookChain = afterHooks.concat(finallyHooks);

return processHooks.call(service, hookChain, hookObject);
})
.then(hookObject =>
// Finally, return the result
// Or the hook object if the `returnHook` flag is set
returnHook ? hookObject : hookObject.result
)
// Handle errors
.catch(error => {
// Combine all app and service `error` and `finally` hooks and process
const errorHooks = getHooks(app, service, 'error', method, true);
const finallyHooks = getHooks(app, service, 'finally', method, true);
const hookChain = errorHooks.concat(finallyHooks);

// A shallow copy of the hook object
const errorHookObject = _.omit(Object.assign({}, error.hook, hookObject, {
type: 'error',
original: error.hook,
error
}), 'result');

return processHooks.call(service, hookChain, errorHookObject)
.catch(error => {
errorHookObject.error = error;

return errorHookObject;
})
.then(hook => {
if (returnHook) {
// Either resolve or reject with the hook object
return typeof hook.result !== 'undefined' ? hook : Promise.reject(hook);
}

// Otherwise return either the result if set (to swallow errors)
// Or reject with the hook error
return typeof hook.result !== 'undefined' ? hook.result : Promise.reject(hook.error);
});
});
return withHooks({
app,
service,
method
})({
before: [
validateHook,
...getHooks(app, service, 'before', method)
],
after: getHooks(app, service, 'after', method, true),
error: getHooks(app, service, 'error', method, true),
finally: getHooks(app, service, 'finally', method, true)
})(...args);
};
});

Expand All @@ -141,3 +170,5 @@ module.exports = function () {
app.mixins.push(hookMixin);
};
};

module.exports.withHooks = withHooks;
Loading
0