jason-rails 0.5.1 → 0.6.4

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 (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;