ar_sync 1.1.1 → 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 +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
|