ar_sync 1.1.0 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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<number, {
10
- id: number
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: number) {
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, _sync_keys?) {}
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 compactQuery(query: ParsedQuery) {
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 _load({ api, id, params, query }, root) {
203
+ static load({ api, id, params, query }: Request, root: ArSyncStore) {
200
204
  const parsedQuery = ArSyncRecord.parseQuery(query)
201
- const compactQuery = ArSyncRecord.compactQuery(parsedQuery)
205
+ const compactQueryAttributes = ArSyncRecord.compactQueryAttributes(parsedQuery)
202
206
  if (id != null) {
203
- return modelBatchRequest.fetch(api, compactQuery, id).then(data => {
207
+ return modelBatchRequest.fetch(api, compactQueryAttributes, id).then(data => {
204
208
  if (!data) throw { retry: false }
205
- const request = { api, id, query: compactQuery }
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: compactQuery, params }
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.sync_keys, 'collection', parsedQuery, response, request, root)
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
- class_name: string
242
- id: number
231
+ class: string
232
+ id: IDType
243
233
  field?: string
244
234
  }
245
235
 
246
236
  class ArSyncRecord extends ArSyncContainerBase {
247
- id: number
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
- constructor(query, data, request, root) {
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
- setSyncKeys(sync_keys: string[] | undefined) {
266
- this.sync_keys = sync_keys ?? []
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 === 'sync_keys') continue
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.sync_keys)
276
+ child.replaceData(subData, this.syncKeys)
285
277
  } else {
286
- const collection = new ArSyncCollection(this.sync_keys, key, subQuery, subData, null, this.root)
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.sync_keys) {
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, class_name: className, id } = notifyData
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
- if (this.data[aliasName] && this.data[aliasName].id === id) return
341
- modelBatchRequest.fetch(className, ArSyncRecord.compactQuery(query), id).then(data => {
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.sync_keys) {
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.sync_keys) this.subscribe(key + path, pathCallback)
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 val = this.queryAttributes[key]
378
- if (!val) return
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
- const reloadQuery = this.reloadQueryCache = { attributes: [] as any[] }
381
+ let arrayQuery = [] as string[] | null
382
+ const hashQuery = {}
391
383
  for (const key in this.queryAttributes) {
392
- if (key === 'sync_keys') continue
384
+ if (key === '_sync') continue
393
385
  const val = this.queryAttributes[key]
394
386
  if (!val || !val.attributes) {
395
- reloadQuery.attributes.push(key)
387
+ arrayQuery?.push(key)
388
+ hashQuery[key] = true
396
389
  } else if (!val.params && Object.keys(val.attributes).length === 0) {
397
- reloadQuery.attributes.push({ [key]: val })
390
+ arrayQuery = null
391
+ hashQuery[key] = val
398
392
  }
399
393
  }
400
- return reloadQuery
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
- compactQuery
426
+ compactQueryAttributes
427
+ syncKeys: string[]
432
428
  data: any[]
433
429
  children: ArSyncRecord[]
434
430
  aliasOrderKey = 'id'
435
- constructor(sync_keys: string[], path: string, query, data: any[], request, root){
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.compactQuery = ArSyncRecord.compactQuery(query)
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, sync_keys)
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(sync_keys: string[]) {
464
- if (sync_keys) {
465
- this.sync_keys = sync_keys.map(key => key + this.path)
462
+ setSyncKeys(parentSyncKeys: string[] | undefined) {
463
+ if (parentSyncKeys) {
464
+ this.syncKeys = parentSyncKeys.map(key => key + this.path)
466
465
  } else {
467
- this.sync_keys = []
466
+ this.syncKeys = []
468
467
  }
469
468
  }
470
- replaceData(data: any[] | { collection: any[]; ordering: Ordering }, sync_keys: string[]) {
471
- this.setSyncKeys(sync_keys)
472
- const existings = new Map<number, ArSyncRecord>()
473
- for (const child of this.children) existings.set(child.data.id, 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 && 'sync_keys' in subData) model = existings.get(subData.id)
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.sync_keys) {
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.data.id)) child.release()
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: number) {
507
+ consumeAdd(className: string, id: IDType) {
511
508
  const { first, last, direction } = this.ordering
512
509
  const limit = first || last
513
- if (this.data.findIndex(a => a.id === id) >= 0) return
514
- if (limit && limit <= this.data.length) {
515
- const lastItem = this.data[this.data.length - 1]
516
- const firstItem = this.data[0]
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
- modelBatchRequest.fetch(className, this.compactQuery, id).then((data: any) => {
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: number) {
591
- const idx = this.data.findIndex(a => a.id === id)
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.class_name, notifyData.id)
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.sync_keys) this.subscribe(key, callback)
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: number, data) {
615
+ markAndSet(id: IDType, data) {
615
616
  this.mark()
616
- const idx = this.data.findIndex(a => a.id === id)
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 default class ArSyncStore {
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: boolean
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.0
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: 2022-04-17 00:00:00.000000000 Z
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.3.3
165
+ rubygems_version: 3.5.9
180
166
  signing_key:
181
167
  specification_version: 4
182
168
  summary: ActiveRecord - JavaScript Sync