assistant 1.0.0.rc1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,154 @@
1
- ---
2
- title: RBS and types
3
- parent: Guides
4
- nav_order: 5
5
- ---
6
-
1
+ <!-- markdownlint-disable MD013 MD024 -->
7
2
  # RBS and types
8
3
 
9
- > **Status:** placeholder full content lands in
10
- > [P4](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md) of the GitHub Pages plan. For now,
11
- > see the [RBS subsection in the Inputs guide](inputs.md#using-assistant-rbs-for-steep-users)
12
- > and the [Recipe in the 0.x 1.x migration guide](https://github.com/ramongr/assistant/blob/main/docs/v1/06-migration-0x-to-1.md#recipe-binassistant-rbs-for-steep-users).
4
+ > **TL;DR**The gem ships hand-written RBS for Assistant's own public API.
5
+ > Your service subclasses still need per-class signatures because
6
+ > `input :email, type: String` creates `#email` and `#email?` with
7
+ > `define_method`. Run `bundle exec assistant-rbs lib --output sig` after
8
+ > editing services, commit the generated `.rbs` files, and let Steep check
9
+ > your call sites against them.
10
+
11
+ Assistant is Steep-ready in two layers:
12
+
13
+ - `lib/**/*.rbs` describes the gem's stable public surface:
14
+ `Assistant::Service`, `Assistant::LogItem`, `Assistant::LogList`, the input
15
+ DSL, callbacks, and the RBS generator internals.
16
+ - `assistant-rbs` generates signatures for your own `Assistant::Service`
17
+ subclasses, where the input names and return types are only known after the
18
+ class body runs.
19
+
20
+ ## Why a generator is needed
21
+
22
+ This Ruby code is clear at runtime:
23
+
24
+ ```ruby
25
+ class CreateUser < Assistant::Service
26
+ input :email, type: String, required: true
27
+ input :name, type: String, required: true
28
+ input :role, type: [String, Symbol], allow_nil: true, default: nil
29
+
30
+ def execute
31
+ { email:, name:, role: role || :member }
32
+ end
33
+ end
34
+ ```
35
+
36
+ When the class loads, `Service.input` defines `#email`, `#email?`, `#name`,
37
+ `#name?`, `#role`, and `#role?`. A generic `Assistant::Service.rbs` cannot list
38
+ those methods because every subclass has different input names. The generator
39
+ loads your service files, inspects their `input_definitions`, and writes the
40
+ subclass-specific signatures Steep needs.
41
+
42
+ ## Generate signatures
43
+
44
+ Run the CLI against the directory or file that defines your services:
45
+
46
+ ```sh
47
+ bundle exec assistant-rbs lib --output sig
48
+ ```
49
+
50
+ When working from a checkout of this repo, the executable can be invoked
51
+ directly:
52
+
53
+ ```sh
54
+ bundle exec exe/assistant-rbs examples/rbs_generator/create_user.rb \
55
+ --output examples/rbs_generator/sig
56
+ ```
57
+
58
+ For the `CreateUser` service above, the output is:
59
+
60
+ ```rbs
61
+ # Generated by assistant-rbs; do not edit.
62
+
63
+ class CreateUser < Assistant::Service
64
+
65
+ def email: () -> String
66
+ def email?: () -> bool
67
+ def name: () -> String
68
+ def name?: () -> bool
69
+ def role: () -> (String | Symbol)?
70
+ def role?: () -> bool
71
+
72
+ end
73
+ ```
74
+
75
+ Type rendering rules are intentionally small:
76
+
77
+ - `type: String` becomes `String`.
78
+ - `type: [String, Symbol]` becomes `(String | Symbol)`.
79
+ - `allow_nil: true` appends `?`, so the role example becomes
80
+ `(String | Symbol)?`.
81
+
82
+ ## Wire Steep
83
+
84
+ Add the generated directory to your `Steepfile` alongside the gem's
85
+ signatures:
86
+
87
+ ```ruby
88
+ target :app do
89
+ signature 'sig'
90
+ check 'lib'
91
+ end
92
+ ```
93
+
94
+ Then run:
95
+
96
+ ```sh
97
+ bundle exec steep check --jobs=1
98
+ ```
99
+
100
+ The runnable repo example keeps a self-contained Steep target in
101
+ [`examples/rbs_generator/Steepfile`](../../examples/rbs_generator/Steepfile):
102
+
103
+ ```ruby
104
+ target :rbs_generator_example do
105
+ signature '../../lib'
106
+ signature 'sig'
107
+
108
+ check 'create_user.rb'
109
+ check 'type_probe.rb'
110
+ end
111
+ ```
112
+
113
+ That lets `type_probe.rb` call `service.email.upcase` and `service.name.length`
114
+ with real `String` knowledge instead of `untyped`.
115
+
116
+ ## Keep generated files safe
117
+
118
+ Every generated file starts with this marker:
119
+
120
+ ```text
121
+ # Generated by assistant-rbs; do not edit.
122
+ ```
123
+
124
+ The writer only overwrites files with that exact first line. If a file already
125
+ exists without the marker, the CLI prints a `[skipped]` warning and leaves it
126
+ alone, so hand-written sidecars are safe.
127
+
128
+ Commit generated signatures and regenerate them whenever a service's `input`
129
+ declarations change. The current CLI does not have a `--diff-only` mode; if you
130
+ want CI drift detection, run the generator into a temporary directory and
131
+ compare it with the committed `sig/` tree.
132
+
133
+ ## Common pitfalls
134
+
135
+ - **Expecting generated validators in the RBS output.** The CLI declares input
136
+ getters and presence predicates only (`#email`, `#email?`). The runtime also
137
+ creates validator helpers such as `#valid_type_email?`, but those are internal
138
+ validation plumbing rather than the generated user-facing signature surface.
139
+ - **Forgetting to load dependencies before generation.** The CLI `require`s the
140
+ files you point it at. If those files assume framework constants are already
141
+ loaded, require your app boot file or pass a directory that loads cleanly.
142
+ - **Using anonymous input types.** `type:` must be a named class or module for
143
+ the renderer to write an RBS type name.
144
+ - **Expecting `#execute` return types.** The generated file covers input
145
+ readers. Add hand-written signatures for domain methods if your app needs
146
+ stricter return types elsewhere.
147
+
148
+ ## See also
13
149
 
14
- This page will cover the `bin/assistant-rbs` per-class generator (M11),
15
- the R1 metaprogramming limitation that motivates it, and how to wire
16
- the generated `.rbs` files into a Steep-checked project.
150
+ - [Inputs guide](./inputs.md) every `input` option the generator reads.
151
+ - [RBS generator example](../examples/rbs-generator.md) runnable service,
152
+ generated signature, and Steep probe.
153
+ - [API reference: `assistant-rbs` CLI](../api-reference.md#assistant-rbs-cli).
154
+ - [0.x to 1.x migration recipe](https://github.com/ramongr/assistant/blob/main/docs/v1/06-migration-0x-to-1.md#recipe-binassistant-rbs-for-steep-users).
@@ -1,9 +1,3 @@
1
- ---
2
- title: Validation
3
- parent: Guides
4
- nav_order: 2
5
- ---
6
-
7
1
  <!-- markdownlint-disable MD013 MD024 -->
8
2
  # Validation
9
3
 
@@ -24,7 +18,7 @@ conditional patterns.
24
18
  For every `Service.input :name, type: T, required: ..., if: ...`,
25
19
  the gem generates and runs:
26
20
 
27
- - `#valid_type_name?` — type check (or multi-type with M3 union).
21
+ - `#valid_type_name?` — type check (also handles multi-type unions).
28
22
  - `#valid_required_name?` — presence check, when `required: true`.
29
23
  - `#valid_required_conditional_name?` — presence + predicate, when
30
24
  `required: true` *and* `if:` are both supplied.
@@ -34,6 +28,30 @@ and `valid_type_*?` method that matches by naming convention before
34
28
  calling your `#validate`. Failures are logged as error-level
35
29
  `LogItem`s and short-circuit `#execute`.
36
30
 
31
+ The full per-request lifecycle:
32
+
33
+ ```mermaid
34
+ flowchart TD
35
+ Run([Service#run]) --> NotifyStart[notifier: :service_started]
36
+ NotifyStart --> Defaults[apply_input_defaults]
37
+ Defaults --> Declarative[Run every<br/>valid_required_*?, valid_required_conditional_*?,<br/>valid_type_*?]
38
+ Declarative --> Validate[Call your #validate]
39
+ Validate --> NotifyValidated[notifier: :service_validated]
40
+ NotifyValidated --> Errors{Any errors logged?}
41
+ Errors -- Yes --> Skip[Skip #execute]
42
+ Skip --> Failed[Return :with_errors payload]
43
+ Failed --> NotifyFailed[notifier: :service_failed]
44
+ Errors -- No --> Execute[Call #execute<br/>through callback chain]
45
+ Execute --> NotifyExecuted[notifier: :service_executed]
46
+ NotifyExecuted --> Result{Errors logged<br/>during execute?}
47
+ Result -- Yes --> Failed
48
+ Result -- No --> Ok[Return :ok or :with_warnings payload]
49
+ ```
50
+
51
+ The notifier hooks fire even when validation short-circuits; see
52
+ [Composing services](./composing-services.md#instrumentation-notifier)
53
+ for how to subscribe.
54
+
37
55
  ## Adding your own checks with `#validate`
38
56
 
39
57
  Override `#validate` to log domain-specific errors:
@@ -130,7 +148,7 @@ be truthy — so the canonical use is "I need this to be present
130
148
  [`inputs.md`](./inputs.md#if-conditional-requirement) for the
131
149
  inverse pattern.
132
150
 
133
- ## `LogItem.new` raises in 1.0 (M10)
151
+ ## `LogItem.new` raises in 1.0
134
152
 
135
153
  Constructing a `LogItem` directly with invalid attributes now raises
136
154
  `ArgumentError`. The `#valid?` family is kept for introspection but
@@ -164,7 +182,7 @@ catalogue.
164
182
  Unexpected exceptions propagate (the gem catches exceptions only
165
183
  in `before_execute` / `around_execute` / `after_execute` hooks).
166
184
  - **Building `LogItem.new(...)` and pushing it onto `#logs`.** Use the
167
- helpers; they apply the same M10 strict construction and keep your
185
+ helpers; they apply the same strict construction and keep your
168
186
  call sites readable.
169
187
  - **Forgetting that `#validate` runs even when a declarative check
170
188
  already failed.** Either guard `#validate` with `return if
@@ -176,5 +194,5 @@ catalogue.
176
194
  generated `valid_*` predicates.
177
195
  - [Logging and results](./logging-and-results.md) — the helpers, the
178
196
  full result hash, log filtering.
179
- - [API reference: LogItem](../api-reference.md#assistantlogitem).
180
- - [API reference: LogList](../api-reference.md#assistantloglist).
197
+ - [API reference: LogItem](../api-reference.md#assistant-logitem).
198
+ - [API reference: LogList](../api-reference.md#assistant-loglist).
data/docs/index.html ADDED
@@ -0,0 +1,291 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <meta name="viewport" content="width=device-width,initial-scale=1">
7
+
8
+ <title>Assistant &mdash; tiny, dependency-free soft-fail service objects for Ruby</title>
9
+ <meta name="description"
10
+ content="Assistant is a tiny, dependency-free Ruby gem for soft-fail service objects with a uniform result shape, structured log items, and first-class RBS/Steep support.">
11
+ <meta name="theme-color" content="#2f4f5a">
12
+
13
+ <link rel="icon" type="image/svg+xml" href="/assistant/_media/assistant_icon.svg">
14
+ <link rel="apple-touch-icon" sizes="180x180" href="/assistant/_media/apple-touch-icon.png">
15
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
+ <link href="https://fonts.googleapis.com/css2?family=MuseoModerno:ital,wght@0,800;1,800&display=swap" rel="stylesheet">
18
+
19
+ <!-- Open Graph / Twitter card -->
20
+ <meta property="og:title" content="Assistant">
21
+ <meta property="og:description" content="Tiny, dependency-free soft-fail service objects for Ruby.">
22
+ <meta property="og:image" content="https://ramongr.github.io/assistant/_media/repo-card.png">
23
+ <meta property="og:image:width" content="1200">
24
+ <meta property="og:image:height" content="630">
25
+ <meta name="twitter:card" content="summary_large_image">
26
+ <meta name="twitter:image" content="https://ramongr.github.io/assistant/_media/repo-card.png">
27
+
28
+ <!-- Docsify default theme + dark/light theme overlay -->
29
+ <link rel="stylesheet"
30
+ href="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/style.min.css"
31
+ title="docsify-darklight-theme"
32
+ type="text/css" />
33
+
34
+ <!-- Prism syntax-highlighting theme (light by default; swapped on dark toggle) -->
35
+ <link rel="stylesheet"
36
+ href="//cdn.jsdelivr.net/npm/prism-themes/themes/prism-material-light.min.css"
37
+ id="prism-theme"
38
+ type="text/css" />
39
+
40
+ <style>
41
+ :root {
42
+ /* Brand palette
43
+ #22223b — Space Cadet (dark text / dark bg)
44
+ #a07178 — Rose Taupe (secondary accent / success)
45
+ #4d7c8a — Steel Teal (dark-mode highlight; brand asset color)
46
+ #2f4f5a — Steel Teal Dark (light-mode links / inline code /
47
+ theme color — 7.87:1 on #f4f2f3,
48
+ 8.30:1 on #ffffff, both AAA)
49
+ #f4f2f3 — Cultured (light background)
50
+ #ffd166 — Naples Yellow (primary accent) */
51
+ --theme-color: #2f4f5a;
52
+ --assistant-space-cadet: #22223b;
53
+ --assistant-naples-yellow: #ffd166;
54
+ }
55
+ .app-name-link {
56
+ align-items: center;
57
+ display: flex;
58
+ justify-content: center;
59
+ padding: 12px 0 16px;
60
+ }
61
+ .assistant-app-name {
62
+ display: block;
63
+ font-family: 'MuseoModerno', 'PT Sans', system-ui, sans-serif;
64
+ font-size: 5.5rem;
65
+ font-style: normal;
66
+ font-weight: 800;
67
+ letter-spacing: -0.09em;
68
+ line-height: 0.82;
69
+ }
70
+ .assistant-home-wordmark {
71
+ font-family: 'MuseoModerno', 'PT Sans', system-ui, sans-serif;
72
+ font-size: clamp(68px, 16vw, 256px) !important;
73
+ font-style: normal;
74
+ font-weight: 800;
75
+ letter-spacing: -1.44px;
76
+ line-height: 0.82;
77
+ margin-bottom: 3.125rem !important;
78
+ text-align: center;
79
+ }
80
+ body.assistant-sidebar-shell .sidebar {
81
+ padding-top: 4rem;
82
+ }
83
+ body.assistant-sidebar-shell .sidebar-toggle {
84
+ align-items: center;
85
+ background: var(--assistant-space-cadet);
86
+ border: 1px solid rgba(255, 209, 102, 0.42);
87
+ border-radius: 999px;
88
+ bottom: auto;
89
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.24);
90
+ color: var(--assistant-naples-yellow);
91
+ display: flex;
92
+ height: 2.25rem;
93
+ justify-content: center;
94
+ left: 0.875rem;
95
+ padding: 0;
96
+ top: 0.875rem;
97
+ transition: background-color 0.2s ease, border-color 0.2s ease,
98
+ color 0.2s ease, transform 0.2s ease;
99
+ width: 2.25rem;
100
+ z-index: 60;
101
+ }
102
+ body.assistant-sidebar-shell .sidebar-toggle:hover {
103
+ background: var(--assistant-naples-yellow);
104
+ border-color: var(--assistant-naples-yellow);
105
+ color: var(--assistant-space-cadet);
106
+ transform: translateY(-1px);
107
+ }
108
+ body.assistant-sidebar-shell .sidebar-toggle span {
109
+ display: none;
110
+ }
111
+ body.assistant-sidebar-shell .sidebar-toggle::before {
112
+ content: "\00d7";
113
+ font-size: 1.75rem;
114
+ font-weight: 700;
115
+ line-height: 1;
116
+ transform: translateY(-0.08em);
117
+ }
118
+ body.assistant-sidebar-shell.close .sidebar-toggle {
119
+ background: var(--assistant-naples-yellow);
120
+ border-color: var(--assistant-naples-yellow);
121
+ color: var(--assistant-space-cadet);
122
+ }
123
+ body.assistant-sidebar-shell.close .sidebar-toggle:hover {
124
+ background: var(--assistant-space-cadet);
125
+ border-color: var(--assistant-space-cadet);
126
+ color: var(--assistant-naples-yellow);
127
+ }
128
+ body.assistant-sidebar-shell.close .sidebar-toggle::before {
129
+ content: "\2630";
130
+ font-size: 1.25rem;
131
+ transform: none;
132
+ }
133
+ </style>
134
+ </head>
135
+ <body class="assistant-sidebar-shell">
136
+ <div id="app">Loading documentation&hellip;</div>
137
+
138
+ <script>
139
+ // Re-applies the matching Prism theme stylesheet whenever the
140
+ // docsify-darklight-theme toggle is clicked. Without this, switching to
141
+ // dark mode keeps the light Prism palette on code blocks.
142
+ const prismThemeSwitcher = function (hook) {
143
+ hook.doneEach(function () {
144
+ const toggle = document.querySelector('.docsify-darklight-theme');
145
+ if (!toggle || toggle.dataset.bound === '1') return;
146
+ toggle.dataset.bound = '1';
147
+ toggle.addEventListener('click', function () {
148
+ const link = document.getElementById('prism-theme');
149
+ if (!link) return;
150
+ const dark = link.href.includes('prism-material-dark');
151
+ link.href = dark
152
+ ? '//cdn.jsdelivr.net/npm/prism-themes/themes/prism-material-light.min.css'
153
+ : '//cdn.jsdelivr.net/npm/prism-themes/themes/prism-material-dark.min.css';
154
+ });
155
+ });
156
+ };
157
+
158
+ // Page footer rendered by docsify after each markdown body.
159
+ const pageFooter = function (hook) {
160
+ const footer = [
161
+ '<hr/>',
162
+ '<footer style="text-align:center; opacity:.7; font-size:.9em; padding:16px 0;">',
163
+ ' <span>Assistant &middot; MIT-licensed &middot; ',
164
+ ' <a href="https://github.com/ramongr/assistant" target="_blank" rel="noopener">GitHub</a> &middot; ',
165
+ ' <a href="https://rubygems.org/gems/assistant" target="_blank" rel="noopener">RubyGems</a>',
166
+ ' </span>',
167
+ '</footer>'
168
+ ].join('');
169
+ hook.afterEach(function (html) { return html + footer; });
170
+ };
171
+
172
+ window.$docsify = {
173
+ name: '<span class="assistant-app-name" aria-label="Assistant">a</span>',
174
+ nameLink: '#/',
175
+ repo: 'ramongr/assistant',
176
+ basePath: '/assistant/',
177
+ homepage: 'index.md',
178
+ coverpage: false,
179
+ loadSidebar: true,
180
+ alias: {
181
+ '/.*/_sidebar.md': '/_sidebar.md'
182
+ },
183
+ loadNavbar: false,
184
+ auto2top: true,
185
+ subMaxLevel: 3,
186
+ maxLevel: 4,
187
+ relativePath: true,
188
+ notFoundPage: true,
189
+ executeScript: true,
190
+ themeColor: '#2f4f5a',
191
+ // Hash-routed by default (`/assistant/#/getting-started`). A
192
+ // history-mode experiment was tried but every clean URL like
193
+ // `/assistant/getting-started` returned HTTP 404 on GitHub Pages — Pages
194
+ // serves `docs/404.html` as the SPA shell for unknown paths, but always
195
+ // with a 404 status code, which breaks link previews, link checkers,
196
+ // and search crawlers. Hash routing keeps every navigable URL on a
197
+ // genuine 200 response.
198
+ search: {
199
+ maxAge: 86400000,
200
+ paths: 'auto',
201
+ placeholder: 'Search the docs …',
202
+ noData: 'No results.',
203
+ depth: 3,
204
+ hideOtherSidebarContent: false
205
+ },
206
+ copyCode: {
207
+ buttonText: 'Copy',
208
+ errorText: 'Error',
209
+ successText: 'Copied'
210
+ },
211
+ mermaidConfig: {
212
+ querySelector: '.mermaid'
213
+ },
214
+ darklightTheme: {
215
+ siteFont: 'PT Sans, system-ui, sans-serif',
216
+ defaultTheme: 'light',
217
+ codeFontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
218
+ bodyFontSize: '1.0625rem',
219
+ dark: {
220
+ accent: '#ffd166',
221
+ toggleBackground: '#22223b',
222
+ background: '#22223b',
223
+ textColor: '#f4f2f3',
224
+ codeTextColor: '#f4f2f3',
225
+ codeBackgroundColor: '#2c2c4a',
226
+ borderColor: '#3a3a5c',
227
+ blockQuoteColor: '#a07178',
228
+ highlightColor: '#4d7c8a',
229
+ sidebarSublink: '#a07178',
230
+ codeTypeColor: '#4d7c8a',
231
+ coverBackground: 'linear-gradient(to left bottom, #22223b 0%, #ffd166 100%)'
232
+ },
233
+ light: {
234
+ accent: '#2f4f5a',
235
+ toggleBackground: '#f4f2f3',
236
+ background: '#f4f2f3',
237
+ textColor: '#22223b',
238
+ codeTextColor: '#22223b',
239
+ codeBackgroundColor: '#ffffff',
240
+ borderColor: 'rgba(34,34,59,0.12)',
241
+ blockQuoteColor: '#a07178',
242
+ highlightColor: '#2f4f5a',
243
+ sidebarSublink: '#22223b',
244
+ codeTypeColor: '#2f4f5a',
245
+ coverBackground: 'linear-gradient(to left bottom, #f4f2f3 0%, #a07178 100%)'
246
+ }
247
+ },
248
+ plugins: [
249
+ prismThemeSwitcher,
250
+ pageFooter,
251
+ function (hook, vm) {
252
+ if (typeof EditOnGithubPlugin !== 'undefined') {
253
+ EditOnGithubPlugin.create(
254
+ 'https://github.com/ramongr/assistant/blob/main/docs/',
255
+ null,
256
+ 'Edit this page on GitHub'
257
+ )(hook, vm);
258
+ }
259
+ }
260
+ ]
261
+ };
262
+ </script>
263
+
264
+ <!-- Docsify core -->
265
+ <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
266
+
267
+ <!-- Dark/light theme toggle -->
268
+ <script src="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/index.min.js"
269
+ type="text/javascript"></script>
270
+
271
+ <!-- Plugins -->
272
+ <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
273
+ <script src="//cdn.jsdelivr.net/npm/docsify-copy-code@2"></script>
274
+ <script src="//cdn.jsdelivr.net/npm/docsify-edit-on-github"></script>
275
+ <script src="//cdn.jsdelivr.net/npm/docsify-tabs@1"></script>
276
+
277
+ <!-- Mermaid (diagrams) — load the UMD bundle from /dist/, not the
278
+ package root, which jsDelivr resolves to an ES-module file that the
279
+ browser refuses to execute as a classic script. -->
280
+ <script src="//cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js"></script>
281
+ <script src="//cdn.jsdelivr.net/npm/docsify-mermaid@2.0.1/dist/docsify-mermaid.js"></script>
282
+ <script>mermaid.initialize({ startOnLoad: false, theme: 'default' });</script>
283
+
284
+ <!-- Prism syntax highlighting: Ruby + autoloader for everything else -->
285
+ <script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-ruby.min.js"></script>
286
+ <script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
287
+ <script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-yaml.min.js"></script>
288
+ <script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-json.min.js"></script>
289
+ <script src="//cdn.jsdelivr.net/npm/prismjs/plugins/autoloader/prism-autoloader.min.js"></script>
290
+ </body>
291
+ </html>
data/docs/index.md CHANGED
@@ -1,25 +1,52 @@
1
- ---
2
- title: Home
3
- layout: home
4
- nav_order: 0
5
- permalink: /
6
- ---
7
-
8
- # Assistant
1
+ <h1 class="assistant-home-wordmark">assistant</h1>
9
2
 
10
3
  **Tiny, dependency-free soft-fail service objects for Ruby.**
11
4
 
12
5
  [![Gem Version](https://badge.fury.io/rb/assistant.svg)](https://rubygems.org/gems/assistant)
13
6
  [![CI](https://github.com/ramongr/assistant/actions/workflows/ci.yml/badge.svg)](https://github.com/ramongr/assistant/actions/workflows/ci.yml)
7
+ [![Docs](https://github.com/ramongr/assistant/actions/workflows/docs.yml/badge.svg)](https://github.com/ramongr/assistant/actions/workflows/docs.yml)
8
+
9
+ A service declares its inputs, validates them, runs its body, and returns a
10
+ uniform result hash that always carries either a value plus warnings, or a
11
+ list of errors. Frozen 1.0 public API. RBS signatures ship in `sig/`.
12
+ **Zero runtime gem dependencies.**
13
+
14
+ > [Get started in 60 seconds](getting-started.md) &nbsp;·&nbsp;
15
+ > [Browse the guides](guides/inputs.md) &nbsp;·&nbsp;
16
+ > [View on GitHub](https://github.com/ramongr/assistant)
17
+
18
+ ---
19
+
20
+ ## Why Assistant?
21
+
22
+ | Soft-fail by default | Zero runtime deps | Typed and Steep-ready |
23
+ |:---|:---|:---|
24
+ | Expected failures become structured `LogItem` errors with a `:status`, not raised exceptions. Callers pattern-match on the result. | The gemspec declares zero runtime dependencies, and CI enforces it on every push. Drop-in for any Ruby 3.4+ project. | Hand-curated RBS sidecars for the public API plus the `assistant-rbs` generator for your own services. Steep-checked in CI. |
25
+
26
+ ---
27
+
28
+ ## How a service flows
29
+
30
+ ```mermaid
31
+ flowchart LR
32
+ Run([Service.run]) --> Validate{Inputs +<br/>#validate}
33
+ Validate -- errors --> Failed[status: :with_errors]
34
+ Validate -- clean --> Execute[#execute]
35
+ Execute -- warnings --> Warns[status: :with_warnings]
36
+ Execute -- clean --> Ok[status: :ok]
37
+ Execute -- errors --> Failed
38
+ ```
39
+
40
+ Validation runs first; `#execute` is skipped entirely when errors are already
41
+ on the log. Warnings never short-circuit anything — they ride along on the
42
+ result so the caller can decide what to surface.
14
43
 
15
- Assistant lets you write service objects that **never raise for expected
16
- failures**. A service declares its inputs, validates them, runs its body, and
17
- returns a uniform result hash that always carries either a value plus
18
- warnings or a list of errors. Ships with RBS signatures, a 1.0-frozen public
19
- API, and zero runtime gem dependencies.
44
+ ---
20
45
 
21
46
  ## Install
22
47
 
48
+ Use the standard pessimistic pin:
49
+
23
50
  ```ruby
24
51
  # Gemfile
25
52
  gem 'assistant', '~> 1.0'
@@ -31,6 +58,8 @@ bundle install
31
58
 
32
59
  Ruby 3.4 or newer is required.
33
60
 
61
+ ---
62
+
34
63
  ## The 60-second example
35
64
 
36
65
  ```ruby
@@ -54,16 +83,30 @@ in { errors:, status: :with_errors }
54
83
  end
55
84
  ```
56
85
 
86
+ The same call returns `:with_warnings` if `#execute` logged any warnings, and
87
+ `:with_errors` (without invoking `#execute`) if the inputs failed validation.
88
+
89
+ > [Walk through this example end-to-end &raquo;](getting-started.md)
90
+
91
+ ---
92
+
57
93
  ## Where to next
58
94
 
59
- - **[Getting started](getting-started.md)** walk through your first
60
- service end-to-end.
61
- - **[Guides](guides/inputs.md)** DSL deep-dives, one page per concern.
62
- - **[API reference](api-reference.md)**every Frozen symbol, deep-link
63
- friendly.
64
- - **[Examples](examples/index.md)** runnable patterns (Rails, CLI,
65
- Sidekiq, composition, callbacks, instrumentation, RBS).
66
- - **[Roadmap](roadmap.md)** what's planned, what shipped.
67
- - **[Changelog](changelog.md)** full release history.
68
-
69
- Source on [GitHub](https://github.com/ramongr/assistant).
95
+ | Page | What you'll find |
96
+ |:---|:---|
97
+ | [Getting started](getting-started.md) | Install, your first service, consuming a result. |
98
+ | [Inputs guide](guides/inputs.md) | The `input` DSL types, defaults, optional, conditional. |
99
+ | [Validation guide](guides/validation.md) | Built-in checks, `#validate`, warning vs error semantics. |
100
+ | [Logging and results](guides/logging-and-results.md) | `LogItem`, `LogList`, the result hash shape. |
101
+ | [Composing services](guides/composing-services.md) | `call_service`, callbacks, notifier, `#input_snapshot`. |
102
+ | [RBS and types](guides/rbs-and-types.md) | Hand-curated sidecars + `bin/assistant-rbs`. |
103
+ | [API reference](api-reference.md) | Every Frozen symbol in 1.0, deep-link friendly. |
104
+ | [Examples](examples/rails-service.md) | Wiring patterns — Rails, CLI, Sidekiq, callbacks, notifier, RBS. |
105
+ | [Deprecations](deprecations.md) | What's flagged for removal in 2.0. |
106
+
107
+ ---
108
+
109
+ Source on [GitHub](https://github.com/ramongr/assistant) · Released under the
110
+ [MIT License](https://github.com/ramongr/assistant/blob/main/LICENSE.txt) ·
111
+ Contributions welcome — see
112
+ [CONTRIBUTING.md](https://github.com/ramongr/assistant/blob/main/CONTRIBUTING.md).