cry-synced-db-client 0.1.141 → 0.1.143
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 +30 -0
- package/dist/index.js +29 -9
- package/dist/src/db/DexieDb.d.ts +2 -1
- package/dist/src/db/SyncedDb.d.ts +2 -1
- package/dist/src/db/managers/CrossTabSyncManager.d.ts +6 -3
- package/dist/src/types/I_DexieDb.d.ts +8 -0
- package/dist/src/types/I_SyncedDb.d.ts +6 -1
- package/dist/src/types/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### `getDirtyMeta()` for lightweight dirty-state inspection
|
|
6
|
+
|
|
7
|
+
- New `SyncedDb.getDirtyMeta()` returns dirty-entry meta (everything except the
|
|
8
|
+
`changes` payload) grouped per collection, only for collections with ≥1 dirty
|
|
9
|
+
record. Mirrors `getDirty()` shape but avoids loading change payloads —
|
|
10
|
+
useful for counts, timestamps, and indicator UIs.
|
|
11
|
+
- New `I_DexieDb.getDirtyMeta(collection)` returning `DirtyMeta[]`.
|
|
12
|
+
- New exported `DirtyMeta` type (`Omit<DirtyChange, "changes">`) and
|
|
13
|
+
`DirtyChange` type surfaced from the package entry.
|
|
14
|
+
|
|
15
|
+
### Fix: multi-tab divergence when offline edits cross leader/follower
|
|
16
|
+
|
|
17
|
+
Fixes a bug where, after both tabs edited different records offline and came
|
|
18
|
+
online with leader-first, the leader ended up with stale record content
|
|
19
|
+
carrying the new server `_rev`. Because `resolveConflict` ignores server
|
|
20
|
+
echoes with equal-or-lower `_rev`, the divergence was permanent until page
|
|
21
|
+
reload. Follower-first came out clean; leader-first did not.
|
|
22
|
+
|
|
23
|
+
Two contributing causes, both fixed:
|
|
24
|
+
|
|
25
|
+
- `SyncEngine` post-upload in-mem patch no longer spreads stale `getInMemById`
|
|
26
|
+
result over server-returned `_rev`/`_ts`. In-mem is now fed the freshly
|
|
27
|
+
patched Dexie item (authoritative content + server meta), so the tab that
|
|
28
|
+
uploaded on behalf of another tab's dirty write ends up with matching
|
|
29
|
+
content and `_rev` in-mem.
|
|
30
|
+
- `CrossTabSyncManager.broadcastMetaUpdate` no longer gated by `isLeader()`.
|
|
31
|
+
Non-leader tabs now broadcast their local writes so the leader's in-mem
|
|
32
|
+
cache learns of them via the existing shared-Dexie reload path. Reload
|
|
33
|
+
broadcasts (post-full-sync) remain leader-only.
|
|
34
|
+
|
|
5
35
|
### BREAKING: Self-healing sync/reconnect lifecycle
|
|
6
36
|
|
|
7
37
|
Fixes a class of bugs where the 60s auto-sync scheduler silently died after a
|
package/dist/index.js
CHANGED
|
@@ -634,13 +634,14 @@ var CrossTabSyncManager = class {
|
|
|
634
634
|
}
|
|
635
635
|
/**
|
|
636
636
|
* Broadcast updated IDs to other tabs (debounced).
|
|
637
|
-
*
|
|
637
|
+
* Any tab with local writes broadcasts so other tabs refresh their in-mem
|
|
638
|
+
* from shared Dexie. Otherwise non-leader writes stay invisible to the leader's
|
|
639
|
+
* in-mem cache, and a later upload patches new _rev onto stale content.
|
|
638
640
|
* While a server sync is in progress, suppresses delta broadcasts and only
|
|
639
641
|
* records which collections were affected (for the post-sync reload broadcast).
|
|
640
642
|
*/
|
|
641
643
|
broadcastMetaUpdate(updates) {
|
|
642
644
|
if (!this.metaUpdateChannel) return;
|
|
643
|
-
if (!this.deps.isLeader()) return;
|
|
644
645
|
if (this.serverSyncInProgress) {
|
|
645
646
|
for (const collection of Object.keys(updates)) {
|
|
646
647
|
this.syncAffectedCollections.add(collection);
|
|
@@ -2668,13 +2669,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2668
2669
|
dexieDeleteIds.push(entity._id);
|
|
2669
2670
|
} else {
|
|
2670
2671
|
dexieSaveBatch.push(dexieItem);
|
|
2671
|
-
|
|
2672
|
-
if (inMemItem) {
|
|
2673
|
-
inMemUpdateBatch.push(__spreadProps(__spreadValues({}, inMemItem), {
|
|
2674
|
-
_rev: entity._rev,
|
|
2675
|
-
_ts: entity._ts
|
|
2676
|
-
}));
|
|
2677
|
-
}
|
|
2672
|
+
inMemUpdateBatch.push(dexieItem);
|
|
2678
2673
|
}
|
|
2679
2674
|
}
|
|
2680
2675
|
}
|
|
@@ -4550,6 +4545,16 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4550
4545
|
}
|
|
4551
4546
|
return result;
|
|
4552
4547
|
}
|
|
4548
|
+
async getDirtyMeta() {
|
|
4549
|
+
const result = {};
|
|
4550
|
+
for (const [collectionName] of this.collections) {
|
|
4551
|
+
const metas = await this.dexieDb.getDirtyMeta(collectionName);
|
|
4552
|
+
if (metas.length > 0) {
|
|
4553
|
+
result[collectionName] = metas;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
return result;
|
|
4557
|
+
}
|
|
4553
4558
|
// ==================== Data Deletion ====================
|
|
4554
4559
|
async dropCollection(collection, force = false) {
|
|
4555
4560
|
this.assertCollection(collection);
|
|
@@ -5170,6 +5175,21 @@ var DexieDb = class extends Dexie {
|
|
|
5170
5175
|
}
|
|
5171
5176
|
return result;
|
|
5172
5177
|
}
|
|
5178
|
+
async getDirtyMeta(collection) {
|
|
5179
|
+
const dirtyEntries = await this.dirtyChanges.where("[collection+id]").between([collection, Dexie.minKey], [collection, Dexie.maxKey]).toArray();
|
|
5180
|
+
const result = [];
|
|
5181
|
+
for (const entry of dirtyEntries) {
|
|
5182
|
+
result.push({
|
|
5183
|
+
collection: entry.collection,
|
|
5184
|
+
id: entry.id,
|
|
5185
|
+
baseTs: entry.baseTs,
|
|
5186
|
+
baseRev: entry.baseRev,
|
|
5187
|
+
createdAt: entry.createdAt,
|
|
5188
|
+
updatedAt: entry.updatedAt
|
|
5189
|
+
});
|
|
5190
|
+
}
|
|
5191
|
+
return result;
|
|
5192
|
+
}
|
|
5173
5193
|
async addDirtyChange(collection, id, changes, baseMeta) {
|
|
5174
5194
|
const stringId = this.idToString(id);
|
|
5175
5195
|
const existing = await this.dirtyChanges.get([collection, stringId]);
|
package/dist/src/db/DexieDb.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Dexie from "dexie";
|
|
2
|
-
import type { DirtyChange, I_DexieDb, SyncMeta } from "../types/I_DexieDb";
|
|
2
|
+
import type { DirtyChange, DirtyMeta, I_DexieDb, SyncMeta } from "../types/I_DexieDb";
|
|
3
3
|
import type { CollectionConfig } from "../types/CollectionConfig";
|
|
4
4
|
import type { Id, LocalDbEntity } from "../types/DbEntity";
|
|
5
5
|
/**
|
|
@@ -31,6 +31,7 @@ export declare class DexieDb extends Dexie implements I_DexieDb {
|
|
|
31
31
|
forEachBatch<T extends LocalDbEntity>(collection: string, batchSize: number, callback: (items: T[]) => Promise<void>): Promise<void>;
|
|
32
32
|
count(collection: string): Promise<number>;
|
|
33
33
|
getDirty<T extends LocalDbEntity>(collection: string): Promise<Partial<T>[]>;
|
|
34
|
+
getDirtyMeta(collection: string): Promise<DirtyMeta[]>;
|
|
34
35
|
addDirtyChange(collection: string, id: Id, changes: Record<string, any>, baseMeta?: {
|
|
35
36
|
_ts?: any;
|
|
36
37
|
_rev?: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AggregateOptions } from "mongodb";
|
|
2
2
|
import type { I_SyncedDb, SyncedDbConfig, WsNotificationInfo, EvictionInfo, EvictionCollectionInfo } from "../types/I_SyncedDb";
|
|
3
|
-
import type { MetaUpdateBroadcast } from "../types/I_DexieDb";
|
|
3
|
+
import type { DirtyMeta, MetaUpdateBroadcast } from "../types/I_DexieDb";
|
|
4
4
|
import type { QuerySpec, QueryOpts, UpdateSpec, InsertSpec, BatchSpec } from "../types/I_RestInterface";
|
|
5
5
|
import type { Id, DbEntity } from "../types/DbEntity";
|
|
6
6
|
/**
|
|
@@ -161,6 +161,7 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
161
161
|
getOnWsNotification(): ((info: WsNotificationInfo) => void) | undefined;
|
|
162
162
|
getOnWakeSync(): ((info: import("./types/managers").WakeSyncInfo) => void) | undefined;
|
|
163
163
|
getDirty<T extends DbEntity>(): Promise<Readonly<Record<string, readonly T[]>>>;
|
|
164
|
+
getDirtyMeta(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
|
|
164
165
|
dropCollection(collection: string, force?: boolean): Promise<void>;
|
|
165
166
|
dropDatabase(force?: boolean): Promise<void>;
|
|
166
167
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CrossTabSyncManager - Manages cross-tab synchronization via BroadcastChannel.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Any tab with local writes (leader or follower) broadcasts the IDs of updated
|
|
5
|
+
* records so other tabs refresh their in-memory state from shared Dexie.
|
|
6
|
+
* Reload broadcasts (post-full-sync) remain leader-only.
|
|
6
7
|
*/
|
|
7
8
|
import type { MetaUpdateBroadcast } from "../../types/I_DexieDb";
|
|
8
9
|
import type { I_CrossTabSyncManager, CrossTabSyncConfig } from "../types/managers";
|
|
@@ -31,7 +32,9 @@ export declare class CrossTabSyncManager implements I_CrossTabSyncManager {
|
|
|
31
32
|
init(): void;
|
|
32
33
|
/**
|
|
33
34
|
* Broadcast updated IDs to other tabs (debounced).
|
|
34
|
-
*
|
|
35
|
+
* Any tab with local writes broadcasts so other tabs refresh their in-mem
|
|
36
|
+
* from shared Dexie. Otherwise non-leader writes stay invisible to the leader's
|
|
37
|
+
* in-mem cache, and a later upload patches new _rev onto stale content.
|
|
35
38
|
* While a server sync is in progress, suppresses delta broadcasts and only
|
|
36
39
|
* records which collections were affected (for the post-sync reload broadcast).
|
|
37
40
|
*/
|
|
@@ -27,6 +27,12 @@ export interface DirtyChange {
|
|
|
27
27
|
/** When last change was accumulated */
|
|
28
28
|
updatedAt: number;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Meta fields of a DirtyChange entry, without the `changes` payload.
|
|
32
|
+
* Used by `getDirtyMeta` for lightweight dirty-state inspection
|
|
33
|
+
* (counts, timestamps) without loading change payloads into memory.
|
|
34
|
+
*/
|
|
35
|
+
export type DirtyMeta = Omit<DirtyChange, "changes">;
|
|
30
36
|
/** Shared fields for all cross-tab broadcast messages */
|
|
31
37
|
interface BroadcastBase {
|
|
32
38
|
/** Unique ID of the SyncedDb instance that sent this broadcast */
|
|
@@ -84,6 +90,8 @@ export interface I_DexieDb {
|
|
|
84
90
|
forEachBatch<T extends LocalDbEntity>(collection: string, batchSize: number, callback: (items: T[]) => Promise<void>): Promise<void>;
|
|
85
91
|
/** Vrne vse dirty objekte (z lokalnimi spremembami) - returns only changed fields + _id + metadata */
|
|
86
92
|
getDirty<T extends LocalDbEntity>(collection: string): Promise<Partial<T>[]>;
|
|
93
|
+
/** Vrne meta podatke vseh dirty vnosov za kolekcijo (brez `changes` payloada) */
|
|
94
|
+
getDirtyMeta(collection: string): Promise<DirtyMeta[]>;
|
|
87
95
|
/** Add or accumulate changes for a record */
|
|
88
96
|
addDirtyChange(collection: string, id: Id, changes: Record<string, any>, baseMeta?: {
|
|
89
97
|
_ts?: any;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AggregateOptions } from "mongodb";
|
|
2
2
|
import type { Id, DbEntity, LocalDbEntity } from "./DbEntity";
|
|
3
3
|
import type { QuerySpec, QueryOpts, UpdateSpec, InsertSpec, BatchSpec, I_RestInterface, CollectionUpdateRequest, CollectionUpdateResult, GetNewerSpec } from "./I_RestInterface";
|
|
4
|
-
import type { I_DexieDb } from "./I_DexieDb";
|
|
4
|
+
import type { DirtyMeta, I_DexieDb } from "./I_DexieDb";
|
|
5
5
|
import type { I_InMemDb } from "./I_InMemDb";
|
|
6
6
|
import type { I_ServerUpdateNotifier } from "./I_ServerUpdateNotifier";
|
|
7
7
|
import type { WakeSyncInfo, NetworkStatusChangeInfo } from "../db/types/managers";
|
|
@@ -700,6 +700,11 @@ export interface I_SyncedDb {
|
|
|
700
700
|
getDebounceRestWritesMs(): number;
|
|
701
701
|
/** Vrne vse dirty objekte iz vseh kolekcij */
|
|
702
702
|
getDirty<T extends DbEntity>(): Promise<Readonly<Record<string, readonly T[]>>>;
|
|
703
|
+
/**
|
|
704
|
+
* Vrne meta podatke dirty vnosov (brez `changes` payloada) po kolekcijah,
|
|
705
|
+
* ki imajo vsaj en dirty zapis. Kolekcije brez dirty vnosov niso vključene.
|
|
706
|
+
*/
|
|
707
|
+
getDirtyMeta(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
|
|
703
708
|
/**
|
|
704
709
|
* Drops a collection, ensuring no data loss.
|
|
705
710
|
* - Throws if offline or forcedOffline (unless force=true)
|
|
@@ -2,7 +2,7 @@ export type { Id, Entity, IdOrEntity, DbEntity, LocalDbEntity } from "./DbEntity
|
|
|
2
2
|
export type { PublishableOperation, PublishRevsPayloadInsert, PublishRevsPayloadUpdate, PublishRevsPayloadDelete, PublishRevsPayloadUpdateMany, PublishRevsPayloadDeleteMany, PublishRevsPayloadBatchItem, PublishRevsPayloadBatch, PublishRevsPayload, PublishRevsSpec, PublishDataPayloadBase, PublishDataPayloadInsert, PublishDataPayloadUpdate, PublishDataPayloadDelete, PublishDataPayloadBatch, PublishDataPayload, PublishDataSpec, PublishSpec, } from "./PublishRevsPayload";
|
|
3
3
|
export type { Obj, QuerySpec, Projection, QueryOpts, KeyOf, InsertKeyOf, InsertSpec, UpdateSpec, BatchSpec, UpsertOptions, GetNewerSpec, I_RestInterface as RestInterface, } from "./I_RestInterface";
|
|
4
4
|
export type { I_InMemDb as InMemDb } from "./I_InMemDb";
|
|
5
|
-
export type { I_DexieDb as DexieDb, SyncMeta } from "./I_DexieDb";
|
|
5
|
+
export type { I_DexieDb as DexieDb, SyncMeta, DirtyChange, DirtyMeta } from "./I_DexieDb";
|
|
6
6
|
export type { I_ServerUpdateNotifier as ServerUpdateNotifier, ServerUpdateCallback, ServerUpdateNotifierCallbacks } from "./I_ServerUpdateNotifier";
|
|
7
7
|
export type { I_SyncedDb as SyncedDb, SyncedDbConfig, CollectionConfig, CollectionSyncConfig, SyncInfo, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerManyResultInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, InfrastructureErrorType, InfrastructureErrorInfo, ConflictSource, ConflictResolutionReport, CrossTabSyncInfo, EvictionInfo, EvictionCollectionInfo, } from "./I_SyncedDb";
|
|
8
8
|
export type { NetworkStatusChangeInfo } from "../db/types/managers";
|