@variojs/core 0.1.0
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/__tests__/README.md +101 -0
- package/__tests__/errors.test.ts +139 -0
- package/__tests__/expression/cache.test.ts +118 -0
- package/__tests__/expression/compiler.test.ts +111 -0
- package/__tests__/expression/evaluate.test.ts +95 -0
- package/__tests__/expression/parser.test.ts +57 -0
- package/__tests__/expression/whitelist.test.ts +59 -0
- package/__tests__/performance.test.ts +379 -0
- package/__tests__/runtime/create-context.test.ts +78 -0
- package/__tests__/runtime/loop-context-pool.test.ts +74 -0
- package/__tests__/runtime/path.test.ts +128 -0
- package/__tests__/vm/executor-timeout.test.ts +117 -0
- package/__tests__/vm/executor.test.ts +173 -0
- package/__tests__/vm/handlers/array.test.ts +113 -0
- package/__tests__/vm/handlers/call.test.ts +93 -0
- package/dist/errors.d.ts +100 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +132 -0
- package/dist/errors.js.map +1 -0
- package/dist/expression/cache.d.ts +53 -0
- package/dist/expression/cache.d.ts.map +1 -0
- package/dist/expression/cache.js +158 -0
- package/dist/expression/cache.js.map +1 -0
- package/dist/expression/compiler.d.ts +34 -0
- package/dist/expression/compiler.d.ts.map +1 -0
- package/dist/expression/compiler.js +123 -0
- package/dist/expression/compiler.js.map +1 -0
- package/dist/expression/dependencies.d.ts +17 -0
- package/dist/expression/dependencies.d.ts.map +1 -0
- package/dist/expression/dependencies.js +106 -0
- package/dist/expression/dependencies.js.map +1 -0
- package/dist/expression/evaluate.d.ts +22 -0
- package/dist/expression/evaluate.d.ts.map +1 -0
- package/dist/expression/evaluate.js +75 -0
- package/dist/expression/evaluate.js.map +1 -0
- package/dist/expression/evaluator.d.ts +22 -0
- package/dist/expression/evaluator.d.ts.map +1 -0
- package/dist/expression/evaluator.js +506 -0
- package/dist/expression/evaluator.js.map +1 -0
- package/dist/expression/index.d.ts +12 -0
- package/dist/expression/index.d.ts.map +1 -0
- package/dist/expression/index.js +12 -0
- package/dist/expression/index.js.map +1 -0
- package/dist/expression/parser.d.ts +15 -0
- package/dist/expression/parser.d.ts.map +1 -0
- package/dist/expression/parser.js +42 -0
- package/dist/expression/parser.js.map +1 -0
- package/dist/expression/utils.d.ts +46 -0
- package/dist/expression/utils.d.ts.map +1 -0
- package/dist/expression/utils.js +78 -0
- package/dist/expression/utils.js.map +1 -0
- package/dist/expression/whitelist.d.ts +24 -0
- package/dist/expression/whitelist.d.ts.map +1 -0
- package/dist/expression/whitelist.js +198 -0
- package/dist/expression/whitelist.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/context.d.ts +8 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +7 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/create-context.d.ts +50 -0
- package/dist/runtime/create-context.d.ts.map +1 -0
- package/dist/runtime/create-context.js +73 -0
- package/dist/runtime/create-context.js.map +1 -0
- package/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/loop-context-pool.d.ts +58 -0
- package/dist/runtime/loop-context-pool.d.ts.map +1 -0
- package/dist/runtime/loop-context-pool.js +114 -0
- package/dist/runtime/loop-context-pool.js.map +1 -0
- package/dist/runtime/path.d.ts +114 -0
- package/dist/runtime/path.d.ts.map +1 -0
- package/dist/runtime/path.js +302 -0
- package/dist/runtime/path.js.map +1 -0
- package/dist/runtime/proxy.d.ts +18 -0
- package/dist/runtime/proxy.d.ts.map +1 -0
- package/dist/runtime/proxy.js +53 -0
- package/dist/runtime/proxy.js.map +1 -0
- package/dist/runtime/sandbox.d.ts +20 -0
- package/dist/runtime/sandbox.d.ts.map +1 -0
- package/dist/runtime/sandbox.js +32 -0
- package/dist/runtime/sandbox.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/vm/action.d.ts +11 -0
- package/dist/vm/action.d.ts.map +1 -0
- package/dist/vm/action.js +10 -0
- package/dist/vm/action.js.map +1 -0
- package/dist/vm/errors.d.ts +5 -0
- package/dist/vm/errors.d.ts.map +1 -0
- package/dist/vm/errors.js +5 -0
- package/dist/vm/errors.js.map +1 -0
- package/dist/vm/executor.d.ts +35 -0
- package/dist/vm/executor.d.ts.map +1 -0
- package/dist/vm/executor.js +137 -0
- package/dist/vm/executor.js.map +1 -0
- package/dist/vm/handlers/array/pop.d.ts +12 -0
- package/dist/vm/handlers/array/pop.d.ts.map +1 -0
- package/dist/vm/handlers/array/pop.js +28 -0
- package/dist/vm/handlers/array/pop.js.map +1 -0
- package/dist/vm/handlers/array/push.d.ts +13 -0
- package/dist/vm/handlers/array/push.d.ts.map +1 -0
- package/dist/vm/handlers/array/push.js +42 -0
- package/dist/vm/handlers/array/push.js.map +1 -0
- package/dist/vm/handlers/array/shift.d.ts +12 -0
- package/dist/vm/handlers/array/shift.d.ts.map +1 -0
- package/dist/vm/handlers/array/shift.js +28 -0
- package/dist/vm/handlers/array/shift.js.map +1 -0
- package/dist/vm/handlers/array/splice.d.ts +12 -0
- package/dist/vm/handlers/array/splice.d.ts.map +1 -0
- package/dist/vm/handlers/array/splice.js +59 -0
- package/dist/vm/handlers/array/splice.js.map +1 -0
- package/dist/vm/handlers/array/unshift.d.ts +13 -0
- package/dist/vm/handlers/array/unshift.d.ts.map +1 -0
- package/dist/vm/handlers/array/unshift.js +42 -0
- package/dist/vm/handlers/array/unshift.js.map +1 -0
- package/dist/vm/handlers/array/utils.d.ts +10 -0
- package/dist/vm/handlers/array/utils.d.ts.map +1 -0
- package/dist/vm/handlers/array/utils.js +33 -0
- package/dist/vm/handlers/array/utils.js.map +1 -0
- package/dist/vm/handlers/batch.d.ts +12 -0
- package/dist/vm/handlers/batch.d.ts.map +1 -0
- package/dist/vm/handlers/batch.js +40 -0
- package/dist/vm/handlers/batch.js.map +1 -0
- package/dist/vm/handlers/call.d.ts +14 -0
- package/dist/vm/handlers/call.d.ts.map +1 -0
- package/dist/vm/handlers/call.js +65 -0
- package/dist/vm/handlers/call.js.map +1 -0
- package/dist/vm/handlers/emit.d.ts +12 -0
- package/dist/vm/handlers/emit.d.ts.map +1 -0
- package/dist/vm/handlers/emit.js +26 -0
- package/dist/vm/handlers/emit.js.map +1 -0
- package/dist/vm/handlers/if.d.ts +13 -0
- package/dist/vm/handlers/if.d.ts.map +1 -0
- package/dist/vm/handlers/if.js +35 -0
- package/dist/vm/handlers/if.js.map +1 -0
- package/dist/vm/handlers/index.d.ts +11 -0
- package/dist/vm/handlers/index.d.ts.map +1 -0
- package/dist/vm/handlers/index.js +46 -0
- package/dist/vm/handlers/index.js.map +1 -0
- package/dist/vm/handlers/log.d.ts +12 -0
- package/dist/vm/handlers/log.d.ts.map +1 -0
- package/dist/vm/handlers/log.js +41 -0
- package/dist/vm/handlers/log.js.map +1 -0
- package/dist/vm/handlers/loop.d.ts +12 -0
- package/dist/vm/handlers/loop.d.ts.map +1 -0
- package/dist/vm/handlers/loop.js +71 -0
- package/dist/vm/handlers/loop.js.map +1 -0
- package/dist/vm/handlers/navigate.d.ts +12 -0
- package/dist/vm/handlers/navigate.d.ts.map +1 -0
- package/dist/vm/handlers/navigate.js +43 -0
- package/dist/vm/handlers/navigate.js.map +1 -0
- package/dist/vm/handlers/set.d.ts +15 -0
- package/dist/vm/handlers/set.d.ts.map +1 -0
- package/dist/vm/handlers/set.js +30 -0
- package/dist/vm/handlers/set.js.map +1 -0
- package/dist/vm/index.d.ts +8 -0
- package/dist/vm/index.d.ts.map +1 -0
- package/dist/vm/index.js +7 -0
- package/dist/vm/index.js.map +1 -0
- package/package.json +34 -0
- package/src/errors.ts +194 -0
- package/src/expression/README.md +192 -0
- package/src/expression/cache.ts +199 -0
- package/src/expression/compiler.ts +144 -0
- package/src/expression/dependencies.ts +116 -0
- package/src/expression/evaluate.ts +95 -0
- package/src/expression/evaluator.ts +640 -0
- package/src/expression/index.ts +27 -0
- package/src/expression/parser.ts +54 -0
- package/src/expression/utils.ts +89 -0
- package/src/expression/whitelist.ts +224 -0
- package/src/globals.d.ts +10 -0
- package/src/index.ts +72 -0
- package/src/runtime/context.ts +8 -0
- package/src/runtime/create-context.ts +133 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/loop-context-pool.ts +134 -0
- package/src/runtime/path.ts +372 -0
- package/src/runtime/proxy.ts +66 -0
- package/src/runtime/sandbox.ts +43 -0
- package/src/types.ts +177 -0
- package/src/vm/errors.ts +10 -0
- package/src/vm/executor.ts +210 -0
- package/src/vm/handlers/array/pop.ts +47 -0
- package/src/vm/handlers/array/push.ts +68 -0
- package/src/vm/handlers/array/shift.ts +47 -0
- package/src/vm/handlers/array/splice.ts +78 -0
- package/src/vm/handlers/array/unshift.ts +68 -0
- package/src/vm/handlers/array/utils.ts +41 -0
- package/src/vm/handlers/batch.ts +57 -0
- package/src/vm/handlers/call.ts +92 -0
- package/src/vm/handlers/emit.ts +39 -0
- package/src/vm/handlers/if.ts +48 -0
- package/src/vm/handlers/index.ts +52 -0
- package/src/vm/handlers/log.ts +54 -0
- package/src/vm/handlers/loop.ts +102 -0
- package/src/vm/handlers/navigate.ts +64 -0
- package/src/vm/handlers/set.ts +43 -0
- package/src/vm/index.ts +8 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +30 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数组指令工具函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { RuntimeContext } from '@/types.js'
|
|
6
|
+
import { evaluate } from '@/expression/evaluate.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 递归求值对象/数组中的表达式
|
|
10
|
+
* 支持在对象属性和数组元素中使用 {{ expression }} 格式
|
|
11
|
+
*/
|
|
12
|
+
export function evaluateExpressionsRecursively(
|
|
13
|
+
value: any,
|
|
14
|
+
ctx: RuntimeContext
|
|
15
|
+
): any {
|
|
16
|
+
// 字符串:检查是否为表达式
|
|
17
|
+
if (typeof value === 'string') {
|
|
18
|
+
if (value.startsWith('{{') && value.endsWith('}}')) {
|
|
19
|
+
const expr = value.slice(2, -2).trim()
|
|
20
|
+
return evaluate(expr, ctx)
|
|
21
|
+
}
|
|
22
|
+
return value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 数组:递归处理每个元素
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.map(item => evaluateExpressionsRecursively(item, ctx))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 对象:递归处理每个属性值
|
|
31
|
+
if (value && typeof value === 'object') {
|
|
32
|
+
const result: Record<string, any> = {}
|
|
33
|
+
for (const [key, val] of Object.entries(value)) {
|
|
34
|
+
result[key] = evaluateExpressionsRecursively(val, ctx)
|
|
35
|
+
}
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 其他类型:直接返回
|
|
40
|
+
return value
|
|
41
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* batch 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:批量执行动作(保证原子性)
|
|
5
|
+
* 示例:{ "type": "batch", "actions": [...] }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
9
|
+
import { ActionError, BatchError, ErrorCodes } from '@/errors.js'
|
|
10
|
+
import { execute } from '../executor.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 处理 batch 动作
|
|
14
|
+
*/
|
|
15
|
+
export async function handleBatch(
|
|
16
|
+
ctx: RuntimeContext,
|
|
17
|
+
action: Action
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
const { actions } = action
|
|
20
|
+
|
|
21
|
+
if (!actions || !Array.isArray(actions)) {
|
|
22
|
+
throw new ActionError(
|
|
23
|
+
action,
|
|
24
|
+
'batch action requires "actions" parameter (array)',
|
|
25
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
26
|
+
{ metadata: { param: 'actions' } }
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const errors: Array<{ action: Action; error: Error }> = []
|
|
31
|
+
|
|
32
|
+
// 批量执行,收集错误
|
|
33
|
+
for (const act of actions) {
|
|
34
|
+
try {
|
|
35
|
+
await execute([act], ctx)
|
|
36
|
+
} catch (error: unknown) {
|
|
37
|
+
errors.push({
|
|
38
|
+
action: act,
|
|
39
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 如果有错误,抛出 BatchError
|
|
45
|
+
if (errors.length > 0) {
|
|
46
|
+
throw new BatchError(
|
|
47
|
+
errors,
|
|
48
|
+
`${errors.length} actions failed in batch`,
|
|
49
|
+
{
|
|
50
|
+
metadata: {
|
|
51
|
+
failedCount: errors.length,
|
|
52
|
+
totalCount: actions.length
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* call 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:调用 method(通过 $methods)
|
|
5
|
+
* 示例:{ "type": "call", "method": "services.fetchUser", "params": {...}, "resultTo": "user" }
|
|
6
|
+
*
|
|
7
|
+
* 参考文档:action-reference.md - call 动作
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
11
|
+
import { ActionError, ServiceError, ErrorCodes } from '@/errors.js'
|
|
12
|
+
import { evaluate } from '@/expression/evaluate.js'
|
|
13
|
+
import { invalidateCache } from '@/expression/cache.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 处理 call 动作
|
|
17
|
+
*/
|
|
18
|
+
export async function handleCall(
|
|
19
|
+
ctx: RuntimeContext,
|
|
20
|
+
action: Action
|
|
21
|
+
): Promise<unknown> {
|
|
22
|
+
const method = action.method as string | undefined
|
|
23
|
+
const params = (action.params || {}) as Record<string, unknown>
|
|
24
|
+
const resultTo = action.resultTo as string | undefined
|
|
25
|
+
|
|
26
|
+
if (!method || typeof method !== 'string') {
|
|
27
|
+
throw new ActionError(
|
|
28
|
+
action,
|
|
29
|
+
'call action requires "method" parameter',
|
|
30
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
31
|
+
{ metadata: { param: 'method' } }
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 查找方法(通过 $methods)
|
|
36
|
+
const handler = ctx.$methods[method]
|
|
37
|
+
if (!handler) {
|
|
38
|
+
throw new ServiceError(
|
|
39
|
+
method,
|
|
40
|
+
`Method "${method}" not found in $methods`,
|
|
41
|
+
undefined,
|
|
42
|
+
{
|
|
43
|
+
metadata: { method }
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 求值参数(支持表达式)
|
|
49
|
+
const finalParams: Record<string, unknown> = {}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
for (const [key, value] of Object.entries(params)) {
|
|
53
|
+
if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) {
|
|
54
|
+
const expr = value.slice(2, -2).trim()
|
|
55
|
+
finalParams[key] = evaluate(expr, ctx)
|
|
56
|
+
} else {
|
|
57
|
+
finalParams[key] = value
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 调用 method
|
|
62
|
+
const result = await handler(ctx, finalParams)
|
|
63
|
+
|
|
64
|
+
// 如果指定了 resultTo,保存结果到状态
|
|
65
|
+
if (resultTo) {
|
|
66
|
+
ctx._set(resultTo, result)
|
|
67
|
+
invalidateCache(resultTo, ctx)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result
|
|
71
|
+
} catch (error: unknown) {
|
|
72
|
+
// 如果错误已经是 ServiceError,直接抛出
|
|
73
|
+
if (error instanceof ServiceError) {
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
78
|
+
const originalError = error instanceof Error ? error : undefined
|
|
79
|
+
|
|
80
|
+
throw new ServiceError(
|
|
81
|
+
method,
|
|
82
|
+
`Service call failed: ${errorMessage}`,
|
|
83
|
+
originalError,
|
|
84
|
+
{
|
|
85
|
+
metadata: {
|
|
86
|
+
method,
|
|
87
|
+
params: finalParams
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* emit 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:触发事件
|
|
5
|
+
* 示例:{ "type": "emit", "event": "submit", "data": { "userId": 123 } }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
9
|
+
import { ActionError, ErrorCodes } from '@/errors.js'
|
|
10
|
+
import { evaluate } from '@/expression/evaluate.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 处理 emit 动作
|
|
14
|
+
*/
|
|
15
|
+
export async function handleEmit(
|
|
16
|
+
ctx: RuntimeContext,
|
|
17
|
+
action: Action
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
const { event, data } = action
|
|
20
|
+
|
|
21
|
+
if (!event || typeof event !== 'string') {
|
|
22
|
+
throw new ActionError(
|
|
23
|
+
action,
|
|
24
|
+
'emit action requires "event" parameter',
|
|
25
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
26
|
+
{ metadata: { param: 'event' } }
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 求值 data(支持表达式)
|
|
31
|
+
let finalData = data
|
|
32
|
+
if (typeof data === 'string' && data.startsWith('{{') && data.endsWith('}}')) {
|
|
33
|
+
const expr = data.slice(2, -2).trim()
|
|
34
|
+
finalData = evaluate(expr, ctx)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 触发事件
|
|
38
|
+
ctx.$emit(event, finalData)
|
|
39
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* if 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:条件分支
|
|
5
|
+
* 示例:{ "type": "if", "cond": "user.age > 18", "then": [...], "else": [...] }
|
|
6
|
+
* 示例:{ "type": "if", "cond": "{{ counter < 10 }}", "then": [...], "else": [...] }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
10
|
+
import { ActionError, ErrorCodes } from '@/errors.js'
|
|
11
|
+
import { evaluate, extractExpression } from '@/expression/index.js'
|
|
12
|
+
import { execute } from '../executor.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 处理 if 动作
|
|
16
|
+
*/
|
|
17
|
+
export async function handleIf(
|
|
18
|
+
ctx: RuntimeContext,
|
|
19
|
+
action: Action
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const { cond, then, else: elseBranch } = action
|
|
22
|
+
|
|
23
|
+
if (!cond || typeof cond !== 'string') {
|
|
24
|
+
throw new ActionError(
|
|
25
|
+
action,
|
|
26
|
+
'if action requires "cond" parameter',
|
|
27
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
28
|
+
{ metadata: { param: 'cond' } }
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 提取表达式(支持 {{ }} 格式)
|
|
33
|
+
const condExpr = extractExpression(cond)
|
|
34
|
+
|
|
35
|
+
// 求值条件表达式
|
|
36
|
+
const condition = evaluate(condExpr, ctx)
|
|
37
|
+
|
|
38
|
+
// 执行对应的分支
|
|
39
|
+
if (condition) {
|
|
40
|
+
if (then && Array.isArray(then)) {
|
|
41
|
+
await execute(then, ctx)
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
if (elseBranch && Array.isArray(elseBranch)) {
|
|
45
|
+
await execute(elseBranch, ctx)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 所有内置动作统一注册到 ctx.$methods
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RuntimeContext, MethodHandler } from '@/types.js'
|
|
8
|
+
import { handleSet } from './set.js'
|
|
9
|
+
import { handleEmit } from './emit.js'
|
|
10
|
+
import { handleIf } from './if.js'
|
|
11
|
+
import { handleLoop } from './loop.js'
|
|
12
|
+
import { handleCall } from './call.js'
|
|
13
|
+
import { handleBatch } from './batch.js'
|
|
14
|
+
import { handleNavigate } from './navigate.js'
|
|
15
|
+
import { handleLog } from './log.js'
|
|
16
|
+
import { handlePush } from './array/push.js'
|
|
17
|
+
import { handlePop } from './array/pop.js'
|
|
18
|
+
import { handleShift } from './array/shift.js'
|
|
19
|
+
import { handleUnshift } from './array/unshift.js'
|
|
20
|
+
import { handleSplice } from './array/splice.js'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 注册所有内置动作到 $methods
|
|
24
|
+
*/
|
|
25
|
+
export function registerBuiltinMethods(ctx: RuntimeContext): void {
|
|
26
|
+
// 动作处理器作为特殊的方法注册,参数是 Action
|
|
27
|
+
ctx.$methods = {
|
|
28
|
+
// 原子动作
|
|
29
|
+
'set': handleSet as MethodHandler,
|
|
30
|
+
'emit': handleEmit as MethodHandler,
|
|
31
|
+
'navigate': handleNavigate as MethodHandler,
|
|
32
|
+
'log': handleLog as MethodHandler,
|
|
33
|
+
|
|
34
|
+
// 控制流动作
|
|
35
|
+
'if': handleIf as MethodHandler,
|
|
36
|
+
'loop': handleLoop as MethodHandler,
|
|
37
|
+
|
|
38
|
+
// 复合动作
|
|
39
|
+
'call': handleCall as MethodHandler,
|
|
40
|
+
'batch': handleBatch as MethodHandler,
|
|
41
|
+
|
|
42
|
+
// 数组操作动作
|
|
43
|
+
'push': handlePush as MethodHandler,
|
|
44
|
+
'pop': handlePop as MethodHandler,
|
|
45
|
+
'shift': handleShift as MethodHandler,
|
|
46
|
+
'unshift': handleUnshift as MethodHandler,
|
|
47
|
+
'splice': handleSplice as MethodHandler,
|
|
48
|
+
|
|
49
|
+
// 保留已有的方法(允许覆盖或扩展)
|
|
50
|
+
...ctx.$methods
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* log 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:调试输出
|
|
5
|
+
* 示例:{ "type": "log", "level": "info", "message": "User logged in" }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
9
|
+
import { ActionError, ErrorCodes } from '@/errors.js'
|
|
10
|
+
import { evaluate } from '@/expression/evaluate.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 处理 log 动作
|
|
14
|
+
*/
|
|
15
|
+
export async function handleLog(
|
|
16
|
+
ctx: RuntimeContext,
|
|
17
|
+
action: Action
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
const { level = 'info', message } = action
|
|
20
|
+
|
|
21
|
+
if (!message) {
|
|
22
|
+
throw new ActionError(
|
|
23
|
+
action,
|
|
24
|
+
'log action requires "message" parameter',
|
|
25
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
26
|
+
{ metadata: { param: 'message' } }
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 求值 message(支持表达式)
|
|
31
|
+
let finalMessage: unknown = message
|
|
32
|
+
if (typeof message === 'string' && message.startsWith('{{') && message.endsWith('}}')) {
|
|
33
|
+
const expr = message.slice(2, -2).trim()
|
|
34
|
+
finalMessage = evaluate(expr, ctx)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 输出日志(将 finalMessage 转换为字符串)
|
|
38
|
+
const logLevel = String(level).toLowerCase()
|
|
39
|
+
const messageStr = String(finalMessage)
|
|
40
|
+
if (typeof console !== 'undefined') {
|
|
41
|
+
switch (logLevel) {
|
|
42
|
+
case 'error':
|
|
43
|
+
console.error('[Vario]', messageStr)
|
|
44
|
+
break
|
|
45
|
+
case 'warn':
|
|
46
|
+
console.warn('[Vario]', messageStr)
|
|
47
|
+
break
|
|
48
|
+
case 'info':
|
|
49
|
+
default:
|
|
50
|
+
console.log('[Vario]', messageStr)
|
|
51
|
+
break
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loop 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:循环执行
|
|
5
|
+
* 示例:{ "type": "loop", "var": "item", "in": "items", "body": [...] }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
9
|
+
import { ActionError, ErrorCodes } from '@/errors.js'
|
|
10
|
+
import { evaluate } from '@/expression/evaluate.js'
|
|
11
|
+
import { invalidateCache } from '@/expression/cache.js'
|
|
12
|
+
import { execute } from '../executor.js'
|
|
13
|
+
import { createLoopContext, releaseLoopContext } from '@/runtime/loop-context-pool.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 处理 loop 动作
|
|
17
|
+
*/
|
|
18
|
+
export async function handleLoop(
|
|
19
|
+
ctx: RuntimeContext,
|
|
20
|
+
action: Action
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const { var: varName, in: inExpr, body } = action
|
|
23
|
+
|
|
24
|
+
if (!varName || typeof varName !== 'string') {
|
|
25
|
+
throw new ActionError(
|
|
26
|
+
action,
|
|
27
|
+
'loop action requires "var" parameter',
|
|
28
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
29
|
+
{ metadata: { param: 'var' } }
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!inExpr || typeof inExpr !== 'string') {
|
|
34
|
+
throw new ActionError(
|
|
35
|
+
action,
|
|
36
|
+
'loop action requires "in" parameter',
|
|
37
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
38
|
+
{ metadata: { param: 'in' } }
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!body || !Array.isArray(body)) {
|
|
43
|
+
throw new ActionError(
|
|
44
|
+
action,
|
|
45
|
+
'loop action requires "body" parameter (array of actions)',
|
|
46
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
47
|
+
{ metadata: { param: 'body' } }
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 求值 in 表达式,获取要遍历的数组或对象
|
|
52
|
+
const iterable = evaluate(inExpr, ctx)
|
|
53
|
+
|
|
54
|
+
if (iterable == null) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 遍历数组
|
|
59
|
+
if (Array.isArray(iterable)) {
|
|
60
|
+
for (let i = 0; i < iterable.length; i++) {
|
|
61
|
+
// 使用对象池创建循环上下文
|
|
62
|
+
const loopCtx = createLoopContext(ctx, iterable[i], i)
|
|
63
|
+
loopCtx[varName] = iterable[i]
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
// 触发缓存失效,确保表达式重新求值
|
|
67
|
+
invalidateCache(varName, loopCtx)
|
|
68
|
+
await execute(body, loopCtx)
|
|
69
|
+
} finally {
|
|
70
|
+
// 释放循环上下文回对象池
|
|
71
|
+
releaseLoopContext(loopCtx)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// 遍历对象
|
|
76
|
+
else if (typeof iterable === 'object' && iterable !== null) {
|
|
77
|
+
const entries = Object.entries(iterable)
|
|
78
|
+
for (let i = 0; i < entries.length; i++) {
|
|
79
|
+
const [, value] = entries[i]
|
|
80
|
+
// 使用对象池创建循环上下文
|
|
81
|
+
const loopCtx = createLoopContext(ctx, value, i)
|
|
82
|
+
loopCtx[varName] = value
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// 触发缓存失效,确保表达式重新求值
|
|
86
|
+
invalidateCache(varName, loopCtx)
|
|
87
|
+
await execute(body, loopCtx)
|
|
88
|
+
} finally {
|
|
89
|
+
// 释放循环上下文回对象池
|
|
90
|
+
releaseLoopContext(loopCtx)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw new ActionError(
|
|
96
|
+
action,
|
|
97
|
+
`loop "in" expression must evaluate to an array or object, got ${typeof iterable}`,
|
|
98
|
+
ErrorCodes.ACTION_INVALID_PARAM,
|
|
99
|
+
{ metadata: { param: 'in', actualType: typeof iterable } }
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* navigate 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:路由导航
|
|
5
|
+
* 示例:{ "type": "navigate", "to": "/users/123" }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
9
|
+
import { ActionError, ErrorCodes } from '@/errors.js'
|
|
10
|
+
import { evaluate } from '@/expression/evaluate.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 处理 navigate 动作
|
|
14
|
+
*/
|
|
15
|
+
export async function handleNavigate(
|
|
16
|
+
ctx: RuntimeContext,
|
|
17
|
+
action: Action
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
const { to } = action
|
|
20
|
+
|
|
21
|
+
if (!to || typeof to !== 'string') {
|
|
22
|
+
throw new ActionError(
|
|
23
|
+
action,
|
|
24
|
+
'navigate action requires "to" parameter',
|
|
25
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
26
|
+
{ metadata: { param: 'to' } }
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 求值 to(支持表达式)
|
|
31
|
+
let finalTo: string = to
|
|
32
|
+
if (to.startsWith('{{') && to.endsWith('}}')) {
|
|
33
|
+
const expr = to.slice(2, -2).trim()
|
|
34
|
+
const result = evaluate(expr, ctx)
|
|
35
|
+
if (typeof result !== 'string') {
|
|
36
|
+
throw new ActionError(
|
|
37
|
+
action,
|
|
38
|
+
`navigate "to" expression must evaluate to a string, got ${typeof result}`,
|
|
39
|
+
ErrorCodes.ACTION_INVALID_PARAM,
|
|
40
|
+
{ metadata: { param: 'to', actualType: typeof result } }
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
finalTo = result
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 调用路由导航方法(如果已注册)
|
|
47
|
+
const navigateHandler = ctx.$methods['navigate'] || ctx.$methods['$navigate']
|
|
48
|
+
if (navigateHandler) {
|
|
49
|
+
await navigateHandler(ctx, { to: finalTo })
|
|
50
|
+
} else {
|
|
51
|
+
// 如果没有注册导航方法,使用默认行为(浏览器导航)
|
|
52
|
+
// 使用类型安全的 window 访问
|
|
53
|
+
if (typeof window !== 'undefined' && (window as { location?: { href: string } }).location) {
|
|
54
|
+
((window as unknown) as { location: { href: string } }).location.href = finalTo
|
|
55
|
+
} else {
|
|
56
|
+
throw new ActionError(
|
|
57
|
+
action,
|
|
58
|
+
'navigate method not registered and window.location is not available',
|
|
59
|
+
ErrorCodes.ACTION_EXECUTION_ERROR,
|
|
60
|
+
{ metadata: { reason: 'navigate_not_available' } }
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* set 动作处理器
|
|
3
|
+
*
|
|
4
|
+
* 功能:修改状态
|
|
5
|
+
* 示例:{ "type": "set", "path": "user.name", "value": "张三" }
|
|
6
|
+
*
|
|
7
|
+
* 注意:缓存失效通过 RuntimeContext 的 onStateChange 钩子处理
|
|
8
|
+
* 框架集成层应在创建上下文时注册该钩子
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { RuntimeContext, Action } from '@/types.js'
|
|
12
|
+
import { ActionError, ErrorCodes } from '@/errors.js'
|
|
13
|
+
import { evaluate } from '@/expression/evaluate.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 处理 set 动作
|
|
17
|
+
*/
|
|
18
|
+
export async function handleSet(
|
|
19
|
+
ctx: RuntimeContext,
|
|
20
|
+
action: Action
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const { path, value } = action
|
|
23
|
+
|
|
24
|
+
if (!path || typeof path !== 'string') {
|
|
25
|
+
throw new ActionError(
|
|
26
|
+
action,
|
|
27
|
+
'set action requires "path" parameter',
|
|
28
|
+
ErrorCodes.ACTION_MISSING_PARAM,
|
|
29
|
+
{ metadata: { param: 'path' } }
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 求值 value(支持表达式)
|
|
34
|
+
let finalValue = value
|
|
35
|
+
if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) {
|
|
36
|
+
// 表达式插值:{{ user.age + 1 }}
|
|
37
|
+
const expr = value.slice(2, -2).trim()
|
|
38
|
+
finalValue = evaluate(expr, ctx)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 设置状态(缓存失效通过 onStateChange 钩子自动处理)
|
|
42
|
+
ctx._set(path, finalValue)
|
|
43
|
+
}
|
package/src/vm/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"baseUrl": "./src",
|
|
7
|
+
"paths": {
|
|
8
|
+
"@/*": ["./*"]
|
|
9
|
+
},
|
|
10
|
+
"types": ["node"],
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": false,
|
|
13
|
+
"preserveSymlinks": false
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
17
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import { resolve } from 'path'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: 'node',
|
|
8
|
+
include: ['__tests__/**/*.test.ts'],
|
|
9
|
+
coverage: {
|
|
10
|
+
provider: 'v8',
|
|
11
|
+
reporter: ['text', 'json', 'html'],
|
|
12
|
+
exclude: ['**/*.test.ts', '**/__tests__/**', '**/test/**']
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
resolve: {
|
|
16
|
+
alias: {
|
|
17
|
+
'@': resolve(__dirname, './src')
|
|
18
|
+
},
|
|
19
|
+
// 确保相对路径导入能正确解析(支持 .ts 文件)
|
|
20
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json']
|
|
21
|
+
},
|
|
22
|
+
// 确保 TypeScript 文件正确解析
|
|
23
|
+
esbuild: {
|
|
24
|
+
target: 'node18'
|
|
25
|
+
},
|
|
26
|
+
// 配置 Vite 的依赖优化
|
|
27
|
+
optimizeDeps: {
|
|
28
|
+
include: ['@babel/parser', '@babel/types']
|
|
29
|
+
}
|
|
30
|
+
})
|