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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +5 -2
  3. data/README.md +6 -6
  4. data/ar_sync.gemspec +2 -2
  5. data/bin/console +1 -2
  6. data/core/ActioncableAdapter.d.ts +26 -2
  7. data/core/ActioncableAdapter.js +3 -3
  8. data/core/ArSyncApi.d.ts +7 -4
  9. data/core/ArSyncApi.js +9 -4
  10. data/core/{ArSyncModelBase.d.ts → ArSyncModel.d.ts} +13 -18
  11. data/core/{ArSyncModelBase.js → ArSyncModel.js} +33 -10
  12. data/{graph → core}/ArSyncStore.d.ts +3 -3
  13. data/{graph → core}/ArSyncStore.js +188 -57
  14. data/core/DataType.d.ts +17 -13
  15. data/core/hooks.d.ts +28 -0
  16. data/core/hooks.js +105 -0
  17. data/index.d.ts +2 -0
  18. data/index.js +6 -0
  19. data/lib/ar_sync.rb +1 -18
  20. data/lib/ar_sync/class_methods.rb +31 -89
  21. data/lib/ar_sync/collection.rb +4 -29
  22. data/lib/ar_sync/core.rb +35 -67
  23. data/lib/ar_sync/instance_methods.rb +40 -86
  24. data/lib/ar_sync/rails.rb +18 -27
  25. data/lib/ar_sync/type_script.rb +39 -18
  26. data/lib/ar_sync/version.rb +1 -1
  27. data/lib/generators/ar_sync/install/install_generator.rb +33 -32
  28. data/lib/generators/ar_sync/types/types_generator.rb +6 -3
  29. data/package-lock.json +21 -10
  30. data/package.json +1 -1
  31. data/src/core/ActioncableAdapter.ts +28 -3
  32. data/src/core/ArSyncApi.ts +8 -4
  33. data/src/core/{ArSyncModelBase.ts → ArSyncModel.ts} +51 -20
  34. data/src/{graph → core}/ArSyncStore.ts +199 -84
  35. data/src/core/DataType.ts +33 -20
  36. data/src/core/hooks.ts +108 -0
  37. data/src/index.ts +2 -0
  38. data/vendor/assets/javascripts/{ar_sync_tree.js.erb → ar_sync.js.erb} +6 -7
  39. metadata +33 -38
  40. data/core/hooksBase.d.ts +0 -29
  41. data/core/hooksBase.js +0 -80
  42. data/graph/ArSyncModel.d.ts +0 -10
  43. data/graph/ArSyncModel.js +0 -22
  44. data/graph/hooks.d.ts +0 -3
  45. data/graph/hooks.js +0 -10
  46. data/graph/index.d.ts +0 -2
  47. data/graph/index.js +0 -4
  48. data/src/core/hooksBase.ts +0 -86
  49. data/src/graph/ArSyncModel.ts +0 -21
  50. data/src/graph/hooks.ts +0 -7
  51. data/src/graph/index.ts +0 -2
  52. data/src/tree/ArSyncModel.ts +0 -145
  53. data/src/tree/ArSyncStore.ts +0 -323
  54. data/src/tree/hooks.ts +0 -7
  55. data/src/tree/index.ts +0 -2
  56. data/tree/ArSyncModel.d.ts +0 -39
  57. data/tree/ArSyncModel.js +0 -143
  58. data/tree/ArSyncStore.d.ts +0 -21
  59. data/tree/ArSyncStore.js +0 -365
  60. data/tree/hooks.d.ts +0 -3
  61. data/tree/hooks.js +0 -10
  62. data/tree/index.d.ts +0 -2
  63. data/tree/index.js +0 -4
  64. 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: c79d18d97624ed5b34f6471838d6b96eb6cfb71bba00cee9344a370dbb309498
4
- data.tar.gz: c967bed5ff59000fcc8c1de827e98f1b8caef20d71d67532d8203eff4d97724a
3
+ metadata.gz: 377f042b14b49b79d357904e68e4382639dc1c37159ad9215385fe7e0dc3ecfa
4
+ data.tar.gz: 808503bd17cf312fdfca041cf7aad54c645c164a77ab1ce83888e5e93c9397ec
5
5
  SHA512:
6
- metadata.gz: 5966a10dfdce14e70e2df1a0d722889db8ff4bece153bf4d1bd73a8148dd6f79cb4581e254a26deed31c2fe09acbdcd9333cdbf6616ec8d892bfc0758ccd44e3
7
- data.tar.gz: 87b53c9634f5afd4b4a1a708ce128d094290d3e5db9a24b70146dc4fc96091760efcdb5b8edf851f3ce83afb13f85e464e556d2c00890c4b36fa852871908d7a
6
+ metadata.gz: 9eb9a61473330c89049a89bb5ff8448d51b1c47a41b5d52f68d9a2b9826dc69454ea3fef0dceae5287b1c073600354a0be01eea96fc6f6d6a91cc64e611b4d05
7
+ data.tar.gz: 1a9cc66d3184ec1aca56d6812aa950b1496072b6d1bdab3c7bf9b2c3241e6126359e9900adef7b54cce7cf498181430a8eef211083ddbede087dd616caf08fcb
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ar_sync (1.0.1)
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/controllers/sync_api_controller.rb
43
- class SyncApiController < ApplicationController
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 :my_simple_user_api do |current_user, id:|
50
- User.where(condition).find id
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
- ArSyncModel.setConnectionAdapter(new ActionCableAdapter)
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
 
@@ -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
@@ -3,8 +3,7 @@
3
3
  require 'bundler/setup'
4
4
  require 'ar_sync'
5
5
  require 'pry'
6
- require_relative '../test/model_tree'
7
- require_relative '../test/model_graph'
6
+ require_relative '../test/model'
8
7
  ArSync.on_notification do |events|
9
8
  puts "\e[1m#{events.inspect}\e[m"
10
9
  end
@@ -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
- constructor();
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 {};
@@ -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 = ActionCable.createConsumer();
23
+ this._cable = this.actionCableClass.createConsumer();
24
24
  return this._cable.subscriptions.create({ channel: 'SyncChannel', key }, { received, disconnected, connected });
25
25
  }
26
26
  ondisconnect() { }
@@ -1,5 +1,8 @@
1
- declare const _default: {
2
- fetch: (request: object) => Promise<{}>;
3
- syncFetch: (request: object) => Promise<{}>;
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 _default;
8
+ export default ArSyncApi;
@@ -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
- apiBatchFetch(this.endpoint, requests).then((results) => {
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((e) => {
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
- exports.default = {
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
- interface Adapter {
17
- subscribe: (key: string, received: (data: any) => void) => {
18
- unsubscribe: () => void;
19
- };
20
- ondisconnect: () => void;
21
- onreconnect: () => void;
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(_adapter: Adapter): void;
69
- static waitForLoad(...models: ArSyncModelBase<{}>[]): Promise<{}>;
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
- class ArSyncModelBase {
3
+ const ArSyncStore_1 = require("./ArSyncStore");
4
+ const ConnectionManager_1 = require("./ConnectionManager");
5
+ class ArSyncModel {
4
6
  constructor(request, option) {
5
- this._ref = this.refManagerClass().retrieveRef(request, option);
7
+ this._ref = ArSyncModel.retrieveRef(request, option);
6
8
  this._listenerSerial = 0;
7
9
  this._listeners = {};
8
10
  this.complete = false;
9
- this.connected = this.connectionManager().networkStatus;
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
- this.refManagerClass()._detach(this._ref);
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 = this.createRefModel(request, option);
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(_adapter) { }
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 = ArSyncModelBase;
131
+ exports.default = ArSyncModel;
132
+ ArSyncModel._cache = {};
133
+ ArSyncModel.cacheTimeout = 10 * 1000;
@@ -1,9 +1,9 @@
1
1
  export default class ArSyncStore {
2
- immutable: any;
3
- markedForFreezeObjects: any;
2
+ immutable: boolean;
3
+ markedForFreezeObjects: any[];
4
4
  changes: any;
5
5
  eventListeners: any;
6
- markForRelease: any;
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("../core/ArSyncApi");
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 parseQuery(query, attrsonly = false) {
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, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root));
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 (subQuery.attributes && (subData instanceof Array || (subData && subData.collection && subData.order))) {
197
- if (this.children[aliasName]) {
198
- this.children[aliasName].replaceData(subData, this.sync_keys);
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 (this.children[aliasName]) {
214
- this.children[aliasName].replaceData(subData);
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 (this.children[aliasName]) {
227
- this.children[aliasName].release();
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[path].release();
243
- this.children[path] = null;
308
+ const child = this.children[aliasName];
309
+ if (child)
310
+ child.release();
311
+ this.children[aliasName] = null;
244
312
  this.mark();
245
- this.data[path] = null;
246
- this.onChange([path], null);
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
- const query = this.query.attributes[path];
252
- ModelBatchRequest.fetch(class_name, query, id).then(data => {
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
- if (this.children[path])
257
- this.children[path].release();
258
- this.children[path] = model;
323
+ const child = this.children[aliasName];
324
+ if (child)
325
+ child.release();
326
+ this.children[aliasName] = model;
259
327
  this.mark();
260
- this.data[path] = model.data;
328
+ this.data[aliasName] = model.data;
261
329
  model.parentModel = this;
262
- model.parentKey = path;
263
- this.onChange([path], model.data);
330
+ model.parentKey = aliasName;
331
+ this.onChange([aliasName], model.data);
264
332
  });
265
333
  }
266
334
  else {
267
- ModelBatchRequest.fetch(class_name, this.reloadQuery(), id).then(data => {
268
- this.update(data);
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.order = { limit: query.params.limit, mode: query.params.order || 'asc' };
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 (data.collection && data.order) {
468
+ if ('collection' in data && 'order' in data) {
360
469
  collection = data.collection;
361
- this.order = data.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 = existings[subData.id];
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
- newChildren.push(model);
379
- newData.push(model.data);
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.query, id).then((data) => {
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.id > id) {
425
- this.children.sort((a, b) => a.data.id < b.data.id ? -1 : +1);
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.id > id) {
439
- this.children.sort((a, b) => a.data.id > b.data.id ? -1 : +1);
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) {