@aligent/nx-openapi 0.1.2 → 1.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.
- package/package.json +1 -1
- package/src/generators/client/client-specific-files/client.ts.template +53 -0
- package/src/generators/client/files/README.md.template +29 -1
- package/src/generators/client/files/eslint.config.mjs.template +3 -0
- package/src/generators/client/files/src/index.ts.template +1 -22
- package/src/generators/client/files/tsconfig.json.template +1 -1
- package/src/generators/client/generator.js +16 -10
- package/src/generators/client/schema.json +2 -1
- package/src/helpers/generate-openapi-types.js +0 -1
- package/src/helpers/utilities.d.ts +20 -2
- package/src/helpers/utilities.js +36 -4
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example client demonstrating how to use the generated API types.
|
|
3
|
+
* Adjust middlewares usage according to your need.
|
|
4
|
+
*
|
|
5
|
+
* - Check out Aligent's middlewares: https://github.com/aligent/microservice-development-utilities
|
|
6
|
+
* - For more information about openapi-fetch middlewares: https://openapi-ts.dev/openapi-fetch/middleware-auth#middleware
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
apiKeyAuthMiddleware,
|
|
10
|
+
fetchSsmParams,
|
|
11
|
+
retryMiddleware,
|
|
12
|
+
} from '@aligent/microservice-util-lib';
|
|
13
|
+
import createClient, { Client, ClientOptions } from 'openapi-fetch';
|
|
14
|
+
import { paths } from './generated-types';
|
|
15
|
+
|
|
16
|
+
export class <%= className %> {
|
|
17
|
+
private credential: string | null = null;
|
|
18
|
+
public readonly client: Client<paths, `${string}/${string}`>;
|
|
19
|
+
|
|
20
|
+
constructor(options: ClientOptions, credentialPath: string) {
|
|
21
|
+
this.client = createClient<paths>(options);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The order in which middleware are registered matters.
|
|
25
|
+
* - For requests, onRequest() will be called in the order registered
|
|
26
|
+
* - For responses, onResponse() will be called in reverse order.
|
|
27
|
+
*/
|
|
28
|
+
this.client.use(
|
|
29
|
+
apiKeyAuthMiddleware({
|
|
30
|
+
header: 'Authorization',
|
|
31
|
+
value: async () => {
|
|
32
|
+
if (!this.credential) {
|
|
33
|
+
const param = await fetchSsmParams(credentialPath);
|
|
34
|
+
if (!param?.Value) {
|
|
35
|
+
throw new Error('Unable to fetch API client credential');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.credential = param.Value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `Bearer ${this.credential}`;
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
this.client.use(
|
|
47
|
+
retryMiddleware({
|
|
48
|
+
onRetry: ({ attempt, error }) =>
|
|
49
|
+
console.log(`Retrying...${attempt} due to ${String(error)}`),
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
# Generated OpenAPI REST API Clients
|
|
2
|
+
|
|
3
|
+
This folder contains TypeScript API clients generated from OpenAPI specifications using Nx generators and `openapi-typescript`. Each client exposes strongly typed request and response types under `generated-types` and a ready-to-use `openapi-fetch` client class.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
- Import the generated client into your code.
|
|
7
|
+
- Instantiate the client with appropriate ClientOptions (base URL, fetch implementation, etc.) and call the typed endpoints.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
```typescript
|
|
11
|
+
import { MyApiClient } from '@clients'; // Adjust the import name as necessary
|
|
12
|
+
|
|
13
|
+
const client = new MyApiClient(
|
|
14
|
+
{ baseUrl: 'https://my-api-client.base-url', signal: AbortSignal.timeout(30000) },
|
|
15
|
+
'/my/api/client/access-token/path'
|
|
16
|
+
).client;
|
|
17
|
+
|
|
18
|
+
// Example of calling a typed endpoint
|
|
19
|
+
async function fetchData() {
|
|
20
|
+
try {
|
|
21
|
+
const response = await client.GET('/path/to/endpoint'); // Replace with actual endpoint method
|
|
22
|
+
console.log(response);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error('Error fetching data:', error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fetchData();
|
|
29
|
+
```
|
|
@@ -1,22 +1 @@
|
|
|
1
|
-
//
|
|
2
|
-
import { paths } from '../generated';
|
|
3
|
-
import createClient from 'openapi-fetch';
|
|
4
|
-
|
|
5
|
-
// This type is an example of what is initialised using the types generated in the paths interface which is created when generation occurs.
|
|
6
|
-
// Its worth looking into that paths interface, to see the the types that were generated for your client.
|
|
7
|
-
type ExampleResponse =
|
|
8
|
-
paths['/customers']['get']['responses']['200']['content']['application/json'];
|
|
9
|
-
|
|
10
|
-
// Using openapi-fetch we can create a fully typed REST client by passing in paths as a generic.
|
|
11
|
-
// If you wish however, you can use any api client you want (axios, basic fetch etc.) and use the paths separately to maintain type safety in your client.
|
|
12
|
-
const client = createClient<paths>({
|
|
13
|
-
baseUrl: '',
|
|
14
|
-
signal: AbortSignal.timeout(10000),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// Client getters are then fully typed. Try deleting '/customers' and seeing what routes you can use!
|
|
18
|
-
const response = client.GET('/customers', {
|
|
19
|
-
params: {
|
|
20
|
-
query: {},
|
|
21
|
-
},
|
|
22
|
-
});
|
|
1
|
+
// When a client is generated, its added as an export to this index file. This makes sure that your entire application has access to all of your clients.
|
|
@@ -6,7 +6,7 @@ const generate_openapi_types_1 = require("../../helpers/generate-openapi-types")
|
|
|
6
6
|
const utilities_1 = require("../../helpers/utilities");
|
|
7
7
|
const VALID_EXTENSIONS = ['yaml', 'yml', 'json'];
|
|
8
8
|
async function clientGenerator(tree, options) {
|
|
9
|
-
const { name, schemaPath, importPath = `@clients
|
|
9
|
+
const { name, schemaPath, importPath = `@clients`, skipValidate, override } = options;
|
|
10
10
|
const ext = schemaPath.split('.').pop() || '';
|
|
11
11
|
if (!VALID_EXTENSIONS.includes(ext)) {
|
|
12
12
|
throw new Error(`Invalid schema file extension: ${ext}`);
|
|
@@ -17,23 +17,29 @@ async function clientGenerator(tree, options) {
|
|
|
17
17
|
throw new Error('Schema validation failed!');
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
const projectRoot = `clients
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
if (!
|
|
25
|
-
|
|
26
|
-
return;
|
|
20
|
+
const projectRoot = `clients`;
|
|
21
|
+
const apiClientDest = `${projectRoot}/src/${name}`;
|
|
22
|
+
const schemaDest = `${apiClientDest}/schema.${ext}`;
|
|
23
|
+
const typesDest = `${apiClientDest}/generated-types.ts`;
|
|
24
|
+
if (!override && tree.exists(apiClientDest)) {
|
|
25
|
+
throw new Error(`Directory "${name}" already exists. If you want to override the current api client in this directory use "--override"`);
|
|
27
26
|
}
|
|
27
|
+
const isNewProject = (0, utilities_1.attemptToAddProjectConfiguration)(tree, projectRoot);
|
|
28
28
|
await (0, generate_openapi_types_1.copySchema)(tree, schemaDest, schemaPath);
|
|
29
29
|
await (0, generate_openapi_types_1.generateOpenApiTypes)(tree, schemaDest, typesDest);
|
|
30
30
|
if (isNewProject) {
|
|
31
31
|
devkit_1.logger.info(`Creating new project at ${projectRoot}`);
|
|
32
|
-
// Generate other files
|
|
33
32
|
(0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, './files'), projectRoot, options);
|
|
34
|
-
// Add the project to the tsconfig paths so it can be imported by namespace
|
|
35
33
|
(0, utilities_1.addTsConfigPath)(tree, importPath, [(0, devkit_1.joinPathFragments)(projectRoot, './src', 'index.ts')]);
|
|
36
34
|
}
|
|
35
|
+
// Generate the files for the specific new client
|
|
36
|
+
(0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, './client-specific-files'), apiClientDest, {
|
|
37
|
+
className: (0, utilities_1.toClassName)(name),
|
|
38
|
+
});
|
|
39
|
+
// Append to index file for imports
|
|
40
|
+
(0, utilities_1.appendToIndexFile)(tree, projectRoot, name);
|
|
37
41
|
await (0, devkit_1.formatFiles)(tree);
|
|
42
|
+
devkit_1.logger.info(`Successfully generated ${name} API client`);
|
|
43
|
+
devkit_1.logger.info(`Next step: Run "nx affected -t lint" to fix any linting issues that may arise from the generated code.`);
|
|
38
44
|
}
|
|
39
45
|
exports.default = clientGenerator;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"name": {
|
|
8
8
|
"type": "string",
|
|
9
|
+
"pattern": "^[a-z0-9-]+$",
|
|
9
10
|
"description": "Name of the api client.",
|
|
10
11
|
"$default": {
|
|
11
12
|
"$source": "argv",
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
},
|
|
38
39
|
"override": {
|
|
39
40
|
"type": "boolean",
|
|
40
|
-
"description": "Override existing
|
|
41
|
+
"description": "Override existing files during generation",
|
|
41
42
|
"default": false
|
|
42
43
|
}
|
|
43
44
|
},
|
|
@@ -103,7 +103,6 @@ async function copySchema(tree, destination, schemaPath) {
|
|
|
103
103
|
async function validateSchema(path) {
|
|
104
104
|
let hasError = false;
|
|
105
105
|
try {
|
|
106
|
-
// TODO: MI-203 - Support private schema endpoint
|
|
107
106
|
const config = await (0, openapi_core_1.loadConfig)();
|
|
108
107
|
const results = await (0, openapi_core_1.lint)({ ref: path, config });
|
|
109
108
|
results.forEach(result => {
|
|
@@ -8,10 +8,10 @@ import { Tree } from '@nx/devkit';
|
|
|
8
8
|
* @param {Tree} tree - The file system tree representing the current project.
|
|
9
9
|
* @param {string} name - The name of the project to add.
|
|
10
10
|
* @param {string} projectRoot - The root directory of the project.
|
|
11
|
-
* @returns
|
|
11
|
+
* @returns - Whether project configuration was added successfully, or it already exists.
|
|
12
12
|
* @throws {Error} If an error occurs that is not related to the project already existing.
|
|
13
13
|
*/
|
|
14
|
-
export declare function attemptToAddProjectConfiguration(tree: Tree,
|
|
14
|
+
export declare function attemptToAddProjectConfiguration(tree: Tree, projectRoot: string): boolean;
|
|
15
15
|
/**
|
|
16
16
|
* Adds a new path mapping to the `paths` property in the root TypeScript configuration file.
|
|
17
17
|
*
|
|
@@ -24,3 +24,21 @@ export declare function attemptToAddProjectConfiguration(tree: Tree, name: strin
|
|
|
24
24
|
* @throws {Error} If the import path already exists in the `paths` property.
|
|
25
25
|
*/
|
|
26
26
|
export declare function addTsConfigPath(tree: Tree, importPath: string, lookupPaths: string[]): void;
|
|
27
|
+
/**
|
|
28
|
+
* Appends a new export statement to the index file.
|
|
29
|
+
*
|
|
30
|
+
* This function reads the content of the index file and appends a new export statement
|
|
31
|
+
* for the specified client.
|
|
32
|
+
*
|
|
33
|
+
* @param {Tree} tree - The file system tree representing the current project.
|
|
34
|
+
* @param {string} clientName - The name of the client to export.
|
|
35
|
+
*/
|
|
36
|
+
export declare function appendToIndexFile(tree: Tree, projectRoot: string, clientName: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Convert a lower-case alphanumeric string (may include hyphens) into a PascalCase string.
|
|
39
|
+
*
|
|
40
|
+
* @param input - The input string to convert.
|
|
41
|
+
* @example:
|
|
42
|
+
* - "my-client" -> "MyClient"
|
|
43
|
+
*/
|
|
44
|
+
export declare function toClassName(input: string): string;
|
package/src/helpers/utilities.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.attemptToAddProjectConfiguration = attemptToAddProjectConfiguration;
|
|
4
4
|
exports.addTsConfigPath = addTsConfigPath;
|
|
5
|
+
exports.appendToIndexFile = appendToIndexFile;
|
|
6
|
+
exports.toClassName = toClassName;
|
|
5
7
|
const devkit_1 = require("@nx/devkit");
|
|
6
8
|
/**
|
|
7
9
|
* Attempts to add a new project configuration to the workspace.
|
|
@@ -12,17 +14,17 @@ const devkit_1 = require("@nx/devkit");
|
|
|
12
14
|
* @param {Tree} tree - The file system tree representing the current project.
|
|
13
15
|
* @param {string} name - The name of the project to add.
|
|
14
16
|
* @param {string} projectRoot - The root directory of the project.
|
|
15
|
-
* @returns
|
|
17
|
+
* @returns - Whether project configuration was added successfully, or it already exists.
|
|
16
18
|
* @throws {Error} If an error occurs that is not related to the project already existing.
|
|
17
19
|
*/
|
|
18
|
-
function attemptToAddProjectConfiguration(tree,
|
|
20
|
+
function attemptToAddProjectConfiguration(tree, projectRoot) {
|
|
19
21
|
try {
|
|
20
|
-
(0, devkit_1.addProjectConfiguration)(tree,
|
|
22
|
+
(0, devkit_1.addProjectConfiguration)(tree, 'clients', {
|
|
21
23
|
root: projectRoot,
|
|
22
24
|
projectType: 'library',
|
|
23
25
|
sourceRoot: `${projectRoot}/src`,
|
|
24
26
|
targets: {},
|
|
25
|
-
tags: ['
|
|
27
|
+
tags: ['clients'],
|
|
26
28
|
});
|
|
27
29
|
return true;
|
|
28
30
|
}
|
|
@@ -69,3 +71,33 @@ function addTsConfigPath(tree, importPath, lookupPaths) {
|
|
|
69
71
|
return json;
|
|
70
72
|
});
|
|
71
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Appends a new export statement to the index file.
|
|
76
|
+
*
|
|
77
|
+
* This function reads the content of the index file and appends a new export statement
|
|
78
|
+
* for the specified client.
|
|
79
|
+
*
|
|
80
|
+
* @param {Tree} tree - The file system tree representing the current project.
|
|
81
|
+
* @param {string} clientName - The name of the client to export.
|
|
82
|
+
*/
|
|
83
|
+
function appendToIndexFile(tree, projectRoot, clientName) {
|
|
84
|
+
const indexPath = `${projectRoot}/src/index.ts`;
|
|
85
|
+
const newLine = `export * from "./${clientName}/client";\n`;
|
|
86
|
+
const indexContent = tree.read(indexPath, 'utf-8');
|
|
87
|
+
tree.write(indexPath, indexContent + newLine);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Convert a lower-case alphanumeric string (may include hyphens) into a PascalCase string.
|
|
91
|
+
*
|
|
92
|
+
* @param input - The input string to convert.
|
|
93
|
+
* @example:
|
|
94
|
+
* - "my-client" -> "MyClient"
|
|
95
|
+
*/
|
|
96
|
+
function toClassName(input) {
|
|
97
|
+
return input
|
|
98
|
+
.trim()
|
|
99
|
+
.toLowerCase()
|
|
100
|
+
.split('-')
|
|
101
|
+
.map(c => c.charAt(0).toUpperCase() + c.slice(1))
|
|
102
|
+
.join('');
|
|
103
|
+
}
|