@lytjs/common-query 6.5.0 → 6.7.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.
- package/README.md +93 -93
- package/dist/index.cjs +59 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +20 -7
- package/dist/index.d.ts +20 -7
- package/dist/index.mjs +59 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/env.d.ts +4 -4
- package/src/index.ts +304 -235
package/README.md
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
# @lytjs/common-query
|
|
2
|
-
|
|
3
|
-
URL 查询字符串解析与构建工具,提供轻量级的 URL 处理能力。
|
|
4
|
-
|
|
5
|
-
## 安装
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @lytjs/common-query
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## API
|
|
12
|
-
|
|
13
|
-
### `parseQueryString(search: string): Record<string, string>`
|
|
14
|
-
|
|
15
|
-
解析 URL 查询字符串为对象。
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { parseQueryString } from '@lytjs/common-query';
|
|
19
|
-
|
|
20
|
-
parseQueryString('?key=value&key2=value2');
|
|
21
|
-
// { key: 'value', key2: 'value2' }
|
|
22
|
-
|
|
23
|
-
parseQueryString('key=value&key2=value2');
|
|
24
|
-
// { key: 'value', key2: 'value2' }
|
|
25
|
-
|
|
26
|
-
parseQueryString('?name=hello%20world');
|
|
27
|
-
// { name: 'hello world' }
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### `stringifyQueryString(params: Record<string, string | number | boolean>): string`
|
|
31
|
-
|
|
32
|
-
将对象序列化为查询字符串。
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
import { stringifyQueryString } from '@lytjs/common-query';
|
|
36
|
-
|
|
37
|
-
stringifyQueryString({ a: '1', b: '2' });
|
|
38
|
-
// 'a=1&b=2'
|
|
39
|
-
|
|
40
|
-
stringifyQueryString({ name: 'hello world' });
|
|
41
|
-
// 'name=hello%20world'
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### `parseURL(url: string): ParsedURL`
|
|
45
|
-
|
|
46
|
-
解析完整 URL,返回结构化信息。
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
import { parseURL } from '@lytjs/common-query';
|
|
50
|
-
|
|
51
|
-
parseURL('https://example.com:8080/path?name=test#section');
|
|
52
|
-
// {
|
|
53
|
-
// protocol: 'https://',
|
|
54
|
-
// host: 'example.com:8080',
|
|
55
|
-
// hostname: 'example.com',
|
|
56
|
-
// port: '8080',
|
|
57
|
-
// pathname: '/path',
|
|
58
|
-
// search: '?name=test',
|
|
59
|
-
// hash: '#section',
|
|
60
|
-
// searchParams: { name: 'test' },
|
|
61
|
-
// origin: 'https://example.com:8080',
|
|
62
|
-
// href: 'https://example.com:8080/path?name=test#section'
|
|
63
|
-
// }
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### `buildURL(base: string, params?, hash?): string`
|
|
67
|
-
|
|
68
|
-
构建完整 URL,支持合并已有查询参数。
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
import { buildURL } from '@lytjs/common-query';
|
|
72
|
-
|
|
73
|
-
buildURL('https://example.com/path', { a: '1', b: '2' });
|
|
74
|
-
// 'https://example.com/path?a=1&b=2'
|
|
75
|
-
|
|
76
|
-
buildURL('https://example.com/path?existing=1', { new: '2' });
|
|
77
|
-
// 'https://example.com/path?existing=1&new=2'
|
|
78
|
-
|
|
79
|
-
buildURL('/path', { page: 1 }, 'top');
|
|
80
|
-
// '/path?page=1#top'
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## 特性
|
|
84
|
-
|
|
85
|
-
- 零运行时依赖
|
|
86
|
-
- 体积 < 2KB(min+gzip)
|
|
87
|
-
- 支持绝对 URL 和相对 URL
|
|
88
|
-
- 自动编解码 URI 组件
|
|
89
|
-
- TypeScript 类型完整
|
|
90
|
-
|
|
91
|
-
## License
|
|
92
|
-
|
|
93
|
-
MIT
|
|
1
|
+
# @lytjs/common-query
|
|
2
|
+
|
|
3
|
+
URL 查询字符串解析与构建工具,提供轻量级的 URL 处理能力。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @lytjs/common-query
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### `parseQueryString(search: string): Record<string, string>`
|
|
14
|
+
|
|
15
|
+
解析 URL 查询字符串为对象。
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { parseQueryString } from '@lytjs/common-query';
|
|
19
|
+
|
|
20
|
+
parseQueryString('?key=value&key2=value2');
|
|
21
|
+
// { key: 'value', key2: 'value2' }
|
|
22
|
+
|
|
23
|
+
parseQueryString('key=value&key2=value2');
|
|
24
|
+
// { key: 'value', key2: 'value2' }
|
|
25
|
+
|
|
26
|
+
parseQueryString('?name=hello%20world');
|
|
27
|
+
// { name: 'hello world' }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### `stringifyQueryString(params: Record<string, string | number | boolean>): string`
|
|
31
|
+
|
|
32
|
+
将对象序列化为查询字符串。
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { stringifyQueryString } from '@lytjs/common-query';
|
|
36
|
+
|
|
37
|
+
stringifyQueryString({ a: '1', b: '2' });
|
|
38
|
+
// 'a=1&b=2'
|
|
39
|
+
|
|
40
|
+
stringifyQueryString({ name: 'hello world' });
|
|
41
|
+
// 'name=hello%20world'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `parseURL(url: string): ParsedURL`
|
|
45
|
+
|
|
46
|
+
解析完整 URL,返回结构化信息。
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { parseURL } from '@lytjs/common-query';
|
|
50
|
+
|
|
51
|
+
parseURL('https://example.com:8080/path?name=test#section');
|
|
52
|
+
// {
|
|
53
|
+
// protocol: 'https://',
|
|
54
|
+
// host: 'example.com:8080',
|
|
55
|
+
// hostname: 'example.com',
|
|
56
|
+
// port: '8080',
|
|
57
|
+
// pathname: '/path',
|
|
58
|
+
// search: '?name=test',
|
|
59
|
+
// hash: '#section',
|
|
60
|
+
// searchParams: { name: 'test' },
|
|
61
|
+
// origin: 'https://example.com:8080',
|
|
62
|
+
// href: 'https://example.com:8080/path?name=test#section'
|
|
63
|
+
// }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `buildURL(base: string, params?, hash?): string`
|
|
67
|
+
|
|
68
|
+
构建完整 URL,支持合并已有查询参数。
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { buildURL } from '@lytjs/common-query';
|
|
72
|
+
|
|
73
|
+
buildURL('https://example.com/path', { a: '1', b: '2' });
|
|
74
|
+
// 'https://example.com/path?a=1&b=2'
|
|
75
|
+
|
|
76
|
+
buildURL('https://example.com/path?existing=1', { new: '2' });
|
|
77
|
+
// 'https://example.com/path?existing=1&new=2'
|
|
78
|
+
|
|
79
|
+
buildURL('/path', { page: 1 }, 'top');
|
|
80
|
+
// '/path?page=1#top'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 特性
|
|
84
|
+
|
|
85
|
+
- 零运行时依赖
|
|
86
|
+
- 体积 < 2KB(min+gzip)
|
|
87
|
+
- 支持绝对 URL 和相对 URL
|
|
88
|
+
- 自动编解码 URI 组件
|
|
89
|
+
- TypeScript 类型完整
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
function parseQueryString(search) {
|
|
4
|
+
function parseQueryString(search, options) {
|
|
5
5
|
if (!search) return {};
|
|
6
6
|
const str = search.startsWith("?") ? search.slice(1) : search;
|
|
7
7
|
if (!str) return {};
|
|
8
|
+
const supportArrays = options?.supportArrays ?? false;
|
|
9
|
+
if (supportArrays) {
|
|
10
|
+
const result2 = {};
|
|
11
|
+
const pairs2 = str.split("&");
|
|
12
|
+
for (const pair of pairs2) {
|
|
13
|
+
if (!pair) continue;
|
|
14
|
+
const eqIndex = pair.indexOf("=");
|
|
15
|
+
let key;
|
|
16
|
+
let value;
|
|
17
|
+
if (eqIndex === -1) {
|
|
18
|
+
key = pair;
|
|
19
|
+
value = "";
|
|
20
|
+
} else {
|
|
21
|
+
key = pair.slice(0, eqIndex);
|
|
22
|
+
value = pair.slice(eqIndex + 1);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
key = decodeURIComponent(key);
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
value = decodeURIComponent(value);
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
if (result2[key]) {
|
|
33
|
+
if (Array.isArray(result2[key])) {
|
|
34
|
+
result2[key].push(value);
|
|
35
|
+
} else {
|
|
36
|
+
result2[key] = [result2[key], value];
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
result2[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result2;
|
|
43
|
+
}
|
|
8
44
|
const result = {};
|
|
9
45
|
const pairs = str.split("&");
|
|
10
46
|
for (const pair of pairs) {
|
|
@@ -31,19 +67,26 @@ function parseQueryString(search) {
|
|
|
31
67
|
}
|
|
32
68
|
return result;
|
|
33
69
|
}
|
|
70
|
+
function parseQueryStringWithArrays(search) {
|
|
71
|
+
return parseQueryString(search, { supportArrays: true });
|
|
72
|
+
}
|
|
34
73
|
function stringifyQueryString(params) {
|
|
35
74
|
const keys = Object.keys(params);
|
|
36
75
|
if (keys.length === 0) return "";
|
|
37
76
|
const parts = [];
|
|
38
77
|
for (const key of keys) {
|
|
39
78
|
const value = params[key];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
for (const item of value) {
|
|
81
|
+
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(item)));
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(value)));
|
|
85
|
+
}
|
|
43
86
|
}
|
|
44
87
|
return parts.join("&");
|
|
45
88
|
}
|
|
46
|
-
function parseURL(url) {
|
|
89
|
+
function parseURL(url, _options) {
|
|
47
90
|
let rest = url;
|
|
48
91
|
let hash = "";
|
|
49
92
|
const hashIndex = rest.indexOf("#");
|
|
@@ -99,7 +142,7 @@ function parseURL(url) {
|
|
|
99
142
|
} else {
|
|
100
143
|
pathname = rest;
|
|
101
144
|
}
|
|
102
|
-
const searchParams = parseQueryString(search);
|
|
145
|
+
const searchParams = parseQueryString(search, { supportArrays: true });
|
|
103
146
|
const origin = protocol ? protocol + host : "";
|
|
104
147
|
return {
|
|
105
148
|
protocol,
|
|
@@ -130,10 +173,17 @@ function buildURL(base, params, hash) {
|
|
|
130
173
|
existingSearch = baseWithoutHash.slice(searchIdx + 1);
|
|
131
174
|
baseWithoutSearch = baseWithoutHash.slice(0, searchIdx);
|
|
132
175
|
}
|
|
133
|
-
const mergedParams =
|
|
176
|
+
const mergedParams = parseQueryStringWithArrays(existingSearch);
|
|
134
177
|
if (params) {
|
|
135
178
|
for (const key of Object.keys(params)) {
|
|
136
|
-
|
|
179
|
+
const value = params[key];
|
|
180
|
+
if (value !== void 0 && value !== null) {
|
|
181
|
+
if (Array.isArray(value)) {
|
|
182
|
+
mergedParams[key] = value.map((v) => String(v));
|
|
183
|
+
} else {
|
|
184
|
+
mergedParams[key] = String(value);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
137
187
|
}
|
|
138
188
|
}
|
|
139
189
|
const queryString = stringifyQueryString(mergedParams);
|
|
@@ -150,6 +200,7 @@ function buildURL(base, params, hash) {
|
|
|
150
200
|
|
|
151
201
|
exports.buildURL = buildURL;
|
|
152
202
|
exports.parseQueryString = parseQueryString;
|
|
203
|
+
exports.parseQueryStringWithArrays = parseQueryStringWithArrays;
|
|
153
204
|
exports.parseURL = parseURL;
|
|
154
205
|
exports.stringifyQueryString = stringifyQueryString;
|
|
155
206
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * @lytjs/common-query\r\n * URL 查询字符串解析与构建工具\r\n */\r\n\r\n/**\r\n * 解析后的 URL 结构\r\n */\r\nexport interface ParsedURL {\r\n protocol: string;\r\n host: string;\r\n hostname: string;\r\n port: string;\r\n pathname: string;\r\n search: string;\r\n hash: string;\r\n searchParams: Record<string, string>;\r\n origin: string;\r\n href: string;\r\n}\r\n\r\n/**\r\n * 解析 URL 查询字符串为对象\r\n *\r\n * @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式\r\n * @returns 解析后的键值对对象\r\n */\r\nexport function parseQueryString(search: string): Record<string, string> {\r\n if (!search) return {};\r\n\r\n const str = search.startsWith('?') ? search.slice(1) : search;\r\n if (!str) return {};\r\n\r\n const result: Record<string, string> = {};\r\n\r\n const pairs = str.split('&');\r\n for (const pair of pairs) {\r\n if (!pair) continue;\r\n const eqIndex = pair.indexOf('=');\r\n let key: string;\r\n let value: string;\r\n if (eqIndex === -1) {\r\n key = pair;\r\n value = '';\r\n } else {\r\n key = pair.slice(0, eqIndex);\r\n value = pair.slice(eqIndex + 1);\r\n }\r\n try {\r\n key = decodeURIComponent(key);\r\n } catch {\r\n // keep key as-is if decode fails\r\n }\r\n try {\r\n value = decodeURIComponent(value);\r\n } catch {\r\n // keep value as-is if decode fails\r\n }\r\n result[key] = value;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * 将对象序列化为查询字符串\r\n *\r\n * @param params - 键值对对象\r\n * @returns 序列化后的查询字符串(不含前导 ?)\r\n */\r\nexport function stringifyQueryString(\r\n params: Record<string, string | number | boolean>,\r\n): string {\r\n const keys = Object.keys(params);\r\n if (keys.length === 0) return '';\r\n\r\n const parts: string[] = [];\r\n for (const key of keys) {\r\n const value = params[key];\r\n parts.push(\r\n encodeURIComponent(key) + '=' + encodeURIComponent(String(value)),\r\n );\r\n }\r\n\r\n return parts.join('&');\r\n}\r\n\r\n/**\r\n * 解析完整 URL\r\n *\r\n * @param url - URL 字符串,支持绝对 URL 和相对 URL\r\n * @returns 解析后的 ParsedURL 对象\r\n */\r\nexport function parseURL(url: string): ParsedURL {\r\n let rest = url;\r\n\r\n // Extract hash\r\n let hash = '';\r\n const hashIndex = rest.indexOf('#');\r\n if (hashIndex !== -1) {\r\n hash = rest.slice(hashIndex);\r\n rest = rest.slice(0, hashIndex);\r\n }\r\n\r\n // Extract search\r\n let search = '';\r\n const searchIndex = rest.indexOf('?');\r\n if (searchIndex !== -1) {\r\n search = rest.slice(searchIndex);\r\n rest = rest.slice(0, searchIndex);\r\n }\r\n\r\n // Extract protocol\r\n let protocol = '';\r\n const protocolIndex = rest.indexOf('://');\r\n if (protocolIndex !== -1) {\r\n protocol = rest.slice(0, protocolIndex + 3);\r\n rest = rest.slice(protocolIndex + 3);\r\n }\r\n\r\n // Extract pathname (everything after host)\r\n let pathname = '';\r\n let host = '';\r\n let hostname = '';\r\n let port = '';\r\n\r\n if (protocol) {\r\n // Absolute URL: extract host and pathname\r\n const slashIndex = rest.indexOf('/');\r\n if (slashIndex !== -1) {\r\n host = rest.slice(0, slashIndex);\r\n pathname = rest.slice(slashIndex);\r\n } else {\r\n host = rest;\r\n pathname = '';\r\n }\r\n\r\n // Parse host into hostname and port\r\n // Handle IPv6 addresses like [::1]:8080\r\n if (host.startsWith('[')) {\r\n const bracketEnd = host.indexOf(']');\r\n if (bracketEnd !== -1) {\r\n hostname = host.slice(0, bracketEnd + 1);\r\n if (host.length > bracketEnd + 1 && host[bracketEnd + 1] === ':') {\r\n port = host.slice(bracketEnd + 2);\r\n }\r\n } else {\r\n hostname = host;\r\n }\r\n } else {\r\n const colonIndex = host.lastIndexOf(':');\r\n if (colonIndex !== -1) {\r\n hostname = host.slice(0, colonIndex);\r\n port = host.slice(colonIndex + 1);\r\n } else {\r\n hostname = host;\r\n port = '';\r\n }\r\n }\r\n } else {\r\n // Relative URL\r\n pathname = rest;\r\n }\r\n\r\n const searchParams = parseQueryString(search);\r\n const origin = protocol ? protocol + host : '';\r\n\r\n return {\r\n protocol,\r\n host,\r\n hostname,\r\n port,\r\n pathname,\r\n search,\r\n hash,\r\n searchParams,\r\n origin,\r\n href: url,\r\n };\r\n}\r\n\r\n/**\r\n * 构建完整 URL\r\n *\r\n * @param base - 基础 URL\r\n * @param params - 查询参数(可选)\r\n * @param hash - hash 片段(可选,不含 #)\r\n * @returns 构建后的完整 URL\r\n */\r\nexport function buildURL(\r\n base: string,\r\n params?: Record<string, string | number | boolean>,\r\n hash?: string,\r\n): string {\r\n if (!params && !hash) return base;\r\n\r\n // Extract and remove existing hash from base\r\n let baseWithoutHash = base;\r\n let existingHash = '';\r\n const hashIdx = base.indexOf('#');\r\n if (hashIdx !== -1) {\r\n existingHash = base.slice(hashIdx);\r\n baseWithoutHash = base.slice(0, hashIdx);\r\n }\r\n\r\n // Parse existing query params from base (without hash)\r\n let existingSearch = '';\r\n let baseWithoutSearch = baseWithoutHash;\r\n const searchIdx = baseWithoutHash.indexOf('?');\r\n if (searchIdx !== -1) {\r\n existingSearch = baseWithoutHash.slice(searchIdx + 1);\r\n baseWithoutSearch = baseWithoutHash.slice(0, searchIdx);\r\n }\r\n\r\n // Merge existing and new params\r\n const mergedParams = parseQueryString(existingSearch);\r\n if (params) {\r\n for (const key of Object.keys(params)) {\r\n mergedParams[key] = String(params[key]);\r\n }\r\n }\r\n\r\n const queryString = stringifyQueryString(mergedParams);\r\n let result = baseWithoutSearch;\r\n if (queryString) {\r\n result += '?' + queryString;\r\n }\r\n // Use provided hash, or keep existing hash if no new hash is given\r\n const finalHash = hash !== undefined ? '#' + encodeURIComponent(hash) : existingHash;\r\n if (finalHash) {\r\n result += finalHash;\r\n }\r\n\r\n return result;\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["result","pairs"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lytjs/common-query\n * URL 查询字符串解析与构建工具\n */\n\n/**\n * 解析后的 URL 结构\n */\nexport interface ParsedURL {\n protocol: string;\n host: string;\n hostname: string;\n port: string;\n pathname: string;\n search: string;\n hash: string;\n searchParams: Record<string, string | string[]>;\n origin: string;\n href: string;\n}\n\n/**\n * 解析 URL 查询字符串为对象\n *\n * @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式\n * @param options - 解析选项\n * @returns 解析后的键值对对象\n */\nexport function parseQueryString(\n search: string,\n options?: { supportArrays?: boolean },\n): Record<string, string> | Record<string, string | string[]> {\n if (!search) return {};\n\n const str = search.startsWith('?') ? search.slice(1) : search;\n if (!str) return {};\n\n const supportArrays = options?.supportArrays ?? false;\n\n if (supportArrays) {\n const result: Record<string, string | string[]> = {};\n const pairs = str.split('&');\n for (const pair of pairs) {\n if (!pair) continue;\n const eqIndex = pair.indexOf('=');\n let key: string;\n let value: string;\n if (eqIndex === -1) {\n key = pair;\n value = '';\n } else {\n key = pair.slice(0, eqIndex);\n value = pair.slice(eqIndex + 1);\n }\n try {\n key = decodeURIComponent(key);\n } catch {\n // keep key as-is if decode fails\n }\n try {\n value = decodeURIComponent(value);\n } catch {\n // keep value as-is if decode fails\n }\n\n if (result[key]) {\n if (Array.isArray(result[key])) {\n (result[key] as string[]).push(value);\n } else {\n result[key] = [result[key] as string, value];\n }\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n // 保持原有的行为(向后兼容)\n const result: Record<string, string> = {};\n const pairs = str.split('&');\n for (const pair of pairs) {\n if (!pair) continue;\n const eqIndex = pair.indexOf('=');\n let key: string;\n let value: string;\n if (eqIndex === -1) {\n key = pair;\n value = '';\n } else {\n key = pair.slice(0, eqIndex);\n value = pair.slice(eqIndex + 1);\n }\n try {\n key = decodeURIComponent(key);\n } catch {\n // keep key as-is if decode fails\n }\n try {\n value = decodeURIComponent(value);\n } catch {\n // keep value as-is if decode fails\n }\n result[key] = value;\n }\n return result;\n}\n\n/**\n * 解析 URL 查询字符串为对象,支持数组值(重复键)\n * 这是 parseQueryString 支持数组的便捷版本\n *\n * @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式\n * @returns 解析后的键值对对象\n */\nexport function parseQueryStringWithArrays(search: string): Record<string, string | string[]> {\n return parseQueryString(search, { supportArrays: true }) as Record<string, string | string[]>;\n}\n\n/**\n * 将对象序列化为查询字符串\n *\n * @param params - 键值对对象,支持数组值\n * @returns 序列化后的查询字符串(不含前导 ?)\n */\nexport function stringifyQueryString(\n params: Record<string, string | number | boolean | Array<string | number | boolean>>,\n): string {\n const keys = Object.keys(params);\n if (keys.length === 0) return '';\n\n const parts: string[] = [];\n for (const key of keys) {\n const value = params[key];\n if (Array.isArray(value)) {\n for (const item of value) {\n parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(item)));\n }\n } else {\n parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(value)));\n }\n }\n\n return parts.join('&');\n}\n\n/**\n * 解析完整 URL\n *\n * @param url - URL 字符串,支持绝对 URL 和相对 URL\n * @returns 解析后的 ParsedURL 对象\n */\nexport function parseURL(url: string, _options?: { supportArrays?: boolean }): ParsedURL {\n let rest = url;\n\n // Extract hash\n let hash = '';\n const hashIndex = rest.indexOf('#');\n if (hashIndex !== -1) {\n hash = rest.slice(hashIndex);\n rest = rest.slice(0, hashIndex);\n }\n\n // Extract search\n let search = '';\n const searchIndex = rest.indexOf('?');\n if (searchIndex !== -1) {\n search = rest.slice(searchIndex);\n rest = rest.slice(0, searchIndex);\n }\n\n // Extract protocol\n let protocol = '';\n const protocolIndex = rest.indexOf('://');\n if (protocolIndex !== -1) {\n protocol = rest.slice(0, protocolIndex + 3);\n rest = rest.slice(protocolIndex + 3);\n }\n\n // Extract pathname (everything after host)\n let pathname = '';\n let host = '';\n let hostname = '';\n let port = '';\n\n if (protocol) {\n // Absolute URL: extract host and pathname\n const slashIndex = rest.indexOf('/');\n if (slashIndex !== -1) {\n host = rest.slice(0, slashIndex);\n pathname = rest.slice(slashIndex);\n } else {\n host = rest;\n pathname = '';\n }\n\n // Parse host into hostname and port\n // Handle IPv6 addresses like [::1]:8080\n if (host.startsWith('[')) {\n const bracketEnd = host.indexOf(']');\n if (bracketEnd !== -1) {\n hostname = host.slice(0, bracketEnd + 1);\n if (host.length > bracketEnd + 1 && host[bracketEnd + 1] === ':') {\n port = host.slice(bracketEnd + 2);\n }\n } else {\n hostname = host;\n }\n } else {\n const colonIndex = host.lastIndexOf(':');\n if (colonIndex !== -1) {\n hostname = host.slice(0, colonIndex);\n port = host.slice(colonIndex + 1);\n } else {\n hostname = host;\n port = '';\n }\n }\n } else {\n // Relative URL\n pathname = rest;\n }\n\n const searchParams = parseQueryString(search, { supportArrays: true }) as Record<\n string,\n string | string[]\n >;\n const origin = protocol ? protocol + host : '';\n\n return {\n protocol,\n host,\n hostname,\n port,\n pathname,\n search,\n hash,\n searchParams,\n origin,\n href: url,\n };\n}\n\n/**\n * 构建完整 URL\n *\n * @param base - 基础 URL\n * @param params - 查询参数(可选)\n * @param hash - hash 片段(可选,不含 #)\n * @returns 构建后的完整 URL\n */\nexport function buildURL(\n base: string,\n params?: Record<string, string | number | boolean | Array<string | number | boolean>>,\n hash?: string,\n): string {\n if (!params && !hash) return base;\n\n // Extract and remove existing hash from base\n let baseWithoutHash = base;\n let existingHash = '';\n const hashIdx = base.indexOf('#');\n if (hashIdx !== -1) {\n existingHash = base.slice(hashIdx);\n baseWithoutHash = base.slice(0, hashIdx);\n }\n\n // Parse existing query params from base (without hash)\n let existingSearch = '';\n let baseWithoutSearch = baseWithoutHash;\n const searchIdx = baseWithoutHash.indexOf('?');\n if (searchIdx !== -1) {\n existingSearch = baseWithoutHash.slice(searchIdx + 1);\n baseWithoutSearch = baseWithoutHash.slice(0, searchIdx);\n }\n\n // Merge existing and new params\n const mergedParams = parseQueryStringWithArrays(existingSearch);\n if (params) {\n for (const key of Object.keys(params)) {\n const value = params[key];\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n mergedParams[key] = value.map((v) => String(v));\n } else {\n mergedParams[key] = String(value);\n }\n }\n }\n }\n\n const queryString = stringifyQueryString(mergedParams);\n let result = baseWithoutSearch;\n if (queryString) {\n result += '?' + queryString;\n }\n // Use provided hash, or keep existing hash if no new hash is given\n const finalHash = hash !== undefined ? '#' + encodeURIComponent(hash) : existingHash;\n if (finalHash) {\n result += finalHash;\n }\n\n return result;\n}\n"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -13,7 +13,7 @@ interface ParsedURL {
|
|
|
13
13
|
pathname: string;
|
|
14
14
|
search: string;
|
|
15
15
|
hash: string;
|
|
16
|
-
searchParams: Record<string, string>;
|
|
16
|
+
searchParams: Record<string, string | string[]>;
|
|
17
17
|
origin: string;
|
|
18
18
|
href: string;
|
|
19
19
|
}
|
|
@@ -21,23 +21,36 @@ interface ParsedURL {
|
|
|
21
21
|
* 解析 URL 查询字符串为对象
|
|
22
22
|
*
|
|
23
23
|
* @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式
|
|
24
|
+
* @param options - 解析选项
|
|
24
25
|
* @returns 解析后的键值对对象
|
|
25
26
|
*/
|
|
26
|
-
declare function parseQueryString(search: string
|
|
27
|
+
declare function parseQueryString(search: string, options?: {
|
|
28
|
+
supportArrays?: boolean;
|
|
29
|
+
}): Record<string, string> | Record<string, string | string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* 解析 URL 查询字符串为对象,支持数组值(重复键)
|
|
32
|
+
* 这是 parseQueryString 支持数组的便捷版本
|
|
33
|
+
*
|
|
34
|
+
* @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式
|
|
35
|
+
* @returns 解析后的键值对对象
|
|
36
|
+
*/
|
|
37
|
+
declare function parseQueryStringWithArrays(search: string): Record<string, string | string[]>;
|
|
27
38
|
/**
|
|
28
39
|
* 将对象序列化为查询字符串
|
|
29
40
|
*
|
|
30
|
-
* @param params -
|
|
41
|
+
* @param params - 键值对对象,支持数组值
|
|
31
42
|
* @returns 序列化后的查询字符串(不含前导 ?)
|
|
32
43
|
*/
|
|
33
|
-
declare function stringifyQueryString(params: Record<string, string | number | boolean
|
|
44
|
+
declare function stringifyQueryString(params: Record<string, string | number | boolean | Array<string | number | boolean>>): string;
|
|
34
45
|
/**
|
|
35
46
|
* 解析完整 URL
|
|
36
47
|
*
|
|
37
48
|
* @param url - URL 字符串,支持绝对 URL 和相对 URL
|
|
38
49
|
* @returns 解析后的 ParsedURL 对象
|
|
39
50
|
*/
|
|
40
|
-
declare function parseURL(url: string
|
|
51
|
+
declare function parseURL(url: string, _options?: {
|
|
52
|
+
supportArrays?: boolean;
|
|
53
|
+
}): ParsedURL;
|
|
41
54
|
/**
|
|
42
55
|
* 构建完整 URL
|
|
43
56
|
*
|
|
@@ -46,6 +59,6 @@ declare function parseURL(url: string): ParsedURL;
|
|
|
46
59
|
* @param hash - hash 片段(可选,不含 #)
|
|
47
60
|
* @returns 构建后的完整 URL
|
|
48
61
|
*/
|
|
49
|
-
declare function buildURL(base: string, params?: Record<string, string | number | boolean
|
|
62
|
+
declare function buildURL(base: string, params?: Record<string, string | number | boolean | Array<string | number | boolean>>, hash?: string): string;
|
|
50
63
|
|
|
51
|
-
export { type ParsedURL, buildURL, parseQueryString, parseURL, stringifyQueryString };
|
|
64
|
+
export { type ParsedURL, buildURL, parseQueryString, parseQueryStringWithArrays, parseURL, stringifyQueryString };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface ParsedURL {
|
|
|
13
13
|
pathname: string;
|
|
14
14
|
search: string;
|
|
15
15
|
hash: string;
|
|
16
|
-
searchParams: Record<string, string>;
|
|
16
|
+
searchParams: Record<string, string | string[]>;
|
|
17
17
|
origin: string;
|
|
18
18
|
href: string;
|
|
19
19
|
}
|
|
@@ -21,23 +21,36 @@ interface ParsedURL {
|
|
|
21
21
|
* 解析 URL 查询字符串为对象
|
|
22
22
|
*
|
|
23
23
|
* @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式
|
|
24
|
+
* @param options - 解析选项
|
|
24
25
|
* @returns 解析后的键值对对象
|
|
25
26
|
*/
|
|
26
|
-
declare function parseQueryString(search: string
|
|
27
|
+
declare function parseQueryString(search: string, options?: {
|
|
28
|
+
supportArrays?: boolean;
|
|
29
|
+
}): Record<string, string> | Record<string, string | string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* 解析 URL 查询字符串为对象,支持数组值(重复键)
|
|
32
|
+
* 这是 parseQueryString 支持数组的便捷版本
|
|
33
|
+
*
|
|
34
|
+
* @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式
|
|
35
|
+
* @returns 解析后的键值对对象
|
|
36
|
+
*/
|
|
37
|
+
declare function parseQueryStringWithArrays(search: string): Record<string, string | string[]>;
|
|
27
38
|
/**
|
|
28
39
|
* 将对象序列化为查询字符串
|
|
29
40
|
*
|
|
30
|
-
* @param params -
|
|
41
|
+
* @param params - 键值对对象,支持数组值
|
|
31
42
|
* @returns 序列化后的查询字符串(不含前导 ?)
|
|
32
43
|
*/
|
|
33
|
-
declare function stringifyQueryString(params: Record<string, string | number | boolean
|
|
44
|
+
declare function stringifyQueryString(params: Record<string, string | number | boolean | Array<string | number | boolean>>): string;
|
|
34
45
|
/**
|
|
35
46
|
* 解析完整 URL
|
|
36
47
|
*
|
|
37
48
|
* @param url - URL 字符串,支持绝对 URL 和相对 URL
|
|
38
49
|
* @returns 解析后的 ParsedURL 对象
|
|
39
50
|
*/
|
|
40
|
-
declare function parseURL(url: string
|
|
51
|
+
declare function parseURL(url: string, _options?: {
|
|
52
|
+
supportArrays?: boolean;
|
|
53
|
+
}): ParsedURL;
|
|
41
54
|
/**
|
|
42
55
|
* 构建完整 URL
|
|
43
56
|
*
|
|
@@ -46,6 +59,6 @@ declare function parseURL(url: string): ParsedURL;
|
|
|
46
59
|
* @param hash - hash 片段(可选,不含 #)
|
|
47
60
|
* @returns 构建后的完整 URL
|
|
48
61
|
*/
|
|
49
|
-
declare function buildURL(base: string, params?: Record<string, string | number | boolean
|
|
62
|
+
declare function buildURL(base: string, params?: Record<string, string | number | boolean | Array<string | number | boolean>>, hash?: string): string;
|
|
50
63
|
|
|
51
|
-
export { type ParsedURL, buildURL, parseQueryString, parseURL, stringifyQueryString };
|
|
64
|
+
export { type ParsedURL, buildURL, parseQueryString, parseQueryStringWithArrays, parseURL, stringifyQueryString };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
function parseQueryString(search) {
|
|
2
|
+
function parseQueryString(search, options) {
|
|
3
3
|
if (!search) return {};
|
|
4
4
|
const str = search.startsWith("?") ? search.slice(1) : search;
|
|
5
5
|
if (!str) return {};
|
|
6
|
+
const supportArrays = options?.supportArrays ?? false;
|
|
7
|
+
if (supportArrays) {
|
|
8
|
+
const result2 = {};
|
|
9
|
+
const pairs2 = str.split("&");
|
|
10
|
+
for (const pair of pairs2) {
|
|
11
|
+
if (!pair) continue;
|
|
12
|
+
const eqIndex = pair.indexOf("=");
|
|
13
|
+
let key;
|
|
14
|
+
let value;
|
|
15
|
+
if (eqIndex === -1) {
|
|
16
|
+
key = pair;
|
|
17
|
+
value = "";
|
|
18
|
+
} else {
|
|
19
|
+
key = pair.slice(0, eqIndex);
|
|
20
|
+
value = pair.slice(eqIndex + 1);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
key = decodeURIComponent(key);
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
value = decodeURIComponent(value);
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
if (result2[key]) {
|
|
31
|
+
if (Array.isArray(result2[key])) {
|
|
32
|
+
result2[key].push(value);
|
|
33
|
+
} else {
|
|
34
|
+
result2[key] = [result2[key], value];
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
result2[key] = value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result2;
|
|
41
|
+
}
|
|
6
42
|
const result = {};
|
|
7
43
|
const pairs = str.split("&");
|
|
8
44
|
for (const pair of pairs) {
|
|
@@ -29,19 +65,26 @@ function parseQueryString(search) {
|
|
|
29
65
|
}
|
|
30
66
|
return result;
|
|
31
67
|
}
|
|
68
|
+
function parseQueryStringWithArrays(search) {
|
|
69
|
+
return parseQueryString(search, { supportArrays: true });
|
|
70
|
+
}
|
|
32
71
|
function stringifyQueryString(params) {
|
|
33
72
|
const keys = Object.keys(params);
|
|
34
73
|
if (keys.length === 0) return "";
|
|
35
74
|
const parts = [];
|
|
36
75
|
for (const key of keys) {
|
|
37
76
|
const value = params[key];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
for (const item of value) {
|
|
79
|
+
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(item)));
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(value)));
|
|
83
|
+
}
|
|
41
84
|
}
|
|
42
85
|
return parts.join("&");
|
|
43
86
|
}
|
|
44
|
-
function parseURL(url) {
|
|
87
|
+
function parseURL(url, _options) {
|
|
45
88
|
let rest = url;
|
|
46
89
|
let hash = "";
|
|
47
90
|
const hashIndex = rest.indexOf("#");
|
|
@@ -97,7 +140,7 @@ function parseURL(url) {
|
|
|
97
140
|
} else {
|
|
98
141
|
pathname = rest;
|
|
99
142
|
}
|
|
100
|
-
const searchParams = parseQueryString(search);
|
|
143
|
+
const searchParams = parseQueryString(search, { supportArrays: true });
|
|
101
144
|
const origin = protocol ? protocol + host : "";
|
|
102
145
|
return {
|
|
103
146
|
protocol,
|
|
@@ -128,10 +171,17 @@ function buildURL(base, params, hash) {
|
|
|
128
171
|
existingSearch = baseWithoutHash.slice(searchIdx + 1);
|
|
129
172
|
baseWithoutSearch = baseWithoutHash.slice(0, searchIdx);
|
|
130
173
|
}
|
|
131
|
-
const mergedParams =
|
|
174
|
+
const mergedParams = parseQueryStringWithArrays(existingSearch);
|
|
132
175
|
if (params) {
|
|
133
176
|
for (const key of Object.keys(params)) {
|
|
134
|
-
|
|
177
|
+
const value = params[key];
|
|
178
|
+
if (value !== void 0 && value !== null) {
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
mergedParams[key] = value.map((v) => String(v));
|
|
181
|
+
} else {
|
|
182
|
+
mergedParams[key] = String(value);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
135
185
|
}
|
|
136
186
|
}
|
|
137
187
|
const queryString = stringifyQueryString(mergedParams);
|
|
@@ -146,6 +196,6 @@ function buildURL(base, params, hash) {
|
|
|
146
196
|
return result;
|
|
147
197
|
}
|
|
148
198
|
|
|
149
|
-
export { buildURL, parseQueryString, parseURL, stringifyQueryString };
|
|
199
|
+
export { buildURL, parseQueryString, parseQueryStringWithArrays, parseURL, stringifyQueryString };
|
|
150
200
|
//# sourceMappingURL=index.mjs.map
|
|
151
201
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * @lytjs/common-query\r\n * URL 查询字符串解析与构建工具\r\n */\r\n\r\n/**\r\n * 解析后的 URL 结构\r\n */\r\nexport interface ParsedURL {\r\n protocol: string;\r\n host: string;\r\n hostname: string;\r\n port: string;\r\n pathname: string;\r\n search: string;\r\n hash: string;\r\n searchParams: Record<string, string>;\r\n origin: string;\r\n href: string;\r\n}\r\n\r\n/**\r\n * 解析 URL 查询字符串为对象\r\n *\r\n * @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式\r\n * @returns 解析后的键值对对象\r\n */\r\nexport function parseQueryString(search: string): Record<string, string> {\r\n if (!search) return {};\r\n\r\n const str = search.startsWith('?') ? search.slice(1) : search;\r\n if (!str) return {};\r\n\r\n const result: Record<string, string> = {};\r\n\r\n const pairs = str.split('&');\r\n for (const pair of pairs) {\r\n if (!pair) continue;\r\n const eqIndex = pair.indexOf('=');\r\n let key: string;\r\n let value: string;\r\n if (eqIndex === -1) {\r\n key = pair;\r\n value = '';\r\n } else {\r\n key = pair.slice(0, eqIndex);\r\n value = pair.slice(eqIndex + 1);\r\n }\r\n try {\r\n key = decodeURIComponent(key);\r\n } catch {\r\n // keep key as-is if decode fails\r\n }\r\n try {\r\n value = decodeURIComponent(value);\r\n } catch {\r\n // keep value as-is if decode fails\r\n }\r\n result[key] = value;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * 将对象序列化为查询字符串\r\n *\r\n * @param params - 键值对对象\r\n * @returns 序列化后的查询字符串(不含前导 ?)\r\n */\r\nexport function stringifyQueryString(\r\n params: Record<string, string | number | boolean>,\r\n): string {\r\n const keys = Object.keys(params);\r\n if (keys.length === 0) return '';\r\n\r\n const parts: string[] = [];\r\n for (const key of keys) {\r\n const value = params[key];\r\n parts.push(\r\n encodeURIComponent(key) + '=' + encodeURIComponent(String(value)),\r\n );\r\n }\r\n\r\n return parts.join('&');\r\n}\r\n\r\n/**\r\n * 解析完整 URL\r\n *\r\n * @param url - URL 字符串,支持绝对 URL 和相对 URL\r\n * @returns 解析后的 ParsedURL 对象\r\n */\r\nexport function parseURL(url: string): ParsedURL {\r\n let rest = url;\r\n\r\n // Extract hash\r\n let hash = '';\r\n const hashIndex = rest.indexOf('#');\r\n if (hashIndex !== -1) {\r\n hash = rest.slice(hashIndex);\r\n rest = rest.slice(0, hashIndex);\r\n }\r\n\r\n // Extract search\r\n let search = '';\r\n const searchIndex = rest.indexOf('?');\r\n if (searchIndex !== -1) {\r\n search = rest.slice(searchIndex);\r\n rest = rest.slice(0, searchIndex);\r\n }\r\n\r\n // Extract protocol\r\n let protocol = '';\r\n const protocolIndex = rest.indexOf('://');\r\n if (protocolIndex !== -1) {\r\n protocol = rest.slice(0, protocolIndex + 3);\r\n rest = rest.slice(protocolIndex + 3);\r\n }\r\n\r\n // Extract pathname (everything after host)\r\n let pathname = '';\r\n let host = '';\r\n let hostname = '';\r\n let port = '';\r\n\r\n if (protocol) {\r\n // Absolute URL: extract host and pathname\r\n const slashIndex = rest.indexOf('/');\r\n if (slashIndex !== -1) {\r\n host = rest.slice(0, slashIndex);\r\n pathname = rest.slice(slashIndex);\r\n } else {\r\n host = rest;\r\n pathname = '';\r\n }\r\n\r\n // Parse host into hostname and port\r\n // Handle IPv6 addresses like [::1]:8080\r\n if (host.startsWith('[')) {\r\n const bracketEnd = host.indexOf(']');\r\n if (bracketEnd !== -1) {\r\n hostname = host.slice(0, bracketEnd + 1);\r\n if (host.length > bracketEnd + 1 && host[bracketEnd + 1] === ':') {\r\n port = host.slice(bracketEnd + 2);\r\n }\r\n } else {\r\n hostname = host;\r\n }\r\n } else {\r\n const colonIndex = host.lastIndexOf(':');\r\n if (colonIndex !== -1) {\r\n hostname = host.slice(0, colonIndex);\r\n port = host.slice(colonIndex + 1);\r\n } else {\r\n hostname = host;\r\n port = '';\r\n }\r\n }\r\n } else {\r\n // Relative URL\r\n pathname = rest;\r\n }\r\n\r\n const searchParams = parseQueryString(search);\r\n const origin = protocol ? protocol + host : '';\r\n\r\n return {\r\n protocol,\r\n host,\r\n hostname,\r\n port,\r\n pathname,\r\n search,\r\n hash,\r\n searchParams,\r\n origin,\r\n href: url,\r\n };\r\n}\r\n\r\n/**\r\n * 构建完整 URL\r\n *\r\n * @param base - 基础 URL\r\n * @param params - 查询参数(可选)\r\n * @param hash - hash 片段(可选,不含 #)\r\n * @returns 构建后的完整 URL\r\n */\r\nexport function buildURL(\r\n base: string,\r\n params?: Record<string, string | number | boolean>,\r\n hash?: string,\r\n): string {\r\n if (!params && !hash) return base;\r\n\r\n // Extract and remove existing hash from base\r\n let baseWithoutHash = base;\r\n let existingHash = '';\r\n const hashIdx = base.indexOf('#');\r\n if (hashIdx !== -1) {\r\n existingHash = base.slice(hashIdx);\r\n baseWithoutHash = base.slice(0, hashIdx);\r\n }\r\n\r\n // Parse existing query params from base (without hash)\r\n let existingSearch = '';\r\n let baseWithoutSearch = baseWithoutHash;\r\n const searchIdx = baseWithoutHash.indexOf('?');\r\n if (searchIdx !== -1) {\r\n existingSearch = baseWithoutHash.slice(searchIdx + 1);\r\n baseWithoutSearch = baseWithoutHash.slice(0, searchIdx);\r\n }\r\n\r\n // Merge existing and new params\r\n const mergedParams = parseQueryString(existingSearch);\r\n if (params) {\r\n for (const key of Object.keys(params)) {\r\n mergedParams[key] = String(params[key]);\r\n }\r\n }\r\n\r\n const queryString = stringifyQueryString(mergedParams);\r\n let result = baseWithoutSearch;\r\n if (queryString) {\r\n result += '?' + queryString;\r\n }\r\n // Use provided hash, or keep existing hash if no new hash is given\r\n const finalHash = hash !== undefined ? '#' + encodeURIComponent(hash) : existingHash;\r\n if (finalHash) {\r\n result += finalHash;\r\n }\r\n\r\n return result;\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["result","pairs"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @lytjs/common-query\n * URL 查询字符串解析与构建工具\n */\n\n/**\n * 解析后的 URL 结构\n */\nexport interface ParsedURL {\n protocol: string;\n host: string;\n hostname: string;\n port: string;\n pathname: string;\n search: string;\n hash: string;\n searchParams: Record<string, string | string[]>;\n origin: string;\n href: string;\n}\n\n/**\n * 解析 URL 查询字符串为对象\n *\n * @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式\n * @param options - 解析选项\n * @returns 解析后的键值对对象\n */\nexport function parseQueryString(\n search: string,\n options?: { supportArrays?: boolean },\n): Record<string, string> | Record<string, string | string[]> {\n if (!search) return {};\n\n const str = search.startsWith('?') ? search.slice(1) : search;\n if (!str) return {};\n\n const supportArrays = options?.supportArrays ?? false;\n\n if (supportArrays) {\n const result: Record<string, string | string[]> = {};\n const pairs = str.split('&');\n for (const pair of pairs) {\n if (!pair) continue;\n const eqIndex = pair.indexOf('=');\n let key: string;\n let value: string;\n if (eqIndex === -1) {\n key = pair;\n value = '';\n } else {\n key = pair.slice(0, eqIndex);\n value = pair.slice(eqIndex + 1);\n }\n try {\n key = decodeURIComponent(key);\n } catch {\n // keep key as-is if decode fails\n }\n try {\n value = decodeURIComponent(value);\n } catch {\n // keep value as-is if decode fails\n }\n\n if (result[key]) {\n if (Array.isArray(result[key])) {\n (result[key] as string[]).push(value);\n } else {\n result[key] = [result[key] as string, value];\n }\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n // 保持原有的行为(向后兼容)\n const result: Record<string, string> = {};\n const pairs = str.split('&');\n for (const pair of pairs) {\n if (!pair) continue;\n const eqIndex = pair.indexOf('=');\n let key: string;\n let value: string;\n if (eqIndex === -1) {\n key = pair;\n value = '';\n } else {\n key = pair.slice(0, eqIndex);\n value = pair.slice(eqIndex + 1);\n }\n try {\n key = decodeURIComponent(key);\n } catch {\n // keep key as-is if decode fails\n }\n try {\n value = decodeURIComponent(value);\n } catch {\n // keep value as-is if decode fails\n }\n result[key] = value;\n }\n return result;\n}\n\n/**\n * 解析 URL 查询字符串为对象,支持数组值(重复键)\n * 这是 parseQueryString 支持数组的便捷版本\n *\n * @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式\n * @returns 解析后的键值对对象\n */\nexport function parseQueryStringWithArrays(search: string): Record<string, string | string[]> {\n return parseQueryString(search, { supportArrays: true }) as Record<string, string | string[]>;\n}\n\n/**\n * 将对象序列化为查询字符串\n *\n * @param params - 键值对对象,支持数组值\n * @returns 序列化后的查询字符串(不含前导 ?)\n */\nexport function stringifyQueryString(\n params: Record<string, string | number | boolean | Array<string | number | boolean>>,\n): string {\n const keys = Object.keys(params);\n if (keys.length === 0) return '';\n\n const parts: string[] = [];\n for (const key of keys) {\n const value = params[key];\n if (Array.isArray(value)) {\n for (const item of value) {\n parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(item)));\n }\n } else {\n parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(value)));\n }\n }\n\n return parts.join('&');\n}\n\n/**\n * 解析完整 URL\n *\n * @param url - URL 字符串,支持绝对 URL 和相对 URL\n * @returns 解析后的 ParsedURL 对象\n */\nexport function parseURL(url: string, _options?: { supportArrays?: boolean }): ParsedURL {\n let rest = url;\n\n // Extract hash\n let hash = '';\n const hashIndex = rest.indexOf('#');\n if (hashIndex !== -1) {\n hash = rest.slice(hashIndex);\n rest = rest.slice(0, hashIndex);\n }\n\n // Extract search\n let search = '';\n const searchIndex = rest.indexOf('?');\n if (searchIndex !== -1) {\n search = rest.slice(searchIndex);\n rest = rest.slice(0, searchIndex);\n }\n\n // Extract protocol\n let protocol = '';\n const protocolIndex = rest.indexOf('://');\n if (protocolIndex !== -1) {\n protocol = rest.slice(0, protocolIndex + 3);\n rest = rest.slice(protocolIndex + 3);\n }\n\n // Extract pathname (everything after host)\n let pathname = '';\n let host = '';\n let hostname = '';\n let port = '';\n\n if (protocol) {\n // Absolute URL: extract host and pathname\n const slashIndex = rest.indexOf('/');\n if (slashIndex !== -1) {\n host = rest.slice(0, slashIndex);\n pathname = rest.slice(slashIndex);\n } else {\n host = rest;\n pathname = '';\n }\n\n // Parse host into hostname and port\n // Handle IPv6 addresses like [::1]:8080\n if (host.startsWith('[')) {\n const bracketEnd = host.indexOf(']');\n if (bracketEnd !== -1) {\n hostname = host.slice(0, bracketEnd + 1);\n if (host.length > bracketEnd + 1 && host[bracketEnd + 1] === ':') {\n port = host.slice(bracketEnd + 2);\n }\n } else {\n hostname = host;\n }\n } else {\n const colonIndex = host.lastIndexOf(':');\n if (colonIndex !== -1) {\n hostname = host.slice(0, colonIndex);\n port = host.slice(colonIndex + 1);\n } else {\n hostname = host;\n port = '';\n }\n }\n } else {\n // Relative URL\n pathname = rest;\n }\n\n const searchParams = parseQueryString(search, { supportArrays: true }) as Record<\n string,\n string | string[]\n >;\n const origin = protocol ? protocol + host : '';\n\n return {\n protocol,\n host,\n hostname,\n port,\n pathname,\n search,\n hash,\n searchParams,\n origin,\n href: url,\n };\n}\n\n/**\n * 构建完整 URL\n *\n * @param base - 基础 URL\n * @param params - 查询参数(可选)\n * @param hash - hash 片段(可选,不含 #)\n * @returns 构建后的完整 URL\n */\nexport function buildURL(\n base: string,\n params?: Record<string, string | number | boolean | Array<string | number | boolean>>,\n hash?: string,\n): string {\n if (!params && !hash) return base;\n\n // Extract and remove existing hash from base\n let baseWithoutHash = base;\n let existingHash = '';\n const hashIdx = base.indexOf('#');\n if (hashIdx !== -1) {\n existingHash = base.slice(hashIdx);\n baseWithoutHash = base.slice(0, hashIdx);\n }\n\n // Parse existing query params from base (without hash)\n let existingSearch = '';\n let baseWithoutSearch = baseWithoutHash;\n const searchIdx = baseWithoutHash.indexOf('?');\n if (searchIdx !== -1) {\n existingSearch = baseWithoutHash.slice(searchIdx + 1);\n baseWithoutSearch = baseWithoutHash.slice(0, searchIdx);\n }\n\n // Merge existing and new params\n const mergedParams = parseQueryStringWithArrays(existingSearch);\n if (params) {\n for (const key of Object.keys(params)) {\n const value = params[key];\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n mergedParams[key] = value.map((v) => String(v));\n } else {\n mergedParams[key] = String(value);\n }\n }\n }\n }\n\n const queryString = stringifyQueryString(mergedParams);\n let result = baseWithoutSearch;\n if (queryString) {\n result += '?' + queryString;\n }\n // Use provided hash, or keep existing hash if no new hash is given\n const finalHash = hash !== undefined ? '#' + encodeURIComponent(hash) : existingHash;\n if (finalHash) {\n result += finalHash;\n }\n\n return result;\n}\n"]}
|
package/package.json
CHANGED
package/src/env.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 全局 __DEV__ 声明
|
|
2
|
-
// 规范版本位于 @lytjs/shared-types/src/global.d.ts
|
|
3
|
-
// 此处保留直接声明以确保 tsup DTS 构建时类型可用
|
|
4
|
-
declare const __DEV__: boolean;
|
|
1
|
+
// 全局 __DEV__ 声明
|
|
2
|
+
// 规范版本位于 @lytjs/shared-types/src/global.d.ts
|
|
3
|
+
// 此处保留直接声明以确保 tsup DTS 构建时类型可用
|
|
4
|
+
declare const __DEV__: boolean;
|
package/src/index.ts
CHANGED
|
@@ -1,235 +1,304 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @lytjs/common-query
|
|
3
|
-
* URL 查询字符串解析与构建工具
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 解析后的 URL 结构
|
|
8
|
-
*/
|
|
9
|
-
export interface ParsedURL {
|
|
10
|
-
protocol: string;
|
|
11
|
-
host: string;
|
|
12
|
-
hostname: string;
|
|
13
|
-
port: string;
|
|
14
|
-
pathname: string;
|
|
15
|
-
search: string;
|
|
16
|
-
hash: string;
|
|
17
|
-
searchParams: Record<string, string>;
|
|
18
|
-
origin: string;
|
|
19
|
-
href: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 解析 URL 查询字符串为对象
|
|
24
|
-
*
|
|
25
|
-
* @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式
|
|
26
|
-
* @
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
key
|
|
47
|
-
value
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @lytjs/common-query
|
|
3
|
+
* URL 查询字符串解析与构建工具
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 解析后的 URL 结构
|
|
8
|
+
*/
|
|
9
|
+
export interface ParsedURL {
|
|
10
|
+
protocol: string;
|
|
11
|
+
host: string;
|
|
12
|
+
hostname: string;
|
|
13
|
+
port: string;
|
|
14
|
+
pathname: string;
|
|
15
|
+
search: string;
|
|
16
|
+
hash: string;
|
|
17
|
+
searchParams: Record<string, string | string[]>;
|
|
18
|
+
origin: string;
|
|
19
|
+
href: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 解析 URL 查询字符串为对象
|
|
24
|
+
*
|
|
25
|
+
* @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式
|
|
26
|
+
* @param options - 解析选项
|
|
27
|
+
* @returns 解析后的键值对对象
|
|
28
|
+
*/
|
|
29
|
+
export function parseQueryString(
|
|
30
|
+
search: string,
|
|
31
|
+
options?: { supportArrays?: boolean },
|
|
32
|
+
): Record<string, string> | Record<string, string | string[]> {
|
|
33
|
+
if (!search) return {};
|
|
34
|
+
|
|
35
|
+
const str = search.startsWith('?') ? search.slice(1) : search;
|
|
36
|
+
if (!str) return {};
|
|
37
|
+
|
|
38
|
+
const supportArrays = options?.supportArrays ?? false;
|
|
39
|
+
|
|
40
|
+
if (supportArrays) {
|
|
41
|
+
const result: Record<string, string | string[]> = {};
|
|
42
|
+
const pairs = str.split('&');
|
|
43
|
+
for (const pair of pairs) {
|
|
44
|
+
if (!pair) continue;
|
|
45
|
+
const eqIndex = pair.indexOf('=');
|
|
46
|
+
let key: string;
|
|
47
|
+
let value: string;
|
|
48
|
+
if (eqIndex === -1) {
|
|
49
|
+
key = pair;
|
|
50
|
+
value = '';
|
|
51
|
+
} else {
|
|
52
|
+
key = pair.slice(0, eqIndex);
|
|
53
|
+
value = pair.slice(eqIndex + 1);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
key = decodeURIComponent(key);
|
|
57
|
+
} catch {
|
|
58
|
+
// keep key as-is if decode fails
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
value = decodeURIComponent(value);
|
|
62
|
+
} catch {
|
|
63
|
+
// keep value as-is if decode fails
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (result[key]) {
|
|
67
|
+
if (Array.isArray(result[key])) {
|
|
68
|
+
(result[key] as string[]).push(value);
|
|
69
|
+
} else {
|
|
70
|
+
result[key] = [result[key] as string, value];
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
result[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 保持原有的行为(向后兼容)
|
|
80
|
+
const result: Record<string, string> = {};
|
|
81
|
+
const pairs = str.split('&');
|
|
82
|
+
for (const pair of pairs) {
|
|
83
|
+
if (!pair) continue;
|
|
84
|
+
const eqIndex = pair.indexOf('=');
|
|
85
|
+
let key: string;
|
|
86
|
+
let value: string;
|
|
87
|
+
if (eqIndex === -1) {
|
|
88
|
+
key = pair;
|
|
89
|
+
value = '';
|
|
90
|
+
} else {
|
|
91
|
+
key = pair.slice(0, eqIndex);
|
|
92
|
+
value = pair.slice(eqIndex + 1);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
key = decodeURIComponent(key);
|
|
96
|
+
} catch {
|
|
97
|
+
// keep key as-is if decode fails
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
value = decodeURIComponent(value);
|
|
101
|
+
} catch {
|
|
102
|
+
// keep value as-is if decode fails
|
|
103
|
+
}
|
|
104
|
+
result[key] = value;
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 解析 URL 查询字符串为对象,支持数组值(重复键)
|
|
111
|
+
* 这是 parseQueryString 支持数组的便捷版本
|
|
112
|
+
*
|
|
113
|
+
* @param search - 查询字符串,支持 `?key=value` 和 `key=value` 格式
|
|
114
|
+
* @returns 解析后的键值对对象
|
|
115
|
+
*/
|
|
116
|
+
export function parseQueryStringWithArrays(search: string): Record<string, string | string[]> {
|
|
117
|
+
return parseQueryString(search, { supportArrays: true }) as Record<string, string | string[]>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 将对象序列化为查询字符串
|
|
122
|
+
*
|
|
123
|
+
* @param params - 键值对对象,支持数组值
|
|
124
|
+
* @returns 序列化后的查询字符串(不含前导 ?)
|
|
125
|
+
*/
|
|
126
|
+
export function stringifyQueryString(
|
|
127
|
+
params: Record<string, string | number | boolean | Array<string | number | boolean>>,
|
|
128
|
+
): string {
|
|
129
|
+
const keys = Object.keys(params);
|
|
130
|
+
if (keys.length === 0) return '';
|
|
131
|
+
|
|
132
|
+
const parts: string[] = [];
|
|
133
|
+
for (const key of keys) {
|
|
134
|
+
const value = params[key];
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
for (const item of value) {
|
|
137
|
+
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(item)));
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(value)));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return parts.join('&');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 解析完整 URL
|
|
149
|
+
*
|
|
150
|
+
* @param url - URL 字符串,支持绝对 URL 和相对 URL
|
|
151
|
+
* @returns 解析后的 ParsedURL 对象
|
|
152
|
+
*/
|
|
153
|
+
export function parseURL(url: string, _options?: { supportArrays?: boolean }): ParsedURL {
|
|
154
|
+
let rest = url;
|
|
155
|
+
|
|
156
|
+
// Extract hash
|
|
157
|
+
let hash = '';
|
|
158
|
+
const hashIndex = rest.indexOf('#');
|
|
159
|
+
if (hashIndex !== -1) {
|
|
160
|
+
hash = rest.slice(hashIndex);
|
|
161
|
+
rest = rest.slice(0, hashIndex);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Extract search
|
|
165
|
+
let search = '';
|
|
166
|
+
const searchIndex = rest.indexOf('?');
|
|
167
|
+
if (searchIndex !== -1) {
|
|
168
|
+
search = rest.slice(searchIndex);
|
|
169
|
+
rest = rest.slice(0, searchIndex);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Extract protocol
|
|
173
|
+
let protocol = '';
|
|
174
|
+
const protocolIndex = rest.indexOf('://');
|
|
175
|
+
if (protocolIndex !== -1) {
|
|
176
|
+
protocol = rest.slice(0, protocolIndex + 3);
|
|
177
|
+
rest = rest.slice(protocolIndex + 3);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Extract pathname (everything after host)
|
|
181
|
+
let pathname = '';
|
|
182
|
+
let host = '';
|
|
183
|
+
let hostname = '';
|
|
184
|
+
let port = '';
|
|
185
|
+
|
|
186
|
+
if (protocol) {
|
|
187
|
+
// Absolute URL: extract host and pathname
|
|
188
|
+
const slashIndex = rest.indexOf('/');
|
|
189
|
+
if (slashIndex !== -1) {
|
|
190
|
+
host = rest.slice(0, slashIndex);
|
|
191
|
+
pathname = rest.slice(slashIndex);
|
|
192
|
+
} else {
|
|
193
|
+
host = rest;
|
|
194
|
+
pathname = '';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Parse host into hostname and port
|
|
198
|
+
// Handle IPv6 addresses like [::1]:8080
|
|
199
|
+
if (host.startsWith('[')) {
|
|
200
|
+
const bracketEnd = host.indexOf(']');
|
|
201
|
+
if (bracketEnd !== -1) {
|
|
202
|
+
hostname = host.slice(0, bracketEnd + 1);
|
|
203
|
+
if (host.length > bracketEnd + 1 && host[bracketEnd + 1] === ':') {
|
|
204
|
+
port = host.slice(bracketEnd + 2);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
hostname = host;
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
const colonIndex = host.lastIndexOf(':');
|
|
211
|
+
if (colonIndex !== -1) {
|
|
212
|
+
hostname = host.slice(0, colonIndex);
|
|
213
|
+
port = host.slice(colonIndex + 1);
|
|
214
|
+
} else {
|
|
215
|
+
hostname = host;
|
|
216
|
+
port = '';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Relative URL
|
|
221
|
+
pathname = rest;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const searchParams = parseQueryString(search, { supportArrays: true }) as Record<
|
|
225
|
+
string,
|
|
226
|
+
string | string[]
|
|
227
|
+
>;
|
|
228
|
+
const origin = protocol ? protocol + host : '';
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
protocol,
|
|
232
|
+
host,
|
|
233
|
+
hostname,
|
|
234
|
+
port,
|
|
235
|
+
pathname,
|
|
236
|
+
search,
|
|
237
|
+
hash,
|
|
238
|
+
searchParams,
|
|
239
|
+
origin,
|
|
240
|
+
href: url,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 构建完整 URL
|
|
246
|
+
*
|
|
247
|
+
* @param base - 基础 URL
|
|
248
|
+
* @param params - 查询参数(可选)
|
|
249
|
+
* @param hash - hash 片段(可选,不含 #)
|
|
250
|
+
* @returns 构建后的完整 URL
|
|
251
|
+
*/
|
|
252
|
+
export function buildURL(
|
|
253
|
+
base: string,
|
|
254
|
+
params?: Record<string, string | number | boolean | Array<string | number | boolean>>,
|
|
255
|
+
hash?: string,
|
|
256
|
+
): string {
|
|
257
|
+
if (!params && !hash) return base;
|
|
258
|
+
|
|
259
|
+
// Extract and remove existing hash from base
|
|
260
|
+
let baseWithoutHash = base;
|
|
261
|
+
let existingHash = '';
|
|
262
|
+
const hashIdx = base.indexOf('#');
|
|
263
|
+
if (hashIdx !== -1) {
|
|
264
|
+
existingHash = base.slice(hashIdx);
|
|
265
|
+
baseWithoutHash = base.slice(0, hashIdx);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Parse existing query params from base (without hash)
|
|
269
|
+
let existingSearch = '';
|
|
270
|
+
let baseWithoutSearch = baseWithoutHash;
|
|
271
|
+
const searchIdx = baseWithoutHash.indexOf('?');
|
|
272
|
+
if (searchIdx !== -1) {
|
|
273
|
+
existingSearch = baseWithoutHash.slice(searchIdx + 1);
|
|
274
|
+
baseWithoutSearch = baseWithoutHash.slice(0, searchIdx);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Merge existing and new params
|
|
278
|
+
const mergedParams = parseQueryStringWithArrays(existingSearch);
|
|
279
|
+
if (params) {
|
|
280
|
+
for (const key of Object.keys(params)) {
|
|
281
|
+
const value = params[key];
|
|
282
|
+
if (value !== undefined && value !== null) {
|
|
283
|
+
if (Array.isArray(value)) {
|
|
284
|
+
mergedParams[key] = value.map((v) => String(v));
|
|
285
|
+
} else {
|
|
286
|
+
mergedParams[key] = String(value);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const queryString = stringifyQueryString(mergedParams);
|
|
293
|
+
let result = baseWithoutSearch;
|
|
294
|
+
if (queryString) {
|
|
295
|
+
result += '?' + queryString;
|
|
296
|
+
}
|
|
297
|
+
// Use provided hash, or keep existing hash if no new hash is given
|
|
298
|
+
const finalHash = hash !== undefined ? '#' + encodeURIComponent(hash) : existingHash;
|
|
299
|
+
if (finalHash) {
|
|
300
|
+
result += finalHash;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return result;
|
|
304
|
+
}
|