@accelbyte/codegen 1.0.0-alpha.9 → 1.0.0-beta.3

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.
@@ -5,8 +5,8 @@ import fs__default from 'fs';
5
5
  import * as path from 'path';
6
6
  import path__default from 'path';
7
7
  import SwaggerParser from '@apidevtools/swagger-parser';
8
- import _ from 'lodash';
9
8
  import { applyPatch } from 'fast-json-patch';
9
+ import _ from 'lodash';
10
10
  import * as https from 'https';
11
11
 
12
12
  const SwaggersConfig = z.array(z.array(z.string()));
@@ -78,15 +78,15 @@ const generateImports = (body, importStatements) => {
78
78
  ${importStatements.sort().join("\n")}`;
79
79
  };
80
80
  const templateClass = (className, body, importStatements) => `/**
81
- * DON'T EDIT THIS FILE, it is AUTO GENERATED
82
- */
83
- ${generateImports(body, importStatements)}
81
+ * DON'T EDIT THIS FILE, it is AUTO GENERATED
82
+ */
83
+ ${generateImports(body, importStatements)}
84
84
 
85
- export class ${className} {
86
- // @ts-ignore
87
- constructor(private axiosInstance: AxiosInstance, private namespace: string, private cache = false) {}
85
+ export class ${className} {
86
+ // @ts-ignore
87
+ constructor(private axiosInstance: AxiosInstance, private namespace: string, private cache = false) {}
88
88
  ${body}
89
- }
89
+ }
90
90
  `;
91
91
 
92
92
  const templateJsdocFile = (apiName, body) => `
@@ -97,7 +97,13 @@ ${body}
97
97
  \`\`\`
98
98
  `.replace(/, \)/g, ")").trim();
99
99
 
100
+ const SERVICE_PREFIXES = ["/iam/", "/gdpr/", "/sessionbrowser/", "/event/", "/admin/", "/public/"];
100
101
  class ParserUtils {
102
+ static generateClassName = (tag) => {
103
+ const className = _.upperFirst(_.camelCase(tag));
104
+ const classGenName = CliParser.isAdmin() ? className + "Admin$" : className + "$";
105
+ return { className, classGenName };
106
+ };
101
107
  static parseQueryParamAttributeDefault = (definition) => {
102
108
  const attrName = definition.name.slice(definition.name.lastIndexOf(".") + 1);
103
109
  const defaultValue = definition.type === "string" ? `'${definition.default}'` : definition.default;
@@ -238,6 +244,54 @@ class ParserUtils {
238
244
  }
239
245
  return parameters.filter((parameter) => parameter.in === "path");
240
246
  }
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) => {
265
+ path_ = path_.replace(prefix, "/");
266
+ });
267
+ path_ = path_.substring(1);
268
+ const arrLastWords = path_.split("}/");
269
+ let lastWords = arrLastWords[arrLastWords.length - 1];
270
+ lastWords = lastWords.split("/{")[0];
271
+ const listBeforeLastWords = [];
272
+ let foundParam = false;
273
+ const listByParams = [];
274
+ const pathElements = path_.split("/");
275
+ pathElements.slice().reverse().forEach((item) => {
276
+ if (item.indexOf("}") >= 0) {
277
+ foundParam = true;
278
+ let param = item.replace("{", "");
279
+ param = param.replace("}", "");
280
+ param = "By" + _.upperFirst(param);
281
+ listByParams.push(param);
282
+ } else if (!foundParam) {
283
+ if (lastWords.indexOf(item) === -1) {
284
+ listBeforeLastWords.push(item);
285
+ }
286
+ } else {
287
+ foundParam = false;
288
+ }
289
+ });
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
+ }
241
295
  static generateClassMethod({
242
296
  path: path2,
243
297
  endpoint,
@@ -291,6 +345,9 @@ class ParserUtils {
291
345
  const jsdocFile = templateJsdocFile(nameArray[0], apiBuffer);
292
346
  fs__default.writeFileSync(`${distDir}/docs/${nameArray[0]}.md`, jsdocFile);
293
347
  }
348
+ static writeSnippetFile(distDir, name, docBuffer) {
349
+ fs__default.writeFileSync(`${distDir}/${name}.json`, docBuffer);
350
+ }
294
351
  static writeDefinitionFile(distDir, name, buffer) {
295
352
  ParserUtils.mkdirIfNotExist(distDir);
296
353
  fs__default.writeFileSync(path__default.join(distDir, `${name}.ts`), ParserUtils.prependCopyrightHeader(buffer));
@@ -299,6 +356,16 @@ class ParserUtils {
299
356
  ParserUtils.mkdirIfNotExist(distDir);
300
357
  fs__default.writeFileSync(path__default.join(distDir, `all-${isAdminWebSdk ? "admin" : "public"}-imports.ts`), ParserUtils.prependCopyrightHeader(buffer));
301
358
  }
359
+ static writeVersionFile(distDir, fileName, serviceName, apiInfo, buildDate) {
360
+ const ver = apiInfo.version ? `'${apiInfo.version}'` : void 0;
361
+ fs__default.writeFileSync(`${distDir}/${fileName}`, `export default {
362
+ title: '${serviceName}',
363
+ name: '${apiInfo.title}',
364
+ version: ${ver},
365
+ buildDate: '${buildDate}'
366
+ }
367
+ `);
368
+ }
302
369
  static toCamelCase(str) {
303
370
  return str.split("/").map(function(word, index) {
304
371
  if (index === 0) {
@@ -332,6 +399,29 @@ class ParserUtils {
332
399
  }
333
400
  const swaggerContent = JSON.parse(fs__default.readFileSync(swaggerFilePath, "utf8"));
334
401
  const swaggerPatchFileContent = JSON.parse(fs__default.readFileSync(possibleSwaggerPatchFilePath, "utf8"));
402
+ for (const patchEntry of swaggerPatchFileContent) {
403
+ const segments = patchEntry.path.split("/").filter(Boolean);
404
+ let currentNode = swaggerContent;
405
+ let aggregatedPath = "";
406
+ for (let i = 0; i < segments.length; i++) {
407
+ const segment = segments[i];
408
+ aggregatedPath += `/${segment}`;
409
+ const effectiveSegment = segment.replace(/(~1)/g, "/").replace(/(~0)/g, "~");
410
+ if (!currentNode[effectiveSegment]) {
411
+ if (i + 1 === segments.length && patchEntry.op === "add") ; else {
412
+ throw new Error([
413
+ `JSON patch error: operation "${patchEntry.op}" on path "${aggregatedPath}" fails because the path doesn't exist in ${swaggerFilePath}. This may be caused by:
414
+ `,
415
+ "1. The related service has patched the service, so patch is no longer needed.",
416
+ "2. There is a breaking change on the service that causes the path to change.\n",
417
+ `In any case, revisit this file: "${possibleSwaggerPatchFilePath}", then try again.
418
+ `
419
+ ].join("\n"));
420
+ }
421
+ }
422
+ currentNode = currentNode[effectiveSegment];
423
+ }
424
+ }
335
425
  const { newDocument } = applyPatch(swaggerContent, swaggerPatchFileContent);
336
426
  fs__default.writeFileSync(swaggerPatchedFilePath, JSON.stringify(newDocument, null, 2));
337
427
  }
@@ -440,50 +530,6 @@ z.object({
440
530
  }).nullish()
441
531
  });
442
532
 
443
- const templateJsdocMethod = ({
444
- classMethod,
445
- httpMethod,
446
- path,
447
- pathParams,
448
- bodyParams,
449
- queryParams
450
- }) => {
451
- let jsdoc = "";
452
- let methodSignature = "";
453
- let newPath = path;
454
- for (const p of pathParams) {
455
- methodSignature += p.name + ", ";
456
- newPath = newPath.replace("{" + p.name + "}", `' + ${p.name} + '`);
457
- jsdoc += `
458
- * @pathParam '${p.name}${p.required ? "" : "?"}' - ${p.description}`;
459
- }
460
- let payloads = "";
461
- for (const p of bodyParams) {
462
- methodSignature += p.name + ", ";
463
- payloads += p.name;
464
- jsdoc += `
465
- * @payload '${p.name}${p.required ? "" : "?"}' - ${p.description} ${p?.schema?.properties ? "=> " + JSON.stringify(p.schema.properties) : ""}`;
466
- }
467
- for (const p of queryParams) {
468
- const optionalMark = p.required ? "" : "?";
469
- methodSignature += p.name + optionalMark + ", ";
470
- jsdoc += `
471
- * @queryParam '${p.name}${p.required ? "" : "?"}' - ${p.description}`;
472
- }
473
- let queryString = "";
474
- for (const p of queryParams) {
475
- queryString += `${p.name}=__&`;
476
- }
477
- queryString = queryString.length > 0 ? "?" + queryString : "";
478
- return `
479
- /**
480
- * ${httpMethod.toUpperCase()} '${newPath}${queryString}' ${payloads ? "payload: '" + payloads + "'" : ""}
481
- * ${jsdoc}
482
- */
483
- ${classMethod}(${methodSignature}): Promise
484
- `;
485
- };
486
-
487
533
  const templateMethod = ({
488
534
  classMethod,
489
535
  description,
@@ -498,6 +544,9 @@ const templateMethod = ({
498
544
  let methodSignature = "";
499
545
  let newPath = `'${path}'`;
500
546
  let dependencies = [];
547
+ let snippetString = "";
548
+ let snippetMethodSignature = "";
549
+ let snippetCurl = "";
501
550
  for (const pathParam of pathParams) {
502
551
  const type = ParserUtils.parseType(pathParam);
503
552
  if (pathParam.name !== "namespace") {
@@ -512,11 +561,20 @@ const templateMethod = ({
512
561
  }
513
562
  }
514
563
  }
564
+ snippetCurl = `<span class='sn-blue'>curl</span> --location --request <span class='sn-blue'>${httpMethod}</span> '<span class='sn-green'>__DOMAIN__${path}</span>' --header 'accept: application/json'`;
515
565
  let dataType = null;
516
566
  if (httpMethod !== "get") {
517
567
  dataType = ParserUtils.parseBodyParamsType(bodyParams);
518
568
  dependencies = ParserUtils.parseBodyParamsImports(bodyParams);
519
569
  methodSignature += dataType ? `data: ${dataType},` : "";
570
+ const snippetParams = bodyParams?.map((ob) => {
571
+ return ` <span class='sn-purple'>${ob.name}</span>`;
572
+ });
573
+ snippetMethodSignature += snippetParams ? `data: { ${snippetParams} }` : "";
574
+ const curlParams = bodyParams?.map((ob) => {
575
+ return ` <span class='sn-purple'>"${ob.name}": ""</span>`;
576
+ });
577
+ snippetCurl += ` --data-raw { ${curlParams}}`;
520
578
  }
521
579
  const isAnyRequired = ParserUtils.isAnyQueryParamRequired(queryParams);
522
580
  const queryParamsType = queryParams.length ? `queryParams${isAnyRequired ? "" : "?"}: {${ParserUtils.parseQueryParamsType(queryParams)}}` : "";
@@ -552,20 +610,23 @@ const templateMethod = ({
552
610
  const methodName = httpMethod === "get" ? cachedFetchMethod : ["post", "put", "patch", "delete"].includes(httpMethod) ? classMethod : "";
553
611
  const methodGenerics = resolvedResponseClass !== "unknown" ? `<T = ${resolvedResponseClass}>` : "";
554
612
  const responseType = resolvedResponseClass !== "unknown" ? `T` : "unknown";
613
+ const generateMethodName = () => `${methodName}${methodGenerics}(${parameters}): Promise<${responseSyncType}<${responseType}>>`;
614
+ const generateSnippetMethodName = () => `${methodName}(${snippetMethodSignature})`;
615
+ snippetString += `${generateSnippetMethodName()}`;
555
616
  const responseSyncType = httpMethod === "get" ? "IResponseWithSync" : ["post", "put", "patch", "delete"].includes(httpMethod) ? "IResponse" : "";
556
617
  methodImpl = `${descriptionText}
557
- ${methodName}${methodGenerics}(${parameters}): Promise<${responseSyncType}<${responseType}>> {
618
+ ${generateMethodName()} {
558
619
  ${queryParamsDefault}
559
620
  const url = ${newPath} ${formPayloadString} ${isFileUpload ? "\n// TODO file upload not implemented" : ""}
560
621
  const resultPromise = this.axiosInstance.${httpMethod}(url, ${dataPayload})
561
622
 
562
- ${httpMethod === "get" ? `const res = () => Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})
623
+ ${httpMethod === "get" ? ` const res = () => Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})
563
624
 
564
625
  if (!this.cache) {
565
626
  return SdkCache.withoutCache(res)
566
627
  }
567
628
  const cacheKey = url + CodeGenUtil.hashCode(JSON.stringify({ params }))
568
- return SdkCache.withCache(cacheKey, res)` : ""}${["post", "put", "patch", "delete"].includes(httpMethod) ? `return Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})` : ""}
629
+ return SdkCache.withCache(cacheKey, res)` : ""}${["post", "put", "patch", "delete"].includes(httpMethod) ? ` return Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})` : ""}
569
630
  }
570
631
  `;
571
632
  if (!isGuardInvoked) {
@@ -578,7 +639,7 @@ ${httpMethod === "get" ? `const res = () => Validate.responseType(() => resultPr
578
639
  }
579
640
  `;
580
641
  }
581
- return [methodImpl, dependencies];
642
+ return [methodImpl, dependencies, snippetString, snippetCurl];
582
643
  };
583
644
 
584
645
  class TemplateZod {
@@ -594,32 +655,32 @@ class TemplateZod {
594
655
  }
595
656
  let imports = "";
596
657
  for (const cl of Array.from(this.importClasses).sort()) {
597
- imports += ` import { ${cl} } from './${cl}'
658
+ imports += `import { ${cl} } from './${cl}'
598
659
  `;
599
660
  }
600
661
  let exportedVariableString;
601
662
  let exportedTypeString;
602
663
  if (containsRecursiveType) {
603
664
  exportedVariableString = `
604
- export const ${fileName}: z.ZodType<${fileName}> = z.lazy(() =>
665
+ export const ${fileName}: z.ZodType<${fileName}> = z.lazy(() =>
605
666
  ${content.schemaString}
606
667
  )
607
668
  `;
608
669
  exportedTypeString = `
609
- export type ${fileName} = {
670
+ export interface ${fileName} {
610
671
  ${content.typeString}
611
- }
612
- `;
672
+ }
673
+ `;
613
674
  } else {
614
675
  exportedVariableString = `export const ${fileName} = ${content.schemaString}`;
615
- exportedTypeString = `export type ${fileName} = z.TypeOf<typeof ${fileName}>`;
676
+ exportedTypeString = `export interface ${fileName} extends z.TypeOf<typeof ${fileName}> {}`;
616
677
  }
617
678
  const template = `import { z } from 'zod'
618
- ${imports}
679
+ ${imports}
619
680
 
620
- ${exportedVariableString}
681
+ ${exportedVariableString}
621
682
 
622
- ${exportedTypeString}
683
+ ${exportedTypeString}
623
684
  `;
624
685
  return { buffer: template, duplicateFound: this.duplicateFound };
625
686
  };
@@ -776,11 +837,11 @@ class TemplateZodArray {
776
837
  render = (name) => {
777
838
  const cls = name.replace("Array", "");
778
839
  const template = `import { z } from 'zod'
779
- import { ${cls} } from './${cls}'
840
+ import { ${cls} } from './${cls}'
780
841
 
781
- export const ${name} = z.array(${cls})
842
+ export const ${name} = z.array(${cls})
782
843
 
783
- export type ${name} = z.TypeOf<typeof ${name}>
844
+ export interface ${name} extends z.TypeOf<typeof ${name}> {}
784
845
  `;
785
846
  return template;
786
847
  };
@@ -815,16 +876,18 @@ const extractEnumObject = (type, isRequired, enumArr) => {
815
876
  };
816
877
  };
817
878
 
879
+ const BUILD_DATE = new Date().toISOString();
818
880
  class CodeGenerator {
819
881
  static getPatchedDir = () => path__default.join(CliParser.getSwaggersOutputPath(), "patched");
820
882
  static getGeneratedPublicFolder = () => `${CliParser.getOutputPath()}/generated-public`;
821
883
  static getGeneratedAdminFolder = () => `${CliParser.getOutputPath()}/generated-admin`;
884
+ static getGeneratedSnippetsFolder = () => `${CliParser.getOutputPath()}/../../../../apps/api-explorer-app/src/generated-snippets`;
822
885
  static iterateApi = async (api) => {
823
886
  const apiBufferByTag = {};
824
- const jsDocApiBufferByTag = {};
825
887
  const dependenciesByTag = {};
826
888
  const classImports = {};
827
889
  let arrayDefinitions = [];
890
+ const snippetMap = {};
828
891
  for (const path2 in api.paths) {
829
892
  const isAdminEndpoint = path2.indexOf("/admin/") > -1;
830
893
  if (CliParser.isAdmin() && !isAdminEndpoint) {
@@ -846,9 +909,10 @@ class CodeGenerator {
846
909
  const description = endpoint.description;
847
910
  const isDeprecated = endpoint.deprecated;
848
911
  const responseClass = ParserUtils.get2xxResponse(endpoint.responses);
849
- const className = _.upperFirst(_.camelCase(tag));
912
+ const { className, classGenName } = ParserUtils.generateClassName(tag);
850
913
  classImports[className] = classImports[className] ? classImports[className] : {};
851
914
  if (!isDeprecated) {
915
+ snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
852
916
  if (responseClass) {
853
917
  const importTypeClass = ParserUtils.parseRefType(responseClass);
854
918
  classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
@@ -860,12 +924,8 @@ class CodeGenerator {
860
924
  const queryParams = ParserUtils.filterQueryParameters(endpoint.parameters);
861
925
  const pathParams = ParserUtils.filterPathParams(endpoint.parameters);
862
926
  let bodyParams = ParserUtils.filterBodyParams(endpoint.parameters);
863
- const classMethod = ParserUtils.generateClassMethod({
864
- path: path2,
865
- endpoint,
866
- httpMethod,
867
- className
868
- });
927
+ const isForm = endpoint.consumes && endpoint.consumes[0] === "application/x-www-form-urlencoded";
928
+ const classMethod = ParserUtils.generateHumanReadableMethod({ path: path2, httpMethod, isForm });
869
929
  if (endpoint.requestBody) {
870
930
  bodyParams = [
871
931
  {
@@ -876,7 +936,7 @@ class CodeGenerator {
876
936
  ];
877
937
  }
878
938
  const pathWithBase = `${api.basePath ?? ""}${path2}`;
879
- const [generatedMethodString, importStatements] = templateMethod({
939
+ const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
880
940
  classMethod,
881
941
  description,
882
942
  httpMethod,
@@ -888,13 +948,16 @@ class CodeGenerator {
888
948
  responseClass
889
949
  });
890
950
  apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
891
- jsDocApiBufferByTag[tag] = (jsDocApiBufferByTag[tag] || "") + templateJsdocMethod({ classMethod, httpMethod, path: path2, pathParams, bodyParams, queryParams });
892
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
+ };
893
956
  }
894
957
  }
895
958
  }
896
959
  arrayDefinitions = [...new Set(arrayDefinitions)];
897
- return { apiBufferByTag, jsDocApiBufferByTag, dependenciesByTag, classImports, arrayDefinitions };
960
+ return { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap };
898
961
  };
899
962
  static main = async (nameArray) => {
900
963
  const serviceName = nameArray[0];
@@ -914,13 +977,16 @@ class CodeGenerator {
914
977
  ParserUtils.mkdirIfNotExist(DIST_DIR);
915
978
  ParserUtils.mkdirIfNotExist(DIST_DOCS_DIR);
916
979
  ParserUtils.mkdirIfNotExist(DIST_DEFINITION_DIR);
917
- const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions } = await CodeGenerator.iterateApi(api);
980
+ ParserUtils.writeVersionFile(DIST_DIR, "Version.ts", serviceName, api.info, BUILD_DATE);
981
+ const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap } = await CodeGenerator.iterateApi(api);
982
+ if (!CliParser.isAdmin()) {
983
+ ParserUtils.writeSnippetFile(CodeGenerator.getGeneratedSnippetsFolder(), api.info.title, JSON.stringify(snippetMap, null, 2));
984
+ }
918
985
  const targetSrcFolder = `${CliParser.getOutputPath()}/`;
919
986
  for (const tag in apiBufferByTag) {
920
- const className = _.upperFirst(_.camelCase(tag));
987
+ const { className, classGenName } = ParserUtils.generateClassName(tag);
921
988
  const apiBuffer = apiBufferByTag[tag];
922
989
  const imports = [.../* @__PURE__ */ new Set([...dependenciesByTag[tag], ...Object.values(classImports[className])])];
923
- const classGenName = CliParser.isAdmin() ? className + "Admin$" : className + "$";
924
990
  ParserUtils.writeClassFile(DIST_DIR, classGenName, apiBuffer, imports);
925
991
  indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path__default.join(DIST_DIR, `${classGenName}`), targetSrcFolder));
926
992
  }