nexabase-console 1.1.5 → 2.0.0

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.
Files changed (72) hide show
  1. package/README.md +25 -8
  2. package/dist/app/NexaApp.d.ts +63 -0
  3. package/dist/app/NexaApp.js +184 -0
  4. package/dist/app/initializeApp.d.ts +8 -0
  5. package/dist/app/initializeApp.js +27 -0
  6. package/dist/auth/Auth.d.ts +21 -0
  7. package/dist/auth/Auth.js +73 -0
  8. package/dist/auth/authTypes.d.ts +1 -0
  9. package/dist/auth/authTypes.js +2 -0
  10. package/dist/auth/persistence.d.ts +9 -0
  11. package/dist/auth/persistence.js +34 -0
  12. package/dist/errors/NexaError.d.ts +7 -0
  13. package/dist/errors/NexaError.js +61 -0
  14. package/dist/firestore/CollectionReference.d.ts +3 -0
  15. package/dist/firestore/CollectionReference.js +7 -0
  16. package/dist/firestore/DocumentReference.d.ts +3 -0
  17. package/dist/firestore/DocumentReference.js +22 -0
  18. package/dist/firestore/FieldPath.d.ts +7 -0
  19. package/dist/firestore/FieldPath.js +23 -0
  20. package/dist/firestore/FieldValue.d.ts +11 -0
  21. package/dist/firestore/FieldValue.js +21 -0
  22. package/dist/firestore/Firestore.d.ts +8 -0
  23. package/dist/firestore/Firestore.js +24 -0
  24. package/dist/firestore/GeoPoint.d.ts +10 -0
  25. package/dist/firestore/GeoPoint.js +22 -0
  26. package/dist/firestore/Query.d.ts +4 -0
  27. package/dist/firestore/Query.js +172 -0
  28. package/dist/firestore/Snapshot.d.ts +6 -0
  29. package/dist/firestore/Snapshot.js +126 -0
  30. package/dist/firestore/SnapshotManager.d.ts +9 -0
  31. package/dist/firestore/SnapshotManager.js +37 -0
  32. package/dist/firestore/Timestamp.d.ts +16 -0
  33. package/dist/firestore/Timestamp.js +36 -0
  34. package/dist/firestore/batch.d.ts +3 -0
  35. package/dist/firestore/batch.js +79 -0
  36. package/dist/firestore/queryConstraints.d.ts +9 -0
  37. package/dist/firestore/queryConstraints.js +31 -0
  38. package/dist/firestore/transaction.d.ts +5 -0
  39. package/dist/firestore/transaction.js +71 -0
  40. package/dist/firestore/writes.d.ts +15 -0
  41. package/dist/firestore/writes.js +146 -0
  42. package/dist/index.d.ts +34 -206
  43. package/dist/index.js +56 -1090
  44. package/dist/persistence/IndexedDB.d.ts +8 -0
  45. package/dist/persistence/IndexedDB.js +37 -0
  46. package/dist/persistence/LocalStore.d.ts +12 -0
  47. package/dist/persistence/LocalStore.js +78 -0
  48. package/dist/persistence/MutationQueue.d.ts +22 -0
  49. package/dist/persistence/MutationQueue.js +145 -0
  50. package/dist/realtime/RealtimeConnection.d.ts +7 -0
  51. package/dist/realtime/RealtimeConnection.js +19 -0
  52. package/dist/realtime/RealtimeDatabase.d.ts +10 -0
  53. package/dist/realtime/RealtimeDatabase.js +62 -0
  54. package/dist/storage/Storage.d.ts +12 -0
  55. package/dist/storage/Storage.js +41 -0
  56. package/dist/storage/StorageReference.d.ts +13 -0
  57. package/dist/storage/StorageReference.js +20 -0
  58. package/dist/storage/UploadTask.d.ts +10 -0
  59. package/dist/storage/UploadTask.js +22 -0
  60. package/dist/sync/ConflictResolver.d.ts +3 -0
  61. package/dist/sync/ConflictResolver.js +24 -0
  62. package/dist/sync/SyncEngine.d.ts +13 -0
  63. package/dist/sync/SyncEngine.js +47 -0
  64. package/dist/transport/HttpClient.d.ts +8 -0
  65. package/dist/transport/HttpClient.js +42 -0
  66. package/dist/transport/SSEClient.d.ts +15 -0
  67. package/dist/transport/SSEClient.js +96 -0
  68. package/dist/types/index.d.ts +150 -0
  69. package/dist/types/index.js +2 -0
  70. package/dist/utils/helpers.d.ts +7 -0
  71. package/dist/utils/helpers.js +124 -0
  72. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,1093 +1,59 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
4
15
  };
5
16
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.runTransaction = exports.writeBatch = exports.enableOfflinePersistence = exports.enableIndexedDbPersistence = exports.onSnapshot = exports.deleteDoc = exports.updateDoc = exports.patchDoc = exports.setDoc = exports.getDoc = exports.getDocs = exports.getCachedDoc = exports.getCachedDocs = exports.limit = exports.orderBy = exports.where = exports.query = exports.doc = exports.collection = exports.getFirestore = exports.initializeApp = exports.NexaBase = exports.NexaApp = void 0;
7
- exports.generateIdempotencyKey = generateIdempotencyKey;
8
- const axios_1 = __importDefault(require("axios"));
9
- // -----------------------------------------------------------------
10
- // Idempotency Utility
11
- // -----------------------------------------------------------------
12
- function generateIdempotencyKey() {
13
- if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
14
- return crypto.randomUUID();
15
- }
16
- return `nexa_idemp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}_${Math.floor(Math.random() * 1000000)}`;
17
- }
18
- // -----------------------------------------------------------------
19
- // Main NexaApp SDK Class
20
- // -----------------------------------------------------------------
21
- class NexaApp {
22
- constructor(config) {
23
- this.token = null;
24
- this.sse = null;
25
- // Internal Firestore and Realtime caches/states
26
- this._firestoreCache = {};
27
- this._snapshotCallbacks = {};
28
- this._sseCallbacks = { data_changed: [], firestore_changed: [] };
29
- this._offlineQueueInitialized = false;
30
- this._isSyncing = false;
31
- this._idb = null;
32
- this._firestoreListening = false;
33
- this._initPromise = null;
34
- // Real-time Engine Utilities (Cross-Tab & Cross-Device)
35
- this._broadcastChannel = null;
36
- this._reconnectAttempts = 0;
37
- this._reconnectTimer = null;
38
- this.projectId = config.projectId;
39
- this.token = config.apiKey || null;
40
- this.enablePersistence = config.enablePersistence !== false;
41
- this.cacheStrategy = config.cacheStrategy || 'network-first';
42
- if (typeof window !== 'undefined' && window.localStorage) {
43
- const savedToken = localStorage.getItem(`nexa_token_${this.projectId}`);
44
- if (savedToken) {
45
- this.token = savedToken;
46
- }
47
- }
48
- // Auto-detect endpoint
49
- let defaultEndpoint = 'https://db.nexabase.id';
50
- if (typeof window !== 'undefined' && window.location) {
51
- defaultEndpoint = window.location.origin;
52
- }
53
- this.endpoint = config.endpoint || defaultEndpoint;
54
- // Do not set default Content-Type globally to let Axios auto-detect FormData
55
- // Include 6000ms timeout for reliable fast failover fallback to local IndexedDB cache when network is slow
56
- this.client = axios_1.default.create({
57
- baseURL: this.endpoint,
58
- timeout: 6000
59
- });
60
- // Request interceptor to attach bearer/x-api-key token and boundary fix
61
- this.client.interceptors.request.use((req) => {
62
- if (this.token) {
63
- // Send both Authorization Bearer and x-api-key for full server compatibility
64
- req.headers.Authorization = `Bearer ${this.token}`;
65
- req.headers['x-api-key'] = this.token;
66
- }
67
- // If data is FormData, remove Content-Type so Axios/Browser can format multipart boundary dynamically
68
- if (typeof FormData !== 'undefined' && req.data instanceof FormData) {
69
- if (req.headers) {
70
- delete req.headers['Content-Type'];
71
- delete req.headers['content-type'];
72
- }
73
- }
74
- else {
75
- // Otherwise default to application/json for non-FormData payload requests
76
- if (req.data && req.headers && !req.headers['Content-Type'] && !req.headers['content-type']) {
77
- req.headers['Content-Type'] = 'application/json';
78
- }
79
- }
80
- return req;
81
- });
82
- // Initialize Cross-Tab Sync via BroadcastChannel
83
- this._initCrossTabSync();
84
- }
85
- // Cross-Tab Engine via BroadcastChannel API
86
- _initCrossTabSync() {
87
- if (typeof window !== 'undefined' && 'BroadcastChannel' in window && !this._broadcastChannel) {
88
- try {
89
- this._broadcastChannel = new BroadcastChannel(`nexabase_sync_${this.projectId}`);
90
- this._broadcastChannel.onmessage = (event) => {
91
- const { type, docPath, data, action } = event.data || {};
92
- if (type === 'cross_tab_mutation' && docPath) {
93
- if (action === 'set' || action === 'update' || action === 'patch') {
94
- if (action === 'patch' || action === 'update') {
95
- const existing = this._firestoreCache[docPath] || {};
96
- this._firestoreCache[docPath] = { ...existing, ...data };
97
- }
98
- else {
99
- this._firestoreCache[docPath] = data;
100
- }
101
- if (this.enablePersistence && this._idb) {
102
- this._setCache(docPath, this._firestoreCache[docPath]);
103
- }
104
- this._notifySnapshotCallbacks(docPath, 'document_written', this._firestoreCache[docPath]);
105
- }
106
- else if (action === 'delete') {
107
- delete this._firestoreCache[docPath];
108
- if (this.enablePersistence && this._idb) {
109
- this._deleteCache(docPath);
110
- }
111
- this._notifySnapshotCallbacks(docPath, 'document_deleted', null);
112
- }
113
- else if (action === 'bulk_update') {
114
- this._notifySnapshotCallbacks('*', 'bulk_update', null);
115
- }
116
- }
117
- };
118
- }
119
- catch (e) {
120
- // BroadcastChannel fallback silently if disabled by security context
121
- }
122
- }
123
- }
124
- _broadcastLocalMutation(docPath, action, data) {
125
- if (this._broadcastChannel) {
126
- try {
127
- this._broadcastChannel.postMessage({
128
- type: 'cross_tab_mutation',
129
- docPath,
130
- action,
131
- data,
132
- timestamp: Date.now()
133
- });
134
- }
135
- catch (e) { }
136
- }
137
- }
138
- // Cross-Device Realtime Connection via SSE with Auto-Reconnect & Heartbeat
139
- connectRealtime() {
140
- if (this.sse)
141
- return this.sse;
142
- const url = `${this.endpoint}/api/firestore/${this.projectId}/listen`;
143
- const listenUrl = this.token ? `${url}?token=${this.token}` : url;
144
- try {
145
- this.sse = new EventSource(listenUrl);
146
- this.sse.onmessage = async (event) => {
147
- try {
148
- const payload = JSON.parse(event.data);
149
- if (payload.type === 'connected') {
150
- this._reconnectAttempts = 0; // Reset backoff on stable connection
151
- return;
152
- }
153
- if (payload.type && (payload.type.includes('document_') || payload.type === 'bulk_update')) {
154
- this._sseCallbacks.firestore_changed.forEach((cb) => cb(payload));
155
- }
156
- else {
157
- this._sseCallbacks.data_changed.forEach((cb) => cb(payload));
158
- }
159
- }
160
- catch (e) {
161
- // Ignore JSON parse errors on ping
162
- }
163
- };
164
- this.sse.onerror = () => {
165
- if (this.sse) {
166
- this.sse.close();
167
- this.sse = null;
168
- }
169
- if (this._reconnectTimer)
170
- clearTimeout(this._reconnectTimer);
171
- // Exponential backoff reconnect: 1s, 2s, 4s, ... max 30s
172
- this._reconnectAttempts++;
173
- const backoffMs = Math.min(1000 * Math.pow(2, this._reconnectAttempts), 30000);
174
- this._reconnectTimer = setTimeout(() => {
175
- this.connectRealtime();
176
- }, backoffMs);
177
- };
178
- }
179
- catch (e) {
180
- // Return dummy EventSource if SSE fails in environment
181
- }
182
- return this.sse;
183
- }
184
- _notifySnapshotCallbacks(docPath, eventType, data) {
185
- Object.keys(this._snapshotCallbacks).forEach((key) => {
186
- const cb = this._snapshotCallbacks[key];
187
- if (docPath === '*') {
188
- cb({ type: eventType });
189
- return;
190
- }
191
- const parts = key.split('_');
192
- const pathKey = parts.slice(2).join('_');
193
- if (pathKey === docPath || docPath.startsWith(pathKey + '/')) {
194
- cb({ type: eventType, docPath, data });
195
- }
196
- });
197
- }
198
- // -----------------------------------------------------------------
199
- // Realtime Key-Value Database API
200
- // -----------------------------------------------------------------
201
- database() {
202
- return {
203
- ref: (path = '') => {
204
- const cleanPath = (path || '').replace(/^\/+|\/+$/g, '');
205
- return {
206
- path: cleanPath,
207
- get: async () => {
208
- const response = await this.client.get(`/api/db/${this.projectId}/${cleanPath}`);
209
- return response.data;
210
- },
211
- set: async (data) => {
212
- const payload = data instanceof Map ? Object.fromEntries(data) : data;
213
- const response = await this.client.put(`/api/db/${this.projectId}/${cleanPath}`, payload);
214
- return response.data;
215
- },
216
- update: async (data) => {
217
- const payload = data instanceof Map ? Object.fromEntries(data) : data;
218
- const response = await this.client.patch(`/api/db/${this.projectId}/${cleanPath}`, payload);
219
- return response.data;
220
- },
221
- delete: async () => {
222
- const response = await this.client.put(`/api/db/${this.projectId}/${cleanPath}`, null);
223
- return response.data;
224
- },
225
- remove: async () => {
226
- const response = await this.client.put(`/api/db/${this.projectId}/${cleanPath}`, null);
227
- return response.data;
228
- },
229
- onDataChanged: (callback) => {
230
- this.connectRealtime();
231
- const handler = (payload) => {
232
- const payloadPath = (payload.path || '').replace(/^\/+|\/+$/g, '');
233
- if (payloadPath === cleanPath ||
234
- payloadPath.startsWith(cleanPath + '/') ||
235
- cleanPath === '' ||
236
- cleanPath.startsWith(payloadPath + '/')) {
237
- callback(payload.data);
238
- }
239
- };
240
- this._sseCallbacks.data_changed.push(handler);
241
- return () => {
242
- this._sseCallbacks.data_changed = this._sseCallbacks.data_changed.filter((cb) => cb !== handler);
243
- };
244
- }
245
- };
246
- }
247
- };
248
- }
249
- // -----------------------------------------------------------------
250
- // User Authentication API
251
- // -----------------------------------------------------------------
252
- auth() {
253
- return {
254
- signInWithEmailAndPassword: async (email, password) => {
255
- const res = await this.client.post(`/api/project/${this.projectId}/auth/login`, { email, password });
256
- if (res.data.token) {
257
- this.token = res.data.token;
258
- if (typeof window !== 'undefined' && window.localStorage) {
259
- localStorage.setItem(`nexa_token_${this.projectId}`, res.data.token);
260
- localStorage.setItem(`nexa_user_${this.projectId}`, JSON.stringify(res.data.user));
261
- }
262
- }
263
- return res.data;
264
- },
265
- createUserWithEmailAndPassword: async (email, password, name) => {
266
- const res = await this.client.post(`/api/project/${this.projectId}/users`, { email, password, name });
267
- return res.data;
268
- },
269
- sendOtp: async (email) => {
270
- const res = await this.client.post(`/api/project/${this.projectId}/auth/otp/send`, { email });
271
- return res.data;
272
- },
273
- signInWithOtp: async (email, otp, name) => {
274
- const res = await this.client.post(`/api/project/${this.projectId}/auth/otp/login`, { email, otp, name });
275
- if (res.data.token) {
276
- this.token = res.data.token;
277
- if (typeof window !== 'undefined' && window.localStorage) {
278
- localStorage.setItem(`nexa_token_${this.projectId}`, res.data.token);
279
- localStorage.setItem(`nexa_user_${this.projectId}`, JSON.stringify(res.data.user));
280
- }
281
- }
282
- return res.data;
283
- },
284
- signOut: () => {
285
- this.token = null;
286
- if (typeof window !== 'undefined' && window.localStorage) {
287
- localStorage.removeItem(`nexa_token_${this.projectId}`);
288
- localStorage.removeItem(`nexa_user_${this.projectId}`);
289
- }
290
- },
291
- getCurrentUser: () => {
292
- if (typeof window !== 'undefined' && window.localStorage) {
293
- const userStr = localStorage.getItem(`nexa_user_${this.projectId}`);
294
- return userStr ? JSON.parse(userStr) : null;
295
- }
296
- return null;
297
- }
298
- };
299
- }
300
- // -----------------------------------------------------------------
301
- // Blob File Storage API
302
- // -----------------------------------------------------------------
303
- storage() {
304
- return {
305
- ref: (path = '') => {
306
- const name = path.substring(path.lastIndexOf('/') + 1) || path;
307
- return {
308
- name,
309
- fullPath: path,
310
- put: async (file, options, onProgress) => {
311
- let actualOptions = {};
312
- let actualOnProgress = onProgress;
313
- if (typeof options === 'function') {
314
- actualOnProgress = options;
315
- }
316
- else if (options && typeof options === 'object') {
317
- actualOptions = options;
318
- }
319
- const formData = new FormData();
320
- formData.append('file', file);
321
- if (actualOptions.contextType) {
322
- formData.append('contextType', actualOptions.contextType);
323
- }
324
- if (actualOptions.contextId) {
325
- formData.append('contextId', actualOptions.contextId);
326
- }
327
- if (actualOptions.fileId) {
328
- formData.append('fileId', actualOptions.fileId);
329
- }
330
- const response = await this.client.post(`/api/project/${this.projectId}/storage/upload`, formData, {
331
- onUploadProgress: (progressEvent) => {
332
- if (actualOnProgress && progressEvent.total) {
333
- const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
334
- actualOnProgress(percent);
335
- }
336
- }
337
- });
338
- return {
339
- url: response.data.url,
340
- success: true,
341
- fileId: response.data.fileId,
342
- proxyViewUrl: response.data.proxyViewUrl,
343
- proxyDownloadUrl: response.data.proxyDownloadUrl,
344
- metadata: response.data.metadata
345
- };
346
- },
347
- delete: async () => {
348
- const response = await this.client.delete(`/api/project/${this.projectId}/storage/${encodeURIComponent(name)}`);
349
- return response.data;
350
- }
351
- };
352
- },
353
- refFromURL: (url) => {
354
- const cleanUrl = url.split('?')[0];
355
- const filename = decodeURIComponent(cleanUrl.substring(cleanUrl.lastIndexOf('/') + 1));
356
- return this.storage().ref(filename);
357
- }
358
- };
359
- }
360
- // -----------------------------------------------------------------
361
- // Offline Synchronization & Local IndexedDB Cache Engine
362
- // -----------------------------------------------------------------
363
- async _initFirestoreState() {
364
- if (!this.enablePersistence)
365
- return; // Respect enablePersistence ON / OFF option
366
- if (this._offlineQueueInitialized)
367
- return;
368
- this._offlineQueueInitialized = true;
369
- this._isSyncing = false;
370
- if (typeof window === 'undefined' || !window.indexedDB) {
371
- return;
372
- }
373
- const dbName = `nexa_idb_${this.projectId}`;
374
- try {
375
- this._idb = await new Promise((resolve, reject) => {
376
- const req = indexedDB.open(dbName, 1);
377
- req.onupgradeneeded = (e) => {
378
- const db = e.target.result;
379
- if (!db.objectStoreNames.contains('offline_queue')) {
380
- db.createObjectStore('offline_queue', { keyPath: 'id' });
381
- }
382
- if (!db.objectStoreNames.contains('offline_cache')) {
383
- db.createObjectStore('offline_cache', { keyPath: 'path' });
384
- }
385
- };
386
- req.onsuccess = () => resolve(req.result);
387
- req.onerror = () => reject(req.error);
388
- });
389
- await this._loadCache();
390
- if (typeof window !== 'undefined') {
391
- window.addEventListener('online', () => this._syncOfflineQueue());
392
- setTimeout(() => this._syncOfflineQueue(), 1000);
393
- // Robust automatic heartbeat polling every 10 seconds to sync any pending queue jobs
394
- setInterval(() => {
395
- if (typeof navigator !== 'undefined' && navigator.onLine) {
396
- this._syncOfflineQueue();
397
- }
398
- }, 10000);
399
- }
400
- }
401
- catch (err) {
402
- console.warn('[NexaBase SDK] IndexedDB persistence unavailable, falling back to memory cache.');
403
- }
404
- }
405
- async _getOfflineQueue() {
406
- if (!this._idb || !this.enablePersistence)
407
- return [];
408
- return new Promise((resolve, reject) => {
409
- const tx = this._idb.transaction('offline_queue', 'readonly');
410
- const store = tx.objectStore('offline_queue');
411
- const req = store.getAll();
412
- req.onsuccess = () => resolve(req.result);
413
- req.onerror = () => reject(req.error);
414
- });
415
- }
416
- async _saveOfflineQueue(queue) {
417
- if (!this._idb || !this.enablePersistence)
418
- return;
419
- return new Promise((resolve, reject) => {
420
- const tx = this._idb.transaction('offline_queue', 'readwrite');
421
- const store = tx.objectStore('offline_queue');
422
- store.clear();
423
- queue.forEach((item) => store.put(item));
424
- tx.oncomplete = () => resolve();
425
- tx.onerror = () => reject(tx.error);
426
- });
427
- }
428
- async _addOfflineJob(job) {
429
- if (!this._idb || !this.enablePersistence)
430
- return;
431
- return new Promise((resolve, reject) => {
432
- const tx = this._idb.transaction('offline_queue', 'readwrite');
433
- const store = tx.objectStore('offline_queue');
434
- store.put(job);
435
- tx.oncomplete = () => {
436
- resolve();
437
- if (typeof navigator !== 'undefined' && navigator.onLine) {
438
- this._syncOfflineQueue();
439
- }
440
- };
441
- tx.onerror = () => reject(tx.error);
442
- });
443
- }
444
- async _setCache(path, data) {
445
- this._firestoreCache[path] = data;
446
- if (!this._idb || !this.enablePersistence)
447
- return;
448
- return new Promise((resolve, reject) => {
449
- const tx = this._idb.transaction('offline_cache', 'readwrite');
450
- const store = tx.objectStore('offline_cache');
451
- store.put({ path, data });
452
- tx.oncomplete = () => resolve();
453
- tx.onerror = () => reject(tx.error);
454
- });
455
- }
456
- async _deleteCache(path) {
457
- delete this._firestoreCache[path];
458
- if (!this._idb || !this.enablePersistence)
459
- return;
460
- return new Promise((resolve, reject) => {
461
- const tx = this._idb.transaction('offline_cache', 'readwrite');
462
- const store = tx.objectStore('offline_cache');
463
- store.delete(path);
464
- tx.oncomplete = () => resolve();
465
- tx.onerror = () => reject(tx.error);
466
- });
467
- }
468
- async _loadCache() {
469
- if (!this._idb || !this.enablePersistence)
470
- return;
471
- return new Promise((resolve, reject) => {
472
- const tx = this._idb.transaction('offline_cache', 'readonly');
473
- const store = tx.objectStore('offline_cache');
474
- const req = store.getAll();
475
- req.onsuccess = () => {
476
- req.result.forEach((item) => {
477
- this._firestoreCache[item.path] = item.data;
478
- });
479
- resolve();
480
- };
481
- req.onerror = () => reject(req.error);
482
- });
483
- }
484
- // Outbox Pattern Offline Queue Flush with Idempotency Key
485
- async _syncOfflineQueue() {
486
- if (this._isSyncing || !this.enablePersistence)
487
- return;
488
- this._isSyncing = true;
489
- const queue = await this._getOfflineQueue();
490
- if (queue.length === 0) {
491
- this._isSyncing = false;
492
- return;
493
- }
494
- for (const job of [...queue]) {
495
- try {
496
- const headers = job.idempotencyKey ? { 'X-Idempotency-Key': job.idempotencyKey } : {};
497
- if (job.type === 'set') {
498
- await this.client.post(`/api/firestore/${this.projectId}/document`, { docPath: job.path, data: job.data, idempotencyKey: job.idempotencyKey }, { headers });
499
- }
500
- else if (job.type === 'update' || job.type === 'patch') {
501
- await this.client.patch(`/api/firestore/${this.projectId}/document`, { docPath: job.path, data: job.data, idempotencyKey: job.idempotencyKey }, { headers });
502
- }
503
- else if (job.type === 'delete') {
504
- await this.client.delete(`/api/firestore/${this.projectId}/document?docPath=${encodeURIComponent(job.path)}`, { headers });
505
- }
506
- const currentQueue = await this._getOfflineQueue();
507
- await this._saveOfflineQueue(currentQueue.filter((q) => q.id !== job.id));
508
- }
509
- catch (err) {
510
- // Discard invalid/unauthorized client-side failures (4xx) to prevent clogging the queue
511
- if (err && err.response && err.response.status >= 400 && err.response.status < 500) {
512
- console.error(`[NexaBase SDK] Discarding invalid offline job (${job.type} on ${job.path}):`, err.response.data);
513
- const currentQueue = await this._getOfflineQueue();
514
- await this._saveOfflineQueue(currentQueue.filter((q) => q.id !== job.id));
515
- continue; // Move on to next job
516
- }
517
- break; // Stop syncing on transient network exceptions or server 5xx errors to retry later
518
- }
519
- }
520
- this._isSyncing = false;
521
- this._notifySnapshotCallbacks('*', 'bulk_update', null);
522
- }
523
- _ensureFirestoreSSE() {
524
- this.connectRealtime();
525
- if (this._firestoreListening)
526
- return;
527
- this._firestoreListening = true;
528
- this._sseCallbacks.firestore_changed.push(async (change) => {
529
- if (change.type === 'bulk_update') {
530
- this._notifySnapshotCallbacks('*', 'bulk_update', null);
531
- return;
532
- }
533
- if (change.type === 'document_written') {
534
- await this._setCache(change.docPath, change.data);
535
- }
536
- else if (change.type === 'document_deleted') {
537
- await this._deleteCache(change.docPath);
538
- }
539
- this._notifySnapshotCallbacks(change.docPath, change.type, change.data);
540
- });
541
- }
542
- }
543
- exports.NexaApp = NexaApp;
544
- // -----------------------------------------------------------------
545
- // Static NexaBase Initialization Helper
546
- // -----------------------------------------------------------------
547
- exports.NexaBase = {
548
- init: (config) => {
549
- const app = new NexaApp(config);
550
- if (app.enablePersistence) {
551
- app._initPromise = app._initFirestoreState();
552
- }
553
- return app;
554
- },
555
- initializeApp: (config) => {
556
- return exports.NexaBase.init(config);
557
- }
558
- };
559
- // -----------------------------------------------------------------
560
- // Modular Firestore-like APIs (Firebase Modular pattern)
561
- // -----------------------------------------------------------------
562
- const initializeApp = (config) => {
563
- return exports.NexaBase.init(config);
564
- };
565
- exports.initializeApp = initializeApp;
566
- const getFirestore = (app) => {
567
- if (app.enablePersistence) {
568
- app._initPromise = app._initFirestoreState();
569
- }
570
- return app;
571
- };
572
- exports.getFirestore = getFirestore;
573
- const collection = (db, collectionPath) => {
574
- return { type: 'collection', path: collectionPath, db };
575
- };
576
- exports.collection = collection;
577
- const doc = (dbOrCollection, ...pathSegments) => {
578
- let docRef;
579
- if ('type' in dbOrCollection && dbOrCollection.type === 'collection') {
580
- docRef = { type: 'document', path: `${dbOrCollection.path}/${pathSegments.join('/')}`, db: dbOrCollection.db };
581
- }
582
- else {
583
- docRef = { type: 'document', path: pathSegments.join('/'), db: dbOrCollection };
584
- }
585
- docRef.patch = (data) => (0, exports.patchDoc)(docRef, data);
586
- return docRef;
587
- };
588
- exports.doc = doc;
589
- const query = (queryObject, ...queryConstraints) => {
590
- const q = {
591
- type: 'query',
592
- path: queryObject.path,
593
- db: queryObject.db,
594
- constraints: queryObject.type === 'query' ? [...(queryObject.constraints || [])] : []
595
- };
596
- q.constraints = [...(q.constraints || []), ...queryConstraints];
597
- return q;
598
- };
599
- exports.query = query;
600
- const where = (fieldPath, opStr, value) => {
601
- return { type: 'where', fieldPath, opStr, value };
602
- };
603
- exports.where = where;
604
- const orderBy = (fieldPath, directionStr = 'asc') => {
605
- return { type: 'orderBy', fieldPath, directionStr };
606
- };
607
- exports.orderBy = orderBy;
608
- const limit = (limitValue) => {
609
- return { type: 'limit', limitValue };
610
- };
611
- exports.limit = limit;
612
- // Helper to retrieve docs from local cache
613
- const getCachedDocs = (queryOrCollection) => {
614
- const db = queryOrCollection.db;
615
- const collectionPath = queryOrCollection.path;
616
- const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
617
- const results = [];
618
- Object.keys(db._firestoreCache).forEach((p) => {
619
- if (p.startsWith(collectionPath + '/')) {
620
- const parts = p.split('/');
621
- const docId = parts[parts.length - 1];
622
- const docRef = (0, exports.doc)(db, `${collectionPath}/${docId}`);
623
- results.push({
624
- id: docId,
625
- ref: docRef,
626
- exists: () => true,
627
- data: () => db._firestoreCache[p]
628
- });
629
- }
630
- });
631
- let filtered = results;
632
- for (const c of constraints) {
633
- if (c.type === 'where' && c.fieldPath && c.opStr) {
634
- filtered = filtered.filter((docItem) => {
635
- const val = docItem.data()[c.fieldPath];
636
- if (c.opStr === '==')
637
- return val === c.value;
638
- if (c.opStr === '>')
639
- return val > c.value;
640
- if (c.opStr === '<')
641
- return val < c.value;
642
- if (c.opStr === '>=')
643
- return val >= c.value;
644
- if (c.opStr === '<=')
645
- return val <= c.value;
646
- if (c.opStr === '!=')
647
- return val !== c.value;
648
- return true;
649
- });
650
- }
651
- }
652
- return {
653
- docs: filtered,
654
- forEach: (cb) => filtered.forEach(cb),
655
- empty: filtered.length === 0,
656
- size: filtered.length,
657
- docChanges: () => filtered.map((d) => ({ type: 'added', doc: d }))
658
- };
659
- };
660
- exports.getCachedDocs = getCachedDocs;
661
- const getCachedDoc = (docRef) => {
662
- const db = docRef.db;
663
- const segments = docRef.path.split('/');
664
- const docId = segments[segments.length - 1];
665
- const data = db._firestoreCache[docRef.path];
666
- return {
667
- id: docId,
668
- ref: docRef,
669
- exists: () => !!data,
670
- data: () => (data || null)
671
- };
672
- };
673
- exports.getCachedDoc = getCachedDoc;
674
- const getDocs = async (queryOrCollection) => {
675
- const db = queryOrCollection.db;
676
- if (db._initPromise)
677
- await db._initPromise;
678
- const collectionPath = queryOrCollection.path;
679
- const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
680
- // If cache-first strategy, try loading from local cache first
681
- if (db.cacheStrategy === 'cache-first') {
682
- const cached = (0, exports.getCachedDocs)(queryOrCollection);
683
- if (cached && !cached.empty) {
684
- // Background-sync silently to update the local cache
685
- db.client.post(`/api/firestore/${db.projectId}/query`, {
686
- collectionPath,
687
- constraints
688
- }).then(async (res) => {
689
- const docs = res.data.documents || [];
690
- for (const d of docs) {
691
- await db._setCache(`${collectionPath}/${d.id}`, d.fields);
692
- }
693
- db._notifySnapshotCallbacks(collectionPath, 'bulk_update', null);
694
- }).catch(() => { });
695
- return cached;
696
- }
697
- }
698
- // Network-first (or cache-first with empty cache): Prioritize the remote database
699
- try {
700
- const res = await db.client.post(`/api/firestore/${db.projectId}/query`, {
701
- collectionPath,
702
- constraints
703
- });
704
- const docs = res.data.documents || [];
705
- for (const d of docs) {
706
- await db._setCache(`${collectionPath}/${d.id}`, d.fields);
707
- }
708
- const docsArray = docs.map((d) => {
709
- const docRef = (0, exports.doc)(db, `${collectionPath}/${d.id}`);
710
- return {
711
- id: d.id,
712
- ref: docRef,
713
- exists: () => true,
714
- data: () => d.fields
715
- };
716
- });
717
- return {
718
- docs: docsArray,
719
- forEach: (cb) => docsArray.forEach(cb),
720
- empty: docsArray.length === 0,
721
- size: docsArray.length,
722
- docChanges: () => docsArray.map((d) => ({ type: 'added', doc: d }))
723
- };
724
- }
725
- catch (err) {
726
- // If it's a definitive authorization or validation 4xx error, propagate it
727
- if (err && err.response && err.response.status < 500) {
728
- throw err;
729
- }
730
- // Failover: Smoothly fallback to the IndexedDB local cache on network/5xx exceptions
731
- return (0, exports.getCachedDocs)(queryOrCollection);
732
- }
733
- };
734
- exports.getDocs = getDocs;
735
- const getDoc = async (docRef) => {
736
- const db = docRef.db;
737
- if (db._initPromise)
738
- await db._initPromise;
739
- const segments = docRef.path.split('/');
740
- const docId = segments[segments.length - 1];
741
- // If cache-first strategy, try loading from local cache first
742
- if (db.cacheStrategy === 'cache-first') {
743
- const cached = (0, exports.getCachedDoc)(docRef);
744
- if (cached && cached.exists()) {
745
- // Background-sync silently to update the local cache
746
- db.client.get(`/api/firestore/${db.projectId}/document?docPath=${encodeURIComponent(docRef.path)}`)
747
- .then(async (res) => {
748
- const data = res.data.data;
749
- await db._setCache(docRef.path, data);
750
- db._notifySnapshotCallbacks(docRef.path, 'document_written', data);
751
- }).catch(() => { });
752
- return cached;
753
- }
754
- }
755
- // Network-first (or cache-first with empty cache): Prioritize the remote database
756
- try {
757
- const res = await db.client.get(`/api/firestore/${db.projectId}/document?docPath=${encodeURIComponent(docRef.path)}`);
758
- const data = res.data.data;
759
- await db._setCache(docRef.path, data);
760
- return {
761
- id: docId,
762
- ref: docRef,
763
- exists: () => !!data,
764
- data: () => data
765
- };
766
- }
767
- catch (err) {
768
- // If it's a definitive authorization or validation 4xx error, propagate it
769
- if (err && err.response && err.response.status < 500) {
770
- throw err;
771
- }
772
- // Failover: Smoothly fallback to the IndexedDB local cache on network/5xx exceptions
773
- return (0, exports.getCachedDoc)(docRef);
774
- }
775
- };
776
- exports.getDoc = getDoc;
777
- // -----------------------------------------------------------------
778
- // Atomic Document Mutation APIs with Idempotency & Partial Patching
779
- // -----------------------------------------------------------------
780
- const setDoc = async (docRef, data, options) => {
781
- const db = docRef.db;
782
- if (db._initPromise)
783
- await db._initPromise;
784
- await db._setCache(docRef.path, data);
785
- // Broadcast cross-tab and notify snapshot listeners
786
- db._broadcastLocalMutation(docRef.path, 'set', data);
787
- db._notifySnapshotCallbacks(docRef.path, 'document_written', data);
788
- const key = options?.idempotencyKey || generateIdempotencyKey();
789
- const jobId = Math.random().toString(36).substring(2, 9);
790
- const isOnline = typeof navigator !== 'undefined' ? navigator.onLine : true;
791
- if (!isOnline) {
792
- await db._addOfflineJob({ id: jobId, type: 'set', path: docRef.path, data, idempotencyKey: key });
793
- return { success: true, message: 'Offline: Tersimpan di cache lokal' };
794
- }
795
- else {
796
- try {
797
- return (await db.client.post(`/api/firestore/${db.projectId}/document`, { docPath: docRef.path, data, idempotencyKey: key }, { headers: { 'X-Idempotency-Key': key } })).data;
798
- }
799
- catch (error) {
800
- await db._addOfflineJob({ id: jobId, type: 'set', path: docRef.path, data, idempotencyKey: key });
801
- return { success: true, message: 'Koneksi terganggu: Tersimpan di antrean offline' };
802
- }
803
- }
804
- };
805
- exports.setDoc = setDoc;
806
- // Partial Update (UpdateDoc) & Atomic Patch (PatchDoc)
807
- const patchDoc = async (docRef, data, options) => {
808
- const db = docRef.db;
809
- if (db._initPromise)
810
- await db._initPromise;
811
- const existingData = db._firestoreCache[docRef.path] || {};
812
- const updatedData = { ...existingData, ...data };
813
- await db._setCache(docRef.path, updatedData);
814
- db._broadcastLocalMutation(docRef.path, 'patch', data);
815
- db._notifySnapshotCallbacks(docRef.path, 'document_written', updatedData);
816
- const key = options?.idempotencyKey || generateIdempotencyKey();
817
- const jobId = Math.random().toString(36).substring(2, 9);
818
- const isOnline = typeof navigator !== 'undefined' ? navigator.onLine : true;
819
- if (!isOnline) {
820
- await db._addOfflineJob({ id: jobId, type: 'patch', path: docRef.path, data, idempotencyKey: key });
821
- return { success: true, message: 'Offline: Tersimpan di cache lokal' };
822
- }
823
- else {
824
- try {
825
- return (await db.client.patch(`/api/firestore/${db.projectId}/document`, { docPath: docRef.path, data, idempotencyKey: key }, { headers: { 'X-Idempotency-Key': key } })).data;
826
- }
827
- catch (error) {
828
- await db._addOfflineJob({ id: jobId, type: 'patch', path: docRef.path, data, idempotencyKey: key });
829
- return { success: true, message: 'Koneksi terganggu: Tersimpan di antrean offline' };
830
- }
831
- }
832
- };
833
- exports.patchDoc = patchDoc;
834
- const updateDoc = async (docRef, data, options) => {
835
- return (0, exports.patchDoc)(docRef, data, options);
836
- };
837
- exports.updateDoc = updateDoc;
838
- const deleteDoc = async (docRef, options) => {
839
- const db = docRef.db;
840
- if (db._initPromise)
841
- await db._initPromise;
842
- await db._deleteCache(docRef.path);
843
- db._broadcastLocalMutation(docRef.path, 'delete');
844
- db._notifySnapshotCallbacks(docRef.path, 'document_deleted', null);
845
- const key = options?.idempotencyKey || generateIdempotencyKey();
846
- const jobId = Math.random().toString(36).substring(2, 9);
847
- const isOnline = typeof navigator !== 'undefined' ? navigator.onLine : true;
848
- if (!isOnline) {
849
- await db._addOfflineJob({ id: jobId, type: 'delete', path: docRef.path, idempotencyKey: key });
850
- return { success: true };
851
- }
852
- else {
853
- try {
854
- return (await db.client.delete(`/api/firestore/${db.projectId}/document?docPath=${encodeURIComponent(docRef.path)}`, { headers: { 'X-Idempotency-Key': key } })).data;
855
- }
856
- catch (error) {
857
- await db._addOfflineJob({ id: jobId, type: 'delete', path: docRef.path, idempotencyKey: key });
858
- return { success: true };
859
- }
860
- }
861
- };
862
- exports.deleteDoc = deleteDoc;
863
- const onSnapshot = (ref, callback) => {
864
- const db = ref.db;
865
- const initSnapshot = async () => {
866
- if (db._initPromise)
867
- await db._initPromise;
868
- db._ensureFirestoreSSE();
869
- const pathKey = ref.path;
870
- // Fast cache emit
871
- if (ref.type === 'collection' || ref.type === 'query') {
872
- const cached = (0, exports.getCachedDocs)(ref);
873
- if (cached && cached.docs.length > 0) {
874
- callback(cached);
875
- }
876
- }
877
- else {
878
- const cached = (0, exports.getCachedDoc)(ref);
879
- if (cached && cached.exists()) {
880
- callback(cached);
881
- }
882
- }
883
- let isInitial = true;
884
- let debounceTimer = null;
885
- const triggerCallback = () => {
886
- if (debounceTimer)
887
- clearTimeout(debounceTimer);
888
- debounceTimer = setTimeout(async () => {
889
- if (ref.type === 'collection' || ref.type === 'query') {
890
- if (isInitial) {
891
- isInitial = false;
892
- callback((0, exports.getCachedDocs)(ref));
893
- try {
894
- const docs = await (0, exports.getDocs)(ref);
895
- callback(docs);
896
- }
897
- catch (e) { }
898
- }
899
- else {
900
- // Real-time updates: emit instantly and synchronously from cache.
901
- // Eliminates redundant HTTP requests and rendering lag/flicker!
902
- callback((0, exports.getCachedDocs)(ref));
903
- }
904
- }
905
- else {
906
- if (isInitial) {
907
- isInitial = false;
908
- callback((0, exports.getCachedDoc)(ref));
909
- try {
910
- const docData = await (0, exports.getDoc)(ref);
911
- callback(docData);
912
- }
913
- catch (e) { }
914
- }
915
- else {
916
- callback((0, exports.getCachedDoc)(ref));
917
- }
918
- }
919
- }, 50); // Debounce to batch rapid updates
920
- };
921
- triggerCallback();
922
- const listenerId = Math.random().toString(36).substring(2, 9);
923
- db._snapshotCallbacks[`${ref.type}_${listenerId}_${pathKey}`] = triggerCallback;
924
- return () => {
925
- delete db._snapshotCallbacks[`${ref.type}_${listenerId}_${pathKey}`];
926
- };
927
- };
928
- let unsubscribe = () => { };
929
- initSnapshot().then((unsub) => {
930
- unsubscribe = unsub;
931
- });
932
- return () => unsubscribe();
933
- };
934
- exports.onSnapshot = onSnapshot;
935
- const enableIndexedDbPersistence = async (db) => {
936
- db.enablePersistence = true;
937
- if (db._initPromise)
938
- await db._initPromise;
939
- };
940
- exports.enableIndexedDbPersistence = enableIndexedDbPersistence;
941
- const enableOfflinePersistence = async (db) => {
942
- db.enablePersistence = true;
943
- if (db._initPromise)
944
- await db._initPromise;
945
- };
946
- exports.enableOfflinePersistence = enableOfflinePersistence;
947
- const writeBatch = (db) => {
948
- const operations = [];
949
- const batchObj = {
950
- set: (docRef, data) => {
951
- operations.push({ type: 'set', path: docRef.path, data });
952
- return batchObj;
953
- },
954
- update: (docRef, data) => {
955
- operations.push({ type: 'update', path: docRef.path, data });
956
- return batchObj;
957
- },
958
- patch: (docRef, data) => {
959
- operations.push({ type: 'update', path: docRef.path, data });
960
- return batchObj;
961
- },
962
- delete: (docRef) => {
963
- operations.push({ type: 'delete', path: docRef.path });
964
- return batchObj;
965
- },
966
- commit: async () => {
967
- if (db._initPromise)
968
- await db._initPromise;
969
- if (operations.length === 0)
970
- return;
971
- const idempotencyKey = generateIdempotencyKey();
972
- const isOnline = typeof navigator !== 'undefined' ? navigator.onLine : true;
973
- // Update local cache optimistically
974
- for (const op of operations) {
975
- if (op.type === 'set' || op.type === 'update') {
976
- const newData = { ...db._firestoreCache[op.path], ...op.data };
977
- await db._setCache(op.path, newData);
978
- db._broadcastLocalMutation(op.path, op.type === 'set' ? 'set' : 'patch', op.data);
979
- db._notifySnapshotCallbacks(op.path, 'document_written', db._firestoreCache[op.path]);
980
- }
981
- else if (op.type === 'delete') {
982
- await db._deleteCache(op.path);
983
- db._broadcastLocalMutation(op.path, 'delete');
984
- db._notifySnapshotCallbacks(op.path, 'document_deleted', null);
985
- }
986
- }
987
- if (!isOnline) {
988
- for (const op of operations) {
989
- await db._addOfflineJob({
990
- id: Math.random().toString(36).substring(2, 9),
991
- type: op.type,
992
- path: op.path,
993
- data: op.data,
994
- idempotencyKey
995
- });
996
- }
997
- return { success: true, message: 'Offline: Batch operations queued in IndexedDB' };
998
- }
999
- else {
1000
- try {
1001
- return (await db.client.post(`/api/firestore/${db.projectId}/batch`, { operations, idempotencyKey }, { headers: { 'X-Idempotency-Key': idempotencyKey } })).data;
1002
- }
1003
- catch (error) {
1004
- for (const op of operations) {
1005
- await db._addOfflineJob({
1006
- id: Math.random().toString(36).substring(2, 9),
1007
- type: op.type,
1008
- path: op.path,
1009
- data: op.data,
1010
- idempotencyKey
1011
- });
1012
- }
1013
- return { success: true, message: 'Koneksi terganggu: Tersimpan di antrean offline' };
1014
- }
1015
- }
1016
- }
1017
- };
1018
- return batchObj;
1019
- };
1020
- exports.writeBatch = writeBatch;
1021
- const runTransaction = async (db, updateFunction, options) => {
1022
- if (db._initPromise)
1023
- await db._initPromise;
1024
- const maxAttempts = options?.maxAttempts ?? 5;
1025
- let attempt = 0;
1026
- while (true) {
1027
- attempt++;
1028
- const reads = [];
1029
- const operations = [];
1030
- const idempotencyKey = generateIdempotencyKey();
1031
- const transaction = {
1032
- get: async (docRef) => {
1033
- const segments = docRef.path.split('/');
1034
- const docId = segments[segments.length - 1];
1035
- const res = await db.client.get(`/api/firestore/${db.projectId}/documentWithMetadata?docPath=${encodeURIComponent(docRef.path)}`);
1036
- const { data, updatedAt } = res.data;
1037
- reads.push({ path: docRef.path, readUpdatedAt: updatedAt });
1038
- return {
1039
- id: docId,
1040
- ref: docRef,
1041
- exists: () => !!data,
1042
- data: () => data
1043
- };
1044
- },
1045
- set: (docRef, data) => {
1046
- operations.push({ type: 'set', path: docRef.path, data });
1047
- return transaction;
1048
- },
1049
- update: (docRef, data) => {
1050
- operations.push({ type: 'update', path: docRef.path, data });
1051
- return transaction;
1052
- },
1053
- patch: (docRef, data) => {
1054
- operations.push({ type: 'update', path: docRef.path, data });
1055
- return transaction;
1056
- },
1057
- delete: (docRef) => {
1058
- operations.push({ type: 'delete', path: docRef.path });
1059
- return transaction;
1060
- }
1061
- };
1062
- try {
1063
- const result = await updateFunction(transaction);
1064
- if (operations.length > 0 || reads.length > 0) {
1065
- await db.client.post(`/api/firestore/${db.projectId}/transaction`, { reads, operations, idempotencyKey }, { headers: { 'X-Idempotency-Key': idempotencyKey } });
1066
- }
1067
- for (const op of operations) {
1068
- if (op.type === 'set' || op.type === 'update') {
1069
- const newData = { ...db._firestoreCache[op.path], ...op.data };
1070
- await db._setCache(op.path, newData);
1071
- db._broadcastLocalMutation(op.path, op.type === 'set' ? 'set' : 'patch', op.data);
1072
- db._notifySnapshotCallbacks(op.path, 'document_written', db._firestoreCache[op.path]);
1073
- }
1074
- else if (op.type === 'delete') {
1075
- await db._deleteCache(op.path);
1076
- db._broadcastLocalMutation(op.path, 'delete');
1077
- db._notifySnapshotCallbacks(op.path, 'document_deleted', null);
1078
- }
1079
- }
1080
- return result;
1081
- }
1082
- catch (err) {
1083
- const isConflict = err && err.response && err.response.status === 409;
1084
- if (isConflict && attempt < maxAttempts) {
1085
- console.warn(`[NexaBase Transaction] Retrying due to concurrency conflict (Attempt ${attempt}/${maxAttempts})`);
1086
- await new Promise((resolve) => setTimeout(resolve, Math.random() * 100 * attempt));
1087
- continue;
1088
- }
1089
- throw err;
1090
- }
1091
- }
1092
- };
1093
- exports.runTransaction = runTransaction;
17
+ // App
18
+ __exportStar(require("./app/initializeApp"), exports);
19
+ __exportStar(require("./app/NexaApp"), exports);
20
+ // Auth
21
+ __exportStar(require("./auth/Auth"), exports);
22
+ __exportStar(require("./auth/authTypes"), exports);
23
+ __exportStar(require("./auth/persistence"), exports);
24
+ // Firestore
25
+ __exportStar(require("./firestore/Firestore"), exports);
26
+ __exportStar(require("./firestore/DocumentReference"), exports);
27
+ __exportStar(require("./firestore/CollectionReference"), exports);
28
+ __exportStar(require("./firestore/Query"), exports);
29
+ __exportStar(require("./firestore/Snapshot"), exports);
30
+ __exportStar(require("./firestore/queryConstraints"), exports);
31
+ __exportStar(require("./firestore/writes"), exports);
32
+ __exportStar(require("./firestore/batch"), exports);
33
+ __exportStar(require("./firestore/transaction"), exports);
34
+ __exportStar(require("./firestore/Timestamp"), exports);
35
+ __exportStar(require("./firestore/GeoPoint"), exports);
36
+ __exportStar(require("./firestore/FieldPath"), exports);
37
+ __exportStar(require("./firestore/FieldValue"), exports);
38
+ __exportStar(require("./firestore/SnapshotManager"), exports);
39
+ // Realtime
40
+ __exportStar(require("./realtime/RealtimeDatabase"), exports);
41
+ __exportStar(require("./realtime/RealtimeConnection"), exports);
42
+ // Storage
43
+ __exportStar(require("./storage/Storage"), exports);
44
+ __exportStar(require("./storage/StorageReference"), exports);
45
+ __exportStar(require("./storage/UploadTask"), exports);
46
+ // Persistence
47
+ __exportStar(require("./persistence/IndexedDB"), exports);
48
+ __exportStar(require("./persistence/LocalStore"), exports);
49
+ __exportStar(require("./persistence/MutationQueue"), exports);
50
+ // Sync
51
+ __exportStar(require("./sync/SyncEngine"), exports);
52
+ __exportStar(require("./sync/ConflictResolver"), exports);
53
+ // Transport
54
+ __exportStar(require("./transport/HttpClient"), exports);
55
+ __exportStar(require("./transport/SSEClient"), exports);
56
+ // Errors & Types
57
+ __exportStar(require("./errors/NexaError"), exports);
58
+ __exportStar(require("./types/index"), exports);
59
+ __exportStar(require("./utils/helpers"), exports);