@azure-tools/typespec-ts 0.29.0-alpha.20240626.1 → 0.30.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/src/contextManager.d.ts +70 -0
  3. package/dist/src/contextManager.d.ts.map +1 -0
  4. package/dist/src/contextManager.js +59 -0
  5. package/dist/src/contextManager.js.map +1 -0
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +13 -2
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/metaTree.d.ts +12 -0
  10. package/dist/src/metaTree.d.ts.map +1 -0
  11. package/dist/src/metaTree.js +2 -0
  12. package/dist/src/metaTree.js.map +1 -0
  13. package/dist/src/modular/buildClassicalClient.js +3 -1
  14. package/dist/src/modular/buildClassicalClient.js.map +1 -1
  15. package/dist/src/modular/buildCodeModel.d.ts.map +1 -1
  16. package/dist/src/modular/buildCodeModel.js +17 -7
  17. package/dist/src/modular/buildCodeModel.js.map +1 -1
  18. package/dist/src/modular/buildHelperSerializers.d.ts +3 -0
  19. package/dist/src/modular/buildHelperSerializers.d.ts.map +1 -0
  20. package/dist/src/modular/buildHelperSerializers.js +60 -0
  21. package/dist/src/modular/buildHelperSerializers.js.map +1 -0
  22. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  23. package/dist/src/modular/buildOperations.js +5 -0
  24. package/dist/src/modular/buildOperations.js.map +1 -1
  25. package/dist/src/modular/buildSerializeUtils.d.ts.map +1 -1
  26. package/dist/src/modular/buildSerializeUtils.js +11 -4
  27. package/dist/src/modular/buildSerializeUtils.js.map +1 -1
  28. package/dist/src/modular/buildSubpathIndex.d.ts.map +1 -1
  29. package/dist/src/modular/buildSubpathIndex.js +6 -0
  30. package/dist/src/modular/buildSubpathIndex.js.map +1 -1
  31. package/dist/src/modular/emitModels.d.ts +5 -5
  32. package/dist/src/modular/emitModels.d.ts.map +1 -1
  33. package/dist/src/modular/emitModels.js +32 -4
  34. package/dist/src/modular/emitModels.js.map +1 -1
  35. package/dist/src/modular/helpers/operationHelpers.d.ts +10 -2
  36. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  37. package/dist/src/modular/helpers/operationHelpers.js +135 -56
  38. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  39. package/dist/src/modular/helpers/typeHelpers.d.ts +1 -0
  40. package/dist/src/modular/helpers/typeHelpers.d.ts.map +1 -1
  41. package/dist/src/modular/helpers/typeHelpers.js +24 -19
  42. package/dist/src/modular/helpers/typeHelpers.js.map +1 -1
  43. package/dist/src/modular/modularCodeModel.d.ts +2 -2
  44. package/dist/src/modular/modularCodeModel.d.ts.map +1 -1
  45. package/dist/src/modular/serialization/buildSerializerFunction.d.ts +4 -0
  46. package/dist/src/modular/serialization/buildSerializerFunction.d.ts.map +1 -0
  47. package/dist/src/modular/serialization/buildSerializerFunction.js +160 -0
  48. package/dist/src/modular/serialization/buildSerializerFunction.js.map +1 -0
  49. package/dist/src/transform/transformSchemas.d.ts.map +1 -1
  50. package/dist/src/transform/transformSchemas.js +3 -0
  51. package/dist/src/transform/transformSchemas.js.map +1 -1
  52. package/dist/src/utils/importHelper.d.ts +10 -0
  53. package/dist/src/utils/importHelper.d.ts.map +1 -0
  54. package/dist/src/utils/importHelper.js +54 -0
  55. package/dist/src/utils/importHelper.js.map +1 -0
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +4 -4
  58. package/src/contextManager.ts +99 -0
  59. package/src/index.ts +14 -2
  60. package/src/metaTree.ts +14 -0
  61. package/src/modular/buildClassicalClient.ts +5 -1
  62. package/src/modular/buildCodeModel.ts +22 -6
  63. package/src/modular/buildHelperSerializers.ts +71 -0
  64. package/src/modular/buildOperations.ts +7 -1
  65. package/src/modular/buildSerializeUtils.ts +19 -11
  66. package/src/modular/buildSubpathIndex.ts +10 -0
  67. package/src/modular/emitModels.ts +66 -16
  68. package/src/modular/helpers/operationHelpers.ts +198 -84
  69. package/src/modular/helpers/typeHelpers.ts +24 -17
  70. package/src/modular/modularCodeModel.ts +2 -2
  71. package/src/modular/serialization/buildSerializerFunction.ts +224 -0
  72. package/src/transform/transformSchemas.ts +3 -0
  73. package/src/utils/importHelper.ts +64 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.29.0-alpha.20240626.1",
3
+ "version": "0.30.0",
4
4
  "description": "An experimental TypeSpec emitter for TypeScript RLC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "author": "Jose Heredia <joheredi@microsoft.com>",
18
18
  "license": "MIT",
19
19
  "devDependencies": {
20
- "@azure-rest/core-client": "^2.0.0",
20
+ "@azure-rest/core-client": "^2.1.0",
21
21
  "@azure-tools/cadl-ranch-expect": "^0.13.4",
22
22
  "@azure-tools/cadl-ranch-specs": "^0.33.4",
23
23
  "@azure-tools/cadl-ranch": "^0.12.8",
@@ -27,7 +27,7 @@
27
27
  "@azure-tools/typespec-client-generator-core": ">=0.43.0 <1.0.0",
28
28
  "@azure/abort-controller": "^2.1.2",
29
29
  "@azure/core-auth": "^1.6.0",
30
- "@azure/core-lro": "3.0.0-beta.2",
30
+ "@azure/core-lro": "3.0.0",
31
31
  "@azure/core-paging": "^1.5.0",
32
32
  "@azure/core-rest-pipeline": "^1.14.0",
33
33
  "@azure/core-util": "^1.4.0",
@@ -70,7 +70,7 @@
70
70
  "@typespec/versioning": ">=0.57.0 <1.0.0"
71
71
  },
72
72
  "dependencies": {
73
- "@azure-tools/rlc-common": "^0.29.0",
73
+ "@azure-tools/rlc-common": "^0.30.0",
74
74
  "fs-extra": "^11.1.0",
75
75
  "lodash": "^4.17.21",
76
76
  "prettier": "^3.1.0",
@@ -0,0 +1,99 @@
1
+ import { Project, SourceFile } from "ts-morph";
2
+ import { ModularMetaTree, RlcMetaTree } from "./metaTree.js";
3
+ import { EmitContext } from "@typespec/compiler";
4
+ import { SdkContext } from "@azure-tools/typespec-client-generator-core";
5
+
6
+ /**
7
+ * Contexts Object Guidelines
8
+ * --------------------------
9
+ * The `Contexts` object contains various application-wide data that needs to be accessible across different parts of the program without the need to pass down props deeply.
10
+ *
11
+ * When to add a new context:
12
+ * - **Cross-Cutting Concerns**: Add a new context when the data or functionality is a cross-cutting concern used by multiple unrelated components or modules.
13
+ * - **Global State**: If the state needs to be globally accessible and mutable by different parts of the application, and does not belong to a specific component or module.
14
+ * - **Performance Optimization**: To avoid unnecessary compute or prop drilling that may lead to performance bottlenecks, especially when passing down props through many levels.
15
+ * - **Shared Resources**: For resources that are shared across various parts of the application, such as settings, configurations, or metadata.
16
+ *
17
+ * Remember, adding too many contexts can lead to complex dependencies and harder-to-maintain code. Always evaluate if the context is truly necessary or if there are better alternatives such as localized state management or passing props for simpler scenarios.
18
+ */
19
+ type Contexts = {
20
+ rlcMetaTree: RlcMetaTree; // Context for RLC types metadata.
21
+ modularMetaTree: ModularMetaTree; // Context for modular types metadata.
22
+ outputProject: Project; // The TS-Morph root project context for code generation.
23
+ symbolMap: Map<string, SourceFile>; // Mapping of symbols to their corresponding source files.
24
+ emitContext: {
25
+ compilerContext: EmitContext;
26
+ tcgcContext: SdkContext;
27
+ };
28
+ };
29
+
30
+ type ContextKey = keyof Contexts;
31
+
32
+ /**
33
+ * Manages shared contexts across the application to minimize prop drilling and enhance modularity.
34
+ * Implements the Singleton pattern to ensure there is a single instance of ContextManager.
35
+ */
36
+ class ContextManager {
37
+ private static instance: ContextManager;
38
+ private contexts: Map<ContextKey, any> = new Map();
39
+
40
+ private constructor() {}
41
+
42
+ /**
43
+ * Retrieves the singleton instance of the ContextManager.
44
+ * @returns {ContextManager} The singleton instance.
45
+ */
46
+ public static getInstance(): ContextManager {
47
+ if (!ContextManager.instance) {
48
+ ContextManager.instance = new ContextManager();
49
+ }
50
+ return ContextManager.instance;
51
+ }
52
+
53
+ /**
54
+ * Sets a value for a specific context key.
55
+ * @param {ContextKey} key - The key of the context to set.
56
+ * @param {Contexts[K]} value - The value to set for the specified context.
57
+ */
58
+ public setContext<K extends ContextKey>(key: K, value: Contexts[K]): void {
59
+ this.contexts.set(key, value);
60
+ }
61
+
62
+ /**
63
+ * Retrieves the context value for a specific key.
64
+ * @param {ContextKey} key - The key of the context to retrieve.
65
+ * @returns {Contexts[K] | undefined} The value of the context if found, otherwise undefined.
66
+ */
67
+ public getContext<K extends ContextKey>(key: K): Contexts[K] | undefined {
68
+ return this.contexts.get(key) as Contexts[K] | undefined;
69
+ }
70
+ }
71
+
72
+ // Expose the singleton instance of the context manager.
73
+ export const contextManager = ContextManager.getInstance();
74
+
75
+ /**
76
+ * A utility function to use a context by key.
77
+ * @param {ContextKey} key - The key of the context to retrieve.
78
+ * @throws Will throw an error if the context is not found.
79
+ * @returns {Contexts[K]} The context value.
80
+ */
81
+ export function useContext<K extends ContextKey>(key: K): Contexts[K] {
82
+ const context = contextManager.getContext(key);
83
+ if (!context) {
84
+ throw new Error(`Context ${key} not found`);
85
+ }
86
+ return context;
87
+ }
88
+
89
+ /**
90
+ * Provides a context with a value to be accessible across the program.
91
+ * @param {ContextKey} key - The key of the context to provide.
92
+ * @param {Contexts[K]} value - The value to set for the specified context.
93
+ */
94
+ export function provideContext<K extends ContextKey>(
95
+ key: K,
96
+ value: Contexts[K]
97
+ ): void {
98
+ contextManager.setContext(key, value);
99
+ }
package/src/index.ts CHANGED
@@ -66,6 +66,8 @@ import { transformRLCOptions } from "./transform/transfromRLCOptions.js";
66
66
  import { getRLCClients } from "./utils/clientUtils.js";
67
67
  import { emitContentByBuilder, emitModels } from "./utils/emitUtil.js";
68
68
  import { GenerationDirDetail, SdkContext } from "./utils/interfaces.js";
69
+ import { provideContext, useContext } from "./contextManager.js";
70
+ import { emitSerializerHelpersFile } from "./modular/buildHelperSerializers.js";
69
71
 
70
72
  export * from "./lib.js";
71
73
 
@@ -79,6 +81,15 @@ export async function $onEmit(context: EmitContext) {
79
81
  string,
80
82
  RLCModel
81
83
  >();
84
+ provideContext("rlcMetaTree", new Map());
85
+ provideContext("symbolMap", new Map());
86
+ provideContext("modularMetaTree", new Map());
87
+ provideContext("outputProject", new Project());
88
+ provideContext("emitContext", {
89
+ compilerContext: context,
90
+ tcgcContext: dpgContext
91
+ });
92
+
82
93
  const rlcCodeModels: RLCModel[] = [];
83
94
  let modularCodeModel: ModularCodeModel;
84
95
  // 1. Enrich the dpg context with path detail and common options
@@ -173,7 +184,8 @@ export async function $onEmit(context: EmitContext) {
173
184
  // TODO: Emit modular parts of the library
174
185
  const modularSourcesRoot =
175
186
  dpgContext.generationPathDetail?.modularSourcesDir ?? "src";
176
- const project = new Project();
187
+ const project = useContext("outputProject");
188
+ emitSerializerHelpersFile(project, modularSourcesRoot);
177
189
  modularCodeModel = emitCodeModel(
178
190
  dpgContext,
179
191
  serviceNameToRlcModelsMap,
@@ -302,7 +314,7 @@ export async function $onEmit(context: EmitContext) {
302
314
  );
303
315
 
304
316
  if (option.isModularLibrary) {
305
- const project = new Project();
317
+ const project = useContext("outputProject");
306
318
  for (const file of project.getSourceFiles()) {
307
319
  await emitContentByBuilder(
308
320
  program,
@@ -0,0 +1,14 @@
1
+ import { Schema as RlcType } from "@azure-tools/rlc-common";
2
+ import { Type as ModularType } from "./modular/modularCodeModel.js";
3
+ import { Type } from "@typespec/compiler";
4
+
5
+ export interface RlcTypeMetadata {
6
+ rlcType: RlcType;
7
+ }
8
+
9
+ export interface ModularTypeMetadata {
10
+ modularType: ModularType;
11
+ }
12
+
13
+ export type RlcMetaTree = Map<Type, RlcTypeMetadata>;
14
+ export type ModularMetaTree = Map<Type, ModularTypeMetadata>;
@@ -142,7 +142,11 @@ function importAllModels(
142
142
  return;
143
143
  }
144
144
 
145
- const exported = [...apiModels.getExportedDeclarations().keys()];
145
+ const exported = [...apiModels.getExportedDeclarations().keys()].filter(
146
+ (e) => {
147
+ return !e.startsWith("_");
148
+ }
149
+ );
146
150
 
147
151
  if (exported.length > 0) {
148
152
  clientFile.addImportDeclaration({
@@ -92,6 +92,7 @@ import {
92
92
  } from "../utils/modelUtils.js";
93
93
  import { getModelNamespaceName } from "../utils/namespaceUtils.js";
94
94
  import {
95
+ extractPagedMetadataNested,
95
96
  getOperationGroupName,
96
97
  getOperationName,
97
98
  isBinaryPayload,
@@ -113,6 +114,7 @@ import {
113
114
  Response,
114
115
  Type as HrlcType
115
116
  } from "./modularCodeModel.js";
117
+ import { useContext } from "../contextManager.js";
116
118
 
117
119
  interface HttpServerParameter {
118
120
  type: "endpointPath";
@@ -308,6 +310,8 @@ function getType(
308
310
  type: EmitterType,
309
311
  options: { disableEffectiveModel?: boolean; usage?: UsageFlags } = {}
310
312
  ): any {
313
+ const modularMetatree = useContext("modularMetaTree");
314
+
311
315
  // don't cache simple type(string, int, etc) since decorators may change the result
312
316
  const enableCache = !isSimpleType(context.program, type);
313
317
  const effectiveModel =
@@ -322,6 +326,7 @@ function getType(
322
326
  }
323
327
  }
324
328
  let newValue: any;
329
+
325
330
  if (isEmptyAnonymousModel(type)) {
326
331
  // do not generate model for empty model, treat it as any
327
332
  newValue = { type: "any" };
@@ -332,6 +337,12 @@ function getType(
332
337
  newValue = applyEncoding(context.program, type, newValue);
333
338
  }
334
339
 
340
+ if (isTypespecType(type)) {
341
+ newValue.tcgcType = getClientType(context, type);
342
+ newValue.__raw = type;
343
+ modularMetatree.set(type, newValue);
344
+ }
345
+
335
346
  if (enableCache) {
336
347
  if (!options.disableEffectiveModel) {
337
348
  typesMap.set(effectiveModel, newValue);
@@ -352,7 +363,8 @@ function getType(
352
363
  }
353
364
  }
354
365
  } else {
355
- const key = JSON.stringify(newValue);
366
+ const { __raw, tcgcType, ...keyableValue } = newValue;
367
+ const key = JSON.stringify(keyableValue);
356
368
  const value = simpleTypesMap.get(key);
357
369
  if (value) {
358
370
  newValue = value;
@@ -373,12 +385,14 @@ function getType(
373
385
  target: type
374
386
  });
375
387
  }
376
- if (!["Credential", "CredentialTypeUnion"].includes(type.kind)) {
377
- newValue.tcgcType = getClientType(context, type as Type);
378
- }
388
+
379
389
  return newValue;
380
390
  }
381
391
 
392
+ function isTypespecType(type: EmitterType): type is Type {
393
+ return type.kind !== "Credential" && type.kind !== "CredentialTypeUnion";
394
+ }
395
+
382
396
  // To pass the yaml dump
383
397
  function getAddedOnVersion(p: Program, t: Type): string | undefined {
384
398
  return getAddedOnVersions(p, t)?.[0]?.value;
@@ -1022,9 +1036,11 @@ function emitModel(
1022
1036
  .join("") + "List";
1023
1037
  }
1024
1038
 
1039
+ const page = extractPagedMetadataNested(context.program, type);
1040
+ const isPaging = page && page.itemsSegments && page.itemsSegments.length > 0;
1025
1041
  return {
1026
1042
  type: "model",
1027
- name: modelName,
1043
+ name: `${isPaging ? "_" : ""}${modelName}`,
1028
1044
  description: getDocStr(context.program, type),
1029
1045
  parents: baseModel ? [baseModel] : [],
1030
1046
  discriminatedSubtypes: [],
@@ -1342,7 +1358,7 @@ function emitUnion(
1342
1358
  usage: UsageFlags
1343
1359
  ): Record<string, any> {
1344
1360
  let sdkType = getSdkUnion(context, type);
1345
- const isNull = sdkType.kind === "nullable";
1361
+ const isNull = false;
1346
1362
  if (sdkType.kind === "nullable") {
1347
1363
  sdkType = sdkType.type;
1348
1364
  }
@@ -0,0 +1,71 @@
1
+ import { Project } from "ts-morph";
2
+ import { useContext } from "../contextManager.js";
3
+ const serializeRecordFunction = {
4
+ symbol: "serializeRecord",
5
+ content: `
6
+ export function serializeRecord<
7
+ T extends string | number | boolean | Date | null,
8
+ R
9
+ >(item: Record<string, T>): Record<string, R>;
10
+ export function serializeRecord<T, R>(
11
+ item: Record<string, T>,
12
+ serializer: (item: T) => R
13
+ ): Record<string, R>;
14
+ export function serializeRecord<T, R>(
15
+ item: Record<string, T>,
16
+ serializer?: (item: T) => R
17
+ ): Record<string, R> {
18
+ return Object.keys(item).reduce(
19
+ (acc, key) => {
20
+ if (isSupportedRecordType(item[key])) {
21
+ acc[key] = item[key] as any;
22
+ } else if (serializer) {
23
+ const value = item[key];
24
+ if (value !== undefined) {
25
+ acc[key] = serializer(value);
26
+ }
27
+ } else {
28
+ console.warn(\`Don't know how to serialize \${item[key]}\`);
29
+ acc[key] = item[key] as any;
30
+ }
31
+ return acc;
32
+ },
33
+ {} as Record<string, R>
34
+ );
35
+ }`
36
+ };
37
+ const isRecordElementSupportedFunction = {
38
+ symbol: "isSupportedRecordType",
39
+ content: `
40
+ function isSupportedRecordType(t: any) {
41
+ return ["number", "string", "boolean", "null"].includes(typeof t) || t instanceof Date;
42
+ }
43
+ `
44
+ };
45
+
46
+ export function emitSerializerHelpersFile(
47
+ project: Project,
48
+ srcPath: string = "src"
49
+ ) {
50
+ const symbolMap = useContext("symbolMap");
51
+ const sourceFile = project.createSourceFile(
52
+ `${srcPath}/helpers/serializerHelpers.ts`,
53
+ "",
54
+ {
55
+ overwrite: true
56
+ }
57
+ );
58
+
59
+ if (!symbolMap.has(serializeRecordFunction.symbol)) {
60
+ symbolMap.set(serializeRecordFunction.symbol, sourceFile);
61
+ }
62
+
63
+ if (!symbolMap.has(isRecordElementSupportedFunction.symbol)) {
64
+ symbolMap.set(isRecordElementSupportedFunction.symbol, sourceFile);
65
+ }
66
+
67
+ sourceFile.addStatements([
68
+ serializeRecordFunction.content,
69
+ isRecordElementSupportedFunction.content
70
+ ]);
71
+ }
@@ -27,7 +27,7 @@ import {
27
27
  getSendPrivateFunction as experimentalGetSendPrivateFunction
28
28
  } from "./serialization/operationHelpers.js";
29
29
  import { SerializerMap } from "./serialization/util.js";
30
-
30
+ import { addImportBySymbol } from "../utils/importHelper.js";
31
31
  /**
32
32
  * This function creates a file under /api for each operation group.
33
33
  * If there is no operation group in the TypeSpec program, we create a single
@@ -195,7 +195,10 @@ export function buildOperationFiles(
195
195
  ]
196
196
  }
197
197
  ]);
198
+
198
199
  addImportsToFiles(codeModel.runtimeImports, operationGroupFile);
200
+ addImportBySymbol("serializeRecord", operationGroupFile);
201
+
199
202
  operationGroupFile.fixMissingImports();
200
203
  // have to fixUnusedIdentifiers after everything get generated.
201
204
  operationGroupFile.fixUnusedIdentifiers();
@@ -222,6 +225,9 @@ export function importModels(
222
225
  const models: string[] = [];
223
226
 
224
227
  for (const [name] of modelsFile?.getExportedDeclarations().entries() ?? []) {
228
+ if (name.startsWith("_")) {
229
+ continue;
230
+ }
225
231
  models.push(name);
226
232
  }
227
233
 
@@ -25,7 +25,7 @@ import { ModularCodeModel, Type } from "./modularCodeModel.js";
25
25
  */
26
26
  export function buildSerializeUtils(model: ModularCodeModel) {
27
27
  const serializeUtilFiles = [];
28
- for (const serializeType of ["serialize", "deserialize"]) {
28
+ for (const serializeType of ["deserialize"]) {
29
29
  const usageCondition =
30
30
  serializeType === "serialize" ? UsageFlags.Input : UsageFlags.Output;
31
31
  const specialUnions = model.types.filter(
@@ -107,7 +107,8 @@ export function buildSerializeUtils(model: ModularCodeModel) {
107
107
  });
108
108
  addImportsToFiles(model.runtimeImports, utilsFile, {
109
109
  rlcIndex: "../rest/index.js",
110
- modularModel: "../models/models.js"
110
+ modularModel: "../models/models.js",
111
+ coreUtil: "@azure/core-util"
111
112
  });
112
113
  serializeUtilFiles.push(utilsFile);
113
114
  }
@@ -517,11 +518,14 @@ function getTypeSerializeFunction(
517
518
  returnType: typeName
518
519
  };
519
520
  if (type.properties) {
520
- statements.push(
521
- `return {${getRequestModelMapping(type, "obj", runtimeImports).join(
522
- ", "
523
- )}};`
521
+ const { propertiesStr } = getRequestModelMapping(
522
+ type,
523
+ "obj",
524
+ runtimeImports
524
525
  );
526
+ statements.push(`return {
527
+ ${propertiesStr.join(", ")}
528
+ };`);
525
529
  } else {
526
530
  statements.push(`return {};`);
527
531
  }
@@ -556,12 +560,16 @@ function getTypeSerializeFunction(
556
560
  parameters: [{ name: "obj", type: type.elementType.name + "[]" }],
557
561
  returnType: `${typeName}[]`
558
562
  };
563
+ const { propertiesStr } = getRequestModelMapping(
564
+ type.elementType,
565
+ "item",
566
+ runtimeImports
567
+ );
568
+
559
569
  statements.push(
560
- `return (obj || []).map(item => { return {${getRequestModelMapping(
561
- type.elementType,
562
- "item",
563
- runtimeImports
564
- ).join(", ")}}})`
570
+ `return (obj || []).map(item => {
571
+ return { ${propertiesStr.join(", ")} }
572
+ })`
565
573
  );
566
574
  functionStatement.statements = statements.join("\n");
567
575
  if (!hasDuplicateFunction(sourceFile, functionStatement)) {
@@ -53,6 +53,16 @@ export function buildSubpathIndexFile(
53
53
  ) {
54
54
  return false;
55
55
  }
56
+
57
+ // skip exporting serializers for models
58
+ if (
59
+ subpath === "models" &&
60
+ ex.getKindName() === "FunctionDeclaration" &&
61
+ exDeclaration[0].endsWith("Serializer")
62
+ ) {
63
+ return false;
64
+ }
65
+
56
66
  return true;
57
67
  });
58
68
  })
@@ -1,4 +1,4 @@
1
- import { getImportSpecifier } from "@azure-tools/rlc-common";
1
+ import { addImportsToFiles, getImportSpecifier } from "@azure-tools/rlc-common";
2
2
  import * as path from "path";
3
3
  import {
4
4
  InterfaceDeclarationStructure,
@@ -10,11 +10,18 @@ import { buildOperationOptions } from "./buildOperations.js";
10
10
  import { getDocsFromDescription } from "./helpers/docsHelpers.js";
11
11
  import { getModularModelFilePath } from "./helpers/namingHelpers.js";
12
12
  import { getType } from "./helpers/typeHelpers.js";
13
- import { Client, ModularCodeModel, Type } from "./modularCodeModel.js";
13
+ import {
14
+ Client,
15
+ ModularCodeModel,
16
+ Type as ModularType
17
+ } from "./modularCodeModel.js";
18
+ import { buildModelSerializer } from "./serialization/buildSerializerFunction.js";
19
+ import { toCamelCase } from "../utils/casingUtils.js";
20
+ import { addImportBySymbol } from "../utils/importHelper.js";
14
21
 
15
22
  // ====== UTILITIES ======
16
23
 
17
- function isAzureCoreErrorSdkType(t: Type) {
24
+ function isAzureCoreErrorSdkType(t: ModularType) {
18
25
  return (
19
26
  t.name &&
20
27
  ["error", "errormodel", "innererror", "errorresponse"].includes(
@@ -24,7 +31,7 @@ function isAzureCoreErrorSdkType(t: Type) {
24
31
  );
25
32
  }
26
33
 
27
- function isAzureCoreLroSdkType(t: Type) {
34
+ function isAzureCoreLroSdkType(t: ModularType) {
28
35
  return (
29
36
  t.name &&
30
37
  ["operationstate"].includes(t.name.toLowerCase()) &&
@@ -32,11 +39,11 @@ function isAzureCoreLroSdkType(t: Type) {
32
39
  );
33
40
  }
34
41
 
35
- function isAnonymousModel(t: Type) {
42
+ function isAnonymousModel(t: ModularType) {
36
43
  return t.type === "model" && t.name === "";
37
44
  }
38
45
 
39
- export function isModelWithAdditionalProperties(t: Type) {
46
+ export function isModelWithAdditionalProperties(t: ModularType) {
40
47
  return t.type === "dict" && t.name !== "Record";
41
48
  }
42
49
 
@@ -54,7 +61,7 @@ function getCoreLroType(name: string, coreLroTypes: Set<string>) {
54
61
 
55
62
  // ====== TYPE EXTRACTION ======
56
63
 
57
- function extractModels(codeModel: ModularCodeModel): Type[] {
64
+ function extractModels(codeModel: ModularCodeModel): ModularType[] {
58
65
  const models = codeModel.types.filter(
59
66
  (t) =>
60
67
  ((t.type === "model" || t.type === "enum") &&
@@ -65,7 +72,7 @@ function extractModels(codeModel: ModularCodeModel): Type[] {
65
72
  );
66
73
 
67
74
  for (const model of codeModel.types) {
68
- if (model.type === "combined" && model.nullable) {
75
+ if (model.type === "combined") {
69
76
  for (const unionModel of model.types ?? []) {
70
77
  if (unionModel.type === "model") {
71
78
  models.push(unionModel);
@@ -81,7 +88,7 @@ function extractModels(codeModel: ModularCodeModel): Type[] {
81
88
  * 1. alias from polymorphic base model, where we need to use typescript union to combine all the sub models
82
89
  * 2. alias from unions, where we also need to use typescript union to combine all the union variants
83
90
  */
84
- export function extractAliases(codeModel: ModularCodeModel): Type[] {
91
+ export function extractAliases(codeModel: ModularCodeModel): ModularType[] {
85
92
  const models = codeModel.types.filter(
86
93
  (t) =>
87
94
  ((t.type === "model" || t.type === "combined") &&
@@ -93,7 +100,7 @@ export function extractAliases(codeModel: ModularCodeModel): Type[] {
93
100
  }
94
101
  // ====== TYPE BUILDERS ======
95
102
  function buildEnumModel(
96
- model: Type
103
+ model: ModularType
97
104
  ): OptionalKind<TypeAliasDeclarationStructure> {
98
105
  const valueType = model.valueType?.type === "string" ? "string" : "number";
99
106
  return {
@@ -122,7 +129,7 @@ type InterfaceStructure = OptionalKind<InterfaceDeclarationStructure> & {
122
129
  };
123
130
 
124
131
  export function buildModelInterface(
125
- model: Type,
132
+ model: ModularType,
126
133
  cache: { coreClientTypes: Set<string>; coreLroTypes: Set<string> }
127
134
  ): InterfaceStructure {
128
135
  const modelProperties = model.properties ?? [];
@@ -178,7 +185,6 @@ export function buildModels(
178
185
  const modelsFile = codeModel.project.createSourceFile(
179
186
  getModularModelFilePath(codeModel, subClient)
180
187
  );
181
-
182
188
  for (const model of models) {
183
189
  if (model.type === "enum") {
184
190
  if (modelsFile.getTypeAlias(model.name!)) {
@@ -214,10 +220,38 @@ export function buildModels(
214
220
  codeModel.modularOptions.compatibilityMode
215
221
  );
216
222
  }
217
- modelsFile.addInterface(modelInterface);
223
+
224
+ if (!modelsFile.getInterface(modelInterface.name)) {
225
+ modelsFile.addInterface(modelInterface);
226
+ }
227
+
228
+ // Generate a serializer function next to each model
229
+ const serializerFunction = buildModelSerializer(
230
+ model,
231
+ codeModel.runtimeImports
232
+ );
233
+
234
+ if (
235
+ serializerFunction &&
236
+ !modelsFile.getFunction(toCamelCase(modelInterface.name + "Serializer"))
237
+ ) {
238
+ modelsFile.addStatements(serializerFunction);
239
+ }
240
+ addImportBySymbol("serializeRecord", modelsFile);
241
+ modelsFile.fixUnusedIdentifiers();
218
242
  }
219
243
  }
220
244
 
245
+ const projectRootFromModels = codeModel.clients.length > 1 ? "../.." : "../";
246
+ addImportsToFiles(codeModel.runtimeImports, modelsFile, {
247
+ rlcIndex: path.posix.join(projectRootFromModels, "rest", "index.js"),
248
+ serializerHelpers: path.posix.join(
249
+ projectRootFromModels,
250
+ "helpers",
251
+ "serializerHelpers.js"
252
+ )
253
+ });
254
+
221
255
  if (coreClientTypes.size > 0) {
222
256
  modelsFile.addImportDeclarations([
223
257
  {
@@ -248,12 +282,22 @@ export function buildModels(
248
282
 
249
283
  aliases.forEach((alias) => {
250
284
  modelsFile.addTypeAlias(buildModelTypeAlias(alias));
285
+ if (!models.includes(alias)) {
286
+ // Generate a serializer function next to each model
287
+ const serializerFunction = buildModelSerializer(
288
+ alias,
289
+ codeModel.runtimeImports
290
+ );
291
+ if (serializerFunction) {
292
+ modelsFile.addStatements(serializerFunction);
293
+ }
294
+ }
251
295
  });
252
296
  return modelsFile;
253
297
  }
254
298
 
255
299
  function addExtendedDictInfo(
256
- model: Type,
300
+ model: ModularType,
257
301
  modelInterface: InterfaceStructure,
258
302
  compatibilityMode: boolean = false
259
303
  ) {
@@ -282,7 +326,7 @@ function addExtendedDictInfo(
282
326
  }
283
327
  }
284
328
 
285
- export function buildModelTypeAlias(model: Type) {
329
+ export function buildModelTypeAlias(model: ModularType) {
286
330
  return {
287
331
  name: model.name!,
288
332
  isExported: true,
@@ -321,7 +365,13 @@ export function buildModelsOptions(
321
365
  }
322
366
  ]);
323
367
 
324
- modelOptionsFile.fixMissingImports();
368
+ modelOptionsFile.fixMissingImports(
369
+ {},
370
+ {
371
+ importModuleSpecifierPreference: "shortest",
372
+ importModuleSpecifierEnding: "js"
373
+ }
374
+ );
325
375
  modelOptionsFile
326
376
  .getImportDeclarations()
327
377
  .filter((id) => {