ar_sync 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/core/ActionCableAdapter.js +19 -17
- data/core/ArSyncApi.js +112 -48
- data/core/ArSyncModel.js +69 -60
- data/core/ArSyncStore.js +401 -314
- data/core/ConnectionManager.js +43 -38
- data/core/hooks.d.ts +4 -0
- data/core/hooks.js +61 -36
- data/lib/ar_sync/type_script.rb +9 -8
- data/lib/ar_sync/version.rb +1 -1
- data/src/core/DataType.ts +1 -1
- data/src/core/hooks.ts +29 -5
- data/tsconfig.json +2 -2
- metadata +2 -2
data/core/ConnectionManager.js
CHANGED
@@ -1,75 +1,80 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
|
4
|
-
|
3
|
+
var ConnectionManager = /** @class */ (function () {
|
4
|
+
function ConnectionManager(adapter) {
|
5
|
+
var _this = this;
|
5
6
|
this.subscriptions = {};
|
6
7
|
this.adapter = adapter;
|
7
8
|
this.networkListeners = {};
|
8
9
|
this.networkListenerSerial = 0;
|
9
10
|
this.networkStatus = true;
|
10
|
-
adapter.ondisconnect = ()
|
11
|
-
|
12
|
-
|
11
|
+
adapter.ondisconnect = function () {
|
12
|
+
_this.unsubscribeAll();
|
13
|
+
_this.triggerNetworkChange(false);
|
13
14
|
};
|
14
|
-
adapter.onreconnect = ()
|
15
|
+
adapter.onreconnect = function () { return _this.triggerNetworkChange(true); };
|
15
16
|
}
|
16
|
-
triggerNetworkChange(status) {
|
17
|
+
ConnectionManager.prototype.triggerNetworkChange = function (status) {
|
17
18
|
if (this.networkStatus == status)
|
18
19
|
return;
|
19
20
|
this.networkStatus = status;
|
20
|
-
for (
|
21
|
+
for (var id in this.networkListeners)
|
21
22
|
this.networkListeners[id](status);
|
22
|
-
}
|
23
|
-
unsubscribeAll() {
|
24
|
-
for (
|
25
|
-
|
23
|
+
};
|
24
|
+
ConnectionManager.prototype.unsubscribeAll = function () {
|
25
|
+
for (var id in this.subscriptions) {
|
26
|
+
var subscription = this.subscriptions[id];
|
26
27
|
subscription.listeners = {};
|
27
28
|
subscription.connection.unsubscribe();
|
28
29
|
}
|
29
30
|
this.subscriptions = {};
|
30
|
-
}
|
31
|
-
subscribeNetwork(func) {
|
32
|
-
|
31
|
+
};
|
32
|
+
ConnectionManager.prototype.subscribeNetwork = function (func) {
|
33
|
+
var _this = this;
|
34
|
+
var id = this.networkListenerSerial++;
|
33
35
|
this.networkListeners[id] = func;
|
34
|
-
|
35
|
-
delete
|
36
|
+
var unsubscribe = function () {
|
37
|
+
delete _this.networkListeners[id];
|
36
38
|
};
|
37
|
-
return { unsubscribe };
|
38
|
-
}
|
39
|
-
subscribe(key, func) {
|
40
|
-
|
41
|
-
|
39
|
+
return { unsubscribe: unsubscribe };
|
40
|
+
};
|
41
|
+
ConnectionManager.prototype.subscribe = function (key, func) {
|
42
|
+
var _this = this;
|
43
|
+
var subscription = this.connect(key);
|
44
|
+
var id = subscription.serial++;
|
42
45
|
subscription.ref++;
|
43
46
|
subscription.listeners[id] = func;
|
44
|
-
|
47
|
+
var unsubscribe = function () {
|
45
48
|
if (!subscription.listeners[id])
|
46
49
|
return;
|
47
50
|
delete subscription.listeners[id];
|
48
51
|
subscription.ref--;
|
49
52
|
if (subscription.ref === 0)
|
50
|
-
|
53
|
+
_this.disconnect(key);
|
51
54
|
};
|
52
|
-
return { unsubscribe };
|
53
|
-
}
|
54
|
-
connect(key) {
|
55
|
+
return { unsubscribe: unsubscribe };
|
56
|
+
};
|
57
|
+
ConnectionManager.prototype.connect = function (key) {
|
58
|
+
var _this = this;
|
55
59
|
if (this.subscriptions[key])
|
56
60
|
return this.subscriptions[key];
|
57
|
-
|
58
|
-
return this.subscriptions[key] = { connection, listeners: {}, ref: 0, serial: 0 };
|
59
|
-
}
|
60
|
-
disconnect(key) {
|
61
|
-
|
61
|
+
var connection = this.adapter.subscribe(key, function (data) { return _this.received(key, data); });
|
62
|
+
return this.subscriptions[key] = { connection: connection, listeners: {}, ref: 0, serial: 0 };
|
63
|
+
};
|
64
|
+
ConnectionManager.prototype.disconnect = function (key) {
|
65
|
+
var subscription = this.subscriptions[key];
|
62
66
|
if (!subscription || subscription.ref !== 0)
|
63
67
|
return;
|
64
68
|
delete this.subscriptions[key];
|
65
69
|
subscription.connection.unsubscribe();
|
66
|
-
}
|
67
|
-
received(key, data) {
|
68
|
-
|
70
|
+
};
|
71
|
+
ConnectionManager.prototype.received = function (key, data) {
|
72
|
+
var subscription = this.subscriptions[key];
|
69
73
|
if (!subscription)
|
70
74
|
return;
|
71
|
-
for (
|
75
|
+
for (var id in subscription.listeners)
|
72
76
|
subscription.listeners[id](data);
|
73
|
-
}
|
74
|
-
|
77
|
+
};
|
78
|
+
return ConnectionManager;
|
79
|
+
}());
|
75
80
|
exports.default = ConnectionManager;
|
data/core/hooks.d.ts
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
declare let useState: <T>(t: T | (() => T)) => [T, (t: T | ((t: T) => T)) => void];
|
2
2
|
declare let useEffect: (f: (() => void) | (() => (() => void)), deps: any[]) => void;
|
3
3
|
declare let useMemo: <T>(f: () => T, deps: any[]) => T;
|
4
|
+
declare let useRef: <T>(value: T) => {
|
5
|
+
current: T;
|
6
|
+
};
|
4
7
|
declare type InitializeHooksParams = {
|
5
8
|
useState: typeof useState;
|
6
9
|
useEffect: typeof useEffect;
|
7
10
|
useMemo: typeof useMemo;
|
11
|
+
useRef: typeof useRef;
|
8
12
|
};
|
9
13
|
export declare function initializeHooks(hooks: InitializeHooksParams): void;
|
10
14
|
interface ModelStatus {
|
data/core/hooks.js
CHANGED
@@ -1,37 +1,43 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
var ArSyncApi_1 = require("./ArSyncApi");
|
4
|
+
var ArSyncModel_1 = require("./ArSyncModel");
|
5
|
+
var useState;
|
6
|
+
var useEffect;
|
7
|
+
var useMemo;
|
8
|
+
var useRef;
|
8
9
|
function initializeHooks(hooks) {
|
9
10
|
useState = hooks.useState;
|
10
11
|
useEffect = hooks.useEffect;
|
11
12
|
useMemo = hooks.useMemo;
|
13
|
+
useRef = hooks.useRef;
|
12
14
|
}
|
13
15
|
exports.initializeHooks = initializeHooks;
|
14
16
|
function checkHooks() {
|
15
17
|
if (!useState)
|
16
|
-
throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo })`';
|
18
|
+
throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo, useRef })`';
|
17
19
|
}
|
18
|
-
|
20
|
+
var initialResult = [null, { complete: false, notfound: undefined, connected: true }];
|
19
21
|
function useArSyncModel(request) {
|
20
22
|
checkHooks();
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
var _a = useState(initialResult), result = _a[0], setResult = _a[1];
|
24
|
+
var requestString = JSON.stringify(request && request.params);
|
25
|
+
var prevRequestStringRef = useRef(requestString);
|
26
|
+
useEffect(function () {
|
27
|
+
prevRequestStringRef.current = requestString;
|
24
28
|
if (!request) {
|
25
29
|
setResult(initialResult);
|
26
|
-
return ()
|
30
|
+
return function () { };
|
27
31
|
}
|
28
|
-
|
32
|
+
var model = new ArSyncModel_1.default(request, { immutable: true });
|
29
33
|
function update() {
|
30
|
-
|
31
|
-
setResult(resultWas
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
var complete = model.complete, notfound = model.notfound, connected = model.connected, data = model.data;
|
35
|
+
setResult(function (resultWas) {
|
36
|
+
var dataWas = resultWas[0], statusWas = resultWas[1];
|
37
|
+
var statusPersisted = statusWas.complete === complete && statusWas.notfound === notfound && statusWas.connected === connected;
|
38
|
+
if (dataWas === data && statusPersisted)
|
39
|
+
return resultWas;
|
40
|
+
var status = statusPersisted ? statusWas : { complete: complete, notfound: notfound, connected: connected };
|
35
41
|
return [data, status];
|
36
42
|
});
|
37
43
|
}
|
@@ -41,21 +47,38 @@ function useArSyncModel(request) {
|
|
41
47
|
else {
|
42
48
|
setResult(initialResult);
|
43
49
|
}
|
50
|
+
model.subscribe('load', update);
|
44
51
|
model.subscribe('change', update);
|
45
52
|
model.subscribe('connection', update);
|
46
|
-
return ()
|
53
|
+
return function () { return model.release(); };
|
47
54
|
}, [requestString]);
|
48
|
-
return result;
|
55
|
+
return prevRequestStringRef.current === requestString ? result : initialResult;
|
49
56
|
}
|
50
57
|
exports.useArSyncModel = useArSyncModel;
|
51
|
-
|
58
|
+
var initialFetchState = { data: null, status: { complete: false, notfound: undefined } };
|
59
|
+
function extractParams(query, output) {
|
60
|
+
if (output === void 0) { output = []; }
|
61
|
+
if (typeof (query) !== 'object' || query == null || Array.isArray(query))
|
62
|
+
return output;
|
63
|
+
if ('params' in query)
|
64
|
+
output.push(query.params);
|
65
|
+
for (var key in query) {
|
66
|
+
extractParams(query[key], output);
|
67
|
+
}
|
68
|
+
return output;
|
69
|
+
}
|
52
70
|
function useArSyncFetch(request) {
|
53
71
|
checkHooks();
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
72
|
+
var _a = useState(initialFetchState), state = _a[0], setState = _a[1];
|
73
|
+
var query = request && request.query;
|
74
|
+
var params = request && request.params;
|
75
|
+
var requestString = useMemo(function () {
|
76
|
+
return JSON.stringify(extractParams(query, [params]));
|
77
|
+
}, [query, params]);
|
78
|
+
var prevRequestStringRef = useRef(requestString);
|
79
|
+
var loader = useMemo(function () {
|
80
|
+
var lastLoadId = 0;
|
81
|
+
var timer = null;
|
59
82
|
function cancel() {
|
60
83
|
if (timer)
|
61
84
|
clearTimeout(timer);
|
@@ -64,28 +87,28 @@ function useArSyncFetch(request) {
|
|
64
87
|
}
|
65
88
|
function fetch(request, retryCount) {
|
66
89
|
cancel();
|
67
|
-
|
68
|
-
ArSyncApi_1.default.fetch(request).then((response)
|
90
|
+
var currentLoadingId = lastLoadId;
|
91
|
+
ArSyncApi_1.default.fetch(request).then(function (response) {
|
69
92
|
if (currentLoadingId !== lastLoadId)
|
70
93
|
return;
|
71
94
|
setState({ data: response, status: { complete: true, notfound: false } });
|
72
|
-
}).catch(e
|
95
|
+
}).catch(function (e) {
|
73
96
|
if (currentLoadingId !== lastLoadId)
|
74
97
|
return;
|
75
98
|
if (!e.retry) {
|
76
99
|
setState({ data: null, status: { complete: true, notfound: true } });
|
77
100
|
return;
|
78
101
|
}
|
79
|
-
timer = setTimeout(()
|
102
|
+
timer = setTimeout(function () { return fetch(request, retryCount + 1); }, 1000 * Math.min(Math.pow(4, retryCount), 30));
|
80
103
|
});
|
81
104
|
}
|
82
105
|
function update() {
|
83
106
|
if (request) {
|
84
|
-
setState(state
|
85
|
-
|
107
|
+
setState(function (state) {
|
108
|
+
var data = state.data, status = state.status;
|
86
109
|
if (!status.complete && status.notfound === undefined)
|
87
110
|
return state;
|
88
|
-
return { data, status: { complete: false, notfound: undefined } };
|
111
|
+
return { data: data, status: { complete: false, notfound: undefined } };
|
89
112
|
});
|
90
113
|
fetch(request, 0);
|
91
114
|
}
|
@@ -93,13 +116,15 @@ function useArSyncFetch(request) {
|
|
93
116
|
setState(initialFetchState);
|
94
117
|
}
|
95
118
|
}
|
96
|
-
return { update, cancel };
|
119
|
+
return { update: update, cancel: cancel };
|
97
120
|
}, [requestString]);
|
98
|
-
useEffect(()
|
121
|
+
useEffect(function () {
|
122
|
+
prevRequestStringRef.current = requestString;
|
99
123
|
setState(initialFetchState);
|
100
124
|
loader.update();
|
101
|
-
return ()
|
125
|
+
return function () { return loader.cancel(); };
|
102
126
|
}, [requestString]);
|
103
|
-
|
127
|
+
var responseState = prevRequestStringRef.current === requestString ? state : initialFetchState;
|
128
|
+
return [responseState.data, responseState.status, loader.update];
|
104
129
|
}
|
105
130
|
exports.useArSyncFetch = useArSyncFetch;
|
data/lib/ar_sync/type_script.rb
CHANGED
@@ -75,23 +75,24 @@ module ArSync::TypeScript
|
|
75
75
|
<<~CODE
|
76
76
|
import { TypeRequest, ApiNameRequests } from './types'
|
77
77
|
import { DataTypeFromRequest as DataTypeFromRequestPair } from 'ar_sync/core/DataType'
|
78
|
-
type
|
78
|
+
export type NeverMatchArgument = { __nevermatch: never }
|
79
|
+
type DataTypeFromRequest<R extends TypeRequest | NeverMatchArgument> = NeverMatchArgument extends R ? never : R extends TypeRequest ? DataTypeFromRequestPair<ApiNameRequests[R['api']], R> : never
|
79
80
|
export default DataTypeFromRequest
|
80
81
|
CODE
|
81
82
|
end
|
82
83
|
|
83
84
|
def self.generate_hooks_script
|
84
85
|
<<~CODE
|
85
|
-
import { useState, useEffect, useMemo } from 'react'
|
86
|
+
import { useState, useEffect, useMemo, useRef } from 'react'
|
86
87
|
import { TypeRequest } from './types'
|
87
|
-
import DataTypeFromRequest from './DataTypeFromRequest'
|
88
|
+
import DataTypeFromRequest, { NeverMatchArgument } from './DataTypeFromRequest'
|
88
89
|
import { initializeHooks, useArSyncModel as useArSyncModelBase, useArSyncFetch as useArSyncFetchBase } from 'ar_sync/core/hooks'
|
89
|
-
initializeHooks({ useState, useEffect, useMemo })
|
90
|
-
export function useArSyncModel<R extends TypeRequest>(request: R | null) {
|
91
|
-
return useArSyncModelBase<DataTypeFromRequest<R>>(request)
|
90
|
+
initializeHooks({ useState, useEffect, useMemo, useRef })
|
91
|
+
export function useArSyncModel<R extends TypeRequest | NeverMatchArgument>(request: R | null) {
|
92
|
+
return useArSyncModelBase<DataTypeFromRequest<R>>(request as TypeRequest)
|
92
93
|
}
|
93
|
-
export function useArSyncFetch<R extends TypeRequest>(request: R | null) {
|
94
|
-
return useArSyncFetchBase<DataTypeFromRequest<R>>(request)
|
94
|
+
export function useArSyncFetch<R extends TypeRequest | NeverMatchArgument>(request: R | null) {
|
95
|
+
return useArSyncFetchBase<DataTypeFromRequest<R>>(request as TypeRequest)
|
95
96
|
}
|
96
97
|
CODE
|
97
98
|
end
|
data/lib/ar_sync/version.rb
CHANGED
data/src/core/DataType.ts
CHANGED
@@ -83,4 +83,4 @@ type DataTypeBaseFromRequestType<R extends RequestBase, ID> = R extends { _meta?
|
|
83
83
|
: never
|
84
84
|
export type DataTypeFromRequest<Req extends RequestBase, R extends RequestBase> = ValidateDataTypeExtraFileds<
|
85
85
|
DataTypeFromQuery<DataTypeBaseFromRequestType<Req, R['id']>, R['query']>
|
86
|
-
>
|
86
|
+
>
|
data/src/core/hooks.ts
CHANGED
@@ -4,18 +4,21 @@ import ArSyncModel from './ArSyncModel'
|
|
4
4
|
let useState: <T>(t: T | (() => T)) => [T, (t: T | ((t: T) => T)) => void]
|
5
5
|
let useEffect: (f: (() => void) | (() => (() => void)), deps: any[]) => void
|
6
6
|
let useMemo: <T>(f: () => T, deps: any[]) => T
|
7
|
+
let useRef: <T>(value: T) => { current: T }
|
7
8
|
type InitializeHooksParams = {
|
8
9
|
useState: typeof useState
|
9
10
|
useEffect: typeof useEffect
|
10
11
|
useMemo: typeof useMemo
|
12
|
+
useRef: typeof useRef
|
11
13
|
}
|
12
14
|
export function initializeHooks(hooks: InitializeHooksParams) {
|
13
15
|
useState = hooks.useState
|
14
16
|
useEffect = hooks.useEffect
|
15
17
|
useMemo = hooks.useMemo
|
18
|
+
useRef = hooks.useRef
|
16
19
|
}
|
17
20
|
function checkHooks() {
|
18
|
-
if (!useState) throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo })`'
|
21
|
+
if (!useState) throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo, useRef })`'
|
19
22
|
}
|
20
23
|
|
21
24
|
interface ModelStatus { complete: boolean; notfound?: boolean; connected: boolean }
|
@@ -27,7 +30,9 @@ export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
|
27
30
|
checkHooks()
|
28
31
|
const [result, setResult] = useState<DataAndStatus<T>>(initialResult)
|
29
32
|
const requestString = JSON.stringify(request && request.params)
|
33
|
+
const prevRequestStringRef = useRef(requestString)
|
30
34
|
useEffect(() => {
|
35
|
+
prevRequestStringRef.current = requestString
|
31
36
|
if (!request) {
|
32
37
|
setResult(initialResult)
|
33
38
|
return () => {}
|
@@ -36,8 +41,9 @@ export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
|
36
41
|
function update() {
|
37
42
|
const { complete, notfound, connected, data } = model
|
38
43
|
setResult(resultWas => {
|
39
|
-
const [, statusWas] = resultWas
|
44
|
+
const [dataWas, statusWas] = resultWas
|
40
45
|
const statusPersisted = statusWas.complete === complete && statusWas.notfound === notfound && statusWas.connected === connected
|
46
|
+
if (dataWas === data && statusPersisted) return resultWas
|
41
47
|
const status = statusPersisted ? statusWas : { complete, notfound, connected }
|
42
48
|
return [data, status]
|
43
49
|
})
|
@@ -47,21 +53,37 @@ export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
|
47
53
|
} else {
|
48
54
|
setResult(initialResult)
|
49
55
|
}
|
56
|
+
model.subscribe('load', update)
|
50
57
|
model.subscribe('change', update)
|
51
58
|
model.subscribe('connection', update)
|
52
59
|
return () => model.release()
|
53
60
|
}, [requestString])
|
54
|
-
return result
|
61
|
+
return prevRequestStringRef.current === requestString ? result : initialResult
|
55
62
|
}
|
56
63
|
|
57
64
|
interface FetchStatus { complete: boolean; notfound?: boolean }
|
58
65
|
type DataStatusUpdate<T> = [T | null, FetchStatus, () => void]
|
59
66
|
type FetchState<T> = { data: T | null; status: FetchStatus }
|
60
67
|
const initialFetchState: FetchState<any> = { data: null, status: { complete: false, notfound: undefined } }
|
68
|
+
|
69
|
+
function extractParams(query: unknown, output: any[] = []): any[] {
|
70
|
+
if (typeof(query) !== 'object' || query == null || Array.isArray(query)) return output
|
71
|
+
if ('params' in query) output.push((query as { params: any }).params)
|
72
|
+
for (const key in query) {
|
73
|
+
extractParams(query[key], output)
|
74
|
+
}
|
75
|
+
return output
|
76
|
+
}
|
77
|
+
|
61
78
|
export function useArSyncFetch<T>(request: Request | null): DataStatusUpdate<T> {
|
62
79
|
checkHooks()
|
63
80
|
const [state, setState] = useState<FetchState<T>>(initialFetchState)
|
64
|
-
const
|
81
|
+
const query = request && request.query
|
82
|
+
const params = request && request.params
|
83
|
+
const requestString = useMemo(() => {
|
84
|
+
return JSON.stringify(extractParams(query, [params]))
|
85
|
+
}, [query, params])
|
86
|
+
const prevRequestStringRef = useRef(requestString)
|
65
87
|
const loader = useMemo(() => {
|
66
88
|
let lastLoadId = 0
|
67
89
|
let timer: null | number = null
|
@@ -100,9 +122,11 @@ export function useArSyncFetch<T>(request: Request | null): DataStatusUpdate<T>
|
|
100
122
|
return { update, cancel }
|
101
123
|
}, [requestString])
|
102
124
|
useEffect(() => {
|
125
|
+
prevRequestStringRef.current = requestString
|
103
126
|
setState(initialFetchState)
|
104
127
|
loader.update()
|
105
128
|
return () => loader.cancel()
|
106
129
|
}, [requestString])
|
107
|
-
|
130
|
+
const responseState = prevRequestStringRef.current === requestString ? state : initialFetchState
|
131
|
+
return [responseState.data, responseState.status, loader.update]
|
108
132
|
}
|
data/tsconfig.json
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
{
|
2
2
|
"compilerOptions": {
|
3
|
-
"incremental": true,
|
4
3
|
"outDir": ".",
|
5
4
|
"rootDir": "./src",
|
6
5
|
"module": "commonjs",
|
7
|
-
"target": "
|
6
|
+
"target": "es5",
|
7
|
+
"lib": ["es2017", "dom"],
|
8
8
|
"strictNullChecks": true,
|
9
9
|
"noUnusedLocals": true,
|
10
10
|
"noUnusedParameters": true,
|