@accelbyte/codegen 1.0.0-beta.3 → 1.0.0-beta.7

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.
@@ -51,6 +51,9 @@ class CliParser {
51
51
  static isAdmin = () => {
52
52
  return CliParser.instance().argv.admin;
53
53
  };
54
+ static getSnippetOutputPath = () => {
55
+ return CliParser.instance().argv.snippetOutput;
56
+ };
54
57
  }
55
58
 
56
59
  const getImportableVarMap = () => ({
@@ -97,7 +100,18 @@ ${body}
97
100
  \`\`\`
98
101
  `.replace(/, \)/g, ")").trim();
99
102
 
100
- const SERVICE_PREFIXES = ["/iam/", "/gdpr/", "/sessionbrowser/", "/event/", "/admin/", "/public/"];
103
+ const REMOVED_KEYWORDS = [
104
+ "/admin/",
105
+ "/public/",
106
+ "/v1/",
107
+ "/v2/",
108
+ "/v3/",
109
+ "/v4/",
110
+ "/v5/",
111
+ "/namespace/",
112
+ "/namespaces/",
113
+ "/{namespace}/"
114
+ ];
101
115
  class ParserUtils {
102
116
  static generateClassName = (tag) => {
103
117
  const className = _.upperFirst(_.camelCase(tag));
@@ -244,30 +258,34 @@ class ParserUtils {
244
258
  }
245
259
  return parameters.filter((parameter) => parameter.in === "path");
246
260
  }
247
- static mappedMethod = (httpMethod, isForm) => {
248
- if (httpMethod === "get") {
249
- return "fetch";
250
- } else if (httpMethod === "post" && isForm) {
251
- return "post";
252
- } else if (httpMethod === "post") {
253
- return "create";
254
- } else if (httpMethod === "put") {
255
- return "update";
256
- } else if (httpMethod === "patch") {
257
- return "patch";
258
- } else if (httpMethod === "delete") {
259
- return "delete";
260
- }
261
- };
262
- static generateHumanReadableMethod({ path: path2, httpMethod, isForm }) {
263
- let path_ = path2.replace();
264
- SERVICE_PREFIXES.forEach((prefix) => {
261
+ static generateNaturalLangMethod = ({ servicePrefix, path: path2, httpMethod, isForm, existingMethods }) => {
262
+ let path_ = path2;
263
+ path_ = path_.replace(`/${servicePrefix}/`, "/");
264
+ REMOVED_KEYWORDS.forEach((prefix) => {
265
265
  path_ = path_.replace(prefix, "/");
266
266
  });
267
267
  path_ = path_.substring(1);
268
+ const isPlural = httpMethod === "get" && !(path2.slice(-1) === "}");
269
+ if (!isPlural) {
270
+ path_ = replaceAll(path_, "ies/", "y/");
271
+ path_ = replaceAll(path_, "s/", "/");
272
+ if (path_.indexOf("status") < 0) {
273
+ path_ = path_.replace(/ies$/, "y");
274
+ path_ = path_.replace(/s$/, "");
275
+ }
276
+ }
268
277
  const arrLastWords = path_.split("}/");
269
278
  let lastWords = arrLastWords[arrLastWords.length - 1];
270
- lastWords = lastWords.split("/{")[0];
279
+ const extractLastWord = (lastWords_) => {
280
+ let res = lastWords_;
281
+ res = res.split("/{")[0];
282
+ return res;
283
+ };
284
+ lastWords = extractLastWord(lastWords);
285
+ if (lastWords.indexOf("{") >= 0 && arrLastWords.length > 1) {
286
+ lastWords = arrLastWords[arrLastWords.length - 2];
287
+ lastWords = extractLastWord(lastWords);
288
+ }
271
289
  const listBeforeLastWords = [];
272
290
  let foundParam = false;
273
291
  const listByParams = [];
@@ -277,7 +295,7 @@ class ParserUtils {
277
295
  foundParam = true;
278
296
  let param = item.replace("{", "");
279
297
  param = param.replace("}", "");
280
- param = "By" + _.upperFirst(param);
298
+ param = "Byword" + _.upperFirst(param);
281
299
  listByParams.push(param);
282
300
  } else if (!foundParam) {
283
301
  if (lastWords.indexOf(item) === -1) {
@@ -287,39 +305,12 @@ class ParserUtils {
287
305
  foundParam = false;
288
306
  }
289
307
  });
290
- const genPath = _.upperFirst(lastWords) + "/" + listBeforeLastWords.join("/") + "/" + listByParams.join("/");
291
- let res = _.camelCase(ParserUtils.mappedMethod(httpMethod, isForm) + genPath);
292
- res = res.replace("ByNamespace", "");
293
- return res;
294
- }
295
- static generateClassMethod({
296
- path: path2,
297
- endpoint,
298
- httpMethod,
299
- className
300
- }) {
301
- let replacedIdsPath = path2;
302
- if (endpoint.parameters) {
303
- for (const parameter of endpoint.parameters) {
304
- if (parameter.in === "path") {
305
- replacedIdsPath = replacedIdsPath.replace("{" + parameter.name + "}", "By" + ParserUtils.toTitleCaseWord(parameter.name));
306
- replacedIdsPath = replacedIdsPath.replace("/iam", "");
307
- replacedIdsPath = replacedIdsPath.replace("/odin-config", "");
308
- }
309
- }
310
- }
311
- let classMethod = httpMethod + "/" + replacedIdsPath;
312
- classMethod = _.camelCase(classMethod);
313
- classMethod = classMethod.replace("PublicNamespacesByNamespace", "Ns");
314
- classMethod = classMethod.replace("AdminNamespacesByNamespace", "AdminNs");
315
- const searchWord = "NamespacesByNamespace";
316
- const nsExistInsideMethod = classMethod.indexOf(searchWord) > 0 && classMethod.indexOf(searchWord) + searchWord.length < classMethod.length;
317
- const excludedClasses = ["Policies"];
318
- if (nsExistInsideMethod && !excludedClasses.includes(className)) {
319
- classMethod = classMethod.replace(searchWord, "");
320
- }
321
- return classMethod;
322
- }
308
+ const genPath = _.upperFirst(lastWords) + "/" + listBeforeLastWords.join("/") + listByParams.reverse().join("/");
309
+ let generatedMethod = _.camelCase(mappedMethod(httpMethod, isForm) + genPath);
310
+ generatedMethod = replaceAll(generatedMethod, "Byword", "_By");
311
+ generatedMethod = resolveConflicts(path2, generatedMethod, existingMethods);
312
+ return generatedMethod;
313
+ };
323
314
  static filterBodyParams(parameters) {
324
315
  if (Array.isArray(parameters) && parameters.length > 0) {
325
316
  return parameters.filter((parameter) => parameter.in === "body" || parameter.in === "formData");
@@ -439,6 +430,70 @@ class ParserUtils {
439
430
  ${content}`;
440
431
  };
441
432
  }
433
+ const replaceAll = (string, search, replace) => {
434
+ return string.split(search).join(replace);
435
+ };
436
+ const mappedMethod = (httpMethod, isForm) => {
437
+ if (httpMethod === "get") {
438
+ return "fetch";
439
+ } else if (httpMethod === "post" && isForm) {
440
+ return "post";
441
+ } else if (httpMethod === "post") {
442
+ return "create";
443
+ } else if (httpMethod === "put") {
444
+ return "update";
445
+ } else if (httpMethod === "patch") {
446
+ return "patch";
447
+ } else if (httpMethod === "delete") {
448
+ return "delete";
449
+ }
450
+ };
451
+ const resolveConflicts = (path2, generatedMethod, existingMethods) => {
452
+ try {
453
+ testConflict(path2, generatedMethod, existingMethods);
454
+ } catch (e) {
455
+ if (path2.indexOf("/namespaces/") >= 0) {
456
+ generatedMethod += "_ByNS";
457
+ }
458
+ }
459
+ try {
460
+ testConflict(path2, generatedMethod, existingMethods);
461
+ } catch (e) {
462
+ if (path2.indexOf("/v4/") >= 0) {
463
+ generatedMethod += "_v4";
464
+ }
465
+ }
466
+ try {
467
+ testConflict(path2, generatedMethod, existingMethods);
468
+ } catch (e) {
469
+ if (path2.indexOf("/v3/") >= 0) {
470
+ generatedMethod += "_v3";
471
+ }
472
+ }
473
+ try {
474
+ testConflict(path2, generatedMethod, existingMethods);
475
+ } catch (e) {
476
+ if (path2.indexOf("/v2/") >= 0) {
477
+ generatedMethod += "_v2";
478
+ }
479
+ }
480
+ try {
481
+ testConflict(path2, generatedMethod, existingMethods);
482
+ } catch (e) {
483
+ if (path2.indexOf("/admin/") >= 0) {
484
+ generatedMethod += "_admin";
485
+ }
486
+ }
487
+ testConflict(path2, generatedMethod, existingMethods);
488
+ return generatedMethod;
489
+ };
490
+ const testConflict = (path2, generatedMethod, existingMethods) => {
491
+ if (existingMethods[generatedMethod]) {
492
+ const conflictingMethod = { path: path2, generatedMethod };
493
+ throw Error(`Duplicate method conflict in ${JSON.stringify(conflictingMethod)},
494
+ existingMethods: ${JSON.stringify(existingMethods, null, 2)}`);
495
+ }
496
+ };
442
497
 
443
498
  const Schema = z.object({
444
499
  $ref: z.string().nullish(),
@@ -765,7 +820,25 @@ ${exportedTypeString}
765
820
  typeString: refType
766
821
  };
767
822
  } else if (items) {
768
- model2 = this.parseEnumItems(items);
823
+ if (items.type === "array") {
824
+ const ref3 = items.items?.$ref;
825
+ if (ref3) {
826
+ const refType = ParserUtils.parseRefType(ref3);
827
+ this.importClasses.add(refType);
828
+ model2 = {
829
+ schemaString: refType,
830
+ typeString: refType
831
+ };
832
+ } else if (items.items) {
833
+ model2 = this.parseEnumItems(items.items);
834
+ }
835
+ return {
836
+ schemaString: `${schemaAttribute} z.array(z.array(${model2.schemaString}))${schemaRequired}`,
837
+ typeString: `${typeAttribute} ${model2.typeString}[]${typeNullishability}`
838
+ };
839
+ } else {
840
+ model2 = this.parseEnumItems(items);
841
+ }
769
842
  } else {
770
843
  return {
771
844
  schemaString: `${schemaAttribute} z.array(z.any())${schemaRequired}`,
@@ -881,21 +954,32 @@ class CodeGenerator {
881
954
  static getPatchedDir = () => path__default.join(CliParser.getSwaggersOutputPath(), "patched");
882
955
  static getGeneratedPublicFolder = () => `${CliParser.getOutputPath()}/generated-public`;
883
956
  static getGeneratedAdminFolder = () => `${CliParser.getOutputPath()}/generated-admin`;
884
- static getGeneratedSnippetsFolder = () => `${CliParser.getOutputPath()}/../../../../apps/api-explorer-app/src/generated-snippets`;
957
+ static getGeneratedSnippetsFolder = () => `${CliParser.getSnippetOutputPath()}/generated-snippets`;
958
+ static getServicePrefix = (servicePaths) => servicePaths[servicePaths.length - 1].split("/")[1];
885
959
  static iterateApi = async (api) => {
886
960
  const apiBufferByTag = {};
887
961
  const dependenciesByTag = {};
888
962
  const classImports = {};
889
963
  let arrayDefinitions = [];
890
964
  const snippetMap = {};
891
- for (const path2 in api.paths) {
965
+ const mapClassMethods = {};
966
+ const sortedPathsByLength = new Map(Object.entries(api.paths).sort((a, b) => {
967
+ if (a[0].length === b[0].length) {
968
+ return a[0].localeCompare(b[0]);
969
+ } else {
970
+ return a[0].length - b[0].length;
971
+ }
972
+ }));
973
+ const sortedKeys = Array.from(sortedPathsByLength.keys());
974
+ const servicePrefix = CodeGenerator.getServicePrefix(sortedKeys);
975
+ console.log("ServicePrefix", servicePrefix, ", Paths:", sortedKeys);
976
+ for (const [path2, operation] of sortedPathsByLength) {
892
977
  const isAdminEndpoint = path2.indexOf("/admin/") > -1;
893
978
  if (CliParser.isAdmin() && !isAdminEndpoint) {
894
979
  continue;
895
980
  } else if (!CliParser.isAdmin() && isAdminEndpoint) {
896
981
  continue;
897
982
  }
898
- const operation = api.paths[path2];
899
983
  const httpMethods = Object.keys(operation);
900
984
  for (const httpMethod of httpMethods) {
901
985
  const endpoint = await Endpoint.parseAsync(operation[httpMethod]).catch((error) => {
@@ -905,55 +989,63 @@ class CodeGenerator {
905
989
  if (!endpoint.tags) {
906
990
  continue;
907
991
  }
992
+ if (endpoint.deprecated) {
993
+ continue;
994
+ }
908
995
  const [tag] = endpoint.tags;
996
+ mapClassMethods[tag] = mapClassMethods[tag] ? mapClassMethods[tag] : {};
997
+ const isForm = endpoint.consumes && endpoint.consumes[0] === "application/x-www-form-urlencoded";
998
+ const classMethod = ParserUtils.generateNaturalLangMethod({
999
+ servicePrefix,
1000
+ path: path2,
1001
+ httpMethod,
1002
+ isForm,
1003
+ existingMethods: mapClassMethods[tag]
1004
+ });
1005
+ mapClassMethods[tag][classMethod] = `${path2} ${httpMethod}`;
1006
+ snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
909
1007
  const description = endpoint.description;
910
- const isDeprecated = endpoint.deprecated;
911
1008
  const responseClass = ParserUtils.get2xxResponse(endpoint.responses);
912
1009
  const { className, classGenName } = ParserUtils.generateClassName(tag);
913
1010
  classImports[className] = classImports[className] ? classImports[className] : {};
914
- if (!isDeprecated) {
915
- snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
916
- if (responseClass) {
917
- const importTypeClass = ParserUtils.parseRefType(responseClass);
918
- classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
919
- }
920
- if (responseClass && responseClass.endsWith("Array")) {
921
- arrayDefinitions.push(responseClass);
922
- }
923
- const isFormUrlEncoded = ParserUtils.isFormUrlEncoded(httpMethod, endpoint.consumes);
924
- const queryParams = ParserUtils.filterQueryParameters(endpoint.parameters);
925
- const pathParams = ParserUtils.filterPathParams(endpoint.parameters);
926
- let bodyParams = ParserUtils.filterBodyParams(endpoint.parameters);
927
- const isForm = endpoint.consumes && endpoint.consumes[0] === "application/x-www-form-urlencoded";
928
- const classMethod = ParserUtils.generateHumanReadableMethod({ path: path2, httpMethod, isForm });
929
- if (endpoint.requestBody) {
930
- bodyParams = [
931
- {
932
- name: "body",
933
- in: "body",
934
- schema: endpoint.requestBody.content["application/json"].schema
935
- }
936
- ];
937
- }
938
- const pathWithBase = `${api.basePath ?? ""}${path2}`;
939
- const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
940
- classMethod,
941
- description,
942
- httpMethod,
943
- path: pathWithBase,
944
- pathParams,
945
- bodyParams,
946
- queryParams,
947
- isFormUrlEncoded,
948
- responseClass
949
- });
950
- apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
951
- dependenciesByTag[tag] = dependenciesByTag[tag] ? [.../* @__PURE__ */ new Set([...importStatements, ...dependenciesByTag[tag]])] : [...new Set(importStatements)];
952
- snippetMap[path2][httpMethod] = {
953
- web: `<span class='sn-blue'>import</span> axios from 'axios'<br/><span class='sn-blue'>import</span> { <span class='sn-purple'>${classGenName}</span> } <span class='sn-blue'>from</span> <span class='sn-green'>'@accelbyte/sdk'</span><br/><br/><span class='sn-blue'>const</span> axios = <span class='sn-blue'>axios</span>.create({<span class='sn-purple'>clientId</span>, <span class='sn-purple'>redirectURI</span>, <span class='sn-purple'>baseURL</span>})<br/><br/><span class='sn-blue'>new</span> ${classGenName}(<span class='sn-purple'>axios</span>, <span class='sn-purple'>namespace</span>).<span class='method-name'>${snippetString}</span>`,
954
- shell: snippetCurl
955
- };
1011
+ if (responseClass) {
1012
+ const importTypeClass = ParserUtils.parseRefType(responseClass);
1013
+ classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
1014
+ }
1015
+ if (responseClass && responseClass.endsWith("Array")) {
1016
+ arrayDefinitions.push(responseClass);
1017
+ }
1018
+ const queryParams = ParserUtils.filterQueryParameters(endpoint.parameters);
1019
+ const pathWithBase = `${api.basePath ?? ""}${path2}`;
1020
+ const isFormUrlEncoded = ParserUtils.isFormUrlEncoded(httpMethod, endpoint.consumes);
1021
+ const pathParams = ParserUtils.filterPathParams(endpoint.parameters);
1022
+ let bodyParams = ParserUtils.filterBodyParams(endpoint.parameters);
1023
+ if (endpoint.requestBody) {
1024
+ bodyParams = [
1025
+ {
1026
+ name: "body",
1027
+ in: "body",
1028
+ schema: endpoint.requestBody.content["application/json"].schema
1029
+ }
1030
+ ];
956
1031
  }
1032
+ const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
1033
+ classMethod,
1034
+ description,
1035
+ httpMethod,
1036
+ path: pathWithBase,
1037
+ pathParams,
1038
+ bodyParams,
1039
+ queryParams,
1040
+ isFormUrlEncoded,
1041
+ responseClass
1042
+ });
1043
+ apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
1044
+ dependenciesByTag[tag] = dependenciesByTag[tag] ? [.../* @__PURE__ */ new Set([...importStatements, ...dependenciesByTag[tag]])] : [...new Set(importStatements)];
1045
+ snippetMap[path2][httpMethod] = {
1046
+ web: `<span class='sn-blue'>import</span> axios from 'axios'<br/><span class='sn-blue'>import</span> { <span class='sn-purple'>${classGenName}</span> } <span class='sn-blue'>from</span> <span class='sn-green'>'@accelbyte/sdk'</span><br/><br/><span class='sn-blue'>const</span> axios = <span class='sn-blue'>axios</span>.create({<span class='sn-purple'>clientId</span>, <span class='sn-purple'>redirectURI</span>, <span class='sn-purple'>baseURL</span>})<br/><br/><span class='sn-blue'>new</span> ${classGenName}(<span class='sn-purple'>axios</span>, <span class='sn-purple'>namespace</span>).<span class='method-name'>${snippetString}</span>`,
1047
+ shell: snippetCurl
1048
+ };
957
1049
  }
958
1050
  }
959
1051
  arrayDefinitions = [...new Set(arrayDefinitions)];
@@ -965,7 +1057,6 @@ class CodeGenerator {
965
1057
  const parser = new SwaggerParser();
966
1058
  const generatedFolder = CliParser.isAdmin() ? CodeGenerator.getGeneratedAdminFolder() : CodeGenerator.getGeneratedPublicFolder();
967
1059
  const DIST_DIR = `${generatedFolder}/${serviceName}`;
968
- const DIST_DOCS_DIR = path__default.join(DIST_DIR, "docs");
969
1060
  const DIST_DEFINITION_DIR = path__default.join(DIST_DIR, "definitions");
970
1061
  const swaggerFilePath = `${CliParser.getSwaggersOutputPath()}/${swaggerFile}`;
971
1062
  const swaggerPatchedFilePath = `${CodeGenerator.getPatchedDir()}/${swaggerFile}`;
@@ -973,13 +1064,12 @@ class CodeGenerator {
973
1064
  ParserUtils.applyPatchIfExists(swaggerFilePath, swaggerPatchFilePath, swaggerPatchedFilePath, CodeGenerator.getPatchedDir());
974
1065
  const api = await parser.parse(swaggerPatchedFilePath);
975
1066
  const indexImportsSet = /* @__PURE__ */ new Set();
976
- console.log("\n----------\n API name: %s, Version: %s schemes %s", api, api.info.title, api.info.version, api.schemes);
1067
+ console.log("----------\nGenerating API:", { title: api.info.title, version: api.info.version });
977
1068
  ParserUtils.mkdirIfNotExist(DIST_DIR);
978
- ParserUtils.mkdirIfNotExist(DIST_DOCS_DIR);
979
1069
  ParserUtils.mkdirIfNotExist(DIST_DEFINITION_DIR);
980
1070
  ParserUtils.writeVersionFile(DIST_DIR, "Version.ts", serviceName, api.info, BUILD_DATE);
981
1071
  const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap } = await CodeGenerator.iterateApi(api);
982
- if (!CliParser.isAdmin()) {
1072
+ if (CliParser.getSnippetOutputPath()) {
983
1073
  ParserUtils.writeSnippetFile(CodeGenerator.getGeneratedSnippetsFolder(), api.info.title, JSON.stringify(snippetMap, null, 2));
984
1074
  }
985
1075
  const targetSrcFolder = `${CliParser.getOutputPath()}/`;
@@ -1018,7 +1108,7 @@ class CodeGenerator {
1018
1108
  ParserUtils.writeDefinitionFile(DIST_DEFINITION_DIR, arrayClass, buffer);
1019
1109
  indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path__default.join(DIST_DEFINITION_DIR, arrayClass), targetSrcFolder));
1020
1110
  }
1021
- console.log("\n----------\nCOMPLETED.\n----------\n\n");
1111
+ console.log("\nCOMPLETED\n----------\n\n");
1022
1112
  return indexImportsSet;
1023
1113
  };
1024
1114
  }
@@ -1045,7 +1135,7 @@ class SwaggerDownloader {
1045
1135
  });
1046
1136
  });
1047
1137
  request.on("error", (err) => {
1048
- console.log(`SwaggerDownloader dl failed for "${targetFileName}" and "${url}"`, err);
1138
+ console.log(`SwaggerDownloader failed for "${targetFileName}" and "${url}"`, err);
1049
1139
  });
1050
1140
  };
1051
1141
  static main = () => {