@wowsql/sdk 3.6.0 → 3.8.0

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/storage.js CHANGED
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  /**
3
- * WowSQL Storage SDK - S3 Storage management with automatic quota validation
3
+ * WowSQL Storage SDK - PostgreSQL-native file storage
4
4
  *
5
- * @version 2.1.0
5
+ * Files are stored as BYTEA inside each project's `storage` schema.
6
+ * No external S3 dependency — everything lives in PostgreSQL.
7
+ *
8
+ * @version 3.0.0
6
9
  * @license MIT
7
10
  */
8
11
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -44,359 +47,267 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
47
  Object.defineProperty(exports, "__esModule", { value: true });
45
48
  exports.WowSQLStorage = exports.StorageLimitExceededError = exports.StorageError = void 0;
46
49
  const axios_1 = __importDefault(require("axios"));
47
- const fs = __importStar(require("fs"));
48
- class StorageError extends Error {
49
- constructor(message, statusCode, response) {
50
- super(message);
51
- this.statusCode = statusCode;
52
- this.response = response;
53
- this.name = 'StorageError';
50
+ const errors_1 = require("./errors");
51
+ Object.defineProperty(exports, "StorageError", { enumerable: true, get: function () { return errors_1.StorageError; } });
52
+ Object.defineProperty(exports, "StorageLimitExceededError", { enumerable: true, get: function () { return errors_1.StorageLimitExceededError; } });
53
+ class StorageFileImpl {
54
+ constructor(data) {
55
+ this.id = data.id;
56
+ this.bucket_id = data.bucket_id;
57
+ this.name = data.name;
58
+ this.path = data.path;
59
+ this.mime_type = data.mime_type ?? null;
60
+ this.size = data.size ?? 0;
61
+ this.metadata = data.metadata ?? {};
62
+ this.created_at = data.created_at ?? '';
63
+ this.public_url = data.public_url;
54
64
  }
55
- }
56
- exports.StorageError = StorageError;
57
- class StorageLimitExceededError extends StorageError {
58
- constructor(message, statusCode, response) {
59
- super(message, statusCode, response);
60
- this.name = 'StorageLimitExceededError';
65
+ get sizeMb() {
66
+ return this.size / (1024 * 1024);
67
+ }
68
+ get sizeGb() {
69
+ return this.size / (1024 * 1024 * 1024);
61
70
  }
62
71
  }
63
- exports.StorageLimitExceededError = StorageLimitExceededError;
64
72
  // ==================== Storage Client ====================
65
73
  /**
66
- * WowSQL Storage Client - Manage S3 storage with automatic quota validation
74
+ * WowSQL Storage Client — PostgreSQL-native file storage.
67
75
  *
68
- * Features:
69
- * - Automatic storage limit validation before upload
70
- * - Real-time quota checking
71
- * - File upload/download/delete operations
72
- * - Presigned URL generation
73
- * - Storage provisioning and management
76
+ * All files are stored directly in the project's PostgreSQL database
77
+ * inside the `storage` schema (buckets + objects as BYTEA).
74
78
  *
75
79
  * @example
76
80
  * ```typescript
77
81
  * const storage = new WowSQLStorage({
78
- * projectSlug: 'myproject',
79
- * apiKey: 'your_api_key'
82
+ * projectUrl: 'myproject',
83
+ * apiKey: 'wowsql_anon_...'
80
84
  * });
81
85
  *
82
- * // Check quota
83
- * const quota = await storage.getQuota();
84
- * console.log(`Available: ${quota.storage_available_gb.toFixed(2)} GB`);
86
+ * // Create a bucket
87
+ * const bucket = await storage.createBucket('avatars', { public: true });
85
88
  *
86
- * // Upload file (auto-validates limits)
87
- * const fileBuffer = fs.readFileSync('document.pdf');
88
- * const result = await storage.uploadFile(fileBuffer, 'document.pdf', {
89
- * folder: 'documents'
90
- * });
89
+ * // Upload a file
90
+ * const file = document.querySelector('input[type="file"]').files[0];
91
+ * const result = await storage.upload('avatars', file, 'users/profile.jpg');
91
92
  *
92
93
  * // List files
93
- * const files = await storage.listFiles({ prefix: 'documents/' });
94
+ * const files = await storage.listFiles('avatars', { prefix: 'users/' });
95
+ *
96
+ * // Get public URL
97
+ * const url = storage.getPublicUrl('avatars', 'users/profile.jpg');
94
98
  * ```
95
99
  */
96
100
  class WowSQLStorage {
97
101
  constructor(config) {
98
- this.projectSlug = config.projectSlug;
99
- this.autoCheckQuota = config.autoCheckQuota !== false;
100
- const baseUrl = config.baseUrl || 'https://api.wowsql.com';
101
- // Create axios instance
102
+ const protocol = config.secure !== false ? 'https' : 'http';
103
+ if (config.baseUrl) {
104
+ this.baseUrl = config.baseUrl.replace(/\/+$/, '');
105
+ }
106
+ else if (config.projectUrl.startsWith('http://') || config.projectUrl.startsWith('https://')) {
107
+ this.baseUrl = config.projectUrl.replace(/\/+$/, '');
108
+ }
109
+ else {
110
+ const domain = config.baseDomain || 'wowsql.com';
111
+ if (config.projectUrl.includes(`.${domain}`) || config.projectUrl.endsWith(domain)) {
112
+ this.baseUrl = `${protocol}://${config.projectUrl}`;
113
+ }
114
+ else {
115
+ this.baseUrl = `${protocol}://${config.projectUrl}.${domain}`;
116
+ }
117
+ }
118
+ this.projectSlug = config.projectSlug || config.projectUrl.split('.')[0].replace(/^https?:\/\//, '');
102
119
  this.client = axios_1.default.create({
103
- baseURL: baseUrl,
120
+ baseURL: `${this.baseUrl}/api/v1/storage`,
104
121
  headers: {
105
122
  'Authorization': `Bearer ${config.apiKey}`,
106
123
  },
107
- timeout: config.timeout || 60000, // 60s default for file uploads
124
+ timeout: config.timeout || 60000,
108
125
  });
109
- // Add error interceptor
110
126
  this.client.interceptors.response.use((response) => response, (error) => {
111
127
  if (error.response) {
112
128
  const errorData = error.response.data;
113
129
  const errorMessage = errorData?.detail || errorData?.message || error.message;
114
- // Check for storage limit exceeded
115
130
  if (error.response.status === 413) {
116
- throw new StorageLimitExceededError(errorMessage, error.response.status, errorData);
131
+ throw new errors_1.StorageLimitExceededError(errorMessage, 413, errorData);
117
132
  }
118
- throw new StorageError(errorMessage, error.response.status, errorData);
133
+ throw new errors_1.StorageError(errorMessage, error.response.status, errorData);
119
134
  }
120
- throw new StorageError(error.message);
135
+ throw new errors_1.StorageError(error.message);
121
136
  });
122
137
  }
138
+ // ==================== Bucket Operations ====================
123
139
  /**
124
- * Get storage quota and usage information
140
+ * Create a new storage bucket.
125
141
  *
126
- * @param forceRefresh - Force refresh quota from server (default: false)
127
- * @returns Storage quota details
128
- *
129
- * @example
130
- * ```typescript
131
- * const quota = await storage.getQuota();
132
- * console.log(`Used: ${quota.storage_used_gb} GB`);
133
- * console.log(`Available: ${quota.storage_available_gb} GB`);
134
- * console.log(`Usage: ${quota.usage_percentage}%`);
135
- * ```
142
+ * @param name - Bucket name (unique per project)
143
+ * @param options - Bucket configuration
136
144
  */
137
- async getQuota(forceRefresh = false) {
138
- if (this.quotaCache && !forceRefresh) {
139
- return this.quotaCache;
140
- }
141
- const response = await this.client.get(`/api/v1/storage/s3/projects/${this.projectSlug}/quota`);
142
- this.quotaCache = response.data;
145
+ async createBucket(name, options) {
146
+ const response = await this.client.post(`/projects/${this.projectSlug}/buckets`, {
147
+ name,
148
+ public: options?.public ?? false,
149
+ file_size_limit: options?.fileSizeLimit ?? null,
150
+ allowed_mime_types: options?.allowedMimeTypes ?? null,
151
+ });
143
152
  return response.data;
144
153
  }
145
154
  /**
146
- * Check if file upload is allowed based on storage quota
147
- *
148
- * @param fileSizeBytes - Size of file to upload in bytes
149
- * @returns Object with allowed status and message
150
- *
151
- * @example
152
- * ```typescript
153
- * const fileSize = 1024 * 1024 * 500; // 500 MB
154
- * const check = await storage.checkUploadAllowed(fileSize);
155
- * if (!check.allowed) {
156
- * console.error(check.message);
157
- * }
158
- * ```
155
+ * List all buckets in the project.
159
156
  */
160
- async checkUploadAllowed(fileSizeBytes) {
161
- const quota = await this.getQuota(true);
162
- const fileSizeGB = fileSizeBytes / (1024 ** 3);
163
- if (fileSizeGB > quota.storage_available_gb) {
164
- return {
165
- allowed: false,
166
- message: `Storage limit exceeded! File size: ${fileSizeGB.toFixed(4)} GB, ` +
167
- `Available: ${quota.storage_available_gb.toFixed(4)} GB. ` +
168
- `Upgrade your plan to get more storage.`
169
- };
170
- }
171
- return {
172
- allowed: true,
173
- message: `Upload allowed. ${quota.storage_available_gb.toFixed(4)} GB available.`
174
- };
157
+ async listBuckets() {
158
+ const response = await this.client.get(`/projects/${this.projectSlug}/buckets`);
159
+ return response.data;
175
160
  }
176
161
  /**
177
- * Upload a file to S3 storage with automatic quota validation
178
- *
179
- * @param fileData - File data as Buffer or Blob
180
- * @param fileName - File name
181
- * @param options - Upload options
182
- * @returns Upload result
183
- *
184
- * @throws {StorageLimitExceededError} If storage quota would be exceeded
185
- * @throws {StorageError} If upload fails
186
- *
187
- * @example
188
- * ```typescript
189
- * // Node.js - from file
190
- * const fileBuffer = fs.readFileSync('photo.jpg');
191
- * const result = await storage.uploadFile(fileBuffer, 'photo.jpg', {
192
- * folder: 'images',
193
- * contentType: 'image/jpeg'
194
- * });
162
+ * Get a specific bucket by name.
163
+ */
164
+ async getBucket(name) {
165
+ const response = await this.client.get(`/projects/${this.projectSlug}/buckets/${name}`);
166
+ return response.data;
167
+ }
168
+ /**
169
+ * Update bucket settings.
170
+ */
171
+ async updateBucket(name, updates) {
172
+ const body = {};
173
+ if (updates.name !== undefined)
174
+ body.name = updates.name;
175
+ if (updates.public !== undefined)
176
+ body.public = updates.public;
177
+ if (updates.fileSizeLimit !== undefined)
178
+ body.file_size_limit = updates.fileSizeLimit;
179
+ if (updates.allowedMimeTypes !== undefined)
180
+ body.allowed_mime_types = updates.allowedMimeTypes;
181
+ const response = await this.client.patch(`/projects/${this.projectSlug}/buckets/${name}`, body);
182
+ return response.data;
183
+ }
184
+ /**
185
+ * Delete a bucket and all its files.
186
+ */
187
+ async deleteBucket(name) {
188
+ const response = await this.client.delete(`/projects/${this.projectSlug}/buckets/${name}`);
189
+ return response.data;
190
+ }
191
+ // ==================== File Operations ====================
192
+ /**
193
+ * Upload a file to a bucket.
195
194
  *
196
- * // Browser - from File input
197
- * const file = document.querySelector('input[type="file"]').files[0];
198
- * const arrayBuffer = await file.arrayBuffer();
199
- * const result = await storage.uploadFile(
200
- * Buffer.from(arrayBuffer),
201
- * file.name,
202
- * { folder: 'uploads' }
203
- * );
204
- * ```
195
+ * @param bucketName - Target bucket
196
+ * @param fileData - File data (File, Blob, or Buffer)
197
+ * @param path - Optional path within the bucket (e.g., 'users/avatar.png')
198
+ * @param fileName - Optional file name override
205
199
  */
206
- async uploadFile(fileData, fileName, options) {
207
- // Get file size
208
- const fileSize = fileData instanceof Buffer ? fileData.length : fileData.size;
209
- // Check quota if enabled
210
- const shouldCheck = options?.checkQuota !== undefined ? options.checkQuota : this.autoCheckQuota;
211
- if (shouldCheck) {
212
- const check = await this.checkUploadAllowed(fileSize);
213
- if (!check.allowed) {
214
- throw new StorageLimitExceededError(check.message, 413);
215
- }
216
- }
217
- // Prepare form data
200
+ async upload(bucketName, fileData, path, fileName) {
218
201
  const formData = new FormData();
219
202
  const blob = fileData instanceof Buffer ? new Blob([fileData]) : fileData;
220
- formData.append('file', blob, fileName);
221
- // Build URL with query params
203
+ const name = fileName || fileData.name || path || 'file';
204
+ formData.append('file', blob, name);
222
205
  const params = new URLSearchParams();
223
- if (options?.folder) {
224
- params.append('folder', options.folder);
206
+ if (path) {
207
+ const folder = path.includes('/') ? path.substring(0, path.lastIndexOf('/')) : '';
208
+ if (folder)
209
+ params.append('folder', folder);
225
210
  }
226
- const url = `/api/v1/storage/s3/projects/${this.projectSlug}/upload${params.toString() ? '?' + params.toString() : ''}`;
227
- // Upload
211
+ const url = `/projects/${this.projectSlug}/buckets/${bucketName}/files${params.toString() ? '?' + params.toString() : ''}`;
228
212
  const response = await this.client.post(url, formData, {
229
- headers: {
230
- 'Content-Type': 'multipart/form-data',
231
- },
213
+ headers: { 'Content-Type': 'multipart/form-data' },
232
214
  });
233
- // Clear quota cache after upload
234
- this.quotaCache = undefined;
235
- return response.data;
215
+ return new StorageFileImpl(response.data);
236
216
  }
237
217
  /**
238
- * Upload a file from filesystem path (Node.js only)
239
- *
240
- * @param filePath - Path to local file
241
- * @param fileName - Optional file name in bucket (defaults to filename)
242
- * @param options - Upload options
243
- * @returns Upload result
218
+ * Upload a file (compatibility alias for upload).
244
219
  *
245
- * @example
246
- * ```typescript
247
- * const result = await storage.uploadFromPath(
248
- * 'documents/report.pdf',
249
- * 'report.pdf',
250
- * { folder: 'reports' }
251
- * );
252
- * ```
220
+ * @param bucketName - Target bucket
221
+ * @param fileData - File data (File, Blob, or Buffer)
222
+ * @param path - Optional path within the bucket
223
+ * @param fileName - Optional file name override
253
224
  */
254
- async uploadFromPath(filePath, fileName, options) {
255
- if (!fs.existsSync(filePath)) {
256
- throw new Error(`File not found: ${filePath}`);
257
- }
258
- const fileBuffer = fs.readFileSync(filePath);
259
- const name = fileName || filePath.split('/').pop() || 'file';
260
- return this.uploadFile(fileBuffer, name, options);
225
+ async uploadFile(bucketName, fileData, path, fileName) {
226
+ return this.upload(bucketName, fileData, path, fileName);
261
227
  }
262
228
  /**
263
- * List files in S3 bucket
229
+ * Upload a file from a local file path (Node.js only).
264
230
  *
265
- * @param options - List options
266
- * @returns Array of storage files
231
+ * @param filePath - Local file path to upload
232
+ * @param bucketName - Target bucket (defaults to 'default')
233
+ * @param path - Optional path within the bucket
234
+ */
235
+ async uploadFromPath(filePath, bucketName = 'default', path) {
236
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
237
+ const nodePath = await Promise.resolve().then(() => __importStar(require('path')));
238
+ const data = fs.readFileSync(filePath);
239
+ const fileName = nodePath.basename(filePath);
240
+ return this.upload(bucketName, Buffer.from(data), path || fileName, fileName);
241
+ }
242
+ /**
243
+ * List files in a bucket.
267
244
  *
268
- * @example
269
- * ```typescript
270
- * const files = await storage.listFiles({ prefix: 'documents/' });
271
- * for (const file of files) {
272
- * console.log(`${file.key}: ${(file.size / 1024 / 1024).toFixed(2)} MB`);
273
- * }
274
- * ```
245
+ * @param bucketName - Bucket to list
246
+ * @param options - Filter and pagination options
275
247
  */
276
- async listFiles(options) {
248
+ async listFiles(bucketName, options) {
277
249
  const params = {};
278
250
  if (options?.prefix)
279
251
  params.prefix = options.prefix;
280
- if (options?.maxKeys)
281
- params.max_keys = options.maxKeys;
282
- const response = await this.client.get(`/api/v1/storage/s3/projects/${this.projectSlug}/files`, { params });
283
- return response.data.files || [];
252
+ if (options?.limit)
253
+ params.limit = options.limit;
254
+ if (options?.offset)
255
+ params.offset = options.offset;
256
+ const response = await this.client.get(`/projects/${this.projectSlug}/buckets/${bucketName}/files`, { params });
257
+ return response.data.map((f) => new StorageFileImpl(f));
284
258
  }
285
259
  /**
286
- * Delete a file from S3 bucket
287
- *
288
- * @param fileKey - Path to file in bucket
289
- * @returns Deletion result
260
+ * Download a file. Returns binary data as ArrayBuffer.
290
261
  *
291
- * @example
292
- * ```typescript
293
- * const result = await storage.deleteFile('documents/old-file.pdf');
294
- * console.log(result.message);
295
- * ```
262
+ * @param bucketName - Bucket name
263
+ * @param filePath - Path to file within the bucket
296
264
  */
297
- async deleteFile(fileKey) {
298
- const response = await this.client.delete(`/api/v1/storage/s3/projects/${this.projectSlug}/files/${fileKey}`);
299
- // Clear quota cache after delete
300
- this.quotaCache = undefined;
265
+ async download(bucketName, filePath) {
266
+ const response = await this.client.get(`/projects/${this.projectSlug}/files/${bucketName}/${filePath}`, { responseType: 'arraybuffer' });
301
267
  return response.data;
302
268
  }
303
269
  /**
304
- * Get presigned URL for file access
270
+ * Download a file and save to a local path (Node.js only).
305
271
  *
306
- * @param fileKey - Path to file in bucket
307
- * @param expiresIn - URL validity in seconds (default: 3600 = 1 hour)
308
- * @returns File URL and metadata
309
- *
310
- * @example
311
- * ```typescript
312
- * const urlData = await storage.getFileUrl('photo.jpg', 7200);
313
- * console.log(urlData.file_url); // Use this for downloads
314
- * ```
272
+ * @param bucketName - Bucket name
273
+ * @param filePath - Path to file within the bucket
274
+ * @param localPath - Local file path to save to
315
275
  */
316
- async getFileUrl(fileKey, expiresIn = 3600) {
317
- const response = await this.client.get(`/api/v1/storage/s3/projects/${this.projectSlug}/files/${fileKey}/url`, { params: { expires_in: expiresIn } });
318
- return response.data;
276
+ async downloadToFile(bucketName, filePath, localPath) {
277
+ const data = await this.download(bucketName, filePath);
278
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
279
+ fs.writeFileSync(localPath, Buffer.from(data));
319
280
  }
320
281
  /**
321
- * Generate presigned URL for file operations
322
- *
323
- * @param fileKey - Path to file in bucket
324
- * @param options - Presigned URL options
325
- * @returns Presigned URL string
326
- *
327
- * @example
328
- * ```typescript
329
- * // Download URL
330
- * const downloadUrl = await storage.getPresignedUrl('file.pdf');
282
+ * Delete a file from a bucket.
331
283
  *
332
- * // Upload URL
333
- * const uploadUrl = await storage.getPresignedUrl('new-file.pdf', {
334
- * operation: 'put_object',
335
- * expiresIn: 1800
336
- * });
337
- * ```
284
+ * @param bucketName - Bucket name
285
+ * @param filePath - Path to file within the bucket
338
286
  */
339
- async getPresignedUrl(fileKey, options) {
340
- const response = await this.client.post(`/api/v1/storage/s3/projects/${this.projectSlug}/presigned-url`, {
341
- file_key: fileKey,
342
- expires_in: options?.expiresIn || 3600,
343
- operation: options?.operation || 'get_object',
344
- });
345
- return response.data.url;
287
+ async deleteFile(bucketName, filePath) {
288
+ const response = await this.client.delete(`/projects/${this.projectSlug}/files/${bucketName}/${filePath}`);
289
+ return response.data;
346
290
  }
291
+ // ==================== Utilities ====================
347
292
  /**
348
- * Get S3 storage information for the project
349
- *
350
- * @returns Storage information
351
- *
352
- * @example
353
- * ```typescript
354
- * const info = await storage.getStorageInfo();
355
- * console.log(`Bucket: ${info.bucket_name}`);
356
- * console.log(`Region: ${info.region}`);
357
- * console.log(`Objects: ${info.total_objects}`);
358
- * console.log(`Size: ${info.total_size_gb.toFixed(2)} GB`);
359
- * ```
293
+ * Get the public URL for a file in a public bucket.
294
+ * This URL works without authentication.
360
295
  */
361
- async getStorageInfo() {
362
- const response = await this.client.get(`/api/v1/storage/s3/projects/${this.projectSlug}/info`);
363
- return response.data;
296
+ getPublicUrl(bucketName, filePath) {
297
+ return `${this.baseUrl}/storage/v1/object/public/${bucketName}/${filePath}`;
364
298
  }
365
299
  /**
366
- * Provision S3 storage for the project
367
- *
368
- * **IMPORTANT**: Save the credentials returned! They're only shown once.
369
- *
370
- * @param region - AWS region (default: 'us-east-1')
371
- * @returns Provisioning result with credentials
372
- *
373
- * @example
374
- * ```typescript
375
- * const result = await storage.provisionStorage('us-west-2');
376
- * console.log(`Bucket: ${result.bucket_name}`);
377
- * console.log(`Access Key: ${result.credentials.access_key_id}`);
378
- * // SAVE THESE CREDENTIALS SECURELY!
379
- * ```
300
+ * Get storage statistics for the project.
380
301
  */
381
- async provisionStorage(region = 'us-east-1') {
382
- const response = await this.client.post(`/api/v1/storage/s3/projects/${this.projectSlug}/provision`, { region });
302
+ async getStats() {
303
+ const response = await this.client.get(`/projects/${this.projectSlug}/stats`);
383
304
  return response.data;
384
305
  }
385
306
  /**
386
- * Get list of available S3 regions with pricing
387
- *
388
- * @returns Array of available regions
389
- *
390
- * @example
391
- * ```typescript
392
- * const regions = await storage.getAvailableRegions();
393
- * for (const region of regions) {
394
- * console.log(`${region.name}: $${region.storage_price_gb}/GB/month`);
395
- * }
396
- * ```
307
+ * Get storage quota for the project.
397
308
  */
398
- async getAvailableRegions() {
399
- const response = await this.client.get('/api/v1/storage/s3/regions');
309
+ async getQuota() {
310
+ const response = await this.client.get(`/projects/${this.projectSlug}/quota`);
400
311
  return response.data;
401
312
  }
402
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wowsql/sdk",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
4
4
  "description": "Official TypeScript/JavaScript SDK for WowSQL - MySQL Backend-as-a-Service with S3 Storage, type-safe queries and fluent API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",