cry-synced-db-client 0.1.165 → 0.1.167
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +122 -10
- package/dist/src/db/SyncedDb.d.ts +39 -0
- package/dist/src/types/I_SyncedDb.d.ts +8 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -507,6 +507,11 @@ function setSegment(current, part, value) {
|
|
|
507
507
|
const idStr = part.slice(1, -1);
|
|
508
508
|
if (!Array.isArray(current)) return false;
|
|
509
509
|
const idx = current.findIndex((item) => item && String(item._id) === idStr);
|
|
510
|
+
if (Array.isArray(value) && value.length === 1 && value[0] && typeof value[0] === "object" && String(value[0]._id) === idStr) {
|
|
511
|
+
if (idx >= 0) current[idx] = value[0];
|
|
512
|
+
else current.push(value[0]);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
510
515
|
if (idx < 0) return false;
|
|
511
516
|
current[idx] = value;
|
|
512
517
|
return true;
|
|
@@ -4958,7 +4963,8 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4958
4963
|
const merged = _SyncedDb.applyDiffLocally(
|
|
4959
4964
|
currentMem != null ? currentMem : existing,
|
|
4960
4965
|
diff,
|
|
4961
|
-
id
|
|
4966
|
+
id,
|
|
4967
|
+
collection
|
|
4962
4968
|
);
|
|
4963
4969
|
this.pendingChanges.schedule(collection, id, merged, 0, "save");
|
|
4964
4970
|
if (!isWriteOnly && !(existing == null ? void 0 : existing._deleted) && !(existing == null ? void 0 : existing._archived)) {
|
|
@@ -4968,6 +4974,9 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4968
4974
|
}
|
|
4969
4975
|
async upsert(collection, query, update) {
|
|
4970
4976
|
this.assertCollection(collection);
|
|
4977
|
+
if ((query == null ? void 0 : query._id) != null && update._id == null) {
|
|
4978
|
+
update._id = query._id;
|
|
4979
|
+
}
|
|
4971
4980
|
this.ensureId(update, "upsert", collection);
|
|
4972
4981
|
query = _SyncedDb.stringifyObjectIds(query);
|
|
4973
4982
|
update = _SyncedDb.stringifyObjectIds(update);
|
|
@@ -5221,16 +5230,31 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5221
5230
|
isSyncing() {
|
|
5222
5231
|
return this.syncing;
|
|
5223
5232
|
}
|
|
5224
|
-
// ==================== Batch Operations
|
|
5233
|
+
// ==================== Batch Operations ====================
|
|
5234
|
+
/**
|
|
5235
|
+
* Run each batch entry through the standard `upsert()` pipeline so every
|
|
5236
|
+
* write — single or batched — goes through `computeDiff` →
|
|
5237
|
+
* `applyDiffLocally` → in-mem write + Dexie schedule + dirty change.
|
|
5238
|
+
*
|
|
5239
|
+
* Server upload remains batched: dirty entries accumulated by the per-item
|
|
5240
|
+
* calls coalesce into one `updateCollections` request at the next sync
|
|
5241
|
+
* flush. We never bypass the diff pipeline (no separate server-only
|
|
5242
|
+
* `restInterface.upsertBatch` route), so bracket-by-_id sub-field path
|
|
5243
|
+
* generation and conflict resolution behave identically to a sequence of
|
|
5244
|
+
* `upsert()` calls.
|
|
5245
|
+
*
|
|
5246
|
+
* Works offline — items queue as dirty and upload on reconnect.
|
|
5247
|
+
*
|
|
5248
|
+
* Per-entry `opts` (`UpsertOptions` / `FindOneAndUpdateOptions`) is
|
|
5249
|
+
* ignored; the diff pipeline has no mongo-`findOneAndUpdate` knob.
|
|
5250
|
+
*/
|
|
5225
5251
|
async upsertBatch(collection, batch) {
|
|
5226
5252
|
this.assertCollection(collection);
|
|
5227
|
-
|
|
5228
|
-
|
|
5253
|
+
const results = [];
|
|
5254
|
+
for (const entry of batch) {
|
|
5255
|
+
results.push(await this.upsert(collection, entry.query, entry.update));
|
|
5229
5256
|
}
|
|
5230
|
-
return
|
|
5231
|
-
this.restInterface.upsertBatch(collection, batch),
|
|
5232
|
-
"upsertBatch"
|
|
5233
|
-
);
|
|
5257
|
+
return results;
|
|
5234
5258
|
}
|
|
5235
5259
|
// ==================== In-Memory Access ====================
|
|
5236
5260
|
getMemoryCollection(collection) {
|
|
@@ -6099,7 +6123,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6099
6123
|
* Returns a new object (the cloned-and-mutated `base`); never mutates
|
|
6100
6124
|
* the input `base` reference.
|
|
6101
6125
|
*/
|
|
6102
|
-
static applyDiffLocally(base, diff, fallbackId) {
|
|
6126
|
+
static applyDiffLocally(base, diff, fallbackId, collection) {
|
|
6103
6127
|
const seed = base ? _SyncedDb.safeDeepClone(base) : { _id: fallbackId };
|
|
6104
6128
|
if (seed._id == null) seed._id = fallbackId;
|
|
6105
6129
|
for (const path of Object.keys(diff)) {
|
|
@@ -6113,10 +6137,98 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6113
6137
|
continue;
|
|
6114
6138
|
}
|
|
6115
6139
|
const ok = setByPath(seed, path, value);
|
|
6116
|
-
if (!ok)
|
|
6140
|
+
if (!ok) {
|
|
6141
|
+
_SyncedDb.materializeBracketPath(seed, path, value, collection, fallbackId);
|
|
6142
|
+
}
|
|
6117
6143
|
}
|
|
6118
6144
|
return seed;
|
|
6119
6145
|
}
|
|
6146
|
+
/**
|
|
6147
|
+
* Fallback for `setByPath` failures inside `applyDiffLocally`. Materializes
|
|
6148
|
+
* the parent array when it's missing from `seed`. Two patterns covered, per
|
|
6149
|
+
* production-spec (mozirje 2026-05-10 — literal bracket-keyed sibling
|
|
6150
|
+
* properties polluting Dexie/in-mem):
|
|
6151
|
+
*
|
|
6152
|
+
* 1. `polje[<id>] = <obj>` → seed.polje = [<obj>]
|
|
6153
|
+
* 2. `polje[<id>].<field> = <v>` → seed.polje = [{_id: <id>, <field>: <v>}]
|
|
6154
|
+
*
|
|
6155
|
+
* Everything else (nested-via-dots before bracket, multi-bracket paths
|
|
6156
|
+
* like `_redundanca.terapije[<id>].postavke[<id2>]`, deeper sub-fields)
|
|
6157
|
+
* is dropped silently — materializing those locally would risk corrupting
|
|
6158
|
+
* unrelated invariants on the nested objects. Dirty-change still carries
|
|
6159
|
+
* the path, so server applies it; next sync brings the canonical state
|
|
6160
|
+
* back to local.
|
|
6161
|
+
*
|
|
6162
|
+
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
6163
|
+
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|
|
6164
|
+
* onto Dexie rows and in-mem state, persisting forever through subsequent
|
|
6165
|
+
* `safeDeepClone`-based save cycles.
|
|
6166
|
+
*/
|
|
6167
|
+
static materializeBracketPath(seed, path, value, collection, id) {
|
|
6168
|
+
const tokens = tokenizePath(path);
|
|
6169
|
+
const firstToken = tokens[0];
|
|
6170
|
+
const dropSilently = typeof firstToken === "string" && firstToken.startsWith("_");
|
|
6171
|
+
const drop = (reason) => {
|
|
6172
|
+
if (dropSilently) return;
|
|
6173
|
+
console.error(
|
|
6174
|
+
`SyncedDb.applyDiffLocally: dropping bracket-path diff entry (${reason})`,
|
|
6175
|
+
{ collection, _id: String(id), path, value }
|
|
6176
|
+
);
|
|
6177
|
+
};
|
|
6178
|
+
if (tokens.length < 2 || tokens.length > 3) {
|
|
6179
|
+
drop(`unsupported token count ${tokens.length}`);
|
|
6180
|
+
return;
|
|
6181
|
+
}
|
|
6182
|
+
if (firstToken === void 0 || firstToken.startsWith("[")) {
|
|
6183
|
+
drop("first segment is not a plain field");
|
|
6184
|
+
return;
|
|
6185
|
+
}
|
|
6186
|
+
const secondToken = tokens[1];
|
|
6187
|
+
if (!secondToken.startsWith("[") || !secondToken.endsWith("]")) {
|
|
6188
|
+
drop("second segment is not a bracket-by-id");
|
|
6189
|
+
return;
|
|
6190
|
+
}
|
|
6191
|
+
if (tokens.length === 3 && tokens[2].startsWith("[")) {
|
|
6192
|
+
drop("nested bracket path");
|
|
6193
|
+
return;
|
|
6194
|
+
}
|
|
6195
|
+
const bracketId = secondToken.slice(1, -1);
|
|
6196
|
+
if (bracketId.length === 0) {
|
|
6197
|
+
drop("empty bracket id");
|
|
6198
|
+
return;
|
|
6199
|
+
}
|
|
6200
|
+
const existing = seed[firstToken];
|
|
6201
|
+
if (existing != null && !Array.isArray(existing)) {
|
|
6202
|
+
drop(`existing "${firstToken}" is not an array`);
|
|
6203
|
+
return;
|
|
6204
|
+
}
|
|
6205
|
+
if (tokens.length === 2) {
|
|
6206
|
+
let element = value;
|
|
6207
|
+
if (Array.isArray(value) && value.length === 1 && value[0] != null && typeof value[0] === "object") {
|
|
6208
|
+
element = value[0];
|
|
6209
|
+
}
|
|
6210
|
+
if (element == null || typeof element !== "object" || Array.isArray(element)) {
|
|
6211
|
+
drop("value is not a single element or wire-form wrapper");
|
|
6212
|
+
return;
|
|
6213
|
+
}
|
|
6214
|
+
if (element._id == null) {
|
|
6215
|
+
element._id = bracketId;
|
|
6216
|
+
}
|
|
6217
|
+
if (existing == null) {
|
|
6218
|
+
seed[firstToken] = [element];
|
|
6219
|
+
} else {
|
|
6220
|
+
existing.push(element);
|
|
6221
|
+
}
|
|
6222
|
+
return;
|
|
6223
|
+
}
|
|
6224
|
+
const fieldName = tokens[2];
|
|
6225
|
+
const newElement = { _id: bracketId, [fieldName]: value };
|
|
6226
|
+
if (existing == null) {
|
|
6227
|
+
seed[firstToken] = [newElement];
|
|
6228
|
+
} else {
|
|
6229
|
+
existing.push(newElement);
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6120
6232
|
/**
|
|
6121
6233
|
* Deep clone for `applyDiffLocally`. Recurses into plain objects and
|
|
6122
6234
|
* arrays; preserves `Date` (cloned to avoid shared reference) and
|
|
@@ -156,6 +156,23 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
156
156
|
sync(calledFrom?: string): Promise<void>;
|
|
157
157
|
private processQueuedWsUpdates;
|
|
158
158
|
isSyncing(): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Run each batch entry through the standard `upsert()` pipeline so every
|
|
161
|
+
* write — single or batched — goes through `computeDiff` →
|
|
162
|
+
* `applyDiffLocally` → in-mem write + Dexie schedule + dirty change.
|
|
163
|
+
*
|
|
164
|
+
* Server upload remains batched: dirty entries accumulated by the per-item
|
|
165
|
+
* calls coalesce into one `updateCollections` request at the next sync
|
|
166
|
+
* flush. We never bypass the diff pipeline (no separate server-only
|
|
167
|
+
* `restInterface.upsertBatch` route), so bracket-by-_id sub-field path
|
|
168
|
+
* generation and conflict resolution behave identically to a sequence of
|
|
169
|
+
* `upsert()` calls.
|
|
170
|
+
*
|
|
171
|
+
* Works offline — items queue as dirty and upload on reconnect.
|
|
172
|
+
*
|
|
173
|
+
* Per-entry `opts` (`UpsertOptions` / `FindOneAndUpdateOptions`) is
|
|
174
|
+
* ignored; the diff pipeline has no mongo-`findOneAndUpdate` knob.
|
|
175
|
+
*/
|
|
159
176
|
upsertBatch<T extends DbEntity>(collection: string, batch: BatchSpec<T>): Promise<T[]>;
|
|
160
177
|
getMemoryCollection<T extends DbEntity>(collection: string): T[];
|
|
161
178
|
getDebounceDexieWritesMs(): number;
|
|
@@ -418,6 +435,28 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
418
435
|
* the input `base` reference.
|
|
419
436
|
*/
|
|
420
437
|
private static applyDiffLocally;
|
|
438
|
+
/**
|
|
439
|
+
* Fallback for `setByPath` failures inside `applyDiffLocally`. Materializes
|
|
440
|
+
* the parent array when it's missing from `seed`. Two patterns covered, per
|
|
441
|
+
* production-spec (mozirje 2026-05-10 — literal bracket-keyed sibling
|
|
442
|
+
* properties polluting Dexie/in-mem):
|
|
443
|
+
*
|
|
444
|
+
* 1. `polje[<id>] = <obj>` → seed.polje = [<obj>]
|
|
445
|
+
* 2. `polje[<id>].<field> = <v>` → seed.polje = [{_id: <id>, <field>: <v>}]
|
|
446
|
+
*
|
|
447
|
+
* Everything else (nested-via-dots before bracket, multi-bracket paths
|
|
448
|
+
* like `_redundanca.terapije[<id>].postavke[<id2>]`, deeper sub-fields)
|
|
449
|
+
* is dropped silently — materializing those locally would risk corrupting
|
|
450
|
+
* unrelated invariants on the nested objects. Dirty-change still carries
|
|
451
|
+
* the path, so server applies it; next sync brings the canonical state
|
|
452
|
+
* back to local.
|
|
453
|
+
*
|
|
454
|
+
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
455
|
+
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|
|
456
|
+
* onto Dexie rows and in-mem state, persisting forever through subsequent
|
|
457
|
+
* `safeDeepClone`-based save cycles.
|
|
458
|
+
*/
|
|
459
|
+
private static materializeBracketPath;
|
|
421
460
|
/**
|
|
422
461
|
* Deep clone for `applyDiffLocally`. Recurses into plain objects and
|
|
423
462
|
* arrays; preserves `Date` (cloned to avoid shared reference) and
|
|
@@ -866,27 +866,15 @@ export interface I_SyncedDb {
|
|
|
866
866
|
*/
|
|
867
867
|
getSyncOnlyTheseCollections(): ReadonlySet<string> | null;
|
|
868
868
|
/**
|
|
869
|
-
*
|
|
869
|
+
* Batch wrapper around `upsert()` — applies each entry via the standard
|
|
870
|
+
* write pipeline (`computeDiff` → `applyDiffLocally` → in-mem + Dexie +
|
|
871
|
+
* dirty change). Dirty entries from all entries coalesce into a single
|
|
872
|
+
* `updateCollections` request at the next sync flush, so the server side
|
|
873
|
+
* still sees one batched upload.
|
|
870
874
|
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
* If you need local state to reflect the changes after calling upsertBatch(),
|
|
876
|
-
* call sync() afterward to fetch the updated data from the server.
|
|
877
|
-
*
|
|
878
|
-
* Note: Server notifications (via serverUpdateNotifier) for upsertBatch changes
|
|
879
|
-
* may trigger handleServerUpdate(), which will update local state. However, this
|
|
880
|
-
* happens asynchronously and may overwrite concurrent local changes if any exist.
|
|
881
|
-
*
|
|
882
|
-
* - Deluje samo online, offline vrže napako
|
|
883
|
-
* - Ne posodablja dexie ali in-mem baze
|
|
884
|
-
* - Uporabljeno za operacije, ki ne potrebujejo lokalne sinhronizacije
|
|
885
|
-
*
|
|
886
|
-
* @example
|
|
887
|
-
* // If you need local state updated after batch operation:
|
|
888
|
-
* await syncedDb.upsertBatch('items', batchSpec);
|
|
889
|
-
* await syncedDb.sync(); // Fetch changes to local state
|
|
875
|
+
* Works offline; entries queue as dirty and upload on reconnect.
|
|
876
|
+
* Per-entry `opts` is ignored (no mongo-`findOneAndUpdate` knob in the
|
|
877
|
+
* diff pipeline).
|
|
890
878
|
*/
|
|
891
879
|
upsertBatch<T extends DbEntity>(collection: string, batch: BatchSpec<T>): Promise<T[]>;
|
|
892
880
|
/** Vrne vse objekte iz in-mem baze za dano kolekcijo */
|