@azure-tools/typespec-ts 0.52.0 → 0.52.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.52.0",
3
+ "version": "0.52.1",
4
4
  "description": "An experimental TypeSpec emitter for TypeScript RLC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
@@ -18,15 +18,15 @@
18
18
  "license": "MIT",
19
19
  "devDependencies": {
20
20
  "@azure-rest/core-client": "^2.3.1",
21
- "@typespec/http-specs": "0.1.0-dev.2",
22
- "@typespec/spector": "0.1.0-dev.3",
23
- "@typespec/spec-api": "0.1.0-alpha.14-dev.3",
21
+ "@typespec/http-specs": "0.1.0-alpha.36-dev.3",
22
+ "@typespec/spector": "0.1.0-alpha.25-dev.5",
23
+ "@typespec/spec-api": "0.1.0-alpha.14-dev.4",
24
24
  "@typespec/tspd": "0.74.1",
25
- "@azure-tools/azure-http-specs": "0.1.0-alpha.39-dev.7",
26
- "@azure-tools/typespec-autorest": "^0.66.1",
27
- "@azure-tools/typespec-azure-core": "^0.66.1",
28
- "@azure-tools/typespec-azure-resource-manager": "^0.66.1",
29
- "@azure-tools/typespec-client-generator-core": "^0.66.4",
25
+ "@azure-tools/azure-http-specs": "0.1.0-alpha.40-dev.0",
26
+ "@azure-tools/typespec-autorest": "^0.67.0",
27
+ "@azure-tools/typespec-azure-core": "^0.67.0",
28
+ "@azure-tools/typespec-azure-resource-manager": "^0.67.0",
29
+ "@azure-tools/typespec-client-generator-core": "^0.67.1",
30
30
  "@azure/abort-controller": "^2.1.2",
31
31
  "@azure/core-auth": "^1.6.0",
32
32
  "@azure/core-lro": "^3.1.0",
@@ -39,12 +39,12 @@
39
39
  "@types/lodash": "^4.17.4",
40
40
  "@typescript-eslint/eslint-plugin": "^8.28.0",
41
41
  "@typescript-eslint/parser": "^8.28.0",
42
- "@typespec/compiler": "^1.10.0",
43
- "@typespec/http": "^1.10.0",
44
- "@typespec/openapi": "^1.10.0",
45
- "@typespec/rest": "^0.80.0",
42
+ "@typespec/compiler": "^1.11.0",
43
+ "@typespec/http": "^1.11.0",
44
+ "@typespec/openapi": "^1.11.0",
45
+ "@typespec/rest": "^0.81.0",
46
46
  "@typespec/ts-http-runtime": "^0.1.0",
47
- "@typespec/versioning": "^0.80.0",
47
+ "@typespec/versioning": "^0.81.0",
48
48
  "chai": "^4.3.6",
49
49
  "chalk": "^4.0.0",
50
50
  "cross-env": "^7.0.3",
@@ -63,16 +63,16 @@
63
63
  "js-yaml": "^4.1.0"
64
64
  },
65
65
  "peerDependencies": {
66
- "@azure-tools/typespec-azure-core": "^0.66.1",
67
- "@azure-tools/typespec-client-generator-core": "^0.66.4",
68
- "@typespec/compiler": "^1.10.0",
69
- "@typespec/http": "^1.10.0",
70
- "@typespec/rest": "^0.80.0",
71
- "@typespec/versioning": "^0.80.0",
72
- "@typespec/xml": "^0.80.0"
66
+ "@azure-tools/typespec-azure-core": "^0.67.0",
67
+ "@azure-tools/typespec-client-generator-core": "^0.67.1",
68
+ "@typespec/compiler": "^1.11.0",
69
+ "@typespec/http": "^1.11.0",
70
+ "@typespec/rest": "^0.81.0",
71
+ "@typespec/versioning": "^0.81.0",
72
+ "@typespec/xml": "^0.81.0"
73
73
  },
74
74
  "dependencies": {
75
- "@azure-tools/rlc-common": "^0.52.0",
75
+ "@azure-tools/rlc-common": "^0.52.1",
76
76
  "fast-xml-parser": "^4.5.0",
77
77
  "fs-extra": "^11.1.0",
78
78
  "lodash": "^4.17.21",
package/src/lib.ts CHANGED
@@ -86,6 +86,15 @@ export interface EmitterOptions {
86
86
  * Set to false to return the non-model types directly.
87
87
  */
88
88
  "wrap-non-model-return"?: boolean;
89
+ /**
90
+ * When set to true, HEAD operations with a void response body will return `{ body: boolean }`
91
+ * instead of `void`, where `body: true` indicates a 2xx success (resource exists) and
92
+ * `body: false` indicates a non-2xx response (e.g., 404 Not Found).
93
+ * This matches the HLC behavior for resource existence check operations.
94
+ * Only applies when `wrap-non-model-return` is also enabled.
95
+ * Defaults to `false`.
96
+ */
97
+ "head-as-boolean"?: boolean;
89
98
  /**
90
99
  * When enabled, every regular (non-LRO, non-paging) operation return type is augmented with a
91
100
  * `_response` property containing `rawResponse`, `parsedBody`, and `parsedHeaders`.
@@ -390,6 +399,12 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
390
399
  description:
391
400
  "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."
392
401
  },
402
+ "head-as-boolean": {
403
+ type: "boolean",
404
+ nullable: true,
405
+ description:
406
+ "When set to true, HEAD operations with void response return `{ body: boolean }` (true=2xx, false=non-2xx) instead of void. Requires wrap-non-model-return to also be enabled. Defaults to false."
407
+ },
393
408
  "enable-storage-compat": {
394
409
  type: "boolean",
395
410
  nullable: true,
@@ -233,6 +233,14 @@ export function getDeserializePrivateFunction(
233
233
  name: (response as any).name ?? "",
234
234
  type: getTypeExpression(context, response.type)
235
235
  };
236
+ } else if (
237
+ !response.type &&
238
+ isHeadOperation(operation) &&
239
+ context.rlcOptions?.headAsBoolean
240
+ ) {
241
+ // HEAD operation with head-as-boolean but wrap-non-model-return disabled:
242
+ // return plain boolean instead of wrapped { body: boolean }
243
+ returnType = { name: "", type: "boolean" };
236
244
  } else {
237
245
  returnType = { name: "", type: "void" };
238
246
  }
@@ -452,6 +460,18 @@ export function getDeserializePrivateFunction(
452
460
  }
453
461
  } else if (returnType.type === "void") {
454
462
  statements.push("return;");
463
+ } else if (
464
+ !deserializedType &&
465
+ isHeadOperation(operation) &&
466
+ context.rlcOptions?.headAsBoolean
467
+ ) {
468
+ if (shouldWrap) {
469
+ // Case 1: wrap-non-model-return + head-as-boolean → return { body: boolean }
470
+ statements.push(`return { body: result.status.startsWith("2") };`);
471
+ } else {
472
+ // Case 2: head-as-boolean only (no wrap) → return plain boolean
473
+ statements.push(`return result.status.startsWith("2");`);
474
+ }
455
475
  } else {
456
476
  statements.push("return;");
457
477
  }
@@ -1040,6 +1060,14 @@ export function getOperationFunction(
1040
1060
  name: "",
1041
1061
  type: `${buildHeaderOnlyResponseType(context, responseHeaders)}`
1042
1062
  };
1063
+ } else if (
1064
+ !response.type &&
1065
+ isHeadOperation(operation) &&
1066
+ context.rlcOptions?.headAsBoolean
1067
+ ) {
1068
+ // HEAD operation with head-as-boolean but wrap-non-model-return disabled:
1069
+ // return plain boolean instead of wrapped { body: boolean }
1070
+ returnType = { name: "", type: "boolean" };
1043
1071
  }
1044
1072
 
1045
1073
  // When storage-compat is enabled, wrap the return type with StorageCompatResponseInfo
@@ -3148,6 +3176,13 @@ function isWrappableType(context: SdkContext, type: SdkType): boolean {
3148
3176
  return true;
3149
3177
  }
3150
3178
 
3179
+ /**
3180
+ * Returns true if the operation uses the HTTP HEAD method.
3181
+ */
3182
+ function isHeadOperation(operation: ServiceOperation): boolean {
3183
+ return operation.operation.verb.toLowerCase() === "head";
3184
+ }
3185
+
3151
3186
  /**
3152
3187
  * Determines whether wrapping the non-model return type is needed for an operation.
3153
3188
  * Returns an object with `shouldWrap` (whether to wrap) and `isBinary` (whether it's a binary response).
@@ -3182,6 +3217,13 @@ export function checkWrapNonModelReturn(
3182
3217
 
3183
3218
  const { type } = operation.response;
3184
3219
  if (!type) {
3220
+ // Special case: HEAD operation with void response → wrap as boolean { body: boolean }
3221
+ // This matches HLC behavior where HEAD operations with no response body
3222
+ // return { body: boolean } indicating if the resource exists (2xx = true, 4xx = false).
3223
+ // Requires `head-as-boolean: true` to be explicitly set in the emitter options.
3224
+ if (isHeadOperation(operation) && context.rlcOptions?.headAsBoolean) {
3225
+ return { shouldWrap: true, isBinary: false };
3226
+ }
3185
3227
  return noWrap; // void return type - no wrap needed
3186
3228
  }
3187
3229
 
@@ -3227,6 +3269,10 @@ export function buildNonModelResponseTypeDeclaration(
3227
3269
  */
3228
3270
  readableStreamBody?: NodeJS.ReadableStream;
3229
3271
  }`;
3272
+ } else if (!operation.response.type && isHeadOperation(operation)) {
3273
+ // HEAD as boolean: the body property is a boolean indicating if the resource exists.
3274
+ // true = resource exists (2xx response), false = resource not found (e.g., 404)
3275
+ typeBody = `{ body: boolean }`;
3230
3276
  } else {
3231
3277
  const returnType = getTypeExpression(context, operation.response.type!);
3232
3278
  typeBody = `{ body: ${returnType} }`;
@@ -95,6 +95,7 @@ function extractRLCOptions(
95
95
  const enableStorageCompat = emitterOptions["enable-storage-compat"] === true;
96
96
  const treatUnknownAsRecord =
97
97
  emitterOptions["treat-unknown-as-record"] === true;
98
+ const headAsBoolean = emitterOptions["head-as-boolean"] === true;
98
99
  const typespecTitleMap = emitterOptions["typespec-title-map"];
99
100
  const hasSubscriptionId = getSubscriptionId(dpgContext);
100
101
  const ignoreNullableOnOptional = getIgnoreNullableOnOptional(
@@ -141,7 +142,8 @@ function extractRLCOptions(
141
142
  wrapNonModelReturn,
142
143
  isMultiService,
143
144
  enableStorageCompat,
144
- treatUnknownAsRecord
145
+ treatUnknownAsRecord,
146
+ headAsBoolean
145
147
  };
146
148
  }
147
149
 
@@ -670,6 +670,14 @@ export type ServiceOperation = SdkServiceMethod<SdkHttpOperation> & {
670
670
  oriName?: string;
671
671
  };
672
672
 
673
+ export type ServiceParameter = (
674
+ | SdkMethodParameter
675
+ | SdkHttpParameter
676
+ | SdkBodyParameter
677
+ ) & {
678
+ oriName?: string;
679
+ };
680
+
673
681
  export function getMethodHierarchiesMap(
674
682
  context: SdkContext,
675
683
  client: SdkClientType<SdkServiceOperation>
@@ -804,9 +812,23 @@ export function isTenantLevelOperation(
804
812
 
805
813
  function resolveParameterNameConflict(
806
814
  operationOrGroup: SdkServiceMethod<SdkHttpOperation>,
807
- p: SdkMethodParameter | SdkHttpParameter | SdkBodyParameter
808
- ): SdkMethodParameter | SdkHttpParameter | SdkBodyParameter {
809
- const paramName = normalizeName(p.name, NameType.Parameter, true);
815
+ p: ServiceParameter
816
+ ): ServiceParameter {
817
+ // When the name starts with $DO_NOT_NORMALIZE$, record the original name so that
818
+ // subsequent calls (e.g. emitNonModelResponseTypes then buildApiOptions both call
819
+ // getMethodHierarchiesMap on the same TCGC object) always normalize from the
820
+ // original, not from the already-mutated value.
821
+ const paramName = normalizeName(
822
+ p.name,
823
+ NameType.Parameter,
824
+ true,
825
+ undefined,
826
+ undefined,
827
+ p.oriName
828
+ );
829
+ if (!p.oriName) {
830
+ p.oriName = p.name;
831
+ }
810
832
  if (paramName === operationOrGroup.name) {
811
833
  p.name = `${paramName}Parameter`;
812
834
  } else {