@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
package/src/types.ts ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Vario Core Types
3
+ *
4
+ * 核心类型定义,遵循架构图设计:
5
+ * - RuntimeContext: 扁平化状态 + $ 前缀系统 API
6
+ * - Action: 动作接口
7
+ * - ExpressionCache: 表达式缓存
8
+ */
9
+
10
+ /**
11
+ * 方法处理器类型
12
+ * 所有注册到 $methods 的方法必须符合此签名
13
+ *
14
+ * 注意:动作处理器也通过 $methods 注册,但它们的参数是 Action
15
+ */
16
+ export type MethodHandler<TParams = unknown, TResult = unknown> = (
17
+ ctx: RuntimeContext,
18
+ params: TParams
19
+ ) => Promise<TResult> | TResult
20
+
21
+ /**
22
+ * 动作处理器类型(特殊的方法处理器)
23
+ */
24
+ export type ActionHandler = MethodHandler<Action, void>
25
+
26
+ /**
27
+ * 方法注册表类型
28
+ * 支持普通方法和指令处理器
29
+ */
30
+ export type MethodsRegistry = Record<string, MethodHandler>
31
+
32
+ /**
33
+ * 表达式求值选项
34
+ */
35
+ export interface ExpressionOptions {
36
+ /**
37
+ * 是否允许访问全局对象(默认 false)
38
+ */
39
+ allowGlobals?: boolean
40
+ /**
41
+ * 最大求值步数
42
+ */
43
+ maxSteps?: number
44
+ /**
45
+ * 求值超时(毫秒)
46
+ */
47
+ timeout?: number
48
+ /**
49
+ * 最大嵌套深度(防止 DoS 攻击,默认 50)
50
+ */
51
+ maxNestingDepth?: number
52
+ }
53
+
54
+ /**
55
+ * 运行时上下文接口
56
+ * 扁平化状态设计:直接属性访问,无 models. 前缀
57
+ *
58
+ * @template TState 状态类型,用于类型推导和约束
59
+ *
60
+ * 设计说明:
61
+ * - 使用接口 + 索引签名,既支持类型推导又支持动态属性
62
+ * - 状态属性通过 TState 泛型约束
63
+ * - 系统 API 使用具体类型定义,确保类型安全
64
+ */
65
+ /**
66
+ * 运行时上下文类型
67
+ * 使用交叉类型结合接口,既支持状态类型推导又支持动态属性
68
+ */
69
+ export type RuntimeContext<TState extends Record<string, unknown> = Record<string, unknown>> =
70
+ // 状态属性(从 TState 推导)
71
+ TState &
72
+ // 系统 API($ 前缀)
73
+ {
74
+ $emit: (event: string, data?: unknown) => void
75
+ $methods: MethodsRegistry
76
+ $exprOptions?: ExpressionOptions
77
+ $event?: Event // 事件对象(在事件处理中可用)
78
+ $item?: TState[keyof TState] // 循环当前项(在 Table/loop 中可用)
79
+ $index?: number // 循环索引(在 Table/loop 中可用)
80
+ // 内部方法(路径解析,不对外暴露)
81
+ _get: <TPath extends string>(path: TPath) => GetPathValue<TState, TPath>
82
+ _set: <TPath extends string>(path: TPath, value: SetPathValue<TState, TPath>, options?: { skipCallback?: boolean }) => void
83
+ } &
84
+ // 允许动态添加状态属性(运行时扩展)
85
+ Record<string, unknown>
86
+
87
+ /**
88
+ * 路径值类型推导工具
89
+ * 根据路径字符串推导对应的值类型
90
+ *
91
+ * @example
92
+ * GetPathValue<{ user: { name: string } }, 'user.name'> // string
93
+ * GetPathValue<{ items: number[] }, 'items.0'> // number
94
+ */
95
+ export type GetPathValue<T, TPath extends string> =
96
+ TPath extends `${infer Key}.${infer Rest}`
97
+ ? Key extends keyof T
98
+ ? T[Key] extends Record<string, unknown>
99
+ ? GetPathValue<T[Key], Rest>
100
+ : unknown
101
+ : unknown
102
+ : TPath extends keyof T
103
+ ? T[TPath]
104
+ : unknown
105
+
106
+ /**
107
+ * 路径设置值类型推导工具
108
+ * 根据路径字符串推导可以设置的值类型
109
+ */
110
+ export type SetPathValue<T, TPath extends string> = GetPathValue<T, TPath>
111
+
112
+ /**
113
+ * 动作接口
114
+ * 使用泛型约束,根据 type 类型推导参数结构
115
+ */
116
+ export interface Action {
117
+ type: string
118
+ [key: string]: unknown
119
+ }
120
+
121
+ /**
122
+ * 动作类型映射
123
+ * 根据 type 值推导对应的动作参数类型
124
+ */
125
+ export type ActionMap = {
126
+ set: { path: string; value: string | unknown }
127
+ emit: { event: string; data?: string | unknown }
128
+ navigate: { to: string }
129
+ log: { level?: 'info' | 'warn' | 'error'; message: string }
130
+ if: { cond: string; then?: Action[]; else?: Action[] }
131
+ loop: { var: string; in: string; body: Action[] }
132
+ call: { method: string; params?: Record<string, unknown>; resultTo?: string }
133
+ batch: { actions: Action[] }
134
+ push: { path: string; value: string | unknown }
135
+ pop: { path: string }
136
+ shift: { path: string }
137
+ unshift: { path: string; value: string | unknown }
138
+ splice: { path: string; start: number | string; deleteCount?: number | string; items?: string | unknown[] }
139
+ }
140
+
141
+ /**
142
+ * 类型守卫:检查动作是否符合特定类型
143
+ */
144
+ export function isActionOfType<T extends keyof ActionMap>(
145
+ action: Action,
146
+ actionType: T
147
+ ): action is Action & ActionMap[T] {
148
+ return action.type === actionType
149
+ }
150
+
151
+ /**
152
+ * 表达式缓存接口
153
+ * 结果类型使用 unknown,因为表达式求值结果类型无法静态推导
154
+ */
155
+ export interface ExpressionCache {
156
+ expr: string
157
+ result: unknown // 表达式求值结果,类型无法静态推导
158
+ dependencies: string[] // 支持通配符:['items.*', 'user.name']
159
+ timestamp: number
160
+ }
161
+
162
+ /**
163
+ * 错误类型定义
164
+ *
165
+ * @deprecated 使用 @vario/core/errors 中的错误类
166
+ * 保留这些导出以保持向后兼容,但建议使用新的错误体系
167
+ */
168
+ export {
169
+ ActionError,
170
+ ExpressionError,
171
+ ServiceError,
172
+ BatchError,
173
+ VarioError,
174
+ ErrorCodes,
175
+ type ErrorContext,
176
+ type ErrorCode
177
+ } from './errors.js'
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Action VM 错误类型
3
+ */
4
+
5
+ export {
6
+ ActionError,
7
+ ExpressionError,
8
+ ServiceError,
9
+ BatchError
10
+ } from '../types.js'
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Action VM 执行器
3
+ *
4
+ * 功能:
5
+ * - 执行动作序列
6
+ * - 通过 $methods 查找动作处理器
7
+ * - 错误处理和堆栈跟踪
8
+ * - 超时保护(防止无限循环)
9
+ *
10
+ * 参考架构图:vario-core/vm - Action VM
11
+ */
12
+
13
+ import type { RuntimeContext, Action } from '../types.js'
14
+ import { ActionError, ServiceError, ErrorCodes } from '../errors.js'
15
+
16
+ /**
17
+ * 执行动作序列的选项
18
+ */
19
+ export interface ExecuteOptions {
20
+ /**
21
+ * 超时时间(毫秒),默认 5000ms
22
+ */
23
+ timeout?: number
24
+ /**
25
+ * 最大执行步数,默认 10000
26
+ * 每执行一个动作计为一步
27
+ */
28
+ maxSteps?: number
29
+ }
30
+
31
+ /**
32
+ * 执行动作序列
33
+ *
34
+ * @param actions 动作数组
35
+ * @param ctx 运行时上下文
36
+ * @param options 执行选项
37
+ */
38
+ export async function execute(
39
+ actions: Action[],
40
+ ctx: RuntimeContext,
41
+ options: ExecuteOptions = {}
42
+ ): Promise<void> {
43
+ const timeout = options.timeout ?? 5000
44
+ const maxSteps = options.maxSteps ?? 10000
45
+ const startTime = Date.now()
46
+ let stepCount = 0
47
+
48
+ // 创建 AbortController 用于超时中断(如果支持)
49
+ let abortController: AbortController | null = null
50
+ if (typeof AbortController !== 'undefined') {
51
+ abortController = new AbortController()
52
+
53
+ // 设置超时定时器
54
+ const timeoutId = setTimeout(() => {
55
+ abortController?.abort()
56
+ }, timeout)
57
+
58
+ // 清理定时器(如果执行完成)
59
+ const cleanup = () => clearTimeout(timeoutId)
60
+
61
+ try {
62
+ await executeActions(actions, ctx, abortController.signal, maxSteps, () => {
63
+ stepCount++
64
+ if (stepCount > maxSteps) {
65
+ throw new ActionError(
66
+ actions[actions.length - 1] || { type: 'unknown' } as Action,
67
+ `Action execution exceeded max steps (${maxSteps})`,
68
+ ErrorCodes.ACTION_MAX_STEPS_EXCEEDED,
69
+ {
70
+ metadata: {
71
+ maxSteps,
72
+ currentSteps: stepCount
73
+ }
74
+ }
75
+ )
76
+ }
77
+ // 检查超时
78
+ if (Date.now() - startTime > timeout) {
79
+ throw new ActionError(
80
+ actions[actions.length - 1] || { type: 'unknown' } as Action,
81
+ `Action execution exceeded timeout (${timeout}ms)`,
82
+ ErrorCodes.ACTION_TIMEOUT,
83
+ {
84
+ metadata: {
85
+ timeout,
86
+ elapsedTime: Date.now() - startTime
87
+ }
88
+ }
89
+ )
90
+ }
91
+ })
92
+ cleanup()
93
+ } catch (error) {
94
+ cleanup()
95
+ throw error
96
+ }
97
+ } else {
98
+ // 不支持 AbortController 的环境,使用时间检查
99
+ await executeActions(actions, ctx, null, maxSteps, () => {
100
+ stepCount++
101
+ if (stepCount > maxSteps) {
102
+ throw new ActionError(
103
+ actions[actions.length - 1] || { type: 'unknown' } as Action,
104
+ `Action execution exceeded max steps (${maxSteps})`,
105
+ ErrorCodes.ACTION_MAX_STEPS_EXCEEDED,
106
+ {
107
+ metadata: {
108
+ maxSteps,
109
+ currentSteps: stepCount
110
+ }
111
+ }
112
+ )
113
+ }
114
+ // 检查超时
115
+ if (Date.now() - startTime > timeout) {
116
+ throw new ActionError(
117
+ actions[actions.length - 1] || { type: 'unknown' } as Action,
118
+ `Action execution exceeded timeout (${timeout}ms)`,
119
+ ErrorCodes.ACTION_TIMEOUT,
120
+ {
121
+ metadata: {
122
+ timeout,
123
+ elapsedTime: Date.now() - startTime
124
+ }
125
+ }
126
+ )
127
+ }
128
+ })
129
+ }
130
+
131
+ // 最终检查超时(即使有 AbortController,也做双重检查)
132
+ if (Date.now() - startTime > timeout) {
133
+ throw new ActionError(
134
+ actions[actions.length - 1] || { type: 'unknown' } as Action,
135
+ `Action execution exceeded timeout (${timeout}ms)`,
136
+ ErrorCodes.ACTION_TIMEOUT,
137
+ {
138
+ metadata: {
139
+ timeout,
140
+ elapsedTime: Date.now() - startTime,
141
+ actionCount: actions.length
142
+ }
143
+ }
144
+ )
145
+ }
146
+ }
147
+
148
+ /**
149
+ * 执行动作序列(内部实现)
150
+ */
151
+ async function executeActions(
152
+ actions: Action[],
153
+ ctx: RuntimeContext,
154
+ signal: AbortSignal | null,
155
+ _maxSteps: number, // 用于类型,实际限制在 checkLimits 中检查
156
+ checkLimits: () => void
157
+ ): Promise<void> {
158
+ for (const action of actions) {
159
+ // 检查中断信号
160
+ if (signal?.aborted) {
161
+ throw new ActionError(
162
+ action,
163
+ 'Action execution was aborted',
164
+ ErrorCodes.ACTION_ABORTED
165
+ )
166
+ }
167
+
168
+ // 检查步数和超时限制
169
+ checkLimits()
170
+
171
+ // 所有动作(包括内置动作)统一通过 $methods 注册
172
+ const handler = ctx.$methods[action.type]
173
+
174
+ if (!handler) {
175
+ throw new ActionError(
176
+ action,
177
+ `Unknown action type: ${action.type}. Make sure the action is registered in $methods`,
178
+ ErrorCodes.ACTION_UNKNOWN_TYPE
179
+ )
180
+ }
181
+
182
+ try {
183
+ await handler(ctx, action)
184
+ } catch (error: unknown) {
185
+ // 如果已经是 ActionError,直接抛出
186
+ if (error instanceof ActionError) {
187
+ throw error
188
+ }
189
+
190
+ // 如果已经是 ServiceError,直接抛出
191
+ if (error instanceof ServiceError) {
192
+ throw error
193
+ }
194
+
195
+ // 包装为 ActionError,收集上下文信息
196
+ const errorMessage = error instanceof Error ? error.message : String(error)
197
+ throw new ActionError(
198
+ action,
199
+ `Action execution failed: ${errorMessage}`,
200
+ ErrorCodes.ACTION_EXECUTION_ERROR,
201
+ {
202
+ metadata: {
203
+ originalError: error instanceof Error ? error.name : 'Unknown',
204
+ stack: error instanceof Error ? error.stack?.split('\n').slice(0, 5) : undefined
205
+ }
206
+ }
207
+ )
208
+ }
209
+ }
210
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * pop 动作处理器
3
+ *
4
+ * 功能:删除数组末尾元素
5
+ * 示例:{ "type": "pop", "path": "items" }
6
+ */
7
+
8
+ import type { RuntimeContext, Action } from '@/types.js'
9
+ import { ActionError, ErrorCodes } from '@/errors.js'
10
+ import { invalidateCache } from '@/expression/cache.js'
11
+
12
+ /**
13
+ * 处理 pop 动作
14
+ */
15
+ export async function handlePop(
16
+ ctx: RuntimeContext,
17
+ action: Action
18
+ ): Promise<void> {
19
+ const { path } = action
20
+
21
+ if (!path || typeof path !== 'string') {
22
+ throw new ActionError(
23
+ action,
24
+ 'pop action requires "path" parameter',
25
+ ErrorCodes.ACTION_MISSING_PARAM,
26
+ { metadata: { param: 'path' } }
27
+ )
28
+ }
29
+
30
+ // 获取数组
31
+ const array = ctx._get(path)
32
+ if (!Array.isArray(array)) {
33
+ throw new ActionError(
34
+ action,
35
+ `Path "${path}" does not point to an array`,
36
+ ErrorCodes.ACTION_INVALID_PARAM,
37
+ { metadata: { param: 'path', path, actualType: typeof array } }
38
+ )
39
+ }
40
+
41
+ // 删除末尾元素
42
+ array.pop()
43
+
44
+ // 使相关缓存失效
45
+ invalidateCache(path, ctx)
46
+ invalidateCache(`${path}.*`, ctx)
47
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * push 动作处理器
3
+ *
4
+ * 功能:添加元素到数组末尾
5
+ * 示例:{ "type": "push", "path": "items", "value": "{{ newItem }}" }
6
+ * 示例:{ "type": "push", "path": "todos", "items": [{ text: "{{ newTodo }}", done: false }] }
7
+ */
8
+
9
+ import type { RuntimeContext, Action } from '@/types.js'
10
+ import { ActionError, ErrorCodes } from '@/errors.js'
11
+ import { invalidateCache } from '@/expression/cache.js'
12
+ import { evaluateExpressionsRecursively } from './utils.js'
13
+
14
+ /**
15
+ * 处理 push 动作
16
+ */
17
+ export async function handlePush(
18
+ ctx: RuntimeContext,
19
+ action: Action
20
+ ): Promise<void> {
21
+ const { path, value, items } = action
22
+
23
+ if (!path || typeof path !== 'string') {
24
+ throw new ActionError(
25
+ action,
26
+ 'push action requires "path" parameter',
27
+ ErrorCodes.ACTION_MISSING_PARAM,
28
+ { metadata: { param: 'path' } }
29
+ )
30
+ }
31
+
32
+ // 获取数组
33
+ const array = ctx._get(path)
34
+ if (!Array.isArray(array)) {
35
+ throw new ActionError(
36
+ action,
37
+ `Path "${path}" does not point to an array`,
38
+ ErrorCodes.ACTION_INVALID_PARAM,
39
+ { metadata: { param: 'path', path, actualType: typeof array } }
40
+ )
41
+ }
42
+
43
+ // 支持 items 作为 value 的别名
44
+ const inputValue = items !== undefined ? items : value
45
+
46
+ if (inputValue === undefined) {
47
+ throw new ActionError(
48
+ action,
49
+ 'push action requires "value" or "items" parameter',
50
+ ErrorCodes.ACTION_MISSING_PARAM,
51
+ { metadata: { param: 'value|items' } }
52
+ )
53
+ }
54
+
55
+ // 递归求值表达式(支持对象/数组中的表达式)
56
+ const finalValue = evaluateExpressionsRecursively(inputValue, ctx)
57
+
58
+ // 如果是数组,展开添加
59
+ if (Array.isArray(finalValue)) {
60
+ array.push(...finalValue)
61
+ } else {
62
+ array.push(finalValue)
63
+ }
64
+
65
+ // 使相关缓存失效
66
+ invalidateCache(path, ctx)
67
+ invalidateCache(`${path}.*`, ctx)
68
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * shift 动作处理器
3
+ *
4
+ * 功能:删除数组首元素
5
+ * 示例:{ "type": "shift", "path": "items" }
6
+ */
7
+
8
+ import type { RuntimeContext, Action } from '@/types.js'
9
+ import { ActionError, ErrorCodes } from '@/errors.js'
10
+ import { invalidateCache } from '@/expression/cache.js'
11
+
12
+ /**
13
+ * 处理 shift 动作
14
+ */
15
+ export async function handleShift(
16
+ ctx: RuntimeContext,
17
+ action: Action
18
+ ): Promise<void> {
19
+ const { path } = action
20
+
21
+ if (!path || typeof path !== 'string') {
22
+ throw new ActionError(
23
+ action,
24
+ 'shift action requires "path" parameter',
25
+ ErrorCodes.ACTION_MISSING_PARAM,
26
+ { metadata: { param: 'path' } }
27
+ )
28
+ }
29
+
30
+ // 获取数组
31
+ const array = ctx._get(path)
32
+ if (!Array.isArray(array)) {
33
+ throw new ActionError(
34
+ action,
35
+ `Path "${path}" does not point to an array`,
36
+ ErrorCodes.ACTION_INVALID_PARAM,
37
+ { metadata: { param: 'path', path, actualType: typeof array } }
38
+ )
39
+ }
40
+
41
+ // 删除首元素
42
+ array.shift()
43
+
44
+ // 使相关缓存失效
45
+ invalidateCache(path, ctx)
46
+ invalidateCache(`${path}.*`, ctx)
47
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * splice 动作处理器
3
+ *
4
+ * 功能:删除或替换数组元素
5
+ * 示例:{ "type": "splice", "path": "items", "start": 0, "deleteCount": 1, "items": "{{ newItem }}" }
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 { evaluateExpressionsRecursively } from './utils.js'
13
+
14
+ /**
15
+ * 处理 splice 动作
16
+ */
17
+ export async function handleSplice(
18
+ ctx: RuntimeContext,
19
+ action: Action
20
+ ): Promise<void> {
21
+ const { path, start, deleteCount = 0, items } = action
22
+
23
+ if (!path || typeof path !== 'string') {
24
+ throw new ActionError(
25
+ action,
26
+ 'splice action requires "path" parameter',
27
+ ErrorCodes.ACTION_MISSING_PARAM,
28
+ { metadata: { param: 'path' } }
29
+ )
30
+ }
31
+
32
+ // 获取数组
33
+ const array = ctx._get(path)
34
+ if (!Array.isArray(array)) {
35
+ throw new ActionError(
36
+ action,
37
+ `Path "${path}" does not point to an array`,
38
+ ErrorCodes.ACTION_INVALID_PARAM,
39
+ { metadata: { param: 'path', path, actualType: typeof array } }
40
+ )
41
+ }
42
+
43
+ // 求值 start(支持表达式)
44
+ let finalStart: number
45
+ if (typeof start === 'string' && start.startsWith('{{') && start.endsWith('}}')) {
46
+ const expr = start.slice(2, -2).trim()
47
+ finalStart = Number(evaluate(expr, ctx) as number)
48
+ } else {
49
+ finalStart = Number(start as number)
50
+ }
51
+
52
+ // 求值 deleteCount(支持表达式)
53
+ let finalDeleteCount: number
54
+ if (typeof deleteCount === 'string' && deleteCount.startsWith('{{') && deleteCount.endsWith('}}')) {
55
+ const expr = deleteCount.slice(2, -2).trim()
56
+ finalDeleteCount = Number(evaluate(expr, ctx) as number)
57
+ } else {
58
+ finalDeleteCount = Number(deleteCount as number)
59
+ }
60
+
61
+ // 求值 items(支持递归表达式)
62
+ let finalItems: unknown[] = []
63
+ if (items != null) {
64
+ const evaluated = evaluateExpressionsRecursively(items, ctx)
65
+ if (Array.isArray(evaluated)) {
66
+ finalItems = evaluated
67
+ } else {
68
+ finalItems = [evaluated]
69
+ }
70
+ }
71
+
72
+ // 执行 splice
73
+ array.splice(finalStart, finalDeleteCount, ...finalItems)
74
+
75
+ // 使相关缓存失效
76
+ invalidateCache(path, ctx)
77
+ invalidateCache(`${path}.*`, ctx)
78
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * unshift 动作处理器
3
+ *
4
+ * 功能:添加元素到数组开头
5
+ * 示例:{ "type": "unshift", "path": "items", "value": "{{ firstItem }}" }
6
+ * 示例:{ "type": "unshift", "path": "todos", "items": [{ text: "{{ newTodo }}", done: false }] }
7
+ */
8
+
9
+ import type { RuntimeContext, Action } from '@/types.js'
10
+ import { ActionError, ErrorCodes } from '@/errors.js'
11
+ import { invalidateCache } from '@/expression/cache.js'
12
+ import { evaluateExpressionsRecursively } from './utils.js'
13
+
14
+ /**
15
+ * 处理 unshift 动作
16
+ */
17
+ export async function handleUnshift(
18
+ ctx: RuntimeContext,
19
+ action: Action
20
+ ): Promise<void> {
21
+ const { path, value, items } = action
22
+
23
+ if (!path || typeof path !== 'string') {
24
+ throw new ActionError(
25
+ action,
26
+ 'unshift action requires "path" parameter',
27
+ ErrorCodes.ACTION_MISSING_PARAM,
28
+ { metadata: { param: 'path' } }
29
+ )
30
+ }
31
+
32
+ // 获取数组
33
+ const array = ctx._get(path)
34
+ if (!Array.isArray(array)) {
35
+ throw new ActionError(
36
+ action,
37
+ `Path "${path}" does not point to an array`,
38
+ ErrorCodes.ACTION_INVALID_PARAM,
39
+ { metadata: { param: 'path', path, actualType: typeof array } }
40
+ )
41
+ }
42
+
43
+ // 支持 items 作为 value 的别名
44
+ const inputValue = items !== undefined ? items : value
45
+
46
+ if (inputValue === undefined) {
47
+ throw new ActionError(
48
+ action,
49
+ 'unshift action requires "value" or "items" parameter',
50
+ ErrorCodes.ACTION_MISSING_PARAM,
51
+ { metadata: { param: 'value|items' } }
52
+ )
53
+ }
54
+
55
+ // 递归求值表达式(支持对象/数组中的表达式)
56
+ const finalValue = evaluateExpressionsRecursively(inputValue, ctx)
57
+
58
+ // 如果是数组,展开添加
59
+ if (Array.isArray(finalValue)) {
60
+ array.unshift(...finalValue)
61
+ } else {
62
+ array.unshift(finalValue)
63
+ }
64
+
65
+ // 使相关缓存失效
66
+ invalidateCache(path, ctx)
67
+ invalidateCache(`${path}.*`, ctx)
68
+ }