{"id":42856813,"url":"https://github.com/brianium/sandestin","last_synced_at":"2026-01-30T12:25:13.735Z","repository":{"id":332004750,"uuid":"1132413319","full_name":"brianium/sandestin","owner":"brianium","description":"An effect system facilitating discoverable effects for humans and LLMs","archived":false,"fork":false,"pushed_at":"2026-01-12T02:52:31.000Z","size":57,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-12T04:51:36.173Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brianium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-11T22:57:15.000Z","updated_at":"2026-01-12T02:52:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/brianium/sandestin","commit_stats":null,"previous_names":["brianium/sandestin"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/brianium/sandestin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fsandestin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fsandestin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fsandestin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fsandestin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brianium","download_url":"https://codeload.github.com/brianium/sandestin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fsandestin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28912909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T12:13:43.263Z","status":"ssl_error","status_checked_at":"2026-01-30T12:13:22.389Z","response_time":66,"last_error":"SSL_read: 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":[],"created_at":"2026-01-30T12:25:12.993Z","updated_at":"2026-01-30T12:25:13.713Z","avatar_url":"https://github.com/brianium.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ascolais/sandestin\n\nAn effect dispatch library for Clojure with schema-driven discoverability.\n\nSandestin provides a structured way to dispatch side effects while maintaining excellent introspection capabilities. It's designed to work seamlessly with LLM-assisted workflows and REPL-driven development.\n\n## Features\n\n- **Effect dispatch** - Dispatch vector-based effects with composable registries\n- **Actions** - Pure functions that expand into effect sequences\n- **Placeholders** - Late-bound value resolution from dispatch context\n- **Interceptors** - Lifecycle hooks for instrumentation and control flow\n- **Schema-driven** - Malli schemas for all registered items\n- **Discoverability** - Built-in functions to describe, sample, search, and inspect\n\n## Installation\n\nAdd to your `deps.edn`:\n\n```clojure\n{:deps\n {io.github.brianium/sandestin {:git/tag \"v0.5.0\" :git/sha \"526d4c5\"}}}\n```\n\n## Quick Start\n\n```clojure\n(ns myapp.core\n  (:require [ascolais.sandestin :as s]))\n\n;; Define a registry with effects\n(def my-registry\n  {::s/effects\n   {:myapp/log\n    {::s/description \"Log a message\"\n     ::s/schema [:tuple [:= :myapp/log] :string]\n     ::s/handler (fn [_ctx _system msg]\n                   (println msg)\n                   :logged)}}})\n\n;; Create a dispatch function\n(def dispatch (s/create-dispatch [my-registry]))\n\n;; Dispatch effects\n(dispatch {} {} [[:myapp/log \"Hello, Sandestin!\"]])\n;; =\u003e {:results [{:effect [:myapp/log \"Hello, Sandestin!\"], :res :logged}]\n;;     :errors []}\n```\n\n## Core Concepts\n\n### Effects\n\nEffects are side-effecting operations. Each effect has a handler that receives:\n- `ctx` - Context map with `:dispatch`, `:dispatch-data`, `:system`\n- `system` - The live system map (database connections, config, etc.)\n- `\u0026 args` - Additional arguments from the effect vector\n\n```clojure\n{::s/effects\n {:db/execute\n  {::s/description \"Execute a SQL query\"\n   ::s/schema [:tuple [:= :db/execute] :string [:* :any]]\n   ::s/system-keys [:datasource]\n   ::s/handler (fn [{:keys [dispatch]} system sql \u0026 params]\n                 (let [result (jdbc/execute! (:datasource system)\n                                             (into [sql] params))]\n                   ;; Optionally dispatch continuation effects\n                   (dispatch {:result result} [[::log \"Query complete\"]])\n                   result))}}}\n```\n\nThe `dispatch` function in handler context supports three arities:\n- `(dispatch fx)` — dispatch with current system and dispatch-data\n- `(dispatch extra-dispatch-data fx)` — merge additional dispatch-data\n- `(dispatch system-override extra-dispatch-data fx)` — merge into both system and dispatch-data\n\nThe 3-arity form enables effects to dispatch nested effects with a modified system context:\n\n```clojure\n;; Route nested effects to a different connection\n(fn [{:keys [dispatch]} system connection-key child-fx]\n  (when-some [alt-conn (get-connection (:pool system) connection-key)]\n    (dispatch {:sse alt-conn}  ; merged into system\n              {}               ; extra dispatch-data\n              child-fx)))\n```\n\n### Actions\n\nActions are pure functions that transform state into effect vectors. They receive immutable state (extracted via `::system-\u003estate`) and return effects.\n\n```clojure\n{::s/actions\n {:myapp/greet-user\n  {::s/description \"Greet a user and log the event\"\n   ::s/schema [:tuple [:= :myapp/greet-user] :string]\n   ::s/handler (fn [state username]\n                 [[:myapp/log (str \"Hello, \" username \"!\")]\n                  [:myapp/save-greeting {:user username :at (System/currentTimeMillis)}]])}}\n\n ::s/system-\u003estate\n (fn [system] (:app-state system))}\n```\n\n### Placeholders\n\nPlaceholders resolve values from dispatch-data at dispatch time. They enable late binding of values, particularly useful for async continuations.\n\n```clojure\n{::s/placeholders\n {:myapp/current-user\n  {::s/description \"Get current user from dispatch context\"\n   ::s/schema :map\n   ::s/handler (fn [dispatch-data]\n                 (:current-user dispatch-data))}}\n\n ::s/effects\n {:myapp/greet\n  {::s/handler (fn [_ctx _sys user]\n                 (str \"Hello, \" (:name user) \"!\"))}}}\n\n;; Usage with placeholder\n(dispatch {} {:current-user {:name \"Alice\"}}\n          [[:myapp/greet [:myapp/current-user]]])\n```\n\n### Interceptors\n\nInterceptors provide lifecycle hooks around dispatch, action expansion, and effect execution.\n\n```clojure\n(def logging-interceptor\n  {:id ::logging\n   :before-dispatch (fn [ctx] (tap\u003e {:event :dispatch-start}) ctx)\n   :after-dispatch (fn [ctx] (tap\u003e {:event :dispatch-end :errors (:errors ctx)}) ctx)\n   :before-effect (fn [ctx] (tap\u003e {:event :effect :effect (:effect ctx)}) ctx)})\n\n{::s/interceptors [logging-interceptor]}\n```\n\nBuilt-in interceptors:\n- `ascolais.sandestin.interceptors/fail-fast` - Stop on first error\n\n## Discoverability\n\nSandestin is designed for LLM-assisted development. Use these functions to explore registered items:\n\n### describe\n\n```clojure\n;; All items\n(s/describe dispatch)\n\n;; By type\n(s/describe dispatch :effects)\n(s/describe dispatch :actions)\n(s/describe dispatch :placeholders)\n\n;; Specific item\n(s/describe dispatch :db/execute)\n;; =\u003e {:ascolais.sandestin/key :db/execute\n;;     :ascolais.sandestin/type :effect\n;;     :ascolais.sandestin/description \"Execute a SQL query\"\n;;     :ascolais.sandestin/schema [:tuple ...]\n;;     :ascolais.sandestin/system-keys [:datasource]}\n```\n\n### sample\n\nGenerate sample data using Malli generators:\n\n```clojure\n(s/sample dispatch :db/execute)\n;; =\u003e [:db/execute \"generated-string\" 42]\n\n(s/sample dispatch :db/execute 3)\n;; =\u003e ([:db/execute ...] [:db/execute ...] [:db/execute ...])\n```\n\n### grep\n\nSearch by pattern:\n\n```clojure\n(s/grep dispatch \"database\")\n;; =\u003e ({:ascolais.sandestin/key :db/execute ...})\n\n(s/grep dispatch #\"log|save\")\n;; =\u003e items matching the regex\n```\n\n### schemas\n\nGet all schemas as a map:\n\n```clojure\n(s/schemas dispatch)\n;; =\u003e {:db/execute [:tuple ...], :myapp/log [:tuple ...], ...}\n```\n\n### system-schema\n\nGet merged system requirements:\n\n```clojure\n(s/system-schema dispatch)\n;; =\u003e {:datasource [...schema...], :config [...schema...]}\n```\n\n## Registry Structure\n\nA registry is a map with these keys (all namespaced under `ascolais.sandestin`):\n\n```clojure\n{::s/effects      {qualified-keyword -\u003e EffectRegistration}\n ::s/actions      {qualified-keyword -\u003e ActionRegistration}\n ::s/placeholders {qualified-keyword -\u003e PlaceholderRegistration}\n ::s/interceptors [Interceptor ...]\n ::s/system-schema {keyword -\u003e MalliSchema}\n ::s/system-\u003estate (fn [system] state)}\n```\n\n### Registration Maps\n\n```clojure\n;; Effect\n{::s/description \"Human-readable description\"\n ::s/schema [:tuple [:= :effect/key] ...args...]\n ::s/handler (fn [ctx system \u0026 args] result)\n ::s/system-keys [:datasource :config]}  ; optional\n\n;; Action\n{::s/description \"...\"\n ::s/schema [:tuple [:= :action/key] ...args...]\n ::s/handler (fn [state \u0026 args] [[effects...]])}\n\n;; Placeholder\n{::s/description \"...\"\n ::s/schema MalliSchema  ; schema for the resolved value\n ::s/handler (fn [dispatch-data \u0026 args] value)}\n\n;; Interceptor\n{:id :qualified/keyword\n :before-dispatch (fn [ctx] ctx)\n :after-dispatch (fn [ctx] ctx)\n :before-action (fn [ctx] ctx)\n :after-action (fn [ctx] ctx)\n :before-effect (fn [ctx] ctx)\n :after-effect (fn [ctx] ctx)}\n```\n\n## Composing Registries\n\nRegistries can be composed from multiple sources:\n\n```clojure\n(def dispatch\n  (s/create-dispatch\n    [[db/registry {:dbtype \"postgresql\"}]  ; vector [fn \u0026 args]\n     auth/registry                          ; zero-arity fn\n     {:myapp/effects {...}}]))              ; plain map\n```\n\nMerge rules:\n- Effects, actions, placeholders: later wins on conflict (with tap\u003e warning)\n- Interceptors: concatenated in order\n- system-schema: merged (later wins per key)\n- system-\u003estate: last wins\n\n## Dispatch Flow\n\n1. Run before-dispatch interceptors\n2. Interpolate placeholders in input\n3. Expand actions to effects (with before/after-action interceptors)\n4. Execute effects (with before/after-effect interceptors)\n5. Run after-dispatch interceptors\n6. Return `{:results [...] :errors [...]}`\n\n## Development\n\n### Start the REPL\n\n```bash\nclj -M:dev\n```\n\n### Development Workflow\n\n```clojure\n(dev)      ; Switch to dev namespace\n(start)    ; Start system (opens Portal)\n(reload)   ; Reload changed namespaces\n(restart)  ; Full restart\n```\n\n### Testing\n\n```bash\nclj -X:test\n```\n\n## Claude Code Skills\n\nSandestin ships with Claude Code skills for LLM-assisted development:\n\n- **`/fx-explore`** - Discover available effects, actions, and placeholders via REPL\n- **`/fx-registry`** - Create new registries following project conventions\n\nInstall by copying to your skills directory:\n\n```bash\ncp -r .claude/skills/fx-explore ~/.claude/skills/\ncp -r .claude/skills/fx-registry ~/.claude/skills/\n```\n\n## License\n\nCopyright 2025 Brian Scaturro\n\nDistributed under the Eclipse Public License version 1.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Fsandestin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrianium%2Fsandestin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Fsandestin/lists"}