Commit b042d15
feat: Phase 1 — Core framework + KurrentDB integration (#1)
* chore: scaffold multi-module Go project structure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add shared primitives — StreamName, Metadata, errors
Implements StreamName (typed string with Category/ID split on first dash),
ExpectedVersion and ExpectedState constants, Metadata map with immutable
WithCorrelationID/WithCausationID helpers, and sentinel error values.
All behaviour is covered by six unit tests (TDD green).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add TypeMap for bidirectional event type registration
Implements TypeMap in core/codec with Register[E], TypeName, and
NewInstance. Uses reflect.Type as the key for the forward map so both
pointer and value forms of a type resolve to the same entry.
Concurrent-safe via sync.RWMutex. Idempotent registration; errors on
conflicts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add Codec interface and JSON implementation
Introduce the Codec interface (Encode/Decode) and JSONCodec backed by
TypeMap for event type resolution. Codec.Decode dereferences the
*T returned by TypeMap.NewInstance so callers always receive a value,
not a pointer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add Aggregate[S] domain type with fold, apply, and guards
Implements the generic Aggregate[S] type that tracks state via a fold
function, accumulates pending changes for optimistic concurrency, and
provides EnsureNew/EnsureExists guards for creation and mutation commands.
Includes all 10 mirrored tests from the .NET Eventuous aggregate test suite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add EventStore/Reader/Writer interfaces and stream types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add in-memory EventStore for testing
Implements a thread-safe in-memory EventStore (memstore.Store) under
core/test/memstore/ that satisfies the store.EventStore interface.
Provides optimistic-concurrency checks, backwards reads, stream
deletion, and truncation — no infrastructure required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add store conformance test suite and validate with in-memory store
Implements storetest.RunAll with sub-suites for Append, Read, ReadBackwards,
OptimisticConcurrency, StreamExists, DeleteStream, and TruncateStream. Fixes
a uint64 overflow bug in memstore.ReadEventsBackwards where passing ^uint64(0)
as the start sentinel was cast to int(-1), bypassing the bounds clamp.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add LoadState, LoadAggregate, StoreAggregate functions
Implements store helper functions for the functional command service and
aggregate command service. LoadState reads all events from a stream,
folds them into state, and handles IsNew/IsExisting/IsAny semantics.
LoadAggregate wraps LoadState to reconstruct an Aggregate[S].
StoreAggregate appends pending changes with optimistic concurrency control.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(core): add shared Booking test domain mirroring .NET Eventuous.Sut.Domain
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add functional command service with handler registration
Implements Service[S] with On[C,S]() registration, Handle() pipeline
(load state → act → append → fold), and 8 tests covering IsNew/IsExisting/IsAny,
no-op, and handler-not-found scenarios.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(core): add aggregate command service
Implement AggregateService[S] and OnAggregate registration in core/command/aggservice.go.
The service loads an aggregate via store.LoadAggregate, enforces IsNew/IsExisting/IsAny guards,
invokes the handler's Act function (which records events via agg.Apply), and persists changes
with store.StoreAggregate. Covers the full test matrix: OnNew success/conflict, OnExisting
success/missing, OnAny new and existing streams, handler not found, and no-op (no Apply called).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add subscription framework with middleware chain
Implements the subscription package with Subscription interface,
EventHandler/HandlerFunc, ConsumeContext, and Middleware chain
including WithLogging, WithConcurrency, and WithPartitioning.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add checkpoint committer with gap detection
Implements CheckpointStore interface and CheckpointCommitter that safely
batches checkpoint writes while ensuring no position is committed until
all prior sequence numbers have been processed, preventing data loss
under concurrent/out-of-order event processing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(core): add subscription conformance test suite
Adds subtest package with TestConsumeProducedEvents and TestResumeFromCheckpoint
exported test functions any subscription implementation can call to verify conformance,
along with collectingHandler and memCheckpointStore helpers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(kurrentdb): implement EventStore with conformance tests passing
Add KurrentDB event store implementation that satisfies the
store.EventStore interface using the KurrentDB Go client. All 21
conformance tests pass against a live KurrentDB instance.
Also update storetest to use per-process unique stream names and
export a NewCodec helper so external store implementations can
reuse the conformance test codec.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(kurrentdb): use testcontainers for integration tests
Replace hardcoded localhost:2113 connection with testcontainers-go to
start an ephemeral EventStoreDB container per test run, removing the
docker-compose dependency for tests. Also fix isStreamNotFound to add
a string-based fallback for errors from rs.Recv() that don't unwrap
into *kurrentdb.Error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(kurrentdb): add catch-up subscription for stream and $all
Implements CatchUp subscription that consumes events from KurrentDB
via either a single stream or the $all stream. Supports checkpoint-
based resumption, middleware chaining, server-side filters, and
link resolution. Includes integration tests for stream consumption,
$all consumption, and checkpoint resume.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(kurrentdb): add persistent subscription with ack/nack
Implement server-managed persistent subscriptions for KurrentDB with
auto-creation of subscription groups, ack on handler success, and nack
with retry on handler failure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(otel): add command tracing and subscription tracing middleware
Implements TracedCommandHandler wrapping any CommandHandler[S] with OTel
span creation and duration/error metrics, and TracingMiddleware providing
a subscription.Middleware that creates a span per consumed event.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(kurrentdb): add subscription conformance tests and enhance subtest package
Wire the subscription conformance suite from core/test/subtest/ to the
KurrentDB catch-up subscription. Export TestEvent type and add NewCodec()
to the subtest package so external modules can build a matching codec.
Add per-process runID to stream names for test isolation against
persistent stores.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(otel): add command tracing and subscription tracing middleware
Implements TracedCommandHandler wrapping command.CommandHandler with OTel spans,
duration histogram, and error counter; TracingMiddleware for subscriptions adds
per-event spans with OK/error status. Adds missing error-path test and success
span.SetStatus(Ok) to subscription middleware.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(kurrentdb): add end-to-end integration test
Exercises the full stack: codec → KurrentDB store → functional command
service (BookRoom + RecordPayment) → catch-up subscription, using the
shared testdomain types and a real KurrentDB container.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: add GitHub Actions CI pipeline
- Lint: go vet + gofmt check across all modules
- Build: parallel build of core, kurrentdb, otel
- Test core: unit tests with race detector (no infra needed)
- Test otel: unit tests with race detector (no infra needed)
- Test kurrentdb: integration tests via testcontainers (Docker)
Runs on PRs and pushes to main/dev.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix gofmt formatting in 6 files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add Apache 2.0 license headers to all Go files
Adds copyright and license headers to all 55 .go files matching
the .NET Eventuous convention. Also adds the full LICENSE file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add README with quick start, architecture, and examples
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address code review feedback
- WithConcurrency: block until handler completes and return its error,
preventing checkpoint/ack before processing finishes
- TruncateStream: validate expected version against data stream before
writing metadata, honoring optimistic concurrency
- StoreAggregate: ClearChanges now advances originalVersion so the
aggregate can be reused without stale version conflicts
- CI: bump Go version to 1.25 to match kurrentdb/otel module requirements
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression tests for code review fixes
- TestWithConcurrency_ReturnsHandlerError: verify handler errors are
propagated, not swallowed (catches fire-and-forget bug)
- TestWithConcurrency_BlocksUntilHandlerCompletes: verify HandleEvent
blocks until the inner handler finishes (catches premature ack bug)
- TestClearChanges_AdvancesVersion: verify ClearChanges bumps
OriginalVersion so the aggregate is reusable
- TestClearChanges_ThenApply: verify Apply after ClearChanges produces
correct versions for a second command cycle
- TestStoreAggregate_AggregateReusableAfterStore: verify the aggregate
can be stored twice without ErrOptimisticConcurrency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix gofmt formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: flesh out CLAUDE.md with architecture, conventions, and test patterns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: skip pipeline for docs-only and non-code changes
Add paths-ignore to the push trigger (was only on pull_request).
Also exclude LICENSE and .gitignore from triggering builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 parent 5b6700a commit b042d15
File tree
67 files changed
+7572
-0
lines changed- .github/workflows
- core
- aggregate
- codec
- command
- store
- subscription
- test
- memstore
- storetest
- subtest
- testdomain
- kurrentdb
- otel
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
67 files changed
+7572
-0
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
0 commit comments