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.cjs
CHANGED
|
@@ -332,7 +332,6 @@ var _Authentication = class _Authentication {
|
|
|
332
332
|
return;
|
|
333
333
|
userDocUnsubscribe = userRef.onSnapshot(
|
|
334
334
|
(snapshot, change) => {
|
|
335
|
-
console.log(snapshot);
|
|
336
335
|
if (change === "delete") {
|
|
337
336
|
this.saveCredentials(null);
|
|
338
337
|
onSignOut?.();
|
|
@@ -392,20 +391,6 @@ var DocumentSnapshot = class _DocumentSnapshot {
|
|
|
392
391
|
}
|
|
393
392
|
};
|
|
394
393
|
|
|
395
|
-
// src/utils/documentNomalizer.ts
|
|
396
|
-
function normalizePayload(payload) {
|
|
397
|
-
const raw = payload?.document ?? payload?.data ?? payload;
|
|
398
|
-
if (!raw)
|
|
399
|
-
return null;
|
|
400
|
-
const doc = { ...raw };
|
|
401
|
-
doc.id = payload.id ?? payload._id;
|
|
402
|
-
delete doc._id;
|
|
403
|
-
return {
|
|
404
|
-
change: payload?.change ?? raw?.change ?? null,
|
|
405
|
-
data: doc
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
394
|
// src/database/DocumentRef.ts
|
|
410
395
|
function validateDocumentData(data, operation) {
|
|
411
396
|
if (data === null || data === void 0) {
|
|
@@ -431,40 +416,22 @@ var DocumentRef = class {
|
|
|
431
416
|
this.syncEngine = app.offline().syncEngine;
|
|
432
417
|
this.localStore = app.offline().localStore;
|
|
433
418
|
}
|
|
434
|
-
// ====================== GET ======================
|
|
435
419
|
async get() {
|
|
436
420
|
if (this.persistence) {
|
|
437
421
|
try {
|
|
438
422
|
const localSnap = await this.persistence.getDocSnapshot(this.collection, this.id);
|
|
423
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
424
|
+
this.refreshFromRemote().catch(() => {
|
|
425
|
+
});
|
|
426
|
+
}
|
|
439
427
|
if (localSnap)
|
|
440
428
|
return localSnap;
|
|
441
429
|
} catch (error) {
|
|
442
430
|
console.error("[EdmaxLabs] Cache read error:", error);
|
|
443
431
|
}
|
|
444
432
|
}
|
|
445
|
-
|
|
446
|
-
const res = await new HttpsRequest({
|
|
447
|
-
method: "POST" /* POST */,
|
|
448
|
-
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
449
|
-
headers: {
|
|
450
|
-
authorization: this.app.getConfig().token,
|
|
451
|
-
"x-project": this.app.getConfig().project
|
|
452
|
-
},
|
|
453
|
-
body: {
|
|
454
|
-
collection: this.collection,
|
|
455
|
-
id: this.id,
|
|
456
|
-
single: true
|
|
457
|
-
}
|
|
458
|
-
}).sendRequest();
|
|
459
|
-
if (!res?.success || !res.document)
|
|
460
|
-
return null;
|
|
461
|
-
return DocumentSnapshot.fromMap(res.document);
|
|
462
|
-
} catch (error) {
|
|
463
|
-
console.error(`[DocumentRef] get(${this.collection}/${this.id}) failed:`, error);
|
|
464
|
-
return null;
|
|
465
|
-
}
|
|
433
|
+
return this.fetchRemoteSnapshot();
|
|
466
434
|
}
|
|
467
|
-
// ====================== UPDATE ======================
|
|
468
435
|
async update(data) {
|
|
469
436
|
if (this._isUpdating) {
|
|
470
437
|
console.warn(`[DocumentRef] update recursion blocked on ${this.collection}/${this.id}`);
|
|
@@ -510,15 +477,13 @@ var DocumentRef = class {
|
|
|
510
477
|
baseRevision: old.revision
|
|
511
478
|
});
|
|
512
479
|
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
513
|
-
this.
|
|
514
|
-
this.localStore?.notifyCollectionChanged(this.collection, this.id, "update");
|
|
480
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "update").catch(console.error);
|
|
515
481
|
this.syncEngine?.flush().catch(console.error);
|
|
516
482
|
return snap;
|
|
517
483
|
} finally {
|
|
518
484
|
this._isUpdating = false;
|
|
519
485
|
}
|
|
520
486
|
}
|
|
521
|
-
// ====================== SET ======================
|
|
522
487
|
async set(data) {
|
|
523
488
|
validateDocumentData(data, "DocumentRef.set");
|
|
524
489
|
if (!this.persistence) {
|
|
@@ -533,6 +498,8 @@ var DocumentRef = class {
|
|
|
533
498
|
}).sendRequest();
|
|
534
499
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
535
500
|
}
|
|
501
|
+
const existing = await this.persistence.getDoc(this.collection, this.id);
|
|
502
|
+
const mutationType = existing?.exists && !existing.deleted ? "update" : "insert";
|
|
536
503
|
const updated = await this.persistence.upsertDoc({
|
|
537
504
|
collection: this.collection,
|
|
538
505
|
id: this.id,
|
|
@@ -541,22 +508,27 @@ var DocumentRef = class {
|
|
|
541
508
|
deleted: false,
|
|
542
509
|
pending: 1,
|
|
543
510
|
localOnly: false,
|
|
544
|
-
status: "pending"
|
|
511
|
+
status: "pending",
|
|
512
|
+
revision: existing?.revision,
|
|
513
|
+
lastSyncedAt: existing?.lastSyncedAt
|
|
545
514
|
});
|
|
546
515
|
await this.persistence.enqueueMutation({
|
|
547
516
|
mutationId: this.persistence.createMutationId(),
|
|
548
517
|
collection: this.collection,
|
|
549
518
|
documentId: this.id,
|
|
550
|
-
type:
|
|
551
|
-
payload: data
|
|
519
|
+
type: mutationType,
|
|
520
|
+
payload: data,
|
|
521
|
+
baseRevision: existing?.revision
|
|
552
522
|
});
|
|
553
523
|
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
554
|
-
this.
|
|
555
|
-
|
|
524
|
+
this.app.offline().realtimeBridge?.publishLocalChange(
|
|
525
|
+
this.collection,
|
|
526
|
+
this.id,
|
|
527
|
+
mutationType === "insert" ? "insert" : "update"
|
|
528
|
+
).catch(console.error);
|
|
556
529
|
this.syncEngine?.flush().catch(console.error);
|
|
557
530
|
return snap;
|
|
558
531
|
}
|
|
559
|
-
// ====================== DELETE ======================
|
|
560
532
|
async delete() {
|
|
561
533
|
if (this.persistence) {
|
|
562
534
|
await this.persistence.markDeleted(this.collection, this.id, 1);
|
|
@@ -567,8 +539,7 @@ var DocumentRef = class {
|
|
|
567
539
|
type: "delete",
|
|
568
540
|
payload: null
|
|
569
541
|
});
|
|
570
|
-
this.
|
|
571
|
-
this.localStore?.notifyCollectionChanged(this.collection, this.id, "delete");
|
|
542
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "delete").catch(console.error);
|
|
572
543
|
this.syncEngine?.flush().catch(console.error);
|
|
573
544
|
return true;
|
|
574
545
|
}
|
|
@@ -583,20 +554,135 @@ var DocumentRef = class {
|
|
|
583
554
|
}).sendRequest();
|
|
584
555
|
return !!res?.success;
|
|
585
556
|
}
|
|
586
|
-
// ====================== SNAPSHOT ======================
|
|
587
557
|
onSnapshot(callback) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
558
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
559
|
+
if (this.persistence && this.localStore && realtimeBridge) {
|
|
560
|
+
const localUnsub = this.localStore.subscribeToDocument(this.collection, this.id, callback);
|
|
561
|
+
const remoteUnsub = realtimeBridge.watchDocument(this.collection, this.id);
|
|
562
|
+
this.persistence.getDocSnapshot(this.collection, this.id).then((snapshot) => callback(snapshot, "initial")).catch(console.error);
|
|
563
|
+
return () => {
|
|
564
|
+
localUnsub();
|
|
565
|
+
remoteUnsub();
|
|
566
|
+
};
|
|
592
567
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
568
|
+
this.fetchRemoteSnapshot().then((snapshot) => callback(snapshot, "initial")).catch(console.error);
|
|
569
|
+
return this.app.rtdb().subscribeToDocumentRaw(this.collection, this.id, (payload, change) => {
|
|
570
|
+
if (change === "delete") {
|
|
571
|
+
callback(null, "delete");
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
callback(DocumentSnapshot.fromMap(payload), change);
|
|
596
575
|
});
|
|
597
576
|
}
|
|
577
|
+
async fetchRemoteSnapshot() {
|
|
578
|
+
try {
|
|
579
|
+
const res = await new HttpsRequest({
|
|
580
|
+
method: "POST" /* POST */,
|
|
581
|
+
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
582
|
+
headers: {
|
|
583
|
+
authorization: this.app.getConfig().token,
|
|
584
|
+
"x-project": this.app.getConfig().project
|
|
585
|
+
},
|
|
586
|
+
body: {
|
|
587
|
+
collection: this.collection,
|
|
588
|
+
id: this.id,
|
|
589
|
+
single: true
|
|
590
|
+
}
|
|
591
|
+
}).sendRequest();
|
|
592
|
+
if (!res?.success || !res.document)
|
|
593
|
+
return null;
|
|
594
|
+
const snapshot = DocumentSnapshot.fromMap(res.document);
|
|
595
|
+
if (this.persistence) {
|
|
596
|
+
await this.persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
597
|
+
}
|
|
598
|
+
return snapshot;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.error(`[DocumentRef] get(${this.collection}/${this.id}) failed:`, error);
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async refreshFromRemote() {
|
|
605
|
+
const snapshot = await this.fetchRemoteSnapshot();
|
|
606
|
+
if (this.persistence && snapshot) {
|
|
607
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "update").catch(console.error);
|
|
608
|
+
}
|
|
609
|
+
return snapshot;
|
|
610
|
+
}
|
|
598
611
|
};
|
|
599
612
|
|
|
613
|
+
// src/database/RealtimeListeners.ts
|
|
614
|
+
function buildTargetKey(collection, filter = {}) {
|
|
615
|
+
return `${collection}:${stableStringify(filter)}`;
|
|
616
|
+
}
|
|
617
|
+
function stableStringify(value) {
|
|
618
|
+
if (value === null || typeof value !== "object") {
|
|
619
|
+
return JSON.stringify(value);
|
|
620
|
+
}
|
|
621
|
+
if (Array.isArray(value)) {
|
|
622
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
623
|
+
}
|
|
624
|
+
const entries = Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
625
|
+
return `{${entries.join(",")}}`;
|
|
626
|
+
}
|
|
627
|
+
function diffSnapshots(previous, next) {
|
|
628
|
+
const previousById = new Map(previous.map((snapshot, index) => [snapshot.id, { snapshot, index }]));
|
|
629
|
+
const nextById = new Map(next.map((snapshot, index) => [snapshot.id, { snapshot, index }]));
|
|
630
|
+
const changes = [];
|
|
631
|
+
next.forEach((snapshot, index) => {
|
|
632
|
+
const before = previousById.get(snapshot.id);
|
|
633
|
+
if (!before) {
|
|
634
|
+
changes.push({
|
|
635
|
+
type: "added",
|
|
636
|
+
doc: snapshot,
|
|
637
|
+
previousDoc: null,
|
|
638
|
+
oldIndex: -1,
|
|
639
|
+
newIndex: index
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (before.index !== index || !areSnapshotsEqual(before.snapshot, snapshot)) {
|
|
644
|
+
changes.push({
|
|
645
|
+
type: "modified",
|
|
646
|
+
doc: snapshot,
|
|
647
|
+
previousDoc: before.snapshot,
|
|
648
|
+
oldIndex: before.index,
|
|
649
|
+
newIndex: index
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
previous.forEach((snapshot, index) => {
|
|
654
|
+
if (nextById.has(snapshot.id))
|
|
655
|
+
return;
|
|
656
|
+
changes.push({
|
|
657
|
+
type: "removed",
|
|
658
|
+
doc: snapshot,
|
|
659
|
+
previousDoc: snapshot,
|
|
660
|
+
oldIndex: index,
|
|
661
|
+
newIndex: -1
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
return changes;
|
|
665
|
+
}
|
|
666
|
+
function applySnapshotChange(current, incoming, source) {
|
|
667
|
+
if (!incoming) {
|
|
668
|
+
return current;
|
|
669
|
+
}
|
|
670
|
+
if (source === "delete") {
|
|
671
|
+
return current.filter((snapshot) => snapshot.id !== incoming.id);
|
|
672
|
+
}
|
|
673
|
+
const next = [...current];
|
|
674
|
+
const index = next.findIndex((snapshot) => snapshot.id === incoming.id);
|
|
675
|
+
if (index === -1) {
|
|
676
|
+
next.push(incoming);
|
|
677
|
+
return next;
|
|
678
|
+
}
|
|
679
|
+
next[index] = incoming;
|
|
680
|
+
return next;
|
|
681
|
+
}
|
|
682
|
+
function areSnapshotsEqual(left, right) {
|
|
683
|
+
return stableStringify(left.data) === stableStringify(right.data);
|
|
684
|
+
}
|
|
685
|
+
|
|
600
686
|
// src/database/Query.ts
|
|
601
687
|
var Query = class {
|
|
602
688
|
constructor(app, collection) {
|
|
@@ -609,6 +695,84 @@ var Query = class {
|
|
|
609
695
|
this.filter.push(expression);
|
|
610
696
|
return this;
|
|
611
697
|
}
|
|
698
|
+
async get() {
|
|
699
|
+
const persistence = this.app.offline().persistence;
|
|
700
|
+
if (this.filter.length > 0 && persistence) {
|
|
701
|
+
const local = await this.getLocalFilteredSnapshots();
|
|
702
|
+
if (typeof navigator === "undefined" || !navigator.onLine) {
|
|
703
|
+
return local;
|
|
704
|
+
}
|
|
705
|
+
return this.refreshFromRemote();
|
|
706
|
+
}
|
|
707
|
+
if (persistence) {
|
|
708
|
+
const local = await persistence.getCollectionSnapshots(this.collection);
|
|
709
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
710
|
+
this.refreshFromRemote().catch(() => {
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
return local;
|
|
714
|
+
}
|
|
715
|
+
return this.refreshFromRemote();
|
|
716
|
+
}
|
|
717
|
+
async update(data) {
|
|
718
|
+
const genfilter = this.buildFilter();
|
|
719
|
+
return new HttpsRequest({
|
|
720
|
+
method: "POST" /* POST */,
|
|
721
|
+
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
722
|
+
headers: { authorization: this.app.getConfig().token, "x-project": this.app.getConfig().project },
|
|
723
|
+
body: {
|
|
724
|
+
collection: this.collection,
|
|
725
|
+
filter: genfilter,
|
|
726
|
+
data
|
|
727
|
+
}
|
|
728
|
+
}).sendRequest();
|
|
729
|
+
}
|
|
730
|
+
onSnapshot(callback) {
|
|
731
|
+
const genfilter = this.buildFilter();
|
|
732
|
+
const persistence = this.app.offline().persistence;
|
|
733
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
734
|
+
if (persistence && this.localStore && realtimeBridge) {
|
|
735
|
+
const targetKey = buildTargetKey(this.collection, genfilter);
|
|
736
|
+
const localUnsub = this.localStore.subscribeToCollection(targetKey, callback);
|
|
737
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
738
|
+
this.collection,
|
|
739
|
+
genfilter,
|
|
740
|
+
() => this.getLocalFilteredSnapshots()
|
|
741
|
+
);
|
|
742
|
+
this.getLocalFilteredSnapshots().then((snapshots) => {
|
|
743
|
+
realtimeBridge.primeCollectionTarget(this.collection, genfilter, snapshots);
|
|
744
|
+
callback(snapshots, "initial");
|
|
745
|
+
}).catch(console.error);
|
|
746
|
+
return () => {
|
|
747
|
+
localUnsub();
|
|
748
|
+
remoteUnsub();
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return this.subscribeOnlineSnapshot(callback);
|
|
752
|
+
}
|
|
753
|
+
onChildListener(callbacks) {
|
|
754
|
+
const genfilter = this.buildFilter();
|
|
755
|
+
const persistence = this.app.offline().persistence;
|
|
756
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
757
|
+
if (persistence && this.localStore && realtimeBridge) {
|
|
758
|
+
const targetKey = buildTargetKey(this.collection, genfilter);
|
|
759
|
+
const localUnsub = this.localStore.subscribeToChildEvents(targetKey, callbacks);
|
|
760
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
761
|
+
this.collection,
|
|
762
|
+
genfilter,
|
|
763
|
+
() => this.getLocalFilteredSnapshots()
|
|
764
|
+
);
|
|
765
|
+
this.getLocalFilteredSnapshots().then((snapshots) => {
|
|
766
|
+
realtimeBridge.primeCollectionTarget(this.collection, genfilter, snapshots);
|
|
767
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
768
|
+
}).catch(console.error);
|
|
769
|
+
return () => {
|
|
770
|
+
localUnsub();
|
|
771
|
+
remoteUnsub();
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
return this.subscribeOnlineChildListener(callbacks);
|
|
775
|
+
}
|
|
612
776
|
buildFilter() {
|
|
613
777
|
const mongoFilter = {};
|
|
614
778
|
this.filter.forEach(({ key, op, value }) => {
|
|
@@ -679,9 +843,7 @@ var Query = class {
|
|
|
679
843
|
if (this.filter.length === 0)
|
|
680
844
|
return docs;
|
|
681
845
|
return docs.filter(
|
|
682
|
-
(snapshot) => this.filter.every(
|
|
683
|
-
(expression) => this.matchesFilter(snapshot.data, expression)
|
|
684
|
-
)
|
|
846
|
+
(snapshot) => this.filter.every((expression) => this.matchesFilter(snapshot.data, expression))
|
|
685
847
|
);
|
|
686
848
|
}
|
|
687
849
|
async getLocalFilteredSnapshots() {
|
|
@@ -691,25 +853,6 @@ var Query = class {
|
|
|
691
853
|
const docs = await persistence.getCollectionSnapshots(this.collection);
|
|
692
854
|
return this.filterDocuments(docs);
|
|
693
855
|
}
|
|
694
|
-
async get() {
|
|
695
|
-
const persistence = this.app.offline().persistence;
|
|
696
|
-
if (this.filter.length > 0 && persistence) {
|
|
697
|
-
const local = await this.getLocalFilteredSnapshots();
|
|
698
|
-
if (typeof navigator === "undefined" || !navigator.onLine) {
|
|
699
|
-
return local;
|
|
700
|
-
}
|
|
701
|
-
return this.refreshFromRemote();
|
|
702
|
-
}
|
|
703
|
-
if (persistence) {
|
|
704
|
-
const local = await persistence.getCollectionSnapshots(this.collection);
|
|
705
|
-
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
706
|
-
this.refreshFromRemote().catch(() => {
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
return local;
|
|
710
|
-
}
|
|
711
|
-
return this.refreshFromRemote();
|
|
712
|
-
}
|
|
713
856
|
async refreshFromRemote() {
|
|
714
857
|
try {
|
|
715
858
|
const genfilter = this.buildFilter();
|
|
@@ -725,69 +868,90 @@ var Query = class {
|
|
|
725
868
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
726
869
|
return [];
|
|
727
870
|
}
|
|
728
|
-
|
|
871
|
+
const snapshots = res.documents.map((d) => {
|
|
729
872
|
delete d.token;
|
|
730
873
|
d.id = d.id ?? d._id;
|
|
731
874
|
delete d._id;
|
|
732
875
|
return DocumentSnapshot.fromMap(d);
|
|
733
876
|
});
|
|
877
|
+
const persistence = this.app.offline().persistence;
|
|
878
|
+
if (!persistence) {
|
|
879
|
+
return snapshots;
|
|
880
|
+
}
|
|
881
|
+
for (const snapshot of snapshots) {
|
|
882
|
+
await persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
883
|
+
}
|
|
884
|
+
return this.getLocalFilteredSnapshots();
|
|
734
885
|
} catch {
|
|
735
886
|
return [];
|
|
736
887
|
}
|
|
737
888
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
889
|
+
subscribeOnlineSnapshot(callback) {
|
|
890
|
+
let current = [];
|
|
891
|
+
this.refreshFromRemote().then((snapshots) => {
|
|
892
|
+
current = snapshots;
|
|
893
|
+
callback(current, "initial");
|
|
894
|
+
}).catch(console.error);
|
|
895
|
+
return this.app.rtdb().subscribeToCollectionRaw(
|
|
896
|
+
this.collection,
|
|
897
|
+
(payload, change) => {
|
|
898
|
+
const source = change;
|
|
899
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
900
|
+
const next = applySnapshotChange(current, incoming, source);
|
|
901
|
+
current = this.filterDocuments(next);
|
|
902
|
+
callback(current, source, incoming.id);
|
|
903
|
+
},
|
|
904
|
+
this.buildFilter()
|
|
905
|
+
);
|
|
752
906
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
907
|
+
subscribeOnlineChildListener(callbacks) {
|
|
908
|
+
let current = [];
|
|
909
|
+
this.refreshFromRemote().then((snapshots) => {
|
|
910
|
+
current = snapshots;
|
|
911
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
912
|
+
}).catch(console.error);
|
|
913
|
+
return this.app.rtdb().subscribeToCollectionRaw(
|
|
914
|
+
this.collection,
|
|
915
|
+
(payload, change) => {
|
|
916
|
+
const source = change;
|
|
917
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
918
|
+
const next = this.filterDocuments(applySnapshotChange(current, incoming, source));
|
|
919
|
+
const childChanges = diffSnapshots(current, next);
|
|
920
|
+
current = next;
|
|
921
|
+
childChanges.forEach((childChange) => {
|
|
922
|
+
const context = {
|
|
923
|
+
snapshots: next,
|
|
924
|
+
source,
|
|
925
|
+
changedDocId: childChange.doc.id,
|
|
926
|
+
fromCache: false,
|
|
927
|
+
oldIndex: childChange.oldIndex,
|
|
928
|
+
newIndex: childChange.newIndex,
|
|
929
|
+
previousDoc: childChange.previousDoc
|
|
930
|
+
};
|
|
931
|
+
if (childChange.type === "added") {
|
|
932
|
+
callbacks.onChildAdded?.(childChange.doc, context);
|
|
933
|
+
} else if (childChange.type === "modified") {
|
|
934
|
+
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
935
|
+
} else if (childChange.type === "removed") {
|
|
936
|
+
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
772
937
|
}
|
|
773
|
-
) ?? (() => {
|
|
774
938
|
});
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
939
|
+
},
|
|
940
|
+
this.buildFilter()
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
emitInitialChildEvents(snapshots, callbacks) {
|
|
944
|
+
diffSnapshots([], snapshots).forEach((change) => {
|
|
945
|
+
callbacks.onChildAdded?.(change.doc, {
|
|
946
|
+
snapshots,
|
|
947
|
+
source: "initial",
|
|
948
|
+
changedDocId: change.doc.id,
|
|
949
|
+
fromCache: true,
|
|
950
|
+
oldIndex: change.oldIndex,
|
|
951
|
+
newIndex: change.newIndex,
|
|
952
|
+
previousDoc: change.previousDoc
|
|
781
953
|
});
|
|
782
|
-
|
|
783
|
-
localUnsub();
|
|
784
|
-
remoteUnsub?.();
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
return this.app.rtdb().subscribeToCollectionRaw(this.collection, (payload, changes) => {
|
|
788
|
-
const res = normalizePayload(payload);
|
|
789
|
-
callback([DocumentSnapshot.fromMap(res?.data)], changes);
|
|
790
|
-
}, genfilter);
|
|
954
|
+
});
|
|
791
955
|
}
|
|
792
956
|
};
|
|
793
957
|
|
|
@@ -819,6 +983,89 @@ var CollectionRef = class {
|
|
|
819
983
|
}
|
|
820
984
|
return local;
|
|
821
985
|
}
|
|
986
|
+
return this.fetchRemoteSnapshots();
|
|
987
|
+
}
|
|
988
|
+
async add(data) {
|
|
989
|
+
if (this.persistence) {
|
|
990
|
+
const localId = this.persistence.createLocalId();
|
|
991
|
+
const docRecord = await this.persistence.upsertDoc({
|
|
992
|
+
collection: this.collection,
|
|
993
|
+
id: localId,
|
|
994
|
+
data: { ...data, id: localId },
|
|
995
|
+
exists: true,
|
|
996
|
+
deleted: false,
|
|
997
|
+
pending: 1,
|
|
998
|
+
localOnly: true,
|
|
999
|
+
status: "pending"
|
|
1000
|
+
});
|
|
1001
|
+
await this.persistence.enqueueMutation({
|
|
1002
|
+
mutationId: this.persistence.createMutationId(),
|
|
1003
|
+
collection: this.collection,
|
|
1004
|
+
documentId: localId,
|
|
1005
|
+
type: "insert",
|
|
1006
|
+
payload: docRecord.data
|
|
1007
|
+
});
|
|
1008
|
+
const snap = DocumentSnapshot.fromMap(docRecord.data);
|
|
1009
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, localId, "insert").catch(console.error);
|
|
1010
|
+
this.syncEngine?.flush().catch(console.error);
|
|
1011
|
+
return snap;
|
|
1012
|
+
}
|
|
1013
|
+
const res = await new HttpsRequest({
|
|
1014
|
+
method: "POST" /* POST */,
|
|
1015
|
+
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
1016
|
+
headers: {
|
|
1017
|
+
authorization: this.app.getConfig().token,
|
|
1018
|
+
"x-project": this.app.getConfig().project
|
|
1019
|
+
},
|
|
1020
|
+
body: { collection: this.collection, data: { ...data, id: "" } }
|
|
1021
|
+
}).sendRequest();
|
|
1022
|
+
if (!res?.success || !res.document)
|
|
1023
|
+
return null;
|
|
1024
|
+
return DocumentSnapshot.fromMap(res.document);
|
|
1025
|
+
}
|
|
1026
|
+
onSnapshot(callback) {
|
|
1027
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
1028
|
+
if (this.persistence && this.localStore && realtimeBridge) {
|
|
1029
|
+
const targetKey = buildTargetKey(this.collection, {});
|
|
1030
|
+
const localUnsub = this.localStore.subscribeToCollection(targetKey, callback);
|
|
1031
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
1032
|
+
this.collection,
|
|
1033
|
+
{},
|
|
1034
|
+
() => this.persistence.getCollectionSnapshots(this.collection)
|
|
1035
|
+
);
|
|
1036
|
+
this.persistence.getCollectionSnapshots(this.collection).then((snapshots) => {
|
|
1037
|
+
realtimeBridge.primeCollectionTarget(this.collection, {}, snapshots);
|
|
1038
|
+
callback(snapshots, "insert");
|
|
1039
|
+
}).catch(console.error);
|
|
1040
|
+
return () => {
|
|
1041
|
+
localUnsub();
|
|
1042
|
+
remoteUnsub();
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
return this.subscribeOnlineSnapshot(callback);
|
|
1046
|
+
}
|
|
1047
|
+
onChildListener(callbacks) {
|
|
1048
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
1049
|
+
if (this.persistence && this.localStore && realtimeBridge) {
|
|
1050
|
+
const targetKey = buildTargetKey(this.collection, {});
|
|
1051
|
+
const localUnsub = this.localStore.subscribeToChildEvents(targetKey, callbacks);
|
|
1052
|
+
const remoteUnsub = realtimeBridge.watchCollection(
|
|
1053
|
+
this.collection,
|
|
1054
|
+
{},
|
|
1055
|
+
() => this.persistence.getCollectionSnapshots(this.collection)
|
|
1056
|
+
);
|
|
1057
|
+
this.persistence.getCollectionSnapshots(this.collection).then((snapshots) => {
|
|
1058
|
+
realtimeBridge.primeCollectionTarget(this.collection, {}, snapshots);
|
|
1059
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
1060
|
+
}).catch(console.error);
|
|
1061
|
+
return () => {
|
|
1062
|
+
localUnsub();
|
|
1063
|
+
remoteUnsub();
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
return this.subscribeOnlineChildListener(callbacks);
|
|
1067
|
+
}
|
|
1068
|
+
async refreshFromRemote() {
|
|
822
1069
|
try {
|
|
823
1070
|
const res = await new HttpsRequest({
|
|
824
1071
|
method: "POST" /* POST */,
|
|
@@ -835,18 +1082,25 @@ var CollectionRef = class {
|
|
|
835
1082
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
836
1083
|
return [];
|
|
837
1084
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
delete
|
|
842
|
-
return
|
|
1085
|
+
const normalized = res.documents.map((raw) => {
|
|
1086
|
+
const doc = { ...raw };
|
|
1087
|
+
doc.id = doc.id ?? doc._id;
|
|
1088
|
+
delete doc._id;
|
|
1089
|
+
return doc;
|
|
843
1090
|
});
|
|
1091
|
+
if (this.persistence) {
|
|
1092
|
+
await this.persistence.reconcileCollectionFromRemote(this.collection, normalized);
|
|
1093
|
+
const snapshots = await this.persistence.getCollectionSnapshots(this.collection);
|
|
1094
|
+
this.app.offline().realtimeBridge?.primeCollectionTarget(this.collection, {}, snapshots);
|
|
1095
|
+
return snapshots;
|
|
1096
|
+
}
|
|
1097
|
+
return normalized.map((doc) => DocumentSnapshot.fromMap(doc));
|
|
844
1098
|
} catch (error) {
|
|
845
|
-
console.error("[EdmaxLabs]
|
|
1099
|
+
console.error("[EdmaxLabs] refreshFromRemote failed:", error);
|
|
846
1100
|
return [];
|
|
847
1101
|
}
|
|
848
1102
|
}
|
|
849
|
-
async
|
|
1103
|
+
async fetchRemoteSnapshots() {
|
|
850
1104
|
try {
|
|
851
1105
|
const res = await new HttpsRequest({
|
|
852
1106
|
method: "POST" /* POST */,
|
|
@@ -863,17 +1117,6 @@ var CollectionRef = class {
|
|
|
863
1117
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
864
1118
|
return [];
|
|
865
1119
|
}
|
|
866
|
-
for (const raw of res.documents) {
|
|
867
|
-
const doc = { ...raw };
|
|
868
|
-
doc.id = doc.id ?? doc._id;
|
|
869
|
-
delete doc._id;
|
|
870
|
-
if (this.persistence) {
|
|
871
|
-
await this.persistence.applyRemoteDoc(this.collection, doc);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
if (this.persistence) {
|
|
875
|
-
return await this.persistence.getCollectionSnapshots(this.collection);
|
|
876
|
-
}
|
|
877
1120
|
return res.documents.map((d) => {
|
|
878
1121
|
delete d.token;
|
|
879
1122
|
d.id = d.id ?? d._id;
|
|
@@ -881,59 +1124,68 @@ var CollectionRef = class {
|
|
|
881
1124
|
return DocumentSnapshot.fromMap(d);
|
|
882
1125
|
});
|
|
883
1126
|
} catch (error) {
|
|
884
|
-
console.error("[EdmaxLabs]
|
|
1127
|
+
console.error("[EdmaxLabs] Collection get failed:", error);
|
|
885
1128
|
return [];
|
|
886
1129
|
}
|
|
887
1130
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
});
|
|
901
|
-
await this.persistence.enqueueMutation({
|
|
902
|
-
mutationId: this.persistence.createMutationId(),
|
|
903
|
-
collection: this.collection,
|
|
904
|
-
documentId: localId,
|
|
905
|
-
type: "insert",
|
|
906
|
-
payload: docRecord.data
|
|
907
|
-
});
|
|
908
|
-
const snap = DocumentSnapshot.fromMap(docRecord.data);
|
|
909
|
-
this.localStore?.emitDocument(this.collection, localId, snap, "insert");
|
|
910
|
-
this.localStore?.notifyCollectionChanged(this.collection, localId, "insert");
|
|
911
|
-
this.syncEngine?.flush().catch(console.error);
|
|
912
|
-
return snap;
|
|
913
|
-
}
|
|
914
|
-
const res = await new HttpsRequest({
|
|
915
|
-
method: "POST" /* POST */,
|
|
916
|
-
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
917
|
-
headers: {
|
|
918
|
-
authorization: this.app.getConfig().token,
|
|
919
|
-
"x-project": this.app.getConfig().project
|
|
920
|
-
},
|
|
921
|
-
body: { collection: this.collection, data: { ...data, id: "" } }
|
|
922
|
-
// server will generate id
|
|
923
|
-
}).sendRequest();
|
|
924
|
-
if (!res?.success || !res.document)
|
|
925
|
-
return null;
|
|
926
|
-
return DocumentSnapshot.fromMap(res.document);
|
|
1131
|
+
subscribeOnlineSnapshot(callback) {
|
|
1132
|
+
let current = [];
|
|
1133
|
+
this.fetchRemoteSnapshots().then((snapshots) => {
|
|
1134
|
+
current = snapshots;
|
|
1135
|
+
callback(current, "initial");
|
|
1136
|
+
}).catch(console.error);
|
|
1137
|
+
return this.app.rtdb().subscribeToCollectionRaw(this.collection, (payload, change) => {
|
|
1138
|
+
const source = change;
|
|
1139
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
1140
|
+
current = applySnapshotChange(current, incoming, source);
|
|
1141
|
+
callback(current, source, incoming.id);
|
|
1142
|
+
});
|
|
927
1143
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1144
|
+
subscribeOnlineChildListener(callbacks) {
|
|
1145
|
+
let current = [];
|
|
1146
|
+
this.fetchRemoteSnapshots().then((snapshots) => {
|
|
1147
|
+
current = snapshots;
|
|
1148
|
+
this.emitInitialChildEvents(snapshots, callbacks);
|
|
1149
|
+
}).catch(console.error);
|
|
1150
|
+
return this.app.rtdb().subscribeToCollectionRaw(this.collection, (payload, change) => {
|
|
1151
|
+
const source = change;
|
|
1152
|
+
const incoming = source === "delete" ? DocumentSnapshot.fromMap({ id: payload?.id ?? payload?._id }) : DocumentSnapshot.fromMap(payload);
|
|
1153
|
+
const next = applySnapshotChange(current, incoming, source);
|
|
1154
|
+
const childChanges = diffSnapshots(current, next);
|
|
1155
|
+
current = next;
|
|
1156
|
+
childChanges.forEach((childChange) => {
|
|
1157
|
+
const context = {
|
|
1158
|
+
snapshots: next,
|
|
1159
|
+
source,
|
|
1160
|
+
changedDocId: childChange.doc.id,
|
|
1161
|
+
fromCache: false,
|
|
1162
|
+
oldIndex: childChange.oldIndex,
|
|
1163
|
+
newIndex: childChange.newIndex,
|
|
1164
|
+
previousDoc: childChange.previousDoc
|
|
1165
|
+
};
|
|
1166
|
+
if (childChange.type === "added") {
|
|
1167
|
+
callbacks.onChildAdded?.(childChange.doc, context);
|
|
1168
|
+
} else if (childChange.type === "modified") {
|
|
1169
|
+
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
1170
|
+
} else if (childChange.type === "removed") {
|
|
1171
|
+
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
1172
|
+
}
|
|
932
1173
|
});
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
emitInitialChildEvents(snapshots, callbacks) {
|
|
1177
|
+
const childChanges = diffSnapshots([], snapshots);
|
|
1178
|
+
childChanges.forEach((change) => {
|
|
1179
|
+
const context = {
|
|
1180
|
+
snapshots,
|
|
1181
|
+
source: "initial",
|
|
1182
|
+
changedDocId: change.doc.id,
|
|
1183
|
+
fromCache: true,
|
|
1184
|
+
oldIndex: change.oldIndex,
|
|
1185
|
+
newIndex: change.newIndex,
|
|
1186
|
+
previousDoc: change.previousDoc
|
|
1187
|
+
};
|
|
1188
|
+
callbacks.onChildAdded?.(change.doc, context);
|
|
937
1189
|
});
|
|
938
1190
|
}
|
|
939
1191
|
};
|
|
@@ -983,6 +1235,7 @@ var Batch = class {
|
|
|
983
1235
|
const persistence = this.app.offline().persistence;
|
|
984
1236
|
const localStore = this.app.offline().localStore;
|
|
985
1237
|
const syncEngine = this.app.offline().syncEngine;
|
|
1238
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
986
1239
|
if (persistence && localStore) {
|
|
987
1240
|
const results = [];
|
|
988
1241
|
for (const op of this.ops) {
|
|
@@ -1011,8 +1264,7 @@ var Batch = class {
|
|
|
1011
1264
|
payload: op.data
|
|
1012
1265
|
});
|
|
1013
1266
|
const snap = DocumentSnapshot.fromMap(upserted.data);
|
|
1014
|
-
|
|
1015
|
-
localStore.notifyCollectionChanged(op.collection, op.id, "insert");
|
|
1267
|
+
realtimeBridge?.publishLocalChange(op.collection, op.id, "insert").catch(console.error);
|
|
1016
1268
|
results.push(snap);
|
|
1017
1269
|
} else if (op.op === "delete") {
|
|
1018
1270
|
const docRef = new DocumentRef(this.app, op.collection, op.id);
|
|
@@ -1124,6 +1376,20 @@ function generateUUID() {
|
|
|
1124
1376
|
});
|
|
1125
1377
|
}
|
|
1126
1378
|
|
|
1379
|
+
// src/utils/documentNomalizer.ts
|
|
1380
|
+
function normalizePayload(payload) {
|
|
1381
|
+
const raw = payload?.document ?? payload?.data ?? payload;
|
|
1382
|
+
if (!raw)
|
|
1383
|
+
return null;
|
|
1384
|
+
const doc = { ...raw };
|
|
1385
|
+
doc.id = doc.id ?? raw?.id ?? raw?._id ?? payload?.id ?? payload?._id;
|
|
1386
|
+
delete doc._id;
|
|
1387
|
+
return {
|
|
1388
|
+
change: payload?.change ?? raw?.change ?? null,
|
|
1389
|
+
data: doc
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1127
1393
|
// src/database/Realtime.ts
|
|
1128
1394
|
var Realtime = class {
|
|
1129
1395
|
constructor(app) {
|
|
@@ -1192,8 +1458,12 @@ var Realtime = class {
|
|
|
1192
1458
|
const channel = `${lid}-${event}`;
|
|
1193
1459
|
const fn = (payload) => {
|
|
1194
1460
|
const normalized = normalizePayload(payload);
|
|
1195
|
-
if (!normalized)
|
|
1461
|
+
if (!normalized) {
|
|
1462
|
+
if (event === "delete") {
|
|
1463
|
+
callback(payload, "delete");
|
|
1464
|
+
}
|
|
1196
1465
|
return;
|
|
1466
|
+
}
|
|
1197
1467
|
callback(normalized.data, event);
|
|
1198
1468
|
};
|
|
1199
1469
|
this.socket.on(channel, fn);
|
|
@@ -1317,11 +1587,11 @@ var LocalStore = class {
|
|
|
1317
1587
|
constructor() {
|
|
1318
1588
|
this.documentListeners = /* @__PURE__ */ new Map();
|
|
1319
1589
|
this.collectionListeners = /* @__PURE__ */ new Map();
|
|
1590
|
+
this.childListeners = /* @__PURE__ */ new Map();
|
|
1320
1591
|
}
|
|
1321
1592
|
docKey(collection, id) {
|
|
1322
1593
|
return `${collection}:${id}`;
|
|
1323
1594
|
}
|
|
1324
|
-
// ===================== DOCUMENT LISTENERS =====================
|
|
1325
1595
|
subscribeToDocument(collection, id, callback) {
|
|
1326
1596
|
const key = this.docKey(collection, id);
|
|
1327
1597
|
if (!this.documentListeners.has(key)) {
|
|
@@ -1336,24 +1606,33 @@ var LocalStore = class {
|
|
|
1336
1606
|
}
|
|
1337
1607
|
};
|
|
1338
1608
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
this.collectionListeners.set(collection, /* @__PURE__ */ new Set());
|
|
1609
|
+
subscribeToCollection(targetKey, callback) {
|
|
1610
|
+
if (!this.collectionListeners.has(targetKey)) {
|
|
1611
|
+
this.collectionListeners.set(targetKey, /* @__PURE__ */ new Set());
|
|
1343
1612
|
}
|
|
1344
|
-
const listeners = this.collectionListeners.get(
|
|
1613
|
+
const listeners = this.collectionListeners.get(targetKey);
|
|
1345
1614
|
listeners.add(callback);
|
|
1346
1615
|
return () => {
|
|
1347
1616
|
listeners.delete(callback);
|
|
1348
1617
|
if (listeners.size === 0) {
|
|
1349
|
-
this.collectionListeners.delete(
|
|
1618
|
+
this.collectionListeners.delete(targetKey);
|
|
1619
|
+
this.childListeners.delete(targetKey);
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
subscribeToChildEvents(targetKey, callbacks) {
|
|
1624
|
+
if (!this.childListeners.has(targetKey)) {
|
|
1625
|
+
this.childListeners.set(targetKey, /* @__PURE__ */ new Set());
|
|
1626
|
+
}
|
|
1627
|
+
const listeners = this.childListeners.get(targetKey);
|
|
1628
|
+
listeners.add(callbacks);
|
|
1629
|
+
return () => {
|
|
1630
|
+
listeners.delete(callbacks);
|
|
1631
|
+
if (listeners.size === 0) {
|
|
1632
|
+
this.childListeners.delete(targetKey);
|
|
1350
1633
|
}
|
|
1351
1634
|
};
|
|
1352
1635
|
}
|
|
1353
|
-
// ===================== EMITTERS =====================
|
|
1354
|
-
/**
|
|
1355
|
-
* Notify all listeners for a specific document
|
|
1356
|
-
*/
|
|
1357
1636
|
emitDocument(collection, id, snapshot, change) {
|
|
1358
1637
|
const key = this.docKey(collection, id);
|
|
1359
1638
|
this.documentListeners.get(key)?.forEach((cb) => {
|
|
@@ -1364,64 +1643,66 @@ var LocalStore = class {
|
|
|
1364
1643
|
}
|
|
1365
1644
|
});
|
|
1366
1645
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
* This is the most important fix — collection listeners now receive the full list.
|
|
1370
|
-
*/
|
|
1371
|
-
emitCollection(collection, snapshots, change, changedDocId) {
|
|
1372
|
-
this.collectionListeners.get(collection)?.forEach((cb) => {
|
|
1646
|
+
emitCollection(targetKey, emission) {
|
|
1647
|
+
this.collectionListeners.get(targetKey)?.forEach((cb) => {
|
|
1373
1648
|
try {
|
|
1374
|
-
cb(snapshots,
|
|
1649
|
+
cb(emission.snapshots, emission.source, emission.changedDocId);
|
|
1375
1650
|
} catch (err) {
|
|
1376
|
-
console.error(`[EdmaxLabs] Error in collection listener for ${
|
|
1651
|
+
console.error(`[EdmaxLabs] Error in collection listener for ${targetKey}:`, err);
|
|
1377
1652
|
}
|
|
1378
1653
|
});
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
this.collectionListeners.get(collection)?.forEach((cb) => {
|
|
1387
|
-
try {
|
|
1388
|
-
cb([], change, changedDocId);
|
|
1389
|
-
} catch (err) {
|
|
1390
|
-
console.error(`[EdmaxLabs] Error in collection listener for ${collection}:`, err);
|
|
1391
|
-
}
|
|
1654
|
+
const childChanges = emission.childChanges ?? [];
|
|
1655
|
+
if (childChanges.length === 0)
|
|
1656
|
+
return;
|
|
1657
|
+
this.childListeners.get(targetKey)?.forEach((callbacks) => {
|
|
1658
|
+
childChanges.forEach((change) => {
|
|
1659
|
+
this.dispatchChildEvent(callbacks, change, emission);
|
|
1660
|
+
});
|
|
1392
1661
|
});
|
|
1393
1662
|
}
|
|
1394
|
-
// ===================== UTILITY =====================
|
|
1395
|
-
/**
|
|
1396
|
-
* Clear all listeners (useful for testing or when persistence is disabled)
|
|
1397
|
-
*/
|
|
1398
1663
|
clearAllListeners() {
|
|
1399
1664
|
this.documentListeners.clear();
|
|
1400
1665
|
this.collectionListeners.clear();
|
|
1666
|
+
this.childListeners.clear();
|
|
1401
1667
|
}
|
|
1402
|
-
/**
|
|
1403
|
-
* Get current listener count (for debugging / dev tools)
|
|
1404
|
-
*/
|
|
1405
1668
|
get listenerCount() {
|
|
1406
1669
|
let docCount = 0;
|
|
1407
1670
|
this.documentListeners.forEach((set) => docCount += set.size);
|
|
1408
1671
|
let collCount = 0;
|
|
1409
1672
|
this.collectionListeners.forEach((set) => collCount += set.size);
|
|
1673
|
+
this.childListeners.forEach((set) => collCount += set.size);
|
|
1410
1674
|
return { documents: docCount, collections: collCount };
|
|
1411
1675
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
removeCollectionListeners(collection) {
|
|
1416
|
-
this.collectionListeners.delete(collection);
|
|
1676
|
+
removeCollectionListeners(targetKey) {
|
|
1677
|
+
this.collectionListeners.delete(targetKey);
|
|
1678
|
+
this.childListeners.delete(targetKey);
|
|
1417
1679
|
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Remove all listeners for a specific document (useful for cleanup)
|
|
1420
|
-
*/
|
|
1421
1680
|
removeDocumentListeners(collection, id) {
|
|
1422
1681
|
const key = this.docKey(collection, id);
|
|
1423
1682
|
this.documentListeners.delete(key);
|
|
1424
1683
|
}
|
|
1684
|
+
dispatchChildEvent(callbacks, change, emission) {
|
|
1685
|
+
const context = {
|
|
1686
|
+
snapshots: emission.snapshots,
|
|
1687
|
+
source: emission.source,
|
|
1688
|
+
changedDocId: emission.changedDocId,
|
|
1689
|
+
fromCache: emission.fromCache ?? true,
|
|
1690
|
+
oldIndex: change.oldIndex,
|
|
1691
|
+
newIndex: change.newIndex,
|
|
1692
|
+
previousDoc: change.previousDoc
|
|
1693
|
+
};
|
|
1694
|
+
try {
|
|
1695
|
+
if (change.type === "added") {
|
|
1696
|
+
callbacks.onChildAdded?.(change.doc, context);
|
|
1697
|
+
} else if (change.type === "modified") {
|
|
1698
|
+
callbacks.onChildUpdated?.(change.doc, context);
|
|
1699
|
+
} else if (change.type === "removed") {
|
|
1700
|
+
callbacks.onChildRemoved?.(change.doc, context);
|
|
1701
|
+
}
|
|
1702
|
+
} catch (err) {
|
|
1703
|
+
console.error("[EdmaxLabs] Error in child listener:", err);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1425
1706
|
};
|
|
1426
1707
|
|
|
1427
1708
|
// ../../../../node_modules/idb/build/wrap-idb-value.js
|
|
@@ -1792,11 +2073,25 @@ var Persistence = class {
|
|
|
1792
2073
|
}
|
|
1793
2074
|
async getPendingMutations() {
|
|
1794
2075
|
const app = await this.getDb();
|
|
1795
|
-
const
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
2076
|
+
const pending = await app.getAllFromIndex("mutations", "by-status", "pending");
|
|
2077
|
+
return pending.sort((a, b) => a.createdAt - b.createdAt);
|
|
2078
|
+
}
|
|
2079
|
+
async getFailedMutations() {
|
|
2080
|
+
const app = await this.getDb();
|
|
2081
|
+
const failed = await app.getAllFromIndex("mutations", "by-status", "failed");
|
|
2082
|
+
return failed.sort((a, b) => a.createdAt - b.createdAt);
|
|
2083
|
+
}
|
|
2084
|
+
async recoverSyncingMutations() {
|
|
2085
|
+
const app = await this.getDb();
|
|
2086
|
+
const syncing = await app.getAllFromIndex("mutations", "by-status", "syncing");
|
|
2087
|
+
for (const mutation of syncing) {
|
|
2088
|
+
await app.put("mutations", {
|
|
2089
|
+
...mutation,
|
|
2090
|
+
status: "pending",
|
|
2091
|
+
updatedAt: this.now()
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
return syncing.length;
|
|
1800
2095
|
}
|
|
1801
2096
|
async getMutation(mutationId) {
|
|
1802
2097
|
const app = await this.getDb();
|
|
@@ -1901,6 +2196,27 @@ var Persistence = class {
|
|
|
1901
2196
|
}
|
|
1902
2197
|
return this.markDeleted(collection, id, 0);
|
|
1903
2198
|
}
|
|
2199
|
+
async reconcileCollectionFromRemote(collection, documents) {
|
|
2200
|
+
const app = await this.getDb();
|
|
2201
|
+
const existing = await app.getAllFromIndex("docs", "by-collection", collection);
|
|
2202
|
+
const incomingIds = /* @__PURE__ */ new Set();
|
|
2203
|
+
for (const data of documents) {
|
|
2204
|
+
const id = data.id ?? data._id;
|
|
2205
|
+
if (!id)
|
|
2206
|
+
continue;
|
|
2207
|
+
incomingIds.add(String(id));
|
|
2208
|
+
await this.applyRemoteDoc(collection, { ...data, id: String(id) });
|
|
2209
|
+
}
|
|
2210
|
+
for (const doc of existing) {
|
|
2211
|
+
if (doc.pending && doc.pending > 0)
|
|
2212
|
+
continue;
|
|
2213
|
+
if (!doc.exists || doc.deleted)
|
|
2214
|
+
continue;
|
|
2215
|
+
if (incomingIds.has(doc.id))
|
|
2216
|
+
continue;
|
|
2217
|
+
await this.markDeleted(collection, doc.id, 0);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
1904
2220
|
// ==================== UTILITIES ====================
|
|
1905
2221
|
createLocalId() {
|
|
1906
2222
|
return `local_${crypto.randomUUID()}`;
|
|
@@ -1930,112 +2246,169 @@ var RealtimeBridge = class {
|
|
|
1930
2246
|
this.app = app;
|
|
1931
2247
|
this.persistence = persistence;
|
|
1932
2248
|
this.store = store;
|
|
1933
|
-
this.
|
|
1934
|
-
this.
|
|
2249
|
+
this.collectionTargets = /* @__PURE__ */ new Map();
|
|
2250
|
+
this.documentTargets = /* @__PURE__ */ new Map();
|
|
1935
2251
|
}
|
|
1936
2252
|
docKey(collection, id) {
|
|
1937
2253
|
return `${collection}:${id}`;
|
|
1938
2254
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
const
|
|
1942
|
-
if (
|
|
1943
|
-
|
|
2255
|
+
watchCollection(collection, filter = {}, read) {
|
|
2256
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2257
|
+
const existing = this.collectionTargets.get(targetKey);
|
|
2258
|
+
if (existing) {
|
|
2259
|
+
existing.refCount += 1;
|
|
2260
|
+
return () => this.releaseCollection(targetKey);
|
|
1944
2261
|
}
|
|
1945
|
-
this.emitCurrentCollection(collection);
|
|
1946
2262
|
const unsub = this.app.rtdb().subscribeToCollectionRaw(
|
|
1947
2263
|
collection,
|
|
1948
2264
|
async (payload, change) => {
|
|
1949
2265
|
if (change === "delete") {
|
|
1950
2266
|
const id = payload?.id || payload?._id;
|
|
1951
|
-
if (id)
|
|
2267
|
+
if (id) {
|
|
1952
2268
|
await this.handleRemoteDelete(collection, id);
|
|
1953
|
-
|
|
1954
|
-
|
|
2269
|
+
}
|
|
2270
|
+
return;
|
|
1955
2271
|
}
|
|
2272
|
+
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
1956
2273
|
},
|
|
1957
2274
|
filter
|
|
1958
2275
|
);
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2276
|
+
this.collectionTargets.set(targetKey, {
|
|
2277
|
+
targetKey,
|
|
2278
|
+
collection,
|
|
2279
|
+
filter,
|
|
2280
|
+
read,
|
|
2281
|
+
refCount: 1,
|
|
2282
|
+
unsub,
|
|
2283
|
+
lastSnapshots: []
|
|
2284
|
+
});
|
|
2285
|
+
return () => this.releaseCollection(targetKey);
|
|
1965
2286
|
}
|
|
1966
2287
|
watchDocument(collection, id) {
|
|
1967
2288
|
const key = this.docKey(collection, id);
|
|
1968
|
-
|
|
1969
|
-
|
|
2289
|
+
const existing = this.documentTargets.get(key);
|
|
2290
|
+
if (existing) {
|
|
2291
|
+
existing.refCount += 1;
|
|
2292
|
+
return () => this.releaseDocument(key);
|
|
1970
2293
|
}
|
|
1971
|
-
this.emitCurrentDocument(collection, id);
|
|
1972
2294
|
const unsub = this.app.rtdb().subscribeToDocumentRaw(
|
|
1973
2295
|
collection,
|
|
1974
2296
|
id,
|
|
1975
2297
|
async (payload, change) => {
|
|
1976
2298
|
if (change === "delete") {
|
|
1977
2299
|
await this.handleRemoteDelete(collection, id);
|
|
1978
|
-
|
|
1979
|
-
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
2300
|
+
return;
|
|
1980
2301
|
}
|
|
2302
|
+
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
1981
2303
|
}
|
|
1982
2304
|
);
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2305
|
+
this.documentTargets.set(key, {
|
|
2306
|
+
key,
|
|
2307
|
+
collection,
|
|
2308
|
+
id,
|
|
2309
|
+
refCount: 1,
|
|
2310
|
+
unsub
|
|
2311
|
+
});
|
|
2312
|
+
return () => this.releaseDocument(key);
|
|
2313
|
+
}
|
|
2314
|
+
async emitCurrentCollection(collection, filter = {}) {
|
|
2315
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2316
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2317
|
+
if (!target)
|
|
2318
|
+
return [];
|
|
2319
|
+
return this.refreshCollectionTarget(target, "initial");
|
|
2320
|
+
}
|
|
2321
|
+
primeCollectionTarget(collection, filter, snapshots) {
|
|
2322
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2323
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2324
|
+
if (!target)
|
|
2325
|
+
return;
|
|
2326
|
+
target.lastSnapshots = snapshots;
|
|
1989
2327
|
}
|
|
1990
|
-
// ===================== INTERNAL HANDLERS =====================
|
|
1991
2328
|
async emitCurrentDocument(collection, id) {
|
|
1992
|
-
const
|
|
1993
|
-
|
|
1994
|
-
|
|
2329
|
+
const snapshot = await this.getCurrentDocumentSnapshot(collection, id);
|
|
2330
|
+
this.store.emitDocument(collection, id, snapshot, "initial");
|
|
2331
|
+
return snapshot;
|
|
1995
2332
|
}
|
|
1996
|
-
async
|
|
1997
|
-
const
|
|
1998
|
-
this.store.
|
|
2333
|
+
async publishLocalChange(collection, id, source) {
|
|
2334
|
+
const snapshot = await this.getCurrentDocumentSnapshot(collection, id);
|
|
2335
|
+
this.store.emitDocument(collection, id, snapshot, source);
|
|
2336
|
+
await this.refreshCollectionTargets(collection, source, id);
|
|
2337
|
+
}
|
|
2338
|
+
async onReconnect() {
|
|
2339
|
+
for (const target of this.collectionTargets.values()) {
|
|
2340
|
+
await this.refreshCollectionTarget(target, "initial");
|
|
2341
|
+
}
|
|
2342
|
+
for (const target of this.documentTargets.values()) {
|
|
2343
|
+
await this.emitCurrentDocument(target.collection, target.id);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
dispose() {
|
|
2347
|
+
this.collectionTargets.forEach((target) => target.unsub());
|
|
2348
|
+
this.documentTargets.forEach((target) => target.unsub());
|
|
2349
|
+
this.collectionTargets.clear();
|
|
2350
|
+
this.documentTargets.clear();
|
|
1999
2351
|
}
|
|
2000
|
-
async handleRemoteCreateOrUpdate(collection, raw,
|
|
2352
|
+
async handleRemoteCreateOrUpdate(collection, raw, source) {
|
|
2001
2353
|
const id = raw.id ?? raw._id;
|
|
2002
2354
|
if (!id)
|
|
2003
2355
|
return;
|
|
2004
2356
|
const saved = await this.persistence.applyRemoteDoc(collection, raw);
|
|
2005
|
-
if (
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
this.
|
|
2357
|
+
if (saved) {
|
|
2358
|
+
const snap = DocumentSnapshot.fromMap(saved.data);
|
|
2359
|
+
this.store.emitDocument(collection, id, snap, source);
|
|
2360
|
+
}
|
|
2361
|
+
await this.refreshCollectionTargets(collection, source, id, false);
|
|
2010
2362
|
}
|
|
2011
2363
|
async handleRemoteDelete(collection, id) {
|
|
2012
2364
|
await this.persistence.applyRemoteDelete(collection, id);
|
|
2013
2365
|
this.store.emitDocument(collection, id, null, "delete");
|
|
2014
|
-
this.
|
|
2366
|
+
await this.refreshCollectionTargets(collection, "delete", id, false);
|
|
2015
2367
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
* Replays pending mutations + refreshes all active subscriptions from cache
|
|
2020
|
-
*/
|
|
2021
|
-
async onReconnect() {
|
|
2022
|
-
for (const [key] of this.collectionUnsubs) {
|
|
2023
|
-
const collection = key.split(":")[0];
|
|
2024
|
-
await this.emitCurrentCollection(collection);
|
|
2025
|
-
}
|
|
2026
|
-
for (const [key] of this.documentUnsubs) {
|
|
2027
|
-
const [collection, id] = key.split(":");
|
|
2028
|
-
await this.emitCurrentDocument(collection, id);
|
|
2029
|
-
}
|
|
2368
|
+
async getCurrentDocumentSnapshot(collection, id) {
|
|
2369
|
+
const localDoc = await this.persistence.getDoc(collection, id);
|
|
2370
|
+
return localDoc && localDoc.exists && !localDoc.deleted ? DocumentSnapshot.fromMap(localDoc.data) : null;
|
|
2030
2371
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2372
|
+
async refreshCollectionTargets(collection, source, changedDocId, fromCache = true) {
|
|
2373
|
+
const targets = Array.from(this.collectionTargets.values()).filter(
|
|
2374
|
+
(target) => target.collection === collection
|
|
2375
|
+
);
|
|
2376
|
+
for (const target of targets) {
|
|
2377
|
+
await this.refreshCollectionTarget(target, source, changedDocId, fromCache);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
async refreshCollectionTarget(target, source, changedDocId, fromCache = true) {
|
|
2381
|
+
const snapshots = await target.read();
|
|
2382
|
+
const childChanges = diffSnapshots(target.lastSnapshots, snapshots);
|
|
2383
|
+
target.lastSnapshots = snapshots;
|
|
2384
|
+
this.store.emitCollection(target.targetKey, {
|
|
2385
|
+
snapshots,
|
|
2386
|
+
source,
|
|
2387
|
+
changedDocId,
|
|
2388
|
+
fromCache,
|
|
2389
|
+
childChanges
|
|
2390
|
+
});
|
|
2391
|
+
return snapshots;
|
|
2392
|
+
}
|
|
2393
|
+
releaseCollection(targetKey) {
|
|
2394
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2395
|
+
if (!target)
|
|
2396
|
+
return;
|
|
2397
|
+
target.refCount -= 1;
|
|
2398
|
+
if (target.refCount > 0)
|
|
2399
|
+
return;
|
|
2400
|
+
target.unsub();
|
|
2401
|
+
this.collectionTargets.delete(targetKey);
|
|
2402
|
+
}
|
|
2403
|
+
releaseDocument(key) {
|
|
2404
|
+
const target = this.documentTargets.get(key);
|
|
2405
|
+
if (!target)
|
|
2406
|
+
return;
|
|
2407
|
+
target.refCount -= 1;
|
|
2408
|
+
if (target.refCount > 0)
|
|
2409
|
+
return;
|
|
2410
|
+
target.unsub();
|
|
2411
|
+
this.documentTargets.delete(key);
|
|
2039
2412
|
}
|
|
2040
2413
|
};
|
|
2041
2414
|
|
|
@@ -2047,16 +2420,19 @@ var SyncEngine = class {
|
|
|
2047
2420
|
this.retryTimeout = null;
|
|
2048
2421
|
this.MAX_RETRIES = 5;
|
|
2049
2422
|
this.BASE_RETRY_DELAY = 2e3;
|
|
2423
|
+
this.handleOnline = () => this.onNetworkOnline();
|
|
2424
|
+
this.handleVisibilityChange = () => {
|
|
2425
|
+
if (document.visibilityState === "visible")
|
|
2426
|
+
this.onNetworkOnline();
|
|
2427
|
+
};
|
|
2050
2428
|
this.app = app;
|
|
2051
2429
|
this.persistence = persistence;
|
|
2052
2430
|
this.store = store;
|
|
2053
2431
|
this.realtimeBridge = realtimeBridge || null;
|
|
2432
|
+
this.persistence.recoverSyncingMutations().catch(console.error);
|
|
2054
2433
|
if (typeof window !== "undefined") {
|
|
2055
|
-
window.addEventListener("online",
|
|
2056
|
-
document.addEventListener("visibilitychange",
|
|
2057
|
-
if (document.visibilityState === "visible")
|
|
2058
|
-
this.onNetworkOnline();
|
|
2059
|
-
});
|
|
2434
|
+
window.addEventListener("online", this.handleOnline);
|
|
2435
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2060
2436
|
}
|
|
2061
2437
|
}
|
|
2062
2438
|
onNetworkOnline() {
|
|
@@ -2073,6 +2449,7 @@ var SyncEngine = class {
|
|
|
2073
2449
|
return;
|
|
2074
2450
|
this.syncing = true;
|
|
2075
2451
|
try {
|
|
2452
|
+
await this.persistence.recoverSyncingMutations();
|
|
2076
2453
|
const pending = await this.persistence.getPendingMutations();
|
|
2077
2454
|
for (const mutation of pending) {
|
|
2078
2455
|
if (mutation.retryCount >= this.MAX_RETRIES) {
|
|
@@ -2149,8 +2526,7 @@ var SyncEngine = class {
|
|
|
2149
2526
|
if (replaced) {
|
|
2150
2527
|
const snap = DocumentSnapshot.fromMap(replaced.data);
|
|
2151
2528
|
this.store.emitDocument(mutation.collection, oldId, snap, "insert");
|
|
2152
|
-
this.
|
|
2153
|
-
this.store.notifyCollectionChanged(mutation.collection, newId, "insert");
|
|
2529
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, newId, "insert");
|
|
2154
2530
|
}
|
|
2155
2531
|
return true;
|
|
2156
2532
|
}
|
|
@@ -2183,7 +2559,7 @@ var SyncEngine = class {
|
|
|
2183
2559
|
});
|
|
2184
2560
|
const snap = DocumentSnapshot.fromMap(local.data);
|
|
2185
2561
|
this.store.emitDocument(mutation.collection, mutation.documentId, snap, "update");
|
|
2186
|
-
this.
|
|
2562
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, mutation.documentId, "update");
|
|
2187
2563
|
return true;
|
|
2188
2564
|
}
|
|
2189
2565
|
async syncDelete(mutation) {
|
|
@@ -2200,7 +2576,7 @@ var SyncEngine = class {
|
|
|
2200
2576
|
return false;
|
|
2201
2577
|
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
2202
2578
|
this.store.emitDocument(mutation.collection, mutation.documentId, null, "delete");
|
|
2203
|
-
this.
|
|
2579
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, mutation.documentId, "delete");
|
|
2204
2580
|
return true;
|
|
2205
2581
|
}
|
|
2206
2582
|
scheduleRetry() {
|
|
@@ -2220,8 +2596,7 @@ var SyncEngine = class {
|
|
|
2220
2596
|
* Returns mutations that exceeded MAX_RETRIES
|
|
2221
2597
|
*/
|
|
2222
2598
|
async getFailedMutations() {
|
|
2223
|
-
|
|
2224
|
-
return all.filter((m) => m.status === "failed");
|
|
2599
|
+
return this.persistence.getFailedMutations();
|
|
2225
2600
|
}
|
|
2226
2601
|
/**
|
|
2227
2602
|
* Retry a specific failed mutation by resetting its retry count
|
|
@@ -2263,6 +2638,10 @@ var SyncEngine = class {
|
|
|
2263
2638
|
if (this.retryTimeout) {
|
|
2264
2639
|
clearTimeout(this.retryTimeout);
|
|
2265
2640
|
}
|
|
2641
|
+
if (typeof window !== "undefined") {
|
|
2642
|
+
window.removeEventListener("online", this.handleOnline);
|
|
2643
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2644
|
+
}
|
|
2266
2645
|
}
|
|
2267
2646
|
};
|
|
2268
2647
|
|