cry-synced-db-client 0.1.183 → 0.1.184
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 +47 -0
- package/dist/index.js +82 -41
- package/dist/src/db/SyncedDb.d.ts +32 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 0.1.184 (2026-05-14)
|
|
4
|
+
|
|
5
|
+
### Fix: `applyDiffLocally` supports multi-segment plain prefix before bracket-by-id
|
|
6
|
+
|
|
7
|
+
The materialization fallback used to require the bracket-by-id to live
|
|
8
|
+
at token position [1] — anything deeper failed the
|
|
9
|
+
`secondToken.startsWith("[")` check and dropped with
|
|
10
|
+
`second segment is not a bracket-by-id`. Diff paths like
|
|
11
|
+
`outer.field[<id>]` or `a.b.c.arr[<id>].deep` fell through this gap;
|
|
12
|
+
the data was silently lost locally (visible drop unless the first
|
|
13
|
+
segment started with `_`).
|
|
14
|
+
|
|
15
|
+
Now any plain dot-notation prefix before a single bracket-by-id is
|
|
16
|
+
materialized:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
plain1.plain2…plainN[<id>][.subA.subB…] → materialize
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Shape | Result |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `outer.arr[<newId>] = <obj>` | `seed.outer = {arr: [<obj>]}` |
|
|
25
|
+
| `outer.arr[<newId>].field = v` | `seed.outer = {arr: [{_id: <newId>, field: v}]}` |
|
|
26
|
+
| `a.b.c.arr[<id>].seg1.seg2 = v` | `seed.a = {b: {c: {arr: [{_id, seg1: {seg2: v}}]}}}` |
|
|
27
|
+
| Matching `_id` exists, intermediate missing | Walk into element, create missing intermediates, set leaf. No duplicate element. |
|
|
28
|
+
| Existing intermediate is not a plain object (Date, primitive, array) | Drop visibly — refuse to overwrite. |
|
|
29
|
+
|
|
30
|
+
**Still drops**: multi-bracket paths (`outer[a].sub[b]`,
|
|
31
|
+
`outer.a.sub[i].nested[j]`) — these need shape knowledge the fallback
|
|
32
|
+
can't reconstruct; server applies the path and next sync hydrates the
|
|
33
|
+
canonical state.
|
|
34
|
+
|
|
35
|
+
**Backward-compat for `_`-mirrored roots**: paths under `_redundanca`,
|
|
36
|
+
`_dobavnica_saved`, etc. that extend beyond the originally-supported
|
|
37
|
+
single-segment-prefix shape (e.g. `_redundanca.terapije[<id>]`) still
|
|
38
|
+
drop silently. These fields are server-mirrored — local materialization
|
|
39
|
+
with partial data could diverge from the canonical state.
|
|
40
|
+
|
|
41
|
+
Tests:
|
|
42
|
+
- `test/applyDiffLocallyObiskShape.test.ts` — 5 new cases covering
|
|
43
|
+
multi-segment prefix shapes plus the `_`-prefix silent-drop contract.
|
|
44
|
+
- `test/applyDiffLocallyMaterialize.test.ts` — flipped the
|
|
45
|
+
`a.b[<id>]` case from "drops with diagnostic log" to
|
|
46
|
+
"materializes `{a: {b: [<el>]}}`".
|
|
47
|
+
|
|
48
|
+
Full suite: 767 bun + 18 vitest pass.
|
|
49
|
+
|
|
3
50
|
## 0.1.183 (2026-05-14)
|
|
4
51
|
|
|
5
52
|
### Fix: `applyDiffLocally` materializes deep sub-field paths under bracket-by-id
|
package/dist/index.js
CHANGED
|
@@ -6653,25 +6653,38 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6653
6653
|
}
|
|
6654
6654
|
/**
|
|
6655
6655
|
* Fallback for `setByPath` failures inside `applyDiffLocally`. Materializes
|
|
6656
|
-
*
|
|
6657
|
-
*
|
|
6658
|
-
* path reaches deeper than the element currently holds.
|
|
6656
|
+
* missing array containers AND missing intermediate plain objects so the
|
|
6657
|
+
* diff entry can land locally instead of being dropped.
|
|
6659
6658
|
*
|
|
6660
|
-
* Supported
|
|
6659
|
+
* Supported path shape (tokenizes to `[…plain prefix, [<id>], …plain suffix]`):
|
|
6661
6660
|
*
|
|
6662
|
-
*
|
|
6663
|
-
*
|
|
6664
|
-
*
|
|
6665
|
-
*
|
|
6666
|
-
* 5. Existing element with `_id` matches `<id>` but missing intermediate
|
|
6667
|
-
* → walk down the element, create missing intermediate plain objects,
|
|
6668
|
-
* set the leaf. No duplicate element pushed.
|
|
6661
|
+
* • Single-segment prefix:
|
|
6662
|
+
* - `polje[<id>] = <obj>` → seed.polje = [<obj>]
|
|
6663
|
+
* - `polje[<id>].field = <v>` → seed.polje = [{_id, field: <v>}]
|
|
6664
|
+
* - `polje[<id>].a.b.c = <v>` → seed.polje = [{_id, a: {b: {c: <v>}}}]
|
|
6669
6665
|
*
|
|
6670
|
-
*
|
|
6671
|
-
*
|
|
6672
|
-
*
|
|
6673
|
-
*
|
|
6674
|
-
*
|
|
6666
|
+
* • Multi-segment plain prefix:
|
|
6667
|
+
* - `outer.polje[<id>] = <obj>` → seed.outer = {polje: [<obj>]}
|
|
6668
|
+
* - `outer.inner.polje[<id>].field = <v>` → seed.outer = {inner: {polje: [{_id, field: <v>}]}}
|
|
6669
|
+
*
|
|
6670
|
+
* • Existing matching `_id` on the target array: walk into the element
|
|
6671
|
+
* and create missing intermediates on the way down; set the leaf
|
|
6672
|
+
* without pushing a duplicate element.
|
|
6673
|
+
*
|
|
6674
|
+
* Dropped:
|
|
6675
|
+
* • Multi-bracket paths (e.g. `polje[a].sub[b]`, `outer[a].sub.inner[b]`)
|
|
6676
|
+
* — require shape knowledge the fallback can't reconstruct. Server
|
|
6677
|
+
* applies the path; next sync hydrates the canonical state locally.
|
|
6678
|
+
* • Existing intermediate that is a non-plain value (Date, primitive,
|
|
6679
|
+
* array where an object is expected).
|
|
6680
|
+
*
|
|
6681
|
+
* `_`-prefixed first segment (e.g. `_redundanca…`): for shapes that
|
|
6682
|
+
* extend BEYOND the originally-supported single-segment prefix, the
|
|
6683
|
+
* drop is **silent** and unconditional — these fields are server-
|
|
6684
|
+
* mirrored and local materialization could create state that diverges
|
|
6685
|
+
* from the canonical server form. Simple single-segment shapes
|
|
6686
|
+
* (`_field[<id>]`, `_field[<id>].sub`, `_field[<id>].sub.deep…`) still
|
|
6687
|
+
* materialize as before.
|
|
6675
6688
|
*
|
|
6676
6689
|
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
6677
6690
|
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|
|
@@ -6697,28 +6710,59 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6697
6710
|
drop("first segment is not a plain field");
|
|
6698
6711
|
return;
|
|
6699
6712
|
}
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6713
|
+
let bracketIdx = -1;
|
|
6714
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
6715
|
+
if (tokens[i].startsWith("[")) {
|
|
6716
|
+
bracketIdx = i;
|
|
6717
|
+
break;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
if (bracketIdx < 0) {
|
|
6721
|
+
drop("no bracket segment found");
|
|
6703
6722
|
return;
|
|
6704
6723
|
}
|
|
6705
|
-
for (let i =
|
|
6724
|
+
for (let i = bracketIdx + 1; i < tokens.length; i++) {
|
|
6706
6725
|
if (tokens[i].startsWith("[")) {
|
|
6707
6726
|
drop("nested bracket path");
|
|
6708
6727
|
return;
|
|
6709
6728
|
}
|
|
6710
6729
|
}
|
|
6711
|
-
|
|
6730
|
+
if (dropSilently && bracketIdx > 1) {
|
|
6731
|
+
return;
|
|
6732
|
+
}
|
|
6733
|
+
const prefixTokens = tokens.slice(0, bracketIdx);
|
|
6734
|
+
const bracketToken = tokens[bracketIdx];
|
|
6735
|
+
const suffixTokens = tokens.slice(bracketIdx + 1);
|
|
6736
|
+
const bracketId = bracketToken.slice(1, -1);
|
|
6712
6737
|
if (bracketId.length === 0) {
|
|
6713
6738
|
drop("empty bracket id");
|
|
6714
6739
|
return;
|
|
6715
6740
|
}
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6741
|
+
let parent = seed;
|
|
6742
|
+
for (let i = 0; i < prefixTokens.length - 1; i++) {
|
|
6743
|
+
const seg = prefixTokens[i];
|
|
6744
|
+
const cur = parent[seg];
|
|
6745
|
+
if (cur === void 0 || cur === null) {
|
|
6746
|
+
parent[seg] = {};
|
|
6747
|
+
} else if (typeof cur !== "object" || Array.isArray(cur) || cur instanceof Date) {
|
|
6748
|
+
drop(
|
|
6749
|
+
`existing intermediate "${prefixTokens.slice(0, i + 1).join(".")}" is not a plain object`
|
|
6750
|
+
);
|
|
6751
|
+
return;
|
|
6752
|
+
}
|
|
6753
|
+
parent = parent[seg];
|
|
6754
|
+
}
|
|
6755
|
+
const lastPrefixSeg = prefixTokens[prefixTokens.length - 1];
|
|
6756
|
+
let arr = parent[lastPrefixSeg];
|
|
6757
|
+
if (arr != null && !Array.isArray(arr)) {
|
|
6758
|
+
drop(`existing "${prefixTokens.join(".")}" is not an array`);
|
|
6719
6759
|
return;
|
|
6720
6760
|
}
|
|
6721
|
-
if (
|
|
6761
|
+
if (arr == null) {
|
|
6762
|
+
arr = [];
|
|
6763
|
+
parent[lastPrefixSeg] = arr;
|
|
6764
|
+
}
|
|
6765
|
+
if (suffixTokens.length === 0) {
|
|
6722
6766
|
let element = value;
|
|
6723
6767
|
if (Array.isArray(value) && value.length === 1 && value[0] != null && typeof value[0] === "object") {
|
|
6724
6768
|
element = value[0];
|
|
@@ -6730,14 +6774,16 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6730
6774
|
if (element._id == null) {
|
|
6731
6775
|
element._id = bracketId;
|
|
6732
6776
|
}
|
|
6733
|
-
|
|
6734
|
-
|
|
6777
|
+
const replaceIdx = arr.findIndex(
|
|
6778
|
+
(it) => it != null && typeof it === "object" && String(it._id) === bracketId
|
|
6779
|
+
);
|
|
6780
|
+
if (replaceIdx >= 0) {
|
|
6781
|
+
arr[replaceIdx] = element;
|
|
6735
6782
|
} else {
|
|
6736
|
-
|
|
6783
|
+
arr.push(element);
|
|
6737
6784
|
}
|
|
6738
6785
|
return;
|
|
6739
6786
|
}
|
|
6740
|
-
const subFieldPath = tokens.slice(2);
|
|
6741
6787
|
const buildNestedSubTree = (segs, leaf) => {
|
|
6742
6788
|
let acc = leaf;
|
|
6743
6789
|
for (let i = segs.length - 1; i >= 0; i--) {
|
|
@@ -6745,28 +6791,23 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6745
6791
|
}
|
|
6746
6792
|
return acc;
|
|
6747
6793
|
};
|
|
6748
|
-
|
|
6749
|
-
const subTree = buildNestedSubTree(subFieldPath, value);
|
|
6750
|
-
seed[firstToken] = [__spreadValues({ _id: bracketId }, subTree)];
|
|
6751
|
-
return;
|
|
6752
|
-
}
|
|
6753
|
-
const existingIdx = existing.findIndex(
|
|
6794
|
+
const existingIdx = arr.findIndex(
|
|
6754
6795
|
(it) => it != null && typeof it === "object" && String(it._id) === bracketId
|
|
6755
6796
|
);
|
|
6756
6797
|
if (existingIdx >= 0) {
|
|
6757
|
-
let cur =
|
|
6758
|
-
for (let i = 0; i <
|
|
6759
|
-
const seg =
|
|
6798
|
+
let cur = arr[existingIdx];
|
|
6799
|
+
for (let i = 0; i < suffixTokens.length - 1; i++) {
|
|
6800
|
+
const seg = suffixTokens[i];
|
|
6760
6801
|
const next = cur[seg];
|
|
6761
6802
|
if (next == null || typeof next !== "object" || Array.isArray(next)) {
|
|
6762
6803
|
cur[seg] = {};
|
|
6763
6804
|
}
|
|
6764
6805
|
cur = cur[seg];
|
|
6765
6806
|
}
|
|
6766
|
-
cur[
|
|
6807
|
+
cur[suffixTokens[suffixTokens.length - 1]] = value;
|
|
6767
6808
|
} else {
|
|
6768
|
-
const subTree = buildNestedSubTree(
|
|
6769
|
-
|
|
6809
|
+
const subTree = buildNestedSubTree(suffixTokens, value);
|
|
6810
|
+
arr.push(__spreadValues({ _id: bracketId }, subTree));
|
|
6770
6811
|
}
|
|
6771
6812
|
}
|
|
6772
6813
|
/**
|
|
@@ -521,25 +521,38 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
521
521
|
private static applyDiffLocally;
|
|
522
522
|
/**
|
|
523
523
|
* Fallback for `setByPath` failures inside `applyDiffLocally`. Materializes
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
* intermediates
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
524
|
+
* missing array containers AND missing intermediate plain objects so the
|
|
525
|
+
* diff entry can land locally instead of being dropped.
|
|
526
|
+
*
|
|
527
|
+
* Supported path shape (tokenizes to `[…plain prefix, [<id>], …plain suffix]`):
|
|
528
|
+
*
|
|
529
|
+
* • Single-segment prefix:
|
|
530
|
+
* - `polje[<id>] = <obj>` → seed.polje = [<obj>]
|
|
531
|
+
* - `polje[<id>].field = <v>` → seed.polje = [{_id, field: <v>}]
|
|
532
|
+
* - `polje[<id>].a.b.c = <v>` → seed.polje = [{_id, a: {b: {c: <v>}}}]
|
|
533
|
+
*
|
|
534
|
+
* • Multi-segment plain prefix:
|
|
535
|
+
* - `outer.polje[<id>] = <obj>` → seed.outer = {polje: [<obj>]}
|
|
536
|
+
* - `outer.inner.polje[<id>].field = <v>` → seed.outer = {inner: {polje: [{_id, field: <v>}]}}
|
|
537
|
+
*
|
|
538
|
+
* • Existing matching `_id` on the target array: walk into the element
|
|
539
|
+
* and create missing intermediates on the way down; set the leaf
|
|
540
|
+
* without pushing a duplicate element.
|
|
541
|
+
*
|
|
542
|
+
* Dropped:
|
|
543
|
+
* • Multi-bracket paths (e.g. `polje[a].sub[b]`, `outer[a].sub.inner[b]`)
|
|
544
|
+
* — require shape knowledge the fallback can't reconstruct. Server
|
|
545
|
+
* applies the path; next sync hydrates the canonical state locally.
|
|
546
|
+
* • Existing intermediate that is a non-plain value (Date, primitive,
|
|
547
|
+
* array where an object is expected).
|
|
548
|
+
*
|
|
549
|
+
* `_`-prefixed first segment (e.g. `_redundanca…`): for shapes that
|
|
550
|
+
* extend BEYOND the originally-supported single-segment prefix, the
|
|
551
|
+
* drop is **silent** and unconditional — these fields are server-
|
|
552
|
+
* mirrored and local materialization could create state that diverges
|
|
553
|
+
* from the canonical server form. Simple single-segment shapes
|
|
554
|
+
* (`_field[<id>]`, `_field[<id>].sub`, `_field[<id>].sub.deep…`) still
|
|
555
|
+
* materialize as before.
|
|
543
556
|
*
|
|
544
557
|
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
545
558
|
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|