-
Notifications
You must be signed in to change notification settings - Fork 5
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 #31 from kouhin/release/v1.1.0
Release/v1.1.0
- Loading branch information
Showing
26 changed files
with
911 additions
and
715 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"presets": [ | ||
"es2015" | ||
] | ||
} |
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,87 @@ | ||
import asyncify from 'async/asyncify'; | ||
import retry from 'async/retry'; | ||
import assign from 'lodash/assign'; | ||
|
||
import { loadFailure, loadSuccess } from './actions'; | ||
import { isAction } from './utils'; | ||
import { DEFAULT_OPTIONS } from './constants'; | ||
|
||
export default class Task { | ||
constructor(context, monitoredAction, params = {}) { | ||
if (!isAction(monitoredAction)) { | ||
throw new Error('action must be a plain object'); | ||
} | ||
|
||
this.context = assign({}, context, { | ||
action: monitoredAction, | ||
}); | ||
|
||
this.params = assign({}, { | ||
success({ action }) { | ||
throw new Error('success() is not implemented', action.type); | ||
}, | ||
error({ action }) { | ||
throw new Error('error() is not implemented', action.type); | ||
}, | ||
loading({ action }) { | ||
return action; | ||
}, | ||
shouldFetch() { | ||
return true; | ||
}, | ||
fetch({ action }) { | ||
throw new Error('Not implemented', action); | ||
}, | ||
}, params); | ||
} | ||
|
||
execute(options = {}, callback) { | ||
const opts = assign({}, DEFAULT_OPTIONS, options); | ||
|
||
const context = this.context; | ||
const dispatch = context.dispatch; | ||
const { | ||
success, | ||
error, | ||
loading, | ||
shouldFetch, | ||
fetch, | ||
} = this.params; | ||
|
||
const disableInternalAction = options.disableInternalAction; | ||
|
||
if (!shouldFetch(context)) { | ||
callback(null, null); // load nothing | ||
if (!disableInternalAction) { | ||
const successAction = loadSuccess(context.action); | ||
dispatch(successAction); | ||
} | ||
return; | ||
} | ||
|
||
dispatch(loading(context)); | ||
|
||
// Retry | ||
const asyncFetch = asyncify(fetch); | ||
retry({ | ||
times: opts.retryTimes, | ||
interval: opts.retryWait, | ||
}, (retryCb) => { | ||
asyncFetch(context, retryCb); | ||
}, (err, result) => { | ||
if (err) { | ||
const errorAction = error(context, err); | ||
if (!disableInternalAction) { | ||
dispatch(loadFailure(context.action, err)); | ||
} | ||
callback(null, dispatch(errorAction)); | ||
return; | ||
} | ||
const successAction = success(context, result); | ||
callback(null, dispatch(successAction)); | ||
if (!disableInternalAction) { | ||
dispatch(loadSuccess(context.action, result)); | ||
} | ||
}); | ||
} | ||
} |
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,31 @@ | ||
import isEqual from 'lodash/isEqual'; | ||
import assign from 'lodash/assign'; | ||
|
||
import Task from './Task'; | ||
import { DEFAULT_OPTIONS } from './constants'; | ||
|
||
export default class TaskDescriptor { | ||
constructor(pattern, params, options = {}) { | ||
this.pattern = pattern; | ||
this.params = params; | ||
this.options = assign({}, DEFAULT_OPTIONS, options); | ||
if (this.options.retryTimes < 1) { | ||
this.options.retryTimes = 1; | ||
} | ||
} | ||
|
||
supports(action) { | ||
switch (typeof this.pattern) { | ||
case 'object': | ||
return isEqual(this.pattern, action); | ||
case 'function': | ||
return this.pattern(action) === true; | ||
default: | ||
return this.pattern === action.type; | ||
} | ||
} | ||
|
||
newTask(context, action) { | ||
return new Task(context, action, this.params); | ||
} | ||
} |
File renamed without changes.
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,9 @@ | ||
import { fixedWait } from './waitStrategies'; | ||
|
||
export const DEFAULT_OPTIONS = { | ||
ttl: 10000, // Default TTL: 10s | ||
retryTimes: 1, | ||
retryWait: fixedWait(0), | ||
}; | ||
|
||
export const REDUX_DATALOADER_ACTION_ID = () => {}; |
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,88 @@ | ||
import findKey from 'lodash/findKey'; | ||
import find from 'lodash/find'; | ||
import isEqual from 'lodash/isEqual'; | ||
import assign from 'lodash/assign'; | ||
import flattenDeep from 'lodash/flattenDeep'; | ||
import get from 'lodash/get'; | ||
import isInteger from 'lodash/isInteger'; | ||
|
||
import { REDUX_DATALOADER_ACTION_ID } from './constants'; | ||
|
||
function findTaskKey(runningTasksMap, action) { | ||
return findKey(runningTasksMap, o => | ||
(o.action.type === action.type && isEqual(o.action, action))); | ||
} | ||
|
||
export default function createDataLoaderMiddleware( | ||
loaders = [], | ||
withArgs = {}, | ||
middlewareOpts = {}, | ||
) { | ||
const flattenedLoaders = flattenDeep(loaders); | ||
let currentId = 1; | ||
const uniqueId = (prefix) => { | ||
currentId += 1; | ||
return `${prefix}${currentId}`; | ||
}; | ||
|
||
const middleware = ({ dispatch, getState }) => { | ||
middleware.runningTasks = {}; | ||
const ctx = assign({}, withArgs, { | ||
dispatch, | ||
getState, | ||
}); | ||
|
||
return next => (receivedAction) => { | ||
// eslint-disable-next-line no-underscore-dangle | ||
if (receivedAction._id !== REDUX_DATALOADER_ACTION_ID) { | ||
return next(receivedAction); | ||
} | ||
return receivedAction.then((asyncAction) => { | ||
// dispatch data loader request action | ||
next(asyncAction); | ||
|
||
const { action } = asyncAction.meta; | ||
const taskKey = findTaskKey(middleware.runningTasks, action); | ||
if (taskKey) { | ||
return middleware.runningTasks[taskKey].promise; | ||
} | ||
|
||
const taskDescriptor = find(flattenedLoaders, loader => loader.supports(action)); | ||
if (!taskDescriptor) { | ||
throw new Error('No loader for action', action); | ||
} | ||
|
||
// Priority: Action Meta Options > TaskDescriptor Options > Middleware Options | ||
const options = assign( | ||
{}, | ||
middlewareOpts, | ||
taskDescriptor.options, | ||
get(asyncAction, 'meta.options', {}), | ||
); | ||
|
||
const task = taskDescriptor.newTask(ctx, action); | ||
const runningTask = new Promise((resolve, reject) => { | ||
task.execute(options, (err, result) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(result); | ||
} | ||
}); | ||
}); | ||
|
||
if (isInteger(options.ttl) && options.ttl > 0) { | ||
const key = uniqueId(`${action.type}__`); | ||
middleware.runningTasks[key] = { action, promise: runningTask }; | ||
if (typeof window !== 'undefined' && typeof document !== 'undefined') { | ||
setTimeout(() => { | ||
delete middleware.runningTasks[key]; | ||
}, options.ttl); | ||
} | ||
} | ||
return runningTask; | ||
}); | ||
}; | ||
}; | ||
return middleware; | ||
} |
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,13 @@ | ||
import TaskDescriptor from './TaskDescriptor'; | ||
|
||
/** | ||
* Create a new TaskDescriptor | ||
* | ||
* @param {string|object|function} pattern pattern to match action | ||
* @param {object} params parameters | ||
* @param {object} options options | ||
* @returns {TaskDescriptor} a descriptor object for creating data loader | ||
*/ | ||
export default function createLoader(pattern, params, options) { | ||
return new TaskDescriptor(pattern, params, options); | ||
} |
Oops, something went wrong.