katagami 3.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +84 -37
  2. package/dist/chunk-R66WOZUM.js +71 -0
  3. package/dist/container/index.d.cts +142 -35
  4. package/dist/container/index.d.ts +142 -35
  5. package/dist/container/policy.d.cts +36 -0
  6. package/dist/container/policy.d.ts +36 -0
  7. package/dist/disposable/index.cjs +27 -5
  8. package/dist/disposable/index.d.cts +17 -7
  9. package/dist/disposable/index.d.ts +17 -7
  10. package/dist/disposable/index.js +2 -41
  11. package/dist/entrypoint/index.d.cts +20 -0
  12. package/dist/entrypoint/index.d.ts +20 -0
  13. package/dist/index.cjs +551 -149
  14. package/dist/index.d.cts +8 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.js +495 -150
  17. package/dist/internal.d.cts +65 -0
  18. package/dist/internal.d.ts +65 -0
  19. package/dist/metadata/index.d.cts +67 -0
  20. package/dist/metadata/index.d.ts +67 -0
  21. package/dist/resolver/index.d.cts +5 -0
  22. package/dist/resolver/index.d.ts +5 -0
  23. package/dist/scope/index.d.cts +49 -24
  24. package/dist/scope/index.d.ts +49 -24
  25. package/dist/scope/operations.d.cts +22 -0
  26. package/dist/scope/operations.d.ts +22 -0
  27. package/docs/README.de.md +3 -2
  28. package/docs/README.es.md +3 -2
  29. package/docs/README.fr.md +3 -2
  30. package/docs/README.ja.md +61 -27
  31. package/docs/README.ko.md +3 -2
  32. package/docs/README.zh-CN.md +3 -2
  33. package/docs/README.zh-TW.md +3 -2
  34. package/docs/ai-coding-agents.md +3 -3
  35. package/docs/articles/ai-coding-agents.md +1 -1
  36. package/docs/articles/request-scope.md +2 -1
  37. package/docs/choosing-di.md +36 -9
  38. package/docs/guide.md +35 -4
  39. package/docs/registration-policies.ja.md +261 -0
  40. package/docs/registration-policies.md +435 -0
  41. package/docs/type-safety.md +13 -4
  42. package/llms.txt +1 -0
  43. package/package.json +1 -1
  44. package/dist/chunk-J2NYR3SH.js +0 -6
package/README.md CHANGED
@@ -57,43 +57,88 @@ These checks assume narrow tokens and preserved registration types; see the
57
57
 
58
58
  ### Library comparison
59
59
 
60
- Reviewed **2026-09-11**, against the npm `latest` versions below and official documentation.
61
- The tables describe the built-in APIs; add-ons and application-specific wrappers can change the
62
- trade-offs. [Version sources and detailed comparison notes](./docs/choosing-di.md#comparison-sources)
63
- explain the distinctions.
64
-
65
- | Library / reviewed version | Dependency typing and missing registrations | Scoped-dependency handling | DI setup |
66
- | --- | --- | --- | --- |
67
- | **Katagami 3.0.2** | **Accumulated literal/unique-symbol tokens; missing required tokens rejected** | **Scoped tokens excluded from singleton/transient factory resolvers** | **No decorators or metadata; zero runtime dependencies** |
68
- | [InversifyJS 8.2.3](https://inversify.io/docs/fundamentals/binding/) | Typed identifiers and bindings; binding existence checked at runtime | Declared binding scopes; not a scope-filtered resolver type | Metadata-based class injection; explicit value/factory bindings also available |
69
- | [tsyringe 4.10.0](https://github.com/microsoft/tsyringe#readme) | Class/generic resolution types; registrations checked at runtime | Runtime lifetime settings; factory receives the container | Decorators and a Reflect metadata polyfill for class injection |
70
- | [TypeDI 0.10.0](https://github.com/typestack/typedi/tree/v0.10.0) | Class and `Token<T>` types; registrations checked at runtime | Shared/transient services and named containers | Decorators and `reflect-metadata` in the TypeScript setup |
71
- | [Awilix 13.0.5](https://github.com/jeffijoe/awilix#readme) | Registration-derived cradle types; broad `resolve` overload still accepts unknown names | `strict: true` checks lifetime leaks at runtime | No decorators or metadata |
72
- | [NestJS 12.0.1](https://docs.nestjs.com/fundamentals/custom-providers) | Typed providers; module/provider graph resolved at runtime | Request scope propagates to dependent providers | Framework modules and metadata-based class injection |
73
- | [Effect 3.22.2](https://effect.website/docs/v3/requirements-management/layers) | Service requirements tracked in `Effect` / `Layer` types | Typed `Scope` requirements and resource finalizers; a different lifetime model | No decorators or metadata; Effect's service/layer model |
74
- | [typed-inject 5.0.0](https://github.com/nicojs/typed-inject#readme) | Accumulated string tokens and checked `inject` tuples | Singleton/transient providers and child injectors; no separate scoped lifetime | No decorators or metadata; zero runtime dependencies |
75
-
76
- Awilix's inferred `cradle` and typed-inject's registration types are real compile-time features.
77
- Katagami's distinction is the combination of accumulated registration checks with **scope-filtered
78
- factory resolvers**, using direct `r.resolve(token)` calls. Effect also checks unsatisfied service
79
- requirements, within its broader effect and resource model.
80
-
81
- | Library | Lifetimes / scope model | Asynchronous services | Resource cleanup |
82
- | --- | --- | --- | --- |
83
- | **Katagami** | **Singleton, transient, scoped; nested scopes** | **Inferred `Promise<T>`; explicitly await dependencies** | **`Symbol.dispose` / `Symbol.asyncDispose` via `disposable()`; `await using`** |
84
- | InversifyJS | Singleton, transient, request (one resolution graph); container hierarchy | Async bindings via `getAsync` / `getAllAsync`; awaits dependencies | Singleton deactivation handlers |
85
- | tsyringe | Singleton, transient, resolution-scoped, container-scoped | Factories can return Promise-valued services; consumer handles the Promise | `container.dispose()` for constructed disposable instances |
86
- | TypeDI | Shared or transient services; named containers | Promise-valued services; consumer handles the Promise | `reset()` / removal can call `destroy()`; returned Promise is not awaited |
87
- | Awilix | Singleton, transient, scoped | Promise-valued factories; consumer handles the Promise | Registered disposers for cached singleton/scoped values |
88
- | NestJS | Singleton, transient, HTTP request; scope propagation | Async providers are awaited before dependent construction | Application lifecycle hooks; not called for request-scoped classes |
89
- | Effect | Memoized layers and explicit resource scopes | Effectful acquisition, including async effects | Scope finalizers and `acquireRelease` |
90
- | typed-inject | Singleton, transient; disposable child injectors | Promise-valued factories with inferred return types | `injector.dispose()` awaits owned instances' `dispose()` |
91
-
92
- “Request” is not identical across libraries: InversifyJS uses a resolution graph, while NestJS can
93
- use an HTTP request. Katagami and Awilix let you create an explicit scope per request.
94
- Returning a Promise and automatically awaiting dependencies are also different capabilities.
95
- See [composition, optional/multiple resolution and tooling](./docs/choosing-di.md#composition-and-tooling)
96
- for the rest of the feature comparison.
60
+ **27 features across eight libraries.** Reviewed **2026-09-11**, against the npm `latest`
61
+ versions shown below and official documentation; Katagami's column describes this release, 4.0.0. [Versions, sources and detailed notes](./docs/choosing-di.md#comparison-sources).
62
+
63
+ **✅ Built-in support · ⚠️ Conditions, a different model or application composition · ➖ No built-in support for this specific capability.**
64
+ Short labels identify the actual API or limitation.
65
+
66
+ #### Type safety and setup
67
+
68
+ | Feature | **Katagami**<br>**4.0.0** | InversifyJS<br>8.2.3 | tsyringe<br>4.10.0 | TypeDI<br>0.10.0 | Awilix<br>13.0.5 | NestJS<br>12.0.1 | Effect<br>3.22.2 | typed-inject<br>5.0.0 |
69
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
70
+ | **Runtime requirements** | **✅ Standard TypeScript** | ⚠️ Reflect metadata for class DI | ⚠️ Reflect metadata for class DI | ⚠️ Reflect metadata setup | No DI metadata | ⚠️ Nest modules / metadata | ✅ Effect / Layer APIs | ✅ Standard TypeScript |
71
+ | **Injection style** | **Explicit factories / constructors** | Constructor / property / factory | Constructor / factory | Constructor / property / factory | Proxy / classic / factory | Constructor / property / factory | Functional services / layers | Constructor / factory + `inject` |
72
+ | **Token types** | **Class / string / number / symbol** | Class / string / symbol | Class / string / symbol | Class / string / `Token<T>` | String / symbol | Class / string / symbol | `Context.Tag` | String literals |
73
+ | **Type safety** | **✅ Inferred services + scope checks** | ✅ Typed identifiers / bindings | ✅ Class / generic types | Class / `Token<T>` | Inferred cradle | Typed providers | Typed service requirements | Tokens + `inject` tuples |
74
+ | **Registration-derived types** | **✅ Accumulated tokens** | | | | `register` cradle | | ⚠️ Layer requirements | Accumulated tokens |
75
+ | **Missing required tokens: compile-time check¹** | **✅ Literal / unique-symbol keys** | ➖ Runtime check | ➖ Runtime check | ➖ Runtime check | ⚠️ Cradle only | ➖ Runtime graph | ✅ Unsatisfied requirements | ✅ Literal keys |
76
+ | **Scoped access from singleton/transient factories: compile-time check¹** | **✅ Scoped tokens excluded** | ➖ | ➖ | ➖ | ⚠️ Runtime strict mode | ⚠️ Request-scope propagation | ⚠️ Different `Scope` model | ➖ No scoped lifetime |
77
+ | **Zero runtime dependency packages²** | **✅** | | ➖ | ⚠️ Reflect polyfill installed separately | ⚠️ Browser entry differs | ➖ | ➖ | ✅ |
78
+ | **Tree-shaking support²** | **✅ ESM / subpaths / `sideEffects: false`** | ⚠️ ESM; `sideEffects: true` | ⚠️ ESM build | ✅ ESM / `sideEffects: false` | ⚠️ ESM / browser builds | ⚠️ ESM / framework setup | ✅ ESM / subpaths / side-effect declaration | ⚠️ ESM build |
79
+
80
+ #### Lifetimes, async services and cleanup
81
+
82
+ | Feature | **Katagami**<br>**4.0.0** | InversifyJS<br>8.2.3 | tsyringe<br>4.10.0 | TypeDI<br>0.10.0 | Awilix<br>13.0.5 | NestJS<br>12.0.1 | Effect<br>3.22.2 | typed-inject<br>5.0.0 |
83
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
84
+ | **Lifetimes** | **✅ Singleton / Transient / Scoped** | Singleton / Transient / Request | Singleton / Transient / Resolution / Container | ✅ Shared / Transient | ✅ Singleton / Transient / Scoped | ✅ Singleton / Transient / Request | ⚠️ Memoized / fresh layers + scopes | ✅ Singleton / Transient |
85
+ | **Request / scoped lifetime³** | **✅ Explicit per-request scope** | ⚠️ One resolution graph | ✅ Container / resolution scoped | ⚠️ Named containers | ✅ Explicit per-request scope | HTTP request scope | ⚠️ Resource scopes | ⚠️ Child injectors; no Scoped provider |
86
+ | **Child containers / nested scopes³** | **✅ Nested scopes** | Container hierarchy | ✅ Child containers | ⚠️ Named containers | Child scopes | ⚠️ Module / request contexts | ⚠️ Nested resource scopes | Child injectors |
87
+ | **Async factories** | **✅ Promise-valued factories** | ✅ Async bindings | ✅ Promise-valued factories | Promise-valued services | ✅ Promise-valued factories | Async providers | Effectful acquisition | ✅ Promise-valued factories |
88
+ | **Async result type tracking** | **✅ Inferred `Promise<T>`** | `getAsync<T>` | ⚠️ Promise-valued service type | ⚠️ Promise-valued service type | Inferred `Promise<T>` | ⚠️ Provider / consumer types | ✅ Effect result / error / requirements | ✅ Inferred `Promise<T>` |
89
+ | **Automatically await async dependencies⁴** | **➖ Explicit `await`** | `getAsync` / `getAllAsync` | Consumer awaits | Consumer awaits | Consumer awaits | ✅ Before consumer construction | ✅ Effect composition | ➖ Consumer awaits |
90
+ | **Resource cleanup⁵** | **✅ Disposal symbols / `await using`** | ⚠️ Singleton deactivation | Constructed disposables | ⚠️ `destroy()` on reset / removal | ⚠️ Cached values + disposer | ⚠️ App lifecycle hooks | ✅ Scope finalizers | ✅ Owned disposable instances |
91
+ | **Await asynchronous cleanup⁵** | **✅ `Symbol.asyncDispose`** | ✅ Async deactivation | ✅ `container.dispose()` | ➖ `destroy()` is not awaited | ✅ `container.dispose()` | ⚠️ App hooks; not request-scoped classes | ✅ Effect finalizers | ✅ `injector.dispose()` |
92
+
93
+ #### Composition and advanced features
94
+
95
+ | Feature | **Katagami**<br>**4.0.0** | InversifyJS<br>8.2.3 | tsyringe<br>4.10.0 | TypeDI<br>0.10.0 | Awilix<br>13.0.5 | NestJS<br>12.0.1 | Effect<br>3.22.2 | typed-inject<br>5.0.0 |
96
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
97
+ | **Optional resolution** | **✅ `tryResolve` / `tryResolveAll`** | ✅ Optional get / inject | ✅ Optional injection | ⚠️ `has` then `get` | ✅ `allowUnregistered` | ✅ Optional injection | ✅ `serviceOption` | ⚠️ Compose optional values |
98
+ | **Multi-binding** | **✅ `resolveAll`** | ✅ `getAll` / `getAllAsync` | ✅ `injectAll` / `resolveAll` | ✅ `getMany` | ⚠️ Collection-valued service | ⚠️ Array provider | ⚠️ Collection-valued service | ⚠️ Collection-valued service |
99
+ | **Lazy resolution⁶** | **✅ `lazy()`; sync class tokens** | ⚠️ Deferred identifiers / factories | ✅ `delay()` proxy | ⚠️ Deferred type reference | ⚠️ Cradle property access | ⚠️ `LazyModuleLoader` | ⚠️ Lazy effect execution | ⚠️ Inject a factory |
100
+ | **Conditional bindings** | **⚠️ Tokens / factory logic** | ✅ Contextual constraints | ✅ Predicate-aware factory | ⚠️ Factory logic | ⚠️ Local injection / factory logic | ⚠️ Dynamic modules / factories | ⚠️ Select / compose layers | ⚠️ Factory logic |
101
+ | **Auto-loading / discovery⁶** | **➖ Explicit `use()`** | ⚠️ Class autobinding | ➖ Explicit registrations | ➖ Explicit imports | ✅ `loadModules` (Node) | ⚠️ `DiscoveryService` | ➖ Explicit layers | ➖ Explicit providers |
102
+ | **Module system / composition** | **✅ `use()`** | ✅ Container modules | ✅ `@registry` | ⚠️ Group registrations | ✅ `loadModules` / `register` | ✅ Modules / dynamic modules | ✅ Layer composition | ⚠️ Compose provider chains |
103
+ | **Circular dependency detection⁷** | **✅ Runtime cycle path** | ✅ Runtime detection | ⚠️ Constructor error / `delay` | ⚠️ Deferred type references | ✅ Runtime cycle path | ⚠️ Cycle errors / `forwardRef` | ⚠️ Typed Layer requirements | ⚠️ Registration order constrains dependencies |
104
+ | **Middleware / interceptors⁶** | **⚠️ `beforeResolve` checks / higher-order factories** | ✅ Activation / deactivation hooks | ✅ Before / after resolution | ⚠️ Factory wrappers | ⚠️ Factory wrappers | ⚠️ Request interceptors, not DI hooks | ⚠️ Effect composition | ⚠️ Provider decoration |
105
+ | **Snapshot / restore⁶** | **➖** | ✅ `snapshot` / `restore` | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ |
106
+ | **Test substitution / isolation** | **✅ Fresh scopes / containers + `use()`** | ✅ Rebind / snapshots | ✅ Child container overrides | ✅ Named containers / reset | ✅ Child scopes / overrides | ✅ `overrideProvider` | ✅ Substitute test layers | ✅ Child injector overrides |
107
+
108
+ **What stands out:** Katagami combines **accumulated registration types, scope-filtered factory
109
+ resolvers, three lifetimes and zero runtime dependencies** with direct `r.resolve(token)` calls.
110
+ Optional/multiple resolution, module composition, lazy class resolution and standards-based cleanup
111
+ stay available without decorator setup. Awilix, Effect and typed-inject also provide meaningful
112
+ compile-time checks, as shown above.
113
+
114
+ <details>
115
+ <summary>Comparison notes: type guarantees, scopes, async behavior and feature boundaries</summary>
116
+
117
+ 1. **Type guarantees:** Katagami's missing-token guarantee applies to accumulated literal keys and
118
+ unique symbols with their types preserved. Class tokens, predeclared maps and mutable aliases
119
+ have [documented boundaries](./docs/type-safety.md). Awilix rejects unknown cradle properties,
120
+ but its broad `resolve` overload accepts unknown names. Effect checks service requirements in
121
+ its own model. Scope checks here mean excluding scoped tokens from singleton/transient resolvers.
122
+ 2. **Setup and bundles:** Metadata notes describe the documented class-injection path; explicit
123
+ value/factory bindings can avoid decorating individual services. Core Katagami needs no polyfills;
124
+ disposal has [host/compiler requirements](./docs/guide.md#compatibility). ESM and side-effect
125
+ declarations help tree shaking, but these are packaging comparisons, not measured bundle sizes.
126
+ 3. **Scopes:** InversifyJS Request means one resolution graph, not an HTTP request. Named containers,
127
+ module contexts, child injectors and Effect resource scopes are not identical lifetime policies.
128
+ 4. **Async:** Returning a Promise is distinct from awaiting dependencies before injection.
129
+ Katagami keeps the Promise in the inferred type and leaves `await` explicit.
130
+ 5. **Cleanup:** Katagami's opt-in `disposable()` integrates `Symbol.dispose`, `Symbol.asyncDispose`
131
+ and `await using`. Ownership varies by library; InversifyJS deactivation is for singletons,
132
+ Awilix disposers are for cached values, and Nest hooks exclude request-scoped classes.
133
+ 6. **Composition versus dedicated APIs:** A service proxy, deferred token and lazy module are
134
+ different features. Autobinding/discovery is not filesystem loading. Katagami's `use()` copies
135
+ registrations; containers are mutable. Factory wrappers are not interceptor APIs, `beforeResolve`
136
+ can reject a resolution but not replace its result, and fresh containers are not snapshots. [Composition details](./docs/choosing-di.md#composition-and-tooling).
137
+ 7. **Cycles:** Runtime cycle detection, deferred references and static dependency requirements are
138
+ different mechanisms. A ⚠️ entry does not promise a general cycle detector; runtime checks do
139
+ not imply detection of every asynchronous deadlock.
140
+
141
+ </details>
97
142
 
98
143
  ## Why Katagami for AI-assisted development?
99
144
 
@@ -145,6 +190,7 @@ describes how to test them.
145
190
  | Class, string, number and symbol tokens | Choose the token style that fits your application |
146
191
  | Optional and multiple resolution | Use `tryResolve`, `resolveAll` and `tryResolveAll` |
147
192
  | Resource cleanup and lazy resolution | Opt in through `katagami/disposable` and `katagami/lazy` |
193
+ | Registration policies and operations | Share one `policy` across modules, require registration metadata and check what resolutions and operations return |
148
194
  | ESM, CommonJS and zero runtime dependencies | Use standard tooling without decorator metadata setup |
149
195
 
150
196
  Use Katagami when dependency wiring, test substitution or request lifetimes need structure.
@@ -154,6 +200,7 @@ See [choosing a DI approach](./docs/choosing-di.md) for trade-offs and links to
154
200
  ## Documentation
155
201
 
156
202
  - [Usage guide and API](./docs/guide.md): lifetimes, composition, classes, async factories, cleanup and lazy resolution.
203
+ - [Registration policies and operations](./docs/registration-policies.md): a shared `policy`, required metadata, return checks and operations scopes.
157
204
  - [Type guarantees](./docs/type-safety.md): accumulated tokens, interface maps and structural class typing.
158
205
  - [AI coding guide](./docs/ai-coding-agents.md): workflow, diagnostics and a prompt to use in your project.
159
206
  - [Request-scope starter](./examples/request-scope/README.md): concurrent requests, fake repositories and cleanup.
@@ -0,0 +1,71 @@
1
+ // src/internal.ts
2
+ var INTERNALS = /* @__PURE__ */ Symbol.for("katagami.internals.v4");
3
+ var CREATIONS = /* @__PURE__ */ Symbol.for("katagami.creations.v4");
4
+ var shared = globalThis;
5
+ if (!shared[CREATIONS]) {
6
+ Object.defineProperty(shared, CREATIONS, { value: /* @__PURE__ */ new WeakMap() });
7
+ }
8
+ var creations = shared[CREATIONS];
9
+ function settle(creation) {
10
+ return creation.then(
11
+ (value) => ({ failed: false, value }),
12
+ (error) => ({ error, failed: true })
13
+ );
14
+ }
15
+ var CONSTRUCTING = /* @__PURE__ */ Symbol.for("katagami.constructing.v4");
16
+ var running = globalThis;
17
+ if (!running[CONSTRUCTING]) {
18
+ Object.defineProperty(running, CONSTRUCTING, { value: [] });
19
+ }
20
+ var constructing = running[CONSTRUCTING];
21
+
22
+ // src/disposable/index.ts
23
+ function disposable(container) {
24
+ const asyncDispose = async () => {
25
+ const internals = container[INTERNALS];
26
+ if (internals.isDisposed()) {
27
+ return;
28
+ }
29
+ internals.markDisposed();
30
+ const instances = [...internals.ownCache.values()].reverse();
31
+ const errors = [];
32
+ for (const instance of instances) {
33
+ let resolved = instance;
34
+ if (instance instanceof Promise) {
35
+ const creation = await (creations.get(instance) ?? settle(instance));
36
+ if (creation.failed) {
37
+ continue;
38
+ }
39
+ resolved = creation.value;
40
+ }
41
+ try {
42
+ if (resolved != null && typeof resolved === "object") {
43
+ if (Symbol.asyncDispose in resolved) {
44
+ await resolved[Symbol.asyncDispose]();
45
+ } else if (Symbol.dispose in resolved) {
46
+ resolved[Symbol.dispose]();
47
+ }
48
+ }
49
+ } catch (error) {
50
+ errors.push(error);
51
+ }
52
+ }
53
+ internals.ownCache.clear();
54
+ if (errors.length > 0) {
55
+ throw new AggregateError(errors, "One or more errors occurred during disposal.");
56
+ }
57
+ };
58
+ Object.defineProperty(container, Symbol.asyncDispose, {
59
+ configurable: true,
60
+ value: asyncDispose
61
+ });
62
+ return container;
63
+ }
64
+
65
+ export {
66
+ INTERNALS,
67
+ creations,
68
+ settle,
69
+ constructing,
70
+ disposable
71
+ };
@@ -1,5 +1,84 @@
1
- import { type ContainerInternals, INTERNALS } from '../internal.cjs';
1
+ import { type Callable, type EntrypointFactory } from '../entrypoint/index.cjs';
2
+ import { type ContainerInternals, INTERNALS, type REGISTRATION_STATE } from '../internal.cjs';
3
+ import { type AnyMetadataEntry, type AnyMetadataKey, type MetadataKeyOf, type MetadataReader, type RegistrationArguments } from '../metadata/index.cjs';
2
4
  import type { AbstractConstructor, Resolver } from '../resolver/index.cjs';
5
+ import { type ContainerPolicy, type RequiredMetadata } from './policy.cjs';
6
+ export type RegisteredTokens<C> = C extends {
7
+ readonly [REGISTRATION_STATE]: infer S;
8
+ } ? S extends {
9
+ readonly untracked: true;
10
+ } ? never : S extends {
11
+ readonly token: infer K;
12
+ } ? K : never : never;
13
+ export interface RegistrationState {
14
+ readonly token: unknown;
15
+ readonly metadata: AnyMetadataKey;
16
+ readonly callable: Callable | never;
17
+ }
18
+ type State<K, E extends readonly AnyMetadataEntry[], F = never> = {
19
+ readonly token: K;
20
+ readonly metadata: MetadataKeyOf<E[number]>;
21
+ readonly callable: F;
22
+ };
23
+ type Replace<S, K, N> = (unknown extends S ? UntrackedState : Exclude<S, {
24
+ readonly token: K;
25
+ }>) | N;
26
+ type UntrackedState<K = unknown> = {
27
+ readonly token: K;
28
+ readonly metadata: never;
29
+ readonly callable: never;
30
+ readonly untracked: true;
31
+ };
32
+ type SourceState<S, K> = unknown extends S ? UntrackedState<K> : S extends {
33
+ readonly untracked: true;
34
+ } ? UntrackedState<K> : S;
35
+ type SourceTokens<S, K> = unknown extends S ? K : S extends {
36
+ readonly untracked: true;
37
+ } ? K : S extends {
38
+ readonly token: infer Token;
39
+ } ? Token : never;
40
+ type Append<S, K, E extends readonly AnyMetadataEntry[], F = never> = unknown extends S ? [MetadataKeyOf<E[number]> | F] extends [never] ? unknown : UntrackedState | State<K, E, F> : Replace<S, K, {
41
+ readonly token: K;
42
+ readonly metadata: [Extract<S, {
43
+ readonly token: K;
44
+ }>] extends [never] ? MetadataKeyOf<E[number]> : Extract<Extract<S, {
45
+ readonly token: K;
46
+ }> extends {
47
+ readonly metadata: infer M;
48
+ } ? M : never, MetadataKeyOf<E[number]>>;
49
+ readonly callable: F;
50
+ }>;
51
+ type TrackedState<S> = S extends {
52
+ readonly metadata: infer M;
53
+ readonly callable: infer F;
54
+ } ? [M | F] extends [never] ? never : S : never;
55
+ type Merge<S, K, N> = unknown extends S ? [TrackedState<N>] extends [never] ? unknown : Replace<S, K, N> : Replace<S, K, N>;
56
+ type MissingMetadata<R, S> = unknown extends S ? R : S extends {
57
+ readonly metadata: infer M;
58
+ } ? Exclude<R, M> : never;
59
+ /**
60
+ * Create a new DI container that follows a shared policy.
61
+ *
62
+ * Containers created with the same policy object share its settings and the origins it observes.
63
+ * Every registration must carry the policy's `requiredMetadata`, and `use()` accepts only modules
64
+ * with the same policy or without one. Declare the policy with `satisfies ContainerPolicy` to keep
65
+ * its concrete types.
66
+ *
67
+ * @param options `policy`: the shared policy object
68
+ */
69
+ export declare function createContainer<const P extends ContainerPolicy>(options: {
70
+ readonly policy: P;
71
+ }): Container<Record<never, never>, never, never, Record<never, never>, never, never, RequiredMetadata<P>, never>;
72
+ /**
73
+ * Create a new DI container that follows a shared policy, with predeclared token type maps.
74
+ *
75
+ * @template T PropertyKey-based token type map (defined via interface, order-independent)
76
+ * @template ScopedT PropertyKey-based token type map for scoped registrations
77
+ * @param options `policy`: the shared policy object
78
+ */
79
+ export declare function createContainer<T, ScopedT, const P extends ContainerPolicy>(options: {
80
+ readonly policy: P;
81
+ }): Container<T, never, never, ScopedT, never, never, RequiredMetadata<P>, never>;
3
82
  /**
4
83
  * Create a new DI container.
5
84
  *
@@ -14,7 +93,9 @@ import type { AbstractConstructor, Resolver } from '../resolver/index.cjs';
14
93
  * .registerTransient(GenerateTextUseCase, r => new GenerateTextUseCase(r.resolve(TextGenerationService)));
15
94
  * ```
16
95
  */
17
- export declare function createContainer<T = Record<never, never>, ScopedT = Record<never, never>>(): Container<T, never, never, ScopedT>;
96
+ export declare function createContainer<T = Record<never, never>, ScopedT = Record<never, never>>(): Container<T, never, never, ScopedT, never, never, readonly [], [
97
+ keyof T | keyof ScopedT
98
+ ] extends [never] ? never : unknown>;
18
99
  /**
19
100
  * Lightweight DI container — registration only.
20
101
  *
@@ -29,33 +110,43 @@ export declare function createContainer<T = Record<never, never>, ScopedT = Reco
29
110
  * @template ScopedT PropertyKey-based token type map for scoped registrations
30
111
  * @template ScopedSync Union of scoped sync class constructors (accumulated via chaining, order-dependent)
31
112
  * @template ScopedAsync Union of scoped async class constructors (accumulated via chaining, order-dependent)
113
+ * @template Required Metadata keys the container's policy requires on every registration
114
+ * @template Registrations Registration state that tracks metadata and operations per token
32
115
  */
33
- export declare class Container<T = Record<never, never>, Sync extends AbstractConstructor = never, Async extends AbstractConstructor = never, ScopedT = Record<never, never>, ScopedSync extends AbstractConstructor = never, ScopedAsync extends AbstractConstructor = never> {
116
+ export declare class Container<T = Record<never, never>, Sync extends AbstractConstructor = never, Async extends AbstractConstructor = never, ScopedT = Record<never, never>, ScopedSync extends AbstractConstructor = never, ScopedAsync extends AbstractConstructor = never, Required extends readonly AnyMetadataKey[] = readonly [], Registrations = unknown> {
34
117
  private readonly registrations;
35
118
  private readonly singletonCache;
119
+ private readonly requiredMetadata;
120
+ private readonly policy;
36
121
  private disposed;
37
- /**
38
- * Internal state accessor for extension modules (scope, disposable).
39
- *
40
- * @internal
41
- */
42
- readonly [INTERNALS]: ContainerInternals;
43
- constructor();
122
+ readonly [REGISTRATION_STATE]: Registrations;
123
+ readonly [INTERNALS]: ContainerInternals & {
124
+ readonly kind: 'container';
125
+ };
126
+ constructor(options?: {
127
+ readonly policy: ContainerPolicy<Required>;
128
+ });
44
129
  /**
45
130
  * Register a factory function as a singleton for the given token.
46
131
  *
47
132
  * Creates the instance on the first resolve and returns the cached value thereafter.
48
133
  * If the same token is registered multiple times, all factories are accumulated.
49
134
  * `resolve()` returns the last registered instance; `resolveAll()` returns all.
135
+ * Register request-dependent work as scoped instead.
50
136
  *
51
137
  * @param token Any value to use as a token
52
- * @param factory Factory function that receives a resolver and returns an instance
138
+ * @param factory Factory function that receives a resolver and returns an instance. Wrap it with
139
+ * `entrypoint()` to expose the function it returns as an operation.
140
+ * @param options `metadata`: entries created with metadata keys. Required when the policy declares
141
+ * `requiredMetadata`.
53
142
  * @returns The container for method chaining
54
143
  */
55
- registerSingleton<V>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => Promise<V>): Container<T, Sync, Async | AbstractConstructor<V>, ScopedT, ScopedSync, ScopedAsync>;
56
- registerSingleton<V>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => V): Container<T, Sync | AbstractConstructor<V>, Async, ScopedT, ScopedSync, ScopedAsync>;
57
- registerSingleton<K extends PropertyKey, V>(token: K, factory: (resolver: Resolver<T, Sync, Async>) => V): Container<Record<K, V> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
58
- registerSingleton<V>(token: unknown, factory: (resolver: Resolver<T, Sync, Async>) => V): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
144
+ registerSingleton<K extends PropertyKey, F extends Callable, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: ((resolver: Resolver<T, Sync, Async>) => F) & EntrypointFactory<Resolver<T, Sync, Async>, F>, ...options: RegistrationArguments<Required, E>): Container<Record<K, F> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E, F>>;
145
+ registerSingleton<K extends PropertyKey, F extends Callable, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: EntrypointFactory<Resolver<T, Sync, Async>, F>, ...options: RegistrationArguments<Required, E>): Container<Record<K, Promise<F>> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E, F>>;
146
+ registerSingleton<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => Promise<V>, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async | AbstractConstructor<V>, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, AbstractConstructor<V>, E>>;
147
+ registerSingleton<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => V, ...options: RegistrationArguments<Required, E>): Container<T, Sync | AbstractConstructor<V>, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, AbstractConstructor<V>, E>>;
148
+ registerSingleton<K extends PropertyKey, V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: (resolver: Resolver<T, Sync, Async>) => V, ...options: RegistrationArguments<Required, E>): Container<Record<K, V> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E>>;
149
+ registerSingleton<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: unknown, factory: (resolver: Resolver<T, Sync, Async>) => V, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Registrations | State<unknown, E>>;
59
150
  /**
60
151
  * Register a factory function as transient for the given token.
61
152
  *
@@ -64,13 +155,18 @@ export declare class Container<T = Record<never, never>, Sync extends AbstractCo
64
155
  * `resolve()` returns the last registered instance; `resolveAll()` returns all.
65
156
  *
66
157
  * @param token Any value to use as a token
67
- * @param factory Factory function that receives a resolver and returns an instance
158
+ * @param factory Factory function that receives a resolver and returns an instance. Wrap it with
159
+ * `entrypoint()` to expose the function it returns as an operation.
160
+ * @param options `metadata`: entries created with metadata keys. Required when the policy declares
161
+ * `requiredMetadata`.
68
162
  * @returns The container for method chaining
69
163
  */
70
- registerTransient<V>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => Promise<V>): Container<T, Sync, Async | AbstractConstructor<V>, ScopedT, ScopedSync, ScopedAsync>;
71
- registerTransient<V>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => V): Container<T, Sync | AbstractConstructor<V>, Async, ScopedT, ScopedSync, ScopedAsync>;
72
- registerTransient<K extends PropertyKey, V>(token: K, factory: (resolver: Resolver<T, Sync, Async>) => V): Container<Record<K, V> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
73
- registerTransient<V>(token: unknown, factory: (resolver: Resolver<T, Sync, Async>) => V): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
164
+ registerTransient<K extends PropertyKey, F extends Callable, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: ((resolver: Resolver<T, Sync, Async>) => F) & EntrypointFactory<Resolver<T, Sync, Async>, F>, ...options: RegistrationArguments<Required, E>): Container<Record<K, F> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E, F>>;
165
+ registerTransient<K extends PropertyKey, F extends Callable, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: EntrypointFactory<Resolver<T, Sync, Async>, F>, ...options: RegistrationArguments<Required, E>): Container<Record<K, Promise<F>> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E, F>>;
166
+ registerTransient<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => Promise<V>, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async | AbstractConstructor<V>, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, AbstractConstructor<V>, E>>;
167
+ registerTransient<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T, Sync, Async>) => V, ...options: RegistrationArguments<Required, E>): Container<T, Sync | AbstractConstructor<V>, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, AbstractConstructor<V>, E>>;
168
+ registerTransient<K extends PropertyKey, V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: (resolver: Resolver<T, Sync, Async>) => V, ...options: RegistrationArguments<Required, E>): Container<Record<K, V> & T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E>>;
169
+ registerTransient<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: unknown, factory: (resolver: Resolver<T, Sync, Async>) => V, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Registrations | State<unknown, E>>;
74
170
  /**
75
171
  * Register a factory function as scoped for the given token.
76
172
  *
@@ -79,30 +175,41 @@ export declare class Container<T = Record<never, never>, Sync extends AbstractCo
79
175
  * Scoped tokens cannot be resolved from the root container — use createScope() first.
80
176
  *
81
177
  * @param token Any value to use as a token
82
- * @param factory Factory function that receives a resolver and returns an instance
178
+ * @param factory Factory function that receives a resolver and returns an instance. Wrap it with
179
+ * `entrypoint()` to expose the function it returns as an operation.
180
+ * @param options `metadata`: entries created with metadata keys. Required when the policy declares
181
+ * `requiredMetadata`.
83
182
  * @returns The container for method chaining
84
183
  */
85
- registerScoped<V>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => Promise<V>): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync | AbstractConstructor<V>>;
86
- registerScoped<V>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V): Container<T, Sync, Async, ScopedT, ScopedSync | AbstractConstructor<V>, ScopedAsync>;
87
- registerScoped<K extends PropertyKey, V>(token: K, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V): Container<T, Sync, Async, Record<K, V> & ScopedT, ScopedSync, ScopedAsync>;
88
- registerScoped<V>(token: unknown, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
184
+ registerScoped<K extends PropertyKey, F extends Callable, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: ((resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => F) & EntrypointFactory<Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>, F>, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, Record<K, F> & ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E, F>>;
185
+ registerScoped<K extends PropertyKey, F extends Callable, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: EntrypointFactory<Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>, F>, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, Record<K, Promise<F>> & ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E, F>>;
186
+ registerScoped<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => Promise<V>, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync | AbstractConstructor<V>, Required, Append<Registrations, AbstractConstructor<V>, E>>;
187
+ registerScoped<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, ScopedT, ScopedSync | AbstractConstructor<V>, ScopedAsync, Required, Append<Registrations, AbstractConstructor<V>, E>>;
188
+ registerScoped<K extends PropertyKey, V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: K, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, Record<K, V> & ScopedT, ScopedSync, ScopedAsync, Required, Append<Registrations, K, E>>;
189
+ registerScoped<V, const E extends readonly AnyMetadataEntry[] = readonly []>(token: unknown, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V, ...options: RegistrationArguments<Required, E>): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync, Required, Registrations | State<unknown, E>>;
190
+ /**
191
+ * Read the metadata of the last registration for a token without creating the instance.
192
+ *
193
+ * @param token A registered token
194
+ * @returns A read-only metadata reader
195
+ * @throws ContainerError if the token is not registered
196
+ */
197
+ getMetadata<V>(token: AbstractConstructor<V> & (Sync | Async | ScopedSync | ScopedAsync)): MetadataReader;
198
+ getMetadata<K extends keyof (T & ScopedT)>(token: K): MetadataReader;
89
199
  /**
90
200
  * Apply all registrations from another container (module) to this container.
91
201
  *
92
- * Copies registration entries (factory + lifetime) by replacing existing entries for each token.
202
+ * Copies registration entries by replacing existing entries for each token.
93
203
  * Singleton instance caches are not shared — each container manages its own.
204
+ * A module with a policy must use this container's policy, and every copied registration must carry
205
+ * the metadata this container's policy requires; otherwise nothing is copied.
94
206
  *
95
207
  * @param source A container whose registrations will be copied into this container
96
208
  * @returns The container for method chaining
97
209
  */
98
- use<MT, MSync extends AbstractConstructor, MAsync extends AbstractConstructor, MScopedT, MScopedSync extends AbstractConstructor, MScopedAsync extends AbstractConstructor>(source: Container<MT, MSync, MAsync, MScopedT, MScopedSync, MScopedAsync>): Container<T & MT, Sync | MSync, Async | MAsync, ScopedT & MScopedT, ScopedSync | MScopedSync, ScopedAsync | MScopedAsync>;
99
- /**
100
- * Add a registration entry. Accumulates registrations for the same token.
101
- *
102
- * @param token Token
103
- * @param factory Factory function
104
- * @param lifetime Lifetime of the registration
105
- * @returns The container for method chaining
106
- */
210
+ use<MT, MS extends AbstractConstructor, MA extends AbstractConstructor, MST, MSS extends AbstractConstructor, MSA extends AbstractConstructor, MR extends readonly AnyMetadataKey[], State>(source: Container<MT, MS, MA, MST, MSS, MSA, MR, State> & ([MissingMetadata<Required[number], State>] extends [never] ? unknown : {
211
+ readonly missingRequiredMetadata: never;
212
+ })): Container<T & MT, Sync | MS, Async | MA, ScopedT & MST, ScopedSync | MSS, ScopedAsync | MSA, Required, Merge<Registrations, SourceTokens<State, keyof MT | keyof MST | MS | MA | MSS | MSA>, SourceState<State, keyof MT | keyof MST | MS | MA | MSS | MSA>>>;
107
213
  private addRegistration;
108
214
  }
215
+ export {};