cfsa-antigravity 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +324 -0
- package/package.json +34 -0
- package/template/.agent/instructions/commands.md +48 -0
- package/template/.agent/instructions/patterns.md +61 -0
- package/template/.agent/instructions/structure.md +29 -0
- package/template/.agent/instructions/tech-stack.md +43 -0
- package/template/.agent/instructions/workflow.md +41 -0
- package/template/.agent/kit-sync.md +15 -0
- package/template/.agent/rules/boundary-not-placeholder.md +146 -0
- package/template/.agent/rules/completion-checklist.md +48 -0
- package/template/.agent/rules/decision-classification.md +103 -0
- package/template/.agent/rules/extensibility.md +47 -0
- package/template/.agent/rules/question-vs-command.md +81 -0
- package/template/.agent/rules/security-first.md +43 -0
- package/template/.agent/rules/specificity-standards.md +54 -0
- package/template/.agent/rules/tdd-contract-first.md +57 -0
- package/template/.agent/rules/vertical-slices.md +42 -0
- package/template/.agent/skill-library/MANIFEST.md +480 -0
- package/template/.agent/skill-library/README.md +38 -0
- package/template/.agent/skill-library/meta/brand-guidelines/SKILL.md +73 -0
- package/template/.agent/skill-library/meta/claude-code/README.md +9 -0
- package/template/.agent/skill-library/meta/claude-code/agent-development/SKILL.md +415 -0
- package/template/.agent/skill-library/meta/claude-code/hook-development/SKILL.md +712 -0
- package/template/.agent/skill-library/meta/claude-code/plugin-structure/SKILL.md +476 -0
- package/template/.agent/skill-library/meta/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/meta/mcp-builder/SKILL.md +236 -0
- package/template/.agent/skill-library/meta/product-marketing-context/SKILL.md +241 -0
- package/template/.agent/skill-library/meta/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skill-library/meta/tmux-processes/SKILL.md +210 -0
- package/template/.agent/skill-library/meta/using-tmux-for-interactive-commands/SKILL.md +178 -0
- package/template/.agent/skill-library/stack/3d/threejs-pro/SKILL.md +300 -0
- package/template/.agent/skill-library/stack/ai/ai-sdk/SKILL.md +77 -0
- package/template/.agent/skill-library/stack/ai/langchain/SKILL.md +530 -0
- package/template/.agent/skill-library/stack/ai/ollama/SKILL.md +321 -0
- package/template/.agent/skill-library/stack/ai/openai-sdk/SKILL.md +549 -0
- package/template/.agent/skill-library/stack/analytics/google-analytics/SKILL.md +153 -0
- package/template/.agent/skill-library/stack/api/graphql/SKILL.md +1061 -0
- package/template/.agent/skill-library/stack/api/trpc/SKILL.md +576 -0
- package/template/.agent/skill-library/stack/auth/authjs/SKILL.md +569 -0
- package/template/.agent/skill-library/stack/auth/clerk/SKILL.md +590 -0
- package/template/.agent/skill-library/stack/auth/firebase-auth/SKILL.md +734 -0
- package/template/.agent/skill-library/stack/cms/payload-cms/SKILL.md +573 -0
- package/template/.agent/skill-library/stack/cms/shopify/SKILL.md +1193 -0
- package/template/.agent/skill-library/stack/cms/wordpress/SKILL.md +1104 -0
- package/template/.agent/skill-library/stack/css/sass-scss/SKILL.md +1121 -0
- package/template/.agent/skill-library/stack/css/tailwind-css-patterns/SKILL.md +863 -0
- package/template/.agent/skill-library/stack/css/tailwind-design-system/SKILL.md +490 -0
- package/template/.agent/skill-library/stack/css/vanilla-css/SKILL.md +1078 -0
- package/template/.agent/skill-library/stack/databases/clickhouse/SKILL.md +311 -0
- package/template/.agent/skill-library/stack/databases/influxdb/SKILL.md +280 -0
- package/template/.agent/skill-library/stack/databases/lancedb/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/databases/mongodb/SKILL.md +1169 -0
- package/template/.agent/skill-library/stack/databases/neo4j/SKILL.md +839 -0
- package/template/.agent/skill-library/stack/databases/pgvector/SKILL.md +241 -0
- package/template/.agent/skill-library/stack/databases/pinecone/SKILL.md +212 -0
- package/template/.agent/skill-library/stack/databases/postgresql/SKILL.md +658 -0
- package/template/.agent/skill-library/stack/databases/qdrant/SKILL.md +312 -0
- package/template/.agent/skill-library/stack/databases/redis/SKILL.md +1079 -0
- package/template/.agent/skill-library/stack/databases/spacetimedb/SKILL.md +532 -0
- package/template/.agent/skill-library/stack/databases/sqlite/SKILL.md +1132 -0
- package/template/.agent/skill-library/stack/databases/supabase/SKILL.md +640 -0
- package/template/.agent/skill-library/stack/databases/surrealdb-expert/SKILL.md +945 -0
- package/template/.agent/skill-library/stack/databases/timescaledb/SKILL.md +745 -0
- package/template/.agent/skill-library/stack/databases/weaviate/SKILL.md +218 -0
- package/template/.agent/skill-library/stack/devops/github-actions/SKILL.md +554 -0
- package/template/.agent/skill-library/stack/devops/kubernetes/SKILL.md +950 -0
- package/template/.agent/skill-library/stack/devops/nginx/SKILL.md +841 -0
- package/template/.agent/skill-library/stack/devops/terraform/SKILL.md +860 -0
- package/template/.agent/skill-library/stack/email/resend/SKILL.md +391 -0
- package/template/.agent/skill-library/stack/engines/godot/SKILL.md +488 -0
- package/template/.agent/skill-library/stack/extensions/chrome-extension/SKILL.md +375 -0
- package/template/.agent/skill-library/stack/extensions/vscode-extension/SKILL.md +453 -0
- package/template/.agent/skill-library/stack/frameworks/astro-framework/SKILL.md +162 -0
- package/template/.agent/skill-library/stack/frameworks/electron/SKILL.md +1286 -0
- package/template/.agent/skill-library/stack/frameworks/fastapi/SKILL.md +650 -0
- package/template/.agent/skill-library/stack/frameworks/hono/SKILL.md +90 -0
- package/template/.agent/skill-library/stack/frameworks/nestjs/SKILL.md +878 -0
- package/template/.agent/skill-library/stack/frameworks/nextjs/SKILL.md +635 -0
- package/template/.agent/skill-library/stack/frameworks/nuxt/SKILL.md +564 -0
- package/template/.agent/skill-library/stack/frameworks/sveltekit/SKILL.md +614 -0
- package/template/.agent/skill-library/stack/frameworks/tauri/SKILL.md +920 -0
- package/template/.agent/skill-library/stack/gamedev/godot/SKILL.md +1032 -0
- package/template/.agent/skill-library/stack/gamedev/unity/SKILL.md +1175 -0
- package/template/.agent/skill-library/stack/hosting/aws/SKILL.md +467 -0
- package/template/.agent/skill-library/stack/hosting/cloudflare/SKILL.md +201 -0
- package/template/.agent/skill-library/stack/hosting/docker-expert/SKILL.md +409 -0
- package/template/.agent/skill-library/stack/hosting/vercel/SKILL.md +484 -0
- package/template/.agent/skill-library/stack/languages/bash-scripting/SKILL.md +773 -0
- package/template/.agent/skill-library/stack/languages/c-cpp/SKILL.md +712 -0
- package/template/.agent/skill-library/stack/languages/gdscript/SKILL.md +789 -0
- package/template/.agent/skill-library/stack/languages/go/SKILL.md +664 -0
- package/template/.agent/skill-library/stack/languages/java/SKILL.md +778 -0
- package/template/.agent/skill-library/stack/languages/kotlin/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/languages/python/SKILL.md +678 -0
- package/template/.agent/skill-library/stack/languages/rust/SKILL.md +673 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/SKILL.md +141 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/advanced-generics.md +90 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/branded-types.md +57 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/builder-pattern.md +71 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/common-pitfalls.md +135 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/conditional-types.md +27 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/decorators.md +98 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/discriminated-unions.md +62 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/mapped-types.md +53 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/performance-best-practices.md +104 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/template-literal-types.md +49 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/testing-types.md +112 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-guards.md +70 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-inference.md +101 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/utility-types.md +98 -0
- package/template/.agent/skill-library/stack/languages/vanilla-javascript/SKILL.md +803 -0
- package/template/.agent/skill-library/stack/messaging/kafka/SKILL.md +235 -0
- package/template/.agent/skill-library/stack/mobile/expo-react-native/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/mobile/flutter/SKILL.md +316 -0
- package/template/.agent/skill-library/stack/mobile/react-native/SKILL.md +337 -0
- package/template/.agent/skill-library/stack/monitoring/posthog/SKILL.md +396 -0
- package/template/.agent/skill-library/stack/monitoring/sentry/SKILL.md +509 -0
- package/template/.agent/skill-library/stack/observability/datadog/SKILL.md +179 -0
- package/template/.agent/skill-library/stack/observability/distributed-tracing/SKILL.md +140 -0
- package/template/.agent/skill-library/stack/observability/logging-best-practices/SKILL.md +168 -0
- package/template/.agent/skill-library/stack/observability/opentelemetry/SKILL.md +164 -0
- package/template/.agent/skill-library/stack/observability/prometheus-grafana/SKILL.md +246 -0
- package/template/.agent/skill-library/stack/observability/python-observability/SKILL.md +158 -0
- package/template/.agent/skill-library/stack/orm/drizzle-orm/SKILL.md +613 -0
- package/template/.agent/skill-library/stack/orm/prisma/SKILL.md +744 -0
- package/template/.agent/skill-library/stack/payments/lemonsqueezy/SKILL.md +393 -0
- package/template/.agent/skill-library/stack/payments/stripe-integration/SKILL.md +457 -0
- package/template/.agent/skill-library/stack/queue/bullmq/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/queue/inngest/SKILL.md +438 -0
- package/template/.agent/skill-library/stack/realtime/socketio/SKILL.md +595 -0
- package/template/.agent/skill-library/stack/search/elasticsearch/SKILL.md +248 -0
- package/template/.agent/skill-library/stack/search/meilisearch/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/security/crypto-patterns/SKILL.md +437 -0
- package/template/.agent/skill-library/stack/security/csp-cors-headers/SKILL.md +588 -0
- package/template/.agent/skill-library/stack/security/dependency-auditing/SKILL.md +560 -0
- package/template/.agent/skill-library/stack/security/input-sanitization/SKILL.md +430 -0
- package/template/.agent/skill-library/stack/security/owasp-web-security/SKILL.md +421 -0
- package/template/.agent/skill-library/stack/state/tanstack-query/SKILL.md +637 -0
- package/template/.agent/skill-library/stack/state/zustand/SKILL.md +483 -0
- package/template/.agent/skill-library/stack/storage/aws-s3/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/testing/playwright/SKILL.md +641 -0
- package/template/.agent/skill-library/stack/testing/storybook/SKILL.md +923 -0
- package/template/.agent/skill-library/stack/testing/testing-library/SKILL.md +872 -0
- package/template/.agent/skill-library/stack/testing/vitest/SKILL.md +714 -0
- package/template/.agent/skill-library/stack/ui/react-best-practices/SKILL.md +877 -0
- package/template/.agent/skill-library/stack/ui/react-composition-patterns/SKILL.md +1107 -0
- package/template/.agent/skill-library/stack/ui/react-flow/SKILL.md +425 -0
- package/template/.agent/skill-library/stack/ui/shadcn-ui/SKILL.md +703 -0
- package/template/.agent/skill-library/surface/api/api-caching/SKILL.md +458 -0
- package/template/.agent/skill-library/surface/api/api-documentation-openapi/SKILL.md +697 -0
- package/template/.agent/skill-library/surface/api/api-error-handling/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/api-security-checklist/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/api-versioning/SKILL.md +420 -0
- package/template/.agent/skill-library/surface/api/email-best-practices/SKILL.md +59 -0
- package/template/.agent/skill-library/surface/api/rate-limiting-abuse-protection/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/webhook-design/SKILL.md +752 -0
- package/template/.agent/skill-library/surface/cli/cli-configuration-management/SKILL.md +445 -0
- package/template/.agent/skill-library/surface/cli/cli-error-diagnostics/SKILL.md +515 -0
- package/template/.agent/skill-library/surface/cli/cli-shell-integration/SKILL.md +479 -0
- package/template/.agent/skill-library/surface/cli/cli-ux-design/SKILL.md +477 -0
- package/template/.agent/skill-library/surface/desktop/desktop-app-distribution/SKILL.md +416 -0
- package/template/.agent/skill-library/surface/desktop/desktop-security-sandboxing/SKILL.md +407 -0
- package/template/.agent/skill-library/surface/desktop/desktop-ux-conventions/SKILL.md +361 -0
- package/template/.agent/skill-library/surface/desktop/native-os-integration/SKILL.md +563 -0
- package/template/.agent/skill-library/surface/extension/browser-extension-patterns/SKILL.md +482 -0
- package/template/.agent/skill-library/surface/extension/plugin-architecture-design/SKILL.md +632 -0
- package/template/.agent/skill-library/surface/extension/vscode-extension-development/SKILL.md +728 -0
- package/template/.agent/skill-library/surface/mobile/app-store-submission/SKILL.md +304 -0
- package/template/.agent/skill-library/surface/mobile/mobile-offline-sync/SKILL.md +443 -0
- package/template/.agent/skill-library/surface/mobile/mobile-responsive-patterns/SKILL.md +432 -0
- package/template/.agent/skill-library/surface/mobile/push-notifications/SKILL.md +495 -0
- package/template/.agent/skill-library/surface/web/accessibility-compliance/SKILL.md +827 -0
- package/template/.agent/skill-library/surface/web/ai-seo/SKILL.md +398 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/content-patterns.md +285 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/platform-ranking-factors.md +152 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/SKILL.md +309 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/event-library.md +260 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/ga4-implementation.md +300 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/gtm-implementation.md +390 -0
- package/template/.agent/skill-library/surface/web/authentication-ui-flows/SKILL.md +530 -0
- package/template/.agent/skill-library/surface/web/dark-mode-theming/SKILL.md +516 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/SKILL.md +105 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/charts.csv +26 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/colors.csv +97 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/landing.csv +31 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/styles.csv +59 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/typography.csv +58 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/ux-guidelines.csv +100 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/core.py +258 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/design_system.py +1067 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/search.py +106 -0
- package/template/.agent/skill-library/surface/web/form-handling-validation/SKILL.md +675 -0
- package/template/.agent/skill-library/surface/web/frontend-design/SKILL.md +1393 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/cppn-hero.tsx +299 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/wave-hero.tsx +875 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/SKILL.md +111 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/scripts/ux_audit.py +739 -0
- package/template/.agent/skill-library/surface/web/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skill-library/surface/web/offline-first-pwa/SKILL.md +657 -0
- package/template/.agent/skill-library/surface/web/page-cro/SKILL.md +182 -0
- package/template/.agent/skill-library/surface/web/page-cro/references/experiments.md +248 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/SKILL.md +238 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/references/playbooks.md +308 -0
- package/template/.agent/skill-library/surface/web/schema-markup/SKILL.md +179 -0
- package/template/.agent/skill-library/surface/web/schema-markup/references/schema-examples.md +398 -0
- package/template/.agent/skill-library/surface/web/seo-audit/SKILL.md +394 -0
- package/template/.agent/skill-library/surface/web/seo-audit/references/ai-writing-detection.md +200 -0
- package/template/.agent/skill-library/surface/web/web-performance-optimization/SKILL.md +646 -0
- package/template/.agent/skill-library/surface/web/web-scraping/SKILL.md +58 -0
- package/template/.agent/skills/accessibility/SKILL.md +522 -0
- package/template/.agent/skills/accessibility/references/WCAG.md +162 -0
- package/template/.agent/skills/adversarial-review/SKILL.md +90 -0
- package/template/.agent/skills/antigravity-workflows/SKILL.md +81 -0
- package/template/.agent/skills/antigravity-workflows/resources/implementation-playbook.md +36 -0
- package/template/.agent/skills/api-design-principles/SKILL.md +37 -0
- package/template/.agent/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/template/.agent/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/template/.agent/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/template/.agent/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/template/.agent/skills/api-design-principles/resources/implementation-playbook.md +513 -0
- package/template/.agent/skills/api-versioning/SKILL.md +420 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +219 -0
- package/template/.agent/skills/bootstrap-agents/SKILL.md +259 -0
- package/template/.agent/skills/brainstorming/SKILL.md +236 -0
- package/template/.agent/skills/brand-guidelines/SKILL.md +44 -0
- package/template/.agent/skills/clean-code/SKILL.md +94 -0
- package/template/.agent/skills/code-review-pro/SKILL.md +152 -0
- package/template/.agent/skills/concise-planning/SKILL.md +68 -0
- package/template/.agent/skills/cross-layer-consistency/SKILL.md +117 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +429 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/design-anti-cliche/SKILL.md +159 -0
- package/template/.agent/skills/design-direction/SKILL.md +45 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +721 -0
- package/template/.agent/skills/find-skills/SKILL.md +145 -0
- package/template/.agent/skills/git-advanced/SKILL.md +972 -0
- package/template/.agent/skills/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +271 -0
- package/template/.agent/skills/logging-best-practices/SKILL.md +851 -0
- package/template/.agent/skills/migration-management/SKILL.md +384 -0
- package/template/.agent/skills/minimalist-surgical-development/SKILL.md +69 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +165 -0
- package/template/.agent/skills/parallel-debugging/SKILL.md +135 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +166 -0
- package/template/.agent/skills/performance-budgeting/SKILL.md +144 -0
- package/template/.agent/skills/pipeline-rubrics/SKILL.md +51 -0
- package/template/.agent/skills/pipeline-rubrics/references/architecture-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +21 -0
- package/template/.agent/skills/pipeline-rubrics/references/fe-rubric.md +20 -0
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +28 -0
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +11 -0
- package/template/.agent/skills/prd-templates/SKILL.md +88 -0
- package/template/.agent/skills/prd-templates/references/architecture-design-template.md +88 -0
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +101 -0
- package/template/.agent/skills/prd-templates/references/data-placement-template.md +74 -0
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +211 -0
- package/template/.agent/skills/prd-templates/references/design-system-decisions.md +198 -0
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +124 -0
- package/template/.agent/skills/prd-templates/references/fe-classification-procedures.md +47 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +84 -0
- package/template/.agent/skills/prd-templates/references/infrastructure-report-template.md +71 -0
- package/template/.agent/skills/prd-templates/references/operational-templates.md +116 -0
- package/template/.agent/skills/prd-templates/references/placeholder-guard-template.md +21 -0
- package/template/.agent/skills/prd-templates/references/surface-model.md +61 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +66 -0
- package/template/.agent/skills/prompt-engineer/README.md +659 -0
- package/template/.agent/skills/prompt-engineer/SKILL.md +249 -0
- package/template/.agent/skills/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +278 -0
- package/template/.agent/skills/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skills/security-scanning-security-hardening/SKILL.md +231 -0
- package/template/.agent/skills/session-continuity/SKILL.md +730 -0
- package/template/.agent/skills/session-continuity/protocols/01-session-resumption.md +38 -0
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +85 -0
- package/template/.agent/skills/session-continuity/protocols/03-progress-update.md +70 -0
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +60 -0
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +37 -0
- package/template/.agent/skills/session-continuity/protocols/06-decision-analysis.md +84 -0
- package/template/.agent/skills/session-continuity/protocols/07-spec-pipeline-generation.md +48 -0
- package/template/.agent/skills/session-continuity/protocols/08-spec-pipeline-update.md +43 -0
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +122 -0
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +104 -0
- package/template/.agent/skills/session-continuity/protocols/ambiguity-gates.md +48 -0
- package/template/.agent/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agent/skills/skill-creator/README.md +270 -0
- package/template/.agent/skills/skill-creator/SKILL.md +590 -0
- package/template/.agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/template/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/template/.agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/template/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/template/.agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/template/.agent/skills/spec-writing/SKILL.md +110 -0
- package/template/.agent/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +297 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/template/.agent/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/template/.agent/skills/systematic-debugging/find-polluter.sh +63 -0
- package/template/.agent/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/template/.agent/skills/systematic-debugging/test-academic.md +14 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.agent/skills/tech-stack-catalog/SKILL.md +49 -0
- package/template/.agent/skills/tech-stack-catalog/references/constraint-questions.md +21 -0
- package/template/.agent/skills/tech-stack-catalog/references/dev-tooling-decisions.md +37 -0
- package/template/.agent/skills/tech-stack-catalog/references/surface-decision-tables.md +69 -0
- package/template/.agent/skills/technical-writer/SKILL.md +242 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +932 -0
- package/template/.agent/skills/verification-before-completion/SKILL.md +145 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +73 -0
- package/template/.agent/workflows/audit-ambiguity-execute.md +165 -0
- package/template/.agent/workflows/audit-ambiguity-rubrics.md +83 -0
- package/template/.agent/workflows/audit-ambiguity.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +201 -0
- package/template/.agent/workflows/bootstrap-agents-provision.md +197 -0
- package/template/.agent/workflows/bootstrap-agents.md +66 -0
- package/template/.agent/workflows/create-prd-architecture.md +119 -0
- package/template/.agent/workflows/create-prd-compile.md +138 -0
- package/template/.agent/workflows/create-prd-design-system.md +135 -0
- package/template/.agent/workflows/create-prd-security.md +113 -0
- package/template/.agent/workflows/create-prd-stack.md +91 -0
- package/template/.agent/workflows/create-prd.md +168 -0
- package/template/.agent/workflows/decompose-architecture-structure.md +82 -0
- package/template/.agent/workflows/decompose-architecture-validate.md +119 -0
- package/template/.agent/workflows/decompose-architecture.md +111 -0
- package/template/.agent/workflows/evolve-contract.md +98 -0
- package/template/.agent/workflows/evolve-feature-cascade.md +140 -0
- package/template/.agent/workflows/evolve-feature-classify.md +116 -0
- package/template/.agent/workflows/evolve-feature.md +56 -0
- package/template/.agent/workflows/ideate-discover.md +144 -0
- package/template/.agent/workflows/ideate-extract.md +129 -0
- package/template/.agent/workflows/ideate-validate.md +117 -0
- package/template/.agent/workflows/ideate.md +113 -0
- package/template/.agent/workflows/implement-slice-setup.md +113 -0
- package/template/.agent/workflows/implement-slice-tdd.md +198 -0
- package/template/.agent/workflows/implement-slice.md +50 -0
- package/template/.agent/workflows/plan-phase.md +202 -0
- package/template/.agent/workflows/propagate-decision-apply.md +135 -0
- package/template/.agent/workflows/propagate-decision-scan.md +147 -0
- package/template/.agent/workflows/propagate-decision.md +56 -0
- package/template/.agent/workflows/remediate-pipeline-assess.md +138 -0
- package/template/.agent/workflows/remediate-pipeline-execute.md +135 -0
- package/template/.agent/workflows/remediate-pipeline.md +55 -0
- package/template/.agent/workflows/resolve-ambiguity.md +82 -0
- package/template/.agent/workflows/sync-kit.md +209 -0
- package/template/.agent/workflows/update-architecture-map.md +74 -0
- package/template/.agent/workflows/validate-phase.md +219 -0
- package/template/.agent/workflows/verify-infrastructure.md +207 -0
- package/template/.agent/workflows/write-architecture-spec-deepen.md +139 -0
- package/template/.agent/workflows/write-architecture-spec-design.md +202 -0
- package/template/.agent/workflows/write-architecture-spec.md +63 -0
- package/template/.agent/workflows/write-be-spec-classify.md +165 -0
- package/template/.agent/workflows/write-be-spec-write.md +98 -0
- package/template/.agent/workflows/write-be-spec.md +76 -0
- package/template/.agent/workflows/write-fe-spec-classify.md +170 -0
- package/template/.agent/workflows/write-fe-spec-write.md +94 -0
- package/template/.agent/workflows/write-fe-spec.md +71 -0
- package/template/AGENTS.md +176 -0
- package/template/GEMINI.md +177 -0
- package/template/docs/README.md +187 -0
- package/template/docs/audits/.gitkeep +0 -0
- package/template/docs/audits/README.md +10 -0
- package/template/docs/plans/.gitkeep +0 -0
- package/template/docs/plans/README.md +21 -0
- package/template/docs/plans/be/.gitkeep +0 -0
- package/template/docs/plans/be/README.md +11 -0
- package/template/docs/plans/fe/.gitkeep +0 -0
- package/template/docs/plans/fe/README.md +11 -0
- package/template/docs/plans/ia/.gitkeep +0 -0
- package/template/docs/plans/ia/README.md +17 -0
- package/template/docs/plans/ia/deep-dives/.gitkeep +0 -0
- package/template/docs/plans/ia/deep-dives/README.md +5 -0
- package/template/docs/plans/phases/.gitkeep +0 -0
- package/template/docs/plans/phases/README.md +11 -0
|
@@ -0,0 +1,1061 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql
|
|
3
|
+
description: "Comprehensive GraphQL development guide covering schema design, resolvers, the N+1 problem with DataLoader, code-first vs schema-first approaches, Apollo Server and Client, error handling, pagination, authentication and authorization, file uploads, subscriptions, caching strategies, federation, input validation, depth limiting, query complexity analysis, testing, and codegen. Use when building or consuming GraphQL APIs, designing schemas, or optimizing GraphQL performance."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GraphQL
|
|
8
|
+
|
|
9
|
+
## 1. Philosophy
|
|
10
|
+
|
|
11
|
+
GraphQL is a **query language for APIs** that gives clients the power to ask for exactly the data they need. Unlike REST, where the server dictates the response shape, GraphQL lets the client define it.
|
|
12
|
+
|
|
13
|
+
**Key principles**:
|
|
14
|
+
- The schema is the contract. Every field, type, and relationship is explicitly defined.
|
|
15
|
+
- Clients ask for what they need. No over-fetching, no under-fetching.
|
|
16
|
+
- One endpoint, many queries. No URL-per-resource pattern.
|
|
17
|
+
- Strong typing. Every field has a type. The schema is self-documenting.
|
|
18
|
+
- The N+1 problem is your responsibility. GraphQL does not solve it -- DataLoader does.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Schema Design
|
|
23
|
+
|
|
24
|
+
### Types
|
|
25
|
+
|
|
26
|
+
```graphql
|
|
27
|
+
# Object types
|
|
28
|
+
type User {
|
|
29
|
+
id: ID!
|
|
30
|
+
email: String!
|
|
31
|
+
name: String!
|
|
32
|
+
avatar: String
|
|
33
|
+
role: UserRole!
|
|
34
|
+
posts(first: Int, after: String): PostConnection!
|
|
35
|
+
createdAt: DateTime!
|
|
36
|
+
updatedAt: DateTime!
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Enum types
|
|
40
|
+
enum UserRole {
|
|
41
|
+
ADMIN
|
|
42
|
+
MODERATOR
|
|
43
|
+
USER
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Custom scalars
|
|
47
|
+
scalar DateTime
|
|
48
|
+
scalar EmailAddress
|
|
49
|
+
|
|
50
|
+
# Interface types
|
|
51
|
+
interface Node {
|
|
52
|
+
id: ID!
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Timestamped {
|
|
56
|
+
createdAt: DateTime!
|
|
57
|
+
updatedAt: DateTime!
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Union types
|
|
61
|
+
union SearchResult = User | Post | Comment
|
|
62
|
+
|
|
63
|
+
type Post implements Node & Timestamped {
|
|
64
|
+
id: ID!
|
|
65
|
+
title: String!
|
|
66
|
+
content: String!
|
|
67
|
+
author: User!
|
|
68
|
+
tags: [String!]!
|
|
69
|
+
status: PostStatus!
|
|
70
|
+
createdAt: DateTime!
|
|
71
|
+
updatedAt: DateTime!
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Queries
|
|
76
|
+
|
|
77
|
+
```graphql
|
|
78
|
+
type Query {
|
|
79
|
+
# Single resource by ID
|
|
80
|
+
user(id: ID!): User
|
|
81
|
+
post(id: ID!): Post
|
|
82
|
+
|
|
83
|
+
# List with pagination and filtering
|
|
84
|
+
users(
|
|
85
|
+
first: Int
|
|
86
|
+
after: String
|
|
87
|
+
filter: UserFilter
|
|
88
|
+
orderBy: UserOrderBy
|
|
89
|
+
): UserConnection!
|
|
90
|
+
|
|
91
|
+
posts(
|
|
92
|
+
first: Int
|
|
93
|
+
after: String
|
|
94
|
+
filter: PostFilter
|
|
95
|
+
): PostConnection!
|
|
96
|
+
|
|
97
|
+
# Search across multiple types
|
|
98
|
+
search(query: String!, types: [SearchType!]): [SearchResult!]!
|
|
99
|
+
|
|
100
|
+
# Current authenticated user
|
|
101
|
+
me: User
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Mutations
|
|
106
|
+
|
|
107
|
+
```graphql
|
|
108
|
+
type Mutation {
|
|
109
|
+
# Create
|
|
110
|
+
createPost(input: CreatePostInput!): CreatePostPayload!
|
|
111
|
+
|
|
112
|
+
# Update
|
|
113
|
+
updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!
|
|
114
|
+
|
|
115
|
+
# Delete
|
|
116
|
+
deletePost(id: ID!): DeletePostPayload!
|
|
117
|
+
|
|
118
|
+
# Authentication
|
|
119
|
+
signIn(input: SignInInput!): AuthPayload!
|
|
120
|
+
signUp(input: SignUpInput!): AuthPayload!
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Input types -- always use input types for mutation arguments
|
|
124
|
+
input CreatePostInput {
|
|
125
|
+
title: String!
|
|
126
|
+
content: String!
|
|
127
|
+
tags: [String!]
|
|
128
|
+
status: PostStatus
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
input UpdatePostInput {
|
|
132
|
+
title: String
|
|
133
|
+
content: String
|
|
134
|
+
tags: [String!]
|
|
135
|
+
status: PostStatus
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Payload types -- always return a payload, not the raw type
|
|
139
|
+
type CreatePostPayload {
|
|
140
|
+
post: Post
|
|
141
|
+
errors: [UserError!]!
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type UserError {
|
|
145
|
+
field: String
|
|
146
|
+
message: String!
|
|
147
|
+
code: ErrorCode!
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Subscriptions
|
|
152
|
+
|
|
153
|
+
```graphql
|
|
154
|
+
type Subscription {
|
|
155
|
+
postCreated: Post!
|
|
156
|
+
postUpdated(id: ID!): Post!
|
|
157
|
+
commentAdded(postId: ID!): Comment!
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 3. Resolvers
|
|
164
|
+
|
|
165
|
+
### Basic Resolver Structure
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// resolvers/user.ts
|
|
169
|
+
import type { Resolvers } from "../generated/graphql";
|
|
170
|
+
|
|
171
|
+
export const userResolvers: Resolvers = {
|
|
172
|
+
Query: {
|
|
173
|
+
user: async (_parent, { id }, context) => {
|
|
174
|
+
return context.dataSources.users.findById(id);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
me: async (_parent, _args, context) => {
|
|
178
|
+
if (!context.currentUser) return null;
|
|
179
|
+
return context.dataSources.users.findById(context.currentUser.id);
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
users: async (_parent, { first, after, filter }, context) => {
|
|
183
|
+
return context.dataSources.users.findMany({ first, after, filter });
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
User: {
|
|
188
|
+
// Field resolver -- called for each User object
|
|
189
|
+
posts: async (parent, { first, after }, context) => {
|
|
190
|
+
return context.dataSources.posts.findByAuthorId(parent.id, { first, after });
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Computed field
|
|
194
|
+
avatar: (parent) => {
|
|
195
|
+
return parent.avatar || `https://api.dicebear.com/7.x/initials/svg?seed=${parent.name}`;
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
Mutation: {
|
|
200
|
+
createPost: async (_parent, { input }, context) => {
|
|
201
|
+
if (!context.currentUser) {
|
|
202
|
+
return {
|
|
203
|
+
post: null,
|
|
204
|
+
errors: [{ message: "Authentication required", code: "UNAUTHENTICATED" }],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const post = await context.dataSources.posts.create({
|
|
209
|
+
...input,
|
|
210
|
+
authorId: context.currentUser.id,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return { post, errors: [] };
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 4. The N+1 Problem and DataLoader
|
|
222
|
+
|
|
223
|
+
### The Problem
|
|
224
|
+
|
|
225
|
+
```graphql
|
|
226
|
+
# This query causes N+1 database queries:
|
|
227
|
+
query {
|
|
228
|
+
posts(first: 10) { # 1 query: SELECT * FROM posts LIMIT 10
|
|
229
|
+
edges {
|
|
230
|
+
node {
|
|
231
|
+
title
|
|
232
|
+
author { # 10 queries: SELECT * FROM users WHERE id = ?
|
|
233
|
+
name # (one for each post)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### The Solution: DataLoader
|
|
242
|
+
|
|
243
|
+
DataLoader batches and deduplicates data fetching within a single request.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// dataloaders/user-loader.ts
|
|
247
|
+
import DataLoader from "dataloader";
|
|
248
|
+
import type { User } from "../types";
|
|
249
|
+
|
|
250
|
+
export function createUserLoader(db: Database) {
|
|
251
|
+
return new DataLoader<string, User | null>(async (userIds) => {
|
|
252
|
+
// Single batch query instead of N individual queries
|
|
253
|
+
const users = await db.query(
|
|
254
|
+
"SELECT * FROM users WHERE id = ANY($1)",
|
|
255
|
+
[userIds as string[]]
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// DataLoader requires results in the same order as the input keys
|
|
259
|
+
const userMap = new Map(users.map((u) => [u.id, u]));
|
|
260
|
+
return userIds.map((id) => userMap.get(id) || null);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// context.ts -- create a new DataLoader per request
|
|
265
|
+
export function createContext({ req }: { req: Request }) {
|
|
266
|
+
return {
|
|
267
|
+
currentUser: getUserFromToken(req),
|
|
268
|
+
loaders: {
|
|
269
|
+
user: createUserLoader(db),
|
|
270
|
+
post: createPostLoader(db),
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// resolvers -- use the loader instead of direct DB queries
|
|
276
|
+
const resolvers = {
|
|
277
|
+
Post: {
|
|
278
|
+
author: (parent, _args, context) => {
|
|
279
|
+
// Batched: all author lookups in this request are combined
|
|
280
|
+
return context.loaders.user.load(parent.authorId);
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### DataLoader Rules
|
|
287
|
+
|
|
288
|
+
| Rule | Why |
|
|
289
|
+
|------|-----|
|
|
290
|
+
| Create a new instance per request | DataLoader caches results -- reusing across requests serves stale data |
|
|
291
|
+
| Return results in input order | DataLoader maps results by position, not by key |
|
|
292
|
+
| Return null for missing items | Do not throw -- return null and let the resolver handle it |
|
|
293
|
+
| Keep batch functions focused | One DataLoader per entity type |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 5. Code-First vs Schema-First
|
|
298
|
+
|
|
299
|
+
### Schema-First
|
|
300
|
+
|
|
301
|
+
Write the schema in SDL (Schema Definition Language), then implement resolvers.
|
|
302
|
+
|
|
303
|
+
```graphql
|
|
304
|
+
# schema.graphql -- the source of truth
|
|
305
|
+
type Query {
|
|
306
|
+
user(id: ID!): User
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
type User {
|
|
310
|
+
id: ID!
|
|
311
|
+
name: String!
|
|
312
|
+
email: String!
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// resolvers.ts -- must match the schema exactly
|
|
318
|
+
const resolvers = {
|
|
319
|
+
Query: {
|
|
320
|
+
user: (_, { id }) => db.users.findById(id),
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Pros**: Schema is readable, serves as documentation, frontend can work from the schema before resolvers exist.
|
|
326
|
+
|
|
327
|
+
**Cons**: Schema and resolvers can drift. No type safety between them without codegen.
|
|
328
|
+
|
|
329
|
+
### Code-First
|
|
330
|
+
|
|
331
|
+
Define the schema programmatically with a library like Pothos, Nexus, or TypeGraphQL.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// schema.ts -- types and resolvers in one place
|
|
335
|
+
import SchemaBuilder from "@pothos/core";
|
|
336
|
+
|
|
337
|
+
const builder = new SchemaBuilder({});
|
|
338
|
+
|
|
339
|
+
builder.objectType("User", {
|
|
340
|
+
fields: (t) => ({
|
|
341
|
+
id: t.exposeID("id"),
|
|
342
|
+
name: t.exposeString("name"),
|
|
343
|
+
email: t.exposeString("email"),
|
|
344
|
+
posts: t.field({
|
|
345
|
+
type: [Post],
|
|
346
|
+
resolve: (user, _args, context) =>
|
|
347
|
+
context.loaders.postsByAuthor.load(user.id),
|
|
348
|
+
}),
|
|
349
|
+
}),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
builder.queryType({
|
|
353
|
+
fields: (t) => ({
|
|
354
|
+
user: t.field({
|
|
355
|
+
type: "User",
|
|
356
|
+
nullable: true,
|
|
357
|
+
args: { id: t.arg.id({ required: true }) },
|
|
358
|
+
resolve: (_root, { id }, context) => context.loaders.user.load(id),
|
|
359
|
+
}),
|
|
360
|
+
}),
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
export const schema = builder.toSchema();
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Pros**: Full type safety, no drift between schema and resolvers, refactoring support from IDE.
|
|
367
|
+
|
|
368
|
+
**Cons**: Schema is harder to read at a glance, steeper learning curve.
|
|
369
|
+
|
|
370
|
+
### Recommendation
|
|
371
|
+
|
|
372
|
+
Use **schema-first with codegen** for teams where the schema serves as a contract between frontend and backend. Use **code-first** when a single team owns both and wants maximum type safety.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## 6. Apollo Server Setup
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// server.ts
|
|
380
|
+
import { ApolloServer } from "@apollo/server";
|
|
381
|
+
import { expressMiddleware } from "@apollo/server/express4";
|
|
382
|
+
import { readFileSync } from "fs";
|
|
383
|
+
import express from "express";
|
|
384
|
+
import { resolvers } from "./resolvers";
|
|
385
|
+
import { createContext } from "./context";
|
|
386
|
+
|
|
387
|
+
const typeDefs = readFileSync("./schema.graphql", "utf-8");
|
|
388
|
+
|
|
389
|
+
const server = new ApolloServer({
|
|
390
|
+
typeDefs,
|
|
391
|
+
resolvers,
|
|
392
|
+
plugins: [
|
|
393
|
+
// Disable introspection in production
|
|
394
|
+
process.env.NODE_ENV === "production"
|
|
395
|
+
? { requestDidStart: async () => ({}) }
|
|
396
|
+
: undefined,
|
|
397
|
+
].filter(Boolean),
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
await server.start();
|
|
401
|
+
|
|
402
|
+
const app = express();
|
|
403
|
+
app.use(
|
|
404
|
+
"/graphql",
|
|
405
|
+
express.json(),
|
|
406
|
+
expressMiddleware(server, {
|
|
407
|
+
context: createContext,
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
app.listen(4000);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 7. Error Handling
|
|
417
|
+
|
|
418
|
+
### User Errors vs System Errors
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
// User errors: expected, part of business logic
|
|
422
|
+
// Return them in the payload, not as GraphQL errors
|
|
423
|
+
|
|
424
|
+
type CreatePostPayload {
|
|
425
|
+
post: Post
|
|
426
|
+
errors: [UserError!]!
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// System errors: unexpected, infrastructure failures
|
|
430
|
+
// Throw them as GraphQL errors
|
|
431
|
+
|
|
432
|
+
import { GraphQLError } from "graphql";
|
|
433
|
+
|
|
434
|
+
function requireAuth(context: Context) {
|
|
435
|
+
if (!context.currentUser) {
|
|
436
|
+
throw new GraphQLError("Authentication required", {
|
|
437
|
+
extensions: { code: "UNAUTHENTICATED" },
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
return context.currentUser;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// In resolvers
|
|
444
|
+
const resolvers = {
|
|
445
|
+
Mutation: {
|
|
446
|
+
createPost: async (_, { input }, context) => {
|
|
447
|
+
const user = requireAuth(context);
|
|
448
|
+
|
|
449
|
+
// Validation -- return as user error
|
|
450
|
+
if (input.title.length < 3) {
|
|
451
|
+
return {
|
|
452
|
+
post: null,
|
|
453
|
+
errors: [
|
|
454
|
+
{ field: "title", message: "Title must be at least 3 characters", code: "VALIDATION" },
|
|
455
|
+
],
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const post = await context.dataSources.posts.create({
|
|
460
|
+
...input,
|
|
461
|
+
authorId: user.id,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
return { post, errors: [] };
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Error Formatting
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
const server = new ApolloServer({
|
|
474
|
+
typeDefs,
|
|
475
|
+
resolvers,
|
|
476
|
+
formatError: (formattedError, error) => {
|
|
477
|
+
// Never expose internal errors to clients
|
|
478
|
+
if (formattedError.extensions?.code === "INTERNAL_SERVER_ERROR") {
|
|
479
|
+
console.error("Internal error:", error);
|
|
480
|
+
return {
|
|
481
|
+
message: "An unexpected error occurred",
|
|
482
|
+
extensions: { code: "INTERNAL_SERVER_ERROR" },
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Strip stack traces in production
|
|
487
|
+
if (process.env.NODE_ENV === "production") {
|
|
488
|
+
delete formattedError.extensions?.stacktrace;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return formattedError;
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## 8. Pagination
|
|
499
|
+
|
|
500
|
+
### Cursor-Based Pagination (Relay Spec)
|
|
501
|
+
|
|
502
|
+
```graphql
|
|
503
|
+
# Connection type
|
|
504
|
+
type PostConnection {
|
|
505
|
+
edges: [PostEdge!]!
|
|
506
|
+
pageInfo: PageInfo!
|
|
507
|
+
totalCount: Int!
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
type PostEdge {
|
|
511
|
+
node: Post!
|
|
512
|
+
cursor: String!
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
type PageInfo {
|
|
516
|
+
hasNextPage: Boolean!
|
|
517
|
+
hasPreviousPage: Boolean!
|
|
518
|
+
startCursor: String
|
|
519
|
+
endCursor: String
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
// Resolver implementation
|
|
525
|
+
async function paginatePosts(
|
|
526
|
+
{ first = 20, after, filter }: PaginationArgs,
|
|
527
|
+
db: Database
|
|
528
|
+
): Promise<PostConnection> {
|
|
529
|
+
const limit = Math.min(first, 100); // Cap at 100
|
|
530
|
+
|
|
531
|
+
// Decode cursor (base64 encoded ID or offset)
|
|
532
|
+
const cursorId = after ? Buffer.from(after, "base64").toString("utf-8") : null;
|
|
533
|
+
|
|
534
|
+
const whereClause = cursorId
|
|
535
|
+
? { id: { gt: cursorId }, ...filter }
|
|
536
|
+
: { ...filter };
|
|
537
|
+
|
|
538
|
+
const posts = await db.post.findMany({
|
|
539
|
+
where: whereClause,
|
|
540
|
+
take: limit + 1, // Fetch one extra to determine hasNextPage
|
|
541
|
+
orderBy: { id: "asc" },
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const hasNextPage = posts.length > limit;
|
|
545
|
+
const edges = posts.slice(0, limit).map((post) => ({
|
|
546
|
+
node: post,
|
|
547
|
+
cursor: Buffer.from(post.id).toString("base64"),
|
|
548
|
+
}));
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
edges,
|
|
552
|
+
pageInfo: {
|
|
553
|
+
hasNextPage,
|
|
554
|
+
hasPreviousPage: !!after,
|
|
555
|
+
startCursor: edges[0]?.cursor ?? null,
|
|
556
|
+
endCursor: edges[edges.length - 1]?.cursor ?? null,
|
|
557
|
+
},
|
|
558
|
+
totalCount: await db.post.count({ where: filter }),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## 9. Authentication and Authorization
|
|
566
|
+
|
|
567
|
+
### Context-Based Auth
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
// context.ts
|
|
571
|
+
import { GraphQLError } from "graphql";
|
|
572
|
+
import { verifyToken } from "./auth";
|
|
573
|
+
|
|
574
|
+
export async function createContext({ req }: { req: Request }) {
|
|
575
|
+
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
576
|
+
let currentUser = null;
|
|
577
|
+
|
|
578
|
+
if (token) {
|
|
579
|
+
try {
|
|
580
|
+
const payload = await verifyToken(token);
|
|
581
|
+
currentUser = await db.user.findById(payload.userId);
|
|
582
|
+
} catch {
|
|
583
|
+
// Invalid token -- continue as unauthenticated
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
currentUser,
|
|
589
|
+
loaders: createLoaders(),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Directive-Based Authorization
|
|
595
|
+
|
|
596
|
+
```graphql
|
|
597
|
+
# Schema directives
|
|
598
|
+
directive @auth(requires: UserRole = USER) on FIELD_DEFINITION
|
|
599
|
+
directive @owner on FIELD_DEFINITION
|
|
600
|
+
|
|
601
|
+
type Query {
|
|
602
|
+
users: [User!]! @auth(requires: ADMIN)
|
|
603
|
+
me: User @auth
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
type Mutation {
|
|
607
|
+
updateUser(id: ID!, input: UpdateUserInput!): User @auth @owner
|
|
608
|
+
deleteUser(id: ID!): Boolean @auth(requires: ADMIN)
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
// Auth directive transformer
|
|
614
|
+
import { mapSchema, MapperKind, getDirective } from "@graphql-tools/utils";
|
|
615
|
+
|
|
616
|
+
function authDirectiveTransformer(schema: GraphQLSchema) {
|
|
617
|
+
return mapSchema(schema, {
|
|
618
|
+
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
|
|
619
|
+
const authDirective = getDirective(schema, fieldConfig, "auth")?.[0];
|
|
620
|
+
if (!authDirective) return fieldConfig;
|
|
621
|
+
|
|
622
|
+
const requiredRole = authDirective.requires || "USER";
|
|
623
|
+
const originalResolve = fieldConfig.resolve;
|
|
624
|
+
|
|
625
|
+
fieldConfig.resolve = async (source, args, context, info) => {
|
|
626
|
+
if (!context.currentUser) {
|
|
627
|
+
throw new GraphQLError("Authentication required", {
|
|
628
|
+
extensions: { code: "UNAUTHENTICATED" },
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!hasRole(context.currentUser, requiredRole)) {
|
|
633
|
+
throw new GraphQLError("Insufficient permissions", {
|
|
634
|
+
extensions: { code: "FORBIDDEN" },
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return originalResolve?.(source, args, context, info);
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
return fieldConfig;
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## 10. Caching Strategies
|
|
650
|
+
|
|
651
|
+
### Apollo Client Normalized Cache
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
|
655
|
+
|
|
656
|
+
const client = new ApolloClient({
|
|
657
|
+
uri: "/graphql",
|
|
658
|
+
cache: new InMemoryCache({
|
|
659
|
+
typePolicies: {
|
|
660
|
+
Query: {
|
|
661
|
+
fields: {
|
|
662
|
+
posts: {
|
|
663
|
+
// Merge paginated results
|
|
664
|
+
keyArgs: ["filter"],
|
|
665
|
+
merge(existing, incoming, { args }) {
|
|
666
|
+
if (!args?.after) return incoming;
|
|
667
|
+
return {
|
|
668
|
+
...incoming,
|
|
669
|
+
edges: [...(existing?.edges || []), ...incoming.edges],
|
|
670
|
+
};
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
User: {
|
|
676
|
+
// Custom cache key
|
|
677
|
+
keyFields: ["id"],
|
|
678
|
+
},
|
|
679
|
+
Post: {
|
|
680
|
+
keyFields: ["id"],
|
|
681
|
+
fields: {
|
|
682
|
+
// Field-level read policy
|
|
683
|
+
createdAt: {
|
|
684
|
+
read(value: string) {
|
|
685
|
+
return new Date(value);
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
}),
|
|
692
|
+
});
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### Server-Side Caching
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
// Cache hints in resolvers
|
|
699
|
+
const resolvers = {
|
|
700
|
+
Query: {
|
|
701
|
+
posts: async (_parent, args, context, info) => {
|
|
702
|
+
// Cache for 60 seconds, shared across users
|
|
703
|
+
info.cacheControl.setCacheHint({ maxAge: 60, scope: "PUBLIC" });
|
|
704
|
+
return context.dataSources.posts.findMany(args);
|
|
705
|
+
},
|
|
706
|
+
|
|
707
|
+
me: async (_parent, _args, context, info) => {
|
|
708
|
+
// User-specific data -- private cache
|
|
709
|
+
info.cacheControl.setCacheHint({ maxAge: 30, scope: "PRIVATE" });
|
|
710
|
+
return context.dataSources.users.findById(context.currentUser.id);
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## 11. Schema Federation
|
|
719
|
+
|
|
720
|
+
Federation splits a monolithic GraphQL schema across multiple services.
|
|
721
|
+
|
|
722
|
+
```graphql
|
|
723
|
+
# User Service
|
|
724
|
+
type User @key(fields: "id") {
|
|
725
|
+
id: ID!
|
|
726
|
+
name: String!
|
|
727
|
+
email: String!
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
type Query {
|
|
731
|
+
me: User
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
# Post Service -- extends User from another service
|
|
735
|
+
type User @key(fields: "id") {
|
|
736
|
+
id: ID!
|
|
737
|
+
posts: [Post!]!
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
type Post @key(fields: "id") {
|
|
741
|
+
id: ID!
|
|
742
|
+
title: String!
|
|
743
|
+
content: String!
|
|
744
|
+
author: User!
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
type Query {
|
|
748
|
+
post(id: ID!): Post
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
// Post Service resolver -- resolve the User reference
|
|
754
|
+
const resolvers = {
|
|
755
|
+
User: {
|
|
756
|
+
__resolveReference: async (user, context) => {
|
|
757
|
+
// Only need to resolve the fields this service owns
|
|
758
|
+
return { id: user.id }; // id comes from the reference
|
|
759
|
+
},
|
|
760
|
+
posts: async (user, _args, context) => {
|
|
761
|
+
return context.dataSources.posts.findByAuthorId(user.id);
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
};
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
## 12. Security: Depth and Complexity Limiting
|
|
770
|
+
|
|
771
|
+
### Depth Limiting
|
|
772
|
+
|
|
773
|
+
Prevent deeply nested queries that could cause exponential resolver execution.
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
import depthLimit from "graphql-depth-limit";
|
|
777
|
+
|
|
778
|
+
const server = new ApolloServer({
|
|
779
|
+
typeDefs,
|
|
780
|
+
resolvers,
|
|
781
|
+
validationRules: [depthLimit(7)], // Max 7 levels deep
|
|
782
|
+
});
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
### Query Complexity Analysis
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
import { createComplexityLimitRule } from "graphql-validation-complexity";
|
|
789
|
+
|
|
790
|
+
const ComplexityLimit = createComplexityLimitRule(1000, {
|
|
791
|
+
scalarCost: 1,
|
|
792
|
+
objectCost: 2,
|
|
793
|
+
listFactor: 10,
|
|
794
|
+
|
|
795
|
+
onCost: (cost) => {
|
|
796
|
+
console.log("Query complexity:", cost);
|
|
797
|
+
},
|
|
798
|
+
|
|
799
|
+
formatErrorMessage: (cost) =>
|
|
800
|
+
`Query complexity ${cost} exceeds maximum allowed complexity of 1000`,
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
const server = new ApolloServer({
|
|
804
|
+
typeDefs,
|
|
805
|
+
resolvers,
|
|
806
|
+
validationRules: [depthLimit(7), ComplexityLimit],
|
|
807
|
+
});
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Persisted Queries
|
|
811
|
+
|
|
812
|
+
Lock down the API to only allow pre-registered queries in production.
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
import { ApolloServerPluginPersistedQueries } from "@apollo/server";
|
|
816
|
+
|
|
817
|
+
const server = new ApolloServer({
|
|
818
|
+
typeDefs,
|
|
819
|
+
resolvers,
|
|
820
|
+
plugins: [
|
|
821
|
+
ApolloServerPluginPersistedQueries({
|
|
822
|
+
// Only allow persisted queries in production
|
|
823
|
+
...(process.env.NODE_ENV === "production" && {
|
|
824
|
+
rejectUnpersistedQueries: true,
|
|
825
|
+
}),
|
|
826
|
+
}),
|
|
827
|
+
],
|
|
828
|
+
});
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## 13. Testing
|
|
834
|
+
|
|
835
|
+
### Resolver Unit Tests
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
import { describe, it, expect, vi } from "vitest";
|
|
839
|
+
import { resolvers } from "./resolvers";
|
|
840
|
+
|
|
841
|
+
describe("Query.user", () => {
|
|
842
|
+
it("returns user by ID", async () => {
|
|
843
|
+
const mockUser = { id: "1", name: "Alice", email: "alice@example.com" };
|
|
844
|
+
const context = {
|
|
845
|
+
dataSources: {
|
|
846
|
+
users: { findById: vi.fn().mockResolvedValue(mockUser) },
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const result = await resolvers.Query.user(null, { id: "1" }, context, {} as any);
|
|
851
|
+
expect(result).toEqual(mockUser);
|
|
852
|
+
expect(context.dataSources.users.findById).toHaveBeenCalledWith("1");
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
it("returns null for non-existent user", async () => {
|
|
856
|
+
const context = {
|
|
857
|
+
dataSources: {
|
|
858
|
+
users: { findById: vi.fn().mockResolvedValue(null) },
|
|
859
|
+
},
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const result = await resolvers.Query.user(null, { id: "999" }, context, {} as any);
|
|
863
|
+
expect(result).toBeNull();
|
|
864
|
+
});
|
|
865
|
+
});
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Integration Tests with Apollo Server
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
import { ApolloServer } from "@apollo/server";
|
|
872
|
+
import { describe, it, expect, beforeAll } from "vitest";
|
|
873
|
+
|
|
874
|
+
describe("GraphQL API", () => {
|
|
875
|
+
let server: ApolloServer;
|
|
876
|
+
|
|
877
|
+
beforeAll(async () => {
|
|
878
|
+
server = new ApolloServer({ typeDefs, resolvers });
|
|
879
|
+
await server.start();
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
it("fetches a user with posts", async () => {
|
|
883
|
+
const response = await server.executeOperation({
|
|
884
|
+
query: `
|
|
885
|
+
query GetUser($id: ID!) {
|
|
886
|
+
user(id: $id) {
|
|
887
|
+
id
|
|
888
|
+
name
|
|
889
|
+
posts(first: 5) {
|
|
890
|
+
edges {
|
|
891
|
+
node {
|
|
892
|
+
title
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
`,
|
|
899
|
+
variables: { id: "1" },
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
expect(response.body.kind).toBe("single");
|
|
903
|
+
if (response.body.kind === "single") {
|
|
904
|
+
expect(response.body.singleResult.errors).toBeUndefined();
|
|
905
|
+
expect(response.body.singleResult.data?.user?.name).toBeDefined();
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it("returns error for unauthenticated mutation", async () => {
|
|
910
|
+
const response = await server.executeOperation({
|
|
911
|
+
query: `
|
|
912
|
+
mutation {
|
|
913
|
+
createPost(input: { title: "Test", content: "Body" }) {
|
|
914
|
+
post { id }
|
|
915
|
+
errors { message code }
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
`,
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
if (response.body.kind === "single") {
|
|
922
|
+
const payload = response.body.singleResult.data?.createPost;
|
|
923
|
+
expect(payload.post).toBeNull();
|
|
924
|
+
expect(payload.errors[0].code).toBe("UNAUTHENTICATED");
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
---
|
|
931
|
+
|
|
932
|
+
## 14. Code Generation
|
|
933
|
+
|
|
934
|
+
### graphql-codegen Setup
|
|
935
|
+
|
|
936
|
+
```yaml
|
|
937
|
+
# codegen.ts
|
|
938
|
+
import type { CodegenConfig } from "@graphql-codegen/cli";
|
|
939
|
+
|
|
940
|
+
const config: CodegenConfig = {
|
|
941
|
+
schema: "./src/schema.graphql",
|
|
942
|
+
documents: "./src/**/*.graphql",
|
|
943
|
+
generates: {
|
|
944
|
+
# Server-side types
|
|
945
|
+
"./src/generated/graphql.ts": {
|
|
946
|
+
plugins: [
|
|
947
|
+
"typescript",
|
|
948
|
+
"typescript-resolvers",
|
|
949
|
+
],
|
|
950
|
+
config: {
|
|
951
|
+
contextType: "../context#Context",
|
|
952
|
+
mapperTypeSuffix: "Model",
|
|
953
|
+
mappers: {
|
|
954
|
+
User: "../models/user#UserModel",
|
|
955
|
+
Post: "../models/post#PostModel",
|
|
956
|
+
},
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
# Client-side types and hooks
|
|
960
|
+
"./src/generated/client.ts": {
|
|
961
|
+
plugins: [
|
|
962
|
+
"typescript",
|
|
963
|
+
"typescript-operations",
|
|
964
|
+
"typescript-react-apollo",
|
|
965
|
+
],
|
|
966
|
+
config: {
|
|
967
|
+
withHooks: true,
|
|
968
|
+
withComponent: false,
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
export default config;
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
```bash
|
|
978
|
+
# Run codegen
|
|
979
|
+
npx graphql-codegen
|
|
980
|
+
|
|
981
|
+
# Watch mode
|
|
982
|
+
npx graphql-codegen --watch
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
### Using Generated Types
|
|
986
|
+
|
|
987
|
+
```typescript
|
|
988
|
+
// Server -- resolvers are fully typed
|
|
989
|
+
import type { Resolvers } from "./generated/graphql";
|
|
990
|
+
|
|
991
|
+
const resolvers: Resolvers = {
|
|
992
|
+
Query: {
|
|
993
|
+
// TypeScript enforces correct return types and argument types
|
|
994
|
+
user: async (_parent, { id }, context) => {
|
|
995
|
+
return context.dataSources.users.findById(id);
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// Client -- hooks are fully typed
|
|
1001
|
+
import { useGetUserQuery } from "./generated/client";
|
|
1002
|
+
|
|
1003
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
1004
|
+
// data is typed as GetUserQuery
|
|
1005
|
+
const { data, loading, error } = useGetUserQuery({
|
|
1006
|
+
variables: { id: userId },
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
if (loading) return <Spinner />;
|
|
1010
|
+
if (error) return <Error message={error.message} />;
|
|
1011
|
+
// data.user is fully typed
|
|
1012
|
+
return <div>{data?.user?.name}</div>;
|
|
1013
|
+
}
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
---
|
|
1017
|
+
|
|
1018
|
+
## 15. Anti-Patterns
|
|
1019
|
+
|
|
1020
|
+
### NEVER
|
|
1021
|
+
|
|
1022
|
+
- Expose database models directly as GraphQL types -- create separate GraphQL types
|
|
1023
|
+
- Return raw database errors to clients -- wrap them in user-friendly messages
|
|
1024
|
+
- Skip DataLoader -- every relationship resolver must use batching
|
|
1025
|
+
- Allow unlimited query depth or complexity -- enforce limits
|
|
1026
|
+
- Use REST-style naming (`getUser`, `listPosts`) -- use `user`, `posts`
|
|
1027
|
+
- Put business logic in resolvers -- resolvers orchestrate, services compute
|
|
1028
|
+
- Ignore nullability -- every field should be explicitly nullable or non-nullable
|
|
1029
|
+
- Use `ID` type for non-identifier fields -- `ID` is for unique identifiers only
|
|
1030
|
+
- Return different shapes for the same error in different mutations
|
|
1031
|
+
- Skip input validation because "the schema handles types"
|
|
1032
|
+
|
|
1033
|
+
### ALWAYS
|
|
1034
|
+
|
|
1035
|
+
- Use input types for mutation arguments
|
|
1036
|
+
- Return payload types from mutations (not raw entities)
|
|
1037
|
+
- Implement cursor-based pagination for list fields
|
|
1038
|
+
- Use DataLoader for all relationship fields
|
|
1039
|
+
- Generate TypeScript types from the schema
|
|
1040
|
+
- Version your API through schema evolution, not URL versioning
|
|
1041
|
+
- Document deprecated fields with `@deprecated(reason: "...")` before removal
|
|
1042
|
+
- Set cache hints on resolvers that return public data
|
|
1043
|
+
- Test both happy paths and error paths in resolver tests
|
|
1044
|
+
- Validate inputs beyond what the schema enforces (string length, format, business rules)
|
|
1045
|
+
|
|
1046
|
+
---
|
|
1047
|
+
|
|
1048
|
+
## 16. Schema Design Checklist
|
|
1049
|
+
|
|
1050
|
+
Before shipping a new type or field:
|
|
1051
|
+
|
|
1052
|
+
- [ ] All fields have explicit nullability (`!` for non-nullable)
|
|
1053
|
+
- [ ] List fields use pagination (not unbounded arrays)
|
|
1054
|
+
- [ ] Mutations return payload types with an `errors` field
|
|
1055
|
+
- [ ] Input types are used for all mutation arguments
|
|
1056
|
+
- [ ] Relationships use DataLoader in resolvers
|
|
1057
|
+
- [ ] Depth and complexity limits are configured
|
|
1058
|
+
- [ ] Generated types are up to date
|
|
1059
|
+
- [ ] Resolver tests cover happy path, error path, and auth
|
|
1060
|
+
- [ ] Schema documentation is written with descriptions
|
|
1061
|
+
- [ ] Deprecated fields have migration paths documented
|