diff --git a/.changeset/enum-array-as-string.md b/.changeset/enum-array-as-string.md new file mode 100644 index 0000000..6199a05 --- /dev/null +++ b/.changeset/enum-array-as-string.md @@ -0,0 +1,5 @@ +--- +"prisma-kysely": patch +--- + +Emit enum arrays as `string` under PostgreSQL and CockroachDB, matching how Kysely represents enum array columns. Fixes #107. diff --git a/src/__test__/e2e.test.ts b/src/__test__/e2e.test.ts index 5157348..5dff3c4 100644 --- a/src/__test__/e2e.test.ts +++ b/src/__test__/e2e.test.ts @@ -3,7 +3,8 @@ import { mkdtemp, rm, symlink } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -setDefaultTimeout(20_000); +// GitHub runners are pretty slow in CI +setDefaultTimeout(120_000); const PROJECT_ROOT = path.resolve(__dirname, "../.."); const GENERATOR_PATH = path.join(PROJECT_ROOT, "dist/bin.js"); @@ -228,6 +229,55 @@ export type TestEnum = (typeof TestEnum)[keyof typeof TestEnum]; `); }); +test("End to end test - enum arrays are typed as strings (#107)", async () => { + await using t = await setupTest(); + await t.prismaInit("postgresql", "postgresql://localhost:5432/test"); + + await Bun.write( + t.tempPath("prisma/schema.prisma"), + `datasource db { + provider = "postgresql" + } + + generator kysely { + provider = "node ${GENERATOR_PATH}" + enumFileName = "enums.ts" + } + + enum Permission { + FOO + BAR + BAZ + } + + model TestUser { + id String @id + role Permission + permissions Permission[] + }` + ); + + // Enum arrays aren't supported by SQLite/MySQL, and Postgres returns them + // as an unparsed array-literal string, so just `generate` (no db push). + await t.prisma("generate"); + + const typeFile = await Bun.file( + t.tempPath("prisma/generated/types.ts") + ).text(); + + // The enum array column is a raw string, the scalar enum keeps its type. + expect(typeFile).toContain(`export type TestUser = { + id: string; + role: Permission; + permissions: string; +};`); + + // The broken template-literal approach from the closed PR #108 must not + // come back: it rejected valid Postgres output like "{FOO,BAR,BAZ}". + expect(typeFile).not.toContain("EnumArray"); + expect(typeFile).not.toContain("permissions: Permission[]"); +}); + test("End to end test - separate entrypoints but no enums", async () => { await using t = await setupTest(); await t.prismaInit("sqlite", "file:./dev.db"); diff --git a/src/helpers/generateModel.test.ts b/src/helpers/generateModel.test.ts index 5bd0e5e..d9c2b4b 100644 --- a/src/helpers/generateModel.test.ts +++ b/src/helpers/generateModel.test.ts @@ -185,3 +185,81 @@ test("it respects camelCase option", () => { userName: string | null; };`); }); + +test("it types enum arrays as strings (#107)", () => { + const model = generateModel( + { + name: "User", + fields: [ + { + name: "id", + isId: true, + isGenerated: false, + kind: "scalar", + type: "String", + hasDefaultValue: false, + isList: false, + isReadOnly: false, + isRequired: true, + isUnique: false, + }, + { + name: "role", + isId: false, + isGenerated: false, + kind: "enum", + type: "Role", + hasDefaultValue: false, + isList: false, + isReadOnly: false, + isRequired: true, + isUnique: false, + }, + { + name: "permissions", + isId: false, + isGenerated: false, + kind: "enum", + type: "Permission", + hasDefaultValue: false, + isList: true, + isReadOnly: false, + isRequired: true, + isUnique: false, + }, + ], + schema: null, + primaryKey: null, + dbName: null, + uniqueFields: [], + uniqueIndexes: [], + }, + { + databaseProvider: "postgresql", + fileName: "", + enumFileName: "", + camelCase: false, + readOnlyIds: false, + groupBySchema: false, + defaultSchema: "public", + dbTypeName: "DB", + importExtension: "", + exportWrappedTypes: false, + }, + { + groupBySchema: false, + defaultSchema: "public", + } + ); + + const source = stringifyTsNode(model.definition); + + // A plain enum field keeps its enum type; an enum array becomes `string` + // because Postgres returns it as a raw array literal string ("{FOO,BAR}") + // that the pg driver doesn't parse for user-defined enum array types. + expect(source).toEqual(`export type User = { + id: string; + role: Role; + permissions: string; +};`); +}); diff --git a/src/helpers/generateModel.ts b/src/helpers/generateModel.ts index 6ff1058..570081b 100644 --- a/src/helpers/generateModel.ts +++ b/src/helpers/generateModel.ts @@ -53,20 +53,34 @@ export const generateModel = ( const schemaPrefix = groupBySchema && multiSchemaMap?.get(field.type); if (field.kind === "enum") { + // Of the SQL providers prisma-kysely supports, only PostgreSQL and + // CockroachDB allow arrays of enums (Prisma rejects them at schema + // validation for mysql/sqlite/sqlserver), so an enum list can only + // ever reach here on one of those two. Both speak the Postgres wire + // protocol via the `pg` driver, which doesn't register a parser for + // user-defined enum array types, so Kysely receives the raw Postgres + // array literal string (e.g. `{FOO,BAR}`, or `{}` for an empty array) + // rather than a parsed array. Typing it as `EnumType[]` would + // therefore be wrong, so we fall back to `string`. + // See https://github.com/valtyr/prisma-kysely/issues/107 + const isEnumArray = field.isList; + return generateField({ isId: field.isId, name: normalizeCase(dbName || field.name, config), - type: ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier( - schemaPrefix && defaultSchema !== schemaPrefix - ? `${capitalize(schemaPrefix)}.${field.type}` - : field.type - ), - undefined - ), + type: isEnumArray + ? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) + : ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier( + schemaPrefix && defaultSchema !== schemaPrefix + ? `${capitalize(schemaPrefix)}.${field.type}` + : field.type + ), + undefined + ), nullable: !field.isRequired, generated: isGenerated, - list: field.isList, + list: false, documentation: field.documentation, config, });