cry-synced-db-client 0.1.181 → 0.1.183
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 +106 -0
- package/dist/index.js +76 -34
- package/dist/src/db/SyncedDb.d.ts +19 -13
- package/dist/src/utils/networkError.d.ts +28 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,111 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 0.1.183 (2026-05-14)
|
|
4
|
+
|
|
5
|
+
### Fix: `applyDiffLocally` materializes deep sub-field paths under bracket-by-id
|
|
6
|
+
|
|
7
|
+
`materializeBracketPath` (the fallback when `setByPath` can't reach the
|
|
8
|
+
target) capped at `tokens.length ≤ 3`. Any deeper path was dropped with
|
|
9
|
+
a `dropping bracket-path diff entry (unsupported token count N)` log
|
|
10
|
+
(silent when first segment started with `_`, visible otherwise).
|
|
11
|
+
|
|
12
|
+
Real-world obisk records carry nested objects on every `zaracunaj`
|
|
13
|
+
element — `karence.karence.meso`, `uporabljeneSerijskeNaNapravi.serijske`,
|
|
14
|
+
etc. Saves like
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
syncedDb.save("obiski", id, {
|
|
18
|
+
"zaracunaj[<newId>].karence.karence.meso": 5,
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
dropped the data even though the intent is unambiguous and the data
|
|
23
|
+
shape is valid.
|
|
24
|
+
|
|
25
|
+
**New behavior** in `materializeBracketPath`:
|
|
26
|
+
|
|
27
|
+
| Shape | Result |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `polje[<id>] = <obj>` | `seed.polje = [<obj>]` (unchanged) |
|
|
30
|
+
| `polje[<id>].field = v` | `seed.polje = [{_id: <id>, field: v}]` (unchanged) |
|
|
31
|
+
| `polje[<id>].a.b.c = v` (new) | `seed.polje = [{_id: <id>, a: {b: {c: v}}}]` |
|
|
32
|
+
| Matching `_id` exists, intermediate missing (new) | Walk into the element, create missing intermediates on the way down, set the leaf. No duplicate element. |
|
|
33
|
+
|
|
34
|
+
Multi-bracket paths (e.g. `polje[a].sub[b]`, `polje[a].b.sub[c]`) still
|
|
35
|
+
drop — they require shape knowledge the fallback can't reconstruct;
|
|
36
|
+
server applies them and the next sync hydrates local.
|
|
37
|
+
|
|
38
|
+
Tests: `test/applyDiffLocallyObiskShape.test.ts` (18 cases) — uses a
|
|
39
|
+
trimmed real-shape obisk fixture with the two _id-arrays (top-level
|
|
40
|
+
`zaracunaj` and `_dobavnica_saved.postavke` nested under a plain
|
|
41
|
+
container), all the composition-change scenarios, the deep-bracket-path
|
|
42
|
+
edge cases that used to drop, and a "no drop-log during typical
|
|
43
|
+
roundtrip" guard. 3 cases were red before the fix; all pass after.
|
|
44
|
+
Full suite: 762 bun + 18 vitest pass.
|
|
45
|
+
|
|
46
|
+
## 0.1.182 (2026-05-14)
|
|
47
|
+
|
|
48
|
+
### `networkError` utility — quiet log noise during sustained offline
|
|
49
|
+
|
|
50
|
+
New `src/utils/networkError.ts`:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
export function networkError(message: string, ...rest: unknown[]): void {
|
|
54
|
+
const isOnline =
|
|
55
|
+
typeof navigator === "undefined" || navigator.onLine !== false;
|
|
56
|
+
if (isOnline) {
|
|
57
|
+
console.error(message, ...rest);
|
|
58
|
+
} else {
|
|
59
|
+
console.info(message, ...rest);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Applied to 15 sites where the failure mode reduces to "REST/WS
|
|
65
|
+
unreachable because the network is down":
|
|
66
|
+
|
|
67
|
+
| Module | Site |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `ConnectionManager` | Failed to go online after forceOffline release |
|
|
70
|
+
| `ConnectionManager` | Auto-sync failed |
|
|
71
|
+
| `ConnectionManager` | Reconnect tryGoOnline failed |
|
|
72
|
+
| `PendingChangesManager` | REST upload failed |
|
|
73
|
+
| `WakeSyncManager` | Wake sync (`<trigger>`) failed |
|
|
74
|
+
| `SyncEngine` | uploadDirtyItems failed (download succeeded) |
|
|
75
|
+
| `SyncEngine` | Sync failed |
|
|
76
|
+
| `SyncedDb` | tryGoOnline on becameLeader failed |
|
|
77
|
+
| `SyncedDb` | referToServer failed for `<collection>` |
|
|
78
|
+
| `SyncedDb` | refreshInBackground failed for `<collection>` |
|
|
79
|
+
| `SyncedDb` | Failed to hard delete `<id>` |
|
|
80
|
+
| `SyncedDb` | `[evict]` server-assisted pass failed |
|
|
81
|
+
| `SyncedDb` | `[evict]` server-assisted batch failed |
|
|
82
|
+
| `Ebus2ProxyNotifier` | WebSocket error: `<event.type>` |
|
|
83
|
+
| `Ebus2ProxyNotifier` | Reconnection failed |
|
|
84
|
+
|
|
85
|
+
Effect: a 5-minute offline period that previously produced ~25–30
|
|
86
|
+
`console.error` lines (auto-sync ticks every 60s, reconnect probes
|
|
87
|
+
every 60s, WS reconnect backoff, dirty-flush retries) now produces 0.
|
|
88
|
+
The events are still logged at `console.info` so an operator inspecting
|
|
89
|
+
them sees the full timeline — they just don't surface as "errors" in
|
|
90
|
+
sentry/log dashboards.
|
|
91
|
+
|
|
92
|
+
What's NOT routed through `networkError` (intentional — these are real
|
|
93
|
+
bugs regardless of network state):
|
|
94
|
+
- Caller bugs (falsy `_id`, missing `_id`, id mismatch, no id provided)
|
|
95
|
+
- Dexie failures (`bulkPut failed`, `Failed to write to Dexie`, etc.)
|
|
96
|
+
- Consumer-supplied callback throws (`onSyncEnd callback failed`, etc.)
|
|
97
|
+
- WS protocol errors (malformed msgpack, server-side error frames)
|
|
98
|
+
- Server-side per-item rejection (`Sync upload error [coll] _id=...`)
|
|
99
|
+
- `DB-WARNING` per-item warnings
|
|
100
|
+
- BroadcastChannel failures (cross-tab, local, not network)
|
|
101
|
+
|
|
102
|
+
Caveat: `navigator.onLine` only reports the OS network interface, not
|
|
103
|
+
server reachability. WiFi-on-but-server-unreachable still logs at error
|
|
104
|
+
severity, which is the correct behavior — that failure is unexpected
|
|
105
|
+
from the browser's point of view.
|
|
106
|
+
|
|
107
|
+
Bundle: 356.0 KB → 356.2 KB. Tests: 736 bun + 18 vitest still pass.
|
|
108
|
+
|
|
3
109
|
## 0.1.181 (2026-05-14)
|
|
4
110
|
|
|
5
111
|
### `measureEndToEndRtt` now rides `callWorker` over the WebSocket
|
package/dist/index.js
CHANGED
|
@@ -732,6 +732,16 @@ function childPathForArrayElement(prefix, element, index) {
|
|
|
732
732
|
return prefix ? `${prefix}.${index}` : String(index);
|
|
733
733
|
}
|
|
734
734
|
|
|
735
|
+
// src/utils/networkError.ts
|
|
736
|
+
function networkError(message, ...rest) {
|
|
737
|
+
const isOnline = typeof navigator === "undefined" || navigator.onLine !== false;
|
|
738
|
+
if (isOnline) {
|
|
739
|
+
console.error(message, ...rest);
|
|
740
|
+
} else {
|
|
741
|
+
console.info(message, ...rest);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
735
745
|
// src/db/managers/InMemManager.ts
|
|
736
746
|
var InMemManager = class {
|
|
737
747
|
constructor(config) {
|
|
@@ -1422,7 +1432,7 @@ var ConnectionManager = class {
|
|
|
1422
1432
|
} else {
|
|
1423
1433
|
this.deps.tryBecomeLeader();
|
|
1424
1434
|
this.tryGoOnline().catch((err) => {
|
|
1425
|
-
|
|
1435
|
+
networkError(`[Connection] Failed to go online after forceOffline release: ${err}`, err);
|
|
1426
1436
|
});
|
|
1427
1437
|
}
|
|
1428
1438
|
}
|
|
@@ -1480,7 +1490,7 @@ var ConnectionManager = class {
|
|
|
1480
1490
|
this.autoSyncTimer = setInterval(() => {
|
|
1481
1491
|
if (this.forcedOffline || !this.online) return;
|
|
1482
1492
|
this.deps.sync(`interval ${intervalMs}ms`).catch((err) => {
|
|
1483
|
-
|
|
1493
|
+
networkError(`[Connection] Auto-sync failed: ${err}`, err);
|
|
1484
1494
|
});
|
|
1485
1495
|
}, intervalMs);
|
|
1486
1496
|
}
|
|
@@ -1489,7 +1499,7 @@ var ConnectionManager = class {
|
|
|
1489
1499
|
this.reconnectTimer = setInterval(() => {
|
|
1490
1500
|
if (this.forcedOffline || this.online || this.tryGoOnlineInFlight) return;
|
|
1491
1501
|
this.tryGoOnline().catch((err) => {
|
|
1492
|
-
|
|
1502
|
+
networkError(`[Connection] Reconnect tryGoOnline failed: ${err}`, err);
|
|
1493
1503
|
});
|
|
1494
1504
|
}, retryMs);
|
|
1495
1505
|
}
|
|
@@ -2827,7 +2837,7 @@ var PendingChangesManager = class {
|
|
|
2827
2837
|
try {
|
|
2828
2838
|
await this.deps.uploadDirtyItems();
|
|
2829
2839
|
} catch (err) {
|
|
2830
|
-
|
|
2840
|
+
networkError(`[PendingChanges] REST upload failed: ${err}`, err);
|
|
2831
2841
|
} finally {
|
|
2832
2842
|
this.isUploadingToRest = false;
|
|
2833
2843
|
resolveUpload();
|
|
@@ -3209,7 +3219,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3209
3219
|
}
|
|
3210
3220
|
}
|
|
3211
3221
|
} catch (err) {
|
|
3212
|
-
|
|
3222
|
+
networkError(
|
|
3213
3223
|
"[SyncEngine] uploadDirtyItems failed (download succeeded, staying online):",
|
|
3214
3224
|
err
|
|
3215
3225
|
);
|
|
@@ -3236,7 +3246,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3236
3246
|
});
|
|
3237
3247
|
} catch (err) {
|
|
3238
3248
|
const reason = err instanceof Error ? err.message : String(err);
|
|
3239
|
-
|
|
3249
|
+
networkError(`[SyncEngine] Sync failed: ${err}`, err);
|
|
3240
3250
|
this.deps.onSyncFailed(`Sync failed: ${reason}`);
|
|
3241
3251
|
this.callOnSyncEnd({
|
|
3242
3252
|
durationMs: Date.now() - startTime,
|
|
@@ -4291,7 +4301,7 @@ var WakeSyncManager = class {
|
|
|
4291
4301
|
}
|
|
4292
4302
|
}
|
|
4293
4303
|
this.deps.sync(`wake-sync:${trigger}`).catch((err) => {
|
|
4294
|
-
|
|
4304
|
+
networkError(`[WakeSync] Wake sync (${trigger}) failed:`, err);
|
|
4295
4305
|
});
|
|
4296
4306
|
}, this.debounceMs);
|
|
4297
4307
|
}
|
|
@@ -4447,7 +4457,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4447
4457
|
onBecameLeader: () => {
|
|
4448
4458
|
if (this.initialized && !this.connectionManager.isOnline() && !this.connectionManager.isForcedOffline()) {
|
|
4449
4459
|
this.connectionManager.tryGoOnline().catch((err) => {
|
|
4450
|
-
|
|
4460
|
+
networkError(`[SyncedDb] tryGoOnline on becameLeader failed: ${err}`, err);
|
|
4451
4461
|
});
|
|
4452
4462
|
}
|
|
4453
4463
|
if (config.onBecameLeader) {
|
|
@@ -5246,7 +5256,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5246
5256
|
await this.syncEngine.processCollectionServerData(collection, serverData, { source: "refresh" });
|
|
5247
5257
|
}
|
|
5248
5258
|
}).catch((err) => {
|
|
5249
|
-
|
|
5259
|
+
networkError(`[SyncedDb] referToServer failed for ${collection}:`, err);
|
|
5250
5260
|
});
|
|
5251
5261
|
}
|
|
5252
5262
|
/**
|
|
@@ -5267,7 +5277,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5267
5277
|
if (!serverItems || serverItems.length === 0) return;
|
|
5268
5278
|
await this.syncEngine.processCollectionServerData(collection, serverItems, { source: "incremental" });
|
|
5269
5279
|
}).catch((err) => {
|
|
5270
|
-
|
|
5280
|
+
networkError(`[SyncedDb] refreshInBackground failed for ${collection}:`, err);
|
|
5271
5281
|
});
|
|
5272
5282
|
}
|
|
5273
5283
|
async ensureItemsAreLoaded(collection, ids, withDeleted) {
|
|
@@ -5532,7 +5542,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5532
5542
|
this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete", { source: "incremental" });
|
|
5533
5543
|
results.push(true);
|
|
5534
5544
|
} catch (err) {
|
|
5535
|
-
|
|
5545
|
+
networkError(`[SyncedDb] Failed to hard delete ${String(item.id)}:`, err);
|
|
5536
5546
|
results.push(false);
|
|
5537
5547
|
}
|
|
5538
5548
|
}
|
|
@@ -5913,7 +5923,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5913
5923
|
for (const id of serverExits) evictIds.push(id);
|
|
5914
5924
|
serverEvictedCount = serverExits.length;
|
|
5915
5925
|
} catch (err) {
|
|
5916
|
-
|
|
5926
|
+
networkError(
|
|
5917
5927
|
`[SyncedDb] [evict] server-assisted pass failed for ${collection} (proceeding with local-only):`,
|
|
5918
5928
|
err
|
|
5919
5929
|
);
|
|
@@ -6030,7 +6040,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6030
6040
|
}
|
|
6031
6041
|
} catch (err) {
|
|
6032
6042
|
serverFailed = true;
|
|
6033
|
-
|
|
6043
|
+
networkError(
|
|
6034
6044
|
"[SyncedDb] [evict] server-assisted batch failed (proceeding with local-only):",
|
|
6035
6045
|
err
|
|
6036
6046
|
);
|
|
@@ -6643,19 +6653,25 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6643
6653
|
}
|
|
6644
6654
|
/**
|
|
6645
6655
|
* Fallback for `setByPath` failures inside `applyDiffLocally`. Materializes
|
|
6646
|
-
* the parent array when it's missing from `seed
|
|
6647
|
-
*
|
|
6648
|
-
*
|
|
6656
|
+
* the parent array when it's missing from `seed`, OR fills in missing
|
|
6657
|
+
* intermediate objects under an existing array element when the diff
|
|
6658
|
+
* path reaches deeper than the element currently holds.
|
|
6649
6659
|
*
|
|
6650
|
-
*
|
|
6651
|
-
* 2. `polje[<id>].<field> = <v>` → seed.polje = [{_id: <id>, <field>: <v>}]
|
|
6660
|
+
* Supported shapes (path tokenizes to `[firstField, [<id>], …rest]`):
|
|
6652
6661
|
*
|
|
6653
|
-
*
|
|
6654
|
-
*
|
|
6655
|
-
*
|
|
6656
|
-
*
|
|
6657
|
-
*
|
|
6658
|
-
*
|
|
6662
|
+
* 1. `polje[<id>] = <obj>` → seed.polje = [<obj>]
|
|
6663
|
+
* 2. `polje[<id>].field = <v>` → seed.polje = [{_id: <id>, field: <v>}]
|
|
6664
|
+
* 3. `polje[<id>].a.b = <v>` → seed.polje = [{_id: <id>, a: {b: <v>}}]
|
|
6665
|
+
* 4. `polje[<id>].a.b.c.d = <v>` → seed.polje = [{_id: <id>, a: {b: {c: {d: <v>}}}}]
|
|
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.
|
|
6669
|
+
*
|
|
6670
|
+
* Dropped (multi-bracket / nested-via-dots-before-bracket / non-plain
|
|
6671
|
+
* intermediates) — materializing those locally would risk corrupting
|
|
6672
|
+
* unrelated invariants on the nested objects. Dirty-change still
|
|
6673
|
+
* carries the path, so the server applies it; next sync brings the
|
|
6674
|
+
* canonical state back to local.
|
|
6659
6675
|
*
|
|
6660
6676
|
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
6661
6677
|
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|
|
@@ -6673,7 +6689,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6673
6689
|
{ collection, _id: String(id), path, value }
|
|
6674
6690
|
);
|
|
6675
6691
|
};
|
|
6676
|
-
if (tokens.length < 2
|
|
6692
|
+
if (tokens.length < 2) {
|
|
6677
6693
|
drop(`unsupported token count ${tokens.length}`);
|
|
6678
6694
|
return;
|
|
6679
6695
|
}
|
|
@@ -6686,9 +6702,11 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6686
6702
|
drop("second segment is not a bracket-by-id");
|
|
6687
6703
|
return;
|
|
6688
6704
|
}
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6705
|
+
for (let i = 2; i < tokens.length; i++) {
|
|
6706
|
+
if (tokens[i].startsWith("[")) {
|
|
6707
|
+
drop("nested bracket path");
|
|
6708
|
+
return;
|
|
6709
|
+
}
|
|
6692
6710
|
}
|
|
6693
6711
|
const bracketId = secondToken.slice(1, -1);
|
|
6694
6712
|
if (bracketId.length === 0) {
|
|
@@ -6719,12 +6737,36 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6719
6737
|
}
|
|
6720
6738
|
return;
|
|
6721
6739
|
}
|
|
6722
|
-
const
|
|
6723
|
-
const
|
|
6740
|
+
const subFieldPath = tokens.slice(2);
|
|
6741
|
+
const buildNestedSubTree = (segs, leaf) => {
|
|
6742
|
+
let acc = leaf;
|
|
6743
|
+
for (let i = segs.length - 1; i >= 0; i--) {
|
|
6744
|
+
acc = { [segs[i]]: acc };
|
|
6745
|
+
}
|
|
6746
|
+
return acc;
|
|
6747
|
+
};
|
|
6724
6748
|
if (existing == null) {
|
|
6725
|
-
|
|
6749
|
+
const subTree = buildNestedSubTree(subFieldPath, value);
|
|
6750
|
+
seed[firstToken] = [__spreadValues({ _id: bracketId }, subTree)];
|
|
6751
|
+
return;
|
|
6752
|
+
}
|
|
6753
|
+
const existingIdx = existing.findIndex(
|
|
6754
|
+
(it) => it != null && typeof it === "object" && String(it._id) === bracketId
|
|
6755
|
+
);
|
|
6756
|
+
if (existingIdx >= 0) {
|
|
6757
|
+
let cur = existing[existingIdx];
|
|
6758
|
+
for (let i = 0; i < subFieldPath.length - 1; i++) {
|
|
6759
|
+
const seg = subFieldPath[i];
|
|
6760
|
+
const next = cur[seg];
|
|
6761
|
+
if (next == null || typeof next !== "object" || Array.isArray(next)) {
|
|
6762
|
+
cur[seg] = {};
|
|
6763
|
+
}
|
|
6764
|
+
cur = cur[seg];
|
|
6765
|
+
}
|
|
6766
|
+
cur[subFieldPath[subFieldPath.length - 1]] = value;
|
|
6726
6767
|
} else {
|
|
6727
|
-
|
|
6768
|
+
const subTree = buildNestedSubTree(subFieldPath, value);
|
|
6769
|
+
existing.push(__spreadValues({ _id: bracketId }, subTree));
|
|
6728
6770
|
}
|
|
6729
6771
|
}
|
|
6730
6772
|
/**
|
|
@@ -10113,7 +10155,7 @@ var Ebus2ProxyServerUpdateNotifier = class {
|
|
|
10113
10155
|
}
|
|
10114
10156
|
}
|
|
10115
10157
|
handleError(event) {
|
|
10116
|
-
|
|
10158
|
+
networkError(`[Ebus2ProxyNotifier] WebSocket error: ${event.type}`, event);
|
|
10117
10159
|
}
|
|
10118
10160
|
handleMessage(event) {
|
|
10119
10161
|
try {
|
|
@@ -10234,7 +10276,7 @@ var Ebus2ProxyServerUpdateNotifier = class {
|
|
|
10234
10276
|
this.reconnectTimer = void 0;
|
|
10235
10277
|
if (this.shouldReconnect && !this.forcedOffline) {
|
|
10236
10278
|
this.createWebSocket().catch((err) => {
|
|
10237
|
-
|
|
10279
|
+
networkError(`[Ebus2ProxyNotifier] Reconnection failed: ${err}`, err);
|
|
10238
10280
|
this.currentReconnectDelay = Math.min(
|
|
10239
10281
|
this.currentReconnectDelay * 2,
|
|
10240
10282
|
this.maxReconnectDelayMs
|
|
@@ -521,19 +521,25 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
521
521
|
private static applyDiffLocally;
|
|
522
522
|
/**
|
|
523
523
|
* Fallback for `setByPath` failures inside `applyDiffLocally`. Materializes
|
|
524
|
-
* the parent array when it's missing from `seed
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
* the
|
|
536
|
-
*
|
|
524
|
+
* the parent array when it's missing from `seed`, OR fills in missing
|
|
525
|
+
* intermediate objects under an existing array element when the diff
|
|
526
|
+
* path reaches deeper than the element currently holds.
|
|
527
|
+
*
|
|
528
|
+
* Supported shapes (path tokenizes to `[firstField, [<id>], …rest]`):
|
|
529
|
+
*
|
|
530
|
+
* 1. `polje[<id>] = <obj>` → seed.polje = [<obj>]
|
|
531
|
+
* 2. `polje[<id>].field = <v>` → seed.polje = [{_id: <id>, field: <v>}]
|
|
532
|
+
* 3. `polje[<id>].a.b = <v>` → seed.polje = [{_id: <id>, a: {b: <v>}}]
|
|
533
|
+
* 4. `polje[<id>].a.b.c.d = <v>` → seed.polje = [{_id: <id>, a: {b: {c: {d: <v>}}}}]
|
|
534
|
+
* 5. Existing element with `_id` matches `<id>` but missing intermediate
|
|
535
|
+
* → walk down the element, create missing intermediate plain objects,
|
|
536
|
+
* set the leaf. No duplicate element pushed.
|
|
537
|
+
*
|
|
538
|
+
* Dropped (multi-bracket / nested-via-dots-before-bracket / non-plain
|
|
539
|
+
* intermediates) — materializing those locally would risk corrupting
|
|
540
|
+
* unrelated invariants on the nested objects. Dirty-change still
|
|
541
|
+
* carries the path, so the server applies it; next sync brings the
|
|
542
|
+
* canonical state back to local.
|
|
537
543
|
*
|
|
538
544
|
* Replaces the pre-fix blind `seed[path] = value` fallback that stamped
|
|
539
545
|
* literal bracket-keyed top-level properties (e.g. `"postavke[<id>]": [<el>]`)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log a failure at appropriate severity depending on perceived network
|
|
3
|
+
* connectivity.
|
|
4
|
+
*
|
|
5
|
+
* - **Online** (`navigator.onLine === true`, or `navigator` unavailable
|
|
6
|
+
* like in Node/SSR): routes to `console.error`. Failures while the
|
|
7
|
+
* browser thinks it's online are unexpected — operators should see
|
|
8
|
+
* them.
|
|
9
|
+
* - **Offline** (`navigator.onLine === false`): routes to
|
|
10
|
+
* `console.info`. Sync/upload/reconnect failures are the expected
|
|
11
|
+
* steady-state and shouldn't pollute the error stream.
|
|
12
|
+
*
|
|
13
|
+
* Use this ONLY for sites whose failure reduces to "the network is
|
|
14
|
+
* down" — caller bugs, Dexie failures, parse errors, and consumer
|
|
15
|
+
* callback throws must keep using `console.error` directly so they're
|
|
16
|
+
* visible regardless of connectivity.
|
|
17
|
+
*
|
|
18
|
+
* Signature mirrors `console.error` — first arg is the tag-line string
|
|
19
|
+
* (per the console-reporting skill), remaining args are objects /
|
|
20
|
+
* values for devtools inspection.
|
|
21
|
+
*
|
|
22
|
+
* Caveat: `navigator.onLine` only tells you whether the browser has a
|
|
23
|
+
* network interface; it can't detect "WiFi connected but server
|
|
24
|
+
* unreachable". In that case this still routes to `console.error`,
|
|
25
|
+
* which is correct — the failure is unexpected from the browser's
|
|
26
|
+
* point of view.
|
|
27
|
+
*/
|
|
28
|
+
export declare function networkError(message: string, ...rest: unknown[]): void;
|