redux-toolkit
redux-toolkit copied to clipboard
[RTK Query Code Generation] Generate enums
Is there any way to generate enum instead of type for enum values?
For example, in my specification there is enum type
"Condition": {
"type": "string",
"enum": [
"eq",
"notEq",
"gt",
"gte",
"lt",
"lte",
"in",
"notIn"
],
},
in generated file we get
export type Condition = 'eq' | 'notEq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn';
but for using in code will be better get
export declare enum Condition {
Eq = "eq",
NotEq = "notEq",
Gt = "gt",
Gte = "gte",
Lt = "lt",
Lte = "lte",
In = "in",
NotIn = "notIn"
}
Generation of TypeScript types is generally handled by the third-party package oazapfts, not the codegen.
But also keep in mind that you could just declare that enum in your own code and just use it instead of strings, that should not pose any problem.
I had this issue myself and I wrote a secondary script to generate a file with all of the enums from the API ('./src/API/enums.ts'). The API itself will still use the string union type, but you'll have an enum object that you can use in your code without having to type it out manually.
enumFactory.ts:
import fs from 'fs';
import { camelCase, upperFirst } from 'lodash';
import path from 'path';
import ts, { EnumDeclaration, factory } from 'typescript';
import openapi from './openapi.json';
const createPropertyName = (raw: string): string =>
upperFirst(camelCase(raw.replace(/Enum$/, '')));
const createCustomEnum = (name: string, values: string[]): EnumDeclaration =>
factory.createEnumDeclaration(
undefined,
[
factory.createToken(ts.SyntaxKind.ExportKeyword)
],
name,
values.map((str) =>
factory.createEnumMember(camelCase(str), factory.createStringLiteral(str))
)
);
// Need to make sure that each variable name is unique.
// The schema `title` has not been de-duped, but the property name has.
const createStatements = (): EnumDeclaration[] => {
const enums: EnumDeclaration[] = [];
Object.entries(openapi.components.schemas).forEach(
([name, schema]) => {
if ('enum' in schema) {
// TODO: figure out how to handle numeric properties instead of ignoring.
if (schema.enum.some(str => !isNaN(parseInt(str)))) return;
enums.push(
createCustomEnum(
createPropertyName(name),
schema.enum
)
)
}
}
);
return enums;
}
const resultFile = ts.createSourceFile(
'someFileName.ts',
'',
ts.ScriptTarget.Latest,
/*setParentNodes*/ false,
ts.ScriptKind.TS
);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const result = printer.printNode(
ts.EmitHint.Unspecified,
factory.createSourceFile(
createStatements(),
factory.createToken(ts.SyntaxKind.EndOfFileToken),
ts.NodeFlags.None
),
resultFile
);
fs.writeFileSync(path.resolve(process.cwd(), './src/API/enums.ts'), result);
package.json
"scripts": {
"create-enums": "esr ./src/API/enumFactory.ts"
},
Add esbuild and esbuild-runner to your devDependencies in order to access that esr command.
Call npm run-script create-enums to execute.
I wrote transformer for myself (transform union types to enum)
const path = require('path');
const ts = require('typescript');
const fs = require('fs');
const filePath = path.resolve('api.ts');
const program = ts.createProgram([filePath], {});
const checker = program.getTypeChecker();
const source = program.getSourceFile(filePath);
const printer = ts.createPrinter();
const transformer = context => {
return sourceFile => {
const visitor = (node) => {
if (ts.isTypeAliasDeclaration(node)) {
const symbol = checker.getSymbolAtLocation(node.name);
const type = checker.getDeclaredTypeOfSymbol(symbol);
if (type.isUnion()) {
const unionType = node.getChildren().filter(n => n.kind === ts.SyntaxKind.UnionType)[0];
values = unionType.getChildren()[0].getChildren().filter(n => n.kind === ts.SyntaxKind.LiteralType).map(c => c.getText().replaceAll('"', ''));
if (values.length > 0) {
return ts.factory.createEnumDeclaration(
undefined,
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
node.name.escapedText,
values.map(value => ts.factory.createEnumMember(
value.split(/-|_/).map(v => v.charAt(0).toUpperCase() + v.slice(1)).map(v => /\d/.test(v) ? `_${v}` : v).join(''),
ts.factory.createStringLiteral(value)
))
);
}
}
}
return ts.visitEachChild(node, visitor, context);
};
return ts.visitNode(sourceFile, visitor);
};
};
// Run source file through our transformer
const result = ts.transform(source, [transformer]);
// Write pretty printed transformed typescript to output directory
fs.writeFileSync(
path.resolve('generated.ts'),
printer.printFile(result.transformed[0])
);
Generation of TypeScript types is generally handled by the third-party package
oazapfts, not the codegen. But also keep in mind that you could just declare that enum in your own code and just use it instead of strings, that should not pose any problem.
The issue is that we lose the fluidity of updating the schema and then generating our types and hooks without manual intervention. This poses a maintenance burden and creates a sharp edge that will cut anyone that is not aware of it and regenerates the hooks and types.
Generation of TypeScript types is generally handled by the third-party package
oazapfts, not the codegen. But also keep in mind that you could just declare that enum in your own code and just use it instead of strings, that should not pose any problem.
With all due respect, I didn't download this package to manually write out stuff that should've been pre-generated by the RTK-gen in the first place. I guess I don't need gas in my car either, as I can just push it along the road no problem.
@ozrix84 then make a PR against the other library that has this behavior that you don't like instead of being salty here.
As I stated, this comes from https://github.com/cellular/oazapfts.
With all due respect
There is no statement that gets better with that prefix.
I guess I don't need gas in my car either, as I can just push it along the road no problem.
Open source means you can contribute. The gas for your car isn't free and as long as you don't pay, have fun pushing.
@ozrix84 then make a PR against the other library that has this behavior that you don't like instead of being salty here.
As I stated, this comes from https://github.com/cellular/oazapfts.
I guess I don't need gas in my car either, as I can just push it along the road no problem.
Open source means you can contribute. The gas for your car isn't free and as long as you don't pay, have fun pushing.
I'm less salty and more surprised that there's resistance from the side of the author to properly maintain his software and secure features that some may consider baseline. It's not my responsibility to do so.
@ozrix84 I'll be very direct now:
I'm not going to have you tell me what to do in my free time.
Providing this library is a service I do after work and on weekends. I am not paid for this. I make not even 100$ of donations of this each month, which would only cover for a fraction of the time I am spending on this, even on minimum wage.
I do not owe you anything. If you continue this entitled tone, it will only result in me ignoring you.
Generally: If you politely ask for a feature, I might consider adding it to a very long list of priorities. But it is a very long list, and something like this is really not a blocking bug, but a "nice to have" - and might even be questionable, since enums are a kinda controversial feature of TypeScript.
If you really want the feature, you can add it and file a pull request. I then will review that pull request when I feel like spending the time and sooner or later it might become part of the library.
All that said, you are still barking up the wrong tree.
This feature would not need to be added to the RTK Query Codegen, as I stated multiple times. (Adding it here would be a band-aid solution.) It would need to be added to oazapfts.
I am not a maintainer of oazapfts. You can file an issue there (I would really recommend you to work on your tone and politely ask for the feature instead of demanding it) or you can implement it yourself and file a pull request.
I have no idea why you are still commenting here. I already told you that you are in the wrong place.
@ozrix84 : I'll echo what Lenz said.
We do this work in our free time, for free.
The library code is available under an MIT license that says "you're welcome to do whatever you want with this, but there is no warranty or implication that this even works".
Now, we do actually take this responsibility seriously, and put forth our best-faith efforts to build a tool that is useful, correct, and solves user problems.
But, yelling at us maintainers because we haven't implemented a feature, or built exactly the thing you want, is both extremely rude and demanding and very counter-productive. We gladly try to help folks who ask reasonable questions and show common courtesy. If someone isn't being civil, there's no reason for us to try and help, and if anything actively discourages us from helping.
(There's also the issue that as Lenz pointed out, the described problem isn't even in RTK itself, but the underlying TS codegen lib - continuing to yell at us about that demonstrates a lack of reading skills.)
I'd strongly encourage you to rethink how you interact with others on the internet, and with the Redux maintainers specifically.
@markerikson @phryneas There is a PR for this issue ready to be checked and merged: https://github.com/reduxjs/redux-toolkit/pull/2854
Could you check this and do something with the PR?
First of all I'd like to say thanks to all contributors and maintainers of this great piece of software which leverages a lot of things.
I've read the comments above and do not yet understand this one
The issue is that we lose the fluidity of updating the schema and then generating our types and hooks without manual intervention. This poses a maintenance burden and creates a sharp edge that will cut anyone that is not aware of it and regenerates the hooks and types.
At the moment only elements contained in the string union are considered valid, see the example from above
export type Condition = 'eq' | 'notEq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn';
If the schema is changed an regeneration is executed, the values in this union will change as well an when a value is removed it will be considered as invalid. This seems to me to be true in either case, string union or enums, but maybe I just do not understand enough of this.
I would nevertheless appreciate enums being generated very much, since we use these as payload to invalidateTags and in our case a compile failure after an incompatible schema change is a desired behaviour.
@fjakop maybe I just do not understand enough of this.
The point probably is that in TypeScript an enum is actually runtime accessible and iterable, whereas literal union types aren't. You need runtime representations if you want to build a type-guard that checks whether an arbitrary string value is actually a valid enum member; or when you want to iterate over all members of an enum.
That said; the built-in enum type for TypeScript is awful. And its awfulness was the number one reason many switched to literal string unions.