From e6534e9b6f907a15c5909cf70ac73f6771f1ff63 Mon Sep 17 00:00:00 2001 From: zewelor Date: Tue, 16 Jun 2026 08:41:00 +0100 Subject: [PATCH] Restore RBS + Steep static type checks --- .rubocop.yml | 3 - AGENTS.md | 13 +- Gemfile | 4 + Gemfile.lock | 59 +++++++ Steepfile | 24 +++ app/lib/r3x/client/google/gmail.rb | 2 +- app/lib/r3x/client/llm.rb | 6 +- app/lib/r3x/client/ocr.rb | 2 +- bin/typecheck | 6 + config/ci.rb | 1 + docs/todo.md | 29 ++-- sig/r3x/client/apify.rbs | 24 +++ sig/r3x/client/discord.rbs | 17 +++ sig/r3x/client/google/gmail.rbs | 21 +++ sig/r3x/client/google/translate.rbs | 25 +++ sig/r3x/client/google_sheets.rbs | 21 +++ sig/r3x/client/healthchecks_io.rbs | 29 ++++ sig/r3x/client/healthchecks_io/response.rbs | 27 ++++ sig/r3x/client/http.rbs | 33 ++++ sig/r3x/client/http/downloaded_file.rbs | 16 ++ sig/r3x/client/llm.rbs | 37 +++++ sig/r3x/client/llm/classifier.rbs | 21 +++ sig/r3x/client/llm/provider_configuration.rbs | 16 ++ sig/r3x/client/llm/provider_registry.rbs | 13 ++ sig/r3x/client/markdownify.rbs | 23 +++ sig/r3x/client/miniflux.rbs | 30 ++++ sig/r3x/client/ocr.rbs | 27 ++++ sig/r3x/client/ocr/result.rbs | 29 ++++ sig/r3x/client/prometheus.rbs | 15 ++ sig/r3x/client/prometheus/result.rbs | 20 +++ sig/r3x/client/victoria_logs.rbs | 20 +++ sig/r3x/external_stubs.rbs | 144 ++++++++++++++++++ sig/r3x/workflow/base.rbs | 7 + sig/r3x/workflow/context.rbs | 16 ++ sig/r3x/workflow/context/client.rbs | 63 ++++++++ sig/r3x/workflow/durable_set.rbs | 17 +++ sig/r3x/workflow/execution.rbs | 13 ++ 37 files changed, 853 insertions(+), 20 deletions(-) create mode 100644 Steepfile create mode 100755 bin/typecheck create mode 100644 sig/r3x/client/apify.rbs create mode 100644 sig/r3x/client/discord.rbs create mode 100644 sig/r3x/client/google/gmail.rbs create mode 100644 sig/r3x/client/google/translate.rbs create mode 100644 sig/r3x/client/google_sheets.rbs create mode 100644 sig/r3x/client/healthchecks_io.rbs create mode 100644 sig/r3x/client/healthchecks_io/response.rbs create mode 100644 sig/r3x/client/http.rbs create mode 100644 sig/r3x/client/http/downloaded_file.rbs create mode 100644 sig/r3x/client/llm.rbs create mode 100644 sig/r3x/client/llm/classifier.rbs create mode 100644 sig/r3x/client/llm/provider_configuration.rbs create mode 100644 sig/r3x/client/llm/provider_registry.rbs create mode 100644 sig/r3x/client/markdownify.rbs create mode 100644 sig/r3x/client/miniflux.rbs create mode 100644 sig/r3x/client/ocr.rbs create mode 100644 sig/r3x/client/ocr/result.rbs create mode 100644 sig/r3x/client/prometheus.rbs create mode 100644 sig/r3x/client/prometheus/result.rbs create mode 100644 sig/r3x/client/victoria_logs.rbs create mode 100644 sig/r3x/external_stubs.rbs create mode 100644 sig/r3x/workflow/base.rbs create mode 100644 sig/r3x/workflow/context.rbs create mode 100644 sig/r3x/workflow/context/client.rbs create mode 100644 sig/r3x/workflow/durable_set.rbs create mode 100644 sig/r3x/workflow/execution.rbs diff --git a/.rubocop.yml b/.rubocop.yml index 91bb625..81dac2a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -40,9 +40,6 @@ Style/ItBlockParameter: Enabled: true EnforcedStyle: allow_single_line -Style/EmptyLiteral: - Enabled: true - Layout/LineLength: Enabled: true Max: 140 diff --git a/AGENTS.md b/AGENTS.md index b68be7d..01dcc6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,11 +167,20 @@ bin/workflow [options] [command] [arguments] This repo uses `.githooks/` directory for git hooks. The pre-commit hook runs `bin/ci` which includes `bin/lint-r3x` to verify AGENTS.md references. +## Static Typing + +- This repo uses RBS + Steep for static type checks. The current Steep scope is intentionally narrow and starts with the workflow-facing API contract. +- Run `bin/typecheck` after changing files covered by `Steepfile`. +- When changing `R3x::Workflow::Context`, `R3x::Workflow::Context::Client`, or `R3x::Client::Http`, update the matching files under `sig/`. +- When adding a new workflow client method to `R3x::Workflow::Context::Client`, add its RBS signature and any minimal dependency stub needed for `bin/typecheck`. +- Keep the Steep scope narrow. Do not expand it to the whole Rails app unless explicitly planned. +- Treat `sig/r3x/client/_stubs.rbs` and `sig/r3x/external_stubs.rbs` as dependency stubs, not source of truth for unchecked implementation details. + ## Iterative Design Reviews - When the user is still reviewing the shape of a refactor or API design, keep the first pass focused on production code only unless they explicitly ask for full follow-through. -- Do not update tests or fixtures for an unaccepted design sketch. Wait until the user accepts the code shape, then synchronize tests in the follow-up pass. -- It is still fine to run syntax checks on the touched Ruby files during the sketch phase. +- Do not update tests, fixtures, or RBS signatures for an unaccepted design sketch. Wait until the user accepts the code shape, then synchronize tests and `sig/` files in the follow-up pass. +- It is still fine to run syntax checks on the touched Ruby files during the sketch phase. Do not treat typecheck failures caused only by intentionally stale RBS as blockers until the user accepts the design. ## JSON diff --git a/Gemfile b/Gemfile index 786b9cb..f5f9959 100644 --- a/Gemfile +++ b/Gemfile @@ -70,6 +70,10 @@ group :development, :test do gem "rubocop-minitest", require: false gem "rubocop-thread_safety", "~> 0.7.3", require: false + # Narrow static type-checking spike for workflow-facing API contracts. + gem "rbs", require: false + gem "steep", require: false + # Auto-load environment variables from .env file gem "dotenv-rails", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index ae2faa0..ddaf6f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -95,6 +95,7 @@ GEM bigdecimal rexml crass (1.0.6) + csv (3.3.5) date (3.5.1) debug (1.11.1) irb (~> 1.10) @@ -122,6 +123,15 @@ GEM net-http (~> 0.5) faraday-retry (2.4.0) faraday (~> 2.0) + ffi (1.17.4-aarch64-linux-gnu) + ffi (1.17.4-aarch64-linux-musl) + ffi (1.17.4-arm-linux-gnu) + ffi (1.17.4-arm-linux-musl) + ffi (1.17.4-arm64-darwin) + ffi (1.17.4-x86_64-darwin) + ffi (1.17.4-x86_64-linux-gnu) + ffi (1.17.4-x86_64-linux-musl) + fileutils (1.8.0) fugit (1.12.2) et-orbi (~> 1.4) raabro (~> 1.4) @@ -267,6 +277,10 @@ GEM base64 language_server-protocol (3.17.0.5) lint_roller (1.1.0) + listen (3.10.0) + logger + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) loofah (2.25.1) crass (~> 1.0.2) @@ -394,6 +408,13 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.4.2) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (4.0.2) + logger + prism (>= 1.6.0) + tsort rdoc (7.2.0) erb psych (>= 4.0.0) @@ -483,9 +504,28 @@ GEM sqlite3 (2.9.5-x86_64-darwin) sqlite3 (2.9.5-x86_64-linux-gnu) sqlite3 (2.9.5-x86_64-linux-musl) + steep (2.0.0) + concurrent-ruby (>= 1.1.10) + csv (>= 3.0.9) + fileutils (>= 1.1.0) + json (>= 2.1.0) + language_server-protocol (>= 3.17.0.4, < 4.0) + listen (~> 3.0) + logger (>= 1.3.0) + parser (>= 3.2) + prism (>= 0.25.0) + rainbow (>= 2.2.2, < 4.0) + rbs (~> 4.0) + securerandom (>= 0.1) + strscan (>= 1.0.0) + terminal-table (>= 2, < 5) + uri (>= 0.12.0) stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.2.0) + strscan (3.1.8) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) thor (1.5.0) timeout (0.6.1) trailblazer-option (0.1.2) @@ -545,6 +585,7 @@ DEPENDENCIES propshaft puma (>= 5.0) rails (~> 8.1.2) + rbs retryable (~> 3.0) rss rubocop-minitest @@ -555,6 +596,7 @@ DEPENDENCIES solid_cache solid_queue sqlite3 (>= 2.1) + steep webmock CHECKSUMS @@ -583,6 +625,7 @@ CHECKSUMS connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d + csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6 declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 @@ -598,6 +641,15 @@ CHECKSUMS faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757 faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588 faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe + ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df + ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39 + ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564 + ffi (1.17.4-arm-linux-musl) sha256=9d4838ded0465bef6e2426935f6bcc93134b6616785a84ffd2a3d82bc3cf6f95 + ffi (1.17.4-arm64-darwin) sha256=19071aaf1419251b0a46852abf960e77330a3b334d13a4ab51d58b31a937001b + ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496 + ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d + ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e + fileutils (1.8.0) sha256=8c6b1df54e2540bdb2f39258f08af78853aa70bad52b4d394bbc6424593c6e02 fugit (1.12.2) sha256=643f2bf28db263bd400cbf8e0dd8b76b2c9b94bdb130e12d2394de04d9c20e5e gapic-common (1.3.0) sha256=4e5d9be1effb7366e2cda13c0f44922285d5613ac8de8b9b18c2c835fe4faa91 globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 @@ -645,6 +697,7 @@ CHECKSUMS jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7 language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 + listen (3.10.0) sha256=c6e182db62143aeccc2e1960033bebe7445309c7272061979bb098d03760c9d2 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04 mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941 @@ -700,6 +753,9 @@ CHECKSUMS railties (8.1.3) sha256=913eb0e0cb520aac687ffd74916bd726d48fa21f47833c6292576ef6a286de22 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 + rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe + rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e + rbs (4.0.2) sha256=af75671e66cd03434cc546622741ebf83f6197ec4328375805306330bf78ef25 rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 @@ -731,8 +787,11 @@ CHECKSUMS sqlite3 (2.9.5-x86_64-darwin) sha256=8e9caae38bd7ebb29cbeee3e7ab1d12dc2327d9a1b92c7fcf0dda05589627a81 sqlite3 (2.9.5-x86_64-linux-gnu) sha256=233dbcb6714148dd23bc5aeb33e8efd6eac974969564ddd5794c23d5f52b231e sqlite3 (2.9.5-x86_64-linux-musl) sha256=e7d3a7474e8af0f96150c21abc203fbab5437206bfcdf11deab7741c0ca516f2 + steep (2.0.0) sha256=6eb0ecc09637bbb54f0a5f2cf63daea6d3208ccace64b4f1107d976333605c30 stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 + strscan (3.1.8) sha256=aae2db611a225559f21ffbb71765c9a4e60fd262534a9ea84f4f11c7f32f679e + terminal-table (4.0.0) sha256=f504793203f8251b2ea7c7068333053f0beeea26093ec9962e62ea79f94301d2 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 diff --git a/Steepfile b/Steepfile new file mode 100644 index 0000000..dc5211b --- /dev/null +++ b/Steepfile @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +target :static_types do + signature "sig" + + check "lib/r3x/workflow/context.rb" + check "lib/r3x/workflow/context/client.rb" + + check "app/lib/r3x/client/http.rb" + check "app/lib/r3x/client/http/downloaded_file.rb" + check "app/lib/r3x/client/miniflux.rb" + check "app/lib/r3x/client/victoria_logs.rb" + + check "app/lib/r3x/client/apify.rb" + check "app/lib/r3x/client/discord.rb" + check "app/lib/r3x/client/google/gmail.rb" + check "app/lib/r3x/client/google/translate.rb" + check "app/lib/r3x/client/google_sheets.rb" + check "app/lib/r3x/client/healthchecks_io.rb" + check "app/lib/r3x/client/llm.rb" + check "app/lib/r3x/client/markdownify.rb" + check "app/lib/r3x/client/ocr.rb" + check "app/lib/r3x/client/prometheus.rb" +end diff --git a/app/lib/r3x/client/google/gmail.rb b/app/lib/r3x/client/google/gmail.rb index 8e8dc25..9e20ea1 100644 --- a/app/lib/r3x/client/google/gmail.rb +++ b/app/lib/r3x/client/google/gmail.rb @@ -47,7 +47,7 @@ def raw_message(to:, subject:, body:, attachments: []) mail.subject = subject if attachments.any? - mail.text_part = Mail::Part.new { it.body = body } + mail.text_part = Mail::Part.new { |part| part.body = body } attachments.each { |attachment| mail.add_file(filename: attachment[:filename], content: attachment[:content]) } else diff --git a/app/lib/r3x/client/llm.rb b/app/lib/r3x/client/llm.rb index 5aca91e..58409ba 100644 --- a/app/lib/r3x/client/llm.rb +++ b/app/lib/r3x/client/llm.rb @@ -4,8 +4,8 @@ class Llm MAX_RETRIES = 3 RETRY_INTERVAL = 60.0 RETRY_BACKOFF_FACTOR = 2 - DEFAULT_CHAT_OPTIONS = {}.freeze - CHAT_OPTIONS_BY_PROVIDER = {} + DEFAULT_CHAT_OPTIONS = Hash.new.freeze + CHAT_OPTIONS_BY_PROVIDER = Hash.new CHAT_OPTIONS_BY_PROVIDER_MUTEX = Mutex.new class << self @@ -52,7 +52,7 @@ def initialize( end def analyze_image(image_bytes, prompt:, model:, schema: nil) - image = StringIO.new(image_bytes).tap { it.set_encoding(Encoding::BINARY) } + image = StringIO.new(image_bytes).tap { |io| io.set_encoding(Encoding::BINARY) } ask_model(model:, prompt:, schema:, attachments: [ image ]).content end diff --git a/app/lib/r3x/client/ocr.rb b/app/lib/r3x/client/ocr.rb index b165846..d39ad2a 100644 --- a/app/lib/r3x/client/ocr.rb +++ b/app/lib/r3x/client/ocr.rb @@ -56,7 +56,7 @@ def detect_mime(io_or_path) end def build_params(io_or_path, mime_type, language:, engine:, overlay:) - params = {} + params = Hash.new params[:isOverlayRequired] = overlay.to_s params[:language] = language if language params[:OCREngine] = engine.to_s if engine diff --git a/bin/typecheck b/bin/typecheck new file mode 100755 index 0000000..32c3cef --- /dev/null +++ b/bin/typecheck @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" + +exec Gem.bin_path("steep", "steep"), "check", *ARGV diff --git a/config/ci.rb b/config/ci.rb index d4cac2f..d27d781 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -6,6 +6,7 @@ step "Style: Ruby", "bin/rubocop" step "Style: YAML", "bin/dprint check" step "Lint: AGENTS.md references", "bin/lint-r3x" + step "Types: Static Types", "bin/typecheck" step "Security: Gem audit", "bin/bundler-audit" step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" diff --git a/docs/todo.md b/docs/todo.md index 1b3b53d..74cfd9b 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -163,18 +163,26 @@ This is brittle against interpreter changes and hard to understand. ## Phase 4 — long-term / optional -### [x] K. Remove RBS + Steep static type checks +### [x] K. RBS signatures are stale for new clients -**Files:** `sig/`, `Steepfile`, `bin/typecheck`, `Gemfile`, `config/ci.rb` +**Files:** `sig/r3x/client/*.rbs`, `Steepfile` **Done:** -- Removed the `sig/` directory, `Steepfile`, and `bin/typecheck` script. -- Removed `rbs` and `steep` gems from `Gemfile` and regenerated `Gemfile.lock`. -- Removed the "Types: Static Types" step from `config/ci.rb`. -- Removed the Static Typing section from `AGENTS.md` and updated references in `docs/todo.md`. - -**Reason:** Steep does not yet support Ruby 3.4's `it` block parameter (see https://github.com/soutaro/steep/pull/2238). Keeping RBS signatures in sync was also adding friction without enough payoff at the current project size. Static typing can be reintroduced once the upstream blocker is resolved. +- Added per-client RBS signatures under `sig/r3x/client/` for: + - `apify`, `discord`, `google/gmail`, `google/translate`, `google_sheets`, `healthchecks_io`, `markdownify`, `ocr`, `prometheus`, `llm` (and nested `Classifier`, `ProviderConfiguration`, `ProviderRegistry`). +- Added signatures for client result/response objects: `Ocr::Result`, `HealthchecksIO::Response`, `Prometheus::Result`. +- Removed the stale `sig/r3x/workflow/base.rbs` (the class was not in `Steepfile` and the signature was incomplete). +- Removed the consolidated `sig/r3x/client/_stubs.rbs` in favor of explicit per-client signature files. +- Added the new clients to `Steepfile` so `bin/typecheck` covers them. +- Added missing dependency stubs to `sig/r3x/external_stubs.rbs` (`HTTPX.get/post/head`, `R3x::Env.fetch!`, `R3x::Client::GoogleAuth`, `Google::Apis::*`, `Mail::Part`, `RubyLLM`, `SecureRandom`, `Base64`, `String#blank?`). +- Made small code tweaks to keep type-checking clean: + - `Ocr#build_params` now starts with an empty `Hash.new`. + - `Llm` uses `Hash.new` for class-level caches and an explicit block param instead of `it`. + - `Google::Translate#translate` validates the translations array explicitly. + - `Google::Gmail#raw_message` uses an explicit `Mail::Part` block param. + +**Verification:** `bin/typecheck` passes; `bin/rails test` passes; `bin/lint-r3x` passes. --- @@ -182,7 +190,7 @@ This is brittle against interpreter changes and hard to understand. **File:** `.githooks/pre-commit` -The hook runs `bin/ci`, which includes `bin/lint-r3x`, RuboCop, dprint, bundler-audit, brakeman, and the full test suite. This can be slow for every commit. +The hook runs `bin/ci`, which includes `bin/lint-r3x`, RuboCop, dprint, typecheck, bundler-audit, brakeman, and the full test suite. This can be slow for every commit. **Suggested fix:** consider a lighter pre-commit (format + lint references) and run the full suite on push / in CI. @@ -195,7 +203,8 @@ These are not actionable todos yet; they are framing notes for larger refactor d ### DHH / 37signals perspective - **Less layering, more Rails.** Much of `app/lib/r3x/dashboard/workflow/` (`Catalog`, `Summaries`, `Runs`) is logic that belongs in models under `app/models/dashboard/`, not in parallel service/query layers. `Dashboard::Run` is the model — let it own its queries and summaries. -- **Pre-commit running full CI is expensive.** Running `bin/ci` (RuboCop, dprint, bundler-audit, brakeman, full test suite) on every commit slows the local loop. A lighter pre-commit with full CI on push is more Rails-like. +- **Pre-commit running full CI is expensive.** Running `bin/ci` (RuboCop, dprint, typecheck, bundler-audit, brakeman, full test suite) on every commit slows the local loop. A lighter pre-commit with full CI on push is more Rails-like. +- **RBS/Steep may be overhead at this size.** Keeping signatures in sync is a tax. If the team is not actively relying on Steep, the narrow scope is good, but widening it would be a mistake. - **Fail fast, don't swallow errors.** `R3x::Env.load_from_vault` catching `StandardError` violates the 37signals preference for loud failures over silent degradation. - **Use your own conventions everywhere.** Direct `ENV.fetch` calls in `config/puma.rb` and `production.rb` undermine the `R3x::Env` helper and create inconsistent semantics. diff --git a/sig/r3x/client/apify.rbs b/sig/r3x/client/apify.rbs new file mode 100644 index 0000000..f407dd9 --- /dev/null +++ b/sig/r3x/client/apify.rbs @@ -0,0 +1,24 @@ +module R3x + module Client + class Apify + include R3x::Concerns::Logger + + BASE_URL: String + DEFAULT_API_KEY_ENV: String + + def initialize: (api_key: String api_key) -> void + + def run_actor: (String actor_id, ?input: untyped input, **untyped options) -> Hash[untyped, untyped] + + def run_actor_sync_get_items: (String actor_id, ?input: untyped input, ?format: String format, ?clean: bool clean, ?limit: Integer? limit, **untyped options) -> untyped + + def raw: () -> untyped + + private + + attr_reader api_key: String + + def connection: () -> untyped + end + end +end diff --git a/sig/r3x/client/discord.rbs b/sig/r3x/client/discord.rbs new file mode 100644 index 0000000..e5dbde4 --- /dev/null +++ b/sig/r3x/client/discord.rbs @@ -0,0 +1,17 @@ +module R3x + module Client + class Discord + include R3x::Concerns::Logger + + DEFAULT_WEBHOOK_URL_ENV: String + + def initialize: (?webhook_url: String? webhook_url, ?webhook_url_env: String webhook_url_env) -> void + + def deliver: (content: String content) -> Hash[String, String] + + private + + attr_reader webhook_url: String + end + end +end diff --git a/sig/r3x/client/google/gmail.rbs b/sig/r3x/client/google/gmail.rbs new file mode 100644 index 0000000..ef96a89 --- /dev/null +++ b/sig/r3x/client/google/gmail.rbs @@ -0,0 +1,21 @@ +module R3x + module Client + module Google + class Gmail + include R3x::Concerns::Logger + + def initialize: (project: String project) -> void + + def deliver: (to: String to, subject: String subject, body: String body, ?attachments: Array[Hash[Symbol, untyped]] attachments) -> Hash[String, String] + + private + + attr_reader project: String + + def build_service: () -> untyped + + def raw_message: (to: String to, subject: String subject, body: String body, attachments: Array[Hash[Symbol, untyped]] attachments) -> String + end + end + end +end diff --git a/sig/r3x/client/google/translate.rbs b/sig/r3x/client/google/translate.rbs new file mode 100644 index 0000000..18d568b --- /dev/null +++ b/sig/r3x/client/google/translate.rbs @@ -0,0 +1,25 @@ +module R3x + module Client + module Google + class Translate + API_URL: String + + def initialize: (project: String project) -> void + + def translate: (untyped text, to: String to, ?from: String? from, ?format: String format) -> String + + private + + attr_reader project: String + + def authorization: () -> untyped + + def authorization_header: () -> Hash[String, String] + + def access_token: () -> String + + def request_body: (String text, to: String to, from: String? from, format: String format) -> Hash[Symbol, untyped] + end + end + end +end diff --git a/sig/r3x/client/google_sheets.rbs b/sig/r3x/client/google_sheets.rbs new file mode 100644 index 0000000..7d3be24 --- /dev/null +++ b/sig/r3x/client/google_sheets.rbs @@ -0,0 +1,21 @@ +module R3x + module Client + class GoogleSheets + def initialize: (spreadsheet_id: String spreadsheet_id, project: String project) -> void + + def read_rows: (range: String range, ?headers: bool headers) -> Array[untyped] + + private + + attr_reader spreadsheet_id: String + attr_reader project: String + attr_reader service: untyped + + def build_service: () -> untyped + + def make_unique_headers: (untyped headers) -> untyped + + def row_to_hash: (untyped headers, untyped row) -> Hash[untyped, untyped] + end + end +end diff --git a/sig/r3x/client/healthchecks_io.rbs b/sig/r3x/client/healthchecks_io.rbs new file mode 100644 index 0000000..97e3ca1 --- /dev/null +++ b/sig/r3x/client/healthchecks_io.rbs @@ -0,0 +1,29 @@ +module R3x + module Client + class HealthchecksIO + include R3x::Concerns::Logger + + def initialize: (String check_uuid, ?ping_endpoint: String? ping_endpoint, ?ping_endpoint_env: String ping_endpoint_env) -> void + + def run: () { (HealthchecksIO, String) -> untyped } -> untyped + + def ping: (?body: String? body, ?rid: String? rid) -> HealthchecksIO::Response + + def fail: (?body: String? body, ?rid: String? rid) -> HealthchecksIO::Response + + def log: (lines: Array[String] | String lines, ?rid: String? rid) -> HealthchecksIO::Response + + def exit_status: (code: Integer code, ?body: String? body, ?rid: String? rid) -> HealthchecksIO::Response + + private + + attr_reader ping_url: String + + def connection: () -> untyped + + def send_start: (?rid: String? rid) -> HealthchecksIO::Response + + def make_request: (Symbol method, String path, ?body: String? body, ?rid: String? rid) -> HealthchecksIO::Response + end + end +end diff --git a/sig/r3x/client/healthchecks_io/response.rbs b/sig/r3x/client/healthchecks_io/response.rbs new file mode 100644 index 0000000..29b6d9a --- /dev/null +++ b/sig/r3x/client/healthchecks_io/response.rbs @@ -0,0 +1,27 @@ +module R3x + module Client + class HealthchecksIO + class Response + def initialize: (untyped response) -> void + + def success?: () -> bool + + def status: () -> Integer + + def body: () -> String + + def headers: () -> untyped + + def body_limit: () -> Integer? + + def to_s: () -> String + + def inspect: () -> String + + private + + attr_reader response: untyped + end + end + end +end diff --git a/sig/r3x/client/http.rbs b/sig/r3x/client/http.rbs new file mode 100644 index 0000000..960b5a6 --- /dev/null +++ b/sig/r3x/client/http.rbs @@ -0,0 +1,33 @@ +module R3x + module Client + class Http + def self.with_persistence: (?verify_ssl: bool verify_ssl, ?timeout: Integer timeout) { (R3x::Client::Http) -> untyped } -> untyped + + def self.session_options: (verify_ssl: bool verify_ssl, timeout: Integer timeout) -> Hash[Symbol, untyped] + + def initialize: (?verify_ssl: bool verify_ssl, ?timeout: Integer timeout) -> void + + def get: (String url, ?params: Hash[untyped, untyped] params, ?headers: Hash[untyped, untyped] headers) -> untyped + + def head: (String url, ?params: Hash[untyped, untyped] params, ?headers: Hash[untyped, untyped] headers) -> untyped + + def post: (String url, Hash[untyped, untyped] payload, ?headers: Hash[untyped, untyped] headers) -> untyped + + def download_file: (String url, ?headers: Hash[untyped, untyped] headers) -> R3x::Client::Http::DownloadedFile + + def upload_file: (String url, untyped file, ?file_field: String file_field, ?filename: String? filename, ?content_type: String? content_type, ?params: Hash[untyped, untyped] params, ?headers: Hash[untyped, untyped] headers) -> untyped + + private + + def filename_from_url: (String url, content_type: String? content_type) -> String + + def sniff_content_type: (untyped file_io) -> String? + + def default_filename: (untyped file_io) -> String? + + def rewind_file: (untyped file_io) -> nil + + def restore_file_position: (untyped file_io, ?Integer? position) -> nil + end + end +end diff --git a/sig/r3x/client/http/downloaded_file.rbs b/sig/r3x/client/http/downloaded_file.rbs new file mode 100644 index 0000000..ec20d03 --- /dev/null +++ b/sig/r3x/client/http/downloaded_file.rbs @@ -0,0 +1,16 @@ +module R3x + module Client + class Http + class DownloadedFile + attr_reader body: String + attr_reader content_type: String? + attr_reader filename: String + attr_reader url: String + + def initialize: (body: String body, content_type: String? content_type, filename: String filename, url: String url) -> void + + def to_io: () -> StringIO + end + end + end +end diff --git a/sig/r3x/client/llm.rbs b/sig/r3x/client/llm.rbs new file mode 100644 index 0000000..3c634c4 --- /dev/null +++ b/sig/r3x/client/llm.rbs @@ -0,0 +1,37 @@ +module R3x + module Client + class Llm + MAX_RETRIES: Integer + RETRY_INTERVAL: Float + RETRY_BACKOFF_FACTOR: Integer + DEFAULT_CHAT_OPTIONS: Hash[Symbol, untyped] + CHAT_OPTIONS_BY_PROVIDER: Hash[Symbol, Hash[Symbol, untyped]] + CHAT_OPTIONS_BY_PROVIDER_MUTEX: Mutex + + def self.chat_options_for: (Symbol provider) -> Hash[Symbol, untyped] + + def self.build_chat_options_for: (Symbol provider) -> Hash[Symbol, untyped] + + def initialize: ( + api_key: String api_key, + config_api_key_attr: String config_api_key_attr, + ?max_retries: Integer? max_retries, + ?retry_interval: Numeric? retry_interval, + ?retry_backoff_factor: Numeric? retry_backoff_factor + ) -> void + + def analyze_image: (String image_bytes, prompt: String prompt, model: String model, ?schema: untyped schema) -> untyped + + def message: (model: String model, prompt: String prompt, ?schema: untyped schema) -> untyped + + def classify: (text: String text, model: String model, categories: Hash[String, String] categories, ?include_reason: bool include_reason, ?allow_other: bool allow_other) -> untyped + + private + + attr_reader llm_context: untyped + attr_reader chat_options: Hash[Symbol, untyped] + + def ask_model: (model: String model, prompt: String prompt, schema: untyped schema, ?attachments: untyped attachments) -> untyped + end + end +end diff --git a/sig/r3x/client/llm/classifier.rbs b/sig/r3x/client/llm/classifier.rbs new file mode 100644 index 0000000..42b5657 --- /dev/null +++ b/sig/r3x/client/llm/classifier.rbs @@ -0,0 +1,21 @@ +module R3x + module Client + class Llm + class Classifier + def initialize: (untyped message_method) -> void + + def classify: (text: String text, model: String model, categories: Hash[String, String] categories, ?include_reason: bool include_reason, ?allow_other: bool allow_other) -> untyped + + private + + attr_reader message_method: untyped + + def build_categories: (Hash[String, String] user_categories, allow_other: bool allow_other) -> Hash[String, String] + + def build_schema: (Array[String] category_names, include_reason: bool include_reason) -> untyped + + def build_prompt: (String text, Hash[String, String] categories, include_reason: bool include_reason) -> String + end + end + end +end diff --git a/sig/r3x/client/llm/provider_configuration.rbs b/sig/r3x/client/llm/provider_configuration.rbs new file mode 100644 index 0000000..64a7fd8 --- /dev/null +++ b/sig/r3x/client/llm/provider_configuration.rbs @@ -0,0 +1,16 @@ +module R3x + module Client + class Llm + class ProviderConfiguration + API_KEY_SUFFIX: String + + attr_reader api_key: String + attr_reader config_api_key_attr: String + + def initialize: (api_key: String api_key, config_api_key_attr: String config_api_key_attr) -> void + + def self.resolve: (api_key_env: String api_key_env) -> ProviderConfiguration + end + end + end +end diff --git a/sig/r3x/client/llm/provider_registry.rbs b/sig/r3x/client/llm/provider_registry.rbs new file mode 100644 index 0000000..1dce3b2 --- /dev/null +++ b/sig/r3x/client/llm/provider_registry.rbs @@ -0,0 +1,13 @@ +module R3x + module Client + class Llm + module ProviderRegistry + def self.register!: () -> void + + def self.register_opencode_go!: () -> void + + def self.opencode_go_provider_class: () -> singleton(Class) + end + end + end +end diff --git a/sig/r3x/client/markdownify.rbs b/sig/r3x/client/markdownify.rbs new file mode 100644 index 0000000..14f5471 --- /dev/null +++ b/sig/r3x/client/markdownify.rbs @@ -0,0 +1,23 @@ +module R3x + module Client + class Markdownify + include R3x::Concerns::Logger + + DEFAULT_METHOD: String + + def initialize: (url: String url, ?method: String method, ?retain_images: bool retain_images) -> void + + def convert: () -> Hash[String, untyped] + + private + + attr_reader url: String + attr_reader method: String + attr_reader retain_images: bool + + def dry_run_result: () -> Hash[String, untyped] + + def connection: () -> untyped + end + end +end diff --git a/sig/r3x/client/miniflux.rbs b/sig/r3x/client/miniflux.rbs new file mode 100644 index 0000000..21eddb1 --- /dev/null +++ b/sig/r3x/client/miniflux.rbs @@ -0,0 +1,30 @@ +module R3x + module Client + class Miniflux + DEFAULT_URL_ENV: String + DEFAULT_API_KEY_ENV: String + + DEFAULT_STATUS: String + DEFAULT_LIMIT: Integer + DEFAULT_ORDER: String + DEFAULT_DIRECTION: String + + def initialize: (?url_env: String url_env, ?api_key_env: String api_key_env) -> void + + def entries: (?status: String status, ?limit: Integer limit, ?order: String order, ?direction: String direction, **untyped filters) -> Hash[untyped, untyped] + + def category_entries: (category_id: Integer | String category_id, ?status: String status, ?limit: Integer limit, ?order: String order, ?direction: String direction, **untyped filters) -> Hash[untyped, untyped] + + def mark_category_entries_as_read: (category_id: Integer | String category_id) -> bool + + private + attr_reader base_url: String + attr_reader api_key: String + + def get: (String path, **untyped params) -> Hash[untyped, untyped] + def put: (String path) -> true + def connection: () -> untyped + end + end +end + diff --git a/sig/r3x/client/ocr.rbs b/sig/r3x/client/ocr.rbs new file mode 100644 index 0000000..ac99ad6 --- /dev/null +++ b/sig/r3x/client/ocr.rbs @@ -0,0 +1,27 @@ +module R3x + module Client + class Ocr + ENDPOINT: String + BASE_URL: String + DEFAULT_API_KEY_ENV: String + + MIME_TYPES: Hash[String, String] + + def initialize: (?api_key_env: String api_key_env) -> void + + def parse: (untyped io_or_path, ?language: String? language, ?engine: Integer? engine, ?filetype: String? filetype, ?overlay: bool overlay) -> Ocr::Result + + private + + attr_reader api_key: String + + def connection: () -> untyped + + def detect_mime: (untyped io_or_path) -> String + + def build_params: (untyped io_or_path, String mime_type, language: String? language, engine: Integer? engine, overlay: bool overlay) -> Hash[Symbol, untyped] + + def binary_string?: (untyped value) -> bool + end + end +end diff --git a/sig/r3x/client/ocr/result.rbs b/sig/r3x/client/ocr/result.rbs new file mode 100644 index 0000000..5c9c30d --- /dev/null +++ b/sig/r3x/client/ocr/result.rbs @@ -0,0 +1,29 @@ +module R3x + module Client + class Ocr + class Result + include Enumerable[untyped] + + def initialize: (untyped body) -> void + + def text: () -> String + + def success?: () -> bool + + def partial?: () -> bool + + def exit_code: () -> Integer + + def processing_time_ms: () -> Integer? + + def each: () { (untyped) -> untyped } -> untyped + | () -> untyped + + private + + attr_reader body: untyped + attr_reader pages: Array[untyped] + end + end + end +end diff --git a/sig/r3x/client/prometheus.rbs b/sig/r3x/client/prometheus.rbs new file mode 100644 index 0000000..90275ce --- /dev/null +++ b/sig/r3x/client/prometheus.rbs @@ -0,0 +1,15 @@ +module R3x + module Client + class Prometheus + DEFAULT_URL_ENV: String + + def initialize: (?url_env: String url_env) -> void + + def query: (String promql) -> Prometheus::Result + + private + + attr_reader base_url: String + end + end +end diff --git a/sig/r3x/client/prometheus/result.rbs b/sig/r3x/client/prometheus/result.rbs new file mode 100644 index 0000000..9920562 --- /dev/null +++ b/sig/r3x/client/prometheus/result.rbs @@ -0,0 +1,20 @@ +module R3x + module Client + class Prometheus + class Result + include Enumerable[untyped] + + attr_reader result_type: String? + + def initialize: (untyped data) -> void + + def each: () { (untyped) -> untyped } -> untyped + | () -> untyped + + private + + attr_reader series: Array[untyped] + end + end + end +end diff --git a/sig/r3x/client/victoria_logs.rbs b/sig/r3x/client/victoria_logs.rbs new file mode 100644 index 0000000..e6e1d22 --- /dev/null +++ b/sig/r3x/client/victoria_logs.rbs @@ -0,0 +1,20 @@ +module R3x + module Client + class VictoriaLogs + DEFAULT_URL_ENV: String + DEFAULT_TIMEOUT: String + + def initialize: (?url_env: String url_env) -> void + def query: (query: String query, ?start_at: untyped start_at, ?end_at: untyped end_at, ?limit: Integer limit, ?timeout: String timeout) -> Array[Hash[String, untyped]] + + private + attr_reader client: untyped + attr_reader base_url: String + + def query_params: (query: String query, start_at: untyped start_at, end_at: untyped end_at, limit: Integer limit, timeout: String timeout) -> Hash[String, untyped] + def sort_query: (String query) -> String + def format_time: (untyped value) -> String? + def parse_json_lines: (untyped body) -> Array[Hash[String, untyped]] + end + end +end diff --git a/sig/r3x/external_stubs.rbs b/sig/r3x/external_stubs.rbs new file mode 100644 index 0000000..71afbd6 --- /dev/null +++ b/sig/r3x/external_stubs.rbs @@ -0,0 +1,144 @@ +module HTTPX + def self.plugin: (*untyped plugins, **untyped keywords) -> untyped + def self.with: (?untyped options, **untyped keywords) -> untyped + def self.get: (String url, ?params: Hash[untyped, untyped] params, **untyped keywords) -> untyped + def self.post: (String url, ?json: untyped json, ?form: untyped form, ?body: untyped body, ?params: Hash[untyped, untyped] params, **untyped keywords) -> untyped + def self.head: (String url, **untyped keywords) -> untyped +end + + +module OpenSSL + module SSL + VERIFY_NONE: untyped + end +end + +module Rack + module Mime + MIME_TYPES: Hash[String, String] + end +end + +module RSS + module Parser + def self.parse: (String source, bool validate) -> untyped + end +end + +module R3x + module Concerns + module Logger + def logger: () -> untyped + end + end + + module Policy + def self.dry_run_for: (Symbol key, ?untyped dry_run) -> bool + end + + module Env + def self.secure_fetch: (String name, prefix: String | Regexp prefix) -> String + def self.fetch!: (String name) -> String + end + + module Client + module GoogleAuth + def self.require_gmail!: () -> void + def self.require_sheets!: () -> void + def self.from_env: (project: String project, scope: String scope) -> untyped + def self.resolve_scope: (String alias_name) -> String + end + end + + module GemLoader + def self.require: (String feature) -> bool + end +end + + +class Tempfile + def self.new: (Array[String?] basename) -> Tempfile + + def binmode: () -> Tempfile + + def write: (untyped content) -> Integer + + def rewind: () -> Integer + + def close: () -> nil + + def unlink: () -> nil +end + +module URI + def self.parse: (String uri) -> untyped +end + +module Marcel + module MimeType + def self.for: (untyped io) -> String? + end +end + +module MultiJSON + def self.parse: (String string, ?untyped options) -> untyped + def self.generate: (untyped object, ?untyped options) -> String +end + +module Google + module Apis + module GmailV1 + class Message + def initialize: (?raw: String raw) -> void + end + + class GmailService + def authorization=: (untyped authorization) -> untyped + end + end + + module SheetsV4 + class SheetsService + def authorization=: (untyped authorization) -> untyped + def get_spreadsheet_values: (String spreadsheet_id, String range) -> untyped + end + end + end +end + +module Mail + def self.new: () -> untyped + + class Part + def self.new: () { (Mail::Part) -> untyped } -> Mail::Part + def body: () -> untyped + def body=: (String? body) -> untyped + end +end + +module RubyLLM + module Provider + def self.providers: () -> Hash[Symbol, untyped] + + def self.register: (Symbol name, untyped provider) -> void + end + + module Providers + class OpenAI + end + end + + def self.context: () { (untyped config) -> untyped } -> untyped +end + +module SecureRandom + def self.uuid: () -> String +end + +module Base64 + def self.strict_encode64: (String data) -> String +end + +class String + def blank?: () -> bool +end diff --git a/sig/r3x/workflow/base.rbs b/sig/r3x/workflow/base.rbs new file mode 100644 index 0000000..009b40f --- /dev/null +++ b/sig/r3x/workflow/base.rbs @@ -0,0 +1,7 @@ +module R3x + module Workflow + class Base + def self.workflow_key: () -> String + end + end +end diff --git a/sig/r3x/workflow/context.rbs b/sig/r3x/workflow/context.rbs new file mode 100644 index 0000000..c683ca1 --- /dev/null +++ b/sig/r3x/workflow/context.rbs @@ -0,0 +1,16 @@ +module R3x + module Workflow + class Context + attr_reader trigger: untyped + attr_reader execution: R3x::Workflow::Execution + attr_reader workflow_class: singleton(R3x::Workflow::Base)? + attr_reader workflow_key: String + + def initialize: (trigger: untyped trigger, workflow_key: String workflow_key, ?workflow_class: singleton(R3x::Workflow::Base)? workflow_class) -> void + + def client: () -> singleton(R3x::Workflow::Context::Client) + + def durable_set: (?Symbol | String name, ?ttl: untyped ttl) -> R3x::Workflow::DurableSet + end + end +end diff --git a/sig/r3x/workflow/context/client.rbs b/sig/r3x/workflow/context/client.rbs new file mode 100644 index 0000000..6f45ebb --- /dev/null +++ b/sig/r3x/workflow/context/client.rbs @@ -0,0 +1,63 @@ +module R3x + module Workflow + class Context + module Client + def http: (?verify_ssl: bool verify_ssl, ?timeout: Integer timeout) -> R3x::Client::Http + + def persistent_http: (?verify_ssl: bool verify_ssl, ?timeout: Integer timeout) { (R3x::Client::Http) -> untyped } -> untyped + + def prometheus: (?url_env: String url_env) -> R3x::Client::Prometheus + + def healthchecks_io: (String check_uuid, ?ping_endpoint: String? ping_endpoint, ?ping_endpoint_env: String ping_endpoint_env) -> R3x::Client::HealthchecksIO + + def apify: (?api_key_env: String api_key_env) -> R3x::Client::Apify + + def llm: (api_key_env: String api_key_env, ?max_retries: Integer? max_retries, ?retry_interval: Numeric? retry_interval, ?retry_backoff_factor: Numeric? retry_backoff_factor) -> R3x::Client::Llm + + def google_sheets: (spreadsheet_id: String spreadsheet_id, project: String project) -> R3x::Client::GoogleSheets + + def gmail: (project: String project) -> R3x::Client::Google::Gmail + + def ocr: (?api_key_env: String api_key_env) -> R3x::Client::Ocr + + def rss: (String url, ?timeout: Integer timeout) -> untyped + + def google_translate: (project: String project) -> R3x::Client::Google::Translate + + def miniflux: (?url_env: String url_env, ?api_key_env: String api_key_env) -> R3x::Client::Miniflux + + def discord: (?webhook_url_env: String webhook_url_env) -> R3x::Client::Discord + + def markdownify: (url: String url, ?method: String method, ?retain_images: bool retain_images) -> String + + def self.http: (?verify_ssl: bool verify_ssl, ?timeout: Integer timeout) -> R3x::Client::Http + + def self.persistent_http: (?verify_ssl: bool verify_ssl, ?timeout: Integer timeout) { (R3x::Client::Http) -> untyped } -> untyped + + def self.prometheus: (?url_env: String url_env) -> R3x::Client::Prometheus + + def self.healthchecks_io: (String check_uuid, ?ping_endpoint: String? ping_endpoint, ?ping_endpoint_env: String ping_endpoint_env) -> R3x::Client::HealthchecksIO + + def self.apify: (?api_key_env: String api_key_env) -> R3x::Client::Apify + + def self.llm: (api_key_env: String api_key_env, ?max_retries: Integer? max_retries, ?retry_interval: Numeric? retry_interval, ?retry_backoff_factor: Numeric? retry_backoff_factor) -> R3x::Client::Llm + + def self.google_sheets: (spreadsheet_id: String spreadsheet_id, project: String project) -> R3x::Client::GoogleSheets + + def self.gmail: (project: String project) -> R3x::Client::Google::Gmail + + def self.ocr: (?api_key_env: String api_key_env) -> R3x::Client::Ocr + + def self.rss: (String url, ?timeout: Integer timeout) -> untyped + + def self.google_translate: (project: String project) -> R3x::Client::Google::Translate + + def self.miniflux: (?url_env: String url_env, ?api_key_env: String api_key_env) -> R3x::Client::Miniflux + + def self.discord: (?webhook_url_env: String webhook_url_env) -> R3x::Client::Discord + + def self.markdownify: (url: String url, ?method: String method, ?retain_images: bool retain_images) -> String + end + end + end +end diff --git a/sig/r3x/workflow/durable_set.rbs b/sig/r3x/workflow/durable_set.rbs new file mode 100644 index 0000000..eb2e2e7 --- /dev/null +++ b/sig/r3x/workflow/durable_set.rbs @@ -0,0 +1,17 @@ +module R3x + module Workflow + class DurableSet + DEFAULT_TTL: untyped + + def initialize: (workflow_key: String workflow_key, ?name: Symbol | String name, ?ttl: untyped ttl) -> void + + def include?: (untyped member) -> bool + + def add: (untyped member, ?ttl: untyped ttl) -> untyped + + def add?: (untyped member, ?ttl: untyped ttl) -> untyped + + def delete: (untyped member) -> untyped + end + end +end diff --git a/sig/r3x/workflow/execution.rbs b/sig/r3x/workflow/execution.rbs new file mode 100644 index 0000000..f990410 --- /dev/null +++ b/sig/r3x/workflow/execution.rbs @@ -0,0 +1,13 @@ +module R3x + module Workflow + class Execution + attr_reader workflow_key: String + + def initialize: (workflow_key: String workflow_key) -> void + + def previous_run_at: () -> untyped + + def first_run?: () -> bool + end + end +end