@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/dist/index.d.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  import { z } from 'zod';
2
2
 
3
- type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
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<z.ZodTypeAny, z.ZodTypeAny>;
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<z.ZodTypeAny, z.ZodTypeAny>;
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: this.config.baseUrl + endpoint.path,
4153
+ url,
4104
4154
  init: {
4105
4155
  method: endpoint.method,
4106
4156
  headers,
4107
- body: endpoint.method !== "GET" ? JSON.stringify(input) : void 0
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) {