@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,134 @@
1
+ /**
2
+ * 循环上下文对象池
3
+ *
4
+ * 功能:
5
+ * - 复用循环上下文对象,减少内存分配
6
+ * - 提升循环性能
7
+ * - 自动清理和重置
8
+ */
9
+
10
+ import type { RuntimeContext } from '../types.js'
11
+
12
+ /**
13
+ * 循环上下文对象池
14
+ */
15
+ class LoopContextPool {
16
+ private pool: Array<Partial<RuntimeContext>> = []
17
+ private maxSize: number
18
+
19
+ constructor(maxSize: number = 10) {
20
+ this.maxSize = maxSize
21
+ }
22
+
23
+ /**
24
+ * 获取一个循环上下文对象
25
+ */
26
+ acquire(): Partial<RuntimeContext> {
27
+ if (this.pool.length > 0) {
28
+ return this.pool.pop()!
29
+ }
30
+ return {}
31
+ }
32
+
33
+ /**
34
+ * 释放循环上下文对象回池中
35
+ */
36
+ release(ctx: Partial<RuntimeContext>): void {
37
+ // 清理循环相关属性
38
+ if ('$item' in ctx) delete ctx.$item
39
+ if ('$index' in ctx) delete ctx.$index
40
+
41
+ // 清理动态添加的循环变量(保留系统 API)
42
+ const keysToDelete: string[] = []
43
+ for (const key in ctx) {
44
+ if (!key.startsWith('$') && !key.startsWith('_')) {
45
+ keysToDelete.push(key)
46
+ }
47
+ }
48
+ for (const key of keysToDelete) {
49
+ delete ctx[key]
50
+ }
51
+
52
+ // 如果池未满,回收对象
53
+ if (this.pool.length < this.maxSize) {
54
+ this.pool.push(ctx)
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 清空对象池
60
+ */
61
+ clear(): void {
62
+ this.pool.length = 0
63
+ }
64
+
65
+ /**
66
+ * 获取池大小
67
+ */
68
+ get size(): number {
69
+ return this.pool.length
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 全局循环上下文对象池实例
75
+ */
76
+ let globalPool: LoopContextPool | null = null
77
+
78
+ /**
79
+ * 获取全局循环上下文对象池
80
+ */
81
+ export function getLoopContextPool(): LoopContextPool {
82
+ if (!globalPool) {
83
+ globalPool = new LoopContextPool()
84
+ }
85
+ return globalPool
86
+ }
87
+
88
+ /**
89
+ * 创建循环上下文(使用对象池)
90
+ *
91
+ * @param parentCtx 父上下文
92
+ * @param item 循环项
93
+ * @param index 循环索引
94
+ * @returns 循环上下文
95
+ */
96
+ export function createLoopContext(
97
+ parentCtx: RuntimeContext,
98
+ item: unknown,
99
+ index: number
100
+ ): RuntimeContext {
101
+ const pool = getLoopContextPool()
102
+ const baseCtx = pool.acquire()
103
+
104
+ // 创建循环上下文(浅拷贝父上下文 + 循环属性)
105
+ const loopCtx = Object.create(parentCtx) as RuntimeContext
106
+
107
+ // 复制基础对象池中的属性(如果有)
108
+ Object.assign(loopCtx, baseCtx)
109
+
110
+ // 设置循环相关属性
111
+ loopCtx.$item = item
112
+ loopCtx.$index = index
113
+
114
+ return loopCtx
115
+ }
116
+
117
+ /**
118
+ * 释放循环上下文(回收到对象池)
119
+ *
120
+ * @param loopCtx 循环上下文
121
+ */
122
+ export function releaseLoopContext(loopCtx: Partial<RuntimeContext>): void {
123
+ const pool = getLoopContextPool()
124
+ pool.release(loopCtx)
125
+ }
126
+
127
+ /**
128
+ * 清空对象池(用于测试或重置)
129
+ */
130
+ export function clearLoopContextPool(): void {
131
+ if (globalPool) {
132
+ globalPool.clear()
133
+ }
134
+ }
@@ -0,0 +1,372 @@
1
+ /**
2
+ * 路径解析工具模块
3
+ *
4
+ * 统一的路径解析逻辑,供 vario-core 和框架集成层使用
5
+ *
6
+ * 设计原则:
7
+ * - 单一职责:只处理路径解析,不涉及响应式
8
+ * - 可组合:提供原子操作,框架集成层可自由组合
9
+ * - 类型安全:尽可能提供类型推导
10
+ */
11
+
12
+ /**
13
+ * 路径段类型
14
+ */
15
+ export type PathSegment = string | number
16
+
17
+ type PathCache = {
18
+ parsed: Map<string, PathSegment[]>
19
+ }
20
+
21
+ const pathCache: PathCache = {
22
+ parsed: new Map()
23
+ }
24
+
25
+ /**
26
+ * 解析路径字符串为段数组
27
+ *
28
+ * 支持两种语法:
29
+ * - 点语法:`user.name` → ['user', 'name']
30
+ * - 括号语法:`users[0].name` → ['users', 0, 'name']
31
+ * - 混合语法:`data.users[0].profile.tags[1]` → ['data', 'users', 0, 'profile', 'tags', 1]
32
+ * - 空括号:`users[].name` → ['users', -1, 'name'](-1 表示动态索引,由循环上下文填充)
33
+ *
34
+ * @param path 路径字符串
35
+ * @returns 路径段数组
36
+ *
37
+ * @example
38
+ * parsePath('user.name') // ['user', 'name']
39
+ * parsePath('items.0.text') // ['items', 0, 'text']
40
+ * parsePath('users[0].name') // ['users', 0, 'name']
41
+ * parsePath('users[].name') // ['users', -1, 'name']
42
+ */
43
+ export function parsePath(path: string): PathSegment[] {
44
+ if (!path || path.length === 0) {
45
+ return []
46
+ }
47
+
48
+ const segments: PathSegment[] = []
49
+ let current = ''
50
+ let i = 0
51
+
52
+ while (i < path.length) {
53
+ const char = path[i]
54
+
55
+ if (char === '.') {
56
+ // 点分隔符:保存当前段
57
+ if (current) {
58
+ segments.push(parseSegment(current))
59
+ current = ''
60
+ }
61
+ i++
62
+ } else if (char === '[') {
63
+ // 括号开始:保存当前段(如果有),然后解析括号内容
64
+ if (current) {
65
+ segments.push(parseSegment(current))
66
+ current = ''
67
+ }
68
+
69
+ // 找到匹配的 ]
70
+ const closeIndex = path.indexOf(']', i)
71
+ if (closeIndex === -1) {
72
+ // 没有匹配的 ],当作普通字符处理
73
+ current += char
74
+ i++
75
+ } else {
76
+ const indexStr = path.slice(i + 1, closeIndex)
77
+ if (indexStr === '') {
78
+ // 空括号 [] 表示动态索引
79
+ segments.push(-1)
80
+ } else if (/^\d+$/.test(indexStr)) {
81
+ // 数字索引
82
+ segments.push(parseInt(indexStr, 10))
83
+ } else {
84
+ // 非数字,当作字符串键
85
+ segments.push(indexStr)
86
+ }
87
+ i = closeIndex + 1
88
+
89
+ // 跳过紧跟的点
90
+ if (path[i] === '.') {
91
+ i++
92
+ }
93
+ }
94
+ } else {
95
+ current += char
96
+ i++
97
+ }
98
+ }
99
+
100
+ // 处理最后一段
101
+ if (current) {
102
+ segments.push(parseSegment(current))
103
+ }
104
+
105
+ return segments
106
+ }
107
+
108
+ /**
109
+ * 解析单个路径段
110
+ */
111
+ function parseSegment(segment: string): PathSegment {
112
+ // 纯数字视为数组索引(保持向后兼容 items.0.text 语法)
113
+ if (/^\d+$/.test(segment)) {
114
+ return parseInt(segment, 10)
115
+ }
116
+ return segment
117
+ }
118
+
119
+ /**
120
+ * 解析路径(带缓存)
121
+ *
122
+ * @param path 点分隔的路径字符串
123
+ * @returns 路径段数组
124
+ */
125
+ export function parsePathCached(path: string): PathSegment[] {
126
+ if (!path || path.length === 0) {
127
+ return []
128
+ }
129
+
130
+ const cached = pathCache.parsed.get(path)
131
+ if (cached) {
132
+ return cached
133
+ }
134
+
135
+ const segments = parsePath(path)
136
+ pathCache.parsed.set(path, segments)
137
+ return segments
138
+ }
139
+
140
+ /**
141
+ * 清理路径缓存
142
+ */
143
+ export function clearPathCache(): void {
144
+ pathCache.parsed.clear()
145
+ }
146
+
147
+ /**
148
+ * 将路径段数组转换为路径字符串
149
+ *
150
+ * @param segments 路径段数组
151
+ * @returns 点分隔的路径字符串
152
+ */
153
+ export function stringifyPath(segments: PathSegment[]): string {
154
+ return segments.map(String).join('.')
155
+ }
156
+
157
+ /**
158
+ * 获取嵌套路径的值
159
+ *
160
+ * @param obj 目标对象
161
+ * @param path 路径字符串或路径段数组
162
+ * @returns 路径对应的值,不存在返回 undefined
163
+ */
164
+ export function getPathValue(
165
+ obj: Record<string, unknown>,
166
+ path: string | PathSegment[]
167
+ ): unknown {
168
+ const segments = typeof path === 'string' ? parsePathCached(path) : path
169
+
170
+ if (segments.length === 0) {
171
+ return obj
172
+ }
173
+
174
+ let value: unknown = obj
175
+
176
+ for (const segment of segments) {
177
+ if (value == null) {
178
+ return undefined
179
+ }
180
+
181
+ if (typeof segment === 'number') {
182
+ // 数组索引
183
+ if (!Array.isArray(value)) {
184
+ return undefined
185
+ }
186
+ value = value[segment]
187
+ } else {
188
+ // 对象属性
189
+ if (typeof value !== 'object') {
190
+ return undefined
191
+ }
192
+ value = (value as Record<string, unknown>)[segment]
193
+ }
194
+ }
195
+
196
+ return value
197
+ }
198
+
199
+ /**
200
+ * 设置嵌套路径的值
201
+ *
202
+ * @param obj 目标对象
203
+ * @param path 路径字符串或路径段数组
204
+ * @param value 要设置的值
205
+ * @param options 配置选项
206
+ * @returns 是否设置成功
207
+ */
208
+ export function setPathValue(
209
+ obj: Record<string, unknown>,
210
+ path: string | PathSegment[],
211
+ value: unknown,
212
+ options: {
213
+ /**
214
+ * 自动创建中间路径
215
+ * @default true
216
+ */
217
+ createIntermediate?: boolean
218
+
219
+ /**
220
+ * 创建对象的工厂函数
221
+ * 用于框架集成层创建响应式对象
222
+ */
223
+ createObject?: () => Record<string, unknown>
224
+
225
+ /**
226
+ * 创建数组的工厂函数
227
+ * 用于框架集成层创建响应式数组
228
+ */
229
+ createArray?: () => unknown[]
230
+ } = {}
231
+ ): boolean {
232
+ const {
233
+ createIntermediate = true,
234
+ createObject = () => ({}),
235
+ createArray = () => []
236
+ } = options
237
+
238
+ const segments = typeof path === 'string' ? parsePathCached(path) : path
239
+
240
+ if (segments.length === 0) {
241
+ return false
242
+ }
243
+
244
+ const lastSegment = segments[segments.length - 1]
245
+ const parentSegments = segments.slice(0, -1)
246
+
247
+ // 找到或创建父对象
248
+ let target: unknown = obj
249
+
250
+ for (let i = 0; i < parentSegments.length; i++) {
251
+ const segment = parentSegments[i]
252
+ const nextSegment = parentSegments[i + 1] ?? lastSegment
253
+ const nextIsArrayIndex = typeof nextSegment === 'number'
254
+
255
+ if (typeof segment === 'number') {
256
+ // 当前段是数组索引
257
+ if (!Array.isArray(target)) {
258
+ if (!createIntermediate) return false
259
+ // 无法将非数组转换为数组
260
+ return false
261
+ }
262
+
263
+ // 确保数组足够长
264
+ while (target.length <= segment) {
265
+ target.push(undefined)
266
+ }
267
+
268
+ // 确保目标位置是对象或数组
269
+ if (target[segment] == null || typeof target[segment] !== 'object') {
270
+ if (!createIntermediate) return false
271
+ target[segment] = nextIsArrayIndex ? createArray() : createObject()
272
+ }
273
+
274
+ target = target[segment]
275
+ } else {
276
+ // 当前段是对象属性
277
+ if (typeof target !== 'object' || target === null) {
278
+ return false
279
+ }
280
+
281
+ const targetObj = target as Record<string, unknown>
282
+
283
+ if (targetObj[segment] == null || typeof targetObj[segment] !== 'object') {
284
+ if (!createIntermediate) return false
285
+ targetObj[segment] = nextIsArrayIndex ? createArray() : createObject()
286
+ }
287
+
288
+ target = targetObj[segment]
289
+ }
290
+ }
291
+
292
+ // 设置最终值
293
+ if (typeof lastSegment === 'number') {
294
+ if (!Array.isArray(target)) {
295
+ return false
296
+ }
297
+ while (target.length <= lastSegment) {
298
+ target.push(undefined)
299
+ }
300
+ target[lastSegment] = value
301
+ } else {
302
+ if (typeof target !== 'object' || target === null) {
303
+ return false
304
+ }
305
+ (target as Record<string, unknown>)[lastSegment] = value
306
+ }
307
+
308
+ return true
309
+ }
310
+
311
+ /**
312
+ * 检查路径是否匹配(支持通配符)
313
+ *
314
+ * @param pattern 模式路径(支持 * 通配符)
315
+ * @param path 目标路径
316
+ * @returns 是否匹配
317
+ *
318
+ * @example
319
+ * matchPath('items.*', 'items.0') // true
320
+ * matchPath('items.*.name', 'items.0.name') // true
321
+ * matchPath('user.name', 'user.name') // true
322
+ * matchPath('user.name', 'user.age') // false
323
+ */
324
+ export function matchPath(pattern: string, path: string): boolean {
325
+ // 精确匹配
326
+ if (pattern === path) {
327
+ return true
328
+ }
329
+
330
+ // 检查是否为父路径
331
+ if (path.startsWith(pattern + '.')) {
332
+ return true
333
+ }
334
+
335
+ // 通配符匹配
336
+ if (pattern.includes('*')) {
337
+ const parentPath = pattern.split('.*')[0]
338
+ return path.startsWith(parentPath + '.') || path === parentPath
339
+ }
340
+
341
+ // 检查是否路径影响模式
342
+ if (pattern.startsWith(path + '.')) {
343
+ return true
344
+ }
345
+
346
+ return false
347
+ }
348
+
349
+ /**
350
+ * 提取路径的父路径
351
+ *
352
+ * @param path 路径字符串
353
+ * @returns 父路径,顶层路径返回空字符串
354
+ */
355
+ export function getParentPath(path: string): string {
356
+ const segments = parsePathCached(path)
357
+ if (segments.length <= 1) {
358
+ return ''
359
+ }
360
+ return stringifyPath(segments.slice(0, -1))
361
+ }
362
+
363
+ /**
364
+ * 获取路径的最后一段
365
+ *
366
+ * @param path 路径字符串
367
+ * @returns 最后一段
368
+ */
369
+ export function getLastSegment(path: string): PathSegment | undefined {
370
+ const segments = parsePathCached(path)
371
+ return segments[segments.length - 1]
372
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Proxy 沙箱实现
3
+ *
4
+ * 功能:
5
+ * - 防止覆盖系统 API($ 和 _ 前缀保护)
6
+ * - 拦截属性设置操作
7
+ */
8
+
9
+ import type { RuntimeContext } from '../types.js'
10
+
11
+ /**
12
+ * 创建受保护的 Proxy 上下文
13
+ * 禁止设置以 $ 或 _ 开头的属性
14
+ *
15
+ * @template T 上下文类型,必须是 RuntimeContext 的子类型
16
+ * @param ctx 运行时上下文对象
17
+ * @returns 受保护的 Proxy 包装的上下文
18
+ */
19
+ export function createProxy<T extends RuntimeContext>(ctx: T): T {
20
+ // 允许设置和删除的特殊变量
21
+ const allowedSpecialVars = ['$event', '$item', '$index', '$methods']
22
+
23
+ return new Proxy(ctx, {
24
+ set(target, prop, value) {
25
+ const propName = String(prop)
26
+
27
+ // 禁止覆盖系统 API
28
+ if (propName.startsWith('$') || propName.startsWith('_')) {
29
+ // 允许设置特殊变量
30
+ if (allowedSpecialVars.includes(propName)) {
31
+ return Reflect.set(target, prop, value)
32
+ }
33
+
34
+ throw new Error(
35
+ `Cannot override system API: ${propName}. ` +
36
+ `Properties starting with "$" or "_" are protected.`
37
+ )
38
+ }
39
+
40
+ return Reflect.set(target, prop, value)
41
+ },
42
+
43
+ get(target, prop) {
44
+ return Reflect.get(target, prop)
45
+ },
46
+
47
+ has(target, prop) {
48
+ return Reflect.has(target, prop)
49
+ },
50
+
51
+ deleteProperty(target, prop) {
52
+ const propName = String(prop)
53
+
54
+ // 禁止删除系统 API
55
+ if (propName.startsWith('$') || propName.startsWith('_')) {
56
+ // 允许删除特殊变量
57
+ if (allowedSpecialVars.includes(propName)) {
58
+ return Reflect.deleteProperty(target, prop)
59
+ }
60
+ throw new Error(`Cannot delete system API: ${propName}`)
61
+ }
62
+
63
+ return Reflect.deleteProperty(target, prop)
64
+ }
65
+ })
66
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 沙箱边界控制
3
+ *
4
+ * 功能:
5
+ * - 表达式层:严格沙箱,无法访问全局对象
6
+ * - 方法层:白名单控制,可访问全局对象
7
+ */
8
+
9
+ import type { RuntimeContext } from '../types.js'
10
+
11
+ /**
12
+ * 创建表达式沙箱上下文
13
+ * 移除全局对象访问能力
14
+ */
15
+ export function createExpressionSandbox(ctx: RuntimeContext): RuntimeContext {
16
+ // 创建受限的上下文副本,移除全局对象
17
+ const sandbox = { ...ctx }
18
+
19
+ // 移除可能的全局对象引用
20
+ // 表达式求值时,只能访问状态属性和白名单函数
21
+
22
+ return sandbox
23
+ }
24
+
25
+ /**
26
+ * 检查属性访问是否安全(用于表达式求值)
27
+ */
28
+ export function isSafePropertyAccess(
29
+ prop: string,
30
+ ctx: RuntimeContext,
31
+ options: { allowGlobals?: boolean } = {}
32
+ ): boolean {
33
+ const allowGlobals = options.allowGlobals ?? ctx.$exprOptions?.allowGlobals ?? false
34
+
35
+ // 禁止访问全局对象(可通过 allowGlobals 开关控制)
36
+ const globalProps = ['window', 'document', 'global', 'globalThis', 'self']
37
+ if (!allowGlobals && globalProps.includes(prop)) {
38
+ return false
39
+ }
40
+
41
+ // 允许访问状态属性和系统 API
42
+ return true
43
+ }