Improve dependency version flexibility for eventcore-sqlite consumers #378

Closed
opened 2026-04-27 07:34:41 -07:00 by jwilger-ai-bot · 0 comments
jwilger-ai-bot commented 2026-04-27 07:34:41 -07:00 (Migrated from github.com)

Problem

eventcore-sqlite currently forces dependency constraints on consumers in two ways:

  1. Default-on encryption feature pulls in rusqlite/bundled-sqlcipher-vendored-openssl, statically linking a vendored SQLCipher (a SQLite fork) and OpenSSL. Any downstream crate that also pulls in rusqlite/sqlite/openssl native code can hit symbol conflicts at link time — not just type incompatibility.
  2. No re-export of rusqlite, so consumers who want to construct connections must declare their own rusqlite dependency, creating the possibility of a version mismatch with whatever eventcore-sqlite was built against. In practice this has caused at least one downstream project to consider downgrading rusqlite to align with eventcore-sqlite.

eventcore-postgres does not have this issue (sqlx + rustls is pure Rust, no native linking).

Proposed Changes

1. Make encryption non-default

[features]
default = []
encryption = ["rusqlite/bundled-sqlcipher-vendored-openssl"]

Consumers who want encryption opt in. Everyone else gets to choose their own rusqlite features (or use system sqlite). This sidesteps the worst class of native-link conflicts entirely for consumers who don't need encryption.

This is technically a breaking change for the default-feature surface; should land in a 0.x minor bump with a clear migration note.

2. Re-export rusqlite from eventcore-sqlite

// eventcore-sqlite/src/lib.rs
pub use rusqlite;

Consumers write use eventcore_sqlite::rusqlite::Connection and never declare rusqlite themselves in the common case. There is nothing to mismatch.

3. Add a "bring-your-own-connection" constructor

Currently eventcore-sqlite constructs the connection internally. Add a constructor that accepts an already-constructed rusqlite::Connection (or pool) so consumers who need fine-grained control over connection setup (custom pragmas, attached databases, encryption keys) can wire it themselves. Keep the convenience constructor for the simple case.

The Rust type system enforces version compatibility for free here: if the consumer's rusqlite::Connection type doesn't unify with ours, they get a compile error at the call site, not a runtime failure.

4. Document the version compatibility contract

Add a README section explicitly stating:

  • Which rusqlite version each eventcore-sqlite release is built against
  • That consumers should prefer eventcore_sqlite::rusqlite re-export rather than declaring rusqlite directly
  • That cargo's version unification handles compatibility automatically when version ranges overlap, and produces a clear compile error when they don't
  • The trade-off of opting into the encryption feature (vendored OpenSSL/SQLCipher native linking)

5. Use permissive version ranges

Confirm rusqlite = "0.32" (not "=0.32.x") so cargo can unify across the full 0.32.* range. Already true today; preserve as a convention.

Generalization (Future Consideration)

The same pattern applies to any backend crate wrapping a third-party dep:

  • Re-export the dep
  • Avoid default-on features that introduce native-linking constraints
  • Provide a BYOC constructor for advanced consumers
  • Document the version pin

This issue is scoped to eventcore-sqlite. If eventcore-postgres ever grows native-linked features, the same conventions should apply there.

Acceptance Criteria

  • encryption is no longer a default feature of eventcore-sqlite
  • eventcore-sqlite re-exports rusqlite at the crate root
  • A constructor exists that accepts a rusqlite::Connection from the consumer
  • README documents the version-compatibility contract and the encryption opt-in
  • Migration note in the changelog for the default-feature change
## Problem `eventcore-sqlite` currently forces dependency constraints on consumers in two ways: 1. **Default-on `encryption` feature** pulls in `rusqlite/bundled-sqlcipher-vendored-openssl`, statically linking a vendored SQLCipher (a SQLite fork) and OpenSSL. Any downstream crate that also pulls in rusqlite/sqlite/openssl native code can hit symbol conflicts at link time — not just type incompatibility. 2. **No re-export of `rusqlite`**, so consumers who want to construct connections must declare their own `rusqlite` dependency, creating the possibility of a version mismatch with whatever `eventcore-sqlite` was built against. In practice this has caused at least one downstream project to consider downgrading `rusqlite` to align with `eventcore-sqlite`. `eventcore-postgres` does not have this issue (sqlx + rustls is pure Rust, no native linking). ## Proposed Changes ### 1. Make `encryption` non-default ```toml [features] default = [] encryption = ["rusqlite/bundled-sqlcipher-vendored-openssl"] ``` Consumers who want encryption opt in. Everyone else gets to choose their own `rusqlite` features (or use system sqlite). This sidesteps the worst class of native-link conflicts entirely for consumers who don't need encryption. This is technically a breaking change for the default-feature surface; should land in a 0.x minor bump with a clear migration note. ### 2. Re-export `rusqlite` from `eventcore-sqlite` ```rust // eventcore-sqlite/src/lib.rs pub use rusqlite; ``` Consumers write `use eventcore_sqlite::rusqlite::Connection` and never declare `rusqlite` themselves in the common case. There is nothing to mismatch. ### 3. Add a "bring-your-own-connection" constructor Currently `eventcore-sqlite` constructs the connection internally. Add a constructor that accepts an already-constructed `rusqlite::Connection` (or pool) so consumers who need fine-grained control over connection setup (custom pragmas, attached databases, encryption keys) can wire it themselves. Keep the convenience constructor for the simple case. The Rust type system enforces version compatibility for free here: if the consumer's `rusqlite::Connection` type doesn't unify with ours, they get a compile error at the call site, not a runtime failure. ### 4. Document the version compatibility contract Add a README section explicitly stating: - Which `rusqlite` version each `eventcore-sqlite` release is built against - That consumers should prefer `eventcore_sqlite::rusqlite` re-export rather than declaring `rusqlite` directly - That cargo's version unification handles compatibility automatically when version ranges overlap, and produces a clear compile error when they don't - The trade-off of opting into the `encryption` feature (vendored OpenSSL/SQLCipher native linking) ### 5. Use permissive version ranges Confirm `rusqlite = "0.32"` (not `"=0.32.x"`) so cargo can unify across the full `0.32.*` range. Already true today; preserve as a convention. ## Generalization (Future Consideration) The same pattern applies to any backend crate wrapping a third-party dep: - Re-export the dep - Avoid default-on features that introduce native-linking constraints - Provide a BYOC constructor for advanced consumers - Document the version pin This issue is scoped to `eventcore-sqlite`. If `eventcore-postgres` ever grows native-linked features, the same conventions should apply there. ## Acceptance Criteria - [ ] `encryption` is no longer a default feature of `eventcore-sqlite` - [ ] `eventcore-sqlite` re-exports `rusqlite` at the crate root - [ ] A constructor exists that accepts a `rusqlite::Connection` from the consumer - [ ] README documents the version-compatibility contract and the encryption opt-in - [ ] Migration note in the changelog for the default-feature change
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jwilger/eventcore#378
No description provided.