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