openapi-typesafe-react-query 1.0.0-rc.6 → 1.0.0-rc.8
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 +38 -0
- package/README.md +1 -1
- package/dist/index.cjs +82 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -2
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.mjs +82 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/body-key.ts +137 -0
- package/src/index.ts +24 -3
- package/src/query-key.ts +48 -87
package/src/query-key.ts
CHANGED
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
* plain values, and the base URL the request goes to or the client's key scope, so the key names
|
|
8
8
|
* what was fetched.
|
|
9
9
|
*/
|
|
10
|
+
import { bodyKey } from "./body-key.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether a value is an object literal, such as the `params` of an init and its records, which
|
|
14
|
+
* the fetch client reads by their members.
|
|
15
|
+
*/
|
|
16
|
+
function isPlainRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
17
|
+
const prototype: unknown =
|
|
18
|
+
typeof value === "object" && value !== null ? Object.getPrototypeOf(value) : undefined;
|
|
19
|
+
return prototype === Object.prototype || prototype === null;
|
|
20
|
+
}
|
|
10
21
|
|
|
11
22
|
/** Headers as a plain object, which TanStack Query hashes by its names and values. */
|
|
12
23
|
type HeadersRecord = Readonly<Record<string, unknown>>;
|
|
@@ -19,23 +30,6 @@ export interface KeyClient {
|
|
|
19
30
|
readonly keyScope?: string | undefined;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
/** `[object Name]`: the type tag is between these indexes of `Object.prototype.toString`. */
|
|
23
|
-
const TYPE_TAG_START = "[object ".length;
|
|
24
|
-
const TYPE_TAG_END = -1;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Whether a value is of the class `name`: an instance of this realm's global class of that name
|
|
28
|
-
* (React Native's classes have no type tag), or a value with its type tag (a class of another
|
|
29
|
-
* realm or of the `undici` package), as the fetch client tells bodies (ADR-030).
|
|
30
|
-
*/
|
|
31
|
-
function isClass(value: object, name: string): boolean {
|
|
32
|
-
const type = (globalThis as Record<string, unknown>)[name];
|
|
33
|
-
return (
|
|
34
|
-
(typeof type === "function" && value instanceof type) ||
|
|
35
|
-
Object.prototype.toString.call(value).slice(TYPE_TAG_START, TYPE_TAG_END) === name
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
33
|
/**
|
|
40
34
|
* The headers of an init as a plain object. A `Headers` object and a list of pairs, which are both
|
|
41
35
|
* iterable, become the names and values the request sends: names in lowercase, a repeated one
|
|
@@ -57,98 +51,65 @@ function plainHeaders(headers: object): HeadersRecord {
|
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
54
|
+
* The members of an object that are not `undefined`, or undefined when none is. A member set to
|
|
55
|
+
* `undefined` (`headers: token ? … : undefined`, `query: { cursor: undefined }`) sends the same
|
|
56
|
+
* request as none, and TanStack Query's hash leaves it out too, so a record of only such members
|
|
57
|
+
* sends nothing and must not count as one. `null`, `[]` and `{}` members stay: `null` removes a
|
|
58
|
+
* header, and a query serializer of the client may send them.
|
|
64
59
|
*/
|
|
65
|
-
function
|
|
66
|
-
const
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** The fields of a FormData in a key, in order, with a file as `blobKey`. */
|
|
71
|
-
function formDataKey(form: FormData): unknown[] {
|
|
72
|
-
// React Native's FormData has no `entries`, so its fields cannot be read.
|
|
73
|
-
if (typeof form.entries !== "function") {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
return Array.from(form.entries(), ([name, value]) => [
|
|
77
|
-
name,
|
|
78
|
-
typeof value === "string" ? value : blobKey(value),
|
|
79
|
-
]);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** The bytes of an ArrayBuffer or a typed array, or null for another value. */
|
|
83
|
-
function bodyBytes(body: object): Uint8Array | null {
|
|
84
|
-
if (ArrayBuffer.isView(body)) {
|
|
85
|
-
return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
|
|
86
|
-
}
|
|
87
|
-
return isClass(body, "ArrayBuffer") ? new Uint8Array(body as ArrayBuffer) : null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** A body that JSON cannot hold, as a value TanStack Query hashes, and the name of its kind. */
|
|
91
|
-
interface RawBodyKey {
|
|
92
|
-
readonly body: unknown;
|
|
93
|
-
readonly bodyType: "URLSearchParams" | "FormData" | "Blob" | "bytes";
|
|
60
|
+
function definedMembers(record: object): Record<string, unknown> | undefined {
|
|
61
|
+
const members = Object.entries(record).filter(([, value]) => value !== undefined);
|
|
62
|
+
return members.length === 0 ? undefined : Object.fromEntries(members);
|
|
94
63
|
}
|
|
95
64
|
|
|
96
65
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
66
|
+
* The `params` of an init without the records that send nothing: `{}`, `{ query: {} }` and
|
|
67
|
+
* `{ query: { cursor: undefined } }` send the request of no `params`, since the fetch client reads
|
|
68
|
+
* a missing record as an empty one. Undefined when no record is left.
|
|
99
69
|
*/
|
|
100
|
-
function
|
|
101
|
-
if (
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
if (isClass(body, "URLSearchParams")) {
|
|
105
|
-
return { body: String(body), bodyType: "URLSearchParams" };
|
|
106
|
-
}
|
|
107
|
-
if (isClass(body, "FormData")) {
|
|
108
|
-
return { body: formDataKey(body as FormData), bodyType: "FormData" };
|
|
109
|
-
}
|
|
110
|
-
if (isClass(body, "Blob") || isClass(body, "File")) {
|
|
111
|
-
return { body: blobKey(body as Blob), bodyType: "Blob" };
|
|
70
|
+
function paramsKey(params: unknown): unknown {
|
|
71
|
+
if (!isPlainRecord(params)) {
|
|
72
|
+
return params;
|
|
112
73
|
}
|
|
113
|
-
const
|
|
114
|
-
|
|
74
|
+
const records = Object.entries(params).map(([location, record]) => [
|
|
75
|
+
location,
|
|
76
|
+
isPlainRecord(record) ? definedMembers(record) : record,
|
|
77
|
+
]);
|
|
78
|
+
return definedMembers(Object.fromEntries(records));
|
|
115
79
|
}
|
|
116
80
|
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
81
|
+
/** The headers of an init as `plainHeaders`, or undefined when they set none. */
|
|
82
|
+
function headersKey(headers: unknown): unknown {
|
|
83
|
+
return typeof headers === "object" && headers !== null
|
|
84
|
+
? definedMembers(plainHeaders(headers))
|
|
85
|
+
: headers;
|
|
123
86
|
}
|
|
124
87
|
|
|
125
88
|
/**
|
|
126
|
-
* The init of a query key: the request init with its
|
|
127
|
-
* client's key scope, or its base URL when the init has
|
|
128
|
-
* Query gives each request its own.
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* of only `undefined` fields, have the key `[method, path]`.
|
|
89
|
+
* The init of a query key: the request init with its params and headers without what sends
|
|
90
|
+
* nothing, its body as `bodyKey`, and the client's key scope, or its base URL when the init has
|
|
91
|
+
* none. The `signal` is left out: TanStack Query gives each request its own. Undefined when
|
|
92
|
+
* nothing is left, so a call without an init and one with an init that sends nothing more, such
|
|
93
|
+
* as `{}` or `{ params: { query: { cursor: undefined } } }`, have the key `[method, path]`.
|
|
132
94
|
*/
|
|
133
95
|
export function queryKeyInit(init: unknown, client: KeyClient): object | undefined {
|
|
134
96
|
const { signal: _signal, ...rest } = (init ?? {}) as {
|
|
135
97
|
signal?: unknown;
|
|
98
|
+
params?: unknown;
|
|
136
99
|
headers?: unknown;
|
|
137
100
|
baseUrl?: unknown;
|
|
138
101
|
body?: unknown;
|
|
139
102
|
};
|
|
140
|
-
const keyInit =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Object.assign(keyInit, rawBody);
|
|
147
|
-
}
|
|
103
|
+
const keyInit: Record<string, unknown> = {
|
|
104
|
+
...rest,
|
|
105
|
+
params: paramsKey(rest.params),
|
|
106
|
+
headers: headersKey(rest.headers),
|
|
107
|
+
...(rest.body === undefined ? {} : bodyKey(rest.body)),
|
|
108
|
+
};
|
|
148
109
|
if (client.keyScope !== undefined) {
|
|
149
110
|
keyInit.keyScope = client.keyScope;
|
|
150
111
|
} else if (!rest.baseUrl && client.baseUrl) {
|
|
151
112
|
keyInit.baseUrl = client.baseUrl;
|
|
152
113
|
}
|
|
153
|
-
return
|
|
114
|
+
return definedMembers(keyInit);
|
|
154
115
|
}
|