@tahanabavi/typefetch 1.1.1 → 1.2.1
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 +149 -236
- package/dist/index.d.mts +107 -4
- package/dist/index.d.ts +107 -4
- package/dist/index.js +138 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +138 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Generic request schema:
|
|
5
|
+
*
|
|
6
|
+
* request: z.object({
|
|
7
|
+
* path: z.object({ ... }).optional(),
|
|
8
|
+
* query: z.object({ ... }).optional(),
|
|
9
|
+
* body: z.object({ ... }).optional(),
|
|
10
|
+
* })
|
|
11
|
+
*/
|
|
12
|
+
type RequestSchema = z.ZodTypeAny;
|
|
13
|
+
type ResponseSchema = z.ZodTypeAny;
|
|
14
|
+
type EndpointDef<TReq extends RequestSchema, TRes extends ResponseSchema> = {
|
|
4
15
|
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
5
16
|
path: string;
|
|
6
17
|
auth?: boolean;
|
|
@@ -10,7 +21,7 @@ type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
|
|
|
10
21
|
};
|
|
11
22
|
type Contracts = {
|
|
12
23
|
[ModuleName: string]: {
|
|
13
|
-
[EndpointName: string]: EndpointDef<
|
|
24
|
+
[EndpointName: string]: EndpointDef<RequestSchema, ResponseSchema>;
|
|
14
25
|
};
|
|
15
26
|
};
|
|
16
27
|
interface MiddlewareContext {
|
|
@@ -25,7 +36,14 @@ type ErrorLike = {
|
|
|
25
36
|
code?: string;
|
|
26
37
|
[key: string]: any;
|
|
27
38
|
};
|
|
28
|
-
type EndpointDefZ = EndpointDef<
|
|
39
|
+
type EndpointDefZ = EndpointDef<RequestSchema, ResponseSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* For each endpoint we expose a method:
|
|
42
|
+
* (input: z.infer<Endpoint["request"]>) => Promise<z.infer<Endpoint["response"]>>
|
|
43
|
+
*
|
|
44
|
+
* So VSCode will show you:
|
|
45
|
+
* { path?: { ... }, query?: { ... }, body?: { ... } }
|
|
46
|
+
*/
|
|
29
47
|
type EndpointMethods<M extends Record<string, EndpointDefZ>> = {
|
|
30
48
|
[K in keyof M]: (input: z.infer<M[K]["request"]>) => Promise<z.infer<M[K]["response"]>>;
|
|
31
49
|
};
|
|
@@ -41,6 +59,18 @@ declare class RichError extends Error implements ErrorLike {
|
|
|
41
59
|
message: string;
|
|
42
60
|
});
|
|
43
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Strongly-typed HTTP client built from Zod contracts.
|
|
64
|
+
*
|
|
65
|
+
* Features:
|
|
66
|
+
* - Type-safe request & response based on Zod schemas
|
|
67
|
+
* - Path/query/body support via { path?, query?, body? } shape
|
|
68
|
+
* - Backwards compatible with flat request bodies
|
|
69
|
+
* - Pluggable middleware pipeline
|
|
70
|
+
* - Token and tokenProvider support
|
|
71
|
+
* - Mock mode with configurable delay
|
|
72
|
+
* - Optional response wrapper for APIs that nest data
|
|
73
|
+
*/
|
|
44
74
|
declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
45
75
|
private config;
|
|
46
76
|
private contracts;
|
|
@@ -62,22 +92,95 @@ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
|
62
92
|
max: number;
|
|
63
93
|
};
|
|
64
94
|
}, contracts: C);
|
|
95
|
+
/**
|
|
96
|
+
* Builds the strongly-typed `modules` API from the provided contracts.
|
|
97
|
+
* Must be called once after constructing the client.
|
|
98
|
+
*/
|
|
65
99
|
init(): void;
|
|
100
|
+
/**
|
|
101
|
+
* Type-safe entrypoint for calling API endpoints.
|
|
102
|
+
* Populated by `init()` based on the `contracts` passed to the constructor.
|
|
103
|
+
*/
|
|
66
104
|
get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
|
|
105
|
+
/**
|
|
106
|
+
* Registers a middleware in the pipeline.
|
|
107
|
+
* Middlewares are executed in reverse order of registration.
|
|
108
|
+
*/
|
|
67
109
|
use<T>(middleware: Middleware<T>, options?: T): void;
|
|
110
|
+
/**
|
|
111
|
+
* Registers a global error handler.
|
|
112
|
+
* The handler is invoked for normalized errors before they are re-thrown.
|
|
113
|
+
*/
|
|
68
114
|
onError(handler: (error: E) => void): void;
|
|
115
|
+
/**
|
|
116
|
+
* Registers a transformation function applied to all successful responses
|
|
117
|
+
* after Zod parsing.
|
|
118
|
+
*/
|
|
69
119
|
useResponseTransform(fn: (data: any) => any): void;
|
|
120
|
+
/**
|
|
121
|
+
* Enables or disables mock mode. When enabled, endpoints with `mockData`
|
|
122
|
+
* return mocked responses instead of performing network requests.
|
|
123
|
+
*/
|
|
70
124
|
setMockMode(enabled: boolean, delay?: {
|
|
71
125
|
min: number;
|
|
72
126
|
max: number;
|
|
73
127
|
}): void;
|
|
128
|
+
/**
|
|
129
|
+
* Registers a schema wrapper for APIs that wrap data in an envelope.
|
|
130
|
+
* Example: { success, data, message, code, ... }.
|
|
131
|
+
*/
|
|
74
132
|
setResponseWrapper(wrapper: (successResponse: z.ZodTypeAny) => z.ZodTypeAny): void;
|
|
133
|
+
/**
|
|
134
|
+
* Sets or updates the token provider used for authenticated endpoints.
|
|
135
|
+
* Overrides any static token provided in the constructor.
|
|
136
|
+
*/
|
|
75
137
|
setTokenProvider(provider: TokenProvider): void;
|
|
138
|
+
/**
|
|
139
|
+
* Returns the current token, preferring the tokenProvider if present,
|
|
140
|
+
* otherwise falling back to the static token from the constructor.
|
|
141
|
+
*/
|
|
76
142
|
getCurrentToken(): Promise<string | undefined>;
|
|
143
|
+
/**
|
|
144
|
+
* Executes a single endpoint request.
|
|
145
|
+
*
|
|
146
|
+
* Expected request shape (new style):
|
|
147
|
+
* z.object({
|
|
148
|
+
* path: z.object({...}).optional(),
|
|
149
|
+
* query: z.object({...}).optional(),
|
|
150
|
+
* body: z.any().optional(),
|
|
151
|
+
* })
|
|
152
|
+
*
|
|
153
|
+
* If the parsed request does not contain `path`, `query` or `body`,
|
|
154
|
+
* the entire input is treated as the legacy flat request body.
|
|
155
|
+
*/
|
|
77
156
|
private request;
|
|
157
|
+
/**
|
|
158
|
+
* Builds the effective URL and request body for an endpoint.
|
|
159
|
+
*
|
|
160
|
+
* - Legacy mode: if the input does not contain `path`, `query` or `body`,
|
|
161
|
+
* the entire input is used as the JSON request body for non-GET methods.
|
|
162
|
+
*
|
|
163
|
+
* - New mode: uses `path` to interpolate `:param` segments, `query` to
|
|
164
|
+
* construct the query string, and `body` as the JSON payload.
|
|
165
|
+
*/
|
|
166
|
+
private buildUrlAndBody;
|
|
167
|
+
/**
|
|
168
|
+
* Returns a mocked response based on `endpoint.mockData`,
|
|
169
|
+
* respecting the configured mock delay and response wrapper.
|
|
170
|
+
*/
|
|
78
171
|
private handleMockRequest;
|
|
172
|
+
/**
|
|
173
|
+
* Returns a random delay in milliseconds within the current mock delay range.
|
|
174
|
+
*/
|
|
79
175
|
private getRandomDelay;
|
|
176
|
+
/**
|
|
177
|
+
* Creates a RichError instance from a partial error description.
|
|
178
|
+
*/
|
|
80
179
|
private createError;
|
|
180
|
+
/**
|
|
181
|
+
* Normalizes unknown errors into a RichError instance.
|
|
182
|
+
* Zod validation errors are converted into a standardized validation error.
|
|
183
|
+
*/
|
|
81
184
|
private normalizeError;
|
|
82
185
|
}
|
|
83
186
|
|
|
@@ -104,4 +207,4 @@ type CacheOptions = {
|
|
|
104
207
|
};
|
|
105
208
|
declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
|
|
106
209
|
|
|
107
|
-
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, type TokenProvider, authMiddleware, cacheMiddleware, loggingMiddleware, retryMiddleware };
|
|
210
|
+
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 RequestSchema, type ResponseSchema, type RetryOptions, RichError, type TokenProvider, authMiddleware, cacheMiddleware, loggingMiddleware, retryMiddleware };
|
package/dist/index.js
CHANGED
|
@@ -4031,6 +4031,10 @@ var ApiClient = class {
|
|
|
4031
4031
|
this.mockDelay = config.mockDelay || { min: 100, max: 1e3 };
|
|
4032
4032
|
this.tokenProvider = config.tokenProvider;
|
|
4033
4033
|
}
|
|
4034
|
+
/**
|
|
4035
|
+
* Builds the strongly-typed `modules` API from the provided contracts.
|
|
4036
|
+
* Must be called once after constructing the client.
|
|
4037
|
+
*/
|
|
4034
4038
|
init() {
|
|
4035
4039
|
const modules = {};
|
|
4036
4040
|
for (const moduleName in this.contracts) {
|
|
@@ -4043,30 +4047,62 @@ var ApiClient = class {
|
|
|
4043
4047
|
}
|
|
4044
4048
|
this._modules = modules;
|
|
4045
4049
|
}
|
|
4050
|
+
/**
|
|
4051
|
+
* Type-safe entrypoint for calling API endpoints.
|
|
4052
|
+
* Populated by `init()` based on the `contracts` passed to the constructor.
|
|
4053
|
+
*/
|
|
4046
4054
|
get modules() {
|
|
4047
4055
|
return this._modules;
|
|
4048
4056
|
}
|
|
4057
|
+
/**
|
|
4058
|
+
* Registers a middleware in the pipeline.
|
|
4059
|
+
* Middlewares are executed in reverse order of registration.
|
|
4060
|
+
*/
|
|
4049
4061
|
use(middleware, options) {
|
|
4050
4062
|
this.middlewares.push({ fn: middleware, options });
|
|
4051
4063
|
}
|
|
4064
|
+
/**
|
|
4065
|
+
* Registers a global error handler.
|
|
4066
|
+
* The handler is invoked for normalized errors before they are re-thrown.
|
|
4067
|
+
*/
|
|
4052
4068
|
onError(handler) {
|
|
4053
4069
|
this.errorHandler = handler;
|
|
4054
4070
|
}
|
|
4071
|
+
/**
|
|
4072
|
+
* Registers a transformation function applied to all successful responses
|
|
4073
|
+
* after Zod parsing.
|
|
4074
|
+
*/
|
|
4055
4075
|
useResponseTransform(fn) {
|
|
4056
4076
|
this.responseTransform = fn;
|
|
4057
4077
|
}
|
|
4078
|
+
/**
|
|
4079
|
+
* Enables or disables mock mode. When enabled, endpoints with `mockData`
|
|
4080
|
+
* return mocked responses instead of performing network requests.
|
|
4081
|
+
*/
|
|
4058
4082
|
setMockMode(enabled, delay) {
|
|
4059
4083
|
this.useMockData = enabled;
|
|
4060
4084
|
if (delay) {
|
|
4061
4085
|
this.mockDelay = delay;
|
|
4062
4086
|
}
|
|
4063
4087
|
}
|
|
4088
|
+
/**
|
|
4089
|
+
* Registers a schema wrapper for APIs that wrap data in an envelope.
|
|
4090
|
+
* Example: { success, data, message, code, ... }.
|
|
4091
|
+
*/
|
|
4064
4092
|
setResponseWrapper(wrapper) {
|
|
4065
4093
|
this.responseWrapper = wrapper;
|
|
4066
4094
|
}
|
|
4095
|
+
/**
|
|
4096
|
+
* Sets or updates the token provider used for authenticated endpoints.
|
|
4097
|
+
* Overrides any static token provided in the constructor.
|
|
4098
|
+
*/
|
|
4067
4099
|
setTokenProvider(provider) {
|
|
4068
4100
|
this.tokenProvider = provider;
|
|
4069
4101
|
}
|
|
4102
|
+
/**
|
|
4103
|
+
* Returns the current token, preferring the tokenProvider if present,
|
|
4104
|
+
* otherwise falling back to the static token from the constructor.
|
|
4105
|
+
*/
|
|
4070
4106
|
getCurrentToken() {
|
|
4071
4107
|
return __async(this, null, function* () {
|
|
4072
4108
|
if (this.tokenProvider) {
|
|
@@ -4075,10 +4111,23 @@ var ApiClient = class {
|
|
|
4075
4111
|
return this.config.token;
|
|
4076
4112
|
});
|
|
4077
4113
|
}
|
|
4114
|
+
/**
|
|
4115
|
+
* Executes a single endpoint request.
|
|
4116
|
+
*
|
|
4117
|
+
* Expected request shape (new style):
|
|
4118
|
+
* z.object({
|
|
4119
|
+
* path: z.object({...}).optional(),
|
|
4120
|
+
* query: z.object({...}).optional(),
|
|
4121
|
+
* body: z.any().optional(),
|
|
4122
|
+
* })
|
|
4123
|
+
*
|
|
4124
|
+
* If the parsed request does not contain `path`, `query` or `body`,
|
|
4125
|
+
* the entire input is treated as the legacy flat request body.
|
|
4126
|
+
*/
|
|
4078
4127
|
request(endpoint, input) {
|
|
4079
4128
|
return __async(this, null, function* () {
|
|
4080
4129
|
var _a, _b, _c, _d;
|
|
4081
|
-
endpoint.request.parse(input);
|
|
4130
|
+
const parsedInput = endpoint.request.parse(input);
|
|
4082
4131
|
if (this.useMockData && endpoint.mockData) {
|
|
4083
4132
|
return this.handleMockRequest(endpoint);
|
|
4084
4133
|
}
|
|
@@ -4099,12 +4148,13 @@ var ApiClient = class {
|
|
|
4099
4148
|
if (endpoint.auth && token) {
|
|
4100
4149
|
headers["Authorization"] = `Bearer ${token}`;
|
|
4101
4150
|
}
|
|
4151
|
+
const { url, body } = this.buildUrlAndBody(endpoint, parsedInput);
|
|
4102
4152
|
const ctx = {
|
|
4103
|
-
url
|
|
4153
|
+
url,
|
|
4104
4154
|
init: {
|
|
4105
4155
|
method: endpoint.method,
|
|
4106
4156
|
headers,
|
|
4107
|
-
body
|
|
4157
|
+
body
|
|
4108
4158
|
}
|
|
4109
4159
|
};
|
|
4110
4160
|
const runner = this.middlewares.reduceRight(
|
|
@@ -4149,6 +4199,81 @@ var ApiClient = class {
|
|
|
4149
4199
|
}
|
|
4150
4200
|
});
|
|
4151
4201
|
}
|
|
4202
|
+
/**
|
|
4203
|
+
* Builds the effective URL and request body for an endpoint.
|
|
4204
|
+
*
|
|
4205
|
+
* - Legacy mode: if the input does not contain `path`, `query` or `body`,
|
|
4206
|
+
* the entire input is used as the JSON request body for non-GET methods.
|
|
4207
|
+
*
|
|
4208
|
+
* - New mode: uses `path` to interpolate `:param` segments, `query` to
|
|
4209
|
+
* construct the query string, and `body` as the JSON payload.
|
|
4210
|
+
*/
|
|
4211
|
+
buildUrlAndBody(endpoint, parsedInput) {
|
|
4212
|
+
const isObject = typeof parsedInput === "object" && parsedInput !== null;
|
|
4213
|
+
const hasNewShape = isObject && ("path" in parsedInput || "query" in parsedInput || "body" in parsedInput);
|
|
4214
|
+
if (!hasNewShape) {
|
|
4215
|
+
const url2 = this.config.baseUrl + endpoint.path;
|
|
4216
|
+
let requestBody2 = void 0;
|
|
4217
|
+
if (endpoint.method !== "GET") {
|
|
4218
|
+
requestBody2 = JSON.stringify(parsedInput);
|
|
4219
|
+
}
|
|
4220
|
+
return { url: url2, body: requestBody2 };
|
|
4221
|
+
}
|
|
4222
|
+
const { path, query, body } = parsedInput;
|
|
4223
|
+
let url = this.config.baseUrl + endpoint.path;
|
|
4224
|
+
if (path) {
|
|
4225
|
+
for (const [key, value] of Object.entries(path)) {
|
|
4226
|
+
if (value === void 0 || value === null) continue;
|
|
4227
|
+
const token = `:${key}`;
|
|
4228
|
+
if (!url.includes(token)) {
|
|
4229
|
+
continue;
|
|
4230
|
+
}
|
|
4231
|
+
url = url.replace(token, encodeURIComponent(String(value)));
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
const missingTokens = Array.from(url.matchAll(/:([A-Za-z0-9_]+)/g)).map(
|
|
4235
|
+
(m) => m[1]
|
|
4236
|
+
);
|
|
4237
|
+
if (missingTokens.length > 0) {
|
|
4238
|
+
throw this.createError({
|
|
4239
|
+
message: `Missing path params for placeholders: ${missingTokens.join(
|
|
4240
|
+
", "
|
|
4241
|
+
)} in "${endpoint.path}"`,
|
|
4242
|
+
code: "MISSING_PATH_PARAMS"
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
if (query) {
|
|
4246
|
+
const searchParams = new URLSearchParams();
|
|
4247
|
+
for (const [key, value] of Object.entries(query)) {
|
|
4248
|
+
if (value === void 0 || value === null) continue;
|
|
4249
|
+
if (Array.isArray(value)) {
|
|
4250
|
+
for (const v of value) {
|
|
4251
|
+
if (v === void 0 || v === null) continue;
|
|
4252
|
+
searchParams.append(key, String(v));
|
|
4253
|
+
}
|
|
4254
|
+
} else if (typeof value === "object") {
|
|
4255
|
+
searchParams.append(key, JSON.stringify(value));
|
|
4256
|
+
} else {
|
|
4257
|
+
searchParams.append(key, String(value));
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
const qs = searchParams.toString();
|
|
4261
|
+
if (qs) {
|
|
4262
|
+
url += (url.includes("?") ? "&" : "?") + qs;
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
let requestBody = void 0;
|
|
4266
|
+
if (endpoint.method !== "GET") {
|
|
4267
|
+
if (typeof body !== "undefined" && body !== null) {
|
|
4268
|
+
requestBody = JSON.stringify(body);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
return { url, body: requestBody };
|
|
4272
|
+
}
|
|
4273
|
+
/**
|
|
4274
|
+
* Returns a mocked response based on `endpoint.mockData`,
|
|
4275
|
+
* respecting the configured mock delay and response wrapper.
|
|
4276
|
+
*/
|
|
4152
4277
|
handleMockRequest(endpoint) {
|
|
4153
4278
|
return __async(this, null, function* () {
|
|
4154
4279
|
const delay = this.getRandomDelay();
|
|
@@ -4175,14 +4300,24 @@ var ApiClient = class {
|
|
|
4175
4300
|
return this.responseTransform(endpoint.response.parse(mockData));
|
|
4176
4301
|
});
|
|
4177
4302
|
}
|
|
4303
|
+
/**
|
|
4304
|
+
* Returns a random delay in milliseconds within the current mock delay range.
|
|
4305
|
+
*/
|
|
4178
4306
|
getRandomDelay() {
|
|
4179
4307
|
return Math.floor(
|
|
4180
4308
|
Math.random() * (this.mockDelay.max - this.mockDelay.min + 1)
|
|
4181
4309
|
) + this.mockDelay.min;
|
|
4182
4310
|
}
|
|
4311
|
+
/**
|
|
4312
|
+
* Creates a RichError instance from a partial error description.
|
|
4313
|
+
*/
|
|
4183
4314
|
createError(error) {
|
|
4184
4315
|
return new RichError(error);
|
|
4185
4316
|
}
|
|
4317
|
+
/**
|
|
4318
|
+
* Normalizes unknown errors into a RichError instance.
|
|
4319
|
+
* Zod validation errors are converted into a standardized validation error.
|
|
4320
|
+
*/
|
|
4186
4321
|
normalizeError(err) {
|
|
4187
4322
|
if (err instanceof RichError) return err;
|
|
4188
4323
|
if (err instanceof external_exports.ZodError) {
|