ar_sync 1.0.1 → 1.0.2
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 +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) {
|