@splicetree/adapter-vue 0.0.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Michael Cocova
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @splicetree/adapter-vue
2
+
3
+ [![version](https://img.shields.io/npm/v/@splicetree/adapter-vue.svg?label=version)](https://www.npmjs.com/package/@splicetree/adapter-vue)
4
+ [![downloads](https://img.shields.io/npm/dm/@splicetree/adapter-vue.svg)](https://npmcharts.com/compare/%40splicetree%2Fadapter-vue?minimal=true)
5
+ [![license](https://img.shields.io/npm/l/@splicetree/adapter-vue.svg)](https://www.npmjs.com/package/@splicetree/adapter-vue)
6
+ [![Website](https://img.shields.io/static/v1?label=Website&message=splicetree.dev&color=blue)](https://www.splicetree.dev)
7
+ [![GitHub](https://img.shields.io/static/v1?label=GitHub&message=splicetree%2Fsplicetree&logo=github)](https://github.com/michaelcocova/splicetree)
8
+
9
+ 提供响应式 `items` 与操作方法,自动监听核心事件刷新视图。
10
+
11
+ ## 简介
12
+
13
+ SpliceTree Vue 适配层,将核心 `items()` 适配为 `shallowRef` 响应式数据,并复用核心 API。
14
+
15
+ ## 官方文档
16
+
17
+ 文档与示例位于 [https://www.splicetree.dev](https://www.splicetree.dev)
18
+
19
+ ## 安装
20
+
21
+ ```sh
22
+ pnpm add @splicetree/adapter-vue
23
+ ```
24
+
25
+ ## 使用
26
+
27
+ ```ts
28
+ import { useSpliceTree } from '@splicetree/adapter-vue'
29
+
30
+ const { items, expand, collapse, toggleExpand } = useSpliceTree(data, {
31
+ defaultExpanded: ['a'],
32
+ })
33
+
34
+ // 在组件中渲染
35
+ // <template>
36
+ // <div v-for="n in items" :key="n.id">{{ n.original.title }}</div>
37
+ // </template>
38
+ ```
39
+
40
+ ## 返回值
41
+
42
+ - `items: ShallowRef<SpliceTreeNode[]>` 响应式可见节点列表
43
+ - 其余方法属性与核心一致(除 `items()`)
44
+
45
+ ## 选项(继承 Core)
46
+
47
+ | 名称 | 类型 | 默认值 | 说明 |
48
+ | ----------------- | -------------------- | ---------- | ------------ |
49
+ | `keyField` | `string` | `'id'` | 主键字段名 |
50
+ | `parentField` | `string` | `'parent'` | 父级字段名 |
51
+ | `plugins` | `SpliceTreePlugin[]` | `[]` | 插件列表 |
52
+ | `defaultExpanded` | `string[]` | `[]` | 初始展开集合 |
53
+
54
+ ## License
55
+
56
+ [MIT](https://github.com/michaelcocova/splicetree/blob/main/LICENSE),仓库地址 [https://github.com/michaelcocova/splicetree](https://github.com/michaelcocova/splicetree)
@@ -0,0 +1,16 @@
1
+ import { Ref, ShallowRef, WritableComputedRef } from "vue";
2
+ import { SpliceTreeData, SpliceTreeData as SpliceTreeData$1, SpliceTreeInstance, SpliceTreeInstance as SpliceTreeInstance$1, SpliceTreeNode, SpliceTreeNode as SpliceTreeNode$1, UseSpliceTreeOptions, UseSpliceTreeOptions as UseSpliceTreeOptions$1 } from "@splicetree/core";
3
+
4
+ //#region src/index.d.ts
5
+ interface UseSpliceTreeReturn<T extends SpliceTreeData$1 = SpliceTreeData$1> extends ShallowRef<Omit<SpliceTreeInstance$1<T>, 'items'>> {
6
+ items: ShallowRef<SpliceTreeNode$1<T>[]>;
7
+ }
8
+ /**
9
+ * Vue 3 适配器
10
+ * - 以 shallowRef 暴露 items,使其在核心派发 visibility 事件时刷新
11
+ * - 保留核心 API 的完整返回(展开/收起/移动等方法)
12
+ * - 适用于任意自定义渲染逻辑与组件绑定
13
+ */
14
+ declare function useSpliceTree<T extends SpliceTreeData$1 = SpliceTreeData$1>(data: Ref<T[]> | T[] | WritableComputedRef<T[]>, options?: UseSpliceTreeOptions$1<T>): UseSpliceTreeReturn<T>;
15
+ //#endregion
16
+ export { type SpliceTreeData, type SpliceTreeInstance, type SpliceTreeNode, type UseSpliceTreeOptions, UseSpliceTreeReturn, useSpliceTree };
package/dist/index.js ADDED
@@ -0,0 +1,450 @@
1
+ import { shallowRef, toValue, watch } from "vue";
2
+
3
+ //#region ../../core/dist/index.js
4
+ /**
5
+ * 轻量事件总线:支持订阅与派发,供核心与插件通信
6
+ */
7
+ function createEmitter() {
8
+ const listeners = /* @__PURE__ */ new Map();
9
+ const on = (name, handler) => {
10
+ if (!listeners.has(name)) listeners.set(name, /* @__PURE__ */ new Set());
11
+ const set = listeners.get(name);
12
+ set.add(handler);
13
+ return () => set.delete(handler);
14
+ };
15
+ const emit = (payload) => {
16
+ const set = listeners.get(payload.name);
17
+ if (!set) return;
18
+ for (const h of set) h(payload);
19
+ };
20
+ return {
21
+ on,
22
+ emit
23
+ };
24
+ }
25
+ /**
26
+ * 创建运行时树节点,并注入必要的 API
27
+ */
28
+ function createSpliceTreeNode(id, original, api) {
29
+ return {
30
+ id,
31
+ original,
32
+ level: 0,
33
+ isExpanded: api.isExpanded,
34
+ hasChildren: api.hasChildren,
35
+ getParent: api.getParent,
36
+ getChildren: api.getChildren,
37
+ toggleExpand: api.toggleExpand
38
+ };
39
+ }
40
+ /**
41
+ * 基于扁平数据构建树结构缓存
42
+ * - by id 的节点 map
43
+ * - parent/children 缓存
44
+ * - 根节点列表
45
+ * 并计算每个节点的 level
46
+ */
47
+ function buildTree(data, keyField, parentField, expandedKeys = /* @__PURE__ */ new Set()) {
48
+ const map = /* @__PURE__ */ new Map();
49
+ const roots = [];
50
+ const parentCache = /* @__PURE__ */ new Map();
51
+ const childrenCache = /* @__PURE__ */ new Map();
52
+ data.forEach((item) => {
53
+ const id = String(Reflect.get(item, keyField || "id"));
54
+ const node = createSpliceTreeNode(id, item, {
55
+ hasChildren: () => !!childrenCache.get(id)?.length,
56
+ getParent: () => parentCache.get(id),
57
+ getChildren: () => childrenCache.get(id) ?? [],
58
+ isExpanded: () => expandedKeys.has(id),
59
+ toggleExpand: () => {
60
+ if (expandedKeys.has(id)) expandedKeys.delete(id);
61
+ else expandedKeys.add(id);
62
+ }
63
+ });
64
+ map.set(id, node);
65
+ });
66
+ for (const id of map.keys()) childrenCache.set(id, []);
67
+ for (const node of map.values()) {
68
+ const parentId = Reflect.get(node.original, parentField || "parentId");
69
+ if (!parentId) {
70
+ parentCache.set(node.id, void 0);
71
+ roots.push(node);
72
+ continue;
73
+ }
74
+ const parentNode = map.get(String(parentId));
75
+ parentCache.set(node.id, parentNode ?? void 0);
76
+ if (parentNode) childrenCache.get(parentNode.id).push(node);
77
+ else roots.push(node);
78
+ }
79
+ const dfs = (node, level) => {
80
+ node.level = level;
81
+ for (const child of childrenCache.get(node.id) ?? []) dfs(child, level + 1);
82
+ };
83
+ for (const r of roots) dfs(r, 0);
84
+ return {
85
+ roots,
86
+ map,
87
+ parentCache,
88
+ childrenCache
89
+ };
90
+ }
91
+ function initDefaultExpansion(map, expanded, def, lvl) {
92
+ const expandAll = () => {
93
+ for (const id of map.keys()) expanded.add(id);
94
+ };
95
+ const expandByLevel = (lv) => {
96
+ for (const n of map.values()) if (n.level < lv) expanded.add(n.id);
97
+ };
98
+ if (def === true) {
99
+ expandAll();
100
+ return;
101
+ }
102
+ if (Array.isArray(def)) {
103
+ for (const id of def) expanded.add(id);
104
+ if (lvl === "deepest") {
105
+ expandAll();
106
+ return;
107
+ }
108
+ if (typeof lvl === "number" && Number.isFinite(lvl) && lvl > 0) expandByLevel(lvl);
109
+ return;
110
+ }
111
+ if (lvl === "deepest") {
112
+ expandAll();
113
+ return;
114
+ }
115
+ if (typeof lvl === "number" && Number.isFinite(lvl) && lvl > 0) expandByLevel(lvl);
116
+ }
117
+ /**
118
+ * 为 Set/Map/Object 创建响应式代理
119
+ * 在集合或对象发生更改时,触发回调以便同步状态(如可见性)
120
+ */
121
+ function createReactive(target, callback) {
122
+ const isSet = target instanceof Set;
123
+ const isMap = target instanceof Map;
124
+ return new Proxy(target, {
125
+ get(obj, prop, receiver) {
126
+ if (isSet) {
127
+ if (prop === "size") return Reflect.get(obj, prop, obj);
128
+ const value = Reflect.get(obj, prop, obj);
129
+ if (prop === "add") return (v) => {
130
+ const existed = obj.has(v);
131
+ const r = obj.add(v);
132
+ if (!existed) callback({
133
+ type: "ADD",
134
+ target: obj,
135
+ newValue: v
136
+ });
137
+ return r;
138
+ };
139
+ if (prop === "delete") return (v) => {
140
+ const existed = obj.has(v);
141
+ const old = v;
142
+ const r = obj.delete(v);
143
+ if (existed) callback({
144
+ type: "DELETE",
145
+ target: obj,
146
+ oldValue: old
147
+ });
148
+ return r;
149
+ };
150
+ if (prop === "clear") return () => {
151
+ if (obj.size > 0) callback({
152
+ type: "CLEAR",
153
+ target: obj
154
+ });
155
+ return obj.clear();
156
+ };
157
+ return typeof value === "function" ? value.bind(obj) : value;
158
+ }
159
+ if (isMap) {
160
+ if (prop === "size") return Reflect.get(obj, prop, obj);
161
+ const value = Reflect.get(obj, prop, obj);
162
+ if (prop === "set") return (k, v) => {
163
+ const old = obj.get(k);
164
+ const r = obj.set(k, v);
165
+ callback({
166
+ type: "MAP_SET",
167
+ target: obj,
168
+ property: k,
169
+ oldValue: old,
170
+ newValue: v
171
+ });
172
+ return r;
173
+ };
174
+ if (prop === "delete") return (k) => {
175
+ const existed = obj.has(k);
176
+ const old = obj.get(k);
177
+ const r = obj.delete(k);
178
+ if (existed) callback({
179
+ type: "DELETE",
180
+ target: obj,
181
+ property: k,
182
+ oldValue: old
183
+ });
184
+ return r;
185
+ };
186
+ if (prop === "clear") return () => {
187
+ if (obj.size > 0) callback({
188
+ type: "CLEAR",
189
+ target: obj
190
+ });
191
+ return obj.clear();
192
+ };
193
+ return typeof value === "function" ? value.bind(obj) : value;
194
+ }
195
+ return Reflect.get(obj, prop, receiver);
196
+ },
197
+ set(obj, prop, value, receiver) {
198
+ const oldValue = Reflect.get(obj, prop, receiver);
199
+ const r = Reflect.set(obj, prop, value);
200
+ callback({
201
+ type: "SET",
202
+ target: obj,
203
+ property: prop,
204
+ oldValue,
205
+ newValue: value
206
+ });
207
+ return r;
208
+ },
209
+ deleteProperty(obj, prop) {
210
+ const oldValue = Reflect.get(obj, prop);
211
+ const r = Reflect.deleteProperty(obj, prop);
212
+ callback({
213
+ type: "DELETE",
214
+ target: obj,
215
+ property: prop,
216
+ oldValue
217
+ });
218
+ return r;
219
+ }
220
+ });
221
+ }
222
+ /**
223
+ * 以 DFS 方式计算当前可见节点序列
224
+ * 仅在父节点展开时展开其子节点
225
+ */
226
+ function computeVisibleItems(roots) {
227
+ const result = [];
228
+ const walk = (node) => {
229
+ result.push(node);
230
+ if (node.isExpanded() && node.hasChildren()) for (const child of node.getChildren()) walk(child);
231
+ };
232
+ for (const root of roots) walk(root);
233
+ return result;
234
+ }
235
+ /**
236
+ * 递归设置节点层级
237
+ */
238
+ function setLevelRecursively(node, childrenCache, startLevel) {
239
+ node.level = startLevel ?? node.level;
240
+ for (const c of childrenCache.get(node.id) ?? []) setLevelRecursively(c, childrenCache, (node.level ?? 0) + 1);
241
+ }
242
+ /**
243
+ * 追加子节点到指定父节点(或根)
244
+ * 自动维护缓存与层级,并触发通知
245
+ */
246
+ function appendChildren(ctx, parentId, children) {
247
+ const parent = parentId ? ctx.map.get(parentId) : void 0;
248
+ for (const item of children) {
249
+ const id = String(Reflect.get(item, ctx.keyField || "id"));
250
+ if (ctx.map.has(id)) continue;
251
+ const node = {
252
+ id,
253
+ original: item,
254
+ level: parent ? parent.level + 1 : 0,
255
+ hasChildren: () => !!ctx.childrenCache.get(id)?.length,
256
+ getParent: () => ctx.parentCache.get(id),
257
+ getChildren: () => ctx.childrenCache.get(id) ?? [],
258
+ isExpanded: () => ctx.expandedKeys.has(id),
259
+ toggleExpand: (expand) => {
260
+ if (expand === void 0) ctx.tree.toggleExpand(id);
261
+ else if (expand) ctx.tree.expand(id);
262
+ else ctx.tree.collapse(id);
263
+ }
264
+ };
265
+ ctx.map.set(id, node);
266
+ ctx.childrenCache.set(id, []);
267
+ ctx.parentCache.set(id, parent);
268
+ if (parent) ctx.childrenCache.get(parent.id).push(node);
269
+ else ctx.roots.push(node);
270
+ }
271
+ if (parent) setLevelRecursively(parent, ctx.childrenCache, parent.level);
272
+ else ctx.roots.forEach((r) => setLevelRecursively(r, ctx.childrenCache, 0));
273
+ ctx.notify();
274
+ }
275
+ /**
276
+ * 移动节点到新父级,并支持在某个兄弟节点之前插入
277
+ * 自动维护缓存与层级,并触发通知
278
+ */
279
+ function moveNode(ctx, id, newParentId, beforeId) {
280
+ const node = ctx.map.get(id);
281
+ if (!node) return;
282
+ const oldParent = ctx.parentCache.get(id);
283
+ const newParent = newParentId ? ctx.map.get(newParentId) : void 0;
284
+ if (oldParent) {
285
+ const arr = ctx.childrenCache.get(oldParent.id) ?? [];
286
+ const idx = arr.findIndex((n) => n.id === id);
287
+ if (idx >= 0) arr.splice(idx, 1);
288
+ } else {
289
+ const idx = ctx.roots.findIndex((n) => n.id === id);
290
+ if (idx >= 0) ctx.roots.splice(idx, 1);
291
+ }
292
+ ctx.parentCache.set(id, newParent);
293
+ node.level = newParent ? newParent.level + 1 : 0;
294
+ if (newParent) {
295
+ const arr = ctx.childrenCache.get(newParent.id) ?? [];
296
+ if (!ctx.childrenCache.has(newParent.id)) ctx.childrenCache.set(newParent.id, arr);
297
+ if (beforeId) {
298
+ const idx = arr.findIndex((n) => n.id === beforeId);
299
+ if (idx >= 0) arr.splice(idx, 0, node);
300
+ else arr.push(node);
301
+ } else arr.push(node);
302
+ } else if (beforeId) {
303
+ const idx = ctx.roots.findIndex((n) => n.id === beforeId);
304
+ if (idx >= 0) ctx.roots.splice(idx, 0, node);
305
+ else ctx.roots.push(node);
306
+ } else ctx.roots.push(node);
307
+ setLevelRecursively(node, ctx.childrenCache, node.level);
308
+ ctx.notify();
309
+ }
310
+ /**
311
+ * 创建 SpliceTree 树实例
312
+ * - 构建缓存结构
313
+ * - 暴露操作方法(展开/收起/追加/移动)
314
+ * - 提供插件扩展点(setup/extendNode)
315
+ */
316
+ function createSpliceTree(data, options = {}) {
317
+ const keyField = options.keyField ?? "id";
318
+ const parentField = options.parentField ?? "parent";
319
+ const events = createEmitter();
320
+ const expandedKeys = createReactive(/* @__PURE__ */ new Set(), (payload) => {
321
+ events.emit({
322
+ name: "visibility",
323
+ keys: Array.from(payload.target)
324
+ });
325
+ });
326
+ const { roots, map, parentCache, childrenCache } = buildTree(data, keyField, parentField, expandedKeys);
327
+ initDefaultExpansion(map, expandedKeys, options.defaultExpanded, options.defaultExpandedLevel);
328
+ const emitVisibility = () => {
329
+ events.emit({
330
+ name: "visibility",
331
+ keys: Array.from(expandedKeys)
332
+ });
333
+ };
334
+ const tree = {
335
+ data,
336
+ options,
337
+ items: () => computeVisibleItems(roots),
338
+ getNode: (id) => map.get(id),
339
+ events,
340
+ expandedKeys: () => Array.from(expandedKeys),
341
+ isExpanded: (id) => expandedKeys.has(id),
342
+ expand(ids) {
343
+ const list = Array.isArray(ids) ? ids : [ids];
344
+ for (const id of list) if (!expandedKeys.has(id)) expandedKeys.add(id);
345
+ emitVisibility();
346
+ },
347
+ collapse(ids) {
348
+ const list = Array.isArray(ids) ? ids : [ids];
349
+ for (const id of list) if (expandedKeys.has(id)) expandedKeys.delete(id);
350
+ emitVisibility();
351
+ },
352
+ toggleExpand(ids) {
353
+ const list = Array.isArray(ids) ? ids : [ids];
354
+ const toExpand = [];
355
+ const toCollapse = [];
356
+ for (const id of list) if (tree.isExpanded(id)) toCollapse.push(id);
357
+ else toExpand.push(id);
358
+ if (toExpand.length) tree.expand(toExpand);
359
+ if (toCollapse.length) tree.collapse(toCollapse);
360
+ },
361
+ expandAll() {
362
+ for (const id of map.keys()) expandedKeys.add(id);
363
+ emitVisibility();
364
+ },
365
+ collapseAll() {
366
+ expandedKeys.clear();
367
+ emitVisibility();
368
+ },
369
+ toggleExpandAll() {
370
+ if (expandedKeys.size > 0) tree.collapseAll();
371
+ else tree.expandAll();
372
+ },
373
+ appendChildren(parentId, children) {
374
+ appendChildren({
375
+ map,
376
+ tree,
377
+ roots,
378
+ keyField,
379
+ parentCache,
380
+ childrenCache,
381
+ expandedKeys,
382
+ notify: emitVisibility
383
+ }, parentId, children);
384
+ },
385
+ moveNode(id, newParentId, beforeId) {
386
+ moveNode({
387
+ map,
388
+ tree,
389
+ roots,
390
+ keyField,
391
+ parentCache,
392
+ childrenCache,
393
+ expandedKeys,
394
+ notify: emitVisibility
395
+ }, id, newParentId, beforeId);
396
+ }
397
+ };
398
+ const pluginCtx = {
399
+ tree,
400
+ options,
401
+ events
402
+ };
403
+ options?.plugins?.forEach((plugin) => {
404
+ const api = plugin.setup?.(pluginCtx);
405
+ Object.assign(tree, api);
406
+ });
407
+ if (options?.plugins?.length) for (const n of map.values()) for (const plugin of options.plugins) plugin.extendNode?.(n, pluginCtx);
408
+ for (const node of map.values()) {
409
+ node.isExpanded = () => tree.isExpanded(node.id);
410
+ node.toggleExpand = (expand) => {
411
+ if (expand === void 0) tree.toggleExpand(node.id);
412
+ else if (expand) tree.expand(node.id);
413
+ else tree.collapse(node.id);
414
+ };
415
+ }
416
+ return tree;
417
+ }
418
+
419
+ //#endregion
420
+ //#region src/index.ts
421
+ /**
422
+ * Vue 3 适配器
423
+ * - 以 shallowRef 暴露 items,使其在核心派发 visibility 事件时刷新
424
+ * - 保留核心 API 的完整返回(展开/收起/移动等方法)
425
+ * - 适用于任意自定义渲染逻辑与组件绑定
426
+ */
427
+ function useSpliceTree(data, options = {}) {
428
+ const api = shallowRef();
429
+ const items = shallowRef(api.value?.items?.() ?? []);
430
+ const createTree = () => {
431
+ api.value = createSpliceTree(toValue(data), options);
432
+ api.value.events.on("visibility", () => {
433
+ items.value = api.value.items();
434
+ });
435
+ items.value = api.value.items();
436
+ };
437
+ watch(() => toValue(data), () => {
438
+ createTree();
439
+ }, {
440
+ deep: true,
441
+ immediate: true
442
+ });
443
+ return {
444
+ ...api,
445
+ items
446
+ };
447
+ }
448
+
449
+ //#endregion
450
+ export { useSpliceTree };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@splicetree/adapter-vue",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "author": {
6
+ "email": "michael.cocova@gmail.com",
7
+ "name": "Michael Cocova"
8
+ },
9
+ "license": "MIT",
10
+ "homepage": "https://www.splicetree.dev",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/michaelcocova/splicetree"
14
+ },
15
+ "exports": {
16
+ ".": "./dist/index.js",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "peerDependencies": {
26
+ "vue": "^3.0.0"
27
+ },
28
+ "dependencies": {
29
+ "unplugin-vue": "^7.1.0",
30
+ "vue-tsc": "^3.1.5"
31
+ },
32
+ "devDependencies": {
33
+ "vue": "^3.0.0",
34
+ "@splicetree/core": "0.0.1"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "dev": "tsdown --watch",
41
+ "build": "tsdown"
42
+ }
43
+ }