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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +9 -19
- data/.gitignore +0 -3
- data/CHANGELOG.md +8 -31
- data/Gemfile +6 -9
- data/Gemfile.lock +3 -104
- data/README.md +8 -6
- data/Rakefile +17 -11
- data/Steepfile +18 -3
- data/assistant.gemspec +1 -1
- data/docs/.nojekyll +0 -0
- data/docs/404.html +292 -0
- data/docs/_media/apple-touch-icon.png +0 -0
- data/docs/_media/favicon.png +0 -0
- data/docs/_media/home-logo.svg +8 -0
- data/docs/_media/repo-card.png +0 -0
- data/docs/_sidebar.md +29 -0
- data/docs/api-reference.md +22 -11
- data/docs/changelog.md +0 -5
- data/docs/deprecations.md +4 -9
- data/docs/examples/{index.md → README.md} +7 -17
- data/docs/examples/cli-handler.md +39 -13
- data/docs/examples/composing-services.md +45 -13
- data/docs/examples/execute-callbacks.md +49 -13
- data/docs/examples/instrumentation-notifier.md +48 -13
- data/docs/examples/rails-service.md +40 -13
- data/docs/examples/rbs-generator.md +90 -13
- data/docs/examples/sidekiq-worker.md +33 -13
- data/docs/getting-started.md +11 -5
- data/docs/guides/{index.md → README.md} +1 -8
- data/docs/guides/composing-services.md +6 -12
- data/docs/guides/inputs.md +6 -12
- data/docs/guides/logging-and-results.md +9 -15
- data/docs/guides/rbs-and-types.md +151 -13
- data/docs/guides/validation.md +29 -11
- data/docs/index.html +291 -0
- data/docs/index.md +67 -24
- data/docs/roadmap.md +8 -13
- data/lib/assistant/version.rb +1 -1
- metadata +12 -5
- data/_config.yml +0 -87
|
@@ -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
|
-
> **
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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).
|
data/docs/guides/validation.md
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
|
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#
|
|
180
|
-
- [API reference: LogList](../api-reference.md#
|
|
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 — 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…</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 · MIT-licensed · ',
|
|
164
|
+
' <a href="https://github.com/ramongr/assistant" target="_blank" rel="noopener">GitHub</a> · ',
|
|
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
|
[](https://rubygems.org/gems/assistant)
|
|
13
6
|
[](https://github.com/ramongr/assistant/actions/workflows/ci.yml)
|
|
7
|
+
[](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) ·
|
|
15
|
+
> [Browse the guides](guides/inputs.md) ·
|
|
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
|
-
|
|
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 »](getting-started.md)
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
57
93
|
## Where to next
|
|
58
94
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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).
|