nexabase-console 2.1.3 → 2.1.4

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.
@@ -92,7 +92,7 @@ export declare class NexaApp {
92
92
  _ensureFirestoreWebSocket(): void;
93
93
  _closeFirestoreSSEIfIdle(): void;
94
94
  _closeFirestoreWebSocketIfIdle(): void;
95
- _notifySnapshotCallbacks(path: string, actionType: string, payload: any): void;
95
+ _notifySnapshotCallbacks(path: string, actionType: string, payload?: any): void;
96
96
  signInWithEmailAndPassword(email: string, password: string): Promise<AuthSession>;
97
97
  createUserWithEmailAndPassword(email: string, password: string, name?: string): Promise<AuthSession>;
98
98
  sendOtp(email: string): Promise<{
@@ -46,7 +46,7 @@ class NexaApp {
46
46
  this.mutationQueue = new MutationQueue_1.MutationQueue(this.projectId, this.indexedDB, this.client);
47
47
  this.syncEngine = new SyncEngine_1.SyncEngine(this.projectId, this.mutationQueue, this.sseClient);
48
48
  // Realtime & Storage
49
- const realtimeConn = new RealtimeConnection_1.RealtimeConnection(this.sseClient);
49
+ const realtimeConn = new RealtimeConnection_1.RealtimeConnection(this.sseClient, this.wsClient);
50
50
  this.realtimeDatabase = new RealtimeDatabase_1.RealtimeDatabase(this.projectId, this.client, realtimeConn);
51
51
  this.storage = new Storage_1.Storage(this.projectId, this.client);
52
52
  this._setupCrossTabSync();
@@ -366,34 +366,44 @@ class NexaApp {
366
366
  const targetDocPath = docPath || rawPath;
367
367
  const isDelete = type === 'document_deleted' || action === 'delete' || type === 'delete';
368
368
  if (targetDocPath && typeof targetDocPath === 'string') {
369
+ const cleanDocPath = targetDocPath.replace(/^\/+|\/+$/g, '');
369
370
  if (isDelete) {
370
- this._deleteCache(targetDocPath);
371
+ this._deleteCache(cleanDocPath);
371
372
  }
372
- else if (data) {
373
- this._setCache(targetDocPath, data);
373
+ else if (data !== undefined && data !== null) {
374
+ this._setCache(cleanDocPath, data);
374
375
  }
375
- const parts = targetDocPath.split('/');
376
+ const parts = cleanDocPath.split('/');
376
377
  const colPath = parts.length > 1 ? parts.slice(0, -1).join('/') : '';
377
- this._notifySnapshotCallbacks(targetDocPath, isDelete ? 'document_deleted' : 'document_written', data);
378
+ this._notifySnapshotCallbacks(cleanDocPath, isDelete ? 'document_deleted' : 'document_written', data);
378
379
  if (colPath) {
379
380
  this._notifySnapshotCallbacks(colPath, isDelete ? 'document_deleted' : 'document_written', data);
380
381
  }
381
382
  }
382
383
  else if (collectionPath && typeof collectionPath === 'string') {
383
- this._notifySnapshotCallbacks(collectionPath, 'server_update', data);
384
+ const cleanColPath = collectionPath.replace(/^\/+|\/+$/g, '');
385
+ this._notifySnapshotCallbacks(cleanColPath, 'server_update', data);
384
386
  }
385
387
  }
386
388
  _ensureFirestoreSSE() {
387
389
  if (this._firestoreListening)
388
390
  return;
389
391
  this._firestoreListening = true;
392
+ // Listen to WebSocketClient
390
393
  const unsub1 = this.wsClient.addListener('data_changed', (payload) => {
391
394
  this._handleServerFirestoreChange(payload);
392
395
  });
393
396
  const unsub2 = this.wsClient.addListener('firestore_changed', (payload) => {
394
397
  this._handleServerFirestoreChange(payload);
395
398
  });
396
- this._firestoreUnsubscribes.push(unsub1, unsub2);
399
+ // Also listen to SSEClient
400
+ const unsub3 = this.sseClient.addListener('data_changed', (payload) => {
401
+ this._handleServerFirestoreChange(payload);
402
+ });
403
+ const unsub4 = this.sseClient.addListener('firestore_changed', (payload) => {
404
+ this._handleServerFirestoreChange(payload);
405
+ });
406
+ this._firestoreUnsubscribes.push(unsub1, unsub2, unsub3, unsub4);
397
407
  }
398
408
  _ensureFirestoreWebSocket() {
399
409
  this._ensureFirestoreSSE();
@@ -411,14 +421,28 @@ class NexaApp {
411
421
  _notifySnapshotCallbacks(path, actionType, payload) {
412
422
  if (!path || typeof path !== 'string')
413
423
  return;
414
- Object.keys(this._snapshotCallbacks).forEach((cbKey) => {
415
- const parts = cbKey.split('_');
416
- const targetPath = parts.slice(2).join('_');
417
- if (cbKey.startsWith('document_') && targetPath === path) {
418
- this._snapshotCallbacks[cbKey](payload);
424
+ const cleanPath = path.replace(/^\/+|\/+$/g, '');
425
+ Object.entries(this._snapshotCallbacks).forEach(([cbKey, callback]) => {
426
+ let type = '';
427
+ let targetPath = '';
428
+ if (cbKey.includes('::')) {
429
+ const parts = cbKey.split('::');
430
+ type = parts[0];
431
+ targetPath = parts.slice(2).join('::');
432
+ }
433
+ else {
434
+ const firstUnderscore = cbKey.indexOf('_');
435
+ const lastUnderscore = cbKey.lastIndexOf('_');
436
+ type = cbKey.substring(0, firstUnderscore);
437
+ targetPath = cbKey.substring(lastUnderscore + 1);
438
+ }
439
+ const cleanTargetPath = (targetPath || '').replace(/^\/+|\/+$/g, '');
440
+ if (type === 'document' && cleanTargetPath === cleanPath) {
441
+ callback(payload);
419
442
  }
420
- else if ((cbKey.startsWith('collection_') || cbKey.startsWith('query_')) && (path.startsWith(targetPath + '/') || path === targetPath)) {
421
- this._snapshotCallbacks[cbKey](payload);
443
+ else if ((type === 'collection' || type === 'query') &&
444
+ (cleanPath === cleanTargetPath || cleanPath.startsWith(cleanTargetPath + '/'))) {
445
+ callback(payload);
422
446
  }
423
447
  });
424
448
  }
@@ -79,9 +79,9 @@ const onSnapshot = (ref, callback) => {
79
79
  }
80
80
  const db = ref.db;
81
81
  let active = true;
82
- const pathKey = ref.path;
82
+ const pathKey = ref.path.replace(/^\/+|\/+$/g, '');
83
83
  const listenerId = `${Date.now()}_${Math.random().toString(36).slice(2)}_${++listenerSequence}`;
84
- const keyToClean = `${ref.type}_${listenerId}_${pathKey}`;
84
+ const keyToClean = `${ref.type}::${listenerId}::${pathKey}`;
85
85
  let isInitial = true;
86
86
  let lastData = undefined;
87
87
  let lastDocsForChanges = [];
@@ -115,13 +115,11 @@ const onSnapshot = (ref, callback) => {
115
115
  };
116
116
  db._snapshotCallbacks[keyToClean] = triggerCallback;
117
117
  triggerCallback();
118
- // Unified transport coordination: Prefer WebSocket if available, otherwise SSE
118
+ // Unified transport coordination: Always ensure listening on change events & subscribe path
119
+ db._ensureFirestoreWebSocket();
119
120
  if (db.wsClient && typeof db.wsClient.subscribe === 'function') {
120
121
  db.wsClient.subscribe(ref.path);
121
122
  }
122
- else {
123
- db._ensureFirestoreSSE();
124
- }
125
123
  (async () => {
126
124
  if (db._initPromise)
127
125
  await db._initPromise;
@@ -1,7 +1,8 @@
1
- import { SSEClient } from '../transport/SSEClient';
1
+ import { SSEClient, WebSocketClient } from '../transport/SSEClient';
2
2
  export declare class RealtimeConnection {
3
3
  private sseClient;
4
- constructor(sseClient: SSEClient);
4
+ private wsClient?;
5
+ constructor(sseClient: SSEClient, wsClient?: WebSocketClient);
5
6
  subscribeToPath(path: string, callback: (value: any) => void): () => void;
6
7
  close(): void;
7
8
  }
@@ -2,15 +2,40 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RealtimeConnection = void 0;
4
4
  class RealtimeConnection {
5
- constructor(sseClient) {
5
+ constructor(sseClient, wsClient) {
6
6
  this.sseClient = sseClient;
7
+ this.wsClient = wsClient;
7
8
  }
8
9
  subscribeToPath(path, callback) {
9
- return this.sseClient.addListener('data_changed', (payload) => {
10
- if (payload && payload.path === path) {
11
- callback(payload.value);
10
+ const cleanPath = (path || '').replace(/^\/+|\/+$/g, '');
11
+ if (this.wsClient && typeof this.wsClient.subscribe === 'function') {
12
+ this.wsClient.subscribe(cleanPath);
13
+ }
14
+ const unsubWS = this.wsClient
15
+ ? this.wsClient.addListener('data_changed', (payload) => {
16
+ if (payload) {
17
+ const p = (payload.path || payload.docPath || '').replace(/^\/+|\/+$/g, '');
18
+ if (p === cleanPath) {
19
+ callback(payload.value !== undefined ? payload.value : payload.data);
20
+ }
21
+ }
22
+ })
23
+ : () => { };
24
+ const unsubSSE = this.sseClient.addListener('data_changed', (payload) => {
25
+ if (payload) {
26
+ const p = (payload.path || payload.docPath || '').replace(/^\/+|\/+$/g, '');
27
+ if (p === cleanPath) {
28
+ callback(payload.value !== undefined ? payload.value : payload.data);
29
+ }
12
30
  }
13
31
  });
32
+ return () => {
33
+ unsubWS();
34
+ unsubSSE();
35
+ if (this.wsClient && typeof this.wsClient.unsubscribe === 'function') {
36
+ this.wsClient.unsubscribe(cleanPath);
37
+ }
38
+ };
14
39
  }
15
40
  close() {
16
41
  this.sseClient.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexabase-console",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "SDK Client resmi untuk NexaBase: Platform Sinkronisasi NoSQL, Realtime, File Storage, & Autentikasi Offline-First.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",