claude-code-superkit 0.0.0-stage → 1.5.3
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/LICENSE +21 -0
- package/README.md +374 -2
- package/bin/cli.js +153 -0
- package/bin/inject-tokens.js +224 -0
- package/bin/measure-tokens.js +17 -0
- package/bin/superkit-counts-verify.sh +30 -0
- package/lib/codex.js +246 -0
- package/lib/docs-scaffold.js +147 -0
- package/lib/installer.js +499 -0
- package/lib/prompts.js +51 -0
- package/lib/settings-builder.js +51 -0
- package/lib/superpowers.js +99 -0
- package/lib/utils.js +117 -0
- package/lib/validator.js +84 -0
- package/package.json +49 -4
- package/packages/codex/AGENTS.md +396 -0
- package/packages/codex/INSTALL.md +342 -0
- package/packages/codex/config.toml +22 -0
- package/packages/codex/hooks/superkit.mjs +203 -0
- package/packages/codex/hooks.json +30 -0
- package/packages/codex/legacy-baseline.json +88 -0
- package/packages/codex/native-skills.txt +65 -0
- package/packages/codex/rules/default.rules +565 -0
- package/packages/codex/skills/ai-slop-cleaner/SKILL.md +44 -0
- package/packages/codex/skills/api-contract-sync/SKILL.md +96 -0
- package/packages/codex/skills/architect/SKILL.md +49 -0
- package/packages/codex/skills/audit-backend/SKILL.md +80 -0
- package/packages/codex/skills/audit-frontend/SKILL.md +82 -0
- package/packages/codex/skills/audit-infra/SKILL.md +87 -0
- package/packages/codex/skills/audit-orchestrator/SKILL.md +114 -0
- package/packages/codex/skills/behavioral-nudge-engine/SKILL.md +110 -0
- package/packages/codex/skills/benchmark/SKILL.md +49 -0
- package/packages/codex/skills/bot-reviewer/SKILL.md +94 -0
- package/packages/codex/skills/code-reviewer/SKILL.md +67 -0
- package/packages/codex/skills/codebase-onboarding-engineer/SKILL.md +46 -0
- package/packages/codex/skills/comment-rot-analyzer/SKILL.md +75 -0
- package/packages/codex/skills/commit-helper/SKILL.md +101 -0
- package/packages/codex/skills/critic/SKILL.md +56 -0
- package/packages/codex/skills/database-reviewer/SKILL.md +79 -0
- package/packages/codex/skills/debug-observer/SKILL.md +107 -0
- package/packages/codex/skills/dependency-checker/SKILL.md +69 -0
- package/packages/codex/skills/design-system-reviewer/SKILL.md +121 -0
- package/packages/codex/skills/dev-orchestrator/SKILL.md +304 -0
- package/packages/codex/skills/docs-reviewer/SKILL.md +86 -0
- package/packages/codex/skills/drizzle-orm-expert/SKILL.md +246 -0
- package/packages/codex/skills/e2e-test-generator/SKILL.md +95 -0
- package/packages/codex/skills/evaluator/SKILL.md +63 -0
- package/packages/codex/skills/frontend-perf-reviewer/SKILL.md +95 -0
- package/packages/codex/skills/frontend-ui-color-reviewer/SKILL.md +68 -0
- package/packages/codex/skills/frontend-ui-design-critic/SKILL.md +99 -0
- package/packages/codex/skills/frontend-ui-interaction-reviewer/SKILL.md +102 -0
- package/packages/codex/skills/frontend-ui-motion-reviewer/SKILL.md +85 -0
- package/packages/codex/skills/frontend-ui-reviewer/SKILL.md +81 -0
- package/packages/codex/skills/frontend-ui-typography-reviewer/SKILL.md +62 -0
- package/packages/codex/skills/gltf-debugging/SKILL.md +128 -0
- package/packages/codex/skills/go-benchmark/SKILL.md +217 -0
- package/packages/codex/skills/go-concurrency-reviewer/SKILL.md +113 -0
- package/packages/codex/skills/go-error-reviewer/SKILL.md +107 -0
- package/packages/codex/skills/go-grpc-patterns/SKILL.md +285 -0
- package/packages/codex/skills/go-modernizer/SKILL.md +111 -0
- package/packages/codex/skills/go-observability-reviewer/SKILL.md +121 -0
- package/packages/codex/skills/go-performance-reviewer/SKILL.md +112 -0
- package/packages/codex/skills/go-reviewer/SKILL.md +109 -0
- package/packages/codex/skills/go-samber-do/SKILL.md +158 -0
- package/packages/codex/skills/go-samber-lo/SKILL.md +178 -0
- package/packages/codex/skills/go-samber-oops/SKILL.md +174 -0
- package/packages/codex/skills/goal-verifier/SKILL.md +53 -0
- package/packages/codex/skills/health-checker/SKILL.md +109 -0
- package/packages/codex/skills/html-to-3d-texture/SKILL.md +153 -0
- package/packages/codex/skills/impeccable-craft/SKILL.md +101 -0
- package/packages/codex/skills/lint-runner/SKILL.md +85 -0
- package/packages/codex/skills/migrate/SKILL.md +60 -0
- package/packages/codex/skills/migration-reviewer/SKILL.md +88 -0
- package/packages/codex/skills/minimal-change-engineer/SKILL.md +44 -0
- package/packages/codex/skills/new-migration/SKILL.md +59 -0
- package/packages/codex/skills/nextjs-supabase-auth/SKILL.md +227 -0
- package/packages/codex/skills/output-enforcement/SKILL.md +71 -0
- package/packages/codex/skills/plan-checker/SKILL.md +53 -0
- package/packages/codex/skills/postgresql-optimization/SKILL.md +159 -0
- package/packages/codex/skills/pre-deploy-validator/SKILL.md +111 -0
- package/packages/codex/skills/presentation-reviewer/SKILL.md +88 -0
- package/packages/codex/skills/product-3d-lighting/SKILL.md +137 -0
- package/packages/codex/skills/project-architecture/SKILL.md +52 -0
- package/packages/codex/skills/py-reviewer/SKILL.md +100 -0
- package/packages/codex/skills/r3f-scene-reviewer/SKILL.md +88 -0
- package/packages/codex/skills/r3f-scroll-driven-3d/SKILL.md +162 -0
- package/packages/codex/skills/reality-checker/SKILL.md +61 -0
- package/packages/codex/skills/redis-patterns/SKILL.md +260 -0
- package/packages/codex/skills/review-orchestrator/SKILL.md +151 -0
- package/packages/codex/skills/rs-reviewer/SKILL.md +98 -0
- package/packages/codex/skills/ru-text/SKILL.md +132 -0
- package/packages/codex/skills/scaffold-endpoint/SKILL.md +44 -0
- package/packages/codex/skills/security-scanner/SKILL.md +94 -0
- package/packages/codex/skills/silent-failure-hunter/SKILL.md +176 -0
- package/packages/codex/skills/telegram-bot-builder/SKILL.md +120 -0
- package/packages/codex/skills/test-generator/SKILL.md +79 -0
- package/packages/codex/skills/test-runner/SKILL.md +101 -0
- package/packages/codex/skills/threejs-color-management/SKILL.md +97 -0
- package/packages/codex/skills/tree-generator/SKILL.md +63 -0
- package/packages/codex/skills/ts-reviewer/SKILL.md +90 -0
- package/packages/codex/skills/ui-design-reviewer/SKILL.md +89 -0
- package/packages/codex/skills/ui-reviewer/SKILL.md +103 -0
- package/packages/codex/skills/visual-reviewer/SKILL.md +85 -0
- package/packages/codex/skills/writing-agents/SKILL.md +165 -0
- package/packages/codex/skills/writing-commands/SKILL.md +173 -0
- package/packages/core/CLAUDE.md +118 -0
- package/packages/core/INTERNAL-FILES +16 -0
- package/packages/core/agents/ai-slop-cleaner.md +187 -0
- package/packages/core/agents/api-contract-sync.md +127 -0
- package/packages/core/agents/architect.md +205 -0
- package/packages/core/agents/audit-backend.md +137 -0
- package/packages/core/agents/audit-frontend.md +129 -0
- package/packages/core/agents/audit-infra.md +182 -0
- package/packages/core/agents/behavioral-nudge-engine.md +170 -0
- package/packages/core/agents/code-reviewer.md +195 -0
- package/packages/core/agents/codebase-onboarding-engineer.md +172 -0
- package/packages/core/agents/comment-rot-analyzer.md +115 -0
- package/packages/core/agents/critic.md +172 -0
- package/packages/core/agents/database-reviewer.md +201 -0
- package/packages/core/agents/debug-observer.md +261 -0
- package/packages/core/agents/dependency-checker.md +199 -0
- package/packages/core/agents/docs-reviewer.md +206 -0
- package/packages/core/agents/e2e-test-generator.md +261 -0
- package/packages/core/agents/evaluator.md +141 -0
- package/packages/core/agents/goal-verifier.md +130 -0
- package/packages/core/agents/health-checker.md +194 -0
- package/packages/core/agents/migration-reviewer.md +161 -0
- package/packages/core/agents/minimal-change-engineer.md +219 -0
- package/packages/core/agents/plan-checker.md +139 -0
- package/packages/core/agents/pre-deploy-validator.md +189 -0
- package/packages/core/agents/reality-checker.md +175 -0
- package/packages/core/agents/scaffold-endpoint.md +162 -0
- package/packages/core/agents/security-scanner.md +236 -0
- package/packages/core/agents/silent-failure-hunter.md +276 -0
- package/packages/core/agents/test-generator.md +146 -0
- package/packages/core/agents/tree-generator.md +141 -0
- package/packages/core/agents/ui-reviewer.md +172 -0
- package/packages/core/agents/visual-reviewer.md +211 -0
- package/packages/core/commands/audit.md +166 -0
- package/packages/core/commands/benchmark.md +130 -0
- package/packages/core/commands/commit.md +148 -0
- package/packages/core/commands/dev.md +302 -0
- package/packages/core/commands/docs-init.md +75 -0
- package/packages/core/commands/lint.md +137 -0
- package/packages/core/commands/migrate.md +146 -0
- package/packages/core/commands/new-migration.md +182 -0
- package/packages/core/commands/pair.md +209 -0
- package/packages/core/commands/review.md +195 -0
- package/packages/core/commands/security-scan.md +142 -0
- package/packages/core/commands/superkit-evolve.md +231 -0
- package/packages/core/commands/superkit-init.md +274 -0
- package/packages/core/commands/test.md +108 -0
- package/packages/core/commands/workflow.md +150 -0
- package/packages/core/docs/guide/01-getting-started.md +41 -0
- package/packages/core/docs-templates/README.md +33 -0
- package/packages/core/docs-templates/adr-template.md +62 -0
- package/packages/core/docs-templates/architecture/api-contracts.md +26 -0
- package/packages/core/docs-templates/architecture/api-reference.md +48 -0
- package/packages/core/docs-templates/architecture/auth-and-sessions.md +38 -0
- package/packages/core/docs-templates/architecture/backend-layers.md +42 -0
- package/packages/core/docs-templates/architecture/data-flow.md +26 -0
- package/packages/core/docs-templates/architecture/database-schema.md +51 -0
- package/packages/core/docs-templates/architecture/deployment.md +41 -0
- package/packages/core/docs-templates/architecture/frontend-state.md +46 -0
- package/packages/core/helpers/statusline.cjs +370 -0
- package/packages/core/hooks/CHAIN-SEMANTICS.md +84 -0
- package/packages/core/hooks/audit-settings-source.sh +98 -0
- package/packages/core/hooks/audit-trail.sh +120 -0
- package/packages/core/hooks/block-dangerous-git.sh +189 -0
- package/packages/core/hooks/bundle-import-check.sh +64 -0
- package/packages/core/hooks/compact-state-inject.sh +84 -0
- package/packages/core/hooks/config-protection.sh +68 -0
- package/packages/core/hooks/console-log-warning.sh +28 -0
- package/packages/core/hooks/context-monitor.sh +35 -0
- package/packages/core/hooks/dev-edit-counter.sh +59 -0
- package/packages/core/hooks/dev-marker-set.sh +55 -0
- package/packages/core/hooks/dev-required-on-commit.sh +426 -0
- package/packages/core/hooks/doc-check-on-commit.sh +396 -0
- package/packages/core/hooks/edit-streak-check.sh +63 -0
- package/packages/core/hooks/evolve-check.sh +78 -0
- package/packages/core/hooks/gateguard-pre-edit.sh +154 -0
- package/packages/core/hooks/gateguard-record-facts.sh +37 -0
- package/packages/core/hooks/intake-classifier.py +449 -0
- package/packages/core/hooks/lib/profile.sh +141 -0
- package/packages/core/hooks/loop-guard.sh +75 -0
- package/packages/core/hooks/migration-safety.sh +56 -0
- package/packages/core/hooks/plan-completion-gate.sh +61 -0
- package/packages/core/hooks/pre-compact-save.sh +89 -0
- package/packages/core/hooks/security-patterns.sh +119 -0
- package/packages/core/hooks/session-context-restore.sh +76 -0
- package/packages/core/hooks/subagent-stop-validate.sh +73 -0
- package/packages/core/hooks/superkit-counts-verify.sh +346 -0
- package/packages/core/hooks/superkit-update.sh +367 -0
- package/packages/core/hooks/tests/README.md +42 -0
- package/packages/core/hooks/tests/audit-trail-chain_test.sh +62 -0
- package/packages/core/hooks/tests/block-dangerous-git_test.sh +144 -0
- package/packages/core/hooks/tests/chain-semantics/h1.sh +11 -0
- package/packages/core/hooks/tests/chain-semantics/h2.sh +9 -0
- package/packages/core/hooks/tests/chain-semantics/h3.sh +11 -0
- package/packages/core/hooks/tests/chain-semantics/run-experiment.sh +65 -0
- package/packages/core/hooks/tests/chain-semantics/settings-experiment.json +15 -0
- package/packages/core/hooks/tests/counts-verify_test.sh +118 -0
- package/packages/core/hooks/tests/dev-required-on-commit_test.sh +207 -0
- package/packages/core/hooks/tests/dev-required-wip-on-main_test.sh +20 -0
- package/packages/core/hooks/tests/doc-check-on-commit_test.sh +122 -0
- package/packages/core/hooks/tests/edit-streak-check_test.sh +106 -0
- package/packages/core/hooks/tests/internal-manifest_test.sh +130 -0
- package/packages/core/hooks/tests/json-path_test.sh +108 -0
- package/packages/core/hooks/tests/mirror-invariants_test.sh +139 -0
- package/packages/core/hooks/tests/nudge-throttle_test.sh +95 -0
- package/packages/core/hooks/tests/profile-helper_test.sh +21 -0
- package/packages/core/hooks/tests/run-all.sh +47 -0
- package/packages/core/hooks/tests/session-key-helper_test.sh +17 -0
- package/packages/core/hooks/tests/superkit-update_test.sh +311 -0
- package/packages/core/hooks/tests/warn-visibility_test.sh +59 -0
- package/packages/core/hooks/user-intent-detect.sh +44 -0
- package/packages/core/hooks/verify-hooks.sh +69 -0
- package/packages/core/rules/auto-commands.md +124 -0
- package/packages/core/rules/coding-style.md +68 -0
- package/packages/core/rules/dev-workflow.md +77 -0
- package/packages/core/rules/documentation.md +106 -0
- package/packages/core/rules/frontend-aesthetics.md +53 -0
- package/packages/core/rules/git-workflow.md +14 -0
- package/packages/core/rules/security.md +29 -0
- package/packages/core/rules/superkit-integrity.md +114 -0
- package/packages/core/settings.json +229 -0
- package/packages/core/skills/drizzle-orm-expert/SKILL.md +260 -0
- package/packages/core/skills/nextjs-supabase-auth/SKILL.md +259 -0
- package/packages/core/skills/postgresql-optimization/SKILL.md +150 -0
- package/packages/core/skills/project-architecture/SKILL.md +31 -0
- package/packages/core/skills/project-scanner/SKILL.md +132 -0
- package/packages/core/skills/redis-patterns/SKILL.md +261 -0
- package/packages/core/skills/ru-text/SKILL.md +156 -0
- package/packages/core/skills/telegram-bot-builder/SKILL.md +145 -0
- package/packages/core/skills/writing-agents/SKILL.md +220 -0
- package/packages/core/skills/writing-commands/SKILL.md +176 -0
- package/packages/core/skills/writing-hooks/SKILL.md +335 -0
- package/packages/extras/bot-reviewer.md +279 -0
- package/packages/extras/design-system-reviewer.md +234 -0
- package/packages/extras/red-blue-auditor/README.md +117 -0
- package/packages/extras/red-blue-auditor/agent.md +128 -0
- package/packages/extras/red-blue-auditor/patterns/agent-config.txt +4 -0
- package/packages/extras/red-blue-auditor/patterns/hook-injection.txt +8 -0
- package/packages/extras/red-blue-auditor/patterns/mcp-risk.txt +5 -0
- package/packages/extras/red-blue-auditor/patterns/permission-abuse.txt +6 -0
- package/packages/extras/red-blue-auditor/patterns/secrets.txt +17 -0
- package/packages/extras/red-blue-auditor/scan.sh +88 -0
- package/packages/extras/skillsmp-search/SKILL.md +134 -0
- package/packages/frontend-3d/README.md +62 -0
- package/packages/frontend-3d/agents/frontend-perf-reviewer.md +164 -0
- package/packages/frontend-3d/agents/presentation-reviewer.md +146 -0
- package/packages/frontend-3d/agents/r3f-scene-reviewer.md +128 -0
- package/packages/frontend-3d/agents/ui-design-reviewer.md +169 -0
- package/packages/frontend-3d/commands/capture-screen.md +166 -0
- package/packages/frontend-3d/hooks/bundle-size-warn.sh +93 -0
- package/packages/frontend-3d/hooks/gsap-pattern-check.sh +74 -0
- package/packages/frontend-3d/hooks/r3f-color-check.sh +76 -0
- package/packages/frontend-3d/hooks/tailwind-version-guard.sh +98 -0
- package/packages/frontend-3d/rules/frontend-aesthetics-3d.md +81 -0
- package/packages/frontend-3d/rules/gsap-conventions.md +129 -0
- package/packages/frontend-3d/rules/threejs-conventions.md +116 -0
- package/packages/frontend-3d/skills/gltf-debugging/SKILL.md +145 -0
- package/packages/frontend-3d/skills/html-to-3d-texture/SKILL.md +183 -0
- package/packages/frontend-3d/skills/output-enforcement/SKILL.md +79 -0
- package/packages/frontend-3d/skills/product-3d-lighting/SKILL.md +166 -0
- package/packages/frontend-3d/skills/r3f-scroll-driven-3d/SKILL.md +209 -0
- package/packages/frontend-3d/skills/threejs-color-management/SKILL.md +111 -0
- package/packages/frontend-ui/NOTICE.md +99 -0
- package/packages/frontend-ui/README.md +59 -0
- package/packages/frontend-ui/agents/ui-color-reviewer.md +174 -0
- package/packages/frontend-ui/agents/ui-design-critic.md +198 -0
- package/packages/frontend-ui/agents/ui-interaction-reviewer.md +259 -0
- package/packages/frontend-ui/agents/ui-motion-reviewer.md +266 -0
- package/packages/frontend-ui/agents/ui-reviewer.md +251 -0
- package/packages/frontend-ui/agents/ui-typography-reviewer.md +222 -0
- package/packages/frontend-ui/hooks/tests/ui-animation-easing-check_test.sh +78 -0
- package/packages/frontend-ui/hooks/tests/ui-banned-fonts-check_test.sh +77 -0
- package/packages/frontend-ui/hooks/tests/ui-color-check_test.sh +75 -0
- package/packages/frontend-ui/hooks/ui-animation-easing-check.sh +86 -0
- package/packages/frontend-ui/hooks/ui-banned-fonts-check.sh +90 -0
- package/packages/frontend-ui/hooks/ui-color-check.sh +102 -0
- package/packages/frontend-ui/rules/color-and-contrast.md +230 -0
- package/packages/frontend-ui/rules/frontend-design-aesthetics.md +124 -0
- package/packages/frontend-ui/rules/interaction-polish.md +335 -0
- package/packages/frontend-ui/rules/motion-and-animation.md +283 -0
- package/packages/frontend-ui/rules/spatial-and-layout.md +212 -0
- package/packages/frontend-ui/rules/typography-guidelines.md +185 -0
- package/packages/frontend-ui/rules/ui-anti-patterns.md +309 -0
- package/packages/frontend-ui/settings.json +24 -0
- package/packages/frontend-ui/skills/impeccable-craft/SKILL.md +187 -0
- package/packages/gan/README.md +232 -0
- package/packages/gan/agents/gan-evaluator.md +214 -0
- package/packages/gan/agents/gan-generator.md +171 -0
- package/packages/gan/agents/gan-planner.md +206 -0
- package/packages/gan/rubrics/functionality.md +66 -0
- package/packages/gan/rubrics/ui-quality.md +77 -0
- package/packages/gan/skills/gan-evaluator/SKILL.md +91 -0
- package/packages/gan/skills/gan-generator/SKILL.md +72 -0
- package/packages/gan/skills/gan-planner/SKILL.md +93 -0
- package/packages/stack-agents/go/go-concurrency-reviewer.md +210 -0
- package/packages/stack-agents/go/go-error-reviewer.md +187 -0
- package/packages/stack-agents/go/go-modernizer.md +160 -0
- package/packages/stack-agents/go/go-observability-reviewer.md +185 -0
- package/packages/stack-agents/go/go-performance-reviewer.md +160 -0
- package/packages/stack-agents/go/go-reviewer.md +277 -0
- package/packages/stack-agents/go/references/benchmark-methodology.md +280 -0
- package/packages/stack-agents/go/references/channel-patterns.md +384 -0
- package/packages/stack-agents/go/references/cli-cobra-viper.md +190 -0
- package/packages/stack-agents/go/references/code-style.md +262 -0
- package/packages/stack-agents/go/references/context-propagation.md +317 -0
- package/packages/stack-agents/go/references/data-structures.md +315 -0
- package/packages/stack-agents/go/references/database-patterns.md +334 -0
- package/packages/stack-agents/go/references/design-patterns.md +411 -0
- package/packages/stack-agents/go/references/di-frameworks.md +146 -0
- package/packages/stack-agents/go/references/error-creation.md +251 -0
- package/packages/stack-agents/go/references/error-inspection.md +348 -0
- package/packages/stack-agents/go/references/error-wrapping.md +230 -0
- package/packages/stack-agents/go/references/gopls-driving.md +62 -0
- package/packages/stack-agents/go/references/goroutine-lifecycle.md +351 -0
- package/packages/stack-agents/go/references/graphql-patterns.md +221 -0
- package/packages/stack-agents/go/references/grpc-patterns.md +373 -0
- package/packages/stack-agents/go/references/modernize-guide.md +522 -0
- package/packages/stack-agents/go/references/module-management.md +191 -0
- package/packages/stack-agents/go/references/naming-conventions.md +176 -0
- package/packages/stack-agents/go/references/observability-pipeline.md +352 -0
- package/packages/stack-agents/go/references/performance-profiling.md +250 -0
- package/packages/stack-agents/go/references/refactoring-mechanics.md +127 -0
- package/packages/stack-agents/go/references/rest-openapi-patterns.md +160 -0
- package/packages/stack-agents/go/references/samber-do.md +201 -0
- package/packages/stack-agents/go/references/samber-libraries.md +363 -0
- package/packages/stack-agents/go/references/samber-lo.md +260 -0
- package/packages/stack-agents/go/references/samber-oops.md +200 -0
- package/packages/stack-agents/go/references/security-checklist.md +441 -0
- package/packages/stack-agents/go/references/standard-stdlib-now.md +181 -0
- package/packages/stack-agents/go/references/stay-updated.md +147 -0
- package/packages/stack-agents/go/references/structs-interfaces.md +312 -0
- package/packages/stack-agents/go/references/sync-primitives.md +395 -0
- package/packages/stack-agents/go/references/testing-patterns.md +483 -0
- package/packages/stack-agents/go/references/troubleshooting.md +98 -0
- package/packages/stack-agents/python/py-reviewer.md +194 -0
- package/packages/stack-agents/rust/rs-reviewer.md +188 -0
- package/packages/stack-agents/typescript/ts-reviewer.md +135 -0
- package/packages/stack-hooks/go/go-context-check-on-edit.sh +56 -0
- package/packages/stack-hooks/go/go-error-check-on-edit.sh +54 -0
- package/packages/stack-hooks/go/go-format-on-edit.sh +32 -0
- package/packages/stack-hooks/go/go-safety-check-on-edit.sh +54 -0
- package/packages/stack-hooks/go/go-vet-on-edit.sh +61 -0
- package/packages/stack-hooks/go/golangci-lint-on-edit.sh +54 -0
- package/packages/stack-hooks/python/ruff-on-edit.sh +67 -0
- package/packages/stack-hooks/rust/cargo-check-on-edit.sh +64 -0
- package/packages/stack-hooks/typescript/typecheck-on-edit.sh +76 -0
- package/packages/stack-rules/go/go-conventions.md +44 -0
- package/packages/stack-rules/go/go-safety.md +35 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# GraphQL Patterns in Go
|
|
2
|
+
|
|
3
|
+
Schema-first GraphQL servers in Go. Covers `gqlgen` (de-facto standard, codegen) and `99designs/gqlgen` workflow patterns.
|
|
4
|
+
|
|
5
|
+
## Stack choice
|
|
6
|
+
|
|
7
|
+
- **`gqlgen`** — schema-first, codegen, type-safe. Use this unless you have a strong reason not to.
|
|
8
|
+
- `graph-gophers/graphql-go` — also good, runtime resolution, less codegen.
|
|
9
|
+
- `thunder` — Facebook's runtime resolver, mostly historical.
|
|
10
|
+
|
|
11
|
+
This document focuses on `gqlgen`.
|
|
12
|
+
|
|
13
|
+
## Project layout
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
graph/
|
|
17
|
+
├── schema.graphqls # source of truth
|
|
18
|
+
├── model/
|
|
19
|
+
│ ├── models_gen.go # generated from schema
|
|
20
|
+
│ └── custom.go # manual model overrides
|
|
21
|
+
├── resolver.go # root resolver struct
|
|
22
|
+
├── schema.resolvers.go # generated resolver stubs
|
|
23
|
+
└── generated/
|
|
24
|
+
└── generated.go # full generated server
|
|
25
|
+
gqlgen.yml # codegen config
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run `go run github.com/99designs/gqlgen generate` to regenerate after schema changes.
|
|
29
|
+
|
|
30
|
+
## gqlgen.yml essentials
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
schema:
|
|
34
|
+
- graph/schema.graphqls
|
|
35
|
+
exec:
|
|
36
|
+
filename: graph/generated/generated.go
|
|
37
|
+
package: generated
|
|
38
|
+
model:
|
|
39
|
+
filename: graph/model/models_gen.go
|
|
40
|
+
package: model
|
|
41
|
+
resolver:
|
|
42
|
+
layout: follow-schema
|
|
43
|
+
dir: graph
|
|
44
|
+
package: graph
|
|
45
|
+
filename_template: '{name}.resolvers.go'
|
|
46
|
+
autobind: []
|
|
47
|
+
models:
|
|
48
|
+
ID:
|
|
49
|
+
model: github.com/99designs/gqlgen/graphql.ID
|
|
50
|
+
Int:
|
|
51
|
+
model:
|
|
52
|
+
- github.com/99designs/gqlgen/graphql.Int
|
|
53
|
+
- github.com/99designs/gqlgen/graphql.Int64
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Schema-first design
|
|
57
|
+
|
|
58
|
+
```graphql
|
|
59
|
+
type User {
|
|
60
|
+
id: ID!
|
|
61
|
+
email: String!
|
|
62
|
+
posts(first: Int = 20, after: String): PostConnection!
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type Query {
|
|
66
|
+
user(id: ID!): User
|
|
67
|
+
users(first: Int = 20, after: String): UserConnection!
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type Mutation {
|
|
71
|
+
createPost(input: CreatePostInput!): Post!
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
input CreatePostInput {
|
|
75
|
+
title: String!
|
|
76
|
+
body: String!
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type Post {
|
|
80
|
+
id: ID!
|
|
81
|
+
author: User!
|
|
82
|
+
title: String!
|
|
83
|
+
body: String!
|
|
84
|
+
createdAt: Time!
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
scalar Time
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Run codegen → `User`, `Post`, `CreatePostInput` are now Go structs.
|
|
91
|
+
|
|
92
|
+
## Resolver — clean implementation
|
|
93
|
+
|
|
94
|
+
```go
|
|
95
|
+
// schema.resolvers.go
|
|
96
|
+
func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
|
|
97
|
+
return r.UserSvc.GetByID(ctx, id)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func (r *userResolver) Posts(ctx context.Context, obj *model.User, first *int, after *string) (*model.PostConnection, error) {
|
|
101
|
+
return r.PostSvc.ListByUser(ctx, obj.ID, *first, after)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Resolvers should be **thin** — delegate to a service, never put business logic in resolvers.
|
|
106
|
+
|
|
107
|
+
## N+1 prevention — DataLoader
|
|
108
|
+
|
|
109
|
+
Without DataLoader, `users { posts }` for 100 users runs 101 queries (1 for users, 100 for posts). Use `graph-gophers/dataloader` or `vektah/dataloaden`:
|
|
110
|
+
|
|
111
|
+
```go
|
|
112
|
+
type UserLoader struct {
|
|
113
|
+
*dataloader.Loader
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func (l *UserLoader) Load(ctx context.Context, id string) (*model.User, error) {
|
|
117
|
+
res, err := l.Loader.Load(ctx, dataloader.StringKey(id))()
|
|
118
|
+
if err != nil { return nil, err }
|
|
119
|
+
return res.(*model.User), nil
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Batch function — takes a list of IDs, returns a list of results in the same order
|
|
123
|
+
func batchUsers(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
|
|
124
|
+
ids := make([]string, len(keys))
|
|
125
|
+
for i, k := range keys { ids[i] = k.String() }
|
|
126
|
+
users, _ := userRepo.GetByIDs(ctx, ids)
|
|
127
|
+
// ... return aligned results
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Attach DataLoader to context in middleware so resolvers can access it.
|
|
132
|
+
|
|
133
|
+
## Authentication / Authorization
|
|
134
|
+
|
|
135
|
+
Pass a `User` into context via HTTP middleware, then check in resolvers:
|
|
136
|
+
|
|
137
|
+
```go
|
|
138
|
+
func authMiddleware(next http.Handler) http.Handler {
|
|
139
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
140
|
+
user, err := parseToken(r.Header.Get("Authorization"))
|
|
141
|
+
if err == nil {
|
|
142
|
+
r = r.WithContext(context.WithValue(r.Context(), userKey, user))
|
|
143
|
+
}
|
|
144
|
+
next.ServeHTTP(w, r)
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
func (r *mutationResolver) CreatePost(ctx context.Context, input model.CreatePostInput) (*model.Post, error) {
|
|
149
|
+
u, ok := ctx.Value(userKey).(*model.User)
|
|
150
|
+
if !ok { return nil, fmt.Errorf("unauthorized") }
|
|
151
|
+
return r.PostSvc.Create(ctx, u.ID, input)
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For per-field authz, use `@auth` directive defined in schema and validated in directive resolver.
|
|
156
|
+
|
|
157
|
+
## Error handling
|
|
158
|
+
|
|
159
|
+
GraphQL has its own error envelope. Use `gqlerror` for structured errors:
|
|
160
|
+
|
|
161
|
+
```go
|
|
162
|
+
import "github.com/vektah/gqlparser/v2/gqlerror"
|
|
163
|
+
|
|
164
|
+
return nil, &gqlerror.Error{
|
|
165
|
+
Message: "post not found",
|
|
166
|
+
Extensions: map[string]interface{}{"code": "NOT_FOUND"},
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Don't return raw Go errors — they leak stack traces.
|
|
171
|
+
|
|
172
|
+
## Subscriptions (WebSocket)
|
|
173
|
+
|
|
174
|
+
```graphql
|
|
175
|
+
type Subscription {
|
|
176
|
+
postAdded(authorID: ID!): Post!
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```go
|
|
181
|
+
func (r *subscriptionResolver) PostAdded(ctx context.Context, authorID string) (<-chan *model.Post, error) {
|
|
182
|
+
ch := make(chan *model.Post, 1)
|
|
183
|
+
r.PubSub.Subscribe(authorID, ch)
|
|
184
|
+
go func() { <-ctx.Done(); r.PubSub.Unsubscribe(authorID, ch); close(ch) }()
|
|
185
|
+
return ch, nil
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Use Redis pub/sub or a queue under the hood — single-process map only works for dev.
|
|
190
|
+
|
|
191
|
+
## Pagination — Relay-style connections
|
|
192
|
+
|
|
193
|
+
Always use cursor-based `PostConnection { edges { node, cursor }, pageInfo }` for production. Offset pagination breaks on inserts/deletes during scroll.
|
|
194
|
+
|
|
195
|
+
## Testing
|
|
196
|
+
|
|
197
|
+
Use `gqlgen`'s `httptest` client:
|
|
198
|
+
|
|
199
|
+
```go
|
|
200
|
+
srv := handler.NewDefaultServer(generated.NewExecutableSchema(...))
|
|
201
|
+
c := client.New(srv)
|
|
202
|
+
|
|
203
|
+
var resp struct {
|
|
204
|
+
User struct { ID string; Email string }
|
|
205
|
+
}
|
|
206
|
+
c.MustPost(`{ user(id: "1") { id email } }`, &resp)
|
|
207
|
+
require.Equal(t, "user@example.com", resp.User.Email)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Anti-patterns
|
|
211
|
+
|
|
212
|
+
- **Business logic in resolvers** — keep resolvers thin
|
|
213
|
+
- **No DataLoader** — N+1 queries are silent perf killers
|
|
214
|
+
- **Returning raw `error`** — leaks internals; use `gqlerror` with codes
|
|
215
|
+
- **Offset pagination** for large lists — use cursors
|
|
216
|
+
- **Auth checked once, ignored after** — every protected resolver must verify
|
|
217
|
+
|
|
218
|
+
## See also
|
|
219
|
+
|
|
220
|
+
- `database-patterns.md` — repo layer that GraphQL resolvers should call
|
|
221
|
+
- `error-creation.md` — Go error wrapping patterns
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# gRPC Patterns
|
|
2
|
+
|
|
3
|
+
> Reference document for go-reviewer. Loaded on demand via Read tool.
|
|
4
|
+
> Upstream: https://pkg.go.dev/google.golang.org/grpc · https://grpc.io/docs/languages/go/
|
|
5
|
+
|
|
6
|
+
## Service Definition
|
|
7
|
+
|
|
8
|
+
Proto-first. Write the contract, generate code with `protoc-gen-go` + `protoc-gen-go-grpc`.
|
|
9
|
+
|
|
10
|
+
```proto
|
|
11
|
+
// api/user.proto
|
|
12
|
+
syntax = "proto3";
|
|
13
|
+
package user.v1;
|
|
14
|
+
option go_package = "example.com/gen/user/v1;userv1";
|
|
15
|
+
|
|
16
|
+
service UserService {
|
|
17
|
+
rpc GetUser (GetUserRequest) returns (User); // unary
|
|
18
|
+
rpc ListUsers (ListUsersRequest) returns (stream User); // server streaming
|
|
19
|
+
rpc UploadUsers (stream User) returns (UploadUsersResponse); // client streaming
|
|
20
|
+
rpc Chat (stream Message) returns (stream Message); // bidi streaming
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
message GetUserRequest { int64 id = 1; }
|
|
24
|
+
message User { int64 id = 1; string email = 2; }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Generation:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
protoc --go_out=gen --go_opt=paths=source_relative \
|
|
31
|
+
--go-grpc_out=gen --go-grpc_opt=paths=source_relative \
|
|
32
|
+
api/user.proto
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Version proto packages (`user.v1`, `user.v2`) — never break a shipped proto. Add fields (they're backward-compatible); don't renumber existing ones.
|
|
36
|
+
|
|
37
|
+
## Server Setup
|
|
38
|
+
|
|
39
|
+
```go
|
|
40
|
+
import (
|
|
41
|
+
"google.golang.org/grpc"
|
|
42
|
+
"google.golang.org/grpc/reflection"
|
|
43
|
+
userv1 "example.com/gen/user/v1"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
func main() {
|
|
47
|
+
lis, err := net.Listen("tcp", ":9090")
|
|
48
|
+
if err != nil { log.Fatal(err) }
|
|
49
|
+
|
|
50
|
+
srv := grpc.NewServer(
|
|
51
|
+
grpc.UnaryInterceptor(loggingInterceptor),
|
|
52
|
+
grpc.StreamInterceptor(streamLoggingInterceptor),
|
|
53
|
+
)
|
|
54
|
+
userv1.RegisterUserServiceServer(srv, &userServer{repo: repo})
|
|
55
|
+
reflection.Register(srv) // dev only — gRPCurl, Evans
|
|
56
|
+
|
|
57
|
+
if err := srv.Serve(lis); err != nil { log.Fatal(err) }
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For graceful shutdown: `srv.GracefulStop()` on SIGTERM — waits for in-flight RPCs, rejects new ones.
|
|
62
|
+
|
|
63
|
+
## The Four Stream Types
|
|
64
|
+
|
|
65
|
+
### Unary
|
|
66
|
+
|
|
67
|
+
```go
|
|
68
|
+
func (s *userServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.User, error) {
|
|
69
|
+
u, err := s.repo.FindByID(ctx, req.Id)
|
|
70
|
+
if err != nil {
|
|
71
|
+
if errors.Is(err, domain.ErrNotFound) {
|
|
72
|
+
return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
|
|
73
|
+
}
|
|
74
|
+
return nil, status.Errorf(codes.Internal, "find user: %v", err)
|
|
75
|
+
}
|
|
76
|
+
return &userv1.User{Id: u.ID, Email: u.Email}, nil
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Server Streaming
|
|
81
|
+
|
|
82
|
+
Server sends 0..N responses; client reads until EOF.
|
|
83
|
+
|
|
84
|
+
```go
|
|
85
|
+
func (s *userServer) ListUsers(req *userv1.ListUsersRequest, stream userv1.UserService_ListUsersServer) error {
|
|
86
|
+
users, err := s.repo.List(stream.Context(), req.PageSize)
|
|
87
|
+
if err != nil { return status.Errorf(codes.Internal, "list users: %v", err) }
|
|
88
|
+
|
|
89
|
+
for _, u := range users {
|
|
90
|
+
if err := stream.Send(&userv1.User{Id: u.ID, Email: u.Email}); err != nil {
|
|
91
|
+
return err // connection likely gone — no wrap needed
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return nil
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Always check `stream.Context().Err()` in long loops to bail when the client cancels.
|
|
99
|
+
|
|
100
|
+
### Client Streaming
|
|
101
|
+
|
|
102
|
+
Client sends N requests; server returns one final response.
|
|
103
|
+
|
|
104
|
+
```go
|
|
105
|
+
func (s *userServer) UploadUsers(stream userv1.UserService_UploadUsersServer) error {
|
|
106
|
+
var count int
|
|
107
|
+
for {
|
|
108
|
+
u, err := stream.Recv()
|
|
109
|
+
if errors.Is(err, io.EOF) {
|
|
110
|
+
return stream.SendAndClose(&userv1.UploadUsersResponse{Count: int32(count)})
|
|
111
|
+
}
|
|
112
|
+
if err != nil { return err }
|
|
113
|
+
if err := s.repo.Save(stream.Context(), u); err != nil {
|
|
114
|
+
return status.Errorf(codes.Internal, "save: %v", err)
|
|
115
|
+
}
|
|
116
|
+
count++
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Bidi Streaming
|
|
122
|
+
|
|
123
|
+
Both sides send/recv concurrently. Typically use two goroutines.
|
|
124
|
+
|
|
125
|
+
```go
|
|
126
|
+
func (s *chatServer) Chat(stream chatv1.ChatService_ChatServer) error {
|
|
127
|
+
errCh := make(chan error, 2)
|
|
128
|
+
go func() {
|
|
129
|
+
for {
|
|
130
|
+
msg, err := stream.Recv()
|
|
131
|
+
if errors.Is(err, io.EOF) { errCh <- nil; return }
|
|
132
|
+
if err != nil { errCh <- err; return }
|
|
133
|
+
s.broadcast(msg)
|
|
134
|
+
}
|
|
135
|
+
}()
|
|
136
|
+
go func() {
|
|
137
|
+
for msg := range s.subscribe() {
|
|
138
|
+
if err := stream.Send(msg); err != nil { errCh <- err; return }
|
|
139
|
+
}
|
|
140
|
+
errCh <- nil
|
|
141
|
+
}()
|
|
142
|
+
return <-errCh
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Error Handling
|
|
147
|
+
|
|
148
|
+
Return `status.Error` / `status.Errorf` with a `codes.*` code. Raw Go errors get wrapped as `codes.Unknown` — always unhelpful for clients.
|
|
149
|
+
|
|
150
|
+
```go
|
|
151
|
+
import (
|
|
152
|
+
"google.golang.org/grpc/codes"
|
|
153
|
+
"google.golang.org/grpc/status"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return nil, status.Errorf(codes.NotFound, "user %d not found", id)
|
|
157
|
+
return nil, status.Errorf(codes.InvalidArgument, "email required")
|
|
158
|
+
return nil, status.Errorf(codes.PermissionDenied, "caller lacks role")
|
|
159
|
+
return nil, status.Errorf(codes.DeadlineExceeded, "query timed out")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Canonical mapping (RFC): https://grpc.io/docs/guides/status-codes/
|
|
163
|
+
|
|
164
|
+
Client side:
|
|
165
|
+
|
|
166
|
+
```go
|
|
167
|
+
resp, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: 42})
|
|
168
|
+
if err != nil {
|
|
169
|
+
st, ok := status.FromError(err)
|
|
170
|
+
if !ok {
|
|
171
|
+
return fmt.Errorf("unknown error: %w", err)
|
|
172
|
+
}
|
|
173
|
+
switch st.Code() {
|
|
174
|
+
case codes.NotFound:
|
|
175
|
+
return handleNotFound()
|
|
176
|
+
case codes.Unavailable:
|
|
177
|
+
return retry()
|
|
178
|
+
default:
|
|
179
|
+
return fmt.Errorf("grpc: %s", st.Message())
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
For rich error details (validation field lists, retry info), attach proto details: `status.WithDetails(&errdetails.BadRequest{...})`.
|
|
185
|
+
|
|
186
|
+
## Interceptors
|
|
187
|
+
|
|
188
|
+
Cross-cutting concerns: auth, logging, metrics, panic recovery. One unary interceptor + one stream interceptor per server, composed.
|
|
189
|
+
|
|
190
|
+
### Unary logging
|
|
191
|
+
|
|
192
|
+
```go
|
|
193
|
+
func loggingInterceptor(
|
|
194
|
+
ctx context.Context,
|
|
195
|
+
req interface{},
|
|
196
|
+
info *grpc.UnaryServerInfo,
|
|
197
|
+
handler grpc.UnaryHandler,
|
|
198
|
+
) (interface{}, error) {
|
|
199
|
+
start := time.Now()
|
|
200
|
+
resp, err := handler(ctx, req)
|
|
201
|
+
slog.Info("rpc",
|
|
202
|
+
"method", info.FullMethod,
|
|
203
|
+
"duration_ms", time.Since(start).Milliseconds(),
|
|
204
|
+
"code", status.Code(err),
|
|
205
|
+
)
|
|
206
|
+
return resp, err
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Auth
|
|
211
|
+
|
|
212
|
+
```go
|
|
213
|
+
func authInterceptor(ctx context.Context, req interface{},
|
|
214
|
+
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
215
|
+
|
|
216
|
+
md, ok := metadata.FromIncomingContext(ctx)
|
|
217
|
+
if !ok { return nil, status.Error(codes.Unauthenticated, "no metadata") }
|
|
218
|
+
|
|
219
|
+
tokens := md.Get("authorization")
|
|
220
|
+
if len(tokens) == 0 {
|
|
221
|
+
return nil, status.Error(codes.Unauthenticated, "missing token")
|
|
222
|
+
}
|
|
223
|
+
user, err := verify(tokens[0])
|
|
224
|
+
if err != nil {
|
|
225
|
+
return nil, status.Error(codes.Unauthenticated, "invalid token")
|
|
226
|
+
}
|
|
227
|
+
ctx = context.WithValue(ctx, userCtxKey{}, user)
|
|
228
|
+
return handler(ctx, req)
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Chain multiple interceptors
|
|
233
|
+
|
|
234
|
+
```go
|
|
235
|
+
srv := grpc.NewServer(
|
|
236
|
+
grpc.ChainUnaryInterceptor(recoveryInterceptor, loggingInterceptor, authInterceptor),
|
|
237
|
+
grpc.ChainStreamInterceptor(streamRecoveryInterceptor, streamLoggingInterceptor),
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Well-known libraries: `go-grpc-middleware` (auth, logging, recovery, prometheus), `otelgrpc` (OpenTelemetry tracing).
|
|
242
|
+
|
|
243
|
+
## Deadline Propagation
|
|
244
|
+
|
|
245
|
+
Client sets a deadline on the context; gRPC sends it as `grpc-timeout` header; server sees it via `ctx.Done()`.
|
|
246
|
+
|
|
247
|
+
```go
|
|
248
|
+
// Client
|
|
249
|
+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
250
|
+
defer cancel()
|
|
251
|
+
resp, err := client.GetUser(ctx, req) // timeout propagates
|
|
252
|
+
|
|
253
|
+
// Server — cascade to downstream calls
|
|
254
|
+
func (s *userServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.User, error) {
|
|
255
|
+
u, err := s.repo.FindByID(ctx, req.Id) // same ctx; DB honors the deadline
|
|
256
|
+
// ...
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Never start a background goroutine with `context.Background()` unless the work must outlive the request — detach only intentionally.
|
|
261
|
+
|
|
262
|
+
## Connection Lifecycle
|
|
263
|
+
|
|
264
|
+
```go
|
|
265
|
+
conn, err := grpc.NewClient("dns:///users.svc:9090",
|
|
266
|
+
grpc.WithTransportCredentials(insecure.NewCredentials()), // TLS below
|
|
267
|
+
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
|
|
268
|
+
)
|
|
269
|
+
if err != nil { return err }
|
|
270
|
+
defer conn.Close()
|
|
271
|
+
|
|
272
|
+
client := userv1.NewUserServiceClient(conn)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
- One `*grpc.ClientConn` per upstream service, shared across goroutines — it's already a pool.
|
|
276
|
+
- `grpc.NewClient` (replaces deprecated `grpc.Dial`) — defers actual connection until first RPC.
|
|
277
|
+
- For retries / hedging, set it in the ServiceConfig JSON, not ad-hoc on the client side.
|
|
278
|
+
|
|
279
|
+
## TLS & mTLS
|
|
280
|
+
|
|
281
|
+
### Server TLS
|
|
282
|
+
|
|
283
|
+
```go
|
|
284
|
+
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
|
|
285
|
+
if err != nil { return err }
|
|
286
|
+
creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}})
|
|
287
|
+
srv := grpc.NewServer(grpc.Creds(creds))
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Client TLS
|
|
291
|
+
|
|
292
|
+
```go
|
|
293
|
+
creds := credentials.NewTLS(&tls.Config{ServerName: "users.svc"})
|
|
294
|
+
conn, _ := grpc.NewClient("users.svc:443", grpc.WithTransportCredentials(creds))
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### mTLS (both sides authenticate)
|
|
298
|
+
|
|
299
|
+
```go
|
|
300
|
+
// Server
|
|
301
|
+
pool := x509.NewCertPool()
|
|
302
|
+
caCert, _ := os.ReadFile("ca.crt")
|
|
303
|
+
pool.AppendCertsFromPEM(caCert)
|
|
304
|
+
creds := credentials.NewTLS(&tls.Config{
|
|
305
|
+
Certificates: []tls.Certificate{serverCert},
|
|
306
|
+
ClientAuth: tls.RequireAndVerifyClientCert,
|
|
307
|
+
ClientCAs: pool,
|
|
308
|
+
})
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Production: use SPIFFE / service mesh (Linkerd, Istio) for cert rotation; rolling your own expiry handling is a slow-moving incident factory.
|
|
312
|
+
|
|
313
|
+
## Testing: bufconn
|
|
314
|
+
|
|
315
|
+
`google.golang.org/grpc/test/bufconn` — in-memory listener, no ports, no network.
|
|
316
|
+
|
|
317
|
+
```go
|
|
318
|
+
import "google.golang.org/grpc/test/bufconn"
|
|
319
|
+
|
|
320
|
+
func newTestServer(t *testing.T) (userv1.UserServiceClient, func()) {
|
|
321
|
+
lis := bufconn.Listen(1024 * 1024)
|
|
322
|
+
srv := grpc.NewServer()
|
|
323
|
+
userv1.RegisterUserServiceServer(srv, &userServer{repo: fakeRepo(t)})
|
|
324
|
+
go srv.Serve(lis)
|
|
325
|
+
|
|
326
|
+
dialer := func(context.Context, string) (net.Conn, error) { return lis.Dial() }
|
|
327
|
+
conn, err := grpc.NewClient("passthrough:///bufnet",
|
|
328
|
+
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
329
|
+
grpc.WithContextDialer(dialer),
|
|
330
|
+
)
|
|
331
|
+
if err != nil { t.Fatal(err) }
|
|
332
|
+
|
|
333
|
+
return userv1.NewUserServiceClient(conn),
|
|
334
|
+
func() { conn.Close(); srv.Stop() }
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Use bufconn for integration tests spanning the full service layer. For handler-only unit tests, mock the generated server interface (`userv1.UserServiceServer`) and call methods directly.
|
|
339
|
+
|
|
340
|
+
## Common Pitfalls
|
|
341
|
+
|
|
342
|
+
| Pitfall | Symptom | Fix |
|
|
343
|
+
|---------|---------|-----|
|
|
344
|
+
| Returning raw `errors.New` from handler | Client sees `codes.Unknown` | Use `status.Errorf(codes.*, ...)` |
|
|
345
|
+
| `context.Background()` inside a handler | Leaks past the RPC; can run forever | Pass the request `ctx` or detach deliberately |
|
|
346
|
+
| Creating a new `ClientConn` per request | TCP storm, TLS handshake on every call | One conn per upstream, shared |
|
|
347
|
+
| Blocking in a stream handler without checking `Context().Done()` | Hangs when client disconnects | Select on `ctx.Done()` in loops |
|
|
348
|
+
| Forgetting `reflection.Register` in dev | grpcurl returns "unknown service" | Register reflection; disable in prod |
|
|
349
|
+
| Breaking proto changes (renumber, rename) | Old clients crash | Add new fields with new numbers; never reuse |
|
|
350
|
+
| No interceptor for panic recovery | Single panic kills the server process | Add `grpc_recovery` or write one |
|
|
351
|
+
| Missing deadlines | Runaway calls pin goroutines | Always set client-side `WithTimeout` |
|
|
352
|
+
|
|
353
|
+
## Review Checklist
|
|
354
|
+
|
|
355
|
+
When reviewing gRPC code, flag:
|
|
356
|
+
|
|
357
|
+
- **CRITICAL** — Handler returns a raw error instead of `status.Error` (clients can't match on code)
|
|
358
|
+
- **CRITICAL** — No recovery interceptor (one panic kills the server)
|
|
359
|
+
- **CRITICAL** — `reflection.Register` enabled in production (schema + method enumeration leak)
|
|
360
|
+
- **WARNING** — Per-request `grpc.NewClient` / per-request `*grpc.ClientConn` (should be shared)
|
|
361
|
+
- **WARNING** — Stream handler without `select { case <-ctx.Done() }` in its loop
|
|
362
|
+
- **WARNING** — Proto field numbers renumbered or reused
|
|
363
|
+
- **WARNING** — Client RPC without a deadline
|
|
364
|
+
- **SUGGESTION** — Cross-cutting code (auth, logging, metrics) outside interceptors — should be an interceptor
|
|
365
|
+
- **SUGGESTION** — Missing `otelgrpc` interceptor in a traced system
|
|
366
|
+
|
|
367
|
+
## Further Reading
|
|
368
|
+
|
|
369
|
+
- gRPC Go docs: https://pkg.go.dev/google.golang.org/grpc
|
|
370
|
+
- Status codes guide: https://grpc.io/docs/guides/status-codes/
|
|
371
|
+
- `go-grpc-middleware`: https://github.com/grpc-ecosystem/go-grpc-middleware
|
|
372
|
+
- OpenTelemetry gRPC: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
|
|
373
|
+
- Buf (proto tooling): https://buf.build/docs
|