@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,483 @@
|
|
|
1
|
+
# Project-Specific Patterns
|
|
2
|
+
|
|
3
|
+
This document covers patterns specific to this project's NestJS GraphQL implementation, including zero-trust authentication, DataLoader integration, and documentation standards.
|
|
4
|
+
|
|
5
|
+
## Zero-Trust Auth System
|
|
6
|
+
|
|
7
|
+
### Auth Architecture
|
|
8
|
+
|
|
9
|
+
**Zero-Trust Principles:**
|
|
10
|
+
- Everything is inaccessible by default (deny-by-default)
|
|
11
|
+
- Every operation must explicitly declare auth requirements
|
|
12
|
+
- Missing auth declarations throw errors at schema build time
|
|
13
|
+
|
|
14
|
+
### Auth Levels
|
|
15
|
+
|
|
16
|
+
| Level | Operation-Level | Field-Level | Description |
|
|
17
|
+
|-------|-----------------|-------------|-------------|
|
|
18
|
+
| `Public` | Yes | Yes | No authentication required |
|
|
19
|
+
| `Authed` | Yes | Yes | Any authenticated user |
|
|
20
|
+
| `Owner` | **No** | Yes | Only the resource owner |
|
|
21
|
+
| `Groups` | Yes | Yes | Members of specified groups |
|
|
22
|
+
|
|
23
|
+
**Why Owner doesn't work at operation level:**
|
|
24
|
+
At the Query/Mutation level, there is no parent `source` object to check ownership against. Owner auth requires a parent object with an owner field (e.g., `todo.ownerId`) which only exists when resolving nested fields.
|
|
25
|
+
|
|
26
|
+
### Operation-Level Auth Decorators
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Public, Authed, Groups } from "../auth";
|
|
30
|
+
|
|
31
|
+
// Public access - no auth required
|
|
32
|
+
@Query(() => String, { description: "Public health check" })
|
|
33
|
+
@Public()
|
|
34
|
+
hello(): string {
|
|
35
|
+
return "Hello World";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Authenticated users only
|
|
39
|
+
@Query(() => [Todo], { description: "User's todos" })
|
|
40
|
+
@Authed()
|
|
41
|
+
async myTodos(@Context() { req }: GraphQLContext): Promise<Todo[]> {
|
|
42
|
+
return this.todoService.findByUser(req.user.id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Group-based access
|
|
46
|
+
@Mutation(() => Boolean, { description: "Admin-only operation" })
|
|
47
|
+
@Groups("ADMINS", "MODERATORS")
|
|
48
|
+
async deleteAllSpam(): Promise<boolean> {
|
|
49
|
+
return this.spamService.deleteAll();
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Ownership Checks in Mutations
|
|
54
|
+
|
|
55
|
+
Since `@Owner` doesn't work at operation level, check ownership in resolver:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
@Mutation(() => Todo, { description: "Update a todo" })
|
|
59
|
+
@Authed()
|
|
60
|
+
async updateTodo(
|
|
61
|
+
@Args("id", { type: () => ID }) id: string,
|
|
62
|
+
@Args("input") input: UpdateTodoInput,
|
|
63
|
+
@Context() { req }: GraphQLContext
|
|
64
|
+
): Promise<Todo> {
|
|
65
|
+
const todo = await this.todoService.findById(id);
|
|
66
|
+
|
|
67
|
+
if (!todo) {
|
|
68
|
+
throw new GraphQLError("Todo not found", {
|
|
69
|
+
extensions: { code: "NOT_FOUND" },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (todo.ownerId !== req.user.id) {
|
|
74
|
+
throw new GraphQLError("Not authorized to update this todo", {
|
|
75
|
+
extensions: { code: "UNAUTHORIZED" },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return this.todoService.update(id, input);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Field-Level Auth
|
|
84
|
+
|
|
85
|
+
For protecting sensitive fields on object types:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { FieldAuth } from "../auth";
|
|
89
|
+
import { AuthLevel } from "../auth/auth.types";
|
|
90
|
+
|
|
91
|
+
@ObjectType()
|
|
92
|
+
export class User {
|
|
93
|
+
@Field()
|
|
94
|
+
name: string;
|
|
95
|
+
|
|
96
|
+
// Only owner or admins can see email
|
|
97
|
+
@Field()
|
|
98
|
+
@FieldAuth({
|
|
99
|
+
read: [AuthLevel.OWNER, AuthLevel.GROUPS],
|
|
100
|
+
write: [AuthLevel.OWNER],
|
|
101
|
+
groups: ["ADMINS"],
|
|
102
|
+
ownerField: "id",
|
|
103
|
+
})
|
|
104
|
+
email: string;
|
|
105
|
+
|
|
106
|
+
// Only owner can see SSN
|
|
107
|
+
@Field()
|
|
108
|
+
@FieldAuth({
|
|
109
|
+
read: [AuthLevel.OWNER],
|
|
110
|
+
write: [AuthLevel.OWNER],
|
|
111
|
+
ownerField: "id",
|
|
112
|
+
})
|
|
113
|
+
ssn: string;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Auth Types
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
interface AuthUser {
|
|
121
|
+
readonly id: string;
|
|
122
|
+
readonly sub: string;
|
|
123
|
+
readonly groups?: readonly string[];
|
|
124
|
+
readonly organizationId?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface GraphQLContext {
|
|
128
|
+
readonly req: {
|
|
129
|
+
readonly user?: AuthUser;
|
|
130
|
+
};
|
|
131
|
+
readonly res: Response;
|
|
132
|
+
readonly loaders: IDataLoaders;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## DataLoader Integration
|
|
137
|
+
|
|
138
|
+
### Architecture Overview
|
|
139
|
+
|
|
140
|
+
DataLoader prevents N+1 queries by batching and caching database requests within a single GraphQL request.
|
|
141
|
+
|
|
142
|
+
**Key Concepts:**
|
|
143
|
+
1. **Per-Request Fresh Loaders**: Each GraphQL request gets new DataLoader instances
|
|
144
|
+
2. **Batch Loading**: Multiple `.load()` calls are batched together
|
|
145
|
+
3. **Order Preservation**: Results return in same order as input keys
|
|
146
|
+
|
|
147
|
+
### IDataLoaders Interface
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// src/data-loader/data-loader.interface.ts
|
|
151
|
+
import DataLoader from "dataloader";
|
|
152
|
+
|
|
153
|
+
export interface IDataLoaders {
|
|
154
|
+
readonly usersLoader: DataLoader<string, User | null>;
|
|
155
|
+
readonly postsByAuthorLoader: DataLoader<string, Post[]>;
|
|
156
|
+
readonly commentsLoader: DataLoader<string, Comment[]>;
|
|
157
|
+
// Add new loaders here as needed
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### DataLoaderService
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// src/data-loader/data-loader.service.ts
|
|
165
|
+
import { Injectable } from "@nestjs/common";
|
|
166
|
+
import DataLoader from "dataloader";
|
|
167
|
+
|
|
168
|
+
@Injectable()
|
|
169
|
+
export class DataLoaderService {
|
|
170
|
+
constructor(
|
|
171
|
+
private readonly userService: UserService,
|
|
172
|
+
private readonly postService: PostService
|
|
173
|
+
) {}
|
|
174
|
+
|
|
175
|
+
getLoaders(): IDataLoaders {
|
|
176
|
+
return {
|
|
177
|
+
usersLoader: this.createUsersLoader(),
|
|
178
|
+
postsByAuthorLoader: this.createPostsByAuthorLoader(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private createUsersLoader(): DataLoader<string, User | null> {
|
|
183
|
+
return new DataLoader<string, User | null>(async (ids) => {
|
|
184
|
+
const users = await this.userService.findByIds([...ids]);
|
|
185
|
+
const userMap = new Map(users.map(u => [u.id, u]));
|
|
186
|
+
return ids.map(id => userMap.get(id) ?? null);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private createPostsByAuthorLoader(): DataLoader<string, Post[]> {
|
|
191
|
+
return new DataLoader<string, Post[]>(async (authorIds) => {
|
|
192
|
+
const posts = await this.postService.findByAuthorIds([...authorIds]);
|
|
193
|
+
const postMap = new Map<string, Post[]>();
|
|
194
|
+
|
|
195
|
+
for (const post of posts) {
|
|
196
|
+
const existing = postMap.get(post.authorId) ?? [];
|
|
197
|
+
postMap.set(post.authorId, [...existing, post]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return authorIds.map(id => postMap.get(id) ?? []);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Using DataLoader in Resolvers
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
@Resolver(() => Post)
|
|
210
|
+
export class PostResolver {
|
|
211
|
+
@ResolveField(() => User, { description: "Post author" })
|
|
212
|
+
async author(
|
|
213
|
+
@Parent() post: Post,
|
|
214
|
+
@Context() { loaders }: GraphQLContext
|
|
215
|
+
): Promise<User | null> {
|
|
216
|
+
if (!post.authorId) return null;
|
|
217
|
+
return loaders.usersLoader.load(post.authorId);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@Resolver(() => User)
|
|
222
|
+
export class UserResolver {
|
|
223
|
+
@ResolveField(() => [Post], { description: "User's posts" })
|
|
224
|
+
async posts(
|
|
225
|
+
@Parent() user: User,
|
|
226
|
+
@Context() { loaders }: GraphQLContext
|
|
227
|
+
): Promise<Post[]> {
|
|
228
|
+
return loaders.postsByAuthorLoader.load(user.id);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Adding a New DataLoader
|
|
234
|
+
|
|
235
|
+
1. Add batch method to service:
|
|
236
|
+
```typescript
|
|
237
|
+
async findByIds(ids: readonly string[]): Promise<Entity[]> {
|
|
238
|
+
return this.repository.findBy({ id: In([...ids]) });
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
2. Add loader type to interface:
|
|
243
|
+
```typescript
|
|
244
|
+
export interface IDataLoaders {
|
|
245
|
+
readonly entityLoader: DataLoader<string, Entity | null>;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
3. Create loader in service:
|
|
250
|
+
```typescript
|
|
251
|
+
private createEntityLoader(): DataLoader<string, Entity | null> {
|
|
252
|
+
return new DataLoader(async (ids) => {
|
|
253
|
+
const entities = await this.entityService.findByIds([...ids]);
|
|
254
|
+
const map = new Map(entities.map(e => [e.id, e]));
|
|
255
|
+
return ids.map(id => map.get(id) ?? null);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
4. Add to getLoaders():
|
|
261
|
+
```typescript
|
|
262
|
+
getLoaders(): IDataLoaders {
|
|
263
|
+
return {
|
|
264
|
+
entityLoader: this.createEntityLoader(),
|
|
265
|
+
// ... other loaders
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
5. Use in resolver:
|
|
271
|
+
```typescript
|
|
272
|
+
@ResolveField(() => Entity, { nullable: true })
|
|
273
|
+
async entity(
|
|
274
|
+
@Parent() parent: Parent,
|
|
275
|
+
@Context() { loaders }: GraphQLContext
|
|
276
|
+
): Promise<Entity | null> {
|
|
277
|
+
if (!parent.entityId) return null;
|
|
278
|
+
return loaders.entityLoader.load(parent.entityId);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## GraphQL Documentation Standards
|
|
283
|
+
|
|
284
|
+
### Documentation Requirements
|
|
285
|
+
|
|
286
|
+
**Every element must be documented:**
|
|
287
|
+
- Types (`@ObjectType`)
|
|
288
|
+
- Fields (`@Field`)
|
|
289
|
+
- Queries (`@Query`)
|
|
290
|
+
- Mutations (`@Mutation`)
|
|
291
|
+
- Arguments (`@Args`)
|
|
292
|
+
- Input types (`@InputType`)
|
|
293
|
+
- Enums (via `registerEnumType`)
|
|
294
|
+
|
|
295
|
+
### Documentation Pattern
|
|
296
|
+
|
|
297
|
+
Use both JSDoc (for code) AND `description` option (for introspection):
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
/**
|
|
301
|
+
* A user account in the system.
|
|
302
|
+
*
|
|
303
|
+
* Represents an authenticated user with profile information.
|
|
304
|
+
*/
|
|
305
|
+
@ObjectType({
|
|
306
|
+
description: "A user account with profile information",
|
|
307
|
+
})
|
|
308
|
+
export class User {
|
|
309
|
+
/**
|
|
310
|
+
* The user's unique identifier.
|
|
311
|
+
*/
|
|
312
|
+
@Field(() => ID, { description: "The user's unique identifier" })
|
|
313
|
+
id: string;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The user's email address.
|
|
317
|
+
*
|
|
318
|
+
* Used for authentication and notifications.
|
|
319
|
+
*/
|
|
320
|
+
@Field(() => String, {
|
|
321
|
+
description: "Email address for authentication and notifications",
|
|
322
|
+
})
|
|
323
|
+
email: string;
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Query/Mutation Documentation
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
/**
|
|
331
|
+
* Retrieves a user by their unique identifier.
|
|
332
|
+
*
|
|
333
|
+
* @param id - The unique identifier of the user
|
|
334
|
+
* @returns The user if found, null otherwise
|
|
335
|
+
*/
|
|
336
|
+
@Query(() => User, {
|
|
337
|
+
nullable: true,
|
|
338
|
+
description: "Retrieves a user by ID. Returns null if not found.",
|
|
339
|
+
})
|
|
340
|
+
@Authed()
|
|
341
|
+
async user(
|
|
342
|
+
@Args("id", { description: "The unique identifier" }) id: string
|
|
343
|
+
): Promise<User | null> {
|
|
344
|
+
return this.userService.findById(id);
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Enum Documentation
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
export enum OrderStatus {
|
|
352
|
+
PENDING = "PENDING",
|
|
353
|
+
CONFIRMED = "CONFIRMED",
|
|
354
|
+
SHIPPED = "SHIPPED",
|
|
355
|
+
DELIVERED = "DELIVERED",
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
registerEnumType(OrderStatus, {
|
|
359
|
+
name: "OrderStatus",
|
|
360
|
+
description: "Status of an order in the fulfillment pipeline",
|
|
361
|
+
valuesMap: {
|
|
362
|
+
PENDING: { description: "Order received but not yet processed" },
|
|
363
|
+
CONFIRMED: { description: "Order confirmed and payment received" },
|
|
364
|
+
SHIPPED: { description: "Order has been shipped" },
|
|
365
|
+
DELIVERED: { description: "Order delivered to customer" },
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Description Guidelines
|
|
371
|
+
|
|
372
|
+
| Element | Guidelines |
|
|
373
|
+
|---------|------------|
|
|
374
|
+
| **First line** | Complete, concise sentence (imperative for actions) |
|
|
375
|
+
| **Constraints** | Document max/min values, valid formats |
|
|
376
|
+
| **Nullability** | Explain null vs empty semantics |
|
|
377
|
+
| **Units** | Always specify: "Duration in milliseconds" |
|
|
378
|
+
| **Defaults** | Document default values: "Default: 20" |
|
|
379
|
+
| **Examples** | Include for complex fields: "Example: 2024-01-15" |
|
|
380
|
+
|
|
381
|
+
### Naming Conventions
|
|
382
|
+
|
|
383
|
+
| Element | Convention | Example |
|
|
384
|
+
|---------|------------|---------|
|
|
385
|
+
| Types | PascalCase, singular | `User`, `OrderItem` |
|
|
386
|
+
| Fields | camelCase | `firstName`, `orderDate` |
|
|
387
|
+
| Arguments | camelCase | `userId`, `pageSize` |
|
|
388
|
+
| Enums | PascalCase | `OrderStatus` |
|
|
389
|
+
| Enum Values | SCREAMING_SNAKE_CASE | `PENDING`, `IN_PROGRESS` |
|
|
390
|
+
| Input Types | PascalCase + `Input` | `CreateUserInput` |
|
|
391
|
+
| Mutations | verb + object | `createUser`, `updateOrder` |
|
|
392
|
+
| Queries | noun (no verb prefix) | `user` not `getUser` |
|
|
393
|
+
|
|
394
|
+
### Deprecation
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
@Field(() => String, {
|
|
398
|
+
nullable: true,
|
|
399
|
+
deprecationReason: "Use `primaryEmail` instead. Will be removed in v3.0.",
|
|
400
|
+
description: "The user's email address.",
|
|
401
|
+
})
|
|
402
|
+
email?: string;
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Module Structure
|
|
406
|
+
|
|
407
|
+
### Feature Module Pattern
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
src/
|
|
411
|
+
├── users/
|
|
412
|
+
│ ├── users.module.ts # Module definition
|
|
413
|
+
│ ├── users.resolver.ts # GraphQL resolver
|
|
414
|
+
│ ├── users.resolver.test.ts
|
|
415
|
+
│ ├── users.service.ts # Business logic
|
|
416
|
+
│ ├── users.service.test.ts
|
|
417
|
+
│ ├── dto/
|
|
418
|
+
│ │ ├── create-user.input.ts
|
|
419
|
+
│ │ └── update-user.input.ts
|
|
420
|
+
│ └── entities/
|
|
421
|
+
│ └── user.entity.ts # ObjectType + TypeORM entity
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Module Registration
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
// src/users/users.module.ts
|
|
428
|
+
@Module({
|
|
429
|
+
providers: [UsersService, UsersResolver],
|
|
430
|
+
exports: [UsersService],
|
|
431
|
+
})
|
|
432
|
+
export class UsersModule {}
|
|
433
|
+
|
|
434
|
+
// src/app.module.ts
|
|
435
|
+
@Module({
|
|
436
|
+
imports: [
|
|
437
|
+
GraphQLModule.forRootAsync({ /* ... */ }),
|
|
438
|
+
DataLoaderModule,
|
|
439
|
+
UsersModule,
|
|
440
|
+
PostsModule,
|
|
441
|
+
],
|
|
442
|
+
})
|
|
443
|
+
export class AppModule {}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Error Handling Patterns
|
|
447
|
+
|
|
448
|
+
### Standard Error Response
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { GraphQLError } from "graphql";
|
|
452
|
+
|
|
453
|
+
// Not found
|
|
454
|
+
throw new GraphQLError("User not found", {
|
|
455
|
+
extensions: { code: "NOT_FOUND", id },
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Unauthorized
|
|
459
|
+
throw new GraphQLError("Not authorized", {
|
|
460
|
+
extensions: { code: "UNAUTHORIZED" },
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Validation
|
|
464
|
+
throw new GraphQLError("Invalid email format", {
|
|
465
|
+
extensions: { code: "VALIDATION_ERROR", field: "email" },
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Business logic
|
|
469
|
+
throw new GraphQLError("Insufficient funds", {
|
|
470
|
+
extensions: { code: "BUSINESS_ERROR", balance: 100, required: 150 },
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Error Codes
|
|
475
|
+
|
|
476
|
+
| Code | HTTP Equivalent | Use Case |
|
|
477
|
+
|------|-----------------|----------|
|
|
478
|
+
| `NOT_FOUND` | 404 | Resource doesn't exist |
|
|
479
|
+
| `UNAUTHORIZED` | 401 | Not authenticated |
|
|
480
|
+
| `FORBIDDEN` | 403 | Not authorized |
|
|
481
|
+
| `VALIDATION_ERROR` | 400 | Invalid input |
|
|
482
|
+
| `BUSINESS_ERROR` | 422 | Business rule violation |
|
|
483
|
+
| `INTERNAL_ERROR` | 500 | Server error |
|