@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,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
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * VM 模块导出
3
+ */
4
+
5
+ export { execute } from './executor.js'
6
+ export type { ExecuteOptions } from './executor.js'
7
+ export { registerBuiltinMethods } from './handlers/index.js'
8
+ export * from './errors.js'
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
+ }
@@ -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
+ })