nexabase-console 2.0.7 → 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.
@@ -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)
@@ -98,6 +101,226 @@ class NexaApp {
98
101
  async _addOfflineJob(job) {
99
102
  await this.mutationQueue.addJob(job);
100
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
+ }
101
324
  async _syncOfflineQueue() {
102
325
  await this.syncEngine.syncOfflineMutations();
103
326
  }
@@ -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>;