@veloxts/storage 0.6.91 → 0.6.92
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/drivers/local.js +31 -2
- package/dist/drivers/s3.js +79 -26
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +18 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/manager.d.ts +53 -1
- package/dist/manager.js +3 -0
- package/dist/plugin.js +2 -0
- package/dist/providers/index.d.ts +30 -0
- package/dist/providers/index.js +32 -0
- package/dist/providers/minio.d.ts +74 -0
- package/dist/providers/minio.js +68 -0
- package/dist/providers/r2.d.ts +75 -0
- package/dist/providers/r2.js +73 -0
- package/dist/testing/index.d.ts +20 -0
- package/dist/testing/index.js +20 -0
- package/dist/testing/provider-compliance.d.ts +72 -0
- package/dist/testing/provider-compliance.js +365 -0
- package/dist/types.d.ts +45 -2
- package/package.json +28 -3
package/dist/drivers/local.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { createReadStream, createWriteStream } from 'node:fs';
|
|
8
8
|
import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
9
9
|
import { dirname, join, relative } from 'node:path';
|
|
10
|
+
import { StorageObjectNotFoundError } from '../errors.js';
|
|
10
11
|
import { detectMimeType, isReadableStream, normalizePath, toBuffer, validatePath, } from '../utils.js';
|
|
11
12
|
/**
|
|
12
13
|
* Default configuration for local storage.
|
|
@@ -93,6 +94,10 @@ export function createLocalStore(config) {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
const store = {
|
|
97
|
+
async init() {
|
|
98
|
+
// Create root directory if it doesn't exist
|
|
99
|
+
await mkdir(root, { recursive: true });
|
|
100
|
+
},
|
|
96
101
|
async put(path, content, putOptions = {}) {
|
|
97
102
|
const fullPath = getFullPath(path);
|
|
98
103
|
await ensureDir(fullPath);
|
|
@@ -101,9 +106,20 @@ export function createLocalStore(config) {
|
|
|
101
106
|
// Stream content to file
|
|
102
107
|
await new Promise((resolve, reject) => {
|
|
103
108
|
const writeStream = createWriteStream(fullPath);
|
|
104
|
-
content
|
|
109
|
+
const readStream = content;
|
|
110
|
+
// Handle errors on both streams to prevent resource leaks
|
|
111
|
+
readStream.on('error', (err) => {
|
|
112
|
+
writeStream.destroy();
|
|
113
|
+
reject(err);
|
|
114
|
+
});
|
|
115
|
+
writeStream.on('error', (err) => {
|
|
116
|
+
if (typeof readStream.destroy === 'function') {
|
|
117
|
+
readStream.destroy();
|
|
118
|
+
}
|
|
119
|
+
reject(err);
|
|
120
|
+
});
|
|
105
121
|
writeStream.on('finish', resolve);
|
|
106
|
-
|
|
122
|
+
readStream.pipe(writeStream);
|
|
107
123
|
});
|
|
108
124
|
}
|
|
109
125
|
else {
|
|
@@ -236,6 +252,13 @@ export function createLocalStore(config) {
|
|
|
236
252
|
return null;
|
|
237
253
|
}
|
|
238
254
|
},
|
|
255
|
+
async head(path) {
|
|
256
|
+
const result = await store.metadata(path);
|
|
257
|
+
if (result === null) {
|
|
258
|
+
throw new StorageObjectNotFoundError(path);
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
},
|
|
239
262
|
async list(prefix = '', listOptions = {}) {
|
|
240
263
|
const { recursive = false, limit = 1000 } = listOptions;
|
|
241
264
|
const files = [];
|
|
@@ -286,6 +309,12 @@ export function createLocalStore(config) {
|
|
|
286
309
|
// In production, you'd implement a token-based verification endpoint
|
|
287
310
|
return store.url(path);
|
|
288
311
|
},
|
|
312
|
+
async signedUploadUrl(options) {
|
|
313
|
+
// Local storage doesn't support true presigned upload URLs
|
|
314
|
+
// Return the URL where the file would be served from
|
|
315
|
+
// In production, you'd implement a token-based upload endpoint
|
|
316
|
+
return store.url(options.key);
|
|
317
|
+
},
|
|
289
318
|
async setVisibility(path, visibility) {
|
|
290
319
|
const storedMeta = (await readStoredMetadata(path)) ?? {
|
|
291
320
|
visibility,
|
package/dist/drivers/s3.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* S3-compatible file storage implementation.
|
|
5
5
|
* Works with AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, and other S3-compatible services.
|
|
6
6
|
*/
|
|
7
|
+
import { StorageObjectNotFoundError } from '../errors.js';
|
|
7
8
|
import { detectMimeType, isReadableStream, joinPath, normalizePath, streamToBuffer, // Still needed for get()
|
|
8
9
|
toBuffer, validatePath, } from '../utils.js';
|
|
9
10
|
/**
|
|
@@ -50,7 +51,7 @@ const DEFAULT_CONFIG = {
|
|
|
50
51
|
*/
|
|
51
52
|
export async function createS3Store(config) {
|
|
52
53
|
const options = { ...DEFAULT_CONFIG, ...config };
|
|
53
|
-
const { bucket, region, endpoint, accessKeyId, secretAccessKey, forcePathStyle, defaultVisibility, prefix, } = options;
|
|
54
|
+
const { bucket, region, endpoint, accessKeyId, secretAccessKey, forcePathStyle, defaultVisibility, prefix, publicUrl, } = options;
|
|
54
55
|
// Dynamic import of AWS SDK (peer dependency)
|
|
55
56
|
const { S3Client, CopyObjectCommand, DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, } = await import('@aws-sdk/client-s3');
|
|
56
57
|
const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
|
@@ -108,6 +109,19 @@ export async function createS3Store(config) {
|
|
|
108
109
|
// Import Upload for streaming multipart uploads (prevents memory exhaustion for large files)
|
|
109
110
|
const { Upload } = await import('@aws-sdk/lib-storage');
|
|
110
111
|
const store = {
|
|
112
|
+
async init() {
|
|
113
|
+
// Verify connectivity by listing a single object
|
|
114
|
+
try {
|
|
115
|
+
await client.send(new ListObjectsV2Command({
|
|
116
|
+
Bucket: bucket,
|
|
117
|
+
MaxKeys: 1,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
122
|
+
throw new Error(`S3 storage init failed for bucket "${bucket}": ${message}`);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
111
125
|
async put(path, content, putOptions = {}) {
|
|
112
126
|
const key = getKey(path);
|
|
113
127
|
const visibility = putOptions.visibility ?? defaultVisibility ?? 'private';
|
|
@@ -229,28 +243,33 @@ export async function createS3Store(config) {
|
|
|
229
243
|
if (paths.length === 0) {
|
|
230
244
|
return 0;
|
|
231
245
|
}
|
|
232
|
-
// S3 supports batch delete of up to 1000 objects
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
246
|
+
// S3 supports batch delete of up to 1000 objects per request
|
|
247
|
+
const BATCH_SIZE = 1000;
|
|
248
|
+
let totalDeleted = 0;
|
|
249
|
+
// Process in batches to respect S3 API limit
|
|
250
|
+
for (let i = 0; i < paths.length; i += BATCH_SIZE) {
|
|
251
|
+
const batch = paths.slice(i, i + BATCH_SIZE);
|
|
252
|
+
const keys = batch.map((path) => ({ Key: getKey(path) }));
|
|
253
|
+
try {
|
|
254
|
+
const response = await client.send(new DeleteObjectsCommand({
|
|
255
|
+
Bucket: bucket,
|
|
256
|
+
Delete: {
|
|
257
|
+
Objects: keys,
|
|
258
|
+
Quiet: false,
|
|
259
|
+
},
|
|
260
|
+
}));
|
|
261
|
+
totalDeleted += response.Deleted?.length ?? 0;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Fall back to individual deletes if batch fails
|
|
265
|
+
const results = await Promise.all(batch.map((path) => store.delete(path)));
|
|
266
|
+
for (const deleted of results) {
|
|
267
|
+
if (deleted)
|
|
268
|
+
totalDeleted++;
|
|
269
|
+
}
|
|
251
270
|
}
|
|
252
|
-
return count;
|
|
253
271
|
}
|
|
272
|
+
return totalDeleted;
|
|
254
273
|
},
|
|
255
274
|
async copy(source, destination, copyOptions = {}) {
|
|
256
275
|
const sourceKey = getKey(source);
|
|
@@ -260,8 +279,11 @@ export async function createS3Store(config) {
|
|
|
260
279
|
const visibility = copyOptions.visibility ?? sourceMeta?.visibility ?? defaultVisibility ?? 'private';
|
|
261
280
|
await client.send(new CopyObjectCommand({
|
|
262
281
|
Bucket: bucket,
|
|
263
|
-
// CopySource
|
|
264
|
-
CopySource:
|
|
282
|
+
// CopySource format: bucket/key - encode key segments but not the bucket/key separator
|
|
283
|
+
CopySource: `${bucket}/${sourceKey
|
|
284
|
+
.split('/')
|
|
285
|
+
.map((s) => encodeURIComponent(s))
|
|
286
|
+
.join('/')}`,
|
|
265
287
|
Key: destKey,
|
|
266
288
|
ACL: visibilityToAcl(visibility),
|
|
267
289
|
}));
|
|
@@ -294,6 +316,13 @@ export async function createS3Store(config) {
|
|
|
294
316
|
throw error;
|
|
295
317
|
}
|
|
296
318
|
},
|
|
319
|
+
async head(path) {
|
|
320
|
+
const result = await store.metadata(path);
|
|
321
|
+
if (result === null) {
|
|
322
|
+
throw new StorageObjectNotFoundError(path);
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
},
|
|
297
326
|
async list(listPrefix = '', listOptions = {}) {
|
|
298
327
|
const { recursive = false, limit = 1000, cursor } = listOptions;
|
|
299
328
|
const fullPrefix = prefix ? joinPath(prefix, listPrefix) : listPrefix;
|
|
@@ -322,7 +351,18 @@ export async function createS3Store(config) {
|
|
|
322
351
|
},
|
|
323
352
|
async url(path) {
|
|
324
353
|
const key = getKey(path);
|
|
325
|
-
|
|
354
|
+
// URL-encode each path segment separately to preserve `/` delimiters
|
|
355
|
+
// but properly encode special characters like spaces, ?, #, etc.
|
|
356
|
+
const encodedKey = key
|
|
357
|
+
.split('/')
|
|
358
|
+
.map((segment) => encodeURIComponent(segment))
|
|
359
|
+
.join('/');
|
|
360
|
+
// Use custom publicUrl (e.g., CDN) if provided, otherwise use S3 URL
|
|
361
|
+
if (publicUrl) {
|
|
362
|
+
const base = publicUrl.endsWith('/') ? publicUrl.slice(0, -1) : publicUrl;
|
|
363
|
+
return `${base}/${encodedKey}`;
|
|
364
|
+
}
|
|
365
|
+
return `${getBaseUrl()}/${encodedKey}`;
|
|
326
366
|
},
|
|
327
367
|
async signedUrl(path, signedOptions = {}) {
|
|
328
368
|
const key = getKey(path);
|
|
@@ -335,13 +375,26 @@ export async function createS3Store(config) {
|
|
|
335
375
|
});
|
|
336
376
|
return await getSignedUrl(client, command, { expiresIn });
|
|
337
377
|
},
|
|
378
|
+
async signedUploadUrl(options) {
|
|
379
|
+
const { key, expiresIn = 3600, contentType } = options;
|
|
380
|
+
const fullKey = getKey(key);
|
|
381
|
+
const command = new PutObjectCommand({
|
|
382
|
+
Bucket: bucket,
|
|
383
|
+
Key: fullKey,
|
|
384
|
+
ContentType: contentType,
|
|
385
|
+
});
|
|
386
|
+
return await getSignedUrl(client, command, { expiresIn });
|
|
387
|
+
},
|
|
338
388
|
async setVisibility(path, visibility) {
|
|
339
389
|
const key = getKey(path);
|
|
340
390
|
// S3 requires copying the object to itself to change ACL
|
|
341
391
|
await client.send(new CopyObjectCommand({
|
|
342
392
|
Bucket: bucket,
|
|
343
|
-
// CopySource
|
|
344
|
-
CopySource:
|
|
393
|
+
// CopySource format: bucket/key - encode key segments but not the bucket/key separator
|
|
394
|
+
CopySource: `${bucket}/${key
|
|
395
|
+
.split('/')
|
|
396
|
+
.map((s) => encodeURIComponent(s))
|
|
397
|
+
.join('/')}`,
|
|
345
398
|
Key: key,
|
|
346
399
|
ACL: visibilityToAcl(visibility),
|
|
347
400
|
MetadataDirective: 'COPY',
|
package/dist/errors.d.ts
CHANGED
|
@@ -70,6 +70,15 @@ export declare class S3Error extends StorageError {
|
|
|
70
70
|
readonly statusCode?: number;
|
|
71
71
|
constructor(operation: string, message: string, statusCode?: number);
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Error thrown when a storage object is not found.
|
|
75
|
+
* Used by head() and similar methods that require the object to exist.
|
|
76
|
+
*/
|
|
77
|
+
export declare class StorageObjectNotFoundError extends StorageError {
|
|
78
|
+
readonly code = "STORAGE_OBJECT_NOT_FOUND";
|
|
79
|
+
readonly key: string;
|
|
80
|
+
constructor(key: string);
|
|
81
|
+
}
|
|
73
82
|
/**
|
|
74
83
|
* Type guard to check if an error is a StorageError.
|
|
75
84
|
*/
|
|
@@ -82,3 +91,7 @@ export declare function isFileNotFoundError(error: unknown): error is FileNotFou
|
|
|
82
91
|
* Type guard to check if an error is a PermissionDeniedError.
|
|
83
92
|
*/
|
|
84
93
|
export declare function isPermissionDeniedError(error: unknown): error is PermissionDeniedError;
|
|
94
|
+
/**
|
|
95
|
+
* Type guard to check if an error is a StorageObjectNotFoundError.
|
|
96
|
+
*/
|
|
97
|
+
export declare function isStorageObjectNotFoundError(error: unknown): error is StorageObjectNotFoundError;
|
package/dist/errors.js
CHANGED
|
@@ -99,6 +99,18 @@ export class S3Error extends StorageError {
|
|
|
99
99
|
this.statusCode = statusCode;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Error thrown when a storage object is not found.
|
|
104
|
+
* Used by head() and similar methods that require the object to exist.
|
|
105
|
+
*/
|
|
106
|
+
export class StorageObjectNotFoundError extends StorageError {
|
|
107
|
+
code = 'STORAGE_OBJECT_NOT_FOUND';
|
|
108
|
+
key;
|
|
109
|
+
constructor(key) {
|
|
110
|
+
super(`Storage object not found: ${key}`);
|
|
111
|
+
this.key = key;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
102
114
|
/**
|
|
103
115
|
* Type guard to check if an error is a StorageError.
|
|
104
116
|
*/
|
|
@@ -117,3 +129,9 @@ export function isFileNotFoundError(error) {
|
|
|
117
129
|
export function isPermissionDeniedError(error) {
|
|
118
130
|
return error instanceof PermissionDeniedError;
|
|
119
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Type guard to check if an error is a StorageObjectNotFoundError.
|
|
134
|
+
*/
|
|
135
|
+
export function isStorageObjectNotFoundError(error) {
|
|
136
|
+
return error instanceof StorageObjectNotFoundError;
|
|
137
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
*/
|
|
40
40
|
export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from './drivers/local.js';
|
|
41
41
|
export { createS3Store, DRIVER_NAME as S3_DRIVER } from './drivers/s3.js';
|
|
42
|
-
export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, } from './errors.js';
|
|
42
|
+
export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, isStorageObjectNotFoundError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, StorageObjectNotFoundError, } from './errors.js';
|
|
43
43
|
export { createStorageManager, type StorageManager, storage } from './manager.js';
|
|
44
44
|
export { _resetStandaloneStorage, closeStorage, getStorage, getStorageFromInstance, storagePlugin, } from './plugin.js';
|
|
45
|
-
export type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, LocalStorageConfig, PutOptions, S3StorageConfig, SignedUrlOptions, StorageBaseOptions, StorageConfig, StorageDefaultOptions, StorageDriver, StorageLocalOptions, StorageManagerOptions, StoragePluginOptions, StorageS3Options, StorageStore, } from './types.js';
|
|
45
|
+
export type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, LocalStorageConfig, PutOptions, S3StorageConfig, SignedUploadOptions, SignedUrlOptions, StorageBaseOptions, StorageConfig, StorageDefaultOptions, StorageDriver, StorageLocalOptions, StorageManagerOptions, StoragePluginOptions, StorageProvider, StorageS3Options, StorageStore, } from './types.js';
|
|
46
46
|
/**
|
|
47
47
|
* Utility functions for storage operations.
|
|
48
48
|
*
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from './drivers/local.js';
|
|
42
42
|
export { createS3Store, DRIVER_NAME as S3_DRIVER } from './drivers/s3.js';
|
|
43
43
|
// Errors
|
|
44
|
-
export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, } from './errors.js';
|
|
44
|
+
export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, isStorageObjectNotFoundError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, StorageObjectNotFoundError, } from './errors.js';
|
|
45
45
|
// Manager
|
|
46
46
|
export { createStorageManager, storage } from './manager.js';
|
|
47
47
|
// Plugin
|
package/dist/manager.d.ts
CHANGED
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
* High-level file storage API with unified interface across all drivers.
|
|
5
5
|
*/
|
|
6
6
|
import type { Readable } from 'node:stream';
|
|
7
|
-
import type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, PutOptions, SignedUrlOptions, StorageManagerOptions } from './types.js';
|
|
7
|
+
import type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, PutOptions, SignedUploadOptions, SignedUrlOptions, StorageManagerOptions } from './types.js';
|
|
8
8
|
/**
|
|
9
9
|
* Storage manager interface providing a unified file storage API.
|
|
10
10
|
*/
|
|
11
11
|
export interface StorageManager {
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the storage manager.
|
|
14
|
+
* Called automatically by the plugin during registration.
|
|
15
|
+
* Can be called manually when using standalone storage.
|
|
16
|
+
*/
|
|
17
|
+
init(): Promise<void>;
|
|
12
18
|
/**
|
|
13
19
|
* Upload a file.
|
|
14
20
|
*
|
|
@@ -151,6 +157,28 @@ export interface StorageManager {
|
|
|
151
157
|
* ```
|
|
152
158
|
*/
|
|
153
159
|
metadata(path: string): Promise<FileMetadata | null>;
|
|
160
|
+
/**
|
|
161
|
+
* Get file metadata, throwing if not found.
|
|
162
|
+
* Unlike metadata() which returns null for missing files,
|
|
163
|
+
* head() throws StorageObjectNotFoundError.
|
|
164
|
+
*
|
|
165
|
+
* @param path - File path/key
|
|
166
|
+
* @returns File metadata
|
|
167
|
+
* @throws StorageObjectNotFoundError if file does not exist
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* try {
|
|
172
|
+
* const meta = await storage.head('uploads/image.jpg');
|
|
173
|
+
* console.log(`Size: ${meta.size}`);
|
|
174
|
+
* } catch (error) {
|
|
175
|
+
* if (isStorageObjectNotFoundError(error)) {
|
|
176
|
+
* console.log('File does not exist');
|
|
177
|
+
* }
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
head(path: string): Promise<FileMetadata>;
|
|
154
182
|
/**
|
|
155
183
|
* List files in a directory/prefix.
|
|
156
184
|
*
|
|
@@ -205,6 +233,30 @@ export interface StorageManager {
|
|
|
205
233
|
* ```
|
|
206
234
|
*/
|
|
207
235
|
signedUrl(path: string, options?: SignedUrlOptions): Promise<string>;
|
|
236
|
+
/**
|
|
237
|
+
* Get a signed/temporary URL for uploading a file directly to storage.
|
|
238
|
+
* Enables direct browser-to-storage uploads without proxying through the server.
|
|
239
|
+
*
|
|
240
|
+
* @param options - Signed upload URL options
|
|
241
|
+
* @returns Signed URL string for PUT upload
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* const uploadUrl = await storage.signedUploadUrl({
|
|
246
|
+
* key: 'uploads/user-123/avatar.jpg',
|
|
247
|
+
* contentType: 'image/jpeg',
|
|
248
|
+
* expiresIn: 300, // 5 minutes
|
|
249
|
+
* });
|
|
250
|
+
*
|
|
251
|
+
* // Client can now PUT directly to this URL
|
|
252
|
+
* await fetch(uploadUrl, {
|
|
253
|
+
* method: 'PUT',
|
|
254
|
+
* body: file,
|
|
255
|
+
* headers: { 'Content-Type': 'image/jpeg' },
|
|
256
|
+
* });
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
signedUploadUrl(options: SignedUploadOptions): Promise<string>;
|
|
208
260
|
/**
|
|
209
261
|
* Set file visibility.
|
|
210
262
|
*
|
package/dist/manager.js
CHANGED
|
@@ -85,6 +85,7 @@ export async function createStorageManager(options = {}) {
|
|
|
85
85
|
}
|
|
86
86
|
// Create manager that delegates to the store
|
|
87
87
|
const manager = {
|
|
88
|
+
init: () => store.init?.() ?? Promise.resolve(),
|
|
88
89
|
put: (path, content, putOptions) => store.put(path, content, putOptions),
|
|
89
90
|
get: (path, getOptions) => store.get(path, getOptions),
|
|
90
91
|
stream: (path, streamOptions) => store.stream(path, streamOptions),
|
|
@@ -94,9 +95,11 @@ export async function createStorageManager(options = {}) {
|
|
|
94
95
|
copy: (source, destination, copyOptions) => store.copy(source, destination, copyOptions),
|
|
95
96
|
move: (source, destination, moveOptions) => store.move(source, destination, moveOptions),
|
|
96
97
|
metadata: (path) => store.metadata(path),
|
|
98
|
+
head: (path) => store.head(path),
|
|
97
99
|
list: (prefix, listOptions) => store.list(prefix, listOptions),
|
|
98
100
|
url: (path) => store.url(path),
|
|
99
101
|
signedUrl: (path, signedOptions) => store.signedUrl(path, signedOptions),
|
|
102
|
+
signedUploadUrl: (options) => store.signedUploadUrl(options),
|
|
100
103
|
setVisibility: (path, visibility) => store.setVisibility(path, visibility),
|
|
101
104
|
getVisibility: (path) => store.getVisibility(path),
|
|
102
105
|
makePublic: (path) => store.setVisibility(path, 'public'),
|
package/dist/plugin.js
CHANGED
|
@@ -52,6 +52,8 @@ export function storagePlugin(options = {}) {
|
|
|
52
52
|
return fp(async (fastify) => {
|
|
53
53
|
// Create storage manager
|
|
54
54
|
const storage = await createStorageManager(options);
|
|
55
|
+
// Initialize the storage (create directories, verify connectivity, etc.)
|
|
56
|
+
await storage.init();
|
|
55
57
|
// Store on fastify instance
|
|
56
58
|
fastify[STORAGE_KEY] = storage;
|
|
57
59
|
// Decorate request with storage accessor
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Providers
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all storage provider factory functions.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { r2, minio } from '@veloxts/storage/providers';
|
|
9
|
+
*
|
|
10
|
+
* // Cloudflare R2
|
|
11
|
+
* const r2Storage = await r2({
|
|
12
|
+
* bucket: 'my-bucket',
|
|
13
|
+
* accountId: 'xxx',
|
|
14
|
+
* accessKeyId: 'xxx',
|
|
15
|
+
* secretAccessKey: 'xxx',
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // MinIO
|
|
19
|
+
* const minioStorage = await minio({
|
|
20
|
+
* bucket: 'my-bucket',
|
|
21
|
+
* endpoint: 'http://localhost:9000',
|
|
22
|
+
* accessKeyId: 'minioadmin',
|
|
23
|
+
* secretAccessKey: 'minioadmin',
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from '../drivers/local.js';
|
|
28
|
+
export { createS3Store, DRIVER_NAME as S3_DRIVER } from '../drivers/s3.js';
|
|
29
|
+
export { type MinIOConfig, minio, PROVIDER_NAME as MINIO_PROVIDER } from './minio.js';
|
|
30
|
+
export { PROVIDER_NAME as R2_PROVIDER, type R2Config, r2 } from './r2.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Providers
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all storage provider factory functions.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { r2, minio } from '@veloxts/storage/providers';
|
|
9
|
+
*
|
|
10
|
+
* // Cloudflare R2
|
|
11
|
+
* const r2Storage = await r2({
|
|
12
|
+
* bucket: 'my-bucket',
|
|
13
|
+
* accountId: 'xxx',
|
|
14
|
+
* accessKeyId: 'xxx',
|
|
15
|
+
* secretAccessKey: 'xxx',
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // MinIO
|
|
19
|
+
* const minioStorage = await minio({
|
|
20
|
+
* bucket: 'my-bucket',
|
|
21
|
+
* endpoint: 'http://localhost:9000',
|
|
22
|
+
* accessKeyId: 'minioadmin',
|
|
23
|
+
* secretAccessKey: 'minioadmin',
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
// Re-export the base drivers for convenience
|
|
28
|
+
export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from '../drivers/local.js';
|
|
29
|
+
export { createS3Store, DRIVER_NAME as S3_DRIVER } from '../drivers/s3.js';
|
|
30
|
+
// Provider factories
|
|
31
|
+
export { minio, PROVIDER_NAME as MINIO_PROVIDER } from './minio.js';
|
|
32
|
+
export { PROVIDER_NAME as R2_PROVIDER, r2 } from './r2.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MinIO Storage Provider
|
|
3
|
+
*
|
|
4
|
+
* Factory function that creates an S3-compatible storage store configured
|
|
5
|
+
* for MinIO.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { StorageStore } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* MinIO configuration schema.
|
|
11
|
+
*/
|
|
12
|
+
declare const MinIOConfigSchema: z.ZodObject<{
|
|
13
|
+
/** MinIO bucket name */
|
|
14
|
+
bucket: z.ZodString;
|
|
15
|
+
/** MinIO endpoint (e.g., 'http://localhost:9000') */
|
|
16
|
+
endpoint: z.ZodString;
|
|
17
|
+
/** MinIO access key ID */
|
|
18
|
+
accessKeyId: z.ZodString;
|
|
19
|
+
/** MinIO secret access key */
|
|
20
|
+
secretAccessKey: z.ZodString;
|
|
21
|
+
/** AWS region (default: 'us-east-1') */
|
|
22
|
+
region: z.ZodDefault<z.ZodString>;
|
|
23
|
+
/** Custom public URL for accessing files */
|
|
24
|
+
publicUrl: z.ZodOptional<z.ZodString>;
|
|
25
|
+
/** Key prefix for all operations */
|
|
26
|
+
prefix: z.ZodOptional<z.ZodString>;
|
|
27
|
+
}, "strip", z.ZodTypeAny, {
|
|
28
|
+
bucket: string;
|
|
29
|
+
region: string;
|
|
30
|
+
endpoint: string;
|
|
31
|
+
accessKeyId: string;
|
|
32
|
+
secretAccessKey: string;
|
|
33
|
+
prefix?: string | undefined;
|
|
34
|
+
publicUrl?: string | undefined;
|
|
35
|
+
}, {
|
|
36
|
+
bucket: string;
|
|
37
|
+
endpoint: string;
|
|
38
|
+
accessKeyId: string;
|
|
39
|
+
secretAccessKey: string;
|
|
40
|
+
region?: string | undefined;
|
|
41
|
+
prefix?: string | undefined;
|
|
42
|
+
publicUrl?: string | undefined;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* MinIO configuration options.
|
|
46
|
+
*/
|
|
47
|
+
export type MinIOConfig = z.input<typeof MinIOConfigSchema>;
|
|
48
|
+
/**
|
|
49
|
+
* Create a MinIO storage store.
|
|
50
|
+
*
|
|
51
|
+
* @param config - MinIO configuration
|
|
52
|
+
* @returns Storage store implementation
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import { minio } from '@veloxts/storage/providers/minio';
|
|
57
|
+
*
|
|
58
|
+
* const storage = await minio({
|
|
59
|
+
* bucket: 'my-bucket',
|
|
60
|
+
* endpoint: 'http://localhost:9000',
|
|
61
|
+
* accessKeyId: 'minioadmin',
|
|
62
|
+
* secretAccessKey: 'minioadmin',
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* await storage.init();
|
|
66
|
+
* await storage.put('uploads/file.txt', 'Hello World');
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function minio(config: MinIOConfig): Promise<StorageStore>;
|
|
70
|
+
/**
|
|
71
|
+
* MinIO provider name identifier.
|
|
72
|
+
*/
|
|
73
|
+
export declare const PROVIDER_NAME: "minio";
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MinIO Storage Provider
|
|
3
|
+
*
|
|
4
|
+
* Factory function that creates an S3-compatible storage store configured
|
|
5
|
+
* for MinIO.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { createS3Store } from '../drivers/s3.js';
|
|
9
|
+
/**
|
|
10
|
+
* MinIO configuration schema.
|
|
11
|
+
*/
|
|
12
|
+
const MinIOConfigSchema = z.object({
|
|
13
|
+
/** MinIO bucket name */
|
|
14
|
+
bucket: z.string().min(1, 'Bucket name is required'),
|
|
15
|
+
/** MinIO endpoint (e.g., 'http://localhost:9000') */
|
|
16
|
+
endpoint: z.string().url('Endpoint must be a valid URL'),
|
|
17
|
+
/** MinIO access key ID */
|
|
18
|
+
accessKeyId: z.string().min(1, 'Access key ID is required'),
|
|
19
|
+
/** MinIO secret access key */
|
|
20
|
+
secretAccessKey: z.string().min(1, 'Secret access key is required'),
|
|
21
|
+
/** AWS region (default: 'us-east-1') */
|
|
22
|
+
region: z.string().default('us-east-1'),
|
|
23
|
+
/** Custom public URL for accessing files */
|
|
24
|
+
publicUrl: z.string().url().optional(),
|
|
25
|
+
/** Key prefix for all operations */
|
|
26
|
+
prefix: z.string().optional(),
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Create a MinIO storage store.
|
|
30
|
+
*
|
|
31
|
+
* @param config - MinIO configuration
|
|
32
|
+
* @returns Storage store implementation
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { minio } from '@veloxts/storage/providers/minio';
|
|
37
|
+
*
|
|
38
|
+
* const storage = await minio({
|
|
39
|
+
* bucket: 'my-bucket',
|
|
40
|
+
* endpoint: 'http://localhost:9000',
|
|
41
|
+
* accessKeyId: 'minioadmin',
|
|
42
|
+
* secretAccessKey: 'minioadmin',
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* await storage.init();
|
|
46
|
+
* await storage.put('uploads/file.txt', 'Hello World');
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export async function minio(config) {
|
|
50
|
+
// Validate configuration with Zod
|
|
51
|
+
const validated = MinIOConfigSchema.parse(config);
|
|
52
|
+
// Create S3-compatible store with MinIO-specific configuration
|
|
53
|
+
return createS3Store({
|
|
54
|
+
driver: 's3',
|
|
55
|
+
bucket: validated.bucket,
|
|
56
|
+
region: validated.region,
|
|
57
|
+
endpoint: validated.endpoint,
|
|
58
|
+
accessKeyId: validated.accessKeyId,
|
|
59
|
+
secretAccessKey: validated.secretAccessKey,
|
|
60
|
+
forcePathStyle: true, // MinIO requires path-style URLs
|
|
61
|
+
prefix: validated.prefix,
|
|
62
|
+
publicUrl: validated.publicUrl,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* MinIO provider name identifier.
|
|
67
|
+
*/
|
|
68
|
+
export const PROVIDER_NAME = 'minio';
|