ruact 0.0.4 → 0.0.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/.github/workflows/ci.yml +54 -2
- data/.rubocop_todo.yml +3 -115
- data/CHANGELOG.md +68 -17
- data/bench/server_functions_dispatch_bench.rb +109 -142
- data/bench/server_functions_dispatch_bench.results.md +29 -0
- data/docs/internal/decisions/server-functions-api.md +402 -0
- data/lib/generators/ruact/install/install_generator.rb +310 -25
- data/lib/generators/ruact/install/templates/Procfile.dev.tt +2 -0
- data/lib/generators/ruact/install/templates/dev.tt +16 -0
- data/lib/generators/ruact/install/templates/package.json.tt +17 -0
- data/lib/generators/ruact/install/templates/vite.config.js.tt +3 -1
- data/lib/generators/ruact/scaffold/scaffold_attribute.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_form_helpers.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_generator.rb +491 -0
- data/lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb +229 -0
- data/lib/generators/ruact/scaffold/templates/components/DeleteDialog.tsx.tt +104 -0
- data/lib/generators/ruact/scaffold/templates/components/Form.tsx.tt +212 -0
- data/lib/generators/ruact/scaffold/templates/components/List.tsx.tt +338 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/DeleteDialog.tsx.tt +92 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/Form.tsx.tt +174 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt +253 -0
- data/lib/generators/ruact/scaffold/templates/controller.rb.tt +130 -0
- data/lib/generators/ruact/scaffold/templates/queries/application_query.rb.tt +13 -0
- data/lib/generators/ruact/scaffold/templates/queries/query.rb.tt +59 -0
- data/lib/generators/ruact/scaffold/templates/request_spec.rb.tt +77 -0
- data/lib/generators/ruact/scaffold/templates/views/edit.html.erb.tt +7 -0
- data/lib/generators/ruact/scaffold/templates/views/index.html.erb.tt +6 -0
- data/lib/generators/ruact/scaffold/templates/views/new.html.erb.tt +5 -0
- data/lib/generators/ruact/scaffold/templates/views/show.html.erb.tt +16 -0
- data/lib/ruact/client_manifest.rb +37 -36
- data/lib/ruact/component_contract.rb +115 -0
- data/lib/ruact/configuration.rb +80 -28
- data/lib/ruact/controller.rb +69 -421
- data/lib/ruact/doctor.rb +133 -4
- data/lib/ruact/erb_preprocessor.rb +65 -12
- data/lib/ruact/erb_preprocessor_hook.rb +4 -1
- data/lib/ruact/errors.rb +30 -44
- data/lib/ruact/html_converter.rb +22 -1
- data/lib/ruact/railtie.rb +56 -200
- data/lib/ruact/server.rb +28 -6
- data/lib/ruact/server_functions/codegen.rb +49 -188
- data/lib/ruact/server_functions/codegen_v2.rb +23 -6
- data/lib/ruact/server_functions/codegen_v2_query_params.rb +140 -0
- data/lib/ruact/server_functions/error_payload.rb +1 -1
- data/lib/ruact/server_functions/error_rendering.rb +22 -29
- data/lib/ruact/server_functions/name_bridge.rb +14 -16
- data/lib/ruact/server_functions/query_source.rb +35 -8
- data/lib/ruact/server_functions/route_source.rb +3 -4
- data/lib/ruact/server_functions/snapshot.rb +12 -139
- data/lib/ruact/server_functions/validation_errors.rb +70 -0
- data/lib/ruact/server_functions.rb +21 -25
- data/lib/ruact/signed_references.rb +162 -0
- data/lib/ruact/string_distance.rb +72 -0
- data/lib/ruact/validation_errors_collector.rb +139 -0
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper.rb +102 -0
- data/lib/ruact.rb +19 -19
- data/lib/tasks/ruact.rake +10 -53
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +3 -0
- data/spec/ruact/client_manifest_spec.rb +36 -0
- data/spec/ruact/component_contract_spec.rb +119 -0
- data/spec/ruact/configuration_spec.rb +51 -34
- data/spec/ruact/controller_request_spec.rb +264 -0
- data/spec/ruact/controller_spec.rb +63 -326
- data/spec/ruact/doctor_spec.rb +201 -0
- data/spec/ruact/erb_preprocessor_hook_spec.rb +4 -1
- data/spec/ruact/erb_preprocessor_spec.rb +127 -0
- data/spec/ruact/errors_spec.rb +0 -45
- data/spec/ruact/html_converter_spec.rb +50 -0
- data/spec/ruact/install_generator_spec.rb +591 -4
- data/spec/ruact/query_request_spec.rb +109 -1
- data/spec/ruact/scaffold_generator_spec.rb +1835 -0
- data/spec/ruact/server_bucket_request_spec.rb +142 -0
- data/spec/ruact/server_function_name_spec.rb +1 -1
- data/spec/ruact/server_functions/codegen_spec.rb +158 -269
- data/spec/ruact/server_functions/name_bridge_spec.rb +9 -9
- data/spec/ruact/server_functions/query_source_spec.rb +51 -0
- data/spec/ruact/server_functions/railtie_integration_spec.rb +71 -268
- data/spec/ruact/server_functions/rake_spec.rb +29 -29
- data/spec/ruact/server_functions/snapshot_spec.rb +53 -213
- data/spec/ruact/server_rescue_request_spec.rb +6 -6
- data/spec/ruact/server_spec.rb +8 -9
- data/spec/ruact/signed_references_spec.rb +164 -0
- data/spec/ruact/string_distance_spec.rb +38 -0
- data/spec/ruact/validation_errors_spec.rb +116 -0
- data/spec/ruact/view_helper_spec.rb +79 -0
- data/spec/spec_helper.rb +0 -5
- data/vendor/javascript/ruact-server-functions-runtime/index.d.ts +44 -47
- data/vendor/javascript/ruact-server-functions-runtime/index.js +151 -107
- data/vendor/javascript/ruact-server-functions-runtime/index.test.mjs +72 -75
- data/vendor/javascript/ruact-server-functions-runtime/package.json +2 -2
- data/vendor/javascript/ruact-server-functions-runtime/usequery.test.mjs +187 -0
- data/vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs +96 -0
- data/vendor/javascript/vite-plugin-ruact/index.js +353 -7
- data/vendor/javascript/vite-plugin-ruact/manifest-contract.test.mjs +182 -0
- data/vendor/javascript/vite-plugin-ruact/package-lock.json +16 -0
- data/vendor/javascript/vite-plugin-ruact/package.json +3 -1
- data/vendor/javascript/vite-plugin-ruact/registry.test.mjs +199 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx +68 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +235 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +473 -0
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.mjs +114 -139
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.test.mjs +199 -262
- data/vendor/javascript/vite-plugin-ruact/tsconfig.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json +20 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts +23 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx +90 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx +238 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx +339 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostDeleteDialog.tsx +85 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostForm.tsx +216 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx +269 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts +78 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts +166 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/typed-query.test-d.ts +48 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/usequery.test-d.ts +26 -0
- metadata +55 -15
- data/lib/generators/ruact/install/templates/application.jsx.tt +0 -51
- data/lib/ruact/server_action.rb +0 -131
- data/lib/ruact/server_functions/endpoint_controller.rb +0 -237
- data/lib/ruact/server_functions/registry.rb +0 -148
- data/lib/ruact/server_functions/registry_entry.rb +0 -26
- data/lib/ruact/server_functions/standalone_context.rb +0 -103
- data/lib/ruact/server_functions/standalone_dispatcher.rb +0 -178
- data/spec/ruact/server_functions/csrf_request_spec.rb +0 -380
- data/spec/ruact/server_functions/dispatch_request_spec.rb +0 -819
- data/spec/ruact/server_functions/registry_spec.rb +0 -199
- data/spec/ruact/server_functions/standalone_action_spec.rb +0 -224
- data/spec/ruact/server_functions/standalone_context_spec.rb +0 -142
- data/spec/ruact/server_functions/standalone_dispatcher_spec.rb +0 -273
|
@@ -22,6 +22,10 @@ require "action_view/railtie"
|
|
|
22
22
|
|
|
23
23
|
require "spec_helper"
|
|
24
24
|
require "rack/test"
|
|
25
|
+
require "global_id"
|
|
26
|
+
require "active_support/core_ext/integer/time"
|
|
27
|
+
require "active_support/core_ext/numeric/time"
|
|
28
|
+
require "active_support/message_verifier"
|
|
25
29
|
|
|
26
30
|
require "ruact/controller"
|
|
27
31
|
require "ruact/server"
|
|
@@ -183,6 +187,41 @@ module QueryRequestSpecSupport # rubocop:disable Style/OneClassPerFile
|
|
|
183
187
|
{ "opt" => opt }
|
|
184
188
|
end
|
|
185
189
|
end
|
|
190
|
+
|
|
191
|
+
# Story 13.2 (FR96) — a GlobalID-locatable record used to exercise the signed
|
|
192
|
+
# reference round-trip through the real host request cycle.
|
|
193
|
+
class RefPost
|
|
194
|
+
include GlobalID::Identification
|
|
195
|
+
|
|
196
|
+
attr_reader :id
|
|
197
|
+
|
|
198
|
+
def initialize(id)
|
|
199
|
+
@id = id.to_s
|
|
200
|
+
self.class.store[@id] = self
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def self.store
|
|
204
|
+
@store ||= {}
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def self.find(id)
|
|
208
|
+
store.fetch(id.to_s)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Story 13.2 (FR96) — a public query (skips auth) that resolves a signed
|
|
213
|
+
# reference handed back from the client. A tampered/expired/wrong-purpose
|
|
214
|
+
# token raises Ruact::InvalidSignedGlobalIDError, which the Story 8.4
|
|
215
|
+
# structured-error chain renders as a clean 400 (no ActiveRecord::RecordNotFound
|
|
216
|
+
# leak, no raw-id trust).
|
|
217
|
+
class SignedRefQuery < ApplicationQuery
|
|
218
|
+
ruact_skip_before_action :require_login
|
|
219
|
+
|
|
220
|
+
def resolve_ref(token:)
|
|
221
|
+
record = Ruact.locate_signed(token, for: :spec_ref)
|
|
222
|
+
{ "id" => record.id }
|
|
223
|
+
end
|
|
224
|
+
end
|
|
186
225
|
end
|
|
187
226
|
|
|
188
227
|
if defined?(ControllerRequestSpecSupport) &&
|
|
@@ -191,7 +230,8 @@ if defined?(ControllerRequestSpecSupport) &&
|
|
|
191
230
|
ControllerRequestSpecSupport.app_class.routes.append do
|
|
192
231
|
ruact_queries QueryRequestSpecSupport::CatalogQuery,
|
|
193
232
|
QueryRequestSpecSupport::PublicCatalogQuery,
|
|
194
|
-
QueryRequestSpecSupport::SearchQuery
|
|
233
|
+
QueryRequestSpecSupport::SearchQuery,
|
|
234
|
+
QueryRequestSpecSupport::SignedRefQuery
|
|
195
235
|
end
|
|
196
236
|
end
|
|
197
237
|
|
|
@@ -596,3 +636,71 @@ RSpec.describe "Story 9.5: FR88 query kwargs sanitization", :story_9_5 do
|
|
|
596
636
|
end
|
|
597
637
|
end
|
|
598
638
|
end
|
|
639
|
+
|
|
640
|
+
# Story 13.2 (FR96) — SignedGlobalID references resolved through the REAL host
|
|
641
|
+
# request cycle: a valid token round-trips; a tampered token surfaces as a clean
|
|
642
|
+
# 400 via the Story 8.4 structured-error chain (no ActiveRecord::RecordNotFound
|
|
643
|
+
# leak, no raw-id trust).
|
|
644
|
+
RSpec.describe "Story 13.2: SignedGlobalID record references (FR96)", :story_13_2 do
|
|
645
|
+
include Rack::Test::Methods
|
|
646
|
+
|
|
647
|
+
let(:app_class) { ControllerRequestSpecSupport.app_class }
|
|
648
|
+
let(:app) { app_class.instance }
|
|
649
|
+
|
|
650
|
+
before do
|
|
651
|
+
Rails.logger = Logger.new(IO::NULL)
|
|
652
|
+
ControllerRequestSpecSupport.boot!
|
|
653
|
+
# globalid signing context for the booted app (the 7.9 app sets
|
|
654
|
+
# secret_key_base but does not load the globalid railtie).
|
|
655
|
+
GlobalID.app = "ruact-test"
|
|
656
|
+
SignedGlobalID.verifier = ActiveSupport::MessageVerifier.new("a" * 64)
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def sign(record, purpose: :spec_ref, expires_in: 1.hour)
|
|
660
|
+
Ruact.signed_global_id(record, for: purpose, expires_in: expires_in)
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
it "round-trips a valid token through the host chain (no auth needed — public query)" do
|
|
664
|
+
record = QueryRequestSpecSupport::RefPost.new("42")
|
|
665
|
+
get "/q/resolveRef", { "token" => sign(record) }
|
|
666
|
+
expect(last_response.status).to(eq(200), "body=#{last_response.body[0, 300]}")
|
|
667
|
+
expect(JSON.parse(last_response.body)).to eq("id" => "42")
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
it "rejects a tampered token as a clean 400 structured error (no RecordNotFound leak)" do
|
|
671
|
+
record = QueryRequestSpecSupport::RefPost.new("99")
|
|
672
|
+
get "/q/resolveRef", { "token" => "#{sign(record)}tamper" }
|
|
673
|
+
|
|
674
|
+
expect(last_response.status).to eq(400)
|
|
675
|
+
body = JSON.parse(last_response.body)
|
|
676
|
+
expect(body.fetch("_ruact_server_action_error")).to be(true)
|
|
677
|
+
expect(body.fetch("action_name")).to eq("resolve_ref")
|
|
678
|
+
expect(body.fetch("error_class")).to eq("Ruact::InvalidSignedGlobalIDError")
|
|
679
|
+
expect(last_response.body).not_to include("RecordNotFound")
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
it "rejects a wrong-purpose token as a clean 400" do
|
|
683
|
+
record = QueryRequestSpecSupport::RefPost.new("7")
|
|
684
|
+
get "/q/resolveRef", { "token" => sign(record, purpose: :other_purpose) }
|
|
685
|
+
expect(last_response.status).to eq(400)
|
|
686
|
+
expect(JSON.parse(last_response.body).fetch("error_class"))
|
|
687
|
+
.to eq("Ruact::InvalidSignedGlobalIDError")
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
it "does not leak dev-only error fields in production payload mode" do
|
|
691
|
+
record = QueryRequestSpecSupport::RefPost.new("5")
|
|
692
|
+
Ruact.configure { |c| c.dev_error_payload_enabled = false }
|
|
693
|
+
get "/q/resolveRef", { "token" => "#{sign(record)}tamper" }
|
|
694
|
+
expect(last_response.status).to eq(400)
|
|
695
|
+
body = JSON.parse(last_response.body)
|
|
696
|
+
# production payload is exactly the four baseline keys — no split backtrace,
|
|
697
|
+
# suggestion, or validation/upload blocks (the real dev-only field names).
|
|
698
|
+
expect(body.keys).to contain_exactly(
|
|
699
|
+
"_ruact_server_action_error", "action_name", "error_class", "message"
|
|
700
|
+
)
|
|
701
|
+
expect(body.keys).not_to include("app_frames", "gem_frames", "suggestion")
|
|
702
|
+
ensure
|
|
703
|
+
Ruact.instance_variable_set(:@config, nil)
|
|
704
|
+
Ruact.instance_variable_set(:@configured_at_least_once, false)
|
|
705
|
+
end
|
|
706
|
+
end
|