assistant 0.1.0 → 1.0.0.rc1
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/PULL_REQUEST_TEMPLATE.md +39 -0
- data/.github/workflows/ci.yml +99 -0
- data/.github/workflows/docs.yml +64 -0
- data/.github/workflows/release.yml +1 -1
- data/.gitignore +5 -1
- data/.opencode/.gitignore +4 -0
- data/.opencode/opencode.json +13 -0
- data/.opencode/skills/create-pr/SKILL.md +138 -0
- data/.opencode/skills/ruby-services/SKILL.md +81 -0
- data/.rubocop.yml +14 -4
- data/.yardopts +17 -0
- data/CHANGELOG.md +378 -0
- data/CONTRIBUTING.md +131 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +196 -29
- data/README.md +125 -16
- data/Rakefile +45 -0
- data/SECURITY.md +50 -0
- data/Steepfile +49 -0
- data/_config.yml +87 -0
- data/assistant.gemspec +24 -7
- data/docs/api-reference.md +264 -0
- data/docs/changelog.md +26 -0
- data/docs/deprecations.md +86 -0
- data/docs/examples/cli-handler.md +17 -0
- data/docs/examples/composing-services.md +17 -0
- data/docs/examples/execute-callbacks.md +17 -0
- data/docs/examples/index.md +29 -0
- data/docs/examples/instrumentation-notifier.md +17 -0
- data/docs/examples/rails-service.md +17 -0
- data/docs/examples/rbs-generator.md +17 -0
- data/docs/examples/sidekiq-worker.md +17 -0
- data/docs/getting-started.md +136 -0
- data/docs/guides/composing-services.md +222 -0
- data/docs/guides/index.md +25 -0
- data/docs/guides/inputs.md +333 -0
- data/docs/guides/logging-and-results.md +202 -0
- data/docs/guides/rbs-and-types.md +16 -0
- data/docs/guides/validation.md +180 -0
- data/docs/index.md +69 -0
- data/docs/roadmap.md +33 -0
- data/exe/assistant-rbs +7 -0
- data/lib/assistant/execute_callbacks.rb +103 -0
- data/lib/assistant/execute_callbacks.rbs +30 -0
- data/lib/assistant/input_builder/accessors.rb +36 -0
- data/lib/assistant/input_builder/accessors.rbs +10 -0
- data/lib/assistant/input_builder/default_option.rb +41 -0
- data/lib/assistant/input_builder/default_option.rbs +11 -0
- data/lib/assistant/input_builder/dsl.rb +37 -0
- data/lib/assistant/input_builder/dsl.rbs +12 -0
- data/lib/assistant/input_builder/optional_option.rb +45 -0
- data/lib/assistant/input_builder/optional_option.rbs +10 -0
- data/lib/assistant/input_builder/registry.rb +27 -0
- data/lib/assistant/input_builder/registry.rbs +13 -0
- data/lib/assistant/input_builder/require_validator.rb +104 -0
- data/lib/assistant/input_builder/require_validator.rbs +24 -0
- data/lib/assistant/input_builder/type_validator.rb +47 -0
- data/lib/assistant/input_builder/type_validator.rbs +18 -0
- data/lib/assistant/input_builder.rb +25 -81
- data/lib/assistant/input_builder.rbs +15 -0
- data/lib/assistant/log_item.rb +74 -16
- data/lib/assistant/log_item.rbs +40 -0
- data/lib/assistant/log_list.rb +43 -17
- data/lib/assistant/log_list.rbs +48 -0
- data/lib/assistant/rbs_generator/cli.rb +109 -0
- data/lib/assistant/rbs_generator/cli.rbs +24 -0
- data/lib/assistant/rbs_generator/renderer.rb +67 -0
- data/lib/assistant/rbs_generator/renderer.rbs +11 -0
- data/lib/assistant/rbs_generator/writer.rb +65 -0
- data/lib/assistant/rbs_generator/writer.rbs +24 -0
- data/lib/assistant/rbs_generator.rb +38 -0
- data/lib/assistant/rbs_generator.rbs +5 -0
- data/lib/assistant/refinements/string_blankness.rb +9 -13
- data/lib/assistant/refinements/string_blankness.rbs +6 -0
- data/lib/assistant/service.rb +300 -11
- data/lib/assistant/service.rbs +82 -1
- data/lib/assistant/version.rb +5 -1
- data/lib/assistant/version.rbs +5 -0
- data/lib/assistant.rb +54 -4
- data/lib/assistant.rbs +25 -0
- data/mise.toml +2 -0
- data/sig/examples/greeter.rbs +14 -0
- metadata +142 -38
- data/.fasterer.yml +0 -19
- data/.rubocop_todo.yml +0 -7
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Sidekiq worker
|
|
3
|
+
parent: Examples
|
|
4
|
+
nav_order: 3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Sidekiq worker
|
|
8
|
+
|
|
9
|
+
> **Status:** placeholder — ships in
|
|
10
|
+
> [P8](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md#p6p12-examples-one-pr-per-example) of
|
|
11
|
+
> the GitHub Pages plan.
|
|
12
|
+
|
|
13
|
+
A background job that runs a service idempotently, logging warnings vs errors separately.
|
|
14
|
+
|
|
15
|
+
When the runnable script under `examples/sidekiq_worker/` lands, this
|
|
16
|
+
page will include it verbatim via Jekyll `include_relative` so the
|
|
17
|
+
prose stays in lockstep with the code.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
nav_order: 1
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- markdownlint-disable MD013 MD024 -->
|
|
7
|
+
# Getting started
|
|
8
|
+
|
|
9
|
+
> **TL;DR** — Subclass `Assistant::Service`, declare your inputs with
|
|
10
|
+
> `input`, write an `execute` body, and call `YourService.run(**args)`.
|
|
11
|
+
> You always get a hash back: either
|
|
12
|
+
> `{ result:, status: :ok | :with_warnings, warnings: [...] }` or
|
|
13
|
+
> `{ result: nil, status: :with_errors, errors: [...] }`. The gem
|
|
14
|
+
> never raises for expected failures.
|
|
15
|
+
|
|
16
|
+
This page walks a brand-new user from `gem install` to a working
|
|
17
|
+
service. For deeper topics, jump to the guides:
|
|
18
|
+
|
|
19
|
+
- [Inputs](./guides/inputs.md) — every option on `input` and `inputs`.
|
|
20
|
+
- [Validation](./guides/validation.md) — the `validate` hook and how
|
|
21
|
+
to log warnings vs. errors.
|
|
22
|
+
- [Logging and results](./guides/logging-and-results.md) — `LogItem`,
|
|
23
|
+
the `log_item_*` shorthands, the result hash.
|
|
24
|
+
- [Composing services](./guides/composing-services.md) — `call_service`,
|
|
25
|
+
callbacks, the instrumentation notifier, `#input_snapshot`.
|
|
26
|
+
|
|
27
|
+
The API contract is enumerated in
|
|
28
|
+
[`api-reference.md`](./api-reference.md).
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
With Bundler (recommended):
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
bundle add assistant
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Without Bundler:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
gem install assistant
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Ruby `>= 3.4` is required. The gem has **zero runtime dependencies** —
|
|
45
|
+
adding it never pulls in another gem.
|
|
46
|
+
|
|
47
|
+
## Your first service
|
|
48
|
+
|
|
49
|
+
Create a file `create_user.rb`:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
require 'assistant'
|
|
53
|
+
|
|
54
|
+
class CreateUser < Assistant::Service
|
|
55
|
+
input :email, type: String, required: true
|
|
56
|
+
input :name, type: String, required: true
|
|
57
|
+
input :age, type: Integer, allow_nil: true, default: nil
|
|
58
|
+
|
|
59
|
+
def validate
|
|
60
|
+
return if email.include?('@')
|
|
61
|
+
|
|
62
|
+
log_item_error(source: :validate, detail: :email, message: 'must contain @')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def execute
|
|
66
|
+
log_item_warning(source: :execute, detail: :age, message: 'age missing') if age.nil?
|
|
67
|
+
|
|
68
|
+
{ id: 42, email:, name:, age: }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run it:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
CreateUser.run(email: 'a@b.com', name: 'Alice')
|
|
77
|
+
# => { result: { id: 42, email: "a@b.com", name: "Alice", age: nil },
|
|
78
|
+
# status: :with_warnings,
|
|
79
|
+
# warnings: [#<Assistant::LogItem level=:warning ...>] }
|
|
80
|
+
|
|
81
|
+
CreateUser.run(email: 'oops', name: 'Bob', age: 30)
|
|
82
|
+
# => { result: nil,
|
|
83
|
+
# status: :with_errors,
|
|
84
|
+
# errors: [#<Assistant::LogItem level=:error ...>,
|
|
85
|
+
# #<Assistant::LogItem level=:error ...>] }
|
|
86
|
+
|
|
87
|
+
CreateUser.run(email: 'c@d.com', name: 'Carol', age: 30)
|
|
88
|
+
# => { result: { id: 42, ... }, status: :ok, warnings: [] }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Three runs, three different statuses, **zero exceptions**.
|
|
92
|
+
|
|
93
|
+
## Reading the result hash
|
|
94
|
+
|
|
95
|
+
Every `Service.run` returns one of two shapes:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# Success (status is :ok or :with_warnings)
|
|
99
|
+
{ result: <Object>, status: :ok | :with_warnings, warnings: Array<LogItem> }
|
|
100
|
+
|
|
101
|
+
# Failure
|
|
102
|
+
{ result: nil, status: :with_errors, errors: Array<LogItem> }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Pattern-matching is the cleanest way to consume it:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
case CreateUser.run(email: 'a@b.com', name: 'Alice')
|
|
109
|
+
in { result:, status: :ok }
|
|
110
|
+
render json: result
|
|
111
|
+
in { result:, status: :with_warnings, warnings: }
|
|
112
|
+
WarningsLogger.log(warnings)
|
|
113
|
+
render json: result
|
|
114
|
+
in { errors:, status: :with_errors }
|
|
115
|
+
render json: { errors: errors.map(&:item) }, status: :unprocessable_entity
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The `:status` value is exhaustively one of `:ok`, `:with_warnings`,
|
|
120
|
+
`:with_errors`. No new status values can be introduced in 1.x without a
|
|
121
|
+
deprecation cycle.
|
|
122
|
+
|
|
123
|
+
## What's next
|
|
124
|
+
|
|
125
|
+
- The same example, but with optional / multi-type inputs and a
|
|
126
|
+
`default:` lambda → [Inputs guide](./guides/inputs.md).
|
|
127
|
+
- Logging structured warnings during `#execute`, conditional
|
|
128
|
+
validation, the strict `LogItem` constructor →
|
|
129
|
+
[Logging and results](./guides/logging-and-results.md) +
|
|
130
|
+
[Validation](./guides/validation.md).
|
|
131
|
+
- Wrapping one service inside another with shared log merging →
|
|
132
|
+
[Composing services](./guides/composing-services.md).
|
|
133
|
+
- Per-class RBS signatures for Steep users → run
|
|
134
|
+
`bundle exec assistant-rbs lib --output sig`. See the
|
|
135
|
+
[Inputs guide](./guides/inputs.md) for the limitation it works
|
|
136
|
+
around.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Composing services
|
|
3
|
+
parent: Guides
|
|
4
|
+
nav_order: 4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- markdownlint-disable MD013 MD024 -->
|
|
8
|
+
# Composing services
|
|
9
|
+
|
|
10
|
+
> **TL;DR** — Use `call_service(OtherService, **inputs)` to nest one
|
|
11
|
+
> service inside another's `#execute`; logs from the inner service
|
|
12
|
+
> are merged into the outer timeline automatically. Use
|
|
13
|
+
> `before_execute` / `after_execute` / `around_execute` to share
|
|
14
|
+
> cross-cutting concerns at the class level. Register
|
|
15
|
+
> `Assistant.notifier =` once to instrument every service. Use
|
|
16
|
+
> `#input_snapshot` to forward a read-only view of the inputs to a
|
|
17
|
+
> collaborator.
|
|
18
|
+
|
|
19
|
+
This guide covers the four composition surfaces shipped in 1.0:
|
|
20
|
+
service-to-service calls (M-S2), execute callbacks (M-S1), the
|
|
21
|
+
instrumentation notifier (M-S3), and the input snapshot (M-S4).
|
|
22
|
+
|
|
23
|
+
## `#call_service`: nest one service inside another
|
|
24
|
+
|
|
25
|
+
`call_service(klass, **inputs)` instantiates `klass`, runs it, merges
|
|
26
|
+
its `#logs` into the outer service's timeline, and returns the inner
|
|
27
|
+
service instance. It does **not** raise on inner failure — the
|
|
28
|
+
outer `#execute` decides what to do based on `inner.success?` /
|
|
29
|
+
`inner.failure?` / `inner.result`.
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
class CreateUser < Assistant::Service
|
|
33
|
+
input :email, type: String, required: true
|
|
34
|
+
def execute = { id: 1, email: }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class SignUp < Assistant::Service
|
|
38
|
+
input :email, type: String, required: true
|
|
39
|
+
|
|
40
|
+
def execute
|
|
41
|
+
user = call_service(CreateUser, email:)
|
|
42
|
+
return if user.failure? # inner errors already on the outer timeline
|
|
43
|
+
|
|
44
|
+
{ user: user.result, signed_up_at: Time.now }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
result = SignUp.run(email: 'a@b.com')
|
|
49
|
+
result.fetch(:status) # => :ok
|
|
50
|
+
result.fetch(:result)[:user][:id] # => 1
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Notes:
|
|
54
|
+
|
|
55
|
+
- `klass` must be a subclass of `Assistant::Service`. Anything else
|
|
56
|
+
raises `ArgumentError`.
|
|
57
|
+
- The inner service's `#logs` are appended to the outer service's
|
|
58
|
+
timeline via `merge_logs(logs: inner.logs)`. Because the outer
|
|
59
|
+
service's `errors` / `warnings` / `status` are derived from its
|
|
60
|
+
full `@logs`, inner errors **automatically** downgrade the outer
|
|
61
|
+
terminal status to `:with_errors`, and inner warnings surface as
|
|
62
|
+
`:with_warnings` — no special handling required.
|
|
63
|
+
- `call_service` does **not** rescue exceptions raised by the inner
|
|
64
|
+
service's `#execute` (or by the configured `Assistant.notifier`).
|
|
65
|
+
Wrap in `begin/rescue` and record via `add_log(level: :error, ...)`
|
|
66
|
+
if the inner service may raise.
|
|
67
|
+
- `call_service` always calls `inner.run`, so calling it twice would
|
|
68
|
+
re-execute the inner service.
|
|
69
|
+
|
|
70
|
+
## Execute callbacks (M-S1)
|
|
71
|
+
|
|
72
|
+
Three class-level DSL methods register callbacks around `#execute`.
|
|
73
|
+
Hooks are evaluated in the context of the service instance.
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
class CreateUser < Assistant::Service
|
|
77
|
+
input :email, type: String, required: true
|
|
78
|
+
|
|
79
|
+
before_execute do
|
|
80
|
+
log_item_info(source: :hook, detail: :before, message: "starting #{email}")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
around_execute do |&blk|
|
|
84
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
85
|
+
value = blk.call
|
|
86
|
+
log_item_info(source: :hook, detail: :around,
|
|
87
|
+
message: "took #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - started}s")
|
|
88
|
+
value
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
after_execute do |result|
|
|
92
|
+
log_item_info(source: :hook, detail: :after, message: "result=#{result.inspect}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def execute = { id: 1, email: }
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Behavior:
|
|
100
|
+
|
|
101
|
+
- **Order:** `before_execute` hooks run in declaration order; then
|
|
102
|
+
`around_execute` hooks wrap the chain with the **first-declared**
|
|
103
|
+
hook as the outermost layer; finally `after_execute` hooks run
|
|
104
|
+
with the execute result as the single positional argument.
|
|
105
|
+
- **Inheritance:** subclasses inherit a `dup` of each parent's hook
|
|
106
|
+
arrays. Adding hooks in a subclass does not affect the parent.
|
|
107
|
+
- **Exceptions:** a `StandardError` raised inside any hook is logged
|
|
108
|
+
with `level: :error, source: :hook, detail: <hook_type>` and the
|
|
109
|
+
remaining hooks still fire. An `around_execute` hook that raises
|
|
110
|
+
before yielding to its continuation produces `nil` for that layer
|
|
111
|
+
and outer hooks still wrap normally.
|
|
112
|
+
- **Missing block:** registering a hook without a block raises
|
|
113
|
+
`ArgumentError` at class-definition time.
|
|
114
|
+
|
|
115
|
+
## Instrumentation notifier (M-S3)
|
|
116
|
+
|
|
117
|
+
Assign a callable to `Assistant.notifier =` to receive a fixed set
|
|
118
|
+
of events for **every** service execution:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
Assistant.notifier = lambda do |event, payload|
|
|
122
|
+
StatsD.increment("assistant.#{event}",
|
|
123
|
+
tags: ["service:#{payload[:service_class]}"])
|
|
124
|
+
StatsD.timing("assistant.duration_s.#{event}",
|
|
125
|
+
payload[:duration_s] * 1000.0,
|
|
126
|
+
tags: ["service:#{payload[:service_class]}"])
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Events (frozen for 1.0):
|
|
131
|
+
|
|
132
|
+
| Event | When |
|
|
133
|
+
|----------------------|-------------------------------------------------|
|
|
134
|
+
| `:service_started` | Top of `#run`, before any validation. |
|
|
135
|
+
| `:service_validated` | After declarative + `#validate` checks pass. |
|
|
136
|
+
| `:service_executed` | Success path — after `#execute` returns. |
|
|
137
|
+
| `:service_failed` | Failure path — `#execute` was skipped. |
|
|
138
|
+
|
|
139
|
+
Payload always includes:
|
|
140
|
+
|
|
141
|
+
- `:service_class` — the `Service` subclass.
|
|
142
|
+
- `:duration_s` — Float seconds since the start of `#run`
|
|
143
|
+
(`Process::CLOCK_MONOTONIC`).
|
|
144
|
+
|
|
145
|
+
The notifier is treated as untrusted infrastructure: any
|
|
146
|
+
`StandardError` it raises is rescued and warned (`Kernel.warn`),
|
|
147
|
+
so a misconfigured notifier cannot tear down every service in the
|
|
148
|
+
process. `SystemExit` / `Interrupt` propagate.
|
|
149
|
+
|
|
150
|
+
To disable instrumentation entirely, restore the default no-op:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
Assistant.notifier = Assistant::DEFAULT_NOTIFIER
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## `#input_snapshot` (M-S4)
|
|
157
|
+
|
|
158
|
+
`#input_snapshot` returns a read-only `Data` view of the service's
|
|
159
|
+
declared inputs (post-`default:` / post-`allow_nil:`). It's the
|
|
160
|
+
canonical way to hand a value object to a collaborator without
|
|
161
|
+
exposing the full service instance:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
class CreateUser < Assistant::Service
|
|
165
|
+
input :email, type: String, required: true
|
|
166
|
+
input :role, type: Symbol, default: :member
|
|
167
|
+
|
|
168
|
+
def execute
|
|
169
|
+
Mailer.welcome(input_snapshot)
|
|
170
|
+
{ id: 1, **input_snapshot.to_h }
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
snapshot = CreateUser.new(email: 'a@b.com').tap(&:run).input_snapshot
|
|
175
|
+
snapshot.email # => "a@b.com"
|
|
176
|
+
snapshot.role # => :member
|
|
177
|
+
snapshot.to_h # => { email: "a@b.com", role: :member }
|
|
178
|
+
|
|
179
|
+
snapshot.email = 'x' # => NoMethodError (Data is structurally immutable)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Notes:
|
|
183
|
+
|
|
184
|
+
- Members are exactly the keys of `input_definitions` — extra keyword
|
|
185
|
+
arguments to `#initialize` that have no `input` declaration are
|
|
186
|
+
excluded so the snapshot mirrors the public DSL.
|
|
187
|
+
- A declared input with no default and no caller-supplied value
|
|
188
|
+
appears with `nil`, mirroring the per-input getter.
|
|
189
|
+
- The snapshot class is memoized at the class level via
|
|
190
|
+
`Service.input_snapshot_class`, so repeated calls are cheap.
|
|
191
|
+
- `Data` is structurally immutable. Mutable member values (an
|
|
192
|
+
`Array` passed as an input, say) keep their normal mutability —
|
|
193
|
+
the snapshot does not deep-freeze.
|
|
194
|
+
|
|
195
|
+
## Common pitfalls
|
|
196
|
+
|
|
197
|
+
- **Forgetting that `call_service` doesn't fail the outer service.**
|
|
198
|
+
If the inner failure should also fail the outer, check
|
|
199
|
+
`inner.failure?` (or `inner.errors.any?`) and call
|
|
200
|
+
`log_item_error(...)` in your `#execute`.
|
|
201
|
+
- **Re-running `inner` after `call_service`.** `inner.run` was
|
|
202
|
+
already called; calling it again will re-execute and double-log.
|
|
203
|
+
- **Mutating `#input_snapshot` member values.** They share identity
|
|
204
|
+
with the underlying inputs. Treat the snapshot as a view, not a
|
|
205
|
+
defensive copy.
|
|
206
|
+
- **Putting validation in `before_execute`.** Use `#validate` — it
|
|
207
|
+
short-circuits `#execute` on errors. Hooks log but don't change
|
|
208
|
+
flow.
|
|
209
|
+
- **Raising from a notifier.** It's swallowed (with a `warn`).
|
|
210
|
+
Non-`StandardError` exceptions still propagate, so don't use
|
|
211
|
+
`Kernel#exit`.
|
|
212
|
+
|
|
213
|
+
## See also
|
|
214
|
+
|
|
215
|
+
- [Inputs guide](./inputs.md) — `Service.input` / `Service.inputs`
|
|
216
|
+
and `#input_snapshot`.
|
|
217
|
+
- [Validation guide](./validation.md) — `#validate`, conditional
|
|
218
|
+
requirements.
|
|
219
|
+
- [Logging and results](./logging-and-results.md) — what
|
|
220
|
+
`merge_logs(logs:)` does, the result hash shape.
|
|
221
|
+
- [API reference](../api-reference.md#assistantservice) for the
|
|
222
|
+
full callback / call_service / notifier surfaces.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Guides
|
|
3
|
+
nav_order: 2
|
|
4
|
+
has_children: true
|
|
5
|
+
permalink: /guides/
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Guides
|
|
9
|
+
|
|
10
|
+
Topic-focused walkthroughs of every part of the Assistant DSL.
|
|
11
|
+
Each guide opens with a TL;DR, ships runnable examples mirrored by
|
|
12
|
+
tests under `test/docs/<guide>_examples_test.rb`, and ends with a
|
|
13
|
+
"common pitfalls" + "see also" section.
|
|
14
|
+
|
|
15
|
+
- [Inputs](./inputs.md) — `input`/`inputs` DSL, `type:`, `required:`,
|
|
16
|
+
`default:`, `allow_nil:`, `optional:`, `if:` conditional requirement,
|
|
17
|
+
and the `assistant-rbs` Steep recipe.
|
|
18
|
+
- [Validation](./validation.md) — auto-checks, `#validate`, warnings
|
|
19
|
+
vs errors, conditional requirements, strict `LogItem.new` (M10).
|
|
20
|
+
- [Logging and results](./logging-and-results.md) — `LogItem`,
|
|
21
|
+
levels, `log_item_*` shorthands, `merge_logs`, the result hash.
|
|
22
|
+
- [Composing services](./composing-services.md) — `call_service`,
|
|
23
|
+
callbacks, the notifier, `#input_snapshot`.
|
|
24
|
+
- [RBS and types](./rbs-and-types.md) — per-class generator, the R1
|
|
25
|
+
metaprogramming limitation, Steep CI hookup.
|