Module: Ruact::ServerFunctions::Snapshot

Defined in:
lib/ruact/server_functions/snapshot.rb

Overview

Pure functions that build the JSON-shaped Hash representing both server- function registries. Serialized to ‘tmp/cache/ruact/server-functions.json` by Snapshot.generate!; the Vite plugin reads that file and emits the TS module.

The “functions” array is sorted by ‘ruby_symbol` for deterministic output so that fingerprint comparisons (used by the write-if-changed guard) are stable across runs. Cross-registry JS-identifier collisions are detected here (the per-registry `Registry#register` only sees its own entries; a `ruact_action :foo` colliding with a `ruact_query :foo` is invisible to both registries in isolation).

Constant Summary collapse

VERSION =

Bump only when the on-disk schema changes incompatibly. The Vite plugin must be updated in lockstep.

1
VERSION_V2 =

Story 9.3 — the route-driven snapshot schema. v2 entries are produced by RouteSource (route table), not the registries.

2

Class Method Summary collapse

Class Method Details

.dump(action_registry, query_registry, now: Time.now.utc) ⇒ Hash

Builds the snapshot Hash for both registries. Pure. See also generate! (writes to disk) and functions_payload (fingerprint surface).

Parameters:

Returns:

  • (Hash)

    the serializable snapshot.

Raises:



38
39
40
41
42
43
44
# File 'lib/ruact/server_functions/snapshot.rb', line 38

def dump(action_registry, query_registry, now: Time.now.utc)
  {
    version: VERSION,
    generated_at: now.utc.iso8601,
    functions: functions_payload(action_registry, query_registry)
  }
end

.dump_v2(entries, now: Time.now.utc) ⇒ Hash

Story 9.3 — wraps route-derived entries into a version-2 snapshot Hash (the shape Codegen.render dispatches on). Pure.

Parameters:

Returns:

  • (Hash)


105
106
107
108
109
110
111
# File 'lib/ruact/server_functions/snapshot.rb', line 105

def dump_v2(entries, now: Time.now.utc)
  {
    version: VERSION_V2,
    generated_at: now.utc.iso8601,
    functions: entries
  }
end

.functions_payload(action_registry, query_registry) ⇒ Array<Hash>

Returns the payload-only array of function entries, sorted by ‘ruby_symbol`. Used both inside dump and as the fingerprint surface by generate!’s short-circuit (so timestamp churn alone never causes a rewrite). Detects cross-registry JS-identifier collisions and raises before emitting — a ‘ruact_action :foo` and `ruact_query :foo` would emit two `export const foo` lines at codegen, which `tsc` rejects.

Returns:

  • (Array<Hash>)

    each entry has string keys per the JSON contract.

Raises:

  • (Ruact::ConfigurationError)

    when the action and query registries both contain entries that map to the same JS identifier.



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/ruact/server_functions/snapshot.rb', line 56

def functions_payload(action_registry, query_registry)
  combined = action_registry.entries.values + query_registry.entries.values
  detect_cross_registry_collision!(combined)
  combined.sort_by { |entry| entry.ruby_symbol.to_s }.map do |entry|
    {
      "ruby_symbol" => entry.ruby_symbol.to_s,
      "js_identifier" => entry.js_identifier,
      "kind" => entry.kind.to_s,
      "controller" => describe_controller(entry.controller)
    }
  end
end

.generate!(action_registry:, query_registry:, path:, now: Time.now.utc) ⇒ Boolean

Builds the snapshot and writes it to path, but only if the functions list differs from the on-disk snapshot. This is the central short-circuit that prevents ‘config.to_prepare` from rewriting the file on every request (Story 8.0a pitfall #1): the JSON’s ‘generated_at` is freshly stamped only when the registry actually changed; otherwise the on-disk content stays byte-identical.

The short-circuit compares both ‘version` and `functions` against the on-disk snapshot — a schema bump (`VERSION` increment) forces a rewrite even when the registry payload is unchanged, so the Vite plugin never reads a stale-version snapshot after a gem upgrade.

Parameters:

Returns:

  • (Boolean)

    true if the file was written; false if no change.



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ruact/server_functions/snapshot.rb', line 86

def generate!(action_registry:, query_registry:, path:, now: Time.now.utc)
  new_functions = functions_payload(action_registry, query_registry)

  existing_version, existing_functions = read_existing_snapshot(path)
  return false if existing_version == VERSION && existing_functions == new_functions

  snapshot = {
    version: VERSION,
    generated_at: now.utc.iso8601,
    functions: new_functions
  }
  SnapshotWriter.write_if_changed!(path: path, content: "#{JSON.pretty_generate(snapshot)}\n")
end

.generate_v2!(entries:, path:, now: Time.now.utc) ⇒ Boolean

Story 9.3 — write-if-changed for the route-driven (v2) bridge. Mirrors generate!: ‘generated_at` is freshly stamped only when the entries changed, so a stable route table never churns the file (and never re-triggers downstream TS rendering). A schema mismatch (`version`) forces a rewrite even when entries are unchanged.

Parameters:

  • entries (Array<Hash>)
  • path (String, Pathname)

    absolute path to the v2 bridge JSON.

Returns:

  • (Boolean)

    true if written, false if unchanged.



122
123
124
125
126
127
128
# File 'lib/ruact/server_functions/snapshot.rb', line 122

def generate_v2!(entries:, path:, now: Time.now.utc)
  existing_version, existing_functions = read_existing_snapshot(path)
  return false if existing_version == VERSION_V2 && existing_functions == entries

  snapshot = { version: VERSION_V2, generated_at: now.utc.iso8601, functions: entries }
  SnapshotWriter.write_if_changed!(path: path, content: "#{JSON.pretty_generate(snapshot)}\n")
end

.v1_declarations?(action_registry, query_registry) ⇒ Boolean

Story 9.3 — the Decision-#6 ownership primitive. True when the app has ANY v1 declaration (‘ruact_action` / `ruact_query`). Story 9.8 consults this to decide whether route-driven codegen takes over the real `server-functions.ts`; in Story 9.3 the v2 codegen always writes the parallel `.next` target regardless, so this is informational here.

Returns:

  • (Boolean)


137
138
139
# File 'lib/ruact/server_functions/snapshot.rb', line 137

def v1_declarations?(action_registry, query_registry)
  !(action_registry.entries.empty? && query_registry.entries.empty?)
end