@azure-tools/typespec-ts 0.49.0 → 0.49.1

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 (106) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/src/framework/hooks/binder.d.ts.map +1 -1
  3. package/dist/src/framework/hooks/binder.js +8 -17
  4. package/dist/src/framework/hooks/binder.js.map +1 -1
  5. package/dist/src/index.d.ts.map +1 -1
  6. package/dist/src/index.js +13 -11
  7. package/dist/src/index.js.map +1 -1
  8. package/dist/src/lib.d.ts +8 -0
  9. package/dist/src/lib.d.ts.map +1 -1
  10. package/dist/src/lib.js +10 -0
  11. package/dist/src/lib.js.map +1 -1
  12. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  13. package/dist/src/modular/buildOperations.js +10 -5
  14. package/dist/src/modular/buildOperations.js.map +1 -1
  15. package/dist/src/modular/buildProjectFiles.d.ts.map +1 -1
  16. package/dist/src/modular/buildProjectFiles.js +13 -10
  17. package/dist/src/modular/buildProjectFiles.js.map +1 -1
  18. package/dist/src/modular/emitModels.d.ts.map +1 -1
  19. package/dist/src/modular/emitModels.js +43 -40
  20. package/dist/src/modular/emitModels.js.map +1 -1
  21. package/dist/src/modular/emitSamples.d.ts.map +1 -1
  22. package/dist/src/modular/emitSamples.js +9 -0
  23. package/dist/src/modular/emitSamples.js.map +1 -1
  24. package/dist/src/modular/helpers/clientHelpers.d.ts.map +1 -1
  25. package/dist/src/modular/helpers/clientHelpers.js +4 -1
  26. package/dist/src/modular/helpers/clientHelpers.js.map +1 -1
  27. package/dist/src/modular/helpers/namingHelpers.d.ts +7 -0
  28. package/dist/src/modular/helpers/namingHelpers.d.ts.map +1 -1
  29. package/dist/src/modular/helpers/namingHelpers.js +15 -0
  30. package/dist/src/modular/helpers/namingHelpers.js.map +1 -1
  31. package/dist/src/modular/helpers/operationHelpers.d.ts +19 -2
  32. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  33. package/dist/src/modular/helpers/operationHelpers.js +410 -39
  34. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  35. package/dist/src/modular/serialization/buildDeserializerFunction.d.ts.map +1 -1
  36. package/dist/src/modular/serialization/buildDeserializerFunction.js +9 -3
  37. package/dist/src/modular/serialization/buildDeserializerFunction.js.map +1 -1
  38. package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts +12 -0
  39. package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts.map +1 -1
  40. package/dist/src/modular/serialization/buildXmlSerializerFunction.js +259 -18
  41. package/dist/src/modular/serialization/buildXmlSerializerFunction.js.map +1 -1
  42. package/dist/src/modular/static-helpers-metadata.d.ts +10 -0
  43. package/dist/src/modular/static-helpers-metadata.d.ts.map +1 -1
  44. package/dist/src/modular/static-helpers-metadata.js +10 -0
  45. package/dist/src/modular/static-helpers-metadata.js.map +1 -1
  46. package/dist/src/modular/type-expressions/get-model-expression.d.ts +3 -1
  47. package/dist/src/modular/type-expressions/get-model-expression.d.ts.map +1 -1
  48. package/dist/src/modular/type-expressions/get-model-expression.js +50 -9
  49. package/dist/src/modular/type-expressions/get-model-expression.js.map +1 -1
  50. package/dist/src/modular/type-expressions/get-nullable-expression.d.ts.map +1 -1
  51. package/dist/src/modular/type-expressions/get-nullable-expression.js +8 -0
  52. package/dist/src/modular/type-expressions/get-nullable-expression.js.map +1 -1
  53. package/dist/src/modular/type-expressions/get-type-expression.d.ts +3 -2
  54. package/dist/src/modular/type-expressions/get-type-expression.d.ts.map +1 -1
  55. package/dist/src/modular/type-expressions/get-type-expression.js.map +1 -1
  56. package/dist/src/transform/transform.d.ts.map +1 -1
  57. package/dist/src/transform/transform.js +10 -10
  58. package/dist/src/transform/transform.js.map +1 -1
  59. package/dist/src/transform/transformSchemas.d.ts.map +1 -1
  60. package/dist/src/transform/transformSchemas.js +4 -4
  61. package/dist/src/transform/transformSchemas.js.map +1 -1
  62. package/dist/src/transform/transfromRLCOptions.d.ts +1 -1
  63. package/dist/src/transform/transfromRLCOptions.d.ts.map +1 -1
  64. package/dist/src/transform/transfromRLCOptions.js +37 -24
  65. package/dist/src/transform/transfromRLCOptions.js.map +1 -1
  66. package/dist/src/utils/clientUtils.d.ts +1 -1
  67. package/dist/src/utils/clientUtils.d.ts.map +1 -1
  68. package/dist/src/utils/clientUtils.js +40 -17
  69. package/dist/src/utils/clientUtils.js.map +1 -1
  70. package/dist/src/utils/crossLanguageDef.d.ts.map +1 -1
  71. package/dist/src/utils/crossLanguageDef.js +9 -3
  72. package/dist/src/utils/crossLanguageDef.js.map +1 -1
  73. package/dist/src/utils/interfaces.d.ts +2 -1
  74. package/dist/src/utils/interfaces.d.ts.map +1 -1
  75. package/dist/src/utils/modelUtils.d.ts +2 -2
  76. package/dist/src/utils/modelUtils.d.ts.map +1 -1
  77. package/dist/src/utils/modelUtils.js +15 -16
  78. package/dist/src/utils/modelUtils.js.map +1 -1
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +22 -22
  81. package/src/framework/hooks/binder.ts +12 -22
  82. package/src/index.ts +17 -10
  83. package/src/lib.ts +20 -0
  84. package/src/modular/buildOperations.ts +12 -4
  85. package/src/modular/buildProjectFiles.ts +19 -16
  86. package/src/modular/emitModels.ts +72 -44
  87. package/src/modular/emitSamples.ts +11 -1
  88. package/src/modular/helpers/clientHelpers.ts +5 -1
  89. package/src/modular/helpers/namingHelpers.ts +19 -0
  90. package/src/modular/helpers/operationHelpers.ts +533 -40
  91. package/src/modular/serialization/buildDeserializerFunction.ts +11 -2
  92. package/src/modular/serialization/buildXmlSerializerFunction.ts +375 -24
  93. package/src/modular/static-helpers-metadata.ts +10 -0
  94. package/src/modular/type-expressions/get-model-expression.ts +78 -13
  95. package/src/modular/type-expressions/get-nullable-expression.ts +9 -0
  96. package/src/modular/type-expressions/get-type-expression.ts +3 -1
  97. package/src/transform/transform.ts +5 -2
  98. package/src/transform/transformSchemas.ts +4 -1
  99. package/src/transform/transfromRLCOptions.ts +65 -24
  100. package/src/utils/clientUtils.ts +49 -16
  101. package/src/utils/crossLanguageDef.ts +8 -0
  102. package/src/utils/interfaces.ts +2 -1
  103. package/src/utils/modelUtils.ts +22 -18
  104. package/static/static-helpers/serialization/serialize-record.ts +3 -3
  105. package/static/static-helpers/serialization/xml-helpers.ts +91 -36
  106. package/static/static-helpers/urlTemplate.ts +5 -5
@@ -17,7 +17,11 @@ import {
17
17
  isSpreadBodyParameter,
18
18
  isTypeNullable
19
19
  } from "./typeHelpers.js";
20
- import { getClassicalLayerPrefix, getOperationName } from "./namingHelpers.js";
20
+ import {
21
+ getClassicalLayerPrefix,
22
+ getOperationName,
23
+ generateLocallyUniqueName
24
+ } from "./namingHelpers.js";
21
25
  import {
22
26
  getCollectionFormatHelper,
23
27
  hasCollectionFormatInfo,
@@ -67,9 +71,11 @@ import {
67
71
  } from "../type-expressions/get-type-expression.js";
68
72
  import { SdkContext } from "../../utils/interfaces.js";
69
73
  import {
74
+ getClientOptions,
70
75
  isHttpMetadata,
71
76
  isReadOnly,
72
77
  SdkBodyParameter,
78
+ SdkClientType,
73
79
  SdkConstantType,
74
80
  SdkEnumType,
75
81
  SdkHttpOperation,
@@ -81,16 +87,19 @@ import {
81
87
  SdkModelPropertyType,
82
88
  SdkModelType,
83
89
  SdkPagingServiceMethod,
90
+ SdkServiceResponseHeader,
84
91
  SdkType
85
92
  } from "@azure-tools/typespec-client-generator-core";
86
- import { isMetadata } from "@typespec/http";
93
+ import { isHeader, isMetadata } from "@typespec/http";
87
94
  import { useContext } from "../../contextManager.js";
88
95
  import { isExtensibleEnum } from "../type-expressions/get-enum-expression.js";
96
+ import { emitInlineModel } from "../type-expressions/get-model-expression.js";
89
97
 
90
98
  export function getSendPrivateFunction(
91
99
  dpgContext: SdkContext,
92
100
  method: [string[], ServiceOperation],
93
- clientType: string
101
+ clientType: string,
102
+ client?: SdkClientType<SdkHttpOperation>
94
103
  ): OptionalKind<FunctionDeclarationStructure> {
95
104
  const operation = method[1];
96
105
  const parameters = getOperationSignatureParameters(
@@ -119,12 +128,23 @@ export function getSendPrivateFunction(
119
128
  ...getQueryParameters(dpgContext, operation)
120
129
  ];
121
130
  if (urlTemplateParams.length > 0) {
122
- statements.push(`const path = ${resolveReference(UrlTemplateHelpers.parseTemplate)}("${operation.operation.uriTemplate}", {
131
+ // Generate a unique local variable name that doesn't conflict with parameter names
132
+ const paramNames = new Set(parameters.map((p) => p.name));
133
+ const pathVarName = generateLocallyUniqueName("path", paramNames);
134
+ const includeRootSlash = client
135
+ ? getClientOptions(client, "includeRootSlash") !== false
136
+ : true;
137
+
138
+ const uriTemplate = includeRootSlash
139
+ ? operation.operation.uriTemplate
140
+ : operation.operation.uriTemplate.replace(/^\//, "");
141
+
142
+ statements.push(`const ${pathVarName} = ${resolveReference(UrlTemplateHelpers.parseTemplate)}("${uriTemplate}", {
123
143
  ${urlTemplateParams.join(",\n")}
124
144
  },{
125
145
  allowReserved: ${optionalParamName}?.requestOptions?.skipUrlEncoding
126
146
  });`);
127
- pathStr = "path";
147
+ pathStr = pathVarName;
128
148
  }
129
149
 
130
150
  statements.push(
@@ -158,6 +178,7 @@ export function getDeserializePrivateFunction(
158
178
  ];
159
179
  const isLroOnly = isLroOnlyOperation(operation);
160
180
  const isLroAndPaging = isLroAndPagingOperation(operation);
181
+ const isPagingOnly = isPagingOnlyOperation(operation);
161
182
 
162
183
  // TODO: Support operation overloads
163
184
  // TODO: Support multiple responses
@@ -166,10 +187,17 @@ export function getDeserializePrivateFunction(
166
187
  let returnType;
167
188
  if (isLroOnly || isLroAndPaging) {
168
189
  returnType = buildLroReturnType(context, operation);
169
- } else if (response.type && restResponse) {
190
+ } else if (isPagingOnly && restResponse?.type) {
191
+ // For paging operations, use the full response model (e.g., _OperationListResult)
192
+ // instead of just the array element type
170
193
  returnType = {
171
194
  name: (restResponse as any).name ?? "",
172
- type: getTypeExpression(context, restResponse.type!)
195
+ type: getTypeExpression(context, restResponse.type)
196
+ };
197
+ } else if (response.type) {
198
+ returnType = {
199
+ name: (response as any).name ?? "",
200
+ type: getTypeExpression(context, response.type)
173
201
  };
174
202
  } else {
175
203
  returnType = { name: "", type: "void" };
@@ -198,7 +226,7 @@ export function getDeserializePrivateFunction(
198
226
  const deserializedType =
199
227
  isLroOnly || isLroAndPaging
200
228
  ? operation?.lroMetadata?.finalResponse?.result
201
- : restResponse
229
+ : isPagingOnly && restResponse?.type
202
230
  ? restResponse.type
203
231
  : response.type;
204
232
  const lroSubSegments = isLroOnly
@@ -364,15 +392,72 @@ export function getDeserializePrivateFunction(
364
392
  };
365
393
  }
366
394
 
395
+ /**
396
+ * Generates a private function to deserialize response headers.
397
+ * Only generated when response headers are present and include-headers-in-response is enabled.
398
+ */
399
+ export function getDeserializeHeadersPrivateFunction(
400
+ context: SdkContext,
401
+ operation: ServiceOperation
402
+ ): OptionalKind<FunctionDeclarationStructure> | undefined {
403
+ const responseHeaders = getResponseHeaders(operation.operation.responses);
404
+ const isResponseHeadersEnabled =
405
+ context.rlcOptions?.includeHeadersInResponse === true;
406
+
407
+ // Only generate if headers exist and feature is enabled
408
+ if (responseHeaders.length === 0 || !isResponseHeadersEnabled) {
409
+ return undefined;
410
+ }
411
+
412
+ const { name } = getOperationName(operation);
413
+ const dependencies = useDependencies();
414
+ const PathUncheckedResponseReference = resolveReference(
415
+ dependencies.PathUncheckedResponse
416
+ );
417
+
418
+ const parameters: OptionalKind<ParameterDeclarationStructure>[] = [
419
+ {
420
+ name: "result",
421
+ type: PathUncheckedResponseReference
422
+ }
423
+ ];
424
+
425
+ const returnType = buildHeaderOnlyResponseType(context, responseHeaders);
426
+
427
+ const functionStatement: OptionalKind<FunctionDeclarationStructure> = {
428
+ isAsync: false,
429
+ isExported: true,
430
+ name: `_${name}DeserializeHeaders`,
431
+ parameters,
432
+ returnType
433
+ };
434
+
435
+ const statements: string[] = [];
436
+ statements.push(
437
+ `return ${buildHeaderOnlyResponseValue(context, responseHeaders)};`
438
+ );
439
+
440
+ return {
441
+ ...functionStatement,
442
+ statements
443
+ };
444
+ }
445
+
367
446
  interface ExceptionThrowDetail {
368
447
  start: number;
369
448
  end?: number;
370
449
  deserializer: string;
450
+ xmlDeserializer?: string;
451
+ /** Whether the exception response is XML-only (no JSON content type) */
452
+ isXmlOnly?: boolean;
371
453
  }
372
454
 
373
455
  interface OperationExceptionDetails {
374
456
  customized: ExceptionThrowDetail[];
375
457
  defaultDeserializer?: string;
458
+ defaultXmlDeserializer?: string;
459
+ /** Whether the default exception response is XML-only */
460
+ defaultIsXmlOnly?: boolean;
376
461
  }
377
462
 
378
463
  function getExceptionDetails(
@@ -381,6 +466,8 @@ function getExceptionDetails(
381
466
  ): OperationExceptionDetails {
382
467
  const customized: ExceptionThrowDetail[] = [];
383
468
  let defaultDeserializer: string | undefined;
469
+ let defaultXmlDeserializer: string | undefined;
470
+ let defaultIsXmlOnly: boolean | undefined;
384
471
  for (const exception of operation.operation.exceptions) {
385
472
  if (!exception.type) {
386
473
  continue;
@@ -400,22 +487,69 @@ function getExceptionDetails(
400
487
  ) {
401
488
  continue;
402
489
  }
490
+
491
+ // Check if the exception type has XML serialization support
492
+ // Use exception contentTypes when available, otherwise check the type itself
493
+ const exceptionContentTypes = exception.contentTypes ?? [];
494
+ const exceptionIsXml = isXmlPayload(exceptionContentTypes);
495
+ const exceptionIsDualFormat = hasDualFormatSupport(exceptionContentTypes);
496
+ const typeHasXml =
497
+ exception.type.kind === "model" && hasXmlSerialization(exception.type);
498
+
499
+ let xmlDeserializerName: string | undefined;
500
+ if (exception.type.kind === "model" && (typeHasXml || exceptionIsXml)) {
501
+ const xmlName = buildXmlModelDeserializer(context, exception.type, {
502
+ nameOnly: true,
503
+ skipDiscriminatedUnionSuffix: false
504
+ });
505
+ if (typeof xmlName === "string") {
506
+ xmlDeserializerName = xmlName;
507
+ }
508
+ }
509
+
510
+ // XML-only when all content types are XML (no JSON support)
511
+ const isXmlOnly =
512
+ xmlDeserializerName !== undefined &&
513
+ exceptionIsXml &&
514
+ !exceptionIsDualFormat;
515
+
403
516
  if (statusCode === "*") {
404
517
  defaultDeserializer = deserializeFunctionName;
518
+ defaultXmlDeserializer = xmlDeserializerName;
519
+ defaultIsXmlOnly = isXmlOnly;
405
520
  } else if (typeof statusCode === "number") {
406
521
  customized.push({
407
522
  start: statusCode,
408
- deserializer: deserializeFunctionName
523
+ deserializer: deserializeFunctionName,
524
+ xmlDeserializer: xmlDeserializerName,
525
+ isXmlOnly
409
526
  });
410
527
  } else {
411
528
  customized.push({
412
529
  start: statusCode.start,
413
530
  end: statusCode.end,
414
- deserializer: deserializeFunctionName
531
+ deserializer: deserializeFunctionName,
532
+ xmlDeserializer: xmlDeserializerName,
533
+ isXmlOnly
415
534
  });
416
535
  }
417
536
  }
418
- return { customized, defaultDeserializer };
537
+ return {
538
+ customized,
539
+ defaultDeserializer,
540
+ defaultXmlDeserializer,
541
+ defaultIsXmlOnly
542
+ };
543
+ }
544
+
545
+ function getExceptionDeserializeExpr(exception: ExceptionThrowDetail): string {
546
+ if (!exception.xmlDeserializer) {
547
+ return `${exception.deserializer}(result.body)`;
548
+ }
549
+ if (exception.isXmlOnly) {
550
+ return `${exception.xmlDeserializer}(result.body)`;
551
+ }
552
+ return `isXml ? ${exception.xmlDeserializer}(result.body) : ${exception.deserializer}(result.body)`;
419
553
  }
420
554
 
421
555
  function getExceptionThrowStatement(
@@ -426,35 +560,72 @@ function getExceptionThrowStatement(
426
560
  const createRestErrorReference = resolveReference(
427
561
  useDependencies().createRestError
428
562
  );
429
- const { customized, defaultDeserializer } = getExceptionDetails(
430
- context,
431
- operation
432
- );
563
+ const {
564
+ customized,
565
+ defaultDeserializer,
566
+ defaultXmlDeserializer,
567
+ defaultIsXmlOnly
568
+ } = getExceptionDetails(context, operation);
569
+
570
+ // Check if any exception has XML deserialization support that requires runtime content-type check
571
+ const hasAnyDualFormatXml =
572
+ (defaultXmlDeserializer !== undefined && !defaultIsXmlOnly) ||
573
+ customized.some((e) => e.xmlDeserializer !== undefined && !e.isXmlOnly);
574
+
433
575
  if (customized.length > 0) {
434
576
  statements.push(`const error = ${createRestErrorReference}(result);`);
577
+ if (hasAnyDualFormatXml) {
578
+ const isXmlContentTypeRef = resolveReference(XmlHelpers.isXmlContentType);
579
+ statements.push(
580
+ `const responseContentType = result.headers?.["content-type"] ?? "";`
581
+ );
582
+ statements.push(
583
+ `const isXml = ${isXmlContentTypeRef}(responseContentType);`
584
+ );
585
+ }
435
586
  statements.push(`const statusCode = Number.parseInt(result.status);`);
436
587
  const stats: string[] = customized.map((exception) => {
588
+ const deserializeExpr = getExceptionDeserializeExpr(exception);
437
589
  if (exception.end) {
438
590
  return `if(statusCode >= ${exception.start} && statusCode <= ${exception.end}) {
439
- error.details = ${exception.deserializer}(result.body);
591
+ error.details = ${deserializeExpr};
440
592
  }`;
441
593
  } else {
442
594
  return `if(statusCode === ${exception.start}) {
443
- error.details = ${exception.deserializer}(result.body);
595
+ error.details = ${deserializeExpr};
444
596
  }`;
445
597
  }
446
598
  });
447
599
  statements.push(stats.join("\nelse "));
448
600
  if (defaultDeserializer) {
601
+ const defaultDeserializeExpr = !defaultXmlDeserializer
602
+ ? `${defaultDeserializer}(result.body)`
603
+ : defaultIsXmlOnly
604
+ ? `${defaultXmlDeserializer}(result.body)`
605
+ : `isXml ? ${defaultXmlDeserializer}(result.body) : ${defaultDeserializer}(result.body)`;
449
606
  statements.push(`else {
450
- error.details = ${defaultDeserializer}(result.body);
607
+ error.details = ${defaultDeserializeExpr};
451
608
  }`);
452
609
  }
453
610
  statements.push("throw error;");
454
611
  } else {
455
612
  if (defaultDeserializer) {
456
- statements.push(`const error = ${createRestErrorReference}(result);
457
- error.details = ${defaultDeserializer}(result.body);`);
613
+ if (defaultXmlDeserializer) {
614
+ if (defaultIsXmlOnly) {
615
+ statements.push(`const error = ${createRestErrorReference}(result);
616
+ error.details = ${defaultXmlDeserializer}(result.body);`);
617
+ } else {
618
+ const isXmlContentTypeRef = resolveReference(
619
+ XmlHelpers.isXmlContentType
620
+ );
621
+ statements.push(`const error = ${createRestErrorReference}(result);
622
+ const responseContentType = result.headers?.["content-type"] ?? "";
623
+ error.details = ${isXmlContentTypeRef}(responseContentType) ? ${defaultXmlDeserializer}(result.body) : ${defaultDeserializer}(result.body);`);
624
+ }
625
+ } else {
626
+ statements.push(`const error = ${createRestErrorReference}(result);
627
+ error.details = ${defaultDeserializer}(result.body);`);
628
+ }
458
629
  statements.push("throw error;");
459
630
  } else {
460
631
  statements.push(`throw ${createRestErrorReference}(result);`);
@@ -497,7 +668,6 @@ function getOperationSignatureParameters(
497
668
  param.methodParameterSegments[0]?.[0] === p
498
669
  );
499
670
  })[0]?.kind !== "cookie" &&
500
- p.clientDefaultValue === undefined &&
501
671
  !p.optional &&
502
672
  !(
503
673
  p.isGeneratedName &&
@@ -573,12 +743,37 @@ export function getOperationFunction(
573
743
 
574
744
  // TODO: Support operation overloads
575
745
  const response = operation.response;
746
+ const responseHeaders = getResponseHeaders(operation.operation.responses);
747
+ const hasHeaderOnlyResponse = !response.type && responseHeaders.length > 0;
748
+ const isResponseHeadersEnabled =
749
+ context.rlcOptions?.includeHeadersInResponse === true;
750
+
576
751
  let returnType = { name: "", type: "void" };
577
752
  if (response.type) {
578
753
  const type = response.type;
754
+
755
+ // If feature flag enabled, we'll append the response headers to the operation response type.
756
+ if (
757
+ type.kind === "model" &&
758
+ responseHeaders.length > 0 &&
759
+ isResponseHeadersEnabled
760
+ ) {
761
+ // Build a composite type that includes both model and additional header properties
762
+ returnType = {
763
+ name: (type as any).name ?? "",
764
+ type: `${buildCompositeResponseType(context, type, responseHeaders)}`
765
+ };
766
+ } else {
767
+ returnType = {
768
+ name: (type as any).name ?? "",
769
+ type: getTypeExpression(context, type!)
770
+ };
771
+ }
772
+ } else if (hasHeaderOnlyResponse && isResponseHeadersEnabled) {
773
+ // Here we handle returning headers when the operation return type is void
579
774
  returnType = {
580
- name: (type as any).name ?? "",
581
- type: getTypeExpression(context, type!)
775
+ name: "",
776
+ type: `${buildHeaderOnlyResponseType(context, responseHeaders)}`
582
777
  };
583
778
  }
584
779
  const { name, fixme = [] } = getOperationName(operation);
@@ -598,18 +793,50 @@ export function getOperationFunction(
598
793
 
599
794
  const statements: string[] = [];
600
795
 
796
+ // Generate unique local variable names that don't conflict with parameter names
797
+ const paramNames = new Set(parameters.map((p) => p.name));
798
+ const resultVarName = generateLocallyUniqueName("result", paramNames);
799
+
601
800
  const parameterList = parameters.map((p) => p.name).join(", ");
602
801
  // Special case for binary-only bodies: use helper to call streaming methods so that Core doesn't poison the response body by
603
802
  // doing a UTF-8 decode on the raw bytes.
604
803
  if (response?.type?.kind === "bytes" && response.type.encode === "bytes") {
605
- statements.push(`const streamableMethod = _${name}Send(${parameterList});`);
804
+ const streamableMethodVarName = generateLocallyUniqueName(
805
+ "streamableMethod",
806
+ paramNames
807
+ );
808
+ statements.push(
809
+ `const ${streamableMethodVarName} = _${name}Send(${parameterList});`
810
+ );
811
+ statements.push(
812
+ `const ${resultVarName} = await ${resolveReference(SerializationHelpers.getBinaryResponse)}(${streamableMethodVarName});`
813
+ );
814
+ } else {
606
815
  statements.push(
607
- `const result = await ${resolveReference(SerializationHelpers.getBinaryResponse)}(streamableMethod);`
816
+ `const ${resultVarName} = await _${name}Send(${parameterList});`
608
817
  );
818
+ }
819
+
820
+ // If the response has headers and the feature flag to include headers in response is enabled, build the headers object and include it in the return value
821
+ if (responseHeaders.length > 0 && isResponseHeadersEnabled) {
822
+ const headersVarName = generateLocallyUniqueName("headers", paramNames);
823
+ statements.push(
824
+ `const ${headersVarName} = _${name}DeserializeHeaders(result);`
825
+ );
826
+
827
+ // If there is no body payload just return the headers
828
+ if (hasHeaderOnlyResponse) {
829
+ statements.push(`return {...${headersVarName} };`);
830
+ } else {
831
+ const payloadVarName = generateLocallyUniqueName("payload", paramNames);
832
+ statements.push(
833
+ `const ${payloadVarName} = await _${name}Deserialize(${resultVarName});`
834
+ );
835
+ statements.push(`return { ...${payloadVarName}, ...${headersVarName} };`);
836
+ }
609
837
  } else {
610
- statements.push(`const result = await _${name}Send(${parameterList});`);
838
+ statements.push(`return _${name}Deserialize(${resultVarName});`);
611
839
  }
612
- statements.push(`return _${name}Deserialize(result);`);
613
840
 
614
841
  return {
615
842
  ...functionStatement,
@@ -673,7 +900,7 @@ function getLroOnlyOperationFunction(
673
900
  allowedFinalLocation.includes(lroMetadata?.finalStateVia)
674
901
  ? `resourceLocationConfig: "${lroMetadata?.finalStateVia}",`
675
902
  : "";
676
- const apiVersion = getApiVersionExpression(operation);
903
+ const apiVersion = getApiVersionExpression(context, operation);
677
904
  const statements: string[] = [];
678
905
 
679
906
  statements.push(`
@@ -716,7 +943,7 @@ function getLroAndPagingOperationFunction(
716
943
  const returnType = buildLroPagingReturnType(context, operation);
717
944
 
718
945
  // Get apiVersion expression for both LRO poller and paging options
719
- const apiVersion = getApiVersionExpression(operation);
946
+ const apiVersion = getApiVersionExpression(context, operation);
720
947
 
721
948
  // Build paging options from metadata
722
949
  const pagingOptions = [
@@ -882,7 +1109,7 @@ function getPagingOnlyOperationFunction(
882
1109
  // Check for nextLinkVerb from TCGC pagingMetadata (supports @Legacy.nextLinkVerb decorator)
883
1110
  const nextLinkMethod = operation.pagingMetadata.nextLinkVerb;
884
1111
 
885
- const apiVersion = getApiVersionExpression(operation);
1112
+ const apiVersion = getApiVersionExpression(context, operation);
886
1113
 
887
1114
  if (itemName) {
888
1115
  options.push(`itemName: "${itemName}"`);
@@ -1073,14 +1300,33 @@ function buildBodyParameter(
1073
1300
  NameType.Parameter,
1074
1301
  true
1075
1302
  );
1076
- const bodyNameExpression = bodyParameter.optional
1303
+ let bodyNameExpression = bodyParameter.optional
1077
1304
  ? `${optionalParamName}["${bodyParamName}"]`
1078
1305
  : bodyParamName;
1079
- const nullOrUndefinedPrefix = getPropertySerializationPrefix(
1080
- context,
1081
- bodyParameter,
1082
- bodyParameter.optional ? optionalParamName : undefined
1083
- );
1306
+
1307
+ // Check if body parameter has a client default value with matching type
1308
+ const hasClientDefault =
1309
+ bodyParameter.optional &&
1310
+ bodyParameter.clientDefaultValue !== undefined &&
1311
+ isDefaultValueTypeMatch(bodyParameter, bodyParameter.clientDefaultValue);
1312
+
1313
+ // Apply client default value if present for optional body parameters
1314
+ if (hasClientDefault) {
1315
+ const formattedDefault = formatDefaultValue(
1316
+ bodyParameter.clientDefaultValue
1317
+ );
1318
+ bodyNameExpression = `(${bodyNameExpression} ?? ${formattedDefault})`;
1319
+ }
1320
+
1321
+ // Only apply nullOrUndefinedPrefix if there's no client default value
1322
+ // because the default value already handles null/undefined cases
1323
+ const nullOrUndefinedPrefix = hasClientDefault
1324
+ ? ""
1325
+ : getPropertySerializationPrefix(
1326
+ context,
1327
+ bodyParameter,
1328
+ bodyParameter.optional ? optionalParamName : undefined
1329
+ );
1084
1330
 
1085
1331
  // For dual-format operations, check the contentType option at runtime
1086
1332
  if (
@@ -1153,6 +1399,10 @@ export function getParameterMap(
1153
1399
 
1154
1400
  // Special case for api-version parameters with default values
1155
1401
  if (param.isApiVersionParam && param.clientDefaultValue) {
1402
+ // For multi-service, use only the default value (don't reference context.apiVersion)
1403
+ if (context.rlcOptions?.isMultiService) {
1404
+ return `"${serializedName}": "${param.clientDefaultValue}"`;
1405
+ }
1156
1406
  return `"${serializedName}": ${param.onClient ? "context." : ""}${param.name} ?? "${param.clientDefaultValue}"`;
1157
1407
  }
1158
1408
 
@@ -1226,7 +1476,7 @@ function getContentTypeValue(
1226
1476
  } else {
1227
1477
  return `contentType: ${
1228
1478
  !param.optional
1229
- ? "contentType"
1479
+ ? normalizeName(param.name, NameType.Property)
1230
1480
  : `${optionalParamName}.` + param.name + " as any"
1231
1481
  }`;
1232
1482
  }
@@ -1283,6 +1533,14 @@ function getOptional(
1283
1533
  serializedName: string
1284
1534
  ) {
1285
1535
  const paramName = `${param.onClient ? "context." : `${optionalParamName}?.`}${param.name}`;
1536
+
1537
+ // Apply client default value if present and type matches
1538
+ const defaultSuffix =
1539
+ param.clientDefaultValue !== undefined &&
1540
+ isDefaultValueTypeMatch(param, param.clientDefaultValue)
1541
+ ? ` ?? ${formatDefaultValue(param.clientDefaultValue)}`
1542
+ : "";
1543
+
1286
1544
  if (param.type.kind === "model") {
1287
1545
  const propertiesStr = getRequestModelMapping(
1288
1546
  context,
@@ -1292,7 +1550,7 @@ function getOptional(
1292
1550
  const serializeContent = `{${propertiesStr.join(",")}}`;
1293
1551
  return `"${serializedName}": ${serializeContent}`;
1294
1552
  }
1295
- return `"${serializedName}": ${serializeRequestValue(
1553
+ const serializedValue = serializeRequestValue(
1296
1554
  context,
1297
1555
  param.type,
1298
1556
  paramName,
@@ -1300,7 +1558,8 @@ function getOptional(
1300
1558
  getEncodeForType(param.type),
1301
1559
  serializedName,
1302
1560
  true
1303
- )}`;
1561
+ );
1562
+ return `"${serializedName}": ${serializedValue}${defaultSuffix}`;
1304
1563
  }
1305
1564
 
1306
1565
  /**
@@ -1621,7 +1880,7 @@ export function getRequestModelMapping(
1621
1880
  ).map(([name, value]) => `"${name}": ${value}`);
1622
1881
  }
1623
1882
 
1624
- function getPropertySerializedName(
1883
+ export function getPropertySerializedName(
1625
1884
  property: SdkHttpParameter | SdkModelPropertyType
1626
1885
  ) {
1627
1886
  return (
@@ -1835,6 +2094,77 @@ export function serializeRequestValue(
1835
2094
  }
1836
2095
  }
1837
2096
 
2097
+ /**
2098
+ * Wrapper of deserializeResponseValue, this is used to handle the special cases for response header deserialization, since response header only supports primitive types, we will have a simpler deserialization logic comparing to response body, and we also need to handle the null/undefined cases differently since if a header is missing, the value will be undefined instead of null.
2099
+ * Note: that this has been added to isolate these changes behind the feature flag. Once the feature flag is removed, we can consider merging this back to deserializeResponseValue if the special handling logic is not needed anymore.
2100
+ */
2101
+ export function deserializeResponseHeadersValue(
2102
+ context: SdkContext,
2103
+ type: SdkType,
2104
+ restValue: string,
2105
+ required: boolean,
2106
+ format?: string,
2107
+ recursionDepth: number = 0
2108
+ ) {
2109
+ const nullOrUndefinedPrefix =
2110
+ isTypeNullable(type) || getOptionalForType(type) || !required
2111
+ ? `${restValue} === undefined || ${restValue} === null ? ${restValue}: `
2112
+ : "";
2113
+
2114
+ switch (type.kind) {
2115
+ case "constant":
2116
+ return `${restValue} as any`;
2117
+ case "boolean":
2118
+ return `${nullOrUndefinedPrefix} ${restValue}.trim().toLowerCase() === "true"`;
2119
+ case "int16":
2120
+ case "int32":
2121
+ case "int64":
2122
+ case "uint16":
2123
+ case "uint32":
2124
+ case "uint64":
2125
+ case "float":
2126
+ case "decimal":
2127
+ case "decimal128":
2128
+ case "float32":
2129
+ case "float64":
2130
+ case "int8":
2131
+ case "integer":
2132
+ case "numeric":
2133
+ case "safeint":
2134
+ case "uint8":
2135
+ return `${nullOrUndefinedPrefix} Number(${restValue})`;
2136
+ case "enum":
2137
+ if (isNormalUnion(type)) {
2138
+ return `${restValue}`;
2139
+ } else if (isSpecialHandledUnion(type)) {
2140
+ const deserializeFunctionName = type
2141
+ ? buildModelDeserializer(context, getNullableValidType(type), {
2142
+ nameOnly: true,
2143
+ skipDiscriminatedUnionSuffix: false
2144
+ })
2145
+ : undefined;
2146
+ if (deserializeFunctionName) {
2147
+ return `${deserializeFunctionName}(${restValue})`;
2148
+ } else {
2149
+ return `${restValue} as any`;
2150
+ }
2151
+ } else {
2152
+ return `${restValue} as any`;
2153
+ }
2154
+ default: {
2155
+ const val = deserializeResponseValue(
2156
+ context,
2157
+ type,
2158
+ restValue,
2159
+ true,
2160
+ format,
2161
+ recursionDepth
2162
+ );
2163
+ return `${nullOrUndefinedPrefix} ${val}`;
2164
+ }
2165
+ }
2166
+ }
2167
+
1838
2168
  /**
1839
2169
  * This function helps converting strings into JS complex types recursively.
1840
2170
  * We need to drill down into Array elements to make sure that the element type is
@@ -2042,12 +2372,59 @@ export function getAllAncestors(type: SdkType): SdkType[] {
2042
2372
  return ancestors;
2043
2373
  }
2044
2374
 
2375
+ /**
2376
+ * Checks if a clientDefaultValue type matches the parameter type.
2377
+ * Returns true if the default value type is compatible with the parameter type.
2378
+ */
2379
+ function isDefaultValueTypeMatch(
2380
+ param: SdkHttpParameter | SdkBodyParameter,
2381
+ defaultValue: unknown
2382
+ ): boolean {
2383
+ const defaultType = typeof defaultValue;
2384
+ const paramType = param.type;
2385
+
2386
+ // Map JavaScript types to TypeSpec types
2387
+ if (defaultType === "string") {
2388
+ return paramType.kind === "string" || paramType.kind === "enum";
2389
+ }
2390
+ if (defaultType === "number") {
2391
+ return (
2392
+ paramType.kind === "int32" ||
2393
+ paramType.kind === "int64" ||
2394
+ paramType.kind === "float32" ||
2395
+ paramType.kind === "float64" ||
2396
+ paramType.kind === "numeric" ||
2397
+ paramType.kind === "integer" ||
2398
+ paramType.kind === "float" ||
2399
+ paramType.kind === "decimal"
2400
+ );
2401
+ }
2402
+ if (defaultType === "boolean") {
2403
+ return paramType.kind === "boolean";
2404
+ }
2405
+
2406
+ // For other types, don't apply the default
2407
+ return false;
2408
+ }
2409
+
2410
+ /**
2411
+ * Formats a default value for code generation.
2412
+ * Strings are wrapped in quotes, other values are used as-is.
2413
+ */
2414
+ function formatDefaultValue(defaultValue: unknown): string {
2415
+ if (typeof defaultValue === "string") {
2416
+ return `"${defaultValue}"`;
2417
+ }
2418
+ return String(defaultValue);
2419
+ }
2420
+
2045
2421
  export function getPropertySerializationPrefix(
2046
2422
  context: SdkContext,
2047
2423
  property: SdkHttpParameter | SdkModelPropertyType,
2048
2424
  propertyPath?: string
2049
2425
  ) {
2050
2426
  const propertyFullName = getPropertyFullName(context, property, propertyPath);
2427
+
2051
2428
  if (property.optional || isTypeNullable(property.type)) {
2052
2429
  return `!${propertyFullName}? ${propertyFullName}:`;
2053
2430
  }
@@ -2073,6 +2450,7 @@ export function getPropertyFullName(
2073
2450
  } else if (propertyPath) {
2074
2451
  fullName = `${propertyPath}["${normalizedPropertyName}"]`;
2075
2452
  }
2453
+
2076
2454
  return fullName;
2077
2455
  }
2078
2456
 
@@ -2102,10 +2480,12 @@ export function getExpectedStatuses(operation: ServiceOperation): string {
2102
2480
 
2103
2481
  /**
2104
2482
  * Gets the apiVersion expression with default value fallback for query parameters.
2483
+ * @param dpgContext - The SDK context
2105
2484
  * @param operation - The operation to get the apiVersion parameter from
2106
2485
  * @returns The apiVersion expression string, or undefined if no apiVersion query param exists
2107
2486
  */
2108
2487
  function getApiVersionExpression(
2488
+ dpgContext: SdkContext,
2109
2489
  operation: ServiceOperation
2110
2490
  ): string | undefined {
2111
2491
  const queryApiVersionParam = operation.operation.parameters.find(
@@ -2114,9 +2494,122 @@ function getApiVersionExpression(
2114
2494
  if (!queryApiVersionParam) {
2115
2495
  return undefined;
2116
2496
  }
2497
+ // For multi-service, use only the default value (don't reference context.apiVersion)
2498
+ if (dpgContext.rlcOptions?.isMultiService) {
2499
+ return queryApiVersionParam.clientDefaultValue
2500
+ ? `"${queryApiVersionParam.clientDefaultValue}"`
2501
+ : undefined;
2502
+ }
2117
2503
  const paramAccess = `${queryApiVersionParam.onClient ? "context." : ""}${queryApiVersionParam.name}`;
2118
2504
  const defaultValueSuffix = queryApiVersionParam.clientDefaultValue
2119
2505
  ? ` ?? "${queryApiVersionParam.clientDefaultValue}"`
2120
2506
  : "";
2121
2507
  return `${paramAccess}${defaultValueSuffix}`;
2122
2508
  }
2509
+
2510
+ /**
2511
+ * Extracts and deduplicates all response headers from operation responses.
2512
+ * @param responses - The operation responses
2513
+ * @returns Array of unique response headers
2514
+ */
2515
+ export function getResponseHeaders(
2516
+ responses: SdkHttpOperation["responses"]
2517
+ ): SdkServiceResponseHeader[] {
2518
+ const headerMap = new Map<string, SdkServiceResponseHeader>();
2519
+ for (const response of responses ?? []) {
2520
+ for (const header of response.headers ?? []) {
2521
+ const key = header.serializedName ?? header.name;
2522
+ if (!headerMap.has(key)) {
2523
+ headerMap.set(key, header);
2524
+ }
2525
+ }
2526
+ }
2527
+ return Array.from(headerMap.values());
2528
+ }
2529
+
2530
+ /**
2531
+ * Builds a composite return type for operations that return both a model and additional headers.
2532
+ * Combines model properties and header properties into an inline object type.
2533
+ * @param context - The SDK context
2534
+ * @param modelType - The model type
2535
+ * @param headers - The response headers that are NOT in the model
2536
+ * @returns The composite type expression as a string (e.g., "{ name: string; email: string; requestId: string }")
2537
+ */
2538
+ function buildCompositeResponseType(
2539
+ context: SdkContext,
2540
+ modelType: SdkModelType,
2541
+ headers: SdkServiceResponseHeader[]
2542
+ ): string {
2543
+ const allParents = getAllAncestors(modelType);
2544
+ const modelProps: (SdkModelPropertyType | SdkServiceResponseHeader)[] =
2545
+ getAllProperties(context, modelType, allParents);
2546
+
2547
+ // Collect header property names already in the model to avoid duplicates
2548
+ const modelHeaderNames = new Set<string>();
2549
+ for (const property of modelProps) {
2550
+ if (isHeader(context.program, property.__raw!)) {
2551
+ modelHeaderNames.add(property.name.toLowerCase());
2552
+ }
2553
+ }
2554
+
2555
+ // Add only additional host response header properties not already in model
2556
+ for (const header of headers) {
2557
+ if (modelHeaderNames.has(header.name.toLowerCase())) {
2558
+ continue;
2559
+ }
2560
+ modelProps.push(header);
2561
+ }
2562
+
2563
+ return emitInlineModel(context, modelProps);
2564
+ }
2565
+
2566
+ /**
2567
+ * Builds an inline type string for header-only responses.
2568
+ * @param context - The SDK context
2569
+ * @param headers - The response headers
2570
+ * @returns The inline type expression as a string (e.g., "{ requestId: string; optionalHeader?: string }")
2571
+ */
2572
+ function buildHeaderOnlyResponseType(
2573
+ context: SdkContext,
2574
+ headers: SdkServiceResponseHeader[]
2575
+ ): string {
2576
+ const properties: string[] = [];
2577
+
2578
+ for (const header of headers) {
2579
+ const headerName = normalizeModelPropertyName(context, header);
2580
+ const headerType = getTypeExpression(context, header.type);
2581
+ const isOptional = header.optional ? "?" : "";
2582
+ properties.push(`${headerName}${isOptional}: ${headerType}`);
2583
+ }
2584
+
2585
+ return `{ ${properties.join("; ")} }`;
2586
+ }
2587
+
2588
+ /**
2589
+ * Builds the object literal expression for a header-only response.
2590
+ * Handles type conversions for headers (string to boolean, Date, number, Uint8Array).
2591
+ * @param operation - The service operation
2592
+ * @param headers - The response headers
2593
+ * @returns JavaScript expression string for the header-only response object
2594
+ */
2595
+ function buildHeaderOnlyResponseValue(
2596
+ context: SdkContext,
2597
+ headers: SdkServiceResponseHeader[]
2598
+ ): string {
2599
+ const props = headers.map((header) => {
2600
+ const headerName = (header.serializedName ?? header.name).toLowerCase();
2601
+ const key = normalizeModelPropertyName(context, header);
2602
+ const value = deserializeResponseHeadersValue(
2603
+ context,
2604
+ header.type,
2605
+ `result.headers[${JSON.stringify(headerName)}]`,
2606
+ !header.optional,
2607
+ getEncodeForType(header.type),
2608
+ 0
2609
+ );
2610
+
2611
+ return `${key}: ${value}`;
2612
+ });
2613
+
2614
+ return `{ ${props.join(", ")} }`;
2615
+ }