react-query-lightbase-codegen 1.6.4 → 2.0.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 (52) hide show
  1. package/dist/config/loadConfig.d.ts +2 -0
  2. package/dist/config/loadConfig.js +24 -0
  3. package/dist/generator/clientGenerator.d.ts +13 -0
  4. package/dist/generator/clientGenerator.js +199 -0
  5. package/dist/generator/instanceGenerator.d.ts +1 -0
  6. package/dist/generator/instanceGenerator.js +21 -0
  7. package/dist/generator/reactQueryGenerator.d.ts +2 -0
  8. package/dist/generator/reactQueryGenerator.js +82 -0
  9. package/dist/generator/schemaGenerator.d.ts +5 -0
  10. package/dist/generator/schemaGenerator.js +116 -0
  11. package/dist/index.d.ts +5 -3
  12. package/dist/index.js +106 -4
  13. package/dist/types/config.d.ts +11 -0
  14. package/dist/types/config.js +2 -0
  15. package/dist/types.d.ts +5 -0
  16. package/dist/types.js +2 -0
  17. package/dist/utils.d.ts +31 -20
  18. package/dist/utils.js +57 -179
  19. package/package.json +54 -82
  20. package/readme.md +94 -0
  21. package/src/config/loadConfig.ts +25 -0
  22. package/src/generator/clientGenerator.ts +234 -0
  23. package/src/generator/instanceGenerator.ts +20 -0
  24. package/src/generator/reactQueryGenerator.ts +92 -0
  25. package/src/generator/schemaGenerator.ts +139 -0
  26. package/src/index.ts +78 -3
  27. package/src/types/config.ts +12 -0
  28. package/src/types.ts +5 -0
  29. package/src/utils.ts +56 -213
  30. package/README.md +0 -77
  31. package/dist/convertSwaggerFile.d.ts +0 -5
  32. package/dist/convertSwaggerFile.js +0 -26
  33. package/dist/convertSwaggerFile.js.map +0 -1
  34. package/dist/generateHooks.d.ts +0 -18
  35. package/dist/generateHooks.js +0 -448
  36. package/dist/generateHooks.js.map +0 -1
  37. package/dist/generateImports.d.ts +0 -6
  38. package/dist/generateImports.js +0 -21
  39. package/dist/generateImports.js.map +0 -1
  40. package/dist/generateSchemas.d.ts +0 -7
  41. package/dist/generateSchemas.js +0 -100
  42. package/dist/generateSchemas.js.map +0 -1
  43. package/dist/importSpecs.d.ts +0 -10
  44. package/dist/importSpecs.js +0 -127
  45. package/dist/importSpecs.js.map +0 -1
  46. package/dist/index.js.map +0 -1
  47. package/dist/utils.js.map +0 -1
  48. package/src/convertSwaggerFile.ts +0 -25
  49. package/src/generateHooks.ts +0 -540
  50. package/src/generateImports.ts +0 -32
  51. package/src/generateSchemas.ts +0 -117
  52. package/src/importSpecs.ts +0 -168
package/dist/utils.d.ts CHANGED
@@ -1,24 +1,35 @@
1
- import type { ReferenceObject, RequestBodyObject, ResponseObject, SchemaObject } from 'openapi3-ts';
2
- export declare const getDocs: (data: ReferenceObject | {
3
- description?: string;
4
- }) => string;
1
+ import type { OpenAPIV3 } from "openapi-types";
2
+ export declare function camelCase(str: string): string;
3
+ export declare function pascalCase(str: string): string;
5
4
  /**
6
- * Discriminator helper for `ReferenceObject`
5
+ * Sanitizes a property name to ensure it's a valid JavaScript identifier.
6
+ * If the name is already a valid identifier (starts with letter/underscore/$ and contains only letters/numbers/underscore/$),
7
+ * returns it as-is. Otherwise wraps it in quotes to make it a valid property accessor.
8
+ *
9
+ * For example:
10
+ * - sanitizePropertyName("validName") => "validName"
11
+ * - sanitizePropertyName("invalid-name") => "'invalid-name'"
12
+ * - sanitizePropertyName("123invalid") => "'123invalid'"
13
+ *
14
+ * @param name The property name to sanitize
15
+ * @returns The sanitized property name, quoted if needed
7
16
  */
8
- export declare const isReference: (property: any) => property is ReferenceObject;
17
+ export declare function sanitizePropertyName(name: string): string;
9
18
  /**
10
- * Return the typescript equivalent of open-api data type
19
+ * Sanitizes a type name to ensure it's a valid TypeScript type identifier.
20
+ * Replaces any characters that aren't alphanumeric or underscore with an underscore.
21
+ *
22
+ * For example:
23
+ * - sanitizeTypeName("ValidType") => "ValidType"
24
+ * - sanitizeTypeName("invalid-type") => "invalid_type"
25
+ * - sanitizeTypeName("type.name") => "type_name"
26
+ * - sanitizeTypeName("type/name") => "type_name"
27
+ *
28
+ * This is used to convert operation IDs and response names from the OpenAPI spec
29
+ * into valid TypeScript type names.
30
+ *
31
+ * @param name The type name to sanitize
32
+ * @returns The sanitized type name with invalid characters replaced by underscores
11
33
  */
12
- export declare const getScalar: (item: SchemaObject) => string;
13
- /**
14
- * Resolve the value of a schema object to a proper type definition.
15
- */
16
- export declare const resolveValue: (schema: SchemaObject) => string;
17
- /**
18
- * Format a description to code documentation.
19
- */
20
- export declare const formatDescription: (description?: string) => string;
21
- /**
22
- * Extract responses / request types from open-api specs
23
- */
24
- export declare const getResReqTypes: (responsesOrRequests: Array<[string, ResponseObject | ReferenceObject | RequestBodyObject]>) => string;
34
+ export declare function sanitizeTypeName(name: string): string;
35
+ export declare function specTitle(spec: OpenAPIV3.Document): string;
package/dist/utils.js CHANGED
@@ -1,179 +1,57 @@
1
- import lodash from 'lodash';
2
- const { uniq, isEmpty } = lodash;
3
- import pasCase from 'case';
4
- import chalk from 'chalk';
5
- const { pascal } = pasCase;
6
- import { imports } from './generateHooks.js';
7
- export const getDocs = (data) => isReference(data) ? '' : formatDescription(data.description);
8
- /**
9
- * Discriminator helper for `ReferenceObject`
10
- */
11
- export const isReference = (property) => {
12
- return Boolean(property.$ref);
13
- };
14
- /**
15
- * Return the typescript equivalent of open-api data type
16
- */
17
- export const getScalar = (item) => {
18
- const nullable = item.nullable ? ' | null' : '';
19
- switch (item.type) {
20
- case 'number':
21
- case 'integer':
22
- return 'number' + nullable;
23
- case 'boolean':
24
- return 'boolean' + nullable;
25
- case 'array':
26
- return getArray(item) + nullable;
27
- case 'string':
28
- if (item.format === 'binary') {
29
- return 'string | { name?: string; type?: string; uri: string }' + nullable;
30
- }
31
- if (item.enum) {
32
- return `"${item.enum.join(`" | "`)}"` + nullable;
33
- }
34
- return 'string' + nullable;
35
- case 'object':
36
- return getObject(item) + nullable;
37
- default:
38
- return getObject(item) + nullable;
39
- }
40
- };
41
- /**
42
- * Return the output type from the $ref
43
- */
44
- const getRef = ($ref) => {
45
- if ($ref.startsWith('#/components/schemas')) {
46
- return pascal($ref.replace('#/components/schemas/', ''));
47
- }
48
- else if ($ref.startsWith('#/components/responses')) {
49
- return pascal($ref.replace('#/components/responses/', '')) + 'Response';
50
- }
51
- else if ($ref.startsWith('#/components/parameters')) {
52
- return pascal($ref.replace('#/components/parameters/', '')) + 'Parameter';
53
- }
54
- else if ($ref.startsWith('#/components/requestBodies')) {
55
- return pascal($ref.replace('#/components/requestBodies/', '')) + 'RequestBody';
56
- }
57
- else {
58
- throw new Error('This library only resolve $ref that are include into `#/components/*` for now');
59
- }
60
- };
61
- /**
62
- * Return the output type from an array
63
- */
64
- const getArray = (item) => {
65
- if (item.items) {
66
- if (!isReference(item.items) && (item.items.oneOf || item.items.allOf || item.items.enum)) {
67
- return `(${resolveValue(item.items)})[]`;
68
- }
69
- else {
70
- return `${resolveValue(item.items)}[]`;
71
- }
72
- }
73
- console.log(chalk.red(`Skipped(${item.title}): All arrays must have an 'items' key define`));
74
- return 'unknown[]';
75
- };
76
- /**
77
- * Return the output type from an object
78
- */
79
- const getObject = (item) => {
80
- if (isReference(item)) {
81
- return getRef(item.$ref);
82
- }
83
- if (item.allOf) {
84
- return item.allOf.map(resolveValue).join(' & ');
85
- }
86
- if (item.oneOf) {
87
- return item.oneOf.map(resolveValue).join(' | ');
88
- }
89
- if (item.anyOf) {
90
- return item.anyOf.map(resolveValue).join(' | ');
91
- }
92
- if (!item.type && !item.properties && !item.additionalProperties) {
93
- return '{}';
94
- }
95
- // Free form object (https://swagger.io/docs/specification/data-models/data-types/#free-form)
96
- if (item.type === 'object' &&
97
- !item.properties &&
98
- (!item.additionalProperties || item.additionalProperties === true || isEmpty(item.additionalProperties))) {
99
- return '{[key: string]: any}';
100
- }
101
- const IdentifierRegexp = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
102
- // Consolidation of item.properties & item.additionalProperties
103
- let output = '{';
104
- if (item.properties) {
105
- output += Object.entries(item.properties)
106
- .map(([key, prop]) => {
107
- const doc = getDocs(prop);
108
- const isRequired = (item.required || []).includes(key);
109
- const processedKey = IdentifierRegexp.test(key) ? key : `"${key}"`;
110
- return `${doc}\n${processedKey}${isRequired ? '' : '?'}: ${resolveValue(prop)}`;
111
- })
112
- .join('');
113
- }
114
- if (item.additionalProperties) {
115
- if (item.properties) {
116
- output += '\n';
117
- }
118
- output += `} & { [key: string]: ${item.additionalProperties === true ? 'any' : resolveValue(item.additionalProperties)}`;
119
- }
120
- if (item.properties || item.additionalProperties) {
121
- if (output === '{\n') {
122
- return '{}';
123
- }
124
- return output + '\n}';
125
- }
126
- return item.type === 'object' ? '{[key: string]: any}' : 'any';
127
- };
128
- /**
129
- * Resolve the value of a schema object to a proper type definition.
130
- */
131
- export const resolveValue = (schema) => {
132
- if (isReference(schema)) {
133
- const ref = getRef(schema.$ref);
134
- imports?.push(ref);
135
- return ref;
136
- }
137
- return getScalar(schema);
138
- };
139
- /**
140
- * Format a description to code documentation.
141
- */
142
- export const formatDescription = (description) => {
143
- if (!description) {
144
- return '';
145
- }
146
- return `\n/**\n${description
147
- .split('\n')
148
- .map((i) => `* ${i}`)
149
- .join('\n')}\n */`;
150
- };
151
- /**
152
- * Extract responses / request types from open-api specs
153
- */
154
- export const getResReqTypes = (responsesOrRequests) => {
155
- return uniq(responsesOrRequests.map(([_, res]) => {
156
- if (!res) {
157
- return;
158
- }
159
- if (isReference(res)) {
160
- const ref = getRef(res.$ref);
161
- imports.push(ref);
162
- return ref;
163
- }
164
- if (res.content) {
165
- for (const contentType of Object.keys(res.content)) {
166
- if (contentType.startsWith('application/json') ||
167
- contentType.startsWith('application/ld+json') ||
168
- contentType.startsWith('application/octet-stream') ||
169
- contentType.startsWith('multipart/form-data')) {
170
- const schema = res.content[contentType].schema;
171
- return resolveValue(schema);
172
- }
173
- }
174
- return;
175
- }
176
- return;
177
- })).join(' | ');
178
- };
179
- //# sourceMappingURL=utils.js.map
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.camelCase = camelCase;
4
+ exports.pascalCase = pascalCase;
5
+ exports.sanitizePropertyName = sanitizePropertyName;
6
+ exports.sanitizeTypeName = sanitizeTypeName;
7
+ exports.specTitle = specTitle;
8
+ function camelCase(str) {
9
+ return str
10
+ .toLowerCase()
11
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
12
+ .replace(/^[A-Z]/, (c) => c.toLowerCase());
13
+ }
14
+ function pascalCase(str) {
15
+ return str
16
+ .toLowerCase()
17
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
18
+ .replace(/^[a-z]/, (c) => c.toUpperCase());
19
+ }
20
+ /**
21
+ * Sanitizes a property name to ensure it's a valid JavaScript identifier.
22
+ * If the name is already a valid identifier (starts with letter/underscore/$ and contains only letters/numbers/underscore/$),
23
+ * returns it as-is. Otherwise wraps it in quotes to make it a valid property accessor.
24
+ *
25
+ * For example:
26
+ * - sanitizePropertyName("validName") => "validName"
27
+ * - sanitizePropertyName("invalid-name") => "'invalid-name'"
28
+ * - sanitizePropertyName("123invalid") => "'123invalid'"
29
+ *
30
+ * @param name The property name to sanitize
31
+ * @returns The sanitized property name, quoted if needed
32
+ */
33
+ function sanitizePropertyName(name) {
34
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name}'`;
35
+ }
36
+ /**
37
+ * Sanitizes a type name to ensure it's a valid TypeScript type identifier.
38
+ * Replaces any characters that aren't alphanumeric or underscore with an underscore.
39
+ *
40
+ * For example:
41
+ * - sanitizeTypeName("ValidType") => "ValidType"
42
+ * - sanitizeTypeName("invalid-type") => "invalid_type"
43
+ * - sanitizeTypeName("type.name") => "type_name"
44
+ * - sanitizeTypeName("type/name") => "type_name"
45
+ *
46
+ * This is used to convert operation IDs and response names from the OpenAPI spec
47
+ * into valid TypeScript type names.
48
+ *
49
+ * @param name The type name to sanitize
50
+ * @returns The sanitized type name with invalid characters replaced by underscores
51
+ */
52
+ function sanitizeTypeName(name) {
53
+ return name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+$/, "");
54
+ }
55
+ function specTitle(spec) {
56
+ return camelCase(spec.info.title.toLowerCase().replace(/\s+/g, "-"));
57
+ }
package/package.json CHANGED
@@ -1,84 +1,56 @@
1
1
  {
2
- "name": "react-query-lightbase-codegen",
3
- "description": "Fully typed react query code generation tool based on openApi specifications",
4
- "version": "1.6.4",
5
- "license": "MIT",
6
- "type": "module",
7
- "exports": "./dist/index.js",
8
- "files": [
9
- "src",
10
- "dist"
11
- ],
12
- "keywords": [
13
- "rest",
14
- "client",
15
- "swagger",
16
- "open-api",
17
- "fetch",
18
- "data fetching",
19
- "code-generation",
20
- "react",
21
- "react-query",
22
- "axios",
23
- "vue-query",
24
- "tanstack"
25
- ],
26
- "author": {
27
- "name": "Oliver Winter",
28
- "email": "owinter86@gmail.com"
29
- },
30
- "repository": {
31
- "type": "git",
32
- "url": "https://github.com/lightbasenl/react-query-codegen"
33
- },
34
- "engines": {
35
- "node": ">=14.16"
36
- },
37
- "scripts": {
38
- "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
39
- "tsc": "tsc -p tsconfig.json",
40
- "test": "npm run tsc && node test/script.mjs",
41
- "deploy": "tsc && semantic-release --no-ci",
42
- "updates": "npx npm-check-updates -i",
43
- "prepare": "husky install"
44
- },
45
- "dependencies": {
46
- "case": "1.6.3",
47
- "chalk": "5.3.0",
48
- "js-yaml": "4.1.0",
49
- "lodash": "4.17.21",
50
- "openapi3-ts": "2.0.2",
51
- "swagger2openapi": "7.0.8",
52
- "yamljs": "0.3.0"
53
- },
54
- "devDependencies": {
55
- "@babel/core": "7.24.3",
56
- "@babel/eslint-parser": "7.24.1",
57
- "@commitlint/cli": "^19.2.1",
58
- "@commitlint/config-conventional": "^19.1.0",
59
- "@react-native-community/eslint-config": "3.2.0",
60
- "@semantic-release/changelog": "^6.0.3",
61
- "@semantic-release/git": "^10.0.1",
62
- "@semantic-release/github": "^10.0.2",
63
- "@semantic-release/npm": "^12.0.0",
64
- "@tanstack/react-query": "5.51.23",
65
- "@types/js-yaml": "4.0.9",
66
- "@types/lodash": "4.17.0",
67
- "@types/node": "20.11.30",
68
- "@types/qs": "6.9.14",
69
- "@types/query-string": "6.3.0",
70
- "@types/yamljs": "0.2.34",
71
- "axios": "1.7.3",
72
- "eslint": "8.57.0",
73
- "eslint-plugin-prettier": "5.1.3",
74
- "husky": "^9.0.11",
75
- "prettier": "3.3.3",
76
- "react": "18.2.0",
77
- "semantic-release": "^23.0.6",
78
- "typescript": "5.4.3"
79
- },
80
- "peerDependencies": {
81
- "@tanstack/react-query": ">= 5.20.0",
82
- "axios": "*"
83
- }
2
+ "name": "react-query-lightbase-codegen",
3
+ "version": "2.0.0",
4
+ "license": "MIT",
5
+ "description": "Generate Axios API clients and React Query options from OpenAPI specifications",
6
+ "exports": "./dist/index.js",
7
+ "files": [
8
+ "src",
9
+ "dist"
10
+ ],
11
+ "author": {
12
+ "name": "Oliver Winter",
13
+ "email": "owinter86@gmail.com"
14
+ },
15
+ "keywords": [
16
+ "rest",
17
+ "client",
18
+ "swagger",
19
+ "open-api",
20
+ "fetch",
21
+ "data fetching",
22
+ "code-generation",
23
+ "react",
24
+ "react-query",
25
+ "axios",
26
+ "tanstack"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/lightbasenl/react-query-codegen"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json",
34
+ "example": "ts-node example/index.ts && biome check --fix --unsafe",
35
+ "check": "biome check --write .",
36
+ "updates": "npx npm-check-updates -i",
37
+ "release": "release-it"
38
+ },
39
+ "dependencies": {
40
+ "axios": "^1.7.9",
41
+ "openapi-types": "^12.1.3",
42
+ "yaml": "^2.7.0"
43
+ },
44
+ "devDependencies": {
45
+ "@biomejs/biome": "1.9.4",
46
+ "@tanstack/react-query": "^5.66.9",
47
+ "@types/node": "^22.13.5",
48
+ "release-it": "^17.0.0",
49
+ "ts-node": "^10.9.2",
50
+ "typescript": "^5.7.3"
51
+ },
52
+ "peerDependencies": {
53
+ "@tanstack/react-query": ">= 5.50.0",
54
+ "axios": "^1.7.0"
55
+ }
84
56
  }
package/readme.md ADDED
@@ -0,0 +1,94 @@
1
+ # react-query-lightbase-codegen
2
+
3
+ Generate type-safe Axios API clients and React Query hooks from OpenAPI specifications.
4
+
5
+ ## Features
6
+
7
+ - 🔄 Automatic code generation from OpenAPI/Swagger specs
8
+ - 📝 Type-safe API client functions
9
+ - 🎣 React Query integration with query options
10
+ - 📦 Support for multiple API specifications
11
+ - 📤 Handle multipart/form-data uploads
12
+ - 💪 Full TypeScript support
13
+ - 🔒 Type-safe request/response handling
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install react-query-lightbase-codegen
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Configure Generation
24
+
25
+ Create a script to generate your API code (e.g., `scripts/generate.ts`):
26
+
27
+ ```typescript
28
+ import { codegenerate } from 'react-query-lightbase-codegen';
29
+ await codegenerate({
30
+ specSource: './specs/api.yaml', // or array of specs
31
+ exportDir: './src/generated'
32
+ });
33
+ ```
34
+
35
+ ### 2. Configure API Instance
36
+
37
+ ```typescript
38
+ import axios from "axios";
39
+ import { setApiClient } from "./src/generated/apiClient";
40
+
41
+ const api = axios.create({ baseURL: "https://api.example.com" });
42
+ // Set the API client instance to be used by the generated client functions
43
+ setApiClient(api);
44
+
45
+ ```
46
+
47
+ ### 3. Use Generated Query Options
48
+
49
+ ```typescript
50
+ import { useQuery } from '@tanstack/react-query';
51
+ import { characteristicListQueryOptions } from './src/generated/pokiApi.queryOptions';
52
+
53
+ const { data, isLoading, error } = useQuery(characteristicListQueryOptions());
54
+ ```
55
+
56
+ and more complex queries:
57
+ ```typescript
58
+ import { useQuery } from '@tanstack/react-query';
59
+ import { characteristicListQueryOptions } from './src/generated/pokiApi.queryOptions';
60
+
61
+ const { data, isLoading, error } = useQuery({
62
+ ...characteristicListQueryOptions(),
63
+ select: (data) => data.results.find(item => item.id === itemId),
64
+ });
65
+ ```
66
+
67
+ easier query invalidation:
68
+ ```typescript
69
+ queryClient.invalidateQueries(characteristicListQueryOptions());
70
+ ```
71
+
72
+
73
+ ## Generated Files
74
+
75
+ A single `apiClient.ts` file is generated to be used as a global Axios instance for all generated clients.
76
+
77
+ For each API specification, the following files are generated:
78
+
79
+ - `{api}.schema.ts` - TypeScript types for requests/responses
80
+ - `{api}.client.ts` - Type-safe API client functions
81
+ - `{api}.queryOptions.ts` - React Query integration
82
+
83
+ ### Multiple API Specifications
84
+
85
+ ```typescript
86
+ await codegenerate({
87
+ specSource: [
88
+ './specs/auth-api.yaml',
89
+ './specs/user-api.json',
90
+ 'https://api.example.com/openapi.yaml'
91
+ ],
92
+ exportDir: './src/generated'
93
+ });
94
+ ```
@@ -0,0 +1,25 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import type { OpenAPIConfig } from "../types/config";
3
+
4
+ export async function loadConfig(configPath: string): Promise<OpenAPIConfig> {
5
+ try {
6
+ const configContent = await readFile(configPath, "utf-8");
7
+ const config = JSON.parse(configContent) as OpenAPIConfig;
8
+
9
+ // Validate required fields
10
+ if (!config.specSource) {
11
+ throw new Error("specSource is required in configuration");
12
+ }
13
+
14
+ if (!config.exportDir) {
15
+ throw new Error("exportDir is required in configuration");
16
+ }
17
+
18
+ return config;
19
+ } catch (error) {
20
+ if (error instanceof Error) {
21
+ throw new Error(`Failed to load configuration: ${error.message}`);
22
+ }
23
+ throw new Error("Failed to load configuration: Unknown error");
24
+ }
25
+ }