create-request 2.1.0 → 2.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.0 — 2026-10-09
4
+
5
+ ### Added
6
+
7
+ - `create.query()`, `createQuery()`, `api.query()` and the `QueryRequest` type for the HTTP `QUERY` method ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)): a safe, idempotent read whose query is sent in the body, for searches too large or too structured for a URL. `"QUERY"` joins the `Method` and `BodyMethod` unions, so `withBody()` and `withGraphQL()` accept it and `withRetries({ methods })` can list it; a `switch` that handles every `Method` needs a `"QUERY"` case.
8
+
3
9
  ## 2.1.0 — 2026-10-03
4
10
 
5
11
  ### Added
package/README.md CHANGED
@@ -104,15 +104,26 @@ try {
104
104
  ### Creating
105
105
 
106
106
  ```typescript
107
- create.get(url); // also head, options, post, put, patch, delete (alias: del)
107
+ create.get(url); // also head, options, post, put, patch, delete (alias: del), query
108
108
  api.get("/users"); // joined to the api's base URL
109
109
  api.get(); // no path → the base URL itself
110
110
  api.get<User>("/me"); // declare the JSON type once; getJson() / getData() / getResult() use it
111
111
  ```
112
112
 
113
113
  Named factories exist as well: `createGet`, `createPost`, `createPut`, `createPatch`,
114
- `createDelete`, `createHead`, `createOptions` (the same functions as `create.get`, …), and
115
- `createApi()` is also available as `create.api()`.
114
+ `createDelete`, `createHead`, `createOptions`, `createQuery` (the same functions as
115
+ `create.get`, …), and `createApi()` is also available as `create.api()`.
116
+
117
+ `QUERY` works like `GET`, but the query goes in the body:
118
+
119
+ ```typescript
120
+ const results = await api
121
+ .query<Page<Post>>("/posts/search")
122
+ .withBody({ text: "fetch", tags: ["http"], sort: "recent" })
123
+ .getJson();
124
+ ```
125
+
126
+ The server must support `QUERY`. In browsers, a cross-origin `QUERY` request is preflighted.
116
127
 
117
128
  ### Configuring
118
129
 
@@ -129,7 +140,7 @@ create
129
140
  // and a key set twice keeps the last value (a request can override an api default)
130
141
  .withQueryParams({ page: 2, tags: ["a", "b"], since: new Date() })
131
142
  .withQueryParam("q", "search term")
132
- // body (POST, PUT, PATCH, DELETE only): objects → JSON, strings → text/plain,
143
+ // body (POST, PUT, PATCH, DELETE, QUERY only): objects → JSON, strings → text/plain,
133
144
  // FormData / Blob / URLSearchParams / ArrayBuffer / typed arrays / ReadableStream → sent as-is
134
145
  .withBody({ name: "Ada" })
135
146
  // resilience
@@ -296,7 +307,7 @@ api.get("/status").withRetries({
296
307
  attempts: 3,
297
308
  delay: ({ attempt }) => attempt * 500, // ms, or a number; default: exponential backoff
298
309
  statuses: [503], // default: 408, 425, 429, 500, 502, 503, 504
299
- methods: ["GET", "HEAD", "OPTIONS", "PUT", "DELETE"], // default: all, POST and PATCH included
310
+ methods: ["GET", "HEAD", "OPTIONS", "QUERY", "PUT", "DELETE"], // default: all, POST and PATCH included
300
311
  maxDelay: 10_000, // caps the backoff (default 30 s); a longer Retry-After gives up instead
301
312
  shouldRetry: ({ error }) => error.code === "NETWORK", // full override of the decision
302
313
  onRetry: ({ attempt, error, delay }) =>
@@ -310,8 +321,8 @@ How retries behave:
310
321
  backoff (300 ms, 600 ms, 1.2 s, … plus up to 100 ms of jitter, capped at `maxDelay`);
311
322
  validation errors and other failures are not.
312
323
  - Every method is retried by default, `POST` and `PATCH` included. Pass
313
- `methods: ["GET", "HEAD", "OPTIONS", "PUT", "DELETE"]` if a repeated request could duplicate
314
- work.
324
+ `methods: ["GET", "HEAD", "OPTIONS", "QUERY", "PUT", "DELETE"]` if a repeated request could
325
+ duplicate work.
315
326
  - A `Retry-After` header is honoured unless you set `delay`; one longer than `maxDelay` cancels
316
327
  the retry so you can react yourself.
317
328
  - Aborted requests and requests with a stream body are never retried, whatever the policy. That
@@ -531,7 +542,7 @@ Measured with `size-limit` on the published build of this version (`npm run size
531
542
 
532
543
  | Import | min + gzip | min + brotli |
533
544
  | ------------------------------ | ---------: | -----------: |
534
- | everything (`import * as …`) | 4.92 KB | 4.46 KB |
545
+ | everything (`import * as …`) | 4.95 KB | 4.48 KB |
535
546
  | `import { createGet }` only | 4.32 KB | |
536
547
  | `import { RequestError }` only | 0.25 KB | |
537
548
 
package/dist/index.cjs CHANGED
@@ -685,7 +685,8 @@ const METHODS = [
685
685
  "POST",
686
686
  "PUT",
687
687
  "PATCH",
688
- "DELETE"
688
+ "DELETE",
689
+ "QUERY"
689
690
  ];
690
691
  let installed = false;
691
692
  function createApi() {
@@ -712,6 +713,7 @@ const createPost = (url) => new HttpRequest("POST", url);
712
713
  const createPut = (url) => new HttpRequest("PUT", url);
713
714
  const createPatch = (url) => new HttpRequest("PATCH", url);
714
715
  const createDelete = (url) => new HttpRequest("DELETE", url);
716
+ const createQuery = (url) => new HttpRequest("QUERY", url);
715
717
  const create = {
716
718
  get: createGet,
717
719
  head: createHead,
@@ -721,6 +723,7 @@ const create = {
721
723
  patch: createPatch,
722
724
  delete: createDelete,
723
725
  del: createDelete,
726
+ query: createQuery,
724
727
  api: createApi
725
728
  };
726
729
 
@@ -736,5 +739,6 @@ exports.createOptions = createOptions;
736
739
  exports.createPatch = createPatch;
737
740
  exports.createPost = createPost;
738
741
  exports.createPut = createPut;
742
+ exports.createQuery = createQuery;
739
743
  exports.default = create;
740
744
  exports.isRequestError = isRequestError;
package/dist/index.d.cts CHANGED
@@ -163,10 +163,13 @@ export declare class RequestError<TData = unknown> extends Error {
163
163
  export declare const isRequestError: (error: unknown) => error is RequestError;
164
164
  //#endregion
165
165
  //#region src/types.d.ts
166
- /** The HTTP methods a request can be created with. */
167
- type Method = "GET" | "HEAD" | "OPTIONS" | "DELETE" | "POST" | "PUT" | "PATCH";
166
+ /**
167
+ * The HTTP methods a request can be created with. `QUERY` ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)) is a
168
+ * safe, idempotent read like `GET` whose query is sent in the body.
169
+ */
170
+ type Method = "GET" | "HEAD" | "OPTIONS" | "DELETE" | "POST" | "PUT" | "PATCH" | "QUERY";
168
171
  /** The HTTP methods that may carry a request body (`withBody` / `withGraphQL` are only available on these). */
169
- type BodyMethod = "POST" | "PUT" | "PATCH" | "DELETE";
172
+ type BodyMethod = "POST" | "PUT" | "PATCH" | "DELETE" | "QUERY";
170
173
  /** What `fetch` accepts as a body. */
171
174
  type FetchBody = NonNullable<RequestInit["body"]>;
172
175
  /** `"include"`, `"omit"` or `"same-origin"`. */
@@ -261,7 +264,7 @@ interface RetryConfig {
261
264
  delay?: number | ((context: RetryContext) => number) | undefined;
262
265
  /** Statuses that are retried. Default: `[408, 425, 429, 500, 502, 503, 504]`. Network errors and timeouts are retried by default. */
263
266
  statuses?: readonly number[] | undefined;
264
- /** Methods that are retried. Default: all. Use `["GET", "HEAD", "OPTIONS", "PUT", "DELETE"]` to retry idempotent requests only. */
267
+ /** Methods that are retried. Default: all. Use `["GET", "HEAD", "OPTIONS", "QUERY", "PUT", "DELETE"]` to retry idempotent requests only. */
265
268
  methods?: readonly Method[] | undefined;
266
269
  /**
267
270
  * Upper bound, in milliseconds, for the default backoff. A `Retry-After` header longer than this
@@ -651,7 +654,7 @@ export declare class HttpRequest<M extends Method = Method, T = unknown> {
651
654
  /** Adds an {@link ErrorInterceptor}, run once after the request has failed for good (after retries); it also receives the request, so it can replay it. */
652
655
  withErrorInterceptor(interceptor: ErrorInterceptor): this;
653
656
  /**
654
- * Sets the request body (POST, PUT, PATCH and DELETE only — a compile error elsewhere). Objects and
657
+ * Sets the request body (POST, PUT, PATCH, DELETE and QUERY only — a compile error elsewhere). Objects and
655
658
  * arrays are JSON-encoded; strings, `Blob`, `FormData`, `URLSearchParams`, `ArrayBuffer`, typed arrays
656
659
  * and `ReadableStream` are sent as-is. `Content-Type` is set to `application/json` / `text/plain`
657
660
  * unless already present, and removed for `FormData` (fetch must add the multipart boundary itself).
@@ -799,6 +802,8 @@ interface ApiBuilder extends ApiChainables {
799
802
  delete<T = unknown>(path?: string): HttpRequest<"DELETE", T>;
800
803
  /** Alias of `delete`. */
801
804
  del<T = unknown>(path?: string): HttpRequest<"DELETE", T>;
805
+ /** Creates a QUERY request (RFC 10008): a safe, idempotent read whose query is sent with `withBody()`. */
806
+ query<T = unknown>(path?: string): HttpRequest<"QUERY", T>;
802
807
  }
803
808
  /**
804
809
  * Creates an empty {@link ApiBuilder}. Configure it once, export it, and create every request through it.
@@ -821,9 +826,11 @@ export type PutRequest<T = unknown> = HttpRequest<"PUT", T>;
821
826
  export type PatchRequest<T = unknown> = HttpRequest<"PATCH", T>;
822
827
  /** A DELETE request. */
823
828
  export type DeleteRequest<T = unknown> = HttpRequest<"DELETE", T>;
829
+ /** A QUERY request (RFC 10008). */
830
+ export type QueryRequest<T = unknown> = HttpRequest<"QUERY", T>;
824
831
  /** Any request — the v1 name for {@link HttpRequest}. */
825
832
  export type BaseRequest<T = unknown> = HttpRequest<Method, T>;
826
- /** Any request that may carry a body — the v1 name for `HttpRequest<"POST" | "PUT" | "PATCH" | "DELETE">`. */
833
+ /** Any request that may carry a body — the v1 name for `HttpRequest<"POST" | "PUT" | "PATCH" | "DELETE" | "QUERY">`. */
827
834
  export type BodyRequest<T = unknown> = HttpRequest<BodyMethod, T>;
828
835
  /** Creates a GET request. `T` declares the JSON type the response is expected to have. */
829
836
  export declare const createGet: <T = unknown>(url: string) => GetRequest<T>;
@@ -839,6 +846,17 @@ export declare const createPut: <T = unknown>(url: string) => PutRequest<T>;
839
846
  export declare const createPatch: <T = unknown>(url: string) => PatchRequest<T>;
840
847
  /** Creates a DELETE request. */
841
848
  export declare const createDelete: <T = unknown>(url: string) => DeleteRequest<T>;
849
+ /**
850
+ * Creates a QUERY request ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)): a safe, idempotent read like GET
851
+ * whose query travels in the body, so it can be as large and as structured as needed. The server must support the
852
+ * method and needs a `Content-Type`, which is set for you except for binary and stream bodies (use `withContentType()`).
853
+ *
854
+ * @example
855
+ * ```typescript
856
+ * const open = await create.query<Issue[]>("https://api.example.com/issues").withBody({ state: "open", labels: ["bug"] }).getJson();
857
+ * ```
858
+ */
859
+ export declare const createQuery: <T = unknown>(url: string) => QueryRequest<T>;
842
860
  /**
843
861
  * The entry point: one factory per HTTP method plus `api()` for configured instances.
844
862
  *
@@ -868,6 +886,8 @@ declare const create: {
868
886
  readonly delete: <T = unknown>(url: string) => DeleteRequest<T>;
869
887
  /** Alias of `delete`. */
870
888
  readonly del: <T = unknown>(url: string) => DeleteRequest<T>;
889
+ /** Creates a QUERY request (RFC 10008): a safe, idempotent read whose query is sent with `withBody()` — see {@link createQuery}. */
890
+ readonly query: <T = unknown>(url: string) => QueryRequest<T>;
871
891
  /** Creates an api instance — see {@link createApi}. */
872
892
  readonly api: typeof createApi;
873
893
  };
package/dist/index.d.ts CHANGED
@@ -163,10 +163,13 @@ export declare class RequestError<TData = unknown> extends Error {
163
163
  export declare const isRequestError: (error: unknown) => error is RequestError;
164
164
  //#endregion
165
165
  //#region src/types.d.ts
166
- /** The HTTP methods a request can be created with. */
167
- type Method = "GET" | "HEAD" | "OPTIONS" | "DELETE" | "POST" | "PUT" | "PATCH";
166
+ /**
167
+ * The HTTP methods a request can be created with. `QUERY` ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)) is a
168
+ * safe, idempotent read like `GET` whose query is sent in the body.
169
+ */
170
+ type Method = "GET" | "HEAD" | "OPTIONS" | "DELETE" | "POST" | "PUT" | "PATCH" | "QUERY";
168
171
  /** The HTTP methods that may carry a request body (`withBody` / `withGraphQL` are only available on these). */
169
- type BodyMethod = "POST" | "PUT" | "PATCH" | "DELETE";
172
+ type BodyMethod = "POST" | "PUT" | "PATCH" | "DELETE" | "QUERY";
170
173
  /** What `fetch` accepts as a body. */
171
174
  type FetchBody = NonNullable<RequestInit["body"]>;
172
175
  /** `"include"`, `"omit"` or `"same-origin"`. */
@@ -261,7 +264,7 @@ interface RetryConfig {
261
264
  delay?: number | ((context: RetryContext) => number) | undefined;
262
265
  /** Statuses that are retried. Default: `[408, 425, 429, 500, 502, 503, 504]`. Network errors and timeouts are retried by default. */
263
266
  statuses?: readonly number[] | undefined;
264
- /** Methods that are retried. Default: all. Use `["GET", "HEAD", "OPTIONS", "PUT", "DELETE"]` to retry idempotent requests only. */
267
+ /** Methods that are retried. Default: all. Use `["GET", "HEAD", "OPTIONS", "QUERY", "PUT", "DELETE"]` to retry idempotent requests only. */
265
268
  methods?: readonly Method[] | undefined;
266
269
  /**
267
270
  * Upper bound, in milliseconds, for the default backoff. A `Retry-After` header longer than this
@@ -651,7 +654,7 @@ export declare class HttpRequest<M extends Method = Method, T = unknown> {
651
654
  /** Adds an {@link ErrorInterceptor}, run once after the request has failed for good (after retries); it also receives the request, so it can replay it. */
652
655
  withErrorInterceptor(interceptor: ErrorInterceptor): this;
653
656
  /**
654
- * Sets the request body (POST, PUT, PATCH and DELETE only — a compile error elsewhere). Objects and
657
+ * Sets the request body (POST, PUT, PATCH, DELETE and QUERY only — a compile error elsewhere). Objects and
655
658
  * arrays are JSON-encoded; strings, `Blob`, `FormData`, `URLSearchParams`, `ArrayBuffer`, typed arrays
656
659
  * and `ReadableStream` are sent as-is. `Content-Type` is set to `application/json` / `text/plain`
657
660
  * unless already present, and removed for `FormData` (fetch must add the multipart boundary itself).
@@ -799,6 +802,8 @@ interface ApiBuilder extends ApiChainables {
799
802
  delete<T = unknown>(path?: string): HttpRequest<"DELETE", T>;
800
803
  /** Alias of `delete`. */
801
804
  del<T = unknown>(path?: string): HttpRequest<"DELETE", T>;
805
+ /** Creates a QUERY request (RFC 10008): a safe, idempotent read whose query is sent with `withBody()`. */
806
+ query<T = unknown>(path?: string): HttpRequest<"QUERY", T>;
802
807
  }
803
808
  /**
804
809
  * Creates an empty {@link ApiBuilder}. Configure it once, export it, and create every request through it.
@@ -821,9 +826,11 @@ export type PutRequest<T = unknown> = HttpRequest<"PUT", T>;
821
826
  export type PatchRequest<T = unknown> = HttpRequest<"PATCH", T>;
822
827
  /** A DELETE request. */
823
828
  export type DeleteRequest<T = unknown> = HttpRequest<"DELETE", T>;
829
+ /** A QUERY request (RFC 10008). */
830
+ export type QueryRequest<T = unknown> = HttpRequest<"QUERY", T>;
824
831
  /** Any request — the v1 name for {@link HttpRequest}. */
825
832
  export type BaseRequest<T = unknown> = HttpRequest<Method, T>;
826
- /** Any request that may carry a body — the v1 name for `HttpRequest<"POST" | "PUT" | "PATCH" | "DELETE">`. */
833
+ /** Any request that may carry a body — the v1 name for `HttpRequest<"POST" | "PUT" | "PATCH" | "DELETE" | "QUERY">`. */
827
834
  export type BodyRequest<T = unknown> = HttpRequest<BodyMethod, T>;
828
835
  /** Creates a GET request. `T` declares the JSON type the response is expected to have. */
829
836
  export declare const createGet: <T = unknown>(url: string) => GetRequest<T>;
@@ -839,6 +846,17 @@ export declare const createPut: <T = unknown>(url: string) => PutRequest<T>;
839
846
  export declare const createPatch: <T = unknown>(url: string) => PatchRequest<T>;
840
847
  /** Creates a DELETE request. */
841
848
  export declare const createDelete: <T = unknown>(url: string) => DeleteRequest<T>;
849
+ /**
850
+ * Creates a QUERY request ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)): a safe, idempotent read like GET
851
+ * whose query travels in the body, so it can be as large and as structured as needed. The server must support the
852
+ * method and needs a `Content-Type`, which is set for you except for binary and stream bodies (use `withContentType()`).
853
+ *
854
+ * @example
855
+ * ```typescript
856
+ * const open = await create.query<Issue[]>("https://api.example.com/issues").withBody({ state: "open", labels: ["bug"] }).getJson();
857
+ * ```
858
+ */
859
+ export declare const createQuery: <T = unknown>(url: string) => QueryRequest<T>;
842
860
  /**
843
861
  * The entry point: one factory per HTTP method plus `api()` for configured instances.
844
862
  *
@@ -868,6 +886,8 @@ declare const create: {
868
886
  readonly delete: <T = unknown>(url: string) => DeleteRequest<T>;
869
887
  /** Alias of `delete`. */
870
888
  readonly del: <T = unknown>(url: string) => DeleteRequest<T>;
889
+ /** Creates a QUERY request (RFC 10008): a safe, idempotent read whose query is sent with `withBody()` — see {@link createQuery}. */
890
+ readonly query: <T = unknown>(url: string) => QueryRequest<T>;
871
891
  /** Creates an api instance — see {@link createApi}. */
872
892
  readonly api: typeof createApi;
873
893
  };
package/dist/index.js CHANGED
@@ -684,7 +684,8 @@ const METHODS = [
684
684
  "POST",
685
685
  "PUT",
686
686
  "PATCH",
687
- "DELETE"
687
+ "DELETE",
688
+ "QUERY"
688
689
  ];
689
690
  let installed = false;
690
691
  function createApi() {
@@ -711,6 +712,7 @@ const createPost = (url) => new HttpRequest("POST", url);
711
712
  const createPut = (url) => new HttpRequest("PUT", url);
712
713
  const createPatch = (url) => new HttpRequest("PATCH", url);
713
714
  const createDelete = (url) => new HttpRequest("DELETE", url);
715
+ const createQuery = (url) => new HttpRequest("QUERY", url);
714
716
  const create = {
715
717
  get: createGet,
716
718
  head: createHead,
@@ -720,8 +722,9 @@ const create = {
720
722
  patch: createPatch,
721
723
  delete: createDelete,
722
724
  del: createDelete,
725
+ query: createQuery,
723
726
  api: createApi
724
727
  };
725
728
 
726
729
  //#endregion
727
- export { HttpRequest, RequestError, ResponseWrapper, createApi, createDelete, createGet, createHead, createOptions, createPatch, createPost, createPut, create as default, isRequestError };
730
+ export { HttpRequest, RequestError, ResponseWrapper, createApi, createDelete, createGet, createHead, createOptions, createPatch, createPost, createPut, createQuery, create as default, isRequestError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-request",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "A tiny, chainable, fully typed wrapper around fetch: retries, timeouts, cancellation, interceptors, api instances, schema validation and one error type",
5
5
  "type": "module",
6
6
  "sideEffects": false,