nexabase-console 1.0.4 → 1.0.5

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/dist/index.d.ts CHANGED
@@ -39,11 +39,15 @@ export interface Query<T = any> {
39
39
  constraints?: QueryConstraint[];
40
40
  }
41
41
  export interface DocumentSnapshot<T = any> {
42
+ id: string;
43
+ ref: DocumentReference<T>;
42
44
  exists: () => boolean;
43
45
  data: () => T | null;
44
46
  }
45
47
  export interface QueryDocumentSnapshot<T = any> {
46
48
  id: string;
49
+ ref: DocumentReference<T>;
50
+ exists: () => boolean;
47
51
  data: () => T;
48
52
  }
49
53
  export interface QuerySnapshot<T = any> {
@@ -51,7 +55,7 @@ export interface QuerySnapshot<T = any> {
51
55
  forEach: (callback: (doc: QueryDocumentSnapshot<T>) => void) => void;
52
56
  empty: boolean;
53
57
  size: number;
54
- docChanges?: () => any[];
58
+ docChanges: () => any[];
55
59
  }
56
60
  export interface DatabaseReference {
57
61
  get: () => Promise<any>;
@@ -137,11 +141,15 @@ export declare const query: <T = any>(queryObject: Query<T> | CollectionReferenc
137
141
  export declare const where: (fieldPath: string, opStr: "==" | ">" | "<" | ">=" | "<=" | "!=", value: any) => QueryConstraint;
138
142
  export declare const orderBy: (fieldPath: string, directionStr?: "asc" | "desc") => QueryConstraint;
139
143
  export declare const limit: (limitValue: number) => QueryConstraint;
144
+ export declare const getCachedDocs: <T = any>(queryOrCollection: Query<T> | CollectionReference<T>) => QuerySnapshot<T>;
145
+ export declare const getCachedDoc: <T = any>(docRef: DocumentReference<T>) => DocumentSnapshot<T>;
140
146
  export declare const getDocs: <T = any>(queryOrCollection: Query<T> | CollectionReference<T>) => Promise<QuerySnapshot<T>>;
141
147
  export declare const getDoc: <T = any>(docRef: DocumentReference<T>) => Promise<DocumentSnapshot<T>>;
142
148
  export declare const setDoc: (docRef: DocumentReference, data: any) => Promise<any>;
143
149
  export declare const updateDoc: (docRef: DocumentReference, data: any) => Promise<any>;
144
150
  export declare const deleteDoc: (docRef: DocumentReference) => Promise<any>;
145
151
  export declare const onSnapshot: (ref: CollectionReference | DocumentReference | Query, callback: (snapshot: any) => void) => (() => void);
152
+ export declare const enableIndexedDbPersistence: (db: NexaApp) => Promise<void>;
153
+ export declare const enableOfflinePersistence: (db: NexaApp) => Promise<void>;
146
154
  export declare const writeBatch: (db: NexaApp) => WriteBatch;
147
155
  export {};
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeBatch = exports.onSnapshot = exports.deleteDoc = exports.updateDoc = exports.setDoc = exports.getDoc = exports.getDocs = exports.limit = exports.orderBy = exports.where = exports.query = exports.doc = exports.collection = exports.getFirestore = exports.initializeApp = exports.NexaApp = void 0;
6
+ exports.writeBatch = exports.enableOfflinePersistence = exports.enableIndexedDbPersistence = exports.onSnapshot = exports.deleteDoc = exports.updateDoc = exports.setDoc = exports.getDoc = exports.getDocs = exports.getCachedDoc = exports.getCachedDocs = exports.limit = exports.orderBy = exports.where = exports.query = exports.doc = exports.collection = exports.getFirestore = exports.initializeApp = exports.NexaApp = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  // -----------------------------------------------------------------
9
9
  // Main NexaApp SDK Class
@@ -29,18 +29,29 @@ class NexaApp {
29
29
  this.token = savedToken;
30
30
  }
31
31
  }
32
- this.endpoint = config.endpoint || 'https://db.nexabase.id';
32
+ // 3. Smart Endpoint Resolution: Auto-detect domain/host if none is specified
33
+ let defaultEndpoint = 'https://db.nexabase.id';
34
+ if (typeof window !== 'undefined' && window.location) {
35
+ // Default to the current page origin in dev/proxy preview environments so it auto-points to /api/*
36
+ defaultEndpoint = window.location.origin;
37
+ }
38
+ this.endpoint = config.endpoint || defaultEndpoint;
33
39
  this.client = axios_1.default.create({
34
40
  baseURL: this.endpoint,
35
41
  headers: {
36
42
  'Content-Type': 'application/json'
37
43
  }
38
44
  });
39
- // Request interceptor to automatically attach authorization Bearer token
45
+ // Request interceptor to attach bearer token and automatically resolve the axios boundary issue for file uploads
40
46
  this.client.interceptors.request.use((req) => {
41
47
  if (this.token) {
42
48
  req.headers.Authorization = `Bearer ${this.token}`;
43
49
  }
50
+ // 2. Fix Axios Multipart Boundary on File Upload
51
+ // If Content-Type is multipart/form-data, delete it so axios/browser automatically appends the boundary header
52
+ if (req.headers && req.headers['Content-Type'] === 'multipart/form-data') {
53
+ delete req.headers['Content-Type'];
54
+ }
44
55
  return req;
45
56
  });
46
57
  }
@@ -160,9 +171,7 @@ class NexaApp {
160
171
  const formData = new FormData();
161
172
  formData.append('file', file);
162
173
  const response = await this.client.post(`/api/project/${this.projectId}/storage/upload`, formData, {
163
- headers: {
164
- 'Content-Type': 'multipart/form-data'
165
- },
174
+ // We omit Content-Type headers here (interceptor strips it anyway) so the browser naturally generates the boundary
166
175
  onUploadProgress: (progressEvent) => {
167
176
  if (onProgress && progressEvent.total) {
168
177
  const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
@@ -403,6 +412,70 @@ const limit = (limitValue) => {
403
412
  return { type: 'limit', limitValue };
404
413
  };
405
414
  exports.limit = limit;
415
+ // 1. Helper to retrieve docs from the local Offline cache (Cache-First)
416
+ const getCachedDocs = (queryOrCollection) => {
417
+ const db = queryOrCollection.db;
418
+ const collectionPath = queryOrCollection.path;
419
+ const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
420
+ const results = [];
421
+ Object.keys(db._firestoreCache).forEach((p) => {
422
+ if (p.startsWith(collectionPath + '/')) {
423
+ const parts = p.split('/');
424
+ const docId = parts[parts.length - 1];
425
+ const docRef = (0, exports.doc)(db, `${collectionPath}/${docId}`);
426
+ results.push({
427
+ id: docId,
428
+ ref: docRef,
429
+ exists: () => true,
430
+ data: () => db._firestoreCache[p]
431
+ });
432
+ }
433
+ });
434
+ // Simple offline filters
435
+ let filtered = results;
436
+ for (const c of constraints) {
437
+ if (c.type === 'where' && c.fieldPath && c.opStr) {
438
+ filtered = filtered.filter((docItem) => {
439
+ const val = docItem.data()[c.fieldPath];
440
+ if (c.opStr === '==')
441
+ return val === c.value;
442
+ if (c.opStr === '>')
443
+ return val > c.value;
444
+ if (c.opStr === '<')
445
+ return val < c.value;
446
+ if (c.opStr === '>=')
447
+ return val >= c.value;
448
+ if (c.opStr === '<=')
449
+ return val <= c.value;
450
+ if (c.opStr === '!=')
451
+ return val !== c.value;
452
+ return true;
453
+ });
454
+ }
455
+ }
456
+ return {
457
+ docs: filtered,
458
+ forEach: (cb) => filtered.forEach(cb),
459
+ empty: filtered.length === 0,
460
+ size: filtered.length,
461
+ docChanges: () => filtered.map(doc => ({ type: 'added', doc }))
462
+ };
463
+ };
464
+ exports.getCachedDocs = getCachedDocs;
465
+ // 1. Helper to retrieve single doc from the local Offline cache (Cache-First)
466
+ const getCachedDoc = (docRef) => {
467
+ const db = docRef.db;
468
+ const segments = docRef.path.split('/');
469
+ const docId = segments[segments.length - 1];
470
+ const data = db._firestoreCache[docRef.path];
471
+ return {
472
+ id: docId,
473
+ ref: docRef,
474
+ exists: () => !!data,
475
+ data: () => (data || null)
476
+ };
477
+ };
478
+ exports.getCachedDoc = getCachedDoc;
406
479
  const getDocs = async (queryOrCollection) => {
407
480
  const db = queryOrCollection.db;
408
481
  if (db._initPromise)
@@ -418,10 +491,15 @@ const getDocs = async (queryOrCollection) => {
418
491
  for (const d of docs) {
419
492
  await db._setCache(`${collectionPath}/${d.id}`, d.fields);
420
493
  }
421
- const docsArray = docs.map((d) => ({
422
- id: d.id,
423
- data: () => d.fields
424
- }));
494
+ const docsArray = docs.map((d) => {
495
+ const docRef = (0, exports.doc)(db, `${collectionPath}/${d.id}`);
496
+ return {
497
+ id: d.id,
498
+ ref: docRef,
499
+ exists: () => true,
500
+ data: () => d.fields
501
+ };
502
+ });
425
503
  return {
426
504
  docs: docsArray,
427
505
  forEach: (cb) => docsArray.forEach(cb),
@@ -434,54 +512,7 @@ const getDocs = async (queryOrCollection) => {
434
512
  if (err && err.response) {
435
513
  throw err;
436
514
  }
437
- const results = [];
438
- Object.keys(db._firestoreCache).forEach((p) => {
439
- if (p.startsWith(collectionPath + '/')) {
440
- const parts = p.split('/');
441
- results.push({
442
- id: parts[parts.length - 1],
443
- data: () => db._firestoreCache[p]
444
- });
445
- }
446
- });
447
- // Simple offline filters
448
- let filtered = results;
449
- for (const c of constraints) {
450
- if (c.type === 'where' && c.fieldPath && c.opStr) {
451
- filtered = filtered.filter((docItem) => {
452
- const val = docItem.data()[c.fieldPath];
453
- if (c.opStr === '==')
454
- return val === c.value;
455
- if (c.opStr === '>')
456
- return val > c.value;
457
- if (c.opStr === '<')
458
- return val < c.value;
459
- if (c.opStr === '>=')
460
- return val >= c.value;
461
- if (c.opStr === '<=')
462
- return val <= c.value;
463
- if (c.opStr === '!=')
464
- return val !== c.value;
465
- if (c.opStr === 'in')
466
- return Array.isArray(c.value) && c.value.includes(val);
467
- if (c.opStr === 'not-in')
468
- return Array.isArray(c.value) && !c.value.includes(val);
469
- if (c.opStr === 'array-contains')
470
- return Array.isArray(val) && val.includes(c.value);
471
- if (c.opStr === 'array-contains-any') {
472
- return Array.isArray(val) && Array.isArray(c.value) && val.some((v) => c.value.includes(v));
473
- }
474
- return true;
475
- });
476
- }
477
- }
478
- return {
479
- docs: filtered,
480
- forEach: (cb) => filtered.forEach(cb),
481
- empty: filtered.length === 0,
482
- size: filtered.length,
483
- docChanges: () => filtered.map(doc => ({ type: 'added', doc }))
484
- };
515
+ return (0, exports.getCachedDocs)(queryOrCollection);
485
516
  }
486
517
  };
487
518
  exports.getDocs = getDocs;
@@ -489,18 +520,24 @@ const getDoc = async (docRef) => {
489
520
  const db = docRef.db;
490
521
  if (db._initPromise)
491
522
  await db._initPromise;
523
+ const segments = docRef.path.split('/');
524
+ const docId = segments[segments.length - 1];
492
525
  try {
493
526
  const res = await db.client.get(`/api/firestore/${db.projectId}/document?docPath=${encodeURIComponent(docRef.path)}`);
494
527
  const data = res.data.data;
495
528
  await db._setCache(docRef.path, data);
496
- return { exists: () => !!data, data: () => data };
529
+ return {
530
+ id: docId,
531
+ ref: docRef,
532
+ exists: () => !!data,
533
+ data: () => data
534
+ };
497
535
  }
498
536
  catch (err) {
499
537
  if (err && err.response) {
500
538
  throw err;
501
539
  }
502
- const data = db._firestoreCache[docRef.path];
503
- return { exists: () => !!data, data: () => (data || null) };
540
+ return (0, exports.getCachedDoc)(docRef);
504
541
  }
505
542
  };
506
543
  exports.getDoc = getDoc;
@@ -597,6 +634,19 @@ const onSnapshot = (ref, callback) => {
597
634
  await db._initPromise;
598
635
  db._ensureFirestoreSSE();
599
636
  const pathKey = ref.path;
637
+ // 1. Native Offline Cache/Persistence Priority: Emits cached snapshot IMMEDIATELY to trigger instant optimistic rendering!
638
+ if (ref.type === 'collection' || ref.type === 'query') {
639
+ const cached = (0, exports.getCachedDocs)(ref);
640
+ if (cached && cached.docs.length > 0) {
641
+ callback(cached);
642
+ }
643
+ }
644
+ else {
645
+ const cached = (0, exports.getCachedDoc)(ref);
646
+ if (cached && cached.exists()) {
647
+ callback(cached);
648
+ }
649
+ }
600
650
  const triggerCallback = async () => {
601
651
  if (ref.type === 'collection' || ref.type === 'query') {
602
652
  const docs = await (0, exports.getDocs)(ref);
@@ -607,6 +657,7 @@ const onSnapshot = (ref, callback) => {
607
657
  callback(docData);
608
658
  }
609
659
  };
660
+ // 2. Fetch the latest live server data and update cache + trigger the callback again
610
661
  triggerCallback();
611
662
  const listenerId = Math.random().toString(36).substring(2, 9);
612
663
  db._snapshotCallbacks[`${ref.type}_${listenerId}_${pathKey}`] = triggerCallback;
@@ -621,6 +672,17 @@ const onSnapshot = (ref, callback) => {
621
672
  return () => unsubscribe();
622
673
  };
623
674
  exports.onSnapshot = onSnapshot;
675
+ // Supporting standard Firebase modular options functions
676
+ const enableIndexedDbPersistence = async (db) => {
677
+ if (db._initPromise)
678
+ await db._initPromise;
679
+ };
680
+ exports.enableIndexedDbPersistence = enableIndexedDbPersistence;
681
+ const enableOfflinePersistence = async (db) => {
682
+ if (db._initPromise)
683
+ await db._initPromise;
684
+ };
685
+ exports.enableOfflinePersistence = enableOfflinePersistence;
624
686
  const writeBatch = (db) => {
625
687
  const operations = [];
626
688
  const batchObj = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexabase-console",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
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",