@polymorphism-tech/morph-spec 6.0.0 → 7.1.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.
- package/README.md +367 -366
- package/docs/CHEATSHEET.md +3 -3
- package/docs/QUICKSTART.md +4 -4
- package/framework/CLAUDE.md +13 -0
- package/framework/MORPH.md +57 -36
- package/framework/agents.json +132 -127
- package/framework/commands/morph-apply.md +4 -21
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-proposal.md +2 -0
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
- package/framework/hooks/claude-code/core/state-sync.js +212 -0
- package/framework/hooks/shared/phase-utils.js +5 -4
- package/framework/hooks/shared/state-reader.js +1 -1
- package/framework/phases.json +1 -1
- package/framework/rubrics/backend.json +41 -36
- package/framework/rules/ai-agents-standards.md +45 -15
- package/framework/rules/morph-workflow.md +15 -34
- package/framework/rules/neon-standards.md +25 -26
- package/framework/schemas/tasks.schema.json +35 -0
- package/framework/skills/core/morph-apply/SKILL.md +154 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
- package/framework/skills/core/morph-implement/SKILL.md +25 -14
- package/framework/skills/core/morph-plan/SKILL.md +91 -23
- package/framework/skills/core/morph-proposal/SKILL.md +137 -0
- package/framework/skills/core/morph-review/SKILL.md +43 -7
- package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
- package/framework/standards/STANDARDS.json +1729 -1134
- package/framework/standards/ai-agents/agent-session.md +160 -0
- package/framework/standards/ai-agents/batching.md +193 -0
- package/framework/standards/ai-agents/context-providers.md +188 -0
- package/framework/standards/ai-agents/mcp-server.md +250 -0
- package/framework/standards/ai-agents/mcp-tools.md +197 -0
- package/framework/standards/ai-agents/middleware-patterns.md +207 -0
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
- package/framework/standards/ai-agents/modalities-vision.md +201 -0
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
- package/framework/standards/ai-agents/production.md +114 -258
- package/framework/standards/ai-agents/prompts-in-db.md +214 -0
- package/framework/standards/ai-agents/providers/model-registry.md +158 -0
- package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
- package/framework/standards/ai-agents/service-tier-flex.md +135 -0
- package/framework/standards/ai-agents/setup.md +142 -316
- package/framework/standards/ai-agents/structured-output.md +249 -0
- package/framework/standards/ai-agents/sweet-spot.md +109 -0
- package/framework/standards/ai-agents/workflows.md +114 -284
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
- package/framework/standards/backend/api/validation.md +92 -88
- package/framework/standards/backend/database/postgresql/database.md +336 -332
- package/framework/standards/backend/database/vector-search-rag.md +599 -541
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
- package/framework/standards/backend/dotnet/result-pattern.md +4 -0
- package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
- package/framework/standards/backend/dotnet/scrutor.md +4 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
- package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
- package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
- package/framework/standards/data/nosql/cache/redis.md +106 -97
- package/framework/standards/data/nosql/cosmos-db.md +127 -118
- package/framework/standards/data/vector-search/rag-chunking.md +113 -104
- package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
- package/framework/standards/frontend/design-system/animations.md +164 -160
- package/framework/standards/frontend/design-system/naming.md +68 -64
- package/framework/standards/frontend/gsap/gsap-core.md +14 -8
- package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
- package/framework/standards/frontend/nextjs/app-router.md +127 -123
- package/framework/standards/frontend/nextjs/components.md +136 -132
- package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
- package/framework/standards/frontend/nextjs/forms.md +134 -128
- package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
- package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
- package/framework/standards/frontend/nextjs/project-structure.md +106 -102
- package/framework/standards/frontend/nextjs/state-management.md +76 -72
- package/framework/standards/frontend/nextjs/testing.md +115 -111
- package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
- package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
- package/framework/standards/integration/mcp/mcp-tools.md +471 -462
- package/framework/templates/REGISTRY.json +1745 -1239
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
- package/framework/templates/docs/onboarding.md +4 -4
- package/framework/templates/tasks.template.json +17 -0
- package/package.json +89 -83
- package/src/commands/project/init.js +16 -0
- package/src/commands/validation/validate-feature.js +1 -1
- package/src/core/paths/output-schema.js +6 -6
- package/src/core/state/state-manager.js +1 -1
- package/src/core/templates/template-registry.js +289 -256
- package/src/lib/detectors/design-system-detector.js +1 -1
- package/src/lib/generators/context-generator.js +516 -518
- package/src/lib/generators/scaffold-generator.js +23 -26
- package/src/lib/installers/memory-installer.js +138 -0
- package/src/lib/phase-chain/phase-validator.js +4 -4
- package/src/lib/risk-detector.js +80 -0
- package/src/lib/tasks/tasks-json-parser.js +185 -0
- package/src/scripts/setup-infra.js +25 -0
- package/src/utils/hooks-installer.js +59 -10
- package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
- package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
|
@@ -1,132 +1,136 @@
|
|
|
1
|
-
# Next.js Component Standards
|
|
2
|
-
|
|
3
|
-
> **Scope:** frontend/nextjs/components
|
|
4
|
-
> **Layer:** 2 (on keyword)
|
|
5
|
-
> **Keywords:** component, shadcn, reusable, shared, ui, three-tier
|
|
6
|
-
> **Load When:** creating or editing React components
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- NEVER
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Tier
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
1
|
+
# Next.js Component Standards
|
|
2
|
+
|
|
3
|
+
> **Scope:** frontend/nextjs/components
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** component, shadcn, reusable, shared, ui, three-tier
|
|
6
|
+
> **Load When:** creating or editing React components
|
|
7
|
+
|
|
8
|
+
**Verified against:** Next.js 15 + shadcn/ui CLI + TanStack Table 8. Last-verified: 2026-05-20.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Three-tier component hierarchy. `components/ui/` is shadcn primitives (never edit). `components/` is composed shared (no business logic). `features/*/components/` is feature-scoped.
|
|
13
|
+
|
|
14
|
+
## Core Rules
|
|
15
|
+
|
|
16
|
+
- NEVER edit files in `components/ui/` — they are regenerated by shadcn CLI
|
|
17
|
+
- NEVER import from `features/` inside `components/` — components know nothing about domain
|
|
18
|
+
- ALWAYS compose shadcn primitives in `components/` instead of editing them
|
|
19
|
+
- ALWAYS add `'use client'` only if the component uses hooks, events, or browser APIs
|
|
20
|
+
- NEVER pass raw API data directly to a component — transform to props first
|
|
21
|
+
|
|
22
|
+
## Three-Tier Hierarchy
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Tier 1: components/ui/ ← shadcn/ui CLI output (DO NOT EDIT)
|
|
26
|
+
↓ composed into
|
|
27
|
+
Tier 2: components/ ← shared project components (no business logic)
|
|
28
|
+
↓ used by
|
|
29
|
+
Tier 3: features/*/components/ ← feature-scoped (knows about users, billing, etc.)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Tier 1 — shadcn/ui Primitives
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Add shadcn components via CLI — never write them manually
|
|
36
|
+
npx shadcn@latest add button card dialog form input table
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If you need to change a shadcn component's behavior, wrap it — do not edit the source file.
|
|
40
|
+
|
|
41
|
+
## Tier 2 — Shared Composed Components
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// components/data-table.tsx — composes shadcn Table + TanStack Table
|
|
45
|
+
'use client';
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
Table, TableBody, TableCell, TableHead, TableHeader, TableRow
|
|
49
|
+
} from '@/components/ui/table';
|
|
50
|
+
import {
|
|
51
|
+
type ColumnDef, flexRender, getCoreRowModel, useReactTable
|
|
52
|
+
} from '@tanstack/react-table';
|
|
53
|
+
|
|
54
|
+
interface DataTableProps<TData> {
|
|
55
|
+
columns: ColumnDef<TData>[];
|
|
56
|
+
data: TData[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function DataTable<TData>({ columns, data }: DataTableProps<TData>) {
|
|
60
|
+
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
|
|
61
|
+
return (
|
|
62
|
+
<Table>
|
|
63
|
+
<TableHeader>
|
|
64
|
+
{table.getHeaderGroups().map((hg) => (
|
|
65
|
+
<TableRow key={hg.id}>
|
|
66
|
+
{hg.headers.map((h) => (
|
|
67
|
+
<TableHead key={h.id}>
|
|
68
|
+
{flexRender(h.column.columnDef.header, h.getContext())}
|
|
69
|
+
</TableHead>
|
|
70
|
+
))}
|
|
71
|
+
</TableRow>
|
|
72
|
+
))}
|
|
73
|
+
</TableHeader>
|
|
74
|
+
<TableBody>
|
|
75
|
+
{table.getRowModel().rows.map((row) => (
|
|
76
|
+
<TableRow key={row.id}>
|
|
77
|
+
{row.getVisibleCells().map((cell) => (
|
|
78
|
+
<TableCell key={cell.id}>
|
|
79
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
80
|
+
</TableCell>
|
|
81
|
+
))}
|
|
82
|
+
</TableRow>
|
|
83
|
+
))}
|
|
84
|
+
</TableBody>
|
|
85
|
+
</Table>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Tier 3 — Feature Components
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// features/users/components/user-list.tsx
|
|
94
|
+
'use client';
|
|
95
|
+
|
|
96
|
+
import { DataTable } from '@/components/data-table';
|
|
97
|
+
import { useUsers } from '@/features/users/hooks/use-users';
|
|
98
|
+
import type { ColumnDef } from '@tanstack/react-table';
|
|
99
|
+
import type { User } from '@/features/users/types/user.types';
|
|
100
|
+
|
|
101
|
+
const columns: ColumnDef<User>[] = [
|
|
102
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
103
|
+
{ accessorKey: 'email', header: 'Email' },
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
export function UserList() {
|
|
107
|
+
const { data: users = [], isLoading } = useUsers();
|
|
108
|
+
if (isLoading) return <div>Loading...</div>;
|
|
109
|
+
return <DataTable columns={columns} data={users} />;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Component Props Conventions
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
interface UserCardProps {
|
|
117
|
+
user: User;
|
|
118
|
+
onEdit?: (id: string) => void;
|
|
119
|
+
className?: string; // Always allow className for Tailwind override
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function UserCard({ user, onEdit, className }: UserCardProps) {}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Common Mistakes
|
|
126
|
+
|
|
127
|
+
| Wrong | Right | Why |
|
|
128
|
+
|-------|-------|-----|
|
|
129
|
+
| Edit `components/ui/button.tsx` | Wrap in `components/action-button.tsx` | shadcn CLI overwrites it |
|
|
130
|
+
| `import { useUsers } from '@/features/users'` in `components/` | Move to a feature component | Breaks tier isolation |
|
|
131
|
+
| `'use client'` on all components | Only on interactive ones | Unnecessary client JS |
|
|
132
|
+
| Pass raw fetch response as prop | Type and validate with Zod first | Runtime type safety |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -1,126 +1,130 @@
|
|
|
1
|
-
# Next.js Data Fetching Standard
|
|
2
|
-
|
|
3
|
-
> **Scope:** frontend/nextjs/data-fetching
|
|
4
|
-
> **Layer:** 2 (on keyword)
|
|
5
|
-
> **Keywords:** data fetching, tanstack query, react query, server component, fetch, api, useQuery, useMutation
|
|
6
|
-
> **Load When:** fetching data from .NET API
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- ALWAYS use
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
1
|
+
# Next.js Data Fetching Standard
|
|
2
|
+
|
|
3
|
+
> **Scope:** frontend/nextjs/data-fetching
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** data fetching, tanstack query, react query, server component, fetch, api, useQuery, useMutation
|
|
6
|
+
> **Load When:** fetching data from .NET API
|
|
7
|
+
|
|
8
|
+
**Verified against:** Next.js 15 + TanStack Query 5 + Zod 4. Last-verified: 2026-05-20.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Server Components for initial page load. TanStack Query for client mutations and interactive data. Never `useEffect` for fetching.
|
|
13
|
+
|
|
14
|
+
## Core Rules
|
|
15
|
+
|
|
16
|
+
- ALWAYS use Server Components for initial data that doesn't require interactivity
|
|
17
|
+
- ALWAYS use TanStack Query (`useQuery`, `useMutation`) for client-side data needs
|
|
18
|
+
- NEVER use `useEffect(() => { fetch(...) }, [])` — this is the old pattern
|
|
19
|
+
- ALWAYS validate API responses with Zod before using them
|
|
20
|
+
- ALWAYS use query key factories for consistent cache invalidation
|
|
21
|
+
|
|
22
|
+
## TanStack Query Setup
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// lib/query-client.tsx
|
|
26
|
+
'use client';
|
|
27
|
+
|
|
28
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
29
|
+
import { useState } from 'react';
|
|
30
|
+
|
|
31
|
+
export function QueryProvider({ children }: { children: React.ReactNode }) {
|
|
32
|
+
const [queryClient] = useState(() => new QueryClient({
|
|
33
|
+
defaultOptions: { queries: { staleTime: 60 * 1000, retry: 1 } },
|
|
34
|
+
}));
|
|
35
|
+
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Query Key Factory Pattern
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// features/users/hooks/query-keys.ts
|
|
43
|
+
export const userKeys = {
|
|
44
|
+
all: ['users'] as const,
|
|
45
|
+
lists: () => [...userKeys.all, 'list'] as const,
|
|
46
|
+
list: (filters: Record<string, unknown>) => [...userKeys.lists(), filters] as const,
|
|
47
|
+
details: () => [...userKeys.all, 'detail'] as const,
|
|
48
|
+
detail: (id: string) => [...userKeys.details(), id] as const,
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## useQuery Hook Pattern
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// features/users/hooks/use-users.ts
|
|
56
|
+
import { useQuery } from '@tanstack/react-query';
|
|
57
|
+
import { userKeys } from './query-keys';
|
|
58
|
+
import { z } from 'zod';
|
|
59
|
+
import { userSchema } from '@/features/users/types/user.schemas';
|
|
60
|
+
|
|
61
|
+
const usersResponseSchema = z.array(userSchema);
|
|
62
|
+
|
|
63
|
+
export function useUsers() {
|
|
64
|
+
return useQuery({
|
|
65
|
+
queryKey: userKeys.lists(),
|
|
66
|
+
queryFn: async () => {
|
|
67
|
+
const res = await fetch('/api/proxy/users');
|
|
68
|
+
if (!res.ok) throw new Error('Failed to fetch users');
|
|
69
|
+
return usersResponseSchema.parse(await res.json());
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## useMutation Hook Pattern
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// features/users/hooks/use-create-user.ts
|
|
79
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
80
|
+
import { userKeys } from './query-keys';
|
|
81
|
+
import type { CreateUserInput } from '@/features/users/types/user.types';
|
|
82
|
+
|
|
83
|
+
export function useCreateUser() {
|
|
84
|
+
const queryClient = useQueryClient();
|
|
85
|
+
return useMutation({
|
|
86
|
+
mutationFn: async (input: CreateUserInput) => {
|
|
87
|
+
const res = await fetch('/api/proxy/users', {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'Content-Type': 'application/json' },
|
|
90
|
+
body: JSON.stringify(input),
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok) throw new Error('Failed to create user');
|
|
93
|
+
return res.json();
|
|
94
|
+
},
|
|
95
|
+
onSuccess: () => {
|
|
96
|
+
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Server Component + TanStack Query Handoff
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// app/(dashboard)/users/page.tsx — Server Component: initial fetch
|
|
106
|
+
export default async function UsersPage() {
|
|
107
|
+
const initialUsers = await fetch(`${process.env.API_URL}/api/users`).then(r => r.json());
|
|
108
|
+
return <UserListClient initialData={initialUsers} />;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// features/users/components/user-list-client.tsx — Client Component
|
|
112
|
+
'use client';
|
|
113
|
+
export function UserListClient({ initialData }: { initialData: User[] }) {
|
|
114
|
+
const { data: users = initialData } = useUsers();
|
|
115
|
+
// mutations, filtering, etc.
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Common Mistakes
|
|
120
|
+
|
|
121
|
+
| Wrong | Right | Why |
|
|
122
|
+
|-------|-------|-----|
|
|
123
|
+
| `useEffect(() => { fetch(...) }, [])` | `useQuery(...)` | Two renders, no cache, no SSR |
|
|
124
|
+
| No Zod on API response | `schema.parse(await res.json())` | Type safety at runtime |
|
|
125
|
+
| Hardcoded query keys: `['users']` | Key factory: `userKeys.lists()` | Consistent invalidation |
|
|
126
|
+
| `queryClient.invalidateQueries(['users'])` | `{ queryKey: userKeys.lists() }` | Object syntax required in v5 |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
*MORPH-SPEC by Polymorphism Tech*
|