@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 +22 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/dist/auto-discovery-5IV22D5D.cjs +73 -0
- package/dist/auto-discovery-5IV22D5D.cjs.map +1 -0
- package/dist/auto-discovery-RPCKK3PB.js +68 -0
- package/dist/auto-discovery-RPCKK3PB.js.map +1 -0
- package/dist/chunk-72YY5X6T.cjs +683 -0
- package/dist/chunk-72YY5X6T.cjs.map +1 -0
- package/dist/chunk-FHQRDO5C.cjs +187 -0
- package/dist/chunk-FHQRDO5C.cjs.map +1 -0
- package/dist/chunk-RTX6B4YY.js +681 -0
- package/dist/chunk-RTX6B4YY.js.map +1 -0
- package/dist/chunk-W7NA3ZZF.js +169 -0
- package/dist/chunk-W7NA3ZZF.js.map +1 -0
- package/dist/cli.cjs +821 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +815 -0
- package/dist/cli.js.map +1 -0
- package/dist/container-C1FFn9_4.d.cts +249 -0
- package/dist/container-C1FFn9_4.d.ts +249 -0
- package/dist/index.cjs +145 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +304 -0
- package/dist/index.d.ts +304 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/setup/index.cjs +87 -0
- package/dist/setup/index.cjs.map +1 -0
- package/dist/setup/index.d.cts +81 -0
- package/dist/setup/index.d.ts +81 -0
- package/dist/setup/index.js +75 -0
- package/dist/setup/index.js.map +1 -0
- package/package.json +92 -0
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
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
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"]}
|