because I got bored of customising my CV for every job
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

style: enforce code quality rules and non-null assertions

- Prohibit non-null assertion operator (!) usage
- Enforce constructor-based instantiation for entities and GraphQL types
- Add getOrThrow usage rule for configuration
- Add domain entity rules (no foreign keys, full entities)
- Add service method rules (findFor methods, full entities)
- Add commit workflow rule for roadmap integration
- Update Biome integration with strict rules

+142
+142
.cursorrules
··· 29 29 - Prefer interface over type for object shapes 30 30 - Use type assertions sparingly and with proper type guards 31 31 - Leverage TypeScript's strict mode features 32 + - **NEVER use non-null assertion operator (`!`)** 33 + - Use proper null checks, optional chaining, and type guards instead 34 + - All properties should be properly typed without assertions 32 35 33 36 ### React/JSX 34 37 - Use functional components with hooks ··· 44 47 - Prefer arrow functions for service methods 45 48 - Use early returns in validation and error handling 46 49 - Leverage decorators for clean, declarative code 50 + - **Use `getOrThrow()` for configuration values instead of manual error throwing** 51 + - Inject ConfigService where needed for environment variable access 52 + - Use proper type annotations with `getOrThrow<Type>("VARIABLE_NAME")` 47 53 48 54 ### Error Handling 49 55 - Use early returns for error conditions ··· 95 101 96 102 // Object spread 97 103 const updatedUser = { ...user, lastLogin: new Date() }; 104 + 105 + // Proper null handling 106 + const userId = user?.id ?? ''; 107 + const userName = user?.name ?? 'Unknown'; 108 + 109 + // Proper entity instantiation 110 + class User { 111 + id: string; 112 + name: string; 113 + 114 + constructor(data: { id: string; name: string }) { 115 + this.id = data.id; 116 + this.name = data.name; 117 + } 118 + } 119 + 120 + // Proper configuration with getOrThrow 121 + const secret = configService.getOrThrow<string>("JWT_SECRET"); 122 + const port = configService.getOrThrow<number>("PORT"); 98 123 ``` 99 124 100 125 ### Avoid ❌ ··· 130 155 131 156 // Object.assign 132 157 const updatedUser = Object.assign({}, user, { lastLogin: new Date() }); 158 + 159 + // Non-null assertions 160 + const userId = user!.id; 161 + const userName = user!.name!; 162 + 163 + // Improper entity instantiation 164 + class User { 165 + id!: string; 166 + name!: string; 167 + 168 + constructor(partial: Partial<User>) { 169 + Object.assign(this, partial); 170 + } 171 + } 172 + 173 + // Manual error throwing for configuration 174 + const secret = configService.get<string>("JWT_SECRET"); 175 + if (!secret) { 176 + throw new Error("JWT_SECRET environment variable is required"); 177 + } 178 + 179 + // Direct process.env access without proper validation 180 + const port = process.env["PORT"] || "3000"; 133 181 ``` 134 182 135 183 ## Project-Specific Rules ··· 156 204 157 205 ### Three-Layer Architecture 158 206 **Prefer GraphQL -> Domain Entity -> Prisma Entity separation** 207 + 208 + #### Domain Entity Rules 209 + - **Domain entities should NOT contain foreign key fields** (userId, companyId, roleId, etc.) 210 + - **Domain entities should contain full related entities** instead of IDs 211 + - **Only include the user relation when it's essential** for the business logic 212 + - **Use `findFor` methods** instead of `findBy` when working with related entities 159 213 160 214 #### Layer 1: GraphQL Types 161 215 - GraphQL types should be separate from domain models 162 216 - Focus on API contract and client needs 163 217 - Use `fromDomain()` static methods to convert from domain entities 218 + - **All GraphQL types MUST instantiate properties through constructors** 219 + - **NEVER use non-null assertion operator (`!`) in GraphQL types** 220 + - Use proper nullable types and constructor parameter validation 164 221 - Example: `UserGraphQL.fromDomain(domainUser)` 165 222 166 223 #### Layer 2: Domain Entities 167 224 - Domain entities should be separate from Prisma models 168 225 - Contain business logic and domain rules 169 226 - Use `toDomain()` and `mapToDomain()` methods for conversion 227 + - **All domain entities MUST instantiate properties through constructors** 228 + - **NEVER use non-null assertion operator (`!`) in domain entities** 229 + - Use proper nullable types and constructor parameter validation 170 230 - Example: `User` domain entity with business logic 171 231 172 232 #### Layer 3: Prisma Models ··· 175 235 - Use `mapToDomain()` to convert to domain entities 176 236 - Example: `prisma.user.findMany()` -> `mapToDomain()` -> `User` domain 177 237 238 + #### Service Method Rules 239 + - **Service methods should accept full entities** instead of IDs when possible 240 + - **Use `findFor` methods** (e.g., `findForUser(user)`) instead of `findBy` methods with IDs 241 + - **DTOs should contain full entities** instead of foreign key IDs 242 + - **Only use IDs for Prisma operations** - extract IDs from entities at the service boundary 243 + 178 244 #### Mapping Functions 179 245 - **`fromDomain()`** - Domain entity to GraphQL type 180 246 - **`toDomain()`** - Prisma model to domain entity ··· 183 249 - Keep domain logic in domain entities 184 250 - Keep database operations in Prisma services 185 251 252 + #### Entity Instantiation Patterns 253 + **All entities and GraphQL types MUST use constructor-based instantiation:** 254 + 255 + ```typescript 256 + // ✅ GOOD: Proper constructor with nullable types 257 + @ObjectType() 258 + export class Organization { 259 + @Field(() => String) 260 + id: string; 261 + 262 + @Field(() => String) 263 + name: string; 264 + 265 + @Field(() => String, { nullable: true }) 266 + description: string | null; 267 + 268 + @Field(() => Date) 269 + createdAt: Date; 270 + 271 + @Field(() => Date) 272 + updatedAt: Date; 273 + 274 + constructor(data: { 275 + id: string; 276 + name: string; 277 + description?: string | null; 278 + createdAt: Date; 279 + updatedAt: Date; 280 + }) { 281 + this.id = data.id; 282 + this.name = data.name; 283 + this.description = data.description ?? null; 284 + this.createdAt = data.createdAt; 285 + this.updatedAt = data.updatedAt; 286 + } 287 + } 288 + ``` 289 + 290 + ```typescript 291 + // ❌ BAD: Non-null assertions and no proper constructor 292 + @ObjectType() 293 + export class Organization { 294 + @Field(() => String) 295 + id!: string; 296 + 297 + @Field(() => String) 298 + name!: string; 299 + 300 + @Field(() => String, { nullable: true }) 301 + description?: string | null; 302 + 303 + @Field(() => Date) 304 + createdAt!: Date; 305 + 306 + @Field(() => Date) 307 + updatedAt!: Date; 308 + 309 + constructor(partial: Partial<Organization>) { 310 + Object.assign(this, partial); 311 + } 312 + } 313 + ``` 314 + 315 + **Key Requirements:** 316 + - **NEVER use `!` non-null assertion operator** 317 + - **ALWAYS use explicit constructor parameters** 318 + - **ALWAYS handle nullable types properly** 319 + - **ALWAYS validate required properties in constructor** 320 + - Use `??` nullish coalescing for default values 321 + - Use proper TypeScript types without assertions 322 + 186 323 ## Biome Integration 187 324 188 325 These rules are enforced by Biome with the following configuration: ··· 194 331 - `useCollapsedIf: "error"` - Prefer early returns 195 332 - `useOptionalChain: "error"` - Prefer optional chaining 196 333 - `useSimplifiedLogicExpression: "error"` - Prefer ternary operations 334 + - `noNonNullAssertion: "error"` - Prohibit non-null assertion operator (`!`) 335 + - `noExplicitAny: "error"` - Prohibit explicit `any` types 336 + - `noUnsafeAssignment: "error"` - Prohibit unsafe assignments 197 337 198 338 ## File Organization 199 339 ··· 222 362 - Do not automatically commit or stage changes 223 363 - Ask for permission before making any commits or staging files 224 364 - Wait for user approval to commit or stage files 365 + - **When making commits, check if any roadmap items were completed and check them off** 366 + - Review ROADMAP.md for relevant completed features and update checkboxes accordingly 225 367 226 368 ### Conventional Commits 227 369 - **Always use conventional commit format**: `type(scope): description`