nexabase-console 1.0.7 → 1.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.
- package/README.md +113 -9
- package/dist/index.js +23 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,8 +7,9 @@ SDK Client Resmi untuk **NexaBase**: Platform sinkronisasi database modern yang
|
|
|
7
7
|
## 🚀 Fitur Utama
|
|
8
8
|
|
|
9
9
|
- **⚡ Real-Time Synchronization**: Sinkronisasi data real-time dengan latency rendah menggunakan Server-Sent Events (SSE).
|
|
10
|
+
- **🔒 Concurrency Control & Transactions (`runTransaction`)**: Transaksi dua arah (Read-Before-Write) atomik dengan kontrol konkurensi (Optimistic Locking & Automatic Retry) untuk mencegah race conditions pada pemotongan stok, kuota, atau transaksi finansial.
|
|
10
11
|
- **📦 Offline-First & Auto-Sync**: Sinkronisasi state lokal secara otomatis menggunakan **IndexedDB**. Data tetap tersimpan di browser jika koneksi terputus dan akan disinkronkan ke server secara otomatis saat kembali online.
|
|
11
|
-
- **🔥 Modular Firestore API**: Desain API modular modern mirip Firebase SDK (v9+)
|
|
12
|
+
- **🔥 Modular Firestore API & TypeScript Generics**: Desain API modular modern mirip Firebase SDK (v9+) dengan dukungan penuh parameter tipe generik (TypeScript Generics) untuk menjamin tipe data yang aman (type-safe) secara end-to-end.
|
|
12
13
|
- **🔑 Secure Auth & Passwordless OTP**: Dukungan penuh registrasi, login email/sandi, serta OTP instan via SMTP yang aman.
|
|
13
14
|
- **📂 File Storage Integration**: Unggah aset media dan file blob ke cloud storage dengan satu perintah.
|
|
14
15
|
|
|
@@ -109,24 +110,82 @@ const listenLive = () => {
|
|
|
109
110
|
};
|
|
110
111
|
```
|
|
111
112
|
|
|
112
|
-
#### Menggunakan Atomic Write Batch
|
|
113
|
+
#### Menggunakan Atomic Write Batch (Untuk Upload File / Excel Massal)
|
|
114
|
+
Jika Anda mengunggah data sekaligus dalam jumlah besar dari file (misal: import 500 resi dari Excel), hindari memanggil `setDoc` satu per satu di dalam _looping_. Gunakan fitur `writeBatch` yang membundel seluruh operasi Anda dalam satu pengiriman jaringan (hingga 500 operasi per eksekusi).
|
|
115
|
+
|
|
116
|
+
**Catatan:** Jika alur kerjanya adalah **kasir/admin melakukan scan barcode satu-per-satu lalu menekan enter**, maka Anda **TIDAK PERLU** menggunakan `writeBatch`. Menyimpan data satu-per-satu (`setDoc` / `addDoc`) setiap kali dienter adalah **cara yang paling benar dan normal** untuk kebutuhan _realtime_. Database dapat dengan mudah menangani 500 scan yang terjadi secara bertahap sepanjang hari.
|
|
117
|
+
|
|
113
118
|
```javascript
|
|
114
119
|
import { doc, writeBatch } from 'nexabase-console';
|
|
115
120
|
|
|
116
|
-
const
|
|
121
|
+
const executeBulkUpload = async (listResi) => {
|
|
117
122
|
const batch = writeBatch(db);
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
// ListResi adalah array berisi objek resi. Maksimal 500 per batch.
|
|
125
|
+
listResi.forEach((resi) => {
|
|
126
|
+
// Referensi dokumen
|
|
127
|
+
const docRef = doc(db, 'resi', resi.nomor_resi);
|
|
128
|
+
batch.set(docRef, resi);
|
|
129
|
+
});
|
|
124
130
|
|
|
131
|
+
// Eksekusi semua secara atomik (bersamaan dalam 1 request)
|
|
125
132
|
const result = await batch.commit();
|
|
126
|
-
console.log(
|
|
133
|
+
console.log(`Berhasil mengunggah ${listResi.length} resi secara massal.`);
|
|
127
134
|
};
|
|
128
135
|
```
|
|
129
136
|
|
|
137
|
+
#### Menggunakan Transactions (`runTransaction`) — Concurrency Control
|
|
138
|
+
```javascript
|
|
139
|
+
import { doc, runTransaction } from 'nexabase-console';
|
|
140
|
+
|
|
141
|
+
async function potongStokAman(productId, variantId, qtyBeli) {
|
|
142
|
+
const productRef = doc(db, 'products', productId);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const hasil = await runTransaction(db, async (transaction) => {
|
|
146
|
+
// 1. Baca dokumen di dalam transaksi (Read)
|
|
147
|
+
const productSnap = await transaction.get(productRef);
|
|
148
|
+
if (!productSnap.exists()) {
|
|
149
|
+
throw new Error("Produk tidak ditemukan!");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const productData = productSnap.data();
|
|
153
|
+
const variantIndex = productData.variants?.findIndex((v) => v.id === variantId);
|
|
154
|
+
if (variantIndex === -1 || variantIndex === undefined) {
|
|
155
|
+
throw new Error("Varian produk tidak ditemukan!");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const variant = productData.variants[variantIndex];
|
|
159
|
+
const stokSaatIni = variant.stock;
|
|
160
|
+
|
|
161
|
+
// 2. Validasi stok sebelum dipotong
|
|
162
|
+
if (stokSaatIni < qtyBeli) {
|
|
163
|
+
throw new Error(`Stok tidak mencukupi! Tersedia: ${stokSaatIni}, Diminta: ${qtyBeli}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 3. Kalkulasi data baru
|
|
167
|
+
const updatedVariants = [...productData.variants];
|
|
168
|
+
updatedVariants[variantIndex] = {
|
|
169
|
+
...variant,
|
|
170
|
+
stock: stokSaatIni - qtyBeli
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// 4. Tulis perubahan di dalam transaksi (Write)
|
|
174
|
+
transaction.update(productRef, {
|
|
175
|
+
variants: updatedVariants,
|
|
176
|
+
updatedAt: new Date().toISOString()
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return { success: true, sisaStok: stokSaatIni - qtyBeli };
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
console.log("Transaksi Berhasil!", hasil);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error("Transaksi Gagal / Conflict:", error.message);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
130
189
|
---
|
|
131
190
|
|
|
132
191
|
### 3. Autentikasi Pengguna & OTP Tanpa Sandi
|
|
@@ -185,6 +244,51 @@ const uploadImage = async (fileBlob) => {
|
|
|
185
244
|
|
|
186
245
|
---
|
|
187
246
|
|
|
247
|
+
### 5. Keamanan Tipe TypeScript (Generics) & Kueri Lanjutan
|
|
248
|
+
|
|
249
|
+
#### Dukungan TypeScript Generics
|
|
250
|
+
Kini Anda bisa mendefinisikan tipe data struktur dokumen Anda agar penulisan dan pembacaan data sepenuhnya aman (Type-safe).
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { collection, doc, getDoc, getDocs } from 'nexabase-console';
|
|
254
|
+
|
|
255
|
+
interface Product {
|
|
256
|
+
name: string;
|
|
257
|
+
price: number;
|
|
258
|
+
stock: number;
|
|
259
|
+
tags: string[];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 1. Definisikan tipe koleksi
|
|
263
|
+
const productsCol = collection<Product>(db, 'products');
|
|
264
|
+
|
|
265
|
+
// 2. Definisikan tipe referensi dokumen
|
|
266
|
+
const productRef = doc<Product>(db, 'products', 'prod_a');
|
|
267
|
+
|
|
268
|
+
// 3. Baca data dengan Autocomplete & Type Checking penuh
|
|
269
|
+
const snapshot = await getDoc(productRef);
|
|
270
|
+
if (snapshot.exists()) {
|
|
271
|
+
const data = snapshot.data(); // Tipe data otomatis terdeteksi sebagai 'Product'
|
|
272
|
+
console.log(data.name); // Aman! Autocomplete tersedia
|
|
273
|
+
console.log(data.price); // Aman!
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Kompatibilitas QuerySnapshot (`docChanges()`)
|
|
278
|
+
Bagi Anda yang bermigrasi dari Firebase, objek `QuerySnapshot` yang dikembalikan oleh `getDocs` atau `onSnapshot` kini mendukung metode `.docChanges()` secara native:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const snapshot = await getDocs(productsCol);
|
|
282
|
+
const changes = snapshot.docChanges(); // Mengembalikan array perubahan dokumen
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Filter Offline Lengkap (Advanced Querying Operators)
|
|
286
|
+
Mekanisme kueri offline pada SDK NexaBase kini mendukung operator Firestore tingkat lanjut secara penuh demi keandalan luar biasa bahkan saat koneksi terputus:
|
|
287
|
+
- Operator dasar: `==`, `!=`, `>`, `<`, `>=`, `<=`
|
|
288
|
+
- Operator array & keanggotaan: `in`, `not-in`, `array-contains`, `array-contains-any`
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
188
292
|
## 🗄️ Dukungan Sinkronisasi Offline (IndexedDB)
|
|
189
293
|
|
|
190
294
|
NexaBase JS SDK dikembangkan dengan arsitektur **Offline-First**.
|
package/dist/index.js
CHANGED
|
@@ -696,15 +696,29 @@ const onSnapshot = (ref, callback) => {
|
|
|
696
696
|
callback(cached);
|
|
697
697
|
}
|
|
698
698
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
699
|
+
let debounceTimer = null;
|
|
700
|
+
const triggerCallback = () => {
|
|
701
|
+
if (debounceTimer)
|
|
702
|
+
clearTimeout(debounceTimer);
|
|
703
|
+
debounceTimer = setTimeout(async () => {
|
|
704
|
+
if (ref.type === 'collection' || ref.type === 'query') {
|
|
705
|
+
// Immediately serve from cache for UI responsiveness
|
|
706
|
+
callback((0, exports.getCachedDocs)(ref));
|
|
707
|
+
try {
|
|
708
|
+
const docs = await (0, exports.getDocs)(ref);
|
|
709
|
+
callback(docs);
|
|
710
|
+
}
|
|
711
|
+
catch (e) { }
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
callback((0, exports.getCachedDoc)(ref));
|
|
715
|
+
try {
|
|
716
|
+
const docData = await (0, exports.getDoc)(ref);
|
|
717
|
+
callback(docData);
|
|
718
|
+
}
|
|
719
|
+
catch (e) { }
|
|
720
|
+
}
|
|
721
|
+
}, 50); // Debounce to batch rapid changes (fast enough for manual scans)
|
|
708
722
|
};
|
|
709
723
|
// 2. Fetch the latest live server data and update cache + trigger the callback again
|
|
710
724
|
triggerCallback();
|
package/package.json
CHANGED