nexabase-console 2.0.6 → 2.0.8
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.d.ts +47 -3
- package/dist/app/NexaApp.js +269 -6
- package/dist/firestore/Query.d.ts +1 -1
- package/dist/firestore/Query.js +176 -125
- package/dist/firestore/QueryUtils.d.ts +4 -0
- package/dist/firestore/QueryUtils.js +85 -0
- package/dist/firestore/writes.d.ts +17 -4
- package/dist/firestore/writes.js +177 -123
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/persistence/LocalStore.d.ts +5 -0
- package/dist/persistence/LocalStore.js +57 -1
- package/dist/persistence/MutationQueue.d.ts +3 -2
- package/dist/persistence/MutationQueue.js +32 -7
- package/dist/transport/SSEClient.d.ts +6 -5
- package/dist/transport/SSEClient.js +19 -76
- package/dist/transport/WebSocketClient.d.ts +24 -0
- package/dist/transport/WebSocketClient.js +245 -0
- package/dist/types/index.d.ts +8 -1
- package/package.json +3 -2
package/dist/app/NexaApp.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
2
|
import { NexaConfig, DatabaseReference, StorageReference, UploadOptions, OfflineJob } from '../types/index';
|
|
3
3
|
import { HttpClient } from '../transport/HttpClient';
|
|
4
|
-
import { SSEClient } from '../transport/SSEClient';
|
|
4
|
+
import { SSEClient, WebSocketClient } from '../transport/SSEClient';
|
|
5
5
|
import { Auth } from '../auth/Auth';
|
|
6
6
|
import { User as AuthUser, AuthSession } from '../auth/authTypes';
|
|
7
7
|
import { IndexedDB } from '../persistence/IndexedDB';
|
|
@@ -16,8 +16,10 @@ export declare class NexaApp {
|
|
|
16
16
|
endpoint: string;
|
|
17
17
|
enablePersistence: boolean;
|
|
18
18
|
cacheStrategy: 'network-first' | 'cache-first';
|
|
19
|
+
_queryCacheMetadata: Record<string, boolean>;
|
|
19
20
|
httpClient: HttpClient;
|
|
20
21
|
client: AxiosInstance;
|
|
22
|
+
wsClient: WebSocketClient;
|
|
21
23
|
sseClient: SSEClient;
|
|
22
24
|
authService: Auth;
|
|
23
25
|
indexedDB: IndexedDB;
|
|
@@ -35,15 +37,55 @@ export declare class NexaApp {
|
|
|
35
37
|
private _setupCrossTabSync;
|
|
36
38
|
_broadcastLocalMutation(path: string, action: string, data?: any): void;
|
|
37
39
|
hasPendingWrites(path: string): boolean;
|
|
38
|
-
markInflight(path: string): void;
|
|
39
|
-
unmarkInflight(path: string): void;
|
|
40
|
+
markInflight(path: string, mutationId: string): void;
|
|
41
|
+
unmarkInflight(path: string, mutationId: string): void;
|
|
40
42
|
_initFirestoreState(): Promise<void>;
|
|
41
43
|
_setCache(path: string, data: any): Promise<void>;
|
|
42
44
|
_deleteCache(path: string): Promise<void>;
|
|
43
45
|
_addOfflineJob(job: OfflineJob): Promise<void>;
|
|
46
|
+
_calculateLocalView(path: string): {
|
|
47
|
+
data: any | null;
|
|
48
|
+
hasPendingWrites: boolean;
|
|
49
|
+
};
|
|
50
|
+
_commitAtomicMutation(path: string, action: 'set' | 'patch' | 'delete', job: OfflineJob): Promise<{
|
|
51
|
+
data: any | null;
|
|
52
|
+
hasPendingWrites: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
_commitMutationAck(options: {
|
|
55
|
+
mutationId: string;
|
|
56
|
+
path: string;
|
|
57
|
+
action: 'set' | 'patch' | 'delete';
|
|
58
|
+
serverDocument?: any;
|
|
59
|
+
updateTime?: string;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
data: any | null;
|
|
62
|
+
hasPendingWrites: boolean;
|
|
63
|
+
}>;
|
|
64
|
+
_rejectMutationAtomically(options: {
|
|
65
|
+
mutationId: string;
|
|
66
|
+
path: string;
|
|
67
|
+
error: any;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
data: any | null;
|
|
70
|
+
hasPendingWrites: boolean;
|
|
71
|
+
}>;
|
|
72
|
+
_commitAtomicQueryResults(options: {
|
|
73
|
+
queryKey: string;
|
|
74
|
+
collectionPath: string;
|
|
75
|
+
serverDocs: {
|
|
76
|
+
id: string;
|
|
77
|
+
fields: any;
|
|
78
|
+
}[];
|
|
79
|
+
isComplete: boolean;
|
|
80
|
+
constraints?: any[];
|
|
81
|
+
readTime?: string;
|
|
82
|
+
revision?: string;
|
|
83
|
+
}): Promise<void>;
|
|
44
84
|
_syncOfflineQueue(): Promise<void>;
|
|
45
85
|
_ensureFirestoreSSE(): void;
|
|
86
|
+
_ensureFirestoreWebSocket(): void;
|
|
46
87
|
_closeFirestoreSSEIfIdle(): void;
|
|
88
|
+
_closeFirestoreWebSocketIfIdle(): void;
|
|
47
89
|
_notifySnapshotCallbacks(path: string, actionType: string, payload: any): void;
|
|
48
90
|
signInWithEmailAndPassword(email: string, password: string): Promise<AuthSession>;
|
|
49
91
|
createUserWithEmailAndPassword(email: string, password: string, name?: string): Promise<AuthSession>;
|
|
@@ -61,4 +103,6 @@ export declare class NexaApp {
|
|
|
61
103
|
path: string;
|
|
62
104
|
}>;
|
|
63
105
|
getDownloadURL(path: string): Promise<string>;
|
|
106
|
+
isConnected(): boolean;
|
|
107
|
+
onConnectionStateChanged(callback: (isConnected: boolean) => void): () => void;
|
|
64
108
|
}
|
package/dist/app/NexaApp.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.NexaApp = void 0;
|
|
4
|
+
const helpers_1 = require("../utils/helpers");
|
|
5
|
+
const QueryUtils_1 = require("../firestore/QueryUtils");
|
|
4
6
|
const HttpClient_1 = require("../transport/HttpClient");
|
|
5
7
|
const SSEClient_1 = require("../transport/SSEClient");
|
|
6
8
|
const Auth_1 = require("../auth/Auth");
|
|
@@ -14,6 +16,7 @@ const Storage_1 = require("../storage/Storage");
|
|
|
14
16
|
class NexaApp {
|
|
15
17
|
constructor(config) {
|
|
16
18
|
this.token = null;
|
|
19
|
+
this._queryCacheMetadata = {};
|
|
17
20
|
// Internal Firestore caches/callbacks
|
|
18
21
|
this._snapshotCallbacks = {};
|
|
19
22
|
this._firestoreListening = false;
|
|
@@ -31,6 +34,7 @@ class NexaApp {
|
|
|
31
34
|
// Transport
|
|
32
35
|
this.httpClient = new HttpClient_1.HttpClient(this.endpoint, () => (this.authService ? this.authService.getToken() : this.token), this.projectId, this.token);
|
|
33
36
|
this.client = this.httpClient.getAxiosInstance();
|
|
37
|
+
this.wsClient = new SSEClient_1.WebSocketClient(this.projectId, this.endpoint);
|
|
34
38
|
this.sseClient = new SSEClient_1.SSEClient(this.projectId, this.endpoint);
|
|
35
39
|
// Auth & Persistence
|
|
36
40
|
this.authService = new Auth_1.Auth(this.projectId, this.client, this.token);
|
|
@@ -65,11 +69,11 @@ class NexaApp {
|
|
|
65
69
|
hasPendingWrites(path) {
|
|
66
70
|
return this.mutationQueue.hasPendingWrites(path);
|
|
67
71
|
}
|
|
68
|
-
markInflight(path) {
|
|
69
|
-
this.mutationQueue.markInflight(path);
|
|
72
|
+
markInflight(path, mutationId) {
|
|
73
|
+
this.mutationQueue.markInflight(path, mutationId);
|
|
70
74
|
}
|
|
71
|
-
unmarkInflight(path) {
|
|
72
|
-
this.mutationQueue.unmarkInflight(path);
|
|
75
|
+
unmarkInflight(path, mutationId) {
|
|
76
|
+
this.mutationQueue.unmarkInflight(path, mutationId);
|
|
73
77
|
}
|
|
74
78
|
async _initFirestoreState() {
|
|
75
79
|
if (!this.enablePersistence)
|
|
@@ -77,6 +81,7 @@ class NexaApp {
|
|
|
77
81
|
try {
|
|
78
82
|
await this.indexedDB.init();
|
|
79
83
|
await this.localStore.loadAll();
|
|
84
|
+
await this.localStore.garbageCollect(1000); // 1000 docs limit
|
|
80
85
|
await this.mutationQueue.init();
|
|
81
86
|
if (typeof window !== 'undefined') {
|
|
82
87
|
window.addEventListener('online', () => this._syncOfflineQueue());
|
|
@@ -96,6 +101,226 @@ class NexaApp {
|
|
|
96
101
|
async _addOfflineJob(job) {
|
|
97
102
|
await this.mutationQueue.addJob(job);
|
|
98
103
|
}
|
|
104
|
+
_calculateLocalView(path) {
|
|
105
|
+
let base = this.localStore.getServerBaseCache()[path];
|
|
106
|
+
base = base !== undefined && base !== null ? (0, QueryUtils_1.cloneNexaData)(base) : base;
|
|
107
|
+
const mQueue = this.mutationQueue.inMemoryQueue || [];
|
|
108
|
+
let hasPendingWrites = false;
|
|
109
|
+
for (const job of mQueue) {
|
|
110
|
+
if (job.path !== path)
|
|
111
|
+
continue;
|
|
112
|
+
hasPendingWrites = true;
|
|
113
|
+
if (job.type === 'delete') {
|
|
114
|
+
base = null;
|
|
115
|
+
}
|
|
116
|
+
else if (job.type === 'set' && !job.merge) {
|
|
117
|
+
base = (0, helpers_1.applyDataTransforms)({}, job.data);
|
|
118
|
+
}
|
|
119
|
+
else if (base !== null && base !== undefined) {
|
|
120
|
+
base = (0, helpers_1.applyDataTransforms)(base, job.data);
|
|
121
|
+
}
|
|
122
|
+
else if (job.type === 'set' || job.type === 'patch' || job.type === 'update') {
|
|
123
|
+
base = (0, helpers_1.applyDataTransforms)(base || {}, job.data);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { data: base === undefined ? null : base, hasPendingWrites };
|
|
127
|
+
}
|
|
128
|
+
async _commitAtomicMutation(path, action, job) {
|
|
129
|
+
const db = this.indexedDB.getRawDB();
|
|
130
|
+
this.mutationQueue.inMemoryQueue.push(job);
|
|
131
|
+
const view = this._calculateLocalView(path);
|
|
132
|
+
const rebasedData = view.data;
|
|
133
|
+
if (rebasedData === null) {
|
|
134
|
+
delete this.localStore.getMemoryCache()[path];
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
this.localStore.getMemoryCache()[path] = rebasedData;
|
|
138
|
+
}
|
|
139
|
+
if (!db)
|
|
140
|
+
return view;
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const tx = db.transaction(['offline_cache', 'offline_queue'], 'readwrite');
|
|
143
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
144
|
+
const queueStore = tx.objectStore('offline_queue');
|
|
145
|
+
if (rebasedData === null) {
|
|
146
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
147
|
+
if (sb !== undefined) {
|
|
148
|
+
cacheStore.put({ path, data: null, serverBase: sb, timestamp: Date.now() });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
cacheStore.delete(path);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
156
|
+
cacheStore.put({ path, data: rebasedData, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
157
|
+
}
|
|
158
|
+
queueStore.put(job);
|
|
159
|
+
tx.oncomplete = () => resolve(view);
|
|
160
|
+
tx.onerror = () => reject(tx.error);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async _commitMutationAck(options) {
|
|
164
|
+
const { mutationId, path, serverDocument } = options;
|
|
165
|
+
const db = this.indexedDB.getRawDB();
|
|
166
|
+
const queue = this.mutationQueue.inMemoryQueue;
|
|
167
|
+
const jobIndex = queue.findIndex((j) => j.id === mutationId);
|
|
168
|
+
if (jobIndex !== -1)
|
|
169
|
+
queue.splice(jobIndex, 1);
|
|
170
|
+
if (options.action === 'delete') {
|
|
171
|
+
this.localStore.getServerBaseCache()[path] = null;
|
|
172
|
+
}
|
|
173
|
+
else if (serverDocument && serverDocument.fields) {
|
|
174
|
+
this.localStore.getServerBaseCache()[path] = serverDocument.fields;
|
|
175
|
+
}
|
|
176
|
+
const view = this._calculateLocalView(path);
|
|
177
|
+
const rebasedData = view.data;
|
|
178
|
+
if (rebasedData === null) {
|
|
179
|
+
delete this.localStore.getMemoryCache()[path];
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.localStore.getMemoryCache()[path] = rebasedData;
|
|
183
|
+
}
|
|
184
|
+
if (!db)
|
|
185
|
+
return view;
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
const tx = db.transaction(['offline_cache', 'offline_queue'], 'readwrite');
|
|
188
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
189
|
+
const queueStore = tx.objectStore('offline_queue');
|
|
190
|
+
queueStore.delete(mutationId);
|
|
191
|
+
if (rebasedData === null) {
|
|
192
|
+
if (options.action === 'delete') {
|
|
193
|
+
cacheStore.put({ path, data: null, serverBase: null, timestamp: Date.now() });
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
197
|
+
cacheStore.put({ path, data: null, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
202
|
+
cacheStore.put({ path, data: rebasedData, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
203
|
+
}
|
|
204
|
+
tx.oncomplete = () => resolve(view);
|
|
205
|
+
tx.onerror = () => reject(tx.error);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async _rejectMutationAtomically(options) {
|
|
209
|
+
const { mutationId, path } = options;
|
|
210
|
+
const db = this.indexedDB.getRawDB();
|
|
211
|
+
const queue = this.mutationQueue.inMemoryQueue;
|
|
212
|
+
const jobIndex = queue.findIndex((j) => j.id === mutationId);
|
|
213
|
+
if (jobIndex !== -1)
|
|
214
|
+
queue.splice(jobIndex, 1);
|
|
215
|
+
const view = this._calculateLocalView(path);
|
|
216
|
+
const rebasedData = view.data;
|
|
217
|
+
if (rebasedData === null) {
|
|
218
|
+
delete this.localStore.getMemoryCache()[path];
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
this.localStore.getMemoryCache()[path] = rebasedData;
|
|
222
|
+
}
|
|
223
|
+
if (!db)
|
|
224
|
+
return view;
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
const tx = db.transaction(['offline_cache', 'offline_queue'], 'readwrite');
|
|
227
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
228
|
+
const queueStore = tx.objectStore('offline_queue');
|
|
229
|
+
queueStore.delete(mutationId);
|
|
230
|
+
if (rebasedData === null) {
|
|
231
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
232
|
+
if (sb === undefined)
|
|
233
|
+
cacheStore.delete(path);
|
|
234
|
+
else
|
|
235
|
+
cacheStore.put({ path, data: null, serverBase: sb, timestamp: Date.now() });
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
239
|
+
cacheStore.put({ path, data: rebasedData, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
240
|
+
}
|
|
241
|
+
tx.oncomplete = () => resolve(view);
|
|
242
|
+
tx.onerror = () => reject(tx.error);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async _commitAtomicQueryResults(options) {
|
|
246
|
+
const { collectionPath, serverDocs, isComplete } = options;
|
|
247
|
+
const db = this.indexedDB.getRawDB();
|
|
248
|
+
const pendingMutations = new Map();
|
|
249
|
+
// Get jobs array directly from mutation queue memory
|
|
250
|
+
const mQueue = this.mutationQueue.inMemoryQueue || [];
|
|
251
|
+
for (const job of mQueue) {
|
|
252
|
+
if (!pendingMutations.has(job.path))
|
|
253
|
+
pendingMutations.set(job.path, []);
|
|
254
|
+
pendingMutations.get(job.path).push(job);
|
|
255
|
+
}
|
|
256
|
+
const finalUpdates = new Map();
|
|
257
|
+
const serverIds = new Set();
|
|
258
|
+
for (const doc of serverDocs) {
|
|
259
|
+
const docPath = `${collectionPath}/${doc.id}`;
|
|
260
|
+
serverIds.add(docPath);
|
|
261
|
+
let data = doc.fields;
|
|
262
|
+
const jobs = pendingMutations.get(docPath);
|
|
263
|
+
if (jobs) {
|
|
264
|
+
for (const job of jobs) {
|
|
265
|
+
if (job.type === 'delete')
|
|
266
|
+
data = null;
|
|
267
|
+
else if (job.type === 'set' && !job.merge)
|
|
268
|
+
data = job.data;
|
|
269
|
+
else if (data !== null) {
|
|
270
|
+
data = { ...data, ...job.data };
|
|
271
|
+
}
|
|
272
|
+
else if (job.type === 'set') {
|
|
273
|
+
data = job.data;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
finalUpdates.set(docPath, data);
|
|
278
|
+
}
|
|
279
|
+
const deletedPaths = [];
|
|
280
|
+
if (isComplete) {
|
|
281
|
+
for (const cachePath of Object.keys(this.localStore.getMemoryCache())) {
|
|
282
|
+
if (cachePath.startsWith(collectionPath + '/') && cachePath.split('/').length === collectionPath.split('/').length + 1) {
|
|
283
|
+
if (!serverIds.has(cachePath)) {
|
|
284
|
+
const jobs = pendingMutations.get(cachePath);
|
|
285
|
+
const hasActivePending = jobs && jobs.length > 0 && jobs[jobs.length - 1].type !== 'delete';
|
|
286
|
+
if (!hasActivePending) {
|
|
287
|
+
deletedPaths.push(cachePath);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const path of Array.from(finalUpdates.keys())) {
|
|
294
|
+
const data = finalUpdates.get(path);
|
|
295
|
+
if (data === null)
|
|
296
|
+
delete this.localStore.getMemoryCache()[path];
|
|
297
|
+
else
|
|
298
|
+
this.localStore.getMemoryCache()[path] = data;
|
|
299
|
+
}
|
|
300
|
+
for (const path of deletedPaths) {
|
|
301
|
+
delete this.localStore.getMemoryCache()[path];
|
|
302
|
+
}
|
|
303
|
+
if (!db)
|
|
304
|
+
return;
|
|
305
|
+
return new Promise((resolve, reject) => {
|
|
306
|
+
const tx = db.transaction(['offline_cache'], 'readwrite');
|
|
307
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
308
|
+
for (const path of Array.from(finalUpdates.keys())) {
|
|
309
|
+
const data = finalUpdates.get(path);
|
|
310
|
+
if (data !== null) {
|
|
311
|
+
cacheStore.put({ path, data, timestamp: Date.now() });
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
cacheStore.delete(path);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
for (const path of deletedPaths) {
|
|
318
|
+
cacheStore.delete(path);
|
|
319
|
+
}
|
|
320
|
+
tx.oncomplete = () => resolve();
|
|
321
|
+
tx.onerror = () => reject(tx.error);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
99
324
|
async _syncOfflineQueue() {
|
|
100
325
|
await this.syncEngine.syncOfflineMutations();
|
|
101
326
|
}
|
|
@@ -103,7 +328,7 @@ class NexaApp {
|
|
|
103
328
|
if (this._firestoreListening)
|
|
104
329
|
return;
|
|
105
330
|
this._firestoreListening = true;
|
|
106
|
-
const unsub1 = this.
|
|
331
|
+
const unsub1 = this.wsClient.addListener('data_changed', (payload) => {
|
|
107
332
|
if (payload && payload.path) {
|
|
108
333
|
if (payload.action === 'delete') {
|
|
109
334
|
this._deleteCache(payload.path);
|
|
@@ -114,11 +339,14 @@ class NexaApp {
|
|
|
114
339
|
this._notifySnapshotCallbacks(payload.path, payload.action === 'delete' ? 'document_deleted' : 'document_written', payload.data);
|
|
115
340
|
}
|
|
116
341
|
});
|
|
117
|
-
const unsub2 = this.
|
|
342
|
+
const unsub2 = this.wsClient.addListener('firestore_changed', (payload) => {
|
|
118
343
|
this._notifySnapshotCallbacks(payload.collectionPath || payload.docPath, 'server_update', payload.data);
|
|
119
344
|
});
|
|
120
345
|
this._firestoreUnsubscribes.push(unsub1, unsub2);
|
|
121
346
|
}
|
|
347
|
+
_ensureFirestoreWebSocket() {
|
|
348
|
+
this._ensureFirestoreSSE();
|
|
349
|
+
}
|
|
122
350
|
_closeFirestoreSSEIfIdle() {
|
|
123
351
|
if (Object.keys(this._snapshotCallbacks).length === 0) {
|
|
124
352
|
this._firestoreUnsubscribes.forEach((unsub) => unsub());
|
|
@@ -126,6 +354,9 @@ class NexaApp {
|
|
|
126
354
|
this._firestoreListening = false;
|
|
127
355
|
}
|
|
128
356
|
}
|
|
357
|
+
_closeFirestoreWebSocketIfIdle() {
|
|
358
|
+
this._closeFirestoreSSEIfIdle();
|
|
359
|
+
}
|
|
129
360
|
_notifySnapshotCallbacks(path, actionType, payload) {
|
|
130
361
|
Object.keys(this._snapshotCallbacks).forEach((cbKey) => {
|
|
131
362
|
const parts = cbKey.split('_');
|
|
@@ -180,5 +411,37 @@ class NexaApp {
|
|
|
180
411
|
async getDownloadURL(path) {
|
|
181
412
|
return this.storage.getDownloadURL(path);
|
|
182
413
|
}
|
|
414
|
+
isConnected() {
|
|
415
|
+
return this.wsClient.isConnected;
|
|
416
|
+
}
|
|
417
|
+
onConnectionStateChanged(callback) {
|
|
418
|
+
let lastState = this.isConnected();
|
|
419
|
+
callback(lastState);
|
|
420
|
+
// Make sure socket is created and connected if it isn't already
|
|
421
|
+
this.wsClient.ensureConnected();
|
|
422
|
+
const unsubOpen = this.wsClient.addListener('open', () => {
|
|
423
|
+
if (!lastState) {
|
|
424
|
+
lastState = true;
|
|
425
|
+
callback(true);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
const unsubClose = this.wsClient.addListener('close', () => {
|
|
429
|
+
if (lastState) {
|
|
430
|
+
lastState = false;
|
|
431
|
+
callback(false);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
const unsubError = this.wsClient.addListener('error', () => {
|
|
435
|
+
if (lastState) {
|
|
436
|
+
lastState = false;
|
|
437
|
+
callback(false);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
return () => {
|
|
441
|
+
unsubOpen();
|
|
442
|
+
unsubClose();
|
|
443
|
+
unsubError();
|
|
444
|
+
};
|
|
445
|
+
}
|
|
183
446
|
}
|
|
184
447
|
exports.NexaApp = NexaApp;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FieldPath } from './FieldPath';
|
|
2
2
|
import { CollectionReference, Query, QueryConstraint, QuerySnapshot, AggregateField, AggregateSpec, AggregateSpecData, AggregateQuerySnapshot } from '../types/index';
|
|
3
3
|
export declare const query: <T = any>(queryObject: Query<T> | CollectionReference<T>, ...queryConstraints: QueryConstraint[]) => Query<T>;
|
|
4
|
-
export declare const getCachedDocs: <T = any>(queryOrCollection: Query<T> | CollectionReference<T
|
|
4
|
+
export declare const getCachedDocs: <T = any>(queryOrCollection: Query<T> | CollectionReference<T>, fromCache?: boolean) => QuerySnapshot<T>;
|
|
5
5
|
export declare const getDocs: <T = any>(queryOrCollection: Query<T> | CollectionReference<T>) => Promise<QuerySnapshot<T>>;
|
|
6
6
|
export declare const count: () => AggregateField<number>;
|
|
7
7
|
export declare const sum: (field: string | FieldPath) => AggregateField<number>;
|