.cursor/skills/orm/SKILL.md
Common patterns for the podverse-orm package
npx skillsauth add podverse/podverse podverse-orm-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides quick reference for common patterns used in the podverse-orm package.
packages/orm/infra/database/main/migrations/packages/helpers*/): @podverse/helpers, @podverse/helpers-validation, @podverse/helpers-config| Package | Purpose |
| --------------- | ------------------------------- |
| Helper packages | Types, DTOs, validation, config |
| typeorm | ORM framework |
// packages/orm/src/entities/Podcast.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToMany,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { Episode } from './Episode';
@Entity('podcast')
export class Podcast {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
title: string;
@Column({ nullable: true })
description?: string;
@Column({ unique: true })
feedUrl: string;
@Column({ nullable: true })
imageUrl?: string;
@OneToMany(() => Episode, (episode) => episode.podcast)
episodes: Episode[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// packages/orm/src/services/PodcastService.ts
import { getRepository } from 'typeorm';
import { Podcast } from '../entities/Podcast';
export const PodcastService = {
async getById(id: string): Promise<Podcast | null> {
const repo = getRepository(Podcast);
return repo.findOne({ where: { id } });
},
async getByFeedUrl(feedUrl: string): Promise<Podcast | null> {
const repo = getRepository(Podcast);
return repo.findOne({ where: { feedUrl } });
},
async create(data: Partial<Podcast>): Promise<Podcast> {
const repo = getRepository(Podcast);
const podcast = repo.create(data);
return repo.save(podcast);
},
async update(id: string, data: Partial<Podcast>): Promise<Podcast | null> {
const repo = getRepository(Podcast);
await repo.update(id, data);
return this.getById(id);
},
async delete(id: string): Promise<void> {
const repo = getRepository(Podcast);
await repo.delete(id);
},
};
// For complex queries
async findWithFilters(filters: PodcastFilters): Promise<Podcast[]> {
const repo = getRepository(Podcast)
const qb = repo.createQueryBuilder('podcast')
if (filters.searchTerm) {
qb.where('podcast.title ILIKE :term', { term: `%${filters.searchTerm}%` })
}
if (filters.category) {
qb.andWhere('podcast.category = :category', { category: filters.category })
}
qb.orderBy('podcast.createdAt', 'DESC')
.skip(filters.offset || 0)
.take(filters.limit || 20)
return qb.getMany()
}
// Loading relations
async getByIdWithEpisodes(id: string): Promise<Podcast | null> {
const repo = getRepository(Podcast)
return repo.findOne({
where: { id },
relations: ['episodes']
})
}
// Eager vs Lazy loading
@OneToMany(() => Episode, (episode) => episode.podcast, { eager: false })
episodes: Episode[]
Migrations are located in infra/database/main/migrations/.
# Generate migration from entity changes
npm run typeorm migration:generate -- -n MigrationName
# Create empty migration
npm run typeorm migration:create -- -n MigrationName
// infra/database/main/migrations/YYYYMMDDHHMMSS-AddPodcastCategory.ts
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
export class AddPodcastCategory1234567890123 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'podcast',
new TableColumn({
name: 'category',
type: 'varchar',
isNullable: true,
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('podcast', 'category');
}
}
packages/orm/
├── src/
│ ├── entities/ # TypeORM entities
│ ├── services/ # Data access services
│ ├── subscribers/ # Entity subscribers
│ └── index.ts # Public exports
├── package.json
└── tsconfig.json
infra/database/main/
├── migrations/ # TypeORM migrations
└── seeds/ # Seed data (if any)
documentation
Per-job env validation and config patterns for the workers app. Use when adding or changing worker commands, touching workers startup validation, or documenting worker env vars.
development
Common patterns and examples for the podverse-web Next.js application
tools
Ensures client-side time displays use formatDateTimeAbbrev for localized, readable timestamps. Use when rendering dates/times in the UI or when the user mentions time formatting or local timezone display.
testing
--- name: podverse-testing-policy description: Skip test implementation unless the user explicitly asks. Use when a plan or task includes adding unit tests, a test phase, or "Phase 3: Tests". version: 1.0.0 --- # Testing policy — skip tests for now ## When to use - When a plan or task includes adding unit tests, a test phase, or "Phase 3: Tests". - When deciding whether to implement tests for a feature. ## Rules 1. **We are not concerning ourselves with tests at this time.** Skip test imp