befly-shared 1.3.9 → 1.3.11

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 (31) hide show
  1. package/{types/arrayToTree.ts → dist/types/arrayToTree.d.ts} +0 -2
  2. package/dist/types/arrayToTree.js +4 -0
  3. package/{types/deepTransformKeys.ts → dist/types/deepTransformKeys.d.ts} +0 -4
  4. package/dist/types/deepTransformKeys.js +6 -0
  5. package/{types/genShortId.ts → dist/types/genShortId.d.ts} +0 -1
  6. package/dist/types/genShortId.js +4 -0
  7. package/{types/hashPassword.ts → dist/types/hashPassword.d.ts} +0 -1
  8. package/dist/types/hashPassword.js +4 -0
  9. package/{types/normalizePathnameListInput.ts → dist/types/normalizePathnameListInput.d.ts} +0 -1
  10. package/dist/types/normalizePathnameListInput.js +4 -0
  11. package/{types/scanViewsDir.ts → dist/types/scanViewsDir.d.ts} +0 -7
  12. package/dist/types/scanViewsDir.js +4 -0
  13. package/{types/withDefaultColumns.ts → dist/types/withDefaultColumns.d.ts} +0 -1
  14. package/dist/types/withDefaultColumns.js +4 -0
  15. package/dist/utils/arrayToTree.d.ts +14 -0
  16. package/{utils/arrayToTree.ts → dist/utils/arrayToTree.js} +23 -54
  17. package/dist/utils/deepTransformKeys.d.ts +84 -0
  18. package/{utils/deepTransformKeys.ts → dist/utils/deepTransformKeys.js} +20 -53
  19. package/dist/utils/fieldClear.d.ts +11 -0
  20. package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +12 -42
  21. package/dist/utils/genShortId.d.ts +10 -0
  22. package/{utils/genShortId.ts → dist/utils/genShortId.js} +1 -1
  23. package/dist/utils/hashPassword.d.ts +11 -0
  24. package/{utils/hashPassword.ts → dist/utils/hashPassword.js} +1 -6
  25. package/dist/utils/normalizePathnameListInput.d.ts +16 -0
  26. package/{utils/normalizePathnameListInput.ts → dist/utils/normalizePathnameListInput.js} +4 -14
  27. package/dist/utils/scanViewsDir.d.ts +43 -0
  28. package/{utils/scanViewsDir.ts → dist/utils/scanViewsDir.js} +18 -52
  29. package/dist/utils/withDefaultColumns.d.ts +14 -0
  30. package/{utils/withDefaultColumns.ts → dist/utils/withDefaultColumns.js} +7 -17
  31. package/package.json +16 -8
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * arrayToTree 类型定义(类型模块,仅供 type 引用)。
3
3
  */
4
-
5
4
  export type ArrayToTreeResult<T extends Record<string, any>> = {
6
5
  flat: Array<T>;
7
6
  tree: Array<T>;
8
7
  map: Map<string, T>;
9
8
  };
10
-
11
9
  export type ArrayToTree = <T extends Record<string, any>>(items: T[], id?: string, pid?: string, children?: string, sort?: string) => ArrayToTreeResult<T>;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * arrayToTree 类型定义(类型模块,仅供 type 引用)。
3
+ */
4
+ export {};
@@ -3,14 +3,10 @@
3
3
  *
4
4
  * 注意:types 模块不包含运行时实现;运行时请从 befly-shared/utils/* 引入。
5
5
  */
6
-
7
6
  export type KeyTransformer = (key: string) => string;
8
-
9
7
  export type PresetTransform = "camel" | "snake" | "kebab" | "pascal" | "upper" | "lower";
10
-
11
8
  export interface TransformOptions {
12
9
  maxDepth?: number;
13
10
  excludeKeys?: string[];
14
11
  }
15
-
16
12
  export type DeepTransformKeys = <T = any>(data: any, transformer: KeyTransformer | PresetTransform, options?: TransformOptions) => T;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * deepTransformKeys 类型定义(类型模块,仅供 type 引用)。
3
+ *
4
+ * 注意:types 模块不包含运行时实现;运行时请从 befly-shared/utils/* 引入。
5
+ */
6
+ export {};
@@ -1,5 +1,4 @@
1
1
  /**
2
2
  * genShortId 类型定义(类型模块,仅供 type 引用)。
3
3
  */
4
-
5
4
  export type GenShortId = () => string;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * genShortId 类型定义(类型模块,仅供 type 引用)。
3
+ */
4
+ export {};
@@ -1,5 +1,4 @@
1
1
  /**
2
2
  * hashPassword 类型定义(类型模块,仅供 type 引用)。
3
3
  */
4
-
5
4
  export type HashPassword = (password: string, salt?: string) => Promise<string>;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * hashPassword 类型定义(类型模块,仅供 type 引用)。
3
+ */
4
+ export {};
@@ -1,5 +1,4 @@
1
1
  /**
2
2
  * normalizePathnameListInput 类型定义(类型模块,仅供 type 引用)。
3
3
  */
4
-
5
4
  export type NormalizePathnameListInput = (value: unknown, fieldLabel: string, forbidMethodPrefix: boolean) => string[];
@@ -0,0 +1,4 @@
1
+ /**
2
+ * normalizePathnameListInput 类型定义(类型模块,仅供 type 引用)。
3
+ */
4
+ export {};
@@ -1,25 +1,18 @@
1
1
  /**
2
2
  * scanViewsDir 相关纯函数的类型定义(类型模块,仅供 type 引用)。
3
3
  */
4
-
5
4
  export type ViewDirMeta = {
6
5
  title: string;
7
6
  order?: number;
8
7
  };
9
-
10
8
  export type MenuNodeLike<T> = {
11
9
  name?: string;
12
10
  path?: string;
13
11
  sort?: number;
14
12
  children?: T[];
15
13
  };
16
-
17
14
  export type CleanDirName = (name: string) => string;
18
-
19
15
  export type NormalizeMenuPath = (path: string) => string;
20
-
21
16
  export type NormalizeMenuTree = <T extends MenuNodeLike<T>>(menus: T[]) => T[];
22
-
23
17
  export type ExtractScriptSetupBlock = (vueContent: string) => string | null;
24
-
25
18
  export type ExtractDefinePageMetaFromScriptSetup = (scriptSetup: string) => ViewDirMeta | null;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * scanViewsDir 相关纯函数的类型定义(类型模块,仅供 type 引用)。
3
+ */
4
+ export {};
@@ -1,5 +1,4 @@
1
1
  /**
2
2
  * withDefaultColumns 类型定义(类型模块,仅供 type 引用)。
3
3
  */
4
-
5
4
  export type WithDefaultColumns = (columns: any[], customConfig?: Record<string, any>) => any[];
@@ -0,0 +1,4 @@
1
+ /**
2
+ * withDefaultColumns 类型定义(类型模块,仅供 type 引用)。
3
+ */
4
+ export {};
@@ -0,0 +1,14 @@
1
+ export type ArrayToTreeResult<T extends Record<string, any>> = {
2
+ flat: Array<T>;
3
+ tree: Array<T>;
4
+ map: Map<string, T>;
5
+ };
6
+ /**
7
+ * 将一维数组按 { id, pid } 组装为树形结构(纯函数 / 无副作用)。
8
+ *
9
+ * - 默认字段:id / pid / children / sort
10
+ * - pid 为空字符串或父节点不存在时,视为根节点
11
+ * - 内部会 clone 一份节点对象,并写入 children: []
12
+ * - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序1
13
+ */
14
+ export declare function arrayToTree<T extends Record<string, any>>(items: T[], id?: string, pid?: string, children?: string, sort?: string): ArrayToTreeResult<T>;
@@ -1,9 +1,3 @@
1
- export type ArrayToTreeResult<T extends Record<string, any>> = {
2
- flat: Array<T>;
3
- tree: Array<T>;
4
- map: Map<string, T>;
5
- };
6
-
7
1
  /**
8
2
  * 将一维数组按 { id, pid } 组装为树形结构(纯函数 / 无副作用)。
9
3
  *
@@ -12,18 +6,15 @@ export type ArrayToTreeResult<T extends Record<string, any>> = {
12
6
  * - 内部会 clone 一份节点对象,并写入 children: []
13
7
  * - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序1
14
8
  */
15
- export function arrayToTree<T extends Record<string, any>>(items: T[], id: string = "id", pid: string = "pid", children: string = "children", sort: string = "sort"): ArrayToTreeResult<T> {
9
+ export function arrayToTree(items, id = "id", pid = "pid", children = "children", sort = "sort") {
16
10
  const idKey = typeof id === "string" && id.length > 0 ? id : "id";
17
11
  const pidKey = typeof pid === "string" && pid.length > 0 ? pid : "pid";
18
12
  const childrenKey = typeof children === "string" && children.length > 0 ? children : "children";
19
13
  const sortKey = typeof sort === "string" && sort.length > 0 ? sort : "sort";
20
-
21
- const map = new Map<string, T>();
22
- const flat: T[] = [];
23
-
14
+ const map = new Map();
15
+ const flat = [];
24
16
  const safeItems = Array.isArray(items) ? items : [];
25
-
26
- const normalizeKey = (value: unknown): string => {
17
+ const normalizeKey = (value) => {
27
18
  if (typeof value === "string") {
28
19
  return value;
29
20
  }
@@ -32,47 +23,36 @@ export function arrayToTree<T extends Record<string, any>>(items: T[], id: strin
32
23
  }
33
24
  return "";
34
25
  };
35
-
36
26
  for (const item of safeItems) {
37
- const rawId = item ? (item as any)[idKey] : undefined;
38
- const rawPid = item ? (item as any)[pidKey] : undefined;
39
-
27
+ const rawId = item ? item[idKey] : undefined;
28
+ const rawPid = item ? item[pidKey] : undefined;
40
29
  const normalizedId = normalizeKey(rawId);
41
30
  const normalizedPid = normalizeKey(rawPid);
42
-
43
- const nextNode = Object.assign({}, item) as T;
44
- (nextNode as any)[idKey] = normalizedId;
45
- (nextNode as any)[pidKey] = normalizedPid;
46
- (nextNode as any)[childrenKey] = [];
47
-
31
+ const nextNode = Object.assign({}, item);
32
+ nextNode[idKey] = normalizedId;
33
+ nextNode[pidKey] = normalizedPid;
34
+ nextNode[childrenKey] = [];
48
35
  flat.push(nextNode);
49
-
50
36
  if (normalizedId.length > 0) {
51
37
  map.set(normalizedId, nextNode);
52
38
  }
53
39
  }
54
-
55
- const tree: T[] = [];
56
-
40
+ const tree = [];
57
41
  for (const node of flat) {
58
- const selfId = normalizeKey(node ? (node as any)[idKey] : undefined);
59
- const parentId = normalizeKey(node ? (node as any)[pidKey] : undefined);
60
-
42
+ const selfId = normalizeKey(node ? node[idKey] : undefined);
43
+ const parentId = normalizeKey(node ? node[pidKey] : undefined);
61
44
  if (parentId.length > 0 && parentId !== selfId) {
62
45
  const parent = map.get(parentId);
63
- if (parent && Array.isArray((parent as any)[childrenKey])) {
64
- (parent as any)[childrenKey].push(node);
46
+ if (parent && Array.isArray(parent[childrenKey])) {
47
+ parent[childrenKey].push(node);
65
48
  continue;
66
49
  }
67
50
  }
68
-
69
51
  tree.push(node);
70
52
  }
71
-
72
53
  const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
73
-
74
- const getSortValue = (node: T): number => {
75
- const raw = node ? (node as any)[sortKey] : undefined;
54
+ const getSortValue = (node) => {
55
+ const raw = node ? node[sortKey] : undefined;
76
56
  if (typeof raw !== "number") {
77
57
  return 999999;
78
58
  }
@@ -84,49 +64,38 @@ export function arrayToTree<T extends Record<string, any>>(items: T[], id: strin
84
64
  }
85
65
  return raw;
86
66
  };
87
-
88
- const compareNode = (a: T, b: T): number => {
67
+ const compareNode = (a, b) => {
89
68
  const aSort = getSortValue(a);
90
69
  const bSort = getSortValue(b);
91
-
92
70
  if (aSort !== bSort) {
93
71
  return aSort - bSort;
94
72
  }
95
-
96
- const aId = a ? (a as any)[idKey] : "";
97
- const bId = b ? (b as any)[idKey] : "";
98
-
73
+ const aId = a ? a[idKey] : "";
74
+ const bId = b ? b[idKey] : "";
99
75
  return collator.compare(typeof aId === "string" ? aId : "", typeof bId === "string" ? bId : "");
100
76
  };
101
-
102
- const sortTreeInPlace = (nodes: Array<T>, seen: WeakSet<object>): void => {
77
+ const sortTreeInPlace = (nodes, seen) => {
103
78
  if (!Array.isArray(nodes)) {
104
79
  return;
105
80
  }
106
-
107
81
  if (nodes.length > 1) {
108
82
  nodes.sort(compareNode);
109
83
  }
110
-
111
84
  for (const node of nodes) {
112
85
  if (typeof node !== "object" || node === null) {
113
86
  continue;
114
87
  }
115
-
116
88
  if (seen.has(node)) {
117
89
  continue;
118
90
  }
119
91
  seen.add(node);
120
-
121
- const childNodes = (node as any)[childrenKey];
92
+ const childNodes = node[childrenKey];
122
93
  if (Array.isArray(childNodes) && childNodes.length > 0) {
123
94
  sortTreeInPlace(childNodes, seen);
124
95
  }
125
96
  }
126
97
  };
127
-
128
- sortTreeInPlace(tree, new WeakSet<object>());
129
-
98
+ sortTreeInPlace(tree, new WeakSet());
130
99
  return {
131
100
  flat: flat,
132
101
  tree: tree,
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 键名转换函数类型
3
+ */
4
+ export type KeyTransformer = (key: string) => string;
5
+ /**
6
+ * 预设的转换方式
7
+ * - camel: 小驼峰 user_id → userId
8
+ * - snake: 下划线 userId → user_id
9
+ * - kebab: 短横线 userId → user-id
10
+ * - pascal: 大驼峰 user_id → UserId
11
+ * - upper: 大写 userId → USERID
12
+ * - lower: 小写 UserId → userid
13
+ */
14
+ export type PresetTransform = "camel" | "snake" | "kebab" | "pascal" | "upper" | "lower";
15
+ /**
16
+ * 转换选项
17
+ */
18
+ export interface TransformOptions {
19
+ /** 最大递归深度,默认 100。设置为 0 表示不限制深度(不推荐) */
20
+ maxDepth?: number;
21
+ /** 排除的键名列表,这些键名不会被转换 */
22
+ excludeKeys?: string[];
23
+ }
24
+ /**
25
+ * 深度递归遍历数据结构,转换所有键名
26
+ * 支持嵌套对象和数组,自动防止循环引用和栈溢出
27
+ *
28
+ * @param data - 源数据(对象、数组或其他类型)
29
+ * @param transformer - 转换函数或预设方式
30
+ * @param options - 转换选项
31
+ * @returns 键名转换后的新数据
32
+ *
33
+ * @example
34
+ * // 小驼峰
35
+ * deepTransformKeys({ user_id: 123, user_info: { first_name: 'John' } }, 'camel')
36
+ * // { userId: 123, userInfo: { firstName: 'John' } }
37
+ *
38
+ * @example
39
+ * // 下划线
40
+ * deepTransformKeys({ userId: 123, userInfo: { firstName: 'John' } }, 'snake')
41
+ * // { user_id: 123, user_info: { first_name: 'John' } }
42
+ *
43
+ * @example
44
+ * // 短横线
45
+ * deepTransformKeys({ userId: 123, userInfo: { firstName: 'John' } }, 'kebab')
46
+ * // { 'user-id': 123, 'user-info': { 'first-name': 'John' } }
47
+ *
48
+ * @example
49
+ * // 大驼峰
50
+ * deepTransformKeys({ user_id: 123 }, 'pascal')
51
+ * // { UserId: 123 }
52
+ *
53
+ * @example
54
+ * // 大写
55
+ * deepTransformKeys({ userId: 123 }, 'upper')
56
+ * // { USERID: 123 }
57
+ *
58
+ * @example
59
+ * // 小写
60
+ * deepTransformKeys({ UserId: 123 }, 'lower')
61
+ * // { userid: 123 }
62
+ *
63
+ * @example
64
+ * // 自定义转换函数
65
+ * deepTransformKeys({ user_id: 123 }, (key) => `prefix_${key}`)
66
+ * // { prefix_user_id: 123 }
67
+ *
68
+ * @example
69
+ * // 限制递归深度
70
+ * deepTransformKeys(deepData, 'camel', { maxDepth: 10 })
71
+ *
72
+ * @example
73
+ * // 排除特定键名
74
+ * deepTransformKeys({ _id: '123', user_name: 'John' }, 'camel', { excludeKeys: ['_id'] })
75
+ * // { _id: '123', userName: 'John' }
76
+ *
77
+ * @example
78
+ * // 嵌套数组和对象
79
+ * deepTransformKeys({
80
+ * user_list: [{ user_id: 1, user_tags: [{ tag_name: 'vip' }] }]
81
+ * }, 'camel')
82
+ * // { userList: [{ userId: 1, userTags: [{ tagName: 'vip' }] }] }
83
+ */
84
+ export declare const deepTransformKeys: <T = any>(data: any, transformer: KeyTransformer | PresetTransform, options?: TransformOptions) => T;
@@ -1,32 +1,5 @@
1
1
  import { isPlainObject } from "es-toolkit/compat";
2
2
  import { camelCase, snakeCase, kebabCase, pascalCase } from "es-toolkit/string";
3
-
4
- /**
5
- * 键名转换函数类型
6
- */
7
- export type KeyTransformer = (key: string) => string;
8
-
9
- /**
10
- * 预设的转换方式
11
- * - camel: 小驼峰 user_id → userId
12
- * - snake: 下划线 userId → user_id
13
- * - kebab: 短横线 userId → user-id
14
- * - pascal: 大驼峰 user_id → UserId
15
- * - upper: 大写 userId → USERID
16
- * - lower: 小写 UserId → userid
17
- */
18
- export type PresetTransform = "camel" | "snake" | "kebab" | "pascal" | "upper" | "lower";
19
-
20
- /**
21
- * 转换选项
22
- */
23
- export interface TransformOptions {
24
- /** 最大递归深度,默认 100。设置为 0 表示不限制深度(不推荐) */
25
- maxDepth?: number;
26
- /** 排除的键名列表,这些键名不会被转换 */
27
- excludeKeys?: string[];
28
- }
29
-
30
3
  /**
31
4
  * 深度递归遍历数据结构,转换所有键名
32
5
  * 支持嵌套对象和数组,自动防止循环引用和栈溢出
@@ -87,40 +60,41 @@ export interface TransformOptions {
87
60
  * }, 'camel')
88
61
  * // { userList: [{ userId: 1, userTags: [{ tagName: 'vip' }] }] }
89
62
  */
90
- export const deepTransformKeys = <T = any>(data: any, transformer: KeyTransformer | PresetTransform, options: TransformOptions = {}): T => {
63
+ export const deepTransformKeys = (data, transformer, options = {}) => {
91
64
  const { maxDepth = 100, excludeKeys = [] } = options;
92
-
93
65
  // 获取实际的转换函数
94
- let transformFn: KeyTransformer;
66
+ let transformFn;
95
67
  if (typeof transformer === "function") {
96
68
  transformFn = transformer;
97
- } else if (transformer === "camel") {
69
+ }
70
+ else if (transformer === "camel") {
98
71
  transformFn = camelCase;
99
- } else if (transformer === "snake") {
72
+ }
73
+ else if (transformer === "snake") {
100
74
  transformFn = snakeCase;
101
- } else if (transformer === "kebab") {
75
+ }
76
+ else if (transformer === "kebab") {
102
77
  transformFn = kebabCase;
103
- } else if (transformer === "pascal") {
78
+ }
79
+ else if (transformer === "pascal") {
104
80
  transformFn = pascalCase;
105
- } else if (transformer === "upper") {
106
- transformFn = (key: string) => key.toUpperCase();
107
- } else {
108
- transformFn = (key: string) => key.toLowerCase();
109
81
  }
110
-
82
+ else if (transformer === "upper") {
83
+ transformFn = (key) => key.toUpperCase();
84
+ }
85
+ else {
86
+ transformFn = (key) => key.toLowerCase();
87
+ }
111
88
  // 用于检测循环引用的 WeakSet
112
89
  const visited = new WeakSet();
113
-
114
90
  // 创建排除键名集合,提升查找性能
115
91
  const excludeSet = new Set(excludeKeys);
116
-
117
92
  // 递归转换函数
118
- const transform = (value: any, depth: number): any => {
93
+ const transform = (value, depth) => {
119
94
  // 处理 null 和 undefined
120
95
  if (value === null || value === undefined) {
121
96
  return value;
122
97
  }
123
-
124
98
  // 处理数组
125
99
  if (Array.isArray(value)) {
126
100
  // 检测循环引用
@@ -128,18 +102,15 @@ export const deepTransformKeys = <T = any>(data: any, transformer: KeyTransforme
128
102
  return value;
129
103
  }
130
104
  visited.add(value);
131
-
132
105
  // 检查深度限制:如果达到限制,返回原数组
133
106
  if (maxDepth > 0 && depth >= maxDepth) {
134
107
  visited.delete(value);
135
108
  return value;
136
109
  }
137
-
138
- const result = value.map((item: any) => transform(item, depth + 1));
110
+ const result = value.map((item) => transform(item, depth + 1));
139
111
  visited.delete(value);
140
112
  return result;
141
113
  }
142
-
143
114
  // 处理对象
144
115
  if (isPlainObject(value)) {
145
116
  // 检测循环引用
@@ -147,14 +118,12 @@ export const deepTransformKeys = <T = any>(data: any, transformer: KeyTransforme
147
118
  return value;
148
119
  }
149
120
  visited.add(value);
150
-
151
121
  // 检查深度限制:如果达到限制,返回原对象
152
122
  if (maxDepth > 0 && depth >= maxDepth) {
153
123
  visited.delete(value);
154
124
  return value;
155
125
  }
156
-
157
- const result: any = {};
126
+ const result = {};
158
127
  for (const [key, val] of Object.entries(value)) {
159
128
  // 检查是否在排除列表中
160
129
  const transformedKey = excludeSet.has(key) ? key : transformFn(key);
@@ -163,10 +132,8 @@ export const deepTransformKeys = <T = any>(data: any, transformer: KeyTransforme
163
132
  visited.delete(value);
164
133
  return result;
165
134
  }
166
-
167
135
  // 其他类型(字符串、数字、布尔值等)直接返回
168
136
  return value;
169
137
  };
170
-
171
- return transform(data, 0) as T;
138
+ return transform(data, 0);
172
139
  };
@@ -0,0 +1,11 @@
1
+ export interface FieldClearOptions {
2
+ pickKeys?: string[];
3
+ omitKeys?: string[];
4
+ keepValues?: any[];
5
+ excludeValues?: any[];
6
+ keepMap?: Record<string, any>;
7
+ }
8
+ export type FieldClearResult<T> = T extends Array<infer U> ? Array<FieldClearResult<U>> : T extends object ? {
9
+ [K in keyof T]?: T[K];
10
+ } : T;
11
+ export declare function fieldClear<T = any>(data: T | T[], options?: FieldClearOptions): FieldClearResult<T>;
@@ -1,40 +1,19 @@
1
- /**
2
- * @typedef {Object} FieldClearOptions
3
- * @property {string[]=} pickKeys
4
- * @property {string[]=} omitKeys
5
- * @property {any[]=} keepValues
6
- * @property {any[]=} excludeValues
7
- * @property {Record<string, any>=} keepMap
8
- */
9
-
1
+ // fieldClear 工具函数实现(shared 版)
2
+ // 支持 pick/omit/keepValues/excludeValues/keepMap,处理对象和数组
10
3
  function isObject(val) {
11
4
  return val !== null && typeof val === "object" && !Array.isArray(val);
12
5
  }
13
-
14
6
  function isArray(val) {
15
7
  return Array.isArray(val);
16
8
  }
17
-
18
- /**
19
- * 清理对象/数组字段
20
- * - 支持 pick/omit/keepValues/excludeValues
21
- * - 支持 keepMap 强制保留
22
- * @template T
23
- * @param {T|T[]} data
24
- * @param {FieldClearOptions=} options
25
- * @returns {any}
26
- */
27
9
  export function fieldClear(data, options = {}) {
28
10
  const pickKeys = options.pickKeys;
29
11
  const omitKeys = options.omitKeys;
30
12
  const keepValues = options.keepValues;
31
13
  const excludeValues = options.excludeValues;
32
14
  const keepMap = options.keepMap;
33
-
34
15
  const filterObj = (obj) => {
35
- /** @type {Record<string, any>} */
36
16
  const result = {};
37
-
38
17
  let keys = Object.keys(obj);
39
18
  if (pickKeys && pickKeys.length) {
40
19
  keys = keys.filter((k) => pickKeys.includes(k));
@@ -42,10 +21,8 @@ export function fieldClear(data, options = {}) {
42
21
  if (omitKeys && omitKeys.length) {
43
22
  keys = keys.filter((k) => !omitKeys.includes(k));
44
23
  }
45
-
46
24
  for (const key of keys) {
47
25
  const value = obj[key];
48
-
49
26
  // 1. keepMap 优先
50
27
  if (keepMap && Object.prototype.hasOwnProperty.call(keepMap, key)) {
51
28
  if (Object.is(keepMap[key], value)) {
@@ -53,42 +30,35 @@ export function fieldClear(data, options = {}) {
53
30
  continue;
54
31
  }
55
32
  }
56
-
57
33
  // 2. keepValues
58
34
  if (keepValues && keepValues.length && !keepValues.includes(value)) {
59
35
  continue;
60
36
  }
61
-
62
37
  // 3. excludeValues
63
38
  if (excludeValues && excludeValues.length && excludeValues.includes(value)) {
64
39
  continue;
65
40
  }
66
-
67
41
  result[key] = value;
68
42
  }
69
-
70
43
  return result;
71
44
  };
72
-
73
45
  if (isArray(data)) {
74
46
  return data
75
47
  .map((item) => {
76
- if (isObject(item)) {
77
- return filterObj(item);
78
- }
79
- return item;
80
- })
48
+ if (isObject(item)) {
49
+ return filterObj(item);
50
+ }
51
+ return item;
52
+ })
81
53
  .filter((item) => {
82
- if (isObject(item)) {
83
- return Object.keys(item).length > 0;
84
- }
85
- return true;
86
- });
54
+ if (isObject(item)) {
55
+ return Object.keys(item).length > 0;
56
+ }
57
+ return true;
58
+ });
87
59
  }
88
-
89
60
  if (isObject(data)) {
90
61
  return filterObj(data);
91
62
  }
92
-
93
63
  return data;
94
64
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 生成短 ID
3
+ * 由时间戳(base36)+ 随机字符组成,约 13 位
4
+ * - 前 8 位:时间戳(可排序)
5
+ * - 后 5 位:随机字符(防冲突)
6
+ * @returns 短 ID 字符串
7
+ * @example
8
+ * genShortId() // "lxyz1a2b3c4"
9
+ */
10
+ export declare function genShortId(): string;
@@ -7,6 +7,6 @@
7
7
  * @example
8
8
  * genShortId() // "lxyz1a2b3c4"
9
9
  */
10
- export function genShortId(): string {
10
+ export function genShortId() {
11
11
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
12
12
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 密码哈希工具
3
+ * 使用 SHA-256 + 盐值对密码进行单向哈希
4
+ */
5
+ /**
6
+ * 使用 SHA-256 对密码进行哈希
7
+ * @param password - 原始密码
8
+ * @param salt - 盐值,默认为 befly
9
+ * @returns 哈希后的密码(十六进制字符串)
10
+ */
11
+ export declare function hashPassword(password: string, salt?: string): Promise<string>;
@@ -2,26 +2,21 @@
2
2
  * 密码哈希工具
3
3
  * 使用 SHA-256 + 盐值对密码进行单向哈希
4
4
  */
5
-
6
5
  /**
7
6
  * 使用 SHA-256 对密码进行哈希
8
7
  * @param password - 原始密码
9
8
  * @param salt - 盐值,默认为 befly
10
9
  * @returns 哈希后的密码(十六进制字符串)
11
10
  */
12
- export async function hashPassword(password: string, salt: string = "befly"): Promise<string> {
11
+ export async function hashPassword(password, salt = "befly") {
13
12
  const data = password + salt;
14
-
15
13
  // 将字符串转换为 Uint8Array
16
14
  const encoder = new TextEncoder();
17
15
  const dataBuffer = encoder.encode(data);
18
-
19
16
  // 使用 Web Crypto API 进行 SHA-256 哈希
20
17
  const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
21
-
22
18
  // 将 ArrayBuffer 转换为十六进制字符串
23
19
  const hashArray = Array.from(new Uint8Array(hashBuffer));
24
20
  const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
25
-
26
21
  return hashHex;
27
22
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 将未知输入规范化为 pathname 字符串数组。
3
+ *
4
+ * 规则(契约):
5
+ * - value 为“假值”(null/undefined/""/0/false/NaN)时返回空数组 []。
6
+ * - value 必须是 string[],否则抛错:`${fieldLabel} 必须是字符串数组`。
7
+ * - 数组元素必须满足:
8
+ * - 类型为 string
9
+ * - 不允许为空字符串
10
+ * - 不允许包含任何空白字符(空格/制表符/换行等)
11
+ * - 必须以 "/" 开头(pathname)
12
+ * - forbidMethodPrefix=true 时,禁止 "GET/POST/..." 等 method 前缀,并给出更明确的错误提示。
13
+ *
14
+ * 注意:该函数不会做任何隐式修复/转换(例如 trim/split/JSON.parse/去重/排序)。
15
+ */
16
+ export declare function normalizePathnameListInput(value: unknown, fieldLabel: string, forbidMethodPrefix: boolean): string[];
@@ -1,5 +1,4 @@
1
1
  const HTTP_METHOD_PREFIX_RE = /^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/i;
2
-
3
2
  /**
4
3
  * 将未知输入规范化为 pathname 字符串数组。
5
4
  *
@@ -15,44 +14,35 @@ const HTTP_METHOD_PREFIX_RE = /^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/i;
15
14
  *
16
15
  * 注意:该函数不会做任何隐式修复/转换(例如 trim/split/JSON.parse/去重/排序)。
17
16
  */
18
- export function normalizePathnameListInput(value: unknown, fieldLabel: string, forbidMethodPrefix: boolean): string[] {
17
+ export function normalizePathnameListInput(value, fieldLabel, forbidMethodPrefix) {
19
18
  // “假值”统一视为空数组:null/undefined/""/0/false/NaN
20
- if (!value) return [];
21
-
19
+ if (!value)
20
+ return [];
22
21
  if (!Array.isArray(value)) {
23
22
  throw new Error(`${fieldLabel} 必须是字符串数组`);
24
23
  }
25
-
26
- const out: string[] = [];
27
-
24
+ const out = [];
28
25
  for (let i = 0; i < value.length; i += 1) {
29
26
  const item = value[i];
30
27
  const itemLabel = `${fieldLabel}[${i}]`;
31
-
32
28
  if (typeof item !== "string") {
33
29
  throw new Error(`${itemLabel} 必须是字符串`);
34
30
  }
35
-
36
31
  if (item.length === 0) {
37
32
  throw new Error(`${itemLabel} 不允许为空字符串`);
38
33
  }
39
-
40
34
  // 优先给出 method 前缀提示(更明确)
41
35
  if (forbidMethodPrefix && HTTP_METHOD_PREFIX_RE.test(item)) {
42
36
  throw new Error(`${itemLabel} 不允许包含 method 前缀,应为 url.pathname(例如 /api/app/xxx)`);
43
37
  }
44
-
45
38
  // 不做 trim 自动转换:含任何空白字符都视为不合法
46
39
  if (/\s/.test(item)) {
47
40
  throw new Error(`${itemLabel} 不允许包含空白字符(空格/制表符/换行等)`);
48
41
  }
49
-
50
42
  if (!item.startsWith("/")) {
51
43
  throw new Error(`${itemLabel} 必须是 pathname(以 / 开头)`);
52
44
  }
53
-
54
45
  out.push(item);
55
46
  }
56
-
57
47
  return out;
58
48
  }
@@ -0,0 +1,43 @@
1
+ export type ViewDirMeta = {
2
+ title: string;
3
+ order?: number;
4
+ };
5
+ type MenuNodeLike<T> = {
6
+ name?: string;
7
+ path?: string;
8
+ sort?: number;
9
+ children?: T[];
10
+ };
11
+ /**
12
+ * 清理目录名中的数字后缀
13
+ * 如:login_1 → login, index_2 → index
14
+ */
15
+ export declare function cleanDirName(name: string): string;
16
+ /**
17
+ * 约束:统一 path 形态,避免隐藏菜单匹配、DB 同步出现重复
18
+ * - 必须以 / 开头
19
+ * - 折叠多个 /
20
+ * - 去掉尾随 /(根 / 除外)
21
+ */
22
+ export declare function normalizeMenuPath(path: string): string;
23
+ /**
24
+ * 递归规范化并按 path 去重(同 path 的 children 合并)
25
+ *
26
+ * 说明:该函数是纯函数,不依赖任何运行时环境;会返回新数组,但会在内部对克隆对象做合并赋值。
27
+ */
28
+ export declare function normalizeMenuTree<T extends MenuNodeLike<T>>(menus: T[]): T[];
29
+ /**
30
+ * 只取第一个 <script ... setup ...> 块
31
+ */
32
+ export declare function extractScriptSetupBlock(vueContent: string): string | null;
33
+ /**
34
+ * 从 <script setup> 中提取 definePage({ meta })
35
+ *
36
+ * 简化约束:
37
+ * - 每个页面只有一个 definePage
38
+ * - title 是纯字符串字面量
39
+ * - order 是数字字面量(可选)
40
+ * - 不考虑变量/表达式/多段 meta 组合
41
+ */
42
+ export declare function extractDefinePageMetaFromScriptSetup(scriptSetup: string): ViewDirMeta | null;
43
+ export {};
@@ -1,129 +1,97 @@
1
- export type ViewDirMeta = {
2
- title: string;
3
- order?: number;
4
- };
5
-
6
- type MenuNodeLike<T> = {
7
- name?: string;
8
- path?: string;
9
- sort?: number;
10
- children?: T[];
11
- };
12
-
13
1
  /**
14
2
  * 清理目录名中的数字后缀
15
3
  * 如:login_1 → login, index_2 → index
16
4
  */
17
- export function cleanDirName(name: string): string {
5
+ export function cleanDirName(name) {
18
6
  return name.replace(/_\d+$/, "");
19
7
  }
20
-
21
8
  /**
22
9
  * 约束:统一 path 形态,避免隐藏菜单匹配、DB 同步出现重复
23
10
  * - 必须以 / 开头
24
11
  * - 折叠多个 /
25
12
  * - 去掉尾随 /(根 / 除外)
26
13
  */
27
- export function normalizeMenuPath(path: string): string {
14
+ export function normalizeMenuPath(path) {
28
15
  let result = path;
29
-
30
16
  if (!result) {
31
17
  return "/";
32
18
  }
33
-
34
19
  if (!result.startsWith("/")) {
35
20
  result = `/${result}`;
36
21
  }
37
-
38
22
  result = result.replace(/\/+/g, "/");
39
-
40
23
  if (result.length > 1) {
41
24
  result = result.replace(/\/+$/, "");
42
25
  }
43
-
44
26
  return result;
45
27
  }
46
-
47
28
  /**
48
29
  * 递归规范化并按 path 去重(同 path 的 children 合并)
49
30
  *
50
31
  * 说明:该函数是纯函数,不依赖任何运行时环境;会返回新数组,但会在内部对克隆对象做合并赋值。
51
32
  */
52
- export function normalizeMenuTree<T extends MenuNodeLike<T>>(menus: T[]): T[] {
53
- const map = new Map<string, T>();
54
-
33
+ export function normalizeMenuTree(menus) {
34
+ const map = new Map();
55
35
  for (const menu of menus) {
56
36
  const rawPath = menu.path;
57
37
  const menuPath = rawPath ? normalizeMenuPath(rawPath) : "";
58
-
59
38
  if (!menuPath) {
60
39
  continue;
61
40
  }
62
-
63
41
  // 不使用 structuredClone:
64
42
  // - 结构中可能出现函数/类实例等不可 clone 的值
65
43
  // - 这里我们只需要“保留额外字段 + 递归 children 生成新数组”
66
44
  // 用浅拷贝即可满足需求
67
- const cloned = Object.assign({}, menu) as T;
68
- (cloned as any).path = menuPath;
69
-
45
+ const cloned = Object.assign({}, menu);
46
+ cloned.path = menuPath;
70
47
  const rawChildren = menu.children;
71
48
  if (rawChildren && rawChildren.length > 0) {
72
- (cloned as any).children = normalizeMenuTree(rawChildren);
49
+ cloned.children = normalizeMenuTree(rawChildren);
73
50
  }
74
-
75
51
  const existing = map.get(menuPath);
76
52
  if (existing) {
77
- const clonedChildren = (cloned as any).children as T[] | undefined;
53
+ const clonedChildren = cloned.children;
78
54
  if (clonedChildren && clonedChildren.length > 0) {
79
- let existingChildren = (existing as any).children as T[] | undefined;
55
+ let existingChildren = existing.children;
80
56
  if (!existingChildren) {
81
57
  existingChildren = [];
82
- (existing as any).children = existingChildren;
58
+ existing.children = existingChildren;
83
59
  }
84
-
85
60
  for (const child of clonedChildren) {
86
61
  existingChildren.push(child);
87
62
  }
88
-
89
- (existing as any).children = normalizeMenuTree(existingChildren);
63
+ existing.children = normalizeMenuTree(existingChildren);
90
64
  }
91
-
92
65
  if (typeof cloned.sort === "number") {
93
- (existing as any).sort = cloned.sort;
66
+ existing.sort = cloned.sort;
94
67
  }
95
-
96
68
  if (typeof cloned.name === "string" && cloned.name) {
97
- (existing as any).name = cloned.name;
69
+ existing.name = cloned.name;
98
70
  }
99
- } else {
71
+ }
72
+ else {
100
73
  map.set(menuPath, cloned);
101
74
  }
102
75
  }
103
-
104
76
  const result = Array.from(map.values());
105
- result.sort((a, b) => ((a as any).sort ?? 999999) - ((b as any).sort ?? 999999));
77
+ result.sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999));
106
78
  return result;
107
79
  }
108
-
109
80
  /**
110
81
  * 只取第一个 <script ... setup ...> 块
111
82
  */
112
- export function extractScriptSetupBlock(vueContent: string): string | null {
83
+ export function extractScriptSetupBlock(vueContent) {
113
84
  const openTag = /<script\b[^>]*\bsetup\b[^>]*>/i.exec(vueContent);
114
85
  if (!openTag) {
115
86
  return null;
116
87
  }
117
-
118
88
  const start = openTag.index + openTag[0].length;
119
89
  const closeIndex = vueContent.indexOf("</script>", start);
120
90
  if (closeIndex < 0) {
121
91
  return null;
122
92
  }
123
-
124
93
  return vueContent.slice(start, closeIndex);
125
94
  }
126
-
127
95
  /**
128
96
  * 从 <script setup> 中提取 definePage({ meta })
129
97
  *
@@ -133,14 +101,12 @@ export function extractScriptSetupBlock(vueContent: string): string | null {
133
101
  * - order 是数字字面量(可选)
134
102
  * - 不考虑变量/表达式/多段 meta 组合
135
103
  */
136
- export function extractDefinePageMetaFromScriptSetup(scriptSetup: string): ViewDirMeta | null {
104
+ export function extractDefinePageMetaFromScriptSetup(scriptSetup) {
137
105
  const titleMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?title\s*:\s*(["'`])([^"'`]+)\1/);
138
106
  if (!titleMatch) {
139
107
  return null;
140
108
  }
141
-
142
109
  const orderMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?order\s*:\s*(\d+)/);
143
-
144
110
  return {
145
111
  title: titleMatch[2],
146
112
  order: orderMatch ? Number(orderMatch[1]) : undefined
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 为表格列添加默认配置(纯函数)。
3
+ *
4
+ * 设计目标:
5
+ * - 不依赖浏览器/Node 特定 API
6
+ * - 不修改入参 columns 的对象引用(会返回新数组,并对每个列对象做浅拷贝合并)
7
+ *
8
+ * 默认行为:
9
+ * - base 默认:{ width: 200, ellipsis: true }
10
+ * - 特殊列:operation/state/id
11
+ * - colKey 以 At/At2 结尾时:默认 { align: "center" }
12
+ * - customConfig 可覆盖/扩展默认 specialColumnConfig(浅合并/替换:若传入 operation 配置,将整体替换默认 operation 配置,默认 fixed="right" 不会被保留)
13
+ */
14
+ export declare function withDefaultColumns(columns: any[], customConfig?: Record<string, any>): any[];
@@ -11,38 +11,28 @@
11
11
  * - colKey 以 At/At2 结尾时:默认 { align: "center" }
12
12
  * - customConfig 可覆盖/扩展默认 specialColumnConfig(浅合并/替换:若传入 operation 配置,将整体替换默认 operation 配置,默认 fixed="right" 不会被保留)
13
13
  */
14
- export function withDefaultColumns(columns: any[], customConfig?: Record<string, any>): any[] {
14
+ export function withDefaultColumns(columns, customConfig) {
15
15
  const safeColumns = Array.isArray(columns) ? columns : [];
16
-
17
- const specialColumnConfig: Record<string, any> = Object.assign(
18
- {
19
- operation: { width: 100, align: "center", fixed: "right" },
20
- state: { width: 100, align: "center" },
21
- id: { width: 200, align: "center" }
22
- },
23
- customConfig || {}
24
- );
25
-
16
+ const specialColumnConfig = Object.assign({
17
+ operation: { width: 100, align: "center", fixed: "right" },
18
+ state: { width: 100, align: "center" },
19
+ id: { width: 200, align: "center" }
20
+ }, customConfig || {});
26
21
  return safeColumns.map((col) => {
27
- const colKey = col && (col as any).colKey;
28
-
22
+ const colKey = col && col.colKey;
29
23
  let specialConfig = colKey ? specialColumnConfig[colKey] : undefined;
30
-
31
24
  if (!specialConfig && typeof colKey === "string" && (colKey.endsWith("At") || colKey.endsWith("At2"))) {
32
25
  specialConfig = { align: "center" };
33
26
  }
34
-
35
27
  const base = {
36
28
  width: 200,
37
29
  ellipsis: true
38
30
  };
39
-
40
31
  const merged = Object.assign({}, base);
41
32
  if (specialConfig) {
42
33
  Object.assign(merged, specialConfig);
43
34
  }
44
35
  Object.assign(merged, col);
45
-
46
36
  return merged;
47
37
  });
48
38
  }
package/package.json CHANGED
@@ -1,23 +1,31 @@
1
1
  {
2
2
  "name": "befly-shared",
3
- "version": "1.3.9",
4
- "gitHead": "f5d17c019a58e82cfacec5ce3a1f04e3225ba838",
3
+ "version": "1.3.11",
4
+ "gitHead": "0914e9a6efb3f41136034f14d5f9cda4d5e7c3d0",
5
5
  "private": false,
6
6
  "description": "Befly 纯函数与纯常量共享包(不包含 Node/浏览器特定实现)",
7
7
  "license": "Apache-2.0",
8
8
  "author": "chensuiyi <bimostyle@qq.com>",
9
9
  "files": [
10
- "package.json",
11
- "types/",
12
- "utils/"
10
+ "dist/"
13
11
  ],
14
12
  "type": "module",
15
13
  "exports": {
16
- "./utils/*": "./utils/*.ts",
17
- "./types/*": "./types/*.ts"
14
+ "./utils/*": {
15
+ "types": "./dist/utils/*.d.ts",
16
+ "default": "./dist/utils/*.js"
17
+ },
18
+ "./types/*": {
19
+ "types": "./dist/types/*.d.ts",
20
+ "default": "./dist/types/*.js"
21
+ }
18
22
  },
19
23
  "scripts": {
20
- "test": "bun test"
24
+ "clean": "rimraf dist",
25
+ "build": "bunx tsgo -p tsconfig.build.json",
26
+ "test": "bun test",
27
+ "prepack": "bun run build && bun run ./scripts/ensureDist.ts",
28
+ "typecheck": "bunx tsgo -p tsconfig.json --noEmit"
21
29
  },
22
30
  "dependencies": {
23
31
  "es-toolkit": "^1.43.0"