@nwire/container 0.9.2 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/awilix.d.ts +33 -0
- package/dist/awilix.js +198 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +61 -0
- package/package.json +10 -6
- package/dist/__tests__/container-list.test.d.ts +0 -2
- package/dist/__tests__/container-list.test.d.ts.map +0 -1
- package/dist/__tests__/container-list.test.js +0 -42
- package/dist/__tests__/container-list.test.js.map +0 -1
- package/dist/__tests__/cradle.test.d.ts +0 -14
- package/dist/__tests__/cradle.test.d.ts.map +0 -1
- package/dist/__tests__/cradle.test.js +0 -83
- package/dist/__tests__/cradle.test.js.map +0 -1
- package/dist/container.d.ts +0 -101
- package/dist/container.d.ts.map +0 -1
- package/dist/container.js +0 -97
- package/dist/container.js.map +0 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ import { createContainer } from "@nwire/container";
|
|
|
44
44
|
|
|
45
45
|
// Each plugin exports its cradle contribution as a type.
|
|
46
46
|
import type { AuthCradle } from "@nwire/auth"; // { "auth.user": User; … }
|
|
47
|
-
import type { DbCradle } from "@nwire/
|
|
47
|
+
import type { DbCradle } from "@nwire/drizzle"; // { "db.pg": PgClient }
|
|
48
48
|
|
|
49
49
|
// App composes the cradle (plus its own bindings).
|
|
50
50
|
type AppCradle = AuthCradle &
|
package/dist/awilix.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/container/awilix` — Awilix-backed implementation of the
|
|
3
|
+
* `Container<TCradle>` contract from the top-level `@nwire/container`
|
|
4
|
+
* entry. Scope hierarchy, lazy proxy cradle, lifetimes
|
|
5
|
+
* (singleton/transient/scoped), classes via asClass, disposers,
|
|
6
|
+
* factory-with-deps. Source-location capture wraps `register()` so
|
|
7
|
+
* Studio's `.nwire/di.json` surfaces "where was this binding made?".
|
|
8
|
+
*
|
|
9
|
+
* Import this entry only when you need a real container; consumers
|
|
10
|
+
* that only need the type contract stay on `@nwire/container`.
|
|
11
|
+
*/
|
|
12
|
+
import { type AwilixContainer } from "awilix";
|
|
13
|
+
import type { BindingEntry, Container, HealthResult, RegisterOptions } from "./index.js";
|
|
14
|
+
export type { BindingEntry, Container, HealthResult, RegisterOptions };
|
|
15
|
+
/**
|
|
16
|
+
* `AwilixBackedContainer<TCradle>` — narrowed `Container<TCradle>` that
|
|
17
|
+
* pins `raw` to the awilix container type so test code reaching through
|
|
18
|
+
* the escape hatch keeps its types.
|
|
19
|
+
*/
|
|
20
|
+
export interface AwilixBackedContainer<TCradle extends object = object> extends Container<TCradle> {
|
|
21
|
+
readonly raw: AwilixContainer<TCradle>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build a container — generic over `TCradle`. The returned value
|
|
25
|
+
* satisfies the `Container<TCradle>` interface and is backed by Awilix
|
|
26
|
+
* (PROXY-mode cradle, scope hierarchy, lifetimes, classes, disposers).
|
|
27
|
+
*
|
|
28
|
+
* type AppCradle = { logger: Logger; pg: PgClient };
|
|
29
|
+
* const root = createContainer<AppCradle>();
|
|
30
|
+
* root.register("logger", () => makeLogger(), { dispose: l => l.flush() });
|
|
31
|
+
* root.cradle.logger.log("…");
|
|
32
|
+
*/
|
|
33
|
+
export declare function createContainer<TCradle extends object = object>(): AwilixBackedContainer<TCradle>;
|
package/dist/awilix.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/container/awilix` — Awilix-backed implementation of the
|
|
3
|
+
* `Container<TCradle>` contract from the top-level `@nwire/container`
|
|
4
|
+
* entry. Scope hierarchy, lazy proxy cradle, lifetimes
|
|
5
|
+
* (singleton/transient/scoped), classes via asClass, disposers,
|
|
6
|
+
* factory-with-deps. Source-location capture wraps `register()` so
|
|
7
|
+
* Studio's `.nwire/di.json` surfaces "where was this binding made?".
|
|
8
|
+
*
|
|
9
|
+
* Import this entry only when you need a real container; consumers
|
|
10
|
+
* that only need the type contract stay on `@nwire/container`.
|
|
11
|
+
*/
|
|
12
|
+
import { createContainer as createAwilixContainer, asValue, asFunction, asClass, InjectionMode, Lifetime, } from "awilix";
|
|
13
|
+
import { captureSourceLocation } from "@nwire/messages";
|
|
14
|
+
const LIFETIME_MAP = {
|
|
15
|
+
singleton: Lifetime.SINGLETON,
|
|
16
|
+
transient: Lifetime.TRANSIENT,
|
|
17
|
+
scoped: Lifetime.SCOPED,
|
|
18
|
+
};
|
|
19
|
+
/** ES classes have non-writable `prototype`. Arrow fns have none. Regular fns have writable. */
|
|
20
|
+
function isClass(fn) {
|
|
21
|
+
if (typeof fn !== "function")
|
|
22
|
+
return false;
|
|
23
|
+
const desc = Object.getOwnPropertyDescriptor(fn, "prototype");
|
|
24
|
+
return desc !== undefined && desc.writable === false;
|
|
25
|
+
}
|
|
26
|
+
const nowMs = typeof performance !== "undefined" && typeof performance.now === "function"
|
|
27
|
+
? () => performance.now()
|
|
28
|
+
: () => Date.now();
|
|
29
|
+
// ─── Implementation ──────────────────────────────────────────────────
|
|
30
|
+
class NwireContainer {
|
|
31
|
+
raw;
|
|
32
|
+
meta = new Map();
|
|
33
|
+
order = 0;
|
|
34
|
+
disposed = false;
|
|
35
|
+
disposing;
|
|
36
|
+
constructor(awilix) {
|
|
37
|
+
this.raw =
|
|
38
|
+
awilix ??
|
|
39
|
+
createAwilixContainer({
|
|
40
|
+
injectionMode: InjectionMode.PROXY,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
get cradle() {
|
|
44
|
+
return this.raw.cradle;
|
|
45
|
+
}
|
|
46
|
+
resolve(name) {
|
|
47
|
+
return this.raw.resolve(name);
|
|
48
|
+
}
|
|
49
|
+
register(name, factory, opts) {
|
|
50
|
+
if (this.disposed) {
|
|
51
|
+
throw new Error(`@nwire/container: cannot register "${name}" on a disposed container`);
|
|
52
|
+
}
|
|
53
|
+
// Skip two frames: this `register` itself plus the synthetic `Error`
|
|
54
|
+
// frame `captureSourceLocation` already accounts for. The regex inside
|
|
55
|
+
// `captureSourceLocation` walks past any remaining `packages/nwire-*`
|
|
56
|
+
// frames (forge's app capability registration, plugin .provide(), etc.)
|
|
57
|
+
// until it lands on user code.
|
|
58
|
+
const source = captureSourceLocation(2);
|
|
59
|
+
const requestedLifetime = opts?.lifetime ?? "singleton";
|
|
60
|
+
const lifetime = LIFETIME_MAP[requestedLifetime];
|
|
61
|
+
if (isClass(factory)) {
|
|
62
|
+
this.raw.register({
|
|
63
|
+
[name]: asClass(factory).setLifetime(lifetime),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else if (typeof factory === "function") {
|
|
67
|
+
this.raw.register({
|
|
68
|
+
[name]: asFunction(factory).setLifetime(lifetime),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.raw.register({
|
|
73
|
+
[name]: asValue(factory),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
this.meta.set(name, {
|
|
77
|
+
name,
|
|
78
|
+
order: this.order++,
|
|
79
|
+
kind: requestedLifetime,
|
|
80
|
+
source,
|
|
81
|
+
dispose: opts?.dispose,
|
|
82
|
+
check: opts?.check,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
createScope() {
|
|
86
|
+
if (this.disposed) {
|
|
87
|
+
throw new Error("@nwire/container: cannot create a scope on a disposed container");
|
|
88
|
+
}
|
|
89
|
+
return new NwireContainer(this.raw.createScope());
|
|
90
|
+
}
|
|
91
|
+
has(name) {
|
|
92
|
+
return this.raw.hasRegistration(name);
|
|
93
|
+
}
|
|
94
|
+
list() {
|
|
95
|
+
const localRegistered = this.raw.registrations;
|
|
96
|
+
return Object.keys(localRegistered).map((name) => {
|
|
97
|
+
const m = this.meta.get(name);
|
|
98
|
+
// Bindings made via `.raw.register({…})` directly won't appear in
|
|
99
|
+
// `meta` — we still surface them, just without dispose/check flags.
|
|
100
|
+
return {
|
|
101
|
+
name,
|
|
102
|
+
kind: m?.kind ?? classifyByLifetime(localRegistered[name]),
|
|
103
|
+
hasDispose: m?.dispose !== undefined,
|
|
104
|
+
hasCheck: m?.check !== undefined,
|
|
105
|
+
source: m?.source,
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async dispose() {
|
|
110
|
+
if (this.disposing)
|
|
111
|
+
return this.disposing;
|
|
112
|
+
if (this.disposed)
|
|
113
|
+
return;
|
|
114
|
+
this.disposing = (async () => {
|
|
115
|
+
// LIFO — opened last, closed first.
|
|
116
|
+
const ordered = Array.from(this.meta.values())
|
|
117
|
+
.filter((m) => m.dispose !== undefined)
|
|
118
|
+
.sort((a, b) => b.order - a.order);
|
|
119
|
+
for (const m of ordered) {
|
|
120
|
+
let value;
|
|
121
|
+
try {
|
|
122
|
+
// Only resolve if instantiated — for SINGLETON Awilix returns the
|
|
123
|
+
// cached value; for TRANSIENT/SCOPED, resolution would create a
|
|
124
|
+
// fresh instance just to dispose it. For non-singletons we skip
|
|
125
|
+
// (the caller is responsible for tracking transients).
|
|
126
|
+
if (m.kind !== "singleton")
|
|
127
|
+
continue;
|
|
128
|
+
value = this.raw.resolve(m.name);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
// Resolution failure during dispose — the binding never
|
|
132
|
+
// materialised; nothing to clean up.
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.error(`[@nwire/container] resolve-during-dispose failed for "${m.name}":`, err);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
await m.dispose(value);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
// eslint-disable-next-line no-console
|
|
142
|
+
console.error(`[@nwire/container] dispose failed for "${m.name}":`, err);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Awilix's own dispose runs additional asValue.disposer hooks (if
|
|
146
|
+
// any were registered via .raw) and tears down the cradle proxy.
|
|
147
|
+
try {
|
|
148
|
+
await this.raw.dispose();
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
// eslint-disable-next-line no-console
|
|
152
|
+
console.error("[@nwire/container] awilix dispose failed:", err);
|
|
153
|
+
}
|
|
154
|
+
this.disposed = true;
|
|
155
|
+
})();
|
|
156
|
+
return this.disposing;
|
|
157
|
+
}
|
|
158
|
+
async runChecks() {
|
|
159
|
+
if (this.disposed)
|
|
160
|
+
return [];
|
|
161
|
+
const checked = Array.from(this.meta.values()).filter((m) => m.check !== undefined);
|
|
162
|
+
if (checked.length === 0)
|
|
163
|
+
return [];
|
|
164
|
+
const results = await Promise.all(checked.map(async (m) => {
|
|
165
|
+
const startedAt = nowMs();
|
|
166
|
+
try {
|
|
167
|
+
const value = this.raw.resolve(m.name);
|
|
168
|
+
await m.check(value);
|
|
169
|
+
return { name: m.name, ok: true, durationMs: nowMs() - startedAt };
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
return { name: m.name, ok: false, error: err, durationMs: nowMs() - startedAt };
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function classifyByLifetime(reg) {
|
|
179
|
+
const lifetime = reg?.lifetime;
|
|
180
|
+
if (lifetime === "TRANSIENT")
|
|
181
|
+
return "transient";
|
|
182
|
+
if (lifetime === "SCOPED")
|
|
183
|
+
return "scoped";
|
|
184
|
+
return "singleton";
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Build a container — generic over `TCradle`. The returned value
|
|
188
|
+
* satisfies the `Container<TCradle>` interface and is backed by Awilix
|
|
189
|
+
* (PROXY-mode cradle, scope hierarchy, lifetimes, classes, disposers).
|
|
190
|
+
*
|
|
191
|
+
* type AppCradle = { logger: Logger; pg: PgClient };
|
|
192
|
+
* const root = createContainer<AppCradle>();
|
|
193
|
+
* root.register("logger", () => makeLogger(), { dispose: l => l.flush() });
|
|
194
|
+
* root.cradle.logger.log("…");
|
|
195
|
+
*/
|
|
196
|
+
export function createContainer() {
|
|
197
|
+
return new NwireContainer();
|
|
198
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/container` — DI seam.
|
|
3
|
+
*
|
|
4
|
+
* The top-level entry exposes the `Container<TCradle>` contract +
|
|
5
|
+
* binding metadata types only — zero awilix dependency. Libraries
|
|
6
|
+
* that need the type contract but not the runtime import from here.
|
|
7
|
+
*
|
|
8
|
+
* `dummyContainer()` is a no-op container that throws a helpful
|
|
9
|
+
* error on `resolve()` — used as a fallback for standalone wire
|
|
10
|
+
* collections that aren't attached to a real app.
|
|
11
|
+
*
|
|
12
|
+
* The awilix-backed implementation lives at `@nwire/container/awilix`:
|
|
13
|
+
*
|
|
14
|
+
* import { createContainer } from "@nwire/container/awilix";
|
|
15
|
+
*
|
|
16
|
+
* The split keeps awilix out of the dependency tree of any consumer
|
|
17
|
+
* that just needs to type a container parameter.
|
|
18
|
+
*/
|
|
19
|
+
import type { SourceLocation } from "@nwire/messages";
|
|
20
|
+
/**
|
|
21
|
+
* One row per active registration, surfaced by `container.list()`. Studio
|
|
22
|
+
* reads this through `.nwire/di.json` (emitted by `@nwire/scan`) to render
|
|
23
|
+
* "what's in the container, and which file registered it?".
|
|
24
|
+
*/
|
|
25
|
+
export interface BindingEntry {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly kind: "singleton" | "transient" | "scoped";
|
|
28
|
+
readonly hasDispose: boolean;
|
|
29
|
+
readonly hasCheck: boolean;
|
|
30
|
+
readonly source?: SourceLocation;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Opt-in per-binding lifecycle + lifetime metadata. Plugins pass these
|
|
34
|
+
* through `bind(name, factory, opts)` in their setup closures; the
|
|
35
|
+
* container persists them so `dispose()` and `runChecks()` can drive them.
|
|
36
|
+
*/
|
|
37
|
+
export interface RegisterOptions<T> {
|
|
38
|
+
readonly dispose?: (value: T) => void | Promise<void>;
|
|
39
|
+
readonly check?: (value: T) => void | Promise<void>;
|
|
40
|
+
readonly lifetime?: "singleton" | "transient" | "scoped";
|
|
41
|
+
}
|
|
42
|
+
/** One row of `runChecks()` output — pairs the binding name with its check result. */
|
|
43
|
+
export interface HealthResult {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly ok: boolean;
|
|
46
|
+
readonly error?: unknown;
|
|
47
|
+
readonly durationMs: number;
|
|
48
|
+
}
|
|
49
|
+
/** Anything that produces a T when called with no args (factory) or with cradle (deps-aware factory). */
|
|
50
|
+
type Factory<T> = ((...args: any[]) => T) | (new (...args: any[]) => T);
|
|
51
|
+
/** Accepted value shapes for `register()`. */
|
|
52
|
+
type Registrable<T> = T | Factory<T>;
|
|
53
|
+
export interface Container<TCradle extends object = object> {
|
|
54
|
+
resolve<T = unknown, K extends string = string>(name: K): K extends keyof TCradle ? TCradle[K] : T;
|
|
55
|
+
register<T = unknown, K extends string = string>(name: K, factory: K extends keyof TCradle ? Registrable<TCradle[K]> : Registrable<T>, opts?: RegisterOptions<K extends keyof TCradle ? TCradle[K] : T>): void;
|
|
56
|
+
readonly cradle: TCradle;
|
|
57
|
+
createScope(): Container<TCradle>;
|
|
58
|
+
has(name: keyof TCradle | (string & {})): boolean;
|
|
59
|
+
list(): ReadonlyArray<BindingEntry>;
|
|
60
|
+
dispose(): Promise<void>;
|
|
61
|
+
runChecks(): Promise<readonly HealthResult[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Escape hatch — the underlying implementation handle (awilix container
|
|
64
|
+
* for the canonical impl). Typed as `unknown` here so the top-level
|
|
65
|
+
* contract carries no implementation dependency.
|
|
66
|
+
*/
|
|
67
|
+
readonly raw: unknown;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `dummyContainer()` — a no-op container that throws on every `resolve`
|
|
71
|
+
* with a clear message naming the missing binding and pointing at the
|
|
72
|
+
* canonical `createContainer` import path.
|
|
73
|
+
*
|
|
74
|
+
* Used by `@nwire/endpoint` as the fallback `containerOf(wire)` result for
|
|
75
|
+
* standalone wire collections that aren't attached to a real app, and by
|
|
76
|
+
* libraries that just want a placeholder for tests.
|
|
77
|
+
*/
|
|
78
|
+
export declare function dummyContainer<TCradle extends object = object>(): Container<TCradle>;
|
|
79
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/container` — DI seam.
|
|
3
|
+
*
|
|
4
|
+
* The top-level entry exposes the `Container<TCradle>` contract +
|
|
5
|
+
* binding metadata types only — zero awilix dependency. Libraries
|
|
6
|
+
* that need the type contract but not the runtime import from here.
|
|
7
|
+
*
|
|
8
|
+
* `dummyContainer()` is a no-op container that throws a helpful
|
|
9
|
+
* error on `resolve()` — used as a fallback for standalone wire
|
|
10
|
+
* collections that aren't attached to a real app.
|
|
11
|
+
*
|
|
12
|
+
* The awilix-backed implementation lives at `@nwire/container/awilix`:
|
|
13
|
+
*
|
|
14
|
+
* import { createContainer } from "@nwire/container/awilix";
|
|
15
|
+
*
|
|
16
|
+
* The split keeps awilix out of the dependency tree of any consumer
|
|
17
|
+
* that just needs to type a container parameter.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* `dummyContainer()` — a no-op container that throws on every `resolve`
|
|
21
|
+
* with a clear message naming the missing binding and pointing at the
|
|
22
|
+
* canonical `createContainer` import path.
|
|
23
|
+
*
|
|
24
|
+
* Used by `@nwire/endpoint` as the fallback `containerOf(wire)` result for
|
|
25
|
+
* standalone wire collections that aren't attached to a real app, and by
|
|
26
|
+
* libraries that just want a placeholder for tests.
|
|
27
|
+
*/
|
|
28
|
+
export function dummyContainer() {
|
|
29
|
+
const cradle = new Proxy({}, {
|
|
30
|
+
get(_t, prop) {
|
|
31
|
+
throw new Error(`@nwire/container: no container provided — cradle access for "${String(prop)}". ` +
|
|
32
|
+
`Use createContainer() from "@nwire/container/awilix" if you need a real container.`);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
resolve(name) {
|
|
37
|
+
throw new Error(`@nwire/container: no container provided — resolve("${name}") failed. ` +
|
|
38
|
+
`Use createContainer() from "@nwire/container/awilix" if you need a real container.`);
|
|
39
|
+
},
|
|
40
|
+
register() {
|
|
41
|
+
throw new Error(`@nwire/container: no container provided — register() not supported on dummyContainer().`);
|
|
42
|
+
},
|
|
43
|
+
cradle,
|
|
44
|
+
createScope() {
|
|
45
|
+
return dummyContainer();
|
|
46
|
+
},
|
|
47
|
+
has() {
|
|
48
|
+
return false;
|
|
49
|
+
},
|
|
50
|
+
list() {
|
|
51
|
+
return [];
|
|
52
|
+
},
|
|
53
|
+
async dispose() {
|
|
54
|
+
// no-op
|
|
55
|
+
},
|
|
56
|
+
async runChecks() {
|
|
57
|
+
return [];
|
|
58
|
+
},
|
|
59
|
+
raw: undefined,
|
|
60
|
+
};
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwire/container",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Nwire — DI container contract + Awilix-backed default. Generic over TCradle; ships scope hierarchy, lazy cradle proxy, lifetimes, disposers via Awilix.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"container",
|
|
@@ -15,12 +15,16 @@
|
|
|
15
15
|
"LICENSE"
|
|
16
16
|
],
|
|
17
17
|
"type": "module",
|
|
18
|
-
"main": "./dist/
|
|
19
|
-
"types": "./dist/
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
20
|
"exports": {
|
|
21
21
|
".": {
|
|
22
|
-
"import": "./dist/
|
|
23
|
-
"types": "./dist/
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./awilix": {
|
|
26
|
+
"import": "./dist/awilix.js",
|
|
27
|
+
"types": "./dist/awilix.d.ts"
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
"publishConfig": {
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
},
|
|
29
33
|
"dependencies": {
|
|
30
34
|
"awilix": "^12.0.4",
|
|
31
|
-
"@nwire/messages": "0.
|
|
35
|
+
"@nwire/messages": "0.10.1"
|
|
32
36
|
},
|
|
33
37
|
"devDependencies": {
|
|
34
38
|
"@types/node": "^22.19.9",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container-list.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/container-list.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { InMemoryContainer } from "../container.js";
|
|
3
|
-
describe("InMemoryContainer.list", () => {
|
|
4
|
-
it("returns every binding registered on this container with kind + source", () => {
|
|
5
|
-
const c = new InMemoryContainer();
|
|
6
|
-
c.register("config", { port: 3000 });
|
|
7
|
-
c.register("factory", () => ({ greeting: "hi" }));
|
|
8
|
-
const list = c.list();
|
|
9
|
-
expect(list).toHaveLength(2);
|
|
10
|
-
const byName = Object.fromEntries(list.map((b) => [b.name, b]));
|
|
11
|
-
expect(byName.config?.kind).toBe("singleton");
|
|
12
|
-
expect(byName.factory?.kind).toBe("transient");
|
|
13
|
-
// captureSourceLocation should produce SOME source location. The exact
|
|
14
|
-
// file the regex lands on depends on the caller — when register is
|
|
15
|
-
// invoked from user code, it's the user's file; when invoked from
|
|
16
|
-
// inside framework tests (this file lives under packages/nwire-*), the
|
|
17
|
-
// regex skips ahead to the test runner. Either way, source must exist.
|
|
18
|
-
expect(byName.config?.source).toBeDefined();
|
|
19
|
-
expect(byName.factory?.source).toBeDefined();
|
|
20
|
-
expect(typeof byName.config?.source?.line).toBe("number");
|
|
21
|
-
});
|
|
22
|
-
it("merges parent bindings with child overrides winning on name collision", () => {
|
|
23
|
-
const parent = new InMemoryContainer();
|
|
24
|
-
parent.register("a", 1);
|
|
25
|
-
parent.register("shared", "from-parent");
|
|
26
|
-
const child = parent.createScope();
|
|
27
|
-
child.register("b", 2);
|
|
28
|
-
child.register("shared", "from-child");
|
|
29
|
-
const names = new Set(child.list().map((b) => b.name));
|
|
30
|
-
expect(names).toEqual(new Set(["a", "b", "shared"]));
|
|
31
|
-
// Child override of `shared` should win — resolve confirms it.
|
|
32
|
-
expect(child.resolve("shared")).toBe("from-child");
|
|
33
|
-
});
|
|
34
|
-
it("does NOT break existing register/resolve/has API", () => {
|
|
35
|
-
const c = new InMemoryContainer();
|
|
36
|
-
c.register("answer", 42);
|
|
37
|
-
expect(c.resolve("answer")).toBe(42);
|
|
38
|
-
expect(c.has("answer")).toBe(true);
|
|
39
|
-
expect(c.has("missing")).toBe(false);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
//# sourceMappingURL=container-list.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container-list.test.js","sourceRoot":"","sources":["../../src/__tests__/container-list.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `Container<TCradle>` — Awilix-backed generic. App declares its cradle
|
|
3
|
-
* shape; plugins export type fragments the app intersects in. No global
|
|
4
|
-
* augmentation, no cross-app pollution.
|
|
5
|
-
*
|
|
6
|
-
* - declared keys resolve via both `.cradle.X` and `.resolve("X")`
|
|
7
|
-
* - wrong-shape factory for a declared key is a real type error
|
|
8
|
-
* - unknown names fall back to `<T>` generic
|
|
9
|
-
* - `.cradle` is a typed Awilix proxy: lazy resolution
|
|
10
|
-
* - child scopes inherit cradle typing
|
|
11
|
-
* - `.raw` exposes the underlying AwilixContainer for advanced cases
|
|
12
|
-
*/
|
|
13
|
-
export {};
|
|
14
|
-
//# sourceMappingURL=cradle.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cradle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cradle.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `Container<TCradle>` — Awilix-backed generic. App declares its cradle
|
|
3
|
-
* shape; plugins export type fragments the app intersects in. No global
|
|
4
|
-
* augmentation, no cross-app pollution.
|
|
5
|
-
*
|
|
6
|
-
* - declared keys resolve via both `.cradle.X` and `.resolve("X")`
|
|
7
|
-
* - wrong-shape factory for a declared key is a real type error
|
|
8
|
-
* - unknown names fall back to `<T>` generic
|
|
9
|
-
* - `.cradle` is a typed Awilix proxy: lazy resolution
|
|
10
|
-
* - child scopes inherit cradle typing
|
|
11
|
-
* - `.raw` exposes the underlying AwilixContainer for advanced cases
|
|
12
|
-
*/
|
|
13
|
-
import { describe, expect, it, expectTypeOf, vi } from "vitest";
|
|
14
|
-
import { asFunction, asValue, Lifetime } from "awilix";
|
|
15
|
-
import { createContainer } from "../container.js";
|
|
16
|
-
describe("createContainer<TCradle> — typed binding surface", () => {
|
|
17
|
-
it("resolves a declared key to its declared type via .cradle and .resolve", () => {
|
|
18
|
-
const c = createContainer();
|
|
19
|
-
c.register("config", { port: 3000, host: "0.0.0.0" });
|
|
20
|
-
c.register("logger", () => ({ log: () => { } }));
|
|
21
|
-
expectTypeOf(c.cradle.config).toEqualTypeOf();
|
|
22
|
-
expectTypeOf(c.cradle.logger).toEqualTypeOf();
|
|
23
|
-
expectTypeOf(c.resolve("config")).toEqualTypeOf();
|
|
24
|
-
expect(c.cradle.config.port).toBe(3000);
|
|
25
|
-
expect(c.resolve("logger")).toBe(c.cradle.logger); // factory.singleton() caches
|
|
26
|
-
c.cradle.logger.log("ok");
|
|
27
|
-
});
|
|
28
|
-
it("rejects a wrong-shaped registration at the type level", () => {
|
|
29
|
-
const c = createContainer();
|
|
30
|
-
// @ts-expect-error config must satisfy AppConfig, not a string
|
|
31
|
-
c.register("config", "not-config");
|
|
32
|
-
expect(c.has("config")).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
it("falls back to the <T> generic for unknown names (escape hatch)", () => {
|
|
35
|
-
const c = createContainer();
|
|
36
|
-
c.register("widget", { id: "w-1" });
|
|
37
|
-
const widget = c.resolve("widget");
|
|
38
|
-
expectTypeOf(widget).toEqualTypeOf();
|
|
39
|
-
expect(widget.id).toBe("w-1");
|
|
40
|
-
});
|
|
41
|
-
it("cradle is lazy — factories run only on first access", () => {
|
|
42
|
-
const factory = vi.fn(() => ({ log: () => { } }));
|
|
43
|
-
const c = createContainer();
|
|
44
|
-
c.register("logger", factory);
|
|
45
|
-
expect(factory).not.toHaveBeenCalled(); // ← never touched, never called
|
|
46
|
-
void c.cradle.logger;
|
|
47
|
-
expect(factory).toHaveBeenCalledTimes(1);
|
|
48
|
-
void c.cradle.logger;
|
|
49
|
-
expect(factory).toHaveBeenCalledTimes(1); // ← singleton: cached
|
|
50
|
-
});
|
|
51
|
-
it("child scope inherits TCradle typing from the parent", () => {
|
|
52
|
-
const parent = createContainer();
|
|
53
|
-
parent.register("config", { port: 8080, host: "localhost" });
|
|
54
|
-
const child = parent.createScope();
|
|
55
|
-
expectTypeOf(child.cradle.config).toEqualTypeOf();
|
|
56
|
-
expect(child.cradle.config.host).toBe("localhost");
|
|
57
|
-
});
|
|
58
|
-
it("child scope overrides parent for the scope only", () => {
|
|
59
|
-
const parent = createContainer();
|
|
60
|
-
parent.register("config", { port: 80, host: "prod" });
|
|
61
|
-
const child = parent.createScope();
|
|
62
|
-
child.register("config", { port: 99, host: "test" });
|
|
63
|
-
expect(child.cradle.config.host).toBe("test");
|
|
64
|
-
expect(parent.cradle.config.host).toBe("prod");
|
|
65
|
-
});
|
|
66
|
-
it(".raw exposes the underlying AwilixContainer for advanced cases", () => {
|
|
67
|
-
const c = createContainer();
|
|
68
|
-
// Use Awilix's full surface directly — lifetimes, disposers, factory-with-deps.
|
|
69
|
-
c.raw.register({
|
|
70
|
-
logger: asValue({ log: () => { } }),
|
|
71
|
-
config: asFunction(() => ({ port: 1, host: "x" })).setLifetime(Lifetime.SINGLETON),
|
|
72
|
-
});
|
|
73
|
-
expect(c.cradle.logger).toBeDefined();
|
|
74
|
-
expect(c.cradle.config.port).toBe(1);
|
|
75
|
-
});
|
|
76
|
-
it("untyped container works with default <TCradle = object>", () => {
|
|
77
|
-
const c = createContainer();
|
|
78
|
-
c.register("anything", 42);
|
|
79
|
-
expect(c.resolve("anything")).toBe(42);
|
|
80
|
-
expect(c.has("anything")).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
//# sourceMappingURL=cradle.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cradle.test.js","sourceRoot":"","sources":["../../src/__tests__/cradle.test.ts"],"names":[],"mappings":"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"}
|
package/dist/container.d.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@nwire/container` — DI seam.
|
|
3
|
-
*
|
|
4
|
-
* Generic over a `TCradle` shape (Awilix-style). Apps declare their cradle
|
|
5
|
-
* type at the boot site; plugins export type fragments the app intersects
|
|
6
|
-
* in. Nothing is globally augmented.
|
|
7
|
-
*
|
|
8
|
-
* type AppCradle = AuthCradle & DbCradle & { config: AppConfig };
|
|
9
|
-
* const c = createContainer<AppCradle>();
|
|
10
|
-
* c.register("auth.user", user); // typed
|
|
11
|
-
* c.cradle["auth.user"] // → User, autocomplete + lazy
|
|
12
|
-
* c.resolve("auth.user") // → User, escape hatch
|
|
13
|
-
*
|
|
14
|
-
* Implementation is Awilix-backed: scope hierarchy, lazy proxy cradle,
|
|
15
|
-
* lifetimes (singleton/transient/scoped), disposers, factory-with-deps.
|
|
16
|
-
* Source-location capture wraps `register()` so Studio's `.nwire/di.json`
|
|
17
|
-
* surfaces "where was this binding made?".
|
|
18
|
-
*
|
|
19
|
-
* Apps consume `createContainer<TCradle>()` as the canonical factory.
|
|
20
|
-
*/
|
|
21
|
-
import { type AwilixContainer } from "awilix";
|
|
22
|
-
import { type SourceLocation } from "@nwire/messages";
|
|
23
|
-
/**
|
|
24
|
-
* One row per active registration, surfaced by `container.list()`. Studio
|
|
25
|
-
* reads this through `.nwire/di.json` (emitted by `@nwire/scan`) to render
|
|
26
|
-
* "what's in the container, and which file registered it?".
|
|
27
|
-
*
|
|
28
|
-
* `kind` reflects how the value was stored: a function factory under
|
|
29
|
-
* `register()` is `"transient"` (re-evaluated on every resolve); a plain
|
|
30
|
-
* value is `"singleton"`. Awilix's full lifetime spectrum
|
|
31
|
-
* (singleton/transient/scoped) is available via `.raw.register({…})`.
|
|
32
|
-
*/
|
|
33
|
-
export interface BindingEntry {
|
|
34
|
-
readonly name: string;
|
|
35
|
-
readonly kind: "singleton" | "transient";
|
|
36
|
-
readonly source?: SourceLocation;
|
|
37
|
-
}
|
|
38
|
-
export interface Container<TCradle extends object = object> {
|
|
39
|
-
/**
|
|
40
|
-
* Imperative lookup by name. Throws when unregistered.
|
|
41
|
-
*
|
|
42
|
-
* Names declared on `TCradle` resolve to their declared type. Unknown
|
|
43
|
-
* names default to `unknown` — pass an explicit `<T>` to override.
|
|
44
|
-
*/
|
|
45
|
-
resolve<T = unknown, K extends string = string>(name: K): K extends keyof TCradle ? TCradle[K] : T;
|
|
46
|
-
/**
|
|
47
|
-
* Register a value or factory under a name. Last-write wins.
|
|
48
|
-
*
|
|
49
|
-
* - Plain value → Awilix `asValue(v)` (singleton-equivalent).
|
|
50
|
-
* - Function `() => T` → Awilix `asFunction(fn).singleton()` (cached after first resolve).
|
|
51
|
-
*
|
|
52
|
-
* Apps that need Awilix's full lifetime / disposer / factory-with-deps
|
|
53
|
-
* surface use `container.raw.register({…})` directly — the underlying
|
|
54
|
-
* `AwilixContainer` is exposed via `.raw`.
|
|
55
|
-
*/
|
|
56
|
-
register<T = unknown, K extends string = string>(name: K, factory: K extends keyof TCradle ? TCradle[K] | (() => TCradle[K]) : T | (() => T)): void;
|
|
57
|
-
/**
|
|
58
|
-
* Typed lazy proxy. `container.cradle["auth.user"]` resolves through the
|
|
59
|
-
* same machinery `container.resolve("auth.user")` uses — bindings are
|
|
60
|
-
* resolved only on access, so untouched ones never instantiate.
|
|
61
|
-
* Awilix's PROXY-mode cradle, surfaced under our typed interface.
|
|
62
|
-
*/
|
|
63
|
-
readonly cradle: TCradle;
|
|
64
|
-
/**
|
|
65
|
-
* Create a child scope that inherits parent registrations but lets the
|
|
66
|
-
* caller override or register locally. Used for per-request scopes —
|
|
67
|
-
* each HTTP request gets a scoped container so per-request values
|
|
68
|
-
* (envelope, user, tenant) don't leak across requests.
|
|
69
|
-
*/
|
|
70
|
-
createScope(): Container<TCradle>;
|
|
71
|
-
/** True when a name is registered (either locally or on a parent). */
|
|
72
|
-
has(name: keyof TCradle | (string & {})): boolean;
|
|
73
|
-
/**
|
|
74
|
-
* Snapshot every binding visible to this container. Purely
|
|
75
|
-
* introspective — never throws, never resolves factories. Studio + CLI
|
|
76
|
-
* `nwire cache` use this to surface the DI surface.
|
|
77
|
-
*
|
|
78
|
-
* Optional so external `Container` adapters stay valid without
|
|
79
|
-
* re-implementing introspection.
|
|
80
|
-
*/
|
|
81
|
-
list?(): ReadonlyArray<BindingEntry>;
|
|
82
|
-
/**
|
|
83
|
-
* Escape hatch — the underlying Awilix container. Use when you need
|
|
84
|
-
* Awilix-specific surface (`asClass`, scoped lifetimes, disposers,
|
|
85
|
-
* `loadModules`, …). Apps that stay on `register` + `cradle` + `resolve`
|
|
86
|
-
* never touch this.
|
|
87
|
-
*/
|
|
88
|
-
readonly raw: AwilixContainer<TCradle>;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Build a container — generic over `TCradle`. The returned value
|
|
92
|
-
* satisfies the `Container<TCradle>` interface and is backed by Awilix
|
|
93
|
-
* (PROXY-mode cradle, scope hierarchy, lifetimes, disposers).
|
|
94
|
-
*
|
|
95
|
-
* type AppCradle = { logger: Logger; pg: PgClient };
|
|
96
|
-
* const root = createContainer<AppCradle>();
|
|
97
|
-
* root.register("logger", asValue(logger));
|
|
98
|
-
* root.cradle.logger.log("…");
|
|
99
|
-
*/
|
|
100
|
-
export declare function createContainer<TCradle extends object = object>(): Container<TCradle>;
|
|
101
|
-
//# sourceMappingURL=container.d.ts.map
|
package/dist/container.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE7E;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED,MAAM,WAAW,SAAS,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM;IACxD;;;;;OAKG;IACH,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5C,IAAI,EAAE,CAAC,GACN,CAAC,SAAS,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAE5C;;;;;;;;;OASG;IACH,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,EAC7C,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,CAAC,SAAS,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GACjF,IAAI,CAAC;IAER;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,WAAW,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC;IAElC,sEAAsE;IACtE,GAAG,CAAC,IAAI,EAAE,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IAElD;;;;;;;OAOG;IACH,IAAI,CAAC,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;IAErC;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;CACxC;AA8ED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,KAAK,SAAS,CAAC,OAAO,CAAC,CAErF"}
|
package/dist/container.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@nwire/container` — DI seam.
|
|
3
|
-
*
|
|
4
|
-
* Generic over a `TCradle` shape (Awilix-style). Apps declare their cradle
|
|
5
|
-
* type at the boot site; plugins export type fragments the app intersects
|
|
6
|
-
* in. Nothing is globally augmented.
|
|
7
|
-
*
|
|
8
|
-
* type AppCradle = AuthCradle & DbCradle & { config: AppConfig };
|
|
9
|
-
* const c = createContainer<AppCradle>();
|
|
10
|
-
* c.register("auth.user", user); // typed
|
|
11
|
-
* c.cradle["auth.user"] // → User, autocomplete + lazy
|
|
12
|
-
* c.resolve("auth.user") // → User, escape hatch
|
|
13
|
-
*
|
|
14
|
-
* Implementation is Awilix-backed: scope hierarchy, lazy proxy cradle,
|
|
15
|
-
* lifetimes (singleton/transient/scoped), disposers, factory-with-deps.
|
|
16
|
-
* Source-location capture wraps `register()` so Studio's `.nwire/di.json`
|
|
17
|
-
* surfaces "where was this binding made?".
|
|
18
|
-
*
|
|
19
|
-
* Apps consume `createContainer<TCradle>()` as the canonical factory.
|
|
20
|
-
*/
|
|
21
|
-
import { createContainer as createAwilixContainer, asValue, asFunction, InjectionMode, } from "awilix";
|
|
22
|
-
import { captureSourceLocation } from "@nwire/messages";
|
|
23
|
-
/** Per-binding source-location capture, keyed by the underlying Awilix container. */
|
|
24
|
-
const sourceByContainer = new WeakMap();
|
|
25
|
-
class NwireContainer {
|
|
26
|
-
raw;
|
|
27
|
-
constructor(awilix) {
|
|
28
|
-
this.raw =
|
|
29
|
-
awilix ??
|
|
30
|
-
createAwilixContainer({
|
|
31
|
-
injectionMode: InjectionMode.PROXY,
|
|
32
|
-
});
|
|
33
|
-
if (!sourceByContainer.has(this.raw)) {
|
|
34
|
-
sourceByContainer.set(this.raw, new Map());
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
get cradle() {
|
|
38
|
-
return this.raw.cradle;
|
|
39
|
-
}
|
|
40
|
-
resolve(name) {
|
|
41
|
-
return this.raw.resolve(name);
|
|
42
|
-
}
|
|
43
|
-
register(name, factory) {
|
|
44
|
-
// Skip two frames: this `register` itself plus the synthetic `Error`
|
|
45
|
-
// frame `captureSourceLocation` already accounts for. The regex inside
|
|
46
|
-
// `captureSourceLocation` walks past any remaining `packages/nwire-*`
|
|
47
|
-
// frames (forge's app capability registration, plugin .provide(), etc.)
|
|
48
|
-
// until it lands on user code.
|
|
49
|
-
const source = captureSourceLocation(2);
|
|
50
|
-
const sources = sourceByContainer.get(this.raw);
|
|
51
|
-
if (source && sources)
|
|
52
|
-
sources.set(name, source);
|
|
53
|
-
if (typeof factory === "function") {
|
|
54
|
-
this.raw.register({
|
|
55
|
-
[name]: asFunction(factory).singleton(),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
this.raw.register({
|
|
60
|
-
[name]: asValue(factory),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
createScope() {
|
|
65
|
-
return new NwireContainer(this.raw.createScope());
|
|
66
|
-
}
|
|
67
|
-
has(name) {
|
|
68
|
-
return this.raw.hasRegistration(name);
|
|
69
|
-
}
|
|
70
|
-
list() {
|
|
71
|
-
const sources = sourceByContainer.get(this.raw) ?? new Map();
|
|
72
|
-
const registrations = this.raw.registrations;
|
|
73
|
-
return Object.keys(registrations).map((name) => {
|
|
74
|
-
// Awilix exposes `lifetime` on each registration; classify by it.
|
|
75
|
-
// `SINGLETON` (which we use for asFunction(...).singleton()) and
|
|
76
|
-
// `TRANSIENT` are the two we surface; SCOPED collapses into "transient"
|
|
77
|
-
// for the Studio view.
|
|
78
|
-
const reg = registrations[name].lifetime;
|
|
79
|
-
const kind = reg === "TRANSIENT" ? "transient" : "singleton";
|
|
80
|
-
return { name, kind, source: sources.get(name) };
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Build a container — generic over `TCradle`. The returned value
|
|
86
|
-
* satisfies the `Container<TCradle>` interface and is backed by Awilix
|
|
87
|
-
* (PROXY-mode cradle, scope hierarchy, lifetimes, disposers).
|
|
88
|
-
*
|
|
89
|
-
* type AppCradle = { logger: Logger; pg: PgClient };
|
|
90
|
-
* const root = createContainer<AppCradle>();
|
|
91
|
-
* root.register("logger", asValue(logger));
|
|
92
|
-
* root.cradle.logger.log("…");
|
|
93
|
-
*/
|
|
94
|
-
export function createContainer() {
|
|
95
|
-
return new NwireContainer();
|
|
96
|
-
}
|
|
97
|
-
//# sourceMappingURL=container.js.map
|
package/dist/container.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"}
|