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
data/_config.yml
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Site configuration for the Assistant docs site, deployed to
|
|
2
|
+
# https://ramongr.github.io/assistant/ via .github/workflows/docs.yml.
|
|
3
|
+
# Toolchain: Jekyll 4 + just-the-docs (Ruby). Source files live under
|
|
4
|
+
# docs/. The v1 planning corpus under docs/v1/ is excluded — those are
|
|
5
|
+
# authored-for-GitHub-render plan docs, not site pages.
|
|
6
|
+
|
|
7
|
+
title: Assistant
|
|
8
|
+
description: >-
|
|
9
|
+
Tiny, dependency-free soft-fail service objects for Ruby. Uniform result
|
|
10
|
+
hash, RBS signatures, 1.0-frozen public API, zero runtime gem dependencies.
|
|
11
|
+
url: https://ramongr.github.io
|
|
12
|
+
baseurl: /assistant
|
|
13
|
+
repository: ramongr/assistant
|
|
14
|
+
|
|
15
|
+
# Source layout
|
|
16
|
+
source: docs
|
|
17
|
+
destination: _site
|
|
18
|
+
|
|
19
|
+
# Theme
|
|
20
|
+
theme: just-the-docs
|
|
21
|
+
color_scheme: light
|
|
22
|
+
|
|
23
|
+
# Search
|
|
24
|
+
search_enabled: true
|
|
25
|
+
search:
|
|
26
|
+
heading_level: 3
|
|
27
|
+
previews: 3
|
|
28
|
+
preview_words_before: 5
|
|
29
|
+
preview_words_after: 10
|
|
30
|
+
tokenizer_separator: /[\s/]+/
|
|
31
|
+
rel_url: true
|
|
32
|
+
button: true
|
|
33
|
+
focus_shortcut_key: 'k'
|
|
34
|
+
|
|
35
|
+
# Navigation
|
|
36
|
+
nav_enabled: true
|
|
37
|
+
heading_anchors: true
|
|
38
|
+
|
|
39
|
+
# Aux links (top-right)
|
|
40
|
+
aux_links:
|
|
41
|
+
View on GitHub:
|
|
42
|
+
- https://github.com/ramongr/assistant
|
|
43
|
+
RubyGems:
|
|
44
|
+
- https://rubygems.org/gems/assistant
|
|
45
|
+
aux_links_new_tab: true
|
|
46
|
+
|
|
47
|
+
# Footer
|
|
48
|
+
gh_edit_link: true
|
|
49
|
+
gh_edit_link_text: Edit this page on GitHub
|
|
50
|
+
gh_edit_repository: https://github.com/ramongr/assistant
|
|
51
|
+
gh_edit_branch: main
|
|
52
|
+
gh_edit_source: docs
|
|
53
|
+
gh_edit_view_mode: tree
|
|
54
|
+
|
|
55
|
+
back_to_top: true
|
|
56
|
+
back_to_top_text: Back to top
|
|
57
|
+
|
|
58
|
+
# Build settings
|
|
59
|
+
markdown: kramdown
|
|
60
|
+
kramdown:
|
|
61
|
+
syntax_highlighter: rouge
|
|
62
|
+
input: GFM
|
|
63
|
+
hard_wrap: false
|
|
64
|
+
|
|
65
|
+
# Files Jekyll should ignore. Paths are relative to `source:` (docs/),
|
|
66
|
+
# so `v1` skips docs/v1/ — the v1 planning corpus authored for GitHub's
|
|
67
|
+
# Markdown renderer (relative links into ../ and across plan files). It
|
|
68
|
+
# is intentionally NOT a site route.
|
|
69
|
+
exclude:
|
|
70
|
+
- v1
|
|
71
|
+
- README.md
|
|
72
|
+
|
|
73
|
+
# Jekyll plugins (the GitHub Pages workflow installs these via Bundler;
|
|
74
|
+
# we are not using github-pages gem, so plugins must be safe-listed too)
|
|
75
|
+
plugins:
|
|
76
|
+
- jekyll-seo-tag
|
|
77
|
+
- jekyll-relative-links
|
|
78
|
+
|
|
79
|
+
# Rewrite intra-docs Markdown links (foo.md) to Jekyll URLs so the
|
|
80
|
+
# same files render both on GitHub (where authors keep .md links for
|
|
81
|
+
# context) and on the site (where .html is needed).
|
|
82
|
+
relative_links:
|
|
83
|
+
enabled: true
|
|
84
|
+
collections: false
|
|
85
|
+
|
|
86
|
+
# just-the-docs strict mode is off because the v1 plan docs use
|
|
87
|
+
# kramdown-incompatible link anchors; this site doesn't include them.
|
data/assistant.gemspec
CHANGED
|
@@ -13,35 +13,52 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.authors = ['Ramon Rodrigues']
|
|
14
14
|
spec.email = ['cerberus.ramon@gmail.com']
|
|
15
15
|
|
|
16
|
-
spec.summary = '
|
|
17
|
-
spec.description =
|
|
16
|
+
spec.summary = 'Tiny, dependency-free soft-fail service objects for Ruby'
|
|
17
|
+
spec.description = <<~DESC
|
|
18
|
+
Assistant is a tiny, dependency-free Ruby library for writing soft-fail,
|
|
19
|
+
composable service objects. A service declares its inputs, validates
|
|
20
|
+
them, runs its body, and returns a uniform result that always carries
|
|
21
|
+
either a value plus warnings or a list of errors — never raising for
|
|
22
|
+
expected failures. Ships with RBS signatures, a 1.0-frozen public API,
|
|
23
|
+
and zero runtime gem dependencies.
|
|
24
|
+
DESC
|
|
18
25
|
spec.homepage = 'https://github.com/ramongr/assistant'
|
|
19
26
|
spec.license = 'MIT'
|
|
20
27
|
spec.required_ruby_version = '>= 3.4'
|
|
21
28
|
|
|
22
29
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
30
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/ramongr/assistant/issues'
|
|
23
31
|
spec.metadata['changelog_uri'] = 'https://github.com/ramongr/assistant/blob/main/CHANGELOG.md'
|
|
32
|
+
spec.metadata['documentation_uri'] = 'https://rubydoc.info/gems/assistant'
|
|
24
33
|
spec.metadata['homepage_uri'] = 'https://github.com/ramongr/assistant'
|
|
25
34
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
26
35
|
spec.metadata['source_code_uri'] = 'https://github.com/ramongr/assistant'
|
|
27
36
|
|
|
28
37
|
# Specify which files should be added to the gem when it is released.
|
|
29
38
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
39
|
+
# Internal planning docs (`docs/v1/`, `docs/v1.x/`) and runnable examples
|
|
40
|
+
# are excluded from the packaged gem (Q9 decision).
|
|
30
41
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
31
|
-
`git ls-files -z`.split("\x0").reject
|
|
42
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
43
|
+
f.match(%r{^(test|spec|features|examples|docs/v1(\.x)?)/})
|
|
44
|
+
end
|
|
32
45
|
end
|
|
33
46
|
|
|
47
|
+
spec.bindir = 'exe'
|
|
48
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
34
49
|
spec.require_paths = 'lib'
|
|
35
50
|
|
|
36
|
-
spec.add_development_dependency 'brakeman', '~> 8.0'
|
|
37
51
|
spec.add_development_dependency 'bundler', '~> 4.0'
|
|
38
52
|
spec.add_development_dependency 'byebug', '~> 13.0'
|
|
39
53
|
spec.add_development_dependency 'colorize', '~> 1.1'
|
|
40
|
-
spec.add_development_dependency '
|
|
41
|
-
spec.add_development_dependency 'minitest', '~> 5.25'
|
|
54
|
+
spec.add_development_dependency 'minitest', '~> 6.0'
|
|
42
55
|
spec.add_development_dependency 'rake', '~> 13.4'
|
|
43
|
-
spec.add_development_dependency 'rubocop', '~> 1.86'
|
|
56
|
+
spec.add_development_dependency 'rubocop', '~> 1.86', '>= 1.86.2'
|
|
44
57
|
spec.add_development_dependency 'rubocop-minitest', '~> 0.39'
|
|
45
58
|
spec.add_development_dependency 'rubocop-performance', '~> 1.26'
|
|
46
59
|
spec.add_development_dependency 'rubocop-rake', '~> 0.7'
|
|
60
|
+
spec.add_development_dependency 'rubocop-style-compact_nesting', '~> 0.1'
|
|
61
|
+
spec.add_development_dependency 'simplecov', '~> 0.22'
|
|
62
|
+
spec.add_development_dependency 'steep', '~> 2.0'
|
|
63
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
|
47
64
|
end
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API reference
|
|
3
|
+
nav_order: 3
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- markdownlint-disable MD013 MD024 -->
|
|
7
|
+
# API reference
|
|
8
|
+
|
|
9
|
+
This is the hand-written, curated reference for every public symbol
|
|
10
|
+
that ships in `assistant` 1.0.0. The source of truth for stability
|
|
11
|
+
labels is
|
|
12
|
+
[`docs/v1/01-api-surface.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/01-api-surface.md); the table
|
|
13
|
+
of contents here mirrors it.
|
|
14
|
+
|
|
15
|
+
Anything not listed on this page is **internal** and may change
|
|
16
|
+
without a major version bump.
|
|
17
|
+
|
|
18
|
+
## Stability labels
|
|
19
|
+
|
|
20
|
+
- **Frozen** — covered by semver from 1.0.0 onward. Breaking changes
|
|
21
|
+
require a 2.0.
|
|
22
|
+
- **Experimental** — public but subject to change in a 1.x minor
|
|
23
|
+
with a deprecation cycle.
|
|
24
|
+
|
|
25
|
+
## Table of contents
|
|
26
|
+
|
|
27
|
+
- [`Assistant` module](#assistant-module)
|
|
28
|
+
- [`Assistant::Service`](#assistantservice)
|
|
29
|
+
- [Class methods](#class-methods)
|
|
30
|
+
- [Instance methods](#instance-methods)
|
|
31
|
+
- [Generated per-input methods](#generated-per-input-methods)
|
|
32
|
+
- [Result shape](#result-shape)
|
|
33
|
+
- [`Assistant::LogItem`](#assistantlogitem)
|
|
34
|
+
- [`Assistant::LogList`](#assistantloglist)
|
|
35
|
+
- [Execute callbacks](#execute-callbacks)
|
|
36
|
+
- [Service composition](#service-composition)
|
|
37
|
+
- [Instrumentation notifier](#instrumentation-notifier)
|
|
38
|
+
- [Input snapshot](#input-snapshot)
|
|
39
|
+
- [`assistant-rbs` CLI](#assistant-rbs-cli)
|
|
40
|
+
- [Semver and deprecation policy](#semver-and-deprecation-policy)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## `Assistant` module
|
|
45
|
+
|
|
46
|
+
| Symbol | Stability | Description |
|
|
47
|
+
|---------------------------------|--------------------------|------------------------------------------------------------------------------------------|
|
|
48
|
+
| `Assistant::VERSION` | Frozen | Semver-compliant `String`. Defined in `lib/assistant/version.rb`. |
|
|
49
|
+
| `Assistant.notifier` | Frozen *(new in 1.0)* | Reader for the instrumentation proc. Defaults to a no-op. |
|
|
50
|
+
| `Assistant.notifier=(callable)` | Frozen *(new in 1.0)* | Writer. Accepts any object responding to `#call(event, payload)`. |
|
|
51
|
+
|
|
52
|
+
See [Instrumentation notifier](#instrumentation-notifier) below for the
|
|
53
|
+
event catalogue.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## `Assistant::Service`
|
|
58
|
+
|
|
59
|
+
Subclass `Assistant::Service` and override `#execute` (and optionally
|
|
60
|
+
`#validate`). Every other method on the table below is provided for
|
|
61
|
+
you.
|
|
62
|
+
|
|
63
|
+
### Class methods
|
|
64
|
+
|
|
65
|
+
| Signature | Stability | Description |
|
|
66
|
+
|---------------------------------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
|
|
67
|
+
| `Service.run(**inputs) -> Hash` | Frozen | One-shot. Instantiates with `inputs`, calls `#run`, returns the result hash. |
|
|
68
|
+
| `Service.input(name, type:, required: false, optional: nil, if: nil, default: nil, allow_nil: false)` | Frozen | Declarative input. `name` is a leading positional symbol; everything else is keyword. See [Inputs](./guides/inputs.md). |
|
|
69
|
+
| `Service.inputs(names, type:, **options)` | Frozen | Bulk declaration. `names` is an `Array<Symbol>`. Options apply to every input. |
|
|
70
|
+
| `Service.before_execute(&block)` | Frozen | Hook block runs after validation, before `#execute`. `instance_exec`'d on the service. |
|
|
71
|
+
| `Service.after_execute(&block)` | Frozen | Hook block runs after `#execute` returns; receives the result as the block argument. |
|
|
72
|
+
| `Service.around_execute(&block)` | Frozen | Hook block is `instance_exec`'d with an inner block argument that yields to the next layer. |
|
|
73
|
+
| `Service.input_snapshot_class -> Class` | Frozen *(new in 1.0)* | Memoised `Data.define(*declared_input_names)` class used by `#input_snapshot`. |
|
|
74
|
+
|
|
75
|
+
> **M12 keyword-only sweep.** Every other public DSL helper (e.g.
|
|
76
|
+
> `merge_logs`, all `InputBuilder` internals) takes keyword arguments
|
|
77
|
+
> only. The two exemptions above — `Service.input` and
|
|
78
|
+
> `Service.inputs` — keep their leading positional `name` / `names`
|
|
79
|
+
> because the class-body declaration reads better as
|
|
80
|
+
> `input :email, type: String` than `input name: :email, type: String`.
|
|
81
|
+
|
|
82
|
+
### Instance methods
|
|
83
|
+
|
|
84
|
+
| Signature | Stability | Description |
|
|
85
|
+
|----------------------------------------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------|
|
|
86
|
+
| `#initialize(**inputs)` | Frozen | Stores `inputs` after applying every `default:`. Does **not** run validation or `#execute`. |
|
|
87
|
+
| `#run -> Hash` | Frozen | Runs `#validate`, the `before_execute` chain, `#execute` wrapped in `around_execute`, then `after_execute`. Returns the result hash. |
|
|
88
|
+
| `#result -> Object` | Frozen | Memoised return value of `#execute`. `nil` on failure. |
|
|
89
|
+
| `#success? -> Boolean` | Frozen | True when status is `:ok` or `:with_warnings`. |
|
|
90
|
+
| `#failure? -> Boolean` | Frozen | True when status is `:with_errors`. |
|
|
91
|
+
| `#status -> Symbol` | Frozen | Exactly one of `:ok`, `:with_warnings`, `:with_errors`. |
|
|
92
|
+
| `#logs -> Array<Assistant::LogItem>` | Frozen *(new in 1.0)* | All accumulated log items, in the order they were added. |
|
|
93
|
+
| `#infos / #warnings / #errors` | Frozen | Filtered subsets of `#logs`, by level. |
|
|
94
|
+
| `#call_service(klass, **inputs) -> Service` | Frozen *(new in 1.0)* | Runs another service, merges its logs into this one, returns the inner instance. Errors on the inner flip this service to `:with_errors`. |
|
|
95
|
+
| `#input_snapshot -> Data` | Frozen *(new in 1.0)* | Frozen value object capturing the post-default, post-`allow_nil` inputs. See [Composing services](./guides/composing-services.md). |
|
|
96
|
+
| `#execute -> Object` (override) | Frozen | Your subclass overrides this. Return value becomes `result:`. |
|
|
97
|
+
| `#validate -> void` (override) | Frozen | Optional. Add log items here to short-circuit `#execute`. |
|
|
98
|
+
|
|
99
|
+
### Generated per-input methods
|
|
100
|
+
|
|
101
|
+
For every `input :name, type: T` declaration, the following are
|
|
102
|
+
generated as instance methods on the service:
|
|
103
|
+
|
|
104
|
+
| Method | When generated | Description |
|
|
105
|
+
|-----------------------------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------|
|
|
106
|
+
| `#name` | Always | Reader for the post-default value of the input. |
|
|
107
|
+
| `#name?` | Always | Present-and-truthy predicate. Whitespace-only strings are treated as missing. |
|
|
108
|
+
| `#valid_type_name? -> Boolean` | Always | True when `name` matches the declared `type:` (or is one of an `Array` type). |
|
|
109
|
+
| `#valid_required_name? -> Boolean` | When `required: true` | True when `name` is present (uses `#name?`). **Canonical name in 1.0.** |
|
|
110
|
+
| `#valid_require_name? -> Boolean` | When `required: true` | **Deprecated in 1.0**, removed in 2.0. Delegates to `#valid_required_name?` with a `Kernel.warn` per call site. |
|
|
111
|
+
| `#valid_required_conditional_name? -> Boolean` | When `required: true` **and** `if:` is supplied | True when the `if:` predicate is truthy **and** `name` is present. |
|
|
112
|
+
| `#valid_require_conditional_name? -> Boolean` | Same | **Deprecated in 1.0**, removed in 2.0. Delegates with a warning. |
|
|
113
|
+
|
|
114
|
+
See [`docs/deprecations.md`](./deprecations.md) for the deprecation
|
|
115
|
+
table and migration recipe.
|
|
116
|
+
|
|
117
|
+
### Result shape
|
|
118
|
+
|
|
119
|
+
`Service#run` (and therefore `Service.run`) always returns one of two
|
|
120
|
+
hash shapes:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# Success
|
|
124
|
+
{ result: <Object>, status: :ok | :with_warnings, warnings: Array<LogItem> }
|
|
125
|
+
|
|
126
|
+
# Failure
|
|
127
|
+
{ result: nil, status: :with_errors, errors: Array<LogItem> }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The status enum is exhaustively `:ok`, `:with_warnings`, `:with_errors`.
|
|
131
|
+
No new status values may be added in 1.x without a deprecation cycle.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## `Assistant::LogItem`
|
|
136
|
+
|
|
137
|
+
> **Breaking change in 1.0 (M10).** `LogItem.new` now raises
|
|
138
|
+
> `ArgumentError` when any required attribute is invalid. The
|
|
139
|
+
> `#valid?` family is **retained** for introspection, but in normal
|
|
140
|
+
> flows it always returns `true` after a successful `new`.
|
|
141
|
+
|
|
142
|
+
| Signature | Stability |
|
|
143
|
+
|------------------------------------------------------------------------------------------|--------------------------|
|
|
144
|
+
| `LogItem.new(level:, source:, detail:, message:, trace: nil)` | Frozen *(raises in 1.0)* |
|
|
145
|
+
| `#level / #source / #detail / #message / #trace` — readers | Frozen |
|
|
146
|
+
| `#info? / #warning? / #error?` — level predicates | Frozen |
|
|
147
|
+
| `#valid? / #valid_level? / #valid_source? / #valid_detail? / #valid_message?` | Frozen |
|
|
148
|
+
| `#item -> Hash{Symbol => Object}` | Frozen |
|
|
149
|
+
| `Assistant::LogItem::VALID_LEVELS` — `%i[info warning error]` | Frozen |
|
|
150
|
+
|
|
151
|
+
`#level`, `#source`, `#detail` are always `Symbol`; `#message` is
|
|
152
|
+
`String`; `#trace` is `String` or `nil`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## `Assistant::LogList`
|
|
157
|
+
|
|
158
|
+
Mixin module included in `Assistant::Service`. Users don't normally
|
|
159
|
+
include it themselves; the methods are available on any service
|
|
160
|
+
instance.
|
|
161
|
+
|
|
162
|
+
| Signature | Stability |
|
|
163
|
+
|--------------------------------------------------------------------|--------------------------|
|
|
164
|
+
| `#add_log(level:, source:, detail:, message:, trace: nil)` | Frozen |
|
|
165
|
+
| `#merge_logs(logs:)` | Frozen *(keyword-only in 1.0)* |
|
|
166
|
+
| `#log_item_error_initialize(attr_name:, message:)` | Frozen |
|
|
167
|
+
| `#log_item_info(source:, detail:, message:, trace: nil)` | Frozen *(new in 1.0)* |
|
|
168
|
+
| `#log_item_warning(source:, detail:, message:, trace: nil)` | Frozen *(new in 1.0)* |
|
|
169
|
+
| `#log_item_error(source:, detail:, message:, trace: nil)` | Frozen *(new in 1.0)* |
|
|
170
|
+
| `#infos / #warnings / #errors -> Array<LogItem>` | Frozen |
|
|
171
|
+
|
|
172
|
+
See [Logging and results](./guides/logging-and-results.md) for the
|
|
173
|
+
`log_item_*` shorthands vs. the explicit `add_log` form.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Execute callbacks
|
|
178
|
+
|
|
179
|
+
New in 1.0. Class-level DSL on `Assistant::Service`; see the table
|
|
180
|
+
under [Class methods](#class-methods).
|
|
181
|
+
|
|
182
|
+
Hook error semantics: an exception raised inside any `before_execute`,
|
|
183
|
+
`after_execute`, or `around_execute` hook is caught and logged via
|
|
184
|
+
`add_log(level: :error, source: :hook, ...)` — it **never** propagates
|
|
185
|
+
out of `#run`. See [Composing services](./guides/composing-services.md).
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Service composition
|
|
190
|
+
|
|
191
|
+
| Signature | Stability |
|
|
192
|
+
|------------------------------------------------------------|--------------------------|
|
|
193
|
+
| `#call_service(klass, **inputs) -> Assistant::Service` | Frozen *(new in 1.0)* |
|
|
194
|
+
|
|
195
|
+
Constructs `klass`, runs it, merges the inner instance's `#logs` into
|
|
196
|
+
the caller's, and returns the inner instance for further inspection.
|
|
197
|
+
If the inner service has any error-level log item, the outer service's
|
|
198
|
+
status becomes `:with_errors`.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Instrumentation notifier
|
|
203
|
+
|
|
204
|
+
Configured via `Assistant.notifier = ->(event, payload) { ... }`.
|
|
205
|
+
|
|
206
|
+
Events emitted in 1.0, each with a payload that always carries
|
|
207
|
+
`:service_class` and `:duration_s`:
|
|
208
|
+
|
|
209
|
+
- `:service_started`
|
|
210
|
+
- `:service_validated`
|
|
211
|
+
- `:service_executed`
|
|
212
|
+
- `:service_failed`
|
|
213
|
+
|
|
214
|
+
The event set is **Frozen** for 1.0. Adding events requires a minor
|
|
215
|
+
release; removing one or removing a payload key requires a full
|
|
216
|
+
deprecation cycle.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Input snapshot
|
|
221
|
+
|
|
222
|
+
| Signature | Stability | Description |
|
|
223
|
+
|------------------------------------|--------------------------|--------------------------------------------------------------------------------------------------------------|
|
|
224
|
+
| `#input_snapshot -> Data` | Frozen *(new in 1.0)* | Returns a frozen `Data.define(*declared_input_names).new(**post_default_inputs)`. Reflects post-`default:` / post-`allow_nil:` values. |
|
|
225
|
+
|
|
226
|
+
The `Data` class is memoised on the service class — repeated calls
|
|
227
|
+
return values whose `class` is `equal?`. See
|
|
228
|
+
[Composing services](./guides/composing-services.md).
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## `assistant-rbs` CLI
|
|
233
|
+
|
|
234
|
+
Bundled executable shipped at `exe/assistant-rbs` (M11). Generates
|
|
235
|
+
per-class RBS signatures for `Assistant::Service` subclasses so Steep
|
|
236
|
+
can type-check user code.
|
|
237
|
+
|
|
238
|
+
| Invocation | Stability |
|
|
239
|
+
|-------------------------------------------------------------|---------------|
|
|
240
|
+
| `bundle exec assistant-rbs PATH [--output sig/]` | Experimental |
|
|
241
|
+
|
|
242
|
+
Scans `PATH` for `Assistant::Service` subclasses and emits
|
|
243
|
+
`sig/<class>.rbs` with `def name: () -> Type` and `def name?: () -> bool`
|
|
244
|
+
per declared input. A multi-type `type:` produces a union; `allow_nil:`
|
|
245
|
+
produces a nullable type. The output is idempotent — running twice
|
|
246
|
+
overwrites the same file with the same contents.
|
|
247
|
+
|
|
248
|
+
Labelled **Experimental** in 1.0 because the output format may evolve
|
|
249
|
+
in 1.x as RBS support for richer types matures.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Semver and deprecation policy
|
|
254
|
+
|
|
255
|
+
- **Patch (1.0.x)**: bug fixes, doc updates, internal refactors with no
|
|
256
|
+
observable behaviour change.
|
|
257
|
+
- **Minor (1.x.0)**: additive API only — new `input` options, new
|
|
258
|
+
`LogItem` predicates, new `Service` hooks. No removals.
|
|
259
|
+
- **Major (2.0.0)**: removals, renames, behaviour changes that break
|
|
260
|
+
the contract above.
|
|
261
|
+
|
|
262
|
+
Every deprecated symbol lives for **at least one further minor** after
|
|
263
|
+
its deprecation, with a `Kernel.warn` runtime warning and a row in
|
|
264
|
+
[`docs/deprecations.md`](./deprecations.md).
|
data/docs/changelog.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Changelog
|
|
3
|
+
nav_order: 7
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Changelog
|
|
7
|
+
|
|
8
|
+
The full release history is in
|
|
9
|
+
[`CHANGELOG.md`](https://github.com/ramongr/assistant/blob/main/CHANGELOG.md)
|
|
10
|
+
at the repository root, formatted per
|
|
11
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
12
|
+
|
|
13
|
+
The same file is what `assistant.gemspec` points to via
|
|
14
|
+
`spec.metadata['changelog_uri']`, so RubyGems shows the identical content
|
|
15
|
+
under the gem's **Changelog** tab.
|
|
16
|
+
|
|
17
|
+
## How to read it
|
|
18
|
+
|
|
19
|
+
- `[Unreleased]` collects changes that have merged to `main` but have not
|
|
20
|
+
been cut into a release tag.
|
|
21
|
+
- Each released version uses an ISO date and groups entries by
|
|
22
|
+
`### Added` / `### Changed` / `### Deprecated` / `### Removed` /
|
|
23
|
+
`### Fixed` / `### Security`.
|
|
24
|
+
- `### Changed (Breaking)` callouts mark semver-breaking changes; for
|
|
25
|
+
the 0.x → 1.x sweep, those are catalogued in
|
|
26
|
+
[`docs/v1/06-migration-0x-to-1.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/06-migration-0x-to-1.md).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deprecations
|
|
3
|
+
nav_order: 4
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deprecations
|
|
7
|
+
|
|
8
|
+
This page tracks every public symbol that is **deprecated** in a 1.x release
|
|
9
|
+
and the version in which it will be removed. Each entry includes a 1:1
|
|
10
|
+
replacement and the runtime warning users will see.
|
|
11
|
+
|
|
12
|
+
The deprecation policy is one full minor cycle: anything marked here in
|
|
13
|
+
`1.x` is removed in `2.0`.
|
|
14
|
+
|
|
15
|
+
## Index
|
|
16
|
+
|
|
17
|
+
| Symbol | Replacement | Deprecated in | Removed in |
|
|
18
|
+
|---------------------------------------------------------|-------------------------------------------------------------------|---------------|------------|
|
|
19
|
+
| `Assistant::Service#valid_require_<name>?` | `Assistant::Service#valid_required_<name>?` | `1.0.0` (M9) | `2.0.0` |
|
|
20
|
+
| `Assistant::Service#valid_require_conditional_<name>?` | `Assistant::Service#valid_required_conditional_<name>?` | `1.0.0` (M9) | `2.0.0` |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## `valid_require_<name>?` → `valid_required_<name>?`
|
|
25
|
+
|
|
26
|
+
**Deprecated in**: `1.0.0` (M9). **Removed in**: `2.0.0`.
|
|
27
|
+
|
|
28
|
+
### What changed
|
|
29
|
+
|
|
30
|
+
Per-input requirement validators are now generated under their
|
|
31
|
+
grammatically correct names. For each `input :name, required: true`
|
|
32
|
+
declaration, `Assistant::Service` subclasses gain:
|
|
33
|
+
|
|
34
|
+
| Canonical (use this) | Deprecated alias (still works, warns once per call site) |
|
|
35
|
+
|-----------------------------------------------|----------------------------------------------------------|
|
|
36
|
+
| `#valid_required_<name>?` | `#valid_require_<name>?` |
|
|
37
|
+
| `#valid_required_conditional_<name>?` | `#valid_require_conditional_<name>?` |
|
|
38
|
+
|
|
39
|
+
Both names refer to the same underlying predicate — the deprecated alias
|
|
40
|
+
simply delegates to the canonical method after emitting the deprecation
|
|
41
|
+
warning. Return values, side effects, and `log_item_error_initialize`
|
|
42
|
+
behaviour are unchanged.
|
|
43
|
+
|
|
44
|
+
### Runtime warning
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
assistant: `#valid_require_email?` is deprecated; use `#valid_required_email?` (removed in assistant 2.0)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The warning fires through `Kernel.warn` (i.e. on `$stderr`) **once per
|
|
51
|
+
textual call site** (`caller_locations(1, 1).first` is keyed by `path +
|
|
52
|
+
lineno`). Calling the alias 100 times from the same line yields exactly
|
|
53
|
+
one warning; calling it from two different lines yields two warnings.
|
|
54
|
+
|
|
55
|
+
Internal framework code (`Service#validate_inputs`) calls only the
|
|
56
|
+
canonical name, so the warning never fires from inside the gem.
|
|
57
|
+
|
|
58
|
+
### Migration
|
|
59
|
+
|
|
60
|
+
Search-and-replace at the call site:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# Before
|
|
64
|
+
service.valid_require_email?
|
|
65
|
+
service.valid_require_conditional_token?
|
|
66
|
+
|
|
67
|
+
# After
|
|
68
|
+
service.valid_required_email?
|
|
69
|
+
service.valid_required_conditional_token?
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If your code overrides one of these predicates (`def valid_require_email?
|
|
73
|
+
...`), rename the override to the canonical name. The deprecated alias
|
|
74
|
+
will still exist in `1.x` and will continue to delegate to the canonical
|
|
75
|
+
method, so an override under the old name is silently bypassed.
|
|
76
|
+
|
|
77
|
+
### Why
|
|
78
|
+
|
|
79
|
+
Q2 in
|
|
80
|
+
[`docs/v1/07-risks-and-open-questions.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/07-risks-and-open-questions.md)
|
|
81
|
+
was decided in favour of Option B: the new names read better, match
|
|
82
|
+
standard English, and are easier to grep for. The old names live one
|
|
83
|
+
minor cycle to give downstream services an upgrade window.
|
|
84
|
+
|
|
85
|
+
See [`docs/v1/02-features.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/02-features.md) **M9** for the
|
|
86
|
+
implementation plan and acceptance criteria.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CLI handler
|
|
3
|
+
parent: Examples
|
|
4
|
+
nav_order: 2
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CLI handler
|
|
8
|
+
|
|
9
|
+
> **Status:** placeholder — ships in
|
|
10
|
+
> [P7](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
|
+
An `OptionParser`-driven script whose exit code derives from `#status`.
|
|
14
|
+
|
|
15
|
+
When the runnable script under `examples/cli_handler/` 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,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Composing services
|
|
3
|
+
parent: Examples
|
|
4
|
+
nav_order: 4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Composing services
|
|
8
|
+
|
|
9
|
+
> **Status:** placeholder — ships in
|
|
10
|
+
> [P9](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
|
+
An outer service uses `call_service` to chain two inner services with log timeline merging.
|
|
14
|
+
|
|
15
|
+
When the runnable script under `examples/composing_services/` 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,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Execute callbacks
|
|
3
|
+
parent: Examples
|
|
4
|
+
nav_order: 5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Execute callbacks
|
|
8
|
+
|
|
9
|
+
> **Status:** placeholder — ships in
|
|
10
|
+
> [P10](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
|
+
`before_execute` audit logger plus an `around_execute` timing wrapper, including failure cases.
|
|
14
|
+
|
|
15
|
+
When the runnable script under `examples/execute_callbacks/` 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,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Examples
|
|
3
|
+
nav_order: 5
|
|
4
|
+
has_children: true
|
|
5
|
+
permalink: /examples/
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Examples
|
|
9
|
+
|
|
10
|
+
> **Status:** gallery scaffolding — each entry below ships with a
|
|
11
|
+
> runnable script under `examples/<slug>/`, a writeup on this site,
|
|
12
|
+
> and a regression test. See
|
|
13
|
+
> [P6–P12 of the GitHub Pages plan](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md#p6p12-examples-one-pr-per-example)
|
|
14
|
+
> for the per-example schedule.
|
|
15
|
+
|
|
16
|
+
| Example | Demonstrates |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| [Rails service](rails-service.md) | Rails-shaped controller; `case service.run in { result:, status: :ok }`. |
|
|
19
|
+
| [CLI handler](cli-handler.md) | `OptionParser` driving a service; exit code derived from `#status`. |
|
|
20
|
+
| [Sidekiq worker](sidekiq-worker.md) | Worker class that runs a service; idempotent; logs warnings vs errors separately. |
|
|
21
|
+
| [Composing services](composing-services.md) | Outer service uses `call_service` to chain two inner services; log timeline merging. |
|
|
22
|
+
| [Execute callbacks](execute-callbacks.md) | `before_execute` audit logger; `around_execute` timing wrapper; failure cases. |
|
|
23
|
+
| [Instrumentation notifier](instrumentation-notifier.md) | `Assistant.notifier=` wired to a fake `ActiveSupport::Notifications`-shaped sink. |
|
|
24
|
+
| [RBS generator](rbs-generator.md) | Service definition → `bin/assistant-rbs --output sig` → Steep proving per-input return types. |
|
|
25
|
+
|
|
26
|
+
Each example is intentionally small enough to be read in one sitting.
|
|
27
|
+
Source for every script lives under
|
|
28
|
+
[`examples/`](https://github.com/ramongr/assistant/tree/main/examples)
|
|
29
|
+
in the repository.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Instrumentation notifier
|
|
3
|
+
parent: Examples
|
|
4
|
+
nav_order: 6
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Instrumentation notifier
|
|
8
|
+
|
|
9
|
+
> **Status:** placeholder — ships in
|
|
10
|
+
> [P11](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
|
+
`Assistant.notifier=` wired to a fake `ActiveSupport::Notifications`-shaped sink.
|
|
14
|
+
|
|
15
|
+
When the runnable script under `examples/instrumentation_notifier/` 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,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Rails service
|
|
3
|
+
parent: Examples
|
|
4
|
+
nav_order: 1
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Rails service
|
|
8
|
+
|
|
9
|
+
> **Status:** placeholder — ships in
|
|
10
|
+
> [P6](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 Rails-shaped controller calling a service and pattern-matching on the result hash.
|
|
14
|
+
|
|
15
|
+
When the runnable script under `examples/rails_service/` 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,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: RBS generator
|
|
3
|
+
parent: Examples
|
|
4
|
+
nav_order: 7
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# RBS generator
|
|
8
|
+
|
|
9
|
+
> **Status:** placeholder — ships in
|
|
10
|
+
> [P12](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
|
+
Service definition → `bin/assistant-rbs --output sig` → Steep proving the per-input return type.
|
|
14
|
+
|
|
15
|
+
When the runnable script under `examples/rbs_generator/` lands, this
|
|
16
|
+
page will include it verbatim via Jekyll `include_relative` so the
|
|
17
|
+
prose stays in lockstep with the code.
|