phlex-reactive 0.11.4 → 0.11.6
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/CHANGELOG.md +89 -0
- data/README.md +197 -5
- data/app/controllers/phlex/reactive/actions_controller.rb +43 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +305 -4
- data/app/javascript/phlex/reactive/reactive_controller.min.js +2 -2
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +3 -3
- data/lib/generators/phlex/reactive/install/templates/phlex_reactive.rb.erb +20 -0
- data/lib/phlex/reactive/apm/adapter.rb +43 -0
- data/lib/phlex/reactive/apm/appsignal.rb +52 -0
- data/lib/phlex/reactive/apm/datadog.rb +44 -0
- data/lib/phlex/reactive/apm/sentry.rb +40 -0
- data/lib/phlex/reactive/apm/subscriber.rb +61 -0
- data/lib/phlex/reactive/apm.rb +91 -0
- data/lib/phlex/reactive/component/dsl.rb +27 -4
- data/lib/phlex/reactive/component/helpers.rb +199 -8
- data/lib/phlex/reactive/engine.rb +6 -0
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +89 -1
- metadata +7 -1
data/lib/phlex/reactive.rb
CHANGED
|
@@ -173,6 +173,18 @@ module Phlex
|
|
|
173
173
|
false
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
+
# Turnkey APM integration (issue #207). Set to a Symbol (:appsignal,
|
|
177
|
+
# :sentry, :datadog), a custom adapter object (responding to
|
|
178
|
+
# record_action/record_error), or nil (the default — off). A Symbol
|
|
179
|
+
# resolves to a built-in adapter LAZILY, at engine attach time, so load
|
|
180
|
+
# order doesn't matter; a set-but-undetectable SDK logs ONE warning at boot
|
|
181
|
+
# and no-ops (the pgbus optionality invariant applied to APMs — no vendor
|
|
182
|
+
# SDK is ever a hard dependency). When set and available, each reactive
|
|
183
|
+
# action is named `Component#action` in the APM (not one blurry
|
|
184
|
+
# ActionsController#create), and an action-body error is reported to the
|
|
185
|
+
# tracker with component/action tags. Default nil.
|
|
186
|
+
attr_accessor :apm
|
|
187
|
+
|
|
176
188
|
# Client debug mode (issue #108): the "devtools-lite" lens on the reactive
|
|
177
189
|
# round trip. When true, reactive_attrs (and so reactive_root) stamps
|
|
178
190
|
# data-reactive-debug="true" on the root, and the generic controller
|
|
@@ -406,6 +418,72 @@ module Phlex
|
|
|
406
418
|
@around_actions = []
|
|
407
419
|
end
|
|
408
420
|
|
|
421
|
+
# Register a block called when a reactive action body raises a
|
|
422
|
+
# previously-uncaught error (issue #207) — the DIY escape hatch for a tracker
|
|
423
|
+
# the gem ships no adapter for, usable WITHOUT choosing an `apm` Symbol. The
|
|
424
|
+
# block receives (error, context) where context is the name-only event payload
|
|
425
|
+
# ({ component:, action:, outcome: :error }). It runs INSIDE the endpoint's
|
|
426
|
+
# error handling, just before the exception is re-raised — so Rails' own error
|
|
427
|
+
# reporting still fires afterward. Register in an initializer.
|
|
428
|
+
#
|
|
429
|
+
# Phlex::Reactive.on_action_error do |error, ctx|
|
|
430
|
+
# Honeybadger.notify(error, context: { component: ctx[:component], action: ctx[:action] })
|
|
431
|
+
# end
|
|
432
|
+
def on_action_error(&block)
|
|
433
|
+
raise ::ArgumentError, "Phlex::Reactive.on_action_error requires a block" unless block
|
|
434
|
+
|
|
435
|
+
on_action_error_hooks << block
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# The registered on_action_error hooks, oldest first.
|
|
439
|
+
def on_action_error_hooks
|
|
440
|
+
@on_action_error_hooks ||= []
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# Drop all registered on_action_error hooks. Test isolation; never in production.
|
|
444
|
+
def reset_on_action_error!
|
|
445
|
+
@on_action_error_hooks = []
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# The name-only keys forwarded to error reporters (issue #207). Deliberately
|
|
449
|
+
# NOT the whole event hash: ActiveSupport::Notifications mutates the SAME hash
|
|
450
|
+
# during error unwinding, adding :exception / :exception_object — a reporter
|
|
451
|
+
# that RETAINS the hash would later observe those, breaking the name-only
|
|
452
|
+
# contract. report_error forwards a fresh slice of just these keys.
|
|
453
|
+
ERROR_CONTEXT_KEYS = %i[component action outcome].freeze
|
|
454
|
+
|
|
455
|
+
# Report a previously-uncaught action-body error to the resolved APM adapter
|
|
456
|
+
# AND every registered on_action_error hook (issue #207). Called by the
|
|
457
|
+
# endpoint's error seam just before it re-raises. Each reporter is wrapped in
|
|
458
|
+
# its own rescue so a broken reporter can NEVER turn one 500 into a different
|
|
459
|
+
# 500 (or swallow the original — the endpoint re-raises regardless). `context`
|
|
460
|
+
# is the mutable event payload; we forward a NAME-ONLY SNAPSHOT (a fresh Hash
|
|
461
|
+
# of ERROR_CONTEXT_KEYS) so a reporter that retains it never picks up the
|
|
462
|
+
# :exception keys ASN adds to the live hash afterward. Best-effort and
|
|
463
|
+
# side-effect only: the return value is ignored.
|
|
464
|
+
def report_error(error, context)
|
|
465
|
+
snapshot = context.slice(*ERROR_CONTEXT_KEYS)
|
|
466
|
+
adapter = @resolved_apm_adapter
|
|
467
|
+
# Each reporter runs through safely_report on its OWN — one broken reporter
|
|
468
|
+
# (a raising adapter or hook) never prevents the others, and never turns one
|
|
469
|
+
# 500 into a different 500. safely_report takes the reporter as a block arg,
|
|
470
|
+
# so there's no nested-block ambiguity.
|
|
471
|
+
safely_report { adapter.record_error(error, snapshot) } if adapter
|
|
472
|
+
on_action_error_hooks.each { report_hook(it, error, snapshot) }
|
|
473
|
+
nil
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# Run one on_action_error hook through safely_report. Extracted so the
|
|
477
|
+
# per-hook isolation reads as one call (no nested reporter block in the loop).
|
|
478
|
+
def report_hook(hook, error, context)
|
|
479
|
+
safely_report { hook.call(error, context) }
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# The APM adapter resolved at engine attach time (issue #207), or nil. Held so
|
|
483
|
+
# report_error can reach record_error without re-running detection per request.
|
|
484
|
+
# Set by APM.attach!; nil when no APM is configured or the SDK was absent.
|
|
485
|
+
attr_accessor :resolved_apm_adapter
|
|
486
|
+
|
|
409
487
|
def verifier
|
|
410
488
|
@verifier ||= default_verifier
|
|
411
489
|
end
|
|
@@ -918,6 +996,16 @@ module Phlex
|
|
|
918
996
|
::Rails.logger if defined?(::Rails) && ::Rails.respond_to?(:logger)
|
|
919
997
|
end
|
|
920
998
|
|
|
999
|
+
# Run a reporter (APM adapter or on_action_error hook) so a raise inside it
|
|
1000
|
+
# NEVER escapes report_error (issue #207) — the endpoint must re-raise the
|
|
1001
|
+
# ORIGINAL action-body error, not a reporter's failure. A broken reporter is
|
|
1002
|
+
# warn-logged so it's diagnosable, then swallowed.
|
|
1003
|
+
def safely_report
|
|
1004
|
+
yield
|
|
1005
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
|
1006
|
+
default_logger&.warn("[phlex-reactive] apm/on_action_error reporter raised: #{e.class}: #{e.message}")
|
|
1007
|
+
end
|
|
1008
|
+
|
|
921
1009
|
# Walk the upgrader chain from `version` up to TOKEN_VERSION, applying each
|
|
922
1010
|
# registered upgrader in turn (issue #111). A gap in the chain (a version
|
|
923
1011
|
# bumped with no shape change, so no upgrader registered) is a no-op step —
|
|
@@ -961,7 +1049,7 @@ lib = File.expand_path("..", __dir__)
|
|
|
961
1049
|
loader.push_dir(lib)
|
|
962
1050
|
# js.rb defines JS and component/dsl.rb defines Component::DSL (acronyms), not
|
|
963
1051
|
# the default-inflected `Js`/`Dsl`. mcp.rb defines MCP (issue #168).
|
|
964
|
-
loader.inflector.inflect("js" => "JS", "dsl" => "DSL", "mcp" => "MCP")
|
|
1052
|
+
loader.inflector.inflect("js" => "JS", "dsl" => "DSL", "mcp" => "MCP", "apm" => "APM")
|
|
965
1053
|
# The gem-name shim (`require "phlex-reactive"`) is a plain require, not a
|
|
966
1054
|
# managed file.
|
|
967
1055
|
loader.ignore("#{lib}/phlex-reactive.rb")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phlex-reactive
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.11.
|
|
4
|
+
version: 0.11.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -143,6 +143,12 @@ files:
|
|
|
143
143
|
- lib/generators/phlex/reactive/install/templates/phlex_reactive.rb.erb
|
|
144
144
|
- lib/phlex-reactive.rb
|
|
145
145
|
- lib/phlex/reactive.rb
|
|
146
|
+
- lib/phlex/reactive/apm.rb
|
|
147
|
+
- lib/phlex/reactive/apm/adapter.rb
|
|
148
|
+
- lib/phlex/reactive/apm/appsignal.rb
|
|
149
|
+
- lib/phlex/reactive/apm/datadog.rb
|
|
150
|
+
- lib/phlex/reactive/apm/sentry.rb
|
|
151
|
+
- lib/phlex/reactive/apm/subscriber.rb
|
|
146
152
|
- lib/phlex/reactive/authorization.rb
|
|
147
153
|
- lib/phlex/reactive/claude/skills/phlex-reactive-debugging/SKILL.md
|
|
148
154
|
- lib/phlex/reactive/client_bindings.rb
|