jason-rails 0.6.1 → 0.6.6
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 +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +27 -2
- data/app/controllers/jason/api_controller.rb +2 -2
- data/client/lib/JasonContext.d.ts +1 -1
- data/client/lib/JasonContext.js +4 -1
- data/client/lib/createJasonReducers.js +3 -4
- data/client/lib/createPayloadHandler.js +2 -2
- data/client/lib/index.d.ts +2 -0
- data/client/lib/index.js +3 -1
- data/client/lib/makeEager.js +2 -2
- data/client/lib/pruneIdsMiddleware.js +1 -1
- data/client/lib/useEager.d.ts +1 -0
- data/client/lib/useEager.js +12 -0
- data/client/lib/useJason.test.js +33 -1
- data/client/package.json +1 -1
- data/client/src/JasonContext.ts +4 -1
- data/client/src/createJasonReducers.ts +3 -4
- data/client/src/createPayloadHandler.ts +2 -2
- data/client/src/index.ts +3 -1
- data/client/src/makeEager.ts +2 -2
- data/client/src/pruneIdsMiddleware.ts +2 -1
- data/client/src/useEager.ts +9 -0
- data/client/src/useJason.test.ts +41 -1
- data/lib/jason.rb +3 -3
- data/lib/jason/graph_helper.rb +15 -4
- data/lib/jason/subscription.rb +17 -2
- data/lib/jason/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 627690fe30b3378cde9c9f148cc8d8d6941bcd540cbbe4777898fa8917f86dc8
|
4
|
+
data.tar.gz: bd3af795626563a049c02045b8dc82018882067f9b2d582e4152e1bd6b34af4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ed1a231161d3eafc616549eebd38c531c29203842f1ab8d0697e953f0d9af8a4766285b8733fa0d92ae1cd7893560f3d660c920385c0b7335f41cfa5f8c9387
|
7
|
+
data.tar.gz: 7289710ebc49e0c53c7bdd52106d0e8153bc4505d98b26251966ccff63895e4c6c5a6ac2009b904bbf3d7d3b552c71ffebe0f17786998aaf9eaedafbf12710fb
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,4 @@
|
|
1
|
+
## v0.6.5
|
2
|
+
- Added `reset!` and `reset!(hard: true)` methods to `Subscription`. Reset will load the IDs that should be part of the subscription from the database, and ensure that the graph matches those. It then re-broadcasts the payloads to all connected clients. Hard reset will do the same, but also clear all cached IDs and subscription hooks on instances - this is equivalent from starting from scratch.
|
3
|
+
- Added `enforce: boolean` option to GraphHelper
|
4
|
+
- When subscriptions are re-activated they now set the IDs with `enforce: true`, as there could be conditions where updates that were made while a subscription was not active would not be properly registered.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Jason
|
2
2
|
|
3
|
-
Jason is still in
|
3
|
+
Jason is still in an experimental phase with a rapidly changing API. It is being used in some production applications, however it is still in 0.x.x series versions, which means that any 0.x version bump could introduce breaking changes.
|
4
4
|
|
5
5
|
## The goal
|
6
6
|
|
@@ -14,6 +14,8 @@ I also wanted to avoid writing essentially the same code multiple times in diffe
|
|
14
14
|
|
15
15
|
Jason attempts to minimize this repitition by auto-generating API endpoints, redux stores and actions from a single schema definition. Further it adds listeners to ActiveRecord models allowing the redux store to be subscribed to updates from a model or set of models.
|
16
16
|
|
17
|
+
An alternative way of thinking about Jason is "what if we applied the Flux/Redux state update pattern to make the _database_ the store?".
|
18
|
+
|
17
19
|
## Installation
|
18
20
|
|
19
21
|
Add the gem and the NPM package
|
@@ -81,7 +83,7 @@ function extraActions(dispatch, store, restClient, act) {
|
|
81
83
|
- `middleware` - Passed directly to `configureStore` with additional Jason middleware
|
82
84
|
|
83
85
|
## Usage
|
84
|
-
Jason provides
|
86
|
+
Jason provides three custom hooks to access functionality.
|
85
87
|
|
86
88
|
### useAct
|
87
89
|
This returns an object which allows you to access actions which both update models on the server, and perform an optimistic update to the Redux store.
|
@@ -126,6 +128,28 @@ export default function PostsList() {
|
|
126
128
|
}
|
127
129
|
```
|
128
130
|
|
131
|
+
### useEager
|
132
|
+
Jason stores all the data in a normalized form - one redux slice per model. Often you might want to get nested data from several slices for use in components. The `useEager` hook provides an API for doing that. Under the hood it's just a wrapper around useSelector, which aims to mimic the behaviour of Rails eager loading.
|
133
|
+
|
134
|
+
Example
|
135
|
+
This will fetch the comment as well as the post and user linked to it.
|
136
|
+
|
137
|
+
```jsx
|
138
|
+
import React from 'react'
|
139
|
+
import { useSelector } from 'react-redux'
|
140
|
+
import { useEager } from '@jamesr2323/jason'
|
141
|
+
import _ from 'lodash'
|
142
|
+
|
143
|
+
export default function Comment({ id }) {
|
144
|
+
const comment = useEager('comments', id, ['post', 'user'])
|
145
|
+
|
146
|
+
return <div>
|
147
|
+
<p>{ comment.body }</p>
|
148
|
+
<p>Made on post { comment.post.name } by { comment.user.name }</p>
|
149
|
+
</div>
|
150
|
+
}
|
151
|
+
```
|
152
|
+
|
129
153
|
## Authorization
|
130
154
|
|
131
155
|
By default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access.
|
@@ -143,6 +167,7 @@ Development is primarily driven by the needs of projects we're using Jason in. I
|
|
143
167
|
- Authorization - more thorough authorization integration, with utility functions for common authorizations. Allowing authorization of access to particular fields such as restricting the fields of a user that are publicly broadcast.
|
144
168
|
- Utilities for "Draft editing" - both storing client-side copies of model trees which can be committed or discarded, as well as persisting a shadow copy to the database (to allow resumable editing, or possibly collaborative editing features)
|
145
169
|
- Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
|
170
|
+
- Assess using RedisGraph for the graph diffing functionality, to see if this would provide a performance boost
|
146
171
|
|
147
172
|
## Development
|
148
173
|
|
@@ -35,10 +35,10 @@ class Jason::ApiController < ::ApplicationController
|
|
35
35
|
all_instance_ids.insert(priority.to_i, instance.id)
|
36
36
|
|
37
37
|
all_instance_ids.each_with_index do |id, i|
|
38
|
-
model.find(id).update!(priority: i
|
38
|
+
model.find(id).update!(priority: i)
|
39
39
|
end
|
40
40
|
|
41
|
-
model.
|
41
|
+
model.find(all_instance_ids).each(&:force_publish_json)
|
42
42
|
elsif action == 'upsert' || action == 'add'
|
43
43
|
payload = api_model.permit(params)
|
44
44
|
return render json: model.find_or_create_by_id!(payload).as_json(api_model.as_json_config)
|
data/client/lib/JasonContext.js
CHANGED
@@ -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
|
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;
|
@@ -42,18 +42,17 @@ function generateJasonSlices(models) {
|
|
42
42
|
setSubscriptionIds(s, a) {
|
43
43
|
const { payload } = a;
|
44
44
|
const { subscriptionId, model, ids } = payload;
|
45
|
-
|
46
|
-
s[model][subscriptionId] = ids;
|
45
|
+
s[model][subscriptionId] = ids.map(id => String(id));
|
47
46
|
},
|
48
47
|
addSubscriptionId(s, a) {
|
49
48
|
const { payload } = a;
|
50
49
|
const { subscriptionId, model, id } = payload;
|
51
|
-
s[model][subscriptionId] = lodash_1.default.union(s[model][subscriptionId] || [], [id]);
|
50
|
+
s[model][subscriptionId] = lodash_1.default.union(s[model][subscriptionId] || [], [String(id)]);
|
52
51
|
},
|
53
52
|
removeSubscriptionId(s, a) {
|
54
53
|
const { payload } = a;
|
55
54
|
const { subscriptionId, model, id } = payload;
|
56
|
-
s[model][subscriptionId] = lodash_1.default.
|
55
|
+
s[model][subscriptionId] = lodash_1.default.difference(s[model][subscriptionId] || [], [String(id)]);
|
57
56
|
},
|
58
57
|
removeSubscription(s, a) {
|
59
58
|
const { payload: { subscriptionId } } = a;
|
@@ -36,7 +36,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, c
|
|
36
36
|
}
|
37
37
|
const { payload, destroy, id, type } = patchQueue[model][idx[model]];
|
38
38
|
if (type === 'payload') {
|
39
|
-
dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload });
|
39
|
+
dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload: payload.map(m => (Object.assign(Object.assign({}, m), { id: String(m.id) }))) });
|
40
40
|
const ids = payload.map(instance => instance.id);
|
41
41
|
dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids } });
|
42
42
|
}
|
@@ -45,7 +45,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, c
|
|
45
45
|
dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id } });
|
46
46
|
}
|
47
47
|
else {
|
48
|
-
dispatch({ type: `${pluralize_1.default(model)}/upsert`, payload });
|
48
|
+
dispatch({ type: `${pluralize_1.default(model)}/upsert`, payload: Object.assign(Object.assign({}, payload), { id: String(payload.id) }) });
|
49
49
|
dispatch({ type: `jasonModels/addSubscriptionId`, payload: { model, subscriptionId, id } });
|
50
50
|
}
|
51
51
|
delete patchQueue[model][idx[model]];
|
data/client/lib/index.d.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
/// <reference types="react" />
|
2
2
|
import _useAct from './useAct';
|
3
3
|
import _useSub from './useSub';
|
4
|
+
import _useEager from './useEager';
|
4
5
|
export declare const JasonProvider: ({ reducers, middleware, extraActions, children }: {
|
5
6
|
reducers?: any;
|
6
7
|
middleware?: any;
|
@@ -9,3 +10,4 @@ export declare const JasonProvider: ({ reducers, middleware, extraActions, child
|
|
9
10
|
}) => JSX.Element;
|
10
11
|
export declare const useAct: typeof _useAct;
|
11
12
|
export declare const useSub: typeof _useSub;
|
13
|
+
export declare const useEager: typeof _useEager;
|
data/client/lib/index.js
CHANGED
@@ -3,10 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.useSub = exports.useAct = exports.JasonProvider = void 0;
|
6
|
+
exports.useEager = exports.useSub = exports.useAct = exports.JasonProvider = void 0;
|
7
7
|
const JasonProvider_1 = __importDefault(require("./JasonProvider"));
|
8
8
|
const useAct_1 = __importDefault(require("./useAct"));
|
9
9
|
const useSub_1 = __importDefault(require("./useSub"));
|
10
|
+
const useEager_1 = __importDefault(require("./useEager"));
|
10
11
|
exports.JasonProvider = JasonProvider_1.default;
|
11
12
|
exports.useAct = useAct_1.default;
|
12
13
|
exports.useSub = useSub_1.default;
|
14
|
+
exports.useEager = useEager_1.default;
|
data/client/lib/makeEager.js
CHANGED
@@ -40,10 +40,10 @@ function default_1(schema) {
|
|
40
40
|
}
|
41
41
|
function useEager(entity, id = null, relations = []) {
|
42
42
|
if (id) {
|
43
|
-
return react_redux_1.useSelector(s => addRelations(s, Object.assign({}, s[entity].entities[String(id)]), entity, relations));
|
43
|
+
return react_redux_1.useSelector(s => addRelations(s, Object.assign({}, s[entity].entities[String(id)]), entity, relations), lodash_1.default.isEqual);
|
44
44
|
}
|
45
45
|
else {
|
46
|
-
return react_redux_1.useSelector(s => addRelations(s, lodash_1.default.values(s[entity].entities), entity, relations));
|
46
|
+
return react_redux_1.useSelector(s => addRelations(s, lodash_1.default.values(s[entity].entities), entity, relations), lodash_1.default.isEqual);
|
47
47
|
}
|
48
48
|
}
|
49
49
|
return useEager;
|
@@ -9,7 +9,7 @@ const pruneIdsMiddleware = schema => store => next => action => {
|
|
9
9
|
const { type, payload } = action;
|
10
10
|
const result = next(action);
|
11
11
|
const state = store.getState();
|
12
|
-
if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/
|
12
|
+
if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionId') {
|
13
13
|
const { model, ids } = payload;
|
14
14
|
let idsInSubs = [];
|
15
15
|
lodash_1.default.map(state.jasonModels[model], (subscribedIds, k) => {
|
@@ -0,0 +1 @@
|
|
1
|
+
export default function useEager(entity: any, id?: null, relations?: never[]): void;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const JasonContext_1 = __importDefault(require("./JasonContext"));
|
7
|
+
const react_1 = require("react");
|
8
|
+
function useEager(entity, id = null, relations = []) {
|
9
|
+
const { eager } = react_1.useContext(JasonContext_1.default);
|
10
|
+
return eager(entity, id, relations);
|
11
|
+
}
|
12
|
+
exports.default = useEager;
|
data/client/lib/useJason.test.js
CHANGED
@@ -81,5 +81,37 @@ test('pruning IDs', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
81
|
idx: 2
|
82
82
|
});
|
83
83
|
// The ID 4 should have been pruned
|
84
|
-
expect(store.getState().posts.ids).toStrictEqual([5]);
|
84
|
+
expect(store.getState().posts.ids).toStrictEqual(['5']);
|
85
|
+
}));
|
86
|
+
test('pruning IDs by destroy', () => __awaiter(void 0, void 0, void 0, function* () {
|
87
|
+
const resp = { data: {
|
88
|
+
schema: { post: {} },
|
89
|
+
transportService: 'action_cable'
|
90
|
+
} };
|
91
|
+
// @ts-ignore
|
92
|
+
restClient_1.default.get.mockResolvedValue(resp);
|
93
|
+
const { result, waitForNextUpdate } = react_hooks_1.renderHook(() => useJason_1.default({ reducers: {
|
94
|
+
test: (s, a) => s || {}
|
95
|
+
} }));
|
96
|
+
yield waitForNextUpdate();
|
97
|
+
const [store, value, connected] = result.current;
|
98
|
+
const { handlePayload, subscribe } = value;
|
99
|
+
const subscription = subscribe({ post: {} });
|
100
|
+
handlePayload({
|
101
|
+
type: 'payload',
|
102
|
+
model: 'post',
|
103
|
+
payload: [{ id: 4, name: 'test' }, { id: 5, name: 'test it out' }],
|
104
|
+
md5Hash: subscription.md5Hash,
|
105
|
+
idx: 1
|
106
|
+
});
|
107
|
+
expect(store.getState().posts.ids).toStrictEqual(['4', '5']);
|
108
|
+
handlePayload({
|
109
|
+
destroy: true,
|
110
|
+
model: 'post',
|
111
|
+
id: 5,
|
112
|
+
md5Hash: subscription.md5Hash,
|
113
|
+
idx: 2
|
114
|
+
});
|
115
|
+
// The ID 4 should have been pruned
|
116
|
+
expect(store.getState().posts.ids).toStrictEqual(['4']);
|
85
117
|
}));
|
data/client/package.json
CHANGED
data/client/src/JasonContext.ts
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
import { createContext } from 'react'
|
2
|
+
const eager = function(entity, id, relations) {
|
3
|
+
console.error("Eager called but is not implemented")
|
4
|
+
}
|
2
5
|
|
3
|
-
const context = createContext({ actions: {} as any, subscribe: null, eager
|
6
|
+
const context = createContext({ actions: {} as any, subscribe: null, eager })
|
4
7
|
|
5
8
|
export default context
|
@@ -42,18 +42,17 @@ function generateJasonSlices(models) {
|
|
42
42
|
setSubscriptionIds(s,a) {
|
43
43
|
const { payload } = a
|
44
44
|
const { subscriptionId, model, ids } = payload
|
45
|
-
|
46
|
-
s[model][subscriptionId] = ids
|
45
|
+
s[model][subscriptionId] = ids.map(id => String(id))
|
47
46
|
},
|
48
47
|
addSubscriptionId(s,a) {
|
49
48
|
const { payload } = a
|
50
49
|
const { subscriptionId, model, id } = payload
|
51
|
-
s[model][subscriptionId] = _.union(s[model][subscriptionId] || [], [id])
|
50
|
+
s[model][subscriptionId] = _.union(s[model][subscriptionId] || [], [String(id)])
|
52
51
|
},
|
53
52
|
removeSubscriptionId(s,a) {
|
54
53
|
const { payload } = a
|
55
54
|
const { subscriptionId, model, id } = payload
|
56
|
-
s[model][subscriptionId] = _.
|
55
|
+
s[model][subscriptionId] = _.difference(s[model][subscriptionId] || [], [String(id)])
|
57
56
|
},
|
58
57
|
removeSubscription(s, a) {
|
59
58
|
const { payload: { subscriptionId } } = a
|
@@ -42,14 +42,14 @@ export default function createPayloadHandler({ dispatch, serverActionQueue, tran
|
|
42
42
|
const { payload, destroy, id, type } = patchQueue[model][idx[model]]
|
43
43
|
|
44
44
|
if (type === 'payload') {
|
45
|
-
dispatch({ type: `${pluralize(model)}/upsertMany`, payload })
|
45
|
+
dispatch({ type: `${pluralize(model)}/upsertMany`, payload: payload.map(m => ({ ...m, id: String(m.id) })) })
|
46
46
|
const ids = payload.map(instance => instance.id)
|
47
47
|
dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids }})
|
48
48
|
} else if (destroy) {
|
49
49
|
// Middleware will determine if this model should be removed if it isn't in any other subscriptions
|
50
50
|
dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id }})
|
51
51
|
} else {
|
52
|
-
dispatch({ type: `${pluralize(model)}/upsert`, payload })
|
52
|
+
dispatch({ type: `${pluralize(model)}/upsert`, payload: { ...payload, id: String(payload.id) } })
|
53
53
|
dispatch({ type: `jasonModels/addSubscriptionId`, payload: { model, subscriptionId, id }})
|
54
54
|
}
|
55
55
|
|
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
|
|
@@ -6,13 +6,14 @@ const pruneIdsMiddleware = schema => store => next => action => {
|
|
6
6
|
const result = next(action)
|
7
7
|
|
8
8
|
const state = store.getState()
|
9
|
-
if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/
|
9
|
+
if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionId') {
|
10
10
|
const { model, ids } = payload
|
11
11
|
|
12
12
|
let idsInSubs = []
|
13
13
|
_.map(state.jasonModels[model], (subscribedIds, k) => {
|
14
14
|
idsInSubs = _.union(idsInSubs, subscribedIds)
|
15
15
|
})
|
16
|
+
|
16
17
|
// Find IDs currently in Redux that aren't in any subscription
|
17
18
|
const idsToRemove = _.difference(state[pluralize(model)].ids, idsInSubs)
|
18
19
|
store.dispatch({ type: `${pluralize(model)}/removeMany`, payload: idsToRemove })
|
data/client/src/useJason.test.ts
CHANGED
@@ -83,5 +83,45 @@ test('pruning IDs', async () => {
|
|
83
83
|
})
|
84
84
|
|
85
85
|
// The ID 4 should have been pruned
|
86
|
-
expect(store.getState().posts.ids).toStrictEqual([5])
|
86
|
+
expect(store.getState().posts.ids).toStrictEqual(['5'])
|
87
|
+
})
|
88
|
+
|
89
|
+
test('pruning IDs by destroy', async () => {
|
90
|
+
const resp = { data: {
|
91
|
+
schema: { post: {} },
|
92
|
+
transportService: 'action_cable'
|
93
|
+
} };
|
94
|
+
|
95
|
+
// @ts-ignore
|
96
|
+
restClient.get.mockResolvedValue(resp);
|
97
|
+
|
98
|
+
const { result, waitForNextUpdate } = renderHook(() => useJason({ reducers: {
|
99
|
+
test: (s,a) => s || {}
|
100
|
+
}}));
|
101
|
+
|
102
|
+
await waitForNextUpdate()
|
103
|
+
const [store, value, connected] = result.current
|
104
|
+
const { handlePayload, subscribe } = value
|
105
|
+
|
106
|
+
const subscription = subscribe({ post: {} })
|
107
|
+
|
108
|
+
handlePayload({
|
109
|
+
type: 'payload',
|
110
|
+
model: 'post',
|
111
|
+
payload: [{ id: 4, name: 'test' }, { id: 5, name: 'test it out' }],
|
112
|
+
md5Hash: subscription.md5Hash,
|
113
|
+
idx: 1
|
114
|
+
})
|
115
|
+
expect(store.getState().posts.ids).toStrictEqual(['4', '5'])
|
116
|
+
|
117
|
+
handlePayload({
|
118
|
+
destroy: true,
|
119
|
+
model: 'post',
|
120
|
+
id: 5,
|
121
|
+
md5Hash: subscription.md5Hash,
|
122
|
+
idx: 2
|
123
|
+
})
|
124
|
+
|
125
|
+
// The ID 4 should have been pruned
|
126
|
+
expect(store.getState().posts.ids).toStrictEqual(['4'])
|
87
127
|
})
|
data/lib/jason.rb
CHANGED
@@ -31,13 +31,15 @@ module Jason
|
|
31
31
|
self.pusher_channel_prefix = 'jason'
|
32
32
|
|
33
33
|
def self.init
|
34
|
+
# Don't run in AR migration / generator etc.
|
35
|
+
return if $PROGRAM_NAME == '-e' || ActiveRecord::Base.connection.migration_context.needs_migration?
|
36
|
+
|
34
37
|
# Check if the schema has changed since last time app was started. If so, do some work to ensure cache contains the correct data
|
35
38
|
got_lock = $redis_jason.set('jason:schema:lock', nx: true, ex: 3600) # Basic lock mechanism for multi-process environments
|
36
39
|
return if !got_lock
|
37
40
|
|
38
41
|
previous_schema = JSON.parse($redis_jason.get('jason:last_schema') || '{}')
|
39
42
|
current_schema = Jason.schema.deep_stringify_keys.deep_transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
|
40
|
-
pp current_schema
|
41
43
|
current_schema.each do |model, config|
|
42
44
|
if config != previous_schema[model]
|
43
45
|
puts "Config changed for #{model}"
|
@@ -52,8 +54,6 @@ module Jason
|
|
52
54
|
$redis_jason.set('jason:last_schema', current_schema.to_json)
|
53
55
|
ensure
|
54
56
|
$redis_jason.del('jason:schema:lock')
|
55
|
-
|
56
|
-
previous_config = 'test'
|
57
57
|
end
|
58
58
|
|
59
59
|
|
data/lib/jason/graph_helper.rb
CHANGED
@@ -33,7 +33,8 @@ class Jason::GraphHelper
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# Add and remove edges, return graph before and after
|
36
|
-
|
36
|
+
# Enforce means make the graph contain only the add_edges
|
37
|
+
def apply_update(add: nil, remove: nil, enforce: false)
|
37
38
|
add_edges = []
|
38
39
|
remove_edges = []
|
39
40
|
|
@@ -48,11 +49,21 @@ class Jason::GraphHelper
|
|
48
49
|
remove_edges += build_edges(edge_set[:model_names], edge_set[:instance_ids], include_root: false)
|
49
50
|
end
|
50
51
|
end
|
51
|
-
|
52
|
+
|
53
|
+
diff_edges_from_graph(add_edges: add_edges, remove_edges: remove_edges, enforce: enforce)
|
52
54
|
end
|
53
55
|
|
54
|
-
def diff_edges_from_graph(add_edges: [], remove_edges: [])
|
55
|
-
|
56
|
+
def diff_edges_from_graph(add_edges: [], remove_edges: [], enforce: false)
|
57
|
+
if enforce
|
58
|
+
old_edges = $redis_jason.multi do |r|
|
59
|
+
r.smembers("jason:subscriptions:#{id}:graph")
|
60
|
+
r.del("jason:subscriptions:#{id}:graph")
|
61
|
+
r.sadd("jason:subscriptions:#{id}:graph", add_edges) if add_edges.present?
|
62
|
+
end[0]
|
63
|
+
new_edges = add_edges
|
64
|
+
else
|
65
|
+
old_edges, new_edges = Jason::LuaGenerator.new.update_set_with_diff("jason:subscriptions:#{id}:graph", add_edges.flatten, remove_edges.flatten)
|
66
|
+
end
|
56
67
|
|
57
68
|
old_graph = build_graph_from_edges(old_edges)
|
58
69
|
new_graph = build_graph_from_edges(new_edges)
|
data/lib/jason/subscription.rb
CHANGED
@@ -269,7 +269,8 @@ class Jason::Subscription
|
|
269
269
|
|
270
270
|
# Build the tree
|
271
271
|
id_changeset = graph_helper.apply_update({
|
272
|
-
add: [edge_set]
|
272
|
+
add: [edge_set],
|
273
|
+
enforce: enforce
|
273
274
|
})
|
274
275
|
apply_id_changeset(id_changeset)
|
275
276
|
end
|
@@ -320,7 +321,7 @@ class Jason::Subscription
|
|
320
321
|
$redis_jason.hset("jason:consumers", consumer_id, Time.now.utc)
|
321
322
|
|
322
323
|
if before_consumer_count == 0
|
323
|
-
set_ids_for_sub_models
|
324
|
+
set_ids_for_sub_models(enforce: true)
|
324
325
|
end
|
325
326
|
end
|
326
327
|
|
@@ -375,6 +376,20 @@ class Jason::Subscription
|
|
375
376
|
}
|
376
377
|
end
|
377
378
|
|
379
|
+
# To be used as a fallback when some corruption of the subscription has taken place
|
380
|
+
def reset!(hard: false)
|
381
|
+
# Remove subscription state
|
382
|
+
if hard
|
383
|
+
clear_all_ids
|
384
|
+
$redis_jason.del("jason:subscriptions:#{id}:graph")
|
385
|
+
end
|
386
|
+
|
387
|
+
set_ids_for_sub_models(enforce: true)
|
388
|
+
includes_helper.all_models.each do |model_name|
|
389
|
+
broadcaster.broadcast(get_for_model(model_name))
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
378
393
|
def add(model_name, instance_id)
|
379
394
|
idx = $redis_jason.incr("jason:subscription:#{id}:#{model_name}:idx")
|
380
395
|
payload = JSON.parse($redis_jason.hget("jason:cache:#{model_name}", instance_id) || '{}')
|
data/lib/jason/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jason-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Rees
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- ".rspec"
|
120
120
|
- ".ruby-version"
|
121
121
|
- ".travis.yml"
|
122
|
+
- CHANGELOG.md
|
122
123
|
- CODE_OF_CONDUCT.md
|
123
124
|
- Gemfile
|
124
125
|
- Gemfile.lock
|
@@ -168,6 +169,8 @@ files:
|
|
168
169
|
- client/lib/transportAdapters/pusherAdapter.js
|
169
170
|
- client/lib/useAct.d.ts
|
170
171
|
- client/lib/useAct.js
|
172
|
+
- client/lib/useEager.d.ts
|
173
|
+
- client/lib/useEager.js
|
171
174
|
- client/lib/useJason.d.ts
|
172
175
|
- client/lib/useJason.js
|
173
176
|
- client/lib/useJason.test.d.ts
|
@@ -194,6 +197,7 @@ files:
|
|
194
197
|
- client/src/transportAdapters/actionCableAdapter.ts
|
195
198
|
- client/src/transportAdapters/pusherAdapter.ts
|
196
199
|
- client/src/useAct.ts
|
200
|
+
- client/src/useEager.ts
|
197
201
|
- client/src/useJason.test.ts
|
198
202
|
- client/src/useJason.ts
|
199
203
|
- client/src/useSub.ts
|