@path-ioc/core 0.1.1
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 +237 -0
- package/dist/index.cjs +256 -0
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +227 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>@path-ioc/core</h1>
|
|
3
|
+
<p><b>Spring 有 Bean,Nest 有 Provider,Path-IoC 有 Mesh。</b></p>
|
|
4
|
+
<p><b>像 lodash-es 一样纯粹通用的 TypeScript/JavaScript 路径依赖查找引擎 (IoC-DL)</b></p>
|
|
5
|
+
<p><b>JavaScript/TypeScript 动态语言模块控制反转的原生正解</b></p>
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<a href="https://www.npmjs.com/package/@path-ioc/core"><img src="https://img.shields.io/npm/v/@path-ioc/core.svg" alt="NPM version"></a>
|
|
9
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/npm/l/@path-ioc/core.svg" alt="License"></a>
|
|
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
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
> **什么是 Path-IoC (IoC-DL)?**
|
|
15
|
+
> 告别原始 `import` 泥潭与传统重型黑盒 DI 在 JS 异步生态里的死锁与元数据包袱。Path-IoC 采用 **Dependency Lookup (依赖查找)** 范式——物理路径即抽象契约,静态图启动期预编译,原生 `async/await` 拓扑并发。零反射、零概念包袱,组件面向 `container` 极简解构。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 快速上手 (Quick Start)
|
|
20
|
+
|
|
21
|
+
在真实的现代前端与全栈工程中,`@path-ioc/core` 与编译期插件 `@path-ioc/unplugin` 深度协同(Compiler-Runtime Co-design)。
|
|
22
|
+
|
|
23
|
+
### 1. 安装核心与构建插件
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 运行时核心
|
|
27
|
+
pnpm add @path-ioc/core
|
|
28
|
+
|
|
29
|
+
# 通用构建插件 (开发依赖)
|
|
30
|
+
pnpm add -D @path-ioc/unplugin
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. 配置构建工具 (支持 Vite / Rolldown / Webpack / Rspack / Rollup / Esbuild)
|
|
34
|
+
|
|
35
|
+
在你的构建配置文件中引入 `@path-ioc/unplugin`:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// vite.config.ts (或 rolldown.config.ts)
|
|
39
|
+
import { defineConfig } from "vite";
|
|
40
|
+
import { vitePlugin as pathIoc } from "@path-ioc/unplugin";
|
|
41
|
+
// 若使用 Rolldown: import { rolldownPlugin as pathIoc } from "@path-ioc/unplugin";
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
plugins: [
|
|
45
|
+
pathIoc({
|
|
46
|
+
modulesPath: "src/modules", // 模块扫描根目录 (默认: 'src/modules')
|
|
47
|
+
typeFileOutput: "types", // 自动生成的全局类型输出目录
|
|
48
|
+
}),
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
*注:Webpack 5 请使用 `webpackPlugin`,Rspack 使用 `rspackPlugin`,Rollup 使用 `rollupPlugin`,Esbuild 使用 `esbuildPlugin`。*
|
|
54
|
+
|
|
55
|
+
### 3. 创建业务模块 (无需 import,物理路径即契约)
|
|
56
|
+
|
|
57
|
+
在 `src/modules` 下自由创建模块目录并导出 `main` 纯函数:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// src/modules/infra/db/index.ts
|
|
61
|
+
export const main = () => {
|
|
62
|
+
return {
|
|
63
|
+
query: (sql: string) => `Executed: ${sql}`,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/modules/biz/user/index.ts
|
|
68
|
+
export const dependencies = ["db"]; // 声明正向依赖
|
|
69
|
+
|
|
70
|
+
export const main = (container: any) => {
|
|
71
|
+
const { db } = container; // 依赖查找 (DL),由拓扑引擎保障前置就绪
|
|
72
|
+
return {
|
|
73
|
+
getUser: (id: string) => db.query(`SELECT * FROM users WHERE id = ${id}`),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. 编写应用启动模块 (`src/modules/start-app/index.ts`)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// 一切业务皆模块:初始调用收敛在 IoC 模块内,天然保障 AOP 切面与依赖拓扑就绪
|
|
82
|
+
export const main = (container: any) => {
|
|
83
|
+
const { user } = container;
|
|
84
|
+
console.log(user.getUser("1001"));
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const dependencies = ["user"];
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 5. 应用入口点火唤醒 (`src/main.ts`)
|
|
91
|
+
|
|
92
|
+
在应用入口(如 `src/main.ts`),一行代码唤醒全量无锁拓扑流:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// src/main.ts
|
|
96
|
+
import { createModularContainer } from "virtual:modular-container";
|
|
97
|
+
|
|
98
|
+
// 入口保持绝对纯粹:仅充当容器点火器,零业务逻辑污染
|
|
99
|
+
createModularContainer();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
插件会自动为全量模块生成 `ignore.modular.d.ts`,享受 100% 静态类型安全与 IDE 自动补全!
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### 6. 原生 Core 独立运行模式 (无打包器 / 纯单测模式)
|
|
107
|
+
|
|
108
|
+
若在纯 Node.js 脚本、无打包工具或编写隔离单元测试时,亦可直接使用 `@path-ioc/core` 原生纯函数接口:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { compileModuleGraph, instantiateModuleContainer } from "@path-ioc/core";
|
|
112
|
+
|
|
113
|
+
// 1. 显式定义模块列表 (短名称声明依赖与依赖查找)
|
|
114
|
+
const modules = [
|
|
115
|
+
{ key: "/infra/db", module: { main: () => "PostgreSQL Connection" } },
|
|
116
|
+
{
|
|
117
|
+
key: "/biz/userService",
|
|
118
|
+
module: {
|
|
119
|
+
dependencies: ["db"],
|
|
120
|
+
main: (c) => ({ db: c.db, getUser: (id: string) => `User ${id}` }),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// 2. 纯同步编译拓扑图 (单次纳秒级)
|
|
126
|
+
const compiledGraph = compileModuleGraph(modules);
|
|
127
|
+
|
|
128
|
+
// 3. 动态填充容器
|
|
129
|
+
const container: Record<string, unknown> = {};
|
|
130
|
+
await instantiateModuleContainer(compiledGraph, container);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 核心设计哲学:向 Spring 致敬与动态语言范式应答 (Architecture Philosophy)
|
|
136
|
+
|
|
137
|
+
Path-IoC 的底层设计建立在对 **Java Spring 经典控制反转 (IoC) 哲学** 的深切致敬上。它让 Spring 的伟大解耦思想在 JavaScript/TypeScript 的单线程 `async`、动态语言特性与纯函数语境下得到了原生的继承与演进:
|
|
138
|
+
|
|
139
|
+
### 1. 动态语言范式应答:单线程 Event Loop 下的原生 DAG 拓扑并发
|
|
140
|
+
|
|
141
|
+
并非 Path-IoC 创造了神迹,而是 Path-IoC 彻底顺应了 JavaScript 单线程非阻塞 Event Loop 的物理特性:
|
|
142
|
+
|
|
143
|
+
- **物理特性的镜像差异(多线程串行 vs 单线程并行/并发)**:Java 虽然拥有物理多线程,但为了规避并发创建 Bean 带来的共享内存竞争、内存可见性与死锁隐患(JMM 模型限制),Spring 容器初始化阶段在框架底层只能退守于**严谨的单线程严格串行装配(Serial Pipeline)**;
|
|
144
|
+
- **利用 JS 天然优势**:JavaScript 的单线程 Event Loop **天然消除了共享内存竞态条件与锁死锁**。Path-IoC 利用这一天然物理优势建立 **原生 `async/await` 反应式拓扑有向无环图 (DAG)**,在初始化阶段实现同层无依赖节点的**全量并行/并发级联点火(Parallel / Concurrent Activation)**——既消除了单线程串行排队的吞吐瓶颈,又发挥了事件驱动非阻塞 I/O 的极高吞吐优势。
|
|
145
|
+
|
|
146
|
+
### 2. 字符串即接口表征:物理特征即为抽象契约 (Path as Contract)
|
|
147
|
+
|
|
148
|
+
- **字符串也是接口的一种表达形式**:无论是 Java 传统的 `interface UserData`、`Class.forName("com.xxx.UserService")` 包路径,还是 Path-IoC 中的全限定路径与短名称,本质上都是在**依赖抽象而非依赖具体**,这正是依赖倒置原则 (DIP) 的终极真相。
|
|
149
|
+
- **物理路径特征契约**:路径不仅是坐标,更是服务发现的天然接口。例如在服务端开发中,只需通过路径特征过滤函数 `name.includes("/entity/orm/")`,即可零配置全自动感知并收集所有 ORM 实体(如 `orm-entities` 模块),达到浑然天成的解耦与热插拔。
|
|
150
|
+
|
|
151
|
+
### 3. AOP 切面机制:零学习成本的原生切面 (DL-based Aspect)
|
|
152
|
+
|
|
153
|
+
- **不内置多余特权概念**:Path-IoC 不内置任何繁重的特权概念工具(如 `Guards`、`Interceptors`、`Pipes`、`Filters` 或复杂的 JDK 动态代理)。Path-IoC 认为在 JavaScript 动态语言下,高阶函数与解构代理本身就是最纯粹的 AOP。
|
|
154
|
+
- **完全 AOP 能力 & 零学习成本**:凭借底层的依赖查找 (DL) 能力与动态语言直觉,切面模块(Aspect Mesh)只需在自身的 `dependencies` 函数中声明它要切入的目标模块路径模式(物理上正向依赖目标模块)。拓扑引擎保证目标模块优先完成实例化,切面模块随后唤醒并对 `container` 上的目标对象施加高阶代理包装,无需学习任何框架特有概念,即可具备完全的非侵入式 AOP 切面能力。
|
|
155
|
+
|
|
156
|
+
### 4. 循环依赖与“依赖感知”理论哲学 (Circular Dependency & Dependency Sensing)
|
|
157
|
+
|
|
158
|
+
- **纯理论提炼**:**“依赖感知”与“循环依赖解环”本质上是同一物理能力的不同理解角度**。
|
|
159
|
+
- **“依赖感知”是核心能力**;
|
|
160
|
+
- **正面作用一**:运行时循环依赖解环;
|
|
161
|
+
- **正面作用二**:按需子图懒加载 (Lazy Loading);
|
|
162
|
+
- **伴生副作用**:遮蔽系统设计缺陷、导致架构隐式腐烂、以及无法原生支持 `async` 异步初始化。
|
|
163
|
+
- **Core 引擎的工程立场**:`@path-ioc/core` 保持严谨正义——用 DFS 静态深搜 Fail-Fast 抛错,拒绝用隐式解环遮蔽系统架构漏洞。
|
|
164
|
+
- **Container 的 Turbo 机制对比**:若因历史包袱需解环,在纯同步全模块场景下,扩展层 `@path-ioc/container` 的 Turbo 模式通过 Proxy Dynamic Getter 实现按需“依赖感知”,在运行期无痛完成无锁解环。
|
|
165
|
+
|
|
166
|
+
### 5. 全局类型补齐:开发期 DX 与静态类型的殊途同归
|
|
167
|
+
|
|
168
|
+
Java 在编译期拥有原生的 class 类型,而 JS/TS 字符串路径在静态阶段缺乏推导。Path-IoC 通过构建插件(`@path-ioc/unplugin`)在开发期(DX 阶段)自动扫描物理目录并实时生成 `ModularContainer` 全局类型接口(`ignore.modular.d.ts`),**完美补齐了动态语言在静态类型推导上的短板**,达到了与 Java 静态编译完全一致的类型安全与智能补全体验。
|
|
169
|
+
|
|
170
|
+
### 6. 零运行期反射与两阶段图编译 (Two-Stage Separation)
|
|
171
|
+
|
|
172
|
+
彻底摆脱 `reflect-metadata` 重型反射包袱。架构上将 **静态图编译 (`compileModuleGraph`)** 与 **动态容器填充 (`instantiateModuleContainer`)** 彻底分离。在 Node.js / Workers 高频 HTTP 请求场景中,服务启动时仅编译一次拓扑图,单次请求到来时直通填充容器,彻底免去每次请求重复解析依赖树与反射的开销,大流量下实测可降低 80% 以上的框架层 CPU 开销。
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 主流 IoC 框架选型对比矩阵 (Selection Matrix)
|
|
177
|
+
|
|
178
|
+
| 对比维度 | **TS 装饰器派**<br>(NestJS / Inversify / TSyringe) | **正则 Proxy 派**<br>(Awilix) | **JVM 反射派**<br>(Java Spring) | **@path-ioc/core** *(及 container 扩展)* |
|
|
179
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
180
|
+
| **底层依据** | `reflect-metadata` + TS Decorator | 函数 `.toString()` 正则 + Proxy | Java 反射 + 字节码 + 缓存 (静态语言工业标杆) | **物理路径契约 + 纯闭包工厂 + DAG 图编译** |
|
|
181
|
+
| **编译/环境兼容性** | 差 (强依赖元数据,纯类型擦除转译即崩溃) | 良好 | JVM 物理机原生支持 | **极致** (纯 ES Module 函数闭包,零元数据) |
|
|
182
|
+
| **初始化装配机制** | 串行主导 / Class 构造纯同步,async Provider 阻塞 | 不支持异步初始化 | 严格单线程串行装配 (出于 JMM 线程安全考量) | **原生 DAG 拓扑并行/并发点火** (利用单线程天然安全,无锁级联调度) |
|
|
183
|
+
| **AOP 切面机制** | 概念繁杂 & 强绑定且仅限 Controller | 无内置 AOP 能力 | 划时代声明式代理 (AspectJ) | **完全 AOP 能力 & 零学习成本** (基于 DL 依赖查找与 JS 高阶代理) |
|
|
184
|
+
| **循环依赖与解环机制** | 死锁重灾区 (`forwardRef` 遇 async 死锁) | 受限 (仅限纯同步) | 辩证支持 (三级缓存解环,易诱发隐蔽 Bug) | **底层 DFS Fail-Fast 拦截** (拒绝遮蔽漏洞)<br>扩展层 Turbo 模式 (Dynamic Getter 解环) |
|
|
185
|
+
| **高并发 / 运行期性能** | 高频元数据反射损耗 | Proxy 属性访问开销 | 工业级高可靠 (受限 JVM 物理模型) | **极高** (静态图编译与填充分离,提升 80% CPU 性能) |
|
|
186
|
+
| **架构解耦与侵入性** | 强侵入 (代码处处与框架类和注解绑定) | 中度 (绑定参数名) | 低侵入 (支持 JSR-330 标准注解) | **零侵入** (模块仅为纯函数,脱离框架完全可跑) |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 形式化 API 规范 (Formal API Specification)
|
|
191
|
+
|
|
192
|
+
### 1. `compileModuleGraph`
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
export interface IOCModule {
|
|
196
|
+
main: (container: any, moduleNames: string[]) => any | Promise<any>;
|
|
197
|
+
dependencies?: string[] | ((moduleNames: string[]) => string[]);
|
|
198
|
+
order?: number;
|
|
199
|
+
skip?: boolean;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface CompiledModuleGraph {
|
|
203
|
+
sortedKeys: string[];
|
|
204
|
+
resolvedDepsMap: Record<string, string[]>;
|
|
205
|
+
shortKeyToFullKeyMap: Record<string, string>;
|
|
206
|
+
fullKeyToShortKeyMap: Record<string, string>;
|
|
207
|
+
moduleMap: Record<string, IOCModule>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function compileModuleGraph(
|
|
211
|
+
modules: { key: string; module: IOCModule }[]
|
|
212
|
+
): CompiledModuleGraph;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- **算法复杂度**:时间复杂度 $O(V + E)$,空间复杂度 $O(V + E)$(基于 Kahn 拓扑排序算法);
|
|
216
|
+
- **异常捕获**:若检测到环路依赖,立即抛出附带完整环路链路路径的错误;若检测到短名称命名冲突,抛出明确的提示。
|
|
217
|
+
|
|
218
|
+
### 2. `instantiateModuleContainer`
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
export function instantiateModuleContainer(
|
|
222
|
+
compiledGraph: CompiledModuleGraph,
|
|
223
|
+
container: Record<string, unknown>
|
|
224
|
+
): Promise<void>;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
- **行为规范**:
|
|
228
|
+
- 按 `compiledGraph.sortedKeys` 顺序依次唤醒模块 `main` 函数;
|
|
229
|
+
- 自动将模块执行返回值挂载到 `container[fullKey]` 与 `container[shortKey]`;
|
|
230
|
+
- 若模块的 `main` 为异步 Promise,引擎自动 `await` 并级联唤醒后续依赖它的子节点。
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 许可证 (License)
|
|
235
|
+
|
|
236
|
+
Released under the [MIT License](./LICENSE).
|
|
237
|
+
Copyright © 2026 [Path-IoC Organization](https://github.com/path-ioc) & Lian HanLin.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
compileModuleGraph: () => compileModuleGraph,
|
|
24
|
+
initialize: () => initialize,
|
|
25
|
+
instantiateModuleContainer: () => instantiateModuleContainer
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/graph.ts
|
|
30
|
+
var import_lodash_es = require("lodash-es");
|
|
31
|
+
var DEFAULT_ORDER = 99999;
|
|
32
|
+
var isPromise = (value) => {
|
|
33
|
+
return value !== null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
|
|
34
|
+
};
|
|
35
|
+
var compileModuleGraph = (modules) => {
|
|
36
|
+
const parsedNames = modules.map(({ key }) => {
|
|
37
|
+
const parts = key.split("/");
|
|
38
|
+
let folderName = parts.pop();
|
|
39
|
+
if (!folderName) {
|
|
40
|
+
if (parts.length > 0) {
|
|
41
|
+
folderName = parts.pop();
|
|
42
|
+
} else {
|
|
43
|
+
console.log("THROWING FOR KEY:", key, "PARTS:", parts, "FOLDERNAME:", folderName);
|
|
44
|
+
throw new Error(`Could not parse module name from key: ${key}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const shortName = folderName[0] + (0, import_lodash_es.camelCase)(folderName).slice(1);
|
|
48
|
+
const modulePath = parts.length > 0 ? parts.join("/") + "/" : key.startsWith("/") ? "/" : "";
|
|
49
|
+
const fullName = `${modulePath}${shortName}`;
|
|
50
|
+
return {
|
|
51
|
+
shortName,
|
|
52
|
+
fullName
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
const moduleDeclarationNames = parsedNames.map((p) => p.fullName);
|
|
56
|
+
const processIOCModule = (iocModule, index) => {
|
|
57
|
+
if (!iocModule || typeof iocModule.main !== "function") {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`[${parsedNames[index].fullName}] 'main' is not a function.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const deps = typeof iocModule.dependencies === "function" ? iocModule.dependencies(moduleDeclarationNames) : iocModule.dependencies || [];
|
|
63
|
+
if (!Array.isArray(deps)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`[${parsedNames[index].fullName}] 'dependencies' must be an array or a function returning an array.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
name: parsedNames[index].shortName,
|
|
70
|
+
fullName: parsedNames[index].fullName,
|
|
71
|
+
main: iocModule.main,
|
|
72
|
+
dependencies: deps,
|
|
73
|
+
order: iocModule.order ?? DEFAULT_ORDER,
|
|
74
|
+
skip: iocModule.skip,
|
|
75
|
+
rawModule: iocModule
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const allDeclarations = modules.map(({ module: mod }, i) => {
|
|
79
|
+
return processIOCModule(mod, i);
|
|
80
|
+
});
|
|
81
|
+
const fullNameToModuleMap = /* @__PURE__ */ new Map();
|
|
82
|
+
const shortNameToFullNameMap = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const mod of allDeclarations) {
|
|
84
|
+
if (fullNameToModuleMap.has(mod.fullName)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`[Configuration Error] Duplicate module fullName detected: '${mod.fullName}'.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
fullNameToModuleMap.set(mod.fullName, mod);
|
|
90
|
+
if (!shortNameToFullNameMap.has(mod.name)) {
|
|
91
|
+
shortNameToFullNameMap.set(mod.name, mod.fullName);
|
|
92
|
+
} else {
|
|
93
|
+
shortNameToFullNameMap.set(mod.name, null);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const resolvedDepsMap = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const mod of allDeclarations) {
|
|
98
|
+
const resolvedDeps = [];
|
|
99
|
+
for (const depName of mod.dependencies) {
|
|
100
|
+
let resolvedFullName;
|
|
101
|
+
if (fullNameToModuleMap.has(depName)) {
|
|
102
|
+
resolvedFullName = depName;
|
|
103
|
+
} else if (shortNameToFullNameMap.has(depName)) {
|
|
104
|
+
const found = shortNameToFullNameMap.get(depName);
|
|
105
|
+
if (found === null) {
|
|
106
|
+
const conflictingModules = allDeclarations.filter((m) => m.name === depName).map((m) => `'${m.fullName}'`).join(" and ");
|
|
107
|
+
throw new Error(
|
|
108
|
+
`[Dependency Error] Module '${mod.fullName}' has an ambiguous dependency on '${depName}'. This short name is used by ${conflictingModules}. Please disambiguate by renaming the module (e.g. 'userService', 'adminService') or refactoring domain boundaries. Avoid hardcoding full paths in business dependencies as it is an architectural anti-pattern.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
resolvedFullName = found;
|
|
112
|
+
}
|
|
113
|
+
if (!resolvedFullName) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`[Dependency Error] Module '${mod.fullName}' depends on '${depName}', which was not found.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (resolvedFullName !== mod.fullName) {
|
|
119
|
+
resolvedDeps.push(resolvedFullName);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
resolvedDepsMap.set(mod.fullName, resolvedDeps);
|
|
123
|
+
}
|
|
124
|
+
const visited = /* @__PURE__ */ new Set();
|
|
125
|
+
const stack = /* @__PURE__ */ new Set();
|
|
126
|
+
const topologicalOrder = [];
|
|
127
|
+
const detectCycle = (fullName) => {
|
|
128
|
+
if (stack.has(fullName)) {
|
|
129
|
+
const pathArray = Array.from(stack);
|
|
130
|
+
const cycleStartIndex = pathArray.indexOf(fullName);
|
|
131
|
+
const cyclePath = pathArray.slice(cycleStartIndex).concat(fullName).join(" -> ");
|
|
132
|
+
throw new Error(
|
|
133
|
+
`[Circular Dependency] A cycle was detected: ${cyclePath}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (visited.has(fullName)) return;
|
|
137
|
+
stack.add(fullName);
|
|
138
|
+
const deps = resolvedDepsMap.get(fullName) || [];
|
|
139
|
+
for (const dep of deps) {
|
|
140
|
+
detectCycle(dep);
|
|
141
|
+
}
|
|
142
|
+
stack.delete(fullName);
|
|
143
|
+
visited.add(fullName);
|
|
144
|
+
topologicalOrder.push(fullName);
|
|
145
|
+
};
|
|
146
|
+
const sortedByOrder = [...allDeclarations].sort((a, b) => a.order - b.order);
|
|
147
|
+
for (const mod of sortedByOrder) {
|
|
148
|
+
detectCycle(mod.fullName);
|
|
149
|
+
}
|
|
150
|
+
const sortedDeclarations = [...allDeclarations].sort((a, b) => {
|
|
151
|
+
return topologicalOrder.indexOf(a.fullName) - topologicalOrder.indexOf(b.fullName);
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
sortedDeclarations,
|
|
155
|
+
resolvedDepsMap,
|
|
156
|
+
fullNameToModuleMap,
|
|
157
|
+
moduleDeclarationNames,
|
|
158
|
+
shortNameToFullNameMap
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
var instantiateModuleContainer = async (compiledGraph, modularContainer) => {
|
|
162
|
+
if (!modularContainer.$logs) {
|
|
163
|
+
modularContainer.$logs = [];
|
|
164
|
+
}
|
|
165
|
+
const logs = modularContainer.$logs;
|
|
166
|
+
const {
|
|
167
|
+
sortedDeclarations,
|
|
168
|
+
resolvedDepsMap,
|
|
169
|
+
fullNameToModuleMap,
|
|
170
|
+
moduleDeclarationNames
|
|
171
|
+
} = compiledGraph;
|
|
172
|
+
logs.push("Phase 5: Reactive execution flow starting...");
|
|
173
|
+
const initPromises = /* @__PURE__ */ new Map();
|
|
174
|
+
const instantiated = /* @__PURE__ */ new Set();
|
|
175
|
+
const commitModuleResult = (mod, result, startTime) => {
|
|
176
|
+
const cost = Date.now() - startTime;
|
|
177
|
+
logs.push(`[Init] ${mod.fullName} initialized in ${cost}ms`);
|
|
178
|
+
modularContainer[mod.fullName] = result;
|
|
179
|
+
instantiated.add(mod.fullName);
|
|
180
|
+
if (compiledGraph.shortNameToFullNameMap.get(mod.name) === mod.fullName && !modularContainer.hasOwnProperty(mod.name)) {
|
|
181
|
+
modularContainer[mod.name] = result;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const resolveModule = (fullName) => {
|
|
185
|
+
if (initPromises.has(fullName)) {
|
|
186
|
+
return initPromises.get(fullName);
|
|
187
|
+
}
|
|
188
|
+
if (instantiated.has(fullName)) {
|
|
189
|
+
return modularContainer[fullName];
|
|
190
|
+
}
|
|
191
|
+
const mod = fullNameToModuleMap.get(fullName);
|
|
192
|
+
if (!mod || mod.skip) {
|
|
193
|
+
const p = Promise.resolve();
|
|
194
|
+
initPromises.set(fullName, p);
|
|
195
|
+
return p;
|
|
196
|
+
}
|
|
197
|
+
const deps = resolvedDepsMap.get(fullName) || [];
|
|
198
|
+
const allDepsReadySync = deps.every((dep) => {
|
|
199
|
+
const depMod = fullNameToModuleMap.get(dep);
|
|
200
|
+
if (!depMod) return true;
|
|
201
|
+
if (depMod.skip) return true;
|
|
202
|
+
return instantiated.has(dep) || modularContainer.hasOwnProperty(dep) && !fullNameToModuleMap.has(dep);
|
|
203
|
+
});
|
|
204
|
+
if (allDepsReadySync) {
|
|
205
|
+
const startTime = Date.now();
|
|
206
|
+
const result = mod.main(modularContainer, moduleDeclarationNames);
|
|
207
|
+
if (isPromise(result)) {
|
|
208
|
+
const promise2 = (async () => {
|
|
209
|
+
const finalResult = await result;
|
|
210
|
+
commitModuleResult(mod, finalResult, startTime);
|
|
211
|
+
return finalResult;
|
|
212
|
+
})();
|
|
213
|
+
initPromises.set(fullName, promise2);
|
|
214
|
+
return promise2;
|
|
215
|
+
} else {
|
|
216
|
+
commitModuleResult(mod, result, startTime);
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const promise = (async () => {
|
|
221
|
+
const sortedDeps = [...deps].sort((a, b) => {
|
|
222
|
+
const modA = fullNameToModuleMap.get(a);
|
|
223
|
+
const modB = fullNameToModuleMap.get(b);
|
|
224
|
+
return (modA?.order ?? DEFAULT_ORDER) - (modB?.order ?? DEFAULT_ORDER);
|
|
225
|
+
});
|
|
226
|
+
const depPromises = sortedDeps.map((dep) => resolveModule(dep));
|
|
227
|
+
await Promise.all(depPromises);
|
|
228
|
+
const startTime = Date.now();
|
|
229
|
+
const result = await mod.main(modularContainer, moduleDeclarationNames);
|
|
230
|
+
commitModuleResult(mod, result, startTime);
|
|
231
|
+
return result;
|
|
232
|
+
})();
|
|
233
|
+
initPromises.set(fullName, promise);
|
|
234
|
+
return promise;
|
|
235
|
+
};
|
|
236
|
+
const resultsOrPromises = sortedDeclarations.map(
|
|
237
|
+
(m) => resolveModule(m.fullName)
|
|
238
|
+
);
|
|
239
|
+
const hasPromise = resultsOrPromises.some(isPromise);
|
|
240
|
+
if (hasPromise) {
|
|
241
|
+
await Promise.all(resultsOrPromises);
|
|
242
|
+
}
|
|
243
|
+
logs.push("Initialization complete.");
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/modular.ts
|
|
247
|
+
var initialize = async (modules, modularContainer) => {
|
|
248
|
+
const compiledGraph = compileModuleGraph(modules);
|
|
249
|
+
await instantiateModuleContainer(compiledGraph, modularContainer);
|
|
250
|
+
};
|
|
251
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
252
|
+
0 && (module.exports = {
|
|
253
|
+
compileModuleGraph,
|
|
254
|
+
initialize,
|
|
255
|
+
instantiateModuleContainer
|
|
256
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type InternalModularContainer = Record<string, unknown> & {
|
|
2
|
+
$logs?: string[];
|
|
3
|
+
};
|
|
4
|
+
interface IOCModule {
|
|
5
|
+
main: (modularContainer: InternalModularContainer, moduleDeclarationNames: string[]) => Promise<unknown> | unknown;
|
|
6
|
+
dependencies?: string[] | ((moduleDeclarationNames: string[]) => string[]);
|
|
7
|
+
order?: number;
|
|
8
|
+
skip?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface ModuleDeclaration {
|
|
11
|
+
name: string;
|
|
12
|
+
fullName: string;
|
|
13
|
+
main: (modularContainer: InternalModularContainer, moduleDeclarationNames: string[]) => Promise<unknown> | unknown;
|
|
14
|
+
dependencies: string[];
|
|
15
|
+
order: number;
|
|
16
|
+
skip?: boolean;
|
|
17
|
+
rawModule: IOCModule;
|
|
18
|
+
}
|
|
19
|
+
interface CompiledModuleGraph {
|
|
20
|
+
sortedDeclarations: ModuleDeclaration[];
|
|
21
|
+
resolvedDepsMap: Map<string, string[]>;
|
|
22
|
+
fullNameToModuleMap: Map<string, ModuleDeclaration>;
|
|
23
|
+
moduleDeclarationNames: string[];
|
|
24
|
+
shortNameToFullNameMap: Map<string, string | null>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 编译模块依赖图 (纯同步执行)。
|
|
28
|
+
* 提取全名/短名映射,解析并映射依赖关系,运行 DFS 循环依赖检测并生成拓扑排序。
|
|
29
|
+
*
|
|
30
|
+
* @param modules - 模块加载器列表,每个包含 key 和 getter
|
|
31
|
+
* @returns 编译后的静态模块依赖图
|
|
32
|
+
*/
|
|
33
|
+
declare const compileModuleGraph: (modules: {
|
|
34
|
+
key: string;
|
|
35
|
+
module: IOCModule;
|
|
36
|
+
}[]) => CompiledModuleGraph;
|
|
37
|
+
/**
|
|
38
|
+
* 基于已编译的依赖图,动态实例化模块容器。
|
|
39
|
+
*
|
|
40
|
+
* @param compiledGraph - 编译后的静态依赖图描述
|
|
41
|
+
* @param modularContainer - 需要填充实例的容器对象
|
|
42
|
+
*/
|
|
43
|
+
declare const instantiateModuleContainer: (compiledGraph: CompiledModuleGraph, modularContainer: Record<string, unknown> & {
|
|
44
|
+
$logs?: string[];
|
|
45
|
+
}) => Promise<void>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 无锁反应式模块化初始化函数 (兼容包壳)。
|
|
49
|
+
* 底层逻辑已拆分为 compileModuleGraph (静态图纯同步编译) 与 instantiateModuleContainer (动态实例填充)。
|
|
50
|
+
*
|
|
51
|
+
* @param modules - 模块加载器列表,每个包含 key 和 getter
|
|
52
|
+
* @param modularContainer - 依赖查找与拓扑解构容器 (ModularContainer)
|
|
53
|
+
*/
|
|
54
|
+
declare const initialize: (modules: {
|
|
55
|
+
key: string;
|
|
56
|
+
module: IOCModule;
|
|
57
|
+
}[], modularContainer: Record<string, unknown> & {
|
|
58
|
+
$logs?: string[];
|
|
59
|
+
}) => Promise<void>;
|
|
60
|
+
|
|
61
|
+
export { type CompiledModuleGraph, type IOCModule, type ModuleDeclaration, compileModuleGraph, initialize, instantiateModuleContainer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type InternalModularContainer = Record<string, unknown> & {
|
|
2
|
+
$logs?: string[];
|
|
3
|
+
};
|
|
4
|
+
interface IOCModule {
|
|
5
|
+
main: (modularContainer: InternalModularContainer, moduleDeclarationNames: string[]) => Promise<unknown> | unknown;
|
|
6
|
+
dependencies?: string[] | ((moduleDeclarationNames: string[]) => string[]);
|
|
7
|
+
order?: number;
|
|
8
|
+
skip?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface ModuleDeclaration {
|
|
11
|
+
name: string;
|
|
12
|
+
fullName: string;
|
|
13
|
+
main: (modularContainer: InternalModularContainer, moduleDeclarationNames: string[]) => Promise<unknown> | unknown;
|
|
14
|
+
dependencies: string[];
|
|
15
|
+
order: number;
|
|
16
|
+
skip?: boolean;
|
|
17
|
+
rawModule: IOCModule;
|
|
18
|
+
}
|
|
19
|
+
interface CompiledModuleGraph {
|
|
20
|
+
sortedDeclarations: ModuleDeclaration[];
|
|
21
|
+
resolvedDepsMap: Map<string, string[]>;
|
|
22
|
+
fullNameToModuleMap: Map<string, ModuleDeclaration>;
|
|
23
|
+
moduleDeclarationNames: string[];
|
|
24
|
+
shortNameToFullNameMap: Map<string, string | null>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 编译模块依赖图 (纯同步执行)。
|
|
28
|
+
* 提取全名/短名映射,解析并映射依赖关系,运行 DFS 循环依赖检测并生成拓扑排序。
|
|
29
|
+
*
|
|
30
|
+
* @param modules - 模块加载器列表,每个包含 key 和 getter
|
|
31
|
+
* @returns 编译后的静态模块依赖图
|
|
32
|
+
*/
|
|
33
|
+
declare const compileModuleGraph: (modules: {
|
|
34
|
+
key: string;
|
|
35
|
+
module: IOCModule;
|
|
36
|
+
}[]) => CompiledModuleGraph;
|
|
37
|
+
/**
|
|
38
|
+
* 基于已编译的依赖图,动态实例化模块容器。
|
|
39
|
+
*
|
|
40
|
+
* @param compiledGraph - 编译后的静态依赖图描述
|
|
41
|
+
* @param modularContainer - 需要填充实例的容器对象
|
|
42
|
+
*/
|
|
43
|
+
declare const instantiateModuleContainer: (compiledGraph: CompiledModuleGraph, modularContainer: Record<string, unknown> & {
|
|
44
|
+
$logs?: string[];
|
|
45
|
+
}) => Promise<void>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 无锁反应式模块化初始化函数 (兼容包壳)。
|
|
49
|
+
* 底层逻辑已拆分为 compileModuleGraph (静态图纯同步编译) 与 instantiateModuleContainer (动态实例填充)。
|
|
50
|
+
*
|
|
51
|
+
* @param modules - 模块加载器列表,每个包含 key 和 getter
|
|
52
|
+
* @param modularContainer - 依赖查找与拓扑解构容器 (ModularContainer)
|
|
53
|
+
*/
|
|
54
|
+
declare const initialize: (modules: {
|
|
55
|
+
key: string;
|
|
56
|
+
module: IOCModule;
|
|
57
|
+
}[], modularContainer: Record<string, unknown> & {
|
|
58
|
+
$logs?: string[];
|
|
59
|
+
}) => Promise<void>;
|
|
60
|
+
|
|
61
|
+
export { type CompiledModuleGraph, type IOCModule, type ModuleDeclaration, compileModuleGraph, initialize, instantiateModuleContainer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/graph.ts
|
|
2
|
+
import { camelCase } from "lodash-es";
|
|
3
|
+
var DEFAULT_ORDER = 99999;
|
|
4
|
+
var isPromise = (value) => {
|
|
5
|
+
return value !== null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
|
|
6
|
+
};
|
|
7
|
+
var compileModuleGraph = (modules) => {
|
|
8
|
+
const parsedNames = modules.map(({ key }) => {
|
|
9
|
+
const parts = key.split("/");
|
|
10
|
+
let folderName = parts.pop();
|
|
11
|
+
if (!folderName) {
|
|
12
|
+
if (parts.length > 0) {
|
|
13
|
+
folderName = parts.pop();
|
|
14
|
+
} else {
|
|
15
|
+
console.log("THROWING FOR KEY:", key, "PARTS:", parts, "FOLDERNAME:", folderName);
|
|
16
|
+
throw new Error(`Could not parse module name from key: ${key}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const shortName = folderName[0] + camelCase(folderName).slice(1);
|
|
20
|
+
const modulePath = parts.length > 0 ? parts.join("/") + "/" : key.startsWith("/") ? "/" : "";
|
|
21
|
+
const fullName = `${modulePath}${shortName}`;
|
|
22
|
+
return {
|
|
23
|
+
shortName,
|
|
24
|
+
fullName
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
const moduleDeclarationNames = parsedNames.map((p) => p.fullName);
|
|
28
|
+
const processIOCModule = (iocModule, index) => {
|
|
29
|
+
if (!iocModule || typeof iocModule.main !== "function") {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`[${parsedNames[index].fullName}] 'main' is not a function.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const deps = typeof iocModule.dependencies === "function" ? iocModule.dependencies(moduleDeclarationNames) : iocModule.dependencies || [];
|
|
35
|
+
if (!Array.isArray(deps)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`[${parsedNames[index].fullName}] 'dependencies' must be an array or a function returning an array.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
name: parsedNames[index].shortName,
|
|
42
|
+
fullName: parsedNames[index].fullName,
|
|
43
|
+
main: iocModule.main,
|
|
44
|
+
dependencies: deps,
|
|
45
|
+
order: iocModule.order ?? DEFAULT_ORDER,
|
|
46
|
+
skip: iocModule.skip,
|
|
47
|
+
rawModule: iocModule
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const allDeclarations = modules.map(({ module: mod }, i) => {
|
|
51
|
+
return processIOCModule(mod, i);
|
|
52
|
+
});
|
|
53
|
+
const fullNameToModuleMap = /* @__PURE__ */ new Map();
|
|
54
|
+
const shortNameToFullNameMap = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const mod of allDeclarations) {
|
|
56
|
+
if (fullNameToModuleMap.has(mod.fullName)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`[Configuration Error] Duplicate module fullName detected: '${mod.fullName}'.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
fullNameToModuleMap.set(mod.fullName, mod);
|
|
62
|
+
if (!shortNameToFullNameMap.has(mod.name)) {
|
|
63
|
+
shortNameToFullNameMap.set(mod.name, mod.fullName);
|
|
64
|
+
} else {
|
|
65
|
+
shortNameToFullNameMap.set(mod.name, null);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const resolvedDepsMap = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const mod of allDeclarations) {
|
|
70
|
+
const resolvedDeps = [];
|
|
71
|
+
for (const depName of mod.dependencies) {
|
|
72
|
+
let resolvedFullName;
|
|
73
|
+
if (fullNameToModuleMap.has(depName)) {
|
|
74
|
+
resolvedFullName = depName;
|
|
75
|
+
} else if (shortNameToFullNameMap.has(depName)) {
|
|
76
|
+
const found = shortNameToFullNameMap.get(depName);
|
|
77
|
+
if (found === null) {
|
|
78
|
+
const conflictingModules = allDeclarations.filter((m) => m.name === depName).map((m) => `'${m.fullName}'`).join(" and ");
|
|
79
|
+
throw new Error(
|
|
80
|
+
`[Dependency Error] Module '${mod.fullName}' has an ambiguous dependency on '${depName}'. This short name is used by ${conflictingModules}. Please disambiguate by renaming the module (e.g. 'userService', 'adminService') or refactoring domain boundaries. Avoid hardcoding full paths in business dependencies as it is an architectural anti-pattern.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
resolvedFullName = found;
|
|
84
|
+
}
|
|
85
|
+
if (!resolvedFullName) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`[Dependency Error] Module '${mod.fullName}' depends on '${depName}', which was not found.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (resolvedFullName !== mod.fullName) {
|
|
91
|
+
resolvedDeps.push(resolvedFullName);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
resolvedDepsMap.set(mod.fullName, resolvedDeps);
|
|
95
|
+
}
|
|
96
|
+
const visited = /* @__PURE__ */ new Set();
|
|
97
|
+
const stack = /* @__PURE__ */ new Set();
|
|
98
|
+
const topologicalOrder = [];
|
|
99
|
+
const detectCycle = (fullName) => {
|
|
100
|
+
if (stack.has(fullName)) {
|
|
101
|
+
const pathArray = Array.from(stack);
|
|
102
|
+
const cycleStartIndex = pathArray.indexOf(fullName);
|
|
103
|
+
const cyclePath = pathArray.slice(cycleStartIndex).concat(fullName).join(" -> ");
|
|
104
|
+
throw new Error(
|
|
105
|
+
`[Circular Dependency] A cycle was detected: ${cyclePath}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (visited.has(fullName)) return;
|
|
109
|
+
stack.add(fullName);
|
|
110
|
+
const deps = resolvedDepsMap.get(fullName) || [];
|
|
111
|
+
for (const dep of deps) {
|
|
112
|
+
detectCycle(dep);
|
|
113
|
+
}
|
|
114
|
+
stack.delete(fullName);
|
|
115
|
+
visited.add(fullName);
|
|
116
|
+
topologicalOrder.push(fullName);
|
|
117
|
+
};
|
|
118
|
+
const sortedByOrder = [...allDeclarations].sort((a, b) => a.order - b.order);
|
|
119
|
+
for (const mod of sortedByOrder) {
|
|
120
|
+
detectCycle(mod.fullName);
|
|
121
|
+
}
|
|
122
|
+
const sortedDeclarations = [...allDeclarations].sort((a, b) => {
|
|
123
|
+
return topologicalOrder.indexOf(a.fullName) - topologicalOrder.indexOf(b.fullName);
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
sortedDeclarations,
|
|
127
|
+
resolvedDepsMap,
|
|
128
|
+
fullNameToModuleMap,
|
|
129
|
+
moduleDeclarationNames,
|
|
130
|
+
shortNameToFullNameMap
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
var instantiateModuleContainer = async (compiledGraph, modularContainer) => {
|
|
134
|
+
if (!modularContainer.$logs) {
|
|
135
|
+
modularContainer.$logs = [];
|
|
136
|
+
}
|
|
137
|
+
const logs = modularContainer.$logs;
|
|
138
|
+
const {
|
|
139
|
+
sortedDeclarations,
|
|
140
|
+
resolvedDepsMap,
|
|
141
|
+
fullNameToModuleMap,
|
|
142
|
+
moduleDeclarationNames
|
|
143
|
+
} = compiledGraph;
|
|
144
|
+
logs.push("Phase 5: Reactive execution flow starting...");
|
|
145
|
+
const initPromises = /* @__PURE__ */ new Map();
|
|
146
|
+
const instantiated = /* @__PURE__ */ new Set();
|
|
147
|
+
const commitModuleResult = (mod, result, startTime) => {
|
|
148
|
+
const cost = Date.now() - startTime;
|
|
149
|
+
logs.push(`[Init] ${mod.fullName} initialized in ${cost}ms`);
|
|
150
|
+
modularContainer[mod.fullName] = result;
|
|
151
|
+
instantiated.add(mod.fullName);
|
|
152
|
+
if (compiledGraph.shortNameToFullNameMap.get(mod.name) === mod.fullName && !modularContainer.hasOwnProperty(mod.name)) {
|
|
153
|
+
modularContainer[mod.name] = result;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const resolveModule = (fullName) => {
|
|
157
|
+
if (initPromises.has(fullName)) {
|
|
158
|
+
return initPromises.get(fullName);
|
|
159
|
+
}
|
|
160
|
+
if (instantiated.has(fullName)) {
|
|
161
|
+
return modularContainer[fullName];
|
|
162
|
+
}
|
|
163
|
+
const mod = fullNameToModuleMap.get(fullName);
|
|
164
|
+
if (!mod || mod.skip) {
|
|
165
|
+
const p = Promise.resolve();
|
|
166
|
+
initPromises.set(fullName, p);
|
|
167
|
+
return p;
|
|
168
|
+
}
|
|
169
|
+
const deps = resolvedDepsMap.get(fullName) || [];
|
|
170
|
+
const allDepsReadySync = deps.every((dep) => {
|
|
171
|
+
const depMod = fullNameToModuleMap.get(dep);
|
|
172
|
+
if (!depMod) return true;
|
|
173
|
+
if (depMod.skip) return true;
|
|
174
|
+
return instantiated.has(dep) || modularContainer.hasOwnProperty(dep) && !fullNameToModuleMap.has(dep);
|
|
175
|
+
});
|
|
176
|
+
if (allDepsReadySync) {
|
|
177
|
+
const startTime = Date.now();
|
|
178
|
+
const result = mod.main(modularContainer, moduleDeclarationNames);
|
|
179
|
+
if (isPromise(result)) {
|
|
180
|
+
const promise2 = (async () => {
|
|
181
|
+
const finalResult = await result;
|
|
182
|
+
commitModuleResult(mod, finalResult, startTime);
|
|
183
|
+
return finalResult;
|
|
184
|
+
})();
|
|
185
|
+
initPromises.set(fullName, promise2);
|
|
186
|
+
return promise2;
|
|
187
|
+
} else {
|
|
188
|
+
commitModuleResult(mod, result, startTime);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const promise = (async () => {
|
|
193
|
+
const sortedDeps = [...deps].sort((a, b) => {
|
|
194
|
+
const modA = fullNameToModuleMap.get(a);
|
|
195
|
+
const modB = fullNameToModuleMap.get(b);
|
|
196
|
+
return (modA?.order ?? DEFAULT_ORDER) - (modB?.order ?? DEFAULT_ORDER);
|
|
197
|
+
});
|
|
198
|
+
const depPromises = sortedDeps.map((dep) => resolveModule(dep));
|
|
199
|
+
await Promise.all(depPromises);
|
|
200
|
+
const startTime = Date.now();
|
|
201
|
+
const result = await mod.main(modularContainer, moduleDeclarationNames);
|
|
202
|
+
commitModuleResult(mod, result, startTime);
|
|
203
|
+
return result;
|
|
204
|
+
})();
|
|
205
|
+
initPromises.set(fullName, promise);
|
|
206
|
+
return promise;
|
|
207
|
+
};
|
|
208
|
+
const resultsOrPromises = sortedDeclarations.map(
|
|
209
|
+
(m) => resolveModule(m.fullName)
|
|
210
|
+
);
|
|
211
|
+
const hasPromise = resultsOrPromises.some(isPromise);
|
|
212
|
+
if (hasPromise) {
|
|
213
|
+
await Promise.all(resultsOrPromises);
|
|
214
|
+
}
|
|
215
|
+
logs.push("Initialization complete.");
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/modular.ts
|
|
219
|
+
var initialize = async (modules, modularContainer) => {
|
|
220
|
+
const compiledGraph = compileModuleGraph(modules);
|
|
221
|
+
await instantiateModuleContainer(compiledGraph, modularContainer);
|
|
222
|
+
};
|
|
223
|
+
export {
|
|
224
|
+
compileModuleGraph,
|
|
225
|
+
initialize,
|
|
226
|
+
instantiateModuleContainer
|
|
227
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@path-ioc/core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Pure topological dependency resolution engine & zero-reflection IoC runtime",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ioc",
|
|
7
|
+
"dependency-injection",
|
|
8
|
+
"dag",
|
|
9
|
+
"topological-sort",
|
|
10
|
+
"reactive",
|
|
11
|
+
"typescript",
|
|
12
|
+
"cloudflare-workers",
|
|
13
|
+
"hono",
|
|
14
|
+
"serverless"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://path-ioc.dev",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/path-ioc/path-ioc.git",
|
|
20
|
+
"directory": "packages/core"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/path-ioc/path-ioc/issues"
|
|
24
|
+
},
|
|
25
|
+
"author": "Lian HanLin",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"main": "dist/index.cjs",
|
|
28
|
+
"module": "dist/index.js",
|
|
29
|
+
"types": "dist/index.d.ts",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"require": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"lodash-es": "^4.17.21"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/lodash-es": "^4.17.12",
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"typescript": "^5.4.5"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
54
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
|
|
55
|
+
}
|
|
56
|
+
}
|