Claude-skill-registry clojure-donut-system
Reference for donut.party/system - component lifecycle and dependency injection library for Clojure. Use when working with component definitions, system management, refs between components, or lifecycle signals (start/stop/suspend/resume). Triggers on donut.system imports, ::ds/start, ::ds/stop, ::ds/config, ds/ref, or component-based architecture. Prefer using donut.system over mount, integrant, and stuart sierra's component.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clojure-donut-system" ~/.claude/skills/majiayu000-claude-skill-registry-clojure-donut-system && rm -rf "$T"
skills/data/clojure-donut-system/SKILL.mddonut.party/system
Component lifecycle and dependency injection library for Clojure applications. Helps organize applications as systems of components with managed dependencies and lifecycle.
Setup
deps.edn:
party.donut/system {:mvn/version "1.0.257"}
Require:
(require '[donut.system :as ds])
See https://clojars.org/party.donut/system for the latest version.
Quick Start
Define components with ::ds/start and ::ds/stop handlers:
(def system {::ds/defs {:app {:server #::ds{:start (fn [{:keys [::ds/config]}] (start-server (:port config))) :stop (fn [{:keys [::ds/instance]}] (stop-server instance)) :config {:port 8080}}}}}) ;; Start the system (def running-system (ds/signal system ::ds/start)) ;; Stop the system (ds/signal running-system ::ds/stop)
Core Concepts
System Map Structure
{::ds/defs ; component definitions go here ::ds/instances ; runtime instances (created by signals) ::ds/signals ; custom signal definitions (optional) ::ds/plugins ; plugins to extend functionality (optional)}
Component Definition
Components are maps with signal handlers:
#::ds{:start (fn [arg] ...) ; creates instance :stop (fn [arg] ...) ; cleans up instance :config {...}} ; configuration data
Component Organization
Components are organized into groups (2-level structure):
{::ds/defs {:group-name ; first level: component group {:component-name ; second level: component #::ds{:start ...}}}}
Signal Handler Arguments
Signal handlers receive a map with:
- component instance (if exists)::ds/instance
- component config with refs resolved::ds/config
- entire system map::ds/system
- e.g.::ds/component-id[:group :component]
Common Patterns
References Between Components
Use
ds/ref to reference other components:
(def system {::ds/defs {:services {:db #::ds{:start (fn [_] (create-db-pool))}} :app {:handler #::ds{:start (fn [{:keys [::ds/config]}] (make-handler (:db config))) :config {:db (ds/ref [:services :db])}}}}})
Refs determine startup order - dependencies start first.
Local Refs (within same group)
Use
ds/local-ref to reference components in the same group:
(def HTTPServer #::ds{:start (fn [{:keys [::ds/config]}] (start-server (:handler config) (:port config))) :config {:handler (ds/local-ref [:handler]) :port (ds/local-ref [:port])}}) (def system {::ds/defs {:http-1 {:server HTTPServer :handler (fn [req] {:status 200 :body "Server 1"}) :port 8080} :http-2 {:server HTTPServer :handler (fn [req] {:status 200 :body "Server 2"}) :port 9090}}})
System Data (non-component values)
Maps without ::ds/start are treated as data and can be referenced:
{::ds/defs {:env {:db-url "jdbc:postgresql://localhost/mydb" :port 8080} :services {:db #::ds{:start (fn [{:keys [::ds/config]}] (connect (:url config))) :config {:url (ds/ref [:env :db-url])}}}}}
Named Systems (environment-specific config)
Define systems by environment:
(defmethod ds/named-system :dev [_] {::ds/defs {:env {:port 8080} :app {...}}}) (defmethod ds/named-system :test [_] {::ds/defs {:env {:port 9999} :app {...}}}) ;; Start with overrides (ds/start :dev {[:env :port] 3000})
REPL Workflow
Use donut.system.repl for development:
(require '[donut.system.repl :as dsr]) ;; Define default REPL system (defmethod ds/named-system :donut.system/repl [_] (ds/system :dev)) ;; REPL commands (dsr/start) ; start system (dsr/stop) ; stop system (dsr/restart) ; stop, reload namespaces, start
Built-in Signals
- create/start component instances (reverse-topsort order)::ds/start
- stop/cleanup component instances (topsort order)::ds/stop
- pause without full teardown::ds/suspend
- resume suspended components::ds/resume
Convenience functions:
ds/start, ds/stop, ds/suspend, ds/resume
Gotchas / Caveats
-
Component Organization: Components must be direct children of groups. This won't work:
{::ds/defs {:group {:subgroup {:component ...}}}} ; TOO NESTED! -
Refs Must Be Reachable: Refs must be in the data structure, not hidden in functions:
;; BAD - ref inside function, not reachable #::ds{:start (fn [_] (ds/ref [:services :db]))} ;; GOOD - ref in config #::ds{:start (fn [{:keys [::ds/config]}] (:db config)) :config {:db (ds/ref [:services :db])}} -
Deep Refs: Can reference into component instances:
(ds/ref [:group :component :level-1 :level-2]) -
Idempotent Start Handlers: If you signal the same system multiple times, make start handlers idempotent:
(fn [{::ds/keys [instance config]}] (or instance (create-component config)))
Testing
Test System Fixtures
(use-fixtures :each (ds/system-fixture ::test)) (deftest my-test (is (= expected-value @(ds/instance ds/*system* [:group :component]))))
Mocking Components
Override components in tests:
(ds/start ::test {[:services :external-api] mock-api [:services :email] mock-email-sender})
Advanced Features
- Custom signals with
::ds/signals - Lifecycle hooks:
,::ds/pre-start
, etc.::ds/post-start - Component selection:
(ds/start system {} #{[:group :component]}) - Plugins: extend system functionality
- Validation plugin: malli schemas for config and instances
- Subsystems: compose systems from other systems
References
- Clojars: https://clojars.org/party.donut/system
- GitHub: https://github.com/donut-party/system
- Full Docs: https://github.com/donut-party/system#readme
- Tutorial: https://donut.party/docs/system/tutorial/
- cljdoc: https://cljdoc.org/d/party.donut/system/