cry-synced-db-client 0.1.73 → 0.1.74

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
@@ -125,7 +125,7 @@ function sortItems(items, sort) {
125
125
  const sortEntries = Object.entries(sort);
126
126
  if (sortEntries.length === 0)
127
127
  return items;
128
- return [...items].sort((a, b) => {
128
+ return items.slice().sort((a, b) => {
129
129
  for (const [field, direction] of sortEntries) {
130
130
  const aVal = getNestedValue(a, field);
131
131
  const bVal = getNestedValue(b, field);
@@ -194,7 +194,7 @@ function projectItem(item, project) {
194
194
  }
195
195
  return result;
196
196
  } else {
197
- const result = { ...item };
197
+ const result = Object.assign({}, item);
198
198
  for (const [field, value] of entries) {
199
199
  if (value === false || value === 0) {
200
200
  delete result[field];
@@ -237,7 +237,9 @@ class InMemManager {
237
237
  if (operation === "upsert") {
238
238
  this.inMemDb.saveMany(collection, items);
239
239
  if (this.useObjectMetadata && config?.hasMetadata) {
240
- const ids = items.map((item) => item._id);
240
+ const ids = [];
241
+ for (const item of items)
242
+ ids.push(item._id);
241
243
  let metadatas;
242
244
  if (config.onObjectsUpdated) {
243
245
  try {
@@ -259,7 +261,9 @@ class InMemManager {
259
261
  this.setObjectsMetadataInternal(collection, ids, metadatas);
260
262
  }
261
263
  } else if (operation === "delete") {
262
- const ids = items.map((item) => item._id);
264
+ const ids = [];
265
+ for (const item of items)
266
+ ids.push(item._id);
263
267
  this.inMemDb.deleteManyByIds(collection, ids);
264
268
  if (this.useObjectMetadata && config?.hasMetadata) {
265
269
  this.deleteObjectsMetadataInternal(collection, ids);
@@ -271,7 +275,9 @@ class InMemManager {
271
275
  if (this.useObjectMetadata && items.length > 0) {
272
276
  const config = this.collections.get(collection);
273
277
  if (config?.hasMetadata) {
274
- const ids = items.map((item) => item._id);
278
+ const ids = [];
279
+ for (const item of items)
280
+ ids.push(item._id);
275
281
  let metadatas;
276
282
  if (config.onObjectsUpdated) {
277
283
  try {
@@ -1722,11 +1728,12 @@ function savePendingWrite(tenant, collection, id, delta) {
1722
1728
  let pending;
1723
1729
  if (existingRaw) {
1724
1730
  const existing = dist_default.parse(existingRaw);
1731
+ Object.assign(existing.data, delta);
1725
1732
  pending = {
1726
1733
  tenant,
1727
1734
  collection,
1728
1735
  id: String(id),
1729
- data: { ...existing.data, ...delta },
1736
+ data: existing.data,
1730
1737
  timestamp: now
1731
1738
  };
1732
1739
  } else {
@@ -1808,7 +1815,7 @@ class PendingChangesManager {
1808
1815
  }
1809
1816
  const deltaWithId = existing ? data : { _id: id, ...data };
1810
1817
  savePendingWrite(this.tenant, collection, id, deltaWithId);
1811
- const fullData = existing ? { ...existing.data, ...data } : { _id: id, ...data };
1818
+ const fullData = existing ? Object.assign(existing.data, data) : { _id: id, ...data };
1812
1819
  const timer = setTimeout(() => {
1813
1820
  this.executePendingChange(key);
1814
1821
  }, this.debounceDexieWritesMs);
@@ -1879,7 +1886,8 @@ class PendingChangesManager {
1879
1886
  baseMeta: { _ts: existing?._ts, _rev: existing?._rev }
1880
1887
  });
1881
1888
  if (existing) {
1882
- saveBatch.push({ ...existing, ...write.data });
1889
+ Object.assign(existing, write.data);
1890
+ saveBatch.push(existing);
1883
1891
  } else {
1884
1892
  insertBatch.push(write.data);
1885
1893
  }
@@ -1887,7 +1895,7 @@ class PendingChangesManager {
1887
1895
  if (dirtyChangesBatch.length > 0) {
1888
1896
  await this.deps.dexieDb.addDirtyChangesBatch(collection, dirtyChangesBatch);
1889
1897
  }
1890
- const allToSave = [...saveBatch, ...insertBatch];
1898
+ const allToSave = saveBatch.concat(insertBatch);
1891
1899
  if (allToSave.length > 0) {
1892
1900
  await this.deps.dexieDb.saveMany(collection, allToSave);
1893
1901
  }
@@ -2063,20 +2071,26 @@ function mergeObjects(local, external) {
2063
2071
  }
2064
2072
  function mergeArrays(local, external) {
2065
2073
  if (local.length === 0) {
2066
- return [...external];
2074
+ return external.slice();
2067
2075
  }
2068
2076
  const firstLocal = local[0];
2069
2077
  const firstExternal = external[0];
2070
2078
  if (typeof firstLocal === "string" || typeof firstExternal === "string") {
2071
- return [...new Set([...local, ...external])];
2079
+ const set2 = new Set(local);
2080
+ for (const item of external)
2081
+ set2.add(item);
2082
+ return Array.from(set2);
2072
2083
  }
2073
2084
  if (isPlainObject3(firstLocal) || isPlainObject3(firstExternal)) {
2074
2085
  return mergeObjectArrays(local, external);
2075
2086
  }
2076
- return [...new Set([...local, ...external])];
2087
+ const set = new Set(local);
2088
+ for (const item of external)
2089
+ set.add(item);
2090
+ return Array.from(set);
2077
2091
  }
2078
2092
  function mergeObjectArrays(local, external) {
2079
- const result = [...local];
2093
+ const result = local.slice();
2080
2094
  const localIds = new Map;
2081
2095
  for (let i = 0;i < local.length; i++) {
2082
2096
  const item = local[i];
@@ -2155,13 +2169,11 @@ class SyncEngine {
2155
2169
  }
2156
2170
  this.callOnFindNewerManyCall(syncSpecs, calledFrom);
2157
2171
  const findNewerManyStartTime = Date.now();
2158
- const allUpdatedIds = {};
2159
2172
  const collectionState = new Map;
2160
2173
  for (const [name] of configMap) {
2161
2174
  collectionState.set(name, {
2162
2175
  maxTs: undefined,
2163
2176
  conflicts: 0,
2164
- updatedIds: [],
2165
2177
  receivedCount: 0
2166
2178
  });
2167
2179
  }
@@ -2179,28 +2191,23 @@ class SyncEngine {
2179
2191
  state.maxTs = stats.maxTs;
2180
2192
  }
2181
2193
  }
2182
- state.updatedIds.push(...stats.updatedIds);
2194
+ if (stats.updatedIds.length > 0) {
2195
+ this.deps.broadcastUpdates({ [collection]: stats.updatedIds });
2196
+ }
2183
2197
  }), "findNewerManyStream");
2184
2198
  for (const [name, state] of collectionState) {
2185
2199
  receivedCount += state.receivedCount;
2186
2200
  conflictsResolved += state.conflicts;
2187
2201
  collectionStats[name] = {
2188
2202
  receivedCount: state.receivedCount,
2189
- sentCount: 0,
2190
- receivedItems: []
2203
+ sentCount: 0
2191
2204
  };
2192
- if (state.updatedIds.length > 0) {
2193
- allUpdatedIds[name] = state.updatedIds;
2194
- }
2195
2205
  }
2196
2206
  this.callOnFindNewerManyResult(syncSpecs, {}, findNewerManyStartTime, true, calledFrom);
2197
2207
  } catch (err) {
2198
2208
  this.callOnFindNewerManyResult(syncSpecs, {}, findNewerManyStartTime, false, calledFrom, err);
2199
2209
  throw err;
2200
2210
  }
2201
- if (Object.keys(allUpdatedIds).length > 0) {
2202
- this.deps.broadcastUpdates(allUpdatedIds);
2203
- }
2204
2211
  const uploadStats = await this.uploadDirtyItems(calledFrom);
2205
2212
  sentCount = uploadStats.sentCount;
2206
2213
  for (const [collectionName, stats] of Object.entries(uploadStats.collectionSentCounts || {})) {
@@ -2209,8 +2216,7 @@ class SyncEngine {
2209
2216
  } else {
2210
2217
  collectionStats[collectionName] = {
2211
2218
  receivedCount: 0,
2212
- sentCount: stats,
2213
- receivedItems: []
2219
+ sentCount: stats
2214
2220
  };
2215
2221
  }
2216
2222
  }
@@ -2297,18 +2303,22 @@ class SyncEngine {
2297
2303
  const collectionSentCounts = {};
2298
2304
  for (const result of results) {
2299
2305
  const { collection, results: { inserted, updated, deleted, errors } } = result;
2300
- const allSuccessIds = [
2301
- ...inserted.map((e) => e._id),
2302
- ...updated.map((e) => e._id),
2303
- ...deleted.map((e) => e._id)
2304
- ];
2306
+ const allSuccessIds = [];
2307
+ for (const e of inserted)
2308
+ allSuccessIds.push(e._id);
2309
+ for (const e of updated)
2310
+ allSuccessIds.push(e._id);
2311
+ for (const e of deleted)
2312
+ allSuccessIds.push(e._id);
2305
2313
  if (allSuccessIds.length > 0) {
2306
2314
  await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
2307
2315
  }
2308
2316
  let collectionSentCount = 0;
2309
- const insertedAndUpdated = [...inserted, ...updated];
2317
+ const insertedAndUpdated = inserted.concat(updated);
2310
2318
  if (insertedAndUpdated.length > 0) {
2311
- const idsToCheck = insertedAndUpdated.map((e) => e._id);
2319
+ const idsToCheck = [];
2320
+ for (const e of insertedAndUpdated)
2321
+ idsToCheck.push(e._id);
2312
2322
  const dexieItems = await this.dexieDb.getByIds(collection, idsToCheck);
2313
2323
  const dexieSaveBatch = [];
2314
2324
  const inMemUpdateBatch = [];
@@ -2316,11 +2326,9 @@ class SyncEngine {
2316
2326
  const entity = insertedAndUpdated[i];
2317
2327
  const dexieItem = dexieItems[i];
2318
2328
  if (dexieItem) {
2319
- dexieSaveBatch.push({
2320
- ...dexieItem,
2321
- _rev: entity._rev,
2322
- _ts: entity._ts
2323
- });
2329
+ dexieItem._rev = entity._rev;
2330
+ dexieItem._ts = entity._ts;
2331
+ dexieSaveBatch.push(dexieItem);
2324
2332
  if (!dexieItem._deleted) {
2325
2333
  const inMemItem = this.deps.getInMemById(collection, entity._id);
2326
2334
  if (inMemItem) {
@@ -2343,21 +2351,27 @@ class SyncEngine {
2343
2351
  collectionSentCount += insertedAndUpdated.length;
2344
2352
  }
2345
2353
  if (deleted.length > 0) {
2346
- const deleteIds = deleted.map((e) => e._id);
2354
+ const deleteIds = [];
2355
+ const deleteDbEntities = [];
2356
+ for (const e of deleted) {
2357
+ deleteIds.push(e._id);
2358
+ deleteDbEntities.push({ _id: e._id });
2359
+ }
2347
2360
  await this.dexieDb.deleteMany(collection, deleteIds);
2348
- this.deps.writeToInMemBatch(collection, deleteIds.map((id) => ({ _id: id })), "delete");
2361
+ this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete");
2349
2362
  sentCount += deleted.length;
2350
2363
  collectionSentCount += deleted.length;
2351
2364
  }
2352
2365
  if (collectionSentCount > 0) {
2353
2366
  collectionSentCounts[collection] = collectionSentCount;
2354
2367
  }
2355
- const allItems = [...inserted, ...updated, ...deleted];
2356
2368
  let maxTs = undefined;
2357
- for (const item of allItems) {
2358
- if (item._ts) {
2359
- if (!maxTs || this.compareTimestamps(item._ts, maxTs) > 0) {
2360
- maxTs = item._ts;
2369
+ for (const arr of [inserted, updated, deleted]) {
2370
+ for (const item of arr) {
2371
+ if (item._ts) {
2372
+ if (!maxTs || this.compareTimestamps(item._ts, maxTs) > 0) {
2373
+ maxTs = item._ts;
2374
+ }
2361
2375
  }
2362
2376
  }
2363
2377
  }
@@ -2418,11 +2432,13 @@ class SyncEngine {
2418
2432
  let sentCount = 0;
2419
2433
  for (const result of results) {
2420
2434
  const { results: { inserted, updated, deleted } } = result;
2421
- const allSuccessIds = [
2422
- ...inserted.map((e) => e._id),
2423
- ...updated.map((e) => e._id),
2424
- ...deleted.map((e) => e._id)
2425
- ];
2435
+ const allSuccessIds = [];
2436
+ for (const e of inserted)
2437
+ allSuccessIds.push(e._id);
2438
+ for (const e of updated)
2439
+ allSuccessIds.push(e._id);
2440
+ for (const e of deleted)
2441
+ allSuccessIds.push(e._id);
2426
2442
  if (allSuccessIds.length > 0) {
2427
2443
  await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
2428
2444
  }
@@ -2451,7 +2467,9 @@ class SyncEngine {
2451
2467
  const BATCH = SyncEngine.SYNC_BATCH_SIZE;
2452
2468
  for (let offset = 0;offset < serverData.length; offset += BATCH) {
2453
2469
  const chunk = serverData.slice(offset, offset + BATCH);
2454
- const chunkIds = chunk.map((item) => item._id);
2470
+ const chunkIds = [];
2471
+ for (const item of chunk)
2472
+ chunkIds.push(item._id);
2455
2473
  const localItems = await this.dexieDb.getByIds(collectionName, chunkIds);
2456
2474
  const dirtyChangesMap = await this.dexieDb.getDirtyChangesBatch(collectionName, chunkIds);
2457
2475
  const dexieBatch = [];
@@ -2461,6 +2479,7 @@ class SyncEngine {
2461
2479
  const serverItem = chunk[i];
2462
2480
  const localItem = localItems[i];
2463
2481
  const dirtyChange = dirtyChangesMap.get(String(serverItem._id));
2482
+ allUpdatedIds.push(String(serverItem._id));
2464
2483
  if (serverItem._ts) {
2465
2484
  if (!maxTs || this.compareTimestamps(serverItem._ts, maxTs) > 0) {
2466
2485
  maxTs = serverItem._ts;
@@ -2500,9 +2519,6 @@ class SyncEngine {
2500
2519
  if (inMemDeleteIds.length > 0) {
2501
2520
  this.deps.writeToInMemBatch(collectionName, inMemDeleteIds.map((id) => ({ _id: id })), "delete");
2502
2521
  }
2503
- for (const id of chunkIds) {
2504
- allUpdatedIds.push(String(id));
2505
- }
2506
2522
  }
2507
2523
  if (maxTs) {
2508
2524
  await this.dexieDb.setSyncMeta(collectionName, maxTs);
@@ -2759,9 +2775,9 @@ class ServerUpdateHandler {
2759
2775
  if (pendingChange) {
2760
2776
  const newFields = this.getNewFieldsFromServer(localItem, serverDelta);
2761
2777
  if (Object.keys(newFields).length > 0) {
2762
- pendingChange.data = { ...pendingChange.data, ...newFields };
2778
+ Object.assign(pendingChange.data, newFields);
2763
2779
  }
2764
- const currentInMemState = { ...localItem, ...pendingChange.data };
2780
+ const currentInMemState = Object.assign({}, localItem, pendingChange.data);
2765
2781
  const merged = this.mergeLocalWithDelta(currentInMemState, serverDelta);
2766
2782
  if (!merged._deleted && !merged._archived) {
2767
2783
  this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
@@ -2820,13 +2836,12 @@ class ServerUpdateHandler {
2820
2836
  return false;
2821
2837
  }
2822
2838
  mergeLocalWithDelta(local, delta) {
2823
- const result = { ...local };
2824
2839
  for (const key of Object.keys(delta)) {
2825
2840
  if (key === "_id" || key === "_dirty")
2826
2841
  continue;
2827
- result[key] = delta[key];
2842
+ local[key] = delta[key];
2828
2843
  }
2829
- return result;
2844
+ return local;
2830
2845
  }
2831
2846
  getNewFieldsFromServer(local, server) {
2832
2847
  const newFields = {};
@@ -3278,16 +3293,20 @@ class SyncedDb {
3278
3293
  }
3279
3294
  await this.pendingChanges.recoverPendingWrites();
3280
3295
  for (const [name] of this.collections) {
3281
- const data = await this.dexieDb.getAll(name);
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;
3296
+ this.inMemManager.clearCollection(name);
3297
+ await this.dexieDb.forEachBatch(name, 2000, async (chunk) => {
3298
+ let writeIdx = 0;
3299
+ for (let i = 0;i < chunk.length; i++) {
3300
+ const item = chunk[i];
3301
+ if (!item._deleted && !item._archived) {
3302
+ chunk[writeIdx++] = item;
3303
+ }
3287
3304
  }
3288
- }
3289
- data.length = writeIdx;
3290
- this.inMemManager.initCollection(name, data);
3305
+ chunk.length = writeIdx;
3306
+ if (chunk.length > 0) {
3307
+ this.inMemManager.writeBatch(name, chunk, "upsert");
3308
+ }
3309
+ });
3291
3310
  const meta = await this.dexieDb.getSyncMeta(name);
3292
3311
  if (meta) {
3293
3312
  this.syncMetaCache.set(name, meta);
@@ -3449,26 +3468,48 @@ class SyncedDb {
3449
3468
  await this.syncCollectionForFind(collection, query, opts);
3450
3469
  }
3451
3470
  const all = await this.dexieDb.getAll(collection);
3452
- const active = all.filter((item) => {
3453
- if (!opts?.returnDeleted && item._deleted)
3454
- return false;
3455
- if (!opts?.returnArchived && item._archived)
3456
- return false;
3457
- return true;
3458
- });
3459
- const filtered = filterByQuery(active, query);
3460
- if (filtered.length === 0) {
3461
- if (opts?.referToServer && this.isOnline()) {
3471
+ const returnDeleted = opts?.returnDeleted;
3472
+ const returnArchived = opts?.returnArchived;
3473
+ const hasSort = opts?.sort && Object.keys(opts.sort).length > 0;
3474
+ if (hasSort) {
3475
+ const filtered = [];
3476
+ for (const item of all) {
3477
+ if (!returnDeleted && item._deleted)
3478
+ continue;
3479
+ if (!returnArchived && item._archived)
3480
+ continue;
3481
+ if (!matchesQuery(item, query))
3482
+ continue;
3483
+ filtered.push(item);
3484
+ }
3485
+ if (filtered.length === 0) {
3486
+ if (opts?.referToServer && this.isOnline())
3487
+ this.referToServerSync(collection, query);
3488
+ return null;
3489
+ }
3490
+ const sorted = applyQueryOpts(filtered, { sort: opts.sort, project: opts?.project });
3491
+ if (opts?.referToServer && this.isOnline())
3462
3492
  this.referToServerSync(collection, query);
3493
+ return sorted[0] ?? null;
3494
+ } else {
3495
+ let result = null;
3496
+ for (const item of all) {
3497
+ if (!returnDeleted && item._deleted)
3498
+ continue;
3499
+ if (!returnArchived && item._archived)
3500
+ continue;
3501
+ if (!matchesQuery(item, query))
3502
+ continue;
3503
+ result = item;
3504
+ break;
3463
3505
  }
3464
- return null;
3465
- }
3466
- const sorted = applyQueryOpts(filtered, { sort: opts?.sort, project: opts?.project });
3467
- const result = sorted[0];
3468
- if (opts?.referToServer && this.isOnline()) {
3469
- this.referToServerSync(collection, query);
3506
+ if (result && opts?.project) {
3507
+ result = projectItem(result, opts.project);
3508
+ }
3509
+ if (opts?.referToServer && this.isOnline())
3510
+ this.referToServerSync(collection, query);
3511
+ return result;
3470
3512
  }
3471
- return result;
3472
3513
  }
3473
3514
  async find(collection, query, opts) {
3474
3515
  this.assertCollection(collection);
@@ -3477,14 +3518,19 @@ class SyncedDb {
3477
3518
  await this.syncCollectionForFind(collection, query, opts);
3478
3519
  }
3479
3520
  const all = await this.dexieDb.getAll(collection);
3480
- const active = all.filter((item) => {
3481
- if (!opts?.returnDeleted && item._deleted)
3482
- return false;
3483
- if (!opts?.returnArchived && item._archived)
3484
- return false;
3485
- return true;
3486
- });
3487
- const filtered = query ? filterByQuery(active, query) : active;
3521
+ const returnDeleted = opts?.returnDeleted;
3522
+ const returnArchived = opts?.returnArchived;
3523
+ const hasQuery = query && Object.keys(query).length > 0;
3524
+ const filtered = [];
3525
+ for (const item of all) {
3526
+ if (!returnDeleted && item._deleted)
3527
+ continue;
3528
+ if (!returnArchived && item._archived)
3529
+ continue;
3530
+ if (hasQuery && !matchesQuery(item, query))
3531
+ continue;
3532
+ filtered.push(item);
3533
+ }
3488
3534
  const result = applyQueryOpts(filtered, opts);
3489
3535
  if (opts?.referToServer && this.isOnline()) {
3490
3536
  this.referToServerSync(collection, query);
@@ -3957,7 +4003,9 @@ class DexieDb extends Dexie {
3957
4003
  if (ids.length === 0)
3958
4004
  return;
3959
4005
  const table = this.getTable(collection);
3960
- const keys = ids.map((id) => this.idToString(id));
4006
+ const keys = [];
4007
+ for (const id of ids)
4008
+ keys.push(this.idToString(id));
3961
4009
  await table.bulkDelete(keys);
3962
4010
  }
3963
4011
  async saveCollection(collection, data) {
@@ -3980,36 +4028,50 @@ class DexieDb extends Dexie {
3980
4028
  if (ids.length === 0)
3981
4029
  return [];
3982
4030
  const table = this.getTable(collection);
3983
- const keys = ids.map((id) => this.idToString(id));
4031
+ const keys = [];
4032
+ for (const id of ids)
4033
+ keys.push(this.idToString(id));
3984
4034
  return await table.bulkGet(keys);
3985
4035
  }
3986
4036
  async getAll(collection) {
3987
4037
  const table = this.getTable(collection);
3988
4038
  return await table.toArray();
3989
4039
  }
4040
+ async forEachBatch(collection, batchSize, callback) {
4041
+ const table = this.getTable(collection);
4042
+ let offset = 0;
4043
+ while (true) {
4044
+ const items = await table.offset(offset).limit(batchSize).toArray();
4045
+ if (items.length === 0)
4046
+ break;
4047
+ await callback(items);
4048
+ if (items.length < batchSize)
4049
+ break;
4050
+ offset += items.length;
4051
+ }
4052
+ }
3990
4053
  async count(collection) {
3991
4054
  const table = this.getTable(collection);
3992
4055
  return await table.count();
3993
4056
  }
3994
4057
  async getDirty(collection) {
3995
4058
  const dirtyEntries = await this.dirtyChanges.where("[collection+id]").between([collection, Dexie.minKey], [collection, Dexie.maxKey]).toArray();
3996
- return dirtyEntries.map((entry) => ({
3997
- _id: entry.id,
3998
- ...entry.changes,
3999
- _ts: entry.baseTs,
4000
- _rev: entry.baseRev
4001
- }));
4059
+ const result = [];
4060
+ for (const entry of dirtyEntries) {
4061
+ const obj = { _id: entry.id, _ts: entry.baseTs, _rev: entry.baseRev };
4062
+ Object.assign(obj, entry.changes);
4063
+ result.push(obj);
4064
+ }
4065
+ return result;
4002
4066
  }
4003
4067
  async addDirtyChange(collection, id, changes, baseMeta) {
4004
4068
  const stringId = this.idToString(id);
4005
4069
  const existing = await this.dirtyChanges.get([collection, stringId]);
4006
4070
  const now = Date.now();
4007
4071
  if (existing) {
4008
- await this.dirtyChanges.put({
4009
- ...existing,
4010
- changes: { ...existing.changes, ...changes },
4011
- updatedAt: now
4012
- });
4072
+ Object.assign(existing.changes, changes);
4073
+ existing.updatedAt = now;
4074
+ await this.dirtyChanges.put(existing);
4013
4075
  } else {
4014
4076
  await this.dirtyChanges.put({
4015
4077
  collection,
@@ -4026,7 +4088,9 @@ class DexieDb extends Dexie {
4026
4088
  if (changesList.length === 0)
4027
4089
  return;
4028
4090
  const now = Date.now();
4029
- const keys = changesList.map((c) => [collection, this.idToString(c.id)]);
4091
+ const keys = [];
4092
+ for (const c of changesList)
4093
+ keys.push([collection, this.idToString(c.id)]);
4030
4094
  const existingEntries = await this.dirtyChanges.bulkGet(keys);
4031
4095
  const toWrite = [];
4032
4096
  for (let i = 0;i < changesList.length; i++) {
@@ -4034,11 +4098,9 @@ class DexieDb extends Dexie {
4034
4098
  const stringId = this.idToString(changeItem.id);
4035
4099
  const existing = existingEntries[i];
4036
4100
  if (existing) {
4037
- toWrite.push({
4038
- ...existing,
4039
- changes: { ...existing.changes, ...changeItem.changes },
4040
- updatedAt: now
4041
- });
4101
+ Object.assign(existing.changes, changeItem.changes);
4102
+ existing.updatedAt = now;
4103
+ toWrite.push(existing);
4042
4104
  } else {
4043
4105
  toWrite.push({
4044
4106
  collection,
@@ -4061,7 +4123,9 @@ class DexieDb extends Dexie {
4061
4123
  const result = new Map;
4062
4124
  if (ids.length === 0)
4063
4125
  return result;
4064
- const keys = ids.map((id) => [collection, this.idToString(id)]);
4126
+ const keys = [];
4127
+ for (const id of ids)
4128
+ keys.push([collection, this.idToString(id)]);
4065
4129
  const entries = await this.dirtyChanges.bulkGet(keys);
4066
4130
  for (let i = 0;i < ids.length; i++) {
4067
4131
  const entry = entries[i];
@@ -4078,7 +4142,9 @@ class DexieDb extends Dexie {
4078
4142
  async clearDirtyChangesBatch(collection, ids) {
4079
4143
  if (ids.length === 0)
4080
4144
  return;
4081
- const keys = ids.map((id) => [collection, this.idToString(id)]);
4145
+ const keys = [];
4146
+ for (const id of ids)
4147
+ keys.push([collection, this.idToString(id)]);
4082
4148
  await this.dirtyChanges.bulkDelete(keys);
4083
4149
  }
4084
4150
  async clearDirtyChanges(collection) {
@@ -6245,11 +6311,46 @@ var pack2 = (x) => packr.pack(preprocessForPack(x));
6245
6311
  var unpack2 = (x) => unpackr.unpack(x);
6246
6312
  var DEFAULT_TIMEOUT = 5000;
6247
6313
  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;
6314
+
6315
+ class StreamBuffer {
6316
+ buf;
6317
+ rPos = 0;
6318
+ wPos = 0;
6319
+ constructor(initialCapacity = 64 * 1024) {
6320
+ this.buf = new Uint8Array(initialCapacity);
6321
+ }
6322
+ get length() {
6323
+ return this.wPos - this.rPos;
6324
+ }
6325
+ append(data) {
6326
+ if (this.rPos > 0 && this.wPos + data.length > this.buf.length) {
6327
+ this.buf.copyWithin(0, this.rPos, this.wPos);
6328
+ this.wPos -= this.rPos;
6329
+ this.rPos = 0;
6330
+ }
6331
+ if (this.wPos + data.length > this.buf.length) {
6332
+ const newSize = Math.max(this.buf.length * 2, this.wPos + data.length);
6333
+ const newBuf = new Uint8Array(newSize);
6334
+ newBuf.set(this.buf.subarray(0, this.wPos));
6335
+ this.buf = newBuf;
6336
+ }
6337
+ this.buf.set(data, this.wPos);
6338
+ this.wPos += data.length;
6339
+ }
6340
+ subarray(start, end) {
6341
+ return this.buf.subarray(this.rPos + start, this.rPos + end);
6342
+ }
6343
+ at(offset) {
6344
+ return this.buf[this.rPos + offset];
6345
+ }
6346
+ consume(n) {
6347
+ this.rPos += n;
6348
+ if (this.rPos > this.buf.length >>> 1) {
6349
+ this.buf.copyWithin(0, this.rPos, this.wPos);
6350
+ this.wPos -= this.rPos;
6351
+ this.rPos = 0;
6352
+ }
6353
+ }
6253
6354
  }
6254
6355
 
6255
6356
  class RestProxy {
@@ -6479,23 +6580,24 @@ class RestProxy {
6479
6580
  }
6480
6581
  async parseStreamingResponse(response, onChunk, onActivity) {
6481
6582
  const reader = response.body.getReader();
6482
- let buffer = new Uint8Array(0);
6583
+ const buffer = new StreamBuffer;
6584
+ const decoder2 = new TextDecoder;
6483
6585
  const readMore = async () => {
6484
6586
  const { done, value } = await reader.read();
6485
6587
  if (done)
6486
6588
  return false;
6487
6589
  onActivity();
6488
- buffer = concatUint8(buffer, value);
6590
+ buffer.append(value);
6489
6591
  return true;
6490
6592
  };
6491
6593
  while (buffer.length < 1) {
6492
6594
  if (!await readMore())
6493
6595
  return;
6494
6596
  }
6495
- const firstByte = buffer[0];
6597
+ const firstByte = buffer.at(0);
6496
6598
  if (firstByte !== 0 && firstByte !== 1) {
6497
6599
  while (await readMore()) {}
6498
- const result = unpack2(buffer);
6600
+ const result = unpack2(buffer.subarray(0, buffer.length));
6499
6601
  for (const [collection, items] of Object.entries(result)) {
6500
6602
  if (items.length > 0) {
6501
6603
  await onChunk(collection, items);
@@ -6508,29 +6610,28 @@ class RestProxy {
6508
6610
  if (!await readMore())
6509
6611
  return;
6510
6612
  }
6511
- if (buffer[0] === 0)
6613
+ if (buffer.at(0) === 0)
6512
6614
  return;
6513
6615
  while (buffer.length < 3) {
6514
6616
  if (!await readMore())
6515
6617
  throw new Error("Unexpected end of stream in chunk header");
6516
6618
  }
6517
- const nameLen = buffer[1] << 8 | buffer[2];
6619
+ const nameLen = buffer.at(1) << 8 | buffer.at(2);
6518
6620
  const headerSize = 1 + 2 + nameLen + 4;
6519
6621
  while (buffer.length < headerSize) {
6520
6622
  if (!await readMore())
6521
6623
  throw new Error("Unexpected end of stream in chunk header");
6522
6624
  }
6523
- const collection = new TextDecoder().decode(buffer.slice(3, 3 + nameLen));
6625
+ const collection = decoder2.decode(buffer.subarray(3, 3 + nameLen));
6524
6626
  const dataOffset = 3 + nameLen;
6525
- const dataLen = buffer[dataOffset] << 24 | buffer[dataOffset + 1] << 16 | buffer[dataOffset + 2] << 8 | buffer[dataOffset + 3];
6627
+ const dataLen = buffer.at(dataOffset) << 24 | buffer.at(dataOffset + 1) << 16 | buffer.at(dataOffset + 2) << 8 | buffer.at(dataOffset + 3);
6526
6628
  const totalChunkSize = headerSize + dataLen;
6527
6629
  while (buffer.length < totalChunkSize) {
6528
6630
  if (!await readMore())
6529
6631
  throw new Error("Unexpected end of stream in chunk data");
6530
6632
  }
6531
- const payloadBytes = buffer.slice(headerSize, totalChunkSize);
6532
- const items = unpack2(payloadBytes);
6533
- buffer = buffer.slice(totalChunkSize);
6633
+ const items = unpack2(buffer.subarray(headerSize, totalChunkSize));
6634
+ buffer.consume(totalChunkSize);
6534
6635
  await onChunk(collection, items);
6535
6636
  }
6536
6637
  }
@@ -25,6 +25,7 @@ export declare class DexieDb extends Dexie implements I_DexieDb {
25
25
  getById<T extends LocalDbEntity>(collection: string, id: Id): Promise<T | undefined>;
26
26
  getByIds<T extends LocalDbEntity>(collection: string, ids: Id[]): Promise<(T | undefined)[]>;
27
27
  getAll<T extends LocalDbEntity>(collection: string): Promise<T[]>;
28
+ forEachBatch<T extends LocalDbEntity>(collection: string, batchSize: number, callback: (items: T[]) => Promise<void>): Promise<void>;
28
29
  count(collection: string): Promise<number>;
29
30
  getDirty<T extends LocalDbEntity>(collection: string): Promise<Partial<T>[]>;
30
31
  addDirtyChange(collection: string, id: Id, changes: Record<string, any>, baseMeta?: {
@@ -38,6 +38,7 @@ export declare class ServerUpdateHandler implements I_ServerUpdateHandler {
38
38
  handleServerItemDelete(collection: string, id: Id): Promise<void>;
39
39
  private stripLocalFields;
40
40
  private timestampsEqual;
41
+ /** @mutates local — mutira vhodni objekt namesto kopiranja */
41
42
  private mergeLocalWithDelta;
42
43
  private getNewFieldsFromServer;
43
44
  private callOnWsNotification;
@@ -64,6 +64,8 @@ export interface I_DexieDb {
64
64
  getByIds<T extends LocalDbEntity>(collection: string, ids: Id[]): Promise<(T | undefined)[]>;
65
65
  /** Vrne vse objekte v kolekciji */
66
66
  getAll<T extends LocalDbEntity>(collection: string): Promise<T[]>;
67
+ /** Iterira po kolekciji v chunkih brez nalaganja vseh zapisov naenkrat */
68
+ forEachBatch<T extends LocalDbEntity>(collection: string, batchSize: number, callback: (items: T[]) => Promise<void>): Promise<void>;
67
69
  /** Vrne vse dirty objekte (z lokalnimi spremembami) - returns only changed fields + _id + metadata */
68
70
  getDirty<T extends LocalDbEntity>(collection: string): Promise<Partial<T>[]>;
69
71
  /** Add or accumulate changes for a record */
@@ -206,8 +206,6 @@ export interface CollectionSyncStats {
206
206
  receivedCount: number;
207
207
  /** Number of dirty items sent to server for this collection */
208
208
  sentCount: number;
209
- /** @deprecated Use receivedCount instead. Will be empty array in future streaming mode. */
210
- receivedItems: LocalDbEntity[];
211
209
  }
212
210
  /**
213
211
  * Informacije o sinhronizaciji za debugging/logging
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",