ar_sync 1.0.5 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +27 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +26 -28
- data/core/ArSyncApi.d.ts +8 -2
- data/core/ArSyncApi.js +11 -1
- data/core/ArSyncStore.js +208 -162
- data/core/ConnectionManager.d.ts +1 -1
- data/core/ConnectionManager.js +2 -0
- data/core/DataType.d.ts +14 -9
- data/core/hooks.d.ts +1 -0
- data/core/hooks.js +9 -6
- data/gemfiles/Gemfile-rails-6 +9 -0
- data/gemfiles/Gemfile-rails-7 +9 -0
- data/index.js +2 -2
- data/lib/ar_sync/class_methods.rb +64 -23
- data/lib/ar_sync/collection.rb +23 -19
- data/lib/ar_sync/core.rb +3 -3
- data/lib/ar_sync/instance_methods.rb +1 -1
- data/lib/ar_sync/rails.rb +14 -17
- data/lib/ar_sync/version.rb +1 -1
- data/package-lock.json +1706 -227
- data/package.json +1 -1
- data/src/core/ArSyncApi.ts +20 -7
- data/src/core/ArSyncStore.ts +191 -143
- data/src/core/ConnectionManager.ts +1 -0
- data/src/core/DataType.ts +14 -15
- data/src/core/hooks.ts +5 -5
- metadata +9 -7
- data/lib/ar_sync/field.rb +0 -96
data/package.json
CHANGED
data/src/core/ArSyncApi.ts
CHANGED
@@ -10,12 +10,17 @@ async function apiBatchFetch(endpoint: string, requests: object[]) {
|
|
10
10
|
if (res.status === 200) return res.json()
|
11
11
|
throw new Error(res.statusText)
|
12
12
|
}
|
13
|
-
|
13
|
+
type FetchError = {
|
14
|
+
type: string
|
15
|
+
message: string
|
16
|
+
retry: boolean
|
17
|
+
}
|
14
18
|
interface PromiseCallback {
|
15
|
-
resolve: (data:
|
16
|
-
reject: (error:
|
19
|
+
resolve: (data: any) => void
|
20
|
+
reject: (error: FetchError) => void
|
17
21
|
}
|
18
22
|
|
23
|
+
type Request = { api: string; params?: any; query: any; id?: number }
|
19
24
|
class ApiFetcher {
|
20
25
|
endpoint: string
|
21
26
|
batches: [object, PromiseCallback][] = []
|
@@ -23,7 +28,15 @@ class ApiFetcher {
|
|
23
28
|
constructor(endpoint: string) {
|
24
29
|
this.endpoint = endpoint
|
25
30
|
}
|
26
|
-
fetch(request:
|
31
|
+
fetch(request: Request) {
|
32
|
+
if (request.id != null) {
|
33
|
+
return new Promise((resolve, reject) => {
|
34
|
+
this.fetch({ api: request.api, params: { ids: [request.id] }, query: request.query }).then((result: any[]) => {
|
35
|
+
if (result[0]) resolve(result[0])
|
36
|
+
else reject({ type: 'Not Found', retry: false })
|
37
|
+
}).catch(reject)
|
38
|
+
})
|
39
|
+
}
|
27
40
|
return new Promise((resolve, reject) => {
|
28
41
|
this.batches.push([request, { resolve, reject }])
|
29
42
|
if (this.batchFetchTimer) return
|
@@ -49,7 +62,7 @@ class ApiFetcher {
|
|
49
62
|
const result = results[i]
|
50
63
|
const callbacks = callbacksList[i]
|
51
64
|
for (const callback of callbacks) {
|
52
|
-
if (result.data) {
|
65
|
+
if (result.data !== undefined) {
|
53
66
|
callback.resolve(result.data)
|
54
67
|
} else {
|
55
68
|
const error = result.error || { type: 'Unknown Error' }
|
@@ -73,7 +86,7 @@ const syncFetcher = new ApiFetcher('/sync_api')
|
|
73
86
|
const ArSyncApi = {
|
74
87
|
domain: null as string | null,
|
75
88
|
_batchFetch: apiBatchFetch,
|
76
|
-
fetch: (request:
|
77
|
-
syncFetch: (request:
|
89
|
+
fetch: (request: Request) => staticFetcher.fetch(request),
|
90
|
+
syncFetch: (request: Request) => syncFetcher.fetch(request),
|
78
91
|
}
|
79
92
|
export default ArSyncApi
|
data/src/core/ArSyncStore.ts
CHANGED
@@ -1,47 +1,56 @@
|
|
1
|
-
import
|
1
|
+
import ArSyncApi from './ArSyncApi'
|
2
2
|
|
3
|
-
|
4
|
-
timer: null
|
5
|
-
apiRequests
|
6
|
-
|
7
|
-
|
8
|
-
query
|
9
|
-
requests: {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
3
|
+
class ModelBatchRequest {
|
4
|
+
timer: number | null = null
|
5
|
+
apiRequests = new Map<string,
|
6
|
+
Map<string,
|
7
|
+
{
|
8
|
+
query,
|
9
|
+
requests: Map<number, {
|
10
|
+
id: number
|
11
|
+
model?
|
12
|
+
callbacks: {
|
13
|
+
resolve: (model: any) => void
|
14
|
+
reject: (error?: any) => void
|
15
|
+
}[]
|
16
|
+
}>
|
16
17
|
}
|
17
|
-
|
18
|
-
|
18
|
+
>
|
19
|
+
>()
|
19
20
|
fetch(api: string, query, id: number) {
|
20
21
|
this.setTimer()
|
21
|
-
return new Promise(resolve => {
|
22
|
+
return new Promise((resolve, reject) => {
|
22
23
|
const queryJSON = JSON.stringify(query)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
let apiRequest = this.apiRequests.get(api)
|
25
|
+
if (!apiRequest) this.apiRequests.set(api, apiRequest = new Map())
|
26
|
+
let queryRequests = apiRequest.get(queryJSON)
|
27
|
+
if (!queryRequests) apiRequest.set(queryJSON, queryRequests = { query, requests: new Map() })
|
28
|
+
let request = queryRequests.requests.get(id)
|
29
|
+
if (!request) queryRequests.requests.set(id, request = { id, callbacks: [] })
|
30
|
+
request.callbacks.push({ resolve, reject })
|
27
31
|
})
|
28
|
-
}
|
32
|
+
}
|
29
33
|
batchFetch() {
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
for (const { model, callbacks } of Object.values(requests)) {
|
38
|
-
for (const callback of callbacks) callback(model)
|
34
|
+
this.apiRequests.forEach((apiRequest, api) => {
|
35
|
+
apiRequest.forEach(({ query, requests }) => {
|
36
|
+
const ids = Array.from(requests.keys())
|
37
|
+
ArSyncApi.syncFetch({ api, query, params: { ids } }).then((models: any[]) => {
|
38
|
+
for (const model of models) {
|
39
|
+
const req = requests.get(model.id)
|
40
|
+
if (req) req.model = model
|
39
41
|
}
|
42
|
+
requests.forEach(({ model, callbacks }) => {
|
43
|
+
callbacks.forEach(cb => cb.resolve(model))
|
44
|
+
})
|
45
|
+
}).catch(e => {
|
46
|
+
requests.forEach(({ callbacks }) => {
|
47
|
+
callbacks.forEach(cb => cb.reject(e))
|
48
|
+
})
|
40
49
|
})
|
41
|
-
}
|
42
|
-
}
|
43
|
-
this.apiRequests
|
44
|
-
}
|
50
|
+
})
|
51
|
+
})
|
52
|
+
this.apiRequests.clear()
|
53
|
+
}
|
45
54
|
setTimer() {
|
46
55
|
if (this.timer) return
|
47
56
|
this.timer = setTimeout(() => {
|
@@ -50,6 +59,7 @@ const ModelBatchRequest = {
|
|
50
59
|
}, 20)
|
51
60
|
}
|
52
61
|
}
|
62
|
+
const modelBatchRequest = new ModelBatchRequest
|
53
63
|
|
54
64
|
type ParsedQuery = {
|
55
65
|
attributes: Record<string, ParsedQuery>
|
@@ -57,31 +67,42 @@ type ParsedQuery = {
|
|
57
67
|
params: any
|
58
68
|
} | {}
|
59
69
|
|
70
|
+
type Unsubscribable = { unsubscribe: () => void }
|
71
|
+
|
60
72
|
class ArSyncContainerBase {
|
61
73
|
data
|
62
|
-
listeners
|
63
|
-
networkSubscriber
|
74
|
+
listeners: Unsubscribable[] = []
|
75
|
+
networkSubscriber?: Unsubscribable
|
64
76
|
parentModel
|
65
77
|
parentKey
|
66
78
|
children: ArSyncContainerBase[] | { [key: string]: ArSyncContainerBase | null }
|
67
79
|
sync_keys: string[]
|
68
|
-
onConnectionChange
|
69
|
-
constructor() {
|
70
|
-
this.listeners = []
|
71
|
-
}
|
80
|
+
onConnectionChange?: (status: boolean) => void
|
72
81
|
replaceData(_data, _sync_keys?) {}
|
73
82
|
initForReload(request) {
|
74
83
|
this.networkSubscriber = ArSyncStore.connectionManager.subscribeNetwork((state) => {
|
75
|
-
if (state) {
|
76
|
-
|
77
|
-
|
84
|
+
if (!state) {
|
85
|
+
if (this.onConnectionChange) this.onConnectionChange(false)
|
86
|
+
return
|
87
|
+
}
|
88
|
+
if (request.id != null) {
|
89
|
+
modelBatchRequest.fetch(request.api, request.query, request.id).then(data => {
|
90
|
+
if (this.data && data) {
|
78
91
|
this.replaceData(data)
|
79
92
|
if (this.onConnectionChange) this.onConnectionChange(true)
|
80
93
|
if (this.onChange) this.onChange([], this.data)
|
81
94
|
}
|
82
95
|
})
|
83
96
|
} else {
|
84
|
-
|
97
|
+
ArSyncApi.syncFetch(request).then(data => {
|
98
|
+
if (this.data && data) {
|
99
|
+
this.replaceData(data)
|
100
|
+
if (this.onConnectionChange) this.onConnectionChange(true)
|
101
|
+
if (this.onChange) this.onChange([], this.data)
|
102
|
+
}
|
103
|
+
}).catch(e => {
|
104
|
+
console.error(`failed to reload. ${e}`)
|
105
|
+
})
|
85
106
|
}
|
86
107
|
})
|
87
108
|
}
|
@@ -103,8 +124,8 @@ class ArSyncContainerBase {
|
|
103
124
|
for (const l of this.listeners) l.unsubscribe()
|
104
125
|
this.listeners = []
|
105
126
|
}
|
106
|
-
static
|
107
|
-
function compactAttributes(attributes: Record<string, ParsedQuery>) {
|
127
|
+
static compactQueryAttributes(query: ParsedQuery) {
|
128
|
+
function compactAttributes(attributes: Record<string, ParsedQuery>): [ParsedQuery, boolean] {
|
108
129
|
const attrs = {}
|
109
130
|
const keys: string[] = []
|
110
131
|
for (const key in attributes) {
|
@@ -118,13 +139,13 @@ class ArSyncContainerBase {
|
|
118
139
|
if (Object.keys(attrs).length === 0) {
|
119
140
|
if (keys.length === 0) return [true, false]
|
120
141
|
if (keys.length === 1) return [keys[0], false]
|
121
|
-
return [keys]
|
142
|
+
return [keys, false]
|
122
143
|
}
|
123
144
|
const needsEscape = attrs['attributes'] || attrs['params'] || attrs['as']
|
124
145
|
if (keys.length === 0) return [attrs, needsEscape]
|
125
146
|
return [[...keys, attrs], needsEscape]
|
126
147
|
}
|
127
|
-
function compactQuery(query: ParsedQuery) {
|
148
|
+
function compactQuery(query: ParsedQuery): ParsedQuery {
|
128
149
|
if (!('attributes' in query)) return true
|
129
150
|
const { as, params } = query
|
130
151
|
const [attributes, needsEscape] = compactAttributes(query.attributes)
|
@@ -138,10 +159,9 @@ class ArSyncContainerBase {
|
|
138
159
|
if (attributes !== true) result.attributes = attributes
|
139
160
|
return result
|
140
161
|
}
|
141
|
-
try{
|
142
162
|
const result = compactQuery(query)
|
163
|
+
if (typeof result === 'object' && 'attributes' in result) return result.attributes
|
143
164
|
return result === true ? {} : result
|
144
|
-
}catch(e){throw JSON.stringify(query)+e.stack}
|
145
165
|
}
|
146
166
|
static parseQuery(query, attrsonly: true): Record<string, ParsedQuery>
|
147
167
|
static parseQuery(query): ParsedQuery
|
@@ -179,13 +199,19 @@ class ArSyncContainerBase {
|
|
179
199
|
}
|
180
200
|
static _load({ api, id, params, query }, root) {
|
181
201
|
const parsedQuery = ArSyncRecord.parseQuery(query)
|
182
|
-
const
|
183
|
-
if (id) {
|
184
|
-
return
|
202
|
+
const compactQueryAttributes = ArSyncRecord.compactQueryAttributes(parsedQuery)
|
203
|
+
if (id != null) {
|
204
|
+
return modelBatchRequest.fetch(api, compactQueryAttributes, id).then(data => {
|
205
|
+
if (!data) throw { retry: false }
|
206
|
+
const request = { api, id, query: compactQueryAttributes }
|
207
|
+
return new ArSyncRecord(parsedQuery, data, request, root)
|
208
|
+
})
|
185
209
|
} else {
|
186
|
-
const request = { api, query:
|
187
|
-
return
|
188
|
-
if (response
|
210
|
+
const request = { api, query: compactQueryAttributes, params }
|
211
|
+
return ArSyncApi.syncFetch(request).then((response: any) => {
|
212
|
+
if (!response) {
|
213
|
+
throw { retry: false }
|
214
|
+
} else if (response.collection && response.order) {
|
189
215
|
return new ArSyncCollection(response.sync_keys, 'collection', parsedQuery, response, request, root)
|
190
216
|
} else if (response instanceof Array) {
|
191
217
|
return new ArSyncCollection([], '', parsedQuery, response, request, root)
|
@@ -222,6 +248,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
222
248
|
id: number
|
223
249
|
root
|
224
250
|
query
|
251
|
+
queryAttributes
|
225
252
|
data
|
226
253
|
children: { [key: string]: ArSyncContainerBase | null }
|
227
254
|
paths: string[]
|
@@ -231,15 +258,13 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
231
258
|
this.root = root
|
232
259
|
if (request) this.initForReload(request)
|
233
260
|
this.query = query
|
261
|
+
this.queryAttributes = query.attributes || {}
|
234
262
|
this.data = {}
|
235
263
|
this.children = {}
|
236
264
|
this.replaceData(data)
|
237
265
|
}
|
238
|
-
setSyncKeys(sync_keys: string[]) {
|
239
|
-
this.sync_keys = sync_keys
|
240
|
-
if (!this.sync_keys) {
|
241
|
-
this.sync_keys = []
|
242
|
-
}
|
266
|
+
setSyncKeys(sync_keys: string[] | undefined) {
|
267
|
+
this.sync_keys = sync_keys ?? []
|
243
268
|
}
|
244
269
|
replaceData(data) {
|
245
270
|
this.setSyncKeys(data.sync_keys)
|
@@ -249,8 +274,8 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
249
274
|
this.data.id = data.id
|
250
275
|
}
|
251
276
|
this.paths = []
|
252
|
-
for (const key in this.
|
253
|
-
const subQuery = this.
|
277
|
+
for (const key in this.queryAttributes) {
|
278
|
+
const subQuery = this.queryAttributes[key]
|
254
279
|
const aliasName = subQuery.as || key
|
255
280
|
const subData = data[aliasName]
|
256
281
|
const child = this.children[aliasName]
|
@@ -291,9 +316,9 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
291
316
|
}
|
292
317
|
}
|
293
318
|
}
|
294
|
-
if (this.
|
319
|
+
if (this.queryAttributes['*']) {
|
295
320
|
for (const key in data) {
|
296
|
-
if (!this.
|
321
|
+
if (!this.queryAttributes[key] && this.data[key] !== data[key]) {
|
297
322
|
this.mark()
|
298
323
|
this.data[key] = data[key]
|
299
324
|
}
|
@@ -302,8 +327,8 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
302
327
|
this.subscribeAll()
|
303
328
|
}
|
304
329
|
onNotify(notifyData: NotifyData, path?: string) {
|
305
|
-
const { action, class_name, id } = notifyData
|
306
|
-
const query = path && this.
|
330
|
+
const { action, class_name: className, id } = notifyData
|
331
|
+
const query = path && this.queryAttributes[path]
|
307
332
|
const aliasName = (query && query.as) || path;
|
308
333
|
if (action === 'remove') {
|
309
334
|
const child = this.children[aliasName]
|
@@ -314,7 +339,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
314
339
|
this.onChange([aliasName], null)
|
315
340
|
} else if (action === 'add') {
|
316
341
|
if (this.data[aliasName] && this.data[aliasName].id === id) return
|
317
|
-
|
342
|
+
modelBatchRequest.fetch(className, ArSyncRecord.compactQueryAttributes(query), id).then(data => {
|
318
343
|
if (!data || !this.data) return
|
319
344
|
const model = new ArSyncRecord(query, data, null, this.root)
|
320
345
|
const child = this.children[aliasName]
|
@@ -325,12 +350,17 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
325
350
|
model.parentModel = this
|
326
351
|
model.parentKey = aliasName
|
327
352
|
this.onChange([aliasName], model.data)
|
353
|
+
}).catch(e => {
|
354
|
+
console.error(`failed to load ${className}:${id} ${e}`)
|
328
355
|
})
|
329
356
|
} else {
|
330
357
|
const { field } = notifyData
|
331
358
|
const query = field ? this.patchQuery(field) : this.reloadQuery()
|
332
|
-
if (query)
|
359
|
+
if (!query) return
|
360
|
+
modelBatchRequest.fetch(className, query, id).then(data => {
|
333
361
|
if (this.data) this.update(data)
|
362
|
+
}).catch(e => {
|
363
|
+
console.error(`failed to load patch ${className}:${id} ${e}`)
|
334
364
|
})
|
335
365
|
}
|
336
366
|
}
|
@@ -345,34 +375,29 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
345
375
|
}
|
346
376
|
}
|
347
377
|
patchQuery(key: string) {
|
348
|
-
const
|
349
|
-
if (
|
350
|
-
let { attributes, as, params } = val
|
351
|
-
if (attributes && Object.keys(val.attributes).length === 0) attributes = null
|
352
|
-
if (!attributes && !as && !params) return key
|
353
|
-
const result: { attributes?; as?; params? } = {}
|
354
|
-
if (attributes) result.attributes = attributes
|
355
|
-
if (as) result.as = as
|
356
|
-
if (params) result.params = params
|
357
|
-
return result
|
378
|
+
const subQuery = this.queryAttributes[key]
|
379
|
+
if (subQuery) return { [key]: subQuery }
|
358
380
|
}
|
359
381
|
reloadQuery() {
|
360
382
|
if (this.reloadQueryCache) return this.reloadQueryCache
|
361
|
-
|
362
|
-
|
383
|
+
let arrayQuery = [] as string[] | null
|
384
|
+
const hashQuery = {}
|
385
|
+
for (const key in this.queryAttributes) {
|
363
386
|
if (key === 'sync_keys') continue
|
364
|
-
const val = this.
|
387
|
+
const val = this.queryAttributes[key]
|
365
388
|
if (!val || !val.attributes) {
|
366
|
-
|
389
|
+
arrayQuery?.push(key)
|
390
|
+
hashQuery[key] = true
|
367
391
|
} else if (!val.params && Object.keys(val.attributes).length === 0) {
|
368
|
-
|
392
|
+
arrayQuery = null
|
393
|
+
hashQuery[key] = val
|
369
394
|
}
|
370
395
|
}
|
371
|
-
return
|
396
|
+
return this.reloadQueryCache = arrayQuery || hashQuery
|
372
397
|
}
|
373
398
|
update(data) {
|
374
399
|
for (const key in data) {
|
375
|
-
const subQuery = this.
|
400
|
+
const subQuery = this.queryAttributes[key]
|
376
401
|
if (subQuery && subQuery.attributes && Object.keys(subQuery.attributes).length > 0) continue
|
377
402
|
if (this.data[key] === data[key]) continue
|
378
403
|
this.mark()
|
@@ -392,12 +417,14 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
392
417
|
}
|
393
418
|
}
|
394
419
|
|
420
|
+
type Ordering = { first?: number; last?: number; orderBy: string; direction: 'asc' | 'desc' }
|
395
421
|
class ArSyncCollection extends ArSyncContainerBase {
|
396
422
|
root
|
397
423
|
path: string
|
398
|
-
|
424
|
+
ordering: Ordering = { orderBy: 'id', direction: 'asc' }
|
399
425
|
query
|
400
|
-
|
426
|
+
queryAttributes
|
427
|
+
compactQueryAttributes
|
401
428
|
data: any[]
|
402
429
|
children: ArSyncRecord[]
|
403
430
|
aliasOrderKey = 'id'
|
@@ -406,31 +433,28 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
406
433
|
this.root = root
|
407
434
|
this.path = path
|
408
435
|
this.query = query
|
409
|
-
this.
|
436
|
+
this.queryAttributes = query.attributes || {}
|
437
|
+
this.compactQueryAttributes = ArSyncRecord.compactQueryAttributes(query)
|
410
438
|
if (request) this.initForReload(request)
|
411
|
-
if (query.params
|
412
|
-
this.setOrdering(query.params
|
439
|
+
if (query.params) {
|
440
|
+
this.setOrdering(query.params)
|
413
441
|
}
|
414
442
|
this.data = []
|
415
443
|
this.children = []
|
416
444
|
this.replaceData(data, sync_keys)
|
417
445
|
}
|
418
|
-
setOrdering(
|
419
|
-
let
|
420
|
-
let
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
if (limitNumber !== null && key !== 'id') throw 'limit with custom order key is not supported'
|
431
|
-
const subQuery = this.query.attributes[key]
|
432
|
-
this.aliasOrderKey = (subQuery && subQuery.as) || key
|
433
|
-
this.order = { limit: limitNumber, mode, key }
|
446
|
+
setOrdering(ordering: { first?: unknown; last?: unknown; orderBy?: unknown; direction?: unknown }) {
|
447
|
+
let direction: 'asc' | 'desc' = 'asc'
|
448
|
+
let orderBy: string = 'id'
|
449
|
+
let first: number | undefined = undefined
|
450
|
+
let last: number | undefined = undefined
|
451
|
+
if (ordering.direction === 'desc') direction = ordering.direction
|
452
|
+
if (typeof ordering.orderBy === 'string') orderBy = ordering.orderBy
|
453
|
+
if (typeof ordering.first === 'number') first = ordering.first
|
454
|
+
if (typeof ordering.last === 'number') last = ordering.last
|
455
|
+
const subQuery = this.queryAttributes[orderBy]
|
456
|
+
this.aliasOrderKey = (subQuery && subQuery.as) || orderBy
|
457
|
+
this.ordering = { first, last, direction, orderBy }
|
434
458
|
}
|
435
459
|
setSyncKeys(sync_keys: string[]) {
|
436
460
|
if (sync_keys) {
|
@@ -439,26 +463,26 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
439
463
|
this.sync_keys = []
|
440
464
|
}
|
441
465
|
}
|
442
|
-
replaceData(data: any[] | { collection: any[];
|
466
|
+
replaceData(data: any[] | { collection: any[]; ordering: Ordering }, sync_keys: string[]) {
|
443
467
|
this.setSyncKeys(sync_keys)
|
444
|
-
const existings
|
445
|
-
for (const child of this.children) existings
|
468
|
+
const existings = new Map<number, ArSyncRecord>()
|
469
|
+
for (const child of this.children) existings.set(child.data.id, child)
|
446
470
|
let collection: any[]
|
447
|
-
if (
|
448
|
-
collection = data.collection
|
449
|
-
this.setOrdering(data.order.limit, data.order.mode)
|
450
|
-
} else {
|
471
|
+
if (Array.isArray(data)) {
|
451
472
|
collection = data
|
473
|
+
} else {
|
474
|
+
collection = data.collection
|
475
|
+
this.setOrdering(data.ordering)
|
452
476
|
}
|
453
477
|
const newChildren: any[] = []
|
454
478
|
const newData: any[] = []
|
455
479
|
for (const subData of collection) {
|
456
|
-
let model: ArSyncRecord |
|
457
|
-
if (typeof(subData) === 'object' && subData && '
|
480
|
+
let model: ArSyncRecord | undefined = undefined
|
481
|
+
if (typeof(subData) === 'object' && subData && 'sync_keys' in subData) model = existings.get(subData.id)
|
458
482
|
let data = subData
|
459
483
|
if (model) {
|
460
484
|
model.replaceData(subData)
|
461
|
-
} else if (subData.
|
485
|
+
} else if (subData.sync_keys) {
|
462
486
|
model = new ArSyncRecord(this.query, subData, null, this.root)
|
463
487
|
model.parentModel = this
|
464
488
|
model.parentKey = subData.id
|
@@ -471,7 +495,7 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
471
495
|
}
|
472
496
|
while (this.children.length) {
|
473
497
|
const child = this.children.pop()!
|
474
|
-
if (!existings
|
498
|
+
if (!existings.has(child.data.id)) child.release()
|
475
499
|
}
|
476
500
|
if (this.data.length || newChildren.length) this.mark()
|
477
501
|
while (this.data.length) this.data.pop()
|
@@ -480,54 +504,78 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
480
504
|
this.subscribeAll()
|
481
505
|
}
|
482
506
|
consumeAdd(className: string, id: number) {
|
507
|
+
const { first, last, direction } = this.ordering
|
508
|
+
const limit = first || last
|
483
509
|
if (this.data.findIndex(a => a.id === id) >= 0) return
|
484
|
-
if (
|
485
|
-
|
486
|
-
|
487
|
-
|
510
|
+
if (limit && limit <= this.data.length) {
|
511
|
+
const lastItem = this.data[this.data.length - 1]
|
512
|
+
const firstItem = this.data[0]
|
513
|
+
if (direction === 'asc') {
|
514
|
+
if (first) {
|
515
|
+
if (lastItem && lastItem.id < id) return
|
516
|
+
} else {
|
517
|
+
if (firstItem && id < firstItem.id) return
|
518
|
+
}
|
488
519
|
} else {
|
489
|
-
|
490
|
-
|
520
|
+
if (first) {
|
521
|
+
if (lastItem && id < lastItem.id) return
|
522
|
+
} else {
|
523
|
+
if (firstItem && firstItem.id < id) return
|
524
|
+
}
|
491
525
|
}
|
492
526
|
}
|
493
|
-
|
527
|
+
modelBatchRequest.fetch(className, this.compactQueryAttributes, id).then((data: any) => {
|
494
528
|
if (!data || !this.data) return
|
495
529
|
const model = new ArSyncRecord(this.query, data, null, this.root)
|
496
530
|
model.parentModel = this
|
497
531
|
model.parentKey = id
|
498
|
-
const overflow =
|
532
|
+
const overflow = limit && limit <= this.data.length
|
499
533
|
let rmodel: ArSyncRecord | undefined
|
500
534
|
this.mark()
|
501
535
|
const orderKey = this.aliasOrderKey
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
this.
|
536
|
+
const firstItem = this.data[0]
|
537
|
+
const lastItem = this.data[this.data.length - 1]
|
538
|
+
if (direction === 'asc') {
|
539
|
+
if (firstItem && data[orderKey] < firstItem[orderKey]) {
|
540
|
+
this.children.unshift(model)
|
541
|
+
this.data.unshift(model.data)
|
542
|
+
} else {
|
543
|
+
const skipSort = lastItem && lastItem[orderKey] < data[orderKey]
|
544
|
+
this.children.push(model)
|
545
|
+
this.data.push(model.data)
|
546
|
+
if (!skipSort) this.markAndSort()
|
511
547
|
}
|
512
548
|
} else {
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
549
|
+
if (firstItem && data[orderKey] > firstItem[orderKey]) {
|
550
|
+
this.children.unshift(model)
|
551
|
+
this.data.unshift(model.data)
|
552
|
+
} else {
|
553
|
+
const skipSort = lastItem && lastItem[orderKey] > data[orderKey]
|
554
|
+
this.children.push(model)
|
555
|
+
this.data.push(model.data)
|
556
|
+
if (!skipSort) this.markAndSort()
|
557
|
+
}
|
558
|
+
}
|
559
|
+
if (overflow) {
|
560
|
+
if (first) {
|
518
561
|
rmodel = this.children.pop()!
|
519
|
-
rmodel.release()
|
520
562
|
this.data.pop()
|
563
|
+
} else {
|
564
|
+
rmodel = this.children.shift()!
|
565
|
+
this.data.shift()
|
521
566
|
}
|
567
|
+
rmodel.release()
|
522
568
|
}
|
523
569
|
this.onChange([model.id], model.data)
|
524
570
|
if (rmodel) this.onChange([rmodel.id], null)
|
571
|
+
}).catch(e => {
|
572
|
+
console.error(`failed to load ${className}:${id} ${e}`)
|
525
573
|
})
|
526
574
|
}
|
527
575
|
markAndSort() {
|
528
576
|
this.mark()
|
529
577
|
const orderKey = this.aliasOrderKey
|
530
|
-
if (this.
|
578
|
+
if (this.ordering.direction === 'asc') {
|
531
579
|
this.children.sort((a, b) => a.data[orderKey] < b.data[orderKey] ? -1 : +1)
|
532
580
|
this.data.sort((a, b) => a[orderKey] < b[orderKey] ? -1 : +1)
|
533
581
|
} else {
|