@vafast/api-client 0.1.1 → 0.1.2
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 +202 -195
- package/TODO.md +83 -0
- package/example/auto-infer.ts +202 -0
- package/example/test-sse.ts +192 -0
- package/package.json +6 -7
- package/src/core/eden.ts +697 -0
- package/src/index.ts +34 -65
- package/src/types/index.ts +17 -116
- package/test/eden.test.ts +425 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +8 -0
- package/example/index.ts +0 -255
- package/src/core/api-client.ts +0 -389
- package/src/core/typed-client.ts +0 -305
- package/src/utils/index.ts +0 -232
- package/src/websocket/websocket-client.ts +0 -347
- package/test/api-client.test.ts +0 -262
- package/test/basic.test.ts +0 -55
- package/test/typed-client.test.ts +0 -304
- package/test/utils.test.ts +0 -363
- package/test/websocket.test.ts +0 -434
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import type { WebSocketClient, WebSocketEvent } from '../types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Vafast WebSocket 客户端
|
|
5
|
-
*/
|
|
6
|
-
export class VafastWebSocketClient implements WebSocketClient {
|
|
7
|
-
private ws: WebSocket | null = null
|
|
8
|
-
private url: string
|
|
9
|
-
private eventListeners = new Map<string, Set<(data: any) => void>>()
|
|
10
|
-
private reconnectAttempts = 0
|
|
11
|
-
private maxReconnectAttempts = 5
|
|
12
|
-
private reconnectDelay = 1000
|
|
13
|
-
private isReconnecting = false
|
|
14
|
-
private autoReconnect = true
|
|
15
|
-
|
|
16
|
-
constructor(url: string, options: {
|
|
17
|
-
autoReconnect?: boolean
|
|
18
|
-
maxReconnectAttempts?: number
|
|
19
|
-
reconnectDelay?: number
|
|
20
|
-
} = {}) {
|
|
21
|
-
this.url = url
|
|
22
|
-
this.autoReconnect = options.autoReconnect ?? true
|
|
23
|
-
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5
|
|
24
|
-
this.reconnectDelay = options.reconnectDelay ?? 1000
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 连接到 WebSocket 服务器
|
|
29
|
-
*/
|
|
30
|
-
async connect(): Promise<void> {
|
|
31
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return new Promise((resolve, reject) => {
|
|
36
|
-
try {
|
|
37
|
-
this.ws = new WebSocket(this.url)
|
|
38
|
-
|
|
39
|
-
this.ws.onopen = () => {
|
|
40
|
-
this.reconnectAttempts = 0
|
|
41
|
-
this.isReconnecting = false
|
|
42
|
-
resolve()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
this.ws.onclose = (event) => {
|
|
46
|
-
if (this.autoReconnect && !this.isReconnecting && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
47
|
-
this.scheduleReconnect()
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
this.ws.onerror = (error) => {
|
|
52
|
-
reject(error)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.ws.onmessage = (event) => {
|
|
56
|
-
this.handleMessage(event)
|
|
57
|
-
}
|
|
58
|
-
} catch (error) {
|
|
59
|
-
reject(error)
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 断开 WebSocket 连接
|
|
66
|
-
*/
|
|
67
|
-
disconnect(): void {
|
|
68
|
-
this.autoReconnect = false
|
|
69
|
-
if (this.ws) {
|
|
70
|
-
this.ws.close()
|
|
71
|
-
this.ws = null
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* 发送数据
|
|
77
|
-
*/
|
|
78
|
-
send(data: any): void {
|
|
79
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
80
|
-
throw new Error('WebSocket is not connected')
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (typeof data === 'string') {
|
|
84
|
-
this.ws.send(data)
|
|
85
|
-
} else {
|
|
86
|
-
this.ws.send(JSON.stringify(data))
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* 监听事件
|
|
92
|
-
*/
|
|
93
|
-
on(event: string, callback: (data: any) => void): void {
|
|
94
|
-
if (!this.eventListeners.has(event)) {
|
|
95
|
-
this.eventListeners.set(event, new Set())
|
|
96
|
-
}
|
|
97
|
-
this.eventListeners.get(event)!.add(callback)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 移除事件监听器
|
|
102
|
-
*/
|
|
103
|
-
off(event: string, callback: (data: any) => void): void {
|
|
104
|
-
const listeners = this.eventListeners.get(event)
|
|
105
|
-
if (listeners) {
|
|
106
|
-
listeners.delete(callback)
|
|
107
|
-
if (listeners.size === 0) {
|
|
108
|
-
this.eventListeners.delete(event)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 检查是否已连接
|
|
115
|
-
*/
|
|
116
|
-
isConnected(): boolean {
|
|
117
|
-
return this.ws !== null && this.ws.readyState === WebSocket.OPEN
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 获取连接状态
|
|
122
|
-
*/
|
|
123
|
-
getReadyState(): number {
|
|
124
|
-
return this.ws ? this.ws.readyState : WebSocket.CLOSED
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 获取连接状态文本
|
|
129
|
-
*/
|
|
130
|
-
getReadyStateText(): string {
|
|
131
|
-
const states: Record<number, string> = {
|
|
132
|
-
[WebSocket.CONNECTING]: 'CONNECTING',
|
|
133
|
-
[WebSocket.OPEN]: 'OPEN',
|
|
134
|
-
[WebSocket.CLOSING]: 'CLOSING',
|
|
135
|
-
[WebSocket.CLOSED]: 'CLOSED'
|
|
136
|
-
}
|
|
137
|
-
return states[this.getReadyState()] || 'UNKNOWN'
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* 设置自动重连
|
|
142
|
-
*/
|
|
143
|
-
setAutoReconnect(enabled: boolean): void {
|
|
144
|
-
this.autoReconnect = enabled
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 设置最大重连次数
|
|
149
|
-
*/
|
|
150
|
-
setMaxReconnectAttempts(attempts: number): void {
|
|
151
|
-
this.maxReconnectAttempts = attempts
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* 设置重连延迟
|
|
156
|
-
*/
|
|
157
|
-
setReconnectDelay(delay: number): void {
|
|
158
|
-
this.reconnectDelay = delay
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* 手动重连
|
|
163
|
-
*/
|
|
164
|
-
async reconnect(): Promise<void> {
|
|
165
|
-
if (this.isReconnecting) {
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
this.isReconnecting = true
|
|
170
|
-
this.disconnect()
|
|
171
|
-
|
|
172
|
-
// 等待一段时间后重连
|
|
173
|
-
await new Promise(resolve => setTimeout(resolve, this.reconnectDelay))
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await this.connect()
|
|
177
|
-
} catch (error) {
|
|
178
|
-
this.isReconnecting = false
|
|
179
|
-
throw error
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 获取事件监听器数量
|
|
185
|
-
*/
|
|
186
|
-
getEventListenerCount(event: string): number {
|
|
187
|
-
const listeners = this.eventListeners.get(event)
|
|
188
|
-
return listeners ? listeners.size : 0
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* 清除所有事件监听器
|
|
193
|
-
*/
|
|
194
|
-
clearEventListeners(event?: string): void {
|
|
195
|
-
if (event) {
|
|
196
|
-
this.eventListeners.delete(event)
|
|
197
|
-
} else {
|
|
198
|
-
this.eventListeners.clear()
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* 获取所有事件名称
|
|
204
|
-
*/
|
|
205
|
-
getEventNames(): string[] {
|
|
206
|
-
return Array.from(this.eventListeners.keys())
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* 处理接收到的消息
|
|
211
|
-
*/
|
|
212
|
-
private handleMessage(event: MessageEvent): void {
|
|
213
|
-
let data: any
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
data = JSON.parse(event.data)
|
|
217
|
-
} catch {
|
|
218
|
-
data = event.data
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const wsEvent: WebSocketEvent = {
|
|
222
|
-
type: 'message',
|
|
223
|
-
data,
|
|
224
|
-
timestamp: Date.now()
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// 触发消息事件监听器
|
|
228
|
-
this.triggerEvent('message', wsEvent)
|
|
229
|
-
|
|
230
|
-
// 如果有特定类型的事件监听器,也触发它们
|
|
231
|
-
if (data && typeof data === 'object' && data.type) {
|
|
232
|
-
this.triggerEvent(data.type, data)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* 触发事件
|
|
238
|
-
*/
|
|
239
|
-
private triggerEvent(event: string, data: any): void {
|
|
240
|
-
const listeners = this.eventListeners.get(event)
|
|
241
|
-
if (listeners) {
|
|
242
|
-
listeners.forEach(callback => {
|
|
243
|
-
try {
|
|
244
|
-
callback(data)
|
|
245
|
-
} catch (error) {
|
|
246
|
-
console.error(`Error in WebSocket event listener for ${event}:`, error)
|
|
247
|
-
}
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* 安排重连
|
|
254
|
-
*/
|
|
255
|
-
private scheduleReconnect(): void {
|
|
256
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
this.reconnectAttempts++
|
|
261
|
-
this.isReconnecting = true
|
|
262
|
-
|
|
263
|
-
setTimeout(async () => {
|
|
264
|
-
try {
|
|
265
|
-
await this.connect()
|
|
266
|
-
} catch (error) {
|
|
267
|
-
console.error('WebSocket reconnection failed:', error)
|
|
268
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
269
|
-
this.scheduleReconnect()
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}, this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1))
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* 创建 WebSocket 客户端
|
|
278
|
-
*/
|
|
279
|
-
export function createWebSocketClient(
|
|
280
|
-
url: string,
|
|
281
|
-
options?: {
|
|
282
|
-
autoReconnect?: boolean
|
|
283
|
-
maxReconnectAttempts?: number
|
|
284
|
-
reconnectDelay?: number
|
|
285
|
-
}
|
|
286
|
-
): VafastWebSocketClient {
|
|
287
|
-
return new VafastWebSocketClient(url, options)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* 创建类型安全的 WebSocket 客户端
|
|
292
|
-
*/
|
|
293
|
-
export function createTypedWebSocketClient<T = any>(
|
|
294
|
-
url: string,
|
|
295
|
-
options?: {
|
|
296
|
-
autoReconnect?: boolean
|
|
297
|
-
maxReconnectAttempts?: number
|
|
298
|
-
reconnectDelay?: number
|
|
299
|
-
}
|
|
300
|
-
): VafastWebSocketClient & {
|
|
301
|
-
on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void
|
|
302
|
-
send<K extends keyof T>(event: K, data: T[K]): void
|
|
303
|
-
} {
|
|
304
|
-
const client = new VafastWebSocketClient(url, options)
|
|
305
|
-
|
|
306
|
-
// Create a new object that extends the client
|
|
307
|
-
const typedClient = Object.create(Object.getPrototypeOf(client))
|
|
308
|
-
|
|
309
|
-
// Copy all properties and methods from the client
|
|
310
|
-
for (const key of Object.getOwnPropertyNames(client)) {
|
|
311
|
-
const descriptor = Object.getOwnPropertyDescriptor(client, key)
|
|
312
|
-
if (descriptor) {
|
|
313
|
-
Object.defineProperty(typedClient, key, descriptor)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Copy all symbol properties
|
|
318
|
-
for (const symbol of Object.getOwnPropertySymbols(client)) {
|
|
319
|
-
const descriptor = Object.getOwnPropertyDescriptor(client, symbol)
|
|
320
|
-
if (descriptor) {
|
|
321
|
-
Object.defineProperty(typedClient, symbol, descriptor)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Override the on method for typed events
|
|
326
|
-
typedClient.on = (event: keyof T, callback: (data: any) => void): void => {
|
|
327
|
-
client.on(String(event), callback)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Override the send method for typed events
|
|
331
|
-
typedClient.send = (event: keyof T, data: any): void => {
|
|
332
|
-
// Use the original client's send method directly
|
|
333
|
-
client.send({ type: event, data })
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Ensure the typed client has access to the original client's properties
|
|
337
|
-
Object.defineProperty(typedClient, 'ws', {
|
|
338
|
-
get() {
|
|
339
|
-
return (client as any).ws
|
|
340
|
-
},
|
|
341
|
-
set(value) {
|
|
342
|
-
(client as any).ws = value
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
return typedClient
|
|
347
|
-
}
|
package/test/api-client.test.ts
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, beforeEach, mock } from 'bun:test'
|
|
2
|
-
import { VafastApiClient } from '../src'
|
|
3
|
-
|
|
4
|
-
// Mock fetch
|
|
5
|
-
const mockFetch = mock(() =>
|
|
6
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), {
|
|
7
|
-
status: 200,
|
|
8
|
-
headers: { 'Content-Type': 'application/json' }
|
|
9
|
-
}))
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
// Mock global fetch
|
|
13
|
-
global.fetch = mockFetch
|
|
14
|
-
|
|
15
|
-
describe('VafastApiClient', () => {
|
|
16
|
-
let client: VafastApiClient
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
client = new VafastApiClient({
|
|
20
|
-
baseURL: 'https://api.example.com',
|
|
21
|
-
timeout: 5000,
|
|
22
|
-
retries: 2
|
|
23
|
-
})
|
|
24
|
-
mockFetch.mockClear()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
describe('Constructor', () => {
|
|
28
|
-
it('should create client with default config', () => {
|
|
29
|
-
const defaultClient = new VafastApiClient()
|
|
30
|
-
expect(defaultClient).toBeDefined()
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('should create client with custom config', () => {
|
|
34
|
-
expect(client).toBeDefined()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should merge config correctly', () => {
|
|
38
|
-
const customClient = new VafastApiClient({
|
|
39
|
-
baseURL: 'https://custom.api.com',
|
|
40
|
-
timeout: 10000
|
|
41
|
-
})
|
|
42
|
-
expect(customClient).toBeDefined()
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
describe('Middleware Management', () => {
|
|
47
|
-
it('should add middleware', () => {
|
|
48
|
-
const middleware = {
|
|
49
|
-
name: 'test',
|
|
50
|
-
onRequest: async (req: Request) => req
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
client.addMiddleware(middleware)
|
|
54
|
-
expect(client).toBeDefined()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('should remove middleware', () => {
|
|
58
|
-
const middleware = {
|
|
59
|
-
name: 'test',
|
|
60
|
-
onRequest: async (req: Request) => req
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
client.addMiddleware(middleware)
|
|
64
|
-
client.removeMiddleware('test')
|
|
65
|
-
expect(client).toBeDefined()
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('should handle non-existent middleware removal', () => {
|
|
69
|
-
client.removeMiddleware('non-existent')
|
|
70
|
-
expect(client).toBeDefined()
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
describe('Interceptor Management', () => {
|
|
75
|
-
it('should add interceptor', () => {
|
|
76
|
-
const interceptor = {
|
|
77
|
-
request: async (config: any) => config
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
client.addInterceptor(interceptor)
|
|
81
|
-
expect(client).toBeDefined()
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('should remove interceptor', () => {
|
|
85
|
-
const interceptor = {
|
|
86
|
-
request: async (config: any) => config
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
client.addInterceptor(interceptor)
|
|
90
|
-
client.removeInterceptor(0)
|
|
91
|
-
expect(client).toBeDefined()
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('should handle invalid interceptor index', () => {
|
|
95
|
-
client.removeInterceptor(-1)
|
|
96
|
-
client.removeInterceptor(999)
|
|
97
|
-
expect(client).toBeDefined()
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
describe('Cache Management', () => {
|
|
102
|
-
it('should clear cache', () => {
|
|
103
|
-
client.clearCache()
|
|
104
|
-
const stats = client.getCacheStats()
|
|
105
|
-
expect(stats.size).toBe(0)
|
|
106
|
-
expect(stats.keys).toEqual([])
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('should get cache stats', () => {
|
|
110
|
-
const stats = client.getCacheStats()
|
|
111
|
-
expect(stats).toHaveProperty('size')
|
|
112
|
-
expect(stats).toHaveProperty('keys')
|
|
113
|
-
expect(Array.isArray(stats.keys)).toBe(true)
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
describe('Configuration Methods', () => {
|
|
118
|
-
it('should set cache config', () => {
|
|
119
|
-
const result = client.setCacheConfig({
|
|
120
|
-
enabled: true,
|
|
121
|
-
ttl: 300000,
|
|
122
|
-
maxSize: 100,
|
|
123
|
-
strategy: 'memory'
|
|
124
|
-
})
|
|
125
|
-
expect(result).toBe(client)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('should set retry config', () => {
|
|
129
|
-
const result = client.setRetryConfig({
|
|
130
|
-
enabled: true,
|
|
131
|
-
maxRetries: 5,
|
|
132
|
-
retryDelay: 1000,
|
|
133
|
-
backoffMultiplier: 2,
|
|
134
|
-
retryableStatuses: [500, 502, 503]
|
|
135
|
-
})
|
|
136
|
-
expect(result).toBe(client)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('should set log config', () => {
|
|
140
|
-
const result = client.setLogConfig({
|
|
141
|
-
enabled: true,
|
|
142
|
-
level: 'info',
|
|
143
|
-
format: 'json',
|
|
144
|
-
includeHeaders: true,
|
|
145
|
-
includeBody: false
|
|
146
|
-
})
|
|
147
|
-
expect(result).toBe(client)
|
|
148
|
-
})
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
describe('HTTP Methods', () => {
|
|
152
|
-
it('should make GET request', async () => {
|
|
153
|
-
const response = await client.get('/users', { page: 1, limit: 10 })
|
|
154
|
-
expect(response).toBeDefined()
|
|
155
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('should make POST request', async () => {
|
|
159
|
-
const response = await client.post('/users', { name: 'John', email: 'john@example.com' })
|
|
160
|
-
expect(response).toBeDefined()
|
|
161
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('should make PUT request', async () => {
|
|
165
|
-
const response = await client.put('/users/123', { name: 'John Updated' })
|
|
166
|
-
expect(response).toBeDefined()
|
|
167
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('should make DELETE request', async () => {
|
|
171
|
-
const response = await client.delete('/users/123')
|
|
172
|
-
expect(response).toBeDefined()
|
|
173
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('should make PATCH request', async () => {
|
|
177
|
-
const response = await client.patch('/users/123', { name: 'John Patched' })
|
|
178
|
-
expect(response).toBeDefined()
|
|
179
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('should make HEAD request', async () => {
|
|
183
|
-
const response = await client.head('/users')
|
|
184
|
-
expect(response).toBeDefined()
|
|
185
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it('should make OPTIONS request', async () => {
|
|
189
|
-
const response = await client.options('/users')
|
|
190
|
-
expect(response).toBeDefined()
|
|
191
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe('Request Configuration', () => {
|
|
196
|
-
it('should handle custom headers', async () => {
|
|
197
|
-
const response = await client.get('/users', undefined, {
|
|
198
|
-
headers: { 'X-Custom-Header': 'test' }
|
|
199
|
-
})
|
|
200
|
-
expect(response).toBeDefined()
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('should handle timeout configuration', async () => {
|
|
204
|
-
const response = await client.get('/users', undefined, {
|
|
205
|
-
timeout: 10000
|
|
206
|
-
})
|
|
207
|
-
expect(response).toBeDefined()
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
it('should handle retry configuration', async () => {
|
|
211
|
-
const response = await client.get('/users', undefined, {
|
|
212
|
-
retries: 5,
|
|
213
|
-
retryDelay: 2000
|
|
214
|
-
})
|
|
215
|
-
expect(response).toBeDefined()
|
|
216
|
-
})
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
describe('Error Handling', () => {
|
|
220
|
-
it('should handle network errors gracefully', async () => {
|
|
221
|
-
// Mock fetch to throw error
|
|
222
|
-
const errorFetch = mock(() => Promise.reject(new Error('Network error')))
|
|
223
|
-
global.fetch = errorFetch
|
|
224
|
-
|
|
225
|
-
const response = await client.get('/users')
|
|
226
|
-
expect(response.error).toBeDefined()
|
|
227
|
-
expect(response.data).toBeNull()
|
|
228
|
-
|
|
229
|
-
// Restore original mock
|
|
230
|
-
global.fetch = mockFetch
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('should handle HTTP error statuses', async () => {
|
|
234
|
-
const errorFetch = mock(() =>
|
|
235
|
-
Promise.resolve(new Response('Not Found', { status: 404 }))
|
|
236
|
-
)
|
|
237
|
-
global.fetch = errorFetch
|
|
238
|
-
|
|
239
|
-
const response = await client.get('/users')
|
|
240
|
-
expect(response.status).toBe(404)
|
|
241
|
-
|
|
242
|
-
// Restore original mock
|
|
243
|
-
global.fetch = mockFetch
|
|
244
|
-
})
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
describe('URL Building', () => {
|
|
248
|
-
it('should build URLs correctly with baseURL', async () => {
|
|
249
|
-
const customClient = new VafastApiClient({
|
|
250
|
-
baseURL: 'https://api.example.com'
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
await customClient.get('/users')
|
|
254
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('should handle absolute URLs', async () => {
|
|
258
|
-
await client.get('https://external.api.com/users')
|
|
259
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
260
|
-
})
|
|
261
|
-
})
|
|
262
|
-
})
|
package/test/basic.test.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, beforeEach } from 'bun:test'
|
|
2
|
-
import { VafastApiClient } from '../src'
|
|
3
|
-
|
|
4
|
-
describe('VafastApiClient', () => {
|
|
5
|
-
let client: VafastApiClient
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
client = new VafastApiClient({
|
|
9
|
-
baseURL: 'https://api.example.com',
|
|
10
|
-
timeout: 5000,
|
|
11
|
-
retries: 2
|
|
12
|
-
})
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('should create client with default config', () => {
|
|
16
|
-
const defaultClient = new VafastApiClient()
|
|
17
|
-
expect(defaultClient).toBeDefined()
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('should create client with custom config', () => {
|
|
21
|
-
expect(client).toBeDefined()
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('should add and remove middleware', () => {
|
|
25
|
-
const middleware = {
|
|
26
|
-
name: 'test',
|
|
27
|
-
onRequest: async (req: Request) => req
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
client.addMiddleware(middleware)
|
|
31
|
-
expect(client.getCacheStats()).toBeDefined()
|
|
32
|
-
|
|
33
|
-
client.removeMiddleware('test')
|
|
34
|
-
expect(client.getCacheStats()).toBeDefined()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should add and remove interceptor', () => {
|
|
38
|
-
const interceptor = {
|
|
39
|
-
request: async (config: any) => config
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
client.addInterceptor(interceptor)
|
|
43
|
-
expect(client.getCacheStats()).toBeDefined()
|
|
44
|
-
|
|
45
|
-
client.removeInterceptor(0)
|
|
46
|
-
expect(client.getCacheStats()).toBeDefined()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('should clear cache', () => {
|
|
50
|
-
client.clearCache()
|
|
51
|
-
const stats = client.getCacheStats()
|
|
52
|
-
expect(stats.size).toBe(0)
|
|
53
|
-
expect(stats.keys).toEqual([])
|
|
54
|
-
})
|
|
55
|
-
})
|