@kumix/storage 0.1.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/README.md +291 -0
- package/dist/cloudinary.d.ts +93 -0
- package/dist/cloudinary.d.ts.map +1 -0
- package/dist/cloudinary.js +516 -0
- package/dist/cloudinary.js.map +1 -0
- package/dist/config-J6AdDh_c.d.ts +594 -0
- package/dist/config-J6AdDh_c.d.ts.map +1 -0
- package/dist/config-t-NVJICl.js +410 -0
- package/dist/config-t-NVJICl.js.map +1 -0
- package/dist/helpers.d.ts +764 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +1042 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/s3.d.ts +109 -0
- package/dist/s3.d.ts.map +1 -0
- package/dist/s3.js +779 -0
- package/dist/s3.js.map +1 -0
- package/package.json +94 -0
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# @kumix/storage
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@kumix/storage)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A comprehensive storage utilities package for SaaS applications. This package provides a unified interface for working with various storage providers including AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Supabase Storage, and Cloudinary.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @kumix/storage
|
|
12
|
+
# or
|
|
13
|
+
bun add @kumix/storage
|
|
14
|
+
|
|
15
|
+
# Install peer dependencies for the provider(s) you need
|
|
16
|
+
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner # For S3-compatible providers
|
|
17
|
+
npm install cloudinary # For Cloudinary
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### S3-Compatible Storage (AWS, R2, MinIO, etc.)
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { S3Service } from "@kumix/storage/s3";
|
|
26
|
+
|
|
27
|
+
const storage = new S3Service({
|
|
28
|
+
provider: "aws", // or 'cloudflare-r2', 'minio', 'digitalocean', 'supabase'
|
|
29
|
+
region: "us-east-1",
|
|
30
|
+
bucket: "my-bucket",
|
|
31
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
32
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Upload a file
|
|
36
|
+
await storage.upload({
|
|
37
|
+
key: "documents/file.pdf",
|
|
38
|
+
file: fileBuffer,
|
|
39
|
+
contentType: "application/pdf",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Download a file
|
|
43
|
+
const result = await storage.download({ key: "documents/file.pdf" });
|
|
44
|
+
|
|
45
|
+
// Generate a presigned URL (temporary access)
|
|
46
|
+
const url = await storage.getPresignedUrl({
|
|
47
|
+
key: "documents/file.pdf",
|
|
48
|
+
operation: "get",
|
|
49
|
+
expiresIn: 3600, // 1 hour
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Cloudinary Storage
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { CloudinaryService } from "@kumix/storage/cloudinary";
|
|
57
|
+
|
|
58
|
+
const storage = new CloudinaryService({
|
|
59
|
+
provider: "cloudinary",
|
|
60
|
+
cloudName: process.env.CLOUDINARY_CLOUD_NAME,
|
|
61
|
+
apiKey: process.env.CLOUDINARY_API_KEY,
|
|
62
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Upload an image
|
|
66
|
+
await storage.upload({
|
|
67
|
+
key: "images/photo.jpg",
|
|
68
|
+
file: imageBuffer,
|
|
69
|
+
contentType: "image/jpeg",
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Key Features
|
|
74
|
+
|
|
75
|
+
- **Unified Interface**: Single API for multiple storage providers
|
|
76
|
+
- **S3-Compatible Providers**: AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Supabase Storage
|
|
77
|
+
- **Cloudinary Support**: Image and media management with built-in optimizations
|
|
78
|
+
- **File Operations**: Upload, download, delete, copy, move, and check existence
|
|
79
|
+
- **Folder Operations**: Create, delete, list, and manage folders
|
|
80
|
+
- **Presigned URLs**: Generate time-limited access URLs for secure sharing
|
|
81
|
+
- **Public URLs**: Get public access URLs with optional CDN support
|
|
82
|
+
- **Type-Safe**: Full TypeScript support with comprehensive types
|
|
83
|
+
- **Flexible Configuration**: Support for custom endpoints and CDN URLs
|
|
84
|
+
|
|
85
|
+
## Supported Providers
|
|
86
|
+
|
|
87
|
+
### S3-Compatible
|
|
88
|
+
|
|
89
|
+
- **AWS S3** - Amazon's object storage service
|
|
90
|
+
- **Cloudflare R2** - Cloudflare's S3-compatible storage (no egress fees)
|
|
91
|
+
- **MinIO** - Self-hosted S3-compatible storage
|
|
92
|
+
- **DigitalOcean Spaces** - DigitalOcean's object storage
|
|
93
|
+
- **Supabase Storage** - Supabase's S3-compatible storage
|
|
94
|
+
|
|
95
|
+
### Other Providers
|
|
96
|
+
|
|
97
|
+
- **Cloudinary** - Image and video management platform
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
### AWS S3
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const storage = new S3Service({
|
|
105
|
+
provider: "aws",
|
|
106
|
+
region: "us-east-1",
|
|
107
|
+
bucket: "my-bucket",
|
|
108
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
109
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
110
|
+
publicUrl: "https://cdn.example.com", // Optional CDN URL
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Cloudflare R2
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const storage = new S3Service({
|
|
118
|
+
provider: "cloudflare-r2",
|
|
119
|
+
region: "auto",
|
|
120
|
+
bucket: "my-bucket",
|
|
121
|
+
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
|
122
|
+
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
|
|
123
|
+
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### MinIO
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const storage = new S3Service({
|
|
131
|
+
provider: "minio",
|
|
132
|
+
region: "us-east-1",
|
|
133
|
+
bucket: "my-bucket",
|
|
134
|
+
accessKeyId: "minioadmin",
|
|
135
|
+
secretAccessKey: "minioadmin",
|
|
136
|
+
endpoint: "http://localhost:9000",
|
|
137
|
+
forcePathStyle: true, // Required for MinIO
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Basic Usage
|
|
142
|
+
|
|
143
|
+
### File Operations
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Upload with options
|
|
147
|
+
await storage.upload({
|
|
148
|
+
key: "uploads/document.pdf",
|
|
149
|
+
file: buffer,
|
|
150
|
+
contentType: "application/pdf",
|
|
151
|
+
metadata: { userId: "123", category: "documents" },
|
|
152
|
+
cacheControl: "max-age=31536000",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Download file
|
|
156
|
+
const { content, metadata } = await storage.download({
|
|
157
|
+
key: "uploads/document.pdf",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Check if file exists
|
|
161
|
+
const exists = await storage.exists("uploads/document.pdf");
|
|
162
|
+
|
|
163
|
+
// Delete file
|
|
164
|
+
await storage.delete({ key: "uploads/document.pdf" });
|
|
165
|
+
|
|
166
|
+
// Copy file
|
|
167
|
+
await storage.copy({
|
|
168
|
+
sourceKey: "uploads/old.pdf",
|
|
169
|
+
destinationKey: "uploads/new.pdf",
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Move file
|
|
173
|
+
await storage.move({
|
|
174
|
+
sourceKey: "uploads/temp.pdf",
|
|
175
|
+
destinationKey: "uploads/final.pdf",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// List files
|
|
179
|
+
const files = await storage.list({
|
|
180
|
+
prefix: "uploads/",
|
|
181
|
+
maxKeys: 100,
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Folder Operations
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Create folder
|
|
189
|
+
await storage.createFolder({ path: "documents/" });
|
|
190
|
+
|
|
191
|
+
// Delete folder (recursive)
|
|
192
|
+
await storage.deleteFolder({
|
|
193
|
+
path: "documents/",
|
|
194
|
+
recursive: true,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// List folders
|
|
198
|
+
const folders = await storage.listFolders({
|
|
199
|
+
prefix: "uploads/",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Check if folder exists
|
|
203
|
+
const exists = await storage.folderExists("documents/");
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### URL Generation
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Get public URL
|
|
210
|
+
const publicUrl = storage.getPublicUrl("images/photo.jpg");
|
|
211
|
+
|
|
212
|
+
// Generate presigned URL for download
|
|
213
|
+
const downloadUrl = await storage.getPresignedUrl({
|
|
214
|
+
key: "documents/private.pdf",
|
|
215
|
+
operation: "get",
|
|
216
|
+
expiresIn: 3600, // 1 hour
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Generate presigned URL for upload
|
|
220
|
+
const uploadUrl = await storage.getPresignedUrl({
|
|
221
|
+
key: "uploads/new-file.pdf",
|
|
222
|
+
operation: "put",
|
|
223
|
+
expiresIn: 900, // 15 minutes
|
|
224
|
+
contentType: "application/pdf",
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## API Reference
|
|
229
|
+
|
|
230
|
+
### File Operations
|
|
231
|
+
|
|
232
|
+
- `upload(options)` - Upload a file
|
|
233
|
+
- `download(options)` - Download a file
|
|
234
|
+
- `delete(options)` - Delete a file
|
|
235
|
+
- `exists(key)` - Check if file exists
|
|
236
|
+
- `copy(options)` - Copy a file
|
|
237
|
+
- `move(options)` - Move a file
|
|
238
|
+
- `list(options)` - List files with prefix
|
|
239
|
+
- `getPresignedUrl(options)` - Generate presigned URL
|
|
240
|
+
- `getPublicUrl(key)` - Get public URL
|
|
241
|
+
|
|
242
|
+
### Folder Operations
|
|
243
|
+
|
|
244
|
+
- `createFolder(options)` - Create a folder
|
|
245
|
+
- `deleteFolder(options)` - Delete a folder
|
|
246
|
+
- `listFolders(options)` - List folders
|
|
247
|
+
- `folderExists(path)` - Check if folder exists
|
|
248
|
+
|
|
249
|
+
## Runtime Compatibility
|
|
250
|
+
|
|
251
|
+
| Export | Node.js | Bun | CF Workers | Deno | Browser |
|
|
252
|
+
| -------------- | ------- | --- | ---------- | ---- | ------- |
|
|
253
|
+
| `.` (config) | Yes | Yes | Yes\* | Yes | Yes |
|
|
254
|
+
| `./helpers` | Yes | Yes | Yes | Yes | Yes |
|
|
255
|
+
| `./s3` | Yes | Yes | No | Yes | No |
|
|
256
|
+
| `./cloudinary` | Yes | Yes | No | Yes | No |
|
|
257
|
+
|
|
258
|
+
\* Pass env via `EnvRecord`: `loadS3Config(myEnv)`.
|
|
259
|
+
|
|
260
|
+
### `EnvRecord` Pattern
|
|
261
|
+
|
|
262
|
+
All config functions accept an optional `env` parameter. On Node/Bun it defaults to `process.env`. On other runtimes, pass your environment explicitly:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { loadS3Config, loadCloudinaryConfig, hasStorageConfig } from "@kumix/storage";
|
|
266
|
+
|
|
267
|
+
// Cloudflare Workers
|
|
268
|
+
const config = loadS3Config(ctx.env);
|
|
269
|
+
|
|
270
|
+
// Deno
|
|
271
|
+
const config = loadCloudinaryConfig(Deno.env.toObject());
|
|
272
|
+
|
|
273
|
+
// Manual config (works everywhere, no env needed)
|
|
274
|
+
const s3 = createS3({ provider: "aws", region: "us-east-1", ... });
|
|
275
|
+
const cloudinary = createCloudinary({ provider: "cloudinary", cloudName: "...", ... });
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
> **Note**: `./s3` and `./cloudinary` subpaths require the AWS SDK or Cloudinary SDK
|
|
279
|
+
> peer dependencies respectively, which are Node.js packages. Use these subpaths only in
|
|
280
|
+
> Node.js, Bun, or Deno environments.
|
|
281
|
+
|
|
282
|
+
## Links
|
|
283
|
+
|
|
284
|
+
- [npm Package](https://www.npmjs.com/package/@kumix/storage)
|
|
285
|
+
- [Contributing Guide](../../CONTRIBUTING.md)
|
|
286
|
+
- [Code of Conduct](../../CODE_OF_CONDUCT.md)
|
|
287
|
+
- [License](../../LICENSE)
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT © [Kumix Labs](../../LICENSE)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { A as DownloadOptions, B as PresignedUrlResult, C as RenameFolderResult, D as CopyResult, E as CopyOptions, F as ListOptions, H as UploadResult, I as ListResult, L as MoveOptions, M as DuplicateOptions, N as DuplicateResult, O as DeleteOptions, P as ExistsResult, R as MoveResult, S as RenameFolderOptions, T as BatchDeleteResult, U as CloudinaryConfig, V as UploadOptions, _ as DeleteFolderOptions, b as ListFoldersOptions, f as StorageInterface, g as CreateFolderResult, h as CreateFolderOptions, j as DownloadResult, k as DeleteResult, m as CopyFolderResult, n as EnvRecord, p as CopyFolderOptions, v as DeleteFolderResult, w as BatchDeleteOptions, x as ListFoldersResult, y as FolderExistsResult, z as PresignedUrlOptions } from "./config-J6AdDh_c.js";
|
|
2
|
+
|
|
3
|
+
//#region src/services/cloudinary.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cloudinary storage service
|
|
6
|
+
* High-level service for Cloudinary storage operations with convenience methods
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
declare class CloudinaryService implements StorageInterface {
|
|
10
|
+
private provider;
|
|
11
|
+
private config;
|
|
12
|
+
constructor();
|
|
13
|
+
constructor(config: CloudinaryConfig);
|
|
14
|
+
getConfig(): CloudinaryConfig;
|
|
15
|
+
getProvider(): string;
|
|
16
|
+
getPublicUrl(key: string): string;
|
|
17
|
+
upload(options: UploadOptions): Promise<UploadResult>;
|
|
18
|
+
download(options: DownloadOptions): Promise<DownloadResult>;
|
|
19
|
+
delete(options: DeleteOptions): Promise<DeleteResult>;
|
|
20
|
+
batchDelete(options: BatchDeleteOptions): Promise<BatchDeleteResult>;
|
|
21
|
+
list(options?: ListOptions): Promise<ListResult>;
|
|
22
|
+
exists(key: string): Promise<ExistsResult>;
|
|
23
|
+
copy(options: CopyOptions): Promise<CopyResult>;
|
|
24
|
+
move(options: MoveOptions): Promise<MoveResult>;
|
|
25
|
+
duplicate(options: DuplicateOptions): Promise<DuplicateResult>;
|
|
26
|
+
getPresignedUrl(options: PresignedUrlOptions): Promise<PresignedUrlResult>;
|
|
27
|
+
createFolder(options: CreateFolderOptions): Promise<CreateFolderResult>;
|
|
28
|
+
deleteFolder(options: DeleteFolderOptions): Promise<DeleteFolderResult>;
|
|
29
|
+
listFolders(options?: ListFoldersOptions): Promise<ListFoldersResult>;
|
|
30
|
+
folderExists(path: string): Promise<FolderExistsResult>;
|
|
31
|
+
renameFolder(options: RenameFolderOptions): Promise<RenameFolderResult>;
|
|
32
|
+
copyFolder(options: CopyFolderOptions): Promise<CopyFolderResult>;
|
|
33
|
+
uploadFile(key: string, file: Buffer | Uint8Array | string, contentType?: string, metadata?: Record<string, string>): Promise<UploadResult>;
|
|
34
|
+
downloadFile(key: string): Promise<DownloadResult>;
|
|
35
|
+
deleteFile(key: string): Promise<DeleteResult>;
|
|
36
|
+
deleteFiles(keys: string[]): Promise<BatchDeleteResult>;
|
|
37
|
+
listFiles(prefix?: string, maxKeys?: number): Promise<ListResult>;
|
|
38
|
+
fileExists(key: string): Promise<boolean>;
|
|
39
|
+
copyFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<CopyResult>;
|
|
40
|
+
moveFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<MoveResult>;
|
|
41
|
+
duplicateFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<DuplicateResult>;
|
|
42
|
+
renameFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<MoveResult>;
|
|
43
|
+
getDownloadUrl(key: string, expiresIn?: number): Promise<PresignedUrlResult>;
|
|
44
|
+
getUploadUrl(key: string, contentType?: string, expiresIn?: number): Promise<PresignedUrlResult>;
|
|
45
|
+
createFolderPath(path: string): Promise<CreateFolderResult>;
|
|
46
|
+
deleteFolderPath(path: string, recursive?: boolean): Promise<DeleteFolderResult>;
|
|
47
|
+
folderPathExists(path: string): Promise<boolean>;
|
|
48
|
+
renameFolderPath(oldPath: string, newPath: string): Promise<RenameFolderResult>;
|
|
49
|
+
copyFolderPath(sourcePath: string, destinationPath: string, recursive?: boolean): Promise<CopyFolderResult>;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/cloudinary.d.ts
|
|
53
|
+
/**
|
|
54
|
+
* Create Cloudinary service using environment variables or manual configuration
|
|
55
|
+
* @param config Optional Cloudinary configuration. If not provided, loads from environment variables
|
|
56
|
+
* @param env - Optional environment record
|
|
57
|
+
* @returns CloudinaryService instance
|
|
58
|
+
* @throws Error if configuration is invalid or environment variables are missing
|
|
59
|
+
* @public
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* import { createCloudinary } from '@kumix/storage/cloudinary';
|
|
64
|
+
*
|
|
65
|
+
* // Using environment variables
|
|
66
|
+
* // Set: KUMIX_CLOUDINARY_CLOUD_NAME=my-cloud, KUMIX_CLOUDINARY_API_KEY=123..., etc.
|
|
67
|
+
* const cloudinaryFromEnv = createCloudinary();
|
|
68
|
+
* await cloudinaryFromEnv.uploadFile('photo.jpg', imageBuffer);
|
|
69
|
+
*
|
|
70
|
+
* // Using manual configuration
|
|
71
|
+
* const cloudinary = createCloudinary({
|
|
72
|
+
* provider: 'cloudinary',
|
|
73
|
+
* cloudName: 'my-cloud-name',
|
|
74
|
+
* apiKey: '123456789012345',
|
|
75
|
+
* apiSecret: 'your-api-secret',
|
|
76
|
+
* secure: true,
|
|
77
|
+
* folder: 'uploads'
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* // Upload with transformations
|
|
81
|
+
* await cloudinary.uploadFile('profile-pic.jpg', imageBuffer);
|
|
82
|
+
*
|
|
83
|
+
* // Get optimized URL
|
|
84
|
+
* const publicUrl = cloudinary.getPublicUrl('profile-pic.jpg');
|
|
85
|
+
* console.log(publicUrl); // Cloudinary optimized URL
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function createCloudinary(): CloudinaryService;
|
|
89
|
+
declare function createCloudinary(config: CloudinaryConfig): CloudinaryService;
|
|
90
|
+
declare function createCloudinary(config: CloudinaryConfig, env: EnvRecord): CloudinaryService;
|
|
91
|
+
//#endregion
|
|
92
|
+
export { CloudinaryService, createCloudinary };
|
|
93
|
+
//# sourceMappingURL=cloudinary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinary.d.ts","names":[],"sources":["../src/services/cloudinary.ts","../src/cloudinary.ts"],"mappings":"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"}
|