jason-rails 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/Gemfile.lock +152 -2
- data/app/controllers/jason/api_controller.rb +1 -1
- data/client/lib/JasonContext.d.ts +6 -1
- data/client/lib/JasonProvider.d.ts +2 -2
- data/client/lib/JasonProvider.js +5 -124
- data/client/lib/createJasonReducers.js +41 -3
- data/client/lib/createOptDis.js +0 -2
- data/client/lib/createPayloadHandler.d.ts +6 -1
- data/client/lib/createPayloadHandler.js +42 -54
- 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/index.d.ts +3 -2
- data/client/lib/pruneIdsMiddleware.d.ts +2 -0
- data/client/lib/pruneIdsMiddleware.js +26 -0
- data/client/lib/restClient.d.ts +2 -0
- data/client/lib/restClient.js +17 -0
- data/client/lib/useJason.d.ts +5 -0
- data/client/lib/useJason.js +99 -0
- data/client/lib/useJason.test.d.ts +1 -0
- data/client/lib/useJason.test.js +79 -0
- data/client/lib/useSub.js +1 -0
- data/client/package.json +4 -3
- data/client/src/JasonProvider.tsx +5 -123
- data/client/src/createJasonReducers.ts +49 -3
- data/client/src/createOptDis.ts +0 -2
- data/client/src/createPayloadHandler.ts +47 -63
- data/client/src/createServerActionQueue.test.ts +42 -0
- data/client/src/createServerActionQueue.ts +47 -0
- data/client/src/pruneIdsMiddleware.ts +24 -0
- data/client/src/restClient.ts +13 -0
- data/client/src/useJason.test.ts +81 -0
- data/client/src/useJason.ts +115 -0
- data/client/src/useSub.ts +1 -0
- data/client/yarn.lock +59 -3
- data/jason-rails.gemspec +4 -0
- data/lib/jason.rb +12 -0
- data/lib/jason/api_model.rb +2 -12
- data/lib/jason/channel.rb +43 -21
- data/lib/jason/lua_generator.rb +49 -0
- data/lib/jason/publisher.rb +76 -35
- data/lib/jason/publisher_old.rb +112 -0
- data/lib/jason/subscription.rb +322 -99
- data/lib/jason/subscription_old.rb +171 -0
- data/lib/jason/version.rb +1 -1
- metadata +67 -3
@@ -0,0 +1,48 @@
|
|
1
|
+
"use strict";
|
2
|
+
// A FIFO queue with deduping of actions whose effect will be cancelled by later actions
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
5
|
+
};
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
8
|
+
function createServerActionQueue() {
|
9
|
+
const queue = [];
|
10
|
+
let inFlight = false;
|
11
|
+
function addItem(item) {
|
12
|
+
// Check if there are any items ahead in the queue that this item would effectively overwrite.
|
13
|
+
// In that case we can remove them
|
14
|
+
// If this is an upsert && item ID is the same && current item attributes are a superset of the earlier item attributes
|
15
|
+
const { type, payload } = item;
|
16
|
+
if (type.split('/')[1] !== 'upsert') {
|
17
|
+
queue.push(item);
|
18
|
+
return;
|
19
|
+
}
|
20
|
+
lodash_1.default.remove(queue, item => {
|
21
|
+
const { type: itemType, payload: itemPayload } = item;
|
22
|
+
if (type !== itemType)
|
23
|
+
return false;
|
24
|
+
if (itemPayload.id !== payload.id)
|
25
|
+
return false;
|
26
|
+
// Check that all keys of itemPayload are in payload.
|
27
|
+
return lodash_1.default.difference(lodash_1.default.keys(itemPayload), lodash_1.default.keys(payload)).length === 0;
|
28
|
+
});
|
29
|
+
queue.push(item);
|
30
|
+
}
|
31
|
+
return {
|
32
|
+
addItem,
|
33
|
+
getItem: () => {
|
34
|
+
if (inFlight)
|
35
|
+
return false;
|
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
|
+
}
|
48
|
+
exports.default = createServerActionQueue;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,37 @@
|
|
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 createServerActionQueue_1 = __importDefault(require("./createServerActionQueue"));
|
7
|
+
test('Adding items', () => {
|
8
|
+
const serverActionQueue = createServerActionQueue_1.default();
|
9
|
+
serverActionQueue.addItem({ type: 'entity/add', payload: { id: 'abc', attribute: 1 } });
|
10
|
+
const item = serverActionQueue.getItem();
|
11
|
+
expect(item).toStrictEqual({ type: 'entity/add', payload: { id: 'abc', attribute: 1 } });
|
12
|
+
});
|
13
|
+
test('Deduping of items that will overwrite each other', () => {
|
14
|
+
const serverActionQueue = createServerActionQueue_1.default();
|
15
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 1 } });
|
16
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 2 } });
|
17
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 3 } });
|
18
|
+
const item = serverActionQueue.getItem();
|
19
|
+
expect(item).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute: 3 } });
|
20
|
+
});
|
21
|
+
test('Deduping of items with a superset', () => {
|
22
|
+
const serverActionQueue = createServerActionQueue_1.default();
|
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
|
+
const item = serverActionQueue.getItem();
|
26
|
+
expect(item).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute: 2, attribute2: 'test' } });
|
27
|
+
});
|
28
|
+
test("doesn't dedupe items with some attributes missing", () => {
|
29
|
+
const serverActionQueue = createServerActionQueue_1.default();
|
30
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute: 1 } });
|
31
|
+
serverActionQueue.addItem({ type: 'entity/upsert', payload: { id: 'abc', attribute2: 'test' } });
|
32
|
+
const item = serverActionQueue.getItem();
|
33
|
+
serverActionQueue.itemProcessed();
|
34
|
+
const item2 = serverActionQueue.getItem();
|
35
|
+
expect(item).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute: 1 } });
|
36
|
+
expect(item2).toStrictEqual({ type: 'entity/upsert', payload: { id: 'abc', attribute2: 'test' } });
|
37
|
+
});
|
data/client/lib/index.d.ts
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
/// <reference types="react" />
|
1
2
|
import _useAct from './useAct';
|
2
3
|
import _useSub from './useSub';
|
3
4
|
export declare const JasonProvider: ({ reducers, middleware, extraActions, children }: {
|
4
5
|
reducers?: any;
|
5
6
|
middleware?: any;
|
6
7
|
extraActions?: any;
|
7
|
-
children?:
|
8
|
-
}) =>
|
8
|
+
children?: import("react").FC<{}> | undefined;
|
9
|
+
}) => JSX.Element;
|
9
10
|
export declare const useAct: typeof _useAct;
|
10
11
|
export declare const useSub: typeof _useSub;
|
@@ -0,0 +1,26 @@
|
|
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 lodash_1 = __importDefault(require("lodash"));
|
7
|
+
const pluralize_1 = __importDefault(require("pluralize"));
|
8
|
+
const pruneIdsMiddleware = schema => store => next => action => {
|
9
|
+
const { type } = action;
|
10
|
+
const result = next(action);
|
11
|
+
const state = store.getState();
|
12
|
+
if (type === 'jasonModels/setSubscriptionIds') {
|
13
|
+
// Check every model
|
14
|
+
lodash_1.default.map(lodash_1.default.keys(schema), model => {
|
15
|
+
let ids = [];
|
16
|
+
lodash_1.default.map(state.jasonModels[model], (subscribedIds, k) => {
|
17
|
+
ids = lodash_1.default.union(ids, subscribedIds);
|
18
|
+
});
|
19
|
+
// Find IDs currently in Redux that aren't in any subscription
|
20
|
+
const idsToRemove = lodash_1.default.difference(state[pluralize_1.default(model)].ids, ids);
|
21
|
+
store.dispatch({ type: `${pluralize_1.default(model)}/removeMany`, payload: idsToRemove });
|
22
|
+
});
|
23
|
+
}
|
24
|
+
return result;
|
25
|
+
};
|
26
|
+
exports.default = pruneIdsMiddleware;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
var _a;
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
8
|
+
const axios_case_converter_1 = __importDefault(require("axios-case-converter"));
|
9
|
+
const uuid_1 = require("uuid");
|
10
|
+
const csrfToken = (_a = document === null || document === void 0 ? void 0 : document.querySelector("meta[name=csrf-token]")) === null || _a === void 0 ? void 0 : _a.content;
|
11
|
+
axios_1.default.defaults.headers.common['X-CSRF-Token'] = csrfToken;
|
12
|
+
const restClient = axios_case_converter_1.default(axios_1.default.create(), {
|
13
|
+
preservedKeys: (key) => {
|
14
|
+
return uuid_1.validate(key);
|
15
|
+
}
|
16
|
+
});
|
17
|
+
exports.default = restClient;
|
@@ -0,0 +1,99 @@
|
|
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 createActions_1 = __importDefault(require("./createActions"));
|
7
|
+
const createJasonReducers_1 = __importDefault(require("./createJasonReducers"));
|
8
|
+
const createPayloadHandler_1 = __importDefault(require("./createPayloadHandler"));
|
9
|
+
const createOptDis_1 = __importDefault(require("./createOptDis"));
|
10
|
+
const createServerActionQueue_1 = __importDefault(require("./createServerActionQueue"));
|
11
|
+
const restClient_1 = __importDefault(require("./restClient"));
|
12
|
+
const pruneIdsMiddleware_1 = __importDefault(require("./pruneIdsMiddleware"));
|
13
|
+
const actioncable_1 = require("@rails/actioncable");
|
14
|
+
const toolkit_1 = require("@reduxjs/toolkit");
|
15
|
+
const makeEager_1 = __importDefault(require("./makeEager"));
|
16
|
+
const humps_1 = require("humps");
|
17
|
+
const blueimp_md5_1 = __importDefault(require("blueimp-md5"));
|
18
|
+
const lodash_1 = __importDefault(require("lodash"));
|
19
|
+
const react_1 = require("react");
|
20
|
+
function useJason({ reducers, middleware = [], extraActions }) {
|
21
|
+
const [store, setStore] = react_1.useState(null);
|
22
|
+
const [value, setValue] = react_1.useState(null);
|
23
|
+
const [connected, setConnected] = react_1.useState(false);
|
24
|
+
react_1.useEffect(() => {
|
25
|
+
restClient_1.default.get('/jason/api/schema')
|
26
|
+
.then(({ data: snakey_schema }) => {
|
27
|
+
const schema = humps_1.camelizeKeys(snakey_schema);
|
28
|
+
console.debug({ schema });
|
29
|
+
const serverActionQueue = createServerActionQueue_1.default();
|
30
|
+
const consumer = actioncable_1.createConsumer();
|
31
|
+
const allReducers = Object.assign(Object.assign({}, reducers), createJasonReducers_1.default(schema));
|
32
|
+
console.debug({ allReducers });
|
33
|
+
const store = toolkit_1.configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware_1.default(schema)] });
|
34
|
+
const dispatch = store.dispatch;
|
35
|
+
const optDis = createOptDis_1.default(schema, dispatch, restClient_1.default, serverActionQueue);
|
36
|
+
const actions = createActions_1.default(schema, store, restClient_1.default, optDis, extraActions);
|
37
|
+
const eager = makeEager_1.default(schema);
|
38
|
+
let payloadHandlers = {};
|
39
|
+
let configs = {};
|
40
|
+
function handlePayload(payload) {
|
41
|
+
const { md5Hash } = payload;
|
42
|
+
const handler = payloadHandlers[md5Hash];
|
43
|
+
if (handler) {
|
44
|
+
handler(payload);
|
45
|
+
}
|
46
|
+
else {
|
47
|
+
console.warn("Payload arrived with no handler", payload, payloadHandlers);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
const subscription = (consumer.subscriptions.create({
|
51
|
+
channel: 'Jason::Channel'
|
52
|
+
}, {
|
53
|
+
connected: () => {
|
54
|
+
setConnected(true);
|
55
|
+
dispatch({ type: 'jason/upsert', payload: { connected: true } });
|
56
|
+
console.debug('Connected to ActionCable');
|
57
|
+
// When AC loses connection - all state is lost, so we need to re-initialize all subscriptions
|
58
|
+
lodash_1.default.values(configs).forEach(config => createSubscription(config));
|
59
|
+
},
|
60
|
+
received: payload => {
|
61
|
+
handlePayload(payload);
|
62
|
+
console.debug("ActionCable Payload received: ", payload);
|
63
|
+
},
|
64
|
+
disconnected: () => {
|
65
|
+
setConnected(false);
|
66
|
+
dispatch({ type: 'jason/upsert', payload: { connected: false } });
|
67
|
+
console.warn('Disconnected from ActionCable');
|
68
|
+
}
|
69
|
+
}));
|
70
|
+
function createSubscription(config) {
|
71
|
+
// We need the hash to be consistent in Ruby / Javascript
|
72
|
+
const hashableConfig = lodash_1.default(Object.assign({ conditions: {}, includes: {} }, config)).toPairs().sortBy(0).fromPairs().value();
|
73
|
+
const md5Hash = blueimp_md5_1.default(JSON.stringify(hashableConfig));
|
74
|
+
payloadHandlers[md5Hash] = createPayloadHandler_1.default({ dispatch, serverActionQueue, subscription, config });
|
75
|
+
configs[md5Hash] = hashableConfig;
|
76
|
+
setTimeout(() => subscription.send({ createSubscription: hashableConfig }), 500);
|
77
|
+
return {
|
78
|
+
remove: () => removeSubscription(hashableConfig),
|
79
|
+
md5Hash
|
80
|
+
};
|
81
|
+
}
|
82
|
+
function removeSubscription(config) {
|
83
|
+
subscription.send({ removeSubscription: config });
|
84
|
+
const md5Hash = blueimp_md5_1.default(JSON.stringify(config));
|
85
|
+
delete payloadHandlers[md5Hash];
|
86
|
+
delete configs[md5Hash];
|
87
|
+
}
|
88
|
+
setValue({
|
89
|
+
actions: actions,
|
90
|
+
subscribe: config => createSubscription(config),
|
91
|
+
eager,
|
92
|
+
handlePayload
|
93
|
+
});
|
94
|
+
setStore(store);
|
95
|
+
});
|
96
|
+
}, []);
|
97
|
+
return [store, value, connected];
|
98
|
+
}
|
99
|
+
exports.default = useJason;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,79 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
const react_hooks_1 = require("@testing-library/react-hooks");
|
16
|
+
const useJason_1 = __importDefault(require("./useJason"));
|
17
|
+
const restClient_1 = __importDefault(require("./restClient"));
|
18
|
+
jest.mock('./restClient');
|
19
|
+
test('it works', () => __awaiter(void 0, void 0, void 0, function* () {
|
20
|
+
const resp = { data: { post: {} } };
|
21
|
+
// @ts-ignore
|
22
|
+
restClient_1.default.get.mockResolvedValue(resp);
|
23
|
+
const { result, waitForNextUpdate } = react_hooks_1.renderHook(() => useJason_1.default({ reducers: {
|
24
|
+
test: (s, a) => s || {}
|
25
|
+
} }));
|
26
|
+
yield waitForNextUpdate();
|
27
|
+
const [store, value, connected] = result.current;
|
28
|
+
const { handlePayload, subscribe } = value;
|
29
|
+
const subscription = subscribe({ post: {} });
|
30
|
+
handlePayload({
|
31
|
+
type: 'payload',
|
32
|
+
model: 'post',
|
33
|
+
payload: [{ id: 4, name: 'test' }],
|
34
|
+
md5Hash: subscription.md5Hash,
|
35
|
+
idx: 1
|
36
|
+
});
|
37
|
+
handlePayload({
|
38
|
+
id: 4,
|
39
|
+
model: 'post',
|
40
|
+
destroy: true,
|
41
|
+
md5Hash: subscription.md5Hash,
|
42
|
+
idx: 2
|
43
|
+
});
|
44
|
+
handlePayload({
|
45
|
+
id: 5,
|
46
|
+
model: 'post',
|
47
|
+
payload: { id: 5, name: 'test2' },
|
48
|
+
md5Hash: subscription.md5Hash,
|
49
|
+
idx: 3
|
50
|
+
});
|
51
|
+
}));
|
52
|
+
test('pruning IDs', () => __awaiter(void 0, void 0, void 0, function* () {
|
53
|
+
const resp = { data: { post: {} } };
|
54
|
+
// @ts-ignore
|
55
|
+
restClient_1.default.get.mockResolvedValue(resp);
|
56
|
+
const { result, waitForNextUpdate } = react_hooks_1.renderHook(() => useJason_1.default({ reducers: {
|
57
|
+
test: (s, a) => s || {}
|
58
|
+
} }));
|
59
|
+
yield waitForNextUpdate();
|
60
|
+
const [store, value, connected] = result.current;
|
61
|
+
const { handlePayload, subscribe } = value;
|
62
|
+
const subscription = subscribe({ post: {} });
|
63
|
+
handlePayload({
|
64
|
+
type: 'payload',
|
65
|
+
model: 'post',
|
66
|
+
payload: [{ id: 4, name: 'test' }],
|
67
|
+
md5Hash: subscription.md5Hash,
|
68
|
+
idx: 1
|
69
|
+
});
|
70
|
+
handlePayload({
|
71
|
+
type: 'payload',
|
72
|
+
model: 'post',
|
73
|
+
payload: [{ id: 5, name: 'test it out' }],
|
74
|
+
md5Hash: subscription.md5Hash,
|
75
|
+
idx: 2
|
76
|
+
});
|
77
|
+
// The ID 4 should have been pruned
|
78
|
+
expect(store.getState().posts.ids).toStrictEqual([5]);
|
79
|
+
}));
|
data/client/lib/useSub.js
CHANGED
data/client/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@jamesr2323/jason",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.5.0",
|
4
4
|
"module": "./lib/index.js",
|
5
5
|
"types": "./lib/index.d.ts",
|
6
6
|
"scripts": {
|
@@ -23,11 +23,12 @@
|
|
23
23
|
"@babel/preset-env": "^7.12.11",
|
24
24
|
"@babel/preset-typescript": "^7.12.7",
|
25
25
|
"@reduxjs/toolkit": "^1.5.0",
|
26
|
+
"@testing-library/react-hooks": "^5.0.3",
|
26
27
|
"@types/jest": "^26.0.19",
|
27
28
|
"babel-jest": "^26.6.3",
|
28
29
|
"jest": "^26.6.3",
|
29
|
-
"react": "^16.
|
30
|
-
"react-dom": "^16.
|
30
|
+
"react": "^16.9.x",
|
31
|
+
"react-dom": "^16.9.x",
|
31
32
|
"react-redux": "^7.2.2",
|
32
33
|
"typescript": "^4.1.2"
|
33
34
|
},
|
@@ -1,130 +1,12 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import JasonContext from './JasonContext'
|
4
|
-
import axios from 'axios'
|
5
|
-
import applyCaseMiddleware from 'axios-case-converter'
|
1
|
+
import React from 'react'
|
2
|
+
import useJason from './useJason'
|
6
3
|
import { Provider } from 'react-redux'
|
7
|
-
import
|
8
|
-
import createJasonReducers from './createJasonReducers'
|
9
|
-
import createPayloadHandler from './createPayloadHandler'
|
10
|
-
import createOptDis from './createOptDis'
|
11
|
-
import makeEager from './makeEager'
|
12
|
-
import { camelizeKeys } from 'humps'
|
13
|
-
import md5 from 'blueimp-md5'
|
14
|
-
import _ from 'lodash'
|
15
|
-
import React, { useState, useEffect } from 'react'
|
16
|
-
import { validate as isUuid } from 'uuid'
|
4
|
+
import JasonContext from './JasonContext'
|
17
5
|
|
18
6
|
const JasonProvider = ({ reducers, middleware, extraActions, children }: { reducers?: any, middleware?: any, extraActions?: any, children?: React.FC }) => {
|
19
|
-
const [store,
|
20
|
-
const [value, setValue] = useState(null)
|
21
|
-
const [connected, setConnected] = useState(false)
|
22
|
-
|
23
|
-
const csrfToken = (document.querySelector("meta[name=csrf-token]") as any).content
|
24
|
-
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
|
25
|
-
const restClient = applyCaseMiddleware(axios.create(), {
|
26
|
-
preservedKeys: (key) => {
|
27
|
-
return isUuid(key)
|
28
|
-
}
|
29
|
-
})
|
30
|
-
|
31
|
-
useEffect(() => {
|
32
|
-
restClient.get('/jason/api/schema')
|
33
|
-
.then(({ data: snakey_schema }) => {
|
34
|
-
const schema = camelizeKeys(snakey_schema)
|
35
|
-
|
36
|
-
const serverActionQueue = function() {
|
37
|
-
const queue: any[] = []
|
38
|
-
let inFlight = false
|
39
|
-
|
40
|
-
return {
|
41
|
-
addItem: (item) => queue.push(item),
|
42
|
-
getItem: () => {
|
43
|
-
if (inFlight) return false
|
44
|
-
|
45
|
-
const item = queue.shift()
|
46
|
-
if (item) {
|
47
|
-
inFlight = true
|
48
|
-
return item
|
49
|
-
}
|
50
|
-
return false
|
51
|
-
},
|
52
|
-
itemProcessed: () => inFlight = false,
|
53
|
-
fullySynced: () => queue.length === 0 && !inFlight,
|
54
|
-
getData: () => ({ queue, inFlight })
|
55
|
-
}
|
56
|
-
}()
|
57
|
-
|
58
|
-
const consumer = createConsumer()
|
59
|
-
const allReducers = {
|
60
|
-
...reducers,
|
61
|
-
...createJasonReducers(schema)
|
62
|
-
}
|
63
|
-
|
64
|
-
console.log({ schema, allReducers })
|
65
|
-
const store = configureStore({ reducer: allReducers, middleware })
|
66
|
-
|
67
|
-
let payloadHandlers = {}
|
68
|
-
function handlePayload(payload) {
|
69
|
-
const { model, md5Hash } = payload
|
70
|
-
console.log({ md5Hash, fn: `${model}:${md5Hash}`, payloadHandlers, model: _.camelCase(model), payload })
|
71
|
-
const handler = payloadHandlers[`${_.camelCase(model)}:${md5Hash}`]
|
72
|
-
if (handler) {
|
73
|
-
handler({ ...payload, model: _.camelCase(model) })
|
74
|
-
}
|
75
|
-
}
|
76
|
-
|
77
|
-
const subscription = (consumer.subscriptions.create({
|
78
|
-
channel: 'Jason::Channel'
|
79
|
-
}, {
|
80
|
-
connected: () => {
|
81
|
-
setConnected(true)
|
82
|
-
},
|
83
|
-
received: payload => {
|
84
|
-
console.log("Payload received", payload)
|
85
|
-
handlePayload(payload)
|
86
|
-
},
|
87
|
-
disconnected: () => console.warn('Disconnected from ActionCable')
|
88
|
-
}));
|
89
|
-
|
90
|
-
console.log('sending message')
|
91
|
-
subscription.send({ message: 'test' })
|
92
|
-
|
93
|
-
function createSubscription(config) {
|
94
|
-
const md5Hash = md5(JSON.stringify(config))
|
95
|
-
console.log('Subscribe with', config, md5Hash)
|
96
|
-
|
97
|
-
_.map(config, (v, model) => {
|
98
|
-
payloadHandlers[`${model}:${md5Hash}`] = createPayloadHandler(store.dispatch, serverActionQueue, subscription, model, schema[model])
|
99
|
-
})
|
100
|
-
subscription.send({ createSubscription: config })
|
101
|
-
|
102
|
-
return () => removeSubscription(config)
|
103
|
-
}
|
104
|
-
|
105
|
-
function removeSubscription(config) {
|
106
|
-
subscription.send({ removeSubscription: config })
|
107
|
-
const md5Hash = md5(JSON.stringify(config))
|
108
|
-
_.map(config, (v, model) => {
|
109
|
-
delete payloadHandlers[`${model}:${md5Hash}`]
|
110
|
-
})
|
111
|
-
}
|
112
|
-
const optDis = createOptDis(schema, store.dispatch, restClient, serverActionQueue)
|
113
|
-
const actions = createActions(schema, store, restClient, optDis, extraActions)
|
114
|
-
const eager = makeEager(schema)
|
115
|
-
|
116
|
-
console.log({ actions })
|
117
|
-
|
118
|
-
setValue({
|
119
|
-
actions: actions,
|
120
|
-
subscribe: (config) => createSubscription(config),
|
121
|
-
eager
|
122
|
-
})
|
123
|
-
setStore(store)
|
124
|
-
})
|
125
|
-
}, [])
|
7
|
+
const [store, value, connected] = useJason({ reducers, middleware, extraActions })
|
126
8
|
|
127
|
-
if(!(store && value
|
9
|
+
if(!(store && value)) return <div /> // Wait for async fetch of schema to complete
|
128
10
|
|
129
11
|
return <Provider store={store}>
|
130
12
|
<JasonContext.Provider value={value}>{ children }</JasonContext.Provider>
|