cry-synced-db-client 0.1.159 → 0.1.161
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
|
@@ -330,28 +330,67 @@ function sameIdSequence(a, b) {
|
|
|
330
330
|
return true;
|
|
331
331
|
}
|
|
332
332
|
function computeArrayDiff(existingArr, updateArr, basePath, diff) {
|
|
333
|
-
if (existingArr.length === 0)
|
|
334
|
-
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
if (!allElementsHaveId(existingArr) || !allElementsHaveId(updateArr)) {
|
|
333
|
+
if (existingArr.length === 0 && updateArr.length === 0) return;
|
|
334
|
+
if (!allElementsHaveId(updateArr) || existingArr.length > 0 && !allElementsHaveId(existingArr)) {
|
|
338
335
|
if (!deepEquals(existingArr, updateArr)) {
|
|
339
336
|
diff[basePath] = updateArr;
|
|
340
337
|
}
|
|
341
338
|
return;
|
|
342
339
|
}
|
|
343
|
-
|
|
344
|
-
|
|
340
|
+
const existingIds = /* @__PURE__ */ new Set();
|
|
341
|
+
for (const e of existingArr) existingIds.add(String(e._id));
|
|
342
|
+
const updateIds = /* @__PURE__ */ new Set();
|
|
343
|
+
for (const u of updateArr) updateIds.add(String(u._id));
|
|
344
|
+
let sameSet = existingIds.size === updateIds.size;
|
|
345
|
+
if (sameSet) {
|
|
346
|
+
for (const id of existingIds) {
|
|
347
|
+
if (!updateIds.has(id)) {
|
|
348
|
+
sameSet = false;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (sameSet) {
|
|
354
|
+
if (sameIdSequence(existingArr, updateArr)) {
|
|
355
|
+
for (let i = 0; i < updateArr.length; i++) {
|
|
356
|
+
const elementId = String(updateArr[i]._id);
|
|
357
|
+
computeDiffInto(
|
|
358
|
+
existingArr[i],
|
|
359
|
+
updateArr[i],
|
|
360
|
+
`${basePath}[${elementId}]`,
|
|
361
|
+
diff
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
diff[basePath] = updateArr;
|
|
366
|
+
}
|
|
345
367
|
return;
|
|
346
368
|
}
|
|
347
|
-
for (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
)
|
|
369
|
+
for (const id of existingIds) {
|
|
370
|
+
if (!updateIds.has(id)) {
|
|
371
|
+
diff[`${basePath}[${id}]`] = void 0;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
for (const updateEl of updateArr) {
|
|
375
|
+
const id = String(updateEl._id);
|
|
376
|
+
if (!existingIds.has(id)) {
|
|
377
|
+
diff[`${basePath}[${id}]`] = [updateEl];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (existingArr.length > 0) {
|
|
381
|
+
const existingById = /* @__PURE__ */ new Map();
|
|
382
|
+
for (const e of existingArr) existingById.set(String(e._id), e);
|
|
383
|
+
for (const updateEl of updateArr) {
|
|
384
|
+
const id = String(updateEl._id);
|
|
385
|
+
if (existingIds.has(id)) {
|
|
386
|
+
computeDiffInto(
|
|
387
|
+
existingById.get(id),
|
|
388
|
+
updateEl,
|
|
389
|
+
`${basePath}[${id}]`,
|
|
390
|
+
diff
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
355
394
|
}
|
|
356
395
|
}
|
|
357
396
|
function computeDiffInto(existing, update, basePath, diff) {
|
|
@@ -482,12 +521,62 @@ function setSegment(current, part, value) {
|
|
|
482
521
|
}
|
|
483
522
|
return false;
|
|
484
523
|
}
|
|
524
|
+
function deleteByPath(target2, path) {
|
|
525
|
+
if (target2 === null || target2 === void 0) return false;
|
|
526
|
+
const parts = tokenizePath(path);
|
|
527
|
+
if (parts.length === 0) return false;
|
|
528
|
+
let current = target2;
|
|
529
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
530
|
+
const next = navigateSegment(current, parts[i]);
|
|
531
|
+
if (next === void 0 || next === null) return false;
|
|
532
|
+
current = next;
|
|
533
|
+
}
|
|
534
|
+
const last = parts[parts.length - 1];
|
|
535
|
+
return deleteSegment(current, last);
|
|
536
|
+
}
|
|
537
|
+
function deleteSegment(current, part) {
|
|
538
|
+
if (current === null || current === void 0) return false;
|
|
539
|
+
if (part.startsWith("[") && part.endsWith("]")) {
|
|
540
|
+
const idStr = part.slice(1, -1);
|
|
541
|
+
if (!Array.isArray(current)) return false;
|
|
542
|
+
const idx = current.findIndex((item) => item && String(item._id) === idStr);
|
|
543
|
+
if (idx < 0) return false;
|
|
544
|
+
current.splice(idx, 1);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
if (/^\d+$/.test(part) && Array.isArray(current)) {
|
|
548
|
+
const idx = Number(part);
|
|
549
|
+
if (idx < 0 || idx >= current.length) return false;
|
|
550
|
+
current.splice(idx, 1);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
if (typeof current === "object") {
|
|
554
|
+
if (!Object.prototype.hasOwnProperty.call(current, part)) return false;
|
|
555
|
+
delete current[part];
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
function isTerminalBracketKey(path) {
|
|
561
|
+
const tokens = tokenizePath(path);
|
|
562
|
+
const last = tokens[tokens.length - 1];
|
|
563
|
+
return !!(last && last.length >= 2 && last.charCodeAt(0) === 91 && last.charCodeAt(last.length - 1) === 93);
|
|
564
|
+
}
|
|
485
565
|
function mergeDirtyPath(accumulated, newPath, newValue) {
|
|
486
566
|
for (const existingKey of Object.keys(accumulated)) {
|
|
487
567
|
if (existingKey === newPath) continue;
|
|
488
568
|
if (isDescendantOrEqual(newPath, existingKey)) {
|
|
569
|
+
const existingValue = accumulated[existingKey];
|
|
570
|
+
const existingIsTerminal = isTerminalBracketKey(existingKey);
|
|
571
|
+
if (existingIsTerminal && existingValue === void 0) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
let mutationTarget = existingValue;
|
|
575
|
+
if (existingIsTerminal && Array.isArray(existingValue) && existingValue.length === 1) {
|
|
576
|
+
mutationTarget = existingValue[0];
|
|
577
|
+
}
|
|
489
578
|
const relativePath = newPath.substring(existingKey.length + 1);
|
|
490
|
-
const ok = setByPath(
|
|
579
|
+
const ok = setByPath(mutationTarget, relativePath, newValue);
|
|
491
580
|
if (ok) return;
|
|
492
581
|
break;
|
|
493
582
|
}
|
|
@@ -508,6 +597,51 @@ function mergeDirtyChanges(accumulated, newChanges) {
|
|
|
508
597
|
}
|
|
509
598
|
}
|
|
510
599
|
|
|
600
|
+
// src/utils/normalizeUndefined.ts
|
|
601
|
+
var SERVER_MANAGED_KEYS = /* @__PURE__ */ new Set(["_ts", "_rev", "_csq"]);
|
|
602
|
+
function isObjectIdLike2(v) {
|
|
603
|
+
return !!(v && typeof v === "object" && (v._bsontype === "ObjectId" || v._bsontype === "ObjectID"));
|
|
604
|
+
}
|
|
605
|
+
function collectUnsetPaths(value) {
|
|
606
|
+
const paths = [];
|
|
607
|
+
walk(value, "", paths);
|
|
608
|
+
return paths;
|
|
609
|
+
}
|
|
610
|
+
function walk(node, prefix, paths) {
|
|
611
|
+
if (node === null || node === void 0) return;
|
|
612
|
+
if (typeof node !== "object") return;
|
|
613
|
+
if (node instanceof Date) return;
|
|
614
|
+
if (isObjectIdLike2(node)) return;
|
|
615
|
+
if (Array.isArray(node)) {
|
|
616
|
+
for (let i = 0; i < node.length; i++) {
|
|
617
|
+
const element = node[i];
|
|
618
|
+
const childPath = childPathForArrayElement(prefix, element, i);
|
|
619
|
+
if (element === void 0) {
|
|
620
|
+
paths.push(childPath);
|
|
621
|
+
} else {
|
|
622
|
+
walk(element, childPath, paths);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
for (const key of Object.keys(node)) {
|
|
628
|
+
if (SERVER_MANAGED_KEYS.has(key)) continue;
|
|
629
|
+
const child = node[key];
|
|
630
|
+
const childPath = prefix ? `${prefix}.${key}` : key;
|
|
631
|
+
if (child === void 0) {
|
|
632
|
+
paths.push(childPath);
|
|
633
|
+
} else {
|
|
634
|
+
walk(child, childPath, paths);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function childPathForArrayElement(prefix, element, index) {
|
|
639
|
+
if (element !== null && element !== void 0 && typeof element === "object" && !Array.isArray(element) && !(element instanceof Date) && !isObjectIdLike2(element) && element._id != null) {
|
|
640
|
+
return `${prefix}[${String(element._id)}]`;
|
|
641
|
+
}
|
|
642
|
+
return prefix ? `${prefix}.${index}` : String(index);
|
|
643
|
+
}
|
|
644
|
+
|
|
511
645
|
// src/db/managers/InMemManager.ts
|
|
512
646
|
var InMemManager = class {
|
|
513
647
|
constructor(config) {
|
|
@@ -2755,62 +2889,19 @@ function fixDotnetArrays(changes, serverRev, baseRev) {
|
|
|
2755
2889
|
}
|
|
2756
2890
|
|
|
2757
2891
|
// src/utils/translateBracketPaths.ts
|
|
2758
|
-
function translateBracketPathsToIndex(changes,
|
|
2759
|
-
|
|
2760
|
-
for (const [key, value] of Object.entries(changes)) {
|
|
2761
|
-
const translated = translateKey(key, entity);
|
|
2762
|
-
if (translated !== null) {
|
|
2763
|
-
result[translated] = value;
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
return result;
|
|
2767
|
-
}
|
|
2768
|
-
function translateKey(key, entity) {
|
|
2769
|
-
const parts = tokenizePath(key);
|
|
2770
|
-
const out = [];
|
|
2771
|
-
let cursor = entity;
|
|
2772
|
-
for (let i = 0; i < parts.length; i++) {
|
|
2773
|
-
const part = parts[i];
|
|
2774
|
-
if (part.startsWith("[") && part.endsWith("]")) {
|
|
2775
|
-
const idStr = part.slice(1, -1);
|
|
2776
|
-
if (!Array.isArray(cursor)) return null;
|
|
2777
|
-
const idx = cursor.findIndex(
|
|
2778
|
-
(item) => item && String(item._id) === idStr
|
|
2779
|
-
);
|
|
2780
|
-
if (idx < 0) return null;
|
|
2781
|
-
out.push(String(idx));
|
|
2782
|
-
cursor = cursor[idx];
|
|
2783
|
-
continue;
|
|
2784
|
-
}
|
|
2785
|
-
out.push(part);
|
|
2786
|
-
if (cursor === null || cursor === void 0) {
|
|
2787
|
-
for (let j = i + 1; j < parts.length; j++) {
|
|
2788
|
-
const p = parts[j];
|
|
2789
|
-
if (p.startsWith("[") && p.endsWith("]")) return null;
|
|
2790
|
-
out.push(p);
|
|
2791
|
-
}
|
|
2792
|
-
return out.join(".");
|
|
2793
|
-
}
|
|
2794
|
-
if (/^\d+$/.test(part) && Array.isArray(cursor)) {
|
|
2795
|
-
cursor = cursor[Number(part)];
|
|
2796
|
-
} else if (typeof cursor === "object") {
|
|
2797
|
-
cursor = cursor[part];
|
|
2798
|
-
} else {
|
|
2799
|
-
cursor = void 0;
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
return out.join(".");
|
|
2892
|
+
function translateBracketPathsToIndex(changes, _entity) {
|
|
2893
|
+
return changes;
|
|
2803
2894
|
}
|
|
2804
2895
|
|
|
2805
2896
|
// src/utils/stripServerManaged.ts
|
|
2806
|
-
var
|
|
2807
|
-
function
|
|
2897
|
+
var SERVER_MANAGED_KEYS2 = /* @__PURE__ */ new Set(["_ts", "_rev", "_csq"]);
|
|
2898
|
+
function isObjectIdLike3(v) {
|
|
2808
2899
|
return !!(v && typeof v === "object" && (v._bsontype === "ObjectId" || v._bsontype === "ObjectID"));
|
|
2809
2900
|
}
|
|
2810
2901
|
function isServerManagedPath(key) {
|
|
2811
2902
|
for (const part of tokenizePath(key)) {
|
|
2812
2903
|
if (part.startsWith("[")) continue;
|
|
2813
|
-
if (
|
|
2904
|
+
if (SERVER_MANAGED_KEYS2.has(part)) return true;
|
|
2814
2905
|
}
|
|
2815
2906
|
return false;
|
|
2816
2907
|
}
|
|
@@ -2821,10 +2912,10 @@ function scrubServerManagedDeep(value) {
|
|
|
2821
2912
|
}
|
|
2822
2913
|
if (typeof value !== "object") return value;
|
|
2823
2914
|
if (value instanceof Date) return value;
|
|
2824
|
-
if (
|
|
2915
|
+
if (isObjectIdLike3(value)) return value;
|
|
2825
2916
|
const out = {};
|
|
2826
2917
|
for (const k of Object.keys(value)) {
|
|
2827
|
-
if (
|
|
2918
|
+
if (SERVER_MANAGED_KEYS2.has(k)) continue;
|
|
2828
2919
|
out[k] = scrubServerManagedDeep(value[k]);
|
|
2829
2920
|
}
|
|
2830
2921
|
return out;
|
|
@@ -4820,7 +4911,11 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4820
4911
|
this.pendingChanges.schedule(collection, id, newData, 0, "save");
|
|
4821
4912
|
const isWriteOnly = (_b = this.collections.get(collection)) == null ? void 0 : _b.writeOnly;
|
|
4822
4913
|
const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
|
|
4823
|
-
const merged =
|
|
4914
|
+
const merged = _SyncedDb.applyDiffLocally(
|
|
4915
|
+
currentMem != null ? currentMem : existing,
|
|
4916
|
+
diff,
|
|
4917
|
+
id
|
|
4918
|
+
);
|
|
4824
4919
|
if (!isWriteOnly && !(existing == null ? void 0 : existing._deleted) && !(existing == null ? void 0 : existing._archived)) {
|
|
4825
4920
|
this.inMemManager.writeBatch(collection, [merged], "upsert", { source: "incremental" });
|
|
4826
4921
|
}
|
|
@@ -4845,6 +4940,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4845
4940
|
this.ensureId(data, "insert", collection);
|
|
4846
4941
|
_SyncedDb.ensureNestedIds(data);
|
|
4847
4942
|
data = _SyncedDb.stringifyObjectIds(data);
|
|
4943
|
+
const unsetPaths = collectUnsetPaths(data);
|
|
4848
4944
|
const id = String(data._id);
|
|
4849
4945
|
const existing = await this.dexieDb.getById(collection, id);
|
|
4850
4946
|
if (existing && !existing._deleted && !existing._archived) {
|
|
@@ -4862,6 +4958,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4862
4958
|
_id: id,
|
|
4863
4959
|
_lastUpdaterId: this.updaterId
|
|
4864
4960
|
});
|
|
4961
|
+
for (const path of unsetPaths) deleteByPath(newData, path);
|
|
4865
4962
|
this.pendingChanges.schedule(collection, id, newData, 0, "insert");
|
|
4866
4963
|
if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
|
|
4867
4964
|
this.inMemManager.writeBatch(collection, [newData], "upsert", { source: "incremental" });
|
|
@@ -5945,6 +6042,64 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5945
6042
|
static isObjectIdLike(v) {
|
|
5946
6043
|
return !!(v && typeof v === "object" && (v._bsontype === "ObjectId" || typeof v.toHexString === "function"));
|
|
5947
6044
|
}
|
|
6045
|
+
/**
|
|
6046
|
+
* Mongo-symmetric local apply: starting from `base` (a safe deep clone of
|
|
6047
|
+
* `currentMem` or `existing`), walk each `(path, value)` entry of `diff`:
|
|
6048
|
+
*
|
|
6049
|
+
* - `value === undefined` → `deleteByPath` (mongo `$unset` symmetric)
|
|
6050
|
+
* - otherwise → `setByPath` (mongo `$set` symmetric)
|
|
6051
|
+
*
|
|
6052
|
+
* The result is what an equivalent server-side `$set` + `$unset` would
|
|
6053
|
+
* have produced. Replaces the previous shallow `{ ...currentMem, ...update }`
|
|
6054
|
+
* merge which dropped nested fields the caller's `update` didn't mention.
|
|
6055
|
+
*
|
|
6056
|
+
* Returns a new object (the cloned-and-mutated `base`); never mutates
|
|
6057
|
+
* the input `base` reference.
|
|
6058
|
+
*/
|
|
6059
|
+
static applyDiffLocally(base, diff, fallbackId) {
|
|
6060
|
+
const seed = base ? _SyncedDb.safeDeepClone(base) : { _id: fallbackId };
|
|
6061
|
+
if (seed._id == null) seed._id = fallbackId;
|
|
6062
|
+
for (const path of Object.keys(diff)) {
|
|
6063
|
+
const value = diff[path];
|
|
6064
|
+
if (value === void 0) {
|
|
6065
|
+
deleteByPath(seed, path);
|
|
6066
|
+
continue;
|
|
6067
|
+
}
|
|
6068
|
+
if (!path.includes(".") && !path.includes("[")) {
|
|
6069
|
+
seed[path] = value;
|
|
6070
|
+
continue;
|
|
6071
|
+
}
|
|
6072
|
+
const ok = setByPath(seed, path, value);
|
|
6073
|
+
if (!ok) seed[path] = value;
|
|
6074
|
+
}
|
|
6075
|
+
return seed;
|
|
6076
|
+
}
|
|
6077
|
+
/**
|
|
6078
|
+
* Deep clone for `applyDiffLocally`. Recurses into plain objects and
|
|
6079
|
+
* arrays; preserves `Date` (cloned to avoid shared reference) and
|
|
6080
|
+
* `ObjectId`-like values by reference (their internal Buffer state is
|
|
6081
|
+
* immutable from our perspective). Other class instances pass through
|
|
6082
|
+
* by reference. Avoids `structuredClone` because it throws on class
|
|
6083
|
+
* instances like bson `ObjectId`.
|
|
6084
|
+
*/
|
|
6085
|
+
static safeDeepClone(value) {
|
|
6086
|
+
if (value === null || value === void 0) return value;
|
|
6087
|
+
if (typeof value !== "object") return value;
|
|
6088
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
6089
|
+
if (_SyncedDb.isObjectIdLike(value)) return value;
|
|
6090
|
+
if (Array.isArray(value)) {
|
|
6091
|
+
const out2 = new Array(value.length);
|
|
6092
|
+
for (let i = 0; i < value.length; i++) out2[i] = _SyncedDb.safeDeepClone(value[i]);
|
|
6093
|
+
return out2;
|
|
6094
|
+
}
|
|
6095
|
+
const proto = Object.getPrototypeOf(value);
|
|
6096
|
+
if (proto !== Object.prototype && proto !== null) return value;
|
|
6097
|
+
const out = {};
|
|
6098
|
+
for (const key of Object.keys(value)) {
|
|
6099
|
+
out[key] = _SyncedDb.safeDeepClone(value[key]);
|
|
6100
|
+
}
|
|
6101
|
+
return out;
|
|
6102
|
+
}
|
|
5948
6103
|
/**
|
|
5949
6104
|
* Recursively walk `value` and ensure every plain object that appears
|
|
5950
6105
|
* as an element of an array carries an `_id`. Missing `_id`s are
|
|
@@ -6345,14 +6500,6 @@ var C1 = new C1Type();
|
|
|
6345
6500
|
C1.name = "MessagePack 0xC1";
|
|
6346
6501
|
var sequentialMode = false;
|
|
6347
6502
|
var inlineObjectReadThreshold = 2;
|
|
6348
|
-
var readStruct;
|
|
6349
|
-
var onLoadedStructures;
|
|
6350
|
-
var onSaveState;
|
|
6351
|
-
try {
|
|
6352
|
-
new Function("");
|
|
6353
|
-
} catch (error) {
|
|
6354
|
-
inlineObjectReadThreshold = Infinity;
|
|
6355
|
-
}
|
|
6356
6503
|
var Unpackr = class _Unpackr {
|
|
6357
6504
|
constructor(options) {
|
|
6358
6505
|
if (options) {
|
|
@@ -6455,8 +6602,8 @@ var Unpackr = class _Unpackr {
|
|
|
6455
6602
|
}
|
|
6456
6603
|
}
|
|
6457
6604
|
_mergeStructures(loadedStructures, existingStructures) {
|
|
6458
|
-
if (
|
|
6459
|
-
loadedStructures =
|
|
6605
|
+
if (this._onLoadedStructures)
|
|
6606
|
+
loadedStructures = this._onLoadedStructures(loadedStructures);
|
|
6460
6607
|
loadedStructures = loadedStructures || [];
|
|
6461
6608
|
if (Object.isFrozen(loadedStructures))
|
|
6462
6609
|
loadedStructures = loadedStructures.map((structure) => structure.slice(0));
|
|
@@ -6494,8 +6641,8 @@ function checkedRead(options) {
|
|
|
6494
6641
|
currentStructures.length = sharedLength;
|
|
6495
6642
|
}
|
|
6496
6643
|
let result;
|
|
6497
|
-
if (currentUnpackr.
|
|
6498
|
-
result =
|
|
6644
|
+
if (currentUnpackr._readStruct && src[position] < 64 && src[position] >= 32) {
|
|
6645
|
+
result = currentUnpackr._readStruct(src, position, srcEnd);
|
|
6499
6646
|
src = null;
|
|
6500
6647
|
if (!(options && options.lazy) && result)
|
|
6501
6648
|
result = result.toJSON();
|
|
@@ -6784,10 +6931,16 @@ var validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
|
|
|
6784
6931
|
function createStructureReader(structure, firstId) {
|
|
6785
6932
|
function readObject() {
|
|
6786
6933
|
if (readObject.count++ > inlineObjectReadThreshold) {
|
|
6787
|
-
let
|
|
6934
|
+
let optimizedReadObject;
|
|
6935
|
+
try {
|
|
6936
|
+
optimizedReadObject = structure.read = new Function("r", "return function(){return " + (currentUnpackr.freezeData ? "Object.freeze" : "") + "({" + structure.map((key) => key === "__proto__" ? "__proto_:r()" : validName.test(key) ? key + ":r()" : "[" + JSON.stringify(key) + "]:r()").join(",") + "})}")(read);
|
|
6937
|
+
} catch (error) {
|
|
6938
|
+
inlineObjectReadThreshold = Infinity;
|
|
6939
|
+
return readObject();
|
|
6940
|
+
}
|
|
6788
6941
|
if (structure.highByte === 0)
|
|
6789
6942
|
structure.read = createSecondByteReader(firstId, structure.read);
|
|
6790
|
-
return
|
|
6943
|
+
return optimizedReadObject();
|
|
6791
6944
|
}
|
|
6792
6945
|
let object = {};
|
|
6793
6946
|
for (let i = 0, l = structure.length; i < l; i++) {
|
|
@@ -7219,7 +7372,7 @@ currentExtensions[66] = (data) => {
|
|
|
7219
7372
|
if (length <= 40) {
|
|
7220
7373
|
let out = view.getBigUint64(start);
|
|
7221
7374
|
for (let i = start + 8; i < end; i += 8) {
|
|
7222
|
-
out <<= BigInt(
|
|
7375
|
+
out <<= BigInt(64);
|
|
7223
7376
|
out |= view.getBigUint64(i);
|
|
7224
7377
|
}
|
|
7225
7378
|
return out;
|
|
@@ -7334,8 +7487,8 @@ currentExtensions[255] = (data) => {
|
|
|
7334
7487
|
return /* @__PURE__ */ new Date("invalid");
|
|
7335
7488
|
};
|
|
7336
7489
|
function saveState(callback) {
|
|
7337
|
-
if (
|
|
7338
|
-
|
|
7490
|
+
if (currentUnpackr && currentUnpackr._onSaveState)
|
|
7491
|
+
currentUnpackr._onSaveState();
|
|
7339
7492
|
let savedSrcEnd = srcEnd;
|
|
7340
7493
|
let savedPosition = position;
|
|
7341
7494
|
let savedStringPosition = stringPosition;
|
|
@@ -7389,6 +7542,7 @@ var FLOAT32_OPTIONS = {
|
|
|
7389
7542
|
};
|
|
7390
7543
|
var f32Array = new Float32Array(1);
|
|
7391
7544
|
var u8Array = new Uint8Array(f32Array.buffer, 0, 4);
|
|
7545
|
+
Unpackr.SUPPORTS_STRUCT_HOOKS = true;
|
|
7392
7546
|
|
|
7393
7547
|
// node_modules/msgpackr/pack.js
|
|
7394
7548
|
var textEncoder;
|
|
@@ -7410,7 +7564,6 @@ var targetView;
|
|
|
7410
7564
|
var position2 = 0;
|
|
7411
7565
|
var safeEnd;
|
|
7412
7566
|
var bundledStrings2 = null;
|
|
7413
|
-
var writeStructSlots;
|
|
7414
7567
|
var MAX_BUNDLE_SIZE = 21760;
|
|
7415
7568
|
var hasNonLatin = /[\u0080-\uFFFF]/;
|
|
7416
7569
|
var RECORD_SYMBOL = /* @__PURE__ */ Symbol("record-id");
|
|
@@ -7512,7 +7665,7 @@ var Packr = class extends Unpackr {
|
|
|
7512
7665
|
hasSharedUpdate = false;
|
|
7513
7666
|
let encodingError;
|
|
7514
7667
|
try {
|
|
7515
|
-
if (packr3.
|
|
7668
|
+
if (packr3._writeStruct && value && typeof value === "object") {
|
|
7516
7669
|
if (value.constructor === Object) writeStruct(value);
|
|
7517
7670
|
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some((extClass) => value instanceof extClass)) {
|
|
7518
7671
|
writeStruct(value.toJSON ? value.toJSON() : value);
|
|
@@ -7575,7 +7728,7 @@ var Packr = class extends Unpackr {
|
|
|
7575
7728
|
if (hasSharedUpdate && packr3.saveStructures) {
|
|
7576
7729
|
let sharedLength = structures.sharedLength || 0;
|
|
7577
7730
|
let returnBuffer = target.subarray(start, position2);
|
|
7578
|
-
let newSharedData = prepareStructures(structures, packr3);
|
|
7731
|
+
let newSharedData = (packr3._prepareStructures || prepareStructures)(structures, packr3);
|
|
7579
7732
|
if (!encodingError) {
|
|
7580
7733
|
if (packr3.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
|
|
7581
7734
|
return packr3.pack(value, encodeOptions);
|
|
@@ -7732,7 +7885,7 @@ var Packr = class extends Unpackr {
|
|
|
7732
7885
|
position2 += length;
|
|
7733
7886
|
} else if (type === "number") {
|
|
7734
7887
|
if (value >>> 0 === value) {
|
|
7735
|
-
if (value < 32 || value < 128 && this.useRecords === false || value < 64 && !this.
|
|
7888
|
+
if (value < 32 || value < 128 && this.useRecords === false || value < 64 && !this._writeStruct) {
|
|
7736
7889
|
target[position2++] = value;
|
|
7737
7890
|
} else if (value < 256) {
|
|
7738
7891
|
target[position2++] = 204;
|
|
@@ -8091,6 +8244,23 @@ var Packr = class extends Unpackr {
|
|
|
8091
8244
|
const writeObject = checkUseRecords ? (object) => {
|
|
8092
8245
|
checkUseRecords(object) ? writeRecord(object) : writePlainObject(object);
|
|
8093
8246
|
} : writeRecord;
|
|
8247
|
+
const writeStruct = (object) => {
|
|
8248
|
+
let newPosition = packr3._writeStruct(object, target, start, position2, structures, makeRoom, (value, newPosition2, notifySharedUpdate) => {
|
|
8249
|
+
if (notifySharedUpdate)
|
|
8250
|
+
return hasSharedUpdate = true;
|
|
8251
|
+
position2 = newPosition2;
|
|
8252
|
+
let startTarget = target;
|
|
8253
|
+
pack3(value);
|
|
8254
|
+
resetStructures();
|
|
8255
|
+
if (startTarget !== target) {
|
|
8256
|
+
return { position: position2, targetView, target };
|
|
8257
|
+
}
|
|
8258
|
+
return position2;
|
|
8259
|
+
});
|
|
8260
|
+
if (newPosition === 0)
|
|
8261
|
+
return writeObject(object);
|
|
8262
|
+
position2 = newPosition;
|
|
8263
|
+
};
|
|
8094
8264
|
const makeRoom = (end) => {
|
|
8095
8265
|
let newSize;
|
|
8096
8266
|
if (end > 16777216) {
|
|
@@ -8191,23 +8361,6 @@ var Packr = class extends Unpackr {
|
|
|
8191
8361
|
target[insertionOffset + start] = keysTarget[0];
|
|
8192
8362
|
}
|
|
8193
8363
|
};
|
|
8194
|
-
const writeStruct = (object) => {
|
|
8195
|
-
let newPosition = writeStructSlots(object, target, start, position2, structures, makeRoom, (value, newPosition2, notifySharedUpdate) => {
|
|
8196
|
-
if (notifySharedUpdate)
|
|
8197
|
-
return hasSharedUpdate = true;
|
|
8198
|
-
position2 = newPosition2;
|
|
8199
|
-
let startTarget = target;
|
|
8200
|
-
pack3(value);
|
|
8201
|
-
resetStructures();
|
|
8202
|
-
if (startTarget !== target) {
|
|
8203
|
-
return { position: position2, targetView, target };
|
|
8204
|
-
}
|
|
8205
|
-
return position2;
|
|
8206
|
-
}, this);
|
|
8207
|
-
if (newPosition === 0)
|
|
8208
|
-
return writeObject(object);
|
|
8209
|
-
position2 = newPosition;
|
|
8210
|
-
};
|
|
8211
8364
|
}
|
|
8212
8365
|
useBuffer(buffer) {
|
|
8213
8366
|
target = buffer;
|
|
@@ -8444,6 +8597,7 @@ function prepareStructures(structures, packr3) {
|
|
|
8444
8597
|
};
|
|
8445
8598
|
return structures;
|
|
8446
8599
|
}
|
|
8600
|
+
Packr.SUPPORTS_STRUCT_HOOKS = true;
|
|
8447
8601
|
var defaultPackr = new Packr({ useRecords: false });
|
|
8448
8602
|
var pack = defaultPackr.pack;
|
|
8449
8603
|
var encode = defaultPackr.pack;
|
|
@@ -403,6 +403,30 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
403
403
|
*/
|
|
404
404
|
private static stringifyObjectIds;
|
|
405
405
|
private static isObjectIdLike;
|
|
406
|
+
/**
|
|
407
|
+
* Mongo-symmetric local apply: starting from `base` (a safe deep clone of
|
|
408
|
+
* `currentMem` or `existing`), walk each `(path, value)` entry of `diff`:
|
|
409
|
+
*
|
|
410
|
+
* - `value === undefined` → `deleteByPath` (mongo `$unset` symmetric)
|
|
411
|
+
* - otherwise → `setByPath` (mongo `$set` symmetric)
|
|
412
|
+
*
|
|
413
|
+
* The result is what an equivalent server-side `$set` + `$unset` would
|
|
414
|
+
* have produced. Replaces the previous shallow `{ ...currentMem, ...update }`
|
|
415
|
+
* merge which dropped nested fields the caller's `update` didn't mention.
|
|
416
|
+
*
|
|
417
|
+
* Returns a new object (the cloned-and-mutated `base`); never mutates
|
|
418
|
+
* the input `base` reference.
|
|
419
|
+
*/
|
|
420
|
+
private static applyDiffLocally;
|
|
421
|
+
/**
|
|
422
|
+
* Deep clone for `applyDiffLocally`. Recurses into plain objects and
|
|
423
|
+
* arrays; preserves `Date` (cloned to avoid shared reference) and
|
|
424
|
+
* `ObjectId`-like values by reference (their internal Buffer state is
|
|
425
|
+
* immutable from our perspective). Other class instances pass through
|
|
426
|
+
* by reference. Avoids `structuredClone` because it throws on class
|
|
427
|
+
* instances like bson `ObjectId`.
|
|
428
|
+
*/
|
|
429
|
+
private static safeDeepClone;
|
|
406
430
|
/**
|
|
407
431
|
* Recursively walk `value` and ensure every plain object that appears
|
|
408
432
|
* as an element of an array carries an `_id`. Missing `_id`s are
|
|
@@ -41,6 +41,34 @@ export declare function hasDotNotationPaths(changes: Record<string, any>): boole
|
|
|
41
41
|
* "arr[A].sub[B].field" → ["arr", "[A]", "sub", "[B]", "field"]
|
|
42
42
|
*/
|
|
43
43
|
export declare function tokenizePath(path: string): string[];
|
|
44
|
+
/**
|
|
45
|
+
* Set a value at a path within a nested target. Supports three segment forms:
|
|
46
|
+
* - Numeric ("0", "1") → array index
|
|
47
|
+
* - Bracket ("[<_id>]") → array element matched by `_id` field
|
|
48
|
+
* - Plain ("field") → object key
|
|
49
|
+
*
|
|
50
|
+
* @returns true if the value was successfully set, false if path traversal failed.
|
|
51
|
+
*/
|
|
52
|
+
export declare function setByPath(target: any, path: string, value: any): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Delete the value at `path` within `target`. Sibling of `setByPath` —
|
|
55
|
+
* navigates the same tokenized path forms (numeric, bracket-by-_id, plain),
|
|
56
|
+
* but the final segment removes the property/element instead of setting it.
|
|
57
|
+
*
|
|
58
|
+
* Used by `save()`/`insert()`/`upsert()` to honor the convention that an
|
|
59
|
+
* `undefined` value in the caller's `update` means "delete this field".
|
|
60
|
+
*
|
|
61
|
+
* Behavior on the last segment:
|
|
62
|
+
* - plain key: `delete obj[key]`
|
|
63
|
+
* - numeric `"<idx>"` on array: `arr.splice(idx, 1)` (removes element)
|
|
64
|
+
* - bracket `"[<_id>]"` on array of `_id`-keyed objects: splice the
|
|
65
|
+
* matching element
|
|
66
|
+
*
|
|
67
|
+
* Returns `true` if a delete actually occurred. `false` if traversal failed
|
|
68
|
+
* (path leads through a missing/non-object value) or the target segment
|
|
69
|
+
* doesn't exist — caller can ignore.
|
|
70
|
+
*/
|
|
71
|
+
export declare function deleteByPath(target: any, path: string): boolean;
|
|
44
72
|
/**
|
|
45
73
|
* Smart-merge a single (path, value) entry into an accumulated dirty changes
|
|
46
74
|
* object. Resolves three relationships between the new path and existing keys:
|
|
@@ -49,6 +77,13 @@ export declare function tokenizePath(path: string): string[];
|
|
|
49
77
|
* new is "koraki.0.diag"): mutate the value inside the existing parent
|
|
50
78
|
* rather than adding a conflicting child path.
|
|
51
79
|
*
|
|
80
|
+
* SPECIAL CASES for terminal-bracket existing keys (whole-element ops):
|
|
81
|
+
* a) existing value is `undefined` (REMOVE marker) → drop new sub-field
|
|
82
|
+
* silently. Element is gone, sub-field write is moot.
|
|
83
|
+
* b) existing value is `[element]` (INSERT wrapper) → navigate INTO the
|
|
84
|
+
* wrapped element[0], not into the array itself. The pending insert
|
|
85
|
+
* absorbs the sub-field edit.
|
|
86
|
+
*
|
|
52
87
|
* 2. New path is an ANCESTOR of existing keys (e.g. existing has "koraki.0.diag",
|
|
53
88
|
* new is "koraki" with full array): remove the now-redundant descendants and
|
|
54
89
|
* set the parent path. The new full value supersedes any field-level deltas.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk `value` recursively without mutation. Return the dot/bracket
|
|
3
|
+
* notation paths of every property whose value is `undefined`.
|
|
4
|
+
*
|
|
5
|
+
* The original `value` (and all nested objects/arrays) is left intact —
|
|
6
|
+
* `undefined` values stay in place so that downstream code (computeDiff,
|
|
7
|
+
* dirty-change storage, REST upload via msgpackr) can carry them through
|
|
8
|
+
* to cry-db, which natively routes `key: undefined` → `$unset` server-side.
|
|
9
|
+
*/
|
|
10
|
+
export declare function collectUnsetPaths(value: any): string[];
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
2
|
+
* Pass-through: returns changes unchanged. Server-side cry-db v2.4.33+
|
|
3
|
+
* `_applyBracketProcessing` handles all bracket forms atomically:
|
|
4
|
+
* - terminal `arr[<id>]` with array value → idempotent `$concatArrays + $filter`
|
|
5
|
+
* - terminal `arr[<id>]` with `undefined` → `$pull`
|
|
6
|
+
* - terminal `arr[<id>]` with object → arrayFilter `$set`
|
|
7
|
+
* - sub-field `arr[<id>].field` → arrayFilter `$set` (position-independent)
|
|
8
|
+
*
|
|
9
|
+
* Signature kept (with `_entity` unused) for backward-compat with callers.
|
|
6
10
|
*/
|
|
7
|
-
export declare function translateBracketPathsToIndex(changes: Record<string, any>,
|
|
11
|
+
export declare function translateBracketPathsToIndex(changes: Record<string, any>, _entity: any): Record<string, any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cry-synced-db-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.161",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"test": "bun test test/*.test.ts test/restProxy/*.test.ts && vitest run",
|
|
24
24
|
"test:bun": "bun test test/*.test.ts test/restProxy/*.test.ts",
|
|
25
25
|
"test:node-only": "vitest run",
|
|
26
|
+
"test:real-mongo": "USE_REAL_MONGO=1 bun test test/bracketArrayPathsAcrossWriters.test.ts test/restProxy/*.test.ts",
|
|
26
27
|
"prepublishOnly": "bun run build"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
@@ -30,19 +31,18 @@
|
|
|
30
31
|
"bson": "^7.2.0",
|
|
31
32
|
"cry-ebus-proxy": "^1.0.3",
|
|
32
33
|
"dexie": "^4.4.2",
|
|
33
|
-
"esbuild": "^0.
|
|
34
|
+
"esbuild": "^0.28.0",
|
|
34
35
|
"fake-indexeddb": "^6.2.5",
|
|
35
36
|
"typescript": "^6",
|
|
36
|
-
"vitest": "^4.1.
|
|
37
|
+
"vitest": "^4.1.5"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"cry-
|
|
40
|
-
"
|
|
41
|
-
"msgpackr": "^1.11.9",
|
|
40
|
+
"cry-helpers": "^2.1.194",
|
|
41
|
+
"msgpackr": "^2.0.1",
|
|
42
42
|
"notepack": "^0.0.2",
|
|
43
43
|
"notepack.io": "^3.0.1",
|
|
44
44
|
"superjson": "^2.2.6",
|
|
45
|
-
"undici": "^
|
|
45
|
+
"undici": "^8.2.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"bson": "^6.0.0 || ^7.0.0",
|