@polymorphism-tech/morph-spec 4.12.0 → 4.13.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/README.md +21 -29
- package/bin/morph-spec.js +41 -24
- package/bin/task-manager.js +962 -1341
- package/bin/validate.js +1 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +10 -11
- package/docs/QUICKSTART.md +15 -15
- package/framework/CLAUDE.md +5 -2
- package/framework/agents/README.md +3 -4
- package/framework/agents/ai-agents/ai-system-architect.md +6 -10
- package/framework/agents/architecture/seo-growth-hacker.md +166 -326
- package/framework/agents/architecture/standards-architect.md +21 -31
- package/framework/agents/backend/dotnet-senior.md +160 -86
- package/framework/agents/backend/ef-modeler.md +2 -6
- package/framework/agents/backend/hangfire-orchestrator.md +8 -9
- package/framework/agents/backend/ms-agent-expert.md +1 -1
- package/framework/agents/frontend/nextjs-expert.md +198 -133
- package/framework/agents/frontend/ui-ux-designer.md +226 -267
- package/framework/agents/infrastructure/infra-architect.md +191 -27
- package/framework/agents/integrations/clerk-auth.md +19 -26
- package/framework/agents/integrations/hangfire-integration.md +1 -4
- package/framework/agents.json +215 -1453
- package/framework/commands/morph-apply.md +4 -4
- package/framework/commands/morph-preflight.md +4 -26
- package/framework/commands/morph-proposal.md +19 -19
- package/framework/commands/morph-status.md +2 -2
- package/framework/commands/morph-troubleshoot.md +6 -13
- package/framework/hooks/README.md +1 -6
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
- package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
- package/framework/hooks/claude-code/statusline.py +4 -5
- package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
- package/framework/hooks/shared/compact-restore.js +28 -5
- package/framework/hooks/shared/phase-utils.js +4 -8
- package/framework/hooks/shared/state-reader.js +34 -3
- package/framework/phases.json +7 -45
- package/framework/rules/ai-agents-standards.md +15 -0
- package/framework/rules/asaas-standards.md +13 -0
- package/framework/rules/clerk-standards.md +13 -0
- package/framework/rules/csharp-standards.md +8 -4
- package/framework/rules/data-standards.md +18 -0
- package/framework/rules/dotnet-templates.md +87 -0
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/hangfire-standards.md +13 -0
- package/framework/rules/infrastructure-standards.md +27 -16
- package/framework/rules/morph-workflow.md +73 -13
- package/framework/rules/nextjs-standards.md +1 -0
- package/framework/rules/nextjs-templates.md +40 -0
- package/framework/rules/resend-standards.md +12 -0
- package/framework/rules/supabase-standards.md +29 -0
- package/framework/skills/README.md +6 -8
- package/framework/skills/level-0-meta/mcp-registry.json +21 -99
- package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
- package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
- package/framework/skills/level-1-workflows/README.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +58 -124
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -2
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +57 -77
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/squad-templates/backend-only.json +6 -6
- package/framework/squad-templates/frontend-only.json +17 -8
- package/framework/squad-templates/full-stack.json +29 -24
- package/framework/standards/STANDARDS.json +1021 -2369
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
- package/framework/standards/backend/dotnet/result-pattern.md +213 -0
- package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
- package/framework/standards/backend/dotnet/scrutor.md +158 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
- package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
- package/framework/standards/integration/mcp/mcp-tools.md +91 -77
- package/framework/templates/IDE-SNIPPETS.md +2 -6
- package/framework/templates/README.md +709 -814
- package/framework/templates/REGISTRY.json +223 -762
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
- package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
- package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
- package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
- package/framework/templates/code/dotnet/backend/repository.cs +148 -141
- package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
- package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
- package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
- package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
- package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
- package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
- package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
- package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
- package/framework/templates/code/dotnet/jobs/job.cs +21 -17
- package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
- package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
- package/framework/templates/code/dotnet/test.cs +192 -106
- package/framework/templates/context/ARCHITECTURE.md +89 -0
- package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
- package/framework/templates/context/CONTEXT.md +180 -181
- package/framework/templates/context/design-system.md +1 -5
- package/framework/templates/docs/onboarding.md +2 -3
- package/framework/templates/docs/proposal.md +171 -182
- package/framework/templates/docs/spec.md +1 -3
- package/framework/templates/docs/ui-components.md +0 -72
- package/framework/templates/examples/design-system-examples.md +4 -4
- package/framework/templates/feature/decisions.md +0 -66
- package/framework/templates/feature/recap.md +1 -2
- package/framework/templates/infrastructure/github/README.md +56 -593
- package/framework/templates/integrations/clerk-config.cs +0 -41
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
- package/framework/templates/project/validate.js +2 -4
- package/framework/templates/ui/design-system.css +1 -1
- package/framework/workflows/README.md +8 -8
- package/framework/workflows/configs/fast-track.json +51 -0
- package/framework/workflows/configs/full.json +68 -0
- package/framework/workflows/configs/standard.json +7 -3
- package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
- package/framework/workflows/docs/enforcement-pipeline.md +18 -19
- package/framework/workflows/docs/full-morph.md +69 -76
- package/framework/workflows/docs/standard.md +49 -44
- package/framework/workflows/docs/ui-refresh.md +1 -1
- package/package.json +3 -3
- package/src/commands/agents/dispatch-agents.js +55 -116
- package/src/commands/context/context-refresh.js +79 -0
- package/src/commands/phase/phase-reset.js +1 -1
- package/src/commands/project/doctor.js +10 -32
- package/src/commands/project/init.js +12 -0
- package/src/commands/project/status.js +6 -12
- package/src/commands/scaffold/scaffold.js +131 -0
- package/src/commands/scope/escalate.js +1 -1
- package/src/commands/state/advance-phase.js +15 -29
- package/src/commands/state/state.js +20 -5
- package/src/commands/tasks/task.js +0 -30
- package/src/commands/validation/validate-feature.js +3 -10
- package/src/core/paths/output-schema.js +12 -13
- package/src/core/state/phase-state-machine.js +211 -217
- package/src/core/state/state-manager.js +9 -9
- package/src/lib/agents/agents-manager.js +2 -2
- package/src/lib/agents/output-collector.js +1 -2
- package/src/lib/detectors/design-system-detector.js +4 -4
- package/src/lib/detectors/index.js +1 -11
- package/src/lib/generators/architecture-scanner.js +640 -0
- package/src/lib/generators/context-generator.js +3 -3
- package/src/lib/generators/design-system-generator.js +508 -587
- package/src/lib/generators/index.js +12 -10
- package/src/lib/generators/metadata-extractor.js +386 -387
- package/src/lib/generators/recap-generator.js +4 -4
- package/src/lib/generators/scaffold-generator.js +317 -0
- package/src/lib/generators/settings-generator.js +0 -1
- package/src/lib/phase-chain/phase-validator.js +64 -42
- package/src/lib/stack/stack-profile.js +2 -2
- package/src/lib/standards/digest-builder.js +72 -285
- package/src/lib/tasks/task-parser.js +11 -5
- package/src/lib/validators/architecture/architecture-validator.js +259 -387
- package/src/lib/validators/content/content-validator.js +287 -287
- package/src/lib/validators/css/css-validator.js +340 -352
- package/src/lib/validators/packages/package-validator.js +347 -360
- package/src/lib/validators/shared/issue-counter.js +14 -18
- package/src/lib/validators/validation-runner.js +3 -19
- package/src/scripts/setup-infra.js +1 -1
- package/src/utils/agents-installer.js +24 -3
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +11 -19
- package/src/utils/skills-installer.js +178 -178
- package/framework/agents/architecture/prompt-engineer.md +0 -195
- package/framework/agents/backend/api-designer.md +0 -106
- package/framework/agents/frontend/blazor-builder.md +0 -229
- package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
- package/framework/agents/infrastructure/azure-architect.md +0 -150
- package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
- package/framework/agents/infrastructure/bicep-architect.md +0 -132
- package/framework/agents/infrastructure/container-specialist.md +0 -137
- package/framework/agents/infrastructure/devops-engineer.md +0 -126
- package/framework/agents/integrations/azure-identity.md +0 -148
- package/framework/agents/quality/code-analyzer.md +0 -239
- package/framework/agents/quality/testing-specialist.md +0 -130
- package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
- package/framework/index/troubleshooting-index.json +0 -184
- package/framework/rules/testing-standards.md +0 -14
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
- package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
- package/framework/standards/ai-agents/blazor-ui.md +0 -364
- package/framework/standards/architecture/ddd/aggregates.md +0 -120
- package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
- package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
- package/framework/standards/architecture/ddd/entities.md +0 -99
- package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
- package/framework/standards/architecture/ddd/value-objects.md +0 -124
- package/framework/standards/backend/api/minimal-api.md +0 -494
- package/framework/standards/backend/api/rest.md +0 -492
- package/framework/standards/backend/authentication/passkeys.md +0 -428
- package/framework/standards/backend/database/ef-core.md +0 -199
- package/framework/standards/backend/database/migrations.md +0 -393
- package/framework/standards/backend/database/repository-patterns.md +0 -528
- package/framework/standards/backend/dotnet/async.md +0 -366
- package/framework/standards/backend/dotnet/core.md +0 -117
- package/framework/standards/backend/dotnet/di.md +0 -439
- package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
- package/framework/standards/context/analytics.md +0 -96
- package/framework/standards/context/bundles.md +0 -110
- package/framework/standards/context/priming.md +0 -78
- package/framework/standards/core/architecture.md +0 -185
- package/framework/standards/core/coding.md +0 -214
- package/framework/standards/core/git-branching-strategy.md +0 -403
- package/framework/standards/core/git.md +0 -185
- package/framework/standards/core/testing.md +0 -295
- package/framework/standards/data/nosql/blob-storage.md +0 -102
- package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
- package/framework/standards/frontend/blazor/design-checklist.md +0 -222
- package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
- package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
- package/framework/standards/frontend/blazor/html-conversion.md +0 -184
- package/framework/standards/frontend/blazor/lifecycle.md +0 -195
- package/framework/standards/frontend/blazor/pitfalls.md +0 -198
- package/framework/standards/frontend/blazor/state.md +0 -191
- package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -266
- package/framework/standards/frontend/design-system/design-trends.md +0 -171
- package/framework/standards/infrastructure/azure/azure.md +0 -624
- package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
- package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
- package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
- package/framework/standards/infrastructure/azure/services/functions.md +0 -486
- package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
- package/framework/standards/infrastructure/azure/services/storage.md +0 -407
- package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
- package/framework/standards/integration/api/graphql.md +0 -91
- package/framework/standards/integration/api/grpc.md +0 -114
- package/framework/standards/integration/api/rest-design.md +0 -95
- package/framework/standards/integration/event-driven/cqrs.md +0 -101
- package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
- package/framework/standards/integration/event-driven/service-bus.md +0 -95
- package/framework/standards/observability/logging.md +0 -131
- package/framework/standards/observability/metrics.md +0 -121
- package/framework/standards/observability/monitoring.md +0 -114
- package/framework/standards/observability/tracing.md +0 -132
- package/framework/standards/workflows/parallel-execution.md +0 -112
- package/framework/standards/workflows/thread-management.md +0 -113
- package/framework/templates/code/dotnet/backend/service.cs +0 -139
- package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
- package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
- package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
- package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
- package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
- package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
- package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
- package/framework/templates/code/dotnet/frontend/component.razor +0 -239
- package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
- package/framework/templates/infrastructure/azure/README.md +0 -286
- package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
- package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
- package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
- package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
- package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
- package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
- package/framework/templates/infrastructure/azure/deploy.sh +0 -208
- package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
- package/framework/templates/infrastructure/azure/main.bicep +0 -189
- package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
- package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
- package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
- package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
- package/framework/templates/infrastructure/azure/storage.bicep +0 -106
- package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
- package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
- package/framework/templates/integrations/azure-identity-config.cs +0 -288
- package/framework/templates/project-structure/dotnet-ddd.md +0 -70
- package/framework/templates/saas/subscription.cs +0 -347
- package/framework/templates/saas/tenant.cs +0 -338
- package/framework/templates/ui/FluentDesignTheme.cs +0 -149
- package/framework/templates/ui/MudTheme.cs +0 -281
- package/framework/workflows/configs/nodejs-cli.json +0 -40
- package/src/commands/standards/context-budget.js +0 -89
- package/src/lib/standards/index.js +0 -7
- package/src/lib/standards/standards-context-injector.js +0 -302
- package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
- package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
- package/src/lib/validators/blazor/blazor-validator.js +0 -369
- package/src/lib/validators/blazor/index.js +0 -9
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
# Azure Storage Services Standard
|
|
2
|
-
|
|
3
|
-
> **Scope:** blazor-azure
|
|
4
|
-
> **Layer:** 2 (on keyword)
|
|
5
|
-
> **Keywords:** azure storage, blob, queue, table, file storage
|
|
6
|
-
> **Load When:** azure storage keywords detected
|
|
7
|
-
|
|
8
|
-
Azure Storage services patterns for Blob, Queue, Table, and File storage.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Overview
|
|
13
|
-
|
|
14
|
-
Azure Storage provides:
|
|
15
|
-
- **Blob Storage**: Object storage for unstructured data (files, images, videos)
|
|
16
|
-
- **Queue Storage**: Message queuing for async processing
|
|
17
|
-
- **Table Storage**: NoSQL key-value store
|
|
18
|
-
- **File Storage**: SMB file shares
|
|
19
|
-
|
|
20
|
-
**Stack:** .NET 10 + Blazor Server
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Core Principles
|
|
25
|
-
|
|
26
|
-
1. **Managed Identity**: Use Managed Identity over connection strings
|
|
27
|
-
2. **SAS Tokens**: Use SAS for temporary access delegation
|
|
28
|
-
3. **Retry Policies**: Configure exponential backoff for transient failures
|
|
29
|
-
4. **Lifecycle Management**: Auto-delete old blobs with lifecycle policies
|
|
30
|
-
5. **Cost Optimization**: Use appropriate storage tiers (Hot/Cool/Archive)
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Installation
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
dotnet add package Azure.Storage.Blobs
|
|
38
|
-
dotnet add package Azure.Storage.Queues
|
|
39
|
-
dotnet add package Azure.Identity
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Blob Storage
|
|
45
|
-
|
|
46
|
-
### DI Configuration
|
|
47
|
-
|
|
48
|
-
```csharp
|
|
49
|
-
// Program.cs
|
|
50
|
-
using Azure.Identity;
|
|
51
|
-
using Azure.Storage.Blobs;
|
|
52
|
-
|
|
53
|
-
builder.Services.AddSingleton(sp =>
|
|
54
|
-
{
|
|
55
|
-
var blobServiceUri = new Uri(builder.Configuration["AzureStorage:BlobServiceUri"]!);
|
|
56
|
-
return new BlobServiceClient(blobServiceUri, new DefaultAzureCredential());
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Upload Blob
|
|
61
|
-
|
|
62
|
-
```csharp
|
|
63
|
-
public class FileUploadService
|
|
64
|
-
{
|
|
65
|
-
private readonly BlobServiceClient _blobServiceClient;
|
|
66
|
-
|
|
67
|
-
public FileUploadService(BlobServiceClient blobServiceClient)
|
|
68
|
-
{
|
|
69
|
-
_blobServiceClient = blobServiceClient;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public async Task<string> UploadFileAsync(Stream fileStream, string fileName, string containerName)
|
|
73
|
-
{
|
|
74
|
-
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
|
|
75
|
-
await containerClient.CreateIfNotExistsAsync();
|
|
76
|
-
|
|
77
|
-
var blobClient = containerClient.GetBlobClient(fileName);
|
|
78
|
-
|
|
79
|
-
await blobClient.UploadAsync(fileStream, overwrite: true);
|
|
80
|
-
|
|
81
|
-
return blobClient.Uri.ToString();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Download Blob
|
|
87
|
-
|
|
88
|
-
```csharp
|
|
89
|
-
public async Task<Stream> DownloadFileAsync(string fileName, string containerName)
|
|
90
|
-
{
|
|
91
|
-
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
|
|
92
|
-
var blobClient = containerClient.GetBlobClient(fileName);
|
|
93
|
-
|
|
94
|
-
var response = await blobClient.DownloadAsync();
|
|
95
|
-
return response.Value.Content;
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Generate SAS Token
|
|
100
|
-
|
|
101
|
-
```csharp
|
|
102
|
-
using Azure.Storage.Sas;
|
|
103
|
-
|
|
104
|
-
public string GenerateSasUri(string containerName, string blobName, TimeSpan validity)
|
|
105
|
-
{
|
|
106
|
-
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
|
|
107
|
-
var blobClient = containerClient.GetBlobClient(blobName);
|
|
108
|
-
|
|
109
|
-
var sasBuilder = new BlobSasBuilder
|
|
110
|
-
{
|
|
111
|
-
BlobContainerName = containerName,
|
|
112
|
-
BlobName = blobName,
|
|
113
|
-
Resource = "b", // Blob
|
|
114
|
-
StartsOn = DateTimeOffset.UtcNow,
|
|
115
|
-
ExpiresOn = DateTimeOffset.UtcNow.Add(validity)
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
sasBuilder.SetPermissions(BlobSasPermissions.Read);
|
|
119
|
-
|
|
120
|
-
var sasUri = blobClient.GenerateSasUri(sasBuilder);
|
|
121
|
-
return sasUri.ToString();
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Queue Storage
|
|
128
|
-
|
|
129
|
-
### DI Configuration
|
|
130
|
-
|
|
131
|
-
```csharp
|
|
132
|
-
// Program.cs
|
|
133
|
-
builder.Services.AddSingleton(sp =>
|
|
134
|
-
{
|
|
135
|
-
var queueServiceUri = new Uri(builder.Configuration["AzureStorage:QueueServiceUri"]!);
|
|
136
|
-
return new QueueServiceClient(queueServiceUri, new DefaultAzureCredential());
|
|
137
|
-
});
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Send Message
|
|
141
|
-
|
|
142
|
-
```csharp
|
|
143
|
-
public class QueueService
|
|
144
|
-
{
|
|
145
|
-
private readonly QueueServiceClient _queueServiceClient;
|
|
146
|
-
|
|
147
|
-
public QueueService(QueueServiceClient queueServiceClient)
|
|
148
|
-
{
|
|
149
|
-
_queueServiceClient = queueServiceClient;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
public async Task SendMessageAsync(string queueName, string message)
|
|
153
|
-
{
|
|
154
|
-
var queueClient = _queueServiceClient.GetQueueClient(queueName);
|
|
155
|
-
await queueClient.CreateIfNotExistsAsync();
|
|
156
|
-
|
|
157
|
-
await queueClient.SendMessageAsync(message);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public async Task SendMessageWithDelayAsync(string queueName, string message, TimeSpan delay)
|
|
161
|
-
{
|
|
162
|
-
var queueClient = _queueServiceClient.GetQueueClient(queueName);
|
|
163
|
-
await queueClient.CreateIfNotExistsAsync();
|
|
164
|
-
|
|
165
|
-
await queueClient.SendMessageAsync(message, visibilityTimeout: delay);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Process Messages
|
|
171
|
-
|
|
172
|
-
```csharp
|
|
173
|
-
using Azure.Storage.Queues.Models;
|
|
174
|
-
|
|
175
|
-
public async Task ProcessMessagesAsync(string queueName, CancellationToken cancellationToken)
|
|
176
|
-
{
|
|
177
|
-
var queueClient = _queueServiceClient.GetQueueClient(queueName);
|
|
178
|
-
|
|
179
|
-
while (!cancellationToken.IsCancellationRequested)
|
|
180
|
-
{
|
|
181
|
-
QueueMessage[] messages = await queueClient.ReceiveMessagesAsync(maxMessages: 32);
|
|
182
|
-
|
|
183
|
-
foreach (var message in messages)
|
|
184
|
-
{
|
|
185
|
-
try
|
|
186
|
-
{
|
|
187
|
-
// Process message
|
|
188
|
-
await ProcessMessageAsync(message.MessageText);
|
|
189
|
-
|
|
190
|
-
// Delete message after successful processing
|
|
191
|
-
await queueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
|
192
|
-
}
|
|
193
|
-
catch (Exception ex)
|
|
194
|
-
{
|
|
195
|
-
// Log error, message will become visible again after timeout
|
|
196
|
-
_logger.LogError(ex, "Failed to process message {MessageId}", message.MessageId);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (messages.Length == 0)
|
|
201
|
-
{
|
|
202
|
-
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Table Storage
|
|
211
|
-
|
|
212
|
-
### DI Configuration
|
|
213
|
-
|
|
214
|
-
```csharp
|
|
215
|
-
dotnet add package Azure.Data.Tables
|
|
216
|
-
|
|
217
|
-
// Program.cs
|
|
218
|
-
builder.Services.AddSingleton(sp =>
|
|
219
|
-
{
|
|
220
|
-
var tableServiceUri = new Uri(builder.Configuration["AzureStorage:TableServiceUri"]!);
|
|
221
|
-
return new TableServiceClient(tableServiceUri, new DefaultAzureCredential());
|
|
222
|
-
});
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Entity Model
|
|
226
|
-
|
|
227
|
-
```csharp
|
|
228
|
-
using Azure;
|
|
229
|
-
using Azure.Data.Tables;
|
|
230
|
-
|
|
231
|
-
public class ProductEntity : ITableEntity
|
|
232
|
-
{
|
|
233
|
-
public string PartitionKey { get; set; } = default!; // Category
|
|
234
|
-
public string RowKey { get; set; } = default!; // ProductId
|
|
235
|
-
public DateTimeOffset? Timestamp { get; set; }
|
|
236
|
-
public ETag ETag { get; set; }
|
|
237
|
-
|
|
238
|
-
public string Name { get; set; } = string.Empty;
|
|
239
|
-
public decimal Price { get; set; }
|
|
240
|
-
public int Stock { get; set; }
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### CRUD Operations
|
|
245
|
-
|
|
246
|
-
```csharp
|
|
247
|
-
public class ProductTableService
|
|
248
|
-
{
|
|
249
|
-
private readonly TableClient _tableClient;
|
|
250
|
-
|
|
251
|
-
public ProductTableService(TableServiceClient tableServiceClient)
|
|
252
|
-
{
|
|
253
|
-
_tableClient = tableServiceClient.GetTableClient("products");
|
|
254
|
-
_tableClient.CreateIfNotExists();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
public async Task AddProductAsync(ProductEntity product)
|
|
258
|
-
{
|
|
259
|
-
await _tableClient.AddEntityAsync(product);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
public async Task<ProductEntity?> GetProductAsync(string category, string productId)
|
|
263
|
-
{
|
|
264
|
-
try
|
|
265
|
-
{
|
|
266
|
-
return await _tableClient.GetEntityAsync<ProductEntity>(category, productId);
|
|
267
|
-
}
|
|
268
|
-
catch (RequestFailedException ex) when (ex.Status == 404)
|
|
269
|
-
{
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
public async Task<List<ProductEntity>> QueryProductsAsync(string category)
|
|
275
|
-
{
|
|
276
|
-
var query = _tableClient.QueryAsync<ProductEntity>(p => p.PartitionKey == category);
|
|
277
|
-
|
|
278
|
-
var results = new List<ProductEntity>();
|
|
279
|
-
await foreach (var product in query)
|
|
280
|
-
{
|
|
281
|
-
results.Add(product);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return results;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
public async Task UpdateProductAsync(ProductEntity product)
|
|
288
|
-
{
|
|
289
|
-
await _tableClient.UpdateEntityAsync(product, ETag.All, TableUpdateMode.Replace);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
public async Task DeleteProductAsync(string category, string productId)
|
|
293
|
-
{
|
|
294
|
-
await _tableClient.DeleteEntityAsync(category, productId);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
## Best Practices
|
|
302
|
-
|
|
303
|
-
### Retry Policy
|
|
304
|
-
|
|
305
|
-
```csharp
|
|
306
|
-
using Azure.Core;
|
|
307
|
-
|
|
308
|
-
var options = new BlobClientOptions
|
|
309
|
-
{
|
|
310
|
-
Retry =
|
|
311
|
-
{
|
|
312
|
-
Mode = RetryMode.Exponential,
|
|
313
|
-
MaxRetries = 3,
|
|
314
|
-
Delay = TimeSpan.FromSeconds(2),
|
|
315
|
-
MaxDelay = TimeSpan.FromSeconds(10)
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
var blobServiceClient = new BlobServiceClient(blobServiceUri, credential, options);
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Blob Metadata
|
|
323
|
-
|
|
324
|
-
```csharp
|
|
325
|
-
var metadata = new Dictionary<string, string>
|
|
326
|
-
{
|
|
327
|
-
{ "uploadedBy", userId },
|
|
328
|
-
{ "fileType", "invoice" },
|
|
329
|
-
{ "department", "finance" }
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
await blobClient.UploadAsync(stream, metadata: metadata);
|
|
333
|
-
|
|
334
|
-
// Retrieve metadata
|
|
335
|
-
var properties = await blobClient.GetPropertiesAsync();
|
|
336
|
-
var uploadedBy = properties.Value.Metadata["uploadedBy"];
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### Lifecycle Management
|
|
340
|
-
|
|
341
|
-
```json
|
|
342
|
-
// Configure in Azure Portal or via Bicep
|
|
343
|
-
{
|
|
344
|
-
"rules": [
|
|
345
|
-
{
|
|
346
|
-
"name": "deleteOldBackups",
|
|
347
|
-
"enabled": true,
|
|
348
|
-
"type": "Lifecycle",
|
|
349
|
-
"definition": {
|
|
350
|
-
"filters": {
|
|
351
|
-
"blobTypes": ["blockBlob"],
|
|
352
|
-
"prefixMatch": ["backups/"]
|
|
353
|
-
},
|
|
354
|
-
"actions": {
|
|
355
|
-
"baseBlob": {
|
|
356
|
-
"delete": {
|
|
357
|
-
"daysAfterModificationGreaterThan": 30
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
]
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
---
|
|
368
|
-
|
|
369
|
-
## Bicep Configuration
|
|
370
|
-
|
|
371
|
-
```bicep
|
|
372
|
-
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
|
|
373
|
-
name: storageAccountName
|
|
374
|
-
location: location
|
|
375
|
-
sku: {
|
|
376
|
-
name: 'Standard_LRS'
|
|
377
|
-
}
|
|
378
|
-
kind: 'StorageV2'
|
|
379
|
-
properties: {
|
|
380
|
-
allowBlobPublicAccess: false
|
|
381
|
-
minimumTlsVersion: 'TLS1_2'
|
|
382
|
-
supportsHttpsTrafficOnly: true
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Grant Container App Blob access
|
|
387
|
-
resource blobRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
|
|
388
|
-
scope: storageAccount
|
|
389
|
-
name: guid(storageAccount.id, principalId, 'Storage Blob Data Contributor')
|
|
390
|
-
properties: {
|
|
391
|
-
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
|
|
392
|
-
principalId: principalId
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## References
|
|
400
|
-
|
|
401
|
-
- [Azure Blob Storage](https://learn.microsoft.com/azure/storage/blobs/)
|
|
402
|
-
- [Azure Queue Storage](https://learn.microsoft.com/azure/storage/queues/)
|
|
403
|
-
- [Azure Table Storage](https://learn.microsoft.com/azure/storage/tables/)
|
|
404
|
-
|
|
405
|
-
---
|
|
406
|
-
|
|
407
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# Vercel Deployment — Database Standard
|
|
2
|
-
|
|
3
|
-
> **Scope:** nextjs-vercel
|
|
4
|
-
> **Layer:** 1 (always-on for Vercel-deployed Next.js)
|
|
5
|
-
> **Keywords:** vercel, neon, planetscale, serverless database, serverless postgres, deploy nextjs
|
|
6
|
-
> **Load When:** deployment target is Vercel with Next.js stack
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Rule: Vercel Requires a Serverless Database
|
|
11
|
-
|
|
12
|
-
**NEVER** use a traditional long-lived database connection pool (PostgreSQL TCP, MySQL TCP) with Vercel.
|
|
13
|
-
Vercel functions are ephemeral and stateless — each invocation may spawn a fresh process.
|
|
14
|
-
A persistent connection pool (e.g., EF Core + plain Npgsql TCP) will exhaust database connection limits within minutes under moderate load.
|
|
15
|
-
|
|
16
|
-
**ALWAYS** use a **serverless-compatible** database when deploying to Vercel.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Approved Serverless Databases
|
|
21
|
-
|
|
22
|
-
| Provider | Type | Connection mode | Notes |
|
|
23
|
-
|----------|------|-----------------|-------|
|
|
24
|
-
| **Neon** | PostgreSQL (serverless) | HTTP (`@neondatabase/serverless`) or pooled TCP via PgBouncer | First choice for SQL |
|
|
25
|
-
| **PlanetScale** | MySQL-compatible (serverless) | HTTP driver (`@planetscale/database`) | Good for MySQL ecosystems |
|
|
26
|
-
| **Supabase** | PostgreSQL + PgBouncer | Pooled TCP (transaction mode) or REST | Full BaaS option |
|
|
27
|
-
| **Turso** | SQLite (distributed) | HTTP (`@libsql/client`) | Lightweight, edge-friendly |
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Implementation: Neon (Recommended)
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
// lib/db.ts
|
|
35
|
-
import { neon } from '@neondatabase/serverless';
|
|
36
|
-
|
|
37
|
-
const sql = neon(process.env.DATABASE_URL!);
|
|
38
|
-
export default sql;
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
// Example: API Route (app/api/items/route.ts)
|
|
43
|
-
import sql from '@/lib/db';
|
|
44
|
-
|
|
45
|
-
export async function GET() {
|
|
46
|
-
const items = await sql`SELECT * FROM items LIMIT 50`;
|
|
47
|
-
return Response.json(items);
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Environment variable
|
|
52
|
-
```
|
|
53
|
-
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/dbname?sslmode=require
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## Implementation: Supabase + PgBouncer (Transaction Mode)
|
|
59
|
-
|
|
60
|
-
Use the **port 6543** connection string (PgBouncer, transaction pooling):
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
DATABASE_URL=postgresql://user:pass@db.xxx.supabase.co:6543/postgres?pgbouncer=true&connection_limit=1
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
If using an ORM (Prisma/Drizzle), set `connection_limit=1` to avoid pool saturation.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Anti-patterns — NEVER use with Vercel
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
// WRONG: plain Npgsql/pg pool without serverless adapter
|
|
74
|
-
import { Pool } from 'pg';
|
|
75
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
76
|
-
// → Leaks connections on every cold start
|
|
77
|
-
|
|
78
|
-
// WRONG: EF Core (dotnet) TCP pool behind a Vercel-deployed Next.js API route
|
|
79
|
-
// → Not applicable (EF Core is .NET), but calling a .NET API that pools
|
|
80
|
-
// connections is fine as long as the .NET side runs on a persistent server
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## Decision Tree: Choose Your DB Tier
|
|
86
|
-
|
|
87
|
-
```
|
|
88
|
-
Deploying to Vercel?
|
|
89
|
-
└── YES → Use serverless DB
|
|
90
|
-
├── Need SQL + PostGIS/pgvector? → Neon
|
|
91
|
-
├── Need full BaaS (auth, storage, RLS)? → Supabase
|
|
92
|
-
├── MySQL ecosystem? → PlanetScale
|
|
93
|
-
└── Edge-only, lightweight? → Turso
|
|
94
|
-
└── NO (EasyPanel, Railway, Render, Azure Container Apps)
|
|
95
|
-
└── Any PostgreSQL with connection pool is fine
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Validator Check (design phase)
|
|
101
|
-
|
|
102
|
-
During `phase-design`, the standards agent must verify:
|
|
103
|
-
|
|
104
|
-
- `config.deployment.target === 'vercel'` → enforce serverless DB selection
|
|
105
|
-
- If `database.provider` is missing or is `postgresql-tcp`, flag as **BLOCKING** issue
|
|
106
|
-
- Add to `decisions.md`: "Database must be serverless-compatible (Vercel constraint)"
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Integration Standard: GraphQL
|
|
2
|
-
|
|
3
|
-
## When to Use GraphQL
|
|
4
|
-
✅ Client needs fine-grained field selection (mobile apps, different clients)
|
|
5
|
-
✅ Complex nested resource relationships (avoid N+1 with DataLoader)
|
|
6
|
-
✅ Real-time subscriptions alongside queries
|
|
7
|
-
❌ Simple CRUD APIs (REST is simpler)
|
|
8
|
-
❌ File upload as primary use case (use REST for uploads)
|
|
9
|
-
❌ When caching is critical (GraphQL caching is harder)
|
|
10
|
-
|
|
11
|
-
## Setup (Hot Chocolate)
|
|
12
|
-
```xml
|
|
13
|
-
<PackageReference Include="HotChocolate.AspNetCore" Version="14.*" />
|
|
14
|
-
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="14.*" />
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```csharp
|
|
18
|
-
builder.Services
|
|
19
|
-
.AddGraphQLServer()
|
|
20
|
-
.AddQueryType<QueryType>()
|
|
21
|
-
.AddMutationType<MutationType>()
|
|
22
|
-
.AddSubscriptionType<SubscriptionType>()
|
|
23
|
-
.AddProjections()
|
|
24
|
-
.AddFiltering()
|
|
25
|
-
.AddSorting()
|
|
26
|
-
.AddAuthorization();
|
|
27
|
-
|
|
28
|
-
app.MapGraphQL("/graphql");
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Query Type
|
|
32
|
-
```csharp
|
|
33
|
-
[QueryType]
|
|
34
|
-
public class QueryType
|
|
35
|
-
{
|
|
36
|
-
[UseProjection]
|
|
37
|
-
[UseFiltering]
|
|
38
|
-
[UseSorting]
|
|
39
|
-
[UsePaging]
|
|
40
|
-
public IQueryable<Order> GetOrders([Service] AppDbContext ctx) => ctx.Orders;
|
|
41
|
-
|
|
42
|
-
public async Task<Order?> GetOrder(Guid id, [Service] AppDbContext ctx)
|
|
43
|
-
=> await ctx.Orders.FindAsync(id);
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Mutation Type
|
|
48
|
-
```csharp
|
|
49
|
-
[MutationType]
|
|
50
|
-
public class MutationType
|
|
51
|
-
{
|
|
52
|
-
public async Task<CreateOrderPayload> CreateOrder(
|
|
53
|
-
CreateOrderInput input,
|
|
54
|
-
[Service] ISender sender)
|
|
55
|
-
{
|
|
56
|
-
var result = await sender.Send(new CreateOrderCommand(input.UserId, input.Items));
|
|
57
|
-
return new CreateOrderPayload(result.OrderId);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public record CreateOrderInput(Guid UserId, List<OrderItemInput> Items);
|
|
62
|
-
public record CreateOrderPayload(Guid OrderId);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## DataLoader (N+1 Prevention)
|
|
66
|
-
```csharp
|
|
67
|
-
public class UserDataLoader : BatchDataLoader<Guid, User>
|
|
68
|
-
{
|
|
69
|
-
protected override async Task<IReadOnlyDictionary<Guid, User>> LoadBatchAsync(
|
|
70
|
-
IReadOnlyList<Guid> keys, CancellationToken ct)
|
|
71
|
-
{
|
|
72
|
-
return await _context.Users
|
|
73
|
-
.Where(u => keys.Contains(u.Id))
|
|
74
|
-
.ToDictionaryAsync(u => u.Id, ct);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Use in resolver
|
|
79
|
-
public async Task<User?> GetUser([Parent] Order order, UserDataLoader loader)
|
|
80
|
-
=> await loader.LoadAsync(order.UserId);
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Security
|
|
84
|
-
- Apply `[Authorize]` on mutations and sensitive queries
|
|
85
|
-
- Disable introspection in production
|
|
86
|
-
- Depth limiting: max 5 levels
|
|
87
|
-
- Complexity limiting: max 1000 nodes per query
|
|
88
|
-
```csharp
|
|
89
|
-
.AddMaxExecutionDepthRule(5)
|
|
90
|
-
.ModifyRequestOptions(opts => opts.MaxAllowedRequestSize = 1_000_000)
|
|
91
|
-
```
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# Integration Standard: gRPC
|
|
2
|
-
|
|
3
|
-
## When to Use gRPC
|
|
4
|
-
✅ Internal service-to-service communication (high performance)
|
|
5
|
-
✅ Streaming (server-streaming for real-time data, client-streaming for uploads)
|
|
6
|
-
✅ Multi-language microservices (proto contracts shared across .NET, Go, Python)
|
|
7
|
-
❌ Browser-to-server (use gRPC-Web or REST instead)
|
|
8
|
-
❌ Debugging priority (binary format is harder to inspect than JSON)
|
|
9
|
-
|
|
10
|
-
## Setup
|
|
11
|
-
```xml
|
|
12
|
-
<PackageReference Include="Grpc.AspNetCore" Version="2.*" />
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
```csharp
|
|
16
|
-
// Program.cs
|
|
17
|
-
builder.Services.AddGrpc(opts =>
|
|
18
|
-
{
|
|
19
|
-
opts.EnableDetailedErrors = builder.Environment.IsDevelopment();
|
|
20
|
-
opts.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4 MB
|
|
21
|
-
});
|
|
22
|
-
app.MapGrpcService<OrdersService>();
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Proto Definition
|
|
26
|
-
```protobuf
|
|
27
|
-
syntax = "proto3";
|
|
28
|
-
option csharp_namespace = "MyApp.Grpc";
|
|
29
|
-
|
|
30
|
-
service Orders {
|
|
31
|
-
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
|
|
32
|
-
rpc GetOrder(GetOrderRequest) returns (OrderResponse);
|
|
33
|
-
rpc StreamOrders(StreamOrdersRequest) returns (stream OrderResponse);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
message CreateOrderRequest {
|
|
37
|
-
string user_id = 1;
|
|
38
|
-
repeated OrderItem items = 2;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
message CreateOrderResponse {
|
|
42
|
-
string order_id = 1;
|
|
43
|
-
double total = 2;
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Service Implementation
|
|
48
|
-
```csharp
|
|
49
|
-
public class OrdersService : Orders.OrdersBase
|
|
50
|
-
{
|
|
51
|
-
public override async Task<CreateOrderResponse> CreateOrder(
|
|
52
|
-
CreateOrderRequest request, ServerCallContext context)
|
|
53
|
-
{
|
|
54
|
-
var cmd = new CreateOrderCommand(
|
|
55
|
-
Guid.Parse(request.UserId),
|
|
56
|
-
request.Items.Select(i => new OrderItem(i.ProductId, i.Quantity)).ToList());
|
|
57
|
-
|
|
58
|
-
var result = await _sender.Send(cmd, context.CancellationToken);
|
|
59
|
-
|
|
60
|
-
return new CreateOrderResponse
|
|
61
|
-
{
|
|
62
|
-
OrderId = result.OrderId.ToString(),
|
|
63
|
-
Total = (double)result.Total
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public override async Task StreamOrders(
|
|
68
|
-
StreamOrdersRequest request,
|
|
69
|
-
IServerStreamWriter<OrderResponse> responseStream,
|
|
70
|
-
ServerCallContext context)
|
|
71
|
-
{
|
|
72
|
-
await foreach (var order in _service.GetOrderStreamAsync(context.CancellationToken))
|
|
73
|
-
{
|
|
74
|
-
await responseStream.WriteAsync(MapToResponse(order), context.CancellationToken);
|
|
75
|
-
if (context.CancellationToken.IsCancellationRequested) break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Client Setup
|
|
82
|
-
```csharp
|
|
83
|
-
// Program.cs (client service)
|
|
84
|
-
builder.Services.AddGrpcClient<Orders.OrdersClient>(opts =>
|
|
85
|
-
{
|
|
86
|
-
opts.Address = new Uri(config["Services:OrdersUrl"]);
|
|
87
|
-
})
|
|
88
|
-
.ConfigureChannel(opts =>
|
|
89
|
-
{
|
|
90
|
-
opts.HttpHandler = new SocketsHttpHandler
|
|
91
|
-
{
|
|
92
|
-
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
|
|
93
|
-
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
|
|
94
|
-
KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Error Handling
|
|
100
|
-
```csharp
|
|
101
|
-
// Map exceptions to gRPC status codes
|
|
102
|
-
try
|
|
103
|
-
{
|
|
104
|
-
return await _service.ProcessAsync(request.Id);
|
|
105
|
-
}
|
|
106
|
-
catch (NotFoundException)
|
|
107
|
-
{
|
|
108
|
-
throw new RpcException(new Status(StatusCode.NotFound, "Order not found"));
|
|
109
|
-
}
|
|
110
|
-
catch (ValidationException ex)
|
|
111
|
-
{
|
|
112
|
-
throw new RpcException(new Status(StatusCode.InvalidArgument, ex.Message));
|
|
113
|
-
}
|
|
114
|
-
```
|