@acrool/rtk-query-codegen-openapi 0.0.2-test.0 → 0.0.2-test.2

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
@@ -30,17 +30,7 @@ import { factory } from './utils/factory';
30
30
  const generatedApiName = 'injectedRtkApi';
31
31
  const v3DocCache: Record<string, OpenAPIV3.Document> = {};
32
32
 
33
- // Add IUseFetcherArgs import
34
- const useFetcherImport = generateImportNode('@acrool/react-fetcher', {
35
- IUseFetcherArgs: 'IUseFetcherArgs',
36
- });
37
-
38
- function defaultIsDataResponse(
39
- code: string,
40
- includeDefault: boolean,
41
- response: OpenAPIV3.ResponseObject,
42
- allResponses: OpenAPIV3.ResponsesObject
43
- ) {
33
+ function defaultIsDataResponse(code: string, includeDefault: boolean) {
44
34
  if (includeDefault && code === 'default') {
45
35
  return true;
46
36
  }
@@ -53,8 +43,7 @@ function getOperationName({ verb, path, operation }: Pick<OperationDefinition, '
53
43
  }
54
44
 
55
45
  function getTags({ verb, pathItem }: Pick<OperationDefinition, 'verb' | 'pathItem'>): string[] {
56
- const tags = verb && pathItem[verb]?.tags ? pathItem[verb].tags : [];
57
- return tags.map((tag) => tag.toString());
46
+ return verb ? pathItem[verb]?.tags || [] : [];
58
47
  }
59
48
 
60
49
  function patternMatches(pattern?: TextMatcher) {
@@ -235,127 +224,6 @@ export async function generateApi(
235
224
  return [...allTagTypes];
236
225
  }
237
226
 
238
- function generateQueryArgType(operationDefinition: OperationDefinition): ts.TypeNode {
239
- const {
240
- operation: { parameters = [], requestBody },
241
- } = operationDefinition;
242
-
243
- const properties: ts.PropertySignature[] = [];
244
-
245
- // Add body property if needed
246
- if (requestBody && !isReference(requestBody)) {
247
- const bodySchema = requestBody.content?.['application/json']?.schema;
248
- if (bodySchema) {
249
- properties.push(
250
- factory.createPropertySignature(
251
- undefined,
252
- factory.createIdentifier('body'),
253
- undefined,
254
- factory.createTypeReferenceNode(factory.createIdentifier('any'), undefined)
255
- )
256
- );
257
- }
258
- }
259
-
260
- // Add params property if needed
261
- const queryParams = parameters.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'query');
262
- if (queryParams.length > 0) {
263
- properties.push(
264
- factory.createPropertySignature(
265
- undefined,
266
- factory.createIdentifier('params'),
267
- undefined,
268
- factory.createTypeLiteralNode(
269
- queryParams.map((param) =>
270
- factory.createPropertySignature(
271
- undefined,
272
- factory.createIdentifier(param.name),
273
- factory.createToken(ts.SyntaxKind.QuestionToken),
274
- factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
275
- )
276
- )
277
- )
278
- )
279
- );
280
- }
281
-
282
- // Add headers property if needed
283
- const headerParams = parameters.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'header');
284
- if (headerParams.length > 0) {
285
- properties.push(
286
- factory.createPropertySignature(
287
- undefined,
288
- factory.createIdentifier('headers'),
289
- undefined,
290
- factory.createTypeLiteralNode(
291
- headerParams.map((param) =>
292
- factory.createPropertySignature(
293
- undefined,
294
- factory.createIdentifier(param.name),
295
- factory.createToken(ts.SyntaxKind.QuestionToken),
296
- factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
297
- )
298
- )
299
- )
300
- )
301
- );
302
- }
303
-
304
- return factory.createTypeLiteralNode([
305
- factory.createPropertySignature(
306
- undefined,
307
- factory.createIdentifier('variables'),
308
- undefined,
309
- factory.createTypeLiteralNode(properties)
310
- ),
311
- factory.createPropertySignature(
312
- undefined,
313
- factory.createIdentifier('fetchOptions'),
314
- factory.createToken(ts.SyntaxKind.QuestionToken),
315
- factory.createTypeReferenceNode(factory.createIdentifier('any'), undefined)
316
- ),
317
- ]);
318
- }
319
-
320
- function generateQueryArgDefinitions(operationDefinition: OperationDefinition): QueryArgDefinitions {
321
- const {
322
- operation: { parameters = [], requestBody },
323
- } = operationDefinition;
324
-
325
- const queryArg: QueryArgDefinitions = {};
326
-
327
- // Add body definition if needed
328
- if (requestBody && !isReference(requestBody)) {
329
- const bodySchema = requestBody.content?.['application/json']?.schema;
330
- if (bodySchema) {
331
- queryArg['body'] = {
332
- name: 'body',
333
- originalName: 'body',
334
- type: factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
335
- required: true,
336
- origin: 'body',
337
- body: requestBody,
338
- };
339
- }
340
- }
341
-
342
- // Add parameter definitions
343
- parameters
344
- .filter((p): p is OpenAPIV3.ParameterObject => !isReference(p))
345
- .forEach((param) => {
346
- queryArg[param.name] = {
347
- name: param.name,
348
- originalName: param.name,
349
- type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
350
- required: param.required || false,
351
- origin: 'param',
352
- param,
353
- };
354
- });
355
-
356
- return queryArg;
357
- }
358
-
359
227
  function generateEndpoint({
360
228
  operationDefinition,
361
229
  overrides,
@@ -378,52 +246,171 @@ export async function generateApi(
378
246
  let ResponseType: ts.TypeNode = factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
379
247
  if (returnsJson) {
380
248
  const returnTypes = Object.entries(responses || {})
381
- .map(([code, response]) =>
382
- isDataResponse(code, includeDefault, response as OpenAPIV3.ResponseObject, responses as OpenAPIV3.ResponsesObject) && response
383
- ? apiGen.getTypeFromResponse(response as OpenAPIV3.ResponseObject)
384
- : undefined
249
+ .map(
250
+ ([code, response]) =>
251
+ [
252
+ code,
253
+ apiGen.resolve(response),
254
+ apiGen.getTypeFromResponse(response, 'readOnly') ||
255
+ factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
256
+ ] as const
385
257
  )
386
- .filter(removeUndefined);
387
-
388
- if (returnTypes.length === 1) {
389
- ResponseType = returnTypes[0];
390
- } else if (returnTypes.length > 1) {
258
+ .filter(([status, response]) =>
259
+ isDataResponse(status, includeDefault, apiGen.resolve(response), responses || {})
260
+ )
261
+ .filter(([_1, _2, type]) => type !== keywordType.void)
262
+ .map(([code, response, type]) =>
263
+ ts.addSyntheticLeadingComment(
264
+ { ...type },
265
+ ts.SyntaxKind.MultiLineCommentTrivia,
266
+ `* status ${code} ${response.description} `,
267
+ false
268
+ )
269
+ );
270
+ if (returnTypes.length > 0) {
391
271
  ResponseType = factory.createUnionTypeNode(returnTypes);
392
272
  }
393
273
  }
394
274
 
395
- const QueryArg = generateQueryArgType(operationDefinition);
396
- const wrappedQueryArg = factory.createTypeReferenceNode(factory.createIdentifier('IUseFetcherArgs'), [QueryArg]);
275
+ const ResponseTypeName = factory.createTypeReferenceNode(
276
+ registerInterface(
277
+ factory.createTypeAliasDeclaration(
278
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
279
+ capitalize(operationName + operationNameSuffix + responseSuffix),
280
+ undefined,
281
+ ResponseType
282
+ )
283
+ ).name
284
+ );
397
285
 
398
- const endpointBuilder = factory.createIdentifier('build');
286
+ const operationParameters = apiGen.resolveArray(operation.parameters);
287
+ const pathItemParameters = apiGen
288
+ .resolveArray(pathItem.parameters)
289
+ .filter((pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in));
399
290
 
400
- const Response = factory.createTypeReferenceNode(
401
- factory.createIdentifier(`${capitalize(operationName)}${responseSuffix}`),
402
- undefined
291
+ const parameters = supportDeepObjects([...pathItemParameters, ...operationParameters]).filter(
292
+ argumentMatches(overrides?.parameterFilter)
403
293
  );
404
294
 
405
- const queryArgDefinitions = generateQueryArgDefinitions(operationDefinition);
295
+ const allNames = parameters.map((p) => p.name);
296
+ const queryArg: QueryArgDefinitions = {};
297
+ function generateName(name: string, potentialPrefix: string) {
298
+ const isPureSnakeCase = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
299
+ // prefix with `query`, `path` or `body` if there are multiple paramters with the same name
300
+ const hasNamingConflict = allNames.filter((n) => n === name).length > 1;
301
+ if (hasNamingConflict) {
302
+ name = `${potentialPrefix}_${name}`;
303
+ }
304
+ // convert to camelCase if the name is pure snake_case and there are no naming conflicts
305
+ const camelCaseName = camelCase(name);
306
+ if (isPureSnakeCase && !allNames.includes(camelCaseName)) {
307
+ name = camelCaseName;
308
+ }
309
+ // if there are still any naming conflicts, prepend with underscore
310
+ while (name in queryArg) {
311
+ name = `_${name}`;
312
+ }
313
+ return name;
314
+ }
315
+
316
+ for (const param of parameters) {
317
+ const name = generateName(param.name, param.in);
318
+ queryArg[name] = {
319
+ origin: 'param',
320
+ name,
321
+ originalName: param.name,
322
+ type: apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly'),
323
+ required: param.required,
324
+ param,
325
+ };
326
+ }
327
+
328
+ if (requestBody) {
329
+ const body = apiGen.resolve(requestBody);
330
+ const schema = apiGen.getSchemaFromContent(body.content);
331
+ const type = apiGen.getTypeFromSchema(schema);
332
+ const schemaName = camelCase(
333
+ (type as any).name ||
334
+ getReferenceName(schema) ||
335
+ (typeof schema === 'object' && 'title' in schema && schema.title) ||
336
+ 'body'
337
+ );
338
+ const name = generateName(schemaName in queryArg ? 'body' : schemaName, 'body');
339
+
340
+ queryArg[name] = {
341
+ origin: 'body',
342
+ name,
343
+ originalName: schemaName,
344
+ type: apiGen.getTypeFromSchema(schema, undefined, 'writeOnly'),
345
+ required: true,
346
+ body,
347
+ };
348
+ }
349
+
350
+ const propertyName = (name: string | ts.PropertyName): ts.PropertyName => {
351
+ if (typeof name === 'string') {
352
+ return isValidIdentifier(name) ? factory.createIdentifier(name) : factory.createStringLiteral(name);
353
+ }
354
+ return name;
355
+ };
356
+
357
+ const queryArgValues = Object.values(queryArg);
406
358
 
407
- const extraEndpointsProps = isQuery
408
- ? generateQueryEndpointProps({ operationDefinition })
409
- : generateMutationEndpointProps({ operationDefinition });
359
+ const isFlatArg = flattenArg && queryArgValues.length === 1;
360
+ const QueryArg = factory.createTypeReferenceNode(
361
+ registerInterface(
362
+ factory.createTypeAliasDeclaration(
363
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
364
+ capitalize(operationName + operationNameSuffix + argSuffix),
365
+ undefined,
366
+ queryArgValues.length > 0
367
+ ? isFlatArg
368
+ ? withQueryComment(
369
+ factory.createUnionTypeNode([
370
+ queryArgValues[0].type,
371
+ ...(!queryArgValues[0].required
372
+ ? [factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)]
373
+ : []),
374
+ ]),
375
+ queryArgValues[0],
376
+ false
377
+ )
378
+ : factory.createTypeLiteralNode(
379
+ queryArgValues.map((def) =>
380
+ withQueryComment(
381
+ factory.createPropertySignature(
382
+ undefined,
383
+ propertyName(def.name),
384
+ createQuestionToken(!def.required),
385
+ def.type
386
+ ),
387
+ def,
388
+ true
389
+ )
390
+ )
391
+ )
392
+ : factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword)
393
+ )
394
+ ).name
395
+ );
410
396
 
411
397
  return generateEndpointDefinition({
412
- operationName,
398
+ operationName: operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName,
413
399
  type: isQuery ? 'query' : 'mutation',
414
- Response,
415
- QueryArg: wrappedQueryArg,
400
+ Response: ResponseTypeName,
401
+ QueryArg,
416
402
  queryFn: generateQueryFn({
417
403
  operationDefinition,
418
- queryArg: queryArgDefinitions,
419
- isFlatArg: flattenArg,
404
+ queryArg,
420
405
  isQuery,
406
+ isFlatArg,
421
407
  encodePathParams,
422
408
  encodeQueryParams,
423
409
  }),
424
- extraEndpointsProps,
410
+ extraEndpointsProps: isQuery
411
+ ? generateQueryEndpointProps({ operationDefinition })
412
+ : generateMutationEndpointProps({ operationDefinition }),
425
413
  tags,
426
- endpointBuilder,
427
414
  });
428
415
  }
429
416
 
@@ -442,109 +429,80 @@ export async function generateApi(
442
429
  encodePathParams: boolean;
443
430
  encodeQueryParams: boolean;
444
431
  }) {
445
- const {
446
- operation: { parameters = [], requestBody },
447
- path,
448
- verb,
449
- } = operationDefinition;
432
+ const { path, verb } = operationDefinition;
450
433
 
451
- const bodyParameter = requestBody && !isReference(requestBody) ? requestBody.content?.['application/json']?.schema : undefined;
452
- const bodyArg = bodyParameter ? queryArg['body'] : undefined;
434
+ const bodyParameter = Object.values(queryArg).find((def) => def.origin === 'body');
453
435
 
454
- const pathParameters = parameters
455
- .filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'path')
456
- .map((param) => ({
457
- name: param.name,
458
- originalName: param.name,
459
- type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
460
- required: param.required,
461
- param,
462
- origin: 'param' as const,
463
- }));
436
+ const rootObject = factory.createIdentifier('queryArg');
464
437
 
465
- const queryParameters = parameters
466
- .filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'query')
467
- .map((param) => ({
468
- name: param.name,
469
- originalName: param.name,
470
- type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
471
- required: param.required,
472
- param,
473
- origin: 'param' as const,
474
- }));
438
+ function pickParams(paramIn: string) {
439
+ return Object.values(queryArg).filter((def) => def.origin === 'param' && def.param.in === paramIn);
440
+ }
475
441
 
476
- const headerParameters = parameters
477
- .filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'header')
478
- .map((param) => ({
479
- name: param.name.includes('-') ? `'${param.name}'` : param.name,
480
- originalName: param.name,
481
- type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
482
- required: param.required,
483
- param,
484
- origin: 'param' as const,
485
- }));
442
+ function createObjectLiteralProperty(parameters: QueryArgDefinition[], propertyName: string) {
443
+ if (parameters.length === 0) return undefined;
486
444
 
487
- const rootObject = factory.createIdentifier('queryArg');
445
+ const properties = parameters.map((param) => {
446
+ const value = isFlatArg ? rootObject : accessProperty(rootObject, param.name);
488
447
 
489
- const objectProperties: ts.ObjectLiteralElementLike[] = [
490
- factory.createPropertyAssignment(
491
- 'url',
492
- generatePathExpression(path, pathParameters, rootObject, isFlatArg, encodePathParams)
493
- ),
494
- factory.createPropertyAssignment('method', factory.createStringLiteral(verb.toUpperCase())),
495
- ];
496
-
497
- if (bodyArg) {
498
- objectProperties.push(
499
- factory.createPropertyAssignment(
500
- 'body',
501
- factory.createPropertyAccessExpression(
502
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
503
- factory.createIdentifier('body')
504
- )
505
- )
506
- );
507
- }
448
+ const encodedValue =
449
+ encodeQueryParams && param.param?.in === 'query'
450
+ ? factory.createConditionalExpression(
451
+ value,
452
+ undefined,
453
+ factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
454
+ factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]),
455
+ ]),
456
+ undefined,
457
+ factory.createIdentifier('undefined')
458
+ )
459
+ : value;
508
460
 
509
- if (queryParameters.length) {
510
- objectProperties.push(
511
- factory.createPropertyAssignment(
512
- 'params',
513
- factory.createPropertyAccessExpression(
514
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
515
- factory.createIdentifier('params')
516
- )
517
- )
518
- );
519
- }
461
+ return createPropertyAssignment(param.originalName, encodedValue);
462
+ });
520
463
 
521
- if (headerParameters.length) {
522
- objectProperties.push(
523
- factory.createPropertyAssignment(
524
- 'headers',
525
- factory.createPropertyAccessExpression(
526
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
527
- factory.createIdentifier('headers')
528
- )
529
- )
464
+ return factory.createPropertyAssignment(
465
+ factory.createIdentifier(propertyName),
466
+ factory.createObjectLiteralExpression(properties, true)
530
467
  );
531
468
  }
532
469
 
533
- // Add fetchOptions
534
- objectProperties.push(
535
- factory.createPropertyAssignment(
536
- 'fetchOptions',
537
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('fetchOptions'))
538
- )
539
- );
540
-
541
470
  return factory.createArrowFunction(
542
471
  undefined,
543
472
  undefined,
544
- [factory.createParameterDeclaration(undefined, undefined, rootObject)],
473
+ Object.keys(queryArg).length
474
+ ? [factory.createParameterDeclaration(undefined, undefined, rootObject, undefined, undefined, undefined)]
475
+ : [],
545
476
  undefined,
546
477
  factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
547
- factory.createParenthesizedExpression(factory.createObjectLiteralExpression(objectProperties, true))
478
+ factory.createParenthesizedExpression(
479
+ factory.createObjectLiteralExpression(
480
+ [
481
+ factory.createPropertyAssignment(
482
+ factory.createIdentifier('url'),
483
+ generatePathExpression(path, pickParams('path'), rootObject, isFlatArg, encodePathParams)
484
+ ),
485
+ isQuery && verb.toUpperCase() === 'GET'
486
+ ? undefined
487
+ : factory.createPropertyAssignment(
488
+ factory.createIdentifier('method'),
489
+ factory.createStringLiteral(verb.toUpperCase())
490
+ ),
491
+ bodyParameter === undefined
492
+ ? undefined
493
+ : factory.createPropertyAssignment(
494
+ factory.createIdentifier('body'),
495
+ isFlatArg
496
+ ? rootObject
497
+ : factory.createPropertyAccessExpression(rootObject, factory.createIdentifier(bodyParameter.name))
498
+ ),
499
+ createObjectLiteralProperty(pickParams('cookie'), 'cookies'),
500
+ createObjectLiteralProperty(pickParams('header'), 'headers'),
501
+ createObjectLiteralProperty(pickParams('query'), 'params'),
502
+ ].filter(removeUndefined),
503
+ false
504
+ )
505
+ )
548
506
  );
549
507
  }
550
508
 
@@ -571,7 +529,7 @@ function generatePathExpression(
571
529
  rootObject: ts.Identifier,
572
530
  isFlatArg: boolean,
573
531
  encodePathParams: boolean
574
- ): ts.Expression {
532
+ ) {
575
533
  const expressions: Array<[string, string]> = [];
576
534
 
577
535
  const head = path.replace(/\{(.*?)}(.*?)(?=\{|$)/g, (_, expression, literal) => {
@@ -585,12 +543,9 @@ function generatePathExpression(
585
543
 
586
544
  return expressions.length
587
545
  ? factory.createTemplateExpression(
588
- factory.createTemplateHead(head, head),
546
+ factory.createTemplateHead(head),
589
547
  expressions.map(([prop, literal], index) => {
590
- const value = factory.createPropertyAccessExpression(
591
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
592
- factory.createIdentifier(prop)
593
- );
548
+ const value = isFlatArg ? rootObject : accessProperty(rootObject, prop);
594
549
  const encodedValue = encodePathParams
595
550
  ? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
596
551
  factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]),
@@ -599,12 +554,12 @@ function generatePathExpression(
599
554
  return factory.createTemplateSpan(
600
555
  encodedValue,
601
556
  index === expressions.length - 1
602
- ? factory.createTemplateTail(literal, literal)
603
- : factory.createTemplateMiddle(literal, literal)
557
+ ? factory.createTemplateTail(literal)
558
+ : factory.createTemplateMiddle(literal)
604
559
  );
605
560
  })
606
561
  )
607
- : factory.createStringLiteral(head);
562
+ : factory.createNoSubstitutionTemplateLiteral(head);
608
563
  }
609
564
 
610
565
  type QueryArgDefinition = {