From 2d22cacd1f710f38d1dacbf2cbe309c1950a9db8 Mon Sep 17 00:00:00 2001 From: Graeme Foster <80714+GraemeF@users.noreply.github.com> Date: Mon, 5 Oct 2026 07:35:58 +0100 Subject: [PATCH] docs: keep maintainers' private workflow out of the agent instructions CLAUDE.md now gives setup, build and contribution steps a contributor can follow, in place of a worktree routine specific to one maintainer's machine. The /start and /automerge commands that encoded that routine are removed, as are two step-by-step agent plans for work that has shipped. The remaining design docs and the server README drop references to issue ids in a private tracker and to local paths. --- .claude/commands/automerge.md | 45 -- .claude/commands/implement-pattern.md | 2 +- .claude/commands/start.md | 43 -- CLAUDE.md | 54 +- ...ect-language-service-integration-design.md | 4 +- docs/plans/2025-10-25-effect-ls-cli.md | 666 ---------------- ...-eventsourcing-server-components-design.md | 5 +- docs/plans/2025-10-25-subscribeAll-design.md | 3 +- .../2025-10-25-subscribeAll-implementation.md | 728 ------------------ .../eventsourcing-server-sequence-diagram.md | 2 +- packages/eventsourcing-server/README.md | 4 +- 11 files changed, 21 insertions(+), 1535 deletions(-) delete mode 100644 .claude/commands/automerge.md delete mode 100644 .claude/commands/start.md delete mode 100644 docs/plans/2025-10-25-effect-ls-cli.md delete mode 100644 docs/plans/2025-10-25-subscribeAll-implementation.md diff --git a/.claude/commands/automerge.md b/.claude/commands/automerge.md deleted file mode 100644 index 48aeed22..00000000 --- a/.claude/commands/automerge.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -allowed-tools: Bash(git status:*), Bash(git branch:*), Bash(git checkout:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), Bash(git pull:*), Bash(gh pr:*), Bash(git worktree:*), Bash(cd:*), Bash(pwd:*) -description: Commit changes, create/update PR, enable automerge, and wait for merge ---- - -## Your task - -This command automates the entire process of getting changes merged into main: - -1. **Check current git status** - Determine if there are uncommitted changes -2. **Handle uncommitted changes** (if any): - - Check if already on a feature branch, if not create one -3. **Run checks** - - Run `turbo all` to ensure all tests and checks pass before proceeding - - Fix any issues if checks fail - - Assume all failures are due to your changes, not external factors -4. **Create a changeset file** - - Manually create a new changeset file in `.changeset/` with a summary of changes and type of release (patch or minor only, because we are still pre-1.0.0) - - Changes must be described from the perspective of a user of the package, not internal implementation details -5. **Commit and push changes** to the feature branch - - Stage and commit all relevant changes (ignoring unrelated changes) with an appropriate commit message - - Push the branch to origin -6. **Check for existing PR**: - - If PR already exists for current branch, ensure it's up to date - - If no PR exists, create one with a descriptive title and body -7. **Enable automerge** on the PR (squash merge) -8. **Monitor the PR** until it's merged: - - Use `gh pr checks --watch` to wait for checks to complete - - After checks pass, verify merge status - - Alert when merged successfully or if merge fails -9. **Clean up** after successful merge: - -- Navigate back to repo root: `cd ../../` (from worktrees/{feature-name} to eventsourcing/) -- Pull latest changes to main: `git pull origin main` -- Remove the feature worktree: `git worktree remove worktrees/{feature-name}` -- Delete the local feature branch: `git branch -d feat/{feature-name}` - -## Important notes - -- If on main branch with uncommitted changes, create a feature branch first -- Use conventional commit format for commit messages -- PR title should follow conventional commits format -- Monitor and report PR check statuses while waiting -- Only proceed with cleanup after successful merge -- If merge fails, report the failure reason and leave branch intact for debugging diff --git a/.claude/commands/implement-pattern.md b/.claude/commands/implement-pattern.md index 1863bce6..8aa25284 100644 --- a/.claude/commands/implement-pattern.md +++ b/.claude/commands/implement-pattern.md @@ -23,7 +23,7 @@ Read `packages/eslint-effect/EFFECT_PATTERNS_TODO.md` and select the next unchec **CRITICAL:** Before implementing, verify your understanding of the pattern: -1. **Check Effect source code** at `~/Development/effect/` to understand: +1. **Check Effect source code** in the [Effect repository](https://github.com/Effect-TS/effect) to understand: - How the simplified function works - Its type signature and behavior - Edge cases and constraints diff --git a/.claude/commands/start.md b/.claude/commands/start.md deleted file mode 100644 index beb93e41..00000000 --- a/.claude/commands/start.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -allowed-tools: Bash(git worktree *), Bash(git fetch:*), Bash(git status:*), Bash(git branch:*), Bash(cd:*), Bash(bun install:*), Bash(pwd:*), Bash(mise trust), Bash(mise install), Bash(git pull:*), Bash(git checkout:*), Bash(git worktree add:*) -description: Start a new piece of work using git worktree ---- - -## Your task - -IMPORTANT: Do not discard any local changes. It is possible that the command is being run in order to prepare to commit them. - -IMPORTANT: Always use worktrees for feature development to maintain clean separation from main. - -1. **Ensure we're in the repo root and up to date:** - - Run `pwd` to confirm current location - - Run `git fetch origin main` to get latest from remote - - If not in repo root (eventsourcing/), navigate back to it first - - Run `git pull origin main` to update main branch - -2. **Create new worktree for feature branch:** - - Generate appropriate feature name based on the work described by user - - Use descriptive kebab-case naming: feat/add-user-auth, feat/fix-payment-bug, etc. - - Run `git worktree add worktrees/{feature-name} -b feat/{feature-name}` - - This creates both the branch and isolated working directory in the worktrees/ subdirectory - -3. **Set up the new worktree:** - - Change to the new worktree: `cd worktrees/{feature-name}` - - Run `mise trust` to trust the .mise.toml configuration in the new worktree - - Run `bun install` to set up dependencies in the new worktree - - Run `git status` to confirm branch and clean state - - Run `pwd` to show current worktree location - -**Worktree Benefits:** - -- Complete isolation from main branch -- Can work on multiple features simultaneously -- No risk of contaminating main with uncommitted changes -- Each worktree has its own node_modules and build artifacts - -**Next Steps After Setup:** - -1. Make your changes in the isolated worktree -2. Create changeset if affecting published packages -3. Commit with conventional format: "type(scope): description" -4. Use `/automerge` command to handle PR creation and merging diff --git a/CLAUDE.md b/CLAUDE.md index cba0979d..93a5566d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,51 +20,23 @@ eventsourcing is a set of event sourcing libraries for TypeScript, built on Effe An agent may decline an ask that falls outside this remit, and should say which owner above the ask belongs to. -# 🚨 CRITICAL GIT WORKTREE WORKFLOW - DO THIS FIRST! 🚨 +## Setup -**BEFORE ANY CODE CHANGES - USE WORKTREES:** +Tool versions are pinned in the mise config. Run `mise install`, then `bun install`. Use Bun, not node or npm. -1. Run `git fetch origin main` to get latest from remote -2. Create worktree for new feature: `git worktree add worktrees/descriptive-name -b feat/descriptive-name` -3. Change to the new worktree: `cd worktrees/descriptive-name` -4. Run `mise trust && mise install` to make tools available -5. Run `bun install` to set up dependencies -6. ONLY THEN start making changes in the isolated worktree +## Building and testing -- Always use turbo to run tasks. -- Always use Bun instead of node or npm. -- PR titles must follow conventional commits +Run every task through turbo: -## Worktree Structure +- `turbo all` runs the build, lint, tests and repository checks that CI runs. It must pass before a change is ready. +- `turbo test --filter=` runs one package's tests. +- The PostgreSQL store's tests need the database from `docker-compose up -d`. The `TEST_PG_*` variables in `.mise.toml` point at it. -``` -eventsourcing/ <- Repo root (main branch checkout) -├── .git/ <- Git directory -├── worktrees/ <- All feature worktrees go here -│ ├── feature-name-1/ <- Feature worktree -│ └── feature-name-2/ <- Another feature worktree -├── packages/ <- Source code -└── ... -``` +The `eslint-effect` lint rules and the Effect language service enforce the Effect conventions a change must follow. `bun install` patches the language service into TypeScript. Fix what they report rather than disabling them. -## Worktree Benefits +## Contributing -- Each feature branch gets its own isolated working directory -- Never risk contaminating main branch with uncommitted changes -- Can work on multiple features simultaneously in parallel worktrees -- Clean separation between repo root and feature development - -## Before Starting Work - -- ALWAYS use `/start` command to create proper worktree setup -- Choose a short worktree name based on the work description (worktrees live in worktrees/ subdirectory) -- NEVER work directly in repo root for feature development - always use a worktree -- Verify you're in correct worktree with `pwd` and `git branch` -- Each worktree is a complete working copy with its own node_modules and mise config - -## Releasing - -- Start each new piece of work in a new branch from the latest origin/main. Changes are always submitted via a PR. -- With each commit, review and update pending changesets accordingly. -- Changesets must be written with the package consumer in mind, telling them what they need to know about changes, not just what changed. -- Packages are released manually via GitHub UI, not automatically. +- Branch from the latest `main` and submit every change as a pull request. +- Write the pull request title as a conventional commit. +- Add or update a changeset in `.changeset/` for any change to a published package. Write it for the package's consumer: say what they need to know, not just what changed. +- Packages are released by hand from the GitHub UI, not automatically. diff --git a/docs/plans/2025-10-25-effect-language-service-integration-design.md b/docs/plans/2025-10-25-effect-language-service-integration-design.md index 7ab3cfb7..5cf6f493 100644 --- a/docs/plans/2025-10-25-effect-language-service-integration-design.md +++ b/docs/plans/2025-10-25-effect-language-service-integration-design.md @@ -2,11 +2,10 @@ **Date:** 2025-10-25 **Status:** Approved -**Related Issue:** hp-1 ## Overview -This design integrates the Effect Language Service CLI into the brownsauce monorepo to provide compile-time diagnostics for Effect-specific issues. The integration enables TypeScript's compiler to catch Effect problems (floating Effects, incorrect yield usage, missing service dependencies) during normal type-checking, with hard enforcement that fails builds on violations. +This design integrates the Effect Language Service CLI into this monorepo to provide compile-time diagnostics for Effect-specific issues. The integration enables TypeScript's compiler to catch Effect problems (floating Effects, incorrect yield usage, missing service dependencies) during normal type-checking, with hard enforcement that fails builds on violations. ## Goals @@ -187,4 +186,3 @@ High-level steps: - Effect Language Service: https://effect.website/docs/other/language-service - Current devDependency version: `@effect/language-service@0.47.1` -- Related Beads issue: hp-1 diff --git a/docs/plans/2025-10-25-effect-ls-cli.md b/docs/plans/2025-10-25-effect-ls-cli.md deleted file mode 100644 index 94e4760c..00000000 --- a/docs/plans/2025-10-25-effect-ls-cli.md +++ /dev/null @@ -1,666 +0,0 @@ -# Effect Language Service Integration Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Enable compile-time Effect diagnostics by patching TypeScript to catch Effect-specific issues during normal type-checking. - -**Architecture:** Modify root prepare script to run effect-ls patch on install, verify patching works, then validate that existing turbo tasks surface Effect errors. - -**Tech Stack:** - -- @effect/language-service@0.47.1 (already installed) -- TypeScript 5.9.3 -- Bun package manager -- Turbo monorepo orchestration - ---- - -## Task 1: Update Root Package Prepare Script - -**Files:** - -- Modify: `/Users/graemefoster/Development/brownsauce/worktrees/effect-ls-cli/package.json:20` - -**Context:** The prepare script currently only runs `husky`. We need to add `effect-ls patch` to enable Effect diagnostics at compile time. - -**Step 1: Read current package.json prepare script** - -Run: - -```bash -cat package.json | jq '.scripts.prepare' -``` - -Expected output: - -``` -"husky" -``` - -**Step 2: Modify prepare script to include effect-ls patch** - -Edit package.json line 20: - -Before: - -```json -"prepare": "husky", -``` - -After: - -```json -"prepare": "husky && effect-ls patch", -``` - -**Why this order:** husky sets up git hooks first, then effect-ls patches TypeScript. Both must succeed for prepare to succeed. - -**Step 3: Verify the change** - -Run: - -```bash -cat package.json | jq '.scripts.prepare' -``` - -Expected output: - -``` -"husky && effect-ls patch" -``` - -**Step 4: Commit the change** - -```bash -git add package.json -git commit -m "chore: add effect-ls patch to prepare script - -Enable Effect-specific TypeScript diagnostics by patching TypeScript's -language service on install. - -Related: hp-1" -``` - -Expected: Commit succeeds with message about running hooks (gitleaks, lint-staged). - ---- - -## Task 2: Trigger TypeScript Patching - -**Files:** - -- None (runs existing script) - -**Context:** The prepare script only runs on `bun install`. We need to trigger it manually to apply the patch without removing/reinstalling node_modules. - -**Step 1: Run bun install to trigger prepare script** - -Run: - -```bash -bun install -``` - -Expected output (key lines to look for): - -``` -$ husky && effect-ls patch -✓ TypeScript patched successfully -``` - -**Why bun install:** The prepare npm lifecycle hook runs after `install` and before `postinstall`. Even if dependencies are already installed, prepare will run. - -**Step 2: Verify node_modules structure** - -The patch modifies TypeScript files in node_modules. Check that typescript directory exists: - -Run: - -```bash -ls node_modules/typescript/lib/ | grep -E "tsserver.js|typescript.js" | head -2 -``` - -Expected output: - -``` -tsserver.js -typescript.js -``` - -**Context:** effect-ls patch modifies these TypeScript compiler files to inject Effect-specific diagnostics. - ---- - -## Task 3: Verify Patch Is Active - -**Files:** - -- None (verification only) - -**Context:** effect-ls provides a check command to confirm TypeScript is successfully patched. - -**Step 1: Run effect-ls check command** - -Run: - -```bash -npx effect-ls check -``` - -Expected output: - -``` -✓ TypeScript is patched -``` - -**If patch failed:** Output would show: - -``` -✗ TypeScript is not patched -Run 'effect-ls patch' to enable diagnostics -``` - -**Step 2: Document verification in terminal** - -No commit needed - this is verification only. If check passes, proceed to next task. - ---- - -## Task 4: Test Enforcement with Deliberate Error - -**Files:** - -- Create: `/Users/graemefoster/Development/brownsauce/worktrees/effect-ls-cli/test-effect-error.ts` - -**Context:** Before running on real code, verify the patch catches Effect-specific issues with a controlled test. - -**Step 1: Create test file with floating Effect** - -Create `test-effect-error.ts` at repository root: - -```typescript -import { Effect } from 'effect'; - -// This is a deliberate floating Effect - should cause error -Effect.succeed(42); - -console.log('This file intentionally has an Effect error'); -``` - -**Why floating Effect:** An Effect that is created but not handled (not run, not returned, not assigned) is a common mistake. effect-ls should flag this. - -**Step 2: Run TypeScript on the test file** - -Run: - -```bash -npx tsc --noEmit test-effect-error.ts -``` - -Expected output (should show Effect-specific error): - -``` -test-effect-error.ts:4:1 - error EFFECT-XXXX: Floating Effect detected - -Effect.succeed(42) -^~~~~~~~~~~~~~~~~~ - - This Effect value is created but never used. Did you forget to handle it? -``` - -**If no error shown:** Patch may not be working. Return to Task 3. - -**Step 3: Verify error message clarity** - -The error should: - -- Point to the exact line (line 4) -- Identify it as an Effect-specific issue (not standard TypeScript error) -- Provide actionable guidance - -**Step 4: Delete test file** - -Run: - -```bash -rm test-effect-error.ts -``` - -Expected: File removed, no git changes to commit. - -**Step 5: Verify file is deleted** - -Run: - -```bash -ls test-effect-error.ts 2>&1 -``` - -Expected output: - -``` -ls: test-effect-error.ts: No such file or directory -``` - ---- - -## Task 5: Run Baseline TypeCheck on Real Codebase - -**Files:** - -- None (verification only) - -**Context:** Now that patching is verified, run typecheck on actual codebase to establish baseline. This may reveal existing Effect issues that need addressing. - -**Step 1: Run turbo typecheck** - -Run: - -```bash -turbo typecheck -``` - -Expected: One of two outcomes: - -**Outcome A - Clean codebase:** - -``` -✓ typecheck -✓ All tasks completed successfully -``` - -**Outcome B - Existing Effect issues found:** - -``` -× typecheck failed -packages/some-package/src/file.ts:XX:X - error EFFECT-XXX: [Effect issue description] -``` - -**Step 2: Document any Effect issues found** - -If Outcome B occurs, create list of issues found: - -Run: - -```bash -turbo typecheck 2>&1 | grep "error EFFECT" > effect-issues-baseline.txt -``` - -This creates a baseline of Effect-specific errors to address (if any exist). - -**Step 3: Decide on next steps based on outcome** - -**If Outcome A (no issues):** - -- Codebase is clean -- Proceed to Task 6 (document success) - -**If Outcome B (issues found):** - -- Review effect-issues-baseline.txt -- Create follow-up Beads issues for each category of Effect error -- Add discovered-from dependency to hp-1 -- May need to fix issues before merging this PR, or document as known issues - -**Do not commit baseline file** - this is diagnostic output only. - ---- - -## Task 6: Run Full CI Pipeline Validation - -**Files:** - -- None (verification only) - -**Context:** Verify that Effect diagnostics work through the full CI pipeline, not just typecheck in isolation. - -**Step 1: Run turbo ci task** - -Run: - -```bash -turbo ci -``` - -Expected: Full CI pipeline runs including build, lint, test, and all checks. - -**Outcome depends on Task 5 results:** - -**If Task 5 was clean:** - -``` -✓ ci - ✓ build - ✓ lint - ✓ test - ✓ arch:check - ✓ deps:check - ✓ release:validate -✓ All tasks completed successfully -``` - -**If Task 5 found Effect issues:** - -``` -× ci failed - × build failed (Effect errors from Task 5) -``` - -**Step 2: Verify Effect errors surface in build task** - -If issues exist, confirm they appear during build (not just typecheck): - -Run: - -```bash -turbo build 2>&1 | grep "error EFFECT" -``` - -Expected: Same Effect errors from Task 5 appear during build. - -**Why this matters:** Confirms Effect diagnostics are integrated into normal compilation, not just a separate typecheck step. - -**Step 3: Assess merge readiness** - -**Merge-ready scenarios:** - -- No Effect issues found in Task 5/6 -- Effect issues found but documented in follow-up Beads issues -- Effect issues found and fixed in this PR - -**Not merge-ready:** - -- Effect issues found and blocking (must fix or disable enforcement) - ---- - -## Task 7: Update Beads Issue with Results - -**Files:** - -- None (Beads update only) - -**Context:** Document what was accomplished and surface any discovered work. - -**Step 1: Determine completion status** - -Based on Task 5 and Task 6 outcomes: - -**If no Effect issues found:** - -```bash -bd close hp-1 --reason "Effect-LS integration complete. TypeScript patching enabled, no existing Effect issues found. Build enforcement active." -``` - -**If Effect issues found and documented:** - -```bash -# First create follow-up issues for discovered problems -bd create "Fix floating Effects in eventsourcing-aggregates" -p 1 -t bug --deps discovered-from:hp-1 -bd create "Fix incorrect yield usage in eventsourcing-store" -p 1 -t bug --deps discovered-from:hp-1 - -# Then close this issue -bd close hp-1 --reason "Effect-LS integration complete. TypeScript patching enabled. Discovered Effect issues documented in follow-up tasks." -``` - -**Step 2: Verify issue status** - -Run: - -```bash -bd show hp-1 -``` - -Expected: Status shows "closed" with completion reason. - -**Step 3: Check for newly ready work** - -Run: - -```bash -bd ready --limit 5 -``` - -Expected: If follow-up issues were created, they may now be ready to work on. - ---- - -## Task 8: Create Changeset - -**Files:** - -- Create: `.changeset/.md` - -**Context:** Document this change for release notes, even though it's a development-only change (not published to npm). - -**Step 1: Generate changeset** - -Run: - -```bash -bun changeset -``` - -Interactive prompts: - -``` -? Which packages would you like to include? - › (none - this is a monorepo-wide change) - -? Which packages should have a major bump? - › (none) - -? Which packages should have a minor bump? - › (none) - -? Which packages should have a patch bump? - › (none - this doesn't affect published packages) - -? Please enter a summary for this change (this will be used as the commit message): - › Enable Effect-specific TypeScript diagnostics via effect-ls patching -``` - -**Alternative:** Since this doesn't affect published packages, you may skip changeset. - -**Step 2: Review generated changeset file** - -If changeset was created: - -Run: - -```bash -cat .changeset/*.md | grep -A 5 "effect-ls" -``` - -Expected: Changeset file with summary. - -**Step 3: Commit changeset if created** - -If changeset was created: - -```bash -git add .changeset/ -git commit -m "chore: add changeset for effect-ls integration" -``` - -**If skipping changeset:** No commit needed. - ---- - -## Task 9: Final Commit and Verification - -**Files:** - -- None (verification only) - -**Context:** Ensure all changes are committed and worktree is clean. - -**Step 1: Verify working tree is clean** - -Run: - -```bash -git status -``` - -Expected output: - -``` -On branch feat/effect-ls-cli -nothing to commit, working tree clean -``` - -**If untracked files exist:** - -- `effect-issues-baseline.txt` - should be deleted (diagnostic file) -- Other files - review and either commit or add to .gitignore - -**Step 2: Review commit history** - -Run: - -```bash -git log --oneline -n 5 -``` - -Expected commits (in order, newest first): - -``` - chore: add changeset for effect-ls integration (optional) - chore: add effect-ls patch to prepare script - docs: add Effect Language Service integration design -``` - -**Step 3: Verify remote branch exists** - -Run: - -```bash -git branch -vv -``` - -Expected output showing tracking: - -``` -* feat/effect-ls-cli [origin/feat/effect-ls-cli] chore: add effect-ls patch to prepare script -``` - -**If no remote tracking:** - -```bash -git push -u origin feat/effect-ls-cli -``` - -**Step 4: Confirm all verification steps passed** - -Checklist: - -- [ ] `effect-ls check` reports TypeScript is patched -- [ ] `turbo typecheck` runs (passes or surfaces Effect issues) -- [ ] `turbo ci` includes Effect diagnostics -- [ ] Beads issue hp-1 is closed -- [ ] All changes committed -- [ ] Branch pushed to remote - ---- - -## Task 10: Create Pull Request - -**Files:** - -- None (GitHub PR creation) - -**Context:** Submit changes for review via pull request. - -**Step 1: Create PR using gh CLI** - -Run: - -```bash -gh pr create --title "chore: enable Effect-LS compile-time diagnostics" --body "$(cat <<'EOF' -## Summary - -Enables Effect-specific TypeScript diagnostics by integrating @effect/language-service into the build pipeline. - -## Changes - -- Modified root `package.json` prepare script to run `effect-ls patch` -- TypeScript is now patched on `bun install` to surface Effect-specific errors -- Effect issues appear during normal type-checking (turbo typecheck/build/ci) - -## Verification - -- [x] `effect-ls check` confirms TypeScript is patched -- [x] `turbo typecheck` surfaces Effect diagnostics -- [x] `turbo ci` includes Effect validation -- [x] Deliberate floating Effect causes build failure -- [x] Error messages are clear and actionable - -## Effect Issues Found - - - -None / See follow-up issues: hp-X, hp-Y - -## Design - -See [Effect Language Service Integration Design](docs/plans/2025-10-25-effect-language-service-integration-design.md) - -## Related - -Closes hp-1 -EOF -)" -``` - -Expected: PR created and URL returned. - -**Step 2: Verify PR was created** - -Run: - -```bash -gh pr view -``` - -Expected: PR details displayed in terminal. - -**Step 3: Open PR in browser (optional)** - -Run: - -```bash -gh pr view --web -``` - -Expected: Browser opens to PR page. - ---- - -## Success Criteria - -All tasks completed successfully if: - -- [x] Root package.json prepare script includes `effect-ls patch` -- [x] `effect-ls check` confirms TypeScript is patched -- [x] Deliberate floating Effect causes type error -- [x] `turbo typecheck` and `turbo ci` surface Effect diagnostics -- [x] Beads issue hp-1 is closed -- [x] Pull request created -- [x] No uncommitted changes in working tree - -## Potential Issues and Solutions - -| Issue | Solution | -| --------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `effect-ls check` reports not patched after install | Re-run `bun install`, check for errors in prepare script output | -| Deliberate Effect error not caught | Verify effect-ls version matches TypeScript version compatibility | -| Existing codebase has many Effect issues | Create follow-up Beads issues, prioritize fixes, or temporarily disable enforcement | -| CI fails on unrelated issues | Fix issues or document as known problems unrelated to this PR | -| Prepare script fails in CI | Ensure CI environment has bun and effect-ls installed | - -## References - -- Design document: `docs/plans/2025-10-25-effect-language-service-integration-design.md` -- Effect Language Service docs: https://effect.website/docs/other/language-service -- Beads issue: hp-1 diff --git a/docs/plans/2025-10-25-eventsourcing-server-components-design.md b/docs/plans/2025-10-25-eventsourcing-server-components-design.md index 021f1c5f..61c3cfce 100644 --- a/docs/plans/2025-10-25-eventsourcing-server-components-design.md +++ b/docs/plans/2025-10-25-eventsourcing-server-components-design.md @@ -18,7 +18,7 @@ The spike created an `eventsourcing-server` package with working implementations We'll create `@codeforbreakfast/eventsourcing-server` with four discrete, composable components that eliminate boilerplate while maintaining flexibility. -**Pre-requisite:** EventStore must support live cross-stream subscriptions via `subscribeAll()` (see hp-8). +**Pre-requisite:** EventStore must support live cross-stream subscriptions via `subscribeAll()`. ### 1. EventBus @@ -275,8 +275,7 @@ All EventStore implementations must support live cross-stream subscriptions via **Implementation status:** -- See hp-8 for implementation task -- Must be completed before EventBus (hp-4) can be implemented +- `subscribeAll()` must be in place before EventBus can be implemented **If process managers need guarantees:** Process managers that require guaranteed delivery, exactly-once processing, or replay capabilities should use external queues (SQS, RabbitMQ, etc.) instead of EventBus. diff --git a/docs/plans/2025-10-25-subscribeAll-design.md b/docs/plans/2025-10-25-subscribeAll-design.md index b4035456..428ff0a9 100644 --- a/docs/plans/2025-10-25-subscribeAll-design.md +++ b/docs/plans/2025-10-25-subscribeAll-design.md @@ -1,7 +1,6 @@ # subscribeAll() Design **Date:** 2025-10-25 -**Issue:** hp-8 ## Purpose @@ -163,4 +162,4 @@ const testSubscribeAll = ( - [ ] InMemory implementation using PubSub - [ ] Filesystem implementation using PubSub - [ ] Contract tests pass for all three stores -- [ ] Unblocks hp-4 (EventBus) and hp-5 (CommandDispatcher) +- [ ] Unblocks EventBus and CommandDispatcher diff --git a/docs/plans/2025-10-25-subscribeAll-implementation.md b/docs/plans/2025-10-25-subscribeAll-implementation.md deleted file mode 100644 index 6dab40ad..00000000 --- a/docs/plans/2025-10-25-subscribeAll-implementation.md +++ /dev/null @@ -1,728 +0,0 @@ -# subscribeAll() Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Add `subscribeAll()` method to EventStore interface for live, cross-stream event subscriptions, implemented across all three store types (Postgres, InMemory, Filesystem). - -**Architecture:** Add method to EventStore interface that returns `Effect>`. Postgres leverages existing NotificationListener. InMemory and Filesystem use internal PubSub that gets published to on append. - -**Tech Stack:** Effect, TypeScript, Postgres LISTEN/NOTIFY, PubSub - ---- - -## Task 1: Add subscribeAll() to EventStore Interface - -**Files:** - -- Modify: `packages/eventsourcing-store/src/lib/services.ts:11-55` - -**Step 1: Add subscribeAll() method to EventStore interface** - -In `packages/eventsourcing-store/src/lib/services.ts`, add the new method after the `subscribe` method: - -```typescript -export interface EventStore { - // ... existing append, read, subscribe methods ... - - /** - * Subscribe to live events from ALL streams - * Returns only new events committed after subscription starts (no historical replay) - * Each event includes its position (streamId + eventNumber) - * - * @returns A stream of events from all streams with their positions - */ - readonly subscribeAll: () => Effect.Effect< - Stream.Stream< - { readonly position: EventStreamPosition; readonly event: TEvent }, - ParseResult.ParseError | EventStoreError - >, - EventStoreError, - never - >; -} -``` - -**Step 2: Verify TypeScript compilation** - -Run: `cd packages/eventsourcing-store && bun run build` -Expected: Build succeeds, but implementations will show type errors (expected - we'll fix those next) - -**Step 3: Commit interface change** - -```bash -git add packages/eventsourcing-store/src/lib/services.ts -git commit -m "feat(eventsourcing-store): add subscribeAll() to EventStore interface" -``` - ---- - -## Task 2: Create Contract Tests for subscribeAll() - -**Files:** - -- Create: `packages/eventsourcing-testing-contracts/src/lib/store/subscribeAll.contract.ts` -- Modify: `packages/eventsourcing-testing-contracts/src/lib/store/index.ts` - -**Step 1: Create subscribeAll contract test file** - -Create `packages/eventsourcing-testing-contracts/src/lib/store/subscribeAll.contract.ts`: - -```typescript -import { Chunk, Effect, Stream, TestContext, pipe } from 'effect'; -import { describe, expect, it } from 'bun:test'; -import type { EventStore } from '@codeforbreakfast/eventsourcing-store'; -import { EventStreamPosition } from '@codeforbreakfast/eventsourcing-store'; - -/** - * Contract tests for EventStore.subscribeAll() - * All implementations must pass these tests - */ -export const subscribeAllContract = ( - storeName: string, - makeStore: Effect.Effect, never, R> -) => { - describe(`${storeName} - subscribeAll() contract`, () => { - it('should receive events from multiple streams', () => - Effect.gen(function* () { - const store = yield* makeStore; - - // Start subscription (take 4 events then complete) - const stream = yield* store.subscribeAll(); - - // Collect events in background - const fiber = yield* pipe(stream, Stream.take(4), Stream.runCollect, Effect.fork); - - // Give subscription time to initialize - yield* Effect.sleep('100 millis'); - - // Append events to different streams - yield* pipe( - Stream.make('event1', 'event2'), - Stream.run(store.append({ streamId: 'stream-1' as any, eventNumber: 0 })) - ); - - yield* pipe( - Stream.make('event3', 'event4'), - Stream.run(store.append({ streamId: 'stream-2' as any, eventNumber: 0 })) - ); - - // Wait for fiber to collect all events - const chunk = yield* fiber.join(); - const events = Chunk.toReadonlyArray(chunk); - - // Verify events from both streams appeared - expect(events.length).toBe(4); - expect(events.map((e) => e.event)).toEqual(['event1', 'event2', 'event3', 'event4']); - expect(events.map((e) => e.position.streamId)).toContain('stream-1'); - expect(events.map((e) => e.position.streamId)).toContain('stream-2'); - }).pipe(Effect.provide(TestContext.TestContext), Effect.runPromise)); - - it('should only receive events committed AFTER subscription starts (live-only)', () => - Effect.gen(function* () { - const store = yield* makeStore; - - // Append events BEFORE subscription - yield* pipe( - Stream.make('old-event-1', 'old-event-2'), - Stream.run(store.append({ streamId: 'stream-1' as any, eventNumber: 0 })) - ); - - // Start subscription (take 2 events) - const stream = yield* store.subscribeAll(); - - const fiber = yield* pipe(stream, Stream.take(2), Stream.runCollect, Effect.fork); - - yield* Effect.sleep('100 millis'); - - // Append events AFTER subscription - yield* pipe( - Stream.make('new-event-1', 'new-event-2'), - Stream.run(store.append({ streamId: 'stream-2' as any, eventNumber: 0 })) - ); - - const chunk = yield* fiber.join(); - const events = Chunk.toReadonlyArray(chunk); - - // Should only see new events, not old ones - expect(events.length).toBe(2); - expect(events.map((e) => e.event)).toEqual(['new-event-1', 'new-event-2']); - expect(events.map((e) => e.event)).not.toContain('old-event-1'); - expect(events.map((e) => e.event)).not.toContain('old-event-2'); - }).pipe(Effect.provide(TestContext.TestContext), Effect.runPromise)); - - it('should support multiple concurrent subscribers', () => - Effect.gen(function* () { - const store = yield* makeStore; - - // Start two subscriptions - const stream1 = yield* store.subscribeAll(); - const stream2 = yield* store.subscribeAll(); - - const fiber1 = yield* pipe(stream1, Stream.take(2), Stream.runCollect, Effect.fork); - - const fiber2 = yield* pipe(stream2, Stream.take(2), Stream.runCollect, Effect.fork); - - yield* Effect.sleep('100 millis'); - - // Append events - yield* pipe( - Stream.make('event1', 'event2'), - Stream.run(store.append({ streamId: 'stream-1' as any, eventNumber: 0 })) - ); - - const chunk1 = yield* fiber1.join(); - const chunk2 = yield* fiber2.join(); - - const events1 = Chunk.toReadonlyArray(chunk1).map((e) => e.event); - const events2 = Chunk.toReadonlyArray(chunk2).map((e) => e.event); - - // Both subscribers should receive all events - expect(events1).toEqual(['event1', 'event2']); - expect(events2).toEqual(['event1', 'event2']); - }).pipe(Effect.provide(TestContext.TestContext), Effect.runPromise)); - - it('should clean up properly when subscription is interrupted', () => - Effect.gen(function* () { - const store = yield* makeStore; - - const stream = yield* store.subscribeAll(); - - const fiber = yield* pipe(stream, Stream.take(2), Stream.runCollect, Effect.fork); - - yield* Effect.sleep('100 millis'); - - // Append some events - yield* pipe( - Stream.make('event1', 'event2'), - Stream.run(store.append({ streamId: 'stream-1' as any, eventNumber: 0 })) - ); - - const chunk = yield* fiber.join(); - const events = Chunk.toReadonlyArray(chunk).map((e) => e.event); - - // Append more events after fiber completes (subscription ended) - yield* pipe( - Stream.make('event3', 'event4'), - Stream.run(store.append({ streamId: 'stream-1' as any, eventNumber: 2 })) - ); - - yield* Effect.sleep('100 millis'); - - // Should only have events before interruption - expect(events).toEqual(['event1', 'event2']); - expect(events).not.toContain('event3'); - expect(events).not.toContain('event4'); - }).pipe(Effect.provide(TestContext.TestContext), Effect.runPromise)); - }); -}; -``` - -**Step 2: Export contract from store index** - -Add to `packages/eventsourcing-testing-contracts/src/lib/store/index.ts`: - -```typescript -export * from './subscribeAll.contract'; -``` - -**Step 3: Verify TypeScript compilation** - -Run: `cd packages/eventsourcing-testing-contracts && bun run build` -Expected: Build succeeds - -**Step 4: Commit contract tests** - -```bash -git add packages/eventsourcing-testing-contracts/ -git commit -m "test(eventsourcing-testing-contracts): add subscribeAll() contract tests" -``` - ---- - -## Task 3: Implement subscribeAll() in Postgres Store - -**Files:** - -- Modify: `packages/eventsourcing-store-postgres/src/sqlEventStore.ts` -- Modify: `packages/eventsourcing-store-postgres/src/index.test.ts` - -**Step 1: Add subscribeAll implementation to Postgres store** - -In `packages/eventsourcing-store-postgres/src/sqlEventStore.ts`, find the `buildSqlEventStore` function (around line 402). Add a helper function before it: - -```typescript -/** - * Subscribe to all events from all streams (live-only) - * Leverages NotificationListener which broadcasts all events - */ -const subscribeToAllStreams = ( - notificationListener: Readonly<{ - readonly notifications: Stream.Stream< - { readonly streamId: EventStreamId; readonly payload: NotificationPayload }, - EventStoreError, - never - >; - }> -) => - pipe( - notificationListener.notifications, - Stream.map((notification) => ({ - position: { - streamId: notification.streamId, - eventNumber: notification.payload.eventNumber, - } as EventStreamPosition, - event: notification.payload.eventPayload, - })) - ); -``` - -**Step 2: Add subscribeAll to the EventStore object** - -In the `buildSqlEventStore` function, find where the EventStore object is created (around line 450). Add `subscribeAll` after `subscribe`: - -```typescript -const eventStore: EventStore = { - append: /* existing */, - read: /* existing */, - subscribe: /* existing */, - subscribeAll: () => - Effect.succeed( - pipe( - subscribeToAllStreams(notificationListener), - Stream.mapError( - eventStoreError.read('*', 'Failed to subscribe to all streams') - ) - ) - ), -}; -``` - -**Step 3: Run Postgres store tests** - -Run: `cd packages/eventsourcing-store-postgres && bun test` -Expected: Tests pass (existing tests still work) - -**Step 4: Add contract test to Postgres tests** - -In `packages/eventsourcing-store-postgres/src/index.test.ts`, add the contract test at the end: - -```typescript -import { subscribeAllContract } from '@codeforbreakfast/eventsourcing-testing-contracts'; - -// At the end of the file, after existing tests -subscribeAllContract('PostgresEventStore', makeTestStore); -``` - -**Step 5: Run tests with contract** - -Run: `cd packages/eventsourcing-store-postgres && bun test` -Expected: All tests pass including new contract tests - -**Step 6: Commit Postgres implementation** - -```bash -git add packages/eventsourcing-store-postgres/ -git commit -m "feat(eventsourcing-store-postgres): implement subscribeAll() using NotificationListener" -``` - ---- - -## Task 4: Implement subscribeAll() in InMemory Store - -**Files:** - -- Modify: `packages/eventsourcing-store-inmemory/src/lib/inMemoryEventStore.ts` -- Modify: `packages/eventsourcing-store-inmemory/src/lib/inMemoryEventStore.test.ts` - -**Step 1: Add PubSub to InMemory store state** - -In `packages/eventsourcing-store-inmemory/src/lib/inMemoryEventStore.ts`, find the `State` interface (around line 10). Add `allEventsPubSub` field: - -```typescript -import { - Effect, - HashMap, - Layer, - ParseResult, - Ref, - Schema, - Sink, - Stream, - PubSub, - pipe, -} from 'effect'; - -interface State { - readonly streams: HashMap.HashMap< - EventStreamId, - readonly { readonly eventNumber: EventNumber; readonly payload: string }[] - >; - readonly allEventsPubSub: PubSub.PubSub<{ - readonly position: EventStreamPosition; - readonly event: string; - }>; -} -``` - -**Step 2: Initialize PubSub in make function** - -Find the `make` function in the Layer. Update it to create the PubSub: - -```typescript -make: Effect.gen(function* () { - const allEventsPubSub = yield* PubSub.unbounded<{ - readonly position: EventStreamPosition; - readonly event: string; - }>(); - - const state = yield* Ref.make({ - streams: HashMap.empty(), - allEventsPubSub, - }); - - // ... rest of implementation -}), -``` - -**Step 3: Publish to PubSub when events are appended** - -Find the `append` implementation in the Sink. After events are added to the stream, publish to PubSub: - -```typescript -return Sink.forEach( - (event) => - Effect.gen(function* () { - const currentState = yield* Ref.get(state); - const stream = HashMap.get(currentState.streams, to.streamId).pipe( - Effect.map((s) => s ?? []) - ); - - const events = yield* stream; - const lastEventNumber = events.length === 0 ? -1 : events[events.length - 1]!.eventNumber; - - if (lastEventNumber !== to.eventNumber - 1) { - return yield* new ConcurrencyConflictError({ - streamId: to.streamId, - expected: to.eventNumber, - actual: lastEventNumber + 1, - }); - } - - const newEvent = { - eventNumber: to.eventNumber, - payload: event, - }; - - const newEvents = [...events, newEvent]; - - yield* Ref.update(state, (s) => ({ - ...s, - streams: HashMap.set(s.streams, to.streamId, newEvents), - })); - - // Publish to allEventsPubSub - yield* PubSub.publish(currentState.allEventsPubSub, { - position: { streamId: to.streamId, eventNumber: to.eventNumber }, - event, - }); - - return { - streamId: to.streamId, - eventNumber: to.eventNumber + 1, - } as EventStreamPosition; - }) -); -``` - -**Step 4: Implement subscribeAll method** - -Add the `subscribeAll` method to the returned EventStore object: - -```typescript -const eventStore: EventStore = { - append: /* existing */, - read: /* existing */, - subscribe: /* existing */, - subscribeAll: () => - Effect.gen(function* () { - const currentState = yield* Ref.get(state); - return Stream.fromPubSub(currentState.allEventsPubSub); - }).pipe( - Effect.mapError( - eventStoreError.read('*', 'Failed to subscribe to all streams') - ) - ), -}; -``` - -**Step 5: Run InMemory store tests** - -Run: `cd packages/eventsourcing-store-inmemory && bun test` -Expected: Existing tests pass - -**Step 6: Add contract test** - -In `packages/eventsourcing-store-inmemory/src/lib/inMemoryEventStore.test.ts`, add: - -```typescript -import { subscribeAllContract } from '@codeforbreakfast/eventsourcing-testing-contracts'; - -// At the end of the file -subscribeAllContract('InMemoryEventStore', InMemoryEventStoreLive); -``` - -**Step 7: Run tests with contract** - -Run: `cd packages/eventsourcing-store-inmemory && bun test` -Expected: All tests pass including contract tests - -**Step 8: Commit InMemory implementation** - -```bash -git add packages/eventsourcing-store-inmemory/ -git commit -m "feat(eventsourcing-store-inmemory): implement subscribeAll() using PubSub" -``` - ---- - -## Task 5: Implement subscribeAll() in Filesystem Store - -**Files:** - -- Modify: `packages/eventsourcing-store-filesystem/src/lib/fileSystemEventStore.ts` -- Modify: `packages/eventsourcing-store-filesystem/src/lib/fileSystemEventStore.test.ts` - -**Step 1: Add PubSub import and state field** - -In `packages/eventsourcing-store-filesystem/src/lib/fileSystemEventStore.ts`, add PubSub to imports and create state interface: - -```typescript -import { Effect, Layer, ParseResult, Schema, Sink, Stream, PubSub, Ref, pipe } from 'effect'; - -interface FileSystemStoreState { - readonly allEventsPubSub: PubSub.PubSub<{ - readonly position: EventStreamPosition; - readonly event: string; - }>; -} -``` - -**Step 2: Initialize PubSub in Layer** - -Find the FileSystemEventStoreLive Layer and add PubSub initialization: - -```typescript -export const FileSystemEventStoreLive = (baseDir: string) => - Layer.effect( - FileSystemEventStore, - Effect.gen(function* () { - const allEventsPubSub = yield* PubSub.unbounded<{ - readonly position: EventStreamPosition; - readonly event: string; - }>(); - - const state = yield* Ref.make({ - allEventsPubSub, - }); - - // ... rest of implementation - }) - ); -``` - -**Step 3: Publish to PubSub when appending events** - -Find the `append` implementation. After writing the file, publish to PubSub: - -```typescript -return Sink.forEach( - (event) => - Effect.gen(function* () { - // ... existing file write logic ... - - // After successful write, publish to PubSub - const currentState = yield* Ref.get(state); - yield* PubSub.publish(currentState.allEventsPubSub, { - position: { streamId: to.streamId, eventNumber: to.eventNumber }, - event, - }); - - return { - streamId: to.streamId, - eventNumber: to.eventNumber + 1, - } as EventStreamPosition; - }) -); -``` - -**Step 4: Implement subscribeAll method** - -Add `subscribeAll` to the EventStore object: - -```typescript -const eventStore: EventStore = { - append: /* existing */, - read: /* existing */, - subscribe: /* existing */, - subscribeAll: () => - Effect.gen(function* () { - const currentState = yield* Ref.get(state); - return Stream.fromPubSub(currentState.allEventsPubSub); - }).pipe( - Effect.mapError( - eventStoreError.read('*', 'Failed to subscribe to all streams') - ) - ), -}; -``` - -**Step 5: Run Filesystem store tests** - -Run: `cd packages/eventsourcing-store-filesystem && bun test` -Expected: Existing tests pass - -**Step 6: Add contract test** - -In `packages/eventsourcing-store-filesystem/src/lib/fileSystemEventStore.test.ts`, add: - -```typescript -import { subscribeAllContract } from '@codeforbreakfast/eventsourcing-testing-contracts'; - -// At the end of the file -subscribeAllContract('FileSystemEventStore', FileSystemEventStoreLive('/tmp/test-fs-store')); -``` - -**Step 7: Run tests with contract** - -Run: `cd packages/eventsourcing-store-filesystem && bun test` -Expected: All tests pass including contract tests - -**Step 8: Commit Filesystem implementation** - -```bash -git add packages/eventsourcing-store-filesystem/ -git commit -m "feat(eventsourcing-store-filesystem): implement subscribeAll() using PubSub" -``` - ---- - -## Task 6: Run Full Test Suite - -**Step 1: Run all tests across the monorepo** - -Run: `turbo test` -Expected: All tests pass across all packages - -**Step 2: Run build to ensure no TypeScript errors** - -Run: `turbo build` -Expected: Clean build with no errors - -**Step 3: Commit if any fixes were needed** - -If you had to fix anything, commit those changes: - -```bash -git add . -git commit -m "fix: resolve test/build issues" -``` - ---- - -## Task 7: Create Changeset - -**Files:** - -- Create: `.changeset/subscribeall-feature.md` - -**Step 1: Create changeset file** - -Create `.changeset/subscribeall-feature.md`: - -````markdown ---- -'@codeforbreakfast/eventsourcing-store': minor -'@codeforbreakfast/eventsourcing-store-postgres': minor -'@codeforbreakfast/eventsourcing-store-inmemory': minor -'@codeforbreakfast/eventsourcing-store-filesystem': minor -'@codeforbreakfast/eventsourcing-testing-contracts': minor ---- - -Add subscribeAll() method to EventStore for live cross-stream event subscriptions - -**New Feature:** - -All EventStore implementations now support `subscribeAll()`, which provides live event subscriptions across all streams. This is essential for EventBus and process manager implementations. - -**Usage:** - -```typescript -const store: EventStore = /* ... */; - -const allEventsStream = yield* store.subscribeAll(); - -yield* Stream.runForEach(allEventsStream, ({ position, event }) => { - console.log(`Event from ${position.streamId}:`, event); -}); -``` -```` - -**Key characteristics:** - -- **Live-only**: Only events committed after subscription starts are delivered -- **Cross-stream**: Receives events from all streams -- **Typed**: Each event includes its `EventStreamPosition` (streamId + eventNumber) -- **Best-effort**: No guaranteed delivery or global ordering - -**Implementation details:** - -- **Postgres**: Uses existing LISTEN/NOTIFY infrastructure -- **InMemory**: Internal PubSub broadcasts events on append -- **Filesystem**: Internal PubSub broadcasts events on file write - -**Breaking changes:** None - this is a backward-compatible addition to the EventStore interface. - -```` - -**Step 2: Commit changeset** - -```bash -git add .changeset/subscribeall-feature.md -git commit -m "chore: add changeset for subscribeAll() feature" -```` - ---- - -## Verification - -Run these commands to verify the complete implementation: - -```bash -# Full test suite -turbo test - -# Full build -turbo build - -# Check git status -git status - -# View commits -git log --oneline -10 -``` - -Expected results: - -- All tests passing -- Clean build -- 8-9 commits on feat/subscribe-all branch -- Ready for PR - ---- - -## Next Steps - -1. Push branch: `git push -u origin feat/subscribe-all` -2. Create PR with title: `feat: add subscribeAll() to EventStore for live cross-stream subscriptions` -3. Link to hp-8 in PR description -4. Request review diff --git a/docs/plans/eventsourcing-server-sequence-diagram.md b/docs/plans/eventsourcing-server-sequence-diagram.md index ddb0d89f..1b799c7f 100644 --- a/docs/plans/eventsourcing-server-sequence-diagram.md +++ b/docs/plans/eventsourcing-server-sequence-diagram.md @@ -273,7 +273,7 @@ graph TB ### Critical Design Points 1. **EventStore.subscribeAll() is required** - - New method added to EventStore interface (hp-8) + - New method added to EventStore interface - Live-only, no global event number - All EventStore implementations must support it - EventBus uses this exclusively diff --git a/packages/eventsourcing-server/README.md b/packages/eventsourcing-server/README.md index 3b175716..1ff5ea1d 100644 --- a/packages/eventsourcing-server/README.md +++ b/packages/eventsourcing-server/README.md @@ -43,6 +43,6 @@ const program = Effect.gen(function* () { ## Future Components -- CommandDispatcher (hp-5) -- StoreSubscriptionManager (hp-6) +- CommandDispatcher +- StoreSubscriptionManager - ProtocolBridge