teamplay 0.4.0-alpha.84 → 0.4.0-alpha.86
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.
- package/orm/Doc.js +454 -135
- package/orm/Query.js +500 -210
- package/package.json +2 -2
package/orm/Doc.js
CHANGED
|
@@ -176,29 +176,162 @@ class Doc {
|
|
|
176
176
|
export class DocSubscriptions {
|
|
177
177
|
constructor (DocClass = Doc) {
|
|
178
178
|
this.DocClass = DocClass
|
|
179
|
-
this.
|
|
180
|
-
this.
|
|
181
|
-
this.ownerSubscribeCount = new Map() // ownerKey -> subscribe intent count
|
|
182
|
-
this.ownerMeta = new Map() // ownerKey -> { hash, segments, rootId }
|
|
183
|
-
this.ownerKeysByHash = new Map() // transportHash -> Set(ownerKey)
|
|
184
|
-
this.docs = new Map()
|
|
185
|
-
this.pendingDestroyTimers = new Map()
|
|
186
|
-
this.transportTasks = new Map()
|
|
179
|
+
this.ownerRecords = new Map() // ownerKey -> owner record
|
|
180
|
+
this.entries = new Map() // transportHash -> transport entry
|
|
187
181
|
this.fr = new FinalizationRegistry(({ hash, ownerKey }) => this.destroyByOwnerKey(ownerKey, { hash, force: true }))
|
|
182
|
+
this.subCount = createReadonlyMapView({
|
|
183
|
+
get: hash => this.getTrackedCount(hash),
|
|
184
|
+
has: hash => this.getTrackedCount(hash) !== undefined,
|
|
185
|
+
size: () => this.getTrackedHashCountSize(),
|
|
186
|
+
keys: () => getTrackedHashes(this.entries)
|
|
187
|
+
})
|
|
188
|
+
this.ownerFetchCount = createReadonlyMapView({
|
|
189
|
+
get: ownerKey => {
|
|
190
|
+
const count = this.ownerRecords.get(ownerKey)?.fetchCount
|
|
191
|
+
return count > 0 ? count : undefined
|
|
192
|
+
},
|
|
193
|
+
has: ownerKey => !!this.ownerRecords.get(ownerKey)?.fetchCount,
|
|
194
|
+
size: () => countMapLike(this.ownerRecords, record => record.fetchCount > 0),
|
|
195
|
+
keys: () => filterMapKeys(this.ownerRecords, record => record.fetchCount > 0)
|
|
196
|
+
})
|
|
197
|
+
this.ownerSubscribeCount = createReadonlyMapView({
|
|
198
|
+
get: ownerKey => {
|
|
199
|
+
const count = this.ownerRecords.get(ownerKey)?.subscribeCount
|
|
200
|
+
return count > 0 ? count : undefined
|
|
201
|
+
},
|
|
202
|
+
has: ownerKey => !!this.ownerRecords.get(ownerKey)?.subscribeCount,
|
|
203
|
+
size: () => countMapLike(this.ownerRecords, record => record.subscribeCount > 0),
|
|
204
|
+
keys: () => filterMapKeys(this.ownerRecords, record => record.subscribeCount > 0)
|
|
205
|
+
})
|
|
206
|
+
this.ownerMeta = createReadonlyMapView({
|
|
207
|
+
get: ownerKey => this.getOwnerMeta(ownerKey),
|
|
208
|
+
has: ownerKey => this.ownerRecords.has(ownerKey),
|
|
209
|
+
size: () => this.ownerRecords.size,
|
|
210
|
+
keys: () => this.ownerRecords.keys()
|
|
211
|
+
})
|
|
212
|
+
this.ownerKeysByHash = createReadonlyMapView({
|
|
213
|
+
get: hash => this.getOwnerKeys(hash),
|
|
214
|
+
has: hash => !!this.getOwnerKeys(hash),
|
|
215
|
+
size: () => countMapLike(this.entries, entry => entry.owners.size > 0),
|
|
216
|
+
keys: () => filterMapKeys(this.entries, entry => entry.owners.size > 0)
|
|
217
|
+
})
|
|
218
|
+
this.docs = createReadonlyMapView({
|
|
219
|
+
get: hash => this.getRuntime(hash),
|
|
220
|
+
has: hash => this.hasRuntime(hash),
|
|
221
|
+
size: () => this.getRuntimeCount(),
|
|
222
|
+
keys: () => filterMapKeys(this.entries, entry => !!entry.runtime)
|
|
223
|
+
})
|
|
224
|
+
this.pendingDestroyTimers = createReadonlyMapView({
|
|
225
|
+
get: hash => this.entries.get(hash)?.pendingDestroy,
|
|
226
|
+
has: hash => !!this.entries.get(hash)?.pendingDestroy,
|
|
227
|
+
size: () => countMapLike(this.entries, entry => !!entry.pendingDestroy),
|
|
228
|
+
keys: () => filterMapKeys(this.entries, entry => !!entry.pendingDestroy)
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getOrCreateOwnerRecord (ownerKey, meta) {
|
|
233
|
+
let record = this.ownerRecords.get(ownerKey)
|
|
234
|
+
if (!record) {
|
|
235
|
+
record = {
|
|
236
|
+
ownerKey,
|
|
237
|
+
rootId: meta.rootId,
|
|
238
|
+
hash: meta.hash,
|
|
239
|
+
segments: meta.segments ? [...meta.segments] : parseDocHash(meta.hash),
|
|
240
|
+
fetchCount: 0,
|
|
241
|
+
subscribeCount: 0
|
|
242
|
+
}
|
|
243
|
+
this.ownerRecords.set(ownerKey, record)
|
|
244
|
+
} else {
|
|
245
|
+
if (meta.rootId != null) record.rootId = meta.rootId
|
|
246
|
+
if (meta.hash != null) record.hash = meta.hash
|
|
247
|
+
if (meta.segments != null) record.segments = [...meta.segments]
|
|
248
|
+
}
|
|
249
|
+
return record
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
getOrCreateEntry (hash, segments) {
|
|
253
|
+
let entry = this.entries.get(hash)
|
|
254
|
+
if (!entry) {
|
|
255
|
+
entry = {
|
|
256
|
+
hash,
|
|
257
|
+
segments: segments ? [...segments] : parseDocHash(hash),
|
|
258
|
+
mode: 'idle',
|
|
259
|
+
targetMode: 'idle',
|
|
260
|
+
phase: 'stable',
|
|
261
|
+
runtime: null,
|
|
262
|
+
owners: new Set(),
|
|
263
|
+
retainCount: 0,
|
|
264
|
+
pendingDestroy: null,
|
|
265
|
+
reconcilePromise: null
|
|
266
|
+
}
|
|
267
|
+
this.entries.set(hash, entry)
|
|
268
|
+
} else if (segments && !entry.segments?.length) {
|
|
269
|
+
entry.segments = [...segments]
|
|
270
|
+
}
|
|
271
|
+
return entry
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
getEntry (hash) {
|
|
275
|
+
return this.entries.get(hash)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
getEntryTotalCount (entry) {
|
|
279
|
+
if (!entry) return 0
|
|
280
|
+
let count = entry.retainCount
|
|
281
|
+
for (const ownerKey of entry.owners) {
|
|
282
|
+
count += this.getOwnerTotalCount(ownerKey)
|
|
283
|
+
}
|
|
284
|
+
return count
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
syncOwnerMirror () {}
|
|
288
|
+
|
|
289
|
+
clearOwnerMirror () {}
|
|
290
|
+
|
|
291
|
+
syncEntryMirror () {}
|
|
292
|
+
|
|
293
|
+
deleteEntryIfEmpty (hash) {
|
|
294
|
+
const entry = this.entries.get(hash)
|
|
295
|
+
if (!entry) return
|
|
296
|
+
if (entry.owners.size > 0) return
|
|
297
|
+
if (entry.retainCount > 0) return
|
|
298
|
+
if (entry.pendingDestroy) return
|
|
299
|
+
if (entry.runtime) return
|
|
300
|
+
if (entry.phase === 'transition') return
|
|
301
|
+
this.entries.delete(hash)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
ensureRuntime (hash, segments) {
|
|
305
|
+
const entry = this.getOrCreateEntry(hash, segments)
|
|
306
|
+
if (!entry.runtime) {
|
|
307
|
+
const runtimeSegments = entry.segments?.length ? entry.segments : parseDocHash(hash)
|
|
308
|
+
entry.runtime = new this.DocClass(...runtimeSegments)
|
|
309
|
+
}
|
|
310
|
+
entry.runtime.init()
|
|
311
|
+
entry.mode = entry.runtime.activeTransportMode || entry.mode
|
|
312
|
+
this.syncEntryMirror(entry)
|
|
313
|
+
return entry.runtime
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
addOwnerToEntry (record) {
|
|
317
|
+
const entry = this.getOrCreateEntry(record.hash, record.segments)
|
|
318
|
+
entry.owners.add(record.ownerKey)
|
|
319
|
+
this.syncEntryMirror(entry)
|
|
320
|
+
return entry
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
removeOwnerFromEntry (record) {
|
|
324
|
+
const entry = this.entries.get(record.hash)
|
|
325
|
+
if (!entry) return
|
|
326
|
+
entry.owners.delete(record.ownerKey)
|
|
327
|
+
this.syncEntryMirror(entry)
|
|
188
328
|
}
|
|
189
329
|
|
|
190
330
|
init ($doc) {
|
|
191
331
|
const segments = [...$doc[SEGMENTS]]
|
|
192
332
|
const hash = hashDoc(segments)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (doc.initialized) return
|
|
196
|
-
doc.init()
|
|
197
|
-
} else {
|
|
198
|
-
doc = new this.DocClass(...segments)
|
|
199
|
-
this.docs.set(hash, doc)
|
|
200
|
-
doc.init()
|
|
201
|
-
}
|
|
333
|
+
this.getOrCreateEntry(hash, segments)
|
|
334
|
+
this.ensureRuntime(hash, segments)
|
|
202
335
|
}
|
|
203
336
|
|
|
204
337
|
subscribe ($doc, { intent = 'subscribe' } = {}) {
|
|
@@ -207,23 +340,22 @@ export class DocSubscriptions {
|
|
|
207
340
|
const rootId = getOwningRootId($doc)
|
|
208
341
|
const ownerKey = getDocOwnerKey(rootId, hash)
|
|
209
342
|
const token = getDocFinalizationToken($doc)
|
|
210
|
-
const
|
|
343
|
+
const entry = this.getOrCreateEntry(hash, segments)
|
|
344
|
+
const previousCount = this.getEntryTotalCount(entry)
|
|
211
345
|
this.cancelDestroy(hash)
|
|
212
|
-
this.
|
|
213
|
-
this.
|
|
214
|
-
this.
|
|
346
|
+
const record = this.getOrCreateOwnerRecord(ownerKey, { hash, segments, rootId })
|
|
347
|
+
this.incrementOwnerIntent(record, intent)
|
|
348
|
+
this.addOwnerToEntry(record)
|
|
215
349
|
if (rootId) {
|
|
216
350
|
registerRootOwnedDirectDocSubscription(rootId, hash, segments, token)
|
|
217
351
|
}
|
|
218
352
|
this.fr.register($doc, { hash, ownerKey }, token)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const doc = this.docs.get(hash)
|
|
353
|
+
this.ensureRuntime(hash, segments)
|
|
354
|
+
const doc = entry.runtime
|
|
222
355
|
if (
|
|
223
356
|
previousCount > 0 &&
|
|
224
357
|
doc &&
|
|
225
|
-
|
|
226
|
-
!this.transportTasks.get(hash) &&
|
|
358
|
+
entry.phase === 'stable' &&
|
|
227
359
|
this.getDesiredTransportMode(hash) === doc.activeTransportMode
|
|
228
360
|
) return
|
|
229
361
|
return this.reconcileTransport(hash)
|
|
@@ -232,10 +364,11 @@ export class DocSubscriptions {
|
|
|
232
364
|
retain ($doc) {
|
|
233
365
|
const segments = [...$doc[SEGMENTS]]
|
|
234
366
|
const hash = hashDoc(segments)
|
|
367
|
+
const entry = this.getOrCreateEntry(hash, segments)
|
|
235
368
|
this.cancelDestroy(hash)
|
|
236
|
-
|
|
237
|
-
this.
|
|
238
|
-
this.
|
|
369
|
+
entry.retainCount += 1
|
|
370
|
+
this.ensureRuntime(hash, segments)
|
|
371
|
+
this.syncEntryMirror(entry)
|
|
239
372
|
}
|
|
240
373
|
|
|
241
374
|
async unsubscribe ($doc, { intent = 'subscribe' } = {}) {
|
|
@@ -244,23 +377,26 @@ export class DocSubscriptions {
|
|
|
244
377
|
const rootId = getOwningRootId($doc)
|
|
245
378
|
const ownerKey = getDocOwnerKey(rootId, hash)
|
|
246
379
|
const token = getDocFinalizationToken($doc)
|
|
247
|
-
const
|
|
380
|
+
const record = this.ownerRecords.get(ownerKey)
|
|
381
|
+
const currentIntentCount = this.getOwnerIntentCount(record, intent)
|
|
248
382
|
if (currentIntentCount <= 0) {
|
|
249
383
|
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES) throw ERRORS.notSubscribed($doc)
|
|
250
384
|
return
|
|
251
385
|
}
|
|
252
|
-
this.setOwnerIntentCount(
|
|
253
|
-
const nextOwnerCount = this.getOwnerTotalCount(
|
|
254
|
-
const count = Math.max((this.subCount.get(hash) || 0) - 1, 0)
|
|
255
|
-
if (count > 0) this.subCount.set(hash, count)
|
|
256
|
-
else this.subCount.set(hash, 0)
|
|
386
|
+
this.setOwnerIntentCount(record, intent, currentIntentCount - 1)
|
|
387
|
+
const nextOwnerCount = this.getOwnerTotalCount(record)
|
|
257
388
|
if (rootId) {
|
|
258
389
|
unregisterRootOwnedDirectDocSubscription(rootId, hash, token)
|
|
259
390
|
}
|
|
391
|
+
const entry = this.getOrCreateEntry(hash, segments)
|
|
260
392
|
if (nextOwnerCount === 0) {
|
|
261
393
|
this.fr.unregister(token)
|
|
262
|
-
|
|
394
|
+
if (record) {
|
|
395
|
+
this.removeOwnerFromEntry(record)
|
|
396
|
+
}
|
|
397
|
+
this.ownerRecords.delete(ownerKey)
|
|
263
398
|
}
|
|
399
|
+
const count = this.getEntryTotalCount(entry)
|
|
264
400
|
const destroyPromise = count === 0 ? this.scheduleDestroy(segments) : undefined
|
|
265
401
|
await this.reconcileTransport(hash)
|
|
266
402
|
if (count > 0) return
|
|
@@ -270,17 +406,17 @@ export class DocSubscriptions {
|
|
|
270
406
|
async release ($doc) {
|
|
271
407
|
const segments = [...$doc[SEGMENTS]]
|
|
272
408
|
const hash = hashDoc(segments)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (count < 0) {
|
|
409
|
+
const entry = this.entries.get(hash)
|
|
410
|
+
if (!entry) {
|
|
276
411
|
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES) throw ERRORS.notSubscribed($doc)
|
|
277
412
|
return
|
|
278
413
|
}
|
|
279
|
-
if (
|
|
280
|
-
|
|
414
|
+
if (entry.retainCount <= 0) {
|
|
415
|
+
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES) throw ERRORS.notSubscribed($doc)
|
|
281
416
|
return
|
|
282
417
|
}
|
|
283
|
-
|
|
418
|
+
entry.retainCount -= 1
|
|
419
|
+
if ((this.getTrackedCount(hash) || 0) > 0) return
|
|
284
420
|
await this.scheduleDestroy(segments)
|
|
285
421
|
}
|
|
286
422
|
|
|
@@ -290,18 +426,12 @@ export class DocSubscriptions {
|
|
|
290
426
|
}
|
|
291
427
|
|
|
292
428
|
async clear () {
|
|
293
|
-
const hashes = new Set(
|
|
294
|
-
...this.pendingDestroyTimers.keys(),
|
|
295
|
-
...this.docs.keys()
|
|
296
|
-
])
|
|
429
|
+
const hashes = new Set(this.entries.keys())
|
|
297
430
|
for (const hash of hashes) {
|
|
298
431
|
await this.destroyByHash(hash, { force: true })
|
|
299
432
|
}
|
|
300
|
-
this.
|
|
301
|
-
this.
|
|
302
|
-
this.ownerSubscribeCount.clear()
|
|
303
|
-
this.ownerMeta.clear()
|
|
304
|
-
this.ownerKeysByHash.clear()
|
|
433
|
+
this.entries.clear()
|
|
434
|
+
this.ownerRecords.clear()
|
|
305
435
|
}
|
|
306
436
|
|
|
307
437
|
async releaseRootOwnedSubscriptions (rootId) {
|
|
@@ -317,7 +447,7 @@ export class DocSubscriptions {
|
|
|
317
447
|
}
|
|
318
448
|
|
|
319
449
|
async flushPendingDestroys () {
|
|
320
|
-
const hashes = Array.from(this.
|
|
450
|
+
const hashes = Array.from(filterMapKeys(this.entries, entry => !!entry.pendingDestroy))
|
|
321
451
|
for (const hash of hashes) {
|
|
322
452
|
await this.destroyByHash(hash)
|
|
323
453
|
}
|
|
@@ -330,18 +460,19 @@ export class DocSubscriptions {
|
|
|
330
460
|
await this.destroyByHash(hash, options)
|
|
331
461
|
return
|
|
332
462
|
}
|
|
333
|
-
const
|
|
463
|
+
const entry = this.getOrCreateEntry(hash, segments)
|
|
464
|
+
const existing = entry.pendingDestroy
|
|
334
465
|
if (existing) {
|
|
335
466
|
if (options.force) existing.force = true
|
|
336
467
|
return existing.promise
|
|
337
468
|
}
|
|
338
|
-
const
|
|
339
|
-
if (options.force)
|
|
340
|
-
|
|
341
|
-
this.destroyByHash(hash, { force:
|
|
469
|
+
const pendingDestroy = createPendingDestroyEntry()
|
|
470
|
+
if (options.force) pendingDestroy.force = true
|
|
471
|
+
pendingDestroy.timer = setTimeout(() => {
|
|
472
|
+
this.destroyByHash(hash, { force: pendingDestroy.force }).catch(ignoreDestroyError)
|
|
342
473
|
}, delay)
|
|
343
|
-
|
|
344
|
-
return
|
|
474
|
+
entry.pendingDestroy = pendingDestroy
|
|
475
|
+
return pendingDestroy.promise
|
|
345
476
|
}
|
|
346
477
|
|
|
347
478
|
cancelDestroy (hash) {
|
|
@@ -351,36 +482,50 @@ export class DocSubscriptions {
|
|
|
351
482
|
}
|
|
352
483
|
|
|
353
484
|
async reconcileTransport (hash) {
|
|
354
|
-
const
|
|
355
|
-
|
|
485
|
+
const entry = this.getOrCreateEntry(hash)
|
|
486
|
+
entry.targetMode = this.getDesiredTransportMode(hash)
|
|
487
|
+
if (entry.phase === 'transition' && entry.reconcilePromise) return entry.reconcilePromise
|
|
488
|
+
const next = Promise.resolve()
|
|
356
489
|
.catch(ignoreDestroyError)
|
|
357
490
|
.then(() => this.reconcileTransportNow(hash))
|
|
358
|
-
|
|
491
|
+
entry.phase = 'transition'
|
|
492
|
+
entry.reconcilePromise = next
|
|
359
493
|
try {
|
|
360
494
|
await next
|
|
361
495
|
} finally {
|
|
362
|
-
|
|
496
|
+
const currentEntry = this.entries.get(hash)
|
|
497
|
+
if (currentEntry?.reconcilePromise === next) {
|
|
498
|
+
currentEntry.reconcilePromise = null
|
|
499
|
+
currentEntry.phase = 'stable'
|
|
500
|
+
}
|
|
501
|
+
this.deleteEntryIfEmpty(hash)
|
|
363
502
|
}
|
|
364
503
|
}
|
|
365
504
|
|
|
366
505
|
async reconcileTransportNow (hash) {
|
|
367
|
-
const
|
|
368
|
-
if (!doc) return
|
|
506
|
+
const entry = this.getOrCreateEntry(hash)
|
|
369
507
|
while (true) {
|
|
370
|
-
|
|
371
|
-
const
|
|
508
|
+
let doc = entry.runtime
|
|
509
|
+
const desiredMode = entry.targetMode = this.getDesiredTransportMode(hash)
|
|
510
|
+
const currentMode = doc?.activeTransportMode ?? entry.mode
|
|
511
|
+
entry.mode = currentMode
|
|
372
512
|
if (desiredMode === currentMode) return
|
|
373
513
|
if (desiredMode === 'idle') {
|
|
374
|
-
if (currentMode
|
|
375
|
-
|
|
514
|
+
if (doc && currentMode !== 'idle') {
|
|
515
|
+
await doc.unsubscribe()
|
|
516
|
+
}
|
|
517
|
+
entry.mode = 'idle'
|
|
376
518
|
continue
|
|
377
519
|
}
|
|
378
|
-
if (currentMode !== 'idle') {
|
|
520
|
+
if (currentMode !== 'idle' && doc) {
|
|
379
521
|
await doc.unsubscribe()
|
|
522
|
+
entry.mode = 'idle'
|
|
380
523
|
continue
|
|
381
524
|
}
|
|
382
|
-
doc
|
|
383
|
-
await doc.
|
|
525
|
+
doc = this.ensureRuntime(hash)
|
|
526
|
+
await doc.subscribe({ mode: desiredMode })
|
|
527
|
+
entry.runtime = doc
|
|
528
|
+
entry.mode = doc.activeTransportMode || desiredMode
|
|
384
529
|
}
|
|
385
530
|
}
|
|
386
531
|
|
|
@@ -397,33 +542,51 @@ export class DocSubscriptions {
|
|
|
397
542
|
}
|
|
398
543
|
|
|
399
544
|
try {
|
|
400
|
-
const
|
|
545
|
+
const entry = this.entries.get(hash)
|
|
546
|
+
if (options.force && entry?.owners.size) {
|
|
547
|
+
this.removeAllOwnersFromEntry(hash)
|
|
548
|
+
}
|
|
549
|
+
const count = entry ? this.getEntryTotalCount(entry) : (this.getTrackedCount(hash) || 0)
|
|
401
550
|
if (!options.force && count > 0) {
|
|
402
551
|
settlePending()
|
|
403
552
|
return
|
|
404
553
|
}
|
|
405
|
-
const doc =
|
|
554
|
+
const doc = entry?.runtime
|
|
406
555
|
if (!doc) {
|
|
407
|
-
|
|
556
|
+
if (entry) {
|
|
557
|
+
entry.mode = 'idle'
|
|
558
|
+
entry.runtime = null
|
|
559
|
+
this.deleteEntryIfEmpty(hash)
|
|
560
|
+
}
|
|
408
561
|
settlePending()
|
|
409
562
|
return
|
|
410
563
|
}
|
|
411
564
|
await this.reconcileTransport(hash)
|
|
412
|
-
|
|
565
|
+
const nextEntry = this.entries.get(hash)
|
|
566
|
+
const nextCount = nextEntry ? this.getEntryTotalCount(nextEntry) : (this.getTrackedCount(hash) || 0)
|
|
567
|
+
if (!options.force && nextCount > 0) {
|
|
413
568
|
settlePending()
|
|
414
569
|
return
|
|
415
570
|
}
|
|
416
|
-
|
|
417
|
-
|
|
571
|
+
const activeDoc = nextEntry?.runtime || doc
|
|
572
|
+
if (activeDoc.activeTransportMode !== 'idle') {
|
|
573
|
+
await activeDoc.unsubscribe()
|
|
418
574
|
}
|
|
419
|
-
|
|
575
|
+
const finalEntryBeforeDestroy = this.entries.get(hash)
|
|
576
|
+
const finalCountBeforeDestroy = finalEntryBeforeDestroy
|
|
577
|
+
? this.getEntryTotalCount(finalEntryBeforeDestroy)
|
|
578
|
+
: (this.getTrackedCount(hash) || 0)
|
|
579
|
+
if (!options.force && finalCountBeforeDestroy > 0) {
|
|
420
580
|
settlePending()
|
|
421
581
|
return
|
|
422
582
|
}
|
|
423
|
-
if (typeof
|
|
424
|
-
if (typeof
|
|
425
|
-
if (pendingDestroy)
|
|
426
|
-
|
|
583
|
+
if (typeof activeDoc.hasPending === 'function' && activeDoc.hasPending()) {
|
|
584
|
+
if (typeof activeDoc.whenNothingPending === 'function') {
|
|
585
|
+
if (pendingDestroy) {
|
|
586
|
+
const nextEntry = this.getOrCreateEntry(hash)
|
|
587
|
+
nextEntry.pendingDestroy = pendingDestroy
|
|
588
|
+
}
|
|
589
|
+
activeDoc.whenNothingPending(() => {
|
|
427
590
|
const nextOptions = pendingDestroy ? { ...options, _pendingDestroy: pendingDestroy } : options
|
|
428
591
|
this.destroyByHash(hash, nextOptions).catch(ignoreDestroyError)
|
|
429
592
|
})
|
|
@@ -432,11 +595,14 @@ export class DocSubscriptions {
|
|
|
432
595
|
}
|
|
433
596
|
return
|
|
434
597
|
}
|
|
435
|
-
if (typeof
|
|
436
|
-
if (typeof
|
|
437
|
-
this.
|
|
438
|
-
|
|
439
|
-
|
|
598
|
+
if (typeof activeDoc.destroy === 'function') await activeDoc.destroy()
|
|
599
|
+
if (typeof activeDoc.dispose === 'function') activeDoc.dispose()
|
|
600
|
+
const finalEntry = this.entries.get(hash)
|
|
601
|
+
if (finalEntry) {
|
|
602
|
+
finalEntry.runtime = null
|
|
603
|
+
finalEntry.mode = 'idle'
|
|
604
|
+
this.deleteEntryIfEmpty(hash)
|
|
605
|
+
}
|
|
440
606
|
settlePending()
|
|
441
607
|
} catch (err) {
|
|
442
608
|
settlePending(err)
|
|
@@ -445,65 +611,72 @@ export class DocSubscriptions {
|
|
|
445
611
|
}
|
|
446
612
|
|
|
447
613
|
takePendingDestroy (hash, expectedEntry) {
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
614
|
+
const transportEntry = this.entries.get(hash)
|
|
615
|
+
const pendingDestroy = transportEntry?.pendingDestroy
|
|
616
|
+
if (!pendingDestroy) return
|
|
617
|
+
if (expectedEntry && pendingDestroy !== expectedEntry) return
|
|
618
|
+
clearTimeout(pendingDestroy.timer)
|
|
619
|
+
transportEntry.pendingDestroy = null
|
|
620
|
+
this.deleteEntryIfEmpty(hash)
|
|
621
|
+
return pendingDestroy
|
|
454
622
|
}
|
|
455
623
|
|
|
456
|
-
getOwnerIntentCount (
|
|
457
|
-
const
|
|
458
|
-
|
|
624
|
+
getOwnerIntentCount (recordOrOwnerKey, intent) {
|
|
625
|
+
const record = typeof recordOrOwnerKey === 'string'
|
|
626
|
+
? this.ownerRecords.get(recordOrOwnerKey)
|
|
627
|
+
: recordOrOwnerKey
|
|
628
|
+
if (!record) return 0
|
|
629
|
+
return intent === 'fetch' ? record.fetchCount : record.subscribeCount
|
|
459
630
|
}
|
|
460
631
|
|
|
461
|
-
setOwnerIntentCount (
|
|
462
|
-
|
|
463
|
-
if (
|
|
464
|
-
else
|
|
632
|
+
setOwnerIntentCount (record, intent, count) {
|
|
633
|
+
if (!record) return
|
|
634
|
+
if (intent === 'fetch') record.fetchCount = Math.max(count, 0)
|
|
635
|
+
else record.subscribeCount = Math.max(count, 0)
|
|
636
|
+
this.syncOwnerMirror(record)
|
|
465
637
|
}
|
|
466
638
|
|
|
467
|
-
incrementOwnerIntent (
|
|
468
|
-
this.setOwnerIntentCount(
|
|
639
|
+
incrementOwnerIntent (record, intent) {
|
|
640
|
+
this.setOwnerIntentCount(record, intent, this.getOwnerIntentCount(record, intent) + 1)
|
|
469
641
|
}
|
|
470
642
|
|
|
471
|
-
getOwnerTotalCount (
|
|
472
|
-
|
|
643
|
+
getOwnerTotalCount (recordOrOwnerKey) {
|
|
644
|
+
const record = typeof recordOrOwnerKey === 'string'
|
|
645
|
+
? this.ownerRecords.get(recordOrOwnerKey)
|
|
646
|
+
: recordOrOwnerKey
|
|
647
|
+
if (!record) return 0
|
|
648
|
+
return record.fetchCount + record.subscribeCount
|
|
473
649
|
}
|
|
474
650
|
|
|
475
651
|
addOwnerMeta (ownerKey, hash, segments, rootId) {
|
|
476
|
-
|
|
477
|
-
this.
|
|
478
|
-
let ownerKeys = this.ownerKeysByHash.get(hash)
|
|
479
|
-
if (!ownerKeys) {
|
|
480
|
-
ownerKeys = new Set()
|
|
481
|
-
this.ownerKeysByHash.set(hash, ownerKeys)
|
|
482
|
-
}
|
|
483
|
-
ownerKeys.add(ownerKey)
|
|
652
|
+
const record = this.getOrCreateOwnerRecord(ownerKey, { hash, segments, rootId })
|
|
653
|
+
this.addOwnerToEntry(record)
|
|
484
654
|
}
|
|
485
655
|
|
|
486
656
|
removeOwnerMeta (ownerKey, hash) {
|
|
487
|
-
const
|
|
488
|
-
const knownHash = hash ??
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
657
|
+
const record = this.ownerRecords.get(ownerKey)
|
|
658
|
+
const knownHash = hash ?? record?.hash
|
|
659
|
+
if (record) {
|
|
660
|
+
this.removeOwnerFromEntry(record)
|
|
661
|
+
this.ownerRecords.delete(ownerKey)
|
|
662
|
+
}
|
|
492
663
|
if (!knownHash) return
|
|
493
|
-
const ownerKeys = this.
|
|
664
|
+
const ownerKeys = this.entries.get(knownHash)?.owners
|
|
494
665
|
if (!ownerKeys) return
|
|
495
666
|
ownerKeys.delete(ownerKey)
|
|
496
|
-
|
|
667
|
+
this.deleteEntryIfEmpty(knownHash)
|
|
497
668
|
}
|
|
498
669
|
|
|
499
670
|
getDesiredTransportMode (hash) {
|
|
500
|
-
const
|
|
671
|
+
const entry = this.entries.get(hash)
|
|
672
|
+
const ownerKeys = entry?.owners
|
|
501
673
|
if (!ownerKeys || ownerKeys.size === 0) return 'idle'
|
|
502
674
|
let hasFetchBackedOwner = false
|
|
503
675
|
for (const ownerKey of ownerKeys) {
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
const
|
|
676
|
+
const record = this.ownerRecords.get(ownerKey)
|
|
677
|
+
const subscribeCount = record?.subscribeCount || 0
|
|
678
|
+
const fetchCount = record?.fetchCount || 0
|
|
679
|
+
const rootId = record?.rootId
|
|
507
680
|
const subscribeMode = getRootTransportMode(rootId, 'subscribe')
|
|
508
681
|
if (subscribeCount > 0 && subscribeMode === 'subscribe') return 'subscribe'
|
|
509
682
|
if (fetchCount > 0 || (subscribeCount > 0 && subscribeMode === 'fetch')) {
|
|
@@ -513,21 +686,123 @@ export class DocSubscriptions {
|
|
|
513
686
|
return hasFetchBackedOwner ? 'fetch' : 'idle'
|
|
514
687
|
}
|
|
515
688
|
|
|
689
|
+
removeAllOwnersFromEntry (hash) {
|
|
690
|
+
const entry = this.entries.get(hash)
|
|
691
|
+
if (!entry) return
|
|
692
|
+
for (const ownerKey of Array.from(entry.owners)) {
|
|
693
|
+
const record = this.ownerRecords.get(ownerKey)
|
|
694
|
+
if (record) this.removeOwnerFromEntry(record)
|
|
695
|
+
else entry.owners.delete(ownerKey)
|
|
696
|
+
this.ownerRecords.delete(ownerKey)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async destroyTransportEntry (hash, runtime) {
|
|
701
|
+
const activeDoc = this.entries.get(hash)?.runtime || runtime
|
|
702
|
+
if (!activeDoc) {
|
|
703
|
+
const entry = this.entries.get(hash)
|
|
704
|
+
if (entry) {
|
|
705
|
+
entry.runtime = null
|
|
706
|
+
entry.mode = 'idle'
|
|
707
|
+
}
|
|
708
|
+
this.deleteEntryIfEmpty(hash)
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
if (activeDoc.activeTransportMode !== 'idle') {
|
|
712
|
+
await activeDoc.unsubscribe()
|
|
713
|
+
}
|
|
714
|
+
if (typeof activeDoc.hasPending === 'function' && activeDoc.hasPending()) {
|
|
715
|
+
if (typeof activeDoc.whenNothingPending === 'function') {
|
|
716
|
+
await new Promise(resolve => activeDoc.whenNothingPending(resolve))
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (typeof activeDoc.destroy === 'function') await activeDoc.destroy()
|
|
720
|
+
if (typeof activeDoc.dispose === 'function') activeDoc.dispose()
|
|
721
|
+
const finalEntry = this.entries.get(hash)
|
|
722
|
+
if (finalEntry && finalEntry.owners.size > 0) return
|
|
723
|
+
if (finalEntry) {
|
|
724
|
+
finalEntry.runtime = null
|
|
725
|
+
finalEntry.mode = 'idle'
|
|
726
|
+
}
|
|
727
|
+
this.deleteEntryIfEmpty(hash)
|
|
728
|
+
}
|
|
729
|
+
|
|
516
730
|
async destroyByOwnerKey (ownerKey, options = {}) {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const
|
|
731
|
+
const record = this.ownerRecords.get(ownerKey)
|
|
732
|
+
const hash = record?.hash ?? options.hash
|
|
733
|
+
if (!hash) return
|
|
734
|
+
const segments = record?.segments ?? parseDocHash(hash)
|
|
735
|
+
const ownerCount = this.getOwnerTotalCount(record || ownerKey)
|
|
521
736
|
if (!options.force && ownerCount > 0) return
|
|
522
737
|
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
738
|
+
const entry = this.entries.get(hash)
|
|
739
|
+
if (record) {
|
|
740
|
+
this.removeOwnerFromEntry(record)
|
|
741
|
+
this.ownerRecords.delete(ownerKey)
|
|
742
|
+
} else if (entry?.owners.has(ownerKey)) {
|
|
743
|
+
entry.owners.delete(ownerKey)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (!entry && !this.getRuntime(hash)) {
|
|
747
|
+
return
|
|
748
|
+
}
|
|
749
|
+
|
|
528
750
|
await this.reconcileTransport(hash)
|
|
529
|
-
|
|
530
|
-
|
|
751
|
+
const nextEntry = this.entries.get(hash)
|
|
752
|
+
const nextCount = nextEntry ? this.getEntryTotalCount(nextEntry) : (this.getTrackedCount(hash) || 0)
|
|
753
|
+
if (nextCount > 0) {
|
|
754
|
+
this.deleteEntryIfEmpty(hash)
|
|
755
|
+
return
|
|
756
|
+
}
|
|
757
|
+
if (options.force) {
|
|
758
|
+
await this.destroyTransportEntry(hash, nextEntry?.runtime || entry?.runtime)
|
|
759
|
+
return
|
|
760
|
+
}
|
|
761
|
+
await this.scheduleDestroy(segments, { force: false })
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
getRuntime (hash) {
|
|
765
|
+
return this.entries.get(hash)?.runtime
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
hasRuntime (hash) {
|
|
769
|
+
return !!this.getRuntime(hash)
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
getRuntimeCount () {
|
|
773
|
+
return countMapLike(this.entries, entry => !!entry.runtime)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
getTrackedCount (hash) {
|
|
777
|
+
const entry = this.entries.get(hash)
|
|
778
|
+
if (entry) {
|
|
779
|
+
const total = this.getEntryTotalCount(entry)
|
|
780
|
+
if (total > 0 || entry.pendingDestroy) return total
|
|
781
|
+
}
|
|
782
|
+
return undefined
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
getTrackedHashCountSize () {
|
|
786
|
+
return countMapLike(this.entries, entry => {
|
|
787
|
+
const total = this.getEntryTotalCount(entry)
|
|
788
|
+
return total > 0 || !!entry.pendingDestroy
|
|
789
|
+
})
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
getOwnerMeta (ownerKey) {
|
|
793
|
+
const record = this.ownerRecords.get(ownerKey)
|
|
794
|
+
if (!record) return undefined
|
|
795
|
+
return {
|
|
796
|
+
hash: record.hash,
|
|
797
|
+
segments: [...record.segments],
|
|
798
|
+
rootId: record.rootId
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
getOwnerKeys (hash) {
|
|
803
|
+
const owners = this.entries.get(hash)?.owners
|
|
804
|
+
if (!owners?.size) return undefined
|
|
805
|
+
return new Set(owners)
|
|
531
806
|
}
|
|
532
807
|
}
|
|
533
808
|
|
|
@@ -537,6 +812,10 @@ function hashDoc (segments) {
|
|
|
537
812
|
return JSON.stringify(segments)
|
|
538
813
|
}
|
|
539
814
|
|
|
815
|
+
function parseDocHash (hash) {
|
|
816
|
+
return JSON.parse(hash)
|
|
817
|
+
}
|
|
818
|
+
|
|
540
819
|
function getDocOwnerKey (rootId, hash) {
|
|
541
820
|
return JSON.stringify({ owner: [rootId, hash] })
|
|
542
821
|
}
|
|
@@ -611,3 +890,43 @@ function has (obj, key) {
|
|
|
611
890
|
const ERRORS = {
|
|
612
891
|
notSubscribed: $doc => Error('trying to unsubscribe when not subscribed. Doc: ' + $doc.path())
|
|
613
892
|
}
|
|
893
|
+
|
|
894
|
+
function createReadonlyMapView ({ get, has, size, keys }) {
|
|
895
|
+
return {
|
|
896
|
+
get,
|
|
897
|
+
has,
|
|
898
|
+
get size () {
|
|
899
|
+
return size()
|
|
900
|
+
},
|
|
901
|
+
* keys () {
|
|
902
|
+
yield * keys()
|
|
903
|
+
},
|
|
904
|
+
* values () {
|
|
905
|
+
for (const key of keys()) yield get(key)
|
|
906
|
+
},
|
|
907
|
+
* entries () {
|
|
908
|
+
for (const key of keys()) yield [key, get(key)]
|
|
909
|
+
},
|
|
910
|
+
[Symbol.iterator] () {
|
|
911
|
+
return this.entries()
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function countMapLike (iterableMap, predicate) {
|
|
917
|
+
let count = 0
|
|
918
|
+
for (const value of iterableMap.values()) {
|
|
919
|
+
if (predicate(value)) count++
|
|
920
|
+
}
|
|
921
|
+
return count
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function * filterMapKeys (iterableMap, predicate) {
|
|
925
|
+
for (const [key, value] of iterableMap.entries()) {
|
|
926
|
+
if (predicate(value)) yield key
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function * getTrackedHashes (entries) {
|
|
931
|
+
yield * entries.keys()
|
|
932
|
+
}
|