@devcraft-ts/diadem 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # diadem
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release.
6
+
7
+ - SSR-safe, framework-agnostic DI container (`DiademContainer`) with direct,
8
+ singleton, transient, lazy-singleton, and **async** lifecycles.
9
+ - Decorators: `@singleton` / `@factory` / `@lazy` / `@lazySingleton` with
10
+ optional environment filtering.
11
+ - **Build-time manifest generator** (`diadem build`) — AST analysis of
12
+ constructor dependencies, token-first resolution, topological ordering, and a
13
+ `--strict` mode that fails on cycles, ambiguous tokens, or unresolved
14
+ required dependencies. No runtime reflection, no `reflect-metadata`.
15
+ - **Compiled emit** (`--emit=compiled`) — generates straight-line `createContainer()`
16
+ wiring (no runtime interpretation), the same codegen approach as Dagger/Micronaut,
17
+ plus a **compile-time-checked `createServices()` accessor**: resolving an
18
+ unregistered token is a `tsc` error, with no custom TypeScript transformer.
19
+ - Container lifecycle: `Disposable` support, `onDispose`, and `dispose()`;
20
+ child scopes via `createChild()`.
21
+ - Pluggable, silent-by-default logging (`setLogger` / `consoleLogger`).
22
+ - Ships ESM + CJS + type declarations; entry points `@devcraft-ts/diadem` and `@devcraft-ts/diadem/setup`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jai Sachdeva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # diadem
2
+
3
+ ![types: included](https://img.shields.io/badge/types-included-blue)
4
+ ![runtime deps: 0](https://img.shields.io/badge/runtime%20deps-0-brightgreen)
5
+ ![reflect-metadata: none](https://img.shields.io/badge/reflect--metadata-none-brightgreen)
6
+ ![license: MIT](https://img.shields.io/badge/license-MIT-green)
7
+
8
+ > Build-time, manifest-driven dependency injection for TypeScript — SSR-safe, framework-agnostic, zero runtime reflection.
9
+
10
+ **diadem** wires your services from a manifest that is generated at build time. A
11
+ generator analyses your decorated classes, extracts each constructor's
12
+ dependencies, topologically sorts them, and emits a manifest module. At runtime
13
+ the container reads that manifest and autowires everything — **no
14
+ `reflect-metadata`, no runtime constructor parsing, no global state**. You create
15
+ and own the container (usually one per application); because the library keeps no
16
+ hidden global container, it is safe in concurrent/SSR environments, and you can
17
+ spin up child scopes for per-request isolation when you need it.
18
+
19
+ ```
20
+ decorated classes ──► build-time generator ──► service-manifest.ts
21
+
22
+
23
+ configureManifest(manifest) ──► DiademContainer
24
+ ```
25
+
26
+ > **Status:** early but complete (`0.1.0`). The runtime container, decorators,
27
+ > dependency resolver, and the **`diadem build` manifest generator** are all in
28
+ > place. You can also hand-write a manifest conforming to `ServiceManifestModule`
29
+ > (see [`examples/basic.ts`](examples/basic.ts)).
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm install @devcraft-ts/diadem
35
+ ```
36
+
37
+ Requires TypeScript with `experimentalDecorators` enabled (legacy decorator
38
+ syntax) and Node ≥ 18. `typescript` is an (optional) peer dependency — needed
39
+ only to run the `diadem build` generator.
40
+
41
+ ```jsonc
42
+ // tsconfig.json
43
+ {
44
+ "compilerOptions": {
45
+ "experimentalDecorators": true
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Generating the manifest
51
+
52
+ Run the generator after writing or changing decorated services:
53
+
54
+ ```bash
55
+ npx diadem build
56
+ ```
57
+
58
+ It scans your source, finds DI-decorated classes via the TypeScript AST,
59
+ extracts and topologically sorts their constructor dependencies, and writes a
60
+ manifest module (default: `src/generated/service-manifest.ts`). Import paths in
61
+ the output are computed relative to the manifest file, so no path aliases are
62
+ required.
63
+
64
+ Configure via flags or a `diadem.config.json` in the project root (flags win):
65
+
66
+ ```jsonc
67
+ // diadem.config.json
68
+ {
69
+ "scanDirs": ["src"],
70
+ "outFile": "src/generated/service-manifest.ts",
71
+ "include": ["\\.ts$"], // optional perf narrowing, e.g. ["Service\\.ts$"]
72
+ "environments": ["development", "production", "test"]
73
+ }
74
+ ```
75
+
76
+ ```bash
77
+ diadem build --scan-dir src --out src/generated/service-manifest.ts --strict
78
+ ```
79
+
80
+ `--strict` exits non-zero on dependency cycles, ambiguous (duplicated) tokens, or
81
+ required dependencies with no implementing service — turning runtime surprises
82
+ into build failures. (`--fail-on-cycle` is the narrower cycles-only variant.)
83
+ Wire it into your build, e.g. `"prebuild": "diadem build --strict"`.
84
+
85
+ ### Compiled mode (zero-overhead wiring)
86
+
87
+ `--emit=compiled` generates **straight-line wiring code** instead of an
88
+ interpreted data manifest — the same approach Dagger/Micronaut take on the JVM.
89
+ There's no manifest to parse, no resolver loop, and no per-dependency lookup
90
+ during construction; services are just constructed in topological order with
91
+ direct references:
92
+
93
+ ```bash
94
+ diadem build --emit=compiled --target-env production --out src/generated/container.ts
95
+ ```
96
+
97
+ ```ts
98
+ // generated container.ts (abridged)
99
+ export function createContainer(): DiademContainer {
100
+ const c = new DiademContainer()
101
+ const _ConsoleLogger = new ConsoleLogger()
102
+ c.register(token(ConsoleLogger), _ConsoleLogger)
103
+ const _Greeter = new Greeter(_ConsoleLogger) // ← direct reference, no lookup
104
+ c.register(token(Greeter), _Greeter)
105
+ c.setReady()
106
+ return c
107
+ }
108
+ ```
109
+
110
+ ```ts
111
+ import { createContainer } from './generated/container'
112
+ const container = createContainer() // fully wired, ready
113
+ const greeter = container.resolve(IGreeter)
114
+ ```
115
+
116
+ This is the fastest path — wiring compiles down to the `new` calls you'd write
117
+ by hand, and bundlers can tree-shake unused services from the output.
118
+
119
+ **Type-safe access.** Compiled mode also emits a `createServices()` accessor
120
+ whose surface contains *only the registered tokens*, each typed to its token —
121
+ so resolving something that isn't wired is a **compile error**, not a runtime
122
+ throw:
123
+
124
+ ```ts
125
+ import { createServices } from './generated/container'
126
+
127
+ const services = createServices()
128
+ const greeting: string = services.IGreeter.greet('world') // ✓ correctly typed
129
+ services.INope // ✗ tsc error: Property 'INope' does not exist on type 'DiademServices'
130
+ ```
131
+
132
+ That's the compile-time guarantee of `typed-inject`/`@wessberg/di` — but reached
133
+ through decorators + auto-discovery, with no custom TypeScript transformer.
134
+ (Ambiguous or unlocatable tokens are omitted from the typed surface but still
135
+ wired into `createContainer()`.)
136
+
137
+ Trade-offs vs. the default manifest emit:
138
+ - One environment is **baked in** per build (`--target-env`, default: all) — no
139
+ runtime env branching.
140
+ - `lazySingleton` is treated as **eager** (its instance is referenced up front).
141
+ - No runtime mock/override registration — use the manifest emit for dev/test if
142
+ you rely on that dynamism.
143
+
144
+ ## Concepts
145
+
146
+ | Concept | What it is |
147
+ | --- | --- |
148
+ | **Token** | An abstract class used as the injection key. |
149
+ | **Decorator** | `@singleton` / `@factory` / `@lazy` / `@lazySingleton` tag an implementation with its token, lifecycle, and optional environment. |
150
+ | **Manifest** | Build-time output describing every service, its dependencies, and a topological registration order. Conforms to `ServiceManifestModule`. |
151
+ | **Container** | `DiademContainer` — reads the manifest and resolves instances. Usually app-scoped; `createChild()` makes an isolated scope when needed. |
152
+
153
+ ### Lifecycles
154
+
155
+ - `singleton` — one instance per container, created eagerly on registration.
156
+ - `lazySingleton` — one instance per container, created on first resolve.
157
+ - `lazy` / `factory` — a new instance on every resolve.
158
+
159
+ ## Quick start
160
+
161
+ ```ts
162
+ import { DiademContainer, configureManifest, singleton } from '@devcraft-ts/diadem'
163
+ import * as manifest from './generated/service-manifest' // your build output
164
+
165
+ abstract class ILogger {
166
+ abstract log(msg: string): void
167
+ }
168
+
169
+ @singleton(ILogger)
170
+ class ConsoleLogger extends ILogger {
171
+ log(msg: string) {
172
+ console.log(msg)
173
+ }
174
+ }
175
+
176
+ // Register the generated manifest once at startup.
177
+ configureManifest(manifest)
178
+
179
+ // Create your application container (once):
180
+ const container = new DiademContainer()
181
+ await container.autoRegisterDiscovered(process.env.NODE_ENV)
182
+ container.setReady()
183
+
184
+ const logger = container.resolve(ILogger)
185
+ logger.log('wired with diadem')
186
+ ```
187
+
188
+ See [`examples/basic.ts`](examples/basic.ts) for a complete, runnable example
189
+ including a hand-written manifest that documents the generator contract.
190
+
191
+ ## Manual registration
192
+
193
+ You don't have to use the manifest — the container is a perfectly good explicit
194
+ DI container on its own:
195
+
196
+ ```ts
197
+ const container = new DiademContainer()
198
+ container.registerSingleton(ILogger, () => new ConsoleLogger())
199
+ container.registerFactory(IClock, () => new SystemClock())
200
+ container.register(IConfig, loadedConfig)
201
+ container.setReady()
202
+ ```
203
+
204
+ ## Logging
205
+
206
+ Diadem is **silent by default** — it writes nothing unless you opt in. Register a
207
+ logger to surface its diagnostics:
208
+
209
+ ```ts
210
+ import { setLogger, consoleLogger } from '@devcraft-ts/diadem'
211
+
212
+ setLogger(consoleLogger) // or your own pino/winston adapter implementing `Logger`
213
+ setLogger(null) // back to silent
214
+ ```
215
+
216
+ ## Async services
217
+
218
+ Some services need awaited construction (open a connection, read a secret).
219
+ Register them as async and resolve with `resolveAsync`:
220
+
221
+ ```ts
222
+ container.registerAsyncSingleton(IDatabase, async () => {
223
+ const db = new Database()
224
+ await db.connect()
225
+ return db
226
+ })
227
+
228
+ const db = await container.resolveAsync(IDatabase) // awaited once, then cached
229
+ ```
230
+
231
+ `registerAsyncFactory` gives a fresh awaited instance per `resolveAsync`. Calling
232
+ the synchronous `resolve()` on an async-only token throws a clear error.
233
+
234
+ ## Lifecycle & disposal
235
+
236
+ Singletons and directly-registered values that implement `Disposable`
237
+ (`{ dispose(): void | Promise<void> }`) are torn down automatically when the
238
+ container is disposed. You can also register arbitrary teardown callbacks:
239
+
240
+ ```ts
241
+ container.registerSingleton(IPool, () => new ConnectionPool()) // has dispose()
242
+ container.onDispose(() => clearInterval(timer))
243
+
244
+ await container.dispose() // runs teardown in reverse order, then clears the container
245
+ ```
246
+
247
+ Child scopes share parent-owned instances by reference and never dispose them —
248
+ only resources a child registers itself are released with it. Transient
249
+ (`factory`) instances are owned by the caller and are not auto-disposed.
250
+
251
+ ## API surface
252
+
253
+ - `@devcraft-ts/diadem` — `DiademContainer`, decorators (`singleton`/`factory`/`lazy`/
254
+ `lazySingleton`), the manifest contract (`configureManifest`,
255
+ `ServiceManifestModule`, `ServiceManifestEntry`, …), `Disposable`,
256
+ auto-discovery utilities, and logging (`setLogger`, `consoleLogger`, `Logger`).
257
+ - `@devcraft-ts/diadem/setup` — environment-aware container factories,
258
+ `validateAutoRegistration`, and `logSetupInfo`.
259
+ - `diadem build` — the manifest generator CLI (`bin`).
260
+
261
+ ## How it compares
262
+
263
+ Most TS/JS DI lives in two camps. **Reflection + decorators** (InversifyJS,
264
+ tsyringe, TypeDI, NestJS) is ergonomic but needs `reflect-metadata` and reads
265
+ constructor types *at runtime*. **Type-safe manual** (typed-inject, brandi,
266
+ ditox) has no decorators and no reflection — the compiler verifies the graph,
267
+ but you wire everything by hand.
268
+
269
+ `diadem` is a third path: decorator ergonomics **and** auto-discovery, but the
270
+ dependency analysis happens in a **build step** that emits a static manifest —
271
+ so there's **no `reflect-metadata` and nothing reflective on the runtime path**.
272
+ On the JVM this is the Spring → **Dagger / Micronaut / Quarkus** shift (runtime
273
+ reflection → build-time wiring); `diadem` brings that shift to TypeScript.
274
+
275
+ | | reflect-metadata | decorators | auto-discovery | graph analysis at |
276
+ | --- | --- | --- | --- | --- |
277
+ | Inversify / tsyringe / Nest | required | yes | yes | runtime |
278
+ | typed-inject / brandi / ditox | no | no | no | compile (type-checked) |
279
+ | **diadem** | **no** | yes | yes | **build (`diadem build`)** |
280
+
281
+ For maximum runtime performance **and** type safety, `diadem build
282
+ --emit=compiled` emits straight-line wiring (no runtime interpretation — the same
283
+ codegen approach as Dagger/Micronaut, the fastest DI path in TS/npm) plus a
284
+ `createServices()` accessor that is **compile-time checked**: resolving an
285
+ unregistered token is a `tsc` error. That matches `typed-inject`/`@wessberg/di`
286
+ on safety, but without their cost — `@wessberg/di` needs a custom TypeScript
287
+ transformer (and `ts-patch`/`ttypescript`, which broke at TS 5.0), whereas
288
+ `diadem` emits plain `.ts` that any toolchain (tsc, esbuild, swc, vite, bun)
289
+ compiles as-is.
290
+
291
+ Trade-offs to know: build-time graph validation is name-based (`--strict` turns
292
+ unresolved/ambiguous tokens into build errors) rather than fully type-driven; a
293
+ singleton's factory runs at registration, so **registration order matters** (the
294
+ generator emits services in topological order for you; use `lazySingleton` to
295
+ defer construction); and the generated manifest grows with the number of
296
+ services. It uses legacy (`experimentalDecorators`) decorators.
297
+
298
+ ## Roadmap
299
+
300
+ - Richer dependency-graph diagnostics from the generator (visualization, unused
301
+ service detection).
302
+ - A `--check` dry-run mode and a benchmark harness.
303
+
304
+ ## License
305
+
306
+ [MIT](LICENSE) © Jai Sachdeva
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ var chunkFHQRDO5C_cjs = require('./chunk-FHQRDO5C.cjs');
4
+
5
+ // src/core/auto-discovery.ts
6
+ var discoveryCache = /* @__PURE__ */ new Map();
7
+ function clearDiscoveryCache() {
8
+ discoveryCache.clear();
9
+ }
10
+ async function discoverServices(config = {}) {
11
+ const { environment, useCache = true } = config;
12
+ const cacheKey = environment ?? "all";
13
+ if (useCache) {
14
+ const cached = discoveryCache.get(cacheKey);
15
+ if (cached) {
16
+ return {
17
+ services: cached,
18
+ totalFound: cached.length,
19
+ totalDecorated: cached.length,
20
+ fromCache: true,
21
+ manifestAvailable: true
22
+ };
23
+ }
24
+ }
25
+ if (!chunkFHQRDO5C_cjs.hasManifest()) {
26
+ chunkFHQRDO5C_cjs.getLogger().warn(
27
+ "Diadem: no manifest configured \u2014 service discovery found nothing. Call configureManifest(...) at startup."
28
+ );
29
+ return {
30
+ services: [],
31
+ totalFound: 0,
32
+ totalDecorated: 0,
33
+ fromCache: false,
34
+ manifestAvailable: false
35
+ };
36
+ }
37
+ const manifest = await chunkFHQRDO5C_cjs.loadManifest();
38
+ const entries = manifest.getServicesForEnvironment(environment);
39
+ const imported = await manifest.importAllServices(entries);
40
+ const services = imported.map((item) => item.serviceClass).filter(
41
+ (serviceClass) => typeof serviceClass === "function" && chunkFHQRDO5C_cjs.hasDIMetadata(serviceClass)
42
+ );
43
+ if (useCache) {
44
+ discoveryCache.set(cacheKey, services);
45
+ }
46
+ chunkFHQRDO5C_cjs.getLogger().debug(
47
+ `Diadem: discovered ${services.length}/${entries.length} services for "${cacheKey}"`
48
+ );
49
+ return {
50
+ services,
51
+ totalFound: entries.length,
52
+ totalDecorated: services.length,
53
+ fromCache: false,
54
+ manifestAvailable: true
55
+ };
56
+ }
57
+ function isManifestAvailable() {
58
+ return chunkFHQRDO5C_cjs.hasManifest();
59
+ }
60
+ async function getManifestStats() {
61
+ if (!chunkFHQRDO5C_cjs.hasManifest()) {
62
+ return null;
63
+ }
64
+ const manifest = await chunkFHQRDO5C_cjs.loadManifest();
65
+ return manifest.MANIFEST_STATS ?? null;
66
+ }
67
+
68
+ exports.clearDiscoveryCache = clearDiscoveryCache;
69
+ exports.discoverServices = discoverServices;
70
+ exports.getManifestStats = getManifestStats;
71
+ exports.isManifestAvailable = isManifestAvailable;
72
+ //# sourceMappingURL=auto-discovery-5IV22D5D.cjs.map
73
+ //# sourceMappingURL=auto-discovery-5IV22D5D.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/auto-discovery.ts"],"names":["hasManifest","getLogger","loadManifest","hasDIMetadata"],"mappings":";;;;;AAiCA,IAAM,cAAA,uBAAqB,GAAA,EAAmC;AAGvD,SAAS,mBAAA,GAA4B;AAC1C,EAAA,cAAA,CAAe,KAAA,EAAM;AACvB;AAKA,eAAsB,gBAAA,CACpB,MAAA,GAA8B,EAAC,EACE;AACjC,EAAA,MAAM,EAAE,WAAA,EAAa,QAAA,GAAW,IAAA,EAAK,GAAI,MAAA;AACzC,EAAA,MAAM,WAAW,WAAA,IAAe,KAAA;AAEhC,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,MAAA,GAAS,cAAA,CAAe,GAAA,CAAI,QAAQ,CAAA;AAC1C,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAO;AAAA,QACL,QAAA,EAAU,MAAA;AAAA,QACV,YAAY,MAAA,CAAO,MAAA;AAAA,QACnB,gBAAgB,MAAA,CAAO,MAAA;AAAA,QACvB,SAAA,EAAW,IAAA;AAAA,QACX,iBAAA,EAAmB;AAAA,OACrB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,CAACA,+BAAY,EAAG;AAClB,IAAAC,2BAAA,EAAU,CAAE,IAAA;AAAA,MACV;AAAA,KAEF;AACA,IAAA,OAAO;AAAA,MACL,UAAU,EAAC;AAAA,MACX,UAAA,EAAY,CAAA;AAAA,MACZ,cAAA,EAAgB,CAAA;AAAA,MAChB,SAAA,EAAW,KAAA;AAAA,MACX,iBAAA,EAAmB;AAAA,KACrB;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,MAAMC,8BAAA,EAAa;AACpC,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,yBAAA,CAA0B,WAAW,CAAA;AAC9D,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,iBAAA,CAAkB,OAAO,CAAA;AAEzD,EAAA,MAAM,WAAW,QAAA,CACd,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,YAAY,CAAA,CAC/B,MAAA;AAAA,IACC,CAAC,YAAA,KACC,OAAO,YAAA,KAAiB,UAAA,IACxBC,gCAAc,YAAmC;AAAA,GACrD;AAEF,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,cAAA,CAAe,GAAA,CAAI,UAAU,QAAQ,CAAA;AAAA,EACvC;AAEA,EAAAF,2BAAA,EAAU,CAAE,KAAA;AAAA,IACV,sBAAsB,QAAA,CAAS,MAAM,IAAI,OAAA,CAAQ,MAAM,kBAAkB,QAAQ,CAAA,CAAA;AAAA,GACnF;AAEA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,YAAY,OAAA,CAAQ,MAAA;AAAA,IACpB,gBAAgB,QAAA,CAAS,MAAA;AAAA,IACzB,SAAA,EAAW,KAAA;AAAA,IACX,iBAAA,EAAmB;AAAA,GACrB;AACF;AAGO,SAAS,mBAAA,GAA+B;AAC7C,EAAA,OAAOD,6BAAA,EAAY;AACrB;AAGA,eAAsB,gBAAA,GAAqC;AACzD,EAAA,IAAI,CAACA,+BAAY,EAAG;AAClB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,QAAA,GAAW,MAAME,8BAAA,EAAa;AACpC,EAAA,OAAO,SAAS,cAAA,IAAkB,IAAA;AACpC","file":"auto-discovery-5IV22D5D.cjs","sourcesContent":["/**\n * Automatic service discovery from a configured build-time manifest.\n *\n * Discovery is cheap: the manifest is an in-memory module registered via\n * `configureManifest`, so this just reads its entries for the requested\n * environment, resolves the corresponding classes, and keeps those carrying DI\n * metadata. Results are cached per environment.\n */\n\nimport { hasDIMetadata } from './decorators'\nimport { getLogger } from './logger'\nimport { hasManifest, loadManifest } from './manifest'\nimport type { ConcreteConstructor } from './types'\n\n/** Configuration for auto-discovery. */\nexport interface AutoDiscoveryConfig {\n /** Environment filter; omit (or 'all') to discover every service. */\n environment?: string\n /** Whether to use the per-environment discovery cache. Defaults to true. */\n useCache?: boolean\n}\n\n/** Result of a discovery pass. */\nexport interface ServiceDiscoveryResult {\n services: ConcreteConstructor[]\n /** Number of manifest entries considered for the environment. */\n totalFound: number\n /** Number of discovered services carrying DI metadata. */\n totalDecorated: number\n fromCache: boolean\n manifestAvailable: boolean\n}\n\nconst discoveryCache = new Map<string, ConcreteConstructor[]>()\n\n/** Clear the per-environment discovery cache. */\nexport function clearDiscoveryCache(): void {\n discoveryCache.clear()\n}\n\n/**\n * Discover all DI-decorated services from the configured manifest.\n */\nexport async function discoverServices(\n config: AutoDiscoveryConfig = {}\n): Promise<ServiceDiscoveryResult> {\n const { environment, useCache = true } = config\n const cacheKey = environment ?? 'all'\n\n if (useCache) {\n const cached = discoveryCache.get(cacheKey)\n if (cached) {\n return {\n services: cached,\n totalFound: cached.length,\n totalDecorated: cached.length,\n fromCache: true,\n manifestAvailable: true\n }\n }\n }\n\n if (!hasManifest()) {\n getLogger().warn(\n 'Diadem: no manifest configured — service discovery found nothing. ' +\n 'Call configureManifest(...) at startup.'\n )\n return {\n services: [],\n totalFound: 0,\n totalDecorated: 0,\n fromCache: false,\n manifestAvailable: false\n }\n }\n\n const manifest = await loadManifest()\n const entries = manifest.getServicesForEnvironment(environment)\n const imported = await manifest.importAllServices(entries)\n\n const services = imported\n .map((item) => item.serviceClass)\n .filter(\n (serviceClass): serviceClass is ConcreteConstructor =>\n typeof serviceClass === 'function' &&\n hasDIMetadata(serviceClass as ConcreteConstructor)\n )\n\n if (useCache) {\n discoveryCache.set(cacheKey, services)\n }\n\n getLogger().debug(\n `Diadem: discovered ${services.length}/${entries.length} services for \"${cacheKey}\"`\n )\n\n return {\n services,\n totalFound: entries.length,\n totalDecorated: services.length,\n fromCache: false,\n manifestAvailable: true\n }\n}\n\n/** Whether a manifest has been configured. */\nexport function isManifestAvailable(): boolean {\n return hasManifest()\n}\n\n/** The configured manifest's `MANIFEST_STATS`, or null if unavailable. */\nexport async function getManifestStats(): Promise<unknown> {\n if (!hasManifest()) {\n return null\n }\n const manifest = await loadManifest()\n return manifest.MANIFEST_STATS ?? null\n}\n"]}
@@ -0,0 +1,68 @@
1
+ import { hasManifest, getLogger, loadManifest, hasDIMetadata } from './chunk-W7NA3ZZF.js';
2
+
3
+ // src/core/auto-discovery.ts
4
+ var discoveryCache = /* @__PURE__ */ new Map();
5
+ function clearDiscoveryCache() {
6
+ discoveryCache.clear();
7
+ }
8
+ async function discoverServices(config = {}) {
9
+ const { environment, useCache = true } = config;
10
+ const cacheKey = environment ?? "all";
11
+ if (useCache) {
12
+ const cached = discoveryCache.get(cacheKey);
13
+ if (cached) {
14
+ return {
15
+ services: cached,
16
+ totalFound: cached.length,
17
+ totalDecorated: cached.length,
18
+ fromCache: true,
19
+ manifestAvailable: true
20
+ };
21
+ }
22
+ }
23
+ if (!hasManifest()) {
24
+ getLogger().warn(
25
+ "Diadem: no manifest configured \u2014 service discovery found nothing. Call configureManifest(...) at startup."
26
+ );
27
+ return {
28
+ services: [],
29
+ totalFound: 0,
30
+ totalDecorated: 0,
31
+ fromCache: false,
32
+ manifestAvailable: false
33
+ };
34
+ }
35
+ const manifest = await loadManifest();
36
+ const entries = manifest.getServicesForEnvironment(environment);
37
+ const imported = await manifest.importAllServices(entries);
38
+ const services = imported.map((item) => item.serviceClass).filter(
39
+ (serviceClass) => typeof serviceClass === "function" && hasDIMetadata(serviceClass)
40
+ );
41
+ if (useCache) {
42
+ discoveryCache.set(cacheKey, services);
43
+ }
44
+ getLogger().debug(
45
+ `Diadem: discovered ${services.length}/${entries.length} services for "${cacheKey}"`
46
+ );
47
+ return {
48
+ services,
49
+ totalFound: entries.length,
50
+ totalDecorated: services.length,
51
+ fromCache: false,
52
+ manifestAvailable: true
53
+ };
54
+ }
55
+ function isManifestAvailable() {
56
+ return hasManifest();
57
+ }
58
+ async function getManifestStats() {
59
+ if (!hasManifest()) {
60
+ return null;
61
+ }
62
+ const manifest = await loadManifest();
63
+ return manifest.MANIFEST_STATS ?? null;
64
+ }
65
+
66
+ export { clearDiscoveryCache, discoverServices, getManifestStats, isManifestAvailable };
67
+ //# sourceMappingURL=auto-discovery-RPCKK3PB.js.map
68
+ //# sourceMappingURL=auto-discovery-RPCKK3PB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/auto-discovery.ts"],"names":[],"mappings":";;;AAiCA,IAAM,cAAA,uBAAqB,GAAA,EAAmC;AAGvD,SAAS,mBAAA,GAA4B;AAC1C,EAAA,cAAA,CAAe,KAAA,EAAM;AACvB;AAKA,eAAsB,gBAAA,CACpB,MAAA,GAA8B,EAAC,EACE;AACjC,EAAA,MAAM,EAAE,WAAA,EAAa,QAAA,GAAW,IAAA,EAAK,GAAI,MAAA;AACzC,EAAA,MAAM,WAAW,WAAA,IAAe,KAAA;AAEhC,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,MAAA,GAAS,cAAA,CAAe,GAAA,CAAI,QAAQ,CAAA;AAC1C,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAO;AAAA,QACL,QAAA,EAAU,MAAA;AAAA,QACV,YAAY,MAAA,CAAO,MAAA;AAAA,QACnB,gBAAgB,MAAA,CAAO,MAAA;AAAA,QACvB,SAAA,EAAW,IAAA;AAAA,QACX,iBAAA,EAAmB;AAAA,OACrB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,aAAY,EAAG;AAClB,IAAA,SAAA,EAAU,CAAE,IAAA;AAAA,MACV;AAAA,KAEF;AACA,IAAA,OAAO;AAAA,MACL,UAAU,EAAC;AAAA,MACX,UAAA,EAAY,CAAA;AAAA,MACZ,cAAA,EAAgB,CAAA;AAAA,MAChB,SAAA,EAAW,KAAA;AAAA,MACX,iBAAA,EAAmB;AAAA,KACrB;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AACpC,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,yBAAA,CAA0B,WAAW,CAAA;AAC9D,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,iBAAA,CAAkB,OAAO,CAAA;AAEzD,EAAA,MAAM,WAAW,QAAA,CACd,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,YAAY,CAAA,CAC/B,MAAA;AAAA,IACC,CAAC,YAAA,KACC,OAAO,YAAA,KAAiB,UAAA,IACxB,cAAc,YAAmC;AAAA,GACrD;AAEF,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,cAAA,CAAe,GAAA,CAAI,UAAU,QAAQ,CAAA;AAAA,EACvC;AAEA,EAAA,SAAA,EAAU,CAAE,KAAA;AAAA,IACV,sBAAsB,QAAA,CAAS,MAAM,IAAI,OAAA,CAAQ,MAAM,kBAAkB,QAAQ,CAAA,CAAA;AAAA,GACnF;AAEA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,YAAY,OAAA,CAAQ,MAAA;AAAA,IACpB,gBAAgB,QAAA,CAAS,MAAA;AAAA,IACzB,SAAA,EAAW,KAAA;AAAA,IACX,iBAAA,EAAmB;AAAA,GACrB;AACF;AAGO,SAAS,mBAAA,GAA+B;AAC7C,EAAA,OAAO,WAAA,EAAY;AACrB;AAGA,eAAsB,gBAAA,GAAqC;AACzD,EAAA,IAAI,CAAC,aAAY,EAAG;AAClB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AACpC,EAAA,OAAO,SAAS,cAAA,IAAkB,IAAA;AACpC","file":"auto-discovery-RPCKK3PB.js","sourcesContent":["/**\n * Automatic service discovery from a configured build-time manifest.\n *\n * Discovery is cheap: the manifest is an in-memory module registered via\n * `configureManifest`, so this just reads its entries for the requested\n * environment, resolves the corresponding classes, and keeps those carrying DI\n * metadata. Results are cached per environment.\n */\n\nimport { hasDIMetadata } from './decorators'\nimport { getLogger } from './logger'\nimport { hasManifest, loadManifest } from './manifest'\nimport type { ConcreteConstructor } from './types'\n\n/** Configuration for auto-discovery. */\nexport interface AutoDiscoveryConfig {\n /** Environment filter; omit (or 'all') to discover every service. */\n environment?: string\n /** Whether to use the per-environment discovery cache. Defaults to true. */\n useCache?: boolean\n}\n\n/** Result of a discovery pass. */\nexport interface ServiceDiscoveryResult {\n services: ConcreteConstructor[]\n /** Number of manifest entries considered for the environment. */\n totalFound: number\n /** Number of discovered services carrying DI metadata. */\n totalDecorated: number\n fromCache: boolean\n manifestAvailable: boolean\n}\n\nconst discoveryCache = new Map<string, ConcreteConstructor[]>()\n\n/** Clear the per-environment discovery cache. */\nexport function clearDiscoveryCache(): void {\n discoveryCache.clear()\n}\n\n/**\n * Discover all DI-decorated services from the configured manifest.\n */\nexport async function discoverServices(\n config: AutoDiscoveryConfig = {}\n): Promise<ServiceDiscoveryResult> {\n const { environment, useCache = true } = config\n const cacheKey = environment ?? 'all'\n\n if (useCache) {\n const cached = discoveryCache.get(cacheKey)\n if (cached) {\n return {\n services: cached,\n totalFound: cached.length,\n totalDecorated: cached.length,\n fromCache: true,\n manifestAvailable: true\n }\n }\n }\n\n if (!hasManifest()) {\n getLogger().warn(\n 'Diadem: no manifest configured — service discovery found nothing. ' +\n 'Call configureManifest(...) at startup.'\n )\n return {\n services: [],\n totalFound: 0,\n totalDecorated: 0,\n fromCache: false,\n manifestAvailable: false\n }\n }\n\n const manifest = await loadManifest()\n const entries = manifest.getServicesForEnvironment(environment)\n const imported = await manifest.importAllServices(entries)\n\n const services = imported\n .map((item) => item.serviceClass)\n .filter(\n (serviceClass): serviceClass is ConcreteConstructor =>\n typeof serviceClass === 'function' &&\n hasDIMetadata(serviceClass as ConcreteConstructor)\n )\n\n if (useCache) {\n discoveryCache.set(cacheKey, services)\n }\n\n getLogger().debug(\n `Diadem: discovered ${services.length}/${entries.length} services for \"${cacheKey}\"`\n )\n\n return {\n services,\n totalFound: entries.length,\n totalDecorated: services.length,\n fromCache: false,\n manifestAvailable: true\n }\n}\n\n/** Whether a manifest has been configured. */\nexport function isManifestAvailable(): boolean {\n return hasManifest()\n}\n\n/** The configured manifest's `MANIFEST_STATS`, or null if unavailable. */\nexport async function getManifestStats(): Promise<unknown> {\n if (!hasManifest()) {\n return null\n }\n const manifest = await loadManifest()\n return manifest.MANIFEST_STATS ?? null\n}\n"]}