cry-synced-db-client 0.1.187 → 0.1.188
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 +84 -0
- package/dist/index.js +63 -0
- package/dist/src/db/SyncedDb.d.ts +27 -0
- package/dist/src/types/I_SyncedDb.d.ts +33 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 0.1.188 (2026-05-15)
|
|
4
|
+
|
|
5
|
+
### `clearDirty()` — manual dirty drain + `onBeforeDirtyClearAll` callback
|
|
6
|
+
|
|
7
|
+
Public counterpart to `getDirty` / `getDirtyMeta` for the "stuck dirty
|
|
8
|
+
drain" recovery flow. Use case: a known-bad dirty entry can't upload
|
|
9
|
+
(e.g. pre-fix `sestevki` parent+descendant producing repeated 500-loops
|
|
10
|
+
on klikvet tabs running pre-0.1.185 bundles) and the operator wants to
|
|
11
|
+
forfeit the pending local intent in favor of server state — without
|
|
12
|
+
calling the heavier `dropCollection` / `dropDatabase` which also wipes
|
|
13
|
+
the main row data.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
clearDirty(
|
|
17
|
+
collection?: string,
|
|
18
|
+
ids?: Id[],
|
|
19
|
+
calledFrom?: string,
|
|
20
|
+
): Promise<DirtyMeta[]>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Returns the `DirtyMeta[]` of every entry that was actually removed, so
|
|
24
|
+
the caller can archive what was lost.
|
|
25
|
+
|
|
26
|
+
| Call shape | Effect | Fires `onBeforeDirtyClearAll` |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `clearDirty()` / `clearDirty(undefined)` | Clear ALL dirty in EVERY collection | ✅ once per collection that has dirty |
|
|
29
|
+
| `clearDirty(coll)` | Clear all dirty in one collection | ❌ |
|
|
30
|
+
| `clearDirty(coll, ids)` | Clear specific ids in that collection | ❌ |
|
|
31
|
+
| `clearDirty(undefined, ids)` | Throws — ambiguous | — |
|
|
32
|
+
|
|
33
|
+
The new config callback:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
onBeforeDirtyClearAll?: (info: BeforeDirtyClearAllInfo) => void;
|
|
37
|
+
|
|
38
|
+
interface BeforeDirtyClearAllInfo {
|
|
39
|
+
reason: string; // `calledFrom` if provided, else "manual"
|
|
40
|
+
collection: string;
|
|
41
|
+
items: DirtyMeta[]; // every entry in that collection (no `changes` payload)
|
|
42
|
+
calledFrom?: string; // passthrough of the third arg
|
|
43
|
+
timestamp: Date;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Fires **only** on the clear-all path, **before** the Dexie delete runs,
|
|
48
|
+
**once per collection** with dirty. Use to archive the dirty state to
|
|
49
|
+
syslog / audit trail before it disappears. Per-collection /
|
|
50
|
+
per-id `clearDirty` calls are intentionally silent — the caller already
|
|
51
|
+
knows what they're touching.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
new SyncedDb({
|
|
55
|
+
// ...
|
|
56
|
+
onBeforeDirtyClearAll: (info) => {
|
|
57
|
+
syslog.warn("dirty cleared", {
|
|
58
|
+
collection: info.collection,
|
|
59
|
+
count: info.items.length,
|
|
60
|
+
reason: info.reason,
|
|
61
|
+
ids: info.items.map((m) => m.id),
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Nuke everything across collections, tagged for the audit log.
|
|
67
|
+
const cleared = await syncedDb.clearDirty(undefined, undefined, "stuck-drain");
|
|
68
|
+
|
|
69
|
+
// Targeted: drop two known-bad rows in `obiski` (silent — no callback).
|
|
70
|
+
await syncedDb.clearDirty("obiski", ["6a032a59...", "6a05a1ba..."]);
|
|
71
|
+
|
|
72
|
+
// Inspect first, then decide.
|
|
73
|
+
const meta = await syncedDb.getDirtyMeta();
|
|
74
|
+
for (const [coll, items] of Object.entries(meta)) {
|
|
75
|
+
if (items.length > 100) {
|
|
76
|
+
await syncedDb.clearDirty(coll, undefined, `over-threshold:${items.length}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`clearDirty` does NOT await in-flight uploads — call `flushToServer()`
|
|
82
|
+
first if a last-chance upload is desired. It also does not touch the
|
|
83
|
+
main row data; in-mem and Dexie rows are preserved at whatever state
|
|
84
|
+
they're at (typically: the server's view if WS-push has landed
|
|
85
|
+
recently, otherwise the pre-rollback local intent).
|
|
86
|
+
|
|
3
87
|
## 0.1.187 (2026-05-16)
|
|
4
88
|
|
|
5
89
|
### Fix: `setByPath` auto-creates plain-object intermediates (root cause of `sestevki` parent+child conflict)
|
package/dist/index.js
CHANGED
|
@@ -4484,6 +4484,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4484
4484
|
this.onEviction = config.onEviction;
|
|
4485
4485
|
this.onSaveIdMismatch = config.onSaveIdMismatch;
|
|
4486
4486
|
this.onUploadSkip = config.onUploadSkip;
|
|
4487
|
+
this.onBeforeDirtyClearAll = config.onBeforeDirtyClearAll;
|
|
4487
4488
|
this.evictStaleRecordsEveryHrs = (_e = config.evictStaleRecordsEveryHrs) != null ? _e : 0;
|
|
4488
4489
|
this.scopeExitLookbehindMs = (_f = config.scopeExitLookbehindMs) != null ? _f : 0;
|
|
4489
4490
|
this.evictOnWake = (_g = config.evictOnWake) != null ? _g : false;
|
|
@@ -5759,6 +5760,68 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5759
5760
|
}
|
|
5760
5761
|
return result;
|
|
5761
5762
|
}
|
|
5763
|
+
/**
|
|
5764
|
+
* Manually clear pending dirty changes WITHOUT touching the main
|
|
5765
|
+
* row data. Counterpart to `getDirty` / `getDirtyMeta` for the
|
|
5766
|
+
* "stuck dirty drain" recovery flow — when a known-bad dirty entry
|
|
5767
|
+
* can't be uploaded (e.g. pre-fix sestevki parent+descendant
|
|
5768
|
+
* conflict producing repeated 500s) and the operator wants to
|
|
5769
|
+
* forfeit the pending local intent in favor of server state.
|
|
5770
|
+
*
|
|
5771
|
+
* Three call shapes:
|
|
5772
|
+
*
|
|
5773
|
+
* | Call | Effect |
|
|
5774
|
+
* |---|---|
|
|
5775
|
+
* | `clearDirty()` / `clearDirty(undefined)` | Clear ALL dirty in EVERY collection. Fires `onBeforeDirtyClearAll` once per collection that has dirty (BEFORE the Dexie delete). |
|
|
5776
|
+
* | `clearDirty(coll)` | Clear all dirty in one collection. No callback. |
|
|
5777
|
+
* | `clearDirty(coll, ids)` | Clear specific ids in that collection. No callback. |
|
|
5778
|
+
* | `clearDirty(undefined, ids)` | Throws — ambiguous (which collection do the ids belong to?). |
|
|
5779
|
+
*
|
|
5780
|
+
* Returns the `DirtyMeta[]` of every entry that was actually
|
|
5781
|
+
* removed so the caller can log / archive what was lost.
|
|
5782
|
+
*
|
|
5783
|
+
* Pairs naturally with `getDirtyMeta()` for the inspect-then-clear
|
|
5784
|
+
* pattern. Doesn't await in-flight uploads — caller's
|
|
5785
|
+
* responsibility to call `flushToServer()` first if a last-chance
|
|
5786
|
+
* upload is desired.
|
|
5787
|
+
*/
|
|
5788
|
+
async clearDirty(collection, ids, calledFrom) {
|
|
5789
|
+
if (!collection && ids && ids.length > 0) {
|
|
5790
|
+
throw new Error(
|
|
5791
|
+
`[SyncedDb] clearDirty: 'ids' requires a 'collection' \u2014 ids alone are ambiguous across collections.`
|
|
5792
|
+
);
|
|
5793
|
+
}
|
|
5794
|
+
const cleared = [];
|
|
5795
|
+
if (!collection) {
|
|
5796
|
+
for (const [name] of this.collections) {
|
|
5797
|
+
const metas2 = await this.dexieDb.getDirtyMeta(name);
|
|
5798
|
+
if (metas2.length === 0) continue;
|
|
5799
|
+
this.safeCallback(this.onBeforeDirtyClearAll, {
|
|
5800
|
+
reason: calledFrom != null ? calledFrom : "manual",
|
|
5801
|
+
collection: name,
|
|
5802
|
+
items: metas2,
|
|
5803
|
+
calledFrom,
|
|
5804
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
5805
|
+
});
|
|
5806
|
+
await this.dexieDb.clearDirtyChanges(name);
|
|
5807
|
+
for (const m of metas2) cleared.push(m);
|
|
5808
|
+
}
|
|
5809
|
+
return cleared;
|
|
5810
|
+
}
|
|
5811
|
+
this.assertCollection(collection);
|
|
5812
|
+
if (ids && ids.length > 0) {
|
|
5813
|
+
const idStrings = new Set(ids.map((id) => String(id)));
|
|
5814
|
+
const allMetas = await this.dexieDb.getDirtyMeta(collection);
|
|
5815
|
+
for (const m of allMetas) {
|
|
5816
|
+
if (idStrings.has(String(m.id))) cleared.push(m);
|
|
5817
|
+
}
|
|
5818
|
+
await this.dexieDb.clearDirtyChangesBatch(collection, ids);
|
|
5819
|
+
return cleared;
|
|
5820
|
+
}
|
|
5821
|
+
const metas = await this.dexieDb.getDirtyMeta(collection);
|
|
5822
|
+
await this.dexieDb.clearDirtyChanges(collection);
|
|
5823
|
+
return metas;
|
|
5824
|
+
}
|
|
5762
5825
|
// ==================== Data Deletion ====================
|
|
5763
5826
|
async dropCollection(collection, force = false) {
|
|
5764
5827
|
this.assertCollection(collection);
|
|
@@ -56,6 +56,7 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
56
56
|
private readonly onEviction?;
|
|
57
57
|
private readonly onSaveIdMismatch?;
|
|
58
58
|
private readonly onUploadSkip?;
|
|
59
|
+
private readonly onBeforeDirtyClearAll?;
|
|
59
60
|
private readonly evictStaleRecordsEveryHrs;
|
|
60
61
|
private readonly scopeExitLookbehindMs;
|
|
61
62
|
private readonly evictOnWake;
|
|
@@ -238,6 +239,32 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
238
239
|
getOnWakeSync(): ((info: import("./types/managers").WakeSyncInfo) => void) | undefined;
|
|
239
240
|
getDirty<T extends DbEntity>(): Promise<Readonly<Record<string, readonly T[]>>>;
|
|
240
241
|
getDirtyMeta(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
|
|
242
|
+
/**
|
|
243
|
+
* Manually clear pending dirty changes WITHOUT touching the main
|
|
244
|
+
* row data. Counterpart to `getDirty` / `getDirtyMeta` for the
|
|
245
|
+
* "stuck dirty drain" recovery flow — when a known-bad dirty entry
|
|
246
|
+
* can't be uploaded (e.g. pre-fix sestevki parent+descendant
|
|
247
|
+
* conflict producing repeated 500s) and the operator wants to
|
|
248
|
+
* forfeit the pending local intent in favor of server state.
|
|
249
|
+
*
|
|
250
|
+
* Three call shapes:
|
|
251
|
+
*
|
|
252
|
+
* | Call | Effect |
|
|
253
|
+
* |---|---|
|
|
254
|
+
* | `clearDirty()` / `clearDirty(undefined)` | Clear ALL dirty in EVERY collection. Fires `onBeforeDirtyClearAll` once per collection that has dirty (BEFORE the Dexie delete). |
|
|
255
|
+
* | `clearDirty(coll)` | Clear all dirty in one collection. No callback. |
|
|
256
|
+
* | `clearDirty(coll, ids)` | Clear specific ids in that collection. No callback. |
|
|
257
|
+
* | `clearDirty(undefined, ids)` | Throws — ambiguous (which collection do the ids belong to?). |
|
|
258
|
+
*
|
|
259
|
+
* Returns the `DirtyMeta[]` of every entry that was actually
|
|
260
|
+
* removed so the caller can log / archive what was lost.
|
|
261
|
+
*
|
|
262
|
+
* Pairs naturally with `getDirtyMeta()` for the inspect-then-clear
|
|
263
|
+
* pattern. Doesn't await in-flight uploads — caller's
|
|
264
|
+
* responsibility to call `flushToServer()` first if a last-chance
|
|
265
|
+
* upload is desired.
|
|
266
|
+
*/
|
|
267
|
+
clearDirty(collection?: string, ids?: Id[], calledFrom?: string): Promise<DirtyMeta[]>;
|
|
241
268
|
dropCollection(collection: string, force?: boolean): Promise<void>;
|
|
242
269
|
dropDatabase(force?: boolean): Promise<void>;
|
|
243
270
|
/**
|
|
@@ -91,6 +91,28 @@ export interface SaveIdMismatchInfo {
|
|
|
91
91
|
/** Timestamp when mismatch detected */
|
|
92
92
|
timestamp: Date;
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Callback payload fired by `clearDirty()` when called WITHOUT a
|
|
96
|
+
* `collection` argument (clear-all path). One invocation per
|
|
97
|
+
* collection that has ≥1 dirty entry, fired BEFORE the underlying
|
|
98
|
+
* Dexie delete runs — caller can archive/inspect the records.
|
|
99
|
+
*
|
|
100
|
+
* Per-collection `clearDirty(coll, ...)` calls do NOT fire this
|
|
101
|
+
* callback (targeted operation; caller already knows what they're
|
|
102
|
+
* touching).
|
|
103
|
+
*/
|
|
104
|
+
export interface BeforeDirtyClearAllInfo {
|
|
105
|
+
/** What triggered the clear-all (e.g. "manual", custom string from caller). */
|
|
106
|
+
reason: string;
|
|
107
|
+
/** Collection whose dirty entries are about to be deleted. */
|
|
108
|
+
collection: string;
|
|
109
|
+
/** Meta of every dirty entry in this collection (no `changes` payload). */
|
|
110
|
+
items: import("./I_DexieDb").DirtyMeta[];
|
|
111
|
+
/** Optional caller tag passed through `clearDirty(undefined, undefined, calledFrom)`. */
|
|
112
|
+
calledFrom?: string;
|
|
113
|
+
/** Timestamp when the callback fires. */
|
|
114
|
+
timestamp: Date;
|
|
115
|
+
}
|
|
94
116
|
/**
|
|
95
117
|
* Callback payload for server write requests (before sending)
|
|
96
118
|
*/
|
|
@@ -649,6 +671,17 @@ export interface SyncedDbConfig {
|
|
|
649
671
|
* where `this._id` and `this.protokol._id` had drifted apart.
|
|
650
672
|
*/
|
|
651
673
|
onSaveIdMismatch?: (info: SaveIdMismatchInfo) => void;
|
|
674
|
+
/**
|
|
675
|
+
* Fired by `clearDirty()` (no collection argument — clear-all path)
|
|
676
|
+
* BEFORE the Dexie delete runs, once per collection that has
|
|
677
|
+
* dirty entries. Use to archive the dirty state to syslog / audit
|
|
678
|
+
* trail before it's gone, or to abort by throwing (the throw
|
|
679
|
+
* aborts only the in-flight clear, doesn't propagate).
|
|
680
|
+
*
|
|
681
|
+
* Per-collection `clearDirty(coll, ...)` calls are SILENT — they
|
|
682
|
+
* don't fire this callback.
|
|
683
|
+
*/
|
|
684
|
+
onBeforeDirtyClearAll?: (info: BeforeDirtyClearAllInfo) => void;
|
|
652
685
|
/**
|
|
653
686
|
* Enable in-memory object metadata feature.
|
|
654
687
|
* When true, collections with hasMetadata=true will have their metadata callbacks invoked
|