@acrool/rtk-query-codegen-openapi 0.0.2-test.1 → 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
- apiGen.getTypeFromSchema(bodySchema)
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
- param.required ? undefined : factory.createToken(ts.SyntaxKind.QuestionToken),
274
- apiGen.getTypeFromSchema(param.schema as OpenAPIV3.SchemaObject)
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.includes('-') ? `'${param.name}'` : param.name),
295
- param.required ? undefined : factory.createToken(ts.SyntaxKind.QuestionToken),
296
- apiGen.getTypeFromSchema(param.schema as OpenAPIV3.SchemaObject)
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,55 +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(
397
- factory.createIdentifier('IUseFetcherArgs'),
398
- [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
399
284
  );
400
285
 
401
- 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));
402
290
 
403
- const Response = factory.createTypeReferenceNode(
404
- factory.createIdentifier(`${capitalize(operationName)}${responseSuffix}`),
405
- undefined
291
+ const parameters = supportDeepObjects([...pathItemParameters, ...operationParameters]).filter(
292
+ argumentMatches(overrides?.parameterFilter)
406
293
  );
407
294
 
408
- 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
+ }
409
315
 
410
- const extraEndpointsProps = isQuery
411
- ? generateQueryEndpointProps({ operationDefinition })
412
- : generateMutationEndpointProps({ operationDefinition });
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);
358
+
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
+ );
413
396
 
414
397
  return generateEndpointDefinition({
415
- operationName,
398
+ operationName: operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName,
416
399
  type: isQuery ? 'query' : 'mutation',
417
- Response,
418
- QueryArg: wrappedQueryArg,
400
+ Response: ResponseTypeName,
401
+ QueryArg,
419
402
  queryFn: generateQueryFn({
420
403
  operationDefinition,
421
- queryArg: queryArgDefinitions,
422
- isFlatArg: flattenArg,
404
+ queryArg,
423
405
  isQuery,
406
+ isFlatArg,
424
407
  encodePathParams,
425
408
  encodeQueryParams,
426
409
  }),
427
- extraEndpointsProps,
410
+ extraEndpointsProps: isQuery
411
+ ? generateQueryEndpointProps({ operationDefinition })
412
+ : generateMutationEndpointProps({ operationDefinition }),
428
413
  tags,
429
- endpointBuilder,
430
414
  });
431
415
  }
432
416
 
@@ -445,109 +429,80 @@ export async function generateApi(
445
429
  encodePathParams: boolean;
446
430
  encodeQueryParams: boolean;
447
431
  }) {
448
- const {
449
- operation: { parameters = [], requestBody },
450
- path,
451
- verb,
452
- } = operationDefinition;
432
+ const { path, verb } = operationDefinition;
453
433
 
454
- const bodyParameter = requestBody && !isReference(requestBody) ? requestBody.content?.['application/json']?.schema : undefined;
455
- const bodyArg = bodyParameter ? queryArg['body'] : undefined;
434
+ const bodyParameter = Object.values(queryArg).find((def) => def.origin === 'body');
456
435
 
457
- const pathParameters = parameters
458
- .filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'path')
459
- .map((param) => ({
460
- name: param.name,
461
- originalName: param.name,
462
- type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
463
- required: param.required,
464
- param,
465
- origin: 'param' as const,
466
- }));
436
+ const rootObject = factory.createIdentifier('queryArg');
467
437
 
468
- const queryParameters = parameters
469
- .filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'query')
470
- .map((param) => ({
471
- name: param.name,
472
- originalName: param.name,
473
- type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
474
- required: param.required,
475
- param,
476
- origin: 'param' as const,
477
- }));
438
+ function pickParams(paramIn: string) {
439
+ return Object.values(queryArg).filter((def) => def.origin === 'param' && def.param.in === paramIn);
440
+ }
478
441
 
479
- const headerParameters = parameters
480
- .filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'header')
481
- .map((param) => ({
482
- name: param.name.includes('-') ? `'${param.name}'` : param.name,
483
- originalName: param.name,
484
- type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
485
- required: param.required,
486
- param,
487
- origin: 'param' as const,
488
- }));
442
+ function createObjectLiteralProperty(parameters: QueryArgDefinition[], propertyName: string) {
443
+ if (parameters.length === 0) return undefined;
489
444
 
490
- const rootObject = factory.createIdentifier('queryArg');
445
+ const properties = parameters.map((param) => {
446
+ const value = isFlatArg ? rootObject : accessProperty(rootObject, param.name);
491
447
 
492
- const objectProperties: ts.ObjectLiteralElementLike[] = [
493
- factory.createPropertyAssignment(
494
- 'url',
495
- generatePathExpression(path, pathParameters, rootObject, isFlatArg, encodePathParams)
496
- ),
497
- factory.createPropertyAssignment('method', factory.createStringLiteral(verb.toUpperCase())),
498
- ];
499
-
500
- if (bodyArg) {
501
- objectProperties.push(
502
- factory.createPropertyAssignment(
503
- 'body',
504
- factory.createPropertyAccessExpression(
505
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
506
- factory.createIdentifier('body')
507
- )
508
- )
509
- );
510
- }
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;
511
460
 
512
- if (queryParameters.length) {
513
- objectProperties.push(
514
- factory.createPropertyAssignment(
515
- 'params',
516
- factory.createPropertyAccessExpression(
517
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
518
- factory.createIdentifier('params')
519
- )
520
- )
521
- );
522
- }
461
+ return createPropertyAssignment(param.originalName, encodedValue);
462
+ });
523
463
 
524
- if (headerParameters.length) {
525
- objectProperties.push(
526
- factory.createPropertyAssignment(
527
- 'headers',
528
- factory.createPropertyAccessExpression(
529
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
530
- factory.createIdentifier('headers')
531
- )
532
- )
464
+ return factory.createPropertyAssignment(
465
+ factory.createIdentifier(propertyName),
466
+ factory.createObjectLiteralExpression(properties, true)
533
467
  );
534
468
  }
535
469
 
536
- // Add fetchOptions
537
- objectProperties.push(
538
- factory.createPropertyAssignment(
539
- 'fetchOptions',
540
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('fetchOptions'))
541
- )
542
- );
543
-
544
470
  return factory.createArrowFunction(
545
471
  undefined,
546
472
  undefined,
547
- [factory.createParameterDeclaration(undefined, undefined, rootObject)],
473
+ Object.keys(queryArg).length
474
+ ? [factory.createParameterDeclaration(undefined, undefined, rootObject, undefined, undefined, undefined)]
475
+ : [],
548
476
  undefined,
549
477
  factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
550
- 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
+ )
551
506
  );
552
507
  }
553
508
 
@@ -574,7 +529,7 @@ function generatePathExpression(
574
529
  rootObject: ts.Identifier,
575
530
  isFlatArg: boolean,
576
531
  encodePathParams: boolean
577
- ): ts.Expression {
532
+ ) {
578
533
  const expressions: Array<[string, string]> = [];
579
534
 
580
535
  const head = path.replace(/\{(.*?)}(.*?)(?=\{|$)/g, (_, expression, literal) => {
@@ -588,12 +543,9 @@ function generatePathExpression(
588
543
 
589
544
  return expressions.length
590
545
  ? factory.createTemplateExpression(
591
- factory.createTemplateHead(head, head),
546
+ factory.createTemplateHead(head),
592
547
  expressions.map(([prop, literal], index) => {
593
- const value = factory.createPropertyAccessExpression(
594
- factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
595
- factory.createIdentifier(prop)
596
- );
548
+ const value = isFlatArg ? rootObject : accessProperty(rootObject, prop);
597
549
  const encodedValue = encodePathParams
598
550
  ? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
599
551
  factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]),
@@ -602,12 +554,12 @@ function generatePathExpression(
602
554
  return factory.createTemplateSpan(
603
555
  encodedValue,
604
556
  index === expressions.length - 1
605
- ? factory.createTemplateTail(literal, literal)
606
- : factory.createTemplateMiddle(literal, literal)
557
+ ? factory.createTemplateTail(literal)
558
+ : factory.createTemplateMiddle(literal)
607
559
  );
608
560
  })
609
561
  )
610
- : factory.createStringLiteral(head);
562
+ : factory.createNoSubstitutionTemplateLiteral(head);
611
563
  }
612
564
 
613
565
  type QueryArgDefinition = {