cry-synced-db-client 0.1.71 → 0.1.73
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/index.js
CHANGED
|
@@ -2155,29 +2155,49 @@ class SyncEngine {
|
|
|
2155
2155
|
}
|
|
2156
2156
|
this.callOnFindNewerManyCall(syncSpecs, calledFrom);
|
|
2157
2157
|
const findNewerManyStartTime = Date.now();
|
|
2158
|
-
|
|
2158
|
+
const allUpdatedIds = {};
|
|
2159
|
+
const collectionState = new Map;
|
|
2160
|
+
for (const [name] of configMap) {
|
|
2161
|
+
collectionState.set(name, {
|
|
2162
|
+
maxTs: undefined,
|
|
2163
|
+
conflicts: 0,
|
|
2164
|
+
updatedIds: [],
|
|
2165
|
+
receivedCount: 0
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2159
2168
|
try {
|
|
2160
|
-
|
|
2161
|
-
|
|
2169
|
+
await this.deps.withSyncTimeout(this.restInterface.findNewerManyStream(syncSpecs, async (collection, items) => {
|
|
2170
|
+
const config = configMap.get(collection);
|
|
2171
|
+
if (!config)
|
|
2172
|
+
return;
|
|
2173
|
+
const state = collectionState.get(collection);
|
|
2174
|
+
state.receivedCount += items.length;
|
|
2175
|
+
const stats = await this.processIncomingServerData(collection, config, items);
|
|
2176
|
+
state.conflicts += stats.conflictsResolved;
|
|
2177
|
+
if (stats.maxTs) {
|
|
2178
|
+
if (!state.maxTs || this.compareTimestamps(stats.maxTs, state.maxTs) > 0) {
|
|
2179
|
+
state.maxTs = stats.maxTs;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
state.updatedIds.push(...stats.updatedIds);
|
|
2183
|
+
}), "findNewerManyStream");
|
|
2184
|
+
for (const [name, state] of collectionState) {
|
|
2185
|
+
receivedCount += state.receivedCount;
|
|
2186
|
+
conflictsResolved += state.conflicts;
|
|
2187
|
+
collectionStats[name] = {
|
|
2188
|
+
receivedCount: state.receivedCount,
|
|
2189
|
+
sentCount: 0,
|
|
2190
|
+
receivedItems: []
|
|
2191
|
+
};
|
|
2192
|
+
if (state.updatedIds.length > 0) {
|
|
2193
|
+
allUpdatedIds[name] = state.updatedIds;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
this.callOnFindNewerManyResult(syncSpecs, {}, findNewerManyStartTime, true, calledFrom);
|
|
2162
2197
|
} catch (err) {
|
|
2163
2198
|
this.callOnFindNewerManyResult(syncSpecs, {}, findNewerManyStartTime, false, calledFrom, err);
|
|
2164
2199
|
throw err;
|
|
2165
2200
|
}
|
|
2166
|
-
const allUpdatedIds = {};
|
|
2167
|
-
for (const [collectionName, config] of configMap) {
|
|
2168
|
-
const serverData = allServerData[collectionName] || [];
|
|
2169
|
-
receivedCount += serverData.length;
|
|
2170
|
-
collectionStats[collectionName] = {
|
|
2171
|
-
receivedCount: serverData.length,
|
|
2172
|
-
sentCount: 0,
|
|
2173
|
-
receivedItems: serverData
|
|
2174
|
-
};
|
|
2175
|
-
const stats = await this.processIncomingServerData(collectionName, config, serverData);
|
|
2176
|
-
conflictsResolved += stats.conflictsResolved;
|
|
2177
|
-
if (stats.updatedIds.length > 0) {
|
|
2178
|
-
allUpdatedIds[collectionName] = stats.updatedIds;
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
2201
|
if (Object.keys(allUpdatedIds).length > 0) {
|
|
2182
2202
|
this.deps.broadcastUpdates(allUpdatedIds);
|
|
2183
2203
|
}
|
|
@@ -2420,60 +2440,69 @@ class SyncEngine {
|
|
|
2420
2440
|
}
|
|
2421
2441
|
return { updatedIds: result.updatedIds };
|
|
2422
2442
|
}
|
|
2443
|
+
static SYNC_BATCH_SIZE = 200;
|
|
2423
2444
|
async processIncomingServerData(collectionName, config, serverData) {
|
|
2424
2445
|
if (serverData.length === 0) {
|
|
2425
2446
|
return { conflictsResolved: 0, maxTs: undefined, updatedIds: [] };
|
|
2426
2447
|
}
|
|
2427
2448
|
let maxTs;
|
|
2428
2449
|
let conflictsResolved = 0;
|
|
2429
|
-
const
|
|
2430
|
-
const
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
const
|
|
2437
|
-
const
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2450
|
+
const allUpdatedIds = [];
|
|
2451
|
+
const BATCH = SyncEngine.SYNC_BATCH_SIZE;
|
|
2452
|
+
for (let offset = 0;offset < serverData.length; offset += BATCH) {
|
|
2453
|
+
const chunk = serverData.slice(offset, offset + BATCH);
|
|
2454
|
+
const chunkIds = chunk.map((item) => item._id);
|
|
2455
|
+
const localItems = await this.dexieDb.getByIds(collectionName, chunkIds);
|
|
2456
|
+
const dirtyChangesMap = await this.dexieDb.getDirtyChangesBatch(collectionName, chunkIds);
|
|
2457
|
+
const dexieBatch = [];
|
|
2458
|
+
const inMemSaveBatch = [];
|
|
2459
|
+
const inMemDeleteIds = [];
|
|
2460
|
+
for (let i = 0;i < chunk.length; i++) {
|
|
2461
|
+
const serverItem = chunk[i];
|
|
2462
|
+
const localItem = localItems[i];
|
|
2463
|
+
const dirtyChange = dirtyChangesMap.get(String(serverItem._id));
|
|
2464
|
+
if (serverItem._ts) {
|
|
2465
|
+
if (!maxTs || this.compareTimestamps(serverItem._ts, maxTs) > 0) {
|
|
2466
|
+
maxTs = serverItem._ts;
|
|
2467
|
+
}
|
|
2442
2468
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2469
|
+
if (localItem) {
|
|
2470
|
+
if (dirtyChange) {
|
|
2471
|
+
conflictsResolved++;
|
|
2472
|
+
const resolved = this.resolveCollectionConflict(collectionName, config, localItem, serverItem, "sync");
|
|
2473
|
+
dexieBatch.push(resolved);
|
|
2474
|
+
if (!resolved._deleted && !resolved._archived) {
|
|
2475
|
+
inMemSaveBatch.push(resolved);
|
|
2476
|
+
} else {
|
|
2477
|
+
inMemDeleteIds.push(serverItem._id);
|
|
2478
|
+
}
|
|
2451
2479
|
} else {
|
|
2452
|
-
|
|
2480
|
+
dexieBatch.push(serverItem);
|
|
2481
|
+
if (!serverItem._deleted && !serverItem._archived) {
|
|
2482
|
+
inMemSaveBatch.push(serverItem);
|
|
2483
|
+
} else {
|
|
2484
|
+
inMemDeleteIds.push(serverItem._id);
|
|
2485
|
+
}
|
|
2453
2486
|
}
|
|
2454
2487
|
} else {
|
|
2455
2488
|
dexieBatch.push(serverItem);
|
|
2456
2489
|
if (!serverItem._deleted && !serverItem._archived) {
|
|
2457
2490
|
inMemSaveBatch.push(serverItem);
|
|
2458
|
-
} else {
|
|
2459
|
-
inMemDeleteIds.push(serverItem._id);
|
|
2460
2491
|
}
|
|
2461
2492
|
}
|
|
2462
|
-
} else {
|
|
2463
|
-
dexieBatch.push(serverItem);
|
|
2464
|
-
if (!serverItem._deleted && !serverItem._archived) {
|
|
2465
|
-
inMemSaveBatch.push(serverItem);
|
|
2466
|
-
}
|
|
2467
2493
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2494
|
+
if (dexieBatch.length > 0) {
|
|
2495
|
+
await this.dexieDb.saveMany(collectionName, dexieBatch);
|
|
2496
|
+
}
|
|
2497
|
+
if (inMemSaveBatch.length > 0) {
|
|
2498
|
+
this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert");
|
|
2499
|
+
}
|
|
2500
|
+
if (inMemDeleteIds.length > 0) {
|
|
2501
|
+
this.deps.writeToInMemBatch(collectionName, inMemDeleteIds.map((id) => ({ _id: id })), "delete");
|
|
2502
|
+
}
|
|
2503
|
+
for (const id of chunkIds) {
|
|
2504
|
+
allUpdatedIds.push(String(id));
|
|
2505
|
+
}
|
|
2477
2506
|
}
|
|
2478
2507
|
if (maxTs) {
|
|
2479
2508
|
await this.dexieDb.setSyncMeta(collectionName, maxTs);
|
|
@@ -2483,8 +2512,7 @@ class SyncEngine {
|
|
|
2483
2512
|
lastSyncTs: maxTs
|
|
2484
2513
|
});
|
|
2485
2514
|
}
|
|
2486
|
-
|
|
2487
|
-
return { conflictsResolved, maxTs, updatedIds };
|
|
2515
|
+
return { conflictsResolved, maxTs, updatedIds: allUpdatedIds };
|
|
2488
2516
|
}
|
|
2489
2517
|
compareTimestamps(a, b) {
|
|
2490
2518
|
const aT = typeof a === "object" && "t" in a ? a.t : 0;
|
|
@@ -3251,8 +3279,15 @@ class SyncedDb {
|
|
|
3251
3279
|
await this.pendingChanges.recoverPendingWrites();
|
|
3252
3280
|
for (const [name] of this.collections) {
|
|
3253
3281
|
const data = await this.dexieDb.getAll(name);
|
|
3254
|
-
|
|
3255
|
-
|
|
3282
|
+
let writeIdx = 0;
|
|
3283
|
+
for (let i = 0;i < data.length; i++) {
|
|
3284
|
+
const item = data[i];
|
|
3285
|
+
if (!item._deleted && !item._archived) {
|
|
3286
|
+
data[writeIdx++] = item;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
data.length = writeIdx;
|
|
3290
|
+
this.inMemManager.initCollection(name, data);
|
|
3256
3291
|
const meta = await this.dexieDb.getSyncMeta(name);
|
|
3257
3292
|
if (meta) {
|
|
3258
3293
|
this.syncMetaCache.set(name, meta);
|
|
@@ -6210,6 +6245,12 @@ var pack2 = (x) => packr.pack(preprocessForPack(x));
|
|
|
6210
6245
|
var unpack2 = (x) => unpackr.unpack(x);
|
|
6211
6246
|
var DEFAULT_TIMEOUT = 5000;
|
|
6212
6247
|
var DEFAULT_PROGRESS_CHUNK_SIZE = 16 * 1024;
|
|
6248
|
+
function concatUint8(a, b) {
|
|
6249
|
+
const result = new Uint8Array(a.length + b.length);
|
|
6250
|
+
result.set(a);
|
|
6251
|
+
result.set(b, a.length);
|
|
6252
|
+
return result;
|
|
6253
|
+
}
|
|
6213
6254
|
|
|
6214
6255
|
class RestProxy {
|
|
6215
6256
|
endpoint;
|
|
@@ -6370,6 +6411,129 @@ class RestProxy {
|
|
|
6370
6411
|
async findNewerMany(spec) {
|
|
6371
6412
|
return await this.restCall("findNewerMany", { spec });
|
|
6372
6413
|
}
|
|
6414
|
+
async findNewerManyStream(spec, onChunk, options) {
|
|
6415
|
+
const connectTimeout = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
6416
|
+
const activityTimeout = options?.activityTimeoutMs ?? 30000;
|
|
6417
|
+
const externalSignal = options?.signal ?? this.globalSignal;
|
|
6418
|
+
const startTime = this.timeRequests ? performance.now() : 0;
|
|
6419
|
+
const data = {
|
|
6420
|
+
payload: {
|
|
6421
|
+
db: this.tenant,
|
|
6422
|
+
operation: "findNewerMany",
|
|
6423
|
+
spec
|
|
6424
|
+
},
|
|
6425
|
+
audit: {
|
|
6426
|
+
tenant: this.tenant,
|
|
6427
|
+
user: this.audit.user,
|
|
6428
|
+
naprava: this.audit.device
|
|
6429
|
+
}
|
|
6430
|
+
};
|
|
6431
|
+
const body = pack2(data);
|
|
6432
|
+
const requestUrl = this.apiKey ? `${this.endpoint}?apikey=${this.apiKey}&stream=1` : `${this.endpoint}?stream=1`;
|
|
6433
|
+
const controller = new AbortController;
|
|
6434
|
+
let timeoutId = setTimeout(() => controller.abort(), connectTimeout);
|
|
6435
|
+
const combinedSignal = externalSignal ? this.combineSignals(externalSignal, controller.signal) : controller.signal;
|
|
6436
|
+
try {
|
|
6437
|
+
const response = await fetch(requestUrl, {
|
|
6438
|
+
method: "POST",
|
|
6439
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
6440
|
+
body,
|
|
6441
|
+
signal: combinedSignal
|
|
6442
|
+
});
|
|
6443
|
+
clearTimeout(timeoutId);
|
|
6444
|
+
timeoutId = undefined;
|
|
6445
|
+
if (!response.ok) {
|
|
6446
|
+
const errorText = await response.text();
|
|
6447
|
+
throw new Error(`REST call failed: ${response.status} - ${errorText}`);
|
|
6448
|
+
}
|
|
6449
|
+
const resetActivity = () => {
|
|
6450
|
+
if (timeoutId !== undefined)
|
|
6451
|
+
clearTimeout(timeoutId);
|
|
6452
|
+
timeoutId = setTimeout(() => controller.abort(), activityTimeout);
|
|
6453
|
+
};
|
|
6454
|
+
resetActivity();
|
|
6455
|
+
await this.parseStreamingResponse(response, onChunk, resetActivity);
|
|
6456
|
+
if (timeoutId !== undefined)
|
|
6457
|
+
clearTimeout(timeoutId);
|
|
6458
|
+
timeoutId = undefined;
|
|
6459
|
+
if (this.timeRequests) {
|
|
6460
|
+
const elapsed = performance.now() - startTime;
|
|
6461
|
+
this._lastRequestMs = elapsed;
|
|
6462
|
+
this._totalRequestMs += elapsed;
|
|
6463
|
+
this._requestCount++;
|
|
6464
|
+
if (this.timeRequestsPrint) {
|
|
6465
|
+
console.log(`[RestProxy] findNewerManyStream: ${elapsed.toFixed(2)}ms (total: ${this._totalRequestMs.toFixed(2)}ms, count: ${this._requestCount})`);
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
} catch (err) {
|
|
6469
|
+
if (timeoutId !== undefined)
|
|
6470
|
+
clearTimeout(timeoutId);
|
|
6471
|
+
if (err.name === "AbortError") {
|
|
6472
|
+
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
6473
|
+
throw new Error(`REST call timeout: findNewerManyStream`);
|
|
6474
|
+
}
|
|
6475
|
+
throw new Error("REST call aborted: findNewerManyStream");
|
|
6476
|
+
}
|
|
6477
|
+
throw err;
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
async parseStreamingResponse(response, onChunk, onActivity) {
|
|
6481
|
+
const reader = response.body.getReader();
|
|
6482
|
+
let buffer = new Uint8Array(0);
|
|
6483
|
+
const readMore = async () => {
|
|
6484
|
+
const { done, value } = await reader.read();
|
|
6485
|
+
if (done)
|
|
6486
|
+
return false;
|
|
6487
|
+
onActivity();
|
|
6488
|
+
buffer = concatUint8(buffer, value);
|
|
6489
|
+
return true;
|
|
6490
|
+
};
|
|
6491
|
+
while (buffer.length < 1) {
|
|
6492
|
+
if (!await readMore())
|
|
6493
|
+
return;
|
|
6494
|
+
}
|
|
6495
|
+
const firstByte = buffer[0];
|
|
6496
|
+
if (firstByte !== 0 && firstByte !== 1) {
|
|
6497
|
+
while (await readMore()) {}
|
|
6498
|
+
const result = unpack2(buffer);
|
|
6499
|
+
for (const [collection, items] of Object.entries(result)) {
|
|
6500
|
+
if (items.length > 0) {
|
|
6501
|
+
await onChunk(collection, items);
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
return;
|
|
6505
|
+
}
|
|
6506
|
+
while (true) {
|
|
6507
|
+
while (buffer.length < 1) {
|
|
6508
|
+
if (!await readMore())
|
|
6509
|
+
return;
|
|
6510
|
+
}
|
|
6511
|
+
if (buffer[0] === 0)
|
|
6512
|
+
return;
|
|
6513
|
+
while (buffer.length < 3) {
|
|
6514
|
+
if (!await readMore())
|
|
6515
|
+
throw new Error("Unexpected end of stream in chunk header");
|
|
6516
|
+
}
|
|
6517
|
+
const nameLen = buffer[1] << 8 | buffer[2];
|
|
6518
|
+
const headerSize = 1 + 2 + nameLen + 4;
|
|
6519
|
+
while (buffer.length < headerSize) {
|
|
6520
|
+
if (!await readMore())
|
|
6521
|
+
throw new Error("Unexpected end of stream in chunk header");
|
|
6522
|
+
}
|
|
6523
|
+
const collection = new TextDecoder().decode(buffer.slice(3, 3 + nameLen));
|
|
6524
|
+
const dataOffset = 3 + nameLen;
|
|
6525
|
+
const dataLen = buffer[dataOffset] << 24 | buffer[dataOffset + 1] << 16 | buffer[dataOffset + 2] << 8 | buffer[dataOffset + 3];
|
|
6526
|
+
const totalChunkSize = headerSize + dataLen;
|
|
6527
|
+
while (buffer.length < totalChunkSize) {
|
|
6528
|
+
if (!await readMore())
|
|
6529
|
+
throw new Error("Unexpected end of stream in chunk data");
|
|
6530
|
+
}
|
|
6531
|
+
const payloadBytes = buffer.slice(headerSize, totalChunkSize);
|
|
6532
|
+
const items = unpack2(payloadBytes);
|
|
6533
|
+
buffer = buffer.slice(totalChunkSize);
|
|
6534
|
+
await onChunk(collection, items);
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6373
6537
|
async deleteOne(collection, query) {
|
|
6374
6538
|
return await this.restCall("deleteOne", { collection, query });
|
|
6375
6539
|
}
|
|
@@ -101,6 +101,28 @@ export declare class RestProxy implements I_RestInterface {
|
|
|
101
101
|
findByIds<T>(collection: string, ids: Id[]): Promise<T[]>;
|
|
102
102
|
findNewer<T>(collection: string, timestamp: Timestamp | number | string | Date, query?: QuerySpec<T>, opts?: QueryOpts): Promise<T[]>;
|
|
103
103
|
findNewerMany<T>(spec?: GetNewerSpec<T>[]): Promise<Record<string, any[]>>;
|
|
104
|
+
/**
|
|
105
|
+
* Streaming variant of findNewerMany.
|
|
106
|
+
* Reads chunked binary response and calls onChunk for each batch.
|
|
107
|
+
* Peak memory = one chunk (~200 docs) instead of entire result set.
|
|
108
|
+
*
|
|
109
|
+
* Binary chunk format:
|
|
110
|
+
* [type:1][nameLen:2][name:N][dataLen:4][msgpack(items[]):M]
|
|
111
|
+
* type=0x01 for data, type=0x00 for end-of-stream.
|
|
112
|
+
*/
|
|
113
|
+
findNewerManyStream<T>(spec: GetNewerSpec<T>[], onChunk: (collection: string, items: T[]) => Promise<void>, options?: {
|
|
114
|
+
timeoutMs?: number;
|
|
115
|
+
signal?: AbortSignal;
|
|
116
|
+
activityTimeoutMs?: number;
|
|
117
|
+
}): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Parse streaming response. Auto-detects format:
|
|
120
|
+
* - Streaming: first byte is 0x00 (end) or 0x01 (data chunk)
|
|
121
|
+
* - Legacy msgpack: first byte is msgpack type marker (0x80+ for map, etc.)
|
|
122
|
+
*
|
|
123
|
+
* Streaming chunk format: [type:1][nameLen:2][name:N][dataLen:4][msgpack(items[]):M]
|
|
124
|
+
*/
|
|
125
|
+
private parseStreamingResponse;
|
|
104
126
|
deleteOne<T>(collection: string, query: QuerySpec<T>): Promise<T>;
|
|
105
127
|
aggregate<T>(collection: string, pipeline: object[], opts?: AggregateOptions): Promise<T[]>;
|
|
106
128
|
upsertBatch<T>(collection: string, batch: BatchSpec<T>): Promise<T[]>;
|
|
@@ -38,6 +38,8 @@ export declare class SyncEngine implements I_SyncEngine {
|
|
|
38
38
|
processCollectionServerData(collectionName: string, serverData: LocalDbEntity[]): Promise<{
|
|
39
39
|
updatedIds: string[];
|
|
40
40
|
}>;
|
|
41
|
+
/** Max items to process per batch in processIncomingServerData */
|
|
42
|
+
private static readonly SYNC_BATCH_SIZE;
|
|
41
43
|
private processIncomingServerData;
|
|
42
44
|
private compareTimestamps;
|
|
43
45
|
private resolveCollectionConflict;
|
|
@@ -67,6 +67,11 @@ export interface I_RestInterface {
|
|
|
67
67
|
findByIds<T>(collection: string, ids: Id[]): Promise<T[]>;
|
|
68
68
|
findNewer<T>(collection: string, timestamp: Timestamp | number | string | Date, query?: QuerySpec<T>, opts?: QueryOpts): Promise<T[]>;
|
|
69
69
|
findNewerMany<T>(spec?: GetNewerSpec<T>[]): Promise<Record<string, any[]>>;
|
|
70
|
+
/** Streaming variant of findNewerMany. Calls onChunk for each batch of items as they arrive. */
|
|
71
|
+
findNewerManyStream<T>(spec: GetNewerSpec<T>[], onChunk: (collection: string, items: T[]) => Promise<void>, options?: {
|
|
72
|
+
timeoutMs?: number;
|
|
73
|
+
signal?: AbortSignal;
|
|
74
|
+
}): Promise<void>;
|
|
70
75
|
deleteOne<T>(collection: string, query: QuerySpec<T>): Promise<T>;
|
|
71
76
|
/** Izvede agregacijo na serverju */
|
|
72
77
|
aggregate<T>(collection: string, pipeline: object[], opts?: AggregateOptions): Promise<T[]>;
|
|
@@ -206,7 +206,7 @@ export interface CollectionSyncStats {
|
|
|
206
206
|
receivedCount: number;
|
|
207
207
|
/** Number of dirty items sent to server for this collection */
|
|
208
208
|
sentCount: number;
|
|
209
|
-
/**
|
|
209
|
+
/** @deprecated Use receivedCount instead. Will be empty array in future streaming mode. */
|
|
210
210
|
receivedItems: LocalDbEntity[];
|
|
211
211
|
}
|
|
212
212
|
/**
|