@zap-studio/fetch 0.5.6 → 1.1.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/dist/index.d.ts CHANGED
@@ -1,8 +1,127 @@
1
- import { $Fetch, ApiMethods, ExtendedRequestInit, FetchDefaults, FetchInput } from "./types.js";
2
- import { GLOBAL_DEFAULTS } from "./constants.js";
3
1
  import { FetchError } from "./errors.js";
4
- import { $fetch, api, createFetch } from "./fetch.js";
5
- import { mergeHeaders } from "./headers.js";
6
- import { NormalizedRequest, normalizeRequest } from "./request.js";
7
- import { resolveRequestUrl } from "./url.js";
8
- export { type $Fetch, $fetch, type ApiMethods, type ExtendedRequestInit, type FetchDefaults, FetchError, type FetchInput, GLOBAL_DEFAULTS, type NormalizedRequest, api, createFetch, mergeHeaders, normalizeRequest, resolveRequestUrl };
2
+ import { $Fetch, ApiMethods, ExtendedRequestInit, FetchDefaults, FetchInput, NormalizedRequest } from "./types.js";
3
+ import { StandardSchemaV1 } from "@zap-studio/validation";
4
+ //#region src/index.d.ts
5
+ /**
6
+ * Default options for the global $fetch
7
+ *
8
+ * These defaults are used by the top-level `$fetch` export.
9
+ * Use `createFetch(...)` when you need per-client defaults.
10
+ *
11
+ * @example
12
+ * import { GLOBAL_DEFAULTS } from "@zap-studio/fetch";
13
+ *
14
+ * console.log(GLOBAL_DEFAULTS.throwOnFetchError); // true
15
+ */
16
+ declare const GLOBAL_DEFAULTS: FetchDefaults;
17
+ /**
18
+ * Type-safe fetch wrapper with Standard Schema validation.
19
+ *
20
+ * - When `throwOnValidationError: true`: validated data of type `TSchema`
21
+ * - When `throwOnValidationError: false`: Standard Schema Result object `{ value?, issues? }`
22
+ * - When `throwOnFetchError: true`: throws `FetchError` on non-ok responses
23
+ *
24
+ * If no schema is provided, returns the raw `Response` object.
25
+ *
26
+ * @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.
27
+ * @throws {ValidationError} When a schema is provided, validation returns issues, and
28
+ * `throwOnValidationError` is `true`.
29
+ * @throws {TypeError} When both `body` and `json` are provided, when JSON request
30
+ * serialization fails, when request construction fails, when headers/search params are
31
+ * invalid, when `response.json()` cannot read the body, or when the runtime `fetch`
32
+ * implementation rejects network-level failures as `TypeError`.
33
+ * @throws {DOMException} When the runtime rejects an aborted request or response body read
34
+ * as an `AbortError` DOMException.
35
+ * @throws {SyntaxError} When a schema is provided and `response.json()` cannot parse the
36
+ * response body.
37
+ * @throws {unknown} Any error thrown or rejected by the provided Standard Schema validator.
38
+ *
39
+ * @example
40
+ * import { z } from "zod";
41
+ * import { $fetch } from "@zap-studio/fetch";
42
+ *
43
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
44
+ *
45
+ * // Basic usage (schema validation)
46
+ * const user = await $fetch("/api/users/1", UserSchema, { headers: { "Authorization": "Bearer token" } });
47
+ * console.log("Validated user:", user);
48
+ *
49
+ * // Raw usage (no schema validation and typed Response object)
50
+ * const result = await $fetch("/api/data", { method: "POST", body: JSON.stringify({ key: "value" }) });
51
+ * const json = await result.json() as ResultType;
52
+ * console.log("Raw response data:", json);
53
+ *
54
+ * // Usage with validation errors returned instead of thrown
55
+ * const result = await $fetch("/api/users/1", UserSchema, { throwOnValidationError: false });
56
+ *
57
+ * if (result.issues) {
58
+ * console.error("Validation errors:", result.issues);
59
+ * } else {
60
+ * console.log("Validated user:", result.value);
61
+ * }
62
+ */
63
+ declare function $fetch<TSchema extends StandardSchemaV1>(input: FetchInput, schema: TSchema, options: ExtendedRequestInit & {
64
+ throwOnValidationError: false;
65
+ }): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
66
+ declare function $fetch<TSchema extends StandardSchemaV1>(input: FetchInput, schema: TSchema, options?: ExtendedRequestInit & {
67
+ throwOnValidationError?: true;
68
+ }): Promise<StandardSchemaV1.InferOutput<TSchema>>;
69
+ declare function $fetch(input: FetchInput, options?: ExtendedRequestInit): Promise<Response>;
70
+ /**
71
+ * Convenience methods for common HTTP verbs.
72
+ *
73
+ * These methods always require a schema for validation.
74
+ * For raw responses without validation, use `$fetch` directly.
75
+ *
76
+ * Each method has the same throw behavior as {@link $fetch}.
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
+ declare const api: ApiMethods;
94
+ /**
95
+ * Creates a custom fetch instance with pre-configured defaults.
96
+ *
97
+ * Use this factory to create API clients with a base URL, default headers,
98
+ * and other shared configuration. Each instance is independent.
99
+ *
100
+ * The returned `$fetch` and `api` methods have the same throw behavior as the
101
+ * top-level {@link $fetch} export.
102
+ *
103
+ * @example
104
+ * import { z } from "zod";
105
+ * import { createFetch } from "@zap-studio/fetch";
106
+ *
107
+ * // Create a configured instance
108
+ * const { $fetch, api } = createFetch({
109
+ * baseURL: "https://api.example.com",
110
+ * headers: { "Authorization": "Bearer token" },
111
+ * });
112
+ *
113
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
114
+ *
115
+ * // Now use relative paths - baseURL is prepended automatically
116
+ * const user = await api.get("/users/1", UserSchema);
117
+ *
118
+ * // Or use $fetch directly
119
+ * const response = await $fetch("/users", UserSchema, { method: "POST", json: { name: "John" } });
120
+ */
121
+ declare const createFetch: (factoryOptions?: Partial<FetchDefaults>) => {
122
+ $fetch: $Fetch;
123
+ api: ApiMethods;
124
+ };
125
+ //#endregion
126
+ export { type $Fetch, $fetch, type ApiMethods, type ExtendedRequestInit, type FetchDefaults, FetchError, type FetchInput, GLOBAL_DEFAULTS, type NormalizedRequest, api, createFetch };
127
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;;cA+Ca,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwbR,OAAO,gBAAgB,kBAC3C,OAAO,YACP,QAAQ,SACR,SAAS;EAAwB;IAChC,QAAQ,iBAAiB,OAAO,iBAAiB,YAAY;iBAE1C,OAAO,gBAAgB,kBAC3C,OAAO,YACP,QAAQ,SACR,UAAU;EAAwB;IACjC,QAAQ,iBAAiB,YAAY;iBAElB,OACpB,OAAO,YACP,UAAU,sBACT,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;cAqCE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmCL,cACX,iBAAgB,QAAQ;EAExB,QAAQ;EACR,KAAK"}
package/dist/index.js CHANGED
@@ -1,7 +1,346 @@
1
- import { GLOBAL_DEFAULTS } from "./constants.js";
2
1
  import { FetchError } from "./errors.js";
3
- import { mergeHeaders } from "./headers.js";
4
- import { normalizeRequest } from "./request.js";
5
- import { resolveRequestUrl } from "./url.js";
6
- import { n as api, r as createFetch, t as $fetch } from "./fetch-D0IXjsUD.js";
7
- export { $fetch, FetchError, GLOBAL_DEFAULTS, api, createFetch, mergeHeaders, normalizeRequest, resolveRequestUrl };
2
+ import { isStandardSchema, standardValidate } from "@zap-studio/validation";
3
+ //#region src/index.ts
4
+ /**
5
+ * Default options for the global $fetch
6
+ *
7
+ * These defaults are used by the top-level `$fetch` export.
8
+ * Use `createFetch(...)` when you need per-client defaults.
9
+ *
10
+ * @example
11
+ * import { GLOBAL_DEFAULTS } from "@zap-studio/fetch";
12
+ *
13
+ * console.log(GLOBAL_DEFAULTS.throwOnFetchError); // true
14
+ */
15
+ const GLOBAL_DEFAULTS = {
16
+ baseURL: "",
17
+ throwOnFetchError: true,
18
+ throwOnValidationError: true
19
+ };
20
+ /**
21
+ * Merges two HeadersInit objects, with the second one taking precedence.
22
+ *
23
+ * @param base - Base/default headers.
24
+ * @param override - Request-level override headers.
25
+ * @returns A merged `Headers` object, or `undefined` when both inputs are empty.
26
+ * @throws {TypeError} When either header input contains invalid header names or values.
27
+ */
28
+ const mergeHeaders = (base, override) => {
29
+ if (base === void 0 && override === void 0) return;
30
+ const merged = new Headers(base);
31
+ for (const [key, value] of new Headers(override).entries()) merged.set(key, value);
32
+ return merged;
33
+ };
34
+ const EMPTY_OPTIONS = {};
35
+ /**
36
+ * Normalizes fetch `input` and request-level options into a consistent internal shape.
37
+ *
38
+ * @param input - Request URL/path or Request instance.
39
+ * @param options - Optional request options.
40
+ * @returns A normalized request structure for internal processing.
41
+ * @throws {TypeError} When cloning a `Request` fails or the merged headers are invalid.
42
+ */
43
+ const normalizeRequest = (input, options) => {
44
+ if (!(input instanceof Request)) {
45
+ const url = input instanceof URL ? input.href : input;
46
+ return {
47
+ options: options ?? EMPTY_OPTIONS,
48
+ url
49
+ };
50
+ }
51
+ const request = new Request(input);
52
+ const { headers, ...rest } = options ?? {};
53
+ const mergedHeaders = mergeHeaders(request.headers, headers);
54
+ const normalizedOptions = { ...rest };
55
+ if (mergedHeaders !== void 0) normalizedOptions.headers = mergedHeaders;
56
+ return {
57
+ options: normalizedOptions,
58
+ request,
59
+ url: request.url
60
+ };
61
+ };
62
+ /**
63
+ * Copies search params into target, overriding duplicate keys.
64
+ */
65
+ const mergeSearchParams = (target, source) => {
66
+ for (const [key, value] of new URLSearchParams(source)) target.set(key, value);
67
+ };
68
+ /**
69
+ * Ensures a URL has a trailing slash for relative URL resolution.
70
+ */
71
+ const ensureTrailingSlash = (url) => url.endsWith("/") ? url : `${url}/`;
72
+ /**
73
+ * Resolves search params by applying default params, URL params, then request params.
74
+ */
75
+ const resolveSearchParams = (url, defaultSearchParams, searchParams) => {
76
+ if (defaultSearchParams === void 0 && searchParams === void 0) return url;
77
+ const hashIndex = url.indexOf("#");
78
+ const hasFragment = hashIndex !== -1;
79
+ const urlWithoutHash = hasFragment ? url.slice(0, hashIndex) : url;
80
+ const hash = hasFragment ? url.slice(hashIndex + 1) : "";
81
+ const queryIndex = urlWithoutHash.indexOf("?");
82
+ const pathname = queryIndex === -1 ? urlWithoutHash : urlWithoutHash.slice(0, queryIndex);
83
+ const urlSearchParams = queryIndex === -1 ? void 0 : urlWithoutHash.slice(queryIndex + 1);
84
+ const resolvedSearchParams = new URLSearchParams();
85
+ mergeSearchParams(resolvedSearchParams, defaultSearchParams);
86
+ mergeSearchParams(resolvedSearchParams, urlSearchParams);
87
+ mergeSearchParams(resolvedSearchParams, searchParams);
88
+ const resolvedSearch = resolvedSearchParams.toString();
89
+ const fragmentSuffix = hasFragment ? `#${hash}` : "";
90
+ if (resolvedSearch.length === 0) return `${pathname}${fragmentSuffix}`;
91
+ return `${pathname}?${resolvedSearch}${fragmentSuffix}`;
92
+ };
93
+ /**
94
+ * Resolves final request URL by applying baseURL and layered search params.
95
+ *
96
+ * Search param precedence:
97
+ * 1. `defaults.searchParams`
98
+ * 2. search params already present in `resourceUrl`
99
+ * 3. per-request `searchParams`
100
+ *
101
+ * @throws {TypeError} When `baseURL` and `resourceUrl` cannot be resolved by
102
+ * `URL`, or when default/per-request search params cannot be converted by
103
+ * `URLSearchParams`.
104
+ */
105
+ const resolveRequestUrl = (resourceUrl, defaults, searchParams) => {
106
+ const url = defaults.baseURL ? new URL(resourceUrl, ensureTrailingSlash(defaults.baseURL)).toString() : resourceUrl;
107
+ return resolveSearchParams(url, defaults.searchParams, searchParams);
108
+ };
109
+ /**
110
+ * Normalizes request-level options into a final RequestInit payload and runtime flags.
111
+ *
112
+ * @param options - Request-level options.
113
+ * @param defaults - Client-level defaults.
114
+ * @returns Fully merged request init payload and effective runtime flags.
115
+ */
116
+ const prepareRequestInit = (options, defaults) => {
117
+ const { headers, json, searchParams, throwOnFetchError = defaults.throwOnFetchError, throwOnValidationError = defaults.throwOnValidationError, ...rest } = options;
118
+ const init = { ...rest };
119
+ const mergedHeaders = mergeHeaders(defaults.headers, headers);
120
+ if (mergedHeaders !== void 0) init.headers = mergedHeaders;
121
+ if (json !== void 0) {
122
+ if (init.body !== void 0 && init.body !== null) throw new TypeError("Cannot provide both `body` and `json`.");
123
+ init.body = JSON.stringify(json);
124
+ const requestHeaders = new Headers(init.headers);
125
+ if (!requestHeaders.has("Content-Type")) requestHeaders.set("Content-Type", "application/json");
126
+ init.headers = requestHeaders;
127
+ }
128
+ return {
129
+ init,
130
+ searchParams,
131
+ throwOnFetchError,
132
+ throwOnValidationError
133
+ };
134
+ };
135
+ /**
136
+ * Logs a fetch response: `debug` for 2xx, `warn` otherwise.
137
+ */
138
+ const logResponse = (logger, method, url, response) => {
139
+ const context = {
140
+ method,
141
+ status: response.status,
142
+ url
143
+ };
144
+ if (response.ok) logger?.debug("fetch response", context);
145
+ else logger?.warn("fetch response", context);
146
+ };
147
+ /**
148
+ * Validates the raw JSON payload against `schema`, logging a `fetch
149
+ * validation failed` message at `error` on failure regardless of throw mode.
150
+ *
151
+ * @throws {unknown} Any error thrown by `standardValidate` in throw mode.
152
+ */
153
+ const validateResponse = async (raw, schema, throwOnValidationError, logger, url) => {
154
+ if (throwOnValidationError) try {
155
+ return await standardValidate(raw, schema, { throwOnError: true });
156
+ } catch (error) {
157
+ logger?.error("fetch validation failed", {
158
+ error,
159
+ url
160
+ });
161
+ throw error;
162
+ }
163
+ const result = await standardValidate(raw, schema, { throwOnError: false });
164
+ if (result.issues) logger?.error("fetch validation failed", {
165
+ issues: result.issues,
166
+ url
167
+ });
168
+ return result;
169
+ };
170
+ /**
171
+ * Internal fetch implementation used by both $fetch and createFetch.
172
+ *
173
+ * This function normalizes request input, resolves final URL + query params,
174
+ * executes `fetch`, optionally throws `FetchError`, and optionally validates
175
+ * JSON response payloads using Standard Schema.
176
+ *
177
+ * @param input - Request URL, path, or Request object.
178
+ * @param schema - Optional Standard Schema for response validation.
179
+ * @param options - Optional request options and package-specific flags.
180
+ * @param defaults - Effective client defaults.
181
+ * @returns Raw `Response` when no schema is provided; otherwise validated output.
182
+ * @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.
183
+ * @throws {ValidationError} When a schema is provided, validation returns issues, and
184
+ * `throwOnValidationError` is `true`.
185
+ * @throws {TypeError} When both `body` and `json` are provided, when JSON request
186
+ * serialization fails, when request construction fails, when headers/search params are
187
+ * invalid, when `response.json()` cannot read the body, or when the runtime `fetch`
188
+ * implementation rejects network-level failures as `TypeError`.
189
+ * @throws {DOMException} When the runtime rejects an aborted request or response body read
190
+ * as an `AbortError` DOMException.
191
+ * @throws {SyntaxError} When a schema is provided and `response.json()` cannot parse the
192
+ * response body.
193
+ * @throws {unknown} Any error thrown or rejected by the provided Standard Schema validator.
194
+ */
195
+ const fetchInternal = async (input, schema, options, defaults) => {
196
+ const { logger } = defaults;
197
+ const request = normalizeRequest(input, options);
198
+ const { init, searchParams, throwOnFetchError, throwOnValidationError } = prepareRequestInit(request.options, defaults);
199
+ const url = resolveRequestUrl(request.url, defaults, searchParams);
200
+ const method = init.method ?? "GET";
201
+ logger?.debug("fetch request", {
202
+ method,
203
+ url
204
+ });
205
+ const response = request.request ? await fetch(new Request(url, request.request), init) : await fetch(url, init);
206
+ logResponse(logger, method, url, response);
207
+ if (throwOnFetchError && !response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);
208
+ if (schema === void 0) return response;
209
+ const raw = await response.json();
210
+ return await validateResponse(raw, schema, throwOnValidationError, logger, url);
211
+ };
212
+ /**
213
+ * Creates an HTTP method helper bound to a fetch function.
214
+ *
215
+ * The returned function mirrors `$Fetch` overloads but forces the provided
216
+ * HTTP method (`GET`, `POST`, etc.) into request options.
217
+ *
218
+ * @param fetchFn - Fetch function to wrap.
219
+ * @param method - HTTP method to enforce.
220
+ * @returns Method-bound fetch function.
221
+ * @throws {unknown} Any error thrown or rejected by `fetchFn` when the returned method-bound
222
+ * fetch function is called.
223
+ *
224
+ * @example
225
+ * const get = createMethod($fetch, "GET");
226
+ * const user = await get("/users/1", UserSchema);
227
+ */
228
+ const createMethod = (fetchFn, method) => {
229
+ /**
230
+ * Method-bound `$Fetch` implementation.
231
+ *
232
+ * Resolves schema/option overloads and injects the configured HTTP method.
233
+ */
234
+ async function methodFetch(input, schemaOrOptions, optionsOrUndefined) {
235
+ if (isStandardSchema(schemaOrOptions)) {
236
+ if (optionsOrUndefined?.throwOnValidationError === false) return await fetchFn(input, schemaOrOptions, {
237
+ ...optionsOrUndefined,
238
+ method,
239
+ throwOnValidationError: false
240
+ });
241
+ const { throwOnValidationError, ...restOptions } = optionsOrUndefined ?? {};
242
+ if (throwOnValidationError === true) return await fetchFn(input, schemaOrOptions, {
243
+ ...restOptions,
244
+ method,
245
+ throwOnValidationError: true
246
+ });
247
+ return await fetchFn(input, schemaOrOptions, {
248
+ ...restOptions,
249
+ method
250
+ });
251
+ }
252
+ return await fetchFn(input, {
253
+ ...schemaOrOptions,
254
+ method
255
+ });
256
+ }
257
+ return methodFetch;
258
+ };
259
+ async function $fetch(input, schemaOrOptions, optionsOrUndefined) {
260
+ const [schema, options] = isStandardSchema(schemaOrOptions) ? [schemaOrOptions, optionsOrUndefined] : [void 0, schemaOrOptions];
261
+ return await fetchInternal(input, schema, options, GLOBAL_DEFAULTS);
262
+ }
263
+ /**
264
+ * Convenience methods for common HTTP verbs.
265
+ *
266
+ * These methods always require a schema for validation.
267
+ * For raw responses without validation, use `$fetch` directly.
268
+ *
269
+ * Each method has the same throw behavior as {@link $fetch}.
270
+ *
271
+ * @example
272
+ * import { z } from "zod";
273
+ * import { api } from "@zap-studio/fetch";
274
+ *
275
+ * const PostSchema = z.object({
276
+ * id: z.number(),
277
+ * title: z.string(),
278
+ * content: z.string(),
279
+ * });
280
+ *
281
+ * async function fetchPost(postId: number) {
282
+ * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
283
+ * return post; // post is typed as { id: number; title: string; content: string; }
284
+ * }
285
+ */
286
+ const api = {
287
+ delete: createMethod($fetch, "DELETE"),
288
+ get: createMethod($fetch, "GET"),
289
+ patch: createMethod($fetch, "PATCH"),
290
+ post: createMethod($fetch, "POST"),
291
+ put: createMethod($fetch, "PUT")
292
+ };
293
+ /**
294
+ * Creates a custom fetch instance with pre-configured defaults.
295
+ *
296
+ * Use this factory to create API clients with a base URL, default headers,
297
+ * and other shared configuration. Each instance is independent.
298
+ *
299
+ * The returned `$fetch` and `api` methods have the same throw behavior as the
300
+ * top-level {@link $fetch} export.
301
+ *
302
+ * @example
303
+ * import { z } from "zod";
304
+ * import { createFetch } from "@zap-studio/fetch";
305
+ *
306
+ * // Create a configured instance
307
+ * const { $fetch, api } = createFetch({
308
+ * baseURL: "https://api.example.com",
309
+ * headers: { "Authorization": "Bearer token" },
310
+ * });
311
+ *
312
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
313
+ *
314
+ * // Now use relative paths - baseURL is prepended automatically
315
+ * const user = await api.get("/users/1", UserSchema);
316
+ *
317
+ * // Or use $fetch directly
318
+ * const response = await $fetch("/users", UserSchema, { method: "POST", json: { name: "John" } });
319
+ */
320
+ const createFetch = (factoryOptions = {}) => {
321
+ const defaults = {
322
+ ...GLOBAL_DEFAULTS,
323
+ ...factoryOptions,
324
+ baseURL: factoryOptions.baseURL ?? GLOBAL_DEFAULTS.baseURL,
325
+ throwOnFetchError: factoryOptions.throwOnFetchError ?? GLOBAL_DEFAULTS.throwOnFetchError,
326
+ throwOnValidationError: factoryOptions.throwOnValidationError ?? GLOBAL_DEFAULTS.throwOnValidationError
327
+ };
328
+ async function customFetch(input, schemaOrOptions, optionsOrUndefined) {
329
+ const [schema, options] = isStandardSchema(schemaOrOptions) ? [schemaOrOptions, optionsOrUndefined] : [void 0, schemaOrOptions];
330
+ return await fetchInternal(input, schema, options, defaults);
331
+ }
332
+ return {
333
+ $fetch: customFetch,
334
+ api: {
335
+ delete: createMethod(customFetch, "DELETE"),
336
+ get: createMethod(customFetch, "GET"),
337
+ patch: createMethod(customFetch, "PATCH"),
338
+ post: createMethod(customFetch, "POST"),
339
+ put: createMethod(customFetch, "PUT")
340
+ }
341
+ };
342
+ };
343
+ //#endregion
344
+ export { $fetch, FetchError, GLOBAL_DEFAULTS, api, createFetch };
345
+
346
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Public entrypoint for the fetch package.\n *\n * Exports `$fetch`, `api`, `createFetch`, `FetchError`, `GLOBAL_DEFAULTS`,\n * and the public type contracts. `FetchError` and the type contracts are\n * also available from dedicated subpaths (`@zap-studio/fetch/errors`,\n * `@zap-studio/fetch/types`) for consumers who prefer granular imports. All\n * exports are side-effect free and tree-shakeable.\n *\n * @module @zap-studio/fetch\n */\n\nimport type { Logger } from \"@zap-studio/logger\";\nimport { isStandardSchema, standardValidate } from \"@zap-studio/validation\";\nimport type { StandardSchemaV1 } from \"@zap-studio/validation\";\n\nimport { FetchError } from \"./errors.js\";\nimport type {\n $Fetch,\n ApiMethods,\n ExtendedRequestInit,\n FetchDefaults,\n FetchInput,\n NormalizedRequest,\n} from \"./types.js\";\n\nexport { FetchError } from \"./errors.js\";\nexport type {\n $Fetch,\n ApiMethods,\n ExtendedRequestInit,\n FetchDefaults,\n FetchInput,\n NormalizedRequest,\n} from \"./types.js\";\n\n/**\n * Default options for the global $fetch\n *\n * These defaults are used by the top-level `$fetch` export.\n * Use `createFetch(...)` when you need per-client defaults.\n *\n * @example\n * import { GLOBAL_DEFAULTS } from \"@zap-studio/fetch\";\n *\n * console.log(GLOBAL_DEFAULTS.throwOnFetchError); // true\n */\nexport const GLOBAL_DEFAULTS: FetchDefaults = {\n baseURL: \"\",\n throwOnFetchError: true,\n throwOnValidationError: true,\n};\n\n/**\n * Merges two HeadersInit objects, with the second one taking precedence.\n *\n * @param base - Base/default headers.\n * @param override - Request-level override headers.\n * @returns A merged `Headers` object, or `undefined` when both inputs are empty.\n * @throws {TypeError} When either header input contains invalid header names or values.\n */\nconst mergeHeaders = (\n base?: HeadersInit,\n override?: HeadersInit\n): Headers | undefined => {\n if (base === undefined && override === undefined) {\n return undefined;\n }\n\n const merged = new Headers(base);\n for (const [key, value] of new Headers(override).entries()) {\n merged.set(key, value);\n }\n return merged;\n};\n\nconst EMPTY_OPTIONS = {} as ExtendedRequestInit;\n\n/**\n * Normalizes fetch `input` and request-level options into a consistent internal shape.\n *\n * @param input - Request URL/path or Request instance.\n * @param options - Optional request options.\n * @returns A normalized request structure for internal processing.\n * @throws {TypeError} When cloning a `Request` fails or the merged headers are invalid.\n */\nconst normalizeRequest = (\n input: FetchInput,\n options?: ExtendedRequestInit\n): NormalizedRequest => {\n if (!(input instanceof Request)) {\n const url = input instanceof URL ? input.href : input;\n return {\n options: options ?? EMPTY_OPTIONS,\n url,\n };\n }\n\n const request = new Request(input);\n const { headers, ...rest } = options ?? {};\n const mergedHeaders = mergeHeaders(request.headers, headers);\n const normalizedOptions = { ...rest } as ExtendedRequestInit;\n\n if (mergedHeaders !== undefined) {\n normalizedOptions.headers = mergedHeaders;\n }\n\n return {\n options: normalizedOptions,\n request,\n url: request.url,\n };\n};\n\n/**\n * Copies search params into target, overriding duplicate keys.\n */\nconst mergeSearchParams = (\n target: URLSearchParams,\n source: ExtendedRequestInit[\"searchParams\"] | undefined\n): void => {\n for (const [key, value] of new URLSearchParams(source)) {\n target.set(key, value);\n }\n};\n\n/**\n * Ensures a URL has a trailing slash for relative URL resolution.\n */\nconst ensureTrailingSlash = (url: string): string =>\n url.endsWith(\"/\") ? url : `${url}/`;\n\n/**\n * Resolves search params by applying default params, URL params, then request params.\n */\nconst resolveSearchParams = (\n url: string,\n defaultSearchParams: FetchDefaults[\"searchParams\"] | undefined,\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined\n): string => {\n if (defaultSearchParams === undefined && searchParams === undefined) {\n return url;\n }\n\n const hashIndex = url.indexOf(\"#\");\n const hasFragment = hashIndex !== -1;\n const urlWithoutHash = hasFragment ? url.slice(0, hashIndex) : url;\n const hash = hasFragment ? url.slice(hashIndex + 1) : \"\";\n const queryIndex = urlWithoutHash.indexOf(\"?\");\n const pathname =\n queryIndex === -1 ? urlWithoutHash : urlWithoutHash.slice(0, queryIndex);\n const urlSearchParams =\n queryIndex === -1 ? undefined : urlWithoutHash.slice(queryIndex + 1);\n const resolvedSearchParams = new URLSearchParams();\n\n mergeSearchParams(resolvedSearchParams, defaultSearchParams);\n mergeSearchParams(resolvedSearchParams, urlSearchParams);\n mergeSearchParams(resolvedSearchParams, searchParams);\n\n const resolvedSearch = resolvedSearchParams.toString();\n const fragmentSuffix = hasFragment ? `#${hash}` : \"\";\n\n if (resolvedSearch.length === 0) {\n return `${pathname}${fragmentSuffix}`;\n }\n\n return `${pathname}?${resolvedSearch}${fragmentSuffix}`;\n};\n\n/**\n * Resolves final request URL by applying baseURL and layered search params.\n *\n * Search param precedence:\n * 1. `defaults.searchParams`\n * 2. search params already present in `resourceUrl`\n * 3. per-request `searchParams`\n *\n * @throws {TypeError} When `baseURL` and `resourceUrl` cannot be resolved by\n * `URL`, or when default/per-request search params cannot be converted by\n * `URLSearchParams`.\n */\nconst resolveRequestUrl = (\n resourceUrl: string,\n defaults: FetchDefaults,\n searchParams?: ExtendedRequestInit[\"searchParams\"]\n): string => {\n const url = defaults.baseURL\n ? new URL(resourceUrl, ensureTrailingSlash(defaults.baseURL)).toString()\n : resourceUrl;\n\n return resolveSearchParams(url, defaults.searchParams, searchParams);\n};\n\n/**\n * Normalizes request-level options into a final RequestInit payload and runtime flags.\n *\n * @param options - Request-level options.\n * @param defaults - Client-level defaults.\n * @returns Fully merged request init payload and effective runtime flags.\n */\nconst prepareRequestInit = (\n options: ExtendedRequestInit,\n defaults: FetchDefaults\n): {\n init: RequestInit;\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined;\n throwOnFetchError: boolean;\n throwOnValidationError: boolean;\n} => {\n const {\n headers,\n json,\n searchParams,\n throwOnFetchError = defaults.throwOnFetchError,\n throwOnValidationError = defaults.throwOnValidationError,\n ...rest\n } = options;\n\n const init: RequestInit = { ...rest };\n const mergedHeaders = mergeHeaders(defaults.headers, headers);\n if (mergedHeaders !== undefined) {\n init.headers = mergedHeaders;\n }\n\n if (json !== undefined) {\n if (init.body !== undefined && init.body !== null) {\n throw new TypeError(\"Cannot provide both `body` and `json`.\");\n }\n\n init.body = JSON.stringify(json);\n const requestHeaders = new Headers(init.headers);\n if (!requestHeaders.has(\"Content-Type\")) {\n requestHeaders.set(\"Content-Type\", \"application/json\");\n }\n init.headers = requestHeaders;\n }\n\n return {\n init,\n searchParams,\n throwOnFetchError,\n throwOnValidationError,\n };\n};\n\n/**\n * Logs a fetch response: `debug` for 2xx, `warn` otherwise.\n */\nconst logResponse = (\n logger: Logger | undefined,\n method: string,\n url: string,\n response: Response\n): void => {\n const context = { method, status: response.status, url };\n if (response.ok) {\n logger?.debug(\"fetch response\", context);\n } else {\n logger?.warn(\"fetch response\", context);\n }\n};\n\n/**\n * Validates the raw JSON payload against `schema`, logging a `fetch\n * validation failed` message at `error` on failure regardless of throw mode.\n *\n * @throws {unknown} Any error thrown by `standardValidate` in throw mode.\n */\nconst validateResponse = async (\n raw: unknown,\n schema: StandardSchemaV1,\n throwOnValidationError: boolean,\n logger: Logger | undefined,\n url: string\n): Promise<unknown> => {\n if (throwOnValidationError) {\n try {\n return await standardValidate(raw, schema, { throwOnError: true });\n } catch (error) {\n logger?.error(\"fetch validation failed\", { error, url });\n throw error;\n }\n }\n\n const result = await standardValidate(raw, schema, { throwOnError: false });\n if (result.issues) {\n logger?.error(\"fetch validation failed\", { issues: result.issues, url });\n }\n return result;\n};\n\n/**\n * Internal fetch implementation used by both $fetch and createFetch.\n *\n * This function normalizes request input, resolves final URL + query params,\n * executes `fetch`, optionally throws `FetchError`, and optionally validates\n * JSON response payloads using Standard Schema.\n *\n * @param input - Request URL, path, or Request object.\n * @param schema - Optional Standard Schema for response validation.\n * @param options - Optional request options and package-specific flags.\n * @param defaults - Effective client defaults.\n * @returns Raw `Response` when no schema is provided; otherwise validated output.\n * @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.\n * @throws {ValidationError} When a schema is provided, validation returns issues, and\n * `throwOnValidationError` is `true`.\n * @throws {TypeError} When both `body` and `json` are provided, when JSON request\n * serialization fails, when request construction fails, when headers/search params are\n * invalid, when `response.json()` cannot read the body, or when the runtime `fetch`\n * implementation rejects network-level failures as `TypeError`.\n * @throws {DOMException} When the runtime rejects an aborted request or response body read\n * as an `AbortError` DOMException.\n * @throws {SyntaxError} When a schema is provided and `response.json()` cannot parse the\n * response body.\n * @throws {unknown} Any error thrown or rejected by the provided Standard Schema validator.\n */\nconst fetchInternal = async (\n input: FetchInput,\n schema: StandardSchemaV1 | undefined,\n options: ExtendedRequestInit | undefined,\n defaults: FetchDefaults\n): Promise<unknown> => {\n const { logger } = defaults;\n const request = normalizeRequest(input, options);\n const { init, searchParams, throwOnFetchError, throwOnValidationError } =\n prepareRequestInit(request.options, defaults);\n const url = resolveRequestUrl(request.url, defaults, searchParams);\n const method = init.method ?? \"GET\";\n\n logger?.debug(\"fetch request\", { method, url });\n\n const response = request.request\n ? await fetch(new Request(url, request.request), init)\n : await fetch(url, init);\n\n logResponse(logger, method, url, response);\n\n if (throwOnFetchError && !response.ok) {\n throw new FetchError(\n `HTTP ${response.status}: ${response.statusText}`,\n response\n );\n }\n\n if (schema === undefined) {\n return response;\n }\n\n const raw: unknown = await response.json();\n return await validateResponse(\n raw,\n schema,\n throwOnValidationError,\n logger,\n url\n );\n};\n\n/**\n * Creates an HTTP method helper bound to a fetch function.\n *\n * The returned function mirrors `$Fetch` overloads but forces the provided\n * HTTP method (`GET`, `POST`, etc.) into request options.\n *\n * @param fetchFn - Fetch function to wrap.\n * @param method - HTTP method to enforce.\n * @returns Method-bound fetch function.\n * @throws {unknown} Any error thrown or rejected by `fetchFn` when the returned method-bound\n * fetch function is called.\n *\n * @example\n * const get = createMethod($fetch, \"GET\");\n * const user = await get(\"/users/1\", UserSchema);\n */\nconst createMethod = (fetchFn: $Fetch, method: string): $Fetch => {\n function methodFetch<TSchema extends StandardSchemaV1>(\n input: FetchInput,\n schema: TSchema,\n options: ExtendedRequestInit & {\n throwOnValidationError: false;\n }\n ): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;\n\n function methodFetch<TSchema extends StandardSchemaV1>(\n input: FetchInput,\n schema: TSchema,\n options?: ExtendedRequestInit & {\n throwOnValidationError?: true;\n }\n ): Promise<StandardSchemaV1.InferOutput<TSchema>>;\n\n function methodFetch(\n input: FetchInput,\n options?: ExtendedRequestInit\n ): Promise<Response>;\n\n /**\n * Method-bound `$Fetch` implementation.\n *\n * Resolves schema/option overloads and injects the configured HTTP method.\n */\n async function methodFetch(\n input: FetchInput,\n schemaOrOptions?: StandardSchemaV1 | ExtendedRequestInit,\n optionsOrUndefined?: ExtendedRequestInit\n ): Promise<unknown> {\n if (isStandardSchema(schemaOrOptions)) {\n if (optionsOrUndefined?.throwOnValidationError === false) {\n return await fetchFn(input, schemaOrOptions, {\n ...optionsOrUndefined,\n method,\n throwOnValidationError: false,\n });\n }\n\n const { throwOnValidationError, ...restOptions } =\n optionsOrUndefined ?? {};\n\n if (throwOnValidationError === true) {\n return await fetchFn(input, schemaOrOptions, {\n ...restOptions,\n method,\n throwOnValidationError: true,\n });\n }\n\n return await fetchFn(input, schemaOrOptions, {\n ...restOptions,\n method,\n });\n }\n\n return await fetchFn(input, {\n ...schemaOrOptions,\n method,\n });\n }\n\n return methodFetch;\n};\n\n/**\n * Type-safe fetch wrapper with Standard Schema validation.\n *\n * - When `throwOnValidationError: true`: validated data of type `TSchema`\n * - When `throwOnValidationError: false`: Standard Schema Result object `{ value?, issues? }`\n * - When `throwOnFetchError: true`: throws `FetchError` on non-ok responses\n *\n * If no schema is provided, returns the raw `Response` object.\n *\n * @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.\n * @throws {ValidationError} When a schema is provided, validation returns issues, and\n * `throwOnValidationError` is `true`.\n * @throws {TypeError} When both `body` and `json` are provided, when JSON request\n * serialization fails, when request construction fails, when headers/search params are\n * invalid, when `response.json()` cannot read the body, or when the runtime `fetch`\n * implementation rejects network-level failures as `TypeError`.\n * @throws {DOMException} When the runtime rejects an aborted request or response body read\n * as an `AbortError` DOMException.\n * @throws {SyntaxError} When a schema is provided and `response.json()` cannot parse the\n * response body.\n * @throws {unknown} Any error thrown or rejected by the provided Standard Schema validator.\n *\n * @example\n * import { z } from \"zod\";\n * import { $fetch } from \"@zap-studio/fetch\";\n *\n * const UserSchema = z.object({ id: z.number(), name: z.string() });\n *\n * // Basic usage (schema validation)\n * const user = await $fetch(\"/api/users/1\", UserSchema, { headers: { \"Authorization\": \"Bearer token\" } });\n * console.log(\"Validated user:\", user);\n *\n * // Raw usage (no schema validation and typed Response object)\n * const result = await $fetch(\"/api/data\", { method: \"POST\", body: JSON.stringify({ key: \"value\" }) });\n * const json = await result.json() as ResultType;\n * console.log(\"Raw response data:\", json);\n *\n * // Usage with validation errors returned instead of thrown\n * const result = await $fetch(\"/api/users/1\", UserSchema, { throwOnValidationError: false });\n *\n * if (result.issues) {\n * console.error(\"Validation errors:\", result.issues);\n * } else {\n * console.log(\"Validated user:\", result.value);\n * }\n */\nexport async function $fetch<TSchema extends StandardSchemaV1>(\n input: FetchInput,\n schema: TSchema,\n options: ExtendedRequestInit & { throwOnValidationError: false }\n): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;\n\nexport async function $fetch<TSchema extends StandardSchemaV1>(\n input: FetchInput,\n schema: TSchema,\n options?: ExtendedRequestInit & { throwOnValidationError?: true }\n): Promise<StandardSchemaV1.InferOutput<TSchema>>;\n\nexport async function $fetch(\n input: FetchInput,\n options?: ExtendedRequestInit\n): Promise<Response>;\n\nexport async function $fetch(\n input: FetchInput,\n schemaOrOptions?: StandardSchemaV1 | ExtendedRequestInit,\n optionsOrUndefined?: ExtendedRequestInit\n): Promise<unknown> {\n const [schema, options] = isStandardSchema(schemaOrOptions)\n ? [schemaOrOptions, optionsOrUndefined]\n : [undefined, schemaOrOptions];\n\n return await fetchInternal(input, schema, options, GLOBAL_DEFAULTS);\n}\n\n/**\n * Convenience methods for common HTTP verbs.\n *\n * These methods always require a schema for validation.\n * For raw responses without validation, use `$fetch` directly.\n *\n * Each method has the same throw behavior as {@link $fetch}.\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: ApiMethods = {\n delete: createMethod($fetch, \"DELETE\"),\n get: createMethod($fetch, \"GET\"),\n patch: createMethod($fetch, \"PATCH\"),\n post: createMethod($fetch, \"POST\"),\n put: createMethod($fetch, \"PUT\"),\n};\n\n/**\n * Creates a custom fetch instance with pre-configured defaults.\n *\n * Use this factory to create API clients with a base URL, default headers,\n * and other shared configuration. Each instance is independent.\n *\n * The returned `$fetch` and `api` methods have the same throw behavior as the\n * top-level {@link $fetch} export.\n *\n * @example\n * import { z } from \"zod\";\n * import { createFetch } from \"@zap-studio/fetch\";\n *\n * // Create a configured instance\n * const { $fetch, api } = createFetch({\n * baseURL: \"https://api.example.com\",\n * headers: { \"Authorization\": \"Bearer token\" },\n * });\n *\n * const UserSchema = z.object({ id: z.number(), name: z.string() });\n *\n * // Now use relative paths - baseURL is prepended automatically\n * const user = await api.get(\"/users/1\", UserSchema);\n *\n * // Or use $fetch directly\n * const response = await $fetch(\"/users\", UserSchema, { method: \"POST\", json: { name: \"John\" } });\n */\nexport const createFetch = (\n factoryOptions: Partial<FetchDefaults> = {}\n): {\n $fetch: $Fetch;\n api: ApiMethods;\n} => {\n const defaults: FetchDefaults = {\n ...GLOBAL_DEFAULTS,\n ...factoryOptions,\n baseURL: factoryOptions.baseURL ?? GLOBAL_DEFAULTS.baseURL,\n throwOnFetchError:\n factoryOptions.throwOnFetchError ?? GLOBAL_DEFAULTS.throwOnFetchError,\n throwOnValidationError:\n factoryOptions.throwOnValidationError ??\n GLOBAL_DEFAULTS.throwOnValidationError,\n };\n\n async function customFetch<TSchema extends StandardSchemaV1>(\n input: FetchInput,\n schema: TSchema,\n options: ExtendedRequestInit & { throwOnValidationError: false }\n ): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;\n\n async function customFetch<TSchema extends StandardSchemaV1>(\n input: FetchInput,\n schema: TSchema,\n options?: ExtendedRequestInit & {\n throwOnValidationError?: true;\n }\n ): Promise<StandardSchemaV1.InferOutput<TSchema>>;\n\n async function customFetch(\n input: FetchInput,\n options?: ExtendedRequestInit\n ): Promise<Response>;\n\n async function customFetch(\n input: FetchInput,\n schemaOrOptions?: StandardSchemaV1 | ExtendedRequestInit,\n optionsOrUndefined?: ExtendedRequestInit\n ): Promise<unknown> {\n const [schema, options] = isStandardSchema(schemaOrOptions)\n ? [schemaOrOptions, optionsOrUndefined]\n : [undefined, schemaOrOptions];\n\n return await fetchInternal(input, schema, options, defaults);\n }\n\n const customApi = {\n delete: createMethod(customFetch, \"DELETE\"),\n get: createMethod(customFetch, \"GET\"),\n patch: createMethod(customFetch, \"PATCH\"),\n post: createMethod(customFetch, \"POST\"),\n put: createMethod(customFetch, \"PUT\"),\n };\n\n return {\n $fetch: customFetch,\n api: customApi,\n };\n};\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import { StandardSchemaV1 } from "@zap-studio/validation";
2
+ import { Logger } from "@zap-studio/logger";
2
3
  //#region src/types.d.ts
3
4
  /**
4
5
  * Accepted `fetch` input type (`string`, `URL`, or `Request`).
6
+ *
7
+ * @example
8
+ * const input: FetchInput = "/users/1";
9
+ * const withUrl: FetchInput = new URL("https://api.example.com/users/1");
5
10
  */
6
11
  type FetchInput = Parameters<typeof fetch>[0];
7
12
  type URLSearchParamsInput = ConstructorParameters<typeof URLSearchParams>[0];
@@ -80,9 +85,24 @@ interface FetchDefaults {
80
85
  * @default true
81
86
  */
82
87
  throwOnValidationError: boolean;
88
+ /**
89
+ * Optional logger for request/response internals. When omitted, nothing
90
+ * is logged.
91
+ *
92
+ * Logs outgoing requests at `debug`, response status at `debug` (2xx) or
93
+ * `warn` (non-2xx), and validation failures at `error`.
94
+ */
95
+ logger?: Logger;
83
96
  }
84
97
  /**
85
98
  * Type-safe fetch function with Standard Schema validation support
99
+ *
100
+ * @example
101
+ * import { z } from "zod";
102
+ *
103
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
104
+ * const fetchUser: $Fetch = $fetch;
105
+ * const user = await fetchUser("/users/1", UserSchema);
86
106
  */
87
107
  interface $Fetch {
88
108
  /**
@@ -135,6 +155,23 @@ interface $Fetch {
135
155
  */
136
156
  (input: FetchInput, options?: ExtendedRequestInit): Promise<Response>;
137
157
  }
158
+ /**
159
+ * Normalized representation used by internal request execution.
160
+ *
161
+ * @example
162
+ * const normalized: NormalizedRequest = {
163
+ * url: "https://api.example.com/users",
164
+ * options: {},
165
+ * };
166
+ */
167
+ interface NormalizedRequest {
168
+ /** Resolved string URL from the input (string or `URL`; `Request` uses `request.url`). */
169
+ url: string;
170
+ /** Original `Request` clone, present when the input was a `Request`. */
171
+ request?: Request;
172
+ /** Normalized request options merged with `Request` headers. */
173
+ options: ExtendedRequestInit;
174
+ }
138
175
  /**
139
176
  * API HTTP method-specific fetch functions
140
177
  *
@@ -164,5 +201,5 @@ interface ApiMethods {
164
201
  put: $Fetch;
165
202
  }
166
203
  //#endregion
167
- export { $Fetch, ApiMethods, ExtendedRequestInit, FetchDefaults, FetchInput };
204
+ export { $Fetch, ApiMethods, ExtendedRequestInit, FetchDefaults, FetchInput, NormalizedRequest };
168
205
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;KAWY,aAAa,kBAAkB;KAEtC,uBAAuB,6BAA6B;KAEpD,kBAAkB;EACrB;;KAGG,eAAe,KAAK;;;;;EAKvB;EACA;;UAGQ;;;;;EAKR,eAAe;;;;;EAKf;;;;;EAKA;;;;;;;;;;;;KAaU,uBAAuB,kBAAkB,gBACnD;;;;;;;;;;;UAYe;;;;;EAKf;;;;;EAKA,UAAU;;;;;EAKV,eAAe;;;;;EAKf;;;;;EAKA;;;;;UAMe;;;;;;;;;;;;;;;;GAgBd,gBAAgB,kBACf,OAAO,YACP,QAAQ,SACR,SAAS;IAAwB;MAChC,QAAQ,iBAAiB,OAAO,iBAAiB,YAAY;;;;;;;;;;;;;;;;;GAkB/D,gBAAgB,kBACf,OAAO,YACP,QAAQ,SACR,UAAU;IACR;MAED,QAAQ,iBAAiB,YAAY;;;;;;;;;;;;GAavC,OAAO,YAAY,UAAU,sBAAsB,QAAQ;;;;;;;;UAS7C;;;;EAIf,QAAQ;;;;EAIR,KAAK;;;;EAIL,OAAO;;;;EAIP,MAAM;;;;EAIN,KAAK"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;KAgBY,aAAa,kBAAkB;KAEtC,uBAAuB,6BAA6B;KAEpD,kBAAkB;EACrB;;KAGG,eAAe,KAAK;;;;;EAKvB;EACA;;UAGQ;;;;;EAKR,eAAe;;;;;EAKf;;;;;EAKA;;;;;;;;;;;;KAaU,uBAAuB,kBAAkB,gBACnD;;;;;;;;;;;UAYe;;;;;EAKf;;;;;EAKA,UAAU;;;;;EAKV,eAAe;;;;;EAKf;;;;;EAKA;;;;;;;;EAQA,SAAS;;;;;;;;;;;;UAaM;;;;;;;;;;;;;;;;GAgBd,gBAAgB,kBACf,OAAO,YACP,QAAQ,SACR,SAAS;IAAwB;MAChC,QAAQ,iBAAiB,OAAO,iBAAiB,YAAY;;;;;;;;;;;;;;;;;GAkB/D,gBAAgB,kBACf,OAAO,YACP,QAAQ,SACR,UAAU;IACR;MAED,QAAQ,iBAAiB,YAAY;;;;;;;;;;;;GAavC,OAAO,YAAY,UAAU,sBAAsB,QAAQ;;;;;;;;;;;UAY7C;;EAEf;;EAEA,UAAU;;EAEV,SAAS;;;;;;;;UASM;;;;EAIf,QAAQ;;;;EAIR,KAAK;;;;EAIL,OAAO;;;;EAIP,MAAM;;;;EAIN,KAAK"}