@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.
Files changed (209) hide show
  1. package/__tests__/README.md +101 -0
  2. package/__tests__/errors.test.ts +139 -0
  3. package/__tests__/expression/cache.test.ts +118 -0
  4. package/__tests__/expression/compiler.test.ts +111 -0
  5. package/__tests__/expression/evaluate.test.ts +95 -0
  6. package/__tests__/expression/parser.test.ts +57 -0
  7. package/__tests__/expression/whitelist.test.ts +59 -0
  8. package/__tests__/performance.test.ts +379 -0
  9. package/__tests__/runtime/create-context.test.ts +78 -0
  10. package/__tests__/runtime/loop-context-pool.test.ts +74 -0
  11. package/__tests__/runtime/path.test.ts +128 -0
  12. package/__tests__/vm/executor-timeout.test.ts +117 -0
  13. package/__tests__/vm/executor.test.ts +173 -0
  14. package/__tests__/vm/handlers/array.test.ts +113 -0
  15. package/__tests__/vm/handlers/call.test.ts +93 -0
  16. package/dist/errors.d.ts +100 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +132 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/expression/cache.d.ts +53 -0
  21. package/dist/expression/cache.d.ts.map +1 -0
  22. package/dist/expression/cache.js +158 -0
  23. package/dist/expression/cache.js.map +1 -0
  24. package/dist/expression/compiler.d.ts +34 -0
  25. package/dist/expression/compiler.d.ts.map +1 -0
  26. package/dist/expression/compiler.js +123 -0
  27. package/dist/expression/compiler.js.map +1 -0
  28. package/dist/expression/dependencies.d.ts +17 -0
  29. package/dist/expression/dependencies.d.ts.map +1 -0
  30. package/dist/expression/dependencies.js +106 -0
  31. package/dist/expression/dependencies.js.map +1 -0
  32. package/dist/expression/evaluate.d.ts +22 -0
  33. package/dist/expression/evaluate.d.ts.map +1 -0
  34. package/dist/expression/evaluate.js +75 -0
  35. package/dist/expression/evaluate.js.map +1 -0
  36. package/dist/expression/evaluator.d.ts +22 -0
  37. package/dist/expression/evaluator.d.ts.map +1 -0
  38. package/dist/expression/evaluator.js +506 -0
  39. package/dist/expression/evaluator.js.map +1 -0
  40. package/dist/expression/index.d.ts +12 -0
  41. package/dist/expression/index.d.ts.map +1 -0
  42. package/dist/expression/index.js +12 -0
  43. package/dist/expression/index.js.map +1 -0
  44. package/dist/expression/parser.d.ts +15 -0
  45. package/dist/expression/parser.d.ts.map +1 -0
  46. package/dist/expression/parser.js +42 -0
  47. package/dist/expression/parser.js.map +1 -0
  48. package/dist/expression/utils.d.ts +46 -0
  49. package/dist/expression/utils.d.ts.map +1 -0
  50. package/dist/expression/utils.js +78 -0
  51. package/dist/expression/utils.js.map +1 -0
  52. package/dist/expression/whitelist.d.ts +24 -0
  53. package/dist/expression/whitelist.d.ts.map +1 -0
  54. package/dist/expression/whitelist.js +198 -0
  55. package/dist/expression/whitelist.js.map +1 -0
  56. package/dist/index.d.ts +19 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +21 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/runtime/context.d.ts +8 -0
  61. package/dist/runtime/context.d.ts.map +1 -0
  62. package/dist/runtime/context.js +7 -0
  63. package/dist/runtime/context.js.map +1 -0
  64. package/dist/runtime/create-context.d.ts +50 -0
  65. package/dist/runtime/create-context.d.ts.map +1 -0
  66. package/dist/runtime/create-context.js +73 -0
  67. package/dist/runtime/create-context.js.map +1 -0
  68. package/dist/runtime/index.d.ts +10 -0
  69. package/dist/runtime/index.d.ts.map +1 -0
  70. package/dist/runtime/index.js +10 -0
  71. package/dist/runtime/index.js.map +1 -0
  72. package/dist/runtime/loop-context-pool.d.ts +58 -0
  73. package/dist/runtime/loop-context-pool.d.ts.map +1 -0
  74. package/dist/runtime/loop-context-pool.js +114 -0
  75. package/dist/runtime/loop-context-pool.js.map +1 -0
  76. package/dist/runtime/path.d.ts +114 -0
  77. package/dist/runtime/path.d.ts.map +1 -0
  78. package/dist/runtime/path.js +302 -0
  79. package/dist/runtime/path.js.map +1 -0
  80. package/dist/runtime/proxy.d.ts +18 -0
  81. package/dist/runtime/proxy.d.ts.map +1 -0
  82. package/dist/runtime/proxy.js +53 -0
  83. package/dist/runtime/proxy.js.map +1 -0
  84. package/dist/runtime/sandbox.d.ts +20 -0
  85. package/dist/runtime/sandbox.d.ts.map +1 -0
  86. package/dist/runtime/sandbox.js +32 -0
  87. package/dist/runtime/sandbox.js.map +1 -0
  88. package/dist/types.d.ts +175 -0
  89. package/dist/types.d.ts.map +1 -0
  90. package/dist/types.js +22 -0
  91. package/dist/types.js.map +1 -0
  92. package/dist/vm/action.d.ts +11 -0
  93. package/dist/vm/action.d.ts.map +1 -0
  94. package/dist/vm/action.js +10 -0
  95. package/dist/vm/action.js.map +1 -0
  96. package/dist/vm/errors.d.ts +5 -0
  97. package/dist/vm/errors.d.ts.map +1 -0
  98. package/dist/vm/errors.js +5 -0
  99. package/dist/vm/errors.js.map +1 -0
  100. package/dist/vm/executor.d.ts +35 -0
  101. package/dist/vm/executor.d.ts.map +1 -0
  102. package/dist/vm/executor.js +137 -0
  103. package/dist/vm/executor.js.map +1 -0
  104. package/dist/vm/handlers/array/pop.d.ts +12 -0
  105. package/dist/vm/handlers/array/pop.d.ts.map +1 -0
  106. package/dist/vm/handlers/array/pop.js +28 -0
  107. package/dist/vm/handlers/array/pop.js.map +1 -0
  108. package/dist/vm/handlers/array/push.d.ts +13 -0
  109. package/dist/vm/handlers/array/push.d.ts.map +1 -0
  110. package/dist/vm/handlers/array/push.js +42 -0
  111. package/dist/vm/handlers/array/push.js.map +1 -0
  112. package/dist/vm/handlers/array/shift.d.ts +12 -0
  113. package/dist/vm/handlers/array/shift.d.ts.map +1 -0
  114. package/dist/vm/handlers/array/shift.js +28 -0
  115. package/dist/vm/handlers/array/shift.js.map +1 -0
  116. package/dist/vm/handlers/array/splice.d.ts +12 -0
  117. package/dist/vm/handlers/array/splice.d.ts.map +1 -0
  118. package/dist/vm/handlers/array/splice.js +59 -0
  119. package/dist/vm/handlers/array/splice.js.map +1 -0
  120. package/dist/vm/handlers/array/unshift.d.ts +13 -0
  121. package/dist/vm/handlers/array/unshift.d.ts.map +1 -0
  122. package/dist/vm/handlers/array/unshift.js +42 -0
  123. package/dist/vm/handlers/array/unshift.js.map +1 -0
  124. package/dist/vm/handlers/array/utils.d.ts +10 -0
  125. package/dist/vm/handlers/array/utils.d.ts.map +1 -0
  126. package/dist/vm/handlers/array/utils.js +33 -0
  127. package/dist/vm/handlers/array/utils.js.map +1 -0
  128. package/dist/vm/handlers/batch.d.ts +12 -0
  129. package/dist/vm/handlers/batch.d.ts.map +1 -0
  130. package/dist/vm/handlers/batch.js +40 -0
  131. package/dist/vm/handlers/batch.js.map +1 -0
  132. package/dist/vm/handlers/call.d.ts +14 -0
  133. package/dist/vm/handlers/call.d.ts.map +1 -0
  134. package/dist/vm/handlers/call.js +65 -0
  135. package/dist/vm/handlers/call.js.map +1 -0
  136. package/dist/vm/handlers/emit.d.ts +12 -0
  137. package/dist/vm/handlers/emit.d.ts.map +1 -0
  138. package/dist/vm/handlers/emit.js +26 -0
  139. package/dist/vm/handlers/emit.js.map +1 -0
  140. package/dist/vm/handlers/if.d.ts +13 -0
  141. package/dist/vm/handlers/if.d.ts.map +1 -0
  142. package/dist/vm/handlers/if.js +35 -0
  143. package/dist/vm/handlers/if.js.map +1 -0
  144. package/dist/vm/handlers/index.d.ts +11 -0
  145. package/dist/vm/handlers/index.d.ts.map +1 -0
  146. package/dist/vm/handlers/index.js +46 -0
  147. package/dist/vm/handlers/index.js.map +1 -0
  148. package/dist/vm/handlers/log.d.ts +12 -0
  149. package/dist/vm/handlers/log.d.ts.map +1 -0
  150. package/dist/vm/handlers/log.js +41 -0
  151. package/dist/vm/handlers/log.js.map +1 -0
  152. package/dist/vm/handlers/loop.d.ts +12 -0
  153. package/dist/vm/handlers/loop.d.ts.map +1 -0
  154. package/dist/vm/handlers/loop.js +71 -0
  155. package/dist/vm/handlers/loop.js.map +1 -0
  156. package/dist/vm/handlers/navigate.d.ts +12 -0
  157. package/dist/vm/handlers/navigate.d.ts.map +1 -0
  158. package/dist/vm/handlers/navigate.js +43 -0
  159. package/dist/vm/handlers/navigate.js.map +1 -0
  160. package/dist/vm/handlers/set.d.ts +15 -0
  161. package/dist/vm/handlers/set.d.ts.map +1 -0
  162. package/dist/vm/handlers/set.js +30 -0
  163. package/dist/vm/handlers/set.js.map +1 -0
  164. package/dist/vm/index.d.ts +8 -0
  165. package/dist/vm/index.d.ts.map +1 -0
  166. package/dist/vm/index.js +7 -0
  167. package/dist/vm/index.js.map +1 -0
  168. package/package.json +34 -0
  169. package/src/errors.ts +194 -0
  170. package/src/expression/README.md +192 -0
  171. package/src/expression/cache.ts +199 -0
  172. package/src/expression/compiler.ts +144 -0
  173. package/src/expression/dependencies.ts +116 -0
  174. package/src/expression/evaluate.ts +95 -0
  175. package/src/expression/evaluator.ts +640 -0
  176. package/src/expression/index.ts +27 -0
  177. package/src/expression/parser.ts +54 -0
  178. package/src/expression/utils.ts +89 -0
  179. package/src/expression/whitelist.ts +224 -0
  180. package/src/globals.d.ts +10 -0
  181. package/src/index.ts +72 -0
  182. package/src/runtime/context.ts +8 -0
  183. package/src/runtime/create-context.ts +133 -0
  184. package/src/runtime/index.ts +28 -0
  185. package/src/runtime/loop-context-pool.ts +134 -0
  186. package/src/runtime/path.ts +372 -0
  187. package/src/runtime/proxy.ts +66 -0
  188. package/src/runtime/sandbox.ts +43 -0
  189. package/src/types.ts +177 -0
  190. package/src/vm/errors.ts +10 -0
  191. package/src/vm/executor.ts +210 -0
  192. package/src/vm/handlers/array/pop.ts +47 -0
  193. package/src/vm/handlers/array/push.ts +68 -0
  194. package/src/vm/handlers/array/shift.ts +47 -0
  195. package/src/vm/handlers/array/splice.ts +78 -0
  196. package/src/vm/handlers/array/unshift.ts +68 -0
  197. package/src/vm/handlers/array/utils.ts +41 -0
  198. package/src/vm/handlers/batch.ts +57 -0
  199. package/src/vm/handlers/call.ts +92 -0
  200. package/src/vm/handlers/emit.ts +39 -0
  201. package/src/vm/handlers/if.ts +48 -0
  202. package/src/vm/handlers/index.ts +52 -0
  203. package/src/vm/handlers/log.ts +54 -0
  204. package/src/vm/handlers/loop.ts +102 -0
  205. package/src/vm/handlers/navigate.ts +64 -0
  206. package/src/vm/handlers/set.ts +43 -0
  207. package/src/vm/index.ts +8 -0
  208. package/tsconfig.json +17 -0
  209. package/vitest.config.ts +30 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 表达式解析器
3
+ *
4
+ * 使用 @babel/parser 解析 JavaScript 表达式为 AST
5
+ * 移除 models. 前缀支持
6
+ */
7
+
8
+ import { parse } from '@babel/parser'
9
+ import type * as ESTree from '@babel/types'
10
+ import { ExpressionError, ErrorCodes } from '../errors.js'
11
+
12
+ /**
13
+ * 解析表达式为 AST
14
+ *
15
+ * @param expr 表达式字符串(如 "user.name + 1")
16
+ * @returns ESTree.Node AST 节点
17
+ */
18
+ export function parseExpression(expr: string): ESTree.Node {
19
+ try {
20
+ // 解析为表达式(ExpressionStatement)
21
+ const ast = parse(`(${expr})`, {
22
+ plugins: ['typescript'],
23
+ sourceType: 'module',
24
+ allowReturnOutsideFunction: true,
25
+ })
26
+
27
+ // 提取表达式部分
28
+ const statement = ast.program.body[0]
29
+ if (statement?.type === 'ExpressionStatement') {
30
+ return statement.expression
31
+ }
32
+
33
+ throw new ExpressionError(
34
+ expr,
35
+ `Failed to parse expression: ${expr}`,
36
+ ErrorCodes.EXPRESSION_PARSE_ERROR
37
+ )
38
+ } catch (error: unknown) {
39
+ if (error instanceof ExpressionError) {
40
+ throw error
41
+ }
42
+ const errorMessage = error instanceof Error ? error.message : String(error)
43
+ throw new ExpressionError(
44
+ expr,
45
+ `Expression parse error: ${errorMessage}`,
46
+ ErrorCodes.EXPRESSION_PARSE_ERROR,
47
+ {
48
+ metadata: {
49
+ originalError: error instanceof Error ? error.name : 'Unknown'
50
+ }
51
+ }
52
+ )
53
+ }
54
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * 表达式工具函数
3
+ *
4
+ * 提供表达式解析、格式化的通用工具
5
+ * 支持 {{ }} 格式的表达式提取和规范化
6
+ */
7
+
8
+ /**
9
+ * 提取表达式字符串
10
+ * 支持 {{ expression }} 格式,自动去掉包装
11
+ *
12
+ * @param expr 表达式字符串,可能是 "{{ showContent }}" 或 "showContent"
13
+ * @returns 去掉 {{ }} 包装后的表达式字符串
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * extractExpression("{{ showContent }}") // => "showContent"
18
+ * extractExpression("showContent") // => "showContent"
19
+ * extractExpression("{{ userRole === 'admin' }}") // => "userRole === 'admin'"
20
+ * ```
21
+ */
22
+ export function extractExpression(expr: string): string {
23
+ if (typeof expr !== 'string') {
24
+ return String(expr)
25
+ }
26
+
27
+ // 如果表达式是 {{ }} 格式,去掉包装
28
+ if (expr.startsWith('{{') && expr.endsWith('}}')) {
29
+ return expr.slice(2, -2).trim()
30
+ }
31
+
32
+ return expr.trim()
33
+ }
34
+
35
+ /**
36
+ * 规范化表达式字符串
37
+ * 确保表达式格式统一,便于后续处理
38
+ *
39
+ * @param expr 表达式字符串
40
+ * @returns 规范化后的表达式字符串(去掉 {{ }} 包装)
41
+ */
42
+ export function normalizeExpression(expr: string): string {
43
+ return extractExpression(expr)
44
+ }
45
+
46
+ /**
47
+ * 检查是否为表达式格式
48
+ *
49
+ * @param value 要检查的值
50
+ * @returns 如果是 {{ }} 格式返回 true
51
+ */
52
+ export function isExpressionFormat(value: any): boolean {
53
+ return typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')
54
+ }
55
+
56
+ /**
57
+ * 批量提取表达式
58
+ * 从对象或数组中提取所有表达式字符串
59
+ *
60
+ * @param value 要处理的值(对象、数组或字符串)
61
+ * @param extractor 提取函数,默认使用 extractExpression
62
+ * @returns 处理后的值
63
+ */
64
+ export function extractExpressionsRecursively(
65
+ value: any,
66
+ extractor: (expr: string) => string = extractExpression
67
+ ): any {
68
+ // 字符串:检查是否为表达式
69
+ if (typeof value === 'string') {
70
+ return extractor(value)
71
+ }
72
+
73
+ // 数组:递归处理每个元素
74
+ if (Array.isArray(value)) {
75
+ return value.map(item => extractExpressionsRecursively(item, extractor))
76
+ }
77
+
78
+ // 对象:递归处理每个属性值
79
+ if (value && typeof value === 'object') {
80
+ const result: Record<string, any> = {}
81
+ for (const [key, val] of Object.entries(value)) {
82
+ result[key] = extractExpressionsRecursively(val, extractor)
83
+ }
84
+ return result
85
+ }
86
+
87
+ // 其他类型:直接返回
88
+ return value
89
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * AST 白名单验证器
3
+ *
4
+ * 功能:
5
+ * - 深度遍历 AST,检查每个节点
6
+ * - 只允许安全的语法节点
7
+ * - 禁止危险的语法(赋值、函数、this 等)
8
+ * - 检查函数调用中的函数名是否在白名单中
9
+ */
10
+
11
+ import type * as ESTree from '@babel/types'
12
+ import { ExpressionError, ErrorCodes } from '../errors.js'
13
+
14
+ /**
15
+ * 白名单全局函数
16
+ */
17
+ const WHITELISTED_GLOBALS = new Set([
18
+ 'String', 'Number', 'Boolean', 'BigInt', 'Symbol',
19
+ 'Array', 'Object', 'Math', 'Date',
20
+ ])
21
+
22
+ /**
23
+ * 白名单函数(带命名空间)
24
+ */
25
+ const WHITELISTED_FUNCTIONS = new Set([
26
+ 'Array.isArray',
27
+ 'Object.is',
28
+ 'Number.isFinite',
29
+ 'Number.isInteger',
30
+ 'Number.isNaN',
31
+ 'Number.isSafeInteger',
32
+ 'Math.abs',
33
+ 'Math.round',
34
+ 'Math.floor',
35
+ 'Math.ceil',
36
+ 'Math.random',
37
+ 'Math.max',
38
+ 'Math.min',
39
+ 'Date.now',
40
+ ])
41
+
42
+ /**
43
+ * 检查名称是否为全局对象名称
44
+ */
45
+ function isGlobalObjectName(name: string): boolean {
46
+ return ['window', 'document', 'global', 'globalThis', 'self'].includes(name)
47
+ }
48
+
49
+ /**
50
+ * 允许的 AST 节点类型
51
+ *
52
+ * 注意:@babel/types 可能使用更具体的字面量类型(如 NumericLiteral, StringLiteral)
53
+ * 这些都应该被允许,因为它们都是 Literal 的子类型
54
+ */
55
+ const ALLOWED_NODE_TYPES = new Set([
56
+ 'MemberExpression', // 成员访问:user.name
57
+ 'OptionalMemberExpression', // 可选链:user?.name
58
+ 'ArrayExpression', // 数组字面量:[1, 2, 3]
59
+ 'ObjectExpression', // 对象字面量:{ a: 1 }
60
+ 'ObjectProperty', // 对象属性:{ a: 1 } 中的 a: 1
61
+ 'Literal', // 字面量:'string', 123, true
62
+ 'NumericLiteral', // 数字字面量:123(@babel/types 的具体类型)
63
+ 'StringLiteral', // 字符串字面量:'string'
64
+ 'BooleanLiteral', // 布尔字面量:true, false
65
+ 'NullLiteral', // null 字面量
66
+ 'Identifier', // 标识符:user, name
67
+ 'BinaryExpression', // 二元运算:a + b
68
+ 'LogicalExpression', // 逻辑运算:a && b
69
+ 'UnaryExpression', // 一元运算:!a, -b
70
+ 'ConditionalExpression', // 三元表达式:a ? b : c
71
+ 'CallExpression', // 函数调用:Math.max()
72
+ 'TemplateLiteral', // 模板字符串:`${name}`
73
+ 'SequenceExpression', // 序列表达式:(a, b)
74
+ 'NullishCoalescingExpression', // 空值合并:a ?? b
75
+ ])
76
+
77
+ /**
78
+ * 禁止的 AST 节点类型
79
+ */
80
+ const FORBIDDEN_NODE_TYPES = new Set([
81
+ 'AssignmentExpression', // 赋值:a = b
82
+ 'UpdateExpression', // 自增/自减:a++, --b
83
+ 'FunctionExpression', // 函数表达式:function() {}
84
+ 'ArrowFunctionExpression', // 箭头函数:() => {}
85
+ 'ThisExpression', // this
86
+ 'NewExpression', // new 运算符
87
+ 'YieldExpression', // yield
88
+ 'AwaitExpression', // await
89
+ 'ImportExpression', // import()
90
+ 'MetaProperty', // import.meta
91
+ 'SpreadElement', // 展开运算符:...array(在函数调用中不安全)
92
+ ])
93
+
94
+ /**
95
+ * 默认最大嵌套深度(防止 DoS 攻击)
96
+ */
97
+ const DEFAULT_MAX_NESTING_DEPTH = 50
98
+
99
+ /**
100
+ * 验证 AST 是否通过白名单检查
101
+ *
102
+ * @param ast AST 节点
103
+ * @param options 验证选项
104
+ * @param options.allowGlobals 是否允许全局函数调用(跳过白名单检查)
105
+ * @param options.maxNestingDepth 最大嵌套深度(默认 50)
106
+ * @throws ExpressionError 如果发现禁止的节点
107
+ */
108
+ export function validateAST(ast: ESTree.Node, options?: { allowGlobals?: boolean; maxNestingDepth?: number }): void {
109
+ const allowGlobals = options?.allowGlobals === true
110
+ const maxNestingDepth = options?.maxNestingDepth ?? DEFAULT_MAX_NESTING_DEPTH
111
+ const errors: string[] = []
112
+
113
+ function getFunctionName(callee: ESTree.Node): string | null {
114
+ if (callee.type === 'Identifier') {
115
+ return callee.name
116
+ }
117
+ if (callee.type === 'MemberExpression') {
118
+ const member = callee as ESTree.MemberExpression
119
+ if (member.object.type === 'Identifier' && member.property.type === 'Identifier') {
120
+ return `${member.object.name}.${member.property.name}`
121
+ }
122
+ }
123
+ return null
124
+ }
125
+
126
+ function traverse(node: ESTree.Node, path: string = 'root', depth: number = 0): void {
127
+ // 检查嵌套深度(防止 DoS 攻击)
128
+ if (depth > maxNestingDepth) {
129
+ errors.push(`Maximum nesting depth (${maxNestingDepth}) exceeded at ${path}`)
130
+ return
131
+ }
132
+
133
+ // 检查节点类型
134
+ if (FORBIDDEN_NODE_TYPES.has(node.type)) {
135
+ errors.push(`Forbidden node type "${node.type}" at ${path}`)
136
+ return
137
+ }
138
+
139
+ // 检查函数调用中的函数名
140
+ if (node.type === 'CallExpression') {
141
+ const call = node as ESTree.CallExpression
142
+ const funcName = getFunctionName(call.callee)
143
+ if (funcName) {
144
+ // 检查是否为危险函数(eval, Function, etc.)- 即使 allowGlobals 也禁止
145
+ const dangerousFunctions = ['eval', 'Function', 'setTimeout', 'setInterval', 'execScript']
146
+ if (dangerousFunctions.includes(funcName) || funcName.startsWith('eval') || funcName.startsWith('Function')) {
147
+ errors.push(`Dangerous function "${funcName}" is not allowed at ${path}`)
148
+ return
149
+ }
150
+
151
+ // 如果 allowGlobals,跳过白名单检查
152
+ if (!allowGlobals) {
153
+ // 检查是否为全局函数调用(如 Array.isArray, Math.max)
154
+ const isGlobalFunction =
155
+ WHITELISTED_FUNCTIONS.has(funcName) ||
156
+ WHITELISTED_GLOBALS.has(funcName.split('.')[0])
157
+
158
+ // 检查是否为对象方法调用(如 array.slice, user.getName)
159
+ // 如果 callee 是 MemberExpression 且 object 不是全局对象,则允许
160
+ let isObjectMethod = false
161
+ if (call.callee.type === 'MemberExpression') {
162
+ const member = call.callee as ESTree.MemberExpression
163
+ // 如果 object 是 Identifier(变量名),且不在全局对象白名单中,则认为是对象方法
164
+ if (member.object.type === 'Identifier') {
165
+ const objName = member.object.name
166
+ // 不是全局对象名称,则认为是用户数据的对象方法
167
+ if (!WHITELISTED_GLOBALS.has(objName) && !isGlobalObjectName(objName)) {
168
+ isObjectMethod = true
169
+ }
170
+ }
171
+ }
172
+
173
+ if (!isGlobalFunction && !isObjectMethod && !funcName.startsWith('$')) {
174
+ // 不在白名单中的函数,禁止调用
175
+ errors.push(`Function "${funcName}" is not in whitelist at ${path}`)
176
+ return
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ if (!ALLOWED_NODE_TYPES.has(node.type) && node.type !== 'Program') {
183
+ // 未知节点类型,保守策略:禁止
184
+ errors.push(`Unknown node type "${node.type}" at ${path}`)
185
+ return
186
+ }
187
+
188
+ // 递归遍历子节点
189
+ for (const key in node) {
190
+ const value = (node as unknown as Record<string, unknown>)[key]
191
+
192
+ if (value == null) continue
193
+
194
+ // 处理数组
195
+ if (Array.isArray(value)) {
196
+ value.forEach((child, index) => {
197
+ if (child && typeof child === 'object' && 'type' in child) {
198
+ traverse(child as ESTree.Node, `${path}[${index}]`, depth + 1)
199
+ }
200
+ })
201
+ }
202
+ // 处理对象节点
203
+ else if (value && typeof value === 'object' && 'type' in value) {
204
+ traverse(value as ESTree.Node, `${path}.${key}`, depth + 1)
205
+ }
206
+ }
207
+ }
208
+
209
+ traverse(ast, 'root', 0)
210
+
211
+ if (errors.length > 0) {
212
+ throw new ExpressionError(
213
+ JSON.stringify(ast),
214
+ `AST validation failed:\n${errors.join('\n')}`,
215
+ ErrorCodes.EXPRESSION_VALIDATION_ERROR,
216
+ {
217
+ metadata: {
218
+ errors,
219
+ nodeType: ast.type
220
+ }
221
+ }
222
+ )
223
+ }
224
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 全局类型声明
3
+ * 用于支持跨平台代码(Node.js 和浏览器)
4
+ */
5
+
6
+ // 声明全局变量,避免 TypeScript 错误(使用 any 以避免 DOM lib 依赖)
7
+ declare const window: any
8
+ declare const document: any
9
+ declare const self: any
10
+ declare const global: typeof globalThis | undefined
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @vario/core - Vario Core Runtime
3
+ *
4
+ * 核心运行时模块,包含:
5
+ * - RuntimeContext: 扁平化状态 + $ 前缀系统 API
6
+ * - Expression System: 安全表达式求值
7
+ * - Action VM: 动作虚拟机
8
+ * - Path Utilities: 统一的路径解析工具
9
+ */
10
+
11
+ // Runtime
12
+ export {
13
+ createRuntimeContext,
14
+ createProxy,
15
+ createExpressionSandbox,
16
+ isSafePropertyAccess,
17
+ // 循环上下文(供框架集成层使用)
18
+ createLoopContext,
19
+ releaseLoopContext,
20
+ // 路径工具(供框架集成层使用)
21
+ parsePath,
22
+ parsePathCached,
23
+ clearPathCache,
24
+ stringifyPath,
25
+ getPathValue,
26
+ setPathValue,
27
+ matchPath,
28
+ getParentPath,
29
+ getLastSegment
30
+ } from './runtime/index.js'
31
+ export type { RuntimeContext } from './types.js'
32
+ export type { PathSegment } from './runtime/path.js'
33
+ export type { CreateContextOptions } from './runtime/create-context.js'
34
+
35
+ // Expression
36
+ export {
37
+ parseExpression,
38
+ validateAST,
39
+ evaluateExpression,
40
+ evaluate,
41
+ extractExpression,
42
+ extractDependencies,
43
+ getCachedExpression,
44
+ setCachedExpression,
45
+ invalidateCache,
46
+ clearCache
47
+ } from './expression/index.js'
48
+
49
+ // VM
50
+ export {
51
+ execute,
52
+ registerBuiltinMethods
53
+ } from './vm/index.js'
54
+ export type { ExecuteOptions } from './vm/index.js'
55
+ export {
56
+ ActionError,
57
+ ExpressionError,
58
+ ServiceError,
59
+ BatchError,
60
+ VarioError,
61
+ ErrorCodes,
62
+ type ErrorContext,
63
+ type ErrorCode
64
+ } from './errors.js'
65
+ export type {
66
+ Action,
67
+ ActionMap,
68
+ ActionHandler,
69
+ ExpressionCache,
70
+ MethodsRegistry,
71
+ ExpressionOptions
72
+ } from './types.js'
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Runtime Context
3
+ *
4
+ * 运行时上下文主模块,导出创建函数
5
+ */
6
+
7
+ export { createRuntimeContext } from './create-context.js'
8
+ export type { RuntimeContext } from '../types.js'
@@ -0,0 +1,133 @@
1
+ /**
2
+ * RuntimeContext 创建工厂
3
+ *
4
+ * 实现要点:
5
+ * - 扁平化状态存储
6
+ * - 系统 API 保护($ 和 _ 前缀)
7
+ * - 命名冲突检测
8
+ * - Proxy 拦截防止覆盖系统 API
9
+ */
10
+
11
+ import type { RuntimeContext, MethodsRegistry, GetPathValue, SetPathValue, ExpressionOptions } from '../types.js'
12
+ import { createProxy } from './proxy.js'
13
+ import { registerBuiltinMethods } from '../vm/handlers/index.js'
14
+ import { getPathValue as getPath, setPathValue as setPath } from './path.js'
15
+ import { invalidateCache } from '../expression/cache.js'
16
+
17
+ /**
18
+ * RuntimeContext 创建选项
19
+ */
20
+ export interface CreateContextOptions {
21
+ /**
22
+ * 事件发射回调
23
+ */
24
+ onEmit?: (event: string, data?: unknown) => void
25
+
26
+ /**
27
+ * 预注册的方法
28
+ */
29
+ methods?: MethodsRegistry
30
+
31
+ /**
32
+ * 状态变更钩子(用于框架集成)
33
+ * 在 _set 调用后触发,可用于缓存失效等
34
+ */
35
+ onStateChange?: (path: string, value: unknown, ctx: RuntimeContext) => void
36
+
37
+ /**
38
+ * 创建对象的工厂函数(用于框架集成创建响应式对象)
39
+ */
40
+ createObject?: () => Record<string, unknown>
41
+
42
+ /**
43
+ * 创建数组的工厂函数(用于框架集成创建响应式数组)
44
+ */
45
+ createArray?: () => unknown[]
46
+
47
+ /**
48
+ * 表达式求值配置
49
+ */
50
+ exprOptions?: ExpressionOptions
51
+ }
52
+
53
+ /**
54
+ * 创建运行时上下文
55
+ *
56
+ * @param initialState 初始状态(扁平化)
57
+ * @param options 配置选项
58
+ *
59
+ * @template TState 状态类型,从 initialState 推导
60
+ */
61
+ export function createRuntimeContext<
62
+ TState extends Record<string, unknown> = Record<string, unknown>
63
+ >(
64
+ initialState: Partial<TState> = {} as Partial<TState>,
65
+ options: CreateContextOptions = {}
66
+ ): RuntimeContext<TState> {
67
+ // 1. 验证命名冲突
68
+ validateStateKeys(initialState)
69
+
70
+ const {
71
+ onEmit,
72
+ methods = {},
73
+ onStateChange,
74
+ createObject = () => ({}),
75
+ createArray = () => [],
76
+ exprOptions
77
+ } = options
78
+
79
+ // 用于存储 proxied 引用,以便 _set 中的 onStateChange 能使用正确的引用
80
+ let proxiedRef: RuntimeContext<TState> | null = null
81
+
82
+ // 2. 创建基础上下文对象
83
+ const ctx = {
84
+ ...initialState,
85
+ $emit: (event: string, data?: unknown) => {
86
+ if (onEmit) {
87
+ onEmit(event, data)
88
+ }
89
+ },
90
+ $methods: methods as MethodsRegistry,
91
+ $exprOptions: exprOptions,
92
+ _get: <TPath extends string>(path: TPath): GetPathValue<TState, TPath> => {
93
+ return getPath(ctx as Record<string, unknown>, path) as GetPathValue<TState, TPath>
94
+ },
95
+ _set: <TPath extends string>(path: TPath, value: SetPathValue<TState, TPath>, options?: { skipCallback?: boolean }): void => {
96
+ setPath(ctx as Record<string, unknown>, path, value, {
97
+ createObject,
98
+ createArray
99
+ })
100
+ // 使缓存失效(使用 proxied 引用以确保缓存键一致)
101
+ invalidateCache(path, (proxiedRef || ctx) as RuntimeContext)
102
+ // 触发状态变更钩子
103
+ if (onStateChange && !options?.skipCallback) {
104
+ onStateChange(path, value, (proxiedRef || ctx) as RuntimeContext)
105
+ }
106
+ },
107
+ } as RuntimeContext<TState>
108
+
109
+ // 3. 自动注册内置指令到 $methods
110
+ registerBuiltinMethods(ctx as RuntimeContext)
111
+
112
+ // 4. 使用 Proxy 保护系统 API
113
+ const proxied = createProxy(ctx as unknown as RuntimeContext) as RuntimeContext<TState>
114
+
115
+ // 5. 保存 proxied 引用供 _set 使用
116
+ proxiedRef = proxied
117
+
118
+ return proxied
119
+ }
120
+
121
+ /**
122
+ * 验证状态键名,防止与系统 API 冲突
123
+ */
124
+ function validateStateKeys(state: Record<string, unknown>): void {
125
+ for (const key in state) {
126
+ if (key.startsWith('$') || key.startsWith('_')) {
127
+ throw new Error(
128
+ `Property name "${key}" conflicts with system API. ` +
129
+ `Properties starting with "$" or "_" are reserved. Use a different name.`
130
+ )
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Runtime 模块导出
3
+ */
4
+
5
+ export { createRuntimeContext } from './create-context.js'
6
+ export { createProxy } from './proxy.js'
7
+ export { createExpressionSandbox, isSafePropertyAccess } from './sandbox.js'
8
+ export {
9
+ createLoopContext,
10
+ releaseLoopContext,
11
+ getLoopContextPool,
12
+ clearLoopContextPool
13
+ } from './loop-context-pool.js'
14
+ export type { RuntimeContext } from '../types.js'
15
+
16
+ // 路径工具(供框架集成层使用)
17
+ export {
18
+ parsePath,
19
+ parsePathCached,
20
+ clearPathCache,
21
+ stringifyPath,
22
+ getPathValue,
23
+ setPathValue,
24
+ matchPath,
25
+ getParentPath,
26
+ getLastSegment,
27
+ type PathSegment
28
+ } from './path.js'