nexabase-console 1.1.4 → 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.
- package/README.md +33 -8
- package/dist/app/NexaApp.d.ts +63 -0
- package/dist/app/NexaApp.js +184 -0
- package/dist/app/initializeApp.d.ts +8 -0
- package/dist/app/initializeApp.js +27 -0
- package/dist/auth/Auth.d.ts +21 -0
- package/dist/auth/Auth.js +73 -0
- package/dist/auth/authTypes.d.ts +1 -0
- package/dist/auth/authTypes.js +2 -0
- package/dist/auth/persistence.d.ts +9 -0
- package/dist/auth/persistence.js +34 -0
- package/dist/errors/NexaError.d.ts +7 -0
- package/dist/errors/NexaError.js +61 -0
- package/dist/firestore/CollectionReference.d.ts +3 -0
- package/dist/firestore/CollectionReference.js +7 -0
- package/dist/firestore/DocumentReference.d.ts +3 -0
- package/dist/firestore/DocumentReference.js +22 -0
- package/dist/firestore/FieldPath.d.ts +7 -0
- package/dist/firestore/FieldPath.js +23 -0
- package/dist/firestore/FieldValue.d.ts +11 -0
- package/dist/firestore/FieldValue.js +21 -0
- package/dist/firestore/Firestore.d.ts +8 -0
- package/dist/firestore/Firestore.js +24 -0
- package/dist/firestore/GeoPoint.d.ts +10 -0
- package/dist/firestore/GeoPoint.js +22 -0
- package/dist/firestore/Query.d.ts +4 -0
- package/dist/firestore/Query.js +172 -0
- package/dist/firestore/Snapshot.d.ts +6 -0
- package/dist/firestore/Snapshot.js +126 -0
- package/dist/firestore/SnapshotManager.d.ts +9 -0
- package/dist/firestore/SnapshotManager.js +37 -0
- package/dist/firestore/Timestamp.d.ts +16 -0
- package/dist/firestore/Timestamp.js +36 -0
- package/dist/firestore/batch.d.ts +3 -0
- package/dist/firestore/batch.js +79 -0
- package/dist/firestore/queryConstraints.d.ts +9 -0
- package/dist/firestore/queryConstraints.js +31 -0
- package/dist/firestore/transaction.d.ts +5 -0
- package/dist/firestore/transaction.js +71 -0
- package/dist/firestore/writes.d.ts +15 -0
- package/dist/firestore/writes.js +146 -0
- package/dist/index.d.ts +34 -206
- package/dist/index.js +56 -1022
- package/dist/persistence/IndexedDB.d.ts +8 -0
- package/dist/persistence/IndexedDB.js +37 -0
- package/dist/persistence/LocalStore.d.ts +12 -0
- package/dist/persistence/LocalStore.js +78 -0
- package/dist/persistence/MutationQueue.d.ts +22 -0
- package/dist/persistence/MutationQueue.js +145 -0
- package/dist/realtime/RealtimeConnection.d.ts +7 -0
- package/dist/realtime/RealtimeConnection.js +19 -0
- package/dist/realtime/RealtimeDatabase.d.ts +10 -0
- package/dist/realtime/RealtimeDatabase.js +62 -0
- package/dist/storage/Storage.d.ts +12 -0
- package/dist/storage/Storage.js +41 -0
- package/dist/storage/StorageReference.d.ts +13 -0
- package/dist/storage/StorageReference.js +20 -0
- package/dist/storage/UploadTask.d.ts +10 -0
- package/dist/storage/UploadTask.js +22 -0
- package/dist/sync/ConflictResolver.d.ts +3 -0
- package/dist/sync/ConflictResolver.js +24 -0
- package/dist/sync/SyncEngine.d.ts +13 -0
- package/dist/sync/SyncEngine.js +47 -0
- package/dist/transport/HttpClient.d.ts +8 -0
- package/dist/transport/HttpClient.js +42 -0
- package/dist/transport/SSEClient.d.ts +15 -0
- package/dist/transport/SSEClient.js +96 -0
- package/dist/types/index.d.ts +150 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/helpers.d.ts +7 -0
- package/dist/utils/helpers.js +124 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ SDK Client Resmi untuk **NexaBase**: Platform sinkronisasi database modern yang
|
|
|
6
6
|
|
|
7
7
|
## 🚀 Fitur Utama
|
|
8
8
|
|
|
9
|
-
- **⚡ Real-Time Synchronization**: Sinkronisasi data real-time dengan latency rendah menggunakan Server-Sent Events (SSE).
|
|
9
|
+
- **⚡ Real-Time Synchronization & Anti-Flicker Engine**: Sinkronisasi data real-time dengan latency rendah menggunakan Server-Sent Events (SSE) yang dilengkapi *Deep Comparison Data Hashing* untuk mencegah re-render/flicker komponen UI saat sync.
|
|
10
|
+
- **🚀 Latency Compensation (Optimistic UI & 0ms Local Render)**: Penulisan data (`setDoc`, `updateDoc`, `deleteDoc`, `writeBatch`) langsung memperbarui cache lokal IndexedDB dan memicu `onSnapshot` secara instan (0ms) sebelum konfirmasi jaringan selesai.
|
|
11
|
+
- **🔄 Delta Updates (`docChanges()`)**: Dukungan native pelacakan perubahan inkremental (`added`, `modified`, `removed`) pada `QuerySnapshot` untuk manipulasi elemen UI secara presisi tanpa mengunduh ulang seluruh dataset.
|
|
10
12
|
- **🔒 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.
|
|
11
13
|
- **📦 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.
|
|
12
14
|
- **🔥 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.
|
|
@@ -49,6 +51,14 @@ Untuk mendukung arsitektur server-side proxy atau direct API key yang sangat fle
|
|
|
49
51
|
|
|
50
52
|
Hal ini mencegah kegagalan otentikasi (Error `403 Forbidden: Please login`) saat memanggil metode di server menggunakan API Key proyek.
|
|
51
53
|
|
|
54
|
+
#### ⚡ Sinkronisasi Offline & Database-Priority Engine (IndexedDB Cache)
|
|
55
|
+
NexaBase SDK mengimplementasikan manajemen cache tangguh yang serupa dengan Firebase untuk menjamin keandalan aplikasi saat jaringan tidak stabil:
|
|
56
|
+
1. **Prioritas Database Utama (`network-first` secara default)**: SDK memprioritaskan pengambilan data dari database remote server untuk menjamin kesegaran data (*data freshness*).
|
|
57
|
+
2. **Fast-Fail Network Timeout**: Setiap permintaan database remote memiliki batas waktu (*timeout*) selama 6 detik. Jika jaringan sangat lambat (*lie-fi*) atau offline, SDK akan gagal cepat dan mengalihkan pembacaan ke **IndexedDB local cache** secara transparan tanpa menghentikan aplikasi.
|
|
58
|
+
3. **Pembaruan Optimistik Instan**: Mutasi data (`setDoc`, `patchDoc`, `deleteDoc`, `writeBatch`) langsung ditulis ke IndexedDB secara optimistik. Semua listener snapshot (`onSnapshot`) akan menerima perubahan secara instan agar UI terasa sangat responsif (*snappy*).
|
|
59
|
+
4. **Heartbeat Polling Outbox (Setiap 10 Detik)**: Di samping mendengarkan event transisi jaringan `online`, SDK menjalankan polling latar belakang setiap 10 detik. Jika koneksi terdeteksi pulih, SDK akan mem-flush antrean mutasi offline secara berurutan (FIFO).
|
|
60
|
+
5. **Deduplikasi & Proteksi Kemacetan Antrean**: SDK menggunakan kunci idempotensi (`X-Idempotency-Key`) untuk mencegah tulisan ganda. Selain itu, jika server membalas dengan kesalahan definitif sisi klien (seperti `400 Bad Request` atau `403 Forbidden`), SDK akan secara otomatis membuang job cacat tersebut dari antrean untuk mencegah antrean macet selamanya.
|
|
61
|
+
|
|
52
62
|
---
|
|
53
63
|
|
|
54
64
|
### 2. Firestore-like Modular API (NoSQL)
|
|
@@ -100,16 +110,31 @@ const readData = async () => {
|
|
|
100
110
|
};
|
|
101
111
|
```
|
|
102
112
|
|
|
103
|
-
#### Mendengarkan Perubahan Data secara Real-Time (onSnapshot)
|
|
113
|
+
#### Mendengarkan Perubahan Data secara Real-Time (onSnapshot & docChanges)
|
|
104
114
|
```javascript
|
|
105
|
-
import {
|
|
115
|
+
import { collection, onSnapshot } from 'nexabase-console';
|
|
106
116
|
|
|
107
|
-
const
|
|
108
|
-
const
|
|
117
|
+
const listenLiveChats = () => {
|
|
118
|
+
const chatsRef = collection(db, 'chats');
|
|
109
119
|
|
|
110
|
-
// onSnapshot
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
// onSnapshot menggunakan Anti-Flicker Engine (Deep Comparison)
|
|
121
|
+
// - Langsung memancarkan cache lokal secara instan (0ms) tanpa layar putih
|
|
122
|
+
// - Membandingkan hash data server sebelum re-render agar UI tidak berkedip
|
|
123
|
+
const unsubscribe = onSnapshot(chatsRef, (querySnapshot) => {
|
|
124
|
+
// 1. Ambil rincian pembaruan inkremental (Delta/Diff Updates)
|
|
125
|
+
if (querySnapshot.docChanges) {
|
|
126
|
+
const changes = querySnapshot.docChanges();
|
|
127
|
+
changes.forEach((change) => {
|
|
128
|
+
if (change.type === 'added') console.log("Pesan baru:", change.doc.data());
|
|
129
|
+
if (change.type === 'modified') console.log("Pesan diedit:", change.doc.data());
|
|
130
|
+
if (change.type === 'removed') console.log("Pesan dihapus:", change.doc.id);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 2. Iterasi seluruh koleksi
|
|
135
|
+
querySnapshot.forEach((doc) => {
|
|
136
|
+
console.log(doc.id, "=>", doc.data());
|
|
137
|
+
});
|
|
113
138
|
});
|
|
114
139
|
|
|
115
140
|
// Panggil unsubscribe() untuk menghentikan pemantauan realtime
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { NexaConfig, User, AuthResponse, DatabaseReference, StorageReference, UploadOptions, OfflineJob } from '../types/index';
|
|
3
|
+
import { HttpClient } from '../transport/HttpClient';
|
|
4
|
+
import { SSEClient } from '../transport/SSEClient';
|
|
5
|
+
import { Auth } from '../auth/Auth';
|
|
6
|
+
import { IndexedDB } from '../persistence/IndexedDB';
|
|
7
|
+
import { LocalStore } from '../persistence/LocalStore';
|
|
8
|
+
import { MutationQueue } from '../persistence/MutationQueue';
|
|
9
|
+
import { SyncEngine } from '../sync/SyncEngine';
|
|
10
|
+
import { RealtimeDatabase } from '../realtime/RealtimeDatabase';
|
|
11
|
+
import { Storage } from '../storage/Storage';
|
|
12
|
+
export declare class NexaApp {
|
|
13
|
+
projectId: string;
|
|
14
|
+
token: string | null;
|
|
15
|
+
endpoint: string;
|
|
16
|
+
enablePersistence: boolean;
|
|
17
|
+
cacheStrategy: 'network-first' | 'cache-first';
|
|
18
|
+
httpClient: HttpClient;
|
|
19
|
+
client: AxiosInstance;
|
|
20
|
+
sseClient: SSEClient;
|
|
21
|
+
authService: Auth;
|
|
22
|
+
indexedDB: IndexedDB;
|
|
23
|
+
localStore: LocalStore;
|
|
24
|
+
mutationQueue: MutationQueue;
|
|
25
|
+
syncEngine: SyncEngine;
|
|
26
|
+
realtimeDatabase: RealtimeDatabase;
|
|
27
|
+
storage: Storage;
|
|
28
|
+
_snapshotCallbacks: Record<string, (payload?: any) => void>;
|
|
29
|
+
_firestoreListening: boolean;
|
|
30
|
+
_firestoreUnsubscribes: (() => void)[];
|
|
31
|
+
_initPromise: Promise<void> | null;
|
|
32
|
+
constructor(config: NexaConfig);
|
|
33
|
+
get _firestoreCache(): Record<string, any>;
|
|
34
|
+
private _setupCrossTabSync;
|
|
35
|
+
_broadcastLocalMutation(path: string, action: string, data?: any): void;
|
|
36
|
+
hasPendingWrites(path: string): boolean;
|
|
37
|
+
markInflight(path: string): void;
|
|
38
|
+
unmarkInflight(path: string): void;
|
|
39
|
+
_initFirestoreState(): Promise<void>;
|
|
40
|
+
_setCache(path: string, data: any): Promise<void>;
|
|
41
|
+
_deleteCache(path: string): Promise<void>;
|
|
42
|
+
_addOfflineJob(job: OfflineJob): Promise<void>;
|
|
43
|
+
_syncOfflineQueue(): Promise<void>;
|
|
44
|
+
_ensureFirestoreSSE(): void;
|
|
45
|
+
_closeFirestoreSSEIfIdle(): void;
|
|
46
|
+
_notifySnapshotCallbacks(path: string, actionType: string, payload: any): void;
|
|
47
|
+
signInWithEmailAndPassword(email: string, password: string): Promise<AuthResponse>;
|
|
48
|
+
createUserWithEmailAndPassword(email: string, password: string, name: string): Promise<User>;
|
|
49
|
+
sendOtp(email: string): Promise<{
|
|
50
|
+
success: boolean;
|
|
51
|
+
message: string;
|
|
52
|
+
}>;
|
|
53
|
+
signInWithOtp(email: string, otp: string, name?: string): Promise<AuthResponse>;
|
|
54
|
+
signOut(): void;
|
|
55
|
+
getCurrentUser(): User | null;
|
|
56
|
+
ref(path: string): DatabaseReference;
|
|
57
|
+
storageRef(path: string): StorageReference;
|
|
58
|
+
uploadFile(path: string, file: File | Blob, options?: UploadOptions): Promise<{
|
|
59
|
+
url: string;
|
|
60
|
+
path: string;
|
|
61
|
+
}>;
|
|
62
|
+
getDownloadURL(path: string): Promise<string>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NexaApp = void 0;
|
|
4
|
+
const HttpClient_1 = require("../transport/HttpClient");
|
|
5
|
+
const SSEClient_1 = require("../transport/SSEClient");
|
|
6
|
+
const Auth_1 = require("../auth/Auth");
|
|
7
|
+
const IndexedDB_1 = require("../persistence/IndexedDB");
|
|
8
|
+
const LocalStore_1 = require("../persistence/LocalStore");
|
|
9
|
+
const MutationQueue_1 = require("../persistence/MutationQueue");
|
|
10
|
+
const SyncEngine_1 = require("../sync/SyncEngine");
|
|
11
|
+
const RealtimeDatabase_1 = require("../realtime/RealtimeDatabase");
|
|
12
|
+
const RealtimeConnection_1 = require("../realtime/RealtimeConnection");
|
|
13
|
+
const Storage_1 = require("../storage/Storage");
|
|
14
|
+
class NexaApp {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.token = null;
|
|
17
|
+
// Internal Firestore caches/callbacks
|
|
18
|
+
this._snapshotCallbacks = {};
|
|
19
|
+
this._firestoreListening = false;
|
|
20
|
+
this._firestoreUnsubscribes = [];
|
|
21
|
+
this._initPromise = null;
|
|
22
|
+
this.projectId = config.projectId;
|
|
23
|
+
this.token = config.apiKey || null;
|
|
24
|
+
this.enablePersistence = config.enablePersistence !== false;
|
|
25
|
+
this.cacheStrategy = config.cacheStrategy || 'network-first';
|
|
26
|
+
let defaultEndpoint = 'https://db.nexabase.id';
|
|
27
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
28
|
+
defaultEndpoint = window.location.origin;
|
|
29
|
+
}
|
|
30
|
+
this.endpoint = config.endpoint || defaultEndpoint;
|
|
31
|
+
// Transport
|
|
32
|
+
this.httpClient = new HttpClient_1.HttpClient(this.endpoint, () => this.authService ? this.authService.getToken() : this.token);
|
|
33
|
+
this.client = this.httpClient.getAxiosInstance();
|
|
34
|
+
this.sseClient = new SSEClient_1.SSEClient(this.projectId, this.endpoint);
|
|
35
|
+
// Auth & Persistence
|
|
36
|
+
this.authService = new Auth_1.Auth(this.projectId, this.client, this.token);
|
|
37
|
+
this.token = this.authService.getToken();
|
|
38
|
+
this.indexedDB = new IndexedDB_1.IndexedDB(`nexabase_${this.projectId}`);
|
|
39
|
+
this.localStore = new LocalStore_1.LocalStore(this.indexedDB);
|
|
40
|
+
this.mutationQueue = new MutationQueue_1.MutationQueue(this.projectId, this.indexedDB, this.client);
|
|
41
|
+
this.syncEngine = new SyncEngine_1.SyncEngine(this.projectId, this.mutationQueue, this.sseClient);
|
|
42
|
+
// Realtime & Storage
|
|
43
|
+
const realtimeConn = new RealtimeConnection_1.RealtimeConnection(this.sseClient);
|
|
44
|
+
this.realtimeDatabase = new RealtimeDatabase_1.RealtimeDatabase(this.projectId, this.client, realtimeConn);
|
|
45
|
+
this.storage = new Storage_1.Storage(this.projectId, this.client);
|
|
46
|
+
this._setupCrossTabSync();
|
|
47
|
+
}
|
|
48
|
+
get _firestoreCache() {
|
|
49
|
+
return this.localStore.getMemoryCache();
|
|
50
|
+
}
|
|
51
|
+
_setupCrossTabSync() {
|
|
52
|
+
this.syncEngine.listenCrossTab((path, action, data) => {
|
|
53
|
+
if (action === 'delete') {
|
|
54
|
+
this.localStore.delete(path);
|
|
55
|
+
}
|
|
56
|
+
else if (data) {
|
|
57
|
+
this.localStore.set(path, data);
|
|
58
|
+
}
|
|
59
|
+
this._notifySnapshotCallbacks(path, action === 'delete' ? 'document_deleted' : 'document_written', data);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
_broadcastLocalMutation(path, action, data) {
|
|
63
|
+
this.syncEngine.broadcastLocalMutation(path, action, data);
|
|
64
|
+
}
|
|
65
|
+
hasPendingWrites(path) {
|
|
66
|
+
return this.mutationQueue.hasPendingWrites(path);
|
|
67
|
+
}
|
|
68
|
+
markInflight(path) {
|
|
69
|
+
this.mutationQueue.markInflight(path);
|
|
70
|
+
}
|
|
71
|
+
unmarkInflight(path) {
|
|
72
|
+
this.mutationQueue.unmarkInflight(path);
|
|
73
|
+
}
|
|
74
|
+
async _initFirestoreState() {
|
|
75
|
+
if (!this.enablePersistence)
|
|
76
|
+
return;
|
|
77
|
+
try {
|
|
78
|
+
await this.indexedDB.init();
|
|
79
|
+
await this.localStore.loadAll();
|
|
80
|
+
await this.mutationQueue.init();
|
|
81
|
+
if (typeof window !== 'undefined') {
|
|
82
|
+
window.addEventListener('online', () => this._syncOfflineQueue());
|
|
83
|
+
}
|
|
84
|
+
this._syncOfflineQueue();
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
console.warn('[NexaApp] Failed to initialize IndexedDB offline cache:', err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async _setCache(path, data) {
|
|
91
|
+
await this.localStore.set(path, data);
|
|
92
|
+
}
|
|
93
|
+
async _deleteCache(path) {
|
|
94
|
+
await this.localStore.delete(path);
|
|
95
|
+
}
|
|
96
|
+
async _addOfflineJob(job) {
|
|
97
|
+
await this.mutationQueue.addJob(job);
|
|
98
|
+
}
|
|
99
|
+
async _syncOfflineQueue() {
|
|
100
|
+
await this.syncEngine.syncOfflineMutations();
|
|
101
|
+
}
|
|
102
|
+
_ensureFirestoreSSE() {
|
|
103
|
+
if (this._firestoreListening)
|
|
104
|
+
return;
|
|
105
|
+
this._firestoreListening = true;
|
|
106
|
+
const unsub1 = this.sseClient.addListener('data_changed', (payload) => {
|
|
107
|
+
if (payload && payload.path) {
|
|
108
|
+
if (payload.action === 'delete') {
|
|
109
|
+
this._deleteCache(payload.path);
|
|
110
|
+
}
|
|
111
|
+
else if (payload.data) {
|
|
112
|
+
this._setCache(payload.path, payload.data);
|
|
113
|
+
}
|
|
114
|
+
this._notifySnapshotCallbacks(payload.path, payload.action === 'delete' ? 'document_deleted' : 'document_written', payload.data);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const unsub2 = this.sseClient.addListener('firestore_changed', (payload) => {
|
|
118
|
+
this._notifySnapshotCallbacks(payload.collectionPath || payload.docPath, 'server_update', payload.data);
|
|
119
|
+
});
|
|
120
|
+
this._firestoreUnsubscribes.push(unsub1, unsub2);
|
|
121
|
+
}
|
|
122
|
+
_closeFirestoreSSEIfIdle() {
|
|
123
|
+
if (Object.keys(this._snapshotCallbacks).length === 0) {
|
|
124
|
+
this._firestoreUnsubscribes.forEach((unsub) => unsub());
|
|
125
|
+
this._firestoreUnsubscribes = [];
|
|
126
|
+
this._firestoreListening = false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
_notifySnapshotCallbacks(path, actionType, payload) {
|
|
130
|
+
Object.keys(this._snapshotCallbacks).forEach((cbKey) => {
|
|
131
|
+
const parts = cbKey.split('_');
|
|
132
|
+
const targetPath = parts.slice(2).join('_');
|
|
133
|
+
if (cbKey.startsWith('document_') && targetPath === path) {
|
|
134
|
+
this._snapshotCallbacks[cbKey](payload);
|
|
135
|
+
}
|
|
136
|
+
else if ((cbKey.startsWith('collection_') || cbKey.startsWith('query_')) && (path.startsWith(targetPath + '/') || path === targetPath)) {
|
|
137
|
+
this._snapshotCallbacks[cbKey](payload);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Auth Shortcuts
|
|
142
|
+
async signInWithEmailAndPassword(email, password) {
|
|
143
|
+
const res = await this.authService.signInWithEmailAndPassword(email, password);
|
|
144
|
+
this.token = this.authService.getToken();
|
|
145
|
+
return res;
|
|
146
|
+
}
|
|
147
|
+
async createUserWithEmailAndPassword(email, password, name) {
|
|
148
|
+
return this.authService.createUserWithEmailAndPassword(email, password, name);
|
|
149
|
+
}
|
|
150
|
+
async sendOtp(email) {
|
|
151
|
+
return this.authService.sendOtp(email);
|
|
152
|
+
}
|
|
153
|
+
async signInWithOtp(email, otp, name) {
|
|
154
|
+
const res = await this.authService.signInWithOtp(email, otp, name);
|
|
155
|
+
this.token = this.authService.getToken();
|
|
156
|
+
return res;
|
|
157
|
+
}
|
|
158
|
+
signOut() {
|
|
159
|
+
this.authService.signOut();
|
|
160
|
+
this.token = null;
|
|
161
|
+
}
|
|
162
|
+
getCurrentUser() {
|
|
163
|
+
return this.authService.getCurrentUser();
|
|
164
|
+
}
|
|
165
|
+
// Realtime Database Shortcut
|
|
166
|
+
ref(path) {
|
|
167
|
+
return this.realtimeDatabase.ref(path);
|
|
168
|
+
}
|
|
169
|
+
// Storage Shortcuts
|
|
170
|
+
storageRef(path) {
|
|
171
|
+
return {
|
|
172
|
+
path,
|
|
173
|
+
upload: (file, options) => this.storage.uploadFile(path, file, options),
|
|
174
|
+
getDownloadURL: () => this.storage.getDownloadURL(path)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async uploadFile(path, file, options) {
|
|
178
|
+
return this.storage.uploadFile(path, file, options);
|
|
179
|
+
}
|
|
180
|
+
async getDownloadURL(path) {
|
|
181
|
+
return this.storage.getDownloadURL(path);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.NexaApp = NexaApp;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NexaApp } from './NexaApp';
|
|
2
|
+
import { NexaConfig } from '../types/index';
|
|
3
|
+
export declare const NexaBase: {
|
|
4
|
+
init: (config: NexaConfig) => NexaApp;
|
|
5
|
+
initializeApp: (config: NexaConfig) => NexaApp;
|
|
6
|
+
};
|
|
7
|
+
export declare const initializeApp: (config: NexaConfig) => NexaApp;
|
|
8
|
+
export declare const getFirestore: (app: NexaApp) => NexaApp;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getFirestore = exports.initializeApp = exports.NexaBase = void 0;
|
|
4
|
+
const NexaApp_1 = require("./NexaApp");
|
|
5
|
+
exports.NexaBase = {
|
|
6
|
+
init: (config) => {
|
|
7
|
+
const app = new NexaApp_1.NexaApp(config);
|
|
8
|
+
if (app.enablePersistence) {
|
|
9
|
+
app._initPromise = app._initFirestoreState();
|
|
10
|
+
}
|
|
11
|
+
return app;
|
|
12
|
+
},
|
|
13
|
+
initializeApp: (config) => {
|
|
14
|
+
return exports.NexaBase.init(config);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const initializeApp = (config) => {
|
|
18
|
+
return exports.NexaBase.init(config);
|
|
19
|
+
};
|
|
20
|
+
exports.initializeApp = initializeApp;
|
|
21
|
+
const getFirestore = (app) => {
|
|
22
|
+
if (app.enablePersistence) {
|
|
23
|
+
app._initPromise = app._initFirestoreState();
|
|
24
|
+
}
|
|
25
|
+
return app;
|
|
26
|
+
};
|
|
27
|
+
exports.getFirestore = getFirestore;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { User, AuthResponse } from './authTypes';
|
|
3
|
+
export declare class Auth {
|
|
4
|
+
private projectId;
|
|
5
|
+
private client;
|
|
6
|
+
private token;
|
|
7
|
+
private persistence;
|
|
8
|
+
constructor(projectId: string, client: AxiosInstance, initialToken: string | null);
|
|
9
|
+
getToken(): string | null;
|
|
10
|
+
setToken(token: string | null): void;
|
|
11
|
+
signInWithEmailAndPassword(email: string, password: string): Promise<AuthResponse>;
|
|
12
|
+
createUserWithEmailAndPassword(email: string, password: string, name: string): Promise<User>;
|
|
13
|
+
sendOtp(email: string): Promise<{
|
|
14
|
+
success: boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
}>;
|
|
17
|
+
signInWithOtp(email: string, otp: string, name?: string): Promise<AuthResponse>;
|
|
18
|
+
signOut(): void;
|
|
19
|
+
getCurrentUser(): User | null;
|
|
20
|
+
}
|
|
21
|
+
export { Auth as AuthService };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthService = exports.Auth = void 0;
|
|
4
|
+
const persistence_1 = require("./persistence");
|
|
5
|
+
const NexaError_1 = require("../errors/NexaError");
|
|
6
|
+
class Auth {
|
|
7
|
+
constructor(projectId, client, initialToken) {
|
|
8
|
+
this.token = null;
|
|
9
|
+
this.projectId = projectId;
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.persistence = new persistence_1.AuthPersistence(projectId);
|
|
12
|
+
this.token = initialToken || this.persistence.getSavedToken();
|
|
13
|
+
}
|
|
14
|
+
getToken() {
|
|
15
|
+
return this.token;
|
|
16
|
+
}
|
|
17
|
+
setToken(token) {
|
|
18
|
+
this.token = token;
|
|
19
|
+
}
|
|
20
|
+
async signInWithEmailAndPassword(email, password) {
|
|
21
|
+
try {
|
|
22
|
+
const res = await this.client.post(`/api/project/${this.projectId}/auth/login`, { email, password });
|
|
23
|
+
if (res.data.token) {
|
|
24
|
+
this.token = res.data.token;
|
|
25
|
+
this.persistence.saveSession(res.data.token, res.data.user);
|
|
26
|
+
}
|
|
27
|
+
return res.data;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
throw (0, NexaError_1.toNexaError)(err);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async createUserWithEmailAndPassword(email, password, name) {
|
|
34
|
+
try {
|
|
35
|
+
const res = await this.client.post(`/api/project/${this.projectId}/users`, { email, password, name });
|
|
36
|
+
return res.data;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
throw (0, NexaError_1.toNexaError)(err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async sendOtp(email) {
|
|
43
|
+
try {
|
|
44
|
+
const res = await this.client.post(`/api/project/${this.projectId}/auth/otp/send`, { email });
|
|
45
|
+
return res.data;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
throw (0, NexaError_1.toNexaError)(err);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async signInWithOtp(email, otp, name) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await this.client.post(`/api/project/${this.projectId}/auth/otp/login`, { email, otp, name });
|
|
54
|
+
if (res.data.token) {
|
|
55
|
+
this.token = res.data.token;
|
|
56
|
+
this.persistence.saveSession(res.data.token, res.data.user);
|
|
57
|
+
}
|
|
58
|
+
return res.data;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw (0, NexaError_1.toNexaError)(err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
signOut() {
|
|
65
|
+
this.token = null;
|
|
66
|
+
this.persistence.clearSession();
|
|
67
|
+
}
|
|
68
|
+
getCurrentUser() {
|
|
69
|
+
return this.persistence.getSavedUser();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.Auth = Auth;
|
|
73
|
+
exports.AuthService = Auth;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { User, AuthResponse } from '../types/index';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { User } from './authTypes';
|
|
2
|
+
export declare class AuthPersistence {
|
|
3
|
+
private projectId;
|
|
4
|
+
constructor(projectId: string);
|
|
5
|
+
saveSession(token: string, user: User): void;
|
|
6
|
+
clearSession(): void;
|
|
7
|
+
getSavedToken(): string | null;
|
|
8
|
+
getSavedUser(): User | null;
|
|
9
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthPersistence = void 0;
|
|
4
|
+
class AuthPersistence {
|
|
5
|
+
constructor(projectId) {
|
|
6
|
+
this.projectId = projectId;
|
|
7
|
+
}
|
|
8
|
+
saveSession(token, user) {
|
|
9
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
10
|
+
localStorage.setItem(`nexa_token_${this.projectId}`, token);
|
|
11
|
+
localStorage.setItem(`nexa_user_${this.projectId}`, JSON.stringify(user));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
clearSession() {
|
|
15
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
16
|
+
localStorage.removeItem(`nexa_token_${this.projectId}`);
|
|
17
|
+
localStorage.removeItem(`nexa_user_${this.projectId}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
getSavedToken() {
|
|
21
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
22
|
+
return localStorage.getItem(`nexa_token_${this.projectId}`);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
getSavedUser() {
|
|
27
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
28
|
+
const userStr = localStorage.getItem(`nexa_user_${this.projectId}`);
|
|
29
|
+
return userStr ? JSON.parse(userStr) : null;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.AuthPersistence = AuthPersistence;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type NexaErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated';
|
|
2
|
+
export declare class NexaError extends Error {
|
|
3
|
+
readonly code: NexaErrorCode;
|
|
4
|
+
readonly details?: unknown;
|
|
5
|
+
constructor(code: NexaErrorCode, message: string, details?: unknown);
|
|
6
|
+
}
|
|
7
|
+
export declare function toNexaError(err: unknown): NexaError;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NexaError = void 0;
|
|
4
|
+
exports.toNexaError = toNexaError;
|
|
5
|
+
class NexaError extends Error {
|
|
6
|
+
constructor(code, message, details) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'NexaError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.details = details;
|
|
11
|
+
// Maintain proper stack trace in V8 / Node / modern browsers
|
|
12
|
+
if (typeof Error.captureStackTrace === 'function') {
|
|
13
|
+
Error.captureStackTrace(this, NexaError);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.NexaError = NexaError;
|
|
18
|
+
function toNexaError(err) {
|
|
19
|
+
if (err instanceof NexaError) {
|
|
20
|
+
return err;
|
|
21
|
+
}
|
|
22
|
+
if (err && typeof err === 'object') {
|
|
23
|
+
const e = err;
|
|
24
|
+
if (e.response) {
|
|
25
|
+
const status = e.response.status;
|
|
26
|
+
const message = e.response.data?.message || e.response.data?.error || e.message || `HTTP ${status} error`;
|
|
27
|
+
const details = e.response.data;
|
|
28
|
+
switch (status) {
|
|
29
|
+
case 400:
|
|
30
|
+
return new NexaError('invalid-argument', message, details);
|
|
31
|
+
case 401:
|
|
32
|
+
return new NexaError('unauthenticated', message, details);
|
|
33
|
+
case 403:
|
|
34
|
+
return new NexaError('permission-denied', message, details);
|
|
35
|
+
case 404:
|
|
36
|
+
return new NexaError('not-found', message, details);
|
|
37
|
+
case 409:
|
|
38
|
+
return new NexaError('already-exists', message, details);
|
|
39
|
+
case 412:
|
|
40
|
+
return new NexaError('failed-precondition', message, details);
|
|
41
|
+
case 429:
|
|
42
|
+
return new NexaError('resource-exhausted', message, details);
|
|
43
|
+
case 501:
|
|
44
|
+
return new NexaError('unimplemented', message, details);
|
|
45
|
+
case 502:
|
|
46
|
+
case 503:
|
|
47
|
+
case 504:
|
|
48
|
+
return new NexaError('unavailable', message, details);
|
|
49
|
+
default:
|
|
50
|
+
return new NexaError('internal', message, details);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (e.code === 'ECONNABORTED' || e.code === 'ERR_NETWORK' || e.message?.includes('Network Error')) {
|
|
54
|
+
return new NexaError('unavailable', 'The service is currently unavailable or network is offline.', e);
|
|
55
|
+
}
|
|
56
|
+
if (e instanceof Error) {
|
|
57
|
+
return new NexaError('unknown', e.message, e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return new NexaError('unknown', String(err));
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doc = void 0;
|
|
4
|
+
const doc = (dbOrCollection, ...pathSegments) => {
|
|
5
|
+
let docRef;
|
|
6
|
+
if ('type' in dbOrCollection && dbOrCollection.type === 'collection') {
|
|
7
|
+
docRef = {
|
|
8
|
+
type: 'document',
|
|
9
|
+
path: `${dbOrCollection.path}/${pathSegments.join('/')}`,
|
|
10
|
+
db: dbOrCollection.db
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
docRef = {
|
|
15
|
+
type: 'document',
|
|
16
|
+
path: pathSegments.join('/'),
|
|
17
|
+
db: dbOrCollection
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return docRef;
|
|
21
|
+
};
|
|
22
|
+
exports.doc = doc;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FieldPath = void 0;
|
|
4
|
+
class FieldPath {
|
|
5
|
+
constructor(...fieldNames) {
|
|
6
|
+
if (fieldNames.length === 0) {
|
|
7
|
+
throw new Error('FieldPath must contain at least one field name');
|
|
8
|
+
}
|
|
9
|
+
this.segments = fieldNames;
|
|
10
|
+
}
|
|
11
|
+
static documentId() {
|
|
12
|
+
return new FieldPath('__name__');
|
|
13
|
+
}
|
|
14
|
+
getPathString() {
|
|
15
|
+
return this.segments.join('.');
|
|
16
|
+
}
|
|
17
|
+
isEqual(other) {
|
|
18
|
+
if (this.segments.length !== other.segments.length)
|
|
19
|
+
return false;
|
|
20
|
+
return this.segments.every((seg, idx) => seg === other.segments[idx]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.FieldPath = FieldPath;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class FieldValue {
|
|
2
|
+
method: string;
|
|
3
|
+
value?: any | undefined;
|
|
4
|
+
_isFieldValue: boolean;
|
|
5
|
+
constructor(method: string, value?: any | undefined);
|
|
6
|
+
}
|
|
7
|
+
export declare const serverTimestamp: () => FieldValue;
|
|
8
|
+
export declare const increment: (n: number) => FieldValue;
|
|
9
|
+
export declare const arrayUnion: (...elements: any[]) => FieldValue;
|
|
10
|
+
export declare const arrayRemove: (...elements: any[]) => FieldValue;
|
|
11
|
+
export declare const deleteField: () => FieldValue;
|