@jaimevalasek/aioson 1.3.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.
Files changed (288) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/CODE_OF_CONDUCT.md +12 -0
  3. package/CONTRIBUTING.md +13 -0
  4. package/LICENSE +21 -0
  5. package/README.md +254 -0
  6. package/bin/aioson.js +4 -0
  7. package/docs/en/cli-reference.md +398 -0
  8. package/docs/en/i18n.md +52 -0
  9. package/docs/en/json-schemas.md +41 -0
  10. package/docs/en/mcp.md +56 -0
  11. package/docs/en/parallel.md +82 -0
  12. package/docs/en/qa-browser.md +339 -0
  13. package/docs/en/release-flow.md +22 -0
  14. package/docs/en/release-notes-template.md +41 -0
  15. package/docs/en/release.md +28 -0
  16. package/docs/en/schemas/agent-prompt.schema.json +17 -0
  17. package/docs/en/schemas/agents.schema.json +32 -0
  18. package/docs/en/schemas/context-validate.schema.json +36 -0
  19. package/docs/en/schemas/doctor.schema.json +89 -0
  20. package/docs/en/schemas/error.schema.json +24 -0
  21. package/docs/en/schemas/i18n-add.schema.json +15 -0
  22. package/docs/en/schemas/index.json +116 -0
  23. package/docs/en/schemas/info.schema.json +39 -0
  24. package/docs/en/schemas/init.schema.json +48 -0
  25. package/docs/en/schemas/install.schema.json +60 -0
  26. package/docs/en/schemas/locale-apply.schema.json +30 -0
  27. package/docs/en/schemas/mcp-doctor.schema.json +95 -0
  28. package/docs/en/schemas/mcp-init.schema.json +122 -0
  29. package/docs/en/schemas/package-test.schema.json +24 -0
  30. package/docs/en/schemas/parallel-assign.schema.json +57 -0
  31. package/docs/en/schemas/parallel-doctor.schema.json +86 -0
  32. package/docs/en/schemas/parallel-init.schema.json +53 -0
  33. package/docs/en/schemas/parallel-status.schema.json +94 -0
  34. package/docs/en/schemas/setup-context.schema.json +39 -0
  35. package/docs/en/schemas/smoke.schema.json +23 -0
  36. package/docs/en/schemas/update.schema.json +48 -0
  37. package/docs/en/schemas/workflow-plan.schema.json +30 -0
  38. package/docs/en/web3.md +54 -0
  39. package/docs/pt/README.md +46 -0
  40. package/docs/pt/advisor-spec.md +335 -0
  41. package/docs/pt/agentes.md +453 -0
  42. package/docs/pt/cenarios.md +1230 -0
  43. package/docs/pt/clientes-ai.md +224 -0
  44. package/docs/pt/comandos-cli.md +511 -0
  45. package/docs/pt/genome-3.0-spec.md +296 -0
  46. package/docs/pt/guia-engineer.md +226 -0
  47. package/docs/pt/inicio-rapido.md +138 -0
  48. package/docs/pt/profiler-system.md +214 -0
  49. package/docs/pt/runtime-observability.md +72 -0
  50. package/docs/pt/squad-genoma.md +777 -0
  51. package/docs/pt/web3.md +797 -0
  52. package/docs/testing/genome-2.0-manual-regression.md +23 -0
  53. package/docs/testing/genome-2.0-matrix.md +36 -0
  54. package/docs/testing/genome-2.0-rollout.md +184 -0
  55. package/package.json +50 -0
  56. package/src/agents.js +56 -0
  57. package/src/cli.js +497 -0
  58. package/src/commands/agents.js +142 -0
  59. package/src/commands/cloud.js +1767 -0
  60. package/src/commands/config.js +90 -0
  61. package/src/commands/context-validate.js +91 -0
  62. package/src/commands/doctor.js +123 -0
  63. package/src/commands/genome-doctor.js +41 -0
  64. package/src/commands/genome-migrate.js +49 -0
  65. package/src/commands/i18n-add.js +56 -0
  66. package/src/commands/info.js +41 -0
  67. package/src/commands/init.js +75 -0
  68. package/src/commands/install.js +68 -0
  69. package/src/commands/locale-apply.js +51 -0
  70. package/src/commands/locale-diff.js +126 -0
  71. package/src/commands/mcp-doctor.js +406 -0
  72. package/src/commands/mcp-init.js +379 -0
  73. package/src/commands/package-e2e.js +273 -0
  74. package/src/commands/parallel-assign.js +403 -0
  75. package/src/commands/parallel-doctor.js +437 -0
  76. package/src/commands/parallel-init.js +249 -0
  77. package/src/commands/parallel-status.js +290 -0
  78. package/src/commands/qa-doctor.js +185 -0
  79. package/src/commands/qa-init.js +161 -0
  80. package/src/commands/qa-report.js +58 -0
  81. package/src/commands/qa-run.js +873 -0
  82. package/src/commands/qa-scan.js +337 -0
  83. package/src/commands/runtime.js +948 -0
  84. package/src/commands/scan-project.js +1107 -0
  85. package/src/commands/setup-context.js +650 -0
  86. package/src/commands/smoke.js +426 -0
  87. package/src/commands/squad-doctor.js +358 -0
  88. package/src/commands/squad-export.js +46 -0
  89. package/src/commands/squad-pipeline.js +97 -0
  90. package/src/commands/squad-repair-genomes.js +39 -0
  91. package/src/commands/squad-status.js +424 -0
  92. package/src/commands/squad-validate.js +230 -0
  93. package/src/commands/test-agents.js +194 -0
  94. package/src/commands/update.js +55 -0
  95. package/src/commands/workflow-next.js +594 -0
  96. package/src/commands/workflow-plan.js +108 -0
  97. package/src/constants.js +314 -0
  98. package/src/context-parse-reason.js +22 -0
  99. package/src/context-writer.js +150 -0
  100. package/src/context.js +217 -0
  101. package/src/detector.js +261 -0
  102. package/src/doctor.js +289 -0
  103. package/src/execution-gateway.js +461 -0
  104. package/src/genome-files.js +198 -0
  105. package/src/genome-format.js +442 -0
  106. package/src/genome-schema.js +215 -0
  107. package/src/genomes/bindings.js +281 -0
  108. package/src/genomes.js +467 -0
  109. package/src/i18n/index.js +103 -0
  110. package/src/i18n/messages/en.js +784 -0
  111. package/src/i18n/messages/es.js +718 -0
  112. package/src/i18n/messages/fr.js +725 -0
  113. package/src/i18n/messages/pt-BR.js +818 -0
  114. package/src/i18n/scaffold.js +64 -0
  115. package/src/installer.js +232 -0
  116. package/src/lib/genomes/compat.js +206 -0
  117. package/src/lib/genomes/migrate.js +90 -0
  118. package/src/lib/squads/genome-repair.js +49 -0
  119. package/src/locales.js +84 -0
  120. package/src/onboarding.js +305 -0
  121. package/src/parser.js +53 -0
  122. package/src/prompt-tool.js +20 -0
  123. package/src/qa-html-report.js +472 -0
  124. package/src/runtime-store.js +1527 -0
  125. package/src/squads/apply-genome.js +21 -0
  126. package/src/squads/genome-binding-service.js +154 -0
  127. package/src/updater.js +32 -0
  128. package/src/utils.js +46 -0
  129. package/src/version.js +50 -0
  130. package/template/.aioson/advisors/.gitkeep +1 -0
  131. package/template/.aioson/agents/analyst.md +225 -0
  132. package/template/.aioson/agents/architect.md +221 -0
  133. package/template/.aioson/agents/dev.md +201 -0
  134. package/template/.aioson/agents/discovery-design-doc.md +196 -0
  135. package/template/.aioson/agents/genoma.md +300 -0
  136. package/template/.aioson/agents/orchestrator.md +107 -0
  137. package/template/.aioson/agents/pm.md +89 -0
  138. package/template/.aioson/agents/product.md +361 -0
  139. package/template/.aioson/agents/profiler-enricher.md +266 -0
  140. package/template/.aioson/agents/profiler-forge.md +188 -0
  141. package/template/.aioson/agents/profiler-researcher.md +245 -0
  142. package/template/.aioson/agents/qa.md +344 -0
  143. package/template/.aioson/agents/setup.md +381 -0
  144. package/template/.aioson/agents/squad.md +837 -0
  145. package/template/.aioson/agents/ux-ui.md +416 -0
  146. package/template/.aioson/config.md +56 -0
  147. package/template/.aioson/context/.gitkeep +0 -0
  148. package/template/.aioson/context/parallel/.gitkeep +0 -0
  149. package/template/.aioson/context/spec.md.template +37 -0
  150. package/template/.aioson/genomas/.gitkeep +0 -0
  151. package/template/.aioson/locales/en/agents/analyst.md +214 -0
  152. package/template/.aioson/locales/en/agents/architect.md +210 -0
  153. package/template/.aioson/locales/en/agents/dev.md +187 -0
  154. package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
  155. package/template/.aioson/locales/en/agents/genoma.md +212 -0
  156. package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
  157. package/template/.aioson/locales/en/agents/pm.md +77 -0
  158. package/template/.aioson/locales/en/agents/product.md +310 -0
  159. package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
  160. package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
  161. package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
  162. package/template/.aioson/locales/en/agents/qa.md +214 -0
  163. package/template/.aioson/locales/en/agents/setup.md +342 -0
  164. package/template/.aioson/locales/en/agents/squad.md +247 -0
  165. package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
  166. package/template/.aioson/locales/es/agents/analyst.md +203 -0
  167. package/template/.aioson/locales/es/agents/architect.md +208 -0
  168. package/template/.aioson/locales/es/agents/dev.md +183 -0
  169. package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
  170. package/template/.aioson/locales/es/agents/genoma.md +102 -0
  171. package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
  172. package/template/.aioson/locales/es/agents/pm.md +81 -0
  173. package/template/.aioson/locales/es/agents/product.md +310 -0
  174. package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
  175. package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
  176. package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
  177. package/template/.aioson/locales/es/agents/qa.md +163 -0
  178. package/template/.aioson/locales/es/agents/setup.md +347 -0
  179. package/template/.aioson/locales/es/agents/squad.md +247 -0
  180. package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
  181. package/template/.aioson/locales/fr/agents/analyst.md +203 -0
  182. package/template/.aioson/locales/fr/agents/architect.md +208 -0
  183. package/template/.aioson/locales/fr/agents/dev.md +183 -0
  184. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
  185. package/template/.aioson/locales/fr/agents/genoma.md +102 -0
  186. package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
  187. package/template/.aioson/locales/fr/agents/pm.md +81 -0
  188. package/template/.aioson/locales/fr/agents/product.md +310 -0
  189. package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
  190. package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
  191. package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
  192. package/template/.aioson/locales/fr/agents/qa.md +163 -0
  193. package/template/.aioson/locales/fr/agents/setup.md +347 -0
  194. package/template/.aioson/locales/fr/agents/squad.md +247 -0
  195. package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
  196. package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
  197. package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
  198. package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
  199. package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
  200. package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
  201. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
  202. package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
  203. package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
  204. package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
  205. package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
  206. package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
  207. package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
  208. package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
  209. package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
  210. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
  211. package/template/.aioson/mcp/servers.md +24 -0
  212. package/template/.aioson/profiler-reports/.gitkeep +1 -0
  213. package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
  214. package/template/.aioson/schemas/genome-meta.schema.json +150 -0
  215. package/template/.aioson/schemas/genome.schema.json +115 -0
  216. package/template/.aioson/schemas/readiness.schema.json +27 -0
  217. package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
  218. package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
  219. package/template/.aioson/skills/dynamic/README.md +30 -0
  220. package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
  221. package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
  222. package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
  223. package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
  224. package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
  225. package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
  226. package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
  227. package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
  228. package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
  229. package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
  230. package/template/.aioson/skills/static/django-patterns.md +342 -0
  231. package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
  232. package/template/.aioson/skills/static/filament-patterns.md +267 -0
  233. package/template/.aioson/skills/static/flux-ui-components.md +262 -0
  234. package/template/.aioson/skills/static/git-conventions.md +227 -0
  235. package/template/.aioson/skills/static/interface-design.md +372 -0
  236. package/template/.aioson/skills/static/jetstream-setup.md +200 -0
  237. package/template/.aioson/skills/static/laravel-conventions.md +491 -0
  238. package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
  239. package/template/.aioson/skills/static/node-express-patterns.md +317 -0
  240. package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
  241. package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
  242. package/template/.aioson/skills/static/rails-conventions.md +307 -0
  243. package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
  244. package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
  245. package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
  246. package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
  247. package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
  248. package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
  249. package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
  250. package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
  251. package/template/.aioson/squads/.artisan/.gitkeep +0 -0
  252. package/template/.aioson/squads/.gitkeep +0 -0
  253. package/template/.aioson/squads/memory.md +5 -0
  254. package/template/.aioson/tasks/squad-analyze.md +83 -0
  255. package/template/.aioson/tasks/squad-create.md +99 -0
  256. package/template/.aioson/tasks/squad-design.md +100 -0
  257. package/template/.aioson/tasks/squad-export.md +20 -0
  258. package/template/.aioson/tasks/squad-extend.md +68 -0
  259. package/template/.aioson/tasks/squad-pipeline.md +122 -0
  260. package/template/.aioson/tasks/squad-repair.md +85 -0
  261. package/template/.aioson/tasks/squad-validate.md +58 -0
  262. package/template/.aioson/templates/squads/content-basic/template.json +21 -0
  263. package/template/.aioson/templates/squads/media-channel/template.json +24 -0
  264. package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
  265. package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
  266. package/template/.claude/commands/aioson/analyst.md +5 -0
  267. package/template/.claude/commands/aioson/architect.md +5 -0
  268. package/template/.claude/commands/aioson/dev.md +5 -0
  269. package/template/.claude/commands/aioson/orchestrator.md +5 -0
  270. package/template/.claude/commands/aioson/pm.md +5 -0
  271. package/template/.claude/commands/aioson/qa.md +5 -0
  272. package/template/.claude/commands/aioson/setup.md +5 -0
  273. package/template/.claude/commands/aioson/ux-ui.md +5 -0
  274. package/template/.gemini/GEMINI.md +10 -0
  275. package/template/.gemini/commands/aios-analyst.toml +4 -0
  276. package/template/.gemini/commands/aios-architect.toml +7 -0
  277. package/template/.gemini/commands/aios-dev.toml +8 -0
  278. package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
  279. package/template/.gemini/commands/aios-orchestrator.toml +8 -0
  280. package/template/.gemini/commands/aios-pm.toml +8 -0
  281. package/template/.gemini/commands/aios-product.toml +4 -0
  282. package/template/.gemini/commands/aios-qa.toml +6 -0
  283. package/template/.gemini/commands/aios-setup.toml +3 -0
  284. package/template/.gemini/commands/aios-ux-ui.toml +8 -0
  285. package/template/AGENTS.md +67 -0
  286. package/template/CLAUDE.md +31 -0
  287. package/template/OPENCODE.md +24 -0
  288. package/template/aioson-models.json +40 -0
@@ -0,0 +1,321 @@
1
+ # Next.js Patterns
2
+
3
+ > App Router, Server Components, and Server Actions. Build fast by default.
4
+
5
+ ---
6
+
7
+ ## Mental model: Server vs Client
8
+
9
+ ```
10
+ Server Components (default) → fetch data, render HTML, never ship JS to browser
11
+ Client Components → interactivity, useState, useEffect, browser APIs
12
+ Server Actions → mutations from forms and client events, run on server
13
+ Route Handlers → REST-like API endpoints (for external consumers)
14
+ ```
15
+
16
+ **Rule:** Start every component as a Server Component. Add `"use client"` only when you need browser APIs, state, or event handlers.
17
+
18
+ ---
19
+
20
+ ## Project structure (App Router)
21
+
22
+ ```
23
+ src/
24
+ app/
25
+ (auth)/ ← route group, no URL segment
26
+ login/page.tsx
27
+ register/page.tsx
28
+ layout.tsx ← minimal layout for auth pages
29
+ (dashboard)/
30
+ layout.tsx ← authenticated shell (sidebar, nav)
31
+ page.tsx ← /dashboard
32
+ appointments/
33
+ page.tsx ← /appointments (list)
34
+ [id]/
35
+ page.tsx ← /appointments/123
36
+ edit/page.tsx ← /appointments/123/edit
37
+ settings/page.tsx
38
+ api/
39
+ webhooks/
40
+ stripe/route.ts ← POST /api/webhooks/stripe
41
+ components/
42
+ ui/ ← design system primitives (Button, Input, Modal)
43
+ features/
44
+ appointments/ ← feature-specific components
45
+ AppointmentCard.tsx
46
+ AppointmentList.tsx ← Server Component (fetches data)
47
+ BookingForm.tsx ← Client Component (form state)
48
+ lib/
49
+ db.ts ← Prisma client singleton
50
+ auth.ts ← NextAuth config
51
+ stripe.ts ← Stripe client
52
+ actions/ ← Server Actions
53
+ appointment.actions.ts
54
+ billing.actions.ts
55
+ types/
56
+ index.ts
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Data fetching — Server Components
62
+
63
+ ```tsx
64
+ // app/(dashboard)/appointments/page.tsx
65
+ // No useEffect, no useState, no API call — just async/await
66
+ export default async function AppointmentsPage() {
67
+ const session = await auth();
68
+ if (!session) redirect('/login');
69
+
70
+ const appointments = await db.appointment.findMany({
71
+ where: { userId: session.user.id },
72
+ include: { doctor: true },
73
+ orderBy: { date: 'asc' },
74
+ take: 50,
75
+ });
76
+
77
+ return (
78
+ <div>
79
+ <AppointmentList appointments={appointments} />
80
+ </div>
81
+ );
82
+ }
83
+
84
+ // With suspense for streaming
85
+ export default function Page() {
86
+ return (
87
+ <Suspense fallback={<AppointmentListSkeleton />}>
88
+ <AppointmentList />
89
+ </Suspense>
90
+ );
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Server Actions — mutations
97
+
98
+ ```ts
99
+ // actions/appointment.actions.ts
100
+ 'use server';
101
+
102
+ import { auth } from '@/lib/auth';
103
+ import { redirect } from 'next/navigation';
104
+ import { revalidatePath } from 'next/cache';
105
+ import { z } from 'zod';
106
+
107
+ const createSchema = z.object({
108
+ doctorId: z.string().uuid(),
109
+ date: z.string().datetime(),
110
+ notes: z.string().max(500).optional(),
111
+ });
112
+
113
+ export async function createAppointment(formData: FormData) {
114
+ const session = await auth();
115
+ if (!session) throw new Error('Unauthorized');
116
+
117
+ const parsed = createSchema.safeParse({
118
+ doctorId: formData.get('doctorId'),
119
+ date: formData.get('date'),
120
+ notes: formData.get('notes'),
121
+ });
122
+
123
+ if (!parsed.success) {
124
+ return { error: parsed.error.flatten().fieldErrors };
125
+ }
126
+
127
+ // Check for conflicts
128
+ const conflict = await db.appointment.findFirst({
129
+ where: {
130
+ doctorId: parsed.data.doctorId,
131
+ date: new Date(parsed.data.date),
132
+ status: { not: 'cancelled' },
133
+ },
134
+ });
135
+
136
+ if (conflict) {
137
+ return { error: { date: ['This time slot is not available.'] } };
138
+ }
139
+
140
+ await db.appointment.create({
141
+ data: { ...parsed.data, userId: session.user.id },
142
+ });
143
+
144
+ revalidatePath('/appointments');
145
+ redirect('/appointments');
146
+ }
147
+ ```
148
+
149
+ Usage in a Client Component:
150
+
151
+ ```tsx
152
+ 'use client';
153
+
154
+ import { createAppointment } from '@/actions/appointment.actions';
155
+ import { useActionState } from 'react';
156
+
157
+ export function BookingForm({ doctors }: { doctors: Doctor[] }) {
158
+ const [state, action, pending] = useActionState(createAppointment, null);
159
+
160
+ return (
161
+ <form action={action}>
162
+ <select name="doctorId" required>
163
+ {doctors.map(d => (
164
+ <option key={d.id} value={d.id}>{d.name}</option>
165
+ ))}
166
+ </select>
167
+ {state?.error?.doctorId && <p className="text-red-500">{state.error.doctorId[0]}</p>}
168
+
169
+ <input name="date" type="datetime-local" required />
170
+ {state?.error?.date && <p className="text-red-500">{state.error.date[0]}</p>}
171
+
172
+ <button type="submit" disabled={pending}>
173
+ {pending ? 'Booking...' : 'Book Appointment'}
174
+ </button>
175
+ </form>
176
+ );
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Client Components — when and how
183
+
184
+ ```tsx
185
+ 'use client'; // Only add when needed
186
+
187
+ // Good reasons for "use client":
188
+ // - useState, useEffect, useReducer
189
+ // - Event handlers (onClick, onChange)
190
+ // - Browser APIs (localStorage, window, navigator)
191
+ // - Third-party libs that need DOM access
192
+
193
+ import { useState } from 'react';
194
+
195
+ export function ConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
196
+ const [open, setOpen] = useState(false);
197
+
198
+ return (
199
+ <>
200
+ <button onClick={() => setOpen(true)}>Delete</button>
201
+ {open && (
202
+ <dialog open>
203
+ <p>Are you sure?</p>
204
+ <button onClick={() => { onConfirm(); setOpen(false); }}>Confirm</button>
205
+ <button onClick={() => setOpen(false)}>Cancel</button>
206
+ </dialog>
207
+ )}
208
+ </>
209
+ );
210
+ }
211
+ ```
212
+
213
+ **Pass Server data as props — do not re-fetch in Client Components:**
214
+
215
+ ```tsx
216
+ // WRONG — Client Component fetching its own data
217
+ 'use client';
218
+ export function AppointmentList() {
219
+ const [appointments, setAppointments] = useState([]);
220
+ useEffect(() => { fetch('/api/appointments').then(...) }, []);
221
+ // ...
222
+ }
223
+
224
+ // RIGHT — Server Component passes data as props
225
+ // app/page.tsx (Server)
226
+ const appointments = await db.appointment.findMany(...);
227
+ return <AppointmentList appointments={appointments} />;
228
+
229
+ // components/AppointmentList.tsx (can be Server too, or Client if interactive)
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Route Handlers — for external consumers only
235
+
236
+ ```ts
237
+ // app/api/webhooks/stripe/route.ts
238
+ import Stripe from 'stripe';
239
+
240
+ export async function POST(request: Request) {
241
+ const signature = request.headers.get('stripe-signature')!;
242
+ const body = await request.text();
243
+
244
+ let event: Stripe.Event;
245
+ try {
246
+ event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
247
+ } catch {
248
+ return new Response('Invalid signature', { status: 400 });
249
+ }
250
+
251
+ switch (event.type) {
252
+ case 'invoice.paid':
253
+ await handleInvoicePaid(event.data.object as Stripe.Invoice);
254
+ break;
255
+ case 'customer.subscription.deleted':
256
+ await handleSubscriptionCancelled(event.data.object as Stripe.Subscription);
257
+ break;
258
+ }
259
+
260
+ return new Response('OK');
261
+ }
262
+ ```
263
+
264
+ Use Route Handlers for webhooks and external API consumers. Use Server Actions for mutations from your own UI.
265
+
266
+ ---
267
+
268
+ ## Metadata and SEO
269
+
270
+ ```tsx
271
+ // app/appointments/[id]/page.tsx
272
+ import type { Metadata } from 'next';
273
+
274
+ export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
275
+ const appointment = await db.appointment.findUnique({ where: { id: params.id } });
276
+
277
+ return {
278
+ title: `Appointment with ${appointment?.doctor.name}`,
279
+ description: `Scheduled for ${appointment?.date.toDateString()}`,
280
+ };
281
+ }
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Loading and error states
287
+
288
+ ```tsx
289
+ // app/appointments/loading.tsx — shown while page.tsx is loading
290
+ export default function Loading() {
291
+ return <AppointmentListSkeleton />;
292
+ }
293
+
294
+ // app/appointments/error.tsx — caught by nearest error boundary
295
+ 'use client';
296
+ export default function Error({ error, reset }: { error: Error; reset: () => void }) {
297
+ return (
298
+ <div>
299
+ <p>Something went wrong: {error.message}</p>
300
+ <button onClick={reset}>Try again</button>
301
+ </div>
302
+ );
303
+ }
304
+ ```
305
+
306
+ ---
307
+
308
+ ## ALWAYS
309
+ - Server Components by default — add `"use client"` only when needed
310
+ - Server Actions for all mutations from your UI
311
+ - Validate in Server Actions with Zod before touching the database
312
+ - `revalidatePath()` after mutations to refresh stale data
313
+ - `Suspense` boundaries with skeletons for streaming
314
+ - `generateMetadata()` for dynamic page titles
315
+
316
+ ## NEVER
317
+ - `useEffect` to fetch data that could be fetched in a Server Component
318
+ - Route Handlers for mutations from your own frontend (use Server Actions)
319
+ - Client Components that re-fetch data they could receive as props
320
+ - `any` in TypeScript — define types for all Prisma responses and API payloads
321
+ - `process.env.*` in Client Components — use only in Server Components or Actions
@@ -0,0 +1,317 @@
1
+ # Node + Express Patterns
2
+
3
+ > Production-ready Express APIs. Clean layers, typed contracts, centralized error handling.
4
+
5
+ ---
6
+
7
+ ## Project structure
8
+
9
+ ```
10
+ src/
11
+ routes/ ← HTTP routing only
12
+ appointments.routes.ts
13
+ auth.routes.ts
14
+ index.ts ← registers all routers
15
+ controllers/ ← request parsing, response formatting
16
+ appointments.controller.ts
17
+ services/ ← business logic, domain operations
18
+ appointments.service.ts
19
+ email.service.ts
20
+ repositories/ ← data access layer (database queries)
21
+ appointments.repository.ts
22
+ middleware/
23
+ auth.middleware.ts
24
+ validate.middleware.ts
25
+ error.middleware.ts
26
+ rate-limit.middleware.ts
27
+ schemas/ ← Zod schemas for request validation
28
+ appointment.schema.ts
29
+ types/
30
+ index.ts
31
+ lib/
32
+ db.ts ← database client singleton
33
+ logger.ts ← winston/pino logger
34
+ app.ts ← Express app setup (no listen)
35
+ server.ts ← server startup + graceful shutdown
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Layer responsibilities
41
+
42
+ ```ts
43
+ // routes — HTTP wiring only
44
+ // src/routes/appointments.routes.ts
45
+ const router = Router();
46
+ router.get('/', authenticate, AppointmentController.list);
47
+ router.post('/', authenticate, validate(createAppointmentSchema), AppointmentController.create);
48
+ router.delete('/:id', authenticate, AppointmentController.cancel);
49
+ export default router;
50
+
51
+ // controllers — parse request, call service, return response
52
+ // src/controllers/appointments.controller.ts
53
+ export const AppointmentController = {
54
+ create: async (req: Request, res: Response, next: NextFunction) => {
55
+ try {
56
+ const appointment = await AppointmentService.create(req.user!.id, req.body);
57
+ res.status(201).json({ data: appointment });
58
+ } catch (err) {
59
+ next(err);
60
+ }
61
+ },
62
+
63
+ list: async (req: Request, res: Response, next: NextFunction) => {
64
+ try {
65
+ const { page = 1, limit = 20 } = req.query;
66
+ const result = await AppointmentService.listForUser(req.user!.id, { page: +page, limit: +limit });
67
+ res.json(result);
68
+ } catch (err) {
69
+ next(err);
70
+ }
71
+ },
72
+ };
73
+
74
+ // services — business logic
75
+ // src/services/appointments.service.ts
76
+ export const AppointmentService = {
77
+ create: async (userId: string, data: CreateAppointmentDto): Promise<Appointment> => {
78
+ const conflict = await AppointmentRepository.findConflict(data.doctorId, data.date);
79
+ if (conflict) throw new ConflictError('This time slot is already booked.');
80
+
81
+ const appointment = await AppointmentRepository.create({ ...data, userId });
82
+ await EmailService.sendConfirmation(appointment);
83
+ return appointment;
84
+ },
85
+ };
86
+
87
+ // repositories — data access
88
+ // src/repositories/appointments.repository.ts
89
+ export const AppointmentRepository = {
90
+ findConflict: async (doctorId: string, date: Date): Promise<Appointment | null> => {
91
+ return db.appointment.findFirst({
92
+ where: { doctorId, date, status: { not: 'cancelled' } },
93
+ });
94
+ },
95
+
96
+ create: async (data: Prisma.AppointmentCreateInput): Promise<Appointment> => {
97
+ return db.appointment.create({ data, include: { doctor: true } });
98
+ },
99
+ };
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Validation middleware with Zod
105
+
106
+ ```ts
107
+ // src/schemas/appointment.schema.ts
108
+ import { z } from 'zod';
109
+
110
+ export const createAppointmentSchema = z.object({
111
+ body: z.object({
112
+ doctorId: z.string().uuid('Invalid doctor ID'),
113
+ date: z.string().datetime('Invalid date format'),
114
+ notes: z.string().max(500).optional(),
115
+ }),
116
+ });
117
+
118
+ export type CreateAppointmentDto = z.infer<typeof createAppointmentSchema>['body'];
119
+
120
+ // src/middleware/validate.middleware.ts
121
+ import { AnyZodObject, ZodError } from 'zod';
122
+
123
+ export const validate = (schema: AnyZodObject) =>
124
+ async (req: Request, res: Response, next: NextFunction) => {
125
+ try {
126
+ await schema.parseAsync({
127
+ body: req.body,
128
+ query: req.query,
129
+ params: req.params,
130
+ });
131
+ next();
132
+ } catch (err) {
133
+ if (err instanceof ZodError) {
134
+ return res.status(422).json({
135
+ error: 'Validation failed',
136
+ details: err.flatten().fieldErrors,
137
+ });
138
+ }
139
+ next(err);
140
+ }
141
+ };
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Authentication middleware
147
+
148
+ ```ts
149
+ // src/middleware/auth.middleware.ts
150
+ import jwt from 'jsonwebtoken';
151
+
152
+ export const authenticate = (req: Request, res: Response, next: NextFunction) => {
153
+ const token = req.headers.authorization?.split(' ')[1];
154
+ if (!token) return res.status(401).json({ error: 'Authentication required.' });
155
+
156
+ try {
157
+ const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
158
+ req.user = { id: payload.sub!, role: payload.role };
159
+ next();
160
+ } catch {
161
+ res.status(401).json({ error: 'Invalid or expired token.' });
162
+ }
163
+ };
164
+
165
+ export const requireRole = (...roles: string[]) =>
166
+ (req: Request, res: Response, next: NextFunction) => {
167
+ if (!roles.includes(req.user!.role)) {
168
+ return res.status(403).json({ error: 'Insufficient permissions.' });
169
+ }
170
+ next();
171
+ };
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Centralized error handling
177
+
178
+ ```ts
179
+ // src/lib/errors.ts — domain error classes
180
+ export class AppError extends Error {
181
+ constructor(public message: string, public statusCode: number) {
182
+ super(message);
183
+ this.name = this.constructor.name;
184
+ }
185
+ }
186
+ export class NotFoundError extends AppError {
187
+ constructor(msg = 'Resource not found') { super(msg, 404); }
188
+ }
189
+ export class ConflictError extends AppError {
190
+ constructor(msg = 'Resource conflict') { super(msg, 409); }
191
+ }
192
+ export class ForbiddenError extends AppError {
193
+ constructor(msg = 'Forbidden') { super(msg, 403); }
194
+ }
195
+
196
+ // src/middleware/error.middleware.ts — MUST be last middleware registered
197
+ export const errorHandler = (
198
+ err: Error,
199
+ req: Request,
200
+ res: Response,
201
+ next: NextFunction
202
+ ) => {
203
+ if (err instanceof AppError) {
204
+ return res.status(err.statusCode).json({ error: err.message });
205
+ }
206
+ // Prisma unique constraint violation
207
+ if (err.constructor.name === 'PrismaClientKnownRequestError' && (err as any).code === 'P2002') {
208
+ return res.status(409).json({ error: 'A record with this value already exists.' });
209
+ }
210
+
211
+ logger.error({ err, url: req.url, method: req.method });
212
+ res.status(500).json({ error: 'Internal server error.' });
213
+ };
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Rate limiting
219
+
220
+ ```ts
221
+ import rateLimit from 'express-rate-limit';
222
+
223
+ // General API rate limit
224
+ export const apiLimiter = rateLimit({
225
+ windowMs: 15 * 60 * 1000, // 15 minutes
226
+ max: 100,
227
+ standardHeaders: true,
228
+ legacyHeaders: false,
229
+ message: { error: 'Too many requests. Please try again later.' },
230
+ });
231
+
232
+ // Strict limit for auth endpoints
233
+ export const authLimiter = rateLimit({
234
+ windowMs: 15 * 60 * 1000,
235
+ max: 5,
236
+ message: { error: 'Too many login attempts. Please try again in 15 minutes.' },
237
+ });
238
+ ```
239
+
240
+ ---
241
+
242
+ ## App setup
243
+
244
+ ```ts
245
+ // src/app.ts
246
+ import express from 'express';
247
+ import cors from 'cors';
248
+ import helmet from 'helmet';
249
+ import compression from 'compression';
250
+
251
+ const app = express();
252
+
253
+ // Security
254
+ app.use(helmet());
255
+ app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
256
+
257
+ // Parsing
258
+ app.use(express.json({ limit: '10mb' }));
259
+ app.use(compression());
260
+
261
+ // Rate limiting
262
+ app.use('/api/', apiLimiter);
263
+ app.use('/api/auth/', authLimiter);
264
+
265
+ // Routes
266
+ app.use('/api/appointments', appointmentRoutes);
267
+ app.use('/api/auth', authRoutes);
268
+
269
+ // Health check
270
+ app.get('/health', (_, res) => res.json({ status: 'ok', uptime: process.uptime() }));
271
+
272
+ // Error handler — always last
273
+ app.use(errorHandler);
274
+
275
+ export { app };
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Graceful shutdown
281
+
282
+ ```ts
283
+ // src/server.ts
284
+ const server = app.listen(PORT, () => {
285
+ logger.info(`Server running on port ${PORT}`);
286
+ });
287
+
288
+ const shutdown = async (signal: string) => {
289
+ logger.info(`${signal} received — shutting down gracefully`);
290
+ server.close(async () => {
291
+ await db.$disconnect();
292
+ logger.info('Shutdown complete.');
293
+ process.exit(0);
294
+ });
295
+ setTimeout(() => process.exit(1), 10_000); // force exit after 10s
296
+ };
297
+
298
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
299
+ process.on('SIGINT', () => shutdown('SIGINT'));
300
+ ```
301
+
302
+ ---
303
+
304
+ ## ALWAYS
305
+ - Separate routes, controllers, services, repositories
306
+ - Validate at the boundary with Zod before touching services
307
+ - Use typed domain error classes (`AppError`, `NotFoundError`, etc.)
308
+ - Register the error handler middleware last
309
+ - Use `next(err)` in controllers — never `res.status(500)` inline
310
+ - Graceful shutdown on SIGTERM/SIGINT
311
+
312
+ ## NEVER
313
+ - Business logic in routes or controllers
314
+ - Raw `try/catch` without `next(err)` in async controllers
315
+ - `console.log` in production — use a structured logger (pino/winston)
316
+ - `process.env.*` without validation at startup
317
+ - Skip rate limiting on auth endpoints