@skill-graph/cli 0.5.6
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/CHANGELOG.md +247 -0
- package/LICENSE +200 -0
- package/NOTICE +62 -0
- package/README.md +398 -0
- package/SKILL_GRAPH.md +443 -0
- package/bin/skill-graph.js +374 -0
- package/docs/ADOPTION.md +117 -0
- package/docs/CONFORMANCE.md +66 -0
- package/docs/PRIMER.md +384 -0
- package/docs/QUICKSTART-30MIN.md +333 -0
- package/docs/ROUTING-METRICS.md +120 -0
- package/docs/SKILL-MD-FORMAT-COMPATIBILITY.md +127 -0
- package/docs/SKILL_AUDIT_CHECKLIST.md +199 -0
- package/docs/SKILL_AUDIT_LOOP.md +195 -0
- package/docs/SKILL_METADATA_PROTOCOL.md +609 -0
- package/docs/_archived/marketplace-publication-priority-2026-05-18.md +239 -0
- package/docs/adr/0001-predicate-set.md +69 -0
- package/docs/adr/0002-json-ld-context.md +82 -0
- package/docs/adr/0003-ontoclean-rigidity-tags.md +65 -0
- package/docs/adr/0004-persistent-identifiers.md +74 -0
- package/docs/adr/0005-freshness-consolidation.md +70 -0
- package/docs/adr/0006-revise-predicate-rename.md +105 -0
- package/docs/adr/0007-audit-loop-cadence.md +99 -0
- package/docs/adr/0008-skill-surface-split-and-curation-policy.md +93 -0
- package/docs/category-consumers.md +168 -0
- package/docs/concept-map.md +194 -0
- package/docs/diagrams/drift-states.mmd +21 -0
- package/docs/diagrams/manifest-pipeline.mmd +25 -0
- package/docs/diagrams/routing-harness.mmd +41 -0
- package/docs/diagrams/starter-graph.mmd +53 -0
- package/docs/field-decision-guide.md +315 -0
- package/docs/field-rationale.md +211 -0
- package/docs/field-reference.generated.md +624 -0
- package/docs/field-reference.md +1426 -0
- package/docs/glossary.md +190 -0
- package/docs/head-noun-glossary.md +63 -0
- package/docs/images/audit-phases.png +0 -0
- package/docs/images/drift-states.png +0 -0
- package/docs/images/graded-mode.png +0 -0
- package/docs/images/manifest-pipeline.png +0 -0
- package/docs/images/routing-harness.png +0 -0
- package/docs/images/skill-anatomy.png +0 -0
- package/docs/images/starter-graph.png +0 -0
- package/docs/images/system-model.png +0 -0
- package/docs/integrations/github-actions.md +155 -0
- package/docs/manifest-field-mapping.md +443 -0
- package/docs/marketplace-publication-queue.generated.md +240 -0
- package/docs/marketplace-release-agent-prompt.md +82 -0
- package/docs/marketplace-skill-candidate-list.md +272 -0
- package/docs/marketplace-syndication.md +222 -0
- package/docs/migration-sample-review.md +155 -0
- package/docs/migrations/v4-to-v5.md +168 -0
- package/docs/migrations/v5-to-v6.md +221 -0
- package/docs/name-exceptions.yaml +37 -0
- package/docs/plans/marketplace-p1-public-migration-plan.md +41 -0
- package/docs/plans/multi-root-workspace.md +148 -0
- package/docs/plans/scripts-roadmap.md +107 -0
- package/docs/plans/v4-schema-bump.md +160 -0
- package/docs/plans/wave-2-extraction.md +122 -0
- package/docs/positioning-vs-marketplaces.md +175 -0
- package/docs/proposals/skill-audit-loop-positioning.md +160 -0
- package/docs/quality-doctrine.md +138 -0
- package/docs/recommended-skills.md +150 -0
- package/docs/research/skill-comprehension-eval-research.md +1830 -0
- package/docs/research/skill-retrieval-evidence.md +66 -0
- package/docs/skill-metadata-protocol.md +471 -0
- package/docs/skills-sh-maintainer-cleanup-request.md +80 -0
- package/examples/audits/a11y/findings.md +52 -0
- package/examples/audits/a11y/scorecard.md +21 -0
- package/examples/audits/a11y/verdict.md +44 -0
- package/examples/audits/debugging/findings.md +59 -0
- package/examples/audits/debugging/scorecard.md +22 -0
- package/examples/audits/debugging/verdict.md +33 -0
- package/examples/audits/documentation/findings.md +59 -0
- package/examples/audits/documentation/scorecard.md +22 -0
- package/examples/audits/documentation/verdict.md +33 -0
- package/examples/evals/a11y.json +140 -0
- package/examples/evals/api-design.json +52 -0
- package/examples/evals/code-review.json +52 -0
- package/examples/evals/data-modeling.json +52 -0
- package/examples/evals/database-migration.json +52 -0
- package/examples/evals/debugging.json +118 -0
- package/examples/evals/dependency-architecture.json +52 -0
- package/examples/evals/design-system-architecture.json +52 -0
- package/examples/evals/error-tracking.json +52 -0
- package/examples/evals/event-contract-design.json +52 -0
- package/examples/evals/form-ux-architecture.json +52 -0
- package/examples/evals/framework-fit-analysis.json +52 -0
- package/examples/evals/graph-audit.json +139 -0
- package/examples/evals/information-architecture.json +52 -0
- package/examples/evals/interaction-feedback.json +52 -0
- package/examples/evals/interaction-patterns.json +52 -0
- package/examples/evals/layout-composition.json +52 -0
- package/examples/evals/lint-overlay.json +117 -0
- package/examples/evals/microcopy.json +52 -0
- package/examples/evals/observability-modeling.json +52 -0
- package/examples/evals/pattern-recognition.json +96 -0
- package/examples/evals/performance-engineering.json +52 -0
- package/examples/evals/refactor.json +128 -0
- package/examples/evals/semiotics.json +52 -0
- package/examples/evals/skill-infrastructure.json +96 -0
- package/examples/evals/skill-router.json +140 -0
- package/examples/evals/skill-router.routing.json +113 -0
- package/examples/evals/system-interface-contracts.json +52 -0
- package/examples/evals/task-analysis.json +52 -0
- package/examples/evals/testing-strategy.json +118 -0
- package/examples/evals/type-safety.json +249 -0
- package/examples/evals/visual-design-foundations.json +52 -0
- package/examples/evals/webhook-integration.json +52 -0
- package/examples/exports/a11y.skill-md.md +80 -0
- package/examples/exports/debugging.skill-md.md +80 -0
- package/examples/exports/refactor.skill-md.md +78 -0
- package/examples/exports/testing-strategy.skill-md.md +81 -0
- package/examples/projects/markdown-static-site/README.md +115 -0
- package/examples/projects/markdown-static-site/skills/content-source-router/SKILL.md +131 -0
- package/examples/projects/markdown-static-site/skills/image-optimization-pipeline-config/SKILL.md +132 -0
- package/examples/projects/markdown-static-site/skills/link-rot-detection/SKILL.md +103 -0
- package/examples/projects/markdown-static-site/skills/markdown-post-frontmatter-validation/SKILL.md +133 -0
- package/examples/projects/markdown-static-site/skills/migrate-posts-to-v2-frontmatter/SKILL.md +140 -0
- package/examples/projects/saas-stripe-postgres/README.md +208 -0
- package/examples/projects/saas-stripe-postgres/db/migrations/0004_canonicalize_orders.sql +37 -0
- package/examples/projects/saas-stripe-postgres/db/schema.sql +112 -0
- package/examples/projects/saas-stripe-postgres/skills/migrate-orders-to-canonical-schema/SKILL.md +149 -0
- package/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation/SKILL.md +154 -0
- package/examples/projects/saas-stripe-postgres/skills/payment-provider-router/SKILL.md +153 -0
- package/examples/projects/saas-stripe-postgres/skills/postgres-rls-pattern/SKILL.md +163 -0
- package/examples/projects/saas-stripe-postgres/skills/stripe-webhook-signature-verification/SKILL.md +137 -0
- package/examples/protocol/skill-metadata-template.md +301 -0
- package/examples/protocol/skills.manifest.sample.json +13245 -0
- package/examples/skill-metadata-template.md +317 -0
- package/examples/skills.manifest.sample.json +13519 -0
- package/examples/tests/v3-1-skos-fixture/SKILL.md +93 -0
- package/marketplace/README.md +17 -0
- package/marketplace/skills/a11y/SKILL.md +66 -0
- package/marketplace/skills/acid-fundamentals/SKILL.md +106 -0
- package/marketplace/skills/agent-engineering/SKILL.md +386 -0
- package/marketplace/skills/agent-eval-design/SKILL.md +55 -0
- package/marketplace/skills/ai-native-development/SKILL.md +294 -0
- package/marketplace/skills/api-design/SKILL.md +60 -0
- package/marketplace/skills/architecture-decision-records/SKILL.md +55 -0
- package/marketplace/skills/background-jobs/SKILL.md +265 -0
- package/marketplace/skills/bounded-context-mapping/SKILL.md +55 -0
- package/marketplace/skills/cap-theorem-tradeoffs/SKILL.md +127 -0
- package/marketplace/skills/client-server-boundary/SKILL.md +187 -0
- package/marketplace/skills/code-review/SKILL.md +120 -0
- package/marketplace/skills/color-system-design/SKILL.md +43 -0
- package/marketplace/skills/component-architecture/SKILL.md +126 -0
- package/marketplace/skills/compression/SKILL.md +112 -0
- package/marketplace/skills/conceptual-modeling/SKILL.md +181 -0
- package/marketplace/skills/connection-pooling/SKILL.md +105 -0
- package/marketplace/skills/constraint-awareness/SKILL.md +287 -0
- package/marketplace/skills/content-monitor/SKILL.md +209 -0
- package/marketplace/skills/context-engineering/SKILL.md +320 -0
- package/marketplace/skills/context-graph/SKILL.md +174 -0
- package/marketplace/skills/context-management/SKILL.md +174 -0
- package/marketplace/skills/context-window/SKILL.md +239 -0
- package/marketplace/skills/contract-testing/SKILL.md +120 -0
- package/marketplace/skills/cron-scheduling/SKILL.md +223 -0
- package/marketplace/skills/dark-mode-implementation/SKILL.md +47 -0
- package/marketplace/skills/data-modeling/SKILL.md +59 -0
- package/marketplace/skills/data-modeling-fundamentals/SKILL.md +117 -0
- package/marketplace/skills/database-migration/SKILL.md +429 -0
- package/marketplace/skills/debugging/SKILL.md +67 -0
- package/marketplace/skills/dependency-architecture/SKILL.md +58 -0
- package/marketplace/skills/design-module-composition/SKILL.md +43 -0
- package/marketplace/skills/design-system-architecture/SKILL.md +61 -0
- package/marketplace/skills/design-thinking/SKILL.md +44 -0
- package/marketplace/skills/diagnosis/SKILL.md +296 -0
- package/marketplace/skills/diff-analysis/SKILL.md +188 -0
- package/marketplace/skills/e2e-test-design/SKILL.md +113 -0
- package/marketplace/skills/entity-relationship-modeling/SKILL.md +218 -0
- package/marketplace/skills/epistemic-grounding/SKILL.md +112 -0
- package/marketplace/skills/error-boundary/SKILL.md +235 -0
- package/marketplace/skills/error-tracking/SKILL.md +261 -0
- package/marketplace/skills/eval-driven-development/SKILL.md +147 -0
- package/marketplace/skills/evaluation/SKILL.md +113 -0
- package/marketplace/skills/event-contract-design/SKILL.md +60 -0
- package/marketplace/skills/event-storming/SKILL.md +56 -0
- package/marketplace/skills/form-ux-architecture/SKILL.md +60 -0
- package/marketplace/skills/framework-fit-analysis/SKILL.md +59 -0
- package/marketplace/skills/frontend-architecture/SKILL.md +43 -0
- package/marketplace/skills/generative-ui/SKILL.md +118 -0
- package/marketplace/skills/graph-audit/SKILL.md +81 -0
- package/marketplace/skills/guardrails/SKILL.md +118 -0
- package/marketplace/skills/hooks-patterns/SKILL.md +185 -0
- package/marketplace/skills/http-semantics/SKILL.md +136 -0
- package/marketplace/skills/ideation/SKILL.md +41 -0
- package/marketplace/skills/indexing-strategy/SKILL.md +108 -0
- package/marketplace/skills/information-architecture/SKILL.md +59 -0
- package/marketplace/skills/integration-test-design/SKILL.md +111 -0
- package/marketplace/skills/intent-recognition/SKILL.md +136 -0
- package/marketplace/skills/interaction-feedback/SKILL.md +59 -0
- package/marketplace/skills/interaction-patterns/SKILL.md +59 -0
- package/marketplace/skills/journey-mapping/SKILL.md +41 -0
- package/marketplace/skills/keywords/SKILL.md +213 -0
- package/marketplace/skills/knowledge-modeling/SKILL.md +232 -0
- package/marketplace/skills/layout-composition/SKILL.md +59 -0
- package/marketplace/skills/linguistics/SKILL.md +429 -0
- package/marketplace/skills/lint-overlay/SKILL.md +76 -0
- package/marketplace/skills/mental-models/SKILL.md +126 -0
- package/marketplace/skills/merge-queue/SKILL.md +94 -0
- package/marketplace/skills/methodology/SKILL.md +317 -0
- package/marketplace/skills/microcopy/SKILL.md +232 -0
- package/marketplace/skills/middleware-patterns/SKILL.md +363 -0
- package/marketplace/skills/mobile-responsive-ux/SKILL.md +287 -0
- package/marketplace/skills/mutation-testing/SKILL.md +112 -0
- package/marketplace/skills/naming-conventions/SKILL.md +112 -0
- package/marketplace/skills/observability-modeling/SKILL.md +59 -0
- package/marketplace/skills/ontology-modeling/SKILL.md +67 -0
- package/marketplace/skills/owasp-security/SKILL.md +153 -0
- package/marketplace/skills/pattern-recognition/SKILL.md +472 -0
- package/marketplace/skills/performance-budgets/SKILL.md +185 -0
- package/marketplace/skills/performance-engineering/SKILL.md +58 -0
- package/marketplace/skills/performance-testing/SKILL.md +125 -0
- package/marketplace/skills/printify/SKILL.md +42 -0
- package/marketplace/skills/prioritization/SKILL.md +118 -0
- package/marketplace/skills/problem-framing/SKILL.md +41 -0
- package/marketplace/skills/problem-locating-solving/SKILL.md +203 -0
- package/marketplace/skills/project-knowledge-extraction/SKILL.md +54 -0
- package/marketplace/skills/prompt-craft/SKILL.md +134 -0
- package/marketplace/skills/prompt-injection-defense/SKILL.md +132 -0
- package/marketplace/skills/property-based-testing/SKILL.md +100 -0
- package/marketplace/skills/prototyping/SKILL.md +43 -0
- package/marketplace/skills/query-optimization/SKILL.md +144 -0
- package/marketplace/skills/real-time-updates/SKILL.md +324 -0
- package/marketplace/skills/ref-patterns/SKILL.md +284 -0
- package/marketplace/skills/refactor/SKILL.md +65 -0
- package/marketplace/skills/rendering-models/SKILL.md +142 -0
- package/marketplace/skills/replication-patterns/SKILL.md +110 -0
- package/marketplace/skills/research-synthesis/SKILL.md +41 -0
- package/marketplace/skills/route-handler-design/SKILL.md +347 -0
- package/marketplace/skills/schema-evolution/SKILL.md +140 -0
- package/marketplace/skills/security-fundamentals/SKILL.md +139 -0
- package/marketplace/skills/semantic-center/SKILL.md +194 -0
- package/marketplace/skills/semantic-relations/SKILL.md +250 -0
- package/marketplace/skills/semantics/SKILL.md +366 -0
- package/marketplace/skills/semiotics/SKILL.md +230 -0
- package/marketplace/skills/seo-strategy/SKILL.md +260 -0
- package/marketplace/skills/server-actions-design/SKILL.md +243 -0
- package/marketplace/skills/server-components-design/SKILL.md +190 -0
- package/marketplace/skills/sharding-strategy/SKILL.md +123 -0
- package/marketplace/skills/shopify/SKILL.md +42 -0
- package/marketplace/skills/skill-infrastructure/SKILL.md +320 -0
- package/marketplace/skills/skill-router/SKILL.md +71 -0
- package/marketplace/skills/skill-scaffold/SKILL.md +105 -0
- package/marketplace/skills/snapshot-testing/SKILL.md +120 -0
- package/marketplace/skills/spec-driven-development/SKILL.md +148 -0
- package/marketplace/skills/state-machine-modeling/SKILL.md +56 -0
- package/marketplace/skills/state-management/SKILL.md +134 -0
- package/marketplace/skills/streaming-architecture/SKILL.md +194 -0
- package/marketplace/skills/summarization/SKILL.md +156 -0
- package/marketplace/skills/suspense-patterns/SKILL.md +265 -0
- package/marketplace/skills/system-interface-contracts/SKILL.md +59 -0
- package/marketplace/skills/task-analysis/SKILL.md +201 -0
- package/marketplace/skills/taxonomy-design/SKILL.md +66 -0
- package/marketplace/skills/test-coverage-strategy/SKILL.md +108 -0
- package/marketplace/skills/test-doubles-design/SKILL.md +98 -0
- package/marketplace/skills/test-driven-development/SKILL.md +96 -0
- package/marketplace/skills/testing-strategy/SKILL.md +67 -0
- package/marketplace/skills/theme-system-design/SKILL.md +43 -0
- package/marketplace/skills/tool-call-flow/SKILL.md +229 -0
- package/marketplace/skills/tool-call-strategy/SKILL.md +292 -0
- package/marketplace/skills/transaction-isolation/SKILL.md +98 -0
- package/marketplace/skills/type-safety/SKILL.md +177 -0
- package/marketplace/skills/typography-system/SKILL.md +43 -0
- package/marketplace/skills/usability-testing/SKILL.md +43 -0
- package/marketplace/skills/user-research/SKILL.md +43 -0
- package/marketplace/skills/vercel-composition-patterns/SKILL.md +157 -0
- package/marketplace/skills/version-control/SKILL.md +233 -0
- package/marketplace/skills/visual-design-foundations/SKILL.md +59 -0
- package/marketplace/skills/visual-hierarchy/SKILL.md +43 -0
- package/marketplace/skills/webhook-integration/SKILL.md +331 -0
- package/marketplace/skills/writing-humanizer/SKILL.md +380 -0
- package/package.json +67 -0
- package/schemas/manifest.schema.json +811 -0
- package/schemas/manifest.v2.schema.json +164 -0
- package/schemas/manifest.v3.schema.json +758 -0
- package/schemas/manifest.v4.schema.json +755 -0
- package/schemas/manifest.v5.schema.json +755 -0
- package/schemas/manifest.v6.schema.json +811 -0
- package/schemas/skill.context.jsonld +279 -0
- package/schemas/skill.schema.json +919 -0
- package/schemas/skill.v2.schema.json +201 -0
- package/schemas/skill.v3.schema.json +827 -0
- package/schemas/skill.v4.schema.json +822 -0
- package/schemas/skill.v5.schema.json +830 -0
- package/schemas/skill.v6.schema.json +946 -0
- package/schemas/vocabulary/keywords.json +180 -0
- package/schemas/vocabulary/workspace_tags.json +23 -0
- package/scripts/__tests__/migrate-skill-v2-to-v3.test.js +161 -0
- package/scripts/__tests__/migrate-skill-v3-to-v4.test.js +158 -0
- package/scripts/__tests__/test-export-parser-drift.js +149 -0
- package/scripts/__tests__/test-marketplace-export.js +114 -0
- package/scripts/__tests__/test-router-paths.js +82 -0
- package/scripts/__tests__/test-stability-promotion.js +244 -0
- package/scripts/__tests__/test-v3-1-alias-contract.js +109 -0
- package/scripts/__tests__/test-v3-1-skos-runtime.js +116 -0
- package/scripts/backfill-schema-version.js +198 -0
- package/scripts/build-field-reference.js +160 -0
- package/scripts/build-retrieval-baseline.js +511 -0
- package/scripts/check-markdown-links.js +211 -0
- package/scripts/check-protocol-consistency.js +979 -0
- package/scripts/export-marketplace-skills.js +610 -0
- package/scripts/export-skill.js +374 -0
- package/scripts/generate-manifest.js +787 -0
- package/scripts/lib/alias-contract.js +83 -0
- package/scripts/lib/audit-prompt-builder.js +771 -0
- package/scripts/lib/mock-grader.js +134 -0
- package/scripts/lib/parse-frontmatter.js +429 -0
- package/scripts/lib/roots.js +119 -0
- package/scripts/lint/check-archetype-sections.js +185 -0
- package/scripts/lint/check-category-enum.js +83 -0
- package/scripts/lint/check-routing-eval.js +146 -0
- package/scripts/lint/check-routing-quality.js +211 -0
- package/scripts/lint/check-stability-promotion.js +220 -0
- package/scripts/lint/format-code-frame.js +206 -0
- package/scripts/marketplace-install.js +125 -0
- package/scripts/migrate-category-to-enum.js +169 -0
- package/scripts/migrate-skill-v2-to-v3.js +424 -0
- package/scripts/migrate-skill-v3-to-v4.js +200 -0
- package/scripts/migrate-skill-v5-to-v6.js +304 -0
- package/scripts/restructure-by-category.js +85 -0
- package/scripts/seed-publication-classification.js +282 -0
- package/scripts/skill-audit.js +893 -0
- package/scripts/skill-graph-drift.js +483 -0
- package/scripts/skill-graph-route.js +766 -0
- package/scripts/skill-graph-routing-eval.js +393 -0
- package/scripts/skill-lint.js +1317 -0
- package/scripts/skill-overlap.js +213 -0
- package/scripts/verify-skill-md-export.js +201 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: route-handler-design
|
|
3
|
+
description: "Use when designing or reviewing Next.js Route Handlers (`route.ts`): when a Route Handler is right vs Server Actions or Server Components, the HTTP-method-as-export contract (GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS), Web-standard Request/Response interface, body-parsing primitives, GET caching and opt-out, dynamic segments, manual CORS, runtime selection (Edge vs Node), streaming via ReadableStream, status/header discipline, error responses, webhook-style handlers, and the rule that Route Handlers are the right surface when the caller is not your own Next.js UI. Do NOT use for internal UI mutations (use server-actions-design), REST contract design (use api-design), abstract HTTP semantics (use http-semantics), cross-route preprocessing (use middleware-patterns), or webhook reliability (use webhook-integration)."
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Read Grep
|
|
6
|
+
metadata:
|
|
7
|
+
metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"engineering\",\"domain\":\"engineering/frontend\",\"scope\":\"reference\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-05-17\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-05-17\\\\\\\"}\",\"eval_artifacts\":\"planned\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"comprehension_state\":\"present\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"Next.js Route Handler\\\\\\\",\\\\\\\"route.ts file\\\\\\\",\\\\\\\"API endpoint via route.ts\\\\\\\",\\\\\\\"GET POST PUT DELETE export\\\\\\\",\\\\\\\"Web Request Response API\\\\\\\",\\\\\\\"NextRequest NextResponse\\\\\\\",\\\\\\\"request.json formData parsing\\\\\\\",\\\\\\\"dynamic force-dynamic segment config\\\\\\\",\\\\\\\"revalidate Route Handler\\\\\\\",\\\\\\\"Edge runtime vs Node runtime\\\\\\\",\\\\\\\"ReadableStream response Next.js\\\\\\\",\\\\\\\"webhook endpoint in route.ts\\\\\\\",\\\\\\\"CORS Route Handler\\\\\\\",\\\\\\\"Response.json HTTP status\\\\\\\"]\",\"triggers\":\"[\\\\\\\"how do I expose an API endpoint in Next.js App Router\\\\\\\",\\\\\\\"when should I use a Route Handler instead of a Server Action\\\\\\\",\\\\\\\"how do I receive a webhook in Next.js\\\\\\\",\\\\\\\"how do I return a streaming response from an API route\\\\\\\",\\\\\\\"how do I parse a JSON body in route.ts\\\\\\\",\\\\\\\"why is my Route Handler GET cached\\\\\\\",\\\\\\\"how do I set CORS headers on a Next.js API route\\\\\\\",\\\\\\\"Edge runtime vs Node runtime for an API route\\\\\\\"]\",\"examples\":\"[\\\\\\\"design the route.ts that receives a Stripe webhook — verify signature before parsing the body, return 200 immediately, queue the heavy work\\\\\\\",\\\\\\\"decide whether a 'export user data' endpoint should be a Route Handler or a Server Action\\\\\\\",\\\\\\\"add CORS to a Route Handler that mobile clients call from a different origin\\\\\\\",\\\\\\\"opt a GET Route Handler out of the default static cache because it depends on the request user\\\\\\\",\\\\\\\"return a streaming binary response (PDF generation, large CSV export) from a Route Handler\\\\\\\"]\",\"anti_examples\":\"[\\\\\\\"design an internal create-comment form mutation triggered only from this app's UI (use server-actions-design)\\\\\\\",\\\\\\\"define the REST contract and resource model for a v2 public API (use api-design)\\\\\\\",\\\\\\\"explain what an HTTP 422 means vs 400 (use http-semantics)\\\\\\\",\\\\\\\"add an auth check that runs before every protected route (use middleware-patterns)\\\\\\\",\\\\\\\"design the idempotency-key + retry + dead-letter-queue strategy for a webhook (use webhook-integration)\\\\\\\",\\\\\\\"design the resource model, versioning, pagination, or error envelope of an HTTP API (use api-design)\\\\\\\",\\\\\\\"decide what an HTTP method, status code, or header should mean per RFC 9110 (use http-semantics)\\\\\\\",\\\\\\\"design signature verification, idempotency keys, retry semantics, or dead-letter queues for vendor webhooks (use webhook-integration)\\\\\\\",\\\\\\\"design a cross-cutting streaming model with Web Streams, SSE, or backpressure (use streaming-architecture)\\\\\\\"]\",\"relations\":\"{\\\\\\\"related\\\\\\\":[\\\\\\\"server-actions-design\\\\\\\",\\\\\\\"api-design\\\\\\\",\\\\\\\"http-semantics\\\\\\\",\\\\\\\"middleware-patterns\\\\\\\",\\\\\\\"webhook-integration\\\\\\\",\\\\\\\"streaming-architecture\\\\\\\",\\\\\\\"client-server-boundary\\\\\\\"],\\\\\\\"boundary\\\\\\\":[{\\\\\\\"skill\\\\\\\":\\\\\\\"server-actions-design\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"server-actions-design owns the internal mutation surface — a function the bundler turns into a network call from your own UI. route-handler-design owns the public-endpoint surface — a Web-standard Request/Response handler invoked by callers (mobile, third-party, webhooks, your own client-side fetches) that are not the Next.js bundler's typed call sites. Use Server Actions when the only caller is this app's UI; use Route Handlers when the caller is anything else or when you need fine-grained HTTP control.\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"middleware-patterns\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"middleware runs once before route resolution and applies to many routes via a `matcher`; route-handler-design runs for one route and one method. Use middleware for cross-cutting concerns (auth gate, header injection, locale rewrite); use Route Handlers for the per-route logic that runs after middleware passes.\\\\\\\"}],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"code-review\\\\\\\",\\\\\\\"api-design\\\\\\\"]}\",\"mental_model\":\"|\",\"purpose\":\"|\",\"boundary\":\"|\",\"analogy\":\"A Route Handler is to a Next.js app what a service window at a government office is to its workflow — different windows handle different services (`GET /posts`, `POST /comments`); each window has a posted sign saying which forms it accepts and what stamps it returns; you do not walk into the back office (Server Action) unless you work there. The window is the contract: filesystem path = window number, export name = service offered, function body = the clerk's actual work.\",\"misconception\":\"|\",\"concept\":\"{\\\\\\\"definition\\\\\\\":\\\\\\\"A Next.js Route Handler is a file named route.ts (or route.js) under the app/ directory that exports one async function per HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS). Each export receives a standard Web Request object and returns a standard Web Response (or NextResponse, which extends it). The file's filesystem path defines the URL; the export name defines the method; the function body defines the handler. There is no Node-style req/res, no middleware chain, no per-method routing config — the file shape IS the contract.\\\\\\\",\\\\\\\"mental_model\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"purpose\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"boundary\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"taxonomy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"analogy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"misconception\\\\\\\":\\\\\\\"|\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/route-handler-design/SKILL.md\"}"
|
|
8
|
+
skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
|
|
9
|
+
skill_graph_protocol: Skill Metadata Protocol v4
|
|
10
|
+
skill_graph_project: Skill Graph
|
|
11
|
+
skill_graph_canonical_skill: skills/route-handler-design/SKILL.md
|
|
12
|
+
skill_graph_export_description: shortened for Agent Skills 1024-character description limit; canonical source keeps the full routing contract
|
|
13
|
+
skill_graph_canonical_description_length: "1299"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Route Handler Design
|
|
17
|
+
|
|
18
|
+
## Coverage
|
|
19
|
+
|
|
20
|
+
The discipline of designing Next.js App Router `route.ts` handlers: the file-and-export convention (one async function per HTTP method, one URL per filesystem path), the Web-standard Request/Response interface that replaces the Node `req`/`res` pair, the body-parsing primitives (`request.json` / `formData` / `text` / `blob` / `arrayBuffer`), the default GET caching behavior and the segment-config opt-outs (`dynamic`, `revalidate`, `runtime`), dynamic segments and search-param access, manual CORS, the Edge vs Node runtime choice, streaming responses via `ReadableStream`, status-code and header discipline, error response shaping, the canonical webhook pattern (verify signature against the raw body before parsing), and the central design rule that determines when a Route Handler is the right surface at all: **the caller is not your own typed UI**.
|
|
21
|
+
|
|
22
|
+
## Philosophy
|
|
23
|
+
|
|
24
|
+
The App Router collapsed three things that used to be distinct in the Pages Router:
|
|
25
|
+
|
|
26
|
+
- **Pages**: rendered routes — moved to Server Components and `page.tsx`.
|
|
27
|
+
- **API routes**: HTTP endpoints under `/api/*` — moved to Route Handlers in `route.ts`.
|
|
28
|
+
- **Custom server handlers**: middleware, edge functions — partly absorbed by `middleware.ts`, partly by per-route runtime selection.
|
|
29
|
+
|
|
30
|
+
The Pages Router `/api/foo.ts` exported a default function taking Node's `req` and `res`. The Route Handler exports per-method async functions taking a Web `Request` and returning a Web `Response`. The change is not cosmetic — it makes Next.js endpoints portable to any runtime that speaks Web standards (Edge, Cloudflare Workers, Deno, browser Service Workers in principle) and removes a category of "Node-specific" footguns.
|
|
31
|
+
|
|
32
|
+
The deeper shift is that the App Router introduced a *competing* mutation surface in **Server Actions**. Before, every mutation needed an API route. Now, most mutations triggered from the app's own UI should use a Server Action (one declaration, no manual wire format). Route Handlers remain the right surface only when the caller is *not* the Next.js bundler's typed call site — mobile apps, third-party integrations, webhooks, server-to-server calls, server-sent events, binary downloads, and anything else that benefits from explicit HTTP semantics.
|
|
33
|
+
|
|
34
|
+
The Route Handler is the **public HTTP endpoint** surface. Use it when you need an HTTP endpoint. Use a Server Action when you need a mutation triggered from this app's UI. The two surfaces can coexist — many apps publish a Route Handler `/api/v1/posts` for external clients AND use Server Actions for the same mutations from their own forms.
|
|
35
|
+
|
|
36
|
+
## When to Use What
|
|
37
|
+
|
|
38
|
+
| Caller | Right surface | Why |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| This app's `<form>` or button | **Server Action** | One declaration; no wire format; progressive enhancement; revalidation built in |
|
|
41
|
+
| This app's component reading data on render | **Server Component** | No round-trip; co-located with the render that uses the data |
|
|
42
|
+
| Mobile app, third-party integration, server-to-server | **Route Handler** | Explicit HTTP contract; the caller does not run the Next.js bundler |
|
|
43
|
+
| Webhook from Stripe / Shopify / GitHub | **Route Handler** | Need raw-body access for signature verification; need exact status codes; vendor expects standard HTTP |
|
|
44
|
+
| Streaming SSE, binary downloads, large CSV/PDF | **Route Handler** | Returns a `ReadableStream` with the right headers; Server Actions can't model this |
|
|
45
|
+
| Client-side `fetch()` from a Client Component | **Route Handler** if it's a real API, **Server Action** if it's a mutation | Don't fetch your own internal mutation; that's what Server Actions exist to replace |
|
|
46
|
+
|
|
47
|
+
The single most common Route Handler mistake in App Router code is using a Route Handler for an internal mutation that a Server Action would serve better — duplicate type contracts, manual fetch wiring, no progressive enhancement, no built-in revalidation.
|
|
48
|
+
|
|
49
|
+
## The File-and-Export Contract
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// app/api/posts/route.ts
|
|
53
|
+
export async function GET(request: Request) {
|
|
54
|
+
const posts = await db.post.findMany()
|
|
55
|
+
return Response.json(posts)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function POST(request: Request) {
|
|
59
|
+
const body = await request.json()
|
|
60
|
+
const created = await db.post.create({ data: body })
|
|
61
|
+
return Response.json(created, { status: 201 })
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The filesystem path `app/api/posts/route.ts` registers `/api/posts`. The exports `GET` and `POST` register the methods. Unhandled methods auto-return `405 Method Not Allowed`. There is no router-level config object, no separate registration step.
|
|
66
|
+
|
|
67
|
+
A Route Handler file cannot coexist with a `page.tsx` at the same path — `/api/posts/page.tsx` and `/api/posts/route.ts` collide and Next.js rejects the build. Choose one or the other per URL.
|
|
68
|
+
|
|
69
|
+
### Dynamic segments
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// app/api/posts/[id]/route.ts
|
|
73
|
+
export async function GET(
|
|
74
|
+
request: Request,
|
|
75
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
76
|
+
) {
|
|
77
|
+
const { id } = await params // Promise in Next 15+
|
|
78
|
+
const post = await db.post.findUnique({ where: { id } })
|
|
79
|
+
return post ? Response.json(post) : new Response(null, { status: 404 })
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
In Next 15+, `params` is a Promise — `await` it. In earlier versions, it was a plain object. The shape of the typed second argument follows the dynamic-segment names in the path.
|
|
84
|
+
|
|
85
|
+
### Search params
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
export async function GET(request: Request) {
|
|
89
|
+
const { searchParams } = new URL(request.url)
|
|
90
|
+
const limit = Number(searchParams.get('limit') ?? '20')
|
|
91
|
+
// ...
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`URLSearchParams` is the Web-standard read surface. There is no Next-specific helper for query strings beyond constructing a `URL` from `request.url`.
|
|
96
|
+
|
|
97
|
+
## Body Parsing
|
|
98
|
+
|
|
99
|
+
The `Request` object exposes five body-reading methods:
|
|
100
|
+
|
|
101
|
+
| Method | Returns | Use when |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `request.json()` | parsed JSON | Content-Type: application/json |
|
|
104
|
+
| `request.formData()` | FormData | Content-Type: multipart/form-data or application/x-www-form-urlencoded |
|
|
105
|
+
| `request.text()` | string | Plain text, raw payload, or when you need to verify a signature against the raw bytes |
|
|
106
|
+
| `request.blob()` | Blob | File upload, binary payload you'll re-serve |
|
|
107
|
+
| `request.arrayBuffer()` | ArrayBuffer | Low-level binary processing |
|
|
108
|
+
|
|
109
|
+
A body can only be read **once**. If you need both the raw body (for HMAC verification) and the parsed body (for handler logic), call `request.text()` and parse it yourself. The webhook section below demonstrates this.
|
|
110
|
+
|
|
111
|
+
## Default Caching and How to Opt Out
|
|
112
|
+
|
|
113
|
+
In production, a Route Handler whose `GET` export does not read from request-scoped sources (cookies, headers, search params accessed via `request.url`'s changing parts) is **statically cached** by default. Subsequent requests hit the cache.
|
|
114
|
+
|
|
115
|
+
For most APIs this is wrong. Opt out via segment config:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// app/api/posts/route.ts
|
|
119
|
+
export const dynamic = 'force-dynamic' // always run; never cache
|
|
120
|
+
|
|
121
|
+
// or
|
|
122
|
+
export const revalidate = 60 // cache for 60s, then revalidate
|
|
123
|
+
|
|
124
|
+
// or
|
|
125
|
+
export const runtime = 'edge' // run on Edge Runtime (changes capability surface)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`POST`, `PUT`, `PATCH`, `DELETE` handlers are never cached — they're assumed to mutate. Only `GET` and `HEAD` are affected by the default-cache rule.
|
|
129
|
+
|
|
130
|
+
Reading cookies, headers, or `request.headers` automatically opts a GET out of static caching. So does reading `searchParams` from the request URL. The rule is: any handler that depends on per-request data is dynamic by definition; Next infers this from your code. The explicit `dynamic = 'force-dynamic'` is the belt-and-suspenders option when the inference might be wrong or when you want the intent to be visible.
|
|
131
|
+
|
|
132
|
+
## NextRequest and NextResponse
|
|
133
|
+
|
|
134
|
+
Standard Web `Request` and `Response` work fine. Next provides extended versions for convenience:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
138
|
+
|
|
139
|
+
export async function GET(request: NextRequest) {
|
|
140
|
+
const session = request.cookies.get('session')?.value
|
|
141
|
+
const country = request.geo?.country // on Vercel Edge
|
|
142
|
+
return NextResponse.json({ session, country }, { status: 200 })
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`NextRequest` extends `Request` with `.cookies`, `.geo`, `.ip`, `.nextUrl`. `NextResponse` extends `Response` with `.cookies.set/.delete`, `NextResponse.redirect`, `NextResponse.rewrite`, `NextResponse.json` (a convenience that mirrors `Response.json`).
|
|
147
|
+
|
|
148
|
+
Use them when you need cookies or geo. Stick with standard `Request`/`Response` when you don't — it makes the handler more portable.
|
|
149
|
+
|
|
150
|
+
## Streaming Responses
|
|
151
|
+
|
|
152
|
+
A Route Handler can return a `ReadableStream` directly. Useful for SSE, AI streaming, large file generation, and chunked CSV/JSON exports.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
export async function GET() {
|
|
156
|
+
const encoder = new TextEncoder()
|
|
157
|
+
const stream = new ReadableStream({
|
|
158
|
+
async start(controller) {
|
|
159
|
+
for await (const chunk of generateData()) {
|
|
160
|
+
controller.enqueue(encoder.encode(chunk))
|
|
161
|
+
}
|
|
162
|
+
controller.close()
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
return new Response(stream, {
|
|
166
|
+
headers: {
|
|
167
|
+
'Content-Type': 'text/event-stream',
|
|
168
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
169
|
+
'Connection': 'keep-alive',
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The `Edge` runtime is the safe choice for long-lived streams — it has lower memory overhead per connection and avoids Node's connection-keepalive quirks. The Node runtime can also stream, but be aware of buffering behavior in your deployment platform (Vercel buffers Node responses by default unless you opt out).
|
|
176
|
+
|
|
177
|
+
## The Webhook Pattern
|
|
178
|
+
|
|
179
|
+
Webhooks need three things almost always: **raw body access, signature verification before parse, fast acknowledgment**.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// app/api/webhooks/stripe/route.ts
|
|
183
|
+
import { headers } from 'next/headers'
|
|
184
|
+
import { stripe } from '@/lib/stripe'
|
|
185
|
+
|
|
186
|
+
export const runtime = 'nodejs' // some HMAC libs need Node crypto
|
|
187
|
+
export const dynamic = 'force-dynamic' // never cache a webhook
|
|
188
|
+
|
|
189
|
+
export async function POST(request: Request) {
|
|
190
|
+
const signature = (await headers()).get('stripe-signature')
|
|
191
|
+
if (!signature) return new Response('Missing signature', { status: 400 })
|
|
192
|
+
|
|
193
|
+
const rawBody = await request.text() // raw bytes for HMAC
|
|
194
|
+
|
|
195
|
+
let event
|
|
196
|
+
try {
|
|
197
|
+
event = stripe.webhooks.constructEvent(rawBody, signature, process.env.STRIPE_WEBHOOK_SECRET!)
|
|
198
|
+
} catch (err) {
|
|
199
|
+
return new Response(`Webhook signature verification failed: ${err.message}`, { status: 400 })
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ACK fast; do heavy work async
|
|
203
|
+
queueEventForProcessing(event)
|
|
204
|
+
return new Response(null, { status: 200 })
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Key decisions encoded above:
|
|
209
|
+
|
|
210
|
+
- **`request.text()`**, not `request.json()` — you need the raw bytes for HMAC; parsing first would mutate whitespace and break verification.
|
|
211
|
+
- **Verify before any business logic** — reject unauthenticated calls with a fast 400 before touching the database.
|
|
212
|
+
- **ACK fast** — return 200 within seconds; queue the actual processing. Vendors interpret slow ACKs as failures and retry, which produces duplicate processing if you're not careful.
|
|
213
|
+
- **`runtime = 'nodejs'`** — some vendor SDKs need Node-only crypto APIs (Stripe's `constructEvent` does in some versions). Edge runtime supports Web Crypto natively; check the vendor SDK.
|
|
214
|
+
|
|
215
|
+
The reliability concerns beyond the endpoint itself — idempotency keys, retry semantics, dead-letter queues, replay protection — belong to `webhook-integration`. This skill covers the framework-specific endpoint surface only.
|
|
216
|
+
|
|
217
|
+
## CORS
|
|
218
|
+
|
|
219
|
+
There is no built-in CORS helper in Route Handlers. Set headers manually, and handle the preflight `OPTIONS` request explicitly:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const CORS_HEADERS = {
|
|
223
|
+
'Access-Control-Allow-Origin': 'https://app.example.com',
|
|
224
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
225
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
226
|
+
'Access-Control-Max-Age': '86400',
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function OPTIONS() {
|
|
230
|
+
return new Response(null, { status: 204, headers: CORS_HEADERS })
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function POST(request: Request) {
|
|
234
|
+
const data = await request.json()
|
|
235
|
+
return Response.json({ ok: true }, { headers: CORS_HEADERS })
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Never reflexively allow `*` for credentialed requests — it doesn't work with `Access-Control-Allow-Credentials: true` and is a footgun for cookies and auth. Allowlist explicit origins.
|
|
240
|
+
|
|
241
|
+
If many routes need the same CORS policy, push it to `middleware-patterns` and the middleware can inject headers globally.
|
|
242
|
+
|
|
243
|
+
## Status Codes and Errors
|
|
244
|
+
|
|
245
|
+
Honor HTTP semantics (see `http-semantics` for the full discipline):
|
|
246
|
+
|
|
247
|
+
- **200** — success with body
|
|
248
|
+
- **201** — created (returning the new resource)
|
|
249
|
+
- **204** — success, no body
|
|
250
|
+
- **400** — client error: malformed input
|
|
251
|
+
- **401** — unauthenticated
|
|
252
|
+
- **403** — authenticated but not authorized
|
|
253
|
+
- **404** — resource not found
|
|
254
|
+
- **409** — conflict (e.g., duplicate)
|
|
255
|
+
- **422** — validation failure (unprocessable entity)
|
|
256
|
+
- **429** — rate limited
|
|
257
|
+
- **500** — unexpected server error
|
|
258
|
+
- **503** — temporarily unavailable
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
export async function POST(request: Request) {
|
|
262
|
+
try {
|
|
263
|
+
const parsed = Schema.safeParse(await request.json())
|
|
264
|
+
if (!parsed.success) {
|
|
265
|
+
return Response.json({ error: parsed.error.flatten() }, { status: 422 })
|
|
266
|
+
}
|
|
267
|
+
const created = await db.post.create({ data: parsed.data })
|
|
268
|
+
return Response.json(created, { status: 201 })
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.error(err)
|
|
271
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 })
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
A thrown exception that escapes the handler becomes a 500. That's a usable fallback, but explicit `try/catch` plus a structured error envelope is better for any handler clients consume programmatically.
|
|
277
|
+
|
|
278
|
+
## Edge vs Node Runtime
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
export const runtime = 'edge' // or 'nodejs' (default)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
| Capability | Edge | Node |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| Web Crypto (`crypto.subtle`) | ✅ | ✅ |
|
|
287
|
+
| Node `crypto` module | ❌ | ✅ |
|
|
288
|
+
| Node `fs`, `child_process`, `net` | ❌ | ✅ |
|
|
289
|
+
| Most npm packages | ⚠️ depends on package | ✅ |
|
|
290
|
+
| Cold start | ~10–50ms | ~200–1000ms |
|
|
291
|
+
| Memory ceiling | lower (per Vercel: 128MB code, fast startup) | higher |
|
|
292
|
+
| Long-lived streams | ✅ better | ⚠️ platform-dependent |
|
|
293
|
+
| Vendor SDKs that need Node crypto | ❌ check vendor support | ✅ |
|
|
294
|
+
|
|
295
|
+
Default to Node when in doubt. Move to Edge only when (a) the cold-start gain matters (geographic distribution, low-traffic endpoints), (b) the handler is a streaming endpoint, or (c) the handler is on a hot path where the latency budget needs Edge's fast startup. Always re-test after switching — package compatibility breaks often.
|
|
296
|
+
|
|
297
|
+
## Common Anti-Patterns
|
|
298
|
+
|
|
299
|
+
| Anti-pattern | Why it's wrong | Fix |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| Using a Route Handler for an internal mutation triggered from this app's UI | Duplicate type contracts, manual fetch wiring, no progressive enhancement, no built-in revalidation | Use a Server Action |
|
|
302
|
+
| Calling `request.json()` on a webhook before HMAC verification | The parse can mutate whitespace; signature verification fails | Read `request.text()`, verify against raw bytes, then `JSON.parse` |
|
|
303
|
+
| Forgetting `dynamic = 'force-dynamic'` on a GET that depends on the request user | Cached response leaks one user's data to another | Either read cookies/headers (auto-opts out) or set `dynamic` explicitly |
|
|
304
|
+
| Slow webhook ACK because the handler awaits the heavy work inline | Vendor retries; duplicate processing | Queue the work; return 200 immediately |
|
|
305
|
+
| Throwing inside the handler and letting it become a generic 500 | Client gets no actionable error | Catch, log, return a structured error envelope with the right status code |
|
|
306
|
+
| Reflexive `Access-Control-Allow-Origin: '*'` with credentialed requests | Browser refuses to send credentials; auth breaks | Allowlist explicit origins; never combine `*` with credentials |
|
|
307
|
+
| Putting a `route.ts` and a `page.tsx` at the same path | Build error | Choose one — the URL has one purpose |
|
|
308
|
+
| Reading `request.body` as a stream and also trying `request.json()` | Body is consumed once | Pick one read method; if you need both raw and parsed, read text and parse yourself |
|
|
309
|
+
| Mixing App Router Route Handlers with Pages Router `pages/api/*` patterns (`req.body`, `res.status`) | Different API surfaces; types don't transfer | App Router uses Web Request/Response; rewrite to that shape |
|
|
310
|
+
| Long-lived stream on the Node runtime without checking platform buffering | Vercel/host may buffer the entire response before sending | Use Edge runtime for streams, or explicitly configure the host |
|
|
311
|
+
|
|
312
|
+
## Verification
|
|
313
|
+
|
|
314
|
+
After applying this skill, verify:
|
|
315
|
+
|
|
316
|
+
- [ ] The handler's caller is genuinely external (mobile, third-party, webhook, cross-origin client) or needs explicit HTTP semantics — otherwise it should be a Server Action or Server Component.
|
|
317
|
+
- [ ] Each HTTP method that the endpoint should support is a named export; unsupported methods are not exported.
|
|
318
|
+
- [ ] Body is read exactly once (or read as `text()` and parsed manually when raw bytes are needed).
|
|
319
|
+
- [ ] GET handlers that depend on per-request data either read cookies/headers explicitly or set `dynamic = 'force-dynamic'`.
|
|
320
|
+
- [ ] Webhook handlers verify signature against the raw body before any parsing or business logic.
|
|
321
|
+
- [ ] Webhook handlers ACK within the vendor's timeout budget; heavy work is queued.
|
|
322
|
+
- [ ] Status codes match the response semantics (201 for created, 422 for validation failure, 401 vs 403 distinguished).
|
|
323
|
+
- [ ] Errors are caught and returned with a structured envelope, not allowed to become bare 500s.
|
|
324
|
+
- [ ] CORS headers — if needed — are explicit allowlists, not `*` with credentials, and an `OPTIONS` handler is present.
|
|
325
|
+
- [ ] Runtime choice (`edge` vs `nodejs`) is intentional and the handler's dependencies are compatible with that runtime.
|
|
326
|
+
|
|
327
|
+
## Grounding Sources
|
|
328
|
+
|
|
329
|
+
- Next.js docs — [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers). The canonical reference for the `route.ts` convention.
|
|
330
|
+
- Next.js docs — [Route Segment Config](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config). `dynamic`, `revalidate`, `runtime`, and the other per-route knobs.
|
|
331
|
+
- MDN — [Fetch API: Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). The Web-standard interface Route Handlers implement.
|
|
332
|
+
- RFC 9110 — [HTTP Semantics](https://datatracker.ietf.org/doc/html/rfc9110). Method semantics, status code semantics, header semantics. The contract Route Handlers honor.
|
|
333
|
+
- Vercel docs — [Edge Runtime API reference](https://vercel.com/docs/functions/runtimes/edge-runtime). The capability surface available to `runtime = 'edge'` handlers.
|
|
334
|
+
- Stripe docs — [Verifying webhook signatures](https://stripe.com/docs/webhooks#verify-events). The canonical example of the raw-body / verify-before-parse pattern that drives Route Handler webhook design.
|
|
335
|
+
- WHATWG Streams — [Streams Living Standard](https://streams.spec.whatwg.org/). The `ReadableStream` interface used for streaming responses.
|
|
336
|
+
|
|
337
|
+
## Do NOT Use When
|
|
338
|
+
|
|
339
|
+
| Instead of this skill | Use | Why |
|
|
340
|
+
|---|---|---|
|
|
341
|
+
| Designing an internal mutation triggered only from this app's UI | `server-actions-design` | Server Actions are the right surface for in-app mutations — one declaration, no wire format, progressive enhancement. |
|
|
342
|
+
| Designing the broader REST/RPC contract: resources, versioning, pagination, error envelopes | `api-design` | api-design owns the contract; this skill owns the Next.js implementation surface that hosts the contract. |
|
|
343
|
+
| Understanding what each HTTP method or status code means in the abstract | `http-semantics` | http-semantics owns the protocol semantics; this skill owns honoring them in App Router. |
|
|
344
|
+
| Adding auth, locale, or header injection across many routes | `middleware-patterns` | middleware runs once before route resolution and applies to many routes; Route Handlers run per-route per-method. |
|
|
345
|
+
| The full webhook reliability story: idempotency, retries, dead-letter queues, replay protection | `webhook-integration` | webhook-integration owns the cross-vendor reliability discipline; this skill covers the endpoint surface only. |
|
|
346
|
+
| The cross-cutting streaming model: Web Streams, SSE, RSC streaming, backpressure | `streaming-architecture` | streaming-architecture covers streaming as a concept; this skill covers the Route Handler streaming surface specifically. |
|
|
347
|
+
| The serialization/directive mechanics of `'use client'` and `'use server'` | `client-server-boundary` | Different boundary — client-server-boundary is for the bundler's component split, not for HTTP endpoints. |
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: schema-evolution
|
|
3
|
+
description: "Use when reasoning about how a database schema changes over time without breaking deployed application code: the expand/contract pattern (Ambler & Sadalage), the zero-downtime change rules, the backwards-and-forwards compatibility envelope (deploy ordering and rollback discipline), the catalog of schema changes (add column, drop column, rename, type change, add constraint, add index) and the safe procedure for each, the dual-write and dual-read transitions that make non-trivial changes safe in production, and the relationship between schema evolution as a design discipline and database-migration mechanics as its tooling. Do NOT use for the mechanical execution of one migration (use database-migration), schema design from scratch (use data-modeling), query tuning (use query-optimization), or distributed-data partitioning (use sharding-strategy)."
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Read Grep
|
|
6
|
+
metadata:
|
|
7
|
+
metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"engineering\",\"domain\":\"engineering/data\",\"scope\":\"reference\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-05-16\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-05-16\\\\\\\"}\",\"eval_artifacts\":\"planned\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"comprehension_state\":\"present\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"schema evolution\\\\\\\",\\\\\\\"expand contract\\\\\\\",\\\\\\\"parallel change\\\\\\\",\\\\\\\"zero-downtime migration\\\\\\\",\\\\\\\"backwards compatibility\\\\\\\",\\\\\\\"rolling deploy\\\\\\\",\\\\\\\"dual write\\\\\\\",\\\\\\\"dual read\\\\\\\",\\\\\\\"schema versioning\\\\\\\",\\\\\\\"additive change\\\\\\\",\\\\\\\"destructive change\\\\\\\"]\",\"triggers\":\"[\\\\\\\"how do we rename this column without downtime\\\\\\\",\\\\\\\"expand contract\\\\\\\",\\\\\\\"is this migration safe\\\\\\\",\\\\\\\"schema versioning\\\\\\\",\\\\\\\"backwards compatibility for database\\\\\\\"]\",\"examples\":\"[\\\\\\\"design the expand-contract sequence to rename a column from `name` to `full_name` across a deployed system\\\\\\\",\\\\\\\"decide whether to add a NOT NULL column with a default or with a separate backfill phase\\\\\\\",\\\\\\\"diagnose a deploy that broke because the schema change shipped before the code change\\\\\\\",\\\\\\\"explain why drop-column is the third phase of expand-contract, not the first\\\\\\\"]\",\"relations\":\"{\\\\\\\"related\\\\\\\":[\\\\\\\"data-modeling\\\\\\\",\\\\\\\"database-migration\\\\\\\",\\\\\\\"indexing-strategy\\\\\\\",\\\\\\\"acid-fundamentals\\\\\\\"],\\\\\\\"boundary\\\\\\\":[{\\\\\\\"skill\\\\\\\":\\\\\\\"data-modeling\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"data-modeling owns the design of a schema at a point in time; this skill owns how that schema changes between points in time. The two compose: data-modeling decides the target shape; this skill decides the safe path from current to target.\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"database-migration\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"database-migration owns the mechanics of applying one migration (ALTER TABLE, batched backfill, CONCURRENTLY indexes, unpooled connections); this skill owns the multi-step sequence of migrations and the deploy-coordination discipline that makes the sequence safe.\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"indexing-strategy\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"indexing-strategy owns which indexes the database has; this skill owns how the index set evolves over time. Adding or removing an index is one type of schema change governed by this skill's discipline.\\\\\\\"}],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"data-modeling\\\\\\\",\\\\\\\"database-migration\\\\\\\"]}\",\"anti_examples\":\"[\\\\\\\"execute one ALTER TABLE migration mechanically (use database-migration)\\\\\\\",\\\\\\\"design a schema from scratch (use data-modeling)\\\\\\\",\\\\\\\"diagnose a slow query (use query-optimization)\\\\\\\"]\",\"mental_model\":\"|\",\"purpose\":\"|\",\"boundary\":\"|\",\"analogy\":\"Schema evolution is to a database what stage carpentry is to a Broadway musical — the show does not stop; you do not bolt a new staircase to the stage during the second act; you build the new staircase upstage while the old staircase serves the cast (expand), gradually rehearse the cast to use the new one while the old still works (migrate), and only after every performer has memorized the new route do you remove the old staircase (contract). Removing the old before everyone has migrated is the production-incident equivalent of a missed cue.\",\"misconception\":\"|\",\"concept\":\"{\\\\\\\"definition\\\\\\\":\\\\\\\"Schema evolution is the discipline of changing a database schema over time in a way that keeps deployed application code working. The unit of work is a *change to the schema* (add a column, rename a column, change a type, add a constraint, drop a column) that must be applied to a database serving an application that does not stop running. The central technique is expand/contract (Ambler & Sadalage 2006; also called parallel change): introduce the new shape *additively* without removing the old shape (expand), migrate the application to use the new shape, then remove the old shape (contract). The discipline is the *ordering* across migrations and deploys, the backwards-and-forwards-compatibility envelope each intermediate state must satisfy, and the rollback discipline that keeps the system recoverable when any step fails. The mechanical execution of any single migration is the concern of database-migration; the *sequence* of migrations and their relationship to application deploys is this skill's concern.\\\\\\\",\\\\\\\"mental_model\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"purpose\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"boundary\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"taxonomy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"analogy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"misconception\\\\\\\":\\\\\\\"|\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/schema-evolution/SKILL.md\"}"
|
|
8
|
+
skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
|
|
9
|
+
skill_graph_protocol: Skill Metadata Protocol v4
|
|
10
|
+
skill_graph_project: Skill Graph
|
|
11
|
+
skill_graph_canonical_skill: skills/schema-evolution/SKILL.md
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Schema Evolution
|
|
15
|
+
|
|
16
|
+
## Coverage
|
|
17
|
+
|
|
18
|
+
The discipline of changing a database schema over time without breaking deployed application code. Covers the expand/contract (parallel change) pattern as the foundational technique, the backwards-and-forwards compatibility envelope each intermediate state must satisfy, the catalog of schema-change types and their safety profiles (additive, constraint-tightening, destructive, renaming, type-change), the backfill strategies (no-backfill, batched, background, dual-write-only, lazy), the dual-write / dual-read transition patterns, the deploy-ordering rules (migration-first vs code-first), and the relationship to the underlying database-migration tooling that executes individual steps.
|
|
19
|
+
|
|
20
|
+
## Philosophy
|
|
21
|
+
|
|
22
|
+
Schema and deployed code are co-evolving systems. The danger is desynchronization — schema changes break running code; code changes assume a schema that hasn't been deployed yet. Expand/contract is the protocol that keeps both sides compatible during transition: additively introduce the new shape, migrate, then contract the old.
|
|
23
|
+
|
|
24
|
+
The discipline is not in the individual ALTER TABLE statement (that's database-migration's concern). It is in the *sequencing* across migrations and deploys, the compatibility envelope at every intermediate point, and the rollback discipline that keeps the system recoverable when a step fails.
|
|
25
|
+
|
|
26
|
+
The most consequential phases are the boundaries — entering migrate (the dual-write/dual-read transition) and entering contract (the irreversible drop). Knowing the criteria for crossing each boundary is the operational hygiene that separates evolved systems from broken ones.
|
|
27
|
+
|
|
28
|
+
## The Expand / Migrate / Contract Phases
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌────────────────────────────────────────┐
|
|
32
|
+
│ Phase 1: EXPAND │
|
|
33
|
+
│ • Add new column / table / index │
|
|
34
|
+
│ • Old shape unchanged │
|
|
35
|
+
│ • Old code continues working unchanged │
|
|
36
|
+
│ • New code can use new shape if deployed│
|
|
37
|
+
│ • Rollback: drop new shape │
|
|
38
|
+
└────────────────────────────────────────┘
|
|
39
|
+
│
|
|
40
|
+
▼
|
|
41
|
+
┌────────────────────────────────────────┐
|
|
42
|
+
│ Phase 2: MIGRATE (multi-deploy) │
|
|
43
|
+
│ • Deploy code that dual-writes │
|
|
44
|
+
│ • Backfill existing data (batched) │
|
|
45
|
+
│ • Deploy code that dual-reads (new w/ │
|
|
46
|
+
│ fallback to old) │
|
|
47
|
+
│ • Verify production traffic on new │
|
|
48
|
+
│ • Deploy code that single-reads new │
|
|
49
|
+
│ • Rollback: revert code; old shape OK │
|
|
50
|
+
└────────────────────────────────────────┘
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
┌────────────────────────────────────────┐
|
|
54
|
+
│ Phase 3: CONTRACT (irreversible) │
|
|
55
|
+
│ • Drop old column / table / index │
|
|
56
|
+
│ • No code references old shape │
|
|
57
|
+
│ • Rollback: impossible (data is gone) │
|
|
58
|
+
└────────────────────────────────────────┘
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The time between expand-complete and contract-start is normally days or weeks, not minutes.
|
|
62
|
+
|
|
63
|
+
## Change-Type Safety Matrix
|
|
64
|
+
|
|
65
|
+
| Change | Single-step safe? | Pattern |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| Add nullable column | Yes | Direct ALTER |
|
|
68
|
+
| Add column with NOT NULL DEFAULT | Yes (in Postgres 11+ for constant defaults; verify per database) | Direct ALTER |
|
|
69
|
+
| Add column with NOT NULL no default | No | Expand: add nullable; backfill; add constraint with NOT VALID; VALIDATE |
|
|
70
|
+
| Add new table | Yes | Direct CREATE TABLE |
|
|
71
|
+
| Add index | Yes (with CONCURRENTLY in Postgres) | CREATE INDEX CONCURRENTLY |
|
|
72
|
+
| Add foreign key | No (validation locks) | ADD FK NOT VALID; then VALIDATE CONSTRAINT separately |
|
|
73
|
+
| Add CHECK constraint | No (validation locks) | Same NOT VALID + VALIDATE pattern |
|
|
74
|
+
| Add UNIQUE constraint | No (needs verified uniqueness) | Verify no duplicates; CREATE UNIQUE INDEX CONCURRENTLY; ADD CONSTRAINT USING INDEX |
|
|
75
|
+
| Drop column | No (deployed code references it) | Full expand/contract |
|
|
76
|
+
| Drop table | No (deployed code references it) | Full expand/contract |
|
|
77
|
+
| Drop index | Yes (only if no query relies on it) | DROP INDEX CONCURRENTLY |
|
|
78
|
+
| Rename column | No | Full expand/contract: add new; dual-write; backfill; switch reads; drop old |
|
|
79
|
+
| Rename table | No | Same expand/contract pattern |
|
|
80
|
+
| Change column type | No | Add new column; backfill with conversion; switch reads; drop old |
|
|
81
|
+
| Change DEFAULT | Yes | Direct ALTER (affects only future inserts) |
|
|
82
|
+
|
|
83
|
+
## The Deploy-Ordering Rule
|
|
84
|
+
|
|
85
|
+
| Direction | When safe | Risk |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| **Migration first, then code** | Migration is additive (column added; nothing depending on it) | Migration runs; old code keeps working; new code rolls out and uses new |
|
|
88
|
+
| **Code first, then migration** | Code is backwards-compatible with old schema | New code rolls out; tolerates old schema; migration runs; new code now uses new shape |
|
|
89
|
+
| **Coordinated (multi-step)** | Most non-trivial changes | Expand/migrate/contract spans multiple deploys |
|
|
90
|
+
|
|
91
|
+
The deploy-ordering choice is a design decision per change. The expand/contract pattern's value is that it makes the ordering *explicit* and *survivable* at every intermediate point.
|
|
92
|
+
|
|
93
|
+
## When To Contract
|
|
94
|
+
|
|
95
|
+
The criteria for crossing into the irreversible contract phase:
|
|
96
|
+
|
|
97
|
+
- [ ] All deployed code reads exclusively from the new shape (verified via code inspection or runtime monitoring).
|
|
98
|
+
- [ ] All deployed code writes exclusively to the new shape, or writes to both with the old write being redundant.
|
|
99
|
+
- [ ] The backfill is complete and verified — no existing rows have inconsistent state.
|
|
100
|
+
- [ ] Observability for the new shape is in place; the old shape's removal would not blind monitoring.
|
|
101
|
+
- [ ] The migrate phase has been stable in production for a defined observation period (typically days).
|
|
102
|
+
- [ ] Rollback path during the contract is not required — the team has decided the change is permanent.
|
|
103
|
+
|
|
104
|
+
Premature contract is a common cause of incidents. The contract phase is irreversible by definition.
|
|
105
|
+
|
|
106
|
+
## Verification
|
|
107
|
+
|
|
108
|
+
After applying this skill, verify:
|
|
109
|
+
- [ ] Every non-trivial schema change is planned as expand → migrate → contract, not as a single deploy.
|
|
110
|
+
- [ ] The compatibility envelope is explicit: which code versions are expected to run, and with which schema versions, at every intermediate point.
|
|
111
|
+
- [ ] Constraint-tightening changes use the NOT VALID + VALIDATE pattern (or equivalent in the specific database) to avoid blocking writes during validation.
|
|
112
|
+
- [ ] Index creation uses CONCURRENTLY (Postgres) or equivalent non-blocking mechanism.
|
|
113
|
+
- [ ] Dual-write / dual-read transitions have defined criteria for advancing to the next state — not "we'll know when we're ready."
|
|
114
|
+
- [ ] The contract phase has explicit verification criteria (above list) before it runs.
|
|
115
|
+
- [ ] Backfill jobs are batched (rows in tractable chunks) and resumable (can pick up where they left off after failure).
|
|
116
|
+
- [ ] Rollback is preserved through expand and migrate; the team accepts irreversibility only at contract.
|
|
117
|
+
- [ ] Migrations are reviewed for the *sequence* and the *envelope*, not just the syntax.
|
|
118
|
+
|
|
119
|
+
## Do NOT Use When
|
|
120
|
+
|
|
121
|
+
| Instead of this skill | Use | Why |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| Executing one ALTER TABLE migration mechanically | `database-migration` | database-migration owns the mechanics; this owns the sequencing |
|
|
124
|
+
| Designing the schema from scratch | `data-modeling` | data-modeling owns design; this owns evolution |
|
|
125
|
+
| Deciding which indexes to maintain | `indexing-strategy` | indexing-strategy owns design; this owns how the index set evolves |
|
|
126
|
+
| Tuning a slow query | `query-optimization` | query-optimization owns retrieval performance; this owns schema change |
|
|
127
|
+
| Horizontal partitioning | `sharding-strategy` | sharding owns partition; this owns schema shape changes |
|
|
128
|
+
| Choosing isolation level | `transaction-isolation` | transaction-isolation owns concurrency; this owns shape |
|
|
129
|
+
|
|
130
|
+
## Key Sources
|
|
131
|
+
|
|
132
|
+
- Ambler, S. W., & Sadalage, P. J. (2006). *Refactoring Databases: Evolutionary Database Design*. Addison-Wesley. The canonical reference for the expand/contract pattern (called "Parallel Change" in some literature) and the broader catalog of database refactorings.
|
|
133
|
+
- Sadalage, P. J., & Fowler, M. ["Evolutionary Database Design"](https://martinfowler.com/articles/evodb.html). Practitioner essay summarizing the discipline of incremental schema change.
|
|
134
|
+
- Fowler, M. ["ParallelChange"](https://martinfowler.com/bliki/ParallelChange.html). Short reference on the parallel-change pattern as a general software-evolution technique (applies beyond databases).
|
|
135
|
+
- PostgreSQL Global Development Group. ["PostgreSQL Documentation — ALTER TABLE"](https://www.postgresql.org/docs/current/sql-altertable.html). Reference for Postgres-specific safe-change patterns (NOT VALID, CONCURRENTLY, etc.).
|
|
136
|
+
- Strong Migrations (Ruby) and pt-online-schema-change (MySQL/Percona). Open-source tools that encode the safe-migration patterns and reject unsafe migrations at lint time.
|
|
137
|
+
- gh-ost (GitHub's online schema change tool for MySQL). Documented patterns for online schema change on large MySQL tables; useful framing for non-Postgres environments.
|
|
138
|
+
- Sandberg, R. (2021). ["Online Migrations at Scale"](https://stripe.com/blog/online-migrations) (Stripe Engineering Blog). Industrial case study on expand/contract at scale.
|
|
139
|
+
- Shopify Engineering. ["Adding a NOT NULL Column to a Table in Postgres"](https://shopify.engineering/safely-adding-not-null-columns-postgres). Industrial guide to one common change with detailed safety reasoning.
|
|
140
|
+
- Kleppmann, M. (2017). *Designing Data-Intensive Applications*. O'Reilly. Chapter 4 covers schema evolution in distributed-data contexts including the document/wide-column store cases.
|