diff --git a/architecture/tad.md b/architecture/tad.md new file mode 100644 index 0000000..b6fb406 --- /dev/null +++ b/architecture/tad.md @@ -0,0 +1,147 @@ +# Tech Architecture Document + +## Stack + +| Layer | Technology | Version / Notes | +|---|---|---| +| Runtime | Node.js | LTS (≥ 20.x) | +| Language | TypeScript | ≥ 5.x, compiled to CommonJS or ESM for execution | +| Framework | Express.js | ≥ 4.x | +| Test runner | Jest or Vitest | Unit tests for counting logic | +| Build tooling | `tsc` (TypeScript compiler) | No bundler required | +| Package manager | npm | Standard `npm install && npm start` workflow | + +## Components + +### 1. HTTP Server (`src/server.ts`) +Bootstraps the Express application, registers middleware (JSON body-parser, error handler), mounts routes, and starts listening on `process.env.PORT` (default `3000`). + +### 2. Router (`src/routes/count.ts`, `src/routes/health.ts`) +Declares the two route handlers: +- **POST /count** — validates the request body, calls the counting service, and returns the result. +- **GET /health** — returns `{ "status": "ok" }` with HTTP 200. + +### 3. Counting Service (`src/services/counter.ts`) +Pure functions with no side-effects: +- `countWords(text: string): number` — trims the input, splits on `/\s+/`, filters empty tokens, returns the token count. +- `countCharacters(text: string): number` — returns `text.length` (UTF-16 code units, spaces included). + +### 4. Validation Middleware (`src/middleware/validateCount.ts`) +Express middleware applied to `POST /count` that checks: +1. Body is present and `Content-Type: application/json` was parsed successfully. +2. `text` field exists. +3. `text` is of type `string`. +Returns HTTP 400 with a descriptive `{ "error": string }` payload on any violation. + +### 5. Error Handler (`src/middleware/errorHandler.ts`) +Global Express error-handling middleware (four-argument signature) that catches any unhandled errors — including JSON parse errors emitted by `express.json()` — and returns a 400 response with a descriptive message. + +### 6. Unit Tests (`src/__tests__/counter.test.ts`) +Tests for `countWords` and `countCharacters` covering: normal input, multiple/consecutive whitespace, leading/trailing whitespace, empty string, and character count including spaces. + +## Data Model + +No persistence layer exists. All data is transient per request. + +### Request — `POST /count` +```json +{ + "text": "hello world" +} +``` + +### Response — `POST /count` (HTTP 200) +```json +{ + "words": 2, + "characters": 11 +} +``` + +### Response — `GET /health` (HTTP 200) +```json +{ + "status": "ok" +} +``` + +### Error Response (HTTP 400) +```json +{ + "error": "" +} +``` + +## Integrations + +None. The service is fully self-contained with no external APIs, databases, message queues, or third-party services. + +## Architecture Diagram + +```mermaid +graph TD + Client["HTTP Client\n(curl / test suite / browser)"] + + subgraph "WCNT API (Node.js / Express)" + Server["Express App\nsrc/server.ts"] + HealthRoute["GET /health\nsrc/routes/health.ts"] + CountRoute["POST /count\nsrc/routes/count.ts"] + ValidateMW["Validation Middleware\nsrc/middleware/validateCount.ts"] + ErrMW["Error Handler\nsrc/middleware/errorHandler.ts"] + CounterSvc["Counting Service\nsrc/services/counter.ts\ncountWords() · countCharacters()"] + end + + Client -->|HTTP request| Server + Server --> HealthRoute + Server --> ValidateMW + ValidateMW -->|valid| CountRoute + ValidateMW -->|invalid| ErrMW + CountRoute --> CounterSvc + CounterSvc -->|result| CountRoute + CountRoute -->|200 JSON| Client + HealthRoute -->|200 JSON| Client + ErrMW -->|400 JSON| Client +``` + +## Request / Response Data Flow + +```mermaid +sequenceDiagram + participant C as HTTP Client + participant E as Express App + participant V as Validation MW + participant R as /count Route + participant S as Counter Service + + C->>E: POST /count { "text": "hello world" } + E->>V: parse JSON body + alt body invalid or text missing/non-string + V-->>C: HTTP 400 { "error": "..." } + else body valid + V->>R: next() + R->>S: countWords(text), countCharacters(text) + S-->>R: { words: 2, characters: 11 } + R-->>C: HTTP 200 { "words": 2, "characters": 11 } + end +``` + +## NFRs + +| ID | Category | How Addressed | +|---|---|---| +| NFR-1 | Runtime | Node.js LTS; TypeScript source compiled via `tsc` before execution. | +| NFR-2 | Framework | Express.js; `express.json()` middleware for body parsing. | +| NFR-3 | Persistence | No database; all state is request-scoped and discarded after the response. | +| NFR-4 | Security | No auth layer; endpoint is open by design for demo use. | +| NFR-5 | Testing | Jest/Vitest unit tests for `countWords` and `countCharacters`; executed in CI via `npm test`. | +| NFR-6 | Simplicity | Single repository; zero non-Node dependencies beyond Express and the test runner. | +| NFR-7 | Portability | No native add-ons or OS-specific code; runs inside any standard `node:lts` Docker image or bare Node environment. | + +## Risks + +| ID | Risk | Likelihood | Impact | Mitigation | +|---|---|---|---|---| +| R-1 | Unicode surrogate pairs counted as 2 characters | Low | Low | Documented as Assumption A4; acceptable for demo scope. Upgrade to `Intl.Segmenter` if true grapheme count is ever required. | +| R-2 | Malformed `Content-Type` header bypasses JSON parsing | Low | Low | Global error handler catches `SyntaxError` from `express.json()` and returns 400. | +| R-3 | Port conflict on default `3000` | Medium | Low | `PORT` environment variable allows any port; documented in README / startup instructions. | +| R-4 | Dependency vulnerabilities in Express | Medium | Medium | Kept on latest stable Express 4.x; `npm audit` run in CI; minimal transitive dependency surface. |