@path-ioc/core 0.1.3 → 0.1.5
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 +139 -50
- package/README.zh-CN.md +155 -62
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,18 +18,32 @@
|
|
|
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
|
+
<a href="https://path-ioc.dev" target="_blank" rel="noopener noreferrer">
|
|
29
|
+
<img src="https://cdn.path-ioc.dev/path-ioc/demo-en.webp" alt="Path-IoC Live Demo & Architecture Tour" width="100%">
|
|
30
|
+
</a>
|
|
31
|
+
<p>
|
|
32
|
+
<em>⚡ <b>Live Architecture Tour</b>: Real-time Coding, Topological Orchestration & Instant Ignition</em><br>
|
|
33
|
+
<a href="https://path-ioc.dev"><b>🌐 Watch Video on path-ioc.dev</b></a> | <a href="https://cdn.path-ioc.dev/path-ioc/demo-en.mp4"><b>▶ Direct MP4 (1080p HD)</b></a>
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
22
36
|
|
|
23
37
|
---
|
|
24
38
|
|
|
25
39
|
## Quick Start
|
|
26
40
|
|
|
27
|
-
In production applications, `@path-ioc/core` works in tandem with the compiler plugin `@path-ioc/unplugin` (Compiler-Runtime Co-design).
|
|
41
|
+
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
42
|
|
|
29
43
|
### 1. Install Runtime & Bundler Plugin
|
|
30
44
|
|
|
31
45
|
```bash
|
|
32
|
-
# Runtime Core
|
|
46
|
+
# Runtime Core (Zero external dependencies)
|
|
33
47
|
pnpm add @path-ioc/core
|
|
34
48
|
|
|
35
49
|
# Universal Bundler Plugin (Dev Dependency)
|
|
@@ -44,7 +58,6 @@ Add `@path-ioc/unplugin` to your build configuration:
|
|
|
44
58
|
// vite.config.ts (or rolldown.config.ts)
|
|
45
59
|
import { defineConfig } from "vite";
|
|
46
60
|
import { vitePlugin as pathIoc } from "@path-ioc/unplugin";
|
|
47
|
-
// If using Rolldown: import { rolldownPlugin as pathIoc } from "@path-ioc/unplugin";
|
|
48
61
|
|
|
49
62
|
export default defineConfig({
|
|
50
63
|
plugins: [
|
|
@@ -58,9 +71,9 @@ export default defineConfig({
|
|
|
58
71
|
|
|
59
72
|
*Note: For Webpack 5 use `webpackPlugin`, Rspack use `rspackPlugin`, Rollup use `rollupPlugin`, and Esbuild use `esbuildPlugin`.*
|
|
60
73
|
|
|
61
|
-
### 3. Create Business Modules (
|
|
74
|
+
### 3. Create Business Modules (Short-Name Mesh ID Standard Practice)
|
|
62
75
|
|
|
63
|
-
Create module directories inside `src/modules` and export a pure `main` function:
|
|
76
|
+
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
77
|
|
|
65
78
|
```typescript
|
|
66
79
|
// src/modules/infra/db/index.ts
|
|
@@ -71,10 +84,11 @@ export const main = () => {
|
|
|
71
84
|
};
|
|
72
85
|
|
|
73
86
|
// src/modules/biz/user/index.ts
|
|
74
|
-
|
|
87
|
+
// ✅ Standard Practice: Clean short names as Mesh IDs
|
|
88
|
+
export const dependencies = ["db"];
|
|
75
89
|
|
|
76
|
-
export const main = (
|
|
77
|
-
|
|
90
|
+
export const main = ({ db }: ModularContainer) => {
|
|
91
|
+
// Direct destructuring with 100% type inference, guaranteed resolved by topological scheduler
|
|
78
92
|
return {
|
|
79
93
|
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
80
94
|
};
|
|
@@ -84,14 +98,12 @@ export const main = (container: any) => {
|
|
|
84
98
|
### 4. Create App Bootstrap Module (`src/modules/start-app/index.ts`)
|
|
85
99
|
|
|
86
100
|
```typescript
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
// All business logic stays inside IoC modules to guarantee AOP aspects and topological readiness
|
|
102
|
+
export const dependencies = ["user"];
|
|
103
|
+
|
|
104
|
+
export const main = ({ user }: ModularContainer) => {
|
|
91
105
|
console.log(user.getUser("1001"));
|
|
92
106
|
};
|
|
93
|
-
|
|
94
|
-
export const dependencies = ["user"];
|
|
95
107
|
```
|
|
96
108
|
|
|
97
109
|
### 5. Ignite the Container (`src/main.ts`)
|
|
@@ -102,7 +114,7 @@ In your application entrypoint (e.g. `src/main.ts`), ignite the lock-free topolo
|
|
|
102
114
|
// src/main.ts
|
|
103
115
|
import { createModularContainer } from "virtual:modular-container";
|
|
104
116
|
|
|
105
|
-
//
|
|
117
|
+
// Host ignition boundary: pure ignition with 0 business pollution
|
|
106
118
|
createModularContainer();
|
|
107
119
|
```
|
|
108
120
|
|
|
@@ -112,31 +124,70 @@ The plugin automatically generates `ignore.modular.d.ts` in the background, prov
|
|
|
112
124
|
|
|
113
125
|
### 6. Standalone / Pure Node.js & Testing Mode
|
|
114
126
|
|
|
115
|
-
When writing isolated unit tests
|
|
127
|
+
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`:
|
|
128
|
+
|
|
129
|
+
#### Scenario A: Unit Testing (Vitest / Jest)
|
|
130
|
+
In test suites, the test case acts as an external inspector asserting container assembly and evaluated outputs:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { expect, it } from "vitest";
|
|
134
|
+
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
135
|
+
|
|
136
|
+
it("should compile and instantiate in topological order", async () => {
|
|
137
|
+
const modules = [
|
|
138
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
139
|
+
{
|
|
140
|
+
key: "/biz/userService",
|
|
141
|
+
module: {
|
|
142
|
+
dependencies: ["db"],
|
|
143
|
+
main: ({ db }: any) => ({
|
|
144
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const compiledGraph = compileModuleGraph(modules);
|
|
151
|
+
const container: Record<string, unknown> = {};
|
|
152
|
+
await instantiateModuleContainer(compiledGraph, container);
|
|
153
|
+
|
|
154
|
+
// Unit test assertion: external probe validates assembly correctness
|
|
155
|
+
expect((container.userService as any).getUser("1001")).toBe("DB: SELECT * FROM users WHERE id = 1001");
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Scenario B: Headless Script Ignition (Business Logic Enclosed in Mesh)
|
|
160
|
+
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
161
|
|
|
117
162
|
```typescript
|
|
118
163
|
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
119
164
|
|
|
120
|
-
// 1. Explicitly define modules (declaring dependencies via keys / short-keys)
|
|
121
165
|
const modules = [
|
|
122
|
-
{ key: "/infra/db", module: { main: () =>
|
|
166
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
123
167
|
{
|
|
124
168
|
key: "/biz/userService",
|
|
125
169
|
module: {
|
|
126
170
|
dependencies: ["db"],
|
|
127
|
-
main: (
|
|
171
|
+
main: ({ db }: any) => ({
|
|
172
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
// Business logic stays inside the Mesh, guaranteeing topological readiness and AOP safety
|
|
178
|
+
key: "/app/start",
|
|
179
|
+
module: {
|
|
180
|
+
dependencies: ["userService"],
|
|
181
|
+
main: ({ userService }: any) => {
|
|
182
|
+
console.log(userService.getUser("1001"));
|
|
183
|
+
},
|
|
128
184
|
},
|
|
129
185
|
},
|
|
130
186
|
];
|
|
131
187
|
|
|
132
|
-
//
|
|
188
|
+
// Host ignition boundary: compiles and ignites with zero outer business logic contamination
|
|
133
189
|
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"));
|
|
190
|
+
await instantiateModuleContainer(compiledGraph, {});
|
|
140
191
|
```
|
|
141
192
|
|
|
142
193
|
---
|
|
@@ -145,26 +196,36 @@ console.log(container.userService.getUser("1001"));
|
|
|
145
196
|
|
|
146
197
|
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
198
|
|
|
148
|
-
### 1.
|
|
149
|
-
- **
|
|
150
|
-
|
|
151
|
-
- **
|
|
152
|
-
|
|
199
|
+
### 1. Counterpart to Native ESM: Application-Level Self-Organizing Mesh
|
|
200
|
+
- **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;
|
|
201
|
+
- **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;
|
|
202
|
+
- **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.
|
|
203
|
+
|
|
204
|
+
### 2. The Dynamic Language Paradigm: Native DAG Topological Concurrency
|
|
205
|
+
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:
|
|
206
|
+
|
|
207
|
+
- **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);
|
|
208
|
+
- **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;
|
|
209
|
+
- **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;
|
|
210
|
+
- **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.
|
|
153
211
|
|
|
154
|
-
|
|
155
|
-
- **Strings are Interfaces**: Whether Java's `interface UserData`, `Class.forName("com.xxx.UserService")`, or Path-IoC's physical path coordinates, both decouple clients by **depending upon abstractions rather than concrete implementations** (true DIP).
|
|
156
|
-
- **Physical Features as Service Discovery**: File paths provide natural coordinates for service discovery. For instance, filtering by `name.includes("/entity/orm/")` allows dynamic, zero-configuration discovery of all ORM entity modules.
|
|
212
|
+
**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.
|
|
157
213
|
|
|
158
|
-
### 3.
|
|
159
|
-
- **
|
|
160
|
-
- **
|
|
214
|
+
### 3. Physical Path as Feature (Label) vs. Short-Name as Mesh ID
|
|
215
|
+
- **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;
|
|
216
|
+
- **Physical Path is a Feature Tag**: Physical directory prefixes (e.g. `/infra/`, `/biz/`, `/aspect/`) are **feature labels** (analogous to metadata tags);
|
|
217
|
+
- **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.
|
|
161
218
|
|
|
162
|
-
### 4.
|
|
163
|
-
- **
|
|
164
|
-
- **
|
|
219
|
+
### 4. Aspect-Oriented Programming (AOP) via Pure Closures
|
|
220
|
+
- **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;
|
|
221
|
+
- **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.
|
|
165
222
|
|
|
166
|
-
### 5.
|
|
167
|
-
|
|
223
|
+
### 5. Physical Law of Cycles & DFS Fail-Fast
|
|
224
|
+
- **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;
|
|
225
|
+
- **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.
|
|
226
|
+
|
|
227
|
+
### 6. Two-Stage Execution & Edge Serverless Readiness
|
|
228
|
+
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
229
|
|
|
169
230
|
---
|
|
170
231
|
|
|
@@ -184,13 +245,13 @@ Tested on Apple Silicon under Node.js v24 (`pnpm bench`):
|
|
|
184
245
|
|
|
185
246
|
## Selection Matrix
|
|
186
247
|
|
|
187
|
-
| Comparison Dimension | **TS Decorator Stack**<br>(NestJS / Inversify / TSyringe) | **Regex Proxy Stack**<br>(Awilix) | **JVM Reflection Stack**<br>(Java Spring) | **@path-ioc/core**
|
|
248
|
+
| Comparison Dimension | **TS Decorator Stack**<br>(NestJS / Inversify / TSyringe) | **Regex Proxy Stack**<br>(Awilix) | **JVM Reflection Stack**<br>(Java Spring) | **@path-ioc/core** |
|
|
188
249
|
| :--- | :--- | :--- | :--- | :--- |
|
|
189
250
|
| **Core Contract** | `reflect-metadata` + TS Decorators | Function `.toString()` parsing + Proxy | Reflection + Bytecode + Caching (Enterprise benchmark) | **Physical Path Contract + Pure Closures + DAG Compilation** |
|
|
190
251
|
| **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** (
|
|
252
|
+
| **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
253
|
| **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** (
|
|
254
|
+
| **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
255
|
| **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
256
|
| **Code Intrusion** | High (pervasive framework decorators) | Moderate (binds parameter names) | Low (supports JSR-330 standard) | **Zero** (Pure ES functions; runs completely independently) |
|
|
196
257
|
|
|
@@ -198,7 +259,7 @@ Tested on Apple Silicon under Node.js v24 (`pnpm bench`):
|
|
|
198
259
|
|
|
199
260
|
## Formal API Reference
|
|
200
261
|
|
|
201
|
-
### 1. `compileModuleGraph`
|
|
262
|
+
### 1. `compileModuleGraph(modules)`
|
|
202
263
|
|
|
203
264
|
```typescript
|
|
204
265
|
export interface IOCModule {
|
|
@@ -221,10 +282,12 @@ export function compileModuleGraph(
|
|
|
221
282
|
): CompiledModuleGraph;
|
|
222
283
|
```
|
|
223
284
|
|
|
224
|
-
- **Complexity**: Time $O(V + E)$, Space $O(V + E)$
|
|
285
|
+
- **Algorithm & Complexity**: Time $O(V + E)$, Space $O(V + E)$ based on **DFS post-order traversal stack topological sorting**;
|
|
225
286
|
- **Fail-Fast Error Handling**: Throws descriptive errors with full cycle paths upon detecting cyclic dependencies, or on short-key collisions.
|
|
226
287
|
|
|
227
|
-
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### 2. `instantiateModuleContainer(compiledGraph, container)`
|
|
228
291
|
|
|
229
292
|
```typescript
|
|
230
293
|
export function instantiateModuleContainer(
|
|
@@ -236,7 +299,33 @@ export function instantiateModuleContainer(
|
|
|
236
299
|
- **Execution Semantics**:
|
|
237
300
|
- Iterates through `compiledGraph.sortedKeys` in validated topological order;
|
|
238
301
|
- Mounts return values to both `container[fullKey]` and `container[shortKey]`;
|
|
239
|
-
- Awaits asynchronous `main` promises before triggering dependent child nodes.
|
|
302
|
+
- Awaits asynchronous `main` promises before triggering dependent child nodes using memoized Promise streams.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### 3. `initialize(modules, container)`
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
export function initialize(
|
|
310
|
+
modules: { key: string; module: IOCModule }[],
|
|
311
|
+
container: Record<string, unknown>
|
|
312
|
+
): Promise<void>;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Convenience utility combining `compileModuleGraph` and `instantiateModuleContainer`.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Mesh Module Export Protocol
|
|
320
|
+
|
|
321
|
+
Every module located at `src/modules/**/index.ts` may export up to four standard identifiers:
|
|
322
|
+
|
|
323
|
+
| Identifier | Type Signature | Default | Description |
|
|
324
|
+
| :--- | :--- | :--- | :--- |
|
|
325
|
+
| **`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/"))`). |
|
|
326
|
+
| **`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. |
|
|
327
|
+
| **`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`). |
|
|
328
|
+
| **`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
329
|
|
|
241
330
|
---
|
|
242
331
|
|
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,32 @@
|
|
|
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
|
+
<a href="https://path-ioc.dev/zh/" target="_blank" rel="noopener noreferrer">
|
|
29
|
+
<img src="https://cdn.path-ioc.dev/path-ioc/demo-zh.webp" alt="Path-IoC 实机编码与极速点火演示" width="100%">
|
|
30
|
+
</a>
|
|
31
|
+
<p>
|
|
32
|
+
<em>⚡ <b>实机架构漫游</b>:实时编码、拓扑依赖编排与极速容器点火</em><br>
|
|
33
|
+
<a href="https://path-ioc.dev/zh/"><b>🌐 前往官网在线播放 (path-ioc.dev)</b></a> | <a href="https://cdn.path-ioc.dev/path-ioc/demo-zh.mp4"><b>▶ 直链播放 MP4 (1080p 超清)</b></a>
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
21
36
|
|
|
22
37
|
---
|
|
23
38
|
|
|
24
39
|
## 快速上手 (Quick Start)
|
|
25
40
|
|
|
26
|
-
在真实的现代前端与全栈工程中,`@path-ioc/core` 与编译期插件 `@path-ioc/unplugin` 深度协同(Compiler-Runtime Co-design
|
|
41
|
+
在真实的现代前端与全栈工程中,`@path-ioc/core` 与编译期插件 `@path-ioc/unplugin` 深度协同(Compiler-Runtime Co-design)。与此同时,`@path-ioc/core` **100% 自治且完全独立**——它拥有极轻量的纯函数运行时(仅 8.8KB,零外部依赖),无需打包工具即可在纯 Node.js、CLI 脚本、单测或 Cloudflare Workers 中直接运行。
|
|
27
42
|
|
|
28
43
|
### 1. 安装核心与构建插件
|
|
29
44
|
|
|
30
45
|
```bash
|
|
31
|
-
# 运行时核心
|
|
46
|
+
# 运行时核心 (零外部依赖)
|
|
32
47
|
pnpm add @path-ioc/core
|
|
33
48
|
|
|
34
49
|
# 通用构建插件 (开发依赖)
|
|
@@ -37,13 +52,12 @@ pnpm add -D @path-ioc/unplugin
|
|
|
37
52
|
|
|
38
53
|
### 2. 配置构建工具 (支持 Vite / Rolldown / Webpack / Rspack / Rollup / Esbuild)
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
在构建配置文件中引入 `@path-ioc/unplugin`:
|
|
41
56
|
|
|
42
57
|
```typescript
|
|
43
58
|
// vite.config.ts (或 rolldown.config.ts)
|
|
44
59
|
import { defineConfig } from "vite";
|
|
45
60
|
import { vitePlugin as pathIoc } from "@path-ioc/unplugin";
|
|
46
|
-
// 若使用 Rolldown: import { rolldownPlugin as pathIoc } from "@path-ioc/unplugin";
|
|
47
61
|
|
|
48
62
|
export default defineConfig({
|
|
49
63
|
plugins: [
|
|
@@ -57,9 +71,9 @@ export default defineConfig({
|
|
|
57
71
|
|
|
58
72
|
*注:Webpack 5 请使用 `webpackPlugin`,Rspack 使用 `rspackPlugin`,Rollup 使用 `rollupPlugin`,Esbuild 使用 `esbuildPlugin`。*
|
|
59
73
|
|
|
60
|
-
### 3. 创建业务模块 (
|
|
74
|
+
### 3. 创建业务模块 (短名称 Mesh ID 正常实践)
|
|
61
75
|
|
|
62
|
-
在 `src/modules` 下自由创建模块目录并导出 `main`
|
|
76
|
+
在 `src/modules` 下自由创建模块目录并导出 `main` 纯函数。静态依赖使用**短名称(Mesh ID)**是框架的正常实践;在静态依赖中硬编码物理全称属于反模式与错误实践(会导致强路径耦合并违反依赖倒置),物理全称专用于动态特征匹配与切面筛选:
|
|
63
77
|
|
|
64
78
|
```typescript
|
|
65
79
|
// src/modules/infra/db/index.ts
|
|
@@ -70,10 +84,11 @@ export const main = () => {
|
|
|
70
84
|
};
|
|
71
85
|
|
|
72
86
|
// src/modules/biz/user/index.ts
|
|
73
|
-
|
|
87
|
+
// ✅ 正常实践:短名称作为 Mesh ID
|
|
88
|
+
export const dependencies = ["db"];
|
|
74
89
|
|
|
75
|
-
export const main = (
|
|
76
|
-
|
|
90
|
+
export const main = ({ db }: ModularContainer) => {
|
|
91
|
+
// 直接解构,无缝调用,全局类型 100% 自动同步推导
|
|
77
92
|
return {
|
|
78
93
|
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
79
94
|
};
|
|
@@ -84,23 +99,22 @@ export const main = (container: any) => {
|
|
|
84
99
|
|
|
85
100
|
```typescript
|
|
86
101
|
// 一切业务皆模块:初始调用收敛在 IoC 模块内,天然保障 AOP 切面与依赖拓扑就绪
|
|
87
|
-
export const
|
|
88
|
-
|
|
102
|
+
export const dependencies = ["user"];
|
|
103
|
+
|
|
104
|
+
export const main = ({ user }: ModularContainer) => {
|
|
89
105
|
console.log(user.getUser("1001"));
|
|
90
106
|
};
|
|
91
|
-
|
|
92
|
-
export const dependencies = ["user"];
|
|
93
107
|
```
|
|
94
108
|
|
|
95
|
-
### 5.
|
|
109
|
+
### 5. 宿主应用入口点火唤醒 (`src/main.ts`)
|
|
96
110
|
|
|
97
|
-
在应用入口(如 `src/main.ts
|
|
111
|
+
在应用入口(如 `src/main.ts`),一行代码唤醒全量拓扑流:
|
|
98
112
|
|
|
99
113
|
```typescript
|
|
100
114
|
// src/main.ts
|
|
101
115
|
import { createModularContainer } from "virtual:modular-container";
|
|
102
116
|
|
|
103
|
-
//
|
|
117
|
+
// 宿主点火边界:入口保持绝对纯粹,仅充当容器点火器,零业务逻辑污染
|
|
104
118
|
createModularContainer();
|
|
105
119
|
```
|
|
106
120
|
|
|
@@ -108,93 +122,144 @@ createModularContainer();
|
|
|
108
122
|
|
|
109
123
|
---
|
|
110
124
|
|
|
111
|
-
### 6. 原生 Core
|
|
125
|
+
### 6. 原生 Core 独立运行与单测模式 (Unit Test & Standalone)
|
|
112
126
|
|
|
113
|
-
|
|
127
|
+
若在编写隔离单元测试(如 Vitest / Jest)或无打包工具的纯 Node.js 脚本时,可直接使用 `@path-ioc/core` 原生纯函数接口:
|
|
128
|
+
|
|
129
|
+
#### 场景 A:单元测试场景 (Vitest / Jest)
|
|
130
|
+
在单测套件中,测试用例作为外部观察者验证依赖图的装配与求值结果:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { expect, it } from "vitest";
|
|
134
|
+
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
135
|
+
|
|
136
|
+
it("should compile and instantiate in topological order", async () => {
|
|
137
|
+
const modules = [
|
|
138
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
139
|
+
{
|
|
140
|
+
key: "/biz/userService",
|
|
141
|
+
module: {
|
|
142
|
+
dependencies: ["db"],
|
|
143
|
+
main: ({ db }: any) => ({
|
|
144
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const compiledGraph = compileModuleGraph(modules);
|
|
151
|
+
const container: Record<string, unknown> = {};
|
|
152
|
+
await instantiateModuleContainer(compiledGraph, container);
|
|
153
|
+
|
|
154
|
+
// 单测断言:外部探针验证装配结果
|
|
155
|
+
expect((container.userService as any).getUser("1001")).toBe("DB: SELECT * FROM users WHERE id = 1001");
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### 场景 B:独立脚本点火 (遵循点火跃迁与业务自闭环)
|
|
160
|
+
即便在没有构建工具的极简脚本中,也必须恪守“点火跃迁”准则——**业务逻辑严格封装在启动模块内,外部仅负责图编译与一行点火唤醒**:
|
|
114
161
|
|
|
115
162
|
```typescript
|
|
116
163
|
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
117
164
|
|
|
118
|
-
// 1. 显式定义模块列表 (短名称声明依赖与依赖查找)
|
|
119
165
|
const modules = [
|
|
120
|
-
{ key: "/infra/db", module: { main: () =>
|
|
166
|
+
{ key: "/infra/db", module: { main: () => ({ query: (sql: string) => `DB: ${sql}` }) } },
|
|
121
167
|
{
|
|
122
168
|
key: "/biz/userService",
|
|
123
169
|
module: {
|
|
124
170
|
dependencies: ["db"],
|
|
125
|
-
main: (
|
|
171
|
+
main: ({ db }: any) => ({
|
|
172
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
// 一切业务闭环在 Mesh 内部,天然保障 AOP 拦截与生命周期完整
|
|
178
|
+
key: "/app/start",
|
|
179
|
+
module: {
|
|
180
|
+
dependencies: ["userService"],
|
|
181
|
+
main: ({ userService }: any) => {
|
|
182
|
+
console.log(userService.getUser("1001"));
|
|
183
|
+
},
|
|
126
184
|
},
|
|
127
185
|
},
|
|
128
186
|
];
|
|
129
187
|
|
|
130
|
-
//
|
|
188
|
+
// 宿主点火边界:只做编译与容器唤醒,零业务逻辑污染
|
|
131
189
|
const compiledGraph = compileModuleGraph(modules);
|
|
132
|
-
|
|
133
|
-
// 3. 动态填充容器
|
|
134
|
-
const container: Record<string, unknown> = {};
|
|
135
|
-
await instantiateModuleContainer(compiledGraph, container);
|
|
190
|
+
await instantiateModuleContainer(compiledGraph, {});
|
|
136
191
|
```
|
|
137
192
|
|
|
138
193
|
---
|
|
139
194
|
|
|
140
195
|
## 核心设计哲学:向 Spring 致敬与动态语言范式应答 (Architecture Philosophy)
|
|
141
196
|
|
|
142
|
-
Path-IoC 的底层设计建立在对 **Java Spring 经典控制反转 (IoC) 哲学**
|
|
143
|
-
|
|
144
|
-
### 1. 动态语言范式应答:单线程 Event Loop 下的原生 DAG 拓扑并发
|
|
197
|
+
Path-IoC 的底层设计建立在对 **Java Spring 经典控制反转 (IoC) 哲学** 的深切致敬上,并针对 JavaScript/TypeScript 的单线程异步模型进行了原生升级:
|
|
145
198
|
|
|
146
|
-
|
|
199
|
+
### 1. 对标原生 ESM:纯粹的应用级自组织模块系统
|
|
200
|
+
- **原生 ESM 的根本局限**:原生 JavaScript `import` 语句本质是文件级的硬链接。当业务演进到数百个模块时,纵横交错的相对路径(`../../../../utils`)会导致严重的重构阻力与死锁隐患;
|
|
201
|
+
- **Path-IoC 的生态位定位**:**对标原生 ESM,作为业务层“应用级自组织模块系统”**。宿主(Hono、Express、Koa、Next.js API、Workers、CLI)只做一行代码点火(`createModularContainer()`),业务模块在网格内自闭环运转,不挑宿主,零框架锁死;
|
|
202
|
+
- **完全脱离传统后端框架的侵入式范畴**:不少开发者习惯将 IoC 与重量级后端框架(如 NestJS)混为一谈,这是典型的范畴谬误。后端全家桶往往用繁琐的类装饰器、控制器与专有管道强行侵入业务代码并绑架应用生命周期;而 Path-IoC 绝非后端框架,而是**极简、轻量、无侵入的自闭环网格(Mesh)**,只专注解决应用层模块解耦与拓扑调度,在浏览器前端、边缘计算、轻量服务端与 CLI 中皆可原生自洽运转。
|
|
147
203
|
|
|
148
|
-
|
|
149
|
-
-
|
|
204
|
+
### 2. 动态语言范式应答:单线程 Event Loop 下的原生 DAG 拓扑并发
|
|
205
|
+
并非 Path-IoC 创造了神迹,而是 Path-IoC 彻底顺应了 JavaScript 单线程非阻塞 Event Loop 的物理特性。在传统认知中,容器启动时的拓扑并发调度常被视作“不可思议的神迹”,其根源在于对比传统 IoC 体系下四大不可调和的深层矛盾:
|
|
150
206
|
|
|
151
|
-
|
|
207
|
+
- **对比 Java Spring 的串行初始化性能**:Java 虽拥有物理多线程,但为了规避并发创建 Bean 带来的共享内存竞争、内存可见性与三级缓存裸指针逃逸风险(受 JMM 内存模型限制),Spring 容器初始化阶段底层只能退守于**严谨的单线程严格串行装配(Serial Pipeline,耗时累加 $\sum t_i$)**;
|
|
208
|
+
- **对比传统 JS IoC 框架的异步初始化与懒加载矛盾**:在 JavaScript 单线程模型中,Proxy 同步 Getter 无法暂停去等待异步微任务,处于 pending 态的 Promise 也无法充当三级缓存的裸指针;传统 JS IoC(如 NestJS)无法解决拓扑并发调度,在核心源码中直接通过 `for...of await` 逐个硬编码串行排队;
|
|
209
|
+
- **DI 的调用期与初始化依赖混淆**:传统构造器注入将未来运行期的方法调用,强行升格为启动时刻的物理先决条件,人为摧毁了纯净 DAG 并制造了大量伪循环依赖;
|
|
210
|
+
- **缺少有效 DL(依赖查找)手段导致的 AOP 缺陷**:缺乏无侵入的依赖查找机制,传统框架的 AOP 只能退化为目标类显式 import 并手写类装饰器(如 `@UseInterceptors`)的“主动组合”,彻底违背了 AOP 非侵入横切的初心。
|
|
152
211
|
|
|
153
|
-
-
|
|
154
|
-
- **物理路径特征契约**:路径不仅是坐标,更是服务发现的天然接口。例如在服务端开发中,只需通过路径特征过滤函数 `name.includes("/entity/orm/")`,即可零配置全自动感知并收集所有 ORM 实体(如 `orm-entities` 模块),达到浑然天成的解耦与热插拔。
|
|
212
|
+
**Path-IoC 的破局之道**:将“初始化依赖(DAG 拓扑)”与“调用期依赖(DL 查找)”彻底正交解耦,成环概率在数学上归零。依托 JavaScript 单线程天然消除共享内存竞态的物理优势,同层无依赖节点通过 `Promise.all` 记忆化并发流级联点火(启动耗时从 $\sum t_i$ 降维为瓶颈节点的 $\max t_i$),并基于纯函数闭包与依赖查找实现真正无侵入的面向切面编程。
|
|
155
213
|
|
|
156
|
-
### 3.
|
|
214
|
+
### 3. 物理路径即特征(Feature)与短名称 Mesh ID
|
|
215
|
+
- **短名称就是 Mesh ID**:日常业务开发中,开发者只认短名称(`dependencies = ["logger", "db"]`,消费时 `const { logger, db } = container;`),极简直观,享受 IDE 自动补全;
|
|
216
|
+
- **全路径是“特征标签”(类比元数据注解)**:全路径中的前缀(如 `/infra/`、`/biz/`)是用于动态特征匹配与切面筛选的特征标签;
|
|
217
|
+
- **设计准则**:在静态依赖中硬编码物理全称属于反模式与错误实践(会引入强路径耦合并严重违反依赖倒置原则);物理全称专用于动态特征匹配与 AOP 切面筛选,常规业务依赖必须始终使用短名称 Mesh ID。
|
|
157
218
|
|
|
158
|
-
|
|
159
|
-
-
|
|
219
|
+
### 4. AOP 切面机制:零学习成本的原生切面 (DL-based Aspect)
|
|
220
|
+
- **不内置多余特权概念**:Path-IoC 不内置任何繁杂概念(如 `Guards`、`Interceptors`、`Pipes`、`Filters`)。在 JavaScript 动态语言下,高阶函数与解构代理本身就是最纯粹的 AOP;
|
|
221
|
+
- **完全 AOP 能力 & 零学习成本**:切面模块在自身 `dependencies` 中声明目标模块路径模式函数。拓扑引擎保证目标模块优先实例化,切面随后唤醒并对目标对象施加代理包装,实现 100% 非侵入切面织入。
|
|
160
222
|
|
|
161
|
-
###
|
|
223
|
+
### 5. 单线程事件循环下的物理铁律:DFS Fail-Fast 拦截
|
|
224
|
+
- **单线程 Proxy 的物理不可逾越性**:JavaScript 的 Proxy Getter(`container.xxx`)是**纯同步操作**,微任务无法在此暂停挂起去 await 异步操作。因此在单线程下“一边动态访问 Proxy Getter,一边还能动态解异步循环依赖”在物理机制上绝不成立;
|
|
225
|
+
- **Core 引擎的工程立场**:`@path-ioc/core` 保持严谨正义——使用 **DFS 拓扑分析严格 Fail-Fast 拦截环形依赖**,并打印完整环路调用链路,拒绝用隐式机制遮蔽架构腐败。
|
|
162
226
|
|
|
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 实现按需“依赖感知”,在运行期无痛完成无锁解环。
|
|
227
|
+
### 6. 零运行期反射与两阶段图编译 (Two-Stage Separation)
|
|
228
|
+
彻底摆脱 `reflect-metadata` 重型反射包袱。架构上将 **静态图编译 (`compileModuleGraph`)** 与 **动态容器填充 (`instantiateModuleContainer`)** 彻底分离。在 Cloudflare Workers 或 Node.js 高频请求场景中,服务冷启动时仅编译一次拓扑图(500 节点仅需 1.72ms),单次请求到来时直通填充容器(仅 21.2µs),大流量下实测降低 80% 以上的框架层 CPU 开销。
|
|
170
229
|
|
|
171
|
-
|
|
230
|
+
---
|
|
172
231
|
|
|
173
|
-
|
|
232
|
+
## 硬核基准性能 (Hardware-Verified Benchmarks)
|
|
174
233
|
|
|
175
|
-
|
|
234
|
+
基于 Apple Silicon 芯片、Node.js v24 原生实测(运行 `pnpm bench`):
|
|
176
235
|
|
|
177
|
-
|
|
236
|
+
| 压测指标 (Benchmark Item) | 复杂度规模 | 平均耗时 (Avg Time) | 性能表现说明 |
|
|
237
|
+
| :--- | :--- | :--- | :--- |
|
|
238
|
+
| **`instantiateModuleContainer`** | **50 节点** 容器实例化 | **`21.2 µs`** | 微秒级直通,Serverless HTTP 请求期 0 延迟 |
|
|
239
|
+
| **`compileModuleGraph`** | **50 节点** 静态图编译 | **`90.8 µs`** | 亚毫秒级完成全拓扑环路校验 |
|
|
240
|
+
| **`instantiateModuleContainer`** | **500 节点** 容器实例化 | **`227 µs`** | 超大型项目依然近乎零开销 |
|
|
241
|
+
| **`compileModuleGraph`** | **500 节点** 复杂交叉依赖 | **`1.72 ms`** | 进程冷启动仅需 1 次,随后全量缓存复用 |
|
|
242
|
+
| **`compileModuleGraph`** | **2,000 节点** 超大规模拓扑 | **`15.5 ms`** | 工业级深层拓扑解析极限 |
|
|
178
243
|
|
|
179
244
|
---
|
|
180
245
|
|
|
181
246
|
## 主流 IoC 框架选型对比矩阵 (Selection Matrix)
|
|
182
247
|
|
|
183
|
-
| 对比维度 | **TS 装饰器派**<br>(NestJS / Inversify / TSyringe) | **正则 Proxy 派**<br>(Awilix) | **JVM 反射派**<br>(Java Spring) | **@path-ioc/core**
|
|
248
|
+
| 对比维度 | **TS 装饰器派**<br>(NestJS / Inversify / TSyringe) | **正则 Proxy 派**<br>(Awilix) | **JVM 反射派**<br>(Java Spring) | **@path-ioc/core** |
|
|
184
249
|
| :--- | :--- | :--- | :--- | :--- |
|
|
185
250
|
| **底层依据** | `reflect-metadata` + TS Decorator | 函数 `.toString()` 正则 + Proxy | Java 反射 + 字节码 + 缓存 (静态语言工业标杆) | **物理路径契约 + 纯闭包工厂 + DAG 图编译** |
|
|
186
|
-
| **编译/环境兼容性** | 差 (强依赖元数据,纯类型擦除转译即崩溃) | 良好 | JVM 物理机原生支持 | **极致** (纯 ES Module
|
|
187
|
-
| **初始化装配机制** | 串行主导 / Class 构造纯同步,async Provider 阻塞 | 不支持异步初始化 | 严格单线程串行装配 (出于 JMM 线程安全考量) | **原生 DAG
|
|
251
|
+
| **编译/环境兼容性** | 差 (强依赖元数据,纯类型擦除转译即崩溃) | 良好 | JVM 物理机原生支持 | **极致** (纯 ES Module 函数闭包,零反射元数据) |
|
|
252
|
+
| **初始化装配机制** | 串行主导 / Class 构造纯同步,async Provider 阻塞 | 不支持异步初始化 | 严格单线程串行装配 (出于 JMM 线程安全考量) | **原生 DAG 拓扑并发流** (DFS 后序遍历压栈 + Promise.all 记忆化) |
|
|
188
253
|
| **AOP 切面机制** | 概念繁杂 & 强绑定且仅限 Controller | 无内置 AOP 能力 | 划时代声明式代理 (AspectJ) | **完全 AOP 能力 & 零学习成本** (基于 DL 依赖查找与 JS 高阶代理) |
|
|
189
|
-
| **循环依赖与解环机制** | 死锁重灾区 (`forwardRef` 遇 async 死锁) | 受限 (仅限纯同步) | 辩证支持 (三级缓存解环,易诱发隐蔽 Bug) |
|
|
190
|
-
| **高并发 / 运行期性能** | 高频元数据反射损耗 | Proxy 属性访问开销 | 工业级高可靠 (受限 JVM 物理模型) | **极高** (
|
|
254
|
+
| **循环依赖与解环机制** | 死锁重灾区 (`forwardRef` 遇 async 死锁) | 受限 (仅限纯同步) | 辩证支持 (三级缓存解环,易诱发隐蔽 Bug) | **DFS Fail-Fast 严格拦截** (拒绝遮蔽设计漏洞,打印完整环路链路) |
|
|
255
|
+
| **高并发 / 运行期性能** | 高频元数据反射损耗 | Proxy 属性访问开销 | 工业级高可靠 (受限 JVM 物理模型) | **极高** (静态图编译与填充分离,仅 21.2µs 直通,降 80% CPU 损耗) |
|
|
191
256
|
| **架构解耦与侵入性** | 强侵入 (代码处处与框架类和注解绑定) | 中度 (绑定参数名) | 低侵入 (支持 JSR-330 标准注解) | **零侵入** (模块仅为纯函数,脱离框架完全可跑) |
|
|
192
257
|
|
|
193
258
|
---
|
|
194
259
|
|
|
195
260
|
## 形式化 API 规范 (Formal API Specification)
|
|
196
261
|
|
|
197
|
-
### 1. `compileModuleGraph`
|
|
262
|
+
### 1. `compileModuleGraph(modules)`
|
|
198
263
|
|
|
199
264
|
```typescript
|
|
200
265
|
export interface IOCModule {
|
|
@@ -217,10 +282,12 @@ export function compileModuleGraph(
|
|
|
217
282
|
): CompiledModuleGraph;
|
|
218
283
|
```
|
|
219
284
|
|
|
220
|
-
- **算法复杂度**:时间复杂度 $O(V + E)$,空间复杂度 $O(V + E)$(基于
|
|
221
|
-
-
|
|
285
|
+
- **算法复杂度**:时间复杂度 $O(V + E)$,空间复杂度 $O(V + E)$(基于 **DFS 后序遍历压栈拓扑排序**);
|
|
286
|
+
- **异常捕获**:若检测到环路依赖,立即抛出附带完整环路链路路径的错误;若检测到短名称命名冲突,抛出明确诊断提示。
|
|
287
|
+
|
|
288
|
+
---
|
|
222
289
|
|
|
223
|
-
### 2. `instantiateModuleContainer`
|
|
290
|
+
### 2. `instantiateModuleContainer(compiledGraph, container)`
|
|
224
291
|
|
|
225
292
|
```typescript
|
|
226
293
|
export function instantiateModuleContainer(
|
|
@@ -230,9 +297,35 @@ export function instantiateModuleContainer(
|
|
|
230
297
|
```
|
|
231
298
|
|
|
232
299
|
- **行为规范**:
|
|
233
|
-
- 按 `compiledGraph.sortedKeys`
|
|
300
|
+
- 按 `compiledGraph.sortedKeys` 拓扑顺序依次唤醒模块 `main` 函数;
|
|
234
301
|
- 自动将模块执行返回值挂载到 `container[fullKey]` 与 `container[shortKey]`;
|
|
235
|
-
- 若模块的 `main` 为异步 Promise
|
|
302
|
+
- 若模块的 `main` 为异步 Promise,引擎自动通过 Promise 记忆化反应式并发流协调后续依赖子节点。
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### 3. `initialize(modules, container)`
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
export function initialize(
|
|
310
|
+
modules: { key: string; module: IOCModule }[],
|
|
311
|
+
container: Record<string, unknown>
|
|
312
|
+
): Promise<void>;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
便捷快捷方法,内部依次调用 `compileModuleGraph` 与 `instantiateModuleContainer`。
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Mesh 模块导出规范 (Mesh Export Protocol)
|
|
320
|
+
|
|
321
|
+
在 `src/modules/**/index.ts` 中,允许导出以下 4 个标准变量:
|
|
322
|
+
|
|
323
|
+
| 导出变量名 | 类型 | 默认值 | 作用说明 |
|
|
324
|
+
| :--- | :--- | :--- | :--- |
|
|
325
|
+
| **`main`** *(必须)* | `(container: ModularContainer, allModuleNames: string[]) => any \| Promise<any>` | - | 模块工厂函数。第二个入参 `allModuleNames` 传递的是全系统全量物理模块 Key 列表;在进行动态模块匹配或 AOP 切面织入时,**必须在函数内显式根据特征过滤**(如 `allModuleNames.filter(p => p.startsWith("/biz/"))`)。 |
|
|
326
|
+
| **`dependencies`** *(可选)* | `string[] \| ((allModuleNames: string[]) => string[])` | `[]` | 拓扑依赖声明。静态依赖必须使用极简短名称(如 `["logger", "db"]`);支持传入函数进行动态拓扑排序前置编排。 |
|
|
327
|
+
| **`order`** *(可选)* | `number` | `99999` | 执行时序权重。**严格遵循升序规则**:数值越小越先执行(如 `order: 1` 优先于 `order: 10`,默认值 `99999`),在无拓扑依赖约束时生效。 |
|
|
328
|
+
| **`skip`** *(可选)* | `boolean` | `false` | 跳过执行标记。用于外部预先注入的模块(如后端请求隔离时注入 `requestContext`)。**注意契约**:即便标记了 `skip: true`,也**必须导出一个 dummy `main` 函数**(如 `export const main = (): MyType => ({} as any)`),以通过核心依赖图校验与 unplugin 静态类型生成。 |
|
|
236
329
|
|
|
237
330
|
---
|
|
238
331
|
|