nexabase-console 1.0.5 → 1.0.7
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 +24 -2
- package/dist/index.js +156 -24
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -58,16 +58,29 @@ export interface QuerySnapshot<T = any> {
|
|
|
58
58
|
docChanges: () => any[];
|
|
59
59
|
}
|
|
60
60
|
export interface DatabaseReference {
|
|
61
|
+
path: string;
|
|
61
62
|
get: () => Promise<any>;
|
|
62
63
|
set: (data: any) => Promise<any>;
|
|
63
|
-
|
|
64
|
+
update: (data: any) => Promise<any>;
|
|
65
|
+
delete: () => Promise<any>;
|
|
66
|
+
remove: () => Promise<any>;
|
|
67
|
+
onDataChanged: (callback: (data: any) => void) => () => void;
|
|
68
|
+
}
|
|
69
|
+
export interface UploadOptions {
|
|
70
|
+
contextType?: 'chat' | 'public_post';
|
|
71
|
+
contextId?: string;
|
|
72
|
+
fileId?: string;
|
|
64
73
|
}
|
|
65
74
|
export interface StorageReference {
|
|
66
75
|
name: string;
|
|
67
76
|
fullPath: string;
|
|
68
|
-
put: (file: File | Blob, onProgress?: (percent: number) => void) => Promise<{
|
|
77
|
+
put: (file: File | Blob, options?: UploadOptions | ((percent: number) => void), onProgress?: (percent: number) => void) => Promise<{
|
|
69
78
|
url: string;
|
|
70
79
|
success: boolean;
|
|
80
|
+
fileId?: string;
|
|
81
|
+
proxyViewUrl?: string;
|
|
82
|
+
proxyDownloadUrl?: string;
|
|
83
|
+
metadata?: any;
|
|
71
84
|
}>;
|
|
72
85
|
delete: () => Promise<{
|
|
73
86
|
success: boolean;
|
|
@@ -152,4 +165,13 @@ export declare const onSnapshot: (ref: CollectionReference | DocumentReference |
|
|
|
152
165
|
export declare const enableIndexedDbPersistence: (db: NexaApp) => Promise<void>;
|
|
153
166
|
export declare const enableOfflinePersistence: (db: NexaApp) => Promise<void>;
|
|
154
167
|
export declare const writeBatch: (db: NexaApp) => WriteBatch;
|
|
168
|
+
export interface Transaction {
|
|
169
|
+
get: <T = any>(docRef: DocumentReference<T>) => Promise<DocumentSnapshot<T>>;
|
|
170
|
+
set: (docRef: DocumentReference, data: any) => Transaction;
|
|
171
|
+
update: (docRef: DocumentReference, data: any) => Transaction;
|
|
172
|
+
delete: (docRef: DocumentReference) => Transaction;
|
|
173
|
+
}
|
|
174
|
+
export declare const runTransaction: <T = any>(db: NexaApp, updateFunction: (transaction: Transaction) => Promise<T>, options?: {
|
|
175
|
+
maxAttempts?: number;
|
|
176
|
+
}) => Promise<T>;
|
|
155
177
|
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.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;
|
|
6
|
+
exports.runTransaction = 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
|
|
@@ -85,25 +85,50 @@ class NexaApp {
|
|
|
85
85
|
// -----------------------------------------------------------------
|
|
86
86
|
database() {
|
|
87
87
|
return {
|
|
88
|
-
ref: (path = '') =>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
88
|
+
ref: (path = '') => {
|
|
89
|
+
const cleanPath = (path || '').replace(/^\/+|\/+$/g, '');
|
|
90
|
+
return {
|
|
91
|
+
path: cleanPath,
|
|
92
|
+
get: async () => {
|
|
93
|
+
const response = await this.client.get(`/api/db/${this.projectId}/${cleanPath}`);
|
|
94
|
+
return response.data;
|
|
95
|
+
},
|
|
96
|
+
set: async (data) => {
|
|
97
|
+
const payload = data instanceof Map ? Object.fromEntries(data) : data;
|
|
98
|
+
const response = await this.client.put(`/api/db/${this.projectId}/${cleanPath}`, payload);
|
|
99
|
+
return response.data;
|
|
100
|
+
},
|
|
101
|
+
update: async (data) => {
|
|
102
|
+
const payload = data instanceof Map ? Object.fromEntries(data) : data;
|
|
103
|
+
const response = await this.client.patch(`/api/db/${this.projectId}/${cleanPath}`, payload);
|
|
104
|
+
return response.data;
|
|
105
|
+
},
|
|
106
|
+
delete: async () => {
|
|
107
|
+
const response = await this.client.put(`/api/db/${this.projectId}/${cleanPath}`, null);
|
|
108
|
+
return response.data;
|
|
109
|
+
},
|
|
110
|
+
remove: async () => {
|
|
111
|
+
const response = await this.client.put(`/api/db/${this.projectId}/${cleanPath}`, null);
|
|
112
|
+
return response.data;
|
|
113
|
+
},
|
|
114
|
+
onDataChanged: (callback) => {
|
|
115
|
+
this.connectRealtime();
|
|
116
|
+
const handler = (payload) => {
|
|
117
|
+
const payloadPath = (payload.path || '').replace(/^\/+|\/+$/g, '');
|
|
118
|
+
if (payloadPath === cleanPath ||
|
|
119
|
+
payloadPath.startsWith(cleanPath + '/') ||
|
|
120
|
+
cleanPath === '' ||
|
|
121
|
+
cleanPath.startsWith(payloadPath + '/')) {
|
|
122
|
+
callback(payload.data);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
this._sseCallbacks.data_changed.push(handler);
|
|
126
|
+
return () => {
|
|
127
|
+
this._sseCallbacks.data_changed = this._sseCallbacks.data_changed.filter((cb) => cb !== handler);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
107
132
|
};
|
|
108
133
|
}
|
|
109
134
|
// -----------------------------------------------------------------
|
|
@@ -167,19 +192,43 @@ class NexaApp {
|
|
|
167
192
|
return {
|
|
168
193
|
name,
|
|
169
194
|
fullPath: path,
|
|
170
|
-
put: async (file, onProgress) => {
|
|
195
|
+
put: async (file, options, onProgress) => {
|
|
196
|
+
let actualOptions = {};
|
|
197
|
+
let actualOnProgress = onProgress;
|
|
198
|
+
if (typeof options === 'function') {
|
|
199
|
+
actualOnProgress = options;
|
|
200
|
+
}
|
|
201
|
+
else if (options && typeof options === 'object') {
|
|
202
|
+
actualOptions = options;
|
|
203
|
+
}
|
|
171
204
|
const formData = new FormData();
|
|
172
205
|
formData.append('file', file);
|
|
206
|
+
if (actualOptions.contextType) {
|
|
207
|
+
formData.append('contextType', actualOptions.contextType);
|
|
208
|
+
}
|
|
209
|
+
if (actualOptions.contextId) {
|
|
210
|
+
formData.append('contextId', actualOptions.contextId);
|
|
211
|
+
}
|
|
212
|
+
if (actualOptions.fileId) {
|
|
213
|
+
formData.append('fileId', actualOptions.fileId);
|
|
214
|
+
}
|
|
173
215
|
const response = await this.client.post(`/api/project/${this.projectId}/storage/upload`, formData, {
|
|
174
216
|
// We omit Content-Type headers here (interceptor strips it anyway) so the browser naturally generates the boundary
|
|
175
217
|
onUploadProgress: (progressEvent) => {
|
|
176
|
-
if (
|
|
218
|
+
if (actualOnProgress && progressEvent.total) {
|
|
177
219
|
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
|
178
|
-
|
|
220
|
+
actualOnProgress(percent);
|
|
179
221
|
}
|
|
180
222
|
}
|
|
181
223
|
});
|
|
182
|
-
return
|
|
224
|
+
return {
|
|
225
|
+
url: response.data.url,
|
|
226
|
+
success: true,
|
|
227
|
+
fileId: response.data.fileId,
|
|
228
|
+
proxyViewUrl: response.data.proxyViewUrl,
|
|
229
|
+
proxyDownloadUrl: response.data.proxyDownloadUrl,
|
|
230
|
+
metadata: response.data.metadata
|
|
231
|
+
};
|
|
183
232
|
},
|
|
184
233
|
delete: async () => {
|
|
185
234
|
const response = await this.client.delete(`/api/project/${this.projectId}/storage/${encodeURIComponent(name)}`);
|
|
@@ -762,3 +811,86 @@ const writeBatch = (db) => {
|
|
|
762
811
|
return batchObj;
|
|
763
812
|
};
|
|
764
813
|
exports.writeBatch = writeBatch;
|
|
814
|
+
const runTransaction = async (db, updateFunction, options) => {
|
|
815
|
+
if (db._initPromise)
|
|
816
|
+
await db._initPromise;
|
|
817
|
+
const maxAttempts = options?.maxAttempts ?? 5;
|
|
818
|
+
let attempt = 0;
|
|
819
|
+
while (true) {
|
|
820
|
+
attempt++;
|
|
821
|
+
const reads = [];
|
|
822
|
+
const operations = [];
|
|
823
|
+
const transaction = {
|
|
824
|
+
get: async (docRef) => {
|
|
825
|
+
const segments = docRef.path.split('/');
|
|
826
|
+
const docId = segments[segments.length - 1];
|
|
827
|
+
const res = await db.client.get(`/api/firestore/${db.projectId}/documentWithMetadata?docPath=${encodeURIComponent(docRef.path)}`);
|
|
828
|
+
const { data, updatedAt } = res.data;
|
|
829
|
+
reads.push({ path: docRef.path, readUpdatedAt: updatedAt });
|
|
830
|
+
return {
|
|
831
|
+
id: docId,
|
|
832
|
+
ref: docRef,
|
|
833
|
+
exists: () => !!data,
|
|
834
|
+
data: () => data
|
|
835
|
+
};
|
|
836
|
+
},
|
|
837
|
+
set: (docRef, data) => {
|
|
838
|
+
operations.push({ type: 'set', path: docRef.path, data });
|
|
839
|
+
return transaction;
|
|
840
|
+
},
|
|
841
|
+
update: (docRef, data) => {
|
|
842
|
+
operations.push({ type: 'update', path: docRef.path, data });
|
|
843
|
+
return transaction;
|
|
844
|
+
},
|
|
845
|
+
delete: (docRef) => {
|
|
846
|
+
operations.push({ type: 'delete', path: docRef.path });
|
|
847
|
+
return transaction;
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
try {
|
|
851
|
+
const result = await updateFunction(transaction);
|
|
852
|
+
if (operations.length > 0 || reads.length > 0) {
|
|
853
|
+
await db.client.post(`/api/firestore/${db.projectId}/transaction`, {
|
|
854
|
+
reads,
|
|
855
|
+
operations
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
for (const op of operations) {
|
|
859
|
+
if (op.type === 'set' || op.type === 'update') {
|
|
860
|
+
const newData = { ...db._firestoreCache[op.path], ...op.data };
|
|
861
|
+
await db._setCache(op.path, newData);
|
|
862
|
+
Object.keys(db._snapshotCallbacks).forEach((key) => {
|
|
863
|
+
const cb = db._snapshotCallbacks[key];
|
|
864
|
+
const parts = key.split('_');
|
|
865
|
+
const pathKey = parts.slice(2).join('_');
|
|
866
|
+
if (pathKey === op.path || op.path.startsWith(pathKey + '/')) {
|
|
867
|
+
cb({ type: 'document_written', docPath: op.path, data: db._firestoreCache[op.path] });
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
else if (op.type === 'delete') {
|
|
872
|
+
await db._deleteCache(op.path);
|
|
873
|
+
Object.keys(db._snapshotCallbacks).forEach((key) => {
|
|
874
|
+
const cb = db._snapshotCallbacks[key];
|
|
875
|
+
const parts = key.split('_');
|
|
876
|
+
const pathKey = parts.slice(2).join('_');
|
|
877
|
+
if (pathKey === op.path || op.path.startsWith(pathKey + '/')) {
|
|
878
|
+
cb({ type: 'document_deleted', docPath: op.path });
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
catch (err) {
|
|
886
|
+
const isConflict = err && err.response && err.response.status === 409;
|
|
887
|
+
if (isConflict && attempt < maxAttempts) {
|
|
888
|
+
console.warn(`[NexaBase Transaction] Retrying due to concurrency conflict (Attempt ${attempt}/${maxAttempts})`);
|
|
889
|
+
await new Promise((resolve) => setTimeout(resolve, Math.random() * 100 * attempt));
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
throw err;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
exports.runTransaction = runTransaction;
|
package/package.json
CHANGED