@qidcloud/sdk 1.2.3 → 1.2.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/dist/index.mjs CHANGED
@@ -95,6 +95,138 @@ class AuthModule {
95
95
  role: data.role
96
96
  };
97
97
  }
98
+ // --- Conventional Auth Methods ---
99
+ /**
100
+ * Login using username and password
101
+ */
102
+ async login(credentials) {
103
+ const resp = await this.sdk.api.post('/api/login/conventional', credentials);
104
+ return resp.data;
105
+ }
106
+ /**
107
+ * Register a new user
108
+ */
109
+ async register(data) {
110
+ const resp = await this.sdk.api.post('/api/register/conventional', data);
111
+ return resp.data;
112
+ }
113
+ /**
114
+ * Initiate registration (OTP based if configured)
115
+ */
116
+ async initiateRegistration(email) {
117
+ const resp = await this.sdk.api.post('/api/register/initiate', { email });
118
+ return resp.data;
119
+ }
120
+ /**
121
+ * Verify OTP or mobile verification
122
+ */
123
+ async verify(data) {
124
+ const resp = await this.sdk.api.post('/api/verify', data);
125
+ return resp.data;
126
+ }
127
+ // --- Password & Account Management ---
128
+ /**
129
+ * Request a password reset link/OTP
130
+ */
131
+ async requestPasswordReset(email) {
132
+ const resp = await this.sdk.api.post('/api/password/reset/request', { email });
133
+ return resp.data;
134
+ }
135
+ /**
136
+ * Confirm password reset with OTP and new password
137
+ */
138
+ async confirmPasswordReset(data) {
139
+ const resp = await this.sdk.api.post('/api/password/reset/confirm', data);
140
+ return resp.data;
141
+ }
142
+ /**
143
+ * Change current password (requires current token)
144
+ */
145
+ async changePassword(token, data) {
146
+ const resp = await this.sdk.api.post('/api/password/change', data, {
147
+ headers: { 'Authorization': `Bearer ${token}` }
148
+ });
149
+ return resp.data;
150
+ }
151
+ /**
152
+ * Delete user account
153
+ */
154
+ async deleteAccount(token) {
155
+ const resp = await this.sdk.api.delete('/api/auth/account', {
156
+ headers: { 'Authorization': `Bearer ${token}` }
157
+ });
158
+ return resp.data;
159
+ }
160
+ // --- MFA Management ---
161
+ /**
162
+ * Toggle MFA status (Enable/Disable)
163
+ */
164
+ async toggleMFA(token, data) {
165
+ const resp = await this.sdk.api.post('/api/auth/mfa/toggle', data, {
166
+ headers: { 'Authorization': `Bearer ${token}` }
167
+ });
168
+ return resp.data;
169
+ }
170
+ /**
171
+ * Verify MFA toggle request
172
+ */
173
+ async verifyMFA(token, otp) {
174
+ const resp = await this.sdk.api.post('/api/auth/mfa/verify', { otp }, {
175
+ headers: { 'Authorization': `Bearer ${token}` }
176
+ });
177
+ return resp.data;
178
+ }
179
+ // --- Session & Recovery ---
180
+ /**
181
+ * Refresh the current active session
182
+ */
183
+ async refreshSession(token) {
184
+ const resp = await this.sdk.api.post('/api/session/refresh', {}, {
185
+ headers: { 'Authorization': `Bearer ${token}` }
186
+ });
187
+ return resp.data;
188
+ }
189
+ /**
190
+ * List all active sessions for the user
191
+ */
192
+ async getSessions(token) {
193
+ const resp = await this.sdk.api.get('/api/sessions', {
194
+ headers: { 'Authorization': `Bearer ${token}` }
195
+ });
196
+ return resp.data;
197
+ }
198
+ /**
199
+ * Revoke a specific session
200
+ */
201
+ async revokeSession(token, sessionToken) {
202
+ const resp = await this.sdk.api.delete(`/api/sessions/${sessionToken}`, {
203
+ headers: { 'Authorization': `Bearer ${token}` }
204
+ });
205
+ return resp.data;
206
+ }
207
+ /**
208
+ * Initiate account recovery (password reset)
209
+ */
210
+ async initiateRecovery(email) {
211
+ const resp = await this.sdk.api.post('/api/recover', { email });
212
+ return resp.data;
213
+ }
214
+ /**
215
+ * Verify recovery OTP and set new password
216
+ */
217
+ async verifyRecovery(data) {
218
+ const resp = await this.sdk.api.post('/api/recover/verify', data);
219
+ return resp.data;
220
+ }
221
+ /**
222
+ * Export all user data (GDPR requirement)
223
+ */
224
+ async exportUserData(token) {
225
+ const resp = await this.sdk.api.get('/api/auth/account/export', {
226
+ headers: { 'Authorization': `Bearer ${token}` }
227
+ });
228
+ return resp.data;
229
+ }
98
230
  }
99
231
 
100
232
  class DbModule {
@@ -128,6 +260,98 @@ class DbModule {
128
260
  const resp = await this.sdk.api.post('/api/db/setup', {}, { headers });
129
261
  return resp.data;
130
262
  }
263
+ /**
264
+ * Run versioned migrations (Django-inspired).
265
+ * Only applies migrations that haven't been applied yet.
266
+ * Tracks applied migrations in a `_qid_migrations` table.
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * await qid.db.migrate([
271
+ * { version: '001', name: 'create_users', up: async () => { await qid.db.query('CREATE TABLE ...') } },
272
+ * { version: '002', name: 'add_likes', up: async () => { await qid.db.query('ALTER TABLE ...') } },
273
+ * ]);
274
+ * ```
275
+ *
276
+ * @param migrations Ordered array of migrations to apply
277
+ * @param userToken Optional session token
278
+ * @returns Result with applied/skipped counts
279
+ */
280
+ async migrate(migrations, userToken) {
281
+ const result = {
282
+ success: true,
283
+ applied: [],
284
+ skipped: [],
285
+ total: migrations.length,
286
+ };
287
+ try {
288
+ // 1. Ensure the migrations tracking table exists
289
+ await this.query(`CREATE TABLE IF NOT EXISTS _qid_migrations (
290
+ version VARCHAR(50) PRIMARY KEY,
291
+ name VARCHAR(255) NOT NULL,
292
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
293
+ )`, [], userToken);
294
+ // 2. Get already-applied migrations
295
+ const appliedRes = await this.query('SELECT version FROM _qid_migrations ORDER BY version', [], userToken);
296
+ const appliedVersions = new Set((appliedRes.data || []).map((r) => r.version));
297
+ // 3. Run only new migrations, in order
298
+ for (const migration of migrations) {
299
+ if (appliedVersions.has(migration.version)) {
300
+ result.skipped.push(migration.version);
301
+ continue;
302
+ }
303
+ try {
304
+ await migration.up();
305
+ // Record as applied
306
+ await this.query('INSERT INTO _qid_migrations (version, name) VALUES ($1, $2) ON CONFLICT (version) DO NOTHING', [migration.version, migration.name], userToken);
307
+ result.applied.push(migration.version);
308
+ }
309
+ catch (migrationError) {
310
+ result.success = false;
311
+ result.failed = {
312
+ version: migration.version,
313
+ error: migrationError.message || 'Unknown error',
314
+ };
315
+ // Stop on first failure (like Django)
316
+ break;
317
+ }
318
+ }
319
+ return result;
320
+ }
321
+ catch (err) {
322
+ return {
323
+ success: false,
324
+ applied: result.applied,
325
+ skipped: result.skipped,
326
+ failed: { version: 'init', error: err.message || 'Migration system init failed' },
327
+ total: migrations.length,
328
+ };
329
+ }
330
+ }
331
+ /**
332
+ * Get the current migration status
333
+ * @param migrations The full list of registered migrations
334
+ * @param userToken Optional session token
335
+ */
336
+ async migrateStatus(migrations, userToken) {
337
+ try {
338
+ const appliedRes = await this.query('SELECT version FROM _qid_migrations ORDER BY version', [], userToken);
339
+ const appliedVersions = new Set((appliedRes.data || []).map((r) => r.version));
340
+ return {
341
+ applied: migrations.filter(m => appliedVersions.has(m.version)).map(m => m.version),
342
+ pending: migrations.filter(m => !appliedVersions.has(m.version)).map(m => m.version),
343
+ total: migrations.length,
344
+ };
345
+ }
346
+ catch {
347
+ // Table doesn't exist yet = all pending
348
+ return {
349
+ applied: [],
350
+ pending: migrations.map(m => m.version),
351
+ total: migrations.length,
352
+ };
353
+ }
354
+ }
131
355
  }
132
356
 
133
357
  class EdgeModule {
@@ -221,19 +445,38 @@ class EdgeModule {
221
445
  }
222
446
  }
223
447
 
448
+ const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
449
+ const CHUNKED_THRESHOLD = 10 * 1024 * 1024; // 10MB — above this, use chunked upload
224
450
  class VaultModule {
225
451
  sdk;
226
452
  constructor(sdk) {
227
453
  this.sdk = sdk;
228
454
  }
229
455
  /**
230
- * Upload a file to the project's secure enclave storage
231
- * @param file The file data (Buffer or Blob)
456
+ * Upload a file to the project's secure enclave storage.
457
+ *
458
+ * Files < 10MB use single-shot upload.
459
+ * Files >= 10MB automatically use chunked upload with progress tracking.
460
+ *
461
+ * @param file The file data (Buffer, Blob, or File)
232
462
  * @param fileName Name of the file
233
463
  * @param metadata Optional E2EE metadata or custom tags
234
464
  * @param userToken Session token for user-scoped storage
465
+ * @param options Upload options (onProgress callback, custom chunk size)
235
466
  */
236
- async upload(file, fileName, metadata = {}, userToken) {
467
+ async upload(file, fileName, metadata = {}, userToken, options) {
468
+ const fileSize = file.size || file.length || file.byteLength || 0;
469
+ // Auto-detect: use chunked upload for large files
470
+ if (fileSize >= CHUNKED_THRESHOLD) {
471
+ return this._chunkedUpload(file, fileName, fileSize, metadata, userToken, options);
472
+ }
473
+ // Small file — single-shot upload (existing behavior)
474
+ return this._singleUpload(file, fileName, metadata, userToken);
475
+ }
476
+ /**
477
+ * Single-shot upload for small files (< 10MB)
478
+ */
479
+ async _singleUpload(file, fileName, metadata, userToken) {
237
480
  const formData = new FormData();
238
481
  formData.append('file', file, fileName);
239
482
  formData.append('metadata', JSON.stringify(metadata));
@@ -246,9 +489,149 @@ class VaultModule {
246
489
  const resp = await this.sdk.api.post('/api/storage/upload', formData, { headers });
247
490
  return resp.data;
248
491
  }
492
+ /**
493
+ * Chunked upload for large files (>= 10MB)
494
+ * Flow: init → upload chunks → complete
495
+ */
496
+ async _chunkedUpload(file, fileName, fileSize, metadata, userToken, options) {
497
+ const chunkSize = options?.chunkSize || DEFAULT_CHUNK_SIZE;
498
+ const totalChunks = Math.ceil(fileSize / chunkSize);
499
+ const onProgress = options?.onProgress;
500
+ const headers = {};
501
+ if (userToken) {
502
+ headers['Authorization'] = `Bearer ${userToken}`;
503
+ }
504
+ // 1. Initialize upload session
505
+ const initResp = await this.sdk.api.post('/api/storage/upload/init', {
506
+ fileName,
507
+ fileSize,
508
+ mimeType: file.type || 'application/octet-stream',
509
+ totalChunks,
510
+ metadata
511
+ }, { headers });
512
+ const { uploadId } = initResp.data;
513
+ let uploadedChunks = 0;
514
+ // 2. Upload chunks sequentially
515
+ for (let i = 0; i < totalChunks; i++) {
516
+ const start = i * chunkSize;
517
+ const end = Math.min(start + chunkSize, fileSize);
518
+ // Slice the chunk from the file
519
+ let chunkData;
520
+ if (file.slice) {
521
+ // Blob or File object
522
+ chunkData = file.slice(start, end);
523
+ }
524
+ else if (file.subarray) {
525
+ // Buffer or Uint8Array
526
+ chunkData = file.subarray(start, end);
527
+ }
528
+ else if (typeof file === 'object' && file.data) {
529
+ // express-fileupload style
530
+ chunkData = file.data.subarray(start, end);
531
+ }
532
+ // Build FormData for this chunk
533
+ const chunkForm = new FormData();
534
+ const chunkBlob = chunkData instanceof Blob ? chunkData : new Blob([chunkData]);
535
+ chunkForm.append('chunk', chunkBlob, `chunk_${i}`);
536
+ await this.sdk.api.put(`/api/storage/upload/${uploadId}/chunk/${i}`, chunkForm, {
537
+ headers: {
538
+ ...headers,
539
+ 'Content-Type': 'multipart/form-data'
540
+ }
541
+ });
542
+ uploadedChunks++;
543
+ // Report progress
544
+ if (onProgress) {
545
+ onProgress({
546
+ percent: Math.round((uploadedChunks / totalChunks) * 100),
547
+ uploadedChunks,
548
+ totalChunks,
549
+ uploadedBytes: Math.min(uploadedChunks * chunkSize, fileSize),
550
+ totalBytes: fileSize
551
+ });
552
+ }
553
+ }
554
+ // 3. Complete upload — server reassembles, encrypts, stores
555
+ const completeResp = await this.sdk.api.post(`/api/storage/upload/${uploadId}/complete`, { metadata }, { headers });
556
+ // Final 100% progress
557
+ if (onProgress) {
558
+ onProgress({
559
+ percent: 100,
560
+ uploadedChunks: totalChunks,
561
+ totalChunks,
562
+ uploadedBytes: fileSize,
563
+ totalBytes: fileSize
564
+ });
565
+ }
566
+ return completeResp.data;
567
+ }
568
+ /**
569
+ * Get the status of a chunked upload (for resume support)
570
+ */
571
+ async getUploadStatus(uploadId, userToken) {
572
+ const headers = {};
573
+ if (userToken) {
574
+ headers['Authorization'] = `Bearer ${userToken}`;
575
+ }
576
+ const resp = await this.sdk.api.get(`/api/storage/upload/${uploadId}/status`, { headers });
577
+ return resp.data;
578
+ }
579
+ /**
580
+ * Resume a partially completed chunked upload
581
+ */
582
+ async resumeUpload(uploadId, file, userToken, options) {
583
+ const headers = {};
584
+ if (userToken) {
585
+ headers['Authorization'] = `Bearer ${userToken}`;
586
+ }
587
+ // Get current status to find missing chunks
588
+ const status = await this.getUploadStatus(uploadId, userToken);
589
+ if (status.status !== 'uploading') {
590
+ throw new Error(`Upload is in "${status.status}" state and cannot be resumed`);
591
+ }
592
+ const chunkSize = options?.chunkSize || status.chunkSize || DEFAULT_CHUNK_SIZE;
593
+ const onProgress = options?.onProgress;
594
+ const missingChunks = status.missingChunks;
595
+ const totalChunks = status.totalChunks;
596
+ const fileSize = status.fileSize;
597
+ let uploadedChunks = status.receivedChunks.length;
598
+ // Upload only the missing chunks
599
+ for (const i of missingChunks) {
600
+ const start = i * chunkSize;
601
+ const end = Math.min(start + chunkSize, fileSize);
602
+ let chunkData;
603
+ if (file.slice) {
604
+ chunkData = file.slice(start, end);
605
+ }
606
+ else if (file.subarray) {
607
+ chunkData = file.subarray(start, end);
608
+ }
609
+ const chunkForm = new FormData();
610
+ const chunkBlob = chunkData instanceof Blob ? chunkData : new Blob([chunkData]);
611
+ chunkForm.append('chunk', chunkBlob, `chunk_${i}`);
612
+ await this.sdk.api.put(`/api/storage/upload/${uploadId}/chunk/${i}`, chunkForm, {
613
+ headers: {
614
+ ...headers,
615
+ 'Content-Type': 'multipart/form-data'
616
+ }
617
+ });
618
+ uploadedChunks++;
619
+ if (onProgress) {
620
+ onProgress({
621
+ percent: Math.round((uploadedChunks / totalChunks) * 100),
622
+ uploadedChunks,
623
+ totalChunks,
624
+ uploadedBytes: Math.min(uploadedChunks * chunkSize, fileSize),
625
+ totalBytes: fileSize
626
+ });
627
+ }
628
+ }
629
+ // Complete
630
+ const completeResp = await this.sdk.api.post(`/api/storage/upload/${uploadId}/complete`, {}, { headers });
631
+ return completeResp.data;
632
+ }
249
633
  /**
250
634
  * List files in the project enclave
251
- * @param userToken Session token for user-scoped storage
252
635
  */
253
636
  async list(userToken) {
254
637
  const headers = {};
@@ -260,8 +643,6 @@ class VaultModule {
260
643
  }
261
644
  /**
262
645
  * Download a file from storage
263
- * @param fileId Unique ID of the file
264
- * @param userToken Session token for user-scoped storage
265
646
  */
266
647
  async download(fileId, userToken) {
267
648
  const headers = {};
@@ -276,8 +657,6 @@ class VaultModule {
276
657
  }
277
658
  /**
278
659
  * Delete a file from storage (Soft Delete)
279
- * @param fileId Unique ID of the file
280
- * @param userToken Session token for user-scoped storage
281
660
  */
282
661
  async delete(fileId, userToken) {
283
662
  const headers = {};
@@ -289,7 +668,6 @@ class VaultModule {
289
668
  }
290
669
  /**
291
670
  * List deleted files in the project enclave (Recycle Bin)
292
- * @param userToken Session token for user-scoped storage
293
671
  */
294
672
  async listDeleted(userToken) {
295
673
  const headers = {};
@@ -301,8 +679,6 @@ class VaultModule {
301
679
  }
302
680
  /**
303
681
  * Restore a deleted file from the recycle bin
304
- * @param fileId Unique ID of the file
305
- * @param userToken Session token for user-scoped storage
306
682
  */
307
683
  async restore(fileId, userToken) {
308
684
  const headers = {};
@@ -314,8 +690,6 @@ class VaultModule {
314
690
  }
315
691
  /**
316
692
  * Permanently purge a deleted file
317
- * @param fileId Unique ID of the file
318
- * @param userToken Session token for user-scoped storage
319
693
  */
320
694
  async purge(fileId, userToken) {
321
695
  const headers = {};
@@ -353,6 +727,341 @@ class LogsModule {
353
727
  }
354
728
  }
355
729
 
730
+ class ProjectModule {
731
+ sdk;
732
+ constructor(sdk) {
733
+ this.sdk = sdk;
734
+ }
735
+ /**
736
+ * Get all projects owned by the currently authenticated user
737
+ */
738
+ async getMyProjects(userToken) {
739
+ const resp = await this.sdk.api.get('/api/projects/my-projects', {
740
+ headers: { 'Authorization': `Bearer ${userToken}` }
741
+ });
742
+ return resp.data;
743
+ }
744
+ /**
745
+ * Create a new project
746
+ */
747
+ async createProject(userToken, data) {
748
+ const resp = await this.sdk.api.post('/api/projects', data, {
749
+ headers: { 'Authorization': `Bearer ${userToken}` }
750
+ });
751
+ return resp.data;
752
+ }
753
+ /**
754
+ * Update project basic details (e.g. rename)
755
+ */
756
+ async updateProject(userToken, tenantId, data) {
757
+ const resp = await this.sdk.api.patch(`/api/projects/${tenantId}`, data, {
758
+ headers: { 'Authorization': `Bearer ${userToken}` }
759
+ });
760
+ return resp.data;
761
+ }
762
+ /**
763
+ * Rotate the API key for a project
764
+ */
765
+ async rotateApiKey(userToken, tenantId) {
766
+ const resp = await this.sdk.api.post(`/api/projects/${tenantId}/rotate-key`, {}, {
767
+ headers: { 'Authorization': `Bearer ${userToken}` }
768
+ });
769
+ return resp.data;
770
+ }
771
+ /**
772
+ * Get project specific settings
773
+ */
774
+ async getSettings(userToken, tenantId) {
775
+ const resp = await this.sdk.api.get(`/api/projects/${tenantId}/settings`, {
776
+ headers: { 'Authorization': `Bearer ${userToken}` }
777
+ });
778
+ return resp.data;
779
+ }
780
+ /**
781
+ * Update project settings
782
+ */
783
+ async updateSettings(userToken, tenantId, settings) {
784
+ const resp = await this.sdk.api.patch(`/api/projects/${tenantId}/settings`, settings, {
785
+ headers: { 'Authorization': `Bearer ${userToken}` }
786
+ });
787
+ return resp.data;
788
+ }
789
+ /**
790
+ * Invite a new member to the project
791
+ */
792
+ async inviteMember(userToken, tenantId, data) {
793
+ const resp = await this.sdk.api.post(`/api/projects/${tenantId}/invite`, data, {
794
+ headers: { 'Authorization': `Bearer ${userToken}` }
795
+ });
796
+ return resp.data;
797
+ }
798
+ /**
799
+ * Remove a member from the project
800
+ */
801
+ async removeMember(userToken, tenantId, userId) {
802
+ const resp = await this.sdk.api.delete(`/api/projects/${tenantId}/members/${userId}`, {
803
+ headers: { 'Authorization': `Bearer ${userToken}` }
804
+ });
805
+ return resp.data;
806
+ }
807
+ /**
808
+ * Get all users/members associated with a project
809
+ */
810
+ async getProjectUsers(userToken, tenantId) {
811
+ const resp = await this.sdk.api.get(`/api/projects/${tenantId}/users`, {
812
+ headers: { 'Authorization': `Bearer ${userToken}` }
813
+ });
814
+ return resp.data;
815
+ }
816
+ /**
817
+ * Get all invitations for the currently authenticated user
818
+ */
819
+ async getMyInvitations(userToken) {
820
+ const resp = await this.sdk.api.get('/api/projects/invitations', {
821
+ headers: { 'Authorization': `Bearer ${userToken}` }
822
+ });
823
+ return resp.data;
824
+ }
825
+ /**
826
+ * Accept or reject a project invitation
827
+ */
828
+ async handleInvitationAction(userToken, tenantId, action) {
829
+ const resp = await this.sdk.api.post(`/api/projects/${tenantId}/invitations/${action}`, {}, {
830
+ headers: { 'Authorization': `Bearer ${userToken}` }
831
+ });
832
+ return resp.data;
833
+ }
834
+ /**
835
+ * Update a member's permissions within a project
836
+ */
837
+ async updateMemberPermissions(userToken, tenantId, userId, permissions) {
838
+ const resp = await this.sdk.api.patch(`/api/projects/${tenantId}/members/${userId}/permissions`, { permissions }, {
839
+ headers: { 'Authorization': `Bearer ${userToken}` }
840
+ });
841
+ return resp.data;
842
+ }
843
+ /**
844
+ * Update a project user's status (active/suspended)
845
+ */
846
+ async updateProjectUserStatus(userToken, tenantId, regUserId, status) {
847
+ const resp = await this.sdk.api.patch(`/api/projects/${tenantId}/users/${regUserId}/status`, { status }, {
848
+ headers: { 'Authorization': `Bearer ${userToken}` }
849
+ });
850
+ return resp.data;
851
+ }
852
+ /**
853
+ * Unlink/Remove a user from the project identity enclave
854
+ */
855
+ async unlinkProjectUser(userToken, tenantId, regUserId) {
856
+ const resp = await this.sdk.api.delete(`/api/projects/${tenantId}/users/${regUserId}`, {
857
+ headers: { 'Authorization': `Bearer ${userToken}` }
858
+ });
859
+ return resp.data;
860
+ }
861
+ /**
862
+ * Generate a new service key for the project
863
+ */
864
+ async generateServiceKey(userToken, tenantId, data) {
865
+ const resp = await this.sdk.api.post(`/api/projects/${tenantId}/service-keys`, data, {
866
+ headers: { 'Authorization': `Bearer ${userToken}` }
867
+ });
868
+ return resp.data;
869
+ }
870
+ /**
871
+ * Revoke an existing service key
872
+ */
873
+ async revokeServiceKey(userToken, tenantId, keyId) {
874
+ const resp = await this.sdk.api.delete(`/api/projects/${tenantId}/service-keys/${keyId}`, {
875
+ headers: { 'Authorization': `Bearer ${userToken}` }
876
+ });
877
+ return resp.data;
878
+ }
879
+ /**
880
+ * Permanently wipe the project's identity enclave data
881
+ */
882
+ async wipeProjectEnclave(userToken, tenantId) {
883
+ const resp = await this.sdk.api.post(`/api/projects/${tenantId}/wipe`, {}, {
884
+ headers: { 'Authorization': `Bearer ${userToken}` }
885
+ });
886
+ return resp.data;
887
+ }
888
+ /**
889
+ * Retrieve security and audit logs for the project
890
+ */
891
+ async getProjectSecurityLogs(userToken, tenantId) {
892
+ const resp = await this.sdk.api.get(`/api/projects/${tenantId}/logs`, {
893
+ headers: { 'Authorization': `Bearer ${userToken}` }
894
+ });
895
+ return resp.data;
896
+ }
897
+ }
898
+
899
+ class ResourceModule {
900
+ sdk;
901
+ constructor(sdk) {
902
+ this.sdk = sdk;
903
+ }
904
+ /**
905
+ * Get the status of all resources for a specific tenant
906
+ */
907
+ async getStatus(userToken, tenantId) {
908
+ const resp = await this.sdk.api.get(`/api/resources/${tenantId}`, {
909
+ headers: { 'Authorization': `Bearer ${userToken}` }
910
+ });
911
+ return resp.data;
912
+ }
913
+ /**
914
+ * Provision a new resource type for a project
915
+ * @param resourceType 'database' | 'storage' | 'edge'
916
+ */
917
+ async provision(userToken, tenantId, resourceType) {
918
+ const resp = await this.sdk.api.post(`/api/resources/${tenantId}/provision/${resourceType}`, {}, {
919
+ headers: { 'Authorization': `Bearer ${userToken}` }
920
+ });
921
+ return resp.data;
922
+ }
923
+ /**
924
+ * Dissolve (delete) an existing resource from a project
925
+ */
926
+ async dissolve(userToken, tenantId, resourceType) {
927
+ const resp = await this.sdk.api.delete(`/api/resources/${tenantId}/provision/${resourceType}`, {
928
+ headers: { 'Authorization': `Bearer ${userToken}` }
929
+ });
930
+ return resp.data;
931
+ }
932
+ }
933
+
934
+ class SdkModule {
935
+ sdk;
936
+ constructor(sdk) {
937
+ this.sdk = sdk;
938
+ }
939
+ /**
940
+ * Get allowed origins for a project
941
+ */
942
+ async getOrigins(userToken, tenantId) {
943
+ const resp = await this.sdk.api.get(`/api/sdk/${tenantId}/origins`, {
944
+ headers: { 'Authorization': `Bearer ${userToken}` }
945
+ });
946
+ return resp.data;
947
+ }
948
+ /**
949
+ * Add an allowed origin for SDK requests
950
+ */
951
+ async addOrigin(userToken, tenantId, domain) {
952
+ const resp = await this.sdk.api.post(`/api/sdk/${tenantId}/origins`, { domain }, {
953
+ headers: { 'Authorization': `Bearer ${userToken}` }
954
+ });
955
+ return resp.data;
956
+ }
957
+ /**
958
+ * Remove an allowed origin
959
+ */
960
+ async removeOrigin(userToken, tenantId, domain) {
961
+ const resp = await this.sdk.api.delete(`/api/sdk/${tenantId}/origins/${domain}`, {
962
+ headers: { 'Authorization': `Bearer ${userToken}` }
963
+ });
964
+ return resp.data;
965
+ }
966
+ /**
967
+ * Test if an origin is allowed
968
+ */
969
+ async testOrigin(userToken, tenantId, domain) {
970
+ const resp = await this.sdk.api.post(`/api/sdk/${tenantId}/origins/test`, { domain }, {
971
+ headers: { 'Authorization': `Bearer ${userToken}` }
972
+ });
973
+ return resp.data;
974
+ }
975
+ }
976
+
977
+ class BillingModule {
978
+ sdk;
979
+ constructor(sdk) {
980
+ this.sdk = sdk;
981
+ }
982
+ /**
983
+ * Retrieve all available plans and their pricing
984
+ */
985
+ async getPlans() {
986
+ const resp = await this.sdk.api.get('/api/billing/plans');
987
+ return resp.data;
988
+ }
989
+ /**
990
+ * Get detailed billing and usage info for a project
991
+ */
992
+ async getProjectBillingInfo(userToken, tenantId) {
993
+ const resp = await this.sdk.api.get(`/api/billing/${tenantId}`, {
994
+ headers: { 'Authorization': `Bearer ${userToken}` }
995
+ });
996
+ return resp.data;
997
+ }
998
+ /**
999
+ * Sync payment status after a successful Razorpay transaction
1000
+ * @param paymentId The Razorpay payment ID
1001
+ */
1002
+ async syncPaymentStatus(userToken, paymentId) {
1003
+ const resp = await this.sdk.api.post('/api/billing/sync', { paymentId }, {
1004
+ headers: { 'Authorization': `Bearer ${userToken}` }
1005
+ });
1006
+ return resp.data;
1007
+ }
1008
+ /**
1009
+ * Get transaction history for the current user
1010
+ */
1011
+ async getTransactions(userToken) {
1012
+ const resp = await this.sdk.api.get('/api/billing/history/transactions', {
1013
+ headers: { 'Authorization': `Bearer ${userToken}` }
1014
+ });
1015
+ return resp.data;
1016
+ }
1017
+ /**
1018
+ * Get usage analytics and history
1019
+ */
1020
+ async getUsageAnalytics(userToken) {
1021
+ const resp = await this.sdk.api.get('/api/billing/history/analytics', {
1022
+ headers: { 'Authorization': `Bearer ${userToken}` }
1023
+ });
1024
+ return resp.data;
1025
+ }
1026
+ /**
1027
+ * Get billing alerts for a specific project
1028
+ */
1029
+ async getBillingAlerts(userToken, tenantId) {
1030
+ const resp = await this.sdk.api.get(`/api/billing/${tenantId}/alerts`, {
1031
+ headers: { 'Authorization': `Bearer ${userToken}` }
1032
+ });
1033
+ return resp.data;
1034
+ }
1035
+ /**
1036
+ * Create or update a billing alert
1037
+ */
1038
+ async createBillingAlert(userToken, tenantId, data) {
1039
+ const resp = await this.sdk.api.post(`/api/billing/${tenantId}/alerts`, data, {
1040
+ headers: { 'Authorization': `Bearer ${userToken}` }
1041
+ });
1042
+ return resp.data;
1043
+ }
1044
+ /**
1045
+ * Delete a billing alert
1046
+ */
1047
+ async deleteBillingAlert(userToken, alertId) {
1048
+ const resp = await this.sdk.api.delete(`/api/billing/alerts/${alertId}`, {
1049
+ headers: { 'Authorization': `Bearer ${userToken}` }
1050
+ });
1051
+ return resp.data;
1052
+ }
1053
+ /**
1054
+ * Generate a consolidated financial report
1055
+ */
1056
+ async getConsolidatedReport(userToken, query) {
1057
+ const resp = await this.sdk.api.get('/api/billing/report/consolidated', {
1058
+ params: query,
1059
+ headers: { 'Authorization': `Bearer ${userToken}` }
1060
+ });
1061
+ return resp.data;
1062
+ }
1063
+ }
1064
+
356
1065
  /**
357
1066
  * A React hook for managing QidCloud authentication lifecycle.
358
1067
  * Handles handshake initialization, WebSocket listeners, and profile fetching.
@@ -701,6 +1410,10 @@ class QidCloud {
701
1410
  edge;
702
1411
  vault;
703
1412
  logs;
1413
+ projects;
1414
+ resources;
1415
+ sdk;
1416
+ billing;
704
1417
  constructor(config) {
705
1418
  this.config = {
706
1419
  baseUrl: 'https://api.qidcloud.com',
@@ -718,6 +1431,10 @@ class QidCloud {
718
1431
  this.edge = new EdgeModule(this);
719
1432
  this.vault = new VaultModule(this);
720
1433
  this.logs = new LogsModule(this);
1434
+ this.projects = new ProjectModule(this);
1435
+ this.resources = new ResourceModule(this);
1436
+ this.sdk = new SdkModule(this);
1437
+ this.billing = new BillingModule(this);
721
1438
  }
722
1439
  /**
723
1440
  * Get the current project configuration