nexabase-console 2.0.2 → 2.0.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.
- package/README.md +34 -0
- package/dist/app/NexaApp.js +1 -1
- package/dist/auth/Auth.js +29 -28
- package/dist/firestore/Query.d.ts +9 -1
- package/dist/firestore/Query.js +62 -1
- package/dist/transport/HttpClient.d.ts +3 -1
- package/dist/transport/HttpClient.js +12 -2
- package/dist/types/index.d.ts +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,6 +142,40 @@ const listenLiveChats = () => {
|
|
|
142
142
|
};
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
#### Agregasi Data & Hitung Dokumen (`getCountFromServer` / `getAggregateFromServer`)
|
|
146
|
+
Mendukung fungsi agregasi server 1:1 seperti Firebase SDK: `getCountFromServer()`, `getAggregateFromServer()`, `count()`, `sum()`, dan `average()`.
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
import {
|
|
150
|
+
collection,
|
|
151
|
+
query,
|
|
152
|
+
where,
|
|
153
|
+
getCountFromServer,
|
|
154
|
+
getAggregateFromServer,
|
|
155
|
+
count,
|
|
156
|
+
sum,
|
|
157
|
+
average
|
|
158
|
+
} from 'nexabase-console';
|
|
159
|
+
|
|
160
|
+
// 1. Menghitung total dokumen (getCountFromServer)
|
|
161
|
+
const productsRef = collection(db, 'products');
|
|
162
|
+
const qAvailable = query(productsRef, where('status', '==', 'active'));
|
|
163
|
+
|
|
164
|
+
const countSnapshot = await getCountFromServer(qAvailable);
|
|
165
|
+
console.log("Total produk aktif:", countSnapshot.data().count);
|
|
166
|
+
|
|
167
|
+
// 2. Agregasi multi-field (getAggregateFromServer)
|
|
168
|
+
const orderStats = await getAggregateFromServer(collection(db, 'orders'), {
|
|
169
|
+
totalOrders: count(),
|
|
170
|
+
totalRevenue: sum('totalAmount'),
|
|
171
|
+
avgOrderValue: average('totalAmount')
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
console.log("Jumlah order:", orderStats.data().totalOrders);
|
|
175
|
+
console.log("Total pendapatan:", orderStats.data().totalRevenue);
|
|
176
|
+
console.log("Rata-rata transaksi:", orderStats.data().avgOrderValue);
|
|
177
|
+
```
|
|
178
|
+
|
|
145
179
|
#### Menggunakan Atomic Write Batch (Untuk Upload File / Excel Massal)
|
|
146
180
|
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).
|
|
147
181
|
|
package/dist/app/NexaApp.js
CHANGED
|
@@ -29,7 +29,7 @@ class NexaApp {
|
|
|
29
29
|
}
|
|
30
30
|
this.endpoint = config.endpoint || defaultEndpoint;
|
|
31
31
|
// Transport
|
|
32
|
-
this.httpClient = new HttpClient_1.HttpClient(this.endpoint, () => this.authService ? this.authService.getToken() : this.token);
|
|
32
|
+
this.httpClient = new HttpClient_1.HttpClient(this.endpoint, () => (this.authService ? this.authService.getToken() : this.token), this.projectId, this.token);
|
|
33
33
|
this.client = this.httpClient.getAxiosInstance();
|
|
34
34
|
this.sseClient = new SSEClient_1.SSEClient(this.projectId, this.endpoint);
|
|
35
35
|
// Auth & Persistence
|
package/dist/auth/Auth.js
CHANGED
|
@@ -165,9 +165,9 @@ class Auth {
|
|
|
165
165
|
return this.refreshPromise;
|
|
166
166
|
}
|
|
167
167
|
async refreshAccessToken(refreshToken) {
|
|
168
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
168
|
+
const endpoint = this.emulatorUrl || `/api/auth/token/refresh`;
|
|
169
169
|
try {
|
|
170
|
-
const res = await this.client.post(endpoint, { refreshToken });
|
|
170
|
+
const res = await this.client.post(endpoint, { refreshToken, projectId: this.projectId });
|
|
171
171
|
const session = res.data;
|
|
172
172
|
this.currentSession = session;
|
|
173
173
|
await this.persistence.saveSession(session);
|
|
@@ -203,8 +203,8 @@ class Auth {
|
|
|
203
203
|
// --- Email & Password Auth ---
|
|
204
204
|
async createUserWithEmailAndPassword(email, password, name) {
|
|
205
205
|
try {
|
|
206
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
207
|
-
const res = await this.client.post(endpoint, { email, password, name });
|
|
206
|
+
const endpoint = this.emulatorUrl || `/api/auth/register`;
|
|
207
|
+
const res = await this.client.post(endpoint, { email, password, name, projectId: this.projectId });
|
|
208
208
|
const session = res.data;
|
|
209
209
|
this.currentSession = session;
|
|
210
210
|
await this.persistence.saveSession(session);
|
|
@@ -218,8 +218,8 @@ class Auth {
|
|
|
218
218
|
}
|
|
219
219
|
async signInWithEmailAndPassword(email, password) {
|
|
220
220
|
try {
|
|
221
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
222
|
-
const res = await this.client.post(endpoint, { email, password });
|
|
221
|
+
const endpoint = this.emulatorUrl || `/api/auth/login`;
|
|
222
|
+
const res = await this.client.post(endpoint, { email, password, projectId: this.projectId });
|
|
223
223
|
const session = res.data;
|
|
224
224
|
this.currentSession = session;
|
|
225
225
|
await this.persistence.saveSession(session);
|
|
@@ -234,8 +234,8 @@ class Auth {
|
|
|
234
234
|
// --- OTP Auth ---
|
|
235
235
|
async sendOtp(email) {
|
|
236
236
|
try {
|
|
237
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
238
|
-
const res = await this.client.post(endpoint, { email });
|
|
237
|
+
const endpoint = this.emulatorUrl || `/api/auth/otp/send`;
|
|
238
|
+
const res = await this.client.post(endpoint, { email, projectId: this.projectId });
|
|
239
239
|
return res.data;
|
|
240
240
|
}
|
|
241
241
|
catch (err) {
|
|
@@ -244,8 +244,8 @@ class Auth {
|
|
|
244
244
|
}
|
|
245
245
|
async signInWithOtp(email, otp) {
|
|
246
246
|
try {
|
|
247
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
248
|
-
const res = await this.client.post(endpoint, { email, otp });
|
|
247
|
+
const endpoint = this.emulatorUrl || `/api/auth/otp/login`;
|
|
248
|
+
const res = await this.client.post(endpoint, { email, otp, projectId: this.projectId });
|
|
249
249
|
const session = res.data;
|
|
250
250
|
this.currentSession = session;
|
|
251
251
|
await this.persistence.saveSession(session);
|
|
@@ -260,8 +260,8 @@ class Auth {
|
|
|
260
260
|
// --- Password Reset & Verification ---
|
|
261
261
|
async sendPasswordResetEmail(email) {
|
|
262
262
|
try {
|
|
263
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
264
|
-
const res = await this.client.post(endpoint, { email });
|
|
263
|
+
const endpoint = this.emulatorUrl || `/api/auth/password-reset/send`;
|
|
264
|
+
const res = await this.client.post(endpoint, { email, projectId: this.projectId });
|
|
265
265
|
return res.data;
|
|
266
266
|
}
|
|
267
267
|
catch (err) {
|
|
@@ -270,8 +270,8 @@ class Auth {
|
|
|
270
270
|
}
|
|
271
271
|
async confirmPasswordReset(code, newPassword) {
|
|
272
272
|
try {
|
|
273
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
274
|
-
const res = await this.client.post(endpoint, { code, newPassword });
|
|
273
|
+
const endpoint = this.emulatorUrl || `/api/auth/password-reset/confirm`;
|
|
274
|
+
const res = await this.client.post(endpoint, { code, newPassword, projectId: this.projectId });
|
|
275
275
|
return res.data;
|
|
276
276
|
}
|
|
277
277
|
catch (err) {
|
|
@@ -281,8 +281,8 @@ class Auth {
|
|
|
281
281
|
async sendEmailVerification() {
|
|
282
282
|
const token = await this.getIdToken();
|
|
283
283
|
try {
|
|
284
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
285
|
-
const res = await this.client.post(endpoint, {}, {
|
|
284
|
+
const endpoint = this.emulatorUrl || `/api/auth/email/send-verification`;
|
|
285
|
+
const res = await this.client.post(endpoint, { projectId: this.projectId }, {
|
|
286
286
|
headers: { Authorization: `Bearer ${token}` }
|
|
287
287
|
});
|
|
288
288
|
return res.data;
|
|
@@ -295,8 +295,8 @@ class Auth {
|
|
|
295
295
|
async updateProfile(update) {
|
|
296
296
|
const token = await this.getIdToken();
|
|
297
297
|
try {
|
|
298
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
299
|
-
const res = await this.client.patch(endpoint, update, {
|
|
298
|
+
const endpoint = this.emulatorUrl || `/api/auth/profile`;
|
|
299
|
+
const res = await this.client.patch(endpoint, { ...update, projectId: this.projectId }, {
|
|
300
300
|
headers: { Authorization: `Bearer ${token}` }
|
|
301
301
|
});
|
|
302
302
|
const updatedUser = res.data;
|
|
@@ -314,8 +314,8 @@ class Auth {
|
|
|
314
314
|
async updatePassword(newPassword) {
|
|
315
315
|
const token = await this.getIdToken();
|
|
316
316
|
try {
|
|
317
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
318
|
-
await this.client.post(endpoint, { newPassword }, {
|
|
317
|
+
const endpoint = this.emulatorUrl || `/api/auth/password/update`;
|
|
318
|
+
await this.client.post(endpoint, { newPassword, projectId: this.projectId }, {
|
|
319
319
|
headers: { Authorization: `Bearer ${token}` }
|
|
320
320
|
});
|
|
321
321
|
}
|
|
@@ -326,9 +326,10 @@ class Auth {
|
|
|
326
326
|
async deleteUser() {
|
|
327
327
|
const token = await this.getIdToken();
|
|
328
328
|
try {
|
|
329
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
329
|
+
const endpoint = this.emulatorUrl || `/api/auth/user/delete`;
|
|
330
330
|
await this.client.delete(endpoint, {
|
|
331
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
331
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
332
|
+
data: { projectId: this.projectId }
|
|
332
333
|
});
|
|
333
334
|
await this.signOut();
|
|
334
335
|
}
|
|
@@ -340,8 +341,8 @@ class Auth {
|
|
|
340
341
|
async revokeSession(sessionId) {
|
|
341
342
|
const token = await this.getIdToken();
|
|
342
343
|
try {
|
|
343
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
344
|
-
await this.client.post(endpoint, { sessionId }, {
|
|
344
|
+
const endpoint = this.emulatorUrl || `/api/auth/session/revoke`;
|
|
345
|
+
await this.client.post(endpoint, { sessionId, projectId: this.projectId }, {
|
|
345
346
|
headers: { Authorization: `Bearer ${token}` }
|
|
346
347
|
});
|
|
347
348
|
}
|
|
@@ -352,8 +353,8 @@ class Auth {
|
|
|
352
353
|
async revokeAllSessions() {
|
|
353
354
|
const token = await this.getIdToken();
|
|
354
355
|
try {
|
|
355
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
356
|
-
await this.client.post(endpoint, {}, {
|
|
356
|
+
const endpoint = this.emulatorUrl || `/api/auth/session/revoke-all`;
|
|
357
|
+
await this.client.post(endpoint, { projectId: this.projectId }, {
|
|
357
358
|
headers: { Authorization: `Bearer ${token}` }
|
|
358
359
|
});
|
|
359
360
|
await this.signOut();
|
|
@@ -368,8 +369,8 @@ class Auth {
|
|
|
368
369
|
this.currentSession = null;
|
|
369
370
|
if (oldSession) {
|
|
370
371
|
try {
|
|
371
|
-
const endpoint = this.emulatorUrl || `/api/
|
|
372
|
-
await this.client.post(endpoint, { refreshToken: oldSession.refreshToken });
|
|
372
|
+
const endpoint = this.emulatorUrl || `/api/auth/logout`;
|
|
373
|
+
await this.client.post(endpoint, { refreshToken: oldSession.refreshToken, projectId: this.projectId });
|
|
373
374
|
}
|
|
374
375
|
catch {
|
|
375
376
|
// Non-blocking logout network failure
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FieldPath } from './FieldPath';
|
|
2
|
+
import { CollectionReference, Query, QueryConstraint, QuerySnapshot, AggregateField, AggregateSpec, AggregateSpecData, AggregateQuerySnapshot } from '../types/index';
|
|
2
3
|
export declare const query: <T = any>(queryObject: Query<T> | CollectionReference<T>, ...queryConstraints: QueryConstraint[]) => Query<T>;
|
|
3
4
|
export declare const getCachedDocs: <T = any>(queryOrCollection: Query<T> | CollectionReference<T>) => QuerySnapshot<T>;
|
|
4
5
|
export declare const getDocs: <T = any>(queryOrCollection: Query<T> | CollectionReference<T>) => Promise<QuerySnapshot<T>>;
|
|
6
|
+
export declare const count: () => AggregateField<number>;
|
|
7
|
+
export declare const sum: (field: string | FieldPath) => AggregateField<number>;
|
|
8
|
+
export declare const average: (field: string | FieldPath) => AggregateField<number | null>;
|
|
9
|
+
export declare const getAggregateFromServer: <T extends AggregateSpec>(queryOrCollection: Query<any> | CollectionReference<any>, aggregateSpec: T) => Promise<AggregateQuerySnapshot<AggregateSpecData<T>>>;
|
|
10
|
+
export declare const getCountFromServer: <T = any>(queryOrCollection: Query<T> | CollectionReference<T>) => Promise<AggregateQuerySnapshot<{
|
|
11
|
+
count: number;
|
|
12
|
+
}>>;
|
package/dist/firestore/Query.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getDocs = exports.getCachedDocs = exports.query = void 0;
|
|
3
|
+
exports.getCountFromServer = exports.getAggregateFromServer = exports.average = exports.sum = exports.count = exports.getDocs = exports.getCachedDocs = exports.query = void 0;
|
|
4
4
|
const SnapshotManager_1 = require("./SnapshotManager");
|
|
5
5
|
const DocumentReference_1 = require("./DocumentReference");
|
|
6
6
|
const NexaError_1 = require("../errors/NexaError");
|
|
@@ -174,3 +174,64 @@ const getDocs = async (queryOrCollection) => {
|
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
176
|
exports.getDocs = getDocs;
|
|
177
|
+
const count = () => {
|
|
178
|
+
return { type: 'count' };
|
|
179
|
+
};
|
|
180
|
+
exports.count = count;
|
|
181
|
+
const sum = (field) => {
|
|
182
|
+
return { type: 'sum', fieldPath: field };
|
|
183
|
+
};
|
|
184
|
+
exports.sum = sum;
|
|
185
|
+
const average = (field) => {
|
|
186
|
+
return { type: 'average', fieldPath: field };
|
|
187
|
+
};
|
|
188
|
+
exports.average = average;
|
|
189
|
+
const getAggregateFromServer = async (queryOrCollection, aggregateSpec) => {
|
|
190
|
+
const querySnapshot = await (0, exports.getDocs)(queryOrCollection);
|
|
191
|
+
const docs = querySnapshot.docs;
|
|
192
|
+
const resultData = {};
|
|
193
|
+
for (const [key, spec] of Object.entries(aggregateSpec)) {
|
|
194
|
+
if (spec.type === 'count') {
|
|
195
|
+
resultData[key] = docs.length;
|
|
196
|
+
}
|
|
197
|
+
else if (spec.type === 'sum') {
|
|
198
|
+
const field = spec.fieldPath || '';
|
|
199
|
+
let sumVal = 0;
|
|
200
|
+
for (const d of docs) {
|
|
201
|
+
const val = (0, helpers_1.getNestedValue)(d.data(), field, d.id);
|
|
202
|
+
if (typeof val === 'number' && !isNaN(val)) {
|
|
203
|
+
sumVal += val;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
resultData[key] = sumVal;
|
|
207
|
+
}
|
|
208
|
+
else if (spec.type === 'average') {
|
|
209
|
+
const field = spec.fieldPath || '';
|
|
210
|
+
let sumVal = 0;
|
|
211
|
+
let countVal = 0;
|
|
212
|
+
for (const d of docs) {
|
|
213
|
+
const val = (0, helpers_1.getNestedValue)(d.data(), field, d.id);
|
|
214
|
+
if (typeof val === 'number' && !isNaN(val)) {
|
|
215
|
+
sumVal += val;
|
|
216
|
+
countVal++;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
resultData[key] = countVal > 0 ? sumVal / countVal : null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
type: 'AggregateQuerySnapshot',
|
|
224
|
+
query: queryOrCollection,
|
|
225
|
+
data: () => resultData
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
exports.getAggregateFromServer = getAggregateFromServer;
|
|
229
|
+
const getCountFromServer = async (queryOrCollection) => {
|
|
230
|
+
const querySnapshot = await (0, exports.getDocs)(queryOrCollection);
|
|
231
|
+
return {
|
|
232
|
+
type: 'AggregateQuerySnapshot',
|
|
233
|
+
query: queryOrCollection,
|
|
234
|
+
data: () => ({ count: querySnapshot.docs.length })
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
exports.getCountFromServer = getCountFromServer;
|
|
@@ -2,7 +2,9 @@ import { AxiosInstance } from 'axios';
|
|
|
2
2
|
export declare class HttpClient {
|
|
3
3
|
private client;
|
|
4
4
|
private getToken;
|
|
5
|
-
|
|
5
|
+
private projectId?;
|
|
6
|
+
private apiKey?;
|
|
7
|
+
constructor(endpoint: string, getTokenFn: () => string | null, projectId?: string, apiKey?: string | null);
|
|
6
8
|
getAxiosInstance(): AxiosInstance;
|
|
7
9
|
setBaseURL(url: string): void;
|
|
8
10
|
}
|
|
@@ -6,18 +6,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.HttpClient = void 0;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
class HttpClient {
|
|
9
|
-
constructor(endpoint, getTokenFn) {
|
|
9
|
+
constructor(endpoint, getTokenFn, projectId, apiKey) {
|
|
10
10
|
this.getToken = getTokenFn;
|
|
11
|
+
this.projectId = projectId;
|
|
12
|
+
this.apiKey = apiKey;
|
|
11
13
|
this.client = axios_1.default.create({
|
|
12
14
|
baseURL: endpoint,
|
|
13
|
-
timeout:
|
|
15
|
+
timeout: 10000
|
|
14
16
|
});
|
|
15
17
|
this.client.interceptors.request.use((req) => {
|
|
16
18
|
const activeToken = this.getToken();
|
|
17
19
|
if (activeToken) {
|
|
18
20
|
req.headers.Authorization = `Bearer ${activeToken}`;
|
|
21
|
+
}
|
|
22
|
+
if (this.apiKey) {
|
|
23
|
+
req.headers['x-api-key'] = this.apiKey;
|
|
24
|
+
}
|
|
25
|
+
else if (activeToken) {
|
|
19
26
|
req.headers['x-api-key'] = activeToken;
|
|
20
27
|
}
|
|
28
|
+
if (this.projectId) {
|
|
29
|
+
req.headers['x-project-id'] = this.projectId;
|
|
30
|
+
}
|
|
21
31
|
if (typeof FormData !== 'undefined' && req.data instanceof FormData) {
|
|
22
32
|
if (req.headers) {
|
|
23
33
|
delete req.headers['Content-Type'];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -77,6 +77,22 @@ export interface QuerySnapshot<T = any> {
|
|
|
77
77
|
metadata: SnapshotMetadata;
|
|
78
78
|
docChanges: () => DocumentChange<T>[];
|
|
79
79
|
}
|
|
80
|
+
export interface AggregateField<T = number> {
|
|
81
|
+
type: 'count' | 'sum' | 'average';
|
|
82
|
+
fieldPath?: string | FieldPath;
|
|
83
|
+
_returnType?: T;
|
|
84
|
+
}
|
|
85
|
+
export type AggregateSpec = Record<string, AggregateField<any>>;
|
|
86
|
+
export type AggregateSpecData<T extends AggregateSpec> = {
|
|
87
|
+
[K in keyof T]: T[K] extends AggregateField<infer R> ? R : number;
|
|
88
|
+
};
|
|
89
|
+
export interface AggregateQuerySnapshot<T extends Record<string, any> = {
|
|
90
|
+
count: number;
|
|
91
|
+
}> {
|
|
92
|
+
type: 'AggregateQuerySnapshot';
|
|
93
|
+
query: Query<any> | CollectionReference<any>;
|
|
94
|
+
data: () => T;
|
|
95
|
+
}
|
|
80
96
|
export interface DatabaseReference {
|
|
81
97
|
path: string;
|
|
82
98
|
get: () => Promise<any>;
|
package/package.json
CHANGED