welp instrumentation ✅, some refactoring remains
This commit is contained in:
187
AGENTS.md
187
AGENTS.md
@@ -1,7 +1,188 @@
|
|||||||
# Laws
|
# AGENTS.md
|
||||||
|
|
||||||
This document defines the laws, principles, and rule sets that govern this codebase. Any agent or developer making changes has to adhere to these rules.
|
This document defines the laws, principles, and rule sets that govern this codebase. Any agent or developer making changes has to adhere to these rules.
|
||||||
|
|
||||||
Before starting off – Read the README.md file to understand the project's goals and objectives.
|
**Before starting off** — Read the README.md file to understand the project's goals and objectives.
|
||||||
|
|
||||||
TODO – HAVE TO WRITE THESE
|
---
|
||||||
|
|
||||||
|
## Agent Rules (Override Everything)
|
||||||
|
|
||||||
|
1. **No testing by yourself** — All testing is done by the team.
|
||||||
|
2. **No assumptions about code or domain logic** — Always confirm and be sure before making changes.
|
||||||
|
3. **No running scripts** — Do not run build, dev, test, or migrate scripts unless explicitly approved.
|
||||||
|
|
||||||
|
More rules are only to be added by the human, in case such a suggestion becomes viable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Project Overview
|
||||||
|
|
||||||
|
- **Monorepo**: Turbo repo
|
||||||
|
- **Package Manager**: pnpm
|
||||||
|
- **Language**: TypeScript everywhere
|
||||||
|
- **Node**: >= 24
|
||||||
|
|
||||||
|
> **Note**: The monorepo architecture is currently in flux. Some patterns are being refined; follow existing conventions and confirm before introducing new ones.
|
||||||
|
|
||||||
|
### Applications
|
||||||
|
|
||||||
|
| App | Purpose |
|
||||||
|
| ---------------- | ----------------------------------------------------------------------------------- |
|
||||||
|
| `apps/main` | SvelteKit UI — the primary user-facing application |
|
||||||
|
| `apps/processor` | Hono web server — intended for asynchronous processing (jobs, workers). Currently minimal; structure is evolving. |
|
||||||
|
|
||||||
|
### Packages
|
||||||
|
|
||||||
|
Packages live under `packages/` and are **standalone, modular** pieces consumed by apps:
|
||||||
|
|
||||||
|
| Package | Purpose |
|
||||||
|
| ------------- | -------------------------------------------- |
|
||||||
|
| `@pkg/logic` | Domain logic (DDD, controllers, repositories) |
|
||||||
|
| `@pkg/db` | Drizzle schema, database access |
|
||||||
|
| `@pkg/logger` | Logging, `getError` for error construction |
|
||||||
|
| `@pkg/result` | Result type, `ERROR_CODES`, `tryCatch` |
|
||||||
|
| `@pkg/keystore` | Redis instance (sessions, 2FA, etc.) |
|
||||||
|
| `@pkg/settings` | App settings / env config |
|
||||||
|
|
||||||
|
### Data Stores
|
||||||
|
|
||||||
|
- **PostgreSQL (Drizzle)** — Primary relational data (auth, users, accounts, etc.)
|
||||||
|
- **Redis (Valkey)** — Sessions, 2FA verification state (via `@pkg/keystore`)
|
||||||
|
|
||||||
|
Additional stores (NoSQL DBs, R2, etc.) may be introduced later. Follow existing domain patterns when adding new data access.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The Logic Package: DDD + Layered Architecture
|
||||||
|
|
||||||
|
The `@pkg/logic` package contains **all domain logic**. It follows:
|
||||||
|
|
||||||
|
1. **Domain-Driven Design (DDD)** — Bounded contexts as domains
|
||||||
|
2. **Layered architecture** — Clear separation of concerns
|
||||||
|
3. **Result-style error handling** — Errors as values; avoid try-catch in domain code
|
||||||
|
|
||||||
|
### Domain Structure
|
||||||
|
|
||||||
|
Each domain is a folder under `packages/logic/domains/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
domains/
|
||||||
|
<domain-name>/
|
||||||
|
data.ts # Types, schemas (Valibot)
|
||||||
|
repository.ts # Data access
|
||||||
|
controller.ts # Use cases / application logic
|
||||||
|
router.ts # HTTP routes (when exposed via API)
|
||||||
|
errors.ts # Domain-specific error constructors (using getError)
|
||||||
|
```
|
||||||
|
|
||||||
|
Not every domain has a router (e.g. auth uses `config.base.ts` with better-auth). Add new domains as needed; mirror existing patterns.
|
||||||
|
|
||||||
|
### Path Aliases (logic package)
|
||||||
|
|
||||||
|
- `@/*` → `./*`
|
||||||
|
- `@domains/*` → `./domains/*`
|
||||||
|
- `@core/*` → `./core/*`
|
||||||
|
|
||||||
|
### Flow Execution Context
|
||||||
|
|
||||||
|
Domain operations receive a `FlowExecCtx` (`fctx`) for tracing and audit:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type FlowExecCtx = {
|
||||||
|
flowId: string;
|
||||||
|
userId?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Error Handling: Result Pattern (neverthrow)
|
||||||
|
|
||||||
|
Errors are **values**, not exceptions. The codebase uses Result-style handling.
|
||||||
|
|
||||||
|
### Current Conventions
|
||||||
|
|
||||||
|
1. **Logic package** — Uses `neverthrow` (`ResultAsync`, `okAsync`, `errAsync`) for async operations that may fail.
|
||||||
|
2. **`@pkg/result`** — Provides `Result<T, Err>`, `ERROR_CODES`, and `tryCatch()`. The `Result` type is legacy; as the project is moving toward neverthrow. Use `ERROR_CODES` for consistent error codes.
|
||||||
|
3. **`getError()`** — From `@pkg/logger`. Use at boundaries when converting a thrown error to an `Err` object:
|
||||||
|
`return getError({ code: ERROR_CODES.XXX, message: "...", description: "...", detail: "..." }, e)`.
|
||||||
|
4. **Domain errors** — Each domain has an `errors.ts` that exports error constructors built with `getError`. Use these instead of ad-hoc error objects.
|
||||||
|
5. **Check before use** — Always check `result.isOk()` / `result.isErr()` before using `result.value`; never assume success.
|
||||||
|
|
||||||
|
### Err Shape
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Err = {
|
||||||
|
flowId?: string;
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
description: string;
|
||||||
|
detail: string;
|
||||||
|
actionable?: boolean;
|
||||||
|
error?: any;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Frontend (Main App)
|
||||||
|
|
||||||
|
The main app is a **SvelteKit** application with a domain-driven UI structure.
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
- **Routes**: File-based routing under `src/routes/`. Layout groups (e.g. `(main)`, `auth`) wrap related pages.
|
||||||
|
- **Domain-driven UI**: Feature code lives under `src/lib/domains/<domain>/` — each domain has its own folder with view models and components.
|
||||||
|
- **View Model (VM) pattern**: Domain logic and state for a screen live in `*.vm.svelte.ts` classes. VMs hold reactive state (`$state`), orchestrate API calls, and expose methods. Pages import and use a VM instance.
|
||||||
|
- **API layer**: A typed Hono client (`hc`) is used against a Router composed from logic package routers. The API is defined in `$lib/api.ts` and proxied through SvelteKit route handlers at `/api/v1/[...paths]`. Auth/session context is injected at the proxy layer.
|
||||||
|
|
||||||
|
### Context Injection
|
||||||
|
|
||||||
|
Logic routers receive `user`, `session`, and `fCtx` via `c.env.locals`. The shape is defined in `HonoContext` (`@core/hono.helpers`). The API proxy builds this context from the session before forwarding requests.
|
||||||
|
|
||||||
|
### Global Stores
|
||||||
|
|
||||||
|
Shared state (e.g. `apiClient`, `user`, `session`, `breadcrumbs`) lives in `$lib/global.stores.ts`.
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
- Pages are thin: they mount a VM, render components, and wire up lifecycle.
|
||||||
|
- VMs own async flows, polling, and error handling for their domain.
|
||||||
|
- UI components under `$lib/components/` are shared; domain-specific components live in `$lib/domains/<domain>/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Processor App
|
||||||
|
|
||||||
|
The processor is a **Hono** server intended for **background workers** and async jobs. Its structure is still evolving.
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
- Minimal Hono server; no BullMQ or job processors yet.
|
||||||
|
- When workers are added, processing logic should live under `src/domains/<domain>/` and call into `@pkg/logic` controllers and repositories.
|
||||||
|
- HTTP routes will expose internal APIs (e.g. task status, webhooks), secured (e.g. API key), for use by the main app or external services.
|
||||||
|
- Async work should be triggered by calling the processor HTTP API, not by importing job queues in the main app or logic package.
|
||||||
|
|
||||||
|
### Conventions (when implemented)
|
||||||
|
|
||||||
|
- The worker processes jobs; do not block it with long-running HTTP logic.
|
||||||
|
- Job payloads should be validated before processing.
|
||||||
|
- Follow existing domain patterns for controllers and repositories.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Validation & Schemas
|
||||||
|
|
||||||
|
- **Valibot** is used for schema validation in the logic package.
|
||||||
|
- Domain data types are defined in `data.ts` per domain.
|
||||||
|
- Use `v.InferOutput<typeof Schema>` for TypeScript types.
|
||||||
|
- Routers use `sValidator` from `@hono/standard-validator` with Valibot schemas for request validation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Package Naming
|
||||||
|
|
||||||
|
- Apps: `@apps/*` (e.g. `@apps/main`, `@apps/processor`)
|
||||||
|
- Packages: `@pkg/*` (e.g. `@pkg/logic`, `@pkg/db`, `@pkg/logger`)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const auth = betterAuth({
|
|||||||
log: (level, message, metadata) => {
|
log: (level, message, metadata) => {
|
||||||
logger.log(level, message, metadata);
|
logger.log(level, message, metadata);
|
||||||
},
|
},
|
||||||
level: settings.isDevelopment ? "debug" : "info",
|
level: "debug",
|
||||||
},
|
},
|
||||||
database: drizzleAdapter(db, { provider: "pg", schema: { ...schema } }),
|
database: drizzleAdapter(db, { provider: "pg", schema: { ...schema } }),
|
||||||
secondaryStorage: {
|
secondaryStorage: {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export class AuthController {
|
|||||||
logger.debug("Transformed URL", { ...fctx, transformedUrl });
|
logger.debug("Transformed URL", { ...fctx, transformedUrl });
|
||||||
|
|
||||||
// Simulate email sending with 90/10 success/failure
|
// Simulate email sending with 90/10 success/failure
|
||||||
const success = Math.random() > 0.1;
|
const success = true;
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logger.error("Failed to send magic link email", {
|
logger.error("Failed to send magic link email", {
|
||||||
@@ -135,7 +135,7 @@ export class AuthController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Magic link email sent successfully", {
|
logger.info("Magic link email sent successfully (NOT REALLY)", {
|
||||||
...fctx,
|
...fctx,
|
||||||
email,
|
email,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ function loadSettings(): Settings {
|
|||||||
databaseUrl: getEnv("DATABASE_URL"),
|
databaseUrl: getEnv("DATABASE_URL"),
|
||||||
|
|
||||||
internalApiKey: getEnv("INTERNAL_API_KEY"),
|
internalApiKey: getEnv("INTERNAL_API_KEY"),
|
||||||
|
debugKey: getEnv("DEBUG_KEY"),
|
||||||
|
|
||||||
processorApiUrl: getEnv("PROCESSOR_API_URL", "http://localhost:3000"),
|
processorApiUrl: getEnv("PROCESSOR_API_URL", "http://localhost:3000"),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user