zlient 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/README.md ADDED
@@ -0,0 +1,292 @@
1
+ # zlient
2
+
3
+ A type-safe HTTP client framework with Zod validation for building robust API clients.
4
+
5
+ ## Features
6
+
7
+ - 🔒 **Type-safe**: Full TypeScript support with automatic type inference
8
+ - ✅ **Runtime validation**: Zod schemas for request/response validation
9
+ - 🔄 **Retry logic**: Built-in configurable retry strategies
10
+ - 🎯 **Authentication**: Multiple auth providers (API Key, Bearer Token, Custom)
11
+ - 🪝 **Interceptors**: Before request and after response hooks
12
+ - ⏱️ **Timeouts**: Configurable request timeouts
13
+ - 📦 **Multiple endpoints**: Easy service separation with base URL mapping
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install zlient zod
19
+ # or
20
+ yarn add zlient zod
21
+ # or
22
+ pnpm add zlient zod
23
+ # or
24
+ bun add zlient zod
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```typescript
30
+ import { HttpClient, BaseEndpoint, BearerTokenAuth } from 'zlient';
31
+ import { z } from 'zod';
32
+
33
+ // Define your schemas
34
+ const UserSchema = z.object({
35
+ id: z.number(),
36
+ name: z.string(),
37
+ email: z.string().email(),
38
+ });
39
+
40
+ // Create a client
41
+ const client = new HttpClient({
42
+ baseUrls: {
43
+ default: 'https://api.example.com',
44
+ },
45
+ auth: new BearerTokenAuth(() => 'your-token'),
46
+ });
47
+
48
+ // Define an endpoint
49
+ class GetUserEndpoint extends BaseEndpoint<typeof z.undefined, typeof UserSchema> {
50
+ protected method = 'GET' as const;
51
+ protected path = (params: { id: number }) => `/users/${params.id}`;
52
+
53
+ constructor(client: HttpClient) {
54
+ super(client, { responseSchema: UserSchema });
55
+ }
56
+ }
57
+
58
+ // Use the endpoint
59
+ const getUserEndpoint = new GetUserEndpoint(client);
60
+ const user = await getUserEndpoint.call({ id: 1 });
61
+ console.log(user); // Fully typed!
62
+ ```
63
+
64
+ ## Core Concepts
65
+
66
+ ### HttpClient
67
+
68
+ The main HTTP client that handles requests, retries, authentication, and interceptors.
69
+
70
+ ```typescript
71
+ import { HttpClient, NoAuth } from 'zlient';
72
+
73
+ const client = new HttpClient({
74
+ baseUrls: {
75
+ default: 'https://api.example.com',
76
+ v2: 'https://api-v2.example.com',
77
+ },
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ retry: {
82
+ maxRetries: 3,
83
+ baseDelayMs: 1000,
84
+ jitter: 0.2,
85
+ },
86
+ timeout: {
87
+ requestTimeoutMs: 30000,
88
+ },
89
+ auth: new NoAuth(),
90
+ });
91
+ ```
92
+
93
+ ### Authentication
94
+
95
+ #### Bearer Token
96
+
97
+ ```typescript
98
+ import { BearerTokenAuth } from 'zlient';
99
+
100
+ const auth = new BearerTokenAuth(async () => {
101
+ // Fetch token from your auth service
102
+ return await getAccessToken();
103
+ });
104
+
105
+ client.setAuth(auth);
106
+ ```
107
+
108
+ #### API Key
109
+
110
+ ```typescript
111
+ import { ApiKeyAuth } from 'zlient';
112
+
113
+ // Header-based
114
+ const auth = new ApiKeyAuth({
115
+ header: 'X-API-Key',
116
+ value: 'your-api-key',
117
+ });
118
+
119
+ // Query parameter-based
120
+ const auth = new ApiKeyAuth({
121
+ query: 'apiKey',
122
+ value: 'your-api-key',
123
+ });
124
+ ```
125
+
126
+ #### Custom Auth
127
+
128
+ ```typescript
129
+ import { AuthProvider } from 'zlient';
130
+
131
+ class CustomAuth implements AuthProvider {
132
+ async apply({ init }) {
133
+ init.headers = {
134
+ ...init.headers,
135
+ 'X-Custom-Auth': 'custom-value',
136
+ };
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### BaseEndpoint
142
+
143
+ Create type-safe endpoints with automatic validation:
144
+
145
+ ```typescript
146
+ import { BaseEndpoint, HttpClient } from 'zlient';
147
+ import { z } from 'zod';
148
+
149
+ const CreateUserSchema = z.object({
150
+ name: z.string(),
151
+ email: z.string().email(),
152
+ });
153
+
154
+ const UserResponseSchema = z.object({
155
+ id: z.number(),
156
+ name: z.string(),
157
+ email: z.string().email(),
158
+ createdAt: z.string().datetime(),
159
+ });
160
+
161
+ class CreateUserEndpoint extends BaseEndpoint<
162
+ typeof CreateUserSchema,
163
+ typeof UserResponseSchema
164
+ > {
165
+ protected method = 'POST' as const;
166
+ protected path = '/users';
167
+
168
+ constructor(client: HttpClient) {
169
+ super(client, {
170
+ requestSchema: CreateUserSchema,
171
+ responseSchema: UserResponseSchema,
172
+ });
173
+ }
174
+ }
175
+
176
+ // Usage
177
+ const endpoint = new CreateUserEndpoint(client);
178
+ const user = await endpoint.call({
179
+ name: 'John Doe',
180
+ email: 'john@example.com',
181
+ });
182
+ ```
183
+
184
+ ### Interceptors
185
+
186
+ Add hooks to inspect or modify requests and responses:
187
+
188
+ ```typescript
189
+ const client = new HttpClient({
190
+ baseUrls: { default: 'https://api.example.com' },
191
+ interceptors: {
192
+ beforeRequest: [
193
+ async ({ url, init }) => {
194
+ console.log('Making request to:', url);
195
+ },
196
+ ],
197
+ afterResponse: [
198
+ async ({ request, response, parsed }) => {
199
+ console.log('Response received:', response.status);
200
+ },
201
+ ],
202
+ },
203
+ });
204
+ ```
205
+
206
+ ### Common Schemas
207
+
208
+ The package includes common reusable schemas:
209
+
210
+ ```typescript
211
+ import { Id, Timestamps, Meta, ApiErrorSchema, Envelope } from 'zlient';
212
+
213
+ // Use in your schemas
214
+ const MyEntitySchema = z.object({
215
+ id: Id,
216
+ name: z.string(),
217
+ ...Timestamps.shape,
218
+ });
219
+
220
+ // Wrap responses in an envelope
221
+ const MyResponseSchema = Envelope(MyEntitySchema);
222
+ ```
223
+
224
+ ## Advanced Usage
225
+
226
+ ### Multiple Base URLs
227
+
228
+ ```typescript
229
+ const client = new HttpClient({
230
+ baseUrls: {
231
+ default: 'https://api.example.com',
232
+ auth: 'https://auth.example.com',
233
+ cdn: 'https://cdn.example.com',
234
+ },
235
+ });
236
+
237
+ // Use specific base URL for a request
238
+ await endpoint.call(data, { baseUrlKey: 'auth' });
239
+ ```
240
+
241
+ ### Request Options
242
+
243
+ ```typescript
244
+ await endpoint.call(data, {
245
+ headers: { 'X-Custom-Header': 'value' },
246
+ baseUrlKey: 'v2',
247
+ signal: abortController.signal,
248
+ query: { filter: 'active', page: 1 },
249
+ });
250
+ ```
251
+
252
+ ### Error Handling
253
+
254
+ ```typescript
255
+ import { ApiError } from 'zlient';
256
+
257
+ try {
258
+ await endpoint.call(data);
259
+ } catch (error) {
260
+ if (error instanceof ApiError) {
261
+ console.error('API Error:', error.message);
262
+ console.error('Status:', error.status);
263
+ console.error('Details:', error.details);
264
+
265
+ if (error.zodError) {
266
+ console.error('Validation errors:', error.zodError.errors);
267
+ }
268
+ }
269
+ }
270
+ ```
271
+
272
+ ## Building from Source
273
+
274
+ ```bash
275
+ # Clone the repository
276
+ git clone <your-repo-url>
277
+ cd zlient
278
+
279
+ # Install dependencies
280
+ bun install
281
+
282
+ # Build the package
283
+ bun run build
284
+ ```
285
+
286
+ ## License
287
+
288
+ MIT
289
+
290
+ ## Contributing
291
+
292
+ Contributions are welcome! Please open an issue or submit a pull request.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { RequestOptions as ReqOpts } from "./types";
2
+ export interface AuthProvider {
3
+ /**
4
+ * Apply authentication to the outgoing request.
5
+ * Called after SDK headers are assembled, but before request is sent.
6
+ */
7
+ apply(req: {
8
+ url: string;
9
+ init: RequestInit;
10
+ options?: ReqOpts;
11
+ }): Promise<void> | void;
12
+ }
13
+ export declare class NoAuth implements AuthProvider {
14
+ apply(): Promise<void>;
15
+ }
16
+ export declare class ApiKeyAuth implements AuthProvider {
17
+ private opts;
18
+ constructor(opts: {
19
+ header?: string;
20
+ query?: string;
21
+ value: string;
22
+ });
23
+ apply({ url, init }: {
24
+ url: string;
25
+ init: RequestInit;
26
+ }): void;
27
+ }
28
+ export declare class BearerTokenAuth implements AuthProvider {
29
+ private getToken;
30
+ constructor(getToken: () => Promise<string> | string);
31
+ apply({ init }: {
32
+ url: string;
33
+ init: RequestInit;
34
+ }): Promise<void>;
35
+ }
36
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/api/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzD,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3F;AAED,qBAAa,MAAO,YAAW,YAAY;IACjC,KAAK;CACd;AAED,qBAAa,UAAW,YAAW,YAAY;IAC/B,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAC5E,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE;CAS1D;AAED,qBAAa,eAAgB,YAAW,YAAY;IACpC,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM;IACtD,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE;CAI3D"}
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { HttpClient } from "../http/HttpClient";
3
+ import { HTTPMethod, RequestOptions } from "../types";
4
+ /**
5
+ * Generic, strongly-typed endpoint with Zod schemas for request and response.
6
+ */
7
+ export declare abstract class BaseEndpoint<ReqSchema extends z.ZodTypeAny, ResSchema extends z.ZodTypeAny> {
8
+ protected client: HttpClient;
9
+ protected abstract readonly method: keyof typeof HTTPMethod;
10
+ protected abstract readonly path: string | ((params: any) => string);
11
+ protected readonly requestSchema?: ReqSchema;
12
+ protected readonly responseSchema: ResSchema;
13
+ constructor(client: HttpClient, cfg: {
14
+ requestSchema?: ReqSchema;
15
+ responseSchema: ResSchema;
16
+ });
17
+ /**
18
+ * Call the endpoint with strong typing derived from schemas.
19
+ */
20
+ call(args: z.infer<ReqSchema>, options?: RequestOptions): Promise<z.infer<ResSchema>>;
21
+ }
22
+ //# sourceMappingURL=BaseEndpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseEndpoint.d.ts","sourceRoot":"","sources":["../../src/api/endpoint/BaseEndpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAGtD;;GAEG;AACH,8BAAsB,YAAY,CAAC,SAAS,SAAS,CAAC,CAAC,UAAU,EAAE,SAAS,SAAS,CAAC,CAAC,UAAU;IAMjF,SAAS,CAAC,MAAM,EAAE,UAAU;IALxC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,UAAU,CAAC;IAC5D,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;IACrE,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC;gBAEvB,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE;QAAE,aAAa,CAAC,EAAE,SAAS,CAAC;QAAC,cAAc,EAAE,SAAS,CAAA;KAAE;IAKvG;;OAEG;IACG,IAAI,CACN,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,EACxB,OAAO,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;CAkBjC"}
@@ -0,0 +1,25 @@
1
+ import { ClientOptions, HTTPMethod, RequestOptions } from "../types";
2
+ import type { AuthProvider } from "../auth";
3
+ export declare class HttpClient {
4
+ private fetchImpl;
5
+ private baseUrls;
6
+ private headers;
7
+ private interceptors;
8
+ private retry;
9
+ private timeoutMs?;
10
+ private auth;
11
+ constructor(opts: ClientOptions & {
12
+ auth?: AuthProvider;
13
+ });
14
+ setAuth(auth: AuthProvider): void;
15
+ private resolveBaseUrl;
16
+ private sleep;
17
+ private withRetry;
18
+ private runBeforeHooks;
19
+ private runAfterHooks;
20
+ request<T = unknown>(method: keyof typeof HTTPMethod, path: string, body?: unknown, options?: RequestOptions): Promise<{
21
+ data: T;
22
+ response: Response;
23
+ }>;
24
+ }
25
+ //# sourceMappingURL=HttpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpClient.d.ts","sourceRoot":"","sources":["../../src/api/http/HttpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,aAAa,EAAa,UAAU,EAAgB,cAAc,EAAgC,MAAM,UAAU,CAAC;AACtI,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,qBAAa,UAAU;IACnB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,IAAI,CAAe;gBAEf,IAAI,EAAE,aAAa,GAAG;QAAE,IAAI,CAAC,EAAE,YAAY,CAAA;KAAE;IAgBzD,OAAO,CAAC,IAAI,EAAE,YAAY;IAE1B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,KAAK;YAEC,SAAS;YAiBT,cAAc;YAMd,aAAa;IAMrB,OAAO,CAAC,CAAC,GAAG,OAAO,EACrB,MAAM,EAAE,MAAM,OAAO,UAAU,EAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC;CA2D9C"}
package/dist/index.cjs ADDED
@@ -0,0 +1,352 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (all) => {
12
+ let target = {};
13
+ for (var name in all) __defProp(target, name, {
14
+ get: all[name],
15
+ enumerable: true
16
+ });
17
+ return target;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
21
+ key = keys[i];
22
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
23
+ get: ((k) => from[k]).bind(null, key),
24
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
25
+ });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
30
+ value: mod,
31
+ enumerable: true
32
+ }) : target, mod));
33
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
+
35
+ //#endregion
36
+ let zod = require("zod");
37
+ zod = __toESM(zod);
38
+
39
+ //#region src/api/types.ts
40
+ const HTTPMethod = {
41
+ GET: "GET",
42
+ POST: "POST",
43
+ PUT: "PUT",
44
+ PATCH: "PATCH",
45
+ DELETE: "DELETE",
46
+ HEAD: "HEAD",
47
+ OPTIONS: "OPTIONS"
48
+ };
49
+ var ApiError = class extends Error {
50
+ status;
51
+ details;
52
+ zodError;
53
+ constructor(message, options) {
54
+ super(message);
55
+ this.name = "ApiError";
56
+ this.status = options?.status;
57
+ this.details = options?.details;
58
+ this.cause = options?.cause;
59
+ this.zodError = options?.zodError;
60
+ }
61
+ };
62
+ const PaginationSchema = zod.z.object({
63
+ items: zod.z.array(zod.z.unknown()),
64
+ total: zod.z.number().int().nonnegative(),
65
+ page: zod.z.number().int().nonnegative(),
66
+ pageSize: zod.z.number().int().positive()
67
+ });
68
+ function toQueryString(q) {
69
+ if (!q) return "";
70
+ if (q instanceof URLSearchParams) {
71
+ const s$1 = q.toString();
72
+ return s$1 ? `?${s$1}` : "";
73
+ }
74
+ const params = new URLSearchParams();
75
+ Object.entries(q).forEach(([k, v]) => {
76
+ if (v !== void 0) params.append(k, String(v));
77
+ });
78
+ const s = params.toString();
79
+ return s ? `?${s}` : "";
80
+ }
81
+
82
+ //#endregion
83
+ //#region src/api/auth.ts
84
+ var auth_exports = /* @__PURE__ */ __export({
85
+ ApiKeyAuth: () => ApiKeyAuth,
86
+ BearerTokenAuth: () => BearerTokenAuth,
87
+ NoAuth: () => NoAuth
88
+ });
89
+ var NoAuth, ApiKeyAuth, BearerTokenAuth;
90
+ var init_auth = __esm({ "src/api/auth.ts": (() => {
91
+ NoAuth = class {
92
+ async apply() {}
93
+ };
94
+ ApiKeyAuth = class {
95
+ constructor(opts) {
96
+ this.opts = opts;
97
+ }
98
+ apply({ url, init }) {
99
+ if (this.opts.header) init.headers = {
100
+ ...init.headers,
101
+ [this.opts.header]: this.opts.value
102
+ };
103
+ else if (this.opts.query) {
104
+ const u = new URL(url);
105
+ u.searchParams.set(this.opts.query, this.opts.value);
106
+ init.__urlOverride = u.toString();
107
+ }
108
+ }
109
+ };
110
+ BearerTokenAuth = class {
111
+ constructor(getToken) {
112
+ this.getToken = getToken;
113
+ }
114
+ async apply({ init }) {
115
+ const token = await this.getToken();
116
+ init.headers = {
117
+ ...init.headers,
118
+ Authorization: `Bearer ${token}`
119
+ };
120
+ }
121
+ };
122
+ }) });
123
+
124
+ //#endregion
125
+ //#region src/api/validation.ts
126
+ function safeParse(schema, data) {
127
+ const res = schema.safeParse(data);
128
+ if (res.success) return {
129
+ success: true,
130
+ data: res.data
131
+ };
132
+ return {
133
+ success: false,
134
+ error: res.error
135
+ };
136
+ }
137
+ function parseOrThrow(schema, data) {
138
+ const res = schema.safeParse(data);
139
+ if (!res.success) throw new ApiError("Response validation failed", { zodError: res.error });
140
+ return res.data;
141
+ }
142
+
143
+ //#endregion
144
+ //#region src/api/http/HttpClient.ts
145
+ var HttpClient = class {
146
+ fetchImpl;
147
+ baseUrls;
148
+ headers;
149
+ interceptors;
150
+ retry;
151
+ timeoutMs;
152
+ auth;
153
+ constructor(opts) {
154
+ this.fetchImpl = opts.fetch ?? globalThis.fetch?.bind(globalThis);
155
+ if (!this.fetchImpl) throw new Error("No fetch implementation found. Pass one via options.fetch.");
156
+ this.baseUrls = opts.baseUrls;
157
+ this.headers = opts.headers ?? { "Content-Type": "application/json" };
158
+ this.interceptors = opts.interceptors ?? {};
159
+ this.retry = opts.retry ?? {
160
+ maxRetries: 2,
161
+ baseDelayMs: 250,
162
+ jitter: .2,
163
+ retryMethods: ["GET", "HEAD"]
164
+ };
165
+ this.timeoutMs = opts.timeout?.requestTimeoutMs;
166
+ this.auth = opts["auth"] ?? new (init_auth(), __toCommonJS(auth_exports)).NoAuth();
167
+ }
168
+ setAuth(auth) {
169
+ this.auth = auth;
170
+ }
171
+ resolveBaseUrl(key) {
172
+ const k = key || "default";
173
+ const url = this.baseUrls[k];
174
+ if (!url) throw new Error(`Unknown baseUrl key: ${k}`);
175
+ return url.replace(/\/$/, "");
176
+ }
177
+ sleep(ms) {
178
+ return new Promise((res) => setTimeout(res, ms));
179
+ }
180
+ async withRetry(fn, canRetry) {
181
+ let attempt = 0;
182
+ const { maxRetries, baseDelayMs, jitter = .2 } = this.retry;
183
+ while (true) try {
184
+ return await fn();
185
+ } catch (err) {
186
+ if (attempt >= maxRetries || !canRetry({
187
+ attempt,
188
+ error: err
189
+ })) throw err;
190
+ const backoff = baseDelayMs * 2 ** attempt;
191
+ const j = 1 + (Math.random() * 2 - 1) * jitter;
192
+ await this.sleep(backoff * j);
193
+ attempt++;
194
+ }
195
+ }
196
+ async runBeforeHooks(url, init) {
197
+ for (const h of this.interceptors.beforeRequest ?? []) await h({
198
+ url,
199
+ init
200
+ });
201
+ }
202
+ async runAfterHooks(req, res, parsed) {
203
+ for (const h of this.interceptors.afterResponse ?? []) await h({
204
+ request: req,
205
+ response: res,
206
+ parsed
207
+ });
208
+ }
209
+ async request(method, path, body, options) {
210
+ let url = `${this.resolveBaseUrl(options?.baseUrlKey)}${path}${toQueryString(options?.query)}`;
211
+ const headers = {
212
+ ...this.headers,
213
+ ...options?.headers ?? {}
214
+ };
215
+ const controller = new AbortController();
216
+ const signal = options?.signal ?? controller.signal;
217
+ const init = {
218
+ method,
219
+ headers,
220
+ body: body != null ? headers["Content-Type"]?.includes("json") ? JSON.stringify(body) : body : void 0,
221
+ signal
222
+ };
223
+ await this.auth.apply({
224
+ url,
225
+ init,
226
+ options
227
+ });
228
+ if (init.__urlOverride) url = init.__urlOverride;
229
+ await this.runBeforeHooks(url, init);
230
+ const doFetch = async () => {
231
+ let timeoutId;
232
+ if (this.timeoutMs && !options?.signal) timeoutId = setTimeout(() => controller.abort(new ApiError("Request timeout", { status: 0 })), this.timeoutMs);
233
+ try {
234
+ const req = new Request(url, init);
235
+ console.log("HTTP Request:", req.method, req.url, req);
236
+ const res = await this.fetchImpl(req);
237
+ if (!res.ok) {
238
+ let text = "";
239
+ try {
240
+ text = await res.text();
241
+ } catch {}
242
+ throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {
243
+ status: res.status,
244
+ details: text
245
+ });
246
+ }
247
+ const data = (res.headers.get("content-type") || "").includes("json") ? await res.json() : await res.text();
248
+ await this.runAfterHooks(new Request(url, init), res, data);
249
+ return {
250
+ data,
251
+ response: res
252
+ };
253
+ } finally {
254
+ if (timeoutId) clearTimeout(timeoutId);
255
+ }
256
+ };
257
+ const canRetry = ({ response, error }) => {
258
+ if (error instanceof ApiError && error.status && error.status >= 500) return true;
259
+ if (error?.name === "AbortError") return false;
260
+ if (!error?.status && !response) return true;
261
+ return false;
262
+ };
263
+ if (!this.retry.retryMethods?.includes(method)) return doFetch();
264
+ return this.withRetry(doFetch, canRetry);
265
+ }
266
+ };
267
+
268
+ //#endregion
269
+ //#region src/api/endpoint/BaseEndpoint.ts
270
+ /**
271
+ * Generic, strongly-typed endpoint with Zod schemas for request and response.
272
+ */
273
+ var BaseEndpoint = class {
274
+ requestSchema;
275
+ responseSchema;
276
+ constructor(client, cfg) {
277
+ this.client = client;
278
+ this.requestSchema = cfg.requestSchema;
279
+ this.responseSchema = cfg.responseSchema;
280
+ }
281
+ /**
282
+ * Call the endpoint with strong typing derived from schemas.
283
+ */
284
+ async call(args, options) {
285
+ if (this.requestSchema) {
286
+ const parsed = this.requestSchema.safeParse(args);
287
+ if (!parsed.success) throw parsed.error;
288
+ }
289
+ const path = typeof this.path === "function" ? this.path(args) : this.path;
290
+ const body = this.method === "GET" || this.method === "HEAD" ? void 0 : args;
291
+ const { data } = await this.client.request(this.method, path, body, {
292
+ ...options,
293
+ query: (this.method === "GET" ? args?.query : void 0) ?? options?.query
294
+ });
295
+ return parseOrThrow(this.responseSchema, data);
296
+ }
297
+ };
298
+
299
+ //#endregion
300
+ //#region src/api/schemas/common.ts
301
+ const Id = zod.z.union([
302
+ zod.z.string().min(1),
303
+ zod.z.number(),
304
+ zod.z.uuid({ version: "v4" })
305
+ ]);
306
+ const Timestamps = zod.z.object({
307
+ createdAt: zod.z.iso.datetime(),
308
+ updatedAt: zod.z.iso.datetime()
309
+ });
310
+ const Meta = zod.z.object({
311
+ requestId: zod.z.string().optional(),
312
+ timestamp: zod.z.iso.datetime().optional(),
313
+ traceId: zod.z.string().optional()
314
+ });
315
+ const ErrorDetail = zod.z.object({
316
+ path: zod.z.string().optional(),
317
+ message: zod.z.string()
318
+ });
319
+ const ApiErrorSchema = zod.z.object({
320
+ code: zod.z.string(),
321
+ message: zod.z.string(),
322
+ details: zod.z.array(ErrorDetail).optional()
323
+ });
324
+ const Envelope = (inner) => zod.z.object({
325
+ success: zod.z.boolean(),
326
+ data: inner.optional(),
327
+ error: ApiErrorSchema.optional(),
328
+ meta: Meta.optional()
329
+ });
330
+
331
+ //#endregion
332
+ //#region src/api/index.ts
333
+ init_auth();
334
+
335
+ //#endregion
336
+ exports.ApiError = ApiError;
337
+ exports.ApiErrorSchema = ApiErrorSchema;
338
+ exports.ApiKeyAuth = ApiKeyAuth;
339
+ exports.BaseEndpoint = BaseEndpoint;
340
+ exports.BearerTokenAuth = BearerTokenAuth;
341
+ exports.Envelope = Envelope;
342
+ exports.ErrorDetail = ErrorDetail;
343
+ exports.HTTPMethod = HTTPMethod;
344
+ exports.HttpClient = HttpClient;
345
+ exports.Id = Id;
346
+ exports.Meta = Meta;
347
+ exports.NoAuth = NoAuth;
348
+ exports.PaginationSchema = PaginationSchema;
349
+ exports.Timestamps = Timestamps;
350
+ exports.parseOrThrow = parseOrThrow;
351
+ exports.safeParse = safeParse;
352
+ exports.toQueryString = toQueryString;
@@ -0,0 +1,7 @@
1
+ export * from "./types";
2
+ export * from "./auth";
3
+ export * from "./validation";
4
+ export { HttpClient } from "./http/HttpClient";
5
+ export { BaseEndpoint } from "./endpoint/BaseEndpoint";
6
+ export * from "./schemas/common";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/api/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAG7B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,329 @@
1
+ import { z } from "zod";
2
+
3
+ //#region rolldown:runtime
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (all) => {
12
+ let target = {};
13
+ for (var name in all) __defProp(target, name, {
14
+ get: all[name],
15
+ enumerable: true
16
+ });
17
+ return target;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
21
+ key = keys[i];
22
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
23
+ get: ((k) => from[k]).bind(null, key),
24
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
25
+ });
26
+ }
27
+ return to;
28
+ };
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ //#endregion
32
+ //#region src/api/types.ts
33
+ const HTTPMethod = {
34
+ GET: "GET",
35
+ POST: "POST",
36
+ PUT: "PUT",
37
+ PATCH: "PATCH",
38
+ DELETE: "DELETE",
39
+ HEAD: "HEAD",
40
+ OPTIONS: "OPTIONS"
41
+ };
42
+ var ApiError = class extends Error {
43
+ status;
44
+ details;
45
+ zodError;
46
+ constructor(message, options) {
47
+ super(message);
48
+ this.name = "ApiError";
49
+ this.status = options?.status;
50
+ this.details = options?.details;
51
+ this.cause = options?.cause;
52
+ this.zodError = options?.zodError;
53
+ }
54
+ };
55
+ const PaginationSchema = z.object({
56
+ items: z.array(z.unknown()),
57
+ total: z.number().int().nonnegative(),
58
+ page: z.number().int().nonnegative(),
59
+ pageSize: z.number().int().positive()
60
+ });
61
+ function toQueryString(q) {
62
+ if (!q) return "";
63
+ if (q instanceof URLSearchParams) {
64
+ const s$1 = q.toString();
65
+ return s$1 ? `?${s$1}` : "";
66
+ }
67
+ const params = new URLSearchParams();
68
+ Object.entries(q).forEach(([k, v]) => {
69
+ if (v !== void 0) params.append(k, String(v));
70
+ });
71
+ const s = params.toString();
72
+ return s ? `?${s}` : "";
73
+ }
74
+
75
+ //#endregion
76
+ //#region src/api/auth.ts
77
+ var auth_exports = /* @__PURE__ */ __export({
78
+ ApiKeyAuth: () => ApiKeyAuth,
79
+ BearerTokenAuth: () => BearerTokenAuth,
80
+ NoAuth: () => NoAuth
81
+ });
82
+ var NoAuth, ApiKeyAuth, BearerTokenAuth;
83
+ var init_auth = __esm({ "src/api/auth.ts": (() => {
84
+ NoAuth = class {
85
+ async apply() {}
86
+ };
87
+ ApiKeyAuth = class {
88
+ constructor(opts) {
89
+ this.opts = opts;
90
+ }
91
+ apply({ url, init }) {
92
+ if (this.opts.header) init.headers = {
93
+ ...init.headers,
94
+ [this.opts.header]: this.opts.value
95
+ };
96
+ else if (this.opts.query) {
97
+ const u = new URL(url);
98
+ u.searchParams.set(this.opts.query, this.opts.value);
99
+ init.__urlOverride = u.toString();
100
+ }
101
+ }
102
+ };
103
+ BearerTokenAuth = class {
104
+ constructor(getToken) {
105
+ this.getToken = getToken;
106
+ }
107
+ async apply({ init }) {
108
+ const token = await this.getToken();
109
+ init.headers = {
110
+ ...init.headers,
111
+ Authorization: `Bearer ${token}`
112
+ };
113
+ }
114
+ };
115
+ }) });
116
+
117
+ //#endregion
118
+ //#region src/api/validation.ts
119
+ function safeParse(schema, data) {
120
+ const res = schema.safeParse(data);
121
+ if (res.success) return {
122
+ success: true,
123
+ data: res.data
124
+ };
125
+ return {
126
+ success: false,
127
+ error: res.error
128
+ };
129
+ }
130
+ function parseOrThrow(schema, data) {
131
+ const res = schema.safeParse(data);
132
+ if (!res.success) throw new ApiError("Response validation failed", { zodError: res.error });
133
+ return res.data;
134
+ }
135
+
136
+ //#endregion
137
+ //#region src/api/http/HttpClient.ts
138
+ var HttpClient = class {
139
+ fetchImpl;
140
+ baseUrls;
141
+ headers;
142
+ interceptors;
143
+ retry;
144
+ timeoutMs;
145
+ auth;
146
+ constructor(opts) {
147
+ this.fetchImpl = opts.fetch ?? globalThis.fetch?.bind(globalThis);
148
+ if (!this.fetchImpl) throw new Error("No fetch implementation found. Pass one via options.fetch.");
149
+ this.baseUrls = opts.baseUrls;
150
+ this.headers = opts.headers ?? { "Content-Type": "application/json" };
151
+ this.interceptors = opts.interceptors ?? {};
152
+ this.retry = opts.retry ?? {
153
+ maxRetries: 2,
154
+ baseDelayMs: 250,
155
+ jitter: .2,
156
+ retryMethods: ["GET", "HEAD"]
157
+ };
158
+ this.timeoutMs = opts.timeout?.requestTimeoutMs;
159
+ this.auth = opts["auth"] ?? new (init_auth(), __toCommonJS(auth_exports)).NoAuth();
160
+ }
161
+ setAuth(auth) {
162
+ this.auth = auth;
163
+ }
164
+ resolveBaseUrl(key) {
165
+ const k = key || "default";
166
+ const url = this.baseUrls[k];
167
+ if (!url) throw new Error(`Unknown baseUrl key: ${k}`);
168
+ return url.replace(/\/$/, "");
169
+ }
170
+ sleep(ms) {
171
+ return new Promise((res) => setTimeout(res, ms));
172
+ }
173
+ async withRetry(fn, canRetry) {
174
+ let attempt = 0;
175
+ const { maxRetries, baseDelayMs, jitter = .2 } = this.retry;
176
+ while (true) try {
177
+ return await fn();
178
+ } catch (err) {
179
+ if (attempt >= maxRetries || !canRetry({
180
+ attempt,
181
+ error: err
182
+ })) throw err;
183
+ const backoff = baseDelayMs * 2 ** attempt;
184
+ const j = 1 + (Math.random() * 2 - 1) * jitter;
185
+ await this.sleep(backoff * j);
186
+ attempt++;
187
+ }
188
+ }
189
+ async runBeforeHooks(url, init) {
190
+ for (const h of this.interceptors.beforeRequest ?? []) await h({
191
+ url,
192
+ init
193
+ });
194
+ }
195
+ async runAfterHooks(req, res, parsed) {
196
+ for (const h of this.interceptors.afterResponse ?? []) await h({
197
+ request: req,
198
+ response: res,
199
+ parsed
200
+ });
201
+ }
202
+ async request(method, path, body, options) {
203
+ let url = `${this.resolveBaseUrl(options?.baseUrlKey)}${path}${toQueryString(options?.query)}`;
204
+ const headers = {
205
+ ...this.headers,
206
+ ...options?.headers ?? {}
207
+ };
208
+ const controller = new AbortController();
209
+ const signal = options?.signal ?? controller.signal;
210
+ const init = {
211
+ method,
212
+ headers,
213
+ body: body != null ? headers["Content-Type"]?.includes("json") ? JSON.stringify(body) : body : void 0,
214
+ signal
215
+ };
216
+ await this.auth.apply({
217
+ url,
218
+ init,
219
+ options
220
+ });
221
+ if (init.__urlOverride) url = init.__urlOverride;
222
+ await this.runBeforeHooks(url, init);
223
+ const doFetch = async () => {
224
+ let timeoutId;
225
+ if (this.timeoutMs && !options?.signal) timeoutId = setTimeout(() => controller.abort(new ApiError("Request timeout", { status: 0 })), this.timeoutMs);
226
+ try {
227
+ const req = new Request(url, init);
228
+ console.log("HTTP Request:", req.method, req.url, req);
229
+ const res = await this.fetchImpl(req);
230
+ if (!res.ok) {
231
+ let text = "";
232
+ try {
233
+ text = await res.text();
234
+ } catch {}
235
+ throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {
236
+ status: res.status,
237
+ details: text
238
+ });
239
+ }
240
+ const data = (res.headers.get("content-type") || "").includes("json") ? await res.json() : await res.text();
241
+ await this.runAfterHooks(new Request(url, init), res, data);
242
+ return {
243
+ data,
244
+ response: res
245
+ };
246
+ } finally {
247
+ if (timeoutId) clearTimeout(timeoutId);
248
+ }
249
+ };
250
+ const canRetry = ({ response, error }) => {
251
+ if (error instanceof ApiError && error.status && error.status >= 500) return true;
252
+ if (error?.name === "AbortError") return false;
253
+ if (!error?.status && !response) return true;
254
+ return false;
255
+ };
256
+ if (!this.retry.retryMethods?.includes(method)) return doFetch();
257
+ return this.withRetry(doFetch, canRetry);
258
+ }
259
+ };
260
+
261
+ //#endregion
262
+ //#region src/api/endpoint/BaseEndpoint.ts
263
+ /**
264
+ * Generic, strongly-typed endpoint with Zod schemas for request and response.
265
+ */
266
+ var BaseEndpoint = class {
267
+ requestSchema;
268
+ responseSchema;
269
+ constructor(client, cfg) {
270
+ this.client = client;
271
+ this.requestSchema = cfg.requestSchema;
272
+ this.responseSchema = cfg.responseSchema;
273
+ }
274
+ /**
275
+ * Call the endpoint with strong typing derived from schemas.
276
+ */
277
+ async call(args, options) {
278
+ if (this.requestSchema) {
279
+ const parsed = this.requestSchema.safeParse(args);
280
+ if (!parsed.success) throw parsed.error;
281
+ }
282
+ const path = typeof this.path === "function" ? this.path(args) : this.path;
283
+ const body = this.method === "GET" || this.method === "HEAD" ? void 0 : args;
284
+ const { data } = await this.client.request(this.method, path, body, {
285
+ ...options,
286
+ query: (this.method === "GET" ? args?.query : void 0) ?? options?.query
287
+ });
288
+ return parseOrThrow(this.responseSchema, data);
289
+ }
290
+ };
291
+
292
+ //#endregion
293
+ //#region src/api/schemas/common.ts
294
+ const Id = z.union([
295
+ z.string().min(1),
296
+ z.number(),
297
+ z.uuid({ version: "v4" })
298
+ ]);
299
+ const Timestamps = z.object({
300
+ createdAt: z.iso.datetime(),
301
+ updatedAt: z.iso.datetime()
302
+ });
303
+ const Meta = z.object({
304
+ requestId: z.string().optional(),
305
+ timestamp: z.iso.datetime().optional(),
306
+ traceId: z.string().optional()
307
+ });
308
+ const ErrorDetail = z.object({
309
+ path: z.string().optional(),
310
+ message: z.string()
311
+ });
312
+ const ApiErrorSchema = z.object({
313
+ code: z.string(),
314
+ message: z.string(),
315
+ details: z.array(ErrorDetail).optional()
316
+ });
317
+ const Envelope = (inner) => z.object({
318
+ success: z.boolean(),
319
+ data: inner.optional(),
320
+ error: ApiErrorSchema.optional(),
321
+ meta: Meta.optional()
322
+ });
323
+
324
+ //#endregion
325
+ //#region src/api/index.ts
326
+ init_auth();
327
+
328
+ //#endregion
329
+ export { ApiError, ApiErrorSchema, ApiKeyAuth, BaseEndpoint, BearerTokenAuth, Envelope, ErrorDetail, HTTPMethod, HttpClient, Id, Meta, NoAuth, PaginationSchema, Timestamps, parseOrThrow, safeParse, toQueryString };
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ export declare const Id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodUUID]>;
3
+ export type Id = z.infer<typeof Id>;
4
+ export declare const Timestamps: z.ZodObject<{
5
+ createdAt: z.ZodISODateTime;
6
+ updatedAt: z.ZodISODateTime;
7
+ }, z.core.$strip>;
8
+ export declare const Meta: z.ZodObject<{
9
+ requestId: z.ZodOptional<z.ZodString>;
10
+ timestamp: z.ZodOptional<z.ZodISODateTime>;
11
+ traceId: z.ZodOptional<z.ZodString>;
12
+ }, z.core.$strip>;
13
+ export declare const ErrorDetail: z.ZodObject<{
14
+ path: z.ZodOptional<z.ZodString>;
15
+ message: z.ZodString;
16
+ }, z.core.$strip>;
17
+ export declare const ApiErrorSchema: z.ZodObject<{
18
+ code: z.ZodString;
19
+ message: z.ZodString;
20
+ details: z.ZodOptional<z.ZodArray<z.ZodObject<{
21
+ path: z.ZodOptional<z.ZodString>;
22
+ message: z.ZodString;
23
+ }, z.core.$strip>>>;
24
+ }, z.core.$strip>;
25
+ export declare const Envelope: <T extends z.ZodTypeAny>(inner: T) => z.ZodObject<{
26
+ success: z.ZodBoolean;
27
+ data: z.ZodOptional<T>;
28
+ error: z.ZodOptional<z.ZodObject<{
29
+ code: z.ZodString;
30
+ message: z.ZodString;
31
+ details: z.ZodOptional<z.ZodArray<z.ZodObject<{
32
+ path: z.ZodOptional<z.ZodString>;
33
+ message: z.ZodString;
34
+ }, z.core.$strip>>>;
35
+ }, z.core.$strip>>;
36
+ meta: z.ZodOptional<z.ZodObject<{
37
+ requestId: z.ZodOptional<z.ZodString>;
38
+ timestamp: z.ZodOptional<z.ZodISODateTime>;
39
+ traceId: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strip>>;
41
+ }, z.core.$strip>;
42
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/api/schemas/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,EAAE,4DAEX,CAAC;AACL,MAAM,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;AAGpC,eAAO,MAAM,UAAU;;;iBAGrB,CAAC;AAGH,eAAO,MAAM,IAAI;;;;iBAIf,CAAC;AAGH,eAAO,MAAM,WAAW;;;iBAGtB,CAAC;AAGH,eAAO,MAAM,cAAc;;;;;;;iBAIzB,CAAC;AAGH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC;;;;;;;;;;;;;;;;iBAMnD,CAAC"}
@@ -0,0 +1,99 @@
1
+ import { z, ZodError } from "zod";
2
+ export type Dictionary<T = unknown> = Record<string, T>;
3
+ export type FetchLike = (input: string | Request | URL, init?: RequestInit) => Promise<Response>;
4
+ export type BaseUrlMap = {
5
+ default: string;
6
+ [service: string]: string;
7
+ };
8
+ export type RetryStrategy = {
9
+ maxRetries: number;
10
+ /** milliseconds */
11
+ baseDelayMs: number;
12
+ /** Jitter factor 0..1 */
13
+ jitter?: number;
14
+ /** HTTP methods eligible for retry */
15
+ retryMethods?: (keyof typeof HTTPMethod)[];
16
+ /** Which status codes are retriable */
17
+ shouldRetry?: (ctx: {
18
+ attempt: number;
19
+ error?: unknown;
20
+ response?: Response;
21
+ }) => boolean;
22
+ };
23
+ export declare const HTTPMethod: {
24
+ readonly GET: "GET";
25
+ readonly POST: "POST";
26
+ readonly PUT: "PUT";
27
+ readonly PATCH: "PATCH";
28
+ readonly DELETE: "DELETE";
29
+ readonly HEAD: "HEAD";
30
+ readonly OPTIONS: "OPTIONS";
31
+ };
32
+ export type HttpMethod = keyof typeof HTTPMethod;
33
+ export type AfterResponseHook = (ctx: {
34
+ request: Request;
35
+ response: Response;
36
+ parsed?: unknown;
37
+ }) => Promise<void> | void;
38
+ export type BeforeRequestHook = (ctx: {
39
+ url: string;
40
+ init: RequestInit;
41
+ }) => Promise<void> | void;
42
+ export interface Interceptors {
43
+ beforeRequest?: BeforeRequestHook[];
44
+ afterResponse?: AfterResponseHook[];
45
+ }
46
+ export interface TimeoutOptions {
47
+ /** ms */
48
+ requestTimeoutMs?: number;
49
+ }
50
+ export interface ClientOptions {
51
+ baseUrls: BaseUrlMap;
52
+ fetch?: FetchLike;
53
+ headers?: Record<string, string>;
54
+ retry?: RetryStrategy;
55
+ interceptors?: Interceptors;
56
+ timeout?: TimeoutOptions;
57
+ }
58
+ export type SafeParseResult<T> = {
59
+ success: true;
60
+ data: T;
61
+ } | {
62
+ success: false;
63
+ error: ZodError;
64
+ };
65
+ export declare class ApiError extends Error {
66
+ status?: number;
67
+ details?: unknown;
68
+ zodError?: ZodError;
69
+ constructor(message: string, options?: {
70
+ status?: number;
71
+ cause?: unknown;
72
+ details?: unknown;
73
+ zodError?: ZodError;
74
+ });
75
+ }
76
+ export type Paginated<T> = {
77
+ items: T[];
78
+ total: number;
79
+ page: number;
80
+ pageSize: number;
81
+ };
82
+ export declare const PaginationSchema: z.ZodObject<{
83
+ items: z.ZodArray<z.ZodUnknown>;
84
+ total: z.ZodNumber;
85
+ page: z.ZodNumber;
86
+ pageSize: z.ZodNumber;
87
+ }, z.core.$strip>;
88
+ export type RequestOptions = {
89
+ /** Override base URL for a single call */
90
+ baseUrlKey?: keyof BaseUrlMap;
91
+ /** Additional headers for this call only */
92
+ headers?: Record<string, string>;
93
+ /** Abort controller */
94
+ signal?: AbortSignal;
95
+ /** Custom query params */
96
+ query?: URLSearchParams | Record<string, string | number | boolean | undefined>;
97
+ };
98
+ export declare function toQueryString(q?: RequestOptions["query"]): string;
99
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/api/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ import { SafeParseResult } from "./types";
3
+ export declare function safeParse<T>(schema: z.ZodTypeAny, data: unknown): SafeParseResult<T>;
4
+ export declare function parseOrThrow<T>(schema: z.ZodTypeAny, data: unknown): T;
5
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/api/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAY,eAAe,EAAE,MAAM,SAAS,CAAC;AAEpD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,CAIpF;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,CAMtE"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "zlient",
3
+ "version": "1.0.0",
4
+ "description": "A type-safe HTTP client framework with Zod validation",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "rolldown -c && tsc --emitDeclarationOnly --outDir dist",
21
+ "prepublishOnly": "bun run build"
22
+ },
23
+ "keywords": [
24
+ "http-client",
25
+ "api-client",
26
+ "zod",
27
+ "validation",
28
+ "typescript"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "devDependencies": {
33
+ "@types/bun": "latest",
34
+ "rolldown": "^1.0.0-beta.44",
35
+ "typescript": "^5.9.3"
36
+ },
37
+ "peerDependencies": {
38
+ "zod": "^3.0.0 || ^4.0.0"
39
+ },
40
+ "dependencies": {
41
+ "zod": "^4.1.12"
42
+ }
43
+ }