Module: Ruact::ServerFunctions
- Defined in:
- lib/ruact/server_functions.rb,
lib/ruact/server_functions/codegen.rb,
lib/ruact/server_functions/snapshot.rb,
lib/ruact/server_functions/codegen_v2.rb,
lib/ruact/server_functions/name_bridge.rb,
lib/ruact/server_functions/query_source.rb,
lib/ruact/server_functions/route_source.rb,
lib/ruact/server_functions/error_payload.rb,
lib/ruact/server_functions/introspection.rb,
lib/ruact/server_functions/query_context.rb,
lib/ruact/server_functions/query_dispatch.rb,
lib/ruact/server_functions/error_rendering.rb,
lib/ruact/server_functions/snapshot_writer.rb,
lib/ruact/server_functions/error_suggestion.rb,
lib/ruact/server_functions/backtrace_cleaner.rb,
lib/ruact/server_functions/validation_errors.rb,
lib/ruact/server_functions/bucket_two_payload.rb,
lib/ruact/server_functions/codegen_v2_query_params.rb
Defined Under Namespace
Modules: BacktraceCleaner, BucketTwoPayload, Codegen, ErrorPayload, ErrorRendering, ErrorSuggestion, Introspection, NameBridge, QueryDispatch, QuerySource, RouteSource, Snapshot, SnapshotWriter, ValidationErrors Classes: QueryContext
Class Method Summary collapse
- .default_logger ⇒ Object
-
.detect_merged_namespace_collisions!(entries) ⇒ Object
Story 9.5 (Task 2) — the merged JS namespace covers route (action) entries AND query entries.
-
.introspect(route_set:, host_predicate: nil, overrides_for: nil, query_class_for: nil) ⇒ Array<Hash>
Story 15.3 (FR107) — the read-only, side-effect-free combine point: collect mutation actions (RouteSource) + read queries (QuerySource), sort by
js_identifier, and run the merged-namespace collision check — WITHOUT writing the bridge/TS. -
.write_v2_snapshot!(route_set:, root:, logger: default_logger) ⇒ Array<Hash>
Story 9.3 / 9.9 — orchestrates the route-driven (v2) codegen.
Class Method Details
.default_logger ⇒ Object
100 101 102 |
# File 'lib/ruact/server_functions.rb', line 100 def self.default_logger defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : nil end |
.detect_merged_namespace_collisions!(entries) ⇒ Object
Story 9.5 (Task 2) — the merged JS namespace covers route (action)
entries AND query entries. RouteSource already rejects action×action
collisions and QuerySource rejects query×query; this final pass catches
a route×query clash — two distinct origins (one a mutation route, one a
query method) mapping to the same js_identifier would emit two
export const <id> lines and crash the generated module at load. Fail
loudly at boot naming BOTH origins. The escape hatch is the
ruact_function_name :<action>, as: "<id>" rename macro on the mutation
controller (Story 9.3) or renaming the colliding query method.
116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/ruact/server_functions.rb', line 116 def self.detect_merged_namespace_collisions!(entries) entries.group_by { |entry| entry["js_identifier"] }.each do |js_id, group| next if group.size < 2 origins = group.map { |entry| "#{entry['controller']}##{entry['action']}" } raise Ruact::ConfigurationError, "server-function naming collision: #{origins.join(' and ')} " \ "both map to JS identifier \"#{js_id}\" — disambiguate with " \ "`ruact_function_name :<action>, as: \"<other-name>\"` on the mutation " \ "controller, or rename the query method." end end |
.introspect(route_set:, host_predicate: nil, overrides_for: nil, query_class_for: nil) ⇒ Array<Hash>
Story 15.3 (FR107) — the read-only, side-effect-free combine point:
collect mutation actions (RouteSource) + read queries (QuerySource),
sort by js_identifier, and run the merged-namespace collision check —
WITHOUT writing the bridge/TS. Both write_v2_snapshot! (codegen) and
Introspection (ruact:routes --json) call THIS,
so what an agent verifies against can never drift from what codegen emits
(single source of truth), and a CI gate can introspect without mutating the
tree. The resolver callables default to real constant resolution; specs
inject lambdas to exercise the derivation without booting controllers.
92 93 94 95 96 97 98 |
# File 'lib/ruact/server_functions.rb', line 92 def self.introspect(route_set:, host_predicate: nil, overrides_for: nil, query_class_for: nil) actions = RouteSource.collect(route_set, host_predicate: host_predicate, overrides_for: overrides_for) queries = QuerySource.collect(route_set, query_class_for: query_class_for) entries = (actions + queries).sort_by { |entry| entry["js_identifier"] } detect_merged_namespace_collisions!(entries) entries end |
.write_v2_snapshot!(route_set:, root:, logger: default_logger) ⇒ Array<Hash>
Story 9.3 / 9.9 — orchestrates the route-driven (v2) codegen. Reads the
route table via RouteSource + QuerySource, writes the version-2 bridge
to the REAL path (write-if-changed), and renders the TS via the Ruby
Codegen. As of Story 9.9 this is the SOLE writer of the real bridge —
the v1 registry path and the parallel .next target were demolished
(epic decision #6: route-driven codegen owns server-functions.ts
unconditionally; the module path @/.ruact/server-functions never changed,
so React imports are unchanged by construction).
AC2 — transparency over silence: the exposed names are ALWAYS logged so a routed non-GET action never becomes a callable server function silently.
Story 9.5 — the entries array carries BOTH mutation actions (from
RouteSource) and read queries (from QuerySource); they share ONE
merged JS namespace. detect_merged_namespace_collisions! catches a
route×query clash (each source already catches its own intra-kind
collisions); the rename-override macro ruact_function_name on the
mutation side (or renaming the query method) resolves it.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/ruact/server_functions.rb', line 58 def self.write_v2_snapshot!(route_set:, root:, logger: default_logger) entries = introspect(route_set: route_set) json_path = root.join("tmp/cache/ruact/server-functions.json") ts_path = root.join("app/javascript/.ruact/server-functions.ts") # Read back from the on-disk bridge (not a fresh dump) so a stable route # table never churns the timestamp baked into the rendered TS header. Snapshot.generate_v2!(entries: entries, path: json_path) Codegen.generate_ts!(snapshot: JSON.parse(File.read(json_path)), output_path: ts_path) # AC2 — ALWAYS log what is exposed (even "(none)"), so a routed non-GET # action never becomes a callable server function silently. names = entries.empty? ? "(none)" : entries.map { |e| e["js_identifier"] }.join(", ") logger&.info "[ruact] codegen: exposing #{names}" entries end |