@redocly/client-generator 0.0.0-snapshot.1786099785 → 0.0.0-snapshot.1786199735

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 (291) hide show
  1. package/README.md +17 -28
  2. package/eject-assets/AGENTS.md +123 -0
  3. package/eject-assets/generators/cli-docs.mjs +593 -0
  4. package/eject-assets/generators/cli.mjs +461 -0
  5. package/eject-assets/generators/go.mjs +831 -0
  6. package/eject-assets/generators/mock.mjs +689 -0
  7. package/eject-assets/generators/php.mjs +805 -0
  8. package/eject-assets/generators/python.mjs +670 -0
  9. package/eject-assets/generators/sdk.mjs +1615 -0
  10. package/eject-assets/generators/swr.mjs +267 -0
  11. package/eject-assets/generators/tanstack-query.mjs +632 -0
  12. package/eject-assets/generators/transformers.mjs +403 -0
  13. package/eject-assets/generators/zod.mjs +520 -0
  14. package/eject-assets/skills/cli-docs-generator/SKILL.md +58 -0
  15. package/eject-assets/skills/cli-generator/SKILL.md +68 -0
  16. package/eject-assets/skills/client-generators/SKILL.md +128 -0
  17. package/eject-assets/skills/go-generator/SKILL.md +81 -0
  18. package/eject-assets/skills/mock-generator/SKILL.md +45 -0
  19. package/eject-assets/skills/php-generator/SKILL.md +97 -0
  20. package/eject-assets/skills/python-generator/SKILL.md +70 -0
  21. package/eject-assets/skills/sdk-generator/SKILL.md +65 -0
  22. package/eject-assets/skills/swr-generator/SKILL.md +44 -0
  23. package/eject-assets/skills/tanstack-query-generator/SKILL.md +48 -0
  24. package/eject-assets/skills/transformers-generator/SKILL.md +43 -0
  25. package/eject-assets/skills/zod-generator/SKILL.md +49 -0
  26. package/lib/authoring/index.d.ts +9 -0
  27. package/lib/authoring/index.d.ts.map +1 -0
  28. package/lib/authoring/index.js +29 -0
  29. package/lib/authoring/index.js.map +1 -0
  30. package/lib/authoring/naming.d.ts +19 -0
  31. package/lib/authoring/naming.d.ts.map +1 -0
  32. package/lib/authoring/naming.js +80 -0
  33. package/lib/authoring/naming.js.map +1 -0
  34. package/lib/authoring/options.d.ts +9 -0
  35. package/lib/authoring/options.d.ts.map +1 -0
  36. package/lib/authoring/options.js +5 -0
  37. package/lib/authoring/options.js.map +1 -0
  38. package/lib/authoring/pagination.d.ts +18 -0
  39. package/lib/authoring/pagination.d.ts.map +1 -0
  40. package/lib/authoring/pagination.js +39 -0
  41. package/lib/authoring/pagination.js.map +1 -0
  42. package/lib/authoring/printer.d.ts +16 -0
  43. package/lib/authoring/printer.d.ts.map +1 -0
  44. package/lib/authoring/printer.js +36 -0
  45. package/lib/authoring/printer.js.map +1 -0
  46. package/lib/authoring/schema.d.ts +49 -0
  47. package/lib/authoring/schema.d.ts.map +1 -0
  48. package/lib/authoring/schema.js +185 -0
  49. package/lib/authoring/schema.js.map +1 -0
  50. package/lib/emitters/cli-docs.d.ts +17 -0
  51. package/lib/emitters/cli-docs.d.ts.map +1 -0
  52. package/lib/emitters/cli-docs.js +183 -0
  53. package/lib/emitters/cli-docs.js.map +1 -0
  54. package/lib/emitters/cli.d.ts +24 -0
  55. package/lib/emitters/cli.d.ts.map +1 -0
  56. package/lib/emitters/cli.js +151 -0
  57. package/lib/emitters/cli.js.map +1 -0
  58. package/lib/emitters/client-assembly.d.ts.map +1 -1
  59. package/lib/emitters/client-assembly.js +45 -161
  60. package/lib/emitters/client-assembly.js.map +1 -1
  61. package/lib/emitters/descriptor.d.ts +3 -12
  62. package/lib/emitters/descriptor.d.ts.map +1 -1
  63. package/lib/emitters/descriptor.js +32 -120
  64. package/lib/emitters/descriptor.js.map +1 -1
  65. package/lib/emitters/emit-options.d.ts +14 -1
  66. package/lib/emitters/emit-options.d.ts.map +1 -1
  67. package/lib/emitters/emit-options.js +1 -1
  68. package/lib/emitters/emit-options.js.map +1 -1
  69. package/lib/emitters/faker.d.ts +3 -3
  70. package/lib/emitters/faker.d.ts.map +1 -1
  71. package/lib/emitters/faker.js +66 -107
  72. package/lib/emitters/faker.js.map +1 -1
  73. package/lib/emitters/go-runtime-sources.d.ts +2 -0
  74. package/lib/emitters/go-runtime-sources.d.ts.map +1 -0
  75. package/lib/emitters/go-runtime-sources.js +3 -0
  76. package/lib/emitters/go-runtime-sources.js.map +1 -0
  77. package/lib/emitters/identifier.d.ts.map +1 -1
  78. package/lib/emitters/identifier.js +10 -0
  79. package/lib/emitters/identifier.js.map +1 -1
  80. package/lib/emitters/inline-runtime.d.ts +2 -0
  81. package/lib/emitters/inline-runtime.d.ts.map +1 -1
  82. package/lib/emitters/inline-runtime.js +14 -45
  83. package/lib/emitters/inline-runtime.js.map +1 -1
  84. package/lib/emitters/jsdoc.d.ts +2 -0
  85. package/lib/emitters/jsdoc.d.ts.map +1 -1
  86. package/lib/emitters/jsdoc.js +4 -1
  87. package/lib/emitters/jsdoc.js.map +1 -1
  88. package/lib/emitters/mock-value.d.ts +33 -0
  89. package/lib/emitters/mock-value.d.ts.map +1 -0
  90. package/lib/emitters/mock-value.js +47 -0
  91. package/lib/emitters/mock-value.js.map +1 -0
  92. package/lib/emitters/mock.d.ts.map +1 -1
  93. package/lib/emitters/mock.js +88 -123
  94. package/lib/emitters/mock.js.map +1 -1
  95. package/lib/emitters/operation-types.d.ts +1 -24
  96. package/lib/emitters/operation-types.d.ts.map +1 -1
  97. package/lib/emitters/operation-types.js +1 -107
  98. package/lib/emitters/operation-types.js.map +1 -1
  99. package/lib/emitters/operations.d.ts +2 -10
  100. package/lib/emitters/operations.d.ts.map +1 -1
  101. package/lib/emitters/operations.js +1 -34
  102. package/lib/emitters/operations.js.map +1 -1
  103. package/lib/emitters/pagination.d.ts +6 -13
  104. package/lib/emitters/pagination.d.ts.map +1 -1
  105. package/lib/emitters/pagination.js +11 -49
  106. package/lib/emitters/pagination.js.map +1 -1
  107. package/lib/emitters/php-runtime-sources.d.ts +2 -0
  108. package/lib/emitters/php-runtime-sources.d.ts.map +1 -0
  109. package/lib/emitters/php-runtime-sources.js +3 -0
  110. package/lib/emitters/php-runtime-sources.js.map +1 -0
  111. package/lib/emitters/python-runtime-sources.d.ts +12 -0
  112. package/lib/emitters/python-runtime-sources.d.ts.map +1 -0
  113. package/lib/emitters/python-runtime-sources.js +12 -0
  114. package/lib/emitters/python-runtime-sources.js.map +1 -0
  115. package/lib/emitters/render-client.d.ts +31 -0
  116. package/lib/emitters/render-client.d.ts.map +1 -0
  117. package/lib/emitters/render-client.js +338 -0
  118. package/lib/emitters/render-client.js.map +1 -0
  119. package/lib/emitters/reserved-names.d.ts +4 -1
  120. package/lib/emitters/reserved-names.d.ts.map +1 -1
  121. package/lib/emitters/reserved-names.js +16 -27
  122. package/lib/emitters/reserved-names.js.map +1 -1
  123. package/lib/emitters/response-headers.d.ts +5 -6
  124. package/lib/emitters/response-headers.d.ts.map +1 -1
  125. package/lib/emitters/response-headers.js +13 -63
  126. package/lib/emitters/response-headers.js.map +1 -1
  127. package/lib/emitters/runtime-sources.d.ts +20 -0
  128. package/lib/emitters/runtime-sources.d.ts.map +1 -1
  129. package/lib/emitters/runtime-sources.js +121 -0
  130. package/lib/emitters/runtime-sources.js.map +1 -1
  131. package/lib/emitters/setup-bake.d.ts.map +1 -1
  132. package/lib/emitters/setup-bake.js +10 -1
  133. package/lib/emitters/setup-bake.js.map +1 -1
  134. package/lib/emitters/sse.d.ts +3 -5
  135. package/lib/emitters/sse.d.ts.map +1 -1
  136. package/lib/emitters/sse.js +1 -11
  137. package/lib/emitters/sse.js.map +1 -1
  138. package/lib/emitters/swr.d.ts.map +1 -1
  139. package/lib/emitters/swr.js +34 -65
  140. package/lib/emitters/swr.js.map +1 -1
  141. package/lib/emitters/tanstack-query.d.ts.map +1 -1
  142. package/lib/emitters/tanstack-query.js +2 -4
  143. package/lib/emitters/tanstack-query.js.map +1 -1
  144. package/lib/emitters/transformers.d.ts.map +1 -1
  145. package/lib/emitters/transformers.js +123 -162
  146. package/lib/emitters/transformers.js.map +1 -1
  147. package/lib/emitters/ts-literal.d.ts +5 -0
  148. package/lib/emitters/ts-literal.d.ts.map +1 -0
  149. package/lib/emitters/ts-literal.js +35 -0
  150. package/lib/emitters/ts-literal.js.map +1 -0
  151. package/lib/emitters/ts-type.d.ts +9 -0
  152. package/lib/emitters/ts-type.d.ts.map +1 -0
  153. package/lib/emitters/ts-type.js +133 -0
  154. package/lib/emitters/ts-type.js.map +1 -0
  155. package/lib/emitters/type-guards.d.ts +2 -19
  156. package/lib/emitters/type-guards.d.ts.map +1 -1
  157. package/lib/emitters/type-guards.js +18 -52
  158. package/lib/emitters/type-guards.js.map +1 -1
  159. package/lib/emitters/types.d.ts +1 -14
  160. package/lib/emitters/types.d.ts.map +1 -1
  161. package/lib/emitters/types.js +4 -124
  162. package/lib/emitters/types.js.map +1 -1
  163. package/lib/emitters/wrapper-support.d.ts +10 -23
  164. package/lib/emitters/wrapper-support.d.ts.map +1 -1
  165. package/lib/emitters/wrapper-support.js +23 -53
  166. package/lib/emitters/wrapper-support.js.map +1 -1
  167. package/lib/emitters/zod.d.ts +2 -3
  168. package/lib/emitters/zod.d.ts.map +1 -1
  169. package/lib/emitters/zod.js +115 -148
  170. package/lib/emitters/zod.js.map +1 -1
  171. package/lib/generate.d.ts +4 -4
  172. package/lib/generate.d.ts.map +1 -1
  173. package/lib/generate.js +19 -103
  174. package/lib/generate.js.map +1 -1
  175. package/lib/generators/cli/index.d.ts +6 -0
  176. package/lib/generators/cli/index.d.ts.map +1 -0
  177. package/lib/generators/cli/index.js +48 -0
  178. package/lib/generators/cli/index.js.map +1 -0
  179. package/lib/generators/cli-docs/index.d.ts +10 -0
  180. package/lib/generators/cli-docs/index.d.ts.map +1 -0
  181. package/lib/generators/cli-docs/index.js +43 -0
  182. package/lib/generators/cli-docs/index.js.map +1 -0
  183. package/lib/generators/compatibility.d.ts +10 -0
  184. package/lib/generators/compatibility.d.ts.map +1 -0
  185. package/lib/generators/compatibility.js +45 -0
  186. package/lib/generators/compatibility.js.map +1 -0
  187. package/lib/generators/go/index.d.ts +12 -0
  188. package/lib/generators/go/index.d.ts.map +1 -0
  189. package/lib/generators/go/index.js +814 -0
  190. package/lib/generators/go/index.js.map +1 -0
  191. package/lib/generators/index.d.ts +2 -2
  192. package/lib/generators/index.d.ts.map +1 -1
  193. package/lib/generators/index.js +33 -53
  194. package/lib/generators/index.js.map +1 -1
  195. package/lib/generators/meta.d.ts +14 -0
  196. package/lib/generators/meta.d.ts.map +1 -0
  197. package/lib/generators/meta.js +161 -0
  198. package/lib/generators/meta.js.map +1 -0
  199. package/lib/generators/{mock.d.ts → mock/index.d.ts} +2 -2
  200. package/lib/generators/mock/index.d.ts.map +1 -0
  201. package/lib/generators/{mock.js → mock/index.js} +4 -4
  202. package/lib/generators/mock/index.js.map +1 -0
  203. package/lib/generators/options.d.ts +7 -0
  204. package/lib/generators/options.d.ts.map +1 -0
  205. package/lib/generators/options.js +74 -0
  206. package/lib/generators/options.js.map +1 -0
  207. package/lib/generators/php/index.d.ts +12 -0
  208. package/lib/generators/php/index.d.ts.map +1 -0
  209. package/lib/generators/php/index.js +788 -0
  210. package/lib/generators/php/index.js.map +1 -0
  211. package/lib/generators/python/index.d.ts +12 -0
  212. package/lib/generators/python/index.d.ts.map +1 -0
  213. package/lib/generators/python/index.js +654 -0
  214. package/lib/generators/python/index.js.map +1 -0
  215. package/lib/generators/resolve.d.ts.map +1 -1
  216. package/lib/generators/resolve.js +68 -11
  217. package/lib/generators/resolve.js.map +1 -1
  218. package/lib/generators/{sdk.d.ts → sdk/index.d.ts} +5 -2
  219. package/lib/generators/sdk/index.d.ts.map +1 -0
  220. package/lib/generators/sdk/index.js +53 -0
  221. package/lib/generators/sdk/index.js.map +1 -0
  222. package/lib/generators/{swr.d.ts → swr/index.d.ts} +2 -2
  223. package/lib/generators/swr/index.d.ts.map +1 -0
  224. package/lib/generators/{swr.js → swr/index.js} +4 -4
  225. package/lib/generators/swr/index.js.map +1 -0
  226. package/lib/generators/{tanstack-query.d.ts → tanstack-query/index.d.ts} +2 -2
  227. package/lib/generators/tanstack-query/index.d.ts.map +1 -0
  228. package/lib/generators/{tanstack-query.js → tanstack-query/index.js} +4 -4
  229. package/lib/generators/{tanstack-query.js.map → tanstack-query/index.js.map} +1 -1
  230. package/lib/generators/{transformers.d.ts → transformers/index.d.ts} +2 -2
  231. package/lib/generators/transformers/index.d.ts.map +1 -0
  232. package/lib/generators/{transformers.js → transformers/index.js} +4 -4
  233. package/lib/generators/transformers/index.js.map +1 -0
  234. package/lib/generators/types.d.ts +66 -2
  235. package/lib/generators/types.d.ts.map +1 -1
  236. package/lib/generators/{zod.d.ts → zod/index.d.ts} +2 -2
  237. package/lib/generators/zod/index.d.ts.map +1 -0
  238. package/lib/generators/{zod.js → zod/index.js} +4 -4
  239. package/lib/generators/zod/index.js.map +1 -0
  240. package/lib/index.d.ts +3 -0
  241. package/lib/index.d.ts.map +1 -1
  242. package/lib/index.js +7 -2
  243. package/lib/index.js.map +1 -1
  244. package/lib/intermediate-representation/build.d.ts.map +1 -1
  245. package/lib/intermediate-representation/build.js +30 -2
  246. package/lib/intermediate-representation/build.js.map +1 -1
  247. package/lib/intermediate-representation/model.d.ts +13 -1
  248. package/lib/intermediate-representation/model.d.ts.map +1 -1
  249. package/lib/intermediate-representation/model.js.map +1 -1
  250. package/lib/intermediate-representation/sanitize-identifiers.d.ts.map +1 -1
  251. package/lib/intermediate-representation/sanitize-identifiers.js +33 -6
  252. package/lib/intermediate-representation/sanitize-identifiers.js.map +1 -1
  253. package/lib/pipeline.d.ts +20 -0
  254. package/lib/pipeline.d.ts.map +1 -0
  255. package/lib/pipeline.js +180 -0
  256. package/lib/pipeline.js.map +1 -0
  257. package/lib/plugin.d.ts +1 -0
  258. package/lib/plugin.d.ts.map +1 -1
  259. package/lib/plugin.js +6 -5
  260. package/lib/plugin.js.map +1 -1
  261. package/lib/runtime/cli.d.ts +102 -0
  262. package/lib/runtime/cli.d.ts.map +1 -0
  263. package/lib/runtime/cli.js +450 -0
  264. package/lib/runtime/cli.js.map +1 -0
  265. package/lib/runtime-sources.d.ts +4 -0
  266. package/lib/runtime-sources.d.ts.map +1 -0
  267. package/lib/runtime-sources.js +9 -0
  268. package/lib/runtime-sources.js.map +1 -0
  269. package/lib/types.d.ts +17 -1
  270. package/lib/types.d.ts.map +1 -1
  271. package/package.json +10 -4
  272. package/lib/emitters/operation-aliases.d.ts +0 -27
  273. package/lib/emitters/operation-aliases.d.ts.map +0 -1
  274. package/lib/emitters/operation-aliases.js +0 -150
  275. package/lib/emitters/operation-aliases.js.map +0 -1
  276. package/lib/emitters/ts.d.ts +0 -42
  277. package/lib/emitters/ts.d.ts.map +0 -1
  278. package/lib/emitters/ts.js +0 -116
  279. package/lib/emitters/ts.js.map +0 -1
  280. package/lib/generators/mock.d.ts.map +0 -1
  281. package/lib/generators/mock.js.map +0 -1
  282. package/lib/generators/sdk.d.ts.map +0 -1
  283. package/lib/generators/sdk.js +0 -26
  284. package/lib/generators/sdk.js.map +0 -1
  285. package/lib/generators/swr.d.ts.map +0 -1
  286. package/lib/generators/swr.js.map +0 -1
  287. package/lib/generators/tanstack-query.d.ts.map +0 -1
  288. package/lib/generators/transformers.d.ts.map +0 -1
  289. package/lib/generators/transformers.js.map +0 -1
  290. package/lib/generators/zod.d.ts.map +0 -1
  291. package/lib/generators/zod.js.map +0 -1
package/README.md CHANGED
@@ -55,43 +55,32 @@ With `runtime: 'package'` the generated client also imports its whole engine fro
55
55
  ### Write a custom generator
56
56
 
57
57
  A custom generator reads the same API model the built-ins consume, runs in the same pass, and returns files.
58
- Build real TypeScript with the emit toolkit from `@redocly/client-generator/generate` — the same `ts.factory` + printer the built-in generators use, so the schema→type mapping matches the sdk's exactly:
58
+ Emitters print text: `Printer` handles indentation, and `tsType` is the same schema→type renderer the built-in sdk uses, so the mapping (refs, arrays, unions, formats, parenthesization) matches the generated client exactly:
59
59
 
60
60
  ```ts
61
61
  // response-map-generator.ts
62
- import { defineGenerator } from '@redocly/client-generator';
63
- import { printStatements, schemaToTypeNode, ts } from '@redocly/client-generator/generate';
64
-
65
- const { factory } = ts;
62
+ import { defineGenerator, Printer } from '@redocly/client-generator';
63
+ import { tsType } from '@redocly/client-generator/generate';
66
64
 
67
65
  export default defineGenerator({
68
66
  name: 'response-map',
69
67
  requires: ['sdk'],
70
68
  run({ model, outputPath }) {
69
+ const printer = new Printer();
71
70
  // One `ResponseShapes` entry per operation with a JSON success body.
72
- const members = model.services
73
- .flatMap((service) => service.operations)
74
- .flatMap((op) => {
75
- const success = op.successResponses.find((r) => r.contentType.includes('json'));
76
- if (!success) return [];
77
- return [
78
- factory.createPropertySignature(
79
- undefined,
80
- op.name,
81
- undefined,
82
- schemaToTypeNode(success.schema)
83
- ),
84
- ];
85
- });
86
- const alias = factory.createTypeAliasDeclaration(
87
- [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
88
- 'ResponseShapes',
89
- undefined,
90
- factory.createTypeLiteralNode(members)
71
+ printer.block(
72
+ 'export type ResponseShapes = {',
73
+ () => {
74
+ for (const service of model.services) {
75
+ for (const op of service.operations) {
76
+ const success = op.successResponses.find((r) => r.contentType.includes('json'));
77
+ if (success) printer.line(`${op.name}: ${tsType(success.schema)};`);
78
+ }
79
+ }
80
+ },
81
+ '};'
91
82
  );
92
- return [
93
- { path: outputPath.replace(/\.ts$/, '.responses.ts'), content: printStatements([alias]) },
94
- ];
83
+ return [{ path: outputPath.replace(/\.ts$/, '.responses.ts'), content: printer.toString() }];
95
84
  },
96
85
  });
97
86
  ```
@@ -150,7 +139,7 @@ Authors a custom generator (`{ name, run }` plus optional `requires`/`errorModes
150
139
  function defineGenerator(generator: CustomGenerator): CustomGenerator;
151
140
  ```
152
141
 
153
- The `@redocly/client-generator/generate` entry also exports the emit toolkit the built-ins use (`ts`, `printStatements`, `parseStatements`, `operationSignature`, `schemaToTypeNode`, `pascalCase`, …), and the package root exports the IR types, so a custom generator emits TypeScript exactly as the first-party ones do — see the [`ast-toolkit-generator` example](https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/ast-toolkit-generator).
142
+ The `@redocly/client-generator/generate` entry also exports the TypeScript renderers the built-ins use (`tsType`, `tsJsdoc`, `codeLiteral`, `operationSignature`, `pascalCase`, `safeIdent`), and the package root exports the IR types plus the language-neutral toolkit, so a custom generator emits TypeScript exactly as the first-party ones do — see the [`typescript-types-generator` example](https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/typescript-types-generator).
154
143
 
155
144
  ### `defineClientSetup`
156
145
 
@@ -0,0 +1,123 @@
1
+ # Writing custom client generators
2
+
3
+ A generator is a plain module: `(input) => GeneratedFile[]`. It receives the
4
+ language-agnostic API model and returns files — in ANY output language. It runs
5
+ in the same pass as the built-ins; select it by path in `redocly.yaml`:
6
+
7
+ ```yaml
8
+ client:
9
+ generators: [sdk, ./generators/my-generator.mjs]
10
+ ```
11
+
12
+ ## The contract
13
+
14
+ ```js
15
+ /** @type {import('@redocly/client-generator').CustomGenerator} */
16
+ export default {
17
+ name: 'my-generator',
18
+ run({ model, outputPath, outputMode, emit }) {
19
+ return [{ path: outputPath.replace(/\.ts$/, '.mine.txt'), content: '…' }];
20
+ },
21
+ // Optional: one idiomatic call snippet per operation for docs (x-codeSamples),
22
+ // collected into an overlay file when `client.codeSamples: true` is set.
23
+ sample(operation, { model, emit }) {
24
+ return { lang: 'python', source: '…' };
25
+ },
26
+ };
27
+ ```
28
+
29
+ ## Declaring options
30
+
31
+ A generator that needs configuration declares it as a schema; `run` then receives
32
+ `options` already validated, with defaults applied:
33
+
34
+ ```js
35
+ export default {
36
+ name: 'permissions-matrix',
37
+ options: {
38
+ type: 'object',
39
+ properties: { groupBy: { enum: ['tag', 'path'], default: 'tag' } },
40
+ additionalProperties: false,
41
+ },
42
+ run({ model, outputPath, options }) {
43
+ return [
44
+ { path: outputPath.replace(/\.ts$/, '.permissions.md'), content: render(options.groupBy) },
45
+ ];
46
+ },
47
+ };
48
+ ```
49
+
50
+ Users set them per generator name:
51
+
52
+ ```yaml
53
+ client:
54
+ generators: [sdk, ./generators/permissions-matrix.mjs]
55
+ options:
56
+ permissions-matrix:
57
+ groupBy: path
58
+ ```
59
+
60
+ The supported subset is a top-level `type: 'object'` with `properties`, `required`, and
61
+ `additionalProperties`; each property is a scalar (`string`/`number`/`boolean`), an
62
+ `enum`, or an array of scalars, and may carry a `default` and a `description`. Don't
63
+ validate options inside `run` — an unknown key, a wrong type, a value outside an `enum`,
64
+ or a missing `required` key already fails generation before `run` is called.
65
+
66
+ Rules: output is deterministic (same description → same bytes); never add
67
+ dependencies to the generated client; **never hand-edit generated output** —
68
+ edit this generator and regenerate. Emitted file paths must stay inside the
69
+ `--output` directory (subdirectories are fine) — escapes are rejected.
70
+ Optionally declare `requiresGenerator` — the `@redocly/client-generator` version
71
+ range you wrote this against (`'^1.2.0'`, `'~1.2.0'`, `'>=1.2.0'`, or an exact
72
+ version). A CLI outside the range then fails with the fix path instead of feeding
73
+ your generator an unexpected model shape. Ejected generators carry it
74
+ automatically; hand-written ones without it are taken as current.
75
+
76
+ ## The model (IR)
77
+
78
+ `model.services[].operations[]` — each operation carries `name`, `specName`,
79
+ `method`, `path`, `tags`, `pathParams`/`queryParams`/`headerParams`/`cookieParams`,
80
+ `requestBody`, `successResponses`/`errorResponses` (each with a `schema`), and
81
+ `security`. `model.schemas` holds the named schemas. Every schema is a
82
+ discriminated union on `kind`: `scalar`, `array`, `object`, `record`, `ref`,
83
+ `literal`, `enum`, `union` (optionally with a discriminator), `intersection`
84
+ (allOf), `null`, `unknown`, `omit`.
85
+
86
+ ## Helpers (import from '@redocly/client-generator')
87
+
88
+ | Helper | Use |
89
+ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
90
+ | `flattenAllOf(schema, model)` | The merged property view of allOf compositions — languages without intersection types render this. |
91
+ | `discriminatorCases(schema, model)` | `{ property, cases }` dispatch table for discriminated unions. |
92
+ | `isNullable(schema)` / `unwrapNullable(schema)` | Detect and strip `null` union members (`Optional[T]`, pointers, `Option<T>`). |
93
+ | `enumValues(schema)` | Values plus SCREAMING_SNAKE member-name suggestions. |
94
+ | `headerCoerceType(schema, model)` | Response-header coerce hint (`integer`/`number`/`boolean`/`string`) through refs, nullables, and allOf wrappers. |
95
+ | `casing` / `identifierFor(name, { style, reserved })` | camel/pascal/snake/screaming; keyword-safe identifiers (`RESERVED_WORDS.python/go/typescript` shipped). |
96
+ | `Printer` | Indentation-aware text builder — no manual whitespace bookkeeping. |
97
+ | `docText(description)` | Description as trimmed lines for any comment syntax. |
98
+ | `schemaAtPointer(schema, pointer, model)` | Resolve an RFC 6901 JSON pointer over a schema (through refs and allOf) — e.g. a pagination `items` pointer to its element type. |
99
+ | `paginationRuleFor(op, config)` | The pagination rule that applies to an operation (per-op config > extension > fitting convention), normalized. |
100
+ | `NotSupportedError` | Throw it to reject an option the generator can't honor — the CLI prints the message as a user error, not a crash. |
101
+ | `AUTHORING_HELPER_NAMES` | The list of the above (introspection). |
102
+
103
+ Worked example: the built-in `python` generator
104
+ (`packages/client-generator/src/generators/python/index.ts` in the Redocly CLI repo) is
105
+ authored with exactly this toolkit and nothing else — models via `flattenAllOf`/
106
+ `enumValues`/`discriminatorCases`, all code through `Printer`, every name through
107
+ `identifierFor(..., RESERVED_WORDS.python)`.
108
+
109
+ A generator that emits TypeScript may additionally use the source-text renderers from
110
+ `@redocly/client-generator/generate` — `tsType` (schema → type), `tsJsdoc`, `codeLiteral`,
111
+ `operationSignature`, `pascalCase`, `safeIdent`. There is no AST toolkit and no
112
+ `typescript` dependency: every generator prints text through `Printer`.
113
+
114
+ ## The loop
115
+
116
+ 1. Edit the generator.
117
+ 2. Run `redocly generate-client`.
118
+ 3. Inspect `git diff` of the generated output.
119
+ 4. Repeat. Generated files are never hand-edited.
120
+
121
+ If you had to work around a **missing helper** or a wrong default, tell the user
122
+ and suggest filing an issue at https://github.com/Redocly/redocly-cli/issues —
123
+ include the generator snippet and the helper you expected to exist.