@venizia/ignis-docs 0.0.1-5 → 0.0.1-6

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.
@@ -1,13 +1,14 @@
1
1
  # Storage Helpers
2
2
 
3
- In-memory and external object storage solutions.
3
+ Storage solutions for in-memory, local filesystem, and cloud object storage with a unified interface.
4
4
 
5
5
  ## Quick Reference
6
6
 
7
- | Helper | Type | Use Case |
8
- |--------|------|----------|
9
- | **MemoryStorageHelper** | In-memory key-value | Caching, temporary state, single-process data |
10
- | **MinioHelper** | S3-compatible object storage | File uploads, persistent storage, MinIO/S3 |
7
+ | Helper | Type | Implements | Use Case |
8
+ |--------|------|------------|----------|
9
+ | **MemoryStorageHelper** | In-memory key-value | - | Caching, temporary state, single-process data |
10
+ | **DiskHelper** | Local filesystem | `IStorageHelper` | Local file storage, development, small-scale apps |
11
+ | **MinioHelper** | S3-compatible storage | `IStorageHelper` | Cloud storage, production, scalable file management |
11
12
 
12
13
  ### MemoryStorageHelper Methods
13
14
 
@@ -19,14 +20,79 @@ In-memory and external object storage solutions.
19
20
  | `keys()` | Get all keys |
20
21
  | `clear()` | Clear all data |
21
22
 
22
- ### MinioHelper Operations
23
+ ### IStorageHelper Operations
24
+
25
+ All storage helpers implementing `IStorageHelper` provide these operations:
23
26
 
24
27
  | Operation | Methods |
25
28
  |-----------|---------|
26
- | **Bucket** | `createBucket()`, `isBucketExists()`, `removeBucket()` |
27
- | **Upload** | `upload({ bucket, files })` |
28
- | **Download** | `getFile({ bucket, name })` |
29
+ | **Validation** | `isValidName()` |
30
+ | **Bucket** | `isBucketExists()`, `getBuckets()`, `getBucket()`, `createBucket()`, `removeBucket()` |
31
+ | **Upload** | `upload({ bucket, files, normalizeNameFn, normalizeLinkFn })` |
32
+ | **Download** | `getFile({ bucket, name })`, `getStat({ bucket, name })` |
29
33
  | **Delete** | `removeObject()`, `removeObjects()` |
34
+ | **List** | `listObjects({ bucket, prefix, useRecursive, maxKeys })` |
35
+ | **Utility** | `getFileType({ mimeType })` |
36
+
37
+ ---
38
+
39
+ ## Storage Architecture
40
+
41
+ ### IStorageHelper Interface
42
+
43
+ The unified storage interface implemented by all file storage helpers:
44
+
45
+ ```typescript
46
+ interface IStorageHelper {
47
+ // Name validation
48
+ isValidName(name: string): boolean;
49
+
50
+ // Bucket operations
51
+ isBucketExists(opts: { name: string }): Promise<boolean>;
52
+ getBuckets(): Promise<IBucketInfo[]>;
53
+ getBucket(opts: { name: string }): Promise<IBucketInfo | null>;
54
+ createBucket(opts: { name: string }): Promise<IBucketInfo | null>;
55
+ removeBucket(opts: { name: string }): Promise<boolean>;
56
+
57
+ // File operations
58
+ upload(opts: {
59
+ bucket: string;
60
+ files: IUploadFile[];
61
+ normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
62
+ normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
63
+ }): Promise<IUploadResult[]>;
64
+
65
+ getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
66
+ getStat(opts: { bucket: string; name: string }): Promise<IFileStat>;
67
+ removeObject(opts: { bucket: string; name: string }): Promise<void>;
68
+ removeObjects(opts: { bucket: string; names: string[] }): Promise<void>;
69
+ listObjects(opts: IListObjectsOptions): Promise<IObjectInfo[]>;
70
+
71
+ // Utility
72
+ getFileType(opts: { mimeType: string }): string;
73
+ }
74
+ ```
75
+
76
+ ### BaseStorageHelper
77
+
78
+ Abstract base class providing common functionality:
79
+
80
+ ```typescript
81
+ abstract class BaseStorageHelper implements IStorageHelper {
82
+ // Built-in security validation
83
+ isValidName(name: string): boolean;
84
+
85
+ // MIME type detection
86
+ getFileType(opts: { mimeType: string }): string;
87
+
88
+ // Abstract methods that implementations must provide
89
+ abstract isBucketExists(opts: { name: string }): Promise<boolean>;
90
+ abstract upload(opts: { /* ... */ }): Promise<IUploadResult[]>;
91
+ // ... other abstract methods
92
+ }
93
+ ```
94
+
95
+ ---
30
96
 
31
97
  ## `MemoryStorageHelper`
32
98
 
@@ -62,9 +128,228 @@ const allKeys = memoryStore.keys();
62
128
  memoryStore.clear();
63
129
  ```
64
130
 
131
+ ---
132
+
133
+ ## `DiskHelper`
134
+
135
+ The `DiskHelper` provides local filesystem storage using a bucket-based directory structure. It implements the `IStorageHelper` interface, making it easy to switch between local and cloud storage.
136
+
137
+ ### Creating an Instance
138
+
139
+ ```typescript
140
+ import { DiskHelper } from '@venizia/ignis-helpers';
141
+
142
+ const diskHelper = new DiskHelper({
143
+ basePath: './app_data/storage', // Base directory for storage
144
+ });
145
+ ```
146
+
147
+ **Options:**
148
+ - `basePath` (string, required): Base directory where buckets will be created
149
+ - `scope` (string, optional): Logger scope name
150
+ - `identifier` (string, optional): Helper identifier
151
+
152
+ **Directory Structure:**
153
+ ```
154
+ app_data/storage/ ← basePath
155
+ ├── bucket-1/ ← bucket (directory)
156
+ │ ├── file1.pdf ← object (file)
157
+ │ └── file2.jpg
158
+ ├── bucket-2/
159
+ │ └── document.docx
160
+ └── user-uploads/
161
+ ├── avatar.png
162
+ └── resume.pdf
163
+ ```
164
+
165
+ ### Key Features
166
+
167
+ ✅ **Automatic Directory Creation** - Creates directories as needed
168
+ ✅ **Built-in Security** - Path traversal protection, name validation
169
+ ✅ **Stream-Based** - Efficient for large files
170
+ ✅ **Metadata Support** - Uses filesystem stats
171
+ ✅ **Compatible Interface** - Same API as MinioHelper
172
+
173
+ ### Bucket Operations
174
+
175
+ ```typescript
176
+ // Check if bucket exists
177
+ const exists = await diskHelper.isBucketExists({ name: 'my-bucket' });
178
+
179
+ // Create a bucket (creates directory)
180
+ const bucket = await diskHelper.createBucket({ name: 'my-bucket' });
181
+ // Returns: { name: 'my-bucket', creationDate: Date }
182
+
183
+ // Get all buckets
184
+ const buckets = await diskHelper.getBuckets();
185
+ // Returns: [{ name: 'bucket-1', creationDate: Date }, ...]
186
+
187
+ // Get specific bucket
188
+ const bucket = await diskHelper.getBucket({ name: 'my-bucket' });
189
+ // Returns: { name: 'my-bucket', creationDate: Date } | null
190
+
191
+ // Remove bucket (removes directory, must be empty)
192
+ const removed = await diskHelper.removeBucket({ name: 'my-bucket' });
193
+ ```
194
+
195
+ ### File Operations
196
+
197
+ #### Upload Files
198
+
199
+ ```typescript
200
+ // Basic upload
201
+ const result = await diskHelper.upload({
202
+ bucket: 'my-bucket',
203
+ files: [
204
+ {
205
+ originalname: 'document.pdf',
206
+ mimetype: 'application/pdf',
207
+ buffer: fileBuffer,
208
+ size: fileBuffer.length,
209
+ },
210
+ ],
211
+ });
212
+ // Returns: [{ bucketName: 'my-bucket', objectName: 'document.pdf', link: '...' }]
213
+
214
+ // Upload with custom normalization
215
+ const result = await diskHelper.upload({
216
+ bucket: 'my-bucket',
217
+ files: files,
218
+ normalizeNameFn: ({` originalName, folderPath `}) => {
219
+ return folderPath ? `${folderPath}/${name}` : name;
220
+ },
221
+ normalizeLinkFn: ({ bucketName, normalizeName }) => {
222
+ return `/files/${bucketName}/${normalizeName}`;
223
+ },
224
+ });
225
+ ```
226
+
227
+ #### Get File (Stream)
228
+
229
+ ```typescript
230
+ const fileStream = await diskHelper.getFile({
231
+ bucket: 'my-bucket',
232
+ name: 'document.pdf',
233
+ });
234
+
235
+ // Pipe to response
236
+ fileStream.pipe(response);
237
+
238
+ // Or save to another location
239
+ import fs from 'fs';
240
+ const writeStream = fs.createWriteStream('./backup/document.pdf');
241
+ fileStream.pipe(writeStream);
242
+ ```
243
+
244
+ #### Get File Metadata
245
+
246
+ ```typescript
247
+ const stat = await diskHelper.getStat({
248
+ bucket: 'my-bucket',
249
+ name: 'document.pdf',
250
+ });
251
+
252
+ console.log(stat);
253
+ // {
254
+ // size: 1024,
255
+ // metadata: { /* filesystem stats */ },
256
+ // lastModified: Date,
257
+ // etag: 'hash',
258
+ // }
259
+ ```
260
+
261
+ #### List Objects
262
+
263
+ ```typescript
264
+ // List all objects in bucket
265
+ const objects = await diskHelper.listObjects({
266
+ bucket: 'my-bucket',
267
+ });
268
+
269
+ // List with prefix (folder-like behavior)
270
+ const objects = await diskHelper.listObjects({
271
+ bucket: 'my-bucket',
272
+ prefix: 'documents/',
273
+ useRecursive: false, // Non-recursive by default
274
+ });
275
+
276
+ // Limit results
277
+ const objects = await diskHelper.listObjects({
278
+ bucket: 'my-bucket',
279
+ maxKeys: 100,
280
+ });
281
+
282
+ console.log(objects);
283
+ // [
284
+ // { name: 'file1.pdf', size: 1024, lastModified: Date },
285
+ // { name: 'file2.jpg', size: 2048, lastModified: Date },
286
+ // ]
287
+ ```
288
+
289
+ #### Delete Objects
290
+
291
+ ```typescript
292
+ // Delete single object
293
+ await diskHelper.removeObject({
294
+ bucket: 'my-bucket',
295
+ name: 'old-file.pdf',
296
+ });
297
+
298
+ // Delete multiple objects
299
+ await diskHelper.removeObjects({
300
+ bucket: 'my-bucket',
301
+ names: ['file1.pdf', 'file2.jpg', 'file3.png'],
302
+ });
303
+ ```
304
+
305
+ ### Security & Validation
306
+
307
+ ```typescript
308
+ // Name validation (inherited from BaseStorageHelper)
309
+ const isValid = diskHelper.isValidName('my-file.pdf'); // true
310
+ const isValid = diskHelper.isValidName('../etc/passwd'); // false ❌ path traversal
311
+ const isValid = diskHelper.isValidName('.hidden'); // false ❌ hidden file
312
+ const isValid = diskHelper.isValidName('file;rm -rf'); // false ❌ shell injection
313
+
314
+ // All operations validate names before execution
315
+ try {
316
+ await diskHelper.createBucket({ name: '../../../etc' });
317
+ } catch (error) {
318
+ // Error: Invalid bucket name
319
+ }
320
+ ```
321
+
322
+ ### Use Cases
323
+
324
+ **Development & Testing:**
325
+ ```typescript
326
+ const devStorage = new DiskHelper({ basePath: './dev-storage' });
327
+ ```
328
+
329
+ **Small-Scale Production:**
330
+ ```typescript
331
+ const prodStorage = new DiskHelper({ basePath: '/var/app/storage' });
332
+ ```
333
+
334
+ **Temporary Files:**
335
+ ```typescript
336
+ const tempStorage = new DiskHelper({ basePath: './temp' });
337
+ ```
338
+
339
+ **Hybrid Setup:**
340
+ ```typescript
341
+ // User uploads → Cloud (MinIO)
342
+ const cloudStorage = new MinioHelper({ /* ... */ });
343
+
344
+ // System files → Local (Disk)
345
+ const localStorage = new DiskHelper({ basePath: './system-files' });
346
+ ```
347
+
348
+ ---
349
+
65
350
  ## `MinioHelper`
66
351
 
67
- The `MinioHelper` is a comprehensive client for interacting with MinIO or any S3-compatible object storage service.
352
+ The `MinioHelper` is a comprehensive client for interacting with MinIO or any S3-compatible object storage service. It implements the `IStorageHelper` interface for unified storage operations.
68
353
 
69
354
  ### Creating a MinIO Client
70
355
 
@@ -98,14 +383,43 @@ if (!bucketExists) {
98
383
  The `upload` method takes an array of file objects, typically from a multipart form data request.
99
384
 
100
385
  ```typescript
101
- // Assuming `files` is an array of IUploadFile objects from a request
386
+ // Basic upload
102
387
  const uploadResult = await minioClient.upload({
103
388
  bucket: 'my-bucket',
104
389
  files: files,
105
390
  });
106
391
  // => [{ bucket: 'my-bucket', fileName: '...', link: '...' }]
392
+
393
+ // Upload with custom filename normalization
394
+ const uploadResult = await minioClient.upload({
395
+ bucket: 'my-bucket',
396
+ files: files,
397
+ normalizeLinkFn: ({ bucketName, normalizeName }) => {
398
+ // Custom link generation
399
+ return `/api/files/${bucketName}/${encodeURIComponent(normalizeName)}`;
400
+ },
401
+ });
402
+ ```
403
+
404
+ **Options:**
405
+ - `bucket` (string): Target bucket name
406
+ - `files` (`Array<IUploadFile>`): Array of file objects to upload
407
+ - `normalizeNameFn` (optional): Custom function to normalize filenames (default: lowercase + replace spaces with underscores)
408
+ - `normalizeLinkFn` (optional): Custom function to generate file access links (default: `/static-assets/{bucket}/{encodedName}`)
409
+
410
+ **IUploadFile Interface:**
411
+ ```typescript
412
+ interface IUploadFile {
413
+ originalname: string;
414
+ mimetype: string;
415
+ buffer: Buffer;
416
+ size: number;
417
+ encoding?: string; // Optional encoding information
418
+ }
107
419
  ```
108
420
 
421
+ **Note:** MinioHelper now implements the same `IStorageHelper` interface as `DiskHelper`, making them interchangeable. See the DiskHelper section for detailed API documentation, which applies to both helpers.
422
+
109
423
  #### Getting an Object
110
424
 
111
425
  The `getFile` method returns a `Readable` stream for an object.
@@ -128,3 +442,216 @@ await minioClient.removeObject({ bucket: 'my-bucket', name: 'my-file.txt' });
128
442
  // Remove multiple objects
129
443
  await minioClient.removeObjects({ bucket: 'my-bucket', names: ['file1.txt', 'file2.txt'] });
130
444
  ```
445
+
446
+ ---
447
+
448
+ ## DiskHelper vs MinioHelper Comparison
449
+
450
+ Both helpers implement the same `IStorageHelper` interface, making them functionally equivalent from an API perspective.
451
+
452
+ ### Feature Comparison
453
+
454
+ | Feature | DiskHelper | MinioHelper |
455
+ |---------|------------|-------------|
456
+ | **Storage Type** | Local filesystem | S3-compatible cloud |
457
+ | **Interface** | `IStorageHelper` | `IStorageHelper` |
458
+ | **Scalability** | Limited to single server | Horizontally scalable |
459
+ | **Setup Complexity** | Simple (just a directory) | Requires MinIO server |
460
+ | **Performance** | Fast (local disk I/O) | Network-dependent |
461
+ | **Backup** | Manual filesystem backup | Built-in replication |
462
+ | **Cost** | Disk space only | Server + storage costs |
463
+ | **Use Case** | Development, small apps | Production, large scale |
464
+
465
+ ### When to Use Each
466
+
467
+ **Use DiskHelper when:**
468
+ - Developing/testing locally
469
+ - Running on a single server
470
+ - Storage needs are < 100GB
471
+ - Simplicity is priority
472
+ - No cloud infrastructure
473
+
474
+ **Use MinioHelper when:**
475
+ - Deploying to production
476
+ - Need horizontal scaling
477
+ - Storage needs > 100GB
478
+ - Need backup/replication
479
+ - Multi-server deployment
480
+
481
+ ### Hybrid Approach
482
+
483
+ Use both simultaneously for different purposes:
484
+
485
+ ```typescript
486
+ import { DiskHelper, MinioHelper } from '@venizia/ignis-helpers';
487
+
488
+ // Cloud storage for user content
489
+ const userStorage = new MinioHelper({ /* ... */ });
490
+
491
+ // Local storage for system files
492
+ const systemStorage = new DiskHelper({ basePath: './system' });
493
+
494
+ // Local storage for temporary files
495
+ const tempStorage = new DiskHelper({ basePath: './temp' });
496
+ ```
497
+
498
+ ---
499
+
500
+ ## Common Patterns
501
+
502
+ ### Pattern 1: Storage Abstraction
503
+
504
+ ```typescript
505
+ class FileService {
506
+ constructor(private storage: IStorageHelper) {}
507
+
508
+ async uploadFile(bucket: string, file: IUploadFile) {
509
+ return await this.storage.upload({ bucket, files: [file] });
510
+ }
511
+
512
+ async getFile(bucket: string, name: string) {
513
+ return await this.storage.getFile({ bucket, name });
514
+ }
515
+ }
516
+
517
+ // Use with either storage type
518
+ const service1 = new FileService(new DiskHelper({ basePath: './files' }));
519
+ const service2 = new FileService(new MinioHelper({ /* ... */ }));
520
+ ```
521
+
522
+ ### Pattern 2: Environment-Based Selection
523
+
524
+ ```typescript
525
+ import { applicationEnvironment } from '@venizia/ignis';
526
+
527
+ const createStorageHelper = (): IStorageHelper => {
528
+ const storageType = applicationEnvironment.get('STORAGE_TYPE');
529
+
530
+ if (storageType === 'minio') {
531
+ return new MinioHelper({
532
+ endPoint: applicationEnvironment.get('MINIO_HOST'),
533
+ port: Number(applicationEnvironment.get('MINIO_PORT')),
534
+ accessKey: applicationEnvironment.get('MINIO_ACCESS_KEY'),
535
+ secretKey: applicationEnvironment.get('MINIO_SECRET_KEY'),
536
+ useSSL: applicationEnvironment.get('MINIO_USE_SSL') === 'true',
537
+ });
538
+ }
539
+
540
+ return new DiskHelper({
541
+ basePath: applicationEnvironment.get('DISK_STORAGE_PATH') || './storage',
542
+ });
543
+ };
544
+
545
+ const storage = createStorageHelper();
546
+ ```
547
+
548
+ ### Pattern 3: Fallback Strategy
549
+
550
+ ```typescript
551
+ class ResilientStorage implements IStorageHelper {
552
+ constructor(
553
+ private primary: IStorageHelper,
554
+ private fallback: IStorageHelper,
555
+ ) {}
556
+
557
+ async upload(opts: any) {
558
+ try {
559
+ return await this.primary.upload(opts);
560
+ } catch (error) {
561
+ console.error('Primary storage failed, using fallback', error);
562
+ return await this.fallback.upload(opts);
563
+ }
564
+ }
565
+
566
+ // Implement other methods with similar fallback logic...
567
+ }
568
+
569
+ // Usage
570
+ const storage = new ResilientStorage(
571
+ new MinioHelper({ /* ... */ }), // Primary: Cloud
572
+ new DiskHelper({ basePath: './backup' }), // Fallback: Local
573
+ );
574
+ ```
575
+
576
+ ---
577
+
578
+ ## Related Documentation
579
+
580
+ - [Static Asset Component](../components/static-asset.md) - Uses storage helpers
581
+ - [Request Utilities](../utilities/request.md) - `parseMultipartBody`
582
+ - [Base Helpers](./index.md) - Helper architecture
583
+
584
+ ---
585
+
586
+ ## TypeScript Interfaces Reference
587
+
588
+ ### IUploadFile
589
+
590
+ ```typescript
591
+ interface IUploadFile {
592
+ originalName: string; // Original filename (renamed from 'originalname')
593
+ mimetype: string; // MIME type (e.g., 'image/png')
594
+ buffer: Buffer; // File content
595
+ size: number; // File size in bytes
596
+ encoding?: string; // Optional encoding (e.g., '7bit', 'base64')
597
+ folderPath?: string; // Optional folder path for organization
598
+ }
599
+ ```
600
+
601
+ **Note:** The property was renamed from `originalname` to `originalName` for consistency.
602
+
603
+ ### IUploadResult
604
+
605
+ ```typescript
606
+ interface IUploadResult {
607
+ bucketName: string; // Bucket where file was stored
608
+ objectName: string; // Stored filename
609
+ link: string; // Access URL
610
+ metaLink?: any; // MetaLink database record (if enabled)
611
+ metaLinkError?: any; // Error message if MetaLink creation failed
612
+ }
613
+ ```
614
+
615
+ ### IFileStat
616
+
617
+ ```typescript
618
+ interface IFileStat {
619
+ size: number; // File size in bytes
620
+ metadata: Record<string, any>; // Storage-specific metadata
621
+ lastModified?: Date; // Last modification date
622
+ etag?: string; // Entity tag
623
+ versionId?: string; // Version ID (if versioning enabled)
624
+ }
625
+ ```
626
+
627
+ ### IBucketInfo
628
+
629
+ ```typescript
630
+ interface IBucketInfo {
631
+ name: string; // Bucket name
632
+ creationDate: Date; // When bucket was created
633
+ }
634
+ ```
635
+
636
+ ### IObjectInfo
637
+
638
+ ```typescript
639
+ interface IObjectInfo {
640
+ name?: string; // Object name
641
+ size?: number; // Object size in bytes
642
+ lastModified?: Date; // Last modification date
643
+ etag?: string; // Entity tag
644
+ prefix?: string; // Prefix (for directory-like listing)
645
+ }
646
+ ```
647
+
648
+ ### IListObjectsOptions
649
+
650
+ ```typescript
651
+ interface IListObjectsOptions {
652
+ bucket: string; // Bucket to list
653
+ prefix?: string; // Filter by prefix
654
+ useRecursive?: boolean; // Recursive listing
655
+ maxKeys?: number; // Maximum objects to return
656
+ }
657
+ ```
@@ -8,5 +8,5 @@ Utilities are pure, standalone functions that provide common, reusable logic for
8
8
  - [Parse](./parse.md): A collection of functions for parsing and converting data types.
9
9
  - [Performance](./performance.md): Utilities for measuring code execution time.
10
10
  - [Promise](./promise.md): Helper functions for working with Promises.
11
- - [Request](./request.md): Utilities for handling HTTP requests, such as parsing multipart form data.
11
+ - [Request](./request.md): Utilities for handling HTTP requests, such as parsing multipart form data and creating secure Content-Disposition headers.
12
12
  - [Schema](./schema.md): Helpers for creating and validating Zod schemas, especially for request and response validation in an OpenAPI context.