UNPKG

15.2 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8
9var _errorActionsMap, _actionHandlers;
10
11exports.makeIndexAction = makeIndexAction;
12exports.makeObjectAction = makeObjectAction;
13
14var _lodash = require('lodash');
15
16var _lodash2 = _interopRequireDefault(_lodash);
17
18var _reduxBatchedActions = require('redux-batched-actions');
19
20var _rio = require('./rio');
21
22var _rio2 = _interopRequireDefault(_rio);
23
24var _status = require('./status');
25
26var _outdated = require('./outdated');
27
28var _outdated2 = _interopRequireDefault(_outdated);
29
30var _jsonApiStandardizer = require('./standardizers/json-api-standardizer');
31
32var _consts = require('./consts');
33
34function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
35
36function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* eslint-disable no-unused-expressions */
37/* eslint no-console: ["error", {allow: ["warn", "error"] }] */
38
39
40var actionsWithoutPayload = new Set([_consts.REMOVE_SUCCESS, _consts.LOAD_REQUEST, _consts.CREATE_REQUEST, _consts.LOAD_ERROR, _consts.CREATE_ERROR, _consts.UPDATE_ERROR, _consts.REMOVE_ERROR]);
41
42var actionsWithTags = new Set([_consts.LOAD_REQUEST, _consts.LOAD_SUCCESS, _consts.LOAD_ERROR]);
43
44/**
45 * Map used to resolve actions in case action.error = true to appropriate ERROR actions
46 * for RIO to be able to dispatch actions for reducers.
47 * @type {{}}
48 */
49var errorActionsMap = (_errorActionsMap = {}, _defineProperty(_errorActionsMap, _consts.LOAD_REQUEST, _consts.LOAD_ERROR), _defineProperty(_errorActionsMap, _consts.LOAD_ERROR, _consts.LOAD_ERROR), _defineProperty(_errorActionsMap, _consts.CREATE_REQUEST, _consts.CREATE_ERROR), _defineProperty(_errorActionsMap, _consts.CREATE_ERROR, _consts.CREATE_ERROR), _defineProperty(_errorActionsMap, _consts.UPDATE_REQUEST, _consts.UPDATE_ERROR), _defineProperty(_errorActionsMap, _consts.UPDATE_ERROR, _consts.UPDATE_ERROR), _defineProperty(_errorActionsMap, _consts.REMOVE_REQUEST, _consts.REMOVE_ERROR), _defineProperty(_errorActionsMap, _consts.REMOVE_ERROR, _consts.REMOVE_ERROR), _errorActionsMap);
50
51var outdated = new _outdated2.default();
52
53function makeErrorAction(actionType, errorPayload) {
54 return {
55 type: actionType,
56 payload: errorPayload,
57 error: true
58 };
59}
60
61function makeIndexAction(sourceAction, actionType, data, schema) {
62 var tag = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : '*';
63
64 if (!actionType) {
65 console.error('Action type is not valid.');
66 return makeErrorAction(_consts.COLLECTION_ERROR, 'Action type is not valid.');
67 }
68 if (!data) {
69 console.error('Data is not valid.');
70 return makeErrorAction(_consts.COLLECTION_ERROR, 'Data is not valid.');
71 }
72 if (!schema) {
73 console.error('Schema is not valid.');
74 return makeErrorAction(_consts.COLLECTION_ERROR, 'Schema is not valid.');
75 }
76 if (tag === undefined || tag === null) {
77 console.error('Tag is not valid.');
78 return makeErrorAction(_consts.COLLECTION_ERROR, 'Tag is not valid.');
79 }
80
81 return {
82 type: actionType,
83 payload: data,
84 meta: _extends({}, sourceAction.meta, {
85 schema: schema,
86 tag: tag
87 })
88 };
89}
90
91function makeObjectAction(sourceAction, actionType, item) {
92 if (!actionType) {
93 console.error('Action type is not valid.');
94 return makeErrorAction(_consts.OBJECT_ERROR, 'Action type is not valid.');
95 }
96 if (!item) {
97 console.error('Data is not valid.');
98 return makeErrorAction(_consts.OBJECT_ERROR, 'Data is not valid.');
99 }
100 if (!_lodash2.default.get(item, 'type')) {
101 console.error('Schema is not valid.');
102 return makeErrorAction(_consts.OBJECT_ERROR, 'Schema is not valid.');
103 }
104 if (!_lodash2.default.get(item, 'id')) {
105 console.error('Id is not valid.');
106 return makeErrorAction(_consts.OBJECT_ERROR, 'Id is not valid.');
107 }
108
109 // finds appropriate standardizer for transformation
110 var transform = _rio2.default.getStandardizer(sourceAction.meta.source);
111 // transforms item into standard model
112 var transformation = transform(item);
113
114 return {
115 type: actionType,
116 payload: transformation.transformedObject,
117 meta: _extends({}, sourceAction.meta, {
118 schema: _lodash2.default.get(item, 'type'),
119 transformation: transformation.transformationDescription
120 })
121 };
122}
123
124var getData = function getData(payload) {
125 var data = payload && payload.data || [];
126 return [].concat(data);
127};
128
129var getIncluded = function getIncluded(payload) {
130 return _lodash2.default.get(payload, 'included', []);
131};
132
133var getLinks = function getLinks(payload) {
134 var links = _lodash2.default.get(payload, 'links');
135 if (!links) {
136 return null;
137 }
138
139 return {
140 prev: links.prev || null,
141 next: links.next || null,
142 self: links.self || null,
143 last: links.last || null
144 };
145};
146
147function saveLinks(action, dispatch) {
148 var _action$meta = action.meta,
149 schema = _action$meta.schema,
150 tag = _action$meta.tag;
151
152 var links = getLinks(action.payload);
153
154 if (links) {
155 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { links: links }, schema, tag));
156 }
157}
158
159var getMeta = function getMeta(payload) {
160 return _lodash2.default.get(payload, 'meta');
161};
162
163function saveMeta(action, dispatch) {
164 var _action$meta2 = action.meta,
165 schema = _action$meta2.schema,
166 tag = _action$meta2.tag;
167
168 var meta = getMeta(action.payload);
169
170 if (meta) {
171 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { meta: meta }, schema, tag));
172 }
173}
174
175var actionHandlers = (_actionHandlers = {}, _defineProperty(_actionHandlers, _consts.LOAD_REQUEST, function (action, data, dispatch) {
176 // Make collection busy to prevent multiple requests
177 var _action$meta3 = action.meta,
178 schema = _action$meta3.schema,
179 tag = _action$meta3.tag;
180
181 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { busyStatus: _status.busyStatus.BUSY }, schema, tag));
182}), _defineProperty(_actionHandlers, _consts.LOAD_SUCCESS, function (action, data, dispatch) {
183 // Dispatch objects to storages and collection with specific tag
184 var _action$meta4 = action.meta,
185 schema = _action$meta4.schema,
186 tag = _action$meta4.tag;
187
188
189 data.map(function (item) {
190 return dispatch(makeObjectAction(action, _consts.OBJECT_FETCHED, item));
191 });
192 // TODO: once when we support findOne action and single reducer, REFERENCE_FETCHED
193 // should trigger only for collections
194 dispatch(makeIndexAction(action, _consts.REFERENCE_FETCHED, data, schema, tag));
195
196 saveMeta(action, dispatch);
197 saveLinks(action, dispatch);
198}), _defineProperty(_actionHandlers, _consts.LOAD_ERROR, function (action, data, dispatch) {
199 // Invalidate and idle collection on error
200 var _action$meta5 = action.meta,
201 schema = _action$meta5.schema,
202 tag = _action$meta5.tag;
203
204 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, {
205 busyStatus: _status.busyStatus.IDLE,
206 validationStatus: _status.validationStatus.INVALID,
207 error: true
208 }, schema, tag));
209}), _defineProperty(_actionHandlers, _consts.CREATE_REQUEST, function (action, data, dispatch) {
210 // Change collection status to busy and invalid to prevent fetching.
211 var schema = action.meta.schema;
212 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { validationStatus: _status.validationStatus.INVALID, busyStatus: _status.busyStatus.BUSY }, schema));
213}), _defineProperty(_actionHandlers, _consts.CREATE_SUCCESS, function (action, data, dispatch) {
214 // Dispatch created objects to storage and change collection status to invalid, idle
215 data.map(function (item) {
216 return dispatch(makeObjectAction(action, _consts.OBJECT_CREATED, item));
217 });
218 var schema = action.meta.schema;
219 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { validationStatus: _status.validationStatus.INVALID, busyStatus: _status.busyStatus.IDLE }, schema));
220}), _defineProperty(_actionHandlers, _consts.CREATE_ERROR, function (action, data, dispatch) {
221 // Change collection status to idle and invalid to fetch again.
222 var schema = action.meta.schema;
223 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, {
224 validationStatus: _status.validationStatus.INVALID,
225 busyStatus: _status.busyStatus.IDLE
226 }, schema));
227}), _defineProperty(_actionHandlers, _consts.UPDATE_REQUEST, function (action, data, dispatch) {
228 // Change collection status to busy and invalid to prevent fetching and because of
229 // local changes in storage state with updated item.
230 var schema = action.meta.schema;
231 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { validationStatus: _status.validationStatus.INVALID, busyStatus: _status.busyStatus.BUSY }, schema));
232 data.map(function (item) {
233 return dispatch(makeObjectAction(action, _consts.OBJECT_UPDATING, item));
234 });
235}), _defineProperty(_actionHandlers, _consts.UPDATE_SUCCESS, function (action, data, dispatch) {
236 // Dispatch updated objects from and change collections status to idle & invalid
237 data.map(function (item) {
238 return dispatch(makeObjectAction(action, _consts.OBJECT_UPDATED, item));
239 });
240 var schema = action.meta.schema;
241 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { validationStatus: _status.validationStatus.INVALID, busyStatus: _status.busyStatus.IDLE }, schema));
242}), _defineProperty(_actionHandlers, _consts.UPDATE_ERROR, function (action, data, dispatch) {
243 // Change collection status to idle and invalid
244 var schema = action.meta.schema;
245 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, {
246 validationStatus: _status.validationStatus.INVALID,
247 busyStatus: _status.busyStatus.IDLE
248 }, schema));
249}), _defineProperty(_actionHandlers, _consts.REMOVE_REQUEST, function (action, data, dispatch) {
250 // Change collections status to busy and invalid because of removing item in
251 // local storage state
252 var schema = action.meta.schema;
253 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { validationStatus: _status.validationStatus.INVALID, busyStatus: _status.busyStatus.BUSY }, schema));
254 data.map(function (item) {
255 return dispatch(makeObjectAction(action, _consts.OBJECT_REMOVING, item));
256 });
257}), _defineProperty(_actionHandlers, _consts.REMOVE_SUCCESS, function (action, data, dispatch) {
258 // Remove object if already not removed during request
259 data.map(function (item) {
260 return dispatch(makeObjectAction(action, _consts.OBJECT_REMOVED, item));
261 });
262 var schema = action.meta.schema;
263 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, { validationStatus: _status.validationStatus.INVALID, busyStatus: _status.busyStatus.IDLE }, schema));
264}), _defineProperty(_actionHandlers, _consts.REMOVE_ERROR, function (action, data, dispatch) {
265 // Change collections status to idle and invalid
266 var schema = action.meta.schema;
267 dispatch(makeIndexAction(action, _consts.REFERENCE_STATUS, {
268 validationStatus: _status.validationStatus.INVALID,
269 busyStatus: _status.busyStatus.IDLE
270 }, schema));
271}), _actionHandlers);
272
273function shouldContainPayload(action) {
274 return !(actionsWithoutPayload.has(action.type) || _lodash2.default.get(action, 'meta.response.status') === 204);
275}
276
277function isValidAction(action) {
278 if (!actionHandlers[action.type]) {
279 // we are not responsible for handling action
280 return false;
281 }
282 // Check for meta object in action
283 if (action.meta === undefined) {
284 console.error('Meta is undefined.');
285 return false;
286 }
287 var meta = action.meta;
288 // Check if source exists
289 if (meta.source === undefined) {
290 console.error('Source is undefined.');
291 return false;
292 }
293 // Source value exists but check if rio support standardization of such source type
294 if (!_rio2.default.getStandardizer(meta.source)) {
295 return false;
296 }
297 // Check that schema is defined
298 if (!meta.schema) {
299 console.error('Action.meta.schema is undefined.');
300 return false;
301 }
302 // Validate payload for payload-specific action, ignore others
303 if (shouldContainPayload(action)) {
304 // TODO: move this into standardizer specific area once json standardization is supported
305 if (!_lodash2.default.has(action, 'payload')) {
306 console.error('Response should contain payload.');
307 return false;
308 }
309 if (meta.source === _jsonApiStandardizer.JSON_API_SOURCE && !_lodash2.default.has(action, 'payload.data')) {
310 console.error(_jsonApiStandardizer.JSON_API_SOURCE + ' response should contain payload.data.');
311 return false;
312 }
313 }
314 // Validate tag for tag-specific action, ignore others
315 if (actionsWithTags.has(action.type) && !_lodash2.default.isString(_lodash2.default.get(action, 'meta.tag'))) {
316 console.error('Tag is invalid, expecting meta.tag');
317 return false;
318 }
319
320 return true;
321}
322
323/**
324 * Handled failed redux-api-middleware request.
325 *
326 * @param action
327 * @param dispatch
328 * @returns {*}
329 */
330function handleFailedRequest(action, dispatch) {
331 var errorAction = errorActionsMap[action.type];
332
333 if (!errorAction) {
334 console.warn('Can not handle failed request for action type ' + action.type + '.');
335 return;
336 }
337
338 // Update reference status for corresponding error action
339 actionHandlers[errorAction](action, undefined, dispatch);
340}
341
342/**
343 * Handle any request related action and dispatch corresponding state updating actions.
344 * Add batched actions to update state data and/or status.
345 * @param action
346 * @param dispatch
347 */
348function handleNetworkAction(action, dispatch) {
349 // First dispatch included objects
350 var included = getIncluded(action.payload);
351 included.map(function (item) {
352 return dispatch(makeObjectAction(action, _consts.OBJECT_FETCHED, item));
353 });
354
355 var data = getData(action.payload);
356 // Find handler for supported action type to make appropriate logic
357 actionHandlers[action.type](action, data, dispatch);
358}
359
360exports.default = function (store) {
361 return function (next) {
362 return function (action) {
363 // Validate action, if not valid pass
364 if (!isValidAction(action)) {
365 return next(action);
366 }
367
368 if (outdated.isOutdated(action)) {
369 return next(action);
370 }
371 outdated.reportChange(action);
372
373 // TODO: add standardization of whole payload once we support json standardization
374
375 var actions = [];
376 var dispatch = function dispatch(dispatchAction) {
377 return actions.push(dispatchAction);
378 };
379
380 if (action.error) {
381 // Request which thrown an error
382 handleFailedRequest(action, dispatch);
383 } else {
384 handleNetworkAction(action, dispatch);
385 }
386
387 if (!_lodash2.default.isEmpty(actions)) {
388 store.dispatch((0, _reduxBatchedActions.batchActions)(actions));
389 }
390
391 // After middleware handled action pass input action to next
392 return next(action);
393 };
394 };
395};
\No newline at end of file