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.
@@ -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;
@@ -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
- await this.localStore.set(path, data);
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>) => QuerySnapshot<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>;