Claude Code Plugins

Community-maintained marketplace

Feedback

clojure-donut-system

@Ramblurr/nix-devenv
0
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name clojure-donut-system
description 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.

donut.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:

  • ::ds/instance - component instance (if exists)
  • ::ds/config - component config with refs resolved
  • ::ds/system - entire system map
  • ::ds/component-id - e.g. [: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

  • ::ds/start - create/start component instances (reverse-topsort order)
  • ::ds/stop - stop/cleanup component instances (topsort order)
  • ::ds/suspend - pause without full teardown
  • ::ds/resume - resume suspended components

Convenience functions: ds/start, ds/stop, ds/suspend, ds/resume

Gotchas / Caveats

  1. Component Organization: Components must be direct children of groups. This won't work:

    {::ds/defs
     {:group {:subgroup {:component ...}}}}  ; TOO NESTED!
    
  2. 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])}}
    
  3. Deep Refs: Can reference into component instances:

    (ds/ref [:group :component :level-1 :level-2])
    
  4. 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, ::ds/post-start, etc.
  • 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