@azure-tools/typespec-ts 0.47.0 → 0.47.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 (37) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/src/framework/hooks/sdkTypes.d.ts.map +1 -1
  3. package/dist/src/framework/hooks/sdkTypes.js +1 -4
  4. package/dist/src/framework/hooks/sdkTypes.js.map +1 -1
  5. package/dist/src/framework/load-static-helpers.d.ts.map +1 -1
  6. package/dist/src/framework/load-static-helpers.js.map +1 -1
  7. package/dist/src/modular/buildClientContext.d.ts.map +1 -1
  8. package/dist/src/modular/buildClientContext.js +71 -24
  9. package/dist/src/modular/buildClientContext.js.map +1 -1
  10. package/dist/src/modular/emitModels.d.ts.map +1 -1
  11. package/dist/src/modular/emitModels.js +4 -7
  12. package/dist/src/modular/emitModels.js.map +1 -1
  13. package/dist/src/modular/helpers/clientHelpers.d.ts +4 -1
  14. package/dist/src/modular/helpers/clientHelpers.d.ts.map +1 -1
  15. package/dist/src/modular/helpers/clientHelpers.js +7 -4
  16. package/dist/src/modular/helpers/clientHelpers.js.map +1 -1
  17. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  18. package/dist/src/modular/helpers/operationHelpers.js +14 -8
  19. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  20. package/dist/src/modular/helpers/typeHelpers.d.ts.map +1 -1
  21. package/dist/src/modular/helpers/typeHelpers.js +5 -3
  22. package/dist/src/modular/helpers/typeHelpers.js.map +1 -1
  23. package/dist/src/modular/serialization/serializeUtils.js.map +1 -1
  24. package/dist/src/transform/transformApiVersionInfo.js.map +1 -1
  25. package/dist/src/utils/mediaTypes.js.map +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +23 -23
  28. package/src/framework/hooks/sdkTypes.ts +2 -5
  29. package/src/framework/load-static-helpers.ts +5 -6
  30. package/src/modular/buildClientContext.ts +88 -31
  31. package/src/modular/emitModels.ts +1 -4
  32. package/src/modular/helpers/clientHelpers.ts +8 -5
  33. package/src/modular/helpers/operationHelpers.ts +14 -10
  34. package/src/modular/helpers/typeHelpers.ts +4 -2
  35. package/src/modular/serialization/serializeUtils.ts +2 -2
  36. package/src/transform/transformApiVersionInfo.ts +1 -1
  37. package/src/utils/mediaTypes.ts +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.47.0",
3
+ "version": "0.47.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-alpha.29-dev.5",
22
- "@typespec/spector": "0.1.0-alpha.21-dev.3",
23
- "@typespec/spec-api": "0.1.0-alpha.11-dev.1",
24
- "@typespec/tspd": "0.73.1",
25
- "@azure-tools/azure-http-specs": "0.1.0-alpha.33-dev.4",
26
- "@azure-tools/typespec-autorest": "^0.62.0",
27
- "@azure-tools/typespec-azure-core": "^0.62.0",
28
- "@azure-tools/typespec-azure-resource-manager": "^0.62.1",
29
- "@azure-tools/typespec-client-generator-core": "^0.62.0",
21
+ "@typespec/http-specs": "0.1.0-alpha.30-dev.0",
22
+ "@typespec/spector": "0.1.0-alpha.22-dev.0",
23
+ "@typespec/spec-api": "0.1.0-alpha.12-dev.0",
24
+ "@typespec/tspd": "0.73.2",
25
+ "@azure-tools/azure-http-specs": "0.1.0-alpha.34-dev.1",
26
+ "@azure-tools/typespec-autorest": "^0.63.0",
27
+ "@azure-tools/typespec-azure-core": "^0.63.0",
28
+ "@azure-tools/typespec-azure-resource-manager": "^0.63.0",
29
+ "@azure-tools/typespec-client-generator-core": "^0.63.0",
30
30
  "@azure/abort-controller": "^2.1.2",
31
31
  "@azure/core-auth": "^1.6.0",
32
32
  "@azure/core-lro": "^3.1.0",
@@ -40,12 +40,12 @@
40
40
  "@types/mocha": "^10.0.6",
41
41
  "@typescript-eslint/eslint-plugin": "^8.28.0",
42
42
  "@typescript-eslint/parser": "^8.28.0",
43
- "@typespec/compiler": "^1.6.0",
44
- "@typespec/http": "^1.6.0",
45
- "@typespec/openapi": "^1.6.0",
46
- "@typespec/rest": "^0.76.0",
43
+ "@typespec/compiler": "^1.7.0",
44
+ "@typespec/http": "^1.7.0",
45
+ "@typespec/openapi": "^1.7.0",
46
+ "@typespec/rest": "^0.77.0",
47
47
  "@typespec/ts-http-runtime": "^0.1.0",
48
- "@typespec/versioning": "^0.76.0",
48
+ "@typespec/versioning": "^0.77.0",
49
49
  "chai": "^4.3.6",
50
50
  "chalk": "^4.0.0",
51
51
  "cross-env": "^7.0.3",
@@ -64,16 +64,16 @@
64
64
  "js-yaml": "^4.1.0"
65
65
  },
66
66
  "peerDependencies": {
67
- "@azure-tools/typespec-azure-core": "^0.62.0",
68
- "@azure-tools/typespec-client-generator-core": "^0.62.0",
69
- "@typespec/compiler": "^1.6.0",
70
- "@typespec/http": "^1.6.0",
71
- "@typespec/rest": "^0.76.0",
72
- "@typespec/versioning": "^0.76.0",
73
- "@typespec/xml": "^0.76.0"
67
+ "@azure-tools/typespec-azure-core": "^0.63.0",
68
+ "@azure-tools/typespec-client-generator-core": "^0.63.0",
69
+ "@typespec/compiler": "^1.7.0",
70
+ "@typespec/http": "^1.7.0",
71
+ "@typespec/rest": "^0.77.0",
72
+ "@typespec/versioning": "^0.77.0",
73
+ "@typespec/xml": "^0.77.0"
74
74
  },
75
75
  "dependencies": {
76
- "@azure-tools/rlc-common": "^0.47.0",
76
+ "@azure-tools/rlc-common": "^0.47.1",
77
77
  "fs-extra": "^11.1.0",
78
78
  "lodash": "^4.17.21",
79
79
  "prettier": "^3.3.3",
@@ -168,7 +168,7 @@ function enrichFlattenProperties(
168
168
  baseModel,
169
169
  getAllAncestors(baseModel)
170
170
  )
171
- .filter((p) => p.flatten === false || p.flatten === undefined)
171
+ .filter((p) => !(p.flatten && p.type.kind === "model"))
172
172
  .map((p) => normalizeModelPropertyName(context, p));
173
173
  baseModelProperties.set(
174
174
  baseModel,
@@ -185,10 +185,7 @@ function enrichFlattenProperties(
185
185
  const conflictMap = new Map<SdkModelPropertyType, string>();
186
186
  const flattenModel = flattenProperty.type;
187
187
 
188
- if (flattenModel.kind !== "model") {
189
- continue;
190
- }
191
- if (baseModelProperties.has(flattenModel)) {
188
+ if (baseModelProperties.has(flattenModel as SdkModelType)) {
192
189
  // If the flatten model is also a base model of other flatten properties, which means it has multiple consecutive flatten operations
193
190
  // Since we cannot handle the flatten transition, report warning and skip it for now
194
191
  reportDiagnostic(context.program, {
@@ -28,10 +28,10 @@ export function isStaticHelperMetadata(
28
28
  ): metadata is StaticHelperMetadata {
29
29
  return Boolean(
30
30
  metadata &&
31
- metadata.name &&
32
- metadata.kind &&
33
- metadata.location &&
34
- metadata[SourceFileSymbol]
31
+ metadata.name &&
32
+ metadata.kind &&
33
+ metadata.location &&
34
+ metadata[SourceFileSymbol]
35
35
  );
36
36
  }
37
37
 
@@ -39,8 +39,7 @@ export type StaticHelpers = Record<string, StaticHelperMetadata>;
39
39
 
40
40
  const DEFAULT_STATIC_HELPERS_PATH = "static/static-helpers";
41
41
 
42
- export interface LoadStaticHelpersOptions
43
- extends Partial<ModularEmitterOptions> {
42
+ export interface LoadStaticHelpersOptions extends Partial<ModularEmitterOptions> {
44
43
  helpersAssetDirectory?: string;
45
44
  sourcesDir?: string;
46
45
  program?: Program;
@@ -78,31 +78,60 @@ export function buildClientContext(
78
78
  getClientContextPath(clientMap, emitterOptions)
79
79
  );
80
80
 
81
+ // Get all client parameters (both required and optional) for the interface
82
+ const requiredInterfaceProperties = getClientParameters(client, dpgContext, {
83
+ onClientOnly: false,
84
+ requiredOnly: true,
85
+ apiVersionAsRequired: true
86
+ })
87
+ .filter((p) => {
88
+ const clientParamName = getClientParameterName(p);
89
+ return (
90
+ clientParamName !== "endpointParam" && clientParamName !== "credential"
91
+ );
92
+ })
93
+ .map((p) => {
94
+ return {
95
+ name: getClientParameterName(p),
96
+ type: getTypeExpression(dpgContext, p.type),
97
+ hasQuestionToken: false,
98
+ docs: getDocsWithKnownVersion(dpgContext, p)
99
+ };
100
+ });
101
+
102
+ // Collect names of required properties to avoid duplicates
103
+ const requiredPropertyNames = new Set(
104
+ requiredInterfaceProperties.map((p) => p.name)
105
+ );
106
+
107
+ const optionalInterfaceProperties = getClientParameters(client, dpgContext, {
108
+ onClientOnly: false,
109
+ optionalOnly: true
110
+ })
111
+ .filter((p) => {
112
+ const clientParamName = getClientParameterName(p);
113
+ return (
114
+ clientParamName !== "endpointParam" &&
115
+ clientParamName !== "credential" &&
116
+ clientParamName !== "endpoint" &&
117
+ !requiredPropertyNames.has(clientParamName) // Avoid duplicating required properties
118
+ );
119
+ })
120
+ .map((p) => {
121
+ return {
122
+ name: getClientParameterName(p),
123
+ type: getTypeExpression(dpgContext, p.type),
124
+ hasQuestionToken: true,
125
+ docs: getDocsWithKnownVersion(dpgContext, p)
126
+ };
127
+ });
128
+
81
129
  clientContextFile.addInterface({
82
130
  isExported: true,
83
131
  name: `${rlcClientName}`,
84
132
  extends: [resolveReference(dependencies.Client)],
85
133
  docs: getDocsFromDescription(client.doc),
86
- properties: getClientParameters(client, dpgContext, {
87
- onClientOnly: false,
88
- requiredOnly: true,
89
- apiVersionAsRequired: true
90
- })
91
- .filter((p) => {
92
- const clientParamName = getClientParameterName(p);
93
- return (
94
- clientParamName !== "endpointParam" &&
95
- clientParamName !== "credential"
96
- );
97
- })
98
- .map((p) => {
99
- return {
100
- name: getClientParameterName(p),
101
- type: getTypeExpression(dpgContext, p.type),
102
- hasQuestionToken: false,
103
- docs: getDocsWithKnownVersion(dpgContext, p)
104
- };
105
- })
134
+ properties: [...requiredInterfaceProperties, ...optionalInterfaceProperties]
106
135
  });
107
136
 
108
137
  const propertiesInOptions = getClientParameters(client, dpgContext, {
@@ -168,11 +197,8 @@ export function buildClientContext(
168
197
  isExported: true
169
198
  });
170
199
 
171
- const endpointParam = buildGetClientEndpointParam(
172
- factoryFunction,
173
- dpgContext,
174
- client
175
- );
200
+ const { endpointParamName: endpointParam, assignedOptionalParams } =
201
+ buildGetClientEndpointParam(factoryFunction, dpgContext, client);
176
202
  const credentialParam = buildGetClientCredentialParam(client, emitterOptions);
177
203
  const optionsParam = buildGetClientOptionsParam(
178
204
  factoryFunction,
@@ -262,13 +288,44 @@ export function buildClientContext(
262
288
  p.name !== "credential" &&
263
289
  p.name !== "options"
264
290
  );
265
- if (contextRequiredParam.length) {
291
+
292
+ // Collect names of required parameters to avoid duplicates
293
+ const requiredParamNames = new Set(contextRequiredParam.map((p) => p.name));
294
+
295
+ // Also include optional parameters from clientInitialization that should be passed through
296
+ const contextOptionalParams = getClientParameters(client, dpgContext, {
297
+ optionalOnly: true,
298
+ onClientOnly: false
299
+ }).filter((p) => {
300
+ const clientParamName = getClientParameterName(p);
301
+ return (
302
+ clientParamName !== "endpointParam" &&
303
+ clientParamName !== "credential" &&
304
+ clientParamName !== "endpoint" &&
305
+ !requiredParamNames.has(clientParamName) // Avoid duplicating required parameters
306
+ );
307
+ });
308
+
309
+ // Build context params, checking if param was already assigned as a required param
310
+ const allContextParams = [
311
+ ...contextRequiredParam.map((p) => p.name),
312
+ ...contextOptionalParams.map((p) => {
313
+ const clientParamName = getClientParameterName(p);
314
+ // If this param was already assigned (e.g., as a required param or in endpoint building), use the value directly
315
+ // Otherwise, get it from options
316
+ if (
317
+ requiredParamNames.has(clientParamName) ||
318
+ (assignedOptionalParams && assignedOptionalParams.has(clientParamName))
319
+ ) {
320
+ return clientParamName;
321
+ }
322
+ return `${clientParamName}: options.${clientParamName}`;
323
+ })
324
+ ];
325
+
326
+ if (allContextParams.length) {
266
327
  factoryFunction.addStatements(
267
- `return { ...clientContext, ${contextRequiredParam
268
- .map((p) => {
269
- return p.name;
270
- })
271
- .join(", ")}} as ${rlcClientName};`
328
+ `return { ...clientContext, ${allContextParams.join(", ")}} as ${rlcClientName};`
272
329
  );
273
330
  } else {
274
331
  factoryFunction.addStatements(`return clientContext;`);
@@ -501,7 +501,7 @@ function buildModelInterface(
501
501
  .filter((p) => !isMetadata(context.program, p.__raw!))
502
502
  .filter((p) => {
503
503
  // filter out the flatten property to be processed later
504
- if (p.flatten) {
504
+ if (p.flatten && p.type.kind === "model") {
505
505
  flattenPropertySet.add(p);
506
506
  return false;
507
507
  }
@@ -512,9 +512,6 @@ function buildModelInterface(
512
512
  })
513
513
  } as InterfaceStructure;
514
514
  for (const flatten of flattenPropertySet.keys()) {
515
- if (flatten.type?.kind !== "model" || !flatten.type.properties) {
516
- continue;
517
- }
518
515
  const conflictMap =
519
516
  useContext("sdkTypes").flattenProperties.get(flatten)?.conflictMap;
520
517
  const allProperties = getAllProperties(
@@ -174,7 +174,8 @@ export function buildGetClientEndpointParam(
174
174
  context: StatementedNode,
175
175
  dpgContext: SdkContext,
176
176
  client: SdkClientType<SdkServiceOperation>
177
- ): string {
177
+ ): { endpointParamName: string; assignedOptionalParams?: Set<string> } {
178
+ const assignedOptionalParams = new Set<string>();
178
179
  let coreEndpointParam = "";
179
180
  if (dpgContext.rlcOptions?.flavor === "azure") {
180
181
  const cloudSettingSuffix = dpgContext.arm
@@ -209,8 +210,10 @@ export function buildGetClientEndpointParam(
209
210
  context.addStatements(
210
211
  `const ${paramName} = options.${paramName} ?? ${defaultValue};`
211
212
  );
213
+ assignedOptionalParams.add(paramName);
212
214
  } else if (templateParam.optional) {
213
215
  context.addStatements(`const ${paramName} = options.${paramName};`);
216
+ assignedOptionalParams.add(paramName);
214
217
  }
215
218
  parameterizedEndpointUrl = parameterizedEndpointUrl.replace(
216
219
  `{${templateParam.name}}`,
@@ -219,7 +222,7 @@ export function buildGetClientEndpointParam(
219
222
  }
220
223
  const endpointUrl = `const endpointUrl = ${coreEndpointParam} ?? \`${parameterizedEndpointUrl}\`;`;
221
224
  context.addStatements(endpointUrl);
222
- return "endpointUrl";
225
+ return { endpointParamName: "endpointUrl", assignedOptionalParams };
223
226
  } else if (endpointParam.type.kind === "endpoint") {
224
227
  const clientDefaultValue =
225
228
  endpointParam.type.templateArguments[0]?.clientDefaultValue;
@@ -231,14 +234,14 @@ export function buildGetClientEndpointParam(
231
234
  : `String(${getClientParameterName(endpointParam)})`;
232
235
  const endpointUrl = `const endpointUrl = ${coreEndpointParam} ?? ${defaultValueStr};`;
233
236
  context.addStatements(endpointUrl);
234
- return "endpointUrl";
237
+ return { endpointParamName: "endpointUrl" };
235
238
  }
236
239
  const endpointUrl = `const endpointUrl = ${coreEndpointParam} ?? String(${getClientParameterName(endpointParam)});`;
237
240
  context.addStatements(endpointUrl);
238
- return "endpointUrl";
241
+ return { endpointParamName: "endpointUrl" };
239
242
  }
240
243
 
241
- return "endpointUrl";
244
+ return { endpointParamName: "endpointUrl" };
242
245
  }
243
246
 
244
247
  /**
@@ -391,8 +391,9 @@ function getOperationSignatureParameters(
391
391
  p.type.kind !== "constant" &&
392
392
  operation.operation.parameters.filter((param) => {
393
393
  return (
394
- param.correspondingMethodParams.length === 1 &&
395
- param.correspondingMethodParams[0] === p
394
+ param.methodParameterSegments.length === 1 &&
395
+ param.methodParameterSegments[0]?.length === 1 &&
396
+ param.methodParameterSegments[0]?.[0] === p
396
397
  );
397
398
  })[0]?.kind !== "cookie" &&
398
399
  p.clientDefaultValue === undefined &&
@@ -742,8 +743,8 @@ function getHeaderAndBodyParameters(
742
743
  }
743
744
  // Check if this parameter still exists in the corresponding method params (after override)
744
745
  if (
745
- param.correspondingMethodParams &&
746
- param.correspondingMethodParams.length > 0
746
+ param.methodParameterSegments &&
747
+ param.methodParameterSegments.length > 0
747
748
  ) {
748
749
  parametersImplementation[param.kind].push({
749
750
  paramMap: getParameterMap(dpgContext, param, optionalParamName),
@@ -1075,9 +1076,12 @@ function getPathParameters(
1075
1076
  const pathParams: string[] = [];
1076
1077
  for (const param of operation.operation.parameters) {
1077
1078
  if (param.kind === "path") {
1078
- pathParams.push(
1079
- `"${param.serializedName}": ${getPathParamExpr(param.correspondingMethodParams[0]!, getDefaultValue(param) as string, optionalParamName)}`
1080
- );
1079
+ const methodParam = param.methodParameterSegments[0]?.[0];
1080
+ if (methodParam) {
1081
+ pathParams.push(
1082
+ `"${param.serializedName}": ${getPathParamExpr(methodParam, getDefaultValue(param) as string, optionalParamName)}`
1083
+ );
1084
+ }
1081
1085
  }
1082
1086
  }
1083
1087
 
@@ -1108,8 +1112,8 @@ function getQueryParameters(
1108
1112
  if (param.kind === "query") {
1109
1113
  // Check if this parameter still exists in the corresponding method params (after override)
1110
1114
  if (
1111
- param.correspondingMethodParams &&
1112
- param.correspondingMethodParams.length > 0
1115
+ param.methodParameterSegments &&
1116
+ param.methodParameterSegments.length > 0
1113
1117
  ) {
1114
1118
  parametersImplementation[param.kind].push({
1115
1119
  paramMap: getParameterMap(dpgContext, {
@@ -1207,7 +1211,7 @@ export function getSerializationExpression(
1207
1211
  propertyPath: string,
1208
1212
  enableFlatten: boolean = true
1209
1213
  ): string {
1210
- if (property.flatten && enableFlatten) {
1214
+ if (property.flatten && property.type.kind === "model" && enableFlatten) {
1211
1215
  return getSerializationExpressionForFlatten(context, property, "item");
1212
1216
  }
1213
1217
  const dot = propertyPath.endsWith("?") ? "." : "";
@@ -132,9 +132,11 @@ export function buildPropertyNameMapper(
132
132
  * @returns
133
133
  */
134
134
  export function isSpreadBodyParameter(body: SdkBodyParameter) {
135
- const methodParams = body.correspondingMethodParams;
135
+ const methodParams = body.methodParameterSegments;
136
136
  return (
137
137
  methodParams.length > 1 ||
138
- (methodParams.length === 1 && methodParams[0]?.type !== body.type)
138
+ (methodParams.length === 1 &&
139
+ methodParams[0]?.length === 1 &&
140
+ methodParams[0][0]?.type !== body.type)
139
141
  );
140
142
  }
@@ -144,8 +144,8 @@ export function isDiscriminatedUnion(
144
144
  }
145
145
  return Boolean(
146
146
  type?.kind === "model" &&
147
- type.discriminatorProperty &&
148
- type.discriminatedSubtypes
147
+ type.discriminatorProperty &&
148
+ type.discriminatedSubtypes
149
149
  );
150
150
  }
151
151
 
@@ -30,7 +30,7 @@ export function transformApiVersionInfo(
30
30
  queryVersionDetail || pathVersionDetail
31
31
  ? Boolean(
32
32
  pathVersionDetail?.isCrossedVersion ||
33
- queryVersionDetail?.isCrossedVersion
33
+ queryVersionDetail?.isCrossedVersion
34
34
  )
35
35
  : undefined;
36
36
  const defaultValue =
@@ -107,8 +107,8 @@ export function isMediaTypeJsonMergePatch(
107
107
  if (Array.isArray(mediaType)) {
108
108
  return Boolean(
109
109
  mediaType.length === 1 &&
110
- mediaType[0] &&
111
- isMediaTypeJsonMergePatch(mediaType[0])
110
+ mediaType[0] &&
111
+ isMediaTypeJsonMergePatch(mediaType[0])
112
112
  );
113
113
  }
114
114
  const mt = parseMediaType(mediaType);
@@ -133,8 +133,8 @@ export function isMediaTypeMultipartFormData(
133
133
  if (Array.isArray(mediaType)) {
134
134
  return Boolean(
135
135
  mediaType.length === 1 &&
136
- mediaType[0] &&
137
- isMediaTypeMultipartFormData(mediaType[0])
136
+ mediaType[0] &&
137
+ isMediaTypeMultipartFormData(mediaType[0])
138
138
  );
139
139
  }
140
140