@shipworthy/openapi-tanstack-query 0.0.2 → 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,26 +31,26 @@ 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. `@shipworthy/openapi-generator`
35
- and `@shipworthy/openapi-transformer` are optional peer dependencies that only
36
- the `./generator` entry point needs. The CLI installs both. The TypeScript generator bundles TypeScript 7's AST
37
- 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:
38
38
 
39
39
  ```sh
40
40
  bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
41
41
  ```
42
42
 
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-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. |
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. |
54
54
 
55
55
  The runtime entry point declares no Node.js `engines` range. The CLI runtime requirement applies to code generation.
56
56
 
@@ -66,13 +66,14 @@ you run the CLI.
66
66
  and the CLI resolves `$ref`s:
67
67
 
68
68
  ```sh
69
- 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
70
70
  # Or generate from a URL:
71
- 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
72
72
  ```
73
73
 
74
74
  This writes `src/generated/hotels/types.ts` (via `@shipworthy/openapi-typescript`)
75
- 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.
76
77
 
77
78
  **2. Create the client and query utils once**:
78
79
 
@@ -110,15 +111,49 @@ function Hotels() {
110
111
  Inputs, response data, and error types are all inferred from the OpenAPI
111
112
  document. You do not write type arguments.
112
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
+
113
148
  ## Generated output
114
149
 
115
- `openapi generate tanstack-query <input> -o <dir> --name <name>` writes the
116
- 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:
117
152
 
118
153
  - `types.ts`: `paths`, `components`, `operations`, and `webhooks` types from
119
- `@shipworthy/openapi-typescript`. This file is regenerated only when it is
120
- missing or stale (see [`--force`](#cli-openapi-generate-tanstack-query)).
121
- - `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`
122
157
  (prefix `MyPets`), it contains:
123
158
 
124
159
  ```ts
@@ -162,47 +197,56 @@ export function createMyPetsApiQueryUtils(
162
197
 
163
198
  Naming rules:
164
199
 
165
- - `<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
166
203
  removed and the next character is uppercased: `hotels` becomes `Hotels`, and
167
204
  `my-pets` becomes `MyPets`.
168
205
  - The generated exports are `create<Prefix>ApiClient`, `<Prefix>ApiClient`
169
206
  (type), `create<Prefix>ApiQueryUtils`, `<Prefix>ApiPaths`,
170
207
  `<Prefix>ApiComponents`, and `<Prefix>ApiOperations`.
171
- - 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"`.
172
209
  - Each operation is available on the query utils object by its `operationId`,
173
210
  for example `hotelsApi.getHotel`.
174
211
 
175
212
  ## CLI: `openapi generate tanstack-query`
176
213
 
177
- 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:
178
216
 
179
217
  ```
180
- openapi generate tanstack-query [flags] <input>
218
+ openapi generate tanstack-query [-i <input>] -o <dir> [--name <name>] [--camel-case] [--wire-types] [--check] [TypeScript flags]
181
219
  ```
182
220
 
183
- | Argument / flag | Type | Default | Description |
184
- | ---------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
185
- | `<input>` | string | none | OpenAPI file path, or an `http:`, `https:`, or `file:` URL. Glob patterns (`*`) are rejected. |
186
- | `--output`, `-o` | string | none | Directory that will contain `<name>/client.ts`. |
187
- | `--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]+)*$`. |
188
- | `--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.`
189
232
 
190
- `types.ts` is regenerated when it is missing, or when the
191
- `@shipworthy/openapi-typescript@<version>:<sha256>` stamp in its header no
192
- longer matches. The hash covers the schema contents, not the schema's location,
193
- 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.
194
238
 
195
239
  Example `package.json` script:
196
240
 
197
241
  ```json
198
242
  {
199
243
  "scripts": {
200
- "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"
201
245
  }
202
246
  }
203
247
  ```
204
248
 
205
- 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:
206
250
 
207
251
  | Error message | Cause |
208
252
  | -------------------------------------- | -------------------------------------------------------- |
@@ -210,7 +254,7 @@ Generation fails with exit code 1, and `client.ts` is left untouched, when:
210
254
  | `Duplicate operationId: <id>` | Two operations share an `operationId`. |
211
255
  | `Unsupported operationId: <id>` | The id is `__proto__`, `constructor`, or `prototype`. |
212
256
  | `Unsupported Swagger version: 2.x. …` | The input is Swagger 2.x. Only OpenAPI 3.x is supported. |
213
- | `Globbing is unsupported.` | `<input>` contains `*`. |
257
+ | `Globbing is unsupported.` | The input contains `*`. |
214
258
 
215
259
  ## Runtime API (`@shipworthy/openapi-tanstack-query`)
216
260
 
@@ -233,6 +277,11 @@ type ApiClient<Paths extends {} = {}> = Client<Paths>; // @shipworthy/openapi-fe
233
277
 
234
278
  This function creates an `@shipworthy/openapi-fetch` client.
235
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
+
236
285
  - **`baseUrl`** (required): the root URL for all requests. A trailing slash is
237
286
  removed.
238
287
  - **`authToken`** (optional): a token string, or a sync or async getter.
@@ -247,6 +296,8 @@ This function creates an `@shipworthy/openapi-fetch` client.
247
296
  through, including `fetch`, `Request`, `headers`, `querySerializer`,
248
297
  `bodySerializer`, `pathSerializer`, `requestInitExt`, and standard
249
298
  `RequestInit` fields such as `credentials`.
299
+ - **`caseMapping`** (optional): a generated `CaseMapping`. When provided,
300
+ the underlying fetch client installs the casing middleware.
250
301
 
251
302
  The returned client is a full `openapi-fetch` client. You can call it directly
252
303
  (`client.GET("/hotels", { params: { query } })`) or add middleware with
@@ -485,17 +536,17 @@ import {
485
536
  } from "@shipworthy/openapi-tanstack-query/generator";
486
537
  ```
487
538
 
488
- | Export | Signature / description |
489
- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
490
- | `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`. |
491
- | `operationRoutes(model)` | `Effect<Record<string, OperationRoute>, TanstackGenerationError>`. Maps each `operationId` to `[METHOD, path]`. Fails on a missing, duplicate, or reserved (`__proto__`, `constructor`, `prototype`) `operationId`. |
492
- | `clientSource(prefix, name, routes)` | `string`. Prints `client.ts` source for the given export prefix, cache-key prefix, and routes. |
493
- | `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`. |
494
545
 
495
546
  Programmatic generation with `@shipworthy/openapi-generator`, which is what
496
547
  the CLI does:
497
548
 
498
- 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:
499
550
 
500
551
  ```ts
501
552
  import { NodeServices } from "@effect/platform-node";
@@ -541,15 +592,15 @@ Generated files use extensionless `./types` imports and expect TypeScript `modul
541
592
 
542
593
  ## Troubleshooting
543
594
 
544
- | Symptom | Fix |
545
- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
546
- | `<METHOD> <path>: Missing operationId` / `Duplicate operationId: …` | Add unique `operationId`s to the OpenAPI document. |
547
- | `--name must start with a letter …` | Use letters and digits, with single `-` or `_` separators, such as `hotels` or `sdk-core`. |
548
- | `types.ts` was not regenerated after editing generator settings | It is regenerated only when the schema hash or generator version changes. Pass `--force`. |
549
- | CLI cannot load `@shipworthy/openapi-tanstack-query/generator` | Install this package, `@shipworthy/openapi-typescript`, and `@shipworthy/openapi-generator` in the project. |
550
- | `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`. |
551
- | `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). |
552
- | 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 })`. |
553
604
 
554
605
  ## Key facts (for AI agents)
555
606
 
@@ -559,11 +610,13 @@ Generated files use extensionless `./types` imports and expect TypeScript `modul
559
610
  For `./generator`, also install the optional peers
560
611
  `@shipworthy/openapi-generator` and `@shipworthy/openapi-transformer`.
561
612
  - **Generate:** run
562
- `openapi generate tanstack-query <spec> -o <dir> --name <name>` from
613
+ `openapi generate tanstack-query -i <spec> -o <dir> [--name <name>]` from
563
614
  `@shipworthy/openapi-cli` (`bunx --bun @shipworthy/openapi-cli`). The output is
564
- `<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
565
618
  files.
566
- - **Generated exports** (Prefix is the PascalCase of `name`):
619
+ - **Generated exports** (Prefix is the PascalCase of the client name):
567
620
  `create<Prefix>ApiClient`, `create<Prefix>ApiQueryUtils`,
568
621
  `<Prefix>ApiClient`, `<Prefix>ApiPaths`, `<Prefix>ApiComponents`, and
569
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,16 +1,17 @@
1
1
  {
2
2
  "name": "@shipworthy/openapi-tanstack-query",
3
- "version": "0.0.2",
3
+ "version": "1.0.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
7
7
  "dependencies": {
8
- "@shipworthy/openapi-fetch": "0.0.2",
9
- "effect": "^4.0.0"
8
+ "@shipworthy/openapi-fetch": "1.0.0",
9
+ "effect": "4.1.4"
10
10
  },
11
11
  "peerDependencies": {
12
- "@shipworthy/openapi-generator": "0.0.2",
13
- "@shipworthy/openapi-transformer": "0.0.2",
12
+ "@shipworthy/openapi-generator": "1.0.0",
13
+ "@shipworthy/openapi-transformer": "1.0.0",
14
+ "@shipworthy/openapi-typescript": "1.0.0",
14
15
  "@tanstack/react-query": "^5.59.0",
15
16
  "react": "^18.0.0 || ^19.0.0"
16
17
  },
@@ -44,15 +45,16 @@
44
45
  "test": "vitest run"
45
46
  },
46
47
  "devDependencies": {
47
- "@shipworthy/openapi-generator": "0.0.2",
48
- "@shipworthy/openapi-transformer": "0.0.2",
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",
53
+ "@types/node": "^26.6.6",
52
54
  "react": "^19.0.0",
53
- "tsdown": "0.23.0",
55
+ "tsdown": "0.23.7",
54
56
  "typescript": "^7.0.2",
55
- "vitest": "5.0.3"
57
+ "vitest": "5.1.0"
56
58
  },
57
59
  "peerDependenciesMeta": {
58
60
  "@shipworthy/openapi-generator": {
@@ -60,6 +62,9 @@
60
62
  },
61
63
  "@shipworthy/openapi-transformer": {
62
64
  "optional": true
65
+ },
66
+ "@shipworthy/openapi-typescript": {
67
+ "optional": true
63
68
  }
64
69
  },
65
70
  "main": "./dist/index.cjs",