@caddis/cli 0.1.0 → 0.2.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 +29 -16
- package/bundles/antigravity-plugin-extras/plugin.json +5 -0
- package/bundles/antigravity-plugin-extras/skills/_registry.md +154 -0
- package/bundles/antigravity-plugin-extras/skills/agent-md-refactor/SKILL.md +356 -0
- package/bundles/antigravity-plugin-extras/skills/agent-orchestration/SKILL.md +517 -0
- package/bundles/antigravity-plugin-extras/skills/algorithmic-art/SKILL.md +293 -0
- package/bundles/antigravity-plugin-extras/skills/anchor-review/SKILL.md +135 -0
- package/bundles/antigravity-plugin-extras/skills/api-client-patterns/SKILL.md +367 -0
- package/bundles/antigravity-plugin-extras/skills/architecture-design/SKILL.md +244 -0
- package/bundles/antigravity-plugin-extras/skills/architecture-diagram/SKILL.md +156 -0
- package/bundles/antigravity-plugin-extras/skills/architecture-document/SKILL.md +321 -0
- package/bundles/antigravity-plugin-extras/skills/artifacts-builder/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/artifacts-builder/SKILL.md +74 -0
- package/bundles/antigravity-plugin-extras/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/bundles/antigravity-plugin-extras/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
- package/bundles/antigravity-plugin-extras/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/bundles/antigravity-plugin-extras/skills/ascii-art/SKILL.md +144 -0
- package/bundles/antigravity-plugin-extras/skills/asking-questions/SKILL.md +81 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/SKILL.md +118 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/cross-service/credential-management.md +450 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/browser/README.md +363 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/code-interpreter/README.md +232 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/README.md +100 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/deploy-template.sh +117 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/deployment-strategies.md +416 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/troubleshooting-guide.md +384 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/validate-deployment.sh +186 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/identity/README.md +186 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/memory/README.md +253 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/observability/README.md +517 -0
- package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/runtime/README.md +215 -0
- package/bundles/antigravity-plugin-extras/skills/aws-cdk-development/SKILL.md +279 -0
- package/bundles/antigravity-plugin-extras/skills/aws-cdk-development/references/cdk-patterns.md +431 -0
- package/bundles/antigravity-plugin-extras/skills/aws-cdk-development/scripts/validate-stack.sh +240 -0
- package/bundles/antigravity-plugin-extras/skills/aws-cost-operations/SKILL.md +318 -0
- package/bundles/antigravity-plugin-extras/skills/aws-cost-operations/references/cloudwatch-alarms.md +567 -0
- package/bundles/antigravity-plugin-extras/skills/aws-cost-operations/references/operations-patterns.md +394 -0
- package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/SKILL.md +758 -0
- package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/deployment-best-practices.md +830 -0
- package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/eda-patterns.md +1002 -0
- package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/observability-best-practices.md +770 -0
- package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/performance-optimization.md +671 -0
- package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/security-best-practices.md +625 -0
- package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/serverless-patterns.md +838 -0
- package/bundles/antigravity-plugin-extras/skills/backend-to-frontend-handoff/SKILL.md +139 -0
- package/bundles/antigravity-plugin-extras/skills/banner-design/SKILL.md +188 -0
- package/bundles/antigravity-plugin-extras/skills/brand-design/SKILL.md +298 -0
- package/bundles/antigravity-plugin-extras/skills/brand-guidelines/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/brand-guidelines/SKILL.md +118 -0
- package/bundles/antigravity-plugin-extras/skills/brand-voice/SKILL.md +93 -0
- package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/SKILL.md +392 -0
- package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/agent-patterns.md +461 -0
- package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/examples.md +188 -0
- package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/state-patterns.md +360 -0
- package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/troubleshooting.md +362 -0
- package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/SKILL.md +266 -0
- package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/references/examples.md +115 -0
- package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/references/oauth-setup.md +338 -0
- package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/references/troubleshooting.md +317 -0
- package/bundles/antigravity-plugin-extras/skills/caching-patterns/SKILL.md +385 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/SKILL.md +91 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/changelog-generator/SKILL.md +105 -0
- package/bundles/antigravity-plugin-extras/skills/code-explainer/SKILL.md +91 -0
- package/bundles/antigravity-plugin-extras/skills/competitive-ads-extractor/SKILL.md +294 -0
- package/bundles/antigravity-plugin-extras/skills/component-testing/SKILL.md +310 -0
- package/bundles/antigravity-plugin-extras/skills/content-research-writer/SKILL.md +539 -0
- package/bundles/antigravity-plugin-extras/skills/cross-review/SKILL.md +51 -0
- package/bundles/antigravity-plugin-extras/skills/data-analysis/SKILL.md +111 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/README.md +248 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/SKILL.md +203 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/adapter.py.template +44 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/api-router.py.template +43 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/contract-test.py.template +384 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/display-dto.py.template +39 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/frontend-service.ts.template +46 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/frontend-types.ts.template +38 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/ingestion-model.py.template +42 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/mapping-doc.md.template +46 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/normalizer.py.template +48 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/prompts/extract_requirements.md +36 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/prompts/extract_ui_demand.md +35 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/prompts/reconcile_mapping.md +66 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/query-catalog.md.template +62 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/references/consumer-driven-contracts.md +131 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/references/drift-check-catalog.md +305 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/references/layer-responsibilities.md +333 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/discover_connections.py +170 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/discover_sources.py +161 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/drift_check.py +171 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/extract_schema.py +1363 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/generate_gap_report.py +81 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/generate_mapping_doc.py +294 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/ts_dto_compare.py +214 -0
- package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/validate_lineage.py +51 -0
- package/bundles/antigravity-plugin-extras/skills/data-loader/SKILL.md +753 -0
- package/bundles/antigravity-plugin-extras/skills/data-validation/SKILL.md +220 -0
- package/bundles/antigravity-plugin-extras/skills/db-testing/SKILL.md +117 -0
- package/bundles/antigravity-plugin-extras/skills/deploy-local/SKILL.md +259 -0
- package/bundles/antigravity-plugin-extras/skills/design-md/SKILL.md +167 -0
- package/bundles/antigravity-plugin-extras/skills/design-system-tokens/SKILL.md +240 -0
- package/bundles/antigravity-plugin-extras/skills/design-system-tokens/references/DESIGN-SYSTEM-TEMPLATE.md +577 -0
- package/bundles/antigravity-plugin-extras/skills/developer-growth-analysis/SKILL.md +323 -0
- package/bundles/antigravity-plugin-extras/skills/doc-coauthoring/SKILL.md +378 -0
- package/bundles/antigravity-plugin-extras/skills/documentation-analyzer/SKILL.md +78 -0
- package/bundles/antigravity-plugin-extras/skills/domain-name-brainstormer/SKILL.md +213 -0
- package/bundles/antigravity-plugin-extras/skills/enterprise-dashboard-aesthetic-system/SKILL.md +120 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/SKILL.md +193 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/references/element-types.md +497 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/references/excalidraw-schema.md +356 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/references/reliable-diagrams.md +115 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/README.md +60 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/add-arrow.py +354 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/add-icon-to-diagram.py +412 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/split-excalidraw-library.py +183 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/business-flow-swimlane-template.excalidraw +334 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/class-diagram-template.excalidraw +558 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/data-flow-diagram-template.excalidraw +279 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/er-diagram-template.excalidraw +662 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/flowchart-template.excalidraw +179 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/mindmap-template.excalidraw +244 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/relationship-template.excalidraw +145 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/reliable-flowchart.excalidraw +1 -0
- package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/sequence-diagram-template.excalidraw +509 -0
- package/bundles/antigravity-plugin-extras/skills/file-organizer/SKILL.md +434 -0
- package/bundles/antigravity-plugin-extras/skills/game-changing-features/README.md +221 -0
- package/bundles/antigravity-plugin-extras/skills/game-changing-features/SKILL.md +265 -0
- package/bundles/antigravity-plugin-extras/skills/github-issues/SKILL.md +359 -0
- package/bundles/antigravity-plugin-extras/skills/github-issues/references/templates.md +90 -0
- package/bundles/antigravity-plugin-extras/skills/golden-nuggets/SKILL.md +192 -0
- package/bundles/antigravity-plugin-extras/skills/humanizer/SKILL.md +194 -0
- package/bundles/antigravity-plugin-extras/skills/image-enhancer/SKILL.md +100 -0
- package/bundles/antigravity-plugin-extras/skills/intent-writer/SKILL.md +201 -0
- package/bundles/antigravity-plugin-extras/skills/internal-comms/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/internal-comms/SKILL.md +124 -0
- package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/3p-updates.md +47 -0
- package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/company-newsletter.md +65 -0
- package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/faq-answers.md +30 -0
- package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/general-comms.md +16 -0
- package/bundles/antigravity-plugin-extras/skills/invoice-organizer/SKILL.md +447 -0
- package/bundles/antigravity-plugin-extras/skills/jira-issues/SKILL.md +182 -0
- package/bundles/antigravity-plugin-extras/skills/job-application/SKILL.md +188 -0
- package/bundles/antigravity-plugin-extras/skills/lead-research-assistant/SKILL.md +200 -0
- package/bundles/antigravity-plugin-extras/skills/llm-application-dev/SKILL.md +217 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/SKILL.md +100 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/evaluation.md +602 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/mcp_best_practices.md +915 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/node_mcp_server.md +916 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/python_mcp_server.md +752 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/connections.py +151 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/bundles/antigravity-plugin-extras/skills/meeting-insights-analyzer/SKILL.md +328 -0
- package/bundles/antigravity-plugin-extras/skills/monorepo/SKILL.md +267 -0
- package/bundles/antigravity-plugin-extras/skills/naming-analyzer/SKILL.md +376 -0
- package/bundles/antigravity-plugin-extras/skills/nextjs-app-router/SKILL.md +271 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/AUTHENTICATION.md +154 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/SKILL.md +270 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/references/api_reference.md +309 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/references/troubleshooting.md +376 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/references/usage_patterns.md +338 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/__init__.py +81 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/ask_question.py +256 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/auth_manager.py +358 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/browser_session.py +255 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/browser_utils.py +107 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/cleanup_manager.py +302 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/config.py +44 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/notebook_manager.py +410 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/run.py +102 -0
- package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/setup_environment.py +204 -0
- package/bundles/antigravity-plugin-extras/skills/observability/SKILL.md +371 -0
- package/bundles/antigravity-plugin-extras/skills/onboard-project/SKILL.md +192 -0
- package/bundles/antigravity-plugin-extras/skills/performance-testing/SKILL.md +226 -0
- package/bundles/antigravity-plugin-extras/skills/pipeline-state/SKILL.md +208 -0
- package/bundles/antigravity-plugin-extras/skills/plantuml/SKILL.md +228 -0
- package/bundles/antigravity-plugin-extras/skills/popular-web-designs/SKILL.md +142 -0
- package/bundles/antigravity-plugin-extras/skills/popular-web-designs/templates/linear.app.md +196 -0
- package/bundles/antigravity-plugin-extras/skills/popular-web-designs/templates/stripe.md +214 -0
- package/bundles/antigravity-plugin-extras/skills/popular-web-designs/templates/vercel.md +218 -0
- package/bundles/antigravity-plugin-extras/skills/prd-to-code/SKILL.md +127 -0
- package/bundles/antigravity-plugin-extras/skills/raffle-winner-picker/SKILL.md +159 -0
- package/bundles/antigravity-plugin-extras/skills/react-useeffect/README.md +320 -0
- package/bundles/antigravity-plugin-extras/skills/react-useeffect/SKILL.md +53 -0
- package/bundles/antigravity-plugin-extras/skills/react-useeffect/alternatives.md +258 -0
- package/bundles/antigravity-plugin-extras/skills/react-useeffect/anti-patterns.md +290 -0
- package/bundles/antigravity-plugin-extras/skills/receiving-code-review/SKILL.md +213 -0
- package/bundles/antigravity-plugin-extras/skills/relay/SKILL.md +246 -0
- package/bundles/antigravity-plugin-extras/skills/requesting-code-review/SKILL.md +105 -0
- package/bundles/antigravity-plugin-extras/skills/requesting-code-review/code-reviewer.md +146 -0
- package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/SKILL.md +350 -0
- package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/references/pwa-checklist.md +226 -0
- package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/references/touch-and-interaction.md +265 -0
- package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/references/transformation-catalogue.md +326 -0
- package/bundles/antigravity-plugin-extras/skills/schema-migration/SKILL.md +275 -0
- package/bundles/antigravity-plugin-extras/skills/setup-project-ai/SKILL.md +120 -0
- package/bundles/antigravity-plugin-extras/skills/shadcn-radix/SKILL.md +330 -0
- package/bundles/antigravity-plugin-extras/skills/sketch/SKILL.md +104 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/SKILL.md +646 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/color_palettes.py +302 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/easing.py +230 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/frame_composer.py +469 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/gif_builder.py +246 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/typography.py +357 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/validators.py +264 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/visual_effects.py +494 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/requirements.txt +4 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/bounce.py +106 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/explode.py +331 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/fade.py +329 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/flip.py +291 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/kaleidoscope.py +211 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/morph.py +329 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/move.py +293 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/pulse.py +268 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/shake.py +127 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/slide.py +291 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/spin.py +269 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/wiggle.py +300 -0
- package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/zoom.py +312 -0
- package/bundles/antigravity-plugin-extras/skills/slides/SKILL.md +38 -0
- package/bundles/antigravity-plugin-extras/skills/state-tracking/SKILL.md +131 -0
- package/bundles/antigravity-plugin-extras/skills/streamlit-dev/SKILL.md +268 -0
- package/bundles/antigravity-plugin-extras/skills/svg-create/SKILL.md +251 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/SKILL.md +59 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/theme-showcase.pdf +0 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/desert-rose.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/golden-hour.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/bundles/antigravity-plugin-extras/skills/ui-styling-patterns/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin-extras/skills/ui-styling-patterns/SKILL.md +320 -0
- package/bundles/antigravity-plugin-extras/skills/ui-testing/SKILL.md +660 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/SKILL.md +660 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/_sync_all.py +414 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/app-interface.csv +31 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/charts.csv +26 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/colors.csv +162 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/design.csv +1776 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/draft.csv +1779 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/google-fonts.csv +1924 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/icons.csv +106 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/landing.csv +35 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/products.csv +162 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/react-performance.csv +45 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/styles.csv +85 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/typography.csv +74 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/ui-reasoning.csv +162 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/ux-guidelines.csv +100 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/scripts/core.py +247 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/scripts/design_system.py +1067 -0
- package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/scripts/search.py +114 -0
- package/bundles/antigravity-plugin-extras/skills/understand-anything/SKILL.md +140 -0
- package/bundles/antigravity-plugin-extras/skills/understand-anything/references/commands.md +252 -0
- package/bundles/antigravity-plugin-extras/skills/understand-anything/references/graph-schema.md +116 -0
- package/bundles/antigravity-plugin-extras/skills/understand-anything/references/pipeline.md +331 -0
- package/bundles/antigravity-plugin-extras/skills/update-readme/SKILL.md +74 -0
- package/bundles/antigravity-plugin-extras/skills/verification-loop/SKILL.md +209 -0
- package/bundles/antigravity-plugin-extras/skills/video-downloader/SKILL.md +107 -0
- package/bundles/antigravity-plugin-extras/skills/webapp-development/SKILL.md +288 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/SKILL.md +339 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/agents/agent-interface.md +175 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/references/html.md +384 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/references/nlp-keywords.md +127 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/references/palettes.md +149 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/references/python.md +249 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/references/react.md +427 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/references/shapes.md +135 -0
- package/bundles/antigravity-plugin-extras/skills/word-cloud/references/stopwords.md +115 -0
- package/bundles/antigravity-plugin-extras/skills/youtube-transcript/SKILL.md +416 -0
- package/bundles/manifest.json +2 -1
- package/dist/cli.js +166 -37
- package/package.json +1 -1
package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/eda-patterns.md
ADDED
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
# Event-Driven Architecture Patterns
|
|
2
|
+
|
|
3
|
+
Comprehensive patterns for building event-driven systems on AWS with serverless technologies.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Core EDA Concepts](#core-eda-concepts)
|
|
8
|
+
- [Event Routing Patterns](#event-routing-patterns)
|
|
9
|
+
- [Event Processing Patterns](#event-processing-patterns)
|
|
10
|
+
- [Event Sourcing Patterns](#event-sourcing-patterns)
|
|
11
|
+
- [Saga Patterns](#saga-patterns)
|
|
12
|
+
- [Best Practices](#best-practices)
|
|
13
|
+
|
|
14
|
+
## Core EDA Concepts
|
|
15
|
+
|
|
16
|
+
### Event Types
|
|
17
|
+
|
|
18
|
+
**Domain Events**: Represent business facts
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"source": "orders",
|
|
22
|
+
"detailType": "OrderPlaced",
|
|
23
|
+
"detail": {
|
|
24
|
+
"orderId": "12345",
|
|
25
|
+
"customerId": "customer-1",
|
|
26
|
+
"amount": 100.00,
|
|
27
|
+
"timestamp": "2025-01-15T10:30:00Z"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**System Events**: Technical occurrences
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"source": "aws.s3",
|
|
36
|
+
"detailType": "Object Created",
|
|
37
|
+
"detail": {
|
|
38
|
+
"bucket": "my-bucket",
|
|
39
|
+
"key": "data/file.json"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Event Contracts
|
|
45
|
+
|
|
46
|
+
Define clear contracts between producers and consumers:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// schemas/order-events.ts
|
|
50
|
+
export interface OrderPlacedEvent {
|
|
51
|
+
orderId: string;
|
|
52
|
+
customerId: string;
|
|
53
|
+
items: Array<{
|
|
54
|
+
productId: string;
|
|
55
|
+
quantity: number;
|
|
56
|
+
price: number;
|
|
57
|
+
}>;
|
|
58
|
+
totalAmount: number;
|
|
59
|
+
timestamp: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Register schema with EventBridge
|
|
63
|
+
const registry = new events.EventBusSchemaRegistry(this, 'SchemaRegistry');
|
|
64
|
+
|
|
65
|
+
const schema = new events.Schema(this, 'OrderPlacedSchema', {
|
|
66
|
+
schemaName: 'OrderPlaced',
|
|
67
|
+
definition: events.SchemaDefinition.fromInline(/* JSON Schema */),
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Event Routing Patterns
|
|
72
|
+
|
|
73
|
+
### Pattern 1: Content-Based Routing
|
|
74
|
+
|
|
75
|
+
Route events based on content:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Route by order amount
|
|
79
|
+
new events.Rule(this, 'HighValueOrders', {
|
|
80
|
+
eventPattern: {
|
|
81
|
+
source: ['orders'],
|
|
82
|
+
detailType: ['OrderPlaced'],
|
|
83
|
+
detail: {
|
|
84
|
+
totalAmount: [{ numeric: ['>', 1000] }],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
targets: [new targets.LambdaFunction(highValueOrderFunction)],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
new events.Rule(this, 'StandardOrders', {
|
|
91
|
+
eventPattern: {
|
|
92
|
+
source: ['orders'],
|
|
93
|
+
detailType: ['OrderPlaced'],
|
|
94
|
+
detail: {
|
|
95
|
+
totalAmount: [{ numeric: ['<=', 1000] }],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
targets: [new targets.LambdaFunction(standardOrderFunction)],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Pattern 2: Event Filtering
|
|
103
|
+
|
|
104
|
+
Filter events before processing:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Filter by multiple criteria
|
|
108
|
+
new events.Rule(this, 'FilteredRule', {
|
|
109
|
+
eventPattern: {
|
|
110
|
+
source: ['inventory'],
|
|
111
|
+
detailType: ['StockUpdate'],
|
|
112
|
+
detail: {
|
|
113
|
+
warehouseId: ['WH-1', 'WH-2'], // Specific warehouses
|
|
114
|
+
quantity: [{ numeric: ['<', 10] }], // Low stock only
|
|
115
|
+
productCategory: ['electronics'], // Specific category
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
targets: [new targets.LambdaFunction(reorderFunction)],
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Pattern 3: Event Replay and Archive
|
|
123
|
+
|
|
124
|
+
Store events for replay and audit:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Archive all events
|
|
128
|
+
const archive = new events.Archive(this, 'EventArchive', {
|
|
129
|
+
eventPattern: {
|
|
130
|
+
account: [this.account],
|
|
131
|
+
},
|
|
132
|
+
retention: Duration.days(365),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Replay events when needed
|
|
136
|
+
// Use AWS Console or CLI to replay from archive
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Pattern 4: Cross-Account Event Routing
|
|
140
|
+
|
|
141
|
+
Route events to other AWS accounts:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Event bus in Account A
|
|
145
|
+
const eventBus = new events.EventBus(this, 'SharedBus');
|
|
146
|
+
|
|
147
|
+
// Grant permission to Account B
|
|
148
|
+
eventBus.addToResourcePolicy(new iam.PolicyStatement({
|
|
149
|
+
effect: iam.Effect.ALLOW,
|
|
150
|
+
principals: [new iam.AccountPrincipal('ACCOUNT-B-ID')],
|
|
151
|
+
actions: ['events:PutEvents'],
|
|
152
|
+
resources: [eventBus.eventBusArn],
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
// Rule forwards to Account B event bus
|
|
156
|
+
new events.Rule(this, 'ForwardToAccountB', {
|
|
157
|
+
eventBus,
|
|
158
|
+
eventPattern: {
|
|
159
|
+
source: ['shared-service'],
|
|
160
|
+
},
|
|
161
|
+
targets: [new targets.EventBus(
|
|
162
|
+
events.EventBus.fromEventBusArn(
|
|
163
|
+
this,
|
|
164
|
+
'AccountBBus',
|
|
165
|
+
'arn:aws:events:us-east-1:ACCOUNT-B-ID:event-bus/default'
|
|
166
|
+
)
|
|
167
|
+
)],
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Event Processing Patterns
|
|
172
|
+
|
|
173
|
+
### Pattern 1: Event Transformation
|
|
174
|
+
|
|
175
|
+
Transform events before routing:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// EventBridge input transformer
|
|
179
|
+
new events.Rule(this, 'TransformRule', {
|
|
180
|
+
eventPattern: {
|
|
181
|
+
source: ['orders'],
|
|
182
|
+
},
|
|
183
|
+
targets: [new targets.LambdaFunction(processFunction, {
|
|
184
|
+
event: events.RuleTargetInput.fromObject({
|
|
185
|
+
orderId: events.EventField.fromPath('$.detail.orderId'),
|
|
186
|
+
customerEmail: events.EventField.fromPath('$.detail.customer.email'),
|
|
187
|
+
amount: events.EventField.fromPath('$.detail.totalAmount'),
|
|
188
|
+
// Transformed structure
|
|
189
|
+
}),
|
|
190
|
+
})],
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Pattern 2: Event Aggregation
|
|
195
|
+
|
|
196
|
+
Aggregate multiple events:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// DynamoDB stores partial results
|
|
200
|
+
export const handler = async (event: any) => {
|
|
201
|
+
const { transactionId, step, data } = event;
|
|
202
|
+
|
|
203
|
+
// Store step result
|
|
204
|
+
await dynamodb.updateItem({
|
|
205
|
+
TableName: process.env.TABLE_NAME,
|
|
206
|
+
Key: { transactionId },
|
|
207
|
+
UpdateExpression: 'SET #step = :data',
|
|
208
|
+
ExpressionAttributeNames: { '#step': step },
|
|
209
|
+
ExpressionAttributeValues: { ':data': data },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Check if all steps complete
|
|
213
|
+
const item = await dynamodb.getItem({
|
|
214
|
+
TableName: process.env.TABLE_NAME,
|
|
215
|
+
Key: { transactionId },
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (allStepsComplete(item)) {
|
|
219
|
+
// Trigger final processing
|
|
220
|
+
await eventBridge.putEvents({
|
|
221
|
+
Entries: [{
|
|
222
|
+
Source: 'aggregator',
|
|
223
|
+
DetailType: 'AllStepsComplete',
|
|
224
|
+
Detail: JSON.stringify(item),
|
|
225
|
+
}],
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Pattern 3: Event Enrichment
|
|
232
|
+
|
|
233
|
+
Enrich events with additional data:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
export const enrichEvent = async (event: any) => {
|
|
237
|
+
const { customerId } = event.detail;
|
|
238
|
+
|
|
239
|
+
// Fetch additional customer data
|
|
240
|
+
const customer = await dynamodb.getItem({
|
|
241
|
+
TableName: process.env.CUSTOMER_TABLE,
|
|
242
|
+
Key: { customerId },
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Publish enriched event
|
|
246
|
+
await eventBridge.putEvents({
|
|
247
|
+
Entries: [{
|
|
248
|
+
Source: 'orders',
|
|
249
|
+
DetailType: 'OrderEnriched',
|
|
250
|
+
Detail: JSON.stringify({
|
|
251
|
+
...event.detail,
|
|
252
|
+
customerName: customer.Item?.name,
|
|
253
|
+
customerTier: customer.Item?.tier,
|
|
254
|
+
customerEmail: customer.Item?.email,
|
|
255
|
+
}),
|
|
256
|
+
}],
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Pattern 4: Event Fork and Join
|
|
262
|
+
|
|
263
|
+
Process event multiple ways then aggregate:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// Step Functions parallel + aggregation
|
|
267
|
+
const parallel = new stepfunctions.Parallel(this, 'ForkProcessing');
|
|
268
|
+
|
|
269
|
+
parallel.branch(new tasks.LambdaInvoke(this, 'ValidateInventory', {
|
|
270
|
+
lambdaFunction: inventoryFunction,
|
|
271
|
+
resultPath: '$.inventory',
|
|
272
|
+
}));
|
|
273
|
+
|
|
274
|
+
parallel.branch(new tasks.LambdaInvoke(this, 'CheckCredit', {
|
|
275
|
+
lambdaFunction: creditFunction,
|
|
276
|
+
resultPath: '$.credit',
|
|
277
|
+
}));
|
|
278
|
+
|
|
279
|
+
parallel.branch(new tasks.LambdaInvoke(this, 'CalculateShipping', {
|
|
280
|
+
lambdaFunction: shippingFunction,
|
|
281
|
+
resultPath: '$.shipping',
|
|
282
|
+
}));
|
|
283
|
+
|
|
284
|
+
const definition = parallel.next(
|
|
285
|
+
new tasks.LambdaInvoke(this, 'AggregateResults', {
|
|
286
|
+
lambdaFunction: aggregateFunction,
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Event Sourcing Patterns
|
|
292
|
+
|
|
293
|
+
### Pattern: Event Store with DynamoDB
|
|
294
|
+
|
|
295
|
+
Store all events as source of truth:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const eventStore = new dynamodb.Table(this, 'EventStore', {
|
|
299
|
+
partitionKey: { name: 'aggregateId', type: dynamodb.AttributeType.STRING },
|
|
300
|
+
sortKey: { name: 'version', type: dynamodb.AttributeType.NUMBER },
|
|
301
|
+
stream: dynamodb.StreamViewType.NEW_IMAGE,
|
|
302
|
+
pointInTimeRecovery: true, // Important for audit
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Append events
|
|
306
|
+
export const appendEvent = async (aggregateId: string, event: any) => {
|
|
307
|
+
const version = await getNextVersion(aggregateId);
|
|
308
|
+
|
|
309
|
+
await dynamodb.putItem({
|
|
310
|
+
TableName: process.env.EVENT_STORE,
|
|
311
|
+
Item: {
|
|
312
|
+
aggregateId,
|
|
313
|
+
version,
|
|
314
|
+
eventType: event.type,
|
|
315
|
+
eventData: event.data,
|
|
316
|
+
timestamp: Date.now(),
|
|
317
|
+
userId: event.userId,
|
|
318
|
+
},
|
|
319
|
+
ConditionExpression: 'attribute_not_exists(version)', // Optimistic locking
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Rebuild state from events
|
|
324
|
+
export const rebuildState = async (aggregateId: string) => {
|
|
325
|
+
const events = await dynamodb.query({
|
|
326
|
+
TableName: process.env.EVENT_STORE,
|
|
327
|
+
KeyConditionExpression: 'aggregateId = :id',
|
|
328
|
+
ExpressionAttributeValues: { ':id': aggregateId },
|
|
329
|
+
ScanIndexForward: true, // Chronological order
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
let state = initialState();
|
|
333
|
+
for (const event of events.Items) {
|
|
334
|
+
state = applyEvent(state, event);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return state;
|
|
338
|
+
};
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Pattern: Materialized Views
|
|
342
|
+
|
|
343
|
+
Create read-optimized projections:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// Event store stream triggers projection
|
|
347
|
+
eventStore.grantStreamRead(projectionFunction);
|
|
348
|
+
|
|
349
|
+
new lambda.EventSourceMapping(this, 'Projection', {
|
|
350
|
+
target: projectionFunction,
|
|
351
|
+
eventSourceArn: eventStore.tableStreamArn,
|
|
352
|
+
startingPosition: lambda.StartingPosition.LATEST,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Projection function updates read model
|
|
356
|
+
export const updateProjection = async (event: DynamoDBStreamEvent) => {
|
|
357
|
+
for (const record of event.Records) {
|
|
358
|
+
if (record.eventName !== 'INSERT') continue;
|
|
359
|
+
|
|
360
|
+
const eventData = record.dynamodb?.NewImage;
|
|
361
|
+
const aggregateId = eventData?.aggregateId.S;
|
|
362
|
+
|
|
363
|
+
// Rebuild current state
|
|
364
|
+
const currentState = await rebuildState(aggregateId);
|
|
365
|
+
|
|
366
|
+
// Update read model
|
|
367
|
+
await readModelTable.putItem({
|
|
368
|
+
TableName: process.env.READ_MODEL_TABLE,
|
|
369
|
+
Item: currentState,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Pattern: Snapshots
|
|
376
|
+
|
|
377
|
+
Optimize event replay with snapshots:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
export const createSnapshot = async (aggregateId: string) => {
|
|
381
|
+
// Rebuild state from all events
|
|
382
|
+
const state = await rebuildState(aggregateId);
|
|
383
|
+
const version = await getLatestVersion(aggregateId);
|
|
384
|
+
|
|
385
|
+
// Store snapshot
|
|
386
|
+
await snapshotTable.putItem({
|
|
387
|
+
TableName: process.env.SNAPSHOT_TABLE,
|
|
388
|
+
Item: {
|
|
389
|
+
aggregateId,
|
|
390
|
+
version,
|
|
391
|
+
state: JSON.stringify(state),
|
|
392
|
+
createdAt: Date.now(),
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Rebuild from snapshot + newer events
|
|
398
|
+
export const rebuildFromSnapshot = async (aggregateId: string) => {
|
|
399
|
+
// Get latest snapshot
|
|
400
|
+
const snapshot = await getLatestSnapshot(aggregateId);
|
|
401
|
+
|
|
402
|
+
let state = JSON.parse(snapshot.state);
|
|
403
|
+
const snapshotVersion = snapshot.version;
|
|
404
|
+
|
|
405
|
+
// Apply only events after snapshot
|
|
406
|
+
const events = await getEventsSinceVersion(aggregateId, snapshotVersion);
|
|
407
|
+
|
|
408
|
+
for (const event of events) {
|
|
409
|
+
state = applyEvent(state, event);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return state;
|
|
413
|
+
};
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Saga Patterns
|
|
417
|
+
|
|
418
|
+
### Pattern: Choreography-Based Saga
|
|
419
|
+
|
|
420
|
+
Services coordinate through events:
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
// Order Service publishes event
|
|
424
|
+
export const placeOrder = async (order: Order) => {
|
|
425
|
+
await saveOrder(order);
|
|
426
|
+
|
|
427
|
+
await eventBridge.putEvents({
|
|
428
|
+
Entries: [{
|
|
429
|
+
Source: 'orders',
|
|
430
|
+
DetailType: 'OrderPlaced',
|
|
431
|
+
Detail: JSON.stringify({ orderId: order.id }),
|
|
432
|
+
}],
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// Inventory Service reacts to event
|
|
437
|
+
new events.Rule(this, 'ReserveInventory', {
|
|
438
|
+
eventPattern: {
|
|
439
|
+
source: ['orders'],
|
|
440
|
+
detailType: ['OrderPlaced'],
|
|
441
|
+
},
|
|
442
|
+
targets: [new targets.LambdaFunction(reserveInventoryFunction)],
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Inventory Service publishes result
|
|
446
|
+
export const reserveInventory = async (event: any) => {
|
|
447
|
+
const { orderId } = event.detail;
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
await reserve(orderId);
|
|
451
|
+
|
|
452
|
+
await eventBridge.putEvents({
|
|
453
|
+
Entries: [{
|
|
454
|
+
Source: 'inventory',
|
|
455
|
+
DetailType: 'InventoryReserved',
|
|
456
|
+
Detail: JSON.stringify({ orderId }),
|
|
457
|
+
}],
|
|
458
|
+
});
|
|
459
|
+
} catch (error) {
|
|
460
|
+
await eventBridge.putEvents({
|
|
461
|
+
Entries: [{
|
|
462
|
+
Source: 'inventory',
|
|
463
|
+
DetailType: 'InventoryReservationFailed',
|
|
464
|
+
Detail: JSON.stringify({ orderId, error: error.message }),
|
|
465
|
+
}],
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// Payment Service reacts to inventory event
|
|
471
|
+
new events.Rule(this, 'ProcessPayment', {
|
|
472
|
+
eventPattern: {
|
|
473
|
+
source: ['inventory'],
|
|
474
|
+
detailType: ['InventoryReserved'],
|
|
475
|
+
},
|
|
476
|
+
targets: [new targets.LambdaFunction(processPaymentFunction)],
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Pattern: Orchestration-Based Saga
|
|
481
|
+
|
|
482
|
+
Central coordinator manages saga:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
// Step Functions orchestrates saga
|
|
486
|
+
const definition = new tasks.LambdaInvoke(this, 'ReserveInventory', {
|
|
487
|
+
lambdaFunction: reserveInventoryFunction,
|
|
488
|
+
resultPath: '$.inventory',
|
|
489
|
+
})
|
|
490
|
+
.next(new tasks.LambdaInvoke(this, 'ProcessPayment', {
|
|
491
|
+
lambdaFunction: processPaymentFunction,
|
|
492
|
+
resultPath: '$.payment',
|
|
493
|
+
}))
|
|
494
|
+
.next(new tasks.LambdaInvoke(this, 'ShipOrder', {
|
|
495
|
+
lambdaFunction: shipOrderFunction,
|
|
496
|
+
resultPath: '$.shipment',
|
|
497
|
+
}))
|
|
498
|
+
.addCatch(
|
|
499
|
+
// Compensation flow
|
|
500
|
+
new tasks.LambdaInvoke(this, 'RefundPayment', {
|
|
501
|
+
lambdaFunction: refundFunction,
|
|
502
|
+
})
|
|
503
|
+
.next(new tasks.LambdaInvoke(this, 'ReleaseInventory', {
|
|
504
|
+
lambdaFunction: releaseFunction,
|
|
505
|
+
})),
|
|
506
|
+
{
|
|
507
|
+
errors: ['States.TaskFailed'],
|
|
508
|
+
resultPath: '$.error',
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
new stepfunctions.StateMachine(this, 'OrderSaga', {
|
|
513
|
+
definition,
|
|
514
|
+
tracingEnabled: true,
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Comparison**:
|
|
519
|
+
|
|
520
|
+
| Aspect | Choreography | Orchestration |
|
|
521
|
+
|--------|--------------|---------------|
|
|
522
|
+
| Coordination | Decentralized | Centralized |
|
|
523
|
+
| Coupling | Loose | Tighter |
|
|
524
|
+
| Visibility | Distributed logs | Single execution history |
|
|
525
|
+
| Debugging | Harder (trace across services) | Easier (single workflow) |
|
|
526
|
+
| Best for | Simple flows | Complex flows |
|
|
527
|
+
|
|
528
|
+
## Best Practices
|
|
529
|
+
|
|
530
|
+
### Idempotency
|
|
531
|
+
|
|
532
|
+
**Always make event handlers idempotent**:
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
// Use idempotency keys
|
|
536
|
+
export const handler = async (event: any) => {
|
|
537
|
+
const idempotencyKey = event.requestId || event.messageId;
|
|
538
|
+
|
|
539
|
+
// Check if already processed
|
|
540
|
+
try {
|
|
541
|
+
const existing = await dynamodb.getItem({
|
|
542
|
+
TableName: process.env.IDEMPOTENCY_TABLE,
|
|
543
|
+
Key: { idempotencyKey },
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
if (existing.Item) {
|
|
547
|
+
console.log('Already processed:', idempotencyKey);
|
|
548
|
+
return existing.Item.result; // Return cached result
|
|
549
|
+
}
|
|
550
|
+
} catch (error) {
|
|
551
|
+
// First time processing
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Process event
|
|
555
|
+
const result = await processEvent(event);
|
|
556
|
+
|
|
557
|
+
// Store result
|
|
558
|
+
await dynamodb.putItem({
|
|
559
|
+
TableName: process.env.IDEMPOTENCY_TABLE,
|
|
560
|
+
Item: {
|
|
561
|
+
idempotencyKey,
|
|
562
|
+
result,
|
|
563
|
+
processedAt: Date.now(),
|
|
564
|
+
},
|
|
565
|
+
// Optional: Set TTL for cleanup
|
|
566
|
+
ExpirationTime: Math.floor(Date.now() / 1000) + 86400, // 24 hours
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
return result;
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Event Versioning
|
|
574
|
+
|
|
575
|
+
**Handle event schema evolution**:
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
// Version events
|
|
579
|
+
interface OrderPlacedEventV1 {
|
|
580
|
+
version: '1.0';
|
|
581
|
+
orderId: string;
|
|
582
|
+
amount: number;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
interface OrderPlacedEventV2 {
|
|
586
|
+
version: '2.0';
|
|
587
|
+
orderId: string;
|
|
588
|
+
amount: number;
|
|
589
|
+
currency: string; // New field
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Handler supports multiple versions
|
|
593
|
+
export const handler = async (event: any) => {
|
|
594
|
+
const eventVersion = event.detail.version || '1.0';
|
|
595
|
+
|
|
596
|
+
switch (eventVersion) {
|
|
597
|
+
case '1.0':
|
|
598
|
+
return processV1(event.detail as OrderPlacedEventV1);
|
|
599
|
+
case '2.0':
|
|
600
|
+
return processV2(event.detail as OrderPlacedEventV2);
|
|
601
|
+
default:
|
|
602
|
+
throw new Error(`Unsupported event version: ${eventVersion}`);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const processV1 = async (event: OrderPlacedEventV1) => {
|
|
607
|
+
// Upgrade to V2 internally
|
|
608
|
+
const v2Event: OrderPlacedEventV2 = {
|
|
609
|
+
...event,
|
|
610
|
+
version: '2.0',
|
|
611
|
+
currency: 'USD', // Default value
|
|
612
|
+
};
|
|
613
|
+
return processV2(v2Event);
|
|
614
|
+
};
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Eventual Consistency
|
|
618
|
+
|
|
619
|
+
**Design for eventual consistency**:
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
// Service A writes to its database
|
|
623
|
+
export const createOrder = async (order: Order) => {
|
|
624
|
+
// Write to Order database
|
|
625
|
+
await orderTable.putItem({ Item: order });
|
|
626
|
+
|
|
627
|
+
// Publish event
|
|
628
|
+
await eventBridge.putEvents({
|
|
629
|
+
Entries: [{
|
|
630
|
+
Source: 'orders',
|
|
631
|
+
DetailType: 'OrderCreated',
|
|
632
|
+
Detail: JSON.stringify({ orderId: order.id }),
|
|
633
|
+
}],
|
|
634
|
+
});
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// Service B eventually updates its database
|
|
638
|
+
export const onOrderCreated = async (event: any) => {
|
|
639
|
+
const { orderId } = event.detail;
|
|
640
|
+
|
|
641
|
+
// Fetch additional data
|
|
642
|
+
const orderDetails = await getOrderDetails(orderId);
|
|
643
|
+
|
|
644
|
+
// Update inventory database (eventual consistency)
|
|
645
|
+
await inventoryTable.updateItem({
|
|
646
|
+
Key: { productId: orderDetails.productId },
|
|
647
|
+
UpdateExpression: 'SET reserved = reserved + :qty',
|
|
648
|
+
ExpressionAttributeValues: { ':qty': orderDetails.quantity },
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Error Handling in EDA
|
|
654
|
+
|
|
655
|
+
**Comprehensive error handling strategy**:
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
658
|
+
// Dead Letter Queue for failed events
|
|
659
|
+
const dlq = new sqs.Queue(this, 'EventDLQ', {
|
|
660
|
+
retentionPeriod: Duration.days(14),
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// EventBridge rule with DLQ
|
|
664
|
+
new events.Rule(this, 'ProcessRule', {
|
|
665
|
+
eventPattern: { /* ... */ },
|
|
666
|
+
targets: [
|
|
667
|
+
new targets.LambdaFunction(processFunction, {
|
|
668
|
+
deadLetterQueue: dlq,
|
|
669
|
+
maxEventAge: Duration.hours(2),
|
|
670
|
+
retryAttempts: 2,
|
|
671
|
+
}),
|
|
672
|
+
],
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// Monitor DLQ
|
|
676
|
+
new cloudwatch.Alarm(this, 'DLQAlarm', {
|
|
677
|
+
metric: dlq.metricApproximateNumberOfMessagesVisible(),
|
|
678
|
+
threshold: 1,
|
|
679
|
+
evaluationPeriods: 1,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// DLQ processor for manual review
|
|
683
|
+
new lambda.EventSourceMapping(this, 'DLQProcessor', {
|
|
684
|
+
target: dlqProcessorFunction,
|
|
685
|
+
eventSourceArn: dlq.queueArn,
|
|
686
|
+
enabled: false, // Enable manually when reviewing
|
|
687
|
+
});
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Message Ordering
|
|
691
|
+
|
|
692
|
+
**When order matters**:
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
// SQS FIFO for strict ordering
|
|
696
|
+
const fifoQueue = new sqs.Queue(this, 'OrderedQueue', {
|
|
697
|
+
fifo: true,
|
|
698
|
+
contentBasedDeduplication: true,
|
|
699
|
+
deduplicationScope: sqs.DeduplicationScope.MESSAGE_GROUP,
|
|
700
|
+
fifoThroughputLimit: sqs.FifoThroughputLimit.PER_MESSAGE_GROUP_ID,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// Publish with message group ID
|
|
704
|
+
await sqs.sendMessage({
|
|
705
|
+
QueueUrl: process.env.QUEUE_URL,
|
|
706
|
+
MessageBody: JSON.stringify(event),
|
|
707
|
+
MessageGroupId: customerId, // All messages for same customer in order
|
|
708
|
+
MessageDeduplicationId: eventId, // Prevent duplicates
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// Kinesis for ordered streams
|
|
712
|
+
const stream = new kinesis.Stream(this, 'Stream', {
|
|
713
|
+
shardCount: 1, // Single shard = strict ordering
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Partition key ensures same partition
|
|
717
|
+
await kinesis.putRecord({
|
|
718
|
+
StreamName: process.env.STREAM_NAME,
|
|
719
|
+
Data: Buffer.from(JSON.stringify(event)),
|
|
720
|
+
PartitionKey: customerId, // Same key = same shard
|
|
721
|
+
});
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Deduplication
|
|
725
|
+
|
|
726
|
+
**Prevent duplicate event processing**:
|
|
727
|
+
|
|
728
|
+
```typescript
|
|
729
|
+
// Content-based deduplication with SQS FIFO
|
|
730
|
+
const queue = new sqs.Queue(this, 'Queue', {
|
|
731
|
+
fifo: true,
|
|
732
|
+
contentBasedDeduplication: true, // Hash of message body
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// Manual deduplication with DynamoDB
|
|
736
|
+
export const handler = async (event: any) => {
|
|
737
|
+
const eventId = event.id || event.messageId;
|
|
738
|
+
|
|
739
|
+
try {
|
|
740
|
+
// Conditional write (fails if exists)
|
|
741
|
+
await dynamodb.putItem({
|
|
742
|
+
TableName: process.env.DEDUP_TABLE,
|
|
743
|
+
Item: {
|
|
744
|
+
eventId,
|
|
745
|
+
processedAt: Date.now(),
|
|
746
|
+
ttl: Math.floor(Date.now() / 1000) + 86400, // 24h TTL
|
|
747
|
+
},
|
|
748
|
+
ConditionExpression: 'attribute_not_exists(eventId)',
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// Event is unique, process it
|
|
752
|
+
await processEvent(event);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
if (error.code === 'ConditionalCheckFailedException') {
|
|
755
|
+
console.log('Duplicate event ignored:', eventId);
|
|
756
|
+
return; // Already processed
|
|
757
|
+
}
|
|
758
|
+
throw error; // Other error
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Backpressure Handling
|
|
764
|
+
|
|
765
|
+
**Prevent overwhelming downstream systems**:
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
// Control Lambda concurrency
|
|
769
|
+
const consumerFunction = new lambda.Function(this, 'Consumer', {
|
|
770
|
+
reservedConcurrentExecutions: 10, // Max 10 concurrent
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// SQS visibility timeout + retry logic
|
|
774
|
+
const queue = new sqs.Queue(this, 'Queue', {
|
|
775
|
+
visibilityTimeout: Duration.seconds(300), // 5 minutes
|
|
776
|
+
receiveMessageWaitTime: Duration.seconds(20), // Long polling
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
new lambda.EventSourceMapping(this, 'Consumer', {
|
|
780
|
+
target: consumerFunction,
|
|
781
|
+
eventSourceArn: queue.queueArn,
|
|
782
|
+
batchSize: 10,
|
|
783
|
+
maxConcurrency: 5, // Process 5 batches concurrently
|
|
784
|
+
reportBatchItemFailures: true,
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// Circuit breaker pattern
|
|
788
|
+
let consecutiveFailures = 0;
|
|
789
|
+
const FAILURE_THRESHOLD = 5;
|
|
790
|
+
|
|
791
|
+
export const handler = async (event: any) => {
|
|
792
|
+
// Check circuit breaker
|
|
793
|
+
if (consecutiveFailures >= FAILURE_THRESHOLD) {
|
|
794
|
+
console.error('Circuit breaker open, skipping processing');
|
|
795
|
+
throw new Error('Circuit breaker open');
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
try {
|
|
799
|
+
await processEvent(event);
|
|
800
|
+
consecutiveFailures = 0; // Reset on success
|
|
801
|
+
} catch (error) {
|
|
802
|
+
consecutiveFailures++;
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
## Advanced Patterns
|
|
809
|
+
|
|
810
|
+
### Pattern: Event Replay
|
|
811
|
+
|
|
812
|
+
Replay events for recovery or testing:
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
// Archive events for replay
|
|
816
|
+
const archive = new events.Archive(this, 'Archive', {
|
|
817
|
+
sourceEventBus: eventBus,
|
|
818
|
+
eventPattern: {
|
|
819
|
+
account: [this.account],
|
|
820
|
+
},
|
|
821
|
+
retention: Duration.days(365),
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// Replay programmatically
|
|
825
|
+
export const replayEvents = async (startTime: Date, endTime: Date) => {
|
|
826
|
+
// Use AWS SDK to start replay
|
|
827
|
+
await eventBridge.startReplay({
|
|
828
|
+
ReplayName: `replay-${Date.now()}`,
|
|
829
|
+
EventSourceArn: archive.archiveArn,
|
|
830
|
+
EventStartTime: startTime,
|
|
831
|
+
EventEndTime: endTime,
|
|
832
|
+
Destination: {
|
|
833
|
+
Arn: eventBus.eventBusArn,
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
### Pattern: Event Time vs Processing Time
|
|
840
|
+
|
|
841
|
+
Handle late-arriving events:
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
// Include event timestamp
|
|
845
|
+
interface Event {
|
|
846
|
+
eventId: string;
|
|
847
|
+
eventTime: string; // When event occurred
|
|
848
|
+
processingTime?: string; // When event processed
|
|
849
|
+
data: any;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Windowed aggregation
|
|
853
|
+
export const aggregateWindow = async (events: Event[]) => {
|
|
854
|
+
// Group by event time window (not processing time)
|
|
855
|
+
const windows = new Map<string, Event[]>();
|
|
856
|
+
|
|
857
|
+
for (const event of events) {
|
|
858
|
+
const window = getWindowForTime(new Date(event.eventTime), Duration.minutes(5));
|
|
859
|
+
const key = window.toISOString();
|
|
860
|
+
|
|
861
|
+
if (!windows.has(key)) {
|
|
862
|
+
windows.set(key, []);
|
|
863
|
+
}
|
|
864
|
+
windows.get(key)!.push(event);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Process each window
|
|
868
|
+
for (const [window, eventsInWindow] of windows) {
|
|
869
|
+
await processWindow(window, eventsInWindow);
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### Pattern: Transactional Outbox
|
|
875
|
+
|
|
876
|
+
Ensure event publishing with database writes:
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
// Single DynamoDB transaction
|
|
880
|
+
export const createOrderWithEvent = async (order: Order) => {
|
|
881
|
+
await dynamodb.transactWriteItems({
|
|
882
|
+
TransactItems: [
|
|
883
|
+
{
|
|
884
|
+
// Write order
|
|
885
|
+
Put: {
|
|
886
|
+
TableName: process.env.ORDERS_TABLE,
|
|
887
|
+
Item: marshall(order),
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
// Write outbox event
|
|
892
|
+
Put: {
|
|
893
|
+
TableName: process.env.OUTBOX_TABLE,
|
|
894
|
+
Item: marshall({
|
|
895
|
+
eventId: uuid(),
|
|
896
|
+
eventType: 'OrderPlaced',
|
|
897
|
+
eventData: order,
|
|
898
|
+
status: 'PENDING',
|
|
899
|
+
createdAt: Date.now(),
|
|
900
|
+
}),
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
});
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
// Separate Lambda processes outbox
|
|
908
|
+
new lambda.EventSourceMapping(this, 'OutboxProcessor', {
|
|
909
|
+
target: outboxFunction,
|
|
910
|
+
eventSourceArn: outboxTable.tableStreamArn,
|
|
911
|
+
startingPosition: lambda.StartingPosition.LATEST,
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
export const processOutbox = async (event: DynamoDBStreamEvent) => {
|
|
915
|
+
for (const record of event.Records) {
|
|
916
|
+
if (record.eventName !== 'INSERT') continue;
|
|
917
|
+
|
|
918
|
+
const outboxEvent = unmarshall(record.dynamodb?.NewImage);
|
|
919
|
+
|
|
920
|
+
// Publish to EventBridge
|
|
921
|
+
await eventBridge.putEvents({
|
|
922
|
+
Entries: [{
|
|
923
|
+
Source: 'orders',
|
|
924
|
+
DetailType: outboxEvent.eventType,
|
|
925
|
+
Detail: JSON.stringify(outboxEvent.eventData),
|
|
926
|
+
}],
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// Mark as processed
|
|
930
|
+
await dynamodb.updateItem({
|
|
931
|
+
TableName: process.env.OUTBOX_TABLE,
|
|
932
|
+
Key: { eventId: outboxEvent.eventId },
|
|
933
|
+
UpdateExpression: 'SET #status = :status',
|
|
934
|
+
ExpressionAttributeNames: { '#status': 'status' },
|
|
935
|
+
ExpressionAttributeValues: { ':status': 'PUBLISHED' },
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
## Testing Event-Driven Systems
|
|
942
|
+
|
|
943
|
+
### Pattern: Event Replay for Testing
|
|
944
|
+
|
|
945
|
+
```typescript
|
|
946
|
+
// Publish test events
|
|
947
|
+
export const publishTestEvents = async () => {
|
|
948
|
+
const testEvents = [
|
|
949
|
+
{ source: 'orders', detailType: 'OrderPlaced', detail: { orderId: '1' } },
|
|
950
|
+
{ source: 'orders', detailType: 'OrderPlaced', detail: { orderId: '2' } },
|
|
951
|
+
];
|
|
952
|
+
|
|
953
|
+
for (const event of testEvents) {
|
|
954
|
+
await eventBridge.putEvents({ Entries: [event] });
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// Monitor processing
|
|
959
|
+
export const verifyProcessing = async () => {
|
|
960
|
+
// Check downstream databases
|
|
961
|
+
const order1 = await orderTable.getItem({ Key: { orderId: '1' } });
|
|
962
|
+
const order2 = await orderTable.getItem({ Key: { orderId: '2' } });
|
|
963
|
+
|
|
964
|
+
expect(order1.Item).toBeDefined();
|
|
965
|
+
expect(order2.Item).toBeDefined();
|
|
966
|
+
};
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
### Pattern: Event Mocking
|
|
970
|
+
|
|
971
|
+
```typescript
|
|
972
|
+
// Mock EventBridge in tests
|
|
973
|
+
const mockEventBridge = {
|
|
974
|
+
putEvents: jest.fn().mockResolvedValue({}),
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// Test event publishing
|
|
978
|
+
test('publishes event on order creation', async () => {
|
|
979
|
+
await createOrder(mockEventBridge, order);
|
|
980
|
+
|
|
981
|
+
expect(mockEventBridge.putEvents).toHaveBeenCalledWith({
|
|
982
|
+
Entries: [
|
|
983
|
+
expect.objectContaining({
|
|
984
|
+
Source: 'orders',
|
|
985
|
+
DetailType: 'OrderPlaced',
|
|
986
|
+
}),
|
|
987
|
+
],
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
## Summary
|
|
993
|
+
|
|
994
|
+
- **Loose Coupling**: Services communicate via events, not direct calls
|
|
995
|
+
- **Async Processing**: Use queues and event buses for asynchronous workflows
|
|
996
|
+
- **Idempotency**: Always handle duplicate events gracefully
|
|
997
|
+
- **Dead Letter Queues**: Configure DLQs for error handling
|
|
998
|
+
- **Event Contracts**: Define clear schemas for events
|
|
999
|
+
- **Observability**: Enable tracing and monitoring across services
|
|
1000
|
+
- **Eventual Consistency**: Design for it, don't fight it
|
|
1001
|
+
- **Saga Patterns**: Use for distributed transactions
|
|
1002
|
+
- **Event Sourcing**: Store events as source of truth when needed
|