cry-synced-db-client 0.1.197 → 0.1.199
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/CHANGELOG.md +24 -0
- package/dist/index.js +167 -6
- package/dist/src/db/DexieDb.d.ts +1 -0
- package/dist/src/db/SyncedDb.d.ts +25 -0
- package/dist/src/db/sync/SyncEngine.d.ts +2 -1
- package/dist/src/db/types/managers.d.ts +2 -1
- package/dist/src/types/I_DexieDb.d.ts +15 -0
- package/dist/src/types/I_SyncedDb.d.ts +42 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 0.1.198 (2026-06-13)
|
|
4
|
+
|
|
5
|
+
### Stuck-item tracking (`getStuckItems` / `discardStuckItems` / `onDirtyItemStuck`)
|
|
6
|
+
|
|
7
|
+
Nov mehanizem za detekcijo dirty itemov, ki jih server vztrajno zavrača.
|
|
8
|
+
|
|
9
|
+
**Fields na `DirtyChange` / `DirtyMeta`:**
|
|
10
|
+
- `firstUploadAttempt?`, `lastUploadAttempt?`, `numUploadAttempts?`, `stuckSince?`
|
|
11
|
+
- `stuckSince` se nastavi ko `numUploadAttempts > DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` (2)
|
|
12
|
+
|
|
13
|
+
**Nove metode na `SyncedDb`:**
|
|
14
|
+
- `getStuckItems()` — vrne samo stuck dirty iteme po kolekcijah (`Record<collection, DirtyMeta[]>`)
|
|
15
|
+
- `discardStuckItems(calledFrom?)` — zbriše vse stuck dirty iteme, sproži `onBeforeDirtyClearAll` z `reason: "discard-stuck"`
|
|
16
|
+
|
|
17
|
+
**Nov callback:**
|
|
18
|
+
- `onDirtyItemStuck(info: DirtyItemStuckInfo)` — sproži se ko item prvič postane stuck (po 3. neuspelem uploadu). Vsebuje `collection`, `items: DirtyMeta[]`, `calledFrom`, `timestamp`.
|
|
19
|
+
|
|
20
|
+
**Internal:**
|
|
21
|
+
- `I_DexieDb.incrementDirtyUploadAttempts(collection, ids)` — nova metoda, vrača `DirtyMeta[]` na novo stuck itemov
|
|
22
|
+
- Vgrajena v `SyncEngine.uploadDirtyItems` in `uploadDirtyItemsForCollection` — per-collection result (errored ids) in catch block (network/timeout)
|
|
23
|
+
- `DexieDb.getDirtyMeta` sedaj vključuje nova polja v izhod
|
|
24
|
+
|
|
25
|
+
**Tests:** 17 testov v `test/stuckItems.test.ts` — unit testi za increment, integracijski za get/discard, e2e za callback skozi upload pipeline.
|
|
26
|
+
|
|
3
27
|
## 0.1.196 (2026-06-10)
|
|
4
28
|
|
|
5
29
|
### `refreshImmediately` — blokirni server-fetch z verzijsko primerjavo
|
package/dist/index.js
CHANGED
|
@@ -3508,7 +3508,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3508
3508
|
* Upload dirty items for all collections.
|
|
3509
3509
|
*/
|
|
3510
3510
|
async uploadDirtyItems(calledFrom) {
|
|
3511
|
-
var _a;
|
|
3511
|
+
var _a, _b;
|
|
3512
3512
|
const collectionBatches = [];
|
|
3513
3513
|
for (const [collectionName] of this.collections) {
|
|
3514
3514
|
const dirtyChanges = await this.dexieDb.getDirty(collectionName);
|
|
@@ -3664,6 +3664,15 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3664
3664
|
writeStartedAt,
|
|
3665
3665
|
calledFrom
|
|
3666
3666
|
);
|
|
3667
|
+
for (const batch of collectionBatches) {
|
|
3668
|
+
for (const b of batch) {
|
|
3669
|
+
const allIds = [];
|
|
3670
|
+
for (const u of b.batch.updates) allIds.push(u._id);
|
|
3671
|
+
for (const d of b.batch.deletes) allIds.push((_a = d._id) != null ? _a : d);
|
|
3672
|
+
const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(b.collection, allIds);
|
|
3673
|
+
this.callOnDirtyItemStuck(b.collection, newlyStuck, calledFrom);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3667
3676
|
throw err;
|
|
3668
3677
|
}
|
|
3669
3678
|
let sentCount = 0;
|
|
@@ -3717,8 +3726,26 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3717
3726
|
if (allSuccessIds.length > 0) {
|
|
3718
3727
|
await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
|
|
3719
3728
|
}
|
|
3729
|
+
const sentIdsForCollection = /* @__PURE__ */ new Set();
|
|
3730
|
+
for (const batch of collectionBatches) {
|
|
3731
|
+
for (const b of batch) {
|
|
3732
|
+
if (b.collection !== collection) continue;
|
|
3733
|
+
for (const u of b.batch.updates) sentIdsForCollection.add(String(u._id));
|
|
3734
|
+
for (const d of b.batch.deletes) sentIdsForCollection.add(String(d._id));
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
const retainedIds = [];
|
|
3738
|
+
for (const sid of sentIdsForCollection) {
|
|
3739
|
+
if (!allSuccessIds.find((aid) => String(aid) === sid)) {
|
|
3740
|
+
retainedIds.push(sid);
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
if (retainedIds.length > 0) {
|
|
3744
|
+
const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(collection, retainedIds);
|
|
3745
|
+
this.callOnDirtyItemStuck(collection, newlyStuck, calledFrom);
|
|
3746
|
+
}
|
|
3720
3747
|
let collectionSentCount = 0;
|
|
3721
|
-
const isWriteOnly = (
|
|
3748
|
+
const isWriteOnly = (_b = this.collections.get(collection)) == null ? void 0 : _b.writeOnly;
|
|
3722
3749
|
const insertedAndUpdated = inserted.concat(updated);
|
|
3723
3750
|
if (insertedAndUpdated.length > 0) {
|
|
3724
3751
|
const idsToCheck = [];
|
|
@@ -3857,7 +3884,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3857
3884
|
/**
|
|
3858
3885
|
* Upload dirty items for a specific collection.
|
|
3859
3886
|
*/
|
|
3860
|
-
async uploadDirtyItemsForCollection(collection) {
|
|
3887
|
+
async uploadDirtyItemsForCollection(collection, calledFrom) {
|
|
3861
3888
|
const dirtyItems = await this.dexieDb.getDirty(collection);
|
|
3862
3889
|
if (dirtyItems.length === 0) {
|
|
3863
3890
|
return { sentCount: 0 };
|
|
@@ -3955,6 +3982,24 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3955
3982
|
if (allSuccessIds.length > 0) {
|
|
3956
3983
|
await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
|
|
3957
3984
|
}
|
|
3985
|
+
const sentIdsForCollection = /* @__PURE__ */ new Set();
|
|
3986
|
+
for (const batch of collectionBatches) {
|
|
3987
|
+
for (const b of batch) {
|
|
3988
|
+
if (b.collection !== collection) continue;
|
|
3989
|
+
for (const u of b.batch.updates) sentIdsForCollection.add(String(u._id));
|
|
3990
|
+
for (const d of b.batch.deletes) sentIdsForCollection.add(String(d._id));
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
const retainedIds = [];
|
|
3994
|
+
for (const sid of sentIdsForCollection) {
|
|
3995
|
+
if (!allSuccessIds.find((aid) => String(aid) === sid)) {
|
|
3996
|
+
retainedIds.push(sid);
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
if (retainedIds.length > 0) {
|
|
4000
|
+
const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(collection, retainedIds);
|
|
4001
|
+
this.callOnDirtyItemStuck(collection, newlyStuck, calledFrom);
|
|
4002
|
+
}
|
|
3958
4003
|
if (mustRefresh && mustRefresh.length > 0) {
|
|
3959
4004
|
await this.adoptMustRefresh(
|
|
3960
4005
|
collection,
|
|
@@ -4192,6 +4237,19 @@ var _SyncEngine = class _SyncEngine {
|
|
|
4192
4237
|
}
|
|
4193
4238
|
}
|
|
4194
4239
|
}
|
|
4240
|
+
callOnDirtyItemStuck(collection, stuckMetas, calledFrom) {
|
|
4241
|
+
if (!this.callbacks.onDirtyItemStuck || stuckMetas.length === 0) return;
|
|
4242
|
+
try {
|
|
4243
|
+
this.callbacks.onDirtyItemStuck({
|
|
4244
|
+
collection,
|
|
4245
|
+
items: stuckMetas,
|
|
4246
|
+
calledFrom,
|
|
4247
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
4248
|
+
});
|
|
4249
|
+
} catch (err) {
|
|
4250
|
+
console.error(`[SyncEngine] onDirtyItemStuck callback failed: ${err}`, err);
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4195
4253
|
callOnServerSyncWrite(request, response, error, startTime, timestamp, calledFrom) {
|
|
4196
4254
|
if (!this.callbacks.onServerSyncWrite) return;
|
|
4197
4255
|
try {
|
|
@@ -4867,7 +4925,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4867
4925
|
onServerSyncWrite: config.onServerSyncWrite,
|
|
4868
4926
|
onFindNewerManyCall: config.onFindNewerManyCall,
|
|
4869
4927
|
onFindNewerManyResult: config.onFindNewerManyResult,
|
|
4870
|
-
onUploadSkip: config.onUploadSkip
|
|
4928
|
+
onUploadSkip: config.onUploadSkip,
|
|
4929
|
+
onDirtyItemStuck: config.onDirtyItemStuck
|
|
4871
4930
|
},
|
|
4872
4931
|
deps: {
|
|
4873
4932
|
getSyncMetaCache: () => this.syncMetaCache,
|
|
@@ -6330,6 +6389,59 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6330
6389
|
await this.dexieDb.clearDirtyChanges(collection);
|
|
6331
6390
|
return metas;
|
|
6332
6391
|
}
|
|
6392
|
+
/**
|
|
6393
|
+
* Return dirty entries whose `stuckSince` is set across all collections.
|
|
6394
|
+
* A stuck entry has exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload
|
|
6395
|
+
* attempts without success — typically a server-side error that prevents
|
|
6396
|
+
* the record from being accepted.
|
|
6397
|
+
*
|
|
6398
|
+
* Same shape as `getDirtyMeta()` but filtered to stuck items only.
|
|
6399
|
+
* Collections with no stuck items are omitted from the result.
|
|
6400
|
+
*/
|
|
6401
|
+
async getStuckItems() {
|
|
6402
|
+
const result = {};
|
|
6403
|
+
for (const [collectionName] of this.collections) {
|
|
6404
|
+
const metas = await this.dexieDb.getDirtyMeta(collectionName);
|
|
6405
|
+
const stuck = metas.filter((m) => m.stuckSince !== void 0);
|
|
6406
|
+
if (stuck.length > 0) {
|
|
6407
|
+
result[collectionName] = stuck;
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
return result;
|
|
6411
|
+
}
|
|
6412
|
+
/**
|
|
6413
|
+
* Discard all stuck dirty entries across ALL collections without touching
|
|
6414
|
+
* the main row data. A stuck entry is one where `stuckSince` is set
|
|
6415
|
+
* (exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload attempts).
|
|
6416
|
+
*
|
|
6417
|
+
* Fires `onBeforeDirtyClearAll` once per collection that has stuck items,
|
|
6418
|
+
* with `reason: "discard-stuck"`. Returns the `DirtyMeta[]` of every
|
|
6419
|
+
* entry that was removed.
|
|
6420
|
+
*
|
|
6421
|
+
* Does NOT await in-flight uploads — call `flushToServer()` first if a
|
|
6422
|
+
* last-chance upload is desired.
|
|
6423
|
+
*
|
|
6424
|
+
* @param calledFrom Diagnostic tag threaded through to the callback.
|
|
6425
|
+
*/
|
|
6426
|
+
async discardStuckItems(calledFrom) {
|
|
6427
|
+
const cleared = [];
|
|
6428
|
+
for (const [collectionName] of this.collections) {
|
|
6429
|
+
const metas = await this.dexieDb.getDirtyMeta(collectionName);
|
|
6430
|
+
const stuck = metas.filter((m) => m.stuckSince !== void 0);
|
|
6431
|
+
if (stuck.length === 0) continue;
|
|
6432
|
+
this.safeCallback(this.onBeforeDirtyClearAll, {
|
|
6433
|
+
reason: "discard-stuck",
|
|
6434
|
+
collection: collectionName,
|
|
6435
|
+
items: stuck,
|
|
6436
|
+
calledFrom,
|
|
6437
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
6438
|
+
});
|
|
6439
|
+
const ids = stuck.map((m) => m.id);
|
|
6440
|
+
await this.dexieDb.clearDirtyChangesBatch(collectionName, ids);
|
|
6441
|
+
for (const m of stuck) cleared.push(m);
|
|
6442
|
+
}
|
|
6443
|
+
return cleared;
|
|
6444
|
+
}
|
|
6333
6445
|
// ==================== Data Deletion ====================
|
|
6334
6446
|
async dropCollection(collection, force = false) {
|
|
6335
6447
|
this.assertCollection(collection);
|
|
@@ -7359,6 +7471,9 @@ var SyncedDb = _SyncedDb;
|
|
|
7359
7471
|
// src/db/DexieDb.ts
|
|
7360
7472
|
import Dexie from "dexie";
|
|
7361
7473
|
|
|
7474
|
+
// src/types/I_DexieDb.ts
|
|
7475
|
+
var DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS = 2;
|
|
7476
|
+
|
|
7362
7477
|
// src/utils/computeDiff.ts
|
|
7363
7478
|
function isDescendantOrEqual(path, candidate) {
|
|
7364
7479
|
if (path === candidate) return true;
|
|
@@ -7645,7 +7760,11 @@ var DexieDb = class extends Dexie {
|
|
|
7645
7760
|
baseTs: entry.baseTs,
|
|
7646
7761
|
baseRev: entry.baseRev,
|
|
7647
7762
|
createdAt: entry.createdAt,
|
|
7648
|
-
updatedAt: entry.updatedAt
|
|
7763
|
+
updatedAt: entry.updatedAt,
|
|
7764
|
+
firstUploadAttempt: entry.firstUploadAttempt,
|
|
7765
|
+
lastUploadAttempt: entry.lastUploadAttempt,
|
|
7766
|
+
numUploadAttempts: entry.numUploadAttempts,
|
|
7767
|
+
stuckSince: entry.stuckSince
|
|
7649
7768
|
});
|
|
7650
7769
|
}
|
|
7651
7770
|
return result;
|
|
@@ -7723,6 +7842,45 @@ var DexieDb = class extends Dexie {
|
|
|
7723
7842
|
}
|
|
7724
7843
|
return result;
|
|
7725
7844
|
}
|
|
7845
|
+
async incrementDirtyUploadAttempts(collection, ids) {
|
|
7846
|
+
var _a;
|
|
7847
|
+
if (ids.length === 0) return [];
|
|
7848
|
+
const keys = [];
|
|
7849
|
+
for (const id of ids) keys.push([collection, this.idToString(id)]);
|
|
7850
|
+
const entries = await this.dirtyChanges.bulkGet(keys);
|
|
7851
|
+
const now = Date.now();
|
|
7852
|
+
const toUpdate = [];
|
|
7853
|
+
const newlyStuck = [];
|
|
7854
|
+
for (let i = 0; i < entries.length; i++) {
|
|
7855
|
+
const entry = entries[i];
|
|
7856
|
+
if (!entry) continue;
|
|
7857
|
+
entry.numUploadAttempts = ((_a = entry.numUploadAttempts) != null ? _a : 0) + 1;
|
|
7858
|
+
if (entry.firstUploadAttempt === void 0) {
|
|
7859
|
+
entry.firstUploadAttempt = now;
|
|
7860
|
+
}
|
|
7861
|
+
entry.lastUploadAttempt = now;
|
|
7862
|
+
if (entry.numUploadAttempts > DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS && entry.stuckSince === void 0) {
|
|
7863
|
+
entry.stuckSince = now;
|
|
7864
|
+
newlyStuck.push({
|
|
7865
|
+
collection: entry.collection,
|
|
7866
|
+
id: entry.id,
|
|
7867
|
+
baseTs: entry.baseTs,
|
|
7868
|
+
baseRev: entry.baseRev,
|
|
7869
|
+
createdAt: entry.createdAt,
|
|
7870
|
+
updatedAt: entry.updatedAt,
|
|
7871
|
+
firstUploadAttempt: entry.firstUploadAttempt,
|
|
7872
|
+
lastUploadAttempt: entry.lastUploadAttempt,
|
|
7873
|
+
numUploadAttempts: entry.numUploadAttempts,
|
|
7874
|
+
stuckSince: entry.stuckSince
|
|
7875
|
+
});
|
|
7876
|
+
}
|
|
7877
|
+
toUpdate.push(entry);
|
|
7878
|
+
}
|
|
7879
|
+
if (toUpdate.length > 0) {
|
|
7880
|
+
await this.dirtyChanges.bulkPut(toUpdate);
|
|
7881
|
+
}
|
|
7882
|
+
return newlyStuck;
|
|
7883
|
+
}
|
|
7726
7884
|
async clearDirtyChange(collection, id) {
|
|
7727
7885
|
const stringId = this.idToString(id);
|
|
7728
7886
|
await this.dirtyChanges.delete([collection, stringId]);
|
|
@@ -8231,6 +8389,7 @@ function createStructureReader(structure, firstId) {
|
|
|
8231
8389
|
inlineObjectReadThreshold = Infinity;
|
|
8232
8390
|
return readObject();
|
|
8233
8391
|
}
|
|
8392
|
+
structure.read0 = optimizedReadObject;
|
|
8234
8393
|
if (structure.highByte === 0)
|
|
8235
8394
|
structure.read = createSecondByteReader(firstId, structure.read);
|
|
8236
8395
|
return optimizedReadObject();
|
|
@@ -8247,6 +8406,7 @@ function createStructureReader(structure, firstId) {
|
|
|
8247
8406
|
return object;
|
|
8248
8407
|
}
|
|
8249
8408
|
readObject.count = 0;
|
|
8409
|
+
structure.read0 = readObject;
|
|
8250
8410
|
if (structure.highByte === 0) {
|
|
8251
8411
|
return createSecondByteReader(firstId, readObject);
|
|
8252
8412
|
}
|
|
@@ -8646,7 +8806,7 @@ var recordDefinition = (id, highByte) => {
|
|
|
8646
8806
|
}
|
|
8647
8807
|
currentStructures[id] = structure;
|
|
8648
8808
|
structure.read = createStructureReader(structure, firstByte);
|
|
8649
|
-
return structure.read();
|
|
8809
|
+
return (structure.read0 || structure.read)();
|
|
8650
8810
|
};
|
|
8651
8811
|
currentExtensions[0] = () => {
|
|
8652
8812
|
};
|
|
@@ -9024,6 +9184,7 @@ var Packr = class extends Unpackr {
|
|
|
9024
9184
|
let newSharedData = (packr3._prepareStructures || prepareStructures)(structures, packr3);
|
|
9025
9185
|
if (!encodingError) {
|
|
9026
9186
|
if (packr3.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
|
|
9187
|
+
structures.uninitialized = true;
|
|
9027
9188
|
return packr3.pack(value, encodeOptions);
|
|
9028
9189
|
}
|
|
9029
9190
|
packr3.lastNamedStructuresLength = sharedLength;
|
package/dist/src/db/DexieDb.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ export declare class DexieDb extends Dexie implements I_DexieDb {
|
|
|
46
46
|
}>): Promise<void>;
|
|
47
47
|
getDirtyChange(collection: string, id: Id): Promise<DirtyChange | undefined>;
|
|
48
48
|
getDirtyChangesBatch(collection: string, ids: Id[]): Promise<Map<string, DirtyChange>>;
|
|
49
|
+
incrementDirtyUploadAttempts(collection: string, ids: Id[]): Promise<DirtyMeta[]>;
|
|
49
50
|
clearDirtyChange(collection: string, id: Id): Promise<void>;
|
|
50
51
|
clearDirtyChangesBatch(collection: string, ids: Id[]): Promise<void>;
|
|
51
52
|
clearDirtyChanges(collection: string): Promise<void>;
|
|
@@ -333,6 +333,31 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
333
333
|
* upload is desired.
|
|
334
334
|
*/
|
|
335
335
|
clearDirty(collection?: string, ids?: Id[], calledFrom?: string): Promise<DirtyMeta[]>;
|
|
336
|
+
/**
|
|
337
|
+
* Return dirty entries whose `stuckSince` is set across all collections.
|
|
338
|
+
* A stuck entry has exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload
|
|
339
|
+
* attempts without success — typically a server-side error that prevents
|
|
340
|
+
* the record from being accepted.
|
|
341
|
+
*
|
|
342
|
+
* Same shape as `getDirtyMeta()` but filtered to stuck items only.
|
|
343
|
+
* Collections with no stuck items are omitted from the result.
|
|
344
|
+
*/
|
|
345
|
+
getStuckItems(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
|
|
346
|
+
/**
|
|
347
|
+
* Discard all stuck dirty entries across ALL collections without touching
|
|
348
|
+
* the main row data. A stuck entry is one where `stuckSince` is set
|
|
349
|
+
* (exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload attempts).
|
|
350
|
+
*
|
|
351
|
+
* Fires `onBeforeDirtyClearAll` once per collection that has stuck items,
|
|
352
|
+
* with `reason: "discard-stuck"`. Returns the `DirtyMeta[]` of every
|
|
353
|
+
* entry that was removed.
|
|
354
|
+
*
|
|
355
|
+
* Does NOT await in-flight uploads — call `flushToServer()` first if a
|
|
356
|
+
* last-chance upload is desired.
|
|
357
|
+
*
|
|
358
|
+
* @param calledFrom Diagnostic tag threaded through to the callback.
|
|
359
|
+
*/
|
|
360
|
+
discardStuckItems(calledFrom?: string): Promise<DirtyMeta[]>;
|
|
336
361
|
dropCollection(collection: string, force?: boolean): Promise<void>;
|
|
337
362
|
dropDatabase(force?: boolean): Promise<void>;
|
|
338
363
|
/**
|
|
@@ -62,7 +62,7 @@ export declare class SyncEngine implements I_SyncEngine {
|
|
|
62
62
|
/**
|
|
63
63
|
* Upload dirty items for a specific collection.
|
|
64
64
|
*/
|
|
65
|
-
uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
|
|
65
|
+
uploadDirtyItemsForCollection(collection: string, calledFrom?: string): Promise<UploadResult>;
|
|
66
66
|
/**
|
|
67
67
|
* Process incoming server data for a single collection.
|
|
68
68
|
* Used by referToServer to process findNewer results.
|
|
@@ -89,5 +89,6 @@ export declare class SyncEngine implements I_SyncEngine {
|
|
|
89
89
|
private callOnFindNewerManyResult;
|
|
90
90
|
private callOnServerWriteRequest;
|
|
91
91
|
private callOnServerWriteResult;
|
|
92
|
+
private callOnDirtyItemStuck;
|
|
92
93
|
private callOnServerSyncWrite;
|
|
93
94
|
}
|
|
@@ -267,6 +267,7 @@ export interface SyncEngineCallbacks {
|
|
|
267
267
|
onFindNewerManyCall?: (info: FindNewerManyCallInfo) => void;
|
|
268
268
|
onFindNewerManyResult?: (info: FindNewerManyResultInfo) => void;
|
|
269
269
|
onUploadSkip?: (info: import("../../types/I_SyncedDb").UploadSkipInfo) => void;
|
|
270
|
+
onDirtyItemStuck?: (info: import("../../types/I_SyncedDb").DirtyItemStuckInfo) => void;
|
|
270
271
|
}
|
|
271
272
|
export interface SyncEngineDeps {
|
|
272
273
|
getSyncMetaCache: () => Map<string, SyncMeta>;
|
|
@@ -326,7 +327,7 @@ export interface I_SyncEngine {
|
|
|
326
327
|
/** Upload dirty items for all collections. */
|
|
327
328
|
uploadDirtyItems(calledFrom?: string): Promise<UploadResult>;
|
|
328
329
|
/** Upload dirty items for a specific collection. */
|
|
329
|
-
uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
|
|
330
|
+
uploadDirtyItemsForCollection(collection: string, calledFrom?: string): Promise<UploadResult>;
|
|
330
331
|
/** Process incoming server data for a single collection (used by referToServer). */
|
|
331
332
|
processCollectionServerData(collectionName: string, serverData: import("../../types/DbEntity").LocalDbEntity[], opts?: {
|
|
332
333
|
source?: SyncSource;
|
|
@@ -7,6 +7,11 @@ export interface SyncMeta {
|
|
|
7
7
|
collection: string;
|
|
8
8
|
lastSyncTs?: any;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* After this many upload attempts without success, the item is considered
|
|
12
|
+
* stuck and `stuckSince` is set on the DirtyChange entry.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS = 2;
|
|
10
15
|
/**
|
|
11
16
|
* Dirty change entry - tracks accumulated changes for a record
|
|
12
17
|
* Stored in _dirty_changes table with composite key [collection, id]
|
|
@@ -26,6 +31,14 @@ export interface DirtyChange {
|
|
|
26
31
|
createdAt: number;
|
|
27
32
|
/** When last change was accumulated */
|
|
28
33
|
updatedAt: number;
|
|
34
|
+
/** When this dirty entry was first attempted for upload (ms timestamp) */
|
|
35
|
+
firstUploadAttempt?: number;
|
|
36
|
+
/** When this dirty entry was last attempted for upload (ms timestamp) */
|
|
37
|
+
lastUploadAttempt?: number;
|
|
38
|
+
/** How many upload attempts have been made so far */
|
|
39
|
+
numUploadAttempts?: number;
|
|
40
|
+
/** Set when numUploadAttempts > DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS (ms timestamp) */
|
|
41
|
+
stuckSince?: number;
|
|
29
42
|
}
|
|
30
43
|
/**
|
|
31
44
|
* Meta fields of a DirtyChange entry, without the `changes` payload.
|
|
@@ -110,6 +123,8 @@ export interface I_DexieDb {
|
|
|
110
123
|
getDirtyChange(collection: string, id: Id): Promise<DirtyChange | undefined>;
|
|
111
124
|
/** Get dirty change entries for multiple records (batch) */
|
|
112
125
|
getDirtyChangesBatch(collection: string, ids: Id[]): Promise<Map<string, DirtyChange>>;
|
|
126
|
+
/** Increment upload attempt counters for retained dirty entries. Returns newly stuck DirtyMeta[]. */
|
|
127
|
+
incrementDirtyUploadAttempts(collection: string, ids: Id[]): Promise<DirtyMeta[]>;
|
|
113
128
|
/** Clear dirty change for a record (after successful sync) */
|
|
114
129
|
clearDirtyChange(collection: string, id: Id): Promise<void>;
|
|
115
130
|
/** Clear dirty changes for multiple records (batch) */
|
|
@@ -91,6 +91,21 @@ export interface SaveIdMismatchInfo {
|
|
|
91
91
|
/** Timestamp when mismatch detected */
|
|
92
92
|
timestamp: Date;
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Payload za `onDirtyItemStuck` callback. Sproži se, ko `incrementDirtyUploadAttempts`
|
|
96
|
+
* nastavi `stuckSince` na DirtyChange entry-ju — pomeni, da je item presegel
|
|
97
|
+
* `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` neuspelih nalaganj.
|
|
98
|
+
*/
|
|
99
|
+
export interface DirtyItemStuckInfo {
|
|
100
|
+
/** Collection name */
|
|
101
|
+
collection: string;
|
|
102
|
+
/** Meta podatki stuck itemov (vsebujejo `stuckSince`, `numUploadAttempts`, itd.) */
|
|
103
|
+
items: import("./I_DexieDb").DirtyMeta[];
|
|
104
|
+
/** Optional caller tag */
|
|
105
|
+
calledFrom?: string;
|
|
106
|
+
/** Timestamp when stuck was detected */
|
|
107
|
+
timestamp: Date;
|
|
108
|
+
}
|
|
94
109
|
/**
|
|
95
110
|
* Callback payload fired by `clearDirty()` when called WITHOUT a
|
|
96
111
|
* `collection` argument (clear-all path). One invocation per
|
|
@@ -741,6 +756,19 @@ export interface SyncedDbConfig {
|
|
|
741
756
|
* reveals which dirty entries hit which skip path.
|
|
742
757
|
*/
|
|
743
758
|
onUploadSkip?: (info: UploadSkipInfo) => void;
|
|
759
|
+
/**
|
|
760
|
+
* Fired when a dirty item exceeds `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS`
|
|
761
|
+
* upload attempts without server acknowledgement. The item's `stuckSince`
|
|
762
|
+
* is set and it will remain dirty until manually cleared via
|
|
763
|
+
* `discardStuckItems()` or `clearDirty()`, or until a future upload
|
|
764
|
+
* succeeds (which clears the dirty entry normally).
|
|
765
|
+
*
|
|
766
|
+
* Fires once per collection per `uploadDirtyItems` cycle, with ALL items
|
|
767
|
+
* that newly became stuck in that cycle (not previously stuck items).
|
|
768
|
+
*
|
|
769
|
+
* Use for alerting / syslog / automatic recovery flows.
|
|
770
|
+
*/
|
|
771
|
+
onDirtyItemStuck?: (info: DirtyItemStuckInfo) => void;
|
|
744
772
|
/**
|
|
745
773
|
* Callback when `save(collection, id, update)` is called with `update._id`
|
|
746
774
|
* that does NOT match `id`. Library auto-strips `_id` from update to prevent
|
|
@@ -1228,6 +1256,20 @@ export interface I_SyncedDb {
|
|
|
1228
1256
|
* ki imajo vsaj en dirty zapis. Kolekcije brez dirty vnosov niso vključene.
|
|
1229
1257
|
*/
|
|
1230
1258
|
getDirtyMeta(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
|
|
1259
|
+
/**
|
|
1260
|
+
* Return dirty entries whose `stuckSince` is set across all collections.
|
|
1261
|
+
* A stuck entry has exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload
|
|
1262
|
+
* attempts without success. Collections with no stuck items are omitted.
|
|
1263
|
+
*/
|
|
1264
|
+
getStuckItems(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
|
|
1265
|
+
/**
|
|
1266
|
+
* Discard all stuck dirty entries across ALL collections without touching
|
|
1267
|
+
* the main row data. Fires `onBeforeDirtyClearAll` per collection with
|
|
1268
|
+
* `reason: "discard-stuck"`. Returns the `DirtyMeta[]` of removed entries.
|
|
1269
|
+
*
|
|
1270
|
+
* @param calledFrom Diagnostic tag threaded through to the callback.
|
|
1271
|
+
*/
|
|
1272
|
+
discardStuckItems(calledFrom?: string): Promise<DirtyMeta[]>;
|
|
1231
1273
|
/**
|
|
1232
1274
|
* Drops a collection, ensuring no data loss.
|
|
1233
1275
|
* - Throws if offline or forcedOffline (unless force=true)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cry-synced-db-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.199",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -30,16 +30,16 @@
|
|
|
30
30
|
"@types/bun": "latest",
|
|
31
31
|
"bson": "^7.2.0",
|
|
32
32
|
"cry-ebus-proxy": "^2.0.0",
|
|
33
|
-
"dexie": "^4.4.
|
|
34
|
-
"esbuild": "^0.28.
|
|
33
|
+
"dexie": "^4.4.3",
|
|
34
|
+
"esbuild": "^0.28.1",
|
|
35
35
|
"fake-indexeddb": "^6.2.5",
|
|
36
36
|
"typescript": "^6",
|
|
37
|
-
"vitest": "^4.1.
|
|
37
|
+
"vitest": "^4.1.8"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"cry-db": "^2.5.0",
|
|
41
|
-
"cry-helpers": "^2.1.
|
|
42
|
-
"msgpackr": "^2.0.
|
|
41
|
+
"cry-helpers": "^2.1.205",
|
|
42
|
+
"msgpackr": "^2.0.4",
|
|
43
43
|
"superjson": "^2.2.6"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|