mm_action 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.
- package/index.js +16 -18
- package/lib/action.js +18 -0
- package/lib/define_action.js +27 -0
- package/lib/dispatcher.js +80 -0
- package/package.json +28 -25
- package/test.js +18 -2
package/index.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = Action;
|
|
1
|
+
const Action = require('./lib/action');
|
|
2
|
+
const Admin = require('mm_corejs').Admin;
|
|
3
|
+
|
|
4
|
+
const admin = new Admin();
|
|
5
|
+
admin.setClass(Action);
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
Action,
|
|
9
|
+
admin,
|
|
10
|
+
actionAdmin(scope, config = {}) {
|
|
11
|
+
if (!scope) {
|
|
12
|
+
scope = 'os';
|
|
13
|
+
}
|
|
14
|
+
return admin.get(scope, config);
|
|
15
|
+
}
|
|
16
|
+
};
|
package/lib/action.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Action —— 动作管理模块。
|
|
5
|
+
*
|
|
6
|
+
* 骨架已就绪:构造时接收配置对象,具体功能待开发。
|
|
7
|
+
*/
|
|
8
|
+
class Action {
|
|
9
|
+
/**
|
|
10
|
+
* 构造函数
|
|
11
|
+
* @param {Object} [config] 模块配置
|
|
12
|
+
*/
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = Action;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 动作定义:纯契约,不持有状态。
|
|
5
|
+
* run(param, ctx) => intent | intent[] | undefined
|
|
6
|
+
*/
|
|
7
|
+
function defineAction(def) {
|
|
8
|
+
if (!def || typeof def !== 'object') {
|
|
9
|
+
throw new TypeError('defineAction expects an object');
|
|
10
|
+
}
|
|
11
|
+
if (!def.name || typeof def.name !== 'string') {
|
|
12
|
+
throw new TypeError('action.name must be a non-empty string');
|
|
13
|
+
}
|
|
14
|
+
if (typeof def.run !== 'function') {
|
|
15
|
+
throw new TypeError('action.execute must be a function');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
name: def.name,
|
|
19
|
+
description: def.description || '',
|
|
20
|
+
// 与 LLM tool 定义对齐的字段,方便后续转换
|
|
21
|
+
paramSchema: def.paramSchema || null,
|
|
22
|
+
validate: def.validate || (() => {}),
|
|
23
|
+
run: def.run,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { defineAction };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const EFFECT_INTENT = '@effect';
|
|
4
|
+
|
|
5
|
+
function assertInterface(obj, methods, label) {
|
|
6
|
+
if (!obj || typeof obj !== 'object') {
|
|
7
|
+
throw new TypeError(`${label} must be an object`);
|
|
8
|
+
}
|
|
9
|
+
for (const m of methods) {
|
|
10
|
+
if (typeof obj[m] !== 'function') {
|
|
11
|
+
throw new TypeError(`${label} must implement ${m}()`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 鸭子类型注入:只要求 store.apply 和 effects.run,不 import 具体类。
|
|
18
|
+
* 因此本包 dependencies 为空,用户可换成任何兼容实现(Redux、自研等)。
|
|
19
|
+
*/
|
|
20
|
+
class Dispatcher {
|
|
21
|
+
constructor({ store, effects, maxDepth = 8 } = {}) {
|
|
22
|
+
assertInterface(store, ['apply'], 'store');
|
|
23
|
+
assertInterface(effects, ['run'], 'effects');
|
|
24
|
+
this.store = store;
|
|
25
|
+
this.effects = effects;
|
|
26
|
+
this.maxDepth = maxDepth;
|
|
27
|
+
this.actions = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
register(action) {
|
|
31
|
+
if (!action || !action.name) {
|
|
32
|
+
throw new TypeError('action must have a name');
|
|
33
|
+
}
|
|
34
|
+
if (typeof action.execute !== 'function') {
|
|
35
|
+
throw new TypeError('action.execute must be a function');
|
|
36
|
+
}
|
|
37
|
+
if (this.actions.has(action.name)) {
|
|
38
|
+
throw new Error(`action already registered: ${action.name}`);
|
|
39
|
+
}
|
|
40
|
+
this.actions.set(action.name, action);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 意图分两类:
|
|
46
|
+
* { type: 'xxx', payload } → store.apply
|
|
47
|
+
* { type: '@effect', name, args, then } → effects.run,结果回流到 then 动作
|
|
48
|
+
*/
|
|
49
|
+
async dispatch(name, input, ctx = {}, depth = 0) {
|
|
50
|
+
if (depth > this.maxDepth) {
|
|
51
|
+
throw new Error(`dispatch depth exceeded: ${name}`);
|
|
52
|
+
}
|
|
53
|
+
const action = this.actions.get(name);
|
|
54
|
+
if (!action) throw new Error(`unknown action: ${name}`);
|
|
55
|
+
|
|
56
|
+
action.validate(input);
|
|
57
|
+
|
|
58
|
+
let intents = await action.execute(input, ctx);
|
|
59
|
+
if (intents == null) return;
|
|
60
|
+
if (!Array.isArray(intents)) intents = [intents];
|
|
61
|
+
|
|
62
|
+
for (const intent of intents) {
|
|
63
|
+
if (!intent || typeof intent.type !== 'string') {
|
|
64
|
+
throw new TypeError('intent must have a string type');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (intent.type === EFFECT_INTENT) {
|
|
68
|
+
if (!intent.name) throw new Error('effect intent requires name');
|
|
69
|
+
const observation = await this.effects.run(intent.name, intent.args, ctx);
|
|
70
|
+
if (intent.then) {
|
|
71
|
+
await this.dispatch(intent.then, { observation }, ctx, depth + 1);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
this.store.apply(intent);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { Dispatcher, EFFECT_INTENT, assertInterface };
|
package/package.json
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
2
|
+
"name": "mm_action",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "动作管理模块:提供动作的注册与管理能力",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://gitee.com/qiuwenwu91/mm_action.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mm_mod",
|
|
12
|
+
"mm_infra",
|
|
13
|
+
"action",
|
|
14
|
+
"动作"
|
|
15
|
+
],
|
|
16
|
+
"layer": "infra",
|
|
17
|
+
"group": "common",
|
|
18
|
+
"author": "邱文武",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node test.js"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"mm_corejs": "^1.1.6"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/test.js
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const assert = require('assert');
|
|
4
|
-
const
|
|
4
|
+
const mod = require('./index.js');
|
|
5
5
|
|
|
6
|
+
// 1. 导出结构校验
|
|
7
|
+
assert.strictEqual(typeof mod.Action, 'function', '应导出 Action 类');
|
|
8
|
+
assert.ok(mod.admin, '应导出 admin 实例');
|
|
9
|
+
assert.strictEqual(typeof mod.actionAdmin, 'function', '应导出 actionAdmin 函数');
|
|
10
|
+
|
|
11
|
+
const { Action } = mod;
|
|
12
|
+
|
|
13
|
+
// 2. 实例化与默认配置
|
|
6
14
|
const instance = new Action();
|
|
7
15
|
assert.ok(instance instanceof Action, 'instance 应为 Action 实例');
|
|
8
16
|
assert.deepStrictEqual(instance.config, {}, '默认配置应为空对象');
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
// 3. 传入配置
|
|
19
|
+
const configured = new Action({ type: 'click' });
|
|
20
|
+
assert.deepStrictEqual(configured.config, { type: 'click' }, '应保存传入的配置');
|
|
21
|
+
|
|
22
|
+
// 4. 工厂函数返回 Action 实例
|
|
23
|
+
const scoped = mod.actionAdmin('test');
|
|
24
|
+
assert.ok(scoped instanceof Action, 'actionAdmin 应返回 Action 实例');
|
|
25
|
+
|
|
26
|
+
console.log('mm_action test passed');
|