Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ULID functionality to drizzle-seed #3882

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

mrmattrc
Copy link

@mrmattrc mrmattrc commented Jan 2, 2025

This PR adds support for generating ULID values for drizzle-seed.

@mrmattrc
Copy link
Author

mrmattrc commented Jan 2, 2025

For anyone trying to get ULID working within Drizzle (and Postgres), I created a custom type. This seems to be working at least for generating the schema and pushing it, but I haven't had luck actually seeding yet.

I also did not have luck using "bytea" since that is not a supported type yet for drizzle-seed; I had to resort to using "varchar(26)" for now.

I followed the example found in this gist. If anyone has any ideas on how to fix the import issue or column type issue, I'd appreciate it!

import type {
	ColumnBaseConfig,
	ColumnBuilderBaseConfig,
	ColumnBuilderRuntimeConfig,
	MakeColumnConfig,
} from 'drizzle-orm'
import { entityKind, sql } from 'drizzle-orm'
import type { AnyPgTable } from 'drizzle-orm/pg-core'
import { PgColumn, PgColumnBuilder } from 'drizzle-orm/pg-core'

// TODO: A runtime error occurs when using `import { ULID } from 'id128'`, so we use `createRequire` to import the module.
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const { Ulid: ULID } = require('id128')

export type PgULIDBuilderInitial<TName extends string> = PgULIDBuilder<{
	name: TName
	dataType: 'string'
	columnType: 'PgULID'
	data: string
	driverParam: Buffer
	enumValues: undefined
}>

export class PgULIDBuilder<
	T extends ColumnBuilderBaseConfig<'string', 'PgULID'>,
> extends PgColumnBuilder<T> {
	static override readonly [entityKind]: string = 'PgULIDBuilder'

	constructor(name: T['name']) {
		super(name, 'string', 'PgULID')
	}

	/**
	 * Adds `default gen_random_ulid()` or equivalent logic for ULID default generation.
	 */
	defaultUlid(): ReturnType<this['default']> {
		const generatedUlid = ULID.generate().toRaw() // Raw binary ULID (16 bytes)
		const hexUlid = Buffer.from(generatedUlid, 'binary').toString('hex') // Convert binary to hex

		return this.default(sql`DECODE('${hexUlid}', 'hex')`) as ReturnType<this['default']>
	}

	/** @internal */
	build<TTableName extends string>(
		table: AnyPgTable<{ name: TTableName }>,
	): PgULID<MakeColumnConfig<T, TTableName>> {
		return new PgULID<MakeColumnConfig<T, TTableName>>(
			table,
			this.config as ColumnBuilderRuntimeConfig<any, any>,
		)
	}
}

export class PgULID<T extends ColumnBaseConfig<'string', 'PgULID'>> extends PgColumn<T> {
	static override readonly [entityKind]: string = 'PgULID'

	getSQLType(): string {
		return 'varchar(26)'
	}

	/** Converts database value (Buffer) to a canonical ULID string */
	override mapFromDriverValue(value: Buffer): string {
		return ULID.fromRawTrusted(value.toString('hex')).toCanonical()
	}

	/** Converts canonical ULID string to a database value (Buffer) */
	override mapToDriverValue(value: string): Buffer {
		return Buffer.from(ULID.fromCanonical(value).toRaw(), 'hex')
	}
}

export function ulid(): PgULIDBuilderInitial<''>
export function ulid<TName extends string>(name: TName): PgULIDBuilderInitial<TName>
export function ulid(name = ''): PgULIDBuilderInitial<string> {
	return new PgULIDBuilder(name)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant