ruact 0.0.11 → 0.0.12

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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +14 -4
  5. data/RELEASING.md +125 -139
  6. data/lib/ruact/packaging.rb +68 -0
  7. data/lib/ruact/version.rb +1 -1
  8. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  9. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  10. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  11. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  12. metadata +6 -107
  13. data/.codecov.yml +0 -31
  14. data/.github/workflows/ci.yml +0 -284
  15. data/.github/workflows/server-functions-bench.yml +0 -54
  16. data/.rubocop.yml +0 -107
  17. data/.rubocop_todo.yml +0 -63
  18. data/Rakefile +0 -10
  19. data/bench/server_functions_dispatch_bench.rb +0 -276
  20. data/bench/server_functions_dispatch_bench.results.md +0 -150
  21. data/docs/internal/README.md +0 -9
  22. data/docs/internal/decisions/server-functions-api.md +0 -2236
  23. data/spec/benchmarks/baseline.json +0 -12
  24. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
  25. data/spec/fixtures/flight/README.md +0 -136
  26. data/spec/fixtures/flight/array.txt +0 -1
  27. data/spec/fixtures/flight/as_json_object.txt +0 -2
  28. data/spec/fixtures/flight/bigint.txt +0 -1
  29. data/spec/fixtures/flight/boolean_false.txt +0 -1
  30. data/spec/fixtures/flight/boolean_true.txt +0 -1
  31. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  32. data/spec/fixtures/flight/client_reference.txt +0 -2
  33. data/spec/fixtures/flight/hash.txt +0 -1
  34. data/spec/fixtures/flight/infinity.txt +0 -1
  35. data/spec/fixtures/flight/nan.txt +0 -1
  36. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  37. data/spec/fixtures/flight/nil.txt +0 -1
  38. data/spec/fixtures/flight/number_float.txt +0 -1
  39. data/spec/fixtures/flight/number_integer.txt +0 -1
  40. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  41. data/spec/fixtures/flight/redirect_row.txt +0 -1
  42. data/spec/fixtures/flight/serializable_object.txt +0 -2
  43. data/spec/fixtures/flight/string_basic.txt +0 -1
  44. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  45. data/spec/fixtures/flight/undefined.txt +0 -1
  46. data/spec/fixtures/readme/children-error.html.erb +0 -3
  47. data/spec/fixtures/readme/children-error.txt +0 -1
  48. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  49. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  50. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  51. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  52. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  53. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  54. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  55. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  56. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  57. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  58. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  59. data/spec/readme_demo_message_spec.rb +0 -67
  60. data/spec/readme_spec.rb +0 -282
  61. data/spec/ruact/client_manifest_spec.rb +0 -270
  62. data/spec/ruact/component_contract_spec.rb +0 -119
  63. data/spec/ruact/configuration_spec.rb +0 -518
  64. data/spec/ruact/controller_request_spec.rb +0 -671
  65. data/spec/ruact/controller_spec.rb +0 -343
  66. data/spec/ruact/doctor_spec.rb +0 -769
  67. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  68. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  69. data/spec/ruact/errors_spec.rb +0 -93
  70. data/spec/ruact/flight/renderer_spec.rb +0 -133
  71. data/spec/ruact/flight/serializer_spec.rb +0 -494
  72. data/spec/ruact/html_converter_spec.rb +0 -375
  73. data/spec/ruact/install_generator_spec.rb +0 -1549
  74. data/spec/ruact/layout_source_spec.rb +0 -108
  75. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  76. data/spec/ruact/query_request_spec.rb +0 -706
  77. data/spec/ruact/query_spec.rb +0 -105
  78. data/spec/ruact/railtie_spec.rb +0 -155
  79. data/spec/ruact/render_context_spec.rb +0 -58
  80. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  81. data/spec/ruact/render_pipeline_spec.rb +0 -928
  82. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  83. data/spec/ruact/serializable_spec.rb +0 -179
  84. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  85. data/spec/ruact/server_function_name_spec.rb +0 -53
  86. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  87. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  88. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  89. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  90. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  91. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  92. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  93. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  94. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  95. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  96. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  97. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  98. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  99. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  100. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  101. data/spec/ruact/server_spec.rb +0 -179
  102. data/spec/ruact/server_upload_request_spec.rb +0 -311
  103. data/spec/ruact/signed_references_spec.rb +0 -164
  104. data/spec/ruact/string_distance_spec.rb +0 -38
  105. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  106. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  107. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  108. data/spec/ruact/validation_errors_spec.rb +0 -116
  109. data/spec/ruact/view_helper_spec.rb +0 -131
  110. data/spec/spec_helper.rb +0 -77
  111. data/spec/support/fixtures/pixel.png +0 -0
  112. data/spec/support/flight_wire_parser.rb +0 -21
  113. data/spec/support/flight_wire_parser_spec.rb +0 -93
  114. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  115. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  116. data/spec/support/rails_stub.rb +0 -115
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "ruact/server"
5
-
6
- # Story 9.3 AC4 — the `ruact_function_name` rename-override macro. Tested via
7
- # the concern's ClassMethods in isolation (no ActionController boot needed) so
8
- # the validation + storage contract is pinned independently of the request
9
- # cycle. RouteSource consumes the resulting `__ruact_function_name_overrides`.
10
- RSpec.describe "Ruact::Server.ruact_function_name", :story_9_3 do
11
- subject(:host) { Class.new { extend Ruact::Server::ClassMethods } }
12
-
13
- it "stores the override keyed by action name (string)" do
14
- host.ruact_function_name(:publish_all, as: "publishEverything")
15
- expect(host.__ruact_function_name_overrides).to eq("publish_all" => "publishEverything")
16
- end
17
-
18
- it "accepts a symbol target and stringifies it" do
19
- host.ruact_function_name(:publish_all, as: :publishEverything)
20
- expect(host.__ruact_function_name_overrides.fetch("publish_all")).to eq("publishEverything")
21
- end
22
-
23
- it "starts empty and is per-class (not shared)" do
24
- other = Class.new { extend Ruact::Server::ClassMethods }
25
- host.ruact_function_name(:create, as: "makePost")
26
- expect(host.__ruact_function_name_overrides).to eq("create" => "makePost")
27
- expect(other.__ruact_function_name_overrides).to eq({})
28
- end
29
-
30
- it "rejects an invalid JS identifier at class-load time" do
31
- expect do
32
- host.ruact_function_name(:create, as: "2bad name")
33
- end.to raise_error(Ruact::ConfigurationError, /not a valid JS identifier/)
34
- end
35
-
36
- it "rejects a reserved JS word" do
37
- expect do
38
- host.ruact_function_name(:create, as: "class")
39
- end.to raise_error(Ruact::ConfigurationError, /reserved/)
40
- end
41
-
42
- it "rejects a runtime-bound name (revalidate)" do
43
- expect do
44
- host.ruact_function_name(:create, as: "revalidate")
45
- end.to raise_error(Ruact::ConfigurationError, /reserved|ruact runtime/)
46
- end
47
-
48
- it "rejects the v2 runtime accessor name (_makeServerFunction)" do
49
- expect do
50
- host.ruact_function_name(:create, as: "_makeServerFunction")
51
- end.to raise_error(Ruact::ConfigurationError, /reserved|ruact runtime/)
52
- end
53
- end
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "ruact/server_functions/backtrace_cleaner"
5
-
6
- module Ruact
7
- module ServerFunctions
8
- RSpec.describe BacktraceCleaner, :story_8_4 do
9
- describe "Story 8.4 — .split classifies frames by Ruact.gem_path prefix" do
10
- let(:gem_root) { "/tmp/ruact-test-gem" }
11
-
12
- before { allow(Ruact).to receive(:gem_path).and_return(gem_root) }
13
-
14
- it "puts non-gem frames into :app, preserving order" do
15
- frames = [
16
- "/Users/dev/host/app/controllers/posts_controller.rb:12:in `create'",
17
- "/Users/dev/host/app/models/post.rb:7:in `save!'"
18
- ]
19
- expect(described_class.split(frames)).to eq(
20
- app: frames,
21
- gem: []
22
- )
23
- end
24
-
25
- it "puts frames under Ruact.gem_path into :gem, preserving order" do
26
- frames = [
27
- "#{gem_root}/lib/ruact/server_functions/endpoint_controller.rb:111:in `dispatch'",
28
- "#{gem_root}/lib/ruact/server_functions/standalone_dispatcher.rb:42:in `apply'"
29
- ]
30
- expect(described_class.split(frames)).to eq(
31
- app: [],
32
- gem: frames
33
- )
34
- end
35
-
36
- it "interleaves app+gem frames into separate buckets while preserving relative order in each bucket" do
37
- frames = [
38
- "/Users/dev/host/app/controllers/posts_controller.rb:12:in `create'",
39
- "#{gem_root}/lib/ruact/server_functions/endpoint_controller.rb:111:in `dispatch'",
40
- "/Users/dev/host/app/models/post.rb:7:in `save!'",
41
- "#{gem_root}/lib/ruact/server_functions/standalone_dispatcher.rb:42:in `apply'"
42
- ]
43
- result = described_class.split(frames)
44
- expect(result[:app]).to eq([frames[0], frames[2]])
45
- expect(result[:gem]).to eq([frames[1], frames[3]])
46
- end
47
-
48
- it "returns { app: [], gem: [] } for nil input" do
49
- expect(described_class.split(nil)).to eq(app: [], gem: [])
50
- end
51
-
52
- it "returns { app: [], gem: [] } for an empty array" do
53
- expect(described_class.split([])).to eq(app: [], gem: [])
54
- end
55
-
56
- it "classifies a frame whose path is exactly Ruact.gem_path as GEM (edge case)" do
57
- frames = ["#{gem_root}:0:in `<top>'"]
58
- expect(described_class.split(frames)).to eq(app: [], gem: frames)
59
- end
60
- end
61
- end
62
- end
63
- end
@@ -1,200 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Story 9.2 — unit spec for the pure Bucket-2 response serializer. Pins the
4
- # prop-exposure policy mirrored from Ruact::Flight::Serializer#serialize_unknown
5
- # (Serializable → ruact_props only; strict → raise; vetted as_json fallback),
6
- # producing a plain JSON-ready Hash (no Flight wire encoding). Pure function —
7
- # no Rails / request / Ruact.config reads (NFR26 / AC8).
8
-
9
- require "spec_helper"
10
- require "ruact/server_functions/bucket_two_payload"
11
-
12
- # Fixtures live OUTSIDE the example group (no leaky constants in the block).
13
- module B2Fixtures
14
- # A Serializable model exposing only some attributes.
15
- class Post
16
- include Ruact::Serializable
17
-
18
- attr_reader :id, :title, :secret
19
-
20
- def initialize(id:, title:, secret:)
21
- @id = id
22
- @title = title
23
- @secret = secret
24
- end
25
-
26
- ruact_props :id, :title
27
- end
28
-
29
- # Serializable whose prop is itself a Serializable (nested).
30
- class AuthoredPost
31
- include Ruact::Serializable
32
-
33
- attr_reader :title, :author
34
-
35
- def initialize(title:, author:)
36
- @title = title
37
- @author = author
38
- end
39
-
40
- ruact_props :title, :author
41
- end
42
-
43
- class Author
44
- include Ruact::Serializable
45
-
46
- attr_reader :name, :password_digest
47
-
48
- def initialize(name:, password_digest:)
49
- @name = name
50
- @password_digest = password_digest
51
- end
52
-
53
- ruact_props :name
54
- end
55
-
56
- # A plain object with as_json (AR-like).
57
- class PlainRecord
58
- def as_json(_opts = nil)
59
- { "id" => 7, "leaked" => "everything" }
60
- end
61
- end
62
-
63
- class SelfReturningAsJson
64
- def as_json(_opts = nil)
65
- self
66
- end
67
- end
68
-
69
- class RaisingAsJson
70
- def as_json(_opts = nil)
71
- raise "boom in as_json"
72
- end
73
- end
74
- end
75
-
76
- RSpec.describe Ruact::ServerFunctions::BucketTwoPayload, :story_9_2 do
77
- describe ".build (AC2 — keyed by ivar name, all exposed ivars)" do
78
- it "keys the result by the assigns names and serializes each value" do
79
- result = described_class.build(
80
- { "post" => B2Fixtures::Post.new(id: 1, title: "Hi", secret: "x"), "count" => 3 },
81
- strict: true
82
- )
83
- expect(result).to eq("post" => { "id" => 1, "title" => "Hi" }, "count" => 3)
84
- end
85
-
86
- it "does NOT unwrap a single ivar — it stays keyed (no magic unwrap)" do
87
- result = described_class.build({ "post" => B2Fixtures::Post.new(id: 1, title: "Hi", secret: "x") }, strict: true)
88
- expect(result).to eq("post" => { "id" => 1, "title" => "Hi" })
89
- end
90
- end
91
-
92
- describe "Serializable policy" do
93
- it "exposes ONLY ruact_props, never undeclared attributes (no secret leak)" do
94
- result = described_class.build({ "post" => B2Fixtures::Post.new(id: 1, title: "Hi", secret: "nope") },
95
- strict: true)
96
- expect(result.fetch("post")).to eq("id" => 1, "title" => "Hi")
97
- expect(result.fetch("post")).not_to have_key("secret")
98
- end
99
-
100
- it "recurses into a Serializable-valued prop (nested), applying ruact_props at each level" do
101
- author = B2Fixtures::Author.new(name: "Ada", password_digest: "HASH")
102
- post = B2Fixtures::AuthoredPost.new(title: "T", author: author)
103
- result = described_class.build({ "post" => post }, strict: true)
104
- expect(result.fetch("post")).to eq("title" => "T", "author" => { "name" => "Ada" })
105
- end
106
-
107
- it "serializes an Array of Serializables element-wise" do
108
- posts = [B2Fixtures::Post.new(id: 1, title: "A", secret: "s"),
109
- B2Fixtures::Post.new(id: 2, title: "B", secret: "s")]
110
- result = described_class.build({ "posts" => posts }, strict: true)
111
- expect(result.fetch("posts")).to eq([{ "id" => 1, "title" => "A" }, { "id" => 2, "title" => "B" }])
112
- end
113
- end
114
-
115
- describe "primitive pass-through (NOT subject to strict policy)" do
116
- it "passes scalars through untouched even under strict" do
117
- result = described_class.build(
118
- { "i" => 5, "f" => 1.5, "s" => "x", "t" => true, "n" => nil, "sym" => :ok },
119
- strict: true
120
- )
121
- expect(result).to eq("i" => 5, "f" => 1.5, "s" => "x", "t" => true, "n" => nil, "sym" => :ok)
122
- end
123
-
124
- it "passes Time through untouched (Rails render json: handles ISO formatting)" do
125
- time = Time.utc(2026, 1, 2, 3, 4, 5)
126
- result = described_class.build({ "at" => time }, strict: true)
127
- expect(result.fetch("at")).to equal(time)
128
- end
129
-
130
- it "stringifies Hash keys and recurses values" do
131
- assigns = { "meta" => { a: 1, b: B2Fixtures::Post.new(id: 9, title: "N", secret: "s") } }
132
- result = described_class.build(assigns, strict: true)
133
- expect(result.fetch("meta")).to eq("a" => 1, "b" => { "id" => 9, "title" => "N" })
134
- end
135
- end
136
-
137
- describe "strict_serialization policy (AC5)" do
138
- it "raises Ruact::SerializationError for a non-Serializable object under strict" do
139
- expect { described_class.build({ "rec" => B2Fixtures::PlainRecord.new }, strict: true) }
140
- .to raise_error(Ruact::SerializationError, /Cannot serialize B2Fixtures::PlainRecord/)
141
- end
142
-
143
- it "falls back to as_json when strict is false" do
144
- result = described_class.build({ "rec" => B2Fixtures::PlainRecord.new }, strict: false)
145
- expect(result.fetch("rec")).to eq("id" => 7, "leaked" => "everything")
146
- end
147
-
148
- it "raises when as_json returns self (infinite-recursion guard), regardless of strict" do
149
- expect { described_class.build({ "x" => B2Fixtures::SelfReturningAsJson.new }, strict: false) }
150
- .to raise_error(Ruact::SerializationError, /as_json returned self/)
151
- end
152
-
153
- it "wraps an exception raised inside as_json as Ruact::SerializationError" do
154
- expect { described_class.build({ "x" => B2Fixtures::RaisingAsJson.new }, strict: false) }
155
- .to raise_error(Ruact::SerializationError, /as_json raised RuntimeError: boom in as_json/)
156
- end
157
- end
158
-
159
- describe "Story 9.4 — .serialize_value (single value, same policy as .build — D6)", :story_9_4 do
160
- it "serializes a Serializable through ruact_props only" do
161
- post = B2Fixtures::Post.new(id: 1, title: "Hi", secret: "nope")
162
- expect(described_class.serialize_value(post, strict: true)).to eq("id" => 1, "title" => "Hi")
163
- end
164
-
165
- it "recurses into Arrays of Serializables" do
166
- posts = [B2Fixtures::Post.new(id: 1, title: "A", secret: "x")]
167
- expect(described_class.serialize_value(posts, strict: true)).to eq([{ "id" => 1, "title" => "A" }])
168
- end
169
-
170
- it "recurses into Hashes, stringifying keys" do
171
- value = { total: 2, post: B2Fixtures::Post.new(id: 1, title: "A", secret: "x") }
172
- expect(described_class.serialize_value(value, strict: true))
173
- .to eq("total" => 2, "post" => { "id" => 1, "title" => "A" })
174
- end
175
-
176
- it "passes primitives through untouched" do
177
- expect(described_class.serialize_value(42, strict: true)).to eq(42)
178
- end
179
-
180
- it "passes nil through (the dispatch controller renders it as JSON null — D6)" do
181
- expect(described_class.serialize_value(nil, strict: true)).to be_nil
182
- end
183
-
184
- it "raises Ruact::SerializationError for a non-Serializable under strict" do
185
- expect { described_class.serialize_value(B2Fixtures::PlainRecord.new, strict: true) }
186
- .to raise_error(Ruact::SerializationError, /Cannot serialize B2Fixtures::PlainRecord/)
187
- end
188
-
189
- it "falls back to as_json when strict is false" do
190
- expect(described_class.serialize_value(B2Fixtures::PlainRecord.new, strict: false))
191
- .to eq("id" => 7, "leaked" => "everything")
192
- end
193
-
194
- it "is the same policy .build applies per ivar (extraction, not a fork)" do
195
- post = B2Fixtures::Post.new(id: 3, title: "Same", secret: "x")
196
- expect(described_class.build({ "post" => post }, strict: true))
197
- .to eq("post" => described_class.serialize_value(post, strict: true))
198
- end
199
- end
200
- end