@kollors/react-codegen 2.0.0-alpha.5 → 2.0.0-alpha.6

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.
Files changed (84) hide show
  1. package/README.md +193 -52
  2. package/README.ru.md +193 -52
  3. package/dist/{cli.js → cli/index.js} +6 -11
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/core/auth.d.ts +7 -0
  6. package/dist/core/auth.js +2 -0
  7. package/dist/core/auth.js.map +1 -0
  8. package/dist/core/config.d.ts +2 -0
  9. package/dist/core/config.js +67 -0
  10. package/dist/core/config.js.map +1 -0
  11. package/dist/{generate.d.ts → core/generate.d.ts} +0 -1
  12. package/dist/core/generate.js +47 -0
  13. package/dist/core/generate.js.map +1 -0
  14. package/dist/core/names.d.ts +5 -0
  15. package/dist/core/names.js +16 -0
  16. package/dist/core/names.js.map +1 -0
  17. package/dist/core/output.d.ts +5 -0
  18. package/dist/core/output.js +39 -0
  19. package/dist/core/output.js.map +1 -0
  20. package/dist/core/paths.d.ts +4 -0
  21. package/dist/core/paths.js +12 -0
  22. package/dist/core/paths.js.map +1 -0
  23. package/dist/{types.d.ts → core/types.d.ts} +2 -2
  24. package/dist/core/types.js.map +1 -0
  25. package/dist/graphql/document.d.ts +8 -0
  26. package/dist/graphql/document.js +118 -0
  27. package/dist/graphql/document.js.map +1 -0
  28. package/dist/graphql/fetcher.d.ts +5 -0
  29. package/dist/graphql/fetcher.js +4 -0
  30. package/dist/graphql/fetcher.js.map +1 -0
  31. package/dist/graphql/generate.d.ts +3 -0
  32. package/dist/graphql/generate.js +59 -0
  33. package/dist/graphql/generate.js.map +1 -0
  34. package/dist/graphql/operations.d.ts +13 -0
  35. package/dist/graphql/operations.js +139 -0
  36. package/dist/graphql/operations.js.map +1 -0
  37. package/dist/{graphql-fetcher.d.ts → graphql/runtime.d.ts} +4 -3
  38. package/dist/{graphql-fetcher.js → graphql/runtime.js} +12 -6
  39. package/dist/graphql/runtime.js.map +1 -0
  40. package/dist/graphql/runtime.source +140 -0
  41. package/dist/graphql/selections.d.ts +12 -0
  42. package/dist/graphql/selections.js +120 -0
  43. package/dist/graphql/selections.js.map +1 -0
  44. package/dist/graphql/types.d.ts +15 -0
  45. package/dist/graphql/types.js +110 -0
  46. package/dist/graphql/types.js.map +1 -0
  47. package/dist/index.d.ts +6 -6
  48. package/dist/index.js +3 -3
  49. package/dist/index.js.map +1 -1
  50. package/dist/openapi/fetcher.d.ts +3 -0
  51. package/dist/openapi/fetcher.js +4 -0
  52. package/dist/openapi/fetcher.js.map +1 -0
  53. package/dist/openapi/generate.d.ts +1 -1
  54. package/dist/openapi/generate.js +2 -4
  55. package/dist/openapi/generate.js.map +1 -1
  56. package/dist/openapi/names.d.ts +1 -5
  57. package/dist/openapi/names.js +1 -15
  58. package/dist/openapi/names.js.map +1 -1
  59. package/dist/openapi/operations.js +1 -1
  60. package/dist/openapi/operations.js.map +1 -1
  61. package/dist/{openapi-runtime.d.ts → openapi/runtime.d.ts} +1 -7
  62. package/dist/{openapi-runtime.js → openapi/runtime.js} +1 -1
  63. package/dist/openapi/runtime.js.map +1 -0
  64. package/dist/openapi/schema.js +17 -16
  65. package/dist/openapi/schema.js.map +1 -1
  66. package/docs/migration.md +21 -0
  67. package/docs/migration.ru.md +21 -0
  68. package/package.json +10 -16
  69. package/dist/cli.js.map +0 -1
  70. package/dist/generate.js +0 -197
  71. package/dist/generate.js.map +0 -1
  72. package/dist/graphql-fetcher.js.map +0 -1
  73. package/dist/openapi-fetcher.d.ts +0 -3
  74. package/dist/openapi-fetcher.js +0 -4
  75. package/dist/openapi-fetcher.js.map +0 -1
  76. package/dist/openapi-runtime.js.map +0 -1
  77. package/dist/path-utils.d.ts +0 -2
  78. package/dist/path-utils.js +0 -5
  79. package/dist/path-utils.js.map +0 -1
  80. package/dist/types.js.map +0 -1
  81. package/templates/graphql.template +0 -23
  82. /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
  83. /package/dist/{types.js → core/types.js} +0 -0
  84. /package/dist/{openapi-runtime.source → openapi/runtime.source} +0 -0
package/README.ru.md CHANGED
@@ -2,27 +2,29 @@
2
2
 
3
3
  [English](README.md)
4
4
 
5
- Генерация одного самодостаточного TypeScript-клиента из OpenAPI-схемы, GraphQL-схемы и операций либо сразу из обеих схем.
6
-
7
- Требуются Node.js 22 или новее и TanStack Query 5 в подключающем приложении.
5
+ Генератор TypeScript-типов, функций запросов и React-хуков для TanStack Query из OpenAPI и GraphQL. Для каждой настроенной схемы создаётся отдельный `.ts`-файл.
8
6
 
9
7
  ## Установка
10
8
 
9
+ Нужна Node.js 22 или новее. В приложении, которое использует сгенерированные хуки, должен быть установлен TanStack Query 5 и настроен `QueryClientProvider`.
10
+
11
11
  ```sh
12
12
  npm install -D @kollors/react-codegen@alpha
13
13
  ```
14
14
 
15
- ## Конфигурация
15
+ Сгенерированные файлы содержат код отправки запросов. Они используют TanStack Query; сам React Codegen нужен только для генерации.
16
+
17
+ ## Конфигурация и запуск
16
18
 
17
19
  Создайте `codegen.config.js`:
18
20
 
19
21
  ```js
20
22
  export default {
21
23
  auth: {
22
- storage: 'localStorage', // 'localStorage' | 'sessionStorage' | 'cookie' | 'none'
24
+ storage: 'localStorage',
23
25
  key: 'accessToken',
24
- scheme: 'Bearer', // false — отправлять токен без схемы
25
- credentials: 'same-origin', // 'omit' | 'same-origin' | 'include'
26
+ scheme: 'Bearer',
27
+ credentials: 'same-origin',
26
28
  },
27
29
  openapi: {
28
30
  schema: './openapi.yaml',
@@ -38,56 +40,168 @@ export default {
38
40
  };
39
41
  ```
40
42
 
41
- Запуск:
43
+ Запустите генерацию:
42
44
 
43
45
  ```sh
44
46
  npx react-codegen codegen.config.js
45
47
  ```
46
48
 
47
- Пути разрешаются относительно файла конфигурации. Наличие секции `openapi` запускает генерацию OpenAPI, наличие `graphql` — GraphQL. Отсутствующую секцию можно опустить; нужна хотя бы одна. Каждая секция создаёт один TypeScript-файл. `documents` — каталог: в нём и всех подпапках рекурсивно ищутся файлы `.graphql`. Собственный OpenAPI-генератор работает в памяти, без сторонних генераторов, дочерних процессов и промежуточных файлов. GraphQL пока использует прежний генератор и временную подпапку внутри `.react-codegen-temp` в каталоге пакета; после генерации она удаляется. Готовые файлы сначала записываются рядом с назначением, затем публикуются атомарным переименованием.
49
+ Этот пример создаёт `src/api/openapi.ts` и `src/api/graphql.ts`. Укажите `openapi`, `graphql` или обе секции; нужна хотя бы одна. Каталоги создаются автоматически, а выходные файлы должны иметь разные имена.
50
+
51
+ Пути считаются от файла конфигурации. Конфиг использует `export default`; в проекте с CommonJS назовите его `codegen.config.mjs` и передайте это имя в команду.
52
+
53
+ | Настройка | Обязательна | Назначение или значение по умолчанию |
54
+ | --- | --- | --- |
55
+ | `openapi.schema` | Да | OpenAPI 3.0.x или 3.1.x в YAML/JSON: путь к файлу или HTTP URL |
56
+ | `openapi.output` | Да | Путь к создаваемому `.ts`-файлу |
57
+ | `openapi.baseUrl` | Нет | Начало URL для запросов; без настройки пути остаются относительными |
58
+ | `graphql.schema` | Да | GraphQL SDL или introspection JSON: путь к файлу или HTTP URL |
59
+ | `graphql.documents` | Да | Каталог с именованными запросами, мутациями и фрагментами в файлах `.graphql`; вложенные каталоги тоже читаются |
60
+ | `graphql.output` | Да | Путь к создаваемому `.ts`-файлу |
61
+ | `graphql.endpoint` | Нет | Адрес запросов; по умолчанию `/api/graphql` |
62
+ | `auth` | Нет | Общие [настройки авторизации](#авторизация) |
63
+ | `openapi.auth`, `graphql.auth` | Нет | Заменяют общие настройки авторизации для выбранного клиента |
64
+
65
+ GraphQL introspection JSON может содержать `__schema` или `data.__schema`. В `schema` также можно указать HTTP-адрес GraphQL API: генератор запросит его схему через introspection.
66
+
67
+ ## Использование сгенерированного кода
68
+
69
+ ### Запросы
70
+
71
+ Для OpenAPI GET-операции с `operationId: getPet` вызовите хук в компоненте или другом хуке:
72
+
73
+ ```ts
74
+ import { useGetPet } from './api/openapi';
75
+
76
+ export function usePetName(id: string) {
77
+ return useGetPet({ pathParams: { id } }, { select: pet => pet.name });
78
+ }
79
+ ```
80
+
81
+ Для GraphQL-запроса `MovieDetailsQuery`, который выбирает `movie.title`:
82
+
83
+ ```ts
84
+ import { useMovieDetailsQuery } from './api/graphql';
85
+
86
+ export function useMovieTitle(id: string) {
87
+ return useMovieDetailsQuery({ id }, { select: data => data.movie?.title });
88
+ }
89
+ ```
90
+
91
+ Оба генератора создают следующие функции для запросов:
92
+
93
+ | Назначение | OpenAPI `getPet` | GraphQL `MovieDetailsQuery` |
94
+ | --- | --- | --- |
95
+ | Хук запроса | `useGetPet` | `useMovieDetailsQuery` |
96
+ | Хук с Suspense | `useSuspenseGetPet` | `useSuspenseMovieDetailsQuery` |
97
+ | Настройки для `QueryClient` | `getPetQuery` | `movieDetailsQuery` |
98
+ | Ключ кеша | `getPetQueryKey` | `movieDetailsQueryKey` |
99
+ | Прямой запрос | `fetchGetPet` | `fetchMovieDetailsQuery` |
100
+
101
+ В OpenAPI имена строятся из `operationId`, а при его отсутствии — из HTTP-метода и пути. В GraphQL — из имени операции. GraphQL также экспортирует тип выбранного результата, тип переменных и строку документа: например, `MovieDetailsQuery`, `MovieDetailsQueryVariables` и `MovieDetailsQueryDocument`.
102
+
103
+ ### Мутации
104
+
105
+ Для OpenAPI-методов кроме GET и для GraphQL-мутаций создаются хуки мутаций. Пример для OpenAPI-операции с `operationId: createPet`:
106
+
107
+ ```ts
108
+ import { useCreatePet, type CreatePetRequestBody } from './api/openapi';
109
+
110
+ export function useSavePet() {
111
+ const mutation = useCreatePet();
112
+ return (body: CreatePetRequestBody) => mutation.mutate({ body });
113
+ }
114
+ ```
115
+
116
+ В OpenAPI аргументы сгруппированы в `pathParams`, `queryParams`, `headers` и `body`. В GraphQL передаются переменные операции. Обязательность задаёт схема; переменные GraphQL со знаком `!` обязательны, если у них нет значения по умолчанию.
117
+
118
+ Если обязательных аргументов нет, хуки и функции запросов можно вызывать без них. В этом случае для мутаций также доступны `mutate()` и `mutateAsync()` без аргументов.
119
+
120
+ ### Пропуск запроса
121
+
122
+ Обычные хуки запросов принимают `skipToken`:
123
+
124
+ ```ts
125
+ import { skipToken } from '@tanstack/react-query';
126
+ import { useGetPet } from './api/openapi';
127
+
128
+ export function useOptionalPet(id?: string) {
129
+ return useGetPet(id ? { pathParams: { id } } : skipToken);
130
+ }
131
+ ```
132
+
133
+ Хуки с Suspense не принимают `skipToken`. Аргументы для них можно опустить, если у операции нет обязательных аргументов.
134
+
135
+ ### Запросы и ключи кеша
48
136
 
49
- В `schema` можно передавать локальный путь или URL. Каталоги для `output` создаются автоматически.
137
+ Загрузка данных через TanStack Query:
138
+
139
+ ```ts
140
+ import { QueryClient } from '@tanstack/react-query';
141
+ import { getPetQuery } from './api/openapi';
50
142
 
51
- ## Генератор OpenAPI
143
+ const queryClient = new QueryClient();
144
+ const pet = await queryClient.fetchQuery(getPetQuery({ pathParams: { id: '42' } }));
145
+ ```
146
+
147
+ Чтобы обновить кеш в приложении, используйте его `QueryClient`:
148
+
149
+ ```ts
150
+ import { useQueryClient } from '@tanstack/react-query';
151
+ import { getPetQueryKey } from './api/openapi';
52
152
 
53
- Обязательны только `schema` и `output`. Один документ OpenAPI 3.0.x или 3.1.x в YAML/JSON превращается в один `.ts`-файл с типами, fetcher, функциями запросов и хуками TanStack Query 5. Swagger 2.0 не поддерживается. Готовый клиент не импортирует наш пакет. `yaml` нужен только для чтения схемы при генерации.
153
+ export function useRefreshPet(id: string) {
154
+ const queryClient = useQueryClient();
155
+ return () => queryClient.invalidateQueries({ queryKey: getPetQueryKey({ pathParams: { id } }) });
156
+ }
157
+ ```
54
158
 
55
- Для операции с `operationId: getPet`:
159
+ Прямой запрос без TanStack Query:
56
160
 
57
161
  ```ts
58
- import { useGetPet, getPetQuery, getPetQueryKey, fetchGetPet } from './api/openapi';
162
+ import { fetchGetPet } from './api/openapi';
59
163
 
60
- const variables = { pathParams: { id: '42' } };
61
- const query = useGetPet(variables, { select: pet => pet.name });
62
- await queryClient.fetchQuery(getPetQuery(variables));
63
- await queryClient.invalidateQueries({ queryKey: getPetQueryKey(variables) });
64
- await fetchGetPet(variables, abortController.signal);
164
+ const controller = new AbortController();
165
+ const pet = await fetchGetPet({ pathParams: { id: '42' } }, controller.signal);
65
166
  ```
66
167
 
67
- Для GET создаются `useX`, `useSuspenseX`, `xQuery`, `xQueryKey` и `fetchX`. Для остальных методов — мутация `useX` и функция `fetchX`, например `useCreatePet().mutate({ body: pet })`. Аргументы сгруппированы в `pathParams`, `queryParams`, `headers` и `body`; обязательность задаёт схема. Если обязательных аргументов нет, вызов возможен без них, включая `useAuthLogout().mutate()` и `mutateAsync()`. Обычные query принимают `skipToken` из TanStack; suspense-query требуют аргументы. Имена строятся из `operationId`, а при его отсутствии — из метода и пути. Коллизия имён сообщает оба места в схеме. Готовый OpenAPI-клиент форматируется Prettier в памяти перед записью.
168
+ Вызов `controller.abort()` отменяет прямой запрос. Хуки запросов получают сигнал отмены от TanStack Query автоматически. Функции запросов и вспомогательные функции GraphQL работают так же.
169
+
170
+ Для работы с кешем используйте сгенерированные функции ключей. Ключи учитывают адрес API и аргументы; в OpenAPI — также заголовки запроса. Токены из хранилища и браузерные cookie в ключ не входят, поэтому при смене пользователя очищайте соответствующий кеш.
171
+
172
+ ## Авторизация
68
173
 
69
- Ключи кеша включают настроенный базовый URL, HTTP-метод, путь, имя операции и аргументы. Сгенерированный `Accept` и явно переданные заголовки приводятся к сериализованным именам и значениям: эквивалентные `Headers`, объекты и массивы пар используют одну запись кеша, разные значения заголовков — разные. Токены из хранилища и браузерные cookie в ключ не входят; при смене пользователя очищайте соответствующий кеш запросов.
174
+ Общая секция `auth` применяется к обоим клиентам. `openapi.auth` или `graphql.auth` целиком заменяет её для выбранного клиента.
70
175
 
71
- ### Что поддерживается
176
+ Без `auth` сгенерированный клиент не добавляет заголовок `Authorization`.
72
177
 
73
- - Примитивы, массивы, объекты, словари, enum, внутренние `$ref`, рекурсивные компоненты, пересечения `allOf`, объединения `oneOf`/`anyOf`. В OpenAPI 3.1 — также булевы схемы, `const` и массивы типов.
74
- - `required` определяет необязательные поля. `null` появляется только там, где разрешён схемой. Массовой замены `null` на `undefined` нет. Для `readOnly`/`writeOnly` при необходимости создаются входные и выходные типы.
75
- - Ограничения объектов объединяются до печати типов. `additionalProperties: false` сохраняет ограничения своей подсхемы, включая композиции; противоречивые обязательные поля дают `never`. Объектные значения `const`/`enum` остаются закрытыми. Повторы в union и нейтральные пересечения упрощаются. TypeScript использует структурную типизацию, поэтому типы не заменяют проверку произвольных объектов во время выполнения.
76
- - Параметры пути/заголовков `simple`; query-параметры `form`, плоский `deepObject` с `explode: true`, массивы `spaceDelimited`/`pipeDelimited` с `explode: false`. Параметры операции переопределяют параметры уровня пути.
77
- - Скалярные параметры выводятся из `enum`, `const` и поддерживаемых композиций без явного `type`. Необязательные свойства объектов со значением `undefined` пропускаются при сериализации параметров. Элементы массивов с `undefined` и вложенные объекты/массивы параметров отклоняются с именем параметра.
78
- - JSON/`+json`, текст/XML как строки, бинарные тела/ответы как `Blob`, multipart и URL-encoded тела из объектов. При нескольких форматах запроса порядок выбора: JSON, `+json`, URL-encoded, multipart, остальные поддерживаемые форматы по алфавиту. Ответ читается по фактическому Content-Type; типы успешных ответов объединяются. `default` используется как успешный ответ только при отсутствии явных 2xx. Пустые ответы мутаций имеют тип `void` и возвращают `undefined`.
79
- - При распознавании Content-Type игнорируются регистр и параметры вроде `charset`; заголовки запроса сохраняют объявленный формат.
80
- - Описания становятся комментариями. Даты остаются строками. Числовые границы, паттерны и длины не добавляют проверок во время выполнения. `oneOf` становится union без проверки исключительности. `discriminator` не добавляет выдуманных полей и значений: поле-дискриминатор нужно описать в схеме.
178
+ | Параметр | Обязателен | Поведение |
179
+ | --- | --- | --- |
180
+ | `storage` | Если указан `auth` | Откуда читать токен; варианты ниже |
181
+ | `key` | Если `storage` отличается от `none` | Имя записи в хранилище или cookie |
182
+ | `scheme` | Нет | Префикс заголовка; по умолчанию `Bearer`. Значение `false` отправляет токен без изменений |
183
+ | `credentials` | Нет | Правила отправки браузерных cookie; по умолчанию `same-origin` |
81
184
 
82
- ### Текущие ограничения
185
+ | `storage` | Источник токена |
186
+ | --- | --- |
187
+ | `localStorage` | `localStorage.getItem(key)` |
188
+ | `sessionStorage` | `sessionStorage.getItem(key)` |
189
+ | `cookie` | Cookie с именем `key`, прочитанная через `document.cookie` |
190
+ | `none` | Токен не читается, заголовок авторизации не добавляется |
83
191
 
84
- Неподдерживаемые конструкции вызывают ошибку с местом в схеме до замены выходного файла: внешние `$ref` (сначала объедините схему), callbacks/webhooks, cookie-параметры, `content` у параметров, вложенные объекты/массивы параметров, `allowReserved`, стили пути `label`/`matrix`, пользовательский encoding форм, неподдерживаемые Content-Type и сложные ключевые слова JSON Schema: условия, `not`, `dependentRequired`/`dependentSchemas`, динамические ссылки, `prefixItems`, `patternProperties`, `unevaluatedProperties`. Типизированные `additionalProperties` вместе с именованными свойствами отклоняются: индексная сигнатура TypeScript ошибочно ограничила бы и именованные свойства; словарь можно вынести в отдельное свойство. Рекурсивные типы должны быть компонентами; циклические цепочки алиасов и циклические YAML-ссылки отклоняются.
192
+ | `credentials` | Браузерные cookie |
193
+ | --- | --- |
194
+ | `omit` | Не отправлять |
195
+ | `same-origin` | Разрешить для запросов к тому же origin |
196
+ | `include` | Разрешить также для запросов к другому origin |
85
197
 
86
- Fetch не позволяет отправлять тело с GET/HEAD и выполнять TRACE. GET с описанным ответом без тела отклоняется: React Query не кеширует `undefined`, и генератор не заменяет его на `null`. Если операция объявляет тело ответа, а сервер не прислал его (включая 204/205, попавшие в `2XX` или `default`), фетчер выдаёт ошибку контракта с HTTP-статусом и URL запроса. Пустые текстовые ответы остаются пустыми строками. Сегменты пути `.` и `..` отклоняются до отправки запроса: Fetch нормализовал бы их и изменил маршрут. `servers` и `security` из схемы не заменяют настройки: используйте `baseUrl` и `auth`. Заголовки ответа отдельно не возвращаются. Сгенерированные типы не проверяют структуру фактических ответов сервера во время выполнения.
198
+ Если токен отсутствует или пустой, заголовок авторизации не добавляется. Для cookie с флагом `HttpOnly`, которые JavaScript не может прочитать, настройте отправку браузером:
87
199
 
88
- ### Переход с прежнего генератора
200
+ ```js
201
+ auth: { storage: 'none', credentials: 'include' }
202
+ ```
89
203
 
90
- Перегенерируйте клиент. Привычные camelCase имена операций и группы аргументов сохраняются, но полной совместимости всех экспортов нет. Удалены пустой контекст, `deepMerge`, `QueryOperation`, общий `queryKeyFn` и `@ts-nocheck`. Начиная с `2.0.0-alpha.4`, ключи кеша имеют вид `[baseUrl, method, path, operationName, normalizedVariables]` вместо `[operationName, variables]`: используйте `xQueryKey`, обновите вручную составленные ключи инвалидации и очистите сохранённый кеш при обновлении. Имена типов приводятся к PascalCase, пунктуация и подчёркивания разделяют слова. Ошибки представлены `OpenapiHttpError` или обычным `Error` вместо `ErrorWrapper`. Более строгие типы могут потребовать исправить места вызова.
204
+ При этом действуют правила браузера для cookie и CORS. При прямом вызове `createOpenapiFetcher` или `createGraphqlFetcher` можно передать `getToken`, возвращающий полное значение заголовка `Authorization`. Этот параметр относится к функциям создания клиента, а не к конфигу генератора.
91
205
 
92
206
  ## Программный API
93
207
 
@@ -95,33 +209,53 @@ Fetch не позволяет отправлять тело с GET/HEAD и вы
95
209
  import { generate, type CodegenConfig } from '@kollors/react-codegen';
96
210
 
97
211
  const config: CodegenConfig = {
98
- auth: {
99
- storage: 'localStorage',
100
- key: 'accessToken',
101
- },
102
- graphql: {
103
- schema: './schema.graphql',
104
- documents: './src/graphql',
105
- output: './src/api/graphql.ts',
106
- endpoint: '/graphql',
212
+ openapi: {
213
+ schema: './openapi.yaml',
214
+ output: './src/api/openapi.ts',
107
215
  },
108
216
  };
109
217
 
110
- const result = await generate(config);
111
- console.log(result.outputs);
218
+ const { outputs } = await generate(config);
219
+ console.log(outputs);
112
220
  ```
113
221
 
114
- В программном API относительные пути считаются от текущей рабочей директории. `generate` возвращает пути созданных файлов.
222
+ Настройки совпадают с конфигом CLI. Относительные пути считаются от текущей рабочей директории. `generate` возвращает пути созданных файлов.
223
+
224
+ ## Поддержка схем и ограничения
225
+
226
+ Оба генератора создают типы по схеме. Они не проверяют данные ответа сервера во время выполнения. Ошибка генерации сохраняет существующие выходные файлы.
115
227
 
116
- ## Авторизация и ошибки
228
+ ### OpenAPI
117
229
 
118
- Общие настройки `auth` применяются к обоим клиентам. Чтобы заменить их для одного клиента, укажите `auth` внутри `openapi` или `graphql`. Поддерживаются `storage`: `localStorage`, `sessionStorage`, `cookie` и `none`. Если секция `auth` указана, `storage` обязателен. В `key` указывается имя записи с токеном; ключ обязателен для `localStorage`, `sessionStorage` и `cookie`, но не используется с `none`. `scheme` необязателен и по умолчанию равен `Bearer`; значение `false` отправляет токен без схемы. `credentials` необязателен и по умолчанию использует поведение Fetch API `same-origin`; также доступны `omit` и `include`.
230
+ - Поддерживаются OpenAPI 3.0.x и 3.1.x; Swagger 2.0 не поддерживается.
231
+ - Поддерживаются объекты, массивы, словари, enum, внутренние `$ref`, рекурсивные компоненты, `allOf`, `oneOf` и `anyOf`. В OpenAPI 3.1 — также `const`, булевы схемы и массивы типов.
232
+ - `required` определяет обязательные поля; `null` включается там, где разрешён схемой. Поля `readOnly` исключаются из типов запросов, `writeOnly` — из типов ответов. Даты остаются строками.
233
+ - Поддерживаются JSON, `+json`, текст/XML как строки, бинарные данные как `Blob`, multipart и URL-encoded тела. При нескольких форматах запроса приоритет такой: JSON, `+json`, URL-encoded, multipart, остальные поддерживаемые форматы по алфавиту.
234
+ - Поддерживаются параметры пути и заголовков `simple`, query-параметры `form`, плоский `deepObject` с `explode: true` и массивы `spaceDelimited`/`pipeDelimited` с `explode: false`. Вложенные объекты и массивы параметров не поддерживаются.
119
235
 
120
- Если `auth` не задан ни глобально, ни в секции клиента, сгенерированный клиент не добавляет заголовок `Authorization`. JavaScript не может прочитать cookie с флагом `HttpOnly`; для них укажите `auth: { storage: 'none', credentials: 'include' }`, и браузер отправит cookie сам. `graphql.endpoint` по умолчанию равен `/api/graphql`. `openapi.baseUrl` по умолчанию пустой, поэтому пути из схемы остаются относительными.
236
+ Внешние `$ref` нужно объединить в один документ перед генерацией. Не поддерживаются callbacks, webhooks, cookie-параметры, `content` у параметров, `allowReserved`, стили пути `label`/`matrix` и пользовательский encoding форм. Сложные ограничения JSON Schema, например условия, `not`, `dependentRequired`, `patternProperties` и `unevaluatedProperties`, вызывают ошибку с местом в схеме.
121
237
 
122
- При прямом использовании фабрик fetcher можно передать `getToken`; функция должна вернуть полное значение заголовка `Authorization`.
238
+ Типизированные `additionalProperties` вместе с именованными свойствами не поддерживаются; вынесите словарь в отдельное свойство. Числовые границы, паттерны и длины не проверяются во время выполнения. `oneOf` становится TypeScript-объединением без проверки, что подходит ровно один вариант; поле дискриминатора и его значения нужно описать в схеме.
123
239
 
124
- Ошибки HTTP представлены классами `GraphqlHttpError` и `OpenapiHttpError`; они содержат статус и данные ответа. Ошибки выполнения GraphQL представлены `GraphqlResponseError` со всеми сообщениями сервера.
240
+ Адрес и авторизация задаются через `baseUrl` и `auth`; `servers` и `security` из схемы не применяются. Для GET нужно описать тело ответа. Пустые ответы мутаций возвращают `undefined` с типом `void`; отсутствие объявленного тела ответа вызывает ошибку запроса. Тела GET/HEAD, метод TRACE и сегменты пути `.` или `..` отклоняются. Заголовки ответа отдельно не возвращаются.
241
+
242
+ ### GraphQL
243
+
244
+ Поддерживаются именованные запросы и мутации, фрагменты, псевдонимы полей, интерфейсы и объединения. Тип результата описывает выбранные поля. Выбирайте `__typename`, если нужно различать варианты объединения.
245
+
246
+ Поля, допускающие `null`, имеют тип `T | null`; условные поля с `@skip` или `@include` могут быть необязательными. Списки сохраняют возможность `null` у элементов. Входные поля и переменные со знаком `!` и значением по умолчанию можно опустить, но нельзя передать `null`. Входные объекты с `@oneOf` требуют ровно одно поле со значением, отличным от `null`. Пользовательские скаляры имеют тип `unknown`.
247
+
248
+ Subscriptions и `@defer`/`@stream` не поддерживаются. Загрузка схемы не использует `auth`; если адрес схемы требует авторизации, используйте локальный экспорт схемы.
249
+
250
+ ### Ошибки запросов
251
+
252
+ | Ошибка | Данные |
253
+ | --- | --- |
254
+ | `OpenapiHttpError` | HTTP-статус и данные ответа в `data` |
255
+ | `GraphqlHttpError` | HTTP-статус и тело ответа в `body` |
256
+ | `GraphqlResponseError` | Ошибки выполнения GraphQL в `errors` и частичные данные ответа в `data` |
257
+
258
+ При обновлении существующих клиентов смотрите [инструкцию по переходу](docs/migration.ru.md).
125
259
 
126
260
  ## Разработка
127
261
 
@@ -130,6 +264,13 @@ npm ci
130
264
  npm run verify
131
265
  ```
132
266
 
267
+ | Каталог | Содержимое |
268
+ | --- | --- |
269
+ | `src/cli/` | Команда CLI |
270
+ | `src/core/` | Конфигурация, запуск генерации, запись файлов и общие утилиты |
271
+ | `src/openapi/` | Генератор OpenAPI и код отправки запросов |
272
+ | `src/graphql/` | Генератор GraphQL и код отправки запросов |
273
+
133
274
  ## Лицензия
134
275
 
135
276
  [MIT](LICENSE)
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from 'node:fs/promises';
3
- import { dirname, isAbsolute, resolve } from 'node:path';
3
+ import { dirname, resolve } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
- import { generate, validateCodegenConfig } from './generate.js';
6
- import { isUrl } from './path-utils.js';
5
+ import { validateCodegenConfig } from '../core/config.js';
6
+ import { generate } from '../core/generate.js';
7
+ import { resolvePath, resolveSchema } from '../core/paths.js';
7
8
  const help = `Usage: react-codegen <codegen.config.js>
8
9
 
9
10
  Generate configured OpenAPI and GraphQL clients.
@@ -12,19 +13,13 @@ Options:
12
13
  -h, --help Show this help message
13
14
  -v, --version Show version
14
15
  `;
15
- function resolvePath(value, directory) {
16
- return isAbsolute(value) ? value : resolve(directory, value);
17
- }
18
- function resolveSchema(value, directory) {
19
- return isUrl(value) ? value : resolvePath(value, directory);
20
- }
21
16
  async function main(args) {
22
17
  if (args.includes('--help') || args.includes('-h')) {
23
18
  process.stdout.write(help);
24
19
  return;
25
20
  }
26
21
  if (args.includes('--version') || args.includes('-v')) {
27
- const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
22
+ const packageJson = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8'));
28
23
  process.stdout.write(`${packageJson.version}\n`);
29
24
  return;
30
25
  }
@@ -68,4 +63,4 @@ catch (error) {
68
63
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
69
64
  process.exitCode = 1;
70
65
  }
71
- //# sourceMappingURL=cli.js.map
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type TokenStorage = 'localStorage' | 'sessionStorage' | 'cookie' | 'none';
2
+ export interface AuthConfig {
3
+ storage: TokenStorage;
4
+ key?: string;
5
+ scheme?: string | false;
6
+ credentials?: RequestCredentials;
7
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ import type { CodegenConfig } from './types.js';
2
+ export declare function validateCodegenConfig(value: unknown): asserts value is CodegenConfig;
@@ -0,0 +1,67 @@
1
+ import { resolve } from 'node:path';
2
+ export function validateCodegenConfig(value) {
3
+ const config = value;
4
+ if (config === null || typeof config !== 'object' || Array.isArray(config)) {
5
+ throw new TypeError('Codegen config must be an object.');
6
+ }
7
+ const keys = Object.keys(config);
8
+ if (keys.some((key) => key !== 'auth' && key !== 'openapi' && key !== 'graphql')) {
9
+ throw new Error(`Unknown codegen config section: ${keys.find((key) => key !== 'auth' && key !== 'openapi' && key !== 'graphql')}`);
10
+ }
11
+ if (!config.openapi && !config.graphql) {
12
+ throw new Error('Codegen config must contain an openapi or graphql section.');
13
+ }
14
+ const validateAuth = (auth, location) => {
15
+ if (!auth || typeof auth !== 'object' || Array.isArray(auth))
16
+ throw new TypeError(`${location} must be an object.`);
17
+ const allowedAuthKeys = ['storage', 'key', 'scheme', 'credentials'];
18
+ const unknownKey = Object.keys(auth).find((key) => !allowedAuthKeys.includes(key));
19
+ if (unknownKey)
20
+ throw new Error(`Unknown ${location} option: ${unknownKey}`);
21
+ if (!['localStorage', 'sessionStorage', 'cookie', 'none'].includes(auth.storage)) {
22
+ throw new TypeError(`${location}.storage must be localStorage, sessionStorage, cookie, or none.`);
23
+ }
24
+ if (auth.storage !== 'none' && (typeof auth.key !== 'string' || !auth.key.trim())) {
25
+ throw new TypeError(`${location}.key must be a non-empty string unless storage is "none".`);
26
+ }
27
+ if (auth.key !== undefined && (typeof auth.key !== 'string' || !auth.key.trim())) {
28
+ throw new TypeError(`${location}.key must be a non-empty string.`);
29
+ }
30
+ if (auth.scheme !== undefined && auth.scheme !== false && (typeof auth.scheme !== 'string' || !auth.scheme.trim())) {
31
+ throw new TypeError(`${location}.scheme must be a non-empty string or false.`);
32
+ }
33
+ if (auth.credentials !== undefined && !['omit', 'same-origin', 'include'].includes(auth.credentials)) {
34
+ throw new TypeError(`${location}.credentials must be omit, same-origin, or include.`);
35
+ }
36
+ };
37
+ if (config.auth !== undefined)
38
+ validateAuth(config.auth, 'config.auth');
39
+ for (const [kind, section] of Object.entries({ openapi: config.openapi, graphql: config.graphql })) {
40
+ if (section === undefined)
41
+ continue;
42
+ if (!section || typeof section !== 'object' || Array.isArray(section)) {
43
+ throw new TypeError(`config.${kind} must be an object.`);
44
+ }
45
+ const expectedKeys = kind === 'openapi' ? ['schema', 'output', 'baseUrl', 'auth'] : ['schema', 'documents', 'output', 'endpoint', 'auth'];
46
+ for (const key of Object.keys(section)) {
47
+ if (!expectedKeys.includes(key))
48
+ throw new Error(`Unknown config.${kind} option: ${key}`);
49
+ }
50
+ for (const key of expectedKeys) {
51
+ const value = section[key];
52
+ if ((key === 'baseUrl' || key === 'endpoint' || key === 'auth') && value === undefined)
53
+ continue;
54
+ if (key === 'auth') {
55
+ validateAuth(value, `config.${kind}.auth`);
56
+ continue;
57
+ }
58
+ if (typeof value !== 'string' || !value.trim())
59
+ throw new TypeError(`config.${kind}.${key} must be a non-empty string.`);
60
+ }
61
+ }
62
+ const outputs = [config.openapi?.output, config.graphql?.output].filter((value) => Boolean(value));
63
+ if (new Set(outputs.map((value) => resolve(value))).size !== outputs.length) {
64
+ throw new Error('OpenAPI and GraphQL outputs must use different files.');
65
+ }
66
+ }
67
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,3 @@
1
1
  import type { CodegenConfig, CodegenResult } from './types.js';
2
2
  /** Generate configured OpenAPI and GraphQL clients into one TypeScript file per enabled section. */
3
3
  export declare function generate(config: CodegenConfig): Promise<CodegenResult>;
4
- export declare function validateCodegenConfig(config: unknown): asserts config is CodegenConfig;
@@ -0,0 +1,47 @@
1
+ import { generateGraphqlSource } from '../graphql/generate.js';
2
+ import { generateOpenapiSource } from '../openapi/generate.js';
3
+ import { validateCodegenConfig } from './config.js';
4
+ import { publishOutputs } from './output.js';
5
+ import { resolvePath, resolveSchema } from './paths.js';
6
+ async function generateOpenapi(config, auth, cwd) {
7
+ try {
8
+ const content = await generateOpenapiSource(resolveSchema(config.schema, cwd), {
9
+ ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
10
+ ...(auth ? { auth } : {}),
11
+ });
12
+ return { path: resolvePath(config.output, cwd), content };
13
+ }
14
+ catch (error) {
15
+ throw new Error(`Could not generate OpenAPI client from ${resolveSchema(config.schema, cwd)}: ${errorMessage(error)}`, {
16
+ cause: error,
17
+ });
18
+ }
19
+ }
20
+ async function generateGraphql(config, auth, cwd) {
21
+ try {
22
+ const content = await generateGraphqlSource(resolveSchema(config.schema, cwd), resolvePath(config.documents, cwd), {
23
+ ...(config.endpoint ? { endpoint: config.endpoint } : {}),
24
+ ...(auth ? { auth } : {}),
25
+ });
26
+ return { path: resolvePath(config.output, cwd), content };
27
+ }
28
+ catch (error) {
29
+ throw new Error(`Could not generate GraphQL client using schema ${resolveSchema(config.schema, cwd)} and documents ${resolvePath(config.documents, cwd)}: ${errorMessage(error)}`, { cause: error });
30
+ }
31
+ }
32
+ function errorMessage(error) {
33
+ return error instanceof Error ? error.message : String(error);
34
+ }
35
+ /** Generate configured OpenAPI and GraphQL clients into one TypeScript file per enabled section. */
36
+ export async function generate(config) {
37
+ validateCodegenConfig(config);
38
+ const cwd = process.cwd();
39
+ const generated = [];
40
+ if (config.openapi)
41
+ generated.push(await generateOpenapi(config.openapi, config.openapi.auth ?? config.auth, cwd));
42
+ if (config.graphql)
43
+ generated.push(await generateGraphql(config.graphql, config.graphql.auth ?? config.auth, cwd));
44
+ publishOutputs(generated);
45
+ return { outputs: generated.map(({ path }) => path) };
46
+ }
47
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/core/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare class Names {
2
+ private readonly used;
3
+ reserve(name: string, location: string): string;
4
+ }
5
+ export declare function comment(value: unknown): string;
@@ -0,0 +1,16 @@
1
+ export class Names {
2
+ used = new Map();
3
+ reserve(name, location) {
4
+ const previous = this.used.get(name);
5
+ if (previous !== undefined)
6
+ throw new Error(`Generated name "${name}" collides between ${previous} and ${location}. Rename one of these definitions.`);
7
+ this.used.set(name, location);
8
+ return name;
9
+ }
10
+ }
11
+ export function comment(value) {
12
+ if (typeof value !== 'string' || !value.trim())
13
+ return '';
14
+ return `/** ${value.replaceAll('*/', '* /').replace(/\r?\n/g, '\n * ')} */\n`;
15
+ }
16
+ //# sourceMappingURL=names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"names.js","sourceRoot":"","sources":["../../src/core/names.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,KAAK;IACC,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAElD,OAAO,CAAC,IAAY,EAAE,QAAgB;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,QAAQ,KAAK,SAAS;YACxB,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,sBAAsB,QAAQ,QAAQ,QAAQ,oCAAoC,CAAC,CAAC;QAC7H,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAC1D,OAAO,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC;AAChF,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface GeneratedOutput {
2
+ path: string;
3
+ content: string;
4
+ }
5
+ export declare function publishOutputs(outputs: GeneratedOutput[]): void;