nexabase-console 2.0.7 → 2.0.9
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 +41 -2
- package/dist/app/NexaApp.js +229 -5
- package/dist/firestore/Query.d.ts +1 -1
- package/dist/firestore/Query.js +176 -125
- package/dist/firestore/QueryUtils.d.ts +5 -0
- package/dist/firestore/QueryUtils.js +111 -0
- package/dist/firestore/writes.d.ts +17 -4
- package/dist/firestore/writes.js +176 -122
- package/dist/persistence/LocalStore.d.ts +4 -0
- package/dist/persistence/LocalStore.js +16 -3
- package/dist/persistence/MutationQueue.d.ts +3 -2
- package/dist/persistence/MutationQueue.js +32 -7
- package/dist/types/index.d.ts +8 -1
- package/package.json +1 -1
package/dist/app/NexaApp.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ 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;
|
|
21
22
|
wsClient: WebSocketClient;
|
|
@@ -36,12 +37,50 @@ export declare class NexaApp {
|
|
|
36
37
|
private _setupCrossTabSync;
|
|
37
38
|
_broadcastLocalMutation(path: string, action: string, data?: any): void;
|
|
38
39
|
hasPendingWrites(path: string): boolean;
|
|
39
|
-
markInflight(path: string): void;
|
|
40
|
-
unmarkInflight(path: string): void;
|
|
40
|
+
markInflight(path: string, mutationId: string): void;
|
|
41
|
+
unmarkInflight(path: string, mutationId: string): void;
|
|
41
42
|
_initFirestoreState(): Promise<void>;
|
|
42
43
|
_setCache(path: string, data: any): Promise<void>;
|
|
43
44
|
_deleteCache(path: string): Promise<void>;
|
|
44
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>;
|
|
45
84
|
_syncOfflineQueue(): Promise<void>;
|
|
46
85
|
_ensureFirestoreSSE(): void;
|
|
47
86
|
_ensureFirestoreWebSocket(): void;
|
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;
|
|
@@ -66,11 +69,11 @@ class NexaApp {
|
|
|
66
69
|
hasPendingWrites(path) {
|
|
67
70
|
return this.mutationQueue.hasPendingWrites(path);
|
|
68
71
|
}
|
|
69
|
-
markInflight(path) {
|
|
70
|
-
this.mutationQueue.markInflight(path);
|
|
72
|
+
markInflight(path, mutationId) {
|
|
73
|
+
this.mutationQueue.markInflight(path, mutationId);
|
|
71
74
|
}
|
|
72
|
-
unmarkInflight(path) {
|
|
73
|
-
this.mutationQueue.unmarkInflight(path);
|
|
75
|
+
unmarkInflight(path, mutationId) {
|
|
76
|
+
this.mutationQueue.unmarkInflight(path, mutationId);
|
|
74
77
|
}
|
|
75
78
|
async _initFirestoreState() {
|
|
76
79
|
if (!this.enablePersistence)
|
|
@@ -90,7 +93,8 @@ class NexaApp {
|
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
async _setCache(path, data) {
|
|
93
|
-
|
|
96
|
+
const revived = (0, QueryUtils_1.reviveNexaData)(data);
|
|
97
|
+
await this.localStore.set(path, revived);
|
|
94
98
|
}
|
|
95
99
|
async _deleteCache(path) {
|
|
96
100
|
await this.localStore.delete(path);
|
|
@@ -98,6 +102,226 @@ class NexaApp {
|
|
|
98
102
|
async _addOfflineJob(job) {
|
|
99
103
|
await this.mutationQueue.addJob(job);
|
|
100
104
|
}
|
|
105
|
+
_calculateLocalView(path) {
|
|
106
|
+
let base = this.localStore.getServerBaseCache()[path];
|
|
107
|
+
base = base !== undefined && base !== null ? (0, QueryUtils_1.cloneNexaData)(base) : base;
|
|
108
|
+
const mQueue = this.mutationQueue.inMemoryQueue || [];
|
|
109
|
+
let hasPendingWrites = false;
|
|
110
|
+
for (const job of mQueue) {
|
|
111
|
+
if (job.path !== path)
|
|
112
|
+
continue;
|
|
113
|
+
hasPendingWrites = true;
|
|
114
|
+
if (job.type === 'delete') {
|
|
115
|
+
base = null;
|
|
116
|
+
}
|
|
117
|
+
else if (job.type === 'set' && !job.merge) {
|
|
118
|
+
base = (0, helpers_1.applyDataTransforms)({}, job.data);
|
|
119
|
+
}
|
|
120
|
+
else if (base !== null && base !== undefined) {
|
|
121
|
+
base = (0, helpers_1.applyDataTransforms)(base, job.data);
|
|
122
|
+
}
|
|
123
|
+
else if (job.type === 'set' || job.type === 'patch' || job.type === 'update') {
|
|
124
|
+
base = (0, helpers_1.applyDataTransforms)(base || {}, job.data);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { data: base === undefined ? null : base, hasPendingWrites };
|
|
128
|
+
}
|
|
129
|
+
async _commitAtomicMutation(path, action, job) {
|
|
130
|
+
const db = this.indexedDB.getRawDB();
|
|
131
|
+
this.mutationQueue.inMemoryQueue.push(job);
|
|
132
|
+
const view = this._calculateLocalView(path);
|
|
133
|
+
const rebasedData = view.data;
|
|
134
|
+
if (rebasedData === null) {
|
|
135
|
+
delete this.localStore.getMemoryCache()[path];
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.localStore.getMemoryCache()[path] = rebasedData;
|
|
139
|
+
}
|
|
140
|
+
if (!db)
|
|
141
|
+
return view;
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
const tx = db.transaction(['offline_cache', 'offline_queue'], 'readwrite');
|
|
144
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
145
|
+
const queueStore = tx.objectStore('offline_queue');
|
|
146
|
+
if (rebasedData === null) {
|
|
147
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
148
|
+
if (sb !== undefined) {
|
|
149
|
+
cacheStore.put({ path, data: null, serverBase: sb, timestamp: Date.now() });
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
cacheStore.delete(path);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
157
|
+
cacheStore.put({ path, data: rebasedData, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
158
|
+
}
|
|
159
|
+
queueStore.put(job);
|
|
160
|
+
tx.oncomplete = () => resolve(view);
|
|
161
|
+
tx.onerror = () => reject(tx.error);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async _commitMutationAck(options) {
|
|
165
|
+
const { mutationId, path, serverDocument } = options;
|
|
166
|
+
const db = this.indexedDB.getRawDB();
|
|
167
|
+
const queue = this.mutationQueue.inMemoryQueue;
|
|
168
|
+
const jobIndex = queue.findIndex((j) => j.id === mutationId);
|
|
169
|
+
if (jobIndex !== -1)
|
|
170
|
+
queue.splice(jobIndex, 1);
|
|
171
|
+
if (options.action === 'delete') {
|
|
172
|
+
this.localStore.getServerBaseCache()[path] = null;
|
|
173
|
+
}
|
|
174
|
+
else if (serverDocument && serverDocument.fields) {
|
|
175
|
+
this.localStore.getServerBaseCache()[path] = (0, QueryUtils_1.reviveNexaData)(serverDocument.fields);
|
|
176
|
+
}
|
|
177
|
+
const view = this._calculateLocalView(path);
|
|
178
|
+
const rebasedData = view.data;
|
|
179
|
+
if (rebasedData === null) {
|
|
180
|
+
delete this.localStore.getMemoryCache()[path];
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.localStore.getMemoryCache()[path] = rebasedData;
|
|
184
|
+
}
|
|
185
|
+
if (!db)
|
|
186
|
+
return view;
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
const tx = db.transaction(['offline_cache', 'offline_queue'], 'readwrite');
|
|
189
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
190
|
+
const queueStore = tx.objectStore('offline_queue');
|
|
191
|
+
queueStore.delete(mutationId);
|
|
192
|
+
if (rebasedData === null) {
|
|
193
|
+
if (options.action === 'delete') {
|
|
194
|
+
cacheStore.put({ path, data: null, serverBase: null, timestamp: Date.now() });
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
198
|
+
cacheStore.put({ path, data: null, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
203
|
+
cacheStore.put({ path, data: rebasedData, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
204
|
+
}
|
|
205
|
+
tx.oncomplete = () => resolve(view);
|
|
206
|
+
tx.onerror = () => reject(tx.error);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async _rejectMutationAtomically(options) {
|
|
210
|
+
const { mutationId, path } = options;
|
|
211
|
+
const db = this.indexedDB.getRawDB();
|
|
212
|
+
const queue = this.mutationQueue.inMemoryQueue;
|
|
213
|
+
const jobIndex = queue.findIndex((j) => j.id === mutationId);
|
|
214
|
+
if (jobIndex !== -1)
|
|
215
|
+
queue.splice(jobIndex, 1);
|
|
216
|
+
const view = this._calculateLocalView(path);
|
|
217
|
+
const rebasedData = view.data;
|
|
218
|
+
if (rebasedData === null) {
|
|
219
|
+
delete this.localStore.getMemoryCache()[path];
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.localStore.getMemoryCache()[path] = rebasedData;
|
|
223
|
+
}
|
|
224
|
+
if (!db)
|
|
225
|
+
return view;
|
|
226
|
+
return new Promise((resolve, reject) => {
|
|
227
|
+
const tx = db.transaction(['offline_cache', 'offline_queue'], 'readwrite');
|
|
228
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
229
|
+
const queueStore = tx.objectStore('offline_queue');
|
|
230
|
+
queueStore.delete(mutationId);
|
|
231
|
+
if (rebasedData === null) {
|
|
232
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
233
|
+
if (sb === undefined)
|
|
234
|
+
cacheStore.delete(path);
|
|
235
|
+
else
|
|
236
|
+
cacheStore.put({ path, data: null, serverBase: sb, timestamp: Date.now() });
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const sb = this.localStore.getServerBaseCache()[path];
|
|
240
|
+
cacheStore.put({ path, data: rebasedData, serverBase: sb !== undefined ? sb : null, timestamp: Date.now() });
|
|
241
|
+
}
|
|
242
|
+
tx.oncomplete = () => resolve(view);
|
|
243
|
+
tx.onerror = () => reject(tx.error);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async _commitAtomicQueryResults(options) {
|
|
247
|
+
const { collectionPath, serverDocs, isComplete } = options;
|
|
248
|
+
const db = this.indexedDB.getRawDB();
|
|
249
|
+
const pendingMutations = new Map();
|
|
250
|
+
// Get jobs array directly from mutation queue memory
|
|
251
|
+
const mQueue = this.mutationQueue.inMemoryQueue || [];
|
|
252
|
+
for (const job of mQueue) {
|
|
253
|
+
if (!pendingMutations.has(job.path))
|
|
254
|
+
pendingMutations.set(job.path, []);
|
|
255
|
+
pendingMutations.get(job.path).push(job);
|
|
256
|
+
}
|
|
257
|
+
const finalUpdates = new Map();
|
|
258
|
+
const serverIds = new Set();
|
|
259
|
+
for (const doc of serverDocs) {
|
|
260
|
+
const docPath = `${collectionPath}/${doc.id}`;
|
|
261
|
+
serverIds.add(docPath);
|
|
262
|
+
let data = (0, QueryUtils_1.reviveNexaData)(doc.fields);
|
|
263
|
+
const jobs = pendingMutations.get(docPath);
|
|
264
|
+
if (jobs) {
|
|
265
|
+
for (const job of jobs) {
|
|
266
|
+
if (job.type === 'delete')
|
|
267
|
+
data = null;
|
|
268
|
+
else if (job.type === 'set' && !job.merge)
|
|
269
|
+
data = job.data;
|
|
270
|
+
else if (data !== null) {
|
|
271
|
+
data = { ...data, ...job.data };
|
|
272
|
+
}
|
|
273
|
+
else if (job.type === 'set') {
|
|
274
|
+
data = job.data;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
finalUpdates.set(docPath, data);
|
|
279
|
+
}
|
|
280
|
+
const deletedPaths = [];
|
|
281
|
+
if (isComplete) {
|
|
282
|
+
for (const cachePath of Object.keys(this.localStore.getMemoryCache())) {
|
|
283
|
+
if (cachePath.startsWith(collectionPath + '/') && cachePath.split('/').length === collectionPath.split('/').length + 1) {
|
|
284
|
+
if (!serverIds.has(cachePath)) {
|
|
285
|
+
const jobs = pendingMutations.get(cachePath);
|
|
286
|
+
const hasActivePending = jobs && jobs.length > 0 && jobs[jobs.length - 1].type !== 'delete';
|
|
287
|
+
if (!hasActivePending) {
|
|
288
|
+
deletedPaths.push(cachePath);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (const path of Array.from(finalUpdates.keys())) {
|
|
295
|
+
const data = finalUpdates.get(path);
|
|
296
|
+
if (data === null)
|
|
297
|
+
delete this.localStore.getMemoryCache()[path];
|
|
298
|
+
else
|
|
299
|
+
this.localStore.getMemoryCache()[path] = data;
|
|
300
|
+
}
|
|
301
|
+
for (const path of deletedPaths) {
|
|
302
|
+
delete this.localStore.getMemoryCache()[path];
|
|
303
|
+
}
|
|
304
|
+
if (!db)
|
|
305
|
+
return;
|
|
306
|
+
return new Promise((resolve, reject) => {
|
|
307
|
+
const tx = db.transaction(['offline_cache'], 'readwrite');
|
|
308
|
+
const cacheStore = tx.objectStore('offline_cache');
|
|
309
|
+
for (const path of Array.from(finalUpdates.keys())) {
|
|
310
|
+
const data = finalUpdates.get(path);
|
|
311
|
+
if (data !== null) {
|
|
312
|
+
cacheStore.put({ path, data, timestamp: Date.now() });
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
cacheStore.delete(path);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (const path of deletedPaths) {
|
|
319
|
+
cacheStore.delete(path);
|
|
320
|
+
}
|
|
321
|
+
tx.oncomplete = () => resolve();
|
|
322
|
+
tx.onerror = () => reject(tx.error);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
101
325
|
async _syncOfflineQueue() {
|
|
102
326
|
await this.syncEngine.syncOfflineMutations();
|
|
103
327
|
}
|
|
@@ -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>;
|