@venizia/ignis-docs 0.2.0 → 0.2.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- 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 +27 -3
- 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 +8 -4
- 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 +247 -153
- 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 +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- 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/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- 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 +58 -218
- package/content/references/utilities/retry.md +139 -0
- 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/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -1,368 +1,253 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The component dynamically generates REST endpoints for each configured storage backend. All backends expose the same API structure under their configured `basePath`.
|
|
8
|
-
|
|
9
|
-
| Method | Path | Description |
|
|
10
|
-
|--------|------|-------------|
|
|
11
|
-
| `GET` | <code v-pre>/{basePath}/buckets</code> | List all buckets |
|
|
12
|
-
| `GET` | <code v-pre>/{basePath}/buckets/:bucketName</code> | Get bucket by name |
|
|
13
|
-
| `POST` | <code v-pre>/{basePath}/buckets/:bucketName</code> | Create a bucket |
|
|
14
|
-
| `DELETE` | <code v-pre>/{basePath}/buckets/:bucketName</code> | Delete a bucket |
|
|
15
|
-
| `POST` | <code v-pre>/{basePath}/buckets/:bucketName/upload</code> | Upload files |
|
|
16
|
-
| `GET` | <code v-pre>/{basePath}/buckets/:bucketName/objects</code> | List objects |
|
|
17
|
-
| `GET` | <code v-pre>/{basePath}/buckets/:bucketName/objects/:objectName</code> | Stream file |
|
|
18
|
-
| `GET` | <code v-pre>/{basePath}/buckets/:bucketName/download/:objectName</code> | Download file |
|
|
19
|
-
| `DELETE` | <code v-pre>/{basePath}/buckets/:bucketName/objects/:objectName</code> | Delete object |
|
|
20
|
-
| `PUT` | <code v-pre>/{basePath}/buckets/:bucketName/meta-links/:objectName</code> | Sync MetaLink (MetaLink only) |
|
|
21
|
-
|
|
22
|
-
#### GET <code v-pre>/{basePath}/buckets</code>
|
|
23
|
-
**Response `200`:**
|
|
24
|
-
```json
|
|
25
|
-
[
|
|
26
|
-
{ "name": "my-bucket", "creationDate": "2025-01-01T00:00:00.000Z" }
|
|
27
|
-
]
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
#### GET <code v-pre>/{basePath}/buckets/:bucketName</code>
|
|
31
|
-
**Parameters:**
|
|
32
|
-
- `bucketName` (path): Bucket name
|
|
1
|
+
---
|
|
2
|
+
title: Static Asset Component - Usage & Examples
|
|
3
|
+
description: Task-oriented walkthroughs for buckets, uploads, downloads, MetaLink tracking, and frontend integration
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
33
6
|
|
|
34
|
-
|
|
7
|
+
# Usage & Examples
|
|
35
8
|
|
|
36
|
-
|
|
37
|
-
```json
|
|
38
|
-
{ "name": "my-bucket", "creationDate": "2025-01-01T00:00:00.000Z" }
|
|
39
|
-
```
|
|
9
|
+
Task-oriented patterns for the endpoints `StaticAssetComponent` generates, plus the full MetaLink tracking setup. All examples assume a backend registered under `basePath: '/assets'` - see [Overview](./) for the binding.
|
|
40
10
|
|
|
41
|
-
|
|
11
|
+
## List and manage buckets
|
|
42
12
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
13
|
+
```
|
|
14
|
+
GET /assets/buckets List all buckets
|
|
15
|
+
GET /assets/buckets/:bucketName Get one bucket (nullable)
|
|
16
|
+
POST /assets/buckets/:bucketName Create a bucket
|
|
17
|
+
DELETE /assets/buckets/:bucketName Delete a bucket
|
|
18
|
+
```
|
|
46
19
|
|
|
47
|
-
|
|
20
|
+
```typescript
|
|
21
|
+
const buckets = await fetch('/assets/buckets').then(r => r.json());
|
|
22
|
+
// [{ name: 'user-uploads', creationDate: '2026-01-01T00:00:00.000Z' }]
|
|
48
23
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{ "name": "my-bucket", "creationDate": "2025-12-13T00:00:00.000Z" }
|
|
24
|
+
await fetch('/assets/buckets/user-uploads', { method: 'POST' });
|
|
25
|
+
const { isDeleted } = await fetch('/assets/buckets/user-uploads', { method: 'DELETE' }).then(r => r.json());
|
|
52
26
|
```
|
|
53
27
|
|
|
54
|
-
|
|
28
|
+
Every `bucketName` is validated with `isValidName()` - single segment, no `..`/`/`/`\`, no shell metacharacters, 255 characters or fewer. See [Error Reference](./errors) for the full rule set.
|
|
55
29
|
|
|
56
|
-
|
|
57
|
-
**Parameters:**
|
|
58
|
-
- `bucketName` (path): Bucket to delete
|
|
30
|
+
## Upload files
|
|
59
31
|
|
|
60
|
-
|
|
32
|
+
`POST /assets/buckets/:bucketName/upload` accepts `multipart/form-data`, plus optional `principalType`, `principalId`, `variant`, and `folderPath` query parameters.
|
|
61
33
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```
|
|
34
|
+
```typescript
|
|
35
|
+
const formData = new FormData();
|
|
36
|
+
formData.append('file', fileBlob, 'document.pdf');
|
|
66
37
|
|
|
67
|
-
|
|
38
|
+
const response = await fetch(
|
|
39
|
+
'/assets/buckets/user-uploads/upload?principalType=user&principalId=42&variant=original&folderPath=invoices/2026',
|
|
40
|
+
{ method: 'POST', body: formData },
|
|
41
|
+
);
|
|
68
42
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
43
|
+
const [result] = await response.json();
|
|
44
|
+
// { bucketName: 'user-uploads', objectName: 'invoices/2026/document.pdf', link: '/assets/buckets/user-uploads/objects/invoices%2F2026%2Fdocument.pdf' }
|
|
45
|
+
```
|
|
72
46
|
|
|
73
|
-
|
|
74
|
-
- `
|
|
75
|
-
-
|
|
76
|
-
- `variant` (optional, string): Variant tag for the upload (e.g., `"thumbnail"`, `"original"`)
|
|
77
|
-
- `folderPath` (optional, string): Target folder path for uploaded files (e.g., `"photos/2024"`). Each segment is validated with `isValidName()` and the segment count must not exceed the configured `maxFolderDepth` (default 2); returns 400 on violation
|
|
47
|
+
- **`folderPath` is validated separately from the filename.** Each segment must pass `isValidName()`. The segment count must stay within `maxFolderDepth` (default `2`). Both checks return `400` before the file is even parsed.
|
|
48
|
+
- **`principalId` is always stored as a string**, coerced with `String()` regardless of whether you send a number or a string.
|
|
49
|
+
- **With MetaLink enabled, the upload always succeeds - even if the tracking write fails.** The response carries one of two shapes:
|
|
78
50
|
|
|
79
|
-
|
|
51
|
+
| Outcome | Response field |
|
|
52
|
+
|---------|-----------------|
|
|
53
|
+
| MetaLink write succeeded | `metaLink`: the created database record |
|
|
54
|
+
| MetaLink write failed | `metaLink: null` plus a `metaLinkError` string |
|
|
80
55
|
|
|
81
|
-
|
|
56
|
+
## Stream or download an object
|
|
82
57
|
|
|
83
58
|
```typescript
|
|
84
|
-
const
|
|
85
|
-
files: z.union([z.instanceof(File), z.array(z.instanceof(File))]),
|
|
86
|
-
});
|
|
87
|
-
```
|
|
59
|
+
const objectName = 'invoices/2026/document.pdf';
|
|
88
60
|
|
|
89
|
-
|
|
61
|
+
// Inline stream - Content-Type comes from storage metadata, falls back to application/octet-stream
|
|
62
|
+
const streamUrl = `/assets/buckets/user-uploads/objects/${encodeURIComponent(objectName)}`;
|
|
90
63
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
"bucketName": "my-bucket",
|
|
96
|
-
"objectName": "file.pdf",
|
|
97
|
-
"link": "/assets/buckets/my-bucket/objects/file.pdf"
|
|
98
|
-
}
|
|
99
|
-
]
|
|
64
|
+
// Forces a browser download dialog via Content-Disposition: attachment
|
|
65
|
+
const downloadUrl = `/assets/buckets/user-uploads/download/${encodeURIComponent(objectName)}`;
|
|
66
|
+
window.open(downloadUrl, '_blank');
|
|
100
67
|
```
|
|
101
68
|
|
|
102
|
-
|
|
103
|
-
```json
|
|
104
|
-
[
|
|
105
|
-
{
|
|
106
|
-
"bucketName": "my-bucket",
|
|
107
|
-
"objectName": "file.pdf",
|
|
108
|
-
"link": "/assets/buckets/my-bucket/objects/file.pdf",
|
|
109
|
-
"metaLink": {
|
|
110
|
-
"id": "uuid",
|
|
111
|
-
"bucketName": "my-bucket",
|
|
112
|
-
"objectName": "file.pdf",
|
|
113
|
-
"link": "/assets/buckets/my-bucket/objects/file.pdf",
|
|
114
|
-
"mimetype": "application/pdf",
|
|
115
|
-
"size": 1024,
|
|
116
|
-
"etag": "abc123",
|
|
117
|
-
"metadata": {},
|
|
118
|
-
"storageType": "minio",
|
|
119
|
-
"isSynced": true,
|
|
120
|
-
"variant": "original",
|
|
121
|
-
"principalType": "user",
|
|
122
|
-
"principalId": "42",
|
|
123
|
-
"createdAt": "2025-12-15T03:00:00.000Z",
|
|
124
|
-
"modifiedAt": "2025-12-15T03:00:00.000Z"
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
]
|
|
128
|
-
```
|
|
69
|
+
Both routes validate `bucketName` with `isValidName()` and `objectName` with `isValidPath()`. Both then forward a fixed whitelist of metadata headers - `content-type`, `content-encoding`, `cache-control`, `etag`, `last-modified` - plus `X-Content-Type-Options: nosniff`. See [Header Sanitization](./api#header-sanitization) for the full list and why it exists.
|
|
129
70
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
[
|
|
133
|
-
{
|
|
134
|
-
"bucketName": "my-bucket",
|
|
135
|
-
"objectName": "file.pdf",
|
|
136
|
-
"link": "/assets/buckets/my-bucket/objects/file.pdf",
|
|
137
|
-
"metaLink": null,
|
|
138
|
-
"metaLinkError": "Database connection failed"
|
|
139
|
-
}
|
|
140
|
-
]
|
|
141
|
-
```
|
|
71
|
+
> [!TIP]
|
|
72
|
+
> `objectName` may embed folder segments, for example `invoices/2026/document.pdf`. Always pass the whole thing through `encodeURIComponent()`. Hono decodes it exactly once before the handler reads it - a second `decodeURIComponent()` on your end is wrong. It can corrupt names that contain a literal `%`.
|
|
142
73
|
|
|
143
|
-
|
|
74
|
+
## List objects in a bucket
|
|
144
75
|
|
|
145
|
-
**Example:**
|
|
146
76
|
```typescript
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const response = await fetch(
|
|
152
|
-
'/assets/buckets/uploads/upload?principalType=user&principalId=123&variant=original',
|
|
153
|
-
{ method: 'POST', body: formData },
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
const result = await response.json();
|
|
157
|
-
console.log(result[0].metaLink); // Database record (if MetaLink enabled)
|
|
158
|
-
```
|
|
77
|
+
const url = new URL('/assets/buckets/user-uploads/objects', location.origin);
|
|
78
|
+
url.searchParams.set('prefix', 'invoices/2026/');
|
|
79
|
+
url.searchParams.set('recursive', 'true'); // only the literal string "true" enables recursion
|
|
80
|
+
url.searchParams.set('maxKeys', '50');
|
|
159
81
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
- `bucketName` (path): Bucket name
|
|
163
|
-
|
|
164
|
-
**Validation:** Bucket name validated with `isValidName()`. Returns 400 `"Invalid bucket name"` if invalid.
|
|
165
|
-
|
|
166
|
-
**Query Parameters:**
|
|
167
|
-
- `prefix` (optional, string): Filter objects by prefix (e.g., `"folder/"`)
|
|
168
|
-
- `recursive` (optional, string): Recursive listing. Parsed via strict string comparison `=== 'true'` -- only the exact string `"true"` enables recursion; any other truthy value (e.g., `"1"`, `"yes"`) does not
|
|
169
|
-
- `maxKeys` (optional, string): Maximum number of objects to return. Parsed as integer via `parseInt(value, 10)`
|
|
170
|
-
|
|
171
|
-
**Response `200`:**
|
|
172
|
-
```json
|
|
173
|
-
[
|
|
174
|
-
{
|
|
175
|
-
"name": "file1.pdf",
|
|
176
|
-
"size": 1024,
|
|
177
|
-
"lastModified": "2025-12-13T00:00:00.000Z",
|
|
178
|
-
"etag": "abc123",
|
|
179
|
-
"prefix": "folder/"
|
|
180
|
-
}
|
|
181
|
-
]
|
|
82
|
+
const objects = await fetch(url).then(r => r.json());
|
|
83
|
+
// [{ name: 'invoices/2026/document.pdf', size: 1024, lastModified: '...', etag: '...' }]
|
|
182
84
|
```
|
|
183
85
|
|
|
184
|
-
|
|
86
|
+
`maxKeys`, if provided, must parse to a positive integer (`Number(maxKeys)` checked with `Number.isInteger`) or the endpoint returns `400`.
|
|
185
87
|
|
|
186
|
-
|
|
187
|
-
**Parameters:**
|
|
188
|
-
- `bucketName` (path): Bucket name
|
|
189
|
-
- `objectName` (path): Object name (URL-encoded)
|
|
88
|
+
## Delete an object
|
|
190
89
|
|
|
191
|
-
|
|
90
|
+
```typescript
|
|
91
|
+
const objectName = 'invoices/2026/document.pdf';
|
|
192
92
|
|
|
193
|
-
|
|
194
|
-
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- Additional whitelisted headers forwarded from storage metadata (see [Header Sanitization](./api#header-sanitization))
|
|
93
|
+
const { success } = await fetch(
|
|
94
|
+
`/assets/buckets/user-uploads/objects/${encodeURIComponent(objectName)}`,
|
|
95
|
+
{ method: 'DELETE' },
|
|
96
|
+
).then(r => r.json());
|
|
97
|
+
```
|
|
199
98
|
|
|
200
|
-
|
|
201
|
-
**
|
|
202
|
-
- `bucketName` (path): Bucket name
|
|
203
|
-
- `objectName` (path): Object name (URL-encoded)
|
|
99
|
+
> [!NOTE]
|
|
100
|
+
> When MetaLink is enabled, the database record deletion is **fire-and-forget**. The response returns as soon as the storage delete completes - it does not wait on the `deleteAll()` call. Deletion errors are logged but never fail the request.
|
|
204
101
|
|
|
205
|
-
|
|
102
|
+
## Sync a MetaLink record manually
|
|
206
103
|
|
|
207
|
-
|
|
208
|
-
- Streams file with download headers
|
|
209
|
-
- `Content-Disposition`: `attachment; filename="..."` (generated via `createContentDispositionHeader()`)
|
|
210
|
-
- `Content-Type`: From storage metadata or `application/octet-stream` as fallback
|
|
211
|
-
- `Content-Length`: File size in bytes
|
|
212
|
-
- `X-Content-Type-Options`: `nosniff`
|
|
213
|
-
- Additional whitelisted headers forwarded from storage metadata (see [Header Sanitization](./api#header-sanitization))
|
|
214
|
-
- Triggers browser download dialog
|
|
104
|
+
`PUT /assets/buckets/:bucketName/meta-links/:objectName` is only registered when `useMetaLink: true`. It re-reads the file's current storage metadata via `helper.getStat()` and creates or updates the matching MetaLink row.
|
|
215
105
|
|
|
216
|
-
**Example:**
|
|
217
106
|
```typescript
|
|
218
|
-
const
|
|
219
|
-
|
|
107
|
+
const objectName = 'invoices/2026/document.pdf';
|
|
108
|
+
|
|
109
|
+
const response = await fetch(
|
|
110
|
+
`/assets/buckets/user-uploads/meta-links/${encodeURIComponent(objectName)}`,
|
|
111
|
+
{ method: 'PUT' },
|
|
112
|
+
);
|
|
113
|
+
const { success, metaLink } = await response.json();
|
|
220
114
|
```
|
|
221
115
|
|
|
222
|
-
|
|
223
|
-
**Parameters:**
|
|
224
|
-
- `bucketName` (path): Bucket name
|
|
225
|
-
- `objectName` (path): Object to delete (URL-encoded)
|
|
116
|
+
Useful for backfilling MetaLink rows for files that already exist in storage, or after a database restore.
|
|
226
117
|
|
|
227
|
-
|
|
118
|
+
## Enable MetaLink tracking
|
|
228
119
|
|
|
229
|
-
|
|
230
|
-
- Deletes file from storage
|
|
231
|
-
- If MetaLink enabled, the MetaLink database record deletion is **fire-and-forget** -- the HTTP response returns immediately after the storage delete completes, without awaiting the database deletion
|
|
232
|
-
- MetaLink deletion errors are logged but do not fail the request
|
|
233
|
-
- MetaLink deletion uses `deleteAll({ where: { bucketName, objectName } })` to remove all matching records
|
|
120
|
+
MetaLink persists an upload's bucket, object name, link, mimetype, size, etag, storage type, principal, and variant to Postgres. `BaseMetaLinkModel` and `BaseMetaLinkRepository` cover the schema - you only write a repository subclass and the table.
|
|
234
121
|
|
|
235
|
-
**
|
|
236
|
-
```json
|
|
237
|
-
{ "success": true }
|
|
238
|
-
```
|
|
122
|
+
**1. Repository.** `BaseMetaLinkModel` is used as-is - no model subclass needed:
|
|
239
123
|
|
|
240
|
-
**Example:**
|
|
241
124
|
```typescript
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
125
|
+
import { repository } from '@venizia/ignis';
|
|
126
|
+
import { BaseMetaLinkModel, BaseMetaLinkRepository } from '@venizia/ignis/static-asset';
|
|
127
|
+
import { PostgresDataSource } from '@/datasources';
|
|
128
|
+
|
|
129
|
+
@repository({ model: BaseMetaLinkModel, dataSource: PostgresDataSource })
|
|
130
|
+
export class MetaLinkRepository extends BaseMetaLinkRepository {}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**2. Table.** `BaseMetaLinkModel` sets `skipMigrate: true`, so create it manually:
|
|
134
|
+
|
|
135
|
+
```sql
|
|
136
|
+
CREATE TABLE "MetaLink" (
|
|
137
|
+
id TEXT PRIMARY KEY,
|
|
138
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
139
|
+
modified_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
140
|
+
bucket_name TEXT NOT NULL,
|
|
141
|
+
object_name TEXT NOT NULL,
|
|
142
|
+
link TEXT NOT NULL,
|
|
143
|
+
mimetype TEXT NOT NULL,
|
|
144
|
+
size INTEGER NOT NULL,
|
|
145
|
+
etag TEXT,
|
|
146
|
+
metadata JSONB,
|
|
147
|
+
storage_type TEXT NOT NULL,
|
|
148
|
+
is_synced BOOLEAN NOT NULL DEFAULT false,
|
|
149
|
+
variant TEXT,
|
|
150
|
+
principal_type TEXT,
|
|
151
|
+
principal_id TEXT
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
CREATE INDEX "IDX_MetaLink_bucketName" ON "MetaLink"(bucket_name);
|
|
155
|
+
CREATE INDEX "IDX_MetaLink_objectName" ON "MetaLink"(object_name);
|
|
156
|
+
CREATE INDEX "IDX_MetaLink_storageType" ON "MetaLink"(storage_type);
|
|
157
|
+
CREATE INDEX "IDX_MetaLink_isSynced" ON "MetaLink"(is_synced);
|
|
250
158
|
```
|
|
251
159
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
```json
|
|
276
|
-
{
|
|
277
|
-
"success": true,
|
|
278
|
-
"metaLink": {
|
|
279
|
-
"id": "uuid",
|
|
280
|
-
"bucketName": "user-uploads",
|
|
281
|
-
"objectName": "document.pdf",
|
|
282
|
-
"link": "/assets/buckets/user-uploads/objects/document.pdf",
|
|
283
|
-
"mimetype": "application/pdf",
|
|
284
|
-
"size": 1048576,
|
|
285
|
-
"etag": "abc123",
|
|
286
|
-
"metadata": {},
|
|
287
|
-
"storageType": "minio",
|
|
288
|
-
"isSynced": true,
|
|
289
|
-
"principalType": null,
|
|
290
|
-
"principalId": null,
|
|
291
|
-
"createdAt": "2025-12-15T03:00:00.000Z",
|
|
292
|
-
"modifiedAt": "2025-12-15T03:00:00.000Z"
|
|
160
|
+
**3. Register the repository and wire it into the component options:**
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
export class Application extends BaseApplication {
|
|
164
|
+
preConfigure() {
|
|
165
|
+
this.repository(MetaLinkRepository);
|
|
166
|
+
|
|
167
|
+
this.bind<TStaticAssetsComponentOptions>({
|
|
168
|
+
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
|
|
169
|
+
}).toValue({
|
|
170
|
+
uploads: {
|
|
171
|
+
controller: { name: 'UploadsController', basePath: '/uploads' },
|
|
172
|
+
storage: StaticAssetStorageTypes.MINIO,
|
|
173
|
+
helper: new MinioHelper({ /* ... */ }),
|
|
174
|
+
useMetaLink: true,
|
|
175
|
+
metaLink: {
|
|
176
|
+
model: BaseMetaLinkModel,
|
|
177
|
+
repository: this.get<MetaLinkRepository>({ key: 'repositories.MetaLinkRepository' }),
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
this.component(StaticAssetComponent);
|
|
293
183
|
}
|
|
294
184
|
}
|
|
295
185
|
```
|
|
296
186
|
|
|
297
|
-
|
|
187
|
+
> [!TIP]
|
|
188
|
+
> Call `this.repository(MetaLinkRepository)` before `this.get({ key: 'repositories.MetaLinkRepository' })` - the binding has to exist in the container first.
|
|
298
189
|
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
// Sync a single file
|
|
302
|
-
const bucketName = 'user-uploads';
|
|
303
|
-
const objectName = 'document.pdf';
|
|
190
|
+
### Custom MetaLink creation
|
|
304
191
|
|
|
305
|
-
|
|
306
|
-
`/assets/buckets/${bucketName}/meta-links/${encodeURIComponent(objectName)}`,
|
|
307
|
-
{ method: 'PUT' }
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
const result = await response.json();
|
|
311
|
-
console.log('Success:', result.success); // true
|
|
312
|
-
console.log('Synced:', result.metaLink.isSynced); // true
|
|
192
|
+
Provide `createMetaLink` on `TMetaLinkConfig` to fully replace the default insert - for example to add extra fields or run validation before persisting.
|
|
313
193
|
|
|
314
|
-
|
|
315
|
-
|
|
194
|
+
```typescript
|
|
195
|
+
metaLink: {
|
|
196
|
+
model: BaseMetaLinkModel,
|
|
197
|
+
repository: metaLinkRepository,
|
|
198
|
+
createMetaLink: async ({ uploadResult, fileStat, query }) =>
|
|
199
|
+
metaLinkRepository.create({
|
|
200
|
+
data: {
|
|
201
|
+
bucketName: uploadResult.bucketName,
|
|
202
|
+
objectName: uploadResult.objectName,
|
|
203
|
+
link: uploadResult.link,
|
|
204
|
+
mimetype: fileStat.metadata?.['mimetype'],
|
|
205
|
+
size: fileStat.size,
|
|
206
|
+
etag: fileStat.etag,
|
|
207
|
+
storageType: 'minio',
|
|
208
|
+
isSynced: true,
|
|
209
|
+
principalId: query.principalId ? String(query.principalId) : undefined,
|
|
210
|
+
principalType: query.principalType,
|
|
211
|
+
variant: query.variant,
|
|
212
|
+
},
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
When `createMetaLink` is omitted, the component uses a default insert that covers every standard field.
|
|
218
|
+
|
|
219
|
+
### Query MetaLink records
|
|
316
220
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
322
|
-
}
|
|
221
|
+
```typescript
|
|
222
|
+
const userFiles = await metaLinkRepository.find({ filter: { where: { principalType: 'user', principalId: '42' } } });
|
|
223
|
+
const thumbnails = await metaLinkRepository.find({ filter: { where: { variant: 'thumbnail' } } });
|
|
224
|
+
const pdfs = await metaLinkRepository.find({ filter: { where: { mimetype: 'application/pdf' } } });
|
|
323
225
|
```
|
|
324
226
|
|
|
325
|
-
## Frontend
|
|
227
|
+
## Frontend integration
|
|
326
228
|
|
|
327
229
|
```typescript
|
|
328
|
-
|
|
329
|
-
|
|
230
|
+
async function uploadFile(
|
|
231
|
+
file: File,
|
|
232
|
+
opts: { principalType?: string; principalId?: string; variant?: string } = {},
|
|
233
|
+
) {
|
|
330
234
|
const formData = new FormData();
|
|
331
235
|
formData.append('file', file);
|
|
332
236
|
|
|
333
|
-
const url = new URL('/assets/buckets/user-uploads/upload',
|
|
334
|
-
|
|
335
|
-
if (principalId) url.searchParams.append('principalId', principalId);
|
|
336
|
-
if (variant) url.searchParams.append('variant', variant);
|
|
337
|
-
|
|
338
|
-
const response = await fetch(url, {
|
|
339
|
-
method: 'POST',
|
|
340
|
-
body: formData,
|
|
341
|
-
});
|
|
237
|
+
const url = new URL('/assets/buckets/user-uploads/upload', location.origin);
|
|
238
|
+
Object.entries(opts).forEach(([key, value]) => value && url.searchParams.set(key, value));
|
|
342
239
|
|
|
343
|
-
const result = await
|
|
344
|
-
return result
|
|
240
|
+
const [result] = await fetch(url, { method: 'POST', body: formData }).then(r => r.json());
|
|
241
|
+
return result.link;
|
|
345
242
|
}
|
|
346
243
|
|
|
347
|
-
// Download file
|
|
348
244
|
function downloadFile(bucketName: string, objectName: string) {
|
|
349
|
-
|
|
350
|
-
window.open(url, '_blank');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// List files in bucket
|
|
354
|
-
async function listFiles(bucketName: string, prefix?: string, recursive?: boolean) {
|
|
355
|
-
const url = new URL(`/assets/buckets/${bucketName}/objects`, window.location.origin);
|
|
356
|
-
if (prefix) url.searchParams.append('prefix', prefix);
|
|
357
|
-
if (recursive) url.searchParams.append('recursive', 'true');
|
|
358
|
-
|
|
359
|
-
const response = await fetch(url);
|
|
360
|
-
return await response.json();
|
|
245
|
+
window.open(`/assets/buckets/${bucketName}/download/${encodeURIComponent(objectName)}`, '_blank');
|
|
361
246
|
}
|
|
362
247
|
```
|
|
363
248
|
|
|
364
|
-
## See
|
|
249
|
+
## See also
|
|
365
250
|
|
|
366
|
-
- [
|
|
367
|
-
- [
|
|
368
|
-
- [Error Reference](./errors) -
|
|
251
|
+
- [Overview](./) - quick start, imports, and common configuration tasks
|
|
252
|
+
- [Full Reference](./api) - request/response schemas, `IStorageHelper` interface, header sanitization, internals
|
|
253
|
+
- [Error Reference](./errors) - name validation rules and troubleshooting
|
|
@@ -69,9 +69,9 @@ When documenting a component, find source material here:
|
|
|
69
69
|
|
|
70
70
|
| What | Path |
|
|
71
71
|
|------|------|
|
|
72
|
-
| Binding keys | `packages/core/src/components/{name}/common/keys.ts` |
|
|
73
|
-
| Config types | `packages/core/src/components/{name}/common/types.ts` |
|
|
74
|
-
| Error messages | `packages/core/src/components/{name}/component.ts` -- look for `throw getError()` |
|
|
72
|
+
| Binding keys | `packages/core-server/src/components/{name}/common/keys.ts` |
|
|
73
|
+
| Config types | `packages/core-server/src/components/{name}/common/types.ts` |
|
|
74
|
+
| Error messages | `packages/core-server/src/components/{name}/component.ts` -- look for `throw getError()` |
|
|
75
75
|
| Helper source | `packages/helpers/src/helpers/{name}/` |
|
|
76
76
|
|
|
77
77
|
## Callout Standard
|