|
| 1 | +# Agent Guide for Ackee |
| 2 | + |
| 3 | +This document provides guidelines for AI coding agents working on the Ackee codebase. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Ackee is a self-hosted Node.js analytics tool built with: |
| 8 | + |
| 9 | +- **Backend**: Node.js (ESM modules), Express, Apollo Server (GraphQL), Mongoose (MongoDB) |
| 10 | +- **Frontend**: React (with createElement as `h`), Apollo Client, SCSS |
| 11 | +- **Build**: Custom build script (`build.js`), Rosid handlers |
| 12 | +- **Testing**: AVA test framework |
| 13 | +- **Code Quality**: ESLint + Prettier (via @electerious configs) |
| 14 | + |
| 15 | +## Commands |
| 16 | + |
| 17 | +### Build |
| 18 | + |
| 19 | +```bash |
| 20 | +npm run build # Production build |
| 21 | +npm run build:pre # Development build (BUILD_ENV=pre) |
| 22 | +npm start # Build and start server |
| 23 | +``` |
| 24 | + |
| 25 | +### Development |
| 26 | + |
| 27 | +```bash |
| 28 | +npm run dev # Start with nodemon (auto-rebuild + restart) |
| 29 | +npm run server # Start server without building |
| 30 | +``` |
| 31 | + |
| 32 | +### Testing |
| 33 | + |
| 34 | +```bash |
| 35 | +npm test # Run lint + all tests |
| 36 | +npm run lint # ESLint + Prettier check only |
| 37 | +ava # Run all tests without linting |
| 38 | +ava test/path/to/file.js # Run a single test file |
| 39 | +ava test/**/*domains*.js # Run tests matching pattern |
| 40 | +ava --watch # Run in watch mode |
| 41 | +``` |
| 42 | + |
| 43 | +### Code Quality |
| 44 | + |
| 45 | +```bash |
| 46 | +npm run eslint # Check JavaScript with ESLint |
| 47 | +npm run prettier -- --check # Check formatting |
| 48 | +npm run format # Auto-fix ESLint + Prettier issues |
| 49 | +``` |
| 50 | + |
| 51 | +### Health Check |
| 52 | + |
| 53 | +```bash |
| 54 | +npm run healthcheck # Run health check script |
| 55 | +``` |
| 56 | + |
| 57 | +## Code Style Guidelines |
| 58 | + |
| 59 | +### General Principles |
| 60 | + |
| 61 | +- Use **ES modules** (`.js` files with `type: "module"` in package.json) |
| 62 | +- No TypeScript - pure JavaScript with JSDoc comments where needed |
| 63 | +- Functional programming style preferred |
| 64 | +- Keep code simple, readable, and minimal |
| 65 | + |
| 66 | +### Imports |
| 67 | + |
| 68 | +- Use `.js` extensions in all import paths |
| 69 | +- Group imports logically: external deps → internal modules → utils |
| 70 | +- Use named exports for utilities, default exports for main components/resolvers |
| 71 | + |
| 72 | +```javascript |
| 73 | +import { randomUUID as uuid } from 'node:crypto' |
| 74 | +import Domain from '../models/Domain.js' |
| 75 | +import sortByProp from '../utils/sortByProp.js' |
| 76 | +``` |
| 77 | + |
| 78 | +### File Naming |
| 79 | + |
| 80 | +- **Backend**: camelCase for files (e.g., `domains.js`, `requireAuth.js`) |
| 81 | +- **Frontend Components**: PascalCase (e.g., `Input.js`, `Dashboard.js`) |
| 82 | +- **Frontend Hooks**: camelCase with `use` prefix (e.g., `useDomains.js`) |
| 83 | +- **Constants**: camelCase files (e.g., `routes.js`, `intervals.js`) |
| 84 | + |
| 85 | +### Formatting |
| 86 | + |
| 87 | +- Uses Prettier via `@electerious/prettier-config` |
| 88 | +- Tabs for indentation (configured in Prettier) |
| 89 | +- Single quotes for strings |
| 90 | +- Trailing commas in multi-line structures |
| 91 | +- **Do not manually format** - run `npm run format` instead |
| 92 | + |
| 93 | +### React Patterns |
| 94 | + |
| 95 | +- Use `createElement as h` instead of JSX |
| 96 | +- Define PropTypes for all components |
| 97 | +- Use functional components with hooks |
| 98 | +- Custom hooks follow `use*` naming convention |
| 99 | + |
| 100 | +```javascript |
| 101 | +import { createElement as h } from 'react' |
| 102 | +import PropTypes from 'prop-types' |
| 103 | + |
| 104 | +const Component = (props) => { |
| 105 | + return h('div', { className: 'example' }, props.children) |
| 106 | +} |
| 107 | + |
| 108 | +Component.propTypes = { |
| 109 | + children: PropTypes.node, |
| 110 | +} |
| 111 | + |
| 112 | +export default Component |
| 113 | +``` |
| 114 | + |
| 115 | +### GraphQL Patterns |
| 116 | + |
| 117 | +- Use `gql` template tag from `@apollo/client` |
| 118 | +- Define fragments in separate files |
| 119 | +- Mutations return `{ success, payload }` structure |
| 120 | + |
| 121 | +```javascript |
| 122 | +const QUERY = gql` |
| 123 | + query fetchDomains { |
| 124 | + domains { |
| 125 | + ...domainFields |
| 126 | + } |
| 127 | + } |
| 128 | + ${domainFields} |
| 129 | +` |
| 130 | +``` |
| 131 | + |
| 132 | +### Error Handling |
| 133 | + |
| 134 | +- Use `KnownError` class for user-facing errors |
| 135 | +- Catch and transform ValidationErrors from Mongoose |
| 136 | +- Always handle promise rejections |
| 137 | +- Use `signale` for logging (not `console.log`) |
| 138 | + |
| 139 | +```javascript |
| 140 | +try { |
| 141 | + entry = await domains.add(input) |
| 142 | +} catch (error) { |
| 143 | + if (error.name === 'ValidationError') { |
| 144 | + throw new KnownError(messages(error.errors)) |
| 145 | + } |
| 146 | + throw error |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +### Middleware Pattern |
| 151 | + |
| 152 | +- Resolvers use `pipe()` utility to compose middleware |
| 153 | +- Common middleware: `requireAuth`, `blockDemoMode` |
| 154 | + |
| 155 | +```javascript |
| 156 | +createDomain: pipe(requireAuth, blockDemoMode, async (parent, { input }) => { |
| 157 | + const entry = await domains.add(input) |
| 158 | + return { payload: entry, success: true } |
| 159 | +}) |
| 160 | +``` |
| 161 | + |
| 162 | +### Database Patterns |
| 163 | + |
| 164 | +- Export named functions for CRUD operations |
| 165 | +- Use `response()` transformer to shape data |
| 166 | +- Use `enhance()` pattern for consistent transformations |
| 167 | + |
| 168 | +```javascript |
| 169 | +export const get = async (id) => { |
| 170 | + const enhance = (entry) => { |
| 171 | + return entry == null ? entry : response(entry) |
| 172 | + } |
| 173 | + return enhance(await Domain.findOne({ id })) |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +### Testing with AVA |
| 178 | + |
| 179 | +- Use `test.serial()` for tests that depend on execution order |
| 180 | +- Import AVA as `test from 'ava'` |
| 181 | +- Use `test.before`, `test.after.always`, `test.beforeEach`, `test.afterEach.always` |
| 182 | +- Test context (`t.context`) stores shared state (e.g., tokens) |
| 183 | +- Organize tests in folders matching source structure |
| 184 | + |
| 185 | +```javascript |
| 186 | +import test from 'ava' |
| 187 | +import { api, cleanup, fillDatabase, gql } from './_utils.js' |
| 188 | + |
| 189 | +test.beforeEach(fillDatabase) |
| 190 | +test.afterEach.always(cleanupDatabase) |
| 191 | + |
| 192 | +test.serial('create domain', async (t) => { |
| 193 | + const { json } = await api(base, body, t.context.token.id) |
| 194 | + t.true(json.data.createDomain.success) |
| 195 | + t.is(json.data.createDomain.payload.title, expectedTitle) |
| 196 | +}) |
| 197 | +``` |
| 198 | + |
| 199 | +## ESLint Rules |
| 200 | + |
| 201 | +Base config: `@electerious/eslint-config` |
| 202 | + |
| 203 | +Disabled rules for this project: |
| 204 | + |
| 205 | +- `import-x/dynamic-import-chunkname` |
| 206 | +- `unicorn/filename-case` |
| 207 | +- `unicorn/consistent-function-scoping` |
| 208 | +- `unicorn/no-await-expression-member` |
| 209 | +- `unicorn/no-anonymous-default-export` |
| 210 | +- `unicorn/prefer-top-level-await` |
| 211 | +- `unicorn/no-thenable` |
| 212 | +- `unicorn/no-process-exit` |
| 213 | + |
| 214 | +## Project Structure |
| 215 | + |
| 216 | +``` |
| 217 | +src/ |
| 218 | +├── aggregations/ # Data aggregation functions |
| 219 | +├── constants/ # Shared constants and enums |
| 220 | +├── database/ # Database CRUD operations |
| 221 | +├── middlewares/ # GraphQL middleware (auth, demo mode) |
| 222 | +├── models/ # Mongoose models |
| 223 | +├── resolvers/ # GraphQL resolvers |
| 224 | +├── stages/ # Pipeline stages |
| 225 | +├── types/ # GraphQL type definitions |
| 226 | +├── ui/ # React frontend |
| 227 | +│ ├── scripts/ # React components, hooks, utils |
| 228 | +│ └── styles/ # SCSS stylesheets |
| 229 | +└── utils/ # Utility functions |
| 230 | +
|
| 231 | +test/ |
| 232 | +├── aggregations/ # Aggregation tests |
| 233 | +├── constants/ # Constants tests |
| 234 | +├── resolvers/ # Resolver tests |
| 235 | +└── utils/ # Utility tests |
| 236 | +``` |
| 237 | + |
| 238 | +## Important Notes |
| 239 | + |
| 240 | +- **Node.js version**: Requires Node.js >= 24 |
| 241 | +- **Environment variables**: Uses `.env` files (see `.env` for local config) |
| 242 | +- **MongoDB**: Required for development and testing (uses mongodb-memory-server for tests) |
| 243 | +- **Development mode**: Set `NODE_ENV=development` for GraphQL Playground access |
| 244 | +- **Demo mode**: Set `ACKEE_DEMO=true` to enable demo mode (blocks mutations) |
| 245 | +- **Contributing**: Always work on `develop` branch, discuss changes in issues first |
| 246 | + |
| 247 | +## References |
| 248 | + |
| 249 | +- [Documentation](docs/) |
| 250 | +- [API Documentation](docs/API.md) |
| 251 | +- [Contributing Guide](CONTRIBUTING.md) |
| 252 | +- [Changelog](CHANGELOG.md) |
0 commit comments