@tahanabavi/typefetch 1.0.2 → 1.0.3
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 +29 -23
- package/dist/index.d.mts +86 -0
- package/dist/index.d.ts +86 -6
- package/dist/index.js +235 -21
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +208 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +9 -4
- package/.github/workflows/publish.yml +0 -36
- package/dist/__tests__/client.test.d.ts +0 -1
- package/dist/__tests__/client.test.js +0 -108
- package/dist/__tests__/middlewares.test.d.ts +0 -1
- package/dist/__tests__/middlewares.test.js +0 -85
- package/dist/client.d.ts +0 -31
- package/dist/client.js +0 -98
- package/dist/middlewares/auth.d.ts +0 -5
- package/dist/middlewares/auth.js +0 -17
- package/dist/middlewares/cache.d.ts +0 -5
- package/dist/middlewares/cache.js +0 -25
- package/dist/middlewares/logging.d.ts +0 -7
- package/dist/middlewares/logging.js +0 -13
- package/dist/middlewares/retry.d.ts +0 -6
- package/dist/middlewares/retry.js +0 -22
- package/jest.config.ts +0 -18
- package/src/__tests__/client.test.ts +0 -137
- package/src/__tests__/middlewares.test.ts +0 -108
- package/src/client.ts +0 -142
- package/src/index.ts +0 -6
- package/src/middlewares/auth.ts +0 -19
- package/src/middlewares/cache.ts +0 -26
- package/src/middlewares/logging.ts +0 -19
- package/src/middlewares/retry.ts +0 -25
- package/src/types.d.ts +0 -46
- package/tsconfig.json +0 -13
package/README.md
CHANGED
|
@@ -6,12 +6,12 @@ TypeFetch is a type-safe client for working with APIs, built with TypeScript and
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
- Fully type-safe using TypeScript and Zod
|
|
10
|
+
- Define contracts for modules and endpoints
|
|
11
|
+
- Support for middlewares to add custom behavior before or after requests
|
|
12
|
+
- Error handling with the `RichError` class
|
|
13
|
+
- Ability to transform responses using a response transformer
|
|
14
|
+
- Authentication support via token
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -68,8 +68,10 @@ const client = new ApiClient(
|
|
|
68
68
|
|
|
69
69
|
client.init();
|
|
70
70
|
|
|
71
|
-
const
|
|
72
|
-
|
|
71
|
+
const { modules: api } = client;
|
|
72
|
+
|
|
73
|
+
const user = await api.user.getUser({ id: "123" });
|
|
74
|
+
const newUser = await api.user.createUser({ name: "Taha" });
|
|
73
75
|
```
|
|
74
76
|
|
|
75
77
|
---
|
|
@@ -91,12 +93,14 @@ client.onError((error: RichError) => {
|
|
|
91
93
|
You can add custom behavior before or after requests. Middlewares work similarly to Express:
|
|
92
94
|
|
|
93
95
|
```ts
|
|
94
|
-
client.use(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
client.use(
|
|
97
|
+
async (ctx: MiddlewareContext, next: MiddlewareNext, options?: any) => {
|
|
98
|
+
console.log("Request URL:", ctx.url);
|
|
99
|
+
const response = await next();
|
|
100
|
+
console.log("Response status:", response.status);
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
);
|
|
100
104
|
```
|
|
101
105
|
|
|
102
106
|
### Built-in Middlewares
|
|
@@ -117,10 +121,10 @@ client.use(AuthMiddleware, { refreshToken: () => "your-auth-token" });
|
|
|
117
121
|
client.use(CacheMiddleware, { ttl: 60 * 1000 });
|
|
118
122
|
```
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
- `LoggingMiddleware` – Logs requests and responses
|
|
125
|
+
- `RetryMiddleware` – Retries failed requests
|
|
126
|
+
- `AuthMiddleware` – Automatically adds Authorization headers
|
|
127
|
+
- `CacheMiddleware` – Caches responses to reduce repeated requests
|
|
124
128
|
|
|
125
129
|
---
|
|
126
130
|
|
|
@@ -138,9 +142,9 @@ client.useResponseTransform((data) => {
|
|
|
138
142
|
|
|
139
143
|
## Important Notes
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
145
|
+
- Always call `client.init()` before using endpoints.
|
|
146
|
+
- Types are automatically inferred from Zod, making inputs and outputs type-safe.
|
|
147
|
+
- Middleware execution order: first added middleware runs last, last added middleware runs first.
|
|
144
148
|
|
|
145
149
|
---
|
|
146
150
|
|
|
@@ -176,8 +180,10 @@ client.onError((err: RichError) => {
|
|
|
176
180
|
console.error("Error:", err.message);
|
|
177
181
|
});
|
|
178
182
|
|
|
183
|
+
const { modules: api } = client;
|
|
184
|
+
|
|
179
185
|
(async () => {
|
|
180
|
-
const post = await
|
|
186
|
+
const post = await api.post.getPost({ id: "1" });
|
|
181
187
|
console.log(post);
|
|
182
188
|
})();
|
|
183
|
-
```
|
|
189
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
|
|
4
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
5
|
+
path: string;
|
|
6
|
+
auth?: boolean;
|
|
7
|
+
request: TReq;
|
|
8
|
+
response: TRes;
|
|
9
|
+
};
|
|
10
|
+
type Contracts = {
|
|
11
|
+
[ModuleName: string]: {
|
|
12
|
+
[EndpointName: string]: EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
interface MiddlewareContext {
|
|
16
|
+
url: string;
|
|
17
|
+
init: RequestInit;
|
|
18
|
+
}
|
|
19
|
+
type MiddlewareNext = () => Promise<Response>;
|
|
20
|
+
type Middleware<Options = any> = (ctx: MiddlewareContext, next: MiddlewareNext, options?: Options) => Promise<Response>;
|
|
21
|
+
type ErrorLike = {
|
|
22
|
+
message: string;
|
|
23
|
+
status?: number;
|
|
24
|
+
code?: string;
|
|
25
|
+
[key: string]: any;
|
|
26
|
+
};
|
|
27
|
+
type EndpointDefZ = EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
|
|
28
|
+
type EndpointMethods<M extends Record<string, EndpointDefZ>> = {
|
|
29
|
+
[K in keyof M]: (input: z.infer<M[K]["request"]>) => Promise<z.infer<M[K]["response"]>>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
declare class RichError extends Error implements ErrorLike {
|
|
33
|
+
status?: number;
|
|
34
|
+
code?: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
detail?: string;
|
|
37
|
+
errors?: Record<string, string[]>;
|
|
38
|
+
constructor(error: Partial<ErrorLike> & {
|
|
39
|
+
message: string;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
43
|
+
private config;
|
|
44
|
+
private contracts;
|
|
45
|
+
private middlewares;
|
|
46
|
+
private errorHandler?;
|
|
47
|
+
private responseTransform;
|
|
48
|
+
private _modules;
|
|
49
|
+
constructor(config: {
|
|
50
|
+
baseUrl: string;
|
|
51
|
+
token?: string;
|
|
52
|
+
}, contracts: C);
|
|
53
|
+
init(): void;
|
|
54
|
+
get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
|
|
55
|
+
use<T>(middleware: Middleware<T>, options?: T): void;
|
|
56
|
+
onError(handler: (error: E) => void): void;
|
|
57
|
+
useResponseTransform(fn: (data: any) => any): void;
|
|
58
|
+
private request;
|
|
59
|
+
private createError;
|
|
60
|
+
private normalizeError;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type LoggingOptions = {
|
|
64
|
+
logRequest?: boolean;
|
|
65
|
+
logResponse?: boolean;
|
|
66
|
+
debug?: boolean;
|
|
67
|
+
};
|
|
68
|
+
declare const loggingMiddleware: Middleware<LoggingOptions>;
|
|
69
|
+
|
|
70
|
+
type RetryOptions = {
|
|
71
|
+
maxRetries?: number;
|
|
72
|
+
delay?: number;
|
|
73
|
+
};
|
|
74
|
+
declare const retryMiddleware: (options?: RetryOptions) => Middleware;
|
|
75
|
+
|
|
76
|
+
type AuthOptions = {
|
|
77
|
+
refreshToken?: () => Promise<string>;
|
|
78
|
+
};
|
|
79
|
+
declare const authMiddleware: Middleware<AuthOptions>;
|
|
80
|
+
|
|
81
|
+
type CacheOptions = {
|
|
82
|
+
ttl?: number;
|
|
83
|
+
};
|
|
84
|
+
declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
|
|
85
|
+
|
|
86
|
+
export { ApiClient, type AuthOptions, type CacheOptions, type Contracts, type EndpointDef, type EndpointDefZ, type EndpointMethods, type ErrorLike, type LoggingOptions, type Middleware, type MiddlewareContext, type MiddlewareNext, type RetryOptions, RichError, authMiddleware, cacheMiddleware, loggingMiddleware, retryMiddleware };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
|
|
4
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
5
|
+
path: string;
|
|
6
|
+
auth?: boolean;
|
|
7
|
+
request: TReq;
|
|
8
|
+
response: TRes;
|
|
9
|
+
};
|
|
10
|
+
type Contracts = {
|
|
11
|
+
[ModuleName: string]: {
|
|
12
|
+
[EndpointName: string]: EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
interface MiddlewareContext {
|
|
16
|
+
url: string;
|
|
17
|
+
init: RequestInit;
|
|
18
|
+
}
|
|
19
|
+
type MiddlewareNext = () => Promise<Response>;
|
|
20
|
+
type Middleware<Options = any> = (ctx: MiddlewareContext, next: MiddlewareNext, options?: Options) => Promise<Response>;
|
|
21
|
+
type ErrorLike = {
|
|
22
|
+
message: string;
|
|
23
|
+
status?: number;
|
|
24
|
+
code?: string;
|
|
25
|
+
[key: string]: any;
|
|
26
|
+
};
|
|
27
|
+
type EndpointDefZ = EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
|
|
28
|
+
type EndpointMethods<M extends Record<string, EndpointDefZ>> = {
|
|
29
|
+
[K in keyof M]: (input: z.infer<M[K]["request"]>) => Promise<z.infer<M[K]["response"]>>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
declare class RichError extends Error implements ErrorLike {
|
|
33
|
+
status?: number;
|
|
34
|
+
code?: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
detail?: string;
|
|
37
|
+
errors?: Record<string, string[]>;
|
|
38
|
+
constructor(error: Partial<ErrorLike> & {
|
|
39
|
+
message: string;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
43
|
+
private config;
|
|
44
|
+
private contracts;
|
|
45
|
+
private middlewares;
|
|
46
|
+
private errorHandler?;
|
|
47
|
+
private responseTransform;
|
|
48
|
+
private _modules;
|
|
49
|
+
constructor(config: {
|
|
50
|
+
baseUrl: string;
|
|
51
|
+
token?: string;
|
|
52
|
+
}, contracts: C);
|
|
53
|
+
init(): void;
|
|
54
|
+
get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
|
|
55
|
+
use<T>(middleware: Middleware<T>, options?: T): void;
|
|
56
|
+
onError(handler: (error: E) => void): void;
|
|
57
|
+
useResponseTransform(fn: (data: any) => any): void;
|
|
58
|
+
private request;
|
|
59
|
+
private createError;
|
|
60
|
+
private normalizeError;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type LoggingOptions = {
|
|
64
|
+
logRequest?: boolean;
|
|
65
|
+
logResponse?: boolean;
|
|
66
|
+
debug?: boolean;
|
|
67
|
+
};
|
|
68
|
+
declare const loggingMiddleware: Middleware<LoggingOptions>;
|
|
69
|
+
|
|
70
|
+
type RetryOptions = {
|
|
71
|
+
maxRetries?: number;
|
|
72
|
+
delay?: number;
|
|
73
|
+
};
|
|
74
|
+
declare const retryMiddleware: (options?: RetryOptions) => Middleware;
|
|
75
|
+
|
|
76
|
+
type AuthOptions = {
|
|
77
|
+
refreshToken?: () => Promise<string>;
|
|
78
|
+
};
|
|
79
|
+
declare const authMiddleware: Middleware<AuthOptions>;
|
|
80
|
+
|
|
81
|
+
type CacheOptions = {
|
|
82
|
+
ttl?: number;
|
|
83
|
+
};
|
|
84
|
+
declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
|
|
85
|
+
|
|
86
|
+
export { ApiClient, type AuthOptions, type CacheOptions, type Contracts, type EndpointDef, type EndpointDefZ, type EndpointMethods, type ErrorLike, type LoggingOptions, type Middleware, type MiddlewareContext, type MiddlewareNext, type RetryOptions, RichError, authMiddleware, cacheMiddleware, loggingMiddleware, retryMiddleware };
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,236 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
9
|
+
var __pow = Math.pow;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
7
19
|
}
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
29
|
+
for (let key of __getOwnPropNames(from))
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
31
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
32
|
+
}
|
|
33
|
+
return to;
|
|
34
|
+
};
|
|
35
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
+
var __async = (__this, __arguments, generator) => {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
var fulfilled = (value) => {
|
|
39
|
+
try {
|
|
40
|
+
step(generator.next(value));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
reject(e);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var rejected = (value) => {
|
|
46
|
+
try {
|
|
47
|
+
step(generator.throw(value));
|
|
48
|
+
} catch (e) {
|
|
49
|
+
reject(e);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
53
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/index.ts
|
|
58
|
+
var index_exports = {};
|
|
59
|
+
__export(index_exports, {
|
|
60
|
+
ApiClient: () => ApiClient,
|
|
61
|
+
RichError: () => RichError,
|
|
62
|
+
authMiddleware: () => authMiddleware,
|
|
63
|
+
cacheMiddleware: () => cacheMiddleware,
|
|
64
|
+
loggingMiddleware: () => loggingMiddleware,
|
|
65
|
+
retryMiddleware: () => retryMiddleware
|
|
66
|
+
});
|
|
67
|
+
module.exports = __toCommonJS(index_exports);
|
|
68
|
+
|
|
69
|
+
// src/client.ts
|
|
70
|
+
var RichError = class extends Error {
|
|
71
|
+
constructor(error) {
|
|
72
|
+
super(error.message);
|
|
73
|
+
Object.assign(this, error);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var ApiClient = class {
|
|
77
|
+
constructor(config, contracts) {
|
|
78
|
+
this.config = config;
|
|
79
|
+
this.contracts = contracts;
|
|
80
|
+
this.middlewares = [];
|
|
81
|
+
this.responseTransform = (d) => d;
|
|
82
|
+
}
|
|
83
|
+
init() {
|
|
84
|
+
const modules = {};
|
|
85
|
+
for (const moduleName in this.contracts) {
|
|
86
|
+
const module2 = this.contracts[moduleName];
|
|
87
|
+
modules[moduleName] = {};
|
|
88
|
+
for (const endpointName in module2) {
|
|
89
|
+
const endpoint = module2[endpointName];
|
|
90
|
+
modules[moduleName][endpointName] = (input) => this.request(endpoint, input);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
this._modules = modules;
|
|
94
|
+
}
|
|
95
|
+
get modules() {
|
|
96
|
+
return this._modules;
|
|
97
|
+
}
|
|
98
|
+
use(middleware, options) {
|
|
99
|
+
this.middlewares.push({ fn: middleware, options });
|
|
100
|
+
}
|
|
101
|
+
onError(handler) {
|
|
102
|
+
this.errorHandler = handler;
|
|
103
|
+
}
|
|
104
|
+
useResponseTransform(fn) {
|
|
105
|
+
this.responseTransform = fn;
|
|
106
|
+
}
|
|
107
|
+
request(endpoint, input) {
|
|
108
|
+
return __async(this, null, function* () {
|
|
109
|
+
var _a, _b, _c;
|
|
110
|
+
endpoint.request.parse(input);
|
|
111
|
+
if (endpoint.auth && !this.config.token) {
|
|
112
|
+
const error = this.createError({
|
|
113
|
+
message: `Missing token for ${endpoint.path}`,
|
|
114
|
+
status: 401,
|
|
115
|
+
code: "NO_TOKEN"
|
|
116
|
+
});
|
|
117
|
+
(_a = this.errorHandler) == null ? void 0 : _a.call(this, error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
const headers = { "Content-Type": "application/json" };
|
|
121
|
+
if (endpoint.auth && this.config.token)
|
|
122
|
+
headers["Authorization"] = `Bearer ${this.config.token}`;
|
|
123
|
+
const ctx = {
|
|
124
|
+
url: this.config.baseUrl + endpoint.path,
|
|
125
|
+
init: {
|
|
126
|
+
method: endpoint.method,
|
|
127
|
+
headers,
|
|
128
|
+
body: endpoint.method !== "GET" ? JSON.stringify(input) : void 0
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const runner = this.middlewares.reduceRight(
|
|
132
|
+
(next, mw) => () => mw.fn(ctx, next, mw.options),
|
|
133
|
+
() => fetch(ctx.url, ctx.init)
|
|
134
|
+
);
|
|
135
|
+
try {
|
|
136
|
+
const res = yield runner();
|
|
137
|
+
if (!res.ok) {
|
|
138
|
+
const errorData = yield res.json().catch(() => ({}));
|
|
139
|
+
const error = this.createError({
|
|
140
|
+
message: errorData.message || res.statusText,
|
|
141
|
+
status: res.status,
|
|
142
|
+
code: errorData.code,
|
|
143
|
+
title: errorData.title,
|
|
144
|
+
detail: errorData.detail,
|
|
145
|
+
errors: errorData.errors
|
|
146
|
+
});
|
|
147
|
+
(_b = this.errorHandler) == null ? void 0 : _b.call(this, error);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
const json = yield res.json();
|
|
151
|
+
return this.responseTransform(endpoint.response.parse(json));
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const error = this.normalizeError(err);
|
|
154
|
+
(_c = this.errorHandler) == null ? void 0 : _c.call(this, error);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
createError(error) {
|
|
160
|
+
return new RichError(error);
|
|
161
|
+
}
|
|
162
|
+
normalizeError(err) {
|
|
163
|
+
if (err instanceof RichError) return err;
|
|
164
|
+
return this.createError({ message: err.message || "Unknown error" });
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/middlewares/logging.ts
|
|
169
|
+
var loggingMiddleware = (ctx, next, options) => __async(null, null, function* () {
|
|
170
|
+
const { logRequest = true, logResponse = true, debug = true } = options || {};
|
|
171
|
+
if (debug && logRequest) console.log("\u27A1\uFE0F Request:", ctx.url, ctx.init);
|
|
172
|
+
const res = yield next();
|
|
173
|
+
if (debug && logResponse) console.log("\u2B05\uFE0F Response:", res.status);
|
|
174
|
+
return res;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// src/middlewares/retry.ts
|
|
178
|
+
var retryMiddleware = (options) => {
|
|
179
|
+
const { maxRetries = 3, delay = 500 } = options || {};
|
|
180
|
+
const middleware = (ctx, next) => __async(null, null, function* () {
|
|
181
|
+
let attempt = 0;
|
|
182
|
+
while (true) {
|
|
183
|
+
try {
|
|
184
|
+
return yield next();
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (attempt >= maxRetries) throw err;
|
|
187
|
+
attempt++;
|
|
188
|
+
yield new Promise((r) => setTimeout(r, delay * __pow(2, attempt)));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return middleware;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/middlewares/auth.ts
|
|
196
|
+
var authMiddleware = (ctx, next, options) => __async(null, null, function* () {
|
|
197
|
+
if (options == null ? void 0 : options.refreshToken) {
|
|
198
|
+
try {
|
|
199
|
+
const newToken = yield options.refreshToken();
|
|
200
|
+
ctx.init.headers = __spreadProps(__spreadValues({}, ctx.init.headers), {
|
|
201
|
+
Authorization: `Bearer ${newToken}`
|
|
202
|
+
});
|
|
203
|
+
} catch (e) {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return next();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// src/middlewares/cache.ts
|
|
210
|
+
var cacheMiddleware = (options = {}) => {
|
|
211
|
+
const { ttl = 6e4 } = options;
|
|
212
|
+
const cache = /* @__PURE__ */ new Map();
|
|
213
|
+
return (ctx, next) => __async(null, null, function* () {
|
|
214
|
+
if (ctx.init.method === "GET") {
|
|
215
|
+
const cached = cache.get(ctx.url);
|
|
216
|
+
const now = Date.now();
|
|
217
|
+
if (cached && cached.expires > now)
|
|
218
|
+
return new Response(JSON.stringify(cached.data));
|
|
219
|
+
const res = yield next();
|
|
220
|
+
const data = yield res.clone().json().catch(() => null);
|
|
221
|
+
if (data) cache.set(ctx.url, { data, expires: now + ttl });
|
|
222
|
+
return res;
|
|
223
|
+
}
|
|
224
|
+
return next();
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
228
|
+
0 && (module.exports = {
|
|
229
|
+
ApiClient,
|
|
230
|
+
RichError,
|
|
231
|
+
authMiddleware,
|
|
232
|
+
cacheMiddleware,
|
|
233
|
+
loggingMiddleware,
|
|
234
|
+
retryMiddleware
|
|
235
|
+
});
|
|
236
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/middlewares/logging.ts","../src/middlewares/retry.ts","../src/middlewares/auth.ts","../src/middlewares/cache.ts"],"sourcesContent":["export * from \"./types\";\nexport * from \"./client\";\nexport * from \"./middlewares/logging\";\nexport * from \"./middlewares/retry\";\nexport * from \"./middlewares/auth\";\nexport * from \"./middlewares/cache\";\n","import {\n Contracts,\n EndpointDef,\n EndpointDefZ,\n Middleware,\n ErrorLike,\n EndpointMethods,\n} from \"@/types\";\nimport { z } from \"zod\";\n\nexport class RichError extends Error implements ErrorLike {\n status?: number;\n code?: string;\n title?: string;\n detail?: string;\n errors?: Record<string, string[]>;\n\n constructor(error: Partial<ErrorLike> & { message: string }) {\n super(error.message);\n Object.assign(this, error);\n }\n}\n\nexport class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {\n private middlewares: Array<{ fn: Middleware; options?: any }> = [];\n private errorHandler?: (error: E) => void;\n private responseTransform: (data: any) => any = (d) => d;\n\n private _modules!: {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n constructor(\n private config: { baseUrl: string; token?: string },\n private contracts: C\n ) {}\n\n init() {\n const modules = {} as {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n for (const moduleName in this.contracts) {\n const module = this.contracts[moduleName];\n (modules as any)[moduleName] = {} as EndpointMethods<typeof module>;\n\n for (const endpointName in module) {\n const endpoint = module[endpointName] as EndpointDefZ;\n\n (modules as any)[moduleName][endpointName] = (\n input: z.infer<(typeof endpoint)[\"request\"]>\n ) => this.request(endpoint, input);\n }\n }\n\n this._modules = modules;\n }\n\n get modules() {\n return this._modules;\n }\n\n use<T>(middleware: Middleware<T>, options?: T) {\n this.middlewares.push({ fn: middleware, options });\n }\n\n onError(handler: (error: E) => void) {\n this.errorHandler = handler;\n }\n\n useResponseTransform(fn: (data: any) => any) {\n this.responseTransform = fn;\n }\n\n private async request<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny>(\n endpoint: EndpointDef<TReq, TRes>,\n input: z.infer<TReq>\n ): Promise<z.infer<TRes>> {\n endpoint.request.parse(input);\n\n if (endpoint.auth && !this.config.token) {\n const error = this.createError({\n message: `Missing token for ${endpoint.path}`,\n status: 401,\n code: \"NO_TOKEN\",\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n if (endpoint.auth && this.config.token)\n headers[\"Authorization\"] = `Bearer ${this.config.token}`;\n\n const ctx = {\n url: this.config.baseUrl + endpoint.path,\n init: {\n method: endpoint.method,\n headers,\n body: endpoint.method !== \"GET\" ? JSON.stringify(input) : undefined,\n },\n };\n\n const runner = this.middlewares.reduceRight(\n (next, mw) => () => mw.fn(ctx, next, mw.options),\n () => fetch(ctx.url, ctx.init)\n );\n\n try {\n const res = await runner();\n if (!res.ok) {\n const errorData = await res.json().catch(() => ({}));\n const error = this.createError({\n message: errorData.message || res.statusText,\n status: res.status,\n code: errorData.code,\n title: errorData.title,\n detail: errorData.detail,\n errors: errorData.errors,\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const json = await res.json();\n return this.responseTransform(endpoint.response.parse(json));\n } catch (err: any) {\n const error = this.normalizeError(err);\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n }\n\n private createError(error: Partial<RichError> & { message: string }) {\n return new RichError(error);\n }\n\n private normalizeError(err: any) {\n if (err instanceof RichError) return err;\n return this.createError({ message: err.message || \"Unknown error\" });\n }\n}\n","import { Middleware } from \"@/types\";\n\nexport type LoggingOptions = {\n logRequest?: boolean;\n logResponse?: boolean;\n debug?: boolean;\n};\n\nexport const loggingMiddleware: Middleware<LoggingOptions> = async (\n ctx,\n next,\n options\n) => {\n const { logRequest = true, logResponse = true, debug = true } = options || {};\n\n if (debug && logRequest) console.log(\"➡️ Request:\", ctx.url, ctx.init);\n\n const res = await next();\n\n if (debug && logResponse) console.log(\"⬅️ Response:\", res.status);\n\n return res;\n};\n","import { Middleware } from \"@/types\";\n\n\nexport type RetryOptions = {\n maxRetries?: number;\n delay?: number; // ms\n};\n\nexport const retryMiddleware = (options?: RetryOptions): Middleware => {\n const { maxRetries = 3, delay = 500 } = options || {};\n\n const middleware: Middleware = async (ctx, next) => {\n let attempt = 0;\n while (true) {\n try {\n return await next();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n attempt++;\n await new Promise((r) => setTimeout(r, delay * 2 ** attempt));\n }\n }\n };\n\n return middleware;\n};\n","import { Middleware } from \"@/types\";\n\nexport type AuthOptions = {\n refreshToken?: () => Promise<string>;\n};\n\nexport const authMiddleware: Middleware<AuthOptions> = async (\n ctx,\n next,\n options\n) => {\n if (options?.refreshToken) {\n try {\n const newToken = await options.refreshToken();\n ctx.init.headers = {\n ...ctx.init.headers,\n Authorization: `Bearer ${newToken}`,\n };\n } catch {}\n }\n\n return next();\n};\n","import { MiddlewareContext, MiddlewareNext } from \"@/types\";\n\nexport type CacheOptions = { ttl?: number };\n\nexport const cacheMiddleware = (options: CacheOptions = {}) => {\n const { ttl = 60000 } = options;\n const cache = new Map<string, { data: any; expires: number }>();\n\n return async (ctx: MiddlewareContext, next: MiddlewareNext) => {\n if (ctx.init.method === \"GET\") {\n const cached = cache.get(ctx.url);\n const now = Date.now();\n if (cached && cached.expires > now)\n return new Response(JSON.stringify(cached.data));\n\n const res = await next();\n const data = await res\n .clone()\n .json()\n .catch(() => null);\n if (data) cache.set(ctx.url, { data, expires: now + ttl });\n return res;\n }\n return next();\n };\n};\n"],"mappings":"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","names":["module"]}
|