ar_sync 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +53 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +128 -0
  8. data/Rakefile +10 -0
  9. data/ar_sync.gemspec +28 -0
  10. data/bin/console +12 -0
  11. data/bin/setup +8 -0
  12. data/core/ActioncableAdapter.d.ts +10 -0
  13. data/core/ActioncableAdapter.js +29 -0
  14. data/core/ArSyncApi.d.ts +5 -0
  15. data/core/ArSyncApi.js +74 -0
  16. data/core/ArSyncModelBase.d.ts +71 -0
  17. data/core/ArSyncModelBase.js +110 -0
  18. data/core/ConnectionAdapter.d.ts +7 -0
  19. data/core/ConnectionAdapter.js +2 -0
  20. data/core/ConnectionManager.d.ts +19 -0
  21. data/core/ConnectionManager.js +75 -0
  22. data/core/DataType.d.ts +60 -0
  23. data/core/DataType.js +2 -0
  24. data/core/hooksBase.d.ts +29 -0
  25. data/core/hooksBase.js +80 -0
  26. data/graph/ArSyncModel.d.ts +10 -0
  27. data/graph/ArSyncModel.js +22 -0
  28. data/graph/ArSyncStore.d.ts +28 -0
  29. data/graph/ArSyncStore.js +593 -0
  30. data/graph/hooks.d.ts +3 -0
  31. data/graph/hooks.js +10 -0
  32. data/graph/index.d.ts +2 -0
  33. data/graph/index.js +4 -0
  34. data/lib/ar_sync.rb +25 -0
  35. data/lib/ar_sync/class_methods.rb +215 -0
  36. data/lib/ar_sync/collection.rb +83 -0
  37. data/lib/ar_sync/config.rb +18 -0
  38. data/lib/ar_sync/core.rb +138 -0
  39. data/lib/ar_sync/field.rb +96 -0
  40. data/lib/ar_sync/instance_methods.rb +130 -0
  41. data/lib/ar_sync/rails.rb +155 -0
  42. data/lib/ar_sync/type_script.rb +80 -0
  43. data/lib/ar_sync/version.rb +3 -0
  44. data/lib/generators/ar_sync/install/install_generator.rb +87 -0
  45. data/lib/generators/ar_sync/types/types_generator.rb +11 -0
  46. data/package-lock.json +1115 -0
  47. data/package.json +19 -0
  48. data/src/core/ActioncableAdapter.ts +30 -0
  49. data/src/core/ArSyncApi.ts +75 -0
  50. data/src/core/ArSyncModelBase.ts +126 -0
  51. data/src/core/ConnectionAdapter.ts +5 -0
  52. data/src/core/ConnectionManager.ts +69 -0
  53. data/src/core/DataType.ts +73 -0
  54. data/src/core/hooksBase.ts +86 -0
  55. data/src/graph/ArSyncModel.ts +21 -0
  56. data/src/graph/ArSyncStore.ts +567 -0
  57. data/src/graph/hooks.ts +7 -0
  58. data/src/graph/index.ts +2 -0
  59. data/src/tree/ArSyncModel.ts +145 -0
  60. data/src/tree/ArSyncStore.ts +323 -0
  61. data/src/tree/hooks.ts +7 -0
  62. data/src/tree/index.ts +2 -0
  63. data/tree/ArSyncModel.d.ts +39 -0
  64. data/tree/ArSyncModel.js +143 -0
  65. data/tree/ArSyncStore.d.ts +21 -0
  66. data/tree/ArSyncStore.js +365 -0
  67. data/tree/hooks.d.ts +3 -0
  68. data/tree/hooks.js +10 -0
  69. data/tree/index.d.ts +2 -0
  70. data/tree/index.js +4 -0
  71. data/tsconfig.json +15 -0
  72. data/vendor/assets/javascripts/ar_sync_actioncable_adapter.js.erb +7 -0
  73. data/vendor/assets/javascripts/ar_sync_graph.js.erb +17 -0
  74. data/vendor/assets/javascripts/ar_sync_tree.js.erb +17 -0
  75. metadata +187 -0
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class ArSyncModelBase {
4
+ constructor(request, option) {
5
+ this._ref = this.refManagerClass().retrieveRef(request, option);
6
+ this._listenerSerial = 0;
7
+ this._listeners = {};
8
+ this.complete = false;
9
+ this.connected = this.connectionManager().networkStatus;
10
+ const setData = () => {
11
+ this.data = this._ref.model.data;
12
+ this.complete = this._ref.model.complete;
13
+ this.notfound = this._ref.model.notfound;
14
+ };
15
+ setData();
16
+ this.subscribe('load', setData);
17
+ this.subscribe('change', setData);
18
+ this.subscribe('connection', (status) => {
19
+ this.connected = status;
20
+ });
21
+ }
22
+ onload(callback) {
23
+ this.subscribeOnce('load', callback);
24
+ }
25
+ subscribeOnce(event, callback) {
26
+ const subscription = this.subscribe(event, (arg) => {
27
+ callback(arg);
28
+ subscription.unsubscribe();
29
+ });
30
+ return subscription;
31
+ }
32
+ subscribe(event, callback) {
33
+ const id = this._listenerSerial++;
34
+ const subscription = this._ref.model.subscribe(event, callback);
35
+ let unsubscribed = false;
36
+ const unsubscribe = () => {
37
+ unsubscribed = true;
38
+ subscription.unsubscribe();
39
+ delete this._listeners[id];
40
+ };
41
+ if (this.complete) {
42
+ if (event === 'load')
43
+ setTimeout(() => {
44
+ if (!unsubscribed)
45
+ callback();
46
+ }, 0);
47
+ if (event === 'change')
48
+ setTimeout(() => {
49
+ if (!unsubscribed)
50
+ callback({ path: [], value: this.data });
51
+ }, 0);
52
+ }
53
+ return this._listeners[id] = { unsubscribe };
54
+ }
55
+ release() {
56
+ for (const id in this._listeners)
57
+ this._listeners[id].unsubscribe();
58
+ this._listeners = {};
59
+ this.refManagerClass()._detach(this._ref);
60
+ this._ref = null;
61
+ }
62
+ static retrieveRef(request, option) {
63
+ const key = JSON.stringify([request, option]);
64
+ let ref = this._cache[key];
65
+ if (!ref) {
66
+ const model = this.createRefModel(request, option);
67
+ ref = this._cache[key] = { key, count: 0, timer: null, model };
68
+ }
69
+ this._attach(ref);
70
+ return ref;
71
+ }
72
+ static createRefModel(_request, _option) {
73
+ throw 'abstract method';
74
+ }
75
+ static _detach(ref) {
76
+ ref.count--;
77
+ const timeout = this.cacheTimeout;
78
+ if (ref.count !== 0)
79
+ return;
80
+ const timedout = () => {
81
+ ref.model.release();
82
+ delete this._cache[ref.key];
83
+ };
84
+ if (timeout) {
85
+ ref.timer = setTimeout(timedout, timeout);
86
+ }
87
+ else {
88
+ timedout();
89
+ }
90
+ }
91
+ static _attach(ref) {
92
+ ref.count++;
93
+ if (ref.timer)
94
+ clearTimeout(ref.timer);
95
+ }
96
+ static setConnectionAdapter(_adapter) { }
97
+ static waitForLoad(...models) {
98
+ return new Promise((resolve) => {
99
+ let count = 0;
100
+ for (const model of models) {
101
+ model.onload(() => {
102
+ count++;
103
+ if (models.length == count)
104
+ resolve(models);
105
+ });
106
+ }
107
+ });
108
+ }
109
+ }
110
+ exports.default = ArSyncModelBase;
@@ -0,0 +1,7 @@
1
+ export default interface ConnectionAdapter {
2
+ ondisconnect: (() => void) | null;
3
+ onreconnect: (() => void) | null;
4
+ subscribe(key: string, callback: (data: any) => void): {
5
+ unsubscribe: () => void;
6
+ };
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ export default class ConnectionManager {
2
+ subscriptions: any;
3
+ adapter: any;
4
+ networkListeners: any;
5
+ networkListenerSerial: any;
6
+ networkStatus: any;
7
+ constructor(adapter: any);
8
+ triggerNetworkChange(status: any): void;
9
+ unsubscribeAll(): void;
10
+ subscribeNetwork(func: any): {
11
+ unsubscribe: () => void;
12
+ };
13
+ subscribe(key: any, func: any): {
14
+ unsubscribe: () => void;
15
+ };
16
+ connect(key: any): any;
17
+ disconnect(key: any): void;
18
+ received(key: any, data: any): void;
19
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class ConnectionManager {
4
+ constructor(adapter) {
5
+ this.subscriptions = {};
6
+ this.adapter = adapter;
7
+ this.networkListeners = {};
8
+ this.networkListenerSerial = 0;
9
+ this.networkStatus = true;
10
+ adapter.ondisconnect = () => {
11
+ this.unsubscribeAll();
12
+ this.triggerNetworkChange(false);
13
+ };
14
+ adapter.onreconnect = () => this.triggerNetworkChange(true);
15
+ }
16
+ triggerNetworkChange(status) {
17
+ if (this.networkStatus == status)
18
+ return;
19
+ this.networkStatus = status;
20
+ for (const id in this.networkListeners)
21
+ this.networkListeners[id](status);
22
+ }
23
+ unsubscribeAll() {
24
+ for (const id in this.subscriptions) {
25
+ const subscription = this.subscriptions[id];
26
+ subscription.listeners = {};
27
+ subscription.connection.unsubscribe();
28
+ }
29
+ this.subscriptions = {};
30
+ }
31
+ subscribeNetwork(func) {
32
+ const id = this.networkListenerSerial++;
33
+ this.networkListeners[id] = func;
34
+ const unsubscribe = () => {
35
+ delete this.networkListeners[id];
36
+ };
37
+ return { unsubscribe };
38
+ }
39
+ subscribe(key, func) {
40
+ const subscription = this.connect(key);
41
+ const id = subscription.serial++;
42
+ subscription.ref++;
43
+ subscription.listeners[id] = func;
44
+ const unsubscribe = () => {
45
+ if (!subscription.listeners[id])
46
+ return;
47
+ delete subscription.listeners[id];
48
+ subscription.ref--;
49
+ if (subscription.ref === 0)
50
+ this.disconnect(key);
51
+ };
52
+ return { unsubscribe };
53
+ }
54
+ connect(key) {
55
+ if (this.subscriptions[key])
56
+ return this.subscriptions[key];
57
+ const connection = this.adapter.subscribe(key, data => this.received(key, data));
58
+ return this.subscriptions[key] = { connection, listeners: {}, ref: 0, serial: 0 };
59
+ }
60
+ disconnect(key) {
61
+ const subscription = this.subscriptions[key];
62
+ if (!subscription || subscription.ref !== 0)
63
+ return;
64
+ delete this.subscriptions[key];
65
+ subscription.connection.unsubscribe();
66
+ }
67
+ received(key, data) {
68
+ const subscription = this.subscriptions[key];
69
+ if (!subscription)
70
+ return;
71
+ for (const id in subscription.listeners)
72
+ subscription.listeners[id](data);
73
+ }
74
+ }
75
+ exports.default = ConnectionManager;
@@ -0,0 +1,60 @@
1
+ declare type RecordType = {
2
+ _meta?: {
3
+ query: any;
4
+ };
5
+ };
6
+ declare type Values<T> = T extends {
7
+ [K in keyof T]: infer U;
8
+ } ? U : never;
9
+ declare type DataTypeExtractField<BaseType, Key extends keyof BaseType> = BaseType[Key] extends RecordType ? (null extends BaseType[Key] ? {} | null : {}) : BaseType[Key] extends RecordType[] ? {}[] : BaseType[Key];
10
+ declare type DataTypeExtractFieldsFromQuery<BaseType, Fields> = '*' extends Fields ? {
11
+ [key in Exclude<keyof BaseType, '_meta'>]: DataTypeExtractField<BaseType, key>;
12
+ } : {
13
+ [key in Fields & keyof (BaseType)]: DataTypeExtractField<BaseType, key>;
14
+ };
15
+ interface ExtraFieldErrorType {
16
+ error: 'extraFieldError';
17
+ }
18
+ declare type DataTypeExtractFromQueryHash<BaseType, QueryType> = '*' extends keyof QueryType ? {
19
+ [key in Exclude<(keyof BaseType) | (keyof QueryType), '_meta' | '_params' | '*'>]: (key extends keyof BaseType ? (key extends keyof QueryType ? (QueryType[key] extends true ? DataTypeExtractField<BaseType, key> : DataTypeFromQuery<BaseType[key] & {}, QueryType[key]>) : DataTypeExtractField<BaseType, key>) : ExtraFieldErrorType);
20
+ } : {
21
+ [key in keyof QueryType]: (key extends keyof BaseType ? (QueryType[key] extends true ? DataTypeExtractField<BaseType, key> : DataTypeFromQuery<BaseType[key] & {}, QueryType[key]>) : ExtraFieldErrorType);
22
+ };
23
+ declare type _DataTypeFromQuery<BaseType, QueryType> = QueryType extends keyof BaseType | '*' ? DataTypeExtractFieldsFromQuery<BaseType, QueryType> : QueryType extends Readonly<(keyof BaseType | '*')[]> ? DataTypeExtractFieldsFromQuery<BaseType, Values<QueryType>> : QueryType extends {
24
+ as: string;
25
+ } ? {
26
+ error: 'type for alias field is not supported';
27
+ } | undefined : DataTypeExtractFromQueryHash<BaseType, QueryType>;
28
+ export declare type DataTypeFromQuery<BaseType, QueryType> = BaseType extends any[] ? CheckAttributesField<BaseType[0], QueryType>[] : null extends BaseType ? CheckAttributesField<BaseType & {}, QueryType> | null : CheckAttributesField<BaseType & {}, QueryType>;
29
+ declare type CheckAttributesField<P, Q> = Q extends {
30
+ attributes: infer R;
31
+ } ? _DataTypeFromQuery<P, R> : _DataTypeFromQuery<P, Q>;
32
+ declare type IsAnyCompareLeftType = {
33
+ __any: never;
34
+ };
35
+ declare type CollectExtraFields<Type, Path> = IsAnyCompareLeftType extends Type ? null : Type extends ExtraFieldErrorType ? Path : Type extends (infer R)[] ? _CollectExtraFields<R> : Type extends object ? _CollectExtraFields<Type> : null;
36
+ declare type _CollectExtraFields<Type> = keyof (Type) extends never ? null : Values<{
37
+ [key in keyof Type]: CollectExtraFields<Type[key], [key]>;
38
+ }>;
39
+ declare type SelectString<T> = T extends string ? T : never;
40
+ declare type _ValidateDataTypeExtraFileds<Extra, Type> = SelectString<Values<Extra>> extends never ? Type : {
41
+ error: {
42
+ extraFields: SelectString<Values<Extra>>;
43
+ };
44
+ };
45
+ declare type ValidateDataTypeExtraFileds<Type> = _ValidateDataTypeExtraFileds<CollectExtraFields<Type, []>, Type>;
46
+ declare type RequestBase = {
47
+ api: string;
48
+ query: any;
49
+ params?: any;
50
+ _meta?: {
51
+ data: any;
52
+ };
53
+ };
54
+ declare type DataTypeBaseFromRequestType<R> = R extends {
55
+ _meta?: {
56
+ data: infer DataType;
57
+ };
58
+ } ? DataType : never;
59
+ export declare type DataTypeFromRequest<Req extends RequestBase, R extends RequestBase> = ValidateDataTypeExtraFileds<DataTypeFromQuery<DataTypeBaseFromRequestType<Req>, R['query']>>;
60
+ export {};
data/core/DataType.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,29 @@
1
+ interface ModelStatus {
2
+ complete: boolean;
3
+ notfound?: boolean;
4
+ connected: boolean;
5
+ }
6
+ export declare type DataAndStatus<T> = [T | null, ModelStatus];
7
+ export interface Request {
8
+ api: string;
9
+ params?: any;
10
+ query: any;
11
+ }
12
+ interface ArSyncModel<T> {
13
+ data: T | null;
14
+ complete: boolean;
15
+ connected: boolean;
16
+ notfound?: boolean;
17
+ release(): void;
18
+ subscribe(type: any, callback: any): any;
19
+ }
20
+ export declare function useArSyncModelWithClass<T>(modelClass: {
21
+ new <T>(req: Request, option?: any): ArSyncModel<T>;
22
+ }, request: Request | null): DataAndStatus<T>;
23
+ interface FetchStatus {
24
+ complete: boolean;
25
+ notfound?: boolean;
26
+ }
27
+ declare type DataAndStatusAndUpdater<T> = [T | null, FetchStatus, () => void];
28
+ export declare function useArSyncFetch<T>(request: Request | null): DataAndStatusAndUpdater<T>;
29
+ export {};
data/core/hooksBase.js ADDED
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_1 = require("react");
4
+ const ArSyncApi_1 = require("./ArSyncApi");
5
+ function useArSyncModelWithClass(modelClass, request) {
6
+ const [data, setData] = react_1.useState(null);
7
+ const [status, setStatus] = react_1.useState({ complete: false, connected: true });
8
+ const updateStatus = (complete, notfound, connected) => {
9
+ if (complete === status.complete || notfound === status.notfound || connected === status.notfound)
10
+ return;
11
+ setStatus({ complete, notfound, connected });
12
+ };
13
+ react_1.useEffect(() => {
14
+ if (!request)
15
+ return () => { };
16
+ const model = new modelClass(request, { immutable: true });
17
+ if (model.complete)
18
+ setData(model.data);
19
+ updateStatus(model.complete, model.notfound, model.connected);
20
+ model.subscribe('change', () => {
21
+ updateStatus(model.complete, model.notfound, model.connected);
22
+ setData(model.data);
23
+ });
24
+ model.subscribe('connection', () => {
25
+ updateStatus(model.complete, model.notfound, model.connected);
26
+ });
27
+ return () => model.release();
28
+ }, [JSON.stringify(request && request.params)]);
29
+ return [data, status];
30
+ }
31
+ exports.useArSyncModelWithClass = useArSyncModelWithClass;
32
+ function useArSyncFetch(request) {
33
+ const [response, setResponse] = react_1.useState(null);
34
+ const [status, setStatus] = react_1.useState({ complete: false });
35
+ const requestString = JSON.stringify(request && request.params);
36
+ let canceled = false;
37
+ let timer = null;
38
+ const update = react_1.useCallback(() => {
39
+ if (!request) {
40
+ setStatus({ complete: false, notfound: undefined });
41
+ return () => { };
42
+ }
43
+ canceled = false;
44
+ timer = null;
45
+ const fetch = (count) => {
46
+ if (timer)
47
+ clearTimeout(timer);
48
+ timer = null;
49
+ ArSyncApi_1.default.fetch(request)
50
+ .then((response) => {
51
+ if (canceled)
52
+ return;
53
+ setResponse(response);
54
+ setStatus({ complete: true, notfound: false });
55
+ })
56
+ .catch(e => {
57
+ if (canceled)
58
+ return;
59
+ if (!e.retry) {
60
+ setResponse(null);
61
+ setStatus({ complete: true, notfound: true });
62
+ return;
63
+ }
64
+ timer = setTimeout(() => fetch(count + 1), 1000 * Math.min(4 ** count, 30));
65
+ });
66
+ };
67
+ fetch(0);
68
+ }, [requestString]);
69
+ react_1.useEffect(() => {
70
+ update();
71
+ return () => {
72
+ canceled = true;
73
+ if (timer)
74
+ clearTimeout(timer);
75
+ timer = null;
76
+ };
77
+ }, [requestString]);
78
+ return [response, status, update];
79
+ }
80
+ exports.useArSyncFetch = useArSyncFetch;
@@ -0,0 +1,10 @@
1
+ import ArSyncModelBase from '../core/ArSyncModelBase';
2
+ import ConnectionAdapter from '../core/ConnectionAdapter';
3
+ export default class ArSyncModel<T> extends ArSyncModelBase<T> {
4
+ static setConnectionAdapter(adapter: ConnectionAdapter): void;
5
+ static createRefModel(request: any, option: any): any;
6
+ refManagerClass(): typeof ArSyncModel;
7
+ connectionManager(): any;
8
+ static _cache: {};
9
+ static cacheTimeout: number;
10
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ArSyncStore_1 = require("./ArSyncStore");
4
+ const ConnectionManager_1 = require("../core/ConnectionManager");
5
+ const ArSyncModelBase_1 = require("../core/ArSyncModelBase");
6
+ class ArSyncModel extends ArSyncModelBase_1.default {
7
+ static setConnectionAdapter(adapter) {
8
+ ArSyncStore_1.default.connectionManager = new ConnectionManager_1.default(adapter);
9
+ }
10
+ static createRefModel(request, option) {
11
+ return new ArSyncStore_1.default(request, option);
12
+ }
13
+ refManagerClass() {
14
+ return ArSyncModel;
15
+ }
16
+ connectionManager() {
17
+ return ArSyncStore_1.default.connectionManager;
18
+ }
19
+ }
20
+ ArSyncModel._cache = {};
21
+ ArSyncModel.cacheTimeout = 10 * 1000;
22
+ exports.default = ArSyncModel;
@@ -0,0 +1,28 @@
1
+ export default class ArSyncStore {
2
+ immutable: any;
3
+ markedForFreezeObjects: any;
4
+ changes: any;
5
+ eventListeners: any;
6
+ markForRelease: any;
7
+ container: any;
8
+ request: any;
9
+ complete: boolean;
10
+ notfound?: boolean;
11
+ data: any;
12
+ changesBufferTimer: number | undefined | null;
13
+ retryLoadTimer: number | undefined | null;
14
+ static connectionManager: any;
15
+ constructor(request: any, { immutable }?: {
16
+ immutable?: boolean | undefined;
17
+ });
18
+ load(retryCount: number): void;
19
+ setChangesBufferTimer(): void;
20
+ subscribe(event: any, callback: any): {
21
+ unsubscribe: () => void;
22
+ };
23
+ trigger(event: any, arg?: any): void;
24
+ mark(object: any): void;
25
+ freezeRecursive(obj: any): any;
26
+ freezeMarked(): void;
27
+ release(): void;
28
+ }