gummy
v0.0.88
Published
A type-safe PostgreSQL schema builder and CRUD layer for [Kysely](https://github.com/kysely-org/kysely), with built-in validation and [Zod](https://github.com/colinhacks/zod) schema generation.
Readme
Gummy
A type-safe PostgreSQL schema builder and CRUD layer for Kysely, with built-in validation and Zod schema generation.
Installation
npm install gummy kysely zodPeer dependencies: kysely >= 0.27.0, zod >= 3.25.0
Quick Start
Define Tables
import { table, serial, varchar, text, bigint, timestamp, decimal, json, boolean } from 'gummy'
const countries = table('countries', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 255 }).unique(),
code: varchar('code', { enum: ['JP', 'US', 'GB'], length: 3 }).notNull(),
order: decimal('order', { precision: 10, scale: 5 }),
metadata: json('metadata').notNull().default(() => ({})),
createdAt: timestamp('created_at').notNull().default(() => new Date()).uneditable(),
})
const cities = table('cities', {
id: serial('id').primaryKey(),
name: text('name'),
countryId: bigint('country_id')
.references(countries.id, { onDelete: 'cascade' })
.notNull(),
})Integrate with Kysely
import { Kysely, PostgresDialect } from 'kysely'
import { Kyselify } from 'gummy'
type Database = {
countries: Kyselify<typeof countries>
cities: Kyselify<typeof cities>
}
const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) })CRUD Operations
import { Gummy } from 'gummy'
const gm = new Gummy(db)
// Create
const country = await gm.create(countries, {
name: 'Japan',
code: 'JP',
order: '1.12345',
})
// Update (full)
await gm.update(
countries,
{ name: 'Nihon', code: 'JP', order: '1.12345' },
(eb) => eb('id', '=', country.id),
)
// Partial update
await gm.partialUpdate(
countries,
{ name: 'Japan' },
(eb) => eb('id', '=', country.id),
)
// Delete
await gm.destroy(countries, (eb) => eb('id', '=', country.id))Column Types
| Function | SQL Type | TypeScript Type |
| --- | --- | --- |
| serial(name) | SERIAL | number |
| bigserial(name) | BIGSERIAL | number |
| integer(name) | INTEGER | number |
| bigint(name) | BIGINT | string |
| varchar(name, { length }) | VARCHAR(n) | string |
| text(name) | TEXT | string |
| email(name) | VARCHAR(255) | string |
| boolean(name) | BOOLEAN | boolean |
| decimal(name, { precision, scale }) | NUMERIC(p,s) | string |
| date(name) | DATE | Date |
| timestamp(name) | TIMESTAMP | Date |
| uuid(name) | UUID | string |
| json(name) | JSON | unknown |
Column Modifiers
serial('id').primaryKey() // PRIMARY KEY (implies notNull + unique)
varchar('name', { length: 255 })
.notNull() // NOT NULL constraint
.unique() // UNIQUE constraint
.index() // CREATE INDEX
.default(() => 'unnamed') // Application-level default
.dbDefault('unnamed') // Database-level default
.onUpdate(() => new Date()) // Auto-set value on update
.uneditable() // Immutable after creation
.references(otherTable.column, { // Foreign key
onDelete: 'cascade', // 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default'
onUpdate: 'cascade',
})Validation
Validation runs automatically before every create, update, and partialUpdate call.
Built-in Validators
import { MinLengthValidator, MaxLengthValidator, MinValueValidator, MaxValueValidator, RegexValidator } from 'gummy'
varchar('password', {
length: 255,
validators: [new MinLengthValidator(8), new RegexValidator(/[A-Z]/, /[0-9]/)],
})
integer('age', {
validators: [new MinValueValidator(0), new MaxValueValidator(150)],
})Automatic Validation
- notNull columns reject
nullvalues (NotNullValidationError) - unique columns check for duplicates (
UniqueValidationError) - references columns verify the foreign row exists (
ForeignKeyValidationError) - enum columns validate against the allowed values (
EnumValidationError) - email columns validate email format (
EmailValidationError) - json columns validate against a Zod schema if provided (
JsonValidationError)
Relations
import { relations } from 'gummy'
const userRelations = relations(users, ({ one, many }) => ({
posts: many(posts),
profile: one(profiles, {
fields: [users.id],
references: [profiles.userId],
}),
}))Zod Schema Generation
Generate Zod schemas from your table definitions for request validation:
import { generateInsertSchema, generateUpdateSchema, generateSelectSchema } from 'gummy'
const insertCountrySchema = generateInsertSchema(countries)
const updateCountrySchema = generateUpdateSchema(countries)
const selectCountrySchema = generateSelectSchema(countries)
// Use with your API framework
const body = insertCountrySchema.parse(req.body)Error Classes
| Error | Cause |
| --- | --- |
| NotNullValidationError | null value for a notNull column |
| UniqueValidationError | Duplicate value for a unique column |
| ForeignKeyValidationError | Referenced row does not exist |
| EnumValidationError | Value not in the allowed enum list |
| EmailValidationError | Invalid email format |
| JsonValidationError | JSON does not match the Zod schema |
| MinLengthValidationError | String too short |
| MaxLengthValidationError | String too long |
| MinValueValidationError | Number below minimum |
| MaxValueValidationError | Number above maximum |
| RegexValidationError | String does not match pattern |
| NotFoundError | Row not found on update/delete |
