@azure-tools/typespec-ts 0.45.1-alpha.20251105.1 → 0.46.0

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.45.1-alpha.20251105.1",
3
+ "version": "0.46.0",
4
4
  "description": "An experimental TypeSpec emitter for TypeScript RLC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
@@ -77,7 +77,7 @@
77
77
  "@typespec/xml": "^0.75.0"
78
78
  },
79
79
  "dependencies": {
80
- "@azure-tools/rlc-common": "0.45.1-alpha.20251105.1",
80
+ "@azure-tools/rlc-common": "^0.46.0",
81
81
  "fs-extra": "^11.1.0",
82
82
  "lodash": "^4.17.21",
83
83
  "prettier": "^3.3.3",
@@ -303,7 +303,7 @@ class BinderImp implements Binder {
303
303
  }
304
304
 
305
305
  private cleanUnreferencedHelpers(sourceRoot: string) {
306
- const usedHelperFiles = new Set<SourceFile>();
306
+ const usedHelperNames: string[] = [];
307
307
  for (const helper of this.staticHelpers.values()) {
308
308
  const sourceFile = helper[SourceFileSymbol];
309
309
  if (!sourceFile) {
@@ -315,16 +315,24 @@ class BinderImp implements Binder {
315
315
  const referencedHelper = this.references.get(refkey(helper));
316
316
 
317
317
  if (referencedHelper?.size) {
318
- usedHelperFiles.add(sourceFile);
318
+ usedHelperNames.push(sourceFile.getBaseNameWithoutExtension());
319
319
  }
320
320
  }
321
321
 
322
+ function isFileUnused(file: SourceFile) {
323
+ const name = file.getBaseNameWithoutExtension();
324
+
325
+ // If one of the used helpers' name is a prefix of this file, the file likely represents a platform-specific implementation of the helper
326
+ // so it should be marked as used even if the file has no direct references.
327
+ return !usedHelperNames.some((s) => name.startsWith(s));
328
+ }
329
+
322
330
  this.project
323
331
  //normalizae the final path to adapt to different systems
324
332
  .getSourceFiles(
325
- normalizePath(path.join(sourceRoot, "static-helpers/**/*.ts"))
333
+ normalizePath(path.join(sourceRoot, "static-helpers/**/*.*ts"))
326
334
  )
327
- .filter((helperFile) => !usedHelperFiles.has(helperFile))
335
+ .filter(isFileUnused)
328
336
  .forEach((helperFile) => helperFile.delete());
329
337
  }
330
338
  }
@@ -187,7 +187,7 @@ async function traverseDirectory(
187
187
  } else if (
188
188
  fileStat.isFile() &&
189
189
  !file.endsWith(".d.ts") &&
190
- file.endsWith(".ts")
190
+ /.*\..?ts$/.test(file)
191
191
  ) {
192
192
  const target = path.join(
193
193
  _targetStaticHelpersBaseDir,
@@ -73,14 +73,15 @@ export function getClientParameters(
73
73
  const hasDefaultValue = (p: SdkParameter) =>
74
74
  p.clientDefaultValue || p.__raw?.defaultValue || p.type.kind === "constant";
75
75
  const isRequired = (p: SdkParameter) =>
76
- !p.optional &&
77
- ((!hasDefaultValue(p) &&
76
+ // Special case: when apiVersionAsRequired is true, apiVersion should always be considered required
77
+ (options.apiVersionAsRequired && p.isApiVersionParam) ||
78
+ (!p.optional &&
79
+ !hasDefaultValue(p) &&
78
80
  !(
79
81
  p.type.kind === "endpoint" &&
80
82
  p.type.templateArguments[0] &&
81
83
  hasDefaultValue(p.type.templateArguments[0])
82
- )) ||
83
- (options.apiVersionAsRequired && p.isApiVersionParam));
84
+ ));
84
85
  const isOptional = (p: SdkParameter) => p.optional || hasDefaultValue(p);
85
86
  const skipCredentials = (p: SdkParameter) => p.kind !== "credential";
86
87
  const skipMethodParam = (p: SdkParameter) => p.kind !== "method";
@@ -98,7 +99,6 @@ export function getClientParameters(
98
99
  const params = clientParams.filter((p) =>
99
100
  filters.every((filter) => !filter || filter(p))
100
101
  );
101
-
102
102
  return params;
103
103
  }
104
104
 
@@ -8,6 +8,7 @@ import { NoTarget, Program } from "@typespec/compiler";
8
8
  import {
9
9
  PagingHelpers,
10
10
  PollingHelpers,
11
+ SerializationHelpers,
11
12
  UrlTemplateHelpers
12
13
  } from "../static-helpers-metadata.js";
13
14
  import {
@@ -472,11 +473,18 @@ export function getOperationFunction(
472
473
  };
473
474
 
474
475
  const statements: string[] = [];
475
- statements.push(
476
- `const result = await _${name}Send(${parameters
477
- .map((p) => p.name)
478
- .join(", ")});`
479
- );
476
+
477
+ const parameterList = parameters.map((p) => p.name).join(", ");
478
+ // Special case for binary-only bodies: use helper to call streaming methods so that Core doesn't poison the response body by
479
+ // doing a UTF-8 decode on the raw bytes.
480
+ if (response?.type?.kind === "bytes" && response.type.encode === "bytes") {
481
+ statements.push(`const streamableMethod = _${name}Send(${parameterList});`);
482
+ statements.push(
483
+ `const result = await ${resolveReference(SerializationHelpers.getBinaryResponse)}(streamableMethod);`
484
+ );
485
+ } else {
486
+ statements.push(`const result = await _${name}Send(${parameterList});`);
487
+ }
480
488
  statements.push(`return _${name}Deserialize(result);`);
481
489
 
482
490
  return {
@@ -1577,7 +1585,7 @@ export function getExpectedStatuses(operation: ServiceOperation): string {
1577
1585
  let statusCodes = operation.operation.responses.map((x) => x.statusCodes);
1578
1586
  // LROs may call the same path but with GET to get the operation status.
1579
1587
  if (isLroOnlyOperation(operation) && operation.operation.verb !== "get") {
1580
- statusCodes = Array.from(new Set([...statusCodes, 200, 202]));
1588
+ statusCodes = Array.from(new Set([...statusCodes, 200, 201, 202]));
1581
1589
  }
1582
1590
 
1583
1591
  return `[${statusCodes.map((x) => `"${x}"`).join(", ")}]`;
@@ -28,6 +28,11 @@ export const SerializationHelpers = {
28
28
  kind: "function",
29
29
  name: "serializeRecord",
30
30
  location: "serialization/serialize-record.ts"
31
+ },
32
+ getBinaryResponse: {
33
+ kind: "function",
34
+ name: "getBinaryResponse",
35
+ location: "serialization/get-binary-response.ts"
31
36
  }
32
37
  } as const;
33
38
 
@@ -0,0 +1,20 @@
1
+ import { HttpResponse, StreamableMethod } from "@azure-rest/core-client";
2
+
3
+ /**
4
+ * Gets a response type representing the given streamable response, using the stream methods
5
+ * to bypass Core's default response handling. This works around an issue where binary bodies in Core
6
+ * are coerced into UTF-8, regardless of whether the body is valid UTF-8 or not.
7
+ */
8
+ export async function getBinaryResponse(streamableMethod: StreamableMethod): Promise<HttpResponse & { body?: Uint8Array }> {
9
+ const response = await streamableMethod.asBrowserStream();
10
+
11
+ if (response.body === undefined) {
12
+ return response as HttpResponse & { body?: Uint8Array };
13
+ }
14
+
15
+ const arrayBuffer = await new Response(response.body).arrayBuffer();
16
+ return {
17
+ ...response,
18
+ body: new Uint8Array(arrayBuffer),
19
+ };
20
+ }
@@ -0,0 +1,25 @@
1
+ import { HttpResponse, StreamableMethod } from "@azure-rest/core-client";
2
+ import { Buffer } from "node:buffer";
3
+
4
+ /**
5
+ * Gets a response type representing the given streamable response, using the stream methods
6
+ * to bypass Core's default response handling. This works around an issue where binary bodies in Core
7
+ * are coerced into UTF-8, regardless of whether the body is valid UTF-8 or not.
8
+ */
9
+ export async function getBinaryResponse(
10
+ streamableMethod: StreamableMethod
11
+ ): Promise<HttpResponse & { body?: Uint8Array }> {
12
+ const response = await streamableMethod.asNodeStream();
13
+ if (response.body === undefined) {
14
+ return response as HttpResponse & { body?: Uint8Array };
15
+ }
16
+ const bufs: Buffer[] = [];
17
+ for await (const buf of response.body) {
18
+ bufs.push(Buffer.isBuffer(buf) ? buf : Buffer.from(buf));
19
+ }
20
+
21
+ return {
22
+ ...response,
23
+ body: Buffer.concat(bufs)
24
+ };
25
+ }