ruact 0.0.7 → 0.0.8

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/docs/internal/decisions/server-functions-api.md +55 -0
  4. data/lib/generators/ruact/install/install_generator.rb +114 -1
  5. data/lib/generators/ruact/install/templates/AGENTS.md.tt +159 -0
  6. data/lib/ruact/controller.rb +9 -0
  7. data/lib/ruact/doctor.rb +67 -9
  8. data/lib/ruact/erb_preprocessor.rb +113 -0
  9. data/lib/ruact/errors.rb +16 -0
  10. data/lib/ruact/manifest_resolver.rb +2 -2
  11. data/lib/ruact/serializable.rb +98 -6
  12. data/lib/ruact/server.rb +163 -0
  13. data/lib/ruact/server_functions/introspection.rb +81 -0
  14. data/lib/ruact/server_functions.rb +26 -4
  15. data/lib/ruact/testing/component_query.rb +113 -0
  16. data/lib/ruact/testing/flight_extractor.rb +221 -0
  17. data/lib/ruact/testing/flight_structure_diff.rb +267 -0
  18. data/lib/ruact/testing/flight_wire_parser.rb +138 -0
  19. data/lib/ruact/testing.rb +90 -0
  20. data/lib/ruact/version.rb +1 -1
  21. data/lib/tasks/ruact.rake +55 -2
  22. data/spec/ruact/doctor_spec.rb +141 -0
  23. data/spec/ruact/erb_preprocessor_spec.rb +145 -0
  24. data/spec/ruact/install_generator_spec.rb +285 -0
  25. data/spec/ruact/manifest_resolver_spec.rb +15 -0
  26. data/spec/ruact/serializable_spec.rb +126 -0
  27. data/spec/ruact/server_bucket_request_spec.rb +291 -0
  28. data/spec/ruact/server_functions/introspection_spec.rb +135 -0
  29. data/spec/ruact/tasks_json_introspection_spec.rb +141 -0
  30. data/spec/ruact/testing/have_ruact_component_spec.rb +170 -0
  31. data/spec/ruact/testing/no_production_load_spec.rb +41 -0
  32. data/spec/support/flight_wire_parser.rb +12 -126
  33. data/spec/support/matchers/flight_fixture_matcher.rb +7 -258
  34. data/vendor/javascript/ruact-server-functions-runtime/index.d.ts +25 -0
  35. data/vendor/javascript/ruact-server-functions-runtime/index.js +104 -7
  36. data/vendor/javascript/ruact-server-functions-runtime/index.test.mjs +173 -0
  37. data/vendor/javascript/vite-plugin-ruact/type-tests/auto-revalidate.test-d.ts +25 -0
  38. metadata +14 -2
@@ -14,9 +14,11 @@
14
14
  // enforces). Reads are CSRF-free (GET semantics).
15
15
  //
16
16
  // The export surface (`_makeServerFunction`, `_makeQuery`, `useQuery`,
17
- // `revalidate`, `configureRuactRuntime`, `RuactActionError`) and the
18
- // `"ruact/server-functions-runtime"` import path are part of the locked API —
19
- // do NOT change without coordinated codegen + Vite-plugin updates.
17
+ // `revalidate`, `withRefresh`, `configureRuactRuntime`, `RuactActionError`) and
18
+ // the `"ruact/server-functions-runtime"` import path are part of the locked API —
19
+ // do NOT change without coordinated codegen + Vite-plugin updates. (Story 15.6
20
+ // added `withRefresh` — a runtime-only wrapper; it is NOT re-exported through the
21
+ // generated `.ruact/server-functions.ts` barrel, so codegen output is unchanged.)
20
22
 
21
23
  // Story 9.5 — `useQuery` is a React hook, so the runtime now depends on React.
22
24
  // React is declared as a `peerDependency` (every host already has it; the
@@ -33,8 +35,16 @@ const RUNTIME_VERSION = 1;
33
35
  // them; instead, hosts call `configureRuactRuntime` once at app boot
34
36
  // to register a headers-producing function. The function runs on
35
37
  // every fetch so dynamic tokens (refreshed at runtime) are picked up.
38
+ // Story 15.6 (FR110) — `autoRevalidate` is the app-wide default for the
39
+ // auto-revalidate opt-in: when `true`, every successful, NON-redirecting mutation
40
+ // (`_makeServerFunction` accessor) `await`s an in-place Flight refresh of the
41
+ // current path (via the existing `revalidate()`) BEFORE resolving with the
42
+ // action's JSON result. Default `false` → today's behavior, byte-identical. A
43
+ // per-call `withRefresh(accessor)` wrapper forces the refresh for a single call
44
+ // and WINS over this global (D5: per-call explicit beats the global default).
36
45
  const runtimeOptions = {
37
46
  defaultHeaders: null,
47
+ autoRevalidate: false,
38
48
  };
39
49
  export function configureRuactRuntime(options) {
40
50
  if (options && Object.prototype.hasOwnProperty.call(options, "defaultHeaders")) {
@@ -47,6 +57,15 @@ export function configureRuactRuntime(options) {
47
57
  );
48
58
  }
49
59
  }
60
+ if (options && Object.prototype.hasOwnProperty.call(options, "autoRevalidate")) {
61
+ const value = options.autoRevalidate;
62
+ if (typeof value !== "boolean") {
63
+ throw new TypeError(
64
+ "configureRuactRuntime: autoRevalidate must be a boolean",
65
+ );
66
+ }
67
+ runtimeOptions.autoRevalidate = value;
68
+ }
50
69
  }
51
70
 
52
71
  /**
@@ -101,12 +120,28 @@ export class RuactActionError extends Error {
101
120
  * (`globalThis.__ruact_navigate`, `window.location.assign` fallback) and
102
121
  * resolves `null`.
103
122
  *
123
+ * Story 15.6 (FR110) — auto-revalidate opt-in. When the call opts in — via the
124
+ * app-wide `configureRuactRuntime({ autoRevalidate: true })` default OR the
125
+ * per-call `withRefresh(accessor)` wrapper — a SUCCESSFUL, NON-redirecting
126
+ * mutation `await`s an in-place Flight refresh of the current path (the existing
127
+ * `revalidate()`) BEFORE the promise resolves, then resolves with the action's
128
+ * JSON result. This is pure client-side COMPOSITION of the two requests that
129
+ * already exist (POST-JSON mutation + GET-Flight refresh) — no new wire shape, no
130
+ * inbound Flight deserialization (serialize-only invariant intact). A `$redirect`
131
+ * WINS: the redirect is followed and the refresh is SKIPPED (refreshing the old
132
+ * path would be wrong). A failed mutation throws before the refresh runs, so no
133
+ * refresh happens. If the refresh is requested with no router installed, the
134
+ * descriptive `revalidate()` error surfaces (it is not swallowed).
135
+ *
104
136
  * @param {{ method: string, path: string, segments?: string[] }} descriptor
105
137
  * @returns {(arg1?: Record<string, unknown> | FormData, arg2?: FormData | Record<string, unknown>) => Promise<unknown>}
106
138
  */
107
139
  export function _makeServerFunction(descriptor) {
108
140
  const { method, path, segments = [] } = descriptor || {};
109
- return async function ruactServerFunctionCall(...callArgs) {
141
+ // The core invoker takes the raw call-args plus a `refreshOverride`:
142
+ // undefined → fall back to the app-wide `runtimeOptions.autoRevalidate`;
143
+ // true/false → an explicit per-call decision that WINS over the global (D5).
144
+ const invoke = async function (callArgs, refreshOverride) {
110
145
  if (callArgs.length > 2) {
111
146
  throw new TypeError(
112
147
  `ruact server function ${method} ${path} called with ${callArgs.length} arguments — ` +
@@ -115,8 +150,61 @@ export function _makeServerFunction(descriptor) {
115
150
  }
116
151
  const args = pickWirePayload(callArgs);
117
152
  const url = interpolatePath(path, segments, args);
153
+ // AC4 — a failed mutation throws HERE, before any refresh is sequenced.
118
154
  const parsed = await ruactInvoke({ method, url, args, label: path });
119
- return followRedirectIfPresent(parsed);
155
+ const { value, redirected } = followRedirect(parsed);
156
+ // AC2 — redirect wins: the destination already re-renders; skip the refresh.
157
+ if (redirected) return value;
158
+ const refresh =
159
+ refreshOverride === undefined ? runtimeOptions.autoRevalidate === true : refreshOverride === true;
160
+ if (refresh) {
161
+ // AC1 — refresh the current path in place BEFORE resolving. AC5 — if no
162
+ // router is installed, `revalidate()`'s descriptive error surfaces here
163
+ // (D3: a mutation that succeeded but whose refresh rejects rejects the
164
+ // promise — consistent with `revalidate()`'s loud-by-default stance).
165
+ await revalidate();
166
+ }
167
+ return value;
168
+ };
169
+ const accessor = function ruactServerFunctionCall(...callArgs) {
170
+ return invoke(callArgs, undefined);
171
+ };
172
+ // Stash the core invoker on the accessor (non-enumerable) so `withRefresh` can
173
+ // force a refresh for a single call WITHOUT the codegen emitting a different
174
+ // accessor shape — the generated `.ruact/server-functions.ts` stays byte-
175
+ // identical (AC6). Keyed by a module-private Symbol so host code can't collide.
176
+ Object.defineProperty(accessor, REFRESH_INVOKER, { value: invoke, enumerable: false });
177
+ return accessor;
178
+ }
179
+
180
+ // Story 15.6 (FR110) — module-private handle used by `withRefresh` to reach a
181
+ // server-function accessor's core invoker (see `_makeServerFunction`).
182
+ const REFRESH_INVOKER = Symbol("ruactRefreshInvoker");
183
+
184
+ /**
185
+ * Story 15.6 (FR110) — per-call auto-revalidate wrapper. Wraps a generated
186
+ * server-function accessor and returns a NEW accessor that forces the in-place
187
+ * Flight refresh after a successful, non-redirecting call — regardless of the
188
+ * app-wide `autoRevalidate` default (D5: per-call explicit wins). Runtime-only:
189
+ * it does not change the codegen or the wire.
190
+ *
191
+ * import { createPost } from "@/.ruact/server-functions";
192
+ * import { withRefresh } from "ruact/server-functions-runtime";
193
+ * await withRefresh(createPost)(formData); // mutation + refresh in one await
194
+ *
195
+ * @param {Function} accessor A `_makeServerFunction`-produced accessor.
196
+ * @returns {(...args: unknown[]) => Promise<unknown>} A refreshing accessor.
197
+ */
198
+ export function withRefresh(accessor) {
199
+ const invoke = typeof accessor === "function" ? accessor[REFRESH_INVOKER] : undefined;
200
+ if (typeof invoke !== "function") {
201
+ throw new TypeError(
202
+ "ruact withRefresh() expects a server-function accessor produced by the ruact codegen " +
203
+ "(imported from @/.ruact/server-functions)",
204
+ );
205
+ }
206
+ return function ruactRefreshingCall(...callArgs) {
207
+ return invoke(callArgs, true);
120
208
  };
121
209
  }
122
210
 
@@ -519,6 +607,15 @@ function readSegment(args, seg) {
519
607
  // `{ "$redirect": "<path>" }`; follow it via the router handoff and resolve
520
608
  // `null` (consistent with the 204→null contract).
521
609
  function followRedirectIfPresent(parsed) {
610
+ return followRedirect(parsed).value;
611
+ }
612
+
613
+ // Story 15.6 — the same follow logic, but reporting WHETHER a redirect was
614
+ // followed as an explicit boolean. Auto-revalidate needs to distinguish "a
615
+ // redirect was followed → skip the refresh" from "the action genuinely resolved
616
+ // `null`/204 → still refresh" — inferring redirect from `value === null` would
617
+ // wrongly skip the refresh after an ordinary empty-body mutation.
618
+ function followRedirect(parsed) {
522
619
  if (
523
620
  parsed &&
524
621
  typeof parsed === "object" &&
@@ -526,9 +623,9 @@ function followRedirectIfPresent(parsed) {
526
623
  typeof parsed.$redirect === "string"
527
624
  ) {
528
625
  navigateTo(parsed.$redirect);
529
- return null;
626
+ return { value: null, redirected: true };
530
627
  }
531
- return parsed;
628
+ return { value: parsed, redirected: false };
532
629
  }
533
630
 
534
631
  function navigateTo(target) {
@@ -15,6 +15,7 @@ import {
15
15
  RuactActionError,
16
16
  configureRuactRuntime,
17
17
  revalidate,
18
+ withRefresh,
18
19
  } from "./index.js";
19
20
 
20
21
  // Helper — a route-driven accessor targeting `POST /posts` (the canonical
@@ -822,3 +823,175 @@ describe("Story 9.3 — _makeServerFunction (route-driven, real path+verb)", ()
822
823
  expect(result).toEqual({ post: { id: 1 }, $redirect: 42 });
823
824
  });
824
825
  });
826
+
827
+ // =============================================================================
828
+ // Story 15.6 (FR110) — auto-revalidate opt-in: one await = mutation + refresh
829
+ // =============================================================================
830
+
831
+ describe("Story 15.6 — auto-revalidate opt-in", () => {
832
+ let originalRevalidate;
833
+ let originalNavigate;
834
+ let originalLocation;
835
+ let originalWindow;
836
+
837
+ beforeEach(() => {
838
+ originalRevalidate = globalThis.__ruact_revalidate;
839
+ originalNavigate = globalThis.__ruact_navigate;
840
+ originalLocation = globalThis.location;
841
+ originalWindow = globalThis.window;
842
+ globalThis.location = { pathname: "/posts", search: "?page=2" };
843
+ });
844
+
845
+ afterEach(() => {
846
+ // Reset the app-wide default so a leaked `autoRevalidate: true` can't bleed
847
+ // into the un-opted-in byte-parity tests elsewhere in the suite.
848
+ configureRuactRuntime({ autoRevalidate: false });
849
+ if (originalRevalidate === undefined) delete globalThis.__ruact_revalidate;
850
+ else globalThis.__ruact_revalidate = originalRevalidate;
851
+ globalThis.__ruact_navigate = originalNavigate;
852
+ if (originalLocation === undefined) {
853
+ // jsdom / browser env — leave the real `location` alone
854
+ } else {
855
+ globalThis.location = originalLocation;
856
+ }
857
+ globalThis.window = originalWindow;
858
+ });
859
+
860
+ // (a) request-composition order — POST then Flight refresh, in that order,
861
+ // resolves with the action's JSON result.
862
+ it("global default: refreshes the current path AFTER the mutation and resolves the JSON result", async () => {
863
+ const order = [];
864
+ mockFetchOk({ id: 7 });
865
+ const fetchSpy = globalThis.fetch;
866
+ fetchSpy.mockImplementation(async () => {
867
+ order.push("fetch");
868
+ return {
869
+ ok: true,
870
+ status: 200,
871
+ headers: { get: (n) => (n.toLowerCase() === "content-type" ? "application/json" : null) },
872
+ text: vi.fn().mockResolvedValue(JSON.stringify({ id: 7 })),
873
+ json: vi.fn().mockResolvedValue({ id: 7 }),
874
+ };
875
+ });
876
+ const revalidateSpy = vi.fn(async () => {
877
+ order.push("revalidate");
878
+ });
879
+ globalThis.__ruact_revalidate = revalidateSpy;
880
+ configureRuactRuntime({ autoRevalidate: true });
881
+
882
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
883
+ const result = await createPost({ title: "x" });
884
+
885
+ expect(order).toEqual(["fetch", "revalidate"]);
886
+ expect(revalidateSpy).toHaveBeenCalledWith("/posts?page=2");
887
+ expect(result).toEqual({ id: 7 });
888
+ });
889
+
890
+ it("per-call withRefresh: forces the refresh even when the global default is OFF", async () => {
891
+ mockFetchOk({ id: 1 });
892
+ const revalidateSpy = vi.fn().mockResolvedValue(undefined);
893
+ globalThis.__ruact_revalidate = revalidateSpy;
894
+ configureRuactRuntime({ autoRevalidate: false });
895
+
896
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
897
+ const result = await withRefresh(createPost)({ title: "x" });
898
+
899
+ expect(revalidateSpy).toHaveBeenCalledWith("/posts?page=2");
900
+ expect(result).toEqual({ id: 1 });
901
+ });
902
+
903
+ it("refreshes after a genuine null/204 result (not misread as a redirect)", async () => {
904
+ mockFetchOk("", { status: 204, contentType: "text/plain" });
905
+ const revalidateSpy = vi.fn().mockResolvedValue(undefined);
906
+ globalThis.__ruact_revalidate = revalidateSpy;
907
+
908
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
909
+ const result = await withRefresh(createPost)({});
910
+
911
+ expect(revalidateSpy).toHaveBeenCalledTimes(1);
912
+ expect(result).toBeNull();
913
+ });
914
+
915
+ // (b) error propagation — a failed mutation rejects and issues NO refresh.
916
+ it("mutation failure propagates and does NOT refresh (refresh sequenced after success)", async () => {
917
+ mockFetchError(422, JSON.stringify({ error: "invalid" }));
918
+ const revalidateSpy = vi.fn().mockResolvedValue(undefined);
919
+ globalThis.__ruact_revalidate = revalidateSpy;
920
+ configureRuactRuntime({ autoRevalidate: true });
921
+
922
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
923
+
924
+ await expect(createPost({ title: "" })).rejects.toMatchObject({
925
+ name: "RuactActionError",
926
+ status: 422,
927
+ });
928
+ expect(revalidateSpy).not.toHaveBeenCalled();
929
+ });
930
+
931
+ // (c) redirect-wins — a $redirect follows the redirect and issues NO refresh.
932
+ it("redirect wins: follows the $redirect, resolves null, and SKIPS the refresh", async () => {
933
+ mockFetchOk({ $redirect: "/posts/1" });
934
+ const navSpy = vi.fn();
935
+ globalThis.__ruact_navigate = navSpy;
936
+ const revalidateSpy = vi.fn().mockResolvedValue(undefined);
937
+ globalThis.__ruact_revalidate = revalidateSpy;
938
+ configureRuactRuntime({ autoRevalidate: true });
939
+
940
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
941
+ const result = await createPost({ title: "x" });
942
+
943
+ expect(navSpy).toHaveBeenCalledWith("/posts/1");
944
+ expect(result).toBeNull();
945
+ expect(revalidateSpy).not.toHaveBeenCalled();
946
+ });
947
+
948
+ // (d) no-router — the descriptive revalidate() error surfaces (not swallowed).
949
+ it("no router installed: surfaces the descriptive revalidate() error", async () => {
950
+ mockFetchOk({ id: 1 });
951
+ if ("__ruact_revalidate" in globalThis) delete globalThis.__ruact_revalidate;
952
+ configureRuactRuntime({ autoRevalidate: true });
953
+
954
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
955
+
956
+ await expect(createPost({ title: "x" })).rejects.toThrow(
957
+ /ruact: revalidate\(\) called but no router is installed/,
958
+ );
959
+ });
960
+
961
+ it("D3: a successful mutation whose refresh REJECTS rejects the returned promise", async () => {
962
+ mockFetchOk({ id: 9 });
963
+ const revalidateSpy = vi.fn().mockRejectedValue(new Error("Flight refresh failed"));
964
+ globalThis.__ruact_revalidate = revalidateSpy;
965
+
966
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
967
+
968
+ await expect(withRefresh(createPost)({ title: "x" })).rejects.toThrow("Flight refresh failed");
969
+ expect(revalidateSpy).toHaveBeenCalledTimes(1);
970
+ });
971
+
972
+ // (e) un-opted-in is byte-identical to today: exactly one fetch, no refresh.
973
+ it("un-opted-in call is byte-identical: one fetch, no refresh", async () => {
974
+ mockFetchOk({ id: 1 });
975
+ const revalidateSpy = vi.fn().mockResolvedValue(undefined);
976
+ globalThis.__ruact_revalidate = revalidateSpy;
977
+ // global default stays OFF (reset in afterEach), no withRefresh wrapper.
978
+
979
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
980
+ const result = await createPost({ title: "x" });
981
+
982
+ expect(globalThis.fetch).toHaveBeenCalledTimes(1);
983
+ expect(revalidateSpy).not.toHaveBeenCalled();
984
+ expect(result).toEqual({ id: 1 });
985
+ });
986
+
987
+ it("withRefresh throws a descriptive TypeError for a non-accessor argument", () => {
988
+ expect(() => withRefresh(() => {})).toThrow(/server-function accessor produced by the ruact codegen/);
989
+ expect(() => withRefresh(null)).toThrow(TypeError);
990
+ });
991
+
992
+ it("configureRuactRuntime rejects a non-boolean autoRevalidate", () => {
993
+ expect(() => configureRuactRuntime({ autoRevalidate: "yes" })).toThrow(
994
+ /autoRevalidate must be a boolean/,
995
+ );
996
+ });
997
+ });
@@ -0,0 +1,25 @@
1
+ // Story 15.6 (FR110) — type-level test for the auto-revalidate opt-in surface.
2
+ // Proves (a) `configureRuactRuntime` accepts the new `autoRevalidate?: boolean`
3
+ // key alongside `defaultHeaders`, and (b) `withRefresh` preserves the wrapped
4
+ // accessor's call signature (so `withRefresh(createPost)` is callable exactly
5
+ // like `createPost`). Runtime-only: this surface is NOT emitted by the codegen,
6
+ // so there is no byte-parity fixture to regenerate.
7
+
8
+ import { _makeServerFunction, configureRuactRuntime, withRefresh } from "ruact/server-functions-runtime";
9
+
10
+ // (a) the app-wide default key type-checks (and stays optional).
11
+ configureRuactRuntime({ autoRevalidate: true });
12
+ configureRuactRuntime({ autoRevalidate: false, defaultHeaders: { Authorization: "Bearer x" } });
13
+ configureRuactRuntime({ defaultHeaders: null });
14
+
15
+ // A non-boolean autoRevalidate is a type error.
16
+ // @ts-expect-error autoRevalidate must be a boolean
17
+ configureRuactRuntime({ autoRevalidate: "yes" });
18
+
19
+ // (b) withRefresh preserves the accessor call signature.
20
+ const createPost = _makeServerFunction({ method: "POST", path: "/posts", segments: [] });
21
+ const createPostRefreshing = withRefresh(createPost);
22
+
23
+ // Callable at the same shapes as the underlying accessor.
24
+ void createPostRefreshing({ title: "x" });
25
+ void createPostRefreshing();
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruact
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luiz Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-30 00:00:00.000000000 Z
11
+ date: 2026-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -46,6 +46,7 @@ files:
46
46
  - docs/internal/README.md
47
47
  - docs/internal/decisions/server-functions-api.md
48
48
  - lib/generators/ruact/install/install_generator.rb
49
+ - lib/generators/ruact/install/templates/AGENTS.md.tt
49
50
  - lib/generators/ruact/install/templates/Procfile.dev.tt
50
51
  - lib/generators/ruact/install/templates/dev.tt
51
52
  - lib/generators/ruact/install/templates/initializer.rb.tt
@@ -102,6 +103,7 @@ files:
102
103
  - lib/ruact/server_functions/error_payload.rb
103
104
  - lib/ruact/server_functions/error_rendering.rb
104
105
  - lib/ruact/server_functions/error_suggestion.rb
106
+ - lib/ruact/server_functions/introspection.rb
105
107
  - lib/ruact/server_functions/name_bridge.rb
106
108
  - lib/ruact/server_functions/query_context.rb
107
109
  - lib/ruact/server_functions/query_dispatch.rb
@@ -112,6 +114,11 @@ files:
112
114
  - lib/ruact/server_functions/validation_errors.rb
113
115
  - lib/ruact/signed_references.rb
114
116
  - lib/ruact/string_distance.rb
117
+ - lib/ruact/testing.rb
118
+ - lib/ruact/testing/component_query.rb
119
+ - lib/ruact/testing/flight_extractor.rb
120
+ - lib/ruact/testing/flight_structure_diff.rb
121
+ - lib/ruact/testing/flight_wire_parser.rb
115
122
  - lib/ruact/validation_errors_collector.rb
116
123
  - lib/ruact/version.rb
117
124
  - lib/ruact/view_helper.rb
@@ -176,6 +183,7 @@ files:
176
183
  - spec/ruact/server_functions/codegen_spec.rb
177
184
  - spec/ruact/server_functions/error_payload_spec.rb
178
185
  - spec/ruact/server_functions/error_suggestion_spec.rb
186
+ - spec/ruact/server_functions/introspection_spec.rb
179
187
  - spec/ruact/server_functions/name_bridge_spec.rb
180
188
  - spec/ruact/server_functions/query_context_spec.rb
181
189
  - spec/ruact/server_functions/query_source_spec.rb
@@ -189,6 +197,9 @@ files:
189
197
  - spec/ruact/server_upload_request_spec.rb
190
198
  - spec/ruact/signed_references_spec.rb
191
199
  - spec/ruact/string_distance_spec.rb
200
+ - spec/ruact/tasks_json_introspection_spec.rb
201
+ - spec/ruact/testing/have_ruact_component_spec.rb
202
+ - spec/ruact/testing/no_production_load_spec.rb
192
203
  - spec/ruact/validation_errors_spec.rb
193
204
  - spec/ruact/view_helper_spec.rb
194
205
  - spec/spec_helper.rb
@@ -218,6 +229,7 @@ files:
218
229
  - vendor/javascript/vite-plugin-ruact/tsconfig.json
219
230
  - vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json
220
231
  - vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json
232
+ - vendor/javascript/vite-plugin-ruact/type-tests/auto-revalidate.test-d.ts
221
233
  - vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts
222
234
  - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx
223
235
  - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx