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