diff --git a/.gitignore b/.gitignore index a5e70b173aab927c4da885b7cff6b382f96ec908..82cf4045443e4653e31ba0fd2ef66cf9d573642d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist .vscode generated tsconfig.tsbuildinfo +test-api diff --git a/README.md b/README.md index d09063bd34e2a823a366664fda0e609fd928ede0..d85e34d435a9c1e44b6c0abfc098616dbc12ec8e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Wax spec generator -Wax library REST API specification generator +Wax library REST/JSON-RPC API specification generator No-download example usage: @@ -45,13 +45,13 @@ npx generate-wax-spec --help #### generate JS and D.TS files only using the hafbe namsepace ```bash -npx generate-wax-spec -i data/hafbe.json -N hafbe +npx generate-wax-spec -i data/hafbe.json -N hafbe -T rest ``` #### Emit entire npm project along the JS and D.TS files ```bash -npx generate-wax-spec -i data/hafbe.json -N hafbe -e --npm-name "@hiveio/wax-api-hafbe" --npm-version 1.27.0-rc1 +npx generate-wax-spec -i data/hafbe.json -N hafbe -T rest -e --npm-name "@hiveio/wax-api-hafbe" --npm-version 1.27.0-rc1 ``` ## Building diff --git a/package.json b/package.json index 1dce11e351b31435fb857524e8717221979acbb2..ebd81854dc24808d8dc931a5911587e06023f547 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hiveio/wax-spec-generator", "version": "0.0.1-LastGitTagPlaceholder.GitHashPlaceholder", - "description": "Wax library REST API specification generator", + "description": "Wax library REST/JSON-RPC API specification generator", "main": "dist/index.js", "private": false, "packageManager": "pnpm@10.0.0+sha512.b8fef5494bd3fe4cbd4edabd0745df2ee5be3e4b0b8b08fa643aa3e4c6702ccc0f00d68fa8a8c9858a735a0032485a44990ed2810526c875e416f001b17df12b", @@ -47,6 +47,7 @@ "blockchain", "hive", "swagger", + "openapi", "wax" ], "files": [ diff --git a/src/generator.ts b/src/generator.ts index cf143b242113244068b507af98dfc5ac71ce20f9..63639c847f3b0c7b9bd32427e3cb74396bc8e491 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -8,6 +8,7 @@ import { addNamespace } from "./utils/object.js"; import { stringifyObjectWithUnstringifiedKeys } from "./utils/text.js"; export interface IGeneratorConfig { + apiType: "jsonrpc" | "rest"; inputFile: string; outputDirectory: string; /** @@ -56,7 +57,7 @@ export const generate = async(config: IGeneratorConfig): Promise => { } }), hooks: { - onCreateRoute: onCreateRoute.bind(undefined, result, runtimeDataResult) + onCreateRoute: onCreateRoute.bind(undefined, config.apiType, result, runtimeDataResult) } }); @@ -68,9 +69,9 @@ export const generate = async(config: IGeneratorConfig): Promise => { }); fs.appendFileSync( outDeclarationsPath, - `${EOL }type TWaxRestAPiExtended = ${ + `${EOL }type TWaxExtended = ${ stringifyObjectWithUnstringifiedKeys([ "result", "params" ], addNamespace(result, config.namespace, false), indentCount) - }${EOL }declare var WaxExtendedData: TWaxRestAPiExtended${ EOL }export default WaxExtendedData${ EOL}`, + }${EOL }declare var WaxExtendedData: TWaxExtended${ EOL }export default WaxExtendedData${ EOL}`, { encoding: fileEncoding } ); diff --git a/src/hooks/on-create-route.ts b/src/hooks/on-create-route.ts index 8da427225de2050600513cb157906aeb10eb2cfd..675f71bea9142b30f249b4f1e542fa4e3321ecae 100644 --- a/src/hooks/on-create-route.ts +++ b/src/hooks/on-create-route.ts @@ -3,10 +3,17 @@ import { type ParsedRoute } from "swagger-typescript-api"; import { indentCharacter, indentCount } from "../constants.js"; import { camelize } from "../utils/text.js"; -export const onCreateRoute = (result: Record, runtimeDataResult: Record, routeData: ParsedRoute): undefined => { +export const onCreateRoute = ( + apiType: "jsonrpc" | "rest", + result: Record, + runtimeDataResult: Record, + routeData: ParsedRoute +): undefined => { const { type } = routeData.response; - const routeParts = routeData.raw.route.split("/").filter(node => node.length); + const isRestApi = apiType === "rest"; + + const routeParts = routeData.raw.route.split(isRestApi ? "/" : ".").filter(node => node.length); let currObj = result; let currObjRuntime = runtimeDataResult; @@ -23,7 +30,7 @@ export const onCreateRoute = (result: Record, runtimeDataResult: Re if (currObj[normalizedName] === undefined) { currObjRuntime[normalizedName] = { - urlPath: camelCaseName + urlPath: urlPathName }; currObj[normalizedName] = {}; } @@ -35,10 +42,13 @@ export const onCreateRoute = (result: Record, runtimeDataResult: Re currObjRuntime.method = routeData.raw.method.toUpperCase(); currObj.result = type; // No query and path params (set params to undefined to allow generation of function with no arguments) - if ((routeData.request as any).pathParams === undefined && (routeData.request as any).query === undefined) + if ( (routeData.request as any).pathParams === undefined + && (routeData.request as any).query === undefined + && (routeData.request as any).payload?.type === undefined) currObj.params = undefined; else // Either query params or no query params, but path params exist, so use TEmptyReq - {} currObj.params = `${((routeData.request as any).requestParams?.typeName) ?? "TEmptyReq" + } & ${(routeData.request as any).payload?.type ?? "TEmptyReq" } & {${EOL}${ (routeData.request as any).parameters.map(node => `${indentCharacter.repeat(indentCount)}/** ${node.description} */${EOL}${ indentCharacter.repeat(indentCount) + node.name + (node.optional ? "?" : "") diff --git a/src/index.ts b/src/index.ts index 799d1fe050b7196055ca08c80b3760f207a561e9..9b91ea51043cb2ebdcc411d921a3d5138f5d0fce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,13 +14,16 @@ const outputDirectory = makePathAbsolute(argv.outputDirectory); if (!fs.existsSync(outputDirectory)) fs.mkdirSync(outputDirectory); -const namespace = argv.namespace ?? parseNamespace(inputFile); +const apiType = argv.apiType as "jsonrpc" | "rest"; + +const namespace = typeof argv.addNamespace === "undefined" ? undefined : (argv.addNamespace || parseNamespace(inputFile)); if (argv.emitNpmProject) { if (argv.npmName === undefined || argv.npmVersion === undefined) throw new Error("Trying to create npm project without npm package name and/or version"); prepareNpmPackage({ + apiType, outputDirectory, version: argv.npmVersion, name: argv.npmName, @@ -30,6 +33,7 @@ if (argv.emitNpmProject) { } await generate({ + apiType, inputFile, outputDirectory, namespace diff --git a/src/npm.ts b/src/npm.ts index 2fc0bbcb8744e358cc586ce8060c731c0a8101d0..a158c45861d8387c4d0c6ba305613eff1cac4ba7 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -5,6 +5,7 @@ import { fileEncoding } from "./constants.js"; import { makePathAbsolute } from "./utils/paths.js"; export interface INpmPackageConfig { + apiType: "jsonrpc" | "rest"; outputDirectory: string; name: string; version: string; @@ -14,7 +15,7 @@ export interface INpmPackageConfig { export const prepareNpmPackage = (config: INpmPackageConfig): void => { const displayName = config.namespace?.length > 0 ? `${config.namespace} API definitions` : config.name; - const description = `Wax REST API definitions${(config.namespace.length > 0 ? " for " : "")}${(config.namespace || "")}`; + const description = `Wax REST API definitions${(config.namespace?.length > 0 ? " for " : "")}${(config.namespace || "")}`; const { name, version } = config; const packageJson = { @@ -47,7 +48,7 @@ export const prepareNpmPackage = (config: INpmPackageConfig): void => { const eta = new Eta({ views: templatesDirectory }); const year = new Date().getFullYear(); - const etaData = { displayName, description, name, version, year }; + const etaData = { apiType: config.apiType, displayName, description, name, version, year }; fs.writeFileSync(path.join(config.outputDirectory, "package.json"), JSON.stringify(packageJson, undefined, 2), { encoding: fileEncoding }); diff --git a/src/parse-options.ts b/src/parse-options.ts index 573893199210f8aa1668f4da52d2814b94620c43..1c2f732d0024a22e7a61113463379ecc9a5a7175 100644 --- a/src/parse-options.ts +++ b/src/parse-options.ts @@ -6,12 +6,21 @@ import { hideBin } from "yargs/helpers"; const __dirname = dirname(fileURLToPath(import.meta.url)); export const parseOptions = () => yargs(hideBin(process.argv)) - .option("namespace", { + .option("add-namespace", { alias: "N", type: "string", - description: "Optional namespace name for the application - adds a nested level of object with given namespace name for all of the parsed API definitions." - + " Sets the namespace to the server URL if not provided or leaves empty if server URL is not present in the Swagger file", - default: undefined + description: "Namespace name for the application - adds a nested level of object with given namespace name for all of the parsed API definitions." + + " Sets the namespace to the server URL if implicitly set (--add-namespace). Usually, when dealing with JSON-RPC APIs, the namespace would be " + + "left empty, as the methods are globally available. For REST APIs, it's recommended to set the namespace to avoid potential name clashes. " + + "In most cases, the namespace can be automatically deduced from the input file name, so you can just use --add-namespace without a value.", + implies: "" + }) + .option("api-type", { + alias: "T", + type: "string", + description: "API type for the application - specifies the type of API being defined. Affects the way the definitions are generated.", + choices: [ "jsonrpc", "rest" ], + default: "rest" }) .option("input-file", { alias: "i", diff --git a/templates/README.md.eta b/templates/README.md.eta index 10baa2d4f7ff5011a3fc2ac4286a67f77c63b723..68876a51d6c0f6c3ce03d868ada7d6f4e37a8237 100644 --- a/templates/README.md.eta +++ b/templates/README.md.eta @@ -4,7 +4,7 @@ <%= it.description %> -> This package has been auto-generated by the @hiveio/wax-spec-generator script +> This package has been auto-generated by the @hiveio/wax-spec-generator script for usage with the [Wax library](https://www.npmjs.com/package/@hiveio/wax). ## Usage @@ -15,13 +15,13 @@ pnpm add <%= it.name %> ``` -And import into your Wax project: +And import into your [Wax](https://www.npmjs.com/package/@hiveio/wax) project: ```ts import WaxExtendedData from '<%= it.name %>'; import { createHiveChain } from '@hiveio/wax'; -const chainExtended = (await createHiveChain()).extendRest(WaxExtendedData); +const chainExtended = (await createHiveChain()).<%= it.apiType === "rest" ? "extendRest" : "extend" %>(WaxExtendedData); ``` ## License