@nxgt/openapi-httpyz 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,590 @@
1
+ # @nxgt/openapi-httpyz
2
+
3
+ Binds the operations [`@nxgt/openapi-codegen`](https://www.npmjs.com/package/@nxgt/openapi-codegen)
4
+ generates from an OpenAPI spec onto a client of
5
+ [`@nxgt/httpyz`](https://www.npmjs.com/package/@nxgt/httpyz). Every call is
6
+ checked against the spec at compile time: the path, the parameters, the body
7
+ and each reply the operation declares. A reply is a union narrowed on its
8
+ status, so nothing throws for a status the spec declares.
9
+
10
+ The client sends, with its middleware, auth, retries and timeouts. The
11
+ binding adds what the spec knows: how each parameter and body is written,
12
+ the replies each operation declares, and checks by the server's own
13
+ validators.
14
+
15
+ > **0.x.** The API is still settling.
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ bun add @nxgt/httpyz @nxgt/openapi-httpyz zod
21
+ bun add -d @nxgt/openapi-codegen typescript
22
+ ```
23
+
24
+ Both peers are required: `@nxgt/httpyz`, since the binding uses your client,
25
+ not a copy of its own, and `typescript` 6, for the types. The generated
26
+ `operations.ts` imports `zod`, so the app needs it too.
27
+
28
+ ## Setup
29
+
30
+ Generate the spec's files, into `generated/openapi` unless `-o` says
31
+ otherwise:
32
+
33
+ ```sh
34
+ bunx nxgt-openapi generate -i openapi.yaml
35
+ ```
36
+
37
+ Then bind them:
38
+
39
+ ```ts
40
+ import { createHttpClient } from '@nxgt/httpyz';
41
+ import { createOpenApiClient } from '@nxgt/openapi-httpyz';
42
+ import { operations } from './generated/openapi/operations.js';
43
+
44
+ const http = createHttpClient({
45
+ baseUrl: 'https://api.example.com',
46
+ timeout: 10_000,
47
+ });
48
+
49
+ export const api = createOpenApiClient(http, operations);
50
+ ```
51
+
52
+ `operations`, the runtime table of the generated `operations.ts`, holds each
53
+ parameter's location and style, the body's media types, each reply's schema,
54
+ and the server's validators. Its type also carries each operation's input and
55
+ replies, `ClientOperations` of the generated `types.ts`, so the client is
56
+ typed from the table alone:
57
+
58
+ - `api.get` offers the paths that have a GET operation, and takes what the
59
+ operation at the chosen path takes.
60
+ - The client has only the methods the spec has an operation for: no `trace`
61
+ on a spec without a TRACE operation.
62
+
63
+ The types may also be given, as a table generated before
64
+ `@nxgt/openapi-codegen` carried them requires:
65
+ `createOpenApiClient<ClientOperations, OperationsByRoute>(http, operations)`.
66
+ `OperationsByRoute` may be left out: it is worked out of `ClientOperations`.
67
+
68
+ The client keeps that table as `api.operations`, for a package built over it,
69
+ such as [`@nxgt/httpyz-query/openapi`](https://www.npmjs.com/package/@nxgt/httpyz-query).
70
+
71
+ The client's options are the client's own: see
72
+ [`@nxgt/httpyz`](https://www.npmjs.com/package/@nxgt/httpyz#setup). The
73
+ binding's, `validate` and `decode`, are listed under
74
+ [`createOpenApiClient`](#createopenapiclient).
75
+
76
+ ## Calls
77
+
78
+ ```ts
79
+ // By method and path, QUERY included
80
+ const reply = await api.get('/employees/{id}', { param: { id } });
81
+
82
+ // By operationId
83
+ await api.op('updateEmployee', { param: { id }, json: { name: 'Ada' } });
84
+
85
+ // fetch options, a timeout or a retry come last
86
+ await api.op('listEmployees', { query: { page: 2 } }, { signal, timeout: 2_000 });
87
+ ```
88
+
89
+ A path the spec has no operation for, for that method, does not compile.
90
+ The input holds each part of the request, as `ClientOperations` types it:
91
+
92
+ | Key | Holds | Written |
93
+ | --- | --- | --- |
94
+ | `param` | path parameters | encoded into the path |
95
+ | `query` | query parameters | a list as a repeated key, or joined with commas when the spec says `explode: false` |
96
+ | `header` | header parameters, by lowercased name | a list joined with commas |
97
+ | `json` | a JSON body | `JSON.stringify`, as the JSON type the spec declares |
98
+ | `form` | a form body | URL-encoded when that is the declared type, else multipart `FormData`; a list as a repeated field |
99
+ | `text` | a text body | as it is, as the text type the spec declares |
100
+ | `body` | a binary body: `Blob`, `ArrayBuffer`, `Uint8Array` | as it is, as the binary type the spec declares |
101
+
102
+ The input may be left out when nothing in it is required, and an operation
103
+ that takes nothing takes no input: `api.op('health')`, or
104
+ `api.op('health', { signal })`.
105
+
106
+ ## Cancelling
107
+
108
+ A call's init takes the client's own ways to end it early: its `signal`,
109
+ and `latest`, a key that aborts the call before it with the same key.
110
+ `api.group()` is the same client over the client's `http.group()`, whose
111
+ calls and streams end together:
112
+
113
+ ```ts
114
+ // Typing ahead: only the last search stays in flight
115
+ await api.get('/employees', { query: { name } }, { latest: 'search' });
116
+
117
+ const page = api.group();
118
+ const employees = await page.op('listEmployees');
119
+ page.cancel(); // on leaving the page: its calls and streams still running abort
120
+ ```
121
+
122
+ A cancelled call rejects with an `AbortError`, never a `ClientError`: tell
123
+ it from a failure with the client's `isAbortError()`. The group goes on
124
+ after `cancel()`, and `page.signal` aborts on the next one. The details are
125
+ in [the client's README](https://www.npmjs.com/package/@nxgt/httpyz#cancelling).
126
+
127
+ ## Replies
128
+
129
+ A call resolves to one of the replies the spec declares:
130
+
131
+ ```ts
132
+ import { unwrap } from '@nxgt/httpyz';
133
+
134
+ const reply = await api.get('/employees/{id}', { param: { id } });
135
+ reply.status; // 200 | 404
136
+ reply.type; // the declared media type: 'application/json'
137
+ reply.data; // Employee when status is 200, ErrorResponse when it is 404
138
+ reply.response; // the Response, for its headers: its body is read
139
+
140
+ const employee = unwrap(reply, 200); // or a ReplyStatusError
141
+ ```
142
+
143
+ A form reply's `data` is its `FormData`, and a binary one's a `Blob`. A
144
+ status the spec does not declare for the operation throws the client's
145
+ `UndeclaredStatusError`; errors are the client's own, listed in
146
+ [its README](https://www.npmjs.com/package/@nxgt/httpyz#errors).
147
+
148
+ ## Streams
149
+
150
+ An operation whose reply the spec describes an item at a time, with OpenAPI
151
+ 3.2's `itemSchema`, is read with `stream()`, by its `operationId`. Server-sent
152
+ events come through the client's `events()`, each narrowed on its name, and
153
+ JSON Lines through its `lines()`:
154
+
155
+ ```ts
156
+ const feed = api.stream('watchFeed', { query: { topic: 'news' } });
157
+ for await (const event of feed) {
158
+ if (event.event === 'added') event.data.name; // Item
159
+ else event.data; // 'note': text
160
+ }
161
+
162
+ for await (const item of api.stream('exportItems', { json: { limit: 100 } })) {
163
+ item.id;
164
+ }
165
+ ```
166
+
167
+ - It connects when read, and `close()` or a `break` ends it. Events
168
+ reconnect as `EventSource` does, but for POST and PATCH; the init after the
169
+ input takes `reconnect`, `lastEventId` and `onUnknownEvent`, beside the call
170
+ options.
171
+ - It sends the stream's declared media type as `Accept`, and writes the input
172
+ as a call does.
173
+ - `validate` checks the request on the first read, before anything is sent,
174
+ and each item against its schema; `decode` yields what each schema
175
+ outputs.
176
+ - Only an operation with a stream is accepted; the others do not compile.
177
+
178
+ An event the spec does not declare is not yielded: it goes to
179
+ `onUnknownEvent`. The stream API is the client's, described in
180
+ [its README](https://www.npmjs.com/package/@nxgt/httpyz#streams).
181
+
182
+ ## Validating and decoding
183
+
184
+ The types hold a call to the spec, but not the values in it: a `page` of `0`
185
+ where the spec says `minimum: 1`, or a reply from a server that drifted.
186
+ `validate` checks both with the schemas in the generated `operations` table:
187
+
188
+ - **The request**, before it is sent, by the validators the server runs, on
189
+ what the server will read: each parameter as the text it travels as, the
190
+ JSON as it parses, a form as its fields. A request the server would refuse
191
+ throws a `ValidationError` with the same issues, and is never sent.
192
+ - **The reply**, before it is returned: a JSON or text reply against its
193
+ schema, as [`@nxgt/openapi-hono`](https://www.npmjs.com/package/@nxgt/openapi-hono)
194
+ checks its own with `validateResponses`.
195
+
196
+ ```ts
197
+ createOpenApiClient(http, operations, { validate: { request: true } });
198
+ ```
199
+
200
+ Unlike a plain call of the client, which checks a declared reply by default,
201
+ the binding checks nothing by default: the types already hold both ends to
202
+ the spec.
203
+
204
+ `decode` returns each reply as its schema outputs it, which differs from what
205
+ JSON carries once the spec is generated with `dates: 'date'`: a date-time is
206
+ a `Date`. The client's replies are typed so:
207
+
208
+ ```ts
209
+ const api = createOpenApiClient(http, operations, { decode: true });
210
+ const employee = unwrap(await api.get('/employees/{id}', { param: { id } }), 200);
211
+ employee.hiredAt; // Date
212
+ ```
213
+
214
+ With the types given, a third type argument says the client decodes:
215
+ `createOpenApiClient<ClientOperations, OperationsByRoute, true>`, which does
216
+ not compile without `decode: true`. Decoding validates the reply, whatever
217
+ `validate` says.
218
+
219
+ ## API
220
+
221
+ The examples below use a spec with `getEmployee` at `GET /employees/{id}`,
222
+ `listEmployees` at `GET /employees`, `createEmployee` at `POST /employees`,
223
+ and `watchFeed`, an event stream, at `GET /feed`.
224
+
225
+ ### Functions
226
+
227
+ #### createOpenApiClient
228
+
229
+ ```ts
230
+ // With decode: true, the replies are typed as their schemas output them
231
+ function createOpenApiClient<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>>(
232
+ http: HttpClient,
233
+ operations: OperationTable<Ops>,
234
+ options: OpenApiOptions & { readonly decode: true },
235
+ ): OpenApiClient<Ops, Routes, true>;
236
+
237
+ function createOpenApiClient<
238
+ Ops extends OperationsShape<Ops>,
239
+ Routes = RoutesOf<Ops>,
240
+ Decoded extends boolean = false,
241
+ >(
242
+ http: HttpClient,
243
+ operations: OperationTable<Ops>,
244
+ ...options: OpenApiArgs<Decoded>
245
+ ): OpenApiClient<Ops, Routes, Decoded>;
246
+ ```
247
+
248
+ Binds the generated `operations` table onto `http`, a client of
249
+ `createHttpClient()` or one of its groups. `Ops` is inferred from the table;
250
+ `Routes` defaults to `RoutesOf<Ops>`; `Decoded` is `true` when `decode: true`
251
+ is passed. See [Setup](#setup) and [Validating and decoding](#validating-and-decoding).
252
+
253
+ | Option | Type | Default | Description |
254
+ | --- | --- | --- | --- |
255
+ | `validate` | `boolean \| { request?: boolean; response?: boolean }` | neither | Checks with the spec's schemas, throwing a `ValidationError`: the request before it is sent, the reply before it is returned. `true` is both |
256
+ | `decode` | `true \| false` | `false` | `true` returns each reply as its schema outputs it, and types it so. It validates the reply, whatever `validate` says. A literal: a `boolean` variable does not compile, since the replies' type depends on it |
257
+
258
+ Returns an [`OpenApiClient`](#openapiclient). It throws nothing itself: an
259
+ unknown `operationId` or path fails the call made with it.
260
+
261
+ ### The client
262
+
263
+ #### op
264
+
265
+ ```ts
266
+ api.op<K extends keyof Ops & string>(id: K, ...args: Args<Ops, K>): Promise<WithResponse<OperationReply<Ops, K, Decoded>>>;
267
+ ```
268
+
269
+ Calls an operation by its `operationId`, with its input, if it takes one,
270
+ then an [`OperationInit`](#operationinit). See [Calls](#calls).
271
+
272
+ Resolves to one of the declared replies, `{ status, type, data, response }`,
273
+ as in [Replies](#replies). Rejects with a `ValidationError` when `validate`
274
+ refuses the request or the reply, with the client's errors, such as
275
+ `UndeclaredStatusError`, and with an `Error` for an `operationId` the table
276
+ does not have.
277
+
278
+ #### Path methods
279
+
280
+ ```ts
281
+ api.get<P extends PathsOf<Routes, 'get'>>(path: P, ...args: Args<Ops, IdOf<Ops, Routes, 'get', P>>): Promise<WithResponse<OperationReply<Ops, IdOf<Ops, Routes, 'get', P>, Decoded>>>;
282
+ // and put, post, delete, options, head, patch, trace, query: each the spec has an operation for
283
+ ```
284
+
285
+ Calls the operation at a method and a path, as the spec writes it:
286
+ `api.get('/employees/{id}', { param: { id } })`. The client types only the
287
+ methods in [`MethodsOf<Routes>`](#methodsof), each taking only its
288
+ [`PathsOf`](#pathsof). The arguments, reply and errors are those of
289
+ [`op`](#op); a path with no operation for the method rejects with an `Error`.
290
+
291
+ #### stream
292
+
293
+ ```ts
294
+ api.stream<K extends StreamIds<Ops>>(id: K, ...args: StreamArgs<Ops, K>): OperationStreamOf<Ops, K, Decoded>;
295
+ ```
296
+
297
+ Reads an operation's stream by its `operationId`, an item at a time: an
298
+ `EventStream` of events narrowed on `event`, or a `Stream` of JSON lines. It
299
+ takes the input, then a [`StreamInit`](#streaminit). See [Streams](#streams).
300
+
301
+ It connects when read, and `close()` ends it. It throws an `Error`, where it
302
+ is called, for an operation without a stream. A `ValidationError` from
303
+ `validate`, or a client error, is thrown where the stream is read.
304
+
305
+ #### group
306
+
307
+ ```ts
308
+ api.group(): OpenApiGroup<Ops, Routes, Decoded>;
309
+ ```
310
+
311
+ The same client, with the same options, over the client's `http.group()`:
312
+ its calls and streams also end on its `cancel()`. See [Cancelling](#cancelling).
313
+
314
+ #### group.cancel
315
+
316
+ ```ts
317
+ page.cancel(reason?: unknown): void;
318
+ ```
319
+
320
+ Aborts every call and stream of the group still running, with `reason`, or
321
+ an `AbortError`. The group goes on: the calls made after it run.
322
+
323
+ #### group.signal
324
+
325
+ ```ts
326
+ readonly page.signal: AbortSignal;
327
+ ```
328
+
329
+ Aborts on the next `cancel()`: for work of your own that ends with the
330
+ group's calls. It is a new signal after each `cancel()`.
331
+
332
+ #### operations
333
+
334
+ ```ts
335
+ readonly api.operations: OperationTable<Ops>;
336
+ ```
337
+
338
+ The generated `operations` table the client was bound to, for a package
339
+ built over it that reads the spec as the client does, such as
340
+ [`@nxgt/httpyz-query/openapi`](https://www.npmjs.com/package/@nxgt/httpyz-query).
341
+
342
+ ### Types
343
+
344
+ #### OpenApiClient
345
+
346
+ ```ts
347
+ type OpenApiClient<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>, Decoded extends boolean = false> = {
348
+ op: …; // see op
349
+ stream: …; // see stream
350
+ group(): OpenApiGroup<Ops, Routes, Decoded>;
351
+ readonly operations: OperationTable<Ops>;
352
+ } & PathMethods<Ops, Routes, Decoded>;
353
+ ```
354
+
355
+ What `createOpenApiClient()` returns: the members are under
356
+ [The client](#the-client).
357
+
358
+ #### OpenApiGroup
359
+
360
+ ```ts
361
+ type OpenApiGroup<Ops, Routes = RoutesOf<Ops>, Decoded extends boolean = false> =
362
+ OpenApiClient<Ops, Routes, Decoded> & {
363
+ cancel(reason?: unknown): void;
364
+ readonly signal: AbortSignal;
365
+ };
366
+ ```
367
+
368
+ What [`group()`](#group) returns: the client, with
369
+ [`cancel`](#groupcancel) and [`signal`](#groupsignal).
370
+
371
+ #### PathMethods
372
+
373
+ ```ts
374
+ type PathMethods<Ops, Routes = RoutesOf<Ops>, Decoded extends boolean = false> = {
375
+ readonly [M in MethodsOf<Routes>]: <P extends PathsOf<Routes, M>>(
376
+ path: P,
377
+ ...args: Args<Ops, IdOf<Ops, Routes, M, P>>
378
+ ) => Promise<WithResponse<OperationReply<Ops, IdOf<Ops, Routes, M, P>, Decoded>>>;
379
+ };
380
+ ```
381
+
382
+ The [path methods](#path-methods) alone, for a package that offers them on an
383
+ object of its own, as [`@nxgt/datasource-rest`](https://www.npmjs.com/package/@nxgt/datasource-rest)
384
+ does. `PathMethods<ClientOperations>` has `get` and `post` on the example spec.
385
+
386
+ #### OpenApiOptions
387
+
388
+ | Field | Type | Description |
389
+ | --- | --- | --- |
390
+ | `validate` | `boolean \| { readonly request?: boolean; readonly response?: boolean }` | Checks the request, the reply, or both (`true`). Default: neither |
391
+
392
+ The options of [`createOpenApiClient`](#createopenapiclient), beside `decode`.
393
+
394
+ #### OpenApiArgs
395
+
396
+ ```ts
397
+ type OpenApiArgs<Decoded extends boolean> = Decoded extends true
398
+ ? [options: OpenApiOptions & { readonly decode: true }]
399
+ : [options?: OpenApiOptions & { readonly decode?: false }];
400
+ ```
401
+
402
+ The options argument of `createOpenApiClient()`, required with `decode: true`
403
+ when `Decoded` is `true`. `OpenApiArgs<false>` resolves to
404
+ `[options?: OpenApiOptions & { readonly decode?: false }]`.
405
+
406
+ #### OperationInit
407
+
408
+ ```ts
409
+ type OperationInit = Omit<CallOptions, 'operationId'>;
410
+ ```
411
+
412
+ What a call takes after its input: the client's call options, the binding
413
+ naming the call itself.
414
+
415
+ | Field | Type | Description |
416
+ | --- | --- | --- |
417
+ | `headers` | `HeadersInit` | Over the client's `headers`; a `Content-Type` here gives way to the one a JSON, text or binary body sets |
418
+ | `timeout` | `number` | This call's timeout, instead of the client's |
419
+ | `retry` | `number \| RetryOptions \| false` | This call's retry, instead of the client's: `false` never retries |
420
+ | `latest` | `string` | Aborts the call before it with the same key, if it still runs |
421
+ | `signal`, `cache`, `credentials`, … | as in `RequestInit` | fetch's own options, but `method`, `body` and `headers` |
422
+
423
+ #### StreamInit
424
+
425
+ ```ts
426
+ interface StreamInit extends OperationInit {
427
+ reconnect?: boolean | ReconnectOptions;
428
+ lastEventId?: string;
429
+ onUnknownEvent?: (event: ServerEvent) => void;
430
+ }
431
+ ```
432
+
433
+ What [`stream()`](#stream) takes after its input.
434
+
435
+ | Field | Type | Description |
436
+ | --- | --- | --- |
437
+ | `reconnect` | `boolean \| ReconnectOptions` | Events only: connects again when the connection drops or the stream ends, as `EventSource` does. Default: on, but for POST and PATCH |
438
+ | `lastEventId` | `string` | Events only: sent as `Last-Event-ID` on the first connection |
439
+ | `onUnknownEvent` | `(event: ServerEvent) => void` | Events only: an event the spec does not declare, which is not yielded |
440
+
441
+ #### Args
442
+
443
+ A call's arguments after the `operationId` or the path: the operation's own,
444
+ then an `OperationInit`.
445
+ `Args<ClientOperations, 'getEmployee'>` resolves to
446
+ `[input: { param: { id: number } }, init?: OperationInit]`.
447
+
448
+ #### StreamArgs
449
+
450
+ A stream's arguments after the `operationId`: the operation's own, then a
451
+ `StreamInit`.
452
+ `StreamArgs<ClientOperations, 'watchFeed'>` resolves to
453
+ `[input: { query: { topic: string } }, init?: StreamInit]`.
454
+
455
+ #### OperationReply
456
+
457
+ An operation's replies, decoded or as JSON carries them, as `op()` and the
458
+ path methods resolve to them without `response`.
459
+ `OperationReply<ClientOperations, 'getEmployee', false>` resolves to
460
+ `ClientOperations['getEmployee']['wire']`, and with `true` to its `reply`.
461
+
462
+ #### OperationStreamOf
463
+
464
+ What [`stream()`](#stream) returns: an `EventStream` for server-sent events, a
465
+ `Stream` for JSON lines, of items decoded or as JSON carries them.
466
+ `OperationStreamOf<ClientOperations, 'watchFeed', false>` resolves to
467
+ `EventStream<ClientOperations['watchFeed']['stream']['wire']>`.
468
+
469
+ #### StreamIds
470
+
471
+ The operations that reply with a stream: the only ones `stream()` accepts.
472
+ `StreamIds<ClientOperations>` resolves to `'watchFeed'`.
473
+
474
+ #### RoutesOf
475
+
476
+ `OperationsByRoute`, worked out of `ClientOperations`: each `'method path'`
477
+ to its `operationId`, and the default of `Routes`.
478
+ `RoutesOf<ClientOperations>['get /employees/{id}']` resolves to `'getEmployee'`.
479
+
480
+ #### MethodsOf
481
+
482
+ The methods the spec has an operation for: the only path methods a client
483
+ offers. `MethodsOf<RoutesOf<ClientOperations>>` resolves to `'get' | 'post'`.
484
+
485
+ #### PathsOf
486
+
487
+ The paths with an operation for a method.
488
+ `PathsOf<RoutesOf<ClientOperations>, 'get'>` resolves to
489
+ `'/employees' | '/employees/{id}' | '/feed'`.
490
+
491
+ #### IdOf
492
+
493
+ The `operationId` of the operation at a method and a path.
494
+ `IdOf<ClientOperations, RoutesOf<ClientOperations>, 'get', '/employees/{id}'>`
495
+ resolves to `'getEmployee'`.
496
+
497
+ #### OperationsShape
498
+
499
+ ```ts
500
+ type OperationsShape<Ops> = { [K in keyof Ops]: ClientOperation };
501
+ ```
502
+
503
+ The constraint on `Ops`: the generated `ClientOperations`, an entry per
504
+ `operationId`.
505
+
506
+ #### ClientOperation
507
+
508
+ | Field | Type | Description |
509
+ | --- | --- | --- |
510
+ | `method` | `Method` | The operation's method, lowercased |
511
+ | `path` | `string` | The path as the spec writes it |
512
+ | `args` | `readonly unknown[]` | What a call takes after the `operationId`: `[input]`, `[input?]` or `[]` |
513
+ | `reply` | `unknown` | Every declared reply, `{ status; type; data }`, decoded |
514
+ | `wire` | `unknown` | The same replies as JSON carries them |
515
+ | `stream` | `OperationStream`, optional | A reply read an item at a time, when the operation has one |
516
+
517
+ What the binding reads of an entry of the generated `ClientOperations`.
518
+
519
+ #### OperationStream
520
+
521
+ | Field | Type | Description |
522
+ | --- | --- | --- |
523
+ | `kind` | `'sse' \| 'jsonl'` | Server-sent events, or JSON lines |
524
+ | `item` | `unknown` | Each item, decoded: an event narrowed on `event`, or a line |
525
+ | `wire` | `unknown` | Each item as JSON carries it |
526
+
527
+ The `stream` of a `ClientOperation`.
528
+
529
+ #### OperationTable
530
+
531
+ ```ts
532
+ type OperationTable<Ops> = {
533
+ readonly [K in keyof Ops]: RuntimeOperation & { readonly '~client'?: Ops[K] };
534
+ };
535
+ ```
536
+
537
+ The type of the generated `operations` table, and of `api.operations`. Each
538
+ entry carries its `ClientOperations` entry as `'~client'`, a type that is
539
+ never set, which is how `createOpenApiClient(http, operations)` infers `Ops`.
540
+
541
+ #### RuntimeOperation
542
+
543
+ | Field | Type | Description |
544
+ | --- | --- | --- |
545
+ | `method` | `Method` | The operation's method |
546
+ | `path` | `string` | As the spec writes it: `/employees/{id}` |
547
+ | `parameters` | `readonly RuntimeParameter[]` | How each parameter is written |
548
+ | `param`, `query`, `header` | `StandardSchemaV1`, optional | The server's validators of each location, which read text: for `validate` |
549
+ | `body` | `{ required: boolean; content: { [mediaType]: RuntimeMedia } }`, optional | The request body's media types |
550
+ | `responses` | `{ [status]: { [mediaType]: RuntimeMedia } }` | Each declared reply's media types |
551
+
552
+ What the binding reads of an entry of the `operations` table.
553
+
554
+ #### RuntimeParameter
555
+
556
+ | Field | Type | Description |
557
+ | --- | --- | --- |
558
+ | `name` | `string` | As the spec writes it |
559
+ | `in` | `'path' \| 'query' \| 'header'` | Where it goes |
560
+ | `required` | `boolean` | Whether the spec requires it |
561
+ | `explode` | `boolean` | A query list as `?a=1&a=2` (`true`) or `?a=1,2` (`false`) |
562
+ | `list` | `boolean` | Whether it is validated as a list |
563
+
564
+ An entry of a `RuntimeOperation`'s `parameters`.
565
+
566
+ #### RuntimeMedia
567
+
568
+ | Field | Type | Description |
569
+ | --- | --- | --- |
570
+ | `kind` | `'json' \| 'form' \| 'text' \| 'binary' \| 'sse' \| 'jsonl'` | How the content is read or written |
571
+ | `schema` | `StandardSchemaV1`, optional | The content's schema; absent for binary content and a stream |
572
+ | `events` | `{ [event]: StandardSchemaV1 \| null }`, optional | `sse`: each event's data, by name: a schema for JSON, `null` for text |
573
+ | `item` | `StandardSchemaV1`, optional | `jsonl`: each item |
574
+
575
+ A media type of a `RuntimeOperation`'s `body` or `responses`.
576
+
577
+ ## Traps
578
+
579
+ - **Without `decode`, a reply is typed as JSON carries it**: with
580
+ `dates: 'date'`, a date-time is a string.
581
+ - **A request is typed as JSON carries it, whatever `decode` says.** With
582
+ `dates: 'date'`, a date-time in a body or a query is still typed as a
583
+ string: pass `date.toISOString()`.
584
+ - **Give each operation its exact statuses.** A `default` or `4XX` reply is
585
+ not generated, so it throws `UndeclaredStatusError`.
586
+ - **A stream's request is refused where the stream is read**, not where
587
+ `stream()` is called: put the `try` around the `for await`.
588
+ - **The client's `baseUrl` is still required outside a browser**, and its
589
+ options, `auth`, `retry` and `use` included, apply to every call the
590
+ binding makes.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `createOpenApiClient`: the operations `@nxgt/openapi-codegen` generates,
3
+ * bound onto a client of `createHttpClient`. The client sends; the binding
4
+ * adds what the spec knows: how each parameter and body is written, the
5
+ * replies each operation declares, and checks by the server's own schemas.
6
+ */
7
+ import { type HttpClient } from '@nxgt/httpyz';
8
+ import type { OpenApiArgs, OpenApiClient, OpenApiOptions, OperationsShape, OperationTable, RoutesOf } from './types';
9
+ /**
10
+ * A client for a spec, bound to the `operations` table of the generated
11
+ * `operations.ts`, which carries the spec's types: nothing else to import.
12
+ *
13
+ * ```ts
14
+ * const http = createHttpClient({ baseUrl: 'https://api.example.com' });
15
+ * const api = createOpenApiClient(http, operations);
16
+ * const reply = await api.get('/employees/{id}', { param: { id } });
17
+ * if (reply.status === 200) reply.data.name;
18
+ * ```
19
+ *
20
+ * With `decode: true`, each reply is returned as its schema outputs it, and
21
+ * typed so. The types may also be given: `createOpenApiClient<ClientOperations,
22
+ * OperationsByRoute, true>`, whose `true` then requires `decode: true`.
23
+ */
24
+ export declare function createOpenApiClient<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>>(http: HttpClient, operations: OperationTable<Ops>, options: OpenApiOptions & {
25
+ readonly decode: true;
26
+ }): OpenApiClient<Ops, Routes, true>;
27
+ export declare function createOpenApiClient<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>, Decoded extends boolean = false>(http: HttpClient, operations: OperationTable<Ops>, ...[given]: OpenApiArgs<Decoded>): OpenApiClient<Ops, Routes, Decoded>;
28
+ //# sourceMappingURL=create-openapi-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-openapi-client.d.ts","sourceRoot":"","sources":["../../src/client/create-openapi-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEN,KAAK,UAAU,EAGf,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EACX,WAAW,EACX,aAAa,EACb,cAAc,EACd,eAAe,EACf,cAAc,EACd,QAAQ,EAIR,MAAM,SAAS,CAAC;AA6DjB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAClC,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EAChC,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,EAEtB,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,cAAc,CAAC,GAAG,CAAC,EAC/B,OAAO,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,GACjD,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;AACpC,wBAAgB,mBAAmB,CAClC,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EAChC,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,EACtB,OAAO,SAAS,OAAO,GAAG,KAAK,EAE/B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,cAAc,CAAC,GAAG,CAAC,EAC/B,GAAG,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,GAC9B,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * The types of a client bound to a generated spec. Every lookup is an index
3
+ * into the interfaces the generator wrote (`ClientOperations`,
4
+ * `OperationsByRoute`), so a call costs TypeScript the same whether the spec
5
+ * has five operations or five hundred.
6
+ */
7
+ import type { CallOptions, EventStream, Method, ReconnectOptions, ServerEvent, StandardSchemaV1, Stream, WithResponse } from '@nxgt/httpyz';
8
+ /** What the binding reads of an entry of the generated `ClientOperations`. */
9
+ export interface ClientOperation {
10
+ readonly method: Method;
11
+ readonly path: string;
12
+ /** What a call takes after the `operationId`: `[input]`, `[input?]` or `[]`. */
13
+ readonly args: readonly unknown[];
14
+ /** Every declared reply, `{ status; type; data }`, decoded. */
15
+ readonly reply: unknown;
16
+ /** The same replies as JSON carries them. */
17
+ readonly wire: unknown;
18
+ /** A reply read an item at a time, when the operation has one. */
19
+ readonly stream?: OperationStream;
20
+ }
21
+ /** An operation's stream: server-sent events, or JSON lines. */
22
+ export interface OperationStream {
23
+ readonly kind: 'sse' | 'jsonl';
24
+ /** Each item, decoded: an event narrowed on `event`, or a line. */
25
+ readonly item: unknown;
26
+ /** Each item as JSON carries it. */
27
+ readonly wire: unknown;
28
+ }
29
+ /** The generated `ClientOperations`: an entry per `operationId`. */
30
+ export type OperationsShape<Ops> = {
31
+ [K in keyof Ops]: ClientOperation;
32
+ };
33
+ /** How a parameter is written: an entry of `parameters` in `operations.ts`. */
34
+ export interface RuntimeParameter {
35
+ readonly name: string;
36
+ readonly in: 'path' | 'query' | 'header';
37
+ readonly required: boolean;
38
+ /** A query list as `?a=1&a=2` (true) or `?a=1,2` (false). */
39
+ readonly explode: boolean;
40
+ readonly list: boolean;
41
+ }
42
+ export interface RuntimeMedia {
43
+ readonly kind: 'json' | 'form' | 'text' | 'binary' | 'sse' | 'jsonl';
44
+ readonly schema?: StandardSchemaV1;
45
+ /** `sse`: each event's data, by name: a schema for JSON, `null` for text. */
46
+ readonly events?: {
47
+ readonly [event: string]: StandardSchemaV1 | null;
48
+ };
49
+ /** `jsonl`: each item. */
50
+ readonly item?: StandardSchemaV1;
51
+ }
52
+ /** What the binding reads of an entry of the generated `operations` table. */
53
+ export interface RuntimeOperation {
54
+ readonly method: Method;
55
+ /** As the spec writes it: `/employees/{id}`. */
56
+ readonly path: string;
57
+ readonly parameters: readonly RuntimeParameter[];
58
+ /** The server's validators of each location, which read text: for `validate`. */
59
+ readonly param?: StandardSchemaV1;
60
+ readonly query?: StandardSchemaV1;
61
+ readonly header?: StandardSchemaV1;
62
+ readonly body?: {
63
+ readonly required: boolean;
64
+ readonly content: {
65
+ readonly [mediaType: string]: RuntimeMedia;
66
+ };
67
+ };
68
+ readonly responses: {
69
+ readonly [status: number]: {
70
+ readonly [mediaType: string]: RuntimeMedia;
71
+ };
72
+ };
73
+ }
74
+ /**
75
+ * The generated `operations` table, keyed like `ClientOperations`. Each entry
76
+ * carries its `ClientOperations` entry as `'~client'`, a type that is never
77
+ * set, which is how `createOpenApiClient(http, operations)` infers `Ops`.
78
+ */
79
+ export type OperationTable<Ops> = {
80
+ readonly [K in keyof Ops]: RuntimeOperation & {
81
+ readonly '~client'?: Ops[K];
82
+ };
83
+ };
84
+ /** `OperationsByRoute`, worked out of `ClientOperations`: `'get /items/{id}'` to its `operationId`. */
85
+ export type RoutesOf<Ops extends OperationsShape<Ops>> = {
86
+ [K in keyof Ops as `${Ops[K]['method']} ${Ops[K]['path']}`]: K;
87
+ };
88
+ export interface OpenApiOptions {
89
+ /**
90
+ * Checks with the spec's schemas, throwing a `ValidationError`: the
91
+ * request before it is sent, the reply before it is returned. `true` is
92
+ * both. Default: neither, since the types already hold both to the spec.
93
+ */
94
+ validate?: boolean | {
95
+ readonly request?: boolean;
96
+ readonly response?: boolean;
97
+ };
98
+ }
99
+ /**
100
+ * The binding's options, and `decode`, which returns each reply as its schema
101
+ * outputs it: with `dates: 'date'`, a date-time as a `Date`. It changes the
102
+ * replies' types: a client whose types are given says so in its third type
103
+ * argument, `createOpenApiClient<ClientOperations, OperationsByRoute, true>`,
104
+ * which in turn requires `decode: true`. Decoding validates the reply.
105
+ */
106
+ export type OpenApiArgs<Decoded extends boolean> = Decoded extends true ? [options: OpenApiOptions & {
107
+ readonly decode: true;
108
+ }] : [options?: OpenApiOptions & {
109
+ readonly decode?: false;
110
+ }];
111
+ /** What a call takes after its input: the core client's call options. */
112
+ export type OperationInit = Omit<CallOptions, 'operationId'>;
113
+ /** A call's arguments after the `operationId` or the path. */
114
+ export type Args<Ops extends OperationsShape<Ops>, K extends keyof Ops> = [
115
+ ...Ops[K]['args'],
116
+ init?: OperationInit
117
+ ];
118
+ /** An operation's replies: decoded, or as JSON carries them. */
119
+ export type OperationReply<Ops extends OperationsShape<Ops>, K extends keyof Ops, Decoded extends boolean> = Decoded extends true ? Ops[K]['reply'] : Ops[K]['wire'];
120
+ /** The operations that reply with a stream. Worked out once per client type. */
121
+ export type StreamIds<Ops> = {
122
+ [K in keyof Ops]: Ops[K] extends {
123
+ readonly stream: OperationStream;
124
+ } ? K : never;
125
+ }[keyof Ops] & string;
126
+ /** What a stream takes after its input: the call options, and for events, how to resume. */
127
+ export interface StreamInit extends OperationInit {
128
+ /**
129
+ * Events only: connects again when the connection drops or the stream
130
+ * ends, as `EventSource` does. Default: on, but for POST and PATCH.
131
+ */
132
+ reconnect?: boolean | ReconnectOptions;
133
+ /** Events only: sent as `Last-Event-ID` on the first connection. */
134
+ lastEventId?: string;
135
+ /** Events only: an event the spec does not declare, which is not yielded. */
136
+ onUnknownEvent?: (event: ServerEvent) => void;
137
+ }
138
+ /** A stream's arguments after the `operationId`. */
139
+ export type StreamArgs<Ops extends OperationsShape<Ops>, K extends keyof Ops> = [...Ops[K]['args'], init?: StreamInit];
140
+ /** What `stream()` returns: events narrowed on `event`, or lines; decoded, or as JSON carries them. */
141
+ export type OperationStreamOf<Ops extends OperationsShape<Ops>, K extends keyof Ops, Decoded extends boolean> = Ops[K] extends {
142
+ readonly stream: {
143
+ readonly kind: infer Kind;
144
+ readonly item: infer Item;
145
+ readonly wire: infer Wire;
146
+ };
147
+ } ? Kind extends 'sse' ? EventStream<Decoded extends true ? Item : Wire> : Stream<Decoded extends true ? Item : Wire> : never;
148
+ /** A bound client whose calls end together. */
149
+ export type OpenApiGroup<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>, Decoded extends boolean = false> = OpenApiClient<Ops, Routes, Decoded> & {
150
+ /**
151
+ * Aborts every call and stream of the group still running, with `reason`,
152
+ * or an `AbortError`. The group goes on: the calls made after it run.
153
+ */
154
+ cancel(reason?: unknown): void;
155
+ /** Aborts on the next `cancel()`: for work of your own that ends with the group's calls. */
156
+ readonly signal: AbortSignal;
157
+ };
158
+ /** The paths with an operation for method `M`: `'/items/{id}'` from `'get /items/{id}'`. */
159
+ export type PathsOf<Routes, M extends Method> = keyof Routes extends infer Route ? Route extends `${M} ${infer Path}` ? Path : never : never;
160
+ /** The methods the spec has an operation for: the only ones a client offers. */
161
+ export type MethodsOf<Routes> = keyof Routes extends infer Route ? Route extends `${infer M extends Method} ${string}` ? M : never : never;
162
+ /** The `operationId` of the operation at `M P`. */
163
+ export type IdOf<Ops, Routes, M extends Method, P extends string> = Routes[`${M} ${P}` & keyof Routes] & keyof Ops;
164
+ export type OpenApiClient<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>, Decoded extends boolean = false> = {
165
+ /** Calls an operation by its `operationId`. */
166
+ op<K extends keyof Ops & string>(id: K, ...args: Args<Ops, K>): Promise<WithResponse<OperationReply<Ops, K, Decoded>>>;
167
+ /**
168
+ * Reads an operation's stream, an item at a time, by its `operationId`:
169
+ * its events, each narrowed on `event`, or its JSON lines. It connects when
170
+ * read: `for await (const event of api.stream('watchFeed', input))`.
171
+ */
172
+ stream<K extends StreamIds<Ops>>(id: K, ...args: StreamArgs<Ops, K>): OperationStreamOf<Ops, K, Decoded>;
173
+ /**
174
+ * The same client over the core client's `group()`: its calls and streams
175
+ * also end on `cancel()`, a page's or a component's together.
176
+ */
177
+ group(): OpenApiGroup<Ops, Routes, Decoded>;
178
+ /**
179
+ * The generated `operations` the client was bound to: for a package built
180
+ * over it, such as `@nxgt/httpyz-query/openapi`, which reads the spec as
181
+ * the client does.
182
+ */
183
+ readonly operations: OperationTable<Ops>;
184
+ } & PathMethods<Ops, Routes, Decoded>;
185
+ /**
186
+ * A method per HTTP method the spec has an operation for, which calls the
187
+ * operation at a path: `api.get('/employees/{id}', { param: { id } })`. For
188
+ * a package that offers them on an object of its own, as
189
+ * `@nxgt/datasource-rest` does.
190
+ */
191
+ export type PathMethods<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>, Decoded extends boolean = false> = {
192
+ readonly [M in MethodsOf<Routes>]: <P extends PathsOf<Routes, M>>(path: P, ...args: Args<Ops, IdOf<Ops, Routes, M, P>>) => Promise<WithResponse<OperationReply<Ops, IdOf<Ops, Routes, M, P>, Decoded>>>;
193
+ };
194
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { createOpenApiClient } from './client/create-openapi-client';
2
+ export type { Args, ClientOperation, IdOf, MethodsOf, OpenApiArgs, OpenApiClient, OpenApiGroup, OpenApiOptions, OperationInit, OperationReply, OperationStream, OperationStreamOf, OperationsShape, OperationTable, PathMethods, PathsOf, RoutesOf, RuntimeMedia, RuntimeOperation, RuntimeParameter, StreamArgs, StreamIds, StreamInit, } from './client/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,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,YAAY,EACX,IAAI,EACJ,eAAe,EACf,IAAI,EACJ,SAAS,EACT,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,EACb,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,WAAW,EACX,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,UAAU,GACV,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ // src/client/create-openapi-client.ts
2
+ import {
3
+ ValidationError
4
+ } from "@nxgt/httpyz";
5
+ import { METHODS } from "@nxgt/httpyz/integration";
6
+
7
+ // src/request/check-request.ts
8
+ import { absent, check, fields, text } from "@nxgt/httpyz/integration";
9
+ async function checkRequest(operation, input) {
10
+ const issues = [];
11
+ const run = async (target, schema, value) => {
12
+ if (!schema)
13
+ return;
14
+ const result = await check(schema, value, target);
15
+ if (!result.ok)
16
+ issues.push(...result.issues);
17
+ };
18
+ const { param, query, header } = readParameters(operation, input);
19
+ await run("param", operation.param, param);
20
+ await run("query", operation.query, query);
21
+ await run("header", operation.header, header);
22
+ const content = Object.values(operation.body?.content ?? {});
23
+ const schemaOf = (kind) => content.find((media) => media.kind === kind)?.schema;
24
+ if (input?.json !== undefined) {
25
+ const sent = JSON.stringify(input.json);
26
+ await run("json", schemaOf("json"), sent === undefined ? undefined : JSON.parse(sent));
27
+ } else if (input?.form !== undefined) {
28
+ await run("form", schemaOf("form"), formFields(input.form));
29
+ } else if (input?.text !== undefined) {
30
+ await run("body", schemaOf("text"), input.text);
31
+ }
32
+ return issues;
33
+ }
34
+ function readParameters(operation, input) {
35
+ const read = { param: {}, query: {}, header: {} };
36
+ for (const parameter of operation.parameters) {
37
+ const { name, list } = parameter;
38
+ if (parameter.in === "path") {
39
+ const value = input?.param?.[name];
40
+ if (!absent(value))
41
+ read.param[name] = text(value);
42
+ continue;
43
+ }
44
+ const value = parameter.in === "query" ? input?.query?.[name] : input?.header?.[name.toLowerCase()];
45
+ if (absent(value))
46
+ continue;
47
+ const values = Array.isArray(value) ? value.map(text) : [text(value)];
48
+ if (parameter.in === "header") {
49
+ const sent = values.join(",").trim();
50
+ read.header[name.toLowerCase()] = list ? sent.split(",").map((item) => item.trim()) : sent;
51
+ } else if (list) {
52
+ read.query[name] = parameter.explode ? values : values.join(",").split(",");
53
+ } else
54
+ read.query[name] = values.join(",");
55
+ }
56
+ return read;
57
+ }
58
+ function formFields(form) {
59
+ const read = {};
60
+ for (const [name, value] of fields(form)) {
61
+ const item = value instanceof Blob && !(value instanceof File) ? new File([value], "blob", { type: value.type }) : value;
62
+ const held = read[name];
63
+ read[name] = held === undefined ? name.endsWith("[]") ? [item] : item : Array.isArray(held) ? [...held, item] : [held, item];
64
+ }
65
+ return read;
66
+ }
67
+
68
+ // src/request/to-request.ts
69
+ import {
70
+ absent as absent2,
71
+ text as text2,
72
+ toFormData,
73
+ toSearchParams
74
+ } from "@nxgt/httpyz/integration";
75
+ var concrete = (type, fallback) => type === undefined || type.includes("*") ? fallback : type;
76
+ function toRequest(operation, input, headers) {
77
+ const query = new URLSearchParams;
78
+ for (const param of operation.parameters) {
79
+ if (param.in === "query") {
80
+ appendQuery(query, param, input?.query?.[param.name]);
81
+ } else if (param.in === "header") {
82
+ const value = input?.header?.[param.name.toLowerCase()];
83
+ if (absent2(value))
84
+ continue;
85
+ headers.set(param.name, Array.isArray(value) ? value.map(text2).join(",") : text2(value));
86
+ }
87
+ }
88
+ return { query, body: toBody(operation, input, headers) };
89
+ }
90
+ function appendQuery(search, param, value) {
91
+ if (absent2(value))
92
+ return;
93
+ if (!Array.isArray(value))
94
+ search.append(param.name, text2(value));
95
+ else if (param.explode) {
96
+ for (const item of value)
97
+ search.append(param.name, text2(item));
98
+ } else
99
+ search.append(param.name, value.map(text2).join(","));
100
+ }
101
+ function toBody(operation, input, headers) {
102
+ if (input === undefined)
103
+ return {};
104
+ const content = Object.entries(operation.body?.content ?? {});
105
+ const declared = (kind) => content.find(([, media]) => media.kind === kind)?.[0];
106
+ if (input.json !== undefined) {
107
+ headers.set("content-type", concrete(declared("json"), "application/json"));
108
+ return { json: input.json };
109
+ }
110
+ if (input.form !== undefined) {
111
+ return {
112
+ form: declared("form") === "application/x-www-form-urlencoded" ? toSearchParams(input.form) : toFormData(input.form)
113
+ };
114
+ }
115
+ if (input.text !== undefined) {
116
+ headers.set("content-type", concrete(declared("text"), "text/plain"));
117
+ return { text: input.text };
118
+ }
119
+ if (input.body !== undefined) {
120
+ headers.set("content-type", concrete(declared("binary"), "application/octet-stream"));
121
+ return { body: input.body };
122
+ }
123
+ return {};
124
+ }
125
+
126
+ // src/client/create-openapi-client.ts
127
+ var toResponses = (operation) => Object.fromEntries(Object.entries(operation.responses).map(([status, content]) => [
128
+ status,
129
+ Object.keys(content).length === 0 ? null : Object.fromEntries(Object.entries(content).map(([type, media]) => [
130
+ type,
131
+ media.schema ?? null
132
+ ]))
133
+ ]));
134
+ function streamOf(operation) {
135
+ for (const [status, content] of Object.entries(operation.responses)) {
136
+ const code = Number(status);
137
+ if (code < 200 || code > 299)
138
+ continue;
139
+ for (const [type, media] of Object.entries(content)) {
140
+ if (media.kind === "sse" || media.kind === "jsonl")
141
+ return [type, media];
142
+ }
143
+ }
144
+ return;
145
+ }
146
+ function checkedFirst(open, check) {
147
+ let inner;
148
+ let closed = false;
149
+ return {
150
+ get lastEventId() {
151
+ return inner?.lastEventId;
152
+ },
153
+ close() {
154
+ closed = true;
155
+ inner?.close();
156
+ },
157
+ async* [Symbol.asyncIterator]() {
158
+ if (!inner) {
159
+ await check();
160
+ if (closed)
161
+ return;
162
+ inner = open();
163
+ }
164
+ yield* inner;
165
+ }
166
+ };
167
+ }
168
+ function createOpenApiClient(http, operations, given) {
169
+ return bind(http, operations, given ?? {}, () => {
170
+ return;
171
+ });
172
+ }
173
+ var joined = (given, scope) => scope === undefined ? given : given ? AbortSignal.any([given, scope]) : scope;
174
+ function bind(http, operations, options, scope) {
175
+ const { validate = false } = options;
176
+ const decode = options.decode === true;
177
+ const checks = {
178
+ request: validate === true || typeof validate === "object" && validate.request === true,
179
+ response: decode || validate === true || typeof validate === "object" && validate.response === true
180
+ };
181
+ const table = operations;
182
+ const byRoute = new Map;
183
+ const responses = new Map;
184
+ for (const [id, operation] of Object.entries(table)) {
185
+ byRoute.set(`${operation.method} ${operation.path}`, id);
186
+ responses.set(id, toResponses(operation));
187
+ }
188
+ const read = (id, args) => {
189
+ const operation = table[id];
190
+ if (!operation)
191
+ throw new Error(`${id} is not an operationId of the spec`);
192
+ const takes = operation.parameters.length > 0 || Object.keys(operation.body?.content ?? {}).length > 0;
193
+ const input = takes ? args[0] : undefined;
194
+ const given = (takes ? args[1] : args[0]) ?? {};
195
+ const signal = joined(given.signal, scope());
196
+ const init = signal ? { ...given, signal } : given;
197
+ return { operation, input, init };
198
+ };
199
+ const check = async (id, operation, input) => {
200
+ if (!checks.request)
201
+ return;
202
+ const issues = await checkRequest(operation, input);
203
+ if (issues.length === 0)
204
+ return;
205
+ const context = {
206
+ operationId: id,
207
+ method: operation.method,
208
+ path: operation.path
209
+ };
210
+ throw new ValidationError(context, {
211
+ kind: "request",
212
+ ...context,
213
+ issues
214
+ });
215
+ };
216
+ const written = (id, operation, input, init) => {
217
+ const headers = new Headers(init.headers);
218
+ const { query, body } = toRequest(operation, input, headers);
219
+ return {
220
+ ...init,
221
+ ...body,
222
+ param: input?.param,
223
+ query,
224
+ headers,
225
+ operationId: id
226
+ };
227
+ };
228
+ const call = async (id, args) => {
229
+ const { operation, input, init } = read(id, args);
230
+ await check(id, operation, input);
231
+ const request = http.request;
232
+ return request(operation.method, operation.path, {
233
+ ...written(id, operation, input, init),
234
+ responses: responses.get(id),
235
+ validate: checks.response,
236
+ decode
237
+ });
238
+ };
239
+ const stream = (id, args) => {
240
+ const { operation, input, init } = read(id, args);
241
+ const found = streamOf(operation);
242
+ if (!found)
243
+ throw new Error(`${id} does not reply with a stream`);
244
+ const [type, media] = found;
245
+ const { reconnect, lastEventId, onUnknownEvent, ...rest } = init;
246
+ const open = () => {
247
+ const request = written(id, operation, input, rest);
248
+ if (!request.headers.has("accept"))
249
+ request.headers.set("accept", type);
250
+ const common = {
251
+ ...request,
252
+ method: operation.method,
253
+ validate: checks.response,
254
+ decode
255
+ };
256
+ const openStream = media.kind === "sse" ? http.events : http.lines;
257
+ return openStream(operation.path, media.kind === "sse" ? {
258
+ ...common,
259
+ events: media.events,
260
+ reconnect,
261
+ lastEventId,
262
+ onUnknownEvent
263
+ } : { ...common, item: media.item });
264
+ };
265
+ return checks.request ? checkedFirst(open, () => check(id, operation, input)) : open();
266
+ };
267
+ const client = {
268
+ operations,
269
+ op: (id, ...args) => call(id, args),
270
+ stream: (id, ...args) => stream(id, args),
271
+ group: () => {
272
+ const group = http.group();
273
+ const bound = bind(group, operations, options, () => group.signal);
274
+ return Object.defineProperties(bound, {
275
+ cancel: { enumerable: true, value: group.cancel },
276
+ signal: { enumerable: true, get: () => group.signal }
277
+ });
278
+ }
279
+ };
280
+ for (const method of METHODS) {
281
+ client[method] = (path, ...args) => {
282
+ const id = byRoute.get(`${method} ${path}`);
283
+ if (id === undefined) {
284
+ return Promise.reject(new Error(`The spec has no ${method.toUpperCase()} ${path} operation`));
285
+ }
286
+ return call(id, args);
287
+ };
288
+ }
289
+ return client;
290
+ }
291
+ export {
292
+ createOpenApiClient
293
+ };
294
+
295
+ //# debugId=D3AB5DC616E1E63964756E2164756E21
296
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/client/create-openapi-client.ts", "../src/request/check-request.ts", "../src/request/to-request.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * `createOpenApiClient`: the operations `@nxgt/openapi-codegen` generates,\n * bound onto a client of `createHttpClient`. The client sends; the binding\n * adds what the spec knows: how each parameter and body is written, the\n * replies each operation declares, and checks by the server's own schemas.\n */\nimport {\n\ttype EventStream,\n\ttype HttpClient,\n\ttype Responses,\n\tValidationError,\n} from '@nxgt/httpyz';\nimport { METHODS } from '@nxgt/httpyz/integration';\nimport { checkRequest } from '../request/check-request';\nimport { type Input, toRequest } from '../request/to-request';\nimport type {\n\tOpenApiArgs,\n\tOpenApiClient,\n\tOpenApiOptions,\n\tOperationsShape,\n\tOperationTable,\n\tRoutesOf,\n\tRuntimeMedia,\n\tRuntimeOperation,\n\tStreamInit,\n} from './types';\n\n/** An operation's replies as the core client declares them. */\nconst toResponses = (operation: RuntimeOperation): Responses =>\n\tObject.fromEntries(\n\t\tObject.entries(operation.responses).map(([status, content]) => [\n\t\t\tstatus,\n\t\t\tObject.keys(content).length === 0\n\t\t\t\t? null\n\t\t\t\t: Object.fromEntries(\n\t\t\t\t\t\tObject.entries(content).map(([type, media]) => [\n\t\t\t\t\t\t\ttype,\n\t\t\t\t\t\t\tmedia.schema ?? null,\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t]),\n\t);\n\n/** The stream an operation replies with, and its media type: the first of its 2xx replies. */\nfunction streamOf(\n\toperation: RuntimeOperation,\n): [type: string, media: RuntimeMedia] | undefined {\n\tfor (const [status, content] of Object.entries(operation.responses)) {\n\t\tconst code = Number(status);\n\t\tif (code < 200 || code > 299) continue;\n\t\tfor (const [type, media] of Object.entries(content)) {\n\t\t\tif (media.kind === 'sse' || media.kind === 'jsonl') return [type, media];\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * `open()`'s stream, opened once `check` passes on the first read: a request\n * the spec refuses is never sent, and throws where the stream is read.\n */\nfunction checkedFirst<T>(\n\topen: () => EventStream<T>,\n\tcheck: () => Promise<void>,\n): EventStream<T> {\n\tlet inner: EventStream<T> | undefined;\n\tlet closed = false;\n\treturn {\n\t\tget lastEventId() {\n\t\t\treturn inner?.lastEventId;\n\t\t},\n\t\tclose() {\n\t\t\tclosed = true;\n\t\t\tinner?.close();\n\t\t},\n\t\tasync *[Symbol.asyncIterator]() {\n\t\t\tif (!inner) {\n\t\t\t\tawait check();\n\t\t\t\tif (closed) return;\n\t\t\t\tinner = open();\n\t\t\t}\n\t\t\tyield* inner;\n\t\t},\n\t};\n}\n\n/**\n * A client for a spec, bound to the `operations` table of the generated\n * `operations.ts`, which carries the spec's types: nothing else to import.\n *\n * ```ts\n * const http = createHttpClient({ baseUrl: 'https://api.example.com' });\n * const api = createOpenApiClient(http, operations);\n * const reply = await api.get('/employees/{id}', { param: { id } });\n * if (reply.status === 200) reply.data.name;\n * ```\n *\n * With `decode: true`, each reply is returned as its schema outputs it, and\n * typed so. The types may also be given: `createOpenApiClient<ClientOperations,\n * OperationsByRoute, true>`, whose `true` then requires `decode: true`.\n */\nexport function createOpenApiClient<\n\tOps extends OperationsShape<Ops>,\n\tRoutes = RoutesOf<Ops>,\n>(\n\thttp: HttpClient,\n\toperations: OperationTable<Ops>,\n\toptions: OpenApiOptions & { readonly decode: true },\n): OpenApiClient<Ops, Routes, true>;\nexport function createOpenApiClient<\n\tOps extends OperationsShape<Ops>,\n\tRoutes = RoutesOf<Ops>,\n\tDecoded extends boolean = false,\n>(\n\thttp: HttpClient,\n\toperations: OperationTable<Ops>,\n\t...[given]: OpenApiArgs<Decoded>\n): OpenApiClient<Ops, Routes, Decoded>;\nexport function createOpenApiClient<\n\tOps extends OperationsShape<Ops>,\n\tRoutes,\n\tDecoded extends boolean,\n>(\n\thttp: HttpClient,\n\toperations: OperationTable<Ops>,\n\tgiven?: OpenApiOptions & { readonly decode?: boolean },\n): OpenApiClient<Ops, Routes, Decoded> {\n\treturn bind(http, operations, given ?? {}, () => undefined);\n}\n\n/** Both signals: a call ends on either. */\nconst joined = (\n\tgiven: AbortSignal | null | undefined,\n\tscope: AbortSignal | undefined,\n): AbortSignal | null | undefined =>\n\tscope === undefined ? given : given ? AbortSignal.any([given, scope]) : scope;\n\n/**\n * The client, whose calls also end on `scope()`'s signal, read as each call\n * is made: a call checked before it is sent reaches the core client later,\n * after a `cancel()` of its group may already have swapped the signal.\n */\nfunction bind<\n\tOps extends OperationsShape<Ops>,\n\tRoutes,\n\tDecoded extends boolean,\n>(\n\thttp: HttpClient,\n\toperations: OperationTable<Ops>,\n\toptions: OpenApiOptions & { readonly decode?: boolean },\n\tscope: () => AbortSignal | undefined,\n): OpenApiClient<Ops, Routes, Decoded> {\n\tconst { validate = false } = options;\n\tconst decode = options.decode === true;\n\tconst checks = {\n\t\trequest:\n\t\t\tvalidate === true ||\n\t\t\t(typeof validate === 'object' && validate.request === true),\n\t\tresponse:\n\t\t\tdecode ||\n\t\t\tvalidate === true ||\n\t\t\t(typeof validate === 'object' && validate.response === true),\n\t};\n\tconst table = operations as unknown as {\n\t\treadonly [id: string]: RuntimeOperation;\n\t};\n\tconst byRoute = new Map<string, string>();\n\tconst responses = new Map<string, Responses>();\n\tfor (const [id, operation] of Object.entries(table)) {\n\t\tbyRoute.set(`${operation.method} ${operation.path}`, id);\n\t\tresponses.set(id, toResponses(operation));\n\t}\n\n\t/** The operation, and its arguments: the input, then the init. */\n\tconst read = (id: string, args: readonly unknown[]) => {\n\t\tconst operation = table[id];\n\t\tif (!operation) throw new Error(`${id} is not an operationId of the spec`);\n\t\t// An operation that takes nothing has no input: its first argument is the init.\n\t\tconst takes =\n\t\t\toperation.parameters.length > 0 ||\n\t\t\tObject.keys(operation.body?.content ?? {}).length > 0;\n\t\tconst input = (takes ? args[0] : undefined) as Input | undefined;\n\t\tconst given = ((takes ? args[1] : args[0]) ?? {}) as StreamInit;\n\t\tconst signal = joined(given.signal, scope());\n\t\tconst init = signal ? { ...given, signal } : given;\n\t\treturn { operation, input, init };\n\t};\n\n\t/** Throws what the server would refuse, before anything is sent. */\n\tconst check = async (\n\t\tid: string,\n\t\toperation: RuntimeOperation,\n\t\tinput: Input | undefined,\n\t): Promise<void> => {\n\t\tif (!checks.request) return;\n\t\tconst issues = await checkRequest(operation, input);\n\t\tif (issues.length === 0) return;\n\t\tconst context = {\n\t\t\toperationId: id,\n\t\t\tmethod: operation.method,\n\t\t\tpath: operation.path,\n\t\t};\n\t\tthrow new ValidationError(context, {\n\t\t\tkind: 'request',\n\t\t\t...context,\n\t\t\tissues,\n\t\t});\n\t};\n\n\t/** The core client's options for the input, written as the server reads it. */\n\tconst written = (\n\t\tid: string,\n\t\toperation: RuntimeOperation,\n\t\tinput: Input | undefined,\n\t\tinit: object & { headers?: HeadersInit },\n\t) => {\n\t\tconst headers = new Headers(init.headers);\n\t\tconst { query, body } = toRequest(operation, input, headers);\n\t\treturn {\n\t\t\t...init,\n\t\t\t...body,\n\t\t\tparam: input?.param,\n\t\t\tquery,\n\t\t\theaders,\n\t\t\toperationId: id,\n\t\t};\n\t};\n\n\tconst call = async (\n\t\tid: string,\n\t\targs: readonly unknown[],\n\t): Promise<unknown> => {\n\t\tconst { operation, input, init } = read(id, args);\n\t\tawait check(id, operation, input);\n\t\tconst request = http.request as (\n\t\t\tmethod: string,\n\t\t\tpath: string,\n\t\t\toptions: object,\n\t\t) => Promise<unknown>;\n\t\treturn request(operation.method, operation.path, {\n\t\t\t...written(id, operation, input, init),\n\t\t\tresponses: responses.get(id),\n\t\t\tvalidate: checks.response,\n\t\t\tdecode,\n\t\t});\n\t};\n\n\tconst stream = (id: string, args: readonly unknown[]) => {\n\t\tconst { operation, input, init } = read(id, args);\n\t\tconst found = streamOf(operation);\n\t\tif (!found) throw new Error(`${id} does not reply with a stream`);\n\t\tconst [type, media] = found;\n\t\tconst { reconnect, lastEventId, onUnknownEvent, ...rest } = init;\n\t\tconst open = () => {\n\t\t\tconst request = written(id, operation, input, rest);\n\t\t\t// The media type the spec declares, which may not be the core client's default.\n\t\t\tif (!request.headers.has('accept')) request.headers.set('accept', type);\n\t\t\tconst common = {\n\t\t\t\t...request,\n\t\t\t\tmethod: operation.method,\n\t\t\t\tvalidate: checks.response,\n\t\t\t\tdecode,\n\t\t\t};\n\t\t\tconst openStream = (media.kind === 'sse' ? http.events : http.lines) as (\n\t\t\t\tpath: string,\n\t\t\t\toptions: object,\n\t\t\t) => EventStream<unknown>;\n\t\t\treturn openStream(\n\t\t\t\toperation.path,\n\t\t\t\tmedia.kind === 'sse'\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...common,\n\t\t\t\t\t\t\tevents: media.events,\n\t\t\t\t\t\t\treconnect,\n\t\t\t\t\t\t\tlastEventId,\n\t\t\t\t\t\t\tonUnknownEvent,\n\t\t\t\t\t\t}\n\t\t\t\t\t: { ...common, item: media.item },\n\t\t\t);\n\t\t};\n\t\treturn checks.request\n\t\t\t? checkedFirst(open, () => check(id, operation, input))\n\t\t\t: open();\n\t};\n\n\tconst client: Record<string, unknown> = {\n\t\toperations,\n\t\top: (id: string, ...args: unknown[]) => call(id, args),\n\t\tstream: (id: string, ...args: unknown[]) => stream(id, args),\n\t\tgroup: () => {\n\t\t\tconst group = http.group();\n\t\t\tconst bound = bind<Ops, Routes, Decoded>(\n\t\t\t\tgroup,\n\t\t\t\toperations,\n\t\t\t\toptions,\n\t\t\t\t() => group.signal,\n\t\t\t);\n\t\t\treturn Object.defineProperties(bound, {\n\t\t\t\tcancel: { enumerable: true, value: group.cancel },\n\t\t\t\tsignal: { enumerable: true, get: () => group.signal },\n\t\t\t});\n\t\t},\n\t};\n\tfor (const method of METHODS) {\n\t\tclient[method] = (path: string, ...args: unknown[]) => {\n\t\t\tconst id = byRoute.get(`${method} ${path}`);\n\t\t\tif (id === undefined) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`The spec has no ${method.toUpperCase()} ${path} operation`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn call(id, args);\n\t\t};\n\t}\n\treturn client as unknown as OpenApiClient<Ops, Routes, Decoded>;\n}\n",
6
+ "/**\n * A request checked before it is sent, by the validators the server runs, on\n * what `@nxgt/openapi-hono` will read from it: each parameter as the\n * text it travels as, the JSON as it parses, a form as its fields. What the\n * server would refuse is refused here with the same issues, and nothing is\n * sent.\n */\nimport type { StandardSchemaV1, ValidationIssue } from '@nxgt/httpyz';\nimport { absent, check, fields, text } from '@nxgt/httpyz/integration';\nimport type { RuntimeMedia, RuntimeOperation } from '../client/types';\nimport type { Input } from './to-request';\n\nexport async function checkRequest(\n\toperation: RuntimeOperation,\n\tinput: Input | undefined,\n): Promise<ValidationIssue[]> {\n\tconst issues: ValidationIssue[] = [];\n\tconst run = async (\n\t\ttarget: ValidationIssue['target'],\n\t\tschema: StandardSchemaV1 | undefined,\n\t\tvalue: unknown,\n\t): Promise<void> => {\n\t\tif (!schema) return;\n\t\tconst result = await check(schema, value, target);\n\t\tif (!result.ok) issues.push(...result.issues);\n\t};\n\tconst { param, query, header } = readParameters(operation, input);\n\t// In the server's order, so the issues come out in the same one.\n\tawait run('param', operation.param, param);\n\tawait run('query', operation.query, query);\n\tawait run('header', operation.header, header);\n\tconst content = Object.values(operation.body?.content ?? {});\n\tconst schemaOf = (kind: RuntimeMedia['kind']) =>\n\t\tcontent.find((media) => media.kind === kind)?.schema;\n\tif (input?.json !== undefined) {\n\t\tconst sent = JSON.stringify(input.json);\n\t\tawait run(\n\t\t\t'json',\n\t\t\tschemaOf('json'),\n\t\t\tsent === undefined ? undefined : JSON.parse(sent),\n\t\t);\n\t} else if (input?.form !== undefined) {\n\t\tawait run('form', schemaOf('form'), formFields(input.form));\n\t} else if (input?.text !== undefined) {\n\t\tawait run('body', schemaOf('text'), input.text);\n\t}\n\treturn issues;\n}\n\n/** The parameters as the engine reads them: strings, a list as every value. */\nfunction readParameters(\n\toperation: RuntimeOperation,\n\tinput: Input | undefined,\n): Record<'param' | 'query' | 'header', Record<string, unknown>> {\n\tconst read = { param: {}, query: {}, header: {} } as Record<\n\t\t'param' | 'query' | 'header',\n\t\tRecord<string, unknown>\n\t>;\n\tfor (const parameter of operation.parameters) {\n\t\tconst { name, list } = parameter;\n\t\tif (parameter.in === 'path') {\n\t\t\tconst value = input?.param?.[name];\n\t\t\tif (!absent(value)) read.param[name] = text(value);\n\t\t\tcontinue;\n\t\t}\n\t\tconst value =\n\t\t\tparameter.in === 'query'\n\t\t\t\t? input?.query?.[name]\n\t\t\t\t: input?.header?.[name.toLowerCase()];\n\t\tif (absent(value)) continue;\n\t\tconst values = Array.isArray(value) ? value.map(text) : [text(value)];\n\t\tif (parameter.in === 'header') {\n\t\t\t// Headers trims what it holds.\n\t\t\tconst sent = values.join(',').trim();\n\t\t\tread.header[name.toLowerCase()] = list\n\t\t\t\t? sent.split(',').map((item) => item.trim())\n\t\t\t\t: sent;\n\t\t} else if (list) {\n\t\t\tread.query[name] = parameter.explode\n\t\t\t\t? values\n\t\t\t\t: values.join(',').split(',');\n\t\t} else read.query[name] = values.join(',');\n\t}\n\treturn read;\n}\n\n/**\n * A form as Hono's `parseBody({ all: true })` reads it: a field sent once is\n * its value, one sent again or named `x[]` a list, and a `Blob` a `File`.\n */\nfunction formFields(\n\tform: Readonly<Record<string, unknown>>,\n): Record<string, unknown> {\n\tconst read: Record<string, unknown> = {};\n\tfor (const [name, value] of fields(form)) {\n\t\tconst item =\n\t\t\tvalue instanceof Blob && !(value instanceof File)\n\t\t\t\t? new File([value], 'blob', { type: value.type })\n\t\t\t\t: value;\n\t\tconst held = read[name];\n\t\tread[name] =\n\t\t\theld === undefined\n\t\t\t\t? name.endsWith('[]')\n\t\t\t\t\t? [item]\n\t\t\t\t\t: item\n\t\t\t\t: Array.isArray(held)\n\t\t\t\t\t? [...held, item]\n\t\t\t\t\t: [held, item];\n\t}\n\treturn read;\n}\n",
7
+ "/**\n * An operation's input as a call of the core client, written the way\n * `@nxgt/openapi-hono` reads it back: a query list as a repeated key,\n * or joined with commas when the spec says `explode: false`; a header list\n * joined with commas; the body as the media type the spec declares for its\n * kind.\n */\nimport type { BodyInput } from '@nxgt/httpyz';\nimport {\n\tabsent,\n\ttext,\n\ttoFormData,\n\ttoSearchParams,\n} from '@nxgt/httpyz/integration';\nimport type {\n\tRuntimeMedia,\n\tRuntimeOperation,\n\tRuntimeParameter,\n} from '../client/types';\n\n/** What an input may hold, as `ClientOperations` types it. */\nexport interface Input {\n\treadonly param?: Readonly<Record<string, unknown>>;\n\treadonly query?: Readonly<Record<string, unknown>>;\n\t/** Keyed by lowercased name. */\n\treadonly header?: Readonly<Record<string, unknown>>;\n\treadonly json?: unknown;\n\treadonly form?: Readonly<Record<string, unknown>>;\n\treadonly text?: string;\n\treadonly body?: Blob | ArrayBuffer | Uint8Array;\n}\n\n/** What to send as `Content-Type` for a declared media type, which may be a range. */\nconst concrete = (type: string | undefined, fallback: string): string =>\n\ttype === undefined || type.includes('*') ? fallback : type;\n\n/** The query and the body; the header parameters and the body's type go onto `headers`. */\nexport function toRequest(\n\toperation: RuntimeOperation,\n\tinput: Input | undefined,\n\theaders: Headers,\n): { query: URLSearchParams; body: BodyInput } {\n\tconst query = new URLSearchParams();\n\tfor (const param of operation.parameters) {\n\t\tif (param.in === 'query') {\n\t\t\tappendQuery(query, param, input?.query?.[param.name]);\n\t\t} else if (param.in === 'header') {\n\t\t\tconst value = input?.header?.[param.name.toLowerCase()];\n\t\t\tif (absent(value)) continue;\n\t\t\theaders.set(\n\t\t\t\tparam.name,\n\t\t\t\tArray.isArray(value) ? value.map(text).join(',') : text(value),\n\t\t\t);\n\t\t}\n\t}\n\treturn { query, body: toBody(operation, input, headers) };\n}\n\nfunction appendQuery(\n\tsearch: URLSearchParams,\n\tparam: RuntimeParameter,\n\tvalue: unknown,\n): void {\n\tif (absent(value)) return;\n\tif (!Array.isArray(value)) search.append(param.name, text(value));\n\telse if (param.explode) {\n\t\tfor (const item of value) search.append(param.name, text(item));\n\t} else search.append(param.name, value.map(text).join(','));\n}\n\nfunction toBody(\n\toperation: RuntimeOperation,\n\tinput: Input | undefined,\n\theaders: Headers,\n): BodyInput {\n\tif (input === undefined) return {};\n\tconst content = Object.entries(operation.body?.content ?? {});\n\tconst declared = (kind: RuntimeMedia['kind']): string | undefined =>\n\t\tcontent.find(([, media]) => media.kind === kind)?.[0];\n\tif (input.json !== undefined) {\n\t\theaders.set('content-type', concrete(declared('json'), 'application/json'));\n\t\treturn { json: input.json };\n\t}\n\tif (input.form !== undefined) {\n\t\treturn {\n\t\t\tform:\n\t\t\t\tdeclared('form') === 'application/x-www-form-urlencoded'\n\t\t\t\t\t? toSearchParams(input.form)\n\t\t\t\t\t: toFormData(input.form),\n\t\t};\n\t}\n\tif (input.text !== undefined) {\n\t\theaders.set('content-type', concrete(declared('text'), 'text/plain'));\n\t\treturn { text: input.text };\n\t}\n\tif (input.body !== undefined) {\n\t\theaders.set(\n\t\t\t'content-type',\n\t\t\tconcrete(declared('binary'), 'application/octet-stream'),\n\t\t);\n\t\treturn { body: input.body };\n\t}\n\treturn {};\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "D3AB5DC616E1E63964756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A request checked before it is sent, by the validators the server runs, on
3
+ * what `@nxgt/openapi-hono` will read from it: each parameter as the
4
+ * text it travels as, the JSON as it parses, a form as its fields. What the
5
+ * server would refuse is refused here with the same issues, and nothing is
6
+ * sent.
7
+ */
8
+ import type { ValidationIssue } from '@nxgt/httpyz';
9
+ import type { RuntimeOperation } from '../client/types';
10
+ import type { Input } from './to-request';
11
+ export declare function checkRequest(operation: RuntimeOperation, input: Input | undefined): Promise<ValidationIssue[]>;
12
+ //# sourceMappingURL=check-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-request.d.ts","sourceRoot":"","sources":["../../src/request/check-request.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,cAAc,CAAC;AAEtE,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,wBAAsB,YAAY,CACjC,SAAS,EAAE,gBAAgB,EAC3B,KAAK,EAAE,KAAK,GAAG,SAAS,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC,CAgC5B"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * An operation's input as a call of the core client, written the way
3
+ * `@nxgt/openapi-hono` reads it back: a query list as a repeated key,
4
+ * or joined with commas when the spec says `explode: false`; a header list
5
+ * joined with commas; the body as the media type the spec declares for its
6
+ * kind.
7
+ */
8
+ import type { BodyInput } from '@nxgt/httpyz';
9
+ import type { RuntimeOperation } from '../client/types';
10
+ /** What an input may hold, as `ClientOperations` types it. */
11
+ export interface Input {
12
+ readonly param?: Readonly<Record<string, unknown>>;
13
+ readonly query?: Readonly<Record<string, unknown>>;
14
+ /** Keyed by lowercased name. */
15
+ readonly header?: Readonly<Record<string, unknown>>;
16
+ readonly json?: unknown;
17
+ readonly form?: Readonly<Record<string, unknown>>;
18
+ readonly text?: string;
19
+ readonly body?: Blob | ArrayBuffer | Uint8Array;
20
+ }
21
+ /** The query and the body; the header parameters and the body's type go onto `headers`. */
22
+ export declare function toRequest(operation: RuntimeOperation, input: Input | undefined, headers: Headers): {
23
+ query: URLSearchParams;
24
+ body: BodyInput;
25
+ };
26
+ //# sourceMappingURL=to-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-request.d.ts","sourceRoot":"","sources":["../../src/request/to-request.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAO9C,OAAO,KAAK,EAEX,gBAAgB,EAEhB,MAAM,iBAAiB,CAAC;AAEzB,8DAA8D;AAC9D,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,gCAAgC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,CAAC;CAChD;AAMD,2FAA2F;AAC3F,wBAAgB,SAAS,CACxB,SAAS,EAAE,gBAAgB,EAC3B,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,OAAO,EAAE,OAAO,GACd;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAe7C"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@nxgt/openapi-httpyz",
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
+ "./package.json": "./package.json"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/softistx/nxgt-http.git",
24
+ "directory": "packages/openapi-httpyz"
25
+ },
26
+ "publishConfig": {
27
+ "registry": "https://registry.npmjs.org",
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "build": "bun run ../../build.ts",
32
+ "fixtures": "bun run test/generate.ts",
33
+ "test": "bun run fixtures && bun test src",
34
+ "typecheck": "bun run fixtures && tsc --noEmit"
35
+ },
36
+ "devDependencies": {
37
+ "@nxgt/httpyz": "^0.0.0",
38
+ "@nxgt/openapi-codegen": "^0.1.0",
39
+ "@nxgt/openapi-hono": "^0.0.0",
40
+ "@types/bun": "^1.4.0",
41
+ "hono": "^4.13.4",
42
+ "zod": "^4.5.4"
43
+ },
44
+ "peerDependencies": {
45
+ "@nxgt/httpyz": "^0.0.0",
46
+ "typescript": "^6.0.3"
47
+ }
48
+ }