-
-
Notifications
You must be signed in to change notification settings - Fork 98
Description
Unlike with other field proxy functions (formFieldProxy() and fieldProxy()), it does not appear to be possible to set the values type through arrayProxy() type parameters. Or rather there does seem to be a way to set the array element type through the FormPathArrays type parameters, however I could not get this to work either. Here are the relevant type definitions from proxies.d.ts:
type ValueErrors = any[];
export type ArrayProxy<T, Path = string, Errors = ValueErrors, ExtraValues = never> = {
path: Path;
values: Writable<(T[] & unknown[]) | ExtraValues>;
errors: Writable<string[] | undefined>;
valueErrors: Writable<Errors>;
};
export declare function arrayProxy<T extends Record<string, unknown>, Path extends FormPathArrays<T, ArrType>, ArrType = any>(superForm: SuperForm<T>, path: Path, options?: {
taint?: TaintOption;
}): ArrayProxy<FormPathType<T, Path> extends (infer U)[] ? U : never, Path>;In any case, what does not seem to be possible is to set non-array (flat) alternative values types. For instance, in my case, I would like values to have the type of FieldValuePrimitive[] | Nullish. The only way to achieve this seems to be through a type assertion that uses the ArrayProxy type which does however accept the ExtraValues parameter. Note that this also requires setting the Errors type parameter (that also gets ignored by the arrayProxy() return type), which feels unnecessary and it should perhaps switch places with ExtraValues. The core issue seems to be that ExtraValues cannot be set through arrayProxy() type parameters and is not inferred from FormPathArrays<T, FieldValue>.
In my case the form data is of type Record<string, Nullish> and uses the following type definitions:
export type FieldValuePrimitive = string | number | boolean | Date; // MySQL data types
export type FieldValue = FieldValuePrimitive | FieldValuePrimitive[];
export type Field = FieldValue | LocalizedProperty;
export type LocalizedProperty<T extends FieldValue = FieldValue> = Record<Locale, T>;
export type FieldNullish = Field | Nullish;Here is a simplified version of my current implementation based on the type assertion (which I'd of course like to avoid):
<script lang="ts" module>
type FieldValue = FieldValuePrimitive[] | Nullish; // this is the type I would like `values` to have
type T = Record<string, FieldNullish>;
type N = FormPathArrays<T, FieldValue>;
</script>
<script
lang="ts"
generics="T extends Record<string, FieldNullish>, N extends FormPathArrays<T, FieldValue>"
>
import type { ArrayProxy, FormPathArrays, SuperForm } from 'sveltekit-superforms';
import { arrayProxy, type FormPathLeaves } from 'sveltekit-superforms/client';
// ...
import type {
FieldNullish,
FieldValuePrimitive,
FormType,
Nullish
} from '$lib/types';
let {
superform,
name,
}: {
superform: SuperForm<T>;
name: N; // while not required by Superforms for JSON data type, it is always required by shadcn and Formsnap
} = $props();
const updateTaints = formType === 'edit' ? true : false;
const { values, errors, valueErrors } = arrayProxy(superform, name, {
taint: updateTaints
}) as ArrayProxy<FieldValuePrimitive, N, unknown[], Nullish>;formFieldProxy() and fieldProxy() can have the value type set through the last type parameter (Type) of those functions:
export type FormFieldProxy<T, Path = string> = {
path: Path;
value: SuperFieldProxy<T>;
errors: Writable<string[] | undefined>;
constraints: Writable<InputConstraint | undefined>;
tainted: Writable<boolean | undefined>;
};
export declare function formFieldProxy<T extends Record<string, unknown>, Path extends FormPathLeaves<T, Type>, Type = any>(superForm: SuperForm<T>, path: Path, options?: ProxyOptions): FormFieldProxy<PathType<Type, T, Path>, Path>;
type SuperFieldProxy<T> = {
subscribe: Readable<T>['subscribe'];
set(this: void, value: T, options?: {
taint?: TaintOption;
}): void;
update(this: void, updater: Updater<T>, options?: {
taint?: TaintOption;
}): void;
};
export type FieldProxy<T> = Writable<T>;
export declare function fieldProxy<T extends Record<string, unknown>, Path extends FormPaths<T, Type>, Type = any>(form: Writable<T> | SuperForm<T>, path: Path, options?: ProxyOptions): FieldProxy<PathType<Type, T, Path>>; They also both use PathType to infer the value type from the Path type parameter, unlike arrayProxy() which uses FormPathType instead. Since PathType accepts that trailing Type type parameter, the whole complicated FormPathType<T, Path> is skipped as long as the Type type parameter is passed to the function and is not any. (I still avoid doing so in my use of formFieldProxy() and fieldProxy().)
There's probably a good reason why FormPathType is used instead of PathType, however the current implementation also does not allow a non-array element type to be passed to ProxyArray since they are excluded by FormPathType<T, Path> extends (infer U)[] ? U : never even if FormPathType<T, Path> were to infer the desired values type from FormPathArrays<T, FieldValue>.
While arrayProxy() does have a trailing type parameter ArrType, this does not get passed to FormPathType, which also does not accept such an "override" that would skip the FormPathType<T, Path> logic for inferring the values type.
The pattern suggested by the documentation of using satisfies followed by the return type of the proxy function that takes in the desired value type works for formFieldProxy() and fieldProxy() but not arrayProxy(). I also tried the solution used here (including changing types so that there are no non-array ExtraValues) but that doesn't work either.
Sorry for the long explanation but I wanted to make sure I give enough background on how I'm approaching the issue. I don't an MRE is needed given the exhaustive explanation (and to me already obvious limitations) but can still create it if you think it's necessary.