New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

sveltekit-superforms

Package Overview
Dependencies
Maintainers
1
Versions
214
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sveltekit-superforms - npm Package Compare versions

Comparing version 2.8.1 to 2.9.0

81

dist/client/form.d.ts
/// <reference types="svelte" />
/// <reference types="@sveltejs/kit" />
import type { FormOptions } from './superForm.js';

@@ -21,80 +20,2 @@ import type { Writable } from 'svelte/store';

};
export declare const scrollToFirstError: <T extends Record<string, unknown>, M>(Form: HTMLFormElement, options: Partial<{
id: string;
applyAction: boolean;
invalidateAll: boolean | "force";
resetForm: boolean | (() => boolean);
scrollToError: boolean | "auto" | ScrollIntoViewOptions | "smooth" | "off";
autoFocusOnError: boolean | "detect";
errorSelector: string;
selectErrorText: boolean;
stickyNavbar: string;
taintedMessage: string | boolean | (() => import("../utils.js").MaybePromise<boolean>) | null;
SPA: true | {
failStatus?: number | undefined;
};
onSubmit: (input: {
action: URL;
formData: FormData;
formElement: HTMLFormElement;
controller: AbortController;
submitter: HTMLElement | null;
cancel(): void;
} & {
jsonData: (data: Record<string, unknown>) => void;
validators: (validators: false | import("../adapters/adapters.js").ValidationAdapter<Partial<T>, Record<string, unknown>>) => void;
}) => unknown;
onResult: (event: {
result: import("@sveltejs/kit").ActionResult;
formEl: HTMLFormElement;
formElement: HTMLFormElement;
cancel: () => void;
}) => unknown;
onUpdate: (event: {
form: import("./index.js").SuperValidated<T, M, T>;
formEl: HTMLFormElement;
formElement: HTMLFormElement;
cancel: () => void;
}) => unknown;
onUpdated: (event: {
form: Readonly<import("./index.js").SuperValidated<T, M, T>>;
}) => unknown;
onError: "apply" | ((event: {
result: {
type: "error";
status?: number | undefined;
error: App.Error;
};
}) => unknown);
onChange: (event: import("./superForm.js").ChangeEvent<T>) => void;
dataType: "form" | "json";
jsonChunkSize: number;
validators: import("../adapters/adapters.js").ClientValidationAdapter<Partial<T>, Record<string, unknown>> | (false | "clear" | import("../adapters/adapters.js").ValidationAdapter<Partial<T>, Record<string, unknown>>);
validationMethod: "auto" | "oninput" | "onblur" | "onsubmit" | "submit-only";
customValidity: boolean;
clearOnSubmit: "message" | "none" | "errors" | "errors-and-message";
delayMs: number;
timeoutMs: number;
multipleSubmits: "abort" | "prevent" | "allow";
syncFlashMessage?: boolean | undefined;
flashMessage: {
module: {
getFlash(page: import("svelte/store").Readable<import("@sveltejs/kit").Page<Record<string, string>, string | null>>): Writable<any>;
updateFlash(page: import("svelte/store").Readable<import("@sveltejs/kit").Page<Record<string, string>, string | null>>, update?: (() => Promise<void>) | undefined): Promise<boolean>;
};
onError?: ((event: {
result: {
type: "error";
status?: number | undefined;
error: App.Error;
};
flashMessage: Writable<any>;
}) => unknown) | undefined;
cookiePath?: string | undefined;
cookieName?: string | undefined;
};
warnings: {
duplicateId?: boolean | undefined;
};
legacy: boolean;
}>) => Promise<void>;
export declare const scrollToFirstError: <T extends Record<string, unknown>, M>(Form: HTMLFormElement, options: FormOptions<T, M>) => Promise<void>;

16

dist/client/superForm.d.ts

@@ -6,3 +6,3 @@ /// <reference types="svelte" />

import { type FormPathType, type FormPath, type FormPathLeaves } from '../stringPath.js';
import { enhance } from '$app/forms';
import { enhance as kitEnhance } from '$app/forms';
import type { ValidationErrors } from '../superValidate.js';

@@ -37,3 +37,3 @@ import type { MaybePromise } from '../utils.js';

failStatus?: number;
};
} | string;
onSubmit: (input: Parameters<SubmitFunction>[0] & {

@@ -162,6 +162,6 @@ /**

options: T extends T ? FormOptions<T, M> : never;
enhance: (el: HTMLFormElement, events?: SuperFormEvents<T, M>) => ReturnType<typeof enhance>;
isTainted: (path?: FormPath<T> | TaintedFields<T> | boolean) => boolean;
enhance: (el: HTMLFormElement, events?: SuperFormEvents<T, M>) => ReturnType<typeof kitEnhance>;
isTainted: (path?: T extends T ? FormPath<T> | TaintedFields<T> | boolean : never) => boolean;
reset: (options?: ResetOptions<T>) => void;
submit: (submitter?: HTMLElement | null) => void;
submit: (submitter?: HTMLElement | Event | EventTarget | null) => void;
capture: Capture<T, M>;

@@ -198,5 +198,5 @@ restore: T extends T ? Restore<T, M> : never;

* Initializes a SvelteKit form, for convenient handling of values, errors and sumbitting data.
* @param {SuperValidated} form Usually data.form from PageData.
* @param {FormOptions} options Configuration for the form.
* @returns {SuperForm} An object with properties for the form.
* @param {SuperValidated} form Usually data.form from PageData or defaults, but can also be an object with default values, but then constraints won't be available.
* @param {FormOptions} formOptions Configuration for the form.
* @returns {SuperForm} A SuperForm object that can be used in a Svelte component.
* @DCI-context

@@ -203,0 +203,0 @@ */

@@ -11,3 +11,3 @@ import { derived, get, readonly, writable } from 'svelte/store';

import { cancelFlash, shouldSyncFlash } from './flash.js';
import { applyAction, enhance } from '$app/forms';
import { applyAction, enhance as kitEnhance } from '$app/forms';
import { setCustomValidityForm, updateCustomValidity } from './customValidity.js';

@@ -18,6 +18,7 @@ import { inputInfo } from './elements.js';

import { fieldProxy } from './proxies.js';
import { shapeFromObject } from '../jsonSchema/schemaShape.js';
const formIds = new WeakMap();
const initialForms = new WeakMap();
const defaultOnError = (event) => {
console.warn('Unhandled Superform error, use onError event to handle it:', event.result.error);
console.warn('Unhandled error caught by Superforms, use onError event to handle it:', event.result.error);
};

@@ -83,5 +84,5 @@ const defaultFormOptions = {

* Initializes a SvelteKit form, for convenient handling of values, errors and sumbitting data.
* @param {SuperValidated} form Usually data.form from PageData.
* @param {FormOptions} options Configuration for the form.
* @returns {SuperForm} An object with properties for the form.
* @param {SuperValidated} form Usually data.form from PageData or defaults, but can also be an object with default values, but then constraints won't be available.
* @param {FormOptions} formOptions Configuration for the form.
* @returns {SuperForm} A SuperForm object that can be used in a Svelte component.
* @DCI-context

@@ -106,2 +107,9 @@ */

}
if (typeof options.SPA === 'string') {
// SPA action mode is "passive", no page updates are made.
if (options.invalidateAll === undefined)
options.invalidateAll = false;
if (options.applyAction === undefined)
options.applyAction = false;
}
initialValidator = options.validators;

@@ -112,3 +120,4 @@ options = {

};
if (options.SPA && options.validators === undefined) {
if ((options.SPA === true || typeof options.SPA === 'object') &&
options.validators === undefined) {
console.warn('No validators set for superForm in SPA mode. ' +

@@ -124,7 +133,8 @@ 'Add a validation adapter to the validators option, or set it to false to disable this warning.');

form = {
id: options.id ?? '',
id: options.id ?? Math.random().toString(36).slice(2, 10),
valid: false,
posted: false,
errors: {},
data: form
data: form,
shape: shapeFromObject(form)
};

@@ -159,6 +169,2 @@ }

initialForm = initialForms.get(form);
if (typeof initialForm.valid !== 'boolean') {
throw new SuperFormError('A non-validation object was passed to superForm. ' +
'It should be an object of type SuperValidated, usually returned from superValidate.');
}
// Detect if a form is posted without JavaScript.

@@ -198,2 +204,3 @@ if (!browser && _currentPage.form && typeof _currentPage.form === 'object') {

formIds.get(_currentPage)?.delete(_initialFormId);
ActionForm_remove();
});

@@ -285,2 +292,5 @@ // Check for nested objects, throw if datatype isn't json

};
function Form_isSPA() {
return options.SPA === true || typeof options.SPA === 'object';
}
async function Form_validate(opts = {}) {

@@ -376,13 +386,20 @@ const dataToValidate = opts.formData ?? Data.form;

}
if (!event || !options.validators || options.validators == 'clear')
return;
let skipValidation = false;
if (!force) {
if (options.validationMethod == 'onsubmit' || options.validationMethod == 'submit-only') {
return;
skipValidation = true;
}
if (options.validationMethod == 'onblur' && event.type == 'input')
return;
if (options.validationMethod == 'oninput' && event.type == 'blur')
return;
else if (options.validationMethod == 'onblur' && event?.type == 'input')
skipValidation = true;
else if (options.validationMethod == 'oninput' && event?.type == 'blur')
skipValidation = true;
}
if (skipValidation || !event || !options.validators || options.validators == 'clear') {
if (event?.paths) {
const formElement = event?.formElement ?? EnhancedForm;
if (formElement)
Form__clearCustomValidity(formElement, event.paths);
}
return;
}
const result = await Form_validate({ adapter });

@@ -398,10 +415,6 @@ // TODO: Add option for always setting result.data?

}
async function Form__displayNewErrors(errors, event, force) {
const { type, immediate, multiple, paths } = event;
const previous = Data.errors;
const output = {};
function Form__clearCustomValidity(formElement, paths) {
const validity = new Map();
const formElement = event.formElement ?? EnhancedForm;
if (options.customValidity && formElement) {
for (const path of event.paths) {
for (const path of paths) {
const name = CSS.escape(mergePath(path));

@@ -416,2 +429,12 @@ const el = formElement.querySelector(`[name="${name}"]`);

}
return validity;
}
async function Form__displayNewErrors(errors, event, force) {
const { type, immediate, multiple, paths } = event;
const previous = Data.errors;
const output = {};
let validity = new Map();
const formElement = event.formElement ?? EnhancedForm;
if (formElement)
validity = Form__clearCustomValidity(formElement, event.paths);
traversePaths(errors, (error) => {

@@ -745,2 +768,23 @@ if (!Array.isArray(error.value))

//#endregion
//#region ActionForm
// SPA action mode
let ActionForm = undefined;
function ActionForm_create(action) {
ActionForm = document.createElement('form');
ActionForm.method = 'POST';
ActionForm.action = action;
superFormEnhance(ActionForm);
document.body.appendChild(ActionForm);
}
function ActionForm_setAction(action) {
if (ActionForm)
ActionForm.action = action;
}
function ActionForm_remove() {
if (ActionForm?.parentElement) {
ActionForm.remove();
ActionForm = undefined;
}
}
//#endregion
const AllErrors = derived(Errors, ($errors) => ($errors ? flattenErrors($errors) : []));

@@ -797,2 +841,3 @@ // Used for options.customValidity to display errors, even if programmatically set

};
///// Store subscriptions ///////////////////////////////////////////////////
if (browser) {

@@ -880,3 +925,390 @@ // Tainted check

}));
if (typeof options.SPA === 'string') {
ActionForm_create(options.SPA);
}
}
/**
* Custom use:enhance that enables all the client-side functionality.
* @param FormElement
* @param events
* @DCI-context
*/
function superFormEnhance(FormElement, events) {
ActionForm_remove();
EnhancedForm = FormElement;
if (events) {
if (events.onError) {
if (options.onError === 'apply') {
throw new SuperFormError('options.onError is set to "apply", cannot add any onError events.');
}
else if (events.onError === 'apply') {
throw new SuperFormError('Cannot add "apply" as onError event in use:enhance.');
}
formEvents.onError.push(events.onError);
}
if (events.onResult)
formEvents.onResult.push(events.onResult);
if (events.onSubmit)
formEvents.onSubmit.push(events.onSubmit);
if (events.onUpdate)
formEvents.onUpdate.push(events.onUpdate);
if (events.onUpdated)
formEvents.onUpdated.push(events.onUpdated);
}
// Now we know that we are enhanced,
// so we can enable the tainted form option.
Tainted_enable();
let lastInputChange;
// TODO: Debounce option?
async function onInput(e) {
const info = inputInfo(e.target);
// Need to wait for immediate updates due to some timing issue
if (info.immediate && !info.file)
await new Promise((r) => setTimeout(r, 0));
lastInputChange = NextChange_paths();
NextChange_additionalEventInformation('input', info.immediate, info.multiple, FormElement, e.target ?? undefined);
}
async function onBlur(e) {
// Avoid triggering client-side validation while submitting
if (Data.submitting)
return;
if (!lastInputChange || NextChange_paths() != lastInputChange) {
return;
}
const info = inputInfo(e.target);
// Need to wait for immediate updates due to some timing issue
if (info.immediate && !info.file)
await new Promise((r) => setTimeout(r, 0));
Form_clientValidation({
paths: lastInputChange,
immediate: info.multiple,
multiple: info.multiple,
type: 'blur',
formElement: FormElement,
target: e.target ?? undefined
});
// Clear input change event, now that the field doesn't have focus anymore.
lastInputChange = undefined;
}
FormElement.addEventListener('focusout', onBlur);
FormElement.addEventListener('input', onInput);
onDestroy(() => {
FormElement.removeEventListener('focusout', onBlur);
FormElement.removeEventListener('input', onInput);
EnhancedForm = undefined;
});
///// SvelteKit enhance function //////////////////////////////////
const htmlForm = HtmlForm(FormElement, { submitting: Submitting, delayed: Delayed, timeout: Timeout }, options);
let currentRequest;
return kitEnhance(FormElement, async (submitParams) => {
let jsonData = undefined;
let validationAdapter = options.validators;
const submit = {
...submitParams,
jsonData(data) {
if (options.dataType !== 'json') {
throw new SuperFormError("options.dataType must be set to 'json' to use jsonData.");
}
jsonData = data;
},
validators(adapter) {
validationAdapter = adapter;
}
};
const _submitCancel = submit.cancel;
let cancelled = false;
function clientValidationResult(validation) {
const validationResult = { ...validation, posted: true };
const status = validationResult.valid
? 200
: (typeof options.SPA === 'boolean' || typeof options.SPA === 'string'
? undefined
: options.SPA?.failStatus) ?? 400;
const data = { form: validationResult };
const result = validationResult.valid
? { type: 'success', status, data }
: { type: 'failure', status, data };
setTimeout(() => validationResponse({ result }), 0);
}
function cancel(opts = {
resetTimers: true
}) {
cancelled = true;
if (opts.resetTimers && htmlForm.isSubmitting()) {
htmlForm.completed({ cancelled });
}
return _submitCancel();
}
submit.cancel = cancel;
if (htmlForm.isSubmitting() && options.multipleSubmits == 'prevent') {
cancel({ resetTimers: false });
}
else {
if (htmlForm.isSubmitting() && options.multipleSubmits == 'abort') {
if (currentRequest)
currentRequest.abort();
}
htmlForm.submitting();
currentRequest = submit.controller;
for (const event of formEvents.onSubmit) {
await event(submit);
}
}
if (cancelled && options.flashMessage)
cancelFlash(options);
if (!cancelled) {
// Client validation
const noValidate = !Form_isSPA() &&
(FormElement.noValidate ||
((submit.submitter instanceof HTMLButtonElement ||
submit.submitter instanceof HTMLInputElement) &&
submit.submitter.formNoValidate));
let validation = undefined;
const validateForm = async () => {
return await Form_validate({ adapter: validationAdapter });
};
if (!noValidate) {
validation = await validateForm();
if (!validation.valid) {
cancel({ resetTimers: false });
clientValidationResult(validation);
}
}
if (!cancelled) {
switch (options.clearOnSubmit) {
case 'errors-and-message':
Errors.clear();
Message.set(undefined);
break;
case 'errors':
Errors.clear();
break;
case 'message':
Message.set(undefined);
break;
}
if (options.flashMessage &&
(options.clearOnSubmit == 'errors-and-message' || options.clearOnSubmit == 'message') &&
shouldSyncFlash(options)) {
options.flashMessage.module.getFlash(page).set(undefined);
}
// Deprecation fix
const submitData = 'formData' in submit ? submit.formData : submit.data;
// Prevent input/blur events to trigger client-side validation,
// and accidentally removing errors set by setError
lastInputChange = undefined;
if (Form_isSPA()) {
if (!validation)
validation = await validateForm();
cancel({ resetTimers: false });
clientValidationResult(validation);
}
else if (options.dataType === 'json') {
if (!validation)
validation = await validateForm();
const postData = clone(jsonData ?? validation.data);
// Move files to form data, since they cannot be serialized.
// Will be reassembled in superValidate.
traversePaths(postData, (data) => {
if (data.value instanceof File) {
const key = '__superform_file_' + mergePath(data.path);
submitData.append(key, data.value);
return data.set(undefined);
}
else if (Array.isArray(data.value) &&
data.value.length &&
data.value.every((v) => v instanceof File)) {
const key = '__superform_files_' + mergePath(data.path);
for (const file of data.value) {
submitData.append(key, file);
}
return data.set(undefined);
}
});
// Clear post data to reduce transfer size,
// since $form should be serialized and sent as json.
Object.keys(postData).forEach((key) => {
// Files should be kept though, even if same key.
if (typeof submitData.get(key) === 'string') {
submitData.delete(key);
}
});
// Split the form data into chunks, in case it gets too large for proxy servers
const chunks = chunkSubstr(stringify(postData), options.jsonChunkSize ?? 500000);
for (const chunk of chunks) {
submitData.append('__superform_json', chunk);
}
}
if (!submitData.has('__superform_id')) {
// Add formId
const id = Data.formId;
if (id !== undefined)
submitData.set('__superform_id', id);
}
if (typeof options.SPA === 'string') {
ActionForm_setAction(options.SPA);
}
}
}
///// End of submit interaction ///////////////////////////////////////
// Thanks to https://stackoverflow.com/a/29202760/70894
function chunkSubstr(str, size) {
const numChunks = Math.ceil(str.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = str.substring(o, o + size);
}
return chunks;
}
async function validationResponse(event) {
let cancelled = false;
currentRequest = null;
// Check if an error was thrown in hooks, in which case it has no type.
let result = event.result.type
? event.result
: {
type: 'error',
status: 500,
error: event.result
};
const cancel = () => (cancelled = true);
const data = {
result,
formEl: FormElement,
formElement: FormElement,
cancel
};
const unsubCheckforNav = STORYBOOK_MODE || !Form_isSPA()
? () => { }
: navigating.subscribe(($nav) => {
// Check for goto to a different route in the events
if (!$nav || $nav.from?.route.id === $nav.to?.route.id)
return;
cancel();
});
for (const event of formEvents.onResult) {
await event(data);
}
// In case it was modified in the event
result = data.result;
if (!cancelled) {
if ((result.type === 'success' || result.type == 'failure') && result.data) {
const forms = Context_findValidationForms(result.data);
if (!forms.length) {
throw new SuperFormError('No form data returned from ActionResult. Make sure you return { form } in the form actions.');
}
for (const newForm of forms) {
if (newForm.id !== Data.formId)
continue;
const data = {
form: newForm,
formEl: FormElement,
formElement: FormElement,
cancel: () => (cancelled = true)
};
for (const event of formEvents.onUpdate) {
await event(data);
}
if (!cancelled) {
if (options.customValidity) {
setCustomValidityForm(FormElement, data.form.errors);
}
// Special reset case for file inputs
if (Form_shouldReset(data.form.valid, result.type == 'success')) {
data.formElement
.querySelectorAll('input[type="file"]')
.forEach((e) => (e.value = ''));
}
}
}
}
if (!cancelled) {
if (result.type !== 'error') {
if (result.type === 'success' && options.invalidateAll) {
await invalidateAll();
}
if (options.applyAction) {
// This will trigger the page subscription in superForm,
// which will in turn call Data_update.
await applyAction(result);
}
else {
// Call Data_update directly to trigger events
await Form_updateFromActionResult(result);
}
}
else {
// Error result
if (options.applyAction) {
if (options.onError == 'apply') {
await applyAction(result);
}
else {
// Transform to failure, to avoid data loss
// Set the data to the error result, so it will be
// picked up in page.subscribe in superForm.
const failResult = {
type: 'failure',
status: Math.floor(result.status || 500),
data: result
};
await applyAction(failResult);
}
}
// Check if the error message should be replaced
if (options.onError !== 'apply') {
const data = { result, message: Message };
for (const onErrorEvent of formEvents.onError) {
if (onErrorEvent !== 'apply' &&
(onErrorEvent != defaultOnError || !options.flashMessage?.onError)) {
await onErrorEvent(data);
}
}
}
}
// Trigger flash message event if there was an error
if (options.flashMessage) {
if (result.type == 'error' && options.flashMessage.onError) {
await options.flashMessage.onError({
result,
flashMessage: options.flashMessage.module.getFlash(page)
});
}
}
}
}
if (cancelled && options.flashMessage) {
cancelFlash(options);
}
// Redirect messages are handled in onDestroy and afterNavigate in client/form.ts.
if (cancelled || result.type != 'redirect') {
htmlForm.completed({ cancelled });
}
else if (STORYBOOK_MODE) {
htmlForm.completed({ cancelled, clearAll: true });
}
else {
const unsub = navigating.subscribe(($nav) => {
if ($nav)
return;
// Timeout required when applyAction is false
setTimeout(() => {
try {
if (unsub)
unsub();
}
catch {
// If component is already destroyed?
}
});
if (htmlForm.isSubmitting()) {
htmlForm.completed({ cancelled, clearAll: true });
}
});
}
unsubCheckforNav();
}
return validationResponse;
});
}
///// Return the SuperForm object /////////////////////////////////

@@ -997,377 +1429,4 @@ return {

isTainted: Tainted_isTainted,
///// Custom use:enhance ////////////////////////////////////////
// @DCI-context
enhance(FormElement, events) {
EnhancedForm = FormElement;
if (events) {
if (events.onError) {
if (options.onError === 'apply') {
throw new SuperFormError('options.onError is set to "apply", cannot add any onError events.');
}
else if (events.onError === 'apply') {
throw new SuperFormError('Cannot add "apply" as onError event in use:enhance.');
}
formEvents.onError.push(events.onError);
}
if (events.onResult)
formEvents.onResult.push(events.onResult);
if (events.onSubmit)
formEvents.onSubmit.push(events.onSubmit);
if (events.onUpdate)
formEvents.onUpdate.push(events.onUpdate);
if (events.onUpdated)
formEvents.onUpdated.push(events.onUpdated);
}
// Now we know that we are enhanced,
// so we can enable the tainted form option.
Tainted_enable();
let lastInputChange;
// TODO: Debounce option?
async function onInput(e) {
const info = inputInfo(e.target);
// Need to wait for immediate updates due to some timing issue
if (info.immediate && !info.file)
await new Promise((r) => setTimeout(r, 0));
lastInputChange = NextChange_paths();
NextChange_additionalEventInformation('input', info.immediate, info.multiple, FormElement, e.target ?? undefined);
}
async function onBlur(e) {
// Avoid triggering client-side validation while submitting
if (Data.submitting)
return;
if (!lastInputChange || NextChange_paths() != lastInputChange) {
return;
}
const info = inputInfo(e.target);
// Need to wait for immediate updates due to some timing issue
if (info.immediate && !info.file)
await new Promise((r) => setTimeout(r, 0));
Form_clientValidation({
paths: lastInputChange,
immediate: info.multiple,
multiple: info.multiple,
type: 'blur',
formElement: FormElement,
target: e.target ?? undefined
});
// Clear input change event, now that the field doesn't have focus anymore.
lastInputChange = undefined;
}
FormElement.addEventListener('focusout', onBlur);
FormElement.addEventListener('input', onInput);
onDestroy(() => {
FormElement.removeEventListener('focusout', onBlur);
FormElement.removeEventListener('input', onInput);
EnhancedForm = undefined;
});
///// SvelteKit enhance function //////////////////////////////////
const htmlForm = HtmlForm(FormElement, { submitting: Submitting, delayed: Delayed, timeout: Timeout }, options);
let currentRequest;
return enhance(FormElement, async (submitParams) => {
let jsonData = undefined;
let validationAdapter = options.validators;
const submit = {
...submitParams,
jsonData(data) {
if (options.dataType !== 'json') {
throw new SuperFormError("options.dataType must be set to 'json' to use jsonData.");
}
jsonData = data;
},
validators(adapter) {
validationAdapter = adapter;
}
};
const _submitCancel = submit.cancel;
let cancelled = false;
function clientValidationResult(validation) {
const validationResult = { ...validation, posted: true };
const status = validationResult.valid
? 200
: (typeof options.SPA === 'boolean' ? undefined : options.SPA?.failStatus) ?? 400;
const data = { form: validationResult };
const result = validationResult.valid
? { type: 'success', status, data }
: { type: 'failure', status, data };
setTimeout(() => validationResponse({ result }), 0);
}
function cancel(opts = {
resetTimers: true
}) {
cancelled = true;
if (opts.resetTimers && htmlForm.isSubmitting()) {
htmlForm.completed({ cancelled });
}
return _submitCancel();
}
submit.cancel = cancel;
if (htmlForm.isSubmitting() && options.multipleSubmits == 'prevent') {
cancel({ resetTimers: false });
}
else {
if (htmlForm.isSubmitting() && options.multipleSubmits == 'abort') {
if (currentRequest)
currentRequest.abort();
}
htmlForm.submitting();
currentRequest = submit.controller;
for (const event of formEvents.onSubmit) {
await event(submit);
}
}
if (cancelled && options.flashMessage)
cancelFlash(options);
if (!cancelled) {
// Client validation
const noValidate = !options.SPA &&
(FormElement.noValidate ||
((submit.submitter instanceof HTMLButtonElement ||
submit.submitter instanceof HTMLInputElement) &&
submit.submitter.formNoValidate));
let validation = undefined;
const validateForm = async () => {
return await Form_validate({ adapter: validationAdapter });
};
if (!noValidate) {
validation = await validateForm();
if (!validation.valid) {
cancel({ resetTimers: false });
clientValidationResult(validation);
}
}
if (!cancelled) {
switch (options.clearOnSubmit) {
case 'errors-and-message':
Errors.clear();
Message.set(undefined);
break;
case 'errors':
Errors.clear();
break;
case 'message':
Message.set(undefined);
break;
}
if (options.flashMessage &&
(options.clearOnSubmit == 'errors-and-message' ||
options.clearOnSubmit == 'message') &&
shouldSyncFlash(options)) {
options.flashMessage.module.getFlash(page).set(undefined);
}
// Deprecation fix
const submitData = 'formData' in submit ? submit.formData : submit.data;
// Prevent input/blur events to trigger client-side validation,
// and accidentally removing errors set by setError
lastInputChange = undefined;
if (options.SPA) {
if (!validation)
validation = await validateForm();
cancel({ resetTimers: false });
clientValidationResult(validation);
}
else if (options.dataType === 'json') {
if (!validation)
validation = await validateForm();
const postData = clone(jsonData ?? validation.data);
// Move files to form data, since they cannot be serialized.
// Will be reassembled in superValidate.
traversePaths(postData, (data) => {
if (data.value instanceof File) {
const key = '__superform_file_' + mergePath(data.path);
submitData.append(key, data.value);
return data.set(undefined);
}
else if (Array.isArray(data.value) &&
data.value.length &&
data.value.every((v) => v instanceof File)) {
const key = '__superform_files_' + mergePath(data.path);
for (const file of data.value) {
submitData.append(key, file);
}
return data.set(undefined);
}
});
// Clear post data to reduce transfer size,
// since $form should be serialized and sent as json.
Object.keys(postData).forEach((key) => {
// Files should be kept though, even if same key.
if (typeof submitData.get(key) === 'string') {
submitData.delete(key);
}
});
// Split the form data into chunks, in case it gets too large for proxy servers
const chunks = chunkSubstr(stringify(postData), options.jsonChunkSize ?? 500000);
for (const chunk of chunks) {
submitData.append('__superform_json', chunk);
}
}
if (!options.SPA && !submitData.has('__superform_id')) {
// Add formId
const id = Data.formId;
if (id !== undefined)
submitData.set('__superform_id', id);
}
}
}
// Thanks to https://stackoverflow.com/a/29202760/70894
function chunkSubstr(str, size) {
const numChunks = Math.ceil(str.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = str.substring(o, o + size);
}
return chunks;
}
async function validationResponse(event) {
let cancelled = false;
currentRequest = null;
// Check if an error was thrown in hooks, in which case it has no type.
let result = event.result.type
? event.result
: {
type: 'error',
status: 500,
error: event.result
};
const cancel = () => (cancelled = true);
const data = {
result,
formEl: FormElement,
formElement: FormElement,
cancel
};
const unsubCheckforNav = STORYBOOK_MODE
? () => { }
: navigating.subscribe(($nav) => {
// Check for goto to a different route in the events
if (!$nav || $nav.from?.route.id === $nav.to?.route.id)
return;
cancel();
});
for (const event of formEvents.onResult) {
await event(data);
}
// In case it was modified in the event
result = data.result;
if (!cancelled) {
if ((result.type === 'success' || result.type == 'failure') && result.data) {
const forms = Context_findValidationForms(result.data);
if (!forms.length) {
throw new SuperFormError('No form data returned from ActionResult. Make sure you return { form } in the form actions.');
}
for (const newForm of forms) {
if (newForm.id !== Data.formId)
continue;
const data = {
form: newForm,
formEl: FormElement,
formElement: FormElement,
cancel: () => (cancelled = true)
};
for (const event of formEvents.onUpdate) {
await event(data);
}
if (!cancelled) {
if (options.customValidity) {
setCustomValidityForm(FormElement, data.form.errors);
}
// Special reset case for file inputs
if (Form_shouldReset(data.form.valid, result.type == 'success')) {
data.formElement
.querySelectorAll('input[type="file"]')
.forEach((e) => (e.value = ''));
}
}
}
}
if (!cancelled) {
if (result.type !== 'error') {
if (result.type === 'success' && options.invalidateAll) {
await invalidateAll();
}
if (options.applyAction) {
// This will trigger the page subscription in superForm,
// which will in turn call Data_update.
await applyAction(result);
}
else {
// Call Data_update directly to trigger events
await Form_updateFromActionResult(result);
}
}
else {
// Error result
if (options.applyAction) {
if (options.onError == 'apply') {
await applyAction(result);
}
else {
// Transform to failure, to avoid data loss
// Set the data to the error result, so it will be
// picked up in page.subscribe in superForm.
const failResult = {
type: 'failure',
status: Math.floor(result.status || 500),
data: result
};
await applyAction(failResult);
}
}
// Check if the error message should be replaced
if (options.onError !== 'apply') {
const data = { result, message: Message };
for (const onErrorEvent of formEvents.onError) {
if (onErrorEvent !== 'apply' &&
(onErrorEvent != defaultOnError || !options.flashMessage?.onError)) {
await onErrorEvent(data);
}
}
}
}
// Trigger flash message event if there was an error
if (options.flashMessage) {
if (result.type == 'error' && options.flashMessage.onError) {
await options.flashMessage.onError({
result,
flashMessage: options.flashMessage.module.getFlash(page)
});
}
}
}
}
if (cancelled && options.flashMessage) {
cancelFlash(options);
}
// Redirect messages are handled in onDestroy and afterNavigate in client/form.ts.
if (cancelled || result.type != 'redirect') {
htmlForm.completed({ cancelled });
}
else if (STORYBOOK_MODE) {
htmlForm.completed({ cancelled, clearAll: true });
}
else {
const unsub = navigating.subscribe(($nav) => {
if ($nav)
return;
// Timeout required when applyAction is false
setTimeout(() => {
try {
if (unsub)
unsub();
}
catch {
// If component is already destroyed?
}
});
if (htmlForm.isSubmitting()) {
htmlForm.completed({ cancelled, clearAll: true });
}
});
}
unsubCheckforNav();
}
return validationResponse;
});
}
enhance: superFormEnhance
};
}
import { schemaInfo } from './schemaInfo.js';
import { merge as deepMerge } from 'ts-deepmerge';
export function constraints(schema) {
return _constraints(schemaInfo(schema, false, []), []);
}
function merge(constraints) {
let output = {};
for (const constraint of constraints) {
if (!constraint)
continue;
output = { ...output, ...constraint };
}
return output;
function merge(...constraints) {
const filtered = constraints.filter((c) => !!c);
if (!filtered.length)
return undefined;
if (filtered.length == 1)
return filtered[0];
return deepMerge(...filtered);
}

@@ -17,7 +17,8 @@ function _constraints(info, path) {

return undefined;
let output = undefined;
// Union
if (info.union) {
if (info.union && info.union.length) {
const infos = info.union.map((s) => schemaInfo(s, info.isOptional, path));
const merged = infos.map((i) => _constraints(i, path));
const output = merge(merged);
output = merge(output, ...merged);
// Delete required if any part of the union is optional

@@ -28,15 +29,10 @@ if (output &&

}
return output && Object.values(output).length ? output : undefined;
}
// Arrays
if (info.array) {
if (info.array.length == 1) {
//console.log('Array constraint', schema, path);
return _constraints(schemaInfo(info.array[0], info.isOptional, path), path);
}
return merge(info.array.map((i) => _constraints(schemaInfo(i, info.isOptional, path), path)));
output = merge(output, ...info.array.map((i) => _constraints(schemaInfo(i, info.isOptional, path), path)));
}
// Objects
if (info.properties) {
const output = {};
const obj = {};
for (const [key, prop] of Object.entries(info.properties)) {

@@ -46,8 +42,8 @@ const propInfo = schemaInfo(prop, !info.required?.includes(key) || prop.default !== undefined, [key]);

if (typeof propConstraint === 'object' && Object.values(propConstraint).length > 0) {
output[key] = propConstraint;
obj[key] = propConstraint;
}
}
return output;
output = merge(output, obj);
}
return constraint(info);
return output ?? constraint(info);
}

@@ -54,0 +50,0 @@ function constraint(info) {

@@ -11,1 +11,2 @@ import type { JSONSchema } from './index.js';

export declare function schemaShape(schema: JSONSchema, path?: string[]): SchemaShape;
export declare function shapeFromObject(obj: object): SchemaShape;

@@ -34,1 +34,14 @@ import { SchemaError } from '../errors.js';

}
export function shapeFromObject(obj) {
let output = {};
const isArray = Array.isArray(obj);
for (const [key, value] of Object.entries(obj)) {
if (!value || typeof value !== 'object')
continue;
if (isArray)
output = { ...output, ...shapeFromObject(value) };
else
output[key] = shapeFromObject(value);
}
return output;
}

@@ -1,12 +0,5 @@

import type { IsAny } from './utils.js';
import type { AllKeys, IsAny, MergeUnion } from './utils.js';
export declare function splitPath(path: string): string[];
export declare function mergePath(path: (string | number | symbol)[]): string;
type BuiltInObjects = Date | Set<unknown> | File;
export type AllKeys<T> = T extends T ? keyof T : never;
export type PickType<T, K extends AllKeys<T>> = T extends {
[k in K]: any;
} ? T[K] : never;
export type MergeUnion<T> = {
[K in AllKeys<T>]: PickType<T, K>;
};
/**

@@ -13,0 +6,0 @@ * Lists all paths in an object as string accessors.

@@ -0,6 +1,7 @@

import type { AllKeys, MergeUnion } from './utils.js';
export type SuperStructArray<T extends Record<string, unknown>, Data, ArrayData = unknown> = {
[Property in keyof T]?: T extends any ? NonNullable<T[Property]> extends Record<string, unknown> ? SuperStructArray<NonNullable<T[Property]>, Data, ArrayData> : NonNullable<T[Property]> extends (infer A)[] ? ArrayData & Record<number | string, NonNullable<A> extends Record<string, unknown> ? SuperStructArray<NonNullable<A>, Data, ArrayData> : Data> : Data : never;
[Property in AllKeys<T>]?: [T] extends [any] ? NonNullable<T[Property]> extends Record<string, unknown> ? SuperStructArray<MergeUnion<NonNullable<T[Property]>>, Data, ArrayData> : NonNullable<T[Property]> extends (infer A)[] ? ArrayData & Record<number | string, NonNullable<A> extends Record<string, unknown> ? SuperStructArray<MergeUnion<NonNullable<A>>, Data, ArrayData> : Data> : Data : never;
};
export type SuperStruct<T extends Record<string, unknown>, Data> = Partial<{
[Property in keyof T]: T extends any ? NonNullable<T[Property]> extends Record<string, unknown> ? SuperStruct<NonNullable<T[Property]>, Data> : NonNullable<T[Property]> extends (infer A)[] ? NonNullable<A> extends Record<string, unknown> ? SuperStruct<NonNullable<A>, Data> : Data : Data : never;
[Property in AllKeys<T>]: [T] extends [any] ? NonNullable<T[Property]> extends Record<string, unknown> ? SuperStruct<MergeUnion<NonNullable<T[Property]>>, Data> : NonNullable<T[Property]> extends (infer A)[] ? NonNullable<A> extends Record<string, unknown> ? SuperStruct<MergeUnion<NonNullable<A>>, Data> : Data : Data : never;
}>;

@@ -12,1 +12,9 @@ import type { JSONSchema7Definition } from 'json-schema';

export declare function assertSchema(schema: JSONSchema7Definition, path: string | (string | number | symbol)[]): asserts schema is JSONSchema;
export type AllKeys<T> = T extends T ? keyof T : never;
type PickType<T, K extends AllKeys<T>> = T extends {
[k in K]: any;
} ? T[K] : never;
export type MergeUnion<T> = {
[K in AllKeys<T>]: PickType<T, K>;
};
export {};
{
"name": "sveltekit-superforms",
"version": "2.8.1",
"version": "2.9.0",
"author": "Andreas Söderlund <ciscoheat@gmail.com> (https://blog.encodeart.dev)",

@@ -127,3 +127,3 @@ "description": "Making SvelteKit forms a pleasure to use!",

"valibot": "^0.29.0",
"yup": "^1.3.3",
"yup": "^1.4.0",
"zod": "^3.22.4",

@@ -144,3 +144,3 @@ "zod-to-json-schema": "^3.22.4"

"@types/json-schema": "^7.0.15",
"@types/node": "^20.11.24",
"@types/node": "^20.11.25",
"@types/throttle-debounce": "^5.0.2",

@@ -152,3 +152,3 @@ "@types/uuid": "^9.0.8",

"eslint-config-prettier": "^9.1.0",
"eslint-plugin-dci-lint": "^0.3.1",
"eslint-plugin-dci-lint": "^0.3.2",
"eslint-plugin-svelte": "2.36.0-next.8",

@@ -163,7 +163,8 @@ "json-schema-to-ts": "^3.0.0",

"svelte-check": "^3.6.6",
"sveltekit-flash-message": "^2.4.2",
"svelte-french-toast": "^1.2.0",
"sveltekit-flash-message": "^2.4.4",
"sveltekit-rate-limiter": "^0.4.3",
"throttle-debounce": "^5.0.0",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"typescript": "^5.4.2",
"uuid": "^9.0.1",

@@ -170,0 +171,0 @@ "vite": "^5.1.5",

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc