@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,117 @@
1
+ /**
2
+ * Action VM 超时和步数限制测试
3
+ */
4
+
5
+ import { describe, it, expect, vi } from 'vitest'
6
+ import { createRuntimeContext } from '../../src/runtime/create-context.js'
7
+ import { execute, type ExecuteOptions } from '../../src/vm/executor.js'
8
+ import { ErrorCodes } from '../../src/errors.js'
9
+
10
+ describe('Action VM 超时和步数限制', () => {
11
+ describe('超时保护', () => {
12
+ it('应该在超时时抛出错误', async () => {
13
+ const ctx = createRuntimeContext({})
14
+
15
+ // 注册一个会延迟的动作
16
+ ctx.$methods['slow'] = async () => {
17
+ await new Promise(resolve => setTimeout(resolve, 100))
18
+ }
19
+
20
+ const options: ExecuteOptions = {
21
+ timeout: 50
22
+ }
23
+
24
+ try {
25
+ await execute([{ type: 'slow' }], ctx, options)
26
+ expect.fail('Should have thrown timeout error')
27
+ } catch (error: any) {
28
+ expect(error.code).toBe(ErrorCodes.ACTION_TIMEOUT)
29
+ }
30
+ })
31
+
32
+ it('应该允许正常执行在超时范围内', async () => {
33
+ const ctx = createRuntimeContext({ count: 0 })
34
+
35
+ const options: ExecuteOptions = {
36
+ timeout: 1000
37
+ }
38
+
39
+ await execute([
40
+ { type: 'set', path: 'count', value: 10 }
41
+ ], ctx, options)
42
+
43
+ expect(ctx.count).toBe(10)
44
+ })
45
+ })
46
+
47
+ describe('步数限制', () => {
48
+ it('应该在超过最大步数时抛出错误', async () => {
49
+ const ctx = createRuntimeContext({ count: 0 })
50
+
51
+ const options: ExecuteOptions = {
52
+ maxSteps: 5
53
+ }
54
+
55
+ // 创建超过步数限制的动作序列
56
+ const actions = Array.from({ length: 10 }, () => ({
57
+ type: 'set' as const,
58
+ path: 'count',
59
+ value: 1
60
+ }))
61
+
62
+ try {
63
+ await execute(actions, ctx, options)
64
+ expect.fail('Should have thrown max steps error')
65
+ } catch (error: any) {
66
+ expect(error.code).toBe(ErrorCodes.ACTION_MAX_STEPS_EXCEEDED)
67
+ }
68
+ })
69
+
70
+ it('应该允许正常执行在步数限制内', async () => {
71
+ const ctx = createRuntimeContext({ count: 0 })
72
+
73
+ const options: ExecuteOptions = {
74
+ maxSteps: 100
75
+ }
76
+
77
+ await execute([
78
+ { type: 'set', path: 'count', value: 10 }
79
+ ], ctx, options)
80
+
81
+ expect(ctx.count).toBe(10)
82
+ })
83
+ })
84
+
85
+ describe('边界条件', () => {
86
+ it('应该处理空动作数组', async () => {
87
+ const ctx = createRuntimeContext({})
88
+
89
+ await execute([], ctx)
90
+
91
+ // 应该正常完成,不抛出错误
92
+ expect(true).toBe(true)
93
+ })
94
+
95
+ it('应该处理单个动作', async () => {
96
+ const ctx = createRuntimeContext({ count: 0 })
97
+
98
+ await execute([
99
+ { type: 'set', path: 'count', value: 1 }
100
+ ], ctx)
101
+
102
+ expect(ctx.count).toBe(1)
103
+ })
104
+
105
+ it('应该正确处理动作执行中的错误', async () => {
106
+ const ctx = createRuntimeContext({})
107
+
108
+ ctx.$methods['error'] = async () => {
109
+ throw new Error('Test error')
110
+ }
111
+
112
+ await expect(
113
+ execute([{ type: 'error' }], ctx)
114
+ ).rejects.toThrow('Test error')
115
+ })
116
+ })
117
+ })
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Action VM 执行器测试
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest'
6
+ import { createRuntimeContext } from '../../src/runtime/create-context'
7
+ import { execute } from '../../src/vm/executor'
8
+
9
+ describe('Action VM', () => {
10
+ let ctx: ReturnType<typeof createRuntimeContext>
11
+
12
+ beforeEach(() => {
13
+ // createRuntimeContext 现在会自动注册内置动作
14
+ ctx = createRuntimeContext({
15
+ user: { name: 'John', age: 30 },
16
+ count: 0,
17
+ items: [1, 2, 3]
18
+ })
19
+ })
20
+
21
+ it('应该执行 set 动作', async () => {
22
+ await execute([
23
+ { type: 'set', path: 'count', value: 10 }
24
+ ], ctx)
25
+
26
+ expect(ctx.count).toBe(10)
27
+ })
28
+
29
+ it('应该执行 set 动作(表达式值)', async () => {
30
+ await execute([
31
+ { type: 'set', path: 'count', value: '{{ count + 1 }}' }
32
+ ], ctx)
33
+
34
+ expect(ctx.count).toBe(1)
35
+ })
36
+
37
+ it('应该执行 emit 动作', async () => {
38
+ const events: Array<{ event: string; data?: any }> = []
39
+ const ctx = createRuntimeContext({}, {
40
+ onEmit: (event, data) => {
41
+ events.push({ event, data })
42
+ }
43
+ })
44
+
45
+ await execute([
46
+ { type: 'emit', event: 'test', data: { value: 123 } }
47
+ ], ctx)
48
+
49
+ expect(events).toHaveLength(1)
50
+ expect(events[0].event).toBe('test')
51
+ })
52
+
53
+ it('应该执行 if 动作', async () => {
54
+ await execute([
55
+ {
56
+ type: 'if',
57
+ cond: 'user.age > 18',
58
+ then: [
59
+ { type: 'set', path: 'isAdult', value: true }
60
+ ],
61
+ else: [
62
+ { type: 'set', path: 'isAdult', value: false }
63
+ ]
64
+ }
65
+ ], ctx)
66
+
67
+ expect(ctx.isAdult).toBe(true)
68
+ })
69
+
70
+ it('应该执行 loop 动作', async () => {
71
+ await execute([
72
+ {
73
+ type: 'loop',
74
+ var: 'item',
75
+ in: 'items',
76
+ body: [
77
+ { type: 'set', path: 'total', value: '{{ (total || 0) + item }}' }
78
+ ]
79
+ }
80
+ ], ctx)
81
+
82
+ expect(ctx.total).toBe(6) // 1 + 2 + 3
83
+ })
84
+
85
+ it('应该执行 batch 动作', async () => {
86
+ await execute([
87
+ {
88
+ type: 'batch',
89
+ actions: [
90
+ { type: 'set', path: 'a', value: 1 },
91
+ { type: 'set', path: 'b', value: 2 }
92
+ ]
93
+ }
94
+ ], ctx)
95
+
96
+ expect(ctx.a).toBe(1)
97
+ expect(ctx.b).toBe(2)
98
+ })
99
+
100
+ it('应该执行数组操作动作', async () => {
101
+ await execute([
102
+ { type: 'push', path: 'items', value: 4 }
103
+ ], ctx)
104
+
105
+ expect(ctx.items).toEqual([1, 2, 3, 4])
106
+
107
+ await execute([
108
+ { type: 'pop', path: 'items' }
109
+ ], ctx)
110
+
111
+ expect(ctx.items).toEqual([1, 2, 3])
112
+ })
113
+
114
+ it('应该在未知动作类型时抛出错误', async () => {
115
+ await expect(
116
+ execute([{ type: 'unknown' }], ctx)
117
+ ).rejects.toThrow('Unknown action type')
118
+ })
119
+
120
+ it('应该执行 call 动作', async () => {
121
+ ctx.$methods['test.method'] = async () => {
122
+ return 'result'
123
+ }
124
+
125
+ await execute([
126
+ {
127
+ type: 'call',
128
+ method: 'test.method',
129
+ resultTo: 'result'
130
+ }
131
+ ], ctx)
132
+
133
+ expect(ctx.result).toBe('result')
134
+ })
135
+
136
+ it('应该执行 navigate 动作', async () => {
137
+ let navigatedTo: string | null = null
138
+ ctx.$methods['navigate'] = async (ctx, params: Record<string, unknown>) => {
139
+ navigatedTo = params.to as string
140
+ }
141
+
142
+ await execute([
143
+ {
144
+ type: 'navigate',
145
+ to: '/test'
146
+ }
147
+ ], ctx)
148
+
149
+ expect(navigatedTo).toBe('/test')
150
+ })
151
+
152
+ it('应该执行 log 动作', async () => {
153
+ const logs: string[] = []
154
+ const originalLog = console.log
155
+ console.log = (...args: unknown[]) => {
156
+ logs.push(args.map(String).join(' '))
157
+ }
158
+
159
+ try {
160
+ await execute([
161
+ {
162
+ type: 'log',
163
+ level: 'info',
164
+ message: 'Test message'
165
+ }
166
+ ], ctx)
167
+
168
+ expect(logs.some(log => log.includes('Test message'))).toBe(true)
169
+ } finally {
170
+ console.log = originalLog
171
+ }
172
+ })
173
+ })
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 数组操作动作测试
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest'
6
+ import { createRuntimeContext } from '../../../src/runtime/create-context.js'
7
+ import { execute } from '../../../src/vm/executor.js'
8
+ import { ErrorCodes } from '../../../src/errors.js'
9
+
10
+ describe('数组操作动作', () => {
11
+ let ctx: ReturnType<typeof createRuntimeContext>
12
+
13
+ beforeEach(() => {
14
+ ctx = createRuntimeContext({
15
+ items: [1, 2, 3],
16
+ todos: []
17
+ })
18
+ })
19
+
20
+ describe('push', () => {
21
+ it('应该添加元素到数组末尾', async () => {
22
+ await execute([
23
+ { type: 'push', path: 'items', value: 4 }
24
+ ], ctx)
25
+
26
+ expect(ctx.items).toEqual([1, 2, 3, 4])
27
+ })
28
+
29
+ it('应该支持添加多个元素', async () => {
30
+ await execute([
31
+ { type: 'push', path: 'items', items: [4, 5] }
32
+ ], ctx)
33
+
34
+ expect(ctx.items).toEqual([1, 2, 3, 4, 5])
35
+ })
36
+
37
+ it('应该在路径不是数组时抛出错误', async () => {
38
+ try {
39
+ await execute([
40
+ { type: 'push', path: 'items', value: 4 }
41
+ ], createRuntimeContext({ items: 'not-array' }))
42
+ expect.fail('Should have thrown error')
43
+ } catch (error: any) {
44
+ expect(error.code).toBe(ErrorCodes.ACTION_INVALID_PARAM)
45
+ }
46
+ })
47
+ })
48
+
49
+ describe('pop', () => {
50
+ it('应该删除数组末尾元素', async () => {
51
+ await execute([
52
+ { type: 'pop', path: 'items' }
53
+ ], ctx)
54
+
55
+ expect(ctx.items).toEqual([1, 2])
56
+ })
57
+
58
+ it('应该处理空数组', async () => {
59
+ await execute([
60
+ { type: 'pop', path: 'todos' }
61
+ ], ctx)
62
+
63
+ expect(ctx.todos).toEqual([])
64
+ })
65
+ })
66
+
67
+ describe('shift', () => {
68
+ it('应该删除数组首元素', async () => {
69
+ await execute([
70
+ { type: 'shift', path: 'items' }
71
+ ], ctx)
72
+
73
+ expect(ctx.items).toEqual([2, 3])
74
+ })
75
+ })
76
+
77
+ describe('unshift', () => {
78
+ it('应该添加元素到数组开头', async () => {
79
+ await execute([
80
+ { type: 'unshift', path: 'items', value: 0 }
81
+ ], ctx)
82
+
83
+ expect(ctx.items).toEqual([0, 1, 2, 3])
84
+ })
85
+ })
86
+
87
+ describe('splice', () => {
88
+ it('应该删除元素', async () => {
89
+ await execute([
90
+ { type: 'splice', path: 'items', start: 1, deleteCount: 1 }
91
+ ], ctx)
92
+
93
+ expect(ctx.items).toEqual([1, 3])
94
+ })
95
+
96
+ it('应该替换元素', async () => {
97
+ await execute([
98
+ { type: 'splice', path: 'items', start: 1, deleteCount: 1, items: [99] }
99
+ ], ctx)
100
+
101
+ expect(ctx.items).toEqual([1, 99, 3])
102
+ })
103
+
104
+ it('应该支持表达式参数', async () => {
105
+ ctx.count = 1
106
+ await execute([
107
+ { type: 'splice', path: 'items', start: '{{ count }}', deleteCount: 1 }
108
+ ], ctx)
109
+
110
+ expect(ctx.items).toEqual([1, 3])
111
+ })
112
+ })
113
+ })
@@ -0,0 +1,93 @@
1
+ /**
2
+ * call 指令处理器测试
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest'
6
+ import { createRuntimeContext } from '../../../src/runtime/create-context'
7
+ import { execute } from '../../../src/vm/executor'
8
+ import { InstructionError, ServiceError } from '../../../src/types'
9
+
10
+ describe('call 指令', () => {
11
+ let ctx: ReturnType<typeof createRuntimeContext>
12
+
13
+ beforeEach(() => {
14
+ // createRuntimeContext 现在会自动注册内置指令
15
+ ctx = createRuntimeContext({
16
+ user: { name: 'John', age: 30 },
17
+ result: null
18
+ })
19
+ })
20
+
21
+ it('应该调用已注册的方法', async () => {
22
+ // 注册一个测试方法
23
+ ctx.$methods['test.method'] = async (ctx, params: Record<string, unknown>) => {
24
+ return { success: true, value: params.value }
25
+ }
26
+
27
+ await execute([
28
+ {
29
+ type: 'call',
30
+ method: 'test.method',
31
+ params: { value: 123 }
32
+ }
33
+ ], ctx)
34
+ })
35
+
36
+ it('应该将结果保存到 resultTo', async () => {
37
+ ctx.$methods['test.return'] = async () => {
38
+ return { data: 'result' }
39
+ }
40
+
41
+ await execute([
42
+ {
43
+ type: 'call',
44
+ method: 'test.return',
45
+ resultTo: 'result'
46
+ }
47
+ ], ctx)
48
+
49
+ expect(ctx.result).toEqual({ data: 'result' })
50
+ })
51
+
52
+ it('应该支持表达式参数', async () => {
53
+ ctx.$methods['test.echo'] = async (ctx, params: Record<string, unknown>) => {
54
+ return params.value
55
+ }
56
+
57
+ await execute([
58
+ {
59
+ type: 'call',
60
+ method: 'test.echo',
61
+ params: {
62
+ value: '{{ user.name }}'
63
+ }
64
+ }
65
+ ], ctx)
66
+ })
67
+
68
+ it('应该在方法不存在时抛出错误', async () => {
69
+ await expect(
70
+ execute([
71
+ {
72
+ type: 'call',
73
+ method: 'nonexistent.method'
74
+ }
75
+ ], ctx)
76
+ ).rejects.toThrow(InstructionError)
77
+ })
78
+
79
+ it('应该在方法执行失败时抛出 ServiceError', async () => {
80
+ ctx.$methods['test.error'] = async () => {
81
+ throw new Error('Test error')
82
+ }
83
+
84
+ await expect(
85
+ execute([
86
+ {
87
+ type: 'call',
88
+ method: 'test.error'
89
+ }
90
+ ], ctx)
91
+ ).rejects.toThrow(ServiceError)
92
+ })
93
+ })
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Vario 错误处理体系
3
+ *
4
+ * 统一的错误基类和错误码系统
5
+ */
6
+ import type { Action } from './types.js';
7
+ /**
8
+ * 错误上下文信息
9
+ */
10
+ export interface ErrorContext {
11
+ /** Schema 路径(如 "events.click[0]") */
12
+ schemaPath?: string;
13
+ /** 表达式字符串 */
14
+ expression?: string;
15
+ /** 动作对象 */
16
+ action?: Action;
17
+ /** 调用栈(简化版) */
18
+ stack?: string[];
19
+ /** 额外上下文信息 */
20
+ metadata?: Record<string, unknown>;
21
+ }
22
+ /**
23
+ * Vario 错误基类
24
+ *
25
+ * 所有 Vario 相关错误都应继承此类
26
+ */
27
+ export declare class VarioError extends Error {
28
+ /** 错误码 */
29
+ readonly code: string;
30
+ /** 错误上下文 */
31
+ readonly context: ErrorContext;
32
+ constructor(message: string, code: string, context?: ErrorContext);
33
+ /**
34
+ * 获取友好的错误消息
35
+ */
36
+ getFriendlyMessage(): string;
37
+ /**
38
+ * 转换为 JSON(用于序列化)
39
+ */
40
+ toJSON(): Record<string, unknown>;
41
+ }
42
+ /**
43
+ * 动作执行错误
44
+ */
45
+ export declare class ActionError extends VarioError {
46
+ constructor(action: Action, message: string, code?: string, context?: Omit<ErrorContext, 'action'>);
47
+ }
48
+ /**
49
+ * 表达式求值错误
50
+ */
51
+ export declare class ExpressionError extends VarioError {
52
+ constructor(expression: string, message: string, code?: string, context?: Omit<ErrorContext, 'expression'>);
53
+ }
54
+ /**
55
+ * 服务调用错误
56
+ */
57
+ export declare class ServiceError extends VarioError {
58
+ readonly service: string;
59
+ readonly originalError?: Error;
60
+ constructor(service: string, message: string, originalError?: Error, context?: ErrorContext);
61
+ }
62
+ /**
63
+ * 批量执行错误
64
+ */
65
+ export declare class BatchError extends VarioError {
66
+ readonly failedActions: Array<{
67
+ action: Action;
68
+ error: Error;
69
+ }>;
70
+ constructor(failedActions: Array<{
71
+ action: Action;
72
+ error: Error;
73
+ }>, message: string, context?: ErrorContext);
74
+ }
75
+ /**
76
+ * 错误码定义
77
+ */
78
+ export declare const ErrorCodes: {
79
+ readonly ACTION_UNKNOWN_TYPE: "ACTION_UNKNOWN_TYPE";
80
+ readonly ACTION_EXECUTION_ERROR: "ACTION_EXECUTION_ERROR";
81
+ readonly ACTION_ABORTED: "ACTION_ABORTED";
82
+ readonly ACTION_TIMEOUT: "ACTION_TIMEOUT";
83
+ readonly ACTION_MAX_STEPS_EXCEEDED: "ACTION_MAX_STEPS_EXCEEDED";
84
+ readonly ACTION_MISSING_PARAM: "ACTION_MISSING_PARAM";
85
+ readonly ACTION_INVALID_PARAM: "ACTION_INVALID_PARAM";
86
+ readonly EXPRESSION_PARSE_ERROR: "EXPRESSION_PARSE_ERROR";
87
+ readonly EXPRESSION_VALIDATION_ERROR: "EXPRESSION_VALIDATION_ERROR";
88
+ readonly EXPRESSION_EVALUATION_ERROR: "EXPRESSION_EVALUATION_ERROR";
89
+ readonly EXPRESSION_TIMEOUT: "EXPRESSION_TIMEOUT";
90
+ readonly EXPRESSION_MAX_STEPS_EXCEEDED: "EXPRESSION_MAX_STEPS_EXCEEDED";
91
+ readonly EXPRESSION_UNSAFE_ACCESS: "EXPRESSION_UNSAFE_ACCESS";
92
+ readonly EXPRESSION_FUNCTION_NOT_WHITELISTED: "EXPRESSION_FUNCTION_NOT_WHITELISTED";
93
+ readonly SERVICE_NOT_FOUND: "SERVICE_NOT_FOUND";
94
+ readonly SERVICE_CALL_ERROR: "SERVICE_CALL_ERROR";
95
+ readonly BATCH_ERROR: "BATCH_ERROR";
96
+ readonly SCHEMA_VALIDATION_ERROR: "SCHEMA_VALIDATION_ERROR";
97
+ readonly SCHEMA_INVALID_ACTION: "SCHEMA_INVALID_ACTION";
98
+ };
99
+ export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
100
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,UAAU;IACV,SAAgB,IAAI,EAAE,MAAM,CAAA;IAC5B,YAAY;IACZ,SAAgB,OAAO,EAAE,YAAY,CAAA;gBAGnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,YAAiB;IAa5B;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAkB5B;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASlC;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,UAAU;gBAEvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAuB,EAC7B,OAAO,GAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAM;CAQ7C;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,UAAU;gBAE3C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAA2B,EACjC,OAAO,GAAE,IAAI,CAAC,YAAY,EAAE,YAAY,CAAM;CAQjD;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C,SAAgB,OAAO,EAAE,MAAM,CAAA;IAC/B,SAAgB,aAAa,CAAC,EAAE,KAAK,CAAA;gBAGnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,KAAK,EACrB,OAAO,GAAE,YAAiB;CAO7B;AAED;;GAEG;AACH,qBAAa,UAAW,SAAQ,UAAU;IACxC,SAAgB,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;gBAGpE,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,EACtD,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,YAAiB;CAM7B;AAED;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;CA6Bb,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA"}