Skip to main content

max / quasi

3.2 KB · 91 lines History Blame Raw
1 //! The embedded store's migration runner.
2 //!
3 //! <!-- wiki: quasi-overview -->
4 //!
5 //! # Why this is a quasi crate
6 //!
7 //! The admission test in `CONTRIBUTING.md` asks which two implementations a
8 //! boundary has. This one is not answering that question, because it is not a
9 //! boundary over a vendor: it is the thing `sqlx::migrate!` used to be and
10 //! nothing replaced. When goingson and Balanced Breakfast moved to rusqlite on
11 //! 2026-08-07 they each hand-wrote a runner, and a third would have been
12 //! written by the first app the scaffolder generated. Two copies existing is
13 //! the evidence; the crate is what stops the third.
14 //!
15 //! It is a quasi crate rather than a makeover one on the audience rule settled
16 //! 2026-08-08: a migration runner tied to our ledger conventions is something
17 //! you take once you are on the stack, not something another developer picks up
18 //! on its own. There are good standalone migration crates and this is not
19 //! competing with them.
20 //!
21 //! # The two halves
22 //!
23 //! They share no dependency, and that is the point rather than tidiness.
24 //!
25 //! - [`runtime`](crate::migrate) reads the ledger and applies what is missing.
26 //! An app links it.
27 //! - [`build`](crate::embed) turns a directory of `.sql` files into a table
28 //! compiled into the binary. An app's `build.rs` links it, with
29 //! `default-features = false`, so a build script does not pull
30 //! `libsqlite3-sys` into a second compile for the host.
31 //!
32 //! ```toml
33 //! [dependencies]
34 //! quasi-store = { path = "../../../quasi/crates/quasi-store" }
35 //!
36 //! [build-dependencies]
37 //! quasi-store = { path = "../../../quasi/crates/quasi-store", default-features = false, features = ["build"] }
38 //! ```
39 //!
40 //! # Using it
41 //!
42 //! `build.rs`, once. Not compiled as a doctest: the two halves are separate
43 //! features and a doctest gets one set of them, so an example naming both would
44 //! have to turn on the driver this half exists to avoid.
45 //!
46 //! ```ignore
47 //! fn main() {
48 //! quasi_store::embed::from_dir("../../migrations").expect("migrations");
49 //! }
50 //! ```
51 //!
52 //! Then the crate, once:
53 //!
54 //! ```ignore
55 //! quasi_store::migrations!();
56 //!
57 //! let mut conn = rusqlite::Connection::open("app.db")?;
58 //! quasi_store::Migrator::new(MIGRATIONS).run(&mut conn)?;
59 //! ```
60 //!
61 //! # What it does not do
62 //!
63 //! Connections, pools, queries, or anything resembling a query layer. quasi
64 //! owns which driver is configured and how migrations run, and queries stay
65 //! written against `rusqlite` directly — the same line the README draws around
66 //! `sqlx`, for the same reason. There is no Postgres half here and there should
67 //! not be one: `sqlx` ships its own migrator and it is one of the reasons to
68 //! use `sqlx`.
69
70 #[cfg(feature = "build")]
71 pub mod embed;
72
73 #[cfg(feature = "runtime")]
74 pub mod migrate;
75
76 #[cfg(feature = "runtime")]
77 pub use crate::migrate::{MigrateError, Migration, Migrator};
78
79 /// Include the table [`embed::from_dir`] generated.
80 ///
81 /// A macro rather than a documented `include!` line, so the path the build
82 /// script writes to is named in one place. Expands to a
83 /// `static MIGRATIONS: &[Migration]`.
84 #[cfg(feature = "runtime")]
85 #[macro_export]
86 macro_rules! migrations {
87 () => {
88 include!(concat!(env!("OUT_DIR"), "/quasi_migrations.rs"));
89 };
90 }
91