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.
Files changed (47) hide show
  1. package/README.md +80 -582
  2. package/dist/chunk-J2NYR3SH.js +6 -0
  3. package/dist/container/index.d.cts +108 -0
  4. package/dist/container/index.d.ts +2 -2
  5. package/dist/disposable/index.cjs +16 -25
  6. package/dist/disposable/index.d.cts +69 -0
  7. package/dist/disposable/index.d.ts +13 -5
  8. package/dist/disposable/index.js +1 -1
  9. package/dist/error/index.d.cts +11 -0
  10. package/dist/index.cjs +91 -55
  11. package/dist/index.d.cts +6 -0
  12. package/dist/index.d.ts +6 -6
  13. package/dist/index.js +76 -31
  14. package/dist/internal.d.cts +29 -0
  15. package/dist/internal.d.ts +3 -1
  16. package/dist/lazy/index.cjs +16 -25
  17. package/dist/lazy/index.d.cts +33 -0
  18. package/dist/lazy/index.d.ts +3 -3
  19. package/dist/lazy/index.js +1 -1
  20. package/dist/resolver/index.d.cts +93 -0
  21. package/dist/scope/index.d.cts +120 -0
  22. package/dist/scope/index.d.ts +4 -4
  23. package/docs/README.de.md +65 -0
  24. package/docs/README.es.md +65 -0
  25. package/docs/README.fr.md +65 -0
  26. package/docs/README.ja.md +66 -0
  27. package/docs/README.ko.md +65 -0
  28. package/docs/README.zh-CN.md +65 -0
  29. package/docs/README.zh-TW.md +65 -0
  30. package/docs/ai-coding-agents.md +78 -0
  31. package/docs/articles/ai-coding-agents.ja.md +83 -0
  32. package/docs/articles/ai-coding-agents.md +70 -0
  33. package/docs/articles/request-scope.md +48 -0
  34. package/docs/articles/without-decorators.md +54 -0
  35. package/docs/choosing-di.md +30 -0
  36. package/docs/growth/baseline-2026-09-11.json +68 -0
  37. package/docs/growth/github-metadata.json +13 -0
  38. package/docs/growth/rollout.md +77 -0
  39. package/docs/guide.md +186 -0
  40. package/docs/type-safety.md +126 -0
  41. package/examples/request-scope/README.md +37 -0
  42. package/examples/request-scope/app.ts +31 -0
  43. package/examples/request-scope/demo.ts +10 -0
  44. package/examples/request-scope/tsconfig.json +11 -0
  45. package/llms.txt +16 -0
  46. package/package.json +56 -23
  47. package/dist/index-jx8b52m0.js +0 -4
@@ -0,0 +1,126 @@
1
+ # Type-safe dependency injection: what Katagami checks
2
+
3
+ Katagami's default `createContainer()` accumulates types from registrations. With literal keys
4
+ or unique symbols preserved, a resolver can only accept tokens accumulated into its visible
5
+ registration set. No separate service interface is required. Run TypeScript with `strict: true`.
6
+
7
+ ## Accumulated registrations
8
+
9
+ ```ts
10
+ import { createContainer, createScope } from 'katagami';
11
+
12
+ const DATABASE = Symbol('database');
13
+ const OTHER_DATABASE = Symbol('database');
14
+ const container = createContainer()
15
+ .registerSingleton(DATABASE, () => ({ query: () => 'ok' }))
16
+ .registerSingleton('greeting', () => 'hello');
17
+
18
+ const scope = createScope(container);
19
+ const greeting: string = scope.resolve('greeting');
20
+ scope.resolve(DATABASE).query();
21
+ // @ts-expect-error — this literal key has not been accumulated
22
+ scope.resolve('missing');
23
+ // @ts-expect-error — the same description does not make two symbols identical
24
+ scope.resolve(OTHER_DATABASE);
25
+ ```
26
+
27
+ Inside a factory, the visible set is the set accumulated **before that registration**.
28
+ Singleton and transient factories receive the non-scoped set. Scoped factories receive both sets.
29
+ Scopes can resolve both. `use()` includes the source module's registration types.
30
+ `disposable()` and nested scopes retain the registration state, including asynchronous results.
31
+
32
+ | Pattern | Compile-time behavior |
33
+ | --- | --- |
34
+ | Unregistered literal key / distinct unique symbol in accumulated mode | Rejected |
35
+ | A structurally incompatible, unregistered class | Rejected |
36
+ | Scoped literal token in a singleton or transient factory | Rejected |
37
+ | Accessing a service method on an unresolved `Promise<Service>` | Rejected |
38
+ | A key declared in `createContainer<Services>()`, but never registered | Accepted; runtime resolution can fail |
39
+ | An unregistered class structurally compatible with a registered class | Can be accepted; runtime resolution can fail |
40
+ | `tryResolve` of an unregistered token | Intentionally accepted; absence is a normal result |
41
+
42
+ The checked examples and regression tests establish these specific behaviors. They do not prove
43
+ arbitrary program correctness or an improvement in AI coding performance.
44
+
45
+ ## Class tokens use structural typing
46
+
47
+ Accumulating a class type does not give TypeScript a nominal identity for each constructor.
48
+ The following compiles in accumulated mode, but throws `ContainerError` if executed:
49
+
50
+ ```ts
51
+ import { createContainer, createScope } from 'katagami';
52
+
53
+ class Registered { value = 1; }
54
+ class NotRegistered { value = 1; }
55
+
56
+ const scope = createScope(createContainer()
57
+ .registerSingleton(Registered, () => new Registered()));
58
+
59
+ scope.resolve(NotRegistered); // Same structure to TypeScript; different runtime Map key
60
+ ```
61
+
62
+ This includes empty classes and can include subclasses. TypeScript checks compatibility;
63
+ the runtime registry checks token identity. When that distinction matters, use unique symbols
64
+ or literal keys, or distinguish class types with separate private member declarations:
65
+
66
+ ```ts
67
+ import { createContainer, createScope } from 'katagami';
68
+
69
+ class Registered {
70
+ declare private brand: undefined;
71
+ value = 1;
72
+ }
73
+ class NotRegistered {
74
+ declare private brand: undefined;
75
+ value = 1;
76
+ }
77
+
78
+ const scope = createScope(createContainer()
79
+ .registerSingleton(Registered, () => new Registered()));
80
+ // @ts-expect-error — these separately declared private members make the types incompatible
81
+ scope.resolve(NotRegistered);
82
+ ```
83
+
84
+ See [TypeScript's type compatibility documentation](https://www.typescriptlang.org/docs/handbook/type-compatibility).
85
+
86
+ ## Predeclared interface maps
87
+
88
+ `createContainer<Services>()` starts with the declared keys already visible. This enables
89
+ order-independent registration, but that type map is a declaration of intent, not a proof
90
+ that every key has a runtime factory:
91
+
92
+ ```ts
93
+ import { createContainer, createScope } from 'katagami';
94
+
95
+ interface Services { greeting: string; }
96
+ const scope = createScope(createContainer<Services>());
97
+ scope.resolve('greeting'); // Compiles, but throws if executed: no factory was registered
98
+ ```
99
+
100
+ Prefer default accumulated registration when you want registration-order checks.
101
+ Use a predeclared map when order independence is useful and verify the composition in runtime tests.
102
+
103
+ ## Preserve the information the checker needs
104
+
105
+ - Keep literal tokens narrow. A token widened to `string`, or a broad index signature, loses the finite key set.
106
+ - Keep the return value of each registration chain. Do not replace its inferred type with a broader annotation.
107
+ - Keep a token's value type and lifetime consistent across registrations and `use()` replacements.
108
+ Katagami is mutable at runtime; arbitrary mutation through aliases is not proven safe by the accumulated type.
109
+ - Use the factory's supplied resolver. Closures over another scope can bypass its lifetime restrictions.
110
+ - Fix the dependency or lifetime; `any`, assertions and suppressions can bypass TypeScript checks.
111
+ - `tryResolve` is for optional dependencies, not a workaround for required registration errors.
112
+
113
+ ## Runtime checks and limits
114
+
115
+ Runtime checks report missing registrations, disposed scopes and circular resolution paths.
116
+ The captive-dependency guard detects scoped resolution in an active singleton call chain,
117
+ including indirect synchronous calls. It does not prove all lifetime relationships: in particular,
118
+ do not rely on it for work resumed after an `await` or for dependencies captured from another scope.
119
+ Use the typed factory API and runtime tests together.
120
+
121
+ ## Verification
122
+
123
+ `bun run typecheck` runs the original type tests and the guarantee regressions in
124
+ `src/container/guarantees/typetest.ts`. Negative checks use `@ts-expect-error`, so a previously
125
+ rejected expression becoming accepted makes the check fail. These files are not runtime examples.
126
+ `bun run docs:check` type-checks every TypeScript fence in the maintained Markdown documentation.
@@ -0,0 +1,37 @@
1
+ # TypeScript request scopes without decorators
2
+
3
+ A small starter for an HTTP handler, CLI, or worker: inject a user repository,
4
+ create isolated state for each request, and dispose that state after success or failure.
5
+ It uses Katagami's accumulated literal tokens; no token-to-type interface is needed.
6
+
7
+ Copy `app.ts`, `demo.ts`, and `tsconfig.json` into a new directory, then run:
8
+
9
+ ```sh
10
+ npm init -y
11
+ npm pkg set type=module
12
+ npm install katagami
13
+ npm install --save-dev typescript
14
+ npx tsc --noEmit
15
+ npx tsc
16
+ node build/demo.js
17
+ ```
18
+
19
+ The demo prints a greeting for Ada and a guest greeting with different request IDs.
20
+ The example is checked on Node.js 22 and 24 in CI and runs on Bun as well.
21
+ It relies on `crypto.randomUUID`, `Symbol.dispose`, and `Symbol.asyncDispose` in the host.
22
+ TypeScript compiles `await using`; Katagami does not install polyfills.
23
+
24
+ From this repository, run `bun run build`, then `bun examples/request-scope/demo.ts`.
25
+ `bun run test:examples` checks concurrency, fake injection and cleanup on rejection.
26
+ `bun run test:package` also compiles and runs this starter against the packed npm artifact in Node.js.
27
+
28
+ For an HTTP application, call the returned `greet` function inside the request handler.
29
+ Create the application once, outside the handler. Each call creates and disposes its own scope.
30
+ Pass authentication context explicitly into the application when adding authentication;
31
+ this example does not provide an authentication or authorization system.
32
+
33
+ Try changing `registerScoped('greeting', ...)` to `registerSingleton('greeting', ...)`.
34
+ `npx tsc --noEmit` will reject `r.resolve('request')`. Restore the scoped registration:
35
+ the greeting captures request state, so it must share that request's lifetime.
36
+
37
+ See the [AI coding guide](../../docs/ai-coding-agents.md) and [type guarantees](../../docs/type-safety.md).
@@ -0,0 +1,31 @@
1
+ import { createContainer, createScope } from 'katagami';
2
+ import { disposable } from 'katagami/disposable';
3
+
4
+ export interface UserRepository {
5
+ findName(id: string): Promise<string | undefined>;
6
+ }
7
+
8
+ /** Inject infrastructure once; create a separate scope for each request. */
9
+ export function createApp(repository: UserRepository, onCleanup: (id: string) => void = () => {}) {
10
+ const container = createContainer()
11
+ .registerSingleton('users', () => repository)
12
+ .registerScoped('request', () => ({
13
+ id: crypto.randomUUID(),
14
+ [Symbol.dispose]() {
15
+ onCleanup(this.id);
16
+ },
17
+ }))
18
+ .registerScoped('greeting', r => {
19
+ const users = r.resolve('users');
20
+ const request = r.resolve('request');
21
+ return async (userId: string) => ({
22
+ requestId: request.id,
23
+ message: `Hello, ${(await users.findName(userId)) ?? 'guest'}!`,
24
+ });
25
+ });
26
+
27
+ return async (userId: string) => {
28
+ await using scope = disposable(createScope(container));
29
+ return await scope.resolve('greeting')(userId);
30
+ };
31
+ }
@@ -0,0 +1,10 @@
1
+ import { createApp } from './app.js';
2
+
3
+ const greet = createApp({
4
+ async findName(id) {
5
+ return new Map([['1', 'Ada']]).get(id);
6
+ },
7
+ });
8
+
9
+ console.log(await greet('1'));
10
+ console.log(await greet('unknown'));
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022", "DOM", "ESNext.Disposable"],
7
+ "strict": true,
8
+ "outDir": "build"
9
+ },
10
+ "include": ["app.ts", "demo.ts"]
11
+ }
package/llms.txt ADDED
@@ -0,0 +1,16 @@
1
+ # Katagami
2
+
3
+ > Type-safe dependency injection for TypeScript. Accumulated registration types, scope-aware factories and async tracking for AI-assisted development. No decorators or runtime dependencies.
4
+
5
+ ## Documentation
6
+
7
+ - [Quick start](https://github.com/hiroiku/katagami/blob/master/README.md): installation and a checked example.
8
+ - [AI coding agents](https://github.com/hiroiku/katagami/blob/master/docs/ai-coding-agents.md): workflow, diagnostics and project instructions.
9
+ - [Type safety](https://github.com/hiroiku/katagami/blob/master/docs/type-safety.md): accumulated tokens, structural classes, declared maps and limits.
10
+ - [API guide](https://github.com/hiroiku/katagami/blob/master/docs/guide.md): lifetimes, factories, modules, disposal and lazy resolution.
11
+ - [Request-scope starter](https://github.com/hiroiku/katagami/tree/master/examples/request-scope): a runnable example with fake injection and cleanup.
12
+ - [Choosing DI](https://github.com/hiroiku/katagami/blob/master/docs/choosing-di.md): use cases and trade-offs.
13
+
14
+ ## Optional
15
+
16
+ - [Evaluation protocol](https://github.com/hiroiku/katagami/tree/master/benchmarks/agent-wiring): test agent outcomes before claiming performance benefits.
package/package.json CHANGED
@@ -1,41 +1,64 @@
1
1
  {
2
2
  "name": "katagami",
3
- "version": "3.0.0",
4
- "description": "Lightweight DI container for TypeScript and JavaScript full type inference, no decorators, no reflect-metadata, hybrid class & PropertyKey tokens.",
3
+ "version": "3.0.1",
4
+ "description": "Type-safe dependency injection for TypeScript, with inferred types and scope checks for AI-assisted development. No decorators or reflect-metadata.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js",
12
- "require": "./dist/index.cjs"
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
13
18
  },
14
19
  "./disposable": {
15
- "types": "./dist/disposable/index.d.ts",
16
- "import": "./dist/disposable/index.js",
17
- "require": "./dist/disposable/index.cjs"
20
+ "import": {
21
+ "types": "./dist/disposable/index.d.ts",
22
+ "default": "./dist/disposable/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/disposable/index.d.cts",
26
+ "default": "./dist/disposable/index.cjs"
27
+ }
18
28
  },
19
29
  "./lazy": {
20
- "types": "./dist/lazy/index.d.ts",
21
- "import": "./dist/lazy/index.js",
22
- "require": "./dist/lazy/index.cjs"
30
+ "import": {
31
+ "types": "./dist/lazy/index.d.ts",
32
+ "default": "./dist/lazy/index.js"
33
+ },
34
+ "require": {
35
+ "types": "./dist/lazy/index.d.cts",
36
+ "default": "./dist/lazy/index.cjs"
37
+ }
23
38
  }
24
39
  },
25
40
  "main": "./dist/index.cjs",
26
41
  "module": "./dist/index.js",
27
42
  "types": "./dist/index.d.ts",
28
43
  "files": [
29
- "dist"
44
+ "dist",
45
+ "docs",
46
+ "examples/request-scope",
47
+ "llms.txt"
30
48
  ],
31
49
  "keywords": [
32
50
  "dependency-injection",
33
- "di container",
51
+ "di-container",
34
52
  "di",
35
- "container",
36
53
  "ioc",
37
54
  "typescript",
38
- "javascript"
55
+ "javascript",
56
+ "type-safe",
57
+ "type-inference",
58
+ "compile-time",
59
+ "ai-assisted-development",
60
+ "ai-coding",
61
+ "coding-agents"
39
62
  ],
40
63
  "repository": {
41
64
  "type": "git",
@@ -44,20 +67,30 @@
44
67
  "scripts": {
45
68
  "clean": "rm -rf dist",
46
69
  "build": "bun run clean && bun run build:types && bun run build:esm && bun run build:cjs",
47
- "build:types": "tsc -p tsconfig.build.json",
48
- "build:esm": "bun build ./src/index.ts ./src/disposable/index.ts ./src/lazy/index.ts --outdir dist --format esm --splitting",
49
- "build:cjs": "bun build ./src/index.ts --outfile dist/index.cjs --format cjs && bun build ./src/disposable/index.ts --outfile dist/disposable/index.cjs --format cjs && bun build ./src/lazy/index.ts --outfile dist/lazy/index.cjs --format cjs",
70
+ "build:types": "tsc -p tsconfig.build.json && node scripts/build-cjs-types.mjs",
71
+ "build:esm": "node scripts/build.mjs esm",
72
+ "build:cjs": "node scripts/build.mjs cjs",
50
73
  "prepublishOnly": "bun run build",
51
- "check": "bun run format",
74
+ "check": "biome check .",
52
75
  "lint": "biome lint .",
53
76
  "format": "biome check --write .",
54
- "verify": "bun run build:types && bun run check && bun test"
55
- },
56
- "dependencies": {
57
- "@types/bun": "^1.3.8"
77
+ "verify": "bun run check && bun run typecheck && bun run build && bun run test && bun run test:examples && bun run test:package && bun run test:agent-fixtures && bun run docs:check",
78
+ "typecheck": "tsc -p tsconfig.typecheck.json && tsc -p tsconfig.examples.json",
79
+ "test": "bun test src",
80
+ "test:examples": "cd examples && bun test",
81
+ "test:package": "node scripts/check-package.mjs",
82
+ "docs:check": "node scripts/check-docs.mjs",
83
+ "metrics": "node scripts/collect-metrics.mjs",
84
+ "test:agent-fixtures": "node scripts/run-agent-task.mjs baseline manual && node scripts/run-agent-task.mjs baseline katagami"
58
85
  },
59
86
  "devDependencies": {
60
87
  "@biomejs/biome": "^2.3.14",
88
+ "@types/bun": "^1.3.8",
89
+ "esbuild": "0.28.2",
61
90
  "typescript": "^5.9.3"
91
+ },
92
+ "homepage": "https://github.com/hiroiku/katagami#readme",
93
+ "bugs": {
94
+ "url": "https://github.com/hiroiku/katagami/issues"
62
95
  }
63
96
  }
@@ -1,4 +0,0 @@
1
- // src/internal.ts
2
- var INTERNALS = Symbol("katagami.internals");
3
-
4
- export { INTERNALS };