/** * ESLint rules for Write Boundary Guardrails * * Enforces write operation boundaries */ module.exports = { // Rule 1: No direct mutations in write boundaries 'no-direct-mutations-in-write-boundaries': { meta: { type: 'problem', docs: { description: 'Forbid direct mutations in write boundaries', category: 'Write Boundary', }, messages: { message: 'Write boundaries must use mutation functions, not direct mutations - see apps/website/lib/contracts/write-boundaries/WriteBoundary.ts', }, }, create(context) { return { AssignmentExpression(node) { const filename = context.getFilename(); if (filename.includes('/lib/write-boundaries/')) { // Check for direct property assignment if (node.left.type === 'MemberExpression' && !isInMutationFunction(node)) { context.report({ node, messageId: 'message', }); } } }, UpdateExpression(node) { const filename = context.getFilename(); if (filename.includes('/lib/write-boundaries/') && !isInMutationFunction(node)) { context.report({ node, messageId: 'message', }); } }, }; }, }, // Rule 2: Write boundaries must use repository pattern 'write-boundaries-must-use-repository': { meta: { type: 'problem', docs: { description: 'Enforce repository pattern in write boundaries', category: 'Write Boundary', }, messages: { message: 'Write boundaries must use repository pattern for data access - see apps/website/lib/contracts/write-boundaries/WriteBoundary.ts', }, }, create(context) { return { CallExpression(node) { const filename = context.getFilename(); if (filename.includes('/lib/write-boundaries/')) { // Check for direct database access if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && ['db', 'prisma', 'knex'].includes(node.callee.object.name) && !isInComment(node)) { context.report({ node, messageId: 'message', }); } } }, }; }, }, }; // Helper functions function isInMutationFunction(node) { // Check if node is inside a mutation function let current = node; while (current) { if (current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression') { const name = current.id?.name || ''; return name.includes('mutate') || name.includes('update') || name.includes('create'); } current = current.parent; } return false; } function isInComment(node) { return false; }