specweave 0.18.1 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +229 -1817
- package/README.md +68 -0
- package/bin/specweave.js +62 -6
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +3 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts +21 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts.map +1 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.js +162 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +65 -6
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts +112 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.js +301 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.js.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +3 -3
- package/dist/plugins/specweave-github/lib/duplicate-detector.js +3 -3
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +7 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +42 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +14 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +51 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +87 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +412 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +64 -13
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/progress-comment-builder.d.ts +78 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js +237 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts +97 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js +301 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts +83 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +386 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +8 -6
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +78 -117
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -1
- package/dist/src/cli/commands/import-docs.js +4 -4
- package/dist/src/cli/commands/import-docs.js.map +1 -1
- package/dist/src/cli/commands/init-multiproject.d.ts.map +1 -1
- package/dist/src/cli/commands/init-multiproject.js +17 -18
- package/dist/src/cli/commands/init-multiproject.js.map +1 -1
- package/dist/src/cli/commands/migrate-to-multiproject.d.ts.map +1 -1
- package/dist/src/cli/commands/migrate-to-multiproject.js +8 -4
- package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
- package/dist/src/cli/commands/switch-project.d.ts.map +1 -1
- package/dist/src/cli/commands/switch-project.js +9 -26
- package/dist/src/cli/commands/switch-project.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-content.js +3 -0
- package/dist/src/cli/commands/sync-spec-content.js.map +1 -1
- package/dist/src/core/deduplication/command-deduplicator.d.ts +166 -0
- package/dist/src/core/deduplication/command-deduplicator.d.ts.map +1 -0
- package/dist/src/core/deduplication/command-deduplicator.js +254 -0
- package/dist/src/core/deduplication/command-deduplicator.js.map +1 -0
- package/dist/src/core/increment/active-increment-manager.d.ts +42 -15
- package/dist/src/core/increment/active-increment-manager.d.ts.map +1 -1
- package/dist/src/core/increment/active-increment-manager.js +113 -46
- package/dist/src/core/increment/active-increment-manager.js.map +1 -1
- package/dist/src/core/increment/conflict-resolver.d.ts +40 -0
- package/dist/src/core/increment/conflict-resolver.d.ts.map +1 -0
- package/dist/src/core/increment/conflict-resolver.js +219 -0
- package/dist/src/core/increment/conflict-resolver.js.map +1 -0
- package/dist/src/core/increment/discipline-checker.d.ts.map +1 -1
- package/dist/src/core/increment/discipline-checker.js +7 -1
- package/dist/src/core/increment/discipline-checker.js.map +1 -1
- package/dist/src/core/increment/duplicate-detector.d.ts +52 -0
- package/dist/src/core/increment/duplicate-detector.d.ts.map +1 -0
- package/dist/src/core/increment/duplicate-detector.js +276 -0
- package/dist/src/core/increment/duplicate-detector.js.map +1 -0
- package/dist/src/core/increment/increment-archiver.d.ts +90 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -0
- package/dist/src/core/increment/increment-archiver.js +368 -0
- package/dist/src/core/increment/increment-archiver.js.map +1 -0
- package/dist/src/core/increment/increment-reopener.d.ts +165 -0
- package/dist/src/core/increment/increment-reopener.d.ts.map +1 -0
- package/dist/src/core/increment/increment-reopener.js +390 -0
- package/dist/src/core/increment/increment-reopener.js.map +1 -0
- package/dist/src/core/increment/metadata-manager.d.ts +26 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +143 -5
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/recent-work-scanner.d.ts +121 -0
- package/dist/src/core/increment/recent-work-scanner.d.ts.map +1 -0
- package/dist/src/core/increment/recent-work-scanner.js +303 -0
- package/dist/src/core/increment/recent-work-scanner.js.map +1 -0
- package/dist/src/core/increment/types.d.ts +1 -0
- package/dist/src/core/increment/types.d.ts.map +1 -1
- package/dist/src/core/increment-utils.d.ts +112 -0
- package/dist/src/core/increment-utils.d.ts.map +1 -0
- package/dist/src/core/increment-utils.js +210 -0
- package/dist/src/core/increment-utils.js.map +1 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.d.ts +65 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.js +175 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.js.map +1 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts +130 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -0
- package/dist/src/core/living-docs/feature-archiver.js +549 -0
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -0
- package/dist/src/core/living-docs/feature-id-manager.d.ts +81 -0
- package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -0
- package/dist/src/core/living-docs/feature-id-manager.js +339 -0
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts +144 -83
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.js +488 -270
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/index.d.ts +6 -0
- package/dist/src/core/living-docs/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/index.js +6 -0
- package/dist/src/core/living-docs/index.js.map +1 -1
- package/dist/src/core/living-docs/project-detector.d.ts +6 -0
- package/dist/src/core/living-docs/project-detector.d.ts.map +1 -1
- package/dist/src/core/living-docs/project-detector.js +35 -1
- package/dist/src/core/living-docs/project-detector.js.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.d.ts +100 -26
- package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.js +1275 -258
- package/dist/src/core/living-docs/spec-distributor.js.map +1 -1
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts +109 -0
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/task-project-specific-generator.js +221 -0
- package/dist/src/core/living-docs/task-project-specific-generator.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +143 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/project-manager.d.ts +2 -17
- package/dist/src/core/project-manager.d.ts.map +1 -1
- package/dist/src/core/project-manager.js +68 -48
- package/dist/src/core/project-manager.js.map +1 -1
- package/dist/src/core/spec-content-sync.d.ts +1 -1
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -1
- package/dist/src/core/sync/enhanced-content-builder.js +2 -1
- package/dist/src/core/sync/enhanced-content-builder.js.map +1 -1
- package/dist/src/core/sync/performance-optimizer.d.ts +153 -0
- package/dist/src/core/sync/performance-optimizer.d.ts.map +1 -0
- package/dist/src/core/sync/performance-optimizer.js +220 -0
- package/dist/src/core/sync/performance-optimizer.js.map +1 -0
- package/dist/src/core/sync/retry-handler.d.ts +98 -0
- package/dist/src/core/sync/retry-handler.d.ts.map +1 -0
- package/dist/src/core/sync/retry-handler.js +196 -0
- package/dist/src/core/sync/retry-handler.js.map +1 -0
- package/dist/src/core/types/config.d.ts +94 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +16 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +6 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js +10 -1
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/dist/src/integrations/jira/jira-incremental-mapper.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-incremental-mapper.js +4 -8
- package/dist/src/integrations/jira/jira-incremental-mapper.js.map +1 -1
- package/dist/src/integrations/jira/jira-mapper.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-mapper.js +4 -8
- package/dist/src/integrations/jira/jira-mapper.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/COMMANDS.md +13 -4
- package/plugins/specweave/commands/specweave-abandon.md +22 -20
- package/plugins/specweave/commands/specweave-archive-features.md +121 -0
- package/plugins/specweave/commands/specweave-archive-increments.md +82 -0
- package/plugins/specweave/commands/specweave-archive.md +363 -0
- package/plugins/specweave/commands/specweave-backlog.md +211 -0
- package/plugins/specweave/commands/specweave-fix-duplicates.md +517 -0
- package/plugins/specweave/commands/specweave-increment.md +4 -3
- package/plugins/specweave/commands/specweave-progress.md +176 -27
- package/plugins/specweave/commands/specweave-reopen.md +391 -0
- package/plugins/specweave/commands/specweave-restore-feature.md +90 -0
- package/plugins/specweave/commands/specweave-restore.md +309 -0
- package/plugins/specweave/commands/specweave-resume.md +51 -23
- package/plugins/specweave/commands/specweave-status.md +41 -7
- package/plugins/specweave/commands/specweave-sync-specs.md +425 -0
- package/plugins/specweave/hooks/hooks.json +4 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/post-task-completion.sh +39 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +83 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/lib/hooks/sync-living-docs.js +2 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +4 -0
- package/plugins/specweave/lib/hooks/update-ac-status.js +102 -0
- package/plugins/specweave/lib/hooks/update-ac-status.ts +192 -0
- package/plugins/specweave/skills/archive-increments/SKILL.md +198 -0
- package/plugins/specweave/skills/increment-planner/scripts/feature-utils.js +14 -0
- package/plugins/specweave/skills/smart-reopen-detector/SKILL.md +244 -0
- package/plugins/specweave-ado/lib/ado-spec-content-sync.js +49 -5
- package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +72 -6
- package/plugins/specweave-confluent/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-confluent/README.md +375 -0
- package/plugins/specweave-confluent/agents/confluent-architect/AGENT.md +306 -0
- package/plugins/specweave-confluent/skills/confluent-kafka-connect/SKILL.md +453 -0
- package/plugins/specweave-confluent/skills/confluent-ksqldb/SKILL.md +470 -0
- package/plugins/specweave-confluent/skills/confluent-schema-registry/SKILL.md +316 -0
- package/plugins/specweave-github/agents/github-task-splitter/AGENT.md +2 -2
- package/plugins/specweave-github/agents/user-story-updater/AGENT.md +148 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +1 -1
- package/plugins/specweave-github/commands/specweave-github-update-user-story.md +156 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh +10 -9
- package/plugins/specweave-github/lib/completion-calculator.js +262 -0
- package/plugins/specweave-github/lib/completion-calculator.ts +434 -0
- package/plugins/specweave-github/lib/duplicate-detector.js +3 -3
- package/plugins/specweave-github/lib/duplicate-detector.ts +4 -4
- package/plugins/specweave-github/lib/epic-content-builder.js +38 -0
- package/plugins/specweave-github/lib/epic-content-builder.ts +59 -0
- package/plugins/specweave-github/lib/github-client-v2.js +49 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +59 -0
- package/plugins/specweave-github/lib/github-epic-sync.ts +1 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +381 -0
- package/plugins/specweave-github/lib/github-feature-sync.ts +568 -0
- package/plugins/specweave-github/lib/github-spec-content-sync.js +40 -10
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +82 -14
- package/plugins/specweave-github/lib/progress-comment-builder.js +229 -0
- package/plugins/specweave-github/lib/progress-comment-builder.ts +324 -0
- package/plugins/specweave-github/lib/user-story-content-builder.js +299 -0
- package/plugins/specweave-github/lib/user-story-content-builder.ts +413 -0
- package/plugins/specweave-github/lib/user-story-issue-builder.js +344 -0
- package/plugins/specweave-github/lib/user-story-issue-builder.ts +543 -0
- package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +189 -0
- package/plugins/{specweave-ado/lib/enhanced-ado-sync.js → specweave-jira/lib/enhanced-jira-sync.js} +25 -61
- package/plugins/specweave-jira/lib/{enhanced-jira-sync.ts.disabled → enhanced-jira-sync.ts} +26 -52
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +26 -0
- package/plugins/specweave-kafka/IMPLEMENTATION-COMPLETE.md +483 -0
- package/plugins/specweave-kafka/README.md +242 -0
- package/plugins/specweave-kafka/agents/kafka-architect/AGENT.md +235 -0
- package/plugins/specweave-kafka/agents/kafka-devops/AGENT.md +209 -0
- package/plugins/specweave-kafka/agents/kafka-observability/AGENT.md +266 -0
- package/plugins/specweave-kafka/commands/deploy.md +99 -0
- package/plugins/specweave-kafka/commands/dev-env.md +176 -0
- package/plugins/specweave-kafka/commands/mcp-configure.md +101 -0
- package/plugins/specweave-kafka/commands/monitor-setup.md +96 -0
- package/plugins/specweave-kafka/docker/kafka-local/docker-compose.yml +187 -0
- package/plugins/specweave-kafka/docker/redpanda/docker-compose.yml +199 -0
- package/plugins/specweave-kafka/docker/templates/consumer-nodejs.js +225 -0
- package/plugins/specweave-kafka/docker/templates/consumer-python.py +220 -0
- package/plugins/specweave-kafka/docker/templates/producer-nodejs.js +168 -0
- package/plugins/specweave-kafka/docker/templates/producer-python.py +167 -0
- package/plugins/specweave-kafka/lib/adapters/apache-kafka-adapter.js +438 -0
- package/plugins/specweave-kafka/lib/adapters/apache-kafka-adapter.ts +541 -0
- package/plugins/specweave-kafka/lib/adapters/platform-adapter.js +47 -0
- package/plugins/specweave-kafka/lib/adapters/platform-adapter.ts +343 -0
- package/plugins/specweave-kafka/lib/cli/kcat-wrapper.js +258 -0
- package/plugins/specweave-kafka/lib/cli/kcat-wrapper.ts +298 -0
- package/plugins/specweave-kafka/lib/cli/types.js +10 -0
- package/plugins/specweave-kafka/lib/cli/types.ts +92 -0
- package/plugins/specweave-kafka/lib/connectors/connector-catalog.js +305 -0
- package/plugins/specweave-kafka/lib/connectors/connector-catalog.ts +528 -0
- package/plugins/specweave-kafka/lib/documentation/diagram-generator.js +114 -0
- package/plugins/specweave-kafka/lib/documentation/diagram-generator.ts +195 -0
- package/plugins/specweave-kafka/lib/documentation/exporter.js +210 -0
- package/plugins/specweave-kafka/lib/documentation/exporter.ts +338 -0
- package/plugins/specweave-kafka/lib/documentation/schema-catalog-generator.js +60 -0
- package/plugins/specweave-kafka/lib/documentation/schema-catalog-generator.ts +130 -0
- package/plugins/specweave-kafka/lib/documentation/topology-generator.js +143 -0
- package/plugins/specweave-kafka/lib/documentation/topology-generator.ts +290 -0
- package/plugins/specweave-kafka/lib/mcp/detector.js +298 -0
- package/plugins/specweave-kafka/lib/mcp/detector.ts +352 -0
- package/plugins/specweave-kafka/lib/mcp/types.js +21 -0
- package/plugins/specweave-kafka/lib/mcp/types.ts +77 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-config-manager.js +193 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-config-manager.ts +362 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-switcher.js +188 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-switcher.ts +359 -0
- package/plugins/specweave-kafka/lib/multi-cluster/health-aggregator.js +195 -0
- package/plugins/specweave-kafka/lib/multi-cluster/health-aggregator.ts +380 -0
- package/plugins/specweave-kafka/lib/observability/opentelemetry-kafka.js +209 -0
- package/plugins/specweave-kafka/lib/observability/opentelemetry-kafka.ts +358 -0
- package/plugins/specweave-kafka/lib/patterns/advanced-ksqldb-patterns.js +354 -0
- package/plugins/specweave-kafka/lib/patterns/advanced-ksqldb-patterns.ts +563 -0
- package/plugins/specweave-kafka/lib/patterns/circuit-breaker-resilience.js +259 -0
- package/plugins/specweave-kafka/lib/patterns/circuit-breaker-resilience.ts +516 -0
- package/plugins/specweave-kafka/lib/patterns/dead-letter-queue.js +233 -0
- package/plugins/specweave-kafka/lib/patterns/dead-letter-queue.ts +423 -0
- package/plugins/specweave-kafka/lib/patterns/exactly-once-semantics.js +266 -0
- package/plugins/specweave-kafka/lib/patterns/exactly-once-semantics.ts +445 -0
- package/plugins/specweave-kafka/lib/patterns/flink-kafka-integration.js +312 -0
- package/plugins/specweave-kafka/lib/patterns/flink-kafka-integration.ts +561 -0
- package/plugins/specweave-kafka/lib/patterns/multi-dc-replication.js +289 -0
- package/plugins/specweave-kafka/lib/patterns/multi-dc-replication.ts +607 -0
- package/plugins/specweave-kafka/lib/patterns/rate-limiting-backpressure.js +264 -0
- package/plugins/specweave-kafka/lib/patterns/rate-limiting-backpressure.ts +498 -0
- package/plugins/specweave-kafka/lib/patterns/stream-processing-optimization.js +263 -0
- package/plugins/specweave-kafka/lib/patterns/stream-processing-optimization.ts +549 -0
- package/plugins/specweave-kafka/lib/patterns/tiered-storage-compaction.js +205 -0
- package/plugins/specweave-kafka/lib/patterns/tiered-storage-compaction.ts +399 -0
- package/plugins/specweave-kafka/lib/performance/performance-optimizer.js +249 -0
- package/plugins/specweave-kafka/lib/performance/performance-optimizer.ts +427 -0
- package/plugins/specweave-kafka/lib/security/kafka-security.js +252 -0
- package/plugins/specweave-kafka/lib/security/kafka-security.ts +494 -0
- package/plugins/specweave-kafka/lib/utils/capacity-planner.js +203 -0
- package/plugins/specweave-kafka/lib/utils/capacity-planner.ts +469 -0
- package/plugins/specweave-kafka/lib/utils/config-validator.js +419 -0
- package/plugins/specweave-kafka/lib/utils/config-validator.ts +564 -0
- package/plugins/specweave-kafka/lib/utils/partitioning.js +329 -0
- package/plugins/specweave-kafka/lib/utils/partitioning.ts +473 -0
- package/plugins/specweave-kafka/lib/utils/sizing.js +221 -0
- package/plugins/specweave-kafka/lib/utils/sizing.ts +374 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-broker-metrics.json +628 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-cluster-overview.json +564 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-consumer-lag.json +509 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-jvm-metrics.json +674 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-topic-metrics.json +578 -0
- package/plugins/specweave-kafka/monitoring/grafana/provisioning/dashboards/kafka.yml +17 -0
- package/plugins/specweave-kafka/monitoring/grafana/provisioning/datasources/prometheus.yml +17 -0
- package/plugins/specweave-kafka/monitoring/prometheus/kafka-alerts.yml +415 -0
- package/plugins/specweave-kafka/monitoring/prometheus/kafka-jmx-exporter.yml +256 -0
- package/plugins/specweave-kafka/package.json +41 -0
- package/plugins/specweave-kafka/skills/kafka-architecture/SKILL.md +647 -0
- package/plugins/specweave-kafka/skills/kafka-cli-tools/SKILL.md +433 -0
- package/plugins/specweave-kafka/skills/kafka-iac-deployment/SKILL.md +449 -0
- package/plugins/specweave-kafka/skills/kafka-kubernetes/SKILL.md +667 -0
- package/plugins/specweave-kafka/skills/kafka-mcp-integration/SKILL.md +273 -0
- package/plugins/specweave-kafka/skills/kafka-observability/SKILL.md +576 -0
- package/plugins/specweave-kafka/templates/config/broker-production.properties +254 -0
- package/plugins/specweave-kafka/templates/config/consumer-low-latency.properties +112 -0
- package/plugins/specweave-kafka/templates/config/producer-high-throughput.properties +120 -0
- package/plugins/specweave-kafka/templates/migration/mirrormaker2-config.properties +234 -0
- package/plugins/specweave-kafka/templates/monitoring/grafana/multi-cluster-dashboard.json +686 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/main.tf +347 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/outputs.tf +107 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/templates/kafka-broker-init.sh.tpl +216 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/variables.tf +156 -0
- package/plugins/specweave-kafka/terraform/aws-msk/main.tf +362 -0
- package/plugins/specweave-kafka/terraform/aws-msk/outputs.tf +93 -0
- package/plugins/specweave-kafka/terraform/aws-msk/templates/server.properties.tpl +32 -0
- package/plugins/specweave-kafka/terraform/aws-msk/variables.tf +235 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/main.tf +281 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/outputs.tf +118 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/variables.tf +148 -0
- package/plugins/specweave-kafka/tsconfig.json +21 -0
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-kafka-streams/README.md +310 -0
- package/plugins/specweave-kafka-streams/skills/kafka-streams-topology/SKILL.md +539 -0
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-n8n/README.md +354 -0
- package/plugins/specweave-n8n/skills/n8n-kafka-workflows/SKILL.md +504 -0
- package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
- package/src/templates/AGENTS.md.template +601 -7
- package/src/templates/CLAUDE.md.template +188 -88
- package/dist/locales/de/.gitkeep +0 -0
- package/dist/locales/de/cli.json +0 -108
- package/dist/locales/en/cli.json +0 -287
- package/dist/locales/en/errors.json +0 -7
- package/dist/locales/en/templates.json +0 -6
- package/dist/locales/es/.gitkeep +0 -0
- package/dist/locales/es/cli.json +0 -41
- package/dist/locales/fr/.gitkeep +0 -0
- package/dist/locales/fr/cli.json +0 -108
- package/dist/locales/ja/.gitkeep +0 -0
- package/dist/locales/ja/cli.json +0 -108
- package/dist/locales/ko/.gitkeep +0 -0
- package/dist/locales/ko/cli.json +0 -108
- package/dist/locales/pt/.gitkeep +0 -0
- package/dist/locales/pt/cli.json +0 -108
- package/dist/locales/ru/.gitkeep +0 -0
- package/dist/locales/ru/cli.json +0 -269
- package/dist/locales/zh/.gitkeep +0 -0
- package/dist/locales/zh/cli.json +0 -108
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +0 -25
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +0 -1
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +0 -191
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +0 -1
- package/dist/spec-parser.js +0 -629
- package/dist/src/core/sync/spec-content-sync.d.ts +0 -88
- package/dist/src/core/sync/spec-content-sync.d.ts.map +0 -1
- package/dist/src/core/sync/spec-content-sync.js +0 -5
- package/dist/src/core/sync/spec-content-sync.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +0 -255
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +0 -248
- package/plugins/specweave-github/commands/specweave-github-sync-from.md +0 -147
- package/plugins/specweave-github/commands/specweave-github-sync-spec.md +0 -208
- package/plugins/specweave-github/commands/specweave-github-sync-tasks.md +0 -530
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +0 -267
- package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +0 -240
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { CompressionTypes } from "kafkajs";
|
|
2
|
+
class ExactlyOnceProducer {
|
|
3
|
+
constructor(kafka, config) {
|
|
4
|
+
this.inTransaction = false;
|
|
5
|
+
this.config = {
|
|
6
|
+
transactionalId: config.transactionalId,
|
|
7
|
+
transactionTimeout: config.transactionTimeout || 6e4,
|
|
8
|
+
idempotent: config.idempotent !== false,
|
|
9
|
+
// Default true
|
|
10
|
+
maxInFlightRequests: config.maxInFlightRequests || 5,
|
|
11
|
+
compressionType: config.compressionType || CompressionTypes.GZIP
|
|
12
|
+
};
|
|
13
|
+
this.producer = kafka.producer({
|
|
14
|
+
transactionalId: this.config.transactionalId,
|
|
15
|
+
idempotent: this.config.idempotent,
|
|
16
|
+
maxInFlightRequests: this.config.maxInFlightRequests
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Connect producer
|
|
21
|
+
*/
|
|
22
|
+
async connect() {
|
|
23
|
+
await this.producer.connect();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Disconnect producer
|
|
27
|
+
*/
|
|
28
|
+
async disconnect() {
|
|
29
|
+
if (this.inTransaction) {
|
|
30
|
+
await this.abortTransaction();
|
|
31
|
+
}
|
|
32
|
+
await this.producer.disconnect();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Begin transaction
|
|
36
|
+
*/
|
|
37
|
+
async beginTransaction() {
|
|
38
|
+
if (this.inTransaction) {
|
|
39
|
+
throw new Error("Transaction already in progress");
|
|
40
|
+
}
|
|
41
|
+
await this.producer.transaction();
|
|
42
|
+
this.inTransaction = true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Send message within transaction
|
|
46
|
+
*/
|
|
47
|
+
async send(topic, messages) {
|
|
48
|
+
if (!this.inTransaction) {
|
|
49
|
+
throw new Error("No active transaction. Call beginTransaction() first.");
|
|
50
|
+
}
|
|
51
|
+
return this.producer.send({
|
|
52
|
+
topic,
|
|
53
|
+
messages,
|
|
54
|
+
compression: this.config.compressionType
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Commit transaction
|
|
59
|
+
*/
|
|
60
|
+
async commitTransaction() {
|
|
61
|
+
if (!this.inTransaction) {
|
|
62
|
+
throw new Error("No active transaction to commit");
|
|
63
|
+
}
|
|
64
|
+
await this.producer.commit();
|
|
65
|
+
this.inTransaction = false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Abort transaction
|
|
69
|
+
*/
|
|
70
|
+
async abortTransaction() {
|
|
71
|
+
if (!this.inTransaction) {
|
|
72
|
+
throw new Error("No active transaction to abort");
|
|
73
|
+
}
|
|
74
|
+
await this.producer.abort();
|
|
75
|
+
this.inTransaction = false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Execute operation within transaction (auto-commit/abort)
|
|
79
|
+
*/
|
|
80
|
+
async executeTransaction(operation) {
|
|
81
|
+
await this.beginTransaction();
|
|
82
|
+
try {
|
|
83
|
+
const result = await operation(this.producer);
|
|
84
|
+
await this.commitTransaction();
|
|
85
|
+
return result;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
await this.abortTransaction();
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
class ExactlyOnceConsumer {
|
|
93
|
+
constructor(kafka, config) {
|
|
94
|
+
this.config = {
|
|
95
|
+
groupId: config.groupId,
|
|
96
|
+
isolationLevel: config.isolationLevel || "read_committed",
|
|
97
|
+
enableAutoCommit: false
|
|
98
|
+
// Must be false for EOS
|
|
99
|
+
};
|
|
100
|
+
this.consumer = kafka.consumer({
|
|
101
|
+
groupId: this.config.groupId,
|
|
102
|
+
// @ts-ignore - isolationLevel typing issue in kafkajs
|
|
103
|
+
isolationLevel: this.config.isolationLevel
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Connect consumer
|
|
108
|
+
*/
|
|
109
|
+
async connect() {
|
|
110
|
+
await this.consumer.connect();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Disconnect consumer
|
|
114
|
+
*/
|
|
115
|
+
async disconnect() {
|
|
116
|
+
await this.consumer.disconnect();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Subscribe to topics
|
|
120
|
+
*/
|
|
121
|
+
async subscribe(topics) {
|
|
122
|
+
await this.consumer.subscribe({
|
|
123
|
+
topics,
|
|
124
|
+
fromBeginning: false
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Run consumer with exactly-once processing
|
|
129
|
+
*
|
|
130
|
+
* Automatically commits offsets only after successful processing.
|
|
131
|
+
*/
|
|
132
|
+
async run(handler) {
|
|
133
|
+
await this.consumer.run({
|
|
134
|
+
autoCommit: false,
|
|
135
|
+
// Manual offset management for EOS
|
|
136
|
+
eachMessage: async (payload) => {
|
|
137
|
+
try {
|
|
138
|
+
await handler(payload);
|
|
139
|
+
await this.consumer.commitOffsets([
|
|
140
|
+
{
|
|
141
|
+
topic: payload.topic,
|
|
142
|
+
partition: payload.partition,
|
|
143
|
+
offset: (parseInt(payload.message.offset) + 1).toString()
|
|
144
|
+
}
|
|
145
|
+
]);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("Message processing failed:", error);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
class ExactlyOnceProcessor {
|
|
155
|
+
constructor(kafka, consumerGroupId, transactionalId) {
|
|
156
|
+
this.transactionalId = transactionalId;
|
|
157
|
+
this.consumer = kafka.consumer({
|
|
158
|
+
groupId: consumerGroupId,
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
isolationLevel: "read_committed"
|
|
161
|
+
});
|
|
162
|
+
this.producer = kafka.producer({
|
|
163
|
+
transactionalId,
|
|
164
|
+
idempotent: true,
|
|
165
|
+
maxInFlightRequests: 5
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Connect consumer and producer
|
|
170
|
+
*/
|
|
171
|
+
async connect() {
|
|
172
|
+
await this.consumer.connect();
|
|
173
|
+
await this.producer.connect();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Disconnect consumer and producer
|
|
177
|
+
*/
|
|
178
|
+
async disconnect() {
|
|
179
|
+
await this.consumer.disconnect();
|
|
180
|
+
await this.producer.disconnect();
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Subscribe to input topics
|
|
184
|
+
*/
|
|
185
|
+
async subscribe(topics) {
|
|
186
|
+
await this.consumer.subscribe({ topics, fromBeginning: false });
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Run exactly-once consume-process-produce loop
|
|
190
|
+
*
|
|
191
|
+
* Pattern: Read → Transform → Write → Commit (all atomic)
|
|
192
|
+
*/
|
|
193
|
+
async run(handler) {
|
|
194
|
+
await this.consumer.run({
|
|
195
|
+
autoCommit: false,
|
|
196
|
+
eachMessage: async (payload) => {
|
|
197
|
+
const transaction = await this.producer.transaction();
|
|
198
|
+
try {
|
|
199
|
+
const output = await handler(payload);
|
|
200
|
+
if (output) {
|
|
201
|
+
await transaction.send(output);
|
|
202
|
+
}
|
|
203
|
+
await transaction.sendOffsets({
|
|
204
|
+
consumerGroupId: this.consumer.groupId,
|
|
205
|
+
topics: [
|
|
206
|
+
{
|
|
207
|
+
topic: payload.topic,
|
|
208
|
+
partitions: [
|
|
209
|
+
{
|
|
210
|
+
partition: payload.partition,
|
|
211
|
+
offset: (parseInt(payload.message.offset) + 1).toString()
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
});
|
|
217
|
+
await transaction.commit();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
await transaction.abort();
|
|
220
|
+
console.error("Transaction aborted:", error);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
class IdempotentProducer {
|
|
228
|
+
constructor(kafka) {
|
|
229
|
+
this.producer = kafka.producer({
|
|
230
|
+
idempotent: true,
|
|
231
|
+
// Enable idempotence
|
|
232
|
+
maxInFlightRequests: 5,
|
|
233
|
+
// Max 5 for idempotence
|
|
234
|
+
acks: -1
|
|
235
|
+
// Wait for all replicas
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
async connect() {
|
|
239
|
+
await this.producer.connect();
|
|
240
|
+
}
|
|
241
|
+
async disconnect() {
|
|
242
|
+
await this.producer.disconnect();
|
|
243
|
+
}
|
|
244
|
+
async send(topic, messages) {
|
|
245
|
+
return this.producer.send({
|
|
246
|
+
topic,
|
|
247
|
+
messages,
|
|
248
|
+
acks: -1,
|
|
249
|
+
// Wait for all in-sync replicas
|
|
250
|
+
compression: CompressionTypes.GZIP
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
var exactly_once_semantics_default = {
|
|
255
|
+
ExactlyOnceProducer,
|
|
256
|
+
ExactlyOnceConsumer,
|
|
257
|
+
ExactlyOnceProcessor,
|
|
258
|
+
IdempotentProducer
|
|
259
|
+
};
|
|
260
|
+
export {
|
|
261
|
+
ExactlyOnceConsumer,
|
|
262
|
+
ExactlyOnceProcessor,
|
|
263
|
+
ExactlyOnceProducer,
|
|
264
|
+
IdempotentProducer,
|
|
265
|
+
exactly_once_semantics_default as default
|
|
266
|
+
};
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exactly-Once Semantics (EOS) Implementation for Kafka
|
|
3
|
+
*
|
|
4
|
+
* Provides patterns and utilities for achieving exactly-once delivery
|
|
5
|
+
* guarantees in Kafka producers and consumers.
|
|
6
|
+
*
|
|
7
|
+
* @module exactly-once-semantics
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Producer, Consumer, Kafka, EachMessagePayload, CompressionTypes } from 'kafkajs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* EOS Configuration for Producer
|
|
14
|
+
*/
|
|
15
|
+
export interface EOSProducerConfig {
|
|
16
|
+
/** Transactional ID (must be unique per producer instance) */
|
|
17
|
+
transactionalId: string;
|
|
18
|
+
/** Max time to wait for transaction commit (default: 60000ms) */
|
|
19
|
+
transactionTimeout?: number;
|
|
20
|
+
/** Enable idempotent producer (required for EOS) */
|
|
21
|
+
idempotent?: boolean;
|
|
22
|
+
/** Max in-flight requests (default: 5 for EOS) */
|
|
23
|
+
maxInFlightRequests?: number;
|
|
24
|
+
/** Compression type (default: gzip) */
|
|
25
|
+
compressionType?: CompressionTypes;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* EOS Configuration for Consumer
|
|
30
|
+
*/
|
|
31
|
+
export interface EOSConsumerConfig {
|
|
32
|
+
/** Consumer group ID */
|
|
33
|
+
groupId: string;
|
|
34
|
+
/** Isolation level (read_committed for EOS) */
|
|
35
|
+
isolationLevel?: 'read_committed' | 'read_uncommitted';
|
|
36
|
+
/** Enable auto-commit (must be false for EOS) */
|
|
37
|
+
enableAutoCommit?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Exactly-Once Producer
|
|
42
|
+
*
|
|
43
|
+
* Implements transactional producer with exactly-once delivery guarantees.
|
|
44
|
+
*/
|
|
45
|
+
export class ExactlyOnceProducer {
|
|
46
|
+
private producer: Producer;
|
|
47
|
+
private config: Required<EOSProducerConfig>;
|
|
48
|
+
private inTransaction = false;
|
|
49
|
+
|
|
50
|
+
constructor(kafka: Kafka, config: EOSProducerConfig) {
|
|
51
|
+
this.config = {
|
|
52
|
+
transactionalId: config.transactionalId,
|
|
53
|
+
transactionTimeout: config.transactionTimeout || 60000,
|
|
54
|
+
idempotent: config.idempotent !== false, // Default true
|
|
55
|
+
maxInFlightRequests: config.maxInFlightRequests || 5,
|
|
56
|
+
compressionType: config.compressionType || CompressionTypes.GZIP,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
this.producer = kafka.producer({
|
|
60
|
+
transactionalId: this.config.transactionalId,
|
|
61
|
+
idempotent: this.config.idempotent,
|
|
62
|
+
maxInFlightRequests: this.config.maxInFlightRequests,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Connect producer
|
|
68
|
+
*/
|
|
69
|
+
async connect(): Promise<void> {
|
|
70
|
+
await this.producer.connect();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Disconnect producer
|
|
75
|
+
*/
|
|
76
|
+
async disconnect(): Promise<void> {
|
|
77
|
+
if (this.inTransaction) {
|
|
78
|
+
await this.abortTransaction();
|
|
79
|
+
}
|
|
80
|
+
await this.producer.disconnect();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Begin transaction
|
|
85
|
+
*/
|
|
86
|
+
async beginTransaction(): Promise<void> {
|
|
87
|
+
if (this.inTransaction) {
|
|
88
|
+
throw new Error('Transaction already in progress');
|
|
89
|
+
}
|
|
90
|
+
await this.producer.transaction();
|
|
91
|
+
this.inTransaction = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Send message within transaction
|
|
96
|
+
*/
|
|
97
|
+
async send(topic: string, messages: Array<{ key?: string; value: string }>) {
|
|
98
|
+
if (!this.inTransaction) {
|
|
99
|
+
throw new Error('No active transaction. Call beginTransaction() first.');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return this.producer.send({
|
|
103
|
+
topic,
|
|
104
|
+
messages,
|
|
105
|
+
compression: this.config.compressionType,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Commit transaction
|
|
111
|
+
*/
|
|
112
|
+
async commitTransaction(): Promise<void> {
|
|
113
|
+
if (!this.inTransaction) {
|
|
114
|
+
throw new Error('No active transaction to commit');
|
|
115
|
+
}
|
|
116
|
+
await this.producer.commit();
|
|
117
|
+
this.inTransaction = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Abort transaction
|
|
122
|
+
*/
|
|
123
|
+
async abortTransaction(): Promise<void> {
|
|
124
|
+
if (!this.inTransaction) {
|
|
125
|
+
throw new Error('No active transaction to abort');
|
|
126
|
+
}
|
|
127
|
+
await this.producer.abort();
|
|
128
|
+
this.inTransaction = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Execute operation within transaction (auto-commit/abort)
|
|
133
|
+
*/
|
|
134
|
+
async executeTransaction<T>(
|
|
135
|
+
operation: (producer: Producer) => Promise<T>
|
|
136
|
+
): Promise<T> {
|
|
137
|
+
await this.beginTransaction();
|
|
138
|
+
try {
|
|
139
|
+
const result = await operation(this.producer);
|
|
140
|
+
await this.commitTransaction();
|
|
141
|
+
return result;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
await this.abortTransaction();
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Exactly-Once Consumer
|
|
151
|
+
*
|
|
152
|
+
* Implements consumer with read_committed isolation level for EOS.
|
|
153
|
+
*/
|
|
154
|
+
export class ExactlyOnceConsumer {
|
|
155
|
+
private consumer: Consumer;
|
|
156
|
+
private config: Required<EOSConsumerConfig>;
|
|
157
|
+
|
|
158
|
+
constructor(kafka: Kafka, config: EOSConsumerConfig) {
|
|
159
|
+
this.config = {
|
|
160
|
+
groupId: config.groupId,
|
|
161
|
+
isolationLevel: config.isolationLevel || 'read_committed',
|
|
162
|
+
enableAutoCommit: false, // Must be false for EOS
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
this.consumer = kafka.consumer({
|
|
166
|
+
groupId: this.config.groupId,
|
|
167
|
+
// @ts-ignore - isolationLevel typing issue in kafkajs
|
|
168
|
+
isolationLevel: this.config.isolationLevel,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Connect consumer
|
|
174
|
+
*/
|
|
175
|
+
async connect(): Promise<void> {
|
|
176
|
+
await this.consumer.connect();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Disconnect consumer
|
|
181
|
+
*/
|
|
182
|
+
async disconnect(): Promise<void> {
|
|
183
|
+
await this.consumer.disconnect();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Subscribe to topics
|
|
188
|
+
*/
|
|
189
|
+
async subscribe(topics: string[]): Promise<void> {
|
|
190
|
+
await this.consumer.subscribe({
|
|
191
|
+
topics,
|
|
192
|
+
fromBeginning: false,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Run consumer with exactly-once processing
|
|
198
|
+
*
|
|
199
|
+
* Automatically commits offsets only after successful processing.
|
|
200
|
+
*/
|
|
201
|
+
async run(
|
|
202
|
+
handler: (payload: EachMessagePayload) => Promise<void>
|
|
203
|
+
): Promise<void> {
|
|
204
|
+
await this.consumer.run({
|
|
205
|
+
autoCommit: false, // Manual offset management for EOS
|
|
206
|
+
eachMessage: async (payload) => {
|
|
207
|
+
try {
|
|
208
|
+
// Process message
|
|
209
|
+
await handler(payload);
|
|
210
|
+
|
|
211
|
+
// Commit offset only after successful processing
|
|
212
|
+
await this.consumer.commitOffsets([
|
|
213
|
+
{
|
|
214
|
+
topic: payload.topic,
|
|
215
|
+
partition: payload.partition,
|
|
216
|
+
offset: (parseInt(payload.message.offset) + 1).toString(),
|
|
217
|
+
},
|
|
218
|
+
]);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
// Error: offset NOT committed, message will be reprocessed
|
|
221
|
+
console.error('Message processing failed:', error);
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* End-to-End Exactly-Once Pattern
|
|
231
|
+
*
|
|
232
|
+
* Combines transactional producer and consumer for consume-process-produce pattern.
|
|
233
|
+
*/
|
|
234
|
+
export class ExactlyOnceProcessor {
|
|
235
|
+
private consumer: Consumer;
|
|
236
|
+
private producer: Producer;
|
|
237
|
+
private transactionalId: string;
|
|
238
|
+
|
|
239
|
+
constructor(kafka: Kafka, consumerGroupId: string, transactionalId: string) {
|
|
240
|
+
this.transactionalId = transactionalId;
|
|
241
|
+
|
|
242
|
+
// Consumer with read_committed
|
|
243
|
+
this.consumer = kafka.consumer({
|
|
244
|
+
groupId: consumerGroupId,
|
|
245
|
+
// @ts-ignore
|
|
246
|
+
isolationLevel: 'read_committed',
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Transactional producer
|
|
250
|
+
this.producer = kafka.producer({
|
|
251
|
+
transactionalId,
|
|
252
|
+
idempotent: true,
|
|
253
|
+
maxInFlightRequests: 5,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Connect consumer and producer
|
|
259
|
+
*/
|
|
260
|
+
async connect(): Promise<void> {
|
|
261
|
+
await this.consumer.connect();
|
|
262
|
+
await this.producer.connect();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Disconnect consumer and producer
|
|
267
|
+
*/
|
|
268
|
+
async disconnect(): Promise<void> {
|
|
269
|
+
await this.consumer.disconnect();
|
|
270
|
+
await this.producer.disconnect();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Subscribe to input topics
|
|
275
|
+
*/
|
|
276
|
+
async subscribe(topics: string[]): Promise<void> {
|
|
277
|
+
await this.consumer.subscribe({ topics, fromBeginning: false });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Run exactly-once consume-process-produce loop
|
|
282
|
+
*
|
|
283
|
+
* Pattern: Read → Transform → Write → Commit (all atomic)
|
|
284
|
+
*/
|
|
285
|
+
async run(
|
|
286
|
+
handler: (
|
|
287
|
+
payload: EachMessagePayload
|
|
288
|
+
) => Promise<{ topic: string; messages: Array<{ key?: string; value: string }> } | null>
|
|
289
|
+
): Promise<void> {
|
|
290
|
+
await this.consumer.run({
|
|
291
|
+
autoCommit: false,
|
|
292
|
+
eachMessage: async (payload) => {
|
|
293
|
+
// Begin transaction
|
|
294
|
+
const transaction = await this.producer.transaction();
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
// 1. Process message (transform)
|
|
298
|
+
const output = await handler(payload);
|
|
299
|
+
|
|
300
|
+
// 2. Send output (if any)
|
|
301
|
+
if (output) {
|
|
302
|
+
await transaction.send(output);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 3. Commit consumer offset within transaction
|
|
306
|
+
await transaction.sendOffsets({
|
|
307
|
+
consumerGroupId: this.consumer.groupId,
|
|
308
|
+
topics: [
|
|
309
|
+
{
|
|
310
|
+
topic: payload.topic,
|
|
311
|
+
partitions: [
|
|
312
|
+
{
|
|
313
|
+
partition: payload.partition,
|
|
314
|
+
offset: (parseInt(payload.message.offset) + 1).toString(),
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// 4. Commit transaction (atomic: write + offset commit)
|
|
322
|
+
await transaction.commit();
|
|
323
|
+
} catch (error) {
|
|
324
|
+
// Abort transaction (rollback everything)
|
|
325
|
+
await transaction.abort();
|
|
326
|
+
console.error('Transaction aborted:', error);
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Idempotent Producer (Simplified EOS)
|
|
336
|
+
*
|
|
337
|
+
* Ensures at-least-once delivery without duplicates (no transactions).
|
|
338
|
+
*/
|
|
339
|
+
export class IdempotentProducer {
|
|
340
|
+
private producer: Producer;
|
|
341
|
+
|
|
342
|
+
constructor(kafka: Kafka) {
|
|
343
|
+
this.producer = kafka.producer({
|
|
344
|
+
idempotent: true, // Enable idempotence
|
|
345
|
+
maxInFlightRequests: 5, // Max 5 for idempotence
|
|
346
|
+
acks: -1, // Wait for all replicas
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async connect(): Promise<void> {
|
|
351
|
+
await this.producer.connect();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async disconnect(): Promise<void> {
|
|
355
|
+
await this.producer.disconnect();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async send(topic: string, messages: Array<{ key?: string; value: string }>) {
|
|
359
|
+
return this.producer.send({
|
|
360
|
+
topic,
|
|
361
|
+
messages,
|
|
362
|
+
acks: -1, // Wait for all in-sync replicas
|
|
363
|
+
compression: CompressionTypes.GZIP,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Example Usage: Exactly-Once Producer
|
|
370
|
+
*
|
|
371
|
+
* ```typescript
|
|
372
|
+
* const kafka = new Kafka({ brokers: ['localhost:9092'] });
|
|
373
|
+
* const eosProducer = new ExactlyOnceProducer(kafka, {
|
|
374
|
+
* transactionalId: 'my-producer-1', // Unique per instance
|
|
375
|
+
* });
|
|
376
|
+
*
|
|
377
|
+
* await eosProducer.connect();
|
|
378
|
+
*
|
|
379
|
+
* // Option 1: Manual transaction control
|
|
380
|
+
* await eosProducer.beginTransaction();
|
|
381
|
+
* await eosProducer.send('orders', [{ key: '123', value: '{"total": 99.99}' }]);
|
|
382
|
+
* await eosProducer.send('analytics', [{ value: '{"event": "order_created"}' }]);
|
|
383
|
+
* await eosProducer.commitTransaction(); // Atomic commit
|
|
384
|
+
*
|
|
385
|
+
* // Option 2: Auto-commit/abort wrapper
|
|
386
|
+
* await eosProducer.executeTransaction(async (producer) => {
|
|
387
|
+
* await producer.send({ topic: 'orders', messages: [...] });
|
|
388
|
+
* await producer.send({ topic: 'analytics', messages: [...] });
|
|
389
|
+
* });
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Example Usage: Exactly-Once Consumer
|
|
395
|
+
*
|
|
396
|
+
* ```typescript
|
|
397
|
+
* const eosConsumer = new ExactlyOnceConsumer(kafka, {
|
|
398
|
+
* groupId: 'my-consumer-group',
|
|
399
|
+
* });
|
|
400
|
+
*
|
|
401
|
+
* await eosConsumer.connect();
|
|
402
|
+
* await eosConsumer.subscribe(['orders']);
|
|
403
|
+
*
|
|
404
|
+
* await eosConsumer.run(async ({ message }) => {
|
|
405
|
+
* const order = JSON.parse(message.value.toString());
|
|
406
|
+
* await processOrder(order); // Process message
|
|
407
|
+
* // Offset committed automatically only on success
|
|
408
|
+
* });
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Example Usage: End-to-End Exactly-Once
|
|
414
|
+
*
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const processor = new ExactlyOnceProcessor(
|
|
417
|
+
* kafka,
|
|
418
|
+
* 'transform-consumer-group',
|
|
419
|
+
* 'transform-producer-1'
|
|
420
|
+
* );
|
|
421
|
+
*
|
|
422
|
+
* await processor.connect();
|
|
423
|
+
* await processor.subscribe(['input-topic']);
|
|
424
|
+
*
|
|
425
|
+
* await processor.run(async ({ message }) => {
|
|
426
|
+
* // Transform message
|
|
427
|
+
* const input = JSON.parse(message.value.toString());
|
|
428
|
+
* const output = transform(input);
|
|
429
|
+
*
|
|
430
|
+
* // Return output to send (or null to skip)
|
|
431
|
+
* return {
|
|
432
|
+
* topic: 'output-topic',
|
|
433
|
+
* messages: [{ key: input.id, value: JSON.stringify(output) }],
|
|
434
|
+
* };
|
|
435
|
+
* });
|
|
436
|
+
* // Read, transform, write, and offset commit are ALL atomic!
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
|
|
440
|
+
export default {
|
|
441
|
+
ExactlyOnceProducer,
|
|
442
|
+
ExactlyOnceConsumer,
|
|
443
|
+
ExactlyOnceProcessor,
|
|
444
|
+
IdempotentProducer,
|
|
445
|
+
};
|