@venizia/ignis-docs 0.0.5 → 0.0.6-1

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.
Files changed (123) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/architectural-patterns.md +0 -2
  3. package/wiki/best-practices/architecture-decisions.md +0 -8
  4. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  5. package/wiki/best-practices/code-style-standards/index.md +0 -1
  6. package/wiki/best-practices/code-style-standards/tooling.md +0 -3
  7. package/wiki/best-practices/contribution-workflow.md +12 -12
  8. package/wiki/best-practices/index.md +4 -14
  9. package/wiki/best-practices/performance-optimization.md +3 -3
  10. package/wiki/best-practices/security-guidelines.md +2 -2
  11. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  12. package/wiki/guides/core-concepts/application/bootstrapping.md +6 -7
  13. package/wiki/guides/core-concepts/components-guide.md +1 -1
  14. package/wiki/guides/core-concepts/components.md +2 -2
  15. package/wiki/guides/core-concepts/dependency-injection.md +4 -5
  16. package/wiki/guides/core-concepts/persistent/datasources.md +4 -5
  17. package/wiki/guides/core-concepts/services.md +1 -1
  18. package/wiki/guides/get-started/5-minute-quickstart.md +4 -5
  19. package/wiki/guides/get-started/philosophy.md +12 -24
  20. package/wiki/guides/index.md +2 -9
  21. package/wiki/guides/reference/mcp-docs-server.md +13 -13
  22. package/wiki/guides/tutorials/building-a-crud-api.md +10 -10
  23. package/wiki/guides/tutorials/complete-installation.md +11 -12
  24. package/wiki/guides/tutorials/ecommerce-api.md +3 -3
  25. package/wiki/guides/tutorials/realtime-chat.md +6 -6
  26. package/wiki/guides/tutorials/testing.md +4 -5
  27. package/wiki/index.md +8 -14
  28. package/wiki/references/base/bootstrapping.md +0 -3
  29. package/wiki/references/base/components.md +2 -2
  30. package/wiki/references/base/controllers.md +0 -1
  31. package/wiki/references/base/datasources.md +1 -1
  32. package/wiki/references/base/dependency-injection.md +2 -2
  33. package/wiki/references/base/filter-system/default-filter.md +2 -3
  34. package/wiki/references/base/filter-system/index.md +1 -1
  35. package/wiki/references/base/filter-system/quick-reference.md +0 -14
  36. package/wiki/references/base/middlewares.md +0 -8
  37. package/wiki/references/base/providers.md +0 -9
  38. package/wiki/references/base/repositories/advanced.md +1 -1
  39. package/wiki/references/base/repositories/mixins.md +2 -3
  40. package/wiki/references/base/services.md +0 -1
  41. package/wiki/references/components/authentication/api.md +444 -0
  42. package/wiki/references/components/authentication/errors.md +177 -0
  43. package/wiki/references/components/authentication/index.md +571 -0
  44. package/wiki/references/components/authentication/usage.md +781 -0
  45. package/wiki/references/components/health-check.md +292 -103
  46. package/wiki/references/components/index.md +14 -12
  47. package/wiki/references/components/mail/api.md +505 -0
  48. package/wiki/references/components/mail/errors.md +176 -0
  49. package/wiki/references/components/mail/index.md +535 -0
  50. package/wiki/references/components/mail/usage.md +404 -0
  51. package/wiki/references/components/request-tracker.md +229 -25
  52. package/wiki/references/components/socket-io/api.md +1051 -0
  53. package/wiki/references/components/socket-io/errors.md +119 -0
  54. package/wiki/references/components/socket-io/index.md +410 -0
  55. package/wiki/references/components/socket-io/usage.md +322 -0
  56. package/wiki/references/components/static-asset/api.md +261 -0
  57. package/wiki/references/components/static-asset/errors.md +89 -0
  58. package/wiki/references/components/static-asset/index.md +617 -0
  59. package/wiki/references/components/static-asset/usage.md +364 -0
  60. package/wiki/references/components/swagger.md +390 -110
  61. package/wiki/references/components/template/api-page.md +125 -0
  62. package/wiki/references/components/template/errors-page.md +100 -0
  63. package/wiki/references/components/template/index.md +104 -0
  64. package/wiki/references/components/template/setup-page.md +134 -0
  65. package/wiki/references/components/template/single-page.md +132 -0
  66. package/wiki/references/components/template/usage-page.md +127 -0
  67. package/wiki/references/components/websocket/api.md +508 -0
  68. package/wiki/references/components/websocket/errors.md +123 -0
  69. package/wiki/references/components/websocket/index.md +453 -0
  70. package/wiki/references/components/websocket/usage.md +475 -0
  71. package/wiki/references/helpers/cron/index.md +224 -0
  72. package/wiki/references/helpers/crypto/index.md +537 -0
  73. package/wiki/references/helpers/env/index.md +214 -0
  74. package/wiki/references/helpers/error/index.md +232 -0
  75. package/wiki/references/helpers/index.md +16 -15
  76. package/wiki/references/helpers/inversion/index.md +608 -0
  77. package/wiki/references/helpers/logger/index.md +600 -0
  78. package/wiki/references/helpers/network/api.md +986 -0
  79. package/wiki/references/helpers/network/index.md +620 -0
  80. package/wiki/references/helpers/queue/index.md +589 -0
  81. package/wiki/references/helpers/redis/index.md +495 -0
  82. package/wiki/references/helpers/socket-io/api.md +497 -0
  83. package/wiki/references/helpers/socket-io/index.md +513 -0
  84. package/wiki/references/helpers/storage/api.md +705 -0
  85. package/wiki/references/helpers/storage/index.md +583 -0
  86. package/wiki/references/helpers/template/index.md +66 -0
  87. package/wiki/references/helpers/template/single-page.md +126 -0
  88. package/wiki/references/helpers/testing/index.md +510 -0
  89. package/wiki/references/helpers/types/index.md +512 -0
  90. package/wiki/references/helpers/uid/index.md +272 -0
  91. package/wiki/references/helpers/websocket/api.md +736 -0
  92. package/wiki/references/helpers/websocket/index.md +574 -0
  93. package/wiki/references/helpers/worker-thread/index.md +470 -0
  94. package/wiki/references/index.md +2 -9
  95. package/wiki/references/quick-reference.md +3 -18
  96. package/wiki/references/utilities/jsx.md +1 -8
  97. package/wiki/references/utilities/statuses.md +0 -7
  98. package/wiki/references/components/authentication.md +0 -476
  99. package/wiki/references/components/mail.md +0 -687
  100. package/wiki/references/components/socket-io.md +0 -562
  101. package/wiki/references/components/static-asset.md +0 -1277
  102. package/wiki/references/helpers/cron.md +0 -108
  103. package/wiki/references/helpers/crypto.md +0 -132
  104. package/wiki/references/helpers/env.md +0 -83
  105. package/wiki/references/helpers/error.md +0 -97
  106. package/wiki/references/helpers/inversion.md +0 -176
  107. package/wiki/references/helpers/logger.md +0 -296
  108. package/wiki/references/helpers/network.md +0 -396
  109. package/wiki/references/helpers/queue.md +0 -150
  110. package/wiki/references/helpers/redis.md +0 -142
  111. package/wiki/references/helpers/socket-io.md +0 -932
  112. package/wiki/references/helpers/storage.md +0 -665
  113. package/wiki/references/helpers/testing.md +0 -133
  114. package/wiki/references/helpers/types.md +0 -167
  115. package/wiki/references/helpers/uid.md +0 -167
  116. package/wiki/references/helpers/worker-thread.md +0 -178
  117. package/wiki/references/src-details/boot.md +0 -379
  118. package/wiki/references/src-details/core.md +0 -263
  119. package/wiki/references/src-details/dev-configs.md +0 -298
  120. package/wiki/references/src-details/docs.md +0 -71
  121. package/wiki/references/src-details/helpers.md +0 -211
  122. package/wiki/references/src-details/index.md +0 -86
  123. package/wiki/references/src-details/inversion.md +0 -340
@@ -0,0 +1,583 @@
1
+ # Storage
2
+
3
+ Unified file storage abstraction with interchangeable backends for S3-compatible object storage, local filesystem, and in-memory key-value caching.
4
+
5
+ ## Quick Reference
6
+
7
+ | Class | Extends | Backend | Implements |
8
+ |-------|---------|---------|------------|
9
+ | **MinioHelper** | `BaseStorageHelper` | S3-compatible (MinIO) | `IStorageHelper` |
10
+ | **DiskHelper** | `BaseStorageHelper` | Local filesystem | `IStorageHelper` |
11
+ | **MemoryStorageHelper** | `BaseHelper` | In-memory key-value | -- |
12
+
13
+ #### Import Paths
14
+
15
+ ```typescript
16
+ // Disk and in-memory storage (from base package)
17
+ import { DiskHelper, MemoryStorageHelper } from '@venizia/ignis-helpers';
18
+
19
+ // MinIO storage (separate export path)
20
+ import { MinioHelper } from '@venizia/ignis-helpers/minio';
21
+
22
+ // Types
23
+ import type {
24
+ IStorageHelper,
25
+ IStorageHelperOptions,
26
+ IDiskHelperOptions,
27
+ IUploadFile,
28
+ IUploadResult,
29
+ IFileStat,
30
+ IBucketInfo,
31
+ IObjectInfo,
32
+ IListObjectsOptions,
33
+ } from '@venizia/ignis-helpers';
34
+ import type { IMinioHelperOptions } from '@venizia/ignis-helpers/minio';
35
+ ```
36
+
37
+ ## Creating an Instance
38
+
39
+ ### MinIO Storage
40
+
41
+ `MinioHelper` connects to MinIO or any S3-compatible object storage server. The constructor accepts all `minio.ClientOptions` properties alongside `IStorageHelperOptions`.
42
+
43
+ ```typescript
44
+ import { MinioHelper } from '@venizia/ignis-helpers/minio';
45
+
46
+ const storage = new MinioHelper({
47
+ endPoint: 'localhost',
48
+ port: 9000,
49
+ useSSL: false,
50
+ accessKey: 'minioadmin',
51
+ secretKey: 'minioadmin',
52
+ });
53
+ ```
54
+
55
+ #### IMinioHelperOptions
56
+
57
+ `IMinioHelperOptions` extends both `IStorageHelperOptions` and the minio `ClientOptions` type, so all [minio Client options](https://min.io/docs/minio/linux/developers/javascript/API.html) are accepted.
58
+
59
+ ```typescript
60
+ interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}
61
+ ```
62
+
63
+ | Option | Type | Default | Description |
64
+ |--------|------|---------|-------------|
65
+ | `endPoint` | `string` | -- | MinIO server hostname. |
66
+ | `port` | `number` | -- | Server port. |
67
+ | `useSSL` | `boolean` | -- | Enable HTTPS. |
68
+ | `accessKey` | `string` | -- | Access key credential. |
69
+ | `secretKey` | `string` | -- | Secret key credential. |
70
+ | `scope` | `string` | `'MinioHelper'` | Logger scope name. |
71
+ | `identifier` | `string` | `'MinioHelper'` | Helper identifier. |
72
+
73
+ > [!TIP]
74
+ > The underlying `minio.Client` is exposed as `storage.client` for direct access to any minio SDK method not covered by the `IStorageHelper` interface.
75
+
76
+ ### Disk Storage
77
+
78
+ `DiskHelper` provides local filesystem storage using a bucket-based directory structure. The `basePath` directory is created automatically if it does not exist.
79
+
80
+ ```typescript
81
+ import { DiskHelper } from '@venizia/ignis-helpers';
82
+
83
+ const storage = new DiskHelper({
84
+ basePath: './app_data/storage',
85
+ });
86
+ ```
87
+
88
+ #### IDiskHelperOptions
89
+
90
+ | Option | Type | Default | Description |
91
+ |--------|------|---------|-------------|
92
+ | `basePath` | `string` | -- | Base directory where buckets will be created. Resolved to an absolute path internally. Created automatically if it does not exist. |
93
+ | `scope` | `string` | `'DiskHelper'` | Logger scope name. |
94
+ | `identifier` | `string` | `'DiskHelper'` | Helper identifier. |
95
+
96
+ The resulting directory structure maps buckets to subdirectories:
97
+
98
+ ```
99
+ app_data/storage/ <-- basePath
100
+ ├── bucket-1/ <-- bucket (directory)
101
+ │ ├── file1.pdf <-- object (file)
102
+ │ └── file2.jpg
103
+ └── user-uploads/
104
+ ├── avatar.png
105
+ └── resume.pdf
106
+ ```
107
+
108
+ ### In-Memory Storage
109
+
110
+ `MemoryStorageHelper` is a standalone, generic key-value store for caching or temporary state within a single process. It does **not** implement `IStorageHelper` and has no bucket or file operations.
111
+
112
+ ```typescript
113
+ import { MemoryStorageHelper } from '@venizia/ignis-helpers';
114
+
115
+ // Direct instantiation
116
+ const cache = new MemoryStorageHelper();
117
+
118
+ // With custom scope for logging
119
+ const cache = new MemoryStorageHelper({ scope: 'SessionCache' });
120
+
121
+ // With typed container using the factory method
122
+ const cache = MemoryStorageHelper.newInstance<{ counter: number; name: string }>();
123
+ ```
124
+
125
+ #### Constructor Options
126
+
127
+ | Option | Type | Default | Description |
128
+ |--------|------|---------|-------------|
129
+ | `scope` | `string` | `'MemoryStorageHelper'` | Logger scope name. |
130
+
131
+ ## Usage
132
+
133
+ `DiskHelper` and `MinioHelper` implement the same `IStorageHelper` interface, making them interchangeable. All examples below apply to both unless noted otherwise.
134
+
135
+ ### Uploading Files
136
+
137
+ Pass an array of `IUploadFile` objects to `upload()`. The method validates all file names before writing, then uploads in parallel.
138
+
139
+ ```typescript
140
+ const results = await storage.upload({
141
+ bucket: 'my-bucket',
142
+ files: [
143
+ {
144
+ originalName: 'report.pdf',
145
+ mimetype: 'application/pdf',
146
+ buffer: fileBuffer,
147
+ size: fileBuffer.length,
148
+ encoding: '7bit',
149
+ },
150
+ ],
151
+ });
152
+
153
+ console.log(results);
154
+ // [{ bucketName: 'my-bucket', objectName: 'report.pdf', link: '/static-assets/my-bucket/report.pdf' }]
155
+ ```
156
+
157
+ #### Custom Name and Link Normalization
158
+
159
+ By default, file names are lowercased with spaces replaced by underscores. The default link prefix differs by backend: MinioHelper uses `/static-assets/{bucket}/{name}`, DiskHelper uses `/static-resources/{bucket}/{name}`. Override either with custom functions:
160
+
161
+ ```typescript
162
+ const results = await storage.upload({
163
+ bucket: 'my-bucket',
164
+ files: files,
165
+ normalizeNameFn: ({ originalName, folderPath }) => {
166
+ const timestamp = Date.now();
167
+ return folderPath
168
+ ? `${folderPath}/${timestamp}_${originalName}`
169
+ : `${timestamp}_${originalName}`;
170
+ },
171
+ normalizeLinkFn: ({ bucketName, normalizeName }) => {
172
+ return `/files/${bucketName}/${normalizeName}`;
173
+ },
174
+ });
175
+ ```
176
+
177
+ #### Upload with Folder Path
178
+
179
+ When `folderPath` is provided in an `IUploadFile`, the default normalization creates subdirectory-based paths:
180
+
181
+ ```typescript
182
+ const results = await storage.upload({
183
+ bucket: 'my-bucket',
184
+ files: [
185
+ {
186
+ originalName: 'avatar.png',
187
+ mimetype: 'image/png',
188
+ buffer: avatarBuffer,
189
+ size: avatarBuffer.length,
190
+ folderPath: 'users',
191
+ },
192
+ ],
193
+ });
194
+ // objectName: 'users/avatar.png'
195
+ ```
196
+
197
+ > [!WARNING]
198
+ > DiskHelper uses `/static-resources/` as the default link prefix, while MinioHelper uses `/static-assets/`. Provide a `normalizeLinkFn` if you need consistent links across storage backends.
199
+
200
+ ### Downloading Files
201
+
202
+ Retrieve a file as a Node.js `Readable` stream:
203
+
204
+ ```typescript
205
+ const fileStream = await storage.getFile({
206
+ bucket: 'my-bucket',
207
+ name: 'report.pdf',
208
+ });
209
+
210
+ // Pipe to an HTTP response
211
+ fileStream.pipe(response);
212
+
213
+ // Or write to disk
214
+ import fs from 'node:fs';
215
+ const writeStream = fs.createWriteStream('./downloads/report.pdf');
216
+ fileStream.pipe(writeStream);
217
+ ```
218
+
219
+ #### MinIO-Specific Options
220
+
221
+ MinioHelper supports additional options for server-side encryption and versioning:
222
+
223
+ ```typescript
224
+ const fileStream = await minioStorage.getFile({
225
+ bucket: 'my-bucket',
226
+ name: 'report.pdf',
227
+ options: {
228
+ versionId: 'specific-version-id',
229
+ SSECustomerAlgorithm: 'AES256',
230
+ SSECustomerKey: 'encryption-key',
231
+ SSECustomerKeyMD5: 'key-md5-hash',
232
+ },
233
+ });
234
+ ```
235
+
236
+ ### Getting File Metadata
237
+
238
+ ```typescript
239
+ const stat = await storage.getStat({
240
+ bucket: 'my-bucket',
241
+ name: 'report.pdf',
242
+ });
243
+
244
+ console.log(stat);
245
+ // {
246
+ // size: 204800,
247
+ // lastModified: 2025-01-15T10:30:00.000Z,
248
+ // metadata: { mimetype: 'application/pdf' },
249
+ // etag: 'abc123', // MinioHelper only
250
+ // versionId: 'v1', // MinioHelper only (if versioning enabled)
251
+ // }
252
+ ```
253
+
254
+ > [!NOTE]
255
+ > DiskHelper populates `metadata.mimetype` using the `getMimeType()` extension-based lookup. It does not return `etag` or `versionId`. MinioHelper returns full metadata from the MinIO server including the original upload metadata, `etag`, and `versionId`.
256
+
257
+ ### Listing Files
258
+
259
+ ```typescript
260
+ // List all objects in a bucket
261
+ const objects = await storage.listObjects({ bucket: 'my-bucket' });
262
+
263
+ // List with prefix filter
264
+ const docs = await storage.listObjects({
265
+ bucket: 'my-bucket',
266
+ prefix: 'documents/',
267
+ });
268
+
269
+ // Recursive listing (includes files in subdirectories)
270
+ const allFiles = await storage.listObjects({
271
+ bucket: 'my-bucket',
272
+ useRecursive: true,
273
+ });
274
+
275
+ // Limit the number of results
276
+ const firstTen = await storage.listObjects({
277
+ bucket: 'my-bucket',
278
+ maxKeys: 10,
279
+ });
280
+
281
+ console.log(allFiles);
282
+ // [
283
+ // { name: 'report.pdf', size: 204800, lastModified: Date, etag: '...' },
284
+ // { name: 'avatar.png', size: 51200, lastModified: Date },
285
+ // ]
286
+ ```
287
+
288
+ ### Deleting Files
289
+
290
+ ```typescript
291
+ // Delete a single object
292
+ await storage.removeObject({ bucket: 'my-bucket', name: 'old-file.pdf' });
293
+
294
+ // Delete multiple objects
295
+ await storage.removeObjects({
296
+ bucket: 'my-bucket',
297
+ names: ['file1.pdf', 'file2.jpg', 'file3.png'],
298
+ });
299
+ ```
300
+
301
+ > [!NOTE]
302
+ > DiskHelper's `removeObject()` throws if the file does not exist. DiskHelper's `removeObjects()` processes deletions sequentially. MinioHelper's `removeObjects()` delegates to the minio SDK's batch removal.
303
+
304
+ ### Bucket Operations
305
+
306
+ ```typescript
307
+ // Check if a bucket exists
308
+ const exists = await storage.isBucketExists({ name: 'my-bucket' });
309
+
310
+ // Create a new bucket
311
+ const bucket = await storage.createBucket({ name: 'my-bucket' });
312
+ // Returns: { name: 'my-bucket', creationDate: Date }
313
+
314
+ // List all buckets
315
+ const buckets = await storage.getBuckets();
316
+ // Returns: [{ name: 'bucket-1', creationDate: Date }, ...]
317
+
318
+ // Get a specific bucket
319
+ const bucket = await storage.getBucket({ name: 'my-bucket' });
320
+ // Returns: { name: 'my-bucket', creationDate: Date } | null
321
+
322
+ // Remove a bucket
323
+ const removed = await storage.removeBucket({ name: 'my-bucket' });
324
+ ```
325
+
326
+ > [!IMPORTANT]
327
+ > DiskHelper's `removeBucket()` requires the bucket directory to be empty. It throws if files remain. Remove all objects first, then remove the bucket.
328
+
329
+ ### In-Memory Storage Operations
330
+
331
+ `MemoryStorageHelper` provides a simple key-value API, separate from the bucket-based `IStorageHelper` interface:
332
+
333
+ ```typescript
334
+ const cache = new MemoryStorageHelper();
335
+
336
+ // Store a value
337
+ cache.set('user:123', { name: 'Alice', role: 'admin' });
338
+
339
+ // Retrieve a typed value
340
+ const user = cache.get<{ name: string; role: string }>('user:123');
341
+
342
+ // Check if a key exists
343
+ cache.isBound('user:123'); // true
344
+
345
+ // Get all keys
346
+ cache.keys(); // ['user:123']
347
+
348
+ // Access the underlying container
349
+ cache.getContainer(); // { 'user:123': { name: 'Alice', role: 'admin' } }
350
+
351
+ // Clear all stored data
352
+ cache.clear();
353
+ ```
354
+
355
+ ### Name Validation
356
+
357
+ All bucket and file operations validate names using `isValidName()` before execution. The following are rejected:
358
+
359
+ | Rule | Example | Reason |
360
+ |------|---------|--------|
361
+ | Contains `..`, `/`, or `\` | `../etc/passwd` | Path traversal |
362
+ | Starts with `.` | `.hidden` | Hidden file |
363
+ | Contains `;`, `\|`, `&`, `$`, `` ` ``, `<`, `>`, `{`, `}`, `[`, `]`, `!`, `#` | `file;rm -rf` | Shell injection |
364
+ | Contains `\n`, `\r`, or `\0` | `file\nname` | Header injection |
365
+ | Longer than 255 characters | (very long string) | DoS prevention |
366
+ | Empty or whitespace-only | `""`, `" "` | Invalid input |
367
+
368
+ ```typescript
369
+ storage.isValidName('my-file.pdf'); // true
370
+ storage.isValidName('../etc/passwd'); // false
371
+ storage.isValidName('.hidden'); // false
372
+ ```
373
+
374
+ ### MIME Type Detection
375
+
376
+ `getMimeType()` determines the MIME type from a filename's extension:
377
+
378
+ ```typescript
379
+ storage.getMimeType('photo.jpg'); // 'image/jpeg'
380
+ storage.getMimeType('data.csv'); // 'text/csv'
381
+ storage.getMimeType('unknown.xyz'); // 'application/octet-stream'
382
+ ```
383
+
384
+ `getFileType()` categorizes a MIME type into a broad group:
385
+
386
+ ```typescript
387
+ storage.getFileType({ mimeType: 'image/png' }); // 'image'
388
+ storage.getFileType({ mimeType: 'video/mp4' }); // 'video'
389
+ storage.getFileType({ mimeType: 'text/plain' }); // 'text'
390
+ storage.getFileType({ mimeType: 'application/pdf' }); // 'unknown'
391
+ ```
392
+
393
+ ### Common Patterns
394
+
395
+ #### Storage Abstraction
396
+
397
+ Use `IStorageHelper` to write storage-agnostic code:
398
+
399
+ ```typescript
400
+ class FileService {
401
+ constructor(private storage: IStorageHelper) {}
402
+
403
+ async uploadFile(bucket: string, file: IUploadFile) {
404
+ return this.storage.upload({ bucket, files: [file] });
405
+ }
406
+ }
407
+
408
+ // Swap backends without changing service code
409
+ const devService = new FileService(new DiskHelper({ basePath: './files' }));
410
+ const prodService = new FileService(new MinioHelper({ /* ... */ }));
411
+ ```
412
+
413
+ #### Environment-Based Selection
414
+
415
+ ```typescript
416
+ import { applicationEnvironment } from '@venizia/ignis-helpers';
417
+
418
+ const createStorage = (): IStorageHelper => {
419
+ if (applicationEnvironment.get('STORAGE_TYPE') === 'minio') {
420
+ return new MinioHelper({
421
+ endPoint: applicationEnvironment.get('MINIO_HOST'),
422
+ port: Number(applicationEnvironment.get('MINIO_PORT')),
423
+ accessKey: applicationEnvironment.get('MINIO_ACCESS_KEY'),
424
+ secretKey: applicationEnvironment.get('MINIO_SECRET_KEY'),
425
+ useSSL: applicationEnvironment.get('MINIO_USE_SSL') === 'true',
426
+ });
427
+ }
428
+
429
+ return new DiskHelper({
430
+ basePath: applicationEnvironment.get('DISK_STORAGE_PATH') || './storage',
431
+ });
432
+ };
433
+ ```
434
+
435
+ ## Troubleshooting
436
+
437
+ ### "[createBucket] Invalid name to create bucket!"
438
+
439
+ **Cause:** The bucket name failed `isValidName()` validation. The name may contain path traversal characters, start with a dot, contain shell-special characters, or exceed 255 characters.
440
+
441
+ **Fix:** Use a simple alphanumeric bucket name:
442
+
443
+ ```typescript
444
+ // Wrong
445
+ await storage.createBucket({ name: '../my-bucket' });
446
+ await storage.createBucket({ name: '.hidden-bucket' });
447
+
448
+ // Correct
449
+ await storage.createBucket({ name: 'my-bucket' });
450
+ ```
451
+
452
+ ### "[removeBucket] Invalid name to remove bucket!"
453
+
454
+ **Cause:** Same as above -- the bucket name failed validation.
455
+
456
+ **Fix:** Provide a valid bucket name that passes `isValidName()`.
457
+
458
+ ### "[createBucket] Bucket already exists | name: {name}"
459
+
460
+ **Cause:** DiskHelper throws when calling `createBucket()` on an existing bucket directory.
461
+
462
+ **Fix:** Check existence first:
463
+
464
+ ```typescript
465
+ const exists = await storage.isBucketExists({ name: 'my-bucket' });
466
+ if (!exists) {
467
+ await storage.createBucket({ name: 'my-bucket' });
468
+ }
469
+ ```
470
+
471
+ ### "[removeBucket] Bucket does not exist | name: {name}"
472
+
473
+ **Cause:** DiskHelper throws when attempting to remove a bucket directory that does not exist.
474
+
475
+ **Fix:** Check existence before removal:
476
+
477
+ ```typescript
478
+ const exists = await storage.isBucketExists({ name: 'my-bucket' });
479
+ if (exists) {
480
+ await storage.removeBucket({ name: 'my-bucket' });
481
+ }
482
+ ```
483
+
484
+ ### "[removeBucket] Bucket is not empty | name: {name}"
485
+
486
+ **Cause:** DiskHelper's `removeBucket()` requires the bucket directory to be empty before removal.
487
+
488
+ **Fix:** Remove all objects first:
489
+
490
+ ```typescript
491
+ const objects = await storage.listObjects({ bucket: 'my-bucket', useRecursive: true });
492
+ if (objects.length > 0) {
493
+ await storage.removeObjects({
494
+ bucket: 'my-bucket',
495
+ names: objects.map(o => o.name!),
496
+ });
497
+ }
498
+ await storage.removeBucket({ name: 'my-bucket' });
499
+ ```
500
+
501
+ ### "[upload] Bucket does not exist | name: {bucket}"
502
+
503
+ **Cause:** The target bucket does not exist. Both DiskHelper and MinioHelper validate bucket existence before uploading.
504
+
505
+ **Fix:** Create the bucket before uploading:
506
+
507
+ ```typescript
508
+ const exists = await storage.isBucketExists({ name: 'uploads' });
509
+ if (!exists) {
510
+ await storage.createBucket({ name: 'uploads' });
511
+ }
512
+ await storage.upload({ bucket: 'uploads', files: [...] });
513
+ ```
514
+
515
+ ### "[upload] Invalid original file name"
516
+
517
+ **Cause:** A file's `originalName` failed `isValidName()` validation.
518
+
519
+ **Fix:** Sanitize file names before uploading, or use `normalizeNameFn` to control the stored name:
520
+
521
+ ```typescript
522
+ await storage.upload({
523
+ bucket: 'my-bucket',
524
+ files: files,
525
+ normalizeNameFn: ({ originalName }) => {
526
+ return originalName.replace(/[^a-zA-Z0-9._-]/g, '_');
527
+ },
528
+ });
529
+ ```
530
+
531
+ ### "[upload] Invalid file size"
532
+
533
+ **Cause:** A file's `size` property is `0`, `undefined`, or falsy.
534
+
535
+ **Fix:** Ensure every file in the upload array has a valid `size` value:
536
+
537
+ ```typescript
538
+ const file: IUploadFile = {
539
+ originalName: 'doc.pdf',
540
+ mimetype: 'application/pdf',
541
+ buffer: fileBuffer,
542
+ size: fileBuffer.length, // Must be > 0
543
+ };
544
+ ```
545
+
546
+ ### "[getFile] File not found | bucket: {bucket} | name: {name}"
547
+
548
+ **Cause:** DiskHelper throws when the requested file does not exist on the filesystem.
549
+
550
+ **Fix:** Verify the file exists before attempting to retrieve it, or handle the error:
551
+
552
+ ```typescript
553
+ try {
554
+ const stream = await storage.getFile({ bucket: 'my-bucket', name: 'file.pdf' });
555
+ } catch (error) {
556
+ // File not found -- handle gracefully
557
+ }
558
+ ```
559
+
560
+ ### MinioHelper connection errors
561
+
562
+ **Cause:** Network or configuration issue between the application and the MinIO server.
563
+
564
+ **Checklist:**
565
+ - The MinIO server is running and reachable at the configured `endPoint` and `port`
566
+ - `useSSL` matches the server's TLS configuration
567
+ - `accessKey` and `secretKey` are correct
568
+ - Network and firewall rules allow the connection
569
+
570
+ ## See Also
571
+
572
+ - **Other Helpers:**
573
+ - [Helpers Index](../index) -- All available helpers
574
+ - [Queue Helper](../queue/) -- Message queue processing
575
+
576
+ - **References:**
577
+ - [Static Asset Component](/references/components/static-asset/) -- Serving stored files via HTTP
578
+ - [Request Utilities](/references/utilities/request) -- `parseMultipartBody` for file uploads
579
+ - [API Reference](./api) -- Full method signatures and types
580
+
581
+ - **External Resources:**
582
+ - [MinIO Documentation](https://min.io/docs/minio/linux/index.html) -- MinIO object storage
583
+ - [MinIO JavaScript SDK](https://min.io/docs/minio/linux/developers/javascript/API.html) -- Full minio client API
@@ -0,0 +1,66 @@
1
+ # Helper Documentation Template
2
+
3
+ Guide for writing consistent, professional helper reference docs for Ignis.
4
+
5
+ ## Tiers
6
+
7
+ | Tier | Structure | When to Use |
8
+ |------|-----------|-------------|
9
+ | **Tier 1 -- Single Page** | One `index.md` in a directory | Most helpers. Simple to medium complexity, single concern |
10
+ | **Tier 2 -- Two Pages** | `index.md` (setup + usage) + `api.md` (API reference) | Helpers with multiple sub-helpers, complex internals, or 800+ lines of source-verified content |
11
+
12
+ ### Tier Assignment
13
+
14
+ | Helper | Tier | Rationale |
15
+ |--------|------|-----------|
16
+ | Cron, Env, Error, UID, Types | 1 | Simple wrappers or single-concern utilities |
17
+ | Crypto, Redis, Inversion, Logger | 1 | Medium complexity but single concern |
18
+ | Testing, Worker-Thread, Queue | 1 | Manageable scope, small variants |
19
+ | Network | 2 | 4 protocol families (HTTP, TCP, TLS, UDP) |
20
+ | Storage | 2 | 3 backends + base abstraction |
21
+ | Socket.IO | 2 | Server + Client helpers, Redis integration |
22
+ | WebSocket | 2 | Largest helper, Server + Emitter, complex internals |
23
+
24
+ ## Principles
25
+
26
+ - **Usage-first** -- Show working code early, not abstract API tables
27
+ - **No `::: details` containers** -- Use `####` sub-headings for collapsed-style content
28
+ - **Scannable** -- Tables, short code blocks, and callouts at top level
29
+ - **Source-verified** -- All content sourced from actual source code, no inventing
30
+ - **Earn your section** -- Remove any section that does not apply
31
+ - **No emojis** -- Plain text only
32
+
33
+ ## Callout Standard
34
+
35
+ Use **GitHub-style only**:
36
+
37
+ ```markdown
38
+ > [!NOTE]
39
+ > Informational -- behavior clarification
40
+
41
+ > [!TIP]
42
+ > Suggestion or best practice
43
+
44
+ > [!WARNING]
45
+ > Gotcha, deprecation, or security concern
46
+
47
+ > [!IMPORTANT]
48
+ > Critical functionality impact
49
+ ```
50
+
51
+ ## Templates
52
+
53
+ | Template | Purpose |
54
+ |----------|---------|
55
+ | [Single Page](./single-page) | Tier 1 template -- one file per helper |
56
+
57
+ Tier 2 follows the same two-page pattern as [Component Tier 2](../components/template/).
58
+
59
+ ## Source Paths
60
+
61
+ | Package | Path |
62
+ |---------|------|
63
+ | Helpers | `packages/helpers/src/helpers/{name}/` |
64
+ | Inversion | `packages/inversion/src/` |
65
+ | Common types | `packages/helpers/src/common/types.ts` |
66
+ | Utilities | `packages/helpers/src/utilities/` |