@wener/common 1.0.3 → 1.0.4
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/lib/cn/DivisionCode.js.map +1 -1
- package/lib/cn/Mod11Checksum.js.map +1 -1
- package/lib/cn/Mod31Checksum.js.map +1 -1
- package/lib/cn/ResidentIdentityCardNumber.js.map +1 -1
- package/lib/cn/UnifiedSocialCreditCode.js.map +1 -1
- package/lib/cn/formatDate.js.map +1 -1
- package/lib/cn/parseSex.js.map +1 -1
- package/lib/cn/types.d.js.map +1 -1
- package/lib/data/formatSort.js +15 -0
- package/lib/data/formatSort.js.map +1 -0
- package/lib/data/index.js +4 -0
- package/lib/data/index.js.map +1 -0
- package/lib/data/maybeNumber.js +22 -0
- package/lib/data/maybeNumber.js.map +1 -0
- package/lib/data/parseSort.js +95 -0
- package/lib/data/parseSort.js.map +1 -0
- package/lib/data/resolvePagination.js +36 -0
- package/lib/data/resolvePagination.js.map +1 -0
- package/lib/data/types.d.js +3 -0
- package/lib/data/types.d.js.map +1 -0
- package/lib/index.js +6 -2
- package/lib/index.js.map +1 -1
- package/lib/jsonschema/JsonSchema.js +4 -4
- package/lib/jsonschema/JsonSchema.js.map +1 -1
- package/lib/jsonschema/types.d.js.map +1 -1
- package/lib/meta/defineFileType.js.map +1 -1
- package/lib/meta/defineInit.js.map +1 -1
- package/lib/meta/defineMetadata.js.map +1 -1
- package/lib/password/PHC.js +8 -8
- package/lib/password/PHC.js.map +1 -1
- package/lib/password/Password.js.map +1 -1
- package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBase64PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -1
- package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createScryptPasswordAlgorithm.js.map +1 -1
- package/lib/search/AdvanceSearch.js.map +1 -1
- package/lib/search/formatAdvanceSearch.js.map +1 -1
- package/lib/search/optimizeAdvanceSearch.js.map +1 -1
- package/lib/search/parseAdvanceSearch.js.map +1 -1
- package/lib/search/parser.d.js.map +1 -1
- package/lib/search/types.d.js.map +1 -1
- package/lib/tools/renderJsonSchemaToMarkdownDoc.js.map +1 -1
- package/package.json +10 -5
- package/src/cn/DivisionCode.test.ts +38 -45
- package/src/cn/DivisionCode.ts +140 -184
- package/src/cn/Mod11Checksum.ts +17 -17
- package/src/cn/Mod31Checksum.ts +25 -25
- package/src/cn/ResidentIdentityCardNumber.test.ts +12 -16
- package/src/cn/ResidentIdentityCardNumber.ts +82 -82
- package/src/cn/UnifiedSocialCreditCode.test.ts +11 -11
- package/src/cn/UnifiedSocialCreditCode.ts +115 -120
- package/src/cn/__snapshots__/ResidentIdentityCardNumber.test.ts.snap +1 -1
- package/src/cn/formatDate.ts +10 -10
- package/src/cn/parseSex.ts +11 -25
- package/src/cn/types.d.ts +43 -43
- package/src/data/formatSort.test.ts +13 -0
- package/src/data/formatSort.ts +18 -0
- package/src/data/index.ts +5 -0
- package/src/data/maybeNumber.ts +23 -0
- package/src/data/parseSort.test.ts +67 -0
- package/src/data/parseSort.ts +108 -0
- package/src/data/resolvePagination.test.ts +58 -0
- package/src/data/resolvePagination.ts +60 -0
- package/src/data/types.d.ts +33 -0
- package/src/index.ts +8 -2
- package/src/jsonschema/JsonSchema.test.ts +13 -22
- package/src/jsonschema/JsonSchema.ts +145 -177
- package/src/jsonschema/types.d.ts +151 -161
- package/src/meta/defineFileType.tsx +54 -54
- package/src/meta/defineInit.ts +32 -53
- package/src/meta/defineMetadata.test.ts +5 -7
- package/src/meta/defineMetadata.ts +28 -46
- package/src/password/PHC.test.ts +186 -277
- package/src/password/PHC.ts +243 -243
- package/src/password/Password.test.ts +38 -50
- package/src/password/Password.ts +73 -95
- package/src/password/createArgon2PasswordAlgorithm.ts +65 -69
- package/src/password/createBase64PasswordAlgorithm.ts +9 -9
- package/src/password/createBcryptPasswordAlgorithm.ts +20 -22
- package/src/password/createPBKDF2PasswordAlgorithm.ts +49 -61
- package/src/password/createScryptPasswordAlgorithm.ts +48 -59
- package/src/search/AdvanceSearch.test.ts +136 -143
- package/src/search/AdvanceSearch.ts +6 -6
- package/src/search/formatAdvanceSearch.ts +44 -53
- package/src/search/optimizeAdvanceSearch.ts +70 -83
- package/src/search/parseAdvanceSearch.ts +16 -19
- package/src/search/parser.d.ts +3 -3
- package/src/search/types.d.ts +28 -54
- package/src/tools/renderJsonSchemaToMarkdownDoc.ts +69 -69
- package/lib/normalizePagination.js +0 -14
- package/lib/normalizePagination.js.map +0 -1
- package/lib/parseSort.js +0 -106
- package/lib/parseSort.js.map +0 -1
- package/src/normalizePagination.ts +0 -25
- package/src/parseSort.test.ts +0 -42
- package/src/parseSort.ts +0 -133
|
@@ -2,15 +2,15 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import { UnifiedSocialCreditCode } from './UnifiedSocialCreditCode';
|
|
3
3
|
|
|
4
4
|
describe('UnifiedSocialCreditCode', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
it('should parse', () => {
|
|
6
|
+
for (const a of [
|
|
7
|
+
//
|
|
8
|
+
'91330106673959654P',
|
|
9
|
+
'91330106MA2CFLDG4R',
|
|
10
|
+
]) {
|
|
11
|
+
let out = UnifiedSocialCreditCode.parse(a);
|
|
12
|
+
expect(UnifiedSocialCreditCode.format(out)).toBe(a);
|
|
13
|
+
expect(out).toMatchSnapshot();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
16
|
});
|
|
@@ -6,143 +6,138 @@ import { Mod31Checksum } from './Mod31Checksum';
|
|
|
6
6
|
* - GB 11714-1997 全国组织机构代码编制规则
|
|
7
7
|
*/
|
|
8
8
|
export namespace UnifiedSocialCreditCode {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
interface CodeValue {
|
|
10
|
+
label: string;
|
|
11
|
+
children?: Record<string, CodeValue>;
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
children: {
|
|
45
|
-
1: { label: '其他' },
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
};
|
|
14
|
+
const CodeLabels: Record<string, CodeValue> = {
|
|
15
|
+
1: {
|
|
16
|
+
label: '机构编制',
|
|
17
|
+
children: {
|
|
18
|
+
1: { label: '机关' },
|
|
19
|
+
2: { label: '事业单位' },
|
|
20
|
+
3: { label: '中央编办直接管理机构编制的群众团体' },
|
|
21
|
+
9: { label: '其他' },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
5: {
|
|
25
|
+
label: '民政',
|
|
26
|
+
children: {
|
|
27
|
+
1: { label: '社会团体' },
|
|
28
|
+
2: { label: '民办非企业单位' },
|
|
29
|
+
3: { label: '基金会' },
|
|
30
|
+
9: { label: '其他' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
9: {
|
|
34
|
+
label: '工商',
|
|
35
|
+
children: {
|
|
36
|
+
1: { label: '企业' },
|
|
37
|
+
2: { label: '个体工商户' },
|
|
38
|
+
3: { label: '农民专业合作社' },
|
|
39
|
+
9: { label: '其他' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
Y: { label: '其他', children: { 1: { label: '其他' } } },
|
|
43
|
+
};
|
|
49
44
|
|
|
50
|
-
|
|
45
|
+
export const pattern = /^([159][1239]|Y1)[0-9]{6}[0-9A-HJ-NP-RTUWXY]{10}$/; // 无 I O Z S V
|
|
51
46
|
|
|
52
|
-
|
|
47
|
+
export const Checksum = new Mod31Checksum();
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
export function parse(s: string): ParsedUnifiedSocialCreditCode {
|
|
50
|
+
let bureau = s[0];
|
|
51
|
+
let subjectType = s[1];
|
|
52
|
+
let division = s.slice(2, 8); // 第3~8位,共6位,正确
|
|
53
|
+
let subject = s.slice(8, 17); // 第9~17位,共9位
|
|
54
|
+
let checksum = s.slice(17, 18); // 第18位,校验码
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
const labels: string[] = [];
|
|
57
|
+
let l1 = CodeLabels[bureau];
|
|
58
|
+
l1 && labels.push(l1?.label);
|
|
59
|
+
let l2 = l1?.children?.[subjectType];
|
|
60
|
+
l2 && labels.push(l2?.label);
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
return {
|
|
63
|
+
bureau,
|
|
64
|
+
subjectType,
|
|
65
|
+
division,
|
|
66
|
+
subject,
|
|
67
|
+
checksum,
|
|
68
|
+
codes: [bureau, subjectType, division, subject],
|
|
69
|
+
labels,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
export function format({
|
|
74
|
+
bureau,
|
|
75
|
+
subjectType,
|
|
76
|
+
division,
|
|
77
|
+
subject,
|
|
78
|
+
checksum,
|
|
79
|
+
}: {
|
|
80
|
+
bureau: string;
|
|
81
|
+
subjectType: string;
|
|
82
|
+
division: string;
|
|
83
|
+
subject: string;
|
|
84
|
+
checksum?: string;
|
|
85
|
+
}): string {
|
|
86
|
+
const base = `${bureau}${subjectType}${division}${subject}`;
|
|
87
|
+
checksum ||= Checksum.compute(base);
|
|
88
|
+
return `${base}${checksum}`;
|
|
89
|
+
}
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
export function next(s: string, delta: number = 1) {
|
|
92
|
+
const sp = s.split('').map((v) => Checksum.numbers[v]);
|
|
93
|
+
for (let i = sp.length - 1; i >= 0; i--) {
|
|
94
|
+
if ((delta > 0 && sp[i] < 30) || (delta < 0 && sp[i] > 0)) {
|
|
95
|
+
sp[i] += delta;
|
|
96
|
+
break;
|
|
97
|
+
} else if (delta > 0 && sp[i] === 30) {
|
|
98
|
+
sp[i] = 0;
|
|
99
|
+
} else if (delta < 0 && sp[i] === 0) {
|
|
100
|
+
sp[i] = 30;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return sp.map((v) => Checksum.chars[v]).join('');
|
|
104
|
+
}
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
/**
|
|
113
108
|
* 解析后的统一社会信用代码信息
|
|
114
109
|
*/
|
|
115
110
|
interface ParsedUnifiedSocialCreditCode {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
/**
|
|
112
|
+
* @title 登记管理部门代码
|
|
113
|
+
* 对应统一社会信用代码的第1位字符,用于标识发证机构类别(登记管理部门)。
|
|
114
|
+
*/
|
|
115
|
+
bureau: string;
|
|
121
116
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
117
|
+
/**
|
|
118
|
+
* @title 机构类别代码
|
|
119
|
+
* 对应统一社会信用代码的第2位字符,用于标识被赋码主体的类型(如企业、事业单位、社会组织等)。
|
|
120
|
+
*/
|
|
121
|
+
subjectType: string;
|
|
127
122
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
/**
|
|
124
|
+
* @title 登记管理机关行政区划代码
|
|
125
|
+
* 对应统一社会信用代码的第3至第8位字符,用于标识该主体登记机关所在的行政区划。
|
|
126
|
+
*/
|
|
127
|
+
division: string;
|
|
133
128
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
/**
|
|
130
|
+
* @title 主体标识码
|
|
131
|
+
* 对应统一社会信用代码的第9至第17位字符,用于标识特定的市场主体或法人单位。
|
|
132
|
+
*/
|
|
133
|
+
subject: string;
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
135
|
+
/**
|
|
136
|
+
* @title 校验码
|
|
137
|
+
* 对应统一社会信用代码的最后一位字符,用特定算法计算,用于校验代码有效性。
|
|
138
|
+
*/
|
|
139
|
+
checksum: string;
|
|
145
140
|
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
codes: string[];
|
|
142
|
+
labels: string[];
|
|
148
143
|
}
|
package/src/cn/formatDate.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export function formatDate(date: Date, format: 'yyyyMMDD') {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
switch (format) {
|
|
3
|
+
case 'yyyyMMDD': {
|
|
4
|
+
const year = date.getFullYear();
|
|
5
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
6
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
7
|
+
return `${year}${month}${day}`;
|
|
8
|
+
}
|
|
9
|
+
default:
|
|
10
|
+
throw new Error(`Invalid format`);
|
|
11
|
+
}
|
|
12
12
|
}
|
package/src/cn/parseSex.ts
CHANGED
|
@@ -1,27 +1,13 @@
|
|
|
1
|
-
export function parseSex(s: string):
|
|
2
|
-
|
|
3
|
-
| {
|
|
4
|
-
sex: 'Male' | 'Female';
|
|
5
|
-
male: boolean;
|
|
6
|
-
female: boolean;
|
|
7
|
-
} {
|
|
8
|
-
if (!s) return undefined;
|
|
1
|
+
export function parseSex(s: string): undefined | { sex: 'Male' | 'Female'; male: boolean; female: boolean } {
|
|
2
|
+
if (!s) return undefined;
|
|
9
3
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
case 'female':
|
|
20
|
-
return {
|
|
21
|
-
sex: 'Female',
|
|
22
|
-
male: false,
|
|
23
|
-
female: true,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return undefined;
|
|
4
|
+
switch (s.toLowerCase()) {
|
|
5
|
+
case '男':
|
|
6
|
+
case 'male':
|
|
7
|
+
return { sex: 'Male', male: true, female: false };
|
|
8
|
+
case '女':
|
|
9
|
+
case 'female':
|
|
10
|
+
return { sex: 'Female', male: false, female: true };
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
27
13
|
}
|
package/src/cn/types.d.ts
CHANGED
|
@@ -5,47 +5,47 @@
|
|
|
5
5
|
* @see https://en.wikipedia.org/wiki/Foreign_Permanent_Resident_ID_Card
|
|
6
6
|
*/
|
|
7
7
|
export interface ResidentIdentityCardInfo {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
8
|
+
/**
|
|
9
|
+
* @title 姓名
|
|
10
|
+
*/
|
|
11
|
+
name: string;
|
|
12
|
+
/**
|
|
13
|
+
* @title 性别
|
|
14
|
+
*/
|
|
15
|
+
sex: 'Male' | 'Female';
|
|
16
|
+
/**
|
|
17
|
+
* @title 民族
|
|
18
|
+
* 例如 '汉'/'满'/'回'
|
|
19
|
+
*/
|
|
20
|
+
ethnicity: string;
|
|
21
|
+
/**
|
|
22
|
+
* @title 出生日期
|
|
23
|
+
* @format date
|
|
24
|
+
*/
|
|
25
|
+
birthDate: string;
|
|
26
|
+
/**
|
|
27
|
+
* @title 地址
|
|
28
|
+
*
|
|
29
|
+
* - 通常为 domicile/户籍地
|
|
30
|
+
*/
|
|
31
|
+
address: string;
|
|
32
|
+
/**
|
|
33
|
+
* @title 身份证号
|
|
34
|
+
*/
|
|
35
|
+
identityCardNumber: string;
|
|
36
|
+
/**
|
|
37
|
+
* @title 签发机关
|
|
38
|
+
*/
|
|
39
|
+
issuer: string;
|
|
40
|
+
/**
|
|
41
|
+
* @title 有效期开始日期
|
|
42
|
+
* @format date
|
|
43
|
+
*/
|
|
44
|
+
validStartDate: string;
|
|
45
|
+
/**
|
|
46
|
+
* @title 有效期结束日期
|
|
47
|
+
* @format date
|
|
48
|
+
* @description 如长期有效则为 空
|
|
49
|
+
*/
|
|
50
|
+
validEndDate?: string;
|
|
51
51
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import { formatSort } from './formatSort';
|
|
3
|
+
|
|
4
|
+
test('formatSort', () => {
|
|
5
|
+
expect(formatSort([{ field: 'name', order: 'asc' }])).toEqual(['name asc']);
|
|
6
|
+
expect(formatSort([{ field: 'age', order: 'desc', nulls: 'last' }])).toEqual(['age desc nulls last']);
|
|
7
|
+
expect(
|
|
8
|
+
formatSort([
|
|
9
|
+
{ field: 'name', order: 'asc' },
|
|
10
|
+
{ field: 'age', order: 'desc' },
|
|
11
|
+
]),
|
|
12
|
+
).toEqual(['name asc', 'age desc']);
|
|
13
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SortRule } from './parseSort';
|
|
2
|
+
|
|
3
|
+
export function formatSort(s: SortRule[]): string[] {
|
|
4
|
+
return s
|
|
5
|
+
.map(({ field, order, nulls }) => {
|
|
6
|
+
if (!field) return '';
|
|
7
|
+
|
|
8
|
+
let r = field;
|
|
9
|
+
if (order) {
|
|
10
|
+
r += ` ${order}`;
|
|
11
|
+
}
|
|
12
|
+
if (nulls) {
|
|
13
|
+
r += ` nulls ${nulls}`;
|
|
14
|
+
}
|
|
15
|
+
return r;
|
|
16
|
+
})
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type MaybeNumber = number | null | string | undefined | bigint;
|
|
2
|
+
|
|
3
|
+
export function maybeNumber(v: MaybeNumber) {
|
|
4
|
+
if (v === null || v === undefined) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
switch (typeof v) {
|
|
9
|
+
case 'number':
|
|
10
|
+
return v;
|
|
11
|
+
case 'bigint':
|
|
12
|
+
return Number(v);
|
|
13
|
+
case 'string':
|
|
14
|
+
if (v === '') {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const n = Number(v);
|
|
19
|
+
if (isNaN(n)) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return n;
|
|
23
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { formatSort } from './formatSort';
|
|
3
|
+
import { parseSort } from './parseSort';
|
|
4
|
+
|
|
5
|
+
describe('parseSort', () => {
|
|
6
|
+
describe('basic parsing', () => {
|
|
7
|
+
test('handles empty inputs', () => {
|
|
8
|
+
expect(parseSort('')).toEqual([]);
|
|
9
|
+
expect(parseSort([])).toEqual([]);
|
|
10
|
+
expect(parseSort(null)).toEqual([]);
|
|
11
|
+
expect(parseSort(undefined)).toEqual([]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('handles simple field names', () => {
|
|
15
|
+
expect(parseSort('a')).toEqual([{ field: 'a', order: 'asc' }]);
|
|
16
|
+
expect(parseSort(['a'])).toEqual([{ field: 'a', order: 'asc' }]);
|
|
17
|
+
expect(parseSort('a.b')).toEqual([{ field: 'a.b', order: 'asc' }]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('handles prefix notation', () => {
|
|
21
|
+
expect(parseSort('-a')).toEqual([{ field: 'a', order: 'desc' }]);
|
|
22
|
+
expect(parseSort('+b')).toEqual([{ field: 'b', order: 'asc' }]);
|
|
23
|
+
expect(parseSort('-a,+b')).toEqual([
|
|
24
|
+
{ field: 'a', order: 'desc' },
|
|
25
|
+
{ field: 'b', order: 'asc' },
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('explicit ordering', () => {
|
|
31
|
+
test('handles explicit order keywords', () => {
|
|
32
|
+
expect(parseSort('a asc')).toEqual([{ field: 'a', order: 'asc' }]);
|
|
33
|
+
expect(parseSort('a desc')).toEqual([{ field: 'a', order: 'desc' }]);
|
|
34
|
+
expect(parseSort('-a asc')).toEqual([{ field: 'a', order: 'asc' }]); // explicit order overrides prefix
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('nulls handling', () => {
|
|
39
|
+
test('handles nulls specification', () => {
|
|
40
|
+
expect(parseSort('a asc nulls last')).toEqual([{ field: 'a', order: 'asc', nulls: 'last' }]);
|
|
41
|
+
expect(parseSort('a asc nulls first')).toEqual([{ field: 'a', order: 'asc', nulls: 'first' }]);
|
|
42
|
+
expect(parseSort('a desc nulls last')).toEqual([{ field: 'a', order: 'desc', nulls: 'last' }]);
|
|
43
|
+
expect(parseSort('a nulls first')).toEqual([{ field: 'a', order: 'asc', nulls: 'first' }]);
|
|
44
|
+
expect(parseSort('-a nulls first')).toEqual([{ field: 'a', order: 'desc', nulls: 'first' }]);
|
|
45
|
+
|
|
46
|
+
// Alternative syntax
|
|
47
|
+
expect(parseSort('a asc last')).toEqual([{ field: 'a', order: 'asc', nulls: 'last' }]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('object notation', () => {
|
|
52
|
+
test('handles object input', () => {
|
|
53
|
+
expect(parseSort([{ field: 'a', order: 'asc' }])).toEqual([{ field: 'a', order: 'asc' }]);
|
|
54
|
+
expect(parseSort([{ field: 'a', order: 'asc', nulls: 'last' }])).toEqual([
|
|
55
|
+
{ field: 'a', order: 'asc', nulls: 'last' },
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('invalid inputs', () => {
|
|
61
|
+
test('handles invalid inputs gracefully', () => {
|
|
62
|
+
expect(parseSort([{ order: 'asc' }])).toEqual([]);
|
|
63
|
+
expect(parseSort(['a,,', { field: '', order: 'asc' }])).toEqual([{ field: 'a', order: 'asc' }]);
|
|
64
|
+
expect(parseSort([',,', { field: 'a', order: 'asc' }])).toEqual([{ field: 'a', order: 'asc' }]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|