@venizia/ignis-docs 0.2.0 → 0.2.1-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -1,368 +1,253 @@
1
- # Static Asset -- Usage & Examples
2
-
3
- > API endpoint specifications, request/response details, and frontend integration examples.
4
-
5
- ## API Endpoints
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
- **Validation:** Bucket name validated with `isValidName()`. Returns 400 `"Invalid bucket name"` if invalid.
7
+ # Usage & Examples
35
8
 
36
- **Response `200`:**
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
- Returns `null` when the bucket does not exist. The response schema is nullable.
11
+ ## List and manage buckets
42
12
 
43
- #### POST <code v-pre>/{basePath}/buckets/:bucketName</code>
44
- **Parameters:**
45
- - `bucketName` (path): Name of the new bucket
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
- **Validation:** Bucket name validated with `isValidName()`. Returns 400 `"Invalid bucket name"` if invalid.
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
- **Response `200`:**
50
- ```json
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
- Returns `null` if bucket creation fails (e.g., already exists). The response schema is nullable.
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
- #### DELETE <code v-pre>/{basePath}/buckets/:bucketName</code>
57
- **Parameters:**
58
- - `bucketName` (path): Bucket to delete
30
+ ## Upload files
59
31
 
60
- **Validation:** Bucket name validated with `isValidName()`. Returns 400 `"Invalid bucket name"` if invalid.
32
+ `POST /assets/buckets/:bucketName/upload` accepts `multipart/form-data`, plus optional `principalType`, `principalId`, `variant`, and `folderPath` query parameters.
61
33
 
62
- **Response `200`:**
63
- ```json
64
- { "isDeleted": true }
65
- ```
34
+ ```typescript
35
+ const formData = new FormData();
36
+ formData.append('file', fileBlob, 'document.pdf');
66
37
 
67
- The `isDeleted` field is a boolean indicating whether the bucket was successfully removed from storage.
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
- #### POST <code v-pre>/{basePath}/buckets/:bucketName/upload</code>
70
- **Parameters:**
71
- - `bucketName` (path): Target bucket name
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
- **Query Parameters:**
74
- - `principalType` (optional, string): Type of the principal to associate with the uploaded files (e.g., `"user"`, `"service"`)
75
- - `principalId` (optional, string or number): ID of the principal. Always coerced to a string via `String()` before storage regardless of input type
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
- **Validation:** Bucket name validated with `isValidName()`. Returns 400 `"Invalid bucket name"` if invalid.
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
- **Request Body:** `multipart/form-data` with file fields. The request body is parsed using the `MultipartBodySchema` Zod schema:
56
+ ## Stream or download an object
82
57
 
83
58
  ```typescript
84
- const MultipartBodySchema = z.object({
85
- files: z.union([z.instanceof(File), z.array(z.instanceof(File))]),
86
- });
87
- ```
59
+ const objectName = 'invoices/2026/document.pdf';
88
60
 
89
- This accepts either a single `File` or an array of `File` objects.
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
- **Response `200` (without MetaLink):**
92
- ```json
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
- **Response `200` (with MetaLink enabled):**
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
- **Response `200` (with MetaLink enabled, MetaLink creation failed):**
131
- ```json
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
- When MetaLink creation fails, the upload itself still succeeds. The response includes `metaLink: null` and a `metaLinkError` string describing the failure. The error is also logged via the controller's scoped logger.
74
+ ## List objects in a bucket
144
75
 
145
- **Example:**
146
76
  ```typescript
147
- const formData = new FormData();
148
- formData.append('file', fileBlob, 'document.pdf');
149
-
150
- // Upload with principal association and variant
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
- #### GET <code v-pre>/{basePath}/buckets/:bucketName/objects</code>
161
- **Parameters:**
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
- All fields in the `IObjectInfo` response are optional. The `prefix` field is present when listing non-recursively and the object is a directory prefix. When listing individual files, `name`, `size`, `lastModified`, and `etag` are typically populated.
86
+ `maxKeys`, if provided, must parse to a positive integer (`Number(maxKeys)` checked with `Number.isInteger`) or the endpoint returns `400`.
185
87
 
186
- #### GET <code v-pre>/{basePath}/buckets/:bucketName/objects/:objectName</code>
187
- **Parameters:**
188
- - `bucketName` (path): Bucket name
189
- - `objectName` (path): Object name (URL-encoded)
88
+ ## Delete an object
190
89
 
191
- **Validation:** Bucket name validated with `isValidName()`; object name validated with `isValidPath()`. Returns 400 `"Invalid bucket name"` or `"Invalid object name or path"` respectively if either is invalid.
90
+ ```typescript
91
+ const objectName = 'invoices/2026/document.pdf';
192
92
 
193
- **Response:**
194
- - Streams file content with appropriate headers
195
- - `Content-Type`: From storage metadata or `application/octet-stream` as fallback
196
- - `Content-Length`: File size in bytes
197
- - `X-Content-Type-Options`: `nosniff`
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
- #### GET <code v-pre>/{basePath}/buckets/:bucketName/download/:objectName</code>
201
- **Parameters:**
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
- **Validation:** Bucket name validated with `isValidName()`; object name validated with `isValidPath()`. Returns 400 `"Invalid bucket name"` or `"Invalid object name or path"` respectively if either is invalid.
102
+ ## Sync a MetaLink record manually
206
103
 
207
- **Response:**
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 downloadUrl = `/assets/buckets/uploads/download/${encodeURIComponent('document.pdf')}`;
219
- window.open(downloadUrl, '_blank');
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
- #### DELETE <code v-pre>/{basePath}/buckets/:bucketName/objects/:objectName</code>
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
- **Validation:** Bucket name validated with `isValidName()`; object name validated with `isValidPath()`. Returns 400 `"Invalid bucket name"` or `"Invalid object name or path"` respectively if either is invalid.
118
+ ## Enable MetaLink tracking
228
119
 
229
- **Behavior:**
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
- **Response `200`:**
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
- const bucketName = 'user-uploads';
243
- const objectName = 'document.pdf';
244
-
245
- await fetch(`/assets/buckets/${bucketName}/objects/${encodeURIComponent(objectName)}`, {
246
- method: 'DELETE',
247
- });
248
- // File deleted from storage
249
- // MetaLink record deletion initiated (if enabled) but may complete after response
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
- #### PUT <code v-pre>/{basePath}/buckets/:bucketName/meta-links/:objectName</code>
253
- **Availability:** Only registered when `useMetaLink: true`.
254
-
255
- **Parameters:**
256
- - `bucketName` (path): Bucket name
257
- - `objectName` (path): Object name (URL-encoded)
258
-
259
- **Validation:** Bucket name validated with `isValidName()`; object name validated with `isValidPath()`. Returns 400 `"Invalid bucket name"` or `"Invalid object name or path"` respectively if either is invalid.
260
-
261
- **Behavior:**
262
- - Fetches current file metadata from storage via `helper.getStat()`
263
- - Generates the file link using `normalizeLinkFn` (or the default link format <code v-pre>{basePath}/buckets/{bucket}/objects/{encodedName}</code>)
264
- - If MetaLink exists (matched by `bucketName` + `objectName`): Updates with latest metadata via `updateById()`, then refetches via `findById()`
265
- - If MetaLink doesn't exist: Creates new MetaLink record via `create()`
266
- - Always sets `isSynced: true` to mark as synchronized
267
-
268
- **Use Cases:**
269
- - Manually sync files that exist in storage but not in database
270
- - Update MetaLink metadata after file changes
271
- - Rebuild MetaLink records after database restore
272
- - Bulk synchronization operations
273
-
274
- **Response `200` (MetaLink created or updated):**
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
- The response always wraps the MetaLink in a `{ success: boolean, metaLink: ... }` envelope. Both create and update flows return the same shape.
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
- **Example:**
300
- ```typescript
301
- // Sync a single file
302
- const bucketName = 'user-uploads';
303
- const objectName = 'document.pdf';
190
+ ### Custom MetaLink creation
304
191
 
305
- const response = await fetch(
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
- // Bulk sync example: sync all files in storage
315
- const objects = await fetch(`/assets/buckets/${bucketName}/objects`).then(r => r.json());
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
- for (const obj of objects) {
318
- await fetch(
319
- `/assets/buckets/${bucketName}/meta-links/${encodeURIComponent(obj.name)}`,
320
- { method: 'PUT' }
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 Integration
227
+ ## Frontend integration
326
228
 
327
229
  ```typescript
328
- // Upload file with principal association and variant
329
- async function uploadFile(file: File, principalType?: string, principalId?: string, variant?: string) {
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', window.location.origin);
334
- if (principalType) url.searchParams.append('principalType', principalType);
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 response.json();
344
- return result[0].link;
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
- const url = `/assets/buckets/${bucketName}/download/${encodeURIComponent(objectName)}`;
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 Also
249
+ ## See also
365
250
 
366
- - [Setup & Configuration](./) - Quick Reference, Setup Steps, Configuration Options
367
- - [API Reference](./api) - Controller Factory, Storage Interface, MetaLink Schema
368
- - [Error Reference](./errors) - Name Validation and Troubleshooting
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