Skip to content

RFC: High-level database API + Auto Schema Sync #58

@AliiiBenn

Description

@AliiiBenn

Summary

This issue proposes a new architecture for @deessejs/collections that provides a higher-level API similar to PayloadCMS but simpler, with automatic schema synchronization in development.


Current State

Currently, @deessejs/collections:

  • Uses defineConfig() to create collections
  • Exposes raw Drizzle instance: db.select().from(posts).where(...)
  • Requires manual CLI commands: db:push, db:generate, db:migrate

Proposed Architecture

1. High-Level API

const { collections, db } = defineConfig({
  database: adapter,
  collections: [Users, Posts]
})

// Instead of: db.select().from(posts).where(...)
// We get:
await db.posts.find({ where: { status: 'published' } })
await db.posts.findById(1)
await db.posts.create({ data: { title: 'Hello' } })
await db.posts.update({ where: { id: 1 }, data: { title: 'Updated' } })
await db.posts.delete({ where: { id: 1 } })
await db.posts.count({ where: { status: 'draft' } })

2. No Generated Types File Needed

Unlike PayloadCMS which needs payload-types.ts because config is runtime, our approach preserves TypeScript types through inference:

// Types are automatically inferred
db.posts.find()  // TypeScript knows the return type
collections.users.slug  // Typed as 'users'

3. Simplified CLI

Only db:push is needed since schema is implicit (no migration files):

collections db:push           # Sync schema to database
collections db:push --dry-run # Preview changes

4. Default Config Path

Default location for collections config:

lib/collections.ts

Can be overridden:

withCollections({
  collectionsPath: './config/my-collections.ts'
}, nextConfig)

5. Auto Schema Push in Development

withCollections includes a worker that automatically watches for config changes and pushes the schema:

// Development mode (automatic)
// - Worker watches lib/collections.ts
// - Detects changes to collections
// - Auto db:push in background

// Production mode
// - No worker
// - User runs db:push once manually

API Design (Detailed)

Query Methods

db.posts.find({
  where: { status: 'published', authorId: 1 },
  select: { id: true, title: true },
  orderBy: { createdAt: 'desc' },
  limit: 10,
  offset: 0
})

db.posts.findById(id, { select: { id: true, title: true } })

db.posts.count({ where: { status: 'draft' } })

Mutation Methods

db.posts.create({
  data: { title: 'Hello', content: '...' },
  returning: true  // default
})

db.posts.update({
  where: { id: 1 },
  data: { title: 'Updated' },
  returning: true
})

db.posts.delete({
  where: { id: 1 },
  returning: false
})

Hooks Support

const posts = collection({
  slug: 'posts',
  fields: { ... },
  hooks: {
    beforeCreate: [validateData],
    afterCreate: [logCreation]
  }
})

Hooks are automatically executed by the high-level API.


Open Questions

  1. Where syntax: Simple equality vs operators (e.g., { title: 'hello' } vs { title: { $contains: 'hello' } })
  2. Pagination: Cursor-based vs offset-based
  3. Transactions: How to handle multi-collection transactions
  4. Return format: Just data vs data + metadata (total, page, etc.)

Implementation Plan

  1. Create high-level DbWrapper with collection methods
  2. Update defineConfig to return db with collection operations
  3. Implement CRUD operations with hooks
  4. Simplify CLI (keep only db:push)
  5. Add default config path detection
  6. Implement schema watcher for auto-push in dev

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions