edmaxlabs-core 2.5.6 → 2.6.7
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 +706 -338
- 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 +706 -338
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -391,20 +391,6 @@ var DocumentSnapshot = class _DocumentSnapshot {
|
|
|
391
391
|
}
|
|
392
392
|
};
|
|
393
393
|
|
|
394
|
-
// src/utils/documentNomalizer.ts
|
|
395
|
-
function normalizePayload(payload) {
|
|
396
|
-
const raw = payload?.document ?? payload?.data ?? payload;
|
|
397
|
-
if (!raw)
|
|
398
|
-
return null;
|
|
399
|
-
const doc = { ...raw };
|
|
400
|
-
doc.id = payload.id ?? payload._id;
|
|
401
|
-
delete doc._id;
|
|
402
|
-
return {
|
|
403
|
-
change: payload?.change ?? raw?.change ?? null,
|
|
404
|
-
data: doc
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
|
|
408
394
|
// src/database/DocumentRef.ts
|
|
409
395
|
function validateDocumentData(data, operation) {
|
|
410
396
|
if (data === null || data === void 0) {
|
|
@@ -430,40 +416,22 @@ var DocumentRef = class {
|
|
|
430
416
|
this.syncEngine = app.offline().syncEngine;
|
|
431
417
|
this.localStore = app.offline().localStore;
|
|
432
418
|
}
|
|
433
|
-
// ====================== GET ======================
|
|
434
419
|
async get() {
|
|
435
420
|
if (this.persistence) {
|
|
436
421
|
try {
|
|
437
422
|
const localSnap = await this.persistence.getDocSnapshot(this.collection, this.id);
|
|
423
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
424
|
+
this.refreshFromRemote().catch(() => {
|
|
425
|
+
});
|
|
426
|
+
}
|
|
438
427
|
if (localSnap)
|
|
439
428
|
return localSnap;
|
|
440
429
|
} catch (error) {
|
|
441
430
|
console.error("[EdmaxLabs] Cache read error:", error);
|
|
442
431
|
}
|
|
443
432
|
}
|
|
444
|
-
|
|
445
|
-
const res = await new HttpsRequest({
|
|
446
|
-
method: "POST" /* POST */,
|
|
447
|
-
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
448
|
-
headers: {
|
|
449
|
-
authorization: this.app.getConfig().token,
|
|
450
|
-
"x-project": this.app.getConfig().project
|
|
451
|
-
},
|
|
452
|
-
body: {
|
|
453
|
-
collection: this.collection,
|
|
454
|
-
id: this.id,
|
|
455
|
-
single: true
|
|
456
|
-
}
|
|
457
|
-
}).sendRequest();
|
|
458
|
-
if (!res?.success || !res.document)
|
|
459
|
-
return null;
|
|
460
|
-
return DocumentSnapshot.fromMap(res.document);
|
|
461
|
-
} catch (error) {
|
|
462
|
-
console.error(`[DocumentRef] get(${this.collection}/${this.id}) failed:`, error);
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
433
|
+
return this.fetchRemoteSnapshot();
|
|
465
434
|
}
|
|
466
|
-
// ====================== UPDATE ======================
|
|
467
435
|
async update(data) {
|
|
468
436
|
if (this._isUpdating) {
|
|
469
437
|
console.warn(`[DocumentRef] update recursion blocked on ${this.collection}/${this.id}`);
|
|
@@ -509,15 +477,13 @@ var DocumentRef = class {
|
|
|
509
477
|
baseRevision: old.revision
|
|
510
478
|
});
|
|
511
479
|
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
512
|
-
this.
|
|
513
|
-
this.localStore?.notifyCollectionChanged(this.collection, this.id, "update");
|
|
480
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "update").catch(console.error);
|
|
514
481
|
this.syncEngine?.flush().catch(console.error);
|
|
515
482
|
return snap;
|
|
516
483
|
} finally {
|
|
517
484
|
this._isUpdating = false;
|
|
518
485
|
}
|
|
519
486
|
}
|
|
520
|
-
// ====================== SET ======================
|
|
521
487
|
async set(data) {
|
|
522
488
|
validateDocumentData(data, "DocumentRef.set");
|
|
523
489
|
if (!this.persistence) {
|
|
@@ -532,6 +498,8 @@ var DocumentRef = class {
|
|
|
532
498
|
}).sendRequest();
|
|
533
499
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
534
500
|
}
|
|
501
|
+
const existing = await this.persistence.getDoc(this.collection, this.id);
|
|
502
|
+
const mutationType = existing?.exists && !existing.deleted ? "update" : "insert";
|
|
535
503
|
const updated = await this.persistence.upsertDoc({
|
|
536
504
|
collection: this.collection,
|
|
537
505
|
id: this.id,
|
|
@@ -540,22 +508,27 @@ var DocumentRef = class {
|
|
|
540
508
|
deleted: false,
|
|
541
509
|
pending: 1,
|
|
542
510
|
localOnly: false,
|
|
543
|
-
status: "pending"
|
|
511
|
+
status: "pending",
|
|
512
|
+
revision: existing?.revision,
|
|
513
|
+
lastSyncedAt: existing?.lastSyncedAt
|
|
544
514
|
});
|
|
545
515
|
await this.persistence.enqueueMutation({
|
|
546
516
|
mutationId: this.persistence.createMutationId(),
|
|
547
517
|
collection: this.collection,
|
|
548
518
|
documentId: this.id,
|
|
549
|
-
type:
|
|
550
|
-
payload: data
|
|
519
|
+
type: mutationType,
|
|
520
|
+
payload: data,
|
|
521
|
+
baseRevision: existing?.revision
|
|
551
522
|
});
|
|
552
523
|
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
553
|
-
this.
|
|
554
|
-
|
|
524
|
+
this.app.offline().realtimeBridge?.publishLocalChange(
|
|
525
|
+
this.collection,
|
|
526
|
+
this.id,
|
|
527
|
+
mutationType === "insert" ? "insert" : "update"
|
|
528
|
+
).catch(console.error);
|
|
555
529
|
this.syncEngine?.flush().catch(console.error);
|
|
556
530
|
return snap;
|
|
557
531
|
}
|
|
558
|
-
// ====================== DELETE ======================
|
|
559
532
|
async delete() {
|
|
560
533
|
if (this.persistence) {
|
|
561
534
|
await this.persistence.markDeleted(this.collection, this.id, 1);
|
|
@@ -566,8 +539,7 @@ var DocumentRef = class {
|
|
|
566
539
|
type: "delete",
|
|
567
540
|
payload: null
|
|
568
541
|
});
|
|
569
|
-
this.
|
|
570
|
-
this.localStore?.notifyCollectionChanged(this.collection, this.id, "delete");
|
|
542
|
+
this.app.offline().realtimeBridge?.publishLocalChange(this.collection, this.id, "delete").catch(console.error);
|
|
571
543
|
this.syncEngine?.flush().catch(console.error);
|
|
572
544
|
return true;
|
|
573
545
|
}
|
|
@@ -582,20 +554,135 @@ var DocumentRef = class {
|
|
|
582
554
|
}).sendRequest();
|
|
583
555
|
return !!res?.success;
|
|
584
556
|
}
|
|
585
|
-
// ====================== SNAPSHOT ======================
|
|
586
557
|
onSnapshot(callback) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
+
};
|
|
591
567
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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);
|
|
595
575
|
});
|
|
596
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
|
+
}
|
|
597
611
|
};
|
|
598
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
|
+
|
|
599
686
|
// src/database/Query.ts
|
|
600
687
|
var Query = class {
|
|
601
688
|
constructor(app, collection) {
|
|
@@ -608,6 +695,84 @@ var Query = class {
|
|
|
608
695
|
this.filter.push(expression);
|
|
609
696
|
return this;
|
|
610
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
|
+
}
|
|
611
776
|
buildFilter() {
|
|
612
777
|
const mongoFilter = {};
|
|
613
778
|
this.filter.forEach(({ key, op, value }) => {
|
|
@@ -678,9 +843,7 @@ var Query = class {
|
|
|
678
843
|
if (this.filter.length === 0)
|
|
679
844
|
return docs;
|
|
680
845
|
return docs.filter(
|
|
681
|
-
(snapshot) => this.filter.every(
|
|
682
|
-
(expression) => this.matchesFilter(snapshot.data, expression)
|
|
683
|
-
)
|
|
846
|
+
(snapshot) => this.filter.every((expression) => this.matchesFilter(snapshot.data, expression))
|
|
684
847
|
);
|
|
685
848
|
}
|
|
686
849
|
async getLocalFilteredSnapshots() {
|
|
@@ -690,25 +853,6 @@ var Query = class {
|
|
|
690
853
|
const docs = await persistence.getCollectionSnapshots(this.collection);
|
|
691
854
|
return this.filterDocuments(docs);
|
|
692
855
|
}
|
|
693
|
-
async get() {
|
|
694
|
-
const persistence = this.app.offline().persistence;
|
|
695
|
-
if (this.filter.length > 0 && persistence) {
|
|
696
|
-
const local = await this.getLocalFilteredSnapshots();
|
|
697
|
-
if (typeof navigator === "undefined" || !navigator.onLine) {
|
|
698
|
-
return local;
|
|
699
|
-
}
|
|
700
|
-
return this.refreshFromRemote();
|
|
701
|
-
}
|
|
702
|
-
if (persistence) {
|
|
703
|
-
const local = await persistence.getCollectionSnapshots(this.collection);
|
|
704
|
-
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
705
|
-
this.refreshFromRemote().catch(() => {
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
return local;
|
|
709
|
-
}
|
|
710
|
-
return this.refreshFromRemote();
|
|
711
|
-
}
|
|
712
856
|
async refreshFromRemote() {
|
|
713
857
|
try {
|
|
714
858
|
const genfilter = this.buildFilter();
|
|
@@ -724,69 +868,90 @@ var Query = class {
|
|
|
724
868
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
725
869
|
return [];
|
|
726
870
|
}
|
|
727
|
-
|
|
871
|
+
const snapshots = res.documents.map((d) => {
|
|
728
872
|
delete d.token;
|
|
729
873
|
d.id = d.id ?? d._id;
|
|
730
874
|
delete d._id;
|
|
731
875
|
return DocumentSnapshot.fromMap(d);
|
|
732
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();
|
|
733
885
|
} catch {
|
|
734
886
|
return [];
|
|
735
887
|
}
|
|
736
888
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
+
);
|
|
751
906
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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);
|
|
771
937
|
}
|
|
772
|
-
) ?? (() => {
|
|
773
938
|
});
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
|
780
953
|
});
|
|
781
|
-
|
|
782
|
-
localUnsub();
|
|
783
|
-
remoteUnsub?.();
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
return this.app.rtdb().subscribeToCollectionRaw(this.collection, (payload, changes) => {
|
|
787
|
-
const res = normalizePayload(payload);
|
|
788
|
-
callback([DocumentSnapshot.fromMap(res?.data)], changes);
|
|
789
|
-
}, genfilter);
|
|
954
|
+
});
|
|
790
955
|
}
|
|
791
956
|
};
|
|
792
957
|
|
|
@@ -818,6 +983,89 @@ var CollectionRef = class {
|
|
|
818
983
|
}
|
|
819
984
|
return local;
|
|
820
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() {
|
|
821
1069
|
try {
|
|
822
1070
|
const res = await new HttpsRequest({
|
|
823
1071
|
method: "POST" /* POST */,
|
|
@@ -834,18 +1082,25 @@ var CollectionRef = class {
|
|
|
834
1082
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
835
1083
|
return [];
|
|
836
1084
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
delete
|
|
841
|
-
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;
|
|
842
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));
|
|
843
1098
|
} catch (error) {
|
|
844
|
-
console.error("[EdmaxLabs]
|
|
1099
|
+
console.error("[EdmaxLabs] refreshFromRemote failed:", error);
|
|
845
1100
|
return [];
|
|
846
1101
|
}
|
|
847
1102
|
}
|
|
848
|
-
async
|
|
1103
|
+
async fetchRemoteSnapshots() {
|
|
849
1104
|
try {
|
|
850
1105
|
const res = await new HttpsRequest({
|
|
851
1106
|
method: "POST" /* POST */,
|
|
@@ -862,17 +1117,6 @@ var CollectionRef = class {
|
|
|
862
1117
|
if (!res?.success || !Array.isArray(res.documents)) {
|
|
863
1118
|
return [];
|
|
864
1119
|
}
|
|
865
|
-
for (const raw of res.documents) {
|
|
866
|
-
const doc = { ...raw };
|
|
867
|
-
doc.id = doc.id ?? doc._id;
|
|
868
|
-
delete doc._id;
|
|
869
|
-
if (this.persistence) {
|
|
870
|
-
await this.persistence.applyRemoteDoc(this.collection, doc);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
if (this.persistence) {
|
|
874
|
-
return await this.persistence.getCollectionSnapshots(this.collection);
|
|
875
|
-
}
|
|
876
1120
|
return res.documents.map((d) => {
|
|
877
1121
|
delete d.token;
|
|
878
1122
|
d.id = d.id ?? d._id;
|
|
@@ -880,59 +1124,68 @@ var CollectionRef = class {
|
|
|
880
1124
|
return DocumentSnapshot.fromMap(d);
|
|
881
1125
|
});
|
|
882
1126
|
} catch (error) {
|
|
883
|
-
console.error("[EdmaxLabs]
|
|
1127
|
+
console.error("[EdmaxLabs] Collection get failed:", error);
|
|
884
1128
|
return [];
|
|
885
1129
|
}
|
|
886
1130
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
});
|
|
900
|
-
await this.persistence.enqueueMutation({
|
|
901
|
-
mutationId: this.persistence.createMutationId(),
|
|
902
|
-
collection: this.collection,
|
|
903
|
-
documentId: localId,
|
|
904
|
-
type: "insert",
|
|
905
|
-
payload: docRecord.data
|
|
906
|
-
});
|
|
907
|
-
const snap = DocumentSnapshot.fromMap(docRecord.data);
|
|
908
|
-
this.localStore?.emitDocument(this.collection, localId, snap, "insert");
|
|
909
|
-
this.localStore?.notifyCollectionChanged(this.collection, localId, "insert");
|
|
910
|
-
this.syncEngine?.flush().catch(console.error);
|
|
911
|
-
return snap;
|
|
912
|
-
}
|
|
913
|
-
const res = await new HttpsRequest({
|
|
914
|
-
method: "POST" /* POST */,
|
|
915
|
-
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
916
|
-
headers: {
|
|
917
|
-
authorization: this.app.getConfig().token,
|
|
918
|
-
"x-project": this.app.getConfig().project
|
|
919
|
-
},
|
|
920
|
-
body: { collection: this.collection, data: { ...data, id: "" } }
|
|
921
|
-
// server will generate id
|
|
922
|
-
}).sendRequest();
|
|
923
|
-
if (!res?.success || !res.document)
|
|
924
|
-
return null;
|
|
925
|
-
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
|
+
});
|
|
926
1143
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
+
}
|
|
931
1173
|
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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);
|
|
936
1189
|
});
|
|
937
1190
|
}
|
|
938
1191
|
};
|
|
@@ -982,6 +1235,7 @@ var Batch = class {
|
|
|
982
1235
|
const persistence = this.app.offline().persistence;
|
|
983
1236
|
const localStore = this.app.offline().localStore;
|
|
984
1237
|
const syncEngine = this.app.offline().syncEngine;
|
|
1238
|
+
const realtimeBridge = this.app.offline().realtimeBridge;
|
|
985
1239
|
if (persistence && localStore) {
|
|
986
1240
|
const results = [];
|
|
987
1241
|
for (const op of this.ops) {
|
|
@@ -1010,8 +1264,7 @@ var Batch = class {
|
|
|
1010
1264
|
payload: op.data
|
|
1011
1265
|
});
|
|
1012
1266
|
const snap = DocumentSnapshot.fromMap(upserted.data);
|
|
1013
|
-
|
|
1014
|
-
localStore.notifyCollectionChanged(op.collection, op.id, "insert");
|
|
1267
|
+
realtimeBridge?.publishLocalChange(op.collection, op.id, "insert").catch(console.error);
|
|
1015
1268
|
results.push(snap);
|
|
1016
1269
|
} else if (op.op === "delete") {
|
|
1017
1270
|
const docRef = new DocumentRef(this.app, op.collection, op.id);
|
|
@@ -1111,16 +1364,18 @@ var Database = class {
|
|
|
1111
1364
|
// src/database/Realtime.ts
|
|
1112
1365
|
var import_socket = require("socket.io-client");
|
|
1113
1366
|
|
|
1114
|
-
// src/utils/
|
|
1115
|
-
function
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1367
|
+
// src/utils/documentNomalizer.ts
|
|
1368
|
+
function normalizePayload(payload) {
|
|
1369
|
+
const raw = payload?.document ?? payload?.data ?? payload;
|
|
1370
|
+
if (!raw)
|
|
1371
|
+
return null;
|
|
1372
|
+
const doc = { ...raw };
|
|
1373
|
+
doc.id = doc.id ?? raw?.id ?? raw?._id ?? payload?.id ?? payload?._id;
|
|
1374
|
+
delete doc._id;
|
|
1375
|
+
return {
|
|
1376
|
+
change: payload?.change ?? raw?.change ?? null,
|
|
1377
|
+
data: doc
|
|
1378
|
+
};
|
|
1124
1379
|
}
|
|
1125
1380
|
|
|
1126
1381
|
// src/database/Realtime.ts
|
|
@@ -1184,15 +1439,19 @@ var Realtime = class {
|
|
|
1184
1439
|
*/
|
|
1185
1440
|
subscribeToCollectionRaw(collection, callback, filter = {}) {
|
|
1186
1441
|
this.connect();
|
|
1187
|
-
const lid =
|
|
1442
|
+
const lid = collection;
|
|
1188
1443
|
const handlers = [];
|
|
1189
|
-
this.socket.emit("subscribe", { collection, filter
|
|
1444
|
+
this.socket.emit("subscribe", { collection, filter });
|
|
1190
1445
|
this.events.forEach((event) => {
|
|
1191
1446
|
const channel = `${lid}-${event}`;
|
|
1192
1447
|
const fn = (payload) => {
|
|
1193
1448
|
const normalized = normalizePayload(payload);
|
|
1194
|
-
if (!normalized)
|
|
1449
|
+
if (!normalized) {
|
|
1450
|
+
if (event === "delete") {
|
|
1451
|
+
callback(payload, "delete");
|
|
1452
|
+
}
|
|
1195
1453
|
return;
|
|
1454
|
+
}
|
|
1196
1455
|
callback(normalized.data, event);
|
|
1197
1456
|
};
|
|
1198
1457
|
this.socket.on(channel, fn);
|
|
@@ -1206,10 +1465,10 @@ var Realtime = class {
|
|
|
1206
1465
|
*/
|
|
1207
1466
|
subscribeToDocumentRaw(collection, id, callback) {
|
|
1208
1467
|
this.connect();
|
|
1209
|
-
const lid =
|
|
1468
|
+
const lid = collection;
|
|
1210
1469
|
const filter = { _id: id };
|
|
1211
1470
|
const handlers = [];
|
|
1212
|
-
this.socket.emit("subscribe", { collection, filter
|
|
1471
|
+
this.socket.emit("subscribe", { collection, filter });
|
|
1213
1472
|
this.events.forEach((event) => {
|
|
1214
1473
|
const channel = `${lid}-${event}`;
|
|
1215
1474
|
const fn = (payload) => {
|
|
@@ -1316,11 +1575,11 @@ var LocalStore = class {
|
|
|
1316
1575
|
constructor() {
|
|
1317
1576
|
this.documentListeners = /* @__PURE__ */ new Map();
|
|
1318
1577
|
this.collectionListeners = /* @__PURE__ */ new Map();
|
|
1578
|
+
this.childListeners = /* @__PURE__ */ new Map();
|
|
1319
1579
|
}
|
|
1320
1580
|
docKey(collection, id) {
|
|
1321
1581
|
return `${collection}:${id}`;
|
|
1322
1582
|
}
|
|
1323
|
-
// ===================== DOCUMENT LISTENERS =====================
|
|
1324
1583
|
subscribeToDocument(collection, id, callback) {
|
|
1325
1584
|
const key = this.docKey(collection, id);
|
|
1326
1585
|
if (!this.documentListeners.has(key)) {
|
|
@@ -1335,24 +1594,33 @@ var LocalStore = class {
|
|
|
1335
1594
|
}
|
|
1336
1595
|
};
|
|
1337
1596
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
this.collectionListeners.set(collection, /* @__PURE__ */ new Set());
|
|
1597
|
+
subscribeToCollection(targetKey, callback) {
|
|
1598
|
+
if (!this.collectionListeners.has(targetKey)) {
|
|
1599
|
+
this.collectionListeners.set(targetKey, /* @__PURE__ */ new Set());
|
|
1342
1600
|
}
|
|
1343
|
-
const listeners = this.collectionListeners.get(
|
|
1601
|
+
const listeners = this.collectionListeners.get(targetKey);
|
|
1344
1602
|
listeners.add(callback);
|
|
1345
1603
|
return () => {
|
|
1346
1604
|
listeners.delete(callback);
|
|
1347
1605
|
if (listeners.size === 0) {
|
|
1348
|
-
this.collectionListeners.delete(
|
|
1606
|
+
this.collectionListeners.delete(targetKey);
|
|
1607
|
+
this.childListeners.delete(targetKey);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
subscribeToChildEvents(targetKey, callbacks) {
|
|
1612
|
+
if (!this.childListeners.has(targetKey)) {
|
|
1613
|
+
this.childListeners.set(targetKey, /* @__PURE__ */ new Set());
|
|
1614
|
+
}
|
|
1615
|
+
const listeners = this.childListeners.get(targetKey);
|
|
1616
|
+
listeners.add(callbacks);
|
|
1617
|
+
return () => {
|
|
1618
|
+
listeners.delete(callbacks);
|
|
1619
|
+
if (listeners.size === 0) {
|
|
1620
|
+
this.childListeners.delete(targetKey);
|
|
1349
1621
|
}
|
|
1350
1622
|
};
|
|
1351
1623
|
}
|
|
1352
|
-
// ===================== EMITTERS =====================
|
|
1353
|
-
/**
|
|
1354
|
-
* Notify all listeners for a specific document
|
|
1355
|
-
*/
|
|
1356
1624
|
emitDocument(collection, id, snapshot, change) {
|
|
1357
1625
|
const key = this.docKey(collection, id);
|
|
1358
1626
|
this.documentListeners.get(key)?.forEach((cb) => {
|
|
@@ -1363,64 +1631,66 @@ var LocalStore = class {
|
|
|
1363
1631
|
}
|
|
1364
1632
|
});
|
|
1365
1633
|
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
* This is the most important fix — collection listeners now receive the full list.
|
|
1369
|
-
*/
|
|
1370
|
-
emitCollection(collection, snapshots, change, changedDocId) {
|
|
1371
|
-
this.collectionListeners.get(collection)?.forEach((cb) => {
|
|
1634
|
+
emitCollection(targetKey, emission) {
|
|
1635
|
+
this.collectionListeners.get(targetKey)?.forEach((cb) => {
|
|
1372
1636
|
try {
|
|
1373
|
-
cb(snapshots,
|
|
1637
|
+
cb(emission.snapshots, emission.source, emission.changedDocId);
|
|
1374
1638
|
} catch (err) {
|
|
1375
|
-
console.error(`[EdmaxLabs] Error in collection listener for ${
|
|
1639
|
+
console.error(`[EdmaxLabs] Error in collection listener for ${targetKey}:`, err);
|
|
1376
1640
|
}
|
|
1377
1641
|
});
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
this.collectionListeners.get(collection)?.forEach((cb) => {
|
|
1386
|
-
try {
|
|
1387
|
-
cb([], change, changedDocId);
|
|
1388
|
-
} catch (err) {
|
|
1389
|
-
console.error(`[EdmaxLabs] Error in collection listener for ${collection}:`, err);
|
|
1390
|
-
}
|
|
1642
|
+
const childChanges = emission.childChanges ?? [];
|
|
1643
|
+
if (childChanges.length === 0)
|
|
1644
|
+
return;
|
|
1645
|
+
this.childListeners.get(targetKey)?.forEach((callbacks) => {
|
|
1646
|
+
childChanges.forEach((change) => {
|
|
1647
|
+
this.dispatchChildEvent(callbacks, change, emission);
|
|
1648
|
+
});
|
|
1391
1649
|
});
|
|
1392
1650
|
}
|
|
1393
|
-
// ===================== UTILITY =====================
|
|
1394
|
-
/**
|
|
1395
|
-
* Clear all listeners (useful for testing or when persistence is disabled)
|
|
1396
|
-
*/
|
|
1397
1651
|
clearAllListeners() {
|
|
1398
1652
|
this.documentListeners.clear();
|
|
1399
1653
|
this.collectionListeners.clear();
|
|
1654
|
+
this.childListeners.clear();
|
|
1400
1655
|
}
|
|
1401
|
-
/**
|
|
1402
|
-
* Get current listener count (for debugging / dev tools)
|
|
1403
|
-
*/
|
|
1404
1656
|
get listenerCount() {
|
|
1405
1657
|
let docCount = 0;
|
|
1406
1658
|
this.documentListeners.forEach((set) => docCount += set.size);
|
|
1407
1659
|
let collCount = 0;
|
|
1408
1660
|
this.collectionListeners.forEach((set) => collCount += set.size);
|
|
1661
|
+
this.childListeners.forEach((set) => collCount += set.size);
|
|
1409
1662
|
return { documents: docCount, collections: collCount };
|
|
1410
1663
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
removeCollectionListeners(collection) {
|
|
1415
|
-
this.collectionListeners.delete(collection);
|
|
1664
|
+
removeCollectionListeners(targetKey) {
|
|
1665
|
+
this.collectionListeners.delete(targetKey);
|
|
1666
|
+
this.childListeners.delete(targetKey);
|
|
1416
1667
|
}
|
|
1417
|
-
/**
|
|
1418
|
-
* Remove all listeners for a specific document (useful for cleanup)
|
|
1419
|
-
*/
|
|
1420
1668
|
removeDocumentListeners(collection, id) {
|
|
1421
1669
|
const key = this.docKey(collection, id);
|
|
1422
1670
|
this.documentListeners.delete(key);
|
|
1423
1671
|
}
|
|
1672
|
+
dispatchChildEvent(callbacks, change, emission) {
|
|
1673
|
+
const context = {
|
|
1674
|
+
snapshots: emission.snapshots,
|
|
1675
|
+
source: emission.source,
|
|
1676
|
+
changedDocId: emission.changedDocId,
|
|
1677
|
+
fromCache: emission.fromCache ?? true,
|
|
1678
|
+
oldIndex: change.oldIndex,
|
|
1679
|
+
newIndex: change.newIndex,
|
|
1680
|
+
previousDoc: change.previousDoc
|
|
1681
|
+
};
|
|
1682
|
+
try {
|
|
1683
|
+
if (change.type === "added") {
|
|
1684
|
+
callbacks.onChildAdded?.(change.doc, context);
|
|
1685
|
+
} else if (change.type === "modified") {
|
|
1686
|
+
callbacks.onChildUpdated?.(change.doc, context);
|
|
1687
|
+
} else if (change.type === "removed") {
|
|
1688
|
+
callbacks.onChildRemoved?.(change.doc, context);
|
|
1689
|
+
}
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
console.error("[EdmaxLabs] Error in child listener:", err);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1424
1694
|
};
|
|
1425
1695
|
|
|
1426
1696
|
// ../../../../node_modules/idb/build/wrap-idb-value.js
|
|
@@ -1791,11 +2061,25 @@ var Persistence = class {
|
|
|
1791
2061
|
}
|
|
1792
2062
|
async getPendingMutations() {
|
|
1793
2063
|
const app = await this.getDb();
|
|
1794
|
-
const
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2064
|
+
const pending = await app.getAllFromIndex("mutations", "by-status", "pending");
|
|
2065
|
+
return pending.sort((a, b) => a.createdAt - b.createdAt);
|
|
2066
|
+
}
|
|
2067
|
+
async getFailedMutations() {
|
|
2068
|
+
const app = await this.getDb();
|
|
2069
|
+
const failed = await app.getAllFromIndex("mutations", "by-status", "failed");
|
|
2070
|
+
return failed.sort((a, b) => a.createdAt - b.createdAt);
|
|
2071
|
+
}
|
|
2072
|
+
async recoverSyncingMutations() {
|
|
2073
|
+
const app = await this.getDb();
|
|
2074
|
+
const syncing = await app.getAllFromIndex("mutations", "by-status", "syncing");
|
|
2075
|
+
for (const mutation of syncing) {
|
|
2076
|
+
await app.put("mutations", {
|
|
2077
|
+
...mutation,
|
|
2078
|
+
status: "pending",
|
|
2079
|
+
updatedAt: this.now()
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
return syncing.length;
|
|
1799
2083
|
}
|
|
1800
2084
|
async getMutation(mutationId) {
|
|
1801
2085
|
const app = await this.getDb();
|
|
@@ -1900,6 +2184,27 @@ var Persistence = class {
|
|
|
1900
2184
|
}
|
|
1901
2185
|
return this.markDeleted(collection, id, 0);
|
|
1902
2186
|
}
|
|
2187
|
+
async reconcileCollectionFromRemote(collection, documents) {
|
|
2188
|
+
const app = await this.getDb();
|
|
2189
|
+
const existing = await app.getAllFromIndex("docs", "by-collection", collection);
|
|
2190
|
+
const incomingIds = /* @__PURE__ */ new Set();
|
|
2191
|
+
for (const data of documents) {
|
|
2192
|
+
const id = data.id ?? data._id;
|
|
2193
|
+
if (!id)
|
|
2194
|
+
continue;
|
|
2195
|
+
incomingIds.add(String(id));
|
|
2196
|
+
await this.applyRemoteDoc(collection, { ...data, id: String(id) });
|
|
2197
|
+
}
|
|
2198
|
+
for (const doc of existing) {
|
|
2199
|
+
if (doc.pending && doc.pending > 0)
|
|
2200
|
+
continue;
|
|
2201
|
+
if (!doc.exists || doc.deleted)
|
|
2202
|
+
continue;
|
|
2203
|
+
if (incomingIds.has(doc.id))
|
|
2204
|
+
continue;
|
|
2205
|
+
await this.markDeleted(collection, doc.id, 0);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
1903
2208
|
// ==================== UTILITIES ====================
|
|
1904
2209
|
createLocalId() {
|
|
1905
2210
|
return `local_${crypto.randomUUID()}`;
|
|
@@ -1929,112 +2234,169 @@ var RealtimeBridge = class {
|
|
|
1929
2234
|
this.app = app;
|
|
1930
2235
|
this.persistence = persistence;
|
|
1931
2236
|
this.store = store;
|
|
1932
|
-
this.
|
|
1933
|
-
this.
|
|
2237
|
+
this.collectionTargets = /* @__PURE__ */ new Map();
|
|
2238
|
+
this.documentTargets = /* @__PURE__ */ new Map();
|
|
1934
2239
|
}
|
|
1935
2240
|
docKey(collection, id) {
|
|
1936
2241
|
return `${collection}:${id}`;
|
|
1937
2242
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
const
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
2243
|
+
watchCollection(collection, filter = {}, read) {
|
|
2244
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2245
|
+
const existing = this.collectionTargets.get(targetKey);
|
|
2246
|
+
if (existing) {
|
|
2247
|
+
existing.refCount += 1;
|
|
2248
|
+
return () => this.releaseCollection(targetKey);
|
|
1943
2249
|
}
|
|
1944
|
-
this.emitCurrentCollection(collection);
|
|
1945
2250
|
const unsub = this.app.rtdb().subscribeToCollectionRaw(
|
|
1946
2251
|
collection,
|
|
1947
2252
|
async (payload, change) => {
|
|
1948
2253
|
if (change === "delete") {
|
|
1949
2254
|
const id = payload?.id || payload?._id;
|
|
1950
|
-
if (id)
|
|
2255
|
+
if (id) {
|
|
1951
2256
|
await this.handleRemoteDelete(collection, id);
|
|
1952
|
-
|
|
1953
|
-
|
|
2257
|
+
}
|
|
2258
|
+
return;
|
|
1954
2259
|
}
|
|
2260
|
+
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
1955
2261
|
},
|
|
1956
2262
|
filter
|
|
1957
2263
|
);
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2264
|
+
this.collectionTargets.set(targetKey, {
|
|
2265
|
+
targetKey,
|
|
2266
|
+
collection,
|
|
2267
|
+
filter,
|
|
2268
|
+
read,
|
|
2269
|
+
refCount: 1,
|
|
2270
|
+
unsub,
|
|
2271
|
+
lastSnapshots: []
|
|
2272
|
+
});
|
|
2273
|
+
return () => this.releaseCollection(targetKey);
|
|
1964
2274
|
}
|
|
1965
2275
|
watchDocument(collection, id) {
|
|
1966
2276
|
const key = this.docKey(collection, id);
|
|
1967
|
-
|
|
1968
|
-
|
|
2277
|
+
const existing = this.documentTargets.get(key);
|
|
2278
|
+
if (existing) {
|
|
2279
|
+
existing.refCount += 1;
|
|
2280
|
+
return () => this.releaseDocument(key);
|
|
1969
2281
|
}
|
|
1970
|
-
this.emitCurrentDocument(collection, id);
|
|
1971
2282
|
const unsub = this.app.rtdb().subscribeToDocumentRaw(
|
|
1972
2283
|
collection,
|
|
1973
2284
|
id,
|
|
1974
2285
|
async (payload, change) => {
|
|
1975
2286
|
if (change === "delete") {
|
|
1976
2287
|
await this.handleRemoteDelete(collection, id);
|
|
1977
|
-
|
|
1978
|
-
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
2288
|
+
return;
|
|
1979
2289
|
}
|
|
2290
|
+
await this.handleRemoteCreateOrUpdate(collection, payload, change);
|
|
1980
2291
|
}
|
|
1981
2292
|
);
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2293
|
+
this.documentTargets.set(key, {
|
|
2294
|
+
key,
|
|
2295
|
+
collection,
|
|
2296
|
+
id,
|
|
2297
|
+
refCount: 1,
|
|
2298
|
+
unsub
|
|
2299
|
+
});
|
|
2300
|
+
return () => this.releaseDocument(key);
|
|
2301
|
+
}
|
|
2302
|
+
async emitCurrentCollection(collection, filter = {}) {
|
|
2303
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2304
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2305
|
+
if (!target)
|
|
2306
|
+
return [];
|
|
2307
|
+
return this.refreshCollectionTarget(target, "initial");
|
|
2308
|
+
}
|
|
2309
|
+
primeCollectionTarget(collection, filter, snapshots) {
|
|
2310
|
+
const targetKey = buildTargetKey(collection, filter);
|
|
2311
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2312
|
+
if (!target)
|
|
2313
|
+
return;
|
|
2314
|
+
target.lastSnapshots = snapshots;
|
|
1988
2315
|
}
|
|
1989
|
-
// ===================== INTERNAL HANDLERS =====================
|
|
1990
2316
|
async emitCurrentDocument(collection, id) {
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
|
|
2317
|
+
const snapshot = await this.getCurrentDocumentSnapshot(collection, id);
|
|
2318
|
+
this.store.emitDocument(collection, id, snapshot, "initial");
|
|
2319
|
+
return snapshot;
|
|
1994
2320
|
}
|
|
1995
|
-
async
|
|
1996
|
-
const
|
|
1997
|
-
this.store.
|
|
2321
|
+
async publishLocalChange(collection, id, source) {
|
|
2322
|
+
const snapshot = await this.getCurrentDocumentSnapshot(collection, id);
|
|
2323
|
+
this.store.emitDocument(collection, id, snapshot, source);
|
|
2324
|
+
await this.refreshCollectionTargets(collection, source, id);
|
|
2325
|
+
}
|
|
2326
|
+
async onReconnect() {
|
|
2327
|
+
for (const target of this.collectionTargets.values()) {
|
|
2328
|
+
await this.refreshCollectionTarget(target, "initial");
|
|
2329
|
+
}
|
|
2330
|
+
for (const target of this.documentTargets.values()) {
|
|
2331
|
+
await this.emitCurrentDocument(target.collection, target.id);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
dispose() {
|
|
2335
|
+
this.collectionTargets.forEach((target) => target.unsub());
|
|
2336
|
+
this.documentTargets.forEach((target) => target.unsub());
|
|
2337
|
+
this.collectionTargets.clear();
|
|
2338
|
+
this.documentTargets.clear();
|
|
1998
2339
|
}
|
|
1999
|
-
async handleRemoteCreateOrUpdate(collection, raw,
|
|
2340
|
+
async handleRemoteCreateOrUpdate(collection, raw, source) {
|
|
2000
2341
|
const id = raw.id ?? raw._id;
|
|
2001
2342
|
if (!id)
|
|
2002
2343
|
return;
|
|
2003
2344
|
const saved = await this.persistence.applyRemoteDoc(collection, raw);
|
|
2004
|
-
if (
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
this.
|
|
2345
|
+
if (saved) {
|
|
2346
|
+
const snap = DocumentSnapshot.fromMap(saved.data);
|
|
2347
|
+
this.store.emitDocument(collection, id, snap, source);
|
|
2348
|
+
}
|
|
2349
|
+
await this.refreshCollectionTargets(collection, source, id, false);
|
|
2009
2350
|
}
|
|
2010
2351
|
async handleRemoteDelete(collection, id) {
|
|
2011
2352
|
await this.persistence.applyRemoteDelete(collection, id);
|
|
2012
2353
|
this.store.emitDocument(collection, id, null, "delete");
|
|
2013
|
-
this.
|
|
2354
|
+
await this.refreshCollectionTargets(collection, "delete", id, false);
|
|
2014
2355
|
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
* Replays pending mutations + refreshes all active subscriptions from cache
|
|
2019
|
-
*/
|
|
2020
|
-
async onReconnect() {
|
|
2021
|
-
for (const [key] of this.collectionUnsubs) {
|
|
2022
|
-
const collection = key.split(":")[0];
|
|
2023
|
-
await this.emitCurrentCollection(collection);
|
|
2024
|
-
}
|
|
2025
|
-
for (const [key] of this.documentUnsubs) {
|
|
2026
|
-
const [collection, id] = key.split(":");
|
|
2027
|
-
await this.emitCurrentDocument(collection, id);
|
|
2028
|
-
}
|
|
2356
|
+
async getCurrentDocumentSnapshot(collection, id) {
|
|
2357
|
+
const localDoc = await this.persistence.getDoc(collection, id);
|
|
2358
|
+
return localDoc && localDoc.exists && !localDoc.deleted ? DocumentSnapshot.fromMap(localDoc.data) : null;
|
|
2029
2359
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2360
|
+
async refreshCollectionTargets(collection, source, changedDocId, fromCache = true) {
|
|
2361
|
+
const targets = Array.from(this.collectionTargets.values()).filter(
|
|
2362
|
+
(target) => target.collection === collection
|
|
2363
|
+
);
|
|
2364
|
+
for (const target of targets) {
|
|
2365
|
+
await this.refreshCollectionTarget(target, source, changedDocId, fromCache);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
async refreshCollectionTarget(target, source, changedDocId, fromCache = true) {
|
|
2369
|
+
const snapshots = await target.read();
|
|
2370
|
+
const childChanges = diffSnapshots(target.lastSnapshots, snapshots);
|
|
2371
|
+
target.lastSnapshots = snapshots;
|
|
2372
|
+
this.store.emitCollection(target.targetKey, {
|
|
2373
|
+
snapshots,
|
|
2374
|
+
source,
|
|
2375
|
+
changedDocId,
|
|
2376
|
+
fromCache,
|
|
2377
|
+
childChanges
|
|
2378
|
+
});
|
|
2379
|
+
return snapshots;
|
|
2380
|
+
}
|
|
2381
|
+
releaseCollection(targetKey) {
|
|
2382
|
+
const target = this.collectionTargets.get(targetKey);
|
|
2383
|
+
if (!target)
|
|
2384
|
+
return;
|
|
2385
|
+
target.refCount -= 1;
|
|
2386
|
+
if (target.refCount > 0)
|
|
2387
|
+
return;
|
|
2388
|
+
target.unsub();
|
|
2389
|
+
this.collectionTargets.delete(targetKey);
|
|
2390
|
+
}
|
|
2391
|
+
releaseDocument(key) {
|
|
2392
|
+
const target = this.documentTargets.get(key);
|
|
2393
|
+
if (!target)
|
|
2394
|
+
return;
|
|
2395
|
+
target.refCount -= 1;
|
|
2396
|
+
if (target.refCount > 0)
|
|
2397
|
+
return;
|
|
2398
|
+
target.unsub();
|
|
2399
|
+
this.documentTargets.delete(key);
|
|
2038
2400
|
}
|
|
2039
2401
|
};
|
|
2040
2402
|
|
|
@@ -2046,16 +2408,19 @@ var SyncEngine = class {
|
|
|
2046
2408
|
this.retryTimeout = null;
|
|
2047
2409
|
this.MAX_RETRIES = 5;
|
|
2048
2410
|
this.BASE_RETRY_DELAY = 2e3;
|
|
2411
|
+
this.handleOnline = () => this.onNetworkOnline();
|
|
2412
|
+
this.handleVisibilityChange = () => {
|
|
2413
|
+
if (document.visibilityState === "visible")
|
|
2414
|
+
this.onNetworkOnline();
|
|
2415
|
+
};
|
|
2049
2416
|
this.app = app;
|
|
2050
2417
|
this.persistence = persistence;
|
|
2051
2418
|
this.store = store;
|
|
2052
2419
|
this.realtimeBridge = realtimeBridge || null;
|
|
2420
|
+
this.persistence.recoverSyncingMutations().catch(console.error);
|
|
2053
2421
|
if (typeof window !== "undefined") {
|
|
2054
|
-
window.addEventListener("online",
|
|
2055
|
-
document.addEventListener("visibilitychange",
|
|
2056
|
-
if (document.visibilityState === "visible")
|
|
2057
|
-
this.onNetworkOnline();
|
|
2058
|
-
});
|
|
2422
|
+
window.addEventListener("online", this.handleOnline);
|
|
2423
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2059
2424
|
}
|
|
2060
2425
|
}
|
|
2061
2426
|
onNetworkOnline() {
|
|
@@ -2072,6 +2437,7 @@ var SyncEngine = class {
|
|
|
2072
2437
|
return;
|
|
2073
2438
|
this.syncing = true;
|
|
2074
2439
|
try {
|
|
2440
|
+
await this.persistence.recoverSyncingMutations();
|
|
2075
2441
|
const pending = await this.persistence.getPendingMutations();
|
|
2076
2442
|
for (const mutation of pending) {
|
|
2077
2443
|
if (mutation.retryCount >= this.MAX_RETRIES) {
|
|
@@ -2148,8 +2514,7 @@ var SyncEngine = class {
|
|
|
2148
2514
|
if (replaced) {
|
|
2149
2515
|
const snap = DocumentSnapshot.fromMap(replaced.data);
|
|
2150
2516
|
this.store.emitDocument(mutation.collection, oldId, snap, "insert");
|
|
2151
|
-
this.
|
|
2152
|
-
this.store.notifyCollectionChanged(mutation.collection, newId, "insert");
|
|
2517
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, newId, "insert");
|
|
2153
2518
|
}
|
|
2154
2519
|
return true;
|
|
2155
2520
|
}
|
|
@@ -2182,7 +2547,7 @@ var SyncEngine = class {
|
|
|
2182
2547
|
});
|
|
2183
2548
|
const snap = DocumentSnapshot.fromMap(local.data);
|
|
2184
2549
|
this.store.emitDocument(mutation.collection, mutation.documentId, snap, "update");
|
|
2185
|
-
this.
|
|
2550
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, mutation.documentId, "update");
|
|
2186
2551
|
return true;
|
|
2187
2552
|
}
|
|
2188
2553
|
async syncDelete(mutation) {
|
|
@@ -2199,7 +2564,7 @@ var SyncEngine = class {
|
|
|
2199
2564
|
return false;
|
|
2200
2565
|
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
2201
2566
|
this.store.emitDocument(mutation.collection, mutation.documentId, null, "delete");
|
|
2202
|
-
this.
|
|
2567
|
+
await this.realtimeBridge?.publishLocalChange(mutation.collection, mutation.documentId, "delete");
|
|
2203
2568
|
return true;
|
|
2204
2569
|
}
|
|
2205
2570
|
scheduleRetry() {
|
|
@@ -2219,8 +2584,7 @@ var SyncEngine = class {
|
|
|
2219
2584
|
* Returns mutations that exceeded MAX_RETRIES
|
|
2220
2585
|
*/
|
|
2221
2586
|
async getFailedMutations() {
|
|
2222
|
-
|
|
2223
|
-
return all.filter((m) => m.status === "failed");
|
|
2587
|
+
return this.persistence.getFailedMutations();
|
|
2224
2588
|
}
|
|
2225
2589
|
/**
|
|
2226
2590
|
* Retry a specific failed mutation by resetting its retry count
|
|
@@ -2262,6 +2626,10 @@ var SyncEngine = class {
|
|
|
2262
2626
|
if (this.retryTimeout) {
|
|
2263
2627
|
clearTimeout(this.retryTimeout);
|
|
2264
2628
|
}
|
|
2629
|
+
if (typeof window !== "undefined") {
|
|
2630
|
+
window.removeEventListener("online", this.handleOnline);
|
|
2631
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2632
|
+
}
|
|
2265
2633
|
}
|
|
2266
2634
|
};
|
|
2267
2635
|
|