Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
966d2ca
Restructure tutorial; add Why DI? and Reference scaffolding
darkleaf Jun 3, 2026
f98496f
Write Handling start failures chapter
darkleaf Jun 24, 2026
c006c37
Rename tutorial chapters to match new order
darkleaf Jun 24, 2026
6c6375b
Move Multimethods to How-to and tighten the prose
darkleaf Jun 24, 2026
d65e9de
Move Side dependencies to How-to and expand the rationale
darkleaf Jun 24, 2026
e72c68f
Move Log to How-to and simplify the prose
darkleaf Jun 24, 2026
9c18442
Move ns-publics to How-to and add it to the TOC
darkleaf Jun 24, 2026
c943388
Move Multi-system to How-to and add a concrete use case
darkleaf Jun 24, 2026
7bdc5c3
Convert Middleware types reference to plain markdown
darkleaf Jun 24, 2026
07e7b45
Update CLAUDE.md with conventions from the v6 docs restructure
darkleaf Jun 24, 2026
71af1e1
Rework the middleware reference into 'The middleware argument'
darkleaf Jun 27, 2026
c9734da
Add "Recurring mistakes to avoid" and the component-not-node rule
darkleaf Jun 28, 2026
bfc1e20
Add the Design article
darkleaf Jun 28, 2026
8a6d1d7
Wire Why DI? into the cljdoc navigation
darkleaf Jul 1, 2026
51d8001
Drop the Factory reference page from the plan
darkleaf Jul 1, 2026
1cca6cb
Expand the di/inspect docstring
darkleaf Jul 1, 2026
dfe4087
Move Inspect from Tutorial to a generated Reference page
darkleaf Jul 1, 2026
2bca80c
Rework Two Databases how-to into "One factory, many keys"
darkleaf Jul 1, 2026
d1c93a3
Disable the override-deps tutorial draft
darkleaf Jul 1, 2026
9d71262
Add the Visualizing your system how-to
darkleaf Jul 1, 2026
9f9dd69
Record v6 docs restructure completion in planning notes
darkleaf Jul 1, 2026
2f2d0ff
Rework middleware terminology and fix doc review findings
darkleaf Jul 1, 2026
dfc376a
nav
darkleaf Jul 1, 2026
306ffe1
Document local cljdoc preview flow in CLAUDE.md
darkleaf Jul 2, 2026
af2131b
Add the Reusing components between tests how-to
darkleaf Jul 2, 2026
76dc3f6
Require an explicit user request before committing
darkleaf Jul 2, 2026
dca5404
Add the Feature flags how-to
darkleaf Jul 2, 2026
6bea390
Compress CLAUDE.md
darkleaf Jul 3, 2026
04a5c3e
Strip CLAUDE.md to practical notes
darkleaf Jul 3, 2026
a528ae7
Add the Startup checks how-to
darkleaf Jul 3, 2026
b30f132
Note that every update-key argument is a factory
darkleaf Jul 3, 2026
5079f00
Add the Documenting environment variables how-to
darkleaf Jul 3, 2026
a2cf7df
Add the Instrumenting services how-to
darkleaf Jul 3, 2026
e0537d9
Tips: a key does not need a require
darkleaf Jul 3, 2026
5be9bca
Tips: an EDN file instead of dotenv
darkleaf Jul 3, 2026
98cd832
remove plan
darkleaf Jul 3, 2026
928426c
Docs review fixes: docstrings, tutorial prose, links
darkleaf Jul 4, 2026
ad7456d
Add prepend-side-dependency
darkleaf Jul 4, 2026
a32f1bb
Rewrite the Side dependencies how-to for prepend-side-dependency
darkleaf Jul 4, 2026
ba82fa7
Add the Graceful shutdown how-to as a draft
darkleaf Jul 4, 2026
64009d8
Docs: fix untrue and unclear wording found in review
darkleaf Jul 4, 2026
ebd4b73
CI: substitute %SHA% in Readme.md for the cljdoc commit
darkleaf Jul 5, 2026
281f763
Docs: fix errors and unclear wording found in review
darkleaf Jul 6, 2026
ee94c11
Docs: rewrite the Graceful shutdown how-to and publish it
darkleaf Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ jobs:
- name: Generate tutorial markdown
run: ./script/tutorial-to-md.sh

- name: Substitute %TAG% in Readme.md
run: sed -i "s/%TAG%/${GITHUB_REF_NAME}/g" Readme.md
- name: Substitute %TAG% and %SHA% in Readme.md
run: |
sed -i "s/%TAG%/${GITHUB_REF_NAME}/g" Readme.md
sed -i "s/%SHA%/${GITHUB_SHA}/g" Readme.md

- name: Commit generated docs (detached HEAD)
env:
Expand All @@ -63,7 +65,7 @@ jobs:
GIT_COMMITTER_NAME: ${{ github.actor }}
GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com
run: |
git add -f doc/tutorial
git add -f doc/tutorial doc/how_to doc/reference
git add Readme.md
git commit -m "Generate cljdoc docs for ${GITHUB_REF_NAME}"

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
/target
/pom.xml
/doc/tutorial/
/doc/how_to/*_test.md
/doc/reference/*_test.md
#/doc/_*.md
.clj-kondo/
.lsp/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Unreleased

+ Add `di/prepend-side-dependency`: a side dependency that is built
before the root and its dependencies. `di/add-side-dependency`
keeps building them after the root, as before.
+ Docs: Backtick code references in public API docstrings so cljdoc
renders them as inline code.

Expand Down
147 changes: 45 additions & 102 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,111 +2,54 @@

## Workflow

- **Do not `git push origin master` automatically.** Commit locally and
wait for the user to push. `master` is protected (PRs required) and
direct pushes only work via admin bypass, so each one is a deliberate
choice the user makes.
- Always include the `Co-Authored-By: Claude Opus 4.7 (1M context)
<noreply@anthropic.com>` trailer in commits authored together.
- **Commit only when the user asks** (amends included). Finish the
work, leave changes in the working tree, and don't announce this
default — mention git state only when surprising or asked.
- **Never `git push origin master` yourself.** `master` is protected
(PRs required); direct pushes work only via the user's admin bypass,
so each one is their deliberate choice.
- Commit trailer: `Co-Authored-By: <% ACTUAL MODEL %> <noreply@anthropic.com>`.

## cljdoc

Docs are published on cljdoc.org. Source articles live in `doc/`:

- `doc/cljdoc.edn` — navigation tree (`{:cljdoc.doc/tree ...}`).
- `doc/integrant.md`, `doc/example.md` — checked-in articles.
- `doc/tutorial/*.md` — **generated** from `test/darkleaf/di/tutorial/*.clj`
by `script/tutorial-to-md.sh`. Path is in `.gitignore`; the files only
exist in CI-built release commits, never on `master`.

### Release flow

`git push origin X.Y.Z` triggers `.github/workflows/ci.yml` → `release` job:

1. Runs the tutorial-to-md script.
2. Substitutes `%TAG%` in `Readme.md` with the tag name.
3. Commits the result on a detached HEAD.
4. Tags that commit `cljdoc-X.Y.Z` and pushes the tag (only the tag; the
commit itself is not on any branch).
5. Builds the jar with `RELEASE_VERSION=X.Y.Z` and deploys to Clojars.

The deployed pom's `<scm><tag>` is the SHA of the detached commit, so
cljdoc fetches docs from there (not from `master`).

### Updating docs without a new release

cljdoc supports a `cljdoc-<VERSION>` tag override: it imports articles and
`cljdoc.edn` from that tag instead of the SCM commit. This affects only
articles and TOC — docstrings still come from the published jar.

**Critical gotcha:** the new commit you tag MUST be a descendant of the
original CI-generated `cljdoc-<VERSION>` commit (whose SHA is hard-coded
in the deployed pom). If you make the new commit on top of `master`,
GitHub eventually GCs the orphaned SCM SHA, cljdoc clone fails with
`unknown-revision`, and the build silently produces a docs page with no
articles.

Correct procedure:

```
# 1. Make the article changes on master (so they live in repo history)
git commit -m "..."
git push origin master

# 2. Rebuild the cljdoc-<VERSION> commit on top of the prior one
git worktree add --detach /tmp/wt cljdoc-X.Y.Z # or the prior SHA
cp doc/<changed-files> /tmp/wt/doc/
(cd /tmp/wt && git add . && \
git -c commit.gpgsign=false commit -m "..." && \
git -c tag.gpgsign=false tag -f cljdoc-X.Y.Z)

# 3. Force-push the moved tag
git push origin :refs/tags/cljdoc-X.Y.Z
git push origin cljdoc-X.Y.Z
git worktree remove /tmp/wt
```

Then trigger a rebuild on `https://cljdoc.org/d/org.clojars.darkleaf/di/X.Y.Z`.

### Article cross-links

cljdoc rewrites markdown links between articles. Use either:

- relative to the source file: `tutorial/a_intro_test.md` (from `doc/example.md`)
- root-relative: `/doc/tutorial/a_intro_test.md`

A bare `doc/tutorial/a_intro_test.md` (no leading slash) is **not**
recognised and renders as a broken external link.

## Release/build gotchas (not cljdoc-specific but related)

- `build.clj` reads version from `RELEASE_VERSION` env var. Local builds
produce a `DEV-SNAPSHOT` jar.
- The published pom must declare `org.clojure/clojure` explicitly in root
`:deps` of `deps.edn`. Otherwise tools.build's basis inherits Clojure
1.10.3 from the system `deps.edn`, the pom advertises 1.10.3, and the
cljdoc analyzer launches with 1.10.3 — which does not understand
`:as-alias` and fails with a fake cyclic-load error.
- The published pom must include a `<licenses>` block (Clojars rejects
uploads without one with `403 Forbidden`). Done via `:pom-data` in
`b/write-pom`; requires `tools.build` ≥ 0.10.
- `deps-deploy` reads the pom from the filesystem, not from inside the
jar. The `:deploy` alias passes `:pom-file
"target/classes/META-INF/maven/org.clojars.darkleaf/di/pom.xml"` so it
finds the one `b/write-pom` produced.
- Local git config has `tag.gpgsign=true`. To make a release-style
lightweight tag, override per-command:
`git -c tag.gpgsign=false tag X.Y.Z`.
- Branch `master` is protected (PRs required). Direct pushes work
because the user has admin bypass, but each one logs a "Bypassed rule
violations" entry.
Docs live on cljdoc.org. Tutorial/how-to articles are generated from
the matching test files by `script/tutorial-to-md.sh`; the generated
`.md` are gitignored and built in CI.

- **Release flow:** pushing tag `X.Y.Z` makes CI generate the
articles, commit them on a detached HEAD, tag that commit
`cljdoc-X.Y.Z`, and deploy to Clojars. The pom's `<scm><tag>` points
cljdoc at that detached commit, not at `master`.
- **Updating docs without a new release:** cljdoc re-imports articles
from the `cljdoc-X.Y.Z` tag. The new tagged commit MUST be a
descendant of the existing cljdoc commit — its SHA is hard-coded in
the deployed pom, and orphaning it makes the build silently lose
all articles. So: rebuild the commit on top of the old tag,
force-move the tag, trigger a rebuild on cljdoc.org.
- **Cross-links:** between articles use root-relative `/doc/...`
paths (a bare `doc/...` renders as a broken external link).
Article → API var: full cljdoc URL with the `CURRENT` version
placeholder, e.g.
`https://cljdoc.org/d/org.clojars.darkleaf/di/CURRENT/api/darkleaf.di.core#->memoize`.

## Release/build gotchas

- `build.clj` reads the version from `RELEASE_VERSION`; local builds
produce `DEV-SNAPSHOT`.
- Root `:deps` of `deps.edn` must declare `org.clojure/clojure`
explicitly — otherwise the pom inherits 1.10.3 from the system
deps.edn and the cljdoc analyzer fails on `:as-alias` with a fake
cyclic-load error.
- The pom must include a `<licenses>` block (Clojars rejects with 403).
Done via `:pom-data` in `b/write-pom`; needs `tools.build` ≥ 0.10.
- `deps-deploy` reads the pom from the filesystem: the `:deploy` alias
passes `:pom-file "target/classes/META-INF/maven/org.clojars.darkleaf/di/pom.xml"`.
- Local git has `tag.gpgsign=true`; for release-style lightweight tags
use `git -c tag.gpgsign=false tag X.Y.Z`.

## Clojars

Once a non-snapshot version is deployed, it cannot be re-deployed even
if the previous attempt failed validation. A burned version (`3.6.1`
was an example: missing license blew up after the upload) requires
bumping to a fresh version, not re-attempting the same tag.

A deploy token is required (not the account password). Stored in
GitHub Actions secrets as `CLOJARS_USERNAME` / `CLOJARS_PASSWORD`.
- A non-snapshot version can never be re-deployed, even after a failed
validation (`3.6.1` was burned this way) — bump to a fresh version.
- Deploys need a token (not the account password), stored in GitHub
Actions secrets `CLOJARS_USERNAME` / `CLOJARS_PASSWORD`.
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ to define a graph of functions and stateful objects.
"PORT" "9090"})
```

It is just a short snippet, please see [example app](https://github.com/darkleaf/di/tree/master/example).
For a complete application, see the [example app](https://github.com/darkleaf/di/tree/master/example).

## Install

Expand Down Expand Up @@ -68,4 +68,4 @@ starting with [user.clj](https://github.com/darkleaf/di/blob/master/example/dev/

Copyright © 2022 Mikhail Kuzmin

Licensed under Eclipse Public License v2.0 (see [LICENSE](LICENSE)).
Licensed under Eclipse Public License v2.0 (see [LICENSE](https://github.com/darkleaf/di/blob/master/LICENSE)).
57 changes: 35 additions & 22 deletions doc/cljdoc.edn
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
{:cljdoc.doc/tree
[["Readme" {:file "Readme.md"}]
["Changelog" {:file "CHANGELOG.md"}]
["Tutorial"
["Base"
["Intro" {:file "doc/tutorial/a_intro_test.md"}]
["Dependencies" {:file "doc/tutorial/b_dependencies_test.md"}]
["Stop" {:file "doc/tutorial/c_stop_test.md"}]
["Registries" {:file "doc/tutorial/l_registries_test.md"}]
["Abstractions" {:file "doc/tutorial/m_abstractions_test.md"}]
["Env" {:file "doc/tutorial/n_env_test.md"}]
["Data DSL" {:file "doc/tutorial/o_data_dsl_test.md"}]
["Derive" {:file "doc/tutorial/p_derive_test.md"}]
["Starting many keys" {:file "doc/tutorial/q_starting_many_keys_test.md"}]
["Multimethods" {:file "doc/tutorial/r_multimethods_test.md"}]]
["Advanced"
["Add a side dependency" {:file "doc/tutorial/x_add_side_dependency_test.md"}]
["Update key" {:file "doc/tutorial/x_update_key_test.md"}]
["Log" {:file "doc/tutorial/x_log_test.md"}]
["Inspect" {:file "doc/tutorial/x_inspect_test.md"}]
["Graceful stop" {:file "doc/tutorial/y_graceful_stop_test.md"}]
["Multi arity service" {:file "doc/tutorial/y_multi_arity_service_test.md"}]
["Multi system" {:file "doc/tutorial/z_multi_system_test.md"}]
["Two Databases" {:file "doc/tutorial/z_two_databases_test.md"}]]]
["Why DI?" {:file "doc/why_di.md"}]
["Design" {:file "doc/design.md"}]
["Integrant vs DI" {:file "doc/integrant.md"}]
["Example app" {:file "doc/example.md"}]
["Integrant vs DI" {:file "doc/integrant.md"}]]}
["Tutorial"
["Your first system" {:file "doc/tutorial/a_your_first_system_test.md"}]
["Dependencies" {:file "doc/tutorial/b_dependencies_test.md"}]
["Stopping components" {:file "doc/tutorial/c_stopping_components_test.md"}]
["Interactive development" {:file "doc/tutorial/d_interactive_development_test.md"}]
["Registries" {:file "doc/tutorial/e_registries_test.md"}]
["Abstractions" {:file "doc/tutorial/f_abstractions_test.md"}]
["Environment variables" {:file "doc/tutorial/g_environment_variables_test.md"}]
["Starting many keys" {:file "doc/tutorial/h_starting_many_keys_test.md"}]
["Wiring inside data" {:file "doc/tutorial/i_wiring_inside_data_test.md"}]
["Transforming values" {:file "doc/tutorial/j_transforming_values_test.md"}]
["Composition with update-key" {:file "doc/tutorial/k_composition_with_update_key_test.md"}]
["Handling start failures" {:file "doc/tutorial/l_handling_start_failures_test.md"}]]
["How-to"
["Tips" {:file "doc/how_to/tips_test.md"}]
["Side dependencies" {:file "doc/how_to/side_dependencies_test.md"}]
["Feature flags" {:file "doc/how_to/feature_flags_test.md"}]
["Startup checks" {:file "doc/how_to/startup_checks_test.md"}]
["Graceful shutdown" {:file "doc/how_to/graceful_shutdown.md"}]
["Logging system lifecycle" {:file "doc/how_to/log_test.md"}]
["Multimethods" {:file "doc/how_to/multimethods_test.md"}]
["All public vars as a component" {:file "doc/how_to/ns_publics_test.md"}]
["Multi-arity services" {:file "doc/how_to/multi_arity_service_test.md"}]
["Multiple systems" {:file "doc/how_to/multiple_systems_test.md"}]
["Reusing components between tests" {:file "doc/how_to/reusing_components_between_tests_test.md"}]
["One factory, many keys" {:file "doc/how_to/one_factory_many_keys_test.md"}]
["Visualizing your system" {:file "doc/how_to/visualizing_test.md"}]
["Documenting environment variables" {:file "doc/how_to/env_docs.md"}]
["Instrumenting services" {:file "doc/how_to/instrumenting_services.md"}]]
["Reference"
["Inspect" {:file "doc/reference/inspect_test.md"}]
["The middleware argument" {:file "doc/reference/middleware_argument.md"}]]]}
Loading
Loading