cry-synced-db-client 0.1.148 → 0.1.151
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 +36 -0
- package/dist/index.js +91 -56
- 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/dist/src/utils/localQuery.d.ts +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
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
|
+
|
|
5
41
|
### `uploadDirtyItems` follow-up pass — drain in-sync writes immediately
|
|
6
42
|
|
|
7
43
|
Writes that land **during** a sync iteration had their
|
package/dist/index.js
CHANGED
|
@@ -69,16 +69,13 @@ function matchesQuery(item, query) {
|
|
|
69
69
|
function matchesCondition(item, key, condition) {
|
|
70
70
|
const value = getNestedValue(item, key);
|
|
71
71
|
if (condition === null || typeof condition !== "object") {
|
|
72
|
-
return value
|
|
72
|
+
return scalarMatches(value, condition);
|
|
73
73
|
}
|
|
74
74
|
if (condition instanceof Date) {
|
|
75
|
-
|
|
76
|
-
return value.getTime() === condition.getTime();
|
|
77
|
-
}
|
|
78
|
-
return false;
|
|
75
|
+
return scalarMatches(value, condition);
|
|
79
76
|
}
|
|
80
77
|
if (Array.isArray(condition)) {
|
|
81
|
-
return condition.
|
|
78
|
+
return condition.some((c) => scalarMatches(value, c));
|
|
82
79
|
}
|
|
83
80
|
for (const [op, opValue] of Object.entries(condition)) {
|
|
84
81
|
if (!matchesOperator(value, op, opValue)) {
|
|
@@ -90,29 +87,32 @@ function matchesCondition(item, key, condition) {
|
|
|
90
87
|
function matchesOperator(value, operator, operand) {
|
|
91
88
|
switch (operator) {
|
|
92
89
|
case "$eq":
|
|
93
|
-
return
|
|
90
|
+
return scalarMatches(value, operand);
|
|
94
91
|
case "$ne":
|
|
95
|
-
return !
|
|
92
|
+
return !scalarMatches(value, operand);
|
|
96
93
|
case "$gt":
|
|
97
|
-
return value > operand;
|
|
94
|
+
return compareScalarOrArray(value, (v) => v > operand);
|
|
98
95
|
case "$gte":
|
|
99
|
-
return value >= operand;
|
|
96
|
+
return compareScalarOrArray(value, (v) => v >= operand);
|
|
100
97
|
case "$lt":
|
|
101
|
-
return value < operand;
|
|
98
|
+
return compareScalarOrArray(value, (v) => v < operand);
|
|
102
99
|
case "$lte":
|
|
103
|
-
return value <= operand;
|
|
100
|
+
return compareScalarOrArray(value, (v) => v <= operand);
|
|
104
101
|
case "$in":
|
|
105
102
|
if (!Array.isArray(operand)) return false;
|
|
106
|
-
return operand.some((item) =>
|
|
103
|
+
return operand.some((item) => scalarMatches(value, item));
|
|
107
104
|
case "$nin":
|
|
108
105
|
if (!Array.isArray(operand)) return true;
|
|
109
|
-
return !operand.some((item) =>
|
|
106
|
+
return !operand.some((item) => scalarMatches(value, item));
|
|
110
107
|
case "$exists":
|
|
111
108
|
return operand ? value !== void 0 : value === void 0;
|
|
112
109
|
case "$regex": {
|
|
113
|
-
if (typeof value !== "string") return false;
|
|
114
110
|
const regex = operand instanceof RegExp ? operand : new RegExp(operand);
|
|
115
|
-
return regex.test(value);
|
|
111
|
+
if (typeof value === "string") return regex.test(value);
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
return value.some((v) => typeof v === "string" && regex.test(v));
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
116
|
}
|
|
117
117
|
case "$elemMatch":
|
|
118
118
|
if (!Array.isArray(value)) return false;
|
|
@@ -152,6 +152,19 @@ function getNestedValue(obj, path) {
|
|
|
152
152
|
}
|
|
153
153
|
return current;
|
|
154
154
|
}
|
|
155
|
+
function scalarMatches(value, scalar) {
|
|
156
|
+
if (equals(value, scalar)) return true;
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
return value.some((elem) => equals(elem, scalar));
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
function compareScalarOrArray(value, predicate) {
|
|
163
|
+
if (Array.isArray(value)) {
|
|
164
|
+
return value.some((v) => predicate(v));
|
|
165
|
+
}
|
|
166
|
+
return predicate(value);
|
|
167
|
+
}
|
|
155
168
|
function equals(a, b) {
|
|
156
169
|
var _a, _b;
|
|
157
170
|
if (a === b) return true;
|
|
@@ -272,12 +285,19 @@ var InMemManager = class {
|
|
|
272
285
|
/**
|
|
273
286
|
* Write batch of items to in-mem.
|
|
274
287
|
* Handles metadata callbacks automatically when useObjectMetadata is enabled.
|
|
288
|
+
*
|
|
289
|
+
* @param opts.source — origin of the write ('initial' | 'refresh' | 'incremental').
|
|
290
|
+
* Implementacija I_InMemDb lahko ta signal uporabi za optimizacije
|
|
291
|
+
* (npr. selektivna reactivity); ne vpliva na sync semantiko.
|
|
292
|
+
* Default: 'incremental'.
|
|
275
293
|
*/
|
|
276
|
-
writeBatch(collection, items, operation) {
|
|
294
|
+
writeBatch(collection, items, operation, opts) {
|
|
295
|
+
var _a;
|
|
277
296
|
if (items.length === 0) return;
|
|
278
297
|
const config = this.collections.get(collection);
|
|
298
|
+
const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
|
|
279
299
|
if (operation === "upsert") {
|
|
280
|
-
this.inMemDb.saveMany(collection, items);
|
|
300
|
+
this.inMemDb.saveMany(collection, items, { source });
|
|
281
301
|
if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
|
|
282
302
|
const ids = [];
|
|
283
303
|
for (const item of items) ids.push(item._id);
|
|
@@ -304,7 +324,7 @@ var InMemManager = class {
|
|
|
304
324
|
} else if (operation === "delete") {
|
|
305
325
|
const ids = [];
|
|
306
326
|
for (const item of items) ids.push(item._id);
|
|
307
|
-
this.inMemDb.deleteManyByIds(collection, ids);
|
|
327
|
+
this.inMemDb.deleteManyByIds(collection, ids, { source });
|
|
308
328
|
if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
|
|
309
329
|
this.deleteObjectsMetadataInternal(collection, ids);
|
|
310
330
|
}
|
|
@@ -2478,10 +2498,13 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2478
2498
|
const findNewerManyStartTime = Date.now();
|
|
2479
2499
|
const collectionState = /* @__PURE__ */ new Map();
|
|
2480
2500
|
for (const [name] of configMap) {
|
|
2501
|
+
const prior = this.deps.getSyncMetaCache().get(name);
|
|
2502
|
+
const isInitial = !(prior == null ? void 0 : prior.lastSyncTs);
|
|
2481
2503
|
collectionState.set(name, {
|
|
2482
2504
|
maxTs: void 0,
|
|
2483
2505
|
conflicts: 0,
|
|
2484
|
-
receivedCount: 0
|
|
2506
|
+
receivedCount: 0,
|
|
2507
|
+
source: isInitial ? "initial" : "incremental"
|
|
2485
2508
|
});
|
|
2486
2509
|
}
|
|
2487
2510
|
try {
|
|
@@ -2499,7 +2522,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2499
2522
|
if (!config) return;
|
|
2500
2523
|
const state = collectionState.get(collection);
|
|
2501
2524
|
state.receivedCount += items.length;
|
|
2502
|
-
const stats = await this.processIncomingServerData(collection, config, items);
|
|
2525
|
+
const stats = await this.processIncomingServerData(collection, config, items, state.source);
|
|
2503
2526
|
state.conflicts += stats.conflictsResolved;
|
|
2504
2527
|
if (stats.maxTs) {
|
|
2505
2528
|
if (!state.maxTs || this.compareTimestamps(stats.maxTs, state.maxTs) > 0) {
|
|
@@ -2723,13 +2746,14 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2723
2746
|
await this.dexieDb.deleteMany(collection, dexieDeleteIds);
|
|
2724
2747
|
}
|
|
2725
2748
|
if (inMemUpdateBatch.length > 0) {
|
|
2726
|
-
this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert");
|
|
2749
|
+
this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert", { source: "incremental" });
|
|
2727
2750
|
}
|
|
2728
2751
|
if (inMemDeleteIds.length > 0) {
|
|
2729
2752
|
this.deps.writeToInMemBatch(
|
|
2730
2753
|
collection,
|
|
2731
2754
|
inMemDeleteIds.map((id) => ({ _id: id })),
|
|
2732
|
-
"delete"
|
|
2755
|
+
"delete",
|
|
2756
|
+
{ source: "incremental" }
|
|
2733
2757
|
);
|
|
2734
2758
|
}
|
|
2735
2759
|
}
|
|
@@ -2745,7 +2769,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2745
2769
|
}
|
|
2746
2770
|
await this.dexieDb.deleteMany(collection, deleteIds);
|
|
2747
2771
|
if (!isWriteOnly) {
|
|
2748
|
-
this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete");
|
|
2772
|
+
this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete", { source: "incremental" });
|
|
2749
2773
|
}
|
|
2750
2774
|
sentCount += deleted.length;
|
|
2751
2775
|
collectionSentCount += deleted.length;
|
|
@@ -2855,17 +2879,23 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2855
2879
|
/**
|
|
2856
2880
|
* Process incoming server data for a single collection.
|
|
2857
2881
|
* Used by referToServer to process findNewer results.
|
|
2882
|
+
*
|
|
2883
|
+
* @param opts.source — origin label posredovan v writeBatch → inMemDb.
|
|
2884
|
+
* Default `'incremental'`. Caller naj uporabi `'refresh'` za množični
|
|
2885
|
+
* reload (referToServerSync) in `'initial'` za first-time fill.
|
|
2858
2886
|
*/
|
|
2859
|
-
async processCollectionServerData(collectionName, serverData) {
|
|
2887
|
+
async processCollectionServerData(collectionName, serverData, opts) {
|
|
2888
|
+
var _a;
|
|
2860
2889
|
const config = this.collections.get(collectionName);
|
|
2861
2890
|
if (!config) return { updatedIds: [] };
|
|
2862
|
-
const
|
|
2891
|
+
const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
|
|
2892
|
+
const result = await this.processIncomingServerData(collectionName, config, serverData, source);
|
|
2863
2893
|
if (result.updatedIds.length > 0) {
|
|
2864
2894
|
this.deps.broadcastUpdates({ [collectionName]: result.updatedIds });
|
|
2865
2895
|
}
|
|
2866
2896
|
return { updatedIds: result.updatedIds };
|
|
2867
2897
|
}
|
|
2868
|
-
async processIncomingServerData(collectionName, config, serverData) {
|
|
2898
|
+
async processIncomingServerData(collectionName, config, serverData, source = "incremental") {
|
|
2869
2899
|
if (serverData.length === 0) {
|
|
2870
2900
|
return { conflictsResolved: 0, maxTs: void 0, updatedIds: [] };
|
|
2871
2901
|
}
|
|
@@ -2927,13 +2957,14 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2927
2957
|
await this.dexieDb.saveMany(collectionName, dexieBatch);
|
|
2928
2958
|
}
|
|
2929
2959
|
if (inMemSaveBatch.length > 0) {
|
|
2930
|
-
this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert");
|
|
2960
|
+
this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert", { source });
|
|
2931
2961
|
}
|
|
2932
2962
|
if (inMemDeleteIds.length > 0) {
|
|
2933
2963
|
this.deps.writeToInMemBatch(
|
|
2934
2964
|
collectionName,
|
|
2935
2965
|
inMemDeleteIds.map((id) => ({ _id: id })),
|
|
2936
|
-
"delete"
|
|
2966
|
+
"delete",
|
|
2967
|
+
{ source }
|
|
2937
2968
|
);
|
|
2938
2969
|
}
|
|
2939
2970
|
}
|
|
@@ -3207,12 +3238,12 @@ var ServerUpdateHandler = class {
|
|
|
3207
3238
|
await this.dexieDb.clearDirtyChange(collection, serverItem._id);
|
|
3208
3239
|
}
|
|
3209
3240
|
if (!serverItem._deleted && !serverItem._archived) {
|
|
3210
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
|
|
3241
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
|
|
3211
3242
|
}
|
|
3212
3243
|
} else {
|
|
3213
3244
|
await this.dexieDb.insert(collection, serverItem);
|
|
3214
3245
|
if (!serverItem._deleted && !serverItem._archived) {
|
|
3215
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
|
|
3246
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
|
|
3216
3247
|
}
|
|
3217
3248
|
}
|
|
3218
3249
|
}
|
|
@@ -3241,7 +3272,7 @@ var ServerUpdateHandler = class {
|
|
|
3241
3272
|
const currentInMemState = Object.assign({}, localItem, pendingChange.data);
|
|
3242
3273
|
const merged = this.mergeLocalWithDelta(currentInMemState, serverDelta);
|
|
3243
3274
|
if (!merged._deleted && !merged._archived) {
|
|
3244
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
3275
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
|
|
3245
3276
|
}
|
|
3246
3277
|
return;
|
|
3247
3278
|
}
|
|
@@ -3253,16 +3284,16 @@ var ServerUpdateHandler = class {
|
|
|
3253
3284
|
await this.dexieDb.save(collection, serverDelta._id, merged);
|
|
3254
3285
|
}
|
|
3255
3286
|
if (!merged._deleted && !merged._archived) {
|
|
3256
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
3287
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
|
|
3257
3288
|
}
|
|
3258
3289
|
} else {
|
|
3259
3290
|
if (!metaChanged) return;
|
|
3260
3291
|
const merged = this.mergeLocalWithDelta(localItem, serverDelta);
|
|
3261
3292
|
await this.dexieDb.save(collection, serverDelta._id, merged);
|
|
3262
3293
|
if (!merged._deleted && !merged._archived) {
|
|
3263
|
-
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
3294
|
+
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
|
|
3264
3295
|
} else {
|
|
3265
|
-
this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete");
|
|
3296
|
+
this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete", { source: "incremental" });
|
|
3266
3297
|
}
|
|
3267
3298
|
}
|
|
3268
3299
|
}
|
|
@@ -3278,7 +3309,7 @@ var ServerUpdateHandler = class {
|
|
|
3278
3309
|
} else {
|
|
3279
3310
|
await this.dexieDb.deleteOne(collection, id);
|
|
3280
3311
|
}
|
|
3281
|
-
this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete");
|
|
3312
|
+
this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
|
|
3282
3313
|
}
|
|
3283
3314
|
// ============================================================
|
|
3284
3315
|
// Private Helpers
|
|
@@ -3603,8 +3634,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
3603
3634
|
isLeader: () => this.leaderElection.isLeader(),
|
|
3604
3635
|
getCollections: () => this.collections,
|
|
3605
3636
|
dexieDb: this.dexieDb,
|
|
3606
|
-
writeToInMemBatch: (collection, items, operation) => {
|
|
3607
|
-
this.inMemManager.writeBatch(collection, items, operation);
|
|
3637
|
+
writeToInMemBatch: (collection, items, operation, opts) => {
|
|
3638
|
+
this.inMemManager.writeBatch(collection, items, operation, opts);
|
|
3608
3639
|
},
|
|
3609
3640
|
isSyncAllowed: (collection) => this.isSyncAllowed(collection),
|
|
3610
3641
|
reloadCollectionFromDexie: (collection) => this.loadCollectionToInMem(collection).then(() => void 0)
|
|
@@ -3683,8 +3714,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
3683
3714
|
deps: {
|
|
3684
3715
|
getSyncMetaCache: () => this.syncMetaCache,
|
|
3685
3716
|
setSyncMetaCache: (collection, meta) => this.syncMetaCache.set(collection, meta),
|
|
3686
|
-
writeToInMemBatch: (collection, items, operation) => {
|
|
3687
|
-
this.inMemManager.writeBatch(collection, items, operation);
|
|
3717
|
+
writeToInMemBatch: (collection, items, operation, opts) => {
|
|
3718
|
+
this.inMemManager.writeBatch(collection, items, operation, opts);
|
|
3688
3719
|
},
|
|
3689
3720
|
getInMemById: (collection, id) => this.inMemDb.getById(collection, id),
|
|
3690
3721
|
withSyncTimeout: (promise, operation) => this.connectionManager.withSyncTimeout(promise, operation),
|
|
@@ -3708,8 +3739,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
3708
3739
|
deps: {
|
|
3709
3740
|
isLeader: () => this.leaderElection.isLeader(),
|
|
3710
3741
|
canReceiveServerUpdates: () => this.connectionManager.canReceiveServerUpdates(),
|
|
3711
|
-
writeToInMemBatch: (collection, items, operation) => {
|
|
3712
|
-
this.inMemManager.writeBatch(collection, items, operation);
|
|
3742
|
+
writeToInMemBatch: (collection, items, operation, opts) => {
|
|
3743
|
+
this.inMemManager.writeBatch(collection, items, operation, opts);
|
|
3713
3744
|
},
|
|
3714
3745
|
broadcastUpdates: (updates) => this.crossTabSync.broadcastMetaUpdate(updates),
|
|
3715
3746
|
getPendingChange: (collection, id) => this.pendingChanges.getPendingChange(collection, id),
|
|
@@ -4018,7 +4049,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4018
4049
|
if (serverItem) {
|
|
4019
4050
|
await this.dexieDb.saveMany(collection, [serverItem]);
|
|
4020
4051
|
if (!serverItem._deleted && !serverItem._archived) {
|
|
4021
|
-
this.inMemManager.writeBatch(collection, [serverItem], "upsert");
|
|
4052
|
+
this.inMemManager.writeBatch(collection, [serverItem], "upsert", { source: "incremental" });
|
|
4022
4053
|
}
|
|
4023
4054
|
}
|
|
4024
4055
|
} catch (e) {
|
|
@@ -4077,7 +4108,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4077
4108
|
await this.dexieDb.saveMany(collection, serverItems);
|
|
4078
4109
|
const toInMem = serverItems.filter((s) => !s._deleted && !s._archived);
|
|
4079
4110
|
if (toInMem.length > 0) {
|
|
4080
|
-
this.inMemManager.writeBatch(collection, toInMem, "upsert");
|
|
4111
|
+
this.inMemManager.writeBatch(collection, toInMem, "upsert", { source: "incremental" });
|
|
4081
4112
|
}
|
|
4082
4113
|
}
|
|
4083
4114
|
} catch (e) {
|
|
@@ -4196,7 +4227,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4196
4227
|
"syncCollectionForFind"
|
|
4197
4228
|
);
|
|
4198
4229
|
if (serverData.length > 0) {
|
|
4199
|
-
|
|
4230
|
+
const source = timestamp === 0 ? "initial" : "incremental";
|
|
4231
|
+
await this.syncEngine.processCollectionServerData(collection, serverData, { source });
|
|
4200
4232
|
}
|
|
4201
4233
|
} catch (e) {
|
|
4202
4234
|
}
|
|
@@ -4214,7 +4246,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4214
4246
|
"referToServer"
|
|
4215
4247
|
).then(async (serverData) => {
|
|
4216
4248
|
if (serverData.length > 0) {
|
|
4217
|
-
await this.syncEngine.processCollectionServerData(collection, serverData);
|
|
4249
|
+
await this.syncEngine.processCollectionServerData(collection, serverData, { source: "refresh" });
|
|
4218
4250
|
}
|
|
4219
4251
|
}).catch((err) => {
|
|
4220
4252
|
console.error(`referToServer failed for ${collection}:`, err);
|
|
@@ -4236,7 +4268,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4236
4268
|
"refreshInBackground"
|
|
4237
4269
|
).then(async (serverItems) => {
|
|
4238
4270
|
if (!serverItems || serverItems.length === 0) return;
|
|
4239
|
-
await this.syncEngine.processCollectionServerData(collection, serverItems);
|
|
4271
|
+
await this.syncEngine.processCollectionServerData(collection, serverItems, { source: "incremental" });
|
|
4240
4272
|
}).catch((err) => {
|
|
4241
4273
|
console.error(`refreshInBackground failed for ${collection}:`, err);
|
|
4242
4274
|
});
|
|
@@ -4274,7 +4306,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4274
4306
|
await this.dexieDb.saveMany(collection, toSaveDexie);
|
|
4275
4307
|
}
|
|
4276
4308
|
if (toSaveInMem.length > 0) {
|
|
4277
|
-
this.inMemManager.writeBatch(collection, toSaveInMem, "upsert");
|
|
4309
|
+
this.inMemManager.writeBatch(collection, toSaveInMem, "upsert", { source: "incremental" });
|
|
4278
4310
|
}
|
|
4279
4311
|
}
|
|
4280
4312
|
// ==================== Write Operations ====================
|
|
@@ -4309,7 +4341,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4309
4341
|
const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
|
|
4310
4342
|
const merged = __spreadValues(__spreadValues({}, currentMem || existing || { _id: id }), update);
|
|
4311
4343
|
if (!isWriteOnly && !(existing == null ? void 0 : existing._deleted) && !(existing == null ? void 0 : existing._archived)) {
|
|
4312
|
-
this.inMemManager.writeBatch(collection, [merged], "upsert");
|
|
4344
|
+
this.inMemManager.writeBatch(collection, [merged], "upsert", { source: "incremental" });
|
|
4313
4345
|
}
|
|
4314
4346
|
return merged;
|
|
4315
4347
|
}
|
|
@@ -4349,7 +4381,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4349
4381
|
});
|
|
4350
4382
|
this.pendingChanges.schedule(collection, id, newData, 0, "insert");
|
|
4351
4383
|
if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
|
|
4352
|
-
this.inMemManager.writeBatch(collection, [newData], "upsert");
|
|
4384
|
+
this.inMemManager.writeBatch(collection, [newData], "upsert", { source: "incremental" });
|
|
4353
4385
|
}
|
|
4354
4386
|
return newData;
|
|
4355
4387
|
}
|
|
@@ -4373,7 +4405,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4373
4405
|
};
|
|
4374
4406
|
this.pendingChanges.schedule(collection, id, deleteUpdate, 0, "deleteOne");
|
|
4375
4407
|
if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
|
|
4376
|
-
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
|
|
4408
|
+
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
|
|
4377
4409
|
}
|
|
4378
4410
|
return existing;
|
|
4379
4411
|
}
|
|
@@ -4410,7 +4442,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4410
4442
|
this.inMemManager.writeBatch(
|
|
4411
4443
|
collection,
|
|
4412
4444
|
idsToDelete.map((id) => ({ _id: id })),
|
|
4413
|
-
"delete"
|
|
4445
|
+
"delete",
|
|
4446
|
+
{ source: "incremental" }
|
|
4414
4447
|
);
|
|
4415
4448
|
}
|
|
4416
4449
|
return idsToDelete.length;
|
|
@@ -4431,7 +4464,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4431
4464
|
"hardDeleteOne"
|
|
4432
4465
|
);
|
|
4433
4466
|
await this.dexieDb.deleteOne(collection, id);
|
|
4434
|
-
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
|
|
4467
|
+
this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
|
|
4435
4468
|
return existing;
|
|
4436
4469
|
}
|
|
4437
4470
|
async hardDelete(collection, query) {
|
|
@@ -4468,7 +4501,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4468
4501
|
"hardDelete"
|
|
4469
4502
|
);
|
|
4470
4503
|
await this.dexieDb.deleteOne(collection, item.id);
|
|
4471
|
-
this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete");
|
|
4504
|
+
this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete", { source: "incremental" });
|
|
4472
4505
|
results.push(true);
|
|
4473
4506
|
} catch (err) {
|
|
4474
4507
|
console.error(`Failed to hard delete ${String(item.id)}:`, err);
|
|
@@ -4848,7 +4881,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4848
4881
|
this.inMemManager.writeBatch(
|
|
4849
4882
|
collection,
|
|
4850
4883
|
evictIds.map((id) => ({ _id: id })),
|
|
4851
|
-
"delete"
|
|
4884
|
+
"delete",
|
|
4885
|
+
{ source: "incremental" }
|
|
4852
4886
|
);
|
|
4853
4887
|
}
|
|
4854
4888
|
this.crossTabSync.broadcastReload([collection]);
|
|
@@ -5101,7 +5135,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5101
5135
|
this.inMemManager.writeBatch(
|
|
5102
5136
|
p.collection,
|
|
5103
5137
|
uniqueEvictIds.map((id) => ({ _id: id })),
|
|
5104
|
-
"delete"
|
|
5138
|
+
"delete",
|
|
5139
|
+
{ source: "incremental" }
|
|
5105
5140
|
);
|
|
5106
5141
|
}
|
|
5107
5142
|
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";
|
|
@@ -5,6 +5,10 @@ import type { DbEntity } from "../types/DbEntity";
|
|
|
5
5
|
* Podpira polje-operatorje: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin,
|
|
6
6
|
* $exists, $regex, $elemMatch, $size, $all — in logične operatorje
|
|
7
7
|
* $and, $or, $nor na top-levelu.
|
|
8
|
+
*
|
|
9
|
+
* Skalarni operandi se ujemajo tudi z array-polji (MongoDB semantika):
|
|
10
|
+
* `{ a: "ok" }` matcha tako `{ a: "ok" }` kot `{ a: ["ok", "more"] }`.
|
|
11
|
+
* Velja za neposredno enakost, $eq, $ne, $in, $nin, $gt/$gte/$lt/$lte in $regex.
|
|
8
12
|
*/
|
|
9
13
|
export declare function matchesQuery<T extends DbEntity>(item: T, query: QuerySpec<T>): boolean;
|
|
10
14
|
/**
|