nexabase-console 1.0.7 → 1.1.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.
- package/README.md +111 -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,80 @@ const listenLive = () => {
|
|
|
109
110
|
};
|
|
110
111
|
```
|
|
111
112
|
|
|
112
|
-
#### Menggunakan Atomic Write Batch
|
|
113
|
+
#### Menggunakan Atomic Write Batch (Sangat Disarankan untuk >100 Data/Hari)
|
|
114
|
+
Jika Anda harus memasukkan data dalam jumlah besar (misal: 500 resi pengiriman), hindari memanggil `setDoc` satu per satu di dalam _looping_ (looping 500 kali = 500 koneksi = berpotensi menimbulkan lag di sisi klien yang sedang berlangganan `onSnapshot`). Gunakan fitur `writeBatch` yang membundel seluruh operasi Anda dalam satu pengiriman jaringan (hingga 500 operasi per eksekusi).
|
|
115
|
+
|
|
113
116
|
```javascript
|
|
114
117
|
import { doc, writeBatch } from 'nexabase-console';
|
|
115
118
|
|
|
116
|
-
const
|
|
119
|
+
const executeBulkUpload = async (listResi) => {
|
|
117
120
|
const batch = writeBatch(db);
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
// ListResi adalah array berisi objek resi. Maksimal 500 per batch.
|
|
123
|
+
listResi.forEach((resi) => {
|
|
124
|
+
// Referensi dokumen
|
|
125
|
+
const docRef = doc(db, 'resi', resi.nomor_resi);
|
|
126
|
+
batch.set(docRef, resi);
|
|
127
|
+
});
|
|
124
128
|
|
|
129
|
+
// Eksekusi semua secara atomik (bersamaan dalam 1 request)
|
|
125
130
|
const result = await batch.commit();
|
|
126
|
-
console.log(
|
|
131
|
+
console.log(`Berhasil mengunggah ${listResi.length} resi secara massal.`);
|
|
127
132
|
};
|
|
128
133
|
```
|
|
129
134
|
|
|
135
|
+
#### Menggunakan Transactions (`runTransaction`) — Concurrency Control
|
|
136
|
+
```javascript
|
|
137
|
+
import { doc, runTransaction } from 'nexabase-console';
|
|
138
|
+
|
|
139
|
+
async function potongStokAman(productId, variantId, qtyBeli) {
|
|
140
|
+
const productRef = doc(db, 'products', productId);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const hasil = await runTransaction(db, async (transaction) => {
|
|
144
|
+
// 1. Baca dokumen di dalam transaksi (Read)
|
|
145
|
+
const productSnap = await transaction.get(productRef);
|
|
146
|
+
if (!productSnap.exists()) {
|
|
147
|
+
throw new Error("Produk tidak ditemukan!");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const productData = productSnap.data();
|
|
151
|
+
const variantIndex = productData.variants?.findIndex((v) => v.id === variantId);
|
|
152
|
+
if (variantIndex === -1 || variantIndex === undefined) {
|
|
153
|
+
throw new Error("Varian produk tidak ditemukan!");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const variant = productData.variants[variantIndex];
|
|
157
|
+
const stokSaatIni = variant.stock;
|
|
158
|
+
|
|
159
|
+
// 2. Validasi stok sebelum dipotong
|
|
160
|
+
if (stokSaatIni < qtyBeli) {
|
|
161
|
+
throw new Error(`Stok tidak mencukupi! Tersedia: ${stokSaatIni}, Diminta: ${qtyBeli}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 3. Kalkulasi data baru
|
|
165
|
+
const updatedVariants = [...productData.variants];
|
|
166
|
+
updatedVariants[variantIndex] = {
|
|
167
|
+
...variant,
|
|
168
|
+
stock: stokSaatIni - qtyBeli
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// 4. Tulis perubahan di dalam transaksi (Write)
|
|
172
|
+
transaction.update(productRef, {
|
|
173
|
+
variants: updatedVariants,
|
|
174
|
+
updatedAt: new Date().toISOString()
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return { success: true, sisaStok: stokSaatIni - qtyBeli };
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
console.log("Transaksi Berhasil!", hasil);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error("Transaksi Gagal / Conflict:", error.message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
130
187
|
---
|
|
131
188
|
|
|
132
189
|
### 3. Autentikasi Pengguna & OTP Tanpa Sandi
|
|
@@ -185,6 +242,51 @@ const uploadImage = async (fileBlob) => {
|
|
|
185
242
|
|
|
186
243
|
---
|
|
187
244
|
|
|
245
|
+
### 5. Keamanan Tipe TypeScript (Generics) & Kueri Lanjutan
|
|
246
|
+
|
|
247
|
+
#### Dukungan TypeScript Generics
|
|
248
|
+
Kini Anda bisa mendefinisikan tipe data struktur dokumen Anda agar penulisan dan pembacaan data sepenuhnya aman (Type-safe).
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { collection, doc, getDoc, getDocs } from 'nexabase-console';
|
|
252
|
+
|
|
253
|
+
interface Product {
|
|
254
|
+
name: string;
|
|
255
|
+
price: number;
|
|
256
|
+
stock: number;
|
|
257
|
+
tags: string[];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 1. Definisikan tipe koleksi
|
|
261
|
+
const productsCol = collection<Product>(db, 'products');
|
|
262
|
+
|
|
263
|
+
// 2. Definisikan tipe referensi dokumen
|
|
264
|
+
const productRef = doc<Product>(db, 'products', 'prod_a');
|
|
265
|
+
|
|
266
|
+
// 3. Baca data dengan Autocomplete & Type Checking penuh
|
|
267
|
+
const snapshot = await getDoc(productRef);
|
|
268
|
+
if (snapshot.exists()) {
|
|
269
|
+
const data = snapshot.data(); // Tipe data otomatis terdeteksi sebagai 'Product'
|
|
270
|
+
console.log(data.name); // Aman! Autocomplete tersedia
|
|
271
|
+
console.log(data.price); // Aman!
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### Kompatibilitas QuerySnapshot (`docChanges()`)
|
|
276
|
+
Bagi Anda yang bermigrasi dari Firebase, objek `QuerySnapshot` yang dikembalikan oleh `getDocs` atau `onSnapshot` kini mendukung metode `.docChanges()` secara native:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const snapshot = await getDocs(productsCol);
|
|
280
|
+
const changes = snapshot.docChanges(); // Mengembalikan array perubahan dokumen
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### Filter Offline Lengkap (Advanced Querying Operators)
|
|
284
|
+
Mekanisme kueri offline pada SDK NexaBase kini mendukung operator Firestore tingkat lanjut secara penuh demi keandalan luar biasa bahkan saat koneksi terputus:
|
|
285
|
+
- Operator dasar: `==`, `!=`, `>`, `<`, `>=`, `<=`
|
|
286
|
+
- Operator array & keanggotaan: `in`, `not-in`, `array-contains`, `array-contains-any`
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
188
290
|
## 🗄️ Dukungan Sinkronisasi Offline (IndexedDB)
|
|
189
291
|
|
|
190
292
|
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
|
+
}, 800); // Debounce to batch rapid changes (e.g. 500 inserts)
|
|
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