@zap-studio/fetch 0.5.4 → 0.5.6
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 +15 -0
- package/LICENSE +1 -1
- package/README.md +19 -5
- package/dist/constants.d.ts +17 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/{constants.mjs → constants.js} +1 -1
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/{errors.mjs → errors.js} +1 -1
- package/dist/errors.js.map +1 -0
- package/dist/fetch-D0IXjsUD.js +222 -0
- package/dist/fetch-D0IXjsUD.js.map +1 -0
- package/dist/fetch.d.ts +114 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +3 -0
- package/dist/headers.d.ts +26 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/{headers.mjs → headers.js} +6 -6
- package/dist/headers.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/request.d.ts +30 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/{request.mjs → request.js} +15 -12
- package/dist/request.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/dist/url.d.ts +26 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/{url.mjs → url.js} +36 -37
- package/dist/url.js.map +1 -0
- package/package.json +19 -28
- package/dist/constants.d.mts +0 -18
- package/dist/constants.d.mts.map +0 -1
- package/dist/constants.mjs.map +0 -1
- package/dist/errors.d.mts +0 -41
- package/dist/errors.d.mts.map +0 -1
- package/dist/errors.mjs.map +0 -1
- package/dist/headers.d.mts +0 -26
- package/dist/headers.d.mts.map +0 -1
- package/dist/headers.mjs.map +0 -1
- package/dist/index.d.mts +0 -115
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -103
- package/dist/index.mjs.map +0 -1
- package/dist/internal.d.mts +0 -33
- package/dist/internal.d.mts.map +0 -1
- package/dist/internal.mjs +0 -76
- package/dist/internal.mjs.map +0 -1
- package/dist/methods.d.mts +0 -23
- package/dist/methods.d.mts.map +0 -1
- package/dist/methods.mjs +0 -58
- package/dist/methods.mjs.map +0 -1
- package/dist/request.d.mts +0 -31
- package/dist/request.d.mts.map +0 -1
- package/dist/request.mjs.map +0 -1
- package/dist/types.d.mts +0 -169
- package/dist/types.d.mts.map +0 -1
- package/dist/types.mjs +0 -1
- package/dist/url.d.mts +0 -27
- package/dist/url.d.mts.map +0 -1
- package/dist/url.mjs.map +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ExtendedRequestInit, FetchInput } from "./types.js";
|
|
2
|
+
//#region src/request.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Normalized representation used by internal request execution.
|
|
5
|
+
*
|
|
6
|
+
* - `url`: resolved string URL from the input (string or `URL`; `Request` uses `request.url`)
|
|
7
|
+
* - `request`: original Request clone when input is a Request
|
|
8
|
+
* - `options`: normalized request options merged with Request headers
|
|
9
|
+
*/
|
|
10
|
+
interface NormalizedRequest {
|
|
11
|
+
url: string;
|
|
12
|
+
request?: Request;
|
|
13
|
+
options: ExtendedRequestInit;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes fetch `input` and request-level options into a consistent internal shape.
|
|
17
|
+
*
|
|
18
|
+
* @param input - Request URL/path or Request instance.
|
|
19
|
+
* @param options - Optional request options.
|
|
20
|
+
* @returns A normalized request structure for internal processing.
|
|
21
|
+
* @throws {TypeError} When cloning a `Request` fails or the merged headers are invalid.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const normalized = normalizeRequest("/users", { method: "GET" });
|
|
25
|
+
* console.log(normalized.url); // "/users"
|
|
26
|
+
*/
|
|
27
|
+
declare const normalizeRequest: (input: FetchInput, options?: ExtendedRequestInit) => NormalizedRequest;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { NormalizedRequest, normalizeRequest };
|
|
30
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","names":[],"sources":["../src/request.ts"],"mappings":";;;;;;;;;UAkBiB;EACf;EACA,UAAU;EACV,SAAS;;;;;;;;;;;;;;cAeE,mBACX,OAAO,YACP,UAAU,wBACT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mergeHeaders } from "./headers.
|
|
1
|
+
import { mergeHeaders } from "./headers.js";
|
|
2
2
|
//#region src/request.ts
|
|
3
3
|
/**
|
|
4
4
|
* Request normalization helpers for fetch `input` values.
|
|
@@ -18,23 +18,26 @@ const EMPTY_OPTIONS = {};
|
|
|
18
18
|
* const normalized = normalizeRequest("/users", { method: "GET" });
|
|
19
19
|
* console.log(normalized.url); // "/users"
|
|
20
20
|
*/
|
|
21
|
-
|
|
22
|
-
if (!(input instanceof Request))
|
|
23
|
-
url
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const normalizeRequest = (input, options) => {
|
|
22
|
+
if (!(input instanceof Request)) {
|
|
23
|
+
const url = input instanceof URL ? input.href : input;
|
|
24
|
+
return {
|
|
25
|
+
options: options ?? EMPTY_OPTIONS,
|
|
26
|
+
url
|
|
27
|
+
};
|
|
28
|
+
}
|
|
26
29
|
const request = new Request(input);
|
|
27
|
-
const { headers, ...rest } = options
|
|
30
|
+
const { headers, ...rest } = options ?? {};
|
|
28
31
|
const mergedHeaders = mergeHeaders(request.headers, headers);
|
|
29
32
|
const normalizedOptions = { ...rest };
|
|
30
|
-
if (mergedHeaders) normalizedOptions.headers = mergedHeaders;
|
|
33
|
+
if (mergedHeaders !== void 0) normalizedOptions.headers = mergedHeaders;
|
|
31
34
|
return {
|
|
32
|
-
|
|
35
|
+
options: normalizedOptions,
|
|
33
36
|
request,
|
|
34
|
-
|
|
37
|
+
url: request.url
|
|
35
38
|
};
|
|
36
|
-
}
|
|
39
|
+
};
|
|
37
40
|
//#endregion
|
|
38
41
|
export { normalizeRequest };
|
|
39
42
|
|
|
40
|
-
//# sourceMappingURL=request.
|
|
43
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","names":[],"sources":["../src/request.ts"],"sourcesContent":["/**\n * Request normalization helpers for fetch `input` values.\n *\n * @module @zap-studio/fetch/request\n */\n\nimport { mergeHeaders } from \"./headers.js\";\nimport type { ExtendedRequestInit, FetchInput } from \"./types.js\";\n\nconst EMPTY_OPTIONS = {} as ExtendedRequestInit;\n\n/**\n * Normalized representation used by internal request execution.\n *\n * - `url`: resolved string URL from the input (string or `URL`; `Request` uses `request.url`)\n * - `request`: original Request clone when input is a Request\n * - `options`: normalized request options merged with Request headers\n */\nexport interface NormalizedRequest {\n url: string;\n request?: Request;\n options: ExtendedRequestInit;\n}\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 *\n * @example\n * const normalized = normalizeRequest(\"/users\", { method: \"GET\" });\n * console.log(normalized.url); // \"/users\"\n */\nexport const 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"],"mappings":";;;;;;;AASA,MAAM,gBAAgB,CAAC;;;;;;;;;;;;;AA2BvB,MAAa,oBACX,OACA,YACsB;CACtB,IAAI,EAAE,iBAAiB,UAAU;EAC/B,MAAM,MAAM,iBAAiB,MAAM,MAAM,OAAO;EAChD,OAAO;GACL,SAAS,WAAW;GACpB;EACF;CACF;CAEA,MAAM,UAAU,IAAI,QAAQ,KAAK;CACjC,MAAM,EAAE,SAAS,GAAG,SAAS,WAAW,CAAC;CACzC,MAAM,gBAAgB,aAAa,QAAQ,SAAS,OAAO;CAC3D,MAAM,oBAAoB,EAAE,GAAG,KAAK;CAEpC,IAAI,kBAAkB,KAAA,GACpB,kBAAkB,UAAU;CAG9B,OAAO;EACL,SAAS;EACT;EACA,KAAK,QAAQ;CACf;AACF"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from "@zap-studio/validation";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Accepted `fetch` input type (`string`, `URL`, or `Request`).
|
|
5
|
+
*/
|
|
6
|
+
type FetchInput = Parameters<typeof fetch>[0];
|
|
7
|
+
type URLSearchParamsInput = ConstructorParameters<typeof URLSearchParams>[0];
|
|
8
|
+
type RequestBodyInit = RequestInit & {
|
|
9
|
+
json?: never;
|
|
10
|
+
};
|
|
11
|
+
type JsonBodyInit = Omit<RequestInit, "body"> & {
|
|
12
|
+
/**
|
|
13
|
+
* JSON body convenience. When provided, this is JSON-stringified into `body`.
|
|
14
|
+
* @default undefined
|
|
15
|
+
*/
|
|
16
|
+
json: unknown;
|
|
17
|
+
body?: never;
|
|
18
|
+
};
|
|
19
|
+
interface CustomRequestInit {
|
|
20
|
+
/**
|
|
21
|
+
* Per-request query/search params
|
|
22
|
+
* @default undefined
|
|
23
|
+
*/
|
|
24
|
+
searchParams?: URLSearchParamsInput;
|
|
25
|
+
/**
|
|
26
|
+
* Whether to throw a FetchError on HTTP errors (non-2xx responses)
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
throwOnFetchError?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to throw a ValidationError on validation errors
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
throwOnValidationError?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extended RequestInit type to include custom fetch options
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const options: ExtendedRequestInit = {
|
|
41
|
+
* method: "POST",
|
|
42
|
+
* json: { name: "Ada" },
|
|
43
|
+
* throwOnFetchError: true,
|
|
44
|
+
* };
|
|
45
|
+
*/
|
|
46
|
+
type ExtendedRequestInit = (RequestBodyInit | JsonBodyInit) & CustomRequestInit;
|
|
47
|
+
/**
|
|
48
|
+
* Internal defaults used by fetchInternal
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const defaults: FetchDefaults = {
|
|
52
|
+
* baseURL: "https://api.example.com",
|
|
53
|
+
* throwOnFetchError: true,
|
|
54
|
+
* throwOnValidationError: true,
|
|
55
|
+
* };
|
|
56
|
+
*/
|
|
57
|
+
interface FetchDefaults {
|
|
58
|
+
/**
|
|
59
|
+
* Base URL to prepend to all requests
|
|
60
|
+
* @default ""
|
|
61
|
+
*/
|
|
62
|
+
baseURL: string;
|
|
63
|
+
/**
|
|
64
|
+
* Default headers to include in all requests (can be overridden per request)
|
|
65
|
+
* @default undefined
|
|
66
|
+
*/
|
|
67
|
+
headers?: HeadersInit;
|
|
68
|
+
/**
|
|
69
|
+
* Default query/search params applied to every request (can be overridden per request)
|
|
70
|
+
* @default undefined
|
|
71
|
+
*/
|
|
72
|
+
searchParams?: URLSearchParamsInput;
|
|
73
|
+
/**
|
|
74
|
+
* Whether to throw a `FetchError` on HTTP errors (non-2xx responses)
|
|
75
|
+
* @default true
|
|
76
|
+
*/
|
|
77
|
+
throwOnFetchError: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Whether to throw a `ValidationError` on validation errors
|
|
80
|
+
* @default true
|
|
81
|
+
*/
|
|
82
|
+
throwOnValidationError: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Type-safe fetch function with Standard Schema validation support
|
|
86
|
+
*/
|
|
87
|
+
interface $Fetch {
|
|
88
|
+
/**
|
|
89
|
+
* Fetch with schema validation and throwOnValidationError: false
|
|
90
|
+
* @param input - URL or path to fetch
|
|
91
|
+
* @param schema - Standard Schema for response validation
|
|
92
|
+
* @param options - Extended request options with throwOnValidationError: false
|
|
93
|
+
* @returns Standard Schema Result object with value or issues
|
|
94
|
+
* @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.
|
|
95
|
+
* @throws {TypeError} When request construction, JSON request serialization, headers,
|
|
96
|
+
* search params, native `fetch`, or `response.json()` body reading fail with a
|
|
97
|
+
* `TypeError`.
|
|
98
|
+
* @throws {DOMException} When native `fetch` or `response.json()` rejects an aborted
|
|
99
|
+
* request/body read as an `AbortError` DOMException.
|
|
100
|
+
* @throws {SyntaxError} When `response.json()` cannot parse the response body.
|
|
101
|
+
* @throws {unknown} Any error thrown or rejected by the provided Standard Schema validator.
|
|
102
|
+
*/
|
|
103
|
+
<TSchema extends StandardSchemaV1>(input: FetchInput, schema: TSchema, options: ExtendedRequestInit & {
|
|
104
|
+
throwOnValidationError: false;
|
|
105
|
+
}): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
|
|
106
|
+
/**
|
|
107
|
+
* Fetch with schema validation and throwOnValidationError: true or undefined (default)
|
|
108
|
+
* @param input - URL or path to fetch
|
|
109
|
+
* @param schema - Standard Schema for response validation
|
|
110
|
+
* @param options - Extended request options
|
|
111
|
+
* @returns Validated data of type TSchema
|
|
112
|
+
* @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.
|
|
113
|
+
* @throws {ValidationError} When validation returns issues.
|
|
114
|
+
* @throws {TypeError} When request construction, JSON request serialization, headers,
|
|
115
|
+
* search params, native `fetch`, or `response.json()` body reading fail with a
|
|
116
|
+
* `TypeError`.
|
|
117
|
+
* @throws {DOMException} When native `fetch` or `response.json()` rejects an aborted
|
|
118
|
+
* request/body read as an `AbortError` DOMException.
|
|
119
|
+
* @throws {SyntaxError} When `response.json()` cannot parse the response body.
|
|
120
|
+
* @throws {unknown} Any error thrown or rejected by the provided Standard Schema validator.
|
|
121
|
+
*/
|
|
122
|
+
<TSchema extends StandardSchemaV1>(input: FetchInput, schema: TSchema, options?: ExtendedRequestInit & {
|
|
123
|
+
throwOnValidationError?: true;
|
|
124
|
+
}): Promise<StandardSchemaV1.InferOutput<TSchema>>;
|
|
125
|
+
/**
|
|
126
|
+
* Fetch without schema validation
|
|
127
|
+
* @param input - URL or path to fetch
|
|
128
|
+
* @param options - Extended request options
|
|
129
|
+
* @returns Raw Response object
|
|
130
|
+
* @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.
|
|
131
|
+
* @throws {TypeError} When request construction, JSON request serialization, headers,
|
|
132
|
+
* search params, or native `fetch` fail with a `TypeError`.
|
|
133
|
+
* @throws {DOMException} When native `fetch` rejects an aborted request as an
|
|
134
|
+
* `AbortError` DOMException.
|
|
135
|
+
*/
|
|
136
|
+
(input: FetchInput, options?: ExtendedRequestInit): Promise<Response>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* API HTTP method-specific fetch functions
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* const user = await api.get("/users/1", UserSchema);
|
|
143
|
+
*/
|
|
144
|
+
interface ApiMethods {
|
|
145
|
+
/**
|
|
146
|
+
* DELETE method fetch function
|
|
147
|
+
*/
|
|
148
|
+
delete: $Fetch;
|
|
149
|
+
/**
|
|
150
|
+
* GET method fetch function
|
|
151
|
+
*/
|
|
152
|
+
get: $Fetch;
|
|
153
|
+
/**
|
|
154
|
+
* PATCH method fetch function
|
|
155
|
+
*/
|
|
156
|
+
patch: $Fetch;
|
|
157
|
+
/**
|
|
158
|
+
* POST method fetch function
|
|
159
|
+
*/
|
|
160
|
+
post: $Fetch;
|
|
161
|
+
/**
|
|
162
|
+
* PUT method fetch function
|
|
163
|
+
*/
|
|
164
|
+
put: $Fetch;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
export { $Fetch, ApiMethods, ExtendedRequestInit, FetchDefaults, FetchInput };
|
|
168
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/types.js
ADDED
|
File without changes
|
package/dist/url.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ExtendedRequestInit, FetchDefaults } from "./types.js";
|
|
2
|
+
//#region src/url.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolves final request URL by applying baseURL and layered search params.
|
|
5
|
+
*
|
|
6
|
+
* Search param precedence:
|
|
7
|
+
* 1. `defaults.searchParams`
|
|
8
|
+
* 2. search params already present in `resourceUrl`
|
|
9
|
+
* 3. per-request `searchParams`
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const finalUrl = resolveRequestUrl(
|
|
13
|
+
* "/users?page=2",
|
|
14
|
+
* { ...defaults, baseURL: "https://api.example.com", searchParams: { locale: "en" } },
|
|
15
|
+
* { page: "3" },
|
|
16
|
+
* );
|
|
17
|
+
* // https://api.example.com/users?locale=en&page=3
|
|
18
|
+
*
|
|
19
|
+
* @throws {TypeError} When `baseURL` and `resourceUrl` cannot be resolved by
|
|
20
|
+
* `URL`, or when default/per-request search params cannot be converted by
|
|
21
|
+
* `URLSearchParams`.
|
|
22
|
+
*/
|
|
23
|
+
declare const resolveRequestUrl: (resourceUrl: string, defaults: FetchDefaults, searchParams?: ExtendedRequestInit["searchParams"]) => string;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { resolveRequestUrl };
|
|
26
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","names":[],"sources":["../src/url.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;cAmFa,oBACX,qBACA,UAAU,eACV,eAAe"}
|
package/dist/{url.mjs → url.js}
RENAMED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
//#region src/url.ts
|
|
2
2
|
/**
|
|
3
|
+
* Copies search params into target, overriding duplicate keys.
|
|
4
|
+
*/
|
|
5
|
+
const mergeSearchParams = (target, source) => {
|
|
6
|
+
for (const [key, value] of new URLSearchParams(source)) target.set(key, value);
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Ensures a URL has a trailing slash for relative URL resolution.
|
|
10
|
+
*/
|
|
11
|
+
const ensureTrailingSlash = (url) => url.endsWith("/") ? url : `${url}/`;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves search params by applying default params, URL params, then request params.
|
|
14
|
+
*/
|
|
15
|
+
const resolveSearchParams = (url, defaultSearchParams, searchParams) => {
|
|
16
|
+
if (defaultSearchParams === void 0 && searchParams === void 0) return url;
|
|
17
|
+
const hashIndex = url.indexOf("#");
|
|
18
|
+
const hasFragment = hashIndex !== -1;
|
|
19
|
+
const urlWithoutHash = hasFragment ? url.slice(0, hashIndex) : url;
|
|
20
|
+
const hash = hasFragment ? url.slice(hashIndex + 1) : "";
|
|
21
|
+
const queryIndex = urlWithoutHash.indexOf("?");
|
|
22
|
+
const pathname = queryIndex === -1 ? urlWithoutHash : urlWithoutHash.slice(0, queryIndex);
|
|
23
|
+
const urlSearchParams = queryIndex === -1 ? void 0 : urlWithoutHash.slice(queryIndex + 1);
|
|
24
|
+
const resolvedSearchParams = new URLSearchParams();
|
|
25
|
+
mergeSearchParams(resolvedSearchParams, defaultSearchParams);
|
|
26
|
+
mergeSearchParams(resolvedSearchParams, urlSearchParams);
|
|
27
|
+
mergeSearchParams(resolvedSearchParams, searchParams);
|
|
28
|
+
const resolvedSearch = resolvedSearchParams.toString();
|
|
29
|
+
const fragmentSuffix = hasFragment ? `#${hash}` : "";
|
|
30
|
+
if (resolvedSearch.length === 0) return `${pathname}${fragmentSuffix}`;
|
|
31
|
+
return `${pathname}?${resolvedSearch}${fragmentSuffix}`;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
3
34
|
* Resolves final request URL by applying baseURL and layered search params.
|
|
4
35
|
*
|
|
5
36
|
* Search param precedence:
|
|
@@ -19,43 +50,11 @@
|
|
|
19
50
|
* `URL`, or when default/per-request search params cannot be converted by
|
|
20
51
|
* `URLSearchParams`.
|
|
21
52
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* Resolves search params by applying default params, URL params, then request params.
|
|
27
|
-
*/
|
|
28
|
-
function resolveSearchParams(url, defaultSearchParams, searchParams) {
|
|
29
|
-
if (!defaultSearchParams && searchParams === void 0) return url;
|
|
30
|
-
const hashIndex = url.indexOf("#");
|
|
31
|
-
const hasFragment = hashIndex >= 0;
|
|
32
|
-
const urlWithoutHash = hasFragment ? url.slice(0, hashIndex) : url;
|
|
33
|
-
const hash = hasFragment ? url.slice(hashIndex + 1) : "";
|
|
34
|
-
const queryIndex = urlWithoutHash.indexOf("?");
|
|
35
|
-
const pathname = queryIndex >= 0 ? urlWithoutHash.slice(0, queryIndex) : urlWithoutHash;
|
|
36
|
-
const urlSearchParams = queryIndex >= 0 ? urlWithoutHash.slice(queryIndex + 1) : void 0;
|
|
37
|
-
const resolvedSearchParams = new URLSearchParams();
|
|
38
|
-
mergeSearchParams(resolvedSearchParams, defaultSearchParams);
|
|
39
|
-
mergeSearchParams(resolvedSearchParams, urlSearchParams);
|
|
40
|
-
mergeSearchParams(resolvedSearchParams, searchParams);
|
|
41
|
-
const resolvedSearch = resolvedSearchParams.toString();
|
|
42
|
-
const fragmentSuffix = hasFragment ? `#${hash}` : "";
|
|
43
|
-
if (!resolvedSearch) return `${pathname}${fragmentSuffix}`;
|
|
44
|
-
return `${pathname}?${resolvedSearch}${fragmentSuffix}`;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Ensures a URL has a trailing slash for relative URL resolution.
|
|
48
|
-
*/
|
|
49
|
-
function ensureTrailingSlash(url) {
|
|
50
|
-
return url.endsWith("/") ? url : `${url}/`;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Copies search params into target, overriding duplicate keys.
|
|
54
|
-
*/
|
|
55
|
-
function mergeSearchParams(target, source) {
|
|
56
|
-
for (const [key, value] of new URLSearchParams(source)) target.set(key, value);
|
|
57
|
-
}
|
|
53
|
+
const resolveRequestUrl = (resourceUrl, defaults, searchParams) => {
|
|
54
|
+
const url = defaults.baseURL ? new URL(resourceUrl, ensureTrailingSlash(defaults.baseURL)).toString() : resourceUrl;
|
|
55
|
+
return resolveSearchParams(url, defaults.searchParams, searchParams);
|
|
56
|
+
};
|
|
58
57
|
//#endregion
|
|
59
58
|
export { resolveRequestUrl };
|
|
60
59
|
|
|
61
|
-
//# sourceMappingURL=url.
|
|
60
|
+
//# sourceMappingURL=url.js.map
|
package/dist/url.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","names":[],"sources":["../src/url.ts"],"sourcesContent":["/**\n * URL resolution and query merging utilities.\n *\n * @module @zap-studio/fetch/url\n */\n\nimport type { ExtendedRequestInit, FetchDefaults } from \"./types.js\";\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 * @example\n * const finalUrl = resolveRequestUrl(\n * \"/users?page=2\",\n * { ...defaults, baseURL: \"https://api.example.com\", searchParams: { locale: \"en\" } },\n * { page: \"3\" },\n * );\n * // https://api.example.com/users?locale=en&page=3\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 */\nexport const 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"],"mappings":";;;;AAWA,MAAM,qBACJ,QACA,WACS;CACT,KAAK,MAAM,CAAC,KAAK,UAAU,IAAI,gBAAgB,MAAM,GACnD,OAAO,IAAI,KAAK,KAAK;AAEzB;;;;AAKA,MAAM,uBAAuB,QAC3B,IAAI,SAAS,GAAG,IAAI,MAAM,GAAG,IAAI;;;;AAKnC,MAAM,uBACJ,KACA,qBACA,iBACW;CACX,IAAI,wBAAwB,KAAA,KAAa,iBAAiB,KAAA,GACxD,OAAO;CAGT,MAAM,YAAY,IAAI,QAAQ,GAAG;CACjC,MAAM,cAAc,cAAc;CAClC,MAAM,iBAAiB,cAAc,IAAI,MAAM,GAAG,SAAS,IAAI;CAC/D,MAAM,OAAO,cAAc,IAAI,MAAM,YAAY,CAAC,IAAI;CACtD,MAAM,aAAa,eAAe,QAAQ,GAAG;CAC7C,MAAM,WACJ,eAAe,KAAK,iBAAiB,eAAe,MAAM,GAAG,UAAU;CACzE,MAAM,kBACJ,eAAe,KAAK,KAAA,IAAY,eAAe,MAAM,aAAa,CAAC;CACrE,MAAM,uBAAuB,IAAI,gBAAgB;CAEjD,kBAAkB,sBAAsB,mBAAmB;CAC3D,kBAAkB,sBAAsB,eAAe;CACvD,kBAAkB,sBAAsB,YAAY;CAEpD,MAAM,iBAAiB,qBAAqB,SAAS;CACrD,MAAM,iBAAiB,cAAc,IAAI,SAAS;CAElD,IAAI,eAAe,WAAW,GAC5B,OAAO,GAAG,WAAW;CAGvB,OAAO,GAAG,SAAS,GAAG,iBAAiB;AACzC;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,qBACX,aACA,UACA,iBACW;CACX,MAAM,MAAM,SAAS,UACjB,IAAI,IAAI,aAAa,oBAAoB,SAAS,OAAO,CAAC,CAAC,CAAC,SAAS,IACrE;CAEJ,OAAO,oBAAoB,KAAK,SAAS,cAAc,YAAY;AACrE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-studio/fetch",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A type-safe fetch wrapper for HTTP requests with runtime schema validation.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,43 +35,34 @@
|
|
|
35
35
|
],
|
|
36
36
|
"type": "module",
|
|
37
37
|
"sideEffects": false,
|
|
38
|
-
"types": "./dist/index.d.
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
39
|
"exports": {
|
|
40
|
-
".": "./dist/index.
|
|
41
|
-
"./constants": "./dist/constants.
|
|
42
|
-
"./errors": "./dist/errors.
|
|
43
|
-
"./
|
|
44
|
-
"./
|
|
45
|
-
"./
|
|
46
|
-
"./
|
|
47
|
-
"./
|
|
48
|
-
"./url": "./dist/url.mjs",
|
|
40
|
+
".": "./dist/index.js",
|
|
41
|
+
"./constants": "./dist/constants.js",
|
|
42
|
+
"./errors": "./dist/errors.js",
|
|
43
|
+
"./fetch": "./dist/fetch.js",
|
|
44
|
+
"./headers": "./dist/headers.js",
|
|
45
|
+
"./request": "./dist/request.js",
|
|
46
|
+
"./types": "./dist/types.js",
|
|
47
|
+
"./url": "./dist/url.js",
|
|
49
48
|
"./package.json": "./package.json"
|
|
50
49
|
},
|
|
51
50
|
"publishConfig": {
|
|
52
51
|
"access": "public"
|
|
53
52
|
},
|
|
54
53
|
"dependencies": {
|
|
55
|
-
"@zap-studio/validation": "
|
|
54
|
+
"@zap-studio/validation": "workspace:*"
|
|
56
55
|
},
|
|
57
56
|
"devDependencies": {
|
|
58
|
-
"@
|
|
59
|
-
"arktype": "
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"vite-plus": "^0.1.19",
|
|
66
|
-
"zod": "^4.3.6",
|
|
67
|
-
"@zap-studio/typescript": "0.0.0"
|
|
57
|
+
"@zap-studio/typescript": "workspace:*",
|
|
58
|
+
"arktype": "catalog:",
|
|
59
|
+
"tsdown": "catalog:",
|
|
60
|
+
"typescript": "catalog:",
|
|
61
|
+
"valibot": "catalog:",
|
|
62
|
+
"vitest": "catalog:",
|
|
63
|
+
"zod": "catalog:"
|
|
68
64
|
},
|
|
69
65
|
"engines": {
|
|
70
66
|
"node": ">=18.0.0"
|
|
71
|
-
},
|
|
72
|
-
"scripts": {
|
|
73
|
-
"build": "vp pack",
|
|
74
|
-
"test": "vp test run",
|
|
75
|
-
"test:watch": "vp test watch"
|
|
76
67
|
}
|
|
77
|
-
}
|
|
68
|
+
}
|
package/dist/constants.d.mts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { FetchDefaults } from "./types.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/constants.d.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/constants";
|
|
12
|
-
*
|
|
13
|
-
* console.log(GLOBAL_DEFAULTS.throwOnFetchError); // true
|
|
14
|
-
*/
|
|
15
|
-
declare const GLOBAL_DEFAULTS: FetchDefaults;
|
|
16
|
-
//#endregion
|
|
17
|
-
export { GLOBAL_DEFAULTS };
|
|
18
|
-
//# sourceMappingURL=constants.d.mts.map
|
package/dist/constants.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.mts","names":[],"sources":["../src/constants.ts"],"mappings":";;;;;;;;;;;;;;cAmBa,eAAA,EAAiB,aAAA"}
|
package/dist/constants.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Shared defaults and constants for fetch behavior.\n *\n * @module @zap-studio/fetch/constants\n */\n\nimport type { FetchDefaults } 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/constants\";\n *\n * console.log(GLOBAL_DEFAULTS.throwOnFetchError); // true\n */\nexport const GLOBAL_DEFAULTS: FetchDefaults = {\n baseURL: \"\",\n throwOnFetchError: true,\n throwOnValidationError: true,\n};\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAa,kBAAiC;CAC5C,SAAS;CACT,mBAAmB;CACnB,wBAAwB;CACzB"}
|
package/dist/errors.d.mts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
//#region src/errors.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Custom error types used by the fetch package.
|
|
4
|
-
*
|
|
5
|
-
* @module @zap-studio/fetch/errors
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Error thrown for HTTP errors (non-2xx responses)
|
|
9
|
-
*
|
|
10
|
-
* Includes the failing `Response` object and status code for
|
|
11
|
-
* downstream error handling and response inspection.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* import { FetchError } from "@zap-studio/fetch/errors";
|
|
15
|
-
*
|
|
16
|
-
* try {
|
|
17
|
-
* await $fetch("/users/404");
|
|
18
|
-
* } catch (error) {
|
|
19
|
-
* if (error instanceof FetchError) {
|
|
20
|
-
* console.error(error.status);
|
|
21
|
-
* console.error(await error.response.text());
|
|
22
|
-
* }
|
|
23
|
-
* }
|
|
24
|
-
*/
|
|
25
|
-
declare class FetchError extends Error {
|
|
26
|
-
/**
|
|
27
|
-
* HTTP status code from the failing response.
|
|
28
|
-
*/
|
|
29
|
-
status: Response["status"];
|
|
30
|
-
/**
|
|
31
|
-
* Full response object for additional inspection.
|
|
32
|
-
*/
|
|
33
|
-
response: Response;
|
|
34
|
-
/**
|
|
35
|
-
* Creates a FetchError from a non-ok response.
|
|
36
|
-
*/
|
|
37
|
-
constructor(message: string, response: Response);
|
|
38
|
-
}
|
|
39
|
-
//#endregion
|
|
40
|
-
export { FetchError };
|
|
41
|
-
//# sourceMappingURL=errors.d.mts.map
|
package/dist/errors.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;AAwBA;;;;;;;;;;;;;;;;;;;;;;cAAa,UAAA,SAAmB,KAAA;;;;EAI9B,MAAA,EAAQ,QAAA;;;;EAIR,QAAA,EAAU,QAAA;;;;EAKV,WAAA,CAAY,OAAA,UAAiB,QAAA,EAAU,QAAA;AAAA"}
|
package/dist/errors.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Custom error types used by the fetch package.\n *\n * @module @zap-studio/fetch/errors\n */\n\n/**\n * Error thrown for HTTP errors (non-2xx responses)\n *\n * Includes the failing `Response` object and status code for\n * downstream error handling and response inspection.\n *\n * @example\n * import { FetchError } from \"@zap-studio/fetch/errors\";\n *\n * try {\n * await $fetch(\"/users/404\");\n * } catch (error) {\n * if (error instanceof FetchError) {\n * console.error(error.status);\n * console.error(await error.response.text());\n * }\n * }\n */\nexport class FetchError extends Error {\n /**\n * HTTP status code from the failing response.\n */\n status: Response[\"status\"];\n /**\n * Full response object for additional inspection.\n */\n response: Response;\n\n /**\n * Creates a FetchError from a non-ok response.\n */\n constructor(message: string, response: Response) {\n super(message);\n this.name = \"FetchError\";\n this.status = response.status;\n this.response = response;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,aAAb,cAAgC,MAAM;;;;CAIpC;;;;CAIA;;;;CAKA,YAAY,SAAiB,UAAoB;AAC/C,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS,SAAS;AACvB,OAAK,WAAW"}
|
package/dist/headers.d.mts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
//#region src/headers.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Header utility helpers for request normalization and merging.
|
|
4
|
-
*
|
|
5
|
-
* @module @zap-studio/fetch/headers
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Merges two HeadersInit objects, with the second one taking precedence.
|
|
9
|
-
*
|
|
10
|
-
* @param base - Base/default headers.
|
|
11
|
-
* @param override - Request-level override headers.
|
|
12
|
-
* @returns A merged `Headers` object, or `undefined` when both inputs are empty.
|
|
13
|
-
* @throws {TypeError} When either header input contains invalid header names or values.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* const headers = mergeHeaders(
|
|
17
|
-
* { Authorization: "Bearer token" },
|
|
18
|
-
* { "X-Trace-Id": "abc" },
|
|
19
|
-
* );
|
|
20
|
-
*
|
|
21
|
-
* console.log(headers?.get("Authorization")); // Bearer token
|
|
22
|
-
*/
|
|
23
|
-
declare function mergeHeaders(base?: HeadersInit, override?: HeadersInit): Headers | undefined;
|
|
24
|
-
//#endregion
|
|
25
|
-
export { mergeHeaders };
|
|
26
|
-
//# sourceMappingURL=headers.d.mts.map
|
package/dist/headers.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headers.d.mts","names":[],"sources":["../src/headers.ts"],"mappings":";;AAsBA;;;;;;;;;;;;;;;;;;;;iBAAgB,YAAA,CAAa,IAAA,GAAO,WAAA,EAAa,QAAA,GAAW,WAAA,GAAc,OAAA"}
|
package/dist/headers.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headers.mjs","names":[],"sources":["../src/headers.ts"],"sourcesContent":["/**\n * Header utility helpers for request normalization and merging.\n *\n * @module @zap-studio/fetch/headers\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 *\n * @example\n * const headers = mergeHeaders(\n * { Authorization: \"Bearer token\" },\n * { \"X-Trace-Id\": \"abc\" },\n * );\n *\n * console.log(headers?.get(\"Authorization\")); // Bearer token\n */\nexport function mergeHeaders(base?: HeadersInit, override?: HeadersInit): Headers | undefined {\n if (!(base || override)) {\n return;\n }\n\n if (!base) {\n return new Headers(override);\n }\n\n if (!override) {\n return new Headers(base);\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,aAAa,MAAoB,UAA6C;AAC5F,KAAI,EAAE,QAAQ,UACZ;AAGF,KAAI,CAAC,KACH,QAAO,IAAI,QAAQ,SAAS;AAG9B,KAAI,CAAC,SACH,QAAO,IAAI,QAAQ,KAAK;CAG1B,MAAM,SAAS,IAAI,QAAQ,KAAK;AAChC,MAAK,MAAM,CAAC,KAAK,UAAU,IAAI,QAAQ,SAAS,CAAC,SAAS,CACxD,QAAO,IAAI,KAAK,MAAM;AAExB,QAAO"}
|