Open thesmart opened 1 month ago
Without looking into the details here, have you tried reversing the order? Hooks are applied serially, which is why it's important, say, to run the index generator last.
I came here to say the same thing.
@kristiandupont, I just tried reversing the order of the hooks and I get the *Initializer
/*Mutator
classes aren't defined.
@ersinakinci are you referring to the "original" types or the Zod schemas?
@kristiandupont I think they are referring to the fact that when using kanel-kysely
, the "original" *Intializer
and *Mutator
interfaces get replaced by the New*
and *Update
types from kanel-kysely
.
Here in my example, running only kanel-zod
for this Members
table, i get
// @generated
// This file is automatically generated by Kanel. Do not modify manually.
import { communitiesId, type CommunitiesId } from './Communities';
import { usersId, type UsersId } from './Users';
import { z } from 'zod';
/** Identifier type for public.members */
export type MembersId = string & { __brand: 'MembersId' };
/** Represents the table public.members */
export default interface Members {
id: MembersId;
createdAt: Date;
updatedAt: Date;
canAdmin: boolean;
communityId: CommunitiesId;
userId: UsersId;
}
/** Represents the initializer for the table public.members */
export interface MembersInitializer {
/** Default value: gen_random_uuid() */
id?: MembersId;
/** Default value: CURRENT_TIMESTAMP */
createdAt?: Date;
/** Default value: CURRENT_TIMESTAMP */
updatedAt?: Date;
canAdmin: boolean;
communityId: CommunitiesId;
userId: UsersId;
}
/** Represents the mutator for the table public.members */
export interface MembersMutator {
id?: MembersId;
createdAt?: Date;
updatedAt?: Date;
canAdmin?: boolean;
communityId?: CommunitiesId;
userId?: UsersId;
}
export const membersId = z.string() as unknown as z.Schema<MembersId>;
export const members = z.object({
id: membersId,
createdAt: z.date(),
updatedAt: z.date(),
canAdmin: z.boolean(),
communityId: communitiesId,
userId: usersId,
}) as unknown as z.Schema<Members>;
export const membersInitializer = z.object({
id: membersId.optional(),
createdAt: z.date().optional(),
updatedAt: z.date().optional(),
canAdmin: z.boolean(),
communityId: communitiesId,
userId: usersId,
}) as unknown as z.Schema<MembersInitializer>;
export const membersMutator = z.object({
id: membersId.optional(),
createdAt: z.date().optional(),
updatedAt: z.date().optional(),
canAdmin: z.boolean().optional(),
communityId: communitiesId.optional(),
userId: usersId.optional(),
}) as unknown as z.Schema<MembersMutator>;
which works.
However, when i also run kanel-kysely
(either before or after, does not really matter, here i've done before), i get
// @generated
// This file is automatically generated by Kanel. Do not modify manually.
import { communitiesId, type CommunitiesId } from './Communities';
import { usersId, type UsersId } from './Users';
import { type ColumnType, type Selectable, type Insertable, type Updateable } from 'kysely';
import { z } from 'zod';
/** Identifier type for public.members */
export type MembersId = string & { __brand: 'MembersId' };
/** Represents the table public.members */
export default interface MembersTable {
id: ColumnType<MembersId, MembersId | undefined, MembersId>;
createdAt: ColumnType<Date, Date | string | undefined, Date | string>;
updatedAt: ColumnType<Date, Date | string | undefined, Date | string>;
canAdmin: ColumnType<boolean, boolean, boolean>;
communityId: ColumnType<CommunitiesId, CommunitiesId, CommunitiesId>;
userId: ColumnType<UsersId, UsersId, UsersId>;
}
export type Members = Selectable<MembersTable>;
export type NewMembers = Insertable<MembersTable>;
export type MembersUpdate = Updateable<MembersTable>;
export const membersId = z.string() as unknown as z.Schema<MembersId>;
export const members = z.object({
id: membersId,
createdAt: z.date(),
updatedAt: z.date(),
canAdmin: z.boolean(),
communityId: communitiesId,
userId: usersId,
}) as unknown as z.Schema<Members>;
export const membersInitializer = z.object({
id: membersId.optional(),
createdAt: z.date().optional(),
updatedAt: z.date().optional(),
canAdmin: z.boolean(),
communityId: communitiesId,
userId: usersId,
}) as unknown as z.Schema<MembersInitializer>;
export const membersMutator = z.object({
id: membersId.optional(),
createdAt: z.date().optional(),
updatedAt: z.date().optional(),
canAdmin: z.boolean().optional(),
communityId: communitiesId.optional(),
userId: usersId.optional(),
}) as unknown as z.Schema<MembersMutator>;
As you can see, the schemas created by kanel-zod
are referring to MembersInitializer
and MembersMutator
, but those get replaced by kanel-kysely
by NewMembers
and MembersMutator
.
This can be easily solved by either
kanel-kysely
as the default.kanelrc.js
for the names of the types for kanel-kysely
or kanel-zod
and mention this in the docskanel-zod
check whether kanel-kysely
is being used and use the other type names.I think a configuration option is probably the easiest. For now I will solve this by creating my own hook that renames the types used by the zod
schemas
Another possible fix, is disabling the cast entirely in .kanelrc.js
like so
module.exports = {
connection: process.env["DATABASE_URL"],
schemas: ["public"],
preDeleteOutputFolder: true,
preRenderHooks: [
makeKyselyHook(),
makeGenerateZodSchemas({
getZodSchemaMetadata: defaultGetZodSchemaMetadata,
getZodIdentifierMetadata: defaultGetZodIdentifierMetadata,
castToSchema: false,
zodTypeMap: defaultZodTypeMap,
}),
],
outputPath: "../packages/db/src",
};
For anyone interested, here is a postRender hook that solves this issue the ugly way, by just renaming the types after they're generated.
// .kanelrc.js
const { makeKyselyHook } = require("kanel-kysely");
const { generateZodSchemas } = require("kanel-zod");
const kanelZodCastRegex = /as unknown as z.Schema<(.*?)(Mutator|Initializer)>/;
/**
* @type {import("kanel").PostRenderHook}
*
* Renames the type of the `as unknown as z.Schema` casts from `kanel-zod` to
* to be compatible with `kanel-kysely`, turning
* 1. `as unknown as z.Schema<TableMutator>` into `as unknown as z.Schema<TableUpdate>`
* 2. `as unknown as z.Schema<TableInitializer>` into `as unknown as z.Schema<NewTable>`
*/
function kanelKyselyZodCompatibilityHook(path, lines, instantiatedConfig) {
return lines.map((line) => {
if (!line.includes("as unknown as z.Schema")) {
return line;
}
const replacedLine = line.replace(
kanelZodCastRegex,
(_, typeName, mutatorOrInitializer) => {
if (!mutatorOrInitializer) {
return `as unknown as z.Schema<${typeName}>`;
}
if (mutatorOrInitializer === "Mutator") {
return `as unknown as z.Schema<${typeName}Update>`;
}
return `as unknown as z.Schema<New${typeName}>`;
}
);
return replacedLine;
});
}
/** @type {import('kanel').Config} */
module.exports = {
connection: process.env["DATABASE_URL"],
schemas: ["public"],
preDeleteOutputFolder: true,
preRenderHooks: [makeKyselyHook(), generateZodSchemas],
postRenderHooks: [kanelKyselyZodCompatibilityHook],
outputPath: "../packages/db/src",
};
I think they are referring to the fact that when using kanel-kysely, the "original" Intializer and Mutator interfaces get replaced by the New and Update types from kanel-kysely.
I didn't even realize that's what's going on, but yes, to answer your question @kristiandupont, that's exactly it.
Ah, I see. Yeah, the thing is that even though I did a pretty big rewrite two years ago, the architecture is already a bit dated for what I and you guys are trying to do with it already. Both of these extensions work in a a bit of a hacky way. It's a constant battle between making things flexible and keeping necessary configuration at a minimum.
I will try to look into this when I have some time but I am not making any promises as to when that might be :-)
In
.kanelrc
I'm usingpreRenderHooks: [makeKyselyHook(), generateZodSchemas],
because I want both zod and kysely types. However, when using themakeGenerateZodSchemas
config propertycastToSchema: true
, the Schema types are missing.Here is an example:
In the output,
PrismaMigrationsInitializer
is not defined.If I remove
makeKyselyHook(),
plugin, then the schema types appear as expected:Is there a way to make these plugins work with each other?