@venizia/ignis-docs 0.2.0 → 0.2.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.
Files changed (142) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +22 -11
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +6 -2
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +182 -93
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -1,8 +1,25 @@
1
- # Storage -- API Reference
1
+ ---
2
+ title: Storage - Full Reference
3
+ description: Complete reference for the storage class hierarchy, every backend's method behavior, name validation rules, and error messages
4
+ difficulty: intermediate
5
+ ---
2
6
 
3
- ## Architecture
7
+ # Storage - Full Reference
4
8
 
5
- The storage system uses a class hierarchy with an abstract base class providing shared logic and three concrete implementations for different backends. `MemoryStorageHelper` is a separate, standalone class that does not participate in the `IStorageHelper` hierarchy.
9
+ Exhaustive reference for `BaseStorageHelper`, the three `IStorageHelper` backends, `MemoryStorageHelper`, and every type. For a readable introduction and the common tasks, start with the [Storage overview](/extensions/helpers/storage/).
10
+
11
+ **Files:**
12
+
13
+ - [`packages/helpers/src/modules/storage/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/base.ts) - `BaseStorageHelper`
14
+ - [`packages/helpers/src/modules/storage/minio/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/minio/helper.ts) - `MinioHelper`
15
+ - [`packages/helpers/src/modules/storage/bun-s3/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/bun-s3/helper.ts) - `BunS3Helper`
16
+ - [`packages/helpers/src/modules/storage/bun-s3/utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/bun-s3/utility.ts) - `buildSignedRequest` (AWS SigV4 for bucket management)
17
+ - [`packages/helpers/src/modules/storage/disk/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/disk/helper.ts) - `DiskHelper`
18
+ - [`packages/helpers/src/modules/storage/in-memory/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/in-memory/helper.ts) - `MemoryStorageHelper`
19
+ - [`packages/helpers/src/modules/storage/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/types.ts) - `IStorageHelper` and every option/result type
20
+ - [`packages/helpers/src/common/constants/mime.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/common/constants/mime.ts) - `MimeTypes` const-class
21
+
22
+ ## Class and Interface Model
6
23
 
7
24
  ```
8
25
  BaseHelper
@@ -10,12 +27,48 @@ BaseHelper
10
27
  │ ├── MinioHelper -- S3-compatible object storage (minio SDK)
11
28
  │ ├── BunS3Helper -- S3-compatible object storage (Bun-native S3Client)
12
29
  │ └── DiskHelper -- Local filesystem storage
13
- └── MemoryStorageHelper -- In-memory key-value store
30
+ └── MemoryStorageHelper -- In-memory key-value store (standalone, not IStorageHelper)
31
+ ```
32
+
33
+ - **`upload()` is a template method.** It validates the bucket and every file, then calls two protected hooks each backend supplies: `defaultLinkPrefix` (a getter) and `writeObject()` (the actual write).
34
+ - **Every other method is backend-specific.** `isBucketExists`, `getBuckets`, `getBucket`, `createBucket`, `removeBucket`, `getFile`, `getStat`, `removeObject`, `removeObjects`, and `listObjects` are declared `abstract` on `BaseStorageHelper` and fully reimplemented per backend - there is no shared logic between a filesystem read and a MinIO `statObject()` call.
35
+
36
+ > [!TIP] Typing rule
37
+ > Declare parameters and bindings as `IStorageHelper` for `MinioHelper` / `BunS3Helper` / `DiskHelper`. `MemoryStorageHelper` does not implement it and has its own standalone API - see [MemoryStorageHelper](#memorystoragehelper).
38
+
39
+ ### Import paths
40
+
41
+ ```typescript
42
+ // Disk and in-memory storage (root package export)
43
+ import { DiskHelper, MemoryStorageHelper } from '@venizia/ignis-helpers';
44
+
45
+ // MinIO storage (separate sub-path export - keeps `minio` an optional dependency)
46
+ import { MinioHelper } from '@venizia/ignis-helpers/minio';
47
+
48
+ // Bun S3 storage (separate sub-path export, Bun runtime only)
49
+ import { BunS3Helper } from '@venizia/ignis-helpers/bun-s3';
50
+
51
+ // Types
52
+ import type {
53
+ IStorageHelper,
54
+ IStorageHelperOptions,
55
+ IDiskHelperOptions,
56
+ IUploadFile,
57
+ IUploadResult,
58
+ IFileStat,
59
+ IBucketInfo,
60
+ IObjectInfo,
61
+ IListObjectsOptions,
62
+ } from '@venizia/ignis-helpers';
63
+ import type { IMinioHelperOptions } from '@venizia/ignis-helpers/minio';
64
+ import type { IBunS3HelperOptions } from '@venizia/ignis-helpers/bun-s3';
14
65
  ```
15
66
 
16
67
  ## BaseStorageHelper
17
68
 
18
- Abstract base class that extends `BaseHelper` and implements `IStorageHelper`. Provides name validation, MIME type detection, and file type categorization. All bucket and file operation methods are abstract and must be implemented by subclasses.
69
+ `Source ->` [`packages/helpers/src/modules/storage/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/base.ts)
70
+
71
+ Abstract class extending `BaseHelper`, implementing `IStorageHelper`. Provides name/path validation, MIME type detection, and the `upload()` template method.
19
72
 
20
73
  ### Constructor
21
74
 
@@ -28,7 +81,9 @@ constructor(opts: { scope: string; identifier: string })
28
81
  | `scope` | `string` | Logger scope name. |
29
82
  | `identifier` | `string` | Helper identifier. |
30
83
 
31
- ### Static Properties
84
+ Every concrete backend's own constructor supplies defaults (`options.scope ?? <ClassName>`, `options.identifier ?? <ClassName>`) before calling `super()` - `scope`/`identifier` are required here but optional on every subclass's public options type.
85
+
86
+ ### Static properties
32
87
 
33
88
  #### MIME_MAP
34
89
 
@@ -38,29 +93,28 @@ protected static MIME_MAP: Record<string, string>
38
93
 
39
94
  Extension-to-MIME-type mapping used by `getMimeType()`:
40
95
 
41
- | Extension | MIME Type |
42
- |-----------|-----------|
43
- | `.png` | `image/png` |
44
- | `.jpg`, `.jpeg` | `image/jpeg` |
45
- | `.gif` | `image/gif` |
46
- | `.webp` | `image/webp` |
47
- | `.svg` | `image/svg+xml` |
48
- | `.pdf` | `application/pdf` |
49
- | `.json` | `application/json` |
50
- | `.txt` | `text/plain` |
51
- | `.html` | `text/html` |
52
- | `.css` | `text/css` |
53
- | `.js` | `text/javascript` |
54
- | `.mp4` | `video/mp4` |
55
- | `.webm` | `video/webm` |
56
- | `.mp3` | `audio/mpeg` |
57
- | `.wav` | `audio/wav` |
58
- | `.zip` | `application/zip` |
59
- | `.csv` | `text/csv` |
60
- | `.xml` | `application/xml` |
96
+ | Extension | MIME Type | Extension | MIME Type |
97
+ |-----------|-----------|-----------|-----------|
98
+ | `.png` | `image/png` | `.mp4` | `video/mp4` |
99
+ | `.jpg`, `.jpeg` | `image/jpeg` | `.webm` | `video/webm` |
100
+ | `.gif` | `image/gif` | `.mp3` | `audio/mpeg` |
101
+ | `.webp` | `image/webp` | `.wav` | `audio/wav` |
102
+ | `.svg` | `image/svg+xml` | `.zip` | `application/zip` |
103
+ | `.pdf` | `application/pdf` | `.csv` | `text/csv` |
104
+ | `.json` | `application/json` | `.xml` | `application/xml` |
105
+ | `.txt` | `text/plain` | `.html` | `text/html` |
106
+ | `.css` | `text/css` | `.js` | `text/javascript` |
61
107
 
62
108
  Falls back to `application/octet-stream` for unrecognized extensions.
63
109
 
110
+ #### DEFAULT_MAX_FOLDER_DEPTH
111
+
112
+ ```typescript
113
+ static readonly DEFAULT_MAX_FOLDER_DEPTH = 2
114
+ ```
115
+
116
+ Default folder nesting allowed by `isValidPath()` and `upload()` when the caller does not pass `maxFolderDepth` / `opts.maxDepth`.
117
+
64
118
  ### Methods
65
119
 
66
120
  #### getMimeType
@@ -69,11 +123,13 @@ Falls back to `application/octet-stream` for unrecognized extensions.
69
123
  getMimeType(filename: string): string
70
124
  ```
71
125
 
72
- Returns the MIME type for a filename based on its extension. Extracts the extension using `path.extname()`, converts to lowercase, and looks it up in `MIME_MAP`.
126
+ Extracts the extension with `path.extname()`, lowercases it, and looks it up in `MIME_MAP`.
73
127
 
74
- | Parameter | Type | Description |
75
- |-----------|------|-------------|
76
- | `filename` | `string` | Filename with extension (e.g., `'photo.jpg'`). |
128
+ ```typescript
129
+ storage.getMimeType('photo.jpg'); // 'image/jpeg'
130
+ storage.getMimeType('data.csv'); // 'text/csv'
131
+ storage.getMimeType('unknown.xyz'); // 'application/octet-stream'
132
+ ```
77
133
 
78
134
  **Returns:** MIME type string, or `'application/octet-stream'` if unrecognized.
79
135
 
@@ -83,24 +139,26 @@ Returns the MIME type for a filename based on its extension. Extracts the extens
83
139
  isValidName(name: string): boolean
84
140
  ```
85
141
 
86
- Validates a **single path segment** (bucket name or bare file name - no slashes) against security rules. Used internally by `isValidPath` to validate each segment. Logs specific error messages for each validation failure.
87
-
88
- | Parameter | Type | Description |
89
- |-----------|------|-------------|
90
- | `name` | `string` | Single-segment name to validate (must not contain `/`). |
142
+ Validates a **single path segment** (bucket name or bare file name - must not contain `/`). Used internally by `isValidPath` to validate each segment. Logs a specific error for whichever rule fails.
91
143
 
92
- **Returns:** `true` if the name passes all checks, `false` otherwise.
144
+ | Rule (checked in order) | Example rejected | Reason |
145
+ |---|---|---|
146
+ | Must be a string | (non-string) | Type safety |
147
+ | Must not be empty | `''` | Invalid input |
148
+ | Must not contain `..`, `/`, or `\` | `../etc/passwd` | Path traversal |
149
+ | Must not start with `.` | `.hidden` | Hidden file |
150
+ | Must not contain `;`, `\|`, `&`, `$`, `` ` ``, `<`, `>`, `{`, `}`, `[`, `]`, `!`, `#` | `file;rm -rf` | Shell injection |
151
+ | Must not contain `\n`, `\r`, or `\0` | `file\nname` | Header injection |
152
+ | Must not exceed 255 characters | (very long string) | DoS prevention |
153
+ | Must not be whitespace-only | `' '` | Invalid input |
93
154
 
94
- **Validation rules (checked in order):**
155
+ ```typescript
156
+ storage.isValidName('my-file.pdf'); // true
157
+ storage.isValidName('../etc/passwd'); // false -- contains path separators
158
+ storage.isValidName('.hidden'); // false -- starts with dot
159
+ ```
95
160
 
96
- 1. Must be a string type
97
- 2. Must not be empty or null
98
- 3. Must not contain `..`, `/`, or `\` (path traversal)
99
- 4. Must not start with `.` (hidden files)
100
- 5. Must not contain `;`, `|`, `&`, `$`, `` ` ``, `<`, `>`, `{`, `}`, `[`, `]`, `!`, `#` (shell injection)
101
- 6. Must not contain `\n`, `\r`, or `\0` (header injection)
102
- 7. Must not exceed 255 characters (DoS prevention)
103
- 8. Must not be whitespace-only
161
+ **Returns:** `true` if the name passes every check, `false` otherwise.
104
162
 
105
163
  #### isValidPath
106
164
 
@@ -108,23 +166,24 @@ Validates a **single path segment** (bucket name or bare file name - no slashes)
108
166
  isValidPath(pathStr: string, opts?: { maxDepth?: number }): boolean
109
167
  ```
110
168
 
111
- Validates a **full object path** that may include folder segments (e.g., `2025/uploads/report.pdf`). Splits the path on `/`, validates each segment with `isValidName`, and enforces a maximum folder depth. Used for object name validation where paths with folder structure are allowed.
169
+ Validates a **full object path** that may include folder segments (e.g. `2025/uploads/report.pdf`). Trims leading/trailing slashes, splits on `/`, validates each segment with `isValidName`, and enforces a maximum folder depth.
112
170
 
113
- | Parameter | Type | Description |
114
- |-----------|------|-------------|
115
- | `pathStr` | `string` | Path string to validate (may contain `/` separators). |
116
- | `opts.maxDepth` | `number` | Maximum folder depth. Default: `2`. |
171
+ | Rule (checked in order) | Description |
172
+ |---|---|
173
+ | 1 | Must be a non-empty string |
174
+ | 2 | After stripping leading/trailing slashes, must not be empty |
175
+ | 3 | Must not contain empty segments (double slashes, e.g. `a//b`) |
176
+ | 4 | Folder depth (`segments.length - 1`) must not exceed `opts.maxDepth` (default `DEFAULT_MAX_FOLDER_DEPTH`, `2`) |
177
+ | 5 | Every segment must pass `isValidName()` |
178
+ | 6 | Total normalized path length must not exceed 1024 characters |
117
179
 
118
- **Returns:** `true` if the path and all its segments are valid, `false` otherwise.
119
-
120
- **Validation rules (checked in order):**
180
+ ```typescript
181
+ storage.isValidPath('folder/file.pdf'); // true
182
+ storage.isValidPath('../etc/passwd'); // false -- path traversal
183
+ storage.isValidPath('a/b/c/d/file.pdf'); // false -- exceeds default max depth (2)
184
+ ```
121
185
 
122
- 1. Must be a non-empty string
123
- 2. After stripping leading/trailing slashes, must not be empty
124
- 3. Must not contain empty segments (double slashes, e.g., `a//b`)
125
- 4. Folder depth must not exceed `maxDepth` (depth = number of `/` separators)
126
- 5. Every segment must pass `isValidName()`
127
- 6. Total path length must not exceed 1024 characters
186
+ **Returns:** `true` if the path and all its segments are valid, `false` otherwise.
128
187
 
129
188
  #### getFileType
130
189
 
@@ -132,149 +191,144 @@ Validates a **full object path** that may include folder segments (e.g., `2025/u
132
191
  getFileType(opts: { mimeType: string }): string
133
192
  ```
134
193
 
135
- Categorizes a MIME type into a broad file type group using the `MimeTypes` constants.
136
-
137
- | Parameter | Type | Description |
138
- |-----------|------|-------------|
139
- | `opts.mimeType` | `string` | Full MIME type string (e.g., `'image/png'`). |
194
+ Categorizes a MIME type using the `MimeTypes` const-class (`UNKNOWN`, `IMAGE`, `VIDEO`, `TEXT`) by checking whether `mimeType` (lowercased) starts with `image`, `video`, or `text`.
140
195
 
141
- **Returns:** One of `'image'`, `'video'`, `'text'`, or `'unknown'`.
196
+ ```typescript
197
+ storage.getFileType({ mimeType: 'image/png' }); // 'image'
198
+ storage.getFileType({ mimeType: 'video/mp4' }); // 'video'
199
+ storage.getFileType({ mimeType: 'text/plain' }); // 'text'
200
+ storage.getFileType({ mimeType: 'application/pdf' }); // 'unknown'
201
+ ```
142
202
 
143
- ### Abstract Methods
203
+ **Returns:** one of `'image'`, `'video'`, `'text'`, or `'unknown'`.
144
204
 
145
- The following methods are declared abstract in `BaseStorageHelper` and implemented by `MinioHelper`, `BunS3Helper`, and `DiskHelper`:
205
+ #### upload (template method - shared by every backend)
146
206
 
147
207
  ```typescript
148
- abstract isBucketExists(opts: { name: string }): Promise<boolean>;
149
- abstract getBuckets(): Promise<IBucketInfo[]>;
150
- abstract getBucket(opts: { name: string }): Promise<IBucketInfo | null>;
151
- abstract createBucket(opts: { name: string }): Promise<IBucketInfo | null>;
152
- abstract removeBucket(opts: { name: string }): Promise<boolean>;
153
-
154
- abstract upload(opts: {
208
+ async upload(opts: {
155
209
  bucket: string;
156
210
  files: IUploadFile[];
157
211
  normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
158
212
  normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
159
213
  maxFolderDepth?: number;
160
- }): Promise<IUploadResult[]>;
161
-
162
- abstract getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
163
- abstract getStat(opts: { bucket: string; name: string }): Promise<IFileStat>;
164
- abstract removeObject(opts: { bucket: string; name: string }): Promise<void>;
165
- abstract removeObjects(opts: { bucket: string; names: string[] }): Promise<void>;
166
- abstract listObjects(opts: {
167
- bucket: string;
168
- prefix?: string;
169
- useRecursive?: boolean;
170
- maxKeys?: number;
171
- }): Promise<IObjectInfo[]>;
214
+ }): Promise<IUploadResult[]>
172
215
  ```
173
216
 
174
- ## MinioHelper
175
-
176
- S3-compatible object storage client built on the `minio` package. Extends `BaseStorageHelper`.
177
-
178
- ### Constructor
179
-
180
- ```typescript
181
- constructor(options: IMinioHelperOptions)
182
- ```
217
+ Implemented once on `BaseStorageHelper`; `MinioHelper`, `BunS3Helper`, and `DiskHelper` do **not** override it. Steps, in order:
183
218
 
184
- Creates a new `minio.Client` internally and stores it as `this.client`.
219
+ 1. Returns `[]` immediately if `files` is empty.
220
+ 2. Calls `isBucketExists({ name: bucket })`; throws if the bucket does not exist.
221
+ 3. Validates every file (`validateUploadFiles`, below).
222
+ 4. For each file, in parallel via `Promise.all()`:
223
+ - Computes `normalizeName` via `normalizeNameFn` if provided, else the default normalizer (lowercase, spaces to `_`, `{folderPath}/` prefix if set).
224
+ - Re-validates `normalizeName` with `isValidPath({ maxDepth: maxFolderDepth })` - this catches a traversal payload returned by a **custom** `normalizeNameFn`, even though `originalName` already passed validation.
225
+ - Computes `normalizeLink` via `normalizeLinkFn` if provided, else the default (`{defaultLinkPrefix}{bucket}/{normalizeName}`, each `/`-segment `encodeURIComponent`-ed).
226
+ - Calls the backend's `writeObject({ bucket, normalizeName, file })`.
227
+ - Logs an info line with `normalizeName`, `normalizeLink`, `mimeType`, `encoding`, `size`, and elapsed time.
228
+ 5. Returns `{ bucketName, objectName, link }` per file.
185
229
 
186
- ```typescript
187
- interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}
188
- ```
230
+ **`validateUploadFiles` (per file, in order):**
189
231
 
190
- | Parameter | Type | Default | Description |
191
- |-----------|------|---------|-------------|
192
- | `options.endPoint` | `string` | -- | MinIO server hostname. |
193
- | `options.port` | `number` | -- | Server port. |
194
- | `options.useSSL` | `boolean` | -- | Enable HTTPS. |
195
- | `options.accessKey` | `string` | -- | Access key credential. |
196
- | `options.secretKey` | `string` | -- | Secret key credential. |
197
- | `options.scope` | `string` | `'MinioHelper'` | Logger scope name. |
198
- | `options.identifier` | `string` | `'MinioHelper'` | Helper identifier. |
232
+ | Check | Throws |
233
+ |---|---|
234
+ | `isValidName(originalName)` | `'[upload] Invalid original file name'` |
235
+ | If `folderPath` set: segment count vs. `maxFolderDepth ?? DEFAULT_MAX_FOLDER_DEPTH` | `` `[upload] Invalid folder path | depth: {depth} | max: {max}` `` |
236
+ | If `folderPath` set: `isValidPath(folderPath, { maxDepth })` | `'[upload] Invalid folder path'` |
237
+ | `size` must be a number `>= 0` (`undefined`/`null`/negative rejected; `0` is a legal empty file) | `` `[upload] Invalid file size | size: {size}` `` |
199
238
 
200
- All additional `minio.ClientOptions` properties are also accepted and passed to the underlying client.
239
+ **Also throws:**
201
240
 
202
- ### Methods
241
+ | When | Message |
242
+ |---|---|
243
+ | Bucket does not exist | `` `[upload] Bucket does not exist | name: {bucket}` `` |
244
+ | A custom `normalizeNameFn` returns a path that fails `isValidPath` | `` `[upload] Invalid normalized object name | name: {name}` `` |
203
245
 
204
- #### isBucketExists
246
+ ### Protected extension points (implemented per backend)
205
247
 
206
248
  ```typescript
207
- async isBucketExists(opts: { name: string }): Promise<boolean>
208
- ```
209
-
210
- Returns `false` if the name fails `isValidName()`. Otherwise delegates to `client.bucketExists()`.
249
+ protected abstract get defaultLinkPrefix(): string;
211
250
 
212
- #### getBuckets
251
+ protected abstract writeObject(opts: {
252
+ bucket: string;
253
+ normalizeName: string;
254
+ file: IUploadFile;
255
+ }): Promise<void>;
213
256
 
214
- ```typescript
215
- async getBuckets(): Promise<IBucketInfo[]>
257
+ protected normalizeObjectName(opts: { originalName: string; folderPath?: string }): string;
258
+ protected normalizeObjectLink(opts: { bucketName: string; normalizeName: string }): string;
259
+ protected validateUploadFiles(opts: { files: IUploadFile[]; maxFolderDepth?: number }): void;
216
260
  ```
217
261
 
218
- Lists all buckets via `client.listBuckets()`.
262
+ - **`defaultLinkPrefix` and `writeObject` are `protected abstract`.** Not part of `IStorageHelper` - they exist purely so `upload()` can be written once.
263
+ - **`normalizeObjectName`, `normalizeObjectLink`, and `validateUploadFiles` are concrete.** Used internally by `upload()`; no backend overrides them.
219
264
 
220
- #### getBucket
265
+ ### Public abstract methods (reimplemented per backend, no shared logic)
221
266
 
222
267
  ```typescript
223
- async getBucket(opts: { name: string }): Promise<IBucketInfo | null>
224
- ```
225
-
226
- Returns the bucket info if it exists, or `null` if not found. Calls `isBucketExists()` first, then searches the full bucket list.
227
-
228
- #### createBucket
268
+ abstract isBucketExists(opts: { name: string }): Promise<boolean>;
269
+ abstract getBuckets(): Promise<IBucketInfo[]>;
270
+ abstract getBucket(opts: { name: string }): Promise<IBucketInfo | null>;
271
+ abstract createBucket(opts: { name: string }): Promise<IBucketInfo | null>;
272
+ abstract removeBucket(opts: { name: string }): Promise<boolean>;
229
273
 
230
- ```typescript
231
- async createBucket(opts: { name: string }): Promise<IBucketInfo | null>
274
+ abstract getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
275
+ abstract getStat(opts: { bucket: string; name: string }): Promise<IFileStat>;
276
+ abstract removeObject(opts: { bucket: string; name: string }): Promise<void>;
277
+ abstract removeObjects(opts: { bucket: string; names: string[] }): Promise<void>;
278
+ abstract listObjects(opts: {
279
+ bucket: string;
280
+ prefix?: string;
281
+ useRecursive?: boolean;
282
+ maxKeys?: number;
283
+ }): Promise<IObjectInfo[]>;
232
284
  ```
233
285
 
234
- Creates a bucket via `client.makeBucket()`. Throws if the name fails validation.
235
-
236
- **Throws:** `'[createBucket] Invalid name to create bucket!'`
286
+ See each backend's section below for behavior.
237
287
 
238
- #### removeBucket
239
-
240
- ```typescript
241
- async removeBucket(opts: { name: string }): Promise<boolean>
242
- ```
288
+ ## MinioHelper
243
289
 
244
- Removes a bucket via `client.removeBucket()`. Throws if the name fails validation.
290
+ `Source ->` [`packages/helpers/src/modules/storage/minio/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/minio/helper.ts)
245
291
 
246
- **Throws:** `'[removeBucket] Invalid name to remove bucket!'`
292
+ S3-compatible object storage built on the `minio` package. Extends `BaseStorageHelper`.
247
293
 
248
- #### upload
294
+ ### Constructor
249
295
 
250
296
  ```typescript
251
- async upload(opts: {
252
- bucket: string;
253
- files: IUploadFile[];
254
- normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
255
- normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
256
- maxFolderDepth?: number;
257
- }): Promise<IUploadResult[]>
297
+ constructor(options: IMinioHelperOptions)
298
+
299
+ interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}
258
300
  ```
259
301
 
260
- Uploads files to a MinIO bucket. Returns `[]` if `files` is empty. Validates the bucket exists and all file names/sizes before uploading. Uploads run in parallel via `Promise.all()`.
302
+ Creates a `minio.Client` internally and stores it as a private `client` field - not exposed. Extend `MinioHelper` in a subclass if you need direct SDK access.
261
303
 
262
304
  | Parameter | Type | Default | Description |
263
305
  |-----------|------|---------|-------------|
264
- | `opts.maxFolderDepth` | `number` | `BaseStorageHelper.DEFAULT_MAX_FOLDER_DEPTH` (`2`) | Folder nesting the caller allows, for both `folderPath` and the normalized object name. |
306
+ | `options.endPoint` | `string` | - | MinIO server hostname. |
307
+ | `options.port` | `number` | - | Server port. |
308
+ | `options.useSSL` | `boolean` | - | Enable HTTPS. |
309
+ | `options.accessKey` | `string` | - | Access key credential. |
310
+ | `options.secretKey` | `string` | - | Secret key credential. |
311
+ | `options.scope` | `string` | `'MinioHelper'` | Logger scope name. |
312
+ | `options.identifier` | `string` | `'MinioHelper'` | Helper identifier. |
265
313
 
266
- **Default name normalization:** Lowercased with spaces replaced by `_`. If `folderPath` is set, prepends `{folderPath}/`.
314
+ All other `minio.ClientOptions` fields (`region`, `transport`, `sessionToken`, `partSize`, `pathStyle`, ...) are also accepted and passed to the client - see the [minio JavaScript SDK docs](https://min.io/docs/minio/linux/developers/javascript/API.html).
267
315
 
268
- **Default link format:** `/static-assets/{bucket}/{normalizeName}`, with each `/`-separated segment of `normalizeName` URI-encoded via `encodeURIComponent()`.
316
+ ### defaultLinkPrefix and writeObject
269
317
 
270
- **Metadata stored:** `originalName`, `normalizeName`, `size`, `encoding`, `mimeType`.
318
+ - `defaultLinkPrefix`: `'/static-assets/'`
319
+ - `writeObject`: calls `client.putObject(bucket, normalizeName, buffer, size, metadata)` where `metadata` is `{ originalName, normalizeName, size, encoding, mimeType }` - the full upload metadata is persisted server-side and returned later by `getStat()`.
271
320
 
272
- **Throws:**
273
- - `'[upload] Bucket does not exist | name: {bucket}'`
274
- - `'[upload] Invalid original file name'`
275
- - `'[upload] Invalid folder path'`
276
- - `'[upload] Invalid file size'`
277
- - `'[upload] Invalid normalized object name | name: {name}'` -- the value returned by a custom `normalizeNameFn` is path-validated before use; a traversal payload is rejected here even if the original name passed validation.
321
+ ### Methods
322
+
323
+ | Method | Behavior |
324
+ |---|---|
325
+ | `isBucketExists` | Returns `false` if the name fails `isValidName()`. Otherwise `client.bucketExists()`. |
326
+ | `getBuckets` | `client.listBuckets()`. |
327
+ | `getBucket` | `isBucketExists()` first; if true, finds the entry in `getBuckets()`; `null` if not found. |
328
+ | `createBucket` | `client.makeBucket()`, then returns `getBucket()`. Throws `'[createBucket] Invalid name to create bucket!'` if the name fails validation. |
329
+ | `removeBucket` | `client.removeBucket()`. Throws `'[removeBucket] Invalid name to remove bucket!'` if the name fails validation. |
330
+ | `removeObject` | `client.removeObject()`. |
331
+ | `removeObjects` | `client.removeObjects()` - a single batch SDK call. |
278
332
 
279
333
  #### getFile
280
334
 
@@ -291,42 +345,28 @@ getFile(opts: {
291
345
  }): Promise<Readable>
292
346
  ```
293
347
 
294
- Returns a readable stream for the file via `client.getObject()`. Supports versioning and server-side encryption options.
295
-
296
- | Parameter | Type | Description |
297
- |-----------|------|-------------|
298
- | `opts.bucket` | `string` | Bucket name. |
299
- | `opts.name` | `string` | Object name. |
300
- | `opts.options.versionId` | `string` | Specific version to retrieve. |
301
- | `opts.options.SSECustomerAlgorithm` | `string` | SSE-C algorithm (e.g., `'AES256'`). |
302
- | `opts.options.SSECustomerKey` | `string` | SSE-C encryption key. |
303
- | `opts.options.SSECustomerKeyMD5` | `string` | MD5 hash of the encryption key. |
304
-
305
- #### getStat
306
-
307
- ```typescript
308
- async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>
309
- ```
310
-
311
- Returns file metadata via `client.statObject()`.
312
-
313
- **Returns:** `IFileStat` with `size`, `metadata` (from MinIO's `metaData`), `lastModified`, `etag`, and `versionId`.
314
-
315
- #### removeObject
348
+ Returns a readable stream via `client.getObject()`. Supports versioning and SSE-C server-side encryption.
316
349
 
317
350
  ```typescript
318
- async removeObject(opts: { bucket: string; name: string }): Promise<void>
351
+ const fileStream = await minioStorage.getFile({
352
+ bucket: 'my-bucket',
353
+ name: 'report.pdf',
354
+ options: {
355
+ versionId: 'specific-version-id',
356
+ SSECustomerAlgorithm: 'AES256',
357
+ SSECustomerKey: 'encryption-key',
358
+ SSECustomerKeyMD5: 'key-md5-hash',
359
+ },
360
+ });
319
361
  ```
320
362
 
321
- Removes a single object via `client.removeObject()`.
322
-
323
- #### removeObjects
363
+ #### getStat
324
364
 
325
365
  ```typescript
326
- async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>
366
+ async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>
327
367
  ```
328
368
 
329
- Removes multiple objects in a single batch via `client.removeObjects()`.
369
+ `client.statObject()`. Returns `size`, `metadata` (MinIO's `metaData` - the full dict written by `writeObject`), `lastModified`, `etag`, and `versionId` (if versioning is enabled).
330
370
 
331
371
  #### listObjects
332
372
 
@@ -339,30 +379,31 @@ async listObjects(opts: {
339
379
  }): Promise<IObjectInfo[]>
340
380
  ```
341
381
 
342
- Lists objects in a bucket using a streaming approach via `client.listObjects()`. The stream is destroyed early if `maxKeys` is reached.
382
+ Streams via `client.listObjects(bucket, prefix, useRecursive)`; the stream is destroyed early once `maxKeys` is reached.
343
383
 
344
- | Parameter | Type | Default | Description |
345
- |-----------|------|---------|-------------|
346
- | `opts.bucket` | `string` | -- | Bucket to list. |
347
- | `opts.prefix` | `string` | `''` | Filter by prefix. |
348
- | `opts.useRecursive` | `boolean` | `false` | List recursively through subdirectories. |
349
- | `opts.maxKeys` | `number` | `undefined` | Maximum number of objects to return. |
384
+ | Parameter | Default | Description |
385
+ |---|---|---|
386
+ | `prefix` | `''` | Filter by prefix. |
387
+ | `useRecursive` | `false` | List recursively through subdirectories. |
388
+ | `maxKeys` | `undefined` | Maximum objects to return. |
350
389
 
351
390
  ## BunS3Helper
352
391
 
353
- S3-compatible object storage using Bun's native `S3Client`. Extends `BaseStorageHelper`. Requires the Bun runtime.
392
+ `Source ->` [`packages/helpers/src/modules/storage/bun-s3/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/bun-s3/helper.ts)
354
393
 
355
- Bucket management operations (list, create, delete) use AWS Signature V4 signed requests built from scratch via the `buildSignedRequest` utility. Object operations (upload, get, stat, delete, list) use Bun's native S3 API.
394
+ S3-compatible object storage using Bun's native `S3Client`. Extends `BaseStorageHelper`.
395
+
396
+ > [!IMPORTANT]
397
+ > Requires the **Bun runtime** - `bun:S3Client` is not available under Node.js.
398
+
399
+ - **Bucket management is hand-built.** `getBuckets`, `createBucket`, and `removeBucket` use AWS Signature V4 signed `fetch()` requests via `buildSignedRequest()`, because Bun's `S3Client` has no bucket-management API.
400
+ - **Object operations use the native SDK.** `upload`'s `writeObject`, `getFile`, `getStat`, `removeObject`, `removeObjects`, and `listObjects` all call Bun's native `S3Client` methods.
356
401
 
357
402
  ### Constructor
358
403
 
359
404
  ```typescript
360
405
  constructor(options: IBunS3HelperOptions)
361
- ```
362
406
 
363
- Creates a Bun `S3Client` internally and stores credentials for bucket management requests.
364
-
365
- ```typescript
366
407
  interface IBunS3HelperOptions extends IStorageHelperOptions {
367
408
  accessKey: string;
368
409
  secretKey: string;
@@ -372,73 +413,34 @@ interface IBunS3HelperOptions extends IStorageHelperOptions {
372
413
  }
373
414
  ```
374
415
 
416
+ Creates a Bun `S3Client` for object operations and stores `{ accessKey, secretKey, endpoint, region, sessionToken }` separately for the signed bucket-management requests.
417
+
375
418
  | Parameter | Type | Default | Description |
376
419
  |-----------|------|---------|-------------|
377
- | `options.accessKey` | `string` | -- | S3 access key credential. |
378
- | `options.secretKey` | `string` | -- | S3 secret key credential. |
379
- | `options.endpoint` | `string` | -- | S3-compatible endpoint URL. |
380
- | `options.region` | `string` | `'us-east-1'` | AWS region for signing. |
381
- | `options.sessionToken` | `string` | -- | Optional session token for temporary credentials. |
420
+ | `options.accessKey` | `string` | - | S3 access key credential. |
421
+ | `options.secretKey` | `string` | - | S3 secret key credential. |
422
+ | `options.endpoint` | `string` | - | S3-compatible endpoint URL (e.g. `'http://localhost:9000'`). |
423
+ | `options.region` | `string` | `'us-east-1'` | Region used for SigV4 signing of bucket-management requests. |
424
+ | `options.sessionToken` | `string` | - | Optional session token for temporary credentials. |
382
425
  | `options.scope` | `string` | `'BunS3Helper'` | Logger scope name. |
383
426
  | `options.identifier` | `string` | `'BunS3Helper'` | Helper identifier. |
384
427
 
385
- ### Methods
386
-
387
- #### isBucketExists
388
-
389
- ```typescript
390
- async isBucketExists(opts: { name: string }): Promise<boolean>
391
- ```
392
-
393
- Returns `false` if the name fails `isValidName()`. Otherwise attempts a `list({ maxKeys: 1 })` against the bucket. Returns `false` on any error.
394
-
395
- #### getBuckets
396
-
397
- ```typescript
398
- async getBuckets(): Promise<IBucketInfo[]>
399
- ```
400
-
401
- Lists all buckets via a signed `GET /` request. Parses the XML response to extract bucket names and creation dates.
402
-
403
- #### getBucket
404
-
405
- ```typescript
406
- async getBucket(opts: { name: string }): Promise<IBucketInfo | null>
407
- ```
408
-
409
- Returns the bucket info from the full bucket list, or `null` if not found.
410
-
411
- #### createBucket
412
-
413
- ```typescript
414
- async createBucket(opts: { name: string }): Promise<IBucketInfo | null>
415
- ```
416
-
417
- Creates a bucket via a signed `PUT /{name}` request. Throws if the name fails validation or the S3 request fails.
418
-
419
- #### removeBucket
420
-
421
- ```typescript
422
- async removeBucket(opts: { name: string }): Promise<boolean>
423
- ```
424
-
425
- Removes a bucket via a signed `DELETE /{name}` request. Throws if the name fails validation or the S3 request fails.
426
-
427
- #### upload
428
+ ### defaultLinkPrefix and writeObject
428
429
 
429
- ```typescript
430
- async upload(opts: {
431
- bucket: string;
432
- files: IUploadFile[];
433
- normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
434
- normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
435
- maxFolderDepth?: number;
436
- }): Promise<IUploadResult[]>
437
- ```
430
+ - `defaultLinkPrefix`: `'/static-assets/'`
431
+ - `writeObject`: `client.write(normalizeName, buffer, { bucket, type: mimeType })` - only the content type is persisted; unlike `MinioHelper`, no `originalName`/`encoding`/`size` metadata dictionary is stored.
438
432
 
439
- Uploads files using `client.write(name, buffer, { bucket, type })`. Same validation and normalization behavior as MinioHelper, including `maxFolderDepth` and the normalized-name path validation.
433
+ ### Methods
440
434
 
441
- **Default link format:** `/static-assets/{bucket}/{normalizeName}`, with each `/`-separated segment of `normalizeName` URI-encoded via `encodeURIComponent()`.
435
+ | Method | Behavior |
436
+ |---|---|
437
+ | `isBucketExists` | Returns `false` if the name fails `isValidName()`. Otherwise attempts `client.list({ maxKeys: 1 }, { bucket: name })`; returns `false` on any error (network, missing bucket, etc.). |
438
+ | `getBuckets` | Signed `GET /`; parses `<Bucket><Name>...<CreationDate>...` from the XML response. |
439
+ | `getBucket` | Finds the entry in `getBuckets()`; `null` if not found. |
440
+ | `createBucket` | Signed `PUT /{name}`. Throws `'[createBucket] Invalid name to create bucket!'` on invalid name, or `` `[createBucket] S3 error: {xml}` `` on a non-OK response. |
441
+ | `removeBucket` | Signed `DELETE /{name}`. Throws `'[removeBucket] Invalid name to remove bucket!'` on invalid name, or `` `[removeBucket] S3 error: {xml}` `` on a non-OK response. |
442
+ | `removeObject` | `client.delete(name, { bucket })`. |
443
+ | `removeObjects` | Deletes in **parallel** via `Promise.all(names.map(...))`. |
442
444
 
443
445
  #### getFile
444
446
 
@@ -446,7 +448,7 @@ Uploads files using `client.write(name, buffer, { bucket, type })`. Same validat
446
448
  async getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>
447
449
  ```
448
450
 
449
- Returns a `Readable` stream by converting the Bun S3 file's web stream via `Readable.fromWeb()`.
451
+ Converts the Bun S3 file's web `ReadableStream` via `Readable.fromWeb()`. The `options` parameter is accepted for interface compatibility but not used.
450
452
 
451
453
  #### getStat
452
454
 
@@ -454,38 +456,20 @@ Returns a `Readable` stream by converting the Bun S3 file's web stream via `Read
454
456
  async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>
455
457
  ```
456
458
 
457
- Returns file metadata via `client.stat()`.
458
-
459
- **Returns:**
459
+ `client.stat(name, { bucket })`. Returns:
460
460
 
461
461
  ```typescript
462
462
  {
463
463
  size: number;
464
464
  lastModified: Date;
465
465
  metadata: {
466
- contentType: string;
467
- mimetype: string;
466
+ contentType: string; // from stat.type
467
+ mimetype: string; // also from stat.type
468
468
  };
469
469
  etag: string;
470
470
  }
471
471
  ```
472
472
 
473
- #### removeObject
474
-
475
- ```typescript
476
- async removeObject(opts: { bucket: string; name: string }): Promise<void>
477
- ```
478
-
479
- Removes a single object via `client.delete()`.
480
-
481
- #### removeObjects
482
-
483
- ```typescript
484
- async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>
485
- ```
486
-
487
- Removes multiple objects in parallel via `Promise.all()`.
488
-
489
473
  #### listObjects
490
474
 
491
475
  ```typescript
@@ -497,110 +481,106 @@ async listObjects(opts: {
497
481
  }): Promise<IObjectInfo[]>
498
482
  ```
499
483
 
500
- Lists objects using `client.list()`. Returns objects with `name` (from `key`), `size`, `lastModified`, and `etag` (from `eTag`).
484
+ `client.list({ prefix, maxKeys }, { bucket })`; maps `contents` entries to `{ name: key, size, lastModified, etag: eTag }`.
501
485
 
502
486
  > [!NOTE]
503
- > The `useRecursive` parameter is accepted for interface compatibility but is not used -- Bun's S3 `list()` does not support recursive mode directly.
487
+ > `useRecursive` is accepted for interface compatibility but is not used - Bun's S3 `list()` has no recursive mode.
488
+
489
+ ### AWS Signature V4 (buildSignedRequest)
490
+
491
+ `Source ->` [`packages/helpers/src/modules/storage/bun-s3/utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/bun-s3/utility.ts)
492
+
493
+ ```typescript
494
+ async function buildSignedRequest(opts: {
495
+ method: string;
496
+ endpoint: string;
497
+ path: string;
498
+ accessKey: string;
499
+ secretKey: string;
500
+ region: string;
501
+ sessionToken?: string;
502
+ body?: string;
503
+ }): Promise<{ url: string; headers: Record<string, string> }>
504
+ ```
505
+
506
+ - **Internal only.** Not exported from the package barrel.
507
+ - **Builds the `Authorization` header from scratch.** Uses `crypto.subtle` (HMAC-SHA256 and SHA-256 digest), following the standard SigV4 derivation: `kDate -> kRegion -> kService -> kSigning`.
508
+ - **Signs four headers.** `host`, `x-amz-content-sha256`, `x-amz-date`, and (if present) `x-amz-security-token`.
509
+ - **Used exclusively for bucket management.** `getBuckets`, `createBucket`, and `removeBucket` on `BunS3Helper`.
504
510
 
505
511
  ## DiskHelper
506
512
 
507
- Local filesystem storage using directory-based buckets. Extends `BaseStorageHelper`.
513
+ `Source ->` [`packages/helpers/src/modules/storage/disk/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/disk/helper.ts)
514
+
515
+ Local filesystem storage using a bucket-based directory structure. Extends `BaseStorageHelper`.
508
516
 
509
517
  ### Constructor
510
518
 
511
519
  ```typescript
512
520
  constructor(options: IDiskHelperOptions)
513
- ```
514
521
 
515
- Resolves `basePath` to an absolute path and creates it if it does not exist.
516
-
517
- ```typescript
518
522
  interface IDiskHelperOptions extends IStorageHelperOptions {
519
523
  basePath: string;
520
524
  }
521
525
  ```
522
526
 
527
+ Resolves `basePath` to an absolute path with `path.resolve()` and creates it (`fs.mkdirSync(..., { recursive: true })`) if it does not exist.
528
+
523
529
  | Parameter | Type | Default | Description |
524
530
  |-----------|------|---------|-------------|
525
- | `options.basePath` | `string` | -- | Base directory for storage. Resolved to an absolute path. Created automatically. |
531
+ | `options.basePath` | `string` | - | Base directory for storage. Resolved to an absolute path. Created automatically. |
526
532
  | `options.scope` | `string` | `'DiskHelper'` | Logger scope name. |
527
533
  | `options.identifier` | `string` | `'DiskHelper'` | Helper identifier. |
528
534
 
529
- ### Methods
530
-
531
- #### isBucketExists
532
-
533
- ```typescript
534
- async isBucketExists(opts: { name: string }): Promise<boolean>
535
- ```
536
-
537
- Returns `false` if the name fails validation. Otherwise checks if the bucket path exists and is a directory.
538
-
539
- #### getBuckets
535
+ The resulting directory structure maps buckets to subdirectories:
540
536
 
541
- ```typescript
542
- async getBuckets(): Promise<IBucketInfo[]>
543
537
  ```
544
-
545
- Lists all directories under `basePath`. Returns each directory as a bucket with its `birthtime` as `creationDate`. Returns `[]` if the base path does not exist.
546
-
547
- #### getBucket
548
-
549
- ```typescript
550
- async getBucket(opts: { name: string }): Promise<IBucketInfo | null>
551
- ```
552
-
553
- Returns bucket info with `birthtime` as `creationDate`, or `null` if the bucket does not exist.
554
-
555
- #### createBucket
556
-
557
- ```typescript
558
- async createBucket(opts: { name: string }): Promise<IBucketInfo | null>
538
+ app_data/storage/ <-- basePath
539
+ ├── bucket-1/ <-- bucket (directory)
540
+ │ ├── file1.pdf <-- object (file)
541
+ │ └── file2.jpg
542
+ └── user-uploads/
543
+ ├── avatar.png
544
+ └── resume.pdf
559
545
  ```
560
546
 
561
- Creates a directory under `basePath`. Throws if the name fails validation or the bucket already exists.
562
-
563
- **Throws:**
564
- - `'[createBucket] Invalid name to create bucket!'`
565
- - `'[createBucket] Bucket already exists | name: {name}'`
566
-
567
- #### removeBucket
547
+ ### defaultLinkPrefix and writeObject
568
548
 
569
- ```typescript
570
- async removeBucket(opts: { name: string }): Promise<boolean>
571
- ```
549
+ - `defaultLinkPrefix`: `'/static-resources/'` (the one backend that differs from `/static-assets/`).
550
+ - `writeObject`: creates the object's parent directory if missing (`fsp.mkdir(dir, { recursive: true })`), then `fsp.writeFile(objectPath, file.buffer)`. No metadata dictionary is persisted alongside the file - `getStat()` derives `mimetype` from the filename at read time.
572
551
 
573
- Removes the bucket directory. Throws if the name fails validation, the bucket does not exist, or the bucket is not empty.
552
+ ### Methods
574
553
 
575
- **Throws:**
576
- - `'[removeBucket] Invalid name to remove bucket!'`
577
- - `'[removeBucket] Bucket does not exist | name: {name}'`
578
- - `'[removeBucket] Bucket is not empty | name: {name}'`
554
+ | Method | Behavior |
555
+ |---|---|
556
+ | `isBucketExists` | Returns `false` if the name fails validation. Otherwise checks the bucket path exists and `stat.isDirectory()`. |
557
+ | `getBuckets` | Lists directories under `basePath` via `fsp.readdir(..., { withFileTypes: true })`. Each directory's `birthtime` becomes `creationDate`. Returns `[]` if `basePath` does not exist. |
558
+ | `getBucket` | `isBucketExists()` first; if true, returns `{ name, creationDate: stat.birthtime }`; else `null`. |
559
+ | `createBucket` | `fsp.mkdir(bucketPath, { recursive: true })`, then returns `getBucket()`. |
560
+ | `removeBucket` | `fsp.rmdir(bucketPath)`. |
561
+ | `removeObject` | Checks the object exists first (`fsp.access`); throws if missing. Otherwise `fsp.unlink(objectPath)`. |
562
+ | `removeObjects` | Deletes **sequentially** by calling `removeObject()` per name in a `for` loop - if any file is missing, the error propagates immediately and remaining names are not attempted. |
579
563
 
580
- #### upload
564
+ **`createBucket` throws:**
581
565
 
582
- ```typescript
583
- async upload(opts: {
584
- bucket: string;
585
- files: IUploadFile[];
586
- normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
587
- normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
588
- maxFolderDepth?: number;
589
- }): Promise<IUploadResult[]>
590
- ```
566
+ | When | Message |
567
+ |---|---|
568
+ | Name fails `isValidName()` | `'[createBucket] Invalid name to create bucket!'` |
569
+ | Bucket directory already exists | `` `[createBucket] Bucket already exists | name: {name}` `` |
591
570
 
592
- Writes files to the bucket directory. Returns `[]` if `files` is empty. Validates the bucket exists and all file names/sizes before writing. Uploads run in parallel via `Promise.all()`. Subdirectories are created automatically if `normalizeName` contains path separators.
571
+ **`removeBucket` throws:**
593
572
 
594
- **Default name normalization:** Same as MinioHelper -- lowercased with spaces replaced by `_`.
573
+ | When | Message |
574
+ |---|---|
575
+ | Name fails `isValidName()` | `'[removeBucket] Invalid name to remove bucket!'` |
576
+ | Bucket directory does not exist | `` `[removeBucket] Bucket does not exist | name: {name}` `` |
577
+ | Bucket directory is not empty (`fsp.readdir` returns entries) | `` `[removeBucket] Bucket is not empty | name: {name}` `` |
595
578
 
596
- **Default link format:** `/static-resources/{bucket}/{normalizeName}`, with each `/`-separated segment of `normalizeName` URI-encoded via `encodeURIComponent()`.
579
+ **`removeObject` throws:**
597
580
 
598
- **Throws:**
599
- - `'[upload] Bucket does not exist | name: {bucket}'`
600
- - `'[upload] Invalid original file name'`
601
- - `'[upload] Invalid folder path'`
602
- - `'[upload] Invalid file size'`
603
- - `'[upload] Invalid normalized object name | name: {name}'` -- the value returned by a custom `normalizeNameFn` is path-validated before it is written to disk; an unvalidated `../../../etc/cron.d/pwn` would otherwise write outside `basePath`.
581
+ | When | Message |
582
+ |---|---|
583
+ | Object does not exist | `` `[removeObject] File not found | bucket: {bucket} | name: {name}` `` |
604
584
 
605
585
  #### getFile
606
586
 
@@ -608,9 +588,9 @@ Writes files to the bucket directory. Returns `[]` if `files` is empty. Validate
608
588
  async getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>
609
589
  ```
610
590
 
611
- Returns a `fs.createReadStream()` for the file. Throws if the file does not exist. The `options` parameter is accepted for interface compatibility but is not used.
591
+ `fs.createReadStream(objectPath)`. The `options` parameter is accepted for interface compatibility but not used.
612
592
 
613
- **Throws:** `'[getFile] File not found | bucket: {bucket} | name: {name}'`
593
+ **Throws:** `` `[getFile] File not found | bucket: {bucket} | name: {name}` `` if the file does not exist.
614
594
 
615
595
  #### getStat
616
596
 
@@ -618,39 +598,21 @@ Returns a `fs.createReadStream()` for the file. Throws if the file does not exis
618
598
  async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>
619
599
  ```
620
600
 
621
- Returns file metadata from the filesystem. The `metadata` field contains `mimetype` detected via `getMimeType()`. Does not return `etag` or `versionId`.
622
-
623
- **Throws:** `'[getStat] File not found | bucket: {bucket} | name: {name}'`
624
-
625
- **Returns:**
601
+ `fsp.stat(objectPath)`. Returns:
626
602
 
627
603
  ```typescript
628
604
  {
629
- size: number; // from fs stat
630
- lastModified: Date; // from fs stat mtime
605
+ size: number; // fs stat size
606
+ lastModified: Date; // fs stat mtime
631
607
  metadata: {
632
- mimetype: string; // detected via getMimeType()
608
+ mimetype: string; // detected via getMimeType() from the name's extension
633
609
  };
634
610
  }
635
611
  ```
636
612
 
637
- #### removeObject
638
-
639
- ```typescript
640
- async removeObject(opts: { bucket: string; name: string }): Promise<void>
641
- ```
642
-
643
- Deletes a file via `fsp.unlink()`. Throws if the file does not exist.
644
-
645
- **Throws:** `'[removeObject] File not found | bucket: {bucket} | name: {name}'`
613
+ Does not return `etag` or `versionId` - those fields are `undefined` on `DiskHelper`.
646
614
 
647
- #### removeObjects
648
-
649
- ```typescript
650
- async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>
651
- ```
652
-
653
- Deletes multiple files sequentially by calling `removeObject()` for each name. If any file does not exist, the error propagates immediately.
615
+ **Throws:** `` `[getStat] File not found | bucket: {bucket} | name: {name}` `` if the file does not exist.
654
616
 
655
617
  #### listObjects
656
618
 
@@ -663,20 +625,21 @@ async listObjects(opts: {
663
625
  }): Promise<IObjectInfo[]>
664
626
  ```
665
627
 
666
- Scans the bucket directory. Returns `[]` if the bucket path does not exist. Only files matching the `prefix` are included. Subdirectories are only traversed when `useRecursive` is `true`. Stops scanning when `maxKeys` is reached.
628
+ Scans the bucket directory recursively via a local `scanDirectory()` closure. Returns `[]` if the bucket path does not exist.
667
629
 
668
- | Parameter | Type | Default | Description |
669
- |-----------|------|---------|-------------|
670
- | `opts.bucket` | `string` | -- | Bucket to list. |
671
- | `opts.prefix` | `string` | `''` | Filter by name prefix. |
672
- | `opts.useRecursive` | `boolean` | `false` | Traverse subdirectories. |
673
- | `opts.maxKeys` | `number` | `undefined` | Maximum objects to return. |
630
+ | Parameter | Default | Description |
631
+ |---|---|---|
632
+ | `prefix` | `''` | Only files whose scanned name starts with `prefix` are included. |
633
+ | `useRecursive` | `false` | Subdirectories are only descended into when `true`; otherwise only top-level files are scanned. |
634
+ | `maxKeys` | `undefined` | Scanning stops once this many objects have been collected. |
674
635
 
675
- **Returns:** Array of `IObjectInfo` with `name`, `size`, `lastModified`. The `etag` field is always `undefined` for disk storage.
636
+ **Returns:** Array of `IObjectInfo` with `name`, `size`, `lastModified`. `etag` is always `undefined` for disk storage.
676
637
 
677
638
  ## MemoryStorageHelper
678
639
 
679
- Generic in-memory key-value store. Extends `BaseHelper` directly. Does **not** implement `IStorageHelper`.
640
+ `Source ->` [`packages/helpers/src/modules/storage/in-memory/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/in-memory/helper.ts)
641
+
642
+ Generic in-memory key-value store. Extends `BaseHelper` directly - does **not** implement `IStorageHelper` and has no bucket or file operations.
680
643
 
681
644
  ```typescript
682
645
  class MemoryStorageHelper<T extends object = AnyObject> extends BaseHelper
@@ -692,7 +655,7 @@ constructor(opts?: { scope?: string })
692
655
  |-----------|------|---------|-------------|
693
656
  | `opts.scope` | `string` | `'MemoryStorageHelper'` | Logger scope name. |
694
657
 
695
- ### Static Methods
658
+ ### Static methods
696
659
 
697
660
  #### newInstance
698
661
 
@@ -700,63 +663,35 @@ constructor(opts?: { scope?: string })
700
663
  static newInstance<T extends object = AnyObject>(): MemoryStorageHelper<T>
701
664
  ```
702
665
 
703
- Factory method that creates and returns a new `MemoryStorageHelper` instance.
666
+ Factory method - equivalent to `new MemoryStorageHelper<T>()`.
704
667
 
705
668
  ### Methods
706
669
 
707
- #### isBound
708
-
709
- ```typescript
710
- isBound(key: string): boolean
711
- ```
712
-
713
- Returns `true` if the key exists in the container (uses the `in` operator).
714
-
715
- #### get
716
-
717
- ```typescript
718
- get<R>(key: keyof T): R
719
- ```
720
-
721
- Returns the value for the given key, cast to type `R`.
722
-
723
- #### set
724
-
725
- ```typescript
726
- set<R>(key: string, value: R): void
727
- ```
728
-
729
- Stores a value under the given key using `Object.assign`.
730
-
731
- #### keys
670
+ | Method | Signature | Behavior |
671
+ |---|---|---|
672
+ | `isBound` | `(key: string): boolean` | `key in this.container`. |
673
+ | `get<R>` | `(key: keyof T): R` | Returns `this.container[key]` cast to `R`. |
674
+ | `set<R>` | `(key: string, value: R): void` | `Object.assign(this.container, { [key]: value })`. |
675
+ | `keys` | `(): string[]` | `Object.keys(this.container)`. |
676
+ | `clear` | `(): void` | Replaces the container with a new empty object. |
677
+ | `getContainer` | `(): T` | Returns the underlying container object directly (not a copy). |
732
678
 
733
679
  ```typescript
734
- keys(): string[]
735
- ```
736
-
737
- Returns all keys in the container via `Object.keys()`.
738
-
739
- #### clear
680
+ const cache = new MemoryStorageHelper();
740
681
 
741
- ```typescript
742
- clear(): void
682
+ cache.set('user:123', { name: 'Alice', role: 'admin' });
683
+ const user = cache.get<{ name: string; role: string }>('user:123');
684
+ cache.isBound('user:123'); // true
685
+ cache.keys(); // ['user:123']
686
+ cache.getContainer(); // { 'user:123': { name: 'Alice', role: 'admin' } }
687
+ cache.clear();
743
688
  ```
744
689
 
745
- Replaces the container with a new empty object.
746
-
747
- #### getContainer
748
-
749
- ```typescript
750
- getContainer(): T
751
- ```
752
-
753
- Returns the underlying container object.
754
-
755
690
  ## Types Reference
756
691
 
757
692
  ### IStorageHelper
758
693
 
759
- The unified interface implemented by `MinioHelper`, `BunS3Helper`, and `DiskHelper`:
694
+ The interface implemented by `MinioHelper`, `BunS3Helper`, and `DiskHelper`:
760
695
 
761
696
  ```typescript
762
697
  interface IStorageHelper {
@@ -801,12 +736,12 @@ interface IStorageHelperOptions {
801
736
  ```typescript
802
737
  interface IUploadFile {
803
738
  originalName: string; // Original filename
804
- mimetype: string; // MIME type (e.g., 'image/png')
805
- buffer: Buffer; // File content
806
- size: number; // File size in bytes
807
- encoding?: string; // Optional encoding (e.g., '7bit', 'base64')
808
- folderPath?: string; // Optional folder path for organization
809
- [key: string | symbol]: any; // Additional properties allowed
739
+ mimetype: string; // MIME type (e.g. 'image/png')
740
+ buffer: Buffer; // File content
741
+ size: number; // File size in bytes
742
+ encoding?: string; // Optional encoding (e.g. '7bit', 'base64')
743
+ folderPath?: string; // Optional folder path for organization
744
+ [key: string | symbol]: any; // Additional properties allowed
810
745
  }
811
746
  ```
812
747
 
@@ -814,11 +749,11 @@ interface IUploadFile {
814
749
 
815
750
  ```typescript
816
751
  interface IUploadResult {
817
- bucketName: string; // Bucket where file was stored
818
- objectName: string; // Stored filename (normalized)
819
- link: string; // Access URL
820
- metaLink?: any; // Optional metadata link
821
- metaLinkError?: any; // Error if metadata link creation failed
752
+ bucketName: string; // Bucket where the file was stored
753
+ objectName: string; // Stored object name (normalized)
754
+ link: string; // Access URL
755
+ metaLink?: any; // Optional metadata link
756
+ metaLinkError?: any; // Error if metadata link creation failed
822
757
  }
823
758
  ```
824
759
 
@@ -827,10 +762,10 @@ interface IUploadResult {
827
762
  ```typescript
828
763
  interface IFileStat {
829
764
  size: number; // File size in bytes
830
- metadata: Record<string, any>; // Storage-specific metadata
831
- lastModified?: Date; // Last modification date
832
- etag?: string; // Entity tag (MinioHelper and BunS3Helper only)
833
- versionId?: string; // Version ID (MinioHelper only)
765
+ metadata: Record<string, any>; // Backend-specific metadata
766
+ lastModified?: Date; // Last modification date
767
+ etag?: string; // Entity tag (MinioHelper and BunS3Helper only)
768
+ versionId?: string; // Version ID (MinioHelper only, if versioning enabled)
834
769
  }
835
770
  ```
836
771
 
@@ -838,8 +773,8 @@ interface IFileStat {
838
773
 
839
774
  ```typescript
840
775
  interface IBucketInfo {
841
- name: string; // Bucket name
842
- creationDate: Date; // When the bucket was created
776
+ name: string;
777
+ creationDate: Date;
843
778
  }
844
779
  ```
845
780
 
@@ -847,11 +782,11 @@ interface IBucketInfo {
847
782
 
848
783
  ```typescript
849
784
  interface IObjectInfo {
850
- name?: string; // Object name
851
- size?: number; // Object size in bytes
852
- lastModified?: Date; // Last modification date
853
- etag?: string; // Entity tag
854
- prefix?: string; // Prefix (for directory-like listing)
785
+ name?: string;
786
+ size?: number;
787
+ lastModified?: Date;
788
+ etag?: string;
789
+ prefix?: string;
855
790
  }
856
791
  ```
857
792
 
@@ -859,10 +794,10 @@ interface IObjectInfo {
859
794
 
860
795
  ```typescript
861
796
  interface IListObjectsOptions {
862
- bucket: string; // Bucket to list
863
- prefix?: string; // Filter by prefix
864
- useRecursive?: boolean; // Recursive listing (default: false)
865
- maxKeys?: number; // Maximum objects to return
797
+ bucket: string;
798
+ prefix?: string;
799
+ useRecursive?: boolean;
800
+ maxKeys?: number;
866
801
  }
867
802
  ```
868
803
 
@@ -870,7 +805,7 @@ interface IListObjectsOptions {
870
805
 
871
806
  ```typescript
872
807
  interface IDiskHelperOptions extends IStorageHelperOptions {
873
- basePath: string; // Base directory for storage
808
+ basePath: string;
874
809
  }
875
810
  ```
876
811
 
@@ -880,7 +815,7 @@ interface IDiskHelperOptions extends IStorageHelperOptions {
880
815
  interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}
881
816
  ```
882
817
 
883
- Inherits all `minio.ClientOptions` properties: `endPoint`, `port`, `useSSL`, `accessKey`, `secretKey`, `region`, `transport`, `sessionToken`, `partSize`, `pathStyle`, and others.
818
+ Inherits every `minio.ClientOptions` field: `endPoint`, `port`, `useSSL`, `accessKey`, `secretKey`, `region`, `transport`, `sessionToken`, `partSize`, `pathStyle`, and others.
884
819
 
885
820
  ### IBunS3HelperOptions
886
821
 
@@ -894,8 +829,175 @@ interface IBunS3HelperOptions extends IStorageHelperOptions {
894
829
  }
895
830
  ```
896
831
 
897
- ## See Also
832
+ ## Backend Behavior Matrix
833
+
834
+ | Behavior | MinioHelper | BunS3Helper | DiskHelper |
835
+ |---|---|---|---|
836
+ | Default link prefix | `/static-assets/` | `/static-assets/` | `/static-resources/` |
837
+ | `getStat().etag` | Yes | Yes | Never (`undefined`) |
838
+ | `getStat().versionId` | Yes, if versioning enabled | No | No |
839
+ | Upload metadata persisted | `originalName`, `normalizeName`, `size`, `encoding`, `mimeType` | Content type only | None (mimetype detected at read time) |
840
+ | `removeObjects` concurrency | Single batch SDK call | Parallel (`Promise.all`) | Sequential (`for` loop; stops at first missing file) |
841
+ | `listObjects.useRecursive` | Honored | Accepted but not used | Honored |
842
+ | `getFile` throws on missing file | No (SDK-level error) | No (SDK-level error) | Yes - explicit `'[getFile] File not found ...'` |
843
+ | Bucket-management transport | `minio.Client` methods | Hand-built AWS SigV4 signed requests | Node `fs`/`fs/promises` |
844
+
845
+ ## Troubleshooting
846
+
847
+ ### "[createBucket] Invalid name to create bucket!"
848
+
849
+ **Cause:** The bucket name failed `isValidName()` - it may contain path traversal characters, start with a dot, contain shell-special characters, or exceed 255 characters.
850
+
851
+ **Fix:**
852
+
853
+ ```typescript
854
+ // Wrong
855
+ await storage.createBucket({ name: '../my-bucket' });
856
+ await storage.createBucket({ name: '.hidden-bucket' });
857
+
858
+ // Correct
859
+ await storage.createBucket({ name: 'my-bucket' });
860
+ ```
861
+
862
+ ### "[removeBucket] Invalid name to remove bucket!"
863
+
864
+ **Cause:** Same as above - the bucket name failed `isValidName()`.
865
+
866
+ ### "[createBucket] Bucket already exists | name: {name}"
867
+
868
+ **Cause:** `DiskHelper` throws this exact message when `createBucket()` targets a directory that already exists. `MinioHelper` and `BunS3Helper` do not perform this check themselves - an existing bucket instead surfaces whatever the `minio` SDK or the raw S3 `PUT` request returns for that case, which depends on the server.
869
+
870
+ **Fix:** Check existence first.
871
+
872
+ ```typescript
873
+ const exists = await storage.isBucketExists({ name: 'my-bucket' });
874
+ if (!exists) {
875
+ await storage.createBucket({ name: 'my-bucket' });
876
+ }
877
+ ```
878
+
879
+ ### "[removeBucket] Bucket does not exist | name: {name}"
880
+
881
+ **Cause:** `DiskHelper` throws when removing a directory that does not exist.
882
+
883
+ **Fix:** Check existence before removal, same pattern as above with `isBucketExists`.
884
+
885
+ ### "[removeBucket] Bucket is not empty | name: {name}"
886
+
887
+ **Cause:** `DiskHelper`'s `removeBucket()` requires the bucket directory to be empty.
888
+
889
+ **Fix:** Remove all objects first.
890
+
891
+ ```typescript
892
+ const objects = await storage.listObjects({ bucket: 'my-bucket', useRecursive: true });
893
+ if (objects.length > 0) {
894
+ await storage.removeObjects({
895
+ bucket: 'my-bucket',
896
+ names: objects.map(o => o.name!),
897
+ });
898
+ }
899
+ await storage.removeBucket({ name: 'my-bucket' });
900
+ ```
901
+
902
+ ### "[upload] Bucket does not exist | name: {bucket}"
903
+
904
+ **Cause:** `upload()` calls `isBucketExists()` before writing anything, on every backend.
905
+
906
+ **Fix:** Create the bucket first.
907
+
908
+ ```typescript
909
+ const exists = await storage.isBucketExists({ name: 'uploads' });
910
+ if (!exists) {
911
+ await storage.createBucket({ name: 'uploads' });
912
+ }
913
+ await storage.upload({ bucket: 'uploads', files: [/* ... */] });
914
+ ```
915
+
916
+ ### "[upload] Invalid original file name"
917
+
918
+ **Cause:** A file's `originalName` failed `isValidName()`.
919
+
920
+ **Fix:** Sanitize before uploading, or override the name entirely with `normalizeNameFn`.
921
+
922
+ ```typescript
923
+ await storage.upload({
924
+ bucket: 'my-bucket',
925
+ files,
926
+ normalizeNameFn: ({ originalName }) => originalName.replace(/[^a-zA-Z0-9._-]/g, '_'),
927
+ });
928
+ ```
929
+
930
+ ### "[upload] Invalid folder path" / "[upload] Invalid folder path | depth: {depth} | max: {max}"
931
+
932
+ **Cause:** A file's `folderPath` either exceeds `maxFolderDepth` (the depth-specific message) or fails `isValidPath()` for another reason (traversal, invalid segment, the generic message).
933
+
934
+ **Fix:** Keep `folderPath` within `maxFolderDepth` (default `2`) segments, and free of `..`/invalid characters.
935
+
936
+ ### "[upload] Invalid file size | size: {size}"
937
+
938
+ **Cause:** A file's `size` is `undefined`, `null`, or negative. A zero-byte file (`size: 0`) is legal and does **not** trigger this.
939
+
940
+ **Fix:** Ensure every file carries a valid `size`.
941
+
942
+ ```typescript
943
+ const file: IUploadFile = {
944
+ originalName: 'doc.pdf',
945
+ mimetype: 'application/pdf',
946
+ buffer: fileBuffer,
947
+ size: fileBuffer.length, // must be a number >= 0
948
+ };
949
+ ```
950
+
951
+ ### "[upload] Invalid normalized object name | name: {name}"
952
+
953
+ **Cause:** A custom `normalizeNameFn` returned a value that fails `isValidPath()` - typically a traversal payload (`../../../etc/cron.d/pwn`) or a name exceeding `maxFolderDepth`. This check exists specifically because `originalName` passing validation does not guarantee the function's *output* is safe.
954
+
955
+ **Fix:** Ensure `normalizeNameFn` returns a plain relative name/path - no `..` segments, no leading `/`, no more folder segments than `maxFolderDepth` allows.
956
+
957
+ ### "[getFile] File not found | bucket: {bucket} | name: {name}"
958
+
959
+ **Cause:** `DiskHelper`-specific - it checks existence before opening a read stream. `MinioHelper` and `BunS3Helper` instead surface whatever error their SDK returns for a missing object.
960
+
961
+ **Fix:** Handle the rejection, or check first.
962
+
963
+ ```typescript
964
+ try {
965
+ const stream = await storage.getFile({ bucket: 'my-bucket', name: 'file.pdf' });
966
+ } catch (error) {
967
+ // File not found -- handle gracefully
968
+ }
969
+ ```
970
+
971
+ ### "[removeObject] File not found | bucket: {bucket} | name: {name}"
972
+
973
+ **Cause:** `DiskHelper`-specific - it checks existence before unlinking. `MinioHelper` and `BunS3Helper` instead surface whatever error their SDK returns for a missing object.
974
+
975
+ **Fix:** Handle the rejection, or check first.
976
+
977
+ ```typescript
978
+ try {
979
+ await storage.removeObject({ bucket: 'my-bucket', name: 'file.pdf' });
980
+ } catch (error) {
981
+ // File not found -- handle gracefully
982
+ }
983
+ ```
984
+
985
+ ### MinioHelper / BunS3Helper connection errors
986
+
987
+ **Cause:** Network or configuration mismatch between the application and the S3-compatible server.
988
+
989
+ **Checklist:**
990
+ - The server is running and reachable at the configured `endPoint`/`endpoint` and `port`.
991
+ - `useSSL` (MinioHelper) matches the server's TLS configuration.
992
+ - `accessKey`/`secretKey` are correct.
993
+ - For `BunS3Helper`, `region` matches what the server expects for SigV4 signing.
994
+ - Network and firewall rules allow the connection.
995
+
996
+ ## See also
898
997
 
899
- - [Setup & Usage](./) -- Getting started, examples, and troubleshooting
900
- - [Helpers Index](../index) -- All available helpers
901
- - [Static Asset Component](/extensions/components/static-asset/) -- Serving stored files via HTTP
998
+ - [Storage overview](/extensions/helpers/storage/) - introduction, the smallest example, and the most common tasks
999
+ - [Helpers Index](../index) - all available helpers
1000
+ - [Static Asset Component](/extensions/components/static-asset/) - serving stored files over HTTP
1001
+ - [Request Utilities](/references/utilities/request) - `parseMultipartBody` for file uploads
1002
+ - [MinIO Documentation](https://min.io/docs/minio/linux/index.html) - MinIO object storage
1003
+ - [MinIO JavaScript SDK](https://min.io/docs/minio/linux/developers/javascript/API.html) - full minio client API