@codyswann/lisa 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/LICENSE +21 -0
- package/README.md +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# NestJS GraphQL Quick Start
|
|
2
|
+
|
|
3
|
+
Reference documentation for setting up and configuring NestJS GraphQL with Apollo.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Core Dependencies
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @nestjs/graphql @nestjs/apollo @apollo/server graphql
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Optional Dependencies
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# DataLoader for N+1 prevention
|
|
17
|
+
bun add dataloader
|
|
18
|
+
|
|
19
|
+
# GraphQL tools for schema transformation
|
|
20
|
+
bun add @graphql-tools/utils
|
|
21
|
+
|
|
22
|
+
# Query complexity limiting
|
|
23
|
+
bun add graphql-query-complexity
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Code-First vs Schema-First
|
|
27
|
+
|
|
28
|
+
NestJS supports two approaches to building GraphQL APIs:
|
|
29
|
+
|
|
30
|
+
### Code-First (Recommended)
|
|
31
|
+
|
|
32
|
+
Define types using TypeScript decorators. The schema is auto-generated.
|
|
33
|
+
|
|
34
|
+
**Advantages:**
|
|
35
|
+
- Type safety between resolvers and schema
|
|
36
|
+
- No schema/code synchronization issues
|
|
37
|
+
- Refactoring support from IDE
|
|
38
|
+
- Single source of truth
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
@ObjectType()
|
|
42
|
+
export class User {
|
|
43
|
+
@Field(() => ID)
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
@Field()
|
|
47
|
+
email: string;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Schema-First
|
|
52
|
+
|
|
53
|
+
Write `.graphql` schema files, generate TypeScript types.
|
|
54
|
+
|
|
55
|
+
```graphql
|
|
56
|
+
type User {
|
|
57
|
+
id: ID!
|
|
58
|
+
email: String!
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Module Configuration
|
|
63
|
+
|
|
64
|
+
### Basic Setup
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
|
68
|
+
import { Module } from "@nestjs/common";
|
|
69
|
+
import { GraphQLModule } from "@nestjs/graphql";
|
|
70
|
+
import { join } from "path";
|
|
71
|
+
|
|
72
|
+
@Module({
|
|
73
|
+
imports: [
|
|
74
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
75
|
+
driver: ApolloDriver,
|
|
76
|
+
autoSchemaFile: join(process.cwd(), "src/schema.gql"),
|
|
77
|
+
sortSchema: true,
|
|
78
|
+
}),
|
|
79
|
+
],
|
|
80
|
+
})
|
|
81
|
+
export class AppModule {}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Production Configuration
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
88
|
+
driver: ApolloDriver,
|
|
89
|
+
// In-memory schema for Lambda (no file writes)
|
|
90
|
+
autoSchemaFile: process.env.IS_OFFLINE === "true"
|
|
91
|
+
? join(process.cwd(), "src/schema.gql")
|
|
92
|
+
: true,
|
|
93
|
+
sortSchema: true,
|
|
94
|
+
playground: false,
|
|
95
|
+
introspection: true, // Enable for API explorers
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Async Configuration with Dependencies
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
|
103
|
+
driver: ApolloDriver,
|
|
104
|
+
imports: [DataLoaderModule],
|
|
105
|
+
inject: [DataLoaderService],
|
|
106
|
+
useFactory: (dataLoaderService: DataLoaderService) => ({
|
|
107
|
+
autoSchemaFile: true,
|
|
108
|
+
sortSchema: true,
|
|
109
|
+
playground: false,
|
|
110
|
+
introspection: true,
|
|
111
|
+
context: ({ req, res }) => ({
|
|
112
|
+
req,
|
|
113
|
+
res,
|
|
114
|
+
loaders: dataLoaderService.getLoaders(),
|
|
115
|
+
}),
|
|
116
|
+
}),
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Configuration Options
|
|
121
|
+
|
|
122
|
+
### autoSchemaFile
|
|
123
|
+
|
|
124
|
+
Controls where the generated schema is written:
|
|
125
|
+
|
|
126
|
+
| Value | Behavior |
|
|
127
|
+
|-------|----------|
|
|
128
|
+
| `true` | In-memory schema (no file) |
|
|
129
|
+
| `"path/to/schema.gql"` | Write to file |
|
|
130
|
+
| `join(process.cwd(), "src/schema.gql")` | Absolute path |
|
|
131
|
+
|
|
132
|
+
### sortSchema
|
|
133
|
+
|
|
134
|
+
When `true`, sorts types and fields alphabetically in generated schema.
|
|
135
|
+
|
|
136
|
+
### playground
|
|
137
|
+
|
|
138
|
+
GraphQL Playground is deprecated. Use Apollo Sandbox instead:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
{
|
|
142
|
+
playground: false,
|
|
143
|
+
plugins: [ApolloServerPluginLandingPageLocalDefault()],
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### introspection
|
|
148
|
+
|
|
149
|
+
Enable/disable schema introspection queries. Required for API explorers and client codegen.
|
|
150
|
+
|
|
151
|
+
### context
|
|
152
|
+
|
|
153
|
+
Function that creates the GraphQL context for each request:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
context: ({ req, res }) => ({
|
|
157
|
+
req, // Express request (with user from auth middleware)
|
|
158
|
+
res, // Express response
|
|
159
|
+
loaders, // DataLoader instances
|
|
160
|
+
// Add custom context properties
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## NestJS CLI Configuration
|
|
165
|
+
|
|
166
|
+
### nest-cli.json
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"$schema": "https://json.schemastore.org/nest-cli",
|
|
171
|
+
"collection": "@nestjs/schematics",
|
|
172
|
+
"sourceRoot": "src",
|
|
173
|
+
"compilerOptions": {
|
|
174
|
+
"deleteOutDir": true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Generating Components
|
|
180
|
+
|
|
181
|
+
Always use NestJS CLI to generate GraphQL components:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Generate module
|
|
185
|
+
bunx nest g module users --no-spec
|
|
186
|
+
|
|
187
|
+
# Generate service
|
|
188
|
+
bunx nest g service users --no-spec
|
|
189
|
+
|
|
190
|
+
# Generate resolver
|
|
191
|
+
bunx nest g resolver users --no-spec
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The `--no-spec` flag skips spec file generation (we write tests first with TDD).
|
|
195
|
+
|
|
196
|
+
## TypeScript Configuration
|
|
197
|
+
|
|
198
|
+
Required compiler options for NestJS decorators:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"compilerOptions": {
|
|
203
|
+
"emitDecoratorMetadata": true,
|
|
204
|
+
"experimentalDecorators": true,
|
|
205
|
+
"strictPropertyInitialization": false
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Schema Transformation
|
|
211
|
+
|
|
212
|
+
Apply transformations to the generated schema:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
216
|
+
// ... other options
|
|
217
|
+
transformSchema: (schema) => {
|
|
218
|
+
// Apply auth enforcement
|
|
219
|
+
const withAuth = authExtensionTransformer(schema);
|
|
220
|
+
// Apply field-level auth
|
|
221
|
+
return fieldAuthExtensionTransformer(withAuth);
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Error Handling
|
|
227
|
+
|
|
228
|
+
### Custom Error Formatting
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
232
|
+
formatError: (error) => ({
|
|
233
|
+
message: error.message,
|
|
234
|
+
code: error.extensions?.code,
|
|
235
|
+
path: error.path,
|
|
236
|
+
// Omit stack traces in production
|
|
237
|
+
...(process.env.NODE_ENV !== "production" && {
|
|
238
|
+
locations: error.locations,
|
|
239
|
+
}),
|
|
240
|
+
}),
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Health Check Integration
|
|
245
|
+
|
|
246
|
+
GraphQL endpoints should be separate from REST health checks for AWS ALB:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// REST health check
|
|
250
|
+
@Controller("health")
|
|
251
|
+
export class HealthController {
|
|
252
|
+
@Get()
|
|
253
|
+
check() {
|
|
254
|
+
return { status: "ok", timestamp: new Date().toISOString() };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# Resolvers and Mutations
|
|
2
|
+
|
|
3
|
+
Comprehensive guide to writing GraphQL resolvers in NestJS.
|
|
4
|
+
|
|
5
|
+
## Resolver Basics
|
|
6
|
+
|
|
7
|
+
### Resolver Class Structure
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { Args, Context, Mutation, Parent, Query, ResolveField, Resolver } from "@nestjs/graphql";
|
|
11
|
+
|
|
12
|
+
@Resolver(() => User)
|
|
13
|
+
export class UserResolver {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly userService: UserService,
|
|
16
|
+
private readonly postService: PostService
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
// Queries, mutations, and field resolvers go here
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Resolver Decorator
|
|
24
|
+
|
|
25
|
+
The `@Resolver()` decorator marks a class as a GraphQL resolver.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Basic resolver (no parent type)
|
|
29
|
+
@Resolver()
|
|
30
|
+
export class QueryResolver {}
|
|
31
|
+
|
|
32
|
+
// Resolver with parent type (enables field resolvers)
|
|
33
|
+
@Resolver(() => User)
|
|
34
|
+
export class UserResolver {}
|
|
35
|
+
|
|
36
|
+
// Resolver with string type name
|
|
37
|
+
@Resolver("User")
|
|
38
|
+
export class UserResolver {}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Queries
|
|
42
|
+
|
|
43
|
+
### Basic Query
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
@Query(() => String, { description: "Returns hello world" })
|
|
47
|
+
hello(): string {
|
|
48
|
+
return "Hello World";
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Query with Arguments
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
@Query(() => User, {
|
|
56
|
+
nullable: true,
|
|
57
|
+
description: "Find user by ID"
|
|
58
|
+
})
|
|
59
|
+
async user(
|
|
60
|
+
@Args("id", { type: () => ID, description: "User's unique identifier" })
|
|
61
|
+
id: string
|
|
62
|
+
): Promise<User | null> {
|
|
63
|
+
return this.userService.findById(id);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Query with Multiple Arguments
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
@Query(() => [User], { description: "Search users" })
|
|
71
|
+
async users(
|
|
72
|
+
@Args("search", { nullable: true }) search?: string,
|
|
73
|
+
@Args("limit", { type: () => Int, defaultValue: 20 }) limit?: number,
|
|
74
|
+
@Args("offset", { type: () => Int, defaultValue: 0 }) offset?: number
|
|
75
|
+
): Promise<User[]> {
|
|
76
|
+
return this.userService.search({ search, limit, offset });
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Query with Input Type
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
@Query(() => [User], { description: "Find users with filters" })
|
|
84
|
+
async users(
|
|
85
|
+
@Args("filter", { nullable: true }) filter?: UserFilterInput
|
|
86
|
+
): Promise<User[]> {
|
|
87
|
+
return this.userService.findMany(filter);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Mutations
|
|
92
|
+
|
|
93
|
+
### Basic Mutation
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
@Mutation(() => User, { description: "Create a new user" })
|
|
97
|
+
async createUser(
|
|
98
|
+
@Args("input") input: CreateUserInput
|
|
99
|
+
): Promise<User> {
|
|
100
|
+
return this.userService.create(input);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Mutation with Context
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
@Mutation(() => Post, { description: "Create post for authenticated user" })
|
|
108
|
+
@Authed()
|
|
109
|
+
async createPost(
|
|
110
|
+
@Args("input") input: CreatePostInput,
|
|
111
|
+
@Context() { req }: GraphQLContext
|
|
112
|
+
): Promise<Post> {
|
|
113
|
+
return this.postService.create({
|
|
114
|
+
...input,
|
|
115
|
+
authorId: req.user.id,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Update Mutation
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
@Mutation(() => User, { description: "Update user profile" })
|
|
124
|
+
@Authed()
|
|
125
|
+
async updateUser(
|
|
126
|
+
@Args("id", { type: () => ID }) id: string,
|
|
127
|
+
@Args("input") input: UpdateUserInput,
|
|
128
|
+
@Context() { req }: GraphQLContext
|
|
129
|
+
): Promise<User> {
|
|
130
|
+
// Check ownership
|
|
131
|
+
const user = await this.userService.findById(id);
|
|
132
|
+
if (user.id !== req.user.id) {
|
|
133
|
+
throw new UnauthorizedError("Not authorized to update this user");
|
|
134
|
+
}
|
|
135
|
+
return this.userService.update(id, input);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Delete Mutation
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
@Mutation(() => Boolean, { description: "Delete a post" })
|
|
143
|
+
@Authed()
|
|
144
|
+
async deletePost(
|
|
145
|
+
@Args("id", { type: () => ID }) id: string,
|
|
146
|
+
@Context() { req }: GraphQLContext
|
|
147
|
+
): Promise<boolean> {
|
|
148
|
+
const post = await this.postService.findById(id);
|
|
149
|
+
if (post.authorId !== req.user.id) {
|
|
150
|
+
throw new UnauthorizedError("Not authorized to delete this post");
|
|
151
|
+
}
|
|
152
|
+
await this.postService.delete(id);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Field Resolvers
|
|
158
|
+
|
|
159
|
+
### Basic Field Resolver
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
@Resolver(() => User)
|
|
163
|
+
export class UserResolver {
|
|
164
|
+
@ResolveField(() => String, { description: "User's full name" })
|
|
165
|
+
fullName(@Parent() user: User): string {
|
|
166
|
+
return `${user.firstName} ${user.lastName}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Field Resolver with DataLoader
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
@ResolveField(() => [Post], { description: "User's posts" })
|
|
175
|
+
async posts(
|
|
176
|
+
@Parent() user: User,
|
|
177
|
+
@Context() { loaders }: GraphQLContext
|
|
178
|
+
): Promise<Post[]> {
|
|
179
|
+
return loaders.postsByAuthorLoader.load(user.id);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Nullable Field Resolver
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
@ResolveField(() => Organization, {
|
|
187
|
+
nullable: true,
|
|
188
|
+
description: "User's organization if any"
|
|
189
|
+
})
|
|
190
|
+
async organization(
|
|
191
|
+
@Parent() user: User,
|
|
192
|
+
@Context() { loaders }: GraphQLContext
|
|
193
|
+
): Promise<Organization | null> {
|
|
194
|
+
if (!user.organizationId) return null;
|
|
195
|
+
return loaders.organizationLoader.load(user.organizationId);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Decorator Reference
|
|
200
|
+
|
|
201
|
+
### @Args()
|
|
202
|
+
|
|
203
|
+
Extract arguments from the GraphQL query:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Simple argument
|
|
207
|
+
@Args("name") name: string
|
|
208
|
+
|
|
209
|
+
// With type specification (required for non-string primitives)
|
|
210
|
+
@Args("count", { type: () => Int }) count: number
|
|
211
|
+
|
|
212
|
+
// Nullable argument
|
|
213
|
+
@Args("search", { nullable: true }) search?: string
|
|
214
|
+
|
|
215
|
+
// With default value
|
|
216
|
+
@Args("limit", { type: () => Int, defaultValue: 20 }) limit: number
|
|
217
|
+
|
|
218
|
+
// With description
|
|
219
|
+
@Args("id", {
|
|
220
|
+
type: () => ID,
|
|
221
|
+
description: "The unique identifier"
|
|
222
|
+
}) id: string
|
|
223
|
+
|
|
224
|
+
// Input type argument
|
|
225
|
+
@Args("input") input: CreateUserInput
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### @Context()
|
|
229
|
+
|
|
230
|
+
Access the GraphQL context:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// Full context
|
|
234
|
+
@Context() context: GraphQLContext
|
|
235
|
+
|
|
236
|
+
// Destructured properties
|
|
237
|
+
@Context() { req, res, loaders }: GraphQLContext
|
|
238
|
+
|
|
239
|
+
// Specific property
|
|
240
|
+
@Context("req") req: Request
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### @Parent()
|
|
244
|
+
|
|
245
|
+
Access the parent object in field resolvers:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
@ResolveField(() => String)
|
|
249
|
+
fullName(@Parent() user: User): string {
|
|
250
|
+
return `${user.firstName} ${user.lastName}`;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### @Info()
|
|
255
|
+
|
|
256
|
+
Access the GraphQL resolve info:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { GraphQLResolveInfo } from "graphql";
|
|
260
|
+
|
|
261
|
+
@Query(() => User)
|
|
262
|
+
async user(
|
|
263
|
+
@Args("id") id: string,
|
|
264
|
+
@Info() info: GraphQLResolveInfo
|
|
265
|
+
): Promise<User> {
|
|
266
|
+
// Use info for query optimization, field selection, etc.
|
|
267
|
+
const requestedFields = info.fieldNodes[0].selectionSet;
|
|
268
|
+
return this.userService.findById(id, requestedFields);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Return Types
|
|
273
|
+
|
|
274
|
+
### Nullable Returns
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// Nullable query result
|
|
278
|
+
@Query(() => User, { nullable: true })
|
|
279
|
+
async user(@Args("id") id: string): Promise<User | null> {
|
|
280
|
+
return this.userService.findById(id);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Nullable array items
|
|
284
|
+
@Query(() => [User], { nullable: "items" })
|
|
285
|
+
async users(): Promise<(User | null)[]> { }
|
|
286
|
+
|
|
287
|
+
// Nullable array itself
|
|
288
|
+
@Query(() => [User], { nullable: true })
|
|
289
|
+
async users(): Promise<User[] | null> { }
|
|
290
|
+
|
|
291
|
+
// Both nullable
|
|
292
|
+
@Query(() => [User], { nullable: "itemsAndList" })
|
|
293
|
+
async users(): Promise<(User | null)[] | null> { }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Complexity
|
|
297
|
+
|
|
298
|
+
Assign complexity to fields for query cost analysis:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
@Query(() => [User], { complexity: 10 })
|
|
302
|
+
async users(): Promise<User[]> { }
|
|
303
|
+
|
|
304
|
+
// Dynamic complexity
|
|
305
|
+
@Query(() => [User], {
|
|
306
|
+
complexity: (options) => options.args.first * 2
|
|
307
|
+
})
|
|
308
|
+
async users(@Args("first", { type: () => Int }) first: number): Promise<User[]> { }
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## GraphQL Context Type
|
|
312
|
+
|
|
313
|
+
Define a typed context interface:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { IDataLoaders } from "../data-loader/data-loader.interface";
|
|
317
|
+
import { AuthUser } from "../auth/auth.types";
|
|
318
|
+
|
|
319
|
+
interface GraphQLContext {
|
|
320
|
+
readonly req: {
|
|
321
|
+
readonly user?: AuthUser;
|
|
322
|
+
};
|
|
323
|
+
readonly res: Response;
|
|
324
|
+
readonly loaders: IDataLoaders;
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Error Handling
|
|
329
|
+
|
|
330
|
+
### Throwing Errors
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { GraphQLError } from "graphql";
|
|
334
|
+
|
|
335
|
+
@Query(() => User)
|
|
336
|
+
async user(@Args("id") id: string): Promise<User> {
|
|
337
|
+
const user = await this.userService.findById(id);
|
|
338
|
+
if (!user) {
|
|
339
|
+
throw new GraphQLError("User not found", {
|
|
340
|
+
extensions: { code: "NOT_FOUND" },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return user;
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Custom Error Classes
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
class NotFoundError extends GraphQLError {
|
|
351
|
+
constructor(resource: string, id: string) {
|
|
352
|
+
super(`${resource} with ID ${id} not found`, {
|
|
353
|
+
extensions: { code: "NOT_FOUND", resource, id },
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Best Practices
|
|
360
|
+
|
|
361
|
+
### 1. Always Add Descriptions
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
@Query(() => User, {
|
|
365
|
+
description: "Retrieves a user by their unique identifier"
|
|
366
|
+
})
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### 2. Use Auth Decorators
|
|
370
|
+
|
|
371
|
+
Every query and mutation must have an auth decorator:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
@Query(() => [Post])
|
|
375
|
+
@Public() // or @Authed() or @Groups("ADMIN")
|
|
376
|
+
async posts(): Promise<Post[]> { }
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 3. Use DataLoaders for Relations
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
// Bad: N+1 problem
|
|
383
|
+
@ResolveField()
|
|
384
|
+
async author(@Parent() post: Post): Promise<User> {
|
|
385
|
+
return this.userService.findById(post.authorId);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Good: Batched loading
|
|
389
|
+
@ResolveField()
|
|
390
|
+
async author(
|
|
391
|
+
@Parent() post: Post,
|
|
392
|
+
@Context() { loaders }: GraphQLContext
|
|
393
|
+
): Promise<User> {
|
|
394
|
+
return loaders.userLoader.load(post.authorId);
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 4. Validate Input
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { IsEmail, MinLength } from "class-validator";
|
|
402
|
+
|
|
403
|
+
@InputType()
|
|
404
|
+
export class CreateUserInput {
|
|
405
|
+
@Field()
|
|
406
|
+
@IsEmail()
|
|
407
|
+
email: string;
|
|
408
|
+
|
|
409
|
+
@Field()
|
|
410
|
+
@MinLength(8)
|
|
411
|
+
password: string;
|
|
412
|
+
}
|
|
413
|
+
```
|