@veloxts/storage 0.6.90 → 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 -24
- package/dist/index.js +1 -28
- 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
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Provider Compliance Test Suite
|
|
3
|
+
*
|
|
4
|
+
* This module exports a test helper that verifies a StorageStore implementation
|
|
5
|
+
* conforms to the expected interface contract. Use this to test custom providers
|
|
6
|
+
* or verify third-party provider implementations.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { runProviderTests } from '@veloxts/storage/testing';
|
|
11
|
+
* import { createLocalStore } from '@veloxts/storage/drivers/local';
|
|
12
|
+
*
|
|
13
|
+
* runProviderTests('Local', () =>
|
|
14
|
+
* createLocalStore({ driver: 'local', root: '/tmp/test-storage' })
|
|
15
|
+
* );
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
19
|
+
import { StorageObjectNotFoundError } from '../errors.js';
|
|
20
|
+
/**
|
|
21
|
+
* Run the provider compliance test suite against a StorageStore implementation.
|
|
22
|
+
*
|
|
23
|
+
* This function runs a comprehensive set of tests to verify that a storage provider
|
|
24
|
+
* correctly implements the StorageStore interface. It covers:
|
|
25
|
+
*
|
|
26
|
+
* - Basic CRUD operations (put, get, delete, deleteMany)
|
|
27
|
+
* - Metadata operations (metadata, head, exists)
|
|
28
|
+
* - Listing files with pagination
|
|
29
|
+
* - Presigned URLs (download and upload)
|
|
30
|
+
* - Copy and move operations
|
|
31
|
+
* - Lifecycle hooks (init, close)
|
|
32
|
+
*
|
|
33
|
+
* @param name - Name of the provider being tested (for test output)
|
|
34
|
+
* @param createProvider - Factory function that creates a new provider instance
|
|
35
|
+
* @param options - Optional test configuration
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Test the local driver
|
|
40
|
+
* runProviderTests('Local', () =>
|
|
41
|
+
* createLocalStore({ driver: 'local', root: '/tmp/test-storage' })
|
|
42
|
+
* );
|
|
43
|
+
*
|
|
44
|
+
* // Test with options
|
|
45
|
+
* runProviderTests('MinIO', async () => minio({
|
|
46
|
+
* bucket: 'test',
|
|
47
|
+
* endpoint: 'http://localhost:9000',
|
|
48
|
+
* accessKeyId: 'minioadmin',
|
|
49
|
+
* secretAccessKey: 'minioadmin',
|
|
50
|
+
* }), { timeout: 10000 });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function runProviderTests(name, createProvider, options = {}) {
|
|
54
|
+
const { skipPresignedUrls = false, skipLifecycle = false, timeout = 5000 } = options;
|
|
55
|
+
describe(`StorageProvider compliance: ${name}`, { timeout }, () => {
|
|
56
|
+
let provider;
|
|
57
|
+
const testPrefix = `compliance-test-${Date.now()}`;
|
|
58
|
+
// Helper to generate unique test keys
|
|
59
|
+
const testKey = (suffix) => `${testPrefix}/${suffix}`;
|
|
60
|
+
// Helper to clean up test files
|
|
61
|
+
const cleanup = async () => {
|
|
62
|
+
try {
|
|
63
|
+
const result = await provider.list(testPrefix, { recursive: true });
|
|
64
|
+
if (result.files.length > 0) {
|
|
65
|
+
await provider.deleteMany(result.files.map((f) => f.path));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Ignore cleanup errors
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
beforeAll(async () => {
|
|
73
|
+
provider = await createProvider();
|
|
74
|
+
if (!skipLifecycle && provider.init) {
|
|
75
|
+
await provider.init();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
afterAll(async () => {
|
|
79
|
+
await cleanup();
|
|
80
|
+
await provider.close();
|
|
81
|
+
});
|
|
82
|
+
beforeEach(async () => {
|
|
83
|
+
// Clean state before each test
|
|
84
|
+
});
|
|
85
|
+
afterEach(async () => {
|
|
86
|
+
// Clean up any test files created during the test
|
|
87
|
+
});
|
|
88
|
+
// =========================================================================
|
|
89
|
+
// Basic Operations
|
|
90
|
+
// =========================================================================
|
|
91
|
+
describe('put/get operations', () => {
|
|
92
|
+
it('should put and get a buffer', async () => {
|
|
93
|
+
const key = testKey('buffer-test.txt');
|
|
94
|
+
const content = Buffer.from('Hello, World!');
|
|
95
|
+
const path = await provider.put(key, content, { contentType: 'text/plain' });
|
|
96
|
+
expect(path).toBe(key);
|
|
97
|
+
const result = await provider.get(key);
|
|
98
|
+
expect(result).not.toBeNull();
|
|
99
|
+
expect(result?.toString()).toBe('Hello, World!');
|
|
100
|
+
});
|
|
101
|
+
it('should put and get a string', async () => {
|
|
102
|
+
const key = testKey('string-test.txt');
|
|
103
|
+
const content = 'String content here';
|
|
104
|
+
await provider.put(key, content, { contentType: 'text/plain' });
|
|
105
|
+
const result = await provider.get(key);
|
|
106
|
+
expect(result).not.toBeNull();
|
|
107
|
+
expect(result?.toString()).toBe('String content here');
|
|
108
|
+
});
|
|
109
|
+
it('should return null for non-existent key', async () => {
|
|
110
|
+
const result = await provider.get(testKey('does-not-exist.txt'));
|
|
111
|
+
expect(result).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
it('should overwrite existing file', async () => {
|
|
114
|
+
const key = testKey('overwrite-test.txt');
|
|
115
|
+
await provider.put(key, 'First content');
|
|
116
|
+
await provider.put(key, 'Second content');
|
|
117
|
+
const result = await provider.get(key);
|
|
118
|
+
expect(result?.toString()).toBe('Second content');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// =========================================================================
|
|
122
|
+
// Metadata Operations
|
|
123
|
+
// =========================================================================
|
|
124
|
+
describe('metadata operations', () => {
|
|
125
|
+
it('should return metadata for existing file', async () => {
|
|
126
|
+
const key = testKey('metadata-test.txt');
|
|
127
|
+
const content = 'Test content for metadata';
|
|
128
|
+
await provider.put(key, content, { contentType: 'text/plain' });
|
|
129
|
+
const meta = await provider.metadata(key);
|
|
130
|
+
expect(meta).not.toBeNull();
|
|
131
|
+
expect(meta?.path).toBe(key);
|
|
132
|
+
expect(meta?.size).toBe(content.length);
|
|
133
|
+
expect(meta?.lastModified).toBeInstanceOf(Date);
|
|
134
|
+
});
|
|
135
|
+
it('should return null for non-existent file', async () => {
|
|
136
|
+
const meta = await provider.metadata(testKey('metadata-not-found.txt'));
|
|
137
|
+
expect(meta).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('head() method', () => {
|
|
141
|
+
it('should return metadata for existing file', async () => {
|
|
142
|
+
const key = testKey('head-test.txt');
|
|
143
|
+
const content = 'Test content for head';
|
|
144
|
+
await provider.put(key, content, { contentType: 'text/plain' });
|
|
145
|
+
const meta = await provider.head(key);
|
|
146
|
+
expect(meta.path).toBe(key);
|
|
147
|
+
expect(meta.size).toBe(content.length);
|
|
148
|
+
expect(meta.lastModified).toBeInstanceOf(Date);
|
|
149
|
+
});
|
|
150
|
+
it('should throw StorageObjectNotFoundError for non-existent file', async () => {
|
|
151
|
+
const key = testKey('head-not-found.txt');
|
|
152
|
+
await expect(provider.head(key)).rejects.toThrow(StorageObjectNotFoundError);
|
|
153
|
+
await expect(provider.head(key)).rejects.toMatchObject({ key });
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('exists() method', () => {
|
|
157
|
+
it('should return true for existing file', async () => {
|
|
158
|
+
const key = testKey('exists-test.txt');
|
|
159
|
+
await provider.put(key, 'Test content');
|
|
160
|
+
const exists = await provider.exists(key);
|
|
161
|
+
expect(exists).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
it('should return false for non-existent file', async () => {
|
|
164
|
+
const exists = await provider.exists(testKey('exists-not-found.txt'));
|
|
165
|
+
expect(exists).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
// =========================================================================
|
|
169
|
+
// Delete Operations
|
|
170
|
+
// =========================================================================
|
|
171
|
+
describe('delete operations', () => {
|
|
172
|
+
it('should delete an existing file', async () => {
|
|
173
|
+
const key = testKey('delete-test.txt');
|
|
174
|
+
await provider.put(key, 'Delete me');
|
|
175
|
+
const deleted = await provider.delete(key);
|
|
176
|
+
expect(deleted).toBe(true);
|
|
177
|
+
const exists = await provider.exists(key);
|
|
178
|
+
expect(exists).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
it('should return false when deleting non-existent file', async () => {
|
|
181
|
+
const deleted = await provider.delete(testKey('delete-not-found.txt'));
|
|
182
|
+
expect(deleted).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
it('should batch delete multiple files with deleteMany', async () => {
|
|
185
|
+
const keys = [
|
|
186
|
+
testKey('batch-delete-1.txt'),
|
|
187
|
+
testKey('batch-delete-2.txt'),
|
|
188
|
+
testKey('batch-delete-3.txt'),
|
|
189
|
+
];
|
|
190
|
+
// Create files
|
|
191
|
+
await Promise.all(keys.map((key) => provider.put(key, `Content for ${key}`)));
|
|
192
|
+
// Delete them
|
|
193
|
+
const count = await provider.deleteMany(keys);
|
|
194
|
+
expect(count).toBe(3);
|
|
195
|
+
// Verify they're gone
|
|
196
|
+
for (const key of keys) {
|
|
197
|
+
const exists = await provider.exists(key);
|
|
198
|
+
expect(exists).toBe(false);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
it('should handle deleteMany with empty array (no-op)', async () => {
|
|
202
|
+
const count = await provider.deleteMany([]);
|
|
203
|
+
expect(count).toBe(0);
|
|
204
|
+
});
|
|
205
|
+
it('should handle deleteMany with mix of existing and non-existing files', async () => {
|
|
206
|
+
const key = testKey('batch-mixed-1.txt');
|
|
207
|
+
await provider.put(key, 'Content');
|
|
208
|
+
const count = await provider.deleteMany([key, testKey('batch-mixed-not-found.txt')]);
|
|
209
|
+
// Should delete at least the existing file
|
|
210
|
+
expect(count).toBeGreaterThanOrEqual(1);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
// =========================================================================
|
|
214
|
+
// List Operations
|
|
215
|
+
// =========================================================================
|
|
216
|
+
describe('list operations', () => {
|
|
217
|
+
const listPrefix = `${testPrefix}/list-test`;
|
|
218
|
+
beforeAll(async () => {
|
|
219
|
+
// Create some test files for listing
|
|
220
|
+
await provider.put(`${listPrefix}/file1.txt`, 'File 1');
|
|
221
|
+
await provider.put(`${listPrefix}/file2.txt`, 'File 2');
|
|
222
|
+
await provider.put(`${listPrefix}/subdir/file3.txt`, 'File 3');
|
|
223
|
+
});
|
|
224
|
+
it('should list files with prefix', async () => {
|
|
225
|
+
const result = await provider.list(listPrefix);
|
|
226
|
+
expect(result.files.length).toBeGreaterThanOrEqual(2);
|
|
227
|
+
expect(result.hasMore).toBeDefined();
|
|
228
|
+
});
|
|
229
|
+
it('should list files recursively', async () => {
|
|
230
|
+
const result = await provider.list(listPrefix, { recursive: true });
|
|
231
|
+
expect(result.files.length).toBeGreaterThanOrEqual(3);
|
|
232
|
+
});
|
|
233
|
+
it('should respect limit option', async () => {
|
|
234
|
+
const result = await provider.list(listPrefix, { recursive: true, limit: 1 });
|
|
235
|
+
expect(result.files.length).toBe(1);
|
|
236
|
+
});
|
|
237
|
+
it('should return file metadata in list results', async () => {
|
|
238
|
+
const result = await provider.list(listPrefix, { recursive: true });
|
|
239
|
+
for (const file of result.files) {
|
|
240
|
+
expect(file.path).toBeDefined();
|
|
241
|
+
expect(file.size).toBeDefined();
|
|
242
|
+
expect(typeof file.size).toBe('number');
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
// =========================================================================
|
|
247
|
+
// Copy/Move Operations
|
|
248
|
+
// =========================================================================
|
|
249
|
+
describe('copy/move operations', () => {
|
|
250
|
+
it('should copy a file', async () => {
|
|
251
|
+
const source = testKey('copy-source.txt');
|
|
252
|
+
const dest = testKey('copy-dest.txt');
|
|
253
|
+
await provider.put(source, 'Copy me');
|
|
254
|
+
const resultPath = await provider.copy(source, dest);
|
|
255
|
+
expect(resultPath).toBe(dest);
|
|
256
|
+
// Both should exist
|
|
257
|
+
expect(await provider.exists(source)).toBe(true);
|
|
258
|
+
expect(await provider.exists(dest)).toBe(true);
|
|
259
|
+
// Content should match
|
|
260
|
+
const content = await provider.get(dest);
|
|
261
|
+
expect(content?.toString()).toBe('Copy me');
|
|
262
|
+
});
|
|
263
|
+
it('should move a file', async () => {
|
|
264
|
+
const source = testKey('move-source.txt');
|
|
265
|
+
const dest = testKey('move-dest.txt');
|
|
266
|
+
await provider.put(source, 'Move me');
|
|
267
|
+
const resultPath = await provider.move(source, dest);
|
|
268
|
+
expect(resultPath).toBe(dest);
|
|
269
|
+
// Source should be gone, dest should exist
|
|
270
|
+
expect(await provider.exists(source)).toBe(false);
|
|
271
|
+
expect(await provider.exists(dest)).toBe(true);
|
|
272
|
+
// Content should match
|
|
273
|
+
const content = await provider.get(dest);
|
|
274
|
+
expect(content?.toString()).toBe('Move me');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
// =========================================================================
|
|
278
|
+
// URL Operations
|
|
279
|
+
// =========================================================================
|
|
280
|
+
describe('URL operations', () => {
|
|
281
|
+
it('should generate public URL', async () => {
|
|
282
|
+
const key = testKey('url-test.txt');
|
|
283
|
+
await provider.put(key, 'URL test content');
|
|
284
|
+
const url = await provider.url(key);
|
|
285
|
+
expect(typeof url).toBe('string');
|
|
286
|
+
expect(url.length).toBeGreaterThan(0);
|
|
287
|
+
});
|
|
288
|
+
if (!skipPresignedUrls) {
|
|
289
|
+
it('should generate presigned download URL', async () => {
|
|
290
|
+
const key = testKey('signed-download.txt');
|
|
291
|
+
await provider.put(key, 'Signed download content');
|
|
292
|
+
const signedUrl = await provider.signedUrl(key, { expiresIn: 300 });
|
|
293
|
+
expect(typeof signedUrl).toBe('string');
|
|
294
|
+
expect(signedUrl.length).toBeGreaterThan(0);
|
|
295
|
+
});
|
|
296
|
+
it('should generate presigned upload URL', async () => {
|
|
297
|
+
const key = testKey('signed-upload.txt');
|
|
298
|
+
const uploadUrl = await provider.signedUploadUrl({
|
|
299
|
+
key,
|
|
300
|
+
contentType: 'text/plain',
|
|
301
|
+
expiresIn: 300,
|
|
302
|
+
});
|
|
303
|
+
expect(typeof uploadUrl).toBe('string');
|
|
304
|
+
expect(uploadUrl.length).toBeGreaterThan(0);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
// =========================================================================
|
|
309
|
+
// Stream Operations
|
|
310
|
+
// =========================================================================
|
|
311
|
+
describe('stream operations', () => {
|
|
312
|
+
it('should return readable stream for existing file', async () => {
|
|
313
|
+
const key = testKey('stream-test.txt');
|
|
314
|
+
const content = 'Streamed content';
|
|
315
|
+
await provider.put(key, content);
|
|
316
|
+
const stream = await provider.stream(key);
|
|
317
|
+
expect(stream).not.toBeNull();
|
|
318
|
+
// Read stream to verify content
|
|
319
|
+
if (stream) {
|
|
320
|
+
const chunks = [];
|
|
321
|
+
for await (const chunk of stream) {
|
|
322
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
323
|
+
}
|
|
324
|
+
const result = Buffer.concat(chunks).toString();
|
|
325
|
+
expect(result).toBe(content);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
it('should return null for non-existent file', async () => {
|
|
329
|
+
const stream = await provider.stream(testKey('stream-not-found.txt'));
|
|
330
|
+
expect(stream).toBeNull();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
// =========================================================================
|
|
334
|
+
// Visibility Operations
|
|
335
|
+
// =========================================================================
|
|
336
|
+
describe('visibility operations', () => {
|
|
337
|
+
it('should set and get visibility', async () => {
|
|
338
|
+
const key = testKey('visibility-test.txt');
|
|
339
|
+
await provider.put(key, 'Visibility test');
|
|
340
|
+
await provider.setVisibility(key, 'public');
|
|
341
|
+
// Note: getVisibility may return null for some providers (like S3)
|
|
342
|
+
// that don't easily expose ACL info
|
|
343
|
+
const visibility = await provider.getVisibility(key);
|
|
344
|
+
// Only check if visibility is returned
|
|
345
|
+
if (visibility !== null) {
|
|
346
|
+
expect(visibility).toBe('public');
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
// =========================================================================
|
|
351
|
+
// Lifecycle
|
|
352
|
+
// =========================================================================
|
|
353
|
+
if (!skipLifecycle) {
|
|
354
|
+
describe('lifecycle', () => {
|
|
355
|
+
it('should have init method (optional)', () => {
|
|
356
|
+
// init is optional, just verify it exists or doesn't
|
|
357
|
+
expect(typeof provider.init === 'function' || provider.init === undefined).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
it('should have close method', () => {
|
|
360
|
+
expect(typeof provider.close).toBe('function');
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -75,7 +75,7 @@ export interface ListResult {
|
|
|
75
75
|
hasMore: boolean;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Options for generating signed URLs.
|
|
78
|
+
* Options for generating signed download URLs.
|
|
79
79
|
*/
|
|
80
80
|
export interface SignedUrlOptions {
|
|
81
81
|
/** URL expiration time in seconds (default: 3600 = 1 hour) */
|
|
@@ -85,6 +85,19 @@ export interface SignedUrlOptions {
|
|
|
85
85
|
/** Response content disposition override */
|
|
86
86
|
responseContentDisposition?: string;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Options for generating signed upload URLs.
|
|
90
|
+
*/
|
|
91
|
+
export interface SignedUploadOptions {
|
|
92
|
+
/** Object key/path to upload to */
|
|
93
|
+
key: string;
|
|
94
|
+
/** URL expiration time in seconds (default: 3600 = 1 hour) */
|
|
95
|
+
expiresIn?: number;
|
|
96
|
+
/** Content type for the upload (required for most providers) */
|
|
97
|
+
contentType?: string;
|
|
98
|
+
/** Maximum content length in bytes (optional enforcement) */
|
|
99
|
+
maxContentLength?: number;
|
|
100
|
+
}
|
|
88
101
|
/**
|
|
89
102
|
* Options for copying files.
|
|
90
103
|
*/
|
|
@@ -128,6 +141,8 @@ export interface S3StorageConfig {
|
|
|
128
141
|
defaultVisibility?: FileVisibility;
|
|
129
142
|
/** Key prefix for all operations */
|
|
130
143
|
prefix?: string;
|
|
144
|
+
/** Custom public URL for accessing files (e.g., CDN URL) */
|
|
145
|
+
publicUrl?: string;
|
|
131
146
|
}
|
|
132
147
|
/**
|
|
133
148
|
* Union type for all storage configurations.
|
|
@@ -137,6 +152,13 @@ export type StorageConfig = LocalStorageConfig | S3StorageConfig;
|
|
|
137
152
|
* Low-level storage store interface implemented by drivers.
|
|
138
153
|
*/
|
|
139
154
|
export interface StorageStore {
|
|
155
|
+
/**
|
|
156
|
+
* Initialize the storage store.
|
|
157
|
+
* Called once at boot by the plugin.
|
|
158
|
+
* Use for config validation, connection verification, directory creation, etc.
|
|
159
|
+
* Throw to fail fast if the store cannot start.
|
|
160
|
+
*/
|
|
161
|
+
init?(): Promise<void>;
|
|
140
162
|
/**
|
|
141
163
|
* Upload a file.
|
|
142
164
|
* @param path - Destination path/key
|
|
@@ -199,6 +221,15 @@ export interface StorageStore {
|
|
|
199
221
|
* @returns File metadata, or null if not found
|
|
200
222
|
*/
|
|
201
223
|
metadata(path: string): Promise<FileMetadata | null>;
|
|
224
|
+
/**
|
|
225
|
+
* Get file metadata, throwing if not found.
|
|
226
|
+
* Unlike metadata() which returns null for missing files,
|
|
227
|
+
* head() throws StorageObjectNotFoundError.
|
|
228
|
+
* @param path - File path/key
|
|
229
|
+
* @returns File metadata
|
|
230
|
+
* @throws StorageObjectNotFoundError if file does not exist
|
|
231
|
+
*/
|
|
232
|
+
head(path: string): Promise<FileMetadata>;
|
|
202
233
|
/**
|
|
203
234
|
* List files in a directory/prefix.
|
|
204
235
|
* @param prefix - Directory prefix
|
|
@@ -213,12 +244,19 @@ export interface StorageStore {
|
|
|
213
244
|
*/
|
|
214
245
|
url(path: string): Promise<string>;
|
|
215
246
|
/**
|
|
216
|
-
* Get a signed/temporary URL for private file access.
|
|
247
|
+
* Get a signed/temporary URL for private file access (download).
|
|
217
248
|
* @param path - File path/key
|
|
218
249
|
* @param options - Signed URL options
|
|
219
250
|
* @returns Signed URL string
|
|
220
251
|
*/
|
|
221
252
|
signedUrl(path: string, options?: SignedUrlOptions): Promise<string>;
|
|
253
|
+
/**
|
|
254
|
+
* Get a signed/temporary URL for uploading a file directly to storage.
|
|
255
|
+
* Enables direct browser-to-storage uploads without proxying through the server.
|
|
256
|
+
* @param options - Signed upload URL options
|
|
257
|
+
* @returns Signed URL string for PUT upload
|
|
258
|
+
*/
|
|
259
|
+
signedUploadUrl(options: SignedUploadOptions): Promise<string>;
|
|
222
260
|
/**
|
|
223
261
|
* Set file visibility.
|
|
224
262
|
* @param path - File path/key
|
|
@@ -294,3 +332,8 @@ export type StoragePluginOptions = StorageLocalOptions | StorageS3Options | Stor
|
|
|
294
332
|
* Storage manager options (alias for plugin options).
|
|
295
333
|
*/
|
|
296
334
|
export type StorageManagerOptions = StoragePluginOptions;
|
|
335
|
+
/**
|
|
336
|
+
* Backwards compatibility alias for StorageStore.
|
|
337
|
+
* @deprecated Use StorageStore instead. Will be removed in v2.0.
|
|
338
|
+
*/
|
|
339
|
+
export type StorageProvider = StorageStore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veloxts/storage",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.92",
|
|
4
4
|
"description": "Multi-driver file storage abstraction for VeloxTS framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,6 +22,30 @@
|
|
|
22
22
|
"./drivers/s3": {
|
|
23
23
|
"types": "./dist/drivers/s3.d.ts",
|
|
24
24
|
"import": "./dist/drivers/s3.js"
|
|
25
|
+
},
|
|
26
|
+
"./providers": {
|
|
27
|
+
"types": "./dist/providers/index.d.ts",
|
|
28
|
+
"import": "./dist/providers/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./providers/r2": {
|
|
31
|
+
"types": "./dist/providers/r2.d.ts",
|
|
32
|
+
"import": "./dist/providers/r2.js"
|
|
33
|
+
},
|
|
34
|
+
"./providers/minio": {
|
|
35
|
+
"types": "./dist/providers/minio.d.ts",
|
|
36
|
+
"import": "./dist/providers/minio.js"
|
|
37
|
+
},
|
|
38
|
+
"./providers/local": {
|
|
39
|
+
"types": "./dist/drivers/local.d.ts",
|
|
40
|
+
"import": "./dist/drivers/local.js"
|
|
41
|
+
},
|
|
42
|
+
"./providers/s3": {
|
|
43
|
+
"types": "./dist/drivers/s3.d.ts",
|
|
44
|
+
"import": "./dist/drivers/s3.js"
|
|
45
|
+
},
|
|
46
|
+
"./testing": {
|
|
47
|
+
"types": "./dist/testing/index.d.ts",
|
|
48
|
+
"import": "./dist/testing/index.js"
|
|
25
49
|
}
|
|
26
50
|
},
|
|
27
51
|
"files": [
|
|
@@ -32,7 +56,8 @@
|
|
|
32
56
|
"dependencies": {
|
|
33
57
|
"fastify-plugin": "5.1.0",
|
|
34
58
|
"mime-types": "3.0.2",
|
|
35
|
-
"
|
|
59
|
+
"zod": "3.24.4",
|
|
60
|
+
"@veloxts/core": "0.6.92"
|
|
36
61
|
},
|
|
37
62
|
"peerDependencies": {
|
|
38
63
|
"@aws-sdk/client-s3": ">=3.0.0",
|
|
@@ -61,7 +86,7 @@
|
|
|
61
86
|
"fastify": "5.7.2",
|
|
62
87
|
"typescript": "5.9.3",
|
|
63
88
|
"vitest": "4.0.18",
|
|
64
|
-
"@veloxts/testing": "0.6.
|
|
89
|
+
"@veloxts/testing": "0.6.92"
|
|
65
90
|
},
|
|
66
91
|
"publishConfig": {
|
|
67
92
|
"access": "public"
|