edmaxlabs-core 1.3.4 → 1.3.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 +36 -53
- package/dist/index.cjs +1506 -785
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +257 -87
- package/dist/index.d.ts +257 -87
- package/dist/index.mjs +1506 -785
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -130,8 +130,8 @@ var _Authentication = class _Authentication {
|
|
|
130
130
|
}) => {
|
|
131
131
|
this.eUser = void 0;
|
|
132
132
|
this.saveCredentials(null);
|
|
133
|
-
const
|
|
134
|
-
const data = await
|
|
133
|
+
const app = this.app?.getDatabase;
|
|
134
|
+
const data = await app?.collection("users").query.where({
|
|
135
135
|
key: "email",
|
|
136
136
|
op: "===",
|
|
137
137
|
value: email
|
|
@@ -139,10 +139,10 @@ var _Authentication = class _Authentication {
|
|
|
139
139
|
if (data.length > 0) {
|
|
140
140
|
throw new Error("Email Already In Use.");
|
|
141
141
|
}
|
|
142
|
-
const userRef = await
|
|
142
|
+
const userRef = await app?.collection("users").add({
|
|
143
143
|
email,
|
|
144
144
|
password,
|
|
145
|
-
token: this.client?.
|
|
145
|
+
token: this.client?.getConfig().token,
|
|
146
146
|
logged: true,
|
|
147
147
|
lastLogged: Date.now()
|
|
148
148
|
});
|
|
@@ -157,8 +157,8 @@ var _Authentication = class _Authentication {
|
|
|
157
157
|
email,
|
|
158
158
|
password
|
|
159
159
|
}) => {
|
|
160
|
-
const
|
|
161
|
-
const data = await
|
|
160
|
+
const app = this.app?.getDatabase;
|
|
161
|
+
const data = await app?.collection("users").query.where({
|
|
162
162
|
key: "email",
|
|
163
163
|
op: "===",
|
|
164
164
|
value: email
|
|
@@ -174,7 +174,7 @@ var _Authentication = class _Authentication {
|
|
|
174
174
|
throw new Error("User Not Found.");
|
|
175
175
|
}
|
|
176
176
|
const _data = data[0];
|
|
177
|
-
await
|
|
177
|
+
await app?.collection("users").doc(data[0].id).update({
|
|
178
178
|
logged: true,
|
|
179
179
|
lastLogged: Date.now()
|
|
180
180
|
});
|
|
@@ -186,21 +186,21 @@ var _Authentication = class _Authentication {
|
|
|
186
186
|
if (!this.eUser) {
|
|
187
187
|
throw new Error("No User Signed in");
|
|
188
188
|
}
|
|
189
|
-
const
|
|
190
|
-
const userRef = await
|
|
191
|
-
if (userRef
|
|
189
|
+
const app = this.app?.getDatabase;
|
|
190
|
+
const userRef = await app?.collection("users").doc(this.eUser.uid).delete();
|
|
191
|
+
if (userRef) {
|
|
192
192
|
throw new Error("Something went wrong try Again.");
|
|
193
193
|
}
|
|
194
194
|
this.eUser = void 0;
|
|
195
195
|
this.saveCredentials(null);
|
|
196
196
|
};
|
|
197
197
|
this.signOut = async () => {
|
|
198
|
-
const
|
|
198
|
+
const app = this.app?.getDatabase;
|
|
199
199
|
const luid = this.currentUser();
|
|
200
200
|
this.eUser = void 0;
|
|
201
201
|
this.saveCredentials(null);
|
|
202
202
|
if (luid)
|
|
203
|
-
await
|
|
203
|
+
await app?.collection("users").doc(luid?.uid).update({
|
|
204
204
|
logged: false,
|
|
205
205
|
lastLogged: Date.now()
|
|
206
206
|
});
|
|
@@ -209,9 +209,9 @@ var _Authentication = class _Authentication {
|
|
|
209
209
|
this.rules = async (path, context) => {
|
|
210
210
|
const res = await new HttpsRequest({
|
|
211
211
|
method: "POST" /* POST */,
|
|
212
|
-
endpoint: this.client.getBaseUrl + "/auth/rules/verify",
|
|
212
|
+
endpoint: this.client.getBaseUrl() + "/auth/rules/verify",
|
|
213
213
|
headers: {
|
|
214
|
-
authorization: this.client.
|
|
214
|
+
authorization: this.client.getConfig().token
|
|
215
215
|
},
|
|
216
216
|
body: {
|
|
217
217
|
path,
|
|
@@ -225,7 +225,7 @@ var _Authentication = class _Authentication {
|
|
|
225
225
|
this.client = EdmaxLabs.instance;
|
|
226
226
|
this.app = new EdmaxLabs({
|
|
227
227
|
token: "auth",
|
|
228
|
-
project: this.client.
|
|
228
|
+
project: this.client.getConfig().project
|
|
229
229
|
});
|
|
230
230
|
_Authentication.instance = this;
|
|
231
231
|
this.restoreSession();
|
|
@@ -299,7 +299,7 @@ var _Authentication = class _Authentication {
|
|
|
299
299
|
onSignOut?.();
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
|
-
const userRef = this.app?.
|
|
302
|
+
const userRef = this.app?.getDatabase.collection("users").doc(creds.uid);
|
|
303
303
|
if (!userRef)
|
|
304
304
|
return;
|
|
305
305
|
userDocUnsubscribe = userRef.onSnapshot(
|
|
@@ -311,6 +311,8 @@ var _Authentication = class _Authentication {
|
|
|
311
311
|
return;
|
|
312
312
|
}
|
|
313
313
|
if (change === "insert" || change === "update") {
|
|
314
|
+
if (!snapshot)
|
|
315
|
+
return;
|
|
314
316
|
if (snapshot.data.logged === false || snapshot.data.logged === "false") {
|
|
315
317
|
this.saveCredentials(null);
|
|
316
318
|
onSignOut?.();
|
|
@@ -342,6 +344,25 @@ var _Authentication = class _Authentication {
|
|
|
342
344
|
_Authentication.instance = null;
|
|
343
345
|
var Authentication = _Authentication;
|
|
344
346
|
|
|
347
|
+
// src/database/DocumentSnapshot.ts
|
|
348
|
+
var DocumentSnapshot = class _DocumentSnapshot {
|
|
349
|
+
constructor(id, doc) {
|
|
350
|
+
this.id = id;
|
|
351
|
+
this.data = doc;
|
|
352
|
+
}
|
|
353
|
+
static fromMap(map) {
|
|
354
|
+
const id = map.id ?? map._id ?? "";
|
|
355
|
+
const document2 = map.document ?? map.documents ?? map.data ?? map;
|
|
356
|
+
return new _DocumentSnapshot(id, document2);
|
|
357
|
+
}
|
|
358
|
+
toMap() {
|
|
359
|
+
return {
|
|
360
|
+
id: this.id,
|
|
361
|
+
data: this.data
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
345
366
|
// src/database/Timestamp.ts
|
|
346
367
|
var Timestamp = class _Timestamp {
|
|
347
368
|
constructor(seconds, nanoseconds = 0) {
|
|
@@ -488,8 +509,8 @@ var ArraySnapshot = class _ArraySnapshot {
|
|
|
488
509
|
static fromMap(map) {
|
|
489
510
|
const index = map.index ?? map.index ?? -1;
|
|
490
511
|
const id = map.id ?? map._id ?? "";
|
|
491
|
-
const
|
|
492
|
-
return new _ArraySnapshot(id, index,
|
|
512
|
+
const document2 = map.data ?? map.document ?? map;
|
|
513
|
+
return new _ArraySnapshot(id, index, document2);
|
|
493
514
|
}
|
|
494
515
|
toMap() {
|
|
495
516
|
return {
|
|
@@ -501,87 +522,126 @@ var ArraySnapshot = class _ArraySnapshot {
|
|
|
501
522
|
};
|
|
502
523
|
|
|
503
524
|
// src/database/Array.ts
|
|
504
|
-
var
|
|
505
|
-
constructor(
|
|
506
|
-
this.
|
|
525
|
+
var Array2 = class {
|
|
526
|
+
constructor(app, collection, key, id) {
|
|
527
|
+
this.app = app;
|
|
507
528
|
this.collection = collection;
|
|
508
529
|
this.key = key;
|
|
509
530
|
this.docID = id;
|
|
531
|
+
this.persistence = app.offline().persistence;
|
|
532
|
+
this.syncEngine = app.offline().syncEngine;
|
|
533
|
+
this.localStore = app.offline().localStore;
|
|
510
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Get current array elements (offline-first: prefers local cache)
|
|
537
|
+
*/
|
|
511
538
|
async show() {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
},
|
|
518
|
-
body: {
|
|
519
|
-
collection: this.collection,
|
|
520
|
-
document: this.docID,
|
|
521
|
-
array: this.key
|
|
539
|
+
if (this.persistence) {
|
|
540
|
+
const doc = await this.persistence.getDoc(this.collection, this.docID);
|
|
541
|
+
if (doc?.exists && !doc.deleted) {
|
|
542
|
+
const arrayData = doc.data[this.key] || [];
|
|
543
|
+
return arrayData.map((item) => ArraySnapshot.fromMap(item));
|
|
522
544
|
}
|
|
523
|
-
}
|
|
524
|
-
if (
|
|
525
|
-
|
|
526
|
-
if (d === void 0)
|
|
527
|
-
return [];
|
|
528
|
-
return ArraySnapshot.fromMap(d);
|
|
545
|
+
}
|
|
546
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
547
|
+
this.refreshFromRemote().catch(() => {
|
|
529
548
|
});
|
|
530
|
-
}
|
|
549
|
+
}
|
|
550
|
+
return [];
|
|
551
|
+
}
|
|
552
|
+
async refreshFromRemote() {
|
|
553
|
+
try {
|
|
554
|
+
const res = await new HttpsRequest({
|
|
555
|
+
method: "POST" /* POST */,
|
|
556
|
+
endpoint: `${this.app.getBaseUrl()}/app/array/show`,
|
|
557
|
+
headers: { authorization: this.app.getConfig().token },
|
|
558
|
+
body: {
|
|
559
|
+
collection: this.collection,
|
|
560
|
+
document: this.docID,
|
|
561
|
+
array: this.key
|
|
562
|
+
}
|
|
563
|
+
}).sendRequest();
|
|
564
|
+
if (!res?.success || !globalThis.Array.isArray(res.documents)) {
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
const snapshots = res.documents.filter((d) => d != null).map((d) => ArraySnapshot.fromMap(d));
|
|
568
|
+
if (this.persistence) {
|
|
569
|
+
const currentDoc = await this.persistence.getDoc(this.collection, this.docID);
|
|
570
|
+
if (currentDoc) {
|
|
571
|
+
await this.persistence.upsertDoc({
|
|
572
|
+
...currentDoc,
|
|
573
|
+
data: { ...currentDoc.data, [this.key]: res.documents },
|
|
574
|
+
pending: 0,
|
|
575
|
+
status: "synced",
|
|
576
|
+
lastSyncedAt: Date.now()
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return snapshots;
|
|
581
|
+
} catch {
|
|
531
582
|
return [];
|
|
532
583
|
}
|
|
533
584
|
}
|
|
534
585
|
async push(data, id) {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
586
|
+
const payload = {
|
|
587
|
+
data,
|
|
588
|
+
id: id || this.persistence?.createLocalId?.() || void 0,
|
|
589
|
+
dt: Timestamp.now()
|
|
590
|
+
};
|
|
591
|
+
if (this.persistence) {
|
|
592
|
+
await this.persistence.enqueueMutation({
|
|
593
|
+
mutationId: this.persistence.createMutationId(),
|
|
542
594
|
collection: this.collection,
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
595
|
+
documentId: this.docID,
|
|
596
|
+
type: "array_push",
|
|
597
|
+
// custom type
|
|
598
|
+
payload: { arrayKey: this.key, ...payload }
|
|
599
|
+
});
|
|
600
|
+
this.syncEngine?.flush().catch(console.error);
|
|
601
|
+
const doc = await this.persistence.getDoc(this.collection, this.docID);
|
|
602
|
+
if (doc) {
|
|
603
|
+
const updatedArray = [...doc.data[this.key] || [], payload];
|
|
604
|
+
const snap = ArraySnapshot.fromMap(payload);
|
|
605
|
+
this.localStore?.emitDocument(
|
|
606
|
+
this.collection,
|
|
607
|
+
this.docID,
|
|
608
|
+
DocumentSnapshot.fromMap({ ...doc.data, [this.key]: updatedArray }),
|
|
609
|
+
"local_update"
|
|
610
|
+
);
|
|
548
611
|
}
|
|
549
|
-
|
|
550
|
-
if (res.success) {
|
|
551
|
-
return ArraySnapshot.fromMap(res.document);
|
|
552
|
-
} else {
|
|
553
|
-
return null;
|
|
612
|
+
return ArraySnapshot.fromMap(payload);
|
|
554
613
|
}
|
|
555
|
-
}
|
|
556
|
-
async update(position, data, id) {
|
|
557
614
|
const res = await new HttpsRequest({
|
|
558
615
|
method: "POST" /* POST */,
|
|
559
|
-
endpoint: this.
|
|
560
|
-
headers: {
|
|
561
|
-
authorization: this.db.getAuth.token
|
|
562
|
-
},
|
|
616
|
+
endpoint: `${this.app.getBaseUrl()}/app/array/push`,
|
|
617
|
+
headers: { authorization: this.app.getConfig().token },
|
|
563
618
|
body: {
|
|
564
619
|
collection: this.collection,
|
|
565
620
|
document: this.docID,
|
|
566
621
|
array: this.key,
|
|
567
|
-
|
|
568
|
-
data,
|
|
569
|
-
id
|
|
622
|
+
...payload
|
|
570
623
|
}
|
|
571
624
|
}).sendRequest();
|
|
572
|
-
|
|
573
|
-
return ArraySnapshot.fromMap(res.document);
|
|
574
|
-
} else {
|
|
575
|
-
return null;
|
|
576
|
-
}
|
|
625
|
+
return res?.success ? ArraySnapshot.fromMap(res.document) : null;
|
|
577
626
|
}
|
|
578
|
-
|
|
627
|
+
// Similar pattern for update, insert, remove, get...
|
|
628
|
+
// (I'll show one more as example; apply the same logic to the rest)
|
|
629
|
+
async update(position, data, id) {
|
|
630
|
+
if (this.persistence) {
|
|
631
|
+
await this.persistence.enqueueMutation({
|
|
632
|
+
mutationId: this.persistence.createMutationId(),
|
|
633
|
+
collection: this.collection,
|
|
634
|
+
documentId: this.docID,
|
|
635
|
+
type: "array_update",
|
|
636
|
+
payload: { arrayKey: this.key, position, data, id }
|
|
637
|
+
});
|
|
638
|
+
this.syncEngine?.flush().catch(console.error);
|
|
639
|
+
return ArraySnapshot.fromMap({ ...data, id });
|
|
640
|
+
}
|
|
579
641
|
const res = await new HttpsRequest({
|
|
580
642
|
method: "POST" /* POST */,
|
|
581
|
-
endpoint: this.
|
|
582
|
-
headers: {
|
|
583
|
-
authorization: this.db.getAuth.token
|
|
584
|
-
},
|
|
643
|
+
endpoint: `${this.app.getBaseUrl()}/app/array/update`,
|
|
644
|
+
headers: { authorization: this.app.getConfig().token },
|
|
585
645
|
body: {
|
|
586
646
|
collection: this.collection,
|
|
587
647
|
document: this.docID,
|
|
@@ -591,212 +651,865 @@ var Array = class {
|
|
|
591
651
|
id
|
|
592
652
|
}
|
|
593
653
|
}).sendRequest();
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
654
|
+
return res?.success ? ArraySnapshot.fromMap(res.document) : null;
|
|
655
|
+
}
|
|
656
|
+
// TODO: Implement insert(), get(), remove() using the exact same offline pattern as push/update
|
|
657
|
+
async insert(position, data, id) {
|
|
658
|
+
return null;
|
|
599
659
|
}
|
|
600
660
|
async get(position) {
|
|
601
|
-
|
|
602
|
-
method: "POST" /* POST */,
|
|
603
|
-
endpoint: this.db.getBaseUrl + "/db/array/read",
|
|
604
|
-
headers: {
|
|
605
|
-
authorization: this.db.getAuth.token
|
|
606
|
-
},
|
|
607
|
-
body: {
|
|
608
|
-
collection: this.collection,
|
|
609
|
-
document: this.docID,
|
|
610
|
-
array: this.key,
|
|
611
|
-
position
|
|
612
|
-
}
|
|
613
|
-
}).sendRequest();
|
|
614
|
-
if (res.success) {
|
|
615
|
-
return ArraySnapshot.fromMap(res.document);
|
|
616
|
-
} else {
|
|
617
|
-
return null;
|
|
618
|
-
}
|
|
661
|
+
return null;
|
|
619
662
|
}
|
|
620
663
|
async remove(position) {
|
|
621
|
-
|
|
622
|
-
method: "POST" /* POST */,
|
|
623
|
-
endpoint: this.db.getBaseUrl + "/db/array/remove",
|
|
624
|
-
headers: {
|
|
625
|
-
authorization: this.db.getAuth.token
|
|
626
|
-
},
|
|
627
|
-
body: {
|
|
628
|
-
collection: this.collection,
|
|
629
|
-
document: this.docID,
|
|
630
|
-
array: this.key,
|
|
631
|
-
position
|
|
632
|
-
}
|
|
633
|
-
}).sendRequest();
|
|
634
|
-
if (res.success) {
|
|
635
|
-
return ArraySnapshot.fromMap(res.document);
|
|
636
|
-
} else {
|
|
637
|
-
return null;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
|
-
|
|
642
|
-
// src/database/DocumentSnapshot.ts
|
|
643
|
-
var DocumentSnapshot = class _DocumentSnapshot {
|
|
644
|
-
constructor(id, doc) {
|
|
645
|
-
this.id = id;
|
|
646
|
-
this.data = doc;
|
|
647
|
-
}
|
|
648
|
-
static fromMap(map) {
|
|
649
|
-
const id = map.id ?? map._id ?? "";
|
|
650
|
-
const document = map.document ?? map.documents ?? map.data ?? map;
|
|
651
|
-
return new _DocumentSnapshot(id, document);
|
|
652
|
-
}
|
|
653
|
-
toMap() {
|
|
654
|
-
return {
|
|
655
|
-
id: this.id,
|
|
656
|
-
data: this.data
|
|
657
|
-
};
|
|
664
|
+
return null;
|
|
658
665
|
}
|
|
659
666
|
};
|
|
660
667
|
|
|
661
668
|
// src/database/DocumentRef.ts
|
|
662
669
|
var DocumentRef = class {
|
|
663
|
-
constructor(
|
|
664
|
-
this.
|
|
670
|
+
constructor(app, collection, id) {
|
|
671
|
+
this.app = app;
|
|
665
672
|
this.collection = collection;
|
|
666
673
|
this.id = id;
|
|
674
|
+
this.persistence = app.offline().persistence;
|
|
675
|
+
this.syncEngine = app.offline().syncEngine;
|
|
676
|
+
this.localStore = app.offline().localStore;
|
|
667
677
|
}
|
|
668
678
|
async get() {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
679
|
+
if (this.persistence) {
|
|
680
|
+
const localSnap = await this.persistence.getDocSnapshot(this.collection, this.id);
|
|
681
|
+
if (localSnap)
|
|
682
|
+
return localSnap;
|
|
683
|
+
}
|
|
684
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
685
|
+
this.refreshFromRemote().catch(() => {
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
async refreshFromRemote() {
|
|
691
|
+
try {
|
|
692
|
+
const res = await new HttpsRequest({
|
|
693
|
+
method: "POST" /* POST */,
|
|
694
|
+
endpoint: `${this.app.getBaseUrl()}/app/read`,
|
|
695
|
+
headers: { authorization: this.app.getConfig().token },
|
|
696
|
+
body: {
|
|
697
|
+
collection: this.collection,
|
|
698
|
+
document: this.id
|
|
699
|
+
}
|
|
700
|
+
}).sendRequest();
|
|
701
|
+
if (!res?.success || !res.document)
|
|
682
702
|
return null;
|
|
683
|
-
|
|
684
|
-
|
|
703
|
+
const doc = {
|
|
704
|
+
...res.document,
|
|
705
|
+
id: res.document.id ?? res.document._id ?? this.id
|
|
706
|
+
};
|
|
707
|
+
delete doc._id;
|
|
708
|
+
if (this.persistence) {
|
|
709
|
+
await this.persistence.applyRemoteDoc(this.collection, doc);
|
|
710
|
+
}
|
|
711
|
+
const snap = DocumentSnapshot.fromMap(doc);
|
|
712
|
+
this.localStore?.emitDocument(this.collection, this.id, snap, "remote_update");
|
|
713
|
+
return snap;
|
|
714
|
+
} catch {
|
|
685
715
|
return null;
|
|
686
716
|
}
|
|
687
717
|
}
|
|
688
718
|
async set(data) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
authorization: this.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
document: this.id,
|
|
698
|
-
data
|
|
699
|
-
}
|
|
700
|
-
}).sendRequest();
|
|
701
|
-
if (res.success) {
|
|
702
|
-
if (res.document === void 0)
|
|
703
|
-
return null;
|
|
704
|
-
return DocumentSnapshot.fromMap(res.document);
|
|
705
|
-
} else {
|
|
706
|
-
return null;
|
|
719
|
+
if (!this.persistence) {
|
|
720
|
+
const res = await new HttpsRequest({
|
|
721
|
+
method: "POST" /* POST */,
|
|
722
|
+
endpoint: `${this.app.getBaseUrl()}/app/create`,
|
|
723
|
+
headers: { authorization: this.app.getConfig().token },
|
|
724
|
+
body: { collection: this.collection, data: { ...data, id: this.id } }
|
|
725
|
+
}).sendRequest();
|
|
726
|
+
return res?.success ? DocumentSnapshot.fromMap(data) : null;
|
|
707
727
|
}
|
|
728
|
+
const updated = await this.persistence.upsertDoc({
|
|
729
|
+
collection: this.collection,
|
|
730
|
+
id: this.id,
|
|
731
|
+
data: { ...data, id: this.id },
|
|
732
|
+
exists: true,
|
|
733
|
+
deleted: false,
|
|
734
|
+
pending: 1,
|
|
735
|
+
localOnly: false,
|
|
736
|
+
status: "pending"
|
|
737
|
+
});
|
|
738
|
+
await this.persistence.enqueueMutation({
|
|
739
|
+
mutationId: this.persistence.createMutationId(),
|
|
740
|
+
collection: this.collection,
|
|
741
|
+
documentId: this.id,
|
|
742
|
+
type: "update",
|
|
743
|
+
// or "create" if you want to distinguish truly new docs
|
|
744
|
+
payload: data
|
|
745
|
+
});
|
|
746
|
+
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
747
|
+
this.localStore?.emitDocument(this.collection, this.id, snap, "local_update");
|
|
748
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(this.collection);
|
|
749
|
+
this.localStore?.emitCollection(this.collection, currentCollection, "local_update", this.id);
|
|
750
|
+
this.syncEngine?.flush().catch(console.error);
|
|
751
|
+
return snap;
|
|
708
752
|
}
|
|
709
753
|
async update(data) {
|
|
710
|
-
if (this.
|
|
754
|
+
if (!this.persistence)
|
|
711
755
|
return null;
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
endpoint: this.db.getBaseUrl + "/db/update",
|
|
715
|
-
headers: {
|
|
716
|
-
authorization: this.db.getAuth.token
|
|
717
|
-
},
|
|
718
|
-
body: {
|
|
719
|
-
collection: this.collection,
|
|
720
|
-
document: this.id,
|
|
721
|
-
data
|
|
722
|
-
}
|
|
723
|
-
}).sendRequest();
|
|
724
|
-
if (res.success) {
|
|
725
|
-
if (res.document === void 0)
|
|
726
|
-
return null;
|
|
727
|
-
return DocumentSnapshot.fromMap(res.document);
|
|
728
|
-
} else {
|
|
756
|
+
const old = await this.persistence.getDoc(this.collection, this.id);
|
|
757
|
+
if (!old || old.deleted)
|
|
729
758
|
return null;
|
|
730
|
-
}
|
|
759
|
+
const mergedData = { ...old.data, ...data, id: this.id };
|
|
760
|
+
const updated = await this.persistence.upsertDoc({
|
|
761
|
+
collection: this.collection,
|
|
762
|
+
id: this.id,
|
|
763
|
+
data: mergedData,
|
|
764
|
+
exists: true,
|
|
765
|
+
deleted: false,
|
|
766
|
+
pending: 1,
|
|
767
|
+
localOnly: old.localOnly,
|
|
768
|
+
status: "pending",
|
|
769
|
+
revision: old.revision,
|
|
770
|
+
lastSyncedAt: old.lastSyncedAt
|
|
771
|
+
});
|
|
772
|
+
await this.persistence.enqueueMutation({
|
|
773
|
+
mutationId: this.persistence.createMutationId(),
|
|
774
|
+
collection: this.collection,
|
|
775
|
+
documentId: this.id,
|
|
776
|
+
type: "update",
|
|
777
|
+
payload: data,
|
|
778
|
+
baseRevision: old.revision
|
|
779
|
+
});
|
|
780
|
+
const snap = DocumentSnapshot.fromMap(updated.data);
|
|
781
|
+
this.localStore?.emitDocument(this.collection, this.id, snap, "local_update");
|
|
782
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(this.collection);
|
|
783
|
+
this.localStore?.emitCollection(this.collection, currentCollection, "local_update", this.id);
|
|
784
|
+
this.syncEngine?.flush().catch(console.error);
|
|
785
|
+
return snap;
|
|
731
786
|
}
|
|
732
787
|
async delete() {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
} else {
|
|
749
|
-
return null;
|
|
750
|
-
}
|
|
788
|
+
if (!this.persistence)
|
|
789
|
+
return false;
|
|
790
|
+
await this.persistence.markDeleted(this.collection, this.id, 1);
|
|
791
|
+
await this.persistence.enqueueMutation({
|
|
792
|
+
mutationId: this.persistence.createMutationId(),
|
|
793
|
+
collection: this.collection,
|
|
794
|
+
documentId: this.id,
|
|
795
|
+
type: "delete",
|
|
796
|
+
payload: null
|
|
797
|
+
});
|
|
798
|
+
this.localStore?.emitDocument(this.collection, this.id, null, "local_delete");
|
|
799
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(this.collection);
|
|
800
|
+
this.localStore?.emitCollection(this.collection, currentCollection, "local_delete", this.id);
|
|
801
|
+
this.syncEngine?.flush().catch(console.error);
|
|
802
|
+
return true;
|
|
751
803
|
}
|
|
752
804
|
array(key) {
|
|
753
|
-
return new
|
|
805
|
+
return new Array2(this.app, this.collection, key, this.id);
|
|
754
806
|
}
|
|
755
|
-
/**
|
|
756
|
-
* Realtime listener for a single document
|
|
757
|
-
*/
|
|
758
807
|
onSnapshot(callback) {
|
|
759
|
-
|
|
808
|
+
this.app.offline().realtimeBridge?.watchDocument(this.collection, this.id);
|
|
809
|
+
return this.localStore?.subscribeToDocument(
|
|
810
|
+
this.collection,
|
|
811
|
+
this.id,
|
|
812
|
+
callback
|
|
813
|
+
) ?? (() => {
|
|
814
|
+
});
|
|
760
815
|
}
|
|
761
816
|
};
|
|
762
817
|
|
|
763
|
-
//
|
|
764
|
-
var
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
818
|
+
// src/database/Query.ts
|
|
819
|
+
var Query = class {
|
|
820
|
+
constructor(app, collection) {
|
|
821
|
+
this.filter = [];
|
|
822
|
+
this.app = app;
|
|
823
|
+
this.collection = collection;
|
|
824
|
+
}
|
|
825
|
+
where(expression) {
|
|
826
|
+
this.filter.push(expression);
|
|
827
|
+
return this;
|
|
828
|
+
}
|
|
829
|
+
buildFilter() {
|
|
830
|
+
const mongoFilter = {};
|
|
831
|
+
this.filter.forEach(({ key, op, value }) => {
|
|
832
|
+
switch (op) {
|
|
833
|
+
case "==":
|
|
834
|
+
case "===":
|
|
835
|
+
mongoFilter[key] = { $eq: value };
|
|
836
|
+
break;
|
|
837
|
+
case "!=":
|
|
838
|
+
mongoFilter[key] = { $ne: value };
|
|
839
|
+
break;
|
|
840
|
+
case "<":
|
|
841
|
+
mongoFilter[key] = { $lt: value };
|
|
842
|
+
break;
|
|
843
|
+
case "<=":
|
|
844
|
+
mongoFilter[key] = { $lte: value };
|
|
845
|
+
break;
|
|
846
|
+
case ">":
|
|
847
|
+
mongoFilter[key] = { $gt: value };
|
|
848
|
+
break;
|
|
849
|
+
case ">=":
|
|
850
|
+
mongoFilter[key] = { $gte: value };
|
|
851
|
+
break;
|
|
852
|
+
case "in":
|
|
853
|
+
mongoFilter[key] = { $in: value };
|
|
854
|
+
break;
|
|
855
|
+
case "nin":
|
|
856
|
+
mongoFilter[key] = { $nin: value };
|
|
857
|
+
break;
|
|
858
|
+
case "contains":
|
|
859
|
+
mongoFilter[key] = { $regex: value, $options: "i" };
|
|
860
|
+
break;
|
|
861
|
+
default:
|
|
862
|
+
throw new Error(`Unknown operator: ${op}`);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
return mongoFilter;
|
|
866
|
+
}
|
|
867
|
+
async get() {
|
|
868
|
+
const persistence = this.app.offline().persistence;
|
|
869
|
+
if (persistence) {
|
|
870
|
+
const local = await persistence.getCollectionSnapshots(this.collection);
|
|
871
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
872
|
+
this.refreshFromRemote().catch(() => {
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
return local;
|
|
876
|
+
}
|
|
877
|
+
return this.refreshFromRemote();
|
|
878
|
+
}
|
|
879
|
+
async refreshFromRemote() {
|
|
880
|
+
try {
|
|
881
|
+
const genfilter = this.buildFilter();
|
|
882
|
+
const res = await new HttpsRequest({
|
|
883
|
+
method: "POST" /* POST */,
|
|
884
|
+
endpoint: `${this.app.getBaseUrl()}/app/read`,
|
|
885
|
+
headers: { authorization: this.app.getConfig().token },
|
|
886
|
+
body: {
|
|
887
|
+
collection: this.collection,
|
|
888
|
+
filter: genfilter
|
|
889
|
+
}
|
|
890
|
+
}).sendRequest();
|
|
891
|
+
if (!res?.success || !Array.isArray(res.documents)) {
|
|
892
|
+
return [];
|
|
893
|
+
}
|
|
894
|
+
return res.documents.map((d) => {
|
|
895
|
+
delete d.token;
|
|
896
|
+
d.id = d.id ?? d._id;
|
|
897
|
+
delete d._id;
|
|
898
|
+
return DocumentSnapshot.fromMap(d);
|
|
899
|
+
});
|
|
900
|
+
} catch {
|
|
901
|
+
return [];
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
// Advanced: query-wide update (keep for now, but document it's server-side only)
|
|
905
|
+
async update(data) {
|
|
906
|
+
const genfilter = this.buildFilter();
|
|
907
|
+
const res = await new HttpsRequest({
|
|
908
|
+
method: "POST" /* POST */,
|
|
909
|
+
endpoint: `${this.app.getBaseUrl()}/app/update`,
|
|
910
|
+
headers: { authorization: this.app.getConfig().token },
|
|
911
|
+
body: {
|
|
912
|
+
collection: this.collection,
|
|
913
|
+
filter: genfilter,
|
|
914
|
+
data
|
|
915
|
+
}
|
|
916
|
+
}).sendRequest();
|
|
917
|
+
return res;
|
|
918
|
+
}
|
|
919
|
+
onSnapshot(callback) {
|
|
920
|
+
const genfilter = this.buildFilter();
|
|
921
|
+
this.app.rtdb().subscribeToCollectionRaw?.(
|
|
922
|
+
this.collection,
|
|
923
|
+
(payload, change) => {
|
|
924
|
+
},
|
|
925
|
+
genfilter
|
|
926
|
+
);
|
|
927
|
+
return this.app.offline().localStore?.subscribeToCollection(
|
|
928
|
+
this.collection,
|
|
929
|
+
callback
|
|
930
|
+
) ?? (() => {
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
// src/database/CollectionRef.ts
|
|
936
|
+
var CollectionRef = class {
|
|
937
|
+
constructor(app, collection) {
|
|
938
|
+
this.app = app;
|
|
939
|
+
this.collection = collection;
|
|
940
|
+
this.persistence = app.offline().persistence;
|
|
941
|
+
this.syncEngine = app.offline().syncEngine;
|
|
942
|
+
this.localStore = app.offline().localStore;
|
|
943
|
+
}
|
|
944
|
+
doc(id) {
|
|
945
|
+
return new DocumentRef(this.app, this.collection, id);
|
|
946
|
+
}
|
|
947
|
+
get query() {
|
|
948
|
+
return new Query(this.app, this.collection);
|
|
949
|
+
}
|
|
950
|
+
async get() {
|
|
951
|
+
if (!this.persistence)
|
|
952
|
+
return [];
|
|
953
|
+
const local = await this.persistence.getCollectionSnapshots(this.collection);
|
|
954
|
+
if (typeof navigator === "undefined" || navigator.onLine) {
|
|
955
|
+
this.refreshFromRemote().catch(() => {
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
return local;
|
|
959
|
+
}
|
|
960
|
+
async refreshFromRemote() {
|
|
961
|
+
try {
|
|
962
|
+
const res = await new HttpsRequest({
|
|
963
|
+
method: "POST" /* POST */,
|
|
964
|
+
endpoint: `${this.app.getBaseUrl()}/app/read`,
|
|
965
|
+
headers: { authorization: this.app.getConfig().token, project: this.app.getConfig().project },
|
|
966
|
+
body: {
|
|
967
|
+
collection: this.collection,
|
|
968
|
+
filter: {}
|
|
969
|
+
}
|
|
970
|
+
}).sendRequest();
|
|
971
|
+
if (!res?.success || !Array.isArray(res.documents)) {
|
|
972
|
+
return [];
|
|
973
|
+
}
|
|
974
|
+
for (const raw of res.documents) {
|
|
975
|
+
const doc = { ...raw };
|
|
976
|
+
doc.id = doc.id ?? doc._id;
|
|
977
|
+
delete doc._id;
|
|
978
|
+
delete doc.token;
|
|
979
|
+
if (this.persistence) {
|
|
980
|
+
await this.persistence.applyRemoteDoc(this.collection, doc);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (this.persistence) {
|
|
984
|
+
const updatedCollection = await this.persistence.getCollectionSnapshots(this.collection);
|
|
985
|
+
this.localStore?.emitCollection(this.collection, updatedCollection, "remote_update");
|
|
986
|
+
return updatedCollection;
|
|
987
|
+
}
|
|
988
|
+
return [];
|
|
989
|
+
} catch {
|
|
990
|
+
return [];
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
async add(data) {
|
|
994
|
+
if (!this.persistence)
|
|
995
|
+
return null;
|
|
996
|
+
const localId = this.persistence.createLocalId();
|
|
997
|
+
const docRecord = await this.persistence.upsertDoc({
|
|
998
|
+
collection: this.collection,
|
|
999
|
+
id: localId,
|
|
1000
|
+
data: { ...data, id: localId },
|
|
1001
|
+
exists: true,
|
|
1002
|
+
deleted: false,
|
|
1003
|
+
pending: 1,
|
|
1004
|
+
localOnly: true,
|
|
1005
|
+
status: "pending"
|
|
1006
|
+
});
|
|
1007
|
+
await this.persistence.enqueueMutation({
|
|
1008
|
+
mutationId: this.persistence.createMutationId(),
|
|
1009
|
+
collection: this.collection,
|
|
1010
|
+
documentId: localId,
|
|
1011
|
+
type: "create",
|
|
1012
|
+
payload: docRecord.data
|
|
1013
|
+
});
|
|
1014
|
+
const snap = DocumentSnapshot.fromMap(docRecord.data);
|
|
1015
|
+
this.localStore?.emitDocument(this.collection, localId, snap, "local_create");
|
|
1016
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(this.collection);
|
|
1017
|
+
this.localStore?.emitCollection(this.collection, currentCollection, "local_create", localId);
|
|
1018
|
+
this.syncEngine?.flush().catch(console.error);
|
|
1019
|
+
return snap;
|
|
1020
|
+
}
|
|
1021
|
+
onSnapshot(callback) {
|
|
1022
|
+
this.app.offline().realtimeBridge?.watchCollection(this.collection);
|
|
1023
|
+
return this.localStore?.subscribeToCollection(this.collection, callback) ?? (() => {
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// src/database/Batch.ts
|
|
1029
|
+
var Batch = class {
|
|
1030
|
+
constructor(app) {
|
|
1031
|
+
this.ops = [];
|
|
1032
|
+
this.app = app;
|
|
1033
|
+
}
|
|
1034
|
+
set(docRef, data) {
|
|
1035
|
+
this.ops.push({
|
|
1036
|
+
op: "set",
|
|
1037
|
+
collection: docRef.collection,
|
|
1038
|
+
id: docRef.id,
|
|
1039
|
+
data
|
|
1040
|
+
});
|
|
1041
|
+
return this;
|
|
1042
|
+
}
|
|
1043
|
+
update(docRef, data) {
|
|
1044
|
+
this.ops.push({
|
|
1045
|
+
op: "update",
|
|
1046
|
+
collection: docRef.collection,
|
|
1047
|
+
id: docRef.id,
|
|
1048
|
+
data
|
|
1049
|
+
});
|
|
1050
|
+
return this;
|
|
1051
|
+
}
|
|
1052
|
+
delete(docRef) {
|
|
1053
|
+
this.ops.push({
|
|
1054
|
+
op: "delete",
|
|
1055
|
+
collection: docRef.collection,
|
|
1056
|
+
id: docRef.id
|
|
1057
|
+
});
|
|
1058
|
+
return this;
|
|
1059
|
+
}
|
|
1060
|
+
async commit() {
|
|
1061
|
+
const persistence = this.app.offline().persistence;
|
|
1062
|
+
const localStore = this.app.offline().localStore;
|
|
1063
|
+
const syncEngine = this.app.offline().syncEngine;
|
|
1064
|
+
if (persistence && localStore) {
|
|
1065
|
+
const results = [];
|
|
1066
|
+
for (const op of this.ops) {
|
|
1067
|
+
try {
|
|
1068
|
+
if (op.op === "set" || op.op === "update") {
|
|
1069
|
+
const docRef = new DocumentRef(this.app, op.collection, op.id);
|
|
1070
|
+
const snap = await docRef.set(op.data);
|
|
1071
|
+
if (snap)
|
|
1072
|
+
results.push(snap);
|
|
1073
|
+
} else if (op.op === "delete") {
|
|
1074
|
+
const docRef = new DocumentRef(this.app, op.collection, op.id);
|
|
1075
|
+
await docRef.delete();
|
|
1076
|
+
}
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
console.error(`[EdmaxLabs Batch] Failed optimistic ${op.op}`, e);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
syncEngine?.flush().catch(console.error);
|
|
1082
|
+
return { success: true, results };
|
|
1083
|
+
}
|
|
1084
|
+
const res = await new HttpsRequest({
|
|
1085
|
+
method: "POST" /* POST */,
|
|
1086
|
+
endpoint: `${this.app.getBaseUrl}/app/batch`,
|
|
1087
|
+
headers: {
|
|
1088
|
+
authorization: this.app.getConfig().token
|
|
1089
|
+
},
|
|
1090
|
+
body: { ops: this.ops }
|
|
1091
|
+
}).sendRequest();
|
|
1092
|
+
if (res?.error) {
|
|
1093
|
+
throw new Error(res.error.message || "Batch commit failed");
|
|
1094
|
+
}
|
|
1095
|
+
return res;
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
// src/database/Database.ts
|
|
1100
|
+
var Database = class {
|
|
1101
|
+
constructor(app) {
|
|
1102
|
+
this.app = app;
|
|
1103
|
+
}
|
|
1104
|
+
collection(name) {
|
|
1105
|
+
return new CollectionRef(this.app, name);
|
|
1106
|
+
}
|
|
1107
|
+
doc(path) {
|
|
1108
|
+
if (path.includes("/")) {
|
|
1109
|
+
const [col, id] = path.split("/");
|
|
1110
|
+
return new DocumentRef(this.app, col, id);
|
|
1111
|
+
}
|
|
1112
|
+
throw new Error("doc(path) expects format 'collection/documentId'");
|
|
1113
|
+
}
|
|
1114
|
+
batch() {
|
|
1115
|
+
return new Batch(this.app);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Server-side transaction (unchanged, but improved error handling)
|
|
1119
|
+
* This remains online-only for now — offline transactions are complex and can be added later as opt-in.
|
|
1120
|
+
*/
|
|
1121
|
+
async runTransaction(transactionFn) {
|
|
1122
|
+
const tx = {
|
|
1123
|
+
ops: [],
|
|
1124
|
+
async get(docRef) {
|
|
1125
|
+
return docRef.get();
|
|
1126
|
+
},
|
|
1127
|
+
set(docRef, data) {
|
|
1128
|
+
tx.ops.push({ op: "set", collection: docRef.collection, id: docRef.id, data });
|
|
1129
|
+
},
|
|
1130
|
+
update(docRef, data) {
|
|
1131
|
+
tx.ops.push({ op: "update", collection: docRef.collection, id: docRef.id, data });
|
|
1132
|
+
},
|
|
1133
|
+
delete(docRef) {
|
|
1134
|
+
tx.ops.push({ op: "delete", collection: docRef.collection, id: docRef.id });
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
await transactionFn(tx);
|
|
1138
|
+
const res = await new HttpsRequest({
|
|
1139
|
+
method: "POST" /* POST */,
|
|
1140
|
+
endpoint: `${this.app.getBaseUrl}/app/transaction`,
|
|
1141
|
+
headers: {
|
|
1142
|
+
authorization: this.app.getConfig().token
|
|
1143
|
+
},
|
|
1144
|
+
body: { ops: tx.ops }
|
|
1145
|
+
}).sendRequest();
|
|
1146
|
+
if (res?.error) {
|
|
1147
|
+
throw new Error(res.error.message || "Transaction failed");
|
|
1148
|
+
}
|
|
1149
|
+
return res;
|
|
1150
|
+
}
|
|
1151
|
+
// ===================== OFFLINE HELPERS (Internal) =====================
|
|
1152
|
+
/**
|
|
1153
|
+
* Returns true if persistence is enabled and we are currently offline
|
|
1154
|
+
*/
|
|
1155
|
+
get shouldUseOffline() {
|
|
1156
|
+
return !!(this.app.offline().persistence && typeof navigator !== "undefined" && !navigator.onLine);
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Returns the SyncEngine if persistence is enabled
|
|
1160
|
+
*/
|
|
1161
|
+
get syncEngine() {
|
|
1162
|
+
return this.app.offline().syncEngine;
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Returns the RealtimeBridge if persistence is enabled
|
|
1166
|
+
*/
|
|
1167
|
+
get realtimeBridge() {
|
|
1168
|
+
return this.app.offline().realtimeBridge;
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
// src/database/Realtime.ts
|
|
1173
|
+
var import_socket = require("socket.io-client");
|
|
1174
|
+
|
|
1175
|
+
// src/utils/uuid.ts
|
|
1176
|
+
function generateUUID() {
|
|
1177
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1178
|
+
return crypto.randomUUID();
|
|
1179
|
+
}
|
|
1180
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1181
|
+
const r = Math.random() * 16 | 0;
|
|
1182
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
1183
|
+
return v.toString(16);
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/database/Realtime.ts
|
|
1188
|
+
var Realtime = class {
|
|
1189
|
+
constructor(app) {
|
|
1190
|
+
this.socket = null;
|
|
1191
|
+
this.events = ["error", "insert", "update", "delete"];
|
|
1192
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
1193
|
+
this.app = app;
|
|
1194
|
+
}
|
|
1195
|
+
connect() {
|
|
1196
|
+
if (this.socket)
|
|
1197
|
+
return this.socket;
|
|
1198
|
+
if (!this.app.getConfig().token) {
|
|
1199
|
+
throw new Error("Auth token is required for realtime connection");
|
|
1200
|
+
}
|
|
1201
|
+
this.socket = (0, import_socket.io)(this.app.getSocketUrl(), {
|
|
1202
|
+
transports: ["websocket"],
|
|
1203
|
+
auth: { token: this.app.getConfig().token, project: this.app.getConfig().project },
|
|
1204
|
+
autoConnect: true,
|
|
1205
|
+
reconnection: true,
|
|
1206
|
+
reconnectionAttempts: Infinity,
|
|
1207
|
+
reconnectionDelay: 1e3,
|
|
1208
|
+
reconnectionDelayMax: 5e3
|
|
1209
|
+
});
|
|
1210
|
+
this.setupSocketListeners();
|
|
1211
|
+
return this.socket;
|
|
1212
|
+
}
|
|
1213
|
+
setupSocketListeners() {
|
|
1214
|
+
if (!this.socket)
|
|
1215
|
+
return;
|
|
1216
|
+
this.socket.on("connect", () => {
|
|
1217
|
+
console.log("[EdmaxLabs Realtime] Connected");
|
|
1218
|
+
this.resubscribeAll();
|
|
1219
|
+
this.app.offline().syncEngine?.flush().catch(console.error);
|
|
1220
|
+
this.app.offline().realtimeBridge?.onReconnect?.().catch(console.error);
|
|
1221
|
+
});
|
|
1222
|
+
this.socket.on("disconnect", (reason) => {
|
|
1223
|
+
console.log(`[EdmaxLabs Realtime] Disconnected: ${reason}`);
|
|
1224
|
+
});
|
|
1225
|
+
this.socket.on("error", (err) => {
|
|
1226
|
+
console.error("[EdmaxLabs Realtime] Socket error:", err);
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
// ===================== PUBLIC HELPERS =====================
|
|
1230
|
+
on(event, cb) {
|
|
1231
|
+
this.connect().on(event, cb);
|
|
1232
|
+
}
|
|
1233
|
+
off(event, cb) {
|
|
1234
|
+
if (cb)
|
|
1235
|
+
this.socket?.off(event, cb);
|
|
1236
|
+
else
|
|
1237
|
+
this.socket?.removeAllListeners(event);
|
|
1238
|
+
}
|
|
1239
|
+
emit(event, data) {
|
|
1240
|
+
this.connect().emit(event, data);
|
|
1241
|
+
}
|
|
1242
|
+
// ===================== INTERNAL RAW SUBSCRIPTIONS =====================
|
|
1243
|
+
/**
|
|
1244
|
+
* Low-level collection subscription (used by RealtimeBridge)
|
|
1245
|
+
*/
|
|
1246
|
+
subscribeToCollectionRaw(collection, callback, filter = {}) {
|
|
1247
|
+
this.connect();
|
|
1248
|
+
const lid = `col_${collection}_${generateUUID()}`;
|
|
1249
|
+
const handlers = [];
|
|
1250
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1251
|
+
this.events.forEach((event) => {
|
|
1252
|
+
const channel = `${lid}-${event}`;
|
|
1253
|
+
const fn = (payload) => {
|
|
1254
|
+
const normalized = this.normalizePayload(payload);
|
|
1255
|
+
if (!normalized)
|
|
1256
|
+
return;
|
|
1257
|
+
callback(normalized.data, normalized.change ?? event);
|
|
1258
|
+
};
|
|
1259
|
+
this.socket.on(channel, fn);
|
|
1260
|
+
handlers.push({ event: channel, fn });
|
|
1261
|
+
});
|
|
1262
|
+
this.registerSubscription(lid, collection, filter, handlers);
|
|
1263
|
+
return () => this.cleanupSubscription(lid);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Low-level document subscription (used by RealtimeBridge)
|
|
1267
|
+
*/
|
|
1268
|
+
subscribeToDocumentRaw(collection, id, callback) {
|
|
1269
|
+
this.connect();
|
|
1270
|
+
const lid = `doc_${collection}_${id}_${generateUUID()}`;
|
|
1271
|
+
const filter = { _id: id };
|
|
1272
|
+
const handlers = [];
|
|
1273
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1274
|
+
this.events.forEach((event) => {
|
|
1275
|
+
const channel = `${lid}-${event}`;
|
|
1276
|
+
const fn = (payload) => {
|
|
1277
|
+
const normalized = this.normalizePayload(payload);
|
|
1278
|
+
if (!normalized) {
|
|
1279
|
+
callback({ id }, "delete");
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
callback(normalized.data, normalized.change ?? event);
|
|
1283
|
+
};
|
|
1284
|
+
this.socket.on(channel, fn);
|
|
1285
|
+
handlers.push({ event: channel, fn });
|
|
1286
|
+
});
|
|
1287
|
+
this.registerSubscription(lid, collection, filter, handlers);
|
|
1288
|
+
return () => this.cleanupSubscription(lid);
|
|
1289
|
+
}
|
|
1290
|
+
// ===================== PRIVATE HELPERS =====================
|
|
1291
|
+
normalizePayload(payload) {
|
|
1292
|
+
const raw = payload?.document ?? payload?.data ?? payload;
|
|
1293
|
+
if (!raw)
|
|
1294
|
+
return null;
|
|
1295
|
+
const doc = { ...raw };
|
|
1296
|
+
doc.id = doc.id ?? doc._id;
|
|
1297
|
+
delete doc._id;
|
|
1298
|
+
return {
|
|
1299
|
+
change: payload?.change ?? raw?.change ?? null,
|
|
1300
|
+
data: doc
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
registerSubscription(lid, collection, filter, handlers) {
|
|
1304
|
+
this.subscriptions.set(lid, { lid, collection, filter, handlers });
|
|
1305
|
+
}
|
|
1306
|
+
resubscribeAll() {
|
|
1307
|
+
if (!this.socket)
|
|
1308
|
+
return;
|
|
1309
|
+
for (const sub of this.subscriptions.values()) {
|
|
1310
|
+
this.socket.emit("subscribe", {
|
|
1311
|
+
collection: sub.collection,
|
|
1312
|
+
filter: sub.filter,
|
|
1313
|
+
lid: sub.lid
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
cleanupSubscription(lid) {
|
|
1318
|
+
const sub = this.subscriptions.get(lid);
|
|
1319
|
+
if (!sub || !this.socket)
|
|
1320
|
+
return;
|
|
1321
|
+
for (const handler of sub.handlers) {
|
|
1322
|
+
this.socket.off(handler.event, handler.fn);
|
|
1323
|
+
}
|
|
1324
|
+
this.socket.emit("unsubscribe", { lid });
|
|
1325
|
+
this.subscriptions.delete(lid);
|
|
1326
|
+
}
|
|
1327
|
+
// ===================== LIFECYCLE =====================
|
|
1328
|
+
disconnect() {
|
|
1329
|
+
this.socket?.disconnect();
|
|
1330
|
+
this.socket = null;
|
|
1331
|
+
this.subscriptions.clear();
|
|
1332
|
+
}
|
|
1333
|
+
dispose() {
|
|
1334
|
+
this.disconnect();
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
// src/functions/Functions.ts
|
|
1339
|
+
var Functions = class {
|
|
1340
|
+
constructor(app) {
|
|
1341
|
+
this.app = app;
|
|
1342
|
+
}
|
|
1343
|
+
async call(functionName, data) {
|
|
1344
|
+
try {
|
|
1345
|
+
const res = await new HttpsRequest({
|
|
1346
|
+
method: "POST" /* POST */,
|
|
1347
|
+
endpoint: this.app.getBaseUrl + "/functions/call/" + functionName,
|
|
1348
|
+
headers: {
|
|
1349
|
+
authorization: this.app.getConfig().token
|
|
1350
|
+
},
|
|
1351
|
+
body: data
|
|
1352
|
+
}).sendRequest();
|
|
1353
|
+
return res;
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
throw {
|
|
1356
|
+
message: err.message || "Function call failed",
|
|
1357
|
+
code: err.code || "UNKNOWN_ERROR"
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
// src/hosting/Hosting.ts
|
|
1364
|
+
var Hosting = class {
|
|
1365
|
+
constructor(app) {
|
|
1366
|
+
this.app = app;
|
|
1367
|
+
}
|
|
1368
|
+
async createSubdomain(name) {
|
|
1369
|
+
try {
|
|
1370
|
+
const res = await new HttpsRequest({
|
|
1371
|
+
method: "POST" /* POST */,
|
|
1372
|
+
endpoint: this.app.getBaseUrl + "/hosting/register",
|
|
1373
|
+
headers: {
|
|
1374
|
+
authorization: this.app.getConfig().token
|
|
1375
|
+
},
|
|
1376
|
+
body: {
|
|
1377
|
+
hostname: name,
|
|
1378
|
+
project: this.app.getConfig().project
|
|
1379
|
+
}
|
|
1380
|
+
}).sendRequest();
|
|
1381
|
+
return res;
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
throw {
|
|
1384
|
+
message: err.message || "Function call failed",
|
|
1385
|
+
code: err.code || "UNKNOWN_ERROR"
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
// src/persistence/LocalStore.ts
|
|
1392
|
+
var LocalStore = class {
|
|
1393
|
+
constructor() {
|
|
1394
|
+
this.documentListeners = /* @__PURE__ */ new Map();
|
|
1395
|
+
this.collectionListeners = /* @__PURE__ */ new Map();
|
|
1396
|
+
}
|
|
1397
|
+
docKey(collection, id) {
|
|
1398
|
+
return `${collection}:${id}`;
|
|
1399
|
+
}
|
|
1400
|
+
// ===================== DOCUMENT LISTENERS =====================
|
|
1401
|
+
subscribeToDocument(collection, id, callback) {
|
|
1402
|
+
const key = this.docKey(collection, id);
|
|
1403
|
+
if (!this.documentListeners.has(key)) {
|
|
1404
|
+
this.documentListeners.set(key, /* @__PURE__ */ new Set());
|
|
1405
|
+
}
|
|
1406
|
+
const listeners = this.documentListeners.get(key);
|
|
1407
|
+
listeners.add(callback);
|
|
1408
|
+
return () => {
|
|
1409
|
+
listeners.delete(callback);
|
|
1410
|
+
if (listeners.size === 0) {
|
|
1411
|
+
this.documentListeners.delete(key);
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
// ===================== COLLECTION LISTENERS =====================
|
|
1416
|
+
subscribeToCollection(collection, callback) {
|
|
1417
|
+
if (!this.collectionListeners.has(collection)) {
|
|
1418
|
+
this.collectionListeners.set(collection, /* @__PURE__ */ new Set());
|
|
1419
|
+
}
|
|
1420
|
+
const listeners = this.collectionListeners.get(collection);
|
|
1421
|
+
listeners.add(callback);
|
|
1422
|
+
return () => {
|
|
1423
|
+
listeners.delete(callback);
|
|
1424
|
+
if (listeners.size === 0) {
|
|
1425
|
+
this.collectionListeners.delete(collection);
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
// ===================== EMITTERS =====================
|
|
1430
|
+
/**
|
|
1431
|
+
* Notify all listeners for a specific document
|
|
1432
|
+
*/
|
|
1433
|
+
emitDocument(collection, id, snapshot, change) {
|
|
1434
|
+
const key = this.docKey(collection, id);
|
|
1435
|
+
this.documentListeners.get(key)?.forEach((cb) => {
|
|
1436
|
+
try {
|
|
1437
|
+
cb(snapshot, change);
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
console.error(`[EdmaxLabs] Error in document listener for ${key}:`, err);
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Notify all listeners for a collection.
|
|
1445
|
+
* This is the most important fix — collection listeners now receive the full list.
|
|
1446
|
+
*/
|
|
1447
|
+
emitCollection(collection, snapshots, change, changedDocId) {
|
|
1448
|
+
this.collectionListeners.get(collection)?.forEach((cb) => {
|
|
1449
|
+
try {
|
|
1450
|
+
cb(snapshots, change, changedDocId);
|
|
1451
|
+
} catch (err) {
|
|
1452
|
+
console.error(`[EdmaxLabs] Error in collection listener for ${collection}:`, err);
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
// ===================== UTILITY =====================
|
|
1457
|
+
/**
|
|
1458
|
+
* Clear all listeners (useful for testing or when persistence is disabled)
|
|
1459
|
+
*/
|
|
1460
|
+
clearAllListeners() {
|
|
1461
|
+
this.documentListeners.clear();
|
|
1462
|
+
this.collectionListeners.clear();
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Get current listener count (for debugging / dev tools)
|
|
1466
|
+
*/
|
|
1467
|
+
get listenerCount() {
|
|
1468
|
+
let docCount = 0;
|
|
1469
|
+
this.documentListeners.forEach((set) => docCount += set.size);
|
|
1470
|
+
let collCount = 0;
|
|
1471
|
+
this.collectionListeners.forEach((set) => collCount += set.size);
|
|
1472
|
+
return { documents: docCount, collections: collCount };
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
// ../../../../node_modules/idb/build/wrap-idb-value.js
|
|
1477
|
+
var instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
|
|
1478
|
+
var idbProxyableTypes;
|
|
1479
|
+
var cursorAdvanceMethods;
|
|
1480
|
+
function getIdbProxyableTypes() {
|
|
1481
|
+
return idbProxyableTypes || (idbProxyableTypes = [
|
|
1482
|
+
IDBDatabase,
|
|
1483
|
+
IDBObjectStore,
|
|
1484
|
+
IDBIndex,
|
|
1485
|
+
IDBCursor,
|
|
1486
|
+
IDBTransaction
|
|
1487
|
+
]);
|
|
1488
|
+
}
|
|
1489
|
+
function getCursorAdvanceMethods() {
|
|
1490
|
+
return cursorAdvanceMethods || (cursorAdvanceMethods = [
|
|
1491
|
+
IDBCursor.prototype.advance,
|
|
1492
|
+
IDBCursor.prototype.continue,
|
|
1493
|
+
IDBCursor.prototype.continuePrimaryKey
|
|
1494
|
+
]);
|
|
1495
|
+
}
|
|
1496
|
+
var cursorRequestMap = /* @__PURE__ */ new WeakMap();
|
|
1497
|
+
var transactionDoneMap = /* @__PURE__ */ new WeakMap();
|
|
1498
|
+
var transactionStoreNamesMap = /* @__PURE__ */ new WeakMap();
|
|
1499
|
+
var transformCache = /* @__PURE__ */ new WeakMap();
|
|
1500
|
+
var reverseTransformCache = /* @__PURE__ */ new WeakMap();
|
|
1501
|
+
function promisifyRequest(request) {
|
|
1502
|
+
const promise = new Promise((resolve, reject) => {
|
|
1503
|
+
const unlisten = () => {
|
|
1504
|
+
request.removeEventListener("success", success);
|
|
1505
|
+
request.removeEventListener("error", error);
|
|
1506
|
+
};
|
|
1507
|
+
const success = () => {
|
|
1508
|
+
resolve(wrap(request.result));
|
|
1509
|
+
unlisten();
|
|
1510
|
+
};
|
|
1511
|
+
const error = () => {
|
|
1512
|
+
reject(request.error);
|
|
800
1513
|
unlisten();
|
|
801
1514
|
};
|
|
802
1515
|
request.addEventListener("success", success);
|
|
@@ -968,542 +1681,521 @@ replaceTraps((oldTraps) => ({
|
|
|
968
1681
|
}));
|
|
969
1682
|
|
|
970
1683
|
// src/persistence/Persistence.ts
|
|
971
|
-
var
|
|
972
|
-
constructor(
|
|
973
|
-
this.
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
if (!
|
|
977
|
-
const
|
|
978
|
-
|
|
1684
|
+
var Persistence = class {
|
|
1685
|
+
constructor() {
|
|
1686
|
+
this.dbPromise = openDB("edmaxlabs_offline", 1, {
|
|
1687
|
+
upgrade(app, oldVersion) {
|
|
1688
|
+
if (oldVersion < 1) {
|
|
1689
|
+
if (!app.objectStoreNames.contains("docs")) {
|
|
1690
|
+
const docs = app.createObjectStore("docs", { keyPath: "key" });
|
|
1691
|
+
docs.createIndex("by-collection", "collection");
|
|
1692
|
+
docs.createIndex("by-id", "id");
|
|
1693
|
+
docs.createIndex("by-updatedAt", "updatedAt");
|
|
1694
|
+
docs.createIndex("by-pending", "pending");
|
|
1695
|
+
}
|
|
1696
|
+
if (!app.objectStoreNames.contains("mutations")) {
|
|
1697
|
+
const mutations = app.createObjectStore("mutations", {
|
|
1698
|
+
keyPath: "mutationId"
|
|
979
1699
|
});
|
|
980
|
-
|
|
981
|
-
|
|
1700
|
+
mutations.createIndex("by-status", "status");
|
|
1701
|
+
mutations.createIndex("by-collection", "collection");
|
|
1702
|
+
mutations.createIndex("by-documentId", "documentId");
|
|
1703
|
+
mutations.createIndex("by-createdAt", "createdAt");
|
|
982
1704
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
return dbPromise;
|
|
986
|
-
};
|
|
987
|
-
this.db = db;
|
|
988
|
-
}
|
|
989
|
-
async syncPendingWrite(id, collection = "default") {
|
|
990
|
-
const idb = await this.getIDB("edmaxlabs");
|
|
991
|
-
const tx = idb.transaction(collection, "readwrite");
|
|
992
|
-
const store = tx.store;
|
|
993
|
-
const oid = IDBKeyRange.only(id);
|
|
994
|
-
const pendingPackets = await store.index("id").getAll(oid);
|
|
995
|
-
for (const packet of pendingPackets) {
|
|
996
|
-
try {
|
|
997
|
-
const res = await new HttpsRequest({
|
|
998
|
-
method: "POST" /* POST */,
|
|
999
|
-
endpoint: `${this.db?.getBaseUrl}/db/${packet.action}`.replace(
|
|
1000
|
-
"$",
|
|
1001
|
-
"/"
|
|
1002
|
-
),
|
|
1003
|
-
body: {
|
|
1004
|
-
collection: packet.collection,
|
|
1005
|
-
data: packet
|
|
1705
|
+
if (!app.objectStoreNames.contains("meta")) {
|
|
1706
|
+
app.createObjectStore("meta");
|
|
1006
1707
|
}
|
|
1007
|
-
}).sendRequest();
|
|
1008
|
-
if (res?.success) {
|
|
1009
|
-
packet.status = "success";
|
|
1010
|
-
packet.timestamp = Timestamp.now();
|
|
1011
|
-
await store.put(packet);
|
|
1012
|
-
return true;
|
|
1013
1708
|
}
|
|
1014
|
-
} catch {
|
|
1015
|
-
return false;
|
|
1016
1709
|
}
|
|
1017
|
-
}
|
|
1018
|
-
await tx.done;
|
|
1710
|
+
});
|
|
1019
1711
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const tx = idb.transaction(collection, "readwrite");
|
|
1023
|
-
const store = tx.store;
|
|
1024
|
-
const pendingPackets = await store.index("status").getAll(IDBKeyRange.only("pending"));
|
|
1025
|
-
for (const packet of pendingPackets) {
|
|
1026
|
-
try {
|
|
1027
|
-
const res = await new HttpsRequest({
|
|
1028
|
-
method: "POST" /* POST */,
|
|
1029
|
-
endpoint: `${this.db?.getBaseUrl}/db/${packet.action}`.replace(
|
|
1030
|
-
"$",
|
|
1031
|
-
"/"
|
|
1032
|
-
),
|
|
1033
|
-
body: {
|
|
1034
|
-
collection: packet.collection,
|
|
1035
|
-
data: packet
|
|
1036
|
-
}
|
|
1037
|
-
}).sendRequest();
|
|
1038
|
-
if (res?.success) {
|
|
1039
|
-
packet.status = "success";
|
|
1040
|
-
packet.timestamp = Timestamp.now();
|
|
1041
|
-
await store.put(packet);
|
|
1042
|
-
}
|
|
1043
|
-
} catch {
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
await tx.done;
|
|
1712
|
+
docKey(collection, id) {
|
|
1713
|
+
return `${collection}:${id}`;
|
|
1047
1714
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
// src/database/Query.ts
|
|
1051
|
-
var Query = class {
|
|
1052
|
-
constructor(db, collection) {
|
|
1053
|
-
this.filter = [];
|
|
1054
|
-
this.db = db;
|
|
1055
|
-
this.collection = collection;
|
|
1715
|
+
now() {
|
|
1716
|
+
return Date.now();
|
|
1056
1717
|
}
|
|
1057
|
-
|
|
1058
|
-
this.
|
|
1059
|
-
return this;
|
|
1718
|
+
async getDb() {
|
|
1719
|
+
return this.dbPromise;
|
|
1060
1720
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
case "==":
|
|
1066
|
-
case "===":
|
|
1067
|
-
mongoFilter[key] = { $eq: value };
|
|
1068
|
-
break;
|
|
1069
|
-
case "!=":
|
|
1070
|
-
mongoFilter[key] = { $ne: value };
|
|
1071
|
-
break;
|
|
1072
|
-
case "<":
|
|
1073
|
-
mongoFilter[key] = { $lt: value };
|
|
1074
|
-
break;
|
|
1075
|
-
case "<=":
|
|
1076
|
-
mongoFilter[key] = { $lte: value };
|
|
1077
|
-
break;
|
|
1078
|
-
case ">":
|
|
1079
|
-
mongoFilter[key] = { $gt: value };
|
|
1080
|
-
break;
|
|
1081
|
-
case ">=":
|
|
1082
|
-
mongoFilter[key] = { $gte: value };
|
|
1083
|
-
break;
|
|
1084
|
-
case "in":
|
|
1085
|
-
mongoFilter[key] = { $in: value };
|
|
1086
|
-
break;
|
|
1087
|
-
case "nin":
|
|
1088
|
-
mongoFilter[key] = { $nin: value };
|
|
1089
|
-
break;
|
|
1090
|
-
case "contains":
|
|
1091
|
-
mongoFilter[key] = { $regex: value };
|
|
1092
|
-
break;
|
|
1093
|
-
default:
|
|
1094
|
-
throw new Error(`Unknown operator: ${op}`);
|
|
1095
|
-
}
|
|
1096
|
-
});
|
|
1097
|
-
return mongoFilter;
|
|
1721
|
+
// ==================== DOCS ====================
|
|
1722
|
+
async getDoc(collection, id) {
|
|
1723
|
+
const app = await this.getDb();
|
|
1724
|
+
return await app.get("docs", this.docKey(collection, id)) ?? null;
|
|
1098
1725
|
}
|
|
1099
|
-
async
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1726
|
+
async getDocSnapshot(collection, id) {
|
|
1727
|
+
const doc = await this.getDoc(collection, id);
|
|
1728
|
+
if (!doc || !doc.exists || doc.deleted)
|
|
1729
|
+
return null;
|
|
1730
|
+
return DocumentSnapshot.fromMap(doc.data);
|
|
1731
|
+
}
|
|
1732
|
+
async getCollection(collection) {
|
|
1733
|
+
const app = await this.getDb();
|
|
1734
|
+
const all = await app.getAllFromIndex("docs", "by-collection", collection);
|
|
1735
|
+
return all.filter((d) => d.exists && !d.deleted);
|
|
1736
|
+
}
|
|
1737
|
+
async getCollectionSnapshots(collection) {
|
|
1738
|
+
const docs = await this.getCollection(collection);
|
|
1739
|
+
return docs.map((d) => DocumentSnapshot.fromMap(d.data));
|
|
1740
|
+
}
|
|
1741
|
+
async upsertDoc(input) {
|
|
1742
|
+
const app = await this.getDb();
|
|
1743
|
+
const record = {
|
|
1744
|
+
...input,
|
|
1745
|
+
key: this.docKey(input.collection, input.id),
|
|
1746
|
+
updatedAt: input.updatedAt ?? this.now()
|
|
1747
|
+
};
|
|
1748
|
+
await app.put("docs", record);
|
|
1749
|
+
return record;
|
|
1750
|
+
}
|
|
1751
|
+
async markDeleted(collection, id, pending = 1) {
|
|
1752
|
+
const old = await this.getDoc(collection, id);
|
|
1753
|
+
const record = {
|
|
1754
|
+
key: this.docKey(collection, id),
|
|
1755
|
+
collection,
|
|
1756
|
+
id,
|
|
1757
|
+
data: old?.data ?? { id },
|
|
1758
|
+
exists: false,
|
|
1759
|
+
deleted: true,
|
|
1760
|
+
pending,
|
|
1761
|
+
localOnly: false,
|
|
1762
|
+
status: pending ? "pending" : "synced",
|
|
1763
|
+
updatedAt: this.now(),
|
|
1764
|
+
lastSyncedAt: old?.lastSyncedAt,
|
|
1765
|
+
revision: old?.revision
|
|
1766
|
+
};
|
|
1767
|
+
const app = await this.getDb();
|
|
1768
|
+
await app.put("docs", record);
|
|
1769
|
+
return record;
|
|
1770
|
+
}
|
|
1771
|
+
// ==================== MUTATIONS ====================
|
|
1772
|
+
async enqueueMutation(mutation) {
|
|
1773
|
+
const app = await this.getDb();
|
|
1774
|
+
const record = {
|
|
1775
|
+
...mutation,
|
|
1776
|
+
createdAt: this.now(),
|
|
1777
|
+
updatedAt: this.now(),
|
|
1778
|
+
retryCount: 0,
|
|
1779
|
+
status: "pending"
|
|
1780
|
+
};
|
|
1781
|
+
await app.put("mutations", record);
|
|
1782
|
+
return record;
|
|
1783
|
+
}
|
|
1784
|
+
async getPendingMutations() {
|
|
1785
|
+
const app = await this.getDb();
|
|
1786
|
+
const [pending, failed] = await Promise.all([
|
|
1787
|
+
app.getAllFromIndex("mutations", "by-status", "pending"),
|
|
1788
|
+
app.getAllFromIndex("mutations", "by-status", "failed")
|
|
1789
|
+
]);
|
|
1790
|
+
return [...pending, ...failed].sort((a, b) => a.createdAt - b.createdAt);
|
|
1791
|
+
}
|
|
1792
|
+
async setMutationStatus(mutationId, status, error) {
|
|
1793
|
+
const app = await this.getDb();
|
|
1794
|
+
const old = await app.get("mutations", mutationId);
|
|
1795
|
+
if (!old)
|
|
1796
|
+
return null;
|
|
1797
|
+
const next = {
|
|
1798
|
+
...old,
|
|
1799
|
+
status,
|
|
1800
|
+
updatedAt: this.now(),
|
|
1801
|
+
retryCount: status === "failed" ? (old.retryCount || 0) + 1 : old.retryCount,
|
|
1802
|
+
error
|
|
1803
|
+
};
|
|
1804
|
+
await app.put("mutations", next);
|
|
1805
|
+
return next;
|
|
1806
|
+
}
|
|
1807
|
+
async removeMutation(mutationId) {
|
|
1808
|
+
const app = await this.getDb();
|
|
1809
|
+
await app.delete("mutations", mutationId);
|
|
1810
|
+
}
|
|
1811
|
+
// ==================== ADVANCED OPERATIONS ====================
|
|
1812
|
+
async replaceDocId(collection, oldId, newId) {
|
|
1813
|
+
const app = await this.getDb();
|
|
1814
|
+
const oldKey = this.docKey(collection, oldId);
|
|
1815
|
+
const oldDoc = await app.get("docs", oldKey);
|
|
1816
|
+
if (!oldDoc)
|
|
1817
|
+
return null;
|
|
1818
|
+
const next = {
|
|
1819
|
+
...oldDoc,
|
|
1820
|
+
id: newId,
|
|
1821
|
+
key: this.docKey(collection, newId),
|
|
1822
|
+
data: { ...oldDoc.data, id: newId },
|
|
1823
|
+
localOnly: false,
|
|
1824
|
+
pending: 0,
|
|
1825
|
+
status: "synced",
|
|
1826
|
+
updatedAt: this.now(),
|
|
1827
|
+
lastSyncedAt: this.now()
|
|
1828
|
+
};
|
|
1829
|
+
const tx = app.transaction(["docs", "mutations"], "readwrite");
|
|
1830
|
+
const docsStore = tx.objectStore("docs");
|
|
1831
|
+
const mutationsStore = tx.objectStore("mutations");
|
|
1832
|
+
await docsStore.put(next);
|
|
1833
|
+
await docsStore.delete(oldKey);
|
|
1834
|
+
const allMutations = await mutationsStore.getAll();
|
|
1835
|
+
for (const mut of allMutations) {
|
|
1836
|
+
if (mut.collection === collection && mut.documentId === oldId) {
|
|
1837
|
+
await mutationsStore.put({
|
|
1838
|
+
...mut,
|
|
1839
|
+
documentId: newId,
|
|
1840
|
+
payload: mut.payload ? { ...mut.payload, id: newId } : null,
|
|
1841
|
+
updatedAt: this.now()
|
|
1842
|
+
});
|
|
1110
1843
|
}
|
|
1111
|
-
}).sendRequest();
|
|
1112
|
-
if (res.success) {
|
|
1113
|
-
return res.documents.map((d) => {
|
|
1114
|
-
delete d.token;
|
|
1115
|
-
d.id = d._id;
|
|
1116
|
-
delete d._id;
|
|
1117
|
-
return DocumentSnapshot.fromMap(d);
|
|
1118
|
-
});
|
|
1119
|
-
} else {
|
|
1120
|
-
return [];
|
|
1121
1844
|
}
|
|
1845
|
+
await tx.done;
|
|
1846
|
+
return next;
|
|
1122
1847
|
}
|
|
1123
|
-
async
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
},
|
|
1131
|
-
body: {
|
|
1132
|
-
collection: this.collection,
|
|
1133
|
-
filter: genfilter
|
|
1134
|
-
}
|
|
1135
|
-
}).sendRequest();
|
|
1136
|
-
if (res.success) {
|
|
1137
|
-
return res.documents.map((d) => {
|
|
1138
|
-
delete d.token;
|
|
1139
|
-
d.id = d._id;
|
|
1140
|
-
delete d._id;
|
|
1141
|
-
return DocumentSnapshot.fromMap(d);
|
|
1142
|
-
});
|
|
1143
|
-
} else {
|
|
1144
|
-
return [];
|
|
1848
|
+
async applyRemoteDoc(collection, data, revision) {
|
|
1849
|
+
const id = data.id ?? data._id;
|
|
1850
|
+
if (!id)
|
|
1851
|
+
return null;
|
|
1852
|
+
const local = await this.getDoc(collection, id);
|
|
1853
|
+
if (local?.pending && local.pending > 0) {
|
|
1854
|
+
return local;
|
|
1145
1855
|
}
|
|
1856
|
+
return this.upsertDoc({
|
|
1857
|
+
collection,
|
|
1858
|
+
id,
|
|
1859
|
+
data: { ...data, id },
|
|
1860
|
+
exists: true,
|
|
1861
|
+
deleted: false,
|
|
1862
|
+
pending: 0,
|
|
1863
|
+
localOnly: false,
|
|
1864
|
+
status: "synced",
|
|
1865
|
+
revision,
|
|
1866
|
+
lastSyncedAt: this.now()
|
|
1867
|
+
});
|
|
1146
1868
|
}
|
|
1147
|
-
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1869
|
+
async applyRemoteDelete(collection, id) {
|
|
1870
|
+
const local = await this.getDoc(collection, id);
|
|
1871
|
+
if (local?.pending && local.pending > 0) {
|
|
1872
|
+
return local;
|
|
1873
|
+
}
|
|
1874
|
+
return this.markDeleted(collection, id, 0);
|
|
1875
|
+
}
|
|
1876
|
+
// ==================== UTILITIES ====================
|
|
1877
|
+
createLocalId() {
|
|
1878
|
+
return `local_${crypto.randomUUID()}`;
|
|
1879
|
+
}
|
|
1880
|
+
createMutationId() {
|
|
1881
|
+
return `mut_${crypto.randomUUID()}`;
|
|
1882
|
+
}
|
|
1883
|
+
// Future-proof maintenance methods (optional for now)
|
|
1884
|
+
async clearAll() {
|
|
1885
|
+
const app = await this.getDb();
|
|
1886
|
+
const tx = app.transaction(["docs", "mutations", "meta"], "readwrite");
|
|
1887
|
+
await Promise.all([
|
|
1888
|
+
tx.objectStore("docs").clear(),
|
|
1889
|
+
tx.objectStore("mutations").clear(),
|
|
1890
|
+
tx.objectStore("meta").clear()
|
|
1891
|
+
]);
|
|
1892
|
+
await tx.done;
|
|
1893
|
+
}
|
|
1894
|
+
async getStorageUsage() {
|
|
1895
|
+
return 0;
|
|
1154
1896
|
}
|
|
1155
1897
|
};
|
|
1156
1898
|
|
|
1157
|
-
// src/
|
|
1158
|
-
var
|
|
1159
|
-
constructor(
|
|
1160
|
-
this.
|
|
1161
|
-
this.
|
|
1162
|
-
this.
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
headers: {
|
|
1175
|
-
authorization: this.db.getAuth.token
|
|
1176
|
-
},
|
|
1177
|
-
body: {
|
|
1178
|
-
collection: this.collection,
|
|
1179
|
-
filter: {}
|
|
1180
|
-
}
|
|
1181
|
-
}).sendRequest();
|
|
1182
|
-
if (res.success) {
|
|
1183
|
-
return res.documents.map((d) => {
|
|
1184
|
-
delete d.token;
|
|
1185
|
-
d.id = d._id;
|
|
1186
|
-
delete d._id;
|
|
1187
|
-
if (d === void 0)
|
|
1188
|
-
return [];
|
|
1189
|
-
return DocumentSnapshot.fromMap(d);
|
|
1190
|
-
});
|
|
1191
|
-
} else {
|
|
1192
|
-
return [];
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
async add(data) {
|
|
1196
|
-
const localId = Timestamp.now().toString();
|
|
1197
|
-
const payload = {
|
|
1198
|
-
id: localId,
|
|
1199
|
-
data,
|
|
1200
|
-
collection: this.collection,
|
|
1201
|
-
action: "create",
|
|
1202
|
-
status: "pending",
|
|
1203
|
-
timestamp: Timestamp.now()
|
|
1204
|
-
};
|
|
1205
|
-
if (this.db.getPersistence) {
|
|
1899
|
+
// src/persistence/RealtimeBridge.ts
|
|
1900
|
+
var RealtimeBridge = class {
|
|
1901
|
+
constructor(app, persistence, store) {
|
|
1902
|
+
this.app = app;
|
|
1903
|
+
this.persistence = persistence;
|
|
1904
|
+
this.store = store;
|
|
1905
|
+
this.collectionUnsubs = /* @__PURE__ */ new Map();
|
|
1906
|
+
this.documentUnsubs = /* @__PURE__ */ new Map();
|
|
1907
|
+
}
|
|
1908
|
+
docKey(collection, id) {
|
|
1909
|
+
return `${collection}:${id}`;
|
|
1910
|
+
}
|
|
1911
|
+
// ===================== PUBLIC API =====================
|
|
1912
|
+
watchCollection(collection, filter = {}) {
|
|
1913
|
+
const key = `${collection}:${JSON.stringify(filter)}`;
|
|
1914
|
+
if (this.collectionUnsubs.has(key)) {
|
|
1915
|
+
return this.collectionUnsubs.get(key);
|
|
1206
1916
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1917
|
+
this.emitCurrentCollection(collection);
|
|
1918
|
+
const unsub = this.app.rtdb().subscribeToCollectionRaw(
|
|
1919
|
+
collection,
|
|
1920
|
+
async (payload, change) => {
|
|
1921
|
+
if (change === "delete") {
|
|
1922
|
+
const id = payload?.id || payload?._id;
|
|
1923
|
+
if (id)
|
|
1924
|
+
await this.handleRemoteDelete(collection, id);
|
|
1925
|
+
} else {
|
|
1926
|
+
await this.handleRemoteCreateOrUpdate(collection, payload);
|
|
1217
1927
|
}
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1928
|
+
},
|
|
1929
|
+
filter
|
|
1930
|
+
);
|
|
1931
|
+
const fullUnsub = () => {
|
|
1932
|
+
unsub();
|
|
1933
|
+
this.collectionUnsubs.delete(key);
|
|
1934
|
+
};
|
|
1935
|
+
this.collectionUnsubs.set(key, fullUnsub);
|
|
1936
|
+
return fullUnsub;
|
|
1937
|
+
}
|
|
1938
|
+
watchDocument(collection, id) {
|
|
1939
|
+
const key = this.docKey(collection, id);
|
|
1940
|
+
if (this.documentUnsubs.has(key)) {
|
|
1941
|
+
return this.documentUnsubs.get(key);
|
|
1942
|
+
}
|
|
1943
|
+
this.emitCurrentDocument(collection, id);
|
|
1944
|
+
const unsub = this.app.rtdb().subscribeToDocumentRaw(
|
|
1945
|
+
collection,
|
|
1946
|
+
id,
|
|
1947
|
+
async (payload, change) => {
|
|
1948
|
+
if (change === "delete") {
|
|
1949
|
+
await this.handleRemoteDelete(collection, id);
|
|
1950
|
+
} else {
|
|
1951
|
+
await this.handleRemoteCreateOrUpdate(collection, payload);
|
|
1223
1952
|
}
|
|
1224
|
-
return null;
|
|
1225
|
-
}
|
|
1226
|
-
payload.status = "success";
|
|
1227
|
-
payload.id = res.id;
|
|
1228
|
-
if (this.db.getPersistence) {
|
|
1229
1953
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1954
|
+
);
|
|
1955
|
+
const fullUnsub = () => {
|
|
1956
|
+
unsub();
|
|
1957
|
+
this.documentUnsubs.delete(key);
|
|
1958
|
+
};
|
|
1959
|
+
this.documentUnsubs.set(key, fullUnsub);
|
|
1960
|
+
return fullUnsub;
|
|
1961
|
+
}
|
|
1962
|
+
// ===================== INTERNAL HANDLERS =====================
|
|
1963
|
+
async emitCurrentDocument(collection, id) {
|
|
1964
|
+
const localDoc = await this.persistence.getDoc(collection, id);
|
|
1965
|
+
const snapshot = localDoc && localDoc.exists && !localDoc.deleted ? DocumentSnapshot.fromMap(localDoc.data) : null;
|
|
1966
|
+
this.store.emitDocument(collection, id, snapshot, "init");
|
|
1967
|
+
}
|
|
1968
|
+
async emitCurrentCollection(collection) {
|
|
1969
|
+
const localDocs = await this.persistence.getCollectionSnapshots(collection);
|
|
1970
|
+
this.store.emitCollection(collection, localDocs, "init");
|
|
1971
|
+
}
|
|
1972
|
+
async handleRemoteCreateOrUpdate(collection, raw) {
|
|
1973
|
+
const id = raw.id ?? raw._id;
|
|
1974
|
+
if (!id)
|
|
1975
|
+
return;
|
|
1976
|
+
const saved = await this.persistence.applyRemoteDoc(collection, raw);
|
|
1977
|
+
if (!saved)
|
|
1978
|
+
return;
|
|
1979
|
+
const snap = DocumentSnapshot.fromMap(saved.data);
|
|
1980
|
+
this.store.emitDocument(collection, id, snap, "remote_update");
|
|
1981
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(collection);
|
|
1982
|
+
this.store.emitCollection(collection, currentCollection, "remote_update", id);
|
|
1983
|
+
}
|
|
1984
|
+
async handleRemoteDelete(collection, id) {
|
|
1985
|
+
await this.persistence.applyRemoteDelete(collection, id);
|
|
1986
|
+
this.store.emitDocument(collection, id, null, "remote_delete");
|
|
1987
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(collection);
|
|
1988
|
+
this.store.emitCollection(collection, currentCollection, "remote_delete", id);
|
|
1989
|
+
}
|
|
1990
|
+
// ===================== LIFECYCLE =====================
|
|
1991
|
+
/**
|
|
1992
|
+
* Call this when Socket.IO reconnects or when going online
|
|
1993
|
+
* Replays pending mutations + refreshes all active subscriptions from cache
|
|
1994
|
+
*/
|
|
1995
|
+
async onReconnect() {
|
|
1996
|
+
for (const [key] of this.collectionUnsubs) {
|
|
1997
|
+
const collection = key.split(":")[0];
|
|
1998
|
+
await this.emitCurrentCollection(collection);
|
|
1999
|
+
}
|
|
2000
|
+
for (const [key] of this.documentUnsubs) {
|
|
2001
|
+
const [collection, id] = key.split(":");
|
|
2002
|
+
await this.emitCurrentDocument(collection, id);
|
|
1236
2003
|
}
|
|
1237
2004
|
}
|
|
1238
2005
|
/**
|
|
1239
|
-
*
|
|
2006
|
+
* Clean up all subscriptions (call from EdmaxLabs destructor if needed)
|
|
1240
2007
|
*/
|
|
1241
|
-
|
|
1242
|
-
|
|
2008
|
+
dispose() {
|
|
2009
|
+
this.collectionUnsubs.forEach((unsub) => unsub());
|
|
2010
|
+
this.documentUnsubs.forEach((unsub) => unsub());
|
|
2011
|
+
this.collectionUnsubs.clear();
|
|
2012
|
+
this.documentUnsubs.clear();
|
|
1243
2013
|
}
|
|
1244
2014
|
};
|
|
1245
2015
|
|
|
1246
|
-
// src/
|
|
1247
|
-
var
|
|
1248
|
-
constructor(app) {
|
|
2016
|
+
// src/persistence/SyncEngine.ts
|
|
2017
|
+
var SyncEngine = class {
|
|
2018
|
+
constructor(app, persistence, store, realtimeBridge) {
|
|
2019
|
+
this.realtimeBridge = null;
|
|
2020
|
+
this.syncing = false;
|
|
2021
|
+
this.retryTimeout = null;
|
|
2022
|
+
this.MAX_RETRIES = 5;
|
|
2023
|
+
this.BASE_RETRY_DELAY = 2e3;
|
|
1249
2024
|
this.app = app;
|
|
1250
|
-
this.
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
2025
|
+
this.persistence = persistence;
|
|
2026
|
+
this.store = store;
|
|
2027
|
+
this.realtimeBridge = realtimeBridge || null;
|
|
2028
|
+
if (typeof window !== "undefined") {
|
|
2029
|
+
window.addEventListener("online", () => this.onNetworkOnline());
|
|
2030
|
+
document.addEventListener("visibilitychange", () => {
|
|
2031
|
+
if (document.visibilityState === "visible")
|
|
2032
|
+
this.onNetworkOnline();
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
1260
2035
|
}
|
|
1261
|
-
|
|
1262
|
-
this.
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
});
|
|
1268
|
-
return this;
|
|
2036
|
+
onNetworkOnline() {
|
|
2037
|
+
if (this.retryTimeout) {
|
|
2038
|
+
clearTimeout(this.retryTimeout);
|
|
2039
|
+
this.retryTimeout = null;
|
|
2040
|
+
}
|
|
2041
|
+
this.flush().catch(console.error);
|
|
1269
2042
|
}
|
|
1270
|
-
|
|
1271
|
-
this.
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
2043
|
+
async flush() {
|
|
2044
|
+
if (this.syncing)
|
|
2045
|
+
return;
|
|
2046
|
+
if (typeof navigator !== "undefined" && !navigator.onLine)
|
|
2047
|
+
return;
|
|
2048
|
+
this.syncing = true;
|
|
2049
|
+
try {
|
|
2050
|
+
const pending = await this.persistence.getPendingMutations();
|
|
2051
|
+
for (const mutation of pending) {
|
|
2052
|
+
if (mutation.retryCount >= this.MAX_RETRIES) {
|
|
2053
|
+
await this.persistence.setMutationStatus(
|
|
2054
|
+
mutation.mutationId,
|
|
2055
|
+
"failed",
|
|
2056
|
+
"Max retries exceeded"
|
|
2057
|
+
);
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
await this.persistence.setMutationStatus(mutation.mutationId, "syncing");
|
|
2061
|
+
try {
|
|
2062
|
+
let success = false;
|
|
2063
|
+
switch (mutation.type) {
|
|
2064
|
+
case "create":
|
|
2065
|
+
success = await this.syncCreate(mutation);
|
|
2066
|
+
break;
|
|
2067
|
+
case "update":
|
|
2068
|
+
success = await this.syncUpdate(mutation);
|
|
2069
|
+
break;
|
|
2070
|
+
case "delete":
|
|
2071
|
+
success = await this.syncDelete(mutation);
|
|
2072
|
+
break;
|
|
2073
|
+
case "array_push":
|
|
2074
|
+
case "array_update":
|
|
2075
|
+
case "array_insert":
|
|
2076
|
+
case "array_remove":
|
|
2077
|
+
default:
|
|
2078
|
+
console.warn(`[EdmaxLabs] Unknown mutation type: ${mutation.type}`);
|
|
2079
|
+
}
|
|
2080
|
+
if (success) {
|
|
2081
|
+
await this.persistence.removeMutation(mutation.mutationId);
|
|
2082
|
+
} else {
|
|
2083
|
+
throw new Error("Sync operation failed");
|
|
2084
|
+
}
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
const nextRetryCount = (mutation.retryCount || 0) + 1;
|
|
2087
|
+
await this.persistence.setMutationStatus(
|
|
2088
|
+
mutation.mutationId,
|
|
2089
|
+
"failed",
|
|
2090
|
+
error?.message ?? "Unknown sync error"
|
|
2091
|
+
);
|
|
2092
|
+
if (nextRetryCount < this.MAX_RETRIES) {
|
|
2093
|
+
this.scheduleRetry();
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
if (this.realtimeBridge) {
|
|
2098
|
+
await this.realtimeBridge.onReconnect();
|
|
2099
|
+
}
|
|
2100
|
+
} finally {
|
|
2101
|
+
this.syncing = false;
|
|
2102
|
+
}
|
|
1277
2103
|
}
|
|
1278
|
-
async
|
|
2104
|
+
async syncCreate(mutation) {
|
|
1279
2105
|
const res = await new HttpsRequest({
|
|
1280
2106
|
method: "POST" /* POST */,
|
|
1281
|
-
endpoint: this.app.getBaseUrl
|
|
1282
|
-
headers: {
|
|
1283
|
-
authorization: this.app.getAuth.token
|
|
1284
|
-
},
|
|
2107
|
+
endpoint: `${this.app.getBaseUrl()}/app/create`,
|
|
2108
|
+
headers: { authorization: this.app.getConfig().token },
|
|
1285
2109
|
body: {
|
|
1286
|
-
|
|
2110
|
+
collection: mutation.collection,
|
|
2111
|
+
data: mutation.payload
|
|
1287
2112
|
}
|
|
1288
2113
|
}).sendRequest();
|
|
1289
|
-
if (res?.
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
if (path.includes("/")) {
|
|
1305
|
-
const [col, id] = path.split("/");
|
|
1306
|
-
return new DocumentRef(this.app, col, id);
|
|
2114
|
+
if (!res?.success || !res.id)
|
|
2115
|
+
return false;
|
|
2116
|
+
const oldId = mutation.documentId;
|
|
2117
|
+
const newId = String(res.id);
|
|
2118
|
+
const replaced = await this.persistence.replaceDocId(
|
|
2119
|
+
mutation.collection,
|
|
2120
|
+
oldId,
|
|
2121
|
+
newId
|
|
2122
|
+
);
|
|
2123
|
+
if (replaced) {
|
|
2124
|
+
const snap = DocumentSnapshot.fromMap(replaced.data);
|
|
2125
|
+
this.store.emitDocument(mutation.collection, oldId, snap, "sync");
|
|
2126
|
+
this.store.emitDocument(mutation.collection, newId, snap, "sync");
|
|
2127
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(mutation.collection);
|
|
2128
|
+
this.store.emitCollection(mutation.collection, currentCollection, "sync", newId);
|
|
1307
2129
|
}
|
|
1308
|
-
|
|
1309
|
-
}
|
|
1310
|
-
batch() {
|
|
1311
|
-
return new Batch(this.app);
|
|
2130
|
+
return true;
|
|
1312
2131
|
}
|
|
1313
|
-
async
|
|
1314
|
-
const tx = {
|
|
1315
|
-
ops: [],
|
|
1316
|
-
async get(docRef) {
|
|
1317
|
-
return docRef.get();
|
|
1318
|
-
},
|
|
1319
|
-
set(docRef, data) {
|
|
1320
|
-
tx.ops.push({
|
|
1321
|
-
op: "set",
|
|
1322
|
-
collection: docRef.collection,
|
|
1323
|
-
id: docRef.id,
|
|
1324
|
-
data
|
|
1325
|
-
});
|
|
1326
|
-
},
|
|
1327
|
-
update(docRef, data) {
|
|
1328
|
-
tx.ops.push({
|
|
1329
|
-
op: "update",
|
|
1330
|
-
collection: docRef.collection,
|
|
1331
|
-
id: docRef.id,
|
|
1332
|
-
data
|
|
1333
|
-
});
|
|
1334
|
-
},
|
|
1335
|
-
delete(docRef) {
|
|
1336
|
-
tx.ops.push({
|
|
1337
|
-
op: "delete",
|
|
1338
|
-
collection: docRef.collection,
|
|
1339
|
-
id: docRef.id
|
|
1340
|
-
});
|
|
1341
|
-
}
|
|
1342
|
-
};
|
|
1343
|
-
await transactionFn(tx);
|
|
2132
|
+
async syncUpdate(mutation) {
|
|
1344
2133
|
const res = await new HttpsRequest({
|
|
1345
2134
|
method: "POST" /* POST */,
|
|
1346
|
-
endpoint: this.app.getBaseUrl
|
|
1347
|
-
headers: {
|
|
1348
|
-
authorization: this.app.getAuth.token
|
|
1349
|
-
},
|
|
2135
|
+
endpoint: `${this.app.getBaseUrl()}/app/update`,
|
|
2136
|
+
headers: { authorization: this.app.getConfig().token },
|
|
1350
2137
|
body: {
|
|
1351
|
-
|
|
2138
|
+
collection: mutation.collection,
|
|
2139
|
+
document: mutation.documentId,
|
|
2140
|
+
data: mutation.payload
|
|
1352
2141
|
}
|
|
1353
2142
|
}).sendRequest();
|
|
1354
|
-
if (res?.
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
// src/utils/uuid.ts
|
|
1364
|
-
function generateUUID() {
|
|
1365
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1366
|
-
return crypto.randomUUID();
|
|
1367
|
-
}
|
|
1368
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1369
|
-
const r = Math.random() * 16 | 0;
|
|
1370
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
1371
|
-
return v.toString(16);
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
// src/database/Realtime.ts
|
|
1376
|
-
var Realtime = class {
|
|
1377
|
-
constructor(db) {
|
|
1378
|
-
this.db = db;
|
|
1379
|
-
this.socket = null;
|
|
1380
|
-
this.events = ["error", "insert", "update", "delete"];
|
|
1381
|
-
}
|
|
1382
|
-
connect() {
|
|
1383
|
-
if (!this.db.getAuth?.token)
|
|
1384
|
-
throw new Error("Auth token missing");
|
|
1385
|
-
this.socket = (0, import_socket.io)(this.db.getSocketUrl, {
|
|
1386
|
-
transports: ["websocket"],
|
|
1387
|
-
auth: { token: this.db.getAuth.token }
|
|
1388
|
-
});
|
|
1389
|
-
return this.socket;
|
|
1390
|
-
}
|
|
1391
|
-
on(event, cb) {
|
|
1392
|
-
this.socket?.on(event, cb);
|
|
1393
|
-
}
|
|
1394
|
-
off(event, cb) {
|
|
1395
|
-
if (cb)
|
|
1396
|
-
this.socket?.off(event, cb);
|
|
1397
|
-
else
|
|
1398
|
-
this.socket?.removeAllListeners(event);
|
|
1399
|
-
}
|
|
1400
|
-
emit(event, data) {
|
|
1401
|
-
this.socket?.emit(event, data);
|
|
1402
|
-
}
|
|
1403
|
-
// ------------------------------------------------------------
|
|
1404
|
-
// 🔥 ADDITIONS BELOW — NO BREAKING CHANGES
|
|
1405
|
-
// ------------------------------------------------------------
|
|
1406
|
-
/**
|
|
1407
|
-
* Realtime listener for entire collection
|
|
1408
|
-
*/
|
|
1409
|
-
subscribeToCollection(collection, callback, filter) {
|
|
1410
|
-
if (!this.socket)
|
|
1411
|
-
this.connect();
|
|
1412
|
-
if (!filter) {
|
|
1413
|
-
filter = {};
|
|
1414
|
-
}
|
|
1415
|
-
const lid = `${generateUUID()}`;
|
|
1416
|
-
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1417
|
-
const handler = (payload) => {
|
|
1418
|
-
const snapshot = DocumentSnapshot.fromMap(payload);
|
|
1419
|
-
callback(snapshot, payload.change);
|
|
1420
|
-
};
|
|
1421
|
-
this.events.forEach((event) => {
|
|
1422
|
-
this.socket.on(`${lid}-${event}`, handler);
|
|
1423
|
-
});
|
|
1424
|
-
return () => {
|
|
1425
|
-
this.socket.emit("unsubscribe", { lid });
|
|
1426
|
-
this.socket.off(lid, handler);
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
/**
|
|
1430
|
-
* Realtime listener for a single document
|
|
1431
|
-
*/
|
|
1432
|
-
subscribeToDocument(collection, id, callback) {
|
|
1433
|
-
if (!this.socket)
|
|
1434
|
-
this.connect();
|
|
1435
|
-
const lid = id;
|
|
1436
|
-
this.socket.emit("subscribe", {
|
|
1437
|
-
collection,
|
|
1438
|
-
filter: {
|
|
1439
|
-
_id: lid
|
|
2143
|
+
if (!res?.success)
|
|
2144
|
+
return false;
|
|
2145
|
+
const local = await this.persistence.upsertDoc({
|
|
2146
|
+
collection: mutation.collection,
|
|
2147
|
+
id: mutation.documentId,
|
|
2148
|
+
data: {
|
|
2149
|
+
...res.document ?? mutation.payload ?? {},
|
|
2150
|
+
id: mutation.documentId
|
|
1440
2151
|
},
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const listener = `${lid}-${event}`;
|
|
1449
|
-
this.socket.on(listener, handler);
|
|
2152
|
+
exists: true,
|
|
2153
|
+
deleted: false,
|
|
2154
|
+
pending: 0,
|
|
2155
|
+
localOnly: false,
|
|
2156
|
+
status: "synced",
|
|
2157
|
+
lastSyncedAt: this.persistence["now"]?.() ?? Date.now()
|
|
2158
|
+
// fallback
|
|
1450
2159
|
});
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
};
|
|
1457
|
-
|
|
1458
|
-
// src/functions/Functions.ts
|
|
1459
|
-
var Functions = class {
|
|
1460
|
-
constructor(app) {
|
|
1461
|
-
this.app = app;
|
|
1462
|
-
}
|
|
1463
|
-
async call(functionName, data) {
|
|
1464
|
-
try {
|
|
1465
|
-
const res = await new HttpsRequest({
|
|
1466
|
-
method: "POST" /* POST */,
|
|
1467
|
-
endpoint: this.app.getBaseUrl + "/functions/call/" + functionName,
|
|
1468
|
-
headers: {
|
|
1469
|
-
authorization: this.app.getAuth.token
|
|
1470
|
-
},
|
|
1471
|
-
body: data
|
|
1472
|
-
}).sendRequest();
|
|
1473
|
-
return res;
|
|
1474
|
-
} catch (err) {
|
|
1475
|
-
throw {
|
|
1476
|
-
message: err.message || "Function call failed",
|
|
1477
|
-
code: err.code || "UNKNOWN_ERROR"
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
2160
|
+
const snap = DocumentSnapshot.fromMap(local.data);
|
|
2161
|
+
this.store.emitDocument(mutation.collection, mutation.documentId, snap, "sync");
|
|
2162
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(mutation.collection);
|
|
2163
|
+
this.store.emitCollection(mutation.collection, currentCollection, "sync", mutation.documentId);
|
|
2164
|
+
return true;
|
|
1480
2165
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
2166
|
+
async syncDelete(mutation) {
|
|
2167
|
+
const res = await new HttpsRequest({
|
|
2168
|
+
method: "POST" /* POST */,
|
|
2169
|
+
endpoint: `${this.app.getBaseUrl()}/app/delete`,
|
|
2170
|
+
headers: { authorization: this.app.getConfig().token },
|
|
2171
|
+
body: {
|
|
2172
|
+
collection: mutation.collection,
|
|
2173
|
+
document: mutation.documentId
|
|
2174
|
+
}
|
|
2175
|
+
}).sendRequest();
|
|
2176
|
+
if (!res?.success)
|
|
2177
|
+
return false;
|
|
2178
|
+
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
2179
|
+
this.store.emitDocument(mutation.collection, mutation.documentId, null, "sync");
|
|
2180
|
+
const currentCollection = await this.persistence.getCollectionSnapshots(mutation.collection);
|
|
2181
|
+
this.store.emitCollection(mutation.collection, currentCollection, "sync", mutation.documentId);
|
|
2182
|
+
return true;
|
|
1487
2183
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
throw {
|
|
1504
|
-
message: err.message || "Function call failed",
|
|
1505
|
-
code: err.code || "UNKNOWN_ERROR"
|
|
1506
|
-
};
|
|
2184
|
+
scheduleRetry() {
|
|
2185
|
+
if (this.retryTimeout)
|
|
2186
|
+
clearTimeout(this.retryTimeout);
|
|
2187
|
+
const delay = this.BASE_RETRY_DELAY * Math.pow(1.5, Math.random() * 0.5);
|
|
2188
|
+
this.retryTimeout = setTimeout(() => {
|
|
2189
|
+
this.flush().catch(console.error);
|
|
2190
|
+
}, delay);
|
|
2191
|
+
}
|
|
2192
|
+
// Public method users can call manually if needed
|
|
2193
|
+
async forceSync() {
|
|
2194
|
+
return this.flush();
|
|
2195
|
+
}
|
|
2196
|
+
dispose() {
|
|
2197
|
+
if (this.retryTimeout) {
|
|
2198
|
+
clearTimeout(this.retryTimeout);
|
|
1507
2199
|
}
|
|
1508
2200
|
}
|
|
1509
2201
|
};
|
|
@@ -1516,8 +2208,8 @@ var StorageSnapshot = class _StorageSnapshot {
|
|
|
1516
2208
|
}
|
|
1517
2209
|
static fromMap(map) {
|
|
1518
2210
|
const id = map.id ?? map._id ?? "";
|
|
1519
|
-
const
|
|
1520
|
-
return new _StorageSnapshot(id,
|
|
2211
|
+
const document2 = map.file ?? map.data ?? map;
|
|
2212
|
+
return new _StorageSnapshot(id, document2);
|
|
1521
2213
|
}
|
|
1522
2214
|
toMap() {
|
|
1523
2215
|
return {
|
|
@@ -1529,15 +2221,15 @@ var StorageSnapshot = class _StorageSnapshot {
|
|
|
1529
2221
|
|
|
1530
2222
|
// src/storage/StorageRef.ts
|
|
1531
2223
|
var StorageRef = class {
|
|
1532
|
-
constructor(
|
|
1533
|
-
this.
|
|
2224
|
+
constructor(app) {
|
|
2225
|
+
this.app = app;
|
|
1534
2226
|
}
|
|
1535
2227
|
async get(id) {
|
|
1536
2228
|
const res = await new HttpsRequest({
|
|
1537
2229
|
method: "GET" /* GET */,
|
|
1538
|
-
endpoint: this.
|
|
2230
|
+
endpoint: this.app.getBaseUrl() + `/storage/${id}`,
|
|
1539
2231
|
headers: {
|
|
1540
|
-
//authorization: this.
|
|
2232
|
+
//authorization: this.app.getConfig.token,
|
|
1541
2233
|
}
|
|
1542
2234
|
}).sendRequest();
|
|
1543
2235
|
if (res.success) {
|
|
@@ -1552,9 +2244,9 @@ var StorageRef = class {
|
|
|
1552
2244
|
async getMeta(id) {
|
|
1553
2245
|
const res = await new HttpsRequest({
|
|
1554
2246
|
method: "GET" /* GET */,
|
|
1555
|
-
endpoint: this.
|
|
2247
|
+
endpoint: this.app.getBaseUrl() + `/storage/${id}/info`,
|
|
1556
2248
|
headers: {
|
|
1557
|
-
//authorization: this.
|
|
2249
|
+
//authorization: this.app.getConfig.token,
|
|
1558
2250
|
}
|
|
1559
2251
|
}).sendRequest();
|
|
1560
2252
|
if (res.success) {
|
|
@@ -1573,9 +2265,9 @@ var StorageRef = class {
|
|
|
1573
2265
|
console.log("SRCF");
|
|
1574
2266
|
const res = await new HttpsRequest({
|
|
1575
2267
|
method: "POST" /* POST */,
|
|
1576
|
-
endpoint: this.
|
|
2268
|
+
endpoint: this.app.getBaseUrl() + "/storage/file/upload",
|
|
1577
2269
|
headers: {
|
|
1578
|
-
authorization: this.
|
|
2270
|
+
authorization: this.app.getConfig().token
|
|
1579
2271
|
},
|
|
1580
2272
|
file: srcFile,
|
|
1581
2273
|
isMultipart: true
|
|
@@ -1592,9 +2284,9 @@ var StorageRef = class {
|
|
|
1592
2284
|
async delete(id) {
|
|
1593
2285
|
const res = await new HttpsRequest({
|
|
1594
2286
|
method: "POST" /* POST */,
|
|
1595
|
-
endpoint: this.
|
|
2287
|
+
endpoint: this.app.getBaseUrl() + "/storage/file/delete",
|
|
1596
2288
|
headers: {
|
|
1597
|
-
authorization: this.
|
|
2289
|
+
authorization: this.app.getConfig().token
|
|
1598
2290
|
},
|
|
1599
2291
|
body: {
|
|
1600
2292
|
file_id: id
|
|
@@ -1620,66 +2312,95 @@ var socketURI = "https://api.edmaxlabs.com";
|
|
|
1620
2312
|
|
|
1621
2313
|
// src/core/EdmaxLabs.ts
|
|
1622
2314
|
var _EdmaxLabs = class _EdmaxLabs {
|
|
1623
|
-
constructor(
|
|
1624
|
-
|
|
2315
|
+
constructor(config) {
|
|
2316
|
+
// Offline layer (only initialized when persistence: true)
|
|
2317
|
+
this.persistence = null;
|
|
2318
|
+
this.localStore = null;
|
|
2319
|
+
this.syncEngine = null;
|
|
2320
|
+
this.realtimeBridge = null;
|
|
2321
|
+
const { token, project, persistence = false } = config;
|
|
2322
|
+
if (!token || !project) {
|
|
2323
|
+
throw new Error("EdmaxLabs: 'token' and 'project' are required in config");
|
|
2324
|
+
}
|
|
1625
2325
|
this.baseUrl = serverURI;
|
|
1626
2326
|
this.socketUrl = socketURI;
|
|
1627
|
-
this.
|
|
2327
|
+
this._config = config;
|
|
1628
2328
|
this._db = new Database(this);
|
|
2329
|
+
this._realtime = new Realtime(this);
|
|
1629
2330
|
this._hosting = new Hosting(this);
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2331
|
+
if (persistence) {
|
|
2332
|
+
this.persistence = new Persistence();
|
|
2333
|
+
this.localStore = new LocalStore();
|
|
2334
|
+
this.realtimeBridge = new RealtimeBridge(this, this.persistence, this.localStore);
|
|
2335
|
+
this.syncEngine = new SyncEngine(
|
|
2336
|
+
this,
|
|
2337
|
+
this.persistence,
|
|
2338
|
+
this.localStore,
|
|
2339
|
+
this.realtimeBridge
|
|
2340
|
+
);
|
|
1636
2341
|
}
|
|
1637
2342
|
}
|
|
2343
|
+
// ===================== PUBLIC API =====================
|
|
2344
|
+
/** Recommended for most React apps (creates fresh instance) */
|
|
2345
|
+
static create(config) {
|
|
2346
|
+
return new _EdmaxLabs(config);
|
|
2347
|
+
}
|
|
2348
|
+
/** Firebase-style singleton (kept for backward compatibility) */
|
|
1638
2349
|
static initialize(config) {
|
|
1639
2350
|
if (!_EdmaxLabs.instance) {
|
|
1640
2351
|
_EdmaxLabs.instance = new _EdmaxLabs(config);
|
|
1641
2352
|
}
|
|
1642
2353
|
return _EdmaxLabs.instance;
|
|
1643
2354
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
1647
|
-
get getAuth() {
|
|
1648
|
-
return this.auth;
|
|
1649
|
-
}
|
|
1650
|
-
get getBaseUrl() {
|
|
1651
|
-
return this.baseUrl;
|
|
1652
|
-
}
|
|
1653
|
-
get getSocketUrl() {
|
|
1654
|
-
return this.socketUrl;
|
|
1655
|
-
}
|
|
1656
|
-
get database() {
|
|
2355
|
+
// ===================== CLEAN GETTERS =====================
|
|
2356
|
+
get getDatabase() {
|
|
1657
2357
|
return this._db;
|
|
1658
2358
|
}
|
|
1659
|
-
get
|
|
1660
|
-
return new Storage(this);
|
|
1661
|
-
}
|
|
1662
|
-
get hosting() {
|
|
1663
|
-
return this._hosting;
|
|
1664
|
-
}
|
|
1665
|
-
get functions() {
|
|
2359
|
+
get getFunctions() {
|
|
1666
2360
|
return new Functions(this);
|
|
1667
2361
|
}
|
|
1668
|
-
get
|
|
1669
|
-
return this
|
|
1670
|
-
}
|
|
1671
|
-
sync(id, collection) {
|
|
1672
|
-
if (!this.getPersistence) {
|
|
1673
|
-
throw new Error("Persistence is Off Can't Sync.");
|
|
1674
|
-
}
|
|
1675
|
-
return this.persistence_worker.syncPendingWrite(id, collection);
|
|
2362
|
+
get getStorage() {
|
|
2363
|
+
return new Storage(this);
|
|
1676
2364
|
}
|
|
1677
|
-
get
|
|
2365
|
+
get getAuthentication() {
|
|
1678
2366
|
if (!Authentication.instance) {
|
|
1679
2367
|
Authentication.instance = new Authentication();
|
|
1680
2368
|
}
|
|
1681
2369
|
return Authentication.instance;
|
|
1682
2370
|
}
|
|
2371
|
+
/** New clean offline namespace - highly recommended */
|
|
2372
|
+
offline() {
|
|
2373
|
+
return {
|
|
2374
|
+
persistence: this.persistence,
|
|
2375
|
+
localStore: this.localStore,
|
|
2376
|
+
syncEngine: this.syncEngine,
|
|
2377
|
+
realtimeBridge: this.realtimeBridge,
|
|
2378
|
+
enabled: !!this.persistence
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
// Internal access (for internal classes only)
|
|
2382
|
+
getConfig() {
|
|
2383
|
+
return { ...this._config };
|
|
2384
|
+
}
|
|
2385
|
+
getBaseUrl() {
|
|
2386
|
+
return this.baseUrl;
|
|
2387
|
+
}
|
|
2388
|
+
getSocketUrl() {
|
|
2389
|
+
return this.socketUrl;
|
|
2390
|
+
}
|
|
2391
|
+
rtdb() {
|
|
2392
|
+
return this._realtime;
|
|
2393
|
+
}
|
|
2394
|
+
// ===================== LIFECYCLE =====================
|
|
2395
|
+
/** Call this when your app unmounts or user logs out */
|
|
2396
|
+
dispose() {
|
|
2397
|
+
this._realtime.dispose?.();
|
|
2398
|
+
this.syncEngine?.dispose?.();
|
|
2399
|
+
this.realtimeBridge?.dispose?.();
|
|
2400
|
+
if (_EdmaxLabs.instance === this) {
|
|
2401
|
+
_EdmaxLabs.instance = null;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
1683
2404
|
};
|
|
1684
2405
|
_EdmaxLabs.instance = null;
|
|
1685
2406
|
var EdmaxLabs = _EdmaxLabs;
|