@openapi-qraft/tanstack-query-react-plugin 1.8.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 (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -0
  3. package/dist/__snapshots__/create-api-client.ts.snapshot.d.ts +1 -0
  4. package/dist/__snapshots__/create-api-client.ts.snapshot.js +88 -0
  5. package/dist/__snapshots__/create-api-client.ts.snapshot.js.map +1 -0
  6. package/dist/__snapshots__/index.ts.snapshot.d.ts +1 -0
  7. package/dist/__snapshots__/index.ts.snapshot.js +13 -0
  8. package/dist/__snapshots__/index.ts.snapshot.js.map +1 -0
  9. package/dist/__snapshots__/services/ApprovalPoliciesService.ts.snapshot.d.ts +1 -0
  10. package/dist/__snapshots__/services/ApprovalPoliciesService.ts.snapshot.js +62 -0
  11. package/dist/__snapshots__/services/ApprovalPoliciesService.ts.snapshot.js.map +1 -0
  12. package/dist/__snapshots__/services/FilesService.ts.snapshot.d.ts +1 -0
  13. package/dist/__snapshots__/services/FilesService.ts.snapshot.js +57 -0
  14. package/dist/__snapshots__/services/FilesService.ts.snapshot.js.map +1 -0
  15. package/dist/__snapshots__/services/index.ts.snapshot.d.ts +1 -0
  16. package/dist/__snapshots__/services/index.ts.snapshot.js +24 -0
  17. package/dist/__snapshots__/services/index.ts.snapshot.js.map +1 -0
  18. package/dist/generateCode.d.ts +18 -0
  19. package/dist/generateCode.js +116 -0
  20. package/dist/generateCode.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +3 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/plugin.d.ts +2 -0
  25. package/dist/plugin.js +30 -0
  26. package/dist/plugin.js.map +1 -0
  27. package/dist/ts-factory/astToString.d.ts +8 -0
  28. package/dist/ts-factory/astToString.js +14 -0
  29. package/dist/ts-factory/astToString.js.map +1 -0
  30. package/dist/ts-factory/getClientFactory.d.ts +7 -0
  31. package/dist/ts-factory/getClientFactory.js +84 -0
  32. package/dist/ts-factory/getClientFactory.js.map +1 -0
  33. package/dist/ts-factory/getIndexFactory.d.ts +5 -0
  34. package/dist/ts-factory/getIndexFactory.js +17 -0
  35. package/dist/ts-factory/getIndexFactory.js.map +1 -0
  36. package/dist/ts-factory/getServiceFactory.d.ts +10 -0
  37. package/dist/ts-factory/getServiceFactory.js +120 -0
  38. package/dist/ts-factory/getServiceFactory.js.map +1 -0
  39. package/dist/ts-factory/getServiceIndexFactory.d.ts +7 -0
  40. package/dist/ts-factory/getServiceIndexFactory.js +26 -0
  41. package/dist/ts-factory/getServiceIndexFactory.js.map +1 -0
  42. package/package.json +48 -0
  43. package/src/__snapshots__/create-api-client.ts.snapshot.ts +88 -0
  44. package/src/__snapshots__/index.ts.snapshot.ts +13 -0
  45. package/src/__snapshots__/services/ApprovalPoliciesService.ts.snapshot.ts +64 -0
  46. package/src/__snapshots__/services/FilesService.ts.snapshot.ts +57 -0
  47. package/src/__snapshots__/services/index.ts.snapshot.ts +24 -0
  48. package/src/generateCode.ts +201 -0
  49. package/src/index.ts +3 -0
  50. package/src/plugin.ts +45 -0
  51. package/src/ts-factory/astToString.ts +34 -0
  52. package/src/ts-factory/getClientFactory.ts +207 -0
  53. package/src/ts-factory/getIndexFactory.ts +58 -0
  54. package/src/ts-factory/getServiceFactory.ts +410 -0
  55. package/src/ts-factory/getServiceIndexFactory.ts +104 -0
@@ -0,0 +1,201 @@
1
+ import { GeneratorFile } from '@openapi-qraft/plugin/lib/GeneratorFile';
2
+ import { Service } from '@openapi-qraft/plugin/lib/open-api/getServices';
3
+ import { OutputOptions as OutputOptionsBase } from '@openapi-qraft/plugin/lib/OutputOptions';
4
+
5
+ import c from 'ansi-colors';
6
+ import { URL } from 'node:url';
7
+ import { Ora } from 'ora';
8
+
9
+ import { astToString } from './ts-factory/astToString.js';
10
+ import { getClientFactory } from './ts-factory/getClientFactory.js';
11
+ import { getIndexFactory } from './ts-factory/getIndexFactory.js';
12
+ import {
13
+ getServiceFactory,
14
+ ServiceImportsFactoryOptions,
15
+ } from './ts-factory/getServiceFactory.js';
16
+ import { getServiceIndexFactory } from './ts-factory/getServiceIndexFactory.js';
17
+
18
+ interface OutputOptions extends OutputOptionsBase {
19
+ fileHeader: string | undefined;
20
+ servicesDirName: string;
21
+ explicitImportExtensions: boolean;
22
+ }
23
+
24
+ export const generateCode = async ({
25
+ spinner,
26
+ services,
27
+ serviceImports,
28
+ output,
29
+ }: {
30
+ spinner: Ora;
31
+ services: Service[];
32
+ serviceImports: ServiceImportsFactoryOptions;
33
+ output: OutputOptions;
34
+ }) => {
35
+ return [
36
+ ...(await generateServices(spinner, services, serviceImports, output)),
37
+ ...(await generateServiceIndex(spinner, services, output)),
38
+ ...(await generateClient(spinner, output)),
39
+ ...(await generateIndex(spinner, output)),
40
+ ];
41
+ };
42
+
43
+ const composeServicesDirPath = (
44
+ output: Pick<OutputOptions, 'servicesDirName' | 'dir'>
45
+ ) => {
46
+ return new URL(`${output.servicesDirName}/`, output.dir);
47
+ };
48
+
49
+ const generateServices = async (
50
+ spinner: Ora,
51
+ services: Service[],
52
+ serviceImports: ServiceImportsFactoryOptions,
53
+ output: OutputOptions
54
+ ) => {
55
+ const servicesDir = composeServicesDirPath(output);
56
+
57
+ spinner.start('Generating services');
58
+
59
+ const serviceFiles: GeneratorFile[] = [
60
+ {
61
+ directory: servicesDir,
62
+ clean: output.clean,
63
+ },
64
+ ];
65
+
66
+ for (const service of services) {
67
+ const { operations, name, typeName, variableName, fileBaseName } = service;
68
+
69
+ spinner.text = `Generating ${c.magenta(name)} service`;
70
+
71
+ try {
72
+ const code = astToString(
73
+ getServiceFactory(
74
+ {
75
+ typeName,
76
+ variableName,
77
+ },
78
+ operations,
79
+ serviceImports
80
+ )
81
+ );
82
+
83
+ serviceFiles.push({
84
+ file: new URL(`${fileBaseName}.ts`, servicesDir),
85
+ code: getFileHeader(output) + code,
86
+ });
87
+ } catch (error) {
88
+ spinner.fail(
89
+ c.redBright(
90
+ `Error occurred during ${c.magenta(name)} service generation`
91
+ )
92
+ );
93
+
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ spinner.succeed(c.green('Services has been generated'));
99
+
100
+ return serviceFiles;
101
+ };
102
+
103
+ const generateServiceIndex = async (
104
+ spinner: Ora,
105
+ services: Service[],
106
+ output: OutputOptions
107
+ ) => {
108
+ spinner.start('Generating services index');
109
+
110
+ const serviceIndexFiles: GeneratorFile[] = [
111
+ {
112
+ directory: composeServicesDirPath(output),
113
+ clean: output.clean,
114
+ },
115
+ ];
116
+
117
+ try {
118
+ const code = astToString(
119
+ getServiceIndexFactory(services, {
120
+ explicitImportExtensions: Boolean(output.explicitImportExtensions),
121
+ })
122
+ );
123
+
124
+ serviceIndexFiles.push({
125
+ file: new URL('index.ts', composeServicesDirPath(output)),
126
+ code: getFileHeader(output) + code,
127
+ });
128
+ } catch (error) {
129
+ spinner.fail(
130
+ c.redBright('Error occurred during services index generation')
131
+ );
132
+
133
+ throw error;
134
+ }
135
+
136
+ spinner.succeed(c.green('Services index has been generated'));
137
+
138
+ return serviceIndexFiles;
139
+ };
140
+
141
+ const generateClient = async (spinner: Ora, output: OutputOptions) => {
142
+ spinner.start('Generating client');
143
+
144
+ const clientFiles: GeneratorFile[] = [];
145
+
146
+ try {
147
+ const code = astToString(
148
+ getClientFactory({
149
+ servicesDirName: output.servicesDirName,
150
+ explicitImportExtensions: Boolean(output.explicitImportExtensions),
151
+ })
152
+ );
153
+
154
+ clientFiles.push({
155
+ file: new URL('create-api-client.ts', output.dir),
156
+ code: getFileHeader(output) + code,
157
+ });
158
+ } catch (error) {
159
+ spinner.fail(c.redBright('Error occurred during client generation'));
160
+
161
+ throw error;
162
+ }
163
+
164
+ spinner.succeed(c.green('Client has been generated'));
165
+
166
+ return clientFiles;
167
+ };
168
+
169
+ const generateIndex = async (spinner: Ora, output: OutputOptions) => {
170
+ spinner.start('Generating index');
171
+
172
+ const indexFiles: GeneratorFile[] = [];
173
+
174
+ try {
175
+ const code = astToString(
176
+ getIndexFactory({
177
+ servicesDirName: output.servicesDirName,
178
+ explicitImportExtensions: Boolean(output.explicitImportExtensions),
179
+ })
180
+ );
181
+
182
+ indexFiles.push({
183
+ file: new URL('index.ts', output.dir),
184
+ code: getFileHeader(output) + code,
185
+ });
186
+ } catch (error) {
187
+ spinner.fail(c.redBright('Error occurred during index generation'));
188
+
189
+ throw error;
190
+ }
191
+
192
+ spinner.succeed(c.green('Index has been generated'));
193
+
194
+ return indexFiles;
195
+ };
196
+
197
+ export const getFileHeader = ({
198
+ fileHeader,
199
+ }: Pick<OutputOptions, 'fileHeader'>) => {
200
+ return fileHeader && `${fileHeader}\n`;
201
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { plugin } from './plugin.js';
2
+
3
+ export default plugin;
package/src/plugin.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { fileHeader } from '@openapi-qraft/plugin/lib/fileHeader';
2
+ import { QraftCommand } from '@openapi-qraft/plugin/lib/QraftCommand';
3
+ import { QraftCommandPlugin } from '@openapi-qraft/plugin/lib/QraftCommandPlugin';
4
+
5
+ import { generateCode } from './generateCode.js';
6
+
7
+ export const plugin: QraftCommandPlugin = {
8
+ setupCommand(command: QraftCommand) {
9
+ command
10
+ .description('Generate TanStack Query React client from OpenAPI Schema')
11
+ .requiredOption(
12
+ '--openapi-types-import-path <path>', // todo::specify better param name to avoid confusion with real path
13
+ 'Path to schema types file (.d.ts), eg: "../openapi.d.ts"'
14
+ )
15
+ .option(
16
+ '--file-header <string>',
17
+ 'Header to be added to the generated file (eg: /* eslint-disable */)'
18
+ )
19
+ .option(
20
+ '--explicit-import-extensions',
21
+ 'All import statements will include explicit `.js` extensions. Ideal for projects using ECMAScript modules.'
22
+ )
23
+ .option(
24
+ '--operation-generics-import-path <path>',
25
+ 'Path to operation generics file',
26
+ '@openapi-qraft/react'
27
+ )
28
+ .action(({ spinner, output, args, services }, resolve) => {
29
+ return void generateCode({
30
+ spinner,
31
+ services,
32
+ serviceImports: {
33
+ operationGenericsImportPath: args.operationGenericsImportPath,
34
+ openapiTypesImportPath: args.openapiTypesImportPath,
35
+ },
36
+ output: {
37
+ ...output,
38
+ fileHeader: args.fileHeader ?? fileHeader,
39
+ explicitImportExtensions: args.explicitImportExtensions,
40
+ servicesDirName: 'services',
41
+ },
42
+ }).then(resolve);
43
+ });
44
+ },
45
+ };
@@ -0,0 +1,34 @@
1
+ import ts from 'typescript';
2
+
3
+ export interface AstToStringOptions {
4
+ fileName?: string;
5
+ sourceText?: string;
6
+ formatOptions?: ts.PrinterOptions;
7
+ }
8
+
9
+ /** Convert TypeScript AST to string */
10
+ export function astToString(
11
+ ast: ts.Node | ts.Node[] | ts.TypeElement | ts.TypeElement[],
12
+ options?: AstToStringOptions
13
+ ): string {
14
+ const sourceFile = ts.createSourceFile(
15
+ options?.fileName ?? 'openapi-ts.ts',
16
+ options?.sourceText ?? '',
17
+ ts.ScriptTarget.ESNext,
18
+ false,
19
+ ts.ScriptKind.TS
20
+ );
21
+
22
+ // @ts-expect-error it’s OK to overwrite statements once
23
+ sourceFile.statements = ts.factory.createNodeArray(
24
+ Array.isArray(ast) ? ast : [ast]
25
+ );
26
+
27
+ const printer = ts.createPrinter({
28
+ newLine: ts.NewLineKind.LineFeed,
29
+ removeComments: false,
30
+ ...options?.formatOptions,
31
+ });
32
+
33
+ return printer.printFile(sourceFile);
34
+ }
@@ -0,0 +1,207 @@
1
+ import ts from 'typescript';
2
+
3
+ type Options = {
4
+ servicesDirName: string;
5
+ explicitImportExtensions: boolean;
6
+ };
7
+
8
+ export const getClientFactory = (options: Options) => {
9
+ return [
10
+ ...getClientImportsFactory(options),
11
+ getCallbacksVariableFactory(),
12
+ getCreateClientFunctionFactory(),
13
+ ];
14
+ };
15
+
16
+ const getClientImportsFactory = ({
17
+ servicesDirName,
18
+ explicitImportExtensions,
19
+ }: Options) => {
20
+ const factory = ts.factory;
21
+
22
+ return [
23
+ factory.createImportDeclaration(
24
+ undefined,
25
+ factory.createImportClause(
26
+ false,
27
+ undefined,
28
+ factory.createNamedImports([
29
+ factory.createImportSpecifier(
30
+ false,
31
+ undefined,
32
+ factory.createIdentifier('qraftAPIClient')
33
+ ),
34
+ factory.createImportSpecifier(
35
+ false,
36
+ undefined,
37
+ factory.createIdentifier('QraftClientOptions')
38
+ ),
39
+ ])
40
+ ),
41
+ factory.createStringLiteral('@openapi-qraft/react'),
42
+ undefined
43
+ ),
44
+ ...serviceCallbacks.map((propertyName) =>
45
+ factory.createImportDeclaration(
46
+ undefined,
47
+ factory.createImportClause(
48
+ false,
49
+ undefined,
50
+ factory.createNamedImports([
51
+ factory.createImportSpecifier(
52
+ false,
53
+ undefined,
54
+ factory.createIdentifier(propertyName)
55
+ ),
56
+ ])
57
+ ),
58
+ factory.createStringLiteral(
59
+ `@openapi-qraft/react/callbacks/${propertyName}`
60
+ ),
61
+ undefined
62
+ )
63
+ ),
64
+ factory.createImportDeclaration(
65
+ undefined,
66
+ factory.createImportClause(
67
+ false,
68
+ undefined,
69
+ factory.createNamedImports([
70
+ factory.createImportSpecifier(
71
+ false,
72
+ undefined,
73
+ factory.createIdentifier('services')
74
+ ),
75
+ factory.createImportSpecifier(
76
+ false,
77
+ undefined,
78
+ factory.createIdentifier('Services')
79
+ ),
80
+ ])
81
+ ),
82
+ factory.createStringLiteral(
83
+ `./${servicesDirName}/index${explicitImportExtensions ? '.js' : ''}`
84
+ ),
85
+ undefined
86
+ ),
87
+ ];
88
+ };
89
+
90
+ const getCallbacksVariableFactory = () => {
91
+ const factory = ts.factory;
92
+
93
+ return factory.createVariableStatement(
94
+ undefined,
95
+ factory.createVariableDeclarationList(
96
+ [
97
+ factory.createVariableDeclaration(
98
+ factory.createIdentifier('callbacks'),
99
+ undefined,
100
+ undefined,
101
+ factory.createObjectLiteralExpression(
102
+ serviceCallbacks.map((propertyName) =>
103
+ factory.createShorthandPropertyAssignment(
104
+ factory.createIdentifier(propertyName),
105
+ undefined
106
+ )
107
+ ),
108
+ true
109
+ )
110
+ ),
111
+ ],
112
+ ts.NodeFlags.Const
113
+ )
114
+ );
115
+ };
116
+
117
+ const getCreateClientFunctionFactory = () => {
118
+ const factory = ts.factory;
119
+
120
+ return factory.createFunctionDeclaration(
121
+ [factory.createToken(ts.SyntaxKind.ExportKeyword)],
122
+ undefined,
123
+ factory.createIdentifier('createAPIClient'),
124
+ undefined,
125
+ [
126
+ factory.createParameterDeclaration(
127
+ undefined,
128
+ undefined,
129
+ factory.createIdentifier('options'),
130
+ factory.createToken(ts.SyntaxKind.QuestionToken),
131
+ factory.createTypeReferenceNode(
132
+ factory.createIdentifier('QraftClientOptions'),
133
+ undefined
134
+ ),
135
+ undefined
136
+ ),
137
+ ],
138
+ factory.createTypeReferenceNode(
139
+ factory.createIdentifier('Services'),
140
+ undefined
141
+ ),
142
+ factory.createBlock(
143
+ [
144
+ factory.createReturnStatement(
145
+ factory.createCallExpression(
146
+ factory.createIdentifier('qraftAPIClient'),
147
+ [
148
+ factory.createTypeReferenceNode(
149
+ factory.createIdentifier('Services'),
150
+ undefined
151
+ ),
152
+ factory.createTypeQueryNode(
153
+ factory.createIdentifier('callbacks'),
154
+ undefined
155
+ ),
156
+ ],
157
+ [
158
+ factory.createIdentifier('services'),
159
+ factory.createIdentifier('callbacks'),
160
+ factory.createIdentifier('options'),
161
+ ]
162
+ )
163
+ ),
164
+ ],
165
+ true
166
+ )
167
+ );
168
+ };
169
+
170
+ // todo::make callbacks type-safe
171
+ const serviceCallbacks = [
172
+ 'getInfiniteQueryData',
173
+ 'getInfiniteQueryKey',
174
+ 'getMutationKey',
175
+ 'getQueryData',
176
+ 'getQueriesData',
177
+ 'getQueryKey',
178
+ 'mutationFn',
179
+ 'queryFn',
180
+ 'setInfiniteQueryData',
181
+ 'setQueryData',
182
+ 'setQueriesData',
183
+ 'useInfiniteQuery',
184
+ 'useMutation',
185
+ 'useQuery',
186
+ 'useSuspenseQuery',
187
+ 'useSuspenseInfiniteQuery',
188
+ 'useMutationState',
189
+ 'useIsMutating',
190
+ 'useQueries',
191
+ 'useSuspenseQueries',
192
+ 'invalidateQueries',
193
+ 'cancelQueries',
194
+ 'resetQueries',
195
+ 'removeQueries',
196
+ 'refetchQueries',
197
+ 'isFetching',
198
+ 'isMutating',
199
+ 'useIsFetching',
200
+ 'fetchQuery',
201
+ 'prefetchQuery',
202
+ 'fetchInfiniteQuery',
203
+ 'prefetchInfiniteQuery',
204
+ 'getQueryState',
205
+ 'getInfiniteQueryState',
206
+ 'operationInvokeFn',
207
+ ] as const;
@@ -0,0 +1,58 @@
1
+ import ts from 'typescript';
2
+
3
+ export const getIndexFactory = ({
4
+ servicesDirName,
5
+ explicitImportExtensions,
6
+ }: {
7
+ servicesDirName: string;
8
+ explicitImportExtensions: boolean;
9
+ }) => {
10
+ const factory = ts.factory;
11
+ const importExtension = explicitImportExtensions ? '.js' : '';
12
+
13
+ return [
14
+ factory.createExportDeclaration(
15
+ undefined,
16
+ false,
17
+ factory.createNamedExports([
18
+ factory.createExportSpecifier(
19
+ false,
20
+ undefined,
21
+ factory.createIdentifier('services')
22
+ ),
23
+ ]),
24
+ factory.createStringLiteral(
25
+ `./${servicesDirName}/index${importExtension}`
26
+ ),
27
+ undefined
28
+ ),
29
+ factory.createExportDeclaration(
30
+ undefined,
31
+ true,
32
+ factory.createNamedExports([
33
+ factory.createExportSpecifier(
34
+ false,
35
+ undefined,
36
+ factory.createIdentifier('Services')
37
+ ),
38
+ ]),
39
+ factory.createStringLiteral(
40
+ `./${servicesDirName}/index${importExtension}`
41
+ ),
42
+ undefined
43
+ ),
44
+ factory.createExportDeclaration(
45
+ undefined,
46
+ false,
47
+ factory.createNamedExports([
48
+ factory.createExportSpecifier(
49
+ false,
50
+ undefined,
51
+ factory.createIdentifier('createAPIClient')
52
+ ),
53
+ ]),
54
+ factory.createStringLiteral(`./create-api-client${importExtension}`),
55
+ undefined
56
+ ),
57
+ ];
58
+ };