ar_sync 1.0.5 → 1.1.1
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/.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 {
|