Expert guidance for developing the re_spla_analysis monorepo - a Splatoon battle analysis application with NestJS backend and React frontend using pnpm workspaces and Turborepo
This skill provides expert guidance for working with the re_spla_analysis monorepo - a Splatoon battle analysis application that tracks game battles, weapons, stages, rules, and user performance data.
The project is a monorepo using:
The application tracks Splatoon battles with these key entities:
1. Install dependencies from root:
```bash
pnpm install
```
2. Setup backend environment:
```bash
cp apps/backend/.env.template apps/backend/.env
# Edit .env with DATABASE_URL and SHADOW_DATABASE_URL
```
3. Start database via Docker:
```bash
docker-compose up -d db
```
4. Initialize database:
```bash
pnpm prisma migrate dev
```
**Root level (use Turbo):**
**Backend (apps/backend):**
**Frontend (apps/frontend):**
**Purpose**: Handle CRUD operations for a **single table only**
**Rules:**
**Transaction Type Definition** (in `src/prisma/prisma.types.ts`):
```typescript
import { PrismaService } from "./prisma.service";
export type PrismaTransaction = Omit<
PrismaService,
"$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends"
>;
```
**Example:**
```typescript
import { PrismaTransaction } from "../prisma/prisma.types";
@Injectable()
export class UserRepository {
constructor(private readonly prisma: PrismaService) {}
async create(data: { name: string; email: string }, tx?: PrismaTransaction): Promise<User> {
const client = tx ?? this.prisma;
return client.user.create({ data });
}
async findByEmail(email: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { email } });
}
}
```
**Purpose**: Orchestrate **multi-table operations with transactions ONLY**
**Rules:**
**Example:**
```typescript
// src/user/dto.ts
export type UserWithSecret = User & { secret: UserSecret | null };
// src/user/user.usecase.ts
@Injectable()
export class CreateUserUseCase {
constructor(
private readonly prisma: PrismaService,
private readonly userRepository: UserRepository,
private readonly userSecretRepository: UserSecretRepository,
) {}
async execute(data: {
name: string;
email: string;
password: string;
}): Promise<UserWithSecret> {
return this.prisma.$transaction(async (tx) => {
const user = await this.userRepository.create(
{ name: data.name, email: data.email },
tx,
);
const secret = await this.userSecretRepository.create(
{ userId: user.id, password: data.password },
tx,
);
return { ...user, secret };
});
}
}
```
**Purpose**: Handle application logic and coordinate layers
**Rules:**
**Example:**
```typescript
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly createUserUseCase: CreateUserUseCase,
) {}
async createUser(dto: CreateUserDto) {
const existingUser = await this.userRepository.findByEmail(dto.email);
if (existingUser) throw new ConflictException("Email already exists");
const user = await this.createUserUseCase.execute(dto);
return this.toUserResponse(user);
}
}
```
| Operation Type | Use | Example |
| --------------------------- | ------------------------ | ------------------------------------- |
| Single table read | Repository directly | `userRepository.findById()` |
| Single table write | Repository directly | `userRepository.update()` |
| Multi-table read | UseCase | `findUserWithSecretUseCase.byEmail()` |
| Multi-table write | UseCase with transaction | `createUserUseCase.execute()` |
| Business logic + validation | Service | `userService.createUser()` |
**NEVER use type assertions** (`as any`, `as unknown as Type`, `as SomeType`)
These bypass TypeScript's type system and hide real issues. **Strictly forbidden** in all code (production and tests).
**Use type guards instead:**
```typescript
// Define type guard
function isCurrentUser(user: unknown): user is CurrentUser {
return (
typeof user === 'object' &&
user !== null &&
'userId' in user &&
'email' in user &&
typeof user.userId === 'string' &&
typeof user.email === 'string'
);
}
// Use it safely
const user: unknown = request.user;
if (!isCurrentUser(user)) {
return undefined;
}
// Now user is properly typed as CurrentUser
```
**For test mocks:**
```typescript
// Define proper mock type
const mockUser: User = {
id: "test-id",
name: "Test User",
email: "[email protected]",
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
};
prismaService.user.create.mockResolvedValue(mockUser);
```
**Never use:**
**For Prisma Service:**
```typescript
type MockPrismaService = {
user: {
create: jest.Mock;
findUnique: jest.Mock;
update: jest.Mock;
delete: jest.Mock;
};
};
describe("UserRepository", () => {
let prismaService: MockPrismaService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserRepository,
{
provide: PrismaService,
useValue: {
user: {
create: jest.fn(),
findUnique: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
},
},
],
}).compile();
prismaService = module.get<MockPrismaService>(PrismaService);
});
});
```
**For Complex Dependencies:**
```typescript
export interface ResponseWithCookie {
cookie(name: string, val: string, options: CookieOptions): this;
}
class AuthService {
setRefreshTokenCookie(res: ResponseWithCookie, token: string) {
res.cookie("refreshToken", token, { httpOnly: true });
}
}
// Mock in tests
const mockResponse: ResponseWithCookie = {
cookie: jest.fn().mockReturnThis(),
};
```
```bash
pnpm prisma migrate dev
pnpm prisma generate
pnpm seed
```
1. Define database schema changes in `prisma/schema.prisma`
2. Run `pnpm prisma migrate dev`
3. Create repository for single-table operations
4. Create UseCase if multi-table transactions needed
5. Implement Service with business logic
6. Add Controller endpoints
7. Write tests with proper type definitions
```bash
cd apps/backend && pnpm test
pnpm test:watch
pnpm test:cov
pnpm test:e2e
```
```bash
pnpm build
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/splatoon-battle-analysis-development/raw