@danmat/query-fetch 0.1.1 → 0.1.2
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.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +10 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAUA,IAAM,8CAA8B,IAAI,GAAA,CAAI,CAAC,GAAA,EAAK,GAAG,CAAC,CAAA;AAGtD,IAAM,uBAAA,GAA0B,wBAAA;AAMzB,IAAM,UAAA,GAAN,MAAM,WAAA,SAAmB,KAAA,CAAM;AAAA,EAC3B,IAAA,GAAO,YAAA;AAAA,EAEhB,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AAEb,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,WAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @danmat/query-fetch\n *\n * A tiny, dependency-free client for the HTTP QUERY method (RFC 10008) — the\n * safe, idempotent request that carries a body like POST but caches like GET.\n *\n * @see https://www.rfc-editor.org/rfc/rfc10008\n */\n\n/** Status codes that signal a server does not understand the QUERY method. */\nconst METHOD_UNSUPPORTED_STATUSES = new Set([405, 501]);\n\n/** Default header used to tunnel the intended method when falling back to POST. */\nconst DEFAULT_OVERRIDE_HEADER = \"X-HTTP-Method-Override\";\n\n/**\n * Error thrown when a QUERY request cannot be constructed according to the\n * requirements of RFC 10008 (e.g. a body with no `Content-Type`).\n */\nexport class QueryError extends Error {\n override name = \"QueryError\";\n\n constructor(message: string) {\n super(message);\n // Restore prototype chain for transpiled/ES5 targets.\n Object.setPrototypeOf(this, QueryError.prototype);\n }\n}\n\n/** Options for {@link query}. Extends `RequestInit` minus the fields we own. */\nexport interface QueryOptions extends Omit<RequestInit, \"method\" | \"body\"> {\n /**\n * The raw query body. Pair with {@link QueryOptions.contentType}. For JSON\n * payloads prefer {@link QueryOptions.json}, which sets the content type for\n * you.\n */\n body?: BodyInit | null;\n\n /**\n * Convenience: a value serialized to JSON and sent as\n * `application/json`. Ignored if {@link QueryOptions.body} is set.\n */\n json?: unknown;\n\n /**\n * MIME type of the query body. **Required** whenever a body is present —\n * RFC 10008 mandates that servers reject a QUERY with a missing or\n * inconsistent `Content-Type`. Ignored when using {@link QueryOptions.json}.\n */\n contentType?: string;\n\n /** Media type(s) acceptable in the response. Sets the `Accept` header. */\n accept?: string | string[];\n\n /**\n * Retry as `POST` when the server reports it does not support QUERY\n * (HTTP 405 or 501). Defaults to `true`.\n */\n fallbackToPost?: boolean;\n\n /**\n * Header name used to advertise the original method on a POST fallback so\n * that override-aware servers can still route it as a QUERY. Set to `false`\n * to disable. Defaults to `\"X-HTTP-Method-Override\"`.\n */\n methodOverrideHeader?: string | false;\n\n /**\n * A `fetch` implementation to use instead of the global. Handy for testing\n * or for runtimes that expose `fetch` on a client rather than globally.\n */\n fetch?: typeof fetch;\n}\n\n/** The result of {@link queryJson}: a parsed body plus the originating response. */\nexport interface QueryJsonResult<T> {\n data: T;\n response: Response;\n}\n\nfunction resolveFetch(custom?: typeof fetch): typeof fetch {\n const impl = custom ?? globalThis.fetch;\n if (typeof impl !== \"function\") {\n throw new QueryError(\n \"No fetch implementation found. Pass `fetch` in options or run on a runtime that provides a global fetch.\",\n );\n }\n return impl;\n}\n\n/**\n * Build the request body and headers, enforcing RFC 10008's `Content-Type`\n * requirement. Returns the effective body so it can be reused by a fallback.\n */\nfunction prepare(options: QueryOptions): {\n headers: Headers;\n body: BodyInit | null | undefined;\n} {\n const headers = new Headers(options.headers);\n\n let body: BodyInit | null | undefined = options.body;\n\n if (body == null && options.json !== undefined) {\n body = JSON.stringify(options.json);\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n } else if (options.contentType) {\n headers.set(\"content-type\", options.contentType);\n }\n\n const hasBody = body != null;\n if (hasBody && !headers.has(\"content-type\")) {\n throw new QueryError(\n \"A QUERY request with a body must set a Content-Type (RFC 10008). Pass `contentType`, use `json`, or set the header explicitly.\",\n );\n }\n\n if (options.accept !== undefined) {\n const accept = Array.isArray(options.accept)\n ? options.accept.join(\", \")\n : options.accept;\n headers.set(\"accept\", accept);\n }\n\n return { headers, body };\n}\n\n/**\n * Perform an HTTP QUERY request (RFC 10008).\n *\n * QUERY is safe and idempotent like GET, but carries a request body like POST,\n * making it ideal for large or structured queries that don't fit in a URL.\n *\n * Redirects — including the RFC's `303 See Other` indirect-result pattern — are\n * handled by the underlying `fetch` per the request's `redirect` mode (default\n * `\"follow\"`), so no special handling is needed here.\n *\n * @example\n * ```ts\n * const res = await query(\"https://api.example.com/search\", {\n * json: { filter: { status: \"active\" }, sort: \"-createdAt\" },\n * accept: \"application/json\",\n * });\n * ```\n */\nexport async function query(\n input: string | URL,\n options: QueryOptions = {},\n): Promise<Response> {\n const doFetch = resolveFetch(options.fetch);\n const { headers, body } = prepare(options);\n\n const {\n fetch: _fetch,\n json: _json,\n contentType: _contentType,\n accept: _accept,\n fallbackToPost = true,\n methodOverrideHeader = DEFAULT_OVERRIDE_HEADER,\n headers: _headers,\n body: _body,\n ...init\n } = options;\n\n const response = await doFetch(input, {\n ...init,\n method: \"QUERY\",\n headers,\n body,\n });\n\n if (!fallbackToPost || !METHOD_UNSUPPORTED_STATUSES.has(response.status)) {\n return response;\n }\n\n // The server doesn't speak QUERY — retry as POST, optionally advertising the\n // original method so override-aware servers can still treat it as a query.\n const fallbackHeaders = new Headers(headers);\n if (methodOverrideHeader) {\n fallbackHeaders.set(methodOverrideHeader, \"QUERY\");\n }\n\n return doFetch(input, {\n ...init,\n method: \"POST\",\n headers: fallbackHeaders,\n body,\n });\n}\n\n/**\n * Like {@link query}, but parses the response body as JSON.\n *\n * Throws {@link QueryError} on a non-2xx response so callers don't silently\n * parse an error page.\n */\nexport async function queryJson<T = unknown>(\n input: string | URL,\n options: QueryOptions = {},\n): Promise<QueryJsonResult<T>> {\n const response = await query(input, {\n accept: \"application/json\",\n ...options,\n });\n\n if (!response.ok) {\n throw new QueryError(\n `QUERY ${input.toString()} failed with status ${response.status} ${response.statusText}`,\n );\n }\n\n return { data: (await response.json()) as T, response };\n}\n"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AAUA,IAAM,8CAA8B,IAAI,GAAA,CAAI,CAAC,GAAA,EAAK,GAAG,CAAC,CAAA;AAGtD,IAAM,uBAAA,GAA0B,wBAAA;AAMzB,IAAM,UAAA,GAAN,MAAM,WAAA,SAAmB,KAAA,CAAM;AAAA,EAC3B,IAAA,GAAO,YAAA;AAAA,EAEhB,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AAEb,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,WAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @danmat/query-fetch\n *\n * A tiny, dependency-free client for the HTTP QUERY method (RFC 10008) — the\n * safe, idempotent request that carries a body like POST but caches like GET.\n *\n * @see https://www.rfc-editor.org/rfc/rfc10008\n */\n\n/** Status codes that signal a server does not understand the QUERY method. */\nconst METHOD_UNSUPPORTED_STATUSES = new Set([405, 501]);\n\n/** Default header used to tunnel the intended method when falling back to POST. */\nconst DEFAULT_OVERRIDE_HEADER = \"X-HTTP-Method-Override\";\n\n/**\n * Error thrown when a QUERY request cannot be constructed according to the\n * requirements of RFC 10008 (e.g. a body with no `Content-Type`).\n */\nexport class QueryError extends Error {\n override name = \"QueryError\";\n\n constructor(message: string) {\n super(message);\n // Restore prototype chain for transpiled/ES5 targets.\n Object.setPrototypeOf(this, QueryError.prototype);\n }\n}\n\n/** Options for {@link query}. Extends `RequestInit` minus the fields we own. */\nexport interface QueryOptions extends Omit<RequestInit, \"method\" | \"body\"> {\n /**\n * The raw query body. Pair with {@link QueryOptions.contentType}. For JSON\n * payloads prefer {@link QueryOptions.json}, which sets the content type for\n * you.\n */\n body?: BodyInit | null;\n\n /**\n * Convenience: a value serialized to JSON and sent as\n * `application/json`. Ignored if {@link QueryOptions.body} is set.\n */\n json?: unknown;\n\n /**\n * MIME type of the query body. **Required** whenever a body is present —\n * RFC 10008 mandates that servers reject a QUERY with a missing or\n * inconsistent `Content-Type`. Ignored when using {@link QueryOptions.json}.\n */\n contentType?: string;\n\n /** Media type(s) acceptable in the response. Sets the `Accept` header. */\n accept?: string | string[];\n\n /**\n * Retry as `POST` when the server reports it does not support QUERY\n * (HTTP 405 or 501). Defaults to `true`.\n */\n fallbackToPost?: boolean;\n\n /**\n * Header name used to advertise the original method on a POST fallback so\n * that override-aware servers can still route it as a QUERY. Set to `false`\n * to disable. Defaults to `\"X-HTTP-Method-Override\"`.\n */\n methodOverrideHeader?: string | false;\n\n /**\n * A `fetch` implementation to use instead of the global. Handy for testing\n * or for runtimes that expose `fetch` on a client rather than globally.\n */\n fetch?: typeof fetch;\n}\n\n/** The result of {@link queryJson}: a parsed body plus the originating response. */\nexport interface QueryJsonResult<T> {\n data: T;\n response: Response;\n}\n\nfunction resolveFetch(custom?: typeof fetch): typeof fetch {\n const impl = custom ?? globalThis.fetch;\n if (typeof impl !== \"function\") {\n throw new QueryError(\n \"No fetch implementation found. Pass `fetch` in options or run on a runtime that provides a global fetch.\",\n );\n }\n return impl;\n}\n\n/**\n * Build the request body and headers, enforcing RFC 10008's `Content-Type`\n * requirement. Returns the effective body so it can be reused by a fallback.\n */\nfunction prepare(options: QueryOptions): {\n headers: Headers;\n body: BodyInit | null | undefined;\n} {\n const headers = new Headers(options.headers);\n\n let body: BodyInit | null | undefined = options.body;\n\n if (body == null && options.json !== undefined) {\n body = JSON.stringify(options.json);\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n } else if (options.contentType) {\n headers.set(\"content-type\", options.contentType);\n }\n\n const hasBody = body != null;\n if (hasBody && !headers.has(\"content-type\")) {\n throw new QueryError(\n \"A QUERY request with a body must set a Content-Type (RFC 10008). Pass `contentType`, use `json`, or set the header explicitly.\",\n );\n }\n\n if (options.accept !== undefined) {\n const accept = Array.isArray(options.accept)\n ? options.accept.join(\", \")\n : options.accept;\n headers.set(\"accept\", accept);\n }\n\n return { headers, body };\n}\n\n/**\n * Perform an HTTP QUERY request (RFC 10008).\n *\n * QUERY is safe and idempotent like GET, but carries a request body like POST,\n * making it ideal for large or structured queries that don't fit in a URL.\n *\n * Redirects — including the RFC's `303 See Other` indirect-result pattern — are\n * handled by the underlying `fetch` per the request's `redirect` mode (default\n * `\"follow\"`), so no special handling is needed here.\n *\n * @example\n * ```ts\n * const res = await query(\"https://api.example.com/search\", {\n * json: { filter: { status: \"active\" }, sort: \"-createdAt\" },\n * accept: \"application/json\",\n * });\n * ```\n */\nexport async function query(\n input: string | URL,\n options: QueryOptions = {},\n): Promise<Response> {\n const doFetch = resolveFetch(options.fetch);\n const { headers, body } = prepare(options);\n\n const {\n fetch: _fetch,\n json: _json,\n contentType: _contentType,\n accept: _accept,\n fallbackToPost = true,\n methodOverrideHeader = DEFAULT_OVERRIDE_HEADER,\n headers: _headers,\n body: _body,\n ...init\n } = options;\n\n const response = await doFetch(input, {\n ...init,\n method: \"QUERY\",\n headers,\n body,\n });\n\n if (!fallbackToPost || !METHOD_UNSUPPORTED_STATUSES.has(response.status)) {\n return response;\n }\n\n // The server doesn't speak QUERY — retry as POST, optionally advertising the\n // original method so override-aware servers can still treat it as a query.\n const fallbackHeaders = new Headers(headers);\n if (methodOverrideHeader) {\n fallbackHeaders.set(methodOverrideHeader, \"QUERY\");\n }\n\n return doFetch(input, {\n ...init,\n method: \"POST\",\n headers: fallbackHeaders,\n body,\n });\n}\n\n/**\n * Like {@link query}, but parses the response body as JSON.\n *\n * Throws {@link QueryError} on a non-2xx response so callers don't silently\n * parse an error page.\n */\nexport async function queryJson<T = unknown>(\n input: string | URL,\n options: QueryOptions = {},\n): Promise<QueryJsonResult<T>> {\n const response = await query(input, {\n accept: \"application/json\",\n ...options,\n });\n\n if (!response.ok) {\n throw new QueryError(\n `QUERY ${input.toString()} failed with status ${response.status} ${response.statusText}`,\n );\n }\n\n return { data: (await response.json()) as T, response };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danmat/query-fetch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A tiny, dependency-free client for the HTTP QUERY method (RFC 10008) — the safe, idempotent request with a body. Content-Type enforcement, POST fallback, and Accept negotiation over native fetch.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"http",
|
|
@@ -39,8 +39,14 @@
|
|
|
39
39
|
"test:watch": "vitest",
|
|
40
40
|
"coverage": "vitest run --coverage",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
|
+
"lint": "biome check .",
|
|
43
|
+
"format": "biome check --write .",
|
|
44
|
+
"prepare": "husky",
|
|
42
45
|
"prepublishOnly": "npm run build"
|
|
43
46
|
},
|
|
47
|
+
"lint-staged": {
|
|
48
|
+
"*.{ts,js,json,jsonc,md}": "biome check --write --no-errors-on-unmatched"
|
|
49
|
+
},
|
|
44
50
|
"author": "Dan Matthew <dannymatthew@gmail.com>",
|
|
45
51
|
"license": "MIT",
|
|
46
52
|
"repository": {
|
|
@@ -55,7 +61,10 @@
|
|
|
55
61
|
"access": "public"
|
|
56
62
|
},
|
|
57
63
|
"devDependencies": {
|
|
64
|
+
"@biomejs/biome": "^2.5.2",
|
|
58
65
|
"@vitest/coverage-v8": "^2.1.8",
|
|
66
|
+
"husky": "^9.1.7",
|
|
67
|
+
"lint-staged": "^15.5.2",
|
|
59
68
|
"tsup": "^8.3.5",
|
|
60
69
|
"typescript": "^5.7.2",
|
|
61
70
|
"vitest": "^2.1.8"
|