aziosxjs 0.4.0 → 1.0.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/LICENSE +21 -0
- package/README.md +92 -267
- package/dist/cache/memoryCache.d.ts +4 -0
- package/dist/cache/memoryCache.js +32 -0
- package/dist/cache/memoryCache.js.map +1 -0
- package/dist/core/Azios.d.ts +33 -3
- package/dist/core/Azios.js +119 -2
- package/dist/core/Azios.js.map +1 -1
- package/dist/core/createInstance.js +3 -0
- package/dist/core/createInstance.js.map +1 -1
- package/dist/core/dispatchRequest.d.ts +1 -2
- package/dist/core/dispatchRequest.js +68 -42
- package/dist/core/dispatchRequest.js.map +1 -1
- package/dist/core/requestStore.d.ts +3 -0
- package/dist/core/requestStore.js +16 -0
- package/dist/core/requestStore.js.map +1 -0
- package/dist/errors/AziosError.d.ts +2 -2
- package/dist/errors/AziosError.js +6 -3
- package/dist/errors/AziosError.js.map +1 -1
- package/dist/helpers/buildURL.d.ts +1 -0
- package/dist/helpers/buildURL.js +27 -0
- package/dist/helpers/buildURL.js.map +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/MiddlewarePipeline.d.ts +54 -0
- package/dist/middleware/MiddlewarePipeline.js +122 -0
- package/dist/middleware/MiddlewarePipeline.js.map +1 -0
- package/dist/middleware/compose.d.ts +1 -0
- package/dist/middleware/compose.js +25 -0
- package/dist/middleware/compose.js.map +1 -0
- package/dist/middleware/middlewareManager.d.ts +4 -0
- package/dist/middleware/middlewareManager.js +12 -0
- package/dist/middleware/middlewareManager.js.map +1 -0
- package/dist/plugins/pluginManager.d.ts +44 -0
- package/dist/plugins/pluginManager.js +120 -0
- package/dist/plugins/pluginManager.js.map +1 -0
- package/dist/rateLimiter/rateLimiter.d.ts +1 -0
- package/dist/rateLimiter/rateLimiter.js +31 -0
- package/dist/rateLimiter/rateLimiter.js.map +1 -0
- package/dist/runtimes/AdapterFactory.d.ts +22 -0
- package/dist/runtimes/AdapterFactory.js +43 -0
- package/dist/runtimes/AdapterFactory.js.map +1 -0
- package/dist/runtimes/HttpAdapter.d.ts +23 -0
- package/dist/runtimes/HttpAdapter.js +10 -0
- package/dist/runtimes/HttpAdapter.js.map +1 -0
- package/dist/runtimes/UniversalHttpAdapter.d.ts +19 -0
- package/dist/runtimes/UniversalHttpAdapter.js +114 -0
- package/dist/runtimes/UniversalHttpAdapter.js.map +1 -0
- package/dist/runtimes/detectRuntime.d.ts +34 -0
- package/dist/runtimes/detectRuntime.js +77 -0
- package/dist/runtimes/detectRuntime.js.map +1 -0
- package/dist/types/config.d.ts +8 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +22 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/middleware.d.ts +16 -0
- package/dist/types/middleware.js +3 -0
- package/dist/types/middleware.js.map +1 -0
- package/dist/types/plugin.d.ts +43 -0
- package/dist/types/plugin.js +3 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/types/request.d.ts +15 -0
- package/dist/types/response.d.ts +23 -1
- package/package.json +24 -5
- package/src/adapters/httpAdapter.ts +0 -0
- package/src/adapters/index.ts +0 -0
- package/src/adapters/xhrAdapter.ts +0 -0
- package/src/cache/memoryCache.ts +45 -0
- package/src/config/mergeConfig.ts +0 -0
- package/src/core/Azios.ts +184 -0
- package/src/core/createInstance.ts +23 -0
- package/src/core/dispatchRequest.ts +111 -0
- package/src/core/requestMethods.ts +0 -0
- package/src/core/requestStore.ts +13 -0
- package/src/errors/AziosError.ts +26 -0
- package/src/helpers/buildURL.ts +44 -0
- package/src/helpers/normalizeHeaders.ts +0 -0
- package/src/helpers/parseHeaders.ts +0 -0
- package/src/index.ts +64 -0
- package/src/interceptors/InterceptorManager.ts +30 -0
- package/src/middleware/MiddlewarePipeline.ts +155 -0
- package/src/middleware/compose.ts +33 -0
- package/src/middleware/middlewareManager.ts +9 -0
- package/src/plugins/pluginManager.ts +143 -0
- package/src/rateLimiter/rateLimiter.ts +36 -0
- package/src/runtimes/AdapterFactory.ts +46 -0
- package/src/runtimes/HttpAdapter.ts +26 -0
- package/src/runtimes/UniversalHttpAdapter.ts +136 -0
- package/src/runtimes/detectRuntime.ts +85 -0
- package/src/types/config.ts +30 -0
- package/src/types/index.ts +5 -0
- package/src/types/middleware.ts +21 -0
- package/src/types/plugin.ts +50 -0
- package/src/types/request.ts +39 -0
- package/src/types/response.ts +37 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type FulfilledFn<T> = (val: T) => T | Promise<T>
|
|
2
|
+
type RejectedFn = (error: any) => any
|
|
3
|
+
|
|
4
|
+
interface Interceptor<T> {
|
|
5
|
+
fulfilled: FulfilledFn<T>
|
|
6
|
+
rejected?: RejectedFn
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default class InterceptorManager<T> {
|
|
10
|
+
|
|
11
|
+
private handlers: Array<Interceptor<T> | null> = []
|
|
12
|
+
|
|
13
|
+
use(fulfilled: FulfilledFn<T>, rejected?: RejectedFn) {
|
|
14
|
+
this.handlers.push({
|
|
15
|
+
fulfilled,
|
|
16
|
+
rejected
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return this.handlers.length - 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
forEach(fn: (interceptor: Interceptor<T>) => void) {
|
|
23
|
+
this.handlers.forEach(h => {
|
|
24
|
+
if (h !== null) {
|
|
25
|
+
fn(h)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { AziosRequestConfig } from '../types/config'
|
|
2
|
+
import type { AziosResponse } from '../types/response'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Middleware handler types
|
|
6
|
+
* Supports async middleware in the style of Koa
|
|
7
|
+
*/
|
|
8
|
+
export type RequestMiddleware = (
|
|
9
|
+
config: AziosRequestConfig,
|
|
10
|
+
next: () => Promise<AziosResponse>
|
|
11
|
+
) => Promise<AziosResponse>
|
|
12
|
+
|
|
13
|
+
export type ResponseMiddleware = (
|
|
14
|
+
response: AziosResponse,
|
|
15
|
+
next: () => Promise<AziosResponse>
|
|
16
|
+
) => Promise<AziosResponse>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Composition utility for middleware chaining
|
|
20
|
+
* Implements Koa-style middleware flow control
|
|
21
|
+
*/
|
|
22
|
+
export class MiddlewarePipeline {
|
|
23
|
+
private requestMiddleware: RequestMiddleware[] = []
|
|
24
|
+
private responseMiddleware: ResponseMiddleware[] = []
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Add request middleware
|
|
28
|
+
* Middleware will execute in registration order
|
|
29
|
+
*/
|
|
30
|
+
useRequest(middleware: RequestMiddleware): this {
|
|
31
|
+
this.requestMiddleware.push(middleware)
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add response middleware
|
|
37
|
+
* Middleware will execute in registration order
|
|
38
|
+
*/
|
|
39
|
+
useResponse(middleware: ResponseMiddleware): this {
|
|
40
|
+
this.responseMiddleware.push(middleware)
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compose request middleware with proper async error handling
|
|
46
|
+
*/
|
|
47
|
+
private composeRequest(): RequestMiddleware {
|
|
48
|
+
const middleware = this.requestMiddleware
|
|
49
|
+
|
|
50
|
+
return async (config: AziosRequestConfig, next: () => Promise<AziosResponse>) => {
|
|
51
|
+
let index = -1
|
|
52
|
+
|
|
53
|
+
const dispatch = async (i: number): Promise<AziosResponse> => {
|
|
54
|
+
if (i <= index) {
|
|
55
|
+
throw new Error('next() called multiple times in middleware')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
index = i
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
if (i < middleware.length) {
|
|
62
|
+
return await middleware[i](config, () => dispatch(i + 1))
|
|
63
|
+
} else {
|
|
64
|
+
return await next()
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
throw err
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return dispatch(0)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Compose response middleware with proper async error handling
|
|
77
|
+
*/
|
|
78
|
+
private composeResponse(): ResponseMiddleware {
|
|
79
|
+
const middleware = this.responseMiddleware
|
|
80
|
+
|
|
81
|
+
return async (response: AziosResponse, next: () => Promise<AziosResponse>) => {
|
|
82
|
+
let index = -1
|
|
83
|
+
|
|
84
|
+
const dispatch = async (i: number): Promise<AziosResponse> => {
|
|
85
|
+
if (i <= index) {
|
|
86
|
+
throw new Error('next() called multiple times in middleware')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
index = i
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
if (i < middleware.length) {
|
|
93
|
+
return await middleware[i](response, () => dispatch(i + 1))
|
|
94
|
+
} else {
|
|
95
|
+
return await next()
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
throw err
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return dispatch(0)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Execute request middleware pipeline
|
|
108
|
+
*/
|
|
109
|
+
async executeRequestMiddleware(
|
|
110
|
+
config: AziosRequestConfig,
|
|
111
|
+
next: () => Promise<AziosResponse>
|
|
112
|
+
): Promise<AziosResponse> {
|
|
113
|
+
if (this.requestMiddleware.length === 0) {
|
|
114
|
+
return next()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const composed = this.composeRequest()
|
|
118
|
+
return composed(config, next)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Execute response middleware pipeline
|
|
123
|
+
*/
|
|
124
|
+
async executeResponseMiddleware(
|
|
125
|
+
response: AziosResponse,
|
|
126
|
+
next: () => Promise<AziosResponse>
|
|
127
|
+
): Promise<AziosResponse> {
|
|
128
|
+
if (this.responseMiddleware.length === 0) {
|
|
129
|
+
return next()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const composed = this.composeResponse()
|
|
133
|
+
return composed(response, next)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clear all middleware
|
|
138
|
+
*/
|
|
139
|
+
clear(): void {
|
|
140
|
+
this.requestMiddleware = []
|
|
141
|
+
this.responseMiddleware = []
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get registered middleware count
|
|
146
|
+
*/
|
|
147
|
+
getMiddlewareCount(): { request: number; response: number } {
|
|
148
|
+
return {
|
|
149
|
+
request: this.requestMiddleware.length,
|
|
150
|
+
response: this.responseMiddleware.length
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default MiddlewarePipeline
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function compose(middleware: any[]) {
|
|
2
|
+
|
|
3
|
+
return function (context: any) {
|
|
4
|
+
|
|
5
|
+
let index = -1
|
|
6
|
+
|
|
7
|
+
function dispatch(i: number): Promise<any> {
|
|
8
|
+
|
|
9
|
+
if (i <= index) {
|
|
10
|
+
return Promise.reject(new Error("next() called multiple times"))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
index = i
|
|
14
|
+
|
|
15
|
+
const fn = middleware[i]
|
|
16
|
+
|
|
17
|
+
if (!fn) return Promise.resolve()
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return Promise.resolve(
|
|
21
|
+
fn(context, () => dispatch(i + 1))
|
|
22
|
+
)
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return Promise.reject(err)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return dispatch(0)
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { AziosPlugin, PluginRegistry } from '../types/plugin'
|
|
2
|
+
import type { AziosInstance } from '../types/request'
|
|
3
|
+
import type { AziosRequestConfig } from '../types/config'
|
|
4
|
+
import type { AziosResponse } from '../types/response'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PluginManager handles plugin lifecycle and hook execution
|
|
8
|
+
* Production-grade plugin system with error handling and validation
|
|
9
|
+
*/
|
|
10
|
+
export default class PluginManager {
|
|
11
|
+
private plugins: PluginRegistry = {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Install a plugin and trigger its onInstall hook
|
|
15
|
+
* @throws Error if plugin name is already registered
|
|
16
|
+
*/
|
|
17
|
+
async install(plugin: AziosPlugin, instance: AziosInstance): Promise<void> {
|
|
18
|
+
if (this.plugins[plugin.name]) {
|
|
19
|
+
throw new Error(`Plugin "${plugin.name}" is already installed`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.plugins[plugin.name] = plugin
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (plugin.hooks.onInstall) {
|
|
26
|
+
await plugin.hooks.onInstall(instance)
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
// Rollback on error
|
|
30
|
+
delete this.plugins[plugin.name]
|
|
31
|
+
throw err
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Uninstall a plugin and trigger its onUninstall hook
|
|
37
|
+
*/
|
|
38
|
+
async uninstall(pluginName: string, instance: AziosInstance): Promise<void> {
|
|
39
|
+
const plugin = this.plugins[pluginName]
|
|
40
|
+
|
|
41
|
+
if (!plugin) {
|
|
42
|
+
throw new Error(`Plugin "${pluginName}" is not installed`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if (plugin.hooks.onUninstall) {
|
|
47
|
+
await plugin.hooks.onUninstall(instance)
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
delete this.plugins[pluginName]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Execute beforeRequest hooks from all plugins
|
|
56
|
+
*/
|
|
57
|
+
async executeBeforeRequestHooks(
|
|
58
|
+
config: AziosRequestConfig
|
|
59
|
+
): Promise<AziosRequestConfig> {
|
|
60
|
+
let result = config
|
|
61
|
+
|
|
62
|
+
for (const plugin of Object.values(this.plugins)) {
|
|
63
|
+
if (plugin.hooks.beforeRequest) {
|
|
64
|
+
try {
|
|
65
|
+
result = await plugin.hooks.beforeRequest(result)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Plugin "${plugin.name}" beforeRequest hook failed: ${err instanceof Error ? err.message : String(err)}`
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Execute afterResponse hooks from all plugins
|
|
79
|
+
*/
|
|
80
|
+
async executeAfterResponseHooks(
|
|
81
|
+
response: AziosResponse
|
|
82
|
+
): Promise<AziosResponse> {
|
|
83
|
+
let result = response
|
|
84
|
+
|
|
85
|
+
for (const plugin of Object.values(this.plugins)) {
|
|
86
|
+
if (plugin.hooks.afterResponse) {
|
|
87
|
+
try {
|
|
88
|
+
result = await plugin.hooks.afterResponse(result)
|
|
89
|
+
} catch (err) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Plugin "${plugin.name}" afterResponse hook failed: ${err instanceof Error ? err.message : String(err)}`
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Execute onError hooks from all plugins
|
|
102
|
+
*/
|
|
103
|
+
async executeOnErrorHooks(error: Error): Promise<Error> {
|
|
104
|
+
let result = error
|
|
105
|
+
|
|
106
|
+
for (const plugin of Object.values(this.plugins)) {
|
|
107
|
+
if (plugin.hooks.onError) {
|
|
108
|
+
try {
|
|
109
|
+
result = await plugin.hooks.onError(result)
|
|
110
|
+
} catch (err) {
|
|
111
|
+
// Log but don't throw - error handlers should not crash
|
|
112
|
+
console.warn(
|
|
113
|
+
`Plugin "${plugin.name}" onError hook failed:`,
|
|
114
|
+
err instanceof Error ? err.message : String(err)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get all currently installed plugins
|
|
125
|
+
*/
|
|
126
|
+
getInstalledPlugins(): AziosPlugin[] {
|
|
127
|
+
return Object.values(this.plugins)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if a plugin is installed
|
|
132
|
+
*/
|
|
133
|
+
isPluginInstalled(name: string): boolean {
|
|
134
|
+
return name in this.plugins
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Clear all plugins
|
|
139
|
+
*/
|
|
140
|
+
clear(): void {
|
|
141
|
+
this.plugins = {}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
let queue: Function[] = [];
|
|
2
|
+
let activeRequests = 0;
|
|
3
|
+
|
|
4
|
+
export function schedule(task: Function, limit: number) {
|
|
5
|
+
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
|
|
8
|
+
const run = async () => {
|
|
9
|
+
|
|
10
|
+
activeRequests++;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const result = await task();
|
|
14
|
+
resolve(result);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
reject(err);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
activeRequests--;
|
|
20
|
+
|
|
21
|
+
if (queue.length) {
|
|
22
|
+
const next = queue.shift();
|
|
23
|
+
next && next();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (activeRequests < limit) {
|
|
29
|
+
run();
|
|
30
|
+
} else {
|
|
31
|
+
queue.push(run);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HttpAdapter } from './HttpAdapter'
|
|
2
|
+
import { currentRuntime, RuntimeType } from './detectRuntime'
|
|
3
|
+
import UniversalHttpAdapter from './UniversalHttpAdapter'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adapter factory for selecting the appropriate HTTP adapter
|
|
7
|
+
* Uses universal Fetch API when available with fallbacks for specific runtimes
|
|
8
|
+
*/
|
|
9
|
+
export class AdapterFactory {
|
|
10
|
+
private static adapters: Map<RuntimeType, HttpAdapter> = new Map()
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get or create adapter for current runtime
|
|
14
|
+
*/
|
|
15
|
+
static getAdapter(): HttpAdapter {
|
|
16
|
+
if (!this.adapters.has(currentRuntime)) {
|
|
17
|
+
this.adapters.set(currentRuntime, new UniversalHttpAdapter())
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const adapter = this.adapters.get(currentRuntime)!
|
|
21
|
+
|
|
22
|
+
if (!adapter.isSupported()) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`No supported HTTP adapter found for runtime: ${currentRuntime}`
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return adapter
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register custom adapter for specific runtime
|
|
33
|
+
*/
|
|
34
|
+
static registerAdapter(runtime: RuntimeType, adapter: HttpAdapter): void {
|
|
35
|
+
this.adapters.set(runtime, adapter)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Clear adapter cache
|
|
40
|
+
*/
|
|
41
|
+
static clearCache(): void {
|
|
42
|
+
this.adapters.clear()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default AdapterFactory
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AziosRequestConfig } from '../types/config'
|
|
2
|
+
import type { AziosResponse } from '../types/response'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Universal HTTP adapter interface
|
|
6
|
+
* Abstracts HTTP functionality across different runtimes
|
|
7
|
+
*/
|
|
8
|
+
export interface HttpAdapter {
|
|
9
|
+
/**
|
|
10
|
+
* Execute an HTTP request
|
|
11
|
+
*/
|
|
12
|
+
request(config: AziosRequestConfig): Promise<AziosResponse>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Method to check if adapter supports the current runtime
|
|
16
|
+
*/
|
|
17
|
+
isSupported(): boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Base adapter class with common logic
|
|
22
|
+
*/
|
|
23
|
+
export abstract class BaseAdapter implements HttpAdapter {
|
|
24
|
+
abstract request(config: AziosRequestConfig): Promise<AziosResponse>
|
|
25
|
+
abstract isSupported(): boolean
|
|
26
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { AziosRequestConfig } from '../types/config'
|
|
2
|
+
import type { AziosResponse } from '../types/response'
|
|
3
|
+
import { BaseAdapter } from './HttpAdapter'
|
|
4
|
+
import AziosError from '../errors/AziosError'
|
|
5
|
+
import { isRuntime, RuntimeType } from './detectRuntime'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Unified HTTP adapter using Fetch API
|
|
9
|
+
* Works across Node 18+, browsers, Bun, Deno, and edge runtimes
|
|
10
|
+
* Falls back to runtime-specific implementations if needed
|
|
11
|
+
*/
|
|
12
|
+
export class UniversalHttpAdapter extends BaseAdapter {
|
|
13
|
+
/**
|
|
14
|
+
* Check if Fetch API is available
|
|
15
|
+
*/
|
|
16
|
+
isSupported(): boolean {
|
|
17
|
+
return typeof fetch !== 'undefined'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execute HTTP request using Fetch API
|
|
22
|
+
*/
|
|
23
|
+
async request(config: AziosRequestConfig): Promise<AziosResponse> {
|
|
24
|
+
if (!this.isSupported()) {
|
|
25
|
+
throw new AziosError(
|
|
26
|
+
'Fetch API is not available in this runtime',
|
|
27
|
+
'UNSUPPORTED_RUNTIME',
|
|
28
|
+
config
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const { url, method = 'GET', headers, data, timeout, signal } = config
|
|
34
|
+
|
|
35
|
+
const baseURL = config.baseURL ? config.baseURL : ''
|
|
36
|
+
const fullURL = new URL(baseURL + url)
|
|
37
|
+
|
|
38
|
+
// Add query parameters
|
|
39
|
+
if (config.params) {
|
|
40
|
+
Object.entries(config.params).forEach(([key, value]) => {
|
|
41
|
+
if (value !== null && value !== undefined) {
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
value.forEach(v => fullURL.searchParams.append(key, String(v)))
|
|
44
|
+
} else {
|
|
45
|
+
fullURL.searchParams.set(key, String(value))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Prepare request body
|
|
52
|
+
let body: string | null = null
|
|
53
|
+
if (data) {
|
|
54
|
+
body = typeof data === 'string' ? data : JSON.stringify(data)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Set up timeout if specified (using AbortController)
|
|
58
|
+
let timeoutId: NodeJS.Timeout | undefined
|
|
59
|
+
const controller = new AbortController()
|
|
60
|
+
const finalSignal = signal || controller.signal
|
|
61
|
+
|
|
62
|
+
if (timeout) {
|
|
63
|
+
timeoutId = setTimeout(() => {
|
|
64
|
+
controller.abort()
|
|
65
|
+
}, timeout)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Execute fetch
|
|
69
|
+
const response = await fetch(fullURL.toString(), {
|
|
70
|
+
method,
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
...headers
|
|
74
|
+
},
|
|
75
|
+
body,
|
|
76
|
+
signal: finalSignal as any
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (timeoutId) {
|
|
80
|
+
clearTimeout(timeoutId)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Parse response body
|
|
84
|
+
let responseData: any
|
|
85
|
+
|
|
86
|
+
if (response.status === 204) {
|
|
87
|
+
// No content
|
|
88
|
+
responseData = null
|
|
89
|
+
} else {
|
|
90
|
+
const contentType = response.headers.get('content-type')
|
|
91
|
+
|
|
92
|
+
if (contentType?.includes('application/json')) {
|
|
93
|
+
try {
|
|
94
|
+
responseData = await response.json()
|
|
95
|
+
} catch {
|
|
96
|
+
responseData = await response.text()
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
responseData = await response.text()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const aziosResponse: AziosResponse = {
|
|
104
|
+
data: responseData,
|
|
105
|
+
status: response.status,
|
|
106
|
+
statusText: response.statusText,
|
|
107
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
108
|
+
config
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return aziosResponse
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof TypeError && err.message.includes('aborted')) {
|
|
114
|
+
throw new AziosError(
|
|
115
|
+
'Request aborted',
|
|
116
|
+
'ABORTED',
|
|
117
|
+
config
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (err instanceof AziosError) {
|
|
122
|
+
throw err
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new AziosError(
|
|
126
|
+
err instanceof Error ? err.message : 'Network request failed',
|
|
127
|
+
'NETWORK_ERROR',
|
|
128
|
+
config,
|
|
129
|
+
undefined,
|
|
130
|
+
err instanceof Error ? err : new Error(String(err))
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default UniversalHttpAdapter
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime detection utilities
|
|
3
|
+
* Identifies the current JavaScript runtime environment
|
|
4
|
+
* Supports: Node.js, Browser, Bun, Deno, Cloudflare Workers/Edge
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export enum RuntimeType {
|
|
8
|
+
Node = 'node',
|
|
9
|
+
Browser = 'browser',
|
|
10
|
+
Bun = 'bun',
|
|
11
|
+
Deno = 'deno',
|
|
12
|
+
Edge = 'edge',
|
|
13
|
+
Unknown = 'unknown'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detects the current runtime environment
|
|
18
|
+
* Production-grade detection with reliable environment checks
|
|
19
|
+
*/
|
|
20
|
+
export function detectRuntime(): RuntimeType {
|
|
21
|
+
// Check for Deno
|
|
22
|
+
if (typeof (globalThis as any).Deno !== 'undefined') {
|
|
23
|
+
return RuntimeType.Deno
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check for Bun
|
|
27
|
+
if (typeof (globalThis as any).Bun !== 'undefined') {
|
|
28
|
+
return RuntimeType.Bun
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check for Cloudflare Workers / Edge
|
|
32
|
+
if (
|
|
33
|
+
typeof (globalThis as any).caches !== 'undefined' &&
|
|
34
|
+
typeof (globalThis as any).ENVIRONMENT !== 'undefined'
|
|
35
|
+
) {
|
|
36
|
+
return RuntimeType.Edge
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for Node.js
|
|
40
|
+
if (
|
|
41
|
+
typeof process !== 'undefined' &&
|
|
42
|
+
process.versions &&
|
|
43
|
+
process.versions.node
|
|
44
|
+
) {
|
|
45
|
+
return RuntimeType.Node
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for Browser
|
|
49
|
+
if (typeof (globalThis as any).window !== 'undefined' && typeof (globalThis as any).document !== 'undefined') {
|
|
50
|
+
return RuntimeType.Browser
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return RuntimeType.Unknown
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get current runtime for use in adapters
|
|
58
|
+
*/
|
|
59
|
+
export const currentRuntime = detectRuntime()
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if running in a specific runtime
|
|
63
|
+
*/
|
|
64
|
+
export function isRuntime(runtime: RuntimeType): boolean {
|
|
65
|
+
return currentRuntime === runtime
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if running in server-side runtime
|
|
70
|
+
*/
|
|
71
|
+
export function isServerRuntime(): boolean {
|
|
72
|
+
return (
|
|
73
|
+
currentRuntime === RuntimeType.Node ||
|
|
74
|
+
currentRuntime === RuntimeType.Bun ||
|
|
75
|
+
currentRuntime === RuntimeType.Deno ||
|
|
76
|
+
currentRuntime === RuntimeType.Edge
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if running in browser runtime
|
|
82
|
+
*/
|
|
83
|
+
export function isBrowserRuntime(): boolean {
|
|
84
|
+
return currentRuntime === RuntimeType.Browser
|
|
85
|
+
}
|