mm_func 0.0.0-stage → 1.0.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 +65 -2
- package/index.js +27 -0
- package/lib/func.js +192 -0
- package/package.json +29 -5
- package/test.js +97 -0
package/README.md
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mm_func
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
函数钩子(Func Hook)管理模块:提供函数钩子的注册、删除与按名执行能力。
|
|
4
|
+
|
|
5
|
+
与 `mm_action`(动作钩子)、`mm_filter`(过滤钩子)共同构成 mm 模块体系的三类钩子基础设施,
|
|
6
|
+
`mm_tpl` 等上层模块通过组合这三个模块获得完整的钩子能力。
|
|
7
|
+
|
|
8
|
+
## 安装
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install mm_func
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 使用
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const { Func, funcAdmin } = require('mm_func');
|
|
18
|
+
|
|
19
|
+
// 直接使用
|
|
20
|
+
var func = new Func();
|
|
21
|
+
|
|
22
|
+
// 注册函数钩子:name, func, sort(越大越先执行), alias(删除用)
|
|
23
|
+
func.addFunc('list', async function makeList(model, req) {
|
|
24
|
+
return await db.query('...');
|
|
25
|
+
});
|
|
26
|
+
func.addFunc('list', function makeStatic() {
|
|
27
|
+
return 'static list';
|
|
28
|
+
}, 50);
|
|
29
|
+
|
|
30
|
+
// 执行:按 sort 降序依次执行,支持异步函数,返回最后一个真值
|
|
31
|
+
var ret = await func.runFunc('list', { query: { page: 1 } }, {});
|
|
32
|
+
|
|
33
|
+
// 按别名删除
|
|
34
|
+
func.delFunc('list', 'makeStatic');
|
|
35
|
+
|
|
36
|
+
// 清空
|
|
37
|
+
func.clear('list'); // 清空指定函数
|
|
38
|
+
func.clear(); // 清空全部
|
|
39
|
+
|
|
40
|
+
// 通过 Admin 工厂按作用域取实例(需安装 mm_corejs)
|
|
41
|
+
var admin = funcAdmin('os', { /* config */ });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
| 方法 | 说明 |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `addFunc(name, func, sort = 100, alias = '')` | 添加函数钩子,返回别名 |
|
|
49
|
+
| `delFunc(name, alias = '')` | 删除函数钩子,别名省略时清空该名称下全部 |
|
|
50
|
+
| `getFunc(name)` | 获取第一个函数体,未注册时返回 undefined |
|
|
51
|
+
| `hasFunc(name)` | 判断函数是否存在 |
|
|
52
|
+
| `listFuncs(name)` | 列出函数名称(省略 name)或函数项列表 |
|
|
53
|
+
| `runFunc(name, ...args)` | 异步按序执行,支持 Promise,返回最后一个真值 |
|
|
54
|
+
| `clear(name)` | 清空函数 |
|
|
55
|
+
|
|
56
|
+
## 语义约定
|
|
57
|
+
|
|
58
|
+
- 同一函数名下可注册多个函数实现,按 `sort` **降序**依次执行;
|
|
59
|
+
- `runFunc` 为异步执行,支持异步函数/Promise 返回值,返回**最后一个真值**结果;
|
|
60
|
+
- 未注册函数名时执行返回 undefined;
|
|
61
|
+
- 单个钩子异常只记录日志并跳过,不中断其他钩子。
|
|
62
|
+
|
|
63
|
+
## 与 mm_tpl 的关系
|
|
64
|
+
|
|
65
|
+
`mm_tpl` 内部的 `Hook` 类是本模块(以及 `mm_action`、`mm_filter`)的组合门面:
|
|
66
|
+
模板中 `tpl.runFunc(name, req)` 的调用最终由 `mm_func` 执行。
|
package/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const Func = require('./lib/func');
|
|
2
|
+
let Admin = null;
|
|
3
|
+
try {
|
|
4
|
+
Admin = require('mm_corejs').Admin;
|
|
5
|
+
} catch (e) {
|
|
6
|
+
// mm_corejs 未安装时仅提供核心类,Admin 工厂不可用
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let admin = null;
|
|
10
|
+
if (Admin) {
|
|
11
|
+
admin = new Admin();
|
|
12
|
+
admin.setClass(Func);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
Func,
|
|
17
|
+
admin,
|
|
18
|
+
funcAdmin(scope, config = {}) {
|
|
19
|
+
if (!admin) {
|
|
20
|
+
throw new Error('mm_func: mm_corejs 未安装,Admin 工厂不可用');
|
|
21
|
+
}
|
|
22
|
+
if (!scope) {
|
|
23
|
+
scope = 'os';
|
|
24
|
+
}
|
|
25
|
+
return admin.get(scope, config);
|
|
26
|
+
}
|
|
27
|
+
};
|
package/lib/func.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Func —— 函数钩子管理模块。
|
|
7
|
+
*
|
|
8
|
+
* 维护「函数名 → 函数列表」的注册表,提供函数钩子的注册、删除与按名执行能力。
|
|
9
|
+
*
|
|
10
|
+
* 语义约定:
|
|
11
|
+
* - 同一函数名下可注册多个函数实现,按 sort 降序依次执行(sort 越大越先执行);
|
|
12
|
+
* - runFunc 为异步执行:支持异步函数/Promise 返回值,返回最后一个真值结果;
|
|
13
|
+
* - 未注册函数名时执行返回 undefined;
|
|
14
|
+
* - 单个函数执行异常只记录日志并跳过,不影响其他函数执行。
|
|
15
|
+
*
|
|
16
|
+
* @class
|
|
17
|
+
*/
|
|
18
|
+
class Func {
|
|
19
|
+
/**
|
|
20
|
+
* 构造函数
|
|
21
|
+
* @param {Object} [config] 模块配置
|
|
22
|
+
* @param {Object} [config.logger] 日志对象,需含 error 方法,缺省使用 console
|
|
23
|
+
*/
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
/**
|
|
27
|
+
* 日志对象
|
|
28
|
+
*/
|
|
29
|
+
this._logger = config.logger || console;
|
|
30
|
+
/**
|
|
31
|
+
* 函数注册表:函数名 → 函数项列表
|
|
32
|
+
* @type {Object<string, Array<{func: Function, sort: number, alias: string}>>}
|
|
33
|
+
*/
|
|
34
|
+
this._methods = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 获取函数别名
|
|
39
|
+
* 具名函数取函数名,匿名函数用 md5 自动生成
|
|
40
|
+
* @param {Function} func - 钩子函数
|
|
41
|
+
* @param {string} alias - 别名
|
|
42
|
+
* @returns {string} 函数别名
|
|
43
|
+
*/
|
|
44
|
+
_getName(func, alias) {
|
|
45
|
+
if (alias) {
|
|
46
|
+
return alias;
|
|
47
|
+
}
|
|
48
|
+
if (func.name) {
|
|
49
|
+
return func.name;
|
|
50
|
+
}
|
|
51
|
+
return crypto.createHash('md5').update((new Date()).toString()).digest('hex').substring(0, 8);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 添加函数钩子
|
|
56
|
+
* @param {string} name - 函数名称
|
|
57
|
+
* @param {Function} func - 函数体
|
|
58
|
+
* @param {number} [sort=100] - 执行顺序,越大越先执行
|
|
59
|
+
* @param {string} [alias=""] - 函数别名,用于删除;省略时自动生成
|
|
60
|
+
* @returns {string} 函数别名
|
|
61
|
+
* @throws {TypeError} 名称非法或函数体非函数时
|
|
62
|
+
*/
|
|
63
|
+
addFunc(name, func, sort = 100, alias = '') {
|
|
64
|
+
if (typeof name !== 'string' || name === '') {
|
|
65
|
+
throw new TypeError(`[${this.constructor.name}] name must be a non-empty string`);
|
|
66
|
+
}
|
|
67
|
+
if (typeof func !== 'function') {
|
|
68
|
+
throw new TypeError(`[${this.constructor.name}] func must be a function`);
|
|
69
|
+
}
|
|
70
|
+
alias = this._getName(func, alias);
|
|
71
|
+
var arr = this._methods[name] || [];
|
|
72
|
+
arr.push({
|
|
73
|
+
func,
|
|
74
|
+
sort,
|
|
75
|
+
alias
|
|
76
|
+
});
|
|
77
|
+
arr.sort((o1, o2) => o2.sort - o1.sort);
|
|
78
|
+
this._methods[name] = arr;
|
|
79
|
+
return alias;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 删除函数钩子
|
|
84
|
+
* @param {string} name - 函数名称
|
|
85
|
+
* @param {string} [alias=""] - 函数别名;省略时删除该函数名下全部函数
|
|
86
|
+
* @returns {boolean} 是否删除成功
|
|
87
|
+
*/
|
|
88
|
+
delFunc(name, alias = '') {
|
|
89
|
+
var arr = this._methods[name];
|
|
90
|
+
if (!arr || !arr.length) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (!alias) {
|
|
94
|
+
delete this._methods[name];
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
var list = [];
|
|
98
|
+
var removed = false;
|
|
99
|
+
for (var i = 0; i < arr.length; i++) {
|
|
100
|
+
var item = arr[i];
|
|
101
|
+
if (item.alias === alias) {
|
|
102
|
+
removed = true;
|
|
103
|
+
} else {
|
|
104
|
+
list.push(item);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (removed) {
|
|
108
|
+
this._methods[name] = list;
|
|
109
|
+
}
|
|
110
|
+
return removed;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 获取函数
|
|
115
|
+
* @param {string} name - 函数名称
|
|
116
|
+
* @returns {Function|undefined} 第一个函数体;未注册时为 undefined
|
|
117
|
+
*/
|
|
118
|
+
getFunc(name) {
|
|
119
|
+
var arr = this._methods[name];
|
|
120
|
+
return arr && arr.length ? arr[0].func : undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 判断函数是否存在
|
|
125
|
+
* @param {string} name - 函数名称
|
|
126
|
+
* @returns {boolean} 是否存在
|
|
127
|
+
*/
|
|
128
|
+
hasFunc(name) {
|
|
129
|
+
var arr = this._methods[name];
|
|
130
|
+
return !!(arr && arr.length);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 列出函数
|
|
135
|
+
* @param {string} [name] - 函数名称;省略时返回全部函数名称列表
|
|
136
|
+
* @returns {Array<string>|Array<{alias: string, sort: number}>} 函数名称列表或函数项列表
|
|
137
|
+
*/
|
|
138
|
+
listFuncs(name) {
|
|
139
|
+
if (name === undefined) {
|
|
140
|
+
return Object.keys(this._methods);
|
|
141
|
+
}
|
|
142
|
+
var arr = this._methods[name] || [];
|
|
143
|
+
return arr.map((item) => ({
|
|
144
|
+
alias: item.alias,
|
|
145
|
+
sort: item.sort
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 执行函数钩子
|
|
151
|
+
* 按顺序异步执行该函数名下的全部函数,支持 Promise 返回值,返回最后一个真值
|
|
152
|
+
* @param {string} name - 函数名称
|
|
153
|
+
* @param {...any} args - 传递给函数的参数
|
|
154
|
+
* @returns {Promise<any>} 最后一个真值结果;未注册时为 undefined
|
|
155
|
+
*/
|
|
156
|
+
async runFunc(name, ...args) {
|
|
157
|
+
var ret;
|
|
158
|
+
var arr = this._methods[name];
|
|
159
|
+
if (!arr) {
|
|
160
|
+
return ret;
|
|
161
|
+
}
|
|
162
|
+
for (var i = 0; i < arr.length; i++) {
|
|
163
|
+
var item = arr[i];
|
|
164
|
+
try {
|
|
165
|
+
var ret_new = item.func(...args);
|
|
166
|
+
if (ret_new && typeof ret_new.then === 'function') {
|
|
167
|
+
ret_new = await ret_new;
|
|
168
|
+
}
|
|
169
|
+
if (ret_new) {
|
|
170
|
+
ret = ret_new;
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
this._logger.error(`[${this.constructor.name}] 执行函数钩子失败 ${item.alias}`, err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return ret;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 清空函数钩子
|
|
181
|
+
* @param {string} [name] - 函数名称;省略时清空全部函数
|
|
182
|
+
*/
|
|
183
|
+
clear(name) {
|
|
184
|
+
if (name) {
|
|
185
|
+
delete this._methods[name];
|
|
186
|
+
} else {
|
|
187
|
+
this._methods = {};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = Func;
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "mm_func",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "函数钩子管理模块:提供函数钩子 addFunc/delFunc/runFunc 的注册与按名执行能力",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://gitee.com/qiuwenwu91/mm_func.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mm_mod",
|
|
12
|
+
"mm_infra",
|
|
13
|
+
"func",
|
|
14
|
+
"hook",
|
|
15
|
+
"函数"
|
|
16
|
+
],
|
|
17
|
+
"layer": "infra",
|
|
18
|
+
"group": "common",
|
|
19
|
+
"author": "邱文武",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node test.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"mm_corejs": "^1.1.6"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const mod = require('./index.js');
|
|
5
|
+
const Func = require('./lib/func');
|
|
6
|
+
|
|
7
|
+
// 1. 导出结构校验
|
|
8
|
+
assert.strictEqual(typeof mod.Func, 'function', '应导出 Func 类');
|
|
9
|
+
assert.strictEqual(typeof mod.funcAdmin, 'function', '应导出 funcAdmin 函数');
|
|
10
|
+
|
|
11
|
+
async function test() {
|
|
12
|
+
console.log('=== mm_func 测试开始 ===');
|
|
13
|
+
|
|
14
|
+
// 2. 添加与执行
|
|
15
|
+
var func = new Func();
|
|
16
|
+
func.addFunc('list', async function makeList(model, req) {
|
|
17
|
+
return ['article1', 'article2'];
|
|
18
|
+
});
|
|
19
|
+
func.addFunc('list', function makeStatic() {
|
|
20
|
+
return 'static';
|
|
21
|
+
}, 50);
|
|
22
|
+
var ret = await func.runFunc('list', { query: { page: 1 } }, {});
|
|
23
|
+
// sort 大的先执行:makeList(100) 先返回数组,makeStatic(50) 后返回字符串,取最后一个真值
|
|
24
|
+
assert.strictEqual(ret, 'static');
|
|
25
|
+
console.log('√ 添加与异步执行(返回最后一个真值)');
|
|
26
|
+
|
|
27
|
+
// 3. 未注册返回 undefined
|
|
28
|
+
assert.strictEqual(await func.runFunc('none'), undefined);
|
|
29
|
+
console.log('√ 未注册返回 undefined');
|
|
30
|
+
|
|
31
|
+
// 4. getFunc:取函数体
|
|
32
|
+
var got = func.getFunc('list');
|
|
33
|
+
assert.strictEqual(typeof got, 'function');
|
|
34
|
+
assert.strictEqual(got.name, 'makeList');
|
|
35
|
+
assert.strictEqual(func.getFunc('none'), undefined);
|
|
36
|
+
console.log('√ getFunc 取函数体');
|
|
37
|
+
|
|
38
|
+
// 5. 别名:具名函数自动取函数名
|
|
39
|
+
var func2 = new Func();
|
|
40
|
+
var alias = func2.addFunc('test', function myFunc() {});
|
|
41
|
+
assert.strictEqual(alias, 'myFunc');
|
|
42
|
+
var alias2 = func2.addFunc('test', function () {});
|
|
43
|
+
assert.ok(alias2 && alias2.length === 8);
|
|
44
|
+
console.log('√ 别名自动生成');
|
|
45
|
+
|
|
46
|
+
// 5. 删除:按别名删除 / 清空
|
|
47
|
+
var func3 = new Func();
|
|
48
|
+
func3.addFunc('run', function f1() { return 1; });
|
|
49
|
+
func3.addFunc('run', function f2() { return 2; });
|
|
50
|
+
assert.ok(func3.delFunc('run', 'f1'));
|
|
51
|
+
assert.strictEqual(await func3.runFunc('run'), 2);
|
|
52
|
+
assert.ok(func3.delFunc('run'));
|
|
53
|
+
assert.ok(!func3.hasFunc('run'));
|
|
54
|
+
console.log('√ 按别名删除与清空');
|
|
55
|
+
|
|
56
|
+
// 6. 参数透传
|
|
57
|
+
var func4 = new Func();
|
|
58
|
+
func4.addFunc('add', function (a, b, c) {
|
|
59
|
+
return a + b + c;
|
|
60
|
+
});
|
|
61
|
+
assert.strictEqual(await func4.runFunc('add', 1, 2, 3), 6);
|
|
62
|
+
console.log('√ 参数透传');
|
|
63
|
+
|
|
64
|
+
// 7. 异常隔离:单个钩子报错不影响其他钩子
|
|
65
|
+
var func5 = new Func({ logger: { error() {} } });
|
|
66
|
+
func5.addFunc('boom', function () {
|
|
67
|
+
throw new Error('boom');
|
|
68
|
+
});
|
|
69
|
+
func5.addFunc('boom', async function ok() {
|
|
70
|
+
return 'ok';
|
|
71
|
+
});
|
|
72
|
+
assert.strictEqual(await func5.runFunc('boom'), 'ok');
|
|
73
|
+
console.log('√ 异常隔离');
|
|
74
|
+
|
|
75
|
+
// 8. 参数校验
|
|
76
|
+
var func6 = new Func();
|
|
77
|
+
assert.throws(() => func6.addFunc('', function () {}), TypeError);
|
|
78
|
+
assert.throws(() => func6.addFunc('x', 'not a function'), TypeError);
|
|
79
|
+
console.log('√ 参数校验');
|
|
80
|
+
|
|
81
|
+
// 9. listFuncs / clear
|
|
82
|
+
var func7 = new Func();
|
|
83
|
+
func7.addFunc('a', function x1() {});
|
|
84
|
+
func7.addFunc('b', function x2() {});
|
|
85
|
+
assert.deepStrictEqual(func7.listFuncs().sort(), ['a', 'b']);
|
|
86
|
+
assert.strictEqual(func7.listFuncs('a')[0].alias, 'x1');
|
|
87
|
+
func7.clear();
|
|
88
|
+
assert.deepStrictEqual(func7.listFuncs(), []);
|
|
89
|
+
console.log('√ listFuncs / clear');
|
|
90
|
+
|
|
91
|
+
console.log('=== mm_func 测试全部通过 ===');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test().catch((err) => {
|
|
95
|
+
console.error(err);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
});
|