@redocly/client-generator 0.0.0-snapshot.1784554531

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 (250) hide show
  1. package/README.md +506 -0
  2. package/lib/config-file.d.ts +8 -0
  3. package/lib/config-file.d.ts.map +1 -0
  4. package/lib/config-file.js +32 -0
  5. package/lib/config-file.js.map +1 -0
  6. package/lib/config.d.ts +63 -0
  7. package/lib/config.d.ts.map +1 -0
  8. package/lib/config.js +2 -0
  9. package/lib/config.js.map +1 -0
  10. package/lib/emitters/auth.d.ts +14 -0
  11. package/lib/emitters/auth.d.ts.map +1 -0
  12. package/lib/emitters/auth.js +30 -0
  13. package/lib/emitters/auth.js.map +1 -0
  14. package/lib/emitters/client-assembly.d.ts +15 -0
  15. package/lib/emitters/client-assembly.d.ts.map +1 -0
  16. package/lib/emitters/client-assembly.js +299 -0
  17. package/lib/emitters/client-assembly.js.map +1 -0
  18. package/lib/emitters/descriptor.d.ts +22 -0
  19. package/lib/emitters/descriptor.d.ts.map +1 -0
  20. package/lib/emitters/descriptor.js +207 -0
  21. package/lib/emitters/descriptor.js.map +1 -0
  22. package/lib/emitters/emit-options.d.ts +71 -0
  23. package/lib/emitters/emit-options.d.ts.map +1 -0
  24. package/lib/emitters/emit-options.js +28 -0
  25. package/lib/emitters/emit-options.js.map +1 -0
  26. package/lib/emitters/faker.d.ts +11 -0
  27. package/lib/emitters/faker.d.ts.map +1 -0
  28. package/lib/emitters/faker.js +204 -0
  29. package/lib/emitters/faker.js.map +1 -0
  30. package/lib/emitters/identifier.d.ts +28 -0
  31. package/lib/emitters/identifier.d.ts.map +1 -0
  32. package/lib/emitters/identifier.js +94 -0
  33. package/lib/emitters/identifier.js.map +1 -0
  34. package/lib/emitters/inline-runtime.d.ts +11 -0
  35. package/lib/emitters/inline-runtime.d.ts.map +1 -0
  36. package/lib/emitters/inline-runtime.js +97 -0
  37. package/lib/emitters/inline-runtime.js.map +1 -0
  38. package/lib/emitters/jsdoc.d.ts +9 -0
  39. package/lib/emitters/jsdoc.d.ts.map +1 -0
  40. package/lib/emitters/jsdoc.js +83 -0
  41. package/lib/emitters/jsdoc.js.map +1 -0
  42. package/lib/emitters/mock.d.ts +22 -0
  43. package/lib/emitters/mock.d.ts.map +1 -0
  44. package/lib/emitters/mock.js +243 -0
  45. package/lib/emitters/mock.js.map +1 -0
  46. package/lib/emitters/operation-aliases.d.ts +27 -0
  47. package/lib/emitters/operation-aliases.d.ts.map +1 -0
  48. package/lib/emitters/operation-aliases.js +138 -0
  49. package/lib/emitters/operation-aliases.js.map +1 -0
  50. package/lib/emitters/operation-signature.d.ts +23 -0
  51. package/lib/emitters/operation-signature.d.ts.map +1 -0
  52. package/lib/emitters/operation-signature.js +34 -0
  53. package/lib/emitters/operation-signature.js.map +1 -0
  54. package/lib/emitters/operation-types.d.ts +32 -0
  55. package/lib/emitters/operation-types.d.ts.map +1 -0
  56. package/lib/emitters/operation-types.js +117 -0
  57. package/lib/emitters/operation-types.js.map +1 -0
  58. package/lib/emitters/operations.d.ts +39 -0
  59. package/lib/emitters/operations.d.ts.map +1 -0
  60. package/lib/emitters/operations.js +33 -0
  61. package/lib/emitters/operations.js.map +1 -0
  62. package/lib/emitters/pagination.d.ts +69 -0
  63. package/lib/emitters/pagination.d.ts.map +1 -0
  64. package/lib/emitters/pagination.js +223 -0
  65. package/lib/emitters/pagination.js.map +1 -0
  66. package/lib/emitters/runtime-sources.d.ts +16 -0
  67. package/lib/emitters/runtime-sources.d.ts.map +1 -0
  68. package/lib/emitters/runtime-sources.js +16 -0
  69. package/lib/emitters/runtime-sources.js.map +1 -0
  70. package/lib/emitters/sample.d.ts +27 -0
  71. package/lib/emitters/sample.d.ts.map +1 -0
  72. package/lib/emitters/sample.js +161 -0
  73. package/lib/emitters/sample.js.map +1 -0
  74. package/lib/emitters/setup-bake.d.ts +15 -0
  75. package/lib/emitters/setup-bake.d.ts.map +1 -0
  76. package/lib/emitters/setup-bake.js +65 -0
  77. package/lib/emitters/setup-bake.js.map +1 -0
  78. package/lib/emitters/sse.d.ts +10 -0
  79. package/lib/emitters/sse.d.ts.map +1 -0
  80. package/lib/emitters/sse.js +46 -0
  81. package/lib/emitters/sse.js.map +1 -0
  82. package/lib/emitters/support.d.ts +13 -0
  83. package/lib/emitters/support.d.ts.map +1 -0
  84. package/lib/emitters/support.js +21 -0
  85. package/lib/emitters/support.js.map +1 -0
  86. package/lib/emitters/swr.d.ts +10 -0
  87. package/lib/emitters/swr.d.ts.map +1 -0
  88. package/lib/emitters/swr.js +88 -0
  89. package/lib/emitters/swr.js.map +1 -0
  90. package/lib/emitters/tanstack-query.d.ts +11 -0
  91. package/lib/emitters/tanstack-query.d.ts.map +1 -0
  92. package/lib/emitters/tanstack-query.js +68 -0
  93. package/lib/emitters/tanstack-query.js.map +1 -0
  94. package/lib/emitters/transformers.d.ts +11 -0
  95. package/lib/emitters/transformers.d.ts.map +1 -0
  96. package/lib/emitters/transformers.js +334 -0
  97. package/lib/emitters/transformers.js.map +1 -0
  98. package/lib/emitters/ts.d.ts +40 -0
  99. package/lib/emitters/ts.d.ts.map +1 -0
  100. package/lib/emitters/ts.js +104 -0
  101. package/lib/emitters/ts.js.map +1 -0
  102. package/lib/emitters/type-guards.d.ts +21 -0
  103. package/lib/emitters/type-guards.d.ts.map +1 -0
  104. package/lib/emitters/type-guards.js +183 -0
  105. package/lib/emitters/type-guards.js.map +1 -0
  106. package/lib/emitters/types.d.ts +23 -0
  107. package/lib/emitters/types.d.ts.map +1 -0
  108. package/lib/emitters/types.js +127 -0
  109. package/lib/emitters/types.js.map +1 -0
  110. package/lib/emitters/wrapper-support.d.ts +40 -0
  111. package/lib/emitters/wrapper-support.d.ts.map +1 -0
  112. package/lib/emitters/wrapper-support.js +112 -0
  113. package/lib/emitters/wrapper-support.js.map +1 -0
  114. package/lib/emitters/zod.d.ts +13 -0
  115. package/lib/emitters/zod.d.ts.map +1 -0
  116. package/lib/emitters/zod.js +344 -0
  117. package/lib/emitters/zod.js.map +1 -0
  118. package/lib/errors.d.ts +4 -0
  119. package/lib/errors.d.ts.map +1 -0
  120. package/lib/errors.js +7 -0
  121. package/lib/errors.js.map +1 -0
  122. package/lib/generators/anchor.d.ts +9 -0
  123. package/lib/generators/anchor.d.ts.map +1 -0
  124. package/lib/generators/anchor.js +10 -0
  125. package/lib/generators/anchor.js.map +1 -0
  126. package/lib/generators/index.d.ts +15 -0
  127. package/lib/generators/index.d.ts.map +1 -0
  128. package/lib/generators/index.js +62 -0
  129. package/lib/generators/index.js.map +1 -0
  130. package/lib/generators/mock.d.ts +9 -0
  131. package/lib/generators/mock.d.ts.map +1 -0
  132. package/lib/generators/mock.js +23 -0
  133. package/lib/generators/mock.js.map +1 -0
  134. package/lib/generators/resolve.d.ts +19 -0
  135. package/lib/generators/resolve.d.ts.map +1 -0
  136. package/lib/generators/resolve.js +68 -0
  137. package/lib/generators/resolve.js.map +1 -0
  138. package/lib/generators/sdk.d.ts +12 -0
  139. package/lib/generators/sdk.d.ts.map +1 -0
  140. package/lib/generators/sdk.js +26 -0
  141. package/lib/generators/sdk.js.map +1 -0
  142. package/lib/generators/swr.d.ts +15 -0
  143. package/lib/generators/swr.d.ts.map +1 -0
  144. package/lib/generators/swr.js +27 -0
  145. package/lib/generators/swr.js.map +1 -0
  146. package/lib/generators/tanstack-query.d.ts +17 -0
  147. package/lib/generators/tanstack-query.d.ts.map +1 -0
  148. package/lib/generators/tanstack-query.js +30 -0
  149. package/lib/generators/tanstack-query.js.map +1 -0
  150. package/lib/generators/transformers.d.ts +19 -0
  151. package/lib/generators/transformers.d.ts.map +1 -0
  152. package/lib/generators/transformers.js +28 -0
  153. package/lib/generators/transformers.js.map +1 -0
  154. package/lib/generators/types.d.ts +66 -0
  155. package/lib/generators/types.d.ts.map +1 -0
  156. package/lib/generators/types.js +2 -0
  157. package/lib/generators/types.js.map +1 -0
  158. package/lib/generators/zod.d.ts +14 -0
  159. package/lib/generators/zod.d.ts.map +1 -0
  160. package/lib/generators/zod.js +23 -0
  161. package/lib/generators/zod.js.map +1 -0
  162. package/lib/index.d.ts +29 -0
  163. package/lib/index.d.ts.map +1 -0
  164. package/lib/index.js +119 -0
  165. package/lib/index.js.map +1 -0
  166. package/lib/intermediate-representation/build.d.ts +4 -0
  167. package/lib/intermediate-representation/build.d.ts.map +1 -0
  168. package/lib/intermediate-representation/build.js +761 -0
  169. package/lib/intermediate-representation/build.js.map +1 -0
  170. package/lib/intermediate-representation/model.d.ts +275 -0
  171. package/lib/intermediate-representation/model.d.ts.map +1 -0
  172. package/lib/intermediate-representation/model.js +5 -0
  173. package/lib/intermediate-representation/model.js.map +1 -0
  174. package/lib/intermediate-representation/normalize-swagger2.d.ts +10 -0
  175. package/lib/intermediate-representation/normalize-swagger2.d.ts.map +1 -0
  176. package/lib/intermediate-representation/normalize-swagger2.js +210 -0
  177. package/lib/intermediate-representation/normalize-swagger2.js.map +1 -0
  178. package/lib/intermediate-representation/sanitize-identifiers.d.ts +30 -0
  179. package/lib/intermediate-representation/sanitize-identifiers.d.ts.map +1 -0
  180. package/lib/intermediate-representation/sanitize-identifiers.js +153 -0
  181. package/lib/intermediate-representation/sanitize-identifiers.js.map +1 -0
  182. package/lib/loader.d.ts +4 -0
  183. package/lib/loader.d.ts.map +1 -0
  184. package/lib/loader.js +14 -0
  185. package/lib/loader.js.map +1 -0
  186. package/lib/plugin.d.ts +19 -0
  187. package/lib/plugin.d.ts.map +1 -0
  188. package/lib/plugin.js +41 -0
  189. package/lib/plugin.js.map +1 -0
  190. package/lib/runtime/auth.d.ts +15 -0
  191. package/lib/runtime/auth.d.ts.map +1 -0
  192. package/lib/runtime/auth.js +67 -0
  193. package/lib/runtime/auth.js.map +1 -0
  194. package/lib/runtime/create-client.d.ts +35 -0
  195. package/lib/runtime/create-client.d.ts.map +1 -0
  196. package/lib/runtime/create-client.js +215 -0
  197. package/lib/runtime/create-client.js.map +1 -0
  198. package/lib/runtime/errors.d.ts +11 -0
  199. package/lib/runtime/errors.d.ts.map +1 -0
  200. package/lib/runtime/errors.js +25 -0
  201. package/lib/runtime/errors.js.map +1 -0
  202. package/lib/runtime/index.d.ts +14 -0
  203. package/lib/runtime/index.d.ts.map +1 -0
  204. package/lib/runtime/index.js +24 -0
  205. package/lib/runtime/index.js.map +1 -0
  206. package/lib/runtime/multipart.d.ts +9 -0
  207. package/lib/runtime/multipart.d.ts.map +1 -0
  208. package/lib/runtime/multipart.js +31 -0
  209. package/lib/runtime/multipart.js.map +1 -0
  210. package/lib/runtime/paginate.d.ts +36 -0
  211. package/lib/runtime/paginate.d.ts.map +1 -0
  212. package/lib/runtime/paginate.js +99 -0
  213. package/lib/runtime/paginate.js.map +1 -0
  214. package/lib/runtime/parse.d.ts +9 -0
  215. package/lib/runtime/parse.d.ts.map +1 -0
  216. package/lib/runtime/parse.js +37 -0
  217. package/lib/runtime/parse.js.map +1 -0
  218. package/lib/runtime/retry.d.ts +15 -0
  219. package/lib/runtime/retry.d.ts.map +1 -0
  220. package/lib/runtime/retry.js +51 -0
  221. package/lib/runtime/retry.js.map +1 -0
  222. package/lib/runtime/send.d.ts +31 -0
  223. package/lib/runtime/send.d.ts.map +1 -0
  224. package/lib/runtime/send.js +138 -0
  225. package/lib/runtime/send.js.map +1 -0
  226. package/lib/runtime/setup.d.ts +11 -0
  227. package/lib/runtime/setup.d.ts.map +1 -0
  228. package/lib/runtime/setup.js +13 -0
  229. package/lib/runtime/setup.js.map +1 -0
  230. package/lib/runtime/sse.d.ts +21 -0
  231. package/lib/runtime/sse.d.ts.map +1 -0
  232. package/lib/runtime/sse.js +163 -0
  233. package/lib/runtime/sse.js.map +1 -0
  234. package/lib/runtime/types.d.ts +232 -0
  235. package/lib/runtime/types.d.ts.map +1 -0
  236. package/lib/runtime/types.js +9 -0
  237. package/lib/runtime/types.js.map +1 -0
  238. package/lib/runtime/url.d.ts +25 -0
  239. package/lib/runtime/url.d.ts.map +1 -0
  240. package/lib/runtime/url.js +97 -0
  241. package/lib/runtime/url.js.map +1 -0
  242. package/lib/runtime-contract.d.ts +27 -0
  243. package/lib/runtime-contract.d.ts.map +1 -0
  244. package/lib/runtime-contract.js +14 -0
  245. package/lib/runtime-contract.js.map +1 -0
  246. package/lib/types.d.ts +118 -0
  247. package/lib/types.d.ts.map +1 -0
  248. package/lib/types.js +2 -0
  249. package/lib/types.js.map +1 -0
  250. package/package.json +59 -0
@@ -0,0 +1,761 @@
1
+ import { isPlainObject, isRef, logger, } from '@redocly/openapi-core';
2
+ import { NotSupportedError } from '../errors.js';
3
+ import { assertSafeIdentifiers, sanitizeIdentifiers } from './sanitize-identifiers.js';
4
+ const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
5
+ function resolveRef(doc, ref) {
6
+ if (!ref.startsWith('#/')) {
7
+ throw new NotSupportedError(`External $ref not supported: ${ref}`);
8
+ }
9
+ const segments = ref
10
+ .slice(2)
11
+ .split('/')
12
+ .map((s) => s.replace(/~1/g, '/').replace(/~0/g, '~'));
13
+ let current = doc;
14
+ for (const segment of segments) {
15
+ if (!isPlainObject(current) && !Array.isArray(current)) {
16
+ throw new NotSupportedError(`Cannot resolve $ref: ${ref}`);
17
+ }
18
+ current = current[segment];
19
+ }
20
+ if (current === undefined) {
21
+ throw new NotSupportedError(`Cannot resolve $ref: ${ref}`);
22
+ }
23
+ return current;
24
+ }
25
+ function deref(doc, value) {
26
+ if (isRef(value)) {
27
+ return resolveRef(doc, value.$ref);
28
+ }
29
+ return value;
30
+ }
31
+ function refName(ref) {
32
+ const idx = ref.lastIndexOf('/');
33
+ return idx >= 0 ? ref.slice(idx + 1) : ref;
34
+ }
35
+ /**
36
+ * Lift validation / annotation keywords off an OAS Schema Object into our
37
+ * neutral SchemaMetadata bag.
38
+ *
39
+ * Normalizes OAS 3.0 boolean `exclusiveMinimum` / `exclusiveMaximum` into the
40
+ * OAS 3.1 numeric form so downstream consumers (the emitter, in particular)
41
+ * have exactly one shape to handle.
42
+ *
43
+ * Returns `undefined` when nothing of interest is present. This keeps the IR
44
+ * uncluttered: schemas without constraints simply have no `metadata` field at
45
+ * all rather than an empty object, which also keeps strict-equality tests
46
+ * stable.
47
+ */
48
+ function extractMetadata(schema) {
49
+ // We index into the schema with looser typing than the official @redocly types
50
+ // because OAS 3.1 keywords (numeric `exclusiveMinimum/Maximum`) aren't in the
51
+ // 3.0-focused type, and `format`/`deprecated` live in a few different places.
52
+ const s = schema;
53
+ const out = {};
54
+ if (typeof s.minimum === 'number')
55
+ out.minimum = s.minimum;
56
+ if (typeof s.maximum === 'number')
57
+ out.maximum = s.maximum;
58
+ // OAS 3.0: exclusiveMinimum/Maximum are booleans paired with minimum/maximum.
59
+ // OAS 3.1: they are numbers and stand alone.
60
+ // We always emit the 3.1 numeric form. When the 3.0 boolean form is used,
61
+ // hoist the paired bound up and drop the inclusive form.
62
+ if (typeof s.exclusiveMinimum === 'number') {
63
+ out.exclusiveMinimum = s.exclusiveMinimum;
64
+ }
65
+ else if (s.exclusiveMinimum === true && typeof s.minimum === 'number') {
66
+ out.exclusiveMinimum = s.minimum;
67
+ delete out.minimum;
68
+ }
69
+ if (typeof s.exclusiveMaximum === 'number') {
70
+ out.exclusiveMaximum = s.exclusiveMaximum;
71
+ }
72
+ else if (s.exclusiveMaximum === true && typeof s.maximum === 'number') {
73
+ out.exclusiveMaximum = s.maximum;
74
+ delete out.maximum;
75
+ }
76
+ if (typeof s.minLength === 'number')
77
+ out.minLength = s.minLength;
78
+ if (typeof s.maxLength === 'number')
79
+ out.maxLength = s.maxLength;
80
+ if (typeof s.pattern === 'string')
81
+ out.pattern = s.pattern;
82
+ if (typeof s.minItems === 'number')
83
+ out.minItems = s.minItems;
84
+ if (typeof s.maxItems === 'number')
85
+ out.maxItems = s.maxItems;
86
+ // `uniqueItems` defaults to false; only the affirmative case carries info.
87
+ if (s.uniqueItems === true)
88
+ out.uniqueItems = true;
89
+ if (typeof s.format === 'string')
90
+ out.format = s.format;
91
+ // Same idea as uniqueItems — explicit `false` is the default; don't emit it.
92
+ if (s.deprecated === true)
93
+ out.deprecated = true;
94
+ if (s.example !== undefined) {
95
+ out.example = s.example;
96
+ }
97
+ else if (Array.isArray(s.examples) && s.examples.length > 0) {
98
+ out.example = s.examples[0];
99
+ }
100
+ if (s.default !== undefined)
101
+ out.default = s.default;
102
+ return Object.keys(out).length > 0 ? out : undefined;
103
+ }
104
+ /**
105
+ * Build discriminator metadata for a `oneOf` / `anyOf` union, when the schema
106
+ * declares an explicit `discriminator`. With an explicit `mapping`, each entry
107
+ * pairs the discriminant value with its target schema name. Without a mapping,
108
+ * the OpenAPI spec says the discriminant value equals the referenced schema's
109
+ * name, so we derive entries from the `$ref` members.
110
+ *
111
+ * Returns `undefined` when there's no usable discriminator (no `discriminator`
112
+ * block, or one that yields no named targets) — the emitter then skips guards.
113
+ */
114
+ function buildDiscriminator(schema, members) {
115
+ const disc = schema.discriminator;
116
+ if (!disc || typeof disc.propertyName !== 'string')
117
+ return undefined;
118
+ const mapping = [];
119
+ if (disc.mapping) {
120
+ for (const [value, target] of Object.entries(disc.mapping)) {
121
+ mapping.push({ value, schemaName: refName(target) });
122
+ }
123
+ }
124
+ else {
125
+ for (const member of members) {
126
+ if (isRef(member)) {
127
+ const name = refName(member.$ref);
128
+ mapping.push({ value: name, schemaName: name });
129
+ }
130
+ }
131
+ }
132
+ if (mapping.length === 0)
133
+ return undefined;
134
+ return { propertyName: disc.propertyName, mapping };
135
+ }
136
+ /** Attach metadata to a freshly-built SchemaModel (no-op when undefined). */
137
+ function withMetadata(model, metadata) {
138
+ if (!metadata)
139
+ return model;
140
+ return { ...model, metadata };
141
+ }
142
+ /** Merge two metadata bags; right wins on conflicts. Returns undefined when both are empty. */
143
+ function mergeMetadata(a, b) {
144
+ if (!a)
145
+ return b;
146
+ if (!b)
147
+ return a;
148
+ return { ...a, ...b };
149
+ }
150
+ /**
151
+ * The first server's URL with `{variable}` templates substituted by their declared
152
+ * defaults (required by the spec). An undeclared variable keeps its placeholder —
153
+ * there is nothing valid to substitute, and the literal template is easier to spot.
154
+ */
155
+ function resolveServerUrl(server) {
156
+ if (!server)
157
+ return '';
158
+ return server.url.replace(/\{([^{}]+)\}/g, (template, variableName) => server.variables?.[variableName]?.default ?? template);
159
+ }
160
+ export function buildApiModel(doc) {
161
+ const title = doc.info?.title ?? 'Api';
162
+ const version = doc.info?.version ?? '0.0.0';
163
+ const description = doc.info?.description;
164
+ const serverUrl = resolveServerUrl(doc.servers?.[0]);
165
+ const schemas = buildNamedSchemas(doc);
166
+ const securitySchemes = buildSecuritySchemes(doc);
167
+ const services = buildServices(doc, securitySchemes);
168
+ const model = {
169
+ title,
170
+ version,
171
+ description,
172
+ serverUrl,
173
+ services,
174
+ schemas,
175
+ securitySchemes,
176
+ };
177
+ // Sanitize names into safe identifiers (and rewrite refs to match) BEFORE any later
178
+ // pass derives names from them — `stripReadOnly` builds `omit` targets from schema
179
+ // names, so it must see the sanitized ones.
180
+ sanitizeIdentifiers(model);
181
+ stripReadOnlyFromRequestBodies(services, schemas);
182
+ // Hard gate: no unsafe name may reach the printer (see sanitize-identifiers.ts).
183
+ assertSafeIdentifiers(model);
184
+ return model;
185
+ }
186
+ /**
187
+ * Drop `readOnly` (server-managed) properties from every request body, in place.
188
+ * OpenAPI says readOnly properties must not be sent in requests, so a create/update
189
+ * body should not demand `id`/`createdAt`/etc. A body that `$ref`s a named schema
190
+ * becomes `Omit<Name, …readOnly>` (keeping the named type); an inline object has
191
+ * its readOnly properties filtered out. Response types are untouched.
192
+ */
193
+ function stripReadOnlyFromRequestBodies(services, schemas) {
194
+ const byName = new Map(schemas.map((s) => [s.name, s.schema]));
195
+ for (const service of services) {
196
+ for (const op of service.operations) {
197
+ if (op.requestBody)
198
+ op.requestBody.schema = stripReadOnly(op.requestBody.schema, byName);
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Remove `unknown` members from a union's member list. `T | unknown` is just
204
+ * `unknown` in TypeScript, so a typeless/empty `oneOf`/`anyOf` branch would erase
205
+ * the real members. When every member is `unknown`, the union is itself `unknown`.
206
+ */
207
+ function dropRedundantUnknown(members) {
208
+ const real = members.filter((m) => m.kind !== 'unknown');
209
+ return real.length > 0 ? real : [{ kind: 'unknown' }];
210
+ }
211
+ function stripReadOnly(schema, byName) {
212
+ if (schema.kind === 'ref') {
213
+ const keys = collectReadOnlyKeys(schema, byName, new Set());
214
+ return keys.length > 0 ? { kind: 'omit', base: schema.name, keys } : schema;
215
+ }
216
+ if (schema.kind === 'object') {
217
+ const kept = schema.properties.filter((p) => !p.readOnly);
218
+ return kept.length === schema.properties.length ? schema : { ...schema, properties: kept };
219
+ }
220
+ return schema;
221
+ }
222
+ /**
223
+ * The readOnly top-level property names of a schema, descending through `$ref`s
224
+ * and `allOf` (intersection) members — the shape entity schemas compose with. A
225
+ * `visited` set guards against recursive refs. Order-preserving and deduped.
226
+ */
227
+ function collectReadOnlyKeys(schema, byName, visited) {
228
+ const keys = [];
229
+ const visit = (s) => {
230
+ if (s.kind === 'ref') {
231
+ if (visited.has(s.name))
232
+ return;
233
+ visited.add(s.name);
234
+ const target = byName.get(s.name);
235
+ if (target)
236
+ visit(target);
237
+ }
238
+ else if (s.kind === 'object') {
239
+ for (const p of s.properties)
240
+ if (p.readOnly && !keys.includes(p.name))
241
+ keys.push(p.name);
242
+ }
243
+ else if (s.kind === 'intersection') {
244
+ for (const member of s.members)
245
+ visit(member);
246
+ }
247
+ };
248
+ visit(schema);
249
+ return keys;
250
+ }
251
+ /**
252
+ * Collect the security schemes the client can apply on the wire, keyed by their
253
+ * `components.securitySchemes` name. Bearer (`http`+`bearer`/oauth2/openIdConnect),
254
+ * HTTP Basic, and apiKey in header/query/cookie are all injectable. `mutualTLS`
255
+ * (and an `http` scheme that is neither bearer nor basic) is skipped — operations
256
+ * that reference only those will simply carry no auth.
257
+ */
258
+ function buildSecuritySchemes(doc) {
259
+ const schemes = doc.components?.securitySchemes;
260
+ if (!schemes)
261
+ return [];
262
+ const result = [];
263
+ for (const [key, raw] of Object.entries(schemes)) {
264
+ const scheme = deref(doc, raw);
265
+ const type = scheme.type;
266
+ if (type === 'oauth2' || type === 'openIdConnect') {
267
+ result.push({ kind: 'bearer', key });
268
+ }
269
+ else if (type === 'http' && (scheme.scheme ?? '').toLowerCase() === 'bearer') {
270
+ result.push({ kind: 'bearer', key });
271
+ }
272
+ else if (type === 'http' && (scheme.scheme ?? '').toLowerCase() === 'basic') {
273
+ result.push({ kind: 'basic', key });
274
+ }
275
+ else if (type === 'apiKey' && scheme.in === 'header' && typeof scheme.name === 'string') {
276
+ result.push({ kind: 'apiKeyHeader', key, headerName: scheme.name });
277
+ }
278
+ else if (type === 'apiKey' && scheme.in === 'query' && typeof scheme.name === 'string') {
279
+ result.push({ kind: 'apiKeyQuery', key, paramName: scheme.name });
280
+ }
281
+ else if (type === 'apiKey' && scheme.in === 'cookie' && typeof scheme.name === 'string') {
282
+ // Injected via the Cookie request header, which browsers refuse to set —
283
+ // the credential silently goes missing there, so tell the user up front.
284
+ logger.warn(`generate-client: security scheme "${key}" sends its credential in the Cookie header, ` +
285
+ `which browsers ignore — cookie auth works only in server-side clients.\n`);
286
+ result.push({ kind: 'apiKeyCookie', key, cookieName: scheme.name });
287
+ }
288
+ // Everything else (http schemes other than bearer/basic, mutualTLS) is not
289
+ // injectable by the generated client — intentionally skipped.
290
+ }
291
+ return result;
292
+ }
293
+ /**
294
+ * Resolve the effective security for one operation into the list of injectable
295
+ * OR-alternatives, each an AND-set of scheme keys. The operation's own `security`
296
+ * overrides the document default; `security: []` opts out entirely.
297
+ *
298
+ * Every fully-injectable alternative is kept — the runtime applies exactly ONE of
299
+ * them (the first whose credentials are all configured), so an operation that
300
+ * accepts "bearer OR apiKey" works with either credential and never sends both.
301
+ * `{}` (the optional-auth marker) and alternatives with non-injectable schemes
302
+ * are skipped; an empty result means no auth is applied.
303
+ */
304
+ function resolveOperationSecurity(operation, doc, injectable) {
305
+ const requirements = operation.security ??
306
+ doc.security;
307
+ if (!requirements)
308
+ return [];
309
+ const alternatives = [];
310
+ for (const requirement of requirements) {
311
+ const keys = Object.keys(requirement);
312
+ if (keys.length === 0)
313
+ continue;
314
+ if (keys.every((key) => injectable.has(key))) {
315
+ alternatives.push([...new Set(keys)]);
316
+ }
317
+ }
318
+ return alternatives;
319
+ }
320
+ function buildNamedSchemas(doc) {
321
+ const namedSchemas = doc.components?.schemas;
322
+ if (!namedSchemas)
323
+ return [];
324
+ return Object.entries(namedSchemas).map(([name, schema]) => {
325
+ if (isRef(schema)) {
326
+ // A top-level entry that is a $ref forwards to another component.
327
+ const target = resolveRef(doc, schema.$ref);
328
+ return {
329
+ name,
330
+ schema: buildSchema(target, `components.schemas.${name}`, doc),
331
+ description: target.description,
332
+ };
333
+ }
334
+ const built = buildSchema(schema, `components.schemas.${name}`, doc);
335
+ return { name, schema: built, description: schema.description };
336
+ });
337
+ }
338
+ function buildServices(doc, securitySchemes) {
339
+ const injectable = new Set(securitySchemes.map((s) => s.key));
340
+ const entries = [];
341
+ const usedNames = new Set();
342
+ for (const [path, pathItemRaw] of Object.entries(doc.paths ?? {})) {
343
+ if (!pathItemRaw)
344
+ continue;
345
+ const pathItem = deref(doc, pathItemRaw);
346
+ const pathLevelParams = (pathItem.parameters ?? []).map((p) => buildParameter(deref(doc, p), `paths.${path}.parameters`, doc));
347
+ for (const method of HTTP_METHODS) {
348
+ const operation = pathItem[method];
349
+ if (!operation)
350
+ continue;
351
+ entries.push({ method, path, operation, pathLevelParams });
352
+ // Reserve every declared operationId up front so a synthesized fallback name
353
+ // never collides with one — declared ids always win.
354
+ if (operation.operationId)
355
+ usedNames.add(operation.operationId);
356
+ }
357
+ }
358
+ const operations = entries.map((entry) => {
359
+ const name = entry.operation.operationId ??
360
+ takeUniqueName(fallbackOperationName(entry.method, entry.path), usedNames);
361
+ return buildOperation(entry.method, entry.path, entry.operation, name, entry.pathLevelParams, doc, injectable);
362
+ });
363
+ return [{ name: 'Default', operations }];
364
+ }
365
+ /**
366
+ * Synthesize an operation name from method + path when the spec omits `operationId`:
367
+ * `<method><PascalCasedPathSegments>` (braces stripped; each segment split on
368
+ * non-identifier chars). Path-param segments are kept so a collection (`GET /pets`)
369
+ * and an item (`GET /pets/{id}`) stay distinct. Always a valid identifier — the
370
+ * lowercase `method` prefix guarantees a letter start.
371
+ */
372
+ function fallbackOperationName(method, path) {
373
+ const segments = path
374
+ .split('/')
375
+ .filter(Boolean)
376
+ .map((segment) => pascalSegment(segment.replace(/[{}]/g, '')));
377
+ return `${method}${segments.join('')}`;
378
+ }
379
+ function pascalSegment(segment) {
380
+ return segment
381
+ .split(/[^A-Za-z0-9]+/)
382
+ .filter(Boolean)
383
+ .map((word) => word[0].toUpperCase() + word.slice(1))
384
+ .join('');
385
+ }
386
+ /** Return `base`, or `base2`/`base3`/… if already taken; records the result in `used`. */
387
+ function takeUniqueName(base, used) {
388
+ let name = base;
389
+ let suffix = 2;
390
+ while (used.has(name))
391
+ name = `${base}${suffix++}`;
392
+ used.add(name);
393
+ return name;
394
+ }
395
+ function buildOperation(method, path, operation, name, pathLevelParams, doc, injectable) {
396
+ const operationParams = (operation.parameters ?? []).map((p) => buildParameter(deref(doc, p), `paths.${path}.${method}.parameters`, doc));
397
+ // Operation-level parameters override path-level ones (by name + in).
398
+ const seen = new Set();
399
+ const allParams = [];
400
+ for (const p of operationParams) {
401
+ seen.add(`${p.in}:${p.name}`);
402
+ allParams.push(p);
403
+ }
404
+ for (const p of pathLevelParams) {
405
+ if (!seen.has(`${p.in}:${p.name}`)) {
406
+ allParams.push(p);
407
+ }
408
+ }
409
+ const pathParams = allParams.filter((p) => p.in === 'path');
410
+ const queryParams = allParams.filter((p) => p.in === 'query');
411
+ const headerParams = allParams.filter((p) => p.in === 'header');
412
+ // Browsers own the Cookie header, so the generated client cannot set cookie
413
+ // params — drop them, but tell the user instead of vanishing them silently.
414
+ const cookieParams = allParams.filter((p) => p.in === 'cookie');
415
+ if (cookieParams.length > 0) {
416
+ logger.warn(`generate-client: skipped cookie parameter(s) ${cookieParams
417
+ .map((p) => `"${p.name}"`)
418
+ .join(', ')} on operation "${name}" — cookie parameters are not supported.\n`);
419
+ }
420
+ const requestBody = operation.requestBody
421
+ ? buildRequestBody(deref(doc, operation.requestBody), `${method} ${path}`, doc)
422
+ : undefined;
423
+ const successResponses = buildSuccessResponses(operation, path, doc);
424
+ const errorResponses = buildErrorResponses(operation, path, doc);
425
+ const security = resolveOperationSecurity(operation, doc, injectable);
426
+ // Extensions aren't in the @redocly operation type — read loosely, like `deprecated`.
427
+ const paginationExtension = operation['x-pagination'];
428
+ return {
429
+ name,
430
+ method,
431
+ path,
432
+ summary: operation.summary,
433
+ description: operation.description,
434
+ pathParams,
435
+ queryParams,
436
+ headerParams,
437
+ requestBody,
438
+ successResponses,
439
+ errorResponses,
440
+ security,
441
+ tags: Array.isArray(operation.tags) ? operation.tags.filter((t) => typeof t === 'string') : [],
442
+ ...(paginationExtension !== undefined ? { paginationExtension } : {}),
443
+ };
444
+ }
445
+ function buildParameter(param, location, doc) {
446
+ if (!param.in) {
447
+ throw new NotSupportedError(`Parameter ${param.name} at ${location} is missing "in"`);
448
+ }
449
+ if (!['path', 'query', 'header', 'cookie'].includes(param.in)) {
450
+ throw new NotSupportedError(`Unsupported parameter location "${param.in}" for ${param.name} at ${location}`);
451
+ }
452
+ let schema = schemaFromSlot(param.schema, `${location}.${param.name}`, doc);
453
+ // OpenAPI lets `deprecated: true` live on the Parameter Object itself, not
454
+ // just on its schema. Fold it into the schema's metadata so the emitter has
455
+ // a single source of truth when rendering tags around this param.
456
+ if (param.deprecated === true) {
457
+ schema = {
458
+ ...schema,
459
+ metadata: mergeMetadata(schema.metadata, { deprecated: true }),
460
+ };
461
+ }
462
+ const model = {
463
+ name: param.name,
464
+ in: param.in,
465
+ schema,
466
+ required: Boolean(param.required),
467
+ description: param.description,
468
+ };
469
+ // Query-serialization hints (OpenAPI `style`/`explode`/`allowReserved`) live on the
470
+ // Parameter Object and aren't in the @redocly types — read them loosely, like
471
+ // `deprecated`. Only set them for query params, and only when present (absence ⇒
472
+ // the defaults, so the IR stays clean and downstream takes the default path).
473
+ if (model.in === 'query') {
474
+ const p = param;
475
+ if (p.style === 'form' ||
476
+ p.style === 'spaceDelimited' ||
477
+ p.style === 'pipeDelimited' ||
478
+ p.style === 'deepObject') {
479
+ model.style = p.style;
480
+ }
481
+ if (typeof p.explode === 'boolean')
482
+ model.explode = p.explode;
483
+ if (typeof p.allowReserved === 'boolean')
484
+ model.allowReserved = p.allowReserved;
485
+ }
486
+ return model;
487
+ }
488
+ function buildRequestBody(rb, location, doc) {
489
+ const content = rb.content ?? {};
490
+ // Prefer JSON; fall back to first available.
491
+ const preferred = content['application/json'] ??
492
+ content['application/merge-patch+json'] ??
493
+ content['application/x-www-form-urlencoded'] ??
494
+ content['multipart/form-data'] ??
495
+ Object.values(content)[0];
496
+ if (!preferred)
497
+ return undefined;
498
+ // `preferred` is one of the values we sampled out of `content`, so its key must exist there.
499
+ const contentType = Object.keys(content).find((k) => content[k] === preferred);
500
+ const schema = schemaFromSlot(preferred.schema, `${location}.requestBody`, doc);
501
+ return {
502
+ contentType,
503
+ schema,
504
+ required: Boolean(rb.required),
505
+ description: rb.description,
506
+ };
507
+ }
508
+ function buildSuccessResponses(operation, path, doc) {
509
+ const responses = operation.responses ?? {};
510
+ const successCodes = Object.keys(responses).filter((code) => /^2\d\d$/.test(code));
511
+ if (successCodes.length === 0) {
512
+ if (responses['default'])
513
+ successCodes.push('default');
514
+ }
515
+ // Pick the first success response.
516
+ const code = successCodes[0];
517
+ if (!code)
518
+ return [];
519
+ const responseRaw = responses[code];
520
+ if (!responseRaw)
521
+ return [];
522
+ const response = deref(doc, responseRaw);
523
+ const content = response.content;
524
+ if (!content)
525
+ return [];
526
+ const status = code === 'default' ? 'default' : Number(code);
527
+ const result = [];
528
+ for (const [contentType, media] of Object.entries(content)) {
529
+ const schema = schemaFromSlot(media.schema, `paths.${path}.response.${code}.${contentType}`, doc);
530
+ const itemSlot = media.itemSchema;
531
+ const item = itemSlot !== undefined
532
+ ? schemaFromSlot(itemSlot, `paths.${path}.response.${code}.${contentType}.itemSchema`, doc)
533
+ : undefined;
534
+ result.push(item === undefined
535
+ ? { contentType, schema, status }
536
+ : { contentType, schema, status, itemSchema: item });
537
+ }
538
+ return result;
539
+ }
540
+ function buildErrorResponses(operation, path, doc) {
541
+ const responses = operation.responses ?? {};
542
+ const codes = Object.keys(responses).filter((code) => /^[45]\d\d$/.test(code));
543
+ // `default` is an error only when a 2xx success exists; otherwise
544
+ // `buildSuccessResponses` already consumes it as the success response.
545
+ const hasSuccess = Object.keys(responses).some((code) => /^2\d\d$/.test(code));
546
+ if (hasSuccess && responses['default'])
547
+ codes.push('default');
548
+ const result = [];
549
+ for (const code of codes) {
550
+ const responseRaw = responses[code];
551
+ if (!responseRaw)
552
+ continue;
553
+ const response = deref(doc, responseRaw);
554
+ const content = response.content;
555
+ if (!content)
556
+ continue;
557
+ const status = code === 'default' ? 'default' : Number(code);
558
+ for (const [contentType, media] of Object.entries(content)) {
559
+ const schema = schemaFromSlot(media.schema, `paths.${path}.response.${code}.${contentType}`, doc);
560
+ result.push({ contentType, schema, status });
561
+ }
562
+ }
563
+ return result;
564
+ }
565
+ /**
566
+ * Decode a "schema slot" — the referenced-or-inline-or-absent schema position
567
+ * that recurs all over an OpenAPI document (`parameter.schema`, `media.schema`,
568
+ * property values, array `items`, `additionalProperties`) — into a SchemaModel.
569
+ *
570
+ * - absent (`undefined`) or a boolean JSON-Schema → `unknown` (we don't model
571
+ * boolean schemas; this collapses the "missing schema" fallbacks too).
572
+ * - a `$ref` → a `ref` node carrying the target's local name (the IR keeps
573
+ * refs un-resolved so named schemas map back to exported types).
574
+ * - an inline schema → recurse via `buildSchema`.
575
+ */
576
+ function schemaFromSlot(slot, location, doc) {
577
+ if (slot === undefined || typeof slot === 'boolean')
578
+ return { kind: 'unknown' };
579
+ if (isRef(slot))
580
+ return { kind: 'ref', name: refName(slot.$ref) };
581
+ return buildSchema(slot, location, doc);
582
+ }
583
+ function buildSchema(schema, location, doc) {
584
+ // Note: every caller checks `isRef(schema)` before invoking this function, so a top-level
585
+ // `{$ref: ...}` never reaches here — see `buildNamedSchemas`, `buildParameter`, etc.
586
+ const metadata = extractMetadata(schema);
587
+ const oneOfish = schema.oneOf ?? schema.anyOf;
588
+ if (oneOfish) {
589
+ const allMembers = oneOfish.map((sub, idx) => isRef(sub)
590
+ ? { kind: 'ref', name: refName(sub.$ref) }
591
+ : buildSchema(sub, `${location}.[${idx}]`, doc));
592
+ // Drop `unknown` members (typeless/empty branches): `T | unknown` collapses to
593
+ // `unknown` in TS, erasing the real members. When only one real member remains,
594
+ // the union degenerates to it.
595
+ const members = dropRedundantUnknown(allMembers);
596
+ // Collapse to the lone member only when dropping `unknown` branches reduced the
597
+ // union to one — an originally single-member union is left as-is.
598
+ if (members.length === 1 && members.length < allMembers.length) {
599
+ const inner = members[0];
600
+ return {
601
+ ...inner,
602
+ description: inner.description ?? schema.description,
603
+ metadata: mergeMetadata(metadata, inner.metadata),
604
+ };
605
+ }
606
+ const discriminator = buildDiscriminator(schema, oneOfish);
607
+ return withMetadata({ kind: 'union', members, discriminator, description: schema.description }, metadata);
608
+ }
609
+ if (schema.allOf) {
610
+ const members = schema.allOf.map((sub, idx) => isRef(sub)
611
+ ? { kind: 'ref', name: refName(sub.$ref) }
612
+ : buildSchema(sub, `${location}.allOf[${idx}]`, doc));
613
+ // `allOf` does not replace sibling `properties`: a schema may declare its own
614
+ // object shape (often a `const` discriminant) alongside `allOf`. Fold that own
615
+ // object into the intersection so those properties aren't dropped.
616
+ const ownProperties = buildProperties(schema, location, doc);
617
+ if (ownProperties.length > 0) {
618
+ members.unshift({ kind: 'object', properties: ownProperties });
619
+ }
620
+ if (members.length === 1) {
621
+ // Single-member allOf collapses to its target. Preserve the wrapper's
622
+ // description/metadata only when the inner doesn't already have them.
623
+ const inner = members[0];
624
+ return {
625
+ ...inner,
626
+ description: inner.description ?? schema.description,
627
+ metadata: mergeMetadata(metadata, inner.metadata),
628
+ };
629
+ }
630
+ return withMetadata({ kind: 'intersection', members, description: schema.description }, metadata);
631
+ }
632
+ // OpenAPI 3.1 style nullable: `type: ['string', 'null']`.
633
+ const rawType = schema.type;
634
+ if (Array.isArray(rawType)) {
635
+ const nonNull = rawType.filter((t) => t !== 'null');
636
+ const hasNull = rawType.includes('null');
637
+ const baseMembers = nonNull.map((t) => {
638
+ // Strip `null` out of any `enum` before recursing: the array-type null is
639
+ // the single source of nullability here, so the enum branch must not add a
640
+ // second `null` member (and an enum of only `null` would otherwise throw).
641
+ const sub = { ...schema, type: t };
642
+ if (Array.isArray(sub.enum)) {
643
+ const filtered = sub.enum.filter((v) => v !== null);
644
+ sub.enum = filtered.length > 0 ? filtered : undefined;
645
+ }
646
+ return buildSchema(sub, location, doc);
647
+ });
648
+ if (hasNull)
649
+ baseMembers.push({ kind: 'null' });
650
+ if (baseMembers.length === 1) {
651
+ return baseMembers[0];
652
+ }
653
+ return withMetadata({ kind: 'union', members: baseMembers, description: schema.description }, metadata);
654
+ }
655
+ // OpenAPI 3.0 nullable.
656
+ if (schema.nullable) {
657
+ const base = buildSchema({ ...schema, nullable: undefined }, location, doc);
658
+ return {
659
+ kind: 'union',
660
+ members: [base, { kind: 'null' }],
661
+ description: schema.description,
662
+ };
663
+ }
664
+ if (schema.const !== undefined) {
665
+ const value = schema.const;
666
+ if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
667
+ throw new NotSupportedError(`Unsupported const value at ${location}: ${JSON.stringify(value)}`);
668
+ }
669
+ return withMetadata({ kind: 'literal', value, description: schema.description }, metadata);
670
+ }
671
+ if (schema.enum) {
672
+ // OAS 3.1 allows `null` among enum values. Model it as `<enum> | null` and
673
+ // keep the enum itself null-free so scalar inference stays simple.
674
+ const rawValues = schema.enum;
675
+ const nonNullValues = rawValues.filter((v) => v !== null);
676
+ const enumHasNull = nonNullValues.length !== rawValues.length;
677
+ if (nonNullValues.length === 0) {
678
+ return withMetadata({ kind: 'null', description: schema.description }, metadata);
679
+ }
680
+ const scalar = scalarForEnumValues(nonNullValues, location);
681
+ // A boolean `enum` conveys no useful narrowing: `[true, false]` is just `boolean`, and a
682
+ // single-value `[false]`/`[true]` is a spec quirk, not an intended literal type. Widen to
683
+ // `boolean` so a normal boolean field never becomes a literal. (`const: false` stays a
684
+ // literal — that's an explicit, deliberate single-value constraint, handled above.)
685
+ const base = scalar === 'boolean'
686
+ ? { kind: 'scalar', scalar: 'boolean', description: schema.description }
687
+ : { kind: 'enum', values: nonNullValues, scalar, description: schema.description };
688
+ if (enumHasNull) {
689
+ return withMetadata({ kind: 'union', members: [base, { kind: 'null' }], description: schema.description }, metadata);
690
+ }
691
+ return withMetadata(base, metadata);
692
+ }
693
+ const type = rawType;
694
+ if (type === 'null') {
695
+ // OAS 3.1 single null type — a value that is always `null` (e.g. a field a
696
+ // variant pins to null). Without this it would fall through to `unknown`.
697
+ return withMetadata({ kind: 'null', description: schema.description }, metadata);
698
+ }
699
+ if (type === 'array') {
700
+ const itemsRaw = schema.items;
701
+ const items = schemaFromSlot(itemsRaw, `${location}.items`, doc);
702
+ return withMetadata({ kind: 'array', items, description: schema.description }, metadata);
703
+ }
704
+ if (type === 'object' || schema.properties || schema.additionalProperties) {
705
+ const properties = buildProperties(schema, location, doc);
706
+ const additional = schema.additionalProperties;
707
+ if (properties.length === 0 && additional !== false) {
708
+ // A property-less object accepts arbitrary keys: OpenAPI defaults absent
709
+ // `additionalProperties` to allowed. Emit a record — of the declared
710
+ // additionalProperties schema, or `unknown` when it's absent/`true` — rather
711
+ // than `{}`, which in TS forbids member access. An explicit
712
+ // `additionalProperties: false` is closed and stays an empty object.
713
+ const value = schemaFromSlot(additional, `${location}.additionalProperties`, doc);
714
+ return withMetadata({ kind: 'record', value, description: schema.description }, metadata);
715
+ }
716
+ return withMetadata({ kind: 'object', properties, description: schema.description }, metadata);
717
+ }
718
+ if (type === 'string' || type === 'number' || type === 'integer' || type === 'boolean') {
719
+ return withMetadata({ kind: 'scalar', scalar: type, description: schema.description }, metadata);
720
+ }
721
+ // No usable type information — fall back to `unknown` instead of erroring,
722
+ // so generation still succeeds for sparsely-typed schemas.
723
+ return withMetadata({ kind: 'unknown', description: schema.description }, metadata);
724
+ }
725
+ function scalarForEnumValues(values, location) {
726
+ let hasStr = false;
727
+ let hasNum = false;
728
+ let hasBool = false;
729
+ for (const v of values) {
730
+ if (typeof v === 'string')
731
+ hasStr = true;
732
+ else if (typeof v === 'number')
733
+ hasNum = true;
734
+ else if (typeof v === 'boolean')
735
+ hasBool = true;
736
+ else
737
+ throw new NotSupportedError(`Unsupported enum value type at ${location}: ${JSON.stringify(v)}`);
738
+ }
739
+ if (hasStr && !hasNum && !hasBool)
740
+ return 'string';
741
+ if (hasNum && !hasStr && !hasBool)
742
+ return 'number';
743
+ if (hasBool && !hasStr && !hasNum)
744
+ return 'boolean';
745
+ return 'string';
746
+ }
747
+ function buildProperties(schema, location, doc) {
748
+ const props = schema.properties ?? {};
749
+ const required = new Set(schema.required ?? []);
750
+ return Object.entries(props).map(([name, sub]) => {
751
+ const readOnly = !isRef(sub) && sub.readOnly === true;
752
+ return {
753
+ name,
754
+ schema: schemaFromSlot(sub, `${location}.${name}`, doc),
755
+ required: required.has(name),
756
+ description: sub.description,
757
+ ...(readOnly ? { readOnly: true } : {}),
758
+ };
759
+ });
760
+ }
761
+ //# sourceMappingURL=build.js.map