ar_sync 1.0.3 → 1.1.0
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 +29 -31
- data/ar_sync.gemspec +1 -1
- data/core/{ActioncableAdapter.d.ts → ActionCableAdapter.d.ts} +0 -0
- data/core/ActionCableAdapter.js +31 -0
- data/core/ArSyncApi.d.ts +8 -2
- data/core/ArSyncApi.js +123 -49
- data/core/ArSyncModel.js +69 -60
- data/core/ArSyncStore.js +522 -381
- data/core/ConnectionManager.d.ts +1 -1
- data/core/ConnectionManager.js +45 -38
- data/core/DataType.d.ts +14 -9
- data/core/hooks.d.ts +5 -0
- data/core/hooks.js +64 -36
- 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 +71 -36
- data/lib/ar_sync/collection.rb +23 -19
- data/lib/ar_sync/core.rb +3 -3
- data/lib/ar_sync/instance_methods.rb +7 -4
- data/lib/ar_sync/rails.rb +1 -1
- data/lib/ar_sync/type_script.rb +50 -14
- data/lib/ar_sync/version.rb +1 -1
- data/lib/generators/ar_sync/install/install_generator.rb +1 -1
- data/package-lock.json +1706 -227
- data/package.json +1 -1
- data/src/core/{ActioncableAdapter.ts → ActionCableAdapter.ts} +0 -0
- data/src/core/ArSyncApi.ts +20 -7
- data/src/core/ArSyncStore.ts +177 -125
- data/src/core/ConnectionManager.ts +1 -0
- data/src/core/DataType.ts +15 -16
- data/src/core/hooks.ts +31 -7
- data/tsconfig.json +2 -2
- data/vendor/assets/javascripts/{ar_sync_actioncable_adapter.js.erb → ar_sync_action_cable_adapter.js.erb} +1 -1
- metadata +17 -16
- data/core/ActioncableAdapter.js +0 -29
- data/lib/ar_sync/field.rb +0 -96
data/package.json
CHANGED
File without changes
|
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
|
}
|
@@ -104,7 +125,7 @@ class ArSyncContainerBase {
|
|
104
125
|
this.listeners = []
|
105
126
|
}
|
106
127
|
static compactQuery(query: ParsedQuery) {
|
107
|
-
function compactAttributes(attributes: Record<string, 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,8 @@ class ArSyncContainerBase {
|
|
138
159
|
if (attributes !== true) result.attributes = attributes
|
139
160
|
return result
|
140
161
|
}
|
141
|
-
try{
|
142
162
|
const result = compactQuery(query)
|
143
163
|
return result === true ? {} : result
|
144
|
-
}catch(e){throw JSON.stringify(query)+e.stack}
|
145
164
|
}
|
146
165
|
static parseQuery(query, attrsonly: true): Record<string, ParsedQuery>
|
147
166
|
static parseQuery(query): ParsedQuery
|
@@ -180,12 +199,18 @@ class ArSyncContainerBase {
|
|
180
199
|
static _load({ api, id, params, query }, root) {
|
181
200
|
const parsedQuery = ArSyncRecord.parseQuery(query)
|
182
201
|
const compactQuery = ArSyncRecord.compactQuery(parsedQuery)
|
183
|
-
if (id) {
|
184
|
-
return
|
202
|
+
if (id != null) {
|
203
|
+
return modelBatchRequest.fetch(api, compactQuery, id).then(data => {
|
204
|
+
if (!data) throw { retry: false }
|
205
|
+
const request = { api, id, query: compactQuery }
|
206
|
+
return new ArSyncRecord(parsedQuery, data, request, root)
|
207
|
+
})
|
185
208
|
} else {
|
186
209
|
const request = { api, query: compactQuery, params }
|
187
|
-
return
|
188
|
-
if (response
|
210
|
+
return ArSyncApi.syncFetch(request).then((response: any) => {
|
211
|
+
if (!response) {
|
212
|
+
throw { retry: false }
|
213
|
+
} else if (response.collection && response.order) {
|
189
214
|
return new ArSyncCollection(response.sync_keys, 'collection', parsedQuery, response, request, root)
|
190
215
|
} else if (response instanceof Array) {
|
191
216
|
return new ArSyncCollection([], '', parsedQuery, response, request, root)
|
@@ -222,6 +247,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
222
247
|
id: number
|
223
248
|
root
|
224
249
|
query
|
250
|
+
queryAttributes
|
225
251
|
data
|
226
252
|
children: { [key: string]: ArSyncContainerBase | null }
|
227
253
|
paths: string[]
|
@@ -231,15 +257,13 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
231
257
|
this.root = root
|
232
258
|
if (request) this.initForReload(request)
|
233
259
|
this.query = query
|
260
|
+
this.queryAttributes = query.attributes || {}
|
234
261
|
this.data = {}
|
235
262
|
this.children = {}
|
236
263
|
this.replaceData(data)
|
237
264
|
}
|
238
|
-
setSyncKeys(sync_keys: string[]) {
|
239
|
-
this.sync_keys = sync_keys
|
240
|
-
if (!this.sync_keys) {
|
241
|
-
this.sync_keys = []
|
242
|
-
}
|
265
|
+
setSyncKeys(sync_keys: string[] | undefined) {
|
266
|
+
this.sync_keys = sync_keys ?? []
|
243
267
|
}
|
244
268
|
replaceData(data) {
|
245
269
|
this.setSyncKeys(data.sync_keys)
|
@@ -249,8 +273,8 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
249
273
|
this.data.id = data.id
|
250
274
|
}
|
251
275
|
this.paths = []
|
252
|
-
for (const key in this.
|
253
|
-
const subQuery = this.
|
276
|
+
for (const key in this.queryAttributes) {
|
277
|
+
const subQuery = this.queryAttributes[key]
|
254
278
|
const aliasName = subQuery.as || key
|
255
279
|
const subData = data[aliasName]
|
256
280
|
const child = this.children[aliasName]
|
@@ -291,9 +315,9 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
291
315
|
}
|
292
316
|
}
|
293
317
|
}
|
294
|
-
if (this.
|
318
|
+
if (this.queryAttributes['*']) {
|
295
319
|
for (const key in data) {
|
296
|
-
if (!this.
|
320
|
+
if (!this.queryAttributes[key] && this.data[key] !== data[key]) {
|
297
321
|
this.mark()
|
298
322
|
this.data[key] = data[key]
|
299
323
|
}
|
@@ -302,8 +326,8 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
302
326
|
this.subscribeAll()
|
303
327
|
}
|
304
328
|
onNotify(notifyData: NotifyData, path?: string) {
|
305
|
-
const { action, class_name, id } = notifyData
|
306
|
-
const query = path && this.
|
329
|
+
const { action, class_name: className, id } = notifyData
|
330
|
+
const query = path && this.queryAttributes[path]
|
307
331
|
const aliasName = (query && query.as) || path;
|
308
332
|
if (action === 'remove') {
|
309
333
|
const child = this.children[aliasName]
|
@@ -314,7 +338,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
314
338
|
this.onChange([aliasName], null)
|
315
339
|
} else if (action === 'add') {
|
316
340
|
if (this.data[aliasName] && this.data[aliasName].id === id) return
|
317
|
-
|
341
|
+
modelBatchRequest.fetch(className, ArSyncRecord.compactQuery(query), id).then(data => {
|
318
342
|
if (!data || !this.data) return
|
319
343
|
const model = new ArSyncRecord(query, data, null, this.root)
|
320
344
|
const child = this.children[aliasName]
|
@@ -325,12 +349,17 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
325
349
|
model.parentModel = this
|
326
350
|
model.parentKey = aliasName
|
327
351
|
this.onChange([aliasName], model.data)
|
352
|
+
}).catch(e => {
|
353
|
+
console.error(`failed to load ${className}:${id} ${e}`)
|
328
354
|
})
|
329
355
|
} else {
|
330
356
|
const { field } = notifyData
|
331
357
|
const query = field ? this.patchQuery(field) : this.reloadQuery()
|
332
|
-
if (query)
|
358
|
+
if (!query) return
|
359
|
+
modelBatchRequest.fetch(className, query, id).then(data => {
|
333
360
|
if (this.data) this.update(data)
|
361
|
+
}).catch(e => {
|
362
|
+
console.error(`failed to load patch ${className}:${id} ${e}`)
|
334
363
|
})
|
335
364
|
}
|
336
365
|
}
|
@@ -345,7 +374,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
345
374
|
}
|
346
375
|
}
|
347
376
|
patchQuery(key: string) {
|
348
|
-
const val = this.
|
377
|
+
const val = this.queryAttributes[key]
|
349
378
|
if (!val) return
|
350
379
|
let { attributes, as, params } = val
|
351
380
|
if (attributes && Object.keys(val.attributes).length === 0) attributes = null
|
@@ -359,9 +388,9 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
359
388
|
reloadQuery() {
|
360
389
|
if (this.reloadQueryCache) return this.reloadQueryCache
|
361
390
|
const reloadQuery = this.reloadQueryCache = { attributes: [] as any[] }
|
362
|
-
for (const key in this.
|
391
|
+
for (const key in this.queryAttributes) {
|
363
392
|
if (key === 'sync_keys') continue
|
364
|
-
const val = this.
|
393
|
+
const val = this.queryAttributes[key]
|
365
394
|
if (!val || !val.attributes) {
|
366
395
|
reloadQuery.attributes.push(key)
|
367
396
|
} else if (!val.params && Object.keys(val.attributes).length === 0) {
|
@@ -372,7 +401,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
372
401
|
}
|
373
402
|
update(data) {
|
374
403
|
for (const key in data) {
|
375
|
-
const subQuery = this.
|
404
|
+
const subQuery = this.queryAttributes[key]
|
376
405
|
if (subQuery && subQuery.attributes && Object.keys(subQuery.attributes).length > 0) continue
|
377
406
|
if (this.data[key] === data[key]) continue
|
378
407
|
this.mark()
|
@@ -392,11 +421,13 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
392
421
|
}
|
393
422
|
}
|
394
423
|
|
424
|
+
type Ordering = { first?: number; last?: number; orderBy: string; direction: 'asc' | 'desc' }
|
395
425
|
class ArSyncCollection extends ArSyncContainerBase {
|
396
426
|
root
|
397
427
|
path: string
|
398
|
-
|
428
|
+
ordering: Ordering = { orderBy: 'id', direction: 'asc' }
|
399
429
|
query
|
430
|
+
queryAttributes
|
400
431
|
compactQuery
|
401
432
|
data: any[]
|
402
433
|
children: ArSyncRecord[]
|
@@ -406,31 +437,28 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
406
437
|
this.root = root
|
407
438
|
this.path = path
|
408
439
|
this.query = query
|
440
|
+
this.queryAttributes = query.attributes || {}
|
409
441
|
this.compactQuery = ArSyncRecord.compactQuery(query)
|
410
442
|
if (request) this.initForReload(request)
|
411
|
-
if (query.params
|
412
|
-
this.setOrdering(query.params
|
443
|
+
if (query.params) {
|
444
|
+
this.setOrdering(query.params)
|
413
445
|
}
|
414
446
|
this.data = []
|
415
447
|
this.children = []
|
416
448
|
this.replaceData(data, sync_keys)
|
417
449
|
}
|
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 }
|
450
|
+
setOrdering(ordering: { first?: unknown; last?: unknown; orderBy?: unknown; direction?: unknown }) {
|
451
|
+
let direction: 'asc' | 'desc' = 'asc'
|
452
|
+
let orderBy: string = 'id'
|
453
|
+
let first: number | undefined = undefined
|
454
|
+
let last: number | undefined = undefined
|
455
|
+
if (ordering.direction === 'desc') direction = ordering.direction
|
456
|
+
if (typeof ordering.orderBy === 'string') orderBy = ordering.orderBy
|
457
|
+
if (typeof ordering.first === 'number') first = ordering.first
|
458
|
+
if (typeof ordering.last === 'number') last = ordering.last
|
459
|
+
const subQuery = this.queryAttributes[orderBy]
|
460
|
+
this.aliasOrderKey = (subQuery && subQuery.as) || orderBy
|
461
|
+
this.ordering = { first, last, direction, orderBy }
|
434
462
|
}
|
435
463
|
setSyncKeys(sync_keys: string[]) {
|
436
464
|
if (sync_keys) {
|
@@ -439,26 +467,26 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
439
467
|
this.sync_keys = []
|
440
468
|
}
|
441
469
|
}
|
442
|
-
replaceData(data: any[] | { collection: any[];
|
470
|
+
replaceData(data: any[] | { collection: any[]; ordering: Ordering }, sync_keys: string[]) {
|
443
471
|
this.setSyncKeys(sync_keys)
|
444
|
-
const existings
|
445
|
-
for (const child of this.children) existings
|
472
|
+
const existings = new Map<number, ArSyncRecord>()
|
473
|
+
for (const child of this.children) existings.set(child.data.id, child)
|
446
474
|
let collection: any[]
|
447
|
-
if (
|
448
|
-
collection = data.collection
|
449
|
-
this.setOrdering(data.order.limit, data.order.mode)
|
450
|
-
} else {
|
475
|
+
if (Array.isArray(data)) {
|
451
476
|
collection = data
|
477
|
+
} else {
|
478
|
+
collection = data.collection
|
479
|
+
this.setOrdering(data.ordering)
|
452
480
|
}
|
453
481
|
const newChildren: any[] = []
|
454
482
|
const newData: any[] = []
|
455
483
|
for (const subData of collection) {
|
456
|
-
let model: ArSyncRecord |
|
457
|
-
if (typeof(subData) === 'object' && subData && '
|
484
|
+
let model: ArSyncRecord | undefined = undefined
|
485
|
+
if (typeof(subData) === 'object' && subData && 'sync_keys' in subData) model = existings.get(subData.id)
|
458
486
|
let data = subData
|
459
487
|
if (model) {
|
460
488
|
model.replaceData(subData)
|
461
|
-
} else if (subData.
|
489
|
+
} else if (subData.sync_keys) {
|
462
490
|
model = new ArSyncRecord(this.query, subData, null, this.root)
|
463
491
|
model.parentModel = this
|
464
492
|
model.parentKey = subData.id
|
@@ -471,7 +499,7 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
471
499
|
}
|
472
500
|
while (this.children.length) {
|
473
501
|
const child = this.children.pop()!
|
474
|
-
if (!existings
|
502
|
+
if (!existings.has(child.data.id)) child.release()
|
475
503
|
}
|
476
504
|
if (this.data.length || newChildren.length) this.mark()
|
477
505
|
while (this.data.length) this.data.pop()
|
@@ -480,54 +508,78 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
480
508
|
this.subscribeAll()
|
481
509
|
}
|
482
510
|
consumeAdd(className: string, id: number) {
|
511
|
+
const { first, last, direction } = this.ordering
|
512
|
+
const limit = first || last
|
483
513
|
if (this.data.findIndex(a => a.id === id) >= 0) return
|
484
|
-
if (
|
485
|
-
|
486
|
-
|
487
|
-
|
514
|
+
if (limit && limit <= this.data.length) {
|
515
|
+
const lastItem = this.data[this.data.length - 1]
|
516
|
+
const firstItem = this.data[0]
|
517
|
+
if (direction === 'asc') {
|
518
|
+
if (first) {
|
519
|
+
if (lastItem && lastItem.id < id) return
|
520
|
+
} else {
|
521
|
+
if (firstItem && id < firstItem.id) return
|
522
|
+
}
|
488
523
|
} else {
|
489
|
-
|
490
|
-
|
524
|
+
if (first) {
|
525
|
+
if (lastItem && id < lastItem.id) return
|
526
|
+
} else {
|
527
|
+
if (firstItem && firstItem.id < id) return
|
528
|
+
}
|
491
529
|
}
|
492
530
|
}
|
493
|
-
|
531
|
+
modelBatchRequest.fetch(className, this.compactQuery, id).then((data: any) => {
|
494
532
|
if (!data || !this.data) return
|
495
533
|
const model = new ArSyncRecord(this.query, data, null, this.root)
|
496
534
|
model.parentModel = this
|
497
535
|
model.parentKey = id
|
498
|
-
const overflow =
|
536
|
+
const overflow = limit && limit <= this.data.length
|
499
537
|
let rmodel: ArSyncRecord | undefined
|
500
538
|
this.mark()
|
501
539
|
const orderKey = this.aliasOrderKey
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
this.
|
540
|
+
const firstItem = this.data[0]
|
541
|
+
const lastItem = this.data[this.data.length - 1]
|
542
|
+
if (direction === 'asc') {
|
543
|
+
if (firstItem && data[orderKey] < firstItem[orderKey]) {
|
544
|
+
this.children.unshift(model)
|
545
|
+
this.data.unshift(model.data)
|
546
|
+
} else {
|
547
|
+
const skipSort = lastItem && lastItem[orderKey] < data[orderKey]
|
548
|
+
this.children.push(model)
|
549
|
+
this.data.push(model.data)
|
550
|
+
if (!skipSort) this.markAndSort()
|
511
551
|
}
|
512
552
|
} else {
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
553
|
+
if (firstItem && data[orderKey] > firstItem[orderKey]) {
|
554
|
+
this.children.unshift(model)
|
555
|
+
this.data.unshift(model.data)
|
556
|
+
} else {
|
557
|
+
const skipSort = lastItem && lastItem[orderKey] > data[orderKey]
|
558
|
+
this.children.push(model)
|
559
|
+
this.data.push(model.data)
|
560
|
+
if (!skipSort) this.markAndSort()
|
561
|
+
}
|
562
|
+
}
|
563
|
+
if (overflow) {
|
564
|
+
if (first) {
|
518
565
|
rmodel = this.children.pop()!
|
519
|
-
rmodel.release()
|
520
566
|
this.data.pop()
|
567
|
+
} else {
|
568
|
+
rmodel = this.children.shift()!
|
569
|
+
this.data.shift()
|
521
570
|
}
|
571
|
+
rmodel.release()
|
522
572
|
}
|
523
573
|
this.onChange([model.id], model.data)
|
524
574
|
if (rmodel) this.onChange([rmodel.id], null)
|
575
|
+
}).catch(e => {
|
576
|
+
console.error(`failed to load ${className}:${id} ${e}`)
|
525
577
|
})
|
526
578
|
}
|
527
579
|
markAndSort() {
|
528
580
|
this.mark()
|
529
581
|
const orderKey = this.aliasOrderKey
|
530
|
-
if (this.
|
582
|
+
if (this.ordering.direction === 'asc') {
|
531
583
|
this.children.sort((a, b) => a.data[orderKey] < b.data[orderKey] ? -1 : +1)
|
532
584
|
this.data.sort((a, b) => a[orderKey] < b[orderKey] ? -1 : +1)
|
533
585
|
} else {
|
data/src/core/DataType.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
type RecordType = { _meta?: { query: any } }
|
2
|
-
type Values<T> = T
|
2
|
+
type Values<T> = T[keyof T]
|
3
3
|
type AddNullable<Test, Type> = null extends Test ? Type | null : Type
|
4
4
|
type DataTypeExtractField<BaseType, Key extends keyof BaseType> = Exclude<BaseType[Key], null> extends RecordType
|
5
5
|
? AddNullable<BaseType[Key], {}>
|
@@ -51,27 +51,26 @@ type CheckAttributesField<P, Q> = Q extends { attributes: infer R }
|
|
51
51
|
|
52
52
|
type IsAnyCompareLeftType = { __any: never }
|
53
53
|
|
54
|
-
type CollectExtraFields<Type,
|
54
|
+
type CollectExtraFields<Type, Key> =
|
55
55
|
IsAnyCompareLeftType extends Type
|
56
|
-
?
|
56
|
+
? never
|
57
57
|
: Type extends ExtraFieldErrorType
|
58
|
-
?
|
58
|
+
? Key
|
59
59
|
: Type extends (infer R)[]
|
60
|
-
?
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
:
|
67
|
-
|
68
|
-
: null
|
60
|
+
? {
|
61
|
+
0: Values<{ [key in keyof R]: CollectExtraFields<R[key], key> }>
|
62
|
+
1: never
|
63
|
+
}[R extends object ? 0 : 1]
|
64
|
+
: {
|
65
|
+
0: Values<{ [key in keyof Type]: CollectExtraFields<Type[key], key> }>
|
66
|
+
1: never
|
67
|
+
}[Type extends object ? 0 : 1]
|
69
68
|
|
70
69
|
type SelectString<T> = T extends string ? T : never
|
71
70
|
type _ValidateDataTypeExtraFileds<Extra, Type> = SelectString<Extra> extends never
|
72
71
|
? Type
|
73
|
-
: { error: { extraFields:
|
74
|
-
type ValidateDataTypeExtraFileds<Type> = _ValidateDataTypeExtraFileds<CollectExtraFields<Type,
|
72
|
+
: { error: { extraFields: Extra } }
|
73
|
+
type ValidateDataTypeExtraFileds<Type> = _ValidateDataTypeExtraFileds<CollectExtraFields<Type, never>, Type>
|
75
74
|
|
76
75
|
type RequestBase = { api: string; query: any; id?: number; params?: any; _meta?: { data: any } }
|
77
76
|
type DataTypeBaseFromRequestType<R extends RequestBase, ID> = R extends { _meta?: { data: infer DataType } }
|
@@ -83,4 +82,4 @@ type DataTypeBaseFromRequestType<R extends RequestBase, ID> = R extends { _meta?
|
|
83
82
|
: never
|
84
83
|
export type DataTypeFromRequest<Req extends RequestBase, R extends RequestBase> = ValidateDataTypeExtraFileds<
|
85
84
|
DataTypeFromQuery<DataTypeBaseFromRequestType<Req, R['id']>, R['query']>
|
86
|
-
>
|
85
|
+
>
|