Module lifecycle management with dependency resolution.
Provides runtime lifecycle management for modules with explicit dependency declaration and automatic startup ordering.
This namespace provides a lightweight registry for managing module lifecycle:
(ns my.module
(:require [patcho.lifecycle :as lifecycle]))
(lifecycle/register-module! :my/module
{:depends-on [:other/module]
:setup (fn []
(create-tables!))
:cleanup (fn []
(drop-tables!))
:start (fn []
(start-connections!)
(subscribe-to-events!))
:stop (fn []
(unsubscribe!)
(close-connections!))})
(ns my.app
(:require
my.database ; Registers :my/database
my.cache ; Registers :my/cache (depends on :my/database)
my.api ; Registers :my/api (depends on :my/cache)
[patcho.lifecycle :as lifecycle]))
;; Set default store once (typically in main or init)
(lifecycle/set-store! (lifecycle/->FileLifecycleStore ".lifecycle"))
;; Simple approach: Just start! (auto-runs setup if needed)
(lifecycle/start! :my/api)
;; → Setups (if needed) and starts: :my/database → :my/cache → :my/api
;; Setup is idempotent - only runs once, tracked via lifecycle store
;; OR explicit control: Run setup separately (optional)
(lifecycle/setup! :my/api) ; One-time: setup all dependencies
(lifecycle/start! :my/api) ; Runtime: start all dependencies
;; Visualize dependencies
(lifecycle/print-dependency-tree :my/api)
;; :my/api
;; └── :my/cache
;; └── :my/database
;; Later: stop (RECURSIVE - stops dependents first)
(lifecycle/stop! :my/database)
;; → Stops: :my/api → :my/cache → :my/database
;; Surgical stop (only this module)
(lifecycle/stop-only! :my/api)
;; Cleanup (RECURSIVE with reference checking)
(lifecycle/cleanup! :my/api)
;; → Cleans :my/api
;; → Cleans :my/cache (if no other modules depend on it)
;; → Cleans :my/database (if no other modules depend on it)
Lifecycle management is complementary to version management:
They work together:
Note: start! automatically runs setup for modules that have a setup function, making the system easy to use while maintaining explicit control when needed.
Module lifecycle management with dependency resolution.
Provides runtime lifecycle management for modules with explicit dependency
declaration and automatic startup ordering.
## Design Philosophy
This namespace provides a lightweight registry for managing module lifecycle:
- Modules register themselves at namespace load time
- Dependencies are declared explicitly
- Operations are RECURSIVE - automatically handle dependencies
- Cleanup uses reference counting (claimed-by semantics)
- State tracking prevents double-start/stop
- Setup phase is separate from start/stop (one-time vs runtime)
## Usage
### Registration (at namespace load time)
(ns my.module
(:require [patcho.lifecycle :as lifecycle]))
(lifecycle/register-module! :my/module
{:depends-on [:other/module]
:setup (fn []
(create-tables!))
:cleanup (fn []
(drop-tables!))
:start (fn []
(start-connections!)
(subscribe-to-events!))
:stop (fn []
(unsubscribe!)
(close-connections!))})
### Application Startup
(ns my.app
(:require
my.database ; Registers :my/database
my.cache ; Registers :my/cache (depends on :my/database)
my.api ; Registers :my/api (depends on :my/cache)
[patcho.lifecycle :as lifecycle]))
;; Set default store once (typically in main or init)
(lifecycle/set-store! (lifecycle/->FileLifecycleStore ".lifecycle"))
;; Simple approach: Just start! (auto-runs setup if needed)
(lifecycle/start! :my/api)
;; → Setups (if needed) and starts: :my/database → :my/cache → :my/api
;; Setup is idempotent - only runs once, tracked via lifecycle store
;; OR explicit control: Run setup separately (optional)
(lifecycle/setup! :my/api) ; One-time: setup all dependencies
(lifecycle/start! :my/api) ; Runtime: start all dependencies
;; Visualize dependencies
(lifecycle/print-dependency-tree :my/api)
;; :my/api
;; └── :my/cache
;; └── :my/database
;; Later: stop (RECURSIVE - stops dependents first)
(lifecycle/stop! :my/database)
;; → Stops: :my/api → :my/cache → :my/database
;; Surgical stop (only this module)
(lifecycle/stop-only! :my/api)
;; Cleanup (RECURSIVE with reference checking)
(lifecycle/cleanup! :my/api)
;; → Cleans :my/api
;; → Cleans :my/cache (if no other modules depend on it)
;; → Cleans :my/database (if no other modules depend on it)
## Relationship to patcho.patch
Lifecycle management is complementary to version management:
- patcho.patch: Version migrations (data/schema state transitions)
- patcho.lifecycle: Runtime management (start/stop with dependencies)
They work together:
1. Apply patches to reach target version (patch/level!)
2. Start runtime services (lifecycle/start!) - auto-runs setup if needed
Note: start! automatically runs setup for modules that have a setup function,
making the system easy to use while maintaining explicit control when needed.
## Design Decisions
- Registry pattern: Modules stored in atom (like Patcho's patches)
- Keyword topics: Same style as Patcho (:my/module)
- Explicit dependencies: Clear, validated at runtime
- RECURSIVE operations: start/setup/cleanup handle deps automatically
- Auto-setup: start! runs setup if needed (idempotent via lifecycle store)
- Reference counting: cleanup only removes if not claimed by others
- State tracking: Prevents double-start bugs
- Functions not protocols: Simple, direct
- Minimal dependencies: Just clojure.coreDynamic var for the lifecycle store.
Defaults to AtomLifecycleStore (in-memory). State is lost on restart unless you explicitly set a persistent store (FileLifecycleStore, database, etc.).
Set globally via set-store!, or temporarily via with-store macro. State is automatically migrated when switching stores.
Dynamic var for the lifecycle store. Defaults to AtomLifecycleStore (in-memory). State is lost on restart unless you explicitly set a persistent store (FileLifecycleStore, database, etc.). Set globally via set-store!, or temporarily via with-store macro. State is automatically migrated when switching stores.
1-arg fn invoked after every module phase transition. Patcho has no logging dependency by design, so this is the seam a host application rebinds to route lifecycle transitions into its own observability stack.
Receives a map:
{:phase :started :topic :my/api} {:phase :stopped :topic :my/api} {:phase :start-failed :topic :my/api :error <Throwable>}
Called for EVERY module, so a host gets guaranteed coverage without each module having to remember to announce itself. Exceptions thrown by the fn are swallowed — observability must never break a lifecycle transition.
Defaults to default-on-lifecycle-event, which prints to stdout/stderr.
Set to nil to silence patcho entirely.
1-arg fn invoked after every module phase transition. Patcho has no logging
dependency by design, so this is the seam a host application rebinds to route
lifecycle transitions into its own observability stack.
Receives a map:
{:phase :started :topic :my/api}
{:phase :stopped :topic :my/api}
{:phase :start-failed :topic :my/api :error <Throwable>}
Called for EVERY module, so a host gets guaranteed coverage without each
module having to remember to announce itself. Exceptions thrown by the fn
are swallowed — observability must never break a lifecycle transition.
Defaults to `default-on-lifecycle-event`, which prints to stdout/stderr.
Set to nil to silence patcho entirely.(cleanup! topic)Run one-time cleanup for a module and all its dependants (modules that depend on it).
Cleanup order:
This ensures no module is left in an inconsistent state with a missing dependency. Dependencies (modules this one depends on) are NOT cleaned - they may be shared.
This is idempotent - tracks if cleanup already ran via LifecycleStore.
Uses lifecycle-store - set it via set-store! or with-store macro.
Parameters: topic - Module keyword
Example: (set-store! (->FileLifecycleStore ".lifecycle")) (cleanup! :my/database) ; Also cleans up :my/cache if it depends on :my/database
Run one-time cleanup for a module and all its dependants (modules that depend on it). Cleanup order: 1. First cleanup all dependants (modules that depend on this one) - recursively 2. Then cleanup this module This ensures no module is left in an inconsistent state with a missing dependency. Dependencies (modules this one depends on) are NOT cleaned - they may be shared. This is idempotent - tracks if cleanup already ran via LifecycleStore. Uses *lifecycle-store* - set it via set-store! or with-store macro. Parameters: topic - Module keyword Example: (set-store! (->FileLifecycleStore ".lifecycle")) (cleanup! :my/database) ; Also cleans up :my/cache if it depends on :my/database
(clear-errors!)Clear all recorded module errors.
Useful after fixing issues and before retrying startup.
Example: (clear-errors!) (start! :my/module) ; Fresh start without old errors
Clear all recorded module errors. Useful after fixing issues and before retrying startup. Example: (clear-errors!) (start! :my/module) ; Fresh start without old errors
(default-on-lifecycle-event {:keys [phase topic error]})Default *on-lifecycle-event* binding: print transitions to the console.
Normal transitions go to stdout, failures to stderr. Patcho has no logging
dependency by design, so this is what makes a bare (start! :my/api)
observable with zero configuration — a module that starts, stops, or blows
up says so without the host wiring anything.
No stack trace is printed: start! rethrows the throwable, so whoever
catches it owns the trace. Printing here would just duplicate it.
Default `*on-lifecycle-event*` binding: print transitions to the console. Normal transitions go to stdout, failures to stderr. Patcho has no logging dependency by design, so this is what makes a bare `(start! :my/api)` observable with zero configuration — a module that starts, stops, or blows up says so without the host wiring anything. No stack trace is printed: `start!` rethrows the throwable, so whoever catches it owns the trace. Printing here would just duplicate it.
(dependency-graph)Returns the dependency graph as a map.
Useful for visualization or debugging.
Returns: {topic {:depends-on [...] :dependents [...]}}
Example: (dependency-graph) ;; => {:my/database {:depends-on [] :dependents [:my/cache]} ;; :my/cache {:depends-on [:my/database] :dependents [:my/api]} ;; :my/api {:depends-on [:my/cache] :dependents []}}
Returns the dependency graph as a map.
Useful for visualization or debugging.
Returns:
{topic {:depends-on [...] :dependents [...]}}
Example:
(dependency-graph)
;; => {:my/database {:depends-on [] :dependents [:my/cache]}
;; :my/cache {:depends-on [:my/database] :dependents [:my/api]}
;; :my/api {:depends-on [:my/cache] :dependents []}}(dependency-tree-string)(dependency-tree-string topic)Generate ASCII tree visualization of module dependencies.
With no arguments, shows all root modules (modules nothing depends on). With topic argument, shows dependency tree for that module.
Parameters: topic - (Optional) Module keyword to visualize
Returns: String with ASCII tree visualization
Example: (dependency-tree-string :my/api) ;; => ":my/api ;; ├── :my/cache ;; │ └── :my/database ;; └── :my/auth ;; └── :my/database"
Generate ASCII tree visualization of module dependencies. With no arguments, shows all root modules (modules nothing depends on). With topic argument, shows dependency tree for that module. Parameters: topic - (Optional) Module keyword to visualize Returns: String with ASCII tree visualization Example: (dependency-tree-string :my/api) ;; => ":my/api ;; ├── :my/cache ;; │ └── :my/database ;; └── :my/auth ;; └── :my/database"
Persistent storage for lifecycle state tracking.
Tracks one-time operations (setup, cleanup) across process restarts.
IMPORTANT: Only :setup-complete? and :cleanup-complete? should be persisted. The :started? flag is RUNTIME-ONLY state - it must NOT be persisted. This ensures :start functions execute fresh on every JVM restart.
Implementations SHOULD:
Persistent storage for lifecycle state tracking. Tracks one-time operations (setup, cleanup) across process restarts. IMPORTANT: Only :setup-complete? and :cleanup-complete? should be persisted. The :started? flag is RUNTIME-ONLY state - it must NOT be persisted. This ensures :start functions execute fresh on every JVM restart. Implementations SHOULD: - Store only :setup-complete? and :cleanup-complete? - Ignore any :started? key passed to write-lifecycle-state - Never return :started? from read-lifecycle-state
(read-lifecycle-state store topic)Read persistent state for a topic. Returns map with :setup-complete? and :cleanup-complete? only. MUST NOT return :started? - that's runtime-only state.
Read persistent state for a topic. Returns map with :setup-complete? and :cleanup-complete? only. MUST NOT return :started? - that's runtime-only state.
(write-lifecycle-state store topic state)Write persistent state for a topic. State should only contain :setup-complete? and :cleanup-complete?. Implementations SHOULD ignore :started? if present.
Write persistent state for a topic. State should only contain :setup-complete? and :cleanup-complete?. Implementations SHOULD ignore :started? if present.
(migrate-store! from-store to-store topics)Migrate lifecycle state from one store to another.
Useful for bootstrapping: start with AtomLifecycleStore, setup database, then migrate state to database-backed store.
Args: from-store - Source LifecycleStore to read from to-store - Destination LifecycleStore 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 (->AtomLifecycleStore (atom {}))) (set-store! bootstrap-store) (setup! :synthigy/database)
;; Migrate to database store (migrate-store! bootstrap-store db/db nil) (set-store! db/db)
Migrate lifecycle state from one store to another.
Useful for bootstrapping: start with AtomLifecycleStore, setup database,
then migrate state to database-backed store.
Args:
from-store - Source LifecycleStore to read from
to-store - Destination LifecycleStore 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 (->AtomLifecycleStore (atom {})))
(set-store! bootstrap-store)
(setup! :synthigy/database)
;; Migrate to database store
(migrate-store! bootstrap-store db/*db* nil)
(set-store! db/*db*)Registry of module errors. Structure: {topic {:error Exception :timestamp Date}}
Registry of module errors. Structure: {topic {:error Exception :timestamp Date}}
(module-info topic)Returns info map for a module, or nil if not registered.
If lifecycle-store is set, also includes :setup-complete? and :cleanup-complete?.
IMPORTANT: :started? is ALWAYS read from in-memory state, never from persistent store. This ensures :start functions run fresh on every JVM restart.
Returns: {:depends-on [...] :started? true/false ; Runtime-only (in-memory) :has-setup? true/false :has-cleanup? true/false :setup-complete? true/false ; Persistent (from lifecycle-store) :cleanup-complete? true/false} ; Persistent (from lifecycle-store)
Returns info map for a module, or nil if not registered.
If *lifecycle-store* is set, also includes :setup-complete? and :cleanup-complete?.
IMPORTANT: :started? is ALWAYS read from in-memory state, never from persistent store.
This ensures :start functions run fresh on every JVM restart.
Returns:
{:depends-on [...]
:started? true/false ; Runtime-only (in-memory)
:has-setup? true/false
:has-cleanup? true/false
:setup-complete? true/false ; Persistent (from *lifecycle-store*)
:cleanup-complete? true/false} ; Persistent (from *lifecycle-store*)(print-dependency-tree)(print-dependency-tree topic)Print ASCII tree visualization of module dependencies.
With no arguments, shows all root modules. With topic argument, shows dependency tree for that module.
Parameters: topic - (Optional) Module keyword to visualize
Example: (print-dependency-tree :my/api) ;; Prints: ;; :my/api ;; ├── :my/cache ;; │ └── :my/database ;; └── :my/auth ;; └── :my/database
Print ASCII tree visualization of module dependencies. With no arguments, shows all root modules. With topic argument, shows dependency tree for that module. Parameters: topic - (Optional) Module keyword to visualize Example: (print-dependency-tree :my/api) ;; Prints: ;; :my/api ;; ├── :my/cache ;; │ └── :my/database ;; └── :my/auth ;; └── :my/database
(print-system-report)Print formatted system status report showing started/stopped modules and errors.
Uses markers: [OK] - Started modules [ ] - Stopped modules (ready to start) [!] - Modules with errors or missing dependencies
Example: (print-system-report) ;; === System Report === ;; Registered: 7 | Started: 3 | Stopped: 4 ;; ;; [OK] Started modules: ;; * :synthigy/transit ;; * :synthigy/database -> [:synthigy/transit] ;; ;; [ ] Stopped modules (ready to start): ;; * :synthigy/iam -> [:synthigy/dataset] ;; ;; [!] Modules with errors: ;; * :synthigy/admin -> [:synthigy/dataset] ;; ERROR at 2026-01-07 18:23:45 ;; Message: No such var: db/db ;; Data: {:module :synthigy/admin}
Print formatted system status report showing started/stopped modules and errors.
Uses markers:
[OK] - Started modules
[ ] - Stopped modules (ready to start)
[!] - Modules with errors or missing dependencies
Example:
(print-system-report)
;; === System Report ===
;; Registered: 7 | Started: 3 | Stopped: 4
;;
;; [OK] Started modules:
;; * :synthigy/transit
;; * :synthigy/database -> [:synthigy/transit]
;;
;; [ ] Stopped modules (ready to start):
;; * :synthigy/iam -> [:synthigy/dataset]
;;
;; [!] Modules with errors:
;; * :synthigy/admin -> [:synthigy/dataset]
;; ERROR at 2026-01-07 18:23:45
;; Message: No such var: db/*db*
;; Data: {:module :synthigy/admin}(print-topology-layers)Print layered list visualization of all modules.
Shows modules grouped by dependency depth with explicit dependencies.
Example: (print-topology-layers) ;; Prints: ;; Layer 0 (foundation): ;; transit ;; ;; Layer 1: ;; database → [transit] ;; ;; Layer 2: ;; dataset → [database]
Print layered list visualization of all modules. Shows modules grouped by dependency depth with explicit dependencies. Example: (print-topology-layers) ;; Prints: ;; Layer 0 (foundation): ;; transit ;; ;; Layer 1: ;; database → [transit] ;; ;; Layer 2: ;; dataset → [database]
(register-module! topic
{:keys [depends-on doc setup cleanup start stop]
:as spec
:or {stop (fn [] nil) start (fn [] nil)}})Register a module with its lifecycle functions and dependencies.
This should be called at namespace load time (top-level form).
Parameters: topic - Keyword identifying the module (e.g., :synthigy/iam) spec - Map with keys: :depends-on - Vector of topic keywords this module depends on :doc - (Optional) one-line description shown in the topology view (e.g. "pure logging, zero deps") :setup - (Optional) fn taking no args for one-time setup :cleanup - (Optional) fn taking no args for one-time cleanup :start - fn taking no args to start runtime services :stop - fn taking no args to stop runtime services
Any other keys in spec are kept as module metadata and surfaced by module-info and system-report (e.g. :requires-license).
Re-registering an already-registered topic preserves its runtime :started? state — reloading a namespace must not make a running module read as stopped.
Example: (register-module! :my/database {:start (fn [] (connect! db-config)) :stop (fn [] (disconnect!))})
(register-module! :my/cache {:depends-on [:my/database] :setup (fn [] (create-cache-tables!)) :cleanup (fn [] (drop-cache-tables!)) :start (fn [] (start-cache!)) :stop (fn [] (stop-cache!))})
Register a module with its lifecycle functions and dependencies.
This should be called at namespace load time (top-level form).
Parameters:
topic - Keyword identifying the module (e.g., :synthigy/iam)
spec - Map with keys:
:depends-on - Vector of topic keywords this module depends on
:doc - (Optional) one-line description shown in the
topology view (e.g. "pure logging, zero deps")
:setup - (Optional) fn taking no args for one-time setup
:cleanup - (Optional) fn taking no args for one-time cleanup
:start - fn taking no args to start runtime services
:stop - fn taking no args to stop runtime services
Any other keys in spec are kept as module metadata and surfaced by
module-info and system-report (e.g. :requires-license).
Re-registering an already-registered topic preserves its runtime :started?
state — reloading a namespace must not make a running module read as stopped.
Example:
(register-module! :my/database
{:start (fn [] (connect! db-config))
:stop (fn [] (disconnect!))})
(register-module! :my/cache
{:depends-on [:my/database]
:setup (fn [] (create-cache-tables!))
:cleanup (fn [] (drop-cache-tables!))
:start (fn [] (start-cache!))
:stop (fn [] (stop-cache!))})(registered-modules)Returns vector of all registered module topics.
Returns vector of all registered module topics.
(reset-registry!)Clear all registered modules.
WARNING: This is primarily for testing. Using in production may leave modules in inconsistent state.
Stop your running modules first before calling this.
Clear all registered modules. WARNING: This is primarily for testing. Using in production may leave modules in inconsistent state. Stop your running modules first before calling this.
(reset-store!)Reset lifecycle store to a fresh in-memory AtomLifecycleStore.
Use this when cleaning up the database to clear all persisted state and start fresh. Does NOT migrate state (old state is discarded).
Example: ;; In database cleanup (reset-store!) ; Fresh atom store, no state
Reset lifecycle store to a fresh in-memory AtomLifecycleStore. Use this when cleaning up the database to clear all persisted state and start fresh. Does NOT migrate state (old state is discarded). Example: ;; In database cleanup (reset-store!) ; Fresh atom store, no state
(restart! topic)Restart a module (stop-only then start).
Uses stop-only! (non-recursive) for surgical control. Useful for REPL development when code changes.
Parameters: topic - Module keyword
Example: (restart! :my/cache)
Restart a module (stop-only then start). Uses stop-only! (non-recursive) for surgical control. Useful for REPL development when code changes. Parameters: topic - Module keyword Example: (restart! :my/cache)
(set-store! store)Set the default lifecycle store globally.
Automatically migrates state from the previous store to the new one. This makes store transitions seamless (e.g., from bootstrap AtomLifecycleStore to database-backed store).
Arguments: store - Implementation of LifecycleStore protocol (or nil to clear)
Example: ;; Bootstrap with in-memory store (set-store! (->AtomLifecycleStore (atom {}))) (setup! :my/database)
;; Switch to DB - state auto-migrates (set-store! db/db)
(setup! :my/app) ; Uses database store
Migration is BEST-EFFORT and runs AFTER the new store is installed. See the comment in the body — getting that order backwards makes a dead old store unrecoverable.
Set the default lifecycle store globally.
Automatically migrates state from the previous store to the new one.
This makes store transitions seamless (e.g., from bootstrap AtomLifecycleStore
to database-backed store).
Arguments:
store - Implementation of LifecycleStore protocol (or nil to clear)
Example:
;; Bootstrap with in-memory store
(set-store! (->AtomLifecycleStore (atom {})))
(setup! :my/database)
;; Switch to DB - state auto-migrates
(set-store! db/*db*)
(setup! :my/app) ; Uses database store
Migration is BEST-EFFORT and runs AFTER the new store is installed. See the
comment in the body — getting that order backwards makes a dead old store
unrecoverable.(setup! & topics)Run one-time setup for one or more modules RECURSIVELY.
This is idempotent - tracks if setup already ran via LifecycleStore. STARTS dependencies first (setup often needs them running).
Uses lifecycle-store - set it via set-store! or with-store macro.
Parameters: topics - One or more module keywords
Example: (set-store! (->FileLifecycleStore ".lifecycle")) (setup! :my/cache) (setup! :my/db :my/cache :my/api)
Run one-time setup for one or more modules RECURSIVELY. This is idempotent - tracks if setup already ran via LifecycleStore. STARTS dependencies first (setup often needs them running). Uses *lifecycle-store* - set it via set-store! or with-store macro. Parameters: topics - One or more module keywords Example: (set-store! (->FileLifecycleStore ".lifecycle")) (setup! :my/cache) (setup! :my/db :my/cache :my/api)
(start! topic)(start! topic & more-topics)Start a module RECURSIVELY (starts all dependencies first).
This is idempotent - won't start if already started. Dependencies are started in correct order automatically. Records errors in module-errors atom if startup fails.
If module has a setup function and setup hasn't been completed yet, automatically runs setup first (idempotent via lifecycle store).
Parameters: topic - Module keyword
Example: (start! :my/api) ;; → Setups (if needed) and starts: :my/database → :my/cache → :my/api
Start a module RECURSIVELY (starts all dependencies first). This is idempotent - won't start if already started. Dependencies are started in correct order automatically. Records errors in module-errors atom if startup fails. If module has a setup function and setup hasn't been completed yet, automatically runs setup first (idempotent via lifecycle store). Parameters: topic - Module keyword Example: (start! :my/api) ;; → Setups (if needed) and starts: :my/database → :my/cache → :my/api
(started-modules)Returns vector of currently started module topics.
Returns vector of currently started module topics.
(started? topic)Returns true if module is currently started.
Returns true if module is currently started.
(stop! topic)(stop! topic & more-topics)Stop a module and all its dependents RECURSIVELY.
Stop order (dependents first):
This ensures no module is left running without its dependencies. This is symmetric with start! (which starts dependencies first).
This is idempotent - won't stop if already stopped.
Parameters: topic - Module keyword
Example: (stop! :my/database) ;; → Stops: :my/api → :my/cache → :my/database (dependents first)
Stop a module and all its dependents RECURSIVELY. Stop order (dependents first): 1. First stop all dependents (modules that depend on this one) - recursively 2. Then stop this module This ensures no module is left running without its dependencies. This is symmetric with start! (which starts dependencies first). This is idempotent - won't stop if already stopped. Parameters: topic - Module keyword Example: (stop! :my/database) ;; → Stops: :my/api → :my/cache → :my/database (dependents first)
(stop-only! topic)Stop a single module (NOT recursive - only stops this module).
This is idempotent - won't stop if already stopped. Use this for surgical control when you don't want to affect dependents.
Parameters: topic - Module keyword
Example: (stop-only! :my/api) ; Only stops :my/api, dependencies stay running
Stop a single module (NOT recursive - only stops this module). This is idempotent - won't stop if already stopped. Use this for surgical control when you don't want to affect dependents. Parameters: topic - Module keyword Example: (stop-only! :my/api) ; Only stops :my/api, dependencies stay running
(system-report)Get comprehensive system status report including errors.
Returns map with: :registered - Total count of registered modules :started - Count of started modules :stopped - Count of stopped modules :modules - Map of {topic {:status :started/:stopped :depends-on [...] :dependents [...] :missing-dependencies [...] :error {:message "..." :timestamp Date :exception Exception}}}
Example: (system-report) ;=> {:registered 7 :started 3 :stopped 4 :modules {:synthigy/transit {:status :started ...} :synthigy/admin {:status :stopped :error {:message "No such var: db/db" :timestamp #inst "2026-01-07" :exception #<ExceptionInfo ...>}}}}}
Get comprehensive system status report including errors.
Returns map with:
:registered - Total count of registered modules
:started - Count of started modules
:stopped - Count of stopped modules
:modules - Map of {topic {:status :started/:stopped
:depends-on [...]
:dependents [...]
:missing-dependencies [...]
:error {:message "..."
:timestamp Date
:exception Exception}}}
Example:
(system-report)
;=> {:registered 7
:started 3
:stopped 4
:modules {:synthigy/transit {:status :started ...}
:synthigy/admin {:status :stopped
:error {:message "No such var: db/*db*"
:timestamp #inst "2026-01-07"
:exception #<ExceptionInfo ...>}}}}}(topology-layers-string)Generate a column-aligned, layered visualization of all modules.
Each module is placed in the lowest layer above all of its dependencies.
A layer label is printed once in the left gutter; sibling modules align
beneath it. Modules with dependencies render → [deps]; root modules
render ← <doc> using their registered :doc string (falling back to
"zero deps").
Returns: String with the layered visualization.
Example: (topology-layers-string) ;; => "Patcho topology (4 layers) ;; ;; Layer 0 :synthigy/log ← pure logging, zero deps ;; :synthigy.dataset/encryption ← key vault, zero deps ;; Layer 1 :synthigy/transit → [:synthigy/log] ;; Layer 2 :synthigy/database → [:synthigy/transit] ;; Layer 3 :synthigy/dataset → [:synthigy/log, :synthigy/database, :synthigy.dataset/encryption]"
Generate a column-aligned, layered visualization of all modules. Each module is placed in the lowest layer above all of its dependencies. A layer label is printed once in the left gutter; sibling modules align beneath it. Modules with dependencies render `→ [deps]`; root modules render `← <doc>` using their registered :doc string (falling back to "zero deps"). Returns: String with the layered visualization. Example: (topology-layers-string) ;; => "Patcho topology (4 layers) ;; ;; Layer 0 :synthigy/log ← pure logging, zero deps ;; :synthigy.dataset/encryption ← key vault, zero deps ;; Layer 1 :synthigy/transit → [:synthigy/log] ;; Layer 2 :synthigy/database → [:synthigy/transit] ;; Layer 3 :synthigy/dataset → [:synthigy/log, :synthigy/database, :synthigy.dataset/encryption]"
(with-store store & body)Execute body with a specific LifecycleStore bound to lifecycle-store.
Useful for testing or temporary overrides.
Arguments: store - Implementation of LifecycleStore protocol body - Expressions to execute with the store bound
Example: (with-store (->FileLifecycleStore ".test-lifecycle") (setup! :my/app) (cleanup! :other-app))
Execute body with a specific LifecycleStore bound to *lifecycle-store*.
Useful for testing or temporary overrides.
Arguments:
store - Implementation of LifecycleStore protocol
body - Expressions to execute with the store bound
Example:
(with-store (->FileLifecycleStore ".test-lifecycle")
(setup! :my/app)
(cleanup! :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 |