jason-rails 0.4.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66c465280dc28e42bdd02b400ef514408a4839df70240aad2d660fc7b7dc1ed2
4
- data.tar.gz: 0ae40af767936631bc8ab174682073a2ad860578ad363bdab7b7080116be2e34
3
+ metadata.gz: dae8eb61f7d3966c7e34ed81120a16e5c58f84d50ff6ea16ec87340d52cbf0ec
4
+ data.tar.gz: cd4bc1d677ec413e2368c235632410e744ab3d47f94e7c65160b7665d3a6db45
5
5
  SHA512:
6
- metadata.gz: 6e27560fdd5953ef4a86cc371577b1e8e4f88d3e8f9099abd3f6fd41ad234071a348f67236c24106345acb700ffc16ed99d0036811e8d2bb29914462d89577cb
7
- data.tar.gz: 8deefbe678cebd020e5439ed3264f5b016d3c066d1d24baf3c9146ee32cd8404a6ef2c6e9bda350f276f4d1775ec3a50a9c5570960228c1f253b7adaae45f7ed
6
+ metadata.gz: 88130bf9bd8d557478de06bcdcb8901799425e3eeaf10a47a969152c13334659c8ffe36911fa2f3c18903a81c4291118e5f0c6daa72ac7a20545c095ab19f918
7
+ data.tar.gz: 3e708e8b214b74cce9dc87ec07b04bf5a4d1453c7398050be657dfe0c34f7668708352d7bef7d9305ffbb1321728267cbab9ab61f03460af3f7987f7d0381253
data/.gitignore CHANGED
@@ -11,4 +11,7 @@
11
11
  .rspec_status
12
12
  client/node_modules/
13
13
 
14
- client/yarn-error.log
14
+ client/yarn-error.log
15
+ *.log
16
+ *.gem
17
+ .DS_Store
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.2
data/Gemfile.lock CHANGED
@@ -1,13 +1,137 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jason-rails (0.3.0)
4
+ jason-rails (0.6.0)
5
+ connection_pool (>= 2.2.3)
6
+ jsondiff
7
+ rails (>= 5)
8
+ redis (>= 4)
5
9
 
6
10
  GEM
7
11
  remote: https://rubygems.org/
8
12
  specs:
13
+ actioncable (6.1.1)
14
+ actionpack (= 6.1.1)
15
+ activesupport (= 6.1.1)
16
+ nio4r (~> 2.0)
17
+ websocket-driver (>= 0.6.1)
18
+ actionmailbox (6.1.1)
19
+ actionpack (= 6.1.1)
20
+ activejob (= 6.1.1)
21
+ activerecord (= 6.1.1)
22
+ activestorage (= 6.1.1)
23
+ activesupport (= 6.1.1)
24
+ mail (>= 2.7.1)
25
+ actionmailer (6.1.1)
26
+ actionpack (= 6.1.1)
27
+ actionview (= 6.1.1)
28
+ activejob (= 6.1.1)
29
+ activesupport (= 6.1.1)
30
+ mail (~> 2.5, >= 2.5.4)
31
+ rails-dom-testing (~> 2.0)
32
+ actionpack (6.1.1)
33
+ actionview (= 6.1.1)
34
+ activesupport (= 6.1.1)
35
+ rack (~> 2.0, >= 2.0.9)
36
+ rack-test (>= 0.6.3)
37
+ rails-dom-testing (~> 2.0)
38
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
39
+ actiontext (6.1.1)
40
+ actionpack (= 6.1.1)
41
+ activerecord (= 6.1.1)
42
+ activestorage (= 6.1.1)
43
+ activesupport (= 6.1.1)
44
+ nokogiri (>= 1.8.5)
45
+ actionview (6.1.1)
46
+ activesupport (= 6.1.1)
47
+ builder (~> 3.1)
48
+ erubi (~> 1.4)
49
+ rails-dom-testing (~> 2.0)
50
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
51
+ activejob (6.1.1)
52
+ activesupport (= 6.1.1)
53
+ globalid (>= 0.3.6)
54
+ activemodel (6.1.1)
55
+ activesupport (= 6.1.1)
56
+ activerecord (6.1.1)
57
+ activemodel (= 6.1.1)
58
+ activesupport (= 6.1.1)
59
+ activestorage (6.1.1)
60
+ actionpack (= 6.1.1)
61
+ activejob (= 6.1.1)
62
+ activerecord (= 6.1.1)
63
+ activesupport (= 6.1.1)
64
+ marcel (~> 0.3.1)
65
+ mimemagic (~> 0.3.2)
66
+ activesupport (6.1.1)
67
+ concurrent-ruby (~> 1.0, >= 1.0.2)
68
+ i18n (>= 1.6, < 2)
69
+ minitest (>= 5.1)
70
+ tzinfo (~> 2.0)
71
+ zeitwerk (~> 2.3)
72
+ builder (3.2.4)
73
+ coderay (1.1.3)
74
+ concurrent-ruby (1.1.7)
75
+ connection_pool (2.2.3)
76
+ crass (1.0.6)
9
77
  diff-lcs (1.4.4)
78
+ erubi (1.10.0)
79
+ globalid (0.4.2)
80
+ activesupport (>= 4.2.0)
81
+ i18n (1.8.7)
82
+ concurrent-ruby (~> 1.0)
83
+ jsondiff (0.0.5)
84
+ loofah (2.8.0)
85
+ crass (~> 1.0.2)
86
+ nokogiri (>= 1.5.9)
87
+ mail (2.7.1)
88
+ mini_mime (>= 0.1.1)
89
+ marcel (0.3.3)
90
+ mimemagic (~> 0.3.2)
91
+ method_source (1.0.0)
92
+ mimemagic (0.3.5)
93
+ mini_mime (1.0.2)
94
+ mini_portile2 (2.5.0)
95
+ minitest (5.14.3)
96
+ nio4r (2.5.4)
97
+ nokogiri (1.11.1)
98
+ mini_portile2 (~> 2.5.0)
99
+ racc (~> 1.4)
100
+ pry (0.13.1)
101
+ coderay (~> 1.1)
102
+ method_source (~> 1.0)
103
+ racc (1.5.2)
104
+ rack (2.2.3)
105
+ rack-test (1.1.0)
106
+ rack (>= 1.0, < 3)
107
+ rails (6.1.1)
108
+ actioncable (= 6.1.1)
109
+ actionmailbox (= 6.1.1)
110
+ actionmailer (= 6.1.1)
111
+ actionpack (= 6.1.1)
112
+ actiontext (= 6.1.1)
113
+ actionview (= 6.1.1)
114
+ activejob (= 6.1.1)
115
+ activemodel (= 6.1.1)
116
+ activerecord (= 6.1.1)
117
+ activestorage (= 6.1.1)
118
+ activesupport (= 6.1.1)
119
+ bundler (>= 1.15.0)
120
+ railties (= 6.1.1)
121
+ sprockets-rails (>= 2.0.0)
122
+ rails-dom-testing (2.0.3)
123
+ activesupport (>= 4.2.0)
124
+ nokogiri (>= 1.6)
125
+ rails-html-sanitizer (1.3.0)
126
+ loofah (~> 2.3)
127
+ railties (6.1.1)
128
+ actionpack (= 6.1.1)
129
+ activesupport (= 6.1.1)
130
+ method_source
131
+ rake (>= 0.8.7)
132
+ thor (~> 1.0)
10
133
  rake (12.3.3)
134
+ redis (4.2.5)
11
135
  rspec (3.10.0)
12
136
  rspec-core (~> 3.10.0)
13
137
  rspec-expectations (~> 3.10.0)
@@ -20,15 +144,41 @@ GEM
20
144
  rspec-mocks (3.10.0)
21
145
  diff-lcs (>= 1.2.0, < 2.0)
22
146
  rspec-support (~> 3.10.0)
147
+ rspec-rails (4.0.2)
148
+ actionpack (>= 4.2)
149
+ activesupport (>= 4.2)
150
+ railties (>= 4.2)
151
+ rspec-core (~> 3.10)
152
+ rspec-expectations (~> 3.10)
153
+ rspec-mocks (~> 3.10)
154
+ rspec-support (~> 3.10)
23
155
  rspec-support (3.10.0)
156
+ sprockets (4.0.2)
157
+ concurrent-ruby (~> 1.0)
158
+ rack (> 1, < 3)
159
+ sprockets-rails (3.2.2)
160
+ actionpack (>= 4.0)
161
+ activesupport (>= 4.0)
162
+ sprockets (>= 3.0.0)
163
+ sqlite3 (1.4.2)
164
+ thor (1.0.1)
165
+ tzinfo (2.0.4)
166
+ concurrent-ruby (~> 1.0)
167
+ websocket-driver (0.7.3)
168
+ websocket-extensions (>= 0.1.0)
169
+ websocket-extensions (0.1.5)
170
+ zeitwerk (2.4.2)
24
171
 
25
172
  PLATFORMS
26
173
  ruby
27
174
 
28
175
  DEPENDENCIES
29
176
  jason-rails!
177
+ pry
30
178
  rake (~> 12.0)
31
179
  rspec (~> 3.0)
180
+ rspec-rails
181
+ sqlite3
32
182
 
33
183
  BUNDLED WITH
34
- 1.17.3
184
+ 2.1.4
data/README.md CHANGED
@@ -1,10 +1,12 @@
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!
4
+
3
5
  ## The goal
4
6
 
5
7
  I wanted:
6
8
  - Automatic updates to client state based on database state
7
- - Automatic persistence to database
9
+ - Persistence to the database without many layers of passing parameters
8
10
  - Redux for awesome state management
9
11
  - Optimistic updates
10
12
 
@@ -24,22 +26,132 @@ gem 'jason-rails'
24
26
  yarn add @jamesr2323/jason
25
27
  ```
26
28
 
29
+ You will also need have peer dependencies of `redux`, `react-redux` and `@reduxjs/toolkit`.
30
+
31
+ ### In Rails
32
+
33
+ Include the module `Jason::Publisher` in all models you want to publish via Jason.
34
+
35
+ Create a new initializer e.g. `jason.rb` which defines your schema
36
+
37
+ ```ruby
38
+ Jason.setup do |config|
39
+ config.schema = {
40
+ post: {
41
+ subscribed_fields: [:id, :name]
42
+ },
43
+ comment: {
44
+ subscribed_fields: [:id]
45
+ },
46
+ user: {
47
+ subscribed_fields: [:id]
48
+ }
49
+ }
50
+ end
51
+ ```
52
+
53
+ ### In your frontend code
54
+
55
+ First you need to wrap your root component in a `JasonProvider`.
56
+
57
+ ```jsx
58
+ import { JasonProvider } from '@jamesr2323/jason'
59
+
60
+ return <JasonProvider>
61
+ <YourApp />
62
+ </JasonProvider>
63
+ ```
64
+
65
+ This is a wrapper around `react-redux` Provider component. This accepts the following props (all optional):
66
+
67
+ - `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
+ - `extraActions` - Extra actions you want to be available via the `useAct` hook. (See below)
69
+ 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
+
71
+ ```js
72
+ function extraActions(dispatch, store, restClient, act) {
73
+ return {
74
+ local: {
75
+ upsert: payload => dis({ type: 'local/upsert', payload })
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ - `middleware` - Passed directly to `configureStore` with additional Jason middleware
82
+
27
83
  ## Usage
84
+ Jason provides two custom hooks to access functionality.
85
+
86
+ ### useAct
87
+ 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.
88
+
89
+ Example
90
+ ```jsx
91
+ import React, { useState } from 'react'
92
+ import { useAct } from '@jamesr2323/jason'
28
93
 
29
- ### Define your schema
94
+ export default function PostCreator() {
95
+ const act = useAct()
96
+ const [name, setName] = useState('')
30
97
 
98
+ function handleClick() {
99
+ act.posts.add({ name })
100
+ }
101
+
102
+ return <div>
103
+ <input value={name} onChange={e => setName(e.target.value)} />
104
+ <button onClick={handleClick}>Add</button>
105
+ </div>
106
+ }
107
+ ```
108
+
109
+ ### useSub
110
+ This subscribes your Redux store to a model or set of models. It will automatically unsubscribe when the component unmounts.
111
+
112
+ Example
113
+ ```jsx
114
+ import React from 'react'
115
+ import { useSelector } from 'react-redux'
116
+ import { useSub } from '@jamesr2323/jason'
117
+ import _ from 'lodash'
118
+
119
+ export default function PostsList() {
120
+ useSub({ model: 'post', includes: ['comments'] })
121
+ const posts = useSelector(s => _.values(s.posts.entities))
122
+
123
+ return <div>
124
+ { posts.map(({ id, name }) => <div key={id}>{ name }</div>) }
125
+ </div>
126
+ }
127
+ ```
128
+
129
+ ## Authorization
130
+
131
+ By default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access.
132
+
133
+ ### Authorizing subscriptions
134
+ 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.
135
+
136
+ ### Authorizing updates
137
+ 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.
138
+
139
+ ## Roadmap
140
+
141
+ Development is primarily driven by the needs of projects we're using Jason in. In no particular order, being considered is:
142
+ - Failure handling - rolling back local state in case of an error on the server
143
+ - 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.
144
+ - 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)
145
+ - Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
31
146
 
32
147
  ## Development
33
148
 
34
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
35
149
 
36
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
150
 
38
151
  ## Contributing
39
152
 
40
153
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jason. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/jason/blob/master/CODE_OF_CONDUCT.md).
41
154
 
42
-
43
155
  ## License
44
156
 
45
157
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -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_API_MODEL.to_json
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
@@ -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
@@ -1,2 +1,7 @@
1
- declare const context: any;
1
+ /// <reference types="react" />
2
+ declare const context: import("react").Context<{
3
+ actions: any;
4
+ subscribe: null;
5
+ eager: (entity: any, id: any, relations: any) => void;
6
+ }>;
2
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;