@matteoaliano/forest-ui 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/stamp-version.mjs +53 -0
- package/bin/sync.mjs +26 -35
- package/dist/{chunk-QF5T6J4D.mjs → chunk-6W3RIXXY.mjs} +109 -165
- package/dist/chunk-6W3RIXXY.mjs.map +1 -0
- package/dist/{index-BxpOMmf6.d.mts → index-DJDmMb_c.d.mts} +99 -42
- package/dist/{index-BxpOMmf6.d.ts → index-DJDmMb_c.d.ts} +99 -42
- package/dist/index.d.mts +96 -4
- package/dist/index.d.ts +96 -4
- package/dist/index.js +679 -175
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +536 -11
- package/dist/index.mjs.map +1 -1
- package/dist/theme.d.mts +1 -1
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +107 -163
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +1 -1
- package/guidelines/FOREST_BE_GUIDELINES.md +90 -0
- package/guidelines/FOREST_FE_GUIDELINES.md +543 -0
- package/guidelines/FOREST_UI_GUIDELINES.md +372 -0
- package/package.json +17 -7
- package/dist/chunk-QF5T6J4D.mjs.map +0 -1
- package/guidelines/FOREST_AI_GUIDELINES.md +0 -344
- package/guidelines/FOREST_DEV_GUIDELINES.md +0 -47
- package/guidelines/themes/FOREST_THEME_ALKEMY_PLUS.md +0 -139
- package/guidelines/themes/FOREST_THEME_DEFAULT.md +0 -70
package/dist/theme.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { alkemyPlusPreset, alpha, avatarColors, bg, border, breadcrumbColors, buildTheme, buildThemeOptions, buttonColors, chartColors, colors, components, container, display, fg, focusRings, fontFamily, fontSize, fontWeight, forestPreset, forestTheme, forestThemeOptions, getAvailablePresets, getPreset, iconColors, lineHeight, navColors, palette, radius, registerPreset, shadows2 as shadows, sliderColors, spacing, text, toggleColors, typography, widths } from './chunk-
|
|
1
|
+
export { alkemyPlusPreset, alpha, avatarColors, bg, border, breadcrumbColors, buildTheme, buildThemeOptions, buttonColors, chartColors, colors, components, container, display, fg, focusRings, fontFamily, fontSize, fontWeight, forestPreset, forestTheme, forestThemeOptions, getAvailablePresets, getPreset, iconColors, lineHeight, navColors, palette, radius, registerPreset, shadows2 as shadows, sliderColors, spacing, text, toggleColors, typography, widths } from './chunk-6W3RIXXY.mjs';
|
|
2
2
|
//# sourceMappingURL=theme.mjs.map
|
|
3
3
|
//# sourceMappingURL=theme.mjs.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# FastAPI API Project: Requirements & Best Practices
|
|
2
|
+
|
|
3
|
+
> **forest-ui v0.2.11**
|
|
4
|
+
|
|
5
|
+
> **This file is synced by the `forest-ui` package.**
|
|
6
|
+
> Run `npx forest-ui sync` to update it.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**Stack:** FastAPI (with dependency injection), Python >=3.9 (via Poetry), PostgreSQL/SQL Server, Redis cache, OpenTelemetry, on-prem Kubernetes. Frontend on Vercel, authentication via Clerk, CI/CD with GitHub Actions, Docker images in Azure Container Registry (ACR) deployed to the cluster.
|
|
11
|
+
|
|
12
|
+
FastAPI is a modern Python web framework built on Starlette and Pydantic. It offers an intuitive dependency-injection system for shared components like DB sessions or auth. Pydantic models automatically validate and parse request data. Organize code into an `app` Python package (with `app/main.py`, sub-packages for routers and a `dependencies.py`) so that imports work cleanly. Use **APIRouters** to group related endpoints and share tags/dependencies. Declare shared logic (auth checks, DB, etc.) as FastAPI dependencies (`Depends(...)`) in `app/dependencies.py` for reuse. Keep path operations concise by leveraging DI: for example, inject database sessions or authorization checks into endpoints rather than repeating code.
|
|
13
|
+
|
|
14
|
+
## Environment & Packaging (Poetry)
|
|
15
|
+
|
|
16
|
+
- **Python & Poetry:** Use Python 3.9+. Manage dependencies with [Poetry](https://python-poetry.org) and define metadata in `pyproject.toml` (PEP 621). Poetry creates isolated virtualenvs and a lockfile for reproducible installs.
|
|
17
|
+
- **Dependency Best Practices:** Initialize with `poetry init` to generate `pyproject.toml`. Add packages with `poetry add <pkg>` so they're recorded in both `pyproject.toml` and `poetry.lock`. Separate dev dependencies (e.g. pytest, linters) using `poetry add --dev`. Commit `poetry.lock` to source control to ensure all installs use exact versions. Regularly run `poetry update` and test to keep dependencies current. Use multi-stage Docker builds to minimize image size (install only production deps).
|
|
18
|
+
- **Project Layout:** Follow a logical package structure: e.g.
|
|
19
|
+
```
|
|
20
|
+
app/
|
|
21
|
+
__init__.py
|
|
22
|
+
main.py # app startup
|
|
23
|
+
dependencies.py # shared Depends (DB sessions, auth checks, etc.)
|
|
24
|
+
routers/ # API route modules
|
|
25
|
+
__init__.py
|
|
26
|
+
items.py
|
|
27
|
+
users.py
|
|
28
|
+
models/ # SQLModel/Pydantic models
|
|
29
|
+
services/ # business logic if needed
|
|
30
|
+
```
|
|
31
|
+
Keep code organized into modules; use semantic imports like `from app.routers import items`. Use `Annotated` dependencies where possible for clarity (e.g. `session: Session = Depends(get_session)`).
|
|
32
|
+
|
|
33
|
+
## Databases (PostgreSQL, SQL Server)
|
|
34
|
+
|
|
35
|
+
- **SQL Databases:** FastAPI works with any SQL DB via SQLAlchemy/SQLModel. For example, [SQLModel](https://github.com/tiangolo/sqlmodel) (built on SQLAlchemy and Pydantic) can connect to PostgreSQL or SQL Server (via `pyodbc` or `pymssql`). In development you might use SQLite for simplicity, but **production** should use a dedicated DB server (e.g. PostgreSQL).
|
|
36
|
+
- **Connection Handling:** Use one DB **session/connection per request** via a FastAPI dependency (`yield session` pattern). Configure SQLAlchemy connection pools to match workload (avoid re-creating engine per query). Use environment variables (or Kubernetes Secrets) for DB URLs/credentials. Use alembic or similar for migrations, rather than auto-creating tables in prod. Ensure proper indexes and foreign keys in schema.
|
|
37
|
+
- **Security:** Never embed credentials in code. Use TLS to connect to the DB if supported. Sanitize inputs (Pydantic prevents SQL injection by type-checking and requiring parameters).
|
|
38
|
+
|
|
39
|
+
## Caching (Redis)
|
|
40
|
+
|
|
41
|
+
- **Redis Usage:** Use Redis for caching or session data. Access via a Redis client (e.g. [redis-py](https://pypi.org/project/redis/) or `aioredis` for async). Store only non-sensitive, expirable data with appropriate TTLs. Abstract Redis access behind a service layer or dependency.
|
|
42
|
+
- **Configuration:** Run Redis in a secured network (not public-facing). Require strong passwords and/or disable the default user. Enable TLS if possible to encrypt in-flight cache traffic. Set memory limits and eviction policies. Monitor usage and consider using Redis clusters for high availability.
|
|
43
|
+
- **Best Practices:** Use Redis only as a cache; do not rely on it for permanent storage unless using Redis persistence properly. Use mature caching patterns (e.g. cache-aside). Flush or handle stale caches on deploy as needed.
|
|
44
|
+
|
|
45
|
+
## Observability (OpenTelemetry)
|
|
46
|
+
|
|
47
|
+
- **Instrumentation:** Use [OpenTelemetry for Python](https://opentelemetry.io/docs/languages/python/) to collect traces and metrics. The Python SDK supports Python 3.9+. Install via pip: `pip install opentelemetry-api opentelemetry-sdk` and any needed instrumentations/exporters.
|
|
48
|
+
- **Tracing & Metrics:** Auto-instrument FastAPI (there are middleware or instrumentation libs for ASGI frameworks) to trace HTTP requests. Instrument database calls (SQLAlchemy) and Redis calls. Export telemetry using OTLP or a vendor (Jaeger, Zipkin, Prometheus). Use trace data to diagnose latency; use metrics (like request rate, DB latency) to monitor health.
|
|
49
|
+
- **Deployment:** Ensure an OpenTelemetry Collector or compatible backend is running to receive data. Tag telemetry with the service name and environment. Use semantic conventions for naming.
|
|
50
|
+
|
|
51
|
+
## Frontend (Vercel) & Authentication (Clerk)
|
|
52
|
+
|
|
53
|
+
- **Vercel Deployment:** Host the frontend on Vercel (typically a Next.js or static SPA). Configure environment variables in Vercel for the API base URL. Use custom domains and automatic HTTPS. Restrict API CORS to the frontend domain using FastAPI's `CORSMiddleware` so only your front-end origin can access endpoints.
|
|
54
|
+
- **Clerk Authentication:** Use [Clerk](https://clerk.com) to handle user signup/sign-in and sessions. Clerk provides UI components (for React/Next) and issues JWT tokens for authenticated users. In the FastAPI backend, verify these JWTs on each request. For example, use the [fastapi-clerk-auth](https://pypi.org/project/fastapi-clerk-auth/) middleware to automatically validate Clerk JWTs via Clerk's JWKS. This cleanly integrates with FastAPI's DI: endpoints requiring auth simply depend on the token guard.
|
|
55
|
+
- **Auth Best Practices:** Enforce HTTPS so tokens can't be stolen in transit. Use Clerk's features (MFA, password policies, session limits) to harden accounts. Never expose backend secrets (like Clerk API keys) to the frontend. Use short-lived JWTs or session tokens.
|
|
56
|
+
|
|
57
|
+
## Security Considerations
|
|
58
|
+
|
|
59
|
+
- **Transport & API Security:** Serve all traffic over HTTPS (TLS). Validate all inputs using Pydantic models to prevent injection or malformed data. Implement CORS rules to only allow trusted origins. Use FastAPI's security dependencies (e.g. `OAuth2PasswordBearer`, HTTP Bearer) to enforce auth on endpoints. Consider rate-limiting and IP blocking for brute-force protection.
|
|
60
|
+
- **Kubernetes & Infrastructure:** Secure the on-prem cluster using Kubernetes best practices. Ensure the API server and etcd have TLS in-transit and (if needed) at-rest encryption. Use Role-Based Access Control (RBAC) to limit cluster operations. Enforce Pod Security Standards: run containers as non-root, use read-only filesystems, drop Linux capabilities. Define NetworkPolicies so Pods can only talk to required services (e.g. API Pod talks to DB/Redis, but not to other namespaces). Use secrets or a vault for all credentials (K8s Secrets should be encrypted at rest by enabling encryption). Audit and log all access (enable Kubernetes audit logs). Keep nodes patched and minimize host privileges.
|
|
61
|
+
- **Container Image Security:** Scan images for vulnerabilities before deployment. Use minimal base images (e.g. Alpine or slim variants) to reduce attack surface. Do not run the application as root inside the container.
|
|
62
|
+
- **Redis Security:** Only allow Redis access from internal service accounts. Use Redis AUTH and TLS. Restrict Redis CLI or admin interfaces.
|
|
63
|
+
- **Clerk/Authentication:** Rely on standard protocols. Verify the `iat` and `exp` claims in JWTs to prevent replay. Synchronize clocks or add leeway if needed. Restrict token scopes so that clients only have the privileges they need.
|
|
64
|
+
- **Secrets Management:** Store sensitive config (DB passwords, JWT keys) outside code. In Kubernetes, use Secrets and mount them into pods. In GitHub Actions, use encrypted secrets (do NOT hardcode credentials). Consider using OpenID Connect (OIDC) in GitHub Actions to avoid long-lived secrets when deploying to Azure.
|
|
65
|
+
- **Audit & Compliance:** Log all significant events (logins, errors, deployments). Regularly review audit logs. Follow principle of least privilege for all services.
|
|
66
|
+
|
|
67
|
+
## Deployment & CI/CD Pipeline
|
|
68
|
+
|
|
69
|
+
- **Docker & ACR:** Dockerize the app with a multi-stage `Dockerfile`: install Poetry and build the app, then copy only the `venv/lib/python...` and app code into a smaller final image. Tag images by commit/PR. Push images to Azure Container Registry in the same region as your cluster for low latency. Use a dedicated ACR per environment or namespace. In ACR, enable Azure RBAC: assign a service principal (for CI/CD) push rights and another identity pull rights. Disable the ACR admin user if not needed.
|
|
70
|
+
- **GitHub Actions:** Automate CI/CD with Actions:
|
|
71
|
+
- **CI (on push/PR):** Steps include `actions/checkout`, `actions/setup-python@v3` (choose Python 3.10+), `poetry install` (or `poetry install --no-dev` for quick CI), run linter (flake8/mypy) and tests. Cache the Poetry virtualenv or pip cache to speed up builds. Fail on test or lint errors.
|
|
72
|
+
- **Build & Push (on main):** After CI passes, build the Docker image. Use `azure/login@v1` (or OIDC) to authenticate to Azure using a service principal stored in a GitHub secret. Then use `azure/CLI` or `docker/login-action` and `docker/build-push-action` to push the image to ACR. Tag images by semantic version or commit SHA.
|
|
73
|
+
- **Deploy to K8s:** After pushing, use `azure/k8s-set-context` (or `kubectl` inside `azure/CLI`) to point to the on-prem cluster (this may require a kubeconfig stored securely). Apply updated Kubernetes manifests (using `kubectl apply` or `helm upgrade`). You can use Helm or Kustomize; store manifests alongside code or in a separate repo.
|
|
74
|
+
- **Checks:** Include branch protection so only successful checks (lint/tests) allow merging to main. Use pull request previews (deploy to a test namespace) before production. Use Actions cache for Docker layers and Poetry packages.
|
|
75
|
+
|
|
76
|
+
## Kubernetes Deployment Patterns
|
|
77
|
+
|
|
78
|
+
- **Manifests:** Define Deployments and Services in YAML. Always create a Service *before* pods that use it, so environment variables get injected correctly. Use DNS service names for inter-service calls. Avoid `hostPort`/`hostNetwork` (it ties pods to nodes); use a NodePort or Ingress for external access. For internal-only services, use ClusterIP or headless Services as needed.
|
|
79
|
+
- **Labels & Configuration:** Use semantic, standardized labels (`app.kubernetes.io/name`, `component`, `tier`, etc.) to identify workloads. Store app config (like DB URLs or feature flags) in ConfigMaps/Secrets and mount them as env vars or files. Keep resource limits/requests set on pods. Use liveness/readiness probes to restart unhealthy pods. Consider HorizontalPodAutoscalers based on CPU/memory or custom metrics (if cluster has VPA/HPA enabled).
|
|
80
|
+
- **Secrets:** Load Kubernetes Secrets (e.g. database credentials, JWT secret) into pods. Optionally use a tool like SealedSecrets or Vault for rotation.
|
|
81
|
+
- **Ingress & Networking:** Expose the API via an Ingress or LoadBalancer with TLS. Ensure the TLS certificate is valid (use internal CA or Let's Encrypt).
|
|
82
|
+
- **Monitoring:** Run a Prometheus/Alertmanager stack (or other monitoring) in the cluster. Configure probes and health endpoints for FastAPI. Forward logs to a central system (ELK/EFK or Grafana Loki). Use OpenTelemetry collector if needed.
|
|
83
|
+
|
|
84
|
+
## Final Checklist
|
|
85
|
+
|
|
86
|
+
- **Code Quality:** Follow PEP 8 and Pylint/myPy linting. Ensure unit and integration tests cover key logic. Use `@pytest` with the TestClient for endpoint tests.
|
|
87
|
+
- **Dependency Audit:** Before each release, run a security scan on Python packages (e.g. `poetry audit` or `safety`) and on container images. Update vulnerable dependencies promptly.
|
|
88
|
+
- **Review Configuration:** Verify all environment variables and secrets are properly set in production. Check that no debug or verbose logging is enabled.
|
|
89
|
+
- **Performance Checks:** Ensure Redis and database are configured for expected load. Benchmark API endpoints and add indexes or cache hot queries as needed.
|
|
90
|
+
- **Security Review:** Confirm TLS is enforced (no HTTP allowed). Test auth flows (invalid tokens are rejected, access is limited by role). Ensure network policies isolate components.
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
# Forest UI — Development Best Practices
|
|
2
|
+
|
|
3
|
+
> **forest-ui v0.2.11**
|
|
4
|
+
|
|
5
|
+
> **This file is synced by the `forest-ui` package.**
|
|
6
|
+
> Run `npx forest-ui sync` to update it.
|
|
7
|
+
|
|
8
|
+
# Next.js Code Agent - v2.0
|
|
9
|
+
|
|
10
|
+
## CORE PHILOSOPHY
|
|
11
|
+
|
|
12
|
+
1. **Server First**: Default Server Components, `'use client'` solo quando necessario
|
|
13
|
+
2. **Type Everything**: TypeScript strict, zero `any`
|
|
14
|
+
3. **Single Responsibility**: Ogni file = 1 job, pagine = orchestratori puri
|
|
15
|
+
4. **Separation of Concerns**: Logica separata da presentazione, data access isolato
|
|
16
|
+
5. **Explicit > Implicit**: Codice auto-documentante, no magic
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. ARCHITETTURA & STRUTTURA
|
|
21
|
+
|
|
22
|
+
### File System Organization
|
|
23
|
+
```
|
|
24
|
+
/app # App Router (Next.js 13+)
|
|
25
|
+
/(routes)/ # Route groups
|
|
26
|
+
/api/ # API routes
|
|
27
|
+
/[route]/
|
|
28
|
+
page.tsx # Pagina (max 20 righe, solo orchestrazione)
|
|
29
|
+
layout.tsx # Layout condiviso
|
|
30
|
+
loading.tsx # Loading UI
|
|
31
|
+
error.tsx # Error handling
|
|
32
|
+
/components
|
|
33
|
+
/ui/ # Design system base (Button, Card, Input...)
|
|
34
|
+
/features/ # Feature-specific (UserCard, ProductGrid...)
|
|
35
|
+
/lib
|
|
36
|
+
/actions/ # Server Actions (mutations)
|
|
37
|
+
/queries/ # Data fetching (read operations)
|
|
38
|
+
/hooks/ # Custom React hooks
|
|
39
|
+
/utils/ # Pure utility functions
|
|
40
|
+
/services/ # Business logic complessa
|
|
41
|
+
/parsers/ # URL params & form data parsing
|
|
42
|
+
/validations/ # Zod schemas
|
|
43
|
+
/types # TypeScript interfaces/types
|
|
44
|
+
/public # Static assets
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Naming Conventions
|
|
48
|
+
- **Componenti**: `PascalCase.tsx` (UserCard.tsx)
|
|
49
|
+
- **Hooks**: `use[Name].ts` (useDebounce.ts)
|
|
50
|
+
- **Actions**: `[entity].actions.ts` (user.actions.ts)
|
|
51
|
+
- **Queries**: `[entity].queries.ts` (user.queries.ts)
|
|
52
|
+
- **Utils**: `[entity].util.ts` (format.util.ts)
|
|
53
|
+
- **Parsers**: `[entity].parser.ts` (product.parser.ts)
|
|
54
|
+
- **Services**: `[entity].service.ts` (analytics.service.ts)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 2. SINGLE RESPONSIBILITY - PAGE LEVEL
|
|
59
|
+
|
|
60
|
+
### Regola d'Oro
|
|
61
|
+
**Una pagina Next.js ha 1 sola responsabilità: "Comporre UI per questa route"**
|
|
62
|
+
|
|
63
|
+
### Pagina Ideale (Template)
|
|
64
|
+
```typescript
|
|
65
|
+
// app/products/page.tsx - Max 20 righe
|
|
66
|
+
import { getProducts } from '@/lib/queries/product.queries';
|
|
67
|
+
import { ProductGrid } from '@/components/features/products/ProductGrid';
|
|
68
|
+
|
|
69
|
+
export default async function ProductsPage() {
|
|
70
|
+
const products = await getProducts(); // Delega fetch
|
|
71
|
+
return <ProductGrid products={products} />; // Delega UI
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### ❌ Una pagina NON deve:
|
|
76
|
+
- Costruire query complesse (>5 righe di logic)
|
|
77
|
+
- Fare trasformazioni dati (.map/.filter/.reduce con logica)
|
|
78
|
+
- Validare o parsare parametri inline
|
|
79
|
+
- Gestire errori con try/catch (usa error.tsx)
|
|
80
|
+
- Contenere JSX >50 righe (estrai componenti)
|
|
81
|
+
- Avere calcoli o aggregazioni
|
|
82
|
+
- Gestire state (useState, useReducer)
|
|
83
|
+
|
|
84
|
+
### ✅ Una pagina deve:
|
|
85
|
+
- Chiamare 1-3 funzioni di data fetching (in parallelo con Promise.all se >1)
|
|
86
|
+
- Comporre componenti feature
|
|
87
|
+
- Passare props ai componenti
|
|
88
|
+
- **Nient'altro**
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 3. SEPARATION OF CONCERNS - EXTRACTION PATTERNS
|
|
93
|
+
|
|
94
|
+
### Data Fetching → lib/queries/
|
|
95
|
+
**Responsabilità:** Read operations, DB access, API calls
|
|
96
|
+
```typescript
|
|
97
|
+
// lib/queries/product.queries.ts
|
|
98
|
+
'use server'
|
|
99
|
+
|
|
100
|
+
export async function getProducts(): Promise<Product[]> {
|
|
101
|
+
return await db.product.findMany({
|
|
102
|
+
where: { status: 'active' },
|
|
103
|
+
orderBy: { createdAt: 'desc' }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function getProductById(id: string): Promise<Product | null> {
|
|
108
|
+
return await db.product.findUnique({ where: { id } });
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Mutations → lib/actions/
|
|
113
|
+
**Responsabilità:** Create, Update, Delete operations
|
|
114
|
+
```typescript
|
|
115
|
+
// lib/actions/product.actions.ts
|
|
116
|
+
'use server'
|
|
117
|
+
|
|
118
|
+
import { revalidatePath } from 'next/cache';
|
|
119
|
+
import { productSchema } from '@/lib/validations/product.schema';
|
|
120
|
+
|
|
121
|
+
export async function createProduct(formData: FormData) {
|
|
122
|
+
const parsed = productSchema.safeParse(Object.fromEntries(formData));
|
|
123
|
+
if (!parsed.success) return { error: parsed.error };
|
|
124
|
+
|
|
125
|
+
const product = await db.product.create({ data: parsed.data });
|
|
126
|
+
revalidatePath('/products');
|
|
127
|
+
|
|
128
|
+
return { success: true, product };
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### URL Parsing → lib/parsers/
|
|
133
|
+
**Responsabilità:** Validare e parsare searchParams, form data
|
|
134
|
+
```typescript
|
|
135
|
+
// lib/parsers/product.parser.ts
|
|
136
|
+
import { z } from 'zod';
|
|
137
|
+
|
|
138
|
+
const filterSchema = z.object({
|
|
139
|
+
category: z.string().default('all'),
|
|
140
|
+
sort: z.enum(['asc', 'desc']).default('desc'),
|
|
141
|
+
page: z.coerce.number().int().min(1).default(1)
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
export function parseProductFilters(params: Record<string, string | undefined>) {
|
|
145
|
+
return filterSchema.parse(params);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Pure Logic → lib/utils/
|
|
150
|
+
**Responsabilità:** Funzioni pure (input → output, no side effects)
|
|
151
|
+
```typescript
|
|
152
|
+
// lib/utils/product.util.ts
|
|
153
|
+
|
|
154
|
+
export function calculateDiscount(price: number, discountPercent: number): number {
|
|
155
|
+
return price * (1 - discountPercent / 100);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function formatPrice(amount: number): string {
|
|
159
|
+
return new Intl.NumberFormat('it-IT', {
|
|
160
|
+
style: 'currency',
|
|
161
|
+
currency: 'EUR'
|
|
162
|
+
}).format(amount);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function isRecentProduct(createdAt: Date): boolean {
|
|
166
|
+
const daysSinceCreation = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60 * 24);
|
|
167
|
+
return daysSinceCreation <= 30;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Complex Business Logic → lib/services/
|
|
172
|
+
**Responsabilità:** Orchestrazione multi-entity, calcoli complessi, aggregazioni
|
|
173
|
+
```typescript
|
|
174
|
+
// lib/services/analytics.service.ts
|
|
175
|
+
'use server'
|
|
176
|
+
|
|
177
|
+
import { getOrders } from '@/lib/queries/order.queries';
|
|
178
|
+
import { getProducts } from '@/lib/queries/product.queries';
|
|
179
|
+
|
|
180
|
+
export async function calculateDashboardStats(userId: string) {
|
|
181
|
+
const [orders, products] = await Promise.all([
|
|
182
|
+
getOrders(userId),
|
|
183
|
+
getProducts()
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
totalRevenue: orders.reduce((sum, o) => sum + o.total, 0),
|
|
188
|
+
avgOrderValue: orders.length > 0 ? orders.reduce((sum, o) => sum + o.total, 0) / orders.length : 0,
|
|
189
|
+
topProducts: calculateTopProducts(orders, products),
|
|
190
|
+
conversionRate: calculateConversionRate(orders, products)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Client Logic → lib/hooks/
|
|
196
|
+
**Responsabilità:** Stateful logic riutilizzabile, side effects
|
|
197
|
+
```typescript
|
|
198
|
+
// lib/hooks/useProductFilters.ts
|
|
199
|
+
'use client'
|
|
200
|
+
|
|
201
|
+
import { useState, useMemo } from 'react';
|
|
202
|
+
|
|
203
|
+
export function useProductFilters(products: Product[]) {
|
|
204
|
+
const [search, setSearch] = useState('');
|
|
205
|
+
const [category, setCategory] = useState<string>('all');
|
|
206
|
+
|
|
207
|
+
const filtered = useMemo(() => {
|
|
208
|
+
return products.filter(p => {
|
|
209
|
+
const matchesSearch = p.name.toLowerCase().includes(search.toLowerCase());
|
|
210
|
+
const matchesCategory = category === 'all' || p.category === category;
|
|
211
|
+
return matchesSearch && matchesCategory;
|
|
212
|
+
});
|
|
213
|
+
}, [products, search, category]);
|
|
214
|
+
|
|
215
|
+
return { filtered, search, setSearch, category, setCategory };
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Validation → lib/validations/
|
|
220
|
+
**Responsabilità:** Zod schemas, validation rules
|
|
221
|
+
```typescript
|
|
222
|
+
// lib/validations/product.schema.ts
|
|
223
|
+
import { z } from 'zod';
|
|
224
|
+
|
|
225
|
+
export const productSchema = z.object({
|
|
226
|
+
name: z.string().min(3).max(100),
|
|
227
|
+
price: z.number().positive(),
|
|
228
|
+
category: z.enum(['electronics', 'clothing', 'food']),
|
|
229
|
+
description: z.string().max(500).optional()
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
export type ProductInput = z.infer<typeof productSchema>;
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 4. REFACTORING MODE
|
|
238
|
+
|
|
239
|
+
### Auto-Trigger Refactoring quando:
|
|
240
|
+
- File pagina >50 righe
|
|
241
|
+
- File componente >150 righe
|
|
242
|
+
- Più di 1 await non in Promise.all
|
|
243
|
+
- Try/catch in pagina (usa error.tsx)
|
|
244
|
+
- Logica trasformazione dati in pagina (.map/.filter con >1 riga)
|
|
245
|
+
- Duplicazione codice (>2 occorrenze stesso pattern)
|
|
246
|
+
|
|
247
|
+
### Processo Refactoring (3 Fasi)
|
|
248
|
+
|
|
249
|
+
#### FASE 1: AUDIT
|
|
250
|
+
```markdown
|
|
251
|
+
Analizza file e identifica:
|
|
252
|
+
|
|
253
|
+
🔴 CRITICAL (Refactoring obbligatorio):
|
|
254
|
+
- [ ] Mixing concerns (fetch + validation + UI stesso file)
|
|
255
|
+
- [ ] Logica business in componenti UI
|
|
256
|
+
- [ ] File >200 righe
|
|
257
|
+
- [ ] Funzioni >50 righe
|
|
258
|
+
- [ ] `any` types presenti
|
|
259
|
+
- [ ] Prop drilling >3 livelli
|
|
260
|
+
|
|
261
|
+
🟡 WARNINGS (Miglioramento consigliato):
|
|
262
|
+
- [ ] Codice duplicato (>2 occorrenze)
|
|
263
|
+
- [ ] Nomi generici (data, item, temp)
|
|
264
|
+
- [ ] Magic numbers/strings
|
|
265
|
+
- [ ] Nessun error handling
|
|
266
|
+
|
|
267
|
+
Genera report: problemi + metriche + priorità
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### FASE 2: PLAN
|
|
271
|
+
```markdown
|
|
272
|
+
Proponi struttura target:
|
|
273
|
+
|
|
274
|
+
Before (PROBLEMA):
|
|
275
|
+
❌ app/products/page.tsx (250 righe)
|
|
276
|
+
├─ Fetch + trasformazioni
|
|
277
|
+
├─ Validazione
|
|
278
|
+
├─ Logica filtri
|
|
279
|
+
└─ UI rendering
|
|
280
|
+
|
|
281
|
+
After (SOLUZIONE):
|
|
282
|
+
✅ app/products/page.tsx (15 righe) - Orchestrazione
|
|
283
|
+
✅ lib/queries/product.queries.ts (60 righe) - Data access
|
|
284
|
+
✅ lib/utils/product.util.ts (30 righe) - Trasformazioni
|
|
285
|
+
✅ lib/parsers/product.parser.ts (20 righe) - Validation
|
|
286
|
+
✅ components/features/products/ProductGrid.tsx (80 righe) - UI
|
|
287
|
+
✅ lib/hooks/useProductFilters.ts (40 righe) - Client logic
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### FASE 3: REFACTOR (Step-by-Step)
|
|
291
|
+
```markdown
|
|
292
|
+
Implementa in questo ordine:
|
|
293
|
+
|
|
294
|
+
1. Types & Validation → types/ + validations/
|
|
295
|
+
2. Pure Utils → lib/utils/
|
|
296
|
+
3. Parsers → lib/parsers/
|
|
297
|
+
4. Queries → lib/queries/
|
|
298
|
+
5. Actions → lib/actions/
|
|
299
|
+
6. Services → lib/services/ (se necessario)
|
|
300
|
+
7. Hooks → lib/hooks/ (client logic)
|
|
301
|
+
8. Components → components/features/
|
|
302
|
+
9. Page Rebuild → app/[route]/page.tsx (minimalista)
|
|
303
|
+
10. Error/Loading → error.tsx, loading.tsx
|
|
304
|
+
|
|
305
|
+
Ogni step: commit separato, testabile incrementalmente
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### SRP Compliance Score
|
|
309
|
+
```
|
|
310
|
+
Score = 100 - penalità
|
|
311
|
+
|
|
312
|
+
Penalità pagine:
|
|
313
|
+
- Righe codice: -1 ogni 10 oltre 20
|
|
314
|
+
- Await multipli: -5 se >1 (non in Promise.all)
|
|
315
|
+
- Nesting depth: -10 ogni livello oltre 2
|
|
316
|
+
- Inline JSX: -2 ogni 10 righe oltre 30
|
|
317
|
+
- Try/catch: -15
|
|
318
|
+
- Transformazioni: -5 per ogni .map/.filter/.reduce
|
|
319
|
+
- Validazioni inline: -10
|
|
320
|
+
|
|
321
|
+
Target: Score ≥ 80
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 5. COMPONENT PATTERNS
|
|
327
|
+
|
|
328
|
+
### Server Components (Default)
|
|
329
|
+
```typescript
|
|
330
|
+
// ✅ Server Component - Fetch diretto
|
|
331
|
+
export default async function ProductPage({ params }: { params: { id: string } }) {
|
|
332
|
+
const product = await getProductById(params.id);
|
|
333
|
+
return <ProductDetail product={product} />;
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Client Components
|
|
338
|
+
```typescript
|
|
339
|
+
// ✅ Client Component - Solo quando necessario
|
|
340
|
+
'use client'
|
|
341
|
+
|
|
342
|
+
export function ProductFilters({ onFilterChange }: Props) {
|
|
343
|
+
const [search, setSearch] = useState('');
|
|
344
|
+
// Interattività, hooks, browser APIs
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Composition Over Configuration
|
|
349
|
+
```typescript
|
|
350
|
+
// ✅ Preferisci
|
|
351
|
+
<Card>
|
|
352
|
+
<CardHeader>{title}</CardHeader>
|
|
353
|
+
<CardContent>{children}</CardContent>
|
|
354
|
+
</Card>
|
|
355
|
+
|
|
356
|
+
// ❌ Evita props drilling
|
|
357
|
+
<Card title={title} content={content} footer={footer} />
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 6. TYPESCRIPT STRICT
|
|
363
|
+
```typescript
|
|
364
|
+
// ✅ Type safety completo
|
|
365
|
+
interface UserCardProps {
|
|
366
|
+
user: User;
|
|
367
|
+
onEdit?: (id: string) => Promise<void>;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function UserCard({ user, onEdit }: UserCardProps) {
|
|
371
|
+
// Implementation
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ❌ Evita any
|
|
375
|
+
function Component({ data }: { data: any }) { ... }
|
|
376
|
+
|
|
377
|
+
// ✅ Usa unknown + narrowing se tipo incerto
|
|
378
|
+
function processData(data: unknown) {
|
|
379
|
+
if (typeof data === 'object' && data !== null && 'id' in data) {
|
|
380
|
+
// Safe to use data.id
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## 7. PERFORMANCE
|
|
388
|
+
```typescript
|
|
389
|
+
// ✅ Dynamic imports
|
|
390
|
+
const HeavyChart = dynamic(() => import('./HeavyChart'), {
|
|
391
|
+
loading: () => <Skeleton />,
|
|
392
|
+
ssr: false
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// ✅ Parallel data fetching
|
|
396
|
+
const [products, categories] = await Promise.all([
|
|
397
|
+
getProducts(),
|
|
398
|
+
getCategories()
|
|
399
|
+
]);
|
|
400
|
+
|
|
401
|
+
// ✅ Image optimization
|
|
402
|
+
import Image from 'next/image';
|
|
403
|
+
<Image src={src} alt={alt} width={500} height={300} />
|
|
404
|
+
|
|
405
|
+
// ❌ Mai
|
|
406
|
+
<img src={src} alt={alt} />
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 8. ERROR HANDLING
|
|
412
|
+
```typescript
|
|
413
|
+
// ✅ Error boundaries dedicated
|
|
414
|
+
// app/products/error.tsx
|
|
415
|
+
'use client'
|
|
416
|
+
|
|
417
|
+
export default function Error({ error, reset }: {
|
|
418
|
+
error: Error;
|
|
419
|
+
reset: () => void;
|
|
420
|
+
}) {
|
|
421
|
+
return <ErrorBoundary error={error} onReset={reset} />;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ✅ Safe data access
|
|
425
|
+
const userName = user?.profile?.name ?? 'Guest';
|
|
426
|
+
|
|
427
|
+
// ✅ Server Action error handling
|
|
428
|
+
export async function createUser(formData: FormData) {
|
|
429
|
+
const parsed = userSchema.safeParse(Object.fromEntries(formData));
|
|
430
|
+
if (!parsed.success) {
|
|
431
|
+
return { error: parsed.error.format() };
|
|
432
|
+
}
|
|
433
|
+
// ... resto logica
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## 9. OUTPUT STANDARDS
|
|
440
|
+
|
|
441
|
+
Quando generi codice, fornisci sempre:
|
|
442
|
+
|
|
443
|
+
1. **File Path** completo
|
|
444
|
+
2. **Imports** ordinati (external → @/ → relative)
|
|
445
|
+
3. **Types/Interfaces** prima dell'implementazione
|
|
446
|
+
4. **Implementation** con commenti su logica complessa
|
|
447
|
+
5. **Usage Example** se non ovvio
|
|
448
|
+
```typescript
|
|
449
|
+
// File: lib/queries/user.queries.ts
|
|
450
|
+
|
|
451
|
+
import { db } from '@/lib/db';
|
|
452
|
+
import type { User } from '@/types/user.types';
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Recupera utenti attivi con i loro profili
|
|
456
|
+
* @returns Array di utenti con relazioni caricate
|
|
457
|
+
*/
|
|
458
|
+
export async function getActiveUsers(): Promise<User[]> {
|
|
459
|
+
return await db.user.findMany({
|
|
460
|
+
where: { status: 'active' },
|
|
461
|
+
include: { profile: true }
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## 10. PRE-DELIVERY CHECKLIST
|
|
469
|
+
|
|
470
|
+
Prima di consegnare codice, verifica:
|
|
471
|
+
|
|
472
|
+
**TypeScript:**
|
|
473
|
+
- [ ] Zero errori TS
|
|
474
|
+
- [ ] Zero `any` (usa `unknown` se necessario)
|
|
475
|
+
- [ ] Tutte props/functions tipizzate
|
|
476
|
+
|
|
477
|
+
**Architecture:**
|
|
478
|
+
- [ ] Server Components dove possibile
|
|
479
|
+
- [ ] Pagine <20 righe (solo orchestrazione)
|
|
480
|
+
- [ ] Zero logica business in componenti UI
|
|
481
|
+
- [ ] File <150 righe (split se necessario)
|
|
482
|
+
|
|
483
|
+
**Code Quality:**
|
|
484
|
+
- [ ] Import ordinati (external → @ → relative)
|
|
485
|
+
- [ ] Naming conventions rispettate
|
|
486
|
+
- [ ] Single responsibility rispettata
|
|
487
|
+
- [ ] Zero codice duplicato
|
|
488
|
+
- [ ] Nomi espliciti (no `data`, `temp`, `x`)
|
|
489
|
+
|
|
490
|
+
**Safety:**
|
|
491
|
+
- [ ] Error handling presente
|
|
492
|
+
- [ ] Safe data access (optional chaining)
|
|
493
|
+
- [ ] Input validation (Zod per form/API)
|
|
494
|
+
- [ ] No magic numbers/strings
|
|
495
|
+
|
|
496
|
+
**Performance:**
|
|
497
|
+
- [ ] `next/image` per immagini
|
|
498
|
+
- [ ] Dynamic imports per componenti pesanti
|
|
499
|
+
- [ ] Parallel fetch con Promise.all
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## CONTEXT LOADING (Per documentazione dettagliata)
|
|
504
|
+
|
|
505
|
+
Se task coinvolge:
|
|
506
|
+
- **Refactoring pagine** → Rileggi questa sezione 3
|
|
507
|
+
- **Separation of concerns** → Rileggi sezione 3 + esempi
|
|
508
|
+
- **Performance optimization** → Focus su sezione 7
|
|
509
|
+
- **Error handling** → Focus su sezione 8
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## QUICK REFERENCE
|
|
514
|
+
```typescript
|
|
515
|
+
// ✅ PAGINA IDEALE
|
|
516
|
+
export default async function Page() {
|
|
517
|
+
const data = await getData();
|
|
518
|
+
return <Feature data={data} />;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ✅ SERVER ACTION
|
|
522
|
+
'use server'
|
|
523
|
+
export async function createItem(formData: FormData) {
|
|
524
|
+
const parsed = schema.safeParse(...);
|
|
525
|
+
if (!parsed.success) return { error: ... };
|
|
526
|
+
const item = await db.create(...);
|
|
527
|
+
revalidatePath('/items');
|
|
528
|
+
return { success: true, item };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ✅ CUSTOM HOOK
|
|
532
|
+
'use client'
|
|
533
|
+
export function useFilters<T>(items: T[], filterFn: (item: T) => boolean) {
|
|
534
|
+
const [filtered, setFiltered] = useState(items);
|
|
535
|
+
// Logic...
|
|
536
|
+
return { filtered, /* ... */ };
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
**Versione:** 2.0
|
|
543
|
+
**Focus:** SRP + Separation of Concerns + Context Efficiency
|