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.
@@ -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