jason-rails 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +34 -0
- data/README.md +6 -9
- data/app/assets/config/jason_engine_manifest.js +1 -0
- data/app/assets/images/jason/engine/.keep +0 -0
- data/app/assets/stylesheets/jason/engine/application.css +15 -0
- data/app/controllers/jason/api_controller.rb +36 -0
- data/app/helpers/jason/engine/application_helper.rb +6 -0
- data/app/jobs/jason/engine/application_job.rb +6 -0
- data/app/mailers/jason/engine/application_mailer.rb +8 -0
- data/app/models/jason/engine/application_record.rb +7 -0
- data/app/views/layouts/jason/engine/application.html.erb +15 -0
- data/client/babel.config.js +13 -0
- data/client/lib/JasonProvider.d.ts +5 -4
- data/client/lib/JasonProvider.js +30 -3
- data/client/lib/actionFactory.js +1 -1
- data/client/lib/createActions.d.ts +1 -1
- data/client/lib/createActions.js +2 -27
- data/client/lib/createJasonReducers.js +1 -0
- data/client/lib/createOptDis.d.ts +1 -0
- data/client/lib/createOptDis.js +45 -0
- data/client/lib/createPayloadHandler.d.ts +1 -1
- data/client/lib/createPayloadHandler.js +23 -6
- data/client/lib/deepCamelizeKeys.d.ts +1 -0
- data/client/lib/deepCamelizeKeys.js +23 -0
- data/client/lib/deepCamelizeKeys.test.d.ts +1 -0
- data/client/lib/deepCamelizeKeys.test.js +106 -0
- data/client/lib/index.d.ts +4 -4
- data/client/package.json +17 -4
- data/client/src/JasonProvider.tsx +33 -5
- data/client/src/actionFactory.ts +1 -1
- data/client/src/createActions.ts +2 -33
- data/client/src/createJasonReducers.ts +1 -0
- data/client/src/createOptDis.ts +47 -0
- data/client/src/createPayloadHandler.ts +26 -4
- data/client/src/deepCamelizeKeys.test.ts +113 -0
- data/client/src/deepCamelizeKeys.ts +17 -0
- data/client/yarn.lock +4539 -81
- data/config/routes.rb +4 -0
- data/jason-rails.gemspec +5 -0
- data/lib/jason.rb +7 -1
- data/lib/jason/api_model.rb +16 -0
- data/lib/jason/channel.rb +2 -2
- data/lib/jason/engine.rb +5 -0
- data/lib/jason/publisher.rb +38 -6
- data/lib/jason/subscription.rb +21 -24
- data/lib/jason/version.rb +1 -1
- metadata +81 -3
@@ -0,0 +1 @@
|
|
1
|
+
export default function deepCamelizeKeys(item: any, excludeIf?: (k: any) => boolean): any;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const lodash_1 = __importDefault(require("lodash"));
|
7
|
+
function deepCamelizeKeys(item, excludeIf = k => false) {
|
8
|
+
function camelizeKey(key) {
|
9
|
+
if (excludeIf(key))
|
10
|
+
return key;
|
11
|
+
return lodash_1.default.camelCase(key);
|
12
|
+
}
|
13
|
+
if (lodash_1.default.isArray(item)) {
|
14
|
+
return lodash_1.default.map(item, item => deepCamelizeKeys(item, excludeIf));
|
15
|
+
}
|
16
|
+
else if (lodash_1.default.isObject(item)) {
|
17
|
+
return lodash_1.default.mapValues(lodash_1.default.mapKeys(item, (v, k) => camelizeKey(k)), (v, k) => deepCamelizeKeys(v, excludeIf));
|
18
|
+
}
|
19
|
+
else {
|
20
|
+
return item;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
exports.default = deepCamelizeKeys;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const deepCamelizeKeys_1 = __importDefault(require("./deepCamelizeKeys"));
|
7
|
+
test('scalar number', () => {
|
8
|
+
expect(deepCamelizeKeys_1.default(1)).toBe(1);
|
9
|
+
});
|
10
|
+
test('scalar number float', () => {
|
11
|
+
expect(deepCamelizeKeys_1.default(1.123)).toBe(1.123);
|
12
|
+
});
|
13
|
+
test('scalar string', () => {
|
14
|
+
expect(deepCamelizeKeys_1.default('test')).toBe('test');
|
15
|
+
});
|
16
|
+
test('scalar null', () => {
|
17
|
+
expect(deepCamelizeKeys_1.default(null)).toBe(null);
|
18
|
+
});
|
19
|
+
test('scalar boolean', () => {
|
20
|
+
expect(deepCamelizeKeys_1.default(true)).toBe(true);
|
21
|
+
});
|
22
|
+
test('object with existing camelized keys', () => {
|
23
|
+
expect(deepCamelizeKeys_1.default({ testMe: 'test' })).toStrictEqual({ testMe: 'test' });
|
24
|
+
});
|
25
|
+
test('array with existing camelized keys', () => {
|
26
|
+
expect(deepCamelizeKeys_1.default([{ testMe: 'test' }, { testMe2: 'test' }])).toStrictEqual([{ testMe: 'test' }, { testMe2: 'test' }]);
|
27
|
+
});
|
28
|
+
test('object with mixed keys', () => {
|
29
|
+
expect(deepCamelizeKeys_1.default({ testMe: 'test', test_2: 'dog', test_me2: true })).toStrictEqual({ testMe: 'test', test2: 'dog', testMe2: true });
|
30
|
+
});
|
31
|
+
test('array with mixed keys', () => {
|
32
|
+
expect(deepCamelizeKeys_1.default([
|
33
|
+
{ testMe: 'test', test_2: 'dog', test_me2: true },
|
34
|
+
{ testMe3: 'test', test_3: 'dog', test_me4: true }
|
35
|
+
])).toStrictEqual([
|
36
|
+
{ testMe: 'test', test2: 'dog', testMe2: true },
|
37
|
+
{ testMe3: 'test', test3: 'dog', testMe4: true }
|
38
|
+
]);
|
39
|
+
});
|
40
|
+
test('nested with object at top level', () => {
|
41
|
+
expect(deepCamelizeKeys_1.default({
|
42
|
+
test_me: {
|
43
|
+
test_me2: {
|
44
|
+
test_me3: [
|
45
|
+
{ test_it_out: '49' },
|
46
|
+
{ test_fun: 'what' }
|
47
|
+
]
|
48
|
+
}
|
49
|
+
}
|
50
|
+
})).toStrictEqual({
|
51
|
+
testMe: {
|
52
|
+
testMe2: {
|
53
|
+
testMe3: [
|
54
|
+
{ testItOut: '49' },
|
55
|
+
{ testFun: 'what' }
|
56
|
+
]
|
57
|
+
}
|
58
|
+
}
|
59
|
+
});
|
60
|
+
});
|
61
|
+
test('nested with object at top level', () => {
|
62
|
+
expect(deepCamelizeKeys_1.default([{
|
63
|
+
test_me: {
|
64
|
+
test_me2: {
|
65
|
+
test_me3: [
|
66
|
+
{ test_it_out: '49' },
|
67
|
+
{ test_fun: 'what' }
|
68
|
+
]
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}, {
|
72
|
+
test_it52: 'what?'
|
73
|
+
}])).toStrictEqual([{
|
74
|
+
testMe: {
|
75
|
+
testMe2: {
|
76
|
+
testMe3: [
|
77
|
+
{ testItOut: '49' },
|
78
|
+
{ testFun: 'what' }
|
79
|
+
]
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}, {
|
83
|
+
testIt52: 'what?'
|
84
|
+
}]);
|
85
|
+
});
|
86
|
+
test('excludes keys by function', () => {
|
87
|
+
expect(deepCamelizeKeys_1.default({
|
88
|
+
test_me: {
|
89
|
+
test_me2: {
|
90
|
+
test_me3: [
|
91
|
+
{ test_it_out: '49' },
|
92
|
+
{ test_fun: 'what' }
|
93
|
+
]
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}, k => (k === 'test_me2'))).toStrictEqual({
|
97
|
+
testMe: {
|
98
|
+
test_me2: {
|
99
|
+
testMe3: [
|
100
|
+
{ testItOut: '49' },
|
101
|
+
{ testFun: 'what' }
|
102
|
+
]
|
103
|
+
}
|
104
|
+
}
|
105
|
+
});
|
106
|
+
});
|
data/client/lib/index.d.ts
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
import _useAct from './useAct';
|
2
2
|
import _useSub from './useSub';
|
3
3
|
export declare const JasonProvider: ({ reducers, middleware, extraActions, children }: {
|
4
|
-
reducers
|
5
|
-
middleware
|
6
|
-
extraActions
|
7
|
-
children
|
4
|
+
reducers?: any;
|
5
|
+
middleware?: any;
|
6
|
+
extraActions?: any;
|
7
|
+
children?: any;
|
8
8
|
}) => any;
|
9
9
|
export declare const useAct: typeof _useAct;
|
10
10
|
export declare const useSub: typeof _useSub;
|
data/client/package.json
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "@jamesr2323/jason",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.4.0",
|
4
4
|
"module": "./lib/index.js",
|
5
|
+
"types": "./lib/index.d.ts",
|
5
6
|
"scripts": {
|
6
|
-
"build": "tsc"
|
7
|
+
"build": "tsc",
|
8
|
+
"test": "jest"
|
7
9
|
},
|
8
10
|
"dependencies": {
|
9
11
|
"@rails/actioncable": "^6.0.3-4",
|
@@ -17,11 +19,22 @@
|
|
17
19
|
"uuid": "^8.3.1"
|
18
20
|
},
|
19
21
|
"devDependencies": {
|
22
|
+
"@babel/core": "^7.12.10",
|
23
|
+
"@babel/preset-env": "^7.12.11",
|
24
|
+
"@babel/preset-typescript": "^7.12.7",
|
25
|
+
"@reduxjs/toolkit": "^1.5.0",
|
26
|
+
"@types/jest": "^26.0.19",
|
27
|
+
"babel-jest": "^26.6.3",
|
28
|
+
"jest": "^26.6.3",
|
29
|
+
"react": "^16.8.3",
|
30
|
+
"react-dom": "^16.8.3",
|
31
|
+
"react-redux": "^7.2.2",
|
20
32
|
"typescript": "^4.1.2"
|
21
33
|
},
|
22
34
|
"peerDependencies": {
|
35
|
+
"@reduxjs/toolkit": "^1.5.0",
|
23
36
|
"react": "^16.8.3",
|
24
|
-
"react-
|
25
|
-
"
|
37
|
+
"react-dom": "^16.8.3",
|
38
|
+
"react-redux": "^7.2.2"
|
26
39
|
}
|
27
40
|
}
|
@@ -7,26 +7,54 @@ import { Provider } from 'react-redux'
|
|
7
7
|
import { createEntityAdapter, createSlice, createReducer, configureStore } from '@reduxjs/toolkit'
|
8
8
|
import createJasonReducers from './createJasonReducers'
|
9
9
|
import createPayloadHandler from './createPayloadHandler'
|
10
|
+
import createOptDis from './createOptDis'
|
10
11
|
import makeEager from './makeEager'
|
11
12
|
import { camelizeKeys } from 'humps'
|
12
13
|
import md5 from 'blueimp-md5'
|
13
14
|
import _ from 'lodash'
|
14
15
|
import React, { useState, useEffect } from 'react'
|
16
|
+
import { validate as isUuid } from 'uuid'
|
15
17
|
|
16
|
-
const JasonProvider = ({ reducers, middleware, extraActions, children }) => {
|
18
|
+
const JasonProvider = ({ reducers, middleware, extraActions, children }: { reducers?: any, middleware?: any, extraActions?: any, children?: React.FC }) => {
|
17
19
|
const [store, setStore] = useState(null)
|
18
20
|
const [value, setValue] = useState(null)
|
19
21
|
const [connected, setConnected] = useState(false)
|
20
22
|
|
21
23
|
const csrfToken = (document.querySelector("meta[name=csrf-token]") as any).content
|
22
24
|
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
|
23
|
-
const restClient = applyCaseMiddleware(axios.create()
|
25
|
+
const restClient = applyCaseMiddleware(axios.create(), {
|
26
|
+
preservedKeys: (key) => {
|
27
|
+
return isUuid(key)
|
28
|
+
}
|
29
|
+
})
|
24
30
|
|
25
31
|
useEffect(() => {
|
26
32
|
restClient.get('/jason/api/schema')
|
27
33
|
.then(({ data: snakey_schema }) => {
|
28
34
|
const schema = camelizeKeys(snakey_schema)
|
29
35
|
|
36
|
+
const serverActionQueue = function() {
|
37
|
+
const queue: any[] = []
|
38
|
+
let inFlight = false
|
39
|
+
|
40
|
+
return {
|
41
|
+
addItem: (item) => queue.push(item),
|
42
|
+
getItem: () => {
|
43
|
+
if (inFlight) return false
|
44
|
+
|
45
|
+
const item = queue.shift()
|
46
|
+
if (item) {
|
47
|
+
inFlight = true
|
48
|
+
return item
|
49
|
+
}
|
50
|
+
return false
|
51
|
+
},
|
52
|
+
itemProcessed: () => inFlight = false,
|
53
|
+
fullySynced: () => queue.length === 0 && !inFlight,
|
54
|
+
getData: () => ({ queue, inFlight })
|
55
|
+
}
|
56
|
+
}()
|
57
|
+
|
30
58
|
const consumer = createConsumer()
|
31
59
|
const allReducers = {
|
32
60
|
...reducers,
|
@@ -67,7 +95,7 @@ const JasonProvider = ({ reducers, middleware, extraActions, children }) => {
|
|
67
95
|
console.log('Subscribe with', config, md5Hash)
|
68
96
|
|
69
97
|
_.map(config, (v, model) => {
|
70
|
-
payloadHandlers[`${model}:${md5Hash}`] = createPayloadHandler(store.dispatch, subscription, model, schema[model])
|
98
|
+
payloadHandlers[`${model}:${md5Hash}`] = createPayloadHandler(store.dispatch, serverActionQueue, subscription, model, schema[model])
|
71
99
|
})
|
72
100
|
subscription.send({ createSubscription: config })
|
73
101
|
|
@@ -81,8 +109,8 @@ const JasonProvider = ({ reducers, middleware, extraActions, children }) => {
|
|
81
109
|
delete payloadHandlers[`${model}:${md5Hash}`]
|
82
110
|
})
|
83
111
|
}
|
84
|
-
|
85
|
-
const actions = createActions(schema, store, restClient, extraActions)
|
112
|
+
const optDis = createOptDis(schema, store.dispatch, restClient, serverActionQueue)
|
113
|
+
const actions = createActions(schema, store, restClient, optDis, extraActions)
|
86
114
|
const eager = makeEager(schema)
|
87
115
|
|
88
116
|
console.log({ actions })
|
data/client/src/actionFactory.ts
CHANGED
@@ -24,7 +24,7 @@ export default (dis, store, entity, { extraActions = {}, hasPriority = false } =
|
|
24
24
|
return dis({ type: `${pluralize(entity)}/remove`, payload: id })
|
25
25
|
}
|
26
26
|
|
27
|
-
const extraActionsResolved = _.mapValues(extraActions, v => v(dis, store, entity))
|
27
|
+
const extraActionsResolved = extraActions ? _.mapValues(extraActions, v => v(dis, store, entity)) : {}
|
28
28
|
|
29
29
|
if (hasPriority) {
|
30
30
|
return { add, upsert, setAll, remove, movePriority, ...extraActionsResolved }
|
data/client/src/createActions.ts
CHANGED
@@ -1,39 +1,8 @@
|
|
1
1
|
import actionFactory from './actionFactory'
|
2
2
|
import pluralize from 'pluralize'
|
3
3
|
import _ from 'lodash'
|
4
|
-
import { v4 as uuidv4 } from 'uuid'
|
5
|
-
|
6
|
-
function enrich(type, payload) {
|
7
|
-
if (type.split('/')[1] === 'upsert' && !(type.split('/')[0] === 'session')) {
|
8
|
-
if (!payload.id) {
|
9
|
-
return { ...payload, id: uuidv4() }
|
10
|
-
}
|
11
|
-
}
|
12
|
-
return payload
|
13
|
-
}
|
14
|
-
|
15
|
-
function makeOptDis(schema, dispatch, restClient) {
|
16
|
-
const plurals = _.keys(schema).map(k => pluralize(k))
|
17
|
-
|
18
|
-
return function (action) {
|
19
|
-
const { type, payload } = action
|
20
|
-
const data = enrich(type, payload)
|
21
|
-
|
22
|
-
dispatch(action)
|
23
|
-
|
24
|
-
if (plurals.indexOf(type.split('/')[0]) > -1) {
|
25
|
-
return restClient.post('/jason/api/action', { type, payload: data } )
|
26
|
-
.catch(e => {
|
27
|
-
dispatch({ type: 'upsertLocalUi', data: { error: JSON.stringify(e) } })
|
28
|
-
})
|
29
|
-
}
|
30
|
-
}
|
31
|
-
}
|
32
|
-
|
33
|
-
function createActions(schema, store, restClient, extraActions) {
|
34
|
-
const dis = store.dispatch;
|
35
|
-
const optDis = makeOptDis(schema, dis, restClient)
|
36
4
|
|
5
|
+
function createActions(schema, store, restClient, optDis, extraActions) {
|
37
6
|
const actions = _.fromPairs(_.map(schema, (config, model: string) => {
|
38
7
|
if (config.priorityScope) {
|
39
8
|
return [pluralize(model), actionFactory(optDis, store, model, { hasPriority: true })]
|
@@ -42,7 +11,7 @@ function createActions(schema, store, restClient, extraActions) {
|
|
42
11
|
}
|
43
12
|
}))
|
44
13
|
|
45
|
-
const extraActionsResolved = extraActions(optDis, store, restClient)
|
14
|
+
const extraActionsResolved = extraActions ? extraActions(optDis, store, restClient, actions) : {}
|
46
15
|
|
47
16
|
return _.merge(actions, extraActionsResolved)
|
48
17
|
}
|
@@ -16,6 +16,7 @@ function generateSlices(schema) {
|
|
16
16
|
add: adapter.addOne,
|
17
17
|
setAll: adapter.setAll,
|
18
18
|
remove: adapter.removeOne,
|
19
|
+
removeMany: adapter.removeMany,
|
19
20
|
movePriority: (s, { payload: { id, priority, parentFilter } }) => {
|
20
21
|
// Get IDs and insert our item at the new index
|
21
22
|
var affectedIds = _.orderBy(_.filter(_.values(s.entities), parentFilter).filter(e => e.id !== id), 'priority').map(e => e.id)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import _ from 'lodash'
|
2
|
+
import pluralize from 'pluralize'
|
3
|
+
import { v4 as uuidv4 } from 'uuid'
|
4
|
+
|
5
|
+
function enrich(type, payload) {
|
6
|
+
if (type.split('/')[1] === 'upsert' && !(type.split('/')[0] === 'session')) {
|
7
|
+
if (!payload.id) {
|
8
|
+
return { ...payload, id: uuidv4() }
|
9
|
+
}
|
10
|
+
}
|
11
|
+
return payload
|
12
|
+
}
|
13
|
+
|
14
|
+
export default function createOptDis(schema, dispatch, restClient, serverActionQueue) {
|
15
|
+
const plurals = _.keys(schema).map(k => pluralize(k))
|
16
|
+
let inFlight = false
|
17
|
+
|
18
|
+
function enqueueServerAction (action) {
|
19
|
+
serverActionQueue.addItem(action)
|
20
|
+
}
|
21
|
+
|
22
|
+
function dispatchServerAction() {
|
23
|
+
const action = serverActionQueue.getItem()
|
24
|
+
if (!action) return
|
25
|
+
|
26
|
+
inFlight = true
|
27
|
+
restClient.post('/jason/api/action', action)
|
28
|
+
.then(serverActionQueue.itemProcessed)
|
29
|
+
.catch(e => {
|
30
|
+
dispatch({ type: 'upsertLocalUi', data: { error: JSON.stringify(e) } })
|
31
|
+
serverActionQueue.itemProcessed()
|
32
|
+
})
|
33
|
+
}
|
34
|
+
|
35
|
+
setInterval(dispatchServerAction, 10)
|
36
|
+
|
37
|
+
return function (action) {
|
38
|
+
const { type, payload } = action
|
39
|
+
const data = enrich(type, payload)
|
40
|
+
|
41
|
+
dispatch({ type, payload: data })
|
42
|
+
|
43
|
+
if (plurals.indexOf(type.split('/')[0]) > -1) {
|
44
|
+
enqueueServerAction({ type, payload: data })
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
@@ -1,16 +1,18 @@
|
|
1
1
|
import { apply_patch } from 'jsonpatch'
|
2
|
-
import
|
2
|
+
import deepCamelizeKeys from './deepCamelizeKeys'
|
3
3
|
import pluralize from 'pluralize'
|
4
4
|
import _ from 'lodash'
|
5
|
+
import { validate as isUuid } from 'uuid'
|
5
6
|
|
6
7
|
function diffSeconds(dt2, dt1) {
|
7
8
|
var diff =(dt2.getTime() - dt1.getTime()) / 1000
|
8
9
|
return Math.abs(Math.round(diff))
|
9
10
|
}
|
10
11
|
|
11
|
-
export default function createPayloadHandler(dispatch, subscription, model, config) {
|
12
|
+
export default function createPayloadHandler(dispatch, serverActionQueue, subscription, model, config) {
|
12
13
|
console.log({ model, config })
|
13
|
-
let payload =
|
14
|
+
let payload = [] as any[]
|
15
|
+
let previousPayload = [] as any[]
|
14
16
|
let idx = 0
|
15
17
|
let patchQueue = {}
|
16
18
|
|
@@ -23,20 +25,40 @@ export default function createPayloadHandler(dispatch, subscription, model, conf
|
|
23
25
|
subscription.send({ getPayload: { model, config } })
|
24
26
|
}
|
25
27
|
|
28
|
+
function camelizeKeys(item) {
|
29
|
+
return deepCamelizeKeys(item, key => isUuid(key))
|
30
|
+
}
|
31
|
+
|
26
32
|
const tGetPayload = _.throttle(getPayload, 10000)
|
27
33
|
|
28
34
|
function dispatchPayload() {
|
35
|
+
// We want to avoid updates from server overwriting changes to local state, so if there is a queue then wait.
|
36
|
+
if (!serverActionQueue.fullySynced()) {
|
37
|
+
console.log(serverActionQueue.getData())
|
38
|
+
setTimeout(dispatchPayload, 100)
|
39
|
+
return
|
40
|
+
}
|
41
|
+
|
29
42
|
const includeModels = (config.includeModels || []).map(m => _.camelCase(m))
|
30
43
|
|
31
44
|
console.log("Dispatching", { payload, includeModels })
|
32
45
|
|
33
46
|
includeModels.forEach(m => {
|
34
47
|
const subPayload = _.flatten(_.compact(camelizeKeys(payload).map(instance => instance[m])))
|
35
|
-
|
48
|
+
const previousSubPayload = _.flatten(_.compact(camelizeKeys(previousPayload).map(instance => instance[m])))
|
49
|
+
|
50
|
+
// Find IDs that were in the payload but are no longer
|
51
|
+
const idsToRemove = _.difference(previousSubPayload.map(i => i.id), subPayload.map(i => i.id))
|
52
|
+
|
36
53
|
dispatch({ type: `${pluralize(m)}/upsertMany`, payload: subPayload })
|
54
|
+
dispatch({ type: `${pluralize(m)}/removeMany`, payload: idsToRemove })
|
37
55
|
})
|
38
56
|
|
57
|
+
const idsToRemove = _.difference(previousPayload.map(i => i.id), payload.map(i => i.id))
|
58
|
+
|
39
59
|
dispatch({ type: `${pluralize(model)}/upsertMany`, payload: camelizeKeys(payload) })
|
60
|
+
dispatch({ type: `${pluralize(model)}/removeMany`, payload: idsToRemove })
|
61
|
+
previousPayload = payload
|
40
62
|
}
|
41
63
|
|
42
64
|
function processQueue() {
|