ruact 0.0.5 → 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 +67 -18
- 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 +40 -45
- data/vendor/javascript/ruact-server-functions-runtime/index.js +42 -98
- data/vendor/javascript/ruact-server-functions-runtime/index.test.mjs +72 -75
- data/vendor/javascript/ruact-server-functions-runtime/package.json +1 -1
- 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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Story 10.2 (AC7 / Task 5) — AMBIENT stubs so the GENERATED `<Model>List.tsx`
|
|
2
|
+
// type-checks in ISOLATION, without a real shadcn install. The fixture
|
|
3
|
+
// `PostList.tsx` in this directory is byte-identical to the generator's live
|
|
4
|
+
// output (a gem rspec example asserts that equality), so type-checking it under
|
|
5
|
+
// `tsconfig.scaffold.json` proves the emitted typed List holds the FR99 server
|
|
6
|
+
// boundary + the shadcn `table`-primitive import contract. There is NO
|
|
7
|
+
// table-engine dependency — Story 10.2b moved the List off the react-table-based
|
|
8
|
+
// `DataTable` recipe onto the dep-free shadcn `table` primitive + a generated sort.
|
|
9
|
+
//
|
|
10
|
+
// These are deliberately MINIMAL structural shims — NOT the real shadcn / react
|
|
11
|
+
// types. Story 10.5 installs the real `@/components/ui/*` modules (incl. the
|
|
12
|
+
// `table` primitive); 10.7 wires the live end-to-end demo. The point here is the
|
|
13
|
+
// generated module's OWN type-safety (typed rows, sortable headers, the search
|
|
14
|
+
// accessor), not re-testing upstream libraries.
|
|
15
|
+
|
|
16
|
+
// JSX without `lib: ["dom"]` / `@types/react`: a permissive intrinsic-element
|
|
17
|
+
// table (every native prop is `any`, so `onChange={(e) => …}` etc. need no DOM
|
|
18
|
+
// event types). `jsx: "preserve"` in tsconfig.scaffold.json still type-checks
|
|
19
|
+
// every JSX expression against this namespace.
|
|
20
|
+
declare namespace JSX {
|
|
21
|
+
interface Element {} // eslint-disable-line @typescript-eslint/no-empty-interface
|
|
22
|
+
interface ElementClass {} // eslint-disable-line @typescript-eslint/no-empty-interface
|
|
23
|
+
interface IntrinsicElements {
|
|
24
|
+
[elem: string]: any;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module "react" {
|
|
29
|
+
export function useState<S>(initial: S | (() => S)): [S, (next: S | ((prev: S) => S)) => void];
|
|
30
|
+
// The generated Form's `handleSubmit(event: FormEvent)` only calls
|
|
31
|
+
// `event.preventDefault()`; a minimal shim keeps the type load-bearing without
|
|
32
|
+
// pulling in `@types/react`.
|
|
33
|
+
export interface FormEvent {
|
|
34
|
+
preventDefault(): void;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare module "@/.ruact/server-functions" {
|
|
39
|
+
// FR88/FR99 wire union — the only param value type a query accessor accepts.
|
|
40
|
+
type Wire = string | number | boolean | null;
|
|
41
|
+
|
|
42
|
+
// The codegen exports a generic `search` from `<Plural>Query#search(q:)`,
|
|
43
|
+
// typed from the declared `(q:)` kwarg (Story 13.4). The List aliases it
|
|
44
|
+
// `search<Plural>`.
|
|
45
|
+
export const search: (params: { q: Wire }) => Promise<unknown>;
|
|
46
|
+
|
|
47
|
+
// Action accessors the generated Form imports (Story 10.3). Actions are NOT
|
|
48
|
+
// typed by FR99 (only queries are — Story 13.4 decision A), so the accessors
|
|
49
|
+
// take a loose payload and return `Promise<unknown>`; the Form narrows the
|
|
50
|
+
// result with an `as { errors?: ... } | null` assertion at the call site.
|
|
51
|
+
export const createPost: (payload: Record<string, unknown>) => Promise<unknown>;
|
|
52
|
+
export const updatePost: (payload: Record<string, unknown>) => Promise<unknown>;
|
|
53
|
+
// Story 10.4 — the destroy accessor the List's RowActions calls in `onConfirm`
|
|
54
|
+
// (DELETE /posts/:id). Like the other actions it is not FR99-typed; it returns
|
|
55
|
+
// `Promise<unknown>` and throws a `RuactActionError` on a non-2xx response.
|
|
56
|
+
export const destroyPost: (payload: Record<string, unknown>) => Promise<unknown>;
|
|
57
|
+
|
|
58
|
+
// Mirrors the runtime declaration (`ruact-server-functions-runtime/index.d.ts`)
|
|
59
|
+
// — `reference` is the widened accessor shape (Story 13.4), `T` the return type.
|
|
60
|
+
export function useQuery<T = unknown>(
|
|
61
|
+
reference: (...args: never[]) => Promise<unknown>,
|
|
62
|
+
params?: Record<string, unknown>,
|
|
63
|
+
): { data: T | undefined; loading: boolean; error: unknown };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Story 10.2b — the shadcn `table` primitive (a styled HTML table; no engine).
|
|
67
|
+
// Permissive structural shims (like Button/Badge/DropdownMenu below): the
|
|
68
|
+
// generated List reads `row.<attr>` directly and drives its own `useState` sort,
|
|
69
|
+
// so there is nothing engine-typed left to check here. Story 10.5 installs the
|
|
70
|
+
// real module (`npx shadcn add table`).
|
|
71
|
+
declare module "@/components/ui/table" {
|
|
72
|
+
type Props = { children?: any; [key: string]: any };
|
|
73
|
+
export function Table(props: Props): any;
|
|
74
|
+
export function TableHeader(props: Props): any;
|
|
75
|
+
export function TableBody(props: Props): any;
|
|
76
|
+
export function TableRow(props: Props): any;
|
|
77
|
+
export function TableHead(props: Props): any;
|
|
78
|
+
export function TableCell(props: Props): any;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
declare module "@/components/ui/badge" {
|
|
82
|
+
export function Badge(props: { children?: any; [key: string]: any }): any;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
declare module "@/components/ui/button" {
|
|
86
|
+
export function Button(props: { children?: any; [key: string]: any }): any;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
declare module "@/components/ui/dropdown-menu" {
|
|
90
|
+
type Props = { children?: any; [key: string]: any };
|
|
91
|
+
export function DropdownMenu(props: Props): any;
|
|
92
|
+
export function DropdownMenuTrigger(props: Props): any;
|
|
93
|
+
export function DropdownMenuContent(props: Props): any;
|
|
94
|
+
export function DropdownMenuItem(props: Props): any;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Story 10.4 — the shadcn AlertDialog parts the generated `<Model>DeleteDialog`
|
|
98
|
+
// imports. The controlled `AlertDialog` types `open` + `onOpenChange` precisely
|
|
99
|
+
// (the controlled contract the generated dialog binds); the rest stay permissive
|
|
100
|
+
// structural shims (like Button/Badge above). Story 10.5 installs the real module.
|
|
101
|
+
declare module "@/components/ui/alert-dialog" {
|
|
102
|
+
type Props = { children?: any; [key: string]: any };
|
|
103
|
+
export function AlertDialog(props: {
|
|
104
|
+
open?: boolean;
|
|
105
|
+
onOpenChange?: (open: boolean) => void;
|
|
106
|
+
[key: string]: any;
|
|
107
|
+
}): any;
|
|
108
|
+
export function AlertDialogTrigger(props: Props): any;
|
|
109
|
+
export function AlertDialogContent(props: Props): any;
|
|
110
|
+
export function AlertDialogHeader(props: Props): any;
|
|
111
|
+
export function AlertDialogFooter(props: Props): any;
|
|
112
|
+
export function AlertDialogTitle(props: Props): any;
|
|
113
|
+
export function AlertDialogDescription(props: Props): any;
|
|
114
|
+
export function AlertDialogCancel(props: Props): any;
|
|
115
|
+
export function AlertDialogAction(props: Props): any;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Story 10.3 — the shadcn Form primitives the generated `<Model>Form.tsx` imports.
|
|
119
|
+
// Permissive structural shims (like Button/Badge above): the point is the
|
|
120
|
+
// generated module's OWN type-safety (controlled state, the typed action
|
|
121
|
+
// accessors, the `initial` prop + `{ id; label }[]` options props), not
|
|
122
|
+
// re-testing upstream shadcn. Story 10.5 installs the real modules.
|
|
123
|
+
declare module "@/components/ui/label" {
|
|
124
|
+
export function Label(props: { children?: any; [key: string]: any }): any;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Input/Textarea/Switch/Select type the value + change handler precisely (the
|
|
128
|
+
// rest of the props stay permissive) so the isolated typecheck actually proves
|
|
129
|
+
// the generated Form binds STRING state to string controls and a boolean to the
|
|
130
|
+
// Switch — the controlled-state contract, not just import resolution.
|
|
131
|
+
declare module "@/components/ui/input" {
|
|
132
|
+
export function Input(props: {
|
|
133
|
+
value?: string;
|
|
134
|
+
onChange?: (event: { target: { value: string } }) => void;
|
|
135
|
+
[key: string]: any;
|
|
136
|
+
}): any;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
declare module "@/components/ui/textarea" {
|
|
140
|
+
export function Textarea(props: {
|
|
141
|
+
value?: string;
|
|
142
|
+
onChange?: (event: { target: { value: string } }) => void;
|
|
143
|
+
[key: string]: any;
|
|
144
|
+
}): any;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
declare module "@/components/ui/switch" {
|
|
148
|
+
export function Switch(props: {
|
|
149
|
+
checked?: boolean;
|
|
150
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
151
|
+
[key: string]: any;
|
|
152
|
+
}): any;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
declare module "@/components/ui/select" {
|
|
156
|
+
type Props = { children?: any; [key: string]: any };
|
|
157
|
+
export function Select(props: {
|
|
158
|
+
value?: string;
|
|
159
|
+
onValueChange?: (value: string) => void;
|
|
160
|
+
[key: string]: any;
|
|
161
|
+
}): any;
|
|
162
|
+
export function SelectContent(props: Props): any;
|
|
163
|
+
export function SelectItem(props: Props): any;
|
|
164
|
+
export function SelectTrigger(props: Props): any;
|
|
165
|
+
export function SelectValue(props: Props): any;
|
|
166
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Story 13.4 (AC6) — compile-time proof that the codegen-emitted typed query
|
|
2
|
+
// `params` signature is ENFORCED by `tsc`. This file mirrors the accessor shape
|
|
3
|
+
// emitted by `renderQueryExportV2` for a query declaring `def search_users(term:,
|
|
4
|
+
// limit: 10)` (see the byte-exact assertion in `server-functions-codegen.test.mjs`
|
|
5
|
+
// → "Story 13.4 … typed query params render"). If codegen drifts, that parity
|
|
6
|
+
// test reddens; if the emitted type is too loose, the `@ts-expect-error` lines
|
|
7
|
+
// below become "unused directive" tsc errors (TS2578) — either way CI catches it.
|
|
8
|
+
//
|
|
9
|
+
// `@ts-expect-error` is self-checking: it is itself an error UNLESS the next line
|
|
10
|
+
// genuinely fails to compile, so a clean call MUST type-check and every illegal
|
|
11
|
+
// call MUST fail.
|
|
12
|
+
|
|
13
|
+
// The emitted value type for a typed query param: the FR88 wire union.
|
|
14
|
+
type Wire = string | number | boolean | null;
|
|
15
|
+
|
|
16
|
+
// Mirror of the emitted accessor: `term` required, `limit` optional.
|
|
17
|
+
declare const searchUsers: (params: {
|
|
18
|
+
term: Wire;
|
|
19
|
+
limit?: Wire;
|
|
20
|
+
}) => Promise<unknown>;
|
|
21
|
+
|
|
22
|
+
// ── Clean calls — MUST type-check ───────────────────────────────────────────
|
|
23
|
+
void searchUsers({ term: "ada" });
|
|
24
|
+
void searchUsers({ term: "ada", limit: 10 });
|
|
25
|
+
void searchUsers({ term: 42, limit: true });
|
|
26
|
+
void searchUsers({ term: null });
|
|
27
|
+
|
|
28
|
+
// ── Missing required param — MUST error ─────────────────────────────────────
|
|
29
|
+
// @ts-expect-error — `term` is required
|
|
30
|
+
void searchUsers({ limit: 10 });
|
|
31
|
+
|
|
32
|
+
// @ts-expect-error — required param object cannot be omitted entirely
|
|
33
|
+
void searchUsers();
|
|
34
|
+
|
|
35
|
+
// ── Unknown key — MUST error (excess property check) ────────────────────────
|
|
36
|
+
// @ts-expect-error — `bogus` is not a declared param
|
|
37
|
+
void searchUsers({ term: "ada", bogus: 1 });
|
|
38
|
+
|
|
39
|
+
// ── Wrong-typed value — MUST error (outside the wire union) ──────────────────
|
|
40
|
+
// @ts-expect-error — an object is not assignable to the wire union
|
|
41
|
+
void searchUsers({ term: { nested: true } });
|
|
42
|
+
|
|
43
|
+
// @ts-expect-error — an array is not assignable to the wire union
|
|
44
|
+
void searchUsers({ term: [1, 2, 3] });
|
|
45
|
+
|
|
46
|
+
// ── Wrong arity — MUST error ────────────────────────────────────────────────
|
|
47
|
+
// @ts-expect-error — the accessor takes exactly one argument
|
|
48
|
+
void searchUsers({ term: "ada" }, "extra");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Story 13.4 (AC3 / Task 6) — proves a codegen-emitted TYPED query accessor is
|
|
2
|
+
// accepted by `useQuery` without a cast (the dev⇄Codex round-1 regression: the
|
|
3
|
+
// pre-13.4 `useQuery` `reference` param was `(params?: Record<string, unknown>)`,
|
|
4
|
+
// which a typed `(params: { term: Wire }) => Promise<unknown>` accessor is NOT
|
|
5
|
+
// assignable to under `strict`). Also confirms the no-kwargs accessor and the
|
|
6
|
+
// return-type generic still work.
|
|
7
|
+
|
|
8
|
+
import { useQuery } from "ruact/server-functions-runtime";
|
|
9
|
+
|
|
10
|
+
type Wire = string | number | boolean | null;
|
|
11
|
+
|
|
12
|
+
// Mirror of the emitted accessors:
|
|
13
|
+
declare const searchUsers: (params: { term: Wire; limit?: Wire }) => Promise<unknown>;
|
|
14
|
+
declare const categories: () => Promise<unknown>;
|
|
15
|
+
|
|
16
|
+
// Typed query ref + params — MUST type-check.
|
|
17
|
+
void useQuery(searchUsers, { term: "ada" });
|
|
18
|
+
void useQuery(searchUsers, { term: "ada", limit: 25 });
|
|
19
|
+
|
|
20
|
+
// No-kwargs query ref — MUST type-check (with and without the return generic).
|
|
21
|
+
void useQuery(categories);
|
|
22
|
+
void useQuery<{ id: number }[]>(categories);
|
|
23
|
+
|
|
24
|
+
// Return-type generic still flows.
|
|
25
|
+
const r = useQuery<{ id: number }[]>(searchUsers, { term: "ada" });
|
|
26
|
+
void r.data;
|
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.
|
|
4
|
+
version: 0.0.6
|
|
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-
|
|
11
|
+
date: 2026-06-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -46,11 +46,32 @@ 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/
|
|
49
|
+
- lib/generators/ruact/install/templates/Procfile.dev.tt
|
|
50
|
+
- lib/generators/ruact/install/templates/dev.tt
|
|
50
51
|
- lib/generators/ruact/install/templates/initializer.rb.tt
|
|
52
|
+
- lib/generators/ruact/install/templates/package.json.tt
|
|
51
53
|
- lib/generators/ruact/install/templates/vite.config.js.tt
|
|
54
|
+
- lib/generators/ruact/scaffold/scaffold_attribute.rb
|
|
55
|
+
- lib/generators/ruact/scaffold/scaffold_form_helpers.rb
|
|
56
|
+
- lib/generators/ruact/scaffold/scaffold_generator.rb
|
|
57
|
+
- lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb
|
|
58
|
+
- lib/generators/ruact/scaffold/templates/components/DeleteDialog.tsx.tt
|
|
59
|
+
- lib/generators/ruact/scaffold/templates/components/Form.tsx.tt
|
|
60
|
+
- lib/generators/ruact/scaffold/templates/components/List.tsx.tt
|
|
61
|
+
- lib/generators/ruact/scaffold/templates/components/agnostic/DeleteDialog.tsx.tt
|
|
62
|
+
- lib/generators/ruact/scaffold/templates/components/agnostic/Form.tsx.tt
|
|
63
|
+
- lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt
|
|
64
|
+
- lib/generators/ruact/scaffold/templates/controller.rb.tt
|
|
65
|
+
- lib/generators/ruact/scaffold/templates/queries/application_query.rb.tt
|
|
66
|
+
- lib/generators/ruact/scaffold/templates/queries/query.rb.tt
|
|
67
|
+
- lib/generators/ruact/scaffold/templates/request_spec.rb.tt
|
|
68
|
+
- lib/generators/ruact/scaffold/templates/views/edit.html.erb.tt
|
|
69
|
+
- lib/generators/ruact/scaffold/templates/views/index.html.erb.tt
|
|
70
|
+
- lib/generators/ruact/scaffold/templates/views/new.html.erb.tt
|
|
71
|
+
- lib/generators/ruact/scaffold/templates/views/show.html.erb.tt
|
|
52
72
|
- lib/ruact.rb
|
|
53
73
|
- lib/ruact/client_manifest.rb
|
|
74
|
+
- lib/ruact/component_contract.rb
|
|
54
75
|
- lib/ruact/configuration.rb
|
|
55
76
|
- lib/ruact/controller.rb
|
|
56
77
|
- lib/ruact/doctor.rb
|
|
@@ -71,13 +92,12 @@ files:
|
|
|
71
92
|
- lib/ruact/routing.rb
|
|
72
93
|
- lib/ruact/serializable.rb
|
|
73
94
|
- lib/ruact/server.rb
|
|
74
|
-
- lib/ruact/server_action.rb
|
|
75
95
|
- lib/ruact/server_functions.rb
|
|
76
96
|
- lib/ruact/server_functions/backtrace_cleaner.rb
|
|
77
97
|
- lib/ruact/server_functions/bucket_two_payload.rb
|
|
78
98
|
- lib/ruact/server_functions/codegen.rb
|
|
79
99
|
- lib/ruact/server_functions/codegen_v2.rb
|
|
80
|
-
- lib/ruact/server_functions/
|
|
100
|
+
- lib/ruact/server_functions/codegen_v2_query_params.rb
|
|
81
101
|
- lib/ruact/server_functions/error_payload.rb
|
|
82
102
|
- lib/ruact/server_functions/error_rendering.rb
|
|
83
103
|
- lib/ruact/server_functions/error_suggestion.rb
|
|
@@ -85,13 +105,13 @@ files:
|
|
|
85
105
|
- lib/ruact/server_functions/query_context.rb
|
|
86
106
|
- lib/ruact/server_functions/query_dispatch.rb
|
|
87
107
|
- lib/ruact/server_functions/query_source.rb
|
|
88
|
-
- lib/ruact/server_functions/registry.rb
|
|
89
|
-
- lib/ruact/server_functions/registry_entry.rb
|
|
90
108
|
- lib/ruact/server_functions/route_source.rb
|
|
91
109
|
- lib/ruact/server_functions/snapshot.rb
|
|
92
110
|
- lib/ruact/server_functions/snapshot_writer.rb
|
|
93
|
-
- lib/ruact/server_functions/
|
|
94
|
-
- lib/ruact/
|
|
111
|
+
- lib/ruact/server_functions/validation_errors.rb
|
|
112
|
+
- lib/ruact/signed_references.rb
|
|
113
|
+
- lib/ruact/string_distance.rb
|
|
114
|
+
- lib/ruact/validation_errors_collector.rb
|
|
95
115
|
- lib/ruact/version.rb
|
|
96
116
|
- lib/ruact/view_helper.rb
|
|
97
117
|
- lib/rubocop/cop/ruact.rb
|
|
@@ -125,7 +145,9 @@ files:
|
|
|
125
145
|
- spec/fixtures/flight/string_dollar_escape.txt
|
|
126
146
|
- spec/fixtures/flight/undefined.txt
|
|
127
147
|
- spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb
|
|
148
|
+
- spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb
|
|
128
149
|
- spec/ruact/client_manifest_spec.rb
|
|
150
|
+
- spec/ruact/component_contract_spec.rb
|
|
129
151
|
- spec/ruact/configuration_spec.rb
|
|
130
152
|
- spec/ruact/controller_request_spec.rb
|
|
131
153
|
- spec/ruact/controller_spec.rb
|
|
@@ -143,14 +165,13 @@ files:
|
|
|
143
165
|
- spec/ruact/render_context_spec.rb
|
|
144
166
|
- spec/ruact/render_pipeline_concurrency_spec.rb
|
|
145
167
|
- spec/ruact/render_pipeline_spec.rb
|
|
168
|
+
- spec/ruact/scaffold_generator_spec.rb
|
|
146
169
|
- spec/ruact/serializable_spec.rb
|
|
147
170
|
- spec/ruact/server_bucket_request_spec.rb
|
|
148
171
|
- spec/ruact/server_function_name_spec.rb
|
|
149
172
|
- spec/ruact/server_functions/backtrace_cleaner_spec.rb
|
|
150
173
|
- spec/ruact/server_functions/bucket_two_payload_spec.rb
|
|
151
174
|
- spec/ruact/server_functions/codegen_spec.rb
|
|
152
|
-
- spec/ruact/server_functions/csrf_request_spec.rb
|
|
153
|
-
- spec/ruact/server_functions/dispatch_request_spec.rb
|
|
154
175
|
- spec/ruact/server_functions/error_payload_spec.rb
|
|
155
176
|
- spec/ruact/server_functions/error_suggestion_spec.rb
|
|
156
177
|
- spec/ruact/server_functions/name_bridge_spec.rb
|
|
@@ -158,16 +179,15 @@ files:
|
|
|
158
179
|
- spec/ruact/server_functions/query_source_spec.rb
|
|
159
180
|
- spec/ruact/server_functions/railtie_integration_spec.rb
|
|
160
181
|
- spec/ruact/server_functions/rake_spec.rb
|
|
161
|
-
- spec/ruact/server_functions/registry_spec.rb
|
|
162
182
|
- spec/ruact/server_functions/route_source_spec.rb
|
|
163
183
|
- spec/ruact/server_functions/snapshot_spec.rb
|
|
164
184
|
- spec/ruact/server_functions/snapshot_writer_spec.rb
|
|
165
|
-
- spec/ruact/server_functions/standalone_action_spec.rb
|
|
166
|
-
- spec/ruact/server_functions/standalone_context_spec.rb
|
|
167
|
-
- spec/ruact/server_functions/standalone_dispatcher_spec.rb
|
|
168
185
|
- spec/ruact/server_rescue_request_spec.rb
|
|
169
186
|
- spec/ruact/server_spec.rb
|
|
170
187
|
- spec/ruact/server_upload_request_spec.rb
|
|
188
|
+
- spec/ruact/signed_references_spec.rb
|
|
189
|
+
- spec/ruact/string_distance_spec.rb
|
|
190
|
+
- spec/ruact/validation_errors_spec.rb
|
|
171
191
|
- spec/ruact/view_helper_spec.rb
|
|
172
192
|
- spec/spec_helper.rb
|
|
173
193
|
- spec/support/fixtures/pixel.png
|
|
@@ -181,11 +201,31 @@ files:
|
|
|
181
201
|
- vendor/javascript/ruact-server-functions-runtime/index.test.mjs
|
|
182
202
|
- vendor/javascript/ruact-server-functions-runtime/package.json
|
|
183
203
|
- vendor/javascript/ruact-server-functions-runtime/usequery.test.mjs
|
|
204
|
+
- vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs
|
|
184
205
|
- vendor/javascript/vite-plugin-ruact/index.js
|
|
206
|
+
- vendor/javascript/vite-plugin-ruact/manifest-contract.test.mjs
|
|
185
207
|
- vendor/javascript/vite-plugin-ruact/package-lock.json
|
|
186
208
|
- vendor/javascript/vite-plugin-ruact/package.json
|
|
209
|
+
- vendor/javascript/vite-plugin-ruact/registry.test.mjs
|
|
210
|
+
- vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx
|
|
211
|
+
- vendor/javascript/vite-plugin-ruact/runtime/flight-client.js
|
|
212
|
+
- vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js
|
|
187
213
|
- vendor/javascript/vite-plugin-ruact/server-functions-codegen.mjs
|
|
188
214
|
- vendor/javascript/vite-plugin-ruact/server-functions-codegen.test.mjs
|
|
215
|
+
- vendor/javascript/vite-plugin-ruact/tsconfig.json
|
|
216
|
+
- vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json
|
|
217
|
+
- vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json
|
|
218
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts
|
|
219
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx
|
|
220
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx
|
|
221
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx
|
|
222
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostDeleteDialog.tsx
|
|
223
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostForm.tsx
|
|
224
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx
|
|
225
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts
|
|
226
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts
|
|
227
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/typed-query.test-d.ts
|
|
228
|
+
- vendor/javascript/vite-plugin-ruact/type-tests/usequery.test-d.ts
|
|
189
229
|
- vendor/javascript/vite-plugin-ruact/vitest.config.mjs
|
|
190
230
|
homepage: https://luizcg.github.io/ruact/
|
|
191
231
|
licenses:
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { createRoot } from 'react-dom/client';
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { createFromFlightPayload } from './flight-client.js';
|
|
4
|
-
import { setupRouter, teardownRouter } from './ruact-router.js';
|
|
5
|
-
|
|
6
|
-
// MODULE_REGISTRY maps react-client-manifest "id" values to component exports.
|
|
7
|
-
// Add each "use client" component here as you create it.
|
|
8
|
-
// Keys must match the "id" field in public/react-client-manifest.json.
|
|
9
|
-
//
|
|
10
|
-
// Example:
|
|
11
|
-
// import { MyButton } from './components/MyButton.jsx';
|
|
12
|
-
// const MODULE_REGISTRY = { '/MyButton.jsx': { MyButton } };
|
|
13
|
-
const MODULE_REGISTRY = {};
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Boot
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
const flightData = globalThis.__FLIGHT_DATA;
|
|
19
|
-
|
|
20
|
-
if (!flightData || flightData.length === 0) {
|
|
21
|
-
// Non-RSC page or Rails server not running — skip hydration.
|
|
22
|
-
const root = document.getElementById('root');
|
|
23
|
-
if (root && root.childNodes.length === 0) {
|
|
24
|
-
root.textContent = '[ruact] No Flight data found — is the Rails server running?';
|
|
25
|
-
}
|
|
26
|
-
} else {
|
|
27
|
-
const payload = flightData.join('');
|
|
28
|
-
|
|
29
|
-
let initialTree;
|
|
30
|
-
try {
|
|
31
|
-
initialTree = createFromFlightPayload(payload, MODULE_REGISTRY);
|
|
32
|
-
} catch (err) {
|
|
33
|
-
console.error('[ruact] Failed to parse Flight payload:', err);
|
|
34
|
-
const root = document.getElementById('root');
|
|
35
|
-
if (root) root.textContent = '[ruact] Error: ' + err.message;
|
|
36
|
-
throw err;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function App() {
|
|
40
|
-
const [tree, setTree] = useState(() => initialTree);
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
setupRouter({ onNavigate: setTree, moduleRegistry: MODULE_REGISTRY });
|
|
44
|
-
return () => teardownRouter();
|
|
45
|
-
}, []);
|
|
46
|
-
|
|
47
|
-
return tree;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
createRoot(document.getElementById('root')).render(<App />);
|
|
51
|
-
}
|
data/lib/ruact/server_action.rb
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ruact
|
|
4
|
-
# Story 8.3 — host module for STANDALONE server actions. The Ruby
|
|
5
|
-
# equivalent of React's `"use server"` directive: a module under
|
|
6
|
-
# `app/server_actions/` that `extend`s {Ruact::ServerAction} and declares
|
|
7
|
-
# one action body per file with the `ruact_action` macro.
|
|
8
|
-
#
|
|
9
|
-
# # app/server_actions/create_post.rb
|
|
10
|
-
# module CreatePost
|
|
11
|
-
# extend Ruact::ServerAction
|
|
12
|
-
#
|
|
13
|
-
# ruact_action :create_post do |params|
|
|
14
|
-
# Post.create!(title: params[:title], body: params[:body])
|
|
15
|
-
# end
|
|
16
|
-
# end
|
|
17
|
-
#
|
|
18
|
-
# The React side cannot tell standalone-hosted actions apart from
|
|
19
|
-
# controller-hosted ones — both export under the same accessor at
|
|
20
|
-
# `app/javascript/.ruact/server-functions.ts` (Story 8.0a codegen).
|
|
21
|
-
#
|
|
22
|
-
# Differences from {Ruact::Controller#ruact_action} (the controller-hosted
|
|
23
|
-
# path from Story 8.1):
|
|
24
|
-
#
|
|
25
|
-
# - NO instance method is defined on the host module. There is no
|
|
26
|
-
# `host.public_send(action_name)` call shape — the dispatcher
|
|
27
|
-
# ({Ruact::ServerFunctions::EndpointController}) detects the standalone
|
|
28
|
-
# host shape and routes through {Ruact::ServerFunctions::StandaloneDispatcher}
|
|
29
|
-
# instead of `host_class.dispatch`. Standalone modules are reachable
|
|
30
|
-
# ONLY through the gem's `POST /__ruact/fn/:name` endpoint; there is
|
|
31
|
-
# no public Ruby surface to invoke them, by design.
|
|
32
|
-
# - NO controller `before_action` chain runs. The dev opts out of the
|
|
33
|
-
# controller-DSL ergonomic by picking the standalone path; the
|
|
34
|
-
# trade-off is no implicit auth/scoping. The dev calls `current_user`
|
|
35
|
-
# (resolved via {Ruact::Configuration#current_user_resolver}) and
|
|
36
|
-
# Pundit / ActionPolicy directly inside the block when needed.
|
|
37
|
-
# - NO `method_added` hook, NO `Thread.current[:__ruact_dispatching]`
|
|
38
|
-
# sentinel. The standalone path has no routing surface that could
|
|
39
|
-
# reach the action body outside of the gem-managed endpoint, so the
|
|
40
|
-
# controller-only security guards are unnecessary (and would be
|
|
41
|
-
# misleading — the block is stored in the registry, not on the
|
|
42
|
-
# module as an instance method).
|
|
43
|
-
#
|
|
44
|
-
# Shared with {Ruact::Controller#ruact_action}:
|
|
45
|
-
#
|
|
46
|
-
# - {Ruact::ServerFunctions::Registry} via `Ruact.action_registry`.
|
|
47
|
-
# Symbols declared by standalone modules and by controller hosts live
|
|
48
|
-
# in the same registry instance; the existing collision detector
|
|
49
|
-
# ({Ruact::ServerFunctions::Registry#detect_collision!}) catches
|
|
50
|
-
# same-symbol-different-host collisions across both host shapes.
|
|
51
|
-
# - The naming-bridge rule ({Ruact::ServerFunctions::NameBridge}) and
|
|
52
|
-
# reserved-identifier sets (`RESERVED_JS_IDENTIFIERS`,
|
|
53
|
-
# `RESERVED_BY_RUACT`).
|
|
54
|
-
# - The block-parameter shape guard (one positional argument, no
|
|
55
|
-
# required keyword arguments) — same regex as Story 8.1 Re-run-5.
|
|
56
|
-
module ServerAction
|
|
57
|
-
# @param symbol [Symbol] the action name; same naming-bridge rule as
|
|
58
|
-
# {Ruact::Controller#ruact_action}.
|
|
59
|
-
# @yield [params] the block runs via `instance_exec` on a fresh
|
|
60
|
-
# {Ruact::ServerFunctions::StandaloneContext} at dispatch time.
|
|
61
|
-
# @return [Ruact::ServerFunctions::RegistryEntry] the entry just registered.
|
|
62
|
-
# @raise [ArgumentError] when +symbol+ is not a Symbol, the block is
|
|
63
|
-
# missing, or the block's parameter shape is rejected.
|
|
64
|
-
# @raise [Ruact::ConfigurationError] when the symbol fails the
|
|
65
|
-
# naming-bridge rule or collides with another `ruact_action` in the
|
|
66
|
-
# registry (mixed-host collisions are caught here too — see AC4).
|
|
67
|
-
def ruact_action(symbol, &block)
|
|
68
|
-
# Story 8.3 review R4 — reject Class hosts loudly. `extend Ruact::ServerAction`
|
|
69
|
-
# on a Class would work at extend time (Class < Module), but the
|
|
70
|
-
# endpoint dispatcher's `standalone_host?` predicate returns false for
|
|
71
|
-
# Class hosts (it requires Module-NOT-Class), and the controller-DSL
|
|
72
|
-
# branch would then try `host_class.dispatch(...)` against a class
|
|
73
|
-
# that has NO Rails dispatch surface — crashing at request time.
|
|
74
|
-
# Catch this at declaration time so the failure is visible at boot.
|
|
75
|
-
if is_a?(Class)
|
|
76
|
-
raise Ruact::ConfigurationError,
|
|
77
|
-
"Ruact::ServerAction is intended for standalone HOST MODULES under " \
|
|
78
|
-
"`app/server_actions/`. You extended it onto #{name || self} which " \
|
|
79
|
-
"is a Class — that's a controller-shape host. For controller-hosted " \
|
|
80
|
-
"actions use `include Ruact::Controller` and declare `ruact_action` " \
|
|
81
|
-
"inside the controller class body; for standalone actions declare a " \
|
|
82
|
-
"`module Foo; extend Ruact::ServerAction; ...; end` instead."
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
unless symbol.is_a?(Symbol)
|
|
86
|
-
raise ArgumentError,
|
|
87
|
-
"ruact_action requires a Symbol argument, got " \
|
|
88
|
-
"#{symbol.inspect} (#{symbol.class}). Use " \
|
|
89
|
-
"`ruact_action :#{symbol}` not `ruact_action #{symbol.inspect}`."
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
unless block
|
|
93
|
-
raise ArgumentError,
|
|
94
|
-
"ruact_action :#{symbol} (standalone) requires a block — declare the " \
|
|
95
|
-
"implementation with `ruact_action :#{symbol} do |params| ... end`"
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Mirror the block-parameter shape guard from Story 8.1 Re-run-5
|
|
99
|
-
# (controller.rb:125-137). The standalone dispatcher invokes the
|
|
100
|
-
# block with one positional argument (the params shadow); blocks
|
|
101
|
-
# with wrong arity / required kwargs would crash at dispatch time.
|
|
102
|
-
req_count = block.parameters.count { |kind, _| kind == :req }
|
|
103
|
-
opt_count = block.parameters.count { |kind, _| kind == :opt }
|
|
104
|
-
rest_count = block.parameters.count { |kind, _| kind == :rest }
|
|
105
|
-
named_positional = req_count + opt_count
|
|
106
|
-
positional_total = named_positional + rest_count
|
|
107
|
-
has_required_kwarg = block.parameters.any? { |kind, _| kind == :keyreq }
|
|
108
|
-
if positional_total.zero? || named_positional > 1 || has_required_kwarg
|
|
109
|
-
raise ArgumentError,
|
|
110
|
-
"ruact_action :#{symbol} (standalone) block must accept exactly one " \
|
|
111
|
-
"positional parameter and no required keyword arguments " \
|
|
112
|
-
"(got parameters=#{block.parameters.inspect}). Use " \
|
|
113
|
-
"`ruact_action :#{symbol} do |params| ... end`."
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# NOTE: no `FRAMEWORK_RESERVED_METHODS` check — standalone modules
|
|
117
|
-
# have no ActionController surface to clobber. NameBridge +
|
|
118
|
-
# RESERVED_JS_IDENTIFIERS + RESERVED_BY_RUACT still fire via the
|
|
119
|
-
# registry's `register` path below.
|
|
120
|
-
#
|
|
121
|
-
# NOTE: no `own_methods` / inherited-helper guard — there is no
|
|
122
|
-
# `define_method` step that could shadow anything; the block lives
|
|
123
|
-
# in the registry, not on the module.
|
|
124
|
-
#
|
|
125
|
-
# NOTE: no `method_added` hook — standalone modules don't define
|
|
126
|
-
# instance methods at all, so a same-name `def` cannot shadow
|
|
127
|
-
# anything (Pitfall #2 in the story spec).
|
|
128
|
-
Ruact.action_registry.register(symbol, kind: :action, controller: self, &block)
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|