579 lines
12 KiB
Markdown
579 lines
12 KiB
Markdown
# Tech Stack & Enforcement Rules
|
|
|
|
**Version:** 1.0
|
|
**Status:** PRODUCTION
|
|
**Last Updated:** January 2026
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This document enforces the specific versions and usage patterns for all technologies in Job Info. All developers must follow these rules to ensure consistency and compatibility.
|
|
|
|
---
|
|
|
|
## 1. Runtime & Build System
|
|
|
|
### Bun
|
|
|
|
**Version:** `latest` (1.0.0+)
|
|
|
|
**Rules:**
|
|
- ✅ Use `bun` for running TypeScript directly
|
|
- ✅ Use `bun install` to manage dependencies
|
|
- ✅ Use `bun run` for scripts
|
|
- ✅ Use `bun run build:css` for Tailwind compilation
|
|
- ❌ Do NOT use `npm` or `yarn`
|
|
- ❌ Do NOT use Node.js for production (Bun is faster, but ensure infrastructure supports it)
|
|
|
|
**Installation:**
|
|
```bash
|
|
curl -fsSL https://bun.sh/install | bash
|
|
```
|
|
|
|
**Common Commands:**
|
|
```bash
|
|
bun install # Install dependencies
|
|
bun add <package> # Add dependency
|
|
bun run <script> # Run npm script
|
|
bun --watch server.ts # Watch & reload
|
|
bun build --target bun # Build for production
|
|
```
|
|
|
|
**package.json Scripts:**
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"dev": "bun --watch backend/server.ts",
|
|
"start": "bun run backend/server.ts",
|
|
"build:css": "tailwindcss -i input.css -o frontend/output.css --watch"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Web Framework
|
|
|
|
### Hono
|
|
|
|
**Version:** `^4.10.8` (pinned)
|
|
|
|
**Rules:**
|
|
- ✅ Use Hono for all backend routes
|
|
- ✅ Use typed context: `(c: Context)`
|
|
- ✅ Use middleware for auth, logging, CORS
|
|
- ✅ Return JSON responses with `c.json()`
|
|
- ❌ Do NOT mix Express or other frameworks
|
|
- ❌ Do NOT use un-typed `req` / `res`
|
|
|
|
**Installation:**
|
|
```bash
|
|
bun add hono@^4.10.8
|
|
```
|
|
|
|
**Routing Pattern:**
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
import { Context } from 'hono';
|
|
|
|
const app = new Hono();
|
|
|
|
// GET endpoint
|
|
app.get('/api/jobs', async (c: Context) => {
|
|
try {
|
|
const page = c.req.query('page') || '1';
|
|
const data = await fetchData();
|
|
return c.json({ items: data });
|
|
} catch (err) {
|
|
console.error('Error:', err);
|
|
return c.json({ error: 'Server error' }, 500);
|
|
}
|
|
});
|
|
|
|
// POST endpoint
|
|
app.post('/api/jobs', async (c: Context) => {
|
|
const body = await c.req.json();
|
|
// Process
|
|
return c.json({ success: true }, 201);
|
|
});
|
|
|
|
export default app;
|
|
```
|
|
|
|
**Middleware Pattern:**
|
|
```typescript
|
|
// Auth middleware
|
|
app.use('*', async (c, next) => {
|
|
const token = c.req.header('Authorization');
|
|
if (!token) return c.json({ error: 'Unauthorized' }, 401);
|
|
await next();
|
|
});
|
|
|
|
// CORS middleware
|
|
app.use('*', async (c, next) => {
|
|
c.header('Access-Control-Allow-Origin', '*');
|
|
await next();
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 3. CSS Framework
|
|
|
|
### PostCSS + Tailwind CSS
|
|
|
|
**Versions:**
|
|
- `postcss`: `^8.5.6` (pinned)
|
|
- `tailwindcss`: `^4.1.18` (pinned)
|
|
- `autoprefixer`: `^10.4.23` (devDependency)
|
|
|
|
**Installation:**
|
|
```bash
|
|
bun add -D postcss@^8.5.6 tailwindcss@^4.1.18 autoprefixer@^10.4.23
|
|
```
|
|
|
|
**Configuration Files:**
|
|
|
|
**postcss.config.js:**
|
|
```javascript
|
|
export default {
|
|
plugins: {
|
|
tailwindcss: {},
|
|
autoprefixer: {},
|
|
},
|
|
};
|
|
```
|
|
|
|
**tailwind.config.js:**
|
|
```javascript
|
|
export default {
|
|
content: [
|
|
'./frontend/**/*.html',
|
|
'./frontend/**/*.js',
|
|
],
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
// Add custom colors if needed
|
|
},
|
|
},
|
|
},
|
|
plugins: [],
|
|
};
|
|
```
|
|
|
|
**Build Command:**
|
|
```bash
|
|
bun run build:css
|
|
```
|
|
|
|
**CSS Input (input.css):**
|
|
```css
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
```
|
|
|
|
**Rules:**
|
|
- ✅ Use Tailwind utility classes: `bg-blue-600`, `px-4`, `rounded-lg`
|
|
- ✅ Use `@apply` for reusable component styles (if needed)
|
|
- ✅ Custom CSS in separate `.css` files (not inline styles)
|
|
- ❌ Do NOT use Bootstrap, Foundation, or other CSS frameworks
|
|
- ❌ Do NOT inline styles: `<div style="...">`
|
|
- ❌ Do NOT write CSS that conflicts with Tailwind
|
|
|
|
**Component Example:**
|
|
```html
|
|
<!-- ✅ CORRECT -->
|
|
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
|
Click Me
|
|
</button>
|
|
|
|
<!-- ❌ WRONG -->
|
|
<button style="background: blue; padding: 10px;">Click Me</button>
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Runtime Type System
|
|
|
|
### TypeScript
|
|
|
|
**Version:** `^5` (peer dependency)
|
|
|
|
**Rules:**
|
|
- ✅ Use strict mode: `"strict": true` in tsconfig.json
|
|
- ✅ Type all function parameters
|
|
- ✅ Type all return values
|
|
- ✅ Use interfaces for objects
|
|
- ✅ Enable `noImplicitAny`
|
|
- ✅ Enable `strictNullChecks`
|
|
- ❌ Do NOT use `any` type
|
|
- ❌ Do NOT skip type annotations
|
|
|
|
**tsconfig.json (Backend):**
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"target": "ES2020",
|
|
"module": "ES2020",
|
|
"lib": ["ES2020"],
|
|
"moduleResolution": "node",
|
|
"strict": true,
|
|
"esModuleInterop": true,
|
|
"skipLibCheck": true,
|
|
"forceConsistentCasingInFileNames": true,
|
|
"resolveJsonModule": true,
|
|
"declaration": true,
|
|
"declarationMap": true,
|
|
"sourceMap": true,
|
|
"noImplicitAny": true,
|
|
"strictNullChecks": true,
|
|
"strictFunctionTypes": true,
|
|
"noUnusedLocals": true,
|
|
"noUnusedParameters": true,
|
|
"noImplicitReturns": true
|
|
},
|
|
"include": ["backend/**/*.ts"],
|
|
"exclude": ["node_modules"]
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
```typescript
|
|
// ✅ CORRECT
|
|
interface Job {
|
|
id: string;
|
|
name: string;
|
|
number: number;
|
|
created_at: string;
|
|
}
|
|
|
|
function getJobNumber(job: Job): number {
|
|
return job.number;
|
|
}
|
|
|
|
async function fetchJob(id: string): Promise<Job | null> {
|
|
try {
|
|
const data = await fetch(`/api/jobs/${id}`);
|
|
if (!data.ok) return null;
|
|
return await data.json();
|
|
} catch (err) {
|
|
console.error('Error fetching job:', err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ❌ WRONG
|
|
function getJobNumber(job) { // No type
|
|
return job.number; // No return type
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Caching & Session Management
|
|
|
|
### ioredis (Redis/Valkey Client)
|
|
|
|
**Version:** `^5.x` (add to dependencies)
|
|
|
|
**Installation:**
|
|
```bash
|
|
bun add ioredis
|
|
```
|
|
|
|
**Usage:**
|
|
```typescript
|
|
import Redis from 'ioredis';
|
|
|
|
const redis = new Redis({
|
|
host: process.env.REDIS_HOST || 'localhost',
|
|
port: Number(process.env.REDIS_PORT || 6379),
|
|
password: process.env.REDIS_PASSWORD,
|
|
retryStrategy(times) {
|
|
const delay = Math.min(times * 50, 2000);
|
|
return delay;
|
|
},
|
|
maxRetriesPerRequest: 3,
|
|
lazyConnect: true,
|
|
});
|
|
|
|
// Cache operations
|
|
export async function getCache(key: string): Promise<any | null> {
|
|
try {
|
|
const value = await redis.get(key);
|
|
if (!value) return null;
|
|
return JSON.parse(value);
|
|
} catch (err) {
|
|
console.error(`Cache get error for key "${key}":`, err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function setCache(key: string, value: any, ttl: number = 300): Promise<boolean> {
|
|
try {
|
|
const serialized = JSON.stringify(value);
|
|
await redis.setex(key, ttl, serialized);
|
|
return true;
|
|
} catch (err) {
|
|
console.error(`Cache set error for key "${key}":`, err);
|
|
return false;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Rules:**
|
|
- ✅ Use `setex` for automatic TTL expiration
|
|
- ✅ Check cache before fetching external data
|
|
- ✅ Log cache hits/misses for debugging
|
|
- ❌ Do NOT store secrets in cache
|
|
- ❌ Do NOT rely on cache for critical data (always have fallback)
|
|
|
|
---
|
|
|
|
## 6. Authentication & Backend Services
|
|
|
|
### PocketBase SDK
|
|
|
|
**Version:** `^0.26.5` (pinned)
|
|
|
|
**Installation:**
|
|
```bash
|
|
bun add pocketbase@^0.26.5
|
|
```
|
|
|
|
**Frontend Usage (auth.js):**
|
|
```javascript
|
|
const PocketBase = window.PocketBase;
|
|
const pb = new PocketBase('https://pocketbase.ccllc.pro');
|
|
|
|
// Login
|
|
const authData = await pb.collection('users')
|
|
.authWithPassword(email, password);
|
|
|
|
// Get token
|
|
const token = authData.token || pb.authStore.exportSession()?.token;
|
|
|
|
// Logout
|
|
pb.authStore.clear();
|
|
```
|
|
|
|
**Rules:**
|
|
- ✅ Single PocketBase instance
|
|
- ✅ Store token in localStorage
|
|
- ✅ Use token in all API requests
|
|
- ❌ Do NOT create multiple PocketBase instances
|
|
- ❌ Do NOT expose secrets in frontend
|
|
|
|
---
|
|
|
|
## 7. Dotenv Configuration
|
|
|
|
### dotenv
|
|
|
|
**Version:** `^17.2.3` (pinned)
|
|
|
|
**Installation:**
|
|
```bash
|
|
bun add dotenv@^17.2.3
|
|
```
|
|
|
|
**Usage (Backend):**
|
|
```typescript
|
|
import dotenv from 'dotenv';
|
|
dotenv.config();
|
|
|
|
const PORT = process.env.PORT || 3005;
|
|
const REDIS_HOST = process.env.REDIS_HOST || 'localhost';
|
|
const REDIS_PORT = Number(process.env.REDIS_PORT || 6379);
|
|
```
|
|
|
|
**Environment Variables:**
|
|
```bash
|
|
# .env (gitignored)
|
|
PORT=3005
|
|
REDIS_HOST=localhost
|
|
REDIS_PORT=6379
|
|
REDIS_PASSWORD=
|
|
POCKETBASE_URL=https://pocketbase.ccllc.pro
|
|
```
|
|
|
|
**Rules:**
|
|
- ✅ Load from `.env` file
|
|
- ✅ Provide sensible defaults for development
|
|
- ✅ Document required variables in `.env.example`
|
|
- ❌ Do NOT commit `.env` to git
|
|
- ❌ Do NOT hardcode secrets
|
|
|
|
---
|
|
|
|
## 8. Development Server
|
|
|
|
### Watch Mode
|
|
|
|
**Command:**
|
|
```bash
|
|
bun --watch backend/server.ts
|
|
```
|
|
|
|
**Features:**
|
|
- Auto-restart on file changes
|
|
- Clear cache on restart
|
|
- Logs all requests
|
|
|
|
**Usage:**
|
|
- Start with `bun run dev` in one terminal
|
|
- Start CSS watcher with `bun run build:css` in another
|
|
- Changes auto-refresh immediately
|
|
|
|
---
|
|
|
|
## 9. Production Build
|
|
|
|
### Bun Bundler
|
|
|
|
**Command:**
|
|
```bash
|
|
bun build --target bun --outdir dist backend/server.ts
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
dist/
|
|
server.ts (bundled, optimized)
|
|
```
|
|
|
|
**Deployment:**
|
|
```bash
|
|
bun run dist/server.ts
|
|
```
|
|
|
|
**Rules:**
|
|
- ✅ Build before deploying
|
|
- ✅ Test built version locally
|
|
- ✅ Deploy from `dist/` folder
|
|
- ❌ Do NOT deploy source TypeScript files
|
|
- ❌ Do NOT use development server in production
|
|
|
|
---
|
|
|
|
## 10. Dependency Management
|
|
|
|
### Package.json Structure
|
|
|
|
**Required:**
|
|
```json
|
|
{
|
|
"name": "job-info-pb",
|
|
"version": "1.0.0",
|
|
"type": "module",
|
|
"private": true,
|
|
"scripts": {
|
|
"dev": "bun --watch backend/server.ts",
|
|
"start": "bun run backend/server.ts",
|
|
"build:css": "tailwindcss -i input.css -o frontend/output.css --watch",
|
|
"build": "bun build --target bun --outdir dist backend/server.ts"
|
|
},
|
|
"dependencies": {
|
|
"dotenv": "^17.2.3",
|
|
"hono": "^4.10.8",
|
|
"ioredis": "^5.x",
|
|
"pocketbase": "^0.26.5"
|
|
},
|
|
"devDependencies": {
|
|
"@types/bun": "latest",
|
|
"autoprefixer": "^10.4.23",
|
|
"postcss": "^8.5.6",
|
|
"tailwindcss": "^4.1.18"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Rules:**
|
|
- ✅ Pin versions for production stability
|
|
- ✅ Use `^` for compatible versions
|
|
- ✅ Regularly audit for security updates
|
|
- ❌ Do NOT use `*` or `latest` in production dependencies
|
|
- ❌ Do NOT add unnecessary dependencies
|
|
|
|
**Update Command:**
|
|
```bash
|
|
bun update # Check for updates
|
|
bun audit # Security audit
|
|
bun install --save-exact # Lock versions
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Enforcement & Validation
|
|
|
|
### Code Review Checklist
|
|
|
|
Before committing, verify:
|
|
|
|
- [ ] Uses Hono for all routes (no Express)
|
|
- [ ] Uses Tailwind for all styles (no inline styles)
|
|
- [ ] All functions have TypeScript types
|
|
- [ ] No `any` type annotations
|
|
- [ ] All dependencies pinned in package.json
|
|
- [ ] Cache implemented for GET endpoints
|
|
- [ ] Errors logged to file and console
|
|
- [ ] No hardcoded secrets
|
|
- [ ] No console.log() in production code (use proper logging)
|
|
- [ ] Follows naming conventions from RULES.md
|
|
|
|
### ESLint Rules (Optional but Recommended)
|
|
|
|
**.eslintrc.json:**
|
|
```json
|
|
{
|
|
"parser": "@typescript-eslint/parser",
|
|
"extends": ["eslint:recommended"],
|
|
"rules": {
|
|
"no-console": ["warn", { "allow": ["error", "warn"] }],
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"@typescript-eslint/explicit-function-return-types": "warn"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Troubleshooting
|
|
|
|
### Bun Issues
|
|
- **"Command not found"**: Add Bun to PATH
|
|
- **"Module not found"**: Run `bun install`
|
|
- **"Permission denied"**: Run `chmod +x` on files
|
|
|
|
### Hono Issues
|
|
- **Route not found**: Check route path spelling
|
|
- **Middleware not applied**: Order matters; add to top of file
|
|
|
|
### Tailwind Issues
|
|
- **Classes not applying**: Run `bun run build:css`
|
|
- **Cache issue**: Delete output.css and rebuild
|
|
|
|
### Redis Issues
|
|
- **Connection refused**: Ensure Redis/Valkey is running
|
|
- **Timeout**: Check credentials in .env
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Technology | Version | Rule |
|
|
|-----------|---------|------|
|
|
| Bun | latest | Runtime & build tool |
|
|
| Hono | ^4.10.8 | All web routes |
|
|
| PostCSS | ^8.5.6 | CSS processing |
|
|
| Tailwind | ^4.1.18 | Utility CSS |
|
|
| TypeScript | ^5 | Strict typing required |
|
|
| ioredis | ^5.x | Cache client |
|
|
| PocketBase | ^0.26.5 | Auth backend |
|
|
| dotenv | ^17.2.3 | Config management |
|
|
|
|
**All new code must use these exact versions and patterns.**
|