ar_sync 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +4 -3
- data/.gitignore +2 -0
- data/Gemfile +1 -0
- data/ar_sync.gemspec +1 -1
- data/bin/console +2 -2
- data/core/ArSyncModel.d.ts +10 -12
- data/core/ArSyncModel.js +7 -5
- data/core/ArSyncStore.d.ts +11 -3
- data/core/ArSyncStore.js +112 -112
- data/core/hooks.d.ts +1 -0
- data/core/hooks.js +5 -4
- data/gemfiles/Gemfile-rails-6 +4 -3
- data/gemfiles/{Gemfile-rails-7 → Gemfile-rails-7-0} +4 -3
- data/gemfiles/Gemfile-rails-7-1 +10 -0
- data/lib/ar_sync/class_methods.rb +1 -5
- data/lib/ar_sync/core.rb +2 -6
- data/lib/ar_sync/instance_methods.rb +16 -1
- data/lib/ar_sync/rails.rb +14 -17
- data/lib/ar_sync/version.rb +1 -1
- data/src/core/ArSyncModel.ts +9 -8
- data/src/core/ArSyncStore.ts +128 -121
- data/src/core/hooks.ts +6 -5
- metadata +5 -19
- data/Gemfile.lock +0 -54
data/src/core/ArSyncStore.ts
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
import ArSyncApi from './ArSyncApi'
|
2
|
+
export type Request = { api: string; query: any; params?: any; id?: IDType }
|
3
|
+
|
4
|
+
type IDType = number | string
|
2
5
|
|
3
6
|
class ModelBatchRequest {
|
4
7
|
timer: number | null = null
|
@@ -6,8 +9,8 @@ class ModelBatchRequest {
|
|
6
9
|
Map<string,
|
7
10
|
{
|
8
11
|
query,
|
9
|
-
requests: Map<
|
10
|
-
id:
|
12
|
+
requests: Map<IDType, {
|
13
|
+
id: IDType
|
11
14
|
model?
|
12
15
|
callbacks: {
|
13
16
|
resolve: (model: any) => void
|
@@ -17,7 +20,7 @@ class ModelBatchRequest {
|
|
17
20
|
}
|
18
21
|
>
|
19
22
|
>()
|
20
|
-
fetch(api: string, query, id:
|
23
|
+
fetch(api: string, query, id: IDType) {
|
21
24
|
this.setTimer()
|
22
25
|
return new Promise((resolve, reject) => {
|
23
26
|
const queryJSON = JSON.stringify(query)
|
@@ -36,7 +39,7 @@ class ModelBatchRequest {
|
|
36
39
|
const ids = Array.from(requests.keys())
|
37
40
|
ArSyncApi.syncFetch({ api, query, params: { ids } }).then((models: any[]) => {
|
38
41
|
for (const model of models) {
|
39
|
-
const req = requests.get(model.id)
|
42
|
+
const req = requests.get(model._sync.id)
|
40
43
|
if (req) req.model = model
|
41
44
|
}
|
42
45
|
requests.forEach(({ model, callbacks }) => {
|
@@ -67,18 +70,18 @@ type ParsedQuery = {
|
|
67
70
|
params: any
|
68
71
|
} | {}
|
69
72
|
|
73
|
+
type SyncField = { id: IDType; keys: string[] }
|
70
74
|
type Unsubscribable = { unsubscribe: () => void }
|
71
75
|
|
72
76
|
class ArSyncContainerBase {
|
73
77
|
data
|
74
78
|
listeners: Unsubscribable[] = []
|
75
79
|
networkSubscriber?: Unsubscribable
|
76
|
-
parentModel
|
80
|
+
parentModel: ArSyncRecord | ArSyncCollection | null = null
|
77
81
|
parentKey
|
78
82
|
children: ArSyncContainerBase[] | { [key: string]: ArSyncContainerBase | null }
|
79
|
-
sync_keys: string[]
|
80
83
|
onConnectionChange?: (status: boolean) => void
|
81
|
-
replaceData(_data,
|
84
|
+
replaceData(_data, _parentSyncKeys?) {}
|
82
85
|
initForReload(request) {
|
83
86
|
this.networkSubscriber = ArSyncStore.connectionManager.subscribeNetwork((state) => {
|
84
87
|
if (!state) {
|
@@ -124,7 +127,7 @@ class ArSyncContainerBase {
|
|
124
127
|
for (const l of this.listeners) l.unsubscribe()
|
125
128
|
this.listeners = []
|
126
129
|
}
|
127
|
-
static
|
130
|
+
static compactQueryAttributes(query: ParsedQuery) {
|
128
131
|
function compactAttributes(attributes: Record<string, ParsedQuery>): [ParsedQuery, boolean] {
|
129
132
|
const attrs = {}
|
130
133
|
const keys: string[] = []
|
@@ -160,6 +163,7 @@ class ArSyncContainerBase {
|
|
160
163
|
return result
|
161
164
|
}
|
162
165
|
const result = compactQuery(query)
|
166
|
+
if (typeof result === 'object' && 'attributes' in result) return result.attributes
|
163
167
|
return result === true ? {} : result
|
164
168
|
}
|
165
169
|
static parseQuery(query, attrsonly: true): Record<string, ParsedQuery>
|
@@ -196,63 +200,52 @@ class ArSyncContainerBase {
|
|
196
200
|
if (attrsonly) return attributes
|
197
201
|
return { attributes, as: column, params }
|
198
202
|
}
|
199
|
-
static
|
203
|
+
static load({ api, id, params, query }: Request, root: ArSyncStore) {
|
200
204
|
const parsedQuery = ArSyncRecord.parseQuery(query)
|
201
|
-
const
|
205
|
+
const compactQueryAttributes = ArSyncRecord.compactQueryAttributes(parsedQuery)
|
202
206
|
if (id != null) {
|
203
|
-
return modelBatchRequest.fetch(api,
|
207
|
+
return modelBatchRequest.fetch(api, compactQueryAttributes, id).then(data => {
|
204
208
|
if (!data) throw { retry: false }
|
205
|
-
const request = { api, id, query:
|
206
|
-
return new ArSyncRecord(parsedQuery, data, request, root)
|
209
|
+
const request = { api, id, query: compactQueryAttributes }
|
210
|
+
return new ArSyncRecord(parsedQuery, data, request, root, null, null)
|
207
211
|
})
|
208
212
|
} else {
|
209
|
-
const request = { api, query:
|
213
|
+
const request = { api, query: compactQueryAttributes, params }
|
210
214
|
return ArSyncApi.syncFetch(request).then((response: any) => {
|
211
215
|
if (!response) {
|
212
216
|
throw { retry: false }
|
213
|
-
} else if (response.collection && response.order) {
|
214
|
-
return new ArSyncCollection(response.
|
217
|
+
} else if (response.collection && response.order && response._sync) {
|
218
|
+
return new ArSyncCollection(response._sync.keys, 'collection', parsedQuery, response, request, root, null, null)
|
215
219
|
} else if (response instanceof Array) {
|
216
|
-
return new ArSyncCollection([], '', parsedQuery, response, request, root)
|
220
|
+
return new ArSyncCollection([], '', parsedQuery, response, request, root, null, null)
|
217
221
|
} else {
|
218
|
-
return new ArSyncRecord(parsedQuery, response, request, root)
|
222
|
+
return new ArSyncRecord(parsedQuery, response, request, root, null, null)
|
219
223
|
}
|
220
224
|
})
|
221
225
|
}
|
222
226
|
}
|
223
|
-
static load(apiParams, root) {
|
224
|
-
if (!(apiParams instanceof Array)) return this._load(apiParams, root)
|
225
|
-
return new Promise((resolve, _reject) => {
|
226
|
-
const resultModels: any[] = []
|
227
|
-
let countdown = apiParams.length
|
228
|
-
apiParams.forEach((param, i) => {
|
229
|
-
this._load(param, root).then(model => {
|
230
|
-
resultModels[i] = model
|
231
|
-
countdown --
|
232
|
-
if (countdown === 0) resolve(resultModels)
|
233
|
-
})
|
234
|
-
})
|
235
|
-
})
|
236
|
-
}
|
237
227
|
}
|
238
228
|
|
239
229
|
type NotifyData = {
|
240
230
|
action: 'add' | 'remove' | 'update'
|
241
|
-
|
242
|
-
id:
|
231
|
+
class: string
|
232
|
+
id: IDType
|
243
233
|
field?: string
|
244
234
|
}
|
245
235
|
|
246
236
|
class ArSyncRecord extends ArSyncContainerBase {
|
247
|
-
id:
|
248
|
-
root
|
237
|
+
id: IDType
|
238
|
+
root: ArSyncStore
|
249
239
|
query
|
250
240
|
queryAttributes
|
251
241
|
data
|
242
|
+
syncKeys: string[]
|
252
243
|
children: { [key: string]: ArSyncContainerBase | null }
|
253
244
|
paths: string[]
|
254
245
|
reloadQueryCache
|
255
|
-
|
246
|
+
rootRecord: boolean
|
247
|
+
fetching = new Set<string>()
|
248
|
+
constructor(query, data, request, root: ArSyncStore, parentModel: ArSyncRecord | ArSyncCollection | null, parentKey: string | number | null) {
|
256
249
|
super()
|
257
250
|
this.root = root
|
258
251
|
if (request) this.initForReload(request)
|
@@ -260,48 +253,43 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
260
253
|
this.queryAttributes = query.attributes || {}
|
261
254
|
this.data = {}
|
262
255
|
this.children = {}
|
256
|
+
this.rootRecord = !parentModel
|
257
|
+
this.id = data._sync.id
|
258
|
+
this.syncKeys = data._sync.keys
|
263
259
|
this.replaceData(data)
|
260
|
+
this.parentModel = parentModel
|
261
|
+
this.parentKey = parentKey
|
264
262
|
}
|
265
|
-
|
266
|
-
this.
|
267
|
-
|
268
|
-
replaceData(data) {
|
269
|
-
this.setSyncKeys(data.sync_keys)
|
263
|
+
replaceData(data: { _sync: SyncField }) {
|
264
|
+
this.id = data._sync.id
|
265
|
+
this.syncKeys = data._sync.keys
|
270
266
|
this.unsubscribeAll()
|
271
|
-
if (this.data.id !== data.id) {
|
272
|
-
this.mark()
|
273
|
-
this.data.id = data.id
|
274
|
-
}
|
275
267
|
this.paths = []
|
276
268
|
for (const key in this.queryAttributes) {
|
277
269
|
const subQuery = this.queryAttributes[key]
|
278
270
|
const aliasName = subQuery.as || key
|
279
271
|
const subData = data[aliasName]
|
280
272
|
const child = this.children[aliasName]
|
281
|
-
if (key === '
|
282
|
-
if (subData instanceof Array || (subData && subData.collection && subData.order)) {
|
273
|
+
if (key === '_sync') continue
|
274
|
+
if (subData instanceof Array || (subData && subData.collection && subData.order && subData._sync)) {
|
283
275
|
if (child) {
|
284
|
-
child.replaceData(subData, this.
|
276
|
+
child.replaceData(subData, this.syncKeys)
|
285
277
|
} else {
|
286
|
-
const collection = new ArSyncCollection(this.
|
278
|
+
const collection = new ArSyncCollection(this.syncKeys, key, subQuery, subData, null, this.root, this, aliasName)
|
287
279
|
this.mark()
|
288
280
|
this.children[aliasName] = collection
|
289
281
|
this.data[aliasName] = collection.data
|
290
|
-
collection.parentModel = this
|
291
|
-
collection.parentKey = aliasName
|
292
282
|
}
|
293
283
|
} else {
|
294
284
|
if (subQuery.attributes && Object.keys(subQuery.attributes).length > 0) this.paths.push(key)
|
295
|
-
if (subData && subData.
|
285
|
+
if (subData && subData._sync) {
|
296
286
|
if (child) {
|
297
287
|
child.replaceData(subData)
|
298
288
|
} else {
|
299
|
-
const model = new ArSyncRecord(subQuery, subData, null, this.root)
|
289
|
+
const model = new ArSyncRecord(subQuery, subData, null, this.root, this, aliasName)
|
300
290
|
this.mark()
|
301
291
|
this.children[aliasName] = model
|
302
292
|
this.data[aliasName] = model.data
|
303
|
-
model.parentModel = this
|
304
|
-
model.parentKey = aliasName
|
305
293
|
}
|
306
294
|
} else {
|
307
295
|
if(child) {
|
@@ -317,6 +305,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
317
305
|
}
|
318
306
|
if (this.queryAttributes['*']) {
|
319
307
|
for (const key in data) {
|
308
|
+
if (key === '_sync') continue
|
320
309
|
if (!this.queryAttributes[key] && this.data[key] !== data[key]) {
|
321
310
|
this.mark()
|
322
311
|
this.data[key] = data[key]
|
@@ -326,28 +315,34 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
326
315
|
this.subscribeAll()
|
327
316
|
}
|
328
317
|
onNotify(notifyData: NotifyData, path?: string) {
|
329
|
-
const { action,
|
318
|
+
const { action, class: className, id } = notifyData
|
330
319
|
const query = path && this.queryAttributes[path]
|
331
320
|
const aliasName = (query && query.as) || path;
|
332
321
|
if (action === 'remove') {
|
333
322
|
const child = this.children[aliasName]
|
323
|
+
this.fetching.delete(`${aliasName}:${id}`) // To cancel consumeAdd
|
334
324
|
if (child) child.release()
|
335
325
|
this.children[aliasName] = null
|
336
326
|
this.mark()
|
337
327
|
this.data[aliasName] = null
|
338
328
|
this.onChange([aliasName], null)
|
339
329
|
} else if (action === 'add') {
|
340
|
-
|
341
|
-
|
330
|
+
const child = this.children[aliasName]
|
331
|
+
if (child instanceof ArSyncRecord && child.id === id) return
|
332
|
+
const fetchKey = `${aliasName}:${id}`
|
333
|
+
this.fetching.add(fetchKey)
|
334
|
+
modelBatchRequest.fetch(className, ArSyncRecord.compactQueryAttributes(query), id).then(data => {
|
335
|
+
// Record already removed
|
336
|
+
if (!this.fetching.has(fetchKey)) return
|
337
|
+
|
338
|
+
this.fetching.delete(fetchKey)
|
342
339
|
if (!data || !this.data) return
|
343
|
-
const model = new ArSyncRecord(query, data, null, this.root)
|
340
|
+
const model = new ArSyncRecord(query, data, null, this.root, this, aliasName)
|
344
341
|
const child = this.children[aliasName]
|
345
342
|
if (child) child.release()
|
346
343
|
this.children[aliasName] = model
|
347
344
|
this.mark()
|
348
345
|
this.data[aliasName] = model.data
|
349
|
-
model.parentModel = this
|
350
|
-
model.parentKey = aliasName
|
351
346
|
this.onChange([aliasName], model.data)
|
352
347
|
}).catch(e => {
|
353
348
|
console.error(`failed to load ${className}:${id} ${e}`)
|
@@ -365,42 +360,42 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
365
360
|
}
|
366
361
|
subscribeAll() {
|
367
362
|
const callback = data => this.onNotify(data)
|
368
|
-
for (const key of this.
|
363
|
+
for (const key of this.syncKeys) {
|
369
364
|
this.subscribe(key, callback)
|
370
365
|
}
|
371
366
|
for (const path of this.paths) {
|
372
367
|
const pathCallback = data => this.onNotify(data, path)
|
373
|
-
for (const key of this.
|
368
|
+
for (const key of this.syncKeys) this.subscribe(key + path, pathCallback)
|
369
|
+
}
|
370
|
+
if (this.rootRecord) {
|
371
|
+
const key = this.syncKeys[0]
|
372
|
+
if (key) this.subscribe(key + '_destroy', () => this.root.handleDestroy())
|
374
373
|
}
|
375
374
|
}
|
376
375
|
patchQuery(key: string) {
|
377
|
-
const
|
378
|
-
if (
|
379
|
-
let { attributes, as, params } = val
|
380
|
-
if (attributes && Object.keys(val.attributes).length === 0) attributes = null
|
381
|
-
if (!attributes && !as && !params) return key
|
382
|
-
const result: { attributes?; as?; params? } = {}
|
383
|
-
if (attributes) result.attributes = attributes
|
384
|
-
if (as) result.as = as
|
385
|
-
if (params) result.params = params
|
386
|
-
return result
|
376
|
+
const subQuery = this.queryAttributes[key]
|
377
|
+
if (subQuery) return { [key]: subQuery }
|
387
378
|
}
|
388
379
|
reloadQuery() {
|
389
380
|
if (this.reloadQueryCache) return this.reloadQueryCache
|
390
|
-
|
381
|
+
let arrayQuery = [] as string[] | null
|
382
|
+
const hashQuery = {}
|
391
383
|
for (const key in this.queryAttributes) {
|
392
|
-
if (key === '
|
384
|
+
if (key === '_sync') continue
|
393
385
|
const val = this.queryAttributes[key]
|
394
386
|
if (!val || !val.attributes) {
|
395
|
-
|
387
|
+
arrayQuery?.push(key)
|
388
|
+
hashQuery[key] = true
|
396
389
|
} else if (!val.params && Object.keys(val.attributes).length === 0) {
|
397
|
-
|
390
|
+
arrayQuery = null
|
391
|
+
hashQuery[key] = val
|
398
392
|
}
|
399
393
|
}
|
400
|
-
return
|
394
|
+
return this.reloadQueryCache = arrayQuery || hashQuery
|
401
395
|
}
|
402
396
|
update(data) {
|
403
397
|
for (const key in data) {
|
398
|
+
if (key === '_sync') continue
|
404
399
|
const subQuery = this.queryAttributes[key]
|
405
400
|
if (subQuery && subQuery.attributes && Object.keys(subQuery.attributes).length > 0) continue
|
406
401
|
if (this.data[key] === data[key]) continue
|
@@ -417,35 +412,39 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
417
412
|
if (!this.root || !this.root.immutable || !Object.isFrozen(this.data)) return
|
418
413
|
this.data = { ...this.data }
|
419
414
|
this.root.mark(this.data)
|
420
|
-
if (this.parentModel) this.parentModel.markAndSet(this.parentKey, this.data)
|
415
|
+
if (this.parentModel && this.parentKey) (this.parentModel as { markAndSet(key: string | number, data: any): any }).markAndSet(this.parentKey, this.data)
|
421
416
|
}
|
422
417
|
}
|
423
418
|
|
424
419
|
type Ordering = { first?: number; last?: number; orderBy: string; direction: 'asc' | 'desc' }
|
425
420
|
class ArSyncCollection extends ArSyncContainerBase {
|
426
|
-
root
|
421
|
+
root: ArSyncStore
|
427
422
|
path: string
|
428
423
|
ordering: Ordering = { orderBy: 'id', direction: 'asc' }
|
429
424
|
query
|
430
425
|
queryAttributes
|
431
|
-
|
426
|
+
compactQueryAttributes
|
427
|
+
syncKeys: string[]
|
432
428
|
data: any[]
|
433
429
|
children: ArSyncRecord[]
|
434
430
|
aliasOrderKey = 'id'
|
435
|
-
|
431
|
+
fetching = new Set<IDType>()
|
432
|
+
constructor(parentSyncKeys: string[], path: string, query, data: any[], request, root: ArSyncStore, parentModel: ArSyncRecord | null, parentKey: string | null){
|
436
433
|
super()
|
437
434
|
this.root = root
|
438
435
|
this.path = path
|
439
436
|
this.query = query
|
440
437
|
this.queryAttributes = query.attributes || {}
|
441
|
-
this.
|
438
|
+
this.compactQueryAttributes = ArSyncRecord.compactQueryAttributes(query)
|
442
439
|
if (request) this.initForReload(request)
|
443
440
|
if (query.params) {
|
444
441
|
this.setOrdering(query.params)
|
445
442
|
}
|
446
443
|
this.data = []
|
447
444
|
this.children = []
|
448
|
-
this.replaceData(data,
|
445
|
+
this.replaceData(data, parentSyncKeys)
|
446
|
+
this.parentModel = parentModel
|
447
|
+
this.parentKey = parentKey
|
449
448
|
}
|
450
449
|
setOrdering(ordering: { first?: unknown; last?: unknown; orderBy?: unknown; direction?: unknown }) {
|
451
450
|
let direction: 'asc' | 'desc' = 'asc'
|
@@ -460,17 +459,17 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
460
459
|
this.aliasOrderKey = (subQuery && subQuery.as) || orderBy
|
461
460
|
this.ordering = { first, last, direction, orderBy }
|
462
461
|
}
|
463
|
-
setSyncKeys(
|
464
|
-
if (
|
465
|
-
this.
|
462
|
+
setSyncKeys(parentSyncKeys: string[] | undefined) {
|
463
|
+
if (parentSyncKeys) {
|
464
|
+
this.syncKeys = parentSyncKeys.map(key => key + this.path)
|
466
465
|
} else {
|
467
|
-
this.
|
466
|
+
this.syncKeys = []
|
468
467
|
}
|
469
468
|
}
|
470
|
-
replaceData(data: any[] | { collection: any[]; ordering: Ordering },
|
471
|
-
this.setSyncKeys(
|
472
|
-
const existings = new Map<
|
473
|
-
for (const child of this.children) existings.set(child.
|
469
|
+
replaceData(data: any[] | { collection: any[]; ordering: Ordering }, parentSyncKeys: string[]) {
|
470
|
+
this.setSyncKeys(parentSyncKeys)
|
471
|
+
const existings = new Map<IDType, ArSyncRecord>()
|
472
|
+
for (const child of this.children) existings.set(child.id, child)
|
474
473
|
let collection: any[]
|
475
474
|
if (Array.isArray(data)) {
|
476
475
|
collection = data
|
@@ -482,14 +481,12 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
482
481
|
const newData: any[] = []
|
483
482
|
for (const subData of collection) {
|
484
483
|
let model: ArSyncRecord | undefined = undefined
|
485
|
-
if (typeof(subData) === 'object' && subData && '
|
484
|
+
if (typeof(subData) === 'object' && subData && '_sync' in subData) model = existings.get(subData._sync.id)
|
486
485
|
let data = subData
|
487
486
|
if (model) {
|
488
487
|
model.replaceData(subData)
|
489
|
-
} else if (subData.
|
490
|
-
model = new ArSyncRecord(this.query, subData, null, this.root)
|
491
|
-
model.parentModel = this
|
492
|
-
model.parentKey = subData.id
|
488
|
+
} else if (subData._sync) {
|
489
|
+
model = new ArSyncRecord(this.query, subData, null, this.root, this, subData._sync.id)
|
493
490
|
}
|
494
491
|
if (model) {
|
495
492
|
newChildren.push(model)
|
@@ -499,7 +496,7 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
499
496
|
}
|
500
497
|
while (this.children.length) {
|
501
498
|
const child = this.children.pop()!
|
502
|
-
if (!existings.has(child.
|
499
|
+
if (!existings.has(child.id)) child.release()
|
503
500
|
}
|
504
501
|
if (this.data.length || newChildren.length) this.mark()
|
505
502
|
while (this.data.length) this.data.pop()
|
@@ -507,13 +504,13 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
507
504
|
for (const el of newData) this.data.push(el)
|
508
505
|
this.subscribeAll()
|
509
506
|
}
|
510
|
-
consumeAdd(className: string, id:
|
507
|
+
consumeAdd(className: string, id: IDType) {
|
511
508
|
const { first, last, direction } = this.ordering
|
512
509
|
const limit = first || last
|
513
|
-
if (this.
|
514
|
-
if (limit && limit <= this.
|
515
|
-
const lastItem = this.
|
516
|
-
const firstItem = this.
|
510
|
+
if (this.children.find(a => a.id === id)) return
|
511
|
+
if (limit && limit <= this.children.length) {
|
512
|
+
const lastItem = this.children[this.children.length - 1]
|
513
|
+
const firstItem = this.children[0]
|
517
514
|
if (direction === 'asc') {
|
518
515
|
if (first) {
|
519
516
|
if (lastItem && lastItem.id < id) return
|
@@ -528,11 +525,14 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
528
525
|
}
|
529
526
|
}
|
530
527
|
}
|
531
|
-
|
528
|
+
this.fetching.add(id)
|
529
|
+
modelBatchRequest.fetch(className, this.compactQueryAttributes, id).then((data: any) => {
|
530
|
+
// Record already removed
|
531
|
+
if (!this.fetching.has(id)) return
|
532
|
+
|
533
|
+
this.fetching.delete(id)
|
532
534
|
if (!data || !this.data) return
|
533
|
-
const model = new ArSyncRecord(this.query, data, null, this.root)
|
534
|
-
model.parentModel = this
|
535
|
-
model.parentKey = id
|
535
|
+
const model = new ArSyncRecord(this.query, data, null, this.root, this, id)
|
536
536
|
const overflow = limit && limit <= this.data.length
|
537
537
|
let rmodel: ArSyncRecord | undefined
|
538
538
|
this.mark()
|
@@ -587,8 +587,9 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
587
587
|
this.data.sort((a, b) => a[orderKey] > b[orderKey] ? -1 : +1)
|
588
588
|
}
|
589
589
|
}
|
590
|
-
consumeRemove(id:
|
591
|
-
const idx = this.
|
590
|
+
consumeRemove(id: IDType) {
|
591
|
+
const idx = this.children.findIndex(a => a.id === id)
|
592
|
+
this.fetching.delete(id) // To cancel consumeAdd
|
592
593
|
if (idx < 0) return
|
593
594
|
this.mark()
|
594
595
|
this.children[idx].release()
|
@@ -596,58 +597,64 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
596
597
|
this.data.splice(idx, 1)
|
597
598
|
this.onChange([id], null)
|
598
599
|
}
|
599
|
-
onNotify(notifyData) {
|
600
|
+
onNotify(notifyData: NotifyData) {
|
600
601
|
if (notifyData.action === 'add') {
|
601
|
-
this.consumeAdd(notifyData.
|
602
|
+
this.consumeAdd(notifyData.class, notifyData.id)
|
602
603
|
} else if (notifyData.action === 'remove') {
|
603
604
|
this.consumeRemove(notifyData.id)
|
604
605
|
}
|
605
606
|
}
|
606
607
|
subscribeAll() {
|
607
608
|
const callback = data => this.onNotify(data)
|
608
|
-
for (const key of this.
|
609
|
+
for (const key of this.syncKeys) this.subscribe(key, callback)
|
609
610
|
}
|
610
611
|
onChange(path: (string | number)[], data) {
|
611
612
|
super.onChange(path, data)
|
612
613
|
if (path[1] === this.aliasOrderKey) this.markAndSort()
|
613
614
|
}
|
614
|
-
markAndSet(id:
|
615
|
+
markAndSet(id: IDType, data) {
|
615
616
|
this.mark()
|
616
|
-
const idx = this.
|
617
|
+
const idx = this.children.findIndex(a => a.id === id)
|
617
618
|
if (idx >= 0) this.data[idx] = data
|
618
619
|
}
|
619
620
|
mark() {
|
620
621
|
if (!this.root || !this.root.immutable || !Object.isFrozen(this.data)) return
|
621
622
|
this.data = [...this.data]
|
622
623
|
this.root.mark(this.data)
|
623
|
-
if (this.parentModel) this.parentModel.markAndSet(this.parentKey, this.data)
|
624
|
+
if (this.parentModel && this.parentKey) (this.parentModel as { markAndSet(key: string | number, data: any): any }).markAndSet(this.parentKey, this.data)
|
624
625
|
}
|
625
626
|
}
|
626
627
|
|
627
|
-
export
|
628
|
+
export class ArSyncStore {
|
628
629
|
immutable: boolean
|
629
630
|
markedForFreezeObjects: any[]
|
630
631
|
changes
|
631
632
|
eventListeners
|
632
633
|
markForRelease: true | undefined
|
633
634
|
container
|
634
|
-
request
|
635
|
-
complete
|
635
|
+
request: Request
|
636
|
+
complete = false
|
636
637
|
notfound?: boolean
|
638
|
+
destroyed = false
|
637
639
|
data
|
638
640
|
changesBufferTimer: number | undefined | null
|
639
641
|
retryLoadTimer: number | undefined | null
|
640
642
|
static connectionManager
|
641
|
-
constructor(request, { immutable } = {} as { immutable?: boolean }) {
|
643
|
+
constructor(request: Request, { immutable } = {} as { immutable?: boolean }) {
|
642
644
|
this.immutable = !!immutable
|
643
645
|
this.markedForFreezeObjects = []
|
644
646
|
this.changes = []
|
645
647
|
this.eventListeners = { events: {}, serial: 0 }
|
646
648
|
this.request = request
|
647
|
-
this.complete = false
|
648
649
|
this.data = null
|
649
650
|
this.load(0)
|
650
651
|
}
|
652
|
+
handleDestroy() {
|
653
|
+
this.release()
|
654
|
+
this.data = null
|
655
|
+
this.destroyed = true
|
656
|
+
this.trigger('destroy')
|
657
|
+
}
|
651
658
|
load(retryCount: number) {
|
652
659
|
ArSyncContainerBase.load(this.request, this).then((container: ArSyncContainerBase) => {
|
653
660
|
if (this.markForRelease) {
|
data/src/core/hooks.ts
CHANGED
@@ -21,11 +21,11 @@ function checkHooks() {
|
|
21
21
|
if (!useState) throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo, useRef })`'
|
22
22
|
}
|
23
23
|
|
24
|
-
interface ModelStatus { complete: boolean; notfound?: boolean; connected: boolean }
|
24
|
+
interface ModelStatus { complete: boolean; notfound?: boolean; connected: boolean; destroyed: boolean }
|
25
25
|
export type DataAndStatus<T> = [T | null, ModelStatus]
|
26
26
|
export interface Request { api: string; params?: any; id?: number; query: any }
|
27
27
|
|
28
|
-
const initialResult: DataAndStatus<any> = [null, { complete: false, notfound: undefined, connected: true }]
|
28
|
+
const initialResult: DataAndStatus<any> = [null, { complete: false, notfound: undefined, connected: true, destroyed: false }]
|
29
29
|
export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
30
30
|
checkHooks()
|
31
31
|
const [result, setResult] = useState<DataAndStatus<T>>(initialResult)
|
@@ -39,12 +39,12 @@ export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
|
39
39
|
}
|
40
40
|
const model = new ArSyncModel<T>(request, { immutable: true })
|
41
41
|
function update() {
|
42
|
-
const { complete, notfound, connected, data } = model
|
42
|
+
const { complete, notfound, connected, destroyed, data } = model
|
43
43
|
setResult(resultWas => {
|
44
44
|
const [dataWas, statusWas] = resultWas
|
45
|
-
const statusPersisted = statusWas.complete === complete && statusWas.notfound === notfound && statusWas.connected === connected
|
45
|
+
const statusPersisted = statusWas.complete === complete && statusWas.notfound === notfound && statusWas.connected === connected && statusWas.destroyed === destroyed
|
46
46
|
if (dataWas === data && statusPersisted) return resultWas
|
47
|
-
const status = statusPersisted ? statusWas : { complete, notfound, connected }
|
47
|
+
const status = statusPersisted ? statusWas : { complete, notfound, connected, destroyed }
|
48
48
|
return [data, status]
|
49
49
|
})
|
50
50
|
}
|
@@ -55,6 +55,7 @@ export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
|
55
55
|
}
|
56
56
|
model.subscribe('load', update)
|
57
57
|
model.subscribe('change', update)
|
58
|
+
model.subscribe('destroy', update)
|
58
59
|
model.subscribe('connection', update)
|
59
60
|
return () => model.release()
|
60
61
|
}, [requestString])
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ar_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tompng
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: pry
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: sqlite3
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -105,7 +91,6 @@ files:
|
|
105
91
|
- ".gitignore"
|
106
92
|
- ".travis.yml"
|
107
93
|
- Gemfile
|
108
|
-
- Gemfile.lock
|
109
94
|
- LICENSE.txt
|
110
95
|
- README.md
|
111
96
|
- Rakefile
|
@@ -129,7 +114,8 @@ files:
|
|
129
114
|
- core/hooks.d.ts
|
130
115
|
- core/hooks.js
|
131
116
|
- gemfiles/Gemfile-rails-6
|
132
|
-
- gemfiles/Gemfile-rails-7
|
117
|
+
- gemfiles/Gemfile-rails-7-0
|
118
|
+
- gemfiles/Gemfile-rails-7-1
|
133
119
|
- index.d.ts
|
134
120
|
- index.js
|
135
121
|
- lib/ar_sync.rb
|
@@ -176,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
162
|
- !ruby/object:Gem::Version
|
177
163
|
version: '0'
|
178
164
|
requirements: []
|
179
|
-
rubygems_version: 3.
|
165
|
+
rubygems_version: 3.5.9
|
180
166
|
signing_key:
|
181
167
|
specification_version: 4
|
182
168
|
summary: ActiveRecord - JavaScript Sync
|