nexabase-console 2.0.9 → 2.1.1

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.
@@ -36,6 +36,7 @@ export declare class NexaApp {
36
36
  get _firestoreCache(): Record<string, any>;
37
37
  private _setupCrossTabSync;
38
38
  _broadcastLocalMutation(path: string, action: string, data?: any): void;
39
+ getPendingWritesCount(): number;
39
40
  hasPendingWrites(path: string): boolean;
40
41
  markInflight(path: string, mutationId: string): void;
41
42
  unmarkInflight(path: string, mutationId: string): void;
@@ -57,6 +58,8 @@ export declare class NexaApp {
57
58
  action: 'set' | 'patch' | 'delete';
58
59
  serverDocument?: any;
59
60
  updateTime?: string;
61
+ mutationData?: any;
62
+ merge?: boolean;
60
63
  }): Promise<{
61
64
  data: any | null;
62
65
  hasPendingWrites: boolean;
@@ -66,6 +66,12 @@ class NexaApp {
66
66
  _broadcastLocalMutation(path, action, data) {
67
67
  this.syncEngine.broadcastLocalMutation(path, action, data);
68
68
  }
69
+ getPendingWritesCount() {
70
+ if (!this.mutationQueue)
71
+ return 0;
72
+ const queue = this.mutationQueue.inMemoryQueue || [];
73
+ return queue.length;
74
+ }
69
75
  hasPendingWrites(path) {
70
76
  return this.mutationQueue.hasPendingWrites(path);
71
77
  }
@@ -166,14 +172,31 @@ class NexaApp {
166
172
  const db = this.indexedDB.getRawDB();
167
173
  const queue = this.mutationQueue.inMemoryQueue;
168
174
  const jobIndex = queue.findIndex((j) => j.id === mutationId);
169
- if (jobIndex !== -1)
175
+ let jobData = null;
176
+ if (jobIndex !== -1) {
177
+ jobData = queue[jobIndex];
170
178
  queue.splice(jobIndex, 1);
179
+ }
171
180
  if (options.action === 'delete') {
172
181
  this.localStore.getServerBaseCache()[path] = null;
173
182
  }
174
183
  else if (serverDocument && serverDocument.fields) {
175
184
  this.localStore.getServerBaseCache()[path] = (0, QueryUtils_1.reviveNexaData)(serverDocument.fields);
176
185
  }
186
+ else {
187
+ // PROMOTION: If server returned success: true without serverDocument.fields (lightweight ACK),
188
+ // promote the local mutation payload to Server Base Cache so the document does not revert to null!
189
+ const rawData = (jobData ? jobData.data : options.mutationData) || {};
190
+ const isMerge = jobData ? jobData.merge : options.merge;
191
+ let currentBase = this.localStore.getServerBaseCache()[path];
192
+ currentBase = currentBase !== undefined && currentBase !== null ? (0, QueryUtils_1.cloneNexaData)(currentBase) : currentBase;
193
+ if (options.action === 'set' && !isMerge) {
194
+ this.localStore.getServerBaseCache()[path] = (0, QueryUtils_1.reviveNexaData)((0, helpers_1.applyDataTransforms)({}, rawData));
195
+ }
196
+ else {
197
+ this.localStore.getServerBaseCache()[path] = (0, QueryUtils_1.reviveNexaData)((0, helpers_1.applyDataTransforms)(currentBase || {}, rawData));
198
+ }
199
+ }
177
200
  const view = this._calculateLocalView(path);
178
201
  const rebasedData = view.data;
179
202
  if (rebasedData === null) {
@@ -9,3 +9,5 @@ export declare const increment: (n: number) => FieldValue;
9
9
  export declare const arrayUnion: (...elements: any[]) => FieldValue;
10
10
  export declare const arrayRemove: (...elements: any[]) => FieldValue;
11
11
  export declare const deleteField: () => FieldValue;
12
+ export declare const atomicIncrement: (n: number) => FieldValue;
13
+ export declare const atomicDecrement: (n: number) => FieldValue;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteField = exports.arrayRemove = exports.arrayUnion = exports.increment = exports.serverTimestamp = exports.FieldValue = void 0;
3
+ exports.atomicDecrement = exports.atomicIncrement = exports.deleteField = exports.arrayRemove = exports.arrayUnion = exports.increment = exports.serverTimestamp = exports.FieldValue = void 0;
4
4
  class FieldValue {
5
5
  constructor(method, value) {
6
6
  this.method = method;
@@ -19,3 +19,8 @@ const arrayRemove = (...elements) => new FieldValue('arrayRemove', elements);
19
19
  exports.arrayRemove = arrayRemove;
20
20
  const deleteField = () => new FieldValue('deleteField');
21
21
  exports.deleteField = deleteField;
22
+ // Helper methods for Offline-First operations (e.g. POS/Inventory systems)
23
+ const atomicIncrement = (n) => new FieldValue('increment', Math.abs(n));
24
+ exports.atomicIncrement = atomicIncrement;
25
+ const atomicDecrement = (n) => new FieldValue('increment', -Math.abs(n));
26
+ exports.atomicDecrement = atomicDecrement;
@@ -17,12 +17,15 @@ const runTransaction = async (db, updateFunction, options) => {
17
17
  let res;
18
18
  try {
19
19
  if (typeof navigator !== 'undefined' && !navigator.onLine) {
20
- throw new Error('offline');
20
+ throw Object.assign(new Error('NEXA_ERR_TRANSACTION_OFFLINE'), { code: 'NEXA_ERR_TRANSACTION_OFFLINE' });
21
21
  }
22
22
  res = await db.client.get(`/api/firestore/${db.projectId}/documentWithMetadata?docPath=${encodeURIComponent(docRef.path)}`);
23
23
  }
24
24
  catch (err) {
25
- throw (0, NexaError_1.toNexaError)(new Error('Transaction failed: Perangkat sedang offline atau jaringan terputus. Transaksi memerlukan koneksi aktif.'));
25
+ if (err?.code === 'NEXA_ERR_TRANSACTION_OFFLINE' || err?.message === 'NEXA_ERR_TRANSACTION_OFFLINE') {
26
+ throw Object.assign(new Error('Transaction failed: Perangkat sedang offline atau jaringan terputus. Transaksi memerlukan koneksi aktif. Gunakan writeBatch atau runOfflineSafeTransaction untuk operasi offline.'), { code: 'NEXA_ERR_TRANSACTION_OFFLINE' });
27
+ }
28
+ throw (0, NexaError_1.toNexaError)(err);
26
29
  }
27
30
  const { data, updatedAt } = res.data;
28
31
  reads.push({ path: docRef.path, readUpdatedAt: updatedAt });
@@ -48,7 +51,7 @@ const runTransaction = async (db, updateFunction, options) => {
48
51
  const result = await updateFunction(transaction);
49
52
  if (operations.length > 0 || reads.length > 0) {
50
53
  if (typeof navigator !== 'undefined' && !navigator.onLine) {
51
- throw (0, NexaError_1.toNexaError)(new Error('Transaction failed: Perangkat sedang offline atau jaringan terputus. Transaksi memerlukan koneksi aktif.'));
54
+ throw Object.assign(new Error('Transaction failed: Perangkat sedang offline atau jaringan terputus. Transaksi memerlukan koneksi aktif. Gunakan writeBatch atau runOfflineSafeTransaction untuk operasi offline.'), { code: 'NEXA_ERR_TRANSACTION_OFFLINE' });
52
55
  }
53
56
  await db.client.post(`/api/firestore/${db.projectId}/transaction`, { reads, operations, idempotencyKey }, { headers: { 'X-Idempotency-Key': idempotencyKey } });
54
57
  }
@@ -1,3 +1,4 @@
1
+ import { NexaApp } from "../app/NexaApp";
1
2
  import { DocumentReference } from '../types/index';
2
3
  export { FieldValue } from './FieldValue';
3
4
  export type WriteResult<T = unknown> = {
@@ -26,3 +27,4 @@ export declare const updateDoc: (docRef: DocumentReference, data: any, options?:
26
27
  export declare const deleteDoc: (docRef: DocumentReference, options?: {
27
28
  idempotencyKey?: string;
28
29
  }) => Promise<WriteResult>;
30
+ export declare const getPendingWritesCount: (db: NexaApp) => number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteDoc = exports.updateDoc = exports.patchDoc = exports.setDoc = exports.FieldValue = void 0;
3
+ exports.getPendingWritesCount = exports.deleteDoc = exports.updateDoc = exports.patchDoc = exports.setDoc = exports.FieldValue = void 0;
4
4
  const helpers_1 = require("../utils/helpers");
5
5
  var FieldValue_1 = require("./FieldValue");
6
6
  Object.defineProperty(exports, "FieldValue", { enumerable: true, get: function () { return FieldValue_1.FieldValue; } });
@@ -173,7 +173,9 @@ const executeWriteMutation = async (docRef, action, data, options) => {
173
173
  path: docRef.path,
174
174
  action,
175
175
  serverDocument: res.document,
176
- updateTime: res.document?.updateTime
176
+ updateTime: res.document?.updateTime,
177
+ mutationData: data,
178
+ merge: options?.merge
177
179
  });
178
180
  }
179
181
  catch (ackError) {
@@ -207,3 +209,7 @@ const deleteDoc = async (docRef, options) => {
207
209
  return executeWriteMutation(docRef, 'delete', null, options);
208
210
  };
209
211
  exports.deleteDoc = deleteDoc;
212
+ const getPendingWritesCount = (db) => {
213
+ return db.getPendingWritesCount();
214
+ };
215
+ exports.getPendingWritesCount = getPendingWritesCount;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexabase-console",
3
- "version": "2.0.9",
3
+ "version": "2.1.1",
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",