forge-server 0.1.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/.claude/hooks/worktree-create.sh +64 -0
- package/.claude/hooks/worktree-remove.sh +57 -0
- package/.claude/settings.local.json +29 -0
- package/.forge/knowledge/conventions.yaml +1 -0
- package/.forge/knowledge/decisions.yaml +1 -0
- package/.forge/knowledge/gotchas.yaml +1 -0
- package/.forge/knowledge/patterns.yaml +1 -0
- package/.forge/manifest.yaml +6 -0
- package/CLAUDE.md +144 -0
- package/bin/setup-forge.sh +132 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +553 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/codebase.d.ts +57 -0
- package/dist/context/codebase.d.ts.map +1 -0
- package/dist/context/codebase.js +301 -0
- package/dist/context/codebase.js.map +1 -0
- package/dist/context/injector.d.ts +147 -0
- package/dist/context/injector.d.ts.map +1 -0
- package/dist/context/injector.js +533 -0
- package/dist/context/injector.js.map +1 -0
- package/dist/context/memory.d.ts +32 -0
- package/dist/context/memory.d.ts.map +1 -0
- package/dist/context/memory.js +140 -0
- package/dist/context/memory.js.map +1 -0
- package/dist/context/session-index.d.ts +54 -0
- package/dist/context/session-index.d.ts.map +1 -0
- package/dist/context/session-index.js +265 -0
- package/dist/context/session-index.js.map +1 -0
- package/dist/context/session.d.ts +42 -0
- package/dist/context/session.d.ts.map +1 -0
- package/dist/context/session.js +121 -0
- package/dist/context/session.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion/chunker.d.ts +19 -0
- package/dist/ingestion/chunker.d.ts.map +1 -0
- package/dist/ingestion/chunker.js +189 -0
- package/dist/ingestion/chunker.js.map +1 -0
- package/dist/ingestion/embedder.d.ts +45 -0
- package/dist/ingestion/embedder.d.ts.map +1 -0
- package/dist/ingestion/embedder.js +152 -0
- package/dist/ingestion/embedder.js.map +1 -0
- package/dist/ingestion/git-analyzer.d.ts +77 -0
- package/dist/ingestion/git-analyzer.d.ts.map +1 -0
- package/dist/ingestion/git-analyzer.js +437 -0
- package/dist/ingestion/git-analyzer.js.map +1 -0
- package/dist/ingestion/indexer.d.ts +79 -0
- package/dist/ingestion/indexer.d.ts.map +1 -0
- package/dist/ingestion/indexer.js +766 -0
- package/dist/ingestion/indexer.js.map +1 -0
- package/dist/ingestion/markdown-chunker.d.ts +19 -0
- package/dist/ingestion/markdown-chunker.d.ts.map +1 -0
- package/dist/ingestion/markdown-chunker.js +243 -0
- package/dist/ingestion/markdown-chunker.js.map +1 -0
- package/dist/ingestion/markdown-knowledge.d.ts +21 -0
- package/dist/ingestion/markdown-knowledge.d.ts.map +1 -0
- package/dist/ingestion/markdown-knowledge.js +129 -0
- package/dist/ingestion/markdown-knowledge.js.map +1 -0
- package/dist/ingestion/parser.d.ts +20 -0
- package/dist/ingestion/parser.d.ts.map +1 -0
- package/dist/ingestion/parser.js +429 -0
- package/dist/ingestion/parser.js.map +1 -0
- package/dist/ingestion/watcher.d.ts +28 -0
- package/dist/ingestion/watcher.d.ts.map +1 -0
- package/dist/ingestion/watcher.js +147 -0
- package/dist/ingestion/watcher.js.map +1 -0
- package/dist/knowledge/hydrator.d.ts +37 -0
- package/dist/knowledge/hydrator.d.ts.map +1 -0
- package/dist/knowledge/hydrator.js +220 -0
- package/dist/knowledge/hydrator.js.map +1 -0
- package/dist/knowledge/registry.d.ts +129 -0
- package/dist/knowledge/registry.d.ts.map +1 -0
- package/dist/knowledge/registry.js +361 -0
- package/dist/knowledge/registry.js.map +1 -0
- package/dist/knowledge/search.d.ts +114 -0
- package/dist/knowledge/search.d.ts.map +1 -0
- package/dist/knowledge/search.js +428 -0
- package/dist/knowledge/search.js.map +1 -0
- package/dist/knowledge/store.d.ts +76 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +230 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/learning/confidence.d.ts +30 -0
- package/dist/learning/confidence.d.ts.map +1 -0
- package/dist/learning/confidence.js +165 -0
- package/dist/learning/confidence.js.map +1 -0
- package/dist/learning/patterns.d.ts +52 -0
- package/dist/learning/patterns.d.ts.map +1 -0
- package/dist/learning/patterns.js +290 -0
- package/dist/learning/patterns.js.map +1 -0
- package/dist/learning/trajectory.d.ts +55 -0
- package/dist/learning/trajectory.d.ts.map +1 -0
- package/dist/learning/trajectory.js +200 -0
- package/dist/learning/trajectory.js.map +1 -0
- package/dist/memory/memory-compat.d.ts +100 -0
- package/dist/memory/memory-compat.d.ts.map +1 -0
- package/dist/memory/memory-compat.js +146 -0
- package/dist/memory/memory-compat.js.map +1 -0
- package/dist/memory/observation-store.d.ts +57 -0
- package/dist/memory/observation-store.d.ts.map +1 -0
- package/dist/memory/observation-store.js +154 -0
- package/dist/memory/observation-store.js.map +1 -0
- package/dist/memory/session-tracker.d.ts +81 -0
- package/dist/memory/session-tracker.d.ts.map +1 -0
- package/dist/memory/session-tracker.js +262 -0
- package/dist/memory/session-tracker.js.map +1 -0
- package/dist/pipeline/engine.d.ts +179 -0
- package/dist/pipeline/engine.d.ts.map +1 -0
- package/dist/pipeline/engine.js +691 -0
- package/dist/pipeline/engine.js.map +1 -0
- package/dist/pipeline/events.d.ts +54 -0
- package/dist/pipeline/events.d.ts.map +1 -0
- package/dist/pipeline/events.js +157 -0
- package/dist/pipeline/events.js.map +1 -0
- package/dist/pipeline/parallel.d.ts +83 -0
- package/dist/pipeline/parallel.d.ts.map +1 -0
- package/dist/pipeline/parallel.js +277 -0
- package/dist/pipeline/parallel.js.map +1 -0
- package/dist/pipeline/state-machine.d.ts +65 -0
- package/dist/pipeline/state-machine.d.ts.map +1 -0
- package/dist/pipeline/state-machine.js +176 -0
- package/dist/pipeline/state-machine.js.map +1 -0
- package/dist/query/graph-queries.d.ts +84 -0
- package/dist/query/graph-queries.d.ts.map +1 -0
- package/dist/query/graph-queries.js +216 -0
- package/dist/query/graph-queries.js.map +1 -0
- package/dist/query/hybrid-search.d.ts +34 -0
- package/dist/query/hybrid-search.d.ts.map +1 -0
- package/dist/query/hybrid-search.js +263 -0
- package/dist/query/hybrid-search.js.map +1 -0
- package/dist/query/intent-detector.d.ts +35 -0
- package/dist/query/intent-detector.d.ts.map +1 -0
- package/dist/query/intent-detector.js +115 -0
- package/dist/query/intent-detector.js.map +1 -0
- package/dist/query/ranking.d.ts +57 -0
- package/dist/query/ranking.d.ts.map +1 -0
- package/dist/query/ranking.js +109 -0
- package/dist/query/ranking.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +291 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/falkordb-store.d.ts +73 -0
- package/dist/storage/falkordb-store.d.ts.map +1 -0
- package/dist/storage/falkordb-store.js +346 -0
- package/dist/storage/falkordb-store.js.map +1 -0
- package/dist/storage/file-cache.d.ts +32 -0
- package/dist/storage/file-cache.d.ts.map +1 -0
- package/dist/storage/file-cache.js +115 -0
- package/dist/storage/file-cache.js.map +1 -0
- package/dist/storage/interfaces.d.ts +151 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +7 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/qdrant-store.d.ts +110 -0
- package/dist/storage/qdrant-store.d.ts.map +1 -0
- package/dist/storage/qdrant-store.js +467 -0
- package/dist/storage/qdrant-store.js.map +1 -0
- package/dist/storage/schema.d.ts +4 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +136 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/storage/sqlite.d.ts +35 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +132 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/tools/collaboration-tools.d.ts +111 -0
- package/dist/tools/collaboration-tools.d.ts.map +1 -0
- package/dist/tools/collaboration-tools.js +174 -0
- package/dist/tools/collaboration-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +293 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +437 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +129 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +237 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/ingestion-tools.d.ts +96 -0
- package/dist/tools/ingestion-tools.d.ts.map +1 -0
- package/dist/tools/ingestion-tools.js +90 -0
- package/dist/tools/ingestion-tools.js.map +1 -0
- package/dist/tools/learning-tools.d.ts +168 -0
- package/dist/tools/learning-tools.d.ts.map +1 -0
- package/dist/tools/learning-tools.js +158 -0
- package/dist/tools/learning-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +183 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +197 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/phase-tools.d.ts +954 -0
- package/dist/tools/phase-tools.d.ts.map +1 -0
- package/dist/tools/phase-tools.js +1215 -0
- package/dist/tools/phase-tools.js.map +1 -0
- package/dist/tools/pipeline-tools.d.ts +140 -0
- package/dist/tools/pipeline-tools.d.ts.map +1 -0
- package/dist/tools/pipeline-tools.js +162 -0
- package/dist/tools/pipeline-tools.js.map +1 -0
- package/dist/tools/registration-tools.d.ts +220 -0
- package/dist/tools/registration-tools.d.ts.map +1 -0
- package/dist/tools/registration-tools.js +391 -0
- package/dist/tools/registration-tools.js.map +1 -0
- package/dist/util/circuit-breaker.d.ts +75 -0
- package/dist/util/circuit-breaker.d.ts.map +1 -0
- package/dist/util/circuit-breaker.js +159 -0
- package/dist/util/circuit-breaker.js.map +1 -0
- package/dist/util/config.d.ts +23 -0
- package/dist/util/config.d.ts.map +1 -0
- package/dist/util/config.js +164 -0
- package/dist/util/config.js.map +1 -0
- package/dist/util/logger.d.ts +13 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +45 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/token-counter.d.ts +24 -0
- package/dist/util/token-counter.d.ts.map +1 -0
- package/dist/util/token-counter.js +48 -0
- package/dist/util/token-counter.js.map +1 -0
- package/dist/util/types.d.ts +525 -0
- package/dist/util/types.d.ts.map +1 -0
- package/dist/util/types.js +5 -0
- package/dist/util/types.js.map +1 -0
- package/docker-compose.yml +20 -0
- package/docs/plans/2026-02-27-swarm-coordination/architecture.md +203 -0
- package/docs/plans/2026-02-27-swarm-coordination/vision.md +57 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/architecture.md +1 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/vision.md +300 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/architecture.md +480 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/verification-checklist.md +462 -0
- package/docs/plans/completed/2026-02-27-git-history-atlassian/git-jira-plan.md +181 -0
- package/package.json +39 -0
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/README.md +134 -0
- package/plugin/agents/architect.md +367 -0
- package/plugin/agents/backend-specialist.md +263 -0
- package/plugin/agents/brainstormer.md +122 -0
- package/plugin/agents/data-specialist.md +266 -0
- package/plugin/agents/designer.md +408 -0
- package/plugin/agents/frontend-specialist.md +241 -0
- package/plugin/agents/inspector.md +406 -0
- package/plugin/agents/knowledge-keeper.md +443 -0
- package/plugin/agents/platform-engineer.md +326 -0
- package/plugin/agents/product-manager.md +268 -0
- package/plugin/agents/product-owner.md +438 -0
- package/plugin/agents/pulse-checker.md +73 -0
- package/plugin/agents/qa-strategist.md +500 -0
- package/plugin/agents/self-improver.md +310 -0
- package/plugin/agents/strategist.md +360 -0
- package/plugin/agents/supervisor.md +380 -0
- package/plugin/commands/brainstorm.md +25 -0
- package/plugin/commands/forge.md +88 -0
- package/plugin/docs/atlassian-integration.md +110 -0
- package/plugin/docs/workflow.md +126 -0
- package/plugin/skills/agent-development/.skillfish.json +10 -0
- package/plugin/skills/agent-development/SKILL.md +415 -0
- package/plugin/skills/agent-development/examples/agent-creation-prompt.md +238 -0
- package/plugin/skills/agent-development/examples/complete-agent-examples.md +427 -0
- package/plugin/skills/agent-development/references/agent-creation-system-prompt.md +207 -0
- package/plugin/skills/agent-development/references/system-prompt-design.md +411 -0
- package/plugin/skills/agent-development/references/triggering-examples.md +491 -0
- package/plugin/skills/agent-development/scripts/validate-agent.sh +217 -0
- package/plugin/skills/agent-handoff/SKILL.md +335 -0
- package/plugin/skills/anti-stub/SKILL.md +317 -0
- package/plugin/skills/brainstorm/SKILL.md +31 -0
- package/plugin/skills/debugging/SKILL.md +276 -0
- package/plugin/skills/fix/SKILL.md +62 -0
- package/plugin/skills/frontend-design/.skillfish.json +10 -0
- package/plugin/skills/frontend-design/SKILL.md +42 -0
- package/plugin/skills/gotchas/SKILL.md +61 -0
- package/plugin/skills/graph-orchestrator/SKILL.md +38 -0
- package/plugin/skills/history/SKILL.md +58 -0
- package/plugin/skills/impact/SKILL.md +59 -0
- package/plugin/skills/implementation-execution/SKILL.md +291 -0
- package/plugin/skills/index-repo/SKILL.md +55 -0
- package/plugin/skills/interviewing/SKILL.md +225 -0
- package/plugin/skills/knowledge-curation/SKILL.md +393 -0
- package/plugin/skills/learn/SKILL.md +69 -0
- package/plugin/skills/mcp-integration/.skillfish.json +10 -0
- package/plugin/skills/mcp-integration/SKILL.md +554 -0
- package/plugin/skills/mcp-integration/examples/http-server.json +20 -0
- package/plugin/skills/mcp-integration/examples/sse-server.json +19 -0
- package/plugin/skills/mcp-integration/examples/stdio-server.json +26 -0
- package/plugin/skills/mcp-integration/references/authentication.md +549 -0
- package/plugin/skills/mcp-integration/references/server-types.md +536 -0
- package/plugin/skills/mcp-integration/references/tool-usage.md +538 -0
- package/plugin/skills/nestjs/.skillfish.json +10 -0
- package/plugin/skills/nestjs/SKILL.md +669 -0
- package/plugin/skills/nestjs/drizzle-reference.md +1894 -0
- package/plugin/skills/nestjs/reference.md +1447 -0
- package/plugin/skills/nestjs/workflow-optimization.md +229 -0
- package/plugin/skills/parallel-dispatch/SKILL.md +308 -0
- package/plugin/skills/project-discovery/SKILL.md +304 -0
- package/plugin/skills/search/SKILL.md +56 -0
- package/plugin/skills/security-audit/SKILL.md +362 -0
- package/plugin/skills/skill-development/.skillfish.json +10 -0
- package/plugin/skills/skill-development/SKILL.md +637 -0
- package/plugin/skills/skill-development/references/skill-creator-original.md +209 -0
- package/plugin/skills/tdd/SKILL.md +273 -0
- package/plugin/skills/terminal-presentation/SKILL.md +395 -0
- package/plugin/skills/test-strategy/SKILL.md +365 -0
- package/plugin/skills/verification-protocol/SKILL.md +256 -0
- package/plugin/skills/visual-explainer/CHANGELOG.md +97 -0
- package/plugin/skills/visual-explainer/LICENSE +21 -0
- package/plugin/skills/visual-explainer/README.md +137 -0
- package/plugin/skills/visual-explainer/SKILL.md +352 -0
- package/plugin/skills/visual-explainer/banner.png +0 -0
- package/plugin/skills/visual-explainer/package.json +11 -0
- package/plugin/skills/visual-explainer/prompts/diff-review.md +68 -0
- package/plugin/skills/visual-explainer/prompts/fact-check.md +63 -0
- package/plugin/skills/visual-explainer/prompts/generate-slides.md +18 -0
- package/plugin/skills/visual-explainer/prompts/generate-web-diagram.md +10 -0
- package/plugin/skills/visual-explainer/prompts/plan-review.md +86 -0
- package/plugin/skills/visual-explainer/prompts/project-recap.md +61 -0
- package/plugin/skills/visual-explainer/references/css-patterns.md +1188 -0
- package/plugin/skills/visual-explainer/references/libraries.md +470 -0
- package/plugin/skills/visual-explainer/references/responsive-nav.md +212 -0
- package/plugin/skills/visual-explainer/references/slide-patterns.md +1403 -0
- package/plugin/skills/visual-explainer/templates/architecture.html +596 -0
- package/plugin/skills/visual-explainer/templates/data-table.html +540 -0
- package/plugin/skills/visual-explainer/templates/mermaid-flowchart.html +435 -0
- package/plugin/skills/visual-explainer/templates/slide-deck.html +913 -0
- package/src/cli.ts +655 -0
- package/src/context/.gitkeep +0 -0
- package/src/context/codebase.ts +393 -0
- package/src/context/injector.ts +797 -0
- package/src/context/memory.ts +187 -0
- package/src/context/session-index.ts +327 -0
- package/src/context/session.ts +152 -0
- package/src/index.ts +47 -0
- package/src/ingestion/.gitkeep +0 -0
- package/src/ingestion/chunker.ts +277 -0
- package/src/ingestion/embedder.ts +167 -0
- package/src/ingestion/git-analyzer.ts +545 -0
- package/src/ingestion/indexer.ts +984 -0
- package/src/ingestion/markdown-chunker.ts +337 -0
- package/src/ingestion/markdown-knowledge.ts +175 -0
- package/src/ingestion/parser.ts +475 -0
- package/src/ingestion/watcher.ts +182 -0
- package/src/knowledge/.gitkeep +0 -0
- package/src/knowledge/hydrator.ts +246 -0
- package/src/knowledge/registry.ts +463 -0
- package/src/knowledge/search.ts +565 -0
- package/src/knowledge/store.ts +262 -0
- package/src/learning/.gitkeep +0 -0
- package/src/learning/confidence.ts +193 -0
- package/src/learning/patterns.ts +360 -0
- package/src/learning/trajectory.ts +268 -0
- package/src/memory/.gitkeep +0 -0
- package/src/memory/memory-compat.ts +233 -0
- package/src/memory/observation-store.ts +224 -0
- package/src/memory/session-tracker.ts +332 -0
- package/src/pipeline/.gitkeep +0 -0
- package/src/pipeline/engine.ts +1139 -0
- package/src/pipeline/events.ts +253 -0
- package/src/pipeline/parallel.ts +394 -0
- package/src/pipeline/state-machine.ts +199 -0
- package/src/query/.gitkeep +0 -0
- package/src/query/graph-queries.ts +262 -0
- package/src/query/hybrid-search.ts +337 -0
- package/src/query/intent-detector.ts +131 -0
- package/src/query/ranking.ts +161 -0
- package/src/server.ts +352 -0
- package/src/storage/.gitkeep +0 -0
- package/src/storage/falkordb-store.ts +388 -0
- package/src/storage/file-cache.ts +141 -0
- package/src/storage/interfaces.ts +201 -0
- package/src/storage/qdrant-store.ts +557 -0
- package/src/storage/schema.ts +139 -0
- package/src/storage/sqlite.ts +168 -0
- package/src/tools/.gitkeep +0 -0
- package/src/tools/collaboration-tools.ts +208 -0
- package/src/tools/context-tools.ts +493 -0
- package/src/tools/graph-tools.ts +295 -0
- package/src/tools/ingestion-tools.ts +122 -0
- package/src/tools/learning-tools.ts +181 -0
- package/src/tools/memory-tools.ts +234 -0
- package/src/tools/phase-tools.ts +1452 -0
- package/src/tools/pipeline-tools.ts +188 -0
- package/src/tools/registration-tools.ts +450 -0
- package/src/util/.gitkeep +0 -0
- package/src/util/circuit-breaker.ts +193 -0
- package/src/util/config.ts +177 -0
- package/src/util/logger.ts +53 -0
- package/src/util/token-counter.ts +52 -0
- package/src/util/types.ts +710 -0
- package/tests/context/.gitkeep +0 -0
- package/tests/integration/.gitkeep +0 -0
- package/tests/knowledge/.gitkeep +0 -0
- package/tests/learning/.gitkeep +0 -0
- package/tests/pipeline/.gitkeep +0 -0
- package/tests/tools/.gitkeep +0 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +10 -0
- package/vscode-extension/.vscodeignore +7 -0
- package/vscode-extension/README.md +43 -0
- package/vscode-extension/out/edge-collector.js +274 -0
- package/vscode-extension/out/edge-collector.js.map +1 -0
- package/vscode-extension/out/extension.js +264 -0
- package/vscode-extension/out/extension.js.map +1 -0
- package/vscode-extension/out/forge-client.js +318 -0
- package/vscode-extension/out/forge-client.js.map +1 -0
- package/vscode-extension/package-lock.json +59 -0
- package/vscode-extension/package.json +71 -0
- package/vscode-extension/src/edge-collector.ts +320 -0
- package/vscode-extension/src/extension.ts +269 -0
- package/vscode-extension/src/forge-client.ts +364 -0
- package/vscode-extension/tsconfig.json +19 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs
|
|
3
|
+
description: Comprehensive NestJS framework guide with Drizzle ORM integration. Use when building NestJS applications, setting up APIs, implementing authentication, working with databases, or integrating Drizzle ORM. Covers controllers, providers, modules, middleware, guards, interceptors, testing, microservices, GraphQL, and database patterns.
|
|
4
|
+
license: Complete terms in LICENSE.txt
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# NestJS Framework with Drizzle ORM
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
- Building REST APIs or GraphQL servers with NestJS
|
|
11
|
+
- Setting up authentication and authorization
|
|
12
|
+
- Implementing middleware, guards, or interceptors
|
|
13
|
+
- Working with databases (TypeORM, Drizzle ORM)
|
|
14
|
+
- Creating microservices architecture
|
|
15
|
+
- Writing unit and integration tests
|
|
16
|
+
- Setting up OpenAPI/Swagger documentation
|
|
17
|
+
|
|
18
|
+
## Core Architecture
|
|
19
|
+
|
|
20
|
+
### Module Structure
|
|
21
|
+
```typescript
|
|
22
|
+
import { Module } from '@nestjs/common';
|
|
23
|
+
|
|
24
|
+
@Module({
|
|
25
|
+
imports: [/* other modules */],
|
|
26
|
+
controllers: [/* controllers */],
|
|
27
|
+
providers: [/* providers */],
|
|
28
|
+
exports: [/* exported providers */],
|
|
29
|
+
})
|
|
30
|
+
export class FeatureModule {}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Controller Pattern
|
|
34
|
+
```typescript
|
|
35
|
+
import { Controller, Get, Post, Body, Param, Query } from '@nestjs/common';
|
|
36
|
+
|
|
37
|
+
@Controller('users')
|
|
38
|
+
export class UsersController {
|
|
39
|
+
@Get()
|
|
40
|
+
findAll(@Query() query: any) {
|
|
41
|
+
return 'This returns all users';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Get(':id')
|
|
45
|
+
findOne(@Param('id') id: string) {
|
|
46
|
+
return `This returns user #${id}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Post()
|
|
50
|
+
create(@Body() createUserDto: any) {
|
|
51
|
+
return 'This creates a user';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Service with Dependency Injection
|
|
57
|
+
```typescript
|
|
58
|
+
import { Injectable } from '@nestjs/common';
|
|
59
|
+
|
|
60
|
+
@Injectable()
|
|
61
|
+
export class UsersService {
|
|
62
|
+
constructor(/* inject dependencies */) {}
|
|
63
|
+
|
|
64
|
+
findAll() {
|
|
65
|
+
return 'Users service logic';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Database Integration with Drizzle
|
|
71
|
+
|
|
72
|
+
### Setup with Drizzle ORM
|
|
73
|
+
|
|
74
|
+
#### Installation
|
|
75
|
+
```bash
|
|
76
|
+
# Using npm
|
|
77
|
+
npm install drizzle-orm pg
|
|
78
|
+
npm install -D drizzle-kit tsx @types/pg
|
|
79
|
+
|
|
80
|
+
# Using yarn
|
|
81
|
+
yarn add drizzle-orm pg
|
|
82
|
+
yarn add -D drizzle-kit tsx @types/pg
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Configuration
|
|
86
|
+
```typescript
|
|
87
|
+
// drizzle.config.ts
|
|
88
|
+
import 'dotenv/config';
|
|
89
|
+
import { defineConfig } from 'drizzle-kit';
|
|
90
|
+
|
|
91
|
+
export default defineConfig({
|
|
92
|
+
out: './drizzle',
|
|
93
|
+
schema: './src/db/schema.ts',
|
|
94
|
+
dialect: 'postgresql',
|
|
95
|
+
dbCredentials: {
|
|
96
|
+
url: process.env.DATABASE_URL!,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Database Schema
|
|
102
|
+
```typescript
|
|
103
|
+
// src/db/schema.ts
|
|
104
|
+
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
|
105
|
+
|
|
106
|
+
export const users = pgTable('users', {
|
|
107
|
+
id: serial('id').primaryKey(),
|
|
108
|
+
name: text('name').notNull(),
|
|
109
|
+
email: text('email').notNull().unique(),
|
|
110
|
+
createdAt: timestamp('created_at').defaultNow(),
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Database Service
|
|
115
|
+
```typescript
|
|
116
|
+
// src/db/database.service.ts
|
|
117
|
+
import { Injectable } from '@nestjs/common';
|
|
118
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
119
|
+
import { Pool } from 'pg';
|
|
120
|
+
import * as schema from './schema';
|
|
121
|
+
|
|
122
|
+
@Injectable()
|
|
123
|
+
export class DatabaseService {
|
|
124
|
+
private db: ReturnType<typeof drizzle>;
|
|
125
|
+
|
|
126
|
+
constructor() {
|
|
127
|
+
const pool = new Pool({
|
|
128
|
+
connectionString: process.env.DATABASE_URL,
|
|
129
|
+
});
|
|
130
|
+
this.db = drizzle(pool, { schema });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get database() {
|
|
134
|
+
return this.db;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### User Repository with Drizzle
|
|
140
|
+
```typescript
|
|
141
|
+
// src/users/user.repository.ts
|
|
142
|
+
import { Injectable } from '@nestjs/common';
|
|
143
|
+
import { DatabaseService } from '../db/database.service';
|
|
144
|
+
import { users } from '../db/schema';
|
|
145
|
+
import { eq } from 'drizzle-orm';
|
|
146
|
+
|
|
147
|
+
@Injectable()
|
|
148
|
+
export class UserRepository {
|
|
149
|
+
constructor(private db: DatabaseService) {}
|
|
150
|
+
|
|
151
|
+
async findAll() {
|
|
152
|
+
return this.db.database.select().from(users);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async findOne(id: number) {
|
|
156
|
+
const result = await this.db.database
|
|
157
|
+
.select()
|
|
158
|
+
.from(users)
|
|
159
|
+
.where(eq(users.id, id))
|
|
160
|
+
.limit(1);
|
|
161
|
+
return result[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async create(data: typeof users.$inferInsert) {
|
|
165
|
+
const result = await this.db.database
|
|
166
|
+
.insert(users)
|
|
167
|
+
.values(data)
|
|
168
|
+
.returning();
|
|
169
|
+
return result[0];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async update(id: number, data: Partial<typeof users.$inferInsert>) {
|
|
173
|
+
const result = await this.db.database
|
|
174
|
+
.update(users)
|
|
175
|
+
.set(data)
|
|
176
|
+
.where(eq(users.id, id))
|
|
177
|
+
.returning();
|
|
178
|
+
return result[0];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async remove(id: number) {
|
|
182
|
+
const result = await this.db.database
|
|
183
|
+
.delete(users)
|
|
184
|
+
.where(eq(users.id, id))
|
|
185
|
+
.returning();
|
|
186
|
+
return result[0];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Complete User Module
|
|
192
|
+
```typescript
|
|
193
|
+
// src/users/users.module.ts
|
|
194
|
+
import { Module } from '@nestjs/common';
|
|
195
|
+
import { UsersController } from './users.controller';
|
|
196
|
+
import { UsersService } from './users.service';
|
|
197
|
+
import { UserRepository } from './user.repository';
|
|
198
|
+
import { DatabaseService } from '../db/database.service';
|
|
199
|
+
|
|
200
|
+
@Module({
|
|
201
|
+
controllers: [UsersController],
|
|
202
|
+
providers: [UsersService, UserRepository, DatabaseService],
|
|
203
|
+
exports: [UsersService],
|
|
204
|
+
})
|
|
205
|
+
export class UsersModule {}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### User Service Implementation
|
|
209
|
+
```typescript
|
|
210
|
+
// src/users/users.service.ts
|
|
211
|
+
import { Injectable } from '@nestjs/common';
|
|
212
|
+
import { UserRepository } from './user.repository';
|
|
213
|
+
import { User } from './interfaces/user.interface';
|
|
214
|
+
|
|
215
|
+
@Injectable()
|
|
216
|
+
export class UsersService {
|
|
217
|
+
constructor(private userRepository: UserRepository) {}
|
|
218
|
+
|
|
219
|
+
async findAll(): Promise<User[]> {
|
|
220
|
+
return this.userRepository.findAll();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async findOne(id: number): Promise<User> {
|
|
224
|
+
const user = await this.userRepository.findOne(id);
|
|
225
|
+
if (!user) {
|
|
226
|
+
throw new Error('User not found');
|
|
227
|
+
}
|
|
228
|
+
return user;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async create(userData: Partial<User>): Promise<User> {
|
|
232
|
+
return this.userRepository.create(userData);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async update(id: number, userData: Partial<User>): Promise<User> {
|
|
236
|
+
await this.findOne(id); // Verify user exists
|
|
237
|
+
return this.userRepository.update(id, userData);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async remove(id: number): Promise<User> {
|
|
241
|
+
await this.findOne(id); // Verify user exists
|
|
242
|
+
return this.userRepository.remove(id);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Authentication & Authorization
|
|
248
|
+
|
|
249
|
+
### JWT Authentication Guard
|
|
250
|
+
```typescript
|
|
251
|
+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
252
|
+
import { JwtService } from '@nestjs/jwt';
|
|
253
|
+
|
|
254
|
+
@Injectable()
|
|
255
|
+
export class JwtAuthGuard implements CanActivate {
|
|
256
|
+
constructor(private jwtService: JwtService) {}
|
|
257
|
+
|
|
258
|
+
canActivate(context: ExecutionContext) {
|
|
259
|
+
const request = context.switchToHttp().getRequest();
|
|
260
|
+
const token = request.headers.authorization?.split(' ')[1];
|
|
261
|
+
|
|
262
|
+
if (!token) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const decoded = this.jwtService.verify(token);
|
|
268
|
+
request.user = decoded;
|
|
269
|
+
return true;
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Roles-Based Guard
|
|
278
|
+
```typescript
|
|
279
|
+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
280
|
+
import { Reflector } from '@nestjs/core';
|
|
281
|
+
|
|
282
|
+
@Injectable()
|
|
283
|
+
export class RolesGuard implements CanActivate {
|
|
284
|
+
constructor(private reflector: Reflector) {}
|
|
285
|
+
|
|
286
|
+
canActivate(context: ExecutionContext): boolean {
|
|
287
|
+
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
|
|
288
|
+
if (!requiredRoles) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { user } = context.switchToHttp().getRequest();
|
|
293
|
+
return requiredRoles.some((role) => user.roles?.includes(role));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Validation with Pipes
|
|
299
|
+
|
|
300
|
+
### Validation Pipe
|
|
301
|
+
```typescript
|
|
302
|
+
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
|
|
303
|
+
import { validate } from 'class-validator';
|
|
304
|
+
import { plainToClass } from 'class-transformer';
|
|
305
|
+
|
|
306
|
+
@Injectable()
|
|
307
|
+
export class ValidationPipe implements PipeTransform {
|
|
308
|
+
async transform(value: any, { metatype }: ArgumentMetadata) {
|
|
309
|
+
if (!metatype || !this.toValidate(metatype)) {
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const object = plainToClass(metatype, value);
|
|
314
|
+
const errors = await validate(object);
|
|
315
|
+
|
|
316
|
+
if (errors.length > 0) {
|
|
317
|
+
throw new BadRequestException(errors);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private toValidate(metatype: Function): boolean {
|
|
324
|
+
const types: Function[] = [String, Boolean, Number, Array, Object];
|
|
325
|
+
return !types.includes(metatype);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Exception Handling
|
|
331
|
+
|
|
332
|
+
### Global Exception Filter
|
|
333
|
+
```typescript
|
|
334
|
+
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
|
|
335
|
+
import { Request, Response } from 'express';
|
|
336
|
+
|
|
337
|
+
@Catch(HttpException)
|
|
338
|
+
export class HttpExceptionFilter implements ExceptionFilter {
|
|
339
|
+
catch(exception: HttpException, host: ArgumentsHost) {
|
|
340
|
+
const ctx = host.switchToHttp();
|
|
341
|
+
const response = ctx.getResponse<Response>();
|
|
342
|
+
const request = ctx.getRequest<Request>();
|
|
343
|
+
const status = exception.getStatus();
|
|
344
|
+
|
|
345
|
+
response.status(status).json({
|
|
346
|
+
statusCode: status,
|
|
347
|
+
timestamp: new Date().toISOString(),
|
|
348
|
+
path: request.url,
|
|
349
|
+
message: exception.message,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Testing Patterns
|
|
356
|
+
|
|
357
|
+
### Unit Testing Services
|
|
358
|
+
```typescript
|
|
359
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
360
|
+
import { UsersService } from './users.service';
|
|
361
|
+
import { UserRepository } from './user.repository';
|
|
362
|
+
|
|
363
|
+
describe('UsersService', () => {
|
|
364
|
+
let service: UsersService;
|
|
365
|
+
let repository: jest.Mocked<UserRepository>;
|
|
366
|
+
|
|
367
|
+
beforeEach(async () => {
|
|
368
|
+
const mockRepository = {
|
|
369
|
+
findAll: jest.fn(),
|
|
370
|
+
findOne: jest.fn(),
|
|
371
|
+
create: jest.fn(),
|
|
372
|
+
update: jest.fn(),
|
|
373
|
+
remove: jest.fn(),
|
|
374
|
+
} as any;
|
|
375
|
+
|
|
376
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
377
|
+
providers: [
|
|
378
|
+
UsersService,
|
|
379
|
+
{
|
|
380
|
+
provide: UserRepository,
|
|
381
|
+
useValue: mockRepository,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
}).compile();
|
|
385
|
+
|
|
386
|
+
service = module.get<UsersService>(UsersService);
|
|
387
|
+
repository = module.get(UserRepository);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should return all users', async () => {
|
|
391
|
+
const expectedUsers = [{ id: 1, name: 'John', email: 'john@example.com' }];
|
|
392
|
+
repository.findAll.mockResolvedValue(expectedUsers);
|
|
393
|
+
|
|
394
|
+
const result = await service.findAll();
|
|
395
|
+
expect(result).toEqual(expectedUsers);
|
|
396
|
+
expect(repository.findAll).toHaveBeenCalled();
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### E2E Testing with Drizzle
|
|
402
|
+
```typescript
|
|
403
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
404
|
+
import { INestApplication } from '@nestjs/common';
|
|
405
|
+
import * as request from 'supertest';
|
|
406
|
+
import { AppModule } from './../src/app.module';
|
|
407
|
+
import { DatabaseService } from '../src/db/database.service';
|
|
408
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
409
|
+
import { migrate } from 'drizzle-node-postgres/migrator';
|
|
410
|
+
import { migrate as drizzleMigrate } from 'drizzle-orm/node-postgres/migrator';
|
|
411
|
+
|
|
412
|
+
describe('UsersController (e2e)', () => {
|
|
413
|
+
let app: INestApplication;
|
|
414
|
+
let db: DatabaseService;
|
|
415
|
+
|
|
416
|
+
beforeAll(async () => {
|
|
417
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
418
|
+
imports: [AppModule],
|
|
419
|
+
}).compile();
|
|
420
|
+
|
|
421
|
+
app = moduleFixture.createNestApplication();
|
|
422
|
+
db = moduleFixture.get<DatabaseService>(DatabaseService);
|
|
423
|
+
|
|
424
|
+
// Run migrations
|
|
425
|
+
await drizzleMigrate(db.database, { migrationsFolder: './drizzle' });
|
|
426
|
+
|
|
427
|
+
await app.init();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
afterAll(async () => {
|
|
431
|
+
await app.close();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
beforeEach(async () => {
|
|
435
|
+
// Clean database
|
|
436
|
+
await db.database.delete(users).execute();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('/users (POST)', () => {
|
|
440
|
+
const createUserDto = {
|
|
441
|
+
name: 'Test User',
|
|
442
|
+
email: 'test@example.com',
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
return request(app.getHttpServer())
|
|
446
|
+
.post('/users')
|
|
447
|
+
.send(createUserDto)
|
|
448
|
+
.expect(201)
|
|
449
|
+
.expect((res) => {
|
|
450
|
+
expect(res.body).toMatchObject(createUserDto);
|
|
451
|
+
expect(res.body).toHaveProperty('id');
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('/users (GET)', async () => {
|
|
456
|
+
// First create a user
|
|
457
|
+
await db.database.insert(users).values({
|
|
458
|
+
name: 'Test User',
|
|
459
|
+
email: 'test@example.com',
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return request(app.getHttpServer())
|
|
463
|
+
.get('/users')
|
|
464
|
+
.expect(200)
|
|
465
|
+
.expect((res) => {
|
|
466
|
+
expect(Array.isArray(res.body)).toBe(true);
|
|
467
|
+
expect(res.body).toHaveLength(1);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Migrations with Drizzle
|
|
474
|
+
|
|
475
|
+
### Generating Migrations
|
|
476
|
+
```bash
|
|
477
|
+
npx drizzle-kit generate
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Running Migrations
|
|
481
|
+
```typescript
|
|
482
|
+
// src/migrations/migration.service.ts
|
|
483
|
+
import { Injectable } from '@nestjs/common';
|
|
484
|
+
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
|
485
|
+
import { DatabaseService } from '../db/database.service';
|
|
486
|
+
|
|
487
|
+
@Injectable()
|
|
488
|
+
export class MigrationService {
|
|
489
|
+
constructor(private db: DatabaseService) {}
|
|
490
|
+
|
|
491
|
+
async runMigrations() {
|
|
492
|
+
try {
|
|
493
|
+
await migrate(this.db.database, { migrationsFolder: './drizzle' });
|
|
494
|
+
console.log('Migrations completed successfully');
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.error('Migration failed:', error);
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Configuration Management
|
|
504
|
+
|
|
505
|
+
### Environment Configuration
|
|
506
|
+
```typescript
|
|
507
|
+
// src/config/configuration.ts
|
|
508
|
+
export default () => ({
|
|
509
|
+
database: {
|
|
510
|
+
url: process.env.DATABASE_URL,
|
|
511
|
+
},
|
|
512
|
+
jwt: {
|
|
513
|
+
secret: process.env.JWT_SECRET || 'default-secret',
|
|
514
|
+
expiresIn: process.env.JWT_EXPIRES_IN || '24h',
|
|
515
|
+
},
|
|
516
|
+
app: {
|
|
517
|
+
port: parseInt(process.env.PORT, 10) || 3000,
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## Advanced Patterns
|
|
523
|
+
|
|
524
|
+
### Custom Decorators
|
|
525
|
+
```typescript
|
|
526
|
+
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
|
527
|
+
|
|
528
|
+
export const User = createParamDecorator(
|
|
529
|
+
(data: string, ctx: ExecutionContext) => {
|
|
530
|
+
const request = ctx.switchToHttp().getRequest();
|
|
531
|
+
const user = request.user;
|
|
532
|
+
|
|
533
|
+
return data ? user?.[data] : user;
|
|
534
|
+
},
|
|
535
|
+
);
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Interceptors for Logging
|
|
539
|
+
```typescript
|
|
540
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
541
|
+
import { Observable } from 'rxjs';
|
|
542
|
+
import { tap } from 'rxjs/operators';
|
|
543
|
+
|
|
544
|
+
@Injectable()
|
|
545
|
+
export class LoggingInterceptor implements NestInterceptor {
|
|
546
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
547
|
+
const request = context.switchToHttp().getRequest();
|
|
548
|
+
const { method, url } = request;
|
|
549
|
+
const now = Date.now();
|
|
550
|
+
|
|
551
|
+
console.log(`[${method}] ${url} - Start`);
|
|
552
|
+
|
|
553
|
+
return next
|
|
554
|
+
.handle()
|
|
555
|
+
.pipe(
|
|
556
|
+
tap(() => console.log(`[${method}] ${url} - End ${Date.now() - now}ms`)),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Microservices
|
|
563
|
+
|
|
564
|
+
### TCP Microservice
|
|
565
|
+
```typescript
|
|
566
|
+
// main.ts
|
|
567
|
+
import { NestFactory } from '@nestjs/core';
|
|
568
|
+
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
|
|
569
|
+
import { AppModule } from './app.module';
|
|
570
|
+
|
|
571
|
+
async function bootstrap() {
|
|
572
|
+
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
|
|
573
|
+
AppModule,
|
|
574
|
+
{
|
|
575
|
+
transport: Transport.TCP,
|
|
576
|
+
options: {
|
|
577
|
+
host: 'localhost',
|
|
578
|
+
port: 8877,
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
);
|
|
582
|
+
await app.listen();
|
|
583
|
+
}
|
|
584
|
+
bootstrap();
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
## GraphQL Integration
|
|
588
|
+
|
|
589
|
+
### GraphQL Resolver with Drizzle
|
|
590
|
+
```typescript
|
|
591
|
+
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
|
|
592
|
+
import { UserRepository } from './user.repository';
|
|
593
|
+
|
|
594
|
+
@Resolver(() => User)
|
|
595
|
+
export class UsersResolver {
|
|
596
|
+
constructor(private userRepository: UserRepository) {}
|
|
597
|
+
|
|
598
|
+
@Query(() => [User])
|
|
599
|
+
async users() {
|
|
600
|
+
return this.userRepository.findAll();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
@Mutation(() => User)
|
|
604
|
+
async createUser(@Args('input') input: CreateUserInput) {
|
|
605
|
+
return this.userRepository.create(input);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
## Best Practices
|
|
611
|
+
|
|
612
|
+
1. **Always use constructor injection** - Never use property injection
|
|
613
|
+
2. **Use DTOs for data transfer** - Define interfaces for request/response
|
|
614
|
+
3. **Implement proper error handling** - Use exception filters
|
|
615
|
+
4. **Validate all inputs** - Use validation pipes
|
|
616
|
+
5. **Keep modules focused** - Single responsibility principle
|
|
617
|
+
6. **Use environment variables** - Never hardcode credentials
|
|
618
|
+
7. **Write comprehensive tests** - Unit and integration tests
|
|
619
|
+
8. **Use transactions for complex operations** - Maintain data consistency
|
|
620
|
+
9. **Implement proper logging** - Use interceptors for cross-cutting concerns
|
|
621
|
+
10. **Use type safety** - Leverage TypeScript features
|
|
622
|
+
|
|
623
|
+
## Common Patterns with Drizzle
|
|
624
|
+
|
|
625
|
+
### Transactions
|
|
626
|
+
```typescript
|
|
627
|
+
async transferFunds(fromId: number, toId: number, amount: number) {
|
|
628
|
+
return this.db.database.transaction(async (tx) => {
|
|
629
|
+
// Debit from account
|
|
630
|
+
await tx
|
|
631
|
+
.update(accounts)
|
|
632
|
+
.set({ balance: sql`${accounts.balance} - ${amount}` })
|
|
633
|
+
.where(eq(accounts.id, fromId));
|
|
634
|
+
|
|
635
|
+
// Credit to account
|
|
636
|
+
await tx
|
|
637
|
+
.update(accounts)
|
|
638
|
+
.set({ balance: sql`${accounts.balance} + ${amount}` })
|
|
639
|
+
.where(eq(accounts.id, toId));
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Soft Deletes
|
|
645
|
+
```typescript
|
|
646
|
+
export const users = pgTable('users', {
|
|
647
|
+
id: serial('id').primaryKey(),
|
|
648
|
+
name: text('name').notNull(),
|
|
649
|
+
email: text('email').notNull().unique(),
|
|
650
|
+
deletedAt: timestamp('deleted_at'),
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
async softDelete(id: number) {
|
|
654
|
+
return this.db.database
|
|
655
|
+
.update(users)
|
|
656
|
+
.set({ deletedAt: new Date() })
|
|
657
|
+
.where(eq(users.id, id));
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Complex Queries with Relations
|
|
662
|
+
```typescript
|
|
663
|
+
async getUsersWithPosts() {
|
|
664
|
+
return this.db.database
|
|
665
|
+
.select()
|
|
666
|
+
.from(users)
|
|
667
|
+
.leftJoin(posts, eq(posts.userId, users.id));
|
|
668
|
+
}
|
|
669
|
+
```
|