nexabase-console 2.1.5 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/NexaApp.js +27 -17
- package/dist/firestore/writes.d.ts +4 -0
- package/dist/firestore/writes.js +23 -1
- package/dist/sync/SyncEngine.d.ts +1 -0
- package/dist/sync/SyncEngine.js +39 -1
- package/package.json +1 -1
package/dist/app/NexaApp.js
CHANGED
|
@@ -59,7 +59,6 @@ class NexaApp {
|
|
|
59
59
|
if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
|
|
60
60
|
document.addEventListener('visibilitychange', () => {
|
|
61
61
|
if (document.visibilityState === 'visible') {
|
|
62
|
-
// Re-ensure SSE and WS when coming back from background
|
|
63
62
|
if (this._firestoreListening) {
|
|
64
63
|
if (this.wsClient && typeof this.wsClient.ensureConnected === 'function') {
|
|
65
64
|
this.wsClient.ensureConnected();
|
|
@@ -67,10 +66,7 @@ class NexaApp {
|
|
|
67
66
|
if (this.sseClient && typeof this.sseClient.ensureConnected === 'function') {
|
|
68
67
|
this.sseClient.ensureConnected();
|
|
69
68
|
}
|
|
70
|
-
// Notify server that we need latest changes by simulating subscribe again for each path?
|
|
71
|
-
// wsClient handles this on resync event.
|
|
72
69
|
}
|
|
73
|
-
// Optionally sync offline queue
|
|
74
70
|
this._syncOfflineQueue().catch(console.error);
|
|
75
71
|
}
|
|
76
72
|
});
|
|
@@ -80,10 +76,10 @@ class NexaApp {
|
|
|
80
76
|
this.syncEngine.listenCrossTab((path, action, data) => {
|
|
81
77
|
const cleanPath = path.replace(/^\/+|\/+$/g, '');
|
|
82
78
|
if (action === 'delete') {
|
|
83
|
-
this.
|
|
79
|
+
this._deleteCache(cleanPath);
|
|
84
80
|
}
|
|
85
|
-
else if (data) {
|
|
86
|
-
this.
|
|
81
|
+
else if (data !== undefined && data !== null) {
|
|
82
|
+
this._setCache(cleanPath, data);
|
|
87
83
|
}
|
|
88
84
|
this._notifySnapshotCallbacks(cleanPath, action === 'delete' ? 'document_deleted' : 'document_written', data);
|
|
89
85
|
const parts = cleanPath.split('/');
|
|
@@ -395,17 +391,31 @@ class NexaApp {
|
|
|
395
391
|
const isDelete = type === 'document_deleted' || action === 'delete' || type === 'delete';
|
|
396
392
|
if (targetDocPath && typeof targetDocPath === 'string') {
|
|
397
393
|
const cleanDocPath = targetDocPath.replace(/^\/+|\/+$/g, '');
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
394
|
+
const segments = cleanDocPath.split('/');
|
|
395
|
+
const isCollection = segments.length % 2 === 1;
|
|
396
|
+
if (isCollection && data !== null && typeof data === 'object' && !Array.isArray(data)) {
|
|
397
|
+
// Distribute collection payload to individual docs
|
|
398
|
+
if (!isDelete) {
|
|
399
|
+
for (const docId of Object.keys(data)) {
|
|
400
|
+
const docData = data[docId];
|
|
401
|
+
this._setCache(`${cleanDocPath}/${docId}`, docData);
|
|
402
|
+
this._notifySnapshotCallbacks(`${cleanDocPath}/${docId}`, 'document_written', docData);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
this._notifySnapshotCallbacks(cleanDocPath, isDelete ? 'document_deleted' : 'document_written', data);
|
|
403
406
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
else {
|
|
408
|
+
if (isDelete) {
|
|
409
|
+
this._deleteCache(cleanDocPath);
|
|
410
|
+
}
|
|
411
|
+
else if (data !== undefined && data !== null) {
|
|
412
|
+
this._setCache(cleanDocPath, data);
|
|
413
|
+
}
|
|
414
|
+
const colPath = segments.length > 1 ? segments.slice(0, -1).join('/') : '';
|
|
415
|
+
this._notifySnapshotCallbacks(cleanDocPath, isDelete ? 'document_deleted' : 'document_written', data);
|
|
416
|
+
if (colPath) {
|
|
417
|
+
this._notifySnapshotCallbacks(colPath, isDelete ? 'document_deleted' : 'document_written', data);
|
|
418
|
+
}
|
|
409
419
|
}
|
|
410
420
|
}
|
|
411
421
|
else if (collectionPath && typeof collectionPath === 'string') {
|
|
@@ -13,6 +13,10 @@ export type WriteResult<T = unknown> = {
|
|
|
13
13
|
writtenLocally: true;
|
|
14
14
|
mutationId: string;
|
|
15
15
|
};
|
|
16
|
+
export declare const addDoc: <T = any>(reference: any, // CollectionReference
|
|
17
|
+
data: any, options?: {
|
|
18
|
+
idempotencyKey?: string;
|
|
19
|
+
}) => Promise<DocumentReference<T>>;
|
|
16
20
|
export declare const setDoc: (docRef: DocumentReference, data: any, options?: {
|
|
17
21
|
merge?: boolean;
|
|
18
22
|
idempotencyKey?: string;
|
package/dist/firestore/writes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getPendingWritesCount = exports.deleteDoc = exports.updateDoc = exports.patchDoc = exports.setDoc = exports.FieldValue = void 0;
|
|
3
|
+
exports.getPendingWritesCount = exports.deleteDoc = exports.updateDoc = exports.patchDoc = exports.setDoc = exports.addDoc = exports.FieldValue = void 0;
|
|
4
4
|
const helpers_1 = require("../utils/helpers");
|
|
5
5
|
const NexaError_1 = require("../errors/NexaError");
|
|
6
6
|
const QueryUtils_1 = require("./QueryUtils");
|
|
@@ -217,6 +217,28 @@ const executeWriteMutation = async (docRef, action, data, options) => {
|
|
|
217
217
|
});
|
|
218
218
|
return { status: 'committed', committed: true, writtenLocally: true, mutationId, data: ackView.data };
|
|
219
219
|
};
|
|
220
|
+
const addDoc = async (reference, // CollectionReference
|
|
221
|
+
data, options) => {
|
|
222
|
+
// We need to import doc, but since writes.ts doesn't have it, we can construct the path.
|
|
223
|
+
// Actually it's easier to just generate an auto ID if we can't import doc.
|
|
224
|
+
// We don't want circular dependencies.
|
|
225
|
+
const db = reference.db;
|
|
226
|
+
const generateAutoId = () => {
|
|
227
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
228
|
+
let autoId = '';
|
|
229
|
+
for (let i = 0; i < 20; i++)
|
|
230
|
+
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
231
|
+
return autoId;
|
|
232
|
+
};
|
|
233
|
+
const d = {
|
|
234
|
+
type: 'document',
|
|
235
|
+
path: `${reference.path}/${generateAutoId()}`,
|
|
236
|
+
db
|
|
237
|
+
};
|
|
238
|
+
await executeWriteMutation(d, 'set', data, options);
|
|
239
|
+
return d;
|
|
240
|
+
};
|
|
241
|
+
exports.addDoc = addDoc;
|
|
220
242
|
const setDoc = async (docRef, data, options) => {
|
|
221
243
|
return executeWriteMutation(docRef, 'set', data, options);
|
|
222
244
|
};
|
|
@@ -7,6 +7,7 @@ export declare class SyncEngine {
|
|
|
7
7
|
private broadcastChannel;
|
|
8
8
|
constructor(projectId: string, mutationQueue: MutationQueue, sseClient: SSEClient);
|
|
9
9
|
private initCrossTabSync;
|
|
10
|
+
private _serializeForBroadcast;
|
|
10
11
|
broadcastLocalMutation(path: string, action: string, data?: any): void;
|
|
11
12
|
listenCrossTab(onMutation: (path: string, action: string, data?: any) => void): () => void;
|
|
12
13
|
syncOfflineMutations(): Promise<void>;
|
package/dist/sync/SyncEngine.js
CHANGED
|
@@ -19,10 +19,48 @@ class SyncEngine {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
_serializeForBroadcast(data, seen = new WeakSet()) {
|
|
23
|
+
if (data === null || data === undefined)
|
|
24
|
+
return data;
|
|
25
|
+
if (typeof data === 'boolean' || typeof data === 'number' || typeof data === 'string')
|
|
26
|
+
return data;
|
|
27
|
+
if (data instanceof Date) {
|
|
28
|
+
return { __nexa_type__: 'timestamp', seconds: Math.floor(data.getTime() / 1000), nanoseconds: (data.getTime() % 1000) * 1000000 };
|
|
29
|
+
}
|
|
30
|
+
// Check if it's a Timestamp object (even without prototype due to potential prior cloning)
|
|
31
|
+
if (typeof data === 'object' && 'seconds' in data && 'nanoseconds' in data && typeof data.seconds === 'number' && typeof data.nanoseconds === 'number') {
|
|
32
|
+
return { __nexa_type__: 'timestamp', seconds: data.seconds, nanoseconds: data.nanoseconds };
|
|
33
|
+
}
|
|
34
|
+
// Check for GeoPoint
|
|
35
|
+
if (typeof data === 'object' && 'latitude' in data && 'longitude' in data && typeof data.latitude === 'number' && typeof data.longitude === 'number') {
|
|
36
|
+
return { __nexa_type__: 'geopoint', latitude: data.latitude, longitude: data.longitude };
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(data)) {
|
|
39
|
+
if (seen.has(data))
|
|
40
|
+
return null;
|
|
41
|
+
seen.add(data);
|
|
42
|
+
const res = data.map(item => this._serializeForBroadcast(item, seen));
|
|
43
|
+
seen.delete(data);
|
|
44
|
+
return res;
|
|
45
|
+
}
|
|
46
|
+
if (typeof data === 'object') {
|
|
47
|
+
if (seen.has(data))
|
|
48
|
+
return null;
|
|
49
|
+
seen.add(data);
|
|
50
|
+
const res = {};
|
|
51
|
+
for (const key of Object.keys(data)) {
|
|
52
|
+
res[key] = this._serializeForBroadcast(data[key], seen);
|
|
53
|
+
}
|
|
54
|
+
seen.delete(data);
|
|
55
|
+
return res;
|
|
56
|
+
}
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
22
59
|
broadcastLocalMutation(path, action, data) {
|
|
23
60
|
if (this.broadcastChannel) {
|
|
24
61
|
try {
|
|
25
|
-
this.
|
|
62
|
+
const serialized = this._serializeForBroadcast(data);
|
|
63
|
+
this.broadcastChannel.postMessage({ type: 'LOCAL_MUTATION', path, action, data: serialized });
|
|
26
64
|
}
|
|
27
65
|
catch (e) { }
|
|
28
66
|
}
|
package/package.json
CHANGED