@tanstack/db 0.0.24 → 0.0.25
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/dist/cjs/collection.cjs +60 -19
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +27 -6
- package/dist/cjs/local-only.cjs +2 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-storage.cjs +2 -1
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +84 -11
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/proxy.d.cts +8 -0
- package/dist/cjs/query/live-query-collection.cjs +2 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/types.d.cts +1 -0
- package/dist/esm/collection.d.ts +27 -6
- package/dist/esm/collection.js +60 -19
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/local-only.js +2 -1
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.js +2 -1
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/proxy.d.ts +8 -0
- package/dist/esm/proxy.js +84 -11
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/live-query-collection.js +2 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/collection.ts +75 -26
- package/src/local-only.ts +4 -1
- package/src/local-storage.ts +4 -1
- package/src/proxy.ts +128 -24
- package/src/query/live-query-collection.ts +3 -1
- package/src/types.ts +1 -0
package/dist/esm/types.d.ts
CHANGED
|
@@ -120,6 +120,7 @@ export interface SyncConfig<T extends object = Record<string, unknown>, TKey ext
|
|
|
120
120
|
begin: () => void;
|
|
121
121
|
write: (message: Omit<ChangeMessage<T>, `key`>) => void;
|
|
122
122
|
commit: () => void;
|
|
123
|
+
markReady: () => void;
|
|
123
124
|
}) => void;
|
|
124
125
|
/**
|
|
125
126
|
* Get the sync metadata for insert operations
|
package/package.json
CHANGED
package/src/collection.ts
CHANGED
|
@@ -229,8 +229,9 @@ export class CollectionImpl<
|
|
|
229
229
|
private hasReceivedFirstCommit = false
|
|
230
230
|
private isCommittingSyncTransactions = false
|
|
231
231
|
|
|
232
|
-
// Array to store one-time
|
|
233
|
-
private
|
|
232
|
+
// Array to store one-time ready listeners
|
|
233
|
+
private onFirstReadyCallbacks: Array<() => void> = []
|
|
234
|
+
private hasBeenReady = false
|
|
234
235
|
|
|
235
236
|
// Event batching for preventing duplicate emissions during transaction flows
|
|
236
237
|
private batchedEvents: Array<ChangeMessage<T, TKey>> = []
|
|
@@ -244,17 +245,66 @@ export class CollectionImpl<
|
|
|
244
245
|
private syncCleanupFn: (() => void) | null = null
|
|
245
246
|
|
|
246
247
|
/**
|
|
247
|
-
* Register a callback to be executed
|
|
248
|
+
* Register a callback to be executed when the collection first becomes ready
|
|
248
249
|
* Useful for preloading collections
|
|
249
|
-
* @param callback Function to call
|
|
250
|
+
* @param callback Function to call when the collection first becomes ready
|
|
250
251
|
* @example
|
|
251
|
-
* collection.
|
|
252
|
-
* console.log('Collection
|
|
252
|
+
* collection.onFirstReady(() => {
|
|
253
|
+
* console.log('Collection is ready for the first time')
|
|
253
254
|
* // Safe to access collection.state now
|
|
254
255
|
* })
|
|
255
256
|
*/
|
|
256
|
-
public
|
|
257
|
-
|
|
257
|
+
public onFirstReady(callback: () => void): void {
|
|
258
|
+
// If already ready, call immediately
|
|
259
|
+
if (this.hasBeenReady) {
|
|
260
|
+
callback()
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.onFirstReadyCallbacks.push(callback)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if the collection is ready for use
|
|
269
|
+
* Returns true if the collection has been marked as ready by its sync implementation
|
|
270
|
+
* @returns true if the collection is ready, false otherwise
|
|
271
|
+
* @example
|
|
272
|
+
* if (collection.isReady()) {
|
|
273
|
+
* console.log('Collection is ready, data is available')
|
|
274
|
+
* // Safe to access collection.state
|
|
275
|
+
* } else {
|
|
276
|
+
* console.log('Collection is still loading')
|
|
277
|
+
* }
|
|
278
|
+
*/
|
|
279
|
+
public isReady(): boolean {
|
|
280
|
+
return this._status === `ready`
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Mark the collection as ready for use
|
|
285
|
+
* This is called by sync implementations to explicitly signal that the collection is ready,
|
|
286
|
+
* providing a more intuitive alternative to using commits for readiness signaling
|
|
287
|
+
* @private - Should only be called by sync implementations
|
|
288
|
+
*/
|
|
289
|
+
private markReady(): void {
|
|
290
|
+
// Can transition to ready from loading or initialCommit states
|
|
291
|
+
if (this._status === `loading` || this._status === `initialCommit`) {
|
|
292
|
+
this.setStatus(`ready`)
|
|
293
|
+
|
|
294
|
+
// Call any registered first ready callbacks (only on first time becoming ready)
|
|
295
|
+
if (!this.hasBeenReady) {
|
|
296
|
+
this.hasBeenReady = true
|
|
297
|
+
|
|
298
|
+
// Also mark as having received first commit for backwards compatibility
|
|
299
|
+
if (!this.hasReceivedFirstCommit) {
|
|
300
|
+
this.hasReceivedFirstCommit = true
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const callbacks = [...this.onFirstReadyCallbacks]
|
|
304
|
+
this.onFirstReadyCallbacks = []
|
|
305
|
+
callbacks.forEach((callback) => callback())
|
|
306
|
+
}
|
|
307
|
+
}
|
|
258
308
|
}
|
|
259
309
|
|
|
260
310
|
public id = ``
|
|
@@ -302,7 +352,7 @@ export class CollectionImpl<
|
|
|
302
352
|
Array<CollectionStatus>
|
|
303
353
|
> = {
|
|
304
354
|
idle: [`loading`, `error`, `cleaned-up`],
|
|
305
|
-
loading: [`initialCommit`, `error`, `cleaned-up`],
|
|
355
|
+
loading: [`initialCommit`, `ready`, `error`, `cleaned-up`],
|
|
306
356
|
initialCommit: [`ready`, `error`, `cleaned-up`],
|
|
307
357
|
ready: [`cleaned-up`, `error`],
|
|
308
358
|
error: [`cleaned-up`, `idle`],
|
|
@@ -455,11 +505,9 @@ export class CollectionImpl<
|
|
|
455
505
|
}
|
|
456
506
|
|
|
457
507
|
this.commitPendingTransactions()
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
this.setStatus(`ready`)
|
|
462
|
-
}
|
|
508
|
+
},
|
|
509
|
+
markReady: () => {
|
|
510
|
+
this.markReady()
|
|
463
511
|
},
|
|
464
512
|
})
|
|
465
513
|
|
|
@@ -492,7 +540,7 @@ export class CollectionImpl<
|
|
|
492
540
|
}
|
|
493
541
|
|
|
494
542
|
// Register callback BEFORE starting sync to avoid race condition
|
|
495
|
-
this.
|
|
543
|
+
this.onFirstReady(() => {
|
|
496
544
|
resolve()
|
|
497
545
|
})
|
|
498
546
|
|
|
@@ -555,7 +603,8 @@ export class CollectionImpl<
|
|
|
555
603
|
this.pendingSyncedTransactions = []
|
|
556
604
|
this.syncedKeys.clear()
|
|
557
605
|
this.hasReceivedFirstCommit = false
|
|
558
|
-
this.
|
|
606
|
+
this.hasBeenReady = false
|
|
607
|
+
this.onFirstReadyCallbacks = []
|
|
559
608
|
this.preloadPromise = null
|
|
560
609
|
this.batchedEvents = []
|
|
561
610
|
this.shouldBatchEvents = false
|
|
@@ -1184,8 +1233,8 @@ export class CollectionImpl<
|
|
|
1184
1233
|
// Call any registered one-time commit listeners
|
|
1185
1234
|
if (!this.hasReceivedFirstCommit) {
|
|
1186
1235
|
this.hasReceivedFirstCommit = true
|
|
1187
|
-
const callbacks = [...this.
|
|
1188
|
-
this.
|
|
1236
|
+
const callbacks = [...this.onFirstReadyCallbacks]
|
|
1237
|
+
this.onFirstReadyCallbacks = []
|
|
1189
1238
|
callbacks.forEach((callback) => callback())
|
|
1190
1239
|
}
|
|
1191
1240
|
}
|
|
@@ -1812,14 +1861,14 @@ export class CollectionImpl<
|
|
|
1812
1861
|
* @returns Promise that resolves to a Map containing all items in the collection
|
|
1813
1862
|
*/
|
|
1814
1863
|
stateWhenReady(): Promise<Map<TKey, T>> {
|
|
1815
|
-
// If we already have data or
|
|
1816
|
-
if (this.size > 0 || this.
|
|
1864
|
+
// If we already have data or collection is ready, resolve immediately
|
|
1865
|
+
if (this.size > 0 || this.isReady()) {
|
|
1817
1866
|
return Promise.resolve(this.state)
|
|
1818
1867
|
}
|
|
1819
1868
|
|
|
1820
|
-
// Otherwise, wait for the
|
|
1869
|
+
// Otherwise, wait for the collection to be ready
|
|
1821
1870
|
return new Promise<Map<TKey, T>>((resolve) => {
|
|
1822
|
-
this.
|
|
1871
|
+
this.onFirstReady(() => {
|
|
1823
1872
|
resolve(this.state)
|
|
1824
1873
|
})
|
|
1825
1874
|
})
|
|
@@ -1841,14 +1890,14 @@ export class CollectionImpl<
|
|
|
1841
1890
|
* @returns Promise that resolves to an Array containing all items in the collection
|
|
1842
1891
|
*/
|
|
1843
1892
|
toArrayWhenReady(): Promise<Array<T>> {
|
|
1844
|
-
// If we already have data or
|
|
1845
|
-
if (this.size > 0 || this.
|
|
1893
|
+
// If we already have data or collection is ready, resolve immediately
|
|
1894
|
+
if (this.size > 0 || this.isReady()) {
|
|
1846
1895
|
return Promise.resolve(this.toArray)
|
|
1847
1896
|
}
|
|
1848
1897
|
|
|
1849
|
-
// Otherwise, wait for the
|
|
1898
|
+
// Otherwise, wait for the collection to be ready
|
|
1850
1899
|
return new Promise<Array<T>>((resolve) => {
|
|
1851
|
-
this.
|
|
1900
|
+
this.onFirstReady(() => {
|
|
1852
1901
|
resolve(this.toArray)
|
|
1853
1902
|
})
|
|
1854
1903
|
})
|
package/src/local-only.ts
CHANGED
|
@@ -240,7 +240,7 @@ function createLocalOnlySync<T extends object, TKey extends string | number>(
|
|
|
240
240
|
* @returns Unsubscribe function (empty since no ongoing sync is needed)
|
|
241
241
|
*/
|
|
242
242
|
sync: (params) => {
|
|
243
|
-
const { begin, write, commit } = params
|
|
243
|
+
const { begin, write, commit, markReady } = params
|
|
244
244
|
|
|
245
245
|
// Capture sync functions for later use by confirmOperationsSync
|
|
246
246
|
syncBegin = begin
|
|
@@ -259,6 +259,9 @@ function createLocalOnlySync<T extends object, TKey extends string | number>(
|
|
|
259
259
|
commit()
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
// Mark collection as ready since local-only collections are immediately ready
|
|
263
|
+
markReady()
|
|
264
|
+
|
|
262
265
|
// Return empty unsubscribe function - no ongoing sync needed
|
|
263
266
|
return () => {}
|
|
264
267
|
},
|
package/src/local-storage.ts
CHANGED
|
@@ -586,7 +586,7 @@ function createLocalStorageSync<T extends object>(
|
|
|
586
586
|
|
|
587
587
|
const syncConfig: SyncConfig<T> & { manualTrigger?: () => void } = {
|
|
588
588
|
sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {
|
|
589
|
-
const { begin, write, commit } = params
|
|
589
|
+
const { begin, write, commit, markReady } = params
|
|
590
590
|
|
|
591
591
|
// Store sync params for later use
|
|
592
592
|
syncParams = params
|
|
@@ -608,6 +608,9 @@ function createLocalStorageSync<T extends object>(
|
|
|
608
608
|
lastKnownData.set(key, storedItem)
|
|
609
609
|
})
|
|
610
610
|
|
|
611
|
+
// Mark collection as ready after initial load
|
|
612
|
+
markReady()
|
|
613
|
+
|
|
611
614
|
// Listen for storage events from other tabs
|
|
612
615
|
const handleStorageEvent = (event: StorageEvent) => {
|
|
613
616
|
// Only respond to changes to our specific key and from our storage
|
package/src/proxy.ts
CHANGED
|
@@ -40,10 +40,21 @@ interface ChangeTracker<T extends object> {
|
|
|
40
40
|
copy_: T
|
|
41
41
|
proxyCount: number
|
|
42
42
|
assigned_: Record<string | symbol, boolean>
|
|
43
|
-
parent?:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
parent?:
|
|
44
|
+
| {
|
|
45
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
46
|
+
prop: string | symbol
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
50
|
+
prop: string | symbol
|
|
51
|
+
updateMap: (newValue: unknown) => void
|
|
52
|
+
}
|
|
53
|
+
| {
|
|
54
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
55
|
+
prop: unknown
|
|
56
|
+
updateSet: (newValue: unknown) => void
|
|
57
|
+
}
|
|
47
58
|
target: T
|
|
48
59
|
}
|
|
49
60
|
|
|
@@ -330,9 +341,18 @@ export function createChangeProxy<
|
|
|
330
341
|
if (state.parent) {
|
|
331
342
|
debugLog(`propagating change to parent`)
|
|
332
343
|
|
|
333
|
-
//
|
|
334
|
-
state.parent
|
|
335
|
-
|
|
344
|
+
// Check if this is a special Map parent with updateMap function
|
|
345
|
+
if (`updateMap` in state.parent) {
|
|
346
|
+
// Use the special updateMap function for Maps
|
|
347
|
+
state.parent.updateMap(state.copy_)
|
|
348
|
+
} else if (`updateSet` in state.parent) {
|
|
349
|
+
// Use the special updateSet function for Sets
|
|
350
|
+
state.parent.updateSet(state.copy_)
|
|
351
|
+
} else {
|
|
352
|
+
// Update parent's copy with this object's current state
|
|
353
|
+
state.parent.tracker.copy_[state.parent.prop] = state.copy_
|
|
354
|
+
state.parent.tracker.assigned_[state.parent.prop] = true
|
|
355
|
+
}
|
|
336
356
|
|
|
337
357
|
// Mark parent as changed
|
|
338
358
|
markChanged(state.parent.tracker)
|
|
@@ -410,7 +430,7 @@ export function createChangeProxy<
|
|
|
410
430
|
// Update parent status based on child changes
|
|
411
431
|
function checkParentStatus(
|
|
412
432
|
parentState: ChangeTracker<Record<string | symbol, unknown>>,
|
|
413
|
-
childProp: string | symbol
|
|
433
|
+
childProp: string | symbol | unknown
|
|
414
434
|
) {
|
|
415
435
|
debugLog(`checkParentStatus called for child prop:`, childProp)
|
|
416
436
|
|
|
@@ -565,6 +585,28 @@ export function createChangeProxy<
|
|
|
565
585
|
// to track changes when the values are accessed and potentially modified
|
|
566
586
|
const originalIterator = result
|
|
567
587
|
|
|
588
|
+
// For values() iterator on Maps, we need to create a value-to-key mapping
|
|
589
|
+
const valueToKeyMap = new Map()
|
|
590
|
+
if (methodName === `values` && ptarget instanceof Map) {
|
|
591
|
+
// Build a mapping from value to key for reverse lookup
|
|
592
|
+
// Use the copy_ (which is the current state) to build the mapping
|
|
593
|
+
for (const [
|
|
594
|
+
key,
|
|
595
|
+
mapValue,
|
|
596
|
+
] of changeTracker.copy_.entries()) {
|
|
597
|
+
valueToKeyMap.set(mapValue, key)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// For Set iterators, we need to create an original-to-modified mapping
|
|
602
|
+
const originalToModifiedMap = new Map()
|
|
603
|
+
if (ptarget instanceof Set) {
|
|
604
|
+
// Initialize with original values
|
|
605
|
+
for (const setValue of changeTracker.copy_.values()) {
|
|
606
|
+
originalToModifiedMap.set(setValue, setValue)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
568
610
|
// Create a proxy for the iterator that will mark changes when next() is called
|
|
569
611
|
return {
|
|
570
612
|
next() {
|
|
@@ -587,15 +629,25 @@ export function createChangeProxy<
|
|
|
587
629
|
nextResult.value[1] &&
|
|
588
630
|
typeof nextResult.value[1] === `object`
|
|
589
631
|
) {
|
|
632
|
+
const mapKey = nextResult.value[0]
|
|
633
|
+
// Create a special parent tracker that knows how to update the Map
|
|
634
|
+
const mapParent = {
|
|
635
|
+
tracker: changeTracker,
|
|
636
|
+
prop: mapKey,
|
|
637
|
+
updateMap: (newValue: unknown) => {
|
|
638
|
+
// Update the Map in the copy
|
|
639
|
+
if (changeTracker.copy_ instanceof Map) {
|
|
640
|
+
changeTracker.copy_.set(mapKey, newValue)
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
}
|
|
644
|
+
|
|
590
645
|
// Create a proxy for the value and replace it in the result
|
|
591
646
|
const { proxy: valueProxy } =
|
|
592
|
-
memoizedCreateChangeProxy(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
? nextResult.value[0]
|
|
597
|
-
: String(nextResult.value[0]),
|
|
598
|
-
})
|
|
647
|
+
memoizedCreateChangeProxy(
|
|
648
|
+
nextResult.value[1],
|
|
649
|
+
mapParent
|
|
650
|
+
)
|
|
599
651
|
nextResult.value[1] = valueProxy
|
|
600
652
|
}
|
|
601
653
|
} else if (
|
|
@@ -608,16 +660,68 @@ export function createChangeProxy<
|
|
|
608
660
|
typeof nextResult.value === `object` &&
|
|
609
661
|
nextResult.value !== null
|
|
610
662
|
) {
|
|
611
|
-
// For
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
663
|
+
// For Map values(), try to find the key using our mapping
|
|
664
|
+
if (
|
|
665
|
+
methodName === `values` &&
|
|
666
|
+
ptarget instanceof Map
|
|
667
|
+
) {
|
|
668
|
+
const mapKey = valueToKeyMap.get(nextResult.value)
|
|
669
|
+
if (mapKey !== undefined) {
|
|
670
|
+
// Create a special parent tracker for this Map value
|
|
671
|
+
const mapParent = {
|
|
672
|
+
tracker: changeTracker,
|
|
673
|
+
prop: mapKey,
|
|
674
|
+
updateMap: (newValue: unknown) => {
|
|
675
|
+
// Update the Map in the copy
|
|
676
|
+
if (changeTracker.copy_ instanceof Map) {
|
|
677
|
+
changeTracker.copy_.set(mapKey, newValue)
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const { proxy: valueProxy } =
|
|
683
|
+
memoizedCreateChangeProxy(
|
|
684
|
+
nextResult.value,
|
|
685
|
+
mapParent
|
|
686
|
+
)
|
|
687
|
+
nextResult.value = valueProxy
|
|
688
|
+
}
|
|
689
|
+
} else if (ptarget instanceof Set) {
|
|
690
|
+
// For Set, we need to track modifications and update the Set accordingly
|
|
691
|
+
const setOriginalValue = nextResult.value
|
|
692
|
+
const setParent = {
|
|
617
693
|
tracker: changeTracker,
|
|
618
|
-
prop:
|
|
619
|
-
|
|
620
|
-
|
|
694
|
+
prop: setOriginalValue, // Use the original value as the prop
|
|
695
|
+
updateSet: (newValue: unknown) => {
|
|
696
|
+
// Update the Set in the copy by removing old value and adding new one
|
|
697
|
+
if (changeTracker.copy_ instanceof Set) {
|
|
698
|
+
changeTracker.copy_.delete(setOriginalValue)
|
|
699
|
+
changeTracker.copy_.add(newValue)
|
|
700
|
+
// Update our mapping for future iterations
|
|
701
|
+
originalToModifiedMap.set(
|
|
702
|
+
setOriginalValue,
|
|
703
|
+
newValue
|
|
704
|
+
)
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const { proxy: valueProxy } =
|
|
710
|
+
memoizedCreateChangeProxy(
|
|
711
|
+
nextResult.value,
|
|
712
|
+
setParent
|
|
713
|
+
)
|
|
714
|
+
nextResult.value = valueProxy
|
|
715
|
+
} else {
|
|
716
|
+
// For other cases, use a symbol as a placeholder
|
|
717
|
+
const tempKey = Symbol(`iterator-value`)
|
|
718
|
+
const { proxy: valueProxy } =
|
|
719
|
+
memoizedCreateChangeProxy(nextResult.value, {
|
|
720
|
+
tracker: changeTracker,
|
|
721
|
+
prop: tempKey,
|
|
722
|
+
})
|
|
723
|
+
nextResult.value = valueProxy
|
|
724
|
+
}
|
|
621
725
|
}
|
|
622
726
|
}
|
|
623
727
|
}
|
|
@@ -203,7 +203,7 @@ export function liveQueryCollectionOptions<
|
|
|
203
203
|
// Create the sync configuration
|
|
204
204
|
const sync: SyncConfig<TResult> = {
|
|
205
205
|
rowUpdateMode: `full`,
|
|
206
|
-
sync: ({ begin, write, commit, collection: theCollection }) => {
|
|
206
|
+
sync: ({ begin, write, commit, markReady, collection: theCollection }) => {
|
|
207
207
|
const { graph, inputs, pipeline } = maybeCompileBasePipeline()
|
|
208
208
|
let messagesCount = 0
|
|
209
209
|
pipeline.pipe(
|
|
@@ -295,6 +295,8 @@ export function liveQueryCollectionOptions<
|
|
|
295
295
|
begin()
|
|
296
296
|
commit()
|
|
297
297
|
}
|
|
298
|
+
// Mark the collection as ready after the first successful run
|
|
299
|
+
markReady()
|
|
298
300
|
}
|
|
299
301
|
}
|
|
300
302
|
|