@path-ioc/core 0.1.2 → 0.1.4
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/LICENSE +21 -0
- package/README.md +136 -50
- package/README.zh-CN.md +152 -62
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hanlin Lian
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -18,18 +18,29 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
20
20
|
> **What is Path-IoC (IoC-DL)?**
|
|
21
|
-
> Say goodbye to explicit `import` hell and the deadlocks, reflection penalties, and metadata bloat of legacy DI containers in asynchronous JavaScript. Path-IoC adopts the **Dependency Lookup (IoC-DL)** paradigm: physical file paths serve as abstract contracts, static topology graphs are compiled once, and modules awaken through native `async/await` DAG concurrency. Zero decorators, zero `reflect-metadata`, and zero framework intrusion.
|
|
21
|
+
> Say goodbye to explicit `import` hell and the deadlocks, reflection penalties, and metadata bloat of legacy DI containers in asynchronous JavaScript. Path-IoC adopts the **Dependency Lookup (IoC-DL)** paradigm: physical file paths serve as abstract feature contracts, static topology graphs are compiled once, and modules awaken through native `async/await` DAG concurrency. Zero decorators, zero `reflect-metadata`, and zero framework intrusion.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Live Demo & Instant Ignition (Live Demo Video)
|
|
26
|
+
|
|
27
|
+
<div align="center">
|
|
28
|
+
<video src="https://cdn.path-ioc.dev/path-ioc/demo-en.mp4" controls width="100%" playsinline>
|
|
29
|
+
Your browser does not support the video tag. <a href="https://cdn.path-ioc.dev/path-ioc/demo-en.mp4">Watch Live Demo Video</a>
|
|
30
|
+
</video>
|
|
31
|
+
<p>⚡ <b><a href="https://cdn.path-ioc.dev/path-ioc/demo-en.mp4">Watch Live Architecture Tour: Real-time Coding, Topological Orchestration & Instant Ignition</a></b></p>
|
|
32
|
+
</div>
|
|
22
33
|
|
|
23
34
|
---
|
|
24
35
|
|
|
25
36
|
## Quick Start
|
|
26
37
|
|
|
27
|
-
In production applications, `@path-ioc/core` works in tandem with the compiler plugin `@path-ioc/unplugin` (Compiler-Runtime Co-design).
|
|
38
|
+
In production applications, `@path-ioc/core` works in tandem with the compiler plugin `@path-ioc/unplugin` (Compiler-Runtime Co-design). However, `@path-ioc/core` is **100% self-sufficient and independent**—it can run in standalone Node.js, CLI tools, unit tests, or Cloudflare Workers without any bundler or companion packages.
|
|
28
39
|
|
|
29
40
|
### 1. Install Runtime & Bundler Plugin
|
|
30
41
|
|
|
31
42
|
```bash
|
|
32
|
-
# Runtime Core
|
|
43
|
+
# Runtime Core (Zero external dependencies)
|
|
33
44
|
pnpm add @path-ioc/core
|
|
34
45
|
|
|
35
46
|
# Universal Bundler Plugin (Dev Dependency)
|
|
@@ -44,7 +55,6 @@ Add `@path-ioc/unplugin` to your build configuration:
|
|
|
44
55
|
// vite.config.ts (or rolldown.config.ts)
|
|
45
56
|
import { defineConfig } from "vite";
|
|
46
57
|
import { vitePlugin as pathIoc } from "@path-ioc/unplugin";
|
|
47
|
-
// If using Rolldown: import { rolldownPlugin as pathIoc } from "@path-ioc/unplugin";
|
|
48
58
|
|
|
49
59
|
export default defineConfig({
|
|
50
60
|
plugins: [
|
|
@@ -58,9 +68,9 @@ export default defineConfig({
|
|
|
58
68
|
|
|
59
69
|
*Note: For Webpack 5 use `webpackPlugin`, Rspack use `rspackPlugin`, Rollup use `rollupPlugin`, and Esbuild use `esbuildPlugin`.*
|
|
60
70
|
|
|
61
|
-
### 3. Create Business Modules (
|
|
71
|
+
### 3. Create Business Modules (Short-Name Mesh ID Standard Practice)
|
|
62
72
|
|
|
63
|
-
Create module directories inside `src/modules` and export a pure `main` function:
|
|
73
|
+
Create module directories inside `src/modules` and export a pure `main` function. Using clean **short names (Mesh IDs)** for static dependencies is the framework's standard practice; hardcoding full physical paths for static dependencies is an anti-pattern and bad practice (it causes strong path coupling and violates the Dependency Inversion Principle). Full paths are designed exclusively for dynamic feature matching and AOP aspect filtering:
|
|
64
74
|
|
|
65
75
|
```typescript
|
|
66
76
|
// src/modules/infra/db/index.ts
|
|
@@ -71,10 +81,11 @@ export const main = () => {
|
|
|
71
81
|
};
|
|
72
82
|
|
|
73
83
|
// src/modules/biz/user/index.ts
|
|
74
|
-
|
|
84
|
+
// ✅ Standard Practice: Clean short names as Mesh IDs
|
|
85
|
+
export const dependencies = ["db"];
|
|
75
86
|
|
|
76
|
-
export const main = (
|
|
77
|
-
|
|
87
|
+
export const main = ({ db }: ModularContainer) => {
|
|
88
|
+
// Direct destructuring with 100% type inference, guaranteed resolved by topological scheduler
|
|
78
89
|
return {
|
|
79
90
|
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
80
91
|
};
|
|
@@ -84,14 +95,12 @@ export const main = (container: any) => {
|
|
|
84
95
|
### 4. Create App Bootstrap Module (`src/modules/start-app/index.ts`)
|
|
85
96
|
|
|
86
97
|
```typescript
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
// All business logic stays inside IoC modules to guarantee AOP aspects and topological readiness
|
|
99
|
+
export const dependencies = ["user"];
|
|
100
|
+
|
|
101
|
+
export const main = ({ user }: ModularContainer) => {
|
|
91
102
|
console.log(user.getUser("1001"));
|
|
92
103
|
};
|
|
93
|
-
|
|
94
|
-
export const dependencies = ["user"];
|
|
95
104
|
```
|
|
96
105
|
|
|
97
106
|
### 5. Ignite the Container (`src/main.ts`)
|
|
@@ -102,7 +111,7 @@ In your application entrypoint (e.g. `src/main.ts`), ignite the lock-free topolo
|
|
|
102
111
|
// src/main.ts
|
|
103
112
|
import { createModularContainer } from "virtual:modular-container";
|
|
104
113
|
|
|
105
|
-
//
|
|
114
|
+
// Host ignition boundary: pure ignition with 0 business pollution
|
|
106
115
|
createModularContainer();
|
|
107
116
|
```
|
|
108
117
|
|
|
@@ -112,31 +121,70 @@ The plugin automatically generates `ignore.modular.d.ts` in the background, prov
|
|
|
112
121
|
|
|
113
122
|
### 6. Standalone / Pure Node.js & Testing Mode
|
|
114
123
|
|
|
115
|
-
When writing isolated unit tests
|
|
124
|
+
When writing isolated unit tests (e.g. with Vitest / Jest) or running in headless CLI scripts without a bundler, you can directly invoke the pure function runtime of `@path-ioc/core`:
|
|
125
|
+
|
|
126
|
+
#### Scenario A: Unit Testing (Vitest / Jest)
|
|
127
|
+
In test suites, the test case acts as an external inspector asserting container assembly and evaluated outputs:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { expect, it } from "vitest";
|
|
131
|
+
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
132
|
+
|
|
133
|
+
it("should compile and instantiate in topological order", async () => {
|
|
134
|
+
const modules = [
|
|
135
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
136
|
+
{
|
|
137
|
+
key: "/biz/userService",
|
|
138
|
+
module: {
|
|
139
|
+
dependencies: ["db"],
|
|
140
|
+
main: ({ db }: any) => ({
|
|
141
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const compiledGraph = compileModuleGraph(modules);
|
|
148
|
+
const container: Record<string, unknown> = {};
|
|
149
|
+
await instantiateModuleContainer(compiledGraph, container);
|
|
150
|
+
|
|
151
|
+
// Unit test assertion: external probe validates assembly correctness
|
|
152
|
+
expect((container.userService as any).getUser("1001")).toBe("DB: SELECT * FROM users WHERE id = 1001");
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Scenario B: Headless Script Ignition (Business Logic Enclosed in Mesh)
|
|
157
|
+
Even in minimal scripts without build tools, the "Ignition Transition" rule holds: **all business logic remains encapsulated inside modules, while the outer script only compiles and ignites**:
|
|
116
158
|
|
|
117
159
|
```typescript
|
|
118
160
|
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
119
161
|
|
|
120
|
-
// 1. Explicitly define modules (declaring dependencies via keys / short-keys)
|
|
121
162
|
const modules = [
|
|
122
|
-
{ key: "/infra/db", module: { main: () =>
|
|
163
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
123
164
|
{
|
|
124
165
|
key: "/biz/userService",
|
|
125
166
|
module: {
|
|
126
167
|
dependencies: ["db"],
|
|
127
|
-
main: (
|
|
168
|
+
main: ({ db }: any) => ({
|
|
169
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
170
|
+
}),
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
// Business logic stays inside the Mesh, guaranteeing topological readiness and AOP safety
|
|
175
|
+
key: "/app/start",
|
|
176
|
+
module: {
|
|
177
|
+
dependencies: ["userService"],
|
|
178
|
+
main: ({ userService }: any) => {
|
|
179
|
+
console.log(userService.getUser("1001"));
|
|
180
|
+
},
|
|
128
181
|
},
|
|
129
182
|
},
|
|
130
183
|
];
|
|
131
184
|
|
|
132
|
-
//
|
|
185
|
+
// Host ignition boundary: compiles and ignites with zero outer business logic contamination
|
|
133
186
|
const compiledGraph = compileModuleGraph(modules);
|
|
134
|
-
|
|
135
|
-
// 3. Instantiate and populate container
|
|
136
|
-
const container: Record<string, unknown> = {};
|
|
137
|
-
await instantiateModuleContainer(compiledGraph, container);
|
|
138
|
-
|
|
139
|
-
console.log(container.userService.getUser("1001"));
|
|
187
|
+
await instantiateModuleContainer(compiledGraph, {});
|
|
140
188
|
```
|
|
141
189
|
|
|
142
190
|
---
|
|
@@ -145,26 +193,36 @@ console.log(container.userService.getUser("1001"));
|
|
|
145
193
|
|
|
146
194
|
Path-IoC pays deep homage to **Java Spring's classic Inversion-of-Control (IoC) principles**, while advancing its decoupled vision natively for JavaScript/TypeScript's single-threaded, asynchronous, and functional execution model:
|
|
147
195
|
|
|
148
|
-
### 1.
|
|
149
|
-
- **
|
|
150
|
-
|
|
151
|
-
- **
|
|
152
|
-
|
|
196
|
+
### 1. Counterpart to Native ESM: Application-Level Self-Organizing Mesh
|
|
197
|
+
- **Native ESM's Inevitable Bottleneck**: Explicit relative `import` statements (`../../../../utils`) become rigid compile-time hardlinks. In large codebases, they create refactoring paralysis and circular dependency deadlocks;
|
|
198
|
+
- **Path-IoC as an Autonomous Mesh**: Counterparts native ESM at the business layer. Host applications (Hono, Express, Koa, Cloudflare Workers, CLI) execute one-line ignition (`createModularContainer()`), while all business lifecycles, dynamic module matching, and cross-cutting concerns circulate autonomously within the mesh;
|
|
199
|
+
- **Completely Free from Monolithic Framework Dogma**: Developers often conflate IoC with heavyweight backend frameworks (such as NestJS). That is a fundamental category error. Monolithic backend frameworks invade business code with class decorators, controllers, and proprietary pipes. Path-IoC is never a backend framework, but a **minimal, lightweight, non-invasive self-contained mesh** dedicated solely to module composition and topological resolution across browser SPAs, edge workers, and servers alike.
|
|
200
|
+
|
|
201
|
+
### 2. The Dynamic Language Paradigm: Native DAG Topological Concurrency
|
|
202
|
+
Path-IoC performed no miracle; it simply obeyed the physical realities of JavaScript's single-threaded non-blocking Event Loop. In conventional industry perceptions, topological concurrent scheduling during container boot is often hailed as a "miracle." This perception stems from contrasting it against the four irreconcilable contradictions in legacy IoC architectures:
|
|
203
|
+
|
|
204
|
+
- **Contrasting Java Spring's Serial Initialization Bottleneck**: Despite OS-level multi-threading, Java Spring must restrict container initialization to a **strictly single-threaded serial pipeline (accumulative latency $\sum t_i$)** to prevent shared-memory race conditions, memory visibility hazards, and three-level-cache raw-pointer escape risks governed by the Java Memory Model (JMM);
|
|
205
|
+
- **Contrasting Legacy JS IoC's Async Initialization vs. Lazy Loading Contradiction**: In JavaScript's single-threaded model, synchronous Proxy Getters cannot pause to await asynchronous microtasks, nor can a pending Promise serve as a raw pointer in a three-level cache. Unable to solve topological async scheduling, legacy JS IoC frameworks (such as NestJS) surrender by hardcoding sequential `for...of await` loops in their core source code;
|
|
206
|
+
- **Conflation of Invocation-Time and Initialization-Time Dependencies in DI**: Traditional constructor injection elevates future runtime method invocations into physical boot-time prerequisites, artificially corrupting clean DAGs and generating spurious circular dependencies;
|
|
207
|
+
- **AOP Deficiencies Caused by the Absence of Effective Dependency Lookup (DL)**: Lacking non-invasive dependency lookup mechanisms, legacy frameworks degrade AOP into intrusive class decorators (e.g. `@UseInterceptors`) requiring explicit `import` statements, entirely violating the non-invasive cross-cutting essence of AOP.
|
|
208
|
+
|
|
209
|
+
**Path-IoC's Breakthrough**: Decouple "initialization dependencies (DAG topology)" orthogonally from "invocation-time dependencies (DL lookup)", reducing cyclic dependency probabilities mathematically to zero. By leveraging JavaScript's single-threaded immunity to shared-memory data races, peer modules without inter-dependencies ignite concurrently via `Promise.all` memoized reactive streams (reducing boot latency from $\sum t_i$ to the bottleneck node's $\max t_i$), enabling genuinely non-invasive Aspect-Oriented Programming through pure functional closures and pattern-matched dependency lookup.
|
|
153
210
|
|
|
154
|
-
###
|
|
155
|
-
- **
|
|
156
|
-
- **Physical
|
|
211
|
+
### 3. Physical Path as Feature (Label) vs. Short-Name as Mesh ID
|
|
212
|
+
- **Short Name is the Mesh ID**: In everyday business development, developers consume short names (`dependencies = ["logger", "db"]`, `const { logger, db } = container;`), enjoying zero ceremony and 100% IDE type inference;
|
|
213
|
+
- **Physical Path is a Feature Tag**: Physical directory prefixes (e.g. `/infra/`, `/biz/`, `/aspect/`) are **feature labels** (analogous to metadata tags);
|
|
214
|
+
- **Design Tenet**: Hardcoding full physical paths for static dependencies is an anti-pattern and bad practice (it introduces strong path coupling and violates the Dependency Inversion Principle). Full paths are designed exclusively for dynamic feature matching and AOP aspect filtering, while everyday business code must always embrace clean short-name Mesh IDs.
|
|
157
215
|
|
|
158
|
-
###
|
|
159
|
-
- **Zero Framework Primitives**: Path-IoC avoids heavy specialized abstractions (`Guards`, `Interceptors`, `Pipes`, `Filters
|
|
160
|
-
- **Natural Aspect Meshes**: An aspect module
|
|
216
|
+
### 4. Aspect-Oriented Programming (AOP) via Pure Closures
|
|
217
|
+
- **Zero Framework Primitives**: Path-IoC avoids heavy specialized abstractions (`Guards`, `Interceptors`, `Pipes`, `Filters`). In dynamic languages, higher-order functions and proxy wrappers represent the purest form of AOP;
|
|
218
|
+
- **Natural Aspect Meshes**: An aspect module declares a dependency filter function matching target module path prefixes (`allModuleNames.filter(p => p.startsWith("/biz/"))`). The topological scheduler guarantees target instances are instantiated first; the aspect module then wraps target methods via higher-order proxies without intrusive annotations.
|
|
161
219
|
|
|
162
|
-
###
|
|
163
|
-
- **
|
|
164
|
-
- **
|
|
220
|
+
### 5. Physical Law of Cycles & DFS Fail-Fast
|
|
221
|
+
- **The Physical Impossibility of Dynamic Async Cycle Unwinding**: JavaScript Proxy Getters (`container.xxx`) are purely synchronous operations; microtasks cannot suspend synchronous execution to await asynchronous operations. Dynamic cycle-breaking under async execution is physically impossible in single-threaded JS;
|
|
222
|
+
- **Strict Fail-Fast by Design**: `@path-ioc/core` remains mathematically rigorous—DFS static cycle analysis enforces **Fail-Fast** error reporting with full cycle chain traces upon boot, forcing clean, orthogonal architecture.
|
|
165
223
|
|
|
166
|
-
###
|
|
167
|
-
By completely eliminating `reflect-metadata`, Path-IoC cleanly separates **Static Graph Compilation (`compileModuleGraph`)** from **Dynamic Container Instantiation (`instantiateModuleContainer`)**. In Cloudflare Workers or serverless Node.js endpoints, the module graph is compiled once on worker cold-start and permanently cached; subsequent requests instantiate lightweight containers directly
|
|
224
|
+
### 6. Two-Stage Execution & Edge Serverless Readiness
|
|
225
|
+
By completely eliminating `reflect-metadata`, Path-IoC cleanly separates **Static Graph Compilation (`compileModuleGraph`)** from **Dynamic Container Instantiation (`instantiateModuleContainer`)**. In Cloudflare Workers or serverless Node.js endpoints, the module graph is compiled once on worker cold-start (1.72ms for 500 nodes) and permanently cached; subsequent requests instantiate lightweight containers directly in **21.2 µs**, reducing framework CPU overhead by over 80%.
|
|
168
226
|
|
|
169
227
|
---
|
|
170
228
|
|
|
@@ -184,13 +242,13 @@ Tested on Apple Silicon under Node.js v24 (`pnpm bench`):
|
|
|
184
242
|
|
|
185
243
|
## Selection Matrix
|
|
186
244
|
|
|
187
|
-
| Comparison Dimension | **TS Decorator Stack**<br>(NestJS / Inversify / TSyringe) | **Regex Proxy Stack**<br>(Awilix) | **JVM Reflection Stack**<br>(Java Spring) | **@path-ioc/core**
|
|
245
|
+
| Comparison Dimension | **TS Decorator Stack**<br>(NestJS / Inversify / TSyringe) | **Regex Proxy Stack**<br>(Awilix) | **JVM Reflection Stack**<br>(Java Spring) | **@path-ioc/core** |
|
|
188
246
|
| :--- | :--- | :--- | :--- | :--- |
|
|
189
247
|
| **Core Contract** | `reflect-metadata` + TS Decorators | Function `.toString()` parsing + Proxy | Reflection + Bytecode + Caching (Enterprise benchmark) | **Physical Path Contract + Pure Closures + DAG Compilation** |
|
|
190
248
|
| **Bundler Compatibility** | Poor (breaks on pure AST type-erasure bundlers) | Good | Native JVM support | **Universal** (Pure ES Modules & closures; zero reflection) |
|
|
191
|
-
| **Initialization & Concurrency** | Serial pipeline; async providers block sequentially | Synchronous only | Strict single-thread serial assembly (JMM thread safety) | **Native DAG Topological Concurrency** (
|
|
249
|
+
| **Initialization & Concurrency** | Serial pipeline; async providers block sequentially | Synchronous only | Strict single-thread serial assembly (JMM thread safety) | **Native DAG Topological Concurrency** (DFS post-order + Promise.all reactive stream) |
|
|
192
250
|
| **AOP Mechanism** | Complex & restricted to HTTP controller layers | None built-in | Declarative bytecode proxy (AspectJ) | **Complete Native AOP** (Zero extra concepts; pure DL & higher-order wrappers) |
|
|
193
|
-
| **Cycle Handling** | Deadlock hazard (`forwardRef` + async hangs) | Limited (Sync only) | Three-level cache unwinding | **Fail-Fast Cycle Interception** (
|
|
251
|
+
| **Cycle Handling** | Deadlock hazard (`forwardRef` + async hangs) | Limited (Sync only) | Three-level cache unwinding | **DFS Fail-Fast Cycle Interception** (Strict compile-time detection with full trace) |
|
|
194
252
|
| **Edge / Serverless Performance** | High CPU overhead due to dynamic metadata reflection | Proxy traversal overhead | Heavy memory footprint | **Ultra-lightweight** (Two-stage separation, 80%+ lower CPU cost) |
|
|
195
253
|
| **Code Intrusion** | High (pervasive framework decorators) | Moderate (binds parameter names) | Low (supports JSR-330 standard) | **Zero** (Pure ES functions; runs completely independently) |
|
|
196
254
|
|
|
@@ -198,7 +256,7 @@ Tested on Apple Silicon under Node.js v24 (`pnpm bench`):
|
|
|
198
256
|
|
|
199
257
|
## Formal API Reference
|
|
200
258
|
|
|
201
|
-
### 1. `compileModuleGraph`
|
|
259
|
+
### 1. `compileModuleGraph(modules)`
|
|
202
260
|
|
|
203
261
|
```typescript
|
|
204
262
|
export interface IOCModule {
|
|
@@ -221,10 +279,12 @@ export function compileModuleGraph(
|
|
|
221
279
|
): CompiledModuleGraph;
|
|
222
280
|
```
|
|
223
281
|
|
|
224
|
-
- **Complexity**: Time $O(V + E)$, Space $O(V + E)$
|
|
282
|
+
- **Algorithm & Complexity**: Time $O(V + E)$, Space $O(V + E)$ based on **DFS post-order traversal stack topological sorting**;
|
|
225
283
|
- **Fail-Fast Error Handling**: Throws descriptive errors with full cycle paths upon detecting cyclic dependencies, or on short-key collisions.
|
|
226
284
|
|
|
227
|
-
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### 2. `instantiateModuleContainer(compiledGraph, container)`
|
|
228
288
|
|
|
229
289
|
```typescript
|
|
230
290
|
export function instantiateModuleContainer(
|
|
@@ -236,7 +296,33 @@ export function instantiateModuleContainer(
|
|
|
236
296
|
- **Execution Semantics**:
|
|
237
297
|
- Iterates through `compiledGraph.sortedKeys` in validated topological order;
|
|
238
298
|
- Mounts return values to both `container[fullKey]` and `container[shortKey]`;
|
|
239
|
-
- Awaits asynchronous `main` promises before triggering dependent child nodes.
|
|
299
|
+
- Awaits asynchronous `main` promises before triggering dependent child nodes using memoized Promise streams.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
### 3. `initialize(modules, container)`
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
export function initialize(
|
|
307
|
+
modules: { key: string; module: IOCModule }[],
|
|
308
|
+
container: Record<string, unknown>
|
|
309
|
+
): Promise<void>;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Convenience utility combining `compileModuleGraph` and `instantiateModuleContainer`.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Mesh Module Export Protocol
|
|
317
|
+
|
|
318
|
+
Every module located at `src/modules/**/index.ts` may export up to four standard identifiers:
|
|
319
|
+
|
|
320
|
+
| Identifier | Type Signature | Default | Description |
|
|
321
|
+
| :--- | :--- | :--- | :--- |
|
|
322
|
+
| **`main`** *(Required)* | `(container: ModularContainer, allModuleNames: string[]) => any \| Promise<any>` | - | Factory function invoked according to topological sort order. The second argument `allModuleNames` provides the full list of all declared physical module keys across the entire system; when performing dynamic module matching or AOP wrapping, you must explicitly filter it (e.g. `allModuleNames.filter(p => p.startsWith("/biz/"))`). |
|
|
323
|
+
| **`dependencies`** *(Optional)* | `string[] \| ((allModuleNames: string[]) => string[])` | `[]` | Explicit topological prerequisites. Use clean short names for static dependencies (e.g. `["logger", "db"]`). Supports dynamic filter functions for topological ordering. |
|
|
324
|
+
| **`order`** *(Optional)* | `number` | `99999` | Priority weight when no explicit topological dependencies constrain ordering. **Strictly ascending numerical order**: smaller numbers execute earlier (e.g., `order: 1` executes before `order: 10`, default `99999`). |
|
|
325
|
+
| **`skip`** *(Optional)* | `boolean` | `false` | Skips runtime execution of `main`. Used for externally injected modules (e.g. injecting `requestContext` in backend request isolation). **Note**: Even with `skip: true`, a dummy `main` function (e.g., `export const main = (): MyType => ({} as any)`) must still be exported to satisfy runtime graph validation and type generation. |
|
|
240
326
|
|
|
241
327
|
---
|
|
242
328
|
|
package/README.zh-CN.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/npm/l/@path-ioc/core.svg" alt="License"></a>
|
|
10
10
|
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.0+-3178C6?logo=typescript&logoColor=white" alt="TypeScript"></a>
|
|
11
11
|
<a href="https://path-ioc.dev/zh/"><img src="https://img.shields.io/badge/文档-path--ioc.dev-8A2BE2.svg" alt="Documentation"></a>
|
|
12
|
+
<a href="https://github.com/sponsors/path-ioc"><img src="https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-EA4AAA.svg" alt="Sponsor"></a>
|
|
12
13
|
</p>
|
|
13
14
|
|
|
14
15
|
<p>
|
|
@@ -17,18 +18,29 @@
|
|
|
17
18
|
</div>
|
|
18
19
|
|
|
19
20
|
> **什么是 Path-IoC (IoC-DL)?**
|
|
20
|
-
> 告别原始 `import`
|
|
21
|
+
> 告别原始 `import` 相对路径泥潭与传统重型黑盒 DI 在 JS 异步生态里的死锁与元数据包袱。Path-IoC 采用 **Dependency Lookup (依赖查找)** 范式——物理路径即抽象特征契约,静态图启动期预编译,原生 `async/await` 拓扑并发。零装饰器、零元数据反射、零框架侵入,业务面向 `container` 极简解构。
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 实机编码与极速点火演示录屏 (Live Demo Video)
|
|
26
|
+
|
|
27
|
+
<div align="center">
|
|
28
|
+
<video src="https://cdn.path-ioc.dev/path-ioc/demo-zh.mp4" controls width="100%" playsinline>
|
|
29
|
+
您的浏览器不支持 HTML5 视频播放。<a href="https://cdn.path-ioc.dev/path-ioc/demo-zh.mp4">点击查看实机演示视频</a>
|
|
30
|
+
</video>
|
|
31
|
+
<p>⚡ <b><a href="https://cdn.path-ioc.dev/path-ioc/demo-zh.mp4">点击查看实机架构漫游:实时编码、拓扑依赖编排与极速容器点火</a></b></p>
|
|
32
|
+
</div>
|
|
21
33
|
|
|
22
34
|
---
|
|
23
35
|
|
|
24
36
|
## 快速上手 (Quick Start)
|
|
25
37
|
|
|
26
|
-
在真实的现代前端与全栈工程中,`@path-ioc/core` 与编译期插件 `@path-ioc/unplugin` 深度协同(Compiler-Runtime Co-design
|
|
38
|
+
在真实的现代前端与全栈工程中,`@path-ioc/core` 与编译期插件 `@path-ioc/unplugin` 深度协同(Compiler-Runtime Co-design)。与此同时,`@path-ioc/core` **100% 自治且完全独立**——它拥有极轻量的纯函数运行时(仅 8.8KB,零外部依赖),无需打包工具即可在纯 Node.js、CLI 脚本、单测或 Cloudflare Workers 中直接运行。
|
|
27
39
|
|
|
28
40
|
### 1. 安装核心与构建插件
|
|
29
41
|
|
|
30
42
|
```bash
|
|
31
|
-
# 运行时核心
|
|
43
|
+
# 运行时核心 (零外部依赖)
|
|
32
44
|
pnpm add @path-ioc/core
|
|
33
45
|
|
|
34
46
|
# 通用构建插件 (开发依赖)
|
|
@@ -37,13 +49,12 @@ pnpm add -D @path-ioc/unplugin
|
|
|
37
49
|
|
|
38
50
|
### 2. 配置构建工具 (支持 Vite / Rolldown / Webpack / Rspack / Rollup / Esbuild)
|
|
39
51
|
|
|
40
|
-
|
|
52
|
+
在构建配置文件中引入 `@path-ioc/unplugin`:
|
|
41
53
|
|
|
42
54
|
```typescript
|
|
43
55
|
// vite.config.ts (或 rolldown.config.ts)
|
|
44
56
|
import { defineConfig } from "vite";
|
|
45
57
|
import { vitePlugin as pathIoc } from "@path-ioc/unplugin";
|
|
46
|
-
// 若使用 Rolldown: import { rolldownPlugin as pathIoc } from "@path-ioc/unplugin";
|
|
47
58
|
|
|
48
59
|
export default defineConfig({
|
|
49
60
|
plugins: [
|
|
@@ -57,9 +68,9 @@ export default defineConfig({
|
|
|
57
68
|
|
|
58
69
|
*注:Webpack 5 请使用 `webpackPlugin`,Rspack 使用 `rspackPlugin`,Rollup 使用 `rollupPlugin`,Esbuild 使用 `esbuildPlugin`。*
|
|
59
70
|
|
|
60
|
-
### 3. 创建业务模块 (
|
|
71
|
+
### 3. 创建业务模块 (短名称 Mesh ID 正常实践)
|
|
61
72
|
|
|
62
|
-
在 `src/modules` 下自由创建模块目录并导出 `main`
|
|
73
|
+
在 `src/modules` 下自由创建模块目录并导出 `main` 纯函数。静态依赖使用**短名称(Mesh ID)**是框架的正常实践;在静态依赖中硬编码物理全称属于反模式与错误实践(会导致强路径耦合并违反依赖倒置),物理全称专用于动态特征匹配与切面筛选:
|
|
63
74
|
|
|
64
75
|
```typescript
|
|
65
76
|
// src/modules/infra/db/index.ts
|
|
@@ -70,10 +81,11 @@ export const main = () => {
|
|
|
70
81
|
};
|
|
71
82
|
|
|
72
83
|
// src/modules/biz/user/index.ts
|
|
73
|
-
|
|
84
|
+
// ✅ 正常实践:短名称作为 Mesh ID
|
|
85
|
+
export const dependencies = ["db"];
|
|
74
86
|
|
|
75
|
-
export const main = (
|
|
76
|
-
|
|
87
|
+
export const main = ({ db }: ModularContainer) => {
|
|
88
|
+
// 直接解构,无缝调用,全局类型 100% 自动同步推导
|
|
77
89
|
return {
|
|
78
90
|
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
79
91
|
};
|
|
@@ -84,23 +96,22 @@ export const main = (container: any) => {
|
|
|
84
96
|
|
|
85
97
|
```typescript
|
|
86
98
|
// 一切业务皆模块:初始调用收敛在 IoC 模块内,天然保障 AOP 切面与依赖拓扑就绪
|
|
87
|
-
export const
|
|
88
|
-
|
|
99
|
+
export const dependencies = ["user"];
|
|
100
|
+
|
|
101
|
+
export const main = ({ user }: ModularContainer) => {
|
|
89
102
|
console.log(user.getUser("1001"));
|
|
90
103
|
};
|
|
91
|
-
|
|
92
|
-
export const dependencies = ["user"];
|
|
93
104
|
```
|
|
94
105
|
|
|
95
|
-
### 5.
|
|
106
|
+
### 5. 宿主应用入口点火唤醒 (`src/main.ts`)
|
|
96
107
|
|
|
97
|
-
在应用入口(如 `src/main.ts
|
|
108
|
+
在应用入口(如 `src/main.ts`),一行代码唤醒全量拓扑流:
|
|
98
109
|
|
|
99
110
|
```typescript
|
|
100
111
|
// src/main.ts
|
|
101
112
|
import { createModularContainer } from "virtual:modular-container";
|
|
102
113
|
|
|
103
|
-
//
|
|
114
|
+
// 宿主点火边界:入口保持绝对纯粹,仅充当容器点火器,零业务逻辑污染
|
|
104
115
|
createModularContainer();
|
|
105
116
|
```
|
|
106
117
|
|
|
@@ -108,93 +119,144 @@ createModularContainer();
|
|
|
108
119
|
|
|
109
120
|
---
|
|
110
121
|
|
|
111
|
-
### 6. 原生 Core
|
|
122
|
+
### 6. 原生 Core 独立运行与单测模式 (Unit Test & Standalone)
|
|
123
|
+
|
|
124
|
+
若在编写隔离单元测试(如 Vitest / Jest)或无打包工具的纯 Node.js 脚本时,可直接使用 `@path-ioc/core` 原生纯函数接口:
|
|
112
125
|
|
|
113
|
-
|
|
126
|
+
#### 场景 A:单元测试场景 (Vitest / Jest)
|
|
127
|
+
在单测套件中,测试用例作为外部观察者验证依赖图的装配与求值结果:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { expect, it } from "vitest";
|
|
131
|
+
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
132
|
+
|
|
133
|
+
it("should compile and instantiate in topological order", async () => {
|
|
134
|
+
const modules = [
|
|
135
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
136
|
+
{
|
|
137
|
+
key: "/biz/userService",
|
|
138
|
+
module: {
|
|
139
|
+
dependencies: ["db"],
|
|
140
|
+
main: ({ db }: any) => ({
|
|
141
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const compiledGraph = compileModuleGraph(modules);
|
|
148
|
+
const container: Record<string, unknown> = {};
|
|
149
|
+
await instantiateModuleContainer(compiledGraph, container);
|
|
150
|
+
|
|
151
|
+
// 单测断言:外部探针验证装配结果
|
|
152
|
+
expect((container.userService as any).getUser("1001")).toBe("DB: SELECT * FROM users WHERE id = 1001");
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### 场景 B:独立脚本点火 (遵循点火跃迁与业务自闭环)
|
|
157
|
+
即便在没有构建工具的极简脚本中,也必须恪守“点火跃迁”准则——**业务逻辑严格封装在启动模块内,外部仅负责图编译与一行点火唤醒**:
|
|
114
158
|
|
|
115
159
|
```typescript
|
|
116
160
|
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
117
161
|
|
|
118
|
-
// 1. 显式定义模块列表 (短名称声明依赖与依赖查找)
|
|
119
162
|
const modules = [
|
|
120
|
-
{ key: "/infra/db", module: { main: () =>
|
|
163
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
121
164
|
{
|
|
122
165
|
key: "/biz/userService",
|
|
123
166
|
module: {
|
|
124
167
|
dependencies: ["db"],
|
|
125
|
-
main: (
|
|
168
|
+
main: ({ db }: any) => ({
|
|
169
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
170
|
+
}),
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
// 一切业务闭环在 Mesh 内部,天然保障 AOP 拦截与生命周期完整
|
|
175
|
+
key: "/app/start",
|
|
176
|
+
module: {
|
|
177
|
+
dependencies: ["userService"],
|
|
178
|
+
main: ({ userService }: any) => {
|
|
179
|
+
console.log(userService.getUser("1001"));
|
|
180
|
+
},
|
|
126
181
|
},
|
|
127
182
|
},
|
|
128
183
|
];
|
|
129
184
|
|
|
130
|
-
//
|
|
185
|
+
// 宿主点火边界:只做编译与容器唤醒,零业务逻辑污染
|
|
131
186
|
const compiledGraph = compileModuleGraph(modules);
|
|
132
|
-
|
|
133
|
-
// 3. 动态填充容器
|
|
134
|
-
const container: Record<string, unknown> = {};
|
|
135
|
-
await instantiateModuleContainer(compiledGraph, container);
|
|
187
|
+
await instantiateModuleContainer(compiledGraph, {});
|
|
136
188
|
```
|
|
137
189
|
|
|
138
190
|
---
|
|
139
191
|
|
|
140
192
|
## 核心设计哲学:向 Spring 致敬与动态语言范式应答 (Architecture Philosophy)
|
|
141
193
|
|
|
142
|
-
Path-IoC 的底层设计建立在对 **Java Spring 经典控制反转 (IoC) 哲学**
|
|
194
|
+
Path-IoC 的底层设计建立在对 **Java Spring 经典控制反转 (IoC) 哲学** 的深切致敬上,并针对 JavaScript/TypeScript 的单线程异步模型进行了原生升级:
|
|
143
195
|
|
|
144
|
-
### 1.
|
|
196
|
+
### 1. 对标原生 ESM:纯粹的应用级自组织模块系统
|
|
197
|
+
- **原生 ESM 的根本局限**:原生 JavaScript `import` 语句本质是文件级的硬链接。当业务演进到数百个模块时,纵横交错的相对路径(`../../../../utils`)会导致严重的重构阻力与死锁隐患;
|
|
198
|
+
- **Path-IoC 的生态位定位**:**对标原生 ESM,作为业务层“应用级自组织模块系统”**。宿主(Hono、Express、Koa、Next.js API、Workers、CLI)只做一行代码点火(`createModularContainer()`),业务模块在网格内自闭环运转,不挑宿主,零框架锁死;
|
|
199
|
+
- **完全脱离传统后端框架的侵入式范畴**:不少开发者习惯将 IoC 与重量级后端框架(如 NestJS)混为一谈,这是典型的范畴谬误。后端全家桶往往用繁琐的类装饰器、控制器与专有管道强行侵入业务代码并绑架应用生命周期;而 Path-IoC 绝非后端框架,而是**极简、轻量、无侵入的自闭环网格(Mesh)**,只专注解决应用层模块解耦与拓扑调度,在浏览器前端、边缘计算、轻量服务端与 CLI 中皆可原生自洽运转。
|
|
145
200
|
|
|
146
|
-
|
|
201
|
+
### 2. 动态语言范式应答:单线程 Event Loop 下的原生 DAG 拓扑并发
|
|
202
|
+
并非 Path-IoC 创造了神迹,而是 Path-IoC 彻底顺应了 JavaScript 单线程非阻塞 Event Loop 的物理特性。在传统认知中,容器启动时的拓扑并发调度常被视作“不可思议的神迹”,其根源在于对比传统 IoC 体系下四大不可调和的深层矛盾:
|
|
147
203
|
|
|
148
|
-
-
|
|
149
|
-
-
|
|
204
|
+
- **对比 Java Spring 的串行初始化性能**:Java 虽拥有物理多线程,但为了规避并发创建 Bean 带来的共享内存竞争、内存可见性与三级缓存裸指针逃逸风险(受 JMM 内存模型限制),Spring 容器初始化阶段底层只能退守于**严谨的单线程严格串行装配(Serial Pipeline,耗时累加 $\sum t_i$)**;
|
|
205
|
+
- **对比传统 JS IoC 框架的异步初始化与懒加载矛盾**:在 JavaScript 单线程模型中,Proxy 同步 Getter 无法暂停去等待异步微任务,处于 pending 态的 Promise 也无法充当三级缓存的裸指针;传统 JS IoC(如 NestJS)无法解决拓扑并发调度,在核心源码中直接通过 `for...of await` 逐个硬编码串行排队;
|
|
206
|
+
- **DI 的调用期与初始化依赖混淆**:传统构造器注入将未来运行期的方法调用,强行升格为启动时刻的物理先决条件,人为摧毁了纯净 DAG 并制造了大量伪循环依赖;
|
|
207
|
+
- **缺少有效 DL(依赖查找)手段导致的 AOP 缺陷**:缺乏无侵入的依赖查找机制,传统框架的 AOP 只能退化为目标类显式 import 并手写类装饰器(如 `@UseInterceptors`)的“主动组合”,彻底违背了 AOP 非侵入横切的初心。
|
|
150
208
|
|
|
151
|
-
|
|
209
|
+
**Path-IoC 的破局之道**:将“初始化依赖(DAG 拓扑)”与“调用期依赖(DL 查找)”彻底正交解耦,成环概率在数学上归零。依托 JavaScript 单线程天然消除共享内存竞态的物理优势,同层无依赖节点通过 `Promise.all` 记忆化并发流级联点火(启动耗时从 $\sum t_i$ 降维为瓶颈节点的 $\max t_i$),并基于纯函数闭包与依赖查找实现真正无侵入的面向切面编程。
|
|
152
210
|
|
|
153
|
-
|
|
154
|
-
-
|
|
211
|
+
### 3. 物理路径即特征(Feature)与短名称 Mesh ID
|
|
212
|
+
- **短名称就是 Mesh ID**:日常业务开发中,开发者只认短名称(`dependencies = ["logger", "db"]`,消费时 `const { logger, db } = container;`),极简直观,享受 IDE 自动补全;
|
|
213
|
+
- **全路径是“特征标签”(类比元数据注解)**:全路径中的前缀(如 `/infra/`、`/biz/`)是用于动态特征匹配与切面筛选的特征标签;
|
|
214
|
+
- **设计准则**:在静态依赖中硬编码物理全称属于反模式与错误实践(会引入强路径耦合并严重违反依赖倒置原则);物理全称专用于动态特征匹配与 AOP 切面筛选,常规业务依赖必须始终使用短名称 Mesh ID。
|
|
155
215
|
|
|
156
|
-
###
|
|
216
|
+
### 4. AOP 切面机制:零学习成本的原生切面 (DL-based Aspect)
|
|
217
|
+
- **不内置多余特权概念**:Path-IoC 不内置任何繁杂概念(如 `Guards`、`Interceptors`、`Pipes`、`Filters`)。在 JavaScript 动态语言下,高阶函数与解构代理本身就是最纯粹的 AOP;
|
|
218
|
+
- **完全 AOP 能力 & 零学习成本**:切面模块在自身 `dependencies` 中声明目标模块路径模式函数。拓扑引擎保证目标模块优先实例化,切面随后唤醒并对目标对象施加代理包装,实现 100% 非侵入切面织入。
|
|
157
219
|
|
|
158
|
-
|
|
159
|
-
-
|
|
220
|
+
### 5. 单线程事件循环下的物理铁律:DFS Fail-Fast 拦截
|
|
221
|
+
- **单线程 Proxy 的物理不可逾越性**:JavaScript 的 Proxy Getter(`container.xxx`)是**纯同步操作**,微任务无法在此暂停挂起去 await 异步操作。因此在单线程下“一边动态访问 Proxy Getter,一边还能动态解异步循环依赖”在物理机制上绝不成立;
|
|
222
|
+
- **Core 引擎的工程立场**:`@path-ioc/core` 保持严谨正义——使用 **DFS 拓扑分析严格 Fail-Fast 拦截环形依赖**,并打印完整环路调用链路,拒绝用隐式机制遮蔽架构腐败。
|
|
160
223
|
|
|
161
|
-
###
|
|
162
|
-
|
|
163
|
-
- **纯理论提炼**:**“依赖感知”与“循环依赖解环”本质上是同一物理能力的不同理解角度**。
|
|
164
|
-
- **“依赖感知”是核心能力**;
|
|
165
|
-
- **正面作用一**:运行时循环依赖解环;
|
|
166
|
-
- **正面作用二**:按需子图懒加载 (Lazy Loading);
|
|
167
|
-
- **伴生副作用**:遮蔽系统设计缺陷、导致架构隐式腐烂、以及无法原生支持 `async` 异步初始化。
|
|
168
|
-
- **Core 引擎的工程立场**:`@path-ioc/core` 保持严谨正义——用 DFS 静态深搜 Fail-Fast 抛错,拒绝用隐式解环遮蔽系统架构漏洞。
|
|
169
|
-
- **Container 的 Turbo 机制对比**:若因历史包袱需解环,在纯同步全模块场景下,扩展层 `@path-ioc/container` 的 Turbo 模式通过 Proxy Dynamic Getter 实现按需“依赖感知”,在运行期无痛完成无锁解环。
|
|
224
|
+
### 6. 零运行期反射与两阶段图编译 (Two-Stage Separation)
|
|
225
|
+
彻底摆脱 `reflect-metadata` 重型反射包袱。架构上将 **静态图编译 (`compileModuleGraph`)** 与 **动态容器填充 (`instantiateModuleContainer`)** 彻底分离。在 Cloudflare Workers 或 Node.js 高频请求场景中,服务冷启动时仅编译一次拓扑图(500 节点仅需 1.72ms),单次请求到来时直通填充容器(仅 21.2µs),大流量下实测降低 80% 以上的框架层 CPU 开销。
|
|
170
226
|
|
|
171
|
-
|
|
227
|
+
---
|
|
172
228
|
|
|
173
|
-
|
|
229
|
+
## 硬核基准性能 (Hardware-Verified Benchmarks)
|
|
174
230
|
|
|
175
|
-
|
|
231
|
+
基于 Apple Silicon 芯片、Node.js v24 原生实测(运行 `pnpm bench`):
|
|
176
232
|
|
|
177
|
-
|
|
233
|
+
| 压测指标 (Benchmark Item) | 复杂度规模 | 平均耗时 (Avg Time) | 性能表现说明 |
|
|
234
|
+
| :--- | :--- | :--- | :--- |
|
|
235
|
+
| **`instantiateModuleContainer`** | **50 节点** 容器实例化 | **`21.2 µs`** | 微秒级直通,Serverless HTTP 请求期 0 延迟 |
|
|
236
|
+
| **`compileModuleGraph`** | **50 节点** 静态图编译 | **`90.8 µs`** | 亚毫秒级完成全拓扑环路校验 |
|
|
237
|
+
| **`instantiateModuleContainer`** | **500 节点** 容器实例化 | **`227 µs`** | 超大型项目依然近乎零开销 |
|
|
238
|
+
| **`compileModuleGraph`** | **500 节点** 复杂交叉依赖 | **`1.72 ms`** | 进程冷启动仅需 1 次,随后全量缓存复用 |
|
|
239
|
+
| **`compileModuleGraph`** | **2,000 节点** 超大规模拓扑 | **`15.5 ms`** | 工业级深层拓扑解析极限 |
|
|
178
240
|
|
|
179
241
|
---
|
|
180
242
|
|
|
181
243
|
## 主流 IoC 框架选型对比矩阵 (Selection Matrix)
|
|
182
244
|
|
|
183
|
-
| 对比维度 | **TS 装饰器派**<br>(NestJS / Inversify / TSyringe) | **正则 Proxy 派**<br>(Awilix) | **JVM 反射派**<br>(Java Spring) | **@path-ioc/core**
|
|
245
|
+
| 对比维度 | **TS 装饰器派**<br>(NestJS / Inversify / TSyringe) | **正则 Proxy 派**<br>(Awilix) | **JVM 反射派**<br>(Java Spring) | **@path-ioc/core** |
|
|
184
246
|
| :--- | :--- | :--- | :--- | :--- |
|
|
185
247
|
| **底层依据** | `reflect-metadata` + TS Decorator | 函数 `.toString()` 正则 + Proxy | Java 反射 + 字节码 + 缓存 (静态语言工业标杆) | **物理路径契约 + 纯闭包工厂 + DAG 图编译** |
|
|
186
|
-
| **编译/环境兼容性** | 差 (强依赖元数据,纯类型擦除转译即崩溃) | 良好 | JVM 物理机原生支持 | **极致** (纯 ES Module
|
|
187
|
-
| **初始化装配机制** | 串行主导 / Class 构造纯同步,async Provider 阻塞 | 不支持异步初始化 | 严格单线程串行装配 (出于 JMM 线程安全考量) | **原生 DAG
|
|
248
|
+
| **编译/环境兼容性** | 差 (强依赖元数据,纯类型擦除转译即崩溃) | 良好 | JVM 物理机原生支持 | **极致** (纯 ES Module 函数闭包,零反射元数据) |
|
|
249
|
+
| **初始化装配机制** | 串行主导 / Class 构造纯同步,async Provider 阻塞 | 不支持异步初始化 | 严格单线程串行装配 (出于 JMM 线程安全考量) | **原生 DAG 拓扑并发流** (DFS 后序遍历压栈 + Promise.all 记忆化) |
|
|
188
250
|
| **AOP 切面机制** | 概念繁杂 & 强绑定且仅限 Controller | 无内置 AOP 能力 | 划时代声明式代理 (AspectJ) | **完全 AOP 能力 & 零学习成本** (基于 DL 依赖查找与 JS 高阶代理) |
|
|
189
|
-
| **循环依赖与解环机制** | 死锁重灾区 (`forwardRef` 遇 async 死锁) | 受限 (仅限纯同步) | 辩证支持 (三级缓存解环,易诱发隐蔽 Bug) |
|
|
190
|
-
| **高并发 / 运行期性能** | 高频元数据反射损耗 | Proxy 属性访问开销 | 工业级高可靠 (受限 JVM 物理模型) | **极高** (
|
|
251
|
+
| **循环依赖与解环机制** | 死锁重灾区 (`forwardRef` 遇 async 死锁) | 受限 (仅限纯同步) | 辩证支持 (三级缓存解环,易诱发隐蔽 Bug) | **DFS Fail-Fast 严格拦截** (拒绝遮蔽设计漏洞,打印完整环路链路) |
|
|
252
|
+
| **高并发 / 运行期性能** | 高频元数据反射损耗 | Proxy 属性访问开销 | 工业级高可靠 (受限 JVM 物理模型) | **极高** (静态图编译与填充分离,仅 21.2µs 直通,降 80% CPU 损耗) |
|
|
191
253
|
| **架构解耦与侵入性** | 强侵入 (代码处处与框架类和注解绑定) | 中度 (绑定参数名) | 低侵入 (支持 JSR-330 标准注解) | **零侵入** (模块仅为纯函数,脱离框架完全可跑) |
|
|
192
254
|
|
|
193
255
|
---
|
|
194
256
|
|
|
195
257
|
## 形式化 API 规范 (Formal API Specification)
|
|
196
258
|
|
|
197
|
-
### 1. `compileModuleGraph`
|
|
259
|
+
### 1. `compileModuleGraph(modules)`
|
|
198
260
|
|
|
199
261
|
```typescript
|
|
200
262
|
export interface IOCModule {
|
|
@@ -217,10 +279,12 @@ export function compileModuleGraph(
|
|
|
217
279
|
): CompiledModuleGraph;
|
|
218
280
|
```
|
|
219
281
|
|
|
220
|
-
- **算法复杂度**:时间复杂度 $O(V + E)$,空间复杂度 $O(V + E)$(基于
|
|
221
|
-
-
|
|
282
|
+
- **算法复杂度**:时间复杂度 $O(V + E)$,空间复杂度 $O(V + E)$(基于 **DFS 后序遍历压栈拓扑排序**);
|
|
283
|
+
- **异常捕获**:若检测到环路依赖,立即抛出附带完整环路链路路径的错误;若检测到短名称命名冲突,抛出明确诊断提示。
|
|
284
|
+
|
|
285
|
+
---
|
|
222
286
|
|
|
223
|
-
### 2. `instantiateModuleContainer`
|
|
287
|
+
### 2. `instantiateModuleContainer(compiledGraph, container)`
|
|
224
288
|
|
|
225
289
|
```typescript
|
|
226
290
|
export function instantiateModuleContainer(
|
|
@@ -230,9 +294,35 @@ export function instantiateModuleContainer(
|
|
|
230
294
|
```
|
|
231
295
|
|
|
232
296
|
- **行为规范**:
|
|
233
|
-
- 按 `compiledGraph.sortedKeys`
|
|
297
|
+
- 按 `compiledGraph.sortedKeys` 拓扑顺序依次唤醒模块 `main` 函数;
|
|
234
298
|
- 自动将模块执行返回值挂载到 `container[fullKey]` 与 `container[shortKey]`;
|
|
235
|
-
- 若模块的 `main` 为异步 Promise
|
|
299
|
+
- 若模块的 `main` 为异步 Promise,引擎自动通过 Promise 记忆化反应式并发流协调后续依赖子节点。
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
### 3. `initialize(modules, container)`
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
export function initialize(
|
|
307
|
+
modules: { key: string; module: IOCModule }[],
|
|
308
|
+
container: Record<string, unknown>
|
|
309
|
+
): Promise<void>;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
便捷快捷方法,内部依次调用 `compileModuleGraph` 与 `instantiateModuleContainer`。
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Mesh 模块导出规范 (Mesh Export Protocol)
|
|
317
|
+
|
|
318
|
+
在 `src/modules/**/index.ts` 中,允许导出以下 4 个标准变量:
|
|
319
|
+
|
|
320
|
+
| 导出变量名 | 类型 | 默认值 | 作用说明 |
|
|
321
|
+
| :--- | :--- | :--- | :--- |
|
|
322
|
+
| **`main`** *(必须)* | `(container: ModularContainer, allModuleNames: string[]) => any \| Promise<any>` | - | 模块工厂函数。第二个入参 `allModuleNames` 传递的是全系统全量物理模块 Key 列表;在进行动态模块匹配或 AOP 切面织入时,**必须在函数内显式根据特征过滤**(如 `allModuleNames.filter(p => p.startsWith("/biz/"))`)。 |
|
|
323
|
+
| **`dependencies`** *(可选)* | `string[] \| ((allModuleNames: string[]) => string[])` | `[]` | 拓扑依赖声明。静态依赖必须使用极简短名称(如 `["logger", "db"]`);支持传入函数进行动态拓扑排序前置编排。 |
|
|
324
|
+
| **`order`** *(可选)* | `number` | `99999` | 执行时序权重。**严格遵循升序规则**:数值越小越先执行(如 `order: 1` 优先于 `order: 10`,默认值 `99999`),在无拓扑依赖约束时生效。 |
|
|
325
|
+
| **`skip`** *(可选)* | `boolean` | `false` | 跳过执行标记。用于外部预先注入的模块(如后端请求隔离时注入 `requestContext`)。**注意契约**:即便标记了 `skip: true`,也**必须导出一个 dummy `main` 函数**(如 `export const main = (): MyType => ({} as any)`),以通过核心依赖图校验与 unplugin 静态类型生成。 |
|
|
236
326
|
|
|
237
327
|
---
|
|
238
328
|
|