diff --git a/packages/generator/src/lib/adapter/adapter.ts b/packages/generator/src/lib/adapter/adapter.ts index 9534d5b..78ef3f6 100644 --- a/packages/generator/src/lib/adapter/adapter.ts +++ b/packages/generator/src/lib/adapter/adapter.ts @@ -6,7 +6,7 @@ import type { ImportValue } from '../syntaxes/imports' import type { Module } from '../syntaxes/module' import type { FieldFunc } from './fields/createField' -type ParsableField = PrismaScalarField | PrismaEnumField +export type ParsableField = PrismaScalarField | PrismaEnumField type DeclarationFunc = { imports: ImportValue[]; func: string } diff --git a/packages/generator/src/lib/adapter/fields/createField.ts b/packages/generator/src/lib/adapter/fields/createField.ts index 81be337..9c858b5 100644 --- a/packages/generator/src/lib/adapter/fields/createField.ts +++ b/packages/generator/src/lib/adapter/fields/createField.ts @@ -1,4 +1,5 @@ import type { DMMF } from '@prisma/generator-helper' +import { getDirective } from '~/lib/directive' import { type ImportValue, namedImport } from '~/lib/syntaxes/imports' import type { MakeRequired, ModifyType, Prettify } from '~/lib/types/utils' @@ -58,25 +59,6 @@ export function createField(input: CreateFieldInput) { } } -/** - * e.g. - * Unknown - * - Input: just a doc - * - Returns: undefined - * When Exists - * - Input: drizzle.type viem::Address - * - Returns: viem:Address - */ -function getDirective(field: DMMF.Field, directive: string) { - if (field.documentation == null) return - - return field.documentation - .split('\n') - .find((doc) => doc.startsWith(directive)) - ?.replaceAll(directive, '') - .trim() -} - function getCustomType(field: DMMF.Field) { const directive = getDirective(field, 'drizzle.type') if (directive == null) return diff --git a/packages/generator/src/lib/adapter/providers/mysql.ts b/packages/generator/src/lib/adapter/providers/mysql.ts index f1125fd..cda60c6 100644 --- a/packages/generator/src/lib/adapter/providers/mysql.ts +++ b/packages/generator/src/lib/adapter/providers/mysql.ts @@ -2,7 +2,7 @@ import { camelCase, kebabCase } from 'lodash' import { getDbName } from '~/lib/prisma-helpers/getDbName' import { namedImport } from '~/lib/syntaxes/imports' import { createModule } from '~/lib/syntaxes/module' -import { getGenerator } from '~/shared/generator-context' +import { getDateMode } from '~/shared/date-mode' import { createAdapter } from '../adapter' import { createField, hasDefault, isDefaultFunc } from '../fields/createField' @@ -97,7 +97,7 @@ export const mysqlAdapter = createAdapter({ return createField({ field, imports: [namedImport(['datetime'], coreModule)], - func: `datetime('${getDbName(field)}', { mode: '${getGenerator().dateMode}', fsp: 3 })`, // biome-ignore format: keep one line + func: `datetime('${getDbName(field)}', { mode: '${getDateMode(field)}', fsp: 3 })`, // biome-ignore format: keep one line // https://github.com/drizzle-team/drizzle-orm/issues/921 onDefault: (field) => { if ( diff --git a/packages/generator/src/lib/adapter/providers/postgres.ts b/packages/generator/src/lib/adapter/providers/postgres.ts index 0cdacd5..3019407 100644 --- a/packages/generator/src/lib/adapter/providers/postgres.ts +++ b/packages/generator/src/lib/adapter/providers/postgres.ts @@ -2,7 +2,7 @@ import { camelCase, kebabCase } from 'lodash' import { getDbName } from '~/lib/prisma-helpers/getDbName' import { namedImport } from '~/lib/syntaxes/imports' import { createModule } from '~/lib/syntaxes/module' -import { getGenerator } from '~/shared/generator-context' +import { getDateMode } from '~/shared/date-mode' import { createAdapter } from '../adapter' import { type CreateFieldInput, @@ -105,7 +105,7 @@ export const postgresAdapter = createAdapter({ return createField({ field, imports: [namedImport(['timestamp'], coreModule)], - func: `timestamp('${getDbName(field)}', { mode: '${getGenerator().dateMode}', precision: 3 })`, // biome-ignore format: keep one line + func: `timestamp('${getDbName(field)}', { mode: '${getDateMode(field)}', precision: 3 })`, // biome-ignore format: keep one line }) }, // https://orm.drizzle.team/docs/column-types/pg/#decimal diff --git a/packages/generator/src/lib/adapter/providers/sqlite.ts b/packages/generator/src/lib/adapter/providers/sqlite.ts index 76cbdf2..c97abab 100644 --- a/packages/generator/src/lib/adapter/providers/sqlite.ts +++ b/packages/generator/src/lib/adapter/providers/sqlite.ts @@ -1,7 +1,7 @@ import { getDbName } from '~/lib/prisma-helpers/getDbName' import { namedImport } from '~/lib/syntaxes/imports' import { createModule } from '~/lib/syntaxes/module' -import { getGenerator } from '~/shared/generator-context' +import { getDateMode } from '~/shared/date-mode' import { createAdapter } from '../adapter' import { createField, hasDefault, isDefaultFunc } from '../fields/createField' @@ -100,7 +100,7 @@ export const sqliteAdapter = createAdapter({ // Prisma: https://arc.net/l/quote/grwnsumx // Drizzle: https://arc.net/l/quote/fpupjigo DateTime(field) { - if (getGenerator().dateMode !== 'date') { + if (getDateMode(field) !== 'date') { throw new Error('Only dateMode `date` is supported for sqlite') } diff --git a/packages/generator/src/lib/config.ts b/packages/generator/src/lib/config.ts index 042b6cf..8c07ca1 100644 --- a/packages/generator/src/lib/config.ts +++ b/packages/generator/src/lib/config.ts @@ -7,8 +7,8 @@ import { object, optional, safeParse, - union, } from 'valibot' +import { DateMode } from '~/shared/date-mode' import { ModuleResolution } from '~/shared/generator-context/module-resolution' import { BooleanInStr, withDefault } from './valibot-schema' @@ -17,10 +17,7 @@ const Config = object({ moduleResolution: optional(ModuleResolution), verbose: optional(BooleanInStr), formatter: optional(literal('prettier')), - dateMode: withDefault( - optional(union([literal('string'), literal('date')])), - 'date' - ), + dateMode: optional(DateMode), }) export type Config = Output diff --git a/packages/generator/src/lib/directive.ts b/packages/generator/src/lib/directive.ts new file mode 100644 index 0000000..c4773f2 --- /dev/null +++ b/packages/generator/src/lib/directive.ts @@ -0,0 +1,20 @@ +import type { DMMF } from '@prisma/generator-helper' + +/** + * e.g. + * Unknown + * - Input: just a doc + * - Returns: undefined + * When Exists + * - Input: drizzle.type viem::Address + * - Returns: viem:Address + */ +export function getDirective(field: DMMF.Field, directive: string) { + if (field.documentation == null) return + + return field.documentation + .split('\n') + .find((doc) => doc.startsWith(directive)) + ?.replaceAll(directive, '') + .trim() +} diff --git a/packages/generator/src/shared/date-mode.ts b/packages/generator/src/shared/date-mode.ts new file mode 100644 index 0000000..7dd5d18 --- /dev/null +++ b/packages/generator/src/shared/date-mode.ts @@ -0,0 +1,13 @@ +import { literal, parse, union } from 'valibot' +import type { ParsableField } from '~/lib/adapter/adapter' +import { getDirective } from '~/lib/directive' +import { getGenerator } from './generator-context' + +export const DateMode = union([literal('string'), literal('date')]) + +export function getDateMode(field: ParsableField) { + const directive = getDirective(field, 'drizzle.dateMode') + if (directive) return parse(DateMode, directive) + + return getGenerator().config.dateMode ?? 'date' +} diff --git a/packages/generator/src/shared/generator-context/index.ts b/packages/generator/src/shared/generator-context/index.ts index 1b42c18..9f0a521 100644 --- a/packages/generator/src/shared/generator-context/index.ts +++ b/packages/generator/src/shared/generator-context/index.ts @@ -10,7 +10,6 @@ type Output = { type Generator = { moduleResolution?: string output: Output - dateMode: Config['dateMode'] // dmmf: GeneratorOptions['dmmf'] config: Config @@ -27,7 +26,6 @@ export function initializeGenerator(options: GeneratorOptions) { const context: Generator = { moduleResolution: config.moduleResolution ?? resolveModuleResolution(), output, - dateMode: config.dateMode, // dmmf: options.dmmf, config, diff --git a/packages/usage/tests/configure-date-mode.test.ts b/packages/usage/tests/configure-date-mode.test.ts new file mode 100644 index 0000000..8da1505 --- /dev/null +++ b/packages/usage/tests/configure-date-mode.test.ts @@ -0,0 +1,74 @@ +import { $ } from 'bun' +import { type TempDirectory, createTempHandler } from './utils/temp' + +const tempHandler = createTempHandler() + +afterAll(async () => { + await tempHandler.cleanup() +}) + +test('global config', async () => { + const temp = await tempHandler.prepare() + + await Bun.write( + getSchemaPath(temp), + `datasource db { + provider = "postgresql" + url = env("PG_DATABASE_URL") + } + + generator drizzle { + provider = "prisma-generator-drizzle" + dateMode = "string" + output = "drizzle.ts" + } + + model User { + id Int @id + date DateTime + }` + ) + await $`bun prisma generate --schema ${getSchemaPath(temp)}`.quiet() + + const output = await Bun.file(`${temp.basePath}/drizzle.ts`).text() + expect(output).toContain( + "date: timestamp('date', { mode: 'string', precision: 3 })" + ) +}) + +test.only('field-level config', async () => { + const temp = await tempHandler.prepare() + + await Bun.write( + getSchemaPath(temp), + `datasource db { + provider = "postgresql" + url = env("PG_DATABASE_URL") + } + + generator drizzle { + provider = "prisma-generator-drizzle" + output = "drizzle.ts" + } + + model User { + id Int @id + normalDate DateTime + /// drizzle.dateMode string + stringDate DateTime + }` + ) + await $`bun prisma generate --schema ${getSchemaPath(temp)}`.quiet() + + const output = await Bun.file(`${temp.basePath}/drizzle.ts`).text() + expect(output).toContain( + "normalDate: timestamp('normalDate', { mode: 'date', precision: 3 })" + ) + expect(output).toContain( + "stringDate: timestamp('stringDate', { mode: 'string', precision: 3 })" + ) +}) + +function getSchemaPath(temp: TempDirectory) { + return `${temp.basePath}/schema.prisma` +} diff --git a/packages/usage/tests/single-file-output.test.ts b/packages/usage/tests/single-file-output.test.ts index e0b535b..996820f 100644 --- a/packages/usage/tests/single-file-output.test.ts +++ b/packages/usage/tests/single-file-output.test.ts @@ -1,54 +1,54 @@ import fs from 'node:fs' -import { createId } from '@paralleldrive/cuid2' import { $ } from 'bun' +import { createTempHandler } from './utils/temp' + +const tempHandler = createTempHandler() afterAll(() => { - fs.rmSync('.temp', { recursive: true, force: true }) + tempHandler.cleanup() }) test('generates drizzle.ts', async () => { - const name = createId() - writeSchemaWithOutput(name, 'drizzle.ts') - await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + const temp = await tempHandler.prepare() + writeSchemaWithOutput(temp.name, 'drizzle.ts') + await $`bun prisma generate --schema .temp/${temp.name}/schema.prisma`.quiet() - expect(fs.existsSync(`.temp/${name}/drizzle.ts`)).toBe(true) + expect(fs.existsSync(`.temp/${temp.name}/drizzle.ts`)).toBe(true) }) test('generates ./drizzle.ts', async () => { - const name = createId() - writeSchemaWithOutput(name, './drizzle.ts') - await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + const temp = await tempHandler.prepare() + writeSchemaWithOutput(temp.name, './drizzle.ts') + await $`bun prisma generate --schema .temp/${temp.name}/schema.prisma`.quiet() - expect(fs.existsSync(`.temp/${name}/drizzle.ts`)).toBe(true) + expect(fs.existsSync(`.temp/${temp.name}/drizzle.ts`)).toBe(true) }) test('generates sub/drizzle.ts', async () => { - const name = createId() - writeSchemaWithOutput(name, 'sub/drizzle.ts') - await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + const temp = await tempHandler.prepare() + writeSchemaWithOutput(temp.name, 'sub/drizzle.ts') + await $`bun prisma generate --schema .temp/${temp.name}/schema.prisma`.quiet() - expect(fs.existsSync(`.temp/${name}/sub/drizzle.ts`)).toBe(true) + expect(fs.existsSync(`.temp/${temp.name}/sub/drizzle.ts`)).toBe(true) }) test('generates ./sub/drizzle.ts', async () => { - const name = createId() - writeSchemaWithOutput(name, './sub/drizzle.ts') - await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + const temp = await tempHandler.prepare() + writeSchemaWithOutput(temp.name, './sub/drizzle.ts') + await $`bun prisma generate --schema .temp/${temp.name}/schema.prisma`.quiet() - expect(fs.existsSync(`.temp/${name}/sub/drizzle.ts`)).toBe(true) + expect(fs.existsSync(`.temp/${temp.name}/sub/drizzle.ts`)).toBe(true) }) test('generates ./sub/multi/drizzle.ts', async () => { - const name = createId() - writeSchemaWithOutput(name, './sub/multi/drizzle.ts') - await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + const temp = await tempHandler.prepare() + writeSchemaWithOutput(temp.name, './sub/multi/drizzle.ts') + await $`bun prisma generate --schema .temp/${temp.name}/schema.prisma`.quiet() - expect(fs.existsSync(`.temp/${name}/sub/multi/drizzle.ts`)).toBe(true) + expect(fs.existsSync(`.temp/${temp.name}/sub/multi/drizzle.ts`)).toBe(true) }) function writeSchemaWithOutput(name: string, output: string) { - fs.mkdirSync(`.temp/${name}`, { recursive: true }) - const schema = fs .readFileSync('./prisma/schema.prisma', { encoding: 'utf-8' }) .replace( diff --git a/packages/usage/tests/utils/temp.ts b/packages/usage/tests/utils/temp.ts new file mode 100644 index 0000000..08fd60d --- /dev/null +++ b/packages/usage/tests/utils/temp.ts @@ -0,0 +1,26 @@ +import fs from 'node:fs/promises' +import { createId } from '@paralleldrive/cuid2' + +export type TempDirectory = { name: string; basePath: string } + +export function createTempHandler() { + const temps: string[] = [] + return { + async prepare(): Promise { + const name = createId() + await fs.mkdir(`.temp/${name}`, { recursive: true }) + + return { + name, + get basePath() { + return `.temp/${name}` + }, + } + }, + async cleanup() { + await Promise.all( + temps.map((name) => fs.rmdir(`.temp/${name}`, { recursive: true })) + ) + }, + } +}