@acrool/rtk-query-codegen-openapi 0.0.2 → 0.0.7

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/src/generate.ts CHANGED
@@ -38,8 +38,8 @@ function defaultIsDataResponse(code: string, includeDefault: boolean) {
38
38
  return !Number.isNaN(parsedCode) && parsedCode >= 200 && parsedCode < 300;
39
39
  }
40
40
 
41
- function getOperationName({ verb, path, operation }: Pick<OperationDefinition, 'verb' | 'path' | 'operation'>) {
42
- return _getOperationName(verb, path, operation.operationId);
41
+ function getOperationName({ verb, path }: Pick<OperationDefinition, 'verb' | 'path'>) {
42
+ return _getOperationName(verb, path, undefined);
43
43
  }
44
44
 
45
45
  function getTags({ verb, pathItem }: Pick<OperationDefinition, 'verb' | 'pathItem'>): string[] {
@@ -128,49 +128,176 @@ export async function generateApi(
128
128
  mergeReadWriteOnly,
129
129
  });
130
130
 
131
- // 如果提供了 sharedTypesFile,則將 components 輸出到該文件
131
+ const schemeTypeNames = new Set<string>();
132
+
133
+ function addSchemeTypeName(name: string) {
134
+ schemeTypeNames.add(name);
135
+ schemeTypeNames.add(camelCase(name));
136
+ schemeTypeNames.add(capitalize(camelCase(name)));
137
+ }
138
+
132
139
  if (sharedTypesFile) {
140
+ const resultFile = ts.createSourceFile(
141
+ 'sharedTypes.ts',
142
+ '',
143
+ ts.ScriptTarget.Latest,
144
+ /*setParentNodes*/ false,
145
+ ts.ScriptKind.TS
146
+ );
147
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
148
+
149
+ const allTypeDefinitions: ts.Statement[] = [];
150
+ const definedTypeNames = new Set<string>();
151
+
133
152
  const components = v3Doc.components;
134
153
  if (components) {
135
- const resultFile = ts.createSourceFile(
136
- 'sharedTypes.ts',
137
- '',
138
- ts.ScriptTarget.Latest,
139
- /*setParentNodes*/ false,
140
- ts.ScriptKind.TS
141
- );
142
- const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
143
-
144
- // 將 components 轉換為 TypeScript 類型定義
145
- const typeDefinitions = Object.entries(components).flatMap(([_, componentDefs]) => {
146
- return Object.entries(componentDefs as Record<string, unknown>).map(([name, def]) => {
147
- const typeNode = apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject);
154
+ // 只處理 schemas,其他 component 類型暫時不處理
155
+ if (components.schemas) {
156
+ const typeEntries = Object.entries(components.schemas).map(([name, def]) => {
157
+ addSchemeTypeName(name);
158
+ const typeName = capitalize(camelCase(name));
159
+ definedTypeNames.add(typeName);
160
+ const typeNode = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject));
148
161
  return factory.createTypeAliasDeclaration(
149
162
  [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
150
- factory.createIdentifier(name),
163
+ factory.createIdentifier(typeName),
151
164
  undefined,
152
165
  typeNode
153
166
  );
154
167
  });
168
+
169
+ allTypeDefinitions.push(
170
+ factory.createModuleDeclaration(
171
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
172
+ factory.createIdentifier('Scheme'),
173
+ factory.createModuleBlock(typeEntries),
174
+ ts.NodeFlags.Namespace
175
+ )
176
+ );
177
+ }
178
+ }
179
+
180
+ const enumEntries = [
181
+ ...apiGen.enumAliases.filter((e) => ts.isEnumDeclaration(e)),
182
+ ...apiGen.enumAliases.filter((e) => ts.isTypeAliasDeclaration(e)),
183
+ ].map((enumDecl) => {
184
+ if (ts.isEnumDeclaration(enumDecl)) {
185
+ return factory.createEnumDeclaration(
186
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
187
+ enumDecl.name,
188
+ enumDecl.members
189
+ );
190
+ } else if (ts.isTypeAliasDeclaration(enumDecl)) {
191
+ return factory.createTypeAliasDeclaration(
192
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
193
+ enumDecl.name,
194
+ enumDecl.typeParameters,
195
+ enumDecl.type
196
+ );
197
+ }
198
+ return enumDecl;
199
+ });
200
+
201
+ const unionTypeEnums = apiGen.aliases
202
+ .filter((alias) => {
203
+ if (ts.isTypeAliasDeclaration(alias) && alias.type) {
204
+ return ts.isUnionTypeNode(alias.type);
205
+ }
206
+ return false;
207
+ })
208
+ .map((alias) => {
209
+ if (ts.isTypeAliasDeclaration(alias)) {
210
+ return factory.createTypeAliasDeclaration(
211
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
212
+ alias.name,
213
+ alias.typeParameters,
214
+ alias.type
215
+ );
216
+ }
217
+ return alias;
155
218
  });
156
219
 
157
- const output = printer.printNode(
158
- ts.EmitHint.Unspecified,
159
- factory.createSourceFile(
160
- typeDefinitions,
161
- factory.createToken(ts.SyntaxKind.EndOfFileToken),
162
- ts.NodeFlags.None
163
- ),
164
- resultFile
220
+ const allEnumEntries = [...enumEntries, ...unionTypeEnums];
221
+
222
+ if (allEnumEntries.length > 0) {
223
+ allTypeDefinitions.push(
224
+ factory.createModuleDeclaration(
225
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
226
+ factory.createIdentifier('Enum'),
227
+ factory.createModuleBlock(allEnumEntries),
228
+ ts.NodeFlags.Namespace
229
+ )
165
230
  );
231
+ }
232
+
233
+ if (apiGen.aliases.length > 0) {
234
+ const aliasEntries = apiGen.aliases
235
+ .filter((alias) => {
236
+ if (ts.isTypeAliasDeclaration(alias)) {
237
+ const isDefinedInComponents = definedTypeNames.has(alias.name.text);
238
+ const isUnionTypeEnum = ts.isUnionTypeNode(alias.type);
239
+ return !isDefinedInComponents && !isUnionTypeEnum;
240
+ }
241
+ return false;
242
+ })
243
+ .map((alias) => {
244
+ if (ts.isTypeAliasDeclaration(alias)) {
245
+ return factory.createTypeAliasDeclaration(
246
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
247
+ alias.name,
248
+ alias.typeParameters,
249
+ alias.type
250
+ );
251
+ }
252
+ return alias;
253
+ });
254
+
255
+ if (aliasEntries.length > 0) {
256
+ const existingSchemeIndex = allTypeDefinitions.findIndex(
257
+ (def) => ts.isModuleDeclaration(def) && ts.isIdentifier(def.name) && def.name.text === 'Scheme'
258
+ );
166
259
 
167
- // 寫入文件
168
- const fs = await import('node:fs/promises');
169
- await fs.writeFile(sharedTypesFile, output, 'utf-8');
260
+ if (existingSchemeIndex >= 0) {
261
+ const existingScheme = allTypeDefinitions[existingSchemeIndex] as ts.ModuleDeclaration;
262
+ const mergedMembers = [...(existingScheme.body as ts.ModuleBlock).statements, ...aliasEntries];
263
+ allTypeDefinitions[existingSchemeIndex] = factory.createModuleDeclaration(
264
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
265
+ factory.createIdentifier('Scheme'),
266
+ factory.createModuleBlock(mergedMembers),
267
+ ts.NodeFlags.Namespace
268
+ );
269
+ } else {
270
+ allTypeDefinitions.push(
271
+ factory.createModuleDeclaration(
272
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
273
+ factory.createIdentifier('Scheme'),
274
+ factory.createModuleBlock(aliasEntries),
275
+ ts.NodeFlags.Namespace
276
+ )
277
+ );
278
+ }
279
+ }
170
280
  }
281
+
282
+ const fs = await import('node:fs/promises');
283
+ const path = await import('node:path');
284
+
285
+ const sharedTypesDir = path.dirname(sharedTypesFile);
286
+ await fs.mkdir(sharedTypesDir, { recursive: true });
287
+
288
+ const output = printer.printNode(
289
+ ts.EmitHint.Unspecified,
290
+ factory.createSourceFile(
291
+ allTypeDefinitions,
292
+ factory.createToken(ts.SyntaxKind.EndOfFileToken),
293
+ ts.NodeFlags.None
294
+ ),
295
+ resultFile
296
+ );
297
+
298
+ await fs.writeFile(sharedTypesFile, output, 'utf-8');
171
299
  }
172
300
 
173
- // temporary workaround for https://github.com/oazapfts/oazapfts/issues/491
174
301
  if (apiGen.spec.components?.schemas) {
175
302
  apiGen.preprocessComponents(apiGen.spec.components.schemas);
176
303
  }
@@ -187,6 +314,7 @@ export async function generateApi(
187
314
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
188
315
 
189
316
  const interfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
317
+
190
318
  function registerInterface(declaration: ts.InterfaceDeclaration | ts.TypeAliasDeclaration) {
191
319
  const name = declaration.name.escapedText.toString();
192
320
  if (name in interfaces) {
@@ -203,17 +331,20 @@ export async function generateApi(
203
331
  apiFile = apiFile.replace(/\\/g, '/');
204
332
  if (!apiFile.startsWith('.')) apiFile = `./${apiFile}`;
205
333
  }
206
- // 處理 sharedTypesFile 的路徑
207
- if (sharedTypesFile && sharedTypesFile.startsWith('.')) {
208
- sharedTypesFile = path.relative(path.dirname(outputFile), sharedTypesFile);
209
- sharedTypesFile = sharedTypesFile.replace(/\\/g, '/');
210
- if (!sharedTypesFile.startsWith('.')) sharedTypesFile = `./${sharedTypesFile}`;
211
- }
212
334
  }
213
335
  apiFile = apiFile.replace(/\.[jt]sx?$/, '');
214
- if (sharedTypesFile) {
215
- sharedTypesFile = sharedTypesFile.replace(/\.[jt]sx?$/, '');
216
- }
336
+
337
+ const sharedTypesImportPath =
338
+ sharedTypesFile && outputFile
339
+ ? (() => {
340
+ let rel = path
341
+ .relative(path.dirname(outputFile), sharedTypesFile)
342
+ .replace(/\\/g, '/')
343
+ .replace(/\.[jt]sx?$/, '');
344
+ if (!rel.startsWith('.')) rel = './' + rel;
345
+ return rel;
346
+ })()
347
+ : './shared-types';
217
348
 
218
349
  return printer.printNode(
219
350
  ts.EmitHint.Unspecified,
@@ -221,18 +352,14 @@ export async function generateApi(
221
352
  [
222
353
  generateImportNode(apiFile, { [apiImport]: 'api' }),
223
354
  generateImportNode('@acrool/react-fetcher', { IRestFulEndpointsQueryReturn: 'IRestFulEndpointsQueryReturn' }),
224
- ...(sharedTypesFile ? [
225
- factory.createImportDeclaration(
226
- undefined,
227
- factory.createImportClause(
228
- false,
229
- undefined,
230
- factory.createNamespaceImport(factory.createIdentifier('SharedTypes'))
231
- ),
232
- factory.createStringLiteral(sharedTypesFile),
233
- undefined
234
- )
235
- ] : []),
355
+ ...(sharedTypesFile
356
+ ? [
357
+ generateImportNode(sharedTypesImportPath, {
358
+ Scheme: 'Scheme',
359
+ ...(useEnumType ? { Enum: 'Enum' } : {}),
360
+ }),
361
+ ]
362
+ : []),
236
363
  ...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
237
364
  generateCreateApiCall({
238
365
  tag,
@@ -241,16 +368,13 @@ export async function generateApi(
241
368
  generateEndpoint({
242
369
  operationDefinition,
243
370
  overrides: getOverrides(operationDefinition, endpointOverrides),
371
+ sharedTypesFile: !!sharedTypesFile,
244
372
  })
245
373
  ),
246
374
  true
247
375
  ),
248
376
  }),
249
- factory.createExportAssignment(
250
- undefined,
251
- undefined,
252
- factory.createIdentifier(generatedApiName)
253
- ),
377
+ factory.createExportAssignment(undefined, undefined, factory.createIdentifier(generatedApiName)),
254
378
  ...Object.values(interfaces),
255
379
  ...(sharedTypesFile ? [] : [...apiGen.aliases, ...apiGen.enumAliases]),
256
380
  ...(hooks
@@ -285,9 +409,11 @@ export async function generateApi(
285
409
  function generateEndpoint({
286
410
  operationDefinition,
287
411
  overrides,
412
+ sharedTypesFile,
288
413
  }: {
289
414
  operationDefinition: OperationDefinition;
290
415
  overrides?: EndpointOverrides;
416
+ sharedTypesFile: boolean;
291
417
  }) {
292
418
  const {
293
419
  verb,
@@ -296,73 +422,37 @@ export async function generateApi(
296
422
  operation,
297
423
  operation: { responses, requestBody },
298
424
  } = operationDefinition;
299
- const operationName = getOperationName({ verb, path, operation });
425
+ const operationName = getOperationName({ verb, path });
300
426
  const tags = tag ? getTags({ verb, pathItem }) : [];
301
427
  const isQuery = testIsQuery(verb, overrides);
302
428
 
303
429
  const returnsJson = apiGen.getResponseType(responses) === 'json';
304
430
  let ResponseType: ts.TypeNode = factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
305
-
306
- function replaceReferences(schema: any): ts.TypeNode {
307
- if (!schema) return factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
308
-
309
- const refName = getReferenceName(schema);
310
- if (refName && sharedTypesFile) {
311
- return factory.createTypeReferenceNode(
312
- factory.createQualifiedName(
313
- factory.createIdentifier('SharedTypes'),
314
- factory.createIdentifier(refName)
315
- ),
316
- undefined
317
- );
318
- }
319
-
320
- if (schema.type === 'object' && schema.properties) {
321
- const members = Object.entries(schema.properties).map(([key, value]: [string, any]) => {
322
- return factory.createPropertySignature(
323
- undefined,
324
- factory.createIdentifier(key),
325
- schema.required?.includes(key) ? undefined : factory.createToken(ts.SyntaxKind.QuestionToken),
326
- replaceReferences(value)
327
- );
328
- });
329
- return factory.createTypeLiteralNode(members);
330
- }
331
-
332
- if (schema.type === 'array' && schema.items) {
333
- return factory.createArrayTypeNode(replaceReferences(schema.items));
334
- }
335
-
336
- return apiGen.getTypeFromSchema(schema);
337
- }
338
-
339
431
  if (returnsJson) {
340
432
  const returnTypes = Object.entries(responses || {})
341
433
  .map(
342
- ([code, response]) => {
343
- const resolvedResponse = apiGen.resolve(response);
344
- if (!resolvedResponse.content?.['application/json']?.schema) {
345
- return [code, resolvedResponse, factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)] as const;
346
- }
347
-
348
- const schema = resolvedResponse.content['application/json'].schema;
349
- const type = replaceReferences(schema);
350
-
351
- return [code, resolvedResponse, type] as const;
352
- }
434
+ ([code, response]) =>
435
+ [
436
+ code,
437
+ apiGen.resolve(response),
438
+ wrapWithSchemeIfComponent(
439
+ apiGen.getTypeFromResponse(response, 'readOnly') ||
440
+ factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
441
+ ),
442
+ ] as const
353
443
  )
354
444
  .filter(([status, response]) =>
355
445
  isDataResponse(status, includeDefault, apiGen.resolve(response), responses || {})
356
446
  )
357
447
  .filter(([_1, _2, type]) => type !== keywordType.void)
358
- .map(([code, response, type]) =>
359
- ts.addSyntheticLeadingComment(
360
- { ...type },
448
+ .map(([code, response, type]) => {
449
+ return ts.addSyntheticLeadingComment(
450
+ type,
361
451
  ts.SyntaxKind.MultiLineCommentTrivia,
362
452
  `* status ${code} ${response.description} `,
363
453
  false
364
- )
365
- );
454
+ );
455
+ });
366
456
  if (returnTypes.length > 0) {
367
457
  ResponseType = factory.createUnionTypeNode(returnTypes);
368
458
  }
@@ -386,34 +476,45 @@ export async function generateApi(
386
476
 
387
477
  const parameters = supportDeepObjects([...pathItemParameters, ...operationParameters])
388
478
  .filter(argumentMatches(overrides?.parameterFilter))
389
- .filter(param => param.in !== 'header');
479
+ .filter((param) => param.in !== 'header');
390
480
 
391
481
  const allNames = parameters.map((p) => p.name);
392
482
  const queryArg: QueryArgDefinitions = {};
483
+
393
484
  function generateName(name: string, potentialPrefix: string) {
394
485
  const isPureSnakeCase = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
395
- // prefix with `query`, `path` or `body` if there are multiple paramters with the same name
396
486
  const hasNamingConflict = allNames.filter((n) => n === name).length > 1;
397
487
  if (hasNamingConflict) {
398
488
  name = `${potentialPrefix}_${name}`;
399
489
  }
400
- // convert to camelCase if the name is pure snake_case and there are no naming conflicts
401
490
  const camelCaseName = camelCase(name);
402
491
  if (isPureSnakeCase && !allNames.includes(camelCaseName)) {
403
492
  name = camelCaseName;
404
493
  }
405
- // if there are still any naming conflicts, prepend with underscore
406
494
  while (name in queryArg) {
407
495
  name = `_${name}`;
408
496
  }
409
497
  return name;
410
498
  }
411
499
 
500
+ for (const param of parameters) {
501
+ const name = generateName(param.name, param.in);
502
+ queryArg[name] = {
503
+ origin: 'param',
504
+ name,
505
+ originalName: param.name,
506
+ type: wrapWithSchemeIfComponent(
507
+ apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly')
508
+ ),
509
+ required: param.required,
510
+ param,
511
+ };
512
+ }
513
+
412
514
  if (requestBody) {
413
515
  const body = apiGen.resolve(requestBody);
414
516
  const schema = apiGen.getSchemaFromContent(body.content);
415
- const type = replaceReferences(schema);
416
-
517
+ const type = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema));
417
518
  const schemaName = camelCase(
418
519
  (type as any).name ||
419
520
  getReferenceName(schema) ||
@@ -426,27 +527,12 @@ export async function generateApi(
426
527
  origin: 'body',
427
528
  name,
428
529
  originalName: schemaName,
429
- type,
530
+ type: wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema, undefined, 'writeOnly')),
430
531
  required: true,
431
532
  body,
432
533
  };
433
534
  }
434
535
 
435
- for (const param of parameters) {
436
- const name = generateName(param.name, param.in);
437
- const paramSchema = isReference(param) ? param : param.schema;
438
- const type = replaceReferences(paramSchema);
439
-
440
- queryArg[name] = {
441
- origin: 'param',
442
- name,
443
- originalName: param.name,
444
- type,
445
- required: param.required,
446
- param,
447
- };
448
- }
449
-
450
536
  const propertyName = (name: string | ts.PropertyName): ts.PropertyName => {
451
537
  if (typeof name === 'string') {
452
538
  return isValidIdentifier(name) ? factory.createIdentifier(name) : factory.createStringLiteral(name);
@@ -498,10 +584,7 @@ export async function generateApi(
498
584
  operationName: operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName,
499
585
  type: isQuery ? 'query' : 'mutation',
500
586
  Response: ResponseTypeName,
501
- QueryArg: factory.createTypeReferenceNode(
502
- factory.createIdentifier('IRestFulEndpointsQueryReturn'),
503
- [QueryArg]
504
- ),
587
+ QueryArg: factory.createTypeReferenceNode(factory.createIdentifier('IRestFulEndpointsQueryReturn'), [QueryArg]),
505
588
  queryFn: generateQueryFn({
506
589
  operationDefinition,
507
590
  queryArg,
@@ -532,13 +615,24 @@ export async function generateApi(
532
615
  encodePathParams: boolean;
533
616
  encodeQueryParams: boolean;
534
617
  }) {
535
- const { path, verb } = operationDefinition;
618
+ const { path, verb, operation } = operationDefinition;
536
619
 
537
620
  const bodyParameter = Object.values(queryArg).find((def) => def.origin === 'body');
538
621
 
539
622
  const rootObject = factory.createIdentifier('queryArg');
540
623
  const variablesObject = factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables'));
541
624
 
625
+ // 提取 content type 的輔助函數
626
+ function getContentType(): string | undefined {
627
+ if (operation.requestBody) {
628
+ const requestBody = apiGen.resolve(operation.requestBody);
629
+ const contentTypes = Object.keys(requestBody.content || {});
630
+ // 直接返回第一個可用的 content type
631
+ return contentTypes[0];
632
+ }
633
+ return undefined;
634
+ }
635
+
542
636
  function pickParams(paramIn: string) {
543
637
  return Object.values(queryArg).filter((def) => def.origin === 'param' && def.param.in === paramIn);
544
638
  }
@@ -547,8 +641,8 @@ export async function generateApi(
547
641
  if (parameters.length === 0) return undefined;
548
642
 
549
643
  const properties = parameters.map((param) => {
550
- const value = isFlatArg
551
- ? variablesObject
644
+ const value = isFlatArg
645
+ ? variablesObject
552
646
  : factory.createPropertyAccessExpression(variablesObject, factory.createIdentifier(param.name));
553
647
 
554
648
  const encodedValue =
@@ -573,6 +667,8 @@ export async function generateApi(
573
667
  );
574
668
  }
575
669
 
670
+ const contentType = getContentType();
671
+
576
672
  return factory.createArrowFunction(
577
673
  undefined,
578
674
  undefined,
@@ -592,13 +688,22 @@ export async function generateApi(
592
688
  factory.createIdentifier('method'),
593
689
  factory.createStringLiteral(verb.toUpperCase())
594
690
  ),
691
+ contentType
692
+ ? factory.createPropertyAssignment(
693
+ factory.createIdentifier('contentType'),
694
+ factory.createStringLiteral(contentType)
695
+ )
696
+ : undefined,
595
697
  bodyParameter === undefined
596
698
  ? undefined
597
699
  : factory.createPropertyAssignment(
598
700
  factory.createIdentifier('body'),
599
701
  isFlatArg
600
702
  ? variablesObject
601
- : factory.createPropertyAccessExpression(variablesObject, factory.createIdentifier(bodyParameter.name))
703
+ : factory.createPropertyAccessExpression(
704
+ variablesObject,
705
+ factory.createIdentifier(bodyParameter.name)
706
+ )
602
707
  ),
603
708
  createObjectLiteralProperty(pickParams('cookie'), 'cookies'),
604
709
  createObjectLiteralProperty(pickParams('query'), 'params'),
@@ -617,14 +722,132 @@ export async function generateApi(
617
722
  );
618
723
  }
619
724
 
620
- // eslint-disable-next-line no-empty-pattern
621
725
  function generateQueryEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
622
- return {}; /* TODO needs implementation - skip for now */
726
+ return {};
623
727
  }
624
728
 
625
- // eslint-disable-next-line no-empty-pattern
626
729
  function generateMutationEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
627
- return {}; /* TODO needs implementation - skip for now */
730
+ return {};
731
+ }
732
+
733
+ function wrapWithSchemeIfComponent(typeNode: ts.TypeNode): ts.TypeNode {
734
+ if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
735
+ const typeName = typeNode.typeName.text;
736
+
737
+ // 檢查是否為 enum 類型(包括在 enumAliases 和 aliases 中的)
738
+ const isEnumType =
739
+ useEnumType &&
740
+ (apiGen.enumAliases.some((enumDecl) => {
741
+ if (ts.isEnumDeclaration(enumDecl) || ts.isTypeAliasDeclaration(enumDecl)) {
742
+ return enumDecl.name.text === typeName;
743
+ }
744
+ return false;
745
+ }) ||
746
+ apiGen.aliases.some((alias) => {
747
+ if (ts.isTypeAliasDeclaration(alias) && alias.type) {
748
+ // 檢查是否為 union type 的 enum
749
+ if (ts.isUnionTypeNode(alias.type)) {
750
+ return alias.name.text === typeName;
751
+ }
752
+ }
753
+ return false;
754
+ }));
755
+
756
+ if (isEnumType) {
757
+ return factory.createTypeReferenceNode(
758
+ factory.createQualifiedName(factory.createIdentifier('Enum'), typeNode.typeName),
759
+ typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
760
+ );
761
+ }
762
+
763
+ if (schemeTypeNames.has(typeName)) {
764
+ return factory.createTypeReferenceNode(
765
+ factory.createQualifiedName(factory.createIdentifier('Scheme'), typeNode.typeName),
766
+ typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
767
+ );
768
+ }
769
+ if (typeNode.typeArguments) {
770
+ return factory.createTypeReferenceNode(
771
+ typeNode.typeName,
772
+ typeNode.typeArguments.map(wrapWithSchemeIfComponent)
773
+ );
774
+ }
775
+ }
776
+ if (ts.isArrayTypeNode(typeNode)) {
777
+ return factory.createArrayTypeNode(wrapWithSchemeIfComponent(typeNode.elementType));
778
+ }
779
+ if (ts.isUnionTypeNode(typeNode)) {
780
+ // 檢查是否為 enum 的 union type
781
+ const unionTypes = typeNode.types;
782
+ if (
783
+ unionTypes.length > 0 &&
784
+ unionTypes.every(
785
+ (type) =>
786
+ ts.isLiteralTypeNode(type) && (ts.isStringLiteral(type.literal) || ts.isNumericLiteral(type.literal))
787
+ )
788
+ ) {
789
+ // 這是一個 enum 的 union type,我們需要找到對應的 enum 類型
790
+ const enumValues = unionTypes
791
+ .map((type) => {
792
+ if (ts.isLiteralTypeNode(type)) {
793
+ if (ts.isStringLiteral(type.literal)) {
794
+ return type.literal.text;
795
+ } else if (ts.isNumericLiteral(type.literal)) {
796
+ return type.literal.text;
797
+ }
798
+ }
799
+ return null;
800
+ })
801
+ .filter(Boolean);
802
+
803
+ // 查找對應的 enum 類型
804
+ const matchingEnum = apiGen.aliases.find((alias) => {
805
+ if (ts.isTypeAliasDeclaration(alias) && ts.isUnionTypeNode(alias.type)) {
806
+ const aliasValues = alias.type.types
807
+ .map((type) => {
808
+ if (ts.isLiteralTypeNode(type)) {
809
+ if (ts.isStringLiteral(type.literal)) {
810
+ return type.literal.text;
811
+ } else if (ts.isNumericLiteral(type.literal)) {
812
+ return type.literal.text;
813
+ }
814
+ }
815
+ return null;
816
+ })
817
+ .filter(Boolean);
818
+
819
+ return aliasValues.length === enumValues.length && aliasValues.every((val) => enumValues.includes(val));
820
+ }
821
+ return false;
822
+ });
823
+
824
+ // 對於所有的 enum 類型,直接使用字串型別,不轉換為 Enum
825
+ // 這樣可以避免自動命名造成的變更問題
826
+ if (matchingEnum && ts.isTypeAliasDeclaration(matchingEnum)) {
827
+ // 直接返回原始的 union type,不轉換為 Enum
828
+ return typeNode;
829
+ }
830
+ }
831
+
832
+ return factory.createUnionTypeNode(typeNode.types.map(wrapWithSchemeIfComponent));
833
+ }
834
+ if (ts.isTypeLiteralNode(typeNode)) {
835
+ return factory.createTypeLiteralNode(
836
+ typeNode.members.map((member) => {
837
+ if (ts.isPropertySignature(member) && member.type) {
838
+ return factory.updatePropertySignature(
839
+ member,
840
+ member.modifiers,
841
+ member.name,
842
+ member.questionToken,
843
+ wrapWithSchemeIfComponent(member.type)
844
+ );
845
+ }
846
+ return member;
847
+ })
848
+ );
849
+ }
850
+ return typeNode;
628
851
  }
629
852
  }
630
853
 
@@ -656,8 +879,8 @@ function generatePathExpression(
656
879
  ? factory.createTemplateExpression(
657
880
  factory.createTemplateHead(head),
658
881
  expressions.map(([prop, literal], index) => {
659
- const value = isFlatArg
660
- ? rootObject
882
+ const value = isFlatArg
883
+ ? rootObject
661
884
  : factory.createPropertyAccessExpression(rootObject, factory.createIdentifier(prop));
662
885
  const encodedValue = encodePathParams
663
886
  ? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [