ar_sync 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -2
- data/README.md +6 -6
- data/ar_sync.gemspec +2 -2
- data/bin/console +1 -2
- data/core/ActioncableAdapter.d.ts +26 -2
- data/core/ActioncableAdapter.js +3 -3
- data/core/ArSyncApi.d.ts +7 -4
- data/core/ArSyncApi.js +9 -4
- data/core/{ArSyncModelBase.d.ts → ArSyncModel.d.ts} +13 -18
- data/core/{ArSyncModelBase.js → ArSyncModel.js} +33 -10
- data/{graph → core}/ArSyncStore.d.ts +3 -3
- data/{graph → core}/ArSyncStore.js +188 -57
- data/core/DataType.d.ts +17 -13
- data/core/hooks.d.ts +28 -0
- data/core/hooks.js +105 -0
- data/index.d.ts +2 -0
- data/index.js +6 -0
- data/lib/ar_sync.rb +1 -18
- data/lib/ar_sync/class_methods.rb +31 -89
- data/lib/ar_sync/collection.rb +4 -29
- data/lib/ar_sync/core.rb +35 -67
- data/lib/ar_sync/instance_methods.rb +40 -86
- data/lib/ar_sync/rails.rb +18 -27
- data/lib/ar_sync/type_script.rb +39 -18
- data/lib/ar_sync/version.rb +1 -1
- data/lib/generators/ar_sync/install/install_generator.rb +33 -32
- data/lib/generators/ar_sync/types/types_generator.rb +6 -3
- data/package-lock.json +21 -10
- data/package.json +1 -1
- data/src/core/ActioncableAdapter.ts +28 -3
- data/src/core/ArSyncApi.ts +8 -4
- data/src/core/{ArSyncModelBase.ts → ArSyncModel.ts} +51 -20
- data/src/{graph → core}/ArSyncStore.ts +199 -84
- data/src/core/DataType.ts +33 -20
- data/src/core/hooks.ts +108 -0
- data/src/index.ts +2 -0
- data/vendor/assets/javascripts/{ar_sync_tree.js.erb → ar_sync.js.erb} +6 -7
- metadata +33 -38
- data/core/hooksBase.d.ts +0 -29
- data/core/hooksBase.js +0 -80
- data/graph/ArSyncModel.d.ts +0 -10
- data/graph/ArSyncModel.js +0 -22
- data/graph/hooks.d.ts +0 -3
- data/graph/hooks.js +0 -10
- data/graph/index.d.ts +0 -2
- data/graph/index.js +0 -4
- data/src/core/hooksBase.ts +0 -86
- data/src/graph/ArSyncModel.ts +0 -21
- data/src/graph/hooks.ts +0 -7
- data/src/graph/index.ts +0 -2
- data/src/tree/ArSyncModel.ts +0 -145
- data/src/tree/ArSyncStore.ts +0 -323
- data/src/tree/hooks.ts +0 -7
- data/src/tree/index.ts +0 -2
- data/tree/ArSyncModel.d.ts +0 -39
- data/tree/ArSyncModel.js +0 -143
- data/tree/ArSyncStore.d.ts +0 -21
- data/tree/ArSyncStore.js +0 -365
- data/tree/hooks.d.ts +0 -3
- data/tree/hooks.js +0 -10
- data/tree/index.d.ts +0 -2
- data/tree/index.js +0 -4
- data/vendor/assets/javascripts/ar_sync_graph.js.erb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 377f042b14b49b79d357904e68e4382639dc1c37159ad9215385fe7e0dc3ecfa
|
4
|
+
data.tar.gz: 808503bd17cf312fdfca041cf7aad54c645c164a77ab1ce83888e5e93c9397ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9eb9a61473330c89049a89bb5ff8448d51b1c47a41b5d52f68d9a2b9826dc69454ea3fef0dceae5287b1c073600354a0be01eea96fc6f6d6a91cc64e611b4d05
|
7
|
+
data.tar.gz: 1a9cc66d3184ec1aca56d6812aa950b1496072b6d1bdab3c7bf9b2c3241e6126359e9900adef7b54cce7cf498181430a8eef211083ddbede087dd616caf08fcb
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ar_sync (1.0.
|
4
|
+
ar_sync (1.0.2)
|
5
5
|
activerecord
|
6
|
-
ar_serializer
|
6
|
+
ar_serializer (= 1.0.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
@@ -14,6 +14,8 @@ GEM
|
|
14
14
|
activemodel (= 5.2.3)
|
15
15
|
activesupport (= 5.2.3)
|
16
16
|
arel (>= 9.0)
|
17
|
+
activerecord-import (1.0.2)
|
18
|
+
activerecord (>= 3.2)
|
17
19
|
activesupport (5.2.3)
|
18
20
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
21
|
i18n (>= 0.7, < 2)
|
@@ -44,6 +46,7 @@ PLATFORMS
|
|
44
46
|
ruby
|
45
47
|
|
46
48
|
DEPENDENCIES
|
49
|
+
activerecord-import
|
47
50
|
ar_sync!
|
48
51
|
pry
|
49
52
|
rake
|
data/README.md
CHANGED
@@ -39,15 +39,14 @@ end
|
|
39
39
|
|
40
40
|
2. Define apis
|
41
41
|
```ruby
|
42
|
-
# app/
|
43
|
-
class
|
44
|
-
include ArSync::ApiControllerConcern
|
42
|
+
# app/models/sync_schema.rb
|
43
|
+
class SyncSchema < ArSync::SyncSchemaBase
|
45
44
|
# User-defined api
|
46
45
|
serializer_field :my_simple_profile_api do |current_user|
|
47
46
|
current_user
|
48
47
|
end
|
49
|
-
serializer_field :
|
50
|
-
|
48
|
+
serializer_field :my_simple_friends_api do |current_user, age:|
|
49
|
+
current_user.friends.where(age: age)
|
51
50
|
end
|
52
51
|
# Reload api (field name = classname, params = `ids:`)
|
53
52
|
serializer_field :User do |current_user, ids:|
|
@@ -103,7 +102,8 @@ rails g ar_sync:types path_to_generated_code_dir/
|
|
103
102
|
```ts
|
104
103
|
import ArSyncModel from 'path_to_generated_code_dir/ArSyncModel'
|
105
104
|
import ActionCableAdapter from 'ar_sync/core/ActionCableAdapter'
|
106
|
-
|
105
|
+
import * as ActionCable from 'actioncable'
|
106
|
+
ArSyncModel.setConnectionAdapter(new ActionCableAdapter(ActionCable))
|
107
107
|
// ArSyncModel.setConnectionAdapter(new MyCustomConnectionAdapter) // If you are using other transports
|
108
108
|
```
|
109
109
|
|
data/ar_sync.gemspec
CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
23
|
spec.add_dependency 'activerecord'
|
24
|
-
spec.add_dependency 'ar_serializer'
|
25
|
-
%w[rake pry sqlite3].each do |gem_name|
|
24
|
+
spec.add_dependency 'ar_serializer', '1.0.0'
|
25
|
+
%w[rake pry sqlite3 activerecord-import].each do |gem_name|
|
26
26
|
spec.add_development_dependency gem_name
|
27
27
|
end
|
28
28
|
end
|
data/bin/console
CHANGED
@@ -1,10 +1,34 @@
|
|
1
|
-
import * as ActionCable from 'actioncable';
|
2
1
|
import ConnectionAdapter from './ConnectionAdapter';
|
2
|
+
declare module ActionCable {
|
3
|
+
function createConsumer(): Cable;
|
4
|
+
interface Cable {
|
5
|
+
subscriptions: Subscriptions;
|
6
|
+
}
|
7
|
+
interface CreateMixin {
|
8
|
+
connected: () => void;
|
9
|
+
disconnected: () => void;
|
10
|
+
received: (obj: any) => void;
|
11
|
+
}
|
12
|
+
interface ChannelNameWithParams {
|
13
|
+
channel: string;
|
14
|
+
[key: string]: any;
|
15
|
+
}
|
16
|
+
interface Subscriptions {
|
17
|
+
create(channel: ChannelNameWithParams, obj: CreateMixin): Channel;
|
18
|
+
}
|
19
|
+
interface Channel {
|
20
|
+
unsubscribe(): void;
|
21
|
+
perform(action: string, data: {}): void;
|
22
|
+
send(data: any): boolean;
|
23
|
+
}
|
24
|
+
}
|
3
25
|
export default class ActionCableAdapter implements ConnectionAdapter {
|
4
26
|
connected: boolean;
|
5
27
|
_cable: ActionCable.Cable;
|
6
|
-
|
28
|
+
actionCableClass: typeof ActionCable;
|
29
|
+
constructor(actionCableClass: typeof ActionCable);
|
7
30
|
subscribe(key: string, received: (data: any) => void): ActionCable.Channel;
|
8
31
|
ondisconnect(): void;
|
9
32
|
onreconnect(): void;
|
10
33
|
}
|
34
|
+
export {};
|
data/core/ActioncableAdapter.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
const ActionCable = require("actioncable");
|
4
3
|
class ActionCableAdapter {
|
5
|
-
constructor() {
|
4
|
+
constructor(actionCableClass) {
|
6
5
|
this.connected = true;
|
6
|
+
this.actionCableClass = actionCableClass;
|
7
7
|
this.subscribe(Math.random().toString(), () => { });
|
8
8
|
}
|
9
9
|
subscribe(key, received) {
|
@@ -20,7 +20,7 @@ class ActionCableAdapter {
|
|
20
20
|
this.onreconnect();
|
21
21
|
};
|
22
22
|
if (!this._cable)
|
23
|
-
this._cable =
|
23
|
+
this._cable = this.actionCableClass.createConsumer();
|
24
24
|
return this._cable.subscriptions.create({ channel: 'SyncChannel', key }, { received, disconnected, connected });
|
25
25
|
}
|
26
26
|
ondisconnect() { }
|
data/core/ArSyncApi.d.ts
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
declare
|
2
|
-
|
3
|
-
|
1
|
+
declare function apiBatchFetch(endpoint: string, requests: object[]): Promise<any>;
|
2
|
+
declare const ArSyncApi: {
|
3
|
+
domain: string | null;
|
4
|
+
_batchFetch: typeof apiBatchFetch;
|
5
|
+
fetch: (request: object) => Promise<unknown>;
|
6
|
+
syncFetch: (request: object) => Promise<unknown>;
|
4
7
|
};
|
5
|
-
export default
|
8
|
+
export default ArSyncApi;
|
data/core/ArSyncApi.js
CHANGED
@@ -7,6 +7,8 @@ async function apiBatchFetch(endpoint, requests) {
|
|
7
7
|
};
|
8
8
|
const body = JSON.stringify({ requests });
|
9
9
|
const option = { credentials: 'include', method: 'POST', headers, body };
|
10
|
+
if (ArSyncApi.domain)
|
11
|
+
endpoint = ArSyncApi.domain + endpoint;
|
10
12
|
const res = await fetch(endpoint, option);
|
11
13
|
if (res.status === 200)
|
12
14
|
return res.json();
|
@@ -41,7 +43,7 @@ class ApiFetcher {
|
|
41
43
|
}
|
42
44
|
}
|
43
45
|
this.batches = [];
|
44
|
-
|
46
|
+
ArSyncApi._batchFetch(this.endpoint, requests).then((results) => {
|
45
47
|
for (const i in callbacksList) {
|
46
48
|
const result = results[i];
|
47
49
|
const callbacks = callbacksList[i];
|
@@ -51,11 +53,11 @@ class ApiFetcher {
|
|
51
53
|
}
|
52
54
|
else {
|
53
55
|
const error = result.error || { type: 'Unknown Error' };
|
54
|
-
callback.reject(error);
|
56
|
+
callback.reject(Object.assign(Object.assign({}, error), { retry: false }));
|
55
57
|
}
|
56
58
|
}
|
57
59
|
}
|
58
|
-
}).catch(
|
60
|
+
}).catch(e => {
|
59
61
|
const error = { type: e.name, message: e.message, retry: true };
|
60
62
|
for (const callbacks of callbacksList) {
|
61
63
|
for (const callback of callbacks)
|
@@ -68,7 +70,10 @@ class ApiFetcher {
|
|
68
70
|
}
|
69
71
|
const staticFetcher = new ApiFetcher('/static_api');
|
70
72
|
const syncFetcher = new ApiFetcher('/sync_api');
|
71
|
-
|
73
|
+
const ArSyncApi = {
|
74
|
+
domain: null,
|
75
|
+
_batchFetch: apiBatchFetch,
|
72
76
|
fetch: (request) => staticFetcher.fetch(request),
|
73
77
|
syncFetch: (request) => syncFetcher.fetch(request),
|
74
78
|
};
|
79
|
+
exports.default = ArSyncApi;
|
@@ -1,9 +1,10 @@
|
|
1
|
+
import ConnectionAdapter from './ConnectionAdapter';
|
1
2
|
interface Request {
|
2
3
|
api: string;
|
3
4
|
query: any;
|
4
5
|
params?: any;
|
5
6
|
}
|
6
|
-
declare type Path = (string | number)[]
|
7
|
+
declare type Path = Readonly<(string | number)[]>;
|
7
8
|
interface Change {
|
8
9
|
path: Path;
|
9
10
|
value: any;
|
@@ -13,14 +14,13 @@ declare type LoadCallback = () => void;
|
|
13
14
|
declare type ConnectionCallback = (status: boolean) => void;
|
14
15
|
declare type SubscriptionType = 'load' | 'change' | 'connection';
|
15
16
|
declare type SubscriptionCallback = ChangeCallback | LoadCallback | ConnectionCallback;
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
export default abstract class ArSyncModelBase<T> {
|
17
|
+
declare type PathFirst<P extends Readonly<any[]>> = ((...args: P) => void) extends (first: infer First, ...other: any) => void ? First : never;
|
18
|
+
declare type PathRest<U> = U extends Readonly<any[]> ? ((...args: U) => any) extends (head: any, ...args: infer T) => any ? U extends Readonly<[any, any, ...any[]]> ? T : never : never : never;
|
19
|
+
declare type DigResult<Data, P extends Readonly<any[]>> = Data extends null | undefined ? Data : PathFirst<P> extends never ? Data : PathFirst<P> extends keyof Data ? (Data extends Readonly<any[]> ? undefined : never) | {
|
20
|
+
0: Data[PathFirst<P>];
|
21
|
+
1: DigResult<Data[PathFirst<P>], PathRest<P>>;
|
22
|
+
}[PathRest<P> extends never ? 0 : 1] : undefined;
|
23
|
+
export default class ArSyncModel<T> {
|
24
24
|
private _ref;
|
25
25
|
private _listenerSerial;
|
26
26
|
private _listeners;
|
@@ -37,10 +37,6 @@ export default abstract class ArSyncModelBase<T> {
|
|
37
37
|
};
|
38
38
|
};
|
39
39
|
static cacheTimeout: number;
|
40
|
-
abstract refManagerClass(): any;
|
41
|
-
abstract connectionManager(): {
|
42
|
-
networkStatus: boolean;
|
43
|
-
};
|
44
40
|
constructor(request: Request, option?: {
|
45
41
|
immutable: boolean;
|
46
42
|
});
|
@@ -48,6 +44,8 @@ export default abstract class ArSyncModelBase<T> {
|
|
48
44
|
subscribeOnce(event: SubscriptionType, callback: SubscriptionCallback): {
|
49
45
|
unsubscribe: () => void;
|
50
46
|
};
|
47
|
+
dig<P extends Path>(path: P): DigResult<T, P> | null;
|
48
|
+
static digData<Data, P extends Path>(data: Data, path: P): DigResult<Data, P>;
|
51
49
|
subscribe(event: SubscriptionType, callback: SubscriptionCallback): {
|
52
50
|
unsubscribe: () => void;
|
53
51
|
};
|
@@ -60,12 +58,9 @@ export default abstract class ArSyncModelBase<T> {
|
|
60
58
|
timer: number | null;
|
61
59
|
model: any;
|
62
60
|
};
|
63
|
-
static createRefModel(_request: Request, _option?: {
|
64
|
-
immutable: boolean;
|
65
|
-
}): void;
|
66
61
|
static _detach(ref: any): void;
|
67
62
|
private static _attach;
|
68
|
-
static setConnectionAdapter(
|
69
|
-
static waitForLoad(...models:
|
63
|
+
static setConnectionAdapter(adapter: ConnectionAdapter): void;
|
64
|
+
static waitForLoad(...models: ArSyncModel<{}>[]): Promise<unknown>;
|
70
65
|
}
|
71
66
|
export {};
|
@@ -1,12 +1,14 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
|
3
|
+
const ArSyncStore_1 = require("./ArSyncStore");
|
4
|
+
const ConnectionManager_1 = require("./ConnectionManager");
|
5
|
+
class ArSyncModel {
|
4
6
|
constructor(request, option) {
|
5
|
-
this._ref =
|
7
|
+
this._ref = ArSyncModel.retrieveRef(request, option);
|
6
8
|
this._listenerSerial = 0;
|
7
9
|
this._listeners = {};
|
8
10
|
this.complete = false;
|
9
|
-
this.connected =
|
11
|
+
this.connected = ArSyncStore_1.default.connectionManager.networkStatus;
|
10
12
|
const setData = () => {
|
11
13
|
this.data = this._ref.model.data;
|
12
14
|
this.complete = this._ref.model.complete;
|
@@ -29,6 +31,26 @@ class ArSyncModelBase {
|
|
29
31
|
});
|
30
32
|
return subscription;
|
31
33
|
}
|
34
|
+
dig(path) {
|
35
|
+
return ArSyncModel.digData(this.data, path);
|
36
|
+
}
|
37
|
+
static digData(data, path) {
|
38
|
+
function dig(data, path) {
|
39
|
+
if (path.length === 0)
|
40
|
+
return data;
|
41
|
+
if (data == null)
|
42
|
+
return data;
|
43
|
+
const key = path[0];
|
44
|
+
const other = path.slice(1);
|
45
|
+
if (Array.isArray(data)) {
|
46
|
+
return this.digData(data.find(el => el.id === key), other);
|
47
|
+
}
|
48
|
+
else {
|
49
|
+
return this.digData(data[key], other);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return dig(data, path);
|
53
|
+
}
|
32
54
|
subscribe(event, callback) {
|
33
55
|
const id = this._listenerSerial++;
|
34
56
|
const subscription = this._ref.model.subscribe(event, callback);
|
@@ -56,22 +78,19 @@ class ArSyncModelBase {
|
|
56
78
|
for (const id in this._listeners)
|
57
79
|
this._listeners[id].unsubscribe();
|
58
80
|
this._listeners = {};
|
59
|
-
|
81
|
+
ArSyncModel._detach(this._ref);
|
60
82
|
this._ref = null;
|
61
83
|
}
|
62
84
|
static retrieveRef(request, option) {
|
63
85
|
const key = JSON.stringify([request, option]);
|
64
86
|
let ref = this._cache[key];
|
65
87
|
if (!ref) {
|
66
|
-
const model =
|
88
|
+
const model = new ArSyncStore_1.default(request, option);
|
67
89
|
ref = this._cache[key] = { key, count: 0, timer: null, model };
|
68
90
|
}
|
69
91
|
this._attach(ref);
|
70
92
|
return ref;
|
71
93
|
}
|
72
|
-
static createRefModel(_request, _option) {
|
73
|
-
throw 'abstract method';
|
74
|
-
}
|
75
94
|
static _detach(ref) {
|
76
95
|
ref.count--;
|
77
96
|
const timeout = this.cacheTimeout;
|
@@ -93,7 +112,9 @@ class ArSyncModelBase {
|
|
93
112
|
if (ref.timer)
|
94
113
|
clearTimeout(ref.timer);
|
95
114
|
}
|
96
|
-
static setConnectionAdapter(
|
115
|
+
static setConnectionAdapter(adapter) {
|
116
|
+
ArSyncStore_1.default.connectionManager = new ConnectionManager_1.default(adapter);
|
117
|
+
}
|
97
118
|
static waitForLoad(...models) {
|
98
119
|
return new Promise((resolve) => {
|
99
120
|
let count = 0;
|
@@ -107,4 +128,6 @@ class ArSyncModelBase {
|
|
107
128
|
});
|
108
129
|
}
|
109
130
|
}
|
110
|
-
exports.default =
|
131
|
+
exports.default = ArSyncModel;
|
132
|
+
ArSyncModel._cache = {};
|
133
|
+
ArSyncModel.cacheTimeout = 10 * 1000;
|
@@ -1,9 +1,9 @@
|
|
1
1
|
export default class ArSyncStore {
|
2
|
-
immutable:
|
3
|
-
markedForFreezeObjects: any;
|
2
|
+
immutable: boolean;
|
3
|
+
markedForFreezeObjects: any[];
|
4
4
|
changes: any;
|
5
5
|
eventListeners: any;
|
6
|
-
markForRelease:
|
6
|
+
markForRelease: true | undefined;
|
7
7
|
container: any;
|
8
8
|
request: any;
|
9
9
|
complete: boolean;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
const ArSyncApi_1 = require("
|
3
|
+
const ArSyncApi_1 = require("./ArSyncApi");
|
4
4
|
const ModelBatchRequest = {
|
5
5
|
timer: null,
|
6
6
|
apiRequests: {},
|
@@ -87,7 +87,59 @@ class ArSyncContainerBase {
|
|
87
87
|
l.unsubscribe();
|
88
88
|
this.listeners = [];
|
89
89
|
}
|
90
|
-
static
|
90
|
+
static compactQuery(query) {
|
91
|
+
function compactAttributes(attributes) {
|
92
|
+
const attrs = {};
|
93
|
+
const keys = [];
|
94
|
+
for (const key in attributes) {
|
95
|
+
const c = compactQuery(attributes[key]);
|
96
|
+
if (c === true) {
|
97
|
+
keys.push(key);
|
98
|
+
}
|
99
|
+
else {
|
100
|
+
attrs[key] = c;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
if (Object.keys(attrs).length === 0) {
|
104
|
+
if (keys.length === 0)
|
105
|
+
return [true, false];
|
106
|
+
if (keys.length === 1)
|
107
|
+
return [keys[0], false];
|
108
|
+
return [keys];
|
109
|
+
}
|
110
|
+
const needsEscape = attrs['attributes'] || attrs['params'] || attrs['as'];
|
111
|
+
if (keys.length === 0)
|
112
|
+
return [attrs, needsEscape];
|
113
|
+
return [[...keys, attrs], needsEscape];
|
114
|
+
}
|
115
|
+
function compactQuery(query) {
|
116
|
+
if (!('attributes' in query))
|
117
|
+
return true;
|
118
|
+
const { as, params } = query;
|
119
|
+
const [attributes, needsEscape] = compactAttributes(query.attributes);
|
120
|
+
if (as == null && params == null) {
|
121
|
+
if (needsEscape)
|
122
|
+
return { attributes };
|
123
|
+
return attributes;
|
124
|
+
}
|
125
|
+
const result = {};
|
126
|
+
if (as)
|
127
|
+
result.as = as;
|
128
|
+
if (params)
|
129
|
+
result.params = params;
|
130
|
+
if (attributes !== true)
|
131
|
+
result.attributes = attributes;
|
132
|
+
return result;
|
133
|
+
}
|
134
|
+
try {
|
135
|
+
const result = compactQuery(query);
|
136
|
+
return result === true ? {} : result;
|
137
|
+
}
|
138
|
+
catch (e) {
|
139
|
+
throw JSON.stringify(query) + e.stack;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
static parseQuery(query, attrsonly) {
|
91
143
|
const attributes = {};
|
92
144
|
let column = null;
|
93
145
|
let params = null;
|
@@ -129,15 +181,19 @@ class ArSyncContainerBase {
|
|
129
181
|
}
|
130
182
|
static _load({ api, id, params, query }, root) {
|
131
183
|
const parsedQuery = ArSyncRecord.parseQuery(query);
|
184
|
+
const compactQuery = ArSyncRecord.compactQuery(parsedQuery);
|
132
185
|
if (id) {
|
133
|
-
return ModelBatchRequest.fetch(api,
|
186
|
+
return ModelBatchRequest.fetch(api, compactQuery, id).then(data => new ArSyncRecord(parsedQuery, data, null, root));
|
134
187
|
}
|
135
188
|
else {
|
136
|
-
const request = { api, query, params };
|
189
|
+
const request = { api, query: compactQuery, params };
|
137
190
|
return ArSyncApi_1.default.syncFetch(request).then((response) => {
|
138
191
|
if (response.collection && response.order) {
|
139
192
|
return new ArSyncCollection(response.sync_keys, 'collection', parsedQuery, response, request, root);
|
140
193
|
}
|
194
|
+
else if (response instanceof Array) {
|
195
|
+
return new ArSyncCollection([], '', parsedQuery, response, request, root);
|
196
|
+
}
|
141
197
|
else {
|
142
198
|
return new ArSyncRecord(parsedQuery, response, request, root);
|
143
199
|
}
|
@@ -176,7 +232,6 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
176
232
|
this.sync_keys = sync_keys;
|
177
233
|
if (!this.sync_keys) {
|
178
234
|
this.sync_keys = [];
|
179
|
-
console.error('warning: no sync_keys');
|
180
235
|
}
|
181
236
|
}
|
182
237
|
replaceData(data) {
|
@@ -191,11 +246,12 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
191
246
|
const subQuery = this.query.attributes[key];
|
192
247
|
const aliasName = subQuery.as || key;
|
193
248
|
const subData = data[aliasName];
|
249
|
+
const child = this.children[aliasName];
|
194
250
|
if (key === 'sync_keys')
|
195
251
|
continue;
|
196
|
-
if (
|
197
|
-
if (
|
198
|
-
|
252
|
+
if (subData instanceof Array || (subData && subData.collection && subData.order)) {
|
253
|
+
if (child) {
|
254
|
+
child.replaceData(subData, this.sync_keys);
|
199
255
|
}
|
200
256
|
else {
|
201
257
|
const collection = new ArSyncCollection(this.sync_keys, key, subQuery, subData, null, this.root);
|
@@ -207,11 +263,11 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
207
263
|
}
|
208
264
|
}
|
209
265
|
else {
|
210
|
-
if (subQuery.attributes)
|
266
|
+
if (subQuery.attributes && Object.keys(subQuery.attributes).length > 0)
|
211
267
|
this.paths.push(key);
|
212
268
|
if (subData && subData.sync_keys) {
|
213
|
-
if (
|
214
|
-
|
269
|
+
if (child) {
|
270
|
+
child.replaceData(subData);
|
215
271
|
}
|
216
272
|
else {
|
217
273
|
const model = new ArSyncRecord(subQuery, subData, null, this.root);
|
@@ -223,8 +279,8 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
223
279
|
}
|
224
280
|
}
|
225
281
|
else {
|
226
|
-
if (
|
227
|
-
|
282
|
+
if (child) {
|
283
|
+
child.release();
|
228
284
|
delete this.children[aliasName];
|
229
285
|
}
|
230
286
|
if (this.data[aliasName] !== subData) {
|
@@ -234,39 +290,55 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
234
290
|
}
|
235
291
|
}
|
236
292
|
}
|
293
|
+
if (this.query.attributes['*']) {
|
294
|
+
for (const key in data) {
|
295
|
+
if (!this.query.attributes[key] && this.data[key] !== data[key]) {
|
296
|
+
this.mark();
|
297
|
+
this.data[key] = data[key];
|
298
|
+
}
|
299
|
+
}
|
300
|
+
}
|
237
301
|
this.subscribeAll();
|
238
302
|
}
|
239
303
|
onNotify(notifyData, path) {
|
240
304
|
const { action, class_name, id } = notifyData;
|
305
|
+
const query = path && this.query.attributes[path];
|
306
|
+
const aliasName = (query && query.as) || path;
|
241
307
|
if (action === 'remove') {
|
242
|
-
this.children[
|
243
|
-
|
308
|
+
const child = this.children[aliasName];
|
309
|
+
if (child)
|
310
|
+
child.release();
|
311
|
+
this.children[aliasName] = null;
|
244
312
|
this.mark();
|
245
|
-
this.data[
|
246
|
-
this.onChange([
|
313
|
+
this.data[aliasName] = null;
|
314
|
+
this.onChange([aliasName], null);
|
247
315
|
}
|
248
316
|
else if (action === 'add') {
|
249
|
-
if (this.data.id === id)
|
317
|
+
if (this.data[aliasName] && this.data[aliasName].id === id)
|
250
318
|
return;
|
251
|
-
|
252
|
-
|
253
|
-
if (!data)
|
319
|
+
ModelBatchRequest.fetch(class_name, ArSyncRecord.compactQuery(query), id).then(data => {
|
320
|
+
if (!data || !this.data)
|
254
321
|
return;
|
255
322
|
const model = new ArSyncRecord(query, data, null, this.root);
|
256
|
-
|
257
|
-
|
258
|
-
|
323
|
+
const child = this.children[aliasName];
|
324
|
+
if (child)
|
325
|
+
child.release();
|
326
|
+
this.children[aliasName] = model;
|
259
327
|
this.mark();
|
260
|
-
this.data[
|
328
|
+
this.data[aliasName] = model.data;
|
261
329
|
model.parentModel = this;
|
262
|
-
model.parentKey =
|
263
|
-
this.onChange([
|
330
|
+
model.parentKey = aliasName;
|
331
|
+
this.onChange([aliasName], model.data);
|
264
332
|
});
|
265
333
|
}
|
266
334
|
else {
|
267
|
-
|
268
|
-
|
269
|
-
|
335
|
+
const { field } = notifyData;
|
336
|
+
const query = field ? this.patchQuery(field) : this.reloadQuery();
|
337
|
+
if (query)
|
338
|
+
ModelBatchRequest.fetch(class_name, query, id).then(data => {
|
339
|
+
if (this.data)
|
340
|
+
this.update(data);
|
341
|
+
});
|
270
342
|
}
|
271
343
|
}
|
272
344
|
subscribeAll() {
|
@@ -280,6 +352,24 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
280
352
|
this.subscribe(key + path, pathCallback);
|
281
353
|
}
|
282
354
|
}
|
355
|
+
patchQuery(key) {
|
356
|
+
const val = this.query.attributes[key];
|
357
|
+
if (!val)
|
358
|
+
return;
|
359
|
+
let { attributes, as, params } = val;
|
360
|
+
if (attributes && Object.keys(val.attributes).length === 0)
|
361
|
+
attributes = null;
|
362
|
+
if (!attributes && !as && !params)
|
363
|
+
return key;
|
364
|
+
const result = {};
|
365
|
+
if (attributes)
|
366
|
+
result.attributes = attributes;
|
367
|
+
if (as)
|
368
|
+
result.as = as;
|
369
|
+
if (params)
|
370
|
+
result.params = params;
|
371
|
+
return result;
|
372
|
+
}
|
283
373
|
reloadQuery() {
|
284
374
|
if (this.reloadQueryCache)
|
285
375
|
return this.reloadQueryCache;
|
@@ -299,6 +389,9 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
299
389
|
}
|
300
390
|
update(data) {
|
301
391
|
for (const key in data) {
|
392
|
+
const subQuery = this.query.attributes[key];
|
393
|
+
if (subQuery && subQuery.attributes && Object.keys(subQuery.attributes).length > 0)
|
394
|
+
continue;
|
302
395
|
if (this.data[key] === data[key])
|
303
396
|
continue;
|
304
397
|
this.mark();
|
@@ -318,35 +411,51 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
318
411
|
if (this.parentModel)
|
319
412
|
this.parentModel.markAndSet(this.parentKey, this.data);
|
320
413
|
}
|
321
|
-
onChange(path, data) {
|
322
|
-
if (this.parentModel)
|
323
|
-
this.parentModel.onChange([this.parentKey, ...path], data);
|
324
|
-
}
|
325
414
|
}
|
326
415
|
class ArSyncCollection extends ArSyncContainerBase {
|
327
416
|
constructor(sync_keys, path, query, data, request, root) {
|
328
417
|
super();
|
418
|
+
this.order = { limit: null, mode: 'asc', key: 'id' };
|
419
|
+
this.aliasOrderKey = 'id';
|
329
420
|
this.root = root;
|
330
421
|
this.path = path;
|
422
|
+
this.query = query;
|
423
|
+
this.compactQuery = ArSyncRecord.compactQuery(query);
|
331
424
|
if (request)
|
332
425
|
this.initForReload(request);
|
333
426
|
if (query.params && (query.params.order || query.params.limit)) {
|
334
|
-
this.
|
335
|
-
}
|
336
|
-
else {
|
337
|
-
this.order = { limit: null, mode: 'asc' };
|
427
|
+
this.setOrdering(query.params.limit, query.params.order);
|
338
428
|
}
|
339
|
-
this.query = query;
|
340
429
|
this.data = [];
|
341
430
|
this.children = [];
|
342
431
|
this.replaceData(data, sync_keys);
|
343
432
|
}
|
433
|
+
setOrdering(limit, order) {
|
434
|
+
let mode = 'asc';
|
435
|
+
let key = 'id';
|
436
|
+
if (order === 'asc' || order === 'desc') {
|
437
|
+
mode = order;
|
438
|
+
}
|
439
|
+
else if (typeof order === 'object' && order) {
|
440
|
+
const keys = Object.keys(order);
|
441
|
+
if (keys.length > 1)
|
442
|
+
throw 'multiple order keys are not supported';
|
443
|
+
if (keys.length === 1)
|
444
|
+
key = keys[0];
|
445
|
+
mode = order[key] === 'asc' ? 'asc' : 'desc';
|
446
|
+
}
|
447
|
+
const limitNumber = (typeof limit === 'number') ? limit : null;
|
448
|
+
if (limitNumber !== null && key !== 'id')
|
449
|
+
throw 'limit with custom order key is not supported';
|
450
|
+
const subQuery = this.query.attributes[key];
|
451
|
+
this.aliasOrderKey = (subQuery && subQuery.as) || key;
|
452
|
+
this.order = { limit: limitNumber, mode, key };
|
453
|
+
}
|
344
454
|
setSyncKeys(sync_keys) {
|
345
455
|
if (sync_keys) {
|
346
456
|
this.sync_keys = sync_keys.map(key => key + this.path);
|
347
457
|
}
|
348
458
|
else {
|
349
|
-
console.error('warning: no sync_keys');
|
350
459
|
this.sync_keys = [];
|
351
460
|
}
|
352
461
|
}
|
@@ -356,9 +465,9 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
356
465
|
for (const child of this.children)
|
357
466
|
existings[child.data.id] = child;
|
358
467
|
let collection;
|
359
|
-
if (
|
468
|
+
if ('collection' in data && 'order' in data) {
|
360
469
|
collection = data.collection;
|
361
|
-
this.order
|
470
|
+
this.setOrdering(data.order.limit, data.order.mode);
|
362
471
|
}
|
363
472
|
else {
|
364
473
|
collection = data;
|
@@ -366,17 +475,23 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
366
475
|
const newChildren = [];
|
367
476
|
const newData = [];
|
368
477
|
for (const subData of collection) {
|
369
|
-
let model =
|
478
|
+
let model = null;
|
479
|
+
if (typeof (subData) === 'object' && subData && 'id' in subData)
|
480
|
+
model = existings[subData.id];
|
481
|
+
let data = subData;
|
370
482
|
if (model) {
|
371
483
|
model.replaceData(subData);
|
372
484
|
}
|
373
|
-
else {
|
485
|
+
else if (subData.id) {
|
374
486
|
model = new ArSyncRecord(this.query, subData, null, this.root);
|
375
487
|
model.parentModel = this;
|
376
488
|
model.parentKey = subData.id;
|
377
489
|
}
|
378
|
-
|
379
|
-
|
490
|
+
if (model) {
|
491
|
+
newChildren.push(model);
|
492
|
+
data = model.data;
|
493
|
+
}
|
494
|
+
newData.push(data);
|
380
495
|
}
|
381
496
|
while (this.children.length) {
|
382
497
|
const child = this.children.pop();
|
@@ -408,8 +523,8 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
408
523
|
return;
|
409
524
|
}
|
410
525
|
}
|
411
|
-
ModelBatchRequest.fetch(className, this.
|
412
|
-
if (!data)
|
526
|
+
ModelBatchRequest.fetch(className, this.compactQuery, id).then((data) => {
|
527
|
+
if (!data || !this.data)
|
413
528
|
return;
|
414
529
|
const model = new ArSyncRecord(this.query, data, null, this.root);
|
415
530
|
model.parentModel = this;
|
@@ -417,14 +532,13 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
417
532
|
const overflow = this.order.limit && this.order.limit === this.data.length;
|
418
533
|
let rmodel;
|
419
534
|
this.mark();
|
535
|
+
const orderKey = this.aliasOrderKey;
|
420
536
|
if (this.order.mode === 'asc') {
|
421
537
|
const last = this.data[this.data.length - 1];
|
422
538
|
this.children.push(model);
|
423
539
|
this.data.push(model.data);
|
424
|
-
if (last && last
|
425
|
-
this.
|
426
|
-
this.data.sort((a, b) => a.id < b.id ? -1 : +1);
|
427
|
-
}
|
540
|
+
if (last && last[orderKey] > data[orderKey])
|
541
|
+
this.markAndSort();
|
428
542
|
if (overflow) {
|
429
543
|
rmodel = this.children.shift();
|
430
544
|
rmodel.release();
|
@@ -435,10 +549,8 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
435
549
|
const first = this.data[0];
|
436
550
|
this.children.unshift(model);
|
437
551
|
this.data.unshift(model.data);
|
438
|
-
if (first && first
|
439
|
-
this.
|
440
|
-
this.data.sort((a, b) => a.id > b.id ? -1 : +1);
|
441
|
-
}
|
552
|
+
if (first && first[orderKey] > data[orderKey])
|
553
|
+
this.markAndSort();
|
442
554
|
if (overflow) {
|
443
555
|
rmodel = this.children.pop();
|
444
556
|
rmodel.release();
|
@@ -450,6 +562,18 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
450
562
|
this.onChange([rmodel.id], null);
|
451
563
|
});
|
452
564
|
}
|
565
|
+
markAndSort() {
|
566
|
+
this.mark();
|
567
|
+
const orderKey = this.aliasOrderKey;
|
568
|
+
if (this.order.mode === 'asc') {
|
569
|
+
this.children.sort((a, b) => a.data[orderKey] < b.data[orderKey] ? -1 : +1);
|
570
|
+
this.data.sort((a, b) => a[orderKey] < b[orderKey] ? -1 : +1);
|
571
|
+
}
|
572
|
+
else {
|
573
|
+
this.children.sort((a, b) => a.data[orderKey] > b.data[orderKey] ? -1 : +1);
|
574
|
+
this.data.sort((a, b) => a[orderKey] > b[orderKey] ? -1 : +1);
|
575
|
+
}
|
576
|
+
}
|
453
577
|
consumeRemove(id) {
|
454
578
|
const idx = this.data.findIndex(a => a.id === id);
|
455
579
|
if (idx < 0)
|
@@ -473,6 +597,11 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
473
597
|
for (const key of this.sync_keys)
|
474
598
|
this.subscribe(key, callback);
|
475
599
|
}
|
600
|
+
onChange(path, data) {
|
601
|
+
super.onChange(path, data);
|
602
|
+
if (path[1] === this.aliasOrderKey)
|
603
|
+
this.markAndSort();
|
604
|
+
}
|
476
605
|
markAndSet(id, data) {
|
477
606
|
this.mark();
|
478
607
|
const idx = this.data.findIndex(a => a.id === id);
|
@@ -490,7 +619,7 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
490
619
|
}
|
491
620
|
class ArSyncStore {
|
492
621
|
constructor(request, { immutable } = {}) {
|
493
|
-
this.immutable = immutable;
|
622
|
+
this.immutable = !!immutable;
|
494
623
|
this.markedForFreezeObjects = [];
|
495
624
|
this.changes = [];
|
496
625
|
this.eventListeners = { events: {}, serial: 0 };
|
@@ -521,6 +650,8 @@ class ArSyncStore {
|
|
521
650
|
this.trigger('connection', state);
|
522
651
|
};
|
523
652
|
}).catch(e => {
|
653
|
+
if (!e || e.retry === undefined)
|
654
|
+
throw e;
|
524
655
|
if (this.markForRelease)
|
525
656
|
return;
|
526
657
|
if (!e.retry) {
|