jason-rails 0.4.1 → 0.5.0
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/.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>
|