@voltro/client 0.50.1 → 0.52.0

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.
package/dist/index.d.ts CHANGED
@@ -961,6 +961,22 @@ export declare type FilterKind = 'text' | 'select' | 'multi-select' | 'number-ra
961
961
  * input field → one FilterDescriptor. Pure. */
962
962
  export declare const filtersFromSchema: (schema: Schema.Schema.Any) => ReadonlyArray<FilterDescriptor>;
963
963
 
964
+ /** DOM id of the JSON script the 422 re-render embeds (survives the
965
+ * `interactive:'none'` strip — it is neither `type="module"` nor `src`). */
966
+ export declare const FORM_FLASH_SCRIPT_ID = "__voltro_form_flash__";
967
+
968
+ /** Hidden field carrying the form's instance key (multi-form pages). */
969
+ export declare const FORM_KEY_FIELD = "__voltro_form";
970
+
971
+ /** URL prefix the web listener mounts the no-JS endpoint under. */
972
+ export declare const FORM_PATH_PREFIX = "/form/";
973
+
974
+ /** Hidden field carrying the declared success-redirect path. */
975
+ export declare const FORM_REDIRECT_FIELD = "__voltro_redirect";
976
+
977
+ /** The path a native `<AutoForm>` POST goes to. */
978
+ export declare const formActionPath: (mutationTag: string) => string;
979
+
964
980
  export declare interface FormBinding<Input, Output> {
965
981
  /** Ordered, render-agnostic field list derived from the input schema. */
966
982
  readonly fields: ReadonlyArray<FieldDescriptor>;
@@ -972,6 +988,9 @@ export declare interface FormBinding<Input, Output> {
972
988
  readonly pending: boolean;
973
989
  /** The mutation's typed failure, if the last submit threw. */
974
990
  readonly submitError: unknown | undefined;
991
+ /** A form-level (non-field) error carried in from a failed no-JS POST —
992
+ * the RPC refused after valid input. Cleared by the next submit/reset. */
993
+ readonly formError: string | undefined;
975
994
  readonly data: Output | undefined;
976
995
  readonly setValue: (name: string, value: unknown) => void;
977
996
  readonly setValues: (patch: Partial<Input>) => void;
@@ -981,6 +1000,50 @@ export declare interface FormBinding<Input, Output> {
981
1000
  readonly submit: () => Promise<Output | undefined>;
982
1001
  }
983
1002
 
1003
+ /**
1004
+ * Convert posted form entries into the input object the mutation's schema
1005
+ * decodes — the shape `validateFields` (and the RPC payload) expects.
1006
+ *
1007
+ * Mapping rules (the documented contract):
1008
+ * - **checkbox/switch**: present → `true`, absent → `false`. A native POST
1009
+ * omits an unchecked checkbox entirely, so absence is data, not a gap.
1010
+ * - **number**: `Number(raw)`; `''` → the field is omitted (`undefined`), so
1011
+ * an optional number stays absent and a required one reports "missing"
1012
+ * instead of silently becoming `0`.
1013
+ * - **date/datetime**: the string passes through (the schema's encoded side);
1014
+ * `''` → omitted, same reasoning as number.
1015
+ * - **arrays** (multi-select): `getAll` semantics — every entry under the key,
1016
+ * items converted per the array's item type (number items → `Number`).
1017
+ * - **unknown keys are dropped**: only names the schema declares are mapped,
1018
+ * so protocol fields (`__voltro_form`, …) never reach the input object —
1019
+ * the server rejects undeclared fields, and a hidden bookkeeping field must
1020
+ * not turn every no-JS submit into a validation error.
1021
+ */
1022
+ export declare const formDataToInput: (schema: Schema.Schema.Any, entries: Iterable<FormEntry>) => Record<string, unknown>;
1023
+
1024
+ /** One posted entry. File entries must be filtered out by the caller —
1025
+ * uploads are a declared limit of the no-JS path. */
1026
+ export declare type FormEntry = readonly [name: string, value: string];
1027
+
1028
+ /**
1029
+ * The error/values payload a failed no-JS form POST carries back into the
1030
+ * 422 re-render. Keyed by `formKey` so a page with several forms re-fills
1031
+ * only the one that was submitted. The SSR render passes it through the
1032
+ * server-request context; the client reads the same payload back off the
1033
+ * `#__voltro_form_flash__` JSON script, so a late-hydrating page shows the
1034
+ * identical state (no mismatch, no vanished errors).
1035
+ */
1036
+ export declare interface FormFlashPayload {
1037
+ /** The submitted form's key — `<AutoForm formKey>` or its mutation tag. */
1038
+ readonly formKey: string;
1039
+ /** The mapped input values as posted (pre-decode), to re-fill the fields. */
1040
+ readonly values: Readonly<Record<string, unknown>>;
1041
+ /** First error per field — the `validateFields` shape. */
1042
+ readonly errors: Readonly<Record<string, string>>;
1043
+ /** A non-field error (the RPC refused after valid input: guard, server). */
1044
+ readonly formError?: string;
1045
+ }
1046
+
984
1047
  export declare interface FrameworkRuntimes {
985
1048
  /** Look up an api handle (runtime + cache) by name. Throws if no such api was mounted. */
986
1049
  readonly get: (name: string) => ApiHandle;
@@ -1010,6 +1073,32 @@ export declare const getMutationNotifier: () => MutationNotifier | undefined;
1010
1073
  /** Read the current feed. Newest-first; up to MAX_MUTATIONS entries. */
1011
1074
  export declare const getMutations: () => ReadonlyArray<MutationEvent>;
1012
1075
 
1076
+ /**
1077
+ * A process-wide singleton React context, pinned on the global symbol registry.
1078
+ *
1079
+ * WHY this exists: the SSG prerender (`voltro build`) loads the framework
1080
+ * through TWO separate module instances — the renderer comes in via Vite's
1081
+ * `ssrLoadModule('@voltro/web/ssr')` (Vite-transformed) while a page's own
1082
+ * framework import is externalised to Node — so a plain `createContext()`
1083
+ * in a shared module is evaluated twice and yields two DISTINCT context
1084
+ * objects. The `<Router>` provider then holds one while the page's
1085
+ * `useLocation()` reads the other, which throws "Router hooks must be used
1086
+ * inside <Router>." The same duplicate-instance hazard shows up wherever a
1087
+ * deployment ends up with two copies of a package (the classic dual-package
1088
+ * hazard, monorepo hoisting quirks, separate SSR vs client bundles).
1089
+ *
1090
+ * Resolving every context through `Symbol.for(...)` on `globalThis` makes all
1091
+ * module copies share ONE instance, so provider and consumer can never diverge.
1092
+ * A duplicated copy of THIS helper is harmless — both copies hit the same
1093
+ * global registry entry.
1094
+ *
1095
+ * Lives in @voltro/client (the lowest React-carrying package) so both
1096
+ * @voltro/web and @voltro/ui reach it without a cycle; the key prefix keeps
1097
+ * its historical spelling on purpose — a mixed dist/src world must resolve
1098
+ * to the same registry entries.
1099
+ */
1100
+ export declare const globalContext: <T>(key: string, initial: T) => Context<T>;
1101
+
1013
1102
  /** Is this the framework's `Unauthenticated`? Matched on `_tag`, the wire
1014
1103
  * contract, rather than on an instance — the error crosses a package boundary
1015
1104
  * and may be re-created by the decoder. */
@@ -1832,6 +1921,29 @@ export declare type SequenceResult<Ctx> = {
1832
1921
  }>;
1833
1922
  };
1834
1923
 
1924
+ export declare const ServerRequestContext: Context<ServerRequestContextValue | null>;
1925
+
1926
+ export declare interface ServerRequestContextValue {
1927
+ readonly cookies: Readonly<Record<string, string>>;
1928
+ readonly headers: Readonly<Record<string, string>>;
1929
+ /** Raw request URL as it came off the wire (path + query). Useful
1930
+ * for SSR pages that need to read `?q=…` style search params
1931
+ * without touching anything client-only. Empty string for build-
1932
+ * time SSG renders where there is no incoming request. */
1933
+ readonly url: string;
1934
+ /** Present ONLY on the 422 re-render of a failed no-JS form POST:
1935
+ * the submitted values + field errors, keyed by formKey. Travels on
1936
+ * the EXISTING request context deliberately — a dedicated provider
1937
+ * would add a fiber fork the client boot does not have, and every
1938
+ * ancestor arity difference shifts every useId in the app. */
1939
+ readonly formFlash?: FormFlashPayload;
1940
+ }
1941
+
1942
+ export declare const ServerRequestProvider: ({ value, children, }: {
1943
+ readonly value: ServerRequestContextValue;
1944
+ readonly children: ReactNode;
1945
+ }) => ReactNode;
1946
+
1835
1947
  /** Register (or clear) the app-wide notifier that `notify:` routes to. Call once
1836
1948
  * at boot, next to your toast provider. */
1837
1949
  export declare const setMutationNotifier: (notifier: MutationNotifier | undefined) => void;
@@ -3048,8 +3160,27 @@ export declare interface UseFormBindingOptions<Input> {
3048
3160
  readonly schema?: Schema.Schema.Any;
3049
3161
  /** Initial field values. */
3050
3162
  readonly defaults?: Partial<Input>;
3163
+ /** The failed no-JS POST's payload for THIS form (`useFormFlash` from
3164
+ * @voltro/web resolves it, SSR and client alike). When present it seeds
3165
+ * the initial values + field errors, so the 422 re-render shows the
3166
+ * submitted state server-side and hydrates to the identical state. */
3167
+ readonly flash?: FormFlashPayload | undefined;
3051
3168
  }
3052
3169
 
3170
+ /**
3171
+ * The failed-no-JS-POST payload for ONE form, or `undefined`.
3172
+ *
3173
+ * On the server (the 422 re-render) it comes off the request context; on the
3174
+ * client it is read back from the `#__voltro_form_flash__` JSON script the
3175
+ * same response embedded. Both carry the SAME payload, so a page that loads
3176
+ * its bundle after a native submit hydrates to exactly the server-rendered
3177
+ * state — errors visible, values filled, no mismatch.
3178
+ *
3179
+ * Keyed: only the form whose `formKey` was submitted receives the payload —
3180
+ * two `<AutoForm>`s on one page re-fill only the one that POSTed.
3181
+ */
3182
+ export declare const useFormFlash: (formKey: string) => FormFlashPayload | undefined;
3183
+
3053
3184
  /** The field shape a `<AutoForm mutation=…>` will render — from the mutation's
3054
3185
  * input Schema. Use it to render a matching skeleton while anything the form
3055
3186
  * depends on is still loading. */
@@ -3198,6 +3329,8 @@ export declare interface UseSequenceResult {
3198
3329
  readonly failedStep: string | undefined;
3199
3330
  }
3200
3331
 
3332
+ export declare const useServerRequest: () => ServerRequestContextValue | null;
3333
+
3201
3334
  export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input: Readonly<Record<string, unknown>>, options: SubscriptionOptions<T> & {
3202
3335
  readonly initialSnapshot: T;
3203
3336
  }): SubscriptionStateWithFallback<T>;