@aponiajs/core 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # @aponiajs/core
2
+
3
+ The initial Aponia runtime foundation:
4
+
5
+ - deterministic module graph compilation;
6
+ - explicit module imports and provider exports;
7
+ - value, factory, class, and alias providers;
8
+ - singleton dependency resolution without decorators or reflection;
9
+ - stable diagnostics for invalid graphs and dependency cycles.
10
+
11
+ Async providers, request scope, lifecycle hooks, HTTP, and Elysia integration are
12
+ intentionally outside this first implementation.
@@ -0,0 +1,39 @@
1
+ import { ControllerDefinition, ModuleDefinition, Provider, Token } from "@aponiajs/common";
2
+ //#region src/graph.d.ts
3
+ interface ModuleInspection {
4
+ readonly id: string;
5
+ readonly imports: readonly string[];
6
+ readonly controllers: readonly string[];
7
+ readonly providers: readonly string[];
8
+ readonly exports: readonly string[];
9
+ }
10
+ interface GraphInspection {
11
+ readonly root: string;
12
+ readonly modules: readonly ModuleInspection[];
13
+ }
14
+ interface ProviderLocation {
15
+ readonly module: ModuleDefinition;
16
+ readonly provider: Provider;
17
+ }
18
+ declare class ModuleGraph {
19
+ #private;
20
+ readonly root: ModuleDefinition;
21
+ readonly modules: readonly ModuleDefinition[];
22
+ constructor(root: ModuleDefinition, modules: readonly ModuleDefinition[]);
23
+ inspect(): GraphInspection;
24
+ locate(module: ModuleDefinition, token: Token<unknown>): ProviderLocation;
25
+ }
26
+ declare function compileModuleGraph(root: ModuleDefinition): ModuleGraph;
27
+ //#endregion
28
+ //#region src/container.d.ts
29
+ declare class AponiaContainer {
30
+ #private;
31
+ readonly graph: ModuleGraph;
32
+ constructor(graph: ModuleGraph);
33
+ get<T>(token: Token<T>): T;
34
+ initializeModule(module: ModuleDefinition): void;
35
+ instantiateController<T>(module: ModuleDefinition, controller: ControllerDefinition): T;
36
+ }
37
+ declare function createContainer(root: ModuleDefinition): AponiaContainer;
38
+ //#endregion
39
+ export { AponiaContainer, type GraphInspection, ModuleGraph, type ModuleInspection, type ProviderLocation, compileModuleGraph, createContainer };
package/dist/index.mjs ADDED
@@ -0,0 +1,193 @@
1
+ import { AponiaError, tokenName } from "@aponiajs/common";
2
+ //#region src/graph.ts
3
+ var ModuleGraph = class {
4
+ root;
5
+ modules;
6
+ #moduleSet;
7
+ constructor(root, modules) {
8
+ this.root = root;
9
+ this.modules = Object.freeze([...modules]);
10
+ this.#moduleSet = new Set(modules);
11
+ }
12
+ inspect() {
13
+ return Object.freeze({
14
+ root: this.root.id,
15
+ modules: Object.freeze(this.modules.map((module) => Object.freeze({
16
+ id: module.id,
17
+ imports: Object.freeze(module.imports.map((item) => item.id)),
18
+ controllers: Object.freeze(module.controllers.map((controller) => tokenName(controller.token))),
19
+ providers: Object.freeze(module.providers.map((provider) => tokenName(provider.provide))),
20
+ exports: Object.freeze(module.exports.map(tokenName))
21
+ })))
22
+ });
23
+ }
24
+ locate(module, token) {
25
+ if (!this.#moduleSet.has(module)) throw new AponiaError("MISSING_PROVIDER", `Module "${module.id}" is not part of the compiled graph.`, {
26
+ module: module.id,
27
+ token: tokenName(token)
28
+ });
29
+ return this.#locate(module, token, /* @__PURE__ */ new Set());
30
+ }
31
+ #locate(module, token, visited) {
32
+ const own = module.providers.find((provider) => provider.provide === token);
33
+ if (own) return {
34
+ module,
35
+ provider: own
36
+ };
37
+ if (visited.has(module)) throw missingProvider(module, token);
38
+ 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);
44
+ }
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;
54
+ }
55
+ };
56
+ function compileModuleGraph(root) {
57
+ const modules = [];
58
+ const modulesById = /* @__PURE__ */ new Map();
59
+ const visiting = [];
60
+ const visited = /* @__PURE__ */ new Set();
61
+ const visit = (module) => {
62
+ const registered = modulesById.get(module.id);
63
+ 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);
65
+ const cycleIndex = visiting.indexOf(module);
66
+ if (cycleIndex >= 0) {
67
+ const cycle = [...visiting.slice(cycleIndex), module].map((item) => item.id);
68
+ throw new AponiaError("MODULE_CYCLE", `Module import cycle detected: ${cycle.join(" -> ")}.`, { cycle });
69
+ }
70
+ if (visited.has(module)) return;
71
+ visiting.push(module);
72
+ for (const imported of module.imports) visit(imported);
73
+ visiting.pop();
74
+ validateOwnProviders(module);
75
+ visited.add(module);
76
+ modules.push(module);
77
+ };
78
+ visit(root);
79
+ const graph = new ModuleGraph(root, modules);
80
+ validateExports(graph);
81
+ validateDependencies(graph);
82
+ validateControllers(graph);
83
+ return graph;
84
+ }
85
+ function validateOwnProviders(module) {
86
+ const tokens = /* @__PURE__ */ new Set();
87
+ for (const provider of module.providers) {
88
+ if (tokens.has(provider.provide)) throw new AponiaError("DUPLICATE_PROVIDER", `Module "${module.id}" declares token "${tokenName(provider.provide)}" more than once.`, {
89
+ module: module.id,
90
+ token: tokenName(provider.provide)
91
+ });
92
+ tokens.add(provider.provide);
93
+ }
94
+ }
95
+ function validateExports(graph) {
96
+ for (const module of graph.modules) for (const token of module.exports) try {
97
+ graph.locate(module, token);
98
+ } catch (error) {
99
+ if (error instanceof AponiaError && error.code === "MISSING_PROVIDER") throw new AponiaError("INVALID_EXPORT", `Module "${module.id}" cannot export missing token "${tokenName(token)}".`, {
100
+ module: module.id,
101
+ token: tokenName(token)
102
+ });
103
+ throw error;
104
+ }
105
+ }
106
+ function validateDependencies(graph) {
107
+ for (const module of graph.modules) for (const provider of module.providers) for (const dependency of providerDependencies(provider)) graph.locate(module, dependency);
108
+ }
109
+ function validateControllers(graph) {
110
+ for (const module of graph.modules) {
111
+ const controllerTokens = /* @__PURE__ */ new Set();
112
+ for (const controller of module.controllers) {
113
+ if (controllerTokens.has(controller.token)) throw new AponiaError("DUPLICATE_PROVIDER", `Module "${module.id}" declares controller "${tokenName(controller.token)}" more than once.`, {
114
+ module: module.id,
115
+ token: tokenName(controller.token)
116
+ });
117
+ controllerTokens.add(controller.token);
118
+ for (const dependency of controller.inject) graph.locate(module, dependency);
119
+ }
120
+ }
121
+ }
122
+ function providerDependencies(provider) {
123
+ switch (provider.kind) {
124
+ case "value": return [];
125
+ case "alias": return [provider.useExisting];
126
+ case "class":
127
+ case "factory": return provider.inject;
128
+ }
129
+ }
130
+ function missingProvider(module, token) {
131
+ return new AponiaError("MISSING_PROVIDER", `Module "${module.id}" cannot resolve token "${tokenName(token)}".`, {
132
+ module: module.id,
133
+ token: tokenName(token)
134
+ });
135
+ }
136
+ //#endregion
137
+ //#region src/container.ts
138
+ var AponiaContainer = class {
139
+ graph;
140
+ #instances = /* @__PURE__ */ new Map();
141
+ #controllers = /* @__PURE__ */ new Map();
142
+ #resolving = [];
143
+ constructor(graph) {
144
+ this.graph = graph;
145
+ }
146
+ get(token) {
147
+ const location = this.graph.locate(this.graph.root, token);
148
+ return this.#resolve(location);
149
+ }
150
+ initializeModule(module) {
151
+ for (const provider of module.providers) this.#resolve({
152
+ module,
153
+ provider
154
+ });
155
+ }
156
+ instantiateController(module, controller) {
157
+ if (this.#controllers.has(controller)) return this.#controllers.get(controller);
158
+ const dependencies = controller.inject.map((dependency) => this.#resolve(this.graph.locate(module, dependency)));
159
+ const instance = Reflect.construct(controller.useClass, dependencies);
160
+ this.#controllers.set(controller, instance);
161
+ return instance;
162
+ }
163
+ #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);
166
+ if (cycleIndex >= 0) {
167
+ const cycle = [...this.#resolving.slice(cycleIndex), location].map((item) => `${item.module.id}:${tokenName(item.provider.provide)}`);
168
+ throw new AponiaError("PROVIDER_CYCLE", `Provider dependency cycle detected: ${cycle.join(" -> ")}.`, { cycle });
169
+ }
170
+ this.#resolving.push(location);
171
+ try {
172
+ const dependencies = providerDependencies(location.provider).map((dependency) => this.#resolve(this.graph.locate(location.module, dependency)));
173
+ const instance = instantiate(location.provider, dependencies);
174
+ this.#instances.set(location.provider, instance);
175
+ return instance;
176
+ } finally {
177
+ this.#resolving.pop();
178
+ }
179
+ }
180
+ };
181
+ function createContainer(root) {
182
+ return new AponiaContainer(compileModuleGraph(root));
183
+ }
184
+ function instantiate(provider, dependencies) {
185
+ switch (provider.kind) {
186
+ case "value": return provider.useValue;
187
+ case "alias": return dependencies[0];
188
+ case "factory": return Reflect.apply(provider.useFactory, void 0, dependencies);
189
+ case "class": return Reflect.construct(provider.useClass, dependencies);
190
+ }
191
+ }
192
+ //#endregion
193
+ export { AponiaContainer, ModuleGraph, compileModuleGraph, createContainer };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@aponiajs/core",
3
+ "version": "0.0.0",
4
+ "description": "Module graph and dependency injection runtime for Aponia.",
5
+ "homepage": "https://github.com/aponiajs/aponiajs#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/aponiajs/aponiajs/issues"
8
+ },
9
+ "license": "MIT",
10
+ "author": "AponiaJS contributors",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/aponiajs/aponiajs.git",
14
+ "directory": "packages/core"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "type": "module",
20
+ "exports": {
21
+ ".": "./dist/index.mjs",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "bunx --bun vp pack",
29
+ "dev": "bunx --bun vp pack --watch",
30
+ "test": "bun test",
31
+ "test:vite-plus": "vp test",
32
+ "check": "bunx --bun vp check",
33
+ "prepublishOnly": "bun run build"
34
+ },
35
+ "dependencies": {
36
+ "@aponiajs/common": "0.0.0"
37
+ }
38
+ }