jason-rails 0.5.1 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +41 -7
  4. data/app/controllers/jason/api/pusher_controller.rb +15 -0
  5. data/app/controllers/jason/api_controller.rb +46 -4
  6. data/client/lib/JasonContext.d.ts +1 -1
  7. data/client/lib/JasonContext.js +4 -1
  8. data/client/lib/JasonProvider.js +1 -1
  9. data/client/lib/createJasonReducers.js +9 -3
  10. data/client/lib/createPayloadHandler.d.ts +6 -3
  11. data/client/lib/createPayloadHandler.js +10 -6
  12. data/client/lib/createTransportAdapter.d.ts +5 -0
  13. data/client/lib/createTransportAdapter.js +20 -0
  14. data/client/lib/index.d.ts +2 -0
  15. data/client/lib/index.js +3 -1
  16. data/client/lib/makeEager.js +2 -2
  17. data/client/lib/pruneIdsMiddleware.js +9 -11
  18. data/client/lib/transportAdapters/actionCableAdapter.d.ts +5 -0
  19. data/client/lib/transportAdapters/actionCableAdapter.js +35 -0
  20. data/client/lib/transportAdapters/pusherAdapter.d.ts +5 -0
  21. data/client/lib/transportAdapters/pusherAdapter.js +68 -0
  22. data/client/lib/useEager.d.ts +1 -0
  23. data/client/lib/useEager.js +12 -0
  24. data/client/lib/useJason.js +14 -34
  25. data/client/lib/useJason.test.js +41 -3
  26. data/client/package.json +2 -1
  27. data/client/src/JasonContext.ts +4 -1
  28. data/client/src/JasonProvider.tsx +1 -1
  29. data/client/src/createJasonReducers.ts +9 -3
  30. data/client/src/createPayloadHandler.ts +11 -6
  31. data/client/src/createTransportAdapter.ts +13 -0
  32. data/client/src/index.ts +3 -1
  33. data/client/src/makeEager.ts +2 -2
  34. data/client/src/pruneIdsMiddleware.ts +12 -11
  35. data/client/src/restClient.ts +1 -0
  36. data/client/src/transportAdapters/actionCableAdapter.ts +38 -0
  37. data/client/src/transportAdapters/pusherAdapter.ts +72 -0
  38. data/client/src/useEager.ts +9 -0
  39. data/client/src/useJason.test.ts +49 -3
  40. data/client/src/useJason.ts +15 -36
  41. data/client/yarn.lock +12 -0
  42. data/config/routes.rb +5 -1
  43. data/lib/jason.rb +56 -8
  44. data/lib/jason/broadcaster.rb +19 -0
  45. data/lib/jason/channel.rb +6 -2
  46. data/lib/jason/graph_helper.rb +165 -0
  47. data/lib/jason/includes_helper.rb +108 -0
  48. data/lib/jason/lua_generator.rb +23 -1
  49. data/lib/jason/publisher.rb +20 -16
  50. data/lib/jason/subscription.rb +208 -185
  51. data/lib/jason/version.rb +1 -1
  52. metadata +18 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25daf144fbb017120604c8a458e6eaeeffc705752ae046f9df03aa7b9e0e1f77
4
- data.tar.gz: 741129a4872a9d384018f88049ded591989bbfa23f87c6915be317008c278051
3
+ metadata.gz: 2788ecafe67bc475346d83681912063b1081610e06b685405d4f3b332a665695
4
+ data.tar.gz: 6647cc3a7815a914b98a9477330c32a642fec64c809aa4195fa5c192a4b4b003
5
5
  SHA512:
6
- metadata.gz: f09a2f2f7003f4a259dcdbea1edc702f7e21a5797ed8e8bb73684997a8eccc227349d87b3db27d53a9815407408e9051fa7749002b4ab476000b4cded468dda2
7
- data.tar.gz: 28a93aa98358bc098921442af5c583eff00b610b2be196365a47233468706dc5e42e4a96ec18aa87bfa339cd12d25c39942708b62ef05a821aa6b9a59898dd4d
6
+ metadata.gz: cf1b1ed464f9ef46cd5b3aa2dd7e8ebb982a3d6d3ebe68a3f92f2ad8b5ab31ea8476817fd16666346d358e9d5a635b4af792bba16e41105eefb7a789082567f1
7
+ data.tar.gz: a06afb2c5db05447d7eda5a6f17020a14fd1d486b50f17e14c155738e7166534a52c418f5463742c0956102d744429198ee0101569cef156366586eee3e7c058
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jason-rails (0.4.1)
4
+ jason-rails (0.6.0)
5
5
  connection_pool (>= 2.2.3)
6
6
  jsondiff
7
7
  rails (>= 5)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Jason
2
2
 
3
- Jason is still in a highly experimental phase with a rapidly changing API. Production use not recommended - but please give it a try!
3
+ Jason is still in an experimental phase with a rapidly changing API. It is being used in some production applications, however it is still in 0.x.x series versions, which means that any 0.x version bump could introduce breaking changes.
4
4
 
5
5
  ## The goal
6
6
 
@@ -14,6 +14,8 @@ I also wanted to avoid writing essentially the same code multiple times in diffe
14
14
 
15
15
  Jason attempts to minimize this repitition by auto-generating API endpoints, redux stores and actions from a single schema definition. Further it adds listeners to ActiveRecord models allowing the redux store to be subscribed to updates from a model or set of models.
16
16
 
17
+ An alternative way of thinking about Jason is "what if we applied the Flux/Redux state update pattern to make the _database_ the store?".
18
+
17
19
  ## Installation
18
20
 
19
21
  Add the gem and the NPM package
@@ -54,7 +56,7 @@ end
54
56
 
55
57
  First you need to wrap your root component in a `JasonProvider`.
56
58
 
57
- ```
59
+ ```jsx
58
60
  import { JasonProvider } from '@jamesr2323/jason'
59
61
 
60
62
  return <JasonProvider>
@@ -67,7 +69,8 @@ This is a wrapper around `react-redux` Provider component. This accepts the foll
67
69
  - `reducers` - An object of reducers that will be included in `configureStore`. Make sure these do not conflict with the names of any of the models you are configuring for use with Jason
68
70
  - `extraActions` - Extra actions you want to be available via the `useAct` hook. (See below)
69
71
  This must be a function which returns an object which will be merged with the main Jason actions. The function will be passed a dispatch function, store, axios instance and the Jason actions. For example you can add actions for one of your custom slices:
70
- ```
72
+
73
+ ```js
71
74
  function extraActions(dispatch, store, restClient, act) {
72
75
  return {
73
76
  local: {
@@ -80,7 +83,7 @@ function extraActions(dispatch, store, restClient, act) {
80
83
  - `middleware` - Passed directly to `configureStore` with additional Jason middleware
81
84
 
82
85
  ## Usage
83
- Jason provides two custom hooks to access functionality.
86
+ Jason provides three custom hooks to access functionality.
84
87
 
85
88
  ### useAct
86
89
  This returns an object which allows you to access actions which both update models on the server, and perform an optimistic update to the Redux store.
@@ -109,7 +112,7 @@ export default function PostCreator() {
109
112
  This subscribes your Redux store to a model or set of models. It will automatically unsubscribe when the component unmounts.
110
113
 
111
114
  Example
112
- ```
115
+ ```jsx
113
116
  import React from 'react'
114
117
  import { useSelector } from 'react-redux'
115
118
  import { useSub } from '@jamesr2323/jason'
@@ -125,14 +128,45 @@ export default function PostsList() {
125
128
  }
126
129
  ```
127
130
 
131
+ ### useEager
132
+ Jason stores all the data in a normalized form - one redux slice per model. Often you might want to get nested data from several slices for use in components. The `useEager` hook provides an API for doing that. Under the hood it's just a wrapper around useSelector, which aims to mimic the behaviour of Rails eager loading.
133
+
134
+ Example
135
+ This will fetch the comment as well as the post and user linked to it.
136
+
137
+ ```jsx
138
+ import React from 'react'
139
+ import { useSelector } from 'react-redux'
140
+ import { useEager } from '@jamesr2323/jason'
141
+ import _ from 'lodash'
142
+
143
+ export default function Comment({ id }) {
144
+ const comment = useEager('comments', id, ['post', 'user'])
145
+
146
+ return <div>
147
+ <p>{ comment.body }</p>
148
+ <p>Made on post { comment.post.name } by { comment.user.name }</p>
149
+ </div>
150
+ }
151
+ ```
152
+
153
+ ## Authorization
154
+
155
+ By default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access.
156
+
157
+ ### Authorizing subscriptions
158
+ You can do this by providing an class to Jason in the initializer under the `subscription_authorization_service` key. This must be a class receiving a message `call` with the parameters `user`, `model`, `conditions`, `sub_models` and return true or false for whether the user is allowed to access a subscription with those parameters. You can decide the implementation details of this to be as simple or complex as your app requires.
159
+
160
+ ### Authorizing updates
161
+ Similarly to authorizing subscriptions, you can do this by providing an class to Jason in the initializer under the `update_authorization_service` key. This must be a class receiving a message `call` with the parameters `user`, `model`, `instance`, `update`, `remove` and return true or false for whether the user is allowed to access a subscription with those parameters.
128
162
 
129
163
  ## Roadmap
130
164
 
131
165
  Development is primarily driven by the needs of projects we're using Jason in. In no particular order, being considered is:
132
166
  - Failure handling - rolling back local state in case of an error on the server
133
- - Authorization - integrating with a library like Pundit to determine who can subscribe to given state updates and perform updates on models
167
+ - Authorization - more thorough authorization integration, with utility functions for common authorizations. Allowing authorization of access to particular fields such as restricting the fields of a user that are publicly broadcast.
134
168
  - Utilities for "Draft editing" - both storing client-side copies of model trees which can be committed or discarded, as well as persisting a shadow copy to the database (to allow resumable editing, or possibly collaborative editing features)
135
- - Integration with pub/sub-as-a-service tools, such as Pusher
169
+ - Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
136
170
 
137
171
  ## Development
138
172
 
@@ -0,0 +1,15 @@
1
+ class Jason::Api::PusherController < ApplicationController
2
+ skip_before_action :verify_authenticity_token
3
+
4
+ def auth
5
+ channel_main_name = params[:channel_name].remove("private-#{Jason.pusher_channel_prefix}-")
6
+ subscription_id = channel_main_name.remove('jason-')
7
+
8
+ if Jason::Subscription.find_by_id(subscription_id).user_can_access?(current_user)
9
+ response = Pusher.authenticate(params[:channel_name], params[:socket_id])
10
+ return render json: response
11
+ else
12
+ return head :forbidden
13
+ end
14
+ end
15
+ end
@@ -1,6 +1,21 @@
1
1
  class Jason::ApiController < ::ApplicationController
2
- def schema
3
- render json: Jason.schema
2
+ before_action :load_and_authorize_subscription, only: [:create_subscription, :remove_subscription, :get_payload]
3
+ # config seems to be a reserved name, resulting in infinite loop
4
+ def configuration
5
+ payload = {
6
+ schema: Jason.schema,
7
+ transportService: Jason.transport_service,
8
+ }
9
+
10
+ if Jason.transport_service == :pusher
11
+ payload.merge!({
12
+ pusherKey: Jason.pusher_key,
13
+ pusherRegion: Jason.pusher_region,
14
+ pusherChannelPrefix: Jason.pusher_channel_prefix
15
+ })
16
+ end
17
+
18
+ render json: payload
4
19
  end
5
20
 
6
21
  def action
@@ -20,10 +35,10 @@ class Jason::ApiController < ::ApplicationController
20
35
  all_instance_ids.insert(priority.to_i, instance.id)
21
36
 
22
37
  all_instance_ids.each_with_index do |id, i|
23
- model.find(id).update!(priority: i, skip_publish_json: true)
38
+ model.find(id).update!(priority: i)
24
39
  end
25
40
 
26
- model.publish_all(model.find(all_instance_ids))
41
+ model.find(all_instance_ids).each(&:force_publish_json)
27
42
  elsif action == 'upsert' || action == 'add'
28
43
  payload = api_model.permit(params)
29
44
  return render json: model.find_or_create_by_id!(payload).as_json(api_model.as_json_config)
@@ -33,4 +48,31 @@ class Jason::ApiController < ::ApplicationController
33
48
 
34
49
  return head :ok
35
50
  end
51
+
52
+ def create_subscription
53
+ @subscription.add_consumer(params[:consumer_id])
54
+ render json: { channelName: @subscription.channel }
55
+ end
56
+
57
+ def remove_subscription
58
+ @subscription.remove_consumer(params[:consumer_id])
59
+ end
60
+
61
+ def get_payload
62
+ if params[:options].try(:[], :force_refresh)
63
+ @subscription.set_ids_for_sub_models
64
+ end
65
+
66
+ render json: @subscription.get
67
+ end
68
+
69
+ private
70
+
71
+ def load_and_authorize_subscription
72
+ config = params[:config].to_unsafe_h
73
+ @subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
74
+ if !@subscription.user_can_access?(current_user)
75
+ return head :forbidden
76
+ end
77
+ end
36
78
  end
@@ -2,6 +2,6 @@
2
2
  declare const context: import("react").Context<{
3
3
  actions: any;
4
4
  subscribe: null;
5
- eager: null;
5
+ eager: (entity: any, id: any, relations: any) => void;
6
6
  }>;
7
7
  export default context;
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const react_1 = require("react");
4
- const context = react_1.createContext({ actions: {}, subscribe: null, eager: null });
4
+ const eager = function (entity, id, relations) {
5
+ console.error("Eager called but is not implemented");
6
+ };
7
+ const context = react_1.createContext({ actions: {}, subscribe: null, eager });
5
8
  exports.default = context;
@@ -8,7 +8,7 @@ const useJason_1 = __importDefault(require("./useJason"));
8
8
  const react_redux_1 = require("react-redux");
9
9
  const JasonContext_1 = __importDefault(require("./JasonContext"));
10
10
  const JasonProvider = ({ reducers, middleware, extraActions, children }) => {
11
- const [store, value, connected] = useJason_1.default({ reducers, middleware, extraActions });
11
+ const [store, value] = useJason_1.default({ reducers, middleware, extraActions });
12
12
  if (!(store && value))
13
13
  return react_1.default.createElement("div", null); // Wait for async fetch of schema to complete
14
14
  return react_1.default.createElement(react_redux_1.Provider, { store: store },
@@ -42,17 +42,23 @@ function generateJasonSlices(models) {
42
42
  setSubscriptionIds(s, a) {
43
43
  const { payload } = a;
44
44
  const { subscriptionId, model, ids } = payload;
45
- s[model][subscriptionId] = ids;
45
+ s[model][subscriptionId] = ids.map(id => String(id));
46
46
  },
47
47
  addSubscriptionId(s, a) {
48
48
  const { payload } = a;
49
49
  const { subscriptionId, model, id } = payload;
50
- s[model][subscriptionId] = lodash_1.default.union(s[model][subscriptionId] || [], [id]);
50
+ s[model][subscriptionId] = lodash_1.default.union(s[model][subscriptionId] || [], [String(id)]);
51
51
  },
52
52
  removeSubscriptionId(s, a) {
53
53
  const { payload } = a;
54
54
  const { subscriptionId, model, id } = payload;
55
- s[model][subscriptionId] = lodash_1.default.remove(s[model][subscriptionId] || [], id);
55
+ s[model][subscriptionId] = lodash_1.default.difference(s[model][subscriptionId] || [], [String(id)]);
56
+ },
57
+ removeSubscription(s, a) {
58
+ const { payload: { subscriptionId } } = a;
59
+ lodash_1.default.map(models, model => {
60
+ delete s[model][subscriptionId];
61
+ });
56
62
  }
57
63
  }
58
64
  }).reducer;
@@ -1,6 +1,9 @@
1
- export default function createPayloadHandler({ dispatch, serverActionQueue, subscription, config }: {
1
+ export default function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }: {
2
2
  dispatch: any;
3
3
  serverActionQueue: any;
4
- subscription: any;
4
+ transportAdapter: any;
5
5
  config: any;
6
- }): (data: any) => void;
6
+ }): {
7
+ handlePayload: (data: any) => void;
8
+ tearDown: () => void;
9
+ };
@@ -11,7 +11,7 @@ function diffSeconds(dt2, dt1) {
11
11
  var diff = (dt2.getTime() - dt1.getTime()) / 1000;
12
12
  return Math.abs(Math.round(diff));
13
13
  }
14
- function createPayloadHandler({ dispatch, serverActionQueue, subscription, config }) {
14
+ function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }) {
15
15
  const subscriptionId = uuid_1.v4();
16
16
  let idx = {};
17
17
  let patchQueue = {};
@@ -19,7 +19,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
19
19
  let updateDeadline = null;
20
20
  let checkInterval;
21
21
  function getPayload() {
22
- setTimeout(() => subscription.send({ getPayload: config }), 1000);
22
+ setTimeout(() => transportAdapter.getPayload(config), 1000);
23
23
  }
24
24
  function camelizeKeys(item) {
25
25
  return deepCamelizeKeys_1.default(item, key => uuid_1.validate(key));
@@ -36,16 +36,16 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
36
36
  }
37
37
  const { payload, destroy, id, type } = patchQueue[model][idx[model]];
38
38
  if (type === 'payload') {
39
- dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload });
39
+ dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload: payload.map(m => (Object.assign(Object.assign({}, m), { id: String(m.id) }))) });
40
40
  const ids = payload.map(instance => instance.id);
41
41
  dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids } });
42
42
  }
43
43
  else if (destroy) {
44
- dispatch({ type: `${pluralize_1.default(model)}/remove`, payload: id });
44
+ // Middleware will determine if this model should be removed if it isn't in any other subscriptions
45
45
  dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id } });
46
46
  }
47
47
  else {
48
- dispatch({ type: `${pluralize_1.default(model)}/upsert`, payload });
48
+ dispatch({ type: `${pluralize_1.default(model)}/upsert`, payload: Object.assign(Object.assign({}, payload), { id: String(payload.id) }) });
49
49
  dispatch({ type: `jasonModels/addSubscriptionId`, payload: { model, subscriptionId, id } });
50
50
  }
51
51
  delete patchQueue[model][idx[model]];
@@ -87,6 +87,10 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
87
87
  }
88
88
  }
89
89
  tGetPayload();
90
- return handlePayload;
90
+ // Clean up after ourselves
91
+ function tearDown() {
92
+ dispatch({ type: `jasonModels/removeSubscription`, payload: { subscriptionId } });
93
+ }
94
+ return { handlePayload, tearDown };
91
95
  }
92
96
  exports.default = createPayloadHandler;
@@ -0,0 +1,5 @@
1
+ export default function createTransportAdapter(jasonConfig: any, handlePayload: any, dispatch: any, onConnect: any): {
2
+ getPayload: (config: any, options: any) => void;
3
+ createSubscription: (config: any) => void;
4
+ removeSubscription: (config: any) => void;
5
+ };
@@ -0,0 +1,20 @@
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 actionCableAdapter_1 = __importDefault(require("./transportAdapters/actionCableAdapter"));
7
+ const pusherAdapter_1 = __importDefault(require("./transportAdapters/pusherAdapter"));
8
+ function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect) {
9
+ const { transportService } = jasonConfig;
10
+ if (transportService === 'action_cable') {
11
+ return actionCableAdapter_1.default(jasonConfig, handlePayload, dispatch, onConnect);
12
+ }
13
+ else if (transportService === 'pusher') {
14
+ return pusherAdapter_1.default(jasonConfig, handlePayload, dispatch);
15
+ }
16
+ else {
17
+ throw (`Transport adapter does not exist for ${transportService}`);
18
+ }
19
+ }
20
+ exports.default = createTransportAdapter;
@@ -1,6 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import _useAct from './useAct';
3
3
  import _useSub from './useSub';
4
+ import _useEager from './useEager';
4
5
  export declare const JasonProvider: ({ reducers, middleware, extraActions, children }: {
5
6
  reducers?: any;
6
7
  middleware?: any;
@@ -9,3 +10,4 @@ export declare const JasonProvider: ({ reducers, middleware, extraActions, child
9
10
  }) => JSX.Element;
10
11
  export declare const useAct: typeof _useAct;
11
12
  export declare const useSub: typeof _useSub;
13
+ export declare const useEager: typeof _useEager;
data/client/lib/index.js CHANGED
@@ -3,10 +3,12 @@ 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
- exports.useSub = exports.useAct = exports.JasonProvider = void 0;
6
+ exports.useEager = exports.useSub = exports.useAct = exports.JasonProvider = void 0;
7
7
  const JasonProvider_1 = __importDefault(require("./JasonProvider"));
8
8
  const useAct_1 = __importDefault(require("./useAct"));
9
9
  const useSub_1 = __importDefault(require("./useSub"));
10
+ const useEager_1 = __importDefault(require("./useEager"));
10
11
  exports.JasonProvider = JasonProvider_1.default;
11
12
  exports.useAct = useAct_1.default;
12
13
  exports.useSub = useSub_1.default;
14
+ exports.useEager = useEager_1.default;
@@ -40,10 +40,10 @@ function default_1(schema) {
40
40
  }
41
41
  function useEager(entity, id = null, relations = []) {
42
42
  if (id) {
43
- return react_redux_1.useSelector(s => addRelations(s, Object.assign({}, s[entity].entities[String(id)]), entity, relations));
43
+ return react_redux_1.useSelector(s => addRelations(s, Object.assign({}, s[entity].entities[String(id)]), entity, relations), lodash_1.default.isEqual);
44
44
  }
45
45
  else {
46
- return react_redux_1.useSelector(s => addRelations(s, lodash_1.default.values(s[entity].entities), entity, relations));
46
+ return react_redux_1.useSelector(s => addRelations(s, lodash_1.default.values(s[entity].entities), entity, relations), lodash_1.default.isEqual);
47
47
  }
48
48
  }
49
49
  return useEager;
@@ -6,20 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const lodash_1 = __importDefault(require("lodash"));
7
7
  const pluralize_1 = __importDefault(require("pluralize"));
8
8
  const pruneIdsMiddleware = schema => store => next => action => {
9
- const { type } = action;
9
+ const { type, payload } = action;
10
10
  const result = next(action);
11
11
  const state = store.getState();
12
- if (type === 'jasonModels/setSubscriptionIds') {
13
- // Check every model
14
- lodash_1.default.map(lodash_1.default.keys(schema), model => {
15
- let ids = [];
16
- lodash_1.default.map(state.jasonModels[model], (subscribedIds, k) => {
17
- ids = lodash_1.default.union(ids, subscribedIds);
18
- });
19
- // Find IDs currently in Redux that aren't in any subscription
20
- const idsToRemove = lodash_1.default.difference(state[pluralize_1.default(model)].ids, ids);
21
- store.dispatch({ type: `${pluralize_1.default(model)}/removeMany`, payload: idsToRemove });
12
+ if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionId') {
13
+ const { model, ids } = payload;
14
+ let idsInSubs = [];
15
+ lodash_1.default.map(state.jasonModels[model], (subscribedIds, k) => {
16
+ idsInSubs = lodash_1.default.union(idsInSubs, subscribedIds);
22
17
  });
18
+ // Find IDs currently in Redux that aren't in any subscription
19
+ const idsToRemove = lodash_1.default.difference(state[pluralize_1.default(model)].ids, idsInSubs);
20
+ store.dispatch({ type: `${pluralize_1.default(model)}/removeMany`, payload: idsToRemove });
23
21
  }
24
22
  return result;
25
23
  };
@@ -0,0 +1,5 @@
1
+ export default function actionCableAdapter(jasonConfig: any, handlePayload: any, dispatch: any, onConnected: any): {
2
+ getPayload: (config: any, options: any) => void;
3
+ createSubscription: (config: any) => void;
4
+ removeSubscription: (config: any) => void;
5
+ };
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const actioncable_1 = require("@rails/actioncable");
4
+ function actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnected) {
5
+ const consumer = actioncable_1.createConsumer();
6
+ const subscription = (consumer.subscriptions.create({
7
+ channel: 'Jason::Channel'
8
+ }, {
9
+ connected: () => {
10
+ dispatch({ type: 'jason/upsert', payload: { connected: true } });
11
+ console.debug('Connected to ActionCable');
12
+ // When AC loses connection - all state is lost, so we need to re-initialize all subscriptions
13
+ onConnected();
14
+ },
15
+ received: payload => {
16
+ handlePayload(payload);
17
+ console.debug("ActionCable Payload received: ", payload);
18
+ },
19
+ disconnected: () => {
20
+ dispatch({ type: 'jason/upsert', payload: { connected: false } });
21
+ console.warn('Disconnected from ActionCable');
22
+ }
23
+ }));
24
+ function getPayload(config, options) {
25
+ subscription.send(Object.assign({ getPayload: config }, options));
26
+ }
27
+ function createSubscription(config) {
28
+ subscription.send({ createSubscription: config });
29
+ }
30
+ function removeSubscription(config) {
31
+ subscription.send({ removeSubscription: config });
32
+ }
33
+ return { getPayload, createSubscription, removeSubscription };
34
+ }
35
+ exports.default = actionCableAdapter;