ar_sync 1.1.1 → 1.1.2
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 +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 +91 -83
- 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/version.rb +1 -1
- data/src/core/ArSyncModel.ts +9 -8
- data/src/core/ArSyncStore.ts +109 -98
- 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) {
|
@@ -197,63 +200,52 @@ class ArSyncContainerBase {
|
|
197
200
|
if (attrsonly) return attributes
|
198
201
|
return { attributes, as: column, params }
|
199
202
|
}
|
200
|
-
static
|
203
|
+
static load({ api, id, params, query }: Request, root: ArSyncStore) {
|
201
204
|
const parsedQuery = ArSyncRecord.parseQuery(query)
|
202
205
|
const compactQueryAttributes = ArSyncRecord.compactQueryAttributes(parsedQuery)
|
203
206
|
if (id != null) {
|
204
207
|
return modelBatchRequest.fetch(api, compactQueryAttributes, id).then(data => {
|
205
208
|
if (!data) throw { retry: false }
|
206
209
|
const request = { api, id, query: compactQueryAttributes }
|
207
|
-
return new ArSyncRecord(parsedQuery, data, request, root)
|
210
|
+
return new ArSyncRecord(parsedQuery, data, request, root, null, null)
|
208
211
|
})
|
209
212
|
} else {
|
210
213
|
const request = { api, query: compactQueryAttributes, params }
|
211
214
|
return ArSyncApi.syncFetch(request).then((response: any) => {
|
212
215
|
if (!response) {
|
213
216
|
throw { retry: false }
|
214
|
-
} else if (response.collection && response.order) {
|
215
|
-
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)
|
216
219
|
} else if (response instanceof Array) {
|
217
|
-
return new ArSyncCollection([], '', parsedQuery, response, request, root)
|
220
|
+
return new ArSyncCollection([], '', parsedQuery, response, request, root, null, null)
|
218
221
|
} else {
|
219
|
-
return new ArSyncRecord(parsedQuery, response, request, root)
|
222
|
+
return new ArSyncRecord(parsedQuery, response, request, root, null, null)
|
220
223
|
}
|
221
224
|
})
|
222
225
|
}
|
223
226
|
}
|
224
|
-
static load(apiParams, root) {
|
225
|
-
if (!(apiParams instanceof Array)) return this._load(apiParams, root)
|
226
|
-
return new Promise((resolve, _reject) => {
|
227
|
-
const resultModels: any[] = []
|
228
|
-
let countdown = apiParams.length
|
229
|
-
apiParams.forEach((param, i) => {
|
230
|
-
this._load(param, root).then(model => {
|
231
|
-
resultModels[i] = model
|
232
|
-
countdown --
|
233
|
-
if (countdown === 0) resolve(resultModels)
|
234
|
-
})
|
235
|
-
})
|
236
|
-
})
|
237
|
-
}
|
238
227
|
}
|
239
228
|
|
240
229
|
type NotifyData = {
|
241
230
|
action: 'add' | 'remove' | 'update'
|
242
|
-
|
243
|
-
id:
|
231
|
+
class: string
|
232
|
+
id: IDType
|
244
233
|
field?: string
|
245
234
|
}
|
246
235
|
|
247
236
|
class ArSyncRecord extends ArSyncContainerBase {
|
248
|
-
id:
|
249
|
-
root
|
237
|
+
id: IDType
|
238
|
+
root: ArSyncStore
|
250
239
|
query
|
251
240
|
queryAttributes
|
252
241
|
data
|
242
|
+
syncKeys: string[]
|
253
243
|
children: { [key: string]: ArSyncContainerBase | null }
|
254
244
|
paths: string[]
|
255
245
|
reloadQueryCache
|
256
|
-
|
246
|
+
rootRecord: boolean
|
247
|
+
fetching = new Set<string>()
|
248
|
+
constructor(query, data, request, root: ArSyncStore, parentModel: ArSyncRecord | ArSyncCollection | null, parentKey: string | number | null) {
|
257
249
|
super()
|
258
250
|
this.root = root
|
259
251
|
if (request) this.initForReload(request)
|
@@ -261,48 +253,43 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
261
253
|
this.queryAttributes = query.attributes || {}
|
262
254
|
this.data = {}
|
263
255
|
this.children = {}
|
256
|
+
this.rootRecord = !parentModel
|
257
|
+
this.id = data._sync.id
|
258
|
+
this.syncKeys = data._sync.keys
|
264
259
|
this.replaceData(data)
|
260
|
+
this.parentModel = parentModel
|
261
|
+
this.parentKey = parentKey
|
265
262
|
}
|
266
|
-
|
267
|
-
this.
|
268
|
-
|
269
|
-
replaceData(data) {
|
270
|
-
this.setSyncKeys(data.sync_keys)
|
263
|
+
replaceData(data: { _sync: SyncField }) {
|
264
|
+
this.id = data._sync.id
|
265
|
+
this.syncKeys = data._sync.keys
|
271
266
|
this.unsubscribeAll()
|
272
|
-
if (this.data.id !== data.id) {
|
273
|
-
this.mark()
|
274
|
-
this.data.id = data.id
|
275
|
-
}
|
276
267
|
this.paths = []
|
277
268
|
for (const key in this.queryAttributes) {
|
278
269
|
const subQuery = this.queryAttributes[key]
|
279
270
|
const aliasName = subQuery.as || key
|
280
271
|
const subData = data[aliasName]
|
281
272
|
const child = this.children[aliasName]
|
282
|
-
if (key === '
|
283
|
-
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)) {
|
284
275
|
if (child) {
|
285
|
-
child.replaceData(subData, this.
|
276
|
+
child.replaceData(subData, this.syncKeys)
|
286
277
|
} else {
|
287
|
-
const collection = new ArSyncCollection(this.
|
278
|
+
const collection = new ArSyncCollection(this.syncKeys, key, subQuery, subData, null, this.root, this, aliasName)
|
288
279
|
this.mark()
|
289
280
|
this.children[aliasName] = collection
|
290
281
|
this.data[aliasName] = collection.data
|
291
|
-
collection.parentModel = this
|
292
|
-
collection.parentKey = aliasName
|
293
282
|
}
|
294
283
|
} else {
|
295
284
|
if (subQuery.attributes && Object.keys(subQuery.attributes).length > 0) this.paths.push(key)
|
296
|
-
if (subData && subData.
|
285
|
+
if (subData && subData._sync) {
|
297
286
|
if (child) {
|
298
287
|
child.replaceData(subData)
|
299
288
|
} else {
|
300
|
-
const model = new ArSyncRecord(subQuery, subData, null, this.root)
|
289
|
+
const model = new ArSyncRecord(subQuery, subData, null, this.root, this, aliasName)
|
301
290
|
this.mark()
|
302
291
|
this.children[aliasName] = model
|
303
292
|
this.data[aliasName] = model.data
|
304
|
-
model.parentModel = this
|
305
|
-
model.parentKey = aliasName
|
306
293
|
}
|
307
294
|
} else {
|
308
295
|
if(child) {
|
@@ -318,6 +305,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
318
305
|
}
|
319
306
|
if (this.queryAttributes['*']) {
|
320
307
|
for (const key in data) {
|
308
|
+
if (key === '_sync') continue
|
321
309
|
if (!this.queryAttributes[key] && this.data[key] !== data[key]) {
|
322
310
|
this.mark()
|
323
311
|
this.data[key] = data[key]
|
@@ -327,28 +315,34 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
327
315
|
this.subscribeAll()
|
328
316
|
}
|
329
317
|
onNotify(notifyData: NotifyData, path?: string) {
|
330
|
-
const { action,
|
318
|
+
const { action, class: className, id } = notifyData
|
331
319
|
const query = path && this.queryAttributes[path]
|
332
320
|
const aliasName = (query && query.as) || path;
|
333
321
|
if (action === 'remove') {
|
334
322
|
const child = this.children[aliasName]
|
323
|
+
this.fetching.delete(`${aliasName}:${id}`) // To cancel consumeAdd
|
335
324
|
if (child) child.release()
|
336
325
|
this.children[aliasName] = null
|
337
326
|
this.mark()
|
338
327
|
this.data[aliasName] = null
|
339
328
|
this.onChange([aliasName], null)
|
340
329
|
} else if (action === 'add') {
|
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)
|
342
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)
|
343
339
|
if (!data || !this.data) return
|
344
|
-
const model = new ArSyncRecord(query, data, null, this.root)
|
340
|
+
const model = new ArSyncRecord(query, data, null, this.root, this, aliasName)
|
345
341
|
const child = this.children[aliasName]
|
346
342
|
if (child) child.release()
|
347
343
|
this.children[aliasName] = model
|
348
344
|
this.mark()
|
349
345
|
this.data[aliasName] = model.data
|
350
|
-
model.parentModel = this
|
351
|
-
model.parentKey = aliasName
|
352
346
|
this.onChange([aliasName], model.data)
|
353
347
|
}).catch(e => {
|
354
348
|
console.error(`failed to load ${className}:${id} ${e}`)
|
@@ -366,12 +360,16 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
366
360
|
}
|
367
361
|
subscribeAll() {
|
368
362
|
const callback = data => this.onNotify(data)
|
369
|
-
for (const key of this.
|
363
|
+
for (const key of this.syncKeys) {
|
370
364
|
this.subscribe(key, callback)
|
371
365
|
}
|
372
366
|
for (const path of this.paths) {
|
373
367
|
const pathCallback = data => this.onNotify(data, path)
|
374
|
-
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())
|
375
373
|
}
|
376
374
|
}
|
377
375
|
patchQuery(key: string) {
|
@@ -383,7 +381,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
383
381
|
let arrayQuery = [] as string[] | null
|
384
382
|
const hashQuery = {}
|
385
383
|
for (const key in this.queryAttributes) {
|
386
|
-
if (key === '
|
384
|
+
if (key === '_sync') continue
|
387
385
|
const val = this.queryAttributes[key]
|
388
386
|
if (!val || !val.attributes) {
|
389
387
|
arrayQuery?.push(key)
|
@@ -397,6 +395,7 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
397
395
|
}
|
398
396
|
update(data) {
|
399
397
|
for (const key in data) {
|
398
|
+
if (key === '_sync') continue
|
400
399
|
const subQuery = this.queryAttributes[key]
|
401
400
|
if (subQuery && subQuery.attributes && Object.keys(subQuery.attributes).length > 0) continue
|
402
401
|
if (this.data[key] === data[key]) continue
|
@@ -413,22 +412,24 @@ class ArSyncRecord extends ArSyncContainerBase {
|
|
413
412
|
if (!this.root || !this.root.immutable || !Object.isFrozen(this.data)) return
|
414
413
|
this.data = { ...this.data }
|
415
414
|
this.root.mark(this.data)
|
416
|
-
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)
|
417
416
|
}
|
418
417
|
}
|
419
418
|
|
420
419
|
type Ordering = { first?: number; last?: number; orderBy: string; direction: 'asc' | 'desc' }
|
421
420
|
class ArSyncCollection extends ArSyncContainerBase {
|
422
|
-
root
|
421
|
+
root: ArSyncStore
|
423
422
|
path: string
|
424
423
|
ordering: Ordering = { orderBy: 'id', direction: 'asc' }
|
425
424
|
query
|
426
425
|
queryAttributes
|
427
426
|
compactQueryAttributes
|
427
|
+
syncKeys: string[]
|
428
428
|
data: any[]
|
429
429
|
children: ArSyncRecord[]
|
430
430
|
aliasOrderKey = 'id'
|
431
|
-
|
431
|
+
fetching = new Set<IDType>()
|
432
|
+
constructor(parentSyncKeys: string[], path: string, query, data: any[], request, root: ArSyncStore, parentModel: ArSyncRecord | null, parentKey: string | null){
|
432
433
|
super()
|
433
434
|
this.root = root
|
434
435
|
this.path = path
|
@@ -441,7 +442,9 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
441
442
|
}
|
442
443
|
this.data = []
|
443
444
|
this.children = []
|
444
|
-
this.replaceData(data,
|
445
|
+
this.replaceData(data, parentSyncKeys)
|
446
|
+
this.parentModel = parentModel
|
447
|
+
this.parentKey = parentKey
|
445
448
|
}
|
446
449
|
setOrdering(ordering: { first?: unknown; last?: unknown; orderBy?: unknown; direction?: unknown }) {
|
447
450
|
let direction: 'asc' | 'desc' = 'asc'
|
@@ -456,17 +459,17 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
456
459
|
this.aliasOrderKey = (subQuery && subQuery.as) || orderBy
|
457
460
|
this.ordering = { first, last, direction, orderBy }
|
458
461
|
}
|
459
|
-
setSyncKeys(
|
460
|
-
if (
|
461
|
-
this.
|
462
|
+
setSyncKeys(parentSyncKeys: string[] | undefined) {
|
463
|
+
if (parentSyncKeys) {
|
464
|
+
this.syncKeys = parentSyncKeys.map(key => key + this.path)
|
462
465
|
} else {
|
463
|
-
this.
|
466
|
+
this.syncKeys = []
|
464
467
|
}
|
465
468
|
}
|
466
|
-
replaceData(data: any[] | { collection: any[]; ordering: Ordering },
|
467
|
-
this.setSyncKeys(
|
468
|
-
const existings = new Map<
|
469
|
-
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)
|
470
473
|
let collection: any[]
|
471
474
|
if (Array.isArray(data)) {
|
472
475
|
collection = data
|
@@ -478,14 +481,12 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
478
481
|
const newData: any[] = []
|
479
482
|
for (const subData of collection) {
|
480
483
|
let model: ArSyncRecord | undefined = undefined
|
481
|
-
if (typeof(subData) === 'object' && subData && '
|
484
|
+
if (typeof(subData) === 'object' && subData && '_sync' in subData) model = existings.get(subData._sync.id)
|
482
485
|
let data = subData
|
483
486
|
if (model) {
|
484
487
|
model.replaceData(subData)
|
485
|
-
} else if (subData.
|
486
|
-
model = new ArSyncRecord(this.query, subData, null, this.root)
|
487
|
-
model.parentModel = this
|
488
|
-
model.parentKey = subData.id
|
488
|
+
} else if (subData._sync) {
|
489
|
+
model = new ArSyncRecord(this.query, subData, null, this.root, this, subData._sync.id)
|
489
490
|
}
|
490
491
|
if (model) {
|
491
492
|
newChildren.push(model)
|
@@ -495,7 +496,7 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
495
496
|
}
|
496
497
|
while (this.children.length) {
|
497
498
|
const child = this.children.pop()!
|
498
|
-
if (!existings.has(child.
|
499
|
+
if (!existings.has(child.id)) child.release()
|
499
500
|
}
|
500
501
|
if (this.data.length || newChildren.length) this.mark()
|
501
502
|
while (this.data.length) this.data.pop()
|
@@ -503,13 +504,13 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
503
504
|
for (const el of newData) this.data.push(el)
|
504
505
|
this.subscribeAll()
|
505
506
|
}
|
506
|
-
consumeAdd(className: string, id:
|
507
|
+
consumeAdd(className: string, id: IDType) {
|
507
508
|
const { first, last, direction } = this.ordering
|
508
509
|
const limit = first || last
|
509
|
-
if (this.
|
510
|
-
if (limit && limit <= this.
|
511
|
-
const lastItem = this.
|
512
|
-
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]
|
513
514
|
if (direction === 'asc') {
|
514
515
|
if (first) {
|
515
516
|
if (lastItem && lastItem.id < id) return
|
@@ -524,11 +525,14 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
524
525
|
}
|
525
526
|
}
|
526
527
|
}
|
528
|
+
this.fetching.add(id)
|
527
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)
|
528
534
|
if (!data || !this.data) return
|
529
|
-
const model = new ArSyncRecord(this.query, data, null, this.root)
|
530
|
-
model.parentModel = this
|
531
|
-
model.parentKey = id
|
535
|
+
const model = new ArSyncRecord(this.query, data, null, this.root, this, id)
|
532
536
|
const overflow = limit && limit <= this.data.length
|
533
537
|
let rmodel: ArSyncRecord | undefined
|
534
538
|
this.mark()
|
@@ -583,8 +587,9 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
583
587
|
this.data.sort((a, b) => a[orderKey] > b[orderKey] ? -1 : +1)
|
584
588
|
}
|
585
589
|
}
|
586
|
-
consumeRemove(id:
|
587
|
-
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
|
588
593
|
if (idx < 0) return
|
589
594
|
this.mark()
|
590
595
|
this.children[idx].release()
|
@@ -592,58 +597,64 @@ class ArSyncCollection extends ArSyncContainerBase {
|
|
592
597
|
this.data.splice(idx, 1)
|
593
598
|
this.onChange([id], null)
|
594
599
|
}
|
595
|
-
onNotify(notifyData) {
|
600
|
+
onNotify(notifyData: NotifyData) {
|
596
601
|
if (notifyData.action === 'add') {
|
597
|
-
this.consumeAdd(notifyData.
|
602
|
+
this.consumeAdd(notifyData.class, notifyData.id)
|
598
603
|
} else if (notifyData.action === 'remove') {
|
599
604
|
this.consumeRemove(notifyData.id)
|
600
605
|
}
|
601
606
|
}
|
602
607
|
subscribeAll() {
|
603
608
|
const callback = data => this.onNotify(data)
|
604
|
-
for (const key of this.
|
609
|
+
for (const key of this.syncKeys) this.subscribe(key, callback)
|
605
610
|
}
|
606
611
|
onChange(path: (string | number)[], data) {
|
607
612
|
super.onChange(path, data)
|
608
613
|
if (path[1] === this.aliasOrderKey) this.markAndSort()
|
609
614
|
}
|
610
|
-
markAndSet(id:
|
615
|
+
markAndSet(id: IDType, data) {
|
611
616
|
this.mark()
|
612
|
-
const idx = this.
|
617
|
+
const idx = this.children.findIndex(a => a.id === id)
|
613
618
|
if (idx >= 0) this.data[idx] = data
|
614
619
|
}
|
615
620
|
mark() {
|
616
621
|
if (!this.root || !this.root.immutable || !Object.isFrozen(this.data)) return
|
617
622
|
this.data = [...this.data]
|
618
623
|
this.root.mark(this.data)
|
619
|
-
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)
|
620
625
|
}
|
621
626
|
}
|
622
627
|
|
623
|
-
export
|
628
|
+
export class ArSyncStore {
|
624
629
|
immutable: boolean
|
625
630
|
markedForFreezeObjects: any[]
|
626
631
|
changes
|
627
632
|
eventListeners
|
628
633
|
markForRelease: true | undefined
|
629
634
|
container
|
630
|
-
request
|
631
|
-
complete
|
635
|
+
request: Request
|
636
|
+
complete = false
|
632
637
|
notfound?: boolean
|
638
|
+
destroyed = false
|
633
639
|
data
|
634
640
|
changesBufferTimer: number | undefined | null
|
635
641
|
retryLoadTimer: number | undefined | null
|
636
642
|
static connectionManager
|
637
|
-
constructor(request, { immutable } = {} as { immutable?: boolean }) {
|
643
|
+
constructor(request: Request, { immutable } = {} as { immutable?: boolean }) {
|
638
644
|
this.immutable = !!immutable
|
639
645
|
this.markedForFreezeObjects = []
|
640
646
|
this.changes = []
|
641
647
|
this.eventListeners = { events: {}, serial: 0 }
|
642
648
|
this.request = request
|
643
|
-
this.complete = false
|
644
649
|
this.data = null
|
645
650
|
this.load(0)
|
646
651
|
}
|
652
|
+
handleDestroy() {
|
653
|
+
this.release()
|
654
|
+
this.data = null
|
655
|
+
this.destroyed = true
|
656
|
+
this.trigger('destroy')
|
657
|
+
}
|
647
658
|
load(retryCount: number) {
|
648
659
|
ArSyncContainerBase.load(this.request, this).then((container: ArSyncContainerBase) => {
|
649
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
|
data/Gemfile.lock
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
ar_sync (1.1.1)
|
5
|
-
activerecord
|
6
|
-
ar_serializer
|
7
|
-
|
8
|
-
GEM
|
9
|
-
remote: https://rubygems.org/
|
10
|
-
specs:
|
11
|
-
activemodel (7.0.2.3)
|
12
|
-
activesupport (= 7.0.2.3)
|
13
|
-
activerecord (7.0.2.3)
|
14
|
-
activemodel (= 7.0.2.3)
|
15
|
-
activesupport (= 7.0.2.3)
|
16
|
-
activerecord-import (1.4.0)
|
17
|
-
activerecord (>= 4.2)
|
18
|
-
activesupport (7.0.2.3)
|
19
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
20
|
-
i18n (>= 1.6, < 2)
|
21
|
-
minitest (>= 5.1)
|
22
|
-
tzinfo (~> 2.0)
|
23
|
-
ar_serializer (1.2.0)
|
24
|
-
activerecord
|
25
|
-
top_n_loader
|
26
|
-
coderay (1.1.3)
|
27
|
-
concurrent-ruby (1.1.10)
|
28
|
-
i18n (1.10.0)
|
29
|
-
concurrent-ruby (~> 1.0)
|
30
|
-
method_source (1.0.0)
|
31
|
-
minitest (5.15.0)
|
32
|
-
pry (0.14.1)
|
33
|
-
coderay (~> 1.1)
|
34
|
-
method_source (~> 1.0)
|
35
|
-
rake (13.0.6)
|
36
|
-
sqlite3 (1.4.2)
|
37
|
-
top_n_loader (1.0.2)
|
38
|
-
activerecord
|
39
|
-
tzinfo (2.0.4)
|
40
|
-
concurrent-ruby (~> 1.0)
|
41
|
-
|
42
|
-
PLATFORMS
|
43
|
-
ruby
|
44
|
-
|
45
|
-
DEPENDENCIES
|
46
|
-
activerecord-import
|
47
|
-
ar_serializer
|
48
|
-
ar_sync!
|
49
|
-
pry
|
50
|
-
rake
|
51
|
-
sqlite3
|
52
|
-
|
53
|
-
BUNDLED WITH
|
54
|
-
2.1.2
|