edmaxlabs-core 2.5.5 → 2.6.6
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/README.md +337 -194
- package/dist/index.cjs +704 -325
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -54
- package/dist/index.d.ts +72 -54
- package/dist/index.mjs +704 -325
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -300,7 +300,6 @@ var _Authentication = class _Authentication {
|
|
|
300
300
|
return;
|
|
301
301
|
userDocUnsubscribe = userRef.onSnapshot(
|
|
302
302
|
(snapshot, change) => {
|
|
303
|
-
console.log(snapshot);
|
|
304
303
|
if (change === "delete") {
|
|
305
304
|
this.saveCredentials(null);
|
|
306
305
|
onSignOut?.();
|
|
@@ -360,20 +359,6 @@ var DocumentSnapshot = class _DocumentSnapshot {
|
|
|
360
359
|
}
|
|
361
360
|
};
|
|
362
361
|
|
|
363
|
-
// src/utils/documentNomalizer.ts
|
|
364
|
-
function normalizePayload(payload) {
|
|
365
|
-
const raw = payload?.document ?? payload?.data ?? payload;
|
|
366
|
-
if (!raw)
|
|
367
|
-
return null;
|
|
368
|
-
const doc = { ...raw };
|
|
369
|
-
doc.id = payload.id ?? payload._id;
|
|
370
|
-
delete doc._id;
|
|
371
|
-
return {
|
|
372
|
-
change: payload?.change ?? raw?.change ?? null,
|
|
373
|
-
data: doc
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
362
|
// src/database/DocumentRef.ts
|
|
378
363
|
function validateDocumentData(data, operation) {
|
|
379
364
|
if (data === null || data === void 0) {
|
|
@@ -399,40 +384,22 @@ var DocumentRef = class {
|
|
|
399
384
|
this.syncEngine = app.offline().syncEngine;
|
|
400
385
|
this.localStore = app.offline().localStore;
|
|
401
386
|
}
|
|
402
|
-
// ====================== GET ======================
|
|
403
387
|
async get() {
|
|
404
388
|
if (this.persistence) {
|
|
405
389
|
try {
|
|
406
390
|
const localSnap = await this.persistence.getDocSnapshot(this.collection, this.id);
|
|
391
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
392
|
+
this.refreshFromRemote().catch(() => {
|
|
393
|
+
});
|
|
394
|
+
}
|
|
407
395
|
if (localSnap)
|
|
408
396
|
return localSnap;
|
|
409
397
|
} catch (error) {
|
|
410
398
|
console.error("[EdmaxLabs] Cache read error:", error);
|
|
411
399
|
}
|
|
412
400
|
}
|
|
413
|
-
|
|
414
|
-
const res = await new HttpsRequest({
|
|
415
|
-
method: "POST" /* POST */,
|
|
416
|
-
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
417
|
-
headers: {
|
|
418
|
-
authorization: this.app.getConfig().token,
|
|
419
|
-
"x-project": this.app.getConfig().project
|
|
420
|
-
},
|
|
421
|
-
body: {
|
|
422
|
-
collection: this.collection,
|
|
423
|
-
id: this.id,
|
|
424
|
-
single: true
|
|
425
|
-
}
|
|
426
|
-
}).sendRequest();
|
|
427
|
-
if (!res?.success || !res.document)
|
|
428
|
-
return null;
|
|
429
|
-
return DocumentSnapshot.fromMap(res.document);
|
|
430
|
-
} catch (error) {
|
|
431
|
-
console.error(`[DocumentRef] get(${this.collection}/${this.id}) failed:`, error);
|
|
432
|
-
return null;
|
|
433
|
-
}
|
|
401
|
+
return this.fetchRemoteSnapshot();
|
|
434
402
|
}
|
|
435
|
-
// ====================== UPDATE ======================
|
|
436
403
|
async update(data) {
|
|
437
404
|
if (this._isUpdating) {
|
|
438
405
|
console.warn(`[DocumentRef] update recursion blocked on ${this.collection}/${this.id}`);
|
|
@@ -478,15 +445,13 @@ var DocumentRef = class {
|
|
|
478
445
|
baseRevision: old.revision
|
|
479
446
|
});
|
|
480
447
|
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
481
|
-
this.
|
|
482
|
-
this.localStore?.notifyCollectionChanged(this.collection, this.id, "update");
|
|
448
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "update").catch(console.error);
|
|
483
449
|
this.syncEngine?.flush().catch(console.error);
|
|
484
450
|
return snap;
|
|
485
451
|
} finally {
|
|
486
452
|
this._isUpdating = false;
|
|
487
453
|
}
|
|
488
454
|
}
|
|
489
|
-
// ====================== SET ======================
|
|
490
455
|
async set(data) {
|
|
491
456
|
validateDocumentData(data, "DocumentRef.set");
|
|
492
457
|
if (!this.persistence) {
|
|
@@ -501,6 +466,8 @@ var DocumentRef = class {
|
|
|
501
466
|
}).sendRequest();
|
|
502
467
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
503
468
|
}
|
|
469
|
+
const existing = await this.persistence.getDoc(this.collection, this.id);
|
|
470
|
+
const mutationType = existing?.exists && !existing.deleted ? "update" : "insert";
|
|
504
471
|
const updated = await this.persistence.upsertDoc({
|
|
505
472
|
collection: this.collection,
|
|
506
473
|
id: this.id,
|
|
@@ -509,22 +476,27 @@ var DocumentRef = class {
|
|
|
509
476
|
deleted: false,
|
|
510
477
|
pending: 1,
|
|
511
478
|
localOnly: false,
|
|
512
|
-
status: "pending"
|
|
479
|
+
status: "pending",
|
|
480
|
+
revision: existing?.revision,
|
|
481
|
+
lastSyncedAt: existing?.lastSyncedAt
|
|
513
482
|
});
|
|
514
483
|
await this.persistence.enqueueMutation({
|
|
515
484
|
mutationId: this.persistence.createMutationId(),
|
|
516
485
|
collection: this.collection,
|
|
517
486
|
documentId: this.id,
|
|
518
|
-
type:
|
|
519
|
-
payload: data
|
|
487
|
+
type: mutationType,
|
|
488
|
+
payload: data,
|
|
489
|
+
baseRevision: existing?.revision
|
|
520
490
|
});
|
|
521
491
|
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
522
|
-
this.
|
|
523
|
-
|
|
492
|
+
this.app.offline().realtimeBridge?.publishLocalChange(
|
|
493
|
+
this.collection,
|
|
494
|
+
this.id,
|
|
495
|
+
mutationType === "insert" ? "insert" : "update"
|
|
496
|
+
).catch(console.error);
|
|
524
497
|
this.syncEngine?.flush().catch(console.error);
|
|
525
498
|
return snap;
|
|
526
499
|
}
|
|
527
|
-
// ====================== DELETE ======================
|
|
528
500
|
async delete() {
|
|
529
501
|
if (this.persistence) {
|
|
530
502
|
await this.persistence.markDeleted(this.collection, this.id, 1);
|
|
@@ -535,8 +507,7 @@ var DocumentRef = class {
|
|
|
535
507
|
type: "delete",
|
|
536
508
|
payload: null
|
|
537
509
|
});
|
|
538
|
-
this.
|
|
539
|
-
this.localStore?.notifyCollectionChanged(this.collection, this.id, "delete");
|
|
510
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "delete").catch(console.error);
|
|
540
511
|
this.syncEngine?.flush().catch(console.error);
|
|
541
512
|
return true;
|
|
542
513
|
}
|
|
@@ -551,20 +522,135 @@ var DocumentRef = class {
|
|
|
551
522
|
}).sendRequest();
|
|
552
523
|
return !!res?.success;
|
|
553
524
|
}
|
|
554
|
-
// ====================== SNAPSHOT ======================
|
|
555
525
|
onSnapshot(callback) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
526
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
527
|
+
if (this.persistence && this.localStore && realtimeBridge) {
|
|
528
|
+
const localUnsub = this.localStore.subscribeToDocument(this.collection, this.id, callback);
|
|
529
|
+
const remoteUnsub = realtimeBridge.watchDocument(this.collection, this.id);
|
|
530
|
+
this.persistence.getDocSnapshot(this.collection, this.id).then((snapshot) => callback(snapshot, "initial")).catch(console.error);
|
|
531
|
+
return () => {
|
|
532
|
+
localUnsub();
|
|
533
|
+
remoteUnsub();
|
|
534
|
+
};
|
|
560
535
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
536
|
+
this.fetchRemoteSnapshot().then((snapshot) => callback(snapshot, "initial")).catch(console.error);
|
|
537
|
+
return this.app.rtdb().subscribeToDocumentRaw(this.collection, this.id, (payload, change) => {
|
|
538
|
+
if (change === "delete") {
|
|
539
|
+
callback(null, "delete");
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
callback(DocumentSnapshot.fromMap(payload), change);
|
|
564
543
|
});
|
|
565
544
|
}
|
|
545
|
+
async fetchRemoteSnapshot() {
|
|
546
|
+
try {
|
|
547
|
+
const res = await new HttpsRequest({
|
|
548
|
+
method: "POST" /* POST */,
|
|
549
|
+
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
550
|
+
headers: {
|
|
551
|
+
authorization: this.app.getConfig().token,
|
|
552
|
+
"x-project": this.app.getConfig().project
|
|
553
|
+
},
|
|
554
|
+
body: {
|
|
555
|
+
collection: this.collection,
|
|
556
|
+
id: this.id,
|
|
557
|
+
single: true
|
|
558
|
+
}
|
|
559
|
+
}).sendRequest();
|
|
560
|
+
if (!res?.success || !res.document)
|
|
561
|
+
return null;
|
|
562
|
+
const snapshot = DocumentSnapshot.fromMap(res.document);
|
|
563
|
+
if (this.persistence) {
|
|
564
|
+
await this.persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
565
|
+
}
|
|
566
|
+
return snapshot;
|
|
567
|
+
} catch (error) {
|
|
568
|
+
console.error(`[DocumentRef] get(${this.collection}/${this.id}) failed:`, error);
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async refreshFromRemote() {
|
|
573
|
+
const snapshot = await this.fetchRemoteSnapshot();
|
|
574
|
+
if (this.persistence && snapshot) {
|
|
575
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "update").catch(console.error);
|
|
576
|
+
}
|
|
577
|
+
return snapshot;
|
|
578
|
+
}
|
|
566
579
|
};
|
|
567
580
|
|
|
581
|
+
// src/database/RealtimeListeners.ts
|
|
582
|
+
function buildTargetKey(collection, filter = {}) {
|
|
583
|
+
return `${collection}:${stableStringify(filter)}`;
|
|
584
|
+
}
|
|
585
|
+
function stableStringify(value) {
|
|
586
|
+
if (value === null || typeof value !== "object") {
|
|
587
|
+
return JSON.stringify(value);
|
|
588
|
+
}
|
|
589
|
+
if (Array.isArray(value)) {
|
|
590
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
591
|
+
}
|
|
592
|
+
const entries = Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
593
|
+
return `{${entries.join(",")}}`;
|
|
594
|
+
}
|
|
595
|
+
function diffSnapshots(previous, next) {
|
|
596
|
+
const previousById = new Map(previous.map((snapshot, index) => [snapshot.id, { snapshot, index }]));
|
|
597
|
+
const nextById = new Map(next.map((snapshot, index) => [snapshot.id, { snapshot, index }]));
|
|
598
|
+
const changes = [];
|
|
599
|
+
next.forEach((snapshot, index) => {
|
|
600
|
+
const before = previousById.get(snapshot.id);
|
|
601
|
+
if (!before) {
|
|
602
|
+
changes.push({
|
|
603
|
+
type: "added",
|
|
604
|
+
doc: snapshot,
|
|
605
|
+
previousDoc: null,
|
|
606
|
+
oldIndex: -1,
|
|
607
|
+
newIndex: index
|
|
608
|
+
});
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (before.index !== index || !areSnapshotsEqual(before.snapshot, snapshot)) {
|
|
612
|
+
changes.push({
|
|
613
|
+
type: "modified",
|
|
614
|
+
doc: snapshot,
|
|
615
|
+
previousDoc: before.snapshot,
|
|
616
|
+
oldIndex: before.index,
|
|
617
|
+
newIndex: index
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
previous.forEach((snapshot, index) => {
|
|
622
|
+
if (nextById.has(snapshot.id))
|
|
623
|
+
return;
|
|
624
|
+
changes.push({
|
|
625
|
+
type: "removed",
|
|
626
|
+
doc: snapshot,
|
|
627
|
+
previousDoc: snapshot,
|
|
628
|
+
oldIndex: index,
|
|
629
|
+
newIndex: -1
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
return changes;
|
|
633
|
+
}
|
|
634
|
+
function applySnapshotChange(current, incoming, source) {
|
|
635
|
+
if (!incoming) {
|
|
636
|
+
return current;
|
|
637
|
+
}
|
|
638
|
+
if (source === "delete") {
|
|
639
|
+
return current.filter((snapshot) => snapshot.id !== incoming.id);
|
|
640
|
+
}
|
|
641
|
+
const next = [...current];
|
|
642
|
+
const index = next.findIndex((snapshot) => snapshot.id === incoming.id);
|
|
643
|
+
if (index === -1) {
|
|
644
|
+
next.push(incoming);
|
|
645
|
+
return next;
|
|
646
|
+
}
|
|
647
|
+
next[index] = incoming;
|
|
648
|
+
return next;
|
|
649
|
+
}
|
|
650
|
+
function areSnapshotsEqual(left, right) {
|
|
651
|
+
return stableStringify(left.data) === stableStringify(right.data);
|
|
652
|
+
}
|
|
653
|
+
|
|
568
654
|
// src/database/Query.ts
|
|
569
655
|
var Query = class {
|
|
570
656
|
constructor(app, collection) {
|
|
@@ -577,6 +663,84 @@ var Query = class {
|
|
|
577
663
|
this.filter.push(expression);
|
|
578
664
|
return this;
|
|
579
665
|
}
|
|
666
|
+
async get() {
|
|
667
|
+
const persistence = this.app.offline().persistence;
|
|
668
|
+
if (this.filter.length > 0 && persistence) {
|
|
669
|
+
const local = await this.getLocalFilteredSnapshots();
|
|
670
|
+
if (typeof navigator === "undefined" || !navigator.onLine) {
|
|
671
|
+
return local;
|
|
672
|
+
}
|
|
673
|
+
return this.refreshFromRemote();
|
|
674
|
+
}
|
|
675
|
+
if (persistence) {
|
|
676
|
+
const local = await persistence.getCollectionSnapshots(this.collection);
|
|
677
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
678
|
+
this.refreshFromRemote().catch(() => {
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return local;
|
|
682
|
+
}
|
|
683
|
+
return this.refreshFromRemote();
|
|
684
|
+
}
|
|
685
|
+
async update(data) {
|
|
686
|
+
const genfilter = this.buildFilter();
|
|
687
|
+
return new HttpsRequest({
|
|
688
|
+
method: "POST" /* POST */,
|
|
689
|
+
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
690
|
+
headers: { authorization: this.app.getConfig().token, "x-project": this.app.getConfig().project },
|
|
691
|
+
body: {
|
|
692
|
+
collection: this.collection,
|
|
693
|
+
filter: genfilter,
|
|
694
|
+
data
|
|
695
|
+
}
|
|
696
|
+
}).sendRequest();
|
|
697
|
+
}
|
|
698
|
+
onSnapshot(callback) {
|
|
699
|
+
const genfilter = this.buildFilter();
|
|
700
|
+
const persistence = this.app.offline().persistence;
|
|
701
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
702
|
+
if (persistence && this.localStore && realtimeBridge) {
|
|
703
|
+
const targetKey = buildTargetKey(this.collection, genfilter);
|
|
704
|
+
const localUnsub = this.localStore.subscribeToCollection(targetKey, callback);
|
|
705
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
706
|
+
this.collection,
|
|
707
|
+
genfilter,
|
|
708
|
+
() => this.getLocalFilteredSnapshots()
|
|
709
|
+
);
|
|
710
|
+
this.getLocalFilteredSnapshots().then((snapshots) => {
|
|
711
|
+
realtimeBridge.primeCollectionTarget(this.collection, genfilter, snapshots);
|
|
712
|
+
callback(snapshots, "initial");
|
|
713
|
+
}).catch(console.error);
|
|
714
|
+
return () => {
|
|
715
|
+
localUnsub();
|
|
716
|
+
remoteUnsub();
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
return this.subscribeOnlineSnapshot(callback);
|
|
720
|
+
}
|
|
721
|
+
onChildListener(callbacks) {
|
|
722
|
+
const genfilter = this.buildFilter();
|
|
723
|
+
const persistence = this.app.offline().persistence;
|
|
724
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
725
|
+
if (persistence && this.localStore && realtimeBridge) {
|
|
726
|
+
const targetKey = buildTargetKey(this.collection, genfilter);
|
|
727
|
+
const localUnsub = this.localStore.subscribeToChildEvents(targetKey, callbacks);
|
|
728
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
729
|
+
this.collection,
|
|
730
|
+
genfilter,
|
|
731
|
+
() => this.getLocalFilteredSnapshots()
|
|
732
|
+
);
|
|
733
|
+
this.getLocalFilteredSnapshots().then((snapshots) => {
|
|
734
|
+
realtimeBridge.primeCollectionTarget(this.collection, genfilter, snapshots);
|
|
735
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
736
|
+
}).catch(console.error);
|
|
737
|
+
return () => {
|
|
738
|
+
localUnsub();
|
|
739
|
+
remoteUnsub();
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
return this.subscribeOnlineChildListener(callbacks);
|
|
743
|
+
}
|
|
580
744
|
buildFilter() {
|
|
581
745
|
const mongoFilter = {};
|
|
582
746
|
this.filter.forEach(({ key, op, value }) => {
|
|
@@ -647,9 +811,7 @@ var Query = class {
|
|
|
647
811
|
if (this.filter.length === 0)
|
|
648
812
|
return docs;
|
|
649
813
|
return docs.filter(
|
|
650
|
-
(snapshot) => this.filter.every(
|
|
651
|
-
(expression) => this.matchesFilter(snapshot.data, expression)
|
|
652
|
-
)
|
|
814
|
+
(snapshot) => this.filter.every((expression) => this.matchesFilter(snapshot.data, expression))
|
|
653
815
|
);
|
|
654
816
|
}
|
|
655
817
|
async getLocalFilteredSnapshots() {
|
|
@@ -659,25 +821,6 @@ var Query = class {
|
|
|
659
821
|
const docs = await persistence.getCollectionSnapshots(this.collection);
|
|
660
822
|
return this.filterDocuments(docs);
|
|
661
823
|
}
|
|
662
|
-
async get() {
|
|
663
|
-
const persistence = this.app.offline().persistence;
|
|
664
|
-
if (this.filter.length > 0 && persistence) {
|
|
665
|
-
const local = await this.getLocalFilteredSnapshots();
|
|
666
|
-
if (typeof navigator === "undefined" || !navigator.onLine) {
|
|
667
|
-
return local;
|
|
668
|
-
}
|
|
669
|
-
return this.refreshFromRemote();
|
|
670
|
-
}
|
|
671
|
-
if (persistence) {
|
|
672
|
-
const local = await persistence.getCollectionSnapshots(this.collection);
|
|
673
|
-
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
674
|
-
this.refreshFromRemote().catch(() => {
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
return local;
|
|
678
|
-
}
|
|
679
|
-
return this.refreshFromRemote();
|
|
680
|
-
}
|
|
681
824
|
async refreshFromRemote() {
|
|
682
825
|
try {
|
|
683
826
|
const genfilter = this.buildFilter();
|
|
@@ -693,69 +836,90 @@ var Query = class {
|
|
|
693
836
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
694
837
|
return [];
|
|
695
838
|
}
|
|
696
|
-
|
|
839
|
+
const snapshots = res.documents.map((d) => {
|
|
697
840
|
delete d.token;
|
|
698
841
|
d.id = d.id ?? d._id;
|
|
699
842
|
delete d._id;
|
|
700
843
|
return DocumentSnapshot.fromMap(d);
|
|
701
844
|
});
|
|
845
|
+
const persistence = this.app.offline().persistence;
|
|
846
|
+
if (!persistence) {
|
|
847
|
+
return snapshots;
|
|
848
|
+
}
|
|
849
|
+
for (const snapshot of snapshots) {
|
|
850
|
+
await persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
851
|
+
}
|
|
852
|
+
return this.getLocalFilteredSnapshots();
|
|
702
853
|
} catch {
|
|
703
854
|
return [];
|
|
704
855
|
}
|
|
705
856
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
857
|
+
subscribeOnlineSnapshot(callback) {
|
|
858
|
+
let current = [];
|
|
859
|
+
this.refreshFromRemote().then((snapshots) => {
|
|
860
|
+
current = snapshots;
|
|
861
|
+
callback(current, "initial");
|
|
862
|
+
}).catch(console.error);
|
|
863
|
+
return this.app.rtdb().subscribeToCollectionRaw(
|
|
864
|
+
this.collection,
|
|
865
|
+
(payload, change) => {
|
|
866
|
+
const source = change;
|
|
867
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
868
|
+
const next = applySnapshotChange(current, incoming, source);
|
|
869
|
+
current = this.filterDocuments(next);
|
|
870
|
+
callback(current, source, incoming.id);
|
|
871
|
+
},
|
|
872
|
+
this.buildFilter()
|
|
873
|
+
);
|
|
720
874
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
875
|
+
subscribeOnlineChildListener(callbacks) {
|
|
876
|
+
let current = [];
|
|
877
|
+
this.refreshFromRemote().then((snapshots) => {
|
|
878
|
+
current = snapshots;
|
|
879
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
880
|
+
}).catch(console.error);
|
|
881
|
+
return this.app.rtdb().subscribeToCollectionRaw(
|
|
882
|
+
this.collection,
|
|
883
|
+
(payload, change) => {
|
|
884
|
+
const source = change;
|
|
885
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
886
|
+
const next = this.filterDocuments(applySnapshotChange(current, incoming, source));
|
|
887
|
+
const childChanges = diffSnapshots(current, next);
|
|
888
|
+
current = next;
|
|
889
|
+
childChanges.forEach((childChange) => {
|
|
890
|
+
const context = {
|
|
891
|
+
snapshots: next,
|
|
892
|
+
source,
|
|
893
|
+
changedDocId: childChange.doc.id,
|
|
894
|
+
fromCache: false,
|
|
895
|
+
oldIndex: childChange.oldIndex,
|
|
896
|
+
newIndex: childChange.newIndex,
|
|
897
|
+
previousDoc: childChange.previousDoc
|
|
898
|
+
};
|
|
899
|
+
if (childChange.type === "added") {
|
|
900
|
+
callbacks.onChildAdded?.(childChange.doc, context);
|
|
901
|
+
} else if (childChange.type === "modified") {
|
|
902
|
+
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
903
|
+
} else if (childChange.type === "removed") {
|
|
904
|
+
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
740
905
|
}
|
|
741
|
-
) ?? (() => {
|
|
742
906
|
});
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
907
|
+
},
|
|
908
|
+
this.buildFilter()
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
emitInitialChildEvents(snapshots, callbacks) {
|
|
912
|
+
diffSnapshots([], snapshots).forEach((change) => {
|
|
913
|
+
callbacks.onChildAdded?.(change.doc, {
|
|
914
|
+
snapshots,
|
|
915
|
+
source: "initial",
|
|
916
|
+
changedDocId: change.doc.id,
|
|
917
|
+
fromCache: true,
|
|
918
|
+
oldIndex: change.oldIndex,
|
|
919
|
+
newIndex: change.newIndex,
|
|
920
|
+
previousDoc: change.previousDoc
|
|
749
921
|
});
|
|
750
|
-
|
|
751
|
-
localUnsub();
|
|
752
|
-
remoteUnsub?.();
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
return this.app.rtdb().subscribeToCollectionRaw(this.collection, (payload, changes) => {
|
|
756
|
-
const res = normalizePayload(payload);
|
|
757
|
-
callback([DocumentSnapshot.fromMap(res?.data)], changes);
|
|
758
|
-
}, genfilter);
|
|
922
|
+
});
|
|
759
923
|
}
|
|
760
924
|
};
|
|
761
925
|
|
|
@@ -787,6 +951,89 @@ var CollectionRef = class {
|
|
|
787
951
|
}
|
|
788
952
|
return local;
|
|
789
953
|
}
|
|
954
|
+
return this.fetchRemoteSnapshots();
|
|
955
|
+
}
|
|
956
|
+
async add(data) {
|
|
957
|
+
if (this.persistence) {
|
|
958
|
+
const localId = this.persistence.createLocalId();
|
|
959
|
+
const docRecord = await this.persistence.upsertDoc({
|
|
960
|
+
collection: this.collection,
|
|
961
|
+
id: localId,
|
|
962
|
+
data: { ...data, id: localId },
|
|
963
|
+
exists: true,
|
|
964
|
+
deleted: false,
|
|
965
|
+
pending: 1,
|
|
966
|
+
localOnly: true,
|
|
967
|
+
status: "pending"
|
|
968
|
+
});
|
|
969
|
+
await this.persistence.enqueueMutation({
|
|
970
|
+
mutationId: this.persistence.createMutationId(),
|
|
971
|
+
collection: this.collection,
|
|
972
|
+
documentId: localId,
|
|
973
|
+
type: "insert",
|
|
974
|
+
payload: docRecord.data
|
|
975
|
+
});
|
|
976
|
+
const snap = DocumentSnapshot.fromMap(docRecord.data);
|
|
977
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, localId, "insert").catch(console.error);
|
|
978
|
+
this.syncEngine?.flush().catch(console.error);
|
|
979
|
+
return snap;
|
|
980
|
+
}
|
|
981
|
+
const res = await new HttpsRequest({
|
|
982
|
+
method: "POST" /* POST */,
|
|
983
|
+
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
984
|
+
headers: {
|
|
985
|
+
authorization: this.app.getConfig().token,
|
|
986
|
+
"x-project": this.app.getConfig().project
|
|
987
|
+
},
|
|
988
|
+
body: { collection: this.collection, data: { ...data, id: "" } }
|
|
989
|
+
}).sendRequest();
|
|
990
|
+
if (!res?.success || !res.document)
|
|
991
|
+
return null;
|
|
992
|
+
return DocumentSnapshot.fromMap(res.document);
|
|
993
|
+
}
|
|
994
|
+
onSnapshot(callback) {
|
|
995
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
996
|
+
if (this.persistence && this.localStore && realtimeBridge) {
|
|
997
|
+
const targetKey = buildTargetKey(this.collection, {});
|
|
998
|
+
const localUnsub = this.localStore.subscribeToCollection(targetKey, callback);
|
|
999
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
1000
|
+
this.collection,
|
|
1001
|
+
{},
|
|
1002
|
+
() => this.persistence.getCollectionSnapshots(this.collection)
|
|
1003
|
+
);
|
|
1004
|
+
this.persistence.getCollectionSnapshots(this.collection).then((snapshots) => {
|
|
1005
|
+
realtimeBridge.primeCollectionTarget(this.collection, {}, snapshots);
|
|
1006
|
+
callback(snapshots, "insert");
|
|
1007
|
+
}).catch(console.error);
|
|
1008
|
+
return () => {
|
|
1009
|
+
localUnsub();
|
|
1010
|
+
remoteUnsub();
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
return this.subscribeOnlineSnapshot(callback);
|
|
1014
|
+
}
|
|
1015
|
+
onChildListener(callbacks) {
|
|
1016
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
1017
|
+
if (this.persistence && this.localStore && realtimeBridge) {
|
|
1018
|
+
const targetKey = buildTargetKey(this.collection, {});
|
|
1019
|
+
const localUnsub = this.localStore.subscribeToChildEvents(targetKey, callbacks);
|
|
1020
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
1021
|
+
this.collection,
|
|
1022
|
+
{},
|
|
1023
|
+
() => this.persistence.getCollectionSnapshots(this.collection)
|
|
1024
|
+
);
|
|
1025
|
+
this.persistence.getCollectionSnapshots(this.collection).then((snapshots) => {
|
|
1026
|
+
realtimeBridge.primeCollectionTarget(this.collection, {}, snapshots);
|
|
1027
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
1028
|
+
}).catch(console.error);
|
|
1029
|
+
return () => {
|
|
1030
|
+
localUnsub();
|
|
1031
|
+
remoteUnsub();
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
return this.subscribeOnlineChildListener(callbacks);
|
|
1035
|
+
}
|
|
1036
|
+
async refreshFromRemote() {
|
|
790
1037
|
try {
|
|
791
1038
|
const res = await new HttpsRequest({
|
|
792
1039
|
method: "POST" /* POST */,
|
|
@@ -803,18 +1050,25 @@ var CollectionRef = class {
|
|
|
803
1050
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
804
1051
|
return [];
|
|
805
1052
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
delete
|
|
810
|
-
return
|
|
1053
|
+
const normalized = res.documents.map((raw) => {
|
|
1054
|
+
const doc = { ...raw };
|
|
1055
|
+
doc.id = doc.id ?? doc._id;
|
|
1056
|
+
delete doc._id;
|
|
1057
|
+
return doc;
|
|
811
1058
|
});
|
|
1059
|
+
if (this.persistence) {
|
|
1060
|
+
await this.persistence.reconcileCollectionFromRemote(this.collection, normalized);
|
|
1061
|
+
const snapshots = await this.persistence.getCollectionSnapshots(this.collection);
|
|
1062
|
+
this.app.offline().realtimeBridge?.primeCollectionTarget(this.collection, {}, snapshots);
|
|
1063
|
+
return snapshots;
|
|
1064
|
+
}
|
|
1065
|
+
return normalized.map((doc) => DocumentSnapshot.fromMap(doc));
|
|
812
1066
|
} catch (error) {
|
|
813
|
-
console.error("[EdmaxLabs]
|
|
1067
|
+
console.error("[EdmaxLabs] refreshFromRemote failed:", error);
|
|
814
1068
|
return [];
|
|
815
1069
|
}
|
|
816
1070
|
}
|
|
817
|
-
async
|
|
1071
|
+
async fetchRemoteSnapshots() {
|
|
818
1072
|
try {
|
|
819
1073
|
const res = await new HttpsRequest({
|
|
820
1074
|
method: "POST" /* POST */,
|
|
@@ -831,17 +1085,6 @@ var CollectionRef = class {
|
|
|
831
1085
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
832
1086
|
return [];
|
|
833
1087
|
}
|
|
834
|
-
for (const raw of res.documents) {
|
|
835
|
-
const doc = { ...raw };
|
|
836
|
-
doc.id = doc.id ?? doc._id;
|
|
837
|
-
delete doc._id;
|
|
838
|
-
if (this.persistence) {
|
|
839
|
-
await this.persistence.applyRemoteDoc(this.collection, doc);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
if (this.persistence) {
|
|
843
|
-
return await this.persistence.getCollectionSnapshots(this.collection);
|
|
844
|
-
}
|
|
845
1088
|
return res.documents.map((d) => {
|
|
846
1089
|
delete d.token;
|
|
847
1090
|
d.id = d.id ?? d._id;
|
|
@@ -849,59 +1092,68 @@ var CollectionRef = class {
|
|
|
849
1092
|
return DocumentSnapshot.fromMap(d);
|
|
850
1093
|
});
|
|
851
1094
|
} catch (error) {
|
|
852
|
-
console.error("[EdmaxLabs]
|
|
1095
|
+
console.error("[EdmaxLabs] Collection get failed:", error);
|
|
853
1096
|
return [];
|
|
854
1097
|
}
|
|
855
1098
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
});
|
|
869
|
-
await this.persistence.enqueueMutation({
|
|
870
|
-
mutationId: this.persistence.createMutationId(),
|
|
871
|
-
collection: this.collection,
|
|
872
|
-
documentId: localId,
|
|
873
|
-
type: "insert",
|
|
874
|
-
payload: docRecord.data
|
|
875
|
-
});
|
|
876
|
-
const snap = DocumentSnapshot.fromMap(docRecord.data);
|
|
877
|
-
this.localStore?.emitDocument(this.collection, localId, snap, "insert");
|
|
878
|
-
this.localStore?.notifyCollectionChanged(this.collection, localId, "insert");
|
|
879
|
-
this.syncEngine?.flush().catch(console.error);
|
|
880
|
-
return snap;
|
|
881
|
-
}
|
|
882
|
-
const res = await new HttpsRequest({
|
|
883
|
-
method: "POST" /* POST */,
|
|
884
|
-
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
885
|
-
headers: {
|
|
886
|
-
authorization: this.app.getConfig().token,
|
|
887
|
-
"x-project": this.app.getConfig().project
|
|
888
|
-
},
|
|
889
|
-
body: { collection: this.collection, data: { ...data, id: "" } }
|
|
890
|
-
// server will generate id
|
|
891
|
-
}).sendRequest();
|
|
892
|
-
if (!res?.success || !res.document)
|
|
893
|
-
return null;
|
|
894
|
-
return DocumentSnapshot.fromMap(res.document);
|
|
1099
|
+
subscribeOnlineSnapshot(callback) {
|
|
1100
|
+
let current = [];
|
|
1101
|
+
this.fetchRemoteSnapshots().then((snapshots) => {
|
|
1102
|
+
current = snapshots;
|
|
1103
|
+
callback(current, "initial");
|
|
1104
|
+
}).catch(console.error);
|
|
1105
|
+
return this.app.rtdb().subscribeToCollectionRaw(this.collection, (payload, change) => {
|
|
1106
|
+
const source = change;
|
|
1107
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
1108
|
+
current = applySnapshotChange(current, incoming, source);
|
|
1109
|
+
callback(current, source, incoming.id);
|
|
1110
|
+
});
|
|
895
1111
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1112
|
+
subscribeOnlineChildListener(callbacks) {
|
|
1113
|
+
let current = [];
|
|
1114
|
+
this.fetchRemoteSnapshots().then((snapshots) => {
|
|
1115
|
+
current = snapshots;
|
|
1116
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
1117
|
+
}).catch(console.error);
|
|
1118
|
+
return this.app.rtdb().subscribeToCollectionRaw(this.collection, (payload, change) => {
|
|
1119
|
+
const source = change;
|
|
1120
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
1121
|
+
const next = applySnapshotChange(current, incoming, source);
|
|
1122
|
+
const childChanges = diffSnapshots(current, next);
|
|
1123
|
+
current = next;
|
|
1124
|
+
childChanges.forEach((childChange) => {
|
|
1125
|
+
const context = {
|
|
1126
|
+
snapshots: next,
|
|
1127
|
+
source,
|
|
1128
|
+
changedDocId: childChange.doc.id,
|
|
1129
|
+
fromCache: false,
|
|
1130
|
+
oldIndex: childChange.oldIndex,
|
|
1131
|
+
newIndex: childChange.newIndex,
|
|
1132
|
+
previousDoc: childChange.previousDoc
|
|
1133
|
+
};
|
|
1134
|
+
if (childChange.type === "added") {
|
|
1135
|
+
callbacks.onChildAdded?.(childChange.doc, context);
|
|
1136
|
+
} else if (childChange.type === "modified") {
|
|
1137
|
+
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
1138
|
+
} else if (childChange.type === "removed") {
|
|
1139
|
+
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
1140
|
+
}
|
|
900
1141
|
});
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
emitInitialChildEvents(snapshots, callbacks) {
|
|
1145
|
+
const childChanges = diffSnapshots([], snapshots);
|
|
1146
|
+
childChanges.forEach((change) => {
|
|
1147
|
+
const context = {
|
|
1148
|
+
snapshots,
|
|
1149
|
+
source: "initial",
|
|
1150
|
+
changedDocId: change.doc.id,
|
|
1151
|
+
fromCache: true,
|
|
1152
|
+
oldIndex: change.oldIndex,
|
|
1153
|
+
newIndex: change.newIndex,
|
|
1154
|
+
previousDoc: change.previousDoc
|
|
1155
|
+
};
|
|
1156
|
+
callbacks.onChildAdded?.(change.doc, context);
|
|
905
1157
|
});
|
|
906
1158
|
}
|
|
907
1159
|
};
|
|
@@ -951,6 +1203,7 @@ var Batch = class {
|
|
|
951
1203
|
const persistence = this.app.offline().persistence;
|
|
952
1204
|
const localStore = this.app.offline().localStore;
|
|
953
1205
|
const syncEngine = this.app.offline().syncEngine;
|
|
1206
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
954
1207
|
if (persistence && localStore) {
|
|
955
1208
|
const results = [];
|
|
956
1209
|
for (const op of this.ops) {
|
|
@@ -979,8 +1232,7 @@ var Batch = class {
|
|
|
979
1232
|
payload: op.data
|
|
980
1233
|
});
|
|
981
1234
|
const snap = DocumentSnapshot.fromMap(upserted.data);
|
|
982
|
-
|
|
983
|
-
localStore.notifyCollectionChanged(op.collection, op.id, "insert");
|
|
1235
|
+
realtimeBridge?.publishLocalChange(op.collection, op.id, "insert").catch(console.error);
|
|
984
1236
|
results.push(snap);
|
|
985
1237
|
} else if (op.op === "delete") {
|
|
986
1238
|
const docRef = new DocumentRef(this.app, op.collection, op.id);
|
|
@@ -1092,6 +1344,20 @@ function generateUUID() {
|
|
|
1092
1344
|
});
|
|
1093
1345
|
}
|
|
1094
1346
|
|
|
1347
|
+
// src/utils/documentNomalizer.ts
|
|
1348
|
+
function normalizePayload(payload) {
|
|
1349
|
+
const raw = payload?.document ?? payload?.data ?? payload;
|
|
1350
|
+
if (!raw)
|
|
1351
|
+
return null;
|
|
1352
|
+
const doc = { ...raw };
|
|
1353
|
+
doc.id = doc.id ?? raw?.id ?? raw?._id ?? payload?.id ?? payload?._id;
|
|
1354
|
+
delete doc._id;
|
|
1355
|
+
return {
|
|
1356
|
+
change: payload?.change ?? raw?.change ?? null,
|
|
1357
|
+
data: doc
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1095
1361
|
// src/database/Realtime.ts
|
|
1096
1362
|
var Realtime = class {
|
|
1097
1363
|
constructor(app) {
|
|
@@ -1160,8 +1426,12 @@ var Realtime = class {
|
|
|
1160
1426
|
const channel = `${lid}-${event}`;
|
|
1161
1427
|
const fn = (payload) => {
|
|
1162
1428
|
const normalized = normalizePayload(payload);
|
|
1163
|
-
if (!normalized)
|
|
1429
|
+
if (!normalized) {
|
|
1430
|
+
if (event === "delete") {
|
|
1431
|
+
callback(payload, "delete");
|
|
1432
|
+
}
|
|
1164
1433
|
return;
|
|
1434
|
+
}
|
|
1165
1435
|
callback(normalized.data, event);
|
|
1166
1436
|
};
|
|
1167
1437
|
this.socket.on(channel, fn);
|
|
@@ -1285,11 +1555,11 @@ var LocalStore = class {
|
|
|
1285
1555
|
constructor() {
|
|
1286
1556
|
this.documentListeners = /* @__PURE__ */ new Map();
|
|
1287
1557
|
this.collectionListeners = /* @__PURE__ */ new Map();
|
|
1558
|
+
this.childListeners = /* @__PURE__ */ new Map();
|
|
1288
1559
|
}
|
|
1289
1560
|
docKey(collection, id) {
|
|
1290
1561
|
return `${collection}:${id}`;
|
|
1291
1562
|
}
|
|
1292
|
-
// ===================== DOCUMENT LISTENERS =====================
|
|
1293
1563
|
subscribeToDocument(collection, id, callback) {
|
|
1294
1564
|
const key = this.docKey(collection, id);
|
|
1295
1565
|
if (!this.documentListeners.has(key)) {
|
|
@@ -1304,24 +1574,33 @@ var LocalStore = class {
|
|
|
1304
1574
|
}
|
|
1305
1575
|
};
|
|
1306
1576
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
this.collectionListeners.set(collection, /* @__PURE__ */ new Set());
|
|
1577
|
+
subscribeToCollection(targetKey, callback) {
|
|
1578
|
+
if (!this.collectionListeners.has(targetKey)) {
|
|
1579
|
+
this.collectionListeners.set(targetKey, /* @__PURE__ */ new Set());
|
|
1311
1580
|
}
|
|
1312
|
-
const listeners = this.collectionListeners.get(
|
|
1581
|
+
const listeners = this.collectionListeners.get(targetKey);
|
|
1313
1582
|
listeners.add(callback);
|
|
1314
1583
|
return () => {
|
|
1315
1584
|
listeners.delete(callback);
|
|
1316
1585
|
if (listeners.size === 0) {
|
|
1317
|
-
this.collectionListeners.delete(
|
|
1586
|
+
this.collectionListeners.delete(targetKey);
|
|
1587
|
+
this.childListeners.delete(targetKey);
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
subscribeToChildEvents(targetKey, callbacks) {
|
|
1592
|
+
if (!this.childListeners.has(targetKey)) {
|
|
1593
|
+
this.childListeners.set(targetKey, /* @__PURE__ */ new Set());
|
|
1594
|
+
}
|
|
1595
|
+
const listeners = this.childListeners.get(targetKey);
|
|
1596
|
+
listeners.add(callbacks);
|
|
1597
|
+
return () => {
|
|
1598
|
+
listeners.delete(callbacks);
|
|
1599
|
+
if (listeners.size === 0) {
|
|
1600
|
+
this.childListeners.delete(targetKey);
|
|
1318
1601
|
}
|
|
1319
1602
|
};
|
|
1320
1603
|
}
|
|
1321
|
-
// ===================== EMITTERS =====================
|
|
1322
|
-
/**
|
|
1323
|
-
* Notify all listeners for a specific document
|
|
1324
|
-
*/
|
|
1325
1604
|
emitDocument(collection, id, snapshot, change) {
|
|
1326
1605
|
const key = this.docKey(collection, id);
|
|
1327
1606
|
this.documentListeners.get(key)?.forEach((cb) => {
|
|
@@ -1332,64 +1611,66 @@ var LocalStore = class {
|
|
|
1332
1611
|
}
|
|
1333
1612
|
});
|
|
1334
1613
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
* This is the most important fix — collection listeners now receive the full list.
|
|
1338
|
-
*/
|
|
1339
|
-
emitCollection(collection, snapshots, change, changedDocId) {
|
|
1340
|
-
this.collectionListeners.get(collection)?.forEach((cb) => {
|
|
1614
|
+
emitCollection(targetKey, emission) {
|
|
1615
|
+
this.collectionListeners.get(targetKey)?.forEach((cb) => {
|
|
1341
1616
|
try {
|
|
1342
|
-
cb(snapshots,
|
|
1617
|
+
cb(emission.snapshots, emission.source, emission.changedDocId);
|
|
1343
1618
|
} catch (err) {
|
|
1344
|
-
console.error(`[EdmaxLabs] Error in collection listener for ${
|
|
1619
|
+
console.error(`[EdmaxLabs] Error in collection listener for ${targetKey}:`, err);
|
|
1345
1620
|
}
|
|
1346
1621
|
});
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
this.collectionListeners.get(collection)?.forEach((cb) => {
|
|
1355
|
-
try {
|
|
1356
|
-
cb([], change, changedDocId);
|
|
1357
|
-
} catch (err) {
|
|
1358
|
-
console.error(`[EdmaxLabs] Error in collection listener for ${collection}:`, err);
|
|
1359
|
-
}
|
|
1622
|
+
const childChanges = emission.childChanges ?? [];
|
|
1623
|
+
if (childChanges.length === 0)
|
|
1624
|
+
return;
|
|
1625
|
+
this.childListeners.get(targetKey)?.forEach((callbacks) => {
|
|
1626
|
+
childChanges.forEach((change) => {
|
|
1627
|
+
this.dispatchChildEvent(callbacks, change, emission);
|
|
1628
|
+
});
|
|
1360
1629
|
});
|
|
1361
1630
|
}
|
|
1362
|
-
// ===================== UTILITY =====================
|
|
1363
|
-
/**
|
|
1364
|
-
* Clear all listeners (useful for testing or when persistence is disabled)
|
|
1365
|
-
*/
|
|
1366
1631
|
clearAllListeners() {
|
|
1367
1632
|
this.documentListeners.clear();
|
|
1368
1633
|
this.collectionListeners.clear();
|
|
1634
|
+
this.childListeners.clear();
|
|
1369
1635
|
}
|
|
1370
|
-
/**
|
|
1371
|
-
* Get current listener count (for debugging / dev tools)
|
|
1372
|
-
*/
|
|
1373
1636
|
get listenerCount() {
|
|
1374
1637
|
let docCount = 0;
|
|
1375
1638
|
this.documentListeners.forEach((set) => docCount += set.size);
|
|
1376
1639
|
let collCount = 0;
|
|
1377
1640
|
this.collectionListeners.forEach((set) => collCount += set.size);
|
|
1641
|
+
this.childListeners.forEach((set) => collCount += set.size);
|
|
1378
1642
|
return { documents: docCount, collections: collCount };
|
|
1379
1643
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
removeCollectionListeners(collection) {
|
|
1384
|
-
this.collectionListeners.delete(collection);
|
|
1644
|
+
removeCollectionListeners(targetKey) {
|
|
1645
|
+
this.collectionListeners.delete(targetKey);
|
|
1646
|
+
this.childListeners.delete(targetKey);
|
|
1385
1647
|
}
|
|
1386
|
-
/**
|
|
1387
|
-
* Remove all listeners for a specific document (useful for cleanup)
|
|
1388
|
-
*/
|
|
1389
1648
|
removeDocumentListeners(collection, id) {
|
|
1390
1649
|
const key = this.docKey(collection, id);
|
|
1391
1650
|
this.documentListeners.delete(key);
|
|
1392
1651
|
}
|
|
1652
|
+
dispatchChildEvent(callbacks, change, emission) {
|
|
1653
|
+
const context = {
|
|
1654
|
+
snapshots: emission.snapshots,
|
|
1655
|
+
source: emission.source,
|
|
1656
|
+
changedDocId: emission.changedDocId,
|
|
1657
|
+
fromCache: emission.fromCache ?? true,
|
|
1658
|
+
oldIndex: change.oldIndex,
|
|
1659
|
+
newIndex: change.newIndex,
|
|
1660
|
+
previousDoc: change.previousDoc
|
|
1661
|
+
};
|
|
1662
|
+
try {
|
|
1663
|
+
if (change.type === "added") {
|
|
1664
|
+
callbacks.onChildAdded?.(change.doc, context);
|
|
1665
|
+
} else if (change.type === "modified") {
|
|
1666
|
+
callbacks.onChildUpdated?.(change.doc, context);
|
|
1667
|
+
} else if (change.type === "removed") {
|
|
1668
|
+
callbacks.onChildRemoved?.(change.doc, context);
|
|
1669
|
+
}
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
console.error("[EdmaxLabs] Error in child listener:", err);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1393
1674
|
};
|
|
1394
1675
|
|
|
1395
1676
|
// ../../../../node_modules/idb/build/wrap-idb-value.js
|
|
@@ -1760,11 +2041,25 @@ var Persistence = class {
|
|
|
1760
2041
|
}
|
|
1761
2042
|
async getPendingMutations() {
|
|
1762
2043
|
const app = await this.getDb();
|
|
1763
|
-
const
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2044
|
+
const pending = await app.getAllFromIndex("mutations", "by-status", "pending");
|
|
2045
|
+
return pending.sort((a, b) => a.createdAt - b.createdAt);
|
|
2046
|
+
}
|
|
2047
|
+
async getFailedMutations() {
|
|
2048
|
+
const app = await this.getDb();
|
|
2049
|
+
const failed = await app.getAllFromIndex("mutations", "by-status", "failed");
|
|
2050
|
+
return failed.sort((a, b) => a.createdAt - b.createdAt);
|
|
2051
|
+
}
|
|
2052
|
+
async recoverSyncingMutations() {
|
|
2053
|
+
const app = await this.getDb();
|
|
2054
|
+
const syncing = await app.getAllFromIndex("mutations", "by-status", "syncing");
|
|
2055
|
+
for (const mutation of syncing) {
|
|
2056
|
+
await app.put("mutations", {
|
|
2057
|
+
...mutation,
|
|
2058
|
+
status: "pending",
|
|
2059
|
+
updatedAt: this.now()
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
return syncing.length;
|
|
1768
2063
|
}
|
|
1769
2064
|
async getMutation(mutationId) {
|
|
1770
2065
|
const app = await this.getDb();
|
|
@@ -1869,6 +2164,27 @@ var Persistence = class {
|
|
|
1869
2164
|
}
|
|
1870
2165
|
return this.markDeleted(collection, id, 0);
|
|
1871
2166
|
}
|
|
2167
|
+
async reconcileCollectionFromRemote(collection, documents) {
|
|
2168
|
+
const app = await this.getDb();
|
|
2169
|
+
const existing = await app.getAllFromIndex("docs", "by-collection", collection);
|
|
2170
|
+
const incomingIds = /* @__PURE__ */ new Set();
|
|
2171
|
+
for (const data of documents) {
|
|
2172
|
+
const id = data.id ?? data._id;
|
|
2173
|
+
if (!id)
|
|
2174
|
+
continue;
|
|
2175
|
+
incomingIds.add(String(id));
|
|
2176
|
+
await this.applyRemoteDoc(collection, { ...data, id: String(id) });
|
|
2177
|
+
}
|
|
2178
|
+
for (const doc of existing) {
|
|
2179
|
+
if (doc.pending && doc.pending > 0)
|
|
2180
|
+
continue;
|
|
2181
|
+
if (!doc.exists || doc.deleted)
|
|
2182
|
+
continue;
|
|
2183
|
+
if (incomingIds.has(doc.id))
|
|
2184
|
+
continue;
|
|
2185
|
+
await this.markDeleted(collection, doc.id, 0);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
1872
2188
|
// ==================== UTILITIES ====================
|
|
1873
2189
|
createLocalId() {
|
|
1874
2190
|
return `local_${crypto.randomUUID()}`;
|
|
@@ -1898,112 +2214,169 @@ var RealtimeBridge = class {
|
|
|
1898
2214
|
this.app = app;
|
|
1899
2215
|
this.persistence = persistence;
|
|
1900
2216
|
this.store = store;
|
|
1901
|
-
this.
|
|
1902
|
-
this.
|
|
2217
|
+
this.collectionTargets = /* @__PURE__ */ new Map();
|
|
2218
|
+
this.documentTargets = /* @__PURE__ */ new Map();
|
|
1903
2219
|
}
|
|
1904
2220
|
docKey(collection, id) {
|
|
1905
2221
|
return `${collection}:${id}`;
|
|
1906
2222
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
const
|
|
1910
|
-
if (
|
|
1911
|
-
|
|
2223
|
+
watchCollection(collection, filter = {}, read) {
|
|
2224
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2225
|
+
const existing = this.collectionTargets.get(targetKey);
|
|
2226
|
+
if (existing) {
|
|
2227
|
+
existing.refCount += 1;
|
|
2228
|
+
return () => this.releaseCollection(targetKey);
|
|
1912
2229
|
}
|
|
1913
|
-
this.emitCurrentCollection(collection);
|
|
1914
2230
|
const unsub = this.app.rtdb().subscribeToCollectionRaw(
|
|
1915
2231
|
collection,
|
|
1916
2232
|
async (payload, change) => {
|
|
1917
2233
|
if (change === "delete") {
|
|
1918
2234
|
const id = payload?.id || payload?._id;
|
|
1919
|
-
if (id)
|
|
2235
|
+
if (id) {
|
|
1920
2236
|
await this.handleRemoteDelete(collection, id);
|
|
1921
|
-
|
|
1922
|
-
|
|
2237
|
+
}
|
|
2238
|
+
return;
|
|
1923
2239
|
}
|
|
2240
|
+
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
1924
2241
|
},
|
|
1925
2242
|
filter
|
|
1926
2243
|
);
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2244
|
+
this.collectionTargets.set(targetKey, {
|
|
2245
|
+
targetKey,
|
|
2246
|
+
collection,
|
|
2247
|
+
filter,
|
|
2248
|
+
read,
|
|
2249
|
+
refCount: 1,
|
|
2250
|
+
unsub,
|
|
2251
|
+
lastSnapshots: []
|
|
2252
|
+
});
|
|
2253
|
+
return () => this.releaseCollection(targetKey);
|
|
1933
2254
|
}
|
|
1934
2255
|
watchDocument(collection, id) {
|
|
1935
2256
|
const key = this.docKey(collection, id);
|
|
1936
|
-
|
|
1937
|
-
|
|
2257
|
+
const existing = this.documentTargets.get(key);
|
|
2258
|
+
if (existing) {
|
|
2259
|
+
existing.refCount += 1;
|
|
2260
|
+
return () => this.releaseDocument(key);
|
|
1938
2261
|
}
|
|
1939
|
-
this.emitCurrentDocument(collection, id);
|
|
1940
2262
|
const unsub = this.app.rtdb().subscribeToDocumentRaw(
|
|
1941
2263
|
collection,
|
|
1942
2264
|
id,
|
|
1943
2265
|
async (payload, change) => {
|
|
1944
2266
|
if (change === "delete") {
|
|
1945
2267
|
await this.handleRemoteDelete(collection, id);
|
|
1946
|
-
|
|
1947
|
-
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
2268
|
+
return;
|
|
1948
2269
|
}
|
|
2270
|
+
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
1949
2271
|
}
|
|
1950
2272
|
);
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2273
|
+
this.documentTargets.set(key, {
|
|
2274
|
+
key,
|
|
2275
|
+
collection,
|
|
2276
|
+
id,
|
|
2277
|
+
refCount: 1,
|
|
2278
|
+
unsub
|
|
2279
|
+
});
|
|
2280
|
+
return () => this.releaseDocument(key);
|
|
2281
|
+
}
|
|
2282
|
+
async emitCurrentCollection(collection, filter = {}) {
|
|
2283
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2284
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2285
|
+
if (!target)
|
|
2286
|
+
return [];
|
|
2287
|
+
return this.refreshCollectionTarget(target, "initial");
|
|
2288
|
+
}
|
|
2289
|
+
primeCollectionTarget(collection, filter, snapshots) {
|
|
2290
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2291
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2292
|
+
if (!target)
|
|
2293
|
+
return;
|
|
2294
|
+
target.lastSnapshots = snapshots;
|
|
1957
2295
|
}
|
|
1958
|
-
// ===================== INTERNAL HANDLERS =====================
|
|
1959
2296
|
async emitCurrentDocument(collection, id) {
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
2297
|
+
const snapshot = await this.getCurrentDocumentSnapshot(collection, id);
|
|
2298
|
+
this.store.emitDocument(collection, id, snapshot, "initial");
|
|
2299
|
+
return snapshot;
|
|
1963
2300
|
}
|
|
1964
|
-
async
|
|
1965
|
-
const
|
|
1966
|
-
this.store.
|
|
2301
|
+
async publishLocalChange(collection, id, source) {
|
|
2302
|
+
const snapshot = await this.getCurrentDocumentSnapshot(collection, id);
|
|
2303
|
+
this.store.emitDocument(collection, id, snapshot, source);
|
|
2304
|
+
await this.refreshCollectionTargets(collection, source, id);
|
|
2305
|
+
}
|
|
2306
|
+
async onReconnect() {
|
|
2307
|
+
for (const target of this.collectionTargets.values()) {
|
|
2308
|
+
await this.refreshCollectionTarget(target, "initial");
|
|
2309
|
+
}
|
|
2310
|
+
for (const target of this.documentTargets.values()) {
|
|
2311
|
+
await this.emitCurrentDocument(target.collection, target.id);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
dispose() {
|
|
2315
|
+
this.collectionTargets.forEach((target) => target.unsub());
|
|
2316
|
+
this.documentTargets.forEach((target) => target.unsub());
|
|
2317
|
+
this.collectionTargets.clear();
|
|
2318
|
+
this.documentTargets.clear();
|
|
1967
2319
|
}
|
|
1968
|
-
async handleRemoteCreateOrUpdate(collection, raw,
|
|
2320
|
+
async handleRemoteCreateOrUpdate(collection, raw, source) {
|
|
1969
2321
|
const id = raw.id ?? raw._id;
|
|
1970
2322
|
if (!id)
|
|
1971
2323
|
return;
|
|
1972
2324
|
const saved = await this.persistence.applyRemoteDoc(collection, raw);
|
|
1973
|
-
if (
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
this.
|
|
2325
|
+
if (saved) {
|
|
2326
|
+
const snap = DocumentSnapshot.fromMap(saved.data);
|
|
2327
|
+
this.store.emitDocument(collection, id, snap, source);
|
|
2328
|
+
}
|
|
2329
|
+
await this.refreshCollectionTargets(collection, source, id, false);
|
|
1978
2330
|
}
|
|
1979
2331
|
async handleRemoteDelete(collection, id) {
|
|
1980
2332
|
await this.persistence.applyRemoteDelete(collection, id);
|
|
1981
2333
|
this.store.emitDocument(collection, id, null, "delete");
|
|
1982
|
-
this.
|
|
2334
|
+
await this.refreshCollectionTargets(collection, "delete", id, false);
|
|
1983
2335
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
* Replays pending mutations + refreshes all active subscriptions from cache
|
|
1988
|
-
*/
|
|
1989
|
-
async onReconnect() {
|
|
1990
|
-
for (const [key] of this.collectionUnsubs) {
|
|
1991
|
-
const collection = key.split(":")[0];
|
|
1992
|
-
await this.emitCurrentCollection(collection);
|
|
1993
|
-
}
|
|
1994
|
-
for (const [key] of this.documentUnsubs) {
|
|
1995
|
-
const [collection, id] = key.split(":");
|
|
1996
|
-
await this.emitCurrentDocument(collection, id);
|
|
1997
|
-
}
|
|
2336
|
+
async getCurrentDocumentSnapshot(collection, id) {
|
|
2337
|
+
const localDoc = await this.persistence.getDoc(collection, id);
|
|
2338
|
+
return localDoc && localDoc.exists && !localDoc.deleted ? DocumentSnapshot.fromMap(localDoc.data) : null;
|
|
1998
2339
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2340
|
+
async refreshCollectionTargets(collection, source, changedDocId, fromCache = true) {
|
|
2341
|
+
const targets = Array.from(this.collectionTargets.values()).filter(
|
|
2342
|
+
(target) => target.collection === collection
|
|
2343
|
+
);
|
|
2344
|
+
for (const target of targets) {
|
|
2345
|
+
await this.refreshCollectionTarget(target, source, changedDocId, fromCache);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
async refreshCollectionTarget(target, source, changedDocId, fromCache = true) {
|
|
2349
|
+
const snapshots = await target.read();
|
|
2350
|
+
const childChanges = diffSnapshots(target.lastSnapshots, snapshots);
|
|
2351
|
+
target.lastSnapshots = snapshots;
|
|
2352
|
+
this.store.emitCollection(target.targetKey, {
|
|
2353
|
+
snapshots,
|
|
2354
|
+
source,
|
|
2355
|
+
changedDocId,
|
|
2356
|
+
fromCache,
|
|
2357
|
+
childChanges
|
|
2358
|
+
});
|
|
2359
|
+
return snapshots;
|
|
2360
|
+
}
|
|
2361
|
+
releaseCollection(targetKey) {
|
|
2362
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2363
|
+
if (!target)
|
|
2364
|
+
return;
|
|
2365
|
+
target.refCount -= 1;
|
|
2366
|
+
if (target.refCount > 0)
|
|
2367
|
+
return;
|
|
2368
|
+
target.unsub();
|
|
2369
|
+
this.collectionTargets.delete(targetKey);
|
|
2370
|
+
}
|
|
2371
|
+
releaseDocument(key) {
|
|
2372
|
+
const target = this.documentTargets.get(key);
|
|
2373
|
+
if (!target)
|
|
2374
|
+
return;
|
|
2375
|
+
target.refCount -= 1;
|
|
2376
|
+
if (target.refCount > 0)
|
|
2377
|
+
return;
|
|
2378
|
+
target.unsub();
|
|
2379
|
+
this.documentTargets.delete(key);
|
|
2007
2380
|
}
|
|
2008
2381
|
};
|
|
2009
2382
|
|
|
@@ -2015,16 +2388,19 @@ var SyncEngine = class {
|
|
|
2015
2388
|
this.retryTimeout = null;
|
|
2016
2389
|
this.MAX_RETRIES = 5;
|
|
2017
2390
|
this.BASE_RETRY_DELAY = 2e3;
|
|
2391
|
+
this.handleOnline = () => this.onNetworkOnline();
|
|
2392
|
+
this.handleVisibilityChange = () => {
|
|
2393
|
+
if (document.visibilityState === "visible")
|
|
2394
|
+
this.onNetworkOnline();
|
|
2395
|
+
};
|
|
2018
2396
|
this.app = app;
|
|
2019
2397
|
this.persistence = persistence;
|
|
2020
2398
|
this.store = store;
|
|
2021
2399
|
this.realtimeBridge = realtimeBridge || null;
|
|
2400
|
+
this.persistence.recoverSyncingMutations().catch(console.error);
|
|
2022
2401
|
if (typeof window !== "undefined") {
|
|
2023
|
-
window.addEventListener("online",
|
|
2024
|
-
document.addEventListener("visibilitychange",
|
|
2025
|
-
if (document.visibilityState === "visible")
|
|
2026
|
-
this.onNetworkOnline();
|
|
2027
|
-
});
|
|
2402
|
+
window.addEventListener("online", this.handleOnline);
|
|
2403
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2028
2404
|
}
|
|
2029
2405
|
}
|
|
2030
2406
|
onNetworkOnline() {
|
|
@@ -2041,6 +2417,7 @@ var SyncEngine = class {
|
|
|
2041
2417
|
return;
|
|
2042
2418
|
this.syncing = true;
|
|
2043
2419
|
try {
|
|
2420
|
+
await this.persistence.recoverSyncingMutations();
|
|
2044
2421
|
const pending = await this.persistence.getPendingMutations();
|
|
2045
2422
|
for (const mutation of pending) {
|
|
2046
2423
|
if (mutation.retryCount >= this.MAX_RETRIES) {
|
|
@@ -2117,8 +2494,7 @@ var SyncEngine = class {
|
|
|
2117
2494
|
if (replaced) {
|
|
2118
2495
|
const snap = DocumentSnapshot.fromMap(replaced.data);
|
|
2119
2496
|
this.store.emitDocument(mutation.collection, oldId, snap, "insert");
|
|
2120
|
-
this.
|
|
2121
|
-
this.store.notifyCollectionChanged(mutation.collection, newId, "insert");
|
|
2497
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, newId, "insert");
|
|
2122
2498
|
}
|
|
2123
2499
|
return true;
|
|
2124
2500
|
}
|
|
@@ -2151,7 +2527,7 @@ var SyncEngine = class {
|
|
|
2151
2527
|
});
|
|
2152
2528
|
const snap = DocumentSnapshot.fromMap(local.data);
|
|
2153
2529
|
this.store.emitDocument(mutation.collection, mutation.documentId, snap, "update");
|
|
2154
|
-
this.
|
|
2530
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, mutation.documentId, "update");
|
|
2155
2531
|
return true;
|
|
2156
2532
|
}
|
|
2157
2533
|
async syncDelete(mutation) {
|
|
@@ -2168,7 +2544,7 @@ var SyncEngine = class {
|
|
|
2168
2544
|
return false;
|
|
2169
2545
|
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
2170
2546
|
this.store.emitDocument(mutation.collection, mutation.documentId, null, "delete");
|
|
2171
|
-
this.
|
|
2547
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, mutation.documentId, "delete");
|
|
2172
2548
|
return true;
|
|
2173
2549
|
}
|
|
2174
2550
|
scheduleRetry() {
|
|
@@ -2188,8 +2564,7 @@ var SyncEngine = class {
|
|
|
2188
2564
|
* Returns mutations that exceeded MAX_RETRIES
|
|
2189
2565
|
*/
|
|
2190
2566
|
async getFailedMutations() {
|
|
2191
|
-
|
|
2192
|
-
return all.filter((m) => m.status === "failed");
|
|
2567
|
+
return this.persistence.getFailedMutations();
|
|
2193
2568
|
}
|
|
2194
2569
|
/**
|
|
2195
2570
|
* Retry a specific failed mutation by resetting its retry count
|
|
@@ -2231,6 +2606,10 @@ var SyncEngine = class {
|
|
|
2231
2606
|
if (this.retryTimeout) {
|
|
2232
2607
|
clearTimeout(this.retryTimeout);
|
|
2233
2608
|
}
|
|
2609
|
+
if (typeof window !== "undefined") {
|
|
2610
|
+
window.removeEventListener("online", this.handleOnline);
|
|
2611
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2612
|
+
}
|
|
2234
2613
|
}
|
|
2235
2614
|
};
|
|
2236
2615
|
|