mm_hook_func 1.0.0 → 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.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * mm_hook_func 钩子函数模块示例
3
+ * 演示 HookFunc 的函数注册表:addFunc / hasFunc / runFunc / listFuncs / delFunc
4
+ * 运行:node example/index.js
5
+ */
6
+ const { HookFunc, getHookFunc } = require('../index.js');
7
+
8
+ /**
9
+ * 主函数
10
+ * @returns {Promise<void>}
11
+ */
12
+ async function main() {
13
+ console.log('=== 钩子函数注册表 ===');
14
+
15
+ // 获取单例(多次获取返回同一实例)
16
+ var hook = getHookFunc();
17
+ var hook2 = getHookFunc();
18
+ console.log('getHookFunc 单例 ->', hook === hook2);
19
+
20
+ // 注册函数
21
+ hook.addFunc('html_filter', function (ctx, params, html) {
22
+ return html.replace(/<script[\s\S]*?<\/script>/ig, '');
23
+ });
24
+ hook.addFunc('hello', function (name) {
25
+ return '你好, ' + name + '!';
26
+ });
27
+ console.log('注册后函数列表 ->', hook.listFuncs().join(', '));
28
+ console.log('hasFunc("hello") ->', hook.hasFunc('hello'));
29
+
30
+ // 运行函数
31
+ console.log('runFunc("hello", "张三") ->', await hook.runFunc('hello', '张三'));
32
+ console.log('runFunc("html_filter") 过滤脚本标签 ->',
33
+ await hook.runFunc('html_filter', {}, {}, '<p>正文</p><script>alert(1)</script>'));
34
+
35
+ // 未注册函数:原样返回最后一个参数
36
+ console.log('runFunc("no_such_func") ->', await hook.runFunc('no_such_func', 'fallback'));
37
+
38
+ // 删除函数
39
+ console.log('delFunc("hello") ->', hook.delFunc('hello'));
40
+ console.log('删除后 hasFunc("hello") ->', hook.hasFunc('hello'));
41
+
42
+ // 直接实例化
43
+ var h2 = new HookFunc();
44
+ console.log('\n直接实例化 HookFunc ->', h2 instanceof HookFunc);
45
+ }
46
+
47
+ main().catch(function (err) {
48
+ console.error(err);
49
+ process.exit(1);
50
+ });
package/lib/hook_func.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { Base } = require('mm_corejs');
3
+ const { Base } = require('mm_mod');
4
4
 
5
5
  /**
6
6
  * 钩子函数类
@@ -112,17 +112,52 @@ class HookFunc extends Base {
112
112
 
113
113
  /**
114
114
  * 绑定 web 服务器:向其请求管道的 render 阶段注册渲染钩子
115
- * @param {Object} web web 服务器实例,需提供 getHandle() 方法
115
+ *
116
+ * 支持两代管线:
117
+ * - 新代(mm_event 七阶段链):web 提供 getEventManager(),本方法向其注册一个
118
+ * render 阶段的事件驱动(驱动脚本 lib/render_hook.js);
119
+ * - 旧代(Handle/Hooks):web 提供 getHandle(),保持原有 on('render') 注册方式兼容。
120
+ *
121
+ * @param {Object} web web 服务器实例,需提供 getEventManager() 或 getHandle() 方法
116
122
  * @returns {HookFunc} 当前实例
117
- * @throws {TypeError} web 不具备 getHandle 方法时
123
+ * @throws {TypeError} web 不具备 getEventManager/getHandle 方法时
118
124
  */
119
125
  bind(web) {
120
- if (!web || typeof web.getHandle !== 'function') {
121
- throw new TypeError(`${this._label()} web must provide getHandle()`);
126
+ if (!web) {
127
+ throw new TypeError(`${this._label()} web is required`);
122
128
  }
123
- this.#web = web;
124
- web.getHandle().on('render', (params, ctx, options) => this._runRender(params, ctx, options), 100, 'hook_func');
125
- return this;
129
+ // 旧代管线(Handle/Hooks):保留兼容
130
+ if (typeof web.getHandle === 'function') {
131
+ this.#web = web;
132
+ web.getHandle().on('render', (params, ctx, options) => this._runRender(params, ctx, options), 100, 'hook_func');
133
+ return this;
134
+ }
135
+ // 新代管线(mm_event 七阶段链):注册 render 阶段事件驱动
136
+ if (typeof web.getEventManager === 'function') {
137
+ const manager = web.getEventManager();
138
+ if (!manager || typeof manager.add !== 'function') {
139
+ throw new TypeError(`${this._label()} web.getEventManager() must return an event manager with add()`);
140
+ }
141
+ this.#web = web;
142
+ /**
143
+ * 已注册的渲染阶段事件驱动
144
+ * @type {Object|null}
145
+ */
146
+ this._render_drive = manager.add({
147
+ name: 'hook_func_render',
148
+ title: '统一渲染钩子',
149
+ description: '由 mm_hook_func 注册:渲染阶段对字符串结果执行配置的过滤函数(默认 html_filter)',
150
+ target: '*',
151
+ stage: 'render',
152
+ sort: 100,
153
+ state: 1,
154
+ dir: __dirname,
155
+ main: 'render_hook.js',
156
+ func_name: 'main'
157
+ });
158
+ return this;
159
+ }
160
+ throw new TypeError(`${this._label()} web must provide getEventManager() or getHandle()`);
126
161
  }
127
162
 
128
163
  /**
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { getHookFunc } = require('../index.js');
4
+
5
+ /**
6
+ * 渲染阶段钩子(mm_event 七阶段链驱动脚本)
7
+ *
8
+ * 由 HookFunc.bind(web) 注册到 web 作用域事件管理器的 render 阶段:
9
+ * 对上下文结果中的字符串执行配置的渲染函数(默认 html_filter),
10
+ * 返回过滤后的内容,事件管理器会将其写回 ctx.result。
11
+ *
12
+ * @param {Object} params 请求参数
13
+ * @param {Object} ctx 请求上下文(ctx.result 为待渲染内容)
14
+ * @param {Object} options 事件选项
15
+ * @returns {Promise<string|undefined>} 过滤后的内容;结果非字符串时返回 undefined
16
+ */
17
+ async function main(params, ctx, options) {
18
+ const hf = getHookFunc();
19
+ const html = ctx && ctx.result;
20
+ if (typeof html !== 'string') {
21
+ return undefined;
22
+ }
23
+ return await hf.runFunc(hf.config.render_name, ctx, options, html);
24
+ }
25
+
26
+ module.exports = { main };
package/package.json CHANGED
@@ -1,30 +1,35 @@
1
1
  {
2
- "name": "mm_hook_func",
3
- "version": "1.0.0",
4
- "description": "钩子函数模块,提供函数名值注册表并可作为插件向 web 管道注册 render 钩子。",
5
- "main": "index.js",
6
- "keywords": [
7
- "mm_mod",
8
- "mm_infra",
9
- "mm_common",
10
- "hook",
11
- "func",
12
- "render"
13
- ],
14
- "layer": "infra",
15
- "group": "common",
16
- "author": "邱文武",
17
- "license": "ISC",
18
- "dependencies": {
19
- "mm_corejs": "^1.1.4"
20
- },
21
- "engines": {
22
- "node": ">=12.0.0"
23
- },
24
- "directories": {
25
- "lib": "lib"
26
- },
27
- "scripts": {
28
- "test": "node test.js"
29
- }
2
+ "name": "mm_hook_func",
3
+ "version": "1.0.1",
4
+ "description": "钩子函数模块,提供函数名值注册表并可作为插件向 web 管道注册 render 钩子。",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://gitee.com/qiuwenwu91/mm_hook_func.git"
9
+ },
10
+ "keywords": [
11
+ "mm_mod",
12
+ "mm_infra",
13
+ "mm_common",
14
+ "hook",
15
+ "func",
16
+ "render"
17
+ ],
18
+ "layer": "infra",
19
+ "group": "common",
20
+ "author": "邱文武",
21
+ "license": "ISC",
22
+ "dependencies": {
23
+ "mm_corejs": "^1.1.4",
24
+ "mm_mod": "^1.0.1"
25
+ },
26
+ "engines": {
27
+ "node": ">=12.0.0"
28
+ },
29
+ "directories": {
30
+ "lib": "lib"
31
+ },
32
+ "scripts": {
33
+ "test": "node test.js && node --test tests/hook_func.test.js"
34
+ }
30
35
  }
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+ /**
3
+ * mm_hook_func 单元测试
4
+ * @description 覆盖函数注册表、两代管线 bind(旧 Handle / 新 mm_event 七阶段链)与渲染驱动脚本
5
+ */
6
+ const test = require('node:test');
7
+ const assert = require('assert');
8
+ const path = require('path');
9
+
10
+ const { HookFunc, getHookFunc } = require('../index.js');
11
+
12
+ test('导出结构与单例', () => {
13
+ assert.strictEqual(typeof HookFunc, 'function');
14
+ assert.strictEqual(typeof getHookFunc, 'function');
15
+ const h1 = getHookFunc();
16
+ const h2 = getHookFunc();
17
+ assert.ok(h1 instanceof HookFunc);
18
+ assert.strictEqual(h1, h2, 'getHookFunc 应返回单例');
19
+ });
20
+
21
+ test('函数注册表:增删查与运行', async () => {
22
+ const hf = new HookFunc();
23
+ assert.strictEqual(hf.hasFunc('x'), false);
24
+ hf.addFunc('x', (a) => `${a}!`);
25
+ assert.strictEqual(hf.hasFunc('x'), true);
26
+ assert.strictEqual(await hf.runFunc('x', 'hi'), 'hi!');
27
+ assert.strictEqual(hf.delFunc('x'), true);
28
+ assert.strictEqual(hf.delFunc('x'), false);
29
+ // 未注册时 runFunc 原样返回最后一个参数
30
+ assert.strictEqual(await hf.runFunc('none', 'a', 'b'), 'b');
31
+ assert.deepStrictEqual(hf.listFuncs(), []);
32
+ hf.addFunc('a', () => 1);
33
+ hf.addFunc('b', () => 2);
34
+ hf.clear();
35
+ assert.deepStrictEqual(hf.listFuncs(), []);
36
+ });
37
+
38
+ test('注册表参数校验', () => {
39
+ const hf = new HookFunc();
40
+ assert.throws(() => hf.addFunc('', () => {}), TypeError);
41
+ assert.throws(() => hf.addFunc(null, () => {}), TypeError);
42
+ assert.throws(() => hf.addFunc('y', 'not-a-fn'), TypeError);
43
+ });
44
+
45
+ test('bind 新代管线:向事件管理器注册 render 阶段驱动', () => {
46
+ const hf = new HookFunc();
47
+ const captured = [];
48
+ const web = {
49
+ getEventManager: () => ({
50
+ add: (config) => {
51
+ captured.push(config);
52
+ return { config };
53
+ },
54
+ }),
55
+ };
56
+ hf.bind(web);
57
+ assert.strictEqual(captured.length, 1, '应注册一个事件驱动');
58
+ const cfg = captured[0];
59
+ assert.strictEqual(cfg.name, 'hook_func_render');
60
+ assert.strictEqual(cfg.stage, 'render', '应注册到 render 阶段');
61
+ assert.strictEqual(cfg.target, '*');
62
+ assert.strictEqual(cfg.state, 1);
63
+ assert.strictEqual(cfg.main, 'render_hook.js', '驱动脚本应为 lib/render_hook.js');
64
+ assert.ok(cfg.dir && cfg.dir.endsWith('lib'), '驱动目录应指向模块 lib 目录');
65
+ // 重复绑定同一 web 不应无限累积(第二次 add 覆盖同名驱动由管理器负责)
66
+ hf.bind(web);
67
+ assert.strictEqual(captured.length, 2);
68
+ });
69
+
70
+ test('bind 新代管线:渲染驱动脚本对 ctx.result 执行 html_filter', async () => {
71
+ // 驱动脚本内部使用 getHookFunc() 单例,此处也用单例注册函数
72
+ const hf = getHookFunc();
73
+ hf.clear();
74
+ let cfg = null;
75
+ const web = {
76
+ getEventManager: () => ({
77
+ add: (config) => {
78
+ cfg = config;
79
+ return { config };
80
+ },
81
+ }),
82
+ };
83
+ hf.bind(web);
84
+ // 模拟事件管理器加载驱动脚本并执行 main
85
+ const drive = require(path.join(cfg.dir, cfg.main));
86
+ assert.strictEqual(typeof drive.main, 'function', '驱动脚本应导出 main 函数');
87
+
88
+ // 场景1:注册了 html_filter 时对字符串结果生效
89
+ hf.addFunc('html_filter', (ctx, options, html) => html.replace(/bad/g, 'good'));
90
+ const ctx1 = { result: 'bad word' };
91
+ const ret1 = await drive.main({}, ctx1, {});
92
+ assert.strictEqual(ret1, 'good word');
93
+ assert.strictEqual(ctx1.result, 'bad word', '驱动仅返回结果,写回 ctx.result 由事件管理器负责');
94
+
95
+ // 场景2:结果非字符串时返回 undefined
96
+ const ctx2 = { result: { json: true } };
97
+ assert.strictEqual(await drive.main({}, ctx2, {}), undefined);
98
+
99
+ // 场景3:未注册 html_filter 时原样返回内容(runFunc 回退语义)
100
+ hf.delFunc('html_filter');
101
+ const ctx3 = { result: 'plain' };
102
+ assert.strictEqual(await drive.main({}, ctx3, {}), 'plain');
103
+ hf.clear();
104
+ });
105
+
106
+ test('bind 旧代管线:getHandle 兼容路径仍可用', () => {
107
+ const hf = new HookFunc();
108
+ const registered = [];
109
+ const web = {
110
+ getHandle: () => ({
111
+ on: (stage, fn, priority, name) => registered.push({ stage, fn, priority, name }),
112
+ }),
113
+ };
114
+ hf.bind(web);
115
+ assert.strictEqual(registered.length, 1);
116
+ assert.strictEqual(registered[0].stage, 'render');
117
+ assert.strictEqual(registered[0].name, 'hook_func');
118
+ assert.strictEqual(typeof registered[0].fn, 'function');
119
+ });
120
+
121
+ test('bind 非法 web 抛出 TypeError', () => {
122
+ const hf = new HookFunc();
123
+ assert.throws(() => hf.bind(null), TypeError);
124
+ assert.throws(() => hf.bind({}), TypeError, '既无 getEventManager 也无 getHandle 时应抛错');
125
+ assert.throws(
126
+ () => hf.bind({ getEventManager: () => ({}) }),
127
+ TypeError,
128
+ '事件管理器缺少 add 方法时应抛错'
129
+ );
130
+ });