Thanks for your interest in contributing! Hyperbee.Migrations is an OSS .NET migrations library with first-class support for Aerospike, Couchbase, MongoDB, OpenSearch, and PostgreSQL. Contributions of all sizes are welcome - bug reports, doc fixes, new migrations for samples, and full provider implementations.
- Documentation site: https://stillpoint-software.github.io/hyperbee.migrations/
- Issue tracker: https://github.com/Stillpoint-Software/hyperbee.migrations/issues
- Code of conduct: https://github.com/Stillpoint-Software/.github (org-wide CoC applies to this repo)
There are three primary paths:
Open a GitHub issue and include:
- Minimal repro steps (a failing test is ideal)
- Expected vs. actual behavior
- Version of
Hyperbee.Migrationsand the relevant provider package - .NET runtime version (
dotnet --info) - OS and version
Open an issue first to discuss the idea before writing code. Substantial features
(new public API surface, new provider, behavior changes that affect existing users)
require an Architecture Decision Record. See docs/decisions/ for the existing ADRs
and the format we use.
See "Development setup" and "Branching model" below. Small fixes (typos, doc clarifications, obvious bugs) do not need a prior issue.
You need:
- .NET 8, 9, and 10 SDKs installed side-by-side. The solution multi-targets all three and tests run on each TFM.
- Docker engine, for Testcontainers-based integration tests.
- Optional: local installs of Aerospike, Couchbase, MongoDB, OpenSearch, or PostgreSQL. Testcontainers will spin these up automatically when integration tests run, so a local install is only useful if you want to inspect state.
git clone https://github.com/Stillpoint-Software/hyperbee.migrations.git
cd hyperbee.migrations
dotnet restore Hyperbee.Migrations.slnx
dotnet build Hyperbee.Migrations.slnx -c Release
dotnet test Hyperbee.Migrations.slnx -c Release
Integration tests are gated behind the INTEGRATIONS compilation symbol. Enable
them with the EnableIntegrationTests MSBuild property:
dotnet test tests/Hyperbee.Migrations.Integration.Tests/Hyperbee.Migrations.Integration.Tests.csproj -c Release -p:EnableIntegrationTests=true
If you only have Docker images for some providers, scope the run with an environment variable:
HYPERBEE_TESTS_PROVIDERS_ONLY="Postgres,MongoDb"
Three tiers, per ADR-0031. Know which one you are adding to.
| Tier | What it is | Where | CI |
|---|---|---|---|
| Unit | No I/O | Hyperbee.Migrations.Tests, .Squash.Tests, .Cli.Tests |
Every PR, net8/9/10 |
| Wire | Real client + real serializer, faked transport | Same projects (*LedgerWireTests) |
Every PR, net8/9/10 |
| Integration | Real containers | Hyperbee.Migrations.Integration.Tests |
See below |
Reach for the wire tier first. A substituted client never serializes, so it
cannot catch a malformed request, a wrong field name, or a bad type inference —
and a container is a slow, flaky way to learn the same thing. InMemoryConnection
(OpenSearch) or a rendered filter compared against serializer output (MongoDB,
Couchbase) runs in milliseconds with no Docker. Both defects behind
ADR-0029 were
caught this way after the fact; they should have been caught this way first.
Every integration class carries exactly one category, and everything runs automatically somewhere:
-
[TestCategory("Gating")]— 115 tests, on every PR, ~2 minutes. Qualifies only if it uses just the sharedInitializeTestContainerscontainer, builds no Docker image, is not multi-node, finishes its provider's cell in about a minute, and has been observed green. -
[TestCategory("LocalOnly")]— 12 tests, on demand via the Heavy Integration Tests workflow. These build Docker images, or need a 3-node cluster, or take 5+ minutes. Multi-node OpenSearch additionally carries[TestCategory("MultiNode")]and has its own manual workflow. -
[TestCategory("Flaky")]— 3 tests, never automated. Reserved for tests that assert on a race and can fail with no defect present. Do not add to this list without recording the measurement and the intended fix; it is debt, not a category for "tests that annoy me".
So: PR = fast and must be green. Everything else is on-demand today, because the heavy suite is not yet dependably green enough to automate — see ADR-0031.
CI selects positively on TestCategory=Gating, and a guard step fails the build if
a matrix cell matches zero tests. Do not "fix" a red gating job by narrowing the
filter — that is how the repo previously ended up with no integration coverage at all
and nobody noticing for three months.
| Path | Purpose |
|---|---|
src/Hyperbee.Migrations/ |
Core library (runner, lock, options, migration discovery) |
src/Hyperbee.Migrations.Providers.*/ |
Per-provider implementations |
runners/Hyperbee.MigrationRunner.*/ |
Per-provider standalone runner executables |
runners/samples/ |
Working samples per provider |
tests/ |
Unit and integration tests |
docs/site/ |
Jekyll documentation site (just-the-docs theme) |
docs/decisions/ |
Architecture Decision Records (ADRs) |
docs/guides/ |
Operator guides |
docs/plans/active/ |
In-flight implementation plans |
We use trunk-based GitHub flow:
- Branch from
main. - Branch naming:
devs/<your-name>/<short-description>for exampledevs/jdoe/postgres-jsonb-support. - Open the PR against
main. CI runs unit + wire tests on net8, net9, and net10, plus theGatingintegration tier (Postgres, MongoDB, OpenSearch, Aerospike, multi-provider) on net10. TheLocalOnlysuite runs after merge — see "Test tiers" above. - Squash-merge is the default.
- C# style: spaced parens, e.g.
AddPostgresMigrations( options => ... ). - Do not use the
global::prefix. Resolve namespace conflicts withusingaliases instead. - No emojis in code or docs unless explicitly requested.
- Tests use MSTest, with FluentAssertions where it improves readability.
- Changes to the public API surface require an added or updated ADR in
docs/decisions/. - Run
dotnet formatbefore pushing. The format bot enforces this in CI and will push a formatting commit if you forget.
- Add a
[Migration( version )] class Foo : Migrationunderrunners/samples/Hyperbee.Migrations.<Provider>.Samples/Migrations/. - For resource-driven migrations, drop a
.sql,.statements.json, or.statementsfile underResources/<version>-<Name>/. - Mark each resource file as
<EmbeddedResource>in the sample's.csproj. - Add an integration test in
tests/Hyperbee.Migrations.Integration.Tests/<Provider>RunnerTest.csthat verifies the migration runs end-to-end.
New providers are welcome but invasive - please open an issue first to discuss architectural fit before starting work.
The expected shape:
- Implement
IMigrationRecordStorefor the provider (internalclass is fine -- it ships through factory-delegate DI registration). - Add a
{Provider}MigrationOptionsclass. See ADR-0006 for options inheritance. - Statement parsing uses Parlot per ADR-0001.
- Locking is provider-native per ADR-0005.
- Tests: add a unit-test parser file under
tests/Hyperbee.Migrations.Tests/and an integration test class undertests/Hyperbee.Migrations.Integration.Tests/. - Ship a sample under
runners/samples/Hyperbee.Migrations.<Provider>.Samples/. - Add a runner project under
runners/Hyperbee.MigrationRunner.<Provider>/including aDockerfile. - Add a site doc page at
docs/site/<provider>.mdfollowing the canonical shape used by existing provider pages. - Add a package README following the canonical short shape used by existing provider packages. Include the standard Multi-provider hosts section pointing at the typed runner + the operator guide.
Every Add{Provider}Migrations extension must:
- Register the concrete options factory with
TryAddSingleton(idempotent under duplicate-registration scenarios). - Register the concrete record store via factory delegate with
TryAddSingletonso the type can stayinternal. Example:services.TryAddSingleton<{Provider}RecordStore>( sp => new {Provider}RecordStore( sp.GetRequiredService<{NativeClient}>(), sp.GetRequiredService<{Provider}MigrationOptions>(), sp.GetRequiredService<ILogger<{Provider}RecordStore>>() ) );
- Register
{Provider}MigrationRunnervia factory delegate withTryAddSingleton. The runner subclass takes the concrete record store + options +ILoggerFactoryand forwards to the baseMigrationRunnerctor. - Wire the legacy aliases through
services.RegisterBaseAliases(...)exactly once at the end:services.RegisterBaseAliases( "{Provider}", sp => sp.GetRequiredService<{Provider}MigrationOptions>(), sp => sp.GetRequiredService<{Provider}RecordStore>(), sp => sp.GetRequiredService<{Provider}MigrationRunner>() );
- Add a multi-provider integration test under
tests/Hyperbee.Migrations.Integration.Tests/that pairs the new provider with at least one existing provider; assert each runner's ledger is independent and the baseMigrationRunnerresolution throws.
The RegisterBaseAliases helper handles the single-vs-multi-provider behavior
for you: first provider installs the legacy aliases; subsequent providers
replace them with throwing factories that name every offending provider so
multi-provider hosts cannot silently shadow. See ADR-0023 and
multi-provider-hosts.md.
Do NOT open a public issue for security vulnerabilities.
If the org publishes a security policy at https://github.com/Stillpoint-Software/.github, follow it. Otherwise email <TODO: maintainer contact> with the details and we will respond privately before any public disclosure.