@aponiajs/core 0.3.17 → 0.3.19

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/dist/index.d.mts CHANGED
@@ -31,6 +31,13 @@ declare class AponiaContainer {
31
31
  readonly graph: ModuleGraph;
32
32
  constructor(graph: ModuleGraph);
33
33
  get<T>(token: Token<T>): T;
34
+ /**
35
+ * Framework platform SPI for resolving a provider in its owning module.
36
+ * Application code should use get(), which enforces root-module visibility.
37
+ *
38
+ * @internal
39
+ */
40
+ resolveModuleProvider<T>(module: ModuleDefinition, token: Token<T>): T;
34
41
  initializeModule(module: ModuleDefinition): void;
35
42
  instantiateController<T>(module: ModuleDefinition, controller: ControllerDefinition): T;
36
43
  }
package/dist/index.mjs CHANGED
@@ -4,10 +4,15 @@ var ModuleGraph = class {
4
4
  root;
5
5
  modules;
6
6
  #moduleSet;
7
+ #providersByModule;
8
+ #exportsByModule;
9
+ #locationsByModule = /* @__PURE__ */ new Map();
7
10
  constructor(root, modules) {
8
11
  this.root = root;
9
12
  this.modules = Object.freeze([...modules]);
10
13
  this.#moduleSet = new Set(modules);
14
+ this.#providersByModule = new Map(modules.map((module) => [module, new Map(module.providers.map((provider) => [provider.provide, provider]))]));
15
+ this.#exportsByModule = new Map(modules.map((module) => [module, new Set(module.exports)]));
11
16
  }
12
17
  inspect() {
13
18
  return Object.freeze({
@@ -26,42 +31,66 @@ var ModuleGraph = class {
26
31
  module: module.id,
27
32
  token: tokenName(token)
28
33
  });
29
- return this.#locate(module, token, /* @__PURE__ */ new Set());
34
+ const cached = this.#locationsByModule.get(module)?.get(token);
35
+ if (cached) return cached;
36
+ const location = this.#locate(module, token, /* @__PURE__ */ new Set());
37
+ this.#cacheLocation(module, token, location);
38
+ return location;
30
39
  }
31
40
  #locate(module, token, visited) {
32
- const own = module.providers.find((provider) => provider.provide === token);
33
- if (own) return {
34
- module,
35
- provider: own
36
- };
41
+ const cached = this.#locationsByModule.get(module)?.get(token);
42
+ if (cached) return cached;
43
+ const own = this.#providersByModule.get(module)?.get(token);
44
+ if (own) {
45
+ const location = Object.freeze({
46
+ module,
47
+ provider: own
48
+ });
49
+ this.#cacheLocation(module, token, location);
50
+ return location;
51
+ }
37
52
  if (visited.has(module)) throw missingProvider(module, token);
38
53
  visited.add(module);
39
- const candidates = /* @__PURE__ */ new Map();
40
- for (const imported of module.imports) {
41
- if (!imported.exports.includes(token)) continue;
42
- const location = this.#locate(imported, token, new Set(visited));
43
- candidates.set(location.provider, location);
54
+ try {
55
+ const candidates = /* @__PURE__ */ new Map();
56
+ for (const imported of module.imports) {
57
+ if (!this.#exportsByModule.get(imported)?.has(token)) continue;
58
+ const location = this.#locate(imported, token, visited);
59
+ candidates.set(location.module, location);
60
+ }
61
+ if (candidates.size === 0) throw missingProvider(module, token);
62
+ if (candidates.size > 1) throw new AponiaError("AMBIGUOUS_PROVIDER", `Token "${tokenName(token)}" is exported by multiple imports of module "${module.id}".`, {
63
+ module: module.id,
64
+ token: tokenName(token),
65
+ candidates: [...candidates.values()].map((item) => item.module.id)
66
+ });
67
+ const location = candidates.values().next().value;
68
+ if (!location) throw missingProvider(module, token);
69
+ this.#cacheLocation(module, token, location);
70
+ return location;
71
+ } finally {
72
+ visited.delete(module);
44
73
  }
45
- if (candidates.size === 0) throw missingProvider(module, token);
46
- if (candidates.size > 1) throw new AponiaError("AMBIGUOUS_PROVIDER", `Token "${tokenName(token)}" is exported by multiple imports of module "${module.id}".`, {
47
- module: module.id,
48
- token: tokenName(token),
49
- candidates: [...candidates.values()].map((item) => item.module.id)
50
- });
51
- const location = candidates.values().next().value;
52
- if (!location) throw missingProvider(module, token);
53
- return location;
74
+ }
75
+ #cacheLocation(module, token, location) {
76
+ let moduleLocations = this.#locationsByModule.get(module);
77
+ if (!moduleLocations) {
78
+ moduleLocations = /* @__PURE__ */ new Map();
79
+ this.#locationsByModule.set(module, moduleLocations);
80
+ }
81
+ moduleLocations.set(token, location);
54
82
  }
55
83
  };
56
84
  function compileModuleGraph(root) {
57
85
  const modules = [];
58
- const modulesById = /* @__PURE__ */ new Map();
86
+ const modulesByIdentity = /* @__PURE__ */ new Map();
59
87
  const visiting = [];
60
88
  const visited = /* @__PURE__ */ new Set();
61
89
  const visit = (module) => {
62
- const registered = modulesById.get(module.id);
90
+ const identity = module.instanceId ?? module.id;
91
+ const registered = modulesByIdentity.get(identity);
63
92
  if (registered && registered !== module) throw new AponiaError("DUPLICATE_MODULE", `Module id "${module.id}" belongs to more than one definition.`, { module: module.id });
64
- modulesById.set(module.id, module);
93
+ modulesByIdentity.set(identity, module);
65
94
  const cycleIndex = visiting.indexOf(module);
66
95
  if (cycleIndex >= 0) {
67
96
  const cycle = [...visiting.slice(cycleIndex), module].map((item) => item.id);
@@ -147,6 +176,16 @@ var AponiaContainer = class {
147
176
  const location = this.graph.locate(this.graph.root, token);
148
177
  return this.#resolve(location);
149
178
  }
179
+ /**
180
+ * Framework platform SPI for resolving a provider in its owning module.
181
+ * Application code should use get(), which enforces root-module visibility.
182
+ *
183
+ * @internal
184
+ */
185
+ resolveModuleProvider(module, token) {
186
+ const location = this.graph.locate(module, token);
187
+ return this.#resolve(location);
188
+ }
150
189
  initializeModule(module) {
151
190
  for (const provider of module.providers) this.#resolve({
152
191
  module,
@@ -154,15 +193,17 @@ var AponiaContainer = class {
154
193
  });
155
194
  }
156
195
  instantiateController(module, controller) {
157
- if (this.#controllers.has(controller)) return this.#controllers.get(controller);
196
+ const moduleControllers = this.#controllers.get(module);
197
+ if (moduleControllers?.has(controller)) return moduleControllers.get(controller);
158
198
  const dependencies = controller.inject.map((dependency) => this.#resolve(this.graph.locate(module, dependency)));
159
199
  const instance = Reflect.construct(controller.useClass, dependencies);
160
- this.#controllers.set(controller, instance);
200
+ this.#moduleCache(this.#controllers, module).set(controller, instance);
161
201
  return instance;
162
202
  }
163
203
  #resolve(location) {
164
- if (this.#instances.has(location.provider)) return this.#instances.get(location.provider);
165
- const cycleIndex = this.#resolving.findIndex((item) => item.provider === location.provider);
204
+ const moduleInstances = this.#instances.get(location.module);
205
+ if (moduleInstances?.has(location.provider)) return moduleInstances.get(location.provider);
206
+ const cycleIndex = this.#resolving.findIndex((item) => item.module === location.module && item.provider === location.provider);
166
207
  if (cycleIndex >= 0) {
167
208
  const cycle = [...this.#resolving.slice(cycleIndex), location].map((item) => `${item.module.id}:${tokenName(item.provider.provide)}`);
168
209
  throw new AponiaError("PROVIDER_CYCLE", `Provider dependency cycle detected: ${cycle.join(" -> ")}.`, { cycle });
@@ -171,12 +212,20 @@ var AponiaContainer = class {
171
212
  try {
172
213
  const dependencies = providerDependencies(location.provider).map((dependency) => this.#resolve(this.graph.locate(location.module, dependency)));
173
214
  const instance = instantiate(location.provider, dependencies);
174
- this.#instances.set(location.provider, instance);
215
+ this.#moduleCache(this.#instances, location.module).set(location.provider, instance);
175
216
  return instance;
176
217
  } finally {
177
218
  this.#resolving.pop();
178
219
  }
179
220
  }
221
+ #moduleCache(cache, module) {
222
+ let moduleCache = cache.get(module);
223
+ if (!moduleCache) {
224
+ moduleCache = /* @__PURE__ */ new Map();
225
+ cache.set(module, moduleCache);
226
+ }
227
+ return moduleCache;
228
+ }
180
229
  };
181
230
  function createContainer(root) {
182
231
  return new AponiaContainer(compileModuleGraph(root));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aponiajs/core",
3
- "version": "0.3.17",
3
+ "version": "0.3.19",
4
4
  "description": "Module graph and dependency injection runtime for Aponia.",
5
5
  "homepage": "https://github.com/aponiajs/aponiajs#readme",
6
6
  "bugs": {
@@ -33,6 +33,6 @@
33
33
  "prepublishOnly": "bun run build"
34
34
  },
35
35
  "dependencies": {
36
- "@aponiajs/common": "0.3.16"
36
+ "@aponiajs/common": "0.3.18"
37
37
  }
38
38
  }