cry-synced-db-client 0.1.147 → 0.1.150
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 +66 -0
- package/dist/index.js +73 -40
- package/dist/src/db/managers/InMemManager.d.ts +9 -1
- package/dist/src/db/sync/SyncEngine.d.ts +8 -1
- package/dist/src/db/types/managers.d.ts +16 -6
- package/dist/src/types/I_InMemDb.d.ts +23 -2
- package/dist/src/types/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### `SyncSource` flag in `I_InMemDb.saveMany` / `deleteManyByIds`
|
|
6
|
+
|
|
7
|
+
The in-mem write API now accepts an opcijski `opts: { source?: SyncSource }`
|
|
8
|
+
parameter that labels the **origin** of each batch:
|
|
9
|
+
|
|
10
|
+
- `'initial'` — first full sync of a collection (no prior `lastSyncTs`)
|
|
11
|
+
- `'refresh'` — `referToServerSync` background reload (timestamp = 0
|
|
12
|
+
for already-synced collection, e.g. when the app needs records outside
|
|
13
|
+
the current sync window)
|
|
14
|
+
- `'incremental'` — everything else: delta sync, server WS push,
|
|
15
|
+
optimistic local writes (`save` / `insert` / `deleteOne` / `deleteMany`
|
|
16
|
+
/ `hardDelete`), `findById` / `findByIds` `returnDeleted` server fetch,
|
|
17
|
+
`ensureItemsAreLoaded`, `refreshByIdsInBackground`, eviction deletes
|
|
18
|
+
|
|
19
|
+
**Default**: when no `opts` is passed, `InMemManager.writeBatch`
|
|
20
|
+
normalizes to `'incremental'`. Backward compatible — existing
|
|
21
|
+
`I_InMemDb` implementations that ignore `opts` keep working unchanged.
|
|
22
|
+
|
|
23
|
+
The signal is **advisory**: it does not change sync semantics or
|
|
24
|
+
conflict resolution. Consumers can use it to apply per-source policies
|
|
25
|
+
(e.g. selective Vue reactivity in the in-mem store: items first seen
|
|
26
|
+
via `'incremental'` upgrade to reactive proxies; `'initial'` /
|
|
27
|
+
`'refresh'` floods stay as plain JS objects).
|
|
28
|
+
|
|
29
|
+
Threading: `SyncEngine` resolves `'initial'` per collection from
|
|
30
|
+
`syncMetaCache` lookup at the start of each `sync()` cycle (no prior
|
|
31
|
+
`lastSyncTs` ⇒ `'initial'`), and propagates the source via the
|
|
32
|
+
`writeToInMemBatch` deps callback through `InMemManager` to
|
|
33
|
+
`I_InMemDb.saveMany` / `deleteManyByIds`. `SyncedDb.referToServerSync`
|
|
34
|
+
hardcodes `'refresh'`; `syncCollectionForFind` derives source from the
|
|
35
|
+
local `timestamp === 0` check.
|
|
36
|
+
|
|
37
|
+
Tests: `test/syncSource.test.ts` (9 cases) covers initial / incremental
|
|
38
|
+
/ refresh propagation across all public write paths. `MockInMemDb`
|
|
39
|
+
exposes `recordedCalls: RecordedInMemCall[]` for assertion.
|
|
40
|
+
|
|
41
|
+
### `uploadDirtyItems` follow-up pass — drain in-sync writes immediately
|
|
42
|
+
|
|
43
|
+
Writes that land **during** a sync iteration had their
|
|
44
|
+
`scheduleRestUpload()` guarded out by `isSyncing()` (silent drop, no
|
|
45
|
+
re-schedule), so they sat in `_dirty_changes` until the next 60s
|
|
46
|
+
auto-sync tick. Particularly visible for high-frequency writeOnly
|
|
47
|
+
collections (e.g. `prehodi`, written on every route change) on tablets
|
|
48
|
+
with intensive navigation: dirty items piled up until the next tick.
|
|
49
|
+
|
|
50
|
+
`SyncEngine.sync()` now performs a single follow-up pass right after the
|
|
51
|
+
primary `uploadDirtyItems()` call:
|
|
52
|
+
|
|
53
|
+
1. `flushAllPendingChanges()` — forces any in-flight 500ms Dexie
|
|
54
|
+
debounces to land in `_dirty_changes` before the second snapshot.
|
|
55
|
+
2. `uploadDirtyItems(calledFrom + ":followUp")` — drains entries that
|
|
56
|
+
accumulated during the first pass's server roundtrip.
|
|
57
|
+
|
|
58
|
+
Single pass (not a loop) — bounded work; later writes after sync
|
|
59
|
+
completes will trigger their own `scheduleRestUpload()` once the
|
|
60
|
+
`isSyncing` flag clears. Sequential `await` ordering means no concurrent
|
|
61
|
+
server roundtrips and no new race conditions vs. the existing
|
|
62
|
+
snapshot-then-clear pattern in `_dirty_changes`. Stats from the
|
|
63
|
+
follow-up pass are merged into `uploadStats` so `onSyncEnd` /
|
|
64
|
+
`collectionStats.sentCount` reflect both passes.
|
|
65
|
+
|
|
66
|
+
Errors in the follow-up are caught by the same outer `try/catch` as the
|
|
67
|
+
first pass — a follow-up failure does not roll back the first pass's
|
|
68
|
+
already-cleared dirty entries; affected items are caught at the next
|
|
69
|
+
sync tick (same retry semantics as before).
|
|
70
|
+
|
|
5
71
|
### Auto-eviction co-located with sync — one round-trip total
|
|
6
72
|
|
|
7
73
|
When `evictStaleRecordsEveryHrs > 0` and the interval has elapsed, the
|
package/dist/index.js
CHANGED
|
@@ -272,12 +272,19 @@ var InMemManager = class {
|
|
|
272
272
|
/**
|
|
273
273
|
* Write batch of items to in-mem.
|
|
274
274
|
* Handles metadata callbacks automatically when useObjectMetadata is enabled.
|
|
275
|
+
*
|
|
276
|
+
* @param opts.source — origin of the write ('initial' | 'refresh' | 'incremental').
|
|
277
|
+
* Implementacija I_InMemDb lahko ta signal uporabi za optimizacije
|
|
278
|
+
* (npr. selektivna reactivity); ne vpliva na sync semantiko.
|
|
279
|
+
* Default: 'incremental'.
|
|
275
280
|
*/
|
|
276
|
-
writeBatch(collection, items, operation) {
|
|
281
|
+
writeBatch(collection, items, operation, opts) {
|
|
282
|
+
var _a;
|
|
277
283
|
if (items.length === 0) return;
|
|
278
284
|
const config = this.collections.get(collection);
|
|
285
|
+
const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
|
|
279
286
|
if (operation === "upsert") {
|
|
280
|
-
this.inMemDb.saveMany(collection, items);
|
|
287
|
+
this.inMemDb.saveMany(collection, items, { source });
|
|
281
288
|
if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
|
|
282
289
|
const ids = [];
|
|
283
290
|
for (const item of items) ids.push(item._id);
|
|
@@ -304,7 +311,7 @@ var InMemManager = class {
|
|
|
304
311
|
} else if (operation === "delete") {
|
|
305
312
|
const ids = [];
|
|
306
313
|
for (const item of items) ids.push(item._id);
|
|
307
|
-
this.inMemDb.deleteManyByIds(collection, ids);
|
|
314
|
+
this.inMemDb.deleteManyByIds(collection, ids, { source });
|
|
308
315
|
if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
|
|
309
316
|
this.deleteObjectsMetadataInternal(collection, ids);
|
|
310
317
|
}
|
|
@@ -2478,10 +2485,13 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2478
2485
|
const findNewerManyStartTime = Date.now();
|
|
2479
2486
|
const collectionState = /* @__PURE__ */ new Map();
|
|
2480
2487
|
for (const [name] of configMap) {
|
|
2488
|
+
const prior = this.deps.getSyncMetaCache().get(name);
|
|
2489
|
+
const isInitial = !(prior == null ? void 0 : prior.lastSyncTs);
|
|
2481
2490
|
collectionState.set(name, {
|
|
2482
2491
|
maxTs: void 0,
|
|
2483
2492
|
conflicts: 0,
|
|
2484
|
-
receivedCount: 0
|
|
2493
|
+
receivedCount: 0,
|
|
2494
|
+
source: isInitial ? "initial" : "incremental"
|
|
2485
2495
|
});
|
|
2486
2496
|
}
|
|
2487
2497
|
try {
|
|
@@ -2499,7 +2509,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2499
2509
|
if (!config) return;
|
|
2500
2510
|
const state = collectionState.get(collection);
|
|
2501
2511
|
state.receivedCount += items.length;
|
|
2502
|
-
const stats = await this.processIncomingServerData(collection, config, items);
|
|
2512
|
+
const stats = await this.processIncomingServerData(collection, config, items, state.source);
|
|
2503
2513
|
state.conflicts += stats.conflictsResolved;
|
|
2504
2514
|
if (stats.maxTs) {
|
|
2505
2515
|
if (!state.maxTs || this.compareTimestamps(stats.maxTs, state.maxTs) > 0) {
|
|
@@ -2553,6 +2563,17 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2553
2563
|
let uploadStats = { sentCount: 0 };
|
|
2554
2564
|
try {
|
|
2555
2565
|
uploadStats = await this.uploadDirtyItems(calledFrom);
|
|
2566
|
+
await this.deps.flushAllPendingChanges();
|
|
2567
|
+
const followUp = await this.uploadDirtyItems(`${calledFrom != null ? calledFrom : "sync"}:followUp`);
|
|
2568
|
+
if (followUp.sentCount > 0) {
|
|
2569
|
+
uploadStats.sentCount += followUp.sentCount;
|
|
2570
|
+
if (followUp.collectionSentCounts) {
|
|
2571
|
+
uploadStats.collectionSentCounts = uploadStats.collectionSentCounts || {};
|
|
2572
|
+
for (const [c, n] of Object.entries(followUp.collectionSentCounts)) {
|
|
2573
|
+
uploadStats.collectionSentCounts[c] = (uploadStats.collectionSentCounts[c] || 0) + n;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2556
2577
|
} catch (err) {
|
|
2557
2578
|
console.error(
|
|
2558
2579
|
"uploadDirtyItems failed (download succeeded, staying online):",
|
|
@@ -2712,13 +2733,14 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2712
2733
|
await this.dexieDb.deleteMany(collection, dexieDeleteIds);
|
|
2713
2734
|
}
|
|
2714
2735
|
if (inMemUpdateBatch.length > 0) {
|
|
2715
|
-
this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert");
|
|
2736
|
+
this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert", { source: "incremental" });
|
|
2716
2737
|
}
|
|
2717
2738
|
if (inMemDeleteIds.length > 0) {
|
|
2718
2739
|
this.deps.writeToInMemBatch(
|
|
2719
2740
|
collection,
|
|
2720
2741
|
inMemDeleteIds.map((id) => ({ _id: id })),
|
|
2721
|
-
"delete"
|
|
2742
|
+
"delete",
|
|
2743
|
+
{ source: "incremental" }
|
|
2722
2744
|
);
|
|
2723
2745
|
}
|
|
2724
2746
|
}
|
|
@@ -2734,7 +2756,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2734
2756
|
}
|
|
2735
2757
|
await this.dexieDb.deleteMany(collection, deleteIds);
|
|
2736
2758
|
if (!isWriteOnly) {
|
|
2737
|
-
this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete");
|
|
2759
|
+
this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete", { source: "incremental" });
|
|
2738
2760
|
}
|
|
2739
2761
|
sentCount += deleted.length;
|
|
2740
2762
|
collectionSentCount += deleted.length;
|
|
@@ -2844,17 +2866,23 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2844
2866
|
/**
|
|
2845
2867
|
* Process incoming server data for a single collection.
|
|
2846
2868
|
* Used by referToServer to process findNewer results.
|
|
2869
|
+
*
|
|
2870
|
+
* @param opts.source — origin label posredovan v writeBatch → inMemDb.
|
|
2871
|
+
* Default `'incremental'`. Caller naj uporabi `'refresh'` za množični
|
|
2872
|
+
* reload (referToServerSync) in `'initial'` za first-time fill.
|
|
2847
2873
|
*/
|
|
2848
|
-
async processCollectionServerData(collectionName, serverData) {
|
|
2874
|
+
async processCollectionServerData(collectionName, serverData, opts) {
|
|
2875
|
+
var _a;
|
|
2849
2876
|
const config = this.collections.get(collectionName);
|
|
2850
2877
|
if (!config) return { updatedIds: [] };
|
|
2851
|
-
const
|
|
2878
|
+
const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
|
|
2879
|
+
const result = await this.processIncomingServerData(collectionName, config, serverData, source);
|
|
2852
2880
|
if (result.updatedIds.length > 0) {
|
|
2853
2881
|
this.deps.broadcastUpdates({ [collectionName]: result.updatedIds });
|
|
2854
2882
|
}
|
|
2855
2883
|
return { updatedIds: result.updatedIds };
|
|
2856
2884
|
}
|
|
2857
|
-
async processIncomingServerData(collectionName, config, serverData) {
|
|
2885
|
+
async processIncomingServerData(collectionName, config, serverData, source = "incremental") {
|
|
2858
2886
|
if (serverData.length === 0) {
|
|
2859
2887
|
return { conflictsResolved: 0, maxTs: void 0, updatedIds: [] };
|
|
2860
2888
|
}
|
|
@@ -2916,13 +2944,14 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2916
2944
|
await this.dexieDb.saveMany(collectionName, dexieBatch);
|
|
2917
2945
|
}
|
|
2918
2946
|
if (inMemSaveBatch.length > 0) {
|
|
2919
|
-
this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert");
|
|
2947
|
+
this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert", { source });
|
|
2920
2948
|
}
|
|
2921
2949
|
if (inMemDeleteIds.length > 0) {
|
|
2922
2950
|
this.deps.writeToInMemBatch(
|
|
2923
2951
|
collectionName,
|
|
2924
2952
|
inMemDeleteIds.map((id) => ({ _id: id })),
|
|
2925
|
-
"delete"
|
|
2953
|
+
"delete",
|
|
2954
|
+
{ source }
|
|
2926
2955
|
);
|
|
2927
2956
|
}
|
|
2928
2957
|
}
|
|
@@ -3196,12 +3225,12 @@ var ServerUpdateHandler = class {
|
|
|
3196
3225
|
await this.dexieDb.clearDirtyChange(collection, serverItem._id);
|
|
3197
3226
|
}
|
|
3198
3227
|
if (!serverItem._deleted && !serverItem._archived) {
|
|
3199
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
|
|
3228
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
|
|
3200
3229
|
}
|
|
3201
3230
|
} else {
|
|
3202
3231
|
await this.dexieDb.insert(collection, serverItem);
|
|
3203
3232
|
if (!serverItem._deleted && !serverItem._archived) {
|
|
3204
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
|
|
3233
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
|
|
3205
3234
|
}
|
|
3206
3235
|
}
|
|
3207
3236
|
}
|
|
@@ -3230,7 +3259,7 @@ var ServerUpdateHandler = class {
|
|
|
3230
3259
|
const currentInMemState = Object.assign({}, localItem, pendingChange.data);
|
|
3231
3260
|
const merged = this.mergeLocalWithDelta(currentInMemState, serverDelta);
|
|
3232
3261
|
if (!merged._deleted && !merged._archived) {
|
|
3233
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
3262
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
|
|
3234
3263
|
}
|
|
3235
3264
|
return;
|
|
3236
3265
|
}
|
|
@@ -3242,16 +3271,16 @@ var ServerUpdateHandler = class {
|
|
|
3242
3271
|
await this.dexieDb.save(collection, serverDelta._id, merged);
|
|
3243
3272
|
}
|
|
3244
3273
|
if (!merged._deleted && !merged._archived) {
|
|
3245
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
3274
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
|
|
3246
3275
|
}
|
|
3247
3276
|
} else {
|
|
3248
3277
|
if (!metaChanged) return;
|
|
3249
3278
|
const merged = this.mergeLocalWithDelta(localItem, serverDelta);
|
|
3250
3279
|
await this.dexieDb.save(collection, serverDelta._id, merged);
|
|
3251
3280
|
if (!merged._deleted && !merged._archived) {
|
|
3252
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
3281
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
|
|
3253
3282
|
} else {
|
|
3254
|
-
this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete");
|
|
3283
|
+
this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete", { source: "incremental" });
|
|
3255
3284
|
}
|
|
3256
3285
|
}
|
|
3257
3286
|
}
|
|
@@ -3267,7 +3296,7 @@ var ServerUpdateHandler = class {
|
|
|
3267
3296
|
} else {
|
|
3268
3297
|
await this.dexieDb.deleteOne(collection, id);
|
|
3269
3298
|
}
|
|
3270
|
-
this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete");
|
|
3299
|
+
this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
|
|
3271
3300
|
}
|
|
3272
3301
|
// ============================================================
|
|
3273
3302
|
// Private Helpers
|
|
@@ -3592,8 +3621,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
3592
3621
|
isLeader: () => this.leaderElection.isLeader(),
|
|
3593
3622
|
getCollections: () => this.collections,
|
|
3594
3623
|
dexieDb: this.dexieDb,
|
|
3595
|
-
writeToInMemBatch: (collection, items, operation) => {
|
|
3596
|
-
this.inMemManager.writeBatch(collection, items, operation);
|
|
3624
|
+
writeToInMemBatch: (collection, items, operation, opts) => {
|
|
3625
|
+
this.inMemManager.writeBatch(collection, items, operation, opts);
|
|
3597
3626
|
},
|
|
3598
3627
|
isSyncAllowed: (collection) => this.isSyncAllowed(collection),
|
|
3599
3628
|
reloadCollectionFromDexie: (collection) => this.loadCollectionToInMem(collection).then(() => void 0)
|
|
@@ -3672,8 +3701,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
3672
3701
|
deps: {
|
|
3673
3702
|
getSyncMetaCache: () => this.syncMetaCache,
|
|
3674
3703
|
setSyncMetaCache: (collection, meta) => this.syncMetaCache.set(collection, meta),
|
|
3675
|
-
writeToInMemBatch: (collection, items, operation) => {
|
|
3676
|
-
this.inMemManager.writeBatch(collection, items, operation);
|
|
3704
|
+
writeToInMemBatch: (collection, items, operation, opts) => {
|
|
3705
|
+
this.inMemManager.writeBatch(collection, items, operation, opts);
|
|
3677
3706
|
},
|
|
3678
3707
|
getInMemById: (collection, id) => this.inMemDb.getById(collection, id),
|
|
3679
3708
|
withSyncTimeout: (promise, operation) => this.connectionManager.withSyncTimeout(promise, operation),
|
|
@@ -3697,8 +3726,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
3697
3726
|
deps: {
|
|
3698
3727
|
isLeader: () => this.leaderElection.isLeader(),
|
|
3699
3728
|
canReceiveServerUpdates: () => this.connectionManager.canReceiveServerUpdates(),
|
|
3700
|
-
writeToInMemBatch: (collection, items, operation) => {
|
|
3701
|
-
this.inMemManager.writeBatch(collection, items, operation);
|
|
3729
|
+
writeToInMemBatch: (collection, items, operation, opts) => {
|
|
3730
|
+
this.inMemManager.writeBatch(collection, items, operation, opts);
|
|
3702
3731
|
},
|
|
3703
3732
|
broadcastUpdates: (updates) => this.crossTabSync.broadcastMetaUpdate(updates),
|
|
3704
3733
|
getPendingChange: (collection, id) => this.pendingChanges.getPendingChange(collection, id),
|
|
@@ -4007,7 +4036,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4007
4036
|
if (serverItem) {
|
|
4008
4037
|
await this.dexieDb.saveMany(collection, [serverItem]);
|
|
4009
4038
|
if (!serverItem._deleted && !serverItem._archived) {
|
|
4010
|
-
this.inMemManager.writeBatch(collection, [serverItem], "upsert");
|
|
4039
|
+
this.inMemManager.writeBatch(collection, [serverItem], "upsert", { source: "incremental" });
|
|
4011
4040
|
}
|
|
4012
4041
|
}
|
|
4013
4042
|
} catch (e) {
|
|
@@ -4066,7 +4095,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4066
4095
|
await this.dexieDb.saveMany(collection, serverItems);
|
|
4067
4096
|
const toInMem = serverItems.filter((s) => !s._deleted && !s._archived);
|
|
4068
4097
|
if (toInMem.length > 0) {
|
|
4069
|
-
this.inMemManager.writeBatch(collection, toInMem, "upsert");
|
|
4098
|
+
this.inMemManager.writeBatch(collection, toInMem, "upsert", { source: "incremental" });
|
|
4070
4099
|
}
|
|
4071
4100
|
}
|
|
4072
4101
|
} catch (e) {
|
|
@@ -4185,7 +4214,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4185
4214
|
"syncCollectionForFind"
|
|
4186
4215
|
);
|
|
4187
4216
|
if (serverData.length > 0) {
|
|
4188
|
-
|
|
4217
|
+
const source = timestamp === 0 ? "initial" : "incremental";
|
|
4218
|
+
await this.syncEngine.processCollectionServerData(collection, serverData, { source });
|
|
4189
4219
|
}
|
|
4190
4220
|
} catch (e) {
|
|
4191
4221
|
}
|
|
@@ -4203,7 +4233,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4203
4233
|
"referToServer"
|
|
4204
4234
|
).then(async (serverData) => {
|
|
4205
4235
|
if (serverData.length > 0) {
|
|
4206
|
-
await this.syncEngine.processCollectionServerData(collection, serverData);
|
|
4236
|
+
await this.syncEngine.processCollectionServerData(collection, serverData, { source: "refresh" });
|
|
4207
4237
|
}
|
|
4208
4238
|
}).catch((err) => {
|
|
4209
4239
|
console.error(`referToServer failed for ${collection}:`, err);
|
|
@@ -4225,7 +4255,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4225
4255
|
"refreshInBackground"
|
|
4226
4256
|
).then(async (serverItems) => {
|
|
4227
4257
|
if (!serverItems || serverItems.length === 0) return;
|
|
4228
|
-
await this.syncEngine.processCollectionServerData(collection, serverItems);
|
|
4258
|
+
await this.syncEngine.processCollectionServerData(collection, serverItems, { source: "incremental" });
|
|
4229
4259
|
}).catch((err) => {
|
|
4230
4260
|
console.error(`refreshInBackground failed for ${collection}:`, err);
|
|
4231
4261
|
});
|
|
@@ -4263,7 +4293,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4263
4293
|
await this.dexieDb.saveMany(collection, toSaveDexie);
|
|
4264
4294
|
}
|
|
4265
4295
|
if (toSaveInMem.length > 0) {
|
|
4266
|
-
this.inMemManager.writeBatch(collection, toSaveInMem, "upsert");
|
|
4296
|
+
this.inMemManager.writeBatch(collection, toSaveInMem, "upsert", { source: "incremental" });
|
|
4267
4297
|
}
|
|
4268
4298
|
}
|
|
4269
4299
|
// ==================== Write Operations ====================
|
|
@@ -4298,7 +4328,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4298
4328
|
const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
|
|
4299
4329
|
const merged = __spreadValues(__spreadValues({}, currentMem || existing || { _id: id }), update);
|
|
4300
4330
|
if (!isWriteOnly && !(existing == null ? void 0 : existing._deleted) && !(existing == null ? void 0 : existing._archived)) {
|
|
4301
|
-
this.inMemManager.writeBatch(collection, [merged], "upsert");
|
|
4331
|
+
this.inMemManager.writeBatch(collection, [merged], "upsert", { source: "incremental" });
|
|
4302
4332
|
}
|
|
4303
4333
|
return merged;
|
|
4304
4334
|
}
|
|
@@ -4338,7 +4368,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4338
4368
|
});
|
|
4339
4369
|
this.pendingChanges.schedule(collection, id, newData, 0, "insert");
|
|
4340
4370
|
if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
|
|
4341
|
-
this.inMemManager.writeBatch(collection, [newData], "upsert");
|
|
4371
|
+
this.inMemManager.writeBatch(collection, [newData], "upsert", { source: "incremental" });
|
|
4342
4372
|
}
|
|
4343
4373
|
return newData;
|
|
4344
4374
|
}
|
|
@@ -4362,7 +4392,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4362
4392
|
};
|
|
4363
4393
|
this.pendingChanges.schedule(collection, id, deleteUpdate, 0, "deleteOne");
|
|
4364
4394
|
if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
|
|
4365
|
-
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
|
|
4395
|
+
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
|
|
4366
4396
|
}
|
|
4367
4397
|
return existing;
|
|
4368
4398
|
}
|
|
@@ -4399,7 +4429,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4399
4429
|
this.inMemManager.writeBatch(
|
|
4400
4430
|
collection,
|
|
4401
4431
|
idsToDelete.map((id) => ({ _id: id })),
|
|
4402
|
-
"delete"
|
|
4432
|
+
"delete",
|
|
4433
|
+
{ source: "incremental" }
|
|
4403
4434
|
);
|
|
4404
4435
|
}
|
|
4405
4436
|
return idsToDelete.length;
|
|
@@ -4420,7 +4451,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4420
4451
|
"hardDeleteOne"
|
|
4421
4452
|
);
|
|
4422
4453
|
await this.dexieDb.deleteOne(collection, id);
|
|
4423
|
-
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
|
|
4454
|
+
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
|
|
4424
4455
|
return existing;
|
|
4425
4456
|
}
|
|
4426
4457
|
async hardDelete(collection, query) {
|
|
@@ -4457,7 +4488,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4457
4488
|
"hardDelete"
|
|
4458
4489
|
);
|
|
4459
4490
|
await this.dexieDb.deleteOne(collection, item.id);
|
|
4460
|
-
this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete");
|
|
4491
|
+
this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete", { source: "incremental" });
|
|
4461
4492
|
results.push(true);
|
|
4462
4493
|
} catch (err) {
|
|
4463
4494
|
console.error(`Failed to hard delete ${String(item.id)}:`, err);
|
|
@@ -4837,7 +4868,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4837
4868
|
this.inMemManager.writeBatch(
|
|
4838
4869
|
collection,
|
|
4839
4870
|
evictIds.map((id) => ({ _id: id })),
|
|
4840
|
-
"delete"
|
|
4871
|
+
"delete",
|
|
4872
|
+
{ source: "incremental" }
|
|
4841
4873
|
);
|
|
4842
4874
|
}
|
|
4843
4875
|
this.crossTabSync.broadcastReload([collection]);
|
|
@@ -5090,7 +5122,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5090
5122
|
this.inMemManager.writeBatch(
|
|
5091
5123
|
p.collection,
|
|
5092
5124
|
uniqueEvictIds.map((id) => ({ _id: id })),
|
|
5093
|
-
"delete"
|
|
5125
|
+
"delete",
|
|
5126
|
+
{ source: "incremental" }
|
|
5094
5127
|
);
|
|
5095
5128
|
}
|
|
5096
5129
|
this.crossTabSync.broadcastReload([p.collection]);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - Encapsulation: Hides metadata storage implementation
|
|
8
8
|
*/
|
|
9
9
|
import type { Id, DbEntity } from "../../types/DbEntity";
|
|
10
|
+
import type { SyncSource } from "../../types/I_InMemDb";
|
|
10
11
|
import type { I_InMemManager, InMemManagerConfig } from "../types/managers";
|
|
11
12
|
export declare class InMemManager implements I_InMemManager {
|
|
12
13
|
private readonly inMemDb;
|
|
@@ -18,8 +19,15 @@ export declare class InMemManager implements I_InMemManager {
|
|
|
18
19
|
/**
|
|
19
20
|
* Write batch of items to in-mem.
|
|
20
21
|
* Handles metadata callbacks automatically when useObjectMetadata is enabled.
|
|
22
|
+
*
|
|
23
|
+
* @param opts.source — origin of the write ('initial' | 'refresh' | 'incremental').
|
|
24
|
+
* Implementacija I_InMemDb lahko ta signal uporabi za optimizacije
|
|
25
|
+
* (npr. selektivna reactivity); ne vpliva na sync semantiko.
|
|
26
|
+
* Default: 'incremental'.
|
|
21
27
|
*/
|
|
22
|
-
writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete"
|
|
28
|
+
writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
|
|
29
|
+
source?: SyncSource;
|
|
30
|
+
}): void;
|
|
23
31
|
/**
|
|
24
32
|
* Initialize collection from Dexie data.
|
|
25
33
|
* Called during SyncedDb.init().
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - Uploading dirty items to server
|
|
8
8
|
*/
|
|
9
9
|
import type { LocalDbEntity } from "../../types/DbEntity";
|
|
10
|
+
import type { SyncSource } from "../../types/I_InMemDb";
|
|
10
11
|
import type { I_SyncEngine, SyncEngineConfig, SyncExtras } from "../types/managers";
|
|
11
12
|
import type { UploadResult } from "../types/internal";
|
|
12
13
|
export declare class SyncEngine implements I_SyncEngine {
|
|
@@ -39,8 +40,14 @@ export declare class SyncEngine implements I_SyncEngine {
|
|
|
39
40
|
/**
|
|
40
41
|
* Process incoming server data for a single collection.
|
|
41
42
|
* Used by referToServer to process findNewer results.
|
|
43
|
+
*
|
|
44
|
+
* @param opts.source — origin label posredovan v writeBatch → inMemDb.
|
|
45
|
+
* Default `'incremental'`. Caller naj uporabi `'refresh'` za množični
|
|
46
|
+
* reload (referToServerSync) in `'initial'` za first-time fill.
|
|
42
47
|
*/
|
|
43
|
-
processCollectionServerData(collectionName: string, serverData: LocalDbEntity[]
|
|
48
|
+
processCollectionServerData(collectionName: string, serverData: LocalDbEntity[], opts?: {
|
|
49
|
+
source?: SyncSource;
|
|
50
|
+
}): Promise<{
|
|
44
51
|
updatedIds: string[];
|
|
45
52
|
}>;
|
|
46
53
|
/** Max items to process per batch in processIncomingServerData */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Id, DbEntity, LocalDbEntity } from "../../types/DbEntity";
|
|
6
6
|
import type { I_DexieDb, SyncMeta, MetaUpdateBroadcast } from "../../types/I_DexieDb";
|
|
7
|
-
import type { I_InMemDb } from "../../types/I_InMemDb";
|
|
7
|
+
import type { I_InMemDb, SyncSource } from "../../types/I_InMemDb";
|
|
8
8
|
import type { I_RestInterface } from "../../types/I_RestInterface";
|
|
9
9
|
import type { PublishDataPayload } from "../../types/PublishRevsPayload";
|
|
10
10
|
import type { CollectionConfig, SyncInfo, ConflictResolutionReport, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerManyResultInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, CrossTabSyncInfo } from "../../types/I_SyncedDb";
|
|
@@ -49,7 +49,9 @@ export interface CrossTabSyncDeps {
|
|
|
49
49
|
/** DexieDb instance. */
|
|
50
50
|
dexieDb: I_DexieDb;
|
|
51
51
|
/** Write to in-mem batch. */
|
|
52
|
-
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete"
|
|
52
|
+
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
|
|
53
|
+
source?: SyncSource;
|
|
54
|
+
}) => void;
|
|
53
55
|
/** Whether a collection participates in sync (not writeOnly, not filtered out). */
|
|
54
56
|
isSyncAllowed: (collection: string) => boolean;
|
|
55
57
|
/** Reload a collection fully from Dexie into in-mem (called on reload broadcast). */
|
|
@@ -212,7 +214,9 @@ export interface InMemManagerConfig {
|
|
|
212
214
|
}
|
|
213
215
|
export interface I_InMemManager {
|
|
214
216
|
/** Write batch of items to in-mem. */
|
|
215
|
-
writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete"
|
|
217
|
+
writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
|
|
218
|
+
source?: import("../../types/I_InMemDb").SyncSource;
|
|
219
|
+
}): void;
|
|
216
220
|
/** Initialize collection from Dexie data. */
|
|
217
221
|
initCollection<T extends DbEntity>(collection: string, items: T[]): void;
|
|
218
222
|
/** Clear collection data and metadata. */
|
|
@@ -261,7 +265,9 @@ export interface SyncEngineCallbacks {
|
|
|
261
265
|
export interface SyncEngineDeps {
|
|
262
266
|
getSyncMetaCache: () => Map<string, SyncMeta>;
|
|
263
267
|
setSyncMetaCache: (collection: string, meta: SyncMeta) => void;
|
|
264
|
-
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete"
|
|
268
|
+
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
|
|
269
|
+
source?: SyncSource;
|
|
270
|
+
}) => void;
|
|
265
271
|
getInMemById: <T extends DbEntity>(collection: string, id: Id) => T | undefined;
|
|
266
272
|
withSyncTimeout: <T>(promise: Promise<T>, operation: string) => Promise<T>;
|
|
267
273
|
/** Notify consumers that a sync cycle failed. Does not mutate online state. */
|
|
@@ -311,7 +317,9 @@ export interface I_SyncEngine {
|
|
|
311
317
|
/** Upload dirty items for a specific collection. */
|
|
312
318
|
uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
|
|
313
319
|
/** Process incoming server data for a single collection (used by referToServer). */
|
|
314
|
-
processCollectionServerData(collectionName: string, serverData: import("../../types/DbEntity").LocalDbEntity[]
|
|
320
|
+
processCollectionServerData(collectionName: string, serverData: import("../../types/DbEntity").LocalDbEntity[], opts?: {
|
|
321
|
+
source?: SyncSource;
|
|
322
|
+
}): Promise<{
|
|
315
323
|
updatedIds: string[];
|
|
316
324
|
}>;
|
|
317
325
|
}
|
|
@@ -323,7 +331,9 @@ export interface ServerUpdateHandlerDeps {
|
|
|
323
331
|
canReceiveServerUpdates: () => boolean;
|
|
324
332
|
getPendingChange: (collection: string, id: Id) => PendingChange | undefined;
|
|
325
333
|
broadcastUpdates: (updates: Record<string, string[]>) => void;
|
|
326
|
-
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete"
|
|
334
|
+
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
|
|
335
|
+
source?: SyncSource;
|
|
336
|
+
}) => void;
|
|
327
337
|
/** Whether a collection participates in sync (not writeOnly, not filtered out). */
|
|
328
338
|
isSyncAllowed: (collection: string) => boolean;
|
|
329
339
|
}
|
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
import type { Id, DbEntity } from "./DbEntity";
|
|
2
|
+
/**
|
|
3
|
+
* Izvor sync zapisa. Implementacija I_InMemDb lahko ta signal
|
|
4
|
+
* uporabi za optimizacije (npr. selektivna reactivity v klijentu),
|
|
5
|
+
* ne vpliva pa na sync semantiko.
|
|
6
|
+
*
|
|
7
|
+
* - 'initial' — full snapshot pri prvem polnjenju kolekcije
|
|
8
|
+
* - 'refresh' — refreshLocal pull (množični reload obstoječe kolekcije)
|
|
9
|
+
* - 'incremental' — delta s servera (vključno z lokalnim optimistic write
|
|
10
|
+
* pred server confirmom)
|
|
11
|
+
*/
|
|
12
|
+
export type SyncSource = "initial" | "refresh" | "incremental";
|
|
13
|
+
/**
|
|
14
|
+
* Opcije za zapisovalne operacije.
|
|
15
|
+
* Field-i so opcijski — implementacije, ki jih ne uporabljajo, jih lahko ignorirajo.
|
|
16
|
+
*/
|
|
17
|
+
export interface SaveManyOpts {
|
|
18
|
+
source?: SyncSource;
|
|
19
|
+
}
|
|
20
|
+
export interface DeleteManyByIdsOpts {
|
|
21
|
+
source?: SyncSource;
|
|
22
|
+
}
|
|
2
23
|
/**
|
|
3
24
|
* Interface za in-memory bazo podatkov
|
|
4
25
|
* UI samo bere iz te baze, posodablja jo samo sync-db
|
|
5
26
|
*/
|
|
6
27
|
export interface I_InMemDb {
|
|
7
28
|
/** Shrani/posodobi več objektov naenkrat (bulk upsert) */
|
|
8
|
-
saveMany<T extends DbEntity>(collection: string, items: T[]): void;
|
|
29
|
+
saveMany<T extends DbEntity>(collection: string, items: T[], opts?: SaveManyOpts): void;
|
|
9
30
|
/** Izbriše več objektov iz kolekcije po ID-jih */
|
|
10
|
-
deleteManyByIds(collection: string, ids: Id[]): void;
|
|
31
|
+
deleteManyByIds(collection: string, ids: Id[], opts?: DeleteManyByIdsOpts): void;
|
|
11
32
|
/** Shrani celotno kolekcijo (nadomesti obstoječo) */
|
|
12
33
|
saveCollection<T extends DbEntity>(collection: string, data: T[]): void;
|
|
13
34
|
/** Izbriše celotno kolekcijo */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { Id, Entity, IdOrEntity, DbEntity, LocalDbEntity } from "./DbEntity";
|
|
2
2
|
export type { PublishableOperation, PublishRevsPayloadInsert, PublishRevsPayloadUpdate, PublishRevsPayloadDelete, PublishRevsPayloadUpdateMany, PublishRevsPayloadDeleteMany, PublishRevsPayloadBatchItem, PublishRevsPayloadBatch, PublishRevsPayload, PublishRevsSpec, PublishDataPayloadBase, PublishDataPayloadInsert, PublishDataPayloadUpdate, PublishDataPayloadDelete, PublishDataPayloadBatch, PublishDataPayload, PublishDataSpec, PublishSpec, } from "./PublishRevsPayload";
|
|
3
3
|
export type { Obj, QuerySpec, Projection, QueryOpts, KeyOf, InsertKeyOf, InsertSpec, UpdateSpec, BatchSpec, UpsertOptions, GetNewerSpec, I_RestInterface as RestInterface, } from "./I_RestInterface";
|
|
4
|
-
export type { I_InMemDb as InMemDb } from "./I_InMemDb";
|
|
4
|
+
export type { I_InMemDb as InMemDb, SyncSource, SaveManyOpts, DeleteManyByIdsOpts } from "./I_InMemDb";
|
|
5
5
|
export type { I_DexieDb as DexieDb, SyncMeta, DirtyChange, DirtyMeta } from "./I_DexieDb";
|
|
6
6
|
export type { I_ServerUpdateNotifier as ServerUpdateNotifier, ServerUpdateCallback, ServerUpdateNotifierCallbacks } from "./I_ServerUpdateNotifier";
|
|
7
7
|
export type { I_SyncedDb as SyncedDb, SyncedDbConfig, CollectionConfig, CollectionSyncConfig, SyncInfo, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerManyResultInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, InfrastructureErrorType, InfrastructureErrorInfo, ConflictSource, ConflictResolutionReport, CrossTabSyncInfo, EvictionInfo, EvictionCollectionInfo, } from "./I_SyncedDb";
|