cry-synced-db-client 0.1.157 → 0.1.158
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -2723,19 +2723,32 @@ function isPlainObject4(value) {
|
|
|
2723
2723
|
function hasArrayIndexPath(key) {
|
|
2724
2724
|
return /\.\d+(\.|$)/.test(key);
|
|
2725
2725
|
}
|
|
2726
|
+
function pathTargetsArrayElement(key, arrayPathTokens) {
|
|
2727
|
+
const keyTokens = tokenizePath(key);
|
|
2728
|
+
if (keyTokens.length <= arrayPathTokens.length) return false;
|
|
2729
|
+
for (let i = 0; i < arrayPathTokens.length; i++) {
|
|
2730
|
+
if (keyTokens[i] !== arrayPathTokens[i]) return false;
|
|
2731
|
+
}
|
|
2732
|
+
const next = keyTokens[arrayPathTokens.length];
|
|
2733
|
+
return next.startsWith("[") || /^\d+$/.test(next);
|
|
2734
|
+
}
|
|
2726
2735
|
function fixDotnetArrays(changes, serverRev, baseRev) {
|
|
2727
|
-
const
|
|
2736
|
+
const arrayPathTokens = [];
|
|
2728
2737
|
for (const [k, v] of Object.entries(changes)) {
|
|
2729
|
-
if (Array.isArray(v))
|
|
2738
|
+
if (Array.isArray(v)) arrayPathTokens.push(tokenizePath(k));
|
|
2730
2739
|
}
|
|
2731
2740
|
const isStale = typeof serverRev === "number" && typeof baseRev === "number" && serverRev > baseRev;
|
|
2732
2741
|
const cleaned = {};
|
|
2733
2742
|
for (const [key, value] of Object.entries(changes)) {
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
if (
|
|
2737
|
-
|
|
2743
|
+
let conflictsWithFullArray = false;
|
|
2744
|
+
for (const tokens of arrayPathTokens) {
|
|
2745
|
+
if (pathTargetsArrayElement(key, tokens)) {
|
|
2746
|
+
conflictsWithFullArray = true;
|
|
2747
|
+
break;
|
|
2748
|
+
}
|
|
2738
2749
|
}
|
|
2750
|
+
if (conflictsWithFullArray) continue;
|
|
2751
|
+
if (isStale && hasArrayIndexPath(key)) continue;
|
|
2739
2752
|
cleaned[key] = value;
|
|
2740
2753
|
}
|
|
2741
2754
|
return cleaned;
|
|
@@ -2789,6 +2802,42 @@ function translateKey(key, entity) {
|
|
|
2789
2802
|
return out.join(".");
|
|
2790
2803
|
}
|
|
2791
2804
|
|
|
2805
|
+
// src/utils/stripServerManaged.ts
|
|
2806
|
+
var SERVER_MANAGED_KEYS = /* @__PURE__ */ new Set(["_ts", "_rev", "_csq"]);
|
|
2807
|
+
function isObjectIdLike2(v) {
|
|
2808
|
+
return !!(v && typeof v === "object" && (v._bsontype === "ObjectId" || v._bsontype === "ObjectID"));
|
|
2809
|
+
}
|
|
2810
|
+
function isServerManagedPath(key) {
|
|
2811
|
+
for (const part of tokenizePath(key)) {
|
|
2812
|
+
if (part.startsWith("[")) continue;
|
|
2813
|
+
if (SERVER_MANAGED_KEYS.has(part)) return true;
|
|
2814
|
+
}
|
|
2815
|
+
return false;
|
|
2816
|
+
}
|
|
2817
|
+
function scrubServerManagedDeep(value) {
|
|
2818
|
+
if (value === null || value === void 0) return value;
|
|
2819
|
+
if (Array.isArray(value)) {
|
|
2820
|
+
return value.map((item) => scrubServerManagedDeep(item));
|
|
2821
|
+
}
|
|
2822
|
+
if (typeof value !== "object") return value;
|
|
2823
|
+
if (value instanceof Date) return value;
|
|
2824
|
+
if (isObjectIdLike2(value)) return value;
|
|
2825
|
+
const out = {};
|
|
2826
|
+
for (const k of Object.keys(value)) {
|
|
2827
|
+
if (SERVER_MANAGED_KEYS.has(k)) continue;
|
|
2828
|
+
out[k] = scrubServerManagedDeep(value[k]);
|
|
2829
|
+
}
|
|
2830
|
+
return out;
|
|
2831
|
+
}
|
|
2832
|
+
function stripServerManagedFromChanges(changes) {
|
|
2833
|
+
const out = {};
|
|
2834
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
2835
|
+
if (isServerManagedPath(key)) continue;
|
|
2836
|
+
out[key] = scrubServerManagedDeep(value);
|
|
2837
|
+
}
|
|
2838
|
+
return out;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2792
2841
|
// src/db/sync/SyncEngine.ts
|
|
2793
2842
|
var _SyncEngine = class _SyncEngine {
|
|
2794
2843
|
constructor(config) {
|
|
@@ -3060,16 +3109,14 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3060
3109
|
collection: collectionName,
|
|
3061
3110
|
batch: {
|
|
3062
3111
|
updates: updates.map((item) => {
|
|
3063
|
-
const
|
|
3064
|
-
const stripped =
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
stripped[k] = v;
|
|
3068
|
-
}
|
|
3112
|
+
const dirtyBaseRev = typeof item.delta._rev === "number" ? item.delta._rev : void 0;
|
|
3113
|
+
const stripped = stripServerManagedFromChanges(
|
|
3114
|
+
item.delta
|
|
3115
|
+
);
|
|
3069
3116
|
const fixed = fixDotnetArrays(
|
|
3070
3117
|
stripped,
|
|
3071
3118
|
item.currentServerRev,
|
|
3072
|
-
|
|
3119
|
+
dirtyBaseRev
|
|
3073
3120
|
);
|
|
3074
3121
|
const cleanedChanges = translateBracketPathsToIndex(fixed, item.fullItem);
|
|
3075
3122
|
return {
|
|
@@ -4745,6 +4792,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4745
4792
|
});
|
|
4746
4793
|
delete update._id;
|
|
4747
4794
|
}
|
|
4795
|
+
_SyncedDb.ensureNestedIds(update);
|
|
4748
4796
|
update = _SyncedDb.stringifyObjectIds(update);
|
|
4749
4797
|
const existing = await this.dexieDb.getById(collection, id);
|
|
4750
4798
|
if (!existing && !((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
|
|
@@ -4775,6 +4823,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4775
4823
|
async upsert(collection, query, update) {
|
|
4776
4824
|
this.assertCollection(collection);
|
|
4777
4825
|
this.ensureId(update, "upsert", collection);
|
|
4826
|
+
_SyncedDb.ensureNestedIds(update);
|
|
4778
4827
|
query = _SyncedDb.stringifyObjectIds(query);
|
|
4779
4828
|
update = _SyncedDb.stringifyObjectIds(update);
|
|
4780
4829
|
const existing = await this.findOne(collection, query);
|
|
@@ -4788,6 +4837,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4788
4837
|
var _a;
|
|
4789
4838
|
this.assertCollection(collection);
|
|
4790
4839
|
this.ensureId(data, "insert", collection);
|
|
4840
|
+
_SyncedDb.ensureNestedIds(data);
|
|
4791
4841
|
data = _SyncedDb.stringifyObjectIds(data);
|
|
4792
4842
|
const id = String(data._id);
|
|
4793
4843
|
const existing = await this.dexieDb.getById(collection, id);
|
|
@@ -5889,6 +5939,48 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5889
5939
|
static isObjectIdLike(v) {
|
|
5890
5940
|
return !!(v && typeof v === "object" && (v._bsontype === "ObjectId" || typeof v.toHexString === "function"));
|
|
5891
5941
|
}
|
|
5942
|
+
/**
|
|
5943
|
+
* Recursively walk `value` and ensure every plain object that appears
|
|
5944
|
+
* as an element of an array carries an `_id`. Missing `_id`s are
|
|
5945
|
+
* generated as `new ObjectId().toHexString()` (string, not BSON instance).
|
|
5946
|
+
*
|
|
5947
|
+
* Mutates the input tree in place — caller sees the freshly-assigned ids
|
|
5948
|
+
* (matches the pattern of `ensureId`).
|
|
5949
|
+
*
|
|
5950
|
+
* Why: `computeDiff` falls back to a full-array replace whenever any
|
|
5951
|
+
* element of an array of objects lacks `_id` (see `allElementsHaveId`).
|
|
5952
|
+
* That defeats element-wise `arr[<_id>].field` paths and re-introduces
|
|
5953
|
+
* the stale-array-overwrite bug. Stamping ids upfront keeps every save
|
|
5954
|
+
* on the per-element bracket path.
|
|
5955
|
+
*
|
|
5956
|
+
* Skipped:
|
|
5957
|
+
* - primitives (numbers, strings, booleans, null, undefined)
|
|
5958
|
+
* - `Date`, `ObjectId`-like values
|
|
5959
|
+
* - top-level objects (only ARRAY ELEMENTS get an auto-id; the entity
|
|
5960
|
+
* itself is handled by `ensureId`)
|
|
5961
|
+
*/
|
|
5962
|
+
static ensureNestedIds(value) {
|
|
5963
|
+
if (value === null || value === void 0) return;
|
|
5964
|
+
if (typeof value !== "object") return;
|
|
5965
|
+
if (value instanceof Date) return;
|
|
5966
|
+
if (_SyncedDb.isObjectIdLike(value)) return;
|
|
5967
|
+
if (Array.isArray(value)) {
|
|
5968
|
+
for (const element of value) {
|
|
5969
|
+
if (element !== null && typeof element === "object" && !Array.isArray(element) && !(element instanceof Date) && !_SyncedDb.isObjectIdLike(element)) {
|
|
5970
|
+
if (element._id == null || element._id === "") {
|
|
5971
|
+
element._id = new ObjectId2().toHexString();
|
|
5972
|
+
} else if (_SyncedDb.isObjectIdLike(element._id)) {
|
|
5973
|
+
element._id = String(element._id);
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
_SyncedDb.ensureNestedIds(element);
|
|
5977
|
+
}
|
|
5978
|
+
return;
|
|
5979
|
+
}
|
|
5980
|
+
for (const key of Object.keys(value)) {
|
|
5981
|
+
_SyncedDb.ensureNestedIds(value[key]);
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5892
5984
|
/**
|
|
5893
5985
|
* Asserts write-only collection has online connectivity for reads.
|
|
5894
5986
|
* @throws Error if offline
|
|
@@ -403,6 +403,27 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
403
403
|
*/
|
|
404
404
|
private static stringifyObjectIds;
|
|
405
405
|
private static isObjectIdLike;
|
|
406
|
+
/**
|
|
407
|
+
* Recursively walk `value` and ensure every plain object that appears
|
|
408
|
+
* as an element of an array carries an `_id`. Missing `_id`s are
|
|
409
|
+
* generated as `new ObjectId().toHexString()` (string, not BSON instance).
|
|
410
|
+
*
|
|
411
|
+
* Mutates the input tree in place — caller sees the freshly-assigned ids
|
|
412
|
+
* (matches the pattern of `ensureId`).
|
|
413
|
+
*
|
|
414
|
+
* Why: `computeDiff` falls back to a full-array replace whenever any
|
|
415
|
+
* element of an array of objects lacks `_id` (see `allElementsHaveId`).
|
|
416
|
+
* That defeats element-wise `arr[<_id>].field` paths and re-introduces
|
|
417
|
+
* the stale-array-overwrite bug. Stamping ids upfront keeps every save
|
|
418
|
+
* on the per-element bracket path.
|
|
419
|
+
*
|
|
420
|
+
* Skipped:
|
|
421
|
+
* - primitives (numbers, strings, booleans, null, undefined)
|
|
422
|
+
* - `Date`, `ObjectId`-like values
|
|
423
|
+
* - top-level objects (only ARRAY ELEMENTS get an auto-id; the entity
|
|
424
|
+
* itself is handled by `ensureId`)
|
|
425
|
+
*/
|
|
426
|
+
private static ensureNestedIds;
|
|
406
427
|
/**
|
|
407
428
|
* Asserts write-only collection has online connectivity for reads.
|
|
408
429
|
* @throws Error if offline
|
|
@@ -13,6 +13,28 @@
|
|
|
13
13
|
* "arr.10" → true (multi-digit index)
|
|
14
14
|
*/
|
|
15
15
|
export declare function hasArrayIndexPath(key: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* `true` if `key` targets an element of the array at `arrayPath`.
|
|
18
|
+
*
|
|
19
|
+
* Both inputs are tokenized via `tokenizePath` (so dot AND bracket-by-_id
|
|
20
|
+
* segments are normalized). The key targets an element iff:
|
|
21
|
+
* 1. its first N tokens equal `arrayPath`'s tokens (prefix match), AND
|
|
22
|
+
* 2. the very next token is a numeric index ("0", "1", …) OR a bracket
|
|
23
|
+
* `[<_id>]` segment.
|
|
24
|
+
*
|
|
25
|
+
* Examples (with arrayPath = "zaracunaj"):
|
|
26
|
+
* "zaracunaj" → false (same path, not a child)
|
|
27
|
+
* "zaracunaj.0.kolicina" → true (child via numeric index)
|
|
28
|
+
* "zaracunaj[<_id>].kolicina" → true (child via bracket _id)
|
|
29
|
+
* "zaracunajX" → false (different field)
|
|
30
|
+
* "zaracunaj.kolicina" → false (object field, not element)
|
|
31
|
+
*
|
|
32
|
+
* Examples (with arrayPath = "_redundanca.terapije"):
|
|
33
|
+
* "_redundanca.terapije[A].postavke[B].x" → true
|
|
34
|
+
* "_redundanca.terapije.0.x" → true
|
|
35
|
+
* "_redundanca.terapija" → false (different field)
|
|
36
|
+
*/
|
|
37
|
+
export declare function pathTargetsArrayElement(key: string, arrayPathTokens: readonly string[]): boolean;
|
|
16
38
|
/**
|
|
17
39
|
* TEMPORARY mitigation: when server has advanced past client's base _rev
|
|
18
40
|
* (i.e., the dirty was emitted against a stale snapshot of the record),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `true` if `key` (a dot/bracket-notation path) targets a server-managed
|
|
3
|
+
* metadata field anywhere along its tokenized path.
|
|
4
|
+
*
|
|
5
|
+
* Examples:
|
|
6
|
+
* "_ts" → true
|
|
7
|
+
* "_ts.t" → true (token `_ts`)
|
|
8
|
+
* "_rev" → true
|
|
9
|
+
* "field._ts" → true
|
|
10
|
+
* "arr[<_id>]._rev" → true
|
|
11
|
+
* "_redundanca.terapije[X]._ts.i" → true
|
|
12
|
+
* "stranka.gsm" → false
|
|
13
|
+
* "zaracunaj[<_id>].kolicina" → false
|
|
14
|
+
*/
|
|
15
|
+
export declare function isServerManagedPath(key: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Recursively scrub `_ts` / `_rev` / `_csq` from a value tree without
|
|
18
|
+
* mutating the input. Returns a new tree with the offending keys removed
|
|
19
|
+
* at every depth, including inside arrays.
|
|
20
|
+
*
|
|
21
|
+
* Primitives, Date, and ObjectId-like instances pass through by reference.
|
|
22
|
+
*/
|
|
23
|
+
export declare function scrubServerManagedDeep<T>(value: T): T;
|
|
24
|
+
/**
|
|
25
|
+
* Strip server-managed metadata from a dirty-changes payload at upload time.
|
|
26
|
+
*
|
|
27
|
+
* 1. Drop any path key whose tokenized path includes `_ts`, `_rev`, or `_csq`.
|
|
28
|
+
* 2. For surviving entries, recursively scrub the value (in case a value is
|
|
29
|
+
* a full object/array replace that contains nested `_ts` / `_rev`).
|
|
30
|
+
*
|
|
31
|
+
* Returns a new object — input is not mutated.
|
|
32
|
+
*/
|
|
33
|
+
export declare function stripServerManagedFromChanges(changes: Record<string, unknown>): Record<string, unknown>;
|