@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.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +6 -2
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +182 -93
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +57 -218
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/package.json +8 -8
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
7
|
+
# Storage - Full Reference
|
|
4
8
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
| `.
|
|
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
|
-
|
|
126
|
+
Extracts the extension with `path.extname()`, lowercases it, and looks it up in `MIME_MAP`.
|
|
73
127
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 -
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
|
114
|
-
|
|
115
|
-
|
|
|
116
|
-
|
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
203
|
+
**Returns:** one of `'image'`, `'video'`, `'text'`, or `'unknown'`.
|
|
144
204
|
|
|
145
|
-
|
|
205
|
+
#### upload (template method - shared by every backend)
|
|
146
206
|
|
|
147
207
|
```typescript
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}
|
|
188
|
-
```
|
|
230
|
+
**`validateUploadFiles` (per file, in order):**
|
|
189
231
|
|
|
190
|
-
|
|
|
191
|
-
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
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
|
-
|
|
239
|
+
**Also throws:**
|
|
201
240
|
|
|
202
|
-
|
|
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
|
-
|
|
246
|
+
### Protected extension points (implemented per backend)
|
|
205
247
|
|
|
206
248
|
```typescript
|
|
207
|
-
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
Returns `false` if the name fails `isValidName()`. Otherwise delegates to `client.bucketExists()`.
|
|
249
|
+
protected abstract get defaultLinkPrefix(): string;
|
|
211
250
|
|
|
212
|
-
|
|
251
|
+
protected abstract writeObject(opts: {
|
|
252
|
+
bucket: string;
|
|
253
|
+
normalizeName: string;
|
|
254
|
+
file: IUploadFile;
|
|
255
|
+
}): Promise<void>;
|
|
213
256
|
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
+
### Public abstract methods (reimplemented per backend, no shared logic)
|
|
221
266
|
|
|
222
267
|
```typescript
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
**Throws:** `'[createBucket] Invalid name to create bucket!'`
|
|
286
|
+
See each backend's section below for behavior.
|
|
237
287
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
async removeBucket(opts: { name: string }): Promise<boolean>
|
|
242
|
-
```
|
|
288
|
+
## MinioHelper
|
|
243
289
|
|
|
244
|
-
|
|
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
|
-
|
|
292
|
+
S3-compatible object storage built on the `minio` package. Extends `BaseStorageHelper`.
|
|
247
293
|
|
|
248
|
-
|
|
294
|
+
### Constructor
|
|
249
295
|
|
|
250
296
|
```typescript
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
316
|
+
### defaultLinkPrefix and writeObject
|
|
269
317
|
|
|
270
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
#### removeObjects
|
|
363
|
+
#### getStat
|
|
324
364
|
|
|
325
365
|
```typescript
|
|
326
|
-
async
|
|
366
|
+
async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>
|
|
327
367
|
```
|
|
328
368
|
|
|
329
|
-
|
|
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
|
-
|
|
382
|
+
Streams via `client.listObjects(bucket, prefix, useRecursive)`; the stream is destroyed early once `maxKeys` is reached.
|
|
343
383
|
|
|
344
|
-
| Parameter |
|
|
345
|
-
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
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` |
|
|
378
|
-
| `options.secretKey` | `string` |
|
|
379
|
-
| `options.endpoint` | `string` |
|
|
380
|
-
| `options.region` | `string` | `'us-east-1'` |
|
|
381
|
-
| `options.sessionToken` | `string` |
|
|
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
|
-
###
|
|
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
|
-
|
|
430
|
-
|
|
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
|
-
|
|
433
|
+
### Methods
|
|
440
434
|
|
|
441
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
484
|
+
`client.list({ prefix, maxKeys }, { bucket })`; maps `contents` entries to `{ name: key, size, lastModified, etag: eTag }`.
|
|
501
485
|
|
|
502
486
|
> [!NOTE]
|
|
503
|
-
>
|
|
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
|
-
|
|
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` |
|
|
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
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
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
|
-
|
|
570
|
-
|
|
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
|
-
|
|
552
|
+
### Methods
|
|
574
553
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
564
|
+
**`createBucket` throws:**
|
|
581
565
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
571
|
+
**`removeBucket` throws:**
|
|
593
572
|
|
|
594
|
-
|
|
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
|
-
|
|
579
|
+
**`removeObject` throws:**
|
|
597
580
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
591
|
+
`fs.createReadStream(objectPath)`. The `options` parameter is accepted for interface compatibility but not used.
|
|
612
592
|
|
|
613
|
-
**Throws:** `
|
|
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
|
-
|
|
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; //
|
|
630
|
-
lastModified: Date; //
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
628
|
+
Scans the bucket directory recursively via a local `scanDirectory()` closure. Returns `[]` if the bucket path does not exist.
|
|
667
629
|
|
|
668
|
-
| Parameter |
|
|
669
|
-
|
|
670
|
-
| `
|
|
671
|
-
| `
|
|
672
|
-
| `
|
|
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`.
|
|
636
|
+
**Returns:** Array of `IObjectInfo` with `name`, `size`, `lastModified`. `etag` is always `undefined` for disk storage.
|
|
676
637
|
|
|
677
638
|
## MemoryStorageHelper
|
|
678
639
|
|
|
679
|
-
|
|
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
|
|
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
|
|
666
|
+
Factory method - equivalent to `new MemoryStorageHelper<T>()`.
|
|
704
667
|
|
|
705
668
|
### Methods
|
|
706
669
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
Returns all keys in the container via `Object.keys()`.
|
|
738
|
-
|
|
739
|
-
#### clear
|
|
680
|
+
const cache = new MemoryStorageHelper();
|
|
740
681
|
|
|
741
|
-
|
|
742
|
-
|
|
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
|
|
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;
|
|
805
|
-
buffer: Buffer;
|
|
806
|
-
size: number;
|
|
807
|
-
encoding?: string;
|
|
808
|
-
folderPath?: string;
|
|
809
|
-
[key: string | symbol]: any;
|
|
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
|
|
819
|
-
link: string;
|
|
820
|
-
metaLink?: any;
|
|
821
|
-
metaLinkError?: any;
|
|
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>; //
|
|
831
|
-
lastModified?: Date;
|
|
832
|
-
etag?: string;
|
|
833
|
-
versionId?: string;
|
|
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;
|
|
842
|
-
creationDate: Date;
|
|
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;
|
|
851
|
-
size?: number;
|
|
852
|
-
lastModified?: Date;
|
|
853
|
-
etag?: string;
|
|
854
|
-
prefix?: string;
|
|
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;
|
|
863
|
-
prefix?: string;
|
|
864
|
-
useRecursive?: boolean;
|
|
865
|
-
maxKeys?: number;
|
|
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;
|
|
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
|
|
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
|
-
##
|
|
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
|
-
- [
|
|
900
|
-
- [Helpers Index](../index)
|
|
901
|
-
- [Static Asset Component](/extensions/components/static-asset/)
|
|
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
|