jason-rails 0.4.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.ruby-version +1 -0
- data/Gemfile.lock +152 -2
- data/README.md +117 -5
- data/app/controllers/jason/api/pusher_controller.rb +15 -0
- data/app/controllers/jason/api_controller.rb +44 -2
- data/client/lib/JasonContext.d.ts +6 -1
- data/client/lib/JasonContext.js +4 -1
- data/client/lib/JasonProvider.d.ts +2 -2
- data/client/lib/JasonProvider.js +5 -124
- data/client/lib/createJasonReducers.js +48 -3
- data/client/lib/createOptDis.js +0 -2
- data/client/lib/createPayloadHandler.d.ts +9 -1
- data/client/lib/createPayloadHandler.js +47 -55
- data/client/lib/createServerActionQueue.d.ts +10 -0
- data/client/lib/createServerActionQueue.js +48 -0
- data/client/lib/createServerActionQueue.test.d.ts +1 -0
- data/client/lib/createServerActionQueue.test.js +37 -0
- data/client/lib/createTransportAdapter.d.ts +5 -0
- data/client/lib/createTransportAdapter.js +20 -0
- data/client/lib/index.d.ts +5 -2
- data/client/lib/index.js +3 -1
- data/client/lib/makeEager.js +2 -2
- data/client/lib/pruneIdsMiddleware.d.ts +2 -0
- data/client/lib/pruneIdsMiddleware.js +24 -0
- data/client/lib/restClient.d.ts +2 -0
- data/client/lib/restClient.js +17 -0
- data/client/lib/transportAdapters/actionCableAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/actionCableAdapter.js +35 -0
- data/client/lib/transportAdapters/pusherAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/pusherAdapter.js +68 -0
- data/client/lib/useJason.d.ts +5 -0
- data/client/lib/useJason.js +94 -0
- data/client/lib/useJason.test.d.ts +1 -0
- data/client/lib/useJason.test.js +85 -0
- data/client/lib/useSub.d.ts +1 -1
- data/client/lib/useSub.js +6 -3
- data/client/package.json +5 -3
- data/client/src/JasonContext.ts +4 -1
- data/client/src/JasonProvider.tsx +5 -123
- data/client/src/createJasonReducers.ts +56 -3
- data/client/src/createOptDis.ts +0 -2
- data/client/src/createPayloadHandler.ts +53 -64
- data/client/src/createServerActionQueue.test.ts +42 -0
- data/client/src/createServerActionQueue.ts +47 -0
- data/client/src/createTransportAdapter.ts +13 -0
- data/client/src/index.ts +3 -1
- data/client/src/makeEager.ts +2 -2
- data/client/src/pruneIdsMiddleware.ts +24 -0
- data/client/src/restClient.ts +14 -0
- data/client/src/transportAdapters/actionCableAdapter.ts +38 -0
- data/client/src/transportAdapters/pusherAdapter.ts +72 -0
- data/client/src/useJason.test.ts +87 -0
- data/client/src/useJason.ts +110 -0
- data/client/src/useSub.ts +6 -3
- data/client/yarn.lock +71 -3
- data/config/routes.rb +5 -1
- data/jason-rails.gemspec +4 -0
- data/lib/jason.rb +61 -1
- data/lib/jason/api_model.rb +2 -12
- data/lib/jason/broadcaster.rb +19 -0
- data/lib/jason/channel.rb +50 -21
- data/lib/jason/graph_helper.rb +165 -0
- data/lib/jason/includes_helper.rb +108 -0
- data/lib/jason/lua_generator.rb +71 -0
- data/lib/jason/publisher.rb +82 -37
- data/lib/jason/publisher_old.rb +112 -0
- data/lib/jason/subscription.rb +349 -97
- data/lib/jason/subscription_old.rb +171 -0
- data/lib/jason/version.rb +1 -1
- metadata +80 -3
@@ -0,0 +1,42 @@
|
|
1
|
+
import createServerActionQueue from './createServerActionQueue'
|
2
|
+
|
3
|
+
test('Adding items', () => {
|
4
|
+
const serverActionQueue = createServerActionQueue()
|
5
|
+
serverActionQueue.addItem({ type: 'entity/add', payload: { id: 'abc', attribute: 1 } })
|
6
|
+
const item = serverActionQueue.getItem()
|
7
|
+
expect(item).toStrictEqual({ type: 'entity/add', payload: { id: 'abc', attribute: 1 } })
|
8
|
+
})
|
9
|
+
|
10
|
+
test('Deduping of items that will overwrite each other', () => {
|
11
|
+
const serverActionQueue = createServerActionQueue()
|
12
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 1 } })
|
13
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 2 } })
|
14
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 3 } })
|
15
|
+
|
16
|
+
const item = serverActionQueue.getItem()
|
17
|
+
|
18
|
+
expect(item).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute: 3 } })
|
19
|
+
})
|
20
|
+
|
21
|
+
test('Deduping of items with a superset', () => {
|
22
|
+
const serverActionQueue = createServerActionQueue()
|
23
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 1 } })
|
24
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 2, attribute2: 'test' } })
|
25
|
+
|
26
|
+
const item = serverActionQueue.getItem()
|
27
|
+
|
28
|
+
expect(item).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute: 2, attribute2: 'test' } })
|
29
|
+
})
|
30
|
+
|
31
|
+
test("doesn't dedupe items with some attributes missing", () => {
|
32
|
+
const serverActionQueue = createServerActionQueue()
|
33
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 1 } })
|
34
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute2: 'test' } })
|
35
|
+
|
36
|
+
const item = serverActionQueue.getItem()
|
37
|
+
serverActionQueue.itemProcessed()
|
38
|
+
const item2 = serverActionQueue.getItem()
|
39
|
+
|
40
|
+
expect(item).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute: 1 } })
|
41
|
+
expect(item2).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute2: 'test' } })
|
42
|
+
})
|
@@ -0,0 +1,47 @@
|
|
1
|
+
// A FIFO queue with deduping of actions whose effect will be cancelled by later actions
|
2
|
+
|
3
|
+
import _ from 'lodash'
|
4
|
+
|
5
|
+
export default function createServerActionQueue() {
|
6
|
+
const queue: any[] = []
|
7
|
+
let inFlight = false
|
8
|
+
|
9
|
+
function addItem(item) {
|
10
|
+
// Check if there are any items ahead in the queue that this item would effectively overwrite.
|
11
|
+
// In that case we can remove them
|
12
|
+
// 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 } = item
|
14
|
+
if (type.split('/')[1] !== 'upsert') {
|
15
|
+
queue.push(item)
|
16
|
+
return
|
17
|
+
}
|
18
|
+
|
19
|
+
_.remove(queue, item => {
|
20
|
+
const { type: itemType, payload: itemPayload } = item
|
21
|
+
if (type !== itemType) return false
|
22
|
+
if (itemPayload.id !== payload.id) return false
|
23
|
+
|
24
|
+
// Check that all keys of itemPayload are in payload.
|
25
|
+
return _.difference(_.keys(itemPayload),_.keys(payload)).length === 0
|
26
|
+
})
|
27
|
+
|
28
|
+
queue.push(item)
|
29
|
+
}
|
30
|
+
|
31
|
+
return {
|
32
|
+
addItem,
|
33
|
+
getItem: () => {
|
34
|
+
if (inFlight) return false
|
35
|
+
|
36
|
+
const item = queue.shift()
|
37
|
+
if (item) {
|
38
|
+
inFlight = true
|
39
|
+
return item
|
40
|
+
}
|
41
|
+
return false
|
42
|
+
},
|
43
|
+
itemProcessed: () => inFlight = false,
|
44
|
+
fullySynced: () => queue.length === 0 && !inFlight,
|
45
|
+
getData: () => ({ queue, inFlight })
|
46
|
+
}
|
47
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import actionCableAdapter from './transportAdapters/actionCableAdapter'
|
2
|
+
import pusherAdapter from './transportAdapters/pusherAdapter'
|
3
|
+
|
4
|
+
export default function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect) {
|
5
|
+
const { transportService } = jasonConfig
|
6
|
+
if (transportService === 'action_cable') {
|
7
|
+
return actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnect)
|
8
|
+
} else if (transportService === 'pusher') {
|
9
|
+
return pusherAdapter(jasonConfig, handlePayload, dispatch)
|
10
|
+
} else {
|
11
|
+
throw(`Transport adapter does not exist for ${transportService}`)
|
12
|
+
}
|
13
|
+
}
|
data/client/src/index.ts
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import _JasonProvider from './JasonProvider'
|
2
2
|
import _useAct from './useAct'
|
3
3
|
import _useSub from './useSub'
|
4
|
+
import _useEager from './useEager'
|
4
5
|
|
5
6
|
export const JasonProvider = _JasonProvider
|
6
7
|
export const useAct = _useAct
|
7
|
-
export const useSub = _useSub
|
8
|
+
export const useSub = _useSub
|
9
|
+
export const useEager = _useEager
|
data/client/src/makeEager.ts
CHANGED
@@ -36,9 +36,9 @@ export default function (schema) {
|
|
36
36
|
|
37
37
|
function useEager(entity, id = null, relations = []) {
|
38
38
|
if (id) {
|
39
|
-
return useSelector(s => addRelations(s, { ...s[entity].entities[String(id)] }, entity, relations))
|
39
|
+
return useSelector(s => addRelations(s, { ...s[entity].entities[String(id)] }, entity, relations), _.isEqual)
|
40
40
|
} else {
|
41
|
-
return useSelector(s => addRelations(s, _.values(s[entity].entities), entity, relations))
|
41
|
+
return useSelector(s => addRelations(s, _.values(s[entity].entities), entity, relations), _.isEqual)
|
42
42
|
}
|
43
43
|
}
|
44
44
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import _ from 'lodash'
|
2
|
+
import pluralize from 'pluralize'
|
3
|
+
|
4
|
+
const pruneIdsMiddleware = schema => store => next => action => {
|
5
|
+
const { type, payload } = action
|
6
|
+
const result = next(action)
|
7
|
+
|
8
|
+
const state = store.getState()
|
9
|
+
if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionIds') {
|
10
|
+
const { model, ids } = payload
|
11
|
+
|
12
|
+
let idsInSubs = []
|
13
|
+
_.map(state.jasonModels[model], (subscribedIds, k) => {
|
14
|
+
idsInSubs = _.union(idsInSubs, subscribedIds)
|
15
|
+
})
|
16
|
+
// Find IDs currently in Redux that aren't in any subscription
|
17
|
+
const idsToRemove = _.difference(state[pluralize(model)].ids, idsInSubs)
|
18
|
+
store.dispatch({ type: `${pluralize(model)}/removeMany`, payload: idsToRemove })
|
19
|
+
}
|
20
|
+
|
21
|
+
return result
|
22
|
+
}
|
23
|
+
|
24
|
+
export default pruneIdsMiddleware
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import axios from 'axios'
|
2
|
+
import applyCaseMiddleware from 'axios-case-converter'
|
3
|
+
import { validate as isUuid } from 'uuid'
|
4
|
+
|
5
|
+
const csrfToken = (document?.querySelector("meta[name=csrf-token]") as any)?.content
|
6
|
+
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
|
7
|
+
|
8
|
+
const restClient = applyCaseMiddleware(axios.create() as any, {
|
9
|
+
preservedKeys: (key) => {
|
10
|
+
return isUuid(key)
|
11
|
+
}
|
12
|
+
}) as any
|
13
|
+
|
14
|
+
export default restClient
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { createConsumer } from "@rails/actioncable"
|
2
|
+
|
3
|
+
export default function actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnected) {
|
4
|
+
const consumer = createConsumer()
|
5
|
+
const subscription = (consumer.subscriptions.create({
|
6
|
+
channel: 'Jason::Channel'
|
7
|
+
}, {
|
8
|
+
connected: () => {
|
9
|
+
dispatch({ type: 'jason/upsert', payload: { connected: true } })
|
10
|
+
console.debug('Connected to ActionCable')
|
11
|
+
|
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
|
+
|
25
|
+
function getPayload(config, options) {
|
26
|
+
subscription.send({ getPayload: config, ...options })
|
27
|
+
}
|
28
|
+
|
29
|
+
function createSubscription(config) {
|
30
|
+
subscription.send({ createSubscription: config })
|
31
|
+
}
|
32
|
+
|
33
|
+
function removeSubscription(config) {
|
34
|
+
subscription.send({ removeSubscription: config })
|
35
|
+
}
|
36
|
+
|
37
|
+
return { getPayload, createSubscription, removeSubscription }
|
38
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import Pusher from 'pusher-js'
|
2
|
+
import { createConsumer } from "@rails/actioncable"
|
3
|
+
import restClient from '../restClient'
|
4
|
+
import { v4 as uuidv4 } from 'uuid'
|
5
|
+
import _ from 'lodash'
|
6
|
+
|
7
|
+
export default function pusherAdapter(jasonConfig, handlePayload, dispatch) {
|
8
|
+
let consumerId = uuidv4()
|
9
|
+
|
10
|
+
const { pusherKey, pusherRegion, pusherChannelPrefix } = jasonConfig
|
11
|
+
const pusher = new Pusher(pusherKey, {
|
12
|
+
cluster: 'eu',
|
13
|
+
forceTLS: true,
|
14
|
+
authEndpoint: '/jason/api/pusher/auth'
|
15
|
+
})
|
16
|
+
pusher.connection.bind('state_change', ({ current }) => {
|
17
|
+
if (current === 'connected') {
|
18
|
+
dispatch({ type: 'jason/upsert', payload: { connected: true } })
|
19
|
+
} else {
|
20
|
+
dispatch({ type: 'jason/upsert', payload: { connected: false } })
|
21
|
+
}
|
22
|
+
})
|
23
|
+
pusher.connection.bind( 'error', error => {
|
24
|
+
dispatch({ type: 'jason/upsert', payload: { connected: false } })
|
25
|
+
});
|
26
|
+
|
27
|
+
const configToChannel = {}
|
28
|
+
|
29
|
+
function createSubscription(config) {
|
30
|
+
restClient.post('/jason/api/create_subscription', { config, consumerId })
|
31
|
+
.then(({ data: { channelName } }) => {
|
32
|
+
configToChannel[JSON.stringify(config)] = channelName
|
33
|
+
subscribeToChannel(channelName)
|
34
|
+
})
|
35
|
+
.catch(e => console.error(e))
|
36
|
+
}
|
37
|
+
|
38
|
+
function removeSubscription(config) {
|
39
|
+
const channelName = configToChannel[JSON.stringify(config)]
|
40
|
+
unsubscribeFromChannel(fullChannelName(channelName))
|
41
|
+
restClient.post('/jason/api/remove_subscription', { config, consumerId })
|
42
|
+
.catch(e => console.error(e))
|
43
|
+
}
|
44
|
+
|
45
|
+
function getPayload(config, options) {
|
46
|
+
restClient.post('/jason/api/get_payload', {
|
47
|
+
config,
|
48
|
+
options
|
49
|
+
})
|
50
|
+
.then(({ data }) => {
|
51
|
+
_.map(data, (payload, modelName) => {
|
52
|
+
handlePayload(payload)
|
53
|
+
})
|
54
|
+
})
|
55
|
+
.catch(e => console.error(e))
|
56
|
+
}
|
57
|
+
|
58
|
+
function subscribeToChannel(channelName) {
|
59
|
+
const channel = pusher.subscribe(fullChannelName(channelName))
|
60
|
+
channel.bind('changed', message => handlePayload(message))
|
61
|
+
}
|
62
|
+
|
63
|
+
function unsubscribeFromChannel(channelName) {
|
64
|
+
const channel = pusher.unsubscribe(fullChannelName(channelName))
|
65
|
+
}
|
66
|
+
|
67
|
+
function fullChannelName(channelName) {
|
68
|
+
return `private-${pusherChannelPrefix}-${channelName}`
|
69
|
+
}
|
70
|
+
|
71
|
+
return { getPayload, createSubscription, removeSubscription }
|
72
|
+
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import { renderHook, act } from '@testing-library/react-hooks'
|
2
|
+
import useJason from './useJason'
|
3
|
+
import restClient from './restClient'
|
4
|
+
|
5
|
+
jest.mock('./restClient')
|
6
|
+
|
7
|
+
test('it works', async () => {
|
8
|
+
const resp = { data: {
|
9
|
+
schema: { post: {} },
|
10
|
+
transportService: 'action_cable'
|
11
|
+
} };
|
12
|
+
// @ts-ignore
|
13
|
+
restClient.get.mockResolvedValue(resp);
|
14
|
+
|
15
|
+
const { result, waitForNextUpdate } = renderHook(() => useJason({ reducers: {
|
16
|
+
test: (s,a) => s || {}
|
17
|
+
}}));
|
18
|
+
|
19
|
+
await waitForNextUpdate()
|
20
|
+
const [store, value, connected] = result.current
|
21
|
+
const { handlePayload, subscribe } = value
|
22
|
+
|
23
|
+
const subscription = subscribe({ post: {} })
|
24
|
+
|
25
|
+
handlePayload({
|
26
|
+
type: 'payload',
|
27
|
+
model: 'post',
|
28
|
+
payload: [{ id: 4, name: 'test' }],
|
29
|
+
md5Hash: subscription.md5Hash,
|
30
|
+
idx: 1
|
31
|
+
})
|
32
|
+
|
33
|
+
handlePayload({
|
34
|
+
id: 4,
|
35
|
+
model: 'post',
|
36
|
+
destroy: true,
|
37
|
+
md5Hash: subscription.md5Hash,
|
38
|
+
idx: 2
|
39
|
+
})
|
40
|
+
|
41
|
+
handlePayload({
|
42
|
+
id: 5,
|
43
|
+
model: 'post',
|
44
|
+
payload: { id: 5, name: 'test2' },
|
45
|
+
md5Hash: subscription.md5Hash,
|
46
|
+
idx: 3
|
47
|
+
})
|
48
|
+
})
|
49
|
+
|
50
|
+
test('pruning IDs', async () => {
|
51
|
+
const resp = { data: {
|
52
|
+
schema: { post: {} },
|
53
|
+
transportService: 'action_cable'
|
54
|
+
} };
|
55
|
+
|
56
|
+
// @ts-ignore
|
57
|
+
restClient.get.mockResolvedValue(resp);
|
58
|
+
|
59
|
+
const { result, waitForNextUpdate } = renderHook(() => useJason({ reducers: {
|
60
|
+
test: (s,a) => s || {}
|
61
|
+
}}));
|
62
|
+
|
63
|
+
await waitForNextUpdate()
|
64
|
+
const [store, value, connected] = result.current
|
65
|
+
const { handlePayload, subscribe } = value
|
66
|
+
|
67
|
+
const subscription = subscribe({ post: {} })
|
68
|
+
|
69
|
+
handlePayload({
|
70
|
+
type: 'payload',
|
71
|
+
model: 'post',
|
72
|
+
payload: [{ id: 4, name: 'test' }],
|
73
|
+
md5Hash: subscription.md5Hash,
|
74
|
+
idx: 1
|
75
|
+
})
|
76
|
+
|
77
|
+
handlePayload({
|
78
|
+
type: 'payload',
|
79
|
+
model: 'post',
|
80
|
+
payload: [{ id: 5, name: 'test it out' }],
|
81
|
+
md5Hash: subscription.md5Hash,
|
82
|
+
idx: 2
|
83
|
+
})
|
84
|
+
|
85
|
+
// The ID 4 should have been pruned
|
86
|
+
expect(store.getState().posts.ids).toStrictEqual([5])
|
87
|
+
})
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import createActions from './createActions'
|
2
|
+
import createJasonReducers from './createJasonReducers'
|
3
|
+
import createPayloadHandler from './createPayloadHandler'
|
4
|
+
import createOptDis from './createOptDis'
|
5
|
+
import createServerActionQueue from './createServerActionQueue'
|
6
|
+
import restClient from './restClient'
|
7
|
+
import pruneIdsMiddleware from './pruneIdsMiddleware'
|
8
|
+
import createTransportAdapater from './createTransportAdapter'
|
9
|
+
|
10
|
+
import { createEntityAdapter, createSlice, createReducer, configureStore } from '@reduxjs/toolkit'
|
11
|
+
|
12
|
+
import makeEager from './makeEager'
|
13
|
+
import { camelizeKeys } from 'humps'
|
14
|
+
import md5 from 'blueimp-md5'
|
15
|
+
import _ from 'lodash'
|
16
|
+
import React, { useState, useEffect } from 'react'
|
17
|
+
|
18
|
+
export default function useJason({ reducers, middleware = [], extraActions }: { reducers?: any, middleware?: any[], extraActions?: any }) {
|
19
|
+
const [store, setStore] = useState(null as any)
|
20
|
+
const [value, setValue] = useState(null as any)
|
21
|
+
|
22
|
+
useEffect(() => {
|
23
|
+
restClient.get('/jason/api/config')
|
24
|
+
.then(({ data: jasonConfig }) => {
|
25
|
+
const { schema: snakey_schema } = jasonConfig
|
26
|
+
const schema = camelizeKeys(snakey_schema)
|
27
|
+
console.debug({ schema })
|
28
|
+
|
29
|
+
const serverActionQueue = createServerActionQueue()
|
30
|
+
|
31
|
+
|
32
|
+
const allReducers = {
|
33
|
+
...reducers,
|
34
|
+
...createJasonReducers(schema)
|
35
|
+
}
|
36
|
+
|
37
|
+
console.debug({ allReducers })
|
38
|
+
|
39
|
+
const store = configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware(schema)] })
|
40
|
+
const dispatch = store.dispatch
|
41
|
+
|
42
|
+
const optDis = createOptDis(schema, dispatch, restClient, serverActionQueue)
|
43
|
+
const actions = createActions(schema, store, restClient, optDis, extraActions)
|
44
|
+
const eager = makeEager(schema)
|
45
|
+
|
46
|
+
let payloadHandlers = {}
|
47
|
+
let configs = {}
|
48
|
+
let subOptions = {}
|
49
|
+
|
50
|
+
function handlePayload(payload) {
|
51
|
+
const { md5Hash } = payload
|
52
|
+
|
53
|
+
const { handlePayload } = payloadHandlers[md5Hash]
|
54
|
+
if (handlePayload) {
|
55
|
+
handlePayload(payload)
|
56
|
+
} else {
|
57
|
+
console.warn("Payload arrived with no handler", payload, payloadHandlers)
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
const transportAdapter = createTransportAdapater(jasonConfig, handlePayload, dispatch, () => _.keys(configs).forEach(md5Hash => createSubscription(configs[md5Hash], subOptions[md5Hash])))
|
62
|
+
|
63
|
+
function createSubscription(config, options = {}) {
|
64
|
+
// We need the hash to be consistent in Ruby / Javascript
|
65
|
+
const hashableConfig = _({ conditions: {}, includes: {}, ...config }).toPairs().sortBy(0).fromPairs().value()
|
66
|
+
const md5Hash = md5(JSON.stringify(hashableConfig))
|
67
|
+
payloadHandlers[md5Hash] = createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config })
|
68
|
+
configs[md5Hash] = hashableConfig
|
69
|
+
subOptions[md5Hash] = options
|
70
|
+
|
71
|
+
setTimeout(() => transportAdapter.createSubscription(hashableConfig), 500)
|
72
|
+
let pollInterval = null as any;
|
73
|
+
|
74
|
+
// This is only for debugging / dev - not prod!
|
75
|
+
// @ts-ignore
|
76
|
+
if (options.pollInterval) {
|
77
|
+
// @ts-ignore
|
78
|
+
pollInterval = setInterval(() => transportAdapter.getPayload(hashableConfig, { forceRefresh: true }), options.pollInterval)
|
79
|
+
}
|
80
|
+
|
81
|
+
return {
|
82
|
+
remove() {
|
83
|
+
removeSubscription(hashableConfig)
|
84
|
+
if (pollInterval) clearInterval(pollInterval)
|
85
|
+
},
|
86
|
+
md5Hash
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
function removeSubscription(config) {
|
91
|
+
transportAdapter.removeSubscription(config)
|
92
|
+
const md5Hash = md5(JSON.stringify(config))
|
93
|
+
payloadHandlers[md5Hash].tearDown()
|
94
|
+
delete payloadHandlers[md5Hash]
|
95
|
+
delete configs[md5Hash]
|
96
|
+
delete subOptions[md5Hash]
|
97
|
+
}
|
98
|
+
|
99
|
+
setValue({
|
100
|
+
actions: actions,
|
101
|
+
subscribe: createSubscription,
|
102
|
+
eager,
|
103
|
+
handlePayload
|
104
|
+
})
|
105
|
+
setStore(store)
|
106
|
+
})
|
107
|
+
}, [])
|
108
|
+
|
109
|
+
return [store, value]
|
110
|
+
}
|