@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
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@variojs/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vario Core Runtime - Instruction VM, Expression System, Runtime Context",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"postbuild": "tsc-alias",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@babel/parser": "^7.23.6",
|
|
25
|
+
"@babel/types": "^7.23.6"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^5.3.3",
|
|
29
|
+
"vitest": "^1.2.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vario 错误处理体系
|
|
3
|
+
*
|
|
4
|
+
* 统一的错误基类和错误码系统
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Action } from './types.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 错误上下文信息
|
|
11
|
+
*/
|
|
12
|
+
export interface ErrorContext {
|
|
13
|
+
/** Schema 路径(如 "events.click[0]") */
|
|
14
|
+
schemaPath?: string
|
|
15
|
+
/** 表达式字符串 */
|
|
16
|
+
expression?: string
|
|
17
|
+
/** 动作对象 */
|
|
18
|
+
action?: Action
|
|
19
|
+
/** 调用栈(简化版) */
|
|
20
|
+
stack?: string[]
|
|
21
|
+
/** 额外上下文信息 */
|
|
22
|
+
metadata?: Record<string, unknown>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Vario 错误基类
|
|
27
|
+
*
|
|
28
|
+
* 所有 Vario 相关错误都应继承此类
|
|
29
|
+
*/
|
|
30
|
+
export class VarioError extends Error {
|
|
31
|
+
/** 错误码 */
|
|
32
|
+
public readonly code: string
|
|
33
|
+
/** 错误上下文 */
|
|
34
|
+
public readonly context: ErrorContext
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
message: string,
|
|
38
|
+
code: string,
|
|
39
|
+
context: ErrorContext = {}
|
|
40
|
+
) {
|
|
41
|
+
super(message)
|
|
42
|
+
this.name = 'VarioError'
|
|
43
|
+
this.code = code
|
|
44
|
+
this.context = context
|
|
45
|
+
|
|
46
|
+
// 确保 stack 属性存在
|
|
47
|
+
if (Error.captureStackTrace) {
|
|
48
|
+
Error.captureStackTrace(this, VarioError)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 获取友好的错误消息
|
|
54
|
+
*/
|
|
55
|
+
getFriendlyMessage(): string {
|
|
56
|
+
const parts: string[] = [this.message]
|
|
57
|
+
|
|
58
|
+
if (this.context.schemaPath) {
|
|
59
|
+
parts.push(`\n Schema 路径: ${this.context.schemaPath}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.context.expression) {
|
|
63
|
+
parts.push(`\n 表达式: ${this.context.expression}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (this.context.action) {
|
|
67
|
+
parts.push(`\n 动作类型: ${this.context.action.type}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return parts.join('')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 转换为 JSON(用于序列化)
|
|
75
|
+
*/
|
|
76
|
+
toJSON(): Record<string, unknown> {
|
|
77
|
+
return {
|
|
78
|
+
name: this.name,
|
|
79
|
+
message: this.message,
|
|
80
|
+
code: this.code,
|
|
81
|
+
context: this.context,
|
|
82
|
+
stack: this.stack
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 动作执行错误
|
|
89
|
+
*/
|
|
90
|
+
export class ActionError extends VarioError {
|
|
91
|
+
constructor(
|
|
92
|
+
action: Action,
|
|
93
|
+
message: string,
|
|
94
|
+
code: string = 'ACTION_ERROR',
|
|
95
|
+
context: Omit<ErrorContext, 'action'> = {}
|
|
96
|
+
) {
|
|
97
|
+
super(message, code, {
|
|
98
|
+
...context,
|
|
99
|
+
action
|
|
100
|
+
})
|
|
101
|
+
this.name = 'ActionError'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 表达式求值错误
|
|
107
|
+
*/
|
|
108
|
+
export class ExpressionError extends VarioError {
|
|
109
|
+
constructor(
|
|
110
|
+
expression: string,
|
|
111
|
+
message: string,
|
|
112
|
+
code: string = 'EXPRESSION_ERROR',
|
|
113
|
+
context: Omit<ErrorContext, 'expression'> = {}
|
|
114
|
+
) {
|
|
115
|
+
super(message, code, {
|
|
116
|
+
...context,
|
|
117
|
+
expression
|
|
118
|
+
})
|
|
119
|
+
this.name = 'ExpressionError'
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 服务调用错误
|
|
125
|
+
*/
|
|
126
|
+
export class ServiceError extends VarioError {
|
|
127
|
+
public readonly service: string
|
|
128
|
+
public readonly originalError?: Error
|
|
129
|
+
|
|
130
|
+
constructor(
|
|
131
|
+
service: string,
|
|
132
|
+
message: string,
|
|
133
|
+
originalError?: Error,
|
|
134
|
+
context: ErrorContext = {}
|
|
135
|
+
) {
|
|
136
|
+
super(message, 'SERVICE_ERROR', context)
|
|
137
|
+
this.name = 'ServiceError'
|
|
138
|
+
this.service = service
|
|
139
|
+
this.originalError = originalError
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 批量执行错误
|
|
145
|
+
*/
|
|
146
|
+
export class BatchError extends VarioError {
|
|
147
|
+
public readonly failedActions: Array<{ action: Action; error: Error }>
|
|
148
|
+
|
|
149
|
+
constructor(
|
|
150
|
+
failedActions: Array<{ action: Action; error: Error }>,
|
|
151
|
+
message: string,
|
|
152
|
+
context: ErrorContext = {}
|
|
153
|
+
) {
|
|
154
|
+
super(message, 'BATCH_ERROR', context)
|
|
155
|
+
this.name = 'BatchError'
|
|
156
|
+
this.failedActions = failedActions
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 错误码定义
|
|
162
|
+
*/
|
|
163
|
+
export const ErrorCodes = {
|
|
164
|
+
// 动作相关错误
|
|
165
|
+
ACTION_UNKNOWN_TYPE: 'ACTION_UNKNOWN_TYPE',
|
|
166
|
+
ACTION_EXECUTION_ERROR: 'ACTION_EXECUTION_ERROR',
|
|
167
|
+
ACTION_ABORTED: 'ACTION_ABORTED',
|
|
168
|
+
ACTION_TIMEOUT: 'ACTION_TIMEOUT',
|
|
169
|
+
ACTION_MAX_STEPS_EXCEEDED: 'ACTION_MAX_STEPS_EXCEEDED',
|
|
170
|
+
ACTION_MISSING_PARAM: 'ACTION_MISSING_PARAM',
|
|
171
|
+
ACTION_INVALID_PARAM: 'ACTION_INVALID_PARAM',
|
|
172
|
+
|
|
173
|
+
// 表达式相关错误
|
|
174
|
+
EXPRESSION_PARSE_ERROR: 'EXPRESSION_PARSE_ERROR',
|
|
175
|
+
EXPRESSION_VALIDATION_ERROR: 'EXPRESSION_VALIDATION_ERROR',
|
|
176
|
+
EXPRESSION_EVALUATION_ERROR: 'EXPRESSION_EVALUATION_ERROR',
|
|
177
|
+
EXPRESSION_TIMEOUT: 'EXPRESSION_TIMEOUT',
|
|
178
|
+
EXPRESSION_MAX_STEPS_EXCEEDED: 'EXPRESSION_MAX_STEPS_EXCEEDED',
|
|
179
|
+
EXPRESSION_UNSAFE_ACCESS: 'EXPRESSION_UNSAFE_ACCESS',
|
|
180
|
+
EXPRESSION_FUNCTION_NOT_WHITELISTED: 'EXPRESSION_FUNCTION_NOT_WHITELISTED',
|
|
181
|
+
|
|
182
|
+
// 服务相关错误
|
|
183
|
+
SERVICE_NOT_FOUND: 'SERVICE_NOT_FOUND',
|
|
184
|
+
SERVICE_CALL_ERROR: 'SERVICE_CALL_ERROR',
|
|
185
|
+
|
|
186
|
+
// 批量执行错误
|
|
187
|
+
BATCH_ERROR: 'BATCH_ERROR',
|
|
188
|
+
|
|
189
|
+
// Schema 相关错误
|
|
190
|
+
SCHEMA_VALIDATION_ERROR: 'SCHEMA_VALIDATION_ERROR',
|
|
191
|
+
SCHEMA_INVALID_ACTION: 'SCHEMA_INVALID_ACTION',
|
|
192
|
+
} as const
|
|
193
|
+
|
|
194
|
+
export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Vario 表达式系统
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
Vario 表达式系统是一个安全、强大的表达式求值引擎,支持复杂的条件判断、数据访问和计算。
|
|
6
|
+
|
|
7
|
+
## 支持的语法
|
|
8
|
+
|
|
9
|
+
### 1. 基础表达式
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// 变量访问
|
|
13
|
+
showContent
|
|
14
|
+
userRole
|
|
15
|
+
count
|
|
16
|
+
|
|
17
|
+
// 字面量
|
|
18
|
+
123
|
|
19
|
+
"hello"
|
|
20
|
+
true
|
|
21
|
+
false
|
|
22
|
+
null
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. 成员访问
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// 对象属性
|
|
29
|
+
user.name
|
|
30
|
+
user.profile.email
|
|
31
|
+
|
|
32
|
+
// 数组访问
|
|
33
|
+
todos[0]
|
|
34
|
+
items[index]
|
|
35
|
+
|
|
36
|
+
// 可选链(安全访问)
|
|
37
|
+
user?.profile?.name
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 3. 二元运算
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// 算术运算
|
|
44
|
+
count + 1
|
|
45
|
+
price * quantity
|
|
46
|
+
total / items.length
|
|
47
|
+
|
|
48
|
+
// 比较运算
|
|
49
|
+
count > 10
|
|
50
|
+
userRole === "admin"
|
|
51
|
+
age >= 18
|
|
52
|
+
name !== ""
|
|
53
|
+
|
|
54
|
+
// 逻辑运算
|
|
55
|
+
showContent && userRole === "admin"
|
|
56
|
+
count > 0 || isActive
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4. 逻辑表达式
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// AND
|
|
63
|
+
userRole === "admin" && showContent
|
|
64
|
+
count > 10 && items.length > 0
|
|
65
|
+
|
|
66
|
+
// OR
|
|
67
|
+
userRole === "admin" || userRole === "user"
|
|
68
|
+
isActive || isEnabled
|
|
69
|
+
|
|
70
|
+
// 空值合并
|
|
71
|
+
name ?? "Guest"
|
|
72
|
+
value ?? defaultValue
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 5. 三元表达式
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// 条件表达式
|
|
79
|
+
count > 10 ? "high" : "low"
|
|
80
|
+
userRole === "admin" ? "Admin" : "User"
|
|
81
|
+
showContent ? "visible" : "hidden"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 6. 函数调用(白名单)
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// 数组方法
|
|
88
|
+
Array.isArray(items)
|
|
89
|
+
todos.length
|
|
90
|
+
items.filter(item => item.active).length
|
|
91
|
+
|
|
92
|
+
// 数学函数
|
|
93
|
+
Math.max(a, b)
|
|
94
|
+
Math.min(x, y)
|
|
95
|
+
Math.round(value)
|
|
96
|
+
|
|
97
|
+
// 字符串方法(通过属性访问)
|
|
98
|
+
name.length
|
|
99
|
+
message.toUpperCase()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 7. 复杂条件示例
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// 多条件组合
|
|
106
|
+
userRole === "admin" && showContent && count > 10
|
|
107
|
+
|
|
108
|
+
// 嵌套条件
|
|
109
|
+
(userRole === "admin" || userRole === "user") && isActive
|
|
110
|
+
|
|
111
|
+
// 数组操作
|
|
112
|
+
todos.length > 0 && todos.filter(t => t.completed).length > 0
|
|
113
|
+
|
|
114
|
+
// 对象属性检查
|
|
115
|
+
user && user.profile && user.profile.email
|
|
116
|
+
user?.profile?.email // 使用可选链更简洁
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 在 Schema 中使用
|
|
120
|
+
|
|
121
|
+
### cond 条件渲染
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"type": "ElAlert",
|
|
126
|
+
"cond": "{{ userRole === 'admin' && showContent }}",
|
|
127
|
+
"props": { "type": "success" },
|
|
128
|
+
"children": "Admin content"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### show 可见性控制
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"type": "ElCard",
|
|
137
|
+
"show": "{{ showContent && items.length > 0 }}",
|
|
138
|
+
"children": "Content"
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### props 中的表达式
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"type": "ElInput",
|
|
147
|
+
"props": {
|
|
148
|
+
"placeholder": "{{ userRole === 'admin' ? 'Admin input' : 'User input' }}",
|
|
149
|
+
"disabled": "{{ !isActive }}"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 循环中的表达式
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"type": "div",
|
|
159
|
+
"loop": {
|
|
160
|
+
"items": "{{ todos.filter(t => !t.completed) }}"
|
|
161
|
+
},
|
|
162
|
+
"children": "{{ $record.text }}"
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 安全特性
|
|
167
|
+
|
|
168
|
+
1. **白名单验证**:只允许安全的 AST 节点类型
|
|
169
|
+
2. **禁止危险操作**:不允许赋值、函数定义、this 访问等
|
|
170
|
+
3. **执行限制**:最大步数和超时限制,防止无限循环
|
|
171
|
+
4. **沙箱隔离**:禁止访问全局对象和危险属性
|
|
172
|
+
|
|
173
|
+
## 性能优化
|
|
174
|
+
|
|
175
|
+
1. **表达式缓存**:相同表达式的求值结果会被缓存
|
|
176
|
+
2. **依赖追踪**:自动追踪表达式依赖的状态路径
|
|
177
|
+
3. **缓存失效**:状态更新时自动失效相关缓存
|
|
178
|
+
|
|
179
|
+
## 限制
|
|
180
|
+
|
|
181
|
+
1. **不支持赋值**:`a = b` 不允许
|
|
182
|
+
2. **不支持函数定义**:`() => {}` 不允许
|
|
183
|
+
3. **不支持 this**:`this.value` 不允许
|
|
184
|
+
4. **不支持 new**:`new Date()` 不允许(但可以使用 Date.now())
|
|
185
|
+
5. **函数调用受限**:只能调用白名单中的函数
|
|
186
|
+
|
|
187
|
+
## 最佳实践
|
|
188
|
+
|
|
189
|
+
1. **使用可选链**:`user?.profile?.name` 比 `user && user.profile && user.profile.name` 更简洁
|
|
190
|
+
2. **简化条件**:将复杂条件拆分为多个简单条件
|
|
191
|
+
3. **利用缓存**:相同表达式会自动缓存,无需担心性能
|
|
192
|
+
4. **类型安全**:表达式结果类型在运行时确定,使用时注意类型检查
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式缓存系统
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 每个 RuntimeContext 独立缓存(WeakMap 关联)
|
|
6
|
+
* - LRU 淘汰策略
|
|
7
|
+
* - 通配符依赖匹配
|
|
8
|
+
* - 缓存失效机制
|
|
9
|
+
*
|
|
10
|
+
* 设计原则:
|
|
11
|
+
* - 惰性求值:只在需要时计算
|
|
12
|
+
* - 依赖追踪:精确的缓存失效
|
|
13
|
+
* - 内存友好:WeakMap + LRU 淘汰
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { RuntimeContext, ExpressionCache } from '../types.js'
|
|
17
|
+
import { matchPath } from '../runtime/path.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 每个上下文独立缓存
|
|
21
|
+
* 使用 RuntimeContext 作为键(WeakMap 自动回收)
|
|
22
|
+
*/
|
|
23
|
+
const cacheMap = new WeakMap<RuntimeContext<Record<string, unknown>>, Map<string, ExpressionCache>>()
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 缓存配置
|
|
27
|
+
*/
|
|
28
|
+
const CACHE_CONFIG = {
|
|
29
|
+
/** 最大缓存条目数 */
|
|
30
|
+
maxSize: 100,
|
|
31
|
+
/** 缓存有效期(毫秒),0 表示不过期 */
|
|
32
|
+
ttl: 0
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 获取上下文的缓存 Map
|
|
37
|
+
*/
|
|
38
|
+
function getCache(ctx: RuntimeContext): Map<string, ExpressionCache> {
|
|
39
|
+
let cache = cacheMap.get(ctx as RuntimeContext<Record<string, unknown>>)
|
|
40
|
+
if (!cache) {
|
|
41
|
+
cache = new Map()
|
|
42
|
+
cacheMap.set(ctx as RuntimeContext<Record<string, unknown>>, cache)
|
|
43
|
+
}
|
|
44
|
+
return cache
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 检查缓存条目是否有效
|
|
49
|
+
*/
|
|
50
|
+
function isCacheValid(
|
|
51
|
+
entry: ExpressionCache,
|
|
52
|
+
ctx: RuntimeContext
|
|
53
|
+
): boolean {
|
|
54
|
+
// TTL 检查
|
|
55
|
+
if (CACHE_CONFIG.ttl > 0 && Date.now() - entry.timestamp > CACHE_CONFIG.ttl) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 依赖检查:所有依赖的值必须存在
|
|
60
|
+
return entry.dependencies.every(dep => {
|
|
61
|
+
if (dep.includes('*')) {
|
|
62
|
+
// 通配符依赖:检查父路径是否存在
|
|
63
|
+
const parentPath = dep.split('.*')[0]
|
|
64
|
+
return ctx._get(parentPath) != null
|
|
65
|
+
}
|
|
66
|
+
// 具体路径:检查值是否存在
|
|
67
|
+
return ctx._get(dep) !== undefined
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* LRU 淘汰:删除最旧的缓存条目
|
|
73
|
+
*/
|
|
74
|
+
function evictOldest(cache: Map<string, ExpressionCache>): void {
|
|
75
|
+
let oldestKey: string | null = null
|
|
76
|
+
let oldestTime = Infinity
|
|
77
|
+
|
|
78
|
+
for (const [key, entry] of cache.entries()) {
|
|
79
|
+
if (entry.timestamp < oldestTime) {
|
|
80
|
+
oldestTime = entry.timestamp
|
|
81
|
+
oldestKey = key
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (oldestKey) {
|
|
86
|
+
cache.delete(oldestKey)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 获取缓存的表达式结果
|
|
92
|
+
*
|
|
93
|
+
* @param expr 表达式字符串
|
|
94
|
+
* @param ctx 运行时上下文
|
|
95
|
+
* @returns 缓存的结果,无缓存或已失效返回 null
|
|
96
|
+
*/
|
|
97
|
+
export function getCachedExpression(
|
|
98
|
+
expr: string,
|
|
99
|
+
ctx: RuntimeContext
|
|
100
|
+
): unknown | null {
|
|
101
|
+
const cache = getCache(ctx)
|
|
102
|
+
const entry = cache.get(expr)
|
|
103
|
+
|
|
104
|
+
if (!entry) {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!isCacheValid(entry, ctx)) {
|
|
109
|
+
cache.delete(expr)
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 更新时间戳(LRU)
|
|
114
|
+
entry.timestamp = Date.now()
|
|
115
|
+
return entry.result
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 设置表达式缓存
|
|
120
|
+
*
|
|
121
|
+
* @param expr 表达式字符串
|
|
122
|
+
* @param result 求值结果
|
|
123
|
+
* @param dependencies 依赖的状态路径
|
|
124
|
+
* @param ctx 运行时上下文
|
|
125
|
+
*/
|
|
126
|
+
export function setCachedExpression(
|
|
127
|
+
expr: string,
|
|
128
|
+
result: unknown,
|
|
129
|
+
dependencies: string[],
|
|
130
|
+
ctx: RuntimeContext
|
|
131
|
+
): void {
|
|
132
|
+
const cache = getCache(ctx)
|
|
133
|
+
|
|
134
|
+
// LRU 淘汰
|
|
135
|
+
if (cache.size >= CACHE_CONFIG.maxSize) {
|
|
136
|
+
evictOldest(cache)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
cache.set(expr, {
|
|
140
|
+
expr,
|
|
141
|
+
result,
|
|
142
|
+
dependencies,
|
|
143
|
+
timestamp: Date.now()
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 使缓存失效
|
|
149
|
+
*
|
|
150
|
+
* 当状态变化时调用,精确删除依赖该状态的缓存
|
|
151
|
+
*
|
|
152
|
+
* @param changedPath 变化的状态路径
|
|
153
|
+
* @param ctx 运行时上下文
|
|
154
|
+
*/
|
|
155
|
+
export function invalidateCache(
|
|
156
|
+
changedPath: string,
|
|
157
|
+
ctx: RuntimeContext
|
|
158
|
+
): void {
|
|
159
|
+
const cache = getCache(ctx)
|
|
160
|
+
const toDelete: string[] = []
|
|
161
|
+
|
|
162
|
+
for (const [expr, entry] of cache.entries()) {
|
|
163
|
+
// 检查是否有依赖被影响
|
|
164
|
+
const isAffected = entry.dependencies.some(dep =>
|
|
165
|
+
matchPath(dep, changedPath) || matchPath(changedPath, dep)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if (isAffected) {
|
|
169
|
+
toDelete.push(expr)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 批量删除
|
|
174
|
+
for (const expr of toDelete) {
|
|
175
|
+
cache.delete(expr)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 清除指定上下文的所有缓存
|
|
181
|
+
*/
|
|
182
|
+
export function clearCache(ctx: RuntimeContext): void {
|
|
183
|
+
const cache = getCache(ctx)
|
|
184
|
+
cache.clear()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 获取缓存统计信息(调试用)
|
|
189
|
+
*/
|
|
190
|
+
export function getCacheStats(ctx: RuntimeContext): {
|
|
191
|
+
size: number
|
|
192
|
+
expressions: string[]
|
|
193
|
+
} {
|
|
194
|
+
const cache = getCache(ctx)
|
|
195
|
+
return {
|
|
196
|
+
size: cache.size,
|
|
197
|
+
expressions: Array.from(cache.keys())
|
|
198
|
+
}
|
|
199
|
+
}
|