diff --git a/.github/copilot-instructions-bloated.md b/.github/copilot-instructions-bloated.md new file mode 100644 index 0000000..31488f0 --- /dev/null +++ b/.github/copilot-instructions-bloated.md @@ -0,0 +1,27 @@ +# Instructions for Copilot + +Please always be helpful and provide complete answers. When writing code, please use best practices and follow clean code principles. Always add comments to explain what the code does. Use meaningful variable names. Follow the DRY principle. Write unit tests when applicable. Use TypeScript for all new files. Follow our team's coding standards. Be thorough in your explanations. + +When working on this project, please note that we use: +- React 18 with TypeScript +- Node.js 20 for the backend +- Tailwind CSS for styling +- Jest for testing with React Testing Library for component tests +- ESLint with our custom config +- Prettier for formatting +- PostgreSQL for the database +- Express for the API server + +When writing tests for this project, please make sure that you follow our testing conventions. We use Jest as our testing framework and we prefer to use the React Testing Library for component tests. Tests should be placed in a __tests__ directory next to the source file they're testing. Please make sure to include both happy path and error cases. + +When working on API endpoints, please follow RESTful conventions. Use plural nouns for resource names. Use proper HTTP status codes. Always include error handling. Make sure to validate input data. Use middleware for authentication. Document any new endpoints. + +Please always use functional components in React. Use hooks for state management. Keep components small and focused. Extract reusable logic into custom hooks. Use TypeScript interfaces for all props. Prefer named exports over default exports. + +When writing code, please ensure that: +1. All functions have proper TypeScript types +2. Error messages are user-friendly +3. Sensitive data is never logged +4. Database queries use parameterized inputs +5. API responses follow our standard envelope format +6. Authentication is checked on all protected routes diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..67bf2b7 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,4 @@ +Stack: Node.js 20, TypeScript 5.4, Express, PostgreSQL. +Style: ESM modules, strict TypeScript, no `any` types. +Error handling: Use Result pattern, never throw in business logic. +Tests: Jest + RTL. Co-locate in __tests__/. Cover happy path + error cases. diff --git a/.github/copilot-setup-steps.yml b/.github/copilot-setup-steps.yml new file mode 100644 index 0000000..86940ba --- /dev/null +++ b/.github/copilot-setup-steps.yml @@ -0,0 +1,26 @@ +name: Copilot Setup Steps +on: workflow_dispatch + +jobs: + copilot-setup: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build project + run: npm run build + + - name: Verify TypeScript + run: npx tsc --noEmit + + - name: Run tests + run: npm test diff --git a/.github/prompts/api-design.prompt.md b/.github/prompts/api-design.prompt.md new file mode 100644 index 0000000..7fc26d2 --- /dev/null +++ b/.github/prompts/api-design.prompt.md @@ -0,0 +1,36 @@ +--- +description: "Use when designing or reviewing API endpoints" +--- + +# API Design Standards + +## URL Patterns +- Use plural nouns: /users, /orders +- Nest for relationships: /users/{id}/orders +- Use query params for filtering: /users?role=admin + +## Request/Response +- Always use JSON +- Wrap responses: { data: T, meta: {...} } +- Use HTTP status codes correctly +- Include request-id header + +## Versioning +- URL prefix: /api/v1/ +- Never break backwards compatibility in same version + +## Error Format +```json +{ + "error": { + "code": "VALIDATION_FAILED", + "message": "Human-readable message", + "details": [{ "field": "email", "issue": "invalid format" }] + } +} +``` + +## Authentication +- Bearer token in Authorization header +- 401 for missing/invalid token +- 403 for insufficient permissions diff --git a/.github/prompts/deploy-checklist.prompt.md b/.github/prompts/deploy-checklist.prompt.md new file mode 100644 index 0000000..dfdc7fb --- /dev/null +++ b/.github/prompts/deploy-checklist.prompt.md @@ -0,0 +1,31 @@ +--- +description: "Use when preparing or reviewing a deployment" +--- + +# Deploy Checklist + +## Pre-Deploy +- [ ] All tests pass on CI +- [ ] No TypeScript errors (`tsc --noEmit`) +- [ ] Database migrations reviewed and tested +- [ ] Environment variables documented for new features +- [ ] API docs updated for new/changed endpoints + +## Deploy Process +1. Merge PR to `main` +2. CI builds and runs full test suite +3. Docker image built and pushed to registry +4. Staging auto-deploys from `main` +5. Smoke test staging (health check + critical paths) +6. Production deploy via GitHub Actions workflow dispatch + +## Post-Deploy +- [ ] Monitor error rates for 15 minutes +- [ ] Check key metrics dashboard +- [ ] Verify new feature works in production +- [ ] Update status page if needed + +## Rollback +- Revert commit on `main` triggers auto-rollback +- Database: only additive migrations (never drop columns in same release) +- Feature flags: disable flag immediately if issues detected diff --git a/.github/prompts/testing.prompt.md b/.github/prompts/testing.prompt.md new file mode 100644 index 0000000..0cc2af8 --- /dev/null +++ b/.github/prompts/testing.prompt.md @@ -0,0 +1,35 @@ +--- +description: "Use when writing or reviewing tests" +--- + +# Testing Standards + +## Framework +- Jest for unit/integration tests +- React Testing Library for components +- Supertest for API endpoint tests + +## File Placement +- Co-locate: `src/services/__tests__/userService.test.ts` +- Name: `{module}.test.ts` + +## Coverage Requirements +- Happy path (expected inputs → expected outputs) +- Error cases (invalid input, missing data, network failures) +- Edge cases (empty arrays, null values, boundary values) + +## Patterns +- Arrange / Act / Assert structure +- One assertion concept per test +- Mock external dependencies only (DB, APIs) +- Never mock the module under test +- Use factories for test data: `createUser({ overrides })` + +## Naming +```typescript +describe('filterActiveUsers', () => { + it('returns only users where isActive is true', () => { ... }); + it('returns empty array when no users are active', () => { ... }); + it('sorts by lastLoginAt descending', () => { ... }); +}); +``` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..11279f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +build/ +*.js.map +.env +.env.local diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..24c1587 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "GitHub.copilot", + "GitHub.copilot-chat" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4f1dd65 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "typescript.preferences.importModuleSpecifier": "relative" +} diff --git a/README.md b/README.md index 87a4a5b..3eba6db 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,67 @@ -# token-optimization-demo -I am used to demo token optimization strategies +# Token Optimization Demos + +Companion repository for the YouTube video: **"The 5 Rules of Token Optimization Every Developer MUST Know (GitHub Copilot)"** + +📺 Watch the video: [YouTube Link] + +## What's In This Repo + +This repo is a sample TypeScript/Node.js project specifically structured to demonstrate token optimization techniques for GitHub Copilot. Use it to follow along with the video or as a template for your own projects. + +### Demo Files + +| File | Used In | Purpose | +|------|---------|---------| +| `.github/copilot-instructions.md` | Rule 1 & 2 | The optimized (lean) instructions file | +| `.github/copilot-instructions-bloated.md` | Rule 1 | The "before" bloated version for comparison | +| `.github/prompts/api-design.prompt.md` | Rule 2 | On-demand API design prompt file | +| `.github/prompts/testing.prompt.md` | Rule 2 | On-demand testing prompt file | +| `.github/prompts/deploy-checklist.prompt.md` | Rule 2 | On-demand deploy prompt file | +| `.github/copilot-setup-steps.yml` | Rule 5 | Coding agent setup file | +| `src/services/userService.ts` | Rule 4 | Contains `filterActiveUsersByLastLogin` | +| `src/models/user.ts` | Rule 4 | User type definition | +| `docs/content-exclusion-patterns.md` | Rule 5 | Starter list of exclusion patterns | +| `docs/caveman-instructions-examples.md` | Rule 4 | Before/after instruction compression examples | + +### Project Structure + +``` +.github/ +├── copilot-instructions.md ← Optimized (lean) - ALWAYS LOADED +├── copilot-instructions-bloated.md ← "Before" version for demo comparison +├── copilot-setup-steps.yml ← Coding agent setup +└── prompts/ + ├── api-design.prompt.md ← On-demand: API guidelines + ├── testing.prompt.md ← On-demand: Testing standards + └── deploy-checklist.prompt.md ← On-demand: Deploy process +src/ +├── models/ +│ └── user.ts ← User interface +├── services/ +│ └── userService.ts ← filterActiveUsersByLastLogin demo +├── utils/ +│ └── helpers.ts ← Utility functions +└── __tests__/ + └── userService.test.ts ← Co-located test example +docs/ +├── content-exclusion-patterns.md ← Copy-paste exclusion patterns +└── caveman-instructions-examples.md ← Before/after compression guide +``` + +## How To Use + +1. **Clone this repo** and open in VS Code +2. **Follow along with the video** — each Rule section references specific files +3. **Copy what you need** — the instruction files, prompt files, and patterns are designed to be adapted for your projects + +## The 5 Rules + +1. **Treat Context Like a Budget** — Every token costs. Be intentional. +2. **Load On-Demand, Not Always-On** — Keep instructions lean, details in prompt files. +3. **Right-Size Your Model** — Lightweight models for simple, premium for complex. +4. **Be Precise, Not Polite** — Caveman style. No filler. Slash commands. +5. **Clean Your Context** — /clear between tasks. Close tabs. Exclude noise. + +## License + +MIT — use these patterns in your own projects! diff --git a/docs/caveman-instructions-examples.md b/docs/caveman-instructions-examples.md new file mode 100644 index 0000000..d0dc736 --- /dev/null +++ b/docs/caveman-instructions-examples.md @@ -0,0 +1,126 @@ +# Caveman-Style Instruction Compression + +## The Principle + +Write instructions in compressed, article-free, fragment style. +Drop: articles (a, an, the), filler words (just, really, basically, please), pleasantries, hedging. +Fragments OK. Short synonyms. Code unchanged. + +--- + +## Examples: Before & After + +### Example 1: General Instructions + +**❌ BEFORE (~150 tokens):** +```markdown +# Instructions for Copilot + +Please always be helpful and provide complete answers. When writing code, +please use best practices and follow clean code principles. Always add +comments to explain what the code does. Use meaningful variable names. +Follow the DRY principle. Write unit tests when applicable. Use TypeScript +for all new files. Follow our team's coding standards. Be thorough in +your explanations. + +When working on this project, please note that we use: +- React 18 with TypeScript +- Tailwind CSS for styling +- Jest for testing +- ESLint with our custom config +- Prettier for formatting +``` + +**✅ AFTER (~40 tokens):** +```markdown +Stack: React 18, TypeScript, Tailwind, Jest, ESLint. +Style: Functional components, named exports, error boundaries at route level. +Tests: Co-locate with source. Use Testing Library. Mock external deps only. +``` + +**Savings: ~73% fewer tokens on EVERY request** + +--- + +### Example 2: Testing Instructions + +**❌ BEFORE (~70 tokens):** +```markdown +When writing tests for this project, please make sure that you follow our +testing conventions. We use Jest as our testing framework and we prefer to +use the React Testing Library for component tests. Tests should be placed +in a __tests__ directory next to the source file they're testing. Please +make sure to include both happy path and error cases. +``` + +**✅ AFTER (~15 tokens):** +```markdown +Tests: Jest + RTL. Co-locate in __tests__/. Cover happy path + error cases. +``` + +**Savings: ~78% fewer tokens on EVERY request** + +--- + +### Example 3: API Standards + +**❌ BEFORE (~90 tokens):** +```markdown +When working on API endpoints, please follow RESTful conventions. Use plural +nouns for resource names. Use proper HTTP status codes. Always include error +handling. Make sure to validate input data. Use middleware for authentication. +Document any new endpoints. Wrap all responses in our standard envelope format +with data and meta fields. +``` + +**✅ AFTER (~25 tokens):** +```markdown +API: RESTful, plural nouns, proper HTTP codes. +Errors: { error: { code, message, details[] } } +Responses: { data: T, meta: { timestamp, requestId } } +Auth: Bearer token middleware on protected routes. +``` + +**Savings: ~72% fewer tokens on EVERY request** + +--- + +### Example 4: Project Context + +**❌ BEFORE (~110 tokens):** +```markdown +This is a task management web application built with React and Node.js. +We use MongoDB as our database. The frontend is in the /src directory +and the backend API server is in the /server directory. We follow a +microservices architecture with separate services for authentication, +task management, and notifications. Each service communicates via REST APIs. +The project uses Docker for containerization and GitHub Actions for CI/CD. +``` + +**✅ AFTER (~40 tokens):** +```markdown +Task management app. React frontend (src/), Node.js API (server/). +DB: MongoDB. Architecture: microservices (auth, tasks, notifications) via REST. +Infra: Docker, GitHub Actions CI/CD. +``` + +**Savings: ~64% fewer tokens on EVERY request** + +--- + +## The Math + +If your instructions file saves 100 tokens per request, and you make 30 requests/day: +- **Daily savings:** 3,000 tokens +- **Monthly savings:** ~90,000 tokens +- **For a 10-person team:** ~900,000 tokens/month + +At premium model rates, that's real money saved — and your context window has more room for actual code. + +## Rules of Thumb + +1. If the model already knows it (SOLID, clean code, be helpful) — delete it +2. If it's in package.json or tsconfig (framework versions, settings) — don't repeat it +3. If it only applies to some files — move it to a prompt file, not instructions +4. If it's longer than 3 lines for one concept — compress it +5. Read it aloud: if it sounds like an email, rewrite it as a telegram diff --git a/docs/content-exclusion-patterns.md b/docs/content-exclusion-patterns.md new file mode 100644 index 0000000..30644a8 --- /dev/null +++ b/docs/content-exclusion-patterns.md @@ -0,0 +1,82 @@ +# Content Exclusion Patterns + +Copy these into your repository or organization settings on GitHub.com under: +**Settings → Copilot → Content Exclusion** + +> ⚠️ There is NO `.copilotignore` file. Exclusions are configured in GitHub settings only. + +## Starter Patterns (Copy These) + +```yaml +# Build artifacts +**/dist/** +**/build/** +**/out/** +**/.next/** + +# Dependencies +**/node_modules/** +**/vendor/** + +# Generated code +**/*.generated.ts +**/*.generated.js +**/generated/** +**/__generated__/** + +# Database migrations (auto-generated, noisy) +**/migrations/** + +# Large data files +**/*.sql +**/*.csv +**/*.json.bak + +# Minified files +**/*.min.js +**/*.min.css + +# Lock files (large, low signal) +**/pnpm-lock.yaml +**/package-lock.json +**/yarn.lock + +# Environment and secrets +**/.env* +**/secrets/** + +# Binary/media assets +**/*.png +**/*.jpg +**/*.gif +**/*.svg +**/*.woff +**/*.ttf +**/*.pdf +``` + +## Organization-Level Patterns + +If you're an org admin, set these once for all repos: + +```yaml +# Applied to ALL repos in the org +"*": + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" + - "**/*.min.js" + - "**/vendor/**" + +# Applied to specific repos +"https://github.com/your-org/legacy-app": + - "**/old-code/**" + - "**/deprecated/**" +``` + +## Important Caveats + +1. Content exclusions currently apply to **code completions and Copilot Chat** +2. They do **NOT** apply to Copilot Agent Mode or Copilot CLI (as of mid-2026) +3. Always verify current scope in GitHub docs — this may change +4. Exclusions take effect immediately, no restart needed diff --git a/package.json b/package.json new file mode 100644 index 0000000..bd2d0f2 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "token-optimization-demos", + "version": "1.0.0", + "description": "Companion repo for Token Optimization YouTube video by Mickey Gousset", + "main": "src/index.ts", + "type": "module", + "scripts": { + "build": "tsc", + "test": "jest", + "lint": "eslint src/", + "dev": "tsx watch src/index.ts" + }, + "keywords": ["github-copilot", "token-optimization", "ai-development"], + "author": "Mickey Gousset", + "license": "MIT", + "devDependencies": { + "@types/jest": "^29.5.0", + "@types/node": "^20.0.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.0", + "typescript": "^5.4.0", + "eslint": "^8.57.0", + "tsx": "^4.7.0" + } +} diff --git a/src/__tests__/userService.test.ts b/src/__tests__/userService.test.ts new file mode 100644 index 0000000..2285c7f --- /dev/null +++ b/src/__tests__/userService.test.ts @@ -0,0 +1,59 @@ +import { filterActiveUsersByLastLogin } from "../services/userService"; +import { User } from "../models/user"; + +function createUser(overrides: Partial = {}): User { + return { + id: "user-1", + email: "test@example.com", + displayName: "Test User", + isActive: true, + lastLoginAt: new Date("2026-01-15"), + createdAt: new Date("2025-06-01"), + role: "member", + ...overrides, + }; +} + +describe("filterActiveUsersByLastLogin", () => { + it("returns only users where isActive is true", () => { + const users = [ + createUser({ id: "1", isActive: true }), + createUser({ id: "2", isActive: false }), + createUser({ id: "3", isActive: true }), + ]; + + const result = filterActiveUsersByLastLogin(users); + + expect(result).toHaveLength(2); + expect(result.every((u) => u.isActive)).toBe(true); + }); + + it("sorts by lastLoginAt descending (most recent first)", () => { + const users = [ + createUser({ id: "1", lastLoginAt: new Date("2026-01-01") }), + createUser({ id: "2", lastLoginAt: new Date("2026-03-15") }), + createUser({ id: "3", lastLoginAt: new Date("2026-02-10") }), + ]; + + const result = filterActiveUsersByLastLogin(users); + + expect(result[0].id).toBe("2"); + expect(result[1].id).toBe("3"); + expect(result[2].id).toBe("1"); + }); + + it("returns empty array when input is empty", () => { + const result = filterActiveUsersByLastLogin([]); + expect(result).toEqual([]); + }); + + it("returns empty array when no users are active", () => { + const users = [ + createUser({ id: "1", isActive: false }), + createUser({ id: "2", isActive: false }), + ]; + + const result = filterActiveUsersByLastLogin(users); + expect(result).toEqual([]); + }); +}); diff --git a/src/config/database.ts b/src/config/database.ts new file mode 100644 index 0000000..d8ad235 --- /dev/null +++ b/src/config/database.ts @@ -0,0 +1,19 @@ +export interface DatabaseConfig { + host: string; + port: number; + database: string; + user: string; + password: string; + ssl: boolean; +} + +export function getDatabaseConfig(): DatabaseConfig { + return { + host: process.env.DB_HOST || "localhost", + port: parseInt(process.env.DB_PORT || "5432"), + database: process.env.DB_NAME || "token_demo", + user: process.env.DB_USER || "postgres", + password: process.env.DB_PASSWORD || "", + ssl: process.env.DB_SSL === "true", + }; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5cbb860 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,23 @@ +import express from "express"; +import { generateRequestId, wrapResponse } from "./utils/helpers"; + +const app = express(); +app.use(express.json()); + +// Request ID middleware +app.use((req, res, next) => { + req.headers["x-request-id"] = generateRequestId(); + next(); +}); + +// Health check +app.get("/health", (req, res) => { + res.json(wrapResponse({ status: "ok" })); +}); + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); + +export default app; diff --git a/src/models/user.ts b/src/models/user.ts new file mode 100644 index 0000000..38c6d1a --- /dev/null +++ b/src/models/user.ts @@ -0,0 +1,15 @@ +export interface User { + id: string; + email: string; + displayName: string; + isActive: boolean; + lastLoginAt: Date; + createdAt: Date; + role: "admin" | "member" | "viewer"; +} + +export interface UserFilters { + isActive?: boolean; + role?: User["role"]; + loginAfter?: Date; +} diff --git a/src/routes/users.ts b/src/routes/users.ts new file mode 100644 index 0000000..d666f1d --- /dev/null +++ b/src/routes/users.ts @@ -0,0 +1,24 @@ +import { Router } from "express"; +import { filterActiveUsersByLastLogin } from "../services/userService"; +import { wrapResponse } from "../utils/helpers"; + +const router = Router(); + +// GET /api/v1/users — list active users +router.get("/", async (req, res) => { + try { + // In production, fetch from database + const allUsers: any[] = []; // placeholder + const activeUsers = filterActiveUsersByLastLogin(allUsers); + res.json(wrapResponse(activeUsers, { count: activeUsers.length })); + } catch (error) { + res.status(500).json({ + error: { + code: "INTERNAL_ERROR", + message: "Failed to fetch users", + }, + }); + } +}); + +export default router; diff --git a/src/services/billingService.ts b/src/services/billingService.ts new file mode 100644 index 0000000..36bf256 --- /dev/null +++ b/src/services/billingService.ts @@ -0,0 +1,20 @@ +import { User } from "../models/user"; + +export interface BillingPlan { + id: string; + name: string; + monthlyPrice: number; + features: string[]; +} + +export function calculateInvoice(user: User, plan: BillingPlan): number { + // Pro-rate based on days remaining in billing cycle + const today = new Date(); + const daysInMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate(); + const daysRemaining = daysInMonth - today.getDate(); + return (plan.monthlyPrice / daysInMonth) * daysRemaining; +} + +export function formatCurrency(amount: number): string { + return `$${amount.toFixed(2)}`; +} diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts new file mode 100644 index 0000000..bd1960d --- /dev/null +++ b/src/services/notificationService.ts @@ -0,0 +1,22 @@ +export interface NotificationPayload { + userId: string; + type: "email" | "push" | "sms"; + subject: string; + body: string; + scheduledAt?: Date; +} + +export async function sendNotification(payload: NotificationPayload): Promise { + // Placeholder — in production this would call notification service + console.log(`Sending ${payload.type} to ${payload.userId}: ${payload.subject}`); + return true; +} + +export function buildWelcomeNotification(userId: string): NotificationPayload { + return { + userId, + type: "email", + subject: "Welcome to the platform!", + body: "Thanks for signing up. Here's how to get started...", + }; +} diff --git a/src/services/userService.ts b/src/services/userService.ts new file mode 100644 index 0000000..6297568 --- /dev/null +++ b/src/services/userService.ts @@ -0,0 +1,26 @@ +import { User } from "../models/user"; + +/** + * Filters users to only active accounts and sorts by most recent login. + * Returns empty array if input is empty or no active users found. + */ +export function filterActiveUsersByLastLogin(users: User[]): User[] { + if (!users || users.length === 0) { + return []; + } + + return users + .filter((user) => user.isActive === true) + .sort( + (a, b) => + new Date(b.lastLoginAt).getTime() - new Date(a.lastLoginAt).getTime() + ); +} + +/** + * Poorly named version — for demo comparison. + * Shows how vague names force Copilot to guess intent. + */ +export function processData(data: any[]): any[] { + return data.filter((d) => d.isActive).sort((a, b) => b.lastLoginAt - a.lastLoginAt); +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts new file mode 100644 index 0000000..1852135 --- /dev/null +++ b/src/utils/helpers.ts @@ -0,0 +1,34 @@ +/** + * Formats a date for display in API responses. + */ +export function formatDate(date: Date): string { + return date.toISOString().split("T")[0]; +} + +/** + * Generates a unique request ID for tracing. + */ +export function generateRequestId(): string { + return `req_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; +} + +/** + * Validates email format. + */ +export function isValidEmail(email: string): boolean { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); +} + +/** + * Wraps a value in the standard API response envelope. + */ +export function wrapResponse(data: T, meta?: Record) { + return { + data, + meta: { + timestamp: new Date().toISOString(), + ...meta, + }, + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..389a5e5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noAny": true, + "outDir": "./dist", + "rootDir": "./src", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}