diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a30232..0e14e9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,11 @@ jobs: - name: Compile with warnings as errors run: mix compile --warnings-as-errors + - name: Check DSL documentation is up-to-date + env: + MIX_ENV: dev + run: mix spark.cheat_sheets --check + - name: Run tests run: mix test diff --git a/CHANGELOG.md b/CHANGELOG.md index 45abb96..5d1a3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Auto-generated DSL reference documentation via `mix spark.cheat_sheets` +- `mix docs` alias now chains `spark.cheat_sheets` → `docs` → `spark.replace_doc_links` +- CI check to verify DSL documentation is up-to-date (`mix spark.cheat_sheets --check`) + ### Changed - State is now returned as a struct (`%MyApp.Counter.State{count: 0}`) instead of a plain atom-keyed map (`%{count: 0}`) diff --git a/documentation/dsls/DSL-DurableObject.Dsl.Extension.md b/documentation/dsls/DSL-DurableObject.Dsl.Extension.md new file mode 100644 index 0000000..861cff4 --- /dev/null +++ b/documentation/dsls/DSL-DurableObject.Dsl.Extension.md @@ -0,0 +1,126 @@ + +# DurableObject.Dsl.Extension + +Spark DSL extension defining the structure for Durable Objects. + +This extension provides three sections: + +- `state` - Define state fields +- `handlers` - Define RPC handlers +- `options` - Configure lifecycle options + + +## state +Define the state fields for this Durable Object + +### Nested DSLs + * [field](#state-field) + + + + + +### state.field +```elixir +field name, type +``` + + +A field in the Durable Object's state + + + + + +### Arguments + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`name`](#state-field-name){: #state-field-name .spark-required} | `atom` | | The name of the field | +| [`type`](#state-field-type){: #state-field-type .spark-required} | `atom` | | The type of the field (for documentation) | +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`default`](#state-field-default){: #state-field-default } | `any` | | The default value for the field | + + + + + +### Introspection + +Target: `DurableObject.Dsl.Field` + + + + +## handlers +Define the handlers (RPC methods) for this Durable Object + +### Nested DSLs + * [handler](#handlers-handler) + + + + + +### handlers.handler +```elixir +handler name +``` + + +An RPC handler for the Durable Object + + + + + +### Arguments + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`name`](#handlers-handler-name){: #handlers-handler-name .spark-required} | `atom` | | The name of the handler | +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`args`](#handlers-handler-args){: #handlers-handler-args } | `list(atom)` | `[]` | List of argument names for the handler | + + + + + +### Introspection + +Target: `DurableObject.Dsl.Handler` + + + + +## options +Configure lifecycle options + + + + + + +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`hibernate_after`](#options-hibernate_after){: #options-hibernate_after } | `pos_integer \| :infinity` | `300000` | Hibernate process after this many ms of inactivity (default: 5 minutes) | +| [`shutdown_after`](#options-shutdown_after){: #options-shutdown_after } | `pos_integer \| :infinity \| nil` | | Stop process after this many ms of inactivity (nil = never) | +| [`object_keys`](#options-object_keys){: #options-object_keys } | `:strings \| :atoms! \| :atoms` | | How to convert string keys within field values when loading from JSON. :strings (default, no conversion), :atoms! (existing atoms only, raises otherwise), :atoms (creates atoms if needed). | + + + + + + + + diff --git a/mix.exs b/mix.exs index 86aff82..edf9d57 100644 --- a/mix.exs +++ b/mix.exs @@ -34,7 +34,8 @@ defmodule DurableObject.MixProject do links: %{ "GitHub" => @source_url }, - files: ~w(lib .formatter.exs mix.exs README.md LICENSE CHANGELOG.md usage-rules.md) + files: + ~w(lib .formatter.exs mix.exs README.md LICENSE CHANGELOG.md usage-rules.md documentation) ] end @@ -43,7 +44,7 @@ defmodule DurableObject.MixProject do main: "readme", source_ref: "v#{@version}", source_url: @source_url, - extras: ["README.md", "guides/lifecycle.md", "guides/testing.md", "CHANGELOG.md", "LICENSE"], + extras: extras(), before_closing_body_tag: fn type -> if type == :html do """ @@ -57,18 +58,54 @@ defmodule DurableObject.MixProject do """ end end, - groups_for_extras: [ - Guides: ~r/guides\/.*/ - ], + groups_for_extras: groups_for_extras(), + groups_for_modules: groups_for_modules(), nest_modules_by_prefix: [ DurableObject.Cluster, DurableObject.Dsl, DurableObject.Scheduler, DurableObject.Storage + ], + spark: [ + mix_tasks: [ + Formatting: [ + Mix.Tasks.Spark.Formatter + ] + ] ] ] end + defp extras do + [ + "README.md", + "guides/lifecycle.md", + "guides/testing.md", + "CHANGELOG.md", + "LICENSE" + ] ++ Path.wildcard("documentation/**/*.md") + end + + defp groups_for_extras do + [ + Guides: ~r/guides\/.*/, + "DSL Reference": ~r/documentation\/dsls\/.*/ + ] + end + + defp groups_for_modules do + [ + "DSL & Core": [ + DurableObject, + DurableObject.Dsl, + DurableObject.Dsl.Extension + ], + Storage: ~r/DurableObject\.Storage.*/, + Scheduling: ~r/DurableObject\.Scheduler.*/, + Internals: ~r/.*/ + ] + end + # Run "mix help compile.app" to learn about applications. def application do [ @@ -82,7 +119,14 @@ defmodule DurableObject.MixProject do defp aliases do [ - test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"] + test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], + docs: [ + "spark.cheat_sheets", + "docs", + "spark.replace_doc_links" + ], + "spark.cheat_sheets": "spark.cheat_sheets --extensions DurableObject.Dsl.Extension", + "spark.formatter": "spark.formatter --extensions DurableObject.Dsl.Extension" ] end