Skip to content

Commit 341b433

Browse files
authored
Merge pull request #68 from HRKings/feature/overload
Overloads and cleanup
2 parents dfcc9f4 + f84a9e7 commit 341b433

File tree

14 files changed

+5249
-8667
lines changed

14 files changed

+5249
-8667
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88
strategy:
99
matrix:
10-
node-version: ['12', '14', '16']
10+
node-version: ['14', '16', '18']
1111
steps:
1212
- name: Clone repository
1313
uses: actions/checkout@v2

dist/src/index.js

+1,088-1,180
Large diffs are not rendered by default.

dist/src/interfaces/PokeAPIOptions.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
class PokeAPIOptions {
2+
/* eslint-disable default-param-last */
23
constructor(config = {}, cache) {
34
this.protocol = 'https';
45
this.hostName = '://pokeapi.co';

dist/src/utils/Getter.js

+26-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable import/no-unresolved */
22
import axios from 'axios';
33
import handleError from './ErrorHandler.js';
4-
// eslint-disable-next-line consistent-return
54
async function getJSON(values, url,
65
// eslint-disable-next-line no-unused-vars
76
callback) {
@@ -12,38 +11,42 @@ callback) {
1211
try {
1312
// Retrieve possible content from memory-cache
1413
const cachedResult = values.cache.get(url);
14+
// If we have in cache
15+
if (callback && cachedResult) {
16+
// Call callback without errors
17+
callback(cachedResult);
18+
}
19+
// Return the cache
1520
if (cachedResult) {
16-
if (callback) {
17-
// Call callback without errors
18-
callback(cachedResult);
19-
}
2021
return cachedResult;
2122
}
23+
// If we don't have in cache
24+
// get the data from the API
2225
const response = await axios.get(url, options);
23-
// if there is an error
26+
// If there is an error on the request
2427
if (response.status !== 200) {
25-
handleError(response, callback);
28+
throw response;
29+
}
30+
// If everything was good
31+
// set the data
32+
const responseData = response.data;
33+
// Cache the object in memory-cache
34+
// only if cacheLimit > 0
35+
if (values.cacheLimit > 0) {
36+
values.cache.set(url, responseData, values.cacheLimit);
2637
}
27-
else {
28-
// If everything was good
29-
// cache the object in memory-cache
30-
// only if cacheLimit > 0
31-
const responseData = response.data;
32-
if (values.cacheLimit > 0) {
33-
values.cache.set(url, responseData, values.cacheLimit);
34-
}
35-
// If a callback is present
36-
if (callback) {
37-
// Call it, without errors
38-
callback(responseData);
39-
}
40-
else {
41-
return responseData;
42-
}
38+
// If a callback is present
39+
if (callback) {
40+
// Call it, without errors
41+
callback(responseData);
4342
}
43+
return responseData;
4444
}
4545
catch (error) {
4646
handleError(error, callback);
4747
}
48+
// If we return nothing and the error handler fails
49+
// reject the promise
50+
return Promise.reject();
4851
}
4952
export default getJSON;

generator/Main.ts

+124-51
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from 'fs';
22
import path from 'path';
3-
import { Project } from 'ts-morph';
3+
import { MethodDeclarationStructure, OptionalKind, Project } from 'ts-morph';
44

55
import endpoints from '../src/utils/Endpoints.js';
66
import rootEndpoints from '../src/utils/RootEndpoints.js';
@@ -115,34 +115,40 @@ console.timeLog(mainLabel, ' - Base generated, generating methods...');
115115

116116
// Add the get generic resource array method
117117
const getResourceCode = `try {
118+
// Fail if the endpoint is not supplied
118119
if (!endpoint) {
119120
throw new Error('Param "endpoint" is required needs to be a string or array of strings');
120121
}
121122
123+
// Fail if the input types aren't accepted
124+
if (!Array.isArray(endpoint) && typeof endpoint !== 'string') {
125+
throw new Error('Param "endpoint" needs to be a string or array of strings');
126+
}
127+
128+
/// If the user has submitted a string, return the JSON promise
122129
if (typeof endpoint === 'string') {
123130
return getJSON<any>(this.options, endpoint, callback);
124-
} else if (typeof endpoint === 'object') {
125-
const mapper = async (endpoints: string) => {
126-
const queryRes = await getJSON<any>(this.options, endpoints);
127-
return queryRes;
128-
};
131+
}
129132
130-
// Fetch data asynchronously to be faster
131-
const mappedResults = await pMap(endpoint, mapper, { concurrency: 4 });
133+
// If the user has submitted an Array return a new promise which will resolve when all getJSON calls are ended
134+
const mapper = async (endpoints: string) => {
135+
const queryRes = await getJSON<any>(this.options, endpoints);
136+
return queryRes;
137+
};
132138
133-
if (callback) {
134-
callback(mappedResults);
135-
}
139+
// Fetch data asynchronously to be faster
140+
const mappedResults = await pMap(endpoint, mapper, { concurrency: 4 });
136141
137-
return mappedResults;
138-
} else {
139-
throw new Error('Param "endpoint" needs to be a string or array of strings');
142+
if (callback) {
143+
callback(mappedResults);
140144
}
145+
146+
return mappedResults;
141147
} catch (error) {
142148
handleError(error, callback);
143149
}`;
144150

145-
let methodStructure = {
151+
let methodStructure: OptionalKind<MethodDeclarationStructure> = {
146152
name: 'getResource',
147153
isAsync: true,
148154
parameters: [{
@@ -155,6 +161,36 @@ let methodStructure = {
155161
hasQuestionToken: true,
156162
}],
157163
returnType: 'Promise<any | any[]>',
164+
overloads: [
165+
{
166+
parameters: [
167+
{
168+
name: 'endpoint',
169+
type: 'string',
170+
},
171+
{
172+
name: 'callback',
173+
type: '(result: any, error?: any) => any',
174+
hasQuestionToken: true,
175+
},
176+
],
177+
returnType: 'Promise<any>',
178+
},
179+
{
180+
parameters: [
181+
{
182+
name: 'endpoint',
183+
type: 'string[]',
184+
},
185+
{
186+
name: 'callback',
187+
type: '(result: any[], error?: any) => any',
188+
hasQuestionToken: true,
189+
},
190+
],
191+
returnType: 'Promise<any[]>',
192+
},
193+
],
158194
};
159195

160196
pokeApiClass.addMethod(methodStructure).setBodyText(getResourceCode);
@@ -182,6 +218,11 @@ for (const [methodName, endpoint, jsdocs] of endpoints) {
182218
const inputParam = methodName.match(/ByName$/) ? 'nameOrId' : 'id';
183219
const inputParamType = methodName.match(/ByName$/) ? 'string | number | Array<string | number>' : 'number | number[]';
184220

221+
const singleParamType = methodName.match(/ByName$/) ? 'string | number' : 'number';
222+
const multipleParamType = methodName.match(/ByName$/) ? 'Array<string | number>' : 'number[]';
223+
224+
const returnType = `PokeAPITypes.${apiMap[endpoint]}`;
225+
185226
methodStructure = {
186227
name: methodName,
187228
isAsync: true,
@@ -191,50 +232,80 @@ for (const [methodName, endpoint, jsdocs] of endpoints) {
191232
},
192233
{
193234
name: 'callback',
194-
type: `(result: PokeAPITypes.${apiMap[endpoint]} | PokeAPITypes.${apiMap[endpoint]}[], error?: any) => any`,
235+
type: `((result: ${returnType}, error?: any) => any) & ((result: ${returnType}[], error?: any) => any)`,
195236
hasQuestionToken: true,
196237
}],
197-
returnType: `Promise<PokeAPITypes.${apiMap[endpoint]} | PokeAPITypes.${apiMap[endpoint]}[]>`,
238+
returnType: `Promise<${returnType} | ${returnType}[]>`,
239+
overloads: [
240+
{
241+
parameters: [
242+
{
243+
name: inputParam,
244+
type: singleParamType,
245+
},
246+
{
247+
name: 'callback',
248+
type: `(result: ${returnType}, error?: any) => any`,
249+
hasQuestionToken: true,
250+
},
251+
],
252+
returnType: `Promise<${returnType}>`,
253+
},
254+
{
255+
parameters: [
256+
{
257+
name: inputParam,
258+
type: multipleParamType,
259+
},
260+
{
261+
name: 'callback',
262+
type: `(result: ${returnType}[], error?: any) => any`,
263+
hasQuestionToken: true,
264+
},
265+
],
266+
returnType: `Promise<${returnType}[]>`,
267+
},
268+
],
198269
};
199270

200271
const generatedMethod = pokeApiClass.addMethod(methodStructure).setBodyText(`try {
201-
if (${inputParam}) {
202-
// If the user has submitted a Name or an ID, return the JSON promise
203-
if (typeof ${inputParam} === 'number' || typeof ${inputParam} === 'string') {
204-
return getJSON<PokeAPITypes.${apiMap[endpoint]}>(this.options, \`\${this.options.protocol}\${this.options.hostName}\${this.options.versionPath}${endpoint}/\${${inputParam}}/\`, callback);
205-
}
272+
// Fail if the param is not supplied
273+
if (!${inputParam}) {
274+
throw new Error('Param "${inputParam}" is required (Must be a ${methodName.match(/ByName$/) ? 'string, array of strings or array of string and/or numbers' : 'number or array of numbers'} )');
275+
}
206276
207-
// If the user has submitted an Array return a new promise which will
208-
// resolve when all getJSON calls are ended
209-
else if (typeof ${inputParam} === 'object') {
210-
const mapper = async (${inputParam}s: ${inputParamType}) => {
211-
const queryRes = await getJSON<PokeAPITypes.${apiMap[endpoint]}>(this.options, \`\${this.options.protocol}\${this.options.hostName}\${this.options.versionPath}${endpoint}/\${${inputParam}s}/\`);
212-
return queryRes;
213-
};
277+
// Fail if the input types aren't accepted
278+
if (!Array.isArray(${inputParam}) && typeof ${inputParam} !== 'number' && typeof ${inputParam} !== 'string') {
279+
throw new Error('Param "${inputParam}" must be a ${methodName.match(/ByName$/) ? 'string, array of strings or array of string and/or numbers' : 'number or array of numbers'}');
280+
}
214281
215-
// Fetch data asynchronously to be faster
216-
const mappedResults = await pMap(${inputParam}, mapper, { concurrency: 4 });
282+
// If the user has submitted a Name or an ID, return the JSON promise
283+
if (typeof ${inputParam} === 'number' || typeof ${inputParam} === 'string') {
284+
return getJSON<${returnType}>(this.options, \`\${this.options.protocol}\${this.options.hostName}\${this.options.versionPath}${endpoint}/\${${inputParam}}/\`, callback);
285+
}
217286
218-
if (callback) {
219-
callback(mappedResults);
220-
}
287+
// If the user has submitted an Array return a new promise which will resolve when all getJSON calls are ended
288+
const mapper = async (${inputParam}s: ${inputParamType}) => {
289+
const queryRes = await getJSON<${returnType}>(this.options, \`\${this.options.protocol}\${this.options.hostName}\${this.options.versionPath}${endpoint}/\${${inputParam}s}/\`);
290+
return queryRes;
291+
};
221292
222-
return mappedResults;
223-
} else {
224-
throw new Error('Param "${inputParam}" must be a ${methodName.match(/ByName$/) ? 'string, array of strings or array of string and/or numbers' : 'number or array of numbers'}');
225-
}
226-
} else {
227-
throw new Error('Param "${inputParam}" is required (Must be a ${methodName.match(/ByName$/) ? 'string, array of strings or array of string and/or numbers' : 'number or array of numbers'} )');
293+
// Fetch data asynchronously to be faster
294+
const mappedResults = await pMap(${inputParam}, mapper, { concurrency: 4 });
295+
296+
// Invoke the callback if we have one
297+
if (callback) {
298+
callback(mappedResults);
228299
}
229-
} catch (error) {
300+
301+
return mappedResults;
302+
} catch (error) {
230303
handleError(error, callback);
231-
}`);
304+
}`);
232305

233306
// Add the declaration to the types file
234-
// Sanitizing the namespace and remove the async keyword
307+
// Removing the async keyword
235308
methodStructure.isAsync = false;
236-
methodStructure.parameters[1].type = methodStructure.parameters[1].type.replace(/PokeAPITypes/g, 'PokeAPI');
237-
methodStructure.returnType = methodStructure.returnType.replace(/PokeAPITypes/g, 'PokeAPI');
238309
const declaredMethod = declarationClass.addMethod(methodStructure);
239310

240311
// If the method has a JSDoc, add it
@@ -263,7 +334,7 @@ for (const [method, rawEndpoint, jsdocs] of rootEndpoints) {
263334
}
264335

265336
// Infer the return type from the name
266-
const returnType = apiMap[endpoint].includes('NamedList') ? 'NamedAPIResourceList' : 'APIResourceList';
337+
const returnType = `PokeAPITypes.${apiMap[endpoint].includes('NamedList') ? 'NamedAPIResourceList' : 'APIResourceList'}`;
267338

268339
const methodStructure = {
269340
name: method,
@@ -275,10 +346,10 @@ for (const [method, rawEndpoint, jsdocs] of rootEndpoints) {
275346
},
276347
{
277348
name: 'callback',
278-
type: `(result: PokeAPITypes.${returnType}, error?: any) => any`,
349+
type: `(result: ${returnType}, error?: any) => any`,
279350
hasQuestionToken: true,
280351
}],
281-
returnType: `Promise<PokeAPITypes.${returnType}>`,
352+
returnType: `Promise<${returnType}>`,
282353
};
283354

284355
const generatedMethod = pokeApiClass.addMethod(methodStructure).setBodyText(`try {
@@ -300,10 +371,8 @@ for (const [method, rawEndpoint, jsdocs] of rootEndpoints) {
300371
}`);
301372

302373
// Add the declaration to the types file
303-
// Sanitizing the namespace and remove the async keyword
374+
// Removing the async keyword
304375
methodStructure.isAsync = false;
305-
methodStructure.parameters[1].type = methodStructure.parameters[1].type.replace(/PokeAPITypes/g, 'PokeAPI');
306-
methodStructure.returnType = methodStructure.returnType.replace(/PokeAPITypes/g, 'PokeAPI');
307376
const declaredMethod = declarationClass.addMethod(methodStructure);
308377

309378
// If the method has a JSDoc, add it
@@ -378,6 +447,10 @@ declarationClass.getParentModule().addExportAssignment({
378447
expression: 'PokeAPI',
379448
});
380449

450+
// Sanitize the namespaces of the declaration file and format it again
451+
declarationClass.replaceWithText(declarationClass.getFullText().replace(/PokeAPITypes/g, 'PokeAPI'));
452+
declarationClass.formatText();
453+
381454
// Top level async function
382455
(async () => {
383456
// Save and compile to JS

generator/TypesGenerator.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async function generateFinalFile(types: string) {
4141
* Code by: HRKings <https://github.com/HRKings/>
4242
* And: Christian Garza <https://github.com/C-Garza/>
4343
* Code inspired by: Mudkip <https://github.com/mudkipme/>
44-
* Execute \`npm run generate\` to regenerate
44+
* Execute \`npm run generate:types\` to regenerate
4545
*/`, { overwrite: true });
4646

4747
// Create the root module
@@ -58,12 +58,14 @@ async function generateFinalFile(types: string) {
5858
namespace.setBodyText(types);
5959

6060
// Remove interfaces that are wrongly generated
61-
namespace.getInterface('EvolutionChainElement').remove();
62-
namespace.getInterface('GenerationElement').remove();
63-
namespace.getInterface('VersionGroupNamedList').remove();
61+
namespace.getInterface('EvolutionChainElement')?.remove();
62+
namespace.getInterface('ResultElement')?.remove();
63+
namespace.getInterface('GenerationElement')?.remove();
64+
namespace.getInterface('VersionGroupNamedList')?.remove();
6465

6566
// Replace the wrong definitions with the correct ones
6667
namespace.setBodyText(namespace.getBodyText()
68+
.replace(/ResultElement/g, 'APIResource')
6769
.replace(/EvolutionChainElement/g, 'APIResource')
6870
.replace(/GenerationElement/g, 'NamedAPIResource'));
6971

0 commit comments

Comments
 (0)