@peanut-admin/admin 0.1.0-alpha.3 → 0.1.0-alpha.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/client-core/src/index.ts +113 -23
- package/client-nuxt/src/index.ts +11 -3
- package/client-uniapp/src/index.ts +2 -2
- package/package.json +16 -6
package/client-core/src/index.ts
CHANGED
|
@@ -7,11 +7,27 @@ export type ClientRequestMethod =
|
|
|
7
7
|
| 'POST'
|
|
8
8
|
| 'PUT'
|
|
9
9
|
|
|
10
|
+
export interface ClientHeaderSource {
|
|
11
|
+
forEach: (callback: (value: string, key: string) => void) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ClientRequestHeaders =
|
|
15
|
+
| Readonly<Record<string, string>>
|
|
16
|
+
| ReadonlyArray<readonly [string, string]>
|
|
17
|
+
| ClientHeaderSource
|
|
18
|
+
|
|
19
|
+
export interface ClientHeaders extends ClientHeaderSource {
|
|
20
|
+
delete: (name: string) => void
|
|
21
|
+
get: (name: string) => string | null
|
|
22
|
+
has: (name: string) => boolean
|
|
23
|
+
set: (name: string, value: string) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
export interface ClientRequest<TData = unknown> {
|
|
11
27
|
readonly path: string
|
|
12
28
|
readonly method?: ClientRequestMethod
|
|
13
29
|
readonly data?: TData
|
|
14
|
-
readonly headers?:
|
|
30
|
+
readonly headers?: ClientRequestHeaders
|
|
15
31
|
readonly auth?: boolean
|
|
16
32
|
}
|
|
17
33
|
|
|
@@ -19,7 +35,7 @@ export interface ClientTransportRequest<TData = unknown> {
|
|
|
19
35
|
readonly path: string
|
|
20
36
|
readonly method: ClientRequestMethod
|
|
21
37
|
readonly data?: TData
|
|
22
|
-
readonly headers:
|
|
38
|
+
readonly headers: ClientHeaders
|
|
23
39
|
}
|
|
24
40
|
|
|
25
41
|
export type ClientTransport = (request: ClientTransportRequest) => Promise<unknown>
|
|
@@ -96,6 +112,9 @@ const pathControlCharacters = /[\u0000-\u001f\u007f]/
|
|
|
96
112
|
const controlCharacters = /[\u0000-\u001f\u007f]/g
|
|
97
113
|
const encodedUnsafePathSegment = /%(?:2e|2f|5c)/i
|
|
98
114
|
const absolutePath = /^[A-Za-z][A-Za-z0-9+.-]*:/
|
|
115
|
+
const httpBaseUrl = /^(https?):\/\/([^/?#]+)(\/[^?#]*)?$/i
|
|
116
|
+
const validHeaderName = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/
|
|
117
|
+
const invalidHeaderValue = /[\u0000\r\n]/
|
|
99
118
|
const safeCode = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/
|
|
100
119
|
const defaultUnauthorizedCode = 'CLIENT_UNAUTHORIZED'
|
|
101
120
|
const defaultBusinessCode = 'CLIENT_BUSINESS_ERROR'
|
|
@@ -130,34 +149,49 @@ const assertClientPath = (path: string): void => {
|
|
|
130
149
|
}
|
|
131
150
|
}
|
|
132
151
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
const invalidBaseUrl = (): ClientRequestError => (
|
|
153
|
+
new ClientRequestError('path', 'CLIENT_BASE_URL_INVALID', 'The client base URL is invalid.')
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const validBaseUrl = (baseUrl: string): { origin: string; pathname: string } => {
|
|
157
|
+
if (
|
|
158
|
+
typeof baseUrl !== 'string'
|
|
159
|
+
|| baseUrl.trim() !== baseUrl
|
|
160
|
+
|| pathControlCharacters.test(baseUrl)
|
|
161
|
+
|| baseUrl.includes('\\')
|
|
162
|
+
) {
|
|
163
|
+
throw invalidBaseUrl()
|
|
139
164
|
}
|
|
140
165
|
|
|
166
|
+
const match = httpBaseUrl.exec(baseUrl)
|
|
167
|
+
const protocol = match?.[1]
|
|
168
|
+
const authority = match?.[2]
|
|
169
|
+
const pathname = match?.[3] ?? '/'
|
|
141
170
|
if (
|
|
142
|
-
|
|
143
|
-
||
|
|
144
|
-
||
|
|
145
|
-
||
|
|
146
|
-
||
|
|
171
|
+
protocol === undefined
|
|
172
|
+
|| authority === undefined
|
|
173
|
+
|| authority === ''
|
|
174
|
+
|| authority.includes('@')
|
|
175
|
+
|| /\s/.test(authority)
|
|
176
|
+
|| encodedUnsafePathSegment.test(pathname)
|
|
177
|
+
|| pathname.split('/').some(segment => segment === '.' || segment === '..')
|
|
147
178
|
) {
|
|
148
|
-
throw
|
|
179
|
+
throw invalidBaseUrl()
|
|
149
180
|
}
|
|
150
181
|
|
|
151
|
-
return
|
|
182
|
+
return {
|
|
183
|
+
origin: `${protocol.toLowerCase()}://${authority}`,
|
|
184
|
+
pathname,
|
|
185
|
+
}
|
|
152
186
|
}
|
|
153
187
|
|
|
154
188
|
export const resolveClientUrl = (baseUrl: string, path: string): string => {
|
|
155
189
|
assertClientPath(path)
|
|
156
190
|
const base = validBaseUrl(baseUrl)
|
|
157
191
|
const basePath = base.pathname.endsWith('/') ? base.pathname : `${base.pathname}/`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
192
|
+
return path.startsWith('/')
|
|
193
|
+
? `${base.origin}${path}`
|
|
194
|
+
: `${base.origin}${basePath}${path}`
|
|
161
195
|
}
|
|
162
196
|
|
|
163
197
|
const safeMessage = (value: unknown, fallback: string): string => {
|
|
@@ -203,9 +237,66 @@ const normalizedDecodedResult = (value: ClientDecodeResult): ClientDecodeResult
|
|
|
203
237
|
}
|
|
204
238
|
}
|
|
205
239
|
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
240
|
+
const isHeaderSource = (value: unknown): value is ClientHeaderSource => (
|
|
241
|
+
typeof value === 'object'
|
|
242
|
+
&& value !== null
|
|
243
|
+
&& typeof (value as { forEach?: unknown }).forEach === 'function'
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
class PortableClientHeaders implements ClientHeaders {
|
|
247
|
+
private readonly values = new Map<string, string>()
|
|
248
|
+
|
|
249
|
+
constructor(headers?: ClientRequestHeaders) {
|
|
250
|
+
if (headers === undefined) return
|
|
251
|
+
|
|
252
|
+
if (Array.isArray(headers)) {
|
|
253
|
+
for (const entry of headers) {
|
|
254
|
+
if (!Array.isArray(entry) || entry.length !== 2) throw new TypeError('invalid header entry')
|
|
255
|
+
this.set(entry[0], entry[1])
|
|
256
|
+
}
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (isHeaderSource(headers)) {
|
|
261
|
+
headers.forEach((value, key) => this.set(key, value))
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (typeof headers === 'object' && headers !== null) {
|
|
266
|
+
for (const [key, value] of Object.entries(headers)) this.set(key, value)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
throw new TypeError('invalid headers')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
delete(name: string): void {
|
|
274
|
+
this.values.delete(name.toLowerCase())
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
get(name: string): string | null {
|
|
278
|
+
return this.values.get(name.toLowerCase()) ?? null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
has(name: string): boolean {
|
|
282
|
+
return this.values.has(name.toLowerCase())
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
set(name: string, value: string): void {
|
|
286
|
+
if (!validHeaderName.test(name) || typeof value !== 'string' || invalidHeaderValue.test(value)) {
|
|
287
|
+
throw new TypeError('invalid header')
|
|
288
|
+
}
|
|
289
|
+
this.values.set(name.toLowerCase(), value.trim())
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
forEach(callback: (value: string, key: string) => void): void {
|
|
293
|
+
this.values.forEach((value, key) => callback(value, key))
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const requestHeaders = (headers: ClientRequestHeaders | undefined): ClientHeaders => {
|
|
298
|
+
const result = new PortableClientHeaders(headers)
|
|
299
|
+
// Header names are normalized, so delete removes every caller spelling.
|
|
209
300
|
result.delete('Authorization')
|
|
210
301
|
return result
|
|
211
302
|
}
|
|
@@ -251,7 +342,7 @@ export const createClient = (options: ClientOptions): Client => {
|
|
|
251
342
|
}
|
|
252
343
|
|
|
253
344
|
const method = methodOf(input.method)
|
|
254
|
-
let headers:
|
|
345
|
+
let headers: ClientHeaders
|
|
255
346
|
try {
|
|
256
347
|
headers = requestHeaders(input.headers)
|
|
257
348
|
} catch {
|
|
@@ -322,4 +413,3 @@ export const createClient = (options: ClientOptions): Client => {
|
|
|
322
413
|
export type ClientResult<TData = unknown> = ClientDecodeResult<TData>
|
|
323
414
|
export type ClientRequestResult<TData = unknown> = Promise<TData>
|
|
324
415
|
export type ClientHook = (error: ClientRequestError) => void | Promise<void>
|
|
325
|
-
export type ClientRequestHeaders = HeadersInit
|
package/client-nuxt/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { resolveClientUrl } from '@peanut-admin/admin/client'
|
|
2
|
-
import type { ClientTransport, ClientTransportRequest } from '@peanut-admin/admin/client'
|
|
2
|
+
import type { ClientHeaders, ClientTransport, ClientTransportRequest } from '@peanut-admin/admin/client'
|
|
3
3
|
|
|
4
4
|
export interface NuxtClientFetchOptions {
|
|
5
5
|
readonly method?: string
|
|
6
6
|
readonly query?: unknown
|
|
7
7
|
readonly body?: unknown
|
|
8
|
-
readonly headers?:
|
|
8
|
+
readonly headers?: Record<string, string>
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export type NuxtClientFetch = (
|
|
@@ -20,6 +20,14 @@ export interface NuxtClientTransportOptions {
|
|
|
20
20
|
|
|
21
21
|
const isQueryMethod = (method: string): boolean => method === 'GET' || method === 'DELETE'
|
|
22
22
|
|
|
23
|
+
const headersRecord = (headers: ClientHeaders): Record<string, string> => {
|
|
24
|
+
const result: Record<string, string> = {}
|
|
25
|
+
headers.forEach((value, key) => {
|
|
26
|
+
result[key] = value
|
|
27
|
+
})
|
|
28
|
+
return result
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
export const createNuxtClientTransport = (
|
|
24
32
|
options: NuxtClientTransportOptions,
|
|
25
33
|
): ClientTransport => {
|
|
@@ -28,7 +36,7 @@ export const createNuxtClientTransport = (
|
|
|
28
36
|
const url = resolveClientUrl(options.baseUrl, request.path)
|
|
29
37
|
const fetchOptions: NuxtClientFetchOptions = {
|
|
30
38
|
method,
|
|
31
|
-
headers: request.headers,
|
|
39
|
+
headers: headersRecord(request.headers),
|
|
32
40
|
...(request.data !== undefined
|
|
33
41
|
? isQueryMethod(method)
|
|
34
42
|
? { query: request.data }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveClientUrl } from '@peanut-admin/admin/client'
|
|
2
|
-
import type { ClientTransport, ClientTransportRequest } from '@peanut-admin/admin/client'
|
|
2
|
+
import type { ClientHeaders, ClientTransport, ClientTransportRequest } from '@peanut-admin/admin/client'
|
|
3
3
|
|
|
4
4
|
export interface UniAppClientResponse {
|
|
5
5
|
readonly data: unknown
|
|
@@ -23,7 +23,7 @@ export interface UniAppClientTransportOptions {
|
|
|
23
23
|
readonly request: UniAppClientRequest
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const headersRecord = (headers:
|
|
26
|
+
const headersRecord = (headers: ClientHeaders): Record<string, string> => {
|
|
27
27
|
const result: Record<string, string> = {}
|
|
28
28
|
headers.forEach((value, key) => {
|
|
29
29
|
result[key] = value
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peanut-admin/admin",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "Reusable Peanut Admin Web services and module contributions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -100,22 +100,32 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
|
-
"
|
|
104
|
-
"openapi-fetch": "0.17.0",
|
|
105
|
-
"pinia": "4.0.2",
|
|
106
|
-
"vue-router": "5.2.0"
|
|
103
|
+
"openapi-fetch": "0.17.0"
|
|
107
104
|
},
|
|
108
105
|
"devDependencies": {
|
|
109
106
|
"@vue/test-utils": "2.4.11",
|
|
107
|
+
"element-plus": "2.14.3",
|
|
110
108
|
"happy-dom": "20.10.6",
|
|
109
|
+
"pinia": "4.0.2",
|
|
111
110
|
"typescript": "5.9.3",
|
|
112
111
|
"vite": "8.1.4",
|
|
113
112
|
"vitest": "4.1.10",
|
|
114
113
|
"vue": "3.5.39",
|
|
114
|
+
"vue-router": "5.2.0",
|
|
115
115
|
"vue-tsc": "3.3.7"
|
|
116
116
|
},
|
|
117
117
|
"peerDependencies": {
|
|
118
|
-
"
|
|
118
|
+
"element-plus": "^2.14.3",
|
|
119
|
+
"pinia": ">=2.0.23 <5",
|
|
120
|
+
"vue": "^3.4.21"
|
|
121
|
+
},
|
|
122
|
+
"peerDependenciesMeta": {
|
|
123
|
+
"element-plus": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"pinia": {
|
|
127
|
+
"optional": true
|
|
128
|
+
}
|
|
119
129
|
},
|
|
120
130
|
"scripts": {
|
|
121
131
|
"test": "vitest run admin-core/tests admin-shell/tests file-media/tests import-export/tests integration-security/tests notification-sms/tests ops-console/tests reference-codes/tests settings/tests testing/tests client-core/tests client-nuxt/tests client-uniapp/tests",
|