@zap-studio/fetch 0.1.0 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @zap-studio/fetch
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 5f1812b: Change files field in package.json to distribute only necessary artifacts
8
+
3
9
  ## 0.1.0
4
10
 
5
11
  ### Minor Changes
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @zap-studio/fetch
2
+
3
+ A type-safe fetch wrapper with Zod validation for TypeScript.
4
+
5
+ ## Features
6
+
7
+ - 🎯 **Type-safe requests** with automatic type inference
8
+ - 🛡️ **Runtime validation** using Zod schemas
9
+ - 🔄 **Automatic content-type** detection and handling
10
+ - ⚡️ **Convenient API methods** (GET, POST, PUT, PATCH, DELETE)
11
+ - 📦 **Multiple response types** (JSON, text, blob, arrayBuffer)
12
+ - 🚨 **Custom error handling** with FetchError class
13
+ - 📘 **Full TypeScript support** with zero configuration
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @zap-studio/fetch
19
+ #or
20
+ npm install @zap-studio/fetch
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { z } from "zod";
27
+ import { api } from "@zap-studio/fetch";
28
+
29
+ // Define your schema
30
+ const UserSchema = z.object({
31
+ id: z.number(),
32
+ name: z.string(),
33
+ email: z.email(),
34
+ });
35
+
36
+ // Make a type-safe request
37
+ const user = await api.get(
38
+ "https://api.example.com/users/1",
39
+ UserSchema
40
+ );
41
+
42
+ // user is fully typed and validated! ✨
43
+ console.log(user.name); // TypeScript knows this is a string
44
+ ```
45
+
46
+ ## API
47
+
48
+ ### `api.get(url, schema, options?)`
49
+
50
+ ```typescript
51
+ const user = await api.get("/api/users/1", UserSchema);
52
+ ```
53
+
54
+ ### `api.post(url, schema, body?, options?)`
55
+
56
+ ```typescript
57
+ const newUser = await api.post("/api/users", UserSchema, {
58
+ name: "John Doe",
59
+ email: "john@example.com",
60
+ });
61
+ ```
62
+
63
+ ### `api.put(url, schema, body?, options?)`
64
+
65
+ ```typescript
66
+ const updated = await api.put("/api/users/1", UserSchema, {
67
+ name: "Jane Doe",
68
+ });
69
+ ```
70
+
71
+ ### `api.patch(url, schema, body?, options?)`
72
+
73
+ ```typescript
74
+ const patched = await api.patch("/api/users/1", UserSchema, {
75
+ email: "newemail@example.com",
76
+ });
77
+ ```
78
+
79
+ ### `api.delete(url, schema, options?)`
80
+
81
+ ```typescript
82
+ const deleted = await api.delete("/api/users/1", UserSchema);
83
+ ```
84
+
85
+ ## Advanced Usage
86
+
87
+ ### Using `safeFetch` directly
88
+
89
+ ```typescript
90
+ import { safeFetch } from "@zap-studio/fetch";
91
+
92
+ const result = await safeFetch(
93
+ "https://api.example.com/users/1",
94
+ UserSchema,
95
+ {
96
+ method: "GET",
97
+ headers: {
98
+ "Authorization": "Bearer token",
99
+ },
100
+ }
101
+ );
102
+ ```
103
+
104
+ ### Custom response types
105
+
106
+ ```typescript
107
+ // Get text response
108
+ const text = await safeFetch(
109
+ "/api/data",
110
+ z.string(),
111
+ { responseType: "text" }
112
+ );
113
+
114
+ // Get blob response
115
+ const blob = await safeFetch(
116
+ "/api/file",
117
+ z.instanceof(Blob),
118
+ { responseType: "blob" }
119
+ );
120
+ ```
121
+
122
+ ### Error handling
123
+
124
+ ```typescript
125
+ import { FetchError } from "@zap-studio/fetch/errors";
126
+
127
+ try {
128
+ const user = await api.get("/api/users/1", UserSchema);
129
+ } catch (error) {
130
+ if (error instanceof FetchError) {
131
+ console.error(`HTTP ${error.status}: ${error.statusText}`);
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Flexible validation
137
+
138
+ ```typescript
139
+ // Throw on validation error (default)
140
+ const user = await safeFetch(url, UserSchema, {
141
+ throwOnValidationError: true,
142
+ });
143
+
144
+ // Return validation result without throwing
145
+ const result = await safeFetch(url, UserSchema, {
146
+ throwOnValidationError: false,
147
+ });
148
+
149
+ if (result.success) {
150
+ console.log(result.data);
151
+ } else {
152
+ console.error(result.error);
153
+ }
154
+ ```
155
+
156
+ ## Why @zap-studio/fetch?
157
+
158
+ **Before:**
159
+ ```typescript
160
+ const response = await fetch("/api/users/1");
161
+ const data = await response.json();
162
+ const user = data as User; // 😱 Unsafe type assertion
163
+ ```
164
+
165
+ **After:**
166
+ ```typescript
167
+ const user = await api.get("/api/users/1", UserSchema);
168
+ // ✨ Typed, validated, and safe!
169
+ ```
@@ -0,0 +1,10 @@
1
+ //#region src/errors/index.d.ts
2
+ declare class FetchError extends Error {
3
+ status: number;
4
+ statusText: string;
5
+ response: Response;
6
+ constructor(message: string, status: number, statusText: string, response: Response);
7
+ }
8
+ //#endregion
9
+ export { FetchError };
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/errors/index.ts"],"sourcesContent":[],"mappings":";cAAa,UAAA,SAAmB,KAAA;EAAnB,MAAA,EAAA,MAAW;EAKL,UAAA,EAAA,MAAA;EAAA,QAAA,EAAA,QAAA;EALa,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAKb,QALa"}
@@ -0,0 +1,3 @@
1
+ import { t as FetchError } from "../errors-DrfSty0J.js";
2
+
3
+ export { FetchError };
@@ -0,0 +1,14 @@
1
+ //#region src/errors/index.ts
2
+ var FetchError = class extends Error {
3
+ constructor(message, status, statusText, response) {
4
+ super(message);
5
+ this.status = status;
6
+ this.statusText = statusText;
7
+ this.response = response;
8
+ this.name = "FetchError";
9
+ }
10
+ };
11
+
12
+ //#endregion
13
+ export { FetchError as t };
14
+ //# sourceMappingURL=errors-DrfSty0J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-DrfSty0J.js","names":["status: number","statusText: string","response: Response"],"sources":["../src/errors/index.ts"],"sourcesContent":["export class FetchError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic response: Response,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"FetchError\";\n\t}\n}\n"],"mappings":";AAAA,IAAa,aAAb,cAAgC,MAAM;CACrC,YACC,SACA,AAAOA,QACP,AAAOC,YACP,AAAOC,UACN;AACD,QAAM,QAAQ;EAJP;EACA;EACA;AAGP,OAAK,OAAO"}
@@ -0,0 +1,9 @@
1
+ //#region src/types/index.d.ts
2
+ type FetchConfig<TBody> = Omit<RequestInit, "body"> & {
3
+ body?: TBody;
4
+ throwOnValidationError?: boolean;
5
+ };
6
+ type ResponseType = "json" | "text" | "blob" | "arrayBuffer" | "formData";
7
+ //#endregion
8
+ export { ResponseType as n, FetchConfig as t };
9
+ //# sourceMappingURL=index-BkzLFXkF.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BkzLFXkF.d.ts","names":[],"sources":["../src/types/index.ts"],"sourcesContent":[],"mappings":";KAAY,qBAAqB,KAAK;EAA1B,IAAA,CAAA,EACJ,KADI;EAA0B,sBAAA,CAAA,EAAA,OAAA;CAAL;AACzB,KAII,YAAA,GAJJ,MAAA,GAAA,MAAA,GAAA,MAAA,GAAA,aAAA,GAAA,UAAA"}
@@ -0,0 +1,63 @@
1
+ import { n as ResponseType, t as FetchConfig } from "./index-BkzLFXkF.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/index.d.ts
5
+
6
+ /**
7
+ * Type-safe fetch wrapper with Zod validation
8
+ *
9
+ * @example
10
+ * import { z } from "zod";
11
+ * import { safeFetch } from "@zap-studio/fetch";
12
+ *
13
+ * const UserSchema = z.object({
14
+ * id: z.number(),
15
+ * name: z.string(),
16
+ * email: z.string().email(),
17
+ * });
18
+ *
19
+ * async function getUser(userId: number) {
20
+ * const user = await safeFetch(
21
+ * `https://api.example.com/users/${userId}`,
22
+ * UserSchema,
23
+ * { method: "GET" }
24
+ * );
25
+ * return user; // user is typed as { id: number; name: string; email: string; }
26
+ * }
27
+ */
28
+ declare function safeFetch<TResponse$1, TBody$1 = unknown>(url: string, responseSchema: z.ZodType<TResponse$1>, config?: FetchConfig<TBody$1> & {
29
+ throwOnValidationError?: true;
30
+ responseType?: ResponseType;
31
+ }): Promise<TResponse$1>;
32
+ declare function safeFetch<TResponse$1, TBody$1 = unknown>(url: string, responseSchema: z.ZodType<TResponse$1>, config?: FetchConfig<TBody$1> & {
33
+ throwOnValidationError: false;
34
+ responseType?: ResponseType;
35
+ }): Promise<ReturnType<typeof responseSchema.safeParse>>;
36
+ /**
37
+ * Convenience methods for common HTTP verbs
38
+ *
39
+ * @example
40
+ * import { z } from "zod";
41
+ * import { api } from "@zap-studio/fetch";
42
+ *
43
+ * const PostSchema = z.object({
44
+ * id: z.number(),
45
+ * title: z.string(),
46
+ * content: z.string(),
47
+ * });
48
+ *
49
+ * async function fetchPost(postId: number) {
50
+ * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
51
+ * return post; // post is typed as { id: number; title: string; content: string; }
52
+ * }
53
+ */
54
+ declare const api: {
55
+ get: <TResponse>(url: string, schema: z.ZodType<TResponse>, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
56
+ post: <TResponse, TBody = unknown>(url: string, schema: z.ZodType<TResponse>, body?: TBody, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
57
+ put: <TResponse, TBody = unknown>(url: string, schema: z.ZodType<TResponse>, body?: TBody, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
58
+ patch: <TResponse, TBody = unknown>(url: string, schema: z.ZodType<TResponse>, body?: TBody, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
59
+ delete: <TResponse>(url: string, schema: z.ZodType<TResponse>, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
60
+ };
61
+ //#endregion
62
+ export { api, safeFetch };
63
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AA0BA;;;;;;;;;AASA;;;;;;;;;;AAoHA;AAGoB,iBAhIE,SAgIF,CAAA,WAAA,EAAA,UAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,cAAA,EA9HH,CAAA,CAAE,OA8HC,CA9HO,WA8HP,CAAA,EAAA,MACT,CADS,EA7HV,WA6HU,CA7HE,OA6HF,CAAA,GAAA;EAAR,sBAAA,CAAA,EAAA,IAAA;EACI,YAAA,CAAA,EA5HC,YA4HD;CAAL,CAAA,EA1HR,OA0HQ,CA1HA,WA0HA,CAAA;AAAoC,iBAxHzB,SAwHyB,CAAA,WAAA,EAAA,UAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,cAAA,EAtH9B,CAAA,CAAE,OAsH4B,CAtHpB,WAsHoB,CAAA,EAAA,MAKrC,CALqC,EArHrC,WAqHqC,CArHzB,OAqHyB,CAAA,GAAA;EAAA,sBAAA,EAAA,KAAA;EAK3B,YAAA,CAAA,EAxHH,YAwHG;CAAV,CAAA,EAtHP,OAsHS,CAtHD,UAsHC,CAAA,OAtHiB,cAAA,CAAe,SAsHhC,CAAA,CAAA;;;;;;;;;;;;;;;;;;;AAgBmC,cAzBlC,GAyBkC,EAAA;EAK3B,GAAA,EAAA,CAAA,SAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EA3BV,CAAA,CAAE,OA2BQ,CA3BA,SA2BA,CAAA,EAAA,MAAA,CAAA,EA1BT,IA0BS,CA1BJ,WA0BI,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GA1B2B,OA0B3B,CA1B2B,SA0B3B,CAAA;EAAR,IAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EArBF,CAAA,CAAE,OAqBA,CArBQ,SAqBR,CAAA,EAAA,IAAA,CAAA,EApBH,KAoBG,EAAA,MAAA,CAAA,EAnBD,IAmBC,CAnBI,WAmBJ,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAnBmC,OAmBnC,CAnBmC,SAmBnC,CAAA;EACI,GAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EAfN,CAAA,CAAE,OAeI,CAfI,SAeJ,CAAA,EAAA,IAAA,CAAA,EAdP,KAcO,EAAA,MAAA,CAAA,EAbL,IAaK,CAbA,WAaA,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAb+B,OAa/B,CAb+B,SAa/B,CAAA;EAAL,KAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EARD,CAAA,CAAE,OAQD,CARS,SAQT,CAAA,EAAA,IAAA,CAAA,EAPF,KAOE,EAAA,MAAA,CAAA,EANA,IAMA,CANK,WAML,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GANoC,OAMpC,CANoC,SAMpC,CAAA;EAAoC,MAAA,EAAA,CAAA,SAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EADrC,CAAA,CAAE,OACmC,CAD3B,SAC2B,CAAA,EAAA,MAAA,CAAA,EAApC,IAAoC,CAA/B,WAA+B,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAAA,OAAA,CAAA,SAAA,CAAA;CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ import { t as FetchError } from "./errors-DrfSty0J.js";
2
+
3
+ //#region src/index.ts
4
+ async function safeFetch(url, responseSchema, config) {
5
+ const { body, headers, throwOnValidationError = true, responseType = "json",...rest } = config || {};
6
+ const fetchConfig = {
7
+ ...rest,
8
+ headers: { ...headers }
9
+ };
10
+ if (body !== void 0) {
11
+ const shouldSetContentType = !headers || typeof headers === "object" && !Array.isArray(headers) && !(headers instanceof Headers) && !("Content-Type" in headers);
12
+ if (body instanceof FormData) fetchConfig.body = body;
13
+ else if (typeof body === "string") {
14
+ fetchConfig.body = body;
15
+ if (shouldSetContentType) fetchConfig.headers["Content-Type"] = "text/plain";
16
+ } else {
17
+ fetchConfig.body = JSON.stringify(body);
18
+ if (shouldSetContentType) fetchConfig.headers["Content-Type"] = "application/json";
19
+ }
20
+ }
21
+ const response = await fetch(url, fetchConfig);
22
+ if (!response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response.status, response.statusText, response);
23
+ let data;
24
+ if (responseType === "json") if (response.headers.get("content-type")?.includes("application/json")) data = await response.json();
25
+ else throw new FetchError("Expected JSON response but received different content type", response.status, response.statusText, response);
26
+ else if (responseType === "text") data = await response.text();
27
+ else if (responseType === "blob") data = await response.blob();
28
+ else if (responseType === "arrayBuffer") data = await response.arrayBuffer();
29
+ else if (responseType === "formData") data = await response.formData();
30
+ if (throwOnValidationError) return responseSchema.parse(data);
31
+ return responseSchema.safeParse(data);
32
+ }
33
+ /**
34
+ * Convenience methods for common HTTP verbs
35
+ *
36
+ * @example
37
+ * import { z } from "zod";
38
+ * import { api } from "@zap-studio/fetch";
39
+ *
40
+ * const PostSchema = z.object({
41
+ * id: z.number(),
42
+ * title: z.string(),
43
+ * content: z.string(),
44
+ * });
45
+ *
46
+ * async function fetchPost(postId: number) {
47
+ * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
48
+ * return post; // post is typed as { id: number; title: string; content: string; }
49
+ * }
50
+ */
51
+ const api = {
52
+ get: (url, schema, config) => safeFetch(url, schema, {
53
+ ...config,
54
+ method: "GET"
55
+ }),
56
+ post: (url, schema, body, config) => safeFetch(url, schema, {
57
+ ...config,
58
+ method: "POST",
59
+ body
60
+ }),
61
+ put: (url, schema, body, config) => safeFetch(url, schema, {
62
+ ...config,
63
+ method: "PUT",
64
+ body
65
+ }),
66
+ patch: (url, schema, body, config) => safeFetch(url, schema, {
67
+ ...config,
68
+ method: "PATCH",
69
+ body
70
+ }),
71
+ delete: (url, schema, config) => safeFetch(url, schema, {
72
+ ...config,
73
+ method: "DELETE"
74
+ })
75
+ };
76
+
77
+ //#endregion
78
+ export { api, safeFetch };
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["fetchConfig: RequestInit","data: unknown"],"sources":["../src/index.ts"],"sourcesContent":["import type { z } from \"zod\";\nimport { FetchError } from \"./errors\";\nimport type { FetchConfig, ResponseType } from \"./types\";\n\n/**\n * Type-safe fetch wrapper with Zod validation\n *\n * @example\n * import { z } from \"zod\";\n * import { safeFetch } from \"@zap-studio/fetch\";\n *\n * const UserSchema = z.object({\n * id: z.number(),\n * name: z.string(),\n * email: z.string().email(),\n * });\n *\n * async function getUser(userId: number) {\n * const user = await safeFetch(\n * `https://api.example.com/users/${userId}`,\n * UserSchema,\n * { method: \"GET\" }\n * );\n * return user; // user is typed as { id: number; name: string; email: string; }\n * }\n */\nexport async function safeFetch<TResponse, TBody = unknown>(\n\turl: string,\n\tresponseSchema: z.ZodType<TResponse>,\n\tconfig?: FetchConfig<TBody> & {\n\t\tthrowOnValidationError?: true;\n\t\tresponseType?: ResponseType;\n\t},\n): Promise<TResponse>;\n\nexport async function safeFetch<TResponse, TBody = unknown>(\n\turl: string,\n\tresponseSchema: z.ZodType<TResponse>,\n\tconfig?: FetchConfig<TBody> & {\n\t\tthrowOnValidationError: false;\n\t\tresponseType?: ResponseType;\n\t},\n): Promise<ReturnType<typeof responseSchema.safeParse>>;\n\nexport async function safeFetch<TResponse, TBody = unknown>(\n\turl: string,\n\tresponseSchema: z.ZodType<TResponse>,\n\tconfig?: FetchConfig<TBody> & { responseType?: ResponseType },\n): Promise<TResponse | ReturnType<typeof responseSchema.safeParse>> {\n\tconst {\n\t\tbody,\n\t\theaders,\n\t\tthrowOnValidationError = true,\n\t\tresponseType = \"json\",\n\t\t...rest\n\t} = config || {};\n\n\tconst fetchConfig: RequestInit = {\n\t\t...rest,\n\t\theaders: {\n\t\t\t...(headers as Record<string, string>),\n\t\t},\n\t};\n\n\t// Only set Content-Type if body exists and it's not FormData\n\tif (body !== undefined) {\n\t\tconst shouldSetContentType =\n\t\t\t!headers ||\n\t\t\t(typeof headers === \"object\" &&\n\t\t\t\t!Array.isArray(headers) &&\n\t\t\t\t!(headers instanceof Headers) &&\n\t\t\t\t!(\"Content-Type\" in headers));\n\n\t\tif (body instanceof FormData) {\n\t\t\tfetchConfig.body = body;\n\t\t} else if (typeof body === \"string\") {\n\t\t\tfetchConfig.body = body;\n\t\t\tif (shouldSetContentType) {\n\t\t\t\t(fetchConfig.headers as Record<string, string>)[\"Content-Type\"] =\n\t\t\t\t\t\"text/plain\";\n\t\t\t}\n\t\t} else {\n\t\t\tfetchConfig.body = JSON.stringify(body);\n\t\t\tif (shouldSetContentType) {\n\t\t\t\t(fetchConfig.headers as Record<string, string>)[\"Content-Type\"] =\n\t\t\t\t\t\"application/json\";\n\t\t\t}\n\t\t}\n\t}\n\n\tconst response = await fetch(url, fetchConfig);\n\n\tif (!response.ok) {\n\t\tthrow new FetchError(\n\t\t\t`HTTP ${response.status}: ${response.statusText}`,\n\t\t\tresponse.status,\n\t\t\tresponse.statusText,\n\t\t\tresponse,\n\t\t);\n\t}\n\n\t// Parse response based on responseType\n\tlet data: unknown;\n\n\tif (responseType === \"json\") {\n\t\tconst contentType = response.headers.get(\"content-type\");\n\t\tif (contentType?.includes(\"application/json\")) {\n\t\t\tdata = await response.json();\n\t\t} else {\n\t\t\tthrow new FetchError(\n\t\t\t\t\"Expected JSON response but received different content type\",\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\tresponse,\n\t\t\t);\n\t\t}\n\t} else if (responseType === \"text\") {\n\t\tdata = await response.text();\n\t} else if (responseType === \"blob\") {\n\t\tdata = await response.blob();\n\t} else if (responseType === \"arrayBuffer\") {\n\t\tdata = await response.arrayBuffer();\n\t} else if (responseType === \"formData\") {\n\t\tdata = await response.formData();\n\t}\n\n\tif (throwOnValidationError) {\n\t\treturn responseSchema.parse(data);\n\t}\n\n\treturn responseSchema.safeParse(data);\n}\n\n/**\n * Convenience methods for common HTTP verbs\n *\n * @example\n * import { z } from \"zod\";\n * import { api } from \"@zap-studio/fetch\";\n *\n * const PostSchema = z.object({\n * id: z.number(),\n * title: z.string(),\n * content: z.string(),\n * });\n *\n * async function fetchPost(postId: number) {\n * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);\n * return post; // post is typed as { id: number; title: string; content: string; }\n * }\n */\nexport const api = {\n\tget: <TResponse>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"GET\" }),\n\n\tpost: <TResponse, TBody = unknown>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tbody?: TBody,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"POST\", body }),\n\n\tput: <TResponse, TBody = unknown>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tbody?: TBody,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"PUT\", body }),\n\n\tpatch: <TResponse, TBody = unknown>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tbody?: TBody,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"PATCH\", body }),\n\n\tdelete: <TResponse>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"DELETE\" }),\n};\n"],"mappings":";;;AA4CA,eAAsB,UACrB,KACA,gBACA,QACmE;CACnE,MAAM,EACL,MACA,SACA,yBAAyB,MACzB,eAAe,OACf,GAAG,SACA,UAAU,EAAE;CAEhB,MAAMA,cAA2B;EAChC,GAAG;EACH,SAAS,EACR,GAAI,SACJ;EACD;AAGD,KAAI,SAAS,QAAW;EACvB,MAAM,uBACL,CAAC,WACA,OAAO,YAAY,YACnB,CAAC,MAAM,QAAQ,QAAQ,IACvB,EAAE,mBAAmB,YACrB,EAAE,kBAAkB;AAEtB,MAAI,gBAAgB,SACnB,aAAY,OAAO;WACT,OAAO,SAAS,UAAU;AACpC,eAAY,OAAO;AACnB,OAAI,qBACH,CAAC,YAAY,QAAmC,kBAC/C;SAEI;AACN,eAAY,OAAO,KAAK,UAAU,KAAK;AACvC,OAAI,qBACH,CAAC,YAAY,QAAmC,kBAC/C;;;CAKJ,MAAM,WAAW,MAAM,MAAM,KAAK,YAAY;AAE9C,KAAI,CAAC,SAAS,GACb,OAAM,IAAI,WACT,QAAQ,SAAS,OAAO,IAAI,SAAS,cACrC,SAAS,QACT,SAAS,YACT,SACA;CAIF,IAAIC;AAEJ,KAAI,iBAAiB,OAEpB,KADoB,SAAS,QAAQ,IAAI,eAAe,EACvC,SAAS,mBAAmB,CAC5C,QAAO,MAAM,SAAS,MAAM;KAE5B,OAAM,IAAI,WACT,8DACA,SAAS,QACT,SAAS,YACT,SACA;UAEQ,iBAAiB,OAC3B,QAAO,MAAM,SAAS,MAAM;UAClB,iBAAiB,OAC3B,QAAO,MAAM,SAAS,MAAM;UAClB,iBAAiB,cAC3B,QAAO,MAAM,SAAS,aAAa;UACzB,iBAAiB,WAC3B,QAAO,MAAM,SAAS,UAAU;AAGjC,KAAI,uBACH,QAAO,eAAe,MAAM,KAAK;AAGlC,QAAO,eAAe,UAAU,KAAK;;;;;;;;;;;;;;;;;;;;AAqBtC,MAAa,MAAM;CAClB,MACC,KACA,QACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAO,CAAC;CAEzD,OACC,KACA,QACA,MACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAQ;EAAM,CAAC;CAEhE,MACC,KACA,QACA,MACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAO;EAAM,CAAC;CAE/D,QACC,KACA,QACA,MACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAS;EAAM,CAAC;CAEjE,SACC,KACA,QACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAU,CAAC;CAC5D"}
@@ -0,0 +1,2 @@
1
+ import { n as ResponseType, t as FetchConfig } from "../index-BkzLFXkF.js";
2
+ export { FetchConfig, ResponseType };
@@ -0,0 +1 @@
1
+ export { };
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@zap-studio/fetch",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "CHANGELOG.md"
13
+ ],
9
14
  "dependencies": {
10
15
  "zod": "^4.1.12"
11
16
  },
@@ -15,9 +20,9 @@
15
20
  "tsdown": "latest",
16
21
  "typescript": "latest",
17
22
  "vitest": "latest",
18
- "@zap-studio/typescript-config": "0.0.0",
23
+ "@zap-studio/tsdown-config": "0.0.0",
19
24
  "@zap-studio/vitest-config": "0.0.0",
20
- "@zap-studio/tsdown-config": "0.0.0"
25
+ "@zap-studio/typescript-config": "0.0.0"
21
26
  },
22
27
  "exports": {
23
28
  ".": "./dist/index.js",
@@ -1,11 +0,0 @@
1
- export class FetchError extends Error {
2
- constructor(
3
- message: string,
4
- public status: number,
5
- public statusText: string,
6
- public response: Response,
7
- ) {
8
- super(message);
9
- this.name = "FetchError";
10
- }
11
- }
package/src/index.ts DELETED
@@ -1,126 +0,0 @@
1
- import type { z } from "zod";
2
- import { FetchError } from "./errors";
3
- import type { FetchConfig, ResponseType } from "./types";
4
- import { parseResponse, prepareHeadersAndBody } from "./utils";
5
-
6
- /**
7
- * Type-safe fetch wrapper with Zod validation
8
- *
9
- * @example
10
- * import { z } from "zod";
11
- * import { safeFetch } from "@zap-studio/fetch";
12
- *
13
- * const UserSchema = z.object({
14
- * id: z.number(),
15
- * name: z.string(),
16
- * email: z.string().email(),
17
- * });
18
- *
19
- * async function getUser(userId: number) {
20
- * const user = await safeFetch(
21
- * `https://api.example.com/users/${userId}`,
22
- * UserSchema,
23
- * { method: "GET" }
24
- * );
25
- * return user; // user is typed as { id: number; name: string; email: string; }
26
- * }
27
- */
28
- export async function safeFetch<
29
- TResponse,
30
- TBody = unknown,
31
- TResponseType extends ResponseType = "json",
32
- >(
33
- resource: string,
34
- responseSchema: z.ZodType<TResponse>,
35
- config?: FetchConfig<TBody> & {
36
- throwOnValidationError?: boolean;
37
- responseType?: TResponseType;
38
- },
39
- ): Promise<
40
- TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>
41
- > {
42
- const {
43
- body,
44
- headers,
45
- throwOnValidationError = true,
46
- responseType = "json" as TResponseType,
47
- ...rest
48
- } = config || {};
49
-
50
- const { body: preparedBody, headers: preparedHeaders } =
51
- prepareHeadersAndBody(body, headers);
52
-
53
- const response = await fetch(resource, {
54
- ...rest,
55
- body: preparedBody,
56
- headers: preparedHeaders,
57
- });
58
-
59
- if (!response.ok) {
60
- throw new FetchError(
61
- `HTTP ${response.status}: ${response.statusText}`,
62
- response.status,
63
- response.statusText,
64
- response,
65
- );
66
- }
67
-
68
- const data = await parseResponse(response, responseType);
69
-
70
- return throwOnValidationError
71
- ? responseSchema.parse(data)
72
- : responseSchema.safeParse(data);
73
- }
74
-
75
- /**
76
- * Convenience methods for common HTTP verbs
77
- *
78
- * @example
79
- * import { z } from "zod";
80
- * import { api } from "@zap-studio/fetch";
81
- *
82
- * const PostSchema = z.object({
83
- * id: z.number(),
84
- * title: z.string(),
85
- * content: z.string(),
86
- * });
87
- *
88
- * async function fetchPost(postId: number) {
89
- * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
90
- * return post; // post is typed as { id: number; title: string; content: string; }
91
- * }
92
- */
93
- export const api = {
94
- get: <TResponse>(
95
- resource: string,
96
- schema: z.ZodType<TResponse>,
97
- options?: Omit<RequestInit, "method" | "body">,
98
- ) => safeFetch(resource, schema, { ...options, method: "GET" }),
99
-
100
- post: <TResponse, TBody = unknown>(
101
- resource: string,
102
- schema: z.ZodType<TResponse>,
103
- body?: TBody,
104
- options?: Omit<RequestInit, "method" | "body">,
105
- ) => safeFetch(resource, schema, { ...options, method: "POST", body }),
106
-
107
- put: <TResponse, TBody = unknown>(
108
- resource: string,
109
- schema: z.ZodType<TResponse>,
110
- body?: TBody,
111
- options?: Omit<RequestInit, "method" | "body">,
112
- ) => safeFetch(resource, schema, { ...options, method: "PUT", body }),
113
-
114
- patch: <TResponse, TBody = unknown>(
115
- resource: string,
116
- schema: z.ZodType<TResponse>,
117
- body?: TBody,
118
- options?: Omit<RequestInit, "method" | "body">,
119
- ) => safeFetch(resource, schema, { ...options, method: "PATCH", body }),
120
-
121
- delete: <TResponse>(
122
- resource: string,
123
- schema: z.ZodType<TResponse>,
124
- options?: Omit<RequestInit, "method" | "body">,
125
- ) => safeFetch(resource, schema, { ...options, method: "DELETE" }),
126
- };
@@ -1,23 +0,0 @@
1
- export type FetchConfig<TBody> = Omit<RequestInit, "body"> & {
2
- body?: TBody;
3
- throwOnValidationError?: boolean;
4
- };
5
-
6
- export type ResponseType =
7
- | "arrayBuffer"
8
- | "blob"
9
- | "bytes"
10
- | "clone"
11
- | "formData"
12
- | "json"
13
- | "text";
14
-
15
- export type ResponseTypeMap = {
16
- arrayBuffer: ArrayBuffer;
17
- blob: Blob;
18
- bytes: Uint8Array;
19
- clone: Response;
20
- formData: FormData;
21
- json: unknown;
22
- text: string;
23
- };