A simple version migration system for Clojure applications.
Patcho provides a declarative way to define version-based patches that can be applied to migrate between different versions of your application or modules.
Key features:
Basic usage: (require '[patcho.patch :as patch])
; Define current version (patch/current-version ::my-app "2.0.0")
; Define upgrade patches (patch/upgrade ::my-app "1.0.0" (println "Initial setup"))
(patch/upgrade ::my-app "2.0.0" (println "Major upgrade"))
; Apply patches (patch/apply ::my-app "1.0.0") ; Runs 2.0.0 upgrade (patch/apply ::my-app nil) ; Runs all upgrades
A simple version migration system for Clojure applications.
Patcho provides a declarative way to define version-based patches that can be
applied to migrate between different versions of your application or modules.
Key features:
- Automatic patch sequencing based on semantic versioning
- Support for both upgrades and downgrades
- Topic-based organization for modular systems
- Simple macro-based API
Basic usage:
(require '[patcho.patch :as patch])
; Define current version
(patch/current-version ::my-app "2.0.0")
; Define upgrade patches
(patch/upgrade ::my-app "1.0.0"
(println "Initial setup"))
(patch/upgrade ::my-app "2.0.0"
(println "Major upgrade"))
; Apply patches
(patch/apply ::my-app "1.0.0") ; Runs 2.0.0 upgrade
(patch/apply ::my-app nil) ; Runs all upgradesDynamic var for the version store.
Defaults to AtomVersionStore (in-memory). State is lost on restart unless you explicitly set a persistent store (FileVersionStore, database, etc.).
Set globally via set-store!, or temporarily via with-store macro. State is automatically migrated when switching stores.
Dynamic var for the version store. Defaults to AtomVersionStore (in-memory). State is lost on restart unless you explicitly set a persistent store (FileVersionStore, database, etc.). Set globally via set-store!, or temporarily via with-store macro. State is automatically migrated when switching stores.
(apply topic)(apply topic current)(apply topic current target)Applies version patches to migrate from one version to another.
With 1 arg: Migrates from installed version (read from version-store) to current-version. With 2 args: Migrates from 'current' to the topic's current version. With 3 args: Migrates from 'current' to 'target' version.
After successful migration, the new version is persisted to version-store.
Arguments: topic - Keyword identifying the module/component to patch current - Current version string (nil or "0" means start from beginning) target - Target version string (optional, defaults to topic's current version)
The function automatically:
Returns: The target version if patches were applied, nil otherwise
Example: (apply ::my-app "1.0.0" "2.0.0") ; Upgrade from 1.0.0 to 2.0.0 (apply ::my-app "2.0.0" "1.0.0") ; Downgrade from 2.0.0 to 1.0.0 (apply ::my-app) ; Upgrade from installed to current
Applies version patches to migrate from one version to another. With 1 arg: Migrates from installed version (read from *version-store*) to current-version. With 2 args: Migrates from 'current' to the topic's current version. With 3 args: Migrates from 'current' to 'target' version. After successful migration, the new version is persisted to *version-store*. Arguments: topic - Keyword identifying the module/component to patch current - Current version string (nil or "0" means start from beginning) target - Target version string (optional, defaults to topic's current version) The function automatically: - Determines upgrade vs downgrade direction - Finds applicable patches between versions - Executes patches in correct order (oldest-first for upgrades, newest-first for downgrades) - Persists the new version to *version-store* Returns: The target version if patches were applied, nil otherwise Example: (apply ::my-app "1.0.0" "2.0.0") ; Upgrade from 1.0.0 to 2.0.0 (apply ::my-app "2.0.0" "1.0.0") ; Downgrade from 2.0.0 to 1.0.0 (apply ::my-app) ; Upgrade from installed to current
(available-versions & topics)Returns a map of topics to their current versions.
With no args: Returns all registered topic versions. With args: Returns versions only for specified topics.
Arguments: topics - Zero or more topic keywords to query
Returns: Map of {topic version-string} for registered topics
Example: (available-versions) ; => {:app "2.0.0" :db "1.5.0"} (available-versions :app) ; => {:app "2.0.0"} (available-versions :app :db :unknown) ; => {:app "2.0.0" :db "1.5.0"}
Returns a map of topics to their current versions.
With no args: Returns all registered topic versions.
With args: Returns versions only for specified topics.
Arguments:
topics - Zero or more topic keywords to query
Returns:
Map of {topic version-string} for registered topics
Example:
(available-versions) ; => {:app "2.0.0" :db "1.5.0"}
(available-versions :app) ; => {:app "2.0.0"}
(available-versions :app :db :unknown) ; => {:app "2.0.0" :db "1.5.0"}(current-version topic & body)Defines the current/target version for a topic.
This version is used as the default target when calling apply with only 2 arguments.
Arguments: topic - Keyword identifying the module/component body - Should return a version string
Example: (current-version ::my-app "2.5.0")
; Can also compute version dynamically (current-version ::my-app (read-version-from-file))
Defines the current/target version for a topic.
This version is used as the default target when calling apply
with only 2 arguments.
Arguments:
topic - Keyword identifying the module/component
body - Should return a version string
Example:
(current-version ::my-app "2.5.0")
; Can also compute version dynamically
(current-version ::my-app
(read-version-from-file))(downgrade topic to & body)Defines code to execute when downgrading FROM the specified version.
The body will be executed when applying patches that include this version in the downgrade path. Note: downgrade happens FROM this version to a lower version.
Arguments: topic - Keyword identifying the module/component to - Version string this downgrade migrates FROM body - Code to execute for the downgrade
Example: (downgrade ::database "2.0.0" (drop-column :users :preferences) (restore-legacy-settings))
Defines code to execute when downgrading FROM the specified version.
The body will be executed when applying patches that include this version
in the downgrade path. Note: downgrade happens FROM this version to a
lower version.
Arguments:
topic - Keyword identifying the module/component
to - Version string this downgrade migrates FROM
body - Code to execute for the downgrade
Example:
(downgrade ::database "2.0.0"
(drop-column :users :preferences)
(restore-legacy-settings))(level! topic)(level! topic & more-topics)Apply all pending patches for a component.
Reads the installed version from version-store, applies patches to reach current-version, and persists the new version.
Arguments: topic - Keyword identifying the module/component
Returns: The target version if patches were applied, nil if already at target.
Example: (level! :myapp/database)
Apply all pending patches for a component. Reads the installed version from *version-store*, applies patches to reach current-version, and persists the new version. Arguments: topic - Keyword identifying the module/component Returns: The target version if patches were applied, nil if already at target. Example: (level! :myapp/database)
(migrate-store! from-store to-store topics)Migrate version state from one store to another.
Useful for bootstrapping: start with AtomVersionStore, setup database, then migrate state to database-backed store.
Args: from-store - Source VersionStore to read from to-store - Destination VersionStore to write to topics - Collection of topic keywords to migrate (or nil for all registered)
Returns: Set of migrated topics
Example: ;; Bootstrap with in-memory store (def bootstrap-store (->AtomVersionStore (atom {}))) (set-store! bootstrap-store) (level! :myapp/database)
;; Migrate to database store (migrate-store! bootstrap-store db nil) (set-store! db)
Migrate version state from one store to another.
Useful for bootstrapping: start with AtomVersionStore, setup database,
then migrate state to database-backed store.
Args:
from-store - Source VersionStore to read from
to-store - Destination VersionStore to write to
topics - Collection of topic keywords to migrate (or nil for all registered)
Returns:
Set of migrated topics
Example:
;; Bootstrap with in-memory store
(def bootstrap-store (->AtomVersionStore (atom {})))
(set-store! bootstrap-store)
(level! :myapp/database)
;; Migrate to database store
(migrate-store! bootstrap-store *db* nil)
(set-store! *db*)(registered-topics)Returns a set of all registered topics (components with current-version defined).
Returns: Set of topic keywords
Example: (registered-topics) ; => #{:synthigy/iam :synthigy/iam-audit :synthigy/dataset}
Returns a set of all registered topics (components with current-version defined).
Returns:
Set of topic keywords
Example:
(registered-topics) ; => #{:synthigy/iam :synthigy/iam-audit :synthigy/dataset}(set-store! store)Set the default version store globally.
Automatically migrates state from the previous store to the new one. This makes store transitions seamless (e.g., from bootstrap AtomVersionStore to database-backed store).
Arguments: store - Implementation of VersionStore protocol (or nil to clear)
Example: ;; Bootstrap with in-memory store (default) (level! :my/database)
;; Switch to DB - state auto-migrates (set-store! db)
(level! :my/app) ; Uses database store
Set the default version store globally. Automatically migrates state from the previous store to the new one. This makes store transitions seamless (e.g., from bootstrap AtomVersionStore to database-backed store). Arguments: store - Implementation of VersionStore protocol (or nil to clear) Example: ;; Bootstrap with in-memory store (default) (level! :my/database) ;; Switch to DB - state auto-migrates (set-store! *db*) (level! :my/app) ; Uses database store
(topic-version topic)(upgrade topic to & body)Defines code to execute when upgrading TO the specified version.
The body will be executed when applying patches that include this version in the upgrade path.
Arguments: topic - Keyword identifying the module/component to - Version string this upgrade migrates TO body - Code to execute for the upgrade
Example: (upgrade ::database "2.0.0" (add-column :users :preferences :jsonb) (migrate-user-settings))
Defines code to execute when upgrading TO the specified version.
The body will be executed when applying patches that include this version
in the upgrade path.
Arguments:
topic - Keyword identifying the module/component
to - Version string this upgrade migrates TO
body - Code to execute for the upgrade
Example:
(upgrade ::database "2.0.0"
(add-column :users :preferences :jsonb)
(migrate-user-settings))Protocol for persisting version state across application restarts
Protocol for persisting version state across application restarts
(read-version this topic)Read the currently installed version for the given topic. Should return a version string or nil/"0" if not found.
Read the currently installed version for the given topic. Should return a version string or nil/"0" if not found.
(write-version this topic version)Persist the installed version for the given topic. Called automatically by apply after successful migration.
Persist the installed version for the given topic. Called automatically by apply after successful migration.
(with-store store & body)Execute body with a specific VersionStore bound to version-store.
Useful for testing or temporary overrides.
Arguments: store - Implementation of VersionStore protocol body - Expressions to execute with the store bound
Example: (with-store (->FileVersionStore ".test-versions") (apply ::my-app) (level! ::other-app))
Execute body with a specific VersionStore bound to *version-store*.
Useful for testing or temporary overrides.
Arguments:
store - Implementation of VersionStore protocol
body - Expressions to execute with the store bound
Example:
(with-store (->FileVersionStore ".test-versions")
(apply ::my-app)
(level! ::other-app))cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |