cry-synced-db-client 0.1.182 → 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 +90 -0
- package/dist/index.js +103 -30
- package/dist/src/db/SyncedDb.d.ts +32 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
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
|
+
|
|
50
|
+
## 0.1.183 (2026-05-14)
|
|
51
|
+
|
|
52
|
+
### Fix: `applyDiffLocally` materializes deep sub-field paths under bracket-by-id
|
|
53
|
+
|
|
54
|
+
`materializeBracketPath` (the fallback when `setByPath` can't reach the
|
|
55
|
+
target) capped at `tokens.length ≤ 3`. Any deeper path was dropped with
|
|
56
|
+
a `dropping bracket-path diff entry (unsupported token count N)` log
|
|
57
|
+
(silent when first segment started with `_`, visible otherwise).
|
|
58
|
+
|
|
59
|
+
Real-world obisk records carry nested objects on every `zaracunaj`
|
|
60
|
+
element — `karence.karence.meso`, `uporabljeneSerijskeNaNapravi.serijske`,
|
|
61
|
+
etc. Saves like
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
syncedDb.save("obiski", id, {
|
|
65
|
+
"zaracunaj[<newId>].karence.karence.meso": 5,
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
dropped the data even though the intent is unambiguous and the data
|
|
70
|
+
shape is valid.
|
|
71
|
+
|
|
72
|
+
**New behavior** in `materializeBracketPath`:
|
|
73
|
+
|
|
74
|
+
| Shape | Result |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `polje[<id>] = <obj>` | `seed.polje = [<obj>]` (unchanged) |
|
|
77
|
+
| `polje[<id>].field = v` | `seed.polje = [{_id: <id>, field: v}]` (unchanged) |
|
|
78
|
+
| `polje[<id>].a.b.c = v` (new) | `seed.polje = [{_id: <id>, a: {b: {c: v}}}]` |
|
|
79
|
+
| Matching `_id` exists, intermediate missing (new) | Walk into the element, create missing intermediates on the way down, set the leaf. No duplicate element. |
|
|
80
|
+
|
|
81
|
+
Multi-bracket paths (e.g. `polje[a].sub[b]`, `polje[a].b.sub[c]`) still
|
|
82
|
+
drop — they require shape knowledge the fallback can't reconstruct;
|
|
83
|
+
server applies them and the next sync hydrates local.
|
|
84
|
+
|
|
85
|
+
Tests: `test/applyDiffLocallyObiskShape.test.ts` (18 cases) — uses a
|
|
86
|
+
trimmed real-shape obisk fixture with the two _id-arrays (top-level
|
|
87
|
+
`zaracunaj` and `_dobavnica_saved.postavke` nested under a plain
|
|
88
|
+
container), all the composition-change scenarios, the deep-bracket-path
|
|
89
|
+
edge cases that used to drop, and a "no drop-log during typical
|
|
90
|
+
roundtrip" guard. 3 cases were red before the fix; all pass after.
|
|
91
|
+
Full suite: 762 bun + 18 vitest pass.
|
|
92
|
+
|
|
3
93
|
## 0.1.182 (2026-05-14)
|
|
4
94
|
|
|
5
95
|
### `networkError` utility — quiet log noise during sustained offline
|
package/dist/index.js
CHANGED
|
@@ -6653,19 +6653,38 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6653
6653
|
}
|
|
6654
6654
|
/**
|
|
6655
6655
|
* Fallback for `setByPath` failures inside `applyDiffLocally`. Materializes
|
|
6656
|
-
*
|
|
6657
|
-
*
|
|
6658
|
-
* properties polluting Dexie/in-mem):
|
|
6656
|
+
* missing array containers AND missing intermediate plain objects so the
|
|
6657
|
+
* diff entry can land locally instead of being dropped.
|
|
6659
6658
|
*
|
|
6660
|
-
*
|
|
6661
|
-
* 2. `polje[<id>].<field> = <v>` → seed.polje = [{_id: <id>, <field>: <v>}]
|
|
6659
|
+
* Supported path shape (tokenizes to `[…plain prefix, [<id>], …plain suffix]`):
|
|
6662
6660
|
*
|
|
6663
|
-
*
|
|
6664
|
-
*
|
|
6665
|
-
*
|
|
6666
|
-
*
|
|
6667
|
-
*
|
|
6668
|
-
*
|
|
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>}}}]
|
|
6665
|
+
*
|
|
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.
|
|
6669
6688
|
*
|
|
6670
6689
|
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
6671
6690
|
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|
|
@@ -6683,7 +6702,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6683
6702
|
{ collection, _id: String(id), path, value }
|
|
6684
6703
|
);
|
|
6685
6704
|
};
|
|
6686
|
-
if (tokens.length < 2
|
|
6705
|
+
if (tokens.length < 2) {
|
|
6687
6706
|
drop(`unsupported token count ${tokens.length}`);
|
|
6688
6707
|
return;
|
|
6689
6708
|
}
|
|
@@ -6691,26 +6710,59 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6691
6710
|
drop("first segment is not a plain field");
|
|
6692
6711
|
return;
|
|
6693
6712
|
}
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
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");
|
|
6697
6722
|
return;
|
|
6698
6723
|
}
|
|
6699
|
-
|
|
6700
|
-
|
|
6724
|
+
for (let i = bracketIdx + 1; i < tokens.length; i++) {
|
|
6725
|
+
if (tokens[i].startsWith("[")) {
|
|
6726
|
+
drop("nested bracket path");
|
|
6727
|
+
return;
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6730
|
+
if (dropSilently && bracketIdx > 1) {
|
|
6701
6731
|
return;
|
|
6702
6732
|
}
|
|
6703
|
-
const
|
|
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);
|
|
6704
6737
|
if (bracketId.length === 0) {
|
|
6705
6738
|
drop("empty bracket id");
|
|
6706
6739
|
return;
|
|
6707
6740
|
}
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
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`);
|
|
6711
6759
|
return;
|
|
6712
6760
|
}
|
|
6713
|
-
if (
|
|
6761
|
+
if (arr == null) {
|
|
6762
|
+
arr = [];
|
|
6763
|
+
parent[lastPrefixSeg] = arr;
|
|
6764
|
+
}
|
|
6765
|
+
if (suffixTokens.length === 0) {
|
|
6714
6766
|
let element = value;
|
|
6715
6767
|
if (Array.isArray(value) && value.length === 1 && value[0] != null && typeof value[0] === "object") {
|
|
6716
6768
|
element = value[0];
|
|
@@ -6722,19 +6774,40 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6722
6774
|
if (element._id == null) {
|
|
6723
6775
|
element._id = bracketId;
|
|
6724
6776
|
}
|
|
6725
|
-
|
|
6726
|
-
|
|
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;
|
|
6727
6782
|
} else {
|
|
6728
|
-
|
|
6783
|
+
arr.push(element);
|
|
6729
6784
|
}
|
|
6730
6785
|
return;
|
|
6731
6786
|
}
|
|
6732
|
-
const
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6787
|
+
const buildNestedSubTree = (segs, leaf) => {
|
|
6788
|
+
let acc = leaf;
|
|
6789
|
+
for (let i = segs.length - 1; i >= 0; i--) {
|
|
6790
|
+
acc = { [segs[i]]: acc };
|
|
6791
|
+
}
|
|
6792
|
+
return acc;
|
|
6793
|
+
};
|
|
6794
|
+
const existingIdx = arr.findIndex(
|
|
6795
|
+
(it) => it != null && typeof it === "object" && String(it._id) === bracketId
|
|
6796
|
+
);
|
|
6797
|
+
if (existingIdx >= 0) {
|
|
6798
|
+
let cur = arr[existingIdx];
|
|
6799
|
+
for (let i = 0; i < suffixTokens.length - 1; i++) {
|
|
6800
|
+
const seg = suffixTokens[i];
|
|
6801
|
+
const next = cur[seg];
|
|
6802
|
+
if (next == null || typeof next !== "object" || Array.isArray(next)) {
|
|
6803
|
+
cur[seg] = {};
|
|
6804
|
+
}
|
|
6805
|
+
cur = cur[seg];
|
|
6806
|
+
}
|
|
6807
|
+
cur[suffixTokens[suffixTokens.length - 1]] = value;
|
|
6736
6808
|
} else {
|
|
6737
|
-
|
|
6809
|
+
const subTree = buildNestedSubTree(suffixTokens, value);
|
|
6810
|
+
arr.push(__spreadValues({ _id: bracketId }, subTree));
|
|
6738
6811
|
}
|
|
6739
6812
|
}
|
|
6740
6813
|
/**
|
|
@@ -521,19 +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
|
-
*
|
|
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.
|
|
537
556
|
*
|
|
538
557
|
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
539
558
|
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|