mm_machine 2.9.3 → 2.9.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.
Files changed (8) hide show
  1. package/dir_scanner.js +262 -0
  2. package/drive.js +668 -490
  3. package/id_gen.js +138 -0
  4. package/index.js +1141 -921
  5. package/mod.js +742 -711
  6. package/package.json +6 -2
  7. package/registry.js +634 -0
  8. package/word.js +209 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mm_machine",
3
- "version": "2.9.3",
3
+ "version": "2.9.5",
4
4
  "description": "A flexible Node.js plugin mechanism system for dynamic loading, management and execution of modules. Supports hot reload, lifecycle management, and modern JavaScript features.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -30,13 +30,17 @@
30
30
  "mod.js",
31
31
  "index.js",
32
32
  "drive.js",
33
+ "dir_scanner.js",
34
+ "id_gen.js",
35
+ "registry.js",
36
+ "word.js",
33
37
  "README.md"
34
38
  ],
35
39
  "dependencies": {
36
40
  "@prettier/sync": "^0.6.1",
37
41
  "mm_config": "^2.4.5",
38
42
  "mm_hot_reload": "^1.4.2",
39
- "mm_tpl": "^2.6.3"
43
+ "mm_tpl": "^2.6.4"
40
44
  },
41
45
  "devDependencies": {
42
46
  "eslint": "^10.7.0",
package/registry.js ADDED
@@ -0,0 +1,634 @@
1
+ /**
2
+ * Registry 注册表类
3
+ * 持久化存储所有模块的元数据,启动时一次 IO 读取即可恢复全部模块信息
4
+ * @class Registry
5
+ * @augments Drive
6
+ */
7
+ var fs = require('fs');
8
+ var path = require('path');
9
+ var { Drive } = require('./drive');
10
+
11
+ /**
12
+ * 新增条目自动补全的默认值
13
+ */
14
+ var defaultEntry = {
15
+ title: '',
16
+ description: '',
17
+ type: 'plugin',
18
+ version: '1.0.0',
19
+ sort: 100,
20
+ main: '',
21
+ enabled: true,
22
+ auto_start: false,
23
+ lazy: 'auto',
24
+ state: 'stopped',
25
+ // eslint-disable-next-line mm_eslint/variable-name
26
+ registered_at: '',
27
+ updated_at: ''
28
+ };
29
+
30
+ class Registry extends Drive {
31
+ /**
32
+ * 构造函数
33
+ * @param {object} manager Manager 实例(必需),用于注册模块
34
+ * @param {object} [cfg] 配置参数
35
+ * @param {string} [cfg.file] 注册表文件绝对路径,默认从 Manager 配置拼接
36
+ */
37
+ constructor(manager, cfg) {
38
+ var config = cfg || {};
39
+ super(config);
40
+ this._manager = manager;
41
+ this._data = { version: '1.0', updated_at: '', modules: [] };
42
+ this._file = config.file || Registry._resolveFile(manager);
43
+ this._save_timer = null;
44
+ this._dirty = false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 解析注册表文件路径
50
+ * @param {object} manager Manager 实例
51
+ * @returns {string} 注册表文件绝对路径
52
+ * @private
53
+ * @static
54
+ */
55
+ Registry._resolveFile = function (manager) {
56
+ // 有显式配置时直接使用
57
+ if (manager.config.registry_file) {
58
+ return path.resolve(manager.dir || process.cwd(), manager.config.registry_file);
59
+ }
60
+ // 默认:data/register/{构造函数名}_{config.name}.json
61
+ var base_dir = path.resolve(manager.dir || process.cwd(), 'data/register');
62
+ var class_name = manager.constructor.name || 'Manager';
63
+ var mod_name = manager.config.name || 'default';
64
+ return path.join(base_dir, class_name + '_' + mod_name + '.json');
65
+ };
66
+
67
+ // ==================== 数据加载/保存 ====================
68
+
69
+ /**
70
+ * 从文件加载注册表数据
71
+ * 使用 require() 加载(支持缓存),文件不存在返回空数据结构
72
+ * @returns {object} 注册表数据 { version, modules[] }
73
+ */
74
+ Registry.prototype.load = function () {
75
+ try {
76
+ if (fs.existsSync(this._file)) {
77
+ var data = require(this._file);
78
+ if (data && data.modules) {
79
+ this._data = data;
80
+ return this._data;
81
+ }
82
+ }
83
+ } catch {
84
+ // 文件不存在或解析失败,使用空数据
85
+ }
86
+ this._data = { version: '1.0', updated_at: '', modules: [] };
87
+ return this._data;
88
+ };
89
+
90
+ /**
91
+ * 保存注册表数据到文件
92
+ * 防抖保存(500ms 内多次调用只写一次),写完后清除 require.cache
93
+ */
94
+ Registry.prototype.save = function () {
95
+ this._dirty = true;
96
+
97
+ if (this._save_timer) {
98
+ return;
99
+ }
100
+
101
+ this._save_timer = setTimeout(() => {
102
+ this._save_timer = null;
103
+ this._flushSave();
104
+ }, 500);
105
+ };
106
+
107
+ /**
108
+ * 立即写入文件
109
+ * @private
110
+ */
111
+ Registry.prototype._flushSave = function () {
112
+ if (!this._dirty) {
113
+ return;
114
+ }
115
+
116
+ this._data.updated_at = new Date().toISOString();
117
+
118
+ try {
119
+ var dir = path.dirname(this._file);
120
+ if (!fs.existsSync(dir)) {
121
+ fs.mkdirSync(dir, { recursive: true });
122
+ }
123
+ fs.writeFileSync(this._file, JSON.stringify(this._data, null, 2), 'utf8');
124
+
125
+ // 清除 require.cache 确保下次 load 读取最新数据
126
+ try {
127
+ delete require.cache[require.resolve(this._file)];
128
+ } catch {
129
+ // 忽略
130
+ }
131
+
132
+ this._dirty = false;
133
+ } catch {
134
+ // 保存失败,记录错误
135
+ }
136
+ };
137
+
138
+ // ==================== 查询方法 ====================
139
+
140
+ /**
141
+ * 获取所有注册表项的浅拷贝数组
142
+ * @returns {Array<object>} 注册表项数组
143
+ */
144
+ Registry.prototype.getAllEntries = function () {
145
+ return this._data.modules.slice();
146
+ };
147
+
148
+ /**
149
+ * 按名称获取单个注册表项
150
+ * @param {string} name 模块名称
151
+ * @returns {object|null} 注册表项,不存在返回 null
152
+ */
153
+ Registry.prototype.getEntry = function (name) {
154
+ var modules = this._data.modules;
155
+ for (var i = 0; i < modules.length; i++) {
156
+ if (modules[i].name === name) {
157
+ return modules[i];
158
+ }
159
+ }
160
+ return null;
161
+ };
162
+
163
+ /**
164
+ * 获取注册表项数量
165
+ * @returns {number} 数量
166
+ */
167
+ Registry.prototype.getEntryCount = function () {
168
+ return this._data.modules.length;
169
+ };
170
+
171
+ /**
172
+ * 检查注册表项是否存在
173
+ * @param {string} name 模块名称
174
+ * @returns {boolean} 是否存在
175
+ */
176
+ Registry.prototype.hasEntry = function (name) {
177
+ return this.getEntry(name) !== null;
178
+ };
179
+
180
+ // ==================== 增删改方法 ====================
181
+
182
+ /**
183
+ * 添加注册表项
184
+ * 自动补全缺失字段,然后保存
185
+ * @param {object} data 注册表项数据
186
+ */
187
+ Registry.prototype.addEntry = function (data) {
188
+ if (!data || !data.name) {
189
+ throw new TypeError('注册表项必须包含 name 字段');
190
+ }
191
+
192
+ if (this.hasEntry(data.name)) {
193
+ return;
194
+ }
195
+
196
+ var now = new Date().toISOString();
197
+ var entry = {
198
+ ...defaultEntry,
199
+ ...data,
200
+ // eslint-disable-next-line mm_eslint/variable-name
201
+ registered_at: data.registered_at || now,
202
+ updated_at: now
203
+ };
204
+
205
+ this._data.modules.push(entry);
206
+ this.save();
207
+ };
208
+
209
+ /**
210
+ * 更新注册表项
211
+ * 部分合并,只覆盖 data 中存在的字段
212
+ * @param {string} name 模块名称
213
+ * @param {object} data 要更新的字段
214
+ */
215
+ Registry.prototype.updateEntry = function (name, data) {
216
+ if (!data) {
217
+ return;
218
+ }
219
+
220
+ var modules = this._data.modules;
221
+ for (var i = 0; i < modules.length; i++) {
222
+ if (modules[i].name === name) {
223
+ for (var k in data) {
224
+ modules[i][k] = data[k];
225
+ }
226
+ modules[i].updated_at = new Date().toISOString();
227
+ this.save();
228
+ return;
229
+ }
230
+ }
231
+ };
232
+
233
+ /**
234
+ * 移除注册表项并保存
235
+ * @param {string} name 模块名称
236
+ */
237
+ Registry.prototype.removeEntry = function (name) {
238
+ var modules = this._data.modules;
239
+ for (var i = 0; i < modules.length; i++) {
240
+ if (modules[i].name === name) {
241
+ modules.splice(i, 1);
242
+ this.save();
243
+ return;
244
+ }
245
+ }
246
+ };
247
+
248
+ // ==================== 自启动管理 ====================
249
+
250
+ /**
251
+ * 设置模块是否自启动
252
+ * @param {string} name 模块名称
253
+ * @param {boolean} flag 是否自启动
254
+ */
255
+ Registry.prototype.setAutoStart = function (name, flag) {
256
+ this.updateEntry(name, { auto_start: !!flag });
257
+ };
258
+
259
+ /**
260
+ * 获取所有自启动模块列表
261
+ * @returns {Array<object>} 自启动模块列表
262
+ */
263
+ Registry.prototype.getAutoStartList = function () {
264
+ var modules = this._data.modules;
265
+ var list = [];
266
+ for (var i = 0; i < modules.length; i++) {
267
+ if (modules[i].auto_start) {
268
+ list.push(modules[i]);
269
+ }
270
+ }
271
+ return list;
272
+ };
273
+
274
+ // ==================== 核心操作 ====================
275
+
276
+ /**
277
+ * 同步注册表数据到 Manager
278
+ * 遍历 _data.modules,从注册表数据直接创建模块实例(0 IO)
279
+ * 已存在的模块跳过(不重复创建)
280
+ */
281
+ Registry.prototype.syncToManager = function () {
282
+ var modules = this._data.modules;
283
+ for (var i = 0; i < modules.length; i++) {
284
+ var entry = modules[i];
285
+
286
+ // 跳过 config_file 不存在的模块
287
+ if (entry.config_file && !fs.existsSync(entry.config_file)) {
288
+ continue;
289
+ }
290
+
291
+ if (this._manager && typeof this._manager.registerModEx === 'function') {
292
+ this._manager.registerModEx(entry);
293
+ }
294
+ }
295
+ };
296
+
297
+ /**
298
+ * 启动自启动模块
299
+ * 遍历 getAutoStartList(),调用 mod.load() 加载,
300
+ * 根据 lazy 策略决定是否继续 init/start
301
+ * @returns {Promise<void>}
302
+ */
303
+ Registry.prototype.startAutoStart = async function () {
304
+ var list = this.getAutoStartList();
305
+
306
+ for (var i = 0; i < list.length; i++) {
307
+ var entry = list[i];
308
+ var mod = this._manager ? this._manager.getMod(entry.name) : null;
309
+ if (!mod) {
310
+ continue;
311
+ }
312
+
313
+ try {
314
+ // 加载模块
315
+ await mod.load();
316
+
317
+ // 根据 lazy 策略决定是否继续
318
+ if (this._shouldContinue(entry)) {
319
+ await mod.init();
320
+ await mod.start();
321
+ }
322
+ } catch {
323
+ // 单个模块启动失败不影响其他模块
324
+ }
325
+ }
326
+ };
327
+
328
+ /**
329
+ * 判断是否继续推进生命周期(init + start)
330
+ * @param {object} entry 注册表项
331
+ * @returns {boolean} 是否继续
332
+ * @private
333
+ */
334
+ Registry.prototype._shouldContinue = function (entry) {
335
+ var lazy = entry.lazy !== undefined ? entry.lazy : 'auto';
336
+
337
+ if (lazy === false) {
338
+ return true;
339
+ }
340
+ if (lazy === 'auto') {
341
+ var mgr_lazy = this._manager ? this._manager.config.lazy_load : false;
342
+ return mgr_lazy === false || mgr_lazy === 'auto';
343
+ }
344
+ return false;
345
+ };
346
+
347
+ /**
348
+ * 启动引导
349
+ * load() → syncToManager() → 注册表为空时扫描 → startAutoStart() → save()
350
+ * @returns {Promise<void>}
351
+ */
352
+ Registry.prototype.boot = async function () {
353
+ // 1. 加载注册表文件
354
+ this.load();
355
+
356
+ // 2. 注册模块到 Manager
357
+ this.syncToManager();
358
+
359
+ // 3. 注册表为空时扫描文件系统(首次启动)
360
+ // 实际扫描由 Manager.boot() 在调用 registry.boot() 后处理
361
+
362
+ // 4. 启动自启动模块
363
+ await this.startAutoStart();
364
+
365
+ // 5. 保存注册表
366
+ this.save();
367
+ };
368
+
369
+ /**
370
+ * 一键注册多个模块
371
+ * @param {Array<string|object>} files 文件路径数组或 {file, config} 对象数组
372
+ * @returns {number} 成功注册的模块数量
373
+ */
374
+ Registry.prototype.registerAll = function (files) {
375
+ if (!files || !Array.isArray(files)) {
376
+ return 0;
377
+ }
378
+
379
+ var count = 0;
380
+
381
+ for (var i = 0; i < files.length; i++) {
382
+ if (this._registerOne(files[i])) {
383
+ count++;
384
+ }
385
+ }
386
+
387
+ return count;
388
+ };
389
+
390
+ /**
391
+ * 注册单个文件条目
392
+ * @param {string|object} item 文件路径或 {file, config} 对象
393
+ * @returns {boolean} 是否注册成功
394
+ * @private
395
+ */
396
+ // eslint-disable-next-line complexity, max-lines-per-function
397
+ Registry.prototype._registerOne = function (item) {
398
+ var file_path = '';
399
+ var cfg = null;
400
+
401
+ if (typeof item === 'string') {
402
+ file_path = item;
403
+ } else if (item && item.file) {
404
+ file_path = item.file;
405
+ cfg = item.config || null;
406
+ } else {
407
+ return false;
408
+ }
409
+
410
+ try {
411
+ // 未提供配置时从文件读取
412
+ if (!cfg) {
413
+ var content = fs.readFileSync(file_path, 'utf8');
414
+ cfg = JSON.parse(content);
415
+ }
416
+
417
+ if (!cfg || !cfg.name) {
418
+ return false;
419
+ }
420
+
421
+ var entry = {
422
+ name: cfg.name,
423
+ title: cfg.title || '',
424
+ description: cfg.description || '',
425
+ type: cfg.type || 'plugin',
426
+ version: cfg.version || '1.0.0',
427
+ sort: cfg.sort || 100,
428
+ config_file: file_path,
429
+ main: cfg.main || '',
430
+ enabled: cfg.enabled !== false,
431
+ auto_start: !!cfg.auto_start,
432
+ lazy: cfg.lazy !== undefined ? cfg.lazy : 'auto'
433
+ };
434
+
435
+ this.addEntry(entry);
436
+
437
+ if (this._manager && typeof this._manager.registerModEx === 'function') {
438
+ this._manager.registerModEx(entry);
439
+ }
440
+
441
+ return true;
442
+ } catch {
443
+ // 单个模块注册失败不影响其他模块
444
+ return false;
445
+ }
446
+ };
447
+
448
+ /**
449
+ * 获取全量模块目录
450
+ * 三步交叉比对:扫描文件系统 → 交叉比对注册表 → 补充运行时状态
451
+ * @param {object} scanner DirScanner 实例
452
+ * @param {string[]} dirs 要扫描的目录列表
453
+ * @param {boolean} [force] 是否强制重新扫描文件系统
454
+ * @returns {Array<object>} 全量目录列表
455
+ */
456
+ Registry.prototype.getCatalog = function (scanner, dirs, force) {
457
+ if (!scanner || typeof scanner.scanAndParse !== 'function') {
458
+ throw new TypeError('scanner 必须包含 scanAndParse 方法');
459
+ }
460
+ if (!dirs || !Array.isArray(dirs)) {
461
+ return [];
462
+ }
463
+
464
+ // 1. 扫描文件系统
465
+ var file_dict = this._scanFileSystem(scanner, dirs, force);
466
+
467
+ // 2. 与注册表交叉比对
468
+ var registry_dict = this._buildRegistryMap();
469
+
470
+ // 3. 构建全量目录
471
+ var result = this._buildCatalog(file_dict, registry_dict);
472
+
473
+ // 4. 补充注册表中有但文件系统中不存在的模块
474
+ this._appendMissingModules(result.catalog, result.processed, registry_dict);
475
+
476
+ return result.catalog;
477
+ };
478
+
479
+ /**
480
+ * 扫描文件系统,构建文件映射
481
+ * @param {object} scanner DirScanner 实例
482
+ * @param {string[]} dirs 目录列表
483
+ * @param {boolean} force 是否强制扫描
484
+ * @returns {object} 文件路径到配置的映射
485
+ * @private
486
+ */
487
+ Registry.prototype._scanFileSystem = function (scanner, dirs, force) {
488
+ var file_dict = {};
489
+ for (var i = 0; i < dirs.length; i++) {
490
+ var dir = dirs[i];
491
+ var entries = scanner.scanAndParse(dir, '*.json', null, !!force);
492
+ for (var j = 0; j < entries.length; j++) {
493
+ var entry = entries[j];
494
+ file_dict[entry.file] = entry.config;
495
+ }
496
+ }
497
+ return file_dict;
498
+ };
499
+
500
+ /**
501
+ * 构建注册表名称映射
502
+ * @returns {object} 名称到注册表项的映射
503
+ * @private
504
+ */
505
+ Registry.prototype._buildRegistryMap = function () {
506
+ var registry_dict = {};
507
+ var reg_modules = this._data.modules;
508
+ for (var k = 0; k < reg_modules.length; k++) {
509
+ var reg = reg_modules[k];
510
+ registry_dict[reg.name] = reg;
511
+ }
512
+ return registry_dict;
513
+ };
514
+
515
+ /**
516
+ * 从文件系统扫描结果构建目录条目
517
+ * @param {object} file_dict 文件映射
518
+ * @param {object} registry_dict 注册表映射
519
+ * @returns {Array} 目录条目列表
520
+ * @private
521
+ */
522
+ Registry.prototype._buildCatalog = function (file_dict, registry_dict) {
523
+ var catalog = [];
524
+ var processed = {};
525
+
526
+ for (var file_path in file_dict) {
527
+ var cfg = file_dict[file_path];
528
+ var name = cfg.name || path.basename(file_path, path.extname(file_path));
529
+ var reg_ref = registry_dict[name] || null;
530
+
531
+ var item = {
532
+ name: name,
533
+ title: cfg.title || '',
534
+ type: cfg.type || 'plugin',
535
+ sort: cfg.sort || 100,
536
+ config_file: file_path,
537
+ main: cfg.main || '',
538
+ registered: !!reg_ref,
539
+ auto_start: reg_ref ? !!reg_ref.auto_start : false,
540
+ lazy: reg_ref ? reg_ref.lazy : 'auto',
541
+ // eslint-disable-next-line mm_eslint/variable-name
542
+ lifecycle_state: null,
543
+ file_exists: true,
544
+ status: 'unregistered'
545
+ };
546
+
547
+ this._addRuntimeState(item, reg_ref, name);
548
+
549
+ catalog.push(item);
550
+ processed[name] = true;
551
+ }
552
+
553
+ return { catalog: catalog, processed: processed };
554
+ };
555
+
556
+ /**
557
+ * 补充运行时状态
558
+ * @param {object} item 目录条目
559
+ * @param {object|null} reg_ref 注册表项
560
+ * @param {string} name 模块名称
561
+ * @private
562
+ */
563
+ Registry.prototype._addRuntimeState = function (item, reg_ref, name) {
564
+ if (!reg_ref || !this._manager) {
565
+ return;
566
+ }
567
+
568
+ var mod = this._manager.getMod(name);
569
+ if (mod) {
570
+ // eslint-disable-next-line no-param-reassign
571
+ item.lifecycle_state = mod.lifecycle_state;
572
+ // eslint-disable-next-line no-param-reassign
573
+ item.status = Registry._calcStatus(mod.lifecycle_state);
574
+ } else {
575
+ // eslint-disable-next-line no-param-reassign
576
+ item.status = 'stopped';
577
+ }
578
+ };
579
+
580
+ /**
581
+ * 追加注册表中有但文件系统中不存在的模块
582
+ * @param {Array} catalog 目录列表
583
+ * @param {object} processed 已处理模块名称集合
584
+ * @param {object} registry_dict 注册表映射
585
+ * @private
586
+ */
587
+ // eslint-disable-next-line complexity
588
+ Registry.prototype._appendMissingModules = function (catalog, processed, registry_dict) {
589
+ var reg_modules = this._data.modules;
590
+
591
+ for (var n = 0; n < reg_modules.length; n++) {
592
+ var reg = reg_modules[n];
593
+ if (processed[reg.name]) {
594
+ continue;
595
+ }
596
+
597
+ var mod = this._manager ? this._manager.getMod(reg.name) : null;
598
+ catalog.push({
599
+ name: reg.name,
600
+ title: reg.title || '',
601
+ type: reg.type || 'plugin',
602
+ sort: reg.sort || 100,
603
+ config_file: reg.config_file || '',
604
+ main: reg.main || '',
605
+ registered: true,
606
+ auto_start: !!reg.auto_start,
607
+ lazy: reg.lazy || 'auto',
608
+ // eslint-disable-next-line mm_eslint/variable-name
609
+ lifecycle_state: mod ? mod.lifecycle_state : null,
610
+ file_exists: false,
611
+ status: 'file_missing'
612
+ });
613
+ }
614
+ };
615
+
616
+ /**
617
+ * 根据 lifecycle_state 计算状态
618
+ * @param {string} state 生命周期状态
619
+ * @returns {string} 状态值
620
+ * @private
621
+ * @static
622
+ */
623
+ Registry._calcStatus = function (state) {
624
+ switch (state) {
625
+ case 'started':
626
+ return 'running';
627
+ case 'paused':
628
+ return 'paused';
629
+ default:
630
+ return 'stopped';
631
+ }
632
+ };
633
+
634
+ module.exports = { Registry };