katagami 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -582
- package/dist/chunk-J2NYR3SH.js +6 -0
- package/dist/container/index.d.cts +108 -0
- package/dist/container/index.d.ts +2 -2
- package/dist/disposable/index.cjs +16 -25
- package/dist/disposable/index.d.cts +69 -0
- package/dist/disposable/index.d.ts +13 -5
- package/dist/disposable/index.js +1 -1
- package/dist/error/index.d.cts +11 -0
- package/dist/index.cjs +91 -55
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +76 -31
- package/dist/internal.d.cts +29 -0
- package/dist/internal.d.ts +3 -1
- package/dist/lazy/index.cjs +16 -25
- package/dist/lazy/index.d.cts +33 -0
- package/dist/lazy/index.d.ts +3 -3
- package/dist/lazy/index.js +1 -1
- package/dist/resolver/index.d.cts +93 -0
- package/dist/scope/index.d.cts +120 -0
- package/dist/scope/index.d.ts +4 -4
- package/docs/README.de.md +65 -0
- package/docs/README.es.md +65 -0
- package/docs/README.fr.md +65 -0
- package/docs/README.ja.md +66 -0
- package/docs/README.ko.md +65 -0
- package/docs/README.zh-CN.md +65 -0
- package/docs/README.zh-TW.md +65 -0
- package/docs/ai-coding-agents.md +78 -0
- package/docs/articles/ai-coding-agents.ja.md +83 -0
- package/docs/articles/ai-coding-agents.md +70 -0
- package/docs/articles/request-scope.md +48 -0
- package/docs/articles/without-decorators.md +54 -0
- package/docs/choosing-di.md +30 -0
- package/docs/growth/baseline-2026-09-11.json +68 -0
- package/docs/growth/github-metadata.json +13 -0
- package/docs/growth/rollout.md +77 -0
- package/docs/guide.md +186 -0
- package/docs/type-safety.md +126 -0
- package/examples/request-scope/README.md +37 -0
- package/examples/request-scope/app.ts +31 -0
- package/examples/request-scope/demo.ts +10 -0
- package/examples/request-scope/tsconfig.json +11 -0
- package/llms.txt +16 -0
- package/package.json +56 -23
- package/dist/index-jx8b52m0.js +0 -4
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Type-safe dependency injection for AI coding agents
|
|
2
|
+
|
|
3
|
+
AI-assisted development still needs a way to check the code it produces. For dependency wiring,
|
|
4
|
+
a useful loop is small: edit the composition root, run TypeScript, read the diagnostics, and repair
|
|
5
|
+
the missing dependency or incorrect lifetime. Katagami makes some of those wiring constraints visible
|
|
6
|
+
to the compiler by accumulating types as dependencies are registered.
|
|
7
|
+
|
|
8
|
+
## Let registration define the available dependencies
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createContainer, createScope } from 'katagami';
|
|
12
|
+
|
|
13
|
+
const container = createContainer()
|
|
14
|
+
.registerSingleton('users', () => ({ find: (id: string) => `user-${id}` }))
|
|
15
|
+
.registerScoped('handler', r => (id: string) => r.resolve('users').find(id));
|
|
16
|
+
|
|
17
|
+
createScope(container).resolve('handler')('42');
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
There is no manually maintained service interface in this example. The first registration adds
|
|
21
|
+
`users` to the type known by the next factory. The second adds `handler` to the scope's visible set.
|
|
22
|
+
Changing `r.resolve('users')` to `r.resolve('user')` produces a type error. Removing the first
|
|
23
|
+
registration also produces a type error at the consumer.
|
|
24
|
+
|
|
25
|
+
The check is useful for both people and coding agents: the factory's actual dependency is written
|
|
26
|
+
in the same code that TypeScript checks. The compiler can report a problem without starting the app.
|
|
27
|
+
|
|
28
|
+
## Turn a diagnostic into a repair
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createContainer } from 'katagami';
|
|
32
|
+
|
|
33
|
+
createContainer()
|
|
34
|
+
.registerScoped('requestId', () => crypto.randomUUID())
|
|
35
|
+
// @ts-expect-error — a singleton factory cannot access this scoped dependency
|
|
36
|
+
.registerSingleton('handler', r => r.resolve('requestId'));
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The diagnostic includes `No overload matches this call`. Inspect the failing resolution and its
|
|
40
|
+
lifetime: the factory requests state belonging to one request while asking to be shared as a singleton.
|
|
41
|
+
Register the handler as scoped, or pass the request ID into a shared service's method without storing it.
|
|
42
|
+
|
|
43
|
+
`@ts-expect-error` is only present to test this intentionally invalid example. It is not the repair.
|
|
44
|
+
In application code, change the wiring, run `npx tsc --noEmit`, then run behavior tests.
|
|
45
|
+
|
|
46
|
+
## Make the workflow usable
|
|
47
|
+
|
|
48
|
+
Give the agent the [v3 usage guide](../ai-coding-agents.md), the application's composition root and
|
|
49
|
+
its verification command. Have it use the supplied factory resolver, preserve inferred registration
|
|
50
|
+
types and run the checker after changes. A [runnable request-scope starter](../../examples/request-scope/README.md)
|
|
51
|
+
shows fake injection, concurrent calls and cleanup on both success and failure.
|
|
52
|
+
|
|
53
|
+
Ordinary constructor or function injection is still a useful baseline. It also gets TypeScript's
|
|
54
|
+
parameter checks and can be enough for small applications. Introduce a container when construction,
|
|
55
|
+
sharing, request scope or module composition is otherwise becoming repetitive.
|
|
56
|
+
|
|
57
|
+
## State the guarantee accurately
|
|
58
|
+
|
|
59
|
+
Accumulated literal keys and unique symbols let the checker reject tokens outside the visible
|
|
60
|
+
registered set. A predeclared `createContainer<Services>()` map instead permits forward references
|
|
61
|
+
and does not establish runtime registration completeness. Class tokens follow TypeScript's
|
|
62
|
+
structural typing, so another compatible class may pass the checker while being a different runtime key.
|
|
63
|
+
See [working examples of these boundaries](../type-safety.md).
|
|
64
|
+
|
|
65
|
+
The compiler cases in this article are tested. We have not measured agent repair success or token
|
|
66
|
+
savings. Those need [controlled repeated trials](https://github.com/hiroiku/katagami/tree/master/benchmarks/agent-wiring),
|
|
67
|
+
including failures and the same tasks, model settings and runtime tests across variants.
|
|
68
|
+
|
|
69
|
+
Install with `npm install katagami`, then start with the
|
|
70
|
+
[quick start](../../README.md#quick-start) or the [request-scope example](../../examples/request-scope/README.md).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Catch request-scope mistakes at compile time in TypeScript
|
|
2
|
+
|
|
3
|
+
A service shared by all requests should not retain state from just one request. A DI container
|
|
4
|
+
can make that mistake easier to express if every factory can resolve every dependency. Katagami's
|
|
5
|
+
typed singleton and transient factories receive a resolver that excludes scoped registrations.
|
|
6
|
+
|
|
7
|
+
## Reproduce the problem
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createContainer } from 'katagami';
|
|
11
|
+
|
|
12
|
+
createContainer()
|
|
13
|
+
.registerScoped('request', () => ({ id: crypto.randomUUID() }))
|
|
14
|
+
// @ts-expect-error — the request belongs to one scope, not a shared singleton
|
|
15
|
+
.registerSingleton('handler', r => ({ requestId: r.resolve('request').id }));
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The checker rejects the resolution of `request`. The fix depends on what the handler should do.
|
|
19
|
+
If it holds request state, give it a scoped lifetime:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createContainer, createScope } from 'katagami';
|
|
23
|
+
|
|
24
|
+
const container = createContainer()
|
|
25
|
+
.registerScoped('request', () => ({ id: crypto.randomUUID() }))
|
|
26
|
+
.registerScoped('handler', r => ({ requestId: r.resolve('request').id }));
|
|
27
|
+
|
|
28
|
+
const first = createScope(container);
|
|
29
|
+
const second = createScope(container);
|
|
30
|
+
first.resolve('handler').requestId !== second.resolve('handler').requestId; // true
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If the service can be stateless and shared, take request data as a method parameter instead.
|
|
34
|
+
Do not close over another request scope to get around the factory resolver.
|
|
35
|
+
|
|
36
|
+
## Test what types cannot establish
|
|
37
|
+
|
|
38
|
+
Type-check the wiring, then check that concurrent requests have distinct IDs, shared infrastructure
|
|
39
|
+
stays shared, and resources are cleaned on rejection as well as success. The
|
|
40
|
+
[request-scope starter](../../examples/request-scope/README.md) and its tests cover these cases.
|
|
41
|
+
|
|
42
|
+
The compile-time example uses accumulated literal keys. A broad type annotation, predeclared map,
|
|
43
|
+
compatible class token, assertion or captured external resolver can change what is checked.
|
|
44
|
+
The runtime captive-dependency guard also has limits around work resumed after an `await`.
|
|
45
|
+
The [type-safety guide](../type-safety.md) explains the exact scope of the guarantee.
|
|
46
|
+
|
|
47
|
+
Katagami provides Singleton, Transient and Scoped lifetimes without decorator metadata.
|
|
48
|
+
The [API guide](../guide.md) shows composition, async factories and optional disposal.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# TypeScript dependency injection without decorators
|
|
2
|
+
|
|
3
|
+
Dependency injection can be ordinary TypeScript: a factory receives a resolver and explicitly
|
|
4
|
+
constructs a service. Katagami uses this model and has no runtime package dependencies, decorator
|
|
5
|
+
compiler flags or reflect-metadata requirement.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install katagami
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Start with a factory
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createContainer, createScope } from 'katagami';
|
|
15
|
+
|
|
16
|
+
class Logger {
|
|
17
|
+
log(message: string) { console.log(message); }
|
|
18
|
+
}
|
|
19
|
+
class Greeting {
|
|
20
|
+
constructor(private logger: Logger) {}
|
|
21
|
+
say(name: string) { this.logger.log(`Hello, ${name}!`); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const container = createContainer()
|
|
25
|
+
.registerSingleton(Logger, () => new Logger())
|
|
26
|
+
.registerTransient(Greeting, r => new Greeting(r.resolve(Logger)));
|
|
27
|
+
|
|
28
|
+
createScope(container).resolve(Greeting).say('Ada');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The registration chain determines which class types the next factory can resolve. Class tokens
|
|
32
|
+
are convenient when the classes are structurally distinguishable. Literal keys or unique symbols
|
|
33
|
+
are also supported; see [token identity and type guarantees](../type-safety.md).
|
|
34
|
+
|
|
35
|
+
## Substitute infrastructure in a test
|
|
36
|
+
|
|
37
|
+
Factories can accept infrastructure as ordinary parameters. The
|
|
38
|
+
[request-scope starter](../../examples/request-scope/README.md) takes a `UserRepository` argument.
|
|
39
|
+
Its tests pass a small fake that returns a known name, then check the greeting. No global container
|
|
40
|
+
or decorator setup is required.
|
|
41
|
+
|
|
42
|
+
For larger compositions, group registrations in a container and copy them with `use()`.
|
|
43
|
+
Apply the fake module before resolving anything. `use()` replaces matching registration entries;
|
|
44
|
+
it is mutable composition, not automatic file loading or a snapshot API.
|
|
45
|
+
|
|
46
|
+
## Choose only the lifecycle features you need
|
|
47
|
+
|
|
48
|
+
Core imports come from `katagami`. Add cleanup from `katagami/disposable` or synchronous class-based
|
|
49
|
+
lazy resolution from `katagami/lazy` when needed. Those entry points are separate so bundlers can
|
|
50
|
+
omit unused implementations. Cleanup uses the host's disposal symbols; it does not provide a polyfill.
|
|
51
|
+
|
|
52
|
+
For a small application, manual constructor parameters may remain the simplest choice.
|
|
53
|
+
The [DI decision guide](../choosing-di.md) explains when accumulated registration types and request
|
|
54
|
+
scopes are useful and links to other containers' own documentation.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Choosing a TypeScript dependency injection approach
|
|
2
|
+
|
|
3
|
+
Choose based on the application's wiring and lifecycle needs. There is no measured claim here
|
|
4
|
+
that Katagami is faster, safer in every scenario, or more effective for every coding agent.
|
|
5
|
+
|
|
6
|
+
| Approach | Consider it when | What to account for |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| Ordinary constructor/function parameters | A small number of dependencies is easy to wire by hand | Your code owns construction, sharing and cleanup; TypeScript still checks parameter types |
|
|
9
|
+
| Katagami | You want explicit factories, accumulated registration types and request scopes | Preserve narrow token types; understand structural class identity and predeclared-map limits |
|
|
10
|
+
| Your framework's existing DI | The application already uses a framework container | Its conventions and lifecycle integrations may avoid maintaining a second container |
|
|
11
|
+
| Awilix | Its registration, injection and loading conventions fit your app | Read its own TypeScript and strict-mode documentation for guarantees and configuration |
|
|
12
|
+
| InversifyJS | Its class-oriented binding model and ecosystem fit your app | Follow its current setup instructions, including metadata requirements where applicable |
|
|
13
|
+
|
|
14
|
+
Katagami's concrete distinction is that the default registration chain accumulates a resolver's
|
|
15
|
+
visible token set. For literal keys or unique symbols, a missing token is rejected before execution.
|
|
16
|
+
That is a useful check for both human-written and AI-generated wiring. It does not establish that
|
|
17
|
+
other libraries lack type safety, and it does not eliminate runtime tests.
|
|
18
|
+
|
|
19
|
+
Start with the [runnable starter](../examples/request-scope/README.md),
|
|
20
|
+
[guarantee examples](./type-safety.md) and [composition guide](./guide.md#composition-and-test-substitution).
|
|
21
|
+
|
|
22
|
+
Primary references, reviewed 2026-09-11:
|
|
23
|
+
|
|
24
|
+
- [Awilix's maintained README](https://github.com/jeffijoe/awilix#readme), including TypeScript and strict mode.
|
|
25
|
+
- [InversifyJS getting started](https://inversify.io/docs/introduction/getting-started/).
|
|
26
|
+
- [TypeScript type compatibility](https://www.typescriptlang.org/docs/handbook/type-compatibility).
|
|
27
|
+
|
|
28
|
+
The previous broad feature matrix was replaced with this decision guide. Replacing registrations
|
|
29
|
+
with `use()` is explicit composition; Katagami does not implement automatic module discovery,
|
|
30
|
+
immutable containers or a dedicated snapshot/restore API.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"collectedAt": "2026-09-11T10:53:46.834Z",
|
|
3
|
+
"package": "katagami",
|
|
4
|
+
"npm": {
|
|
5
|
+
"week": {
|
|
6
|
+
"downloads": 2,
|
|
7
|
+
"start": "2026-09-03",
|
|
8
|
+
"end": "2026-09-09",
|
|
9
|
+
"package": "katagami"
|
|
10
|
+
},
|
|
11
|
+
"month": {
|
|
12
|
+
"downloads": 109,
|
|
13
|
+
"start": "2026-08-11",
|
|
14
|
+
"end": "2026-09-09",
|
|
15
|
+
"package": "katagami"
|
|
16
|
+
},
|
|
17
|
+
"nonOverlappingWeeks": [
|
|
18
|
+
{
|
|
19
|
+
"start": "2026-07-16",
|
|
20
|
+
"end": "2026-07-22",
|
|
21
|
+
"downloads": 2
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"start": "2026-07-23",
|
|
25
|
+
"end": "2026-07-29",
|
|
26
|
+
"downloads": 15
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"start": "2026-07-30",
|
|
30
|
+
"end": "2026-08-05",
|
|
31
|
+
"downloads": 8
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"start": "2026-08-06",
|
|
35
|
+
"end": "2026-08-12",
|
|
36
|
+
"downloads": 57
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"start": "2026-08-13",
|
|
40
|
+
"end": "2026-08-19",
|
|
41
|
+
"downloads": 48
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"start": "2026-08-20",
|
|
45
|
+
"end": "2026-08-26",
|
|
46
|
+
"downloads": 15
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"start": "2026-08-27",
|
|
50
|
+
"end": "2026-09-02",
|
|
51
|
+
"downloads": 14
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"start": "2026-09-03",
|
|
55
|
+
"end": "2026-09-09",
|
|
56
|
+
"downloads": 2
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"github": {
|
|
61
|
+
"stars": 4,
|
|
62
|
+
"forks": 0,
|
|
63
|
+
"topics": [],
|
|
64
|
+
"homepage": null,
|
|
65
|
+
"pushedAt": "2026-02-09T11:30:14Z"
|
|
66
|
+
},
|
|
67
|
+
"errors": []
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Type-safe dependency injection for TypeScript, with inferred types and scope checks for AI-assisted development. No decorators or reflect-metadata.",
|
|
3
|
+
"homepage": "https://github.com/hiroiku/katagami#readme",
|
|
4
|
+
"topics": [
|
|
5
|
+
"typescript",
|
|
6
|
+
"dependency-injection",
|
|
7
|
+
"di-container",
|
|
8
|
+
"type-safety",
|
|
9
|
+
"type-inference",
|
|
10
|
+
"ai-assisted-development",
|
|
11
|
+
"coding-agents"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Katagami discovery and adoption rollout
|
|
2
|
+
|
|
3
|
+
## Positioning and audience
|
|
4
|
+
|
|
5
|
+
Type-safe dependency injection for TypeScript, with compiler feedback for AI-assisted development.
|
|
6
|
+
The first audience is developers building a new TypeScript application with coding agents who need
|
|
7
|
+
explicit dependency wiring, fake injection or request state. Avoid forcing a container into tiny apps.
|
|
8
|
+
|
|
9
|
+
## Release assets
|
|
10
|
+
|
|
11
|
+
- English and seven localized README entry points.
|
|
12
|
+
- npm description/keywords and [GitHub metadata](./github-metadata.json).
|
|
13
|
+
- [AI guide](../ai-coding-agents.md), [type guarantees](../type-safety.md), [API guide](../guide.md).
|
|
14
|
+
- [Runnable starter](../../examples/request-scope/README.md) with checked examples.
|
|
15
|
+
- [AI coding article](../articles/ai-coding-agents.md), [request scopes](../articles/request-scope.md),
|
|
16
|
+
[decorator-free DI](../articles/without-decorators.md) and a [Japanese Zenn draft](../articles/ai-coding-agents.ja.md).
|
|
17
|
+
- [Evaluation protocol](../../benchmarks/agent-wiring/README.md) and results summarizer; agent benefits remain unmeasured.
|
|
18
|
+
|
|
19
|
+
## First four weeks
|
|
20
|
+
|
|
21
|
+
| Period | Action | Evidence to retain |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Week 1 | Ship checks, README, package metadata and starter; update GitHub Topics | Released version, CI run and public metrics snapshot |
|
|
24
|
+
| Week 2 | Publish the worked-example article on the owner's chosen blog/Zenn account; adapt relative links to the published location | URL, date, channel and campaign label |
|
|
25
|
+
| Weeks 3–4 | Observe 3–5 willing developers using the starter with their usual agent | Task attempted, first-run outcome, diagnostic confusion, whether they keep using it |
|
|
26
|
+
| Weekly | Capture npm trends and, locally, owner-only repository traffic | Non-overlapping date ranges and unavailable data recorded as null |
|
|
27
|
+
| After four weeks | Compare the funnel and fix the largest observed obstacle | Evidence for discovery, first successful use, continued use; no attribution from downloads alone |
|
|
28
|
+
|
|
29
|
+
Publishing destinations and interview participants are selected by the owner. The article files
|
|
30
|
+
are ready for adaptation, but an external publication or a participant interview must be recorded
|
|
31
|
+
only after it actually happens. Do not post or message people without authorization for that destination.
|
|
32
|
+
|
|
33
|
+
## Collect metrics
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
bun run metrics --output metrics.local/public.json
|
|
37
|
+
bun run metrics --traffic --output metrics.local/owner-traffic.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The second command requires authenticated owner access through `gh` and contains private traffic
|
|
41
|
+
data. `metrics.local/` is ignored by Git. Do not commit those snapshots. Public release baselines can
|
|
42
|
+
be recorded separately under this directory. Re-run weekly: GitHub traffic is a short rolling window.
|
|
43
|
+
|
|
44
|
+
Track npm totals across four weeks using the non-overlapping weekly buckets, then compare with
|
|
45
|
+
the preceding four weeks. Downloads are not unique users or installations and can include automation.
|
|
46
|
+
Traffic, clones and external adoption reports provide different evidence; none alone is a conversion rate.
|
|
47
|
+
Where the chosen publication platform supports it, use a per-channel campaign link to a page with
|
|
48
|
+
analytics. GitHub alone does not provide complete visit-to-install attribution.
|
|
49
|
+
|
|
50
|
+
## Interview script
|
|
51
|
+
|
|
52
|
+
Ask a consenting participant to add a service and a fake to the starter using their normal agent.
|
|
53
|
+
Observe rather than coach the first attempt. Record:
|
|
54
|
+
|
|
55
|
+
1. What made the package seem relevant, and what almost stopped installation?
|
|
56
|
+
2. Did the first run work? Which command or prerequisite was unclear?
|
|
57
|
+
3. Could the agent fix a missing registration and an incorrect scope without suppressions?
|
|
58
|
+
4. Were error messages actionable? Which documentation did the participant or agent actually read?
|
|
59
|
+
5. Would the participant use it in a real project? Follow up on continued use with their consent.
|
|
60
|
+
|
|
61
|
+
Record observations without names or private code unless the participant agrees. A suggested
|
|
62
|
+
invitation, for an owner-approved channel:
|
|
63
|
+
|
|
64
|
+
> TypeScriptのDIコンテナKatagamiの導入体験を改善しています。普段使っているAIエージェントで
|
|
65
|
+
> 小さなサンプルにサービスとテスト用の依存を追加する、20分ほどの試用に協力いただける方を探しています。
|
|
66
|
+
> うまく動かない点や分かりにくい点を知りたいです。公開コードだけで参加できます。
|
|
67
|
+
|
|
68
|
+
## Search and claim hygiene
|
|
69
|
+
|
|
70
|
+
Keep the description specific to dependency injection. Link guides from the README and use actual
|
|
71
|
+
examples for queries about AI coding agents, request scopes and DI without decorators. `llms.txt`
|
|
72
|
+
is a navigation aid, not a promised ranking boost.
|
|
73
|
+
|
|
74
|
+
Sources: [npm discovery metadata](https://docs.npmjs.com/files/package.json/),
|
|
75
|
+
[npm README publication](https://docs.npmjs.com/about-package-readme-files/),
|
|
76
|
+
[GitHub Topics](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics),
|
|
77
|
+
[Google AI search guidance](https://developers.google.com/search/docs/appearance/ai-features).
|
package/docs/guide.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Katagami usage guide and API
|
|
2
|
+
|
|
3
|
+
## Compatibility
|
|
4
|
+
|
|
5
|
+
Katagami publishes ESM, CommonJS and TypeScript declarations, with no runtime dependencies.
|
|
6
|
+
The CI consumer checks run on Node.js 22 and 24; runtime tests and examples also run with Bun.
|
|
7
|
+
Type examples use TypeScript 5.9, `strict: true`, and the `ES2022` and `ESNext.Disposable` libraries.
|
|
8
|
+
Browser APIs in examples, such as `crypto.randomUUID`, additionally need the `DOM` library.
|
|
9
|
+
|
|
10
|
+
Core DI does not require decorator compiler flags, metadata emission or polyfills.
|
|
11
|
+
The optional disposable entry point uses `Symbol.dispose` / `Symbol.asyncDispose`; the runtime
|
|
12
|
+
must supply those symbols. `await using` requires a compiler/runtime that supports or transforms it.
|
|
13
|
+
Katagami does not provide those polyfills. `lazy` uses JavaScript `Proxy`.
|
|
14
|
+
|
|
15
|
+
## Lifetimes
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createContainer, createScope } from 'katagami';
|
|
19
|
+
|
|
20
|
+
const container = createContainer()
|
|
21
|
+
.registerSingleton('shared', () => ({ value: 0 }))
|
|
22
|
+
.registerTransient('fresh', () => ({ value: 0 }))
|
|
23
|
+
.registerScoped('request', () => ({ value: 0 }));
|
|
24
|
+
|
|
25
|
+
const first = createScope(container);
|
|
26
|
+
const second = createScope(container);
|
|
27
|
+
first.resolve('shared') === second.resolve('shared'); // true
|
|
28
|
+
first.resolve('request') === first.resolve('request'); // true
|
|
29
|
+
first.resolve('request') === second.resolve('request'); // false
|
|
30
|
+
first.resolve('fresh') === first.resolve('fresh'); // false
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Factories run when first resolved; transient factories run on each resolution.
|
|
34
|
+
Use `createScope(existingScope)` for another scope with its own scoped cache and shared singletons.
|
|
35
|
+
Singleton and transient factories cannot directly resolve scoped registrations through their typed resolver.
|
|
36
|
+
|
|
37
|
+
## Class tokens and async factories
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createContainer, createScope } from 'katagami';
|
|
41
|
+
|
|
42
|
+
class Database {
|
|
43
|
+
query() { return ['Ada']; }
|
|
44
|
+
}
|
|
45
|
+
class UserService {
|
|
46
|
+
constructor(private db: Database) {}
|
|
47
|
+
list() { return this.db.query(); }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const container = createContainer()
|
|
51
|
+
.registerSingleton(Database, async () => new Database())
|
|
52
|
+
.registerScoped(UserService, async r => new UserService(await r.resolve(Database)));
|
|
53
|
+
|
|
54
|
+
const service = await createScope(container).resolve(UserService);
|
|
55
|
+
service.list();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Class return types are inferred from constructors. Classes follow TypeScript's structural typing;
|
|
59
|
+
see [token identity](./type-safety.md#class-tokens-use-structural-typing).
|
|
60
|
+
Async factories produce Promise-typed resolutions. Await them explicitly.
|
|
61
|
+
|
|
62
|
+
## Composition and test substitution
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { createContainer, createScope } from 'katagami';
|
|
66
|
+
|
|
67
|
+
const infrastructure = createContainer()
|
|
68
|
+
.registerSingleton('users', () => ({ find: (id: string) => `user-${id}` }));
|
|
69
|
+
const fakeInfrastructure = createContainer()
|
|
70
|
+
.registerSingleton('users', () => ({ find: (id: string) => `fake-${id}` }));
|
|
71
|
+
|
|
72
|
+
const testContainer = createContainer()
|
|
73
|
+
.use(infrastructure)
|
|
74
|
+
.use(fakeInfrastructure)
|
|
75
|
+
.registerScoped('handler', r => (id: string) => r.resolve('users').find(id));
|
|
76
|
+
|
|
77
|
+
createScope(testContainer).resolve('handler')('1'); // fake-1
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`use(source)` copies registration entries and replaces entries for matching tokens. It does not
|
|
81
|
+
share the source's singleton cache. Containers are mutable: `register*` and `use` update the container
|
|
82
|
+
and return it with accumulated types. Build the composition before creating or resolving scopes.
|
|
83
|
+
Preserve each token's value type and lifetime when substituting registrations.
|
|
84
|
+
|
|
85
|
+
## Optional and multiple resolution
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { createContainer, createScope } from 'katagami';
|
|
89
|
+
|
|
90
|
+
const scope = createScope(createContainer()
|
|
91
|
+
.registerSingleton('plugin', () => 'first')
|
|
92
|
+
.registerSingleton('plugin', () => 'second'));
|
|
93
|
+
|
|
94
|
+
scope.resolve('plugin'); // 'second'
|
|
95
|
+
scope.resolveAll('plugin'); // ['first', 'second']
|
|
96
|
+
scope.tryResolve('optional'); // undefined
|
|
97
|
+
scope.tryResolveAll('optional'); // undefined
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Repeated `register*` calls accumulate factories for a token. `resolve` selects the last;
|
|
101
|
+
`resolveAll` returns an array in registration order. For async factories, the array contains promises;
|
|
102
|
+
use `Promise.all`. Keep one consistent value type and lifetime for a token.
|
|
103
|
+
`tryResolve` and `tryResolveAll` accept missing tokens intentionally. They still report circular
|
|
104
|
+
dependencies and operations on disposed scopes.
|
|
105
|
+
|
|
106
|
+
## Resource cleanup
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createContainer, createScope } from 'katagami';
|
|
110
|
+
import { disposable } from 'katagami/disposable';
|
|
111
|
+
|
|
112
|
+
const container = createContainer().registerScoped('connection', () => ({
|
|
113
|
+
query: () => 'ok',
|
|
114
|
+
[Symbol.dispose]() { console.log('connection closed'); },
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
{
|
|
118
|
+
await using scope = disposable(createScope(container));
|
|
119
|
+
scope.resolve('connection').query();
|
|
120
|
+
} // the scoped connection is disposed here
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`disposable(containerOrScope)` returns a view with `[Symbol.asyncDispose]()` and hides registration
|
|
124
|
+
methods. Disposable scopes retain resolution methods. `createScope` accepts either disposable view
|
|
125
|
+
and retains its registered token types.
|
|
126
|
+
|
|
127
|
+
Scope disposal cleans its cached scoped instances; container disposal cleans its singleton cache.
|
|
128
|
+
Instances are processed in reverse cache insertion order, asynchronous results are awaited, and
|
|
129
|
+
cleanup errors are combined in an `AggregateError`. Disposal is idempotent. Transient instances
|
|
130
|
+
are not cached or automatically owned: arrange their cleanup explicitly.
|
|
131
|
+
|
|
132
|
+
## Lazy resolution
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { createContainer, createScope } from 'katagami';
|
|
136
|
+
import { lazy } from 'katagami/lazy';
|
|
137
|
+
|
|
138
|
+
class Report { render() { return 'report'; } }
|
|
139
|
+
const scope = createScope(createContainer().registerSingleton(Report, () => new Report()));
|
|
140
|
+
const report = lazy(scope, Report);
|
|
141
|
+
report.render(); // resolves on first access
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`lazy` accepts synchronous class tokens on a scope or disposable scope. It does not accept async
|
|
145
|
+
or PropertyKey tokens. Core imports do not include the lazy or disposable implementation; these
|
|
146
|
+
are separate entry points with `sideEffects: false` for bundlers.
|
|
147
|
+
|
|
148
|
+
## Predeclared service maps
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { createContainer, createScope } from 'katagami';
|
|
152
|
+
|
|
153
|
+
interface Services {
|
|
154
|
+
greeting: string;
|
|
155
|
+
name: string;
|
|
156
|
+
}
|
|
157
|
+
const container = createContainer<Services>()
|
|
158
|
+
.registerSingleton('greeting', r => `Hello, ${r.resolve('name')}`)
|
|
159
|
+
.registerSingleton('name', () => 'Ada');
|
|
160
|
+
|
|
161
|
+
createScope(container).resolve('greeting');
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The first generic defines non-scoped PropertyKey tokens; the second defines scoped PropertyKey tokens.
|
|
165
|
+
Maps permit forward references. They do not verify that each declared key has a registration.
|
|
166
|
+
Default accumulated registration provides the registration-order checks described in the
|
|
167
|
+
[type-safety guide](./type-safety.md).
|
|
168
|
+
|
|
169
|
+
## API reference
|
|
170
|
+
|
|
171
|
+
| API | Behavior |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `createContainer<T, ScopedT>()` | Create a registration container; omit type maps to infer them by chaining |
|
|
174
|
+
| `registerSingleton(token, factory)` | Register a shared, cached factory |
|
|
175
|
+
| `registerTransient(token, factory)` | Register a factory called on every resolution |
|
|
176
|
+
| `registerScoped(token, factory)` | Register a factory cached per scope |
|
|
177
|
+
| `use(source)` | Copy another container's registrations, replacing matching token entries |
|
|
178
|
+
| `createScope(source)` | Create a scope from a container, scope or disposable view |
|
|
179
|
+
| `scope.resolve(token)` | Resolve the last registration; fail if missing |
|
|
180
|
+
| `scope.resolveAll(token)` | Resolve every registration for a token |
|
|
181
|
+
| `scope.tryResolve(token)` | Resolve the last registration or return `undefined` |
|
|
182
|
+
| `scope.tryResolveAll(token)` | Resolve all registrations or return `undefined` |
|
|
183
|
+
| `disposable(source)` from `katagami/disposable` | Add async cleanup and return a restricted view |
|
|
184
|
+
| `lazy(scope, classToken)` from `katagami/lazy` | Defer synchronous resolution until first access |
|
|
185
|
+
| `ContainerError` | Runtime error for missing registrations, cycles and invalid scope operations |
|
|
186
|
+
| `Resolver` (type export) | Factory resolver type; retain inferred generics when extracting factories |
|