@shipworthy/openapi-tanstack-query 0.0.1 → 1.0.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 CHANGED
@@ -4,8 +4,8 @@ Typed [TanStack Query](https://tanstack.com/query) options, keys, and mutations
4
4
  for every operation in an OpenAPI 3.x document. Use it in a React (or React
5
5
  Native) app that already uses `@tanstack/react-query`. The package is a thin
6
6
  adapter over [`@shipworthy/openapi-fetch`](../openapi-fetch). Types come from
7
- `@shipworthy/openapi-typescript`. Code generation only writes a small `client.ts`
8
- that maps each `operationId` to `[METHOD, path]`. You then pass the returned
7
+ `@shipworthy/openapi-typescript`. Code generation writes a `client.ts` with
8
+ operation routes and, when requested, schema-based field mappings. You then pass the returned
9
9
  options to TanStack's own hooks (`useQuery`, `useMutation`, `useInfiniteQuery`,
10
10
  `useSuspenseQuery`, `queryClient.fetchQuery`, and so on). The package has no
11
11
  hooks or React provider of its own.
@@ -31,34 +31,33 @@ hooks or React provider of its own.
31
31
  bun add @shipworthy/openapi-tanstack-query @tanstack/react-query react
32
32
  ```
33
33
 
34
- Add the code generators as dev dependencies. `effect`,
35
- `@shipworthy/openapi-generator`, and `@shipworthy/openapi-transformer` are
36
- optional peer dependencies that only the `./generator` entry point needs. The
37
- CLI installs the last two. The TypeScript generator bundles TypeScript 7's AST
38
- modules, so your project can keep its own TypeScript version:
34
+ Add the code generators as dev dependencies. `@shipworthy/openapi-typescript` supplies
35
+ the schema-based case mapping used by `./generator` and generates `types.ts`.
36
+ The TypeScript generator bundles TypeScript 7's AST modules, so your
37
+ project can keep its own TypeScript version:
39
38
 
40
39
  ```sh
41
- bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript effect@^4
40
+ bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
42
41
  ```
43
42
 
44
- | Requirement | Value |
45
- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
- | Peer dependencies | `@tanstack/react-query` `^5.59.0`, `react` `^18.0.0 \|\| ^19.0.0` |
47
- | Optional peers | `effect` `^4.0.0`, `@shipworthy/openapi-generator`, and `@shipworthy/openapi-transformer`, needed only for `./generator` (the CLI and programmatic generation) |
48
- | Runtime dependencies | `@shipworthy/openapi-fetch` (installed automatically) |
49
- | Module format | `.` ships ESM and CommonJS, each with matching declarations (`.d.ts` and `.d.cts`). `./generator` is ESM only. |
50
- | Entry points | `.` (runtime) and `./generator` (generator plugin), compiled JavaScript with declarations in `dist/`. A `react-native` field points at `./dist/index.js`. |
51
- | Runtime | Any environment with `fetch`, `Request`, and `Response`, or pass your own `fetch`. No TypeScript transpilation of `node_modules` is needed. |
52
- | TypeScript | Declarations type-check with TypeScript `5.0` and later, using `NodeNext` (CommonJS or ESM) or `bundler` module resolution. |
53
- | CLI runtime | The `openapi` binary from `@shipworthy/openapi-cli` runs on **Node.js** `>=22.19.0` or **Bun** (`bunx --bun @shipworthy/openapi-cli`). |
54
- | OpenAPI versions | 3.0.x and 3.1.x. 3.0 documents are upgraded to 3.1 internally. Swagger 2.x is rejected. |
43
+ | Requirement | Value |
44
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | Peer dependencies | `@tanstack/react-query` `^5.59.0`, `react` `^18.0.0 \|\| ^19.0.0` |
46
+ | Optional peers | `@shipworthy/openapi-typescript`, `@shipworthy/openapi-generator`, and `@shipworthy/openapi-transformer`, needed only for `./generator` (the CLI and programmatic generation) |
47
+ | Runtime dependencies | `@shipworthy/openapi-fetch` and `effect` (installed automatically; only `./generator` imports `effect`) |
48
+ | Module format | `.` ships ESM and CommonJS, each with matching declarations (`.d.ts` and `.d.cts`). `./generator` is ESM only. |
49
+ | Entry points | `.` (runtime) and `./generator` (generator plugin), compiled JavaScript with declarations in `dist/`. A `react-native` field points at `./dist/index.js`. |
50
+ | Runtime | Any environment with `fetch`, `Request`, and `Response`, or pass your own `fetch`. No TypeScript transpilation of `node_modules` is needed. |
51
+ | TypeScript | Declarations type-check with TypeScript `5.0` and later, using `NodeNext` (CommonJS or ESM) or `bundler` module resolution. |
52
+ | CLI runtime | The `openapi` binary from `@shipworthy/openapi-cli` runs on **Node.js** `>=22.19.0` or **Bun** (`bunx --bun @shipworthy/openapi-cli`). |
53
+ | OpenAPI versions | 3.0.x and 3.1.x. 3.0 documents are upgraded to 3.1 internally. Swagger 2.x is rejected. |
55
54
 
56
55
  The runtime entry point declares no Node.js `engines` range. The CLI runtime requirement applies to code generation.
57
56
 
58
57
  `@shipworthy/openapi-cli` loads the generator with
59
58
  `import("@shipworthy/openapi-tanstack-query/generator")`. It loads
60
- `@shipworthy/openapi-typescript` for `types.ts`. Both packages, plus `effect`
61
- and `@shipworthy/openapi-generator`, must be resolvable from the directory where
59
+ `@shipworthy/openapi-typescript` for `types.ts`. Both packages, plus
60
+ `@shipworthy/openapi-generator`, must be resolvable from the directory where
62
61
  you run the CLI.
63
62
 
64
63
  ## Quick start
@@ -67,13 +66,14 @@ you run the CLI.
67
66
  and the CLI resolves `$ref`s:
68
67
 
69
68
  ```sh
70
- bunx --bun @shipworthy/openapi-cli generate tanstack-query ./openapi.json -o src/generated --name hotels
69
+ bunx --bun @shipworthy/openapi-cli generate tanstack-query -i ./openapi.json -o src/generated/hotels
71
70
  # Or generate from a URL:
72
- bunx --bun @shipworthy/openapi-cli generate tanstack-query https://api.example.com/openapi.json -o src/generated --name hotels
71
+ bunx --bun @shipworthy/openapi-cli generate tanstack-query -i https://api.example.com/openapi.json -o src/generated/hotels
73
72
  ```
74
73
 
75
74
  This writes `src/generated/hotels/types.ts` (via `@shipworthy/openapi-typescript`)
76
- and `src/generated/hotels/client.ts`.
75
+ and `src/generated/hotels/client.ts`. The client name `hotels` comes from the
76
+ output directory's name; pass `--name` to choose another.
77
77
 
78
78
  **2. Create the client and query utils once**:
79
79
 
@@ -111,15 +111,49 @@ function Hotels() {
111
111
  Inputs, response data, and error types are all inferred from the OpenAPI
112
112
  document. You do not write type arguments.
113
113
 
114
+ ### camelCase app fields
115
+
116
+ Add `--camel-case` when the API uses snake_case fields and the app should use camelCase:
117
+
118
+ ```sh
119
+ openapi generate tanstack-query -i ./openapi.json -o src/generated/hotels --camel-case
120
+ ```
121
+
122
+ With this flag, `types.ts` uses names such as `minStars` and `hotelId`. The generated
123
+ `createHotelsApiClient` converts typed path, query, header, and cookie parameters and JSON
124
+ request bodies to the names declared in OpenAPI. It converts JSON success and error bodies
125
+ back to camelCase before returning data or placing it in the TanStack Query cache:
126
+
127
+ ```ts
128
+ const hotels = useQuery(hotelsApi.getHotels.queryOptions({ input: { query: { minStars: 5 } } }));
129
+ // The request sends ?min_stars=5. A response field hotel_id is exposed as hotelId.
130
+ ```
131
+
132
+ Direct calls through the generated client use the same conversion. Dictionary keys declared by
133
+ `additionalProperties` or `patternProperties` keep their spelling, while declared fields inside
134
+ their values are converted. Operation IDs, URLs, enum values, and the raw `Response` retain their
135
+ API spelling. Generation fails when distinct wire fields would share a camelCase name within
136
+ an object, including nested fields combined by `allOf`, `anyOf`, or `oneOf`. Conversion applies
137
+ to `application/json` and `+json` bodies.
138
+
139
+ Use `--wire-types` with `--camel-case` to write `wire-types.ts` alongside `types.ts`. The wire file
140
+ has the API's field names and the same other TypeScript generation settings. For example, import
141
+ `components` from `./generated/hotels/wire-types` to type a raw `Response` fixture or a custom `fetch`
142
+ wrapper. `types.ts` remains the app-facing camelCase contract.
143
+
144
+ JSON Pointer strings such as `errors[].pointer: "/slot_id"` keep their API spelling. Undeclared
145
+ fields also keep their API spelling. When a response status has no matching schema and no
146
+ `default` response in the OpenAPI document, its error body is returned without conversion.
147
+
114
148
  ## Generated output
115
149
 
116
- `openapi generate tanstack-query <input> -o <dir> --name <name>` writes the
117
- following into `<dir>/<name>/`:
150
+ `openapi generate tanstack-query -i <input> -o <dir>` writes the following
151
+ into `<dir>/`. Every file is regenerated on every successful run:
118
152
 
119
153
  - `types.ts`: `paths`, `components`, `operations`, and `webhooks` types from
120
- `@shipworthy/openapi-typescript`. This file is regenerated only when it is
121
- missing or stale (see [`--force`](#cli-openapi-generate-tanstack-query)).
122
- - `client.ts`: overwritten on every successful run. For `--name my-pets`
154
+ `@shipworthy/openapi-typescript`.
155
+ - `wire-types.ts`: optional API-facing types when both `--camel-case` and `--wire-types` are set.
156
+ - `client.ts`: for `-o src/generated/my-pets` or `--name my-pets`
123
157
  (prefix `MyPets`), it contains:
124
158
 
125
159
  ```ts
@@ -163,47 +197,56 @@ export function createMyPetsApiQueryUtils(
163
197
 
164
198
  Naming rules:
165
199
 
166
- - `<Prefix>` is the PascalCase form of `--name`. `-` and `_` separators are
200
+ - The client name is `--name`, or the basename of the output directory when
201
+ `--name` is omitted.
202
+ - `<Prefix>` is the PascalCase form of the client name. `-` and `_` separators are
167
203
  removed and the next character is uppercased: `hotels` becomes `Hotels`, and
168
204
  `my-pets` becomes `MyPets`.
169
205
  - The generated exports are `create<Prefix>ApiClient`, `<Prefix>ApiClient`
170
206
  (type), `create<Prefix>ApiQueryUtils`, `<Prefix>ApiPaths`,
171
207
  `<Prefix>ApiComponents`, and `<Prefix>ApiOperations`.
172
- - The default cache-key prefix is the raw `--name` value, such as `"my-pets"`.
208
+ - The default cache-key prefix is the raw client name, such as `"my-pets"`.
173
209
  - Each operation is available on the query utils object by its `operationId`,
174
210
  for example `hotelsApi.getHotel`.
175
211
 
176
212
  ## CLI: `openapi generate tanstack-query`
177
213
 
178
- The command is provided by `@shipworthy/openapi-cli`.
214
+ The command is provided by `@shipworthy/openapi-cli`. The `tanstack-query` plugin writes
215
+ `client.ts` and the `types.ts` it imports into the output directory:
179
216
 
180
217
  ```
181
- openapi generate tanstack-query [flags] <input>
218
+ openapi generate tanstack-query [-i <input>] -o <dir> [--name <name>] [--camel-case] [--wire-types] [--check] [TypeScript flags]
182
219
  ```
183
220
 
184
- | Argument / flag | Type | Default | Description |
185
- | ---------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
186
- | `<input>` | string | none | OpenAPI file path, or an `http:`, `https:`, or `file:` URL. Glob patterns (`*`) are rejected. |
187
- | `--output`, `-o` | string | none | Directory that will contain `<name>/client.ts`. |
188
- | `--name` | string | none | Client name. It sets the output subdirectory, the PascalCase export prefix, and the default cache-key prefix. It must match `^[a-zA-Z][a-zA-Z0-9]*(?:[-_][a-zA-Z0-9]+)*$`. |
189
- | `--force` | boolean | `false` | Regenerate `types.ts` even when its stamp matches. |
221
+ | Flag | Type | Default | Description |
222
+ | ---------------- | ------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
223
+ | `--input`, `-i` | string | stdin | OpenAPI file path, or an `http:`, `https:`, or `file:` URL. Glob patterns (`*`) are rejected. |
224
+ | `--output`, `-o` | string | required | Directory that receives `client.ts` and `types.ts`. |
225
+ | `--name` | string | output directory's basename | Client name. It sets the PascalCase export prefix and the default cache-key prefix. It must match `^[a-zA-Z][a-zA-Z0-9]*(?:[-_][a-zA-Z0-9]+)*$`. |
226
+ | `--camel-case` | boolean | `false` | Use camelCase in `types.ts` and convert declared parameter and JSON body fields in the generated client. |
227
+ | `--wire-types` | boolean | `false` | Also write `wire-types.ts` with API field names. Requires `--camel-case`. |
228
+ | `--check` | boolean | `false` | Write nothing; exit 1 when a generated file differs from the file in the output directory or is missing. |
229
+
230
+ When `--name` is omitted and the output directory's basename is not a valid name,
231
+ the command fails with `Cannot derive a client name from the output directory "<basename>". Pass --name.`
190
232
 
191
- `types.ts` is regenerated when it is missing, or when the
192
- `@shipworthy/openapi-typescript@<version>:<sha256>` stamp in its header no
193
- longer matches. The hash covers the schema contents, not the schema's location,
194
- so moving the file does not trigger a rebuild.
233
+ The `tanstack-query` plugin can be combined with other plugins in one run, for example
234
+ `openapi generate tanstack-query zod -i openapi.json -o src/generated/hotels`, which also
235
+ writes `zod-schema.ts` against the same `types.ts`. See the
236
+ [CLI reference](../openapi-cli/README.md#openapi-generate-plugins) for every flag, including
237
+ the TypeScript generation flags.
195
238
 
196
239
  Example `package.json` script:
197
240
 
198
241
  ```json
199
242
  {
200
243
  "scripts": {
201
- "generate": "openapi generate tanstack-query openapi.json -o src/generated --name hotels"
244
+ "generate": "openapi generate tanstack-query -i openapi.json -o src/generated/hotels"
202
245
  }
203
246
  }
204
247
  ```
205
248
 
206
- Generation fails with exit code 1, and `client.ts` is left untouched, when:
249
+ Generation fails with exit code 1, and no file is written, when:
207
250
 
208
251
  | Error message | Cause |
209
252
  | -------------------------------------- | -------------------------------------------------------- |
@@ -211,7 +254,7 @@ Generation fails with exit code 1, and `client.ts` is left untouched, when:
211
254
  | `Duplicate operationId: <id>` | Two operations share an `operationId`. |
212
255
  | `Unsupported operationId: <id>` | The id is `__proto__`, `constructor`, or `prototype`. |
213
256
  | `Unsupported Swagger version: 2.x. …` | The input is Swagger 2.x. Only OpenAPI 3.x is supported. |
214
- | `Globbing is unsupported.` | `<input>` contains `*`. |
257
+ | `Globbing is unsupported.` | The input contains `*`. |
215
258
 
216
259
  ## Runtime API (`@shipworthy/openapi-tanstack-query`)
217
260
 
@@ -234,6 +277,11 @@ type ApiClient<Paths extends {} = {}> = Client<Paths>; // @shipworthy/openapi-fe
234
277
 
235
278
  This function creates an `@shipworthy/openapi-fetch` client.
236
279
 
280
+ With `--camel-case`, the generated factory passes `caseMapping` to
281
+ `createApiClient<Paths>(options)`. The underlying fetch client installs the casing middleware.
282
+ `@shipworthy/openapi-typescript` builds the mapping from the OpenAPI schema. Use the generated
283
+ factory so runtime values match `types.ts`.
284
+
237
285
  - **`baseUrl`** (required): the root URL for all requests. A trailing slash is
238
286
  removed.
239
287
  - **`authToken`** (optional): a token string, or a sync or async getter.
@@ -248,6 +296,8 @@ This function creates an `@shipworthy/openapi-fetch` client.
248
296
  through, including `fetch`, `Request`, `headers`, `querySerializer`,
249
297
  `bodySerializer`, `pathSerializer`, `requestInitExt`, and standard
250
298
  `RequestInit` fields such as `credentials`.
299
+ - **`caseMapping`** (optional): a generated `CaseMapping`. When provided,
300
+ the underlying fetch client installs the casing middleware.
251
301
 
252
302
  The returned client is a full `openapi-fetch` client. You can call it directly
253
303
  (`client.GET("/hotels", { params: { query } })`) or add middleware with
@@ -486,17 +536,17 @@ import {
486
536
  } from "@shipworthy/openapi-tanstack-query/generator";
487
537
  ```
488
538
 
489
- | Export | Signature / description |
490
- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
491
- | `tanstackQuery` | `Generator<string, TanstackGenerationError>` (from `@shipworthy/openapi-generator`) with `id: "tanstack-query"`. `generate(model, options)` returns `Effect<string, TanstackGenerationError>`, which is the `client.ts` source with no header comment. Options: `prefix?: string` (export prefix, default `""`) and `name?: string` (cache-key prefix, default `prefix`). Other option shapes fail with `TanstackGenerationError`. |
492
- | `operationRoutes(model)` | `Effect<Record<string, OperationRoute>, TanstackGenerationError>`. Maps each `operationId` to `[METHOD, path]`. Fails on a missing, duplicate, or reserved (`__proto__`, `constructor`, `prototype`) `operationId`. |
493
- | `clientSource(prefix, name, routes)` | `string`. Prints `client.ts` source for the given export prefix, cache-key prefix, and routes. |
494
- | `TanstackGenerationError` | Effect `Schema.TaggedError` with `_tag: "TanstackGenerationError"` and `message: string`. |
539
+ | Export | Signature / description |
540
+ | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
541
+ | `tanstackQuery` | `Generator<string, TanstackGenerationError>` (from `@shipworthy/openapi-generator`) with `id: "tanstack-query"`. `generate(model, options)` returns `Effect<string, TanstackGenerationError>`, which is the `client.ts` source with no header comment. Options: `prefix?: string` (export prefix, default `""`), `name?: string` (cache-key prefix, default `prefix`), and `camelCase?: boolean` (default `false`). Other option shapes fail with `TanstackGenerationError`. |
542
+ | `operationRoutes(model)` | `Effect<Record<string, OperationRoute>, TanstackGenerationError>`. Maps each `operationId` to `[METHOD, path]`. Fails on a missing, duplicate, or reserved (`__proto__`, `constructor`, `prototype`) `operationId`. |
543
+ | `clientSource(prefix, name, routes)` | `string`. Prints `client.ts` source for the given export prefix, cache-key prefix, and routes. |
544
+ | `TanstackGenerationError` | Effect `Schema.TaggedError` with `_tag: "TanstackGenerationError"` and `message: string`. |
495
545
 
496
546
  Programmatic generation with `@shipworthy/openapi-generator`, which is what
497
547
  the CLI does:
498
548
 
499
- Install `@effect/platform-node@^4` as a dev dependency for the Node.js platform layer used in this example:
549
+ Install `@effect/platform-node@4.1.4` as a dev dependency for the Node.js platform layer used in this example:
500
550
 
501
551
  ```ts
502
552
  import { NodeServices } from "@effect/platform-node";
@@ -542,29 +592,31 @@ Generated files use extensionless `./types` imports and expect TypeScript `modul
542
592
 
543
593
  ## Troubleshooting
544
594
 
545
- | Symptom | Fix |
546
- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
547
- | `<METHOD> <path>: Missing operationId` / `Duplicate operationId: …` | Add unique `operationId`s to the OpenAPI document. |
548
- | `--name must start with a letter …` | Use letters and digits, with single `-` or `_` separators, such as `hotels` or `sdk-core`. |
549
- | `types.ts` was not regenerated after editing generator settings | It is regenerated only when the schema hash or generator version changes. Pass `--force`. |
550
- | CLI cannot load `@shipworthy/openapi-tanstack-query/generator` | Install this package, `@shipworthy/openapi-typescript`, `@shipworthy/openapi-generator`, and `effect` in the project. |
551
- | `openapi` fails to start on an older Node.js | The CLI needs Node.js `>=22.19.0`. Upgrade Node.js, or run it with `bunx --bun @shipworthy/openapi-cli`. |
552
- | `error instanceof Error` is false in `onError` | This is expected for HTTP errors: the error is the parsed error body. See [errors](#requests-responses-and-errors). |
553
- | Two APIs with the same `--name` overwrite each other's cache entries | Pass a different `prefix` to `create<Prefix>ApiQueryUtils(client, { prefix })`. |
595
+ | Symptom | Fix |
596
+ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
597
+ | `<METHOD> <path>: Missing operationId` / `Duplicate operationId: …` | Add unique `operationId`s to the OpenAPI document. |
598
+ | `Invalid value for flag --name: "…". Expected: Schema validation failed: --name must start with a letter …` | Use letters and digits, with single `-` or `_` separators, such as `hotels` or `sdk-core`. |
599
+ | `Cannot derive a client name from the output directory "…". Pass --name.` | Rename the output directory to a valid name, or pass `--name`. |
600
+ | CLI cannot load `@shipworthy/openapi-tanstack-query/generator` | Install this package, `@shipworthy/openapi-typescript`, and `@shipworthy/openapi-generator` in the project. |
601
+ | `openapi` fails to start on an older Node.js | The CLI needs Node.js `>=22.19.0`. Upgrade Node.js, or run it with `bunx --bun @shipworthy/openapi-cli`. |
602
+ | `error instanceof Error` is false in `onError` | This is expected for HTTP errors: the error is the parsed error body. See [errors](#requests-responses-and-errors). |
603
+ | Two APIs with the same client name overwrite each other's cache entries | Pass a different `prefix` to `create<Prefix>ApiQueryUtils(client, { prefix })`. |
554
604
 
555
605
  ## Key facts (for AI agents)
556
606
 
557
607
  - **Install:** `@shipworthy/openapi-tanstack-query`, plus peers
558
608
  `@tanstack/react-query@^5.59.0` and `react@^18 || ^19`. The runtime entry
559
609
  ships ESM and CommonJS with `.d.ts`/`.d.cts` declarations for TypeScript 5.0+.
560
- For `./generator`, also install the optional peers `effect@^4`,
561
- `@shipworthy/openapi-generator`, and `@shipworthy/openapi-transformer`.
610
+ For `./generator`, also install the optional peers
611
+ `@shipworthy/openapi-generator` and `@shipworthy/openapi-transformer`.
562
612
  - **Generate:** run
563
- `openapi generate tanstack-query <spec> -o <dir> --name <name>` from
613
+ `openapi generate tanstack-query -i <spec> -o <dir> [--name <name>]` from
564
614
  `@shipworthy/openapi-cli` (`bunx --bun @shipworthy/openapi-cli`). The output is
565
- `<dir>/<name>/types.ts` and `<dir>/<name>/client.ts`. Do not edit generated
615
+ `<dir>/types.ts` and `<dir>/client.ts`, regenerated on every run. `name`
616
+ defaults to the basename of `<dir>`. Add other plugins to the same run, e.g.
617
+ `openapi generate tanstack-query zod -i <spec> -o <dir>`. Do not edit generated
566
618
  files.
567
- - **Generated exports** (Prefix is the PascalCase of `name`):
619
+ - **Generated exports** (Prefix is the PascalCase of the client name):
568
620
  `create<Prefix>ApiClient`, `create<Prefix>ApiQueryUtils`,
569
621
  `<Prefix>ApiClient`, `<Prefix>ApiPaths`, `<Prefix>ApiComponents`, and
570
622
  `<Prefix>ApiOperations`.
@@ -3,17 +3,14 @@ import { Generator } from "@shipworthy/openapi-generator";
3
3
  import { ParsedService } from "@shipworthy/openapi-transformer";
4
4
  import { HttpMethod } from "@shipworthy/openapi-fetch";
5
5
  import "@tanstack/react-query";
6
- //#region src/errors.d.ts
7
6
  declare const TanstackGenerationError_base: Schema.Class<TanstackGenerationError, Schema.TaggedStruct<"TanstackGenerationError", {
8
7
  readonly message: Schema.String;
9
- }>, import("effect/Cause").YieldableError>;
8
+ }>, import("effect/Cause").YieldableError & {
9
+ readonly brand: unique symbol;
10
+ }>;
10
11
  export declare class TanstackGenerationError extends TanstackGenerationError_base {}
11
- //#endregion
12
- //#region src/query-utils.d.ts
13
12
  type OperationRoute = readonly [Uppercase<HttpMethod>, string];
14
- //#endregion
15
- //#region src/generator.d.ts
16
13
  /** operationId → [METHOD, path]. Every operation needs an id. */
17
14
  export declare const operationRoutes: (model: ParsedService) => Effect.Effect<Record<string, OperationRoute>, TanstackGenerationError, never>;
18
15
  export declare const tanstackQuery: Generator<string, TanstackGenerationError>;
19
- //#endregion
16
+ export {};
package/dist/generator.js CHANGED
@@ -1,8 +1,8 @@
1
- import { Effect, Schema } from "effect";
2
- //#region src/errors.ts
1
+ import { buildCaseMapping } from "@shipworthy/openapi-typescript/case-mapping";
2
+ import { Array, Effect, Formatter, Record, Schema, pipe } from "effect";
3
+ // src/errors.ts
3
4
  var TanstackGenerationError = class extends Schema.TaggedError()("TanstackGenerationError", { message: Schema.String }) {};
4
- //#endregion
5
- //#region src/generator.ts
5
+ // src/generator.ts
6
6
  const blockedOperationIds = /* @__PURE__ */ new Set([
7
7
  "__proto__",
8
8
  "constructor",
@@ -10,7 +10,8 @@ const blockedOperationIds = /* @__PURE__ */ new Set([
10
10
  ]);
11
11
  const PluginOptions = Schema.Struct({
12
12
  prefix: Schema.optionalKey(Schema.String),
13
- name: Schema.optionalKey(Schema.String)
13
+ name: Schema.optionalKey(Schema.String),
14
+ camelCase: Schema.optionalKey(Schema.Boolean)
14
15
  });
15
16
  function reject(message) {
16
17
  return new TanstackGenerationError({ message });
@@ -23,7 +24,7 @@ const operationRoutes = Effect.fnUntraced(function* (model) {
23
24
  const id = operation.operationId;
24
25
  if (!id) return yield* reject(`${label}: Missing operationId`);
25
26
  if (blockedOperationIds.has(id)) return yield* reject(`Unsupported operationId: ${id}`);
26
- if (Object.hasOwn(routes, id)) return yield* reject(`Duplicate operationId: ${id}`);
27
+ if (Record.has(routes, id)) return yield* reject(`Duplicate operationId: ${id}`);
27
28
  routes[id] = [operation.method, operation.path];
28
29
  return routes;
29
30
  }));
@@ -33,25 +34,28 @@ const tanstackQuery = {
33
34
  generate: Effect.fnUntraced(function* (model, options) {
34
35
  const decoded = yield* Effect.mapError(Schema.decodeEffect(PluginOptions)(options), (cause) => reject(cause.message));
35
36
  const prefix = decoded.prefix ?? "";
36
- return clientSource(prefix, decoded.name ?? prefix, yield* operationRoutes(model));
37
+ return clientSource(prefix, decoded.name ?? prefix, yield* operationRoutes(model), decoded.camelCase ? yield* buildCaseMapping(model).pipe(Effect.mapError((error) => new TanstackGenerationError({ message: error.message }))) : void 0);
37
38
  })
38
39
  };
39
- function clientSource(prefix, name, routes) {
40
- return `import { createApiClient, createTanstackQueryUtils, type TanstackQueryUtilsOptions } from "@shipworthy/openapi-tanstack-query";
40
+ function clientSource(prefix, name, routes, mapping) {
41
+ const entries = pipe(Record.collect(routes, (id, route) => ` ${Formatter.formatJson(id)}: ${Formatter.formatJson(route)},`), Array.join("\n"));
42
+ return `import { ${mapping ? "createApiClient, createTanstackQueryUtils, type ApiClientOptions, type TanstackQueryUtilsOptions" : "createApiClient, createTanstackQueryUtils, type TanstackQueryUtilsOptions"} } from "@shipworthy/openapi-tanstack-query";
41
43
  import type { operations, paths } from "./types";
42
44
 
43
45
  export type { paths as ${prefix}ApiPaths, components as ${prefix}ApiComponents, operations as ${prefix}ApiOperations } from "./types";
44
- export const create${prefix}ApiClient: typeof createApiClient<paths> = createApiClient<paths>;
46
+ ${mapping ? `const caseMapping = ${Formatter.formatJson(mapping, { space: 2 })} as const;
47
+
48
+ export const create${prefix}ApiClient = (options: ApiClientOptions) =>
49
+ createApiClient<paths>({ ...options, caseMapping });` : `export const create${prefix}ApiClient: typeof createApiClient<paths> = createApiClient<paths>;`}
45
50
  export type ${prefix}ApiClient = ReturnType<typeof create${prefix}ApiClient>;
46
51
 
47
52
  const routes = {
48
- ${Object.entries(routes).map(([id, route]) => ` ${JSON.stringify(id)}: ${JSON.stringify(route)},`).join("\n")}
53
+ ${entries}
49
54
  } as const;
50
55
 
51
56
  export function create${prefix}ApiQueryUtils(client: ${prefix}ApiClient, options?: Partial<TanstackQueryUtilsOptions>) {
52
- return createTanstackQueryUtils<Pick<operations, keyof typeof routes>>(client, routes, { prefix: ${JSON.stringify(name)}, ...options });
57
+ return createTanstackQueryUtils<Pick<operations, keyof typeof routes>>(client, routes, { prefix: ${Formatter.formatJson(name)}, ...options });
53
58
  }
54
59
  `;
55
60
  }
56
- //#endregion
57
61
  export { TanstackGenerationError, operationRoutes, tanstackQuery };
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _shipworthy_openapi_fetch = require("@shipworthy/openapi-fetch");
3
3
  let _tanstack_react_query = require("@tanstack/react-query");
4
- //#region src/client.ts
4
+ // src/client.ts
5
5
  function createApiClient({ authToken, ...options }) {
6
6
  const client = (0, _shipworthy_openapi_fetch.createClient)(options);
7
7
  if (authToken !== void 0) client.use({ async onRequest({ request }) {
@@ -17,8 +17,7 @@ function createApiClient({ authToken, ...options }) {
17
17
  } });
18
18
  return client;
19
19
  }
20
- //#endregion
21
- //#region src/query-utils.ts
20
+ // src/query-utils.ts
22
21
  function cacheInput(input = {}) {
23
22
  const { header, ...rest } = input;
24
23
  if (!header) return rest;
@@ -102,6 +101,5 @@ function createTanstackQueryUtils(client, routes, { prefix }) {
102
101
  }];
103
102
  }));
104
103
  }
105
- //#endregion
106
104
  exports.createApiClient = createApiClient;
107
105
  exports.createTanstackQueryUtils = createTanstackQueryUtils;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Client, ClientOptions, ErrorResponse, HttpMethod, MediaType, RequestBodyOption, RequiredKeysOf, ResponseObjectMap, SuccessResponse } from "@shipworthy/openapi-fetch";
2
2
  import { DataTag, InfiniteData, InfiniteQueryPageParamsOptions, QueryFunction, QueryKey, QueryObserverOptions, SkipToken, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
3
- //#region src/client.d.ts
4
3
  type AuthToken = string | undefined | null;
5
4
  type AuthTokenProvider = () => AuthToken | Promise<AuthToken>;
6
5
  interface ApiClientOptions extends Omit<ClientOptions, "baseUrl"> {
@@ -9,8 +8,6 @@ interface ApiClientOptions extends Omit<ClientOptions, "baseUrl"> {
9
8
  }
10
9
  export declare function createApiClient<Paths extends {}>({ authToken, ...options }: ApiClientOptions): Client<Paths>;
11
10
  type ApiClient<Paths extends {} = {}> = Client<Paths>;
12
- //#endregion
13
- //#region src/operation.d.ts
14
11
  /** Preserve OpenAPI's parameter groups and add its typed request body. */
15
12
  type OperationInput<Op> = (Op extends {
16
13
  parameters: infer Parameters;
@@ -28,8 +25,6 @@ type JsonResponses<Op> = { [Status in keyof Responses<Op>]: Responses<Op>[Status
28
25
  } : Responses<Op>[Status]; };
29
26
  type OperationData<Op> = NormalizeData<SuccessResponse<JsonResponses<Op>, MediaType>>;
30
27
  type OperationError<Op> = ErrorResponse<Responses<Op>, MediaType> | Error;
31
- //#endregion
32
- //#region src/query-utils.d.ts
33
28
  type InputOf<O> = OperationInput<O>;
34
29
  type DataOf<O> = OperationData<O>;
35
30
  type ErrorOf<O> = OperationError<O>;
@@ -79,5 +74,4 @@ interface TanstackQueryUtilsOptions {
79
74
  }
80
75
  type OperationRoute = readonly [Uppercase<HttpMethod>, string];
81
76
  export declare function createTanstackQueryUtils<Operations extends Record<string, any>>(client: Client<any>, routes: { [Name in keyof Operations]: OperationRoute; }, { prefix }: TanstackQueryUtilsOptions): { [Name in keyof Operations]: OperationQueryUtils<Operations[Name]>; };
82
- //#endregion
83
77
  export type { ApiClient, ApiClientOptions, AuthToken, AuthTokenProvider, OperationData, OperationError, OperationInput, OperationQueryUtils, OperationRoute, TanstackQueryUtilsOptions };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Client, ClientOptions, ErrorResponse, HttpMethod, MediaType, RequestBodyOption, RequiredKeysOf, ResponseObjectMap, SuccessResponse } from "@shipworthy/openapi-fetch";
2
2
  import { DataTag, InfiniteData, InfiniteQueryPageParamsOptions, QueryFunction, QueryKey, QueryObserverOptions, SkipToken, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
3
- //#region src/client.d.ts
4
3
  type AuthToken = string | undefined | null;
5
4
  type AuthTokenProvider = () => AuthToken | Promise<AuthToken>;
6
5
  interface ApiClientOptions extends Omit<ClientOptions, "baseUrl"> {
@@ -9,8 +8,6 @@ interface ApiClientOptions extends Omit<ClientOptions, "baseUrl"> {
9
8
  }
10
9
  export declare function createApiClient<Paths extends {}>({ authToken, ...options }: ApiClientOptions): Client<Paths>;
11
10
  type ApiClient<Paths extends {} = {}> = Client<Paths>;
12
- //#endregion
13
- //#region src/operation.d.ts
14
11
  /** Preserve OpenAPI's parameter groups and add its typed request body. */
15
12
  type OperationInput<Op> = (Op extends {
16
13
  parameters: infer Parameters;
@@ -28,8 +25,6 @@ type JsonResponses<Op> = { [Status in keyof Responses<Op>]: Responses<Op>[Status
28
25
  } : Responses<Op>[Status]; };
29
26
  type OperationData<Op> = NormalizeData<SuccessResponse<JsonResponses<Op>, MediaType>>;
30
27
  type OperationError<Op> = ErrorResponse<Responses<Op>, MediaType> | Error;
31
- //#endregion
32
- //#region src/query-utils.d.ts
33
28
  type InputOf<O> = OperationInput<O>;
34
29
  type DataOf<O> = OperationData<O>;
35
30
  type ErrorOf<O> = OperationError<O>;
@@ -79,5 +74,4 @@ interface TanstackQueryUtilsOptions {
79
74
  }
80
75
  type OperationRoute = readonly [Uppercase<HttpMethod>, string];
81
76
  export declare function createTanstackQueryUtils<Operations extends Record<string, any>>(client: Client<any>, routes: { [Name in keyof Operations]: OperationRoute; }, { prefix }: TanstackQueryUtilsOptions): { [Name in keyof Operations]: OperationQueryUtils<Operations[Name]>; };
82
- //#endregion
83
77
  export type { ApiClient, ApiClientOptions, AuthToken, AuthTokenProvider, OperationData, OperationError, OperationInput, OperationQueryUtils, OperationRoute, TanstackQueryUtilsOptions };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createClient } from "@shipworthy/openapi-fetch";
2
2
  import { skipToken } from "@tanstack/react-query";
3
- //#region src/client.ts
3
+ // src/client.ts
4
4
  function createApiClient({ authToken, ...options }) {
5
5
  const client = createClient(options);
6
6
  if (authToken !== void 0) client.use({ async onRequest({ request }) {
@@ -16,8 +16,7 @@ function createApiClient({ authToken, ...options }) {
16
16
  } });
17
17
  return client;
18
18
  }
19
- //#endregion
20
- //#region src/query-utils.ts
19
+ // src/query-utils.ts
21
20
  function cacheInput(input = {}) {
22
21
  const { header, ...rest } = input;
23
22
  if (!header) return rest;
@@ -101,5 +100,4 @@ function createTanstackQueryUtils(client, routes, { prefix }) {
101
100
  }];
102
101
  }));
103
102
  }
104
- //#endregion
105
103
  export { createApiClient, createTanstackQueryUtils };
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "@shipworthy/openapi-tanstack-query",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
7
7
  "dependencies": {
8
- "@shipworthy/openapi-fetch": "0.0.1"
8
+ "@shipworthy/openapi-fetch": "1.0.0",
9
+ "effect": "4.1.4"
9
10
  },
10
11
  "peerDependencies": {
11
- "@shipworthy/openapi-generator": "0.0.1",
12
- "@shipworthy/openapi-transformer": "0.0.1",
12
+ "@shipworthy/openapi-generator": "1.0.0",
13
+ "@shipworthy/openapi-transformer": "1.0.0",
14
+ "@shipworthy/openapi-typescript": "1.0.0",
13
15
  "@tanstack/react-query": "^5.59.0",
14
- "effect": "^4.0.0",
15
16
  "react": "^18.0.0 || ^19.0.0"
16
17
  },
17
18
  "license": "MIT",
@@ -44,16 +45,16 @@
44
45
  "test": "vitest run"
45
46
  },
46
47
  "devDependencies": {
47
- "@shipworthy/openapi-generator": "0.0.1",
48
- "@shipworthy/openapi-transformer": "0.0.1",
48
+ "@shipworthy/openapi-generator": "1.0.0",
49
+ "@shipworthy/openapi-transformer": "1.0.0",
50
+ "@shipworthy/openapi-typescript": "1.0.0",
49
51
  "@shipworthy/typescript-config": "0.0.1",
50
52
  "@tanstack/react-query": "^5.104.1",
51
- "@types/node": "^24.10.3",
52
- "effect": "^4.0.0",
53
+ "@types/node": "^26.6.6",
53
54
  "react": "^19.0.0",
54
- "tsdown": "0.23.0",
55
+ "tsdown": "0.23.7",
55
56
  "typescript": "^7.0.2",
56
- "vitest": "5.0.3"
57
+ "vitest": "5.1.0"
57
58
  },
58
59
  "peerDependenciesMeta": {
59
60
  "@shipworthy/openapi-generator": {
@@ -62,7 +63,7 @@
62
63
  "@shipworthy/openapi-transformer": {
63
64
  "optional": true
64
65
  },
65
- "effect": {
66
+ "@shipworthy/openapi-typescript": {
66
67
  "optional": true
67
68
  }
68
69
  },