fetch-client-generator 1.0.1 → 1.1.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/api-client.js ADDED
@@ -0,0 +1,67 @@
1
+ class ApiClient {
2
+ constructor(baseUrl = '', options = {}) {
3
+ this.baseUrl = baseUrl;
4
+ this.defaultOptions = {
5
+ headers: {
6
+ 'Content-Type': 'application/json',
7
+ ...options.headers
8
+ },
9
+ ...options
10
+ };
11
+ }
12
+
13
+ async request(path, options = {}) {
14
+ const url = this.baseUrl + path;
15
+ const config = {
16
+ ...this.defaultOptions,
17
+ ...options,
18
+ headers: {
19
+ ...this.defaultOptions.headers,
20
+ ...options.headers
21
+ }
22
+ };
23
+
24
+ const response = await fetch(url, config);
25
+
26
+ if (!response.ok) {
27
+ throw new Error(`HTTP error! status: ${response.status}`);
28
+ }
29
+
30
+ const contentType = response.headers.get('content-type');
31
+ if (contentType && contentType.includes('application/json')) {
32
+ return await response.json();
33
+ } else if (contentType && contentType.includes('text/')) {
34
+ return await response.text();
35
+ } else {
36
+ return response;
37
+ }
38
+ }
39
+
40
+ async token() {
41
+ return await this.request('/authentication/token', {
42
+ method: 'POST'
43
+ });
44
+ }
45
+
46
+ async logout(data) {
47
+ return await this.request('/authentication/logout', {
48
+ method: 'POST',
49
+ body: JSON.stringify(data)
50
+ });
51
+ }
52
+
53
+ async getAuthenticationPing() {
54
+ return await this.request('/authentication/ping', {
55
+ method: 'GET'
56
+ });
57
+ }
58
+
59
+ async getApiOrganisations() {
60
+ return await this.request('/api/organisations', {
61
+ method: 'GET'
62
+ });
63
+ }
64
+
65
+ }
66
+
67
+ export default ApiClient;
package/bin/cli.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { program } from 'commander';
4
- import { readFileSync } from 'fs';
4
+ import { readFileSync, writeFileSync } from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
  import path from 'path';
7
+ import { generate } from '../index.js';
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url);
9
10
  const __dirname = path.dirname(__filename);
@@ -20,10 +21,29 @@ program
20
21
  .option('-i, --input <file>', 'input specification file')
21
22
  .option('-o, --output <file>', 'output file path')
22
23
  .action((options) => {
23
- console.log('Generating fetch client...');
24
- console.log('Input:', options.input);
25
- console.log('Output:', options.output);
26
- // Implementation will go here
24
+ if (!options.input) {
25
+ console.error('Error: Input file is required');
26
+ process.exit(1);
27
+ }
28
+
29
+ if (!options.output) {
30
+ console.error('Error: Output file is required');
31
+ process.exit(1);
32
+ }
33
+
34
+ try {
35
+ console.log('Generating fetch client...');
36
+ console.log('Input:', options.input);
37
+ console.log('Output:', options.output);
38
+
39
+ const clientCode = generate(options.input);
40
+ writeFileSync(options.output, clientCode, 'utf8');
41
+
42
+ console.log('✓ Fetch client generated successfully!');
43
+ } catch (error) {
44
+ console.error('Error generating client:', error.message);
45
+ process.exit(1);
46
+ }
27
47
  });
28
48
 
29
49
  program.parse();
package/index.js CHANGED
@@ -1,4 +1,141 @@
1
- // fetch-client-generator main entry point
1
+ import { readFileSync } from 'fs';
2
+
3
+ export function parseOpenAPISpec(specPath) {
4
+ const spec = JSON.parse(readFileSync(specPath, 'utf8'));
5
+
6
+ const endpoints = [];
7
+
8
+ for (const [path, pathItem] of Object.entries(spec.paths)) {
9
+ for (const [method, operation] of Object.entries(pathItem)) {
10
+ const endpoint = {
11
+ path,
12
+ method: method.toUpperCase(),
13
+ operationId: operation.operationId,
14
+ requestBody: operation.requestBody,
15
+ responses: operation.responses,
16
+ tags: operation.tags
17
+ };
18
+ endpoints.push(endpoint);
19
+ }
20
+ }
21
+
22
+ return {
23
+ info: spec.info,
24
+ endpoints,
25
+ schemas: spec.components?.schemas || {}
26
+ };
27
+ }
28
+
29
+ export function generateFetchClient(parsedSpec, options = {}) {
30
+ const { info, endpoints, schemas } = parsedSpec;
31
+ const className = options.className || 'ApiClient';
32
+
33
+ let clientCode = `class ${className} {
34
+ constructor(baseUrl = '', options = {}) {
35
+ this.baseUrl = baseUrl;
36
+ this.defaultOptions = {
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ ...options.headers
40
+ },
41
+ ...options
42
+ };
43
+ }
44
+
45
+ async request(path, options = {}) {
46
+ const url = this.baseUrl + path;
47
+ const config = {
48
+ ...this.defaultOptions,
49
+ ...options,
50
+ headers: {
51
+ ...this.defaultOptions.headers,
52
+ ...options.headers
53
+ }
54
+ };
55
+
56
+ const response = await fetch(url, config);
57
+
58
+ if (!response.ok) {
59
+ throw new Error(\`HTTP error! status: \${response.status}\`);
60
+ }
61
+
62
+ const contentType = response.headers.get('content-type');
63
+ if (contentType && contentType.includes('application/json')) {
64
+ return await response.json();
65
+ } else if (contentType && contentType.includes('text/')) {
66
+ return await response.text();
67
+ } else {
68
+ return response;
69
+ }
70
+ }
71
+
72
+ `;
73
+
74
+ for (const endpoint of endpoints) {
75
+ let methodName = endpoint.operationId;
76
+
77
+ if (!methodName) {
78
+ // Generate camelCase method name from path and method
79
+ const pathParts = endpoint.path.split('/').filter(part => part && !part.startsWith('{'));
80
+ // Convert each part to PascalCase and join
81
+ const pascalParts = pathParts.map(part => part.charAt(0).toUpperCase() + part.slice(1));
82
+ const cleanPath = pascalParts.join('');
83
+ methodName = `${endpoint.method.toLowerCase()}${cleanPath}`;
84
+ }
85
+
86
+ // Convert to camelCase if not already
87
+ methodName = methodName.charAt(0).toLowerCase() + methodName.slice(1);
88
+
89
+ const hasRequestBody = endpoint.requestBody &&
90
+ endpoint.requestBody.content &&
91
+ Object.keys(endpoint.requestBody.content).length > 0;
92
+
93
+ const params = hasRequestBody ? 'data' : '';
94
+ const methodParams = params ? `(${params})` : '()';
95
+
96
+ clientCode += ` async ${methodName}${methodParams} {
97
+ `;
98
+
99
+ if (hasRequestBody) {
100
+ clientCode += ` return await this.request('${endpoint.path}', {
101
+ method: '${endpoint.method}',
102
+ body: JSON.stringify(data)
103
+ });
104
+ `;
105
+ } else {
106
+ clientCode += ` return await this.request('${endpoint.path}', {
107
+ method: '${endpoint.method}'
108
+ });
109
+ `;
110
+ }
111
+
112
+ clientCode += ` }
113
+
114
+ `;
115
+ }
116
+
117
+ clientCode += `}
118
+
119
+ export default ${className};`;
120
+
121
+ return clientCode;
122
+ }
123
+
124
+ export function generate(inputPath, outputPath, options = {}) {
125
+ const parsedSpec = parseOpenAPISpec(inputPath);
126
+ const clientCode = generateFetchClient(parsedSpec, options);
127
+
128
+ if (outputPath) {
129
+ import('fs').then(({ writeFileSync }) => {
130
+ writeFileSync(outputPath, clientCode, 'utf8');
131
+ });
132
+ }
133
+
134
+ return clientCode;
135
+ }
136
+
2
137
  export default {
3
- // Main functionality will be implemented here
138
+ parseOpenAPISpec,
139
+ generateFetchClient,
140
+ generate
4
141
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fetch-client-generator",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A tool for generating fetch-based HTTP client code",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,8 +1,104 @@
1
1
  import { expect } from 'chai';
2
- import fetchClientGenerator from '../index.js';
2
+ import { parseOpenAPISpec, generateFetchClient, generate } from '../index.js';
3
+ import { writeFileSync, unlinkSync } from 'fs';
4
+ import path from 'path';
5
+
6
+ const sampleOpenAPI = {
7
+ "openapi": "3.0.1",
8
+ "info": {
9
+ "title": "Test API",
10
+ "version": "1.0.0"
11
+ },
12
+ "paths": {
13
+ "/users": {
14
+ "get": {
15
+ "operationId": "getUsers",
16
+ "responses": {
17
+ "200": {
18
+ "description": "OK",
19
+ "content": {
20
+ "application/json": {
21
+ "schema": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": "object"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ },
32
+ "post": {
33
+ "operationId": "createUser",
34
+ "requestBody": {
35
+ "content": {
36
+ "application/json": {
37
+ "schema": {
38
+ "type": "object",
39
+ "properties": {
40
+ "name": { "type": "string" }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ },
46
+ "responses": {
47
+ "201": {
48
+ "description": "Created"
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ };
3
55
 
4
56
  describe('fetch-client-generator', () => {
5
- it('should export an object', () => {
6
- expect(fetchClientGenerator).to.be.an('object');
57
+ let testSpecPath;
58
+
59
+ beforeEach(() => {
60
+ testSpecPath = path.join(process.cwd(), 'test-spec.json');
61
+ writeFileSync(testSpecPath, JSON.stringify(sampleOpenAPI, null, 2));
62
+ });
63
+
64
+ afterEach(() => {
65
+ try {
66
+ unlinkSync(testSpecPath);
67
+ } catch (err) {
68
+ // File might not exist
69
+ }
70
+ });
71
+
72
+ describe('parseOpenAPISpec', () => {
73
+ it('should parse OpenAPI specification', () => {
74
+ const result = parseOpenAPISpec(testSpecPath);
75
+
76
+ expect(result).to.have.property('info');
77
+ expect(result).to.have.property('endpoints');
78
+ expect(result).to.have.property('schemas');
79
+ expect(result.info.title).to.equal('Test API');
80
+ expect(result.endpoints).to.have.length(2);
81
+ });
82
+ });
83
+
84
+ describe('generateFetchClient', () => {
85
+ it('should generate fetch client code', () => {
86
+ const parsedSpec = parseOpenAPISpec(testSpecPath);
87
+ const clientCode = generateFetchClient(parsedSpec);
88
+
89
+ expect(clientCode).to.include('class ApiClient');
90
+ expect(clientCode).to.include('async getUsers()');
91
+ expect(clientCode).to.include('async createUser(data)');
92
+ expect(clientCode).to.include('fetch(url, config)');
93
+ });
94
+ });
95
+
96
+ describe('generate', () => {
97
+ it('should generate and return client code', () => {
98
+ const clientCode = generate(testSpecPath);
99
+
100
+ expect(clientCode).to.be.a('string');
101
+ expect(clientCode).to.include('class ApiClient');
102
+ });
7
103
  });
8
104
  });