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