@aws/nx-plugin 1.0.0-rc.30 → 1.0.0-rc.32

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 (59) hide show
  1. package/LICENSE-THIRD-PARTY +235 -1336
  2. package/README.md +1 -1
  3. package/package.json +1 -2
  4. package/src/mcp-server/tools/create-workspace-command.js +2 -2
  5. package/src/mcp-server/tools/create-workspace-command.js.map +1 -1
  6. package/src/open-api/ts-client/__snapshots__/generator.additional-properties.spec.ts.snap +147 -33
  7. package/src/open-api/ts-client/__snapshots__/generator.arrays.spec.ts.snap +308 -75
  8. package/src/open-api/ts-client/__snapshots__/generator.complex-types.spec.ts.snap +286 -62
  9. package/src/open-api/ts-client/__snapshots__/generator.composite-types.spec.ts.snap +674 -93
  10. package/src/open-api/ts-client/__snapshots__/generator.content-type.spec.ts.snap +211 -36
  11. package/src/open-api/ts-client/__snapshots__/generator.duplicate-types.spec.ts.snap +196 -44
  12. package/src/open-api/ts-client/__snapshots__/generator.edge-cases.spec.ts.snap +5507 -0
  13. package/src/open-api/ts-client/__snapshots__/generator.fast-api.spec.ts.snap +219 -60
  14. package/src/open-api/ts-client/__snapshots__/generator.multipart.spec.ts.snap +280 -0
  15. package/src/open-api/ts-client/__snapshots__/generator.primitive-types.spec.ts.snap +351 -80
  16. package/src/open-api/ts-client/__snapshots__/generator.request.spec.ts.snap +170 -49
  17. package/src/open-api/ts-client/__snapshots__/generator.reserved-keywords.spec.ts.snap +98 -22
  18. package/src/open-api/ts-client/__snapshots__/generator.response.spec.ts.snap +147 -33
  19. package/src/open-api/ts-client/__snapshots__/generator.streaming.spec.ts.snap +392 -88
  20. package/src/open-api/ts-client/__snapshots__/generator.tags.spec.ts.snap +196 -44
  21. package/src/open-api/ts-client/files/client.gen.ts.template +166 -19
  22. package/src/open-api/ts-client/files/types.gen.ts.template +4 -4
  23. package/src/open-api/ts-client/generator.js +1 -1
  24. package/src/open-api/ts-client/generator.js.map +1 -1
  25. package/src/open-api/ts-hooks/files/options-proxy.gen.ts.template +5 -0
  26. package/src/open-api/ts-hooks/generator.spec.tsx +70 -0
  27. package/src/open-api/utils/codegen-data/languages.d.ts +0 -6
  28. package/src/open-api/utils/codegen-data/languages.js +23 -18
  29. package/src/open-api/utils/codegen-data/languages.js.map +1 -1
  30. package/src/open-api/utils/codegen-data/types.d.ts +240 -10
  31. package/src/open-api/utils/codegen-data/types.js +24 -1
  32. package/src/open-api/utils/codegen-data/types.js.map +1 -1
  33. package/src/open-api/utils/codegen-data.d.ts +2 -2
  34. package/src/open-api/utils/codegen-data.js +357 -617
  35. package/src/open-api/utils/codegen-data.js.map +1 -1
  36. package/src/open-api/utils/normalise.js +167 -22
  37. package/src/open-api/utils/normalise.js.map +1 -1
  38. package/src/open-api/utils/parser.d.ts +55 -0
  39. package/src/open-api/utils/parser.js +754 -0
  40. package/src/open-api/utils/parser.js.map +1 -0
  41. package/src/open-api/utils/types.d.ts +18 -0
  42. package/src/open-api/utils/types.js.map +1 -1
  43. package/src/preset/__snapshots__/generator.spec.ts.snap +6 -6
  44. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +414 -6
  45. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +276 -4
  46. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +414 -6
  47. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +0 -23
  48. package/src/ts/mcp-server/generator.js +10 -22
  49. package/src/ts/mcp-server/generator.js.map +1 -1
  50. package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +69 -1
  51. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +69 -1
  52. package/src/utils/mcp.js +1 -1
  53. package/src/utils/mcp.js.map +1 -1
  54. package/src/utils/names.d.ts +6 -0
  55. package/src/utils/names.js +6 -1
  56. package/src/utils/names.js.map +1 -1
  57. package/src/utils/versions.d.ts +5 -5
  58. package/src/utils/versions.js +4 -4
  59. package/src/utils/versions.js.map +1 -1
@@ -0,0 +1,754 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import camelCase from "lodash.camelcase";
5
+ import trim from "lodash.trim";
6
+ import { COLLECTION_TYPES, COMPOSED_SCHEMA_TYPES, createModel, DEFAULT_SERVICE_NAME, indexModelsByName, PRIMITIVE_TYPES } from "./codegen-data/types.js";
7
+ import { isRef, resolveIfRef, splitRef } from "./refs.js";
8
+ /**
9
+ * OpenAPI primitive `type` values mapped to the internal model `type`.
10
+ */ const PRIMITIVE_TYPE_MAP = {
11
+ string: 'string',
12
+ number: 'number',
13
+ integer: 'number',
14
+ boolean: 'boolean',
15
+ null: 'null'
16
+ };
17
+ const HTTP_METHODS = [
18
+ 'get',
19
+ 'put',
20
+ 'post',
21
+ 'delete',
22
+ 'options',
23
+ 'head',
24
+ 'patch',
25
+ 'trace'
26
+ ];
27
+ const schemaType = (schema)=>schema.type;
28
+ const isNullable = (schema)=>{
29
+ const type = schemaType(schema);
30
+ return schema.nullable === true || type === 'null' || Array.isArray(type) && type.includes('null');
31
+ };
32
+ /**
33
+ * The "primary" type of a schema, ignoring 'null' in a 3.1 type array.
34
+ */ const primaryType = (schema)=>{
35
+ const type = schemaType(schema);
36
+ return Array.isArray(type) ? type.find((t)=>t !== 'null') : type;
37
+ };
38
+ const isEnumSchema = (schema)=>Array.isArray(schema.enum) && schema.enum.length > 0;
39
+ const compositeExport = (schema)=>{
40
+ if (schema.allOf) return 'all-of';
41
+ if (schema.anyOf) return 'any-of';
42
+ if (schema.oneOf) return 'one-of';
43
+ return undefined;
44
+ };
45
+ const compositeMembers = (schema)=>schema.allOf ?? schema.anyOf ?? schema.oneOf ?? [];
46
+ /**
47
+ * A schema is a "dictionary" (map) when it is an object with no explicit
48
+ * properties (it may have `additionalProperties`, or be a bare `object`).
49
+ */ const isDictionarySchema = (schema)=>{
50
+ if (primaryType(schema) !== 'object') return false;
51
+ if (compositeExport(schema) || isEnumSchema(schema)) return false;
52
+ const hasProperties = Object.keys(schema.properties ?? {}).length > 0;
53
+ return !hasProperties && !schema.patternProperties;
54
+ };
55
+ /**
56
+ * The internal primitive type name for a primitive schema.
57
+ */ const primitiveType = (schema)=>{
58
+ const type = primaryType(schema);
59
+ if (type === 'string' && isBinaryStringSchema(schema)) return 'binary';
60
+ return (type && PRIMITIVE_TYPE_MAP[type]) ?? 'unknown';
61
+ };
62
+ /**
63
+ * Whether a `string` schema denotes raw binary content (rendered as a `Blob`).
64
+ * Covers the 3.0 `format: binary` idiom and the 3.1 `contentMediaType` idiom
65
+ * (as emitted by FastAPI for `UploadFile`). A base64 `contentEncoding`, or a
66
+ * textual/JSON media type, keeps the value a string on the wire.
67
+ */ const isBinaryStringSchema = (schema)=>{
68
+ if (schema.format === 'binary') return true;
69
+ const contentMediaType = schema.contentMediaType;
70
+ if (!contentMediaType || schema.contentEncoding) return false;
71
+ return !(contentMediaType.startsWith('text/') || contentMediaType === 'application/json' || contentMediaType.endsWith('+json') || contentMediaType === 'application/xml');
72
+ };
73
+ /**
74
+ * Prefer application/json, falling back to the first declared media type.
75
+ */ const preferredMediaType = (content)=>'application/json' in content ? 'application/json' : Object.keys(content)[0];
76
+ const parseResponseCode = (code)=>/^\d+$/.test(code) ? parseInt(code, 10) : code;
77
+ /**
78
+ * The unique imports declared across a set of models, in first-seen order.
79
+ */ const collectImports = (models)=>[
80
+ ...new Set(models.flatMap((m)=>m.imports))
81
+ ];
82
+ /**
83
+ * The structural fields (export/type/link/properties/enum/imports/format) a
84
+ * schema contributes to a model.
85
+ */ const schemaStructure = (spec, schema)=>{
86
+ // A null member (3.0 nullable enums list null as a value) marks the model
87
+ // nullable rather than becoming a literal. An enum of only null degrades to
88
+ // a plain (nullable) schema.
89
+ const enumMembers = (schema.enum ?? []).filter((value)=>value !== null);
90
+ if (isEnumSchema(schema) && enumMembers.length > 0) {
91
+ // A string (or untyped) enum renders as a literal union; a boolean/number
92
+ // enum renders as its bare primitive, so it must carry the declared type.
93
+ const declared = primaryType(schema);
94
+ return {
95
+ export: 'enum',
96
+ type: (declared && PRIMITIVE_TYPE_MAP[declared]) ?? 'string',
97
+ enum: enumMembers.map((value)=>({
98
+ value: value
99
+ })),
100
+ ...enumMembers.length < schema.enum.length ? {
101
+ isNullable: true
102
+ } : {}
103
+ };
104
+ }
105
+ const composite = compositeExport(schema);
106
+ if (composite) {
107
+ const properties = compositeMembers(schema).map((member)=>buildInlineModel(spec, member));
108
+ return {
109
+ export: composite,
110
+ type: 'unknown',
111
+ properties,
112
+ imports: collectImports(properties)
113
+ };
114
+ }
115
+ const type = primaryType(schema);
116
+ if (type === 'array') {
117
+ const items = schema.items;
118
+ return {
119
+ export: 'array',
120
+ ...collectionValue(spec, items)
121
+ };
122
+ }
123
+ if (isDictionarySchema(schema)) {
124
+ return {
125
+ export: 'dictionary',
126
+ ...dictionaryValue(spec, schema)
127
+ };
128
+ }
129
+ if (type === 'object' || schema.properties || schema.patternProperties) {
130
+ const properties = buildProperties(spec, schema);
131
+ return {
132
+ export: 'interface',
133
+ type: 'unknown',
134
+ properties,
135
+ imports: collectImports(properties)
136
+ };
137
+ }
138
+ // A schema with no type (e.g. `{}`) is an untyped object.
139
+ if (type === undefined) {
140
+ return {
141
+ export: 'interface',
142
+ type: 'unknown'
143
+ };
144
+ }
145
+ return {
146
+ export: 'generic',
147
+ type: primitiveType(schema),
148
+ ...schema.format ? {
149
+ format: schema.format
150
+ } : {}
151
+ };
152
+ };
153
+ /**
154
+ * The value type of a collection (array items / dictionary values): a `type`,
155
+ * `link` and `imports`.
156
+ *
157
+ * - ref value → `type` is the referenced model name; `link` is left null and
158
+ * resolved later by `linkModels`.
159
+ * - non-ref value → a `link` model carrying the (innermost) type and imports.
160
+ */ const collectionValue = (spec, value)=>{
161
+ if (value && isRef(value)) {
162
+ const type = splitRef(value.$ref)[2];
163
+ return {
164
+ type,
165
+ imports: [
166
+ type
167
+ ],
168
+ link: null
169
+ };
170
+ }
171
+ const link = createModel(schemaStructure(spec, value ?? {}));
172
+ return {
173
+ link,
174
+ type: link.type,
175
+ imports: [
176
+ ...link.imports
177
+ ]
178
+ };
179
+ };
180
+ /**
181
+ * The value type of a dictionary (map).
182
+ */ const dictionaryValue = (spec, schema)=>{
183
+ const additional = schema.additionalProperties;
184
+ if (additional && additional !== true) {
185
+ return collectionValue(spec, additional);
186
+ }
187
+ if (schema.properties && additional !== true) {
188
+ // An object with an (empty) `properties` map but no additionalProperties
189
+ // renders as `{}`.
190
+ return {
191
+ type: 'unknown',
192
+ link: null
193
+ };
194
+ }
195
+ // `additionalProperties: true`, or a bare `{ type: 'object' }` → an "any"
196
+ // value type.
197
+ return {
198
+ type: 'unknown',
199
+ link: createModel({
200
+ export: 'interface',
201
+ type: 'unknown'
202
+ })
203
+ };
204
+ };
205
+ /**
206
+ * A model for an inline sub-schema (property value, array item, dictionary
207
+ * value, composite member, parameter, or response).
208
+ */ export const buildInlineModel = (spec, schemaOrRef)=>{
209
+ if (isRef(schemaOrRef)) {
210
+ const name = splitRef(schemaOrRef.$ref)[2];
211
+ return createModel({
212
+ export: 'reference',
213
+ type: name,
214
+ imports: [
215
+ name
216
+ ]
217
+ });
218
+ }
219
+ return createModel({
220
+ description: schemaOrRef.description ?? null,
221
+ deprecated: !!schemaOrRef.deprecated,
222
+ isNullable: isNullable(schemaOrRef),
223
+ isReadOnly: !!schemaOrRef.readOnly,
224
+ ...schemaStructure(spec, schemaOrRef)
225
+ });
226
+ };
227
+ /**
228
+ * A model for a top-level named schema (a definition). Object definitions are
229
+ * typed as their own name (the type the generator emits for them).
230
+ */ const buildDefinitionModel = (spec, name, schema)=>{
231
+ const structure = schemaStructure(spec, schema);
232
+ const isNamedObject = schema.type === 'object' && !!(schema.properties || schema.patternProperties);
233
+ return createModel({
234
+ name,
235
+ description: schema.description ?? null,
236
+ deprecated: !!schema.deprecated,
237
+ isNullable: isNullable(schema),
238
+ ...structure,
239
+ ...isNamedObject ? {
240
+ type: name
241
+ } : {}
242
+ });
243
+ };
244
+ /**
245
+ * The property models for an object schema.
246
+ */ const buildProperties = (spec, schema)=>{
247
+ const required = new Set(schema.required ?? []);
248
+ return Object.entries(schema.properties ?? {}).map(([name, propSchema])=>({
249
+ ...buildInlineModel(spec, propSchema),
250
+ name,
251
+ isRequired: required.has(name)
252
+ }));
253
+ };
254
+ /**
255
+ * A `{ modelName → { propertyName } }` map of the discriminator properties
256
+ * declared by composite schemas with an explicit `mapping`.
257
+ */ const discriminatorTargets = (spec)=>{
258
+ const targets = new Map();
259
+ for (const schemaOrRef of Object.values(spec.components?.schemas ?? {})){
260
+ const { discriminator } = resolveIfRef(spec, schemaOrRef);
261
+ if (!discriminator?.propertyName || !discriminator.mapping) continue;
262
+ for (const mappedRef of Object.values(discriminator.mapping)){
263
+ if (typeof mappedRef !== 'string' || !mappedRef.startsWith('#/')) continue;
264
+ const modelName = splitRef(mappedRef)[2];
265
+ const properties = targets.get(modelName) ?? new Set();
266
+ properties.add(discriminator.propertyName);
267
+ targets.set(modelName, properties);
268
+ }
269
+ }
270
+ return targets;
271
+ };
272
+ /**
273
+ * For discriminated composites, collapse each mapped schema's discriminator
274
+ * property from its enum reference to the bare primitive type (`export` stays
275
+ * `reference`, `type` becomes the primitive, `imports` are cleared). The
276
+ * referenced enum models are left in place. This drops the literal narrowing
277
+ * but keeps the emitted code valid.
278
+ */ const collapseDiscriminators = (spec, models)=>{
279
+ const targets = discriminatorTargets(spec);
280
+ if (targets.size === 0) return models;
281
+ const byName = new Map(models.map((m)=>[
282
+ m.name,
283
+ m
284
+ ]));
285
+ return models.map((model)=>{
286
+ const properties = targets.get(model.name);
287
+ if (!properties) return model;
288
+ return {
289
+ ...model,
290
+ properties: model.properties.map((property)=>{
291
+ if (!properties.has(property.name) || property.export !== 'reference') {
292
+ return property;
293
+ }
294
+ const referenced = byName.get(property.type);
295
+ return referenced?.export === 'enum' ? {
296
+ ...property,
297
+ type: referenced.type,
298
+ imports: []
299
+ } : property;
300
+ })
301
+ };
302
+ });
303
+ };
304
+ const buildModels = (spec)=>collapseDiscriminators(spec, Object.entries(spec.components?.schemas ?? {}).map(([name, schemaOrRef])=>buildDefinitionModel(spec, name, resolveIfRef(spec, schemaOrRef))));
305
+ /**
306
+ * The synthetic `body` parameter for an operation's request body.
307
+ */ const buildRequestBody = (spec, specOp)=>{
308
+ const requestBody = resolveIfRef(spec, specOp.requestBody);
309
+ // An empty content map declares no acceptable media types, ie no body.
310
+ if (!requestBody?.content || Object.keys(requestBody.content).length === 0) return null;
311
+ const mediaType = preferredMediaType(requestBody.content);
312
+ const base = buildInlineModel(spec, requestBody.content[mediaType]?.schema ?? {});
313
+ return {
314
+ ...base,
315
+ name: 'requestBody',
316
+ prop: 'requestBody',
317
+ in: 'body',
318
+ mediaType,
319
+ isRequired: !!requestBody.required,
320
+ description: requestBody.description ?? base.description
321
+ };
322
+ };
323
+ /**
324
+ * The parameter models for an operation, including a synthetic body parameter.
325
+ * Ordered required-first with a stable sort (`Array.prototype.sort`), so the
326
+ * original relative order within each group is preserved.
327
+ */ const buildParameters = (spec, specOp, pathParameters = [])=>{
328
+ const declared = mergeParameters(spec, pathParameters, [
329
+ ...specOp.parameters ?? []
330
+ ]).flatMap((p)=>{
331
+ const param = resolveIfRef(spec, p);
332
+ if (!param) return [];
333
+ const base = buildInlineModel(spec, param.schema ?? {});
334
+ return [
335
+ {
336
+ ...base,
337
+ name: param.name,
338
+ prop: param.name,
339
+ in: param.in,
340
+ mediaType: null,
341
+ isRequired: !!param.required,
342
+ description: param.description ? param.description : base.description
343
+ }
344
+ ];
345
+ });
346
+ const body = buildRequestBody(spec, specOp);
347
+ const params = body ? [
348
+ ...declared,
349
+ body
350
+ ] : declared;
351
+ return [
352
+ ...params
353
+ ].sort((a, b)=>a.isRequired === b.isRequired ? 0 : a.isRequired ? -1 : 1);
354
+ };
355
+ /**
356
+ * Merge path-level parameters with operation-level parameters. Per the OpenAPI
357
+ * spec, path-level parameters apply to every operation in the path unless an
358
+ * operation-level parameter overrides one with the same `name` + `in`.
359
+ */ const mergeParameters = (spec, pathParameters, operationParameters)=>{
360
+ const key = (p)=>{
361
+ const param = resolveIfRef(spec, p);
362
+ return param ? specParameterKey(param) : null;
363
+ };
364
+ const overridden = new Set(operationParameters.map(key));
365
+ const inherited = pathParameters.filter((p)=>!overridden.has(key(p)));
366
+ return [
367
+ ...inherited,
368
+ ...operationParameters
369
+ ];
370
+ };
371
+ /**
372
+ * The response models for an operation.
373
+ */ const buildResponses = (spec, specOp)=>Object.entries(specOp.responses ?? {}).flatMap(([code, resOrRef])=>{
374
+ const response = resolveIfRef(spec, resOrRef);
375
+ if (!response) return [];
376
+ const content = response.content ?? {};
377
+ const mediaType = Object.keys(content).length ? preferredMediaType(content) : undefined;
378
+ const responseSchema = mediaType ? content[mediaType]?.schema : undefined;
379
+ return [
380
+ {
381
+ ...responseSchema ? buildInlineModel(spec, responseSchema) : createModel({
382
+ export: 'generic',
383
+ type: 'void'
384
+ }),
385
+ name: '',
386
+ code: parseResponseCode(code),
387
+ in: 'response',
388
+ description: response.description ?? null
389
+ }
390
+ ];
391
+ });
392
+ const buildOperation = (spec, path, method, specOp, pathParameters = [])=>{
393
+ const parameters = buildParameters(spec, specOp, pathParameters);
394
+ const responses = buildResponses(spec, specOp);
395
+ const id = specOp.operationId;
396
+ return {
397
+ id,
398
+ name: id,
399
+ method: method.toUpperCase(),
400
+ path,
401
+ description: specOp.description ?? null,
402
+ tags: specOp.tags ?? null,
403
+ deprecated: !!specOp.deprecated,
404
+ parameters,
405
+ parametersBody: parameters.find((p)=>p.in === 'body') ?? null,
406
+ responses,
407
+ imports: collectImports([
408
+ ...parameters,
409
+ ...responses
410
+ ])
411
+ };
412
+ };
413
+ const buildOperations = (spec)=>Object.entries(spec.paths ?? {}).flatMap(([path, pathItemOrRef])=>{
414
+ const pathItem = resolveIfRef(spec, pathItemOrRef);
415
+ if (!pathItem) return [];
416
+ const pathParameters = pathItem.parameters ?? [];
417
+ return HTTP_METHODS.flatMap((method)=>{
418
+ const specOp = pathItem[method];
419
+ return specOp ? [
420
+ buildOperation(spec, path, method, specOp, pathParameters)
421
+ ] : [];
422
+ });
423
+ });
424
+ const buildDefaultService = (spec)=>{
425
+ const operations = buildOperations(spec);
426
+ return {
427
+ name: DEFAULT_SERVICE_NAME,
428
+ operations,
429
+ imports: [
430
+ ...new Set(operations.flatMap((op)=>op.imports))
431
+ ]
432
+ };
433
+ };
434
+ /**
435
+ * Look up the spec operation object for a parsed operation.
436
+ */ export const getSpecOperation = (spec, op)=>spec.paths?.[op.path]?.[op.method.toLowerCase()];
437
+ /**
438
+ * The `in`-qualified key for a parameter, distinguishing parameters that share
439
+ * a name across positions (e.g. `id` in both path and query).
440
+ */ export const specParameterKey = (parameter)=>`${parameter.in}:${parameter.name}`;
441
+ /**
442
+ * An operation's resolved parameters indexed by `in:name`, including any
443
+ * path-level parameters inherited from the containing path item
444
+ * (operation-level parameters take precedence when both declare the same
445
+ * `name` + `in`).
446
+ */ export const getSpecParametersByKey = (spec, specOp, pathParameters = [])=>Object.fromEntries([
447
+ ...pathParameters,
448
+ ...specOp?.parameters ?? []
449
+ ].map((p)=>{
450
+ const param = resolveIfRef(spec, p);
451
+ return [
452
+ specParameterKey(param),
453
+ param
454
+ ];
455
+ }));
456
+ /**
457
+ * The path-level parameters declared on the path item containing an operation.
458
+ */ export const getSpecPathParameters = (spec, op)=>{
459
+ const pathItem = resolveIfRef(spec, spec.paths?.[op.path]);
460
+ return pathItem?.parameters ?? [];
461
+ };
462
+ /**
463
+ * The member sub-schemas of a composite (allOf/anyOf/oneOf) schema, in order.
464
+ */ export const compositeMemberSchemas = (schema, compositeExport)=>{
465
+ const keyword = camelCase(compositeExport);
466
+ return schema[keyword] ?? [];
467
+ };
468
+ /**
469
+ * Recursively stitch a model's collection `link` to the named model it
470
+ * references (ref values are left null by the builders, since the target model
471
+ * does not exist until every definition is built).
472
+ */ export const linkModel = (spec, modelsByName, model, schema, visited = new Set())=>{
473
+ if (visited.has(model)) return;
474
+ visited.add(model);
475
+ if (model.export === 'dictionary' && 'additionalProperties' in schema && schema.additionalProperties) {
476
+ if (isRef(schema.additionalProperties)) {
477
+ const name = splitRef(schema.additionalProperties.$ref)[2];
478
+ if (modelsByName[name] && !model.link) {
479
+ model.link = modelsByName[name];
480
+ }
481
+ } else if (model.link && typeof schema.additionalProperties !== 'boolean') {
482
+ linkModel(spec, modelsByName, model.link, schema.additionalProperties, visited);
483
+ }
484
+ } else if (model.export === 'array' && 'items' in schema && schema.items) {
485
+ if (isRef(schema.items)) {
486
+ const name = splitRef(schema.items.$ref)[2];
487
+ if (modelsByName[name] && !model.link) {
488
+ model.link = modelsByName[name];
489
+ }
490
+ } else if (model.link) {
491
+ linkModel(spec, modelsByName, model.link, schema.items, visited);
492
+ }
493
+ }
494
+ model.properties.filter((p)=>!visited.has(p) && schema.properties?.[trim(p.name, `"'`)]).forEach((property)=>{
495
+ const subSchema = resolveIfRef(spec, schema.properties[trim(property.name, `"'`)]);
496
+ linkModel(spec, modelsByName, property, subSchema, visited);
497
+ });
498
+ if (COMPOSED_SCHEMA_TYPES.has(model.export)) {
499
+ const memberSchemas = compositeMemberSchemas(schema, model.export);
500
+ model.properties.forEach((property, i)=>{
501
+ const subSchema = resolveIfRef(spec, memberSchemas[i]);
502
+ if (subSchema) {
503
+ linkModel(spec, modelsByName, property, subSchema, visited);
504
+ }
505
+ });
506
+ }
507
+ };
508
+ /**
509
+ * Stitch collection links across every model, operation parameter and response.
510
+ */ const linkModels = (spec, data)=>{
511
+ const modelsByName = indexModelsByName(data.models);
512
+ const visited = new Set();
513
+ data.models.forEach((model)=>{
514
+ const schema = resolveIfRef(spec, spec.components?.schemas?.[model.name]);
515
+ if (schema) {
516
+ linkModel(spec, modelsByName, model, schema, visited);
517
+ }
518
+ });
519
+ data.services.forEach((service)=>{
520
+ service.operations.forEach((op)=>{
521
+ const specOp = getSpecOperation(spec, op);
522
+ const specParametersByKey = getSpecParametersByKey(spec, specOp, getSpecPathParameters(spec, op));
523
+ op.parameters.forEach((parameter)=>{
524
+ const specParameter = specParametersByKey[specParameterKey({
525
+ in: parameter.in,
526
+ name: parameter.prop
527
+ })];
528
+ const specParameterSchema = resolveIfRef(spec, specParameter?.schema);
529
+ if (specParameterSchema) {
530
+ linkModel(spec, modelsByName, parameter, specParameterSchema, visited);
531
+ } else if (parameter.in === 'body') {
532
+ const specBody = resolveIfRef(spec, specOp?.requestBody);
533
+ const specBodySchema = resolveIfRef(spec, specBody?.content?.[parameter.mediaType]?.schema);
534
+ if (specBodySchema) {
535
+ linkModel(spec, modelsByName, parameter, specBodySchema, visited);
536
+ }
537
+ }
538
+ });
539
+ op.responses.forEach((response)=>{
540
+ const specResponse = resolveIfRef(spec, specOp?.responses?.[response.code]);
541
+ Object.keys(specResponse?.content ?? {}).forEach((mediaType)=>{
542
+ const responseSchema = resolveIfRef(spec, specResponse?.content?.[mediaType]?.schema);
543
+ if (responseSchema) {
544
+ linkModel(spec, modelsByName, response, responseSchema, visited);
545
+ }
546
+ });
547
+ });
548
+ });
549
+ });
550
+ };
551
+ /**
552
+ * Populate `composedModels` and `composedPrimitives` on each composite model
553
+ * with the models and primitive types it is composed of.
554
+ */ const resolveComposedModel = (modelsByName, model, visited)=>{
555
+ if (!COMPOSED_SCHEMA_TYPES.has(model.export) || visited.has(model)) return;
556
+ visited.add(model);
557
+ const members = model.properties.filter((p)=>!p.name);
558
+ const referenced = members.filter((p)=>p.export === 'reference').flatMap((r)=>modelsByName[r.type] ? [
559
+ modelsByName[r.type]
560
+ ] : []);
561
+ // Resolve recursively so all-of mixins include nested all-of properties
562
+ referenced.forEach((m)=>resolveComposedModel(modelsByName, m, visited));
563
+ // Enums serialise as primitives, so group them with the primitives
564
+ const composedModels = referenced.filter((m)=>m.export !== 'enum');
565
+ const composedPrimitives = [
566
+ ...members.filter((p)=>p.export !== 'reference'),
567
+ ...referenced.filter((m)=>m.export === 'enum')
568
+ ];
569
+ // A composite of multiple non-primitive array members can't be told apart at
570
+ // runtime: each arrives as a plain JSON array with no property to switch on.
571
+ // A `discriminator` can't disambiguate here either — it names a property on
572
+ // an object member, not on an array. Model a polymorphic list as an array of
573
+ // a discriminated union instead (`type: array` whose `items` is the oneOf).
574
+ const isPrimitiveArray = (m)=>m.link && COLLECTION_TYPES.has(m.export) ? isPrimitiveArray(m.link) : PRIMITIVE_TYPES.has(m.type) && ![
575
+ 'date',
576
+ 'date-time'
577
+ ].includes(m.format ?? '');
578
+ const arrayComposedModels = composedPrimitives.filter((m)=>m.export === 'array' && !isPrimitiveArray(m));
579
+ if (arrayComposedModels.length > 1) {
580
+ throw new Error(`Schema "${model.name}" defines ${camelCase(model.export)} with multiple array types which cannot be distinguished at runtime. Model a polymorphic list as an array whose items are a discriminated union instead (a "type: array" schema with a "oneOf" in "items").`);
581
+ }
582
+ if (model.export === 'all-of' && composedPrimitives.length > 0) {
583
+ throw new Error(`Schema "${model.name}" defines allOf with non-object types. allOf may only compose object types in the OpenAPI specification.`);
584
+ }
585
+ model.composedModels = composedModels;
586
+ model.composedPrimitives = composedPrimitives;
587
+ };
588
+ const resolveComposedModels = (data)=>{
589
+ const modelsByName = indexModelsByName(data.models);
590
+ const visited = new Set();
591
+ data.models.forEach((model)=>resolveComposedModel(modelsByName, model, visited));
592
+ };
593
+ const isHoisted = (spec, name)=>!!resolveIfRef(spec, spec.components?.schemas?.[name])?.['x-aws-nx-hoisted'];
594
+ /**
595
+ * The on-the-wire name of a schema: its original name before normalisation to
596
+ * a valid identifier (recorded by the normaliser), or its current name. An
597
+ * implicit discriminator matches on this, not the normalised model name.
598
+ */ const wireName = (spec, name)=>resolveIfRef(spec, spec.components?.schemas?.[name])?.['x-aws-nx-original-name'] ?? name;
599
+ /**
600
+ * The value → model-name mapping for a discriminator, resolved either from an
601
+ * explicit `mapping` (value → schema ref) or implicitly (each candidate model's
602
+ * schema name is its own discriminator value). Only candidates in
603
+ * `candidateNames` are kept, so unknown values fall through to the default
604
+ * marshalling; hoisted synthetic names (which never appear on the wire) are
605
+ * excluded from the implicit form.
606
+ */ const buildDiscriminatorMapping = (spec, discriminator, candidateNames)=>{
607
+ if (discriminator.mapping) {
608
+ const mapping = [];
609
+ for (const [value, ref] of Object.entries(discriminator.mapping)){
610
+ if (typeof ref !== 'string' || !ref.startsWith('#/')) continue;
611
+ const modelName = splitRef(ref)[2];
612
+ if (candidateNames.has(modelName)) mapping.push({
613
+ value,
614
+ modelName
615
+ });
616
+ }
617
+ return mapping;
618
+ }
619
+ return [
620
+ ...candidateNames
621
+ ].filter((name)=>!isHoisted(spec, name))// The wire value is the schema's original name; the model it selects is the
622
+ // normalised name.
623
+ .map((name)=>({
624
+ value: wireName(spec, name),
625
+ modelName: name
626
+ }));
627
+ };
628
+ /**
629
+ * Build the {@link Discriminator} for a `oneOf`/`anyOf` composite, if declared.
630
+ * The candidates are the models composed by the union.
631
+ */ const buildCompositeDiscriminator = (spec, model, schema)=>{
632
+ const { discriminator } = schema;
633
+ if (!discriminator?.propertyName) return undefined;
634
+ const candidateNames = new Set((model.composedModels ?? []).map((m)=>m.name));
635
+ const mapping = buildDiscriminatorMapping(spec, discriminator, candidateNames);
636
+ return mapping.length > 0 ? {
637
+ propertyName: discriminator.propertyName,
638
+ mapping
639
+ } : undefined;
640
+ };
641
+ /**
642
+ * Build the {@link Discriminator} for an inheritance base — an `object` schema
643
+ * carrying a `discriminator` whose subtypes `allOf`-compose it. The candidates
644
+ * are those subtypes (found by scanning every model for one that composes this
645
+ * base). Marked `isBase` so the generator emits a non-dispatching body the
646
+ * subtypes can compose without recursing.
647
+ */ const buildBaseDiscriminator = (spec, base, schema, models)=>{
648
+ const { discriminator } = schema;
649
+ if (!discriminator?.propertyName) return undefined;
650
+ const subtypeNames = new Set(models.filter((m)=>m.export === 'all-of' && (m.composedModels ?? []).some((c)=>c.name === base.name)).map((m)=>m.name));
651
+ if (subtypeNames.size === 0) return undefined;
652
+ const mapping = buildDiscriminatorMapping(spec, discriminator, subtypeNames);
653
+ return mapping.length > 0 ? {
654
+ propertyName: discriminator.propertyName,
655
+ mapping,
656
+ isBase: true
657
+ } : undefined;
658
+ };
659
+ /**
660
+ * Attach discriminator metadata so the generator can marshal directly to the
661
+ * matching branch/subtype. Two shapes carry a discriminator:
662
+ * - a `oneOf`/`anyOf` composite (dispatch to the matching member), and
663
+ * - an inheritance base `object` whose subtypes `allOf`-compose it (dispatch
664
+ * to the matching subtype so subtype-only fields survive marshalling).
665
+ * `allOf` itself is a conjunction (not a choice) so it is never discriminated.
666
+ */ const resolveDiscriminators = (spec, data)=>{
667
+ data.models.forEach((model)=>{
668
+ const schema = resolveIfRef(spec, spec.components?.schemas?.[model.name]);
669
+ if (!schema) return;
670
+ if (model.export === 'one-of' || model.export === 'any-of') {
671
+ const discriminator = buildCompositeDiscriminator(spec, model, schema);
672
+ if (discriminator) model.discriminator = discriminator;
673
+ } else if (model.export === 'interface') {
674
+ const discriminator = buildBaseDiscriminator(spec, model, schema, data.models);
675
+ if (discriminator) model.discriminator = discriminator;
676
+ }
677
+ });
678
+ };
679
+ /**
680
+ * Type each discriminated subtype's discriminator property as its literal
681
+ * value(s), turning `Cat | Dog` into a true TypeScript tagged union that
682
+ * narrows on the discriminator (e.g. `Cat.petType: 'cat'`).
683
+ *
684
+ * The literal is taken from every discriminator's `mapping` (value → subtype).
685
+ * A subtype selected by several values within one discriminator becomes a union
686
+ * of those literals. A subtype that appears in multiple discriminators with
687
+ * conflicting values can't be pinned to a single literal, so it is left as the
688
+ * plain primitive (`string`) — the marshalling dispatch is unaffected either
689
+ * way, this only affects the emitted type.
690
+ */ const resolveDiscriminatorLiterals = (data)=>{
691
+ const modelsByName = indexModelsByName(data.models);
692
+ // Collect, per (subtype, discriminator property), the set of literal values
693
+ // that select it — across every discriminator in the spec.
694
+ const valuesBySubtypeProp = new Map();
695
+ const conflicting = new Set();
696
+ for (const model of data.models){
697
+ const discriminator = model.discriminator;
698
+ if (!discriminator) continue;
699
+ const byName = new Map();
700
+ for (const { value, modelName } of discriminator.mapping){
701
+ byName.set(modelName, [
702
+ ...byName.get(modelName) ?? [],
703
+ value
704
+ ]);
705
+ }
706
+ for (const [modelName, values] of byName){
707
+ const key = `${modelName}${discriminator.propertyName}`;
708
+ const existing = valuesBySubtypeProp.get(key);
709
+ const incoming = new Set(values);
710
+ if (existing && !setsEqual(existing, incoming)) {
711
+ // Same subtype+property tagged differently by another union.
712
+ conflicting.add(key);
713
+ }
714
+ valuesBySubtypeProp.set(key, existing ? union(existing, incoming) : incoming);
715
+ }
716
+ }
717
+ for (const [key, values] of valuesBySubtypeProp){
718
+ if (conflicting.has(key)) continue;
719
+ const [modelName, propertyName] = key.split('');
720
+ const property = modelsByName[modelName]?.properties.find((p)=>p.name === propertyName);
721
+ // Only pin a literal when the property is a plain (string/enum) scalar —
722
+ // never an object/array reference.
723
+ if (property && (property.type === 'string' || property.isEnum)) {
724
+ property.discriminatorValue = [
725
+ ...values
726
+ ].map((v)=>JSON.stringify(v)).join(' | ');
727
+ }
728
+ }
729
+ };
730
+ const setsEqual = (a, b)=>a.size === b.size && [
731
+ ...a
732
+ ].every((v)=>b.has(v));
733
+ const union = (a, b)=>new Set([
734
+ ...a,
735
+ ...b
736
+ ]);
737
+ /**
738
+ * Build the client data structure from a (normalised) OpenAPI spec: a fully
739
+ * linked model graph with composite members resolved, ready for augmentation.
740
+ */ export const buildClientData = (spec)=>{
741
+ const data = {
742
+ models: buildModels(spec),
743
+ services: [
744
+ buildDefaultService(spec)
745
+ ]
746
+ };
747
+ linkModels(spec, data);
748
+ resolveComposedModels(data);
749
+ resolveDiscriminators(spec, data);
750
+ resolveDiscriminatorLiterals(data);
751
+ return data;
752
+ };
753
+
754
+ //# sourceMappingURL=parser.js.map