jason-rails 0.6.8 → 0.7.5
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.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +34 -0
 - data/Gemfile.lock +5 -3
 - data/README.md +20 -14
 - data/app/controllers/jason/jason_controller.rb +26 -4
 - data/app/workers/jason/outbound_message_queue_worker.rb +1 -1
 - data/client/lib/JasonProvider.d.ts +3 -1
 - data/client/lib/JasonProvider.js +2 -2
 - data/client/lib/addRelations.d.ts +1 -0
 - data/client/lib/addRelations.js +39 -0
 - data/client/lib/createJasonReducers.js +4 -2
 - data/client/lib/createOptDis.d.ts +1 -1
 - data/client/lib/createOptDis.js +9 -8
 - data/client/lib/createServerActionQueue.d.ts +3 -2
 - data/client/lib/createServerActionQueue.js +32 -6
 - data/client/lib/createServerActionQueue.test.js +61 -6
 - data/client/lib/createThenable.d.ts +1 -0
 - data/client/lib/createThenable.js +5 -0
 - data/client/lib/createTransportAdapter.d.ts +1 -1
 - data/client/lib/createTransportAdapter.js +2 -2
 - data/client/lib/index.d.ts +8 -1
 - data/client/lib/index.js +3 -1
 - data/client/lib/transportAdapters/actionCableAdapter.d.ts +1 -1
 - data/client/lib/transportAdapters/actionCableAdapter.js +27 -6
 - data/client/lib/transportAdapters/pusherAdapter.js +1 -1
 - data/client/lib/useDraft.d.ts +1 -0
 - data/client/lib/useDraft.js +13 -0
 - data/client/lib/useEager.d.ts +1 -1
 - data/client/lib/useEager.js +10 -5
 - data/client/lib/useJason.d.ts +3 -1
 - data/client/lib/useJason.js +4 -7
 - data/client/package.json +1 -1
 - data/client/src/JasonProvider.tsx +2 -2
 - data/client/src/addRelations.ts +33 -0
 - data/client/src/createJasonReducers.ts +4 -2
 - data/client/src/createOptDis.ts +10 -8
 - data/client/src/createServerActionQueue.test.ts +60 -6
 - data/client/src/createServerActionQueue.ts +41 -6
 - data/client/src/createTransportAdapter.ts +2 -2
 - data/client/src/index.ts +2 -0
 - data/client/src/transportAdapters/actionCableAdapter.ts +28 -7
 - data/client/src/transportAdapters/pusherAdapter.ts +1 -2
 - data/client/src/useDraft.ts +17 -0
 - data/client/src/useEager.ts +9 -6
 - data/client/src/useJason.ts +10 -7
 - data/lib/jason.rb +9 -2
 - data/lib/jason/api_model.rb +0 -4
 - data/lib/jason/channel.rb +0 -7
 - data/lib/jason/conditions_matcher.rb +88 -0
 - data/lib/jason/consistency_checker.rb +65 -0
 - data/lib/jason/graph_helper.rb +4 -0
 - data/lib/jason/publisher.rb +36 -36
 - data/lib/jason/subscription.rb +51 -15
 - data/lib/jason/version.rb +1 -1
 - metadata +12 -5
 - data/client/src/makeEager.ts +0 -46
 - data/lib/jason/publisher_old.rb +0 -112
 - data/lib/jason/subscription_old.rb +0 -171
 
| 
         @@ -1,31 +1,65 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            // A FIFO queue with deduping of actions whose effect will be cancelled by later actions
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
      
 2 
     | 
    
         
            +
            import { v4 as uuidv4 } from 'uuid'
         
     | 
| 
       3 
3 
     | 
    
         
             
            import _ from 'lodash'
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
      
 5 
     | 
    
         
            +
            class Deferred {
         
     | 
| 
      
 6 
     | 
    
         
            +
              promise: Promise<any>;
         
     | 
| 
      
 7 
     | 
    
         
            +
              resolve: any;
         
     | 
| 
      
 8 
     | 
    
         
            +
              reject: any;
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              constructor() {
         
     | 
| 
      
 11 
     | 
    
         
            +
                this.promise = new Promise((resolve, reject)=> {
         
     | 
| 
      
 12 
     | 
    
         
            +
                  this.reject = reject
         
     | 
| 
      
 13 
     | 
    
         
            +
                  this.resolve = resolve
         
     | 
| 
      
 14 
     | 
    
         
            +
                })
         
     | 
| 
      
 15 
     | 
    
         
            +
              }
         
     | 
| 
      
 16 
     | 
    
         
            +
            }
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       5 
18 
     | 
    
         
             
            export default function createServerActionQueue() {
         
     | 
| 
       6 
19 
     | 
    
         
             
              const queue: any[] = []
         
     | 
| 
      
 20 
     | 
    
         
            +
              const deferreds = {}
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
       7 
22 
     | 
    
         
             
              let inFlight = false
         
     | 
| 
       8 
23 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
              function addItem( 
     | 
| 
      
 24 
     | 
    
         
            +
              function addItem(action) {
         
     | 
| 
       10 
25 
     | 
    
         
             
                // Check if there are any items ahead in the queue that this item would effectively overwrite.
         
     | 
| 
       11 
26 
     | 
    
         
             
                // In that case we can remove them
         
     | 
| 
       12 
27 
     | 
    
         
             
                // If this is an upsert && item ID is the same && current item attributes are a superset of the earlier item attributes
         
     | 
| 
       13 
     | 
    
         
            -
                const { type, payload } =  
     | 
| 
      
 28 
     | 
    
         
            +
                const { type, payload } = action
         
     | 
| 
      
 29 
     | 
    
         
            +
                const id = uuidv4()
         
     | 
| 
      
 30 
     | 
    
         
            +
                const dfd = new Deferred()
         
     | 
| 
      
 31 
     | 
    
         
            +
                deferreds[id] = [dfd]
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                const item = { id, action }
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
       14 
35 
     | 
    
         
             
                if (type.split('/')[1] !== 'upsert') {
         
     | 
| 
       15 
36 
     | 
    
         
             
                  queue.push(item)
         
     | 
| 
       16 
     | 
    
         
            -
                  return
         
     | 
| 
      
 37 
     | 
    
         
            +
                  return dfd.promise
         
     | 
| 
       17 
38 
     | 
    
         
             
                }
         
     | 
| 
       18 
39 
     | 
    
         | 
| 
       19 
40 
     | 
    
         
             
                _.remove(queue, item => {
         
     | 
| 
       20 
     | 
    
         
            -
                  const { type: itemType, payload: itemPayload } = item
         
     | 
| 
      
 41 
     | 
    
         
            +
                  const { type: itemType, payload: itemPayload } = item.action
         
     | 
| 
       21 
42 
     | 
    
         
             
                  if (type !== itemType) return false
         
     | 
| 
       22 
43 
     | 
    
         
             
                  if (itemPayload.id !== payload.id) return false
         
     | 
| 
       23 
44 
     | 
    
         | 
| 
       24 
45 
     | 
    
         
             
                  // Check that all keys of itemPayload are in payload.
         
     | 
| 
      
 46 
     | 
    
         
            +
                  deferreds[id].push(...deferreds[item.id])
         
     | 
| 
       25 
47 
     | 
    
         
             
                  return _.difference(_.keys(itemPayload),_.keys(payload)).length === 0
         
     | 
| 
       26 
48 
     | 
    
         
             
                })
         
     | 
| 
       27 
49 
     | 
    
         | 
| 
       28 
50 
     | 
    
         
             
                queue.push(item)
         
     | 
| 
      
 51 
     | 
    
         
            +
                return dfd.promise
         
     | 
| 
      
 52 
     | 
    
         
            +
              }
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
              function itemProcessed(id, data?: any) {
         
     | 
| 
      
 55 
     | 
    
         
            +
                inFlight = false
         
     | 
| 
      
 56 
     | 
    
         
            +
                deferreds[id].forEach(dfd => dfd.resolve(data))
         
     | 
| 
      
 57 
     | 
    
         
            +
              }
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
              function itemFailed(id, error?: any) {
         
     | 
| 
      
 60 
     | 
    
         
            +
                queue.length = 0
         
     | 
| 
      
 61 
     | 
    
         
            +
                deferreds[id].forEach(dfd => dfd.reject(error))
         
     | 
| 
      
 62 
     | 
    
         
            +
                inFlight = false
         
     | 
| 
       29 
63 
     | 
    
         
             
              }
         
     | 
| 
       30 
64 
     | 
    
         | 
| 
       31 
65 
     | 
    
         
             
              return {
         
     | 
| 
         @@ -40,7 +74,8 @@ export default function createServerActionQueue() { 
     | 
|
| 
       40 
74 
     | 
    
         
             
                  }
         
     | 
| 
       41 
75 
     | 
    
         
             
                  return false
         
     | 
| 
       42 
76 
     | 
    
         
             
                },
         
     | 
| 
       43 
     | 
    
         
            -
                itemProcessed 
     | 
| 
      
 77 
     | 
    
         
            +
                itemProcessed,
         
     | 
| 
      
 78 
     | 
    
         
            +
                itemFailed,
         
     | 
| 
       44 
79 
     | 
    
         
             
                fullySynced: () => queue.length === 0 && !inFlight,
         
     | 
| 
       45 
80 
     | 
    
         
             
                getData: () => ({ queue, inFlight })
         
     | 
| 
       46 
81 
     | 
    
         
             
              }
         
     | 
| 
         @@ -1,10 +1,10 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import actionCableAdapter from './transportAdapters/actionCableAdapter'
         
     | 
| 
       2 
2 
     | 
    
         
             
            import pusherAdapter from './transportAdapters/pusherAdapter'
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
            export default function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect) {
         
     | 
| 
      
 4 
     | 
    
         
            +
            export default function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect, transportOptions) {
         
     | 
| 
       5 
5 
     | 
    
         
             
              const { transportService } = jasonConfig
         
     | 
| 
       6 
6 
     | 
    
         
             
              if (transportService === 'action_cable') {
         
     | 
| 
       7 
     | 
    
         
            -
                return actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnect)
         
     | 
| 
      
 7 
     | 
    
         
            +
                return actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnect, transportOptions)
         
     | 
| 
       8 
8 
     | 
    
         
             
              } else if (transportService === 'pusher') {
         
     | 
| 
       9 
9 
     | 
    
         
             
                return pusherAdapter(jasonConfig, handlePayload, dispatch)
         
     | 
| 
       10 
10 
     | 
    
         
             
              } else {
         
     | 
    
        data/client/src/index.ts
    CHANGED
    
    | 
         @@ -1,8 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import _JasonContext from './JasonContext'
         
     | 
| 
       1 
2 
     | 
    
         
             
            import _JasonProvider from './JasonProvider'
         
     | 
| 
       2 
3 
     | 
    
         
             
            import _useAct from './useAct'
         
     | 
| 
       3 
4 
     | 
    
         
             
            import _useSub from './useSub'
         
     | 
| 
       4 
5 
     | 
    
         
             
            import _useEager from './useEager'
         
     | 
| 
       5 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
            export const JasonContext = _JasonContext
         
     | 
| 
       6 
8 
     | 
    
         
             
            export const JasonProvider = _JasonProvider
         
     | 
| 
       7 
9 
     | 
    
         
             
            export const useAct = _useAct
         
     | 
| 
       8 
10 
     | 
    
         
             
            export const useSub = _useSub
         
     | 
| 
         @@ -1,7 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import { createConsumer } from "@rails/actioncable"
         
     | 
| 
      
 2 
     | 
    
         
            +
            import restClient from '../restClient'
         
     | 
| 
      
 3 
     | 
    
         
            +
            import { v4 as uuidv4 } from 'uuid'
         
     | 
| 
      
 4 
     | 
    
         
            +
            import _ from 'lodash'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            export default function actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnected, transportOptions) {
         
     | 
| 
      
 7 
     | 
    
         
            +
              const consumerId = uuidv4()
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              const { cableUrl } = transportOptions
         
     | 
| 
      
 10 
     | 
    
         
            +
              const consumer = cableUrl ? createConsumer(cableUrl) : createConsumer()
         
     | 
| 
       2 
11 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            export default function actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnected) {
         
     | 
| 
       4 
     | 
    
         
            -
              const consumer = createConsumer()
         
     | 
| 
       5 
12 
     | 
    
         
             
              const subscription = (consumer.subscriptions.create({
         
     | 
| 
       6 
13 
     | 
    
         
             
                channel: 'Jason::Channel'
         
     | 
| 
       7 
14 
     | 
    
         
             
              }, {
         
     | 
| 
         @@ -22,16 +29,30 @@ export default function actionCableAdapter(jasonConfig, handlePayload, dispatch, 
     | 
|
| 
       22 
29 
     | 
    
         
             
                }
         
     | 
| 
       23 
30 
     | 
    
         
             
              }));
         
     | 
| 
       24 
31 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
              function getPayload(config, options) {
         
     | 
| 
       26 
     | 
    
         
            -
                subscription.send({ getPayload: config, ...options })
         
     | 
| 
       27 
     | 
    
         
            -
              }
         
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
32 
     | 
    
         
             
              function createSubscription(config) {
         
     | 
| 
       30 
33 
     | 
    
         
             
                subscription.send({ createSubscription: config })
         
     | 
| 
       31 
34 
     | 
    
         
             
              }
         
     | 
| 
       32 
35 
     | 
    
         | 
| 
       33 
36 
     | 
    
         
             
              function removeSubscription(config) {
         
     | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
      
 37 
     | 
    
         
            +
                restClient.post('/jason/api/remove_subscription', { config, consumerId })
         
     | 
| 
      
 38 
     | 
    
         
            +
                .catch(e => console.error(e))
         
     | 
| 
      
 39 
     | 
    
         
            +
              }
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              function getPayload(config, options) {
         
     | 
| 
      
 42 
     | 
    
         
            +
                restClient.post('/jason/api/get_payload', {
         
     | 
| 
      
 43 
     | 
    
         
            +
                  config,
         
     | 
| 
      
 44 
     | 
    
         
            +
                  options
         
     | 
| 
      
 45 
     | 
    
         
            +
                })
         
     | 
| 
      
 46 
     | 
    
         
            +
                .then(({ data }) => {
         
     | 
| 
      
 47 
     | 
    
         
            +
                  _.map(data, (payload, modelName) => {
         
     | 
| 
      
 48 
     | 
    
         
            +
                    handlePayload(payload)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  })
         
     | 
| 
      
 50 
     | 
    
         
            +
                })
         
     | 
| 
      
 51 
     | 
    
         
            +
                .catch(e => console.error(e))
         
     | 
| 
      
 52 
     | 
    
         
            +
              }
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
              function fullChannelName(channelName) {
         
     | 
| 
      
 55 
     | 
    
         
            +
                return channelName
         
     | 
| 
       35 
56 
     | 
    
         
             
              }
         
     | 
| 
       36 
57 
     | 
    
         | 
| 
       37 
58 
     | 
    
         
             
              return { getPayload, createSubscription, removeSubscription }
         
     | 
| 
         @@ -1,11 +1,10 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import Pusher from 'pusher-js'
         
     | 
| 
       2 
     | 
    
         
            -
            import { createConsumer } from "@rails/actioncable"
         
     | 
| 
       3 
2 
     | 
    
         
             
            import restClient from '../restClient'
         
     | 
| 
       4 
3 
     | 
    
         
             
            import { v4 as uuidv4 } from 'uuid'
         
     | 
| 
       5 
4 
     | 
    
         
             
            import _ from 'lodash'
         
     | 
| 
       6 
5 
     | 
    
         | 
| 
       7 
6 
     | 
    
         
             
            export default function pusherAdapter(jasonConfig, handlePayload, dispatch) {
         
     | 
| 
       8 
     | 
    
         
            -
               
     | 
| 
      
 7 
     | 
    
         
            +
              const consumerId = uuidv4()
         
     | 
| 
       9 
8 
     | 
    
         | 
| 
       10 
9 
     | 
    
         
             
              const { pusherKey, pusherRegion, pusherChannelPrefix } = jasonConfig
         
     | 
| 
       11 
10 
     | 
    
         
             
              const pusher = new Pusher(pusherKey, {
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import _ from 'lodash'
         
     | 
| 
      
 2 
     | 
    
         
            +
            import { useSelector } from 'react-redux'
         
     | 
| 
      
 3 
     | 
    
         
            +
            import addRelations from './addRelations'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            /* Can be called as
         
     | 
| 
      
 6 
     | 
    
         
            +
            useDraft() => draft object for making updates
         
     | 
| 
      
 7 
     | 
    
         
            +
            useDraft('entity', id) => returns [draft, object]
         
     | 
| 
      
 8 
     | 
    
         
            +
            useDraft('entity', id, relations) => returns [draft, objectWithEmbeddedRelations]
         
     | 
| 
      
 9 
     | 
    
         
            +
            */
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            export default function useDraft(entity, id, relations = []) {
         
     | 
| 
      
 12 
     | 
    
         
            +
              // const entityDraft =`${entity}Draft`
         
     | 
| 
      
 13 
     | 
    
         
            +
              // const object = { ...s[entityDraft].entities[String(id)] }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              // return useSelector(s => addRelations(s, object, entity, relations, 'Draft'), _.isEqual)
         
     | 
| 
      
 16 
     | 
    
         
            +
            }
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
    
        data/client/src/useEager.ts
    CHANGED
    
    | 
         @@ -1,9 +1,12 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import  
     | 
| 
       2 
     | 
    
         
            -
            import {  
     | 
| 
      
 1 
     | 
    
         
            +
            import _ from 'lodash'
         
     | 
| 
      
 2 
     | 
    
         
            +
            import { useSelector } from 'react-redux'
         
     | 
| 
      
 3 
     | 
    
         
            +
            import addRelations from './addRelations'
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
            export default function useEager(entity, id =  
     | 
| 
       5 
     | 
    
         
            -
               
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
               
     | 
| 
      
 5 
     | 
    
         
            +
            export default function useEager(entity: string, id = '', relations = [] as any) {
         
     | 
| 
      
 6 
     | 
    
         
            +
              if (id) {
         
     | 
| 
      
 7 
     | 
    
         
            +
                return useSelector(s => addRelations(s, { ...s[entity].entities[String(id)] }, entity, relations), _.isEqual)
         
     | 
| 
      
 8 
     | 
    
         
            +
              } else {
         
     | 
| 
      
 9 
     | 
    
         
            +
                return useSelector(s => addRelations(s, _.values(s[entity].entities), entity, relations), _.isEqual)
         
     | 
| 
      
 10 
     | 
    
         
            +
              }
         
     | 
| 
       8 
11 
     | 
    
         
             
            }
         
     | 
| 
       9 
12 
     | 
    
         | 
    
        data/client/src/useJason.ts
    CHANGED
    
    | 
         @@ -9,13 +9,12 @@ import createTransportAdapater from './createTransportAdapter' 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            import { createEntityAdapter, createSlice, createReducer, configureStore } from '@reduxjs/toolkit'
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
            import makeEager from './makeEager'
         
     | 
| 
       13 
12 
     | 
    
         
             
            import { camelizeKeys } from 'humps'
         
     | 
| 
       14 
13 
     | 
    
         
             
            import md5 from 'blueimp-md5'
         
     | 
| 
       15 
14 
     | 
    
         
             
            import _ from 'lodash'
         
     | 
| 
       16 
15 
     | 
    
         
             
            import React, { useState, useEffect } from 'react'
         
     | 
| 
       17 
16 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
            export default function useJason({ reducers, middleware = [], extraActions }: { reducers?: any, middleware?: any[], extraActions?: any }) {
         
     | 
| 
      
 17 
     | 
    
         
            +
            export default function useJason({ reducers, middleware = [], enhancers = [], transportOptions = {}, extraActions }: { reducers?: any, middleware?: any[], enhancers?: any[], extraActions?: any, transportOptions?: any }) {
         
     | 
| 
       19 
18 
     | 
    
         
             
              const [store, setStore] = useState(null as any)
         
     | 
| 
       20 
19 
     | 
    
         
             
              const [value, setValue] = useState(null as any)
         
     | 
| 
       21 
20 
     | 
    
         | 
| 
         @@ -36,12 +35,11 @@ export default function useJason({ reducers, middleware = [], extraActions }: { 
     | 
|
| 
       36 
35 
     | 
    
         | 
| 
       37 
36 
     | 
    
         
             
                  console.debug({ allReducers })
         
     | 
| 
       38 
37 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
                  const store = configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware(schema)] })
         
     | 
| 
      
 38 
     | 
    
         
            +
                  const store = configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware(schema)], enhancers })
         
     | 
| 
       40 
39 
     | 
    
         
             
                  const dispatch = store.dispatch
         
     | 
| 
       41 
40 
     | 
    
         | 
| 
       42 
41 
     | 
    
         
             
                  const optDis = createOptDis(schema, dispatch, restClient, serverActionQueue)
         
     | 
| 
       43 
42 
     | 
    
         
             
                  const actions = createActions(schema, store, restClient, optDis, extraActions)
         
     | 
| 
       44 
     | 
    
         
            -
                  const eager = makeEager(schema)
         
     | 
| 
       45 
43 
     | 
    
         | 
| 
       46 
44 
     | 
    
         
             
                  let payloadHandlers = {}
         
     | 
| 
       47 
45 
     | 
    
         
             
                  let configs = {}
         
     | 
| 
         @@ -50,7 +48,7 @@ export default function useJason({ reducers, middleware = [], extraActions }: { 
     | 
|
| 
       50 
48 
     | 
    
         
             
                  function handlePayload(payload) {
         
     | 
| 
       51 
49 
     | 
    
         
             
                    const { md5Hash } = payload
         
     | 
| 
       52 
50 
     | 
    
         | 
| 
       53 
     | 
    
         
            -
                    const { handlePayload } = payloadHandlers[md5Hash]
         
     | 
| 
      
 51 
     | 
    
         
            +
                    const { handlePayload } = payloadHandlers[md5Hash] || {}
         
     | 
| 
       54 
52 
     | 
    
         
             
                    if (handlePayload) {
         
     | 
| 
       55 
53 
     | 
    
         
             
                      handlePayload(payload)
         
     | 
| 
       56 
54 
     | 
    
         
             
                    } else {
         
     | 
| 
         @@ -58,7 +56,13 @@ export default function useJason({ reducers, middleware = [], extraActions }: { 
     | 
|
| 
       58 
56 
     | 
    
         
             
                    }
         
     | 
| 
       59 
57 
     | 
    
         
             
                  }
         
     | 
| 
       60 
58 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                  const transportAdapter = createTransportAdapater( 
     | 
| 
      
 59 
     | 
    
         
            +
                  const transportAdapter = createTransportAdapater(
         
     | 
| 
      
 60 
     | 
    
         
            +
                    jasonConfig,
         
     | 
| 
      
 61 
     | 
    
         
            +
                    handlePayload,
         
     | 
| 
      
 62 
     | 
    
         
            +
                    dispatch,
         
     | 
| 
      
 63 
     | 
    
         
            +
                    () => _.keys(configs).forEach(md5Hash => createSubscription(configs[md5Hash], subOptions[md5Hash])),
         
     | 
| 
      
 64 
     | 
    
         
            +
                    transportOptions
         
     | 
| 
      
 65 
     | 
    
         
            +
                  )
         
     | 
| 
       62 
66 
     | 
    
         | 
| 
       63 
67 
     | 
    
         
             
                  function createSubscription(config, options = {}) {
         
     | 
| 
       64 
68 
     | 
    
         
             
                    // We need the hash to be consistent in Ruby / Javascript
         
     | 
| 
         @@ -99,7 +103,6 @@ export default function useJason({ reducers, middleware = [], extraActions }: { 
     | 
|
| 
       99 
103 
     | 
    
         
             
                  setValue({
         
     | 
| 
       100 
104 
     | 
    
         
             
                    actions: actions,
         
     | 
| 
       101 
105 
     | 
    
         
             
                    subscribe: createSubscription,
         
     | 
| 
       102 
     | 
    
         
            -
                    eager,
         
     | 
| 
       103 
106 
     | 
    
         
             
                    handlePayload
         
     | 
| 
       104 
107 
     | 
    
         
             
                  })
         
     | 
| 
       105 
108 
     | 
    
         
             
                  setStore(store)
         
     | 
    
        data/lib/jason.rb
    CHANGED
    
    | 
         @@ -12,6 +12,8 @@ require 'jason/engine' 
     | 
|
| 
       12 
12 
     | 
    
         
             
            require 'jason/lua_generator'
         
     | 
| 
       13 
13 
     | 
    
         
             
            require 'jason/includes_helper'
         
     | 
| 
       14 
14 
     | 
    
         
             
            require 'jason/graph_helper'
         
     | 
| 
      
 15 
     | 
    
         
            +
            require 'jason/conditions_matcher'
         
     | 
| 
      
 16 
     | 
    
         
            +
            require 'jason/consistency_checker'
         
     | 
| 
       15 
17 
     | 
    
         | 
| 
       16 
18 
     | 
    
         
             
            module Jason
         
     | 
| 
       17 
19 
     | 
    
         
             
              class Error < StandardError; end
         
     | 
| 
         @@ -23,7 +25,8 @@ module Jason 
     | 
|
| 
       23 
25 
     | 
    
         
             
              self.mattr_accessor :pusher_key
         
     | 
| 
       24 
26 
     | 
    
         
             
              self.mattr_accessor :pusher_region
         
     | 
| 
       25 
27 
     | 
    
         
             
              self.mattr_accessor :pusher_channel_prefix
         
     | 
| 
       26 
     | 
    
         
            -
              self.mattr_accessor : 
     | 
| 
      
 28 
     | 
    
         
            +
              self.mattr_accessor :subscription_authorization_service
         
     | 
| 
      
 29 
     | 
    
         
            +
              self.mattr_accessor :update_authorization_service
         
     | 
| 
       27 
30 
     | 
    
         
             
              self.mattr_accessor :sidekiq_queue
         
     | 
| 
       28 
31 
     | 
    
         | 
| 
       29 
32 
     | 
    
         
             
              self.schema = {}
         
     | 
| 
         @@ -48,7 +51,11 @@ module Jason 
     | 
|
| 
       48 
51 
     | 
    
         
             
                    puts "Old config was #{previous_schema[model]}"
         
     | 
| 
       49 
52 
     | 
    
         
             
                    puts "New config is #{config}"
         
     | 
| 
       50 
53 
     | 
    
         
             
                    puts "Rebuilding cache for #{model}"
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    # This is necessary to ensure all Rails methods have been added to model before we attempt to cache.
         
     | 
| 
      
 56 
     | 
    
         
            +
                    Rails.configuration.after_initialize do
         
     | 
| 
      
 57 
     | 
    
         
            +
                       model.classify.constantize.cache_all
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
       52 
59 
     | 
    
         
             
                    puts "Done"
         
     | 
| 
       53 
60 
     | 
    
         
             
                  end
         
     | 
| 
       54 
61 
     | 
    
         
             
                end
         
     | 
    
        data/lib/jason/api_model.rb
    CHANGED
    
    | 
         @@ -36,11 +36,7 @@ class Jason::ApiModel 
     | 
|
| 
       36 
36 
     | 
    
         
             
              end
         
     | 
| 
       37 
37 
     | 
    
         | 
| 
       38 
38 
     | 
    
         
             
              def permit(params)
         
     | 
| 
       39 
     | 
    
         
            -
                pp self
         
     | 
| 
       40 
     | 
    
         
            -
                pp params
         
     | 
| 
       41 
39 
     | 
    
         
             
                params = params.require(:payload).permit(allowed_params).tap do |allowed|
         
     | 
| 
       42 
     | 
    
         
            -
                  pp "ALLOWED"
         
     | 
| 
       43 
     | 
    
         
            -
                  pp allowed
         
     | 
| 
       44 
40 
     | 
    
         
             
                  allowed_object_params.each do |key|
         
     | 
| 
       45 
41 
     | 
    
         
             
                    allowed[key] = params[:payload][key].to_unsafe_h if params[:payload][key]
         
     | 
| 
       46 
42 
     | 
    
         
             
                  end
         
     | 
    
        data/lib/jason/channel.rb
    CHANGED
    
    | 
         @@ -12,7 +12,6 @@ class Jason::Channel < ActionCable::Channel::Base 
     | 
|
| 
       12 
12 
     | 
    
         
             
              private
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
              def handle_message(message)
         
     | 
| 
       15 
     | 
    
         
            -
                pp message['createSubscription']
         
     | 
| 
       16 
15 
     | 
    
         
             
                @subscriptions ||= []
         
     | 
| 
       17 
16 
     | 
    
         | 
| 
       18 
17 
     | 
    
         
             
                begin # ActionCable swallows errors in this message - ensure they're output to logs.
         
     | 
| 
         @@ -38,18 +37,12 @@ class Jason::Channel < ActionCable::Channel::Base 
     | 
|
| 
       38 
37 
     | 
    
         | 
| 
       39 
38 
     | 
    
         
             
                subscriptions.push(subscription)
         
     | 
| 
       40 
39 
     | 
    
         
             
                subscription.add_consumer(identifier)
         
     | 
| 
       41 
     | 
    
         
            -
                subscription.get.each do |payload|
         
     | 
| 
       42 
     | 
    
         
            -
                  pp payload
         
     | 
| 
       43 
     | 
    
         
            -
                  transmit(payload) if payload.present?
         
     | 
| 
       44 
     | 
    
         
            -
                end
         
     | 
| 
       45 
40 
     | 
    
         
             
              end
         
     | 
| 
       46 
41 
     | 
    
         | 
| 
       47 
42 
     | 
    
         
             
              def remove_subscription(config)
         
     | 
| 
       48 
43 
     | 
    
         
             
                subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
         
     | 
| 
       49 
44 
     | 
    
         
             
                subscriptions.reject! { |s| s.id == subscription.id }
         
     | 
| 
       50 
45 
     | 
    
         
             
                subscription.remove_consumer(identifier)
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                # TODO Stop streams
         
     | 
| 
       53 
46 
     | 
    
         
             
              end
         
     | 
| 
       54 
47 
     | 
    
         | 
| 
       55 
48 
     | 
    
         
             
              def get_payload(config, force_refresh = false)
         
     | 
| 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Jason::ConditionsMatcher
         
     | 
| 
      
 2 
     | 
    
         
            +
              attr_reader :klass
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              def initialize(klass)
         
     | 
| 
      
 5 
     | 
    
         
            +
                @klass = klass
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              # key, rules = 'post_id', 123
         
     | 
| 
      
 9 
     | 
    
         
            +
              # key, rules = 'post_id', { 'value': [123,C456], 'type': 'between' }
         
     | 
| 
      
 10 
     | 
    
         
            +
              # key, rules = 'post_id', { 'value': [123,456], 'type': 'between', 'not': true }
         
     | 
| 
      
 11 
     | 
    
         
            +
              # key, rules = 'post_id', { 'value': 123, 'type': 'equals', 'not': true }
         
     | 
| 
      
 12 
     | 
    
         
            +
              def test_match(key, rules, previous_changes)
         
     | 
| 
      
 13 
     | 
    
         
            +
                return nil if !previous_changes.keys.include?(key)
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                if rules.is_a?(Hash)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  matches = false
         
     | 
| 
      
 17 
     | 
    
         
            +
                  value = convert_to_datatype(key, rules['value'])
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  if rules['type'] == 'equals'
         
     | 
| 
      
 20 
     | 
    
         
            +
                    matches = previous_changes[key][1] == value
         
     | 
| 
      
 21 
     | 
    
         
            +
                  elsif rules['type'] == 'between'
         
     | 
| 
      
 22 
     | 
    
         
            +
                    matches = (value[0]..value[1]).cover?(previous_changes[key][1])
         
     | 
| 
      
 23 
     | 
    
         
            +
                  else
         
     | 
| 
      
 24 
     | 
    
         
            +
                    raise "Unrecognized rule type #{rules['type']}"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  if rules['not']
         
     | 
| 
      
 28 
     | 
    
         
            +
                    return !matches
         
     | 
| 
      
 29 
     | 
    
         
            +
                  else
         
     | 
| 
      
 30 
     | 
    
         
            +
                    return matches
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                elsif rules.is_a?(Array)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  value = convert_to_datatype(key, rules)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  return previous_changes[key][1].includes?(value)
         
     | 
| 
      
 36 
     | 
    
         
            +
                else
         
     | 
| 
      
 37 
     | 
    
         
            +
                  value = convert_to_datatype(key, rules)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  return previous_changes[key][1] == value
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              # conditions = { 'post_id' => 123, 'created_at' => { 'type' => 'between', 'value' => ['2020-01-01', '2020-01-02'] } }
         
     | 
| 
      
 43 
     | 
    
         
            +
              def apply_conditions(relation, conditions)
         
     | 
| 
      
 44 
     | 
    
         
            +
                conditions.each do |key, rules|
         
     | 
| 
      
 45 
     | 
    
         
            +
                  relation = apply_condition(relation, key, rules)
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                relation
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              private
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
              def apply_condition(relation, key, rules)
         
     | 
| 
      
 54 
     | 
    
         
            +
                if rules.is_a?(Hash)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  value = convert_to_datatype(key, rules['value'])
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  if rules['type'] == 'equals'
         
     | 
| 
      
 58 
     | 
    
         
            +
                    arg = { key => value }
         
     | 
| 
      
 59 
     | 
    
         
            +
                  elsif rules['type'] == 'between'
         
     | 
| 
      
 60 
     | 
    
         
            +
                    arg = { key => value[0]..value[1] }
         
     | 
| 
      
 61 
     | 
    
         
            +
                  else
         
     | 
| 
      
 62 
     | 
    
         
            +
                    raise "Unrecognized rule type #{rules['type']}"
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  if rules['not']
         
     | 
| 
      
 66 
     | 
    
         
            +
                    return relation.where.not(arg)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  else
         
     | 
| 
      
 68 
     | 
    
         
            +
                    return relation.where(arg)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                else
         
     | 
| 
      
 71 
     | 
    
         
            +
                  value = convert_to_datatype(key, rules)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  return relation.where({ key => value })
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              def convert_to_datatype(key, value)
         
     | 
| 
      
 77 
     | 
    
         
            +
                datatype = klass.type_for_attribute(key).type
         
     | 
| 
      
 78 
     | 
    
         
            +
                if datatype == :datetime || datatype == :date
         
     | 
| 
      
 79 
     | 
    
         
            +
                  if value.is_a?(Array)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    value.map { |v| v&.to_datetime }
         
     | 
| 
      
 81 
     | 
    
         
            +
                  else
         
     | 
| 
      
 82 
     | 
    
         
            +
                    value&.to_datetime
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
                else
         
     | 
| 
      
 85 
     | 
    
         
            +
                  value
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,65 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Jason::ConsistencyChecker
         
     | 
| 
      
 2 
     | 
    
         
            +
              attr_reader :subscription
         
     | 
| 
      
 3 
     | 
    
         
            +
              attr_reader :inconsistent
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              def self.check_all(fix: false)
         
     | 
| 
      
 6 
     | 
    
         
            +
                Jason::Subscription.all.each do |sub|
         
     | 
| 
      
 7 
     | 
    
         
            +
                  next if sub.consumer_count == 0
         
     | 
| 
      
 8 
     | 
    
         
            +
                  checker = Jason::ConsistencyChecker.new(sub)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  result = checker.check
         
     | 
| 
      
 10 
     | 
    
         
            +
                  if checker.inconsistent?
         
     | 
| 
      
 11 
     | 
    
         
            +
                    pp sub.config
         
     | 
| 
      
 12 
     | 
    
         
            +
                    pp result
         
     | 
| 
      
 13 
     | 
    
         
            +
                    if fix
         
     | 
| 
      
 14 
     | 
    
         
            +
                      sub.reset!(hard: true)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              def self.fix_all
         
     | 
| 
      
 21 
     | 
    
         
            +
                check_all(fix: true)
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              def wipe_all_subs
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def initialize(subscription)
         
     | 
| 
      
 29 
     | 
    
         
            +
                @subscription = subscription
         
     | 
| 
      
 30 
     | 
    
         
            +
                @inconsistent = false
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              def inconsistent?
         
     | 
| 
      
 34 
     | 
    
         
            +
                inconsistent
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              # Take a subscription, get the current cached payload, and compare it to the data retrieved from the database
         
     | 
| 
      
 38 
     | 
    
         
            +
              def check
         
     | 
| 
      
 39 
     | 
    
         
            +
                cached_payload = subscription.get
         
     | 
| 
      
 40 
     | 
    
         
            +
                edge_set = subscription.load_ids_for_sub_models(subscription.model, nil)
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                result = cached_payload.map do |model_name, data|
         
     | 
| 
      
 43 
     | 
    
         
            +
                  cached_payload_instance_ids = data[:payload].map { |row| row['id'] }
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  model_idx = edge_set[:model_names].index(model_name)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  if model_idx.present?
         
     | 
| 
      
 47 
     | 
    
         
            +
                    edge_set_instance_ids = edge_set[:instance_ids].map { |row| row[model_idx] }
         
     | 
| 
      
 48 
     | 
    
         
            +
                  else
         
     | 
| 
      
 49 
     | 
    
         
            +
                    next
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  missing = edge_set_instance_ids - cached_payload_instance_ids
         
     | 
| 
      
 53 
     | 
    
         
            +
                  intruding = cached_payload_instance_ids - edge_set_instance_ids
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  if missing.present? || intruding.present?
         
     | 
| 
      
 56 
     | 
    
         
            +
                    @inconsistent = true
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  [model_name, {
         
     | 
| 
      
 60 
     | 
    
         
            +
                    'missing' => missing,
         
     | 
| 
      
 61 
     | 
    
         
            +
                    'intruding' => intruding
         
     | 
| 
      
 62 
     | 
    
         
            +
                  }]
         
     | 
| 
      
 63 
     | 
    
         
            +
                end.compact.to_h
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     |