@nwire/container 0.7.1 → 0.8.17

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=container-list.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-list.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/container-list.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,42 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-list.test.js","sourceRoot":"","sources":["../../src/__tests__/container-list.test.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,21 @@
8
8
  *
9
9
  * See: architecture-sketch.html §05 (Foundation tier).
10
10
  */
11
+ import { type SourceLocation } from "@nwire/messages";
12
+ /**
13
+ * One row per active registration, surfaced by `container.list()`. Studio
14
+ * reads this through `.nwire/di.json` (emitted by `@nwire/scan`) to render
15
+ * "what's in the container, and which file registered it?".
16
+ *
17
+ * `kind` reflects how the value was stored: a function factory under
18
+ * `register()` is `"transient"` (re-evaluated on every resolve); a plain
19
+ * value is `"singleton"`.
20
+ */
21
+ export interface BindingEntry {
22
+ readonly name: string;
23
+ readonly kind: "singleton" | "transient";
24
+ readonly source?: SourceLocation;
25
+ }
11
26
  export interface Container {
12
27
  /** Look up a registered value by name. Throws when unregistered. */
13
28
  resolve<T = unknown>(name: string): T;
@@ -22,6 +37,17 @@ export interface Container {
22
37
  createScope(): Container;
23
38
  /** True when a name is registered (either locally or on a parent). */
24
39
  has(name: string): boolean;
40
+ /**
41
+ * Snapshot every binding visible to this container (own bindings union
42
+ * parent bindings; child overrides win on name collisions). Purely
43
+ * introspective — never throws, never resolves factories. Studio + CLI
44
+ * `nwire cache` use this to surface the DI surface; runtime code should
45
+ * stick to `resolve` / `has`.
46
+ *
47
+ * Optional so external `Container` adapters (Awilix, tsyringe, plain
48
+ * objects) stay valid without re-implementing introspection.
49
+ */
50
+ list?(): ReadonlyArray<BindingEntry>;
25
51
  }
26
52
  export declare class InMemoryContainer implements Container {
27
53
  private readonly parent?;
@@ -31,6 +57,7 @@ export declare class InMemoryContainer implements Container {
31
57
  register<T = unknown>(name: string, factory: T | (() => T)): void;
32
58
  createScope(): Container;
33
59
  has(name: string): boolean;
60
+ list(): ReadonlyArray<BindingEntry>;
34
61
  }
35
62
  /** A blank root container, ready to be populated by the wire. */
36
63
  export declare const rootContainer: Container;
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,SAAS;IACxB,oEAAoE;IACpE,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC;IAEtC,iEAAiE;IACjE,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;IAElE;;;;;OAKG;IACH,WAAW,IAAI,SAAS,CAAC;IAEzB,sEAAsE;IACtE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,qBAAa,iBAAkB,YAAW,SAAS;IAGrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAFpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgD;gBAE5C,MAAM,CAAC,EAAE,iBAAiB,YAAA;IAEvD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC;IASrC,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI;IAIjE,WAAW,IAAI,SAAS;IAIxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAI3B;AAED,iEAAiE;AACjE,eAAO,MAAM,aAAa,EAAE,SAAmC,CAAC"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE7E;;;;;;;;GAQG;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;IACxB,oEAAoE;IACpE,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC;IAEtC,iEAAiE;IACjE,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;IAElE;;;;;OAKG;IACH,WAAW,IAAI,SAAS,CAAC;IAEzB,sEAAsE;IACtE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAE3B;;;;;;;;;OASG;IACH,IAAI,CAAC,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;CACtC;AAOD,qBAAa,iBAAkB,YAAW,SAAS;IAGrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAFpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;gBAEhC,MAAM,CAAC,EAAE,iBAAiB,YAAA;IAEvD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC;IAUrC,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI;IAUjE,WAAW,IAAI,SAAS;IAIxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAK1B,IAAI,IAAI,aAAa,CAAC,YAAY,CAAC;CAepC;AAED,iEAAiE;AACjE,eAAO,MAAM,aAAa,EAAE,SAAmC,CAAC"}
package/dist/container.js CHANGED
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * See: architecture-sketch.html §05 (Foundation tier).
10
10
  */
11
+ import { captureSourceLocation } from "@nwire/messages";
11
12
  export class InMemoryContainer {
12
13
  parent;
13
14
  bindings = new Map();
@@ -15,8 +16,9 @@ export class InMemoryContainer {
15
16
  this.parent = parent;
16
17
  }
17
18
  resolve(name) {
18
- if (this.bindings.has(name)) {
19
- const v = this.bindings.get(name);
19
+ const own = this.bindings.get(name);
20
+ if (own !== undefined) {
21
+ const v = own.value;
20
22
  return typeof v === "function" ? v() : v;
21
23
  }
22
24
  if (this.parent)
@@ -24,7 +26,13 @@ export class InMemoryContainer {
24
26
  throw new Error(`Container: no binding for "${name}"`);
25
27
  }
26
28
  register(name, factory) {
27
- this.bindings.set(name, factory);
29
+ // Skip two frames: this `register` itself plus the synthetic `Error`
30
+ // frame `captureSourceLocation` already accounts for. The regex inside
31
+ // `captureSourceLocation` walks past any remaining `packages/nwire-*`
32
+ // frames (forge's app capability registration, plugin .provide(), etc.)
33
+ // until it lands on user code.
34
+ const source = captureSourceLocation(2);
35
+ this.bindings.set(name, { value: factory, source });
28
36
  }
29
37
  createScope() {
30
38
  return new InMemoryContainer(this);
@@ -34,6 +42,21 @@ export class InMemoryContainer {
34
42
  return true;
35
43
  return this.parent?.has(name) ?? false;
36
44
  }
45
+ list() {
46
+ // Walk parent chain first so child-overrides win when we re-insert.
47
+ const merged = new Map();
48
+ for (const entry of this.parent?.list?.() ?? []) {
49
+ merged.set(entry.name, entry);
50
+ }
51
+ for (const [name, binding] of this.bindings) {
52
+ merged.set(name, {
53
+ name,
54
+ kind: typeof binding.value === "function" ? "transient" : "singleton",
55
+ source: binding.source,
56
+ });
57
+ }
58
+ return Array.from(merged.values());
59
+ }
37
60
  }
38
61
  /** A blank root container, ready to be populated by the wire. */
39
62
  export const rootContainer = new InMemoryContainer();
@@ -1 +1 @@
1
- {"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH,MAAM,OAAO,iBAAiB;IAGC;IAFZ,QAAQ,GAAG,IAAI,GAAG,EAAqC,CAAC;IAEzE,YAA6B,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;IAAG,CAAC;IAE3D,OAAO,CAAc,IAAY;QAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAE,CAAa,EAAE,CAAC,CAAC,CAAE,CAAO,CAAC;QAC/D,CAAC;QACD,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAI,IAAI,CAAC,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,GAAG,CAAC,CAAC;IACzD,CAAC;IAED,QAAQ,CAAc,IAAY,EAAE,OAAsB;QACxD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAkB,CAAC,CAAC;IAC9C,CAAC;IAED,WAAW;QACT,OAAO,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;IACzC,CAAC;CACF;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,aAAa,GAAc,IAAI,iBAAiB,EAAE,CAAC"}
1
+ {"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,qBAAqB,EAAuB,MAAM,iBAAiB,CAAC;AAqD7E,MAAM,OAAO,iBAAiB;IAGC;IAFZ,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE7D,YAA6B,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;IAAG,CAAC;IAE3D,OAAO,CAAc,IAAY;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC;YACpB,OAAO,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAE,CAAa,EAAE,CAAC,CAAC,CAAE,CAAO,CAAC;QAC/D,CAAC;QACD,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAI,IAAI,CAAC,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,GAAG,CAAC,CAAC;IACzD,CAAC;IAED,QAAQ,CAAc,IAAY,EAAE,OAAsB;QACxD,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,wEAAwE;QACxE,+BAA+B;QAC/B,MAAM,MAAM,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,OAAkB,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,WAAW;QACT,OAAO,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;IACzC,CAAC;IAED,IAAI;QACF,oEAAoE;QACpE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;YAChD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE;gBACf,IAAI;gBACJ,IAAI,EAAE,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW;gBACrE,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;CACF;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,aAAa,GAAc,IAAI,iBAAiB,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nwire/container",
3
- "version": "0.7.1",
3
+ "version": "0.8.17",
4
4
  "description": "Nwire — DI container contract + InMemory default. Production adapters land as @nwire/container-awilix.",
5
5
  "keywords": [
6
6
  "container",
@@ -26,6 +26,9 @@
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
29
+ "dependencies": {
30
+ "@nwire/messages": "0.8.17"
31
+ },
29
32
  "devDependencies": {
30
33
  "@types/node": "^22.19.9",
31
34
  "typescript": "^5.9.3",