@nxgt/httpyz-query 0.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/README.md ADDED
@@ -0,0 +1,614 @@
1
+ # @nxgt/httpyz-query
2
+
3
+ [TanStack Query](https://tanstack.com/query) options for the calls of an
4
+ [`@nxgt/httpyz`](https://www.npmjs.com/package/@nxgt/httpyz) client. Each
5
+ function returns a plain options object, with a key that tells calls apart by
6
+ what they send, and a `queryFn` that sends the call with the query's
7
+ `signal`: a query TanStack cancels aborts its request.
8
+
9
+ It adds no runtime of TanStack's, so any adapter takes the objects:
10
+ `useQuery` in React or Vue, `createQuery` in Solid or Svelte, `injectQuery`
11
+ in Angular.
12
+
13
+ > **0.x.** The API is still settling.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ bun add @nxgt/httpyz @nxgt/httpyz-query @tanstack/react-query
19
+ ```
20
+
21
+ | Peer | | |
22
+ | --- | --- | --- |
23
+ | `@nxgt/httpyz` | required | the client whose calls the queries send |
24
+ | `@tanstack/query-core` | required | TanStack's types. Every adapter depends on it, so installing yours is enough |
25
+ | `typescript` | required | `^6.0.3` |
26
+ | `@nxgt/openapi-httpyz` | optional | only for [`@nxgt/httpyz-query/openapi`](#with-an-openapi-spec) |
27
+
28
+ ## Setup
29
+
30
+ ```ts
31
+ import { createHttpClient } from '@nxgt/httpyz';
32
+ import { createQueries } from '@nxgt/httpyz-query';
33
+
34
+ export const http = createHttpClient({ baseUrl: 'https://api.example.com' });
35
+ export const queries = createQueries(http);
36
+ ```
37
+
38
+ | Option | Default | |
39
+ | --- | --- | --- |
40
+ | `scope` | none | put first in every key: two clients whose paths are the same keep their queries apart |
41
+
42
+ ## Subpaths
43
+
44
+ | Import | What it has |
45
+ | --- | --- |
46
+ | `@nxgt/httpyz-query` | `createQueries`, for a client of `createHttpClient`, and the types |
47
+ | `@nxgt/httpyz-query/openapi` | `createOpenApiQueries`, for a client bound to a spec by `@nxgt/openapi-httpyz` |
48
+
49
+ ## Queries
50
+
51
+ `queryOptions` takes a call as the client's `request` does: the method, the
52
+ path, then its options. The query resolves to the data of a 2xx reply, and
53
+ fails with the client's `ReplyStatusError` on any other:
54
+
55
+ ```ts
56
+ import { useQuery } from '@tanstack/react-query';
57
+ import { z } from 'zod';
58
+ import { queries } from './queries.js';
59
+
60
+ const Employee = z.object({ id: z.int(), name: z.string() });
61
+ const Problem = z.object({ title: z.string() });
62
+
63
+ const { data } = useQuery({
64
+ ...queries.queryOptions('get', '/employees/{id}', {
65
+ param: { id },
66
+ responses: { 200: Employee, 404: Problem },
67
+ }),
68
+ enabled: id !== undefined,
69
+ });
70
+ data?.name; // Employee: the 404 is the query's error
71
+ ```
72
+
73
+ The key is `[method, path, input]`, after the `scope` when there is one.
74
+ `input` holds what the call sends, `param`, `query`, `header` and its body, and
75
+ `decode: false`, since that changes the data; it is left out when empty. The
76
+ call options, `headers`, `timeout`, `signal`, are never in it. The key is
77
+ tagged with the data, so `queryClient.getQueryData(options.queryKey)` is
78
+ typed.
79
+
80
+ ## Cancelling
81
+
82
+ TanStack aborts a query's `signal` when the query is cancelled, and when its
83
+ last observer unmounts while it still runs. The call is sent with that
84
+ signal, so it is aborted too: its request stops, not only its result. A
85
+ `signal` in the call's own options still ends it, as does its `latest`.
86
+
87
+ ```ts
88
+ await queryClient.cancelQueries({ queryKey: queries.queryKey('get', '/search') });
89
+ ```
90
+
91
+ ## Infinite queries
92
+
93
+ `infiniteQueryOptions` takes the call's options, then how it pages:
94
+ TanStack's own `initialPageParam` and `getNextPageParam`, and
95
+ `pageParamName`, the query parameter each page's param is sent as. A `null`
96
+ page param is left out, so the first page goes without it:
97
+
98
+ ```ts
99
+ import { useInfiniteQuery } from '@tanstack/react-query';
100
+
101
+ const { data, fetchNextPage } = useInfiniteQuery(
102
+ queries.infiniteQueryOptions(
103
+ 'post',
104
+ '/employees/search',
105
+ { json: { sort }, query: { first: 20 }, responses: { 200: EmployeePage } },
106
+ {
107
+ pageParamName: 'after',
108
+ initialPageParam: null as string | null,
109
+ getNextPageParam: (last) => last.cursor,
110
+ },
111
+ ),
112
+ );
113
+ ```
114
+
115
+ Its key ends with `'infinite'`, apart from the plain query of the same call,
116
+ whose data is one page rather than all of them.
117
+
118
+ ## Mutations
119
+
120
+ `mutationOptions` takes the method and the path, and what every call shares,
121
+ such as `responses`. Its `mutate()` takes what each call sends:
122
+
123
+ ```ts
124
+ import { useMutation } from '@tanstack/react-query';
125
+
126
+ const remove = useMutation({
127
+ ...queries.mutationOptions('delete', '/employees/{id}', {
128
+ responses: { 204: null },
129
+ }),
130
+ onSuccess: () =>
131
+ queryClient.invalidateQueries({ queryKey: queries.queryKey('get', '/employees') }),
132
+ });
133
+ remove.mutate({ param: { id } });
134
+ ```
135
+
136
+ `mutate()` takes nothing when the path has no `{name}`s to fill.
137
+
138
+ ## Keys and filters
139
+
140
+ `queryKey(method, path?, input?)` is a key, or the start of one: TanStack
141
+ matches a filter's key as a prefix, so every query of a path matches,
142
+ whatever its `param`:
143
+
144
+ ```ts
145
+ queryClient.invalidateQueries({ queryKey: queries.queryKey('get', '/employees/{id}') });
146
+ queryClient.removeQueries({ queryKey: queries.queryKey('get') }); // every GET
147
+ ```
148
+
149
+ ## With an OpenAPI spec
150
+
151
+ `@nxgt/httpyz-query/openapi` does the same for a client bound to a
152
+ generated spec by
153
+ [`@nxgt/openapi-httpyz`](https://www.npmjs.com/package/@nxgt/openapi-httpyz),
154
+ an optional peer. It takes the bound client, and reads the spec from the
155
+ `operations` the client was bound to, as the client does:
156
+
157
+ ```ts
158
+ import { createOpenApiQueries } from '@nxgt/httpyz-query/openapi';
159
+ import { api } from './api.js'; // createOpenApiClient(http, operations)
160
+
161
+ export const queries = createOpenApiQueries(api);
162
+
163
+ // As api.get() takes it: the path, the input, then the init
164
+ useQuery(queries.queryOptions('get', '/employees/{id}', { param: { id } }));
165
+ useQuery(queries.queryOptions('get', '/health'));
166
+
167
+ useInfiniteQuery(
168
+ queries.infiniteQueryOptions(
169
+ 'post',
170
+ '/employees/search',
171
+ { json: { sort }, query: { first: 20 } },
172
+ {
173
+ pageParamName: 'after',
174
+ initialPageParam: null as string | null,
175
+ getNextPageParam: (last) => last.cursor,
176
+ },
177
+ ),
178
+ );
179
+
180
+ const remove = useMutation(queries.mutationOptions('delete', '/employees/{id}'));
181
+ remove.mutate({ param: { id } });
182
+ ```
183
+
184
+ - Every call is typed by the spec, as the bound client's are: the method, one
185
+ the spec has an operation for, the path, the input, and the data, a 2xx
186
+ reply's, decoded when the client decodes.
187
+ - The key holds the input, header parameters included, and never the init.
188
+ - `pageParamName` is one of the operation's query parameters.
189
+ - `mutate()` takes the operation's input; `mutationOptions`' third argument
190
+ is the init every call shares.
191
+
192
+ Coming from `openapi-react-query`: `$api.queryOptions('get', path, { params:
193
+ { path, query }, body })` becomes `queries.queryOptions('get', path, { param,
194
+ query, json })`, and `$api.useQuery(...)` becomes
195
+ `useQuery(queries.queryOptions(...))`.
196
+
197
+ ## API
198
+
199
+ ### `@nxgt/httpyz-query`
200
+
201
+ #### Functions
202
+
203
+ ##### `createQueries`
204
+
205
+ ```ts
206
+ function createQueries(http: HttpClient, options?: QueriesOptions): HttpQueries;
207
+ ```
208
+
209
+ The options objects for the calls of `http`, a client of `createHttpClient`.
210
+ Its calls go through `http.request`, so the client's middleware, retries and
211
+ timeouts apply. See [Setup](#setup).
212
+
213
+ | Option | Type | Default | Description |
214
+ | --- | --- | --- | --- |
215
+ | `http` | `HttpClient` | | the client whose calls the queries send |
216
+ | `options.scope` | `string` | none | put first in every key |
217
+
218
+ Returns an [`HttpQueries`](#httpqueries). Throws nothing.
219
+
220
+ #### The queries object
221
+
222
+ ##### `queryOptions`
223
+
224
+ ```ts
225
+ queryOptions<Path extends string, R extends Responses | undefined = undefined, Decoded extends boolean = true>(
226
+ method: Method,
227
+ path: Path,
228
+ ...args: RequestArgs<Path, R, Decoded> // [options?], or [options] when the path has {name}s
229
+ ): HttpQueryOptions<QueryData<R, Decoded>>;
230
+ ```
231
+
232
+ The options of a query of one call. See [Queries](#queries).
233
+
234
+ | Option | Type | Default | Description |
235
+ | --- | --- | --- | --- |
236
+ | `method` | `Method` | | `'get'`, `'post'`, `'query'`… |
237
+ | `path` | `Path` | | the path, `{name}`s unfilled: `'/employees/{id}'` |
238
+ | `options` | `RequestOptions<Path, R, Decoded>` | | what the client's `request` takes: `param`, `query`, a body, `responses`, `decode`, and call options such as `signal` or `latest`. Required when the path has `{name}`s |
239
+
240
+ | Field | Type | Description |
241
+ | --- | --- | --- |
242
+ | `queryKey` | `HttpQueryKey<Data>` | `[scope?, method, path, input?]`, tagged with the data |
243
+ | `queryFn` | `({ signal }) => Promise<Data>` | sends the call with the query's `signal`, joined with the options' own |
244
+
245
+ The `queryFn` resolves to the data of a 2xx reply, and rejects with a
246
+ `ReplyStatusError` on any other, or with whatever the call throws:
247
+ `UndeclaredStatusError`, `ValidationError`, `TimeoutError`, an abort.
248
+
249
+ ##### `infiniteQueryOptions`
250
+
251
+ ```ts
252
+ infiniteQueryOptions<Path extends string, PageParam, R extends Responses | undefined = undefined, Decoded extends boolean = true>(
253
+ method: Method,
254
+ path: Path,
255
+ options: RequestOptions<Path, R, Decoded>,
256
+ paging: Paging<QueryData<R, Decoded>, PageParam>,
257
+ ): HttpInfiniteQueryOptions<QueryData<R, Decoded>, PageParam>;
258
+ ```
259
+
260
+ The options of an infinite query: each page is the call sent with its
261
+ `pageParam` as the query parameter `pageParamName`, left out when `null` or
262
+ `undefined`. See [Infinite queries](#infinite-queries).
263
+
264
+ | Option | Type | Default | Description |
265
+ | --- | --- | --- | --- |
266
+ | `method` | `Method` | | as `queryOptions` |
267
+ | `path` | `Path` | | as `queryOptions` |
268
+ | `options` | `RequestOptions<Path, R, Decoded>` | | as `queryOptions`, always given. Its `query` may be an object or a `URLSearchParams` |
269
+ | `paging` | [`Paging`](#paging) | | `pageParamName`, and TanStack's paging options |
270
+
271
+ | Field | Type | Description |
272
+ | --- | --- | --- |
273
+ | `queryKey` | `HttpQueryKey<InfiniteData<Data, PageParam>>` | the call's key, then `'infinite'` |
274
+ | `queryFn` | `({ signal, pageParam }) => Promise<Data>` | sends one page |
275
+ | `initialPageParam` | `PageParam` | from `paging` |
276
+ | `getNextPageParam` | `GetNextPageParamFunction<PageParam, Data>` | from `paging` |
277
+ | `getPreviousPageParam` | `GetPreviousPageParamFunction<PageParam, Data>` | from `paging`, when given |
278
+ | `maxPages` | `number` | from `paging`, when given |
279
+
280
+ Each page rejects as a `queryOptions` query does.
281
+
282
+ ##### `mutationOptions`
283
+
284
+ ```ts
285
+ mutationOptions<Path extends string, R extends Responses | undefined = undefined, Decoded extends boolean = true>(
286
+ method: Method,
287
+ path: Path,
288
+ options?: CallOptions & ReplyOptions<R, Decoded>,
289
+ ): HttpMutationOptions<Path, QueryData<R, Decoded>>;
290
+ ```
291
+
292
+ The options of a mutation of one call, whose `mutate()` takes what each call
293
+ sends. See [Mutations](#mutations).
294
+
295
+ | Option | Type | Default | Description |
296
+ | --- | --- | --- | --- |
297
+ | `method` | `Method` | | as `queryOptions` |
298
+ | `path` | `Path` | | as `queryOptions` |
299
+ | `options` | `CallOptions & ReplyOptions<R, Decoded>` | none | what every call shares: `responses`, `validate`, `decode`, `headers`, `timeout`… |
300
+
301
+ | Field | Type | Description |
302
+ | --- | --- | --- |
303
+ | `mutationKey` | `readonly unknown[]` | `[scope?, method, path]` |
304
+ | `mutationFn` | `(variables: MutationVariables<Path>) => Promise<Data>` | sends the call with `options`, then `variables` over them. `variables` may be left out when the path has no `{name}`s |
305
+
306
+ The `mutationFn` rejects as a `queryOptions` query does. It is sent with no
307
+ signal of TanStack's: a mutation is not cancelled.
308
+
309
+ ##### `queryKey`
310
+
311
+ ```ts
312
+ queryKey(method: Method, path?: string, input?: KeyInput): readonly unknown[];
313
+ ```
314
+
315
+ A key, or the start of one, for a filter: `[scope?, method, path?, input?]`,
316
+ built as the options' keys are. It is not tagged with any data. See
317
+ [Keys and filters](#keys-and-filters).
318
+
319
+ | Option | Type | Default | Description |
320
+ | --- | --- | --- | --- |
321
+ | `method` | `Method` | | the method |
322
+ | `path` | `string` | none | the path, `{name}`s unfilled |
323
+ | `input` | [`KeyInput`](#keyinput) | none | what the calls send: `param`, `query`, a body |
324
+
325
+ #### Types
326
+
327
+ ##### `HttpQueries`
328
+
329
+ What `createQueries` returns: [`queryOptions`](#queryoptions),
330
+ [`infiniteQueryOptions`](#infinitequeryoptions),
331
+ [`mutationOptions`](#mutationoptions) and [`queryKey`](#querykey).
332
+
333
+ ##### `QueriesOptions`
334
+
335
+ | Field | Type | Description |
336
+ | --- | --- | --- |
337
+ | `scope` | `string` | optional: put first in every key, `'catalog'` |
338
+
339
+ The second argument of `createQueries` and `createOpenApiQueries`.
340
+
341
+ ##### `HttpQueryOptions`
342
+
343
+ ```ts
344
+ interface HttpQueryOptions<Data> {
345
+ readonly queryKey: HttpQueryKey<Data>;
346
+ readonly queryFn: (context: { readonly signal: AbortSignal }) => Promise<Data>;
347
+ }
348
+ ```
349
+
350
+ What `queryOptions` returns, in both entry points.
351
+
352
+ ##### `HttpInfiniteQueryOptions`
353
+
354
+ ```ts
355
+ interface HttpInfiniteQueryOptions<Data, PageParam> extends Omit<Paging<Data, PageParam>, 'pageParamName'> {
356
+ readonly queryKey: HttpQueryKey<InfiniteData<Data, PageParam>>;
357
+ readonly queryFn: (context: { readonly signal: AbortSignal; readonly pageParam: PageParam }) => Promise<Data>;
358
+ }
359
+ ```
360
+
361
+ What `infiniteQueryOptions` returns, in both entry points.
362
+
363
+ ##### `HttpMutationOptions`
364
+
365
+ ```ts
366
+ interface HttpMutationOptions<Path extends string, Data> {
367
+ readonly mutationKey: readonly unknown[];
368
+ // MutationVariables<Path> | void when the path has no {name}s
369
+ readonly mutationFn: (variables: MutationVariables<Path>) => Promise<Data>;
370
+ }
371
+ ```
372
+
373
+ What `mutationOptions` of `createQueries` returns.
374
+
375
+ ##### `Paging`
376
+
377
+ | Field | Type | Description |
378
+ | --- | --- | --- |
379
+ | `pageParamName` | `string` | the query parameter each page's `pageParam` is sent as: `'after'`, `'page'` |
380
+ | `initialPageParam` | `PageParam` | the first page's param |
381
+ | `getNextPageParam` | `GetNextPageParamFunction<PageParam, Data>` | the next page's param, from the last page |
382
+ | `getPreviousPageParam` | `GetPreviousPageParamFunction<PageParam, Data>` | optional: the previous page's param |
383
+ | `maxPages` | `number` | optional: how many pages TanStack keeps |
384
+
385
+ The fourth argument of `infiniteQueryOptions`. In `./openapi`,
386
+ `pageParamName` is narrowed to the operation's query parameters.
387
+
388
+ ##### `KeyInput`
389
+
390
+ | Field | Type | Description |
391
+ | --- | --- | --- |
392
+ | `param` | `{ readonly [name: string]: unknown }` | the path's parameters |
393
+ | `query` | `QueryInput` | the query: an object, or a `URLSearchParams` |
394
+ | `json` | `unknown` | a JSON body |
395
+ | `form` | `unknown` | a form body |
396
+ | `text` | `string` | a text body |
397
+ | `body` | `unknown` | a raw body |
398
+
399
+ What `queryKey` takes as its `input`, every field optional.
400
+
401
+ ##### `HttpQueryKey`
402
+
403
+ A key tagged with its data, `DataTag<readonly unknown[], Data>`, which is
404
+ what types `queryClient.getQueryData(key)`.
405
+
406
+ ```ts
407
+ const key: HttpQueryKey<Employee> = queries.queryOptions('get', '/employees/{id}', options).queryKey;
408
+ ```
409
+
410
+ ##### `QueryData`
411
+
412
+ The data of a call's 2xx replies, from its `responses` and whether it
413
+ decodes.
414
+
415
+ ```ts
416
+ type Data = QueryData<{ 200: typeof Employee; 404: typeof Problem }, true>; // z.output<typeof Employee>
417
+ ```
418
+
419
+ ##### `MutationVariables`
420
+
421
+ What `mutate()` takes: what the call to `Path` sends, `RequestInput<Path>`,
422
+ and its `CallOptions`.
423
+
424
+ ```ts
425
+ type Variables = MutationVariables<'/employees/{id}'>; // { param: { id }, query?, json?… } & CallOptions
426
+ ```
427
+
428
+ ### `@nxgt/httpyz-query/openapi`
429
+
430
+ #### Functions
431
+
432
+ ##### `createOpenApiQueries`
433
+
434
+ ```ts
435
+ function createOpenApiQueries<Ops extends OperationsShape<Ops>, Routes, Decoded extends boolean>(
436
+ api: OpenApiClient<Ops, Routes, Decoded>,
437
+ options?: QueriesOptions,
438
+ ): OpenApiQueries<Ops, Routes, Decoded>;
439
+ ```
440
+
441
+ The options objects for the operations of `api`, a client of
442
+ `createOpenApiClient`, whose types it infers. It reads `api.operations` to
443
+ tell an operation's input from its init, as the client does: an operation
444
+ that takes nothing is called with its init alone. The calls are the client's
445
+ own, `api.get(path, …)` and the other methods. See
446
+ [With an OpenAPI spec](#with-an-openapi-spec).
447
+
448
+ | Option | Type | Default | Description |
449
+ | --- | --- | --- | --- |
450
+ | `api` | `OpenApiClient<Ops, Routes, Decoded>` | | the bound client |
451
+ | `options.scope` | `string` | none | put first in every key |
452
+
453
+ Returns an [`OpenApiQueries`](#openapiqueries). Throws nothing itself; its
454
+ members throw an `Error`, `The spec has no GET /x operation`, for a method
455
+ and path the spec has no operation at, which the types already refuse.
456
+
457
+ #### The queries object
458
+
459
+ ##### `queryOptions`
460
+
461
+ ```ts
462
+ queryOptions<M extends MethodsOf<Routes>, P extends PathsOf<Routes, M>>(
463
+ method: M,
464
+ path: P,
465
+ ...args: Args<Ops, IdOf<Ops, Routes, M, P>> // [input, init?], or [init?] for an operation that takes nothing
466
+ ): HttpQueryOptions<OperationData<Ops, IdOf<Ops, Routes, M, P>, Decoded>>;
467
+ ```
468
+
469
+ The options of a query of the operation at `method` and `path`, taking what
470
+ `api[method](path, …)` takes.
471
+
472
+ | Option | Type | Default | Description |
473
+ | --- | --- | --- | --- |
474
+ | `method` | `MethodsOf<Routes>` | | a method the spec has an operation for |
475
+ | `path` | `PathsOf<Routes, M>` | | a path with an operation for that method |
476
+ | `input` | the operation's input | | `param`, `query`, `header`, a body. Absent for an operation that takes nothing |
477
+ | `init` | `OperationInit` | none | the core client's call options: `signal`, `timeout`, `headers`, `latest`… |
478
+
479
+ | Field | Type | Description |
480
+ | --- | --- | --- |
481
+ | `queryKey` | `HttpQueryKey<Data>` | `[scope?, method, path, input?]`: the input, never the init |
482
+ | `queryFn` | `({ signal }) => Promise<Data>` | calls the operation with the query's `signal`, joined with the init's own |
483
+
484
+ The `queryFn` resolves to the data of a 2xx reply, and rejects with a
485
+ `ReplyStatusError` on any other, or with whatever the call throws.
486
+ `queryOptions` throws at once for an operation the spec does not have.
487
+
488
+ ##### `infiniteQueryOptions`
489
+
490
+ ```ts
491
+ infiniteQueryOptions<M extends MethodsOf<Routes>, P extends PathsOf<Routes, M>, PageParam>(
492
+ method: M,
493
+ path: P,
494
+ input: Ops[IdOf<Ops, Routes, M, P>]['args'][0],
495
+ paging: Omit<Paging<Data, PageParam>, 'pageParamName'> & { readonly pageParamName: QueryNames<Input> },
496
+ init?: OperationInit,
497
+ ): HttpInfiniteQueryOptions<Data, PageParam>;
498
+ // Input: Ops[IdOf<Ops, Routes, M, P>]['args'][0]; Data: OperationData<Ops, IdOf<Ops, Routes, M, P>, Decoded>
499
+ ```
500
+
501
+ The options of an infinite query of an operation, each page sent with its
502
+ `pageParam` as one of the operation's query parameters.
503
+
504
+ | Option | Type | Default | Description |
505
+ | --- | --- | --- | --- |
506
+ | `method` | `MethodsOf<Routes>` | | as `queryOptions` |
507
+ | `path` | `PathsOf<Routes, M>` | | as `queryOptions` |
508
+ | `input` | the operation's input | | as `queryOptions`, always given |
509
+ | `paging` | [`Paging`](#paging) | | `pageParamName` is one of the input's query parameters |
510
+ | `init` | `OperationInit` | none | the call options every page is sent with |
511
+
512
+ Returns the fields of [`infiniteQueryOptions`](#infinitequeryoptions) above.
513
+ A page of an operation the spec does not have rejects when it is fetched.
514
+
515
+ ##### `mutationOptions`
516
+
517
+ ```ts
518
+ mutationOptions<M extends MethodsOf<Routes>, P extends PathsOf<Routes, M>>(
519
+ method: M,
520
+ path: P,
521
+ init?: OperationInit,
522
+ ): OpenApiMutationOptions<
523
+ OperationVariables<Ops[IdOf<Ops, Routes, M, P>]['args']>,
524
+ OperationData<Ops, IdOf<Ops, Routes, M, P>, Decoded>
525
+ >;
526
+ ```
527
+
528
+ The options of a mutation of an operation, whose `mutate()` takes its input.
529
+
530
+ | Option | Type | Default | Description |
531
+ | --- | --- | --- | --- |
532
+ | `method` | `MethodsOf<Routes>` | | as `queryOptions` |
533
+ | `path` | `PathsOf<Routes, M>` | | as `queryOptions` |
534
+ | `init` | `OperationInit` | none | the call options every call shares |
535
+
536
+ | Field | Type | Description |
537
+ | --- | --- | --- |
538
+ | `mutationKey` | `readonly unknown[]` | `[scope?, method, path]` |
539
+ | `mutationFn` | `(variables: OperationVariables<Args>) => Promise<Data>` | calls the operation with `variables` as its input, and `init` |
540
+
541
+ The `mutationFn` rejects as a query does, and is sent with no signal of
542
+ TanStack's. `mutationOptions` throws at once for an operation the spec does
543
+ not have.
544
+
545
+ ##### `queryKey`
546
+
547
+ ```ts
548
+ queryKey(method: Method, path?: string, input?: KeyInput): readonly unknown[];
549
+ ```
550
+
551
+ As [`queryKey`](#querykey) of `createQueries`: any method and path, untyped
552
+ by the spec.
553
+
554
+ #### Types
555
+
556
+ `QueriesOptions`, `HttpQueryOptions`, `HttpInfiniteQueryOptions`, `Paging`
557
+ and `KeyInput` are exported from `@nxgt/httpyz-query`.
558
+
559
+ ##### `OpenApiQueries`
560
+
561
+ What `createOpenApiQueries` returns: [`queryOptions`](#queryoptions-1),
562
+ [`infiniteQueryOptions`](#infinitequeryoptions-1),
563
+ [`mutationOptions`](#mutationoptions-1) and [`queryKey`](#querykey-1), typed
564
+ by the client's `Ops`, `Routes` and `Decoded`.
565
+
566
+ ##### `OpenApiMutationOptions`
567
+
568
+ ```ts
569
+ interface OpenApiMutationOptions<Variables, Data> {
570
+ readonly mutationKey: readonly unknown[];
571
+ readonly mutationFn: (variables: Variables) => Promise<Data>;
572
+ }
573
+ ```
574
+
575
+ What `mutationOptions` of `createOpenApiQueries` returns.
576
+
577
+ ##### `OperationData`
578
+
579
+ The data of an operation's 2xx replies, decoded when `Decoded` is `true`.
580
+
581
+ ```ts
582
+ type Employee = OperationData<ClientOperations, 'getEmployee', false>;
583
+ ```
584
+
585
+ ##### `OperationVariables`
586
+
587
+ What `mutate()` takes, from an operation's arguments: `void` when it takes
588
+ nothing, its input, or its input or nothing when none of it is required.
589
+
590
+ ```ts
591
+ type Variables = OperationVariables<ClientOperations['deleteEmployee']['args']>; // { param: { id: number } }
592
+ ```
593
+
594
+ ##### `QueryNames`
595
+
596
+ The names of the query parameters of an operation's input: what
597
+ `pageParamName` may be.
598
+
599
+ ```ts
600
+ type Names = QueryNames<{ query?: { first?: number; after?: string } }>; // 'first' | 'after'
601
+ ```
602
+
603
+ ## Traps
604
+
605
+ - **A binary body is not told apart in a key.** A `Blob` or a stream hashes
606
+ as `{}`: give such a query a `queryKey` of your own after the spread.
607
+ - **A status the call does not declare fails differently.** A declared
608
+ non-2xx reply fails the query with a `ReplyStatusError`, but one missing
609
+ from `responses` throws the client's `UndeclaredStatusError` first: declare
610
+ every status the API replies with.
611
+ - **`queryKey()` is not tagged.** `queryClient.getQueryData(queries.queryKey(...))`
612
+ is `unknown`: read the cache with the options' own `queryKey`.
613
+ - **Set `initialPageParam`'s type.** `null` alone types every page param as
614
+ `null`: `null as string | null`.
@@ -0,0 +1,3 @@
1
+ export { createQueries } from './queries/create-queries';
2
+ export type { HttpInfiniteQueryOptions, HttpMutationOptions, HttpQueries, HttpQueryKey, HttpQueryOptions, KeyInput, MutationVariables, Paging, QueriesOptions, QueryData, } from './queries/types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,YAAY,EACX,wBAAwB,EACxB,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,QAAQ,EACR,iBAAiB,EACjB,MAAM,EACN,cAAc,EACd,SAAS,GACT,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,87 @@
1
+ // src/queries/create-queries.ts
2
+ import { ok } from "@nxgt/httpyz";
3
+
4
+ // src/key/key.ts
5
+ var joined = (given, query) => given ? AbortSignal.any([given, query]) : query;
6
+ var hashable = (value) => value instanceof URLSearchParams || typeof FormData !== "undefined" && value instanceof FormData ? [...value.entries()] : value;
7
+ var PARTS = [
8
+ "param",
9
+ "query",
10
+ "header",
11
+ "json",
12
+ "form",
13
+ "text",
14
+ "body"
15
+ ];
16
+ function keyed(input) {
17
+ if (!input)
18
+ return;
19
+ const given = input;
20
+ const key = {};
21
+ for (const part of PARTS) {
22
+ if (given[part] !== undefined)
23
+ key[part] = hashable(given[part]);
24
+ }
25
+ if (given.decode === false)
26
+ key.decode = false;
27
+ return Object.keys(key).length > 0 ? key : undefined;
28
+ }
29
+ function keyOf(scope, method, path, input) {
30
+ const key = scope === undefined ? [method] : [scope, method];
31
+ if (path !== undefined)
32
+ key.push(path);
33
+ const rest = keyed(input);
34
+ if (rest !== undefined)
35
+ key.push(rest);
36
+ return key;
37
+ }
38
+ function paged(input, name, pageParam) {
39
+ const given = input?.query;
40
+ if (given instanceof URLSearchParams) {
41
+ const query = new URLSearchParams(given);
42
+ if (pageParam === null || pageParam === undefined)
43
+ query.delete(name);
44
+ else
45
+ query.set(name, String(pageParam));
46
+ return { ...input, query };
47
+ }
48
+ return {
49
+ ...input,
50
+ query: { ...given, [name]: pageParam }
51
+ };
52
+ }
53
+
54
+ // src/queries/create-queries.ts
55
+ function createQueries(http, { scope } = {}) {
56
+ const send = http.request;
57
+ const queryKey = (method, path, input) => keyOf(scope, method, path, input);
58
+ const call = (method, path, options, signal) => send(method, path, {
59
+ ...options,
60
+ signal: joined(options?.signal, signal)
61
+ }).then(ok);
62
+ return {
63
+ queryKey,
64
+ queryOptions: (method, path, options) => ({
65
+ queryKey: queryKey(method, path, options),
66
+ queryFn: ({ signal }) => call(method, path, options, signal)
67
+ }),
68
+ infiniteQueryOptions: (method, path, options, { pageParamName, ...paging }) => ({
69
+ ...paging,
70
+ queryKey: [...queryKey(method, path, options), "infinite"],
71
+ queryFn: ({
72
+ signal,
73
+ pageParam
74
+ }) => call(method, path, paged(options, pageParamName, pageParam), signal)
75
+ }),
76
+ mutationOptions: (method, path, options) => ({
77
+ mutationKey: queryKey(method, path),
78
+ mutationFn: (variables) => send(method, path, { ...options, ...variables || {} }).then(ok)
79
+ })
80
+ };
81
+ }
82
+ export {
83
+ createQueries
84
+ };
85
+
86
+ //# debugId=E5A4FC415731130364756E2164756E21
87
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/queries/create-queries.ts", "../src/key/key.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * `createQueries`: TanStack Query options for the calls of a client of\n * `createHttpClient`. It adds no runtime of TanStack's: each function returns\n * a plain object, whose `queryFn` sends the call with the query's `signal`.\n */\nimport { type HttpClient, type Method, ok } from '@nxgt/httpyz';\nimport { joined, keyOf, paged } from '../key/key';\nimport type { HttpQueries, KeyInput, Paging, QueriesOptions } from './types';\n\ntype Send = (\n\tmethod: Method,\n\tpath: string,\n\toptions: object,\n) => Promise<{ readonly status: number; readonly data: unknown }>;\n\ntype Options = KeyInput & { readonly signal?: AbortSignal | null };\n\n/**\n * TanStack Query options for the calls of `http`.\n *\n * ```ts\n * const queries = createQueries(http);\n * const { data } = useQuery(\n * queries.queryOptions('get', '/items/{id}', { param: { id }, responses: { 200: Item } }),\n * );\n * ```\n */\nexport function createQueries(\n\thttp: HttpClient,\n\t{ scope }: QueriesOptions = {},\n): HttpQueries {\n\tconst send = http.request as unknown as Send;\n\tconst queryKey = (method: Method, path?: string, input?: KeyInput) =>\n\t\tkeyOf(scope, method, path, input);\n\tconst call = (\n\t\tmethod: Method,\n\t\tpath: string,\n\t\toptions: Options | undefined,\n\t\tsignal: AbortSignal,\n\t) =>\n\t\tsend(method, path, {\n\t\t\t...options,\n\t\t\tsignal: joined(options?.signal, signal),\n\t\t}).then(ok);\n\n\treturn {\n\t\tqueryKey,\n\t\tqueryOptions: (method: Method, path: string, options?: Options) => ({\n\t\t\tqueryKey: queryKey(method, path, options),\n\t\t\tqueryFn: ({ signal }: { signal: AbortSignal }) =>\n\t\t\t\tcall(method, path, options, signal),\n\t\t}),\n\t\tinfiniteQueryOptions: (\n\t\t\tmethod: Method,\n\t\t\tpath: string,\n\t\t\toptions: Options,\n\t\t\t{ pageParamName, ...paging }: Paging<unknown, unknown>,\n\t\t) => ({\n\t\t\t...paging,\n\t\t\t// Apart from the query of the same call, whose data is one page, not all of them.\n\t\t\tqueryKey: [...queryKey(method, path, options), 'infinite'],\n\t\t\tqueryFn: ({\n\t\t\t\tsignal,\n\t\t\t\tpageParam,\n\t\t\t}: {\n\t\t\t\tsignal: AbortSignal;\n\t\t\t\tpageParam: unknown;\n\t\t\t}) =>\n\t\t\t\tcall(method, path, paged(options, pageParamName, pageParam), signal),\n\t\t}),\n\t\tmutationOptions: (method: Method, path: string, options?: object) => ({\n\t\t\tmutationKey: queryKey(method, path),\n\t\t\tmutationFn: (variables: object | undefined) =>\n\t\t\t\tsend(method, path, { ...options, ...(variables || {}) }).then(ok),\n\t\t}),\n\t} as unknown as HttpQueries;\n}\n",
6
+ "/** What the keys and calls of both entry points share. */\n\n/** Both signals: the call ends on either. */\nexport const joined = (\n\tgiven: AbortSignal | null | undefined,\n\tquery: AbortSignal,\n): AbortSignal => (given ? AbortSignal.any([given, query]) : query);\n\n/** A value TanStack hashes as it is: a `URLSearchParams` or `FormData` as its entries. */\nconst hashable = (value: unknown): unknown =>\n\tvalue instanceof URLSearchParams ||\n\t(typeof FormData !== 'undefined' && value instanceof FormData)\n\t\t? [...value.entries()]\n\t\t: value;\n\n/** The parts of an input that tell two calls to one path apart. */\nconst PARTS = [\n\t'param',\n\t'query',\n\t'header',\n\t'json',\n\t'form',\n\t'text',\n\t'body',\n] as const;\n\n/** What tells two calls to one path apart: what they send, and `decode: false`. */\nfunction keyed(input: object | undefined): object | undefined {\n\tif (!input) return undefined;\n\tconst given = input as { readonly [part: string]: unknown };\n\tconst key: Record<string, unknown> = {};\n\tfor (const part of PARTS) {\n\t\tif (given[part] !== undefined) key[part] = hashable(given[part]);\n\t}\n\tif (given.decode === false) key.decode = false;\n\treturn Object.keys(key).length > 0 ? key : undefined;\n}\n\n/** A key, or the start of one: `[scope?, method, path?, input?]`. */\nexport function keyOf(\n\tscope: string | undefined,\n\tmethod: string,\n\tpath?: string,\n\tinput?: object,\n): unknown[] {\n\tconst key: unknown[] = scope === undefined ? [method] : [scope, method];\n\tif (path !== undefined) key.push(path);\n\tconst rest = keyed(input);\n\tif (rest !== undefined) key.push(rest);\n\treturn key;\n}\n\n/** `input` with `pageParam` as its query parameter `name`: left out when `null`. */\nexport function paged<Input extends { readonly query?: unknown }>(\n\tinput: Input | undefined,\n\tname: string,\n\tpageParam: unknown,\n): Input {\n\tconst given = input?.query;\n\tif (given instanceof URLSearchParams) {\n\t\tconst query = new URLSearchParams(given);\n\t\tif (pageParam === null || pageParam === undefined) query.delete(name);\n\t\telse query.set(name, String(pageParam));\n\t\treturn { ...input, query } as Input;\n\t}\n\treturn {\n\t\t...input,\n\t\tquery: { ...(given as object | undefined), [name]: pageParam },\n\t} as Input;\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "E5A4FC415731130364756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,10 @@
1
+ /** What the keys and calls of both entry points share. */
2
+ /** Both signals: the call ends on either. */
3
+ export declare const joined: (given: AbortSignal | null | undefined, query: AbortSignal) => AbortSignal;
4
+ /** A key, or the start of one: `[scope?, method, path?, input?]`. */
5
+ export declare function keyOf(scope: string | undefined, method: string, path?: string, input?: object): unknown[];
6
+ /** `input` with `pageParam` as its query parameter `name`: left out when `null`. */
7
+ export declare function paged<Input extends {
8
+ readonly query?: unknown;
9
+ }>(input: Input | undefined, name: string, pageParam: unknown): Input;
10
+ //# sourceMappingURL=key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../../src/key/key.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAE1D,6CAA6C;AAC7C,eAAO,MAAM,MAAM,GAClB,OAAO,WAAW,GAAG,IAAI,GAAG,SAAS,EACrC,OAAO,WAAW,KAChB,WAAgE,CAAC;AAgCpE,qEAAqE;AACrE,wBAAgB,KAAK,CACpB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,MAAM,GACZ,OAAO,EAAE,CAMX;AAED,oFAAoF;AACpF,wBAAgB,KAAK,CAAC,KAAK,SAAS;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC/D,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,GAChB,KAAK,CAYP"}
@@ -0,0 +1,15 @@
1
+ import type { OpenApiClient, OperationsShape } from '@nxgt/openapi-httpyz';
2
+ import type { QueriesOptions } from '../queries/types';
3
+ import type { OpenApiQueries } from './types';
4
+ /**
5
+ * TanStack Query options for the operations of `api`. The client's own
6
+ * `operations` tell an operation's input from its init, as they do for its
7
+ * calls: one that takes nothing is called with its init alone.
8
+ *
9
+ * ```ts
10
+ * const queries = createOpenApiQueries(api);
11
+ * const { data } = useQuery(queries.queryOptions('get', '/employees/{id}', { param: { id } }));
12
+ * ```
13
+ */
14
+ export declare function createOpenApiQueries<Ops extends OperationsShape<Ops>, Routes, Decoded extends boolean>(api: OpenApiClient<Ops, Routes, Decoded>, { scope }?: QueriesOptions): OpenApiQueries<Ops, Routes, Decoded>;
15
+ //# sourceMappingURL=create-openapi-queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-openapi-queries.d.ts","sourceRoot":"","sources":["../../src/openapi/create-openapi-queries.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACX,aAAa,EAEb,eAAe,EAEf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAoB,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAO9C;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CACnC,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EAChC,MAAM,EACN,OAAO,SAAS,OAAO,EAEvB,GAAG,EAAE,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EACxC,EAAE,KAAK,EAAE,GAAE,cAAmB,GAC5B,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAwFtC"}
@@ -0,0 +1,42 @@
1
+ /** The options `createOpenApiQueries()` returns, typed by the generated spec. */
2
+ import type { Method, Success } from '@nxgt/httpyz';
3
+ import type { Args, IdOf, MethodsOf, OperationInit, OperationReply, OperationsShape, PathsOf } from '@nxgt/openapi-httpyz';
4
+ import type { HttpInfiniteQueryOptions, HttpQueryOptions, KeyInput, Paging } from '../queries/types';
5
+ /** Each reply's `data`. */
6
+ type DataOf<Reply> = Reply extends {
7
+ readonly data: infer Data;
8
+ } ? Data : never;
9
+ /** What a query of an operation resolves to: the data of its 2xx replies. */
10
+ export type OperationData<Ops extends OperationsShape<Ops>, K extends keyof Ops, Decoded extends boolean> = DataOf<Success<OperationReply<Ops, K, Decoded>>>;
11
+ /** An operation's input: `void` when it takes none, and may be left out when nothing in it is required. */
12
+ export type OperationVariables<Args> = Args extends readonly [] ? void : Args extends readonly [infer Input] ? Input : Args extends readonly [(infer Input)?] ? // biome-ignore lint/suspicious/noConfusingVoidType: void, not undefined, is what lets `mutate()` be called with nothing
13
+ Input | void : never;
14
+ /** The query parameters of an operation's input. */
15
+ export type QueryNames<Input> = Input extends {
16
+ readonly query?: infer Query;
17
+ } ? keyof NonNullable<Query> & string : never;
18
+ export interface OpenApiMutationOptions<Variables, Data> {
19
+ readonly mutationKey: readonly unknown[];
20
+ readonly mutationFn: (variables: Variables) => Promise<Data>;
21
+ }
22
+ export type OpenApiQueries<Ops extends OperationsShape<Ops>, Routes, Decoded extends boolean> = {
23
+ /**
24
+ * A query of the operation at a path, taking what `api.get(path, ...)`
25
+ * takes. It resolves to the data of a 2xx reply, and throws a
26
+ * `ReplyStatusError` for any other.
27
+ */
28
+ queryOptions<M extends MethodsOf<Routes>, P extends PathsOf<Routes, M>>(method: M, path: P, ...args: Args<Ops, IdOf<Ops, Routes, M, P>>): HttpQueryOptions<OperationData<Ops, IdOf<Ops, Routes, M, P>, Decoded>>;
29
+ /** An infinite query of an operation: each page sent with its `pageParam` as one of its query parameters. */
30
+ infiniteQueryOptions<M extends MethodsOf<Routes>, P extends PathsOf<Routes, M>, PageParam>(method: M, path: P, input: Ops[IdOf<Ops, Routes, M, P>]['args'][0], paging: Omit<Paging<OperationData<Ops, IdOf<Ops, Routes, M, P>, Decoded>, PageParam>, 'pageParamName'> & {
31
+ readonly pageParamName: QueryNames<Ops[IdOf<Ops, Routes, M, P>]['args'][0]>;
32
+ }, init?: OperationInit): HttpInfiniteQueryOptions<OperationData<Ops, IdOf<Ops, Routes, M, P>, Decoded>, PageParam>;
33
+ /**
34
+ * A mutation of the operation at a path, whose `mutate()` takes its input.
35
+ * `init` holds what every call shares.
36
+ */
37
+ mutationOptions<M extends MethodsOf<Routes>, P extends PathsOf<Routes, M>>(method: M, path: P, init?: OperationInit): OpenApiMutationOptions<OperationVariables<Ops[IdOf<Ops, Routes, M, P>]['args']>, OperationData<Ops, IdOf<Ops, Routes, M, P>, Decoded>>;
38
+ /** A key, or the start of one, for a filter: `queries.queryKey('get', '/items')`. */
39
+ queryKey(method: Method, path?: string, input?: KeyInput): readonly unknown[];
40
+ };
41
+ export {};
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/openapi/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { createOpenApiQueries } from './openapi/create-openapi-queries';
2
+ export type { OpenApiMutationOptions, OpenApiQueries, OperationData, OperationVariables, QueryNames, } from './openapi/types';
3
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,YAAY,EACX,sBAAsB,EACtB,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,UAAU,GACV,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,105 @@
1
+ // src/openapi/create-openapi-queries.ts
2
+ import { ok } from "@nxgt/httpyz";
3
+
4
+ // src/key/key.ts
5
+ var joined = (given, query) => given ? AbortSignal.any([given, query]) : query;
6
+ var hashable = (value) => value instanceof URLSearchParams || typeof FormData !== "undefined" && value instanceof FormData ? [...value.entries()] : value;
7
+ var PARTS = [
8
+ "param",
9
+ "query",
10
+ "header",
11
+ "json",
12
+ "form",
13
+ "text",
14
+ "body"
15
+ ];
16
+ function keyed(input) {
17
+ if (!input)
18
+ return;
19
+ const given = input;
20
+ const key = {};
21
+ for (const part of PARTS) {
22
+ if (given[part] !== undefined)
23
+ key[part] = hashable(given[part]);
24
+ }
25
+ if (given.decode === false)
26
+ key.decode = false;
27
+ return Object.keys(key).length > 0 ? key : undefined;
28
+ }
29
+ function keyOf(scope, method, path, input) {
30
+ const key = scope === undefined ? [method] : [scope, method];
31
+ if (path !== undefined)
32
+ key.push(path);
33
+ const rest = keyed(input);
34
+ if (rest !== undefined)
35
+ key.push(rest);
36
+ return key;
37
+ }
38
+ function paged(input, name, pageParam) {
39
+ const given = input?.query;
40
+ if (given instanceof URLSearchParams) {
41
+ const query = new URLSearchParams(given);
42
+ if (pageParam === null || pageParam === undefined)
43
+ query.delete(name);
44
+ else
45
+ query.set(name, String(pageParam));
46
+ return { ...input, query };
47
+ }
48
+ return {
49
+ ...input,
50
+ query: { ...given, [name]: pageParam }
51
+ };
52
+ }
53
+
54
+ // src/openapi/create-openapi-queries.ts
55
+ function createOpenApiQueries(api, { scope } = {}) {
56
+ const takes = new Map;
57
+ for (const operation of Object.values(api.operations)) {
58
+ takes.set(`${operation.method} ${operation.path}`, operation.parameters.length > 0 || Object.keys(operation.body?.content ?? {}).length > 0);
59
+ }
60
+ const takesInput = (method, path) => {
61
+ const found = takes.get(`${method} ${path}`);
62
+ if (found === undefined) {
63
+ throw new Error(`The spec has no ${method.toUpperCase()} ${path} operation`);
64
+ }
65
+ return found;
66
+ };
67
+ const split = (method, path, args) => takesInput(method, path) ? { input: args[0], init: args[1] } : { input: undefined, init: args[0] };
68
+ const call = (method, path, input, init, signal) => {
69
+ const sent = signal ? { ...init, signal: joined(init?.signal, signal) } : init;
70
+ const at = api[method];
71
+ return (takesInput(method, path) ? at(path, input, sent) : at(path, sent)).then(ok);
72
+ };
73
+ const queryKey = (method, path, input) => keyOf(scope, method, path, input);
74
+ return {
75
+ queryKey,
76
+ queryOptions: (method, path, ...args) => {
77
+ const { input, init } = split(method, path, args);
78
+ return {
79
+ queryKey: queryKey(method, path, input),
80
+ queryFn: ({ signal }) => call(method, path, input, init, signal)
81
+ };
82
+ },
83
+ infiniteQueryOptions: (method, path, input, { pageParamName, ...paging }, init) => ({
84
+ ...paging,
85
+ queryKey: [...queryKey(method, path, input), "infinite"],
86
+ queryFn: ({
87
+ signal,
88
+ pageParam
89
+ }) => call(method, path, paged(input, pageParamName, pageParam), init, signal)
90
+ }),
91
+ mutationOptions: (method, path, init) => {
92
+ takesInput(method, path);
93
+ return {
94
+ mutationKey: queryKey(method, path),
95
+ mutationFn: (input) => call(method, path, input || undefined, init)
96
+ };
97
+ }
98
+ };
99
+ }
100
+ export {
101
+ createOpenApiQueries
102
+ };
103
+
104
+ //# debugId=21510642E24588C464756E2164756E21
105
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/openapi/create-openapi-queries.ts", "../src/key/key.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * `createOpenApiQueries`: TanStack Query options for the operations of a\n * client bound to a generated spec by `@nxgt/openapi-httpyz`. It imports that\n * package's types only: the calls are the bound client's own.\n */\nimport { type Method, ok } from '@nxgt/httpyz';\nimport type {\n\tOpenApiClient,\n\tOperationInit,\n\tOperationsShape,\n\tRuntimeOperation,\n} from '@nxgt/openapi-httpyz';\nimport { joined, keyOf, paged } from '../key/key';\nimport type { KeyInput, Paging, QueriesOptions } from '../queries/types';\nimport type { OpenApiQueries } from './types';\n\ntype Call = (\n\tpath: string,\n\t...args: unknown[]\n) => Promise<{ readonly status: number; readonly data: unknown }>;\n\n/**\n * TanStack Query options for the operations of `api`. The client's own\n * `operations` tell an operation's input from its init, as they do for its\n * calls: one that takes nothing is called with its init alone.\n *\n * ```ts\n * const queries = createOpenApiQueries(api);\n * const { data } = useQuery(queries.queryOptions('get', '/employees/{id}', { param: { id } }));\n * ```\n */\nexport function createOpenApiQueries<\n\tOps extends OperationsShape<Ops>,\n\tRoutes,\n\tDecoded extends boolean,\n>(\n\tapi: OpenApiClient<Ops, Routes, Decoded>,\n\t{ scope }: QueriesOptions = {},\n): OpenApiQueries<Ops, Routes, Decoded> {\n\t// As the bound client reads its arguments: an operation that takes nothing has no input.\n\tconst takes = new Map<string, boolean>();\n\tfor (const operation of Object.values<RuntimeOperation>(\n\t\tapi.operations as unknown as { readonly [id: string]: RuntimeOperation },\n\t)) {\n\t\ttakes.set(\n\t\t\t`${operation.method} ${operation.path}`,\n\t\t\toperation.parameters.length > 0 ||\n\t\t\t\tObject.keys(operation.body?.content ?? {}).length > 0,\n\t\t);\n\t}\n\tconst takesInput = (method: Method, path: string): boolean => {\n\t\tconst found = takes.get(`${method} ${path}`);\n\t\tif (found === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`The spec has no ${method.toUpperCase()} ${path} operation`,\n\t\t\t);\n\t\t}\n\t\treturn found;\n\t};\n\t/** The input and the init of a call's arguments. */\n\tconst split = (method: Method, path: string, args: readonly unknown[]) =>\n\t\ttakesInput(method, path)\n\t\t\t? { input: args[0] as object | undefined, init: args[1] as OperationInit }\n\t\t\t: { input: undefined, init: args[0] as OperationInit };\n\tconst call = (\n\t\tmethod: Method,\n\t\tpath: string,\n\t\tinput: object | undefined,\n\t\tinit: OperationInit | undefined,\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\tconst sent = signal\n\t\t\t? { ...init, signal: joined(init?.signal, signal) }\n\t\t\t: init;\n\t\tconst at = (api as unknown as Record<Method, Call>)[method];\n\t\treturn (\n\t\t\ttakesInput(method, path) ? at(path, input, sent) : at(path, sent)\n\t\t).then(ok);\n\t};\n\tconst queryKey = (method: Method, path?: string, input?: KeyInput) =>\n\t\tkeyOf(scope, method, path, input);\n\n\treturn {\n\t\tqueryKey,\n\t\tqueryOptions: (method: Method, path: string, ...args: unknown[]) => {\n\t\t\tconst { input, init } = split(method, path, args);\n\t\t\treturn {\n\t\t\t\tqueryKey: queryKey(method, path, input),\n\t\t\t\tqueryFn: ({ signal }: { signal: AbortSignal }) =>\n\t\t\t\t\tcall(method, path, input, init, signal),\n\t\t\t};\n\t\t},\n\t\tinfiniteQueryOptions: (\n\t\t\tmethod: Method,\n\t\t\tpath: string,\n\t\t\tinput: object | undefined,\n\t\t\t{ pageParamName, ...paging }: Paging<unknown, unknown>,\n\t\t\tinit?: OperationInit,\n\t\t) => ({\n\t\t\t...paging,\n\t\t\t// Apart from the query of the same call, whose data is one page, not all of them.\n\t\t\tqueryKey: [...queryKey(method, path, input), 'infinite'],\n\t\t\tqueryFn: ({\n\t\t\t\tsignal,\n\t\t\t\tpageParam,\n\t\t\t}: {\n\t\t\t\tsignal: AbortSignal;\n\t\t\t\tpageParam: unknown;\n\t\t\t}) =>\n\t\t\t\tcall(\n\t\t\t\t\tmethod,\n\t\t\t\t\tpath,\n\t\t\t\t\tpaged(input, pageParamName, pageParam),\n\t\t\t\t\tinit,\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t}),\n\t\tmutationOptions: (method: Method, path: string, init?: OperationInit) => {\n\t\t\ttakesInput(method, path);\n\t\t\treturn {\n\t\t\t\tmutationKey: queryKey(method, path),\n\t\t\t\tmutationFn: (input: object | undefined) =>\n\t\t\t\t\tcall(method, path, input || undefined, init),\n\t\t\t};\n\t\t},\n\t} as unknown as OpenApiQueries<Ops, Routes, Decoded>;\n}\n",
6
+ "/** What the keys and calls of both entry points share. */\n\n/** Both signals: the call ends on either. */\nexport const joined = (\n\tgiven: AbortSignal | null | undefined,\n\tquery: AbortSignal,\n): AbortSignal => (given ? AbortSignal.any([given, query]) : query);\n\n/** A value TanStack hashes as it is: a `URLSearchParams` or `FormData` as its entries. */\nconst hashable = (value: unknown): unknown =>\n\tvalue instanceof URLSearchParams ||\n\t(typeof FormData !== 'undefined' && value instanceof FormData)\n\t\t? [...value.entries()]\n\t\t: value;\n\n/** The parts of an input that tell two calls to one path apart. */\nconst PARTS = [\n\t'param',\n\t'query',\n\t'header',\n\t'json',\n\t'form',\n\t'text',\n\t'body',\n] as const;\n\n/** What tells two calls to one path apart: what they send, and `decode: false`. */\nfunction keyed(input: object | undefined): object | undefined {\n\tif (!input) return undefined;\n\tconst given = input as { readonly [part: string]: unknown };\n\tconst key: Record<string, unknown> = {};\n\tfor (const part of PARTS) {\n\t\tif (given[part] !== undefined) key[part] = hashable(given[part]);\n\t}\n\tif (given.decode === false) key.decode = false;\n\treturn Object.keys(key).length > 0 ? key : undefined;\n}\n\n/** A key, or the start of one: `[scope?, method, path?, input?]`. */\nexport function keyOf(\n\tscope: string | undefined,\n\tmethod: string,\n\tpath?: string,\n\tinput?: object,\n): unknown[] {\n\tconst key: unknown[] = scope === undefined ? [method] : [scope, method];\n\tif (path !== undefined) key.push(path);\n\tconst rest = keyed(input);\n\tif (rest !== undefined) key.push(rest);\n\treturn key;\n}\n\n/** `input` with `pageParam` as its query parameter `name`: left out when `null`. */\nexport function paged<Input extends { readonly query?: unknown }>(\n\tinput: Input | undefined,\n\tname: string,\n\tpageParam: unknown,\n): Input {\n\tconst given = input?.query;\n\tif (given instanceof URLSearchParams) {\n\t\tconst query = new URLSearchParams(given);\n\t\tif (pageParam === null || pageParam === undefined) query.delete(name);\n\t\telse query.set(name, String(pageParam));\n\t\treturn { ...input, query } as Input;\n\t}\n\treturn {\n\t\t...input,\n\t\tquery: { ...(given as object | undefined), [name]: pageParam },\n\t} as Input;\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "21510642E24588C464756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `createQueries`: TanStack Query options for the calls of a client of
3
+ * `createHttpClient`. It adds no runtime of TanStack's: each function returns
4
+ * a plain object, whose `queryFn` sends the call with the query's `signal`.
5
+ */
6
+ import { type HttpClient } from '@nxgt/httpyz';
7
+ import type { HttpQueries, QueriesOptions } from './types';
8
+ /**
9
+ * TanStack Query options for the calls of `http`.
10
+ *
11
+ * ```ts
12
+ * const queries = createQueries(http);
13
+ * const { data } = useQuery(
14
+ * queries.queryOptions('get', '/items/{id}', { param: { id }, responses: { 200: Item } }),
15
+ * );
16
+ * ```
17
+ */
18
+ export declare function createQueries(http: HttpClient, { scope }?: QueriesOptions): HttpQueries;
19
+ //# sourceMappingURL=create-queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-queries.d.ts","sourceRoot":"","sources":["../../src/queries/create-queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,cAAc,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAoB,cAAc,EAAE,MAAM,SAAS,CAAC;AAU7E;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC5B,IAAI,EAAE,UAAU,EAChB,EAAE,KAAK,EAAE,GAAE,cAAmB,GAC5B,WAAW,CA8Cb"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The options objects `createQueries()` returns. They are plain objects, so
3
+ * any TanStack Query adapter takes them: `useQuery(queries.queryOptions(...))`
4
+ * in React, `createQuery` in Solid, `injectQuery` in Angular.
5
+ */
6
+ import type { CallOptions, HttpReply, Method, PathParamNames, QueryInput, ReplyOptions, RequestArgs, RequestInput, RequestOptions, Responses, Success } from '@nxgt/httpyz';
7
+ import type { DataTag, GetNextPageParamFunction, GetPreviousPageParamFunction, InfiniteData } from '@tanstack/query-core';
8
+ /** What a query resolves to: the data of the call's 2xx replies. */
9
+ export type QueryData<R extends Responses | undefined, Decoded extends boolean> = Success<HttpReply<R, Decoded>>['data'];
10
+ /**
11
+ * A key: `[method, path, input]`, after the client's `scope`, tagged with its
12
+ * data so that `queryClient.getQueryData(key)` is typed.
13
+ */
14
+ export type HttpQueryKey<Data> = DataTag<readonly unknown[], Data>;
15
+ export interface HttpQueryOptions<Data> {
16
+ readonly queryKey: HttpQueryKey<Data>;
17
+ /** Sends the call with the query's `signal`: a cancelled query aborts it. */
18
+ readonly queryFn: (context: {
19
+ readonly signal: AbortSignal;
20
+ }) => Promise<Data>;
21
+ }
22
+ /** How an infinite query pages: TanStack's own options, and where the page goes. */
23
+ export interface Paging<Data, PageParam> {
24
+ /**
25
+ * The query parameter each page's `pageParam` is sent as: `'after'`,
26
+ * `'page'`. A `null` or `undefined` page param is left out.
27
+ */
28
+ readonly pageParamName: string;
29
+ readonly initialPageParam: PageParam;
30
+ readonly getNextPageParam: GetNextPageParamFunction<PageParam, Data>;
31
+ readonly getPreviousPageParam?: GetPreviousPageParamFunction<PageParam, Data>;
32
+ readonly maxPages?: number;
33
+ }
34
+ export interface HttpInfiniteQueryOptions<Data, PageParam> extends Omit<Paging<Data, PageParam>, 'pageParamName'> {
35
+ readonly queryKey: HttpQueryKey<InfiniteData<Data, PageParam>>;
36
+ readonly queryFn: (context: {
37
+ readonly signal: AbortSignal;
38
+ readonly pageParam: PageParam;
39
+ }) => Promise<Data>;
40
+ }
41
+ /** What a mutation's `mutate()` takes: what the call sends, and its call options. */
42
+ export type MutationVariables<Path extends string> = RequestInput<Path> & CallOptions;
43
+ export interface HttpMutationOptions<Path extends string, Data> {
44
+ readonly mutationKey: readonly unknown[];
45
+ /** Optional when the path has no `{name}`s: `mutate()`. */
46
+ readonly mutationFn: (variables: [PathParamNames<Path>] extends [never] ? // biome-ignore lint/suspicious/noConfusingVoidType: void, not undefined, is what lets `mutate()` be called with nothing
47
+ MutationVariables<Path> | void : MutationVariables<Path>) => Promise<Data>;
48
+ }
49
+ /** What a key may hold, for a filter: any part of an input. */
50
+ export interface KeyInput {
51
+ readonly param?: {
52
+ readonly [name: string]: unknown;
53
+ };
54
+ readonly query?: QueryInput;
55
+ readonly json?: unknown;
56
+ readonly form?: unknown;
57
+ readonly text?: string;
58
+ readonly body?: unknown;
59
+ }
60
+ export interface HttpQueries {
61
+ /**
62
+ * A query of a call: `useQuery(queries.queryOptions('get', '/items/{id}', { param: { id }, responses }))`.
63
+ * It resolves to the data of a 2xx reply, and throws a `ReplyStatusError`
64
+ * for any other.
65
+ */
66
+ queryOptions<Path extends string, R extends Responses | undefined = undefined, Decoded extends boolean = true>(method: Method, path: Path, ...args: RequestArgs<Path, R, Decoded>): HttpQueryOptions<QueryData<R, Decoded>>;
67
+ /** An infinite query of a call: each page sent with its `pageParam` as a query parameter. */
68
+ infiniteQueryOptions<Path extends string, PageParam, R extends Responses | undefined = undefined, Decoded extends boolean = true>(method: Method, path: Path, options: RequestOptions<Path, R, Decoded>, paging: Paging<QueryData<R, Decoded>, PageParam>): HttpInfiniteQueryOptions<QueryData<R, Decoded>, PageParam>;
69
+ /**
70
+ * A mutation of a call, whose `mutate()` takes what it sends:
71
+ * `useMutation(queries.mutationOptions('delete', '/items/{id}'))`, then
72
+ * `mutate({ param: { id } })`. `options` holds what every call shares.
73
+ */
74
+ mutationOptions<Path extends string, R extends Responses | undefined = undefined, Decoded extends boolean = true>(method: Method, path: Path, options?: CallOptions & ReplyOptions<R, Decoded>): HttpMutationOptions<Path, QueryData<R, Decoded>>;
75
+ /**
76
+ * A key, or the start of one, for a filter:
77
+ * `queryClient.invalidateQueries({ queryKey: queries.queryKey('get', '/items') })`
78
+ * matches every query of `GET /items`, whatever its input.
79
+ */
80
+ queryKey(method: Method, path?: string, input?: KeyInput): readonly unknown[];
81
+ }
82
+ export interface QueriesOptions {
83
+ /**
84
+ * Put first in every key, to keep two clients' queries apart when their
85
+ * paths are the same: `'catalog'`.
86
+ */
87
+ readonly scope?: string;
88
+ }
89
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/queries/types.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@nxgt/httpyz-query",
3
+ "version": "0.1.0",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "package.json"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./openapi": {
20
+ "types": "./dist/openapi.d.ts",
21
+ "import": "./dist/openapi.js",
22
+ "default": "./dist/openapi.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "nxgt": {
27
+ "entrypoints": [
28
+ "src/index.ts",
29
+ "src/openapi.ts"
30
+ ]
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/softistx/nxgt-http.git",
35
+ "directory": "packages/httpyz-query"
36
+ },
37
+ "publishConfig": {
38
+ "registry": "https://registry.npmjs.org",
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "bun run ../../build.ts",
43
+ "fixtures": "bun run test/generate.ts",
44
+ "test": "bun run fixtures && bun test src",
45
+ "typecheck": "bun run fixtures && tsc --noEmit"
46
+ },
47
+ "devDependencies": {
48
+ "@nxgt/httpyz": "^0.0.0",
49
+ "@nxgt/openapi-codegen": "^0.1.0",
50
+ "@nxgt/openapi-httpyz": "^0.0.0",
51
+ "@tanstack/query-core": "^5.102.8",
52
+ "@types/bun": "^1.4.0",
53
+ "zod": "^4.5.4"
54
+ },
55
+ "peerDependencies": {
56
+ "@nxgt/httpyz": "^0.0.0",
57
+ "@nxgt/openapi-httpyz": "^0.0.0",
58
+ "@tanstack/query-core": "^5.90.0",
59
+ "typescript": "^6.0.3"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@nxgt/openapi-httpyz": {
63
+ "optional": true
64
+ }
65
+ }
66
+ }