@azure-tools/typespec-ts 0.28.0 → 0.29.0-alpha.20240531.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 (94) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/src/index.d.ts +2 -0
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/index.js +24 -18
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/lib.d.ts +14 -4
  7. package/dist/src/lib.d.ts.map +1 -1
  8. package/dist/src/lib.js +9 -2
  9. package/dist/src/lib.js.map +1 -1
  10. package/dist/src/modular/buildClassicalClient.d.ts.map +1 -1
  11. package/dist/src/modular/buildClassicalClient.js +2 -0
  12. package/dist/src/modular/buildClassicalClient.js.map +1 -1
  13. package/dist/src/modular/buildClassicalOperationGroups.d.ts.map +1 -1
  14. package/dist/src/modular/buildClassicalOperationGroups.js +3 -0
  15. package/dist/src/modular/buildClassicalOperationGroups.js.map +1 -1
  16. package/dist/src/modular/buildCodeModel.d.ts.map +1 -1
  17. package/dist/src/modular/buildCodeModel.js +80 -83
  18. package/dist/src/modular/buildCodeModel.js.map +1 -1
  19. package/dist/src/modular/buildLroFiles.d.ts +24 -0
  20. package/dist/src/modular/buildLroFiles.d.ts.map +1 -0
  21. package/dist/src/modular/buildLroFiles.js +399 -0
  22. package/dist/src/modular/buildLroFiles.js.map +1 -0
  23. package/dist/src/modular/buildOperations.d.ts +6 -0
  24. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  25. package/dist/src/modular/buildOperations.js +64 -5
  26. package/dist/src/modular/buildOperations.js.map +1 -1
  27. package/dist/src/modular/buildPagingFiles.js +3 -3
  28. package/dist/src/modular/buildPagingFiles.js.map +1 -1
  29. package/dist/src/modular/buildProjectFiles.d.ts +0 -3
  30. package/dist/src/modular/buildProjectFiles.d.ts.map +1 -1
  31. package/dist/src/modular/buildProjectFiles.js +0 -399
  32. package/dist/src/modular/buildProjectFiles.js.map +1 -1
  33. package/dist/src/modular/buildRootIndex.d.ts.map +1 -1
  34. package/dist/src/modular/buildRootIndex.js +20 -0
  35. package/dist/src/modular/buildRootIndex.js.map +1 -1
  36. package/dist/src/modular/buildSubpathIndex.js +4 -3
  37. package/dist/src/modular/buildSubpathIndex.js.map +1 -1
  38. package/dist/src/modular/emitModels.d.ts +2 -0
  39. package/dist/src/modular/emitModels.d.ts.map +1 -1
  40. package/dist/src/modular/emitModels.js +73 -11
  41. package/dist/src/modular/emitModels.js.map +1 -1
  42. package/dist/src/modular/helpers/operationHelpers.d.ts +5 -5
  43. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  44. package/dist/src/modular/helpers/operationHelpers.js +171 -55
  45. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  46. package/dist/src/modular/helpers/typeHelpers.d.ts.map +1 -1
  47. package/dist/src/modular/helpers/typeHelpers.js +6 -1
  48. package/dist/src/modular/helpers/typeHelpers.js.map +1 -1
  49. package/dist/src/modular/interfaces.d.ts +5 -0
  50. package/dist/src/modular/interfaces.d.ts.map +1 -1
  51. package/dist/src/modular/modularCodeModel.d.ts +9 -1
  52. package/dist/src/modular/modularCodeModel.d.ts.map +1 -1
  53. package/dist/src/transform/transformHelperFunctionDetails.js +1 -1
  54. package/dist/src/transform/transformParameters.js +2 -2
  55. package/dist/src/transform/transformParameters.js.map +1 -1
  56. package/dist/src/transform/transformPaths.js +1 -1
  57. package/dist/src/transform/transformPaths.js.map +1 -1
  58. package/dist/src/transform/transformResponses.js +2 -2
  59. package/dist/src/transform/transformResponses.js.map +1 -1
  60. package/dist/src/transform/transformSchemas.d.ts.map +1 -1
  61. package/dist/src/transform/transformSchemas.js +23 -31
  62. package/dist/src/transform/transformSchemas.js.map +1 -1
  63. package/dist/src/transform/transfromRLCOptions.d.ts.map +1 -1
  64. package/dist/src/transform/transfromRLCOptions.js +8 -1
  65. package/dist/src/transform/transfromRLCOptions.js.map +1 -1
  66. package/dist/src/utils/modelUtils.d.ts +12 -2
  67. package/dist/src/utils/modelUtils.d.ts.map +1 -1
  68. package/dist/src/utils/modelUtils.js +88 -49
  69. package/dist/src/utils/modelUtils.js.map +1 -1
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +19 -18
  72. package/src/index.ts +36 -22
  73. package/src/lib.ts +10 -2
  74. package/src/modular/buildClassicalClient.ts +2 -0
  75. package/src/modular/buildClassicalOperationGroups.ts +4 -0
  76. package/src/modular/buildCodeModel.ts +100 -155
  77. package/src/modular/buildLroFiles.ts +433 -0
  78. package/src/modular/buildOperations.ts +91 -10
  79. package/src/modular/buildPagingFiles.ts +4 -4
  80. package/src/modular/buildProjectFiles.ts +0 -471
  81. package/src/modular/buildRootIndex.ts +49 -0
  82. package/src/modular/buildSubpathIndex.ts +4 -4
  83. package/src/modular/emitModels.ts +102 -15
  84. package/src/modular/helpers/operationHelpers.ts +207 -72
  85. package/src/modular/helpers/typeHelpers.ts +6 -2
  86. package/src/modular/interfaces.ts +6 -0
  87. package/src/modular/modularCodeModel.ts +10 -1
  88. package/src/transform/transformHelperFunctionDetails.ts +1 -1
  89. package/src/transform/transformParameters.ts +2 -2
  90. package/src/transform/transformPaths.ts +1 -1
  91. package/src/transform/transformResponses.ts +2 -2
  92. package/src/transform/transformSchemas.ts +22 -33
  93. package/src/transform/transfromRLCOptions.ts +10 -1
  94. package/src/utils/modelUtils.ts +107 -46
@@ -0,0 +1,433 @@
1
+ import { SourceFile } from "ts-morph";
2
+ import { getClientName } from "./helpers/namingHelpers.js";
3
+ import { isLroOnlyOperation } from "./helpers/operationHelpers.js";
4
+ import { ModularCodeModel, Client } from "./modularCodeModel.js";
5
+ import path from "path";
6
+ import { buildLroDeserDetailMap } from "./buildOperations.js";
7
+ import { NameType, normalizeName } from "@azure-tools/rlc-common";
8
+
9
+ /**
10
+ * Always import LRO dependencies and remember to remove if unused.
11
+ * @param clientFile - The file to add the imports to.
12
+ */
13
+ export function importLroCoreDependencies(clientFile: SourceFile) {
14
+ clientFile.addImportDeclaration({
15
+ moduleSpecifier: `@azure/core-lro`,
16
+ namedImports: [
17
+ "PollerLike",
18
+ "OperationState",
19
+ "deserializeState",
20
+ "ResourceLocationConfig",
21
+ "LongRunningOperation",
22
+ "createHttpPoller",
23
+ "OperationResponse"
24
+ ]
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Generate `restorePollerHelpers.ts` file
30
+ * @param codeModel - The code model.
31
+ * @param client - The client.
32
+ * @returns
33
+ */
34
+ export function buildRestorePollerHelper(
35
+ codeModel: ModularCodeModel,
36
+ client: Client
37
+ ) {
38
+ const lros = client.operationGroups
39
+ .flatMap((op) => op.operations)
40
+ .filter(isLroOnlyOperation);
41
+ if (lros.length === 0) {
42
+ return;
43
+ }
44
+ const srcPath = codeModel.modularOptions.sourceRoot;
45
+ const subfolder = client.subfolder ?? "";
46
+ const filePath = path.join(
47
+ `${srcPath}/${
48
+ subfolder !== "" ? subfolder + "/" : ""
49
+ }restorePollerHelpers.ts`
50
+ );
51
+ const restorePollerFile = codeModel.project.createSourceFile(
52
+ filePath,
53
+ undefined,
54
+ {
55
+ overwrite: true
56
+ }
57
+ );
58
+
59
+ importLroCoreDependencies(restorePollerFile);
60
+ const clientNames = importClientContext(client, restorePollerFile);
61
+ importGetPollerHelper(restorePollerFile);
62
+ const deserializeMap = importDeserializeHelpers(client, restorePollerFile);
63
+ const restorePollerHelperContent = `import {
64
+ PathUncheckedResponse,
65
+ OperationOptions
66
+ } from "@azure-rest/core-client";
67
+ import { AbortSignalLike } from "@azure/abort-controller";
68
+
69
+ export interface RestorePollerOptions<
70
+ TResult,
71
+ TResponse extends PathUncheckedResponse = PathUncheckedResponse
72
+ > extends OperationOptions {
73
+ /** Delay to wait until next poll, in milliseconds. */
74
+ updateIntervalInMs?: number;
75
+ /**
76
+ * The signal which can be used to abort requests.
77
+ */
78
+ abortSignal?: AbortSignalLike;
79
+ /** Deserialization function for raw response body */
80
+ processResponseBody?: (result: TResponse) => PromiseLike<TResult>;
81
+ }
82
+
83
+ /**
84
+ * Creates a poller from the serialized state of another poller. This can be
85
+ * useful when you want to create pollers on a different host or a poller
86
+ * needs to be constructed after the original one is not in scope.
87
+ */
88
+ export function restorePoller<TResponse extends PathUncheckedResponse, TResult>(
89
+ client: ${clientNames.join(" | ")},
90
+ serializedState: string,
91
+ sourceOperation: (
92
+ ...args: any[]
93
+ ) => PollerLike<OperationState<TResult>, TResult>,
94
+ options?: RestorePollerOptions<TResult>
95
+ ): PollerLike<OperationState<TResult>, TResult> {
96
+ const pollerConfig = deserializeState(serializedState).config;
97
+ const { initialUrl, requestMethod, metadata } = pollerConfig;
98
+ if (!initialUrl || !requestMethod) {
99
+ throw new Error(
100
+ \`Invalid serialized state: \${serializedState} for sourceOperation \${sourceOperation?.name}\`
101
+ );
102
+ }
103
+ const resourceLocationConfig = metadata?.["resourceLocationConfig"] as
104
+ | ResourceLocationConfig
105
+ | undefined;
106
+ const deserializeHelper =
107
+ options?.processResponseBody ??
108
+ getDeserializationHelper(initialUrl, requestMethod);
109
+ if (!deserializeHelper) {
110
+ throw new Error(
111
+ \`Please ensure the operation is in this client! We can't find its deserializeHelper for \${sourceOperation?.name}.\`
112
+ );
113
+ }
114
+ return getLongRunningPoller(
115
+ (client as any)["_client"] ?? client,
116
+ deserializeHelper as (result: TResponse) => PromiseLike<TResult>,
117
+ {
118
+ updateIntervalInMs: options?.updateIntervalInMs,
119
+ abortSignal: options?.abortSignal,
120
+ resourceLocationConfig,
121
+ restoreFrom: serializedState,
122
+ initialUrl
123
+ }
124
+ );
125
+ }
126
+
127
+ const deserializeMap: Record<string, Function> = {
128
+ ${deserializeMap.join(",\n")}
129
+ };
130
+
131
+ function getDeserializationHelper(
132
+ urlStr: string,
133
+ method: string
134
+ ): ((result: unknown) => PromiseLike<unknown>) | undefined {
135
+ const path = new URL(urlStr).pathname;
136
+ const pathParts = path.split("/");
137
+
138
+ // Traverse list to match the longest candidate
139
+ // matchedLen: the length of candidate path
140
+ // matchedValue: the matched status code array
141
+ let matchedLen = -1,
142
+ matchedValue: ((result: unknown) => PromiseLike<unknown>) | undefined;
143
+
144
+ // Iterate the responseMap to find a match
145
+ for (const [key, value] of Object.entries(deserializeMap)) {
146
+ // Extracting the path from the map key which is in format
147
+ // GET /path/foo
148
+ if (!key.startsWith(method)) {
149
+ continue;
150
+ }
151
+ const candidatePath = getPathFromMapKey(key);
152
+ // Get each part of the url path
153
+ const candidateParts = candidatePath.split("/");
154
+
155
+ // track if we have found a match to return the values found.
156
+ let found = true;
157
+ for (
158
+ let i = candidateParts.length - 1, j = pathParts.length - 1;
159
+ i >= 1 && j >= 1;
160
+ i--, j--
161
+ ) {
162
+ if (
163
+ candidateParts[i]?.startsWith("{") &&
164
+ candidateParts[i]?.indexOf("}") !== -1
165
+ ) {
166
+ const start = candidateParts[i]!.indexOf("}") + 1,
167
+ end = candidateParts[i]?.length;
168
+ // If the current part of the candidate is a "template" part
169
+ // Try to use the suffix of pattern to match the path
170
+ // {guid} ==> $
171
+ // {guid}:export ==> :export$
172
+ const isMatched = new RegExp(
173
+ \`\${candidateParts[i]?.slice(start, end)}\`
174
+ ).test(pathParts[j] || "");
175
+
176
+ if (!isMatched) {
177
+ found = false;
178
+ break;
179
+ }
180
+ continue;
181
+ }
182
+
183
+ // If the candidate part is not a template and
184
+ // the parts don't match mark the candidate as not found
185
+ // to move on with the next candidate path.
186
+ if (candidateParts[i] !== pathParts[j]) {
187
+ found = false;
188
+ break;
189
+ }
190
+ }
191
+
192
+ // We finished evaluating the current candidate parts
193
+ // Update the matched value if and only if we found the longer pattern
194
+ if (found && candidatePath.length > matchedLen) {
195
+ matchedLen = candidatePath.length;
196
+ matchedValue = value as (result: unknown) => PromiseLike<unknown>;
197
+ }
198
+ }
199
+
200
+ return matchedValue;
201
+ }
202
+
203
+ function getPathFromMapKey(mapKey: string): string {
204
+ const pathStart = mapKey.indexOf("/");
205
+ return mapKey.slice(pathStart);
206
+ }
207
+ `;
208
+ restorePollerFile.addStatements(restorePollerHelperContent);
209
+ restorePollerFile.fixMissingImports();
210
+ restorePollerFile.fixUnusedIdentifiers();
211
+ }
212
+
213
+ function importClientContext(client: Client, sourceFile: SourceFile): string[] {
214
+ const name = getClientName(client);
215
+ const classicalClientname = `${getClientName(client)}Client`;
216
+ const clientContextName = client.rlcClientName;
217
+ sourceFile.addImportDeclaration({
218
+ namedImports: [`${clientContextName}`],
219
+ moduleSpecifier: `./api/${normalizeName(name, NameType.File)}Context.js`
220
+ });
221
+
222
+ sourceFile.addImportDeclaration({
223
+ namedImports: [`${classicalClientname}`],
224
+ moduleSpecifier: `./${normalizeName(classicalClientname, NameType.File)}.js`
225
+ });
226
+ return [clientContextName, classicalClientname];
227
+ }
228
+
229
+ function importGetPollerHelper(sourceFile: SourceFile) {
230
+ sourceFile.addImportDeclaration({
231
+ namedImports: [`getLongRunningPoller`],
232
+ moduleSpecifier: `./api/pollingHelpers.js`
233
+ });
234
+ }
235
+
236
+ function importDeserializeHelpers(client: Client, sourceFile: SourceFile) {
237
+ const deserializeDetails = buildLroDeserDetailMap(client);
238
+ const deserializeMap: string[] = [];
239
+ for (const [key, value] of deserializeDetails.entries()) {
240
+ sourceFile.addImportDeclaration({
241
+ namedImports: value.map((detail) =>
242
+ detail.renamedDeserName
243
+ ? `${detail.deserName} as ${detail.renamedDeserName}`
244
+ : detail.deserName
245
+ ),
246
+ moduleSpecifier: key
247
+ });
248
+ value.forEach((detail) => {
249
+ deserializeMap.push(
250
+ `"${detail.path}": ${detail.renamedDeserName ?? detail.deserName}`
251
+ );
252
+ });
253
+ }
254
+ return deserializeMap;
255
+ }
256
+
257
+ /**
258
+ * Generate `api/pollingHelpers.ts` file
259
+ * @param codeModel - The code model.
260
+ * @param client - The client.
261
+ * @param needUnexpectedHelper - Whether need to import unexpected helper.
262
+ * @param isMultiClients - Whether the client is multi-clients.
263
+ * @returns
264
+ */
265
+ export function buildGetPollerHelper(
266
+ codeModel: ModularCodeModel,
267
+ client: Client,
268
+ needUnexpectedHelper: boolean = true,
269
+ isMultiClients: boolean = false
270
+ ) {
271
+ const lroOperstions = client.operationGroups
272
+ .flatMap((op) => op.operations)
273
+ .filter(isLroOnlyOperation);
274
+ if (lroOperstions.length === 0) {
275
+ return;
276
+ }
277
+ const checkResponseStatus = needUnexpectedHelper
278
+ ? `if (isUnexpected(response as PathUncheckedResponse)) {
279
+ throw createRestError(response);
280
+ }`
281
+ : `if (Number.isNaN(response.status)) {
282
+ throw createRestError(
283
+ \`Status code of the response is not a number. Value: \${response.status}\`,
284
+ response
285
+ );
286
+ }`;
287
+
288
+ const unexpectedHelperImport = needUnexpectedHelper
289
+ ? `import { isUnexpected } from "${
290
+ isMultiClients ? "../" : ""
291
+ }../rest/index.js";`
292
+ : "";
293
+ const getLroPollerContent = `
294
+ import {
295
+ Client,
296
+ PathUncheckedResponse,
297
+ createRestError
298
+ } from "@azure-rest/core-client";
299
+ import { AbortSignalLike } from "@azure/abort-controller";
300
+ ${unexpectedHelperImport}
301
+
302
+ export interface GetLongRunningPollerOptions<TResponse> {
303
+ /** Delay to wait until next poll, in milliseconds. */
304
+ updateIntervalInMs?: number;
305
+ /**
306
+ * The signal which can be used to abort requests.
307
+ */
308
+ abortSignal?: AbortSignalLike;
309
+ /**
310
+ * The potential location of the result of the LRO if specified by the LRO extension in the swagger.
311
+ */
312
+ resourceLocationConfig?: ResourceLocationConfig;
313
+ /**
314
+ * The original url of the LRO
315
+ * Should not be null when restoreFrom is set
316
+ */
317
+ initialUrl?: string;
318
+ /**
319
+ * A serialized poller which can be used to resume an existing paused Long-Running-Operation.
320
+ */
321
+ restoreFrom?: string;
322
+ /**
323
+ * The function to get the initial response
324
+ */
325
+ getInitialResponse?: () => PromiseLike<TResponse>;
326
+ }
327
+ export function getLongRunningPoller<
328
+ TResponse extends PathUncheckedResponse,
329
+ TResult = void
330
+ >(
331
+ client: Client,
332
+ processResponseBody: (result: TResponse) => PromiseLike<TResult>,
333
+ options: GetLongRunningPollerOptions<TResponse>
334
+ ): PollerLike<OperationState<TResult>, TResult> {
335
+ const { restoreFrom, getInitialResponse } = options;
336
+ if (!restoreFrom && !getInitialResponse) {
337
+ throw new Error(
338
+ "Either restoreFrom or getInitialResponse must be specified"
339
+ );
340
+ }
341
+ let initialResponse: TResponse | undefined = undefined;
342
+ const pollAbortController = new AbortController();
343
+ const poller: LongRunningOperation<TResponse> = {
344
+ sendInitialRequest: async () => {
345
+ if (!getInitialResponse) {
346
+ throw new Error(
347
+ "getInitialResponse is required when initializing a new poller"
348
+ );
349
+ }
350
+ initialResponse = await getInitialResponse();
351
+ return getLroResponse(initialResponse);
352
+ },
353
+ sendPollRequest: async (
354
+ path: string,
355
+ pollOptions?: {
356
+ abortSignal?: AbortSignalLike;
357
+ }
358
+ ) => {
359
+ // The poll request would both listen to the user provided abort signal and the poller's own abort signal
360
+ function abortListener(): void {
361
+ pollAbortController.abort();
362
+ }
363
+ const abortSignal = pollAbortController.signal;
364
+ if (options.abortSignal?.aborted) {
365
+ pollAbortController.abort();
366
+ } else if (pollOptions?.abortSignal?.aborted) {
367
+ pollAbortController.abort();
368
+ } else if (!abortSignal.aborted) {
369
+ options.abortSignal?.addEventListener("abort", abortListener, {
370
+ once: true
371
+ });
372
+ pollOptions?.abortSignal?.addEventListener("abort", abortListener, {
373
+ once: true
374
+ });
375
+ }
376
+ let response;
377
+ try {
378
+ response = await client.pathUnchecked(path).get({ abortSignal });
379
+ } finally {
380
+ options.abortSignal?.removeEventListener("abort", abortListener);
381
+ pollOptions?.abortSignal?.removeEventListener("abort", abortListener);
382
+ }
383
+ if (options.initialUrl || initialResponse) {
384
+ response.headers["x-ms-original-url"] =
385
+ options.initialUrl ?? initialResponse!.request.url;
386
+ }
387
+
388
+ return getLroResponse(response as TResponse);
389
+ }
390
+ };
391
+ return createHttpPoller(poller, {
392
+ intervalInMs: options?.updateIntervalInMs,
393
+ resourceLocationConfig: options?.resourceLocationConfig,
394
+ restoreFrom: options?.restoreFrom,
395
+ processResult: (result: unknown) => {
396
+ return processResponseBody(result as TResponse) as TResult;
397
+ }
398
+ });
399
+ }
400
+ /**
401
+ * Converts a Rest Client response to a response that the LRO implementation understands
402
+ * @param response - a rest client http response
403
+ * @param deserializeFn - deserialize function to convert Rest response to modular output
404
+ * @returns - An LRO response that the LRO implementation understands
405
+ */
406
+ function getLroResponse<TResponse extends PathUncheckedResponse>(
407
+ response: TResponse
408
+ ): OperationResponse<TResponse> {
409
+ ${checkResponseStatus}
410
+ return {
411
+ flatResponse: response,
412
+ rawResponse: {
413
+ ...response,
414
+ statusCode: Number.parseInt(response.status),
415
+ body: response.body
416
+ }
417
+ };
418
+ }
419
+ `;
420
+ const filePath = path.join(
421
+ codeModel.modularOptions.sourceRoot,
422
+ client.subfolder ?? "",
423
+ `api/pollingHelpers.ts`
424
+ );
425
+
426
+ const fileContent = codeModel.project.createSourceFile(filePath, undefined, {
427
+ overwrite: true
428
+ });
429
+ importLroCoreDependencies(fileContent);
430
+ fileContent.addStatements(getLroPollerContent);
431
+ fileContent.fixMissingImports();
432
+ fileContent.fixUnusedIdentifiers();
433
+ }
@@ -5,7 +5,8 @@ import {
5
5
  getOperationFunction,
6
6
  getSendPrivateFunction,
7
7
  getDeserializePrivateFunction,
8
- getOperationOptionsName
8
+ getOperationOptionsName,
9
+ isLroOnlyOperation
9
10
  } from "./helpers/operationHelpers.js";
10
11
  import { Client, ModularCodeModel, Operation } from "./modularCodeModel.js";
11
12
  import { isRLCMultiEndpoint } from "../utils/clientUtils.js";
@@ -16,6 +17,9 @@ import {
16
17
  addImportsToFiles,
17
18
  clearImportSets
18
19
  } from "@azure-tools/rlc-common";
20
+ import { importLroCoreDependencies } from "./buildLroFiles.js";
21
+ import { OperationPathAndDeserDetails } from "./interfaces.js";
22
+ import { getOperationName } from "./helpers/namingHelpers.js";
19
23
 
20
24
  /**
21
25
  * This function creates a file under /api for each operation group.
@@ -49,6 +53,11 @@ export function buildOperationFiles(
49
53
  subfolder && subfolder !== "" ? subfolder + "/" : ""
50
54
  }api/${operationFileName}.ts`
51
55
  );
56
+ // We need to import the lro helpers and types explicitly because ts-morph may not be able to find correct ones.
57
+ importLroDependencies(
58
+ operationGroupFile,
59
+ operationGroup.namespaceHierarchies.length
60
+ );
52
61
 
53
62
  // Import models used from ./models.ts
54
63
  // We SHOULD keep this because otherwise ts-morph will "helpfully" try to import models from the rest layer when we call fixMissingImports().
@@ -181,8 +190,8 @@ export function importModels(
181
190
  );
182
191
  const models: string[] = [];
183
192
 
184
- for (const entry of modelsFile?.getExportedDeclarations().entries() ?? []) {
185
- models.push(entry[0]);
193
+ for (const [name] of modelsFile?.getExportedDeclarations().entries() ?? []) {
194
+ models.push(name);
186
195
  }
187
196
 
188
197
  if (models.length > 0 && !hasModelsImport) {
@@ -273,6 +282,20 @@ export function importPagingDependencies(
273
282
  });
274
283
  }
275
284
 
285
+ export function importLroDependencies(
286
+ sourceFile: SourceFile,
287
+ importLayer: number = 0
288
+ ) {
289
+ sourceFile.addImportDeclaration({
290
+ moduleSpecifier: `${
291
+ importLayer === 0 ? "./" : "../".repeat(importLayer)
292
+ }pollingHelpers.js`,
293
+ namedImports: ["getLongRunningPoller"]
294
+ });
295
+
296
+ importLroCoreDependencies(sourceFile);
297
+ }
298
+
276
299
  /**
277
300
  * This function generates the interfaces for each operation options
278
301
  */
@@ -286,17 +309,75 @@ export function buildOperationOptions(
286
309
  const options = [...optionalParameters];
287
310
 
288
311
  const name = getOperationOptionsName(operation, true);
312
+ const lroOptions = {
313
+ name: "updateIntervalInMs",
314
+ type: "number",
315
+ hasQuestionToken: true,
316
+ docs: ["Delay to wait until next poll, in milliseconds."]
317
+ };
289
318
 
290
319
  sourceFile.addInterface({
291
320
  name,
292
321
  isExported: true,
293
322
  extends: ["OperationOptions"],
294
- properties: options.map((p) => {
295
- return {
296
- docs: getDocsFromDescription(p.description),
297
- hasQuestionToken: true,
298
- ...buildType(p.clientName, p.type, p.format)
299
- };
300
- })
323
+ properties: (isLroOnlyOperation(operation) ? [lroOptions] : []).concat(
324
+ options.map((p) => {
325
+ return {
326
+ docs: getDocsFromDescription(p.description),
327
+ hasQuestionToken: true,
328
+ ...buildType(p.clientName, p.type, p.format)
329
+ };
330
+ })
331
+ )
301
332
  });
302
333
  }
334
+
335
+ /**
336
+ * This function creates a map of operation file path to operation names.
337
+ */
338
+ export function buildLroDeserDetailMap(client: Client) {
339
+ const map = new Map<string, OperationPathAndDeserDetails[]>();
340
+ const existingNames = new Set<string>();
341
+ for (const operationGroup of client.operationGroups) {
342
+ const operations = operationGroup.operations.filter((o) =>
343
+ isLroOnlyOperation(o)
344
+ );
345
+ // skip this operation group if it has no LRO operations
346
+ if (operations.length === 0) {
347
+ continue;
348
+ }
349
+
350
+ const operationFileName =
351
+ operationGroup.className && operationGroup.namespaceHierarchies.length > 0
352
+ ? `${operationGroup.namespaceHierarchies
353
+ .map((hierarchy) => {
354
+ return normalizeName(hierarchy, NameType.File);
355
+ })
356
+ .join("/")}/index`
357
+ : // When the program has no operation groups defined all operations are put
358
+ // into a nameless operation group. We'll call this operations.
359
+ "operations";
360
+ map.set(
361
+ `./api/${operationFileName}.js`,
362
+ operations.map((o) => {
363
+ const { name } = getOperationName(o);
364
+ const deserName = `_${name}Deserialize`;
365
+ let renamedDeserName = undefined;
366
+ if (existingNames.has(deserName)) {
367
+ const newName = `${name}Deserialize_${operationFileName
368
+ .split("/")
369
+ .slice(0, -1)
370
+ .join("_")}`;
371
+ renamedDeserName = `_${normalizeName(newName, NameType.Method)}`;
372
+ }
373
+ existingNames.add(deserName);
374
+ return {
375
+ path: `${o.method.toUpperCase()} ${o.url}`,
376
+ deserName,
377
+ renamedDeserName
378
+ };
379
+ })
380
+ );
381
+ }
382
+ return map;
383
+ }
@@ -1,12 +1,12 @@
1
1
  import path from "path";
2
2
  import { Client, ModularCodeModel } from "./modularCodeModel.js";
3
3
  import {
4
- hasPagingOperation,
5
- isPagingOperation
4
+ hasPagingOnlyOperation,
5
+ isPagingOnlyOperation
6
6
  } from "./helpers/operationHelpers.js";
7
7
 
8
8
  export function buildPagingTypes(client: Client, codeModel: ModularCodeModel) {
9
- if (!hasPagingOperation(client)) {
9
+ if (!hasPagingOnlyOperation(client)) {
10
10
  return;
11
11
  }
12
12
  const filePath = path.join(
@@ -119,7 +119,7 @@ export function buildPagingHelpers(
119
119
  ) {
120
120
  const pagingOperstions = client.operationGroups
121
121
  .flatMap((op) => op.operations)
122
- .filter(isPagingOperation);
122
+ .filter(isPagingOnlyOperation);
123
123
  if (!pagingOperstions || pagingOperstions.length === 0) {
124
124
  return;
125
125
  }