@3lineas/grooveskills 1.7.2 → 1.8.0

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.
@@ -11,158 +11,91 @@ Canonical reference for React fundamentals in GrooveSkills projects: function co
11
11
 
12
12
  ## 1. Golden Rules (Non-negotiable)
13
13
 
14
- 1. **One Component per File & < 200 Lines**:
14
+ 1. **Strictly Prohibited: `React.FC` and `React.FunctionComponent`**:
15
+ - In modern React (React 18 / 19), **NEVER** type components with `React.FC` or `React.FunctionComponent`.
16
+ - Declare standard functions and type `props` directly in the parameter list.
17
+ - For components requiring children, type `children: ReactNode` explicitly in the props interface.
18
+
19
+ 2. **One Component per File & < 200 Lines**:
15
20
  - Every exported React component resides in its own dedicated `.tsx` file and never exceeds **200 lines**.
16
21
  - Extract sub-components or custom hooks (`use-*.ts`) into atomic files before reaching the limit.
17
- 2. **Mandatory Path Aliases (`@/...`) & Zero Barrel Files**:
22
+
23
+ 3. **Domain Custom Hooks for Form Encapsulation**:
24
+ - Form components must encapsulate their own state using domain hooks (e.g. `useLoginForm`, `useRegisterForm`) instead of bubbling raw `form` and `onSubmit` props up to higher-level screen containers.
25
+
26
+ 4. **Mandatory Path Aliases (`@/...`) & Zero Barrel Files**:
18
27
  - Import components, hooks, schemas, and utilities directly via path aliases (`@/components/ui/card`, `@/hooks/use-window-width`).
19
28
  - Never create `index.ts` or `index.tsx` barrel files to re-export components or hooks.
20
- 3. **Strict Props Typing (Zero `any`)**:
21
- - Define explicit `interface` or `type` declarations for component props.
22
- - Treat `props` and `state` as strictly immutable.
23
- 4. **Early Returns for UI States (No Nested Ternaries)**:
29
+
30
+ 5. **Early Returns for UI States (No Nested Ternaries)**:
24
31
  - Handle loading, error, and empty states with top-level guard clauses (`if (!data) return <EmptyState />;`) instead of deeply nested JSX conditionals.
25
- 5. **Top-Level Hooks Only (Rules of Hooks)**:
26
- - Call hooks unconditionally at the top level of function components or custom hooks—never inside loops, conditions, or nested functions (except React 19's `use()`, covered in `react-advanced`).
32
+
27
33
  6. **Immutable State Updates & Functional Updaters**:
28
34
  - Never mutate arrays or objects in place (`items.push()`). Always pass a new reference (`[...items, next]`).
29
35
  - Use functional updaters (`setCount((prev) => prev + step)`) whenever next state depends on previous state.
30
- 7. **Compiler-Aware Memoization**:
31
- - Do not wrap every value or callback in `useMemo` or `useCallback` by reflex. When the React Compiler is active, it memoizes automatically.
32
- - Reserve manual `useMemo` and `useCallback` for stabilizing references passed to `useEffect` dependency arrays or for genuinely expensive computations in uncompiled modules.
33
- 8. **Stable Identity Keys in Lists**:
36
+
37
+ 7. **Stable Identity Keys in Lists**:
34
38
  - Always provide a stable, unique `key` derived from domain identity (`item.id`), never the array index (`i`) for dynamic or reorderable lists.
35
- 9. **Controlled Inputs with Single Source of Truth**:
36
- - Derive input `value` from state and handle updates via `onChange`, validating user input with **Valibot** (`* as v from "valibot"`) where applicable.
37
39
 
38
40
  ---
39
41
 
40
- ## 2. Components, JSX, Props, and Early Returns
41
-
42
- Name components in `PascalCase`. Return a single root element or Fragment (`<>...</>`). Use guard clauses to keep the primary render path flat.
42
+ ## 2. Component Typing Standards
43
43
 
44
44
  ```tsx
45
- import { useState } from "react";
45
+ import type { ReactNode } from "react";
46
46
 
47
- interface UserSummaryProps {
47
+ // ✅ Component with props
48
+ export interface UserAvatarProps {
48
49
  name: string;
49
- role?: string;
50
- isActive: boolean;
51
- step?: number;
50
+ avatarUrl?: string;
52
51
  }
53
52
 
54
- export function UserSummary({ name, role = "Member", isActive, step = 1 }: UserSummaryProps) {
55
- const [count, setCount] = useState<number>(0);
56
-
57
- if (!isActive) {
58
- return <p className="text-muted">This account is currently inactive.</p>;
59
- }
60
-
53
+ export function UserAvatar({ name, avatarUrl }: UserAvatarProps) {
61
54
  return (
62
- <section className="user-summary">
63
- <h2>Hello, {name} ({role})</h2>
64
- <button type="button" onClick={() => setCount((prev) => prev + step)}>
65
- Count: {count}
66
- </button>
67
- </section>
55
+ <div className="flex items-center gap-2">
56
+ {avatarUrl ? <img src={avatarUrl} alt={name} /> : null}
57
+ <span>{name}</span>
58
+ </div>
68
59
  );
69
60
  }
70
- ```
71
-
72
- ---
73
-
74
- ## 3. Core Hooks Guide
75
-
76
- - **`useState`**: Holds reactive local state (`const [isOpen, setIsOpen] = useState<boolean>(false);`).
77
- - **`useEffect`**: Synchronizes with external systems (DOM listeners, timers, subscriptions). Always declare accurate dependencies and return a cleanup function.
78
- - **`useRef`**: Holds a mutable value across renders without causing re-renders, or references a DOM node.
79
- - **`useMemo` & `useCallback`**: Deliberate escape hatches to stabilize references for `useEffect` dependency arrays or heavy computations in uncompiled components.
80
- - **`useContext`**: Reads shared state from a `Context` higher in the tree (`<ThemeContext value={theme}>` in React 19).
81
-
82
- ```tsx
83
- import { useEffect, useRef, useState } from "react";
84
-
85
- export function AutoFocusWindowTracker() {
86
- const inputRef = useRef<HTMLInputElement>(null);
87
- const [width, setWidth] = useState<number>(() => window.innerWidth);
88
-
89
- useEffect(() => {
90
- inputRef.current?.focus();
91
- const handleResize = (): void => setWidth(window.innerWidth);
92
- window.addEventListener("resize", handleResize);
93
- return () => window.removeEventListener("resize", handleResize);
94
- }, []);
95
-
96
- return <input ref={inputRef} placeholder={`Viewport: ${width}px`} />;
97
- }
98
- ```
99
-
100
- ---
101
-
102
- ## 4. Composition, Lists, and Controlled Inputs
103
-
104
- Favor composition via `children` over bloated configuration props, use stable domain keys (`todo.id`) for lists, and bind controlled inputs to state:
105
-
106
- ```tsx
107
- import { useState, type ReactNode } from "react";
108
-
109
- export interface TodoItem {
110
- id: string;
111
- text: string;
112
- done: boolean;
113
- }
114
61
 
115
- export function TodoCard({
116
- title,
117
- todos,
118
- children,
119
- }: {
62
+ // ✅ Component with children
63
+ export interface CardProps {
120
64
  title: string;
121
- todos: TodoItem[];
122
- children?: ReactNode;
123
- }) {
124
- const [filter, setFilter] = useState<string>("");
125
-
126
- if (todos.length === 0) {
127
- return <p>Your task list is clear. Add a new item to get started.</p>;
128
- }
129
-
130
- const visible = todos.filter((t) => t.text.toLowerCase().includes(filter.toLowerCase()));
65
+ children: ReactNode;
66
+ }
131
67
 
68
+ export function Card({ title, children }: CardProps) {
132
69
  return (
133
- <article className="card">
134
- <h2>{title}</h2>
135
- <input
136
- value={filter}
137
- onChange={(event) => setFilter(event.target.value)}
138
- placeholder="Filter tasks..."
139
- />
140
- <ul>
141
- {visible.map((todo) => (
142
- <li key={todo.id}>{todo.done ? <s>{todo.text}</s> : todo.text}</li>
143
- ))}
144
- </ul>
70
+ <section className="rounded-lg border p-4">
71
+ <h3 className="font-semibold text-lg">{title}</h3>
145
72
  {children}
146
- </article>
73
+ </section>
147
74
  );
148
75
  }
76
+
77
+ // ✅ Component without props
78
+ export function EmptyState() {
79
+ return <p className="text-muted-foreground text-sm">No items found.</p>;
80
+ }
149
81
  ```
150
82
 
151
83
  ---
152
84
 
153
- ## 5. Anti-Patterns to Avoid
85
+ ## 3. Anti-Patterns to Avoid
154
86
 
155
87
  | Anti-Pattern | Why It Fails | Correct Pattern |
156
88
  | :--- | :--- | :--- |
89
+ | `export const Button: React.FC<Props>` | Verbose, breaks TS generics, legacy implicit children | `export function Button({ label }: ButtonProps)` |
157
90
  | Mutating state in place (`items.push(next)`) | Same array reference; React skips re-render | Create a new reference: `setItems((prev) => [...prev, next])` |
158
- | Calling hooks after `if` or inside loops | Breaks React's deterministic hook call order | Call hooks unconditionally at the top level; branch on results |
91
+ | Calling hooks after `if` or inside loops | Breaks React's deterministic hook call order | Call hooks unconditionally at top level |
159
92
  | Using array index `key={i}` on dynamic lists | Misattributes component state on insert, delete, or sort | Use a stable domain identifier: `key={item.id}` |
160
- | Fetching or deriving synchronous state inside `useEffect` | Triggers an extra render pass and causes UI flicker | Derive values inline during render; use `use()` / Suspense or Actions for async data (`react-advanced`) |
161
93
  | Re-exporting components via `components/index.ts` | Violates Zero Barrel Files and harms tree-shaking | Import directly from `@/components/ui/card` |
162
94
 
163
95
  ---
164
96
 
165
- ## 6. Sibling Skills
97
+ ## 4. Sibling Skills
166
98
 
167
99
  - **`best-practices`**: Universal clean architecture, `< 200` lines limit, `@/` path aliases, strict typing, and courteous user feedback.
168
- - **`react-advanced`**: React 19 platform features, React Compiler 1.0 auto-memoization, concurrent rendering (`useTransition`, `useDeferredValue`, `Suspense`), `use()`, Actions (`useActionState`, `useOptimistic`, `useFormStatus`), context optimization, virtualization, and RSC boundaries.
100
+ - **`react-advanced`**: React 19 platform features, React Compiler auto-memoization, concurrent rendering, and RSC boundaries.
101
+ - **`react-create-page`**: Page coordinator architecture, custom domain hooks, and Valibot forms.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: testing-vitest
3
- description: Clean, deterministic unit and integration testing with Vitest. Enforces Arrange-Act-Assert (AAA), behavioral testing over implementation details, in-memory fakes over brittle mocks, strict assertions, and boundary/edge case test matrices. TRIGGER when writing, maintaining, or fixing automated tests (*.test.ts, *.spec.ts), test utilities, or testing infrastructure with Vitest.
3
+ description: Clean, deterministic unit, integration, and HTTP route testing with Vitest. Enforces Arrange-Act-Assert (AAA), behavioral testing over implementation details, in-memory SQLite/D1 fakes over brittle mocks, four-tier testing pyramid, and strict assertion contracts. TRIGGER when writing, maintaining, or fixing automated tests (*.test.ts, *.spec.ts), test utilities, or testing infrastructure with Vitest.
4
4
  ---
5
5
 
6
- # Vitest Testing Standards
6
+ # Vitest Testing Standards & 4-Tier Backend Pyramid
7
7
 
8
- Guidelines for authoring reliable, fast, and maintainable unit and integration tests with **Vitest**.
8
+ Guidelines for authoring reliable, fast, and maintainable unit, integration, and route tests with **Vitest**.
9
9
 
10
10
  ---
11
11
 
@@ -22,88 +22,95 @@ Guidelines for authoring reliable, fast, and maintainable unit and integration t
22
22
  - `// Assert`: Verify outputs and invariants.
23
23
 
24
24
  3. **In-Memory Fakes Over Brittle Mocks**:
25
- - Prefer in-memory fakes (e.g., `FakeUserRepository`, `MemoryStorage`) over deeply nested `vi.spyOn()` or mock chains.
26
- - Only mock external boundaries (network HTTP calls, system clocks, third-party APIs).
25
+ - Prefer in-memory fakes (e.g., `testDb` backed by `:memory:` SQLite / D1 emulator) over mock chains.
26
+ - Only mock external boundaries (network HTTP calls, system clocks, third-party payment APIs).
27
27
 
28
- 4. **Deterministic & Isolated (Zero Shared Mutable State)**:
29
- - Each test must run independently in any order without relying on state from preceding tests.
30
- - Use `beforeEach` strictly to reset test fixtures.
28
+ 4. **Four-Tier Testing Pyramid**:
29
+ - **Tier 1 (DTOs / Schemas)**: Test constraint validations, sanitization, trims, and invalid payload rejections.
30
+ - **Tier 2 (Actions)**: Test isolated business logic functions with typed context.
31
+ - **Tier 3 (Transactions)**: Test multi-mutation atomic batching and rollback mechanics.
32
+ - **Tier 4 (HTTP Routes)**: Mount thin routes on `testApp`, inject `testDb`, and verify HTTP lifecycle with `testApp.request()`.
31
33
 
32
- 5. **Exhaustive Edge Cases & Failure Modes**:
33
- - Always test boundary values: empty strings, zero, negative numbers, missing optional fields, expired tokens, and rejected promises.
34
- - Never write happy-path-only tests.
34
+ 5. **Mandatory Response Envelope Validation (`ActionResponse<T>`)**:
35
+ - Route tests must assert that responses adhere to `ActionResponse<T>` (`success`, `message`, `data`, `error`).
36
+ - Verify that sensitive fields (e.g. `passwordHash`) are stripped by output whitelisting schemas.
35
37
 
36
- 6. **Clear, Intention-Revealing Test Names**:
37
- - Use descriptive sentences: `it("should calculate tax accurately when order total exceeds threshold")`.
38
- - Avoid vague names like `it("works")` or `it("test 1")`.
38
+ 6. **Hard Size Limit: <200 Lines**:
39
+ - Keep test files focused and under 200 lines (target: 30-100 lines).
39
40
 
40
41
  ---
41
42
 
42
- ## 2. Standard Test Pattern (AAA)
43
-
44
- ```ts
45
- import { describe, it, expect } from "vitest";
46
- import { calculateDiscount } from "./calculate-discount";
47
-
48
- describe("calculateDiscount", () => {
49
- it("should apply 15% discount when user is a premium member", () => {
50
- // Arrange
51
- const originalPrice = 100;
52
- const isPremium = true;
53
-
54
- // Act
55
- const finalPrice = calculateDiscount(originalPrice, isPremium);
43
+ ## 2. Directory Taxonomy
44
+
45
+ ```text
46
+ packages/testing/src/backend/ (or tests/)
47
+ ├── setup.ts # Global testDb initialization & migrations
48
+ ├── mocks/
49
+ │ └── in-memory-d1.ts # In-memory SQLite emulator applying migrations
50
+ └── {domain}/
51
+ ├── {entity}-dto.test.ts # Tier 1: Schema validation tests
52
+ ├── {domain}-actions.test.ts# Tier 2: Pipeline actions unit tests
53
+ └── {action}.route.test.ts # Tier 4: Route integration tests
54
+ ```
56
55
 
57
- // Assert
58
- expect(finalPrice).toBe(85);
59
- });
56
+ ---
60
57
 
61
- it("should return unchanged price when discount percentage is zero", () => {
62
- // Arrange & Act
63
- const finalPrice = calculateDiscount(50, false);
58
+ ## 3. Four-Tier Pattern Examples
64
59
 
65
- // Assert
66
- expect(finalPrice).toBe(50);
60
+ ### Tier 1: DTO Validation Test
61
+ ```ts
62
+ import { describe, it, expect } from "vitest";
63
+ import * as v from "valibot";
64
+ import { CreateUserDto } from "@/dtos/users/create-user.dto";
65
+
66
+ describe("Users DTO Validation", () => {
67
+ it("should accept valid payload and lowercase email", () => {
68
+ const input = { fullName: "Carlos Santana", email: "CARLOS@EXAMPLE.COM" };
69
+ const parsed = v.parse(CreateUserDto, input);
70
+ expect(parsed.email).toBe("carlos@example.com");
67
71
  });
68
72
 
69
- it("should throw an AppError when input price is negative", () => {
70
- // Arrange, Act & Assert
71
- expect(() => calculateDiscount(-10, true)).toThrowError(/Price must be non-negative/);
73
+ it("should reject invalid email", () => {
74
+ expect(() => v.parse(CreateUserDto, { fullName: "Carlos", email: "invalid" })).toThrow();
72
75
  });
73
76
  });
74
77
  ```
75
78
 
76
- ---
77
-
78
- ## 3. Mocking External Boundaries Safely
79
-
79
+ ### Tier 4: Route Integration Test with `testApp.request()`
80
80
  ```ts
81
- import { describe, it, expect, vi, beforeEach } from "vitest";
82
- import { fetchExchangeRate } from "./fetch-exchange-rate";
83
-
84
- describe("fetchExchangeRate", () => {
85
- beforeEach(() => {
86
- vi.restoreAllMocks();
81
+ import { describe, it, expect, vi } from "vitest";
82
+ import { Hono } from "hono";
83
+ import { createUserRoute } from "@/routes/modules/users/create-user.route";
84
+ import { testDb } from "@/backend/setup";
85
+ import type { AppContext } from "@/types/context/app-context.type";
86
+ import type { ActionResponse } from "@/types/common/action-response.type";
87
+
88
+ describe("POST /users Route Integration", () => {
89
+ const testApp = new Hono<AppContext>();
90
+ testApp.use("*", async (ctx, next) => {
91
+ ctx.set("db", testDb);
92
+ ctx.set("logger", { info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any);
93
+ await next();
87
94
  });
95
+ testApp.route("/users", createUserRoute);
88
96
 
89
- it("should parse rate safely when API returns 200", async () => {
90
- const fakeFetch = vi.fn().mockResolvedValue({
91
- ok: true,
92
- json: async () => ({ rate: 1.08 }),
97
+ it("should return 201 with ActionResponse contract", async () => {
98
+ const response = await testApp.request("/users", {
99
+ method: "POST",
100
+ headers: { "Content-Type": "application/json" },
101
+ body: JSON.stringify({ fullName: "Valeria Gomez", email: "valeria@example.com" }),
93
102
  });
94
- vi.stubGlobal("fetch", fakeFetch);
95
-
96
- const rate = await fetchExchangeRate("EUR", "USD");
97
103
 
98
- expect(rate).toBe(1.08);
99
- expect(fakeFetch).toHaveBeenCalledWith(expect.stringContaining("EUR/USD"));
104
+ expect(response.status).toBe(201);
105
+ const json = (await response.json()) as ActionResponse<{ id: string; passwordHash?: string }>;
106
+ expect(json.success).toBe(true);
107
+ expect(json.data?.passwordHash).toBeUndefined();
100
108
  });
101
109
  });
102
110
  ```
103
111
 
104
112
  ---
105
113
 
106
- ## 4. Sibling Skills
114
+ ## 4. Deep References
107
115
 
108
- - **`best-practices`**: Universal clean code rules.
109
- - **`error-handling-logging`**: Testing expected `AppError` throws and error payloads.
116
+ - `./references/backend-d1-testing-pyramid.md`: Comprehensive guide for In-Memory SQLite/D1 setup and test matrices.
@@ -0,0 +1,69 @@
1
+ # Implementation Reference: Backend Testing Pyramid & In-Memory D1 Setup
2
+
3
+ A complete guide for orchestrating Vitest test suites across the 4-tier testing pyramid using in-memory SQLite / Cloudflare D1 emulators.
4
+
5
+ ---
6
+
7
+ ## 1. Global Setup with In-Memory Database (`setup.ts`)
8
+
9
+ Instead of recreating database tables inside every test file, initialize migrations once on an in-memory database:
10
+
11
+ ```ts
12
+ import { beforeAll, afterAll } from "vitest";
13
+ import Database from "better-sqlite3";
14
+ import { drizzle } from "drizzle-orm/better-sqlite3";
15
+ import { migrate } from "drizzle-orm/better-sqlite3/migrator";
16
+ import * as schema from "@/db/schema";
17
+
18
+ const sqliteConnection = new Database(":memory:");
19
+ export const testDb = drizzle(sqliteConnection, { schema });
20
+
21
+ beforeAll(async () => {
22
+ // Apply schema migrations automatically before suite execution
23
+ migrate(testDb, { migrationsFolder: "./drizzle" });
24
+ });
25
+
26
+ afterAll(() => {
27
+ sqliteConnection.close();
28
+ });
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 2. Testing Transactions & Rollback (Tier 3)
34
+
35
+ ```ts
36
+ import { describe, it, expect } from "vitest";
37
+ import { withD1Transaction } from "@/common/utils/d1-transaction";
38
+ import { testDb } from "@/backend/setup";
39
+ import { usersTable } from "@/db/schema";
40
+
41
+ describe("D1 Atomic Batch Transactions", () => {
42
+ it("should rollback all mutations if an error occurs mid-transaction", async () => {
43
+ const executeFailingTransaction = async () => {
44
+ await withD1Transaction(testDb as any, async (transaction) => {
45
+ transaction.stage(
46
+ testDb.insert(usersTable).values({ id: "user_1", email: "first@example.com" }) as any
47
+ );
48
+
49
+ throw new Error("Simulated failure mid-transaction");
50
+ });
51
+ };
52
+
53
+ await expect(executeFailingTransaction()).rejects.toThrow("Simulated failure mid-transaction");
54
+
55
+ const users = await testDb.select().from(usersTable);
56
+ expect(users).toHaveLength(0);
57
+ });
58
+ });
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 3. Best Practices Checklist
64
+
65
+ - [ ] Run migrations on in-memory SQLite rather than hitting live cloud databases.
66
+ - [ ] Reset mutable database state between tests using transaction rollbacks or truncation.
67
+ - [ ] Validate both happy path and rejection paths on every DTO schema.
68
+ - [ ] Ensure route tests never assert raw implementation details, only HTTP status codes and `ActionResponse<T>` envelopes.
69
+ - [ ] Keep every test file strictly below 200 lines.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: typescript-clean
3
- description: Advanced TypeScript 5.5+ standards, strict tsconfig rules, discriminated unions, satisfies operator, const type parameters, unknown over any, custom type guards, and elimination of unsafe type assertions (as / any). TRIGGER when writing or modifying TypeScript files (.ts, .tsx), type definitions, tsconfig.json, or when handling complex polymorphic domain models.
3
+ description: Advanced TypeScript 5.5+ standards, strict tsconfig rules, discriminated unions, satisfies operator, const type parameters, unknown over any, atomic type isolation in @/types/, and elimination of unsafe type assertions (as / any). TRIGGER when writing or modifying TypeScript files (.ts, .tsx), type definitions, tsconfig.json, or when handling complex polymorphic domain models.
4
4
  ---
5
5
 
6
6
  # TypeScript Clean Code Standards (TS 5.5+)
7
7
 
8
- Guidelines for writing safe, expressive, and strictly typed TypeScript with zero unsafe casts, exhaustive pattern matching, and modern language features.
8
+ Guidelines for writing safe, expressive, strictly typed TypeScript with zero unsafe casts, exhaustive pattern matching, and atomic domain type isolation.
9
9
 
10
10
  ---
11
11
 
@@ -15,31 +15,57 @@ Guidelines for writing safe, expressive, and strictly typed TypeScript with zero
15
15
  - `any` disables the compiler and hides runtime bugs. Use `unknown` with narrowers or generics.
16
16
  - Non-null assertion operator `!` assumes runtime state without proof. Use optional chaining `?.` or explicit guard clauses.
17
17
 
18
- 2. **Discriminated Unions for Domain States**:
18
+ 2. **Strict Type Isolation (`@/types/{domain}/`)**:
19
+ - In modular domain architectures, **NEVER** write `interface` or `type` definitions inside DTOs, Actions, Routes, or Utility helpers.
20
+ - Every contract has its own dedicated file: `@/types/{domain}/{name}.type.ts`.
21
+ - Organize into domains: `common/`, `context/`, `db/`, `pipeline/`, `services/`, and `{domain}/`.
22
+
23
+ 3. **Discriminated Unions for Domain States**:
19
24
  - Represent valid combinations of state using tagged unions (`type = "idle" | "loading" | "success" | "error"`).
20
25
  - Never use "optional soup" where every field is `x?: string`.
21
26
 
22
- 3. **Exhaustive Type Checking with `never`**:
27
+ 4. **Exhaustive Type Checking with `never`**:
23
28
  - Every `switch` or conditional on a union must have an `assertNever(value)` exhaustive check in default branch.
24
29
 
25
- 4. **Favor `satisfies` Over Type Annotations on Literals**:
30
+ 5. **Favor `satisfies` Over Type Annotations on Literals**:
26
31
  - `satisfies` checks that a value adheres to a contract without widening or discarding specific literal types.
27
32
 
28
- 5. **`const` Type Parameters (TS 5.0+)**:
33
+ 6. **`const` Type Parameters (TS 5.0+)**:
29
34
  - Use `<const T>` in generic helper functions to preserve literal types without requiring `as const` at call site.
30
35
 
31
- 6. **Custom Type Guards & Inferred Type Predicates (TS 5.5+)**:
36
+ 7. **Custom Type Guards & Inferred Type Predicates (TS 5.5+)**:
32
37
  - Use type predicates (`x is TargetType`) or rely on TS 5.5 automatic type narrowing for array filters.
33
38
 
34
- 7. **Avoid Unsafe `as` Type Assertions**:
39
+ 8. **Avoid Unsafe `as` Type Assertions**:
35
40
  - Never use `value as SomeType` to bypass the type checker. Validate with Valibot schema or custom type guard first.
36
41
 
42
+ 9. **Hard Size Limit of <200 Lines**:
43
+ - Keep type definition files atomic and concise (target: 10-50 lines).
44
+
37
45
  ---
38
46
 
39
- ## 2. Key Patterns
47
+ ## 2. Directory Structure for Type Isolation
48
+
49
+ ```text
50
+ src/types/
51
+ ├── common/ # Global shared contracts
52
+ │ └── action-response.type.ts # interface ActionResponse<T>
53
+ ├── context/ # Infrastructure & runtime execution context
54
+ │ ├── env.type.ts # interface Env (Bindings / Secrets)
55
+ │ └── app-context.type.ts # Context typing (Bindings, Variables)
56
+ ├── db/ # Database client and ORM entities
57
+ │ ├── database-client.type.ts # Database client handle
58
+ │ └── {entity}.type.ts # Select / Insert schema types
59
+ └── {domain}/ # Domain-specific DTO & action contracts
60
+ ├── create-{entity}-input.type.ts # Inferred from input DTO
61
+ └── {entity}-response.type.ts # Inferred from output DTO
62
+ ```
40
63
 
41
- ### A. Discriminated Unions & Exhaustiveness
64
+ ---
42
65
 
66
+ ## 3. Key Patterns
67
+
68
+ ### A. Discriminated Unions & Exhaustiveness
43
69
  ```ts
44
70
  export type RequestState<T> =
45
71
  | { status: "idle" }
@@ -68,33 +94,27 @@ export function renderStatus<T>(state: RequestState<T>): string {
68
94
  ```
69
95
 
70
96
  ### B. `satisfies` vs Widening Annotations
71
-
72
97
  ```ts
73
98
  type RouteConfig = Record<string, { path: string; isProtected: boolean }>;
74
99
 
75
- // ✅ Preserves exact key literals and structure
76
100
  export const APP_ROUTES = {
77
101
  HOME: { path: "/", isProtected: false },
78
102
  DASHBOARD: { path: "/dashboard", isProtected: true },
79
103
  } satisfies RouteConfig;
80
-
81
- // APP_ROUTES.HOME.path is typed as "/" not string!
82
104
  ```
83
105
 
84
106
  ### C. Const Generic Parameters
85
-
86
107
  ```ts
87
108
  export function createEnumSet<const T extends readonly string[]>(values: T): ReadonlySet<T[number]> {
88
109
  return new Set(values);
89
110
  }
90
111
 
91
- // Inferred as ReadonlySet<"admin" | "editor" | "viewer">
92
112
  export const ROLES = createEnumSet(["admin", "editor", "viewer"]);
93
113
  ```
94
114
 
95
115
  ---
96
116
 
97
- ## 3. Strict TSConfig Expectations
117
+ ## 4. Strict TSConfig Expectations
98
118
 
99
119
  Always enable:
100
120
  - `"strict": true`
@@ -104,8 +124,8 @@ Always enable:
104
124
 
105
125
  ---
106
126
 
107
- ## 4. Sibling Skills
127
+ ## 5. Sibling Skills
108
128
 
109
- - **`best-practices`**: Universal clean code rules and 200-line limit.
129
+ - **`best-practices`**: Universal clean code rules, descriptive naming, and 200-line limit.
110
130
  - **`valibot-validation`**: Runtime validation inferred to static TS types.
111
- - Reference guide: `references/js-ts-antipatterns.md`.
131
+ - Reference guide: `./references/js-ts-antipatterns.md`.