cry-synced-db-client 0.1.72 → 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 +167 -19
- package/dist/src/db/RestProxy.d.ts +22 -0
- package/dist/src/types/I_RestInterface.d.ts +5 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2155,30 +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
|
-
delete allServerData[collectionName];
|
|
2170
|
-
receivedCount += serverData.length;
|
|
2171
|
-
collectionStats[collectionName] = {
|
|
2172
|
-
receivedCount: serverData.length,
|
|
2173
|
-
sentCount: 0,
|
|
2174
|
-
receivedItems: []
|
|
2175
|
-
};
|
|
2176
|
-
const stats = await this.processIncomingServerData(collectionName, config, serverData);
|
|
2177
|
-
conflictsResolved += stats.conflictsResolved;
|
|
2178
|
-
if (stats.updatedIds.length > 0) {
|
|
2179
|
-
allUpdatedIds[collectionName] = stats.updatedIds;
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
2201
|
if (Object.keys(allUpdatedIds).length > 0) {
|
|
2183
2202
|
this.deps.broadcastUpdates(allUpdatedIds);
|
|
2184
2203
|
}
|
|
@@ -6226,6 +6245,12 @@ var pack2 = (x) => packr.pack(preprocessForPack(x));
|
|
|
6226
6245
|
var unpack2 = (x) => unpackr.unpack(x);
|
|
6227
6246
|
var DEFAULT_TIMEOUT = 5000;
|
|
6228
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
|
+
}
|
|
6229
6254
|
|
|
6230
6255
|
class RestProxy {
|
|
6231
6256
|
endpoint;
|
|
@@ -6386,6 +6411,129 @@ class RestProxy {
|
|
|
6386
6411
|
async findNewerMany(spec) {
|
|
6387
6412
|
return await this.restCall("findNewerMany", { spec });
|
|
6388
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
|
+
}
|
|
6389
6537
|
async deleteOne(collection, query) {
|
|
6390
6538
|
return await this.restCall("deleteOne", { collection, query });
|
|
6391
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[]>;
|
|
@@ -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[]>;
|