@azure-tools/typespec-ts 0.50.2 → 0.50.3

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 (66) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/src/framework/hooks/binder.d.ts +1 -1
  3. package/dist/src/framework/hooks/binder.d.ts.map +1 -1
  4. package/dist/src/framework/hooks/binder.js +11 -3
  5. package/dist/src/framework/hooks/binder.js.map +1 -1
  6. package/dist/src/framework/load-static-helpers.d.ts +3 -0
  7. package/dist/src/framework/load-static-helpers.d.ts.map +1 -1
  8. package/dist/src/framework/load-static-helpers.js +49 -38
  9. package/dist/src/framework/load-static-helpers.js.map +1 -1
  10. package/dist/src/index.d.ts.map +1 -1
  11. package/dist/src/index.js +19 -10
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/lib.d.ts +7 -0
  14. package/dist/src/lib.d.ts.map +1 -1
  15. package/dist/src/lib.js +5 -0
  16. package/dist/src/lib.js.map +1 -1
  17. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  18. package/dist/src/modular/buildOperations.js +1 -1
  19. package/dist/src/modular/buildOperations.js.map +1 -1
  20. package/dist/src/modular/emitModels.d.ts +8 -0
  21. package/dist/src/modular/emitModels.d.ts.map +1 -1
  22. package/dist/src/modular/emitModels.js +32 -2
  23. package/dist/src/modular/emitModels.js.map +1 -1
  24. package/dist/src/modular/emitSamples.js +9 -4
  25. package/dist/src/modular/emitSamples.js.map +1 -1
  26. package/dist/src/modular/emitTests.d.ts +7 -0
  27. package/dist/src/modular/emitTests.d.ts.map +1 -0
  28. package/dist/src/modular/emitTests.js +160 -0
  29. package/dist/src/modular/emitTests.js.map +1 -0
  30. package/dist/src/modular/external-dependencies.d.ts +42 -0
  31. package/dist/src/modular/external-dependencies.d.ts.map +1 -1
  32. package/dist/src/modular/external-dependencies.js +42 -0
  33. package/dist/src/modular/external-dependencies.js.map +1 -1
  34. package/dist/src/modular/helpers/exampleValueHelpers.d.ts +83 -0
  35. package/dist/src/modular/helpers/exampleValueHelpers.d.ts.map +1 -0
  36. package/dist/src/modular/helpers/exampleValueHelpers.js +631 -0
  37. package/dist/src/modular/helpers/exampleValueHelpers.js.map +1 -0
  38. package/dist/src/modular/helpers/operationHelpers.d.ts +22 -2
  39. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  40. package/dist/src/modular/helpers/operationHelpers.js +178 -9
  41. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  42. package/dist/src/modular/static-helpers-metadata.d.ts +12 -0
  43. package/dist/src/modular/static-helpers-metadata.d.ts.map +1 -1
  44. package/dist/src/modular/static-helpers-metadata.js +12 -0
  45. package/dist/src/modular/static-helpers-metadata.js.map +1 -1
  46. package/dist/src/transform/transfromRLCOptions.d.ts.map +1 -1
  47. package/dist/src/transform/transfromRLCOptions.js +10 -0
  48. package/dist/src/transform/transfromRLCOptions.js.map +1 -1
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +2 -2
  51. package/src/framework/hooks/binder.ts +15 -5
  52. package/src/framework/load-static-helpers.ts +79 -51
  53. package/src/index.ts +22 -7
  54. package/src/lib.ts +13 -0
  55. package/src/modular/buildOperations.ts +2 -1
  56. package/src/modular/emitModels.ts +47 -2
  57. package/src/modular/emitSamples.ts +7 -1
  58. package/src/modular/emitTests.ts +227 -0
  59. package/src/modular/external-dependencies.ts +43 -0
  60. package/src/modular/helpers/exampleValueHelpers.ts +940 -0
  61. package/src/modular/helpers/operationHelpers.ts +229 -17
  62. package/src/modular/static-helpers-metadata.ts +13 -0
  63. package/src/transform/transfromRLCOptions.ts +14 -0
  64. package/static/static-helpers/serialization/get-binary-response-body-browser.mts +22 -0
  65. package/static/static-helpers/serialization/get-binary-response-body.ts +24 -0
  66. package/static/test-helpers/recordedClient.ts +30 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.50.2",
3
+ "version": "0.50.3",
4
4
  "description": "An experimental TypeSpec emitter for TypeScript RLC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
@@ -73,7 +73,7 @@
73
73
  "@typespec/xml": "^0.79.0"
74
74
  },
75
75
  "dependencies": {
76
- "@azure-tools/rlc-common": "^0.50.2",
76
+ "@azure-tools/rlc-common": "^0.50.3",
77
77
  "fast-xml-parser": "^4.5.0",
78
78
  "fs-extra": "^11.1.0",
79
79
  "lodash": "^4.17.21",
@@ -42,7 +42,7 @@ export interface Binder {
42
42
  sourceFile: SourceFile
43
43
  ): string;
44
44
  resolveReference(refkey: unknown): string;
45
- resolveAllReferences(sourceRoot: string): void;
45
+ resolveAllReferences(sourceRoot: string, testRoot?: string): void;
46
46
  }
47
47
 
48
48
  const PLACEHOLDER_PREFIX = "__PLACEHOLDER_";
@@ -216,7 +216,7 @@ class BinderImp implements Binder {
216
216
  /**
217
217
  * Applies all tracked imports to their respective source files.
218
218
  */
219
- resolveAllReferences(sourceRoot: string): void {
219
+ resolveAllReferences(sourceRoot: string, testRoot?: string): void {
220
220
  this.project.getSourceFiles().map((file) => {
221
221
  this.resolveDeclarationReferences(file);
222
222
  this.resolveDependencyReferences(file);
@@ -232,7 +232,7 @@ class BinderImp implements Binder {
232
232
  }
233
233
  });
234
234
 
235
- this.cleanUnreferencedHelpers(sourceRoot);
235
+ this.cleanUnreferencedHelpers(sourceRoot, testRoot);
236
236
  }
237
237
 
238
238
  private resolveDependencyReferences(file: SourceFile) {
@@ -292,7 +292,7 @@ class BinderImp implements Binder {
292
292
  this.references.get(refkey)!.add(sourceFile);
293
293
  }
294
294
 
295
- private cleanUnreferencedHelpers(sourceRoot: string) {
295
+ private cleanUnreferencedHelpers(sourceRoot: string, testRoot?: string) {
296
296
  const usedHelperNames: string[] = [];
297
297
  for (const helper of this.staticHelpers.values()) {
298
298
  const sourceFile = helper[SourceFileSymbol];
@@ -311,7 +311,6 @@ class BinderImp implements Binder {
311
311
 
312
312
  function isFileUnused(file: SourceFile) {
313
313
  const name = file.getBaseNameWithoutExtension();
314
-
315
314
  // If one of the used helpers' name is a prefix of this file, the file likely represents a platform-specific implementation of the helper
316
315
  // so it should be marked as used even if the file has no direct references.
317
316
  return !usedHelperNames.some((s) => name.startsWith(s));
@@ -324,6 +323,17 @@ class BinderImp implements Binder {
324
323
  )
325
324
  .filter(isFileUnused)
326
325
  .forEach((helperFile) => helperFile.delete());
326
+
327
+ if (!testRoot) {
328
+ return;
329
+ }
330
+ this.project
331
+ //normalizae the final path to adapt to different systems
332
+ .getSourceFiles(
333
+ normalizePath(path.join(testRoot, "test/generated/util/**/*.*ts"))
334
+ )
335
+ .filter(isFileUnused)
336
+ .forEach((helperFile) => helperFile.delete());
327
337
  }
328
338
  }
329
339
 
@@ -37,12 +37,21 @@ export function isStaticHelperMetadata(
37
37
 
38
38
  export type StaticHelpers = Record<string, StaticHelperMetadata>;
39
39
 
40
- const DEFAULT_STATIC_HELPERS_PATH = "static/static-helpers";
40
+ const DEFAULT_SOURCES_STATIC_HELPERS_PATH = "static/static-helpers";
41
+ const DEFAULT_SOURCES_TESTING_HELPERS_PATH = "static/test-helpers";
41
42
 
42
43
  export interface LoadStaticHelpersOptions extends Partial<ModularEmitterOptions> {
43
44
  helpersAssetDirectory?: string;
44
45
  sourcesDir?: string;
46
+ rootDir?: string;
45
47
  program?: Program;
48
+ /** When true, also load test helpers from static/test-helpers/ into test/generated/util/ */
49
+ loadTestHelpers?: boolean;
50
+ }
51
+
52
+ interface FileMetadata {
53
+ source: string;
54
+ target: string;
46
55
  }
47
56
 
48
57
  export async function loadStaticHelpers(
@@ -50,64 +59,85 @@ export async function loadStaticHelpers(
50
59
  helpers: StaticHelpers,
51
60
  options: LoadStaticHelpersOptions = {}
52
61
  ): Promise<Map<string, StaticHelperMetadata>> {
53
- const sourcesDir = options.sourcesDir ?? "";
54
62
  const helpersMap = new Map<string, StaticHelperMetadata>();
63
+ // Load static helpers used in sources code
55
64
  const defaultStaticHelpersPath = path.join(
56
65
  resolveProjectRoot(),
57
- DEFAULT_STATIC_HELPERS_PATH
66
+ DEFAULT_SOURCES_STATIC_HELPERS_PATH
58
67
  );
59
- const files = await traverseDirectory(
68
+ const filesInSources = await traverseDirectory(
60
69
  options.helpersAssetDirectory ?? defaultStaticHelpersPath,
61
70
  options.program
62
71
  );
72
+ await loadFiles(filesInSources, options.sourcesDir ?? "");
73
+ // Load static helpers used in testing code (only when loadTestHelpers is enabled)
74
+ if (
75
+ options.loadTestHelpers ??
76
+ (options.options?.generateTest &&
77
+ isAzurePackage({ options: options.options }))
78
+ ) {
79
+ const defaultTestingHelpersPath = path.join(
80
+ resolveProjectRoot(),
81
+ DEFAULT_SOURCES_TESTING_HELPERS_PATH
82
+ );
83
+ const filesInTestings = await traverseDirectory(
84
+ defaultTestingHelpersPath,
85
+ options.program,
86
+ [],
87
+ "",
88
+ "test/generated/util"
89
+ );
90
+ await loadFiles(filesInTestings, options.rootDir ?? "");
91
+ }
92
+ return assertAllHelpersLoadedPresent(helpersMap);
63
93
 
64
- for (const file of files) {
65
- const targetPath = path.join(sourcesDir, file.target);
66
- const contents = await readFile(file.source, "utf-8");
67
- const addedFile = project.createSourceFile(targetPath, contents, {
68
- overwrite: true
69
- });
70
- addedFile.getImportDeclarations().map((i) => {
71
- if (!isAzurePackage({ options: options.options })) {
72
- if (
73
- i
74
- .getModuleSpecifier()
75
- .getFullText()
76
- .includes("@azure/core-rest-pipeline")
77
- ) {
78
- i.setModuleSpecifier("@typespec/ts-http-runtime");
94
+ async function loadFiles(files: FileMetadata[], generateDir: string) {
95
+ for (const file of files) {
96
+ const targetPath = path.join(generateDir, file.target);
97
+ const contents = await readFile(file.source, "utf-8");
98
+ const addedFile = project.createSourceFile(targetPath, contents, {
99
+ overwrite: true
100
+ });
101
+ addedFile.getImportDeclarations().map((i) => {
102
+ if (!isAzurePackage({ options: options.options })) {
103
+ if (
104
+ i
105
+ .getModuleSpecifier()
106
+ .getFullText()
107
+ .includes("@azure/core-rest-pipeline")
108
+ ) {
109
+ i.setModuleSpecifier("@typespec/ts-http-runtime");
110
+ }
111
+ if (
112
+ i
113
+ .getModuleSpecifier()
114
+ .getFullText()
115
+ .includes("@azure-rest/core-client")
116
+ ) {
117
+ i.setModuleSpecifier("@typespec/ts-http-runtime");
118
+ }
79
119
  }
80
- if (
81
- i
82
- .getModuleSpecifier()
83
- .getFullText()
84
- .includes("@azure-rest/core-client")
85
- ) {
86
- i.setModuleSpecifier("@typespec/ts-http-runtime");
120
+ });
121
+
122
+ for (const entry of Object.values(helpers)) {
123
+ if (!addedFile.getFilePath().endsWith(entry.location)) {
124
+ continue;
87
125
  }
88
- }
89
- });
90
126
 
91
- for (const entry of Object.values(helpers)) {
92
- if (!addedFile.getFilePath().endsWith(entry.location)) {
93
- continue;
94
- }
127
+ const declaration = getDeclarationByMetadata(addedFile, entry);
128
+ if (!declaration) {
129
+ throw new Error(
130
+ `Declaration ${
131
+ entry.name
132
+ } not found in file ${addedFile.getFilePath()}\n This is an Emitter bug, make sure that the map of static helpers passed to loadStaticHelpers matches what is in the file.`
133
+ );
134
+ }
95
135
 
96
- const declaration = getDeclarationByMetadata(addedFile, entry);
97
- if (!declaration) {
98
- throw new Error(
99
- `Declaration ${
100
- entry.name
101
- } not found in file ${addedFile.getFilePath()}\n This is an Emitter bug, make sure that the map of static helpers passed to loadStaticHelpers matches what is in the file.`
102
- );
136
+ entry[SourceFileSymbol] = addedFile;
137
+ helpersMap.set(refkey(entry), entry);
103
138
  }
104
-
105
- entry[SourceFileSymbol] = addedFile;
106
- helpersMap.set(refkey(entry), entry);
107
139
  }
108
140
  }
109
-
110
- return assertAllHelpersLoadedPresent(helpersMap);
111
141
  }
112
142
 
113
143
  function assertAllHelpersLoadedPresent(
@@ -166,7 +196,8 @@ async function traverseDirectory(
166
196
  directory: string,
167
197
  program?: Program,
168
198
  result: { source: string; target: string }[] = [],
169
- relativePath: string = ""
199
+ relativePath: string = "",
200
+ targetBaseDir: string = _targetStaticHelpersBaseDir
170
201
  ): Promise<{ source: string; target: string }[]> {
171
202
  try {
172
203
  const files = await readdir(directory);
@@ -181,18 +212,15 @@ async function traverseDirectory(
181
212
  filePath,
182
213
  program,
183
214
  result,
184
- path.join(relativePath, file)
215
+ path.join(relativePath, file),
216
+ targetBaseDir
185
217
  );
186
218
  } else if (
187
219
  fileStat.isFile() &&
188
220
  !file.endsWith(".d.ts") &&
189
221
  /.*\..?ts$/.test(file)
190
222
  ) {
191
- const target = path.join(
192
- _targetStaticHelpersBaseDir,
193
- relativePath,
194
- file
195
- );
223
+ const target = path.join(targetBaseDir, relativePath, file);
196
224
  result.push({ source: filePath, target });
197
225
  }
198
226
  })
package/src/index.ts CHANGED
@@ -6,13 +6,15 @@ import {
6
6
  AzureCoreDependencies,
7
7
  AzureIdentityDependencies,
8
8
  AzurePollingDependencies,
9
- DefaultCoreDependencies
9
+ DefaultCoreDependencies,
10
+ AzureTestDependencies
10
11
  } from "./modular/external-dependencies.js";
11
12
  import { clearDirectory } from "./utils/fileSystemUtils.js";
12
13
  import { EmitContext, Program } from "@typespec/compiler";
13
14
  import { GenerationDirDetail, SdkContext } from "./utils/interfaces.js";
14
15
  import {
15
16
  CloudSettingHelpers,
17
+ CreateRecorderHelpers,
16
18
  MultipartHelpers,
17
19
  PagingHelpers,
18
20
  PollingHelpers,
@@ -91,7 +93,7 @@ import {
91
93
  } from "@azure-tools/typespec-client-generator-core";
92
94
  import { transformModularEmitterOptions } from "./modular/buildModularOptions.js";
93
95
  import { emitLoggerFile } from "./modular/emitLoggerFile.js";
94
- import { emitTypes } from "./modular/emitModels.js";
96
+ import { emitTypes, emitNonModelResponseTypes } from "./modular/emitModels.js";
95
97
  import { existsSync } from "fs";
96
98
  import { getModuleExports } from "./modular/buildProjectFiles.js";
97
99
  import {
@@ -107,6 +109,7 @@ import { provideSdkTypes } from "./framework/hooks/sdkTypes.js";
107
109
  import { transformRLCModel } from "./transform/transform.js";
108
110
  import { transformRLCOptions } from "./transform/transfromRLCOptions.js";
109
111
  import { emitSamples } from "./modular/emitSamples.js";
112
+ import { emitTests } from "./modular/emitTests.js";
110
113
  import { generateCrossLanguageDefinitionFile } from "./utils/crossLanguageDef.js";
111
114
  import { getClassicalClientName } from "./modular/helpers/namingHelpers.js";
112
115
 
@@ -148,10 +151,12 @@ export async function $onEmit(context: EmitContext) {
148
151
  ...MultipartHelpers,
149
152
  ...CloudSettingHelpers,
150
153
  ...XmlHelpers,
154
+ ...(rlcOptions.generateTest ? CreateRecorderHelpers : {}),
151
155
  ...(rlcOptions.enableStorageCompat ? StorageCompatHelpers : {})
152
156
  },
153
157
  {
154
158
  sourcesDir: dpgContext.generationPathDetail?.modularSourcesDir,
159
+ rootDir: dpgContext.generationPathDetail?.rootDir,
155
160
  options: rlcOptions,
156
161
  program
157
162
  }
@@ -160,7 +165,8 @@ export async function $onEmit(context: EmitContext) {
160
165
  ? {
161
166
  ...AzurePollingDependencies,
162
167
  ...AzureCoreDependencies,
163
- ...AzureIdentityDependencies
168
+ ...AzureIdentityDependencies,
169
+ ...AzureTestDependencies
164
170
  }
165
171
  : { ...DefaultCoreDependencies };
166
172
  const binder = provideBinder(outputProject, {
@@ -327,6 +333,7 @@ export async function $onEmit(context: EmitContext) {
327
333
  );
328
334
 
329
335
  emitTypes(dpgContext, { sourceRoot: modularSourcesRoot });
336
+ emitNonModelResponseTypes(dpgContext, { sourceRoot: modularSourcesRoot });
330
337
  buildSubpathIndexFile(modularEmitterOptions, "models", undefined, {
331
338
  recursive: true
332
339
  });
@@ -381,7 +388,15 @@ export async function $onEmit(context: EmitContext) {
381
388
  }
382
389
  }
383
390
 
384
- binder.resolveAllReferences(modularSourcesRoot);
391
+ // Enable modular test generation when generateTest is true
392
+ if (dpgContext.rlcOptions?.generateTest === true) {
393
+ await emitTests(dpgContext);
394
+ }
395
+
396
+ binder.resolveAllReferences(
397
+ modularSourcesRoot,
398
+ dpgContext.generationPathDetail?.rootDir
399
+ );
385
400
  if (program.compilerOptions.noEmit || program.hasError()) {
386
401
  return;
387
402
  }
@@ -453,7 +468,7 @@ export async function $onEmit(context: EmitContext) {
453
468
  "test"
454
469
  );
455
470
  const hasTestFolder = await existsSync(existingTestFolderPath);
456
- if (option.azureSdkForJs && option.generateTest === undefined) {
471
+ if (option.generateTest === undefined) {
457
472
  if (hasTestFolder) {
458
473
  option.generateTest = false;
459
474
  } else {
@@ -535,7 +550,7 @@ export async function $onEmit(context: EmitContext) {
535
550
  }
536
551
 
537
552
  // TODO: need support snippets generation for multi-client cases. https://github.com/Azure/autorest.typescript/issues/3048
538
- if (option.generateTest && isAzureFlavor) {
553
+ if (option.generateTest) {
539
554
  for (const subClient of dpgContext.sdkPackage.clients) {
540
555
  commonBuilders.push((model) =>
541
556
  buildSnippets(model, subClient.name, option.azureSdkForJs)
@@ -632,7 +647,7 @@ export async function $onEmit(context: EmitContext) {
632
647
  }
633
648
 
634
649
  // Generate test relevant files
635
- if (option.generateTest && isAzureFlavor && !hasTestFolder) {
650
+ if (option.generateTest && !hasTestFolder) {
636
651
  await emitContentByBuilder(
637
652
  program,
638
653
  [buildRecordedClientFile, buildSampleTest],
package/src/lib.ts CHANGED
@@ -79,6 +79,13 @@ export interface EmitterOptions {
79
79
  //TODO should remove this after finish the release tool test
80
80
  "should-use-pnpm-dep"?: boolean;
81
81
  "ignore-nullable-on-optional"?: boolean;
82
+ /**
83
+ * When set to true (default for Azure services), non-model return types (arrays, scalars, enums,
84
+ * bytes with binary content type) will be wrapped in an XxxResponse type to maintain backward
85
+ * compatibility with HLC-generated code during TypeSpec migration.
86
+ * Set to false to return the non-model types directly.
87
+ */
88
+ "wrap-non-model-return"?: boolean;
82
89
  /**
83
90
  * When enabled, every regular (non-LRO, non-paging) operation return type is augmented with a
84
91
  * `_response` property containing `rawResponse`, `parsedBody`, and `parsedHeaders`.
@@ -370,6 +377,12 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
370
377
  description:
371
378
  "If an optional property is also marked as nullable, it will be treated as just optional. Defaults to `true` for Azure services."
372
379
  },
380
+ "wrap-non-model-return": {
381
+ type: "boolean",
382
+ nullable: true,
383
+ description:
384
+ "When set to true (default for Azure services), non-model return types (arrays, scalars, enums, bytes with binary content type) will be wrapped in an XxxResponse type for HLC backward compatibility during TypeSpec migration."
385
+ },
373
386
  "enable-storage-compat": {
374
387
  type: "boolean",
375
388
  nullable: true,
@@ -92,7 +92,7 @@ export function buildOperationFiles(
92
92
  );
93
93
  const deserializeOperationDeclaration = getDeserializePrivateFunction(
94
94
  dpgContext,
95
- op
95
+ [prefixes, op]
96
96
  );
97
97
  const deserializeHeadersDeclaration =
98
98
  getDeserializeHeadersPrivateFunction(dpgContext, op);
@@ -108,6 +108,7 @@ export function buildOperationFiles(
108
108
  if (deserializeExceptionHeadersDeclaration) {
109
109
  functionsToAdd.push(deserializeExceptionHeadersDeclaration);
110
110
  }
111
+
111
112
  operationGroupFile.addFunctions(functionsToAdd);
112
113
  addDeclaration(
113
114
  operationGroupFile,
@@ -79,9 +79,15 @@ import {
79
79
  flattenPropertyModelMap,
80
80
  getAllOperationsFromClient
81
81
  } from "../framework/hooks/sdkTypes.js";
82
- import { getAllAncestors } from "./helpers/operationHelpers.js";
83
- import { getAllProperties } from "./helpers/operationHelpers.js";
82
+ import {
83
+ getAllAncestors,
84
+ getAllProperties,
85
+ buildNonModelResponseTypeDeclaration,
86
+ checkWrapNonModelReturn
87
+ } from "./helpers/operationHelpers.js";
84
88
  import { getDirectSubtypes } from "./helpers/typeHelpers.js";
89
+ import { getClientHierarchyMap } from "../utils/clientUtils.js";
90
+ import { getMethodHierarchiesMap } from "../utils/operationUtil.js";
85
91
 
86
92
  type InterfaceStructure = OptionalKind<InterfaceDeclarationStructure> & {
87
93
  extends?: string[];
@@ -164,6 +170,45 @@ export function emitTypes(
164
170
  return result;
165
171
  }
166
172
 
173
+ /**
174
+ * Emits the XxxResponse wrapper type aliases for non-model return operations
175
+ * into the models.ts file, so they are exported publicly as part of the models API surface.
176
+ * This must be called after emitTypes() and before buildSubpathIndexFile("models").
177
+ */
178
+ export function emitNonModelResponseTypes(
179
+ context: SdkContext,
180
+ { sourceRoot }: { sourceRoot: string }
181
+ ) {
182
+ const outputProject = useContext("outputProject");
183
+ const clientMap = getClientHierarchyMap(context);
184
+
185
+ const filepath = getModelsPath(sourceRoot);
186
+ let modelsFile = outputProject.getSourceFile(filepath);
187
+
188
+ for (const subClient of clientMap) {
189
+ const methodHierarchies = getMethodHierarchiesMap(context, subClient[1]);
190
+ for (const [prefixKey, operations] of methodHierarchies) {
191
+ const prefixes = prefixKey.split("/");
192
+ for (const op of operations) {
193
+ const { shouldWrap, isBinary } = checkWrapNonModelReturn(context, op);
194
+ if (!shouldWrap) {
195
+ continue;
196
+ }
197
+ if (!modelsFile) {
198
+ modelsFile = outputProject.createSourceFile(filepath);
199
+ }
200
+ const method: [string[], typeof op] = [prefixes, op];
201
+ const typeAlias = buildNonModelResponseTypeDeclaration(
202
+ context,
203
+ method,
204
+ isBinary
205
+ );
206
+ addDeclaration(modelsFile, typeAlias, refkey(op, "response"));
207
+ }
208
+ }
209
+ }
210
+ }
211
+
167
212
  function emitType(context: SdkContext, type: SdkType, sourceFile: SourceFile) {
168
213
  if (type.kind === "model") {
169
214
  if (isAzureCoreErrorType(context.program, type.__raw)) {
@@ -627,9 +627,15 @@ function getParameterValue(
627
627
  propRetValue =
628
628
  paramValue.length > 2 ? paramValue.slice(1, -1) : undefined;
629
629
  } else {
630
+ // Don't propagate enableFlatten:false to deeper levels — it's only
631
+ // meant to block consecutive (transition) flatten at the direct child
632
+ // level. Non-flatten properties should recurse with default behavior
633
+ // so that independent inner flattens at deeper levels still work.
634
+ const childOptions =
635
+ options?.overrides?.enableFlatten === false ? undefined : options;
630
636
  propRetValue =
631
637
  `"${mapper.get(propName) ?? propName}": ` +
632
- getParameterValue(context, propValue, options);
638
+ getParameterValue(context, propValue, childOptions);
633
639
  }
634
640
  if (propRetValue) values.push(propRetValue);
635
641
  }