Code Style
UniPulse follows consistent coding conventions across the entire monorepo. TypeScript strict mode is enabled everywhere.
General Principles
| Principle | Details |
|---|---|
| TypeScript everywhere | Strict mode enabled in all packages |
| Functional patterns | Prefer functions and objects over classes |
| Named exports | Prefer named exports over default exports |
| Absolute imports | Use path aliases (@/, @unipulse/shared) |
No any | Avoid any type -- use unknown if type is truly unknown |
| Explicit return types | Add return types to service functions |
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Files (services) | kebab-case.service.ts | post.service.ts, ai-advisor.service.ts |
| Files (routes) | kebab-case.routes.ts | post.routes.ts, ab-test.routes.ts |
| Files (types) | kebab-case.types.ts | post.types.ts, brand-voice.types.ts |
| Files (validators) | kebab-case.validators.ts | post.validators.ts |
| Files (queues) | kebab-case.queue.ts | publish.queue.ts |
| Components | PascalCase.tsx | PostCard.tsx, DataTable.tsx |
| Stores | kebab-case.store.ts | auth.store.ts, ui.store.ts |
| Hooks | use-kebab-case.ts | use-posts.ts, use-workspace.ts |
| Functions | camelCase | getPostById, generateCaption |
| Constants | UPPER_SNAKE_CASE | MAX_RETRY_COUNT, PROMPT_CONFIGS |
| Types/Interfaces | PascalCase | CreatePostInput, WorkflowNode |
| Enums | PascalCase (type), UPPER_SNAKE_CASE (values) | Role.ADMIN, Platform.FACEBOOK |
| Database fields | camelCase | createdAt, workspaceId |
| Environment variables | UPPER_SNAKE_CASE | DATABASE_URL, JWT_SECRET |
| Queue names | kebab-case | analytics-sync, ice-process |
File Organization
| Rule | Details |
|---|---|
| One service per file | post.service.ts contains all post-related business logic |
| One component per file | PostCard.tsx exports a single component |
| Group by feature domain | pages/analytics/, services/analytics.service.ts |
| Index files for re-exports | components/ui/index.ts for barrel exports |
| Co-locate tests | services/__tests__/post.service.test.ts |
Backend Patterns
Service Pattern
// apps/api/src/services/example.service.ts
import { prisma } from '../lib/prisma';
import type { CreateExampleInput } from '@unipulse/shared';
export const exampleService = {
async getAll(workspaceId: string): Promise<Example[]> {
return prisma.example.findMany({
where: { workspaceId },
orderBy: { createdAt: 'desc' },
});
},
async create(workspaceId: string, data: CreateExampleInput): Promise<Example> {
return prisma.example.create({
data: { ...data, workspaceId },
});
},
};
Route Pattern
// apps/api/src/routes/example.routes.ts
import { Router } from 'express';
import { authenticate } from '../middleware/auth';
import { requireWorkspace } from '../middleware/requireWorkspace';
import { validate } from '../middleware/validate';
import { createExampleSchema } from '@unipulse/shared';
import { exampleService } from '../services/example.service';
const router = Router();
router.get('/', authenticate, requireWorkspace('VIEWER'), async (req, res, next) => {
try {
const items = await exampleService.getAll(req.workspace.id);
res.json(items);
} catch (error) {
next(error);
}
});
router.post('/', authenticate, requireWorkspace('EDITOR'), validate(createExampleSchema), async (req, res, next) => {
try {
const item = await exampleService.create(req.workspace.id, req.body);
res.status(201).json(item);
} catch (error) {
next(error);
}
});
export default router;
Frontend Patterns
Component Pattern
// apps/web/src/pages/examples/ExampleList.tsx
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { DataTable } from '@/components/data-table';
import type { Example } from '@unipulse/shared';
export function ExampleList() {
const { t } = useTranslation('examples');
const { data, isLoading } = useQuery<Example[]>({
queryKey: ['examples'],
queryFn: () => api.examples.getAll(),
});
if (isLoading) return <Skeleton />;
return (
<div>
<h1>{t('title')}</h1>
<DataTable data={data ?? []} columns={columns} />
</div>
);
}
Store Pattern
// apps/web/src/stores/example.store.ts
import { create } from 'zustand';
interface ExampleState {
selectedId: string | null;
setSelected: (id: string | null) => void;
}
export const useExampleStore = create<ExampleState>((set) => ({
selectedId: null,
setSelected: (id) => set({ selectedId: id }),
}));
Code Quality Rules
| Rule | Tool | Enforcement |
|---|---|---|
| Zod for all validation | @unipulse/shared validators | Middleware + zodResolver |
| Prisma for all DB access | @prisma/client | No raw SQL unless necessary |
| TanStack Query for API calls | @tanstack/react-query | No manual fetch in components |
| Workspace scoping | workspaceId in every query | Service layer convention |
| Error handling at boundaries | AppError + global handler | Don't catch silently |
| Translation for all user-facing text | react-i18next | t() function, never hardcoded strings |
| Logical CSS properties | Tailwind v4 ms-, me-, ps-, pe- | RTL support |
Import Order
// 1. Node.js built-ins
import path from 'path';
// 2. External packages
import { Router } from 'express';
import { z } from 'zod';
// 3. Internal packages
import type { Post } from '@unipulse/shared';
// 4. Local imports
import { prisma } from '../lib/prisma';
import { postService } from '../services/post.service';
Cross-Reference
- PR Process -- how to submit code changes
- Testing -- testing conventions
- New Feature Checklist -- end-to-end guide
- Shared Package -- adding shared types and validators