@venizia/ignis-atlas 0.1.0-13 → 0.1.0-15

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 (131) hide show
  1. package/dist/corpus/changelogs/2025-12-31-json-path-filtering-array-operators.md +3 -3
  2. package/dist/corpus/changelogs/2026-09-14-an-application-declares-no-artifacts.md +105 -0
  3. package/dist/corpus/changelogs/2026-09-14-asset-options-take-resolvers.md +59 -0
  4. package/dist/corpus/changelogs/2026-09-14-component-options-reach-binding.md +61 -0
  5. package/dist/corpus/changelogs/2026-09-14-download-route-singular.md +57 -0
  6. package/dist/corpus/changelogs/2026-09-14-inject-target-through-an-import-cycle.md +85 -0
  7. package/dist/corpus/changelogs/2026-09-14-metalink-table-is-yours.md +92 -0
  8. package/dist/corpus/changelogs/2026-09-14-stats-key-header-in-api-reference.md +44 -0
  9. package/dist/corpus/changelogs/2026-09-14-unresolved-binding-report.md +71 -0
  10. package/dist/corpus/changelogs/index.md +8 -0
  11. package/dist/corpus/releases.json +157 -0
  12. package/dist/corpus/symbols.json +270 -120
  13. package/dist/corpus/wiki/best-practices/architectural-patterns.md +75 -53
  14. package/dist/corpus/wiki/best-practices/code-style-standards/index.md +1 -1
  15. package/dist/corpus/wiki/best-practices/code-style-standards/tooling.md +6 -2
  16. package/dist/corpus/wiki/best-practices/contribution-workflow.md +60 -34
  17. package/dist/corpus/wiki/best-practices/data-modeling.md +1 -1
  18. package/dist/corpus/wiki/best-practices/deployment-strategies.md +1 -1
  19. package/dist/corpus/wiki/best-practices/security-guidelines.md +2 -2
  20. package/dist/corpus/wiki/extensions/atlas/index.md +8 -3
  21. package/dist/corpus/wiki/extensions/components/api-reference.md +5 -3
  22. package/dist/corpus/wiki/extensions/components/authentication/api.md +60 -30
  23. package/dist/corpus/wiki/extensions/components/authentication/errors.md +5 -2
  24. package/dist/corpus/wiki/extensions/components/authentication/index.md +2 -2
  25. package/dist/corpus/wiki/extensions/components/authentication/usage.md +4 -1
  26. package/dist/corpus/wiki/extensions/components/authorization/api.md +127 -49
  27. package/dist/corpus/wiki/extensions/components/authorization/getting-started.md +18 -7
  28. package/dist/corpus/wiki/extensions/components/authorization/usage.md +5 -2
  29. package/dist/corpus/wiki/extensions/components/health-check.md +26 -6
  30. package/dist/corpus/wiki/extensions/components/index.md +14 -12
  31. package/dist/corpus/wiki/extensions/components/mail/api.md +72 -9
  32. package/dist/corpus/wiki/extensions/components/mail/errors.md +25 -6
  33. package/dist/corpus/wiki/extensions/components/mail/index.md +20 -16
  34. package/dist/corpus/wiki/extensions/components/mail/usage.md +1 -1
  35. package/dist/corpus/wiki/extensions/components/request-tracker.md +7 -1
  36. package/dist/corpus/wiki/extensions/components/socket-io/api.md +2 -1
  37. package/dist/corpus/wiki/extensions/components/static-asset/api.md +288 -98
  38. package/dist/corpus/wiki/extensions/components/static-asset/errors.md +45 -28
  39. package/dist/corpus/wiki/extensions/components/static-asset/index.md +11 -6
  40. package/dist/corpus/wiki/extensions/components/static-asset/usage.md +45 -15
  41. package/dist/corpus/wiki/extensions/components/template/api-page.md +9 -5
  42. package/dist/corpus/wiki/extensions/components/template/index.md +13 -4
  43. package/dist/corpus/wiki/extensions/components/template/setup-page.md +22 -9
  44. package/dist/corpus/wiki/extensions/components/template/single-page.md +25 -9
  45. package/dist/corpus/wiki/extensions/helpers/cron/index.md +1 -1
  46. package/dist/corpus/wiki/extensions/helpers/crypto/index.md +20 -6
  47. package/dist/corpus/wiki/extensions/helpers/crypto/reference.md +54 -11
  48. package/dist/corpus/wiki/extensions/helpers/error/index.md +8 -2
  49. package/dist/corpus/wiki/extensions/helpers/index.md +15 -3
  50. package/dist/corpus/wiki/extensions/helpers/inversion/index.md +16 -1
  51. package/dist/corpus/wiki/extensions/helpers/inversion/reference.md +94 -14
  52. package/dist/corpus/wiki/extensions/helpers/kafka/admin.md +6 -3
  53. package/dist/corpus/wiki/extensions/helpers/kafka/consumer.md +17 -4
  54. package/dist/corpus/wiki/extensions/helpers/kafka/examples.md +2 -2
  55. package/dist/corpus/wiki/extensions/helpers/kafka/index.md +4 -2
  56. package/dist/corpus/wiki/extensions/helpers/kafka/producer.md +19 -9
  57. package/dist/corpus/wiki/extensions/helpers/kafka/schema-registry.md +12 -5
  58. package/dist/corpus/wiki/extensions/helpers/logger/reference.md +1 -1
  59. package/dist/corpus/wiki/extensions/helpers/network/api.md +102 -1
  60. package/dist/corpus/wiki/extensions/helpers/queue/index.md +6 -6
  61. package/dist/corpus/wiki/extensions/helpers/redis/reference.md +8 -7
  62. package/dist/corpus/wiki/extensions/helpers/socket-io/api.md +1 -1
  63. package/dist/corpus/wiki/extensions/helpers/socket-io/index.md +2 -1
  64. package/dist/corpus/wiki/extensions/helpers/storage/api.md +484 -226
  65. package/dist/corpus/wiki/extensions/helpers/storage/index.md +33 -16
  66. package/dist/corpus/wiki/extensions/helpers/types/index.md +4 -1
  67. package/dist/corpus/wiki/extensions/helpers/types/reference.md +155 -3
  68. package/dist/corpus/wiki/extensions/helpers/uid/index.md +2 -2
  69. package/dist/corpus/wiki/extensions/helpers/websocket/api.md +16 -3
  70. package/dist/corpus/wiki/extensions/helpers/websocket/index.md +3 -3
  71. package/dist/corpus/wiki/extensions/helpers/worker-thread/index.md +9 -1
  72. package/dist/corpus/wiki/extensions/index.md +14 -12
  73. package/dist/corpus/wiki/guides/core-concepts/application/bootstrapping.md +21 -8
  74. package/dist/corpus/wiki/guides/core-concepts/application/build-info.md +147 -0
  75. package/dist/corpus/wiki/guides/core-concepts/application/index.md +37 -15
  76. package/dist/corpus/wiki/guides/core-concepts/components-guide.md +1 -0
  77. package/dist/corpus/wiki/guides/core-concepts/components.md +8 -6
  78. package/dist/corpus/wiki/guides/core-concepts/dependency-injection.md +1 -1
  79. package/dist/corpus/wiki/guides/core-concepts/grpc-controllers.md +8 -4
  80. package/dist/corpus/wiki/guides/core-concepts/persistent/datasources.md +33 -21
  81. package/dist/corpus/wiki/guides/core-concepts/persistent/index.md +2 -2
  82. package/dist/corpus/wiki/guides/core-concepts/persistent/models.md +3 -3
  83. package/dist/corpus/wiki/guides/core-concepts/persistent/postgres-drivers.md +13 -7
  84. package/dist/corpus/wiki/guides/core-concepts/persistent/search-meilisearch.md +4 -2
  85. package/dist/corpus/wiki/guides/core-concepts/persistent/search-typesense.md +21 -8
  86. package/dist/corpus/wiki/guides/core-concepts/persistent/transactions.md +5 -5
  87. package/dist/corpus/wiki/guides/core-concepts/rest-controllers.md +2 -1
  88. package/dist/corpus/wiki/guides/core-concepts/services.md +3 -1
  89. package/dist/corpus/wiki/guides/get-started/philosophy.md +4 -2
  90. package/dist/corpus/wiki/guides/migrations/unified-connectors-migration.md +1 -1
  91. package/dist/corpus/wiki/guides/reference/glossary.md +3 -3
  92. package/dist/corpus/wiki/guides/tutorials/building-a-crud-api.md +16 -8
  93. package/dist/corpus/wiki/guides/tutorials/complete-installation.md +33 -22
  94. package/dist/corpus/wiki/guides/tutorials/ecommerce-api.md +53 -42
  95. package/dist/corpus/wiki/guides/tutorials/realtime-chat.md +60 -49
  96. package/dist/corpus/wiki/guides/tutorials/testing.md +18 -8
  97. package/dist/corpus/wiki/references/base/application.md +129 -45
  98. package/dist/corpus/wiki/references/base/bootstrapping.md +127 -27
  99. package/dist/corpus/wiki/references/base/components.md +61 -26
  100. package/dist/corpus/wiki/references/base/connectors.md +44 -25
  101. package/dist/corpus/wiki/references/base/controllers.md +20 -9
  102. package/dist/corpus/wiki/references/base/datasources-reference.md +139 -61
  103. package/dist/corpus/wiki/references/base/datasources.md +13 -7
  104. package/dist/corpus/wiki/references/base/dependency-injection.md +34 -14
  105. package/dist/corpus/wiki/references/base/filter-system/default-filter.md +9 -6
  106. package/dist/corpus/wiki/references/base/filter-system/fields-order-pagination.md +36 -6
  107. package/dist/corpus/wiki/references/base/filter-system/index.md +2 -2
  108. package/dist/corpus/wiki/references/base/filter-system/logical-operators.md +1 -1
  109. package/dist/corpus/wiki/references/base/filter-system/pattern-matching.md +8 -5
  110. package/dist/corpus/wiki/references/base/filter-system/quick-reference.md +15 -3
  111. package/dist/corpus/wiki/references/base/filter-system/tips.md +13 -4
  112. package/dist/corpus/wiki/references/base/filter-system/use-cases.md +8 -3
  113. package/dist/corpus/wiki/references/base/grpc-controllers.md +20 -4
  114. package/dist/corpus/wiki/references/base/index.md +37 -18
  115. package/dist/corpus/wiki/references/base/middlewares.md +108 -42
  116. package/dist/corpus/wiki/references/base/models-reference.md +17 -7
  117. package/dist/corpus/wiki/references/base/models.md +1 -1
  118. package/dist/corpus/wiki/references/base/providers.md +29 -10
  119. package/dist/corpus/wiki/references/base/repositories/advanced.md +42 -20
  120. package/dist/corpus/wiki/references/base/repositories/index.md +10 -5
  121. package/dist/corpus/wiki/references/base/repositories/mixins.md +5 -5
  122. package/dist/corpus/wiki/references/base/repositories/soft-deletable.md +22 -12
  123. package/dist/corpus/wiki/references/base/secrets.md +9 -6
  124. package/dist/corpus/wiki/references/base/services.md +46 -14
  125. package/dist/corpus/wiki/references/configuration/environment-variables.md +52 -9
  126. package/dist/corpus/wiki/references/configuration/index.md +13 -6
  127. package/dist/corpus/wiki/references/quick-reference.md +79 -33
  128. package/dist/corpus/wiki/references/utilities/module.md +19 -3
  129. package/dist/corpus/wiki/references/utilities/parse.md +2 -0
  130. package/dist/corpus/wiki/references/utilities/schema.md +4 -2
  131. package/package.json +2 -2
@@ -209,6 +209,6 @@ All changes are additive enhancements. Existing filter queries work unchanged.
209
209
  ## Documentation
210
210
 
211
211
  Full documentation with examples available at:
212
- - [Repositories - JSON Path Filtering](/references/base/repositories/#json-path-filtering)
213
- - [Repositories - Array Column Operators](/references/base/repositories/#array-column-operators-postgresql)
214
- - [Repositories - Query Operators](/references/base/repositories/#query-operators)
212
+ - [Repositories - JSON Path Filtering](/references/base/filter-system/json-filtering)
213
+ - [Repositories - Array Column Operators](/references/base/filter-system/array-operators)
214
+ - [Repositories - Query Operators](/references/base/filter-system/quick-reference)
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: An Application Lists None Of Its Own Artifacts
3
+ description: "configs.discoverArtifacts: true registers every decorated class in the import graph, so a stereotype is the whole declaration. when still keeps several applications in one process apart."
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## An application lists none of its own artifacts
9
+
10
+ <Badge type="tip" text="Feature" />
11
+
12
+ **In one line.** Set `discoverArtifacts: true`, import the generated file for its side effects, and
13
+ every decorated class registers itself.
14
+
15
+ ```ts
16
+ // index.ts - side effects only. No list, no export to name.
17
+ import './generated/artifacts';
18
+
19
+ // application.ts
20
+ export const beConfigs: IApplicationConfigs = {
21
+ path: { base: '/', isStrict: true },
22
+ discoverArtifacts: true, // no list of services, repositories, components or configurations
23
+ };
24
+ ```
25
+
26
+ `@service()` on the class is the whole declaration. Before, the decorator said what a class is and
27
+ `configs.artifacts` separately said the class exists - two statements for one fact, and the second
28
+ was hand-maintained.
29
+
30
+ ## Why discovery happens at import, not at boot
31
+
32
+ A class exists at run time only because something imported it. That rules out the boot-time
33
+ filesystem scan other frameworks use: a compiled single-file binary has no source tree to walk, and
34
+ IGNIS removed that machinery on purpose.
35
+
36
+ So `injectable` - the one function `@service`, `@repository`, `@controller`, `@datasource`,
37
+ `@component`, `@configuration` and `@model` all call - records the class as it decorates it. The
38
+ generated file already imports every one of those classes, so importing it is what makes them exist.
39
+
40
+ `@model` is recorded and then skipped: a model has a binding namespace but no index field, because
41
+ its repository registers it.
42
+
43
+ ## Discovery is off until you ask, and that is deliberate
44
+
45
+ | `discoverArtifacts` | `artifacts` | What registers |
46
+ |---|---|---|
47
+ | unset | unset | nothing - unchanged |
48
+ | unset | set | exactly what it lists - unchanged |
49
+ | `true` | unset | every decorated class |
50
+ | `true` | set | the union, de-duplicated |
51
+
52
+ **An absent `artifacts` still registers nothing.** Reading it as "register everything" was the first
53
+ shape of this change, and it is a trap: a consumer whose `createAppConfig` omits the key on purpose
54
+ would have booted with every decorated class in the binary - no compile error, no warning, just ten
55
+ times the bindings. One of its applications does exactly that, deliberately, to register nothing.
56
+
57
+ The two compose because a library exports an index and an application still wants to discover its own
58
+ classes beside it. A class named in both registers once.
59
+
60
+ ## Several applications in one process
61
+
62
+ The discovered list is process-wide, so without a filter every application registers everything.
63
+ `when` already carries this, and already receives the application:
64
+
65
+ ```ts
66
+ @controller({ when: ({ application }) => application instanceof SearchApplication })
67
+ class OrderController {}
68
+ ```
69
+
70
+ **A class with no `when` registers in every application of the process.** That is the cost, and it is
71
+ why a worker that must not mount controllers keeps using `when`.
72
+
73
+ ## Framework components stay explicit
74
+
75
+ No class inside IGNIS carries a stereotype, so discovery never turns on a framework component behind
76
+ your back. Choosing which features an application runs is a real decision, and it stays one:
77
+
78
+ ```ts
79
+ preConfigure() {
80
+ this.component(HealthCheckComponent);
81
+ this.component(StaticAssetComponent);
82
+ }
83
+ ```
84
+
85
+ ## Measured on the reference application
86
+
87
+ `examples/vert` lists none of its own. A probe booting it finds 2 services, 10 repositories, 3
88
+ controllers, 1 datasource and 1 component - the same 17 classes its generated index listed, class for
89
+ class.
90
+
91
+ ## Who is affected
92
+
93
+ **You set `configs.artifacts`.** Nothing. Same behaviour, same classes, same order.
94
+
95
+ **You want to stop listing.** Replace `import { GeneratedArtifacts } from './generated/artifacts'`
96
+ with `import './generated/artifacts'`, set `discoverArtifacts: true`, delete the `artifacts` entry,
97
+ and move any framework component into `preConfigure()`.
98
+
99
+ **You run several applications in one process.** Read the `when` section before setting the flag.
100
+
101
+ **Files:**
102
+
103
+ - [`packages/kernel/src/base/metadata/injectable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/kernel/src/base/metadata/injectable.ts) - records the class it decorates
104
+ - [`packages/kernel/src/base/applications/artifact-index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/kernel/src/base/applications/artifact-index.ts) - `buildDiscoveredIndex`
105
+ - [`packages/kernel/src/base/applications/rest.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/kernel/src/base/applications/rest.ts) - `registerDiscoveredArtifacts`
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: Asset Options Take A Resolver Anywhere They Took A Value
3
+ description: "controller.bucket, metaLink.model and metaLink.repository accept a value or a function returning one; TResolveObjectName is renamed TObjectNameResolver, following the verb-last naming the repository already uses."
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## Asset options take a resolver anywhere they took a value
9
+
10
+ <Badge type="danger" text="Breaking" />
11
+
12
+ **In one line.** One type is renamed, and three options widen to `TValueOrAsyncResolver`.
13
+
14
+ | Before | After |
15
+ |---|---|
16
+ | `TResolveObjectName` | `TObjectNameResolver` |
17
+ | `controller.bucket?: string \| (() => string)` | `controller.bucket?: TValueOrAsyncResolver<string>` |
18
+ | `metaLink.model: typeof BaseRelationalEntity<Schema>` | `metaLink.model: TValueOrAsyncResolver<...>` |
19
+ | `metaLink.repository: DefaultCRUDRepository<Schema>` | `metaLink.repository: TValueOrAsyncResolver<...>` |
20
+
21
+ The rename is the breaking half. `TResolveObjectName` read verb-first, which named an action; every
22
+ other type in the repository names a thing. The shape is untouched, so the fix is the new name.
23
+
24
+ The route table also gained a name of its own, `TStaticAssetRoutes`, lifted out of the inline
25
+ `controller.routes` shape. Nothing about it changed.
26
+
27
+ The other breaking change landing today touches the same component: the download route drops its
28
+ plural, `/downloads/{objectName}` becomes
29
+ [`/download/{objectName}`](./2026-09-14-download-route-singular).
30
+
31
+ ## Why a resolver, and what it buys
32
+
33
+ A resolver is read when the value is used, not when the options object is built. That is what lets a
34
+ repository be named before the container has bound it, and what lets a bucket come from an
35
+ environment variable read per request rather than at boot:
36
+
37
+ ```ts
38
+ controller: { name: 'AssetController', basePath: '/assets', bucket: () => process.env.APP_ENV_BUCKET },
39
+ metaLink: {
40
+ model: MetaLinkModel,
41
+ repository: () => application.get<MetaLinkRepository>({ key: 'repositories.MetaLinkRepository' }),
42
+ },
43
+ ```
44
+
45
+ A plain value still works everywhere. A class is treated as a value, never as a factory, so
46
+ `model: MetaLinkModel` is never called.
47
+
48
+ ## Who is affected
49
+
50
+ **You name `TResolveObjectName`.** Rename it. The compiler finds every site.
51
+
52
+ **You pass a plain bucket, model or repository.** Nothing. A value is still a value.
53
+
54
+ **You wrote `bucket: () => env`.** Nothing. That form already worked and is now the documented shape.
55
+
56
+ **Files:**
57
+
58
+ - [`packages/core-server/src/components/static-asset/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/static-asset/common/types.ts) - `TObjectNameResolver`, `TStaticAssetRoutes`, `TMetaLinkConfig`
59
+ - [`packages/core-server/src/components/static-asset/controller/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/static-asset/controller/factory.ts) - `resolveBucket`
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: A Component Reads Its Options In binding(), Without Overriding configure()
3
+ description: BaseComponent stores what application.component(Ctor, { options }) handed it on this.configuredOptions before binding() runs. StaticAssetComponent takes its options that way.
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## A component reads its options in `binding()`
9
+
10
+ <Badge type="tip" text="Enhancement" />
11
+
12
+ **In one line.** `BaseComponent` keeps the options it was configured with on
13
+ `this.configuredOptions`, set before `binding()` runs.
14
+
15
+ ```ts
16
+ class MailComponent extends BaseComponent<IMailOptions> {
17
+ override binding() {
18
+ const { provider } = this.configuredOptions ?? {};
19
+ }
20
+ }
21
+
22
+ application.component(MailComponent, { options: { provider: 'amazon-ses' } });
23
+ ```
24
+
25
+ Until now the options reached `configure()` and stopped there: `binding()` had no way to read them,
26
+ so every component that wanted them overrode `configure()` to stash a copy. Each one invented its
27
+ own field name.
28
+
29
+ ## `StaticAssetComponent` takes its options directly
30
+
31
+ ```ts
32
+ application.component(StaticAssetComponent, {
33
+ options: {
34
+ assets: {
35
+ controller: { name: 'AssetController', basePath: '/assets' },
36
+ storage: StaticAssetStorageTypes.BUN_S3,
37
+ helper: storageHelper,
38
+ },
39
+ },
40
+ });
41
+ ```
42
+
43
+ `StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS` still works, and is still the right
44
+ answer in one case: a component registered through `configs.artifacts` gets no options, because an
45
+ artifact index carries classes and nothing else. Direct options win when both are present.
46
+
47
+ ## Who is affected
48
+
49
+ **You bind `STATIC_ASSET_COMPONENT_OPTIONS`.** Nothing. The key is read whenever no options are
50
+ passed.
51
+
52
+ **You register `StaticAssetComponent` through `configs.artifacts`.** Nothing, and the key stays the
53
+ way to configure it - through a `@provide` method on a component of your own, for example.
54
+
55
+ **You write your own component.** Drop the `configure()` override that existed only to keep a copy
56
+ of the options.
57
+
58
+ **Files:**
59
+
60
+ - [`packages/kernel/src/base/components/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/kernel/src/base/components/base.ts) - `BaseComponent.configuredOptions`
61
+ - [`packages/core-server/src/components/static-asset/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/static-asset/component.ts) - `StaticAssetComponent.binding`
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: The Download Route Drops Its Plural
3
+ description: GET /downloads/{objectName} becomes GET /download/{objectName}. A verb segment names one action, not a collection, and this reverses one row of the 2026-09-08 rename.
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## The download route drops its plural
9
+
10
+ <Badge type="danger" text="Breaking" />
11
+
12
+ **In one line.** The static-asset download route is now singular.
13
+
14
+ | Before | After |
15
+ |---|---|
16
+ | `GET {base}/downloads/{objectName}` | `GET {base}/download/{objectName}` |
17
+
18
+ Nothing else moves. `{base}` is `/buckets/{bucketName}` unless `controller.bucket` is configured.
19
+
20
+ ## Why the plural was wrong
21
+
22
+ `/buckets` and `/objects` are plural because each names a collection you can list. `/download` names
23
+ neither - it is the action you take on one object, and the object is already in the next segment.
24
+ The [2026-09-08 rename](./2026-09-08-storage-hardening-and-bun-s3-only) argued the segment named "a
25
+ set of representations". It does not. There is one representation, and one verb.
26
+
27
+ `POST {base}/objects` for upload is unaffected, and correct for the same reason: the collection is
28
+ the resource, and `POST` is already the verb.
29
+
30
+ The action still sits **before** the key, and that part is forced rather than chosen. With nested
31
+ object keys enabled, a suffix route never matches - Hono serves `/objects/photos/a.png/download`
32
+ through the catch-all with the key `photos/a.png/download`.
33
+
34
+ ## Who is affected
35
+
36
+ **You never called the download route.** Nothing.
37
+
38
+ **You are on `0.2.0-31` and call `/downloads/{objectName}`.** Drop the `s`. A client on the old path
39
+ gets a `404` at runtime; nothing fails at compile time.
40
+
41
+ **You pin the path yourself.** An override wins over both spellings:
42
+
43
+ ```ts
44
+ application.component(StaticAssetComponent, {
45
+ options: {
46
+ controller: {
47
+ routes: { downloadObjectByName: { path: '/download/{objectName}{.+}' } },
48
+ },
49
+ },
50
+ });
51
+ ```
52
+
53
+ The route key `downloadObjectByName` and the handler name are unchanged - only the URL segment moved.
54
+
55
+ **Files:**
56
+
57
+ - [`packages/core-server/src/components/static-asset/controller/base.definition.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/static-asset/controller/base.definition.ts) - `DOWNLOAD_OBJECT_BY_NAME`
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: A Dependency Reached Through An Import Cycle Can Still Name Its Class
3
+ description: "@inject({ target }) takes a function returning the class. The function runs when the container resolves, so a class a barrel makes unreachable at module load is still nameable."
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## A dependency reached through an import cycle can still name its class
9
+
10
+ <Badge type="tip" text="Feature" />
11
+
12
+ **In one line.** `@inject({ target })` accepts a function that returns the class, and the function
13
+ runs at resolve time.
14
+
15
+ ```ts
16
+ @inject({ target: () => TaxOnboardingService })
17
+ private readonly taxOnboardingService: TaxOnboardingService
18
+ ```
19
+
20
+ `@inject({ target: TaxOnboardingService })` is unchanged and stays the default. Reach for the
21
+ function form only where a barrel makes the class unreachable at module load.
22
+
23
+ ## The failure it answers
24
+
25
+ A barrel re-exports two modules and one imports the other back. The module the barrel evaluates
26
+ first reads a class that is still in its temporal dead zone:
27
+
28
+ ```
29
+ ReferenceError: Cannot access 'TaxOnboardingService' before initialization
30
+ ```
31
+
32
+ That error comes from the module system, before `inject` is ever called, so the framework cannot
33
+ name the decorator, the class, or the parameter. The only fix available today is to abandon the
34
+ barrel and deep-import - which works only when both files are yours.
35
+
36
+ ## The annotation has to move too
37
+
38
+ The thunk covers the decorator argument. It does not cover the type annotation, because
39
+ `emitDecoratorMetadata` turns that annotation into a second, eager value reference:
40
+
41
+ ```js
42
+ __legacyMetadataTS('design:paramtypes', [typeof NoteService === 'undefined' ? Object : NoteService]);
43
+ ```
44
+
45
+ `typeof` on a binding in the temporal dead zone throws too, so TypeScript's own guard does not help.
46
+ Import the class twice - once as a value for the thunk, once as a type for the annotation:
47
+
48
+ ```ts
49
+ import { NoteService } from './index';
50
+ import type { NoteService as TNoteService } from './index';
51
+
52
+ constructor(@inject({ target: () => NoteService }) readonly noteService: TNoteService) {}
53
+ ```
54
+
55
+ Worth knowing even if you never use the thunk: **`@inject({ key })` dies on the same cycle**, for the
56
+ same reason. The plural spelling of the problem is `emitDecoratorMetadata`, not the class form.
57
+
58
+ ## There is no async form, on purpose
59
+
60
+ `Container.instantiate` is synchronous and ends in `new cls(...args)`. An async resolver would make
61
+ it async, then `get()` async, then every `@inject` site async. The target is only read to look up a
62
+ binding key recorded on the class, so there is nothing to await.
63
+
64
+ ## The resolver vocabulary moved down a layer
65
+
66
+ `TResolver`, `TAsyncResolver`, `TValueOrResolver`, `TValueOrAsyncResolver`, `resolveValue` and
67
+ `resolveValueAsync` now live in `@venizia/ignis-inversion`, beside the container that branches on
68
+ them. `@venizia/ignis-helpers/common` re-exports all six, so every existing import keeps working and
69
+ there is one declaration instead of two.
70
+
71
+ ## Who is affected
72
+
73
+ **You use `@inject({ key })` or `@inject({ target: Class })`.** Nothing. Both are unchanged.
74
+
75
+ **You hit a circular-import crash and worked around it by deep-importing.** The thunk plus a
76
+ type-only annotation replaces the workaround.
77
+
78
+ **You import a resolver type from `@venizia/ignis-helpers/common`.** Nothing. The names and shapes
79
+ are identical.
80
+
81
+ **Files:**
82
+
83
+ - [`packages/inversion/src/common/utilities.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/common/utilities.ts) - `resolveValue`, `resolveValueAsync`, `resolveInjectTarget`
84
+ - [`packages/inversion/src/modules/container/container.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/container/container.ts) - `Container.resolveBindingKey`
85
+ - [`packages/inversion/src/modules/metadata/injectors.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/metadata/injectors.ts) - `TInjectOptions`
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: The MetaLink Table Can Live In Your Own Postgres Schema
3
+ description: TMetaLinkConfig pinned the MetaLink type to the exact table IGNIS ships, so a column-identical table under another Postgres schema was refused at compile time. The constraint is now the row, not the table.
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## The MetaLink table can live in your own Postgres schema
9
+
10
+ <Badge type="tip" text="Fix" />
11
+
12
+ **In one line.** `TStaticAssetsComponentOptions` takes the MetaLink table as a type argument, and the
13
+ constraint is now the row rather than the exact table IGNIS ships.
14
+
15
+ A column-for-column identical table under a Postgres schema of your own used to be refused:
16
+
17
+ ```
18
+ error TS2322: Types of property 'schema' are incompatible.
19
+ Type '"commerce"' is not assignable to type 'undefined'.
20
+ ```
21
+
22
+ Nothing was wrong at runtime. The repository carries its own schema, so the row landed in
23
+ `commerce.MetaLink` exactly as intended. Only the type layer said no, and an application had no way
24
+ around it - the option type never took a type argument, so declaring one on `TMetaLinkConfig`
25
+ changed nothing.
26
+
27
+ ## Pass your table as the type argument
28
+
29
+ ```ts
30
+ const commerceSchema = pgSchema('commerce');
31
+
32
+ @model({ type: 'entity' })
33
+ export class MetaLinkModel extends BaseRelationalEntity<typeof MetaLinkModel.schema> {
34
+ static override schema = commerceSchema.table('MetaLink', { /* the MetaLink columns */ });
35
+ }
36
+
37
+ this.bind<TStaticAssetsComponentOptions<typeof MetaLinkModel.schema>>({
38
+ key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
39
+ }).toValue({ /* ... */ });
40
+ ```
41
+
42
+ The new constraint is `TMetaLinkCompatibleSchema`: any table whose row carries the MetaLink fields.
43
+ The table is free to sit in another Postgres schema, or to carry another name.
44
+
45
+ **A table missing one of those fields is still refused.** That is the part worth keeping - the
46
+ workaround people reach for, `as unknown as TMetaLinkConfig['model']`, silences the missing-column
47
+ check along with the schema complaint.
48
+
49
+ ## `createMetaLink` returns a row, and now says so
50
+
51
+ Its return type read `{ count: number; data: Schema }`, where `Schema` is the TABLE type. The
52
+ component reads a row out of it, and `repository.create()` returns a row, so the annotation named the
53
+ wrong thing. It is `{ count: number; data: TTableObject<Schema> }`.
54
+
55
+ If you wrote `createMetaLink` and fought the type, the fight is over. Nothing about the value changed.
56
+
57
+ ## The class is generic too, not only the options
58
+
59
+ `component(Ctor, { options })` infers the options from the CLASS, so a class pinned to the shipped
60
+ table can only ever carry options for that table. Making the option types generic fixed the
61
+ `bind<>()` path and left the `component()` path failing:
62
+
63
+ ```
64
+ error TS2345: Argument of type 'typeof StaticAssetComponent' is not assignable to parameter of type
65
+ 'TClass<BaseComponent<{ asset: { controller: ... }}>>'
66
+ ```
67
+
68
+ `StaticAssetComponent<Schema>` carries the table now, so both paths take a namespaced table:
69
+
70
+ ```ts
71
+ application.component(StaticAssetComponent, { options: { assets: { /* ... */ } } });
72
+ ```
73
+
74
+ Inside the controller factory the table type is erased on purpose: every read and write there is on
75
+ MetaLink COLUMNS, and threading a table type through 700 lines of handler buys nothing the public
76
+ option types do not already check.
77
+
78
+ ## Who is affected
79
+
80
+ **You do not use `useMetaLink`.** Nothing.
81
+
82
+ **You use the MetaLink table IGNIS ships.** Nothing. `TStaticAssetsComponentOptions` with no type
83
+ argument keeps meaning what it meant.
84
+
85
+ **You kept your MetaLink table in another Postgres schema behind a cast.** Drop the cast and pass the
86
+ table as the type argument. The missing-column check comes back with it.
87
+
88
+ **Files:**
89
+
90
+ - [`packages/core-server/src/components/static-asset/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/static-asset/common/types.ts) - `TMetaLinkConfig`, `TStaticAssetsComponentOptions`
91
+ - [`packages/core-server/src/components/static-asset/models/base.model.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/static-asset/models/base.model.ts) - `TMetaLinkCompatibleSchema`
92
+ - [`examples/vert/src/components/platform.component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/examples/vert/src/components/platform.component.ts) - a working `useMetaLink: true` example
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: The Stats Key Header Shows Up In The API Reference
3
+ description: GET /health/stats now declares x-health-key in its OpenAPI definition, so the API reference renders an input for it instead of answering 404 with no way to send the key.
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## The stats key header shows up in the API reference
9
+
10
+ <Badge type="tip" text="Enhancement" />
11
+
12
+ **In one line.** `GET /health/stats` declares `x-health-key` as a header parameter, so the API
13
+ reference renders a box for it.
14
+
15
+ Before this, a host that set `stats.secretKey` locked the route behind a header the reference never
16
+ offered. Opening `/health/stats` from the explorer returned `404` with no way to send the key, and
17
+ the `404` is deliberate - an unauthorised caller must not learn the route exists - so the reference
18
+ gave no hint about what was missing.
19
+
20
+ ## The header stays optional
21
+
22
+ A host with no `stats.secretKey` runs an open stats route and needs no key. Marking the parameter
23
+ required would make the reference lie to that host, so it is declared optional and the description
24
+ carries the condition:
25
+
26
+ ```
27
+ x-health-key string, optional
28
+ The stats key. A host that configures `stats.secretKey` answers 404 without it.
29
+ ```
30
+
31
+ ## Who is affected
32
+
33
+ **You set `stats.secretKey`.** The reference now has a box for the key. Nothing changes on the wire.
34
+
35
+ **You run an open stats route.** One optional parameter appears in the reference. Requests are
36
+ unaffected - the value is never read when no key is configured.
37
+
38
+ **You call the route from code.** Nothing changes. The header name, the gate and the `404` are the
39
+ same.
40
+
41
+ **Files:**
42
+
43
+ - [`packages/core-server/src/components/health-check/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/health-check/controller.ts) - `RouteConfigs.STATS`
44
+ - [`packages/core-server/src/components/health-check/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/health-check/common/constants.ts) - `HealthCheckHeaders.SECRET_KEY`
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: The Container Can Name A Binding Nobody Resolves
3
+ description: doVerify catches a dependency injected but never registered. The reverse - a class registered that nothing injects - was silent. The container now counts what it hands out.
4
+ ---
5
+
6
+ # Changelog - 2026-09-14
7
+
8
+ ## The container can name a binding nobody resolves
9
+
10
+ <Badge type="tip" text="Feature" />
11
+
12
+ **In one line.** The container counts every key it hands out, so an application can ask which
13
+ registrations nothing ever read.
14
+
15
+ ```ts
16
+ await application.initialize();
17
+ application.startResolutionCounting(); // counting is off until you ask
18
+
19
+ // ... run the traffic that exercises the application ...
20
+
21
+ application.getUnresolvedBindings(); // ['repositories.OrphanRepository', ...]
22
+ ```
23
+
24
+ ## The asymmetry it closes
25
+
26
+ | The mistake | Before |
27
+ |---|---|
28
+ | A dependency is injected but never registered | boot dies, naming the class - `bootChecks.binding.doVerify` |
29
+ | A class is registered but nothing ever injects it | nothing says anything, ever |
30
+
31
+ So a hand-written artifact list can only grow. Someone adds a repository, a later change removes the
32
+ last site that injects it, and the registration line stays. Measured in one downstream service: 59
33
+ repositories declared by hand, 47 actually injected.
34
+
35
+ A static scan cannot answer this, and that is not a tooling gap. The same service resolves five
36
+ repositories by building a binding key at run time, which no grep sees - a static verdict would
37
+ condemn five live registrations.
38
+
39
+ ## Four members, and counting is off until you ask
40
+
41
+ | Member | Answers |
42
+ |---|---|
43
+ | `getResolutionCounts()` | a copy of key to read count; a key bound and never read is absent |
44
+ | `startResolutionCounting()` | clears the counts and starts counting - OFF by default |
45
+ | `stopResolutionCounting()` | stops counting, keeps what was counted |
46
+ | `getUnresolvedBindings({ tags })` | bound keys with no count, defaulting to `services` and `repositories` |
47
+
48
+ **Counting is off by default, and that is the point.** The counter sits on `BaseContainer.get`, the
49
+ one method every injection passes through. Measured over 2 million resolutions: **89 ns with counting
50
+ off, 96 ns with it on.** An application that never reads the report pays nothing.
51
+
52
+ **Start after `initialize()`.** `doVerify: true` reads every service and repository at boot, so
53
+ counting across boot reports empty by construction. Starting is also the reset. Reading the report
54
+ without having started **throws**, naming the method to call - a list that names every binding is a
55
+ wrong answer, not an empty one.
56
+
57
+ ## A count of zero is evidence, not a verdict
58
+
59
+ An unread binding may be dead code. It may equally be an allow-list entry, or a technical floor only
60
+ one deployment exercises. The framework cannot tell those apart, so it reports and stops there - no
61
+ warning, no boot gate, no deletion. Read the list yourself.
62
+
63
+ ## Who is affected
64
+
65
+ **Nobody, unless you ask.** No new boot step, no new option, no behaviour change, and no cost until
66
+ `startResolutionCounting()` runs.
67
+
68
+ **Files:**
69
+
70
+ - [`packages/inversion/src/modules/container/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/container/base.ts) - `startResolutionCounting`, `getResolutionCounts`
71
+ - [`packages/kernel/src/base/applications/rest.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/kernel/src/base/applications/rest.ts) - `RestApplication.getUnresolvedBindings`
@@ -17,6 +17,14 @@ This section tracks the history of significant changes, refactors, and updates t
17
17
 
18
18
  | Date | Title | Type |
19
19
  |------|-------|------|
20
+ | 2026-09-14 | [An Application Declares No Artifacts](./2026-09-14-an-application-declares-no-artifacts) | Feature |
21
+ | 2026-09-14 | [The Container Can Name A Binding Nobody Resolves](./2026-09-14-unresolved-binding-report) | Feature |
22
+ | 2026-09-14 | [Asset Options Take A Resolver Anywhere They Took A Value](./2026-09-14-asset-options-take-resolvers) | Breaking |
23
+ | 2026-09-14 | [A Component Reads Its Options In binding()](./2026-09-14-component-options-reach-binding) | Enhancement |
24
+ | 2026-09-14 | [A Dependency Reached Through An Import Cycle Can Still Name Its Class](./2026-09-14-inject-target-through-an-import-cycle) | Feature |
25
+ | 2026-09-14 | [The MetaLink Table Can Live In Your Own Postgres Schema](./2026-09-14-metalink-table-is-yours) | Fix |
26
+ | 2026-09-14 | [The Stats Key Header Shows Up In The API Reference](./2026-09-14-stats-key-header-in-api-reference) | Enhancement |
27
+ | 2026-09-14 | [The Download Route Drops Its Plural](./2026-09-14-download-route-singular) | Breaking |
20
28
  | 2026-09-12 | [An Open Stats Route Now Says So At Boot](./2026-09-12-unguarded-stats-warning) | Enhancement |
21
29
  | 2026-09-12 | [Empty Env Values Are Allowed By Default](./2026-09-12-empty-envs-allowed-by-default) | Breaking |
22
30
  | 2026-09-12 | [A Blank Env Line No Longer Beats Its Default At Module Load](./2026-09-12-blank-env-at-module-load) | Fix |