@decohere/crucible 0.0.1-dev.20 → 0.0.1-dev.20261105.54

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 (48) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +354 -0
  3. package/commonjs/src/const.js +5 -7
  4. package/commonjs/src/decorators/index.js +0 -2
  5. package/commonjs/src/decorators/inject.decorator.js +28 -15
  6. package/commonjs/src/decorators/injectable.dectorator.js +0 -2
  7. package/commonjs/src/index.js +0 -2
  8. package/commonjs/src/models/service-key.js +20 -0
  9. package/commonjs/src/models/service-registration.model.js +1 -6
  10. package/commonjs/src/services/container.js +207 -0
  11. package/commonjs/src/services/index.js +2 -4
  12. package/commonjs/src/services/service-container-builder.js +67 -0
  13. package/commonjs/src/services/service-registry.js +29 -22
  14. package/commonjs/src/types/errors.js +0 -2
  15. package/commonjs/src/types/index.js +1 -2
  16. package/commonjs/src/types/types.js +0 -2
  17. package/es6/src/const.js +3 -5
  18. package/es6/src/decorators/index.js +0 -2
  19. package/es6/src/decorators/inject.decorator.js +27 -14
  20. package/es6/src/decorators/injectable.dectorator.js +0 -2
  21. package/es6/src/index.js +0 -2
  22. package/es6/src/models/service-key.js +10 -0
  23. package/es6/src/models/service-registration.model.js +1 -6
  24. package/es6/src/services/container.js +197 -0
  25. package/es6/src/services/index.js +2 -4
  26. package/es6/src/services/service-container-builder.js +57 -0
  27. package/es6/src/services/service-registry.js +34 -23
  28. package/es6/src/types/errors.js +0 -2
  29. package/es6/src/types/index.js +1 -2
  30. package/es6/src/types/types.js +0 -2
  31. package/package.json +11 -3
  32. package/types/src/const.d.ts +1 -1
  33. package/types/src/decorators/inject.decorator.d.ts +17 -2
  34. package/types/src/models/service-key.d.ts +9 -0
  35. package/types/src/models/service-registration.model.d.ts +5 -4
  36. package/types/src/services/{crucible.d.ts → container.d.ts} +16 -5
  37. package/types/src/services/index.d.ts +2 -2
  38. package/types/src/services/{crucible-builder.d.ts → service-container-builder.d.ts} +9 -5
  39. package/types/src/services/service-registry.d.ts +12 -6
  40. package/types/src/types/index.d.ts +1 -0
  41. package/types/src/types/types.d.ts +1 -1
  42. package/commonjs/src/models/registration-key.model.js +0 -31
  43. package/commonjs/src/services/crucible-builder.js +0 -68
  44. package/commonjs/src/services/crucible.js +0 -165
  45. package/es6/src/models/registration-key.model.js +0 -21
  46. package/es6/src/services/crucible-builder.js +0 -58
  47. package/es6/src/services/crucible.js +0 -155
  48. package/types/src/models/registration-key.model.d.ts +0 -7
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025-2026 Typesmiths
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,354 @@
1
+ # @decohere/crucible
2
+
3
+ Lightweight dependency injection for Decohere. Register classes (or tokens) on a `ServiceContainerBuilder`, `build()` a container, then `resolve()` graphs of constructor-injected services.
4
+
5
+ Higher layers (for example `@decohere/http`'s `Builder.withServices`) register on a `ServiceContainerBuilder`. You can also build and resolve from a container yourself.
6
+
7
+ ## When to use
8
+
9
+ | Use `@decohere/crucible` | Do not use it |
10
+ |---|---|
11
+ | Constructor-injected services and controllers | Replacing the HTTP, config or data packages — those compose *on top of* the container |
12
+ | Tokens for interfaces, config objects, and “many of this kind” | Auto-scanning folders or Nest-style modules (not in this package) |
13
+ | Request / singleton / transient lifetimes and `using` disposal | Compile-time graph checking (the graph is checked when a service is first resolved) |
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm i @decohere/crucible
19
+ ```
20
+
21
+ ```ts
22
+ import { ServiceContainerBuilder, LifetimeScopeToken, Scopes } from "@decohere/crucible";
23
+ import { inject, injectable } from "@decohere/crucible/decorators";
24
+ ```
25
+
26
+ Decorator metadata is required. Enable it in `tsconfig.json` (the Decohere scaffold and this repo already do):
27
+
28
+ ```json
29
+ {
30
+ "compilerOptions": {
31
+ "experimentalDecorators": true,
32
+ "emitDecoratorMetadata": true
33
+ }
34
+ }
35
+ ```
36
+
37
+ `@decohere/core/reflection` loads `reflect-metadata`. Import `@decohere/crucible` (or `@decohere/core`) before constructing decorated classes.
38
+
39
+ ---
40
+
41
+ ## Constructor injection — `@inject` is usually unnecessary
42
+
43
+ If the constructor parameter is a **concrete class**, the container reads `design:paramtypes` and resolves that class. Mark the *consumer* `@injectable()` so TypeScript/SWC emit that metadata. Do **not** add `@inject` for that parameter.
44
+
45
+ ```ts
46
+ import { injectable } from "@decohere/crucible/decorators";
47
+ import { SampleService } from "./sample.service.js";
48
+
49
+ @injectable()
50
+ export class HelloController {
51
+ public constructor(private readonly sample: SampleService) {}
52
+ }
53
+ ```
54
+
55
+ ```ts
56
+ import { ServiceContainerBuilder } from "@decohere/crucible";
57
+
58
+ const builder = new ServiceContainerBuilder();
59
+ builder.registerSingleton(SampleService);
60
+ builder.registerRequestScoped(HelloController);
61
+ const container = builder.build();
62
+
63
+ using scope = container.beginScope("req");
64
+ const hello = scope.resolve(HelloController);
65
+ ```
66
+
67
+ `@injectable()` is an empty class decorator. Its job is to force `design:paramtypes` onto the constructor. Any other class decorator (`@Controller()`, `@Trace`, …) also does that; `@injectable()` is the one to use when there is nothing else.
68
+
69
+ ### Do not `import type` injected classes
70
+
71
+ `import type` is erased at compile time. `emitDecoratorMetadata` then records `Object` (or nothing) instead of the constructor. The container cannot tell what to inject and **throws `ResolveError`** when the consumer is resolved, naming the parameter and telling you to add `@inject`.
72
+
73
+ ```ts
74
+ // Wrong — SampleService does not exist at runtime, paramtypes collapse to Object
75
+ import type { SampleService } from "./sample.service.js";
76
+
77
+ // Right — value import keeps the constructor for reflect-metadata
78
+ import { SampleService } from "./sample.service.js";
79
+ ```
80
+
81
+ `import type` is correct for **interfaces** and type-only helpers. Those have no runtime constructor; pair them with `@inject(token)` (see below).
82
+
83
+ Linters that prefer type-only imports (`import type` / `import { type X }`) will flag a class that appears unused except as a constructor type. Keep the value import and suppress that rule locally — for example with oxlint (or Biome, or ESLint):
84
+
85
+ ```ts
86
+ // oxlint-disable-next-line typescript/consistent-type-imports -- constructor injection needs the class at runtime
87
+ import { Workspace } from "@decohere/workspace";
88
+ ```
89
+
90
+ That pattern is used throughout Forge (`Build`, `Clean`, …). Same idea applies to ESLint `consistent-type-imports` or equivalent.
91
+
92
+ ---
93
+
94
+ ## When you *do* need `@inject`
95
+
96
+ `@inject(key)` overrides the paramtypes lookup with an explicit key: a class constructor, string, or symbol. Use it when there is no useful constructor in `design:paramtypes`.
97
+
98
+ | Situation | Why `@inject` |
99
+ |---|---|
100
+ | Interface or type alias (`ISampleStore`) | No runtime class; paramtypes is `Object` |
101
+ | String / symbol token | The service is registered under the token, not the class |
102
+ | Array of implementations (`Transformer[]`) | Paramtypes is `Array`, which says nothing about the element type |
103
+ | Paramtypes collapsed to `Object` (the class's import was dropped) | `@inject(TheClass)` uses the class as a value, so its import is kept |
104
+
105
+ ```ts
106
+ import { inject, injectable } from "@decohere/crucible/decorators";
107
+
108
+ export const SampleStoreToken = Symbol("SampleStore");
109
+
110
+ export interface ISampleStore {
111
+ get(id: string): Promise<unknown>;
112
+ }
113
+
114
+ @injectable()
115
+ export class SampleService {
116
+ public constructor(
117
+ @inject(SampleStoreToken) private readonly store: ISampleStore,
118
+ ) {}
119
+ }
120
+ ```
121
+
122
+ Keep tokens in a `const.ts` (or next to the interface). Register against the same token:
123
+
124
+ ```ts
125
+ builder.registerSingleton(SampleStoreToken, PostgresSampleStore);
126
+ // or an already-built instance:
127
+ builder.registerSingleton(SampleStoreToken, store);
128
+ ```
129
+
130
+ `@inject` takes `string | symbol | Constructable`. To inject a class, pass the class: `@inject("SampleService")` does **not** find `SampleService` (see [Keys](#registration-and-lifetimes)). Arrays use `resolveMany` automatically when the reflected type is `Array`. For a dependency that may be absent, add `@optional()` (see [Optional / missing](#optional--missing-dependencies)).
131
+
132
+ ---
133
+
134
+ ## Registration and lifetimes
135
+
136
+ Keys match by **identity** ([ADR 0013](../../docs/adr/0013-container-keys-and-lifetimes.md)): a class matches only itself, a symbol only itself, a string only an equal string. So `resolve("SampleService")` does not find `registerSingleton(SampleService)`, `Symbol("TokenService")` does not collide with a class `TokenService`, two classes with the same name in different modules stay apart, and minified class names still work.
137
+
138
+ Registering the same class (or the same type on a token) twice **throws** `RegistrationError` — silent last-wins hides accidental double-registers. Replace it explicitly:
139
+
140
+ ```ts
141
+ builder.override.registerSingleton(SampleService);
142
+ builder.override.registerTransient(SampleService);
143
+ ```
144
+
145
+ `builder.override` is a registrar with replace already bound — the builder itself is not mutated. Two different types on one token is still multi-bind: `resolve` throws `ResolveError`, `resolveMany` returns both.
146
+
147
+ | Lifetime | Cached |
148
+ |---|---|
149
+ | Singleton | On the root container — shared with every nested scope |
150
+ | Request | On the current scope — new instance per `beginScope` |
151
+ | Transient | Never — new instance every `resolve` |
152
+
153
+ A singleton may not depend on a request-scoped service, directly or through transients: it would keep the first request's instance forever (a "captive dependency", e.g. one database connection for the life of the process). Resolving one throws `ResolveError` naming the chain. Make the consumer request-scoped, or inject `LifetimeScopeToken` and resolve the request-scoped service per request. `LifetimeScopeToken` itself is allowed: a singleton gets the root container.
154
+
155
+ A dependency cycle (`A → B → A`) throws `ResolveError` listing the cycle.
156
+
157
+ ```ts
158
+ builder.registerSingleton(SampleService);
159
+ builder.registerSingleton(Token, SampleService);
160
+ builder.registerSingleton(Token, existingInstance);
161
+
162
+ builder.registerRequestScoped(HelloController);
163
+ builder.registerRequestScoped(Token, HelloController);
164
+ builder.registerRequestScoped(Token, () => createConnection());
165
+
166
+ builder.registerTransient(Helper);
167
+ builder.registerTransient(Token, Helper);
168
+ ```
169
+
170
+ `registerType(type, scope?)` and `registerTypes(...)` are the same registrations without the lifetime in the method name (default **transient**). `Scopes.singleton` is the string `"single-instance"`.
171
+
172
+ HTTP (`@decohere/http`): controllers are **request-scoped** (one per request). Services are usually **singletons**.
173
+
174
+ `registerSingleton(Class, instance)` binds an existing object under the class. The CLI does this for the workspace:
175
+
176
+ ```ts
177
+ builder.registerSingleton(workspace.constructor, workspace);
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Recipes
183
+
184
+ ### Many implementations of one token
185
+
186
+ Register several types under the same token. Inject them as an array with `@inject(Token)`.
187
+
188
+ ```ts
189
+ export const TransformerKey = Symbol("TransformerKey");
190
+
191
+ builder.registerSingleton(TransformerKey, SwcTranspiler);
192
+ builder.registerSingleton(TransformerKey, TypeEmitter);
193
+ ```
194
+
195
+ ```ts
196
+ @injectable()
197
+ export class Build {
198
+ public constructor(
199
+ @inject(TransformerKey) private readonly transformers: Transformer[],
200
+ ) {}
201
+ }
202
+ ```
203
+
204
+ `resolve(TransformerKey)` **throws** `ResolveError` when more than one registration exists. Use `resolveMany(TransformerKey)` or constructor injection of an array.
205
+
206
+ ### Request scopes and disposal
207
+
208
+ ```ts
209
+ builder.registerRequestScoped(UnitOfWork);
210
+ builder.registerSingleton(Clock);
211
+ const container = builder.build();
212
+
213
+ {
214
+ using scope = container.beginScope("req-1");
215
+ const uow = scope.resolve(UnitOfWork); // per-scope
216
+ const clock = scope.resolve(Clock); // same singleton as the root
217
+ } // scope[Symbol.dispose]() — request-scoped instances are disposed
218
+
219
+ await using scope = container.beginScope("req-2");
220
+ // async teardown via Symbol.asyncDispose if the instance implements it
221
+ ```
222
+
223
+ - Child scopes look registrations up in their parent (nothing is copied, so `beginScope` costs the same however many services are registered); they do not share the request-instance cache.
224
+ - Nested `beginScope` is allowed; each level has its own request instances.
225
+ - Named scopes: a live duplicate name throws `DuplicateScopeError`. After dispose, the name can be reused. Unnamed scopes do not collide.
226
+ - Disposing a parent does **not** dispose child scopes. Dispose the scope you created.
227
+ - Singletons are cached on the root container and shared with nested scopes. Resolving a singleton after the root is disposed throws.
228
+
229
+ `App.dispose` (`@decohere/http`) (Flux HMR / SIGINT) disposes the **root** container, then Scribe. Implement `Symbol.asyncDispose` (or `Symbol.dispose`) on long-lived services — sockets, pools, timers. Only **constructed** instances are disposed (resolve them at boot; transients are not cached). `export default app` is the composition-root contract; do not wrap it for extra teardown.
230
+
231
+ The current scope is always registered as `LifetimeScopeToken`:
232
+
233
+ ```ts
234
+ import { type LifetimeScope, LifetimeScopeToken } from "@decohere/crucible";
235
+ import { inject, injectable } from "@decohere/crucible/decorators";
236
+
237
+ @injectable()
238
+ export class ExceptionFilterHost {
239
+ public constructor(
240
+ @inject(LifetimeScopeToken) private readonly scope: LifetimeScope,
241
+ ) {}
242
+ }
243
+ ```
244
+
245
+ ### Factories
246
+
247
+ ```ts
248
+ builder.registerRequestScoped("connection", () => createConnection());
249
+ const connection = container.resolve("connection");
250
+ ```
251
+
252
+ Arrow factories are called as functions (not `new`). Use them when construction needs arguments the container should not know about.
253
+
254
+ ### Optional / missing dependencies
255
+
256
+ A missing registration **throws** `ResolveError`, naming the chain that needed it (`App → Router → Symbol(AuthorizationService)`). `resolveMany` returns `[]` instead. A `?` on the parameter type does not make it optional to the container; add `@optional()`, and the container passes `undefined` (so a default value applies):
257
+
258
+ ```ts
259
+ import { inject, injectable, optional } from "@decohere/crucible/decorators";
260
+
261
+ @injectable()
262
+ export class App {
263
+ public constructor(
264
+ @inject(HttpProviderToken) @optional() private readonly http?: HttpProvider,
265
+ ) {}
266
+ }
267
+ ```
268
+
269
+ ### HTTP apps
270
+
271
+ ```ts
272
+ import { Builder } from "@decohere/http";
273
+ import { FastifyProvider } from "@decohere/http/providers";
274
+ import { SampleService } from "./services/sample.service.js";
275
+ import { HelloController } from "./controllers/hello.controller.js";
276
+
277
+ await Builder.create()
278
+ .withHttp(FastifyProvider)
279
+ .withControllers(HelloController)
280
+ .withServices((cb) => {
281
+ cb.registerSingleton(SampleService);
282
+ })
283
+ .buildAndStart();
284
+ ```
285
+
286
+ `withHttp` / `withControllers` already register the HTTP stack (router, controllers as request-scoped, …). Application services go in `withServices`.
287
+
288
+ ### Tests
289
+
290
+ Prefer constructing the subject with explicit fakes when you do not need the container. Use `ServiceContainerBuilder` when the test *is* the wiring (scopes, tokens, many-implementations).
291
+
292
+ ```ts
293
+ const builder = new ServiceContainerBuilder();
294
+ builder.registerSingleton(SampleService, fakeSample);
295
+ builder.registerRequestScoped(HelloController);
296
+ const container = builder.build();
297
+ using scope = container.beginScope("test");
298
+ const hello = scope.resolve(HelloController);
299
+ ```
300
+
301
+ To assert constructor arguments under DI, use `spyOnConstructor` from `@decohere/testing` — a bare `vi.spyOn` / `vi.fn` constructor drops `design:paramtypes`. See [Asserting DI constructor arguments](../../docs/testing/how-tos/vitest-constructor-spy-with-di.md).
302
+
303
+ ---
304
+
305
+ ## Public API
306
+
307
+ ### `@decohere/crucible`
308
+
309
+ | Export | Role |
310
+ |---|---|
311
+ | `ServiceContainerBuilder` | Fluent registration; `build()` → `Container` |
312
+ | `Container` | The container: `resolve`, `resolveMany`, `beginScope`, `Symbol.dispose` / `Symbol.asyncDispose` |
313
+ | `IContainer` / `LifetimeScope` / `ContainerBuilder` / `ServiceProvider` | Composition interfaces |
314
+ | `Scopes` | `{ request, singleton: "single-instance", transient }` |
315
+ | `LifetimeScopeToken` | Symbol for the current scope |
316
+ | `ResolveError` | Nothing registered, several registrations for `resolve`, a cycle, a captive dependency, or a parameter with no usable type |
317
+ | `DuplicateScopeError` | `beginScope` with a live duplicate name |
318
+ | `RegistrationError` | Same type registered twice without `builder.override` |
319
+
320
+ ### `@decohere/crucible/decorators`
321
+
322
+ | Export | Role |
323
+ |---|---|
324
+ | `injectable()` | Empty class decorator; emit `design:paramtypes` |
325
+ | `inject(key)` | Parameter decorator; resolve this key instead of the reflected type |
326
+ | `optional()` | Parameter decorator; pass `undefined` when nothing is registered instead of throwing |
327
+
328
+ ---
329
+
330
+ ## Pitfalls
331
+
332
+ 1. **`import type` on an injected class** — paramtypes become `Object` → `ResolveError` asking for `@inject`. Use a value import, or `@inject(TheClass)`.
333
+ 2. **Missing `@injectable()` (or any decorator) on the consumer** — no paramtypes, constructor is called with zero arguments.
334
+ 3. **`resolve` of a multi-bound token** — throws; use `resolveMany` or inject `T[]` with `@inject(token)`.
335
+ 4. **Same type registered twice** — throws `RegistrationError`. Use `builder.override.registerSingleton(...)` (tests, plugin replacements).
336
+ 5. **A string key for a class** — `@inject("SampleService")` does not find `SampleService`. Pass the class.
337
+ 6. **Two copies of a package** — the class from one copy is not the class from the other, so a service registered with one is not found with the other. Install each package once.
338
+ 7. **A singleton depending on a request-scoped service** — throws; see [lifetimes](#registration-and-lifetimes).
339
+
340
+ ---
341
+
342
+ ## Non-goals
343
+
344
+ - Folder scanning / “declare a module and everything is registered”
345
+ - Property or setter injection (constructors only)
346
+ - Compile-time graph checking
347
+
348
+ ## Dependencies
349
+
350
+ | Package | Role |
351
+ |---|---|
352
+ | [`@decohere/core`](../core) | Reflection helpers for `design:paramtypes` (`getMeta`, `isConstructable`, …) |
353
+
354
+ Layering: [architecture](../../docs/architecture/).
@@ -9,8 +9,8 @@ function _export(target, all) {
9
9
  });
10
10
  }
11
11
  _export(exports, {
12
- get CrucibleMeta () {
13
- return CrucibleMeta;
12
+ get ContainerMeta () {
13
+ return ContainerMeta;
14
14
  },
15
15
  get LifetimeScopeToken () {
16
16
  return LifetimeScopeToken;
@@ -19,15 +19,13 @@ _export(exports, {
19
19
  return Scopes;
20
20
  }
21
21
  });
22
- const Crucible = "Crucible";
22
+ const Container = "Container";
23
23
  const LifetimeScopeToken = Symbol("LifetimeScope");
24
- const CrucibleMeta = {
25
- injecttoken: `${Crucible}:injecttoken`
24
+ const ContainerMeta = {
25
+ injecttoken: `${Container}:injecttoken`
26
26
  };
27
27
  const Scopes = {
28
28
  request: "request",
29
29
  singleton: "single-instance",
30
30
  transient: "transient"
31
31
  };
32
-
33
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2NydWNpYmxlL3NyYy9jb25zdC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IFNjb3BlIH0gZnJvbSBcIi4vdHlwZXMvdHlwZXMuanNcIjtcblxuY29uc3QgQ3J1Y2libGUgPSBcIkNydWNpYmxlXCI7XG50eXBlIENydWNpYmxlTWV0YSA9IFwiaW5qZWN0dG9rZW5cIjtcblxuZXhwb3J0IGNvbnN0IExpZmV0aW1lU2NvcGVUb2tlbiA9IFN5bWJvbChcIkxpZmV0aW1lU2NvcGVcIik7XG5cbmV4cG9ydCBjb25zdCBDcnVjaWJsZU1ldGEgPSB7XG5cdGluamVjdHRva2VuOiBgJHtDcnVjaWJsZX06aW5qZWN0dG9rZW5gLFxufSBzYXRpc2ZpZXMgUmVjb3JkPENydWNpYmxlTWV0YSwgc3RyaW5nPjtcblxuZXhwb3J0IGNvbnN0IFNjb3BlcyA9IHtcblx0cmVxdWVzdDogXCJyZXF1ZXN0XCIsXG5cdHNpbmdsZXRvbjogXCJzaW5nbGUtaW5zdGFuY2VcIixcblx0dHJhbnNpZW50OiBcInRyYW5zaWVudFwiLFxufSBzYXRpc2ZpZXMgUmVjb3JkPHN0cmluZywgU2NvcGU+O1xuIl0sIm5hbWVzIjpbIkNydWNpYmxlTWV0YSIsIkxpZmV0aW1lU2NvcGVUb2tlbiIsIlNjb3BlcyIsIkNydWNpYmxlIiwiU3ltYm9sIiwiaW5qZWN0dG9rZW4iLCJyZXF1ZXN0Iiwic2luZ2xldG9uIiwidHJhbnNpZW50Il0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7OztRQU9hQTtlQUFBQTs7UUFGQUM7ZUFBQUE7O1FBTUFDO2VBQUFBOzs7QUFUYixNQUFNQyxXQUFXO0FBR1YsTUFBTUYscUJBQXFCRyxPQUFPO0FBRWxDLE1BQU1KLGVBQWU7SUFDM0JLLGFBQWEsR0FBR0YsU0FBUyxZQUFZLENBQUM7QUFDdkM7QUFFTyxNQUFNRCxTQUFTO0lBQ3JCSSxTQUFTO0lBQ1RDLFdBQVc7SUFDWEMsV0FBVztBQUNaIn0=
@@ -17,5 +17,3 @@ function _export_star(from, to) {
17
17
  });
18
18
  return from;
19
19
  }
20
-
21
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2NydWNpYmxlL3NyYy9kZWNvcmF0b3JzL2luZGV4LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCAqIGZyb20gXCIuL2luamVjdC5kZWNvcmF0b3IuanNcIjtcbmV4cG9ydCAqIGZyb20gXCIuL2luamVjdGFibGUuZGVjdG9yYXRvci5qc1wiO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7cUJBQWM7cUJBQ0EifQ==
@@ -2,26 +2,39 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "inject", {
6
- enumerable: true,
7
- get: function() {
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get inject () {
8
13
  return inject;
14
+ },
15
+ get optional () {
16
+ return optional;
9
17
  }
10
18
  });
11
- const _reflection = require("@decohere/nexus/reflection");
19
+ const _reflection = require("@decohere/core/reflection");
12
20
  const _const = require("../const.js");
13
21
  function inject(key) {
14
- return (target, _propertyKey, idx)=>{
15
- const metadata = getExistingMeta(target);
16
- metadata.set(idx, {
22
+ return updateToken((token)=>({
23
+ ...token,
17
24
  key
18
- });
19
- (0, _reflection.defineMeta)(_const.CrucibleMeta.injecttoken, metadata, target);
20
- };
25
+ }));
21
26
  }
22
- function getExistingMeta(target) {
23
- const meta = (0, _reflection.getMeta)(_const.CrucibleMeta.injecttoken, target) ?? new Map();
24
- return meta;
27
+ function optional() {
28
+ return updateToken((token)=>({
29
+ ...token,
30
+ optional: true
31
+ }));
32
+ }
33
+ function updateToken(update) {
34
+ return (target, _propertyKey, idx)=>{
35
+ // Copy: the metadata can be inherited from a base class, which must not change.
36
+ const metadata = new Map((0, _reflection.getMeta)(_const.ContainerMeta.injecttoken, target));
37
+ metadata.set(idx, update(metadata.get(idx)));
38
+ (0, _reflection.defineMeta)(_const.ContainerMeta.injecttoken, metadata, target);
39
+ };
25
40
  }
26
-
27
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2NydWNpYmxlL3NyYy9kZWNvcmF0b3JzL2luamVjdC5kZWNvcmF0b3IudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZGVmaW5lTWV0YSwgZ2V0TWV0YSB9IGZyb20gXCJAZGVjb2hlcmUvbmV4dXMvcmVmbGVjdGlvblwiO1xuaW1wb3J0IHsgQ3J1Y2libGVNZXRhIH0gZnJvbSBcIi4uL2NvbnN0LmpzXCI7XG5cbmV4cG9ydCB0eXBlIEluamVjdGlvbk1ldGFkYXRhID0gTWFwPG51bWJlciwgSW5qZWN0aW9uVG9rZW4+O1xuXG5leHBvcnQgdHlwZSBJbmplY3Rpb25Ub2tlbiA9IHtcblx0a2V5OiBzdHJpbmcgfCBzeW1ib2w7XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gaW5qZWN0KGtleTogc3RyaW5nIHwgc3ltYm9sKSB7XG5cdHJldHVybiAodGFyZ2V0OiBvYmplY3QsIF9wcm9wZXJ0eUtleTogc3RyaW5nIHwgc3ltYm9sIHwgdW5kZWZpbmVkLCBpZHg6IG51bWJlcikgPT4ge1xuXHRcdGNvbnN0IG1ldGFkYXRhID0gZ2V0RXhpc3RpbmdNZXRhKHRhcmdldCk7XG5cdFx0bWV0YWRhdGEuc2V0KGlkeCwgeyBrZXkgfSk7XG5cdFx0ZGVmaW5lTWV0YShDcnVjaWJsZU1ldGEuaW5qZWN0dG9rZW4sIG1ldGFkYXRhLCB0YXJnZXQpO1xuXHR9O1xufVxuXG5mdW5jdGlvbiBnZXRFeGlzdGluZ01ldGEodGFyZ2V0OiBvYmplY3QpIHtcblx0Y29uc3QgbWV0YSA9XG5cdFx0Z2V0TWV0YTxNYXA8bnVtYmVyLCBJbmplY3Rpb25Ub2tlbj4+KENydWNpYmxlTWV0YS5pbmplY3R0b2tlbiwgdGFyZ2V0KSA/P1xuXHRcdG5ldyBNYXA8bnVtYmVyLCBJbmplY3Rpb25Ub2tlbj4oKTtcblx0cmV0dXJuIG1ldGE7XG59XG4iXSwibmFtZXMiOlsiaW5qZWN0Iiwia2V5IiwidGFyZ2V0IiwiX3Byb3BlcnR5S2V5IiwiaWR4IiwibWV0YWRhdGEiLCJnZXRFeGlzdGluZ01ldGEiLCJzZXQiLCJkZWZpbmVNZXRhIiwiQ3J1Y2libGVNZXRhIiwiaW5qZWN0dG9rZW4iLCJtZXRhIiwiZ2V0TWV0YSIsIk1hcCJdLCJtYXBwaW5ncyI6Ijs7OzsrQkFTZ0JBOzs7ZUFBQUE7Ozs0QkFUb0I7dUJBQ1A7QUFRdEIsU0FBU0EsT0FBT0MsR0FBb0I7SUFDMUMsT0FBTyxDQUFDQyxRQUFnQkMsY0FBMkNDO1FBQ2xFLE1BQU1DLFdBQVdDLGdCQUFnQko7UUFDakNHLFNBQVNFLEdBQUcsQ0FBQ0gsS0FBSztZQUFFSDtRQUFJO1FBQ3hCTyxJQUFBQSxzQkFBVSxFQUFDQyxtQkFBWSxDQUFDQyxXQUFXLEVBQUVMLFVBQVVIO0lBQ2hEO0FBQ0Q7QUFFQSxTQUFTSSxnQkFBZ0JKLE1BQWM7SUFDdEMsTUFBTVMsT0FDTEMsSUFBQUEsbUJBQU8sRUFBOEJILG1CQUFZLENBQUNDLFdBQVcsRUFBRVIsV0FDL0QsSUFBSVc7SUFDTCxPQUFPRjtBQUNSIn0=
@@ -12,5 +12,3 @@ function injectable() {
12
12
  // no implementation for now. Just used to ensure reflect metadata is generated
13
13
  return ()=>{};
14
14
  }
15
-
16
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2NydWNpYmxlL3NyYy9kZWNvcmF0b3JzL2luamVjdGFibGUuZGVjdG9yYXRvci50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZnVuY3Rpb24gaW5qZWN0YWJsZSgpOiBDbGFzc0RlY29yYXRvciB7XG5cdC8vIG5vIGltcGxlbWVudGF0aW9uIGZvciBub3cuIEp1c3QgdXNlZCB0byBlbnN1cmUgcmVmbGVjdCBtZXRhZGF0YSBpcyBnZW5lcmF0ZWRcblx0cmV0dXJuICgpID0+IHt9O1xufVxuIl0sIm5hbWVzIjpbImluamVjdGFibGUiXSwibWFwcGluZ3MiOiI7Ozs7K0JBQWdCQTs7O2VBQUFBOzs7QUFBVCxTQUFTQTtJQUNmLCtFQUErRTtJQUMvRSxPQUFPLEtBQU87QUFDZiJ9
@@ -18,5 +18,3 @@ function _export_star(from, to) {
18
18
  });
19
19
  return from;
20
20
  }
21
-
22
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2NydWNpYmxlL3NyYy9pbmRleC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgKiBmcm9tIFwiLi9jb25zdC5qc1wiO1xuZXhwb3J0ICogZnJvbSBcIi4vc2VydmljZXMvaW5kZXguanNcIjtcbmV4cG9ydCAqIGZyb20gXCIuL3R5cGVzL2luZGV4LmpzXCI7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztxQkFBYztxQkFDQTtxQkFDQSJ9
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "describeKey", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return describeKey;
9
+ }
10
+ });
11
+ function describeKey(key) {
12
+ switch(typeof key){
13
+ case "string":
14
+ return `"${key}"`;
15
+ case "symbol":
16
+ return key.toString();
17
+ default:
18
+ return key.name || "(anonymous factory)";
19
+ }
20
+ }
@@ -19,12 +19,7 @@ class ServiceRegistration {
19
19
  this.scope = scope;
20
20
  this.isInstance = isInstance;
21
21
  }
22
- isEqual(other) {
23
- if (other.isInstance) {
24
- return false;
25
- }
22
+ /** Same class, or the same instance object. */ isEqual(other) {
26
23
  return this.type === other.type;
27
24
  }
28
25
  }
29
-
30
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2NydWNpYmxlL3NyYy9tb2RlbHMvc2VydmljZS1yZWdpc3RyYXRpb24ubW9kZWwudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHR5cGUgeyBDb25zdHJ1Y3RhYmxlLCBGdW5jdGlvblR5cGUgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL3JlZmxlY3Rpb25cIjtcbmltcG9ydCB0eXBlIHsgU2NvcGUgfSBmcm9tIFwiLi4vdHlwZXMvdHlwZXMuanNcIjtcbmltcG9ydCB0eXBlIHsgUmVnaXN0cmF0aW9uS2V5IH0gZnJvbSBcIi4vcmVnaXN0cmF0aW9uLWtleS5tb2RlbC5qc1wiO1xuXG5leHBvcnQgY2xhc3MgU2VydmljZVJlZ2lzdHJhdGlvbiB7XG5cdHB1YmxpYyBrZXk6IFJlZ2lzdHJhdGlvbktleTtcblx0cHVibGljIHNjb3BlOiBTY29wZTtcblx0cHVibGljIHR5cGU6IENvbnN0cnVjdGFibGUgfCBGdW5jdGlvblR5cGUgfCBvYmplY3Q7XG5cdHB1YmxpYyBpc0luc3RhbmNlOiBib29sZWFuO1xuXG5cdHB1YmxpYyBjb25zdHJ1Y3Rvcihcblx0XHRrZXk6IFJlZ2lzdHJhdGlvbktleSxcblx0XHR0eXBlOiBDb25zdHJ1Y3RhYmxlIHwgRnVuY3Rpb25UeXBlIHwgb2JqZWN0LFxuXHRcdHNjb3BlOiBTY29wZSxcblx0XHRpc0luc3RhbmNlOiBib29sZWFuLFxuXHQpIHtcblx0XHR0aGlzLmtleSA9IGtleTtcblx0XHR0aGlzLnR5cGUgPSB0eXBlO1xuXHRcdHRoaXMuc2NvcGUgPSBzY29wZTtcblx0XHR0aGlzLmlzSW5zdGFuY2UgPSBpc0luc3RhbmNlO1xuXHR9XG5cblx0cHVibGljIGlzRXF1YWwob3RoZXI6IFNlcnZpY2VSZWdpc3RyYXRpb24pIHtcblx0XHRpZiAob3RoZXIuaXNJbnN0YW5jZSkge1xuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH1cblxuXHRcdHJldHVybiB0aGlzLnR5cGUgPT09IG90aGVyLnR5cGU7XG5cdH1cbn1cbiJdLCJuYW1lcyI6WyJTZXJ2aWNlUmVnaXN0cmF0aW9uIiwia2V5Iiwic2NvcGUiLCJ0eXBlIiwiaXNJbnN0YW5jZSIsImlzRXF1YWwiLCJvdGhlciJdLCJtYXBwaW5ncyI6Ijs7OzsrQkFJYUE7OztlQUFBQTs7O0FBQU4sTUFBTUE7SUFDTEMsSUFBcUI7SUFDckJDLE1BQWE7SUFDYkMsS0FBNEM7SUFDNUNDLFdBQW9CO0lBRTNCLFlBQ0NILEdBQW9CLEVBQ3BCRSxJQUEyQyxFQUMzQ0QsS0FBWSxFQUNaRSxVQUFtQixDQUNsQjtRQUNELElBQUksQ0FBQ0gsR0FBRyxHQUFHQTtRQUNYLElBQUksQ0FBQ0UsSUFBSSxHQUFHQTtRQUNaLElBQUksQ0FBQ0QsS0FBSyxHQUFHQTtRQUNiLElBQUksQ0FBQ0UsVUFBVSxHQUFHQTtJQUNuQjtJQUVPQyxRQUFRQyxLQUEwQixFQUFFO1FBQzFDLElBQUlBLE1BQU1GLFVBQVUsRUFBRTtZQUNyQixPQUFPO1FBQ1I7UUFFQSxPQUFPLElBQUksQ0FBQ0QsSUFBSSxLQUFLRyxNQUFNSCxJQUFJO0lBQ2hDO0FBQ0QifQ==