{"id":37231584,"url":"https://github.com/kevlarr/jrny","last_synced_at":"2026-01-15T03:44:51.783Z","repository":{"id":52851057,"uuid":"244147204","full_name":"kevlarr/jrny","owner":"kevlarr","description":"A lightweight, compiled, zero-dependency PostgreSQL schema revision tool; just add SQL!","archived":false,"fork":false,"pushed_at":"2024-03-14T00:01:30.000Z","size":190,"stargazers_count":6,"open_issues_count":22,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-13T10:35:28.373Z","etag":null,"topics":["postgresql","revisions","schema","sql"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kevlarr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-01T12:42:20.000Z","updated_at":"2025-08-12T16:56:33.000Z","dependencies_parsed_at":"2024-05-02T20:49:21.482Z","dependency_job_id":"ec4241bc-7d0c-44f1-a851-74e6bf459e02","html_url":"https://github.com/kevlarr/jrny","commit_stats":null,"previous_names":["crudecomputer/jrny","kevlarr/jrny"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/kevlarr/jrny","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevlarr%2Fjrny","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevlarr%2Fjrny/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevlarr%2Fjrny/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevlarr%2Fjrny/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kevlarr","download_url":"https://codeload.github.com/kevlarr/jrny/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevlarr%2Fjrny/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28416120,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:38:59.149Z","status":"ssl_error","status_checked_at":"2026-01-14T08:38:43.588Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["postgresql","revisions","schema","sql"],"created_at":"2026-01-15T03:44:51.116Z","updated_at":"2026-01-15T03:44:51.772Z","avatar_url":"https://github.com/kevlarr.png","language":"Rust","readme":"# Journey\n\n*Data modeling is a journey - manage yours with `jrny`*\n\n**Important:** Journey is still very much a prototype; being version \u003e= 1 simply means that it reached minimum required (and working) features, and development continues to be sporadic due to the responsibilities of life.\n\nIn other words: **USE WITH DISCRETION**\n\n## Overview\n\nOther SQL-based schema migration tools already exist (like [dbmate](\u003chttps://github.com/amacneil/dbmate\u003e)),\nbut there is still room for another.\n\n`jrny` is an option for people who...\n\n* ... think database revision files should be an immutable record and are **guaranteed to represent** what was applied to database\n\n* ... want a **guaranteed revision order** across all environments\n\n* ... would **rather write SQL** than translate it to method calls or YAML entries that are often more verbose and less documented\n\n* ... want **explicit control of transactions** and the ability to easily ignore them or leverage them across multiple revisions\n\n* ... prefer to **install compiled binaries** rather than manage a language and dependencies on whatever system(s) run migrations\n\n* ... like the idea of **single responsibility**, especially if multiple applications (potentially in different repos and written in different languages) access the same tables\n\n* ... believe that **separating migrations from application deploys** encourages one to write non-breaking migrations and helps enable zero-downtime updates\n\n* ... prefer to **avoid reverse migrations**, especially as they make it trivially easy to 'change' history by forgetting to add a preexisting index, check constraint, etc. during a typical upgrade/downgrade/edit/upgrade cycle. (Feel free to add your thoughts on this subject [here](\u003chttps://github.com/kevlarr/jrny/issues/25\u003e))\n\n## CLI Usage\n\n`jrny` is primarily intended to be used as a precompiled, standalone CLI tool,\nbut it can also be used [as a library](#library-usage).\n\n### Installation\n\n#### From source\n\nAssuming `cargo` is installed (easiest is using [rustup](\u003chttps://rustup.rs/\u003e)) then simply run:\n\n```bash\n$ cargo install jrny --version 2.0.0-beta.8\n\n    Updating crates.io index\n  Downloaded jrny v2.0.0-beta.8\n  Downloaded 1 crate (28.6 KB) in 0.39s\n  Installing jrny v2.0.0-beta.8\n   ...\n   ...\n   ...\n   Compiling jrny v2.0.0-beta.8\n    Finished release [optimized] target(s) in 2m 03s\n  Installing /Users/\u003cuser\u003e/.cargo/bin/jrny\n   Installed package `jrny v2.0.0-beta.8` (executable `jrny`)\n```\n\n### Usage\n\nThere are **4 steps** to managing schema changes with `jrny`:\n\n1. `begin`\n2. `plan`\n3. `review`\n4. `embark`\n\n#### Begin the journey\n\nProject setup is simple - all that is required is a config file and an empty revisions directory alongside it.\nThese can be created manually or via `jrny begin`.\n\n```bash\n$ jrny begin \u003cproject-dir\u003e\n\nA journey has begun\n  \u003cproject-dir\u003e\n  ├── \u003cproject-dir\u003e/revisions [created]\n  └── \u003cproject-dir\u003e/jrny.toml [created]\n  └── \u003cproject-dir\u003e/jrny-env.toml [created]\n  └── \u003cproject-dir\u003e/jrny-env.example.toml [created]\n  \n```\n\nThe default `jrny.toml` file specifies the directory in which to\nlocate revisions as well as the database schema \u0026 name for the\n\"state table\" in which to record details for applied revisions.\n\n```toml\n# jrny.toml\n\n[revisions]\ndirectory = \"revisions\"\n\n[table]\nschema = \"public\"\nname = \"jrny_revision\"\n```\n\nThe revision directory can be renamed any time, provided that the SQL\nfiles themselves do not change,\nbut the schena \u0026 table cannot be changed once any revisions have been applied.\nOtherwise, `jrny` will see an empty state table and attempt to\napply all revisions again.\n\nAdditionally, `jrny-env.toml` and `jrny-env.example.toml` files will be created.\nThe `jrny-env.toml` environment file is optional but is used to store\nenvironment-specific information, including the database connection string.\n\n```toml\n# jrny-env.example.toml\n\n[database]\n# Database connection string - for permissible formats and options see:\n# https://docs.rs/postgres/0.19.1/postgres/config/struct.Config.html\nurl = \"postgresql://user:password@host:port/dbname\"\n```\n\nBoth the config and environment files can be freely renamed,\nbut changing their names (or running `jrny` outside of the\nproject directory) will require passing in their paths via\n`-c [or --config]` and `-e [or --environment]` respectively.\n\n#### Plan the journey\n\nTo create a new SQL revision, run `jrny plan [-c \u003cpath-to-config\u003e]` either specifying the path to the\nconfig file via `-c` or (if ommitted) by looking for `jrny.toml` in the current directory.\n\n```bash\n$ jrny plan create-users\n\nCreated revisions/001.1606743300.create-users.sql\n\n$ jrny plan 'name with spaces' -c /path/to/my/config.toml\n\nCreated /path/to/my/revisions/002.1606743400.name with spaces.sql\n```\n\nThis will create a (mostly) empty SQL file for you to populate with wonderful statements.\nNotice that `jrny` **encourages transactions per-revision** but you are free to remove these,\nparticularly if you need to execute statements outside of a transaction - or if you want to write several revision files that should\nspan the same transaction.\n\n``` bash\n$ cat /path/to/my/revisions/002.1606743400.name\\ with\\ spaces.sql\n\n-- Revision: name with spaces\n--\n-- Add description here\n\nbegin;\n\n-- Add SQL here\n\ncommit;\n```\n\n\u003e Note: It's encouraged to comment-out the `commit;` line so that you\n\u003e can run the revision in the database without changes actually persisting.\n\nRevision filenames follow the pattern of `[id].[timestamp].[name].sql`.\n\nTimestamps are just great metadata to capture, and `jrny` assigns a sequential id to each file.\nThe reason being this enforces a stricter revision order than simply using timestamps can,\nall without needing pointers between files.\n(For more information, see the [rational behind sequencing](\u003chttps://github.com/kevlarr/jrny/issues/17\u003e).)\n\nGaps in the id sequence are fine (eg. if you create two new revisions, remove the first one, and then apply the second),\nand ids can be manually changed as long as the revision hasn't been applied.\n\n#### Review the journey\n\nTo summarize the state of revisions, run `jrny review`.\nIf you are outside of the project directory, you'll need to\nspecify the config file location, and\nyou will either need to specify the path to the environment\nfile or provide the database URL directly, eg:\n\n```bash\n# From within project directory \u0026 default filenames\n$ jrny review\n\n# From outside the project directory *or* with a custom config filename.\n#\n# This will look for an environment file named `jrny-env.toml` in\n# the same directory as the custom config file.\n$ jrny review -c path/to/my-jrny-config.toml\n\n# Same as above except can specify custom environment file with different name\n# or in a different directory than the config file.\n$ jrny review -c path/to/my-jrny-config.toml -e path/to/my-jrny-env.toml\n\n# Specifying database URL within project directory \u0026 default config filename.\n# Can be used in conjunction with custom config and/or environment file paths.\n#\n# If there is a default environment file in the current directory, the URL option\n# will take precedence over the URL supplied by the environment file.\n$ jrny review -d 'postgresql://user:password@host:5432/dbname'\n```\n\nThis will list all ordered revisions, each with time of creation as well as time of application, if applied to the specified database.\n\n```bash\n$ jrny review\n\nThe journey thus far:\n\n  [1] my-first-revision\n    Created on 30-Mar-2023 09:10:22\n    Applied on 30-Mar-2023 09:11:06\n\n  [2] another-revision\n    Created on 30-Mar-2023 09:10:32\n    Applied on 30-Mar-2023 09:11:06\n\n  [3] YET-another-revision\n    Created on 30-Mar-2023 09:27:58\n```\n\nAdditionally, `jrny` performs several checks during review to guarantee that...\n\n- Applied revisions' files have not been changed after having been applied\n- Applied revisions' files have not been removed\n- Pending revisions have not been inserted into the sequence prior to applied revisions\n- All revisions in the sequence (pending and applied) have unique ids\n\n```bash\nThe journey thus far:\n\n  [1] revision-that-gets-changed\n    Created on 30-Mar-2023 09:31:05\n    Applied on 30-Mar-2023 10:17:33\n    Errors:\n      - File has changed after being applied\n\n  [2] revision-that-gets-removed\n    Created on 30-Mar-2023 09:57:56\n    Applied on 30-Mar-2023 10:17:33\n    Errors:\n      - File could not be found\n\n  [3] a-revision-added-in-between\n    Created on 30-Mar-2023 10:18:58\n    Errors:\n      - Later revisions have already been applied\n\n  [4] some-revision-that-is-fine\n    Created on 30-Mar-2023 09:58:19\n    Applied on 30-Mar-2023 10:17:33\n\n  [5] a-revision-that-was-fine\n    Created on 30-Mar-2023 10:17:24\n    Applied on 30-Mar-2023 10:17:33\n    Errors:\n      - Revision has a duplicate id\n\n  [5] revision-with-duplicate-id\n    Created on 30-Mar-2023 10:19:57\n    Errors:\n      - Revision has a duplicate id\n\nThe journey has problems:\n  - 1 revision has been changed after being applied\n  - 2 revisions have duplicate ids\n  - 1 revision file could not be found\n  - 1 pending revision has been inserted before revisions already applied\n```\n\nThese checks are not necessarily mutually-exclusive, either, meaning a single revision\ncan potentially have multiple errors, eg. having been changed after being applied AND\nhaving a duplicate id, if the sequence has been altered as well.\n\n**Note:** Review will fail with even the addition (or removal) of whitespace or comments;\nthere is currently no attempt to scrub those out prior to generating the checksums used to\ndetermine if a file has been changed after being applied.\n\n#### Embark on the journey!\n\nTo apply all pending revisions, run `jrny embark`.\n\nAs with `jrny review`, applying revisions looks for default config \u0026 environment files in the current directory,\nbut either can be overridden and, again, the database URL can be supplied directly.\n\nRevisions will be reviewed prior to applying any pending, and if files have changed, are no longer\npresent on disk, etc., then `jrny` will issue an error and exit without applying any new revisions.\n\nOtherwise, `jrny` will simply either list the names of the revisions applied...\n\n```bash\n$ jrny embark\n\nApplying 1 revision(s)\n\n  003.1680182878.YET-another-revision.sql\n```\n\n... or a message indicating that no pending revisions were found.\n\n```bash\n$ jrny embark\n\nNo revisions to apply\n```\n\nAdditionally, instead of applying all pending revisions, you can apply only those\nup through a specified id using `--through` or `-t`.\n\nFor instance, given a review like:\n\n```bash\n$ jrny review\n\nThe journey thus far:\n\n  [1] my-first-revision\n    Created on 30-Mar-2023 09:10:22\n    Applied on 30-Mar-2023 09:11:06\n\n  [2] another-revision\n    Created on 30-Mar-2023 09:10:32\n\n  [3] YET-another-revision\n    Created on 30-Mar-2023 09:27:58\n\n  [4] shocker-a-revision\n    Created on 19-Apr-2023 15:42:29\n\n  [5] surprise-another-revision\n    Created on 19-Apr-2023 15:42:36\n```\n\nIf you only wanted to run up through `YET-another-revision` you would just pass the id `3`:\n\n```bash\n$ jrny embark --through 3\n\nApplying 2 revision(s), skipping 2\n\n  002.1680181832.another-revision.sql\n  008.1681952321.YET another revision.sql\n```\n\n## Library Usage\n\nThe `jrny` CLI tool is a thin wrapper around several structs and functions that can\nalternatively be imported into a Rust application, if one wants to manage revisions\nmore programmatically.\n\nThe library functions make no assumptions about configuration and environment, however;\nyou must explicitly create those objects as necessary, which is admittedly\nnot very ergonomic at the moment.\n\nFor a complete (basic) example:\n\n```rust\nuse std::env;\nuse std::path::PathBuf;\n\nuse jrny::context as ctx;\n\n\nfn main() {\n    // Initialize a new `jrny` setup in the `./jrny-test` subdirectory.\n    //\n    // Note: In addition to creating the necessary revisions directory, this *also*\n    // creates the `jrny.toml`, etc files that, when using `jrny` as a library,\n    // are entirely unnecessary.\n    //\n    // See: https://github.com/kevlarr/jrny/issues/35\n    jrny::begin(\u0026PathBuf::from(\"jrny-test\")).unwrap();\n\n    // The rest of the commands will need to know the project configuration\n    // and potentially other environment details as well.\n    let cfg = ctx::Config {\n        revisions: ctx::RevisionsSettings {\n            directory: PathBuf::from(\"jrny-test/revisions\"),\n        },\n        table: ctx::TableSettings {\n            schema: \"public\".to_owned(),\n            name: \"jrny_revision\".to_owned(),\n        },\n    };\n    let env = ctx::Environment::from_database_url(\u0026env::var(\"DATABASE_URL\").unwrap());\n\n    // Create a new empty migration\n    jrny::plan(\u0026cfg, \"my first migration\", None).unwrap();\n\n    // Create another migration with some contents\n    jrny::plan(\u0026cfg, \"a more useful migration\", Some(\"\n        create table my_cool_table (\n            id bigint\n                primary key\n                generated always as identity\n        )\n    \")).unwrap();\n\n    // Review the migrations\n    jrny::review(\u0026cfg, \u0026env).unwrap();\n\n    // Run the migrations\n    jrny::embark(\u0026cfg, \u0026env).unwrap();\n}\n```\n\n## Planned improvements, or \"things that are missing\"\n\nSee [enhancements](https://github.com/kevlarr/jrny/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement)\nfor a running list of planned new features.\n\nMore importantly, there is currently zero test coverage; fixing this is part of the\n[v2.0.0 milestone](https://github.com/kevlarr/jrny/issues?q=is%3Aopen+is%3Aissue+milestone%3Av2.0.0).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevlarr%2Fjrny","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkevlarr%2Fjrny","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevlarr%2Fjrny/lists"}