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