making strides in the device and link domain setup

This commit is contained in:
user
2026-03-27 23:58:06 +02:00
parent 8c45efc92e
commit c7c303a934
17 changed files with 1202 additions and 198 deletions

183
AGENTS.md
View File

@@ -17,182 +17,57 @@ More rules are only to be added by the human, in case such a suggestion becomes
---
## 1. Project Overview
## 1. Stack
- **Monorepo**: Turbo repo
- **Package Manager**: pnpm
- **Language**: TypeScript everywhere
- **Node**: >= 24
### 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.
- **Monorepo**: Turborepo + pnpm
- **Language**: TypeScript everywhere, Node >= 24
- **Apps**: `@apps/main` (SvelteKit), `@apps/front` (Hono), `@apps/orchestrator` (Hono)
- **Packages**: `@pkg/logic`, `@pkg/db`, `@pkg/logger`, `@pkg/result`, `@pkg/keystore`, `@pkg/settings`
- **DB**: PostgreSQL via Drizzle ORM; Redis (Valkey) via `@pkg/keystore`
---
## 2. The Logic Package: DDD + Layered Architecture
## 2. Logic Package Conventions
The `@pkg/logic` package contains **all domain logic**. It follows:
All domain logic lives in `@pkg/logic` under `packages/logic/domains/<domain>/` with four files: `data.ts`, `repository.ts`, `controller.ts`, `errors.ts`. Mirror this exactly when adding a domain.
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
errors.ts # Domain-specific error constructors (using getError)
```
The logic package is **pure domain logic** — no HTTP routes or routers. API exposure is handled by the main app via SvelteKit remote functions. 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:
**Path aliases** (logic package only):
- `@/*``./*` · `@domains/*``./domains/*` · `@core/*``./core/*`
**FlowExecCtx** (`fctx`) — passed into every domain operation for tracing:
```ts
type FlowExecCtx = {
flowId: string;
userId?: string;
sessionId?: string;
};
type FlowExecCtx = { flowId: string; userId?: string; sessionId?: string; };
```
---
## 3. Error Handling: Result Pattern (neverthrow)
## 3. Error Handling
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; So don't reach for it primarily.
3. Use `ERROR_CODES` for consistent error codes.
4. **`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)`.
5. **Domain errors** — Each domain has an `errors.ts` that exports error constructors built with `getError`. Use these instead of ad-hoc error objects.
6. **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;
// Flexible, but more "defined base fields" in the future
};
```
- Use `neverthrow` (`ResultAsync`, `okAsync`, `errAsync`) for all fallible async logic.
- `@pkg/result`'s `Result` type is **legacy** — do not reach for it primarily.
- Use `ERROR_CODES` from `@pkg/result` for all error codes.
- Use `getError()` from `@pkg/logger` when converting thrown errors into `Err` objects at boundaries.
- Use domain-specific error constructors from `errors.ts` — never ad-hoc error objects.
- Always check `result.isOk()` / `result.isErr()` before accessing `result.value`.
---
## 4. Frontend (Main App)
## 4. Main App Conventions (`apps/main`)
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, components, and remote functions.
- **View Model (VM) pattern**: Domain logic and state for a screen live in `*.vm.svelte.ts` classes. VMs hold reactive state (`$state`), orchestrate remote function calls, and expose methods. Pages import and use a VM instance.
### SvelteKit Remote Functions
The main app uses **SvelteKit remote functions** as the primary API layer — replacing Hono routers in the logic package. Each domain has a `*.remote.ts` file that exposes `query` (reads) and `command` (writes) functions, called directly from VMs. Auth context and `FlowExecCtx` are built inside each remote function from `event.locals` via helpers in `$lib/core/server.utils`.
Naming convention: `*SQ` for queries, `*SC` for commands.
### Global Stores
Shared state (`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.
- VMs call remote functions directly; remote functions invoke logic controllers.
- UI components under `$lib/components/` are shared; domain-specific components live in `$lib/domains/<domain>/`.
- Feature code lives in `src/lib/domains/<domain>/` — view model, components, remote functions.
- **VM pattern**: `*.vm.svelte.ts` classes own all reactive state and async flows for a screen. Pages are thin — they just mount the VM.
- **Remote functions**: `*.remote.ts` per domain. Naming: `*SQ` for queries, `*SC` for commands.
- Auth context and `fctx` are built inside remote functions from `event.locals` via `$lib/core/server.utils`.
---
## 5. Processor App
## 5. Observability Convention
The processor is a **Hono** server intended for **background work** and async jobs. Its structure is still evolving and it is to be updated soon.
When logic is added, processing logic should live under `src/domains/<domain>/` and call into `@pkg/logic` controllers and repositories.
When adding any new operation in a repository or controller, wrap it with `traceResultAsync` from `@pkg/logic/core/observability.ts`. Keep span names descriptive and consistent (e.g. `"user.getUserInfo"`). Do not add ad-hoc spans.
---
## 6. Observability
## 6. Validation
The stack uses **OpenTelemetry** end-to-end for traces, logs, and metrics, shipped to a **SigNoz** instance (via OTel Collector).
### How it fits together
- **`apps/main`** bootstraps the OTel SDK in `instrumentation.server.ts` (auto-instrumentation via `@opentelemetry/sdk-node`). SvelteKit's `tracing` and `instrumentation` experimental flags wire this into the request lifecycle.
- **`@pkg/logger`** ships Winston logs to OTel via `OpenTelemetryTransportV3` — logs are correlated with active traces automatically.
- **`@pkg/logic/core/observability.ts`** provides two tracing helpers for domain code:
- `traceResultAsync` — wraps a `ResultAsync` operation in an OTel span. Use this in repositories and controllers.
- `withFlowSpan` — lower-level span wrapper for non-Result async code.
- Both helpers accept `fctx` and stamp spans with `flow.id`, `flow.user_id`, and `flow.session_id` for end-to-end trace correlation.
### Convention
When adding new domain operations in repositories or controllers, wrap them with `traceResultAsync`. Keep span names consistent and descriptive (e.g. `"user.getUserInfo"`). Do not add ad-hoc spans outside these helpers.
---
## 7. Validation & Schemas
- **Valibot** is used for schema validation in the logic package and in remote function input.
- Domain data types are defined in `data.ts` per domain.
- Use `v.InferOutput<typeof Schema>` for TypeScript types.
- Remote functions pass Valibot schemas to `query()` and `command()` for input validation.
---
## 8. Package Naming
- Apps: `@apps/*` (e.g. `@apps/main`, `@apps/processor`)
- Packages: `@pkg/*` (e.g. `@pkg/logic`, `@pkg/db`, `@pkg/logger`)
- **Valibot** for all schemas. Types via `v.InferOutput<typeof Schema>`.
- Domain data types defined in `data.ts`. Remote function inputs validated via Valibot schemas passed to `query()` / `command()`.