Open ersinakinci opened 3 months ago
Searching through the code, I discovered the enumStyle
option:
// invokeKanelProgrammatically.js
import kanel from "kanel";
import config from "../.kanelrc";
const { processDatabase } = kanel;
async function run() {
await processDatabase({ ...config, enumStyle: "type" });
}
run();
For some reason, I can't use it in my .kanelrc.js
file, I have to use it in a programmatically-invoked Kanel config.
The result is:
type EntityType =
| 'person'
| 'company';
export default EntityType;
Which gets us really close!
Could we just simply add a readonly
array with the enum types as strings and export that as a named export when using enumStyle: "type"
? And also export the type as a named export, the same way that I indicated in my original post? (No need to change the default export.)
And could we also allow setting enumStyle
from within .kanelrc.js
?
And could we also allow setting enumStyle from within .kanelrc.js?
I thought it was, that's strange!
But this is a good point. I was actually contemplating removing the enum
option altogether because I don't use it myself but I acknowledge that others do and I don't want to cause too many breaking changes. The thing that annoys me the most, which would apply to your suggestion as well, is that it's the only place where Kanel emits runable code (as opposed to just types). But as it's like that already, your suggestion doesn't make this any worse :-D
I guess this could be a third enum style. I would also want to rename the type type
to union
as I think that's more intuitive.
I thought it was, that's strange!
Maybe you can set it from the config file? I just know that it doesn't show up in IntelliSense 😛, so at least the types are missing.
I guess this could be a third enum style. I would also want to rename the type type to union as I think that's more intuitive.
Works for me! That would be great. I hope it doesn't go against your design goals too much 😅.
It's really good if the feature is natively implemented!
Current my workaround hook is here:
const extractEnumValuesHook = (path, lines) => {
let isEnumFile = lines.some((line) => line.includes('Represents the enum'))
if (!isEnumFile) {
return lines
}
const l = lines.length
for (let i = 0; i < l; i++) {
{
const match = lines[i].match(/export type (.+) =/)
if (match) {
lines.push(`export const ${match[1]}Values = [`)
}
}
{
const match = lines[i].match(/\| '(.+)'/)
if (match) {
lines.push(` '${match[1]}', `)
}
}
}
lines.push('] as const')
return lines
}
module.exports = {
connection: process.env['DATABASE_URL'],
preRenderHooks: [makeKyselyHook(), kyselyCamelCaseHook],
postRenderHooks: [
extractEnumValuesHook,
],
enumStyle: 'type',
}
Outputs this:
/** Represents the enum public.gender_enum */
export type GenderEnum =
| 'MALE'
| 'FEMALE'
| 'UNKNOWN';
export const GenderEnumValues = [
'MALE',
'FEMALE',
'UNKNOWN',
] as const
@acro5piano's hook didn't work for me. I'm not sure about his setup, but it looks to me like the type files are exported using export default
, which the code above doesn't detect.
I expanded on @acro5piano's hook:
export default
and removes it.MyType
and both exported as named exports; rather than a named export const named MyTypeValues
and a default export type named MyType
). That's just my personal preference.// .kanelrc.ts
const extractEnumValuesHook = (_path: string, lines: string[]) => {
let l = lines.length;
const isTableFile = lines.some((line: string) =>
line.includes("Represents the table")
);
const isEnumFile = lines.some((line: string) =>
line.includes("Represents the enum")
);
if (isTableFile) {
for (let i = 0; i < l; i++) {
const match = lines[i].match(/^import type { default as (.+) }/);
if (match) {
lines[i] = `import type { ${match[1]} } from './${match[1]}';`;
}
}
}
if (isEnumFile) {
for (let i = 0; i < l; i++) {
{
const match = lines[i].match(/^type (.+) =/);
if (match) {
lines[i] = `export const ${match[1]} = [`;
lines.push(`export type ${match[1]} = (typeof ${match[1]})[number];`);
}
}
{
const match = lines[i].match(/^export default/);
if (match) {
lines.splice(i, 1);
l--;
}
}
{
const match = lines[i].match(/\| '(.+)'$/);
if (match) {
lines[i] = ` '${match[1]}', `;
}
}
{
const match = lines[i].match(/\| '(.+)';$/);
if (match) {
lines[i] = ` '${match[1]}',`;
lines.splice(i + 1, 0, `] as const;`);
l++;
}
}
}
}
return lines;
};
// Kanel config
export default {
...,
postRenderHooks: [extractEnumValuesHook],
enumStyle: "type",
};
Example output:
EntityType.ts (enum file)
/** Represents the enum public.entity_type */
export const EntityType = [
'person',
'sole-proprietorship',
'gp',
'lp',
'corporation',
'llc',
'llp',
'cooperative',
'other',
] as const;
export type EntityType = (typeof EntityType)[number];
Entity.ts (table file)
import type { EntityType } from './EntityType';
/** Represents the table public.entity */
export default interface EntityTable {
type: ColumnType<EntityType, EntityType, EntityType>;
...
}
Would be great to have this built into Kanel 😄
Nice work! I'm happy you got it working.
I've switched from using enums to iterable type unions, as outlined in this post. I find them much more ergonomic than traditional TS enums and they have the benefit of being iterable.
Any chance that we could get an option in the kanel-kysely plugin to generate this kind of union for PostgreSQL enums instead of native TS enums when using a custom type? For example, instead of generating:
We could generate:
Opt-in API: