Comparing version
{ | ||
"name": "apicase", | ||
"version": "0.3.1", | ||
"version": "0.3.2", | ||
"description": "Create, group and manage your APIs with json declaration", | ||
@@ -5,0 +5,0 @@ "author": "Anton Kosykh", |
@@ -26,15 +26,30 @@ import * as Utils from './utils' | ||
}, | ||
// Normalize and add mixin | ||
addMixin (name, mixin) { | ||
this.mixins[name] = Utils.normalizeMixin(mixin) | ||
}, | ||
// Normalize and add hook | ||
addHook (name, hook) { | ||
this.hooks[name].push(Utils.normalizeHook(hook)) | ||
}, | ||
// Add hooks list | ||
addHooks (list) { | ||
merge(this.hooks, Utils.normalizeHooks(list)) | ||
}, | ||
// Add service in container | ||
// unfolds children services (if exists) | ||
addService (name, service) { | ||
this.services[name] = createService(service, result) | ||
set(this.services, name, createService(service, result)) | ||
if (!service.children) { | ||
this.services[name] = service | ||
return | ||
} | ||
Object.assign( | ||
this.services, | ||
mapValues( | ||
Utils.flatServices({ name: service }), | ||
service => createService(service, this) | ||
) | ||
) | ||
}, | ||
// Call service | ||
async go (name, data, options = {}) { | ||
@@ -45,2 +60,10 @@ return await this.services[name].go(data, options) | ||
if (config.base) { | ||
result.base = config.base | ||
} | ||
if (config.headers) { | ||
result.headers = config.headers | ||
} | ||
if (config.services) { | ||
@@ -52,5 +75,7 @@ result.services = mapValues( | ||
} | ||
if (config.hooks) { | ||
result.addHooks(config.hooks) | ||
} | ||
if (config.mixins) { | ||
@@ -61,11 +86,18 @@ forEach(config.mixins, (mixin, name) => { | ||
} | ||
if (config.headers) { | ||
result.headers = config.headers | ||
} | ||
if (config.base) { | ||
result.base = config.base | ||
} | ||
// Add nested variant for services | ||
// Note that you should not use reserved words for service names | ||
// It can cause unexpected behavior | ||
// TODO: show warning on using bad names for services | ||
Object.defineProperty(result, 'tree', { | ||
get () { | ||
return zipObjectDeep( | ||
Object.keys(this.services).map(item => item.split('/').join('.')), | ||
Object.values(this.services) | ||
) | ||
} | ||
}) | ||
return result | ||
} |
import Service from './service.js' | ||
import Container from './container.js' | ||
import { mergeHooksList } from './utils.js' | ||
import { ApiAbort, ApiError } from './errors' | ||
export default { | ||
module.exports = { | ||
ApiAbort, | ||
ApiError, | ||
Service, | ||
@@ -7,0 +10,0 @@ Container, |
import * as Utils from './utils' | ||
import omit from 'lodash-es/omit' | ||
import mapValues from 'lodash-es/mapValues' | ||
export default function createResult (service) { | ||
return { | ||
let result = { | ||
ok: false, | ||
query: null, | ||
headers: null, | ||
body: null, | ||
pending: false, | ||
aborted: false, | ||
ok: false, | ||
reason: null, | ||
response: null, | ||
result: null, | ||
// Set query | ||
setQuery (query) { | ||
this.query = query | ||
}, | ||
// Set headers | ||
setHeaders (headers) { | ||
this.headers = headers | ||
}, | ||
// Set response and result | ||
setResult (response, res = undefined) { | ||
@@ -27,6 +31,14 @@ this.response = response | ||
}, | ||
// Set aborted flag | ||
// set abort reason or null | ||
setAborted (reason = null) { | ||
this.aborted = true | ||
this.reason = reason | ||
}, | ||
// Normalize new mixins | ||
// merge it with already added | ||
setMixins (list) { | ||
Object.defineProperties(this, mapValues( | ||
Object.assign( | ||
{}. | ||
{}, | ||
service.container.mixins, | ||
@@ -40,2 +52,18 @@ service.config.mixins, | ||
} | ||
// Returns clean data without methods | ||
Object.defineProperty(result, 'clean', { | ||
get () { | ||
return omit(this, [ | ||
'clean', | ||
'setQuery', | ||
'setResult', | ||
'setMixins', | ||
'setAborted', | ||
'setHeaders' | ||
]) | ||
} | ||
}) | ||
return result | ||
} |
import * as Utils from './utils' | ||
import { ApiAbort } from './errors' | ||
import createResult from './result' | ||
@@ -14,2 +15,3 @@ | ||
// Merge hooks | ||
let prepareHooks = function (hooks = {}) { | ||
@@ -23,2 +25,3 @@ return Utils.mergeHooksList( | ||
// Merge mixins | ||
let prepareMixins = function (mixins = {}) { | ||
@@ -32,2 +35,4 @@ return Object.assign( | ||
// Add query string for GET requests | ||
// return path-to-regexp method | ||
let prepareUrl = function (data = {}) { | ||
@@ -42,2 +47,3 @@ let url = `${state.container.base}${state.config.url}` | ||
// Generate options object for fetch | ||
let prepareOptions = function (data, headers) { | ||
@@ -51,2 +57,3 @@ return { | ||
// Merge headers | ||
let prepareHeaders = function (headers = {}) { | ||
@@ -67,2 +74,3 @@ let normalizeHeaders = (headers) => | ||
// Returns corrected request body object | ||
let prepareBody = function (data = {}) { | ||
@@ -76,2 +84,3 @@ return state.config.method === 'GET' | ||
// Prepare query callback | ||
let prepareCallback = function (params = {}) { | ||
@@ -104,28 +113,28 @@ let self = state | ||
let prepareBeforeHook = function (hook) { | ||
// Wrap before hook in function | ||
// with abort features | ||
let prepareBeforeHook = function (hook, temp) { | ||
return async (ctx, next) => { | ||
let result | ||
let abortInfo = {} | ||
await hook.handler( | ||
ctx, | ||
(data, abortData = {}) => { | ||
result = data | ||
abortInfo = abortData | ||
(data = undefined) => { | ||
if (data === false) { | ||
temp.abort = true | ||
return | ||
} | ||
if (data instanceof ApiAbort) { | ||
temp.abort = data | ||
return | ||
} | ||
if (data !== undefined) { | ||
ctx.query = data | ||
} | ||
} | ||
) | ||
switch (result) { | ||
case undefined: | ||
next() | ||
break | ||
case false: | ||
ctx.aborted = true | ||
ctx.abortInfo = abortInfo | ||
break | ||
default: | ||
ctx.query = result | ||
next() | ||
} | ||
if (!temp.abort) next() | ||
} | ||
} | ||
// Wrap success and after hooks in function | ||
// that no need to call next() | ||
let prepareAfterHook = function (hook) { | ||
@@ -138,2 +147,11 @@ return async (ctx, next) => { | ||
// Wrap aborted hook in function | ||
// with abort reason in second argument | ||
let prepareAbortedHook = function (hook, reason) { | ||
return async (ctx, next) => { | ||
await hook.handler(ctx, reason) | ||
next() | ||
} | ||
} | ||
let state = { | ||
@@ -161,42 +179,73 @@ config, | ||
result.setQuery(data) | ||
result.aborted = false | ||
let callback = prepareCallback(omit(params, ['hooks', 'mixins'])) | ||
let queue = [ | ||
...hooks.before.map(prepareBeforeHook), | ||
callback | ||
] | ||
return new Promise(async (resolve) => { | ||
let endCallback = (ctx, next) => { | ||
this.calls++ | ||
resolve(ctx) | ||
resolve(ctx.clean) | ||
next() | ||
} | ||
try { | ||
await compose(hooks.before.map(prepareBeforeHook))(result) | ||
if (result.aborted) { | ||
if ('type' in result.abortInfo) { | ||
await compose(hooks[result.abortInfo.type].map(prepareAfterHook))(result) | ||
let temp = { | ||
abort: false | ||
} | ||
await compose( | ||
hooks.before.map(hook => prepareBeforeHook(hook, temp)) | ||
)(result) | ||
// If call is aborted in before hook | ||
if (temp.abort) { | ||
// next(false) | ||
if (temp.abort === true) { | ||
result.setAborted(null) | ||
return | ||
} | ||
if ('name' in result.abortInfo) { | ||
if (typeof temp.abort !== 'object') { | ||
throw new Error('Abort info should be an object') | ||
} | ||
result.setAborted(temp.abort.reason) | ||
// next(new ApiAbort(data, { type: 'error' })) | ||
if (temp.abort.info.type) { | ||
await compose(hooks[temp.abort.info.type].map(prepareAfterHook))(result) | ||
} | ||
// next(new ApiAbort(data, { name: 'someHook' })) | ||
if (temp.abort.info.name) { | ||
let abortHooks = flatten(values(hooks)) | ||
if (!abortHooks.length) return | ||
await compose(abortHooks.map(prepareAfterHook))(result) | ||
await compose( | ||
abortHooks.map(prepareAfterHook) | ||
)(result) | ||
} | ||
return | ||
// next(new ApiAbort(reason)) | ||
if (!temp.abort.info.type && !temp.abort.info.name) { | ||
await compose( | ||
hooks.aborted.map(hook => prepareAbortedHook(hook, temp.abort.reason)) | ||
)(result) | ||
} | ||
} else { | ||
// Start query | ||
result.pending = true | ||
await callback(result) | ||
result.pending = false | ||
// Success hooks | ||
await compose( | ||
hooks.success.map(prepareAfterHook) | ||
)(result) | ||
} | ||
result.pending = true | ||
await callback(result) | ||
result.pending = false | ||
await compose(hooks.success.map(prepareAfterHook))(result) | ||
} catch (err) { | ||
// Log error in debug mode | ||
if (this.container.debug) { | ||
console.error(err) | ||
} | ||
result.pending = false | ||
await compose(hooks.error.map(prepareAfterHook))(result) | ||
// Call error hooks | ||
if (result.aborted) return | ||
await compose( | ||
hooks.error.map(prepareAfterHook) | ||
)(result) | ||
} finally { | ||
await compose([ | ||
...hooks.finished.map(prepareAfterHook), | ||
endCallback | ||
])(result) | ||
if (result.aborted) return | ||
await compose( | ||
hooks.finished.map(prepareAfterHook).concat(endCallback) | ||
)(result) | ||
} | ||
@@ -203,0 +252,0 @@ }) |
@@ -13,2 +13,3 @@ import pick from 'lodash-es/pick' | ||
// Convert mixin to getter | ||
export const normalizeMixin = function (mixin) { | ||
@@ -31,2 +32,3 @@ if (isFunction(mixin)) { | ||
// Convert hook into { name, handler } object | ||
export const normalizeHook = function (hook) { | ||
@@ -48,2 +50,3 @@ if (isFunction(hook)) { | ||
// Normalize hooks array | ||
export const normalizeHooks = function (hooks) { | ||
@@ -53,11 +56,12 @@ return mapValues( | ||
(hooks, hookType) => | ||
Array.isArray(hooks) | ||
? hooks.map(normalizeHook) | ||
: [hooks].map(normalizeHook) | ||
flatten([hooks]).map(normalizeHook) | ||
) | ||
} | ||
// Normalize hooks object | ||
// add missed hook types arrays | ||
// add new hooks into result object | ||
export const mergeHooksList = function (...lists) { | ||
return mergeWith( | ||
{ before: [], success: [], error: [], finished: [] }, | ||
{ before: [], success: [], error: [], finished: [], aborted: [] }, | ||
...lists, | ||
@@ -71,2 +75,4 @@ (a, b) => | ||
// Convert services with children | ||
// into flat object with `service/nested/name` keys | ||
export const flatServices = function (services, alias = '') { | ||
@@ -83,2 +89,3 @@ let result = {} | ||
// Convert json object to query string | ||
export const jsonToQueryString = json => { | ||
@@ -85,0 +92,0 @@ return !isPlainObject(json) || !Object.keys(json).length |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
479567
1.53%13
8.33%5639
2.6%