jason-rails 0.4.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.ruby-version +1 -0
  4. data/Gemfile.lock +152 -2
  5. data/README.md +117 -5
  6. data/app/controllers/jason/api/pusher_controller.rb +15 -0
  7. data/app/controllers/jason/api_controller.rb +44 -2
  8. data/client/lib/JasonContext.d.ts +6 -1
  9. data/client/lib/JasonContext.js +4 -1
  10. data/client/lib/JasonProvider.d.ts +2 -2
  11. data/client/lib/JasonProvider.js +5 -124
  12. data/client/lib/createJasonReducers.js +48 -3
  13. data/client/lib/createOptDis.js +0 -2
  14. data/client/lib/createPayloadHandler.d.ts +9 -1
  15. data/client/lib/createPayloadHandler.js +47 -55
  16. data/client/lib/createServerActionQueue.d.ts +10 -0
  17. data/client/lib/createServerActionQueue.js +48 -0
  18. data/client/lib/createServerActionQueue.test.d.ts +1 -0
  19. data/client/lib/createServerActionQueue.test.js +37 -0
  20. data/client/lib/createTransportAdapter.d.ts +5 -0
  21. data/client/lib/createTransportAdapter.js +20 -0
  22. data/client/lib/index.d.ts +5 -2
  23. data/client/lib/index.js +3 -1
  24. data/client/lib/makeEager.js +2 -2
  25. data/client/lib/pruneIdsMiddleware.d.ts +2 -0
  26. data/client/lib/pruneIdsMiddleware.js +24 -0
  27. data/client/lib/restClient.d.ts +2 -0
  28. data/client/lib/restClient.js +17 -0
  29. data/client/lib/transportAdapters/actionCableAdapter.d.ts +5 -0
  30. data/client/lib/transportAdapters/actionCableAdapter.js +35 -0
  31. data/client/lib/transportAdapters/pusherAdapter.d.ts +5 -0
  32. data/client/lib/transportAdapters/pusherAdapter.js +68 -0
  33. data/client/lib/useJason.d.ts +5 -0
  34. data/client/lib/useJason.js +94 -0
  35. data/client/lib/useJason.test.d.ts +1 -0
  36. data/client/lib/useJason.test.js +85 -0
  37. data/client/lib/useSub.d.ts +1 -1
  38. data/client/lib/useSub.js +6 -3
  39. data/client/package.json +5 -3
  40. data/client/src/JasonContext.ts +4 -1
  41. data/client/src/JasonProvider.tsx +5 -123
  42. data/client/src/createJasonReducers.ts +56 -3
  43. data/client/src/createOptDis.ts +0 -2
  44. data/client/src/createPayloadHandler.ts +53 -64
  45. data/client/src/createServerActionQueue.test.ts +42 -0
  46. data/client/src/createServerActionQueue.ts +47 -0
  47. data/client/src/createTransportAdapter.ts +13 -0
  48. data/client/src/index.ts +3 -1
  49. data/client/src/makeEager.ts +2 -2
  50. data/client/src/pruneIdsMiddleware.ts +24 -0
  51. data/client/src/restClient.ts +14 -0
  52. data/client/src/transportAdapters/actionCableAdapter.ts +38 -0
  53. data/client/src/transportAdapters/pusherAdapter.ts +72 -0
  54. data/client/src/useJason.test.ts +87 -0
  55. data/client/src/useJason.ts +110 -0
  56. data/client/src/useSub.ts +6 -3
  57. data/client/yarn.lock +71 -3
  58. data/config/routes.rb +5 -1
  59. data/jason-rails.gemspec +4 -0
  60. data/lib/jason.rb +61 -1
  61. data/lib/jason/api_model.rb +2 -12
  62. data/lib/jason/broadcaster.rb +19 -0
  63. data/lib/jason/channel.rb +50 -21
  64. data/lib/jason/graph_helper.rb +165 -0
  65. data/lib/jason/includes_helper.rb +108 -0
  66. data/lib/jason/lua_generator.rb +71 -0
  67. data/lib/jason/publisher.rb +82 -37
  68. data/lib/jason/publisher_old.rb +112 -0
  69. data/lib/jason/subscription.rb +349 -97
  70. data/lib/jason/subscription_old.rb +171 -0
  71. data/lib/jason/version.rb +1 -1
  72. metadata +80 -3
@@ -3,6 +3,6 @@ declare const JasonProvider: ({ reducers, middleware, extraActions, children }:
3
3
  reducers?: any;
4
4
  middleware?: any;
5
5
  extraActions?: any;
6
- children?: any;
7
- }) => any;
6
+ children?: React.FC<{}> | undefined;
7
+ }) => JSX.Element;
8
8
  export default JasonProvider;
@@ -1,134 +1,15 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
22
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
4
  };
24
5
  Object.defineProperty(exports, "__esModule", { value: true });
25
- const createActions_1 = __importDefault(require("./createActions"));
26
- const actioncable_1 = require("@rails/actioncable");
27
- const JasonContext_1 = __importDefault(require("./JasonContext"));
28
- const axios_1 = __importDefault(require("axios"));
29
- const axios_case_converter_1 = __importDefault(require("axios-case-converter"));
6
+ const react_1 = __importDefault(require("react"));
7
+ const useJason_1 = __importDefault(require("./useJason"));
30
8
  const react_redux_1 = require("react-redux");
31
- const toolkit_1 = require("@reduxjs/toolkit");
32
- const createJasonReducers_1 = __importDefault(require("./createJasonReducers"));
33
- const createPayloadHandler_1 = __importDefault(require("./createPayloadHandler"));
34
- const createOptDis_1 = __importDefault(require("./createOptDis"));
35
- const makeEager_1 = __importDefault(require("./makeEager"));
36
- const humps_1 = require("humps");
37
- const blueimp_md5_1 = __importDefault(require("blueimp-md5"));
38
- const lodash_1 = __importDefault(require("lodash"));
39
- const react_1 = __importStar(require("react"));
40
- const uuid_1 = require("uuid");
9
+ const JasonContext_1 = __importDefault(require("./JasonContext"));
41
10
  const JasonProvider = ({ reducers, middleware, extraActions, children }) => {
42
- const [store, setStore] = react_1.useState(null);
43
- const [value, setValue] = react_1.useState(null);
44
- const [connected, setConnected] = react_1.useState(false);
45
- const csrfToken = document.querySelector("meta[name=csrf-token]").content;
46
- axios_1.default.defaults.headers.common['X-CSRF-Token'] = csrfToken;
47
- const restClient = axios_case_converter_1.default(axios_1.default.create(), {
48
- preservedKeys: (key) => {
49
- return uuid_1.validate(key);
50
- }
51
- });
52
- react_1.useEffect(() => {
53
- restClient.get('/jason/api/schema')
54
- .then(({ data: snakey_schema }) => {
55
- const schema = humps_1.camelizeKeys(snakey_schema);
56
- const serverActionQueue = function () {
57
- const queue = [];
58
- let inFlight = false;
59
- return {
60
- addItem: (item) => queue.push(item),
61
- getItem: () => {
62
- if (inFlight)
63
- return false;
64
- const item = queue.shift();
65
- if (item) {
66
- inFlight = true;
67
- return item;
68
- }
69
- return false;
70
- },
71
- itemProcessed: () => inFlight = false,
72
- fullySynced: () => queue.length === 0 && !inFlight,
73
- getData: () => ({ queue, inFlight })
74
- };
75
- }();
76
- const consumer = actioncable_1.createConsumer();
77
- const allReducers = Object.assign(Object.assign({}, reducers), createJasonReducers_1.default(schema));
78
- console.log({ schema, allReducers });
79
- const store = toolkit_1.configureStore({ reducer: allReducers, middleware });
80
- let payloadHandlers = {};
81
- function handlePayload(payload) {
82
- const { model, md5Hash } = payload;
83
- console.log({ md5Hash, fn: `${model}:${md5Hash}`, payloadHandlers, model: lodash_1.default.camelCase(model), payload });
84
- const handler = payloadHandlers[`${lodash_1.default.camelCase(model)}:${md5Hash}`];
85
- if (handler) {
86
- handler(Object.assign(Object.assign({}, payload), { model: lodash_1.default.camelCase(model) }));
87
- }
88
- }
89
- const subscription = (consumer.subscriptions.create({
90
- channel: 'Jason::Channel'
91
- }, {
92
- connected: () => {
93
- setConnected(true);
94
- },
95
- received: payload => {
96
- console.log("Payload received", payload);
97
- handlePayload(payload);
98
- },
99
- disconnected: () => console.warn('Disconnected from ActionCable')
100
- }));
101
- console.log('sending message');
102
- subscription.send({ message: 'test' });
103
- function createSubscription(config) {
104
- const md5Hash = blueimp_md5_1.default(JSON.stringify(config));
105
- console.log('Subscribe with', config, md5Hash);
106
- lodash_1.default.map(config, (v, model) => {
107
- payloadHandlers[`${model}:${md5Hash}`] = createPayloadHandler_1.default(store.dispatch, serverActionQueue, subscription, model, schema[model]);
108
- });
109
- subscription.send({ createSubscription: config });
110
- return () => removeSubscription(config);
111
- }
112
- function removeSubscription(config) {
113
- subscription.send({ removeSubscription: config });
114
- const md5Hash = blueimp_md5_1.default(JSON.stringify(config));
115
- lodash_1.default.map(config, (v, model) => {
116
- delete payloadHandlers[`${model}:${md5Hash}`];
117
- });
118
- }
119
- const optDis = createOptDis_1.default(schema, store.dispatch, restClient, serverActionQueue);
120
- const actions = createActions_1.default(schema, store, restClient, optDis, extraActions);
121
- const eager = makeEager_1.default(schema);
122
- console.log({ actions });
123
- setValue({
124
- actions: actions,
125
- subscribe: (config) => createSubscription(config),
126
- eager
127
- });
128
- setStore(store);
129
- });
130
- }, []);
131
- if (!(store && value && connected))
11
+ const [store, value] = useJason_1.default({ reducers, middleware, extraActions });
12
+ if (!(store && value))
132
13
  return react_1.default.createElement("div", null); // Wait for async fetch of schema to complete
133
14
  return react_1.default.createElement(react_redux_1.Provider, { store: store },
134
15
  react_1.default.createElement(JasonContext_1.default.Provider, { value: value }, children));
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const toolkit_1 = require("@reduxjs/toolkit");
7
7
  const pluralize_1 = __importDefault(require("pluralize"));
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
- function generateSlices(schema) {
10
- const sliceNames = schema.map(k => pluralize_1.default(k));
9
+ function generateSlices(models) {
10
+ const sliceNames = models.map(k => pluralize_1.default(k));
11
11
  const adapter = toolkit_1.createEntityAdapter();
12
12
  return lodash_1.default.fromPairs(lodash_1.default.map(sliceNames, name => {
13
13
  return [name, toolkit_1.createSlice({
@@ -31,7 +31,52 @@ function generateSlices(schema) {
31
31
  }).reducer];
32
32
  }));
33
33
  }
34
+ function generateJasonSlices(models) {
35
+ const initialState = lodash_1.default.fromPairs(lodash_1.default.map(models, (model_name) => {
36
+ return [model_name, {}];
37
+ }));
38
+ const modelSliceReducer = toolkit_1.createSlice({
39
+ name: 'jasonModels',
40
+ initialState,
41
+ reducers: {
42
+ setSubscriptionIds(s, a) {
43
+ const { payload } = a;
44
+ const { subscriptionId, model, ids } = payload;
45
+ console.log({ initialState });
46
+ s[model][subscriptionId] = ids;
47
+ },
48
+ addSubscriptionId(s, a) {
49
+ const { payload } = a;
50
+ const { subscriptionId, model, id } = payload;
51
+ s[model][subscriptionId] = lodash_1.default.union(s[model][subscriptionId] || [], [id]);
52
+ },
53
+ removeSubscriptionId(s, a) {
54
+ const { payload } = a;
55
+ const { subscriptionId, model, id } = payload;
56
+ s[model][subscriptionId] = lodash_1.default.remove(s[model][subscriptionId] || [], id);
57
+ },
58
+ removeSubscription(s, a) {
59
+ const { payload: { subscriptionId } } = a;
60
+ lodash_1.default.map(models, model => {
61
+ delete s[model][subscriptionId];
62
+ });
63
+ }
64
+ }
65
+ }).reducer;
66
+ const jasonSliceReducer = toolkit_1.createSlice({
67
+ name: 'jason',
68
+ initialState: {
69
+ connected: false,
70
+ queueSize: 0
71
+ },
72
+ reducers: {
73
+ upsert: (s, a) => (Object.assign(Object.assign({}, s), a.payload))
74
+ }
75
+ }).reducer;
76
+ return { jason: jasonSliceReducer, jasonModels: modelSliceReducer };
77
+ }
34
78
  function createJasonReducers(schema) {
35
- return generateSlices(lodash_1.default.keys(schema));
79
+ const models = lodash_1.default.keys(schema);
80
+ return Object.assign(Object.assign({}, generateSlices(models)), generateJasonSlices(models));
36
81
  }
37
82
  exports.default = createJasonReducers;
@@ -16,7 +16,6 @@ function enrich(type, payload) {
16
16
  }
17
17
  function createOptDis(schema, dispatch, restClient, serverActionQueue) {
18
18
  const plurals = lodash_1.default.keys(schema).map(k => pluralize_1.default(k));
19
- let inFlight = false;
20
19
  function enqueueServerAction(action) {
21
20
  serverActionQueue.addItem(action);
22
21
  }
@@ -24,7 +23,6 @@ function createOptDis(schema, dispatch, restClient, serverActionQueue) {
24
23
  const action = serverActionQueue.getItem();
25
24
  if (!action)
26
25
  return;
27
- inFlight = true;
28
26
  restClient.post('/jason/api/action', action)
29
27
  .then(serverActionQueue.itemProcessed)
30
28
  .catch(e => {
@@ -1 +1,9 @@
1
- export default function createPayloadHandler(dispatch: any, serverActionQueue: any, subscription: any, model: any, config: any): (data: any) => null | undefined;
1
+ export default function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }: {
2
+ dispatch: any;
3
+ serverActionQueue: any;
4
+ transportAdapter: any;
5
+ config: any;
6
+ }): {
7
+ handlePayload: (data: any) => void;
8
+ tearDown: () => void;
9
+ };
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const jsonpatch_1 = require("jsonpatch");
7
6
  const deepCamelizeKeys_1 = __importDefault(require("./deepCamelizeKeys"));
8
7
  const pluralize_1 = __importDefault(require("pluralize"));
9
8
  const lodash_1 = __importDefault(require("lodash"));
@@ -12,93 +11,86 @@ function diffSeconds(dt2, dt1) {
12
11
  var diff = (dt2.getTime() - dt1.getTime()) / 1000;
13
12
  return Math.abs(Math.round(diff));
14
13
  }
15
- function createPayloadHandler(dispatch, serverActionQueue, subscription, model, config) {
16
- console.log({ model, config });
17
- let payload = [];
18
- let previousPayload = [];
19
- let idx = 0;
14
+ function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }) {
15
+ const subscriptionId = uuid_1.v4();
16
+ let idx = {};
20
17
  let patchQueue = {};
21
18
  let lastCheckAt = new Date();
22
19
  let updateDeadline = null;
23
20
  let checkInterval;
24
21
  function getPayload() {
25
- console.log({ getPayload: model, subscription });
26
- subscription.send({ getPayload: { model, config } });
22
+ setTimeout(() => transportAdapter.getPayload(config), 1000);
27
23
  }
28
24
  function camelizeKeys(item) {
29
25
  return deepCamelizeKeys_1.default(item, key => uuid_1.validate(key));
30
26
  }
31
27
  const tGetPayload = lodash_1.default.throttle(getPayload, 10000);
32
- function dispatchPayload() {
33
- // We want to avoid updates from server overwriting changes to local state, so if there is a queue then wait.
34
- if (!serverActionQueue.fullySynced()) {
35
- console.log(serverActionQueue.getData());
36
- setTimeout(dispatchPayload, 100);
37
- return;
38
- }
39
- const includeModels = (config.includeModels || []).map(m => lodash_1.default.camelCase(m));
40
- console.log("Dispatching", { payload, includeModels });
41
- includeModels.forEach(m => {
42
- const subPayload = lodash_1.default.flatten(lodash_1.default.compact(camelizeKeys(payload).map(instance => instance[m])));
43
- const previousSubPayload = lodash_1.default.flatten(lodash_1.default.compact(camelizeKeys(previousPayload).map(instance => instance[m])));
44
- // Find IDs that were in the payload but are no longer
45
- const idsToRemove = lodash_1.default.difference(previousSubPayload.map(i => i.id), subPayload.map(i => i.id));
46
- dispatch({ type: `${pluralize_1.default(m)}/upsertMany`, payload: subPayload });
47
- dispatch({ type: `${pluralize_1.default(m)}/removeMany`, payload: idsToRemove });
48
- });
49
- const idsToRemove = lodash_1.default.difference(previousPayload.map(i => i.id), payload.map(i => i.id));
50
- dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload: camelizeKeys(payload) });
51
- dispatch({ type: `${pluralize_1.default(model)}/removeMany`, payload: idsToRemove });
52
- previousPayload = payload;
53
- }
54
- function processQueue() {
55
- console.log({ idx, patchQueue });
28
+ function processQueue(model) {
29
+ console.debug("processQueue", model, idx[model], patchQueue[model]);
56
30
  lastCheckAt = new Date();
57
- if (patchQueue[idx]) {
58
- payload = jsonpatch_1.apply_patch(payload, patchQueue[idx]);
59
- if (patchQueue[idx]) {
60
- dispatchPayload();
31
+ if (patchQueue[model][idx[model]]) {
32
+ if (!serverActionQueue.fullySynced()) {
33
+ console.debug(serverActionQueue.getData());
34
+ setTimeout(() => processQueue(model), 100);
35
+ return;
36
+ }
37
+ const { payload, destroy, id, type } = patchQueue[model][idx[model]];
38
+ if (type === 'payload') {
39
+ dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload });
40
+ const ids = payload.map(instance => instance.id);
41
+ dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids } });
61
42
  }
62
- delete patchQueue[idx];
63
- idx++;
43
+ else if (destroy) {
44
+ // Middleware will determine if this model should be removed if it isn't in any other subscriptions
45
+ dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id } });
46
+ }
47
+ else {
48
+ dispatch({ type: `${pluralize_1.default(model)}/upsert`, payload });
49
+ dispatch({ type: `jasonModels/addSubscriptionId`, payload: { model, subscriptionId, id } });
50
+ }
51
+ delete patchQueue[model][idx[model]];
52
+ idx[model]++;
64
53
  updateDeadline = null;
65
- processQueue();
54
+ processQueue(model);
66
55
  // If there are updates in the queue that are ahead of the index, some have arrived out of order
67
56
  // Set a deadline for new updates before it declares the update missing and refetches.
68
57
  }
69
- else if (lodash_1.default.keys(patchQueue).length > 0 && !updateDeadline) {
58
+ else if (lodash_1.default.keys(patchQueue[model]).length > 0 && !updateDeadline) {
70
59
  var t = new Date();
71
60
  t.setSeconds(t.getSeconds() + 3);
72
61
  updateDeadline = t;
73
- setTimeout(processQueue, 3100);
62
+ setTimeout(() => processQueue(model), 3100);
74
63
  // If more than 10 updates in queue, or deadline has passed, restart
75
64
  }
76
- else if (lodash_1.default.keys(patchQueue).length > 10 || (updateDeadline && diffSeconds(updateDeadline, new Date()) < 0)) {
65
+ else if (lodash_1.default.keys(patchQueue[model]).length > 10 || (updateDeadline && diffSeconds(updateDeadline, new Date()) < 0)) {
77
66
  tGetPayload();
78
67
  updateDeadline = null;
79
68
  }
80
69
  }
81
70
  function handlePayload(data) {
82
- const { value, idx: newIdx, diff, latency, type } = data;
83
- console.log({ data });
71
+ const { idx: newIdx, model: snake_model, type } = data;
72
+ const model = lodash_1.default.camelCase(snake_model);
73
+ idx[model] = idx[model] || 0;
74
+ patchQueue[model] = patchQueue[model] || {};
84
75
  if (type === 'payload') {
85
- if (!value)
86
- return null;
87
- payload = value;
88
- dispatchPayload();
89
- idx = newIdx + 1;
76
+ idx[model] = newIdx;
90
77
  // Clear any old changes left in the queue
91
- patchQueue = lodash_1.default.pick(patchQueue, lodash_1.default.keys(patchQueue).filter(k => k > newIdx + 1));
92
- return;
78
+ patchQueue[model] = lodash_1.default.pick(patchQueue[model], lodash_1.default.keys(patchQueue[model]).filter(k => k > newIdx + 1));
93
79
  }
94
- patchQueue[newIdx] = diff;
95
- processQueue();
80
+ patchQueue[model][newIdx] = camelizeKeys(Object.assign(Object.assign({}, data), { model }));
81
+ console.debug("Added to queue", model, idx[model], camelizeKeys(Object.assign(Object.assign({}, data), { model })), serverActionQueue.getData());
82
+ processQueue(model);
96
83
  if (diffSeconds((new Date()), lastCheckAt) >= 3) {
97
84
  lastCheckAt = new Date();
98
- console.log('Interval lost. Pulling from server');
85
+ console.debug('Interval lost. Pulling from server');
99
86
  tGetPayload();
100
87
  }
101
88
  }
102
- return handlePayload;
89
+ tGetPayload();
90
+ // Clean up after ourselves
91
+ function tearDown() {
92
+ dispatch({ type: `jasonModels/removeSubscription`, payload: { subscriptionId } });
93
+ }
94
+ return { handlePayload, tearDown };
103
95
  }
104
96
  exports.default = createPayloadHandler;
@@ -0,0 +1,10 @@
1
+ export default function createServerActionQueue(): {
2
+ addItem: (item: any) => void;
3
+ getItem: () => any;
4
+ itemProcessed: () => boolean;
5
+ fullySynced: () => boolean;
6
+ getData: () => {
7
+ queue: any[];
8
+ inFlight: boolean;
9
+ };
10
+ };
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ // A FIFO queue with deduping of actions whose effect will be cancelled by later actions
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ function createServerActionQueue() {
9
+ const queue = [];
10
+ let inFlight = false;
11
+ function addItem(item) {
12
+ // Check if there are any items ahead in the queue that this item would effectively overwrite.
13
+ // In that case we can remove them
14
+ // If this is an upsert && item ID is the same && current item attributes are a superset of the earlier item attributes
15
+ const { type, payload } = item;
16
+ if (type.split('/')[1] !== 'upsert') {
17
+ queue.push(item);
18
+ return;
19
+ }
20
+ lodash_1.default.remove(queue, item => {
21
+ const { type: itemType, payload: itemPayload } = item;
22
+ if (type !== itemType)
23
+ return false;
24
+ if (itemPayload.id !== payload.id)
25
+ return false;
26
+ // Check that all keys of itemPayload are in payload.
27
+ return lodash_1.default.difference(lodash_1.default.keys(itemPayload), lodash_1.default.keys(payload)).length === 0;
28
+ });
29
+ queue.push(item);
30
+ }
31
+ return {
32
+ addItem,
33
+ getItem: () => {
34
+ if (inFlight)
35
+ return false;
36
+ const item = queue.shift();
37
+ if (item) {
38
+ inFlight = true;
39
+ return item;
40
+ }
41
+ return false;
42
+ },
43
+ itemProcessed: () => inFlight = false,
44
+ fullySynced: () => queue.length === 0 && !inFlight,
45
+ getData: () => ({ queue, inFlight })
46
+ };
47
+ }
48
+ exports.default = createServerActionQueue;